@fitchmultz/pi-subagents 0.44.7 → 0.46.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.
Files changed (955) hide show
  1. package/CHANGELOG.md +264 -7
  2. package/LICENSE +1 -0
  3. package/README.md +58 -1483
  4. package/agents/context-builder.md +19 -1
  5. package/agents/debugger.md +10 -3
  6. package/agents/delegate.md +1 -0
  7. package/agents/fixer.md +4 -1
  8. package/agents/oracle.md +13 -2
  9. package/agents/planner.md +14 -3
  10. package/agents/researcher.md +11 -2
  11. package/agents/reviewer-claude.md +11 -2
  12. package/agents/reviewer-gpt.md +12 -2
  13. package/agents/reviewer-ponytail.md +11 -1
  14. package/agents/reviewer-security.md +13 -2
  15. package/agents/reviewer.md +12 -3
  16. package/agents/scout.md +12 -1
  17. package/agents/ui-designer.md +10 -3
  18. package/agents/watcher.md +1 -0
  19. package/agents/worker.md +8 -2
  20. package/agents/writer.md +6 -3
  21. package/dist/agents/agent-defaults.js +9 -0
  22. package/dist/agents/agent-definition.js +196 -0
  23. package/dist/agents/agent-management.js +64 -905
  24. package/dist/agents/agent-scope.js +4 -2
  25. package/dist/agents/agent-selection.js +10 -5
  26. package/dist/agents/agent-serializer.js +55 -67
  27. package/dist/agents/agents.js +75 -716
  28. package/dist/agents/builtin-overrides.js +218 -0
  29. package/dist/agents/chain-serializer.js +3 -367
  30. package/dist/agents/config-values.js +52 -0
  31. package/dist/agents/discovery-paths.js +91 -0
  32. package/dist/agents/frontmatter.js +15 -5
  33. package/dist/agents/identity.js +25 -8
  34. package/dist/agents/json-chain.js +254 -0
  35. package/dist/agents/management-config.js +332 -0
  36. package/dist/agents/management-format.js +231 -0
  37. package/dist/agents/management-persistence.js +287 -0
  38. package/dist/agents/management-targets.js +136 -0
  39. package/dist/agents/management-warnings.js +81 -0
  40. package/dist/agents/markdown-chain.js +210 -0
  41. package/dist/agents/package-agents.js +154 -0
  42. package/dist/agents/skill-files.js +157 -0
  43. package/dist/agents/skill-search-paths.js +246 -0
  44. package/dist/agents/skill-types.js +1 -0
  45. package/dist/agents/skills.js +54 -499
  46. package/dist/extension/acceptance-schema.js +55 -0
  47. package/dist/extension/build-info.js +1 -1
  48. package/dist/extension/chain-schema.js +196 -0
  49. package/dist/extension/child-runtime.js +237 -0
  50. package/dist/extension/compact-tools.js +122 -51
  51. package/dist/extension/config-schema.js +29 -0
  52. package/dist/extension/config.js +27 -11
  53. package/dist/extension/control-notice-schema.js +37 -0
  54. package/dist/extension/control-notices.js +13 -6
  55. package/dist/extension/delegation-bridges.js +43 -0
  56. package/dist/extension/doctor.js +48 -25
  57. package/dist/extension/everyday-schemas.js +207 -0
  58. package/dist/extension/fanout-child.js +18 -319
  59. package/dist/extension/index.js +8 -563
  60. package/dist/extension/message-renderers.js +225 -0
  61. package/dist/extension/nested-control-inbox.js +154 -0
  62. package/dist/extension/parent-runtime.js +339 -0
  63. package/dist/extension/primary-tool.js +69 -0
  64. package/dist/extension/result-snapshot.js +22 -0
  65. package/dist/extension/runtime-reload.js +62 -0
  66. package/dist/extension/runtime-storage.js +43 -0
  67. package/dist/extension/schema-overrides.js +75 -0
  68. package/dist/extension/schemas.js +265 -336
  69. package/dist/extension/tool-input.js +27 -12
  70. package/dist/extension/tool-result.js +19 -4
  71. package/dist/history/answer-texts.js +57 -0
  72. package/dist/history/canonical-result.js +76 -43
  73. package/dist/history/canonical-selection.js +40 -0
  74. package/dist/history/database-generation.js +82 -0
  75. package/dist/history/database.js +57 -33
  76. package/dist/history/entry-ingestion.js +61 -0
  77. package/dist/history/index.js +242 -97
  78. package/dist/history/ingest.js +168 -331
  79. package/dist/history/output-digests.js +40 -0
  80. package/dist/history/preview.js +210 -0
  81. package/dist/history/prune.js +33 -0
  82. package/dist/history/queries.js +60 -349
  83. package/dist/history/query-scope.js +190 -0
  84. package/dist/history/rows.js +77 -0
  85. package/dist/history/run-projection.js +137 -0
  86. package/dist/history/run-queries.js +153 -0
  87. package/dist/history/schema.js +31 -0
  88. package/dist/history/search-queries.js +163 -0
  89. package/dist/history/selected-record.js +108 -0
  90. package/dist/history/source-file.js +45 -0
  91. package/dist/history/source-watches.js +141 -0
  92. package/dist/history/store.js +164 -153
  93. package/dist/history/text-chunks.js +169 -0
  94. package/dist/history/text.js +35 -0
  95. package/dist/history/transcript-queries.js +177 -0
  96. package/dist/history/types.js +5 -1
  97. package/dist/history/values.js +36 -0
  98. package/dist/history/wire-inputs.js +122 -0
  99. package/dist/history/wire-models.js +38 -0
  100. package/dist/history/worker-runtime.js +441 -0
  101. package/dist/history/worker.js +66 -462
  102. package/dist/intercom/intercom-bridge.js +48 -20
  103. package/dist/intercom/live-intercom.js +80 -23
  104. package/dist/intercom/nested-result-tree.js +163 -0
  105. package/dist/intercom/result-delivery.js +40 -0
  106. package/dist/intercom/result-intercom.js +178 -261
  107. package/dist/pi-intercom/agent-dir.js +7 -3
  108. package/dist/pi-intercom/async.js +27 -0
  109. package/dist/pi-intercom/bridge-ask.js +57 -0
  110. package/dist/pi-intercom/bridge-config.js +89 -0
  111. package/dist/pi-intercom/bridge-http.js +86 -0
  112. package/dist/pi-intercom/bridge-launch-policy.js +88 -0
  113. package/dist/pi-intercom/bridge-launch.js +217 -0
  114. package/dist/pi-intercom/bridge-protocol.js +106 -0
  115. package/dist/pi-intercom/bridge-routes.js +72 -0
  116. package/dist/pi-intercom/bridge-rpc-process.js +264 -0
  117. package/dist/pi-intercom/bridge-session.js +420 -0
  118. package/dist/pi-intercom/bridge.js +221 -606
  119. package/dist/pi-intercom/broker/broker.js +236 -244
  120. package/dist/pi-intercom/broker/client.js +189 -304
  121. package/dist/pi-intercom/broker/connection.js +165 -0
  122. package/dist/pi-intercom/broker/framing.js +5 -3
  123. package/dist/pi-intercom/broker/paths.js +4 -1
  124. package/dist/pi-intercom/broker/pid.js +53 -25
  125. package/dist/pi-intercom/broker/snapshot-reader.js +78 -0
  126. package/dist/pi-intercom/broker/snapshots.js +188 -0
  127. package/dist/pi-intercom/broker/spawn.js +36 -25
  128. package/dist/pi-intercom/config.js +85 -71
  129. package/dist/pi-intercom/connection.js +297 -0
  130. package/dist/pi-intercom/identity.js +58 -0
  131. package/dist/pi-intercom/inbound-delivery.js +356 -0
  132. package/dist/pi-intercom/inbound-journal.js +303 -0
  133. package/dist/pi-intercom/inbound-record.js +113 -0
  134. package/dist/pi-intercom/inbound.js +179 -0
  135. package/dist/pi-intercom/index.js +79 -2933
  136. package/dist/pi-intercom/inspection.js +62 -0
  137. package/dist/pi-intercom/interview.js +236 -0
  138. package/dist/pi-intercom/lifecycle.js +168 -0
  139. package/dist/pi-intercom/message-format.js +69 -0
  140. package/dist/pi-intercom/outbound.js +304 -0
  141. package/dist/pi-intercom/peer-awareness.js +66 -0
  142. package/dist/pi-intercom/reply-tracker.js +62 -43
  143. package/dist/pi-intercom/reply-wait.js +201 -0
  144. package/dist/pi-intercom/runtime-types.js +3 -0
  145. package/dist/pi-intercom/session-events.js +180 -0
  146. package/dist/pi-intercom/session-targets.js +42 -34
  147. package/dist/pi-intercom/subagent-events.js +238 -0
  148. package/dist/pi-intercom/subagent-live-events.js +129 -86
  149. package/dist/pi-intercom/supervisor.js +282 -0
  150. package/dist/pi-intercom/tool-arguments.js +74 -0
  151. package/dist/pi-intercom/tool-registration.js +95 -0
  152. package/dist/pi-intercom/tool-results.js +38 -0
  153. package/dist/pi-intercom/tool-schemas.js +82 -0
  154. package/dist/pi-intercom/tools.js +51 -0
  155. package/dist/pi-intercom/topic-actions.js +177 -0
  156. package/dist/pi-intercom/topics.js +177 -92
  157. package/dist/pi-intercom/transport.js +1 -0
  158. package/dist/pi-intercom/types.js +151 -114
  159. package/dist/pi-intercom/ui/compose-paste.js +93 -0
  160. package/dist/pi-intercom/ui/compose.js +121 -143
  161. package/dist/pi-intercom/ui/inline-message.js +59 -59
  162. package/dist/pi-intercom/ui/message-renderers.js +147 -0
  163. package/dist/pi-intercom/ui/overlay.js +181 -0
  164. package/dist/pi-intercom/ui/session-format.js +124 -0
  165. package/dist/pi-intercom/ui/session-list.js +76 -43
  166. package/dist/pi-intercom/ui/tool-renderers.js +110 -0
  167. package/dist/pi-intercom/ui/topic-view.js +88 -0
  168. package/dist/pi-intercom/validation.js +59 -0
  169. package/dist/runs/background/async-chain-plan.js +265 -0
  170. package/dist/runs/background/async-control.js +134 -58
  171. package/dist/runs/background/async-execution.js +136 -633
  172. package/dist/runs/background/async-job-admission.js +40 -0
  173. package/dist/runs/background/async-job-projection.js +169 -0
  174. package/dist/runs/background/async-job-tracker.js +297 -327
  175. package/dist/runs/background/async-launch.js +216 -0
  176. package/dist/runs/background/async-plan.js +116 -0
  177. package/dist/runs/background/async-result-file.js +47 -23
  178. package/dist/runs/background/async-resume.js +195 -253
  179. package/dist/runs/background/async-run-discovery.js +213 -0
  180. package/dist/runs/background/async-run-location.js +135 -0
  181. package/dist/runs/background/async-run-record.js +1 -0
  182. package/dist/runs/background/async-run-repair.js +223 -0
  183. package/dist/runs/background/async-run-summary.js +1 -0
  184. package/dist/runs/background/async-single-plan.js +105 -0
  185. package/dist/runs/background/async-status-projection.js +155 -0
  186. package/dist/runs/background/async-status-render.js +117 -0
  187. package/dist/runs/background/async-status-validation.js +181 -0
  188. package/dist/runs/background/async-status.js +3 -442
  189. package/dist/runs/background/async-value.js +21 -0
  190. package/dist/runs/background/completion-dedupe.js +36 -12
  191. package/dist/runs/background/completion-delivery.js +211 -205
  192. package/dist/runs/background/completion-receipts.js +237 -0
  193. package/dist/runs/background/legacy-status-acceptance.js +34 -0
  194. package/dist/runs/background/notify.js +96 -48
  195. package/dist/runs/background/parallel-groups.js +36 -19
  196. package/dist/runs/background/result-payload.js +178 -0
  197. package/dist/runs/background/result-watcher.js +348 -339
  198. package/dist/runs/background/run-id-resolver.js +53 -33
  199. package/dist/runs/background/run-schemas.js +189 -0
  200. package/dist/runs/background/run-status-contracts.js +14 -0
  201. package/dist/runs/background/run-status-render.js +270 -0
  202. package/dist/runs/background/run-status.js +231 -339
  203. package/dist/runs/background/runner-attempt-output.js +155 -0
  204. package/dist/runs/background/runner-attempt.js +263 -0
  205. package/dist/runs/background/runner-child-executor.js +166 -0
  206. package/dist/runs/background/runner-child-observer.js +192 -0
  207. package/dist/runs/background/runner-completion-summary.js +71 -0
  208. package/dist/runs/background/runner-completion.js +211 -0
  209. package/dist/runs/background/runner-config.js +1 -0
  210. package/dist/runs/background/runner-contract.js +1 -0
  211. package/dist/runs/background/runner-dynamic.js +121 -0
  212. package/dist/runs/background/runner-finalization.js +149 -0
  213. package/dist/runs/background/runner-graph.js +68 -0
  214. package/dist/runs/background/runner-initial-status.js +60 -0
  215. package/dist/runs/background/runner-lifecycle.js +142 -0
  216. package/dist/runs/background/runner-monitor.js +309 -0
  217. package/dist/runs/background/runner-parallel.js +204 -0
  218. package/dist/runs/background/runner-prepare-step.js +86 -0
  219. package/dist/runs/background/runner-reporting.js +153 -0
  220. package/dist/runs/background/runner-status.js +148 -0
  221. package/dist/runs/background/runner-step-output.js +92 -0
  222. package/dist/runs/background/runner-step-record.js +45 -0
  223. package/dist/runs/background/runner-step.js +112 -0
  224. package/dist/runs/background/runner-streaming.js +173 -0
  225. package/dist/runs/background/runner-workflow.js +213 -0
  226. package/dist/runs/background/stale-run-reconciler.js +155 -292
  227. package/dist/runs/background/subagent-runner-launcher.js +33 -13
  228. package/dist/runs/background/subagent-runner.js +51 -1842
  229. package/dist/runs/background/top-level-async.js +2 -1
  230. package/dist/runs/foreground/chain-clarify.js +293 -1017
  231. package/dist/runs/foreground/clarification-draft.js +87 -0
  232. package/dist/runs/foreground/clarify-contracts.js +1 -0
  233. package/dist/runs/foreground/clarify-invocation.js +129 -38
  234. package/dist/runs/foreground/clarify-text-editor.js +210 -0
  235. package/dist/runs/foreground/clarify-view.js +167 -0
  236. package/dist/runs/foreground/execution-input.js +226 -353
  237. package/dist/runs/foreground/execution-routing.js +33 -0
  238. package/dist/runs/foreground/execution-timeout.js +81 -0
  239. package/dist/runs/foreground/foreground-control.js +222 -763
  240. package/dist/runs/foreground/foreground-memory.js +99 -0
  241. package/dist/runs/foreground/invocation-context.js +55 -0
  242. package/dist/runs/foreground/invocation-execution.js +331 -0
  243. package/dist/runs/foreground/invocation-expansion.js +48 -0
  244. package/dist/runs/foreground/management-actions.js +295 -0
  245. package/dist/runs/foreground/nested-control.js +179 -0
  246. package/dist/runs/foreground/question-continuation.js +78 -0
  247. package/dist/runs/foreground/question-control.js +155 -50
  248. package/dist/runs/foreground/resume-target.js +221 -0
  249. package/dist/runs/foreground/revival-launch-policy.js +58 -0
  250. package/dist/runs/foreground/run-async-path.js +190 -178
  251. package/dist/runs/foreground/run-interrupt.js +164 -0
  252. package/dist/runs/foreground/run-nudge.js +278 -0
  253. package/dist/runs/foreground/saved-revival.js +251 -0
  254. package/dist/runs/foreground/subagent-executor.js +135 -424
  255. package/dist/runs/foreground/subagent-params.js +67 -30
  256. package/dist/runs/foreground/wait-observation.js +104 -0
  257. package/dist/runs/foreground/wait-registration.js +17 -0
  258. package/dist/runs/foreground/wait-run.js +283 -123
  259. package/dist/runs/shared/acceptance-contract.js +193 -99
  260. package/dist/runs/shared/acceptance-defaults.js +1 -0
  261. package/dist/runs/shared/acceptance-evaluation.js +201 -181
  262. package/dist/runs/shared/acceptance-finalization.js +256 -180
  263. package/dist/runs/shared/acceptance-outcome.js +53 -0
  264. package/dist/runs/shared/acceptance-reports.js +137 -72
  265. package/dist/runs/shared/acceptance-submission.js +167 -0
  266. package/dist/runs/shared/acceptance-verification.js +100 -0
  267. package/dist/runs/shared/chain-outputs.js +80 -52
  268. package/dist/runs/shared/child-attempt-types.js +1 -0
  269. package/dist/runs/shared/child-attempt.js +314 -667
  270. package/dist/runs/shared/child-boundaries.js +173 -0
  271. package/dist/runs/shared/child-context-filter.js +44 -0
  272. package/dist/runs/shared/child-event-observer.js +390 -0
  273. package/dist/runs/shared/child-execution-cwd.js +73 -43
  274. package/dist/runs/shared/child-execution-outcome.js +72 -0
  275. package/dist/runs/shared/child-invocation.js +54 -0
  276. package/dist/runs/shared/child-message-validation.js +215 -0
  277. package/dist/runs/shared/child-native-accounting.js +106 -0
  278. package/dist/runs/shared/child-native-observation.js +69 -0
  279. package/dist/runs/shared/child-observation-spool.js +164 -0
  280. package/dist/runs/shared/child-observations.js +157 -76
  281. package/dist/runs/shared/child-presence.js +3 -0
  282. package/dist/runs/shared/child-route-env.js +119 -0
  283. package/dist/runs/shared/child-stream-observer.js +221 -0
  284. package/dist/runs/shared/child-stream-projection.js +137 -0
  285. package/dist/runs/shared/child-structured-tool.js +27 -0
  286. package/dist/runs/shared/claude-code.js +121 -158
  287. package/dist/runs/shared/claude-model.js +42 -0
  288. package/dist/runs/shared/claude-session.js +83 -0
  289. package/dist/runs/shared/claude-types.js +1 -0
  290. package/dist/runs/shared/completion-guard.js +18 -18
  291. package/dist/runs/shared/control-event-policy.js +8 -0
  292. package/dist/runs/shared/control-notification-owner.js +13 -0
  293. package/dist/runs/shared/dynamic-fanout.js +188 -173
  294. package/dist/runs/shared/dynamic-item.js +102 -0
  295. package/dist/runs/shared/history-index.js +62 -36
  296. package/dist/runs/shared/legacy-async-recovery.js +114 -0
  297. package/dist/runs/shared/legacy-output-recovery.js +148 -0
  298. package/dist/runs/shared/mcp-config-schema.js +36 -0
  299. package/dist/runs/shared/mcp-direct-tool-allowlist.js +147 -116
  300. package/dist/runs/shared/model-fallback.js +97 -38
  301. package/dist/runs/shared/mutating-tool-guard.js +128 -88
  302. package/dist/runs/shared/native-finalization-types.js +1 -0
  303. package/dist/runs/shared/native-finalization.js +155 -76
  304. package/dist/runs/shared/native-usage.js +238 -98
  305. package/dist/runs/shared/nested-control-records.js +128 -0
  306. package/dist/runs/shared/nested-events.js +8 -700
  307. package/dist/runs/shared/nested-path.js +32 -28
  308. package/dist/runs/shared/nested-projection.js +155 -0
  309. package/dist/runs/shared/nested-protocol.js +18 -0
  310. package/dist/runs/shared/nested-registry.js +191 -0
  311. package/dist/runs/shared/nested-render.js +106 -58
  312. package/dist/runs/shared/nested-route.js +220 -0
  313. package/dist/runs/shared/nested-validation.js +216 -0
  314. package/dist/runs/shared/owned-child-evidence.js +125 -0
  315. package/dist/runs/shared/owned-run-list.js +146 -0
  316. package/dist/runs/shared/owned-run-observation.js +267 -0
  317. package/dist/runs/shared/owned-run-progress.js +109 -0
  318. package/dist/runs/shared/owned-run-read-state.js +1 -0
  319. package/dist/runs/shared/owned-run-results.js +202 -0
  320. package/dist/runs/shared/owned-run-status.js +180 -0
  321. package/dist/runs/shared/owned-run-view.js +51 -0
  322. package/dist/runs/shared/owned-workflow-details.js +17 -0
  323. package/dist/runs/shared/owner-output.js +43 -0
  324. package/dist/runs/shared/parallel-utils.js +36 -28
  325. package/dist/runs/shared/parent-receipts.js +90 -32
  326. package/dist/runs/shared/parent-usage.js +169 -57
  327. package/dist/runs/shared/pi-args.js +108 -201
  328. package/dist/runs/shared/pi-launch-input.js +1 -0
  329. package/dist/runs/shared/pi-session-launch.js +86 -0
  330. package/dist/runs/shared/pi-spawn.js +35 -21
  331. package/dist/runs/shared/run-history-queries.js +126 -35
  332. package/dist/runs/shared/run-history.js +101 -35
  333. package/dist/runs/shared/run-metadata-paths.js +14 -0
  334. package/dist/runs/shared/run-metadata.js +158 -0
  335. package/dist/runs/shared/run-persistence.js +131 -0
  336. package/dist/runs/shared/run-records.js +9 -679
  337. package/dist/runs/shared/run-restoration.js +338 -0
  338. package/dist/runs/shared/run-state-owner.js +26 -0
  339. package/dist/runs/shared/session-cwd-preload.js +27 -18
  340. package/dist/runs/shared/single-output.js +148 -70
  341. package/dist/runs/shared/stable-value.js +15 -0
  342. package/dist/runs/shared/streaming-text.js +23 -12
  343. package/dist/runs/shared/structured-output.js +25 -13
  344. package/dist/runs/shared/subagent-control.js +167 -100
  345. package/dist/runs/shared/subagent-prompt-runtime.js +27 -130
  346. package/dist/runs/shared/subagent-tool-loop-guard.js +81 -52
  347. package/dist/runs/shared/supervisor-question-state.js +145 -0
  348. package/dist/runs/shared/supervisor-questions.js +185 -254
  349. package/dist/runs/shared/workflow-graph.js +259 -167
  350. package/dist/runs/shared/workflow-policy.js +120 -48
  351. package/dist/runs/shared/worktree-contract.js +1 -0
  352. package/dist/runs/shared/worktree-hook.js +138 -0
  353. package/dist/runs/shared/worktree-location.js +47 -0
  354. package/dist/runs/shared/worktree-preservation.js +8 -0
  355. package/dist/runs/shared/worktree-process.js +107 -0
  356. package/dist/runs/shared/worktree.js +183 -322
  357. package/dist/shared/agent-context-policy.js +34 -44
  358. package/dist/shared/agent-dir.js +14 -0
  359. package/dist/shared/artifacts.js +30 -12
  360. package/dist/shared/atomic-json.js +4 -1
  361. package/dist/shared/chain-files.js +72 -0
  362. package/dist/shared/concurrency.js +18 -0
  363. package/dist/shared/content-preview.js +115 -0
  364. package/dist/shared/cooperative.js +5 -2
  365. package/dist/shared/execution-cwd.js +13 -5
  366. package/dist/shared/file-coalescer.js +2 -1
  367. package/dist/shared/fork-context.js +10 -12
  368. package/dist/shared/formatters.js +45 -19
  369. package/dist/shared/journal-frames.js +164 -0
  370. package/dist/shared/journal-projections.js +121 -0
  371. package/dist/shared/journal-reader.js +6 -407
  372. package/dist/shared/json-projection.js +191 -0
  373. package/dist/shared/lazy-tools.js +19 -8
  374. package/dist/shared/message-output.js +116 -0
  375. package/dist/shared/model-info.js +31 -14
  376. package/dist/shared/native-import.js +20 -0
  377. package/dist/shared/native-journal.js +140 -0
  378. package/dist/shared/native-session-loader.js +17 -0
  379. package/dist/shared/native-session.js +2 -13
  380. package/dist/shared/native-tui.js +7 -2
  381. package/dist/shared/native-typebox.js +15 -3
  382. package/dist/shared/output-page.js +63 -0
  383. package/dist/shared/post-exit-stdio-guard.js +128 -88
  384. package/dist/shared/prompt-sections.js +2 -0
  385. package/dist/shared/session-entries.js +70 -35
  386. package/dist/shared/session-identity.js +5 -3
  387. package/dist/shared/settings.js +62 -192
  388. package/dist/shared/status-format.js +97 -37
  389. package/dist/shared/task-file-policy.js +24 -0
  390. package/dist/shared/temp-root.js +44 -15
  391. package/dist/shared/types/acceptance.js +1 -0
  392. package/dist/shared/types/async.js +1 -0
  393. package/dist/shared/types/config.js +1 -0
  394. package/dist/shared/types/details.js +1 -0
  395. package/dist/shared/types/history.js +1 -0
  396. package/dist/shared/types/inputs.js +1 -0
  397. package/dist/shared/types/intercom.js +1 -0
  398. package/dist/shared/types/launch.js +1 -0
  399. package/dist/shared/types/messages.js +1 -0
  400. package/dist/shared/types/nested.js +1 -0
  401. package/dist/shared/types/owned-runs.js +1 -0
  402. package/dist/shared/types/progress.js +1 -0
  403. package/dist/shared/types/questions.js +1 -0
  404. package/dist/shared/types/results.js +1 -0
  405. package/dist/shared/types/state.js +1 -0
  406. package/dist/shared/types/usage.js +1 -0
  407. package/dist/shared/types/workflow.js +1 -0
  408. package/dist/shared/types.js +75 -30
  409. package/dist/shared/unknown.js +52 -0
  410. package/dist/shared/utils.js +54 -260
  411. package/dist/slash/command-discovery.js +82 -0
  412. package/dist/slash/command-input.js +190 -0
  413. package/dist/slash/prompt-template-bridge.js +94 -270
  414. package/dist/slash/prompt-template-contract.js +77 -0
  415. package/dist/slash/prompt-template-results.js +125 -0
  416. package/dist/slash/request-protocol.js +68 -0
  417. package/dist/slash/result-delivery.js +237 -0
  418. package/dist/slash/saved-chain.js +78 -0
  419. package/dist/slash/slash-bridge.js +123 -89
  420. package/dist/slash/slash-commands.js +101 -572
  421. package/dist/slash/slash-live-state.js +68 -47
  422. package/dist/tui/action-hints.js +50 -22
  423. package/dist/tui/activity-lines.js +32 -0
  424. package/dist/tui/agent-browser.js +290 -0
  425. package/dist/tui/agent-controls.js +289 -0
  426. package/dist/tui/agent-conversation.js +444 -0
  427. package/dist/tui/agent-dock.js +171 -0
  428. package/dist/tui/agent-history.js +50 -183
  429. package/dist/tui/agent-model.js +62 -0
  430. package/dist/tui/agent-picker.js +262 -0
  431. package/dist/tui/agent-view.js +232 -1564
  432. package/dist/tui/compact-results.js +193 -0
  433. package/dist/tui/conversation-details.js +190 -0
  434. package/dist/tui/conversation-frame.js +135 -0
  435. package/dist/tui/conversation-history.js +201 -0
  436. package/dist/tui/conversation-menu.js +78 -0
  437. package/dist/tui/conversation-notices.js +79 -0
  438. package/dist/tui/conversation-selection.js +58 -0
  439. package/dist/tui/conversation-viewport.js +129 -0
  440. package/dist/tui/direction-message.js +27 -0
  441. package/dist/tui/display.js +282 -0
  442. package/dist/tui/editor-viewport.js +18 -0
  443. package/dist/tui/expanded-content.js +87 -0
  444. package/dist/tui/expanded-multi.js +170 -0
  445. package/dist/tui/expanded-single.js +65 -0
  446. package/dist/tui/history-cards.js +216 -0
  447. package/dist/tui/history-display.js +238 -0
  448. package/dist/tui/history-json.js +16 -0
  449. package/dist/tui/history-metadata.js +152 -0
  450. package/dist/tui/history-record.js +251 -0
  451. package/dist/tui/history-text.js +34 -0
  452. package/dist/tui/indexed-history.js +116 -0
  453. package/dist/tui/nested-widget.js +120 -0
  454. package/dist/tui/picker-page.js +59 -0
  455. package/dist/tui/render.js +58 -1501
  456. package/dist/tui/result-facts.js +64 -0
  457. package/dist/tui/task-observation.js +59 -0
  458. package/dist/tui/task-store.js +366 -0
  459. package/dist/tui/text-values.js +16 -0
  460. package/dist/tui/view-connections.js +47 -0
  461. package/dist/tui/view-model.js +94 -0
  462. package/dist/tui/view-persistence.js +51 -0
  463. package/dist/tui/view-ports.js +1 -0
  464. package/dist/tui/view-session.js +37 -0
  465. package/dist/tui/widget-details.js +144 -0
  466. package/dist/tui/widget-status.js +245 -0
  467. package/dist/tui/widget.js +145 -0
  468. package/dist/tui/workflow-labels.js +223 -0
  469. package/dist/tui/workflow-outcomes.js +124 -0
  470. package/docs/code-quality.md +38 -0
  471. package/docs/development.md +99 -0
  472. package/docs/intercom-bridge.md +77 -22
  473. package/docs/intercom.md +132 -106
  474. package/docs/performance.md +19 -17
  475. package/docs/reference.md +1472 -0
  476. package/generated/RunValidators.d.ts +28 -0
  477. package/generated/RunValidators.js +2 -0
  478. package/package.json +27 -6
  479. package/prompts/parallel-cleanup.md +3 -0
  480. package/prompts/parallel-context-build.md +3 -0
  481. package/prompts/parallel-handoff-plan.md +6 -0
  482. package/prompts/parallel-research.md +4 -0
  483. package/prompts/parallel-review.md +2 -0
  484. package/prompts/review-loop.md +2 -0
  485. package/scripts/build.mjs +129 -85
  486. package/scripts/check-compat.mjs +48 -11
  487. package/scripts/compat-host.mjs +35 -7
  488. package/scripts/generate-run-schemas.mjs +210 -0
  489. package/scripts/intercom-bridge-credentials.mjs +158 -29
  490. package/scripts/intercom-remote.mjs +213 -52
  491. package/scripts/local-install-smoke.mjs +47 -37
  492. package/scripts/native-package-smoke.mjs +135 -25
  493. package/scripts/package-smoke.mjs +360 -140
  494. package/scripts/prepare.mjs +54 -34
  495. package/scripts/real-pi-processes.mjs +261 -0
  496. package/scripts/real-pi-smoke.mjs +432 -351
  497. package/scripts/run-tests.mjs +29 -15
  498. package/skills/pi-intercom/SKILL.md +11 -11
  499. package/skills/pi-intercom/references/peer-sessions.md +7 -2
  500. package/skills/pi-subagents/SKILL.md +1 -1
  501. package/skills/pi-subagents/references/full-orchestration-guide.md +254 -112
  502. package/src/agents/agent-defaults.ts +13 -0
  503. package/src/agents/agent-definition.ts +253 -0
  504. package/src/agents/agent-management.ts +111 -808
  505. package/src/agents/agent-scope.ts +7 -3
  506. package/src/agents/agent-selection.ts +26 -16
  507. package/src/agents/agent-serializer.ts +84 -93
  508. package/src/agents/agents.ts +199 -906
  509. package/src/agents/builtin-overrides.ts +297 -0
  510. package/src/agents/chain-serializer.ts +3 -329
  511. package/src/agents/config-values.ts +57 -0
  512. package/src/agents/discovery-paths.ts +134 -0
  513. package/src/agents/frontmatter.ts +35 -17
  514. package/src/agents/identity.ts +49 -18
  515. package/src/agents/json-chain.ts +305 -0
  516. package/src/agents/management-config.ts +342 -0
  517. package/src/agents/management-format.ts +243 -0
  518. package/src/agents/management-persistence.ts +408 -0
  519. package/src/agents/management-targets.ts +260 -0
  520. package/src/agents/management-warnings.ts +115 -0
  521. package/src/agents/markdown-chain.ts +237 -0
  522. package/src/agents/package-agents.ts +231 -0
  523. package/src/agents/skill-files.ts +182 -0
  524. package/src/agents/skill-search-paths.ts +289 -0
  525. package/src/agents/skill-types.ts +31 -0
  526. package/src/agents/skills.ts +152 -610
  527. package/src/extension/acceptance-schema.ts +88 -0
  528. package/src/extension/chain-schema.ts +276 -0
  529. package/src/extension/child-runtime.ts +302 -0
  530. package/src/extension/compact-tools.ts +259 -102
  531. package/src/extension/config-schema.ts +59 -0
  532. package/src/extension/config.ts +39 -17
  533. package/src/extension/control-notice-schema.ts +55 -0
  534. package/src/extension/control-notices.ts +57 -33
  535. package/src/extension/delegation-bridges.ts +59 -0
  536. package/src/extension/doctor.ts +171 -135
  537. package/src/extension/everyday-schemas.ts +252 -0
  538. package/src/extension/fanout-child.ts +29 -308
  539. package/src/extension/index.ts +17 -642
  540. package/src/extension/message-renderers.ts +334 -0
  541. package/src/extension/nested-control-inbox.ts +196 -0
  542. package/src/extension/parent-runtime.ts +405 -0
  543. package/src/extension/primary-tool.ts +130 -0
  544. package/src/extension/result-snapshot.ts +35 -0
  545. package/src/extension/runtime-reload.ts +72 -0
  546. package/src/extension/runtime-storage.ts +49 -0
  547. package/src/extension/schema-overrides.ts +114 -0
  548. package/src/extension/schemas.ts +451 -418
  549. package/src/extension/tool-input.ts +40 -13
  550. package/src/extension/tool-result.ts +45 -14
  551. package/src/history/answer-texts.ts +74 -0
  552. package/src/history/canonical-result.ts +127 -51
  553. package/src/history/canonical-selection.ts +54 -0
  554. package/src/history/database-generation.ts +86 -0
  555. package/src/history/database.ts +86 -48
  556. package/src/history/entry-ingestion.ts +129 -0
  557. package/src/history/index.ts +426 -148
  558. package/src/history/ingest.ts +372 -342
  559. package/src/history/output-digests.ts +41 -0
  560. package/src/history/preview.ts +235 -0
  561. package/src/history/prune.ts +48 -0
  562. package/src/history/queries.ts +122 -256
  563. package/src/history/query-scope.ts +278 -0
  564. package/src/history/rows.ts +118 -0
  565. package/src/history/run-projection.ts +164 -0
  566. package/src/history/run-queries.ts +199 -0
  567. package/src/history/schema.ts +34 -0
  568. package/src/history/search-queries.ts +216 -0
  569. package/src/history/selected-record.ts +140 -0
  570. package/src/history/source-file.ts +51 -0
  571. package/src/history/source-watches.ts +156 -0
  572. package/src/history/store.ts +310 -197
  573. package/src/history/text-chunks.ts +181 -0
  574. package/src/history/text.ts +43 -0
  575. package/src/history/transcript-queries.ts +251 -0
  576. package/src/history/types.ts +27 -164
  577. package/src/history/values.ts +36 -0
  578. package/src/history/wire-inputs.ts +141 -0
  579. package/src/history/wire-models.ts +51 -0
  580. package/src/history/worker-runtime.ts +502 -0
  581. package/src/history/worker.ts +94 -336
  582. package/src/intercom/intercom-bridge.ts +136 -69
  583. package/src/intercom/live-intercom.ts +197 -92
  584. package/src/intercom/nested-result-tree.ts +213 -0
  585. package/src/intercom/result-delivery.ts +56 -0
  586. package/src/intercom/result-intercom.ts +364 -382
  587. package/src/pi-intercom/agent-dir.ts +9 -3
  588. package/src/pi-intercom/async.ts +33 -0
  589. package/src/pi-intercom/bridge-ask.ts +64 -0
  590. package/src/pi-intercom/bridge-config.ts +120 -0
  591. package/src/pi-intercom/bridge-http.ts +97 -0
  592. package/src/pi-intercom/bridge-launch-policy.ts +109 -0
  593. package/src/pi-intercom/bridge-launch.ts +317 -0
  594. package/src/pi-intercom/bridge-protocol.ts +123 -0
  595. package/src/pi-intercom/bridge-routes.ts +105 -0
  596. package/src/pi-intercom/bridge-rpc-process.ts +293 -0
  597. package/src/pi-intercom/bridge-session.ts +510 -0
  598. package/src/pi-intercom/bridge.ts +305 -549
  599. package/src/pi-intercom/broker/broker.ts +326 -263
  600. package/src/pi-intercom/broker/client.ts +273 -354
  601. package/src/pi-intercom/broker/connection.ts +192 -0
  602. package/src/pi-intercom/broker/framing.ts +11 -6
  603. package/src/pi-intercom/broker/paths.ts +28 -25
  604. package/src/pi-intercom/broker/pid.ts +68 -24
  605. package/src/pi-intercom/broker/snapshot-reader.ts +91 -0
  606. package/src/pi-intercom/broker/snapshots.ts +241 -0
  607. package/src/pi-intercom/broker/spawn.ts +58 -34
  608. package/src/pi-intercom/config.ts +113 -108
  609. package/src/pi-intercom/connection.ts +346 -0
  610. package/src/pi-intercom/identity.ts +71 -0
  611. package/src/pi-intercom/inbound-delivery.ts +450 -0
  612. package/src/pi-intercom/inbound-journal.ts +350 -0
  613. package/src/pi-intercom/inbound-record.ts +141 -0
  614. package/src/pi-intercom/inbound.ts +237 -0
  615. package/src/pi-intercom/index.ts +86 -2923
  616. package/src/pi-intercom/inspection.ts +82 -0
  617. package/src/pi-intercom/interview.ts +295 -0
  618. package/src/pi-intercom/lifecycle.ts +183 -0
  619. package/src/pi-intercom/message-format.ts +77 -0
  620. package/src/pi-intercom/outbound.ts +418 -0
  621. package/src/pi-intercom/peer-awareness.ts +84 -0
  622. package/src/pi-intercom/reply-tracker.ts +121 -59
  623. package/src/pi-intercom/reply-wait.ts +247 -0
  624. package/src/pi-intercom/runtime-types.ts +97 -0
  625. package/src/pi-intercom/session-events.ts +247 -0
  626. package/src/pi-intercom/session-targets.ts +124 -72
  627. package/src/pi-intercom/subagent-events.ts +299 -0
  628. package/src/pi-intercom/subagent-live-events.ts +212 -109
  629. package/src/pi-intercom/supervisor.ts +362 -0
  630. package/src/pi-intercom/tool-arguments.ts +120 -0
  631. package/src/pi-intercom/tool-registration.ts +121 -0
  632. package/src/pi-intercom/tool-results.ts +54 -0
  633. package/src/pi-intercom/tool-schemas.ts +134 -0
  634. package/src/pi-intercom/tools.ts +80 -0
  635. package/src/pi-intercom/topic-actions.ts +243 -0
  636. package/src/pi-intercom/topics.ts +267 -121
  637. package/src/pi-intercom/transport.ts +14 -0
  638. package/src/pi-intercom/types.ts +288 -190
  639. package/src/pi-intercom/ui/compose-paste.ts +109 -0
  640. package/src/pi-intercom/ui/compose.ts +173 -157
  641. package/src/pi-intercom/ui/inline-message.ts +80 -62
  642. package/src/pi-intercom/ui/message-renderers.ts +182 -0
  643. package/src/pi-intercom/ui/overlay.ts +238 -0
  644. package/src/pi-intercom/ui/session-format.ts +146 -0
  645. package/src/pi-intercom/ui/session-list.ts +145 -62
  646. package/src/pi-intercom/ui/tool-renderers.ts +148 -0
  647. package/src/pi-intercom/ui/topic-view.ts +107 -0
  648. package/src/pi-intercom/validation.ts +69 -0
  649. package/src/runs/background/async-chain-plan.ts +451 -0
  650. package/src/runs/background/async-control.ts +201 -71
  651. package/src/runs/background/async-execution.ts +206 -829
  652. package/src/runs/background/async-job-admission.ts +54 -0
  653. package/src/runs/background/async-job-projection.ts +225 -0
  654. package/src/runs/background/async-job-tracker.ts +416 -360
  655. package/src/runs/background/async-launch.ts +319 -0
  656. package/src/runs/background/async-plan.ts +285 -0
  657. package/src/runs/background/async-result-file.ts +79 -37
  658. package/src/runs/background/async-resume.ts +338 -285
  659. package/src/runs/background/async-run-discovery.ts +270 -0
  660. package/src/runs/background/async-run-location.ts +194 -0
  661. package/src/runs/background/async-run-record.ts +14 -0
  662. package/src/runs/background/async-run-repair.ts +307 -0
  663. package/src/runs/background/async-run-summary.ts +71 -0
  664. package/src/runs/background/async-single-plan.ts +168 -0
  665. package/src/runs/background/async-status-projection.ts +188 -0
  666. package/src/runs/background/async-status-render.ts +164 -0
  667. package/src/runs/background/async-status-validation.ts +238 -0
  668. package/src/runs/background/async-status.ts +9 -481
  669. package/src/runs/background/async-value.ts +29 -0
  670. package/src/runs/background/completion-dedupe.ts +103 -54
  671. package/src/runs/background/completion-delivery.ts +313 -187
  672. package/src/runs/background/completion-receipts.ts +309 -0
  673. package/src/runs/background/legacy-status-acceptance.ts +38 -0
  674. package/src/runs/background/notify.ts +167 -106
  675. package/src/runs/background/parallel-groups.ts +95 -46
  676. package/src/runs/background/result-payload.ts +268 -0
  677. package/src/runs/background/result-watcher.ts +484 -387
  678. package/src/runs/background/run-id-resolver.ts +126 -57
  679. package/src/runs/background/run-schemas.ts +240 -0
  680. package/src/runs/background/run-status-contracts.ts +46 -0
  681. package/src/runs/background/run-status-render.ts +364 -0
  682. package/src/runs/background/run-status.ts +338 -356
  683. package/src/runs/background/runner-attempt-output.ts +281 -0
  684. package/src/runs/background/runner-attempt.ts +354 -0
  685. package/src/runs/background/runner-child-executor.ts +232 -0
  686. package/src/runs/background/runner-child-observer.ts +270 -0
  687. package/src/runs/background/runner-completion-summary.ts +106 -0
  688. package/src/runs/background/runner-completion.ts +274 -0
  689. package/src/runs/background/runner-config.ts +52 -0
  690. package/src/runs/background/runner-contract.ts +51 -0
  691. package/src/runs/background/runner-dynamic.ts +185 -0
  692. package/src/runs/background/runner-finalization.ts +203 -0
  693. package/src/runs/background/runner-graph.ts +91 -0
  694. package/src/runs/background/runner-initial-status.ts +72 -0
  695. package/src/runs/background/runner-lifecycle.ts +159 -0
  696. package/src/runs/background/runner-monitor.ts +399 -0
  697. package/src/runs/background/runner-parallel.ts +280 -0
  698. package/src/runs/background/runner-prepare-step.ts +128 -0
  699. package/src/runs/background/runner-reporting.ts +192 -0
  700. package/src/runs/background/runner-status.ts +220 -0
  701. package/src/runs/background/runner-step-output.ts +158 -0
  702. package/src/runs/background/runner-step-record.ts +76 -0
  703. package/src/runs/background/runner-step.ts +150 -0
  704. package/src/runs/background/runner-streaming.ts +220 -0
  705. package/src/runs/background/runner-workflow.ts +284 -0
  706. package/src/runs/background/stale-run-reconciler.ts +279 -336
  707. package/src/runs/background/subagent-runner-launcher.ts +67 -35
  708. package/src/runs/background/subagent-runner.ts +80 -2048
  709. package/src/runs/background/top-level-async.ts +9 -7
  710. package/src/runs/foreground/chain-clarify.ts +499 -1334
  711. package/src/runs/foreground/clarification-draft.ts +92 -0
  712. package/src/runs/foreground/clarify-contracts.ts +33 -0
  713. package/src/runs/foreground/clarify-invocation.ts +192 -65
  714. package/src/runs/foreground/clarify-text-editor.ts +229 -0
  715. package/src/runs/foreground/clarify-view.ts +222 -0
  716. package/src/runs/foreground/execution-input.ts +358 -424
  717. package/src/runs/foreground/execution-routing.ts +41 -0
  718. package/src/runs/foreground/execution-timeout.ts +92 -0
  719. package/src/runs/foreground/foreground-control.ts +333 -812
  720. package/src/runs/foreground/foreground-memory.ts +132 -0
  721. package/src/runs/foreground/invocation-context.ts +88 -0
  722. package/src/runs/foreground/invocation-execution.ts +465 -0
  723. package/src/runs/foreground/invocation-expansion.ts +58 -0
  724. package/src/runs/foreground/management-actions.ts +360 -0
  725. package/src/runs/foreground/nested-control.ts +248 -0
  726. package/src/runs/foreground/question-continuation.ts +112 -0
  727. package/src/runs/foreground/question-control.ts +297 -70
  728. package/src/runs/foreground/resume-target.ts +330 -0
  729. package/src/runs/foreground/revival-launch-policy.ts +112 -0
  730. package/src/runs/foreground/run-async-path.ts +307 -223
  731. package/src/runs/foreground/run-interrupt.ts +202 -0
  732. package/src/runs/foreground/run-nudge.ts +355 -0
  733. package/src/runs/foreground/saved-revival.ts +356 -0
  734. package/src/runs/foreground/subagent-executor.ts +195 -493
  735. package/src/runs/foreground/subagent-params.ts +314 -242
  736. package/src/runs/foreground/wait-observation.ts +160 -0
  737. package/src/runs/foreground/wait-registration.ts +18 -0
  738. package/src/runs/foreground/wait-run.ts +361 -119
  739. package/src/runs/shared/acceptance-contract.ts +410 -209
  740. package/src/runs/shared/acceptance-defaults.ts +1 -0
  741. package/src/runs/shared/acceptance-evaluation.ts +314 -240
  742. package/src/runs/shared/acceptance-finalization.ts +447 -305
  743. package/src/runs/shared/acceptance-outcome.ts +69 -0
  744. package/src/runs/shared/acceptance-reports.ts +220 -126
  745. package/src/runs/shared/acceptance-submission.ts +251 -0
  746. package/src/runs/shared/acceptance-verification.ts +118 -0
  747. package/src/runs/shared/acceptance.ts +25 -25
  748. package/src/runs/shared/chain-outputs.ts +190 -98
  749. package/src/runs/shared/child-attempt-types.ts +118 -0
  750. package/src/runs/shared/child-attempt.ts +372 -608
  751. package/src/runs/shared/child-boundaries.ts +217 -0
  752. package/src/runs/shared/child-context-filter.ts +53 -0
  753. package/src/runs/shared/child-event-observer.ts +496 -0
  754. package/src/runs/shared/child-execution-cwd.ts +139 -68
  755. package/src/runs/shared/child-execution-outcome.ts +103 -0
  756. package/src/runs/shared/child-invocation.ts +74 -0
  757. package/src/runs/shared/child-message-validation.ts +233 -0
  758. package/src/runs/shared/child-native-accounting.ts +154 -0
  759. package/src/runs/shared/child-native-observation.ts +79 -0
  760. package/src/runs/shared/child-observation-spool.ts +188 -0
  761. package/src/runs/shared/child-observations.ts +219 -89
  762. package/src/runs/shared/child-presence.ts +6 -0
  763. package/src/runs/shared/child-route-env.ts +142 -0
  764. package/src/runs/shared/child-stream-observer.ts +302 -0
  765. package/src/runs/shared/child-stream-projection.ts +160 -0
  766. package/src/runs/shared/child-structured-tool.ts +38 -0
  767. package/src/runs/shared/claude-code.ts +201 -228
  768. package/src/runs/shared/claude-model.ts +67 -0
  769. package/src/runs/shared/claude-session.ts +124 -0
  770. package/src/runs/shared/claude-types.ts +31 -0
  771. package/src/runs/shared/completion-guard.ts +35 -26
  772. package/src/runs/shared/control-event-policy.ts +15 -0
  773. package/src/runs/shared/control-notification-owner.ts +20 -0
  774. package/src/runs/shared/dynamic-fanout.ts +402 -238
  775. package/src/runs/shared/dynamic-item.ts +114 -0
  776. package/src/runs/shared/history-index.ts +112 -51
  777. package/src/runs/shared/legacy-async-recovery.ts +153 -0
  778. package/src/runs/shared/legacy-output-recovery.ts +214 -0
  779. package/src/runs/shared/mcp-config-schema.ts +46 -0
  780. package/src/runs/shared/mcp-direct-tool-allowlist.ts +396 -307
  781. package/src/runs/shared/model-fallback.ts +302 -180
  782. package/src/runs/shared/mutating-tool-guard.ts +259 -158
  783. package/src/runs/shared/native-finalization-types.ts +29 -0
  784. package/src/runs/shared/native-finalization.ts +233 -109
  785. package/src/runs/shared/native-usage.ts +379 -96
  786. package/src/runs/shared/nested-control-records.ts +178 -0
  787. package/src/runs/shared/nested-events.ts +45 -752
  788. package/src/runs/shared/nested-path.ts +47 -34
  789. package/src/runs/shared/nested-projection.ts +230 -0
  790. package/src/runs/shared/nested-protocol.ts +67 -0
  791. package/src/runs/shared/nested-registry.ts +273 -0
  792. package/src/runs/shared/nested-render.ts +198 -100
  793. package/src/runs/shared/nested-route.ts +292 -0
  794. package/src/runs/shared/nested-validation.ts +291 -0
  795. package/src/runs/shared/owned-child-evidence.ts +184 -0
  796. package/src/runs/shared/owned-run-list.ts +187 -0
  797. package/src/runs/shared/owned-run-observation.ts +368 -0
  798. package/src/runs/shared/owned-run-progress.ts +172 -0
  799. package/src/runs/shared/owned-run-read-state.ts +7 -0
  800. package/src/runs/shared/owned-run-results.ts +289 -0
  801. package/src/runs/shared/owned-run-status.ts +239 -0
  802. package/src/runs/shared/owned-run-view.ts +65 -0
  803. package/src/runs/shared/owned-workflow-details.ts +24 -0
  804. package/src/runs/shared/owner-output.ts +73 -0
  805. package/src/runs/shared/parallel-utils.ts +93 -129
  806. package/src/runs/shared/parent-receipts.ts +177 -50
  807. package/src/runs/shared/parent-usage.ts +314 -91
  808. package/src/runs/shared/pi-args.ts +241 -311
  809. package/src/runs/shared/pi-launch-input.ts +48 -0
  810. package/src/runs/shared/pi-session-launch.ts +94 -0
  811. package/src/runs/shared/pi-spawn.ts +57 -33
  812. package/src/runs/shared/run-history-queries.ts +189 -38
  813. package/src/runs/shared/run-history.ts +197 -76
  814. package/src/runs/shared/run-id-ambiguity.ts +7 -3
  815. package/src/runs/shared/run-metadata-paths.ts +17 -0
  816. package/src/runs/shared/run-metadata.ts +240 -0
  817. package/src/runs/shared/run-persistence.ts +181 -0
  818. package/src/runs/shared/run-records.ts +19 -607
  819. package/src/runs/shared/run-restoration.ts +455 -0
  820. package/src/runs/shared/run-state-owner.ts +31 -0
  821. package/src/runs/shared/session-cwd-preload.ts +39 -19
  822. package/src/runs/shared/single-output.ts +358 -205
  823. package/src/runs/shared/stable-value.ts +15 -0
  824. package/src/runs/shared/streaming-text.ts +39 -12
  825. package/src/runs/shared/structured-output.ts +76 -46
  826. package/src/runs/shared/subagent-control.ts +330 -197
  827. package/src/runs/shared/subagent-prompt-runtime.ts +92 -174
  828. package/src/runs/shared/subagent-tool-loop-guard.ts +118 -68
  829. package/src/runs/shared/supervisor-question-state.ts +221 -0
  830. package/src/runs/shared/supervisor-questions.ts +396 -401
  831. package/src/runs/shared/workflow-graph.ts +400 -198
  832. package/src/runs/shared/workflow-policy.ts +245 -110
  833. package/src/runs/shared/worktree-contract.ts +29 -0
  834. package/src/runs/shared/worktree-hook.ts +181 -0
  835. package/src/runs/shared/worktree-location.ts +61 -0
  836. package/src/runs/shared/worktree-preservation.ts +10 -0
  837. package/src/runs/shared/worktree-process.ts +132 -0
  838. package/src/runs/shared/worktree.ts +459 -587
  839. package/src/shared/agent-context-policy.ts +176 -145
  840. package/src/shared/agent-dir.ts +15 -0
  841. package/src/shared/artifacts.ts +99 -68
  842. package/src/shared/atomic-json.ts +14 -11
  843. package/src/shared/chain-files.ts +72 -0
  844. package/src/shared/concurrency.ts +27 -0
  845. package/src/shared/content-preview.ts +117 -0
  846. package/src/shared/cooperative.ts +17 -13
  847. package/src/shared/execution-cwd.ts +34 -9
  848. package/src/shared/file-coalescer.ts +24 -22
  849. package/src/shared/fork-context.ts +52 -48
  850. package/src/shared/formatters.ts +88 -47
  851. package/src/shared/journal-frames.ts +197 -0
  852. package/src/shared/journal-projections.ts +133 -0
  853. package/src/shared/journal-reader.ts +18 -333
  854. package/src/shared/json-projection.ts +224 -0
  855. package/src/shared/lazy-tools.ts +76 -27
  856. package/src/shared/message-output.ts +146 -0
  857. package/src/shared/model-info.ts +94 -58
  858. package/src/shared/native-import.ts +25 -0
  859. package/src/shared/native-journal.ts +172 -0
  860. package/src/shared/native-session-loader.ts +13 -0
  861. package/src/shared/native-session.ts +3 -5
  862. package/src/shared/native-tui.ts +13 -3
  863. package/src/shared/native-typebox.ts +18 -3
  864. package/src/shared/output-page.ts +76 -0
  865. package/src/shared/post-exit-stdio-guard.ts +232 -172
  866. package/src/shared/prompt-sections.ts +13 -7
  867. package/src/shared/session-entries.ts +103 -44
  868. package/src/shared/session-identity.ts +17 -10
  869. package/src/shared/settings.ts +182 -367
  870. package/src/shared/status-format.ts +207 -98
  871. package/src/shared/stream-json.d.ts +10 -0
  872. package/src/shared/task-file-policy.ts +35 -0
  873. package/src/shared/temp-root.ts +122 -71
  874. package/src/shared/types/acceptance.ts +149 -0
  875. package/src/shared/types/async.ts +247 -0
  876. package/src/shared/types/config.ts +139 -0
  877. package/src/shared/types/details.ts +104 -0
  878. package/src/shared/types/history.ts +217 -0
  879. package/src/shared/types/inputs.ts +4 -0
  880. package/src/shared/types/intercom.ts +57 -0
  881. package/src/shared/types/launch.ts +39 -0
  882. package/src/shared/types/messages.ts +104 -0
  883. package/src/shared/types/nested.ts +153 -0
  884. package/src/shared/types/owned-runs.ts +148 -0
  885. package/src/shared/types/progress.ts +106 -0
  886. package/src/shared/types/questions.ts +103 -0
  887. package/src/shared/types/results.ts +119 -0
  888. package/src/shared/types/state.ts +56 -0
  889. package/src/shared/types/usage.ts +36 -0
  890. package/src/shared/types/workflow.ts +187 -0
  891. package/src/shared/types.ts +222 -1161
  892. package/src/shared/ui-mode.ts +1 -1
  893. package/src/shared/unknown.ts +59 -0
  894. package/src/shared/utils.ts +111 -305
  895. package/src/slash/command-discovery.ts +109 -0
  896. package/src/slash/command-input.ts +232 -0
  897. package/src/slash/prompt-template-bridge.ts +153 -400
  898. package/src/slash/prompt-template-contract.ts +152 -0
  899. package/src/slash/prompt-template-results.ts +189 -0
  900. package/src/slash/request-protocol.ts +87 -0
  901. package/src/slash/result-delivery.ts +312 -0
  902. package/src/slash/saved-chain.ts +115 -0
  903. package/src/slash/slash-bridge.ts +202 -156
  904. package/src/slash/slash-commands.ts +176 -598
  905. package/src/slash/slash-live-state.ts +287 -229
  906. package/src/tui/action-hints.ts +147 -61
  907. package/src/tui/activity-lines.ts +40 -0
  908. package/src/tui/agent-browser.ts +354 -0
  909. package/src/tui/agent-controls.ts +355 -0
  910. package/src/tui/agent-conversation.ts +514 -0
  911. package/src/tui/agent-dock.ts +250 -0
  912. package/src/tui/agent-history.ts +78 -203
  913. package/src/tui/agent-model.ts +78 -0
  914. package/src/tui/agent-picker.ts +325 -0
  915. package/src/tui/agent-view.ts +391 -1333
  916. package/src/tui/compact-results.ts +342 -0
  917. package/src/tui/conversation-details.ts +206 -0
  918. package/src/tui/conversation-frame.ts +184 -0
  919. package/src/tui/conversation-history.ts +246 -0
  920. package/src/tui/conversation-menu.ts +119 -0
  921. package/src/tui/conversation-notices.ts +116 -0
  922. package/src/tui/conversation-selection.ts +66 -0
  923. package/src/tui/conversation-viewport.ts +169 -0
  924. package/src/tui/direction-message.ts +35 -0
  925. package/src/tui/display.ts +409 -0
  926. package/src/tui/editor-viewport.ts +19 -0
  927. package/src/tui/expanded-content.ts +134 -0
  928. package/src/tui/expanded-multi.ts +264 -0
  929. package/src/tui/expanded-single.ts +92 -0
  930. package/src/tui/history-cards.ts +316 -0
  931. package/src/tui/history-display.ts +334 -0
  932. package/src/tui/history-json.ts +17 -0
  933. package/src/tui/history-metadata.ts +177 -0
  934. package/src/tui/history-record.ts +321 -0
  935. package/src/tui/history-text.ts +34 -0
  936. package/src/tui/indexed-history.ts +149 -0
  937. package/src/tui/nested-widget.ts +192 -0
  938. package/src/tui/picker-page.ts +80 -0
  939. package/src/tui/render.ts +100 -1546
  940. package/src/tui/result-facts.ts +99 -0
  941. package/src/tui/task-observation.ts +96 -0
  942. package/src/tui/task-store.ts +432 -0
  943. package/src/tui/text-values.ts +20 -0
  944. package/src/tui/view-connections.ts +64 -0
  945. package/src/tui/view-model.ts +181 -0
  946. package/src/tui/view-persistence.ts +57 -0
  947. package/src/tui/view-ports.ts +55 -0
  948. package/src/tui/view-session.ts +42 -0
  949. package/src/tui/widget-details.ts +220 -0
  950. package/src/tui/widget-status.ts +331 -0
  951. package/src/tui/widget.ts +208 -0
  952. package/src/tui/workflow-labels.ts +340 -0
  953. package/src/tui/workflow-outcomes.ts +150 -0
  954. package/tsconfig.build.json +11 -10
  955. package/tsconfig.json +30 -17
@@ -1,635 +1,507 @@
1
- import { spawn, spawnSync } from "node:child_process";
2
- import { addAbortListener } from "node:events";
3
1
  import * as fs from "node:fs";
4
- import * as os from "node:os";
5
2
  import * as path from "node:path";
6
- import { TEMP_ROOT_DIR } from "../../shared/types.ts";
3
+ import { errorMessage as getErrorMessage, hasErrorCode } from "../../shared/unknown.ts";
4
+ import { markWorktreesForPreservation } from "./worktree-preservation.ts";
5
+ import { buildWorktreeBranch, buildWorktreePath } from "./worktree-location.ts";
6
+ export {
7
+ findWorktreeTaskCwdConflict,
8
+ formatWorktreeTaskCwdConflict,
9
+ resolveExpectedWorktreeAgentCwd,
10
+ } from "./worktree-location.ts";
7
11
  import { ensureTempRoot } from "../../shared/temp-root.ts";
8
- import { trySignalChildTree } from "../../shared/post-exit-stdio-guard.ts";
12
+ import { runGitChecked, runSetupCommand, runSetupGit } from "./worktree-process.ts";
13
+ import {
14
+ DEFAULT_WORKTREE_SETUP_HOOK_TIMEOUT_MS,
15
+ resolveWorktreeSetupHook,
16
+ runWorktreeSetupHook,
17
+ type WorktreeSetupHookConfig,
18
+ type ResolvedWorktreeSetupHook,
19
+ } from "./worktree-hook.ts";
9
20
 
10
21
  export class WorktreeCleanupError extends Error {}
11
22
 
12
- export interface WorktreeSetup {
13
- cwd: string;
14
- worktrees: WorktreeInfo[];
15
- baseCommit: string;
16
- preserveOnCleanup?: boolean;
17
- preservationReason?: string;
18
- }
19
-
20
- interface WorktreeInfo {
21
- path: string;
22
- agentCwd: string;
23
- branch: string;
24
- index: number;
25
- syntheticPaths: string[];
26
- }
27
-
28
- interface WorktreeDiff {
29
- index: number;
30
- agent: string;
31
- branch: string;
32
- diffStat: string;
33
- filesChanged: number;
34
- insertions: number;
35
- deletions: number;
36
- patchPath: string;
37
- worktreePath?: string;
38
- captureError?: string;
39
- }
40
-
41
- interface WorktreeTaskCwdConflict {
42
- index: number;
43
- agent: string;
44
- cwd: string;
45
- }
46
-
47
- interface WorktreeSetupHookConfig {
48
- hookPath: string;
49
- timeoutMs?: number;
50
- }
23
+ export type { WorktreeSetup } from "./worktree-contract.ts";
24
+ import type { WorktreeSetup, WorktreeInfo, WorktreeDiff } from "./worktree-contract.ts";
51
25
 
52
26
  interface CreateWorktreesOptions {
53
- agents?: string[];
54
- setupHook?: WorktreeSetupHookConfig;
55
- signal?: AbortSignal;
56
- }
57
-
58
- interface ResolvedWorktreeSetupHook {
59
- hookPath: string;
60
- timeoutMs: number;
61
- }
62
-
63
- interface WorktreeSetupHookInput {
64
- version: 1;
65
- repoRoot: string;
66
- worktreePath: string;
67
- agentCwd: string;
68
- branch: string;
69
- index: number;
70
- runId: string;
71
- baseCommit: string;
72
- agent?: string;
73
- }
74
-
75
- interface WorktreeSetupHookOutput {
76
- syntheticPaths?: string[];
77
- }
78
-
79
- interface GitResult {
80
- stdout: string;
81
- stderr: string;
82
- status: number | null;
27
+ readonly agents?: readonly string[];
28
+ readonly setupHook?: WorktreeSetupHookConfig;
29
+ readonly signal?: AbortSignal;
83
30
  }
84
31
 
85
32
  interface RepoState {
86
- toplevel: string;
87
- cwdRelative: string;
88
- baseCommit: string;
89
- }
90
-
91
- const DEFAULT_WORKTREE_SETUP_HOOK_TIMEOUT_MS = 30000;
92
-
93
- function runGit(cwd: string, args: string[]): GitResult {
94
- const result = spawnSync("git", ["-C", cwd, ...args], { encoding: "utf-8" });
95
- return {
96
- stdout: result.stdout ?? "",
97
- stderr: result.stderr ?? "",
98
- status: result.status,
99
- };
100
- }
101
-
102
- function runGitChecked(cwd: string, args: string[]): string {
103
- const result = runGit(cwd, args);
104
- if (result.status !== 0) {
105
- const command = `git -C ${cwd} ${args.join(" ")}`;
106
- const message = result.stderr.trim() || result.stdout.trim() || `${command} failed`;
107
- throw new Error(message);
108
- }
109
- return result.stdout;
110
- }
111
-
112
- function runSetupCommand(
113
- command: string, args: string[], cwd: string, signal?: AbortSignal, input?: string, timeoutMs?: number,
114
- ): Promise<GitResult> {
115
- signal?.throwIfAborted();
116
- return new Promise((resolve, reject) => {
117
- let stdout = "";
118
- let stderr = "";
119
- let outputBytes = 0;
120
- let failure: Error | undefined;
121
- const child = spawn(command, args, { cwd, detached: true, stdio: "pipe" });
122
- const terminate = () => { trySignalChildTree(child, "SIGKILL"); };
123
- const abortListener = signal && addAbortListener(signal, terminate);
124
- const timer = timeoutMs === undefined ? undefined : setTimeout(() => {
125
- failure = new Error(`worktree setup hook timed out after ${timeoutMs}ms`);
126
- terminate();
127
- }, timeoutMs);
128
- timer?.unref();
129
- for (const stream of [child.stdout, child.stderr]) {
130
- stream.setEncoding("utf8");
131
- stream.on("data", (chunk: string) => {
132
- outputBytes += Buffer.byteLength(chunk);
133
- // Keep the previous spawnSync output bound.
134
- if (outputBytes > 1024 * 1024) {
135
- failure ??= new Error(`${command} output exceeded 1048576 bytes`);
136
- terminate();
137
- } else if (stream === child.stdout) stdout += chunk;
138
- else stderr += chunk;
139
- });
140
- }
141
- child.on("error", (error) => { failure = error; terminate(); });
142
- child.on("exit", (status) => { if (status !== 0) terminate(); });
143
- child.on("close", (status) => {
144
- clearTimeout(timer);
145
- abortListener?.[Symbol.dispose]();
146
- if (signal?.aborted) reject(signal.reason);
147
- else if (failure) reject(failure);
148
- else resolve({ stdout, stderr, status });
149
- });
150
- child.stdin.on("error", (error: NodeJS.ErrnoException) => {
151
- if (error.code === "EPIPE") return; // The hook can exit without reading its payload.
152
- failure = error;
153
- terminate();
154
- });
155
- child.stdin.end(input);
156
- });
157
- }
158
-
159
- async function runSetupGit(cwd: string, args: string[], signal?: AbortSignal): Promise<string> {
160
- const result = await runSetupCommand("git", ["-C", cwd, ...args], cwd, signal);
161
- if (result.status !== 0) {
162
- throw new Error(result.stderr.trim() || result.stdout.trim() || `git -C ${cwd} ${args.join(" ")} failed`);
163
- }
164
- return result.stdout;
33
+ readonly toplevel: string;
34
+ readonly cwdRelative: string;
35
+ readonly baseCommit: string;
165
36
  }
166
37
 
167
38
  async function resolveRepoState(cwd: string, signal?: AbortSignal): Promise<RepoState> {
168
- const repoCheck = await runSetupCommand("git", ["-C", cwd, "rev-parse", "--is-inside-work-tree"], cwd, signal);
169
- if (repoCheck.status !== 0 || repoCheck.stdout.trim() !== "true") {
170
- throw new Error("worktree isolation requires a git repository");
171
- }
172
- const cwdRelative = (await runSetupGit(cwd, ["rev-parse", "--show-prefix"], signal)).trim().replace(/[\\/]+$/, "");
173
- const toplevel = (await runSetupGit(cwd, ["rev-parse", "--show-toplevel"], signal)).trim();
174
-
175
- const status = await runSetupGit(toplevel, ["status", "--porcelain"], signal);
176
- if (status.trim().length > 0) {
177
- throw new Error("worktree isolation requires a clean git working tree. Commit or stash changes first, or rerun without worktree isolation if shared-checkout edits are intentional.");
178
- }
179
-
180
- const baseCommit = (await runSetupGit(toplevel, ["rev-parse", "HEAD"], signal)).trim();
181
- return { toplevel, cwdRelative, baseCommit };
182
- }
183
-
184
- function normalizeComparableCwd(cwd: string): string {
185
- const resolved = path.resolve(cwd);
186
- try {
187
- return fs.realpathSync(resolved);
188
- } catch {
189
- // Use the unresolved absolute path when realpath resolution is unavailable.
190
- return resolved;
191
- }
192
- }
193
-
194
- export function findWorktreeTaskCwdConflict(
195
- tasks: ReadonlyArray<{ agent: string; cwd?: string }>,
196
- sharedCwd: string,
197
- ): WorktreeTaskCwdConflict | undefined {
198
- const normalizedSharedCwd = normalizeComparableCwd(sharedCwd);
199
- for (let index = 0; index < tasks.length; index++) {
200
- const task = tasks[index]!;
201
- if (!task.cwd) continue;
202
- const taskCwd = path.isAbsolute(task.cwd) ? task.cwd : path.resolve(sharedCwd, task.cwd);
203
- if (normalizeComparableCwd(taskCwd) === normalizedSharedCwd) continue;
204
- return { index, agent: task.agent, cwd: task.cwd };
205
- }
206
- return undefined;
207
- }
208
-
209
- export function formatWorktreeTaskCwdConflict(
210
- conflict: WorktreeTaskCwdConflict,
211
- sharedCwd: string,
212
- ): string {
213
- return `worktree isolation uses the shared cwd (${sharedCwd}); task ${conflict.index + 1} (${conflict.agent}) sets cwd to ${conflict.cwd}. Remove task-level cwd overrides or disable worktree.`;
39
+ const repoCheck = await runSetupCommand({
40
+ command: "git",
41
+ args: ["-C", cwd, "rev-parse", "--is-inside-work-tree"],
42
+ cwd,
43
+ signal,
44
+ });
45
+ if (repoCheck.status !== 0 || repoCheck.stdout.trim() !== "true") {
46
+ throw new Error("worktree isolation requires a git repository");
47
+ }
48
+ const cwdRelative = (await runSetupGit(cwd, ["rev-parse", "--show-prefix"], signal))
49
+ .trim()
50
+ .replace(/[\\/]+$/, "");
51
+ const toplevel = (await runSetupGit(cwd, ["rev-parse", "--show-toplevel"], signal)).trim();
52
+
53
+ const status = await runSetupGit(toplevel, ["status", "--porcelain"], signal);
54
+ if (status.trim().length > 0) {
55
+ throw new Error(
56
+ "worktree isolation requires a clean git working tree. Commit or stash changes first, or rerun without worktree isolation if shared-checkout edits are intentional.",
57
+ );
58
+ }
59
+
60
+ const baseCommit = (await runSetupGit(toplevel, ["rev-parse", "HEAD"], signal)).trim();
61
+ return { toplevel, cwdRelative, baseCommit };
214
62
  }
215
63
 
216
64
  function safePatchAgentName(agent: string): string {
217
- return agent.replace(/[^\w.-]/g, "_");
218
- }
219
-
220
- function buildWorktreeBranch(runId: string, index: number): string {
221
- return `pi-parallel-${runId}-${index}`;
222
- }
223
-
224
- function buildWorktreePath(runId: string, index: number): string {
225
- return path.join(TEMP_ROOT_DIR, "worktrees", `pi-worktree-${runId}-${index}`);
226
- }
227
-
228
- function resolveRepoCwdRelative(cwd: string): string {
229
- const repoCheck = runGit(cwd, ["rev-parse", "--is-inside-work-tree"]);
230
- if (repoCheck.status !== 0 || repoCheck.stdout.trim() !== "true") {
231
- throw new Error("worktree isolation requires a git repository");
232
- }
233
- const rawPrefix = runGitChecked(cwd, ["rev-parse", "--show-prefix"]).trim();
234
- const normalizedPrefix = rawPrefix
235
- ? path.normalize(rawPrefix.replace(/[\\/]+$/, ""))
236
- : "";
237
- return normalizedPrefix === "." ? "" : normalizedPrefix;
238
- }
239
-
240
- export function resolveExpectedWorktreeAgentCwd(cwd: string, runId: string, index: number): string {
241
- const cwdRelative = resolveRepoCwdRelative(cwd);
242
- const worktreePath = buildWorktreePath(runId, index);
243
- return cwdRelative ? path.join(worktreePath, cwdRelative) : worktreePath;
244
- }
245
-
246
- function parseHookTimeout(timeoutMs: number | undefined): number {
247
- if (timeoutMs === undefined) return DEFAULT_WORKTREE_SETUP_HOOK_TIMEOUT_MS;
248
- if (!Number.isInteger(timeoutMs) || timeoutMs <= 0) {
249
- throw new Error("worktree setup hook timeout must be an integer greater than 0");
250
- }
251
- return timeoutMs;
65
+ return agent.replace(/[^\w.-]/g, "_");
252
66
  }
253
67
 
254
- function resolveWorktreeSetupHook(
255
- repoRoot: string,
256
- config: WorktreeSetupHookConfig | undefined,
257
- ): ResolvedWorktreeSetupHook | undefined {
258
- if (!config) return undefined;
259
- const hookPath = config.hookPath.trim();
260
- if (!hookPath) {
261
- throw new Error("worktree setup hook path cannot be empty");
262
- }
263
-
264
- const expandedHookPath = hookPath.startsWith("~/") ? path.join(os.homedir(), hookPath.slice(2)) : hookPath;
265
- let resolvedPath: string;
266
- if (path.isAbsolute(expandedHookPath)) {
267
- resolvedPath = expandedHookPath;
268
- } else if (expandedHookPath.includes("/") || expandedHookPath.includes("\\")) {
269
- resolvedPath = path.resolve(repoRoot, expandedHookPath);
270
- } else {
271
- throw new Error("worktree setup hook must be an absolute path or a repo-relative path");
272
- }
273
-
274
- if (!fs.existsSync(resolvedPath)) {
275
- throw new Error(`worktree setup hook not found: ${resolvedPath}`);
276
- }
277
- if (fs.statSync(resolvedPath).isDirectory()) {
278
- throw new Error(`worktree setup hook must be a file, got directory: ${resolvedPath}`);
279
- }
280
-
281
- return {
282
- hookPath: resolvedPath,
283
- timeoutMs: parseHookTimeout(config.timeoutMs),
284
- };
68
+ function escapesRoot(relative: string): boolean {
69
+ return relative === ".." || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative);
285
70
  }
286
71
 
287
- function normalizeSyntheticPath(worktreePath: string, rawPath: string): string {
288
- const trimmed = rawPath.trim();
289
- if (!trimmed) throw new Error("synthetic path cannot be empty");
290
- if (path.isAbsolute(trimmed)) throw new Error(`synthetic path must be relative: ${rawPath}`);
291
-
292
- const resolved = path.resolve(worktreePath, trimmed);
293
- const relative = path.relative(worktreePath, resolved);
294
- if (!relative || relative === ".") {
295
- throw new Error(`synthetic path cannot target the worktree root: ${rawPath}`);
296
- }
297
- if (relative === ".." || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) {
298
- throw new Error(`synthetic path escapes the worktree root: ${rawPath}`);
299
- }
300
- return path.normalize(relative);
301
- }
302
-
303
- async function hasTrackedEntries(worktreePath: string, relativePath: string, signal?: AbortSignal): Promise<boolean> {
304
- const result = await runSetupCommand("git", ["-C", worktreePath, "ls-files", "--", relativePath], worktreePath, signal);
305
- return result.status === 0 && result.stdout.trim().length > 0;
306
- }
307
-
308
- function parseWorktreeSetupHookOutput(rawStdout: string): WorktreeSetupHookOutput {
309
- const trimmed = rawStdout.trim();
310
- if (!trimmed) {
311
- throw new Error("worktree setup hook returned empty stdout; expected JSON object");
312
- }
313
- let parsed: unknown;
314
- try {
315
- parsed = JSON.parse(trimmed);
316
- } catch (error) {
317
- const message = error instanceof Error ? error.message : String(error);
318
- throw new Error(`worktree setup hook returned invalid JSON: ${message}`);
319
- }
320
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
321
- throw new Error("worktree setup hook stdout must be a JSON object");
322
- }
323
- return parsed as WorktreeSetupHookOutput;
324
- }
325
-
326
- async function runWorktreeSetupHook(
327
- hook: ResolvedWorktreeSetupHook,
328
- input: WorktreeSetupHookInput,
329
- signal?: AbortSignal,
330
- ): Promise<string[]> {
331
- let result: GitResult;
332
- try {
333
- result = await runSetupCommand(hook.hookPath, [], input.worktreePath, signal, JSON.stringify(input), hook.timeoutMs);
334
- } catch (error) {
335
- signal?.throwIfAborted();
336
- throw new Error(`worktree setup hook failed: ${getErrorMessage(error)}`);
337
- }
338
-
339
- if (result.status !== 0) {
340
- const details = result.stderr.trim() || result.stdout.trim() || "no output";
341
- throw new Error(`worktree setup hook failed with exit code ${result.status}: ${details}`);
342
- }
343
-
344
- const output = parseWorktreeSetupHookOutput(result.stdout);
345
- if (output.syntheticPaths === undefined) return [];
346
- if (!Array.isArray(output.syntheticPaths)) {
347
- throw new Error("worktree setup hook output field 'syntheticPaths' must be an array of relative paths");
348
- }
349
-
350
- const uniquePaths = new Set<string>();
351
- for (const candidate of output.syntheticPaths) {
352
- if (typeof candidate !== "string") {
353
- throw new Error("worktree setup hook output field 'syntheticPaths' must contain only strings");
354
- }
355
- const normalizedPath = normalizeSyntheticPath(input.worktreePath, candidate);
356
- if (await hasTrackedEntries(input.worktreePath, normalizedPath, signal)) {
357
- throw new Error(`worktree setup hook cannot mark tracked paths as synthetic: ${normalizedPath}`);
358
- }
359
- uniquePaths.add(normalizedPath);
360
- }
361
- return [...uniquePaths];
72
+ function readSyntheticStat(resolved: string): fs.Stats | undefined {
73
+ try {
74
+ return fs.lstatSync(resolved);
75
+ } catch (error) {
76
+ if (hasErrorCode(error, "ENOENT")) {
77
+ return undefined;
78
+ }
79
+ throw error;
80
+ }
362
81
  }
363
82
 
364
83
  function removeSyntheticPath(worktree: WorktreeInfo, syntheticPath: string): void {
365
- const resolved = path.resolve(worktree.path, syntheticPath);
366
- const relative = path.relative(worktree.path, resolved);
367
- if (!relative || relative === "." || relative === ".." || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) {
368
- return;
369
- }
370
-
371
- let stat: fs.Stats;
372
- try {
373
- stat = fs.lstatSync(resolved);
374
- } catch (error) {
375
- const code = error && typeof error === "object" && "code" in error ? (error as { code?: unknown }).code : undefined;
376
- if (code === "ENOENT") return;
377
- throw error;
378
- }
379
-
380
- const realRoot = fs.realpathSync(worktree.path);
381
- const realParent = fs.realpathSync(path.dirname(resolved));
382
- const parentRelative = path.relative(realRoot, realParent);
383
- if (parentRelative === ".." || parentRelative.startsWith(`..${path.sep}`) || path.isAbsolute(parentRelative)) {
384
- throw new Error(`synthetic path resolves outside the worktree: ${syntheticPath}`);
385
- }
386
-
387
- if (stat.isSymbolicLink()) {
388
- fs.unlinkSync(resolved);
389
- return;
390
- }
391
- if (stat.isDirectory()) {
392
- fs.rmSync(resolved, { recursive: true, force: true });
393
- return;
394
- }
395
- fs.rmSync(resolved, { force: true });
84
+ const resolved = path.resolve(worktree.path, syntheticPath);
85
+ const relative = path.relative(worktree.path, resolved);
86
+ if (relative.length === 0 || relative === "." || escapesRoot(relative)) {
87
+ return;
88
+ }
89
+ const stat = readSyntheticStat(resolved);
90
+ if (!stat) {
91
+ return;
92
+ }
93
+ const realRoot = fs.realpathSync(worktree.path);
94
+ const realParent = fs.realpathSync(path.dirname(resolved));
95
+ const parentRelative = path.relative(realRoot, realParent);
96
+ if (escapesRoot(parentRelative)) {
97
+ throw new Error(`synthetic path resolves outside the worktree: ${syntheticPath}`);
98
+ }
99
+
100
+ if (stat.isSymbolicLink()) {
101
+ fs.unlinkSync(resolved);
102
+ return;
103
+ }
104
+ if (stat.isDirectory()) {
105
+ fs.rmSync(resolved, { recursive: true, force: true });
106
+ return;
107
+ }
108
+ fs.rmSync(resolved, { force: true });
396
109
  }
397
110
 
398
111
  function removeSyntheticPathsBeforeDiff(worktree: WorktreeInfo): void {
399
- if (worktree.syntheticPaths.length === 0) return;
400
- const seen = new Set<string>();
401
- for (const syntheticPath of worktree.syntheticPaths) {
402
- if (seen.has(syntheticPath)) continue;
403
- seen.add(syntheticPath);
404
- removeSyntheticPath(worktree, syntheticPath);
405
- }
406
- }
407
-
408
- function emptyDiff(index: number, agent: string, branch: string, patchPath: string, worktreePath?: string, captureError?: string): WorktreeDiff {
409
- return {
410
- index,
411
- agent,
412
- branch,
413
- diffStat: "",
414
- filesChanged: 0,
415
- insertions: 0,
416
- deletions: 0,
417
- patchPath,
418
- ...(worktreePath ? { worktreePath } : {}),
419
- ...(captureError ? { captureError } : {}),
420
- };
421
- }
422
-
423
- function parseNumstat(numstat: string): { filesChanged: number; insertions: number; deletions: number } {
424
- const lines = numstat
425
- .split("\n")
426
- .map((line) => line.trim())
427
- .filter(Boolean);
428
- let filesChanged = 0;
429
- let insertions = 0;
430
- let deletions = 0;
431
-
432
- for (const line of lines) {
433
- const [rawInsertions, rawDeletions] = line.split("\t");
434
- if (rawInsertions === undefined || rawDeletions === undefined) continue;
435
- filesChanged++;
436
- if (/^\d+$/.test(rawInsertions)) insertions += parseInt(rawInsertions, 10);
437
- if (/^\d+$/.test(rawDeletions)) deletions += parseInt(rawDeletions, 10);
438
- }
439
-
440
- return { filesChanged, insertions, deletions };
112
+ if (worktree.syntheticPaths.length === 0) {
113
+ return;
114
+ }
115
+ const seen = new Set<string>();
116
+ for (const syntheticPath of worktree.syntheticPaths) {
117
+ if (seen.has(syntheticPath)) {
118
+ continue;
119
+ }
120
+ seen.add(syntheticPath);
121
+ removeSyntheticPath(worktree, syntheticPath);
122
+ }
123
+ }
124
+
125
+ function emptyDiff(
126
+ input: Pick<
127
+ WorktreeDiff,
128
+ "index" | "agent" | "branch" | "patchPath" | "worktreePath" | "captureError"
129
+ >,
130
+ ): WorktreeDiff {
131
+ return {
132
+ ...input,
133
+ diffStat: "",
134
+ filesChanged: 0,
135
+ insertions: 0,
136
+ deletions: 0,
137
+ };
138
+ }
139
+
140
+ function parseNumstat(numstat: string): {
141
+ filesChanged: number;
142
+ insertions: number;
143
+ deletions: number;
144
+ } {
145
+ const lines = numstat
146
+ .split("\n")
147
+ .map((line) => line.trim())
148
+ .filter(Boolean);
149
+ let filesChanged = 0;
150
+ let insertions = 0;
151
+ let deletions = 0;
152
+
153
+ for (const line of lines) {
154
+ const columns = line.split("\t");
155
+ if (columns.length < 2) {
156
+ continue;
157
+ }
158
+ const [rawInsertions, rawDeletions] = columns;
159
+ filesChanged++;
160
+ if (/^\d+$/.test(rawInsertions)) {
161
+ insertions += parseInt(rawInsertions, 10);
162
+ }
163
+ if (/^\d+$/.test(rawDeletions)) {
164
+ deletions += parseInt(rawDeletions, 10);
165
+ }
166
+ }
167
+
168
+ return { filesChanged, insertions, deletions };
441
169
  }
442
170
 
443
171
  function captureWorktreeDiff(
444
- setup: WorktreeSetup,
445
- worktree: WorktreeInfo,
446
- agent: string,
447
- patchPath: string,
172
+ setup: Readonly<WorktreeSetup>,
173
+ worktree: WorktreeInfo,
174
+ agent: string,
175
+ patchPath: string,
448
176
  ): WorktreeDiff {
449
- removeSyntheticPathsBeforeDiff(worktree);
450
- runGitChecked(worktree.path, ["add", "-A"]);
451
- const diffStat = runGitChecked(worktree.path, ["diff", "--cached", "--stat", setup.baseCommit]).trim();
452
- const patch = runGitChecked(worktree.path, ["diff", "--cached", "--binary", "--no-textconv", "--no-ext-diff", setup.baseCommit]);
453
- const numstat = runGitChecked(worktree.path, ["diff", "--cached", "--numstat", setup.baseCommit]);
454
- fs.writeFileSync(patchPath, patch, "utf-8");
455
-
456
- if (!patch.trim()) {
457
- return emptyDiff(worktree.index, agent, worktree.branch, patchPath, worktree.path);
458
- }
459
-
460
- const parsed = parseNumstat(numstat);
461
- return {
462
- index: worktree.index,
463
- agent,
464
- branch: worktree.branch,
465
- diffStat,
466
- filesChanged: parsed.filesChanged,
467
- insertions: parsed.insertions,
468
- deletions: parsed.deletions,
469
- patchPath,
470
- worktreePath: worktree.path,
471
- };
177
+ removeSyntheticPathsBeforeDiff(worktree);
178
+ runGitChecked(worktree.path, ["add", "-A"]);
179
+ const diffStat = runGitChecked(worktree.path, [
180
+ "diff",
181
+ "--cached",
182
+ "--stat",
183
+ setup.baseCommit,
184
+ ]).trim();
185
+ const patch = runGitChecked(worktree.path, [
186
+ "diff",
187
+ "--cached",
188
+ "--binary",
189
+ "--no-textconv",
190
+ "--no-ext-diff",
191
+ setup.baseCommit,
192
+ ]);
193
+ const numstat = runGitChecked(worktree.path, ["diff", "--cached", "--numstat", setup.baseCommit]);
194
+ fs.writeFileSync(patchPath, patch, "utf-8");
195
+
196
+ if (patch.trim().length === 0) {
197
+ return emptyDiff({
198
+ index: worktree.index,
199
+ agent,
200
+ branch: worktree.branch,
201
+ patchPath,
202
+ worktreePath: worktree.path,
203
+ });
204
+ }
205
+
206
+ const parsed = parseNumstat(numstat);
207
+ return {
208
+ index: worktree.index,
209
+ agent,
210
+ branch: worktree.branch,
211
+ diffStat,
212
+ filesChanged: parsed.filesChanged,
213
+ insertions: parsed.insertions,
214
+ deletions: parsed.deletions,
215
+ patchPath,
216
+ worktreePath: worktree.path,
217
+ };
472
218
  }
473
219
 
474
220
  function writeEmptyPatch(patchPath: string): void {
475
- try {
476
- fs.writeFileSync(patchPath, "", "utf-8");
477
- } catch {
478
- // Diff artifact writing is best-effort in error paths.
479
- }
221
+ try {
222
+ fs.writeFileSync(patchPath, "", "utf-8");
223
+ } catch {
224
+ // Diff artifact writing is best-effort in error paths.
225
+ }
480
226
  }
481
227
 
482
228
  function cleanupSingleWorktree(repoCwd: string, worktree: WorktreeInfo): void {
483
- try { runGitChecked(repoCwd, ["worktree", "remove", "--force", worktree.path]); } catch (error) {
484
- console.error(`Failed to remove worktree '${worktree.path}': ${getErrorMessage(error)}`);
485
- }
486
- try { runGitChecked(repoCwd, ["branch", "-D", worktree.branch]); } catch (error) {
487
- console.error(`Failed to delete worktree branch '${worktree.branch}': ${getErrorMessage(error)}`);
488
- }
229
+ try {
230
+ runGitChecked(repoCwd, ["worktree", "remove", "--force", worktree.path]);
231
+ } catch (error) {
232
+ console.error(`Failed to remove worktree '${worktree.path}': ${getErrorMessage(error)}`);
233
+ }
234
+ try {
235
+ runGitChecked(repoCwd, ["branch", "-D", worktree.branch]);
236
+ } catch (error) {
237
+ console.error(
238
+ `Failed to delete worktree branch '${worktree.branch}': ${getErrorMessage(error)}`,
239
+ );
240
+ }
489
241
  }
490
242
 
491
243
  function hasWorktreeChanges(diff: WorktreeDiff): boolean {
492
- return Boolean(diff.captureError) || diff.filesChanged > 0 || diff.insertions > 0 || diff.deletions > 0 || diff.diffStat.trim().length > 0;
493
- }
494
-
495
- function getErrorMessage(error: unknown): string {
496
- return error instanceof Error ? error.message : String(error);
497
- }
498
-
499
- function markWorktreesForPreservation(setup: WorktreeSetup, reason: string): void {
500
- setup.preserveOnCleanup = true;
501
- setup.preservationReason = setup.preservationReason
502
- ? `${setup.preservationReason}; ${reason}`
503
- : reason;
504
- }
505
-
506
- export async function createWorktrees(cwd: string, runId: string, count: number, options?: CreateWorktreesOptions): Promise<WorktreeSetup> {
507
- const signal = options?.signal;
508
- const repo = await resolveRepoState(cwd, signal);
509
- const setupHook = resolveWorktreeSetupHook(repo.toplevel, options?.setupHook);
510
- const worktrees: WorktreeInfo[] = [];
511
- const rollback: Array<{ path?: string; branch?: string }> = [];
512
-
513
- try {
514
- for (let index = 0; index < count; index++) {
515
- const branch = buildWorktreeBranch(runId, index);
516
- const worktreePath = buildWorktreePath(runId, index);
517
- ensureTempRoot();
518
- const pathExisted = fs.existsSync(worktreePath);
519
- const branchExisted = (await runSetupGit(repo.toplevel, ["branch", "--list", branch], signal)).trim().length > 0;
520
- rollback.push({ path: pathExisted ? undefined : worktreePath, branch: branchExisted ? undefined : branch });
521
- await runSetupGit(repo.toplevel, ["worktree", "add", worktreePath, "-b", branch, "HEAD"], signal);
522
- const agentCwd = repo.cwdRelative ? path.join(worktreePath, repo.cwdRelative) : worktreePath;
523
- const syntheticPaths = setupHook ? await runWorktreeSetupHook(setupHook, {
524
- version: 1, repoRoot: repo.toplevel, worktreePath, agentCwd, branch, index, runId,
525
- baseCommit: repo.baseCommit, agent: options?.agents?.[index],
526
- }, signal) : [];
527
- worktrees.push({ path: worktreePath, agentCwd, branch, index, syntheticPaths });
528
- }
529
- } catch (error) {
530
- // Rollback still runs after Stop, but its own Git hooks must not hang the owner.
531
- const cleanupSignal = AbortSignal.timeout(setupHook?.timeoutMs ?? DEFAULT_WORKTREE_SETUP_HOOK_TIMEOUT_MS);
532
- const failures: string[] = [];
533
- for (const entry of rollback.reverse()) {
534
- if (entry.path && fs.existsSync(entry.path)) {
535
- try {
536
- // Interrupted checkout can leave Git's "initializing" lock on this new worktree.
537
- await runSetupGit(repo.toplevel, ["worktree", "remove", "--force", "--force", entry.path], cleanupSignal);
538
- } catch (cleanupError) { failures.push(`Could not remove worktree '${entry.path}': ${getErrorMessage(cleanupError)}`); }
539
- }
540
- if (entry.branch) {
541
- try {
542
- const exists = (await runSetupGit(repo.toplevel, ["branch", "--list", entry.branch], cleanupSignal)).trim();
543
- if (exists) await runSetupGit(repo.toplevel, ["branch", "-D", entry.branch], cleanupSignal);
544
- } catch (cleanupError) { failures.push(`Could not delete worktree branch '${entry.branch}': ${getErrorMessage(cleanupError)}`); }
545
- }
546
- }
547
- if (failures.length) throw new WorktreeCleanupError(`${getErrorMessage(error)}\n\nWorktree rollback incomplete:\n${failures.join("\n")}`);
548
- throw error;
549
- }
550
-
551
- return { cwd: repo.toplevel, worktrees, baseCommit: repo.baseCommit };
552
- }
553
-
554
- export function diffWorktrees(setup: WorktreeSetup, agents: string[], diffsDir: string): WorktreeDiff[] {
555
- try {
556
- fs.mkdirSync(diffsDir, { recursive: true });
557
- } catch (error) {
558
- const message = `failed to create worktree diff artifact directory '${diffsDir}': ${getErrorMessage(error)}`;
559
- markWorktreesForPreservation(setup, message);
560
- return setup.worktrees.map((worktree, index) => {
561
- const agent = agents[index] ?? `task-${index + 1}`;
562
- const patchPath = path.join(diffsDir, `task-${index}-${safePatchAgentName(agent)}.patch`);
563
- return emptyDiff(index, agent, worktree.branch, patchPath, worktree.path, message);
564
- });
565
- }
566
-
567
- const diffs: WorktreeDiff[] = [];
568
- for (let index = 0; index < setup.worktrees.length; index++) {
569
- const worktree = setup.worktrees[index]!;
570
- const agent = agents[index] ?? `task-${index + 1}`;
571
- const patchPath = path.join(diffsDir, `task-${index}-${safePatchAgentName(agent)}.patch`);
572
- try {
573
- diffs.push(captureWorktreeDiff(setup, worktree, agent, patchPath));
574
- } catch (error) {
575
- const message = `failed to capture worktree diff for task ${index + 1} (${agent}) at '${worktree.path}': ${getErrorMessage(error)}`;
576
- markWorktreesForPreservation(setup, message);
577
- writeEmptyPatch(patchPath);
578
- diffs.push(emptyDiff(index, agent, worktree.branch, patchPath, worktree.path, message));
579
- }
580
- }
581
-
582
- return diffs;
583
- }
584
-
585
- export function cleanupWorktrees(setup: WorktreeSetup): void {
586
- if (setup.preserveOnCleanup) return;
587
- for (let index = setup.worktrees.length - 1; index >= 0; index--) {
588
- cleanupSingleWorktree(setup.cwd, setup.worktrees[index]!);
589
- }
590
- try { runGitChecked(setup.cwd, ["worktree", "prune"]); } catch (error) {
591
- console.error(`Failed to prune worktrees in '${setup.cwd}': ${getErrorMessage(error)}`);
592
- }
593
- }
594
-
595
- export function formatWorktreeDiffSummary(diffs: WorktreeDiff[]): string {
596
- const changed = diffs.filter(hasWorktreeChanges);
597
- if (changed.length === 0) return "";
598
-
599
- const lines: string[] = ["=== Worktree Changes ===", ""];
600
- for (const diff of changed) {
601
- lines.push(
602
- `--- Task ${diff.index + 1} (${diff.agent}): ${diff.filesChanged} files changed, +${diff.insertions} -${diff.deletions} ---`,
603
- );
604
- if (diff.captureError) {
605
- lines.push(`Diff capture failed: ${diff.captureError}`);
606
- if (diff.worktreePath) lines.push(`Preserved worktree: ${diff.worktreePath}`);
607
- lines.push(`Preserved branch: ${diff.branch}`);
608
- }
609
- if (diff.diffStat.trim().length > 0) {
610
- lines.push(diff.diffStat);
611
- }
612
- lines.push("");
613
- }
614
-
615
- const patchesDir = path.dirname(changed[0]!.patchPath);
616
- if (changed.some((diff) => !diff.captureError)) {
617
- lines.push(`Full patches: ${patchesDir}`);
618
- } else {
619
- lines.push("Patch artifacts unavailable; preserved worktrees contain the recoverable changes.");
620
- }
621
- return lines.join("\n").trimEnd();
244
+ return (
245
+ Boolean(diff.captureError) ||
246
+ diff.filesChanged > 0 ||
247
+ diff.insertions > 0 ||
248
+ diff.deletions > 0 ||
249
+ diff.diffStat.trim().length > 0
250
+ );
251
+ }
252
+
253
+ interface WorktreeReservation {
254
+ readonly path?: string;
255
+ readonly branch?: string;
256
+ }
257
+ interface WorktreePlan {
258
+ readonly repo: Readonly<RepoState>;
259
+ readonly runId: string;
260
+ readonly hook?: ResolvedWorktreeSetupHook;
261
+ readonly signal?: AbortSignal;
262
+ readonly agents?: readonly string[];
263
+ }
264
+
265
+ async function reserveWorktree(
266
+ plan: WorktreePlan,
267
+ index: number,
268
+ ): Promise<{ worktree: WorktreeInfo; reservation: WorktreeReservation }> {
269
+ const { repo, signal } = plan;
270
+ const branch = buildWorktreeBranch(plan.runId, index);
271
+ const worktreePath = buildWorktreePath(plan.runId, index);
272
+ ensureTempRoot();
273
+ const pathExisted = fs.existsSync(worktreePath);
274
+ const branchExisted =
275
+ (await runSetupGit(repo.toplevel, ["branch", "--list", branch], signal)).trim().length > 0;
276
+ // Reserve only resources this attempt can create, before Git can partially initialize them.
277
+ const agentCwd =
278
+ repo.cwdRelative.length > 0 ? path.join(worktreePath, repo.cwdRelative) : worktreePath;
279
+ return {
280
+ worktree: { path: worktreePath, agentCwd, branch, index, syntheticPaths: [] },
281
+ reservation: {
282
+ path: pathExisted ? undefined : worktreePath,
283
+ branch: branchExisted ? undefined : branch,
284
+ },
285
+ };
286
+ }
287
+
288
+ async function initializeWorktree(
289
+ plan: WorktreePlan,
290
+ worktree: WorktreeInfo,
291
+ ): Promise<WorktreeInfo> {
292
+ const { repo, signal } = plan;
293
+ const { path: worktreePath, agentCwd, branch, index } = worktree;
294
+ await runSetupGit(repo.toplevel, ["worktree", "add", worktreePath, "-b", branch, "HEAD"], signal);
295
+ const syntheticPaths = plan.hook
296
+ ? await runWorktreeSetupHook(
297
+ plan.hook,
298
+ {
299
+ version: 1,
300
+ repoRoot: repo.toplevel,
301
+ worktreePath,
302
+ agentCwd,
303
+ branch,
304
+ index,
305
+ runId: plan.runId,
306
+ baseCommit: repo.baseCommit,
307
+ agent: plan.agents?.[index],
308
+ },
309
+ signal,
310
+ )
311
+ : [];
312
+ return { path: worktreePath, agentCwd, branch, index, syntheticPaths };
313
+ }
314
+
315
+ async function rollbackWorktree(
316
+ repoCwd: string,
317
+ entry: WorktreeReservation,
318
+ signal: AbortSignal,
319
+ ): Promise<string[]> {
320
+ const failures: string[] = [];
321
+ if (entry.path !== undefined && fs.existsSync(entry.path)) {
322
+ try {
323
+ // An interrupted checkout can leave Git's initializing lock on this newly owned path.
324
+ await runSetupGit(repoCwd, ["worktree", "remove", "--force", "--force", entry.path], signal);
325
+ } catch (error) {
326
+ failures.push(`Could not remove worktree '${entry.path}': ${getErrorMessage(error)}`);
327
+ }
328
+ }
329
+ if (entry.branch !== undefined) {
330
+ try {
331
+ const exists = (
332
+ await runSetupGit(repoCwd, ["branch", "--list", entry.branch], signal)
333
+ ).trim();
334
+ if (exists.length > 0) {
335
+ await runSetupGit(repoCwd, ["branch", "-D", entry.branch], signal);
336
+ }
337
+ } catch (error) {
338
+ failures.push(
339
+ `Could not delete worktree branch '${entry.branch}': ${getErrorMessage(error)}`,
340
+ );
341
+ }
342
+ }
343
+ return failures;
344
+ }
345
+
346
+ export async function createWorktrees(
347
+ cwd: string,
348
+ runId: string,
349
+ count: number,
350
+ options?: CreateWorktreesOptions,
351
+ ): Promise<WorktreeSetup> {
352
+ const signal = options?.signal;
353
+ const repo = await resolveRepoState(cwd, signal);
354
+ const hook = resolveWorktreeSetupHook(repo.toplevel, options?.setupHook);
355
+ const plan: WorktreePlan = { repo, runId, hook, signal, agents: options?.agents };
356
+ const worktrees: WorktreeInfo[] = [];
357
+ const reservations: WorktreeReservation[] = [];
358
+ try {
359
+ for (let index = 0; index < count; index++) {
360
+ // Git modifies a shared repository; finish setup before reserving the next worktree.
361
+ // oxlint-disable-next-line no-await-in-loop
362
+ const { worktree, reservation } = await reserveWorktree(plan, index);
363
+ reservations.push(reservation);
364
+ // Register rollback authority before Git can partially initialize this worktree.
365
+ // oxlint-disable-next-line no-await-in-loop
366
+ worktrees.push(await initializeWorktree(plan, worktree));
367
+ }
368
+ } catch (error) {
369
+ // Rollback remains owned after cancellation and has its own bounded lifetime.
370
+ const cleanupSignal = AbortSignal.timeout(
371
+ hook?.timeoutMs ?? DEFAULT_WORKTREE_SETUP_HOOK_TIMEOUT_MS,
372
+ );
373
+ const failures: string[] = [];
374
+ for (const entry of reservations.reverse()) {
375
+ // Reverse-order resource release must finish before removing the preceding reservation.
376
+ // oxlint-disable-next-line no-await-in-loop
377
+ failures.push(...(await rollbackWorktree(repo.toplevel, entry, cleanupSignal)));
378
+ }
379
+ if (failures.length > 0) {
380
+ throw new WorktreeCleanupError(
381
+ `${getErrorMessage(error)}\n\nWorktree rollback incomplete:\n${failures.join("\n")}`,
382
+ { cause: error },
383
+ );
384
+ }
385
+ throw error;
386
+ }
387
+ return { cwd: repo.toplevel, worktrees, baseCommit: repo.baseCommit };
388
+ }
389
+
390
+ export function diffWorktrees(
391
+ setup: Readonly<WorktreeSetup>,
392
+ agents: readonly string[],
393
+ diffsDir: string,
394
+ ): WorktreeDiff[] {
395
+ try {
396
+ fs.mkdirSync(diffsDir, { recursive: true });
397
+ } catch (error) {
398
+ const message = `failed to create worktree diff artifact directory '${diffsDir}': ${getErrorMessage(error)}`;
399
+ markWorktreesForPreservation(setup, message);
400
+ return setup.worktrees.map((worktree, index) => {
401
+ const agent = agents[index] ?? `task-${index + 1}`;
402
+ const patchPath = path.join(diffsDir, `task-${index}-${safePatchAgentName(agent)}.patch`);
403
+ return emptyDiff({
404
+ index,
405
+ agent,
406
+ branch: worktree.branch,
407
+ patchPath,
408
+ worktreePath: worktree.path,
409
+ captureError: message,
410
+ });
411
+ });
412
+ }
413
+
414
+ const diffs: WorktreeDiff[] = [];
415
+ for (let index = 0; index < setup.worktrees.length; index++) {
416
+ const worktree = setup.worktrees[index];
417
+ const agent = agents[index] ?? `task-${index + 1}`;
418
+ const patchPath = path.join(diffsDir, `task-${index}-${safePatchAgentName(agent)}.patch`);
419
+ try {
420
+ diffs.push(captureWorktreeDiff(setup, worktree, agent, patchPath));
421
+ } catch (error) {
422
+ const message = `failed to capture worktree diff for task ${index + 1} (${agent}) at '${worktree.path}': ${getErrorMessage(error)}`;
423
+ markWorktreesForPreservation(setup, message);
424
+ writeEmptyPatch(patchPath);
425
+ diffs.push(
426
+ emptyDiff({
427
+ index,
428
+ agent,
429
+ branch: worktree.branch,
430
+ patchPath,
431
+ worktreePath: worktree.path,
432
+ captureError: message,
433
+ }),
434
+ );
435
+ }
436
+ }
437
+
438
+ return diffs;
439
+ }
440
+
441
+ export function cleanupWorktrees(setup: Readonly<WorktreeSetup>): void {
442
+ if (setup.preserveOnCleanup === true) {
443
+ return;
444
+ }
445
+ for (let index = setup.worktrees.length - 1; index >= 0; index--) {
446
+ cleanupSingleWorktree(setup.cwd, setup.worktrees[index]);
447
+ }
448
+ try {
449
+ runGitChecked(setup.cwd, ["worktree", "prune"]);
450
+ } catch (error) {
451
+ console.error(`Failed to prune worktrees in '${setup.cwd}': ${getErrorMessage(error)}`);
452
+ }
453
+ }
454
+
455
+ function worktreeRecoveryLines(diff: WorktreeDiff): string[] {
456
+ if (diff.captureError === undefined || diff.captureError.length === 0) {
457
+ return [];
458
+ }
459
+ const lines = [`Diff capture failed: ${diff.captureError}`];
460
+ if (diff.worktreePath !== undefined && diff.worktreePath.length > 0) {
461
+ lines.push(`Preserved worktree: ${diff.worktreePath}`);
462
+ }
463
+ lines.push(`Preserved branch: ${diff.branch}`);
464
+ return lines;
465
+ }
466
+
467
+ export function formatWorktreeDiffSummary(diffs: readonly WorktreeDiff[]): string {
468
+ const changed = diffs.filter(hasWorktreeChanges);
469
+ if (changed.length === 0) {
470
+ return "";
471
+ }
472
+
473
+ const lines: string[] = ["=== Worktree Changes ===", ""];
474
+ for (const diff of changed) {
475
+ lines.push(
476
+ `--- Task ${diff.index + 1} (${diff.agent}): ${diff.filesChanged} files changed, +${diff.insertions} -${diff.deletions} ---`,
477
+ );
478
+ lines.push(...worktreeRecoveryLines(diff));
479
+ if (diff.diffStat.trim().length > 0) {
480
+ lines.push(diff.diffStat);
481
+ }
482
+ lines.push("");
483
+ }
484
+
485
+ const patchesDir = path.dirname(changed[0]?.patchPath ?? "");
486
+ if (changed.some((diff) => (diff.captureError ?? "").length === 0)) {
487
+ lines.push(`Full patches: ${patchesDir}`);
488
+ } else {
489
+ lines.push("Patch artifacts unavailable; preserved worktrees contain the recoverable changes.");
490
+ }
491
+ return lines.join("\n").trimEnd();
622
492
  }
623
493
 
624
494
  export function formatParallelWorktreeSummary(
625
- worktreeSetup: WorktreeSetup | undefined,
626
- diffsDir: string,
627
- agents: string[],
495
+ worktreeSetup: Readonly<WorktreeSetup> | undefined,
496
+ diffsDir: string,
497
+ agents: readonly string[],
628
498
  ): string {
629
- if (!worktreeSetup) return "";
630
- return formatWorktreeDiffSummary(diffWorktrees(worktreeSetup, agents, diffsDir));
499
+ if (!worktreeSetup) {
500
+ return "";
501
+ }
502
+ return formatWorktreeDiffSummary(diffWorktrees(worktreeSetup, agents, diffsDir));
631
503
  }
632
504
 
633
505
  export function appendWorktreeSummary(output: string, worktreeSummary: string): string {
634
- return worktreeSummary ? `${output}\n\n${worktreeSummary}` : output;
506
+ return worktreeSummary.length > 0 ? `${output}\n\n${worktreeSummary}` : output;
635
507
  }