pi-crew 0.9.15 → 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 +145 -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 +193 -40
  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 +86 -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
@@ -1,23 +1,23 @@
1
- import { spawn, type ChildProcess, type SpawnOptions } from "node:child_process";
1
+ import { type ChildProcess, type SpawnOptions, spawn } from "node:child_process";
2
2
  import * as fs from "node:fs";
3
3
  import * as os from "node:os";
4
4
  import * as path from "node:path";
5
- import { WINDOWS_ESSENTIAL_ENV_VARS } from "../utils/env-allowlist.ts";
6
5
  import type { AgentConfig } from "../agents/agent-config.ts";
7
- import type { WorkerExitStatus } from "../state/types.ts";
8
- import { buildPiWorkerArgs, checkCrewDepth, cleanupTempDir } from "./pi-args.ts";
9
- import { getPiSpawnCommand } from "./pi-spawn.ts";
10
6
  import { DEFAULT_CHILD_PI } from "../config/defaults.ts";
7
+ import { registerChildProcess, unregisterChildProcess } from "../extension/crew-cleanup.ts";
8
+ import type { WorkerExitStatus } from "../state/types.ts";
9
+ import { WINDOWS_ESSENTIAL_ENV_VARS } from "../utils/env-allowlist.ts";
10
+ import { sanitizeEnvSecrets } from "../utils/env-filter.ts";
11
11
  import { logInternalError } from "../utils/internal-error.ts";
12
- import { attachPostExitStdioGuard, trySignalChild } from "./post-exit-stdio-guard.ts";
13
12
  import { redactJsonLine, redactSecretString } from "../utils/redaction.ts";
13
+ import { resolveRealContainedPath } from "../utils/safe-paths.ts";
14
14
  import { applyCompactPipeline } from "./compact-pipeline.ts";
15
- import { TruncationStage, TailCaptureStage } from "./compact-stages/index.ts";
16
- import { sanitizeEnvSecrets } from "../utils/env-filter.ts";
17
- import { registerChildProcess, unregisterChildProcess } from "../extension/crew-cleanup.ts";
15
+ import { TailCaptureStage, TruncationStage } from "./compact-stages/index.ts";
18
16
  import { classifyProcessCrash } from "./crash-classification.ts";
19
- import { resolveRealContainedPath } from "../utils/safe-paths.ts";
17
+ import { buildPiWorkerArgs, checkCrewDepth, cleanupTempDir } from "./pi-args.ts";
20
18
  import { extractText } from "./pi-json-output.ts";
19
+ import { getPiSpawnCommand } from "./pi-spawn.ts";
20
+ import { attachPostExitStdioGuard, trySignalChild } from "./post-exit-stdio-guard.ts";
21
21
 
22
22
  const POST_EXIT_STDIO_GUARD_MS = DEFAULT_CHILD_PI.postExitStdioGuardMs;
23
23
  const FINAL_DRAIN_MS = DEFAULT_CHILD_PI.finalDrainMs;
@@ -75,13 +75,27 @@ export function killProcessPid(pid: number): void {
75
75
  // 3.8: Windows path uses taskkill /T /F (force kill the entire tree).
76
76
  // taskkill itself can silently fail (PID gone, permission denied, etc.)
77
77
  // so verify after 2s and log a warning if the process is still alive.
78
- spawn("taskkill", ["/pid", String(pid), "/t", "/f"], { stdio: "ignore", windowsHide: true });
78
+ spawn("taskkill", ["/pid", String(pid), "/t", "/f"], {
79
+ stdio: "ignore",
80
+ windowsHide: true,
81
+ });
79
82
  const verifyTimer = setTimeout(() => {
80
83
  try {
81
84
  process.kill(pid, 0); // throws ESRCH when dead
82
85
  // Still alive — log and retry once.
83
- logInternalError("child-pi.taskkill-stuck", new Error(`process ${pid} still alive 2s after taskkill /T /F; retrying`), `pid=${pid}`);
84
- try { spawn("taskkill", ["/pid", String(pid), "/t", "/f"], { stdio: "ignore", windowsHide: true }); } catch { /* best-effort */ }
86
+ logInternalError(
87
+ "child-pi.taskkill-stuck",
88
+ new Error(`process ${pid} still alive 2s after taskkill /T /F; retrying`),
89
+ `pid=${pid}`,
90
+ );
91
+ try {
92
+ spawn("taskkill", ["/pid", String(pid), "/t", "/f"], {
93
+ stdio: "ignore",
94
+ windowsHide: true,
95
+ });
96
+ } catch {
97
+ /* best-effort */
98
+ }
85
99
  } catch {
86
100
  // ESRCH or EPERM — process is gone. OK.
87
101
  }
@@ -131,7 +145,9 @@ function killProcessTree(pid: number | undefined, child?: ChildProcess): void {
131
145
  new Error(`pid=${pid} called from:\n${callerStack.split("\n").slice(0, 8).join("\n")}`),
132
146
  `pid=${pid}`,
133
147
  );
134
- } catch { /* diagnostic best-effort */ }
148
+ } catch {
149
+ /* diagnostic best-effort */
150
+ }
135
151
  if (!pid || !Number.isInteger(pid) || pid <= 0) return;
136
152
  if (child && child.exitCode !== null) return;
137
153
  killProcessPid(pid);
@@ -144,7 +160,6 @@ export function terminateActiveChildPiProcesses(): number {
144
160
  return entries.length;
145
161
  }
146
162
 
147
-
148
163
  /** Structured lifecycle event emitted by child-pi for critical transitions. */
149
164
  export interface ChildPiLifecycleEvent {
150
165
  /** Event discriminator. */
@@ -417,11 +432,7 @@ function appendTranscript(input: ChildPiRunInput, line: string): void {
417
432
  }
418
433
  }
419
434
 
420
- export function compactString(
421
- value: string,
422
- maxChars = MAX_COMPACT_CONTENT_CHARS,
423
- opts: { preserveImportant?: boolean } = {},
424
- ): string {
435
+ export function compactString(value: string, maxChars = MAX_COMPACT_CONTENT_CHARS, opts: { preserveImportant?: boolean } = {}): string {
425
436
  if (value.length <= maxChars) return value;
426
437
  // L4: head + tail instead of head-only. Keeps closing markdown structure
427
438
  // (code fences, headings, list tails) instead of dropping them — the old
@@ -438,7 +449,11 @@ export function compactString(
438
449
  // P0-B: the TruncationStage scans the middle slice for important diagnostic
439
450
  // lines (error, file:line, HTTP 4xx/5xx, compiler codes) and preserves them
440
451
  // within a 15% slack budget. The `preserveImportant` opt propagates here.
441
- const result = applyCompactPipeline(value, [new TruncationStage(maxChars, { preserveImportant: opts.preserveImportant })]);
452
+ const result = applyCompactPipeline(value, [
453
+ new TruncationStage(maxChars, {
454
+ preserveImportant: opts.preserveImportant,
455
+ }),
456
+ ]);
442
457
  return result.text;
443
458
  }
444
459
 
@@ -466,9 +481,30 @@ export function compactValue(value: unknown): unknown {
466
481
  function compactContentPart(part: unknown): unknown | undefined {
467
482
  const record = asRecord(part);
468
483
  if (!record) return undefined;
469
- if (record.type === "text") return { type: "text", text: typeof record.text === "string" ? compactString(record.text, MAX_ASSISTANT_TEXT_CHARS, { preserveImportant: false }) : "" };
470
- if (record.type === "toolCall") return { type: "toolCall", name: record.name, input: compactValue(typeof record.input === "string" ? compactString(record.input, MAX_TOOL_INPUT_CHARS) : record.input) };
471
- if (record.type === "toolResult") return { type: "toolResult", name: record.name, content: compactValue(typeof record.content === "string" ? compactString(record.content, MAX_TOOL_RESULT_CHARS) : record.content) };
484
+ if (record.type === "text")
485
+ return {
486
+ type: "text",
487
+ text:
488
+ typeof record.text === "string"
489
+ ? compactString(record.text, MAX_ASSISTANT_TEXT_CHARS, {
490
+ preserveImportant: false,
491
+ })
492
+ : "",
493
+ };
494
+ if (record.type === "toolCall")
495
+ return {
496
+ type: "toolCall",
497
+ name: record.name,
498
+ input: compactValue(typeof record.input === "string" ? compactString(record.input, MAX_TOOL_INPUT_CHARS) : record.input),
499
+ };
500
+ if (record.type === "toolResult")
501
+ return {
502
+ type: "toolResult",
503
+ name: record.name,
504
+ content: compactValue(
505
+ typeof record.content === "string" ? compactString(record.content, MAX_TOOL_RESULT_CHARS) : record.content,
506
+ ),
507
+ };
472
508
  return undefined;
473
509
  }
474
510
 
@@ -477,16 +513,33 @@ function compactChildPiEvent(event: unknown): unknown | undefined {
477
513
  if (!record) return undefined;
478
514
  if (record.type === "message_update") return undefined;
479
515
  if (record.type === "tool_execution_start" || record.type === "tool_execution_end") {
480
- return { type: record.type, toolName: record.toolName, args: record.args };
516
+ return {
517
+ type: record.type,
518
+ toolName: record.toolName,
519
+ args: record.args,
520
+ };
481
521
  }
482
522
  if (record.type === "tool_result_end" || record.type === "message_end" || record.type === "message") {
483
523
  const message = asRecord(record.message);
484
524
  if (message?.role === "user" || message?.role === "system") return undefined;
485
- const content = Array.isArray(message?.content) ? message.content.map(compactContentPart).filter((part) => part !== undefined) : undefined;
525
+ const content = Array.isArray(message?.content)
526
+ ? message.content.map(compactContentPart).filter((part) => part !== undefined)
527
+ : undefined;
486
528
  return {
487
529
  type: record.type,
488
530
  ...(typeof record.text === "string" ? { text: record.text } : {}),
489
- ...(message ? { message: { role: message.role, ...(content ? { content } : {}), usage: message.usage, model: message.model, errorMessage: message.errorMessage, stopReason: message.stopReason } } : {}),
531
+ ...(message
532
+ ? {
533
+ message: {
534
+ role: message.role,
535
+ ...(content ? { content } : {}),
536
+ usage: message.usage,
537
+ model: message.model,
538
+ errorMessage: message.errorMessage,
539
+ stopReason: message.stopReason,
540
+ },
541
+ }
542
+ : {}),
490
543
  usage: record.usage,
491
544
  model: record.model,
492
545
  provider: record.provider,
@@ -506,18 +559,31 @@ function displayTextFromCompactEvent(event: unknown): string | undefined {
506
559
  const message = asRecord(record.message);
507
560
  if (message?.role !== undefined && message.role !== "assistant") return undefined;
508
561
  const content = Array.isArray(message?.content) ? message.content : [];
509
- const text = content.flatMap((part) => {
510
- const item = asRecord(part);
511
- return item?.type === "text" && typeof item.text === "string" ? [item.text] : [];
512
- }).join("\n").trim();
562
+ const text = content
563
+ .flatMap((part) => {
564
+ const item = asRecord(part);
565
+ return item?.type === "text" && typeof item.text === "string" ? [item.text] : [];
566
+ })
567
+ .join("\n")
568
+ .trim();
513
569
  return text || (typeof record.text === "string" ? record.text : undefined);
514
570
  }
515
571
 
516
- function compactChildPiLine(line: string): { persistedLine: string; event?: unknown; displayLine?: string; json: boolean } {
572
+ function compactChildPiLine(line: string): {
573
+ persistedLine: string;
574
+ event?: unknown;
575
+ displayLine?: string;
576
+ json: boolean;
577
+ } {
517
578
  try {
518
579
  const parsed = JSON.parse(line);
519
580
  const compact = compactChildPiEvent(parsed);
520
- return { json: true, event: compact, persistedLine: compact ? JSON.stringify(compact) : "", displayLine: displayTextFromCompactEvent(compact) };
581
+ return {
582
+ json: true,
583
+ event: compact,
584
+ persistedLine: compact ? JSON.stringify(compact) : "",
585
+ displayLine: displayTextFromCompactEvent(compact),
586
+ };
521
587
  } catch {
522
588
  return { json: false, persistedLine: line, displayLine: line };
523
589
  }
@@ -632,7 +698,7 @@ function observeStdoutChunk(input: ChildPiRunInput, text: string): void {
632
698
  }
633
699
 
634
700
  function asRecord(value: unknown): Record<string, unknown> | undefined {
635
- return value && typeof value === "object" && !Array.isArray(value) ? value as Record<string, unknown> : undefined;
701
+ return value && typeof value === "object" && !Array.isArray(value) ? (value as Record<string, unknown>) : undefined;
636
702
  }
637
703
 
638
704
  function isFinalAssistantEvent(event: unknown): boolean {
@@ -641,7 +707,8 @@ function isFinalAssistantEvent(event: unknown): boolean {
641
707
  const message = asRecord(obj.message);
642
708
  const role = message?.role;
643
709
  if (role !== undefined && role !== "assistant") return false;
644
- const stopReason = typeof message?.stopReason === "string" ? message.stopReason : typeof obj.stopReason === "string" ? obj.stopReason : undefined;
710
+ const stopReason =
711
+ typeof message?.stopReason === "string" ? message.stopReason : typeof obj.stopReason === "string" ? obj.stopReason : undefined;
645
712
  if (stopReason !== undefined && stopReason !== "stop") return false;
646
713
  const content = Array.isArray(message?.content) ? message.content : [];
647
714
  return !content.some((part) => asRecord(part)?.type === "toolCall");
@@ -651,11 +718,17 @@ export async function runChildPi(input: ChildPiRunInput): Promise<ChildPiRunResu
651
718
  // Phase 1 (live-session parity): prepend parent context when inheritContext is true.
652
719
  // This mirrors the effectivePrompt logic in live-session-runtime.ts so that
653
720
  // child-process workers receive the same inherited-context treatment.
654
- const effectiveTask = input.inheritContext === true && input.parentContext
655
- ? `${input.parentContext}\n\n---\n# Child Worker Task\n${input.task}`
656
- : input.task;
721
+ const effectiveTask =
722
+ input.inheritContext === true && input.parentContext
723
+ ? `${input.parentContext}\n\n---\n# Child Worker Task\n${input.task}`
724
+ : input.task;
657
725
  const depth = checkCrewDepth(input.maxDepth);
658
- if (depth.blocked) return { exitCode: 1, stdout: "", stderr: `pi-crew depth guard blocked child worker: depth ${depth.depth} >= max ${depth.maxDepth}` };
726
+ if (depth.blocked)
727
+ return {
728
+ exitCode: 1,
729
+ stdout: "",
730
+ stderr: `pi-crew depth guard blocked child worker: depth ${depth.depth} >= max ${depth.maxDepth}`,
731
+ };
659
732
  const mock = process.env.PI_TEAMS_MOCK_CHILD_PI;
660
733
  if (mock) {
661
734
  // SECURITY (Issue #2): Mock mode security model is intentionally asymmetric.
@@ -671,7 +744,11 @@ export async function runChildPi(input: ChildPiRunInput): Promise<ChildPiRunResu
671
744
  // Setup hooks cannot inject PI_CREW_ALLOW_MOCK into the parent's env.
672
745
  const allowMock = process.env.PI_CREW_ALLOW_MOCK === "1" || process.env.PI_CREW_ALLOW_MOCK === "true";
673
746
  if (!allowMock) {
674
- return { exitCode: 1, stdout: "", stderr: "Mock mode requires PI_CREW_ALLOW_MOCK=1" };
747
+ return {
748
+ exitCode: 1,
749
+ stdout: "",
750
+ stderr: "Mock mode requires PI_CREW_ALLOW_MOCK=1",
751
+ };
675
752
  }
676
753
  // SECURITY: Log mock mode activation prominently for audit trail
677
754
  logInternalError("child-pi.mock", new Error(`Mock mode active: ${mock}`), "NOT running real agents");
@@ -681,14 +758,66 @@ export async function runChildPi(input: ChildPiRunInput): Promise<ChildPiRunResu
681
758
  return { exitCode: 0, stdout, stderr: "" };
682
759
  }
683
760
  if (mock === "json-success" || mock === "adaptive-plan") {
684
- const text = mock === "adaptive-plan" && effectiveTask.includes("ADAPTIVE_PLAN_JSON_START")
685
- ? `[MOCK] Adaptive plan\nADAPTIVE_PLAN_JSON_START\n${JSON.stringify({ phases: [{ name: "research", tasks: [{ role: "explorer", task: "Explore adaptive target" }, { role: "analyst", task: "Analyze adaptive target" }, { role: "planner", task: "Plan adaptive target" }] }, { name: "build", tasks: [{ role: "executor", task: "Implement adaptive target" }] }, { name: "check", tasks: [{ role: "reviewer", task: "Review adaptive target" }, { role: "test-engineer", task: "Test adaptive target" }, { role: "writer", task: "Summarize adaptive target" }] }] })}\nADAPTIVE_PLAN_JSON_END`
686
- : `[MOCK] JSON success for ${input.agent.name}`;
761
+ const text =
762
+ mock === "adaptive-plan" && effectiveTask.includes("ADAPTIVE_PLAN_JSON_START")
763
+ ? `[MOCK] Adaptive plan\nADAPTIVE_PLAN_JSON_START\n${JSON.stringify({
764
+ phases: [
765
+ {
766
+ name: "research",
767
+ tasks: [
768
+ {
769
+ role: "explorer",
770
+ task: "Explore adaptive target",
771
+ },
772
+ {
773
+ role: "analyst",
774
+ task: "Analyze adaptive target",
775
+ },
776
+ {
777
+ role: "planner",
778
+ task: "Plan adaptive target",
779
+ },
780
+ ],
781
+ },
782
+ {
783
+ name: "build",
784
+ tasks: [
785
+ {
786
+ role: "executor",
787
+ task: "Implement adaptive target",
788
+ },
789
+ ],
790
+ },
791
+ {
792
+ name: "check",
793
+ tasks: [
794
+ {
795
+ role: "reviewer",
796
+ task: "Review adaptive target",
797
+ },
798
+ {
799
+ role: "test-engineer",
800
+ task: "Test adaptive target",
801
+ },
802
+ {
803
+ role: "writer",
804
+ task: "Summarize adaptive target",
805
+ },
806
+ ],
807
+ },
808
+ ],
809
+ })}\nADAPTIVE_PLAN_JSON_END`
810
+ : `[MOCK] JSON success for ${input.agent.name}`;
687
811
  const stdout = `${JSON.stringify({ type: "message", message: { role: "assistant", content: [{ type: "text", text }] } })}\n${JSON.stringify({ type: "message_end", usage: { input: 10, output: 5, cost: 0.001, turns: 1 } })}\n`;
688
812
  observeStdoutChunk(input, stdout);
689
813
  return { exitCode: 0, stdout, stderr: "" };
690
814
  }
691
- if (mock === "retryable-failure") return { exitCode: 1, stdout: "", stderr: "[MOCK] rate limit: mock failure" };
815
+ if (mock === "retryable-failure")
816
+ return {
817
+ exitCode: 1,
818
+ stdout: "",
819
+ stderr: "[MOCK] rate limit: mock failure",
820
+ };
692
821
  // E2E fallback-chain fixture: invocation #1 returns a SILENT retryable
693
822
  // failure (exit code 0, no real assistant text, message_end carries a
694
823
  // retryable-pattern errorMessage). Invocation #2+ delegates to the
@@ -740,7 +869,15 @@ export async function runChildPi(input: ChildPiRunInput): Promise<ChildPiRunResu
740
869
  }
741
870
  return { exitCode: 1, stdout: "", stderr: `[MOCK] failure: ${mock}` };
742
871
  }
743
- const built = buildPiWorkerArgs({ task: effectiveTask, agent: input.agent, model: input.model, sessionEnabled: true, maxDepth: input.maxDepth, skillPaths: input.skillPaths, role: input.role });
872
+ const built = buildPiWorkerArgs({
873
+ task: effectiveTask,
874
+ agent: input.agent,
875
+ model: input.model,
876
+ sessionEnabled: true,
877
+ maxDepth: input.maxDepth,
878
+ skillPaths: input.skillPaths,
879
+ role: input.role,
880
+ });
744
881
  const spawnSpec = getPiSpawnCommand(built.args);
745
882
  try {
746
883
  return await new Promise<ChildPiRunResult>((resolve) => {
@@ -755,20 +892,37 @@ export async function runChildPi(input: ChildPiRunInput): Promise<ChildPiRunResu
755
892
  // built.env, the assertion will throw before the secret reaches the child process.
756
893
  for (const key of Object.keys(built.env)) {
757
894
  if (!key.startsWith("PI_CREW_") && !key.startsWith("PI_TEAMS_")) {
758
- throw new Error(`SECURITY: built.env contains unexpected key "${key}"; expected only PI_CREW_* or PI_TEAMS_* execution-control vars`);
895
+ throw new Error(
896
+ `SECURITY: built.env contains unexpected key "${key}"; expected only PI_CREW_* or PI_TEAMS_* execution-control vars`,
897
+ );
759
898
  }
760
899
  }
761
- const child = spawn(spawnSpec.command, spawnSpec.args, buildChildPiSpawnOptions(input.cwd, { ...process.env, ...built.env }));
900
+ const child = spawn(
901
+ spawnSpec.command,
902
+ spawnSpec.args,
903
+ buildChildPiSpawnOptions(input.cwd, {
904
+ ...process.env,
905
+ ...built.env,
906
+ }),
907
+ );
762
908
  if (child.pid) {
763
909
  activeChildProcesses.set(child.pid, child);
764
910
  input.onSpawn?.(child.pid);
765
- input.onLifecycleEvent?.({ type: "spawned", pid: child.pid, ts: new Date().toISOString() });
911
+ input.onLifecycleEvent?.({
912
+ type: "spawned",
913
+ pid: child.pid,
914
+ ts: new Date().toISOString(),
915
+ });
766
916
  // Register with cleanup handler for graceful shutdown
767
917
  if (input.runId && input.agentId) {
768
918
  registerChildProcess(child.pid, input.runId, input.agentId);
769
919
  }
770
920
  } else {
771
- input.onLifecycleEvent?.({ type: "spawn_error", error: "spawn returned no pid", ts: new Date().toISOString() });
921
+ input.onLifecycleEvent?.({
922
+ type: "spawn_error",
923
+ error: "spawn returned no pid",
924
+ ts: new Date().toISOString(),
925
+ });
772
926
  }
773
927
  let stdout = "";
774
928
  let stderr = "";
@@ -797,8 +951,11 @@ export async function runChildPi(input: ChildPiRunInput): Promise<ChildPiRunResu
797
951
  const RESPONSE_TIMEOUT_MIN_MS = 1_000;
798
952
  const RESPONSE_TIMEOUT_MAX_MS = 3_600_000;
799
953
  const responseTimeoutEnv = Number.parseInt(process.env.PI_TEAMS_CHILD_RESPONSE_TIMEOUT_MS ?? "", 10);
800
- const envInRange = Number.isFinite(responseTimeoutEnv) && responseTimeoutEnv >= RESPONSE_TIMEOUT_MIN_MS && responseTimeoutEnv <= RESPONSE_TIMEOUT_MAX_MS;
801
- const responseTimeoutMs = envInRange ? responseTimeoutEnv : input.responseTimeoutMs ?? RESPONSE_TIMEOUT_MS;
954
+ const envInRange =
955
+ Number.isFinite(responseTimeoutEnv) &&
956
+ responseTimeoutEnv >= RESPONSE_TIMEOUT_MIN_MS &&
957
+ responseTimeoutEnv <= RESPONSE_TIMEOUT_MAX_MS;
958
+ const responseTimeoutMs = envInRange ? responseTimeoutEnv : (input.responseTimeoutMs ?? RESPONSE_TIMEOUT_MS);
802
959
  let responseTimeoutHit = false;
803
960
  let forcedFinalDrain = false;
804
961
  let abortRequested = input.signal?.aborted === true;
@@ -849,8 +1006,12 @@ export async function runChildPi(input: ChildPiRunInput): Promise<ChildPiRunResu
849
1006
  // MaxListenersExceededWarning and each leaked listener pinned the task's
850
1007
  // stack frame (abortDueToParentSignal closure) in memory. { once: true }
851
1008
  // only auto-removes AFTER the signal fires; on normal completion it leaks.
852
- const onParentAbort = (): void => { abortDueToParentSignal = true; };
853
- input.signal?.addEventListener("abort", onParentAbort, { once: true });
1009
+ const onParentAbort = (): void => {
1010
+ abortDueToParentSignal = true;
1011
+ };
1012
+ input.signal?.addEventListener("abort", onParentAbort, {
1013
+ once: true,
1014
+ });
854
1015
  const restartNoResponseTimer = (): void => {
855
1016
  if (responseTimeoutMs <= 0) return;
856
1017
  if (noResponseTimer) clearTimeout(noResponseTimer);
@@ -860,7 +1021,13 @@ export async function runChildPi(input: ChildPiRunInput): Promise<ChildPiRunResu
860
1021
  // SEC-1: redact secrets before embedding in lifecycle event so
861
1022
  // worker-emitted secrets (API keys etc.) don't bypass redaction.
862
1023
  const timeoutStderr = redactStderrExcerpt(stderr, 1024); // Last 1KB of stderr (redacted, SEC-1)
863
- input.onLifecycleEvent?.({ type: "response_timeout", pid: child.pid, error: `No output for ${responseTimeoutMs}ms`, ts: new Date().toISOString(), stderr: timeoutStderr || undefined });
1024
+ input.onLifecycleEvent?.({
1025
+ type: "response_timeout",
1026
+ pid: child.pid,
1027
+ error: `No output for ${responseTimeoutMs}ms`,
1028
+ ts: new Date().toISOString(),
1029
+ stderr: timeoutStderr || undefined,
1030
+ });
864
1031
  killProcessTree(child.pid, child);
865
1032
  try {
866
1033
  child.kill(process.platform === "win32" ? undefined : "SIGTERM");
@@ -946,24 +1113,45 @@ export async function runChildPi(input: ChildPiRunInput): Promise<ChildPiRunResu
946
1113
  // disableTools/maxTurns:1 exit-null bug). Just log + let the hard-abort
947
1114
  // path handle a genuinely runaway worker.
948
1115
  if (child.stdin?.writable) {
949
- const steerPayload = JSON.stringify({ type: "steer", message: "You have reached your turn limit. Wrap up immediately — provide your final answer now." }) + "\n";
1116
+ const steerPayload =
1117
+ JSON.stringify({
1118
+ type: "steer",
1119
+ message:
1120
+ "You have reached your turn limit. Wrap up immediately — provide your final answer now.",
1121
+ }) + "\n";
950
1122
  const writeSucceeded = child.stdin.write(steerPayload);
951
1123
  if (!writeSucceeded) {
952
1124
  // Normal Node backpressure: the payload is buffered and will flush on
953
1125
  // 'drain'. NOT a failure — do NOT kill the worker. The steer is
954
1126
  // advisory; if the worker ignores it and runs past maxTurns +
955
1127
  // graceTurns, the hard-abort below terminates it.
956
- logInternalError("child-pi.steer-backpressure", new Error("stdin write returned false (normal backpressure); steer buffered, worker NOT killed"), `pid=${child.pid}`);
1128
+ logInternalError(
1129
+ "child-pi.steer-backpressure",
1130
+ new Error(
1131
+ "stdin write returned false (normal backpressure); steer buffered, worker NOT killed",
1132
+ ),
1133
+ `pid=${child.pid}`,
1134
+ );
957
1135
  }
958
1136
  } else {
959
1137
  // stdin closed (worker already finished) or otherwise unwritable.
960
1138
  // Also advisory — the worker is done or nearly done; let it exit
961
1139
  // naturally. Hard-abort remains the safety net for true runaways.
962
- logInternalError("child-pi.steer-not-writable", new Error("stdin not writable when attempting steer injection (worker may be done); worker NOT killed"), `pid=${child.pid}`);
1140
+ logInternalError(
1141
+ "child-pi.steer-not-writable",
1142
+ new Error(
1143
+ "stdin not writable when attempting steer injection (worker may be done); worker NOT killed",
1144
+ ),
1145
+ `pid=${child.pid}`,
1146
+ );
963
1147
  }
964
1148
  } else if (maxTurns !== undefined && softLimitReached && turnCount >= maxTurns + (graceTurns ?? 5)) {
965
1149
  // Hard abort — terminate after grace turns
966
- try { child.kill(process.platform === "win32" ? undefined : "SIGTERM"); } catch { /* best-effort */ }
1150
+ try {
1151
+ child.kill(process.platform === "win32" ? undefined : "SIGTERM");
1152
+ } catch {
1153
+ /* best-effort */
1154
+ }
967
1155
  }
968
1156
  }
969
1157
  }
@@ -980,7 +1168,11 @@ export async function runChildPi(input: ChildPiRunInput): Promise<ChildPiRunResu
980
1168
  if (settled || childExited) return;
981
1169
  forcedFinalDrain = true;
982
1170
  finalDrainFiredMonotonicMs = performance.now(); // Phase-0 diagnostic: race timing.
983
- input.onLifecycleEvent?.({ type: "final_drain", pid: child.pid, ts: new Date().toISOString() });
1171
+ input.onLifecycleEvent?.({
1172
+ type: "final_drain",
1173
+ pid: child.pid,
1174
+ ts: new Date().toISOString(),
1175
+ });
984
1176
  try {
985
1177
  child.kill(process.platform === "win32" ? undefined : "SIGTERM");
986
1178
  } catch (error) {
@@ -990,7 +1182,11 @@ export async function runChildPi(input: ChildPiRunInput): Promise<ChildPiRunResu
990
1182
  if (settled || childExited) return;
991
1183
  try {
992
1184
  hardKilled = true;
993
- input.onLifecycleEvent?.({ type: "hard_kill", pid: child.pid, ts: new Date().toISOString() });
1185
+ input.onLifecycleEvent?.({
1186
+ type: "hard_kill",
1187
+ pid: child.pid,
1188
+ ts: new Date().toISOString(),
1189
+ });
994
1190
  child.kill(process.platform === "win32" ? undefined : "SIGKILL");
995
1191
  } catch (error) {
996
1192
  logInternalError("child-pi.final-drain-kill", error, `pid=${child.pid}`);
@@ -1051,7 +1247,7 @@ export async function runChildPi(input: ChildPiRunInput): Promise<ChildPiRunResu
1051
1247
  finalDrainArmed,
1052
1248
  forcedFinalDrain,
1053
1249
  finalDrainFiredMonotonicMs,
1054
- }
1250
+ }
1055
1251
  : {}),
1056
1252
  cleanupErrors,
1057
1253
  finalDrainMs,
@@ -1099,7 +1295,11 @@ export async function runChildPi(input: ChildPiRunInput): Promise<ChildPiRunResu
1099
1295
  let backpressureBytes = 0;
1100
1296
  const releaseBackpressure = (): void => {
1101
1297
  backpressureBytes = 0;
1102
- try { child.stdout?.resume(); } catch { /* ignore */ }
1298
+ try {
1299
+ child.stdout?.resume();
1300
+ } catch {
1301
+ /* ignore */
1302
+ }
1103
1303
  };
1104
1304
  child.stdout?.on("data", (chunk: Buffer) => {
1105
1305
  restartNoResponseTimer();
@@ -1111,7 +1311,11 @@ export async function runChildPi(input: ChildPiRunInput): Promise<ChildPiRunResu
1111
1311
  logInternalError("child-pi.line-observer-observe", err, `text=${text.slice(0, 100)}`);
1112
1312
  }
1113
1313
  if (backpressureBytes > BACKPRESSURE_HIGH && child.stdout && !child.stdout.isPaused()) {
1114
- try { child.stdout.pause(); } catch { /* ignore */ }
1314
+ try {
1315
+ child.stdout.pause();
1316
+ } catch {
1317
+ /* ignore */
1318
+ }
1115
1319
  const timer = setTimeout(releaseBackpressure, 50);
1116
1320
  timer.unref();
1117
1321
  }
@@ -1128,11 +1332,37 @@ export async function runChildPi(input: ChildPiRunInput): Promise<ChildPiRunResu
1128
1332
  );
1129
1333
  rejectPendingOperations(processError);
1130
1334
  try {
1131
- input.onLifecycleEvent?.({ type: "spawn_error", pid: child.pid, error: processError.message, ts: new Date().toISOString(), stderrExcerpt: redactStderrExcerpt(stderr, 500) || undefined });
1335
+ input.onLifecycleEvent?.({
1336
+ type: "spawn_error",
1337
+ pid: child.pid,
1338
+ error: processError.message,
1339
+ ts: new Date().toISOString(),
1340
+ stderrExcerpt: redactStderrExcerpt(stderr, 500) || undefined,
1341
+ });
1132
1342
  } catch (err) {
1133
1343
  logInternalError("child-pi.on-lifecycle-event", err, `event=error, pid=${child.pid}`);
1134
1344
  }
1135
- settle({ exitCode: null, stdout, stderr, error: processError.message, exitStatus: { exitCode: null, cancelled: abortRequested, timedOut: responseTimeoutHit, killed: false, cleanupErrors, finalDrainMs, crashClass: classifyProcessCrash({ exitCode: null, cancelled: abortRequested, timedOut: responseTimeoutHit, spawnError: error, stderrSnippet: stderr ? redactStderrExcerpt(stderr, 1000) : undefined }).crashClass } });
1345
+ settle({
1346
+ exitCode: null,
1347
+ stdout,
1348
+ stderr,
1349
+ error: processError.message,
1350
+ exitStatus: {
1351
+ exitCode: null,
1352
+ cancelled: abortRequested,
1353
+ timedOut: responseTimeoutHit,
1354
+ killed: false,
1355
+ cleanupErrors,
1356
+ finalDrainMs,
1357
+ crashClass: classifyProcessCrash({
1358
+ exitCode: null,
1359
+ cancelled: abortRequested,
1360
+ timedOut: responseTimeoutHit,
1361
+ spawnError: error,
1362
+ stderrSnippet: stderr ? redactStderrExcerpt(stderr, 1000) : undefined,
1363
+ }).crashClass,
1364
+ },
1365
+ });
1136
1366
  });
1137
1367
  child.on("exit", (code, signal) => {
1138
1368
  if (child.pid) {
@@ -1150,9 +1380,9 @@ export async function runChildPi(input: ChildPiRunInput): Promise<ChildPiRunResu
1150
1380
  const isUnexpectedExit = !childExited && !settled && !responseTimeoutHit && !abortRequested && abnormalExit;
1151
1381
  const exitError = isUnexpectedExit
1152
1382
  ? new Error(
1153
- `Child Pi process exited unexpectedly (code=${code ?? "null"} signal=${signal ?? "null"}). `
1154
- + `Stderr: ${redactStderrExcerpt(stderr, 1000) || "(none)"}`,
1155
- )
1383
+ `Child Pi process exited unexpectedly (code=${code ?? "null"} signal=${signal ?? "null"}). ` +
1384
+ `Stderr: ${redactStderrExcerpt(stderr, 1000) || "(none)"}`,
1385
+ )
1156
1386
  : null;
1157
1387
  if (exitError) {
1158
1388
  rejectPendingOperations(exitError);
@@ -1179,7 +1409,7 @@ export async function runChildPi(input: ChildPiRunInput): Promise<ChildPiRunResu
1179
1409
  finalAssistantEventMonotonicMs,
1180
1410
  exitMonotonicMs: performance.now() - spawnMonotonicMs,
1181
1411
  },
1182
- }
1412
+ }
1183
1413
  : {}),
1184
1414
  });
1185
1415
  } catch (err) {
@@ -1203,18 +1433,36 @@ export async function runChildPi(input: ChildPiRunInput): Promise<ChildPiRunResu
1203
1433
  unregisterChildProcess(child.pid);
1204
1434
  }
1205
1435
  try {
1206
- input.onLifecycleEvent?.({ type: "close", pid: child.pid, exitCode, ts: new Date().toISOString() });
1436
+ input.onLifecycleEvent?.({
1437
+ type: "close",
1438
+ pid: child.pid,
1439
+ exitCode,
1440
+ ts: new Date().toISOString(),
1441
+ });
1207
1442
  } catch (err) {
1208
1443
  logInternalError("child-pi.on-lifecycle-event", err, `event=close, pid=${child.pid}`);
1209
1444
  }
1210
- const timeoutError = responseTimeoutHit && !stderr.trim() ? { error: `Child Pi produced no new output for ${responseTimeoutMs}ms; process was terminated as unresponsive.` } : responseTimeoutHit && stderr.trim() ? { error: `Child Pi timed out after ${responseTimeoutMs}ms with stderr: ${redactStderrExcerpt(stderr, 500)}` } : undefined;
1445
+ const timeoutError =
1446
+ responseTimeoutHit && !stderr.trim()
1447
+ ? {
1448
+ error: `Child Pi produced no new output for ${responseTimeoutMs}ms; process was terminated as unresponsive.`,
1449
+ }
1450
+ : responseTimeoutHit && stderr.trim()
1451
+ ? {
1452
+ error: `Child Pi timed out after ${responseTimeoutMs}ms with stderr: ${redactStderrExcerpt(stderr, 500)}`,
1453
+ }
1454
+ : undefined;
1211
1455
  // M6 fix: log when forced final drain converts non-zero exit to 0.
1212
- // This is expected in normal operation (child finished cleanly but linger was killed),
1213
- // but the telemetry helps detect regressions where crashes are hidden.
1214
- if (forcedFinalDrain && !timeoutError && exitCode !== 0) {
1215
- logInternalError("child-pi.final-drain-zero-exit", new Error(`Child exit code overridden to 0 after forced final drain (original=${exitCode})`), `pid=${child.pid}, finalDrainMs=${finalDrainMs}`);
1216
- }
1217
- const finalExitCode = forcedFinalDrain && !timeoutError ? 0 : exitCode;
1456
+ // This is expected in normal operation (child finished cleanly but linger was killed),
1457
+ // but the telemetry helps detect regressions where crashes are hidden.
1458
+ if (forcedFinalDrain && !timeoutError && exitCode !== 0) {
1459
+ logInternalError(
1460
+ "child-pi.final-drain-zero-exit",
1461
+ new Error(`Child exit code overridden to 0 after forced final drain (original=${exitCode})`),
1462
+ `pid=${child.pid}, finalDrainMs=${finalDrainMs}`,
1463
+ );
1464
+ }
1465
+ const finalExitCode = forcedFinalDrain && !timeoutError ? 0 : exitCode;
1218
1466
  const wasGraceAborted = softLimitReached && turnCount >= (maxTurns ?? 0) + (graceTurns ?? 5);
1219
1467
  const wasParentAborted = abortDueToParentSignal && !wasGraceAborted;
1220
1468
  // steerInjectionFailed is now always false (Phase-1 fix: steer backpressure
@@ -1233,7 +1481,24 @@ export async function runChildPi(input: ChildPiRunInput): Promise<ChildPiRunResu
1233
1481
  spawnError: undefined,
1234
1482
  stderrSnippet: stderr ? redactStderrExcerpt(stderr, 1000) : undefined,
1235
1483
  });
1236
- settle({ exitCode: finalExitCode, stdout, stderr, ...(timeoutError ? { error: timeoutError.error } : {}), ...(steerError ? { error: steerError } : {}), aborted: wasGraceAborted || wasParentAborted, steered: softLimitReached && !wasGraceAborted, exitStatus: { exitCode: finalExitCode, cancelled: abortRequested, timedOut: responseTimeoutHit, killed: hardKilled, cleanupErrors, finalDrainMs, crashClass: crashClassification.crashClass } });
1484
+ settle({
1485
+ exitCode: finalExitCode,
1486
+ stdout,
1487
+ stderr,
1488
+ ...(timeoutError ? { error: timeoutError.error } : {}),
1489
+ ...(steerError ? { error: steerError } : {}),
1490
+ aborted: wasGraceAborted || wasParentAborted,
1491
+ steered: softLimitReached && !wasGraceAborted,
1492
+ exitStatus: {
1493
+ exitCode: finalExitCode,
1494
+ cancelled: abortRequested,
1495
+ timedOut: responseTimeoutHit,
1496
+ killed: hardKilled,
1497
+ cleanupErrors,
1498
+ finalDrainMs,
1499
+ crashClass: crashClassification.crashClass,
1500
+ },
1501
+ });
1237
1502
  });
1238
1503
  });
1239
1504
  } finally {