Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 11 additions & 5 deletions src/supervisor/agents/commandcode/argv.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,24 @@ import type { ThreadConfig } from "@/shared/contracts";
* always pin an explicit `--permission-mode <standard|auto-accept|plan>` —
* except when the user picks Bypass Permissions, where we intentionally omit
* it so `--yolo` selects bypass as the starting mode.
* - `command-code` has no flag to pre-assign a session id, so resume uses
* `--continue` (continue the last conversation in this cwd) rather than a
* tracked id. The initial prompt is passed as a trailing positional arg.
* - `command-code` has no flag to pre-assign or report a session id, but it
* does support `--resume <id>` (load that exact conversation). We discover
* the real id from `~/.commandcode/projects/<cwd>/<id>.jsonl` (see
* sessionFiles.ts) and pass it here. `resumeSessionId` is: `undefined` for a
* fresh launch (no resume flag), a non-empty id for `--resume <id>`, or `""`
* for the `--continue` fallback when no real id is known. The initial prompt
* is passed as a trailing positional arg.
*/
export function buildCommandCodeArgs(
config: ThreadConfig,
prompt: string,
resume?: boolean,
resumeSessionId?: string,
): string[] {
const args: string[] = ["--trust", "--skip-onboarding"];

if (resume) {
if (resumeSessionId) {
args.push("--resume", resumeSessionId);
} else if (resumeSessionId === "") {
args.push("--continue");
}
if (config.model) {
Expand Down
132 changes: 112 additions & 20 deletions src/supervisor/agents/commandcode/commandcode.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
parseCommandCodeModels,
} from "./detection";
import { authJsonHasApiKey, detectCommandCodeInvalidSessionRef } from "./session";
import { commandCodeTranscriptId, isUuid, sanitizeCommandCodeCwd } from "./sessionFiles";
import { detectCommandCodeTerminalStatus } from "./terminal";

describe("buildCommandCodeArgs", () => {
Expand All @@ -31,8 +32,23 @@ describe("buildCommandCodeArgs", () => {
]);
});

it("adds --continue when resuming", () => {
expect(buildCommandCodeArgs(config, "next", true)).toEqual([
it("resumes a specific session with --resume <id>", () => {
expect(buildCommandCodeArgs(config, "next", "af75c40e-44dd-4369-a187-571745a01df2")).toEqual([
"--trust",
"--skip-onboarding",
"--resume",
"af75c40e-44dd-4369-a187-571745a01df2",
"--model",
"claude-opus-4-8",
"--permission-mode",
"standard",
"--yolo",
"next",
]);
});

it("falls back to --continue when the resume id is the empty string", () => {
expect(buildCommandCodeArgs(config, "next", "")).toEqual([
"--trust",
"--skip-onboarding",
"--continue",
Expand All @@ -45,6 +61,12 @@ describe("buildCommandCodeArgs", () => {
]);
});

it("adds no resume flag for a fresh launch", () => {
const args = buildCommandCodeArgs(config, "next");
expect(args).not.toContain("--resume");
expect(args).not.toContain("--continue");
});

it("omits the prompt positional when empty", () => {
expect(buildCommandCodeArgs(config, " ")).toEqual([
"--trust",
Expand Down Expand Up @@ -114,7 +136,9 @@ describe("createCommandCodeAdapter", () => {
expect(adapter.update?.npm).toBe("command-code");
});

it("mints a synthetic sessionRef on launch so the thread is resumable", () => {
it("launches without a sessionRef so the runtime discovers the real id", () => {
// The synthetic ref is gone: returning no ref is what lets the runtime's
// discoverSessionRef path run and capture command-code's real session id.
const adapter = createCommandCodeAdapter();
const launch = adapter.buildLaunchArgv(project, { model: "gpt-5.5" }, "hi");

Expand All @@ -129,31 +153,35 @@ describe("createCommandCodeAdapter", () => {
"--yolo",
"hi",
]);
expect(launch.sessionRef?.providerSessionId).toEqual(expect.any(String));
expect(launch.sessionRef?.providerSessionId.length).toBeGreaterThan(0);
expect(launch.sessionRef).toBeUndefined();
expect(adapter.discoverSessionRef).toBeTypeOf("function");
expect(adapter.watchSessionRef).toBeTypeOf("function");
});

it("resumes with --continue and ignores the synthetic session id", () => {
it("resumes a discovered session id with --resume", () => {
const adapter = createCommandCodeAdapter();
const resume = adapter.buildResumeArgv(project, { model: "gpt-5.5" }, "next", {
providerSessionId: "synthetic-id",
providerSessionId: "af75c40e-44dd-4369-a187-571745a01df2",
discoveredAt: "2026-05-20T00:00:00.000Z",
});

expect(resume).toMatchObject({
binary: "command-code",
args: [
"--trust",
"--skip-onboarding",
"--continue",
"--model",
"gpt-5.5",
"--permission-mode",
"standard",
"--yolo",
"next",
],
expect(resume.args).toContain("--resume");
expect(resume.args).toContain("af75c40e-44dd-4369-a187-571745a01df2");
expect(resume.args).not.toContain("--continue");
});

it("falls back to --continue for a non-uuid (legacy/synthetic) ref", () => {
// A non-UUID ref can't be a real command-code session id, so resume can't
// target one — it uses --continue and never passes the bogus id. A stale
// *uuid* ref instead goes through --resume and the runtime recovers it.
const adapter = createCommandCodeAdapter();
const resume = adapter.buildResumeArgv(project, { model: "gpt-5.5" }, "next", {
providerSessionId: "synthetic-id",
discoveredAt: "2026-05-20T00:00:00.000Z",
});

expect(resume.args).toContain("--continue");
expect(resume.args).not.toContain("--resume");
expect(resume.args).not.toContain("synthetic-id");
});

Expand Down Expand Up @@ -354,9 +382,73 @@ describe("detectCommandCodeInvalidSessionRef", () => {
it("detects empty-continue messages", () => {
expect(detectCommandCodeInvalidSessionRef("No previous conversation found")).toBe(true);
expect(detectCommandCodeInvalidSessionRef("Nothing to continue")).toBe(true);
expect(detectCommandCodeInvalidSessionRef("No conversations found to resume.")).toBe(true);
});

it("detects a missing --resume <id> target and a corrupt transcript", () => {
expect(
detectCommandCodeInvalidSessionRef(
'No session "deadbeef-0000-4000-8000-000000000000" found to resume.',
),
).toBe(true);
expect(
detectCommandCodeInvalidSessionRef(
"Session could not be loaded. 1 lines could not be parsed.",
),
).toBe(true);
});

it("ignores unrelated output", () => {
expect(detectCommandCodeInvalidSessionRef("Command Code ready")).toBe(false);
});
});

describe("commandCodeTranscriptId", () => {
const uuid = "af75c40e-44dd-4369-a187-571745a01df2";

it("accepts a real <uuid>.jsonl transcript", () => {
expect(commandCodeTranscriptId(`${uuid}.jsonl`)).toBe(uuid);
});

it("rejects every sidecar and audit file (the corruption guard)", () => {
// Passing any of these basenames to `--resume` is what yields
// "Session could not be loaded. N lines could not be parsed."
expect(commandCodeTranscriptId(`hooks-audit-${uuid}.jsonl`)).toBeUndefined();
expect(commandCodeTranscriptId(`hooks-audit-hooks-audit-${uuid}.jsonl`)).toBeUndefined();
expect(commandCodeTranscriptId(`hooks-audit-${uuid}.checkpoints.jsonl`)).toBeUndefined();
expect(commandCodeTranscriptId(`${uuid}.checkpoints.jsonl`)).toBeUndefined();
expect(commandCodeTranscriptId(`${uuid}.prompts.jsonl`)).toBeUndefined();
expect(commandCodeTranscriptId(`${uuid}.meta.json`)).toBeUndefined();
expect(commandCodeTranscriptId(`${uuid}.share.json`)).toBeUndefined();
expect(commandCodeTranscriptId("settings.json")).toBeUndefined();
expect(commandCodeTranscriptId("not-a-uuid.jsonl")).toBeUndefined();
});

it("validates the uuid shape", () => {
expect(isUuid(uuid)).toBe(true);
expect(isUuid("hooks-audit-" + uuid)).toBe(false);
expect(isUuid("synthetic-id")).toBe(false);
});
});

describe("sanitizeCommandCodeCwd", () => {
// These fixture paths don't exist on the test machine, so realpath throws and
// the raw path is sanitized — deterministic, and matching the verified
// on-disk layout (lowercase, leading slash dropped, non-alphanumeric -> '-').
it("maps a project cwd to command-code's projects/<dir> name", () => {
expect(sanitizeCommandCodeCwd("/Users/test-fixture-xyz/work/lightcode")).toBe(
"users-test-fixture-xyz-work-lightcode",
);
});

it("lowercases and collapses dots and slashes (worktree + temp paths)", () => {
expect(
sanitizeCommandCodeCwd(
"/Users/test-fixture-xyz/.lightcode/worktrees/lc-bbea/lc-golden-pixel-8f39b4b5",
),
).toBe("users-test-fixture-xyz-lightcode-worktrees-lc-bbea-lc-golden-pixel-8f39b4b5");
expect(sanitizeCommandCodeCwd("/private/var/T/cc-dbg-ca.ppww")).toBe(
"private-var-t-cc-dbg-ca-ppww",
);
});
});
37 changes: 29 additions & 8 deletions src/supervisor/agents/commandcode/index.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
import type { AgentCapability, PromptSegment } from "@/shared/contracts";
import { createKnownSessionRef, detectAgentInstall, type AgentAdapter } from "../base";
import { detectAgentInstall, type AgentAdapter } from "../base";
import { buildCommandCodeArgs } from "./argv";
import {
COMMANDCODE_DEFAULT_MODEL_ID,
commandCodeDetectionSpec,
defaultCommandCodeCapabilities,
} from "./detection";
import { detectCommandCodeInvalidSessionRef } from "./session";
import {
isUuid,
makeCommandCodeDiscoverSessionRef,
makeCommandCodeWatchSessionRef,
snapshotCommandCodePreSpawnSessions,
} from "./sessionFiles";
import { detectCommandCodeTerminalStatus } from "./terminal";

export { detectCommandCodeInvalidSessionRef } from "./session";
Expand Down Expand Up @@ -44,24 +50,39 @@ export function createCommandCodeAdapter(): AgentAdapter {
return status;
},

buildLaunchArgv(_location, config, prompt) {
buildLaunchArgv(location, config, prompt) {
// `command-code` has no flag to pre-assign or report a session id, so we
// mint a synthetic ref to mark the thread resumable immediately. Resume
// then uses `--continue` (the last conversation in this cwd) — robust for
// the worktree-isolated common case; see buildResumeArgv.
// snapshot the existing transcripts here and let the runtime discover the
// real id afterward (discoverSessionRef below). Returning no sessionRef
// is what enables that discovery path; resume then targets the exact id.
const cwd = location.kind === "wsl" ? location.linuxPath : location.path;
snapshotCommandCodePreSpawnSessions(location, cwd);
const args = buildCommandCodeArgs(config, prompt);
return { binary: "command-code", args, sessionRef: createKnownSessionRef() };
return { binary: "command-code", args };
},

buildResumeArgv(_location, config, prompt) {
const args = buildCommandCodeArgs(config, prompt, true);
buildResumeArgv(_location, config, prompt, sessionRef) {
// Resume the exact discovered session id (`--resume <id>`). A dead/stale
// id surfaces command-code's "found to resume" error, which the runtime
// recovers by relaunching fresh (see detectCommandCodeInvalidSessionRef)
// — same contract as grok/codex. A non-UUID ref (legacy/degenerate) falls
// back to `--continue`.
const id = sessionRef?.providerSessionId;
const args = buildCommandCodeArgs(config, prompt, id && isUuid(id) ? id : "");
return { binary: "command-code", args };
},

createInitialSessionRef() {
return undefined;
},

// `command-code` writes the transcript only on the first message, so poll a
// beat after launch and rely on watchSessionRef to catch the file's
// creation. Mirrors codex's discovery cadence.
initialSessionRefDiscoveryDelayMs: 1000,
discoverSessionRef: makeCommandCodeDiscoverSessionRef(),
watchSessionRef: makeCommandCodeWatchSessionRef(),

buildDirectInput(prompt) {
// The TUI treats bulk writes as a paste, so an embedded `\r` becomes a
// literal newline instead of submitting. Pause ~40ms between the text and
Expand Down
12 changes: 8 additions & 4 deletions src/supervisor/agents/commandcode/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,15 @@ export function commandCodeHasStoredCredentials(location: ProjectLocation): bool
}
}

// Emitted by `--continue` when there is no prior conversation in the cwd, or by
// a stale resume. Returning true here lets the runtime drop the (synthetic)
// sessionRef and relaunch fresh instead of looping on a dead resume.
// Emitted when a resume target is missing or unloadable: `--continue` with no
// prior conversation (`No conversations found to resume.`), `--resume <id>`
// with an unknown id (`No session "<id>" found to resume.`), or a corrupt
// transcript (`Session could not be loaded. N lines could not be parsed.`).
// Returning true lets the runtime drop the dead ref and relaunch fresh instead
// of looping on it. The `found to resume` / `could not be loaded` anchors are
// specific enough not to fire on ordinary agent output during launch.
const INVALID_SESSION_RE =
/no\s+(?:previous\s+)?conversation|nothing\s+to\s+continue|no\s+session\s+to\s+(?:resume|continue)/i;
/no\s+(?:previous\s+)?conversation|nothing\s+to\s+continue|no\s+session\s+to\s+(?:resume|continue)|found\s+to\s+resume|session\s+could\s+not\s+be\s+loaded|lines?\s+could\s+not\s+be\s+parsed/i;

export function detectCommandCodeInvalidSessionRef(output: string): boolean {
return INVALID_SESSION_RE.test(output);
Expand Down
Loading