pi-crew 0.9.16 → 0.9.17

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 (432) hide show
  1. package/CHANGELOG.md +81 -0
  2. package/README.md +19 -0
  3. package/dist/build-meta.json +21790 -0
  4. package/dist/index.mjs +83070 -0
  5. package/dist/index.mjs.map +7 -0
  6. package/docs/A +358 -0
  7. package/docs/M-A +357 -0
  8. package/docs/REVIEW-FINDINGS-2026-06 +357 -0
  9. package/docs/Y +357 -0
  10. package/docs/a +357 -0
  11. package/docs/aA +358 -0
  12. package/docs/archive/README.md +91 -0
  13. package/docs/patterns/command-agent-skill.md +1 -1
  14. package/docs/skills/REFERENCE.md +0 -17
  15. package/docs//303/242mmaAAA/303/242 +357 -0
  16. package/index.bundle.ts +25 -0
  17. package/index.ts +95 -4
  18. package/package.json +15 -4
  19. package/skills/iterative-audit/SKILL.md +0 -1
  20. package/skills/widget-rendering/SKILL.md +17 -0
  21. package/src/adapters/claude-adapter.ts +1 -3
  22. package/src/adapters/export-util.ts +16 -16
  23. package/src/adapters/index.ts +5 -5
  24. package/src/agents/agent-config.ts +20 -14
  25. package/src/agents/agent-serializer.ts +1 -1
  26. package/src/agents/discover-agents.ts +83 -39
  27. package/src/benchmark/benchmark-runner.ts +51 -51
  28. package/src/benchmark/feedback-loop.ts +1 -1
  29. package/src/config/config.ts +166 -590
  30. package/src/config/defaults.ts +28 -2
  31. package/src/config/drift-detector.ts +11 -14
  32. package/src/config/markers.ts +201 -203
  33. package/src/config/resilient-parser.ts +14 -6
  34. package/src/config/role-tools.ts +3 -3
  35. package/src/config/suggestions.ts +5 -12
  36. package/src/config/types.ts +9 -25
  37. package/src/errors.ts +151 -157
  38. package/src/extension/action-suggestions.ts +54 -9
  39. package/src/extension/async-notifier.ts +43 -13
  40. package/src/extension/autonomous-policy.ts +77 -37
  41. package/src/extension/command-completions.ts +9 -8
  42. package/src/extension/context-status-injection.ts +14 -12
  43. package/src/extension/crew-autocomplete.ts +8 -16
  44. package/src/extension/crew-cleanup.ts +2 -1
  45. package/src/extension/crew-shortcuts.ts +9 -6
  46. package/src/extension/cross-extension-rpc.ts +136 -42
  47. package/src/extension/help.ts +1 -1
  48. package/src/extension/import-index.ts +10 -5
  49. package/src/extension/knowledge-injection.ts +88 -15
  50. package/src/extension/management.ts +89 -349
  51. package/src/extension/message-renderers.ts +3 -4
  52. package/src/extension/notification-router.ts +22 -5
  53. package/src/extension/notification-sink.ts +6 -3
  54. package/src/extension/pi-api.ts +17 -8
  55. package/src/extension/plan-orchestrate.ts +8 -27
  56. package/src/extension/project-init.ts +21 -6
  57. package/src/extension/register.ts +329 -975
  58. package/src/extension/registration/artifact-cleanup.ts +9 -4
  59. package/src/extension/registration/command-utils.ts +5 -1
  60. package/src/extension/registration/commands.ts +806 -314
  61. package/src/extension/registration/compaction-guard.ts +15 -15
  62. package/src/extension/registration/lifecycle.ts +259 -0
  63. package/src/extension/registration/observability.ts +293 -0
  64. package/src/extension/registration/subagent-helpers.ts +29 -7
  65. package/src/extension/registration/subagent-tools.ts +253 -44
  66. package/src/extension/registration/team-tool.ts +19 -84
  67. package/src/extension/registration/ui.ts +168 -0
  68. package/src/extension/registration/viewers.ts +64 -24
  69. package/src/extension/result-watcher.ts +18 -7
  70. package/src/extension/run-bundle-schema.ts +21 -6
  71. package/src/extension/run-export.ts +15 -6
  72. package/src/extension/run-import.ts +64 -29
  73. package/src/extension/run-index.ts +28 -9
  74. package/src/extension/run-maintenance.ts +32 -12
  75. package/src/extension/session-summary.ts +1 -1
  76. package/src/extension/team-manager-command.ts +64 -7
  77. package/src/extension/team-onboard.ts +151 -150
  78. package/src/extension/team-recommendation.ts +121 -21
  79. package/src/extension/team-tool/anchor.ts +31 -64
  80. package/src/extension/team-tool/api.ts +929 -141
  81. package/src/extension/team-tool/auto-summarize.ts +14 -26
  82. package/src/extension/team-tool/cache-control.ts +1 -5
  83. package/src/extension/team-tool/cancel.ts +138 -38
  84. package/src/extension/team-tool/chain-dispatch.ts +5 -13
  85. package/src/extension/team-tool/chain-executor.ts +29 -51
  86. package/src/extension/team-tool/config-patch.ts +1 -1
  87. package/src/extension/team-tool/context.ts +40 -20
  88. package/src/extension/team-tool/destructive-gate.ts +10 -5
  89. package/src/extension/team-tool/doctor.ts +170 -51
  90. package/src/extension/team-tool/explain.ts +228 -214
  91. package/src/extension/team-tool/failure-patterns.ts +3 -9
  92. package/src/extension/team-tool/goal-wrap.ts +71 -29
  93. package/src/extension/team-tool/goal.ts +185 -35
  94. package/src/extension/team-tool/handle-schedule.ts +34 -15
  95. package/src/extension/team-tool/handle-settings.ts +94 -56
  96. package/src/extension/team-tool/health-monitor.ts +27 -108
  97. package/src/extension/team-tool/inspect.ts +42 -9
  98. package/src/extension/team-tool/intent-policy.ts +4 -1
  99. package/src/extension/team-tool/lifecycle-actions.ts +244 -48
  100. package/src/extension/team-tool/orchestrate.ts +7 -18
  101. package/src/extension/team-tool/parallel-dispatch.ts +51 -29
  102. package/src/extension/team-tool/plan.ts +29 -5
  103. package/src/extension/team-tool/respond.ts +49 -16
  104. package/src/extension/team-tool/run-not-found.ts +3 -8
  105. package/src/extension/team-tool/run.ts +80 -317
  106. package/src/extension/team-tool/status.ts +135 -43
  107. package/src/extension/team-tool/workflow-manage.ts +92 -25
  108. package/src/extension/team-tool-types.ts +8 -1
  109. package/src/extension/team-tool.ts +142 -551
  110. package/src/extension/validate-resources.ts +39 -8
  111. package/src/hooks/registry.ts +66 -17
  112. package/src/hooks/types.ts +1 -1
  113. package/src/i18n.ts +25 -11
  114. package/src/observability/correlation.ts +17 -3
  115. package/src/observability/event-bus.ts +51 -56
  116. package/src/observability/event-to-metric.ts +117 -15
  117. package/src/observability/exporters/otlp-exporter.ts +56 -17
  118. package/src/observability/exporters/prometheus-exporter.ts +1 -1
  119. package/src/observability/metric-registry.ts +16 -4
  120. package/src/observability/metric-retention.ts +8 -2
  121. package/src/observability/metric-sink.ts +11 -3
  122. package/src/observability/metrics-primitives.ts +42 -9
  123. package/src/plugins/plugin-define.ts +2 -2
  124. package/src/plugins/plugin-registry.ts +25 -25
  125. package/src/plugins/plugins/index.ts +1 -1
  126. package/src/plugins/plugins/nextjs.ts +16 -16
  127. package/src/plugins/plugins/vite.ts +7 -11
  128. package/src/plugins/plugins/vitest.ts +6 -11
  129. package/src/runtime/adaptive-plan.ts +225 -40
  130. package/src/runtime/agent-control.ts +58 -15
  131. package/src/runtime/agent-memory.ts +15 -9
  132. package/src/runtime/agent-observability.ts +10 -2
  133. package/src/runtime/anchor-manager.ts +27 -25
  134. package/src/runtime/async-marker.ts +7 -1
  135. package/src/runtime/async-runner.ts +27 -23
  136. package/src/runtime/auto-summarize.ts +7 -13
  137. package/src/runtime/background-runner.ts +189 -251
  138. package/src/runtime/batch-barrier.ts +18 -16
  139. package/src/runtime/cancellation-token.ts +16 -6
  140. package/src/runtime/cancellation.ts +46 -8
  141. package/src/runtime/capability-inventory.ts +6 -4
  142. package/src/runtime/chain-parser.ts +44 -11
  143. package/src/runtime/chain-runner.ts +63 -69
  144. package/src/runtime/checkpoint.ts +12 -56
  145. package/src/runtime/child-pi.ts +343 -78
  146. package/src/runtime/coalesce-tasks.ts +268 -0
  147. package/src/runtime/code-summary.ts +71 -26
  148. package/src/runtime/compact-stages/index.ts +15 -5
  149. package/src/runtime/compact-stages/tail-capture-stage.ts +7 -2
  150. package/src/runtime/compact-stages/truncation-stage.ts +4 -1
  151. package/src/runtime/compaction-summary.ts +17 -10
  152. package/src/runtime/completion-guard.ts +33 -16
  153. package/src/runtime/crash-classification.ts +28 -7
  154. package/src/runtime/crash-recovery.ts +177 -37
  155. package/src/runtime/crew-agent-records.ts +145 -47
  156. package/src/runtime/crew-hooks.ts +9 -24
  157. package/src/runtime/cross-extension-rpc.ts +25 -23
  158. package/src/runtime/custom-tools/irc-tool.ts +43 -15
  159. package/src/runtime/custom-tools/submit-result-tool.ts +16 -5
  160. package/src/runtime/delivery-coordinator.ts +34 -7
  161. package/src/runtime/delta-conflict.ts +9 -21
  162. package/src/runtime/deterministic-ast.ts +1 -1
  163. package/src/runtime/diagnostic-export.ts +53 -20
  164. package/src/runtime/direct-run.ts +12 -2
  165. package/src/runtime/dwf-state-store.ts +1 -1
  166. package/src/runtime/dynamic-workflow-context.ts +186 -58
  167. package/src/runtime/dynamic-workflow-runner.ts +42 -20
  168. package/src/runtime/effectiveness.ts +16 -8
  169. package/src/runtime/errors/crew-errors.ts +7 -11
  170. package/src/runtime/event-stream-bridge.ts +9 -3
  171. package/src/runtime/foreground-control.ts +54 -14
  172. package/src/runtime/foreground-watchdog.ts +9 -6
  173. package/src/runtime/goal-achievement.ts +27 -10
  174. package/src/runtime/goal-evaluator.ts +54 -19
  175. package/src/runtime/goal-loop-runner.ts +280 -99
  176. package/src/runtime/goal-state-store.ts +27 -17
  177. package/src/runtime/green-contract.ts +11 -2
  178. package/src/runtime/group-join.ts +64 -31
  179. package/src/runtime/handoff-manager.ts +37 -42
  180. package/src/runtime/heartbeat-gradient.ts +10 -2
  181. package/src/runtime/heartbeat-watcher.ts +32 -6
  182. package/src/runtime/hidden-handoff.ts +12 -30
  183. package/src/runtime/important-line-classifier.ts +12 -2
  184. package/src/runtime/iteration-hooks.ts +25 -15
  185. package/src/runtime/live-agent-control.ts +55 -7
  186. package/src/runtime/live-agent-manager.ts +130 -27
  187. package/src/runtime/live-control-realtime.ts +23 -3
  188. package/src/runtime/live-irc.ts +4 -1
  189. package/src/runtime/live-session-health.ts +12 -4
  190. package/src/runtime/live-session-runtime.ts +379 -117
  191. package/src/runtime/manifest-cache.ts +28 -12
  192. package/src/runtime/mcp-proxy.ts +8 -16
  193. package/src/runtime/metric-parser.ts +1 -5
  194. package/src/runtime/model-fallback.ts +55 -18
  195. package/src/runtime/model-resolver.ts +2 -6
  196. package/src/runtime/model-scope.ts +19 -3
  197. package/src/runtime/notebook-helpers.ts +60 -62
  198. package/src/runtime/orphan-worker-registry.ts +37 -26
  199. package/src/runtime/output-validator.ts +18 -3
  200. package/src/runtime/overflow-recovery.ts +5 -4
  201. package/src/runtime/parallel-research.ts +29 -5
  202. package/src/runtime/parallel-utils.ts +9 -11
  203. package/src/runtime/path-overlap.ts +150 -0
  204. package/src/runtime/peer-dep.ts +12 -17
  205. package/src/runtime/per-write-validator.ts +1 -3
  206. package/src/runtime/phase-tracker.ts +342 -330
  207. package/src/runtime/pi-args.ts +26 -8
  208. package/src/runtime/pi-json-output.ts +1 -1
  209. package/src/runtime/pi-spawn.ts +30 -19
  210. package/src/runtime/pipeline-runner.ts +56 -55
  211. package/src/runtime/plan-templates.ts +5 -6
  212. package/src/runtime/policy-engine.ts +43 -7
  213. package/src/runtime/post-checks.ts +12 -4
  214. package/src/runtime/post-exit-stdio-guard.ts +2 -2
  215. package/src/runtime/process-lifecycle.ts +20 -10
  216. package/src/runtime/process-status.ts +16 -5
  217. package/src/runtime/progress-event-coalescer.ts +2 -1
  218. package/src/runtime/progress-tracker.ts +103 -103
  219. package/src/runtime/prose-compressor.ts +9 -11
  220. package/src/runtime/recovery-recipes.ts +118 -24
  221. package/src/runtime/replace.ts +25 -10
  222. package/src/runtime/resilient-edit.ts +10 -25
  223. package/src/runtime/result-extractor.ts +1 -2
  224. package/src/runtime/retry-executor.ts +20 -4
  225. package/src/runtime/retry-runner.ts +26 -50
  226. package/src/runtime/role-permission.ts +6 -1
  227. package/src/runtime/run-coalesced-task-group.ts +256 -0
  228. package/src/runtime/run-drift.ts +14 -15
  229. package/src/runtime/run-tracker.ts +6 -28
  230. package/src/runtime/runtime-policy.ts +1 -6
  231. package/src/runtime/runtime-resolver.ts +80 -15
  232. package/src/runtime/scheduler.ts +47 -18
  233. package/src/runtime/semaphore.ts +5 -7
  234. package/src/runtime/sensitive-paths.ts +2 -1
  235. package/src/runtime/session-usage.ts +1 -1
  236. package/src/runtime/settings-store.ts +16 -12
  237. package/src/runtime/sidechain-output.ts +6 -2
  238. package/src/runtime/single-agent-compose.ts +1 -4
  239. package/src/runtime/skill-effectiveness.ts +29 -97
  240. package/src/runtime/skill-instructions.ts +40 -83
  241. package/src/runtime/stale-reconciler.ts +73 -197
  242. package/src/runtime/stream-preview.ts +1 -1
  243. package/src/runtime/streaming-output.ts +1 -1
  244. package/src/runtime/subagent-manager.ts +44 -169
  245. package/src/runtime/subprocess-tool-registry.ts +4 -1
  246. package/src/runtime/supervisor-contact.ts +10 -5
  247. package/src/runtime/task-display.ts +19 -5
  248. package/src/runtime/task-graph-scheduler.ts +95 -21
  249. package/src/runtime/task-graph.ts +5 -11
  250. package/src/runtime/task-health.ts +54 -47
  251. package/src/runtime/task-id.ts +11 -18
  252. package/src/runtime/task-output-context.ts +265 -81
  253. package/src/runtime/task-packet.ts +12 -20
  254. package/src/runtime/task-quality.ts +6 -14
  255. package/src/runtime/task-runner/context-retrieval.ts +4 -13
  256. package/src/runtime/task-runner/live-executor.ts +114 -36
  257. package/src/runtime/task-runner/output-splitter.ts +152 -0
  258. package/src/runtime/task-runner/progress.ts +50 -12
  259. package/src/runtime/task-runner/prompt-builder.ts +31 -7
  260. package/src/runtime/task-runner/prompt-pipeline.ts +31 -7
  261. package/src/runtime/task-runner/result-utils.ts +3 -1
  262. package/src/runtime/task-runner/retrieval-orchestrator.ts +310 -0
  263. package/src/runtime/task-runner/run-projection.ts +27 -8
  264. package/src/runtime/task-runner/state-helpers.ts +29 -8
  265. package/src/runtime/task-runner/tail-read.ts +2 -6
  266. package/src/runtime/task-runner.ts +180 -326
  267. package/src/runtime/team-runner-artifacts.ts +13 -0
  268. package/src/runtime/team-runner.ts +456 -974
  269. package/src/runtime/tool-output-pruner.ts +6 -9
  270. package/src/runtime/tool-progress.ts +11 -21
  271. package/src/runtime/verification-gates.ts +33 -24
  272. package/src/runtime/verification-integrity.ts +1 -4
  273. package/src/runtime/verification-worktree.ts +62 -12
  274. package/src/runtime/worker-heartbeat.ts +5 -1
  275. package/src/runtime/worker-startup.ts +29 -5
  276. package/src/runtime/workflow-state.ts +17 -9
  277. package/src/runtime/workspace-lock.ts +30 -35
  278. package/src/runtime/workspace-tree.ts +20 -32
  279. package/src/runtime/yield-handler.ts +43 -9
  280. package/src/runtime/zombie-scanner.ts +5 -4
  281. package/src/schema/config-schema.ts +266 -172
  282. package/src/schema/team-tool-schema.ts +29 -74
  283. package/src/schema/validation-types.ts +25 -17
  284. package/src/skills/discover-skills.ts +35 -10
  285. package/src/skills/skill-templates.ts +109 -27
  286. package/src/skills/validate.ts +26 -8
  287. package/src/state/active-run-registry.ts +87 -28
  288. package/src/state/artifact-store.ts +9 -5
  289. package/src/state/atomic-write-v2.ts +85 -63
  290. package/src/state/atomic-write.ts +105 -26
  291. package/src/state/blob-store.ts +47 -20
  292. package/src/state/contracts.ts +20 -5
  293. package/src/state/crew-init.ts +5 -22
  294. package/src/state/decision-ledger.ts +19 -73
  295. package/src/state/event-log-rotation.ts +41 -15
  296. package/src/state/event-log.ts +168 -53
  297. package/src/state/event-reconstructor.ts +11 -1
  298. package/src/state/gitignore-manager.ts +2 -8
  299. package/src/state/health-store.ts +57 -57
  300. package/src/state/hook-instinct-bridge.ts +1 -1
  301. package/src/state/hook-integrations.ts +1 -1
  302. package/src/state/instinct-store.ts +17 -5
  303. package/src/state/jsonl-writer.ts +1 -1
  304. package/src/state/locks.ts +23 -9
  305. package/src/state/mailbox.ts +151 -28
  306. package/src/state/observation-store.ts +20 -14
  307. package/src/state/run-cache.ts +142 -135
  308. package/src/state/run-graph.ts +6 -15
  309. package/src/state/run-metrics.ts +5 -18
  310. package/src/state/schedule.ts +15 -9
  311. package/src/state/state-store.ts +129 -45
  312. package/src/state/task-claims.ts +12 -2
  313. package/src/state/tiered-eval.ts +52 -43
  314. package/src/state/types-eval.ts +2 -2
  315. package/src/state/types.ts +24 -20
  316. package/src/state/usage.ts +20 -4
  317. package/src/state/worker-atomic-writer.ts +6 -3
  318. package/src/subagents/index.ts +2 -2
  319. package/src/teams/discover-teams.ts +21 -6
  320. package/src/tools/safe-bash-extension.ts +5 -6
  321. package/src/tools/safe-bash.ts +10 -11
  322. package/src/types/new-api-types.ts +6 -10
  323. package/src/ui/agent-management-overlay.ts +52 -40
  324. package/src/ui/card-colors.ts +13 -4
  325. package/src/ui/crew-footer.ts +8 -7
  326. package/src/ui/crew-select-list.ts +1 -1
  327. package/src/ui/dashboard-panes/agents-pane.ts +41 -25
  328. package/src/ui/dashboard-panes/cancellation-pane.ts +1 -1
  329. package/src/ui/dashboard-panes/capability-pane.ts +32 -15
  330. package/src/ui/dashboard-panes/health-pane.ts +2 -1
  331. package/src/ui/dashboard-panes/metrics-pane.ts +4 -1
  332. package/src/ui/dashboard-panes/progress-pane.ts +12 -9
  333. package/src/ui/heartbeat-aggregator.ts +15 -4
  334. package/src/ui/keybinding-map.ts +40 -7
  335. package/src/ui/live-conversation-overlay.ts +36 -20
  336. package/src/ui/live-duration.ts +1 -4
  337. package/src/ui/live-run-sidebar.ts +88 -25
  338. package/src/ui/loaders.ts +2 -8
  339. package/src/ui/mascot.ts +20 -36
  340. package/src/ui/overlays/agent-picker-overlay.ts +11 -3
  341. package/src/ui/overlays/confirm-overlay.ts +4 -2
  342. package/src/ui/overlays/help-overlay.ts +25 -14
  343. package/src/ui/overlays/mailbox-compose-overlay.ts +48 -11
  344. package/src/ui/overlays/mailbox-compose-preview.ts +20 -5
  345. package/src/ui/overlays/mailbox-detail-overlay.ts +27 -6
  346. package/src/ui/pi-ui-compat.ts +7 -7
  347. package/src/ui/powerbar-publisher.ts +120 -42
  348. package/src/ui/render-diff.ts +10 -3
  349. package/src/ui/render-scheduler.ts +12 -4
  350. package/src/ui/run-action-dispatcher.ts +81 -18
  351. package/src/ui/run-dashboard.ts +172 -76
  352. package/src/ui/run-event-bus.ts +59 -37
  353. package/src/ui/run-snapshot-cache.ts +276 -86
  354. package/src/ui/settings-overlay.ts +361 -74
  355. package/src/ui/status-colors.ts +12 -1
  356. package/src/ui/syntax-highlight.ts +1 -1
  357. package/src/ui/theme-adapter.ts +16 -14
  358. package/src/ui/theme-discovery.ts +13 -2
  359. package/src/ui/tool-progress-formatter.ts +7 -7
  360. package/src/ui/tool-render.ts +128 -56
  361. package/src/ui/tool-renderers/brief-mode.ts +45 -27
  362. package/src/ui/tool-renderers/index.ts +109 -43
  363. package/src/ui/transcript-cache.ts +32 -6
  364. package/src/ui/transcript-entries.ts +25 -23
  365. package/src/ui/transcript-viewer.ts +78 -29
  366. package/src/ui/widget/index.ts +99 -40
  367. package/src/ui/widget/widget-formatters.ts +13 -6
  368. package/src/ui/widget/widget-model.ts +19 -8
  369. package/src/ui/widget/widget-renderer.ts +23 -13
  370. package/src/ui/widget/widget-types.ts +1 -1
  371. package/src/utils/bm25-search.ts +199 -199
  372. package/src/utils/conflict-detect.ts +22 -21
  373. package/src/utils/env-filter.ts +14 -7
  374. package/src/utils/file-coalescer.ts +5 -1
  375. package/src/utils/fingerprint.ts +3 -6
  376. package/src/utils/frontmatter.ts +3 -1
  377. package/src/utils/fs-watch.ts +2 -6
  378. package/src/utils/gh-protocol.ts +119 -44
  379. package/src/utils/git.ts +13 -15
  380. package/src/utils/guards.ts +2 -5
  381. package/src/utils/ids.ts +9 -2
  382. package/src/utils/incremental-reader.ts +14 -3
  383. package/src/utils/internal-error.ts +2 -1
  384. package/src/utils/names.ts +12 -3
  385. package/src/utils/paths.ts +49 -5
  386. package/src/utils/project-detector.ts +2 -2
  387. package/src/utils/redaction.ts +29 -19
  388. package/src/utils/resolve-shell.ts +9 -7
  389. package/src/utils/run-watcher-registry.ts +19 -31
  390. package/src/utils/safe-paths.ts +45 -24
  391. package/src/utils/scan-cache.ts +9 -2
  392. package/src/utils/session-utils.ts +2 -4
  393. package/src/utils/sleep.ts +12 -5
  394. package/src/utils/sse-parser.ts +5 -17
  395. package/src/utils/visual.ts +52 -32
  396. package/src/workflows/discover-workflows.ts +41 -109
  397. package/src/workflows/intermediate-store.ts +5 -21
  398. package/src/workflows/preflight-validator.ts +6 -33
  399. package/src/workflows/topology-analyzer.ts +5 -21
  400. package/src/workflows/workflow-config.ts +7 -6
  401. package/src/worktree/branch-freshness.ts +66 -8
  402. package/src/worktree/cleanup.ts +130 -20
  403. package/src/worktree/worktree-manager.ts +212 -69
  404. package/skills/artifact-analysis-loop/SKILL.md +0 -303
  405. package/skills/detection-pipeline-design/SKILL.md +0 -286
  406. package/skills/hunting-investigation-loop/SKILL.md +0 -402
  407. package/skills/incident-playbook-construction/SKILL.md +0 -384
  408. package/skills/security-review/SKILL.md +0 -561
  409. package/skills/threat-hypothesis-framework/SKILL.md +0 -176
  410. package/skills/ui-render-performance/SKILL.md +0 -58
  411. /package/docs/{followup-review-round3-2026-05-12.md → archive/followup-review-round3-2026-05-12.md} +0 -0
  412. /package/docs/{followup-review-round4-2026-05-13.md → archive/followup-review-round4-2026-05-13.md} +0 -0
  413. /package/docs/{pi-crew-bugs.md → archive/pi-crew-bugs.md} +0 -0
  414. /package/docs/{pi-crew-test-final.md → archive/pi-crew-test-final.md} +0 -0
  415. /package/docs/{pi-crew-test-results.md → archive/pi-crew-test-results.md} +0 -0
  416. /package/docs/{pi-crew-test-round2.md → archive/pi-crew-test-round2.md} +0 -0
  417. /package/docs/{pi-crew-test-round4.md → archive/pi-crew-test-round4.md} +0 -0
  418. /package/docs/{pi-crew-test-round5.md → archive/pi-crew-test-round5.md} +0 -0
  419. /package/docs/{pi-crew-test-round6.md → archive/pi-crew-test-round6.md} +0 -0
  420. /package/docs/{pi-crew-v0.5.10-audit-fix-plan.md → archive/pi-crew-v0.5.10-audit-fix-plan.md} +0 -0
  421. /package/docs/{pi-crew-v0.5.11-audit-fix-plan.md → archive/pi-crew-v0.5.11-audit-fix-plan.md} +0 -0
  422. /package/docs/{pi-crew-v0.5.12-audit-fix-plan.md → archive/pi-crew-v0.5.12-audit-fix-plan.md} +0 -0
  423. /package/docs/{pi-crew-v0.5.13-audit-fix-plan.md → archive/pi-crew-v0.5.13-audit-fix-plan.md} +0 -0
  424. /package/docs/{pi-crew-v0.5.14-audit-fix-plan.md → archive/pi-crew-v0.5.14-audit-fix-plan.md} +0 -0
  425. /package/docs/{pi-crew-v0.5.16-audit-fix-plan.md → archive/pi-crew-v0.5.16-audit-fix-plan.md} +0 -0
  426. /package/docs/{pi-crew-v0.5.17-audit-fix-plan.md → archive/pi-crew-v0.5.17-audit-fix-plan.md} +0 -0
  427. /package/docs/{pi-crew-v0.5.5-audit-fix-plan.md → archive/pi-crew-v0.5.5-audit-fix-plan.md} +0 -0
  428. /package/docs/{pi-crew-v0.5.9-audit-fix-plan.md → archive/pi-crew-v0.5.9-audit-fix-plan.md} +0 -0
  429. /package/docs/{pi-mono-opportunities.md → archive/pi-mono-opportunities.md} +0 -0
  430. /package/docs/{pi-mono-review.md → archive/pi-mono-review.md} +0 -0
  431. /package/docs/{pi-subagent4-comparison.md → archive/pi-subagent4-comparison.md} +0 -0
  432. /package/docs/{pi-subagents3-deep-analysis.md → archive/pi-subagents3-deep-analysis.md} +0 -0
@@ -143,12 +143,18 @@ export function classifyProcessCrash(input: CrashClassificationInput): CrashClas
143
143
  // 1. Timeout takes precedence: the response-timeout guard is the proximate
144
144
  // cause of death even if cancellation was also requested.
145
145
  if (input.timedOut) {
146
- return { crashClass: "timeout", reason: "process timed out (response timeout guard fired)" };
146
+ return {
147
+ crashClass: "timeout",
148
+ reason: "process timed out (response timeout guard fired)",
149
+ };
147
150
  }
148
151
 
149
152
  // 2. Cooperative cancellation.
150
153
  if (input.cancelled) {
151
- return { crashClass: "cancelled", reason: "process was cancelled (abort requested)" };
154
+ return {
155
+ crashClass: "cancelled",
156
+ reason: "process was cancelled (abort requested)",
157
+ };
152
158
  }
153
159
 
154
160
  // 3. Spawn error: the child never started or emitted a process error.
@@ -165,13 +171,19 @@ export function classifyProcessCrash(input: CrashClassificationInput): CrashClas
165
171
  if (abnormalExit) {
166
172
  const panic = detectNativePanic(input.stderrSnippet);
167
173
  if (panic !== null) {
168
- return { crashClass: "native_panic", reason: `native panic detected: ${panic}` };
174
+ return {
175
+ crashClass: "native_panic",
176
+ reason: `native panic detected: ${panic}`,
177
+ };
169
178
  }
170
179
  }
171
180
 
172
181
  // 5. Signal exit.
173
182
  if (signal !== null) {
174
- return { crashClass: "signal_exit", reason: `process exited after signal ${signal}` };
183
+ return {
184
+ crashClass: "signal_exit",
185
+ reason: `process exited after signal ${signal}`,
186
+ };
175
187
  }
176
188
 
177
189
  // 6. Clean exit.
@@ -181,7 +193,10 @@ export function classifyProcessCrash(input: CrashClassificationInput): CrashClas
181
193
 
182
194
  // 7. Non-zero exit.
183
195
  if (exitCode !== null) {
184
- return { crashClass: "non_zero_exit", reason: `process exited with code ${exitCode}` };
196
+ return {
197
+ crashClass: "non_zero_exit",
198
+ reason: `process exited with code ${exitCode}`,
199
+ };
185
200
  }
186
201
 
187
202
  // 8. Protocol exit: exitCode is null with no signal — the process stream
@@ -189,11 +204,17 @@ export function classifyProcessCrash(input: CrashClassificationInput): CrashClas
189
204
  // If `killed` is true but no signal was recorded, treat as protocol_exit
190
205
  // (the kill may not have delivered a signal we could capture).
191
206
  if (input.killed) {
192
- return { crashClass: "protocol_exit", reason: "process was killed but no signal/exit code was captured" };
207
+ return {
208
+ crashClass: "protocol_exit",
209
+ reason: "process was killed but no signal/exit code was captured",
210
+ };
193
211
  }
194
212
 
195
213
  // 8b. Truly null exitCode with no other context — protocol/stream ended early.
196
- return { crashClass: "protocol_exit", reason: "process exited before protocol completion (exit code unknown)" };
214
+ return {
215
+ crashClass: "protocol_exit",
216
+ reason: "process exited before protocol completion (exit code unknown)",
217
+ };
197
218
  }
198
219
 
199
220
  /** Render an unknown error value to a short message string. */
@@ -1,22 +1,22 @@
1
- import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
2
1
  import * as fs from "node:fs";
3
2
  import * as path from "node:path";
3
+ import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
4
+ import { appendHookEvent, executeHook } from "../hooks/registry.ts";
4
5
  import type { MetricRegistry } from "../observability/metric-registry.ts";
6
+ import { readActiveRunRegistry, unregisterActiveRun } from "../state/active-run-registry.ts";
5
7
  import { appendEvent, scanSequence } from "../state/event-log.ts";
6
- import { recordFromTask, upsertCrewAgent } from "./crew-agent-records.ts";
7
8
  import { withRunLockSync } from "../state/locks.ts";
8
9
  import { loadRunManifestById, saveRunTasks, updateRunStatus } from "../state/state-store.ts";
9
10
  import type { TeamTaskState } from "../state/types.ts";
10
- import { isWorkerHeartbeatStale } from "./worker-heartbeat.ts";
11
- import type { ManifestCache } from "./manifest-cache.ts";
12
- import { checkProcessLiveness } from "./process-status.ts";
13
- import { isPlanApprovalPending, reconcileStaleRun, type ReconcileResult } from "./stale-reconciler.ts";
14
- import { executeHook, appendHookEvent } from "../hooks/registry.ts";
15
- import { unregisterActiveRun, readActiveRunRegistry } from "../state/active-run-registry.ts";
16
- import { resolveRealContainedPath } from "../utils/safe-paths.ts";
11
+ import { logInternalError } from "../utils/internal-error.ts";
17
12
  import { projectCrewRoot, userCrewRoot } from "../utils/paths.ts";
13
+ import { resolveRealContainedPath } from "../utils/safe-paths.ts";
14
+ import { recordFromTask, upsertCrewAgent } from "./crew-agent-records.ts";
18
15
  import { terminateLiveAgentsForRun } from "./live-agent-manager.ts";
19
- import { logInternalError } from "../utils/internal-error.ts";
16
+ import type { ManifestCache } from "./manifest-cache.ts";
17
+ import { checkProcessLiveness } from "./process-status.ts";
18
+ import { isPlanApprovalPending, type ReconcileResult, reconcileStaleRun } from "./stale-reconciler.ts";
19
+ import { isWorkerHeartbeatStale } from "./worker-heartbeat.ts";
20
20
 
21
21
  export interface RecoveryPlan {
22
22
  runId: string;
@@ -26,7 +26,13 @@ export interface RecoveryPlan {
26
26
  }
27
27
 
28
28
  function isTerminalTask(task: TeamTaskState): boolean {
29
- return task.status === "completed" || task.status === "failed" || task.status === "cancelled" || task.status === "skipped" || task.status === "needs_attention";
29
+ return (
30
+ task.status === "completed" ||
31
+ task.status === "failed" ||
32
+ task.status === "cancelled" ||
33
+ task.status === "skipped" ||
34
+ task.status === "needs_attention"
35
+ );
30
36
  }
31
37
 
32
38
  function shouldRecoverTask(task: TeamTaskState, deadMs: number): boolean {
@@ -47,7 +53,12 @@ export function detectInterruptedRuns(cwd: string, manifestCache: ManifestCache,
47
53
  if (!loaded) continue;
48
54
  const resumableTasks = loaded.tasks.filter((task) => shouldRecoverTask(task, deadMs)).map((task) => task.id);
49
55
  if (!resumableTasks.length) continue;
50
- plans.push({ runId: manifest.runId, resumableTasks, preservedTasks: loaded.tasks.filter(isTerminalTask).map((task) => task.id), lastEventSeq: scanSequence(loaded.manifest.eventsPath) });
56
+ plans.push({
57
+ runId: manifest.runId,
58
+ resumableTasks,
59
+ preservedTasks: loaded.tasks.filter(isTerminalTask).map((task) => task.id),
60
+ lastEventSeq: scanSequence(loaded.manifest.eventsPath),
61
+ });
51
62
  }
52
63
  return plans;
53
64
  }
@@ -56,17 +67,44 @@ export async function applyRecoveryPlan(plan: RecoveryPlan, ctx: Pick<ExtensionC
56
67
  const loaded = loadRunManifestById(ctx.cwd, plan.runId); // NOTE: no withRunLock - best-effort only; concurrent writes may cause inconsistency
57
68
  if (!loaded) throw new Error(`Run '${plan.runId}' not found.`);
58
69
 
59
- const hookReport = await executeHook("run_recovery", { runId: plan.runId, cwd: ctx.cwd });
70
+ const hookReport = await executeHook("run_recovery", {
71
+ runId: plan.runId,
72
+ cwd: ctx.cwd,
73
+ });
60
74
  appendHookEvent(loaded.manifest, hookReport);
61
75
  if (hookReport.outcome === "block") {
62
- appendEvent(loaded.manifest.eventsPath, { type: "crew.run.recovery_blocked", runId: plan.runId, message: `Recovery blocked by hook: ${hookReport.reason ?? "run_recovery hook blocked the operation."}`, data: { hookOutcome: "block", reason: hookReport.reason } });
76
+ appendEvent(loaded.manifest.eventsPath, {
77
+ type: "crew.run.recovery_blocked",
78
+ runId: plan.runId,
79
+ message: `Recovery blocked by hook: ${hookReport.reason ?? "run_recovery hook blocked the operation."}`,
80
+ data: { hookOutcome: "block", reason: hookReport.reason },
81
+ });
63
82
  return;
64
83
  }
65
84
 
66
85
  const reset = new Set(plan.resumableTasks);
67
- const tasks = loaded.tasks.map((task) => reset.has(task.id) ? { ...task, status: "queued" as const, startedAt: undefined, finishedAt: undefined, error: undefined, heartbeat: undefined } : task);
86
+ const tasks = loaded.tasks.map((task) =>
87
+ reset.has(task.id)
88
+ ? {
89
+ ...task,
90
+ status: "queued" as const,
91
+ startedAt: undefined,
92
+ finishedAt: undefined,
93
+ error: undefined,
94
+ heartbeat: undefined,
95
+ }
96
+ : task,
97
+ );
68
98
  saveRunTasks(loaded.manifest, tasks);
69
- appendEvent(loaded.manifest.eventsPath, { type: "crew.run.resumed", runId: plan.runId, message: `Recovered ${plan.resumableTasks.length} interrupted task(s).`, data: { recoveredFromSeq: plan.lastEventSeq, resumableTasks: plan.resumableTasks } });
99
+ appendEvent(loaded.manifest.eventsPath, {
100
+ type: "crew.run.resumed",
101
+ runId: plan.runId,
102
+ message: `Recovered ${plan.resumableTasks.length} interrupted task(s).`,
103
+ data: {
104
+ recoveredFromSeq: plan.lastEventSeq,
105
+ resumableTasks: plan.resumableTasks,
106
+ },
107
+ });
70
108
  registry?.counter("crew.run.count", "Total runs by status").inc({ status: "resumed" });
71
109
  }
72
110
 
@@ -74,7 +112,12 @@ export function declineRecoveryPlan(plan: RecoveryPlan, ctx: Pick<ExtensionConte
74
112
  const loaded = loadRunManifestById(ctx.cwd, plan.runId); // NOTE: no withRunLock - best-effort only; concurrent writes may cause inconsistency
75
113
  if (!loaded) throw new Error(`Run '${plan.runId}' not found.`);
76
114
  // Log the event first — if appendEvent fails, state remains consistent.
77
- appendEvent(loaded.manifest.eventsPath, { type: "crew.run.recovery_declined", runId: plan.runId, message: "Interrupted run was not resumed.", data: { recoveredFromSeq: plan.lastEventSeq } });
115
+ appendEvent(loaded.manifest.eventsPath, {
116
+ type: "crew.run.recovery_declined",
117
+ runId: plan.runId,
118
+ message: "Interrupted run was not resumed.",
119
+ data: { recoveredFromSeq: plan.lastEventSeq },
120
+ });
78
121
  updateRunStatus(loaded.manifest, "cancelled", "interrupted-not-resumed");
79
122
  }
80
123
 
@@ -156,26 +199,53 @@ export function cancelOrphanedRuns(
156
199
  if (!fresh) return;
157
200
  if (fresh.manifest.status !== "running" && fresh.manifest.status !== "blocked") {
158
201
  // Status changed between initial check (line 109) and acquiring the lock — normal concurrent update, not an orphan
159
- appendEvent(loaded.manifest.eventsPath, { type: "crew.run.orphan_skip", runId: manifest.runId, message: `Skipped orphan cancellation: status is '${fresh.manifest.status}' (was 'running'/'blocked' at initial scan)`, data: { currentStatus: fresh.manifest.status } });
202
+ appendEvent(loaded.manifest.eventsPath, {
203
+ type: "crew.run.orphan_skip",
204
+ runId: manifest.runId,
205
+ message: `Skipped orphan cancellation: status is '${fresh.manifest.status}' (was 'running'/'blocked' at initial scan)`,
206
+ data: { currentStatus: fresh.manifest.status },
207
+ });
160
208
  return;
161
209
  }
162
210
 
163
211
  const now_iso = new Date(now).toISOString();
164
212
  const repairedTasks = fresh.tasks.map((task) => {
165
213
  if (task.status === "running" || task.status === "queued" || task.status === "waiting") {
166
- return { ...task, status: "cancelled" as const, finishedAt: now_iso, error: `Orphaned run: owner session ${ownerId} no longer exists` };
214
+ return {
215
+ ...task,
216
+ status: "cancelled" as const,
217
+ finishedAt: now_iso,
218
+ error: `Orphaned run: owner session ${ownerId} no longer exists`,
219
+ };
167
220
  }
168
221
  return task;
169
222
  });
170
223
 
171
224
  saveRunTasks(fresh.manifest, repairedTasks);
172
- for (const task of repairedTasks) { try { upsertCrewAgent(fresh.manifest, recordFromTask(fresh.manifest, task, "scaffold")); } catch { /* non-critical */ } }
225
+ for (const task of repairedTasks) {
226
+ try {
227
+ upsertCrewAgent(fresh.manifest, recordFromTask(fresh.manifest, task, "scaffold"));
228
+ } catch {
229
+ /* non-critical */
230
+ }
231
+ }
173
232
  updateRunStatus(fresh.manifest, "cancelled", `Orphaned run: owner session ${ownerId} no longer exists`);
174
- appendEvent(fresh.manifest.eventsPath, { type: "crew.run.orphan_cancelled", runId: manifest.runId, message: `Auto-cancelled orphaned run (owner: ${ownerId})`, data: { ownerSessionId: ownerId, cancelledTasks: repairedTasks.filter((t) => t.status === "cancelled").length } });
233
+ appendEvent(fresh.manifest.eventsPath, {
234
+ type: "crew.run.orphan_cancelled",
235
+ runId: manifest.runId,
236
+ message: `Auto-cancelled orphaned run (owner: ${ownerId})`,
237
+ data: {
238
+ ownerSessionId: ownerId,
239
+ cancelledTasks: repairedTasks.filter((t) => t.status === "cancelled").length,
240
+ },
241
+ });
175
242
  cancelled.push(manifest.runId);
176
243
  cancelledRun = true;
177
244
  });
178
- if (cancelledRun) void terminateLiveAgentsForRun(manifest.runId, "cancelled", appendEvent, loaded.manifest.eventsPath).catch((error) => logInternalError("crash-recovery.orphan.terminate", error, `runId=${manifest.runId}`));
245
+ if (cancelledRun)
246
+ void terminateLiveAgentsForRun(manifest.runId, "cancelled", appendEvent, loaded.manifest.eventsPath).catch((error) =>
247
+ logInternalError("crash-recovery.orphan.terminate", error, `runId=${manifest.runId}`),
248
+ );
179
249
  }
180
250
 
181
251
  return { cancelled, skipped };
@@ -245,7 +315,12 @@ function heartbeatAgeMs(entry: { stateRoot: string }, now: number): number {
245
315
  * gone, hanging the workflow permanently at the current task with no
246
316
  * recoverable state ("Run not found").
247
317
  */
248
- function hasRecentLifeEvidence(entry: { stateRoot: string }, manifestUpdatedAt: string | undefined, now: number, staleThresholdMs: number): boolean {
318
+ function hasRecentLifeEvidence(
319
+ entry: { stateRoot: string },
320
+ manifestUpdatedAt: string | undefined,
321
+ now: number,
322
+ staleThresholdMs: number,
323
+ ): boolean {
249
324
  const manifestMs = manifestUpdatedAt ? new Date(manifestUpdatedAt).getTime() : NaN;
250
325
  if (Number.isFinite(manifestMs) && now - manifestMs <= staleThresholdMs) return true;
251
326
  const hbAge = heartbeatAgeMs(entry, now);
@@ -282,7 +357,14 @@ export function purgeStaleActiveRunIndex(staleThresholdMs = 300_000, now = Date.
282
357
  }
283
358
 
284
359
  // 3. Read manifest status
285
- let manifest: { status?: string; updatedAt?: string; async?: { pid?: number }; ownerSessionId?: string } | undefined;
360
+ let manifest:
361
+ | {
362
+ status?: string;
363
+ updatedAt?: string;
364
+ async?: { pid?: number };
365
+ ownerSessionId?: string;
366
+ }
367
+ | undefined;
286
368
  try {
287
369
  manifest = JSON.parse(fs.readFileSync(entry.manifestPath, "utf-8"));
288
370
  } catch {
@@ -322,14 +404,32 @@ export function purgeStaleActiveRunIndex(staleThresholdMs = 300_000, now = Date.
322
404
  const now_iso = new Date(now).toISOString();
323
405
  const repairedTasks = fullLoaded.tasks.map((task) => {
324
406
  if (task.status === "running" || task.status === "queued" || task.status === "waiting") {
325
- return { ...task, status: "cancelled" as const, finishedAt: now_iso, error: "Orphaned run: worker process dead and no recent activity" };
407
+ return {
408
+ ...task,
409
+ status: "cancelled" as const,
410
+ finishedAt: now_iso,
411
+ error: "Orphaned run: worker process dead and no recent activity",
412
+ };
326
413
  }
327
414
  return task;
328
415
  });
329
416
  saveRunTasks(fullLoaded.manifest, repairedTasks);
330
- for (const task of repairedTasks) { try { upsertCrewAgent(fullLoaded.manifest, recordFromTask(fullLoaded.manifest, task, "scaffold")); } catch { /* non-critical */ } }
417
+ for (const task of repairedTasks) {
418
+ try {
419
+ upsertCrewAgent(fullLoaded.manifest, recordFromTask(fullLoaded.manifest, task, "scaffold"));
420
+ } catch {
421
+ /* non-critical */
422
+ }
423
+ }
331
424
  updateRunStatus(fullLoaded.manifest, "cancelled", "Orphaned run: worker process dead and no recent activity");
332
- void terminateLiveAgentsForRun(fullLoaded.manifest.runId, "cancelled", appendEvent, fullLoaded.manifest.eventsPath).catch((error) => logInternalError("crash-recovery.pid-dead.terminate", error, `runId=${fullLoaded.manifest.runId}`));
425
+ void terminateLiveAgentsForRun(
426
+ fullLoaded.manifest.runId,
427
+ "cancelled",
428
+ appendEvent,
429
+ fullLoaded.manifest.eventsPath,
430
+ ).catch((error) =>
431
+ logInternalError("crash-recovery.pid-dead.terminate", error, `runId=${fullLoaded.manifest.runId}`),
432
+ );
333
433
  }
334
434
  } catch {
335
435
  // Best-effort manifest cleanup
@@ -352,14 +452,38 @@ export function purgeStaleActiveRunIndex(staleThresholdMs = 300_000, now = Date.
352
452
  const now_iso = new Date(now).toISOString();
353
453
  const repairedTasks = fullLoaded.tasks.map((task) => {
354
454
  if (task.status === "running" || task.status === "queued" || task.status === "waiting") {
355
- return { ...task, status: "cancelled" as const, finishedAt: now_iso, error: "Orphaned run: workflow completed but manifest never updated to terminal status" };
455
+ return {
456
+ ...task,
457
+ status: "cancelled" as const,
458
+ finishedAt: now_iso,
459
+ error: "Orphaned run: workflow completed but manifest never updated to terminal status",
460
+ };
356
461
  }
357
462
  return task;
358
463
  });
359
464
  saveRunTasks(fullLoaded.manifest, repairedTasks);
360
- for (const task of repairedTasks) { try { upsertCrewAgent(fullLoaded.manifest, recordFromTask(fullLoaded.manifest, task, "scaffold")); } catch { /* non-critical */ } }
361
- updateRunStatus(fullLoaded.manifest, "cancelled", "Orphaned run: no async worker and no manifest update in over " + Math.round(staleThresholdMs / 60000) + " minutes");
362
- void terminateLiveAgentsForRun(fullLoaded.manifest.runId, "cancelled", appendEvent, fullLoaded.manifest.eventsPath).catch((error) => logInternalError("crash-recovery.pid-dead.terminate", error, `runId=${fullLoaded.manifest.runId}`));
465
+ for (const task of repairedTasks) {
466
+ try {
467
+ upsertCrewAgent(fullLoaded.manifest, recordFromTask(fullLoaded.manifest, task, "scaffold"));
468
+ } catch {
469
+ /* non-critical */
470
+ }
471
+ }
472
+ updateRunStatus(
473
+ fullLoaded.manifest,
474
+ "cancelled",
475
+ "Orphaned run: no async worker and no manifest update in over " +
476
+ Math.round(staleThresholdMs / 60000) +
477
+ " minutes",
478
+ );
479
+ void terminateLiveAgentsForRun(
480
+ fullLoaded.manifest.runId,
481
+ "cancelled",
482
+ appendEvent,
483
+ fullLoaded.manifest.eventsPath,
484
+ ).catch((error) =>
485
+ logInternalError("crash-recovery.pid-dead.terminate", error, `runId=${fullLoaded.manifest.runId}`),
486
+ );
363
487
  }
364
488
  } catch {
365
489
  // Best-effort
@@ -379,7 +503,10 @@ export function purgeStaleActiveRunIndex(staleThresholdMs = 300_000, now = Date.
379
503
  export function reconcileAllStaleRuns(cwd: string, manifestCache: ManifestCache, now = Date.now()): ReconcileResult[] {
380
504
  const results: ReconcileResult[] = [];
381
505
  // Capture runIds to reconcile BEFORE acquiring locks — avoids TOCTOU between cache iteration and lock acquisition.
382
- const runIds = manifestCache.list(50).filter((m) => m.status === "running" || m.status === "blocked").map((m) => m.runId);
506
+ const runIds = manifestCache
507
+ .list(50)
508
+ .filter((m) => m.status === "running" || m.status === "blocked")
509
+ .map((m) => m.runId);
383
510
  for (const runId of runIds) {
384
511
  const cached = manifestCache.get(runId);
385
512
  if (!cached) continue;
@@ -405,12 +532,25 @@ export function reconcileAllStaleRuns(cwd: string, manifestCache: ManifestCache,
405
532
  const result = reconcileStaleRun(fresh.manifest, fresh.tasks, now);
406
533
  if (result.repaired || result.verdict === "result_exists") {
407
534
  if (result.repairedTasks) {
408
- saveRunTasks(fresh.manifest, result.repairedTasks);
409
- for (const task of result.repairedTasks) { try { upsertCrewAgent(fresh.manifest, recordFromTask(fresh.manifest, task, "scaffold")); } catch { /* non-critical */ } }
410
- }
535
+ saveRunTasks(fresh.manifest, result.repairedTasks);
536
+ for (const task of result.repairedTasks) {
537
+ try {
538
+ upsertCrewAgent(fresh.manifest, recordFromTask(fresh.manifest, task, "scaffold"));
539
+ } catch {
540
+ /* non-critical */
541
+ }
542
+ }
543
+ }
411
544
  updateRunStatus(fresh.manifest, "failed", `Stale run reconciled: ${result.detail}`);
412
- void terminateLiveAgentsForRun(fresh.manifest.runId, "failed", appendEvent, fresh.manifest.eventsPath).catch((error) => logInternalError("crash-recovery.reconcile.terminate", error, `runId=${fresh.manifest.runId}`));
413
- appendEvent(fresh.manifest.eventsPath, { type: "crew.run.reconciled_stale", runId, message: result.detail, data: { verdict: result.verdict } });
545
+ void terminateLiveAgentsForRun(fresh.manifest.runId, "failed", appendEvent, fresh.manifest.eventsPath).catch((error) =>
546
+ logInternalError("crash-recovery.reconcile.terminate", error, `runId=${fresh.manifest.runId}`),
547
+ );
548
+ appendEvent(fresh.manifest.eventsPath, {
549
+ type: "crew.run.reconciled_stale",
550
+ runId,
551
+ message: result.detail,
552
+ data: { verdict: result.verdict },
553
+ });
414
554
  }
415
555
  if (result.verdict !== "healthy") {
416
556
  results.push(result);