pi-crew 0.9.53 → 0.9.55

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 (493) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/package.json +6 -3
  3. package/scripts/clean-strip-types.mjs +60 -0
  4. package/src/adapters/claude-adapter.ts +23 -0
  5. package/src/adapters/codex-adapter.ts +21 -0
  6. package/src/adapters/cursor-adapter.ts +17 -0
  7. package/src/adapters/export-util.ts +143 -0
  8. package/src/adapters/index.ts +15 -0
  9. package/src/adapters/registry.ts +18 -0
  10. package/src/adapters/types.ts +23 -0
  11. package/src/agents/agent-config.ts +194 -0
  12. package/src/agents/agent-search.ts +98 -0
  13. package/src/agents/agent-serializer.ts +38 -0
  14. package/src/agents/discover-agents.ts +623 -0
  15. package/src/benchmark/benchmark-runner.ts +313 -0
  16. package/src/benchmark/feedback-loop.ts +73 -0
  17. package/src/config/config.ts +1270 -0
  18. package/src/config/defaults.ts +193 -0
  19. package/src/config/drift-detector.ts +290 -0
  20. package/src/config/markers.ts +330 -0
  21. package/src/config/resilient-parser.ts +117 -0
  22. package/src/config/role-tools.ts +118 -0
  23. package/src/config/suggestions.ts +75 -0
  24. package/src/config/types.ts +280 -0
  25. package/src/errors.ts +194 -0
  26. package/src/extension/action-suggestions.ts +117 -0
  27. package/src/extension/async-notifier.ts +179 -0
  28. package/src/extension/autonomous-policy.ts +209 -0
  29. package/src/extension/command-completions.ts +120 -0
  30. package/src/extension/context-status-injection.ts +185 -0
  31. package/src/extension/crew-autocomplete.ts +131 -0
  32. package/src/extension/crew-cleanup.ts +168 -0
  33. package/src/extension/crew-input-router.ts +93 -0
  34. package/src/extension/crew-shortcuts.ts +87 -0
  35. package/src/extension/crew-vibes/cat-frames.ts +18 -0
  36. package/src/extension/crew-vibes/config.ts +195 -0
  37. package/src/extension/crew-vibes/figures.ts +101 -0
  38. package/src/extension/crew-vibes/font-detect.ts +71 -0
  39. package/src/extension/crew-vibes/footer.ts +292 -0
  40. package/src/extension/crew-vibes/index.ts +432 -0
  41. package/src/extension/crew-vibes/provider-usage.ts +396 -0
  42. package/src/extension/crew-vibes/render.ts +206 -0
  43. package/src/extension/crew-vibes/speed.ts +286 -0
  44. package/src/extension/cross-extension-rpc.ts +350 -0
  45. package/src/extension/help.ts +62 -0
  46. package/src/extension/import-index.ts +74 -0
  47. package/src/extension/knowledge-injection.ts +402 -0
  48. package/src/extension/management.ts +571 -0
  49. package/src/extension/message-renderers.ts +108 -0
  50. package/src/extension/notification-router.ts +152 -0
  51. package/src/extension/notification-sink.ts +54 -0
  52. package/src/extension/pi-api.ts +56 -0
  53. package/src/extension/plan-orchestrate.ts +302 -0
  54. package/src/extension/project-init.ts +170 -0
  55. package/src/extension/register.ts +113 -0
  56. package/src/extension/registration/artifact-cleanup.ts +20 -0
  57. package/src/extension/registration/command-registration.ts +58 -0
  58. package/src/extension/registration/command-utils.ts +58 -0
  59. package/src/extension/registration/commands.ts +1228 -0
  60. package/src/extension/registration/compaction-guard.ts +333 -0
  61. package/src/extension/registration/context-builder.ts +135 -0
  62. package/src/extension/registration/crash-recovery-cache.ts +54 -0
  63. package/src/extension/registration/foreground-run-controller.ts +238 -0
  64. package/src/extension/registration/hook-registration.ts +119 -0
  65. package/src/extension/registration/lazy-configurers.ts +124 -0
  66. package/src/extension/registration/lifecycle-handlers.ts +923 -0
  67. package/src/extension/registration/lifecycle.ts +259 -0
  68. package/src/extension/registration/observability.ts +322 -0
  69. package/src/extension/registration/registration-types.ts +158 -0
  70. package/src/extension/registration/runtime-cleanup.ts +230 -0
  71. package/src/extension/registration/subagent-helpers.ts +125 -0
  72. package/src/extension/registration/subagent-manager-setup.ts +330 -0
  73. package/src/extension/registration/subagent-tools.ts +456 -0
  74. package/src/extension/registration/team-tool.ts +225 -0
  75. package/src/extension/registration/tool-registration.ts +50 -0
  76. package/src/extension/registration/ui.ts +172 -0
  77. package/src/extension/registration/viewers.ts +121 -0
  78. package/src/extension/registration/wire-cross-extension.ts +37 -0
  79. package/src/extension/result-watcher.ts +139 -0
  80. package/src/extension/rpc-hmac.ts +256 -0
  81. package/src/extension/run-bundle-schema.ts +104 -0
  82. package/src/extension/run-export.ts +97 -0
  83. package/src/extension/run-import.ts +168 -0
  84. package/src/extension/run-index.ts +131 -0
  85. package/src/extension/run-maintenance.ts +190 -0
  86. package/src/extension/session-summary.ts +18 -0
  87. package/src/extension/team-manager-command.ts +153 -0
  88. package/src/extension/team-onboard.ts +180 -0
  89. package/src/extension/team-recommendation.ts +288 -0
  90. package/src/extension/team-tool/anchor.ts +172 -0
  91. package/src/extension/team-tool/api.ts +1244 -0
  92. package/src/extension/team-tool/auto-summarize.ts +142 -0
  93. package/src/extension/team-tool/cache-control.ts +19 -0
  94. package/src/extension/team-tool/cancel.ts +337 -0
  95. package/src/extension/team-tool/chain-dispatch.ts +95 -0
  96. package/src/extension/team-tool/chain-executor.ts +379 -0
  97. package/src/extension/team-tool/config-patch.ts +50 -0
  98. package/src/extension/team-tool/context.ts +149 -0
  99. package/src/extension/team-tool/destructive-gate.ts +52 -0
  100. package/src/extension/team-tool/dispatch/automate.ts +80 -0
  101. package/src/extension/team-tool/dispatch/control.ts +42 -0
  102. package/src/extension/team-tool/dispatch/index.ts +95 -0
  103. package/src/extension/team-tool/dispatch/manage.ts +161 -0
  104. package/src/extension/team-tool/dispatch/run.ts +53 -0
  105. package/src/extension/team-tool/dispatch/status.ts +187 -0
  106. package/src/extension/team-tool/doctor.ts +431 -0
  107. package/src/extension/team-tool/explain.ts +282 -0
  108. package/src/extension/team-tool/failure-patterns.ts +118 -0
  109. package/src/extension/team-tool/goal-wrap.ts +330 -0
  110. package/src/extension/team-tool/goal.ts +556 -0
  111. package/src/extension/team-tool/handle-schedule.ts +332 -0
  112. package/src/extension/team-tool/handle-settings.ts +520 -0
  113. package/src/extension/team-tool/health-monitor.ts +481 -0
  114. package/src/extension/team-tool/inspect.ts +88 -0
  115. package/src/extension/team-tool/intent-policy.ts +45 -0
  116. package/src/extension/team-tool/lifecycle-actions.ts +615 -0
  117. package/src/extension/team-tool/orchestrate.ts +98 -0
  118. package/src/extension/team-tool/parallel-dispatch.ts +183 -0
  119. package/src/extension/team-tool/plan.ts +50 -0
  120. package/src/extension/team-tool/respond.ts +150 -0
  121. package/src/extension/team-tool/run-deadline.ts +66 -0
  122. package/src/extension/team-tool/run-not-found.ts +49 -0
  123. package/src/extension/team-tool/run.ts +1051 -0
  124. package/src/extension/team-tool/status.ts +260 -0
  125. package/src/extension/team-tool/workflow-manage.ts +261 -0
  126. package/src/extension/team-tool-types.ts +26 -0
  127. package/src/extension/team-tool.ts +750 -0
  128. package/src/extension/tool-result.ts +16 -0
  129. package/src/extension/validate-resources.ts +108 -0
  130. package/src/hooks/registry.ts +200 -0
  131. package/src/hooks/types.ts +69 -0
  132. package/src/i18n.ts +212 -0
  133. package/src/observability/correlation.ts +50 -0
  134. package/src/observability/event-bus.ts +86 -0
  135. package/src/observability/event-to-metric.ts +170 -0
  136. package/src/observability/exporters/adapter.ts +27 -0
  137. package/src/observability/exporters/otlp-exporter.ts +356 -0
  138. package/src/observability/exporters/prometheus-exporter.ts +54 -0
  139. package/src/observability/metric-registry.ts +100 -0
  140. package/src/observability/metric-retention.ts +64 -0
  141. package/src/observability/metric-sink.ts +99 -0
  142. package/src/observability/metrics-primitives.ts +219 -0
  143. package/src/plugins/plugin-define.ts +6 -0
  144. package/src/plugins/plugin-registry.ts +32 -0
  145. package/src/plugins/plugins/index.ts +3 -0
  146. package/src/plugins/plugins/nextjs.ts +19 -0
  147. package/src/plugins/plugins/vite.ts +10 -0
  148. package/src/plugins/plugins/vitest.ts +9 -0
  149. package/src/prompt/prompt-runtime.ts +380 -0
  150. package/src/runtime/adaptive-plan.ts +563 -0
  151. package/src/runtime/agent-control.ts +215 -0
  152. package/src/runtime/agent-memory.ts +81 -0
  153. package/src/runtime/agent-observability.ts +122 -0
  154. package/src/runtime/anchor-manager.ts +475 -0
  155. package/src/runtime/async-marker.ts +32 -0
  156. package/src/runtime/async-runner.ts +381 -0
  157. package/src/runtime/attention-events.ts +28 -0
  158. package/src/runtime/auto-summarize.ts +344 -0
  159. package/src/runtime/background-runner.ts +840 -0
  160. package/src/runtime/batch-barrier.ts +147 -0
  161. package/src/runtime/broker-issuer.ts +39 -0
  162. package/src/runtime/cancellation-token.ts +99 -0
  163. package/src/runtime/cancellation.ts +99 -0
  164. package/src/runtime/capability-inventory.ts +137 -0
  165. package/src/runtime/chain-parser.ts +237 -0
  166. package/src/runtime/chain-runner.ts +606 -0
  167. package/src/runtime/checkpoint.ts +291 -0
  168. package/src/runtime/child-pi-constants.ts +42 -0
  169. package/src/runtime/child-pi-kill.ts +180 -0
  170. package/src/runtime/child-pi-pool.ts +68 -0
  171. package/src/runtime/child-pi-spawn.ts +287 -0
  172. package/src/runtime/child-pi-steering.ts +128 -0
  173. package/src/runtime/child-pi-streams.ts +296 -0
  174. package/src/runtime/child-pi-transcript.ts +169 -0
  175. package/src/runtime/child-pi.ts +1105 -0
  176. package/src/runtime/coalesce-tasks.ts +268 -0
  177. package/src/runtime/code-summary.ts +292 -0
  178. package/src/runtime/command-trace.ts +105 -0
  179. package/src/runtime/compact-pipeline.ts +56 -0
  180. package/src/runtime/compact-stages/ansi-strip-stage.ts +25 -0
  181. package/src/runtime/compact-stages/blank-collapse-stage.ts +31 -0
  182. package/src/runtime/compact-stages/deduplicate-stage.ts +34 -0
  183. package/src/runtime/compact-stages/head-snap-stage.ts +57 -0
  184. package/src/runtime/compact-stages/index.ts +23 -0
  185. package/src/runtime/compact-stages/tail-capture-stage.ts +77 -0
  186. package/src/runtime/compact-stages/truncation-stage.ts +74 -0
  187. package/src/runtime/compaction-summary.ts +278 -0
  188. package/src/runtime/completion-guard.ts +207 -0
  189. package/src/runtime/concurrency.ts +58 -0
  190. package/src/runtime/crash-classification.ts +229 -0
  191. package/src/runtime/crash-recovery.ts +594 -0
  192. package/src/runtime/crew-agent-records.ts +595 -0
  193. package/src/runtime/crew-agent-runtime.ts +62 -0
  194. package/src/runtime/crew-broker-child.ts +88 -0
  195. package/src/runtime/crew-broker-client.ts +673 -0
  196. package/src/runtime/crew-broker-tokens.ts +159 -0
  197. package/src/runtime/crew-broker.ts +1304 -0
  198. package/src/runtime/crew-hooks.ts +227 -0
  199. package/src/runtime/cross-extension-rpc.ts +143 -0
  200. package/src/runtime/custom-tools/irc-tool.ts +282 -0
  201. package/src/runtime/custom-tools/submit-result-tool.ts +102 -0
  202. package/src/runtime/deadletter.ts +47 -0
  203. package/src/runtime/delivery-coordinator.ts +211 -0
  204. package/src/runtime/delta-conflict.ts +348 -0
  205. package/src/runtime/deterministic-ast.ts +161 -0
  206. package/src/runtime/diagnostic-export.ts +171 -0
  207. package/src/runtime/direct-run.ts +45 -0
  208. package/src/runtime/dwf-state-store.ts +102 -0
  209. package/src/runtime/dynamic-workflow-context.ts +1013 -0
  210. package/src/runtime/dynamic-workflow-runner.ts +325 -0
  211. package/src/runtime/effectiveness.ts +90 -0
  212. package/src/runtime/errors/crew-errors.ts +162 -0
  213. package/src/runtime/event-stream-bridge.ts +98 -0
  214. package/src/runtime/foreground-control.ts +193 -0
  215. package/src/runtime/foreground-watchdog.ts +132 -0
  216. package/src/runtime/global-worker-cap.ts +96 -0
  217. package/src/runtime/goal-achievement.ts +148 -0
  218. package/src/runtime/goal-evaluator.ts +365 -0
  219. package/src/runtime/goal-loop-runner.ts +825 -0
  220. package/src/runtime/goal-state-store.ts +219 -0
  221. package/src/runtime/green-contract.ts +55 -0
  222. package/src/runtime/group-join.ts +267 -0
  223. package/src/runtime/handoff-manager.ts +598 -0
  224. package/src/runtime/heartbeat-gradient.ts +36 -0
  225. package/src/runtime/heartbeat-watcher.ts +239 -0
  226. package/src/runtime/hidden-handoff.ts +407 -0
  227. package/src/runtime/important-line-classifier.ts +140 -0
  228. package/src/runtime/intercom-bridge.ts +187 -0
  229. package/src/runtime/iteration-hooks.ts +305 -0
  230. package/src/runtime/live-agent-control.ts +136 -0
  231. package/src/runtime/live-agent-manager.ts +704 -0
  232. package/src/runtime/live-control-realtime.ts +56 -0
  233. package/src/runtime/live-extension-bridge.ts +148 -0
  234. package/src/runtime/live-irc.ts +97 -0
  235. package/src/runtime/live-session-health.ts +108 -0
  236. package/src/runtime/live-session-runtime.ts +1146 -0
  237. package/src/runtime/loop-gates.ts +128 -0
  238. package/src/runtime/manifest-cache.ts +375 -0
  239. package/src/runtime/mcp-proxy.ts +105 -0
  240. package/src/runtime/metric-parser.ts +36 -0
  241. package/src/runtime/model-fallback.ts +409 -0
  242. package/src/runtime/model-resolver.ts +126 -0
  243. package/src/runtime/model-scope.ts +158 -0
  244. package/src/runtime/orphan-worker-registry.ts +465 -0
  245. package/src/runtime/output-validator.ts +202 -0
  246. package/src/runtime/overflow-recovery.ts +206 -0
  247. package/src/runtime/parallel-research.ts +68 -0
  248. package/src/runtime/parallel-utils.ts +160 -0
  249. package/src/runtime/parent-guard.ts +134 -0
  250. package/src/runtime/path-overlap.ts +150 -0
  251. package/src/runtime/peer-dep.ts +292 -0
  252. package/src/runtime/per-write-validator.ts +181 -0
  253. package/src/runtime/phase-progress.ts +217 -0
  254. package/src/runtime/phase-tracker.ts +385 -0
  255. package/src/runtime/pi-args.ts +655 -0
  256. package/src/runtime/pi-json-output.ts +170 -0
  257. package/src/runtime/pi-spawn.ts +273 -0
  258. package/src/runtime/pipeline-runner.ts +523 -0
  259. package/src/runtime/plan-templates.ts +201 -0
  260. package/src/runtime/policy-engine.ts +115 -0
  261. package/src/runtime/post-checks.ts +142 -0
  262. package/src/runtime/post-exit-stdio-guard.ts +109 -0
  263. package/src/runtime/process-lifecycle.ts +491 -0
  264. package/src/runtime/process-status.ts +154 -0
  265. package/src/runtime/progress-event-coalescer.ts +44 -0
  266. package/src/runtime/progress-tracker.ts +124 -0
  267. package/src/runtime/prose-compressor.ts +162 -0
  268. package/src/runtime/recovery-recipes.ts +203 -0
  269. package/src/runtime/replace.ts +570 -0
  270. package/src/runtime/resilient-edit.ts +153 -0
  271. package/src/runtime/result-extractor.ts +217 -0
  272. package/src/runtime/retry-executor.ts +109 -0
  273. package/src/runtime/retry-runner.ts +336 -0
  274. package/src/runtime/role-permission.ts +59 -0
  275. package/src/runtime/run-coalesced-task-group.ts +308 -0
  276. package/src/runtime/run-drift.ts +219 -0
  277. package/src/runtime/run-tracker.ts +116 -0
  278. package/src/runtime/run-worker.ts +78 -0
  279. package/src/runtime/runtime-policy.ts +29 -0
  280. package/src/runtime/runtime-resolver.ts +172 -0
  281. package/src/runtime/runtime-warmup.ts +160 -0
  282. package/src/runtime/scheduler.ts +353 -0
  283. package/src/runtime/semaphore.ts +141 -0
  284. package/src/runtime/sensitive-paths.ts +93 -0
  285. package/src/runtime/session-resources.ts +25 -0
  286. package/src/runtime/session-snapshot.ts +59 -0
  287. package/src/runtime/session-usage.ts +79 -0
  288. package/src/runtime/settings-store.ts +155 -0
  289. package/src/runtime/sidechain-output.ts +35 -0
  290. package/src/runtime/single-agent-compose.ts +84 -0
  291. package/src/runtime/skill-effectiveness.ts +524 -0
  292. package/src/runtime/skill-instructions.ts +394 -0
  293. package/src/runtime/stale-reconciler.ts +680 -0
  294. package/src/runtime/stream-preview.ts +184 -0
  295. package/src/runtime/streaming-output.ts +50 -0
  296. package/src/runtime/subagent-manager.ts +561 -0
  297. package/src/runtime/subprocess-tool-registry.ts +70 -0
  298. package/src/runtime/supervisor-contact.ts +64 -0
  299. package/src/runtime/task-display.ts +52 -0
  300. package/src/runtime/task-graph-scheduler.ts +227 -0
  301. package/src/runtime/task-graph.ts +290 -0
  302. package/src/runtime/task-health.ts +83 -0
  303. package/src/runtime/task-id.ts +161 -0
  304. package/src/runtime/task-output-context.ts +602 -0
  305. package/src/runtime/task-packet.ts +268 -0
  306. package/src/runtime/task-quality.ts +199 -0
  307. package/src/runtime/task-runner/capabilities.ts +78 -0
  308. package/src/runtime/task-runner/child-executor.ts +790 -0
  309. package/src/runtime/task-runner/context-retrieval.ts +163 -0
  310. package/src/runtime/task-runner/live-executor.ts +227 -0
  311. package/src/runtime/task-runner/output-splitter.ts +152 -0
  312. package/src/runtime/task-runner/post-execution.ts +480 -0
  313. package/src/runtime/task-runner/pre-execution.ts +365 -0
  314. package/src/runtime/task-runner/progress.ts +157 -0
  315. package/src/runtime/task-runner/prompt-builder.ts +278 -0
  316. package/src/runtime/task-runner/prompt-pipeline.ts +88 -0
  317. package/src/runtime/task-runner/result-utils.ts +16 -0
  318. package/src/runtime/task-runner/retrieval-orchestrator.ts +310 -0
  319. package/src/runtime/task-runner/run-projection.ts +125 -0
  320. package/src/runtime/task-runner/scaffold-executor.ts +25 -0
  321. package/src/runtime/task-runner/state-helpers.ts +176 -0
  322. package/src/runtime/task-runner/tail-read.ts +34 -0
  323. package/src/runtime/task-runner.ts +216 -0
  324. package/src/runtime/team-runner-artifacts.ts +13 -0
  325. package/src/runtime/team-runner.ts +2396 -0
  326. package/src/runtime/tool-output-pruner.ts +333 -0
  327. package/src/runtime/tool-progress.ts +278 -0
  328. package/src/runtime/usage-tracker.ts +73 -0
  329. package/src/runtime/verification-gates.ts +579 -0
  330. package/src/runtime/verification-integrity.ts +108 -0
  331. package/src/runtime/verification-worktree.ts +186 -0
  332. package/src/runtime/worker-heartbeat.ts +25 -0
  333. package/src/runtime/worker-startup.ts +83 -0
  334. package/src/runtime/workflow-state.ts +195 -0
  335. package/src/runtime/workspace-lock.ts +443 -0
  336. package/src/runtime/workspace-tree.ts +312 -0
  337. package/src/runtime/yield-handler.ts +224 -0
  338. package/src/runtime/zombie-scanner.ts +298 -0
  339. package/src/schema/config-schema.ts +327 -0
  340. package/src/schema/team-tool-schema.ts +492 -0
  341. package/src/schema/validation-types.ts +159 -0
  342. package/src/skills/discover-skills.ts +203 -0
  343. package/src/skills/skill-templates.ts +456 -0
  344. package/src/skills/validate.ts +285 -0
  345. package/src/state/active-run-registry.ts +439 -0
  346. package/src/state/artifact-store.ts +176 -0
  347. package/src/state/atomic-write.ts +970 -0
  348. package/src/state/blob-store.ts +308 -0
  349. package/src/state/contracts.ts +160 -0
  350. package/src/state/crew-init.ts +269 -0
  351. package/src/state/decision-ledger.ts +372 -0
  352. package/src/state/event-log-rotation.ts +399 -0
  353. package/src/state/event-log.ts +1465 -0
  354. package/src/state/event-reconstructor.ts +229 -0
  355. package/src/state/gitignore-manager.ts +46 -0
  356. package/src/state/health-store.ts +79 -0
  357. package/src/state/hook-instinct-bridge.ts +94 -0
  358. package/src/state/hook-integrations.ts +51 -0
  359. package/src/state/instinct-store.ts +275 -0
  360. package/src/state/jsonl-writer.ts +107 -0
  361. package/src/state/locks.ts +562 -0
  362. package/src/state/mailbox.ts +916 -0
  363. package/src/state/observation-store.ts +176 -0
  364. package/src/state/run-cache.ts +193 -0
  365. package/src/state/run-graph.ts +183 -0
  366. package/src/state/run-metrics.ts +165 -0
  367. package/src/state/schedule.ts +166 -0
  368. package/src/state/session-state-map.ts +51 -0
  369. package/src/state/state-store.ts +973 -0
  370. package/src/state/task-claims.ts +61 -0
  371. package/src/state/tiered-eval.ts +480 -0
  372. package/src/state/types-eval.ts +58 -0
  373. package/src/state/types.ts +447 -0
  374. package/src/state/usage.ts +138 -0
  375. package/src/state/worker-atomic-writer.ts +198 -0
  376. package/src/subagents/async-entry.ts +1 -0
  377. package/src/subagents/index.ts +3 -0
  378. package/src/subagents/live/control.ts +1 -0
  379. package/src/subagents/live/manager.ts +1 -0
  380. package/src/subagents/live/realtime.ts +1 -0
  381. package/src/subagents/live/session-runtime.ts +1 -0
  382. package/src/subagents/manager.ts +1 -0
  383. package/src/subagents/spawn.ts +1 -0
  384. package/src/teams/discover-teams.ts +187 -0
  385. package/src/teams/team-config.ts +27 -0
  386. package/src/teams/team-serializer.ts +38 -0
  387. package/src/tools/safe-bash-extension.ts +54 -0
  388. package/src/tools/safe-bash.ts +505 -0
  389. package/src/types/diff.d.ts +18 -0
  390. package/src/types/new-api-types.ts +35 -0
  391. package/src/ui/agent-management-overlay.ts +160 -0
  392. package/src/ui/card-colors.ts +139 -0
  393. package/src/ui/crew-footer.ts +102 -0
  394. package/src/ui/crew-select-list.ts +112 -0
  395. package/src/ui/dashboard-panes/agents-pane.ts +164 -0
  396. package/src/ui/dashboard-panes/cancellation-pane.ts +66 -0
  397. package/src/ui/dashboard-panes/capability-pane.ts +77 -0
  398. package/src/ui/dashboard-panes/health-pane.ts +31 -0
  399. package/src/ui/dashboard-panes/mailbox-pane.ts +35 -0
  400. package/src/ui/dashboard-panes/metrics-pane.ts +37 -0
  401. package/src/ui/dashboard-panes/progress-pane.ts +38 -0
  402. package/src/ui/dashboard-panes/transcript-pane.ts +10 -0
  403. package/src/ui/deploy-bundled-themes.ts +71 -0
  404. package/src/ui/dwf-phase-display.ts +151 -0
  405. package/src/ui/dynamic-border.ts +35 -0
  406. package/src/ui/format-helpers.ts +31 -0
  407. package/src/ui/heartbeat-aggregator.ts +74 -0
  408. package/src/ui/key-utils.ts +42 -0
  409. package/src/ui/keybinding-map.ts +248 -0
  410. package/src/ui/layout-primitives.ts +106 -0
  411. package/src/ui/live-conversation-overlay.ts +183 -0
  412. package/src/ui/live-duration.ts +55 -0
  413. package/src/ui/live-run-sidebar.ts +293 -0
  414. package/src/ui/loaders.ts +6 -0
  415. package/src/ui/mascot.ts +449 -0
  416. package/src/ui/overlays/agent-picker-overlay.ts +66 -0
  417. package/src/ui/overlays/confirm-overlay.ts +60 -0
  418. package/src/ui/overlays/help-overlay.ts +177 -0
  419. package/src/ui/overlays/mailbox-compose-overlay.ts +181 -0
  420. package/src/ui/overlays/mailbox-compose-preview.ts +78 -0
  421. package/src/ui/overlays/mailbox-detail-overlay.ts +155 -0
  422. package/src/ui/pi-ui-compat.ts +68 -0
  423. package/src/ui/powerbar-publisher.ts +459 -0
  424. package/src/ui/render-coalescer.ts +101 -0
  425. package/src/ui/render-diff.ts +160 -0
  426. package/src/ui/render-scheduler.ts +278 -0
  427. package/src/ui/run-action-dispatcher.ts +182 -0
  428. package/src/ui/run-dashboard.ts +910 -0
  429. package/src/ui/run-event-bus.ts +381 -0
  430. package/src/ui/run-snapshot-cache.ts +1057 -0
  431. package/src/ui/settings-overlay.ts +1067 -0
  432. package/src/ui/shared-overlay-scheduler.ts +96 -0
  433. package/src/ui/snapshot-types.ts +90 -0
  434. package/src/ui/spinner.ts +17 -0
  435. package/src/ui/status-colors.ts +143 -0
  436. package/src/ui/syntax-highlight.ts +136 -0
  437. package/src/ui/terminal-status.ts +269 -0
  438. package/src/ui/theme-adapter.ts +271 -0
  439. package/src/ui/theme-discovery.ts +199 -0
  440. package/src/ui/tool-progress-formatter.ts +93 -0
  441. package/src/ui/tool-renderers/brief-mode.ts +225 -0
  442. package/src/ui/tool-renderers/index.ts +726 -0
  443. package/src/ui/transcript-cache.ts +128 -0
  444. package/src/ui/transcript-entries.ts +256 -0
  445. package/src/ui/transcript-viewer.ts +424 -0
  446. package/src/ui/widget/index.ts +418 -0
  447. package/src/ui/widget/widget-formatters.ts +182 -0
  448. package/src/ui/widget/widget-model.ts +110 -0
  449. package/src/ui/widget/widget-renderer.ts +182 -0
  450. package/src/ui/widget/widget-types.ts +36 -0
  451. package/src/utils/bm25-search.ts +235 -0
  452. package/src/utils/completion-dedupe.ts +63 -0
  453. package/src/utils/conflict-detect.ts +668 -0
  454. package/src/utils/env-allowlist.ts +30 -0
  455. package/src/utils/env-filter.ts +155 -0
  456. package/src/utils/file-coalescer.ts +98 -0
  457. package/src/utils/fingerprint.ts +180 -0
  458. package/src/utils/frontmatter.ts +70 -0
  459. package/src/utils/fs-watch.ts +37 -0
  460. package/src/utils/gh-protocol.ts +556 -0
  461. package/src/utils/git.ts +260 -0
  462. package/src/utils/guards.ts +107 -0
  463. package/src/utils/ids.ts +24 -0
  464. package/src/utils/incremental-reader.ts +220 -0
  465. package/src/utils/internal-error.ts +10 -0
  466. package/src/utils/names.ts +36 -0
  467. package/src/utils/ndjson.ts +115 -0
  468. package/src/utils/paths.ts +227 -0
  469. package/src/utils/project-detector.ts +160 -0
  470. package/src/utils/redaction.ts +370 -0
  471. package/src/utils/resolve-shell.ts +36 -0
  472. package/src/utils/run-watcher-registry.ts +152 -0
  473. package/src/utils/safe-paths.ts +393 -0
  474. package/src/utils/scan-cache.ts +144 -0
  475. package/src/utils/session-utils.ts +110 -0
  476. package/src/utils/sleep.ts +54 -0
  477. package/src/utils/socket-path.ts +164 -0
  478. package/src/utils/sse-parser.ts +131 -0
  479. package/src/utils/task-name-generator.ts +337 -0
  480. package/src/utils/timings.ts +33 -0
  481. package/src/utils/token-counter.ts +200 -0
  482. package/src/utils/visual.ts +207 -0
  483. package/src/workflows/cost-estimator.ts +34 -0
  484. package/src/workflows/discover-workflows.ts +308 -0
  485. package/src/workflows/intermediate-store.ts +166 -0
  486. package/src/workflows/preflight-validator.ts +168 -0
  487. package/src/workflows/topology-analyzer.ts +203 -0
  488. package/src/workflows/validate-workflow.ts +50 -0
  489. package/src/workflows/workflow-config.ts +75 -0
  490. package/src/workflows/workflow-serializer.ts +32 -0
  491. package/src/worktree/branch-freshness.ts +112 -0
  492. package/src/worktree/cleanup.ts +341 -0
  493. package/src/worktree/worktree-manager.ts +1204 -0
@@ -0,0 +1,336 @@
1
+ /**
2
+ * RetryRunner - Execute tasks with retry support and summary accumulation.
3
+ *
4
+ * Based on pi-boomerang's --rethrow pattern:
5
+ * - Retries failed tasks up to maxAttempts
6
+ * - Generates handoffs between attempts
7
+ * - Accumulates context from previous attempts
8
+ * - Supports exponential backoff
9
+ * - Limits handoff accumulation to prevent memory leaks
10
+ *
11
+ * @see docs/pi-boomerang-integration-plan.md
12
+ */
13
+
14
+ import type { HandoffManager, HandoffSummary, TaskPacket, TaskResult } from "./handoff-manager.ts";
15
+
16
+ /**
17
+ * Retry configuration.
18
+ */
19
+ export interface RetryConfig {
20
+ /** Maximum number of retry attempts */
21
+ maxAttempts: number;
22
+ /** Generate summary between attempts for context accumulation */
23
+ summaryBetweenAttempts?: boolean;
24
+ /** Stop retrying if task succeeds */
25
+ stopOnSuccess?: boolean;
26
+ /** Base backoff delay in milliseconds (multiplied by attempt number) */
27
+ backoffMs?: number;
28
+ /** Backoff multiplier (default: 1) */
29
+ backoffMultiplier?: number;
30
+ /** Maximum backoff delay cap in milliseconds */
31
+ maxBackoffMs?: number;
32
+ /** Custom retry condition (return true to retry) */
33
+ retryCondition?: (result: TaskResult, attempt: number) => boolean;
34
+ /** Maximum handoffs to retain (default: 100) - prevents memory leaks */
35
+ maxHandoffs?: number;
36
+ }
37
+
38
+ /**
39
+ * Result of a single attempt.
40
+ */
41
+ export interface AttemptResult {
42
+ attempt: number;
43
+ result: TaskResult;
44
+ summary?: HandoffSummary;
45
+ duration: number;
46
+ error?: string;
47
+ timestamp: number;
48
+ }
49
+
50
+ /**
51
+ * Final retry result.
52
+ */
53
+ export interface RetryResult {
54
+ success: boolean;
55
+ attempts: AttemptResult[];
56
+ finalResult?: TaskResult;
57
+ totalHandoffs: HandoffSummary[];
58
+ totalDuration: number;
59
+ }
60
+
61
+ /**
62
+ * Task runner interface (minimal for retry functionality).
63
+ */
64
+ export interface TaskRunnerLike {
65
+ runTask(packet: TaskPacket): Promise<TaskResult>;
66
+ }
67
+
68
+ /**
69
+ * RetryRunner handles task execution with automatic retry and summary accumulation.
70
+ */
71
+ export class RetryRunner {
72
+ private _disposed = false;
73
+
74
+ private taskRunner: TaskRunnerLike;
75
+ private handoffManager: HandoffManager;
76
+ private _handoffs: HandoffSummary[] = [];
77
+
78
+ constructor(taskRunner: TaskRunnerLike, handoffManager: HandoffManager) {
79
+ this.taskRunner = taskRunner;
80
+ this.handoffManager = handoffManager;
81
+ }
82
+
83
+ /**
84
+ * Check if this runner has been disposed.
85
+ */
86
+ get isDisposed(): boolean {
87
+ return this._disposed;
88
+ }
89
+
90
+ /**
91
+ * Dispose of resources held by this runner.
92
+ * Clears any accumulated state and prevents further operations.
93
+ */
94
+ dispose(): void {
95
+ this._disposed = true;
96
+ }
97
+
98
+ /**
99
+ * Clear accumulated handoffs to free memory.
100
+ * Useful when you want to reset state without disposing.
101
+ */
102
+ clearHandoffs(): void {
103
+ this._handoffs = [];
104
+ }
105
+
106
+ /**
107
+ * Get the effective max handoffs limit from config or default.
108
+ */
109
+ private getMaxHandoffs(config: RetryConfig): number {
110
+ return config.maxHandoffs ?? 100;
111
+ }
112
+
113
+ /**
114
+ * Trim handoffs array to max size, keeping most recent.
115
+ */
116
+ private trimHandoffs(handoffs: HandoffSummary[], maxSize: number): HandoffSummary[] {
117
+ if (handoffs.length <= maxSize) {
118
+ return handoffs;
119
+ }
120
+ // Keep the most recent handoffs (last maxSize items)
121
+ return handoffs.slice(-maxSize);
122
+ }
123
+
124
+ /**
125
+ * Execute task with retry support.
126
+ * Summaries accumulate between attempts for better context.
127
+ *
128
+ * @param packet - Task packet to execute
129
+ * @param config - Retry configuration (stopOnSuccess defaults to true)
130
+ * @returns Final retry result with all attempts
131
+ */
132
+ async runWithRetry(packet: TaskPacket, config: RetryConfig): Promise<RetryResult> {
133
+ if (this._disposed) {
134
+ throw new Error("RetryRunner has been disposed");
135
+ }
136
+
137
+ const attempts: AttemptResult[] = [];
138
+ const handoffs: HandoffSummary[] = [];
139
+ const startTime = Date.now();
140
+ const maxHandoffs = this.getMaxHandoffs(config);
141
+
142
+ // Clear previous handoffs at start of each retry run
143
+ this._handoffs = [];
144
+
145
+ for (let attempt = 1; attempt <= config.maxAttempts; attempt++) {
146
+ if (this._disposed) {
147
+ throw new Error("RetryRunner was disposed during retry");
148
+ }
149
+ const attemptStart = Date.now();
150
+
151
+ try {
152
+ // Inject accumulated handoffs into context
153
+ const enrichedPacket = this.enrichPacketWithHandoffs(packet, handoffs, attempt);
154
+
155
+ // Execute task
156
+ const result = await this.taskRunner.runTask(enrichedPacket);
157
+
158
+ const attemptResult: AttemptResult = {
159
+ attempt,
160
+ result,
161
+ duration: Date.now() - attemptStart,
162
+ timestamp: Date.now(),
163
+ };
164
+
165
+ // Generate summary between attempts
166
+ if (config.summaryBetweenAttempts !== false) {
167
+ const summary = await this.handoffManager.generateSummary(packet, result);
168
+ attemptResult.summary = summary;
169
+ handoffs.push(summary);
170
+
171
+ // Trim handoffs to prevent memory leak
172
+ if (handoffs.length > maxHandoffs) {
173
+ handoffs.splice(0, handoffs.length - maxHandoffs);
174
+ }
175
+ }
176
+
177
+ attempts.push(attemptResult);
178
+
179
+ // Stop on success if configured (default to true when undefined)
180
+ if ((config.stopOnSuccess ?? true) && result.outcome === "success") {
181
+ return {
182
+ success: true,
183
+ attempts,
184
+ finalResult: result,
185
+ totalHandoffs: handoffs,
186
+ totalDuration: Date.now() - startTime,
187
+ };
188
+ }
189
+
190
+ // Check custom retry condition
191
+ if (config.retryCondition && !config.retryCondition(result, attempt)) {
192
+ break;
193
+ }
194
+
195
+ // Check default retry condition (retry on failure)
196
+ if (result.outcome !== "success" && attempt < config.maxAttempts) {
197
+ // Apply backoff before retry
198
+ const backoffDelay = this.calculateBackoff(
199
+ config.backoffMs ?? 1000,
200
+ attempt,
201
+ config.backoffMultiplier ?? 1,
202
+ config.maxBackoffMs,
203
+ );
204
+ if (backoffDelay > 0) {
205
+ await this.sleep(backoffDelay);
206
+ }
207
+ }
208
+ } catch (error) {
209
+ attempts.push({
210
+ attempt,
211
+ result: {
212
+ outcome: "failure",
213
+ error: error instanceof Error ? error.message : String(error),
214
+ },
215
+ duration: Date.now() - attemptStart,
216
+ timestamp: Date.now(),
217
+ error: error instanceof Error ? error.message : String(error),
218
+ });
219
+
220
+ // Apply backoff before retry on error
221
+ if (config.backoffMs && attempt < config.maxAttempts) {
222
+ const backoffDelay = this.calculateBackoff(
223
+ config.backoffMs,
224
+ attempt,
225
+ config.backoffMultiplier ?? 1,
226
+ config.maxBackoffMs,
227
+ );
228
+ if (backoffDelay > 0) {
229
+ await this.sleep(backoffDelay);
230
+ }
231
+ }
232
+ }
233
+ }
234
+
235
+ const finalAttempt = attempts[attempts.length - 1];
236
+ return {
237
+ success: finalAttempt?.result.outcome === "success",
238
+ attempts,
239
+ finalResult: finalAttempt?.result,
240
+ totalHandoffs: this.trimHandoffs(handoffs, maxHandoffs),
241
+ totalDuration: Date.now() - startTime,
242
+ };
243
+ }
244
+
245
+ /**
246
+ * Enrich packet with accumulated handoffs from previous attempts.
247
+ */
248
+ private enrichPacketWithHandoffs(packet: TaskPacket, handoffs: HandoffSummary[], attempt: number): TaskPacket {
249
+ if (handoffs.length === 0) {
250
+ return packet;
251
+ }
252
+
253
+ // Build accumulated context from previous attempts
254
+ const accumulatedContext = handoffs
255
+ .map(
256
+ (h, index) =>
257
+ `## Attempt ${index + 1}: ${h.task}\n` +
258
+ `Outcome: ${h.outcome}\n` +
259
+ `Files: created=${h.filesCreated.join(", ")}, modified=${h.filesModified.join(", ")}\n` +
260
+ `Decisions: ${h.decisions.map((d) => d.rationale).join("; ")}\n` +
261
+ `Blockers: ${h.blockers.join(", ")}\n` +
262
+ `Next Steps: ${h.nextSteps.join(", ")}\n`,
263
+ )
264
+ .join("\n---\n");
265
+
266
+ return {
267
+ ...packet,
268
+ context: {
269
+ ...packet.context,
270
+ __boomerangAttempts: attempt,
271
+ __boomerangHandoffs: handoffs,
272
+ __boomerangContext: `Previous attempts summary:\n${accumulatedContext}`,
273
+ },
274
+ };
275
+ }
276
+
277
+ /**
278
+ * Calculate backoff delay with optional capping.
279
+ */
280
+ private calculateBackoff(baseMs: number, attempt: number, multiplier: number, maxBackoffMs?: number): number {
281
+ let delay = baseMs * multiplier ** (attempt - 1);
282
+ if (maxBackoffMs !== undefined) {
283
+ delay = Math.min(delay, maxBackoffMs);
284
+ }
285
+ return delay;
286
+ }
287
+
288
+ /**
289
+ * Sleep helper.
290
+ */
291
+ private sleep(ms: number): Promise<void> {
292
+ return new Promise((resolve) => setTimeout(resolve, ms));
293
+ }
294
+ }
295
+
296
+ /**
297
+ * Create a RetryRunner with default dependencies.
298
+ */
299
+ export function createRetryRunner(taskRunner: TaskRunnerLike, handoffManager: HandoffManager): RetryRunner {
300
+ return new RetryRunner(taskRunner, handoffManager);
301
+ }
302
+
303
+ /**
304
+ * Default retry config for common scenarios.
305
+ */
306
+ export const DEFAULT_RETRY_CONFIG: RetryConfig = {
307
+ maxAttempts: 3,
308
+ summaryBetweenAttempts: true,
309
+ stopOnSuccess: true,
310
+ backoffMs: 1000,
311
+ backoffMultiplier: 2,
312
+ maxBackoffMs: 30000,
313
+ };
314
+
315
+ /**
316
+ * Retry config for transient failures (quick retries).
317
+ */
318
+ export const TRANSIENT_FAILURE_RETRY_CONFIG: RetryConfig = {
319
+ maxAttempts: 2,
320
+ summaryBetweenAttempts: false,
321
+ stopOnSuccess: true,
322
+ backoffMs: 500,
323
+ backoffMultiplier: 1,
324
+ };
325
+
326
+ /**
327
+ * Retry config for persistent failures (exponential backoff).
328
+ */
329
+ export const PERSISTENT_FAILURE_RETRY_CONFIG: RetryConfig = {
330
+ maxAttempts: 5,
331
+ summaryBetweenAttempts: true,
332
+ stopOnSuccess: true,
333
+ backoffMs: 2000,
334
+ backoffMultiplier: 2,
335
+ maxBackoffMs: 60000,
336
+ };
@@ -0,0 +1,59 @@
1
+ export type RolePermissionMode = "read_only" | "workspace_write" | "danger_full_access" | "explicit_confirm";
2
+
3
+ // (FIND-12 R1 review: the AgentPermissionOptions opt-in was dropped — no caller
4
+ // passed `options` and AgentConfig has no `permissions` field, so it was dead
5
+ // code. Custom write-capable roles must instead be added to WRITE_ROLES below.)
6
+
7
+ // Read-only roles: cannot mutate files/source. `verifier` is NOT here — it runs
8
+ // tests (bash + cache writes) so it is a WRITE role (F4). `planner` stays
9
+ // read-only to preserve the plan-approval gate boundary (F3).
10
+ const READ_ONLY_ROLES = new Set(["explorer", "reviewer", "security-reviewer", "analyst", "critic", "planner"]);
11
+ // Write-capable roles: can mutate files/source within the workspace.
12
+ // FIND-12 (R1 review): this is an EXPLICIT allowlist — every shipped write-capable
13
+ // role must be listed here, otherwise default-deny demotes it to read-only.
14
+ // `agent` is the default direct-agent role (run.ts); `cold-verifier` runs bash/
15
+ // cache writes; `chain-executor` is the chain-workflow executor variant;
16
+ // `worker` is the autonomous goal-loop + dynamic-workflow executor role
17
+ // (goal-loop-runner.ts, run.ts dynamic workflow synthesis).
18
+ const WRITE_ROLES = new Set(["executor", "test-engineer", "writer", "verifier", "agent", "cold-verifier", "chain-executor", "worker"]);
19
+ export interface PermissionCheckResult {
20
+ allowed: boolean;
21
+ mode: RolePermissionMode;
22
+ reason?: string;
23
+ }
24
+
25
+ /**
26
+ * Resolve the permission mode for a given role.
27
+ *
28
+ * FIND-12 (2026-07-20): **default-deny** — unknown/unrecognized roles now
29
+ * receive `"read_only"` instead of the previous permissive `"workspace_write"`.
30
+ * This prevents privilege escalation via typo'd or custom agent names that
31
+ * accidentally inherit write capabilities. Write-capable roles are an EXPLICIT
32
+ * allowlist (`WRITE_ROLES`); to add a custom write-capable role, add it there.
33
+ * (R1 review: a per-agent-config `permissions.workspaceWrite` opt-in was
34
+ * considered but dropped — it was unreachable from all 8 call sites.)
35
+ *
36
+ * @param role the role name (case-sensitive, must match exactly)
37
+ */
38
+ export function permissionForRole(role: string): RolePermissionMode {
39
+ if (READ_ONLY_ROLES.has(role)) return "read_only";
40
+ if (WRITE_ROLES.has(role)) return "workspace_write";
41
+ // FIND-12: default-deny for unknown/unrecognized roles.
42
+ return "read_only";
43
+ }
44
+
45
+ export function currentCrewRole(env: NodeJS.ProcessEnv = process.env): string | undefined {
46
+ return env.PI_CREW_ROLE?.trim() || env.PI_TEAMS_ROLE?.trim() || undefined;
47
+ }
48
+
49
+ export function checkSubagentSpawnPermission(role: string | undefined): PermissionCheckResult {
50
+ if (!role) return { allowed: true, mode: "workspace_write" };
51
+ const mode = permissionForRole(role);
52
+ if (mode === "read_only")
53
+ return {
54
+ allowed: false,
55
+ mode,
56
+ reason: `Role '${role}' is read-only and cannot spawn additional subagents.`,
57
+ };
58
+ return { allowed: true, mode };
59
+ }
@@ -0,0 +1,308 @@
1
+ import type { AgentConfig } from "../agents/agent-config.ts";
2
+ import { writeArtifact } from "../state/artifact-store.ts";
3
+ import { appendEventAsync } from "../state/event-log.ts";
4
+ import { saveRunTasksAsync, updateRunStatus } from "../state/state-store.ts";
5
+ import type { TeamRunManifest, TeamTaskState } from "../state/types.ts";
6
+ import type { WorkflowStep } from "../workflows/workflow-config.ts";
7
+ import { DEFAULT_RETRY_POLICY, executeWithRetry } from "./retry-executor.ts";
8
+ import { permissionForRole } from "./role-permission.ts";
9
+ import { runWorker } from "./run-worker.ts";
10
+ import type { CrewRuntimeMode } from "./runtime-resolver.ts";
11
+ import { sanitizeTaskText } from "./task-packet.ts";
12
+ import { splitCoalescedOutput } from "./task-runner/output-splitter.ts";
13
+ import { mergeArtifacts } from "./team-runner-artifacts.ts";
14
+ import { createWorkerHeartbeat, touchWorkerHeartbeat } from "./worker-heartbeat.ts";
15
+ import { buildWorkspaceTree } from "./workspace-tree.ts";
16
+
17
+ export interface CoalescedTaskGroupInput {
18
+ manifest: TeamRunManifest;
19
+ tasks: TeamTaskState[];
20
+ groupTasks: TeamTaskState[];
21
+ step: WorkflowStep;
22
+ agent: AgentConfig;
23
+ signal?: AbortSignal;
24
+ executeWorkers: boolean;
25
+ runtimeKind?: CrewRuntimeMode;
26
+ workspaceId: string;
27
+ onJsonEvent?: (taskId: string, runId: string, event: unknown) => void;
28
+ teamRole?: unknown;
29
+ perTaskRuntime?: CrewRuntimeMode;
30
+ }
31
+
32
+ export interface CoalescedTaskGroupResult {
33
+ manifest: TeamRunManifest;
34
+ tasks: TeamTaskState[];
35
+ taskIds: string[];
36
+ rawOutput: string;
37
+ success: boolean;
38
+ }
39
+
40
+ export async function runCoalescedTaskGroup(input: CoalescedTaskGroupInput): Promise<CoalescedTaskGroupResult> {
41
+ const { manifest, groupTasks, step, agent, signal, executeWorkers } = input;
42
+ const groupId = groupTasks.map((t) => t.id).join("+");
43
+ const firstTask = groupTasks[0]!;
44
+ const taskIds = groupTasks.map((t) => t.id);
45
+
46
+ // Set ALL N tasks to "running" before spawn so the dashboard reflects
47
+ // the in-flight state.
48
+ let updatedTasks: TeamTaskState[] = input.tasks.map((t) => {
49
+ if (taskIds.includes(t.id) && t.status !== "running") {
50
+ return { ...t, status: "running" as const, startedAt: new Date().toISOString() };
51
+ }
52
+ return t;
53
+ });
54
+ await saveRunTasksAsync(manifest, updatedTasks);
55
+ await appendEventAsync(manifest.eventsPath, {
56
+ type: "task.coalesced_dispatch_start",
57
+ runId: manifest.runId,
58
+ message: `Dispatching ${groupTasks.length} coalesced tasks in 1 worker (role=${firstTask.role}, cwd=${firstTask.cwd})`,
59
+ data: { groupId, role: firstTask.role, cwd: firstTask.cwd, taskIds },
60
+ });
61
+
62
+ const combinedPrompt = await buildCoalescedPrompt(manifest, step, groupTasks, agent);
63
+
64
+ // FIX (M6): write heartbeats for ALL N tasks in the coalesced group so the
65
+ // background watcher doesn't fire heartbeat_dead against the (single)
66
+ // child pi worker. Previously runCoalescedTaskGroup never wrote heartbeats,
67
+ // so the watcher saw `heartbeat.lastSeenAt: undefined` within 2 seconds of
68
+ // spawn and emitted heartbeat_dead even though the worker continued to
69
+ // completion (false-positive stuck-worker alarms). The singleton path
70
+ // (runTeamTask) writes heartbeats via persistHeartbeat — M6 needs the
71
+ // equivalent to avoid the false-positive.
72
+ updatedTasks = updatedTasks.map((t) => {
73
+ if (!taskIds.includes(t.id)) return t;
74
+ return {
75
+ ...t,
76
+ heartbeat: t.heartbeat ?? createWorkerHeartbeat(t.id),
77
+ };
78
+ });
79
+ await saveRunTasksAsync(manifest, updatedTasks);
80
+
81
+ let rawOutput = "";
82
+ let success = false;
83
+ // FIND-06: serialize heartbeat saves and retain the active save so terminal
84
+ // results can drain it before their final write.
85
+ let heartbeatTimer: ReturnType<typeof setInterval> | null = null;
86
+ let heartbeatInFlight = false;
87
+ let heartbeatPromise: Promise<void> | null = null;
88
+ // FIND-06 P1 fix (R1 review): a heartbeat save that exceeds the 5s drain
89
+ // timeout continues in the background and can rename its temp file AFTER
90
+ // the terminal write, clobbering terminal state with the pre-terminal
91
+ // snapshot. finalWriteStarted lets the IIFE repair that by re-writing the
92
+ // (now-terminal) updatedTasks AFTER its own save resolves, so the terminal
93
+ // state always lands last regardless of disk timing.
94
+ let finalWriteStarted = false;
95
+ if (!executeWorkers) {
96
+ rawOutput = buildScaffoldOutput(groupTasks);
97
+ success = true;
98
+ } else {
99
+ // Heartbeat refresher: touch every task's heartbeat every 15s while the
100
+ // worker is alive. Set `alive: true` explicitly so post-completion
101
+ // staleness checks immediately recognize liveness.
102
+ heartbeatTimer = setInterval(() => {
103
+ // FIND-06: setInterval does not await async callbacks. Do not start a
104
+ // second read/modify/write while the preceding heartbeat save is active.
105
+ if (heartbeatInFlight) return;
106
+ heartbeatInFlight = true;
107
+ heartbeatPromise = (async () => {
108
+ try {
109
+ updatedTasks = updatedTasks.map((t) => {
110
+ if (!taskIds.includes(t.id)) return t;
111
+ // FIND-06 belt-and-suspenders: never replace terminal state or its
112
+ // resultArtifact with a heartbeat-only snapshot.
113
+ if (t.status === "completed" || t.status === "failed") return t;
114
+ return {
115
+ ...t,
116
+ heartbeat: touchWorkerHeartbeat(t.heartbeat ?? createWorkerHeartbeat(t.id), { alive: true }),
117
+ };
118
+ });
119
+ await saveRunTasksAsync(manifest, updatedTasks);
120
+ } catch {
121
+ // Run may have been pruned mid-dispatch — best-effort only.
122
+ } finally {
123
+ heartbeatInFlight = false;
124
+ // FIND-06 P1 fix: if the terminal write started while our heartbeat
125
+ // save was in flight, re-write the (now-terminal) updatedTasks so
126
+ // our late snapshot cannot leave stale pre-terminal state on disk.
127
+ // Runs after our own save resolves, so it always lands last.
128
+ if (finalWriteStarted) {
129
+ try {
130
+ await saveRunTasksAsync(manifest, updatedTasks);
131
+ } catch {
132
+ // best-effort repair — same swallow policy as the heartbeat.
133
+ }
134
+ }
135
+ }
136
+ })();
137
+ }, 15_000);
138
+ try {
139
+ const result = await executeWithRetry(
140
+ async () => {
141
+ return await runWorker({
142
+ cwd: firstTask.cwd,
143
+ task: combinedPrompt,
144
+ agent,
145
+ signal,
146
+ excludeContextBash: true,
147
+ maxTurns: 5,
148
+ onJsonEvent: (e) => input.onJsonEvent?.(firstTask.id, manifest.runId, e),
149
+ });
150
+ },
151
+ DEFAULT_RETRY_POLICY,
152
+ { signal },
153
+ );
154
+ rawOutput = result.rawFinalText ?? result.stdout ?? "";
155
+ success = result.exitStatus?.exitCode === 0;
156
+ } catch (err) {
157
+ rawOutput = `Worker dispatch failed: ${err instanceof Error ? err.message : String(err)}`;
158
+ success = false;
159
+ } finally {
160
+ if (heartbeatTimer !== null) clearInterval(heartbeatTimer);
161
+ }
162
+ }
163
+
164
+ const split = splitCoalescedOutput(rawOutput, taskIds);
165
+
166
+ const finishedAt = new Date().toISOString();
167
+ const newArtifacts: TeamRunManifest["artifacts"] = [];
168
+ updatedTasks = updatedTasks.map((t) => {
169
+ if (!taskIds.includes(t.id)) return t;
170
+ const entry = split.find((s) => s.taskId === t.id);
171
+ const ok = success && Boolean(entry?.text);
172
+ const text = entry?.text ?? rawOutput;
173
+ // BUGFIX (M6 real dispatch): write to artifactsRoot via writeArtifact
174
+ // so task.resultArtifact is set and aggregateTaskOutputs can read
175
+ // the per-task text. Previously the coalesced path used a raw
176
+ // writeFile to stateRoot/results/<id>.txt which aggregated task
177
+ // outputs could not locate — they only consult task.resultArtifact.
178
+ // Result: tasks reported "EMPTY OUTPUT" in the batch summary even
179
+ // though on-disk results were correct.
180
+ const resultArtifact = writeArtifact(manifest.artifactsRoot, {
181
+ kind: "result",
182
+ relativePath: `results/${t.id}.txt`,
183
+ content: text,
184
+ producer: t.id,
185
+ });
186
+ newArtifacts.push(resultArtifact);
187
+ return {
188
+ ...t,
189
+ status: ok ? ("completed" as const) : ("failed" as const),
190
+ finishedAt,
191
+ result: {
192
+ text,
193
+ producer: groupId,
194
+ strategy: entry?.strategy ?? "broadcast",
195
+ },
196
+ resultArtifact,
197
+ };
198
+ });
199
+
200
+ // FIND-06: stop ticks and drain the snapshot captured by any active
201
+ // heartbeat before persisting terminal results. Bound the wait so a stuck
202
+ // filesystem operation cannot block dispatch completion indefinitely.
203
+ // P1 fix (R1 review): set finalWriteStarted BEFORE the drain so any pending
204
+ // heartbeat IIFE observes it and re-writes the terminal state after its
205
+ // own (possibly late) save resolves — preventing a late heartbeat snapshot
206
+ // from clobbering the terminal write.
207
+ finalWriteStarted = true;
208
+ if (heartbeatTimer !== null) clearInterval(heartbeatTimer);
209
+ const pendingHeartbeat = heartbeatPromise;
210
+ if (pendingHeartbeat) {
211
+ let drainTimeout: ReturnType<typeof setTimeout> | undefined;
212
+ try {
213
+ await Promise.race([
214
+ pendingHeartbeat,
215
+ new Promise<void>((resolve) => {
216
+ drainTimeout = setTimeout(resolve, 5_000);
217
+ }),
218
+ ]);
219
+ } finally {
220
+ if (drainTimeout !== undefined) clearTimeout(drainTimeout);
221
+ }
222
+ }
223
+ await saveRunTasksAsync(manifest, updatedTasks);
224
+ let updatedManifest: TeamRunManifest = {
225
+ ...manifest,
226
+ artifacts: mergeArtifacts([...manifest.artifacts, ...newArtifacts]),
227
+ };
228
+
229
+ if (success) {
230
+ updatedManifest = updateRunStatus(updatedManifest, "running");
231
+ }
232
+ await appendEventAsync(updatedManifest.eventsPath, {
233
+ type: "task.coalesced_dispatch_end",
234
+ runId: manifest.runId,
235
+ message: `Coalesced dispatch ${success ? "completed" : "failed"} (${taskIds.length} tasks, ${split[0]?.strategy ?? "broadcast"} split)`,
236
+ data: { groupId, taskIds, success, strategy: split[0]?.strategy },
237
+ });
238
+
239
+ return { manifest: updatedManifest, tasks: updatedTasks, taskIds, rawOutput, success };
240
+ }
241
+
242
+ async function buildCoalescedPrompt(
243
+ manifest: TeamRunManifest,
244
+ step: WorkflowStep,
245
+ groupTasks: TeamTaskState[],
246
+ agent: AgentConfig,
247
+ ): Promise<string> {
248
+ const tree = await buildWorkspaceTree(groupTasks[0]!.cwd);
249
+ const treeBlock = tree.rendered ? `# Workspace Structure\n${tree.rendered}` : "";
250
+ const roleInstructions =
251
+ permissionForRole(groupTasks[0]!.role) === "read_only"
252
+ ? `You are running in READ-ONLY mode. Do not create, modify, delete, or move files. Emit your findings as TEXT in your final output.`
253
+ : "";
254
+
255
+ const taskBlocks = groupTasks
256
+ .map((task, idx) => {
257
+ return [
258
+ `### Task ${idx + 1} of ${groupTasks.length} (id: ${task.id})`,
259
+ `Step: ${step.id}`,
260
+ `Role: ${step.role}`,
261
+ `Task: ${sanitizeTaskText(step.task.replaceAll("{goal}", manifest.goal))}`,
262
+ ].join("\n");
263
+ })
264
+ .join("\n\n---\n\n");
265
+
266
+ const outputInstructions = groupTasks.map((task) => `<<<TASK_RESULT:${task.id}>>>`).join(" ... ");
267
+
268
+ return [
269
+ "# pi-crew Coalesced Worker Prompt",
270
+ `Run ID: ${manifest.runId}`,
271
+ `Team: ${manifest.team}`,
272
+ `Workflow: ${manifest.workflow ?? "(none)"}`,
273
+ `Goal: ${manifest.goal}`,
274
+ `Tasks in this batch: ${groupTasks.length}`,
275
+ ``,
276
+ roleInstructions,
277
+ ``,
278
+ treeBlock,
279
+ ``,
280
+ `# Your Tasks`,
281
+ `Complete ALL ${groupTasks.length} tasks below. For each, structure your final output using the delimiters shown.`,
282
+ ``,
283
+ taskBlocks,
284
+ ``,
285
+ `# Output Format (CRITICAL)`,
286
+ `After completing all tasks, structure your final output using these delimiters:`,
287
+ ``,
288
+ outputInstructions,
289
+ ``,
290
+ `Wrap each task's result between the start and end delimiters:`,
291
+ `<<<TASK_RESULT:{taskId}>>>`,
292
+ `...your result for this task...`,
293
+ `<<<END_TASK_RESULT>>>`,
294
+ ``,
295
+ `If delimiters don't fit your workflow, use \`### Task N of M\` headings and we'll parse those instead.`,
296
+ ]
297
+ .filter(Boolean)
298
+ .join("\n");
299
+ }
300
+
301
+ function buildScaffoldOutput(groupTasks: TeamTaskState[]): string {
302
+ return groupTasks
303
+ .map(
304
+ (task, idx) =>
305
+ `<<<TASK_RESULT:${task.id}>>>\nScaffold result for task ${idx + 1} of ${groupTasks.length}: ${task.id}\n<<<END_TASK_RESULT>>>`,
306
+ )
307
+ .join("\n\n");
308
+ }