feat: add --continue flag to `ao start` for orchestrator conversation continuity

When the orchestrator needs a fresh system prompt (e.g. CLAUDE.local.md was
updated), `ao start --continue` kills the existing session and respawns with
`claude --continue`, preserving conversation history while reloading prompts.

Changes:
- Add `continue` option to AgentLaunchConfig and OrchestratorSpawnConfig
- Claude Code agent plugin passes --continue to CLI when set
- Session manager forwards continue option to agent launch config
- `ao start --continue` kills existing orchestrator, respawns with continue flag
- Other agent plugins unaffected (optional field, backwards-compatible)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Prateek 2026-03-02 14:11:12 +05:30
parent 01a6c56e1b
commit 93ff9341be
5 changed files with 65 additions and 3 deletions

View File

@ -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

View File

@ -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);

View File

@ -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 {

View File

@ -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");
});
});
// =========================================================================

View File

@ -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");
}