128 lines
4.6 KiB
TypeScript
128 lines
4.6 KiB
TypeScript
import { describe, expect, test } from "bun:test";
|
|
import { createRequire } from "node:module";
|
|
import { existsSync } from "node:fs";
|
|
import { SubprocessTransport } from "./transport.js";
|
|
|
|
describe("CLI resolution", () => {
|
|
test("resolves @letta-ai/letta-code via package main export", () => {
|
|
// This is the resolution strategy used by findCli().
|
|
// Previously we resolved "@letta-ai/letta-code/letta.js" which fails
|
|
// with ERR_PACKAGE_PATH_NOT_EXPORTED because the subpath isn't in the
|
|
// package.json exports field. The main export "." maps to "./letta.js".
|
|
const require = createRequire(import.meta.url);
|
|
const resolved = require.resolve("@letta-ai/letta-code");
|
|
expect(resolved).toBeDefined();
|
|
expect(resolved.endsWith("letta.js")).toBe(true);
|
|
expect(existsSync(resolved)).toBe(true);
|
|
});
|
|
|
|
test("subpath resolution fails without explicit export", () => {
|
|
// This documents why we can't use the subpath directly.
|
|
const require = createRequire(import.meta.url);
|
|
expect(() => {
|
|
require.resolve("@letta-ai/letta-code/letta.js");
|
|
}).toThrow();
|
|
});
|
|
});
|
|
|
|
describe("transport args", () => {
|
|
function buildArgsFor(options: {
|
|
permissionMode?: "default" | "acceptEdits" | "plan" | "bypassPermissions";
|
|
allowedTools?: string[];
|
|
disallowedTools?: string[];
|
|
memfs?: boolean;
|
|
skillSources?: Array<"bundled" | "global" | "agent" | "project">;
|
|
systemInfoReminder?: boolean;
|
|
sleeptime?: {
|
|
trigger?: "off" | "step-count" | "compaction-event";
|
|
behavior?: "reminder" | "auto-launch";
|
|
stepCount?: number;
|
|
};
|
|
} = {}): string[] {
|
|
const transport = new SubprocessTransport(options);
|
|
// Access private helper for deterministic argument testing.
|
|
// biome-ignore lint/suspicious/noExplicitAny: test access to private method
|
|
return (transport as any).buildArgs();
|
|
}
|
|
|
|
test("acceptEdits uses --permission-mode acceptEdits", () => {
|
|
const args = buildArgsFor({ permissionMode: "acceptEdits" });
|
|
expect(args).toContain("--permission-mode");
|
|
expect(args).toContain("acceptEdits");
|
|
expect(args).not.toContain("--accept-edits");
|
|
});
|
|
|
|
test("plan mode uses --permission-mode plan", () => {
|
|
const args = buildArgsFor({ permissionMode: "plan" });
|
|
expect(args).toContain("--permission-mode");
|
|
expect(args).toContain("plan");
|
|
});
|
|
|
|
test("bypassPermissions still uses --yolo alias", () => {
|
|
const args = buildArgsFor({ permissionMode: "bypassPermissions" });
|
|
expect(args).toContain("--yolo");
|
|
expect(args).not.toContain("--permission-mode");
|
|
});
|
|
|
|
test("allowedTools and disallowedTools are forwarded to CLI flags", () => {
|
|
const args = buildArgsFor({
|
|
allowedTools: ["Read", "Bash"],
|
|
disallowedTools: ["EnterPlanMode", "ExitPlanMode"],
|
|
});
|
|
expect(args).toContain("--allowedTools");
|
|
expect(args).toContain("Read,Bash");
|
|
expect(args).toContain("--disallowedTools");
|
|
expect(args).toContain("EnterPlanMode,ExitPlanMode");
|
|
});
|
|
|
|
test("memfs true forwards --memfs", () => {
|
|
const args = buildArgsFor({ memfs: true });
|
|
expect(args).toContain("--memfs");
|
|
});
|
|
|
|
test("memfs false forwards --no-memfs", () => {
|
|
const args = buildArgsFor({ memfs: false });
|
|
expect(args).toContain("--no-memfs");
|
|
expect(args).not.toContain("--memfs");
|
|
});
|
|
|
|
test("memfs undefined does not forward memfs flags", () => {
|
|
expect(buildArgsFor({})).not.toContain("--memfs");
|
|
expect(buildArgsFor({})).not.toContain("--no-memfs");
|
|
});
|
|
|
|
test("empty skillSources forwards --no-skills", () => {
|
|
const args = buildArgsFor({ skillSources: [] });
|
|
expect(args).toContain("--no-skills");
|
|
expect(args).not.toContain("--skill-sources");
|
|
});
|
|
|
|
test("skillSources list forwards --skill-sources csv", () => {
|
|
const args = buildArgsFor({ skillSources: ["project", "global"] });
|
|
expect(args).toContain("--skill-sources");
|
|
expect(args).toContain("project,global");
|
|
expect(args).not.toContain("--no-skills");
|
|
});
|
|
|
|
test("systemInfoReminder false forwards --no-system-info-reminder", () => {
|
|
const args = buildArgsFor({ systemInfoReminder: false });
|
|
expect(args).toContain("--no-system-info-reminder");
|
|
});
|
|
|
|
test("sleeptime options forward reflection flags", () => {
|
|
const args = buildArgsFor({
|
|
sleeptime: {
|
|
trigger: "step-count",
|
|
behavior: "reminder",
|
|
stepCount: 12,
|
|
},
|
|
});
|
|
expect(args).toContain("--reflection-trigger");
|
|
expect(args).toContain("step-count");
|
|
expect(args).toContain("--reflection-behavior");
|
|
expect(args).toContain("reminder");
|
|
expect(args).toContain("--reflection-step-count");
|
|
expect(args).toContain("12");
|
|
});
|
|
});
|