diff --git a/packages/cli/src/commands/start.ts b/packages/cli/src/commands/start.ts index a0889246a..55afe1109 100644 --- a/packages/cli/src/commands/start.ts +++ b/packages/cli/src/commands/start.ts @@ -124,6 +124,10 @@ export function registerStart(program: Command): void { .option("--no-dashboard", "Skip starting the dashboard server") .option("--no-orchestrator", "Skip starting the orchestrator agent") .option("--rebuild", "Clean and rebuild dashboard before starting") + .option( + "--continue", + "Continue the most recent orchestrator conversation (kills existing session first, preserves conversation history with fresh system prompt)", + ) .action( async ( projectArg?: string, @@ -131,6 +135,7 @@ export function registerStart(program: Command): void { dashboard?: boolean; orchestrator?: boolean; rebuild?: boolean; + continue?: boolean; }, ) => { try { @@ -180,6 +185,15 @@ export function registerStart(program: Command): void { const existing = await sm.get(sessionId); exists = existing !== null && existing.status !== "killed"; + // --continue: kill existing session so claude --continue picks up + // the most recent conversation with a fresh system prompt + if (opts?.continue && exists) { + spinner.start("Killing existing orchestrator session for --continue"); + await sm.kill(sessionId); + spinner.succeed("Existing orchestrator session killed"); + exists = false; + } + if (exists) { if (existing?.runtimeHandle?.id) { tmuxTarget = existing.runtimeHandle.id; @@ -191,14 +205,26 @@ export function registerStart(program: Command): void { ); } else { try { - spinner.start("Creating orchestrator session"); + spinner.start( + opts?.continue + ? "Continuing orchestrator session" + : "Creating orchestrator session", + ); const systemPrompt = generateOrchestratorPrompt({ config, projectId, project }); - const session = await sm.spawnOrchestrator({ projectId, systemPrompt }); + const session = await sm.spawnOrchestrator({ + projectId, + systemPrompt, + continue: opts?.continue, + }); if (session.runtimeHandle?.id) { tmuxTarget = session.runtimeHandle.id; } - spinner.succeed("Orchestrator session created"); + spinner.succeed( + opts?.continue + ? "Orchestrator session continued" + : "Orchestrator session created", + ); } catch (err) { spinner.fail("Orchestrator setup failed"); // Cleanup dashboard if orchestrator setup fails diff --git a/packages/core/src/session-manager.ts b/packages/core/src/session-manager.ts index 126d4934e..61da6664f 100644 --- a/packages/core/src/session-manager.ts +++ b/packages/core/src/session-manager.ts @@ -641,6 +641,7 @@ export function createSessionManager(deps: SessionManagerDeps): SessionManager { permissions: "skip" as const, model: project.agentConfig?.model, systemPromptFile, + continue: orchestratorConfig.continue, }; const launchCommand = plugins.agent.getLaunchCommand(agentLaunchConfig); diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 617c60b28..d629eae5c 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -184,6 +184,12 @@ export interface SessionSpawnConfig { export interface OrchestratorSpawnConfig { projectId: string; systemPrompt?: string; + /** + * Continue the most recent conversation in the project directory. + * When true, the agent is launched with --continue (or equivalent), + * preserving conversation history while loading fresh system prompts. + */ + continue?: boolean; } // ============================================================================= @@ -352,6 +358,13 @@ export interface AgentLaunchConfig { * - Codex/Aider: similar shell substitution */ systemPromptFile?: string; + /** + * Continue the most recent conversation in the project directory. + * Loads fresh system prompt while preserving conversation history. + * - Claude Code: --continue flag + * - Other agents: ignored if not supported + */ + continue?: boolean; } export interface WorkspaceHooksConfig { diff --git a/packages/plugins/agent-claude-code/src/index.test.ts b/packages/plugins/agent-claude-code/src/index.test.ts index dabeafd61..b7a13fac1 100644 --- a/packages/plugins/agent-claude-code/src/index.test.ts +++ b/packages/plugins/agent-claude-code/src/index.test.ts @@ -203,6 +203,24 @@ describe("getLaunchCommand", () => { expect(cmd).not.toMatch(/\s-p\s/); expect(cmd).not.toContain("Do the task"); }); + + it("includes --continue when continue=true", () => { + const cmd = agent.getLaunchCommand(makeLaunchConfig({ continue: true })); + expect(cmd).toContain("--continue"); + expect(cmd).toBe("claude --continue"); + }); + + it("combines --continue with other flags", () => { + const cmd = agent.getLaunchCommand( + makeLaunchConfig({ continue: true, permissions: "skip", model: "opus" }), + ); + expect(cmd).toBe("claude --continue --dangerously-skip-permissions --model 'opus'"); + }); + + it("omits --continue when continue is not set", () => { + const cmd = agent.getLaunchCommand(makeLaunchConfig()); + expect(cmd).not.toContain("--continue"); + }); }); // ========================================================================= diff --git a/packages/plugins/agent-claude-code/src/index.ts b/packages/plugins/agent-claude-code/src/index.ts index db8b475ee..695a47097 100644 --- a/packages/plugins/agent-claude-code/src/index.ts +++ b/packages/plugins/agent-claude-code/src/index.ts @@ -635,6 +635,10 @@ function createClaudeCodeAgent(): Agent { // This command must be safe for both shell and execFile contexts. const parts: string[] = ["claude"]; + if (config.continue) { + parts.push("--continue"); + } + if (config.permissions === "skip") { parts.push("--dangerously-skip-permissions"); }