@agentproto/worktree 0.13.0 → 0.14.1

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 CHANGED
@@ -167,6 +167,41 @@ agentproto worktree archive <path> [--base <ref>] [--keep-branch] [--json]
167
167
  runs its teardown hooks, and removes it (deleting the branch unless
168
168
  `--keep-branch`).
169
169
 
170
+ ## Provisioning concurrency (daemon-wide queue)
171
+
172
+ `worktree.provision` runs its heavy phases (`cloneGlobs`, `depsCmd`,
173
+ `copyGlobs`, `setup` hooks) through `provisionScheduler`, a process-wide FIFO
174
+ queue with a cap (default 2; `0` = unlimited). One slot covers the whole heavy
175
+ segment of one provisioning, so a single spawn never interleaves with itself.
176
+ `git worktree add` and the cheap prep (`linkPaths`, `writeFiles`) run before
177
+ the queue and are never throttled.
178
+
179
+ ```ts
180
+ import { provisionScheduler, parseProvisionLimits, runWithProvisionContext } from "@agentproto/worktree"
181
+
182
+ provisionScheduler.configure(parseProvisionLimits(config.worktrees))
183
+
184
+ await runWithProvisionContext(
185
+ { callerKey: parentSessionId, signal, onProgress: p => log(p) },
186
+ () => runTool({ tool: provisionWorktreeTool, candidates, input }),
187
+ )
188
+ ```
189
+
190
+ - **Fairness.** FIFO, with the caller holding the fewest running slots going
191
+ first, so one orchestrator's burst cannot starve another caller.
192
+ - **Cancellation.** Aborting `signal` drops a queued entry, or kills a running
193
+ phase's whole process group (SIGTERM, then SIGKILL after 5s), and rejects
194
+ with `ProvisionCancelledError`. A cancelled provisioning removes its
195
+ half-made worktree and branch; a plain failure keeps the worktree.
196
+ - **Progress.** `onProgress` receives `queued` (with `position`), `started`,
197
+ `phase` and `done` (`ok`, `failed` or `cancelled`).
198
+ - **Config.** `worktrees.provisionConcurrency`,
199
+ `worktrees.provisionConcurrencyByRepo`, `worktrees.provisionLoadFactor` in
200
+ `~/.agentproto/config.json`; env `AGENTPROTO_WORKTREES_PROVISION_CONCURRENCY`
201
+ wins. See the CLI docs for the full table.
202
+ - **Scope.** The queue is per process. The daemon owns the shared one;
203
+ `agentproto worktree new` has its own instance.
204
+
170
205
  ## `worktreeAgentWorkflow`
171
206
 
172
207
  This package also exports the `RuntimeWorkflow` def that chains the three
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
- import { connectDaemonAgentSessionHost, worktreeAgentWorkflow } from '../chunk-YMDOSMMD.mjs';
2
+ import { connectDaemonAgentSessionHost, worktreeAgentWorkflow } from '../chunk-FC3I5NTS.mjs';
3
3
  import '../chunk-E3EM24BS.mjs';
4
- import '../chunk-MYH5XOX6.mjs';
4
+ import '../chunk-IEW2O3PF.mjs';
5
5
  import '../chunk-3FZWPMLF.mjs';
6
6
  import { runWorkflow } from '@agentproto/workflow-runtime';
7
7
  import { resolve } from 'path';
@@ -1,4 +1,4 @@
1
- import { worktreeProvider } from './chunk-MYH5XOX6.mjs';
1
+ import { worktreeProvider } from './chunk-IEW2O3PF.mjs';
2
2
  import { provisionWorktreeTool, runGateTool, cleanupWorktreeTool } from './chunk-3FZWPMLF.mjs';
3
3
  import { z } from 'zod';
4
4
  import { resolveMcpUrl, HarnessClient } from '@agentproto/harness';
@@ -196,5 +196,5 @@ async function waitForSettled(client, sessionId, since) {
196
196
  }
197
197
 
198
198
  export { connectDaemonAgentSessionHost, makeDaemonAgentSessionHost, worktreeAgentInputSchema, worktreeAgentWorkflow };
199
- //# sourceMappingURL=chunk-YMDOSMMD.mjs.map
200
- //# sourceMappingURL=chunk-YMDOSMMD.mjs.map
199
+ //# sourceMappingURL=chunk-FC3I5NTS.mjs.map
200
+ //# sourceMappingURL=chunk-FC3I5NTS.mjs.map
@@ -1 +1 @@
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"]}
1
+ {"version":3,"sources":["../src/workflow.ts","../src/agent-session-host.ts"],"names":[],"mappings":";;;;;;;;;AASO,IAAM,wBAAA,GAA2B,EAAE,MAAA,CAAO;AAAA,EAC/C,QAAA,EAAU,CAAA,CAAE,MAAA,EAAO,CAAE,SAAS,2CAA2C,CAAA;AAAA,EACzE,IAAA,EAAM,CAAA,CAAE,MAAA,EAAO,CAAE,SAAS,oDAAoD,CAAA;AAAA,EAC9E,MAAM,CAAA,CAAE,MAAA,GAAS,QAAA,EAAS,CAAE,SAAS,6DAA6D,CAAA;AAAA,EAClG,SAAS,CAAA,CAAE,MAAA,GAAS,QAAA,EAAS,CAAE,SAAS,oFAAoF,CAAA;AAAA,EAC5H,SAAA,EAAW,CAAA,CAAE,KAAA,CAAM,CAAA,CAAE,MAAA,EAAQ,CAAA,CAAE,QAAA,EAAS,CAAE,QAAA,CAAS,2DAA2D,CAAA;AAAA,EAC9G,SAAA,EAAW,CAAA,CAAE,KAAA,CAAM,CAAA,CAAE,MAAA,EAAQ,CAAA,CAAE,QAAA,EAAS,CAAE,QAAA,CAAS,6KAA6K,CAAA;AAAA,EAChO,YAAY,CAAA,CACT,KAAA;AAAA,IACC,EAAE,MAAA,CAAO;AAAA,MACP,IAAA,EAAM,CAAA,CAAE,MAAA,EAAO,CAAE,SAAS,qCAAqC,CAAA;AAAA,MAC/D,OAAA,EAAS,CAAA,CAAE,MAAA,EAAO,CAAE,SAAS,6CAA6C,CAAA;AAAA,MAC1E,IAAA,EAAM,CAAA,CACH,IAAA,CAAK,CAAC,QAAA,EAAU,QAAQ,CAAC,CAAA,CACzB,QAAA,EAAS,CACT,QAAA,CAAS,4KAA4K;AAAA,KACzL;AAAA,GACH,CACC,QAAA,EAAS,CACT,QAAA,CAAS,0HAA0H,CAAA;AAAA,EACtI,IAAA,EAAM,CAAA,CAAE,MAAA,EAAO,CAAE,SAAS,sCAAsC,CAAA;AAAA,EAChE,SAAS,CAAA,CAAE,MAAA,GAAS,QAAA,EAAS,CAAE,SAAS,4CAA4C,CAAA;AAAA,EACpF,OAAA,EAAS,CAAA,CAAE,MAAA,EAAO,CAAE,SAAS,8EAA8E,CAAA;AAAA,EAC3G,cAAc,CAAA,CAAE,OAAA,GAAU,QAAA,EAAS,CAAE,SAAS,wDAAwD;AACxG,CAAC;AAID,IAAM,UAAA,GAAa,CAAC,gBAAgB,CAAA;AAEpC,SAAS,MAAM,CAAA,EAA2C;AACxD,EAAA,OAAO,CAAA,CAAE,KAAA;AACX;AAOO,IAAM,qBAAA,GAAyC;AAAA,EACpD,EAAA,EAAI,gBAAA;AAAA,EACJ,WAAA,EACE,kNAAA;AAAA,EAGF,KAAA,EAAO;AAAA,IACL;AAAA,MACE,IAAA,EAAM,MAAA;AAAA,MACN,EAAA,EAAI,WAAA;AAAA,MACJ,IAAA,EAAM,qBAAA;AAAA,MACN,UAAA;AAAA,MACA,KAAA,EAAO,CAAC,CAAA,KAAgB;AACtB,QAAA,MAAM,CAAA,GAAI,MAAM,CAAC,CAAA;AACjB,QAAA,OAAO;AAAA,UACL,UAAU,CAAA,CAAE,QAAA;AAAA,UACZ,MAAM,CAAA,CAAE,IAAA;AAAA,UACR,MAAM,CAAA,CAAE,IAAA;AAAA,UACR,SAAS,CAAA,CAAE,OAAA;AAAA,UACX,WAAW,CAAA,CAAE,SAAA;AAAA,UACb,WAAW,CAAA,CAAE,SAAA;AAAA,UACb,YAAY,CAAA,CAAE;AAAA,SAChB;AAAA,MACF;AAAA,KACF;AAAA,IACA;AAAA,MACE,IAAA,EAAM,OAAA;AAAA,MACN,EAAA,EAAI,MAAA;AAAA,MACJ,SAAS,CAAC,CAAA,KAAgB,KAAA,CAAM,CAAC,EAAE,OAAA,IAAW,aAAA;AAAA,MAC9C,GAAA,EAAK,CAAC,CAAA,KAAiB,CAAA,CAAE,MAAM,SAAA,CAA8B,GAAA;AAAA,MAC7D,MAAA,EAAQ,CAAC,CAAA,KAAgB,KAAA,CAAM,CAAC,CAAA,CAAE;AAAA,KACpC;AAAA,IACA;AAAA,MACE,IAAA,EAAM,MAAA;AAAA,MACN,EAAA,EAAI,MAAA;AAAA,MACJ,IAAA,EAAM,WAAA;AAAA,MACN,UAAA;AAAA,MACA,KAAA,EAAO,CAAC,CAAA,MAAiB;AAAA,QACvB,GAAA,EAAM,CAAA,CAAE,KAAA,CAAM,SAAA,CAA8B,GAAA;AAAA,QAC5C,GAAA,EAAK,KAAA,CAAM,CAAC,CAAA,CAAE;AAAA,OAChB;AAAA,KACF;AAAA,IACA;AAAA,MACE,IAAA,EAAM,QAAA;AAAA,MACN,EAAA,EAAI,OAAA;AAAA,MACJ,IAAA,EAAM,CAAC,CAAA,KAAiB,CAAA,CAAE,MAAM,IAAA,CAA6B,MAAA;AAAA,MAC7D,IAAA,EAAM;AAAA,QACJ;AAAA,UACE,IAAA,EAAM,UAAA;AAAA,UACN,EAAA,EAAI,UAAA;AAAA,UACJ,QAAQ,MAAM,wCAAA;AAAA,UACd,SAAA,EAAW;AAAA,YACT;AAAA,cACE,IAAA,EAAM,MAAA;AAAA,cACN,EAAA,EAAI,SAAA;AAAA,cACJ,IAAA,EAAM,mBAAA;AAAA,cACN,UAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,cAOA,KAAA,EAAO,CAAC,CAAA,MAAiB;AAAA,gBACvB,QAAA,EAAU,KAAA,CAAM,CAAC,CAAA,CAAE,QAAA;AAAA,gBACnB,GAAA,EAAM,CAAA,CAAE,KAAA,CAAM,SAAA,CAA8B,GAAA;AAAA,gBAC5C,MAAA,EAAS,CAAA,CAAE,KAAA,CAAM,SAAA,CAAiC,MAAA;AAAA,gBAClD,YAAA,EAAc,KAAA,CAAM,CAAC,CAAA,CAAE,YAAA,IAAgB,IAAA;AAAA,gBACvC,gBAAA,EAAkB,IAAA;AAAA,gBAClB,eAAA,EAAiB;AAAA,eACnB;AAAA;AACF,WACF;AAAA,UACA,QAAA,EAAU;AAAA,YACR;AAAA,cACE,IAAA,EAAM,WAAA;AAAA,cACN,EAAA,EAAI,MAAA;AAAA,cACJ,SAAS,OAAO,EAAE,OAAA,EAAS,KAAA,EAAO,QAAQ,kBAAA,EAA4B;AAAA;AACxE;AACF;AACF,OACF;AAAA,MACA,SAAA,EAAW;AAAA,QACT;AAAA,UACE,IAAA,EAAM,WAAA;AAAA,UACN,EAAA,EAAI,aAAA;AAAA,UACJ,SAAS,OAAO,EAAE,OAAA,EAAS,KAAA,EAAO,QAAQ,aAAA,EAAuB;AAAA;AACnE;AACF;AACF,GACF;AAAA,EACA,MAAA,EAAQ,CAAC,CAAA,MAAiB;AAAA,IACxB,GAAA,EAAM,CAAA,CAAE,KAAA,CAAM,SAAA,CAA8B,GAAA;AAAA,IAC5C,MAAA,EAAS,CAAA,CAAE,KAAA,CAAM,SAAA,CAAiC,MAAA;AAAA,IAClD,IAAA,EAAM,EAAE,KAAA,CAAM,IAAA;AAAA,IACd,OAAA,EAAS,EAAE,KAAA,CAAM,OAAA,IAAW,EAAE,KAAA,CAAM,IAAA,IAAQ,CAAA,CAAE,KAAA,CAAM,aAAa;AAAA,GACnE;AACF;ACpIA,IAAM,WAAA,GAAc,IAAA;AAmDpB,eAAsB,8BACpB,IAAA,EACiC;AACjC,EAAA,MAAM,GAAA,GAAM,cAAc,IAAI,CAAA;AAC9B,EAAA,IAAI,MAAA;AACJ,EAAA,IAAI;AACF,IAAA,MAAA,GAAS,MAAM,aAAA,CAAc,OAAA,CAAQ,EAAE,KAAK,CAAA;AAAA,EAC9C,SAAS,GAAA,EAAK;AACZ,IAAA,MAAM,IAAI,KAAA;AAAA,MACR,2EAA2E,GAAG,CAAA;AAAA,CAAA,EAGxE,eAAe,KAAA,GAAQ,GAAA,CAAI,OAAA,GAAU,MAAA,CAAO,GAAG,CAAC,CAAA,CAAA;AAAA,KACxD;AAAA,EACF;AACA,EAAA,OAAO,2BAA2B,MAAM,CAAA;AAC1C;AAGO,SAAS,2BAA2B,MAAA,EAA8C;AACvF,EAAA,MAAM,eAAA,uBAAsB,GAAA,EAAoB;AAEhD,EAAA,OAAO;AAAA,IACL,MAAM,KAAA,CAAM,OAAA,EAAS,IAAA,EAAuB;AAC1C,MAAA,MAAM,IAAA,GAAO,MAAM,MAAA,CAAO,KAAA,CAAM;AAAA,QAC9B,OAAA;AAAA,QACA,GAAI,KAAK,GAAA,KAAQ,MAAA,GAAY,EAAE,GAAA,EAAK,IAAA,CAAK,GAAA,EAAI,GAAI,EAAC;AAAA,QAClD,GAAI,KAAK,aAAA,KAAkB,MAAA,GAAY,EAAE,aAAA,EAAe,IAAA,CAAK,aAAA,EAAc,GAAI,EAAC;AAAA,QAChF,GAAI,KAAK,MAAA,KAAW,MAAA,GAAY,EAAE,KAAA,EAAO,IAAA,CAAK,MAAA,EAAO,GAAI;AAAC,OAC3D,CAAA;AACD,MAAA,IAAI,KAAK,MAAA,EAAQ,eAAA,CAAgB,IAAI,IAAA,CAAK,MAAA,EAAQ,KAAK,EAAE,CAAA;AACzD,MAAA,OAAO,IAAA,CAAK,EAAA;AAAA,IACd,CAAA;AAAA,IAEA,MAAM,iBAAA,CAAkB,SAAA,EAAW,MAAA,EAAuB;AASxD,MAAA,MAAM,KAAA,GAAQ,MAAM,MAAA,CAAO,mBAAA,EAAoB;AAC/C,MAAA,MAAM,MAAA,CAAO,MAAA,CAAO,SAAA,EAAW,MAAM,CAAA;AACrC,MAAA,MAAM,cAAA,CAAe,MAAA,EAAQ,SAAA,EAAW,KAAK,CAAA;AAAA,IAC/C,CAAA;AAAA,IAEA,eAAe,MAAA,EAA4B;AACzC,MAAA,OAAO,eAAA,CAAgB,IAAI,MAAM,CAAA;AAAA,IACnC,CAAA;AAAA,IAEA,MAAM,MAAM,IAAA,EAAkC;AAC5C,MAAA,OAAO,MAAA,CAAO,MAAM,IAAI,CAAA;AAAA,IAC1B,CAAA;AAAA,IAEA,MAAM,MAAA,CAAO,SAAA,EAAW,MAAA,EAAQ,IAAA,EAAqB;AACnD,MAAA,MAAM,MAAA,CAAO,MAAA,CAAO,SAAA,EAAW,MAAA,EAAQ,IAAI,CAAA;AAAA,IAC7C,CAAA;AAAA,IAEA,MAAM,MAAA,CAAO,SAAA,EAAW,KAAA,EAAwB;AAC9C,MAAA,OAAO,MAAA,CAAO,MAAA,CAAO,SAAA,EAAW,KAAK,CAAA;AAAA,IACvC,CAAA;AAAA,IAEA,MAAM,KAAK,SAAA,EAA0B;AACnC,MAAA,MAAM,MAAA,CAAO,KAAK,SAAS,CAAA;AAAA,IAC7B,CAAA;AAAA,IAEA,MAAM,UAAA,CAAW,UAAA,EAAY,IAAA,EAA2B;AACtD,MAAA,OAAO,MAAA,CAAO,UAAA,CAAW,UAAA,EAAY,IAAI,CAAA;AAAA,IAC3C,CAAA;AAAA,IAEA,MAAM,mBAAA,GAAuC;AAC3C,MAAA,OAAO,OAAO,mBAAA,EAAoB;AAAA,IACpC,CAAA;AAAA,IAEA,GAAI,OAAO,MAAA,CAAO,KAAA,KAAU,UAAA,GACxB;AAAA,MACE,MAAM,MAAM,SAAA,EAAkD;AAC5D,QAAA,OAAO,MAAA,CAAO,MAAO,SAAS,CAAA;AAAA,MAChC;AAAA,QAEF,EAAC;AAAA,IAEL,MAAM,KAAA,GAAuB;AAC3B,MAAA,MAAM,OAAO,KAAA,EAAM;AAAA,IACrB;AAAA,GACF;AACF;AAWA,eAAe,cAAA,CAAe,MAAA,EAAsB,SAAA,EAAmB,KAAA,EAA8B;AACnG,EAAA,WAAS;AACP,IAAA,MAAM,MAAA,GAAS,MAAM,MAAA,CAAO,UAAA,CAAW,CAAC,SAAS,CAAA,EAAG,EAAE,KAAA,EAAO,KAAA,EAAO,SAAA,EAAW,WAAA,EAAa,OAAO,CAAA;AACnG,IAAA,IAAI,CAAC,OAAO,QAAA,EAAU;AAAA,EACxB;AACF","file":"chunk-FC3I5NTS.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"]}