@agentproto/worktree 0.10.0 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +61 -3
- package/dist/bin/worktree-agent.mjs +3 -3
- package/dist/{chunk-IKCBL2UN.mjs → chunk-3FZWPMLF.mjs} +10 -5
- package/dist/chunk-3FZWPMLF.mjs.map +1 -0
- package/dist/{chunk-KRQFVJPK.mjs → chunk-MYH5XOX6.mjs} +307 -33
- package/dist/chunk-MYH5XOX6.mjs.map +1 -0
- package/dist/{chunk-6EV7WP2U.mjs → chunk-YMDOSMMD.mjs} +4 -4
- package/dist/{chunk-6EV7WP2U.mjs.map → chunk-YMDOSMMD.mjs.map} +1 -1
- package/dist/index.d.ts +260 -9
- package/dist/index.mjs +17 -7
- package/dist/index.mjs.map +1 -1
- package/dist/provider/index.mjs +2 -2
- package/dist/tools/index.d.ts +3 -0
- package/dist/tools/index.mjs +1 -1
- package/package.json +2 -2
- package/dist/chunk-IKCBL2UN.mjs.map +0 -1
- package/dist/chunk-KRQFVJPK.mjs.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/workflow.ts","../src/agent-session-host.ts"],"names":[],"mappings":"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","file":"chunk-6EV7WP2U.mjs","sourcesContent":["import { z } from \"zod\"\nimport type { Bindings, RuntimeWorkflow } from \"@agentproto/workflow-runtime\"\nimport { provisionWorktreeTool, cleanupWorktreeTool, runGateTool } from \"./tools/index.js\"\nimport { worktreeProvider } from \"./provider/index.js\"\n\n/**\n * Input to the worktree-agent workflow: launch a coding agent inside a\n * fresh git worktree, gate its work, and — on approval — clean up.\n */\nexport const worktreeAgentInputSchema = z.object({\n repoRoot: z.string().describe(\"Absolute path to the git repository root.\"),\n slug: z.string().describe(\"Worktree/branch identifier, e.g. 'fix-flaky-test'.\"),\n base: z.string().optional().describe(\"Ref to cut the worktree branch from. Default 'origin/main'.\"),\n depsCmd: z.string().optional().describe(\"Command to install deps inside the worktree, e.g. 'pnpm install --prefer-offline'.\"),\n copyGlobs: z.array(z.string()).optional().describe(\"Gitignored files to copy into the worktree, e.g. secrets.\"),\n linkPaths: z.array(z.string()).optional().describe(\"Gitignored dirs/files symlinked from the host repo into the worktree before depsCmd (node_modules, sibling workspace repos) so the graph resolves without a full reinstall.\"),\n writeFiles: z\n .array(\n z.object({\n path: z.string().describe(\"Path relative to the worktree root.\"),\n content: z.string().describe(\"File content, written or appended verbatim.\"),\n mode: z\n .enum([\"create\", \"append\"])\n .optional()\n .describe(\"'create' (default): write only if path doesn't already exist. 'append': always append (creating if missing); if git tracks the path, it's marked skip-worktree afterwards.\"),\n }),\n )\n .optional()\n .describe(\"Files written into the worktree before depsCmd runs, e.g. a package-manager config generated for this specific worktree.\"),\n task: z.string().describe(\"The prompt sent to the coding agent.\"),\n adapter: z.string().optional().describe(\"Agent adapter slug. Default 'claude-code'.\"),\n gateCmd: z.string().describe(\"Command run inside the worktree to check the agent's work, e.g. 'pnpm test'.\"),\n deleteBranch: z.boolean().optional().describe(\"Delete the worktree's branch on cleanup. Default true.\"),\n})\n\nexport type WorktreeAgentInput = z.infer<typeof worktreeAgentInputSchema>\n\nconst candidates = [worktreeProvider]\n\nfunction input(b: { input: unknown }): WorktreeAgentInput {\n return b.input as WorktreeAgentInput\n}\n\n/**\n * provision (tool) → code (AgentStep, cwd bound to provision's output) →\n * gate (tool) → on pass: approval → cleanup (tool); on fail: leave the\n * worktree in place for inspection.\n */\nexport const worktreeAgentWorkflow: RuntimeWorkflow = {\n id: \"worktree-agent\",\n description:\n \"Launch a coding agent inside a fresh git worktree, gate its work with a \" +\n \"check command, get a human ack, then clean up. On gate failure the \" +\n \"worktree is left in place for inspection instead of being cleaned up.\",\n steps: [\n {\n kind: \"tool\",\n id: \"provision\",\n tool: provisionWorktreeTool,\n candidates,\n input: (b: Bindings) => {\n const i = input(b)\n return {\n repoRoot: i.repoRoot,\n slug: i.slug,\n base: i.base,\n depsCmd: i.depsCmd,\n copyGlobs: i.copyGlobs,\n linkPaths: i.linkPaths,\n writeFiles: i.writeFiles,\n }\n },\n },\n {\n kind: \"agent\",\n id: \"code\",\n adapter: (b: Bindings) => input(b).adapter ?? \"claude-code\",\n cwd: (b: Bindings) => (b.steps.provision as { cwd: string }).cwd,\n prompt: (b: Bindings) => input(b).task,\n },\n {\n kind: \"tool\",\n id: \"gate\",\n tool: runGateTool,\n candidates,\n input: (b: Bindings) => ({\n cwd: (b.steps.provision as { cwd: string }).cwd,\n cmd: input(b).gateCmd,\n }),\n },\n {\n kind: \"branch\",\n id: \"route\",\n cond: (b: Bindings) => (b.steps.gate as { passed: boolean }).passed,\n then: [\n {\n kind: \"approval\",\n id: \"approval\",\n prompt: () => \"Gate passed. Approve worktree cleanup?\",\n onApprove: [\n {\n kind: \"tool\",\n id: \"cleanup\",\n tool: cleanupWorktreeTool,\n candidates,\n // discardUntracked/discardModified: worktree.cleanup refuses a\n // dirty tree by default (PLAN.md §5.2 layer 3). This workflow's\n // own approval step, just above, is that guard already — a\n // human explicitly approved tearing this worktree down, so\n // both flags are granted here rather than surfacing a second,\n // redundant refusal.\n input: (b: Bindings) => ({\n repoRoot: input(b).repoRoot,\n cwd: (b.steps.provision as { cwd: string }).cwd,\n branch: (b.steps.provision as { branch: string }).branch,\n deleteBranch: input(b).deleteBranch ?? true,\n discardUntracked: true,\n discardModified: true,\n }),\n },\n ],\n onReject: [\n {\n kind: \"transform\",\n id: \"held\",\n compute: () => ({ removed: false, reason: \"cleanup_rejected\" as const }),\n },\n ],\n },\n ],\n otherwise: [\n {\n kind: \"transform\",\n id: \"gate-failed\",\n compute: () => ({ removed: false, reason: \"gate_failed\" as const }),\n },\n ],\n },\n ],\n output: (b: Bindings) => ({\n cwd: (b.steps.provision as { cwd: string }).cwd,\n branch: (b.steps.provision as { branch: string }).branch,\n gate: b.steps.gate,\n cleanup: b.steps.cleanup ?? b.steps.held ?? b.steps[\"gate-failed\"],\n }),\n}\n","import {\n HarnessClient,\n resolveMcpUrl,\n type ConnectHarnessOptions,\n type SessionDescriptor,\n type SessionUsageSnapshot,\n type StartAgentArgs,\n type TurnEvent,\n type TurnResult,\n} from \"@agentproto/harness\"\nimport type { AgentSessionHost } from \"@agentproto/workflow-runtime\"\n\n/** `session_monitor`'s max accepted long-poll window (`orchestration-tools.ts`). */\nconst MAX_POLL_MS = 49_000\n\n/** The subset of `HarnessClient` the host drives — narrowed for fake-client tests. */\nexport type DaemonClient = Pick<\n HarnessClient,\n \"start\" | \"prompt\" | \"output\" | \"kill\" | \"waitForAny\" | \"currentEventsCursor\" | \"close\"\n> & {\n /** `session_usage` — optional so pre-existing fakes/clients without it still\n * satisfy the type; the host simply exposes no `usage` then. */\n usage?: HarnessClient[\"usage\"]\n}\n\nexport interface DaemonAgentSessionHost extends AgentSessionHost {\n close(): Promise<void>\n /** Full `agent_start`, returning the raw descriptor and forwarding every\n * `StartAgentArgs` field (`mcpServers`, `model`, `effort`, `label`, …) —\n * unlike `spawn()` above (the narrow AgentStep-shaped convenience\n * wrapper), which only takes `cwd`/`workspaceSlug`/`stepId`. Consumers\n * that need finer per-turn control than the AgentStep contract provides\n * (e.g. `@agentproto/sandbox`'s sandbox agent-session proxy) use this\n * plus `prompt`/`output`/`kill`/`waitForAny` directly instead of\n * `spawn`/`sendPromptAndWait`. */\n start(args: StartAgentArgs): Promise<SessionDescriptor>\n /** Send a follow-up turn — `opts.interrupt` cancels an in-flight turn and\n * redirects the session onto this prompt (mirrors `agent_prompt`). */\n prompt(sessionId: string, prompt: string, opts?: { interrupt?: boolean }): Promise<void>\n /** Tail the ring buffer (`agent_output`). */\n output(sessionId: string, lastN?: number): Promise<string>\n /** SIGTERM the session (`agent_kill`). */\n kill(sessionId: string): Promise<void>\n /** Multiplexed long-poll (`session_monitor`) — see `HarnessClient.waitForAny`. */\n waitForAny(\n sessionIds: string[],\n opts?: { timeoutMs?: number; event?: TurnEvent; since?: number },\n ): Promise<TurnResult>\n /** Race-free cursor for a subsequent `waitForAny({ since })` — see\n * `HarnessClient.currentEventsCursor`. */\n currentEventsCursor(): Promise<number>\n /** Usage accounting for a session on this daemon (`session_usage`) — present\n * when the underlying client can answer it. `@agentproto/sandbox`'s host\n * inherits it, which is how a sandboxed session's cost/tokens/model reach\n * the HOST daemon's descriptor (the proxy's text stream carries none). */\n usage?(sessionId: string): Promise<SessionUsageSnapshot>\n}\n\n/**\n * Connect to the agentproto daemon's MCP endpoint and return a real,\n * supervisable `AgentSessionHost` — every `AgentStep` becomes an `agent_start`\n * daemon session rather than a bare subprocess. Fails loudly (no silent\n * fallback) if the daemon isn't reachable.\n */\nexport async function connectDaemonAgentSessionHost(\n opts?: ConnectHarnessOptions,\n): Promise<DaemonAgentSessionHost> {\n const url = resolveMcpUrl(opts)\n let client: HarnessClient\n try {\n client = await HarnessClient.connect({ url })\n } catch (err) {\n throw new Error(\n `worktree-agent: could not reach the agentproto daemon's MCP endpoint at ${url}. ` +\n \"Start the daemon (`agentproto daemon start`), or point at a running one via \" +\n `AGENTPROTO_MCP_URL, before running this command.\\n` +\n `(${err instanceof Error ? err.message : String(err)})`,\n )\n }\n return makeDaemonAgentSessionHost(client)\n}\n\n/** Build the host over an already-connected client (exported for tests with a fake client). */\nexport function makeDaemonAgentSessionHost(client: DaemonClient): DaemonAgentSessionHost {\n const sessionByStepId = new Map<string, string>()\n\n return {\n async spawn(adapter, opts): Promise<string> {\n const desc = await client.start({\n adapter,\n ...(opts.cwd !== undefined ? { cwd: opts.cwd } : {}),\n ...(opts.workspaceSlug !== undefined ? { workspaceSlug: opts.workspaceSlug } : {}),\n ...(opts.stepId !== undefined ? { label: opts.stepId } : {}),\n })\n if (opts.stepId) sessionByStepId.set(opts.stepId, desc.id)\n return desc.id\n },\n\n async sendPromptAndWait(sessionId, prompt): Promise<void> {\n // Capture a race-free cursor BEFORE sending: an extremely fast turn\n // can settle before a live bus subscription even starts — a real\n // race over the network round-trip to `prompt`/`waitForAny`, not\n // just in-process call ordering (see `HarnessClient\n // .currentEventsCursor`'s doc). The awaited cursor fetch completes\n // strictly before `prompt` is sent, so the daemon's ring-replay\n // branch can always find the matching turn-end regardless of how\n // late the long-poll's own subscription lands.\n const since = await client.currentEventsCursor()\n await client.prompt(sessionId, prompt)\n await waitForSettled(client, sessionId, since)\n },\n\n resolveByLabel(stepId): string | undefined {\n return sessionByStepId.get(stepId)\n },\n\n async start(args): Promise<SessionDescriptor> {\n return client.start(args)\n },\n\n async prompt(sessionId, prompt, opts): Promise<void> {\n await client.prompt(sessionId, prompt, opts)\n },\n\n async output(sessionId, lastN): Promise<string> {\n return client.output(sessionId, lastN)\n },\n\n async kill(sessionId): Promise<void> {\n await client.kill(sessionId)\n },\n\n async waitForAny(sessionIds, opts): Promise<TurnResult> {\n return client.waitForAny(sessionIds, opts)\n },\n\n async currentEventsCursor(): Promise<number> {\n return client.currentEventsCursor()\n },\n\n ...(typeof client.usage === \"function\"\n ? {\n async usage(sessionId: string): Promise<SessionUsageSnapshot> {\n return client.usage!(sessionId)\n },\n }\n : {}),\n\n async close(): Promise<void> {\n await client.close()\n },\n }\n}\n\n/**\n * `session_monitor` long-polls cap at 49s; loop past timeouts for longer\n * turns. A clean timeout comes back as `{ timedOut: true, sessionIds }` —\n * there is no `event: \"timeout\"` on the wire, `event` is only ever set on a\n * real match. Keep polling while `timedOut` is true; stop once it's\n * falsy/absent (a real turn-end/awaiting-input/exited match). `since` is\n * reused unchanged across retries — a timeout means nothing has matched\n * since that cursor yet, so there is nothing to advance.\n */\nasync function waitForSettled(client: DaemonClient, sessionId: string, since: number): Promise<void> {\n for (;;) {\n const result = await client.waitForAny([sessionId], { event: \"any\", timeoutMs: MAX_POLL_MS, since })\n if (!result.timedOut) return\n }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/workflow.ts","../src/agent-session-host.ts"],"names":[],"mappings":"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","file":"chunk-YMDOSMMD.mjs","sourcesContent":["import { z } from \"zod\"\nimport type { Bindings, RuntimeWorkflow } from \"@agentproto/workflow-runtime\"\nimport { provisionWorktreeTool, cleanupWorktreeTool, runGateTool } from \"./tools/index.js\"\nimport { worktreeProvider } from \"./provider/index.js\"\n\n/**\n * Input to the worktree-agent workflow: launch a coding agent inside a\n * fresh git worktree, gate its work, and — on approval — clean up.\n */\nexport const worktreeAgentInputSchema = z.object({\n repoRoot: z.string().describe(\"Absolute path to the git repository root.\"),\n slug: z.string().describe(\"Worktree/branch identifier, e.g. 'fix-flaky-test'.\"),\n base: z.string().optional().describe(\"Ref to cut the worktree branch from. Default 'origin/main'.\"),\n depsCmd: z.string().optional().describe(\"Command to install deps inside the worktree, e.g. 'pnpm install --prefer-offline'.\"),\n copyGlobs: z.array(z.string()).optional().describe(\"Gitignored files to copy into the worktree, e.g. secrets.\"),\n linkPaths: z.array(z.string()).optional().describe(\"Gitignored dirs/files symlinked from the host repo into the worktree before depsCmd (node_modules, sibling workspace repos) so the graph resolves without a full reinstall.\"),\n writeFiles: z\n .array(\n z.object({\n path: z.string().describe(\"Path relative to the worktree root.\"),\n content: z.string().describe(\"File content, written or appended verbatim.\"),\n mode: z\n .enum([\"create\", \"append\"])\n .optional()\n .describe(\"'create' (default): write only if path doesn't already exist. 'append': always append (creating if missing); if git tracks the path, it's marked skip-worktree afterwards.\"),\n }),\n )\n .optional()\n .describe(\"Files written into the worktree before depsCmd runs, e.g. a package-manager config generated for this specific worktree.\"),\n task: z.string().describe(\"The prompt sent to the coding agent.\"),\n adapter: z.string().optional().describe(\"Agent adapter slug. Default 'claude-code'.\"),\n gateCmd: z.string().describe(\"Command run inside the worktree to check the agent's work, e.g. 'pnpm test'.\"),\n deleteBranch: z.boolean().optional().describe(\"Delete the worktree's branch on cleanup. Default true.\"),\n})\n\nexport type WorktreeAgentInput = z.infer<typeof worktreeAgentInputSchema>\n\nconst candidates = [worktreeProvider]\n\nfunction input(b: { input: unknown }): WorktreeAgentInput {\n return b.input as WorktreeAgentInput\n}\n\n/**\n * provision (tool) → code (AgentStep, cwd bound to provision's output) →\n * gate (tool) → on pass: approval → cleanup (tool); on fail: leave the\n * worktree in place for inspection.\n */\nexport const worktreeAgentWorkflow: RuntimeWorkflow = {\n id: \"worktree-agent\",\n description:\n \"Launch a coding agent inside a fresh git worktree, gate its work with a \" +\n \"check command, get a human ack, then clean up. On gate failure the \" +\n \"worktree is left in place for inspection instead of being cleaned up.\",\n steps: [\n {\n kind: \"tool\",\n id: \"provision\",\n tool: provisionWorktreeTool,\n candidates,\n input: (b: Bindings) => {\n const i = input(b)\n return {\n repoRoot: i.repoRoot,\n slug: i.slug,\n base: i.base,\n depsCmd: i.depsCmd,\n copyGlobs: i.copyGlobs,\n linkPaths: i.linkPaths,\n writeFiles: i.writeFiles,\n }\n },\n },\n {\n kind: \"agent\",\n id: \"code\",\n adapter: (b: Bindings) => input(b).adapter ?? \"claude-code\",\n cwd: (b: Bindings) => (b.steps.provision as { cwd: string }).cwd,\n prompt: (b: Bindings) => input(b).task,\n },\n {\n kind: \"tool\",\n id: \"gate\",\n tool: runGateTool,\n candidates,\n input: (b: Bindings) => ({\n cwd: (b.steps.provision as { cwd: string }).cwd,\n cmd: input(b).gateCmd,\n }),\n },\n {\n kind: \"branch\",\n id: \"route\",\n cond: (b: Bindings) => (b.steps.gate as { passed: boolean }).passed,\n then: [\n {\n kind: \"approval\",\n id: \"approval\",\n prompt: () => \"Gate passed. Approve worktree cleanup?\",\n onApprove: [\n {\n kind: \"tool\",\n id: \"cleanup\",\n tool: cleanupWorktreeTool,\n candidates,\n // discardUntracked/discardModified: worktree.cleanup refuses a\n // dirty tree by default (PLAN.md §5.2 layer 3). This workflow's\n // own approval step, just above, is that guard already — a\n // human explicitly approved tearing this worktree down, so\n // both flags are granted here rather than surfacing a second,\n // redundant refusal.\n input: (b: Bindings) => ({\n repoRoot: input(b).repoRoot,\n cwd: (b.steps.provision as { cwd: string }).cwd,\n branch: (b.steps.provision as { branch: string }).branch,\n deleteBranch: input(b).deleteBranch ?? true,\n discardUntracked: true,\n discardModified: true,\n }),\n },\n ],\n onReject: [\n {\n kind: \"transform\",\n id: \"held\",\n compute: () => ({ removed: false, reason: \"cleanup_rejected\" as const }),\n },\n ],\n },\n ],\n otherwise: [\n {\n kind: \"transform\",\n id: \"gate-failed\",\n compute: () => ({ removed: false, reason: \"gate_failed\" as const }),\n },\n ],\n },\n ],\n output: (b: Bindings) => ({\n cwd: (b.steps.provision as { cwd: string }).cwd,\n branch: (b.steps.provision as { branch: string }).branch,\n gate: b.steps.gate,\n cleanup: b.steps.cleanup ?? b.steps.held ?? b.steps[\"gate-failed\"],\n }),\n}\n","import {\n HarnessClient,\n resolveMcpUrl,\n type ConnectHarnessOptions,\n type SessionDescriptor,\n type SessionUsageSnapshot,\n type StartAgentArgs,\n type TurnEvent,\n type TurnResult,\n} from \"@agentproto/harness\"\nimport type { AgentSessionHost } from \"@agentproto/workflow-runtime\"\n\n/** `session_monitor`'s max accepted long-poll window (`orchestration-tools.ts`). */\nconst MAX_POLL_MS = 49_000\n\n/** The subset of `HarnessClient` the host drives — narrowed for fake-client tests. */\nexport type DaemonClient = Pick<\n HarnessClient,\n \"start\" | \"prompt\" | \"output\" | \"kill\" | \"waitForAny\" | \"currentEventsCursor\" | \"close\"\n> & {\n /** `session_usage` — optional so pre-existing fakes/clients without it still\n * satisfy the type; the host simply exposes no `usage` then. */\n usage?: HarnessClient[\"usage\"]\n}\n\nexport interface DaemonAgentSessionHost extends AgentSessionHost {\n close(): Promise<void>\n /** Full `agent_start`, returning the raw descriptor and forwarding every\n * `StartAgentArgs` field (`mcpServers`, `model`, `effort`, `label`, …) —\n * unlike `spawn()` above (the narrow AgentStep-shaped convenience\n * wrapper), which only takes `cwd`/`workspaceSlug`/`stepId`. Consumers\n * that need finer per-turn control than the AgentStep contract provides\n * (e.g. `@agentproto/sandbox`'s sandbox agent-session proxy) use this\n * plus `prompt`/`output`/`kill`/`waitForAny` directly instead of\n * `spawn`/`sendPromptAndWait`. */\n start(args: StartAgentArgs): Promise<SessionDescriptor>\n /** Send a follow-up turn — `opts.interrupt` cancels an in-flight turn and\n * redirects the session onto this prompt (mirrors `agent_prompt`). */\n prompt(sessionId: string, prompt: string, opts?: { interrupt?: boolean }): Promise<void>\n /** Tail the ring buffer (`agent_output`). */\n output(sessionId: string, lastN?: number): Promise<string>\n /** SIGTERM the session (`agent_kill`). */\n kill(sessionId: string): Promise<void>\n /** Multiplexed long-poll (`session_monitor`) — see `HarnessClient.waitForAny`. */\n waitForAny(\n sessionIds: string[],\n opts?: { timeoutMs?: number; event?: TurnEvent; since?: number },\n ): Promise<TurnResult>\n /** Race-free cursor for a subsequent `waitForAny({ since })` — see\n * `HarnessClient.currentEventsCursor`. */\n currentEventsCursor(): Promise<number>\n /** Usage accounting for a session on this daemon (`session_usage`) — present\n * when the underlying client can answer it. `@agentproto/sandbox`'s host\n * inherits it, which is how a sandboxed session's cost/tokens/model reach\n * the HOST daemon's descriptor (the proxy's text stream carries none). */\n usage?(sessionId: string): Promise<SessionUsageSnapshot>\n}\n\n/**\n * Connect to the agentproto daemon's MCP endpoint and return a real,\n * supervisable `AgentSessionHost` — every `AgentStep` becomes an `agent_start`\n * daemon session rather than a bare subprocess. Fails loudly (no silent\n * fallback) if the daemon isn't reachable.\n */\nexport async function connectDaemonAgentSessionHost(\n opts?: ConnectHarnessOptions,\n): Promise<DaemonAgentSessionHost> {\n const url = resolveMcpUrl(opts)\n let client: HarnessClient\n try {\n client = await HarnessClient.connect({ url })\n } catch (err) {\n throw new Error(\n `worktree-agent: could not reach the agentproto daemon's MCP endpoint at ${url}. ` +\n \"Start the daemon (`agentproto daemon start`), or point at a running one via \" +\n `AGENTPROTO_MCP_URL, before running this command.\\n` +\n `(${err instanceof Error ? err.message : String(err)})`,\n )\n }\n return makeDaemonAgentSessionHost(client)\n}\n\n/** Build the host over an already-connected client (exported for tests with a fake client). */\nexport function makeDaemonAgentSessionHost(client: DaemonClient): DaemonAgentSessionHost {\n const sessionByStepId = new Map<string, string>()\n\n return {\n async spawn(adapter, opts): Promise<string> {\n const desc = await client.start({\n adapter,\n ...(opts.cwd !== undefined ? { cwd: opts.cwd } : {}),\n ...(opts.workspaceSlug !== undefined ? { workspaceSlug: opts.workspaceSlug } : {}),\n ...(opts.stepId !== undefined ? { label: opts.stepId } : {}),\n })\n if (opts.stepId) sessionByStepId.set(opts.stepId, desc.id)\n return desc.id\n },\n\n async sendPromptAndWait(sessionId, prompt): Promise<void> {\n // Capture a race-free cursor BEFORE sending: an extremely fast turn\n // can settle before a live bus subscription even starts — a real\n // race over the network round-trip to `prompt`/`waitForAny`, not\n // just in-process call ordering (see `HarnessClient\n // .currentEventsCursor`'s doc). The awaited cursor fetch completes\n // strictly before `prompt` is sent, so the daemon's ring-replay\n // branch can always find the matching turn-end regardless of how\n // late the long-poll's own subscription lands.\n const since = await client.currentEventsCursor()\n await client.prompt(sessionId, prompt)\n await waitForSettled(client, sessionId, since)\n },\n\n resolveByLabel(stepId): string | undefined {\n return sessionByStepId.get(stepId)\n },\n\n async start(args): Promise<SessionDescriptor> {\n return client.start(args)\n },\n\n async prompt(sessionId, prompt, opts): Promise<void> {\n await client.prompt(sessionId, prompt, opts)\n },\n\n async output(sessionId, lastN): Promise<string> {\n return client.output(sessionId, lastN)\n },\n\n async kill(sessionId): Promise<void> {\n await client.kill(sessionId)\n },\n\n async waitForAny(sessionIds, opts): Promise<TurnResult> {\n return client.waitForAny(sessionIds, opts)\n },\n\n async currentEventsCursor(): Promise<number> {\n return client.currentEventsCursor()\n },\n\n ...(typeof client.usage === \"function\"\n ? {\n async usage(sessionId: string): Promise<SessionUsageSnapshot> {\n return client.usage!(sessionId)\n },\n }\n : {}),\n\n async close(): Promise<void> {\n await client.close()\n },\n }\n}\n\n/**\n * `session_monitor` long-polls cap at 49s; loop past timeouts for longer\n * turns. A clean timeout comes back as `{ timedOut: true, sessionIds }` —\n * there is no `event: \"timeout\"` on the wire, `event` is only ever set on a\n * real match. Keep polling while `timedOut` is true; stop once it's\n * falsy/absent (a real turn-end/awaiting-input/exited match). `since` is\n * reused unchanged across retries — a timeout means nothing has matched\n * since that cursor yet, so there is nothing to advance.\n */\nasync function waitForSettled(client: DaemonClient, sessionId: string, since: number): Promise<void> {\n for (;;) {\n const result = await client.waitForAny([sessionId], { event: \"any\", timeoutMs: MAX_POLL_MS, since })\n if (!result.timedOut) return\n }\n}\n"]}
|
package/dist/index.d.ts
CHANGED
|
@@ -245,10 +245,121 @@ declare function execArgv(command: string, args: readonly string[], cwd: string,
|
|
|
245
245
|
declare function execShell(cmd: string, cwd: string, opts?: ExecOptions): Promise<ExecResult>;
|
|
246
246
|
declare function execGit(repoRoot: string, args: readonly string[]): Promise<ExecResult>;
|
|
247
247
|
|
|
248
|
+
/**
|
|
249
|
+
* Fast worktree removal: `rename` to a same-volume trash dir + `git worktree
|
|
250
|
+
* prune`, with the bytes deleted by a detached background child.
|
|
251
|
+
*
|
|
252
|
+
* WHY THIS EXISTS: `git worktree remove` deletes the worktree tree in
|
|
253
|
+
* process, one file at a time — on a pnpm worktree (its own `node_modules` /
|
|
254
|
+
* `.pnpm-vstores`, hundreds of thousands of files) on an external SSD that
|
|
255
|
+
* measured ~10 minutes PER WORKTREE. Every part of the removal that callers
|
|
256
|
+
* actually wait on is instant:
|
|
257
|
+
*
|
|
258
|
+
* 1. `rename` the worktree directory to `<dirname(path)>/.trash/<base>-
|
|
259
|
+
* <timestamp>` — same volume by construction, so it's a metadata
|
|
260
|
+
* operation, not a copy. `git worktree remove`'s per-file refusals have
|
|
261
|
+
* already run (see `removeWorktreeFast`), so at this point the bytes
|
|
262
|
+
* are authorized to die; only the cost is being moved.
|
|
263
|
+
* 2. `git worktree prune` — git forgets the (now missing) registration.
|
|
264
|
+
* The worktree disappears from `git worktree list` immediately.
|
|
265
|
+
* 3. `rm -rf` on the trash dir runs in a DETACHED child
|
|
266
|
+
* (`spawn("rm", ["-rf", trashDir], { detached: true, stdio: "ignore" })`
|
|
267
|
+
* + `.unref()`) — the parent never waits for the hundreds of thousands
|
|
268
|
+
* of unlinks, and survives its own exit. The next `gc` apply also
|
|
269
|
+
* sweeps stale `.trash` dirs (`sweepWorktreeTrash`) so nothing leaks
|
|
270
|
+
* even if a child is killed mid-delete.
|
|
271
|
+
*
|
|
272
|
+
* SAFETY — the plain/forced `git worktree remove` semantics this must not
|
|
273
|
+
* weaken, verified empirically against git (see the brief):
|
|
274
|
+
* - A non-force removal must refuse a dirty tree: the caller passes
|
|
275
|
+
* `force: false` only for a tree already checked clean, and
|
|
276
|
+
* `removeWorktreeFast` re-checks `git status --porcelain` itself and
|
|
277
|
+
* throws (leaving the worktree in place) rather than renaming dirt away.
|
|
278
|
+
* - A forced removal is only ever passed after a salvage snapshot or an
|
|
279
|
+
* explicit user-granted discard flag upstream — this module doesn't
|
|
280
|
+
* loosen that; it just performs the same destruction instantly.
|
|
281
|
+
*
|
|
282
|
+
* Fallback: if the `rename` fails for any reason (cross-device, permissions,
|
|
283
|
+
* the trash parent unmountable), the function falls back to today's plain
|
|
284
|
+
* `git worktree remove [--force]` — behavior identical to before, just slow.
|
|
285
|
+
*/
|
|
286
|
+
/** Directory (inside the worktrees root's repo bucket) background-delete leftovers land in. */
|
|
287
|
+
declare const WORKTREE_TRASH_DIRNAME = ".trash";
|
|
288
|
+
interface RemoveWorktreeFastOptions {
|
|
289
|
+
/**
|
|
290
|
+
* `false` (default): re-verify the tree is clean via `git status
|
|
291
|
+
* --porcelain` and refuse (throw) on any dirt — the same refusal a plain
|
|
292
|
+
* `git worktree remove` performs. `true`: skip the cleanliness gate,
|
|
293
|
+
* exactly like `git worktree remove --force` — only pass this when dirt
|
|
294
|
+
* has been authorized (salvage snapshot durable / discard flags granted)
|
|
295
|
+
* upstream.
|
|
296
|
+
*/
|
|
297
|
+
force?: boolean;
|
|
298
|
+
/**
|
|
299
|
+
* Injectable clock for the trash dir's timestamp suffix — tests freeze it
|
|
300
|
+
* so the dir name is deterministic. Defaults to `Date.now`.
|
|
301
|
+
*/
|
|
302
|
+
now?: () => number;
|
|
303
|
+
/**
|
|
304
|
+
* Test seam — overrides the detached `rm -rf` child (e.g. an in-process
|
|
305
|
+
* spy). Receives the trash dir path. Default: a detached
|
|
306
|
+
* `spawn("rm", ["-rf", …])` the parent never waits on.
|
|
307
|
+
*/
|
|
308
|
+
spawnRemoval?: (trashDir: string) => void;
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* Remove a linked git worktree instantly: validate → rename to trash →
|
|
312
|
+
* prune → delete the trash in the background. Throws the same *shape* of
|
|
313
|
+
* error `execGit` would on refusal (message carries git's own stderr where
|
|
314
|
+
* git produced one) so callers' existing error handling keeps working.
|
|
315
|
+
*/
|
|
316
|
+
declare function removeWorktreeFast(repoRoot: string, path: string, options?: RemoveWorktreeFastOptions): Promise<void>;
|
|
317
|
+
/**
|
|
318
|
+
* Delete every leftover `.trash/*` dir under `root` in the background — the
|
|
319
|
+
* sweep that keeps rename-removal self-cleaning when a detached `rm` child
|
|
320
|
+
* was killed before finishing (or a host rebooted mid-delete). Called at the
|
|
321
|
+
* start of a `gc` apply, where seconds of stale bytes are harmless. Missing
|
|
322
|
+
* root is a no-op, not an error.
|
|
323
|
+
*/
|
|
324
|
+
declare function sweepWorktreeTrash(root: string, options?: {
|
|
325
|
+
spawnRemoval?: (dir: string) => void;
|
|
326
|
+
}): void;
|
|
327
|
+
|
|
248
328
|
/** Compile a `/`-separated glob (supporting `*`, `?`, `**`) into an anchored regex. */
|
|
249
329
|
declare function globToRegExp(pattern: string): RegExp;
|
|
250
330
|
/** Expand a glob against `root`, returning root-relative posix paths of matching files. */
|
|
251
331
|
declare function expandGlob(root: string, pattern: string): Promise<string[]>;
|
|
332
|
+
/** A `cloneGlobs`/`copyGlobs` pattern that could resolve outside its root — a
|
|
333
|
+
* leading `/`, or a literal `..` path segment. */
|
|
334
|
+
declare class GlobTraversalError extends Error {
|
|
335
|
+
constructor(pattern: string);
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* Expand a glob against `root` for CLONING a whole entry. Unlike
|
|
339
|
+
* {@link expandGlob} — which enumerates individual FILES and deliberately
|
|
340
|
+
* skips descending into `node_modules` — this matches whole entries (files
|
|
341
|
+
* OR directories) and never descends past a match: a matching directory is
|
|
342
|
+
* returned as a single unit for the caller to clone as a whole tree, so a
|
|
343
|
+
* pattern like `node_modules` costs one `readdir`, not a walk of everything
|
|
344
|
+
* inside it. `**` is not supported (a clone target must be a single named
|
|
345
|
+
* entry per level, not an arbitrary-depth wildcard) and throws.
|
|
346
|
+
*
|
|
347
|
+
* Throws {@link GlobTraversalError} on a pattern that could resolve outside
|
|
348
|
+
* `root` (a leading `/`, or any `..` segment) — checked before any
|
|
349
|
+
* filesystem access.
|
|
350
|
+
*/
|
|
351
|
+
declare function expandCloneGlob(root: string, pattern: string): Promise<string[]>;
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* Clone every entry matched by `patterns` (see {@link expandCloneGlob}) from
|
|
355
|
+
* `repoRoot` into `cwd`, meant to run BEFORE `depsCmd` so a gitignored,
|
|
356
|
+
* expensive-to-recreate tree (e.g. `node_modules`) is already present when
|
|
357
|
+
* the package manager runs — turning `depsCmd` into a quick verify/repair
|
|
358
|
+
* instead of a full reinstall. Skips an entry whose destination already
|
|
359
|
+
* exists — same never-clobber rule `linkPaths`/`writeFiles` follow in
|
|
360
|
+
* `provision-worktree.body.ts`.
|
|
361
|
+
*/
|
|
362
|
+
declare function cloneEntries(repoRoot: string, cwd: string, patterns: readonly string[]): Promise<void>;
|
|
252
363
|
|
|
253
364
|
/**
|
|
254
365
|
* `agentproto.json` — the declarative per-repo worktree lifecycle.
|
|
@@ -310,6 +421,71 @@ declare function getScript(config: AgentprotoConfig, name: string): NamedScript
|
|
|
310
421
|
*/
|
|
311
422
|
declare function loadConfigFromBase(repoRoot: string, base?: string): Promise<AgentprotoConfig | null>;
|
|
312
423
|
|
|
424
|
+
/**
|
|
425
|
+
* `<workspace>/.agentproto/worktree.json` — a LOCAL, host-owned worktree
|
|
426
|
+
* lifecycle default, read straight off disk (never `git show`, never a
|
|
427
|
+
* worktree's own tree). Same idea as `<workspace>/.agentproto/allowed-
|
|
428
|
+
* commands.json` (`command-allowlist.ts`): a per-machine setting that lets
|
|
429
|
+
* one host declare what a fresh worktree needs (a `node_modules` clone, a
|
|
430
|
+
* per-worktree pnpm store) without committing that machine's paths into the
|
|
431
|
+
* repo. `.agentproto/` is gitignored repo-wide (see this repo's own
|
|
432
|
+
* `.gitignore`), so this file can never ride in on a branch — there is
|
|
433
|
+
* nothing here for `config.ts`'s "only what a reviewer merged" guarantee to
|
|
434
|
+
* defend against; the trust model is simply "whoever owns this machine's
|
|
435
|
+
* filesystem".
|
|
436
|
+
*
|
|
437
|
+
* `workspace` is the SOURCE checkout — the same directory passed as
|
|
438
|
+
* `provisionWorktreeTool`'s `repoRoot` — never the freshly created worktree
|
|
439
|
+
* (which starts with no `.agentproto/worktree.json` of its own, since the
|
|
440
|
+
* dir is gitignored and nothing copies it in).
|
|
441
|
+
*/
|
|
442
|
+
declare const LOCAL_WORKTREE_CONFIG_REL = ".agentproto/worktree.json";
|
|
443
|
+
declare const localWriteFileEntrySchema: z.ZodObject<{
|
|
444
|
+
path: z.ZodString;
|
|
445
|
+
content: z.ZodString;
|
|
446
|
+
mode: z.ZodOptional<z.ZodEnum<{
|
|
447
|
+
create: "create";
|
|
448
|
+
append: "append";
|
|
449
|
+
}>>;
|
|
450
|
+
}, z.core.$strip>;
|
|
451
|
+
declare const localWorktreeConfigSchema: z.ZodObject<{
|
|
452
|
+
copyGlobs: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
453
|
+
cloneGlobs: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
454
|
+
writeFiles: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
455
|
+
path: z.ZodString;
|
|
456
|
+
content: z.ZodString;
|
|
457
|
+
mode: z.ZodOptional<z.ZodEnum<{
|
|
458
|
+
create: "create";
|
|
459
|
+
append: "append";
|
|
460
|
+
}>>;
|
|
461
|
+
}, z.core.$strip>>>;
|
|
462
|
+
depsCmd: z.ZodOptional<z.ZodString>;
|
|
463
|
+
linkPaths: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
464
|
+
}, z.core.$strip>;
|
|
465
|
+
type LocalWriteFileEntry = z.infer<typeof localWriteFileEntrySchema>;
|
|
466
|
+
type LocalWorktreeConfig = z.infer<typeof localWorktreeConfigSchema>;
|
|
467
|
+
/**
|
|
468
|
+
* Parse + validate raw `worktree.json` text. Throws {@link ConfigError} on
|
|
469
|
+
* malformed JSON or a schema violation — same failure shape as
|
|
470
|
+
* `config.ts`'s `parseConfig`, so callers can handle both uniformly.
|
|
471
|
+
*/
|
|
472
|
+
declare function parseLocalWorktreeConfig(raw: string): LocalWorktreeConfig;
|
|
473
|
+
/**
|
|
474
|
+
* Load `<workspace>/.agentproto/worktree.json` straight off disk. Returns
|
|
475
|
+
* `null` when the file is absent (the common case — most workspaces don't
|
|
476
|
+
* opt in). Throws {@link ConfigError} on a present-but-invalid file.
|
|
477
|
+
*/
|
|
478
|
+
declare function loadLocalWorktreeConfig(workspace: string): Promise<LocalWorktreeConfig | null>;
|
|
479
|
+
/** Substitute the literal `{slug}` token for `slug` in `text`. */
|
|
480
|
+
declare function applySlugPlaceholder(text: string, slug: string): string;
|
|
481
|
+
/**
|
|
482
|
+
* Resolve a local config's `writeFiles` for one concrete worktree: expands
|
|
483
|
+
* `{slug}` in `path`/`content` so a declarative entry (e.g. a pnpm
|
|
484
|
+
* `virtualStoreDir` pointed outside the worktree) can be written once and
|
|
485
|
+
* still land at a distinct, collision-free path per worktree.
|
|
486
|
+
*/
|
|
487
|
+
declare function resolveLocalWriteFiles(files: readonly LocalWriteFileEntry[] | undefined, slug: string): LocalWriteFileEntry[];
|
|
488
|
+
|
|
313
489
|
/**
|
|
314
490
|
* Typed env surface for worktree lifecycle hooks, scripts, and services.
|
|
315
491
|
*
|
|
@@ -413,11 +589,29 @@ interface HookRun {
|
|
|
413
589
|
command: string;
|
|
414
590
|
result: ExecResult;
|
|
415
591
|
}
|
|
416
|
-
/** Raised when a `setup` hook exits non-zero — carries the captured output
|
|
592
|
+
/** Raised when a `setup` hook exits non-zero — carries the captured output
|
|
593
|
+
* (see {@link result}) plus a short, noise-filtered tail in `message` for
|
|
594
|
+
* callers that only surface a single string (e.g.
|
|
595
|
+
* `SessionDescriptor.lastError`).
|
|
596
|
+
*
|
|
597
|
+
* Incident (2026-09-27): three back-to-back `agent_start` worktree
|
|
598
|
+
* provisions all failed at `pnpm build`, but every `lastError` showed only
|
|
599
|
+
* the `.npmrc`/`NODE_AUTH_TOKEN` warning + turbo's one-line run summary —
|
|
600
|
+
* never the actual tsup/tsc error. Root cause: the OLD message used
|
|
601
|
+
* `(run.result.stderr || run.result.stdout).trim()` — since that WARN line
|
|
602
|
+
* (and turbo's failure banner) land on stderr, ANY non-empty stderr threw
|
|
603
|
+
* away stdout WHOLESALE, and stdout is exactly where a failing package's
|
|
604
|
+
* own build error prints. Fixed by combining both streams, filtering only
|
|
605
|
+
* the known-noise lines (not a whole stream), and keeping a real tail of
|
|
606
|
+
* the combined output instead of picking one stream whole. */
|
|
417
607
|
declare class HookError extends Error {
|
|
418
608
|
readonly command: string;
|
|
419
609
|
readonly result: ExecResult;
|
|
420
|
-
|
|
610
|
+
/** Path to the full, unfiltered stdout+stderr log for this and every
|
|
611
|
+
* other hook command in the same run, when the caller asked for one to
|
|
612
|
+
* be persisted (see {@link runSetup}'s `opts.logPath`). */
|
|
613
|
+
readonly logPath?: string;
|
|
614
|
+
constructor(phase: "setup" | "teardown", run: HookRun, logPath?: string);
|
|
421
615
|
}
|
|
422
616
|
/**
|
|
423
617
|
* Run the `worktree.setup` hooks sequentially in the worktree, with the
|
|
@@ -429,14 +623,41 @@ declare class HookError extends Error {
|
|
|
429
623
|
* setup hook that doesn't use turbo simply never reads the var. The first
|
|
430
624
|
* non-zero exit throws {@link HookError} (setup failure must fail
|
|
431
625
|
* provisioning). No-op when the repo declares no setup.
|
|
626
|
+
*
|
|
627
|
+
* `opts.logPath`, when given, gets every command's FULL stdout+stderr
|
|
628
|
+
* appended (success or failure) — see {@link appendHookLog}. Callers that
|
|
629
|
+
* know the failure will surface through a single short string (the daemon's
|
|
630
|
+
* `agent_start` path, via `SessionDescriptor.lastError`) should pass a path
|
|
631
|
+
* under that session's own directory so the full log outlives a
|
|
632
|
+
* subsequently-reclaimed worktree.
|
|
633
|
+
*
|
|
634
|
+
* `opts.retryOnFailure` (default `false`) re-runs a failing command exactly
|
|
635
|
+
* ONCE before giving up — a COMPLEMENT to, never a substitute for, an actual
|
|
636
|
+
* root-cause fix. It exists for the unattended `agent_start` path: a caller
|
|
637
|
+
* with no human watching has no way to retry a transient failure itself
|
|
638
|
+
* short of minting an entirely new worktree (a fresh `git worktree add`,
|
|
639
|
+
* paying the full setup cost again) — see `session-spawn.ts`'s
|
|
640
|
+
* `worktreeSetupLogPath` call sites, which both opt in. Left `false` for the
|
|
641
|
+
* default (`agentproto worktree new`, and this function's own default): an
|
|
642
|
+
* interactive human watching a genuine, deterministic build failure gets it
|
|
643
|
+
* on the first try instead of waiting through an identical, doomed rerun.
|
|
644
|
+
* Both attempts are logged (when `logPath` is given); the thrown
|
|
645
|
+
* `HookError` carries the RETRY's own result, since that's the one the
|
|
646
|
+
* caller is actually giving up on.
|
|
432
647
|
*/
|
|
433
|
-
declare function runSetup(config: AgentprotoConfig, ctx: WorktreeEnvContext
|
|
648
|
+
declare function runSetup(config: AgentprotoConfig, ctx: WorktreeEnvContext, opts?: {
|
|
649
|
+
logPath?: string;
|
|
650
|
+
retryOnFailure?: boolean;
|
|
651
|
+
}): Promise<HookRun[]>;
|
|
434
652
|
/**
|
|
435
653
|
* Run the `worktree.teardown` hooks sequentially. Unlike setup, a failing
|
|
436
654
|
* teardown hook does NOT throw — cleanup must proceed — so failures are
|
|
437
655
|
* returned for the caller to log. No-op when no teardown is declared.
|
|
656
|
+
* `opts.logPath` mirrors {@link runSetup}'s — best-effort, full output.
|
|
438
657
|
*/
|
|
439
|
-
declare function runTeardown(config: AgentprotoConfig, ctx: WorktreeEnvContext
|
|
658
|
+
declare function runTeardown(config: AgentprotoConfig, ctx: WorktreeEnvContext, opts?: {
|
|
659
|
+
logPath?: string;
|
|
660
|
+
}): Promise<HookRun[]>;
|
|
440
661
|
|
|
441
662
|
/**
|
|
442
663
|
* The proxy routing table: `<hostname>` → local service port.
|
|
@@ -849,6 +1070,15 @@ type TreeState = {
|
|
|
849
1070
|
* a liveness-independent "someone touched this a moment ago" signal.
|
|
850
1071
|
*/
|
|
851
1072
|
newestMtimeMs: number | null;
|
|
1073
|
+
/**
|
|
1074
|
+
* Set to `.plans/` when every non-noise dirty path starts with it —
|
|
1075
|
+
* i.e. the ENTIRE tree's dirt lives under `.plans/`. `undefined`
|
|
1076
|
+
* otherwise (mixed dirt, or dirt outside `.plans/` entirely). Feeds
|
|
1077
|
+
* gc's plans-only salvage rule (`classifyForGc`, gc.ts): `.plans/`
|
|
1078
|
+
* holds the user's plan files, never to be discarded, but also never a
|
|
1079
|
+
* reason to hold a no-commit worktree forever.
|
|
1080
|
+
*/
|
|
1081
|
+
onlyUnder?: string;
|
|
852
1082
|
};
|
|
853
1083
|
interface ComputeTreeStateOptions {
|
|
854
1084
|
/**
|
|
@@ -1282,10 +1512,21 @@ interface ClassifyForGcOptions {
|
|
|
1282
1512
|
}
|
|
1283
1513
|
/**
|
|
1284
1514
|
* gc's own classifier, layered on top of `classify` (status.ts) rather than
|
|
1285
|
-
* reimplementing it — reuse first (AGENTS.md, PLAN.md §0.6).
|
|
1286
|
-
* `
|
|
1287
|
-
*
|
|
1288
|
-
*
|
|
1515
|
+
* reimplementing it — reuse first (AGENTS.md, PLAN.md §0.6). `classify`
|
|
1516
|
+
* itself never lets a `fresh` worktree salvage, clean or not — that
|
|
1517
|
+
* invariant is load-bearing for `ls --status` and every other caller (see
|
|
1518
|
+
* its 2026-07-15 note) and stays untouched here. What `classify` cannot know
|
|
1519
|
+
* is gc-specific archival policy; two overrides live here instead:
|
|
1520
|
+
* - `--include-detached`: a clean, idle DETACHED worktree moves `hold` →
|
|
1521
|
+
* `reclaim`.
|
|
1522
|
+
* - the "plans-only" rule below: a no-commit worktree (`fresh`, or
|
|
1523
|
+
* `detached` when `includeDetached` is set — same gate, since a detached
|
|
1524
|
+
* HEAD's commit safety is exactly as unproven either way) whose ONLY
|
|
1525
|
+
* dirt is untracked files under `.plans/` moves `hold` → `salvage`, so
|
|
1526
|
+
* `--salvage-dirty` archives the plan files (never discards them) instead
|
|
1527
|
+
* of holding the worktree forever. Narrow on purpose: any OTHER dirt
|
|
1528
|
+
* (even one non-`.plans/` file, or a tracked/staged change) leaves this
|
|
1529
|
+
* untouched and `classify`'s ordinary `hold` stands.
|
|
1289
1530
|
*/
|
|
1290
1531
|
declare function classifyForGc(tree: TreeState, integration: IntegrationState, liveness: LivenessState, options?: ClassifyForGcOptions): GcClass;
|
|
1291
1532
|
/**
|
|
@@ -1453,6 +1694,16 @@ interface ApplyGcOptions {
|
|
|
1453
1694
|
protectedPaths?: string[];
|
|
1454
1695
|
/** See `PlanGcInput.noisePaths`. */
|
|
1455
1696
|
noisePaths?: readonly string[];
|
|
1697
|
+
/**
|
|
1698
|
+
* The bucket directory this repo's worktrees are provisioned under — the
|
|
1699
|
+
* same value `planGc` takes. When passed, apply first fires
|
|
1700
|
+
* `sweepWorktreeTrash(worktreesRoot)` (fast-remove.ts): every leftover
|
|
1701
|
+
* `.trash/*` dir left behind by an interrupted background delete is
|
|
1702
|
+
* handed to a fresh detached `rm -rf` child, so a killed mid-delete
|
|
1703
|
+
* removal (crash, reboot) still converges — seconds of stale bytes at
|
|
1704
|
+
* apply time are harmless, and the sweep never blocks on the deletes.
|
|
1705
|
+
*/
|
|
1706
|
+
worktreesRoot?: string;
|
|
1456
1707
|
}
|
|
1457
1708
|
type GcApplyOutcome = {
|
|
1458
1709
|
path: string;
|
|
@@ -2002,4 +2253,4 @@ declare function recordBranchVerdict(input: {
|
|
|
2002
2253
|
now?: () => string;
|
|
2003
2254
|
}): Promise<BranchVerdictRecord>;
|
|
2004
2255
|
|
|
2005
|
-
export { type AgentprotoConfig, type ApplyBranchGcOptions, type ApplyGcOptions, BRANCH_GC_SCOPES, BRANCH_GC_STATE_DIR, BRANCH_TRIAGE_VERDICTS, BRANCH_VERDICTS_PATH, type BaseDivergence, type BranchCoverage, type BranchGcApplyOutcome, type BranchGcApplyResult, type BranchGcClass, type BranchGcHoldReason, type BranchGcPlan, type BranchGcPlanEntry, type BranchGcReclaimReason, type BranchGcRestoreEntry, type BranchGcRestoreLog, type BranchGcSummary, type BranchHistory, type BranchPushState, type BranchRef, type BranchRefKind, type BranchReviewCandidate, type BranchReviewQueue, type BranchStatus, type BranchTriageVerdict, type BranchVerdict, BranchVerdictError, type BranchVerdictRecord, type BranchVerdictStore, type BranchVerdictSummary, CONFIG_FILENAME, type Classification, type ClassifyForGcOptions, type ComputeProvenanceOptions, type ComputeTreeStateOptions, type ComputeWorktreeStatusInput, ConfigError, DEFAULT_BRANCH_GC_MIN_AGE_DAYS, DEFAULT_GC_NOISE_PATHS, DEFAULT_PROXY_PORT, type DaemonAgentSessionHost, type DaemonClient, ENV_VARS, type ExecOptions, type ExecResult, FileBranchVerdictStore, FileVerdictMemoStore, type ForgeClient, type ForgePullRequestRef, ForgeUnavailableError, type GateState, type GcApplyOutcome, type GcClass, type GcHoldReason, type GcPlanEntry, type GcReclaimReason, GhCliForgeClient, type GitWorktreeRef, HookError, type HookRun, type HostnameParts, type InBaseReclaimReason, InMemoryBranchVerdictStore, InMemoryVerdictMemoStore, type IntegrationState, type LadderContext, type ListWorktreeStatusesInput, type LivenessState, type NamedScript, type PeerService, type PlanBranchGcInput, type PlanGcInput, type ProvenanceConfidence, type ProvenanceInfo, ProxyTable, type ReconcileIntegrationInput, type ResolveGcClassOptions, type ResolveSupervisorInput, type ResolvedGcClass, type ResolvedWorktreeRuntime, RestForgeClient, type RunningProxy, SALVAGE_ROOT, SESSIONS_BUCKETS_ROOT, SESSIONS_FILE_PATH, type SalvageManifest, type SalvageResult, type SalvageWorktreeInput, type ScriptConfig, type ServiceRuntime, type ServiceStatus, ServiceSupervisor, type SessionRef, type SupervisorOptions, type TipClassification, type TreeIndex, type TreeState, UnreachableForgeClient, VERDICT_MEMO_PATH, type VerdictMemoRecord, type VerdictMemoStore, WORKTREES_TURBO_CACHE_DIR_ENV, type WorktreeAgentInput, type WorktreeClass, type WorktreeEnvContext, type WorktreeMarker, WorktreeNotRemovableError, type WorktreeStatusEntry, allocatePort, applyBranchGc, applyGc, branchReviewQueue, branchVerdictSchema, classify, classifyForGc, classifyTip, classifyTipAgainstBase, computeBaseDivergence, computeLiveness, computeProvenance, computeTreeState, computeWorktreeStatus, connectDaemonAgentSessionHost, createForgeClient, createLadderContext, createProxyServer, detectAnchor, detectDefaultBranch, disposeSupervisor, ephemeralPort, execArgv, execGit, execShell, expandGlob, getScript, getSupervisor, globToRegExp, hookEnv, isMechanicalDepBumpRange, isPortFree, listGitWorktrees, listScripts, listServices, listWorktreeStatuses, loadConfigFromBase, makeDaemonAgentSessionHost, normalizeHook, parseConfig, parseGithubOwnerRepo, peerEnv, peerEnvKey, planBranchGc, planGc, readBranchGcRestoreLog, readSessionsRegistry, readWorktreeMarker, reclaimOneWorktree, reconcileIntegration, recordBranchVerdict, repoLabel, resolveGcClass, resolveSupervisor, resolveWorktreesTurboCacheDir, runSetup, runTeardown, salvageWorktree, serviceEnv, serviceEnvToken, serviceHostname, serviceUrl, sessionInWorktree, sharedProxyTable, slugify, startProxy, stripPort, summarizeBranchGcPlan, worktreeAgentInputSchema, worktreeAgentWorkflow, writeWorktreeMarker };
|
|
2256
|
+
export { type AgentprotoConfig, type ApplyBranchGcOptions, type ApplyGcOptions, BRANCH_GC_SCOPES, BRANCH_GC_STATE_DIR, BRANCH_TRIAGE_VERDICTS, BRANCH_VERDICTS_PATH, type BaseDivergence, type BranchCoverage, type BranchGcApplyOutcome, type BranchGcApplyResult, type BranchGcClass, type BranchGcHoldReason, type BranchGcPlan, type BranchGcPlanEntry, type BranchGcReclaimReason, type BranchGcRestoreEntry, type BranchGcRestoreLog, type BranchGcSummary, type BranchHistory, type BranchPushState, type BranchRef, type BranchRefKind, type BranchReviewCandidate, type BranchReviewQueue, type BranchStatus, type BranchTriageVerdict, type BranchVerdict, BranchVerdictError, type BranchVerdictRecord, type BranchVerdictStore, type BranchVerdictSummary, CONFIG_FILENAME, type Classification, type ClassifyForGcOptions, type ComputeProvenanceOptions, type ComputeTreeStateOptions, type ComputeWorktreeStatusInput, ConfigError, DEFAULT_BRANCH_GC_MIN_AGE_DAYS, DEFAULT_GC_NOISE_PATHS, DEFAULT_PROXY_PORT, type DaemonAgentSessionHost, type DaemonClient, ENV_VARS, type ExecOptions, type ExecResult, FileBranchVerdictStore, FileVerdictMemoStore, type ForgeClient, type ForgePullRequestRef, ForgeUnavailableError, type GateState, type GcApplyOutcome, type GcClass, type GcHoldReason, type GcPlanEntry, type GcReclaimReason, GhCliForgeClient, type GitWorktreeRef, GlobTraversalError, HookError, type HookRun, type HostnameParts, type InBaseReclaimReason, InMemoryBranchVerdictStore, InMemoryVerdictMemoStore, type IntegrationState, LOCAL_WORKTREE_CONFIG_REL, type LadderContext, type ListWorktreeStatusesInput, type LivenessState, type LocalWorktreeConfig, type LocalWriteFileEntry, type NamedScript, type PeerService, type PlanBranchGcInput, type PlanGcInput, type ProvenanceConfidence, type ProvenanceInfo, ProxyTable, type ReconcileIntegrationInput, type RemoveWorktreeFastOptions, type ResolveGcClassOptions, type ResolveSupervisorInput, type ResolvedGcClass, type ResolvedWorktreeRuntime, RestForgeClient, type RunningProxy, SALVAGE_ROOT, SESSIONS_BUCKETS_ROOT, SESSIONS_FILE_PATH, type SalvageManifest, type SalvageResult, type SalvageWorktreeInput, type ScriptConfig, type ServiceRuntime, type ServiceStatus, ServiceSupervisor, type SessionRef, type SupervisorOptions, type TipClassification, type TreeIndex, type TreeState, UnreachableForgeClient, VERDICT_MEMO_PATH, type VerdictMemoRecord, type VerdictMemoStore, WORKTREES_TURBO_CACHE_DIR_ENV, WORKTREE_TRASH_DIRNAME, type WorktreeAgentInput, type WorktreeClass, type WorktreeEnvContext, type WorktreeMarker, WorktreeNotRemovableError, type WorktreeStatusEntry, allocatePort, applyBranchGc, applyGc, applySlugPlaceholder, branchReviewQueue, branchVerdictSchema, classify, classifyForGc, classifyTip, classifyTipAgainstBase, cloneEntries, computeBaseDivergence, computeLiveness, computeProvenance, computeTreeState, computeWorktreeStatus, connectDaemonAgentSessionHost, createForgeClient, createLadderContext, createProxyServer, detectAnchor, detectDefaultBranch, disposeSupervisor, ephemeralPort, execArgv, execGit, execShell, expandCloneGlob, expandGlob, getScript, getSupervisor, globToRegExp, hookEnv, isMechanicalDepBumpRange, isPortFree, listGitWorktrees, listScripts, listServices, listWorktreeStatuses, loadConfigFromBase, loadLocalWorktreeConfig, makeDaemonAgentSessionHost, normalizeHook, parseConfig, parseGithubOwnerRepo, parseLocalWorktreeConfig, peerEnv, peerEnvKey, planBranchGc, planGc, readBranchGcRestoreLog, readSessionsRegistry, readWorktreeMarker, reclaimOneWorktree, reconcileIntegration, recordBranchVerdict, removeWorktreeFast, repoLabel, resolveGcClass, resolveLocalWriteFiles, resolveSupervisor, resolveWorktreesTurboCacheDir, runSetup, runTeardown, salvageWorktree, serviceEnv, serviceEnvToken, serviceHostname, serviceUrl, sessionInWorktree, sharedProxyTable, slugify, startProxy, stripPort, summarizeBranchGcPlan, sweepWorktreeTrash, worktreeAgentInputSchema, worktreeAgentWorkflow, writeWorktreeMarker };
|
package/dist/index.mjs
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
export { connectDaemonAgentSessionHost, makeDaemonAgentSessionHost, worktreeAgentInputSchema, worktreeAgentWorkflow } from './chunk-
|
|
1
|
+
export { connectDaemonAgentSessionHost, makeDaemonAgentSessionHost, worktreeAgentInputSchema, worktreeAgentWorkflow } from './chunk-YMDOSMMD.mjs';
|
|
2
2
|
import './chunk-E3EM24BS.mjs';
|
|
3
|
-
import { computeProvenance, execArgv, classify, computeWorktreeStatus, listGitWorktrees, execGit, worktreeProvider } from './chunk-
|
|
4
|
-
export { CONFIG_FILENAME, ConfigError, DEFAULT_PROXY_PORT, ENV_VARS, FileVerdictMemoStore, HookError, InMemoryVerdictMemoStore, ProxyTable, SESSIONS_BUCKETS_ROOT, SESSIONS_FILE_PATH, ServiceSupervisor, VERDICT_MEMO_PATH, WORKTREES_TURBO_CACHE_DIR_ENV, WorktreeNotRemovableError, allocatePort, classify, computeBaseDivergence, computeLiveness, computeProvenance, computeTreeState, computeWorktreeStatus, detectDefaultBranch, disposeSupervisor, ephemeralPort, execArgv, execGit, execShell, expandGlob, getScript, getSupervisor, globToRegExp, hookEnv, isPortFree, listGitWorktrees, listScripts, listServices, listWorktreeStatuses, loadConfigFromBase, normalizeHook, parseConfig, peerEnv, peerEnvKey, readSessionsRegistry, readWorktreeMarker, reconcileIntegration, repoLabel, resolveSupervisor, resolveWorktreesTurboCacheDir, runSetup, runTeardown, serviceEnv, serviceEnvToken, serviceHostname, serviceUrl, sessionInWorktree, sharedProxyTable, slugify, stripPort, worktreeProvider, writeWorktreeMarker } from './chunk-
|
|
5
|
-
import { cleanupWorktreeTool } from './chunk-
|
|
6
|
-
export { cleanupWorktreeTool, listServicesTool, provisionWorktreeTool, runGateTool, runScriptTool, serviceStatusSchema, startServiceTool, stopServiceTool } from './chunk-
|
|
3
|
+
import { computeProvenance, execArgv, classify, computeWorktreeStatus, sweepWorktreeTrash, listGitWorktrees, execGit, worktreeProvider } from './chunk-MYH5XOX6.mjs';
|
|
4
|
+
export { CONFIG_FILENAME, ConfigError, DEFAULT_PROXY_PORT, ENV_VARS, FileVerdictMemoStore, GlobTraversalError, HookError, InMemoryVerdictMemoStore, LOCAL_WORKTREE_CONFIG_REL, ProxyTable, SESSIONS_BUCKETS_ROOT, SESSIONS_FILE_PATH, ServiceSupervisor, VERDICT_MEMO_PATH, WORKTREES_TURBO_CACHE_DIR_ENV, WORKTREE_TRASH_DIRNAME, WorktreeNotRemovableError, allocatePort, applySlugPlaceholder, classify, cloneEntries, computeBaseDivergence, computeLiveness, computeProvenance, computeTreeState, computeWorktreeStatus, detectDefaultBranch, disposeSupervisor, ephemeralPort, execArgv, execGit, execShell, expandCloneGlob, expandGlob, getScript, getSupervisor, globToRegExp, hookEnv, isPortFree, listGitWorktrees, listScripts, listServices, listWorktreeStatuses, loadConfigFromBase, loadLocalWorktreeConfig, normalizeHook, parseConfig, parseLocalWorktreeConfig, peerEnv, peerEnvKey, readSessionsRegistry, readWorktreeMarker, reconcileIntegration, removeWorktreeFast, repoLabel, resolveLocalWriteFiles, resolveSupervisor, resolveWorktreesTurboCacheDir, runSetup, runTeardown, serviceEnv, serviceEnvToken, serviceHostname, serviceUrl, sessionInWorktree, sharedProxyTable, slugify, stripPort, sweepWorktreeTrash, worktreeProvider, writeWorktreeMarker } from './chunk-MYH5XOX6.mjs';
|
|
5
|
+
import { cleanupWorktreeTool } from './chunk-3FZWPMLF.mjs';
|
|
6
|
+
export { cleanupWorktreeTool, listServicesTool, provisionWorktreeTool, runGateTool, runScriptTool, serviceStatusSchema, startServiceTool, stopServiceTool } from './chunk-3FZWPMLF.mjs';
|
|
7
7
|
import { mkdir, copyFile, readFile, writeFile, open, rename, readdir, realpath, rm, stat } from 'fs/promises';
|
|
8
8
|
import { homedir } from 'os';
|
|
9
9
|
import { resolve, join, dirname, basename, sep, relative, isAbsolute } from 'path';
|
|
@@ -1215,13 +1215,22 @@ async function recordBranchVerdict(input) {
|
|
|
1215
1215
|
// src/gc.ts
|
|
1216
1216
|
var candidates = [worktreeProvider];
|
|
1217
1217
|
function classifyForGc(tree, integration, liveness, options = {}) {
|
|
1218
|
+
const idleOrUnreachable = liveness.state === "idle" || liveness.state === "daemon-unreachable";
|
|
1218
1219
|
if (options.includeDetached && integration.state === "detached") {
|
|
1219
|
-
const idleOrUnreachable = liveness.state === "idle" || liveness.state === "daemon-unreachable";
|
|
1220
1220
|
if (tree.state === "clean" && idleOrUnreachable) return "reclaim";
|
|
1221
1221
|
}
|
|
1222
|
+
const noCommitsOfItsOwn = integration.state === "fresh" || Boolean(options.includeDetached) && integration.state === "detached";
|
|
1223
|
+
if (noCommitsOfItsOwn && idleOrUnreachable && tree.state === "dirty" && tree.modified === 0 && tree.staged === 0 && tree.untracked > 0 && tree.onlyUnder === ".plans/") {
|
|
1224
|
+
return "salvage";
|
|
1225
|
+
}
|
|
1222
1226
|
return classify(tree, integration, liveness, options.nowMs).class;
|
|
1223
1227
|
}
|
|
1224
|
-
var DEFAULT_GC_NOISE_PATHS = [
|
|
1228
|
+
var DEFAULT_GC_NOISE_PATHS = [
|
|
1229
|
+
"pnpm-lock.yaml",
|
|
1230
|
+
"package-lock.json",
|
|
1231
|
+
".opencode/package-lock.json",
|
|
1232
|
+
".claude/launch.json"
|
|
1233
|
+
];
|
|
1225
1234
|
function isProtectedPath(worktreePath, protectedPaths) {
|
|
1226
1235
|
if (!protectedPaths || protectedPaths.length === 0) return false;
|
|
1227
1236
|
return protectedPaths.some((p) => p === worktreePath || p.startsWith(worktreePath + sep));
|
|
@@ -1646,6 +1655,7 @@ async function applyOne(entry, options) {
|
|
|
1646
1655
|
return salvageOne(options, fresh);
|
|
1647
1656
|
}
|
|
1648
1657
|
async function applyGc(plan, options) {
|
|
1658
|
+
if (options.worktreesRoot) sweepWorktreeTrash(options.worktreesRoot);
|
|
1649
1659
|
const outcomes = [];
|
|
1650
1660
|
for (const entry of plan) {
|
|
1651
1661
|
outcomes.push(await applyOne(entry, options));
|