diff --git a/packages/coding-agent/README.md b/packages/coding-agent/README.md index 084bf826..44b12507 100644 --- a/packages/coding-agent/README.md +++ b/packages/coding-agent/README.md @@ -205,6 +205,7 @@ pi -c # Continue most recent session pi -r # Browse and select from past sessions pi --no-session # Ephemeral mode (don't save) pi --session # Use specific session file or ID +pi --fork # Fork specific session file or ID into a new session ``` ### Branching @@ -219,6 +220,8 @@ pi --session # Use specific session file or ID **`/fork`** - Create a new session file from the current branch. Opens a selector, copies history up to the selected point, and places that message in the editor for modification. +**`--fork `** - Fork an existing session file or partial session UUID directly from the CLI. This copies the full source session into a new session file in the current project. + ### Compaction Long sessions can exhaust context windows. Compaction summarizes older messages while keeping recent ones. @@ -475,6 +478,7 @@ cat README.md | pi -p "Summarize this text" | `-c`, `--continue` | Continue most recent session | | `-r`, `--resume` | Browse and select session | | `--session ` | Use specific session file or partial UUID | +| `--fork ` | Fork specific session file or partial UUID into a new session | | `--session-dir ` | Custom session storage directory | | `--no-session` | Ephemeral mode (don't save) | diff --git a/packages/coding-agent/src/cli/args.ts b/packages/coding-agent/src/cli/args.ts index 580ec8de..a630e862 100644 --- a/packages/coding-agent/src/cli/args.ts +++ b/packages/coding-agent/src/cli/args.ts @@ -23,6 +23,7 @@ export interface Args { mode?: Mode; noSession?: boolean; session?: string; + fork?: string; sessionDir?: string; models?: string[]; tools?: ToolName[]; @@ -89,6 +90,8 @@ export function parseArgs(args: string[], extensionFlags?: Map Use specific session file + --fork Fork specific session file or partial UUID into a new session --session-dir Directory for session storage and lookup --no-session Don't save session (ephemeral) --models Comma-separated model patterns for Ctrl+P cycling diff --git a/packages/coding-agent/src/main.ts b/packages/coding-agent/src/main.ts index a1666040..b32409fa 100644 --- a/packages/coding-agent/src/main.ts +++ b/packages/coding-agent/src/main.ts @@ -408,6 +408,32 @@ async function callSessionDirectoryHook(extensions: LoadExtensionsResult, cwd: s return customSessionDir; } +function validateForkFlags(parsed: Args): void { + if (!parsed.fork) return; + + const conflictingFlags = [ + parsed.session ? "--session" : undefined, + parsed.continue ? "--continue" : undefined, + parsed.resume ? "--resume" : undefined, + parsed.noSession ? "--no-session" : undefined, + ].filter((flag): flag is string => flag !== undefined); + + if (conflictingFlags.length > 0) { + console.error(chalk.red(`Error: --fork cannot be combined with ${conflictingFlags.join(", ")}`)); + process.exit(1); + } +} + +function forkSessionOrExit(sourcePath: string, cwd: string, sessionDir?: string): SessionManager { + try { + return SessionManager.forkFrom(sourcePath, cwd, sessionDir); + } catch (error: unknown) { + const message = error instanceof Error ? error.message : String(error); + console.error(chalk.red(`Error: ${message}`)); + process.exit(1); + } +} + async function createSessionManager( parsed: Args, cwd: string, @@ -423,6 +449,21 @@ async function createSessionManager( effectiveSessionDir = await callSessionDirectoryHook(extensions, cwd); } + if (parsed.fork) { + const resolved = await resolveSessionPath(parsed.fork, cwd, effectiveSessionDir); + + switch (resolved.type) { + case "path": + case "local": + case "global": + return forkSessionOrExit(resolved.path, cwd, effectiveSessionDir); + + case "not_found": + console.error(chalk.red(`No session found matching '${resolved.arg}'`)); + process.exit(1); + } + } + if (parsed.session) { const resolved = await resolveSessionPath(parsed.session, cwd, effectiveSessionDir); @@ -439,7 +480,7 @@ async function createSessionManager( console.log(chalk.dim("Aborted.")); process.exit(0); } - return SessionManager.forkFrom(resolved.path, cwd, effectiveSessionDir); + return forkSessionOrExit(resolved.path, cwd, effectiveSessionDir); } case "not_found": @@ -698,6 +739,8 @@ export async function main(args: string[]) { process.exit(1); } + validateForkFlags(parsed); + const { initialMessage, initialImages } = await prepareInitialMessage( parsed, settingsManager.getImageAutoResize(), diff --git a/packages/coding-agent/test/args.test.ts b/packages/coding-agent/test/args.test.ts index d388704c..5e2e53be 100644 --- a/packages/coding-agent/test/args.test.ts +++ b/packages/coding-agent/test/args.test.ts @@ -110,6 +110,12 @@ describe("parseArgs", () => { expect(result.session).toBe("/path/to/session.jsonl"); }); + test("parses --fork", () => { + const result = parseArgs(["--fork", "1234abcd"]); + expect(result.fork).toBe("1234abcd"); + expect(result.messages).toEqual([]); + }); + test("parses --export", () => { const result = parseArgs(["--export", "session.jsonl"]); expect(result.export).toBe("session.jsonl");