pi-crew 0.9.53 → 0.9.55

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (493) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/package.json +6 -3
  3. package/scripts/clean-strip-types.mjs +60 -0
  4. package/src/adapters/claude-adapter.ts +23 -0
  5. package/src/adapters/codex-adapter.ts +21 -0
  6. package/src/adapters/cursor-adapter.ts +17 -0
  7. package/src/adapters/export-util.ts +143 -0
  8. package/src/adapters/index.ts +15 -0
  9. package/src/adapters/registry.ts +18 -0
  10. package/src/adapters/types.ts +23 -0
  11. package/src/agents/agent-config.ts +194 -0
  12. package/src/agents/agent-search.ts +98 -0
  13. package/src/agents/agent-serializer.ts +38 -0
  14. package/src/agents/discover-agents.ts +623 -0
  15. package/src/benchmark/benchmark-runner.ts +313 -0
  16. package/src/benchmark/feedback-loop.ts +73 -0
  17. package/src/config/config.ts +1270 -0
  18. package/src/config/defaults.ts +193 -0
  19. package/src/config/drift-detector.ts +290 -0
  20. package/src/config/markers.ts +330 -0
  21. package/src/config/resilient-parser.ts +117 -0
  22. package/src/config/role-tools.ts +118 -0
  23. package/src/config/suggestions.ts +75 -0
  24. package/src/config/types.ts +280 -0
  25. package/src/errors.ts +194 -0
  26. package/src/extension/action-suggestions.ts +117 -0
  27. package/src/extension/async-notifier.ts +179 -0
  28. package/src/extension/autonomous-policy.ts +209 -0
  29. package/src/extension/command-completions.ts +120 -0
  30. package/src/extension/context-status-injection.ts +185 -0
  31. package/src/extension/crew-autocomplete.ts +131 -0
  32. package/src/extension/crew-cleanup.ts +168 -0
  33. package/src/extension/crew-input-router.ts +93 -0
  34. package/src/extension/crew-shortcuts.ts +87 -0
  35. package/src/extension/crew-vibes/cat-frames.ts +18 -0
  36. package/src/extension/crew-vibes/config.ts +195 -0
  37. package/src/extension/crew-vibes/figures.ts +101 -0
  38. package/src/extension/crew-vibes/font-detect.ts +71 -0
  39. package/src/extension/crew-vibes/footer.ts +292 -0
  40. package/src/extension/crew-vibes/index.ts +432 -0
  41. package/src/extension/crew-vibes/provider-usage.ts +396 -0
  42. package/src/extension/crew-vibes/render.ts +206 -0
  43. package/src/extension/crew-vibes/speed.ts +286 -0
  44. package/src/extension/cross-extension-rpc.ts +350 -0
  45. package/src/extension/help.ts +62 -0
  46. package/src/extension/import-index.ts +74 -0
  47. package/src/extension/knowledge-injection.ts +402 -0
  48. package/src/extension/management.ts +571 -0
  49. package/src/extension/message-renderers.ts +108 -0
  50. package/src/extension/notification-router.ts +152 -0
  51. package/src/extension/notification-sink.ts +54 -0
  52. package/src/extension/pi-api.ts +56 -0
  53. package/src/extension/plan-orchestrate.ts +302 -0
  54. package/src/extension/project-init.ts +170 -0
  55. package/src/extension/register.ts +113 -0
  56. package/src/extension/registration/artifact-cleanup.ts +20 -0
  57. package/src/extension/registration/command-registration.ts +58 -0
  58. package/src/extension/registration/command-utils.ts +58 -0
  59. package/src/extension/registration/commands.ts +1228 -0
  60. package/src/extension/registration/compaction-guard.ts +333 -0
  61. package/src/extension/registration/context-builder.ts +135 -0
  62. package/src/extension/registration/crash-recovery-cache.ts +54 -0
  63. package/src/extension/registration/foreground-run-controller.ts +238 -0
  64. package/src/extension/registration/hook-registration.ts +119 -0
  65. package/src/extension/registration/lazy-configurers.ts +124 -0
  66. package/src/extension/registration/lifecycle-handlers.ts +923 -0
  67. package/src/extension/registration/lifecycle.ts +259 -0
  68. package/src/extension/registration/observability.ts +322 -0
  69. package/src/extension/registration/registration-types.ts +158 -0
  70. package/src/extension/registration/runtime-cleanup.ts +230 -0
  71. package/src/extension/registration/subagent-helpers.ts +125 -0
  72. package/src/extension/registration/subagent-manager-setup.ts +330 -0
  73. package/src/extension/registration/subagent-tools.ts +456 -0
  74. package/src/extension/registration/team-tool.ts +225 -0
  75. package/src/extension/registration/tool-registration.ts +50 -0
  76. package/src/extension/registration/ui.ts +172 -0
  77. package/src/extension/registration/viewers.ts +121 -0
  78. package/src/extension/registration/wire-cross-extension.ts +37 -0
  79. package/src/extension/result-watcher.ts +139 -0
  80. package/src/extension/rpc-hmac.ts +256 -0
  81. package/src/extension/run-bundle-schema.ts +104 -0
  82. package/src/extension/run-export.ts +97 -0
  83. package/src/extension/run-import.ts +168 -0
  84. package/src/extension/run-index.ts +131 -0
  85. package/src/extension/run-maintenance.ts +190 -0
  86. package/src/extension/session-summary.ts +18 -0
  87. package/src/extension/team-manager-command.ts +153 -0
  88. package/src/extension/team-onboard.ts +180 -0
  89. package/src/extension/team-recommendation.ts +288 -0
  90. package/src/extension/team-tool/anchor.ts +172 -0
  91. package/src/extension/team-tool/api.ts +1244 -0
  92. package/src/extension/team-tool/auto-summarize.ts +142 -0
  93. package/src/extension/team-tool/cache-control.ts +19 -0
  94. package/src/extension/team-tool/cancel.ts +337 -0
  95. package/src/extension/team-tool/chain-dispatch.ts +95 -0
  96. package/src/extension/team-tool/chain-executor.ts +379 -0
  97. package/src/extension/team-tool/config-patch.ts +50 -0
  98. package/src/extension/team-tool/context.ts +149 -0
  99. package/src/extension/team-tool/destructive-gate.ts +52 -0
  100. package/src/extension/team-tool/dispatch/automate.ts +80 -0
  101. package/src/extension/team-tool/dispatch/control.ts +42 -0
  102. package/src/extension/team-tool/dispatch/index.ts +95 -0
  103. package/src/extension/team-tool/dispatch/manage.ts +161 -0
  104. package/src/extension/team-tool/dispatch/run.ts +53 -0
  105. package/src/extension/team-tool/dispatch/status.ts +187 -0
  106. package/src/extension/team-tool/doctor.ts +431 -0
  107. package/src/extension/team-tool/explain.ts +282 -0
  108. package/src/extension/team-tool/failure-patterns.ts +118 -0
  109. package/src/extension/team-tool/goal-wrap.ts +330 -0
  110. package/src/extension/team-tool/goal.ts +556 -0
  111. package/src/extension/team-tool/handle-schedule.ts +332 -0
  112. package/src/extension/team-tool/handle-settings.ts +520 -0
  113. package/src/extension/team-tool/health-monitor.ts +481 -0
  114. package/src/extension/team-tool/inspect.ts +88 -0
  115. package/src/extension/team-tool/intent-policy.ts +45 -0
  116. package/src/extension/team-tool/lifecycle-actions.ts +615 -0
  117. package/src/extension/team-tool/orchestrate.ts +98 -0
  118. package/src/extension/team-tool/parallel-dispatch.ts +183 -0
  119. package/src/extension/team-tool/plan.ts +50 -0
  120. package/src/extension/team-tool/respond.ts +150 -0
  121. package/src/extension/team-tool/run-deadline.ts +66 -0
  122. package/src/extension/team-tool/run-not-found.ts +49 -0
  123. package/src/extension/team-tool/run.ts +1051 -0
  124. package/src/extension/team-tool/status.ts +260 -0
  125. package/src/extension/team-tool/workflow-manage.ts +261 -0
  126. package/src/extension/team-tool-types.ts +26 -0
  127. package/src/extension/team-tool.ts +750 -0
  128. package/src/extension/tool-result.ts +16 -0
  129. package/src/extension/validate-resources.ts +108 -0
  130. package/src/hooks/registry.ts +200 -0
  131. package/src/hooks/types.ts +69 -0
  132. package/src/i18n.ts +212 -0
  133. package/src/observability/correlation.ts +50 -0
  134. package/src/observability/event-bus.ts +86 -0
  135. package/src/observability/event-to-metric.ts +170 -0
  136. package/src/observability/exporters/adapter.ts +27 -0
  137. package/src/observability/exporters/otlp-exporter.ts +356 -0
  138. package/src/observability/exporters/prometheus-exporter.ts +54 -0
  139. package/src/observability/metric-registry.ts +100 -0
  140. package/src/observability/metric-retention.ts +64 -0
  141. package/src/observability/metric-sink.ts +99 -0
  142. package/src/observability/metrics-primitives.ts +219 -0
  143. package/src/plugins/plugin-define.ts +6 -0
  144. package/src/plugins/plugin-registry.ts +32 -0
  145. package/src/plugins/plugins/index.ts +3 -0
  146. package/src/plugins/plugins/nextjs.ts +19 -0
  147. package/src/plugins/plugins/vite.ts +10 -0
  148. package/src/plugins/plugins/vitest.ts +9 -0
  149. package/src/prompt/prompt-runtime.ts +380 -0
  150. package/src/runtime/adaptive-plan.ts +563 -0
  151. package/src/runtime/agent-control.ts +215 -0
  152. package/src/runtime/agent-memory.ts +81 -0
  153. package/src/runtime/agent-observability.ts +122 -0
  154. package/src/runtime/anchor-manager.ts +475 -0
  155. package/src/runtime/async-marker.ts +32 -0
  156. package/src/runtime/async-runner.ts +381 -0
  157. package/src/runtime/attention-events.ts +28 -0
  158. package/src/runtime/auto-summarize.ts +344 -0
  159. package/src/runtime/background-runner.ts +840 -0
  160. package/src/runtime/batch-barrier.ts +147 -0
  161. package/src/runtime/broker-issuer.ts +39 -0
  162. package/src/runtime/cancellation-token.ts +99 -0
  163. package/src/runtime/cancellation.ts +99 -0
  164. package/src/runtime/capability-inventory.ts +137 -0
  165. package/src/runtime/chain-parser.ts +237 -0
  166. package/src/runtime/chain-runner.ts +606 -0
  167. package/src/runtime/checkpoint.ts +291 -0
  168. package/src/runtime/child-pi-constants.ts +42 -0
  169. package/src/runtime/child-pi-kill.ts +180 -0
  170. package/src/runtime/child-pi-pool.ts +68 -0
  171. package/src/runtime/child-pi-spawn.ts +287 -0
  172. package/src/runtime/child-pi-steering.ts +128 -0
  173. package/src/runtime/child-pi-streams.ts +296 -0
  174. package/src/runtime/child-pi-transcript.ts +169 -0
  175. package/src/runtime/child-pi.ts +1105 -0
  176. package/src/runtime/coalesce-tasks.ts +268 -0
  177. package/src/runtime/code-summary.ts +292 -0
  178. package/src/runtime/command-trace.ts +105 -0
  179. package/src/runtime/compact-pipeline.ts +56 -0
  180. package/src/runtime/compact-stages/ansi-strip-stage.ts +25 -0
  181. package/src/runtime/compact-stages/blank-collapse-stage.ts +31 -0
  182. package/src/runtime/compact-stages/deduplicate-stage.ts +34 -0
  183. package/src/runtime/compact-stages/head-snap-stage.ts +57 -0
  184. package/src/runtime/compact-stages/index.ts +23 -0
  185. package/src/runtime/compact-stages/tail-capture-stage.ts +77 -0
  186. package/src/runtime/compact-stages/truncation-stage.ts +74 -0
  187. package/src/runtime/compaction-summary.ts +278 -0
  188. package/src/runtime/completion-guard.ts +207 -0
  189. package/src/runtime/concurrency.ts +58 -0
  190. package/src/runtime/crash-classification.ts +229 -0
  191. package/src/runtime/crash-recovery.ts +594 -0
  192. package/src/runtime/crew-agent-records.ts +595 -0
  193. package/src/runtime/crew-agent-runtime.ts +62 -0
  194. package/src/runtime/crew-broker-child.ts +88 -0
  195. package/src/runtime/crew-broker-client.ts +673 -0
  196. package/src/runtime/crew-broker-tokens.ts +159 -0
  197. package/src/runtime/crew-broker.ts +1304 -0
  198. package/src/runtime/crew-hooks.ts +227 -0
  199. package/src/runtime/cross-extension-rpc.ts +143 -0
  200. package/src/runtime/custom-tools/irc-tool.ts +282 -0
  201. package/src/runtime/custom-tools/submit-result-tool.ts +102 -0
  202. package/src/runtime/deadletter.ts +47 -0
  203. package/src/runtime/delivery-coordinator.ts +211 -0
  204. package/src/runtime/delta-conflict.ts +348 -0
  205. package/src/runtime/deterministic-ast.ts +161 -0
  206. package/src/runtime/diagnostic-export.ts +171 -0
  207. package/src/runtime/direct-run.ts +45 -0
  208. package/src/runtime/dwf-state-store.ts +102 -0
  209. package/src/runtime/dynamic-workflow-context.ts +1013 -0
  210. package/src/runtime/dynamic-workflow-runner.ts +325 -0
  211. package/src/runtime/effectiveness.ts +90 -0
  212. package/src/runtime/errors/crew-errors.ts +162 -0
  213. package/src/runtime/event-stream-bridge.ts +98 -0
  214. package/src/runtime/foreground-control.ts +193 -0
  215. package/src/runtime/foreground-watchdog.ts +132 -0
  216. package/src/runtime/global-worker-cap.ts +96 -0
  217. package/src/runtime/goal-achievement.ts +148 -0
  218. package/src/runtime/goal-evaluator.ts +365 -0
  219. package/src/runtime/goal-loop-runner.ts +825 -0
  220. package/src/runtime/goal-state-store.ts +219 -0
  221. package/src/runtime/green-contract.ts +55 -0
  222. package/src/runtime/group-join.ts +267 -0
  223. package/src/runtime/handoff-manager.ts +598 -0
  224. package/src/runtime/heartbeat-gradient.ts +36 -0
  225. package/src/runtime/heartbeat-watcher.ts +239 -0
  226. package/src/runtime/hidden-handoff.ts +407 -0
  227. package/src/runtime/important-line-classifier.ts +140 -0
  228. package/src/runtime/intercom-bridge.ts +187 -0
  229. package/src/runtime/iteration-hooks.ts +305 -0
  230. package/src/runtime/live-agent-control.ts +136 -0
  231. package/src/runtime/live-agent-manager.ts +704 -0
  232. package/src/runtime/live-control-realtime.ts +56 -0
  233. package/src/runtime/live-extension-bridge.ts +148 -0
  234. package/src/runtime/live-irc.ts +97 -0
  235. package/src/runtime/live-session-health.ts +108 -0
  236. package/src/runtime/live-session-runtime.ts +1146 -0
  237. package/src/runtime/loop-gates.ts +128 -0
  238. package/src/runtime/manifest-cache.ts +375 -0
  239. package/src/runtime/mcp-proxy.ts +105 -0
  240. package/src/runtime/metric-parser.ts +36 -0
  241. package/src/runtime/model-fallback.ts +409 -0
  242. package/src/runtime/model-resolver.ts +126 -0
  243. package/src/runtime/model-scope.ts +158 -0
  244. package/src/runtime/orphan-worker-registry.ts +465 -0
  245. package/src/runtime/output-validator.ts +202 -0
  246. package/src/runtime/overflow-recovery.ts +206 -0
  247. package/src/runtime/parallel-research.ts +68 -0
  248. package/src/runtime/parallel-utils.ts +160 -0
  249. package/src/runtime/parent-guard.ts +134 -0
  250. package/src/runtime/path-overlap.ts +150 -0
  251. package/src/runtime/peer-dep.ts +292 -0
  252. package/src/runtime/per-write-validator.ts +181 -0
  253. package/src/runtime/phase-progress.ts +217 -0
  254. package/src/runtime/phase-tracker.ts +385 -0
  255. package/src/runtime/pi-args.ts +655 -0
  256. package/src/runtime/pi-json-output.ts +170 -0
  257. package/src/runtime/pi-spawn.ts +273 -0
  258. package/src/runtime/pipeline-runner.ts +523 -0
  259. package/src/runtime/plan-templates.ts +201 -0
  260. package/src/runtime/policy-engine.ts +115 -0
  261. package/src/runtime/post-checks.ts +142 -0
  262. package/src/runtime/post-exit-stdio-guard.ts +109 -0
  263. package/src/runtime/process-lifecycle.ts +491 -0
  264. package/src/runtime/process-status.ts +154 -0
  265. package/src/runtime/progress-event-coalescer.ts +44 -0
  266. package/src/runtime/progress-tracker.ts +124 -0
  267. package/src/runtime/prose-compressor.ts +162 -0
  268. package/src/runtime/recovery-recipes.ts +203 -0
  269. package/src/runtime/replace.ts +570 -0
  270. package/src/runtime/resilient-edit.ts +153 -0
  271. package/src/runtime/result-extractor.ts +217 -0
  272. package/src/runtime/retry-executor.ts +109 -0
  273. package/src/runtime/retry-runner.ts +336 -0
  274. package/src/runtime/role-permission.ts +59 -0
  275. package/src/runtime/run-coalesced-task-group.ts +308 -0
  276. package/src/runtime/run-drift.ts +219 -0
  277. package/src/runtime/run-tracker.ts +116 -0
  278. package/src/runtime/run-worker.ts +78 -0
  279. package/src/runtime/runtime-policy.ts +29 -0
  280. package/src/runtime/runtime-resolver.ts +172 -0
  281. package/src/runtime/runtime-warmup.ts +160 -0
  282. package/src/runtime/scheduler.ts +353 -0
  283. package/src/runtime/semaphore.ts +141 -0
  284. package/src/runtime/sensitive-paths.ts +93 -0
  285. package/src/runtime/session-resources.ts +25 -0
  286. package/src/runtime/session-snapshot.ts +59 -0
  287. package/src/runtime/session-usage.ts +79 -0
  288. package/src/runtime/settings-store.ts +155 -0
  289. package/src/runtime/sidechain-output.ts +35 -0
  290. package/src/runtime/single-agent-compose.ts +84 -0
  291. package/src/runtime/skill-effectiveness.ts +524 -0
  292. package/src/runtime/skill-instructions.ts +394 -0
  293. package/src/runtime/stale-reconciler.ts +680 -0
  294. package/src/runtime/stream-preview.ts +184 -0
  295. package/src/runtime/streaming-output.ts +50 -0
  296. package/src/runtime/subagent-manager.ts +561 -0
  297. package/src/runtime/subprocess-tool-registry.ts +70 -0
  298. package/src/runtime/supervisor-contact.ts +64 -0
  299. package/src/runtime/task-display.ts +52 -0
  300. package/src/runtime/task-graph-scheduler.ts +227 -0
  301. package/src/runtime/task-graph.ts +290 -0
  302. package/src/runtime/task-health.ts +83 -0
  303. package/src/runtime/task-id.ts +161 -0
  304. package/src/runtime/task-output-context.ts +602 -0
  305. package/src/runtime/task-packet.ts +268 -0
  306. package/src/runtime/task-quality.ts +199 -0
  307. package/src/runtime/task-runner/capabilities.ts +78 -0
  308. package/src/runtime/task-runner/child-executor.ts +790 -0
  309. package/src/runtime/task-runner/context-retrieval.ts +163 -0
  310. package/src/runtime/task-runner/live-executor.ts +227 -0
  311. package/src/runtime/task-runner/output-splitter.ts +152 -0
  312. package/src/runtime/task-runner/post-execution.ts +480 -0
  313. package/src/runtime/task-runner/pre-execution.ts +365 -0
  314. package/src/runtime/task-runner/progress.ts +157 -0
  315. package/src/runtime/task-runner/prompt-builder.ts +278 -0
  316. package/src/runtime/task-runner/prompt-pipeline.ts +88 -0
  317. package/src/runtime/task-runner/result-utils.ts +16 -0
  318. package/src/runtime/task-runner/retrieval-orchestrator.ts +310 -0
  319. package/src/runtime/task-runner/run-projection.ts +125 -0
  320. package/src/runtime/task-runner/scaffold-executor.ts +25 -0
  321. package/src/runtime/task-runner/state-helpers.ts +176 -0
  322. package/src/runtime/task-runner/tail-read.ts +34 -0
  323. package/src/runtime/task-runner.ts +216 -0
  324. package/src/runtime/team-runner-artifacts.ts +13 -0
  325. package/src/runtime/team-runner.ts +2396 -0
  326. package/src/runtime/tool-output-pruner.ts +333 -0
  327. package/src/runtime/tool-progress.ts +278 -0
  328. package/src/runtime/usage-tracker.ts +73 -0
  329. package/src/runtime/verification-gates.ts +579 -0
  330. package/src/runtime/verification-integrity.ts +108 -0
  331. package/src/runtime/verification-worktree.ts +186 -0
  332. package/src/runtime/worker-heartbeat.ts +25 -0
  333. package/src/runtime/worker-startup.ts +83 -0
  334. package/src/runtime/workflow-state.ts +195 -0
  335. package/src/runtime/workspace-lock.ts +443 -0
  336. package/src/runtime/workspace-tree.ts +312 -0
  337. package/src/runtime/yield-handler.ts +224 -0
  338. package/src/runtime/zombie-scanner.ts +298 -0
  339. package/src/schema/config-schema.ts +327 -0
  340. package/src/schema/team-tool-schema.ts +492 -0
  341. package/src/schema/validation-types.ts +159 -0
  342. package/src/skills/discover-skills.ts +203 -0
  343. package/src/skills/skill-templates.ts +456 -0
  344. package/src/skills/validate.ts +285 -0
  345. package/src/state/active-run-registry.ts +439 -0
  346. package/src/state/artifact-store.ts +176 -0
  347. package/src/state/atomic-write.ts +970 -0
  348. package/src/state/blob-store.ts +308 -0
  349. package/src/state/contracts.ts +160 -0
  350. package/src/state/crew-init.ts +269 -0
  351. package/src/state/decision-ledger.ts +372 -0
  352. package/src/state/event-log-rotation.ts +399 -0
  353. package/src/state/event-log.ts +1465 -0
  354. package/src/state/event-reconstructor.ts +229 -0
  355. package/src/state/gitignore-manager.ts +46 -0
  356. package/src/state/health-store.ts +79 -0
  357. package/src/state/hook-instinct-bridge.ts +94 -0
  358. package/src/state/hook-integrations.ts +51 -0
  359. package/src/state/instinct-store.ts +275 -0
  360. package/src/state/jsonl-writer.ts +107 -0
  361. package/src/state/locks.ts +562 -0
  362. package/src/state/mailbox.ts +916 -0
  363. package/src/state/observation-store.ts +176 -0
  364. package/src/state/run-cache.ts +193 -0
  365. package/src/state/run-graph.ts +183 -0
  366. package/src/state/run-metrics.ts +165 -0
  367. package/src/state/schedule.ts +166 -0
  368. package/src/state/session-state-map.ts +51 -0
  369. package/src/state/state-store.ts +973 -0
  370. package/src/state/task-claims.ts +61 -0
  371. package/src/state/tiered-eval.ts +480 -0
  372. package/src/state/types-eval.ts +58 -0
  373. package/src/state/types.ts +447 -0
  374. package/src/state/usage.ts +138 -0
  375. package/src/state/worker-atomic-writer.ts +198 -0
  376. package/src/subagents/async-entry.ts +1 -0
  377. package/src/subagents/index.ts +3 -0
  378. package/src/subagents/live/control.ts +1 -0
  379. package/src/subagents/live/manager.ts +1 -0
  380. package/src/subagents/live/realtime.ts +1 -0
  381. package/src/subagents/live/session-runtime.ts +1 -0
  382. package/src/subagents/manager.ts +1 -0
  383. package/src/subagents/spawn.ts +1 -0
  384. package/src/teams/discover-teams.ts +187 -0
  385. package/src/teams/team-config.ts +27 -0
  386. package/src/teams/team-serializer.ts +38 -0
  387. package/src/tools/safe-bash-extension.ts +54 -0
  388. package/src/tools/safe-bash.ts +505 -0
  389. package/src/types/diff.d.ts +18 -0
  390. package/src/types/new-api-types.ts +35 -0
  391. package/src/ui/agent-management-overlay.ts +160 -0
  392. package/src/ui/card-colors.ts +139 -0
  393. package/src/ui/crew-footer.ts +102 -0
  394. package/src/ui/crew-select-list.ts +112 -0
  395. package/src/ui/dashboard-panes/agents-pane.ts +164 -0
  396. package/src/ui/dashboard-panes/cancellation-pane.ts +66 -0
  397. package/src/ui/dashboard-panes/capability-pane.ts +77 -0
  398. package/src/ui/dashboard-panes/health-pane.ts +31 -0
  399. package/src/ui/dashboard-panes/mailbox-pane.ts +35 -0
  400. package/src/ui/dashboard-panes/metrics-pane.ts +37 -0
  401. package/src/ui/dashboard-panes/progress-pane.ts +38 -0
  402. package/src/ui/dashboard-panes/transcript-pane.ts +10 -0
  403. package/src/ui/deploy-bundled-themes.ts +71 -0
  404. package/src/ui/dwf-phase-display.ts +151 -0
  405. package/src/ui/dynamic-border.ts +35 -0
  406. package/src/ui/format-helpers.ts +31 -0
  407. package/src/ui/heartbeat-aggregator.ts +74 -0
  408. package/src/ui/key-utils.ts +42 -0
  409. package/src/ui/keybinding-map.ts +248 -0
  410. package/src/ui/layout-primitives.ts +106 -0
  411. package/src/ui/live-conversation-overlay.ts +183 -0
  412. package/src/ui/live-duration.ts +55 -0
  413. package/src/ui/live-run-sidebar.ts +293 -0
  414. package/src/ui/loaders.ts +6 -0
  415. package/src/ui/mascot.ts +449 -0
  416. package/src/ui/overlays/agent-picker-overlay.ts +66 -0
  417. package/src/ui/overlays/confirm-overlay.ts +60 -0
  418. package/src/ui/overlays/help-overlay.ts +177 -0
  419. package/src/ui/overlays/mailbox-compose-overlay.ts +181 -0
  420. package/src/ui/overlays/mailbox-compose-preview.ts +78 -0
  421. package/src/ui/overlays/mailbox-detail-overlay.ts +155 -0
  422. package/src/ui/pi-ui-compat.ts +68 -0
  423. package/src/ui/powerbar-publisher.ts +459 -0
  424. package/src/ui/render-coalescer.ts +101 -0
  425. package/src/ui/render-diff.ts +160 -0
  426. package/src/ui/render-scheduler.ts +278 -0
  427. package/src/ui/run-action-dispatcher.ts +182 -0
  428. package/src/ui/run-dashboard.ts +910 -0
  429. package/src/ui/run-event-bus.ts +381 -0
  430. package/src/ui/run-snapshot-cache.ts +1057 -0
  431. package/src/ui/settings-overlay.ts +1067 -0
  432. package/src/ui/shared-overlay-scheduler.ts +96 -0
  433. package/src/ui/snapshot-types.ts +90 -0
  434. package/src/ui/spinner.ts +17 -0
  435. package/src/ui/status-colors.ts +143 -0
  436. package/src/ui/syntax-highlight.ts +136 -0
  437. package/src/ui/terminal-status.ts +269 -0
  438. package/src/ui/theme-adapter.ts +271 -0
  439. package/src/ui/theme-discovery.ts +199 -0
  440. package/src/ui/tool-progress-formatter.ts +93 -0
  441. package/src/ui/tool-renderers/brief-mode.ts +225 -0
  442. package/src/ui/tool-renderers/index.ts +726 -0
  443. package/src/ui/transcript-cache.ts +128 -0
  444. package/src/ui/transcript-entries.ts +256 -0
  445. package/src/ui/transcript-viewer.ts +424 -0
  446. package/src/ui/widget/index.ts +418 -0
  447. package/src/ui/widget/widget-formatters.ts +182 -0
  448. package/src/ui/widget/widget-model.ts +110 -0
  449. package/src/ui/widget/widget-renderer.ts +182 -0
  450. package/src/ui/widget/widget-types.ts +36 -0
  451. package/src/utils/bm25-search.ts +235 -0
  452. package/src/utils/completion-dedupe.ts +63 -0
  453. package/src/utils/conflict-detect.ts +668 -0
  454. package/src/utils/env-allowlist.ts +30 -0
  455. package/src/utils/env-filter.ts +155 -0
  456. package/src/utils/file-coalescer.ts +98 -0
  457. package/src/utils/fingerprint.ts +180 -0
  458. package/src/utils/frontmatter.ts +70 -0
  459. package/src/utils/fs-watch.ts +37 -0
  460. package/src/utils/gh-protocol.ts +556 -0
  461. package/src/utils/git.ts +260 -0
  462. package/src/utils/guards.ts +107 -0
  463. package/src/utils/ids.ts +24 -0
  464. package/src/utils/incremental-reader.ts +220 -0
  465. package/src/utils/internal-error.ts +10 -0
  466. package/src/utils/names.ts +36 -0
  467. package/src/utils/ndjson.ts +115 -0
  468. package/src/utils/paths.ts +227 -0
  469. package/src/utils/project-detector.ts +160 -0
  470. package/src/utils/redaction.ts +370 -0
  471. package/src/utils/resolve-shell.ts +36 -0
  472. package/src/utils/run-watcher-registry.ts +152 -0
  473. package/src/utils/safe-paths.ts +393 -0
  474. package/src/utils/scan-cache.ts +144 -0
  475. package/src/utils/session-utils.ts +110 -0
  476. package/src/utils/sleep.ts +54 -0
  477. package/src/utils/socket-path.ts +164 -0
  478. package/src/utils/sse-parser.ts +131 -0
  479. package/src/utils/task-name-generator.ts +337 -0
  480. package/src/utils/timings.ts +33 -0
  481. package/src/utils/token-counter.ts +200 -0
  482. package/src/utils/visual.ts +207 -0
  483. package/src/workflows/cost-estimator.ts +34 -0
  484. package/src/workflows/discover-workflows.ts +308 -0
  485. package/src/workflows/intermediate-store.ts +166 -0
  486. package/src/workflows/preflight-validator.ts +168 -0
  487. package/src/workflows/topology-analyzer.ts +203 -0
  488. package/src/workflows/validate-workflow.ts +50 -0
  489. package/src/workflows/workflow-config.ts +75 -0
  490. package/src/workflows/workflow-serializer.ts +32 -0
  491. package/src/worktree/branch-freshness.ts +112 -0
  492. package/src/worktree/cleanup.ts +341 -0
  493. package/src/worktree/worktree-manager.ts +1204 -0
@@ -0,0 +1,291 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ import { atomicWriteFile } from "../state/atomic-write.ts";
4
+ import { logInternalError } from "../utils/internal-error.ts";
5
+ import { projectCrewRoot } from "../utils/paths.ts";
6
+ import { assertSafePathId } from "../utils/safe-paths.ts";
7
+
8
+ export interface Checkpoint {
9
+ runId: string;
10
+ taskId: string;
11
+ step: number;
12
+ context: string;
13
+ progress: string;
14
+ savedAt: number;
15
+ agentId: string;
16
+ agentModel?: string;
17
+ }
18
+
19
+ export interface CheckpointStore {
20
+ save(checkpoint: Checkpoint): void;
21
+ load(runId: string, taskId: string): Checkpoint | null;
22
+ delete(runId: string, taskId: string): void;
23
+ list(runId: string): Checkpoint[];
24
+ hasCheckpoint(runId: string, taskId: string): boolean;
25
+ }
26
+
27
+ interface CheckpointEntry {
28
+ checkpoints: Record<string, Checkpoint>;
29
+ }
30
+
31
+ /**
32
+ * File-based checkpoint store.
33
+ * Saves checkpoints as JSON files in .crew/state/runs/<runId>/checkpoints/
34
+ */
35
+ export class FileCheckpointStore implements CheckpointStore {
36
+ private readonly stateRoot: string;
37
+
38
+ constructor(stateRoot: string) {
39
+ this.stateRoot = stateRoot;
40
+ }
41
+
42
+ private checkpointDir(): string {
43
+ return path.join(this.stateRoot, "checkpoints");
44
+ }
45
+
46
+ private checkpointPath(taskId: string): string {
47
+ return path.join(this.checkpointDir(), `${taskId}.json`);
48
+ }
49
+
50
+ private ensureDir(): void {
51
+ const dir = this.checkpointDir();
52
+ if (!fs.existsSync(dir)) {
53
+ fs.mkdirSync(dir, { recursive: true });
54
+ }
55
+ }
56
+
57
+ save(checkpoint: Checkpoint): void {
58
+ // Validate taskId to prevent path traversal: the taskId is used to
59
+ // build a file path under this.checkpointDir(). Without validation, a
60
+ // malicious or buggy taskId like "../../../etc/passwd" could escape
61
+ // the checkpoints directory.
62
+ assertSafePathId("taskId", checkpoint.taskId);
63
+ this.ensureDir();
64
+ const p = this.checkpointPath(checkpoint.taskId);
65
+ // Atomic write: write to temp file first, then rename, then fsync parent.
66
+ // This guarantees either the old file or the new file, never a partial
67
+ // write, even on network filesystems or certain journal modes.
68
+ //
69
+ // Round 22 (BUG 1): the temp filename MUST be unique per save call.
70
+ // Previously a fixed '.tmp.checkpoint' was shared across ALL concurrent
71
+ // saves; pi-crew's multi-process architecture (main + detached background
72
+ // workers each checkpointing their own tasks) made this realistic: two
73
+ // processes writing '.tmp.checkpoint' at once → one's rename picks up the
74
+ // other's data (silent corruption) and the second rename hits ENOENT
75
+ // (silent data loss). Including taskId + pid + timestamp guarantees
76
+ // uniqueness across processes and across tasks.
77
+ // atomicWriteFile handles temp + write + fsync(data) + rename + fsync(parent)
78
+ // with a UUID-based unique temp name (cross-process safe). The previous
79
+ // hand-rolled version missed data-fsync (ST-5), risking empty/stale content
80
+ // after power loss.
81
+ atomicWriteFile(p, JSON.stringify(checkpoint, null, 2));
82
+ }
83
+
84
+ load(runId: string, taskId: string): Checkpoint | null {
85
+ assertSafePathId("taskId", taskId);
86
+ const p = this.checkpointPath(taskId);
87
+ if (!fs.existsSync(p)) return null;
88
+
89
+ try {
90
+ const data = JSON.parse(fs.readFileSync(p, "utf-8")) as Checkpoint;
91
+ // Verify it's for the correct run
92
+ if (data.runId !== runId) return null;
93
+ return data;
94
+ } catch {
95
+ // File existed but JSON was corrupt — log and rename for later inspection
96
+ logInternalError("checkpoint-load", new Error("JSON parse failed"), `file=${p}`);
97
+ try {
98
+ fs.renameSync(p, `${p}.corrupt.${Date.now()}`);
99
+ } catch {
100
+ // Best effort — ignore rename failure
101
+ }
102
+ return null;
103
+ }
104
+ }
105
+
106
+ delete(runId: string, taskId: string): void {
107
+ assertSafePathId("taskId", taskId);
108
+ const p = this.checkpointPath(taskId);
109
+ if (fs.existsSync(p)) {
110
+ try {
111
+ const data = JSON.parse(fs.readFileSync(p, "utf-8")) as Checkpoint;
112
+ if (data.runId === runId) {
113
+ fs.unlinkSync(p);
114
+ }
115
+ } catch {
116
+ // File existed but couldn't read — delete it anyway
117
+ try {
118
+ fs.unlinkSync(p);
119
+ } catch {
120
+ /* ignore */
121
+ }
122
+ }
123
+ }
124
+ }
125
+
126
+ list(runId: string): Checkpoint[] {
127
+ const dir = this.checkpointDir();
128
+ if (!fs.existsSync(dir)) return [];
129
+
130
+ return fs
131
+ .readdirSync(dir)
132
+ .filter((f) => f.endsWith(".json"))
133
+ .map((f) => {
134
+ try {
135
+ return JSON.parse(fs.readFileSync(path.join(dir, f), "utf-8")) as Checkpoint;
136
+ } catch {
137
+ return null;
138
+ }
139
+ })
140
+ .filter((c): c is Checkpoint => c !== null && c.runId === runId);
141
+ }
142
+
143
+ hasCheckpoint(runId: string, taskId: string): boolean {
144
+ return this.load(runId, taskId) !== null;
145
+ }
146
+ }
147
+
148
+ const MAX_STORES = 100;
149
+ const _stores = new Map<string, FileCheckpointStore>();
150
+
151
+ /**
152
+ * Get checkpoint store for a run's state root.
153
+ * Uses LRU eviction when the store exceeds MAX_STORES entries.
154
+ */
155
+ export function getCheckpointStore(stateRoot: string): CheckpointStore {
156
+ if (!_stores.has(stateRoot)) {
157
+ if (_stores.size >= MAX_STORES) {
158
+ // Evict the oldest entry (first in insertion order)
159
+ const oldestKey = _stores.keys().next().value;
160
+ if (oldestKey !== undefined) {
161
+ _stores.delete(oldestKey);
162
+ }
163
+ }
164
+ _stores.set(stateRoot, new FileCheckpointStore(stateRoot));
165
+ }
166
+ return _stores.get(stateRoot)!;
167
+ }
168
+
169
+ /**
170
+ * Clear all checkpoint stores (for testing).
171
+ */
172
+ export function clearCheckpointStores(): void {
173
+ _stores.clear();
174
+ }
175
+
176
+ /**
177
+ * Save a checkpoint during agent execution.
178
+ */
179
+ export function saveCheckpoint(
180
+ runId: string,
181
+ taskId: string,
182
+ step: number,
183
+ context: string,
184
+ progress: string,
185
+ agentId: string,
186
+ agentModel?: string,
187
+ cwd?: string,
188
+ ): void {
189
+ // Validate both runId and taskId to prevent path traversal: these are
190
+ // used to build the file path under <crewRoot>/state/runs/<runId>/checkpoints/<taskId>.json.
191
+ assertSafePathId("runId", runId);
192
+ assertSafePathId("taskId", taskId);
193
+ const checkpoint: Checkpoint = {
194
+ runId,
195
+ taskId,
196
+ step,
197
+ context,
198
+ progress,
199
+ savedAt: Date.now(),
200
+ agentId,
201
+ agentModel,
202
+ };
203
+
204
+ // State root is parent of checkpoints dir. Use projectCrewRoot() so the
205
+ // path lands in .pi/teams/state/runs/ for .pi-based projects (issue #29).
206
+ const stateRoot = path.join(projectCrewRoot(cwd ?? process.cwd()), "state", "runs", runId);
207
+ const store = getCheckpointStore(stateRoot);
208
+ store.save(checkpoint);
209
+ }
210
+
211
+ /**
212
+ * Load a checkpoint for resuming.
213
+ */
214
+ export function loadCheckpoint(runId: string, taskId: string, cwd?: string): Checkpoint | null {
215
+ assertSafePathId("runId", runId);
216
+ assertSafePathId("taskId", taskId);
217
+ const stateRoot = path.join(projectCrewRoot(cwd ?? process.cwd()), "state", "runs", runId);
218
+ const store = getCheckpointStore(stateRoot);
219
+ return store.load(runId, taskId);
220
+ }
221
+
222
+ /**
223
+ * Delete a checkpoint after successful completion.
224
+ */
225
+ export function clearCheckpoint(runId: string, taskId: string, cwd?: string): void {
226
+ assertSafePathId("runId", runId);
227
+ assertSafePathId("taskId", taskId);
228
+ const stateRoot = path.join(projectCrewRoot(cwd ?? process.cwd()), "state", "runs", runId);
229
+ const store = getCheckpointStore(stateRoot);
230
+ store.delete(runId, taskId);
231
+ }
232
+
233
+ /**
234
+ * Check if a checkpoint exists for a task.
235
+ */
236
+ export function hasCheckpoint(runId: string, taskId: string, cwd?: string): boolean {
237
+ assertSafePathId("runId", runId);
238
+ assertSafePathId("taskId", taskId);
239
+ const stateRoot = path.join(projectCrewRoot(cwd ?? process.cwd()), "state", "runs", runId);
240
+ const store = getCheckpointStore(stateRoot);
241
+ return store.hasCheckpoint(runId, taskId);
242
+ }
243
+
244
+ /**
245
+ * List all checkpoints for a run.
246
+ */
247
+ export function listCheckpoints(runId: string, cwd?: string): Checkpoint[] {
248
+ assertSafePathId("runId", runId);
249
+ const stateRoot = path.join(projectCrewRoot(cwd ?? process.cwd()), "state", "runs", runId);
250
+ const store = getCheckpointStore(stateRoot);
251
+ return store.list(runId);
252
+ }
253
+
254
+ /**
255
+ * Format a checkpoint for display.
256
+ */
257
+ export function formatCheckpoint(checkpoint: Checkpoint): string {
258
+ return [
259
+ `## Checkpoint: ${checkpoint.taskId}`,
260
+ "",
261
+ `**Agent:** ${checkpoint.agentId}`,
262
+ checkpoint.agentModel ? `**Model:** ${checkpoint.agentModel}` : "",
263
+ "",
264
+ `**Progress:** ${checkpoint.progress}`,
265
+ "",
266
+ `**Step:** ${checkpoint.step}`,
267
+ `**Saved:** ${new Date(checkpoint.savedAt).toISOString()}`,
268
+ "",
269
+ `**Context:** ${checkpoint.context.slice(0, 300)}${checkpoint.context.length > 300 ? "..." : ""}`,
270
+ ]
271
+ .filter(Boolean)
272
+ .join("\n");
273
+ }
274
+
275
+ /**
276
+ * Format all checkpoints for a run.
277
+ */
278
+ export function formatAllCheckpoints(runId: string, cwd?: string): string {
279
+ const checkpoints = listCheckpoints(runId, cwd);
280
+ if (checkpoints.length === 0) {
281
+ return `No checkpoints found for run ${runId}`;
282
+ }
283
+
284
+ return [
285
+ `# Checkpoints: ${runId}`,
286
+ "",
287
+ ...checkpoints.map((cp, i) => `${i + 1}. **${cp.taskId}** — ${cp.progress} (${new Date(cp.savedAt).toLocaleString()})`),
288
+ "",
289
+ `Use \`team action='resume' runId=${runId} taskId=<taskId>\` to resume.`,
290
+ ].join("\n");
291
+ }
@@ -0,0 +1,42 @@
1
+ /**
2
+ * child-pi-constants.ts — Shared timing and capture constants for child-pi runtime.
3
+ *
4
+ * Extracted from child-pi.ts (H-7 decomposition, step 3). Zero behavior change.
5
+ *
6
+ * These constants are shared between runChildPi (in child-pi.ts) and the kill
7
+ * helpers (in child-pi-kill.ts). Centralizing them here avoids circular imports
8
+ * and makes the timing budget configurable from one place.
9
+ */
10
+
11
+ import { DEFAULT_CHILD_PI } from "../config/defaults.ts";
12
+
13
+ /** Post-exit window during which stdio is guarded against late writes. */
14
+ export const POST_EXIT_STDIO_GUARD_MS = DEFAULT_CHILD_PI.postExitStdioGuardMs;
15
+
16
+ /** Maximum time to wait for a final assistant event after the last stdout byte. */
17
+ export const FINAL_DRAIN_MS = DEFAULT_CHILD_PI.finalDrainMs;
18
+
19
+ /** Time after SIGTERM to escalate to SIGKILL. */
20
+ export const HARD_KILL_MS = DEFAULT_CHILD_PI.hardKillMs;
21
+
22
+ /** Maximum time with no output before the child is considered unresponsive. */
23
+ export const RESPONSE_TIMEOUT_MS = DEFAULT_CHILD_PI.responseTimeoutMs;
24
+
25
+ /**
26
+ * Maximum size (bytes) for the ChildPiLineObserver's line accumulation buffer.
27
+ * When exceeded, the buffer is force-flushed to prevent unbounded memory growth
28
+ * from chatty child processes that produce output without newlines.
29
+ */
30
+ export const MAX_LINE_BUFFER_BYTES = 1024 * 1024; // 1 MB
31
+
32
+ /** Maximum characters for assistant text fragments in compacted events. */
33
+ export const MAX_ASSISTANT_TEXT_CHARS = DEFAULT_CHILD_PI.maxAssistantTextChars;
34
+
35
+ /** Maximum characters for tool-result fragments in compacted events. */
36
+ export const MAX_TOOL_RESULT_CHARS = DEFAULT_CHILD_PI.maxToolResultChars;
37
+
38
+ /** Maximum characters for tool-input fragments in compacted events. */
39
+ export const MAX_TOOL_INPUT_CHARS = DEFAULT_CHILD_PI.maxToolInputChars;
40
+
41
+ /** Maximum characters for general compactable content (used by TruncationStage). */
42
+ export const MAX_COMPACT_CONTENT_CHARS = DEFAULT_CHILD_PI.maxCompactContentChars;
@@ -0,0 +1,180 @@
1
+ /**
2
+ * child-pi-kill.ts — Kill/escape path for child Pi worker processes.
3
+ *
4
+ * Extracted from child-pi.ts (H-7 decomposition, step 2). Zero behavior change.
5
+ *
6
+ * Responsibilities:
7
+ * - appendBoundedTail(): bounded concatenation with truncation marker.
8
+ * - killProcessPid(): send SIGTERM → SIGKILL escalation, with Windows taskkill fallback.
9
+ * - killProcessTree(): log+kill a child by pid, optionally attach exit listener to clear timer.
10
+ * - terminateActiveChildPiProcesses(): kill all known active children.
11
+ * - registerActiveChild()/unregisterActiveChild(): book-keeping for the active-children Map.
12
+ * - Periodic zombie reaper (setInterval) that drops dead entries.
13
+ *
14
+ * The activeChildrenMap and hardKillTimers Map are kept here (not re-exported)
15
+ * because they are tightly coupled to the kill lifecycle.
16
+ */
17
+
18
+ import type { ChildProcess } from "node:child_process";
19
+ import { spawn } from "node:child_process";
20
+ import { DEFAULT_CHILD_PI } from "../config/defaults.ts";
21
+ import { logInternalError } from "../utils/internal-error.ts";
22
+ import { HARD_KILL_MS } from "./child-pi-constants.ts";
23
+ import { TailCaptureStage } from "./compact-stages/tail-capture-stage.ts";
24
+
25
+ const MAX_CAPTURE_BYTES = DEFAULT_CHILD_PI.maxCaptureBytes;
26
+
27
+ // Active children bookkeeping. Mutated by registerActiveChild/unregisterActiveChild.
28
+ const activeChildProcesses = new Map<number, ChildProcess>();
29
+ const childHardKillTimers = new Map<number, NodeJS.Timeout>();
30
+
31
+ // Periodic cleanup of dead child process entries to prevent memory leaks.
32
+ // If a child process never emits exit/close (zombie), the entry would leak.
33
+ setInterval(() => {
34
+ for (const [pid, child] of activeChildProcesses) {
35
+ try {
36
+ process.kill(pid, 0); // Throws ESRCH if dead
37
+ } catch {
38
+ activeChildProcesses.delete(pid);
39
+ }
40
+ }
41
+ }, 60_000).unref();
42
+
43
+ /** Register a newly-spawned child so it can be tracked + killed on shutdown. */
44
+ export function registerActiveChild(pid: number, child: ChildProcess): void {
45
+ activeChildProcesses.set(pid, child);
46
+ }
47
+
48
+ /** Remove a child from the active set once it has exited (or before kill). */
49
+ export function unregisterActiveChild(pid: number): void {
50
+ activeChildProcesses.delete(pid);
51
+ }
52
+
53
+ /** Clear the SIGKILL escalation timer for a pid (called on early exit). */
54
+ export function clearHardKillTimer(pid: number | undefined): void {
55
+ if (pid === undefined) return;
56
+ const timer = childHardKillTimers.get(pid);
57
+ if (timer) {
58
+ clearTimeout(timer);
59
+ childHardKillTimers.delete(pid);
60
+ }
61
+ }
62
+
63
+ /**
64
+ * Append `chunk` to `current`, keeping the result within `maxBytes`. When the
65
+ * cap is exceeded, returns the tail (most recent bytes) prefixed with a marker
66
+ * showing how much was dropped. Delegates to TailCaptureStage so the logic
67
+ * stays consistent with the rest of the compaction pipeline.
68
+ */
69
+ export function appendBoundedTail(current: string, chunk: string, maxBytes = MAX_CAPTURE_BYTES): string {
70
+ return new TailCaptureStage({
71
+ maxBytes,
72
+ marker: `[pi-crew captured output truncated to last ${Math.round(maxBytes / 1024)} KiB]`,
73
+ }).apply(current + chunk);
74
+ }
75
+
76
+ function spawnTaskkillSafe(pid: number): void {
77
+ // B6: spawn taskkill and attach an 'error' listener. spawn() emits ENOENT/EACCES
78
+ // via the 'error' event — unhandled these would become uncaughtException and
79
+ // can crash the parent as an uncaught exception. taskkill is a standard Windows
80
+ // utility and rarely fails to spawn, but be defensive.
81
+ try {
82
+ const taskkillChild = spawn("taskkill", ["/pid", String(pid), "/t", "/f"], {
83
+ stdio: "ignore",
84
+ detached: false,
85
+ });
86
+ taskkillChild.on("error", (err) => {
87
+ logInternalError("child-pi.taskkill-spawn-error", err instanceof Error ? err : new Error(String(err)), `pid=${pid}`);
88
+ });
89
+ taskkillChild.unref();
90
+ } catch (error) {
91
+ logInternalError("child-pi.taskkill-sync-error", error instanceof Error ? error : new Error(String(error)), `pid=${pid}`);
92
+ }
93
+ }
94
+
95
+ export function killProcessPid(pid: number): void {
96
+ if (!Number.isInteger(pid) || pid <= 0) return;
97
+ try {
98
+ if (process.platform === "win32") {
99
+ // 3.8: Windows path uses taskkill /T /F (force kill the entire tree).
100
+ // taskkill itself can silently fail (PID gone, permission denied, etc.)
101
+ // so verify after 2s and log a warning if the process is still alive.
102
+ spawnTaskkillSafe(pid);
103
+ const verifyTimer = setTimeout(() => {
104
+ try {
105
+ process.kill(pid, 0); // throws ESRCH when dead
106
+ // Still alive — log and retry once.
107
+ logInternalError(
108
+ "child-pi.taskkill-stuck",
109
+ new Error(`process ${pid} still alive 2s after taskkill /T /F; retrying`),
110
+ `pid=${pid}`,
111
+ "error",
112
+ );
113
+ try {
114
+ spawnTaskkillSafe(pid);
115
+ } catch {
116
+ /* best-effort */
117
+ }
118
+ } catch {
119
+ // ESRCH or EPERM — process is gone. OK.
120
+ }
121
+ }, 2000);
122
+ verifyTimer.unref();
123
+ return;
124
+ }
125
+ try {
126
+ process.kill(-pid, "SIGTERM");
127
+ } catch (error) {
128
+ logInternalError("child-pi.sigterm", error, `pid=${pid}`);
129
+ try {
130
+ process.kill(pid, "SIGTERM");
131
+ } catch (fallbackError) {
132
+ logInternalError("child-pi.sigterm-absolute", fallbackError, `pid=${pid}`);
133
+ }
134
+ }
135
+ clearHardKillTimer(pid);
136
+ const hardKillTimer = setTimeout(() => {
137
+ try {
138
+ process.kill(-pid, "SIGKILL");
139
+ } catch (error) {
140
+ logInternalError("child-pi.sigkill", error, `pid=${pid}`);
141
+ try {
142
+ process.kill(pid, "SIGKILL");
143
+ } catch (fallbackError) {
144
+ logInternalError("child-pi.sigkill-absolute", fallbackError, `pid=${pid}`);
145
+ }
146
+ }
147
+ childHardKillTimers.delete(pid);
148
+ }, HARD_KILL_MS);
149
+ hardKillTimer.unref();
150
+ childHardKillTimers.set(pid, hardKillTimer);
151
+ } catch (error) {
152
+ logInternalError("child-pi.kill-process-pid", error, `pid=${pid}`);
153
+ }
154
+ }
155
+
156
+ export function killProcessTree(pid: number | undefined, child?: ChildProcess): void {
157
+ // Phase-0 diagnostic (HB-003a): capture who invoked killProcessTree so the
158
+ // exit-null race has a provenance trail. .stack is best-effort (may be undefined
159
+ // under deep async), so we take a snapshot lazily.
160
+ try {
161
+ const callerStack = new Error("killProcessTree caller").stack ?? "(no stack)";
162
+ logInternalError(
163
+ "child-pi.kill-process-tree-invoked",
164
+ new Error(`pid=${pid} called from:\n${callerStack.split("\n").slice(0, 8).join("\n")}`),
165
+ `pid=${pid}`,
166
+ );
167
+ } catch {
168
+ /* diagnostic best-effort */
169
+ }
170
+ if (!pid || !Number.isInteger(pid) || pid <= 0) return;
171
+ if (child && child.exitCode !== null) return;
172
+ killProcessPid(pid);
173
+ child?.once("exit", () => clearHardKillTimer(pid));
174
+ }
175
+
176
+ export function terminateActiveChildPiProcesses(): number {
177
+ const entries = [...activeChildProcesses.entries()];
178
+ for (const [pid, child] of entries) killProcessTree(pid, child);
179
+ return entries.length;
180
+ }
@@ -0,0 +1,68 @@
1
+ // 2.6 — Child-pi warm pool skeleton (ADR 0008 Proposed).
2
+ //
3
+ // This module exposes the pool interface that child-pi.ts will consume
4
+ // once Pi gains the wait-for-prompt handshake protocol. Until the Pi-side
5
+ // support lands, the pool is permanently disabled (size=0): callers always
6
+ // receive null from `acquirePooledChild` and fall through to the regular
7
+ // spawn path. This keeps the integration point ready without altering
8
+ // production behaviour.
9
+ //
10
+ // Enabling a real pool requires three pieces in order:
11
+ //
12
+ // 1. Pi runtime: respond to a `PI_CREW_POOL_HEALTH=1` ping on stdin and
13
+ // block in a "wait-for-prompt" state until the parent writes a real
14
+ // prompt. Pi does not currently implement this.
15
+ // 2. This module: replace the `acquirePooledChild` returning null with
16
+ // an actual pool that spawn-and-park N processes.
17
+ // 3. child-pi.ts: prefer pooled children on `runChildPi` entry; fall
18
+ // back to fresh spawn on miss.
19
+ //
20
+ // Disabled by default; opt-in via `runtime.warmPool.size > 0` config or
21
+ // the `PI_CREW_WARM_POOL_SIZE` env var.
22
+ import type { ChildProcess } from "node:child_process";
23
+
24
+ export interface WarmPoolOptions {
25
+ /** Number of warm processes to maintain. 0 disables the pool. */
26
+ size: number;
27
+ /** Drop pooled processes that have been idle longer than this. */
28
+ maxIdleMs: number;
29
+ }
30
+
31
+ export const DEFAULT_WARM_POOL_OPTIONS: WarmPoolOptions = {
32
+ size: 0,
33
+ maxIdleMs: 5 * 60_000,
34
+ };
35
+
36
+ /** Resolve the effective pool size from env / config / defaults. */
37
+ export function resolveWarmPoolSize(env: NodeJS.ProcessEnv = process.env, configured?: number): number {
38
+ const fromEnv = Number.parseInt(env.PI_CREW_WARM_POOL_SIZE ?? "", 10);
39
+ if (Number.isFinite(fromEnv) && fromEnv >= 0) return fromEnv;
40
+ if (typeof configured === "number" && Number.isFinite(configured) && configured >= 0) return configured;
41
+ return DEFAULT_WARM_POOL_OPTIONS.size;
42
+ }
43
+
44
+ /**
45
+ * Try to acquire a parked child from the pool. Returns null when the pool
46
+ * is disabled or empty; caller should spawn a fresh child instead.
47
+ *
48
+ * Skeleton — currently always returns null. See module docstring.
49
+ */
50
+ export function acquirePooledChild(_options: Partial<WarmPoolOptions> = {}): ChildProcess | null {
51
+ return null;
52
+ }
53
+
54
+ /**
55
+ * Mark a pooled child as done. Pool processes are single-use: this terminates
56
+ * the child rather than returning it to the pool, because state contamination
57
+ * across runs would be unsafe (file handles, env mutations, mounted FDs).
58
+ *
59
+ * Skeleton — currently a no-op since acquirePooledChild never returns a child.
60
+ */
61
+ export function releasePooledChild(_child: ChildProcess | null | undefined): void {
62
+ // no-op while the pool is disabled
63
+ }
64
+
65
+ /** Drain and terminate every parked child. Call on cleanupRuntime. */
66
+ export function disposeWarmPool(): void {
67
+ // no-op while the pool is disabled
68
+ }