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,140 @@
1
+ /**
2
+ * Important-Line Classifier (P0-B) — scan middle slice of a truncated value
3
+ * for diagnostic lines worth preserving between head and tail.
4
+ *
5
+ * Ported from Hypa's `ImportantLineClassifier.cs` (5 regexes) and the
6
+ * middle-scanning portion of `Stages/TruncationStage.cs:24-46`, adapted to TS
7
+ * (no `[GeneratedRegex]` AOT) and to pi-crew's head(75%)/tail(25%) split.
8
+ *
9
+ * Design rationale:
10
+ * - Patterns are intentionally OVER-INCLUSIVE. False positives preserve
11
+ * harmless lines; false negatives drop critical diagnostics, which is
12
+ * unacceptable (the whole point of this module). Hypa uses the same
13
+ * over-inclusive design.
14
+ * - Patterns are evaluated against a WHOLE line, not against the raw
15
+ * truncated slice, so a match at a line boundary is reliable.
16
+ * - The `splitWithImportantLines` helper performs the head/tail split AND
17
+ * greedily picks whole important lines from the middle that fit inside
18
+ * `slackFactor * maxChars` (default 15% slack). Callers compose their own
19
+ * marker using the returned parts — keeping `compactString` (marker
20
+ * "compacted ... chars, head+tail preserved") and `readIfSmall` (marker
21
+ * "truncated ... bytes, head+tail preserved") backward-compatible when no
22
+ * important lines are present.
23
+ */
24
+
25
+ /** Diagnostic patterns. Anchored where safe to avoid matching noise. */
26
+ export const IMPORTANT_LINE_PATTERNS: readonly RegExp[] = [
27
+ // error keywords — NOTE: "warning" is intentionally excluded here; it has
28
+ // its own case-sensitive pattern below so that the common prose word
29
+ // "warning" does not over-match. (Hypa does the same split.)
30
+ /\b(error|failed|exception|fatal|panic)\b/i,
31
+ // file:line diagnostic — `child-pi.ts:383:`, `App.tsx:42:`
32
+ /\w+\.\w+:\d+:/,
33
+ // HTTP 4xx / 5xx — bounded so it does not match phone numbers etc.
34
+ /\b[45]\d{2}\b/,
35
+ // k8s / linter "Warning" event (case-sensitive so prose is not matched)
36
+ /\bWarning\b/,
37
+ // compiler / linter diagnostic id — `TS2304`, `CS0246`, `ES1234`
38
+ /\b[A-Z]{2,4}\d{3,5}\b/,
39
+ ];
40
+
41
+ /** True iff `line` matches at least one important-line pattern. */
42
+ export function isImportantLine(line: string): boolean {
43
+ if (!line) return false;
44
+ for (const pattern of IMPORTANT_LINE_PATTERNS) {
45
+ if (pattern.test(line)) return true;
46
+ }
47
+ return false;
48
+ }
49
+
50
+ /**
51
+ * Extract up to `maxLines` important lines from `text`. Lines are split on
52
+ * `\n` (also handles `\r\n`). Order preserved; duplicates kept (callers may
53
+ * want to see the same diagnostic twice if it appears twice — that often
54
+ * signals a recurring failure).
55
+ */
56
+ export function extractImportantLines(text: string, maxLines = 30): string[] {
57
+ if (!text || maxLines <= 0) return [];
58
+ const out: string[] = [];
59
+ for (const line of text.split(/\r?\n/)) {
60
+ if (out.length >= maxLines) break;
61
+ if (isImportantLine(line)) out.push(line);
62
+ }
63
+ return out;
64
+ }
65
+
66
+ export interface TruncationSplit {
67
+ /** The first 75% of the value (by char count), verbatim. */
68
+ head: string;
69
+ /** The last 25% of the value (by char count), verbatim. */
70
+ tail: string;
71
+ /**
72
+ * Important lines from the middle slice, greedily picked (whole lines) so
73
+ * the joined length fits inside `slackFactor * maxChars`. Empty when
74
+ * `preserveImportant` is false OR no important lines are present OR none
75
+ * fit the slack budget.
76
+ */
77
+ importantLines: string[];
78
+ /** `value.length - maxChars` — chars dropped if no important lines preserved. */
79
+ baseDropped: number;
80
+ }
81
+
82
+ export interface SplitOptions {
83
+ /** When false, important-line scanning is skipped (assistant-text mode). */
84
+ preserveImportant?: boolean;
85
+ /** Hard cap on candidate lines before slack-budget selection. Default 30. */
86
+ maxImportantLines?: number;
87
+ /** Fraction of `maxChars` available for important-line content. Default 0.15. */
88
+ slackFactor?: number;
89
+ }
90
+
91
+ /**
92
+ * Split `value` into head + important-middle + tail, returning the parts.
93
+ * The caller is responsible for composing the final result (marker + glue)
94
+ * because the marker wording differs between `compactString` and
95
+ * `readIfSmall`.
96
+ *
97
+ * When no important lines are picked, the returned `importantLines` is `[]`
98
+ * and the marker wording stays bit-identical to the pre-P0-B format.
99
+ */
100
+ export function splitWithImportantLines(value: string, maxChars: number, opts: SplitOptions = {}): TruncationSplit {
101
+ if (value.length <= maxChars) {
102
+ return { head: value, tail: "", importantLines: [], baseDropped: 0 };
103
+ }
104
+ const headLen = Math.floor(maxChars * 0.75);
105
+ const tailLen = maxChars - headLen;
106
+ const head = value.slice(0, headLen);
107
+ const tail = value.slice(value.length - tailLen);
108
+
109
+ if (opts.preserveImportant === false) {
110
+ return {
111
+ head,
112
+ tail,
113
+ importantLines: [],
114
+ baseDropped: value.length - maxChars,
115
+ };
116
+ }
117
+
118
+ const slackFactor = opts.slackFactor ?? 0.15;
119
+ const slackChars = Math.max(0, Math.floor(maxChars * slackFactor));
120
+ const maxCandidates = opts.maxImportantLines ?? 30;
121
+ const middle = value.slice(headLen, value.length - tailLen);
122
+ const candidates = extractImportantLines(middle, maxCandidates);
123
+
124
+ // Greedily pick whole lines that fit in the slack budget.
125
+ const chosen: string[] = [];
126
+ let used = 0;
127
+ for (const line of candidates) {
128
+ const addLen = (chosen.length > 0 ? 1 : 0) + line.length; // '\n' separator
129
+ if (used + addLen > slackChars) break;
130
+ chosen.push(line);
131
+ used += addLen;
132
+ }
133
+
134
+ return {
135
+ head,
136
+ tail,
137
+ importantLines: chosen,
138
+ baseDropped: value.length - maxChars,
139
+ };
140
+ }
@@ -0,0 +1,187 @@
1
+ /**
2
+ * Intercom bridge — workers can escalate questions to the orchestrator.
3
+ *
4
+ * Pattern origin: pi-subagents/src/intercom-bridge.ts — contact_supervisor tool
5
+ * for child agents to escalate decisions, report progress, or ask questions.
6
+ *
7
+ * This module provides the message queue and correlation logic.
8
+ * The actual tool registration happens in task-runner.ts.
9
+ */
10
+
11
+ // ── Types ────────────────────────────────────────────────────────────────
12
+
13
+ export type IntercomUrgency = "low" | "medium" | "high" | "critical";
14
+ export type IntercomType = "question" | "escalation" | "progress" | "block";
15
+
16
+ export interface IntercomMessage {
17
+ type: IntercomType;
18
+ taskStepId: string;
19
+ content: string;
20
+ urgency: IntercomUrgency;
21
+ timestamp: number;
22
+ timeout?: number; // ms to wait for response
23
+ }
24
+
25
+ export interface IntercomResponse {
26
+ answer: string;
27
+ source: "orchestrator" | "human" | "timeout";
28
+ timestamp: number;
29
+ messageId: string;
30
+ }
31
+
32
+ // ── Message Queue ────────────────────────────────────────────────────────
33
+
34
+ interface PendingMessage {
35
+ message: IntercomMessage;
36
+ id: string;
37
+ resolve: (response: IntercomResponse) => void;
38
+ timer?: ReturnType<typeof setTimeout>;
39
+ }
40
+
41
+ const MAX_QUEUE_SIZE = 100;
42
+
43
+ /**
44
+ * In-process intercom queue for worker→orchestrator communication.
45
+ *
46
+ * Each message gets a unique ID. Callers await a response via a Promise.
47
+ * If no response arrives within the timeout, resolves with source="timeout".
48
+ */
49
+ export class IntercomQueue {
50
+ private pending = new Map<string, PendingMessage>();
51
+ private queue: IntercomMessage[] = [];
52
+ /** Monotonic sequence counter — guarantees unique IDs even when many
53
+ * messages enqueue within the same millisecond (avoids Date.now()
54
+ * collision + weak Math.random birthday-paradox flakes under load). */
55
+ private seq = 0;
56
+
57
+ /**
58
+ * Enqueue a message and return a promise that resolves when the
59
+ * orchestrator responds (or times out).
60
+ */
61
+ enqueue(message: IntercomMessage): Promise<IntercomResponse> {
62
+ if (this.pending.size >= MAX_QUEUE_SIZE) {
63
+ // Evict oldest
64
+ const firstKey = this.pending.keys().next().value;
65
+ if (firstKey) this.evict(firstKey, "queue_full");
66
+ }
67
+
68
+ const id = `icm-${Date.now().toString(36)}-${(this.seq++).toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
69
+
70
+ return new Promise<IntercomResponse>((resolve) => {
71
+ const entry: PendingMessage = { message, id, resolve };
72
+
73
+ // Set timeout if specified
74
+ if (message.timeout && message.timeout > 0) {
75
+ entry.timer = setTimeout(() => {
76
+ resolve({
77
+ answer: "No response received within timeout",
78
+ source: "timeout",
79
+ timestamp: Date.now(),
80
+ messageId: id,
81
+ });
82
+ this.pending.delete(id);
83
+ }, message.timeout);
84
+ // Defense in depth: never let a pending-message timer block
85
+ // process exit. The timer is cleared via respond()/evict() in
86
+ // the normal case; .unref() ensures shutdown isn't blocked if
87
+ // the queue is abandoned.
88
+ if (entry.timer && typeof entry.timer.unref === "function") {
89
+ entry.timer.unref();
90
+ }
91
+ }
92
+
93
+ this.pending.set(id, entry);
94
+ this.queue.push({ ...message });
95
+ });
96
+ }
97
+
98
+ /**
99
+ * Respond to a pending message by ID.
100
+ */
101
+ respond(messageId: string, answer: string, source: "orchestrator" | "human" = "orchestrator"): boolean {
102
+ const entry = this.pending.get(messageId);
103
+ if (!entry) return false;
104
+
105
+ if (entry.timer) clearTimeout(entry.timer);
106
+
107
+ entry.resolve({
108
+ answer,
109
+ source,
110
+ timestamp: Date.now(),
111
+ messageId,
112
+ });
113
+
114
+ this.pending.delete(messageId);
115
+ return true;
116
+ }
117
+
118
+ /**
119
+ * Get all pending messages (for orchestrator to process).
120
+ */
121
+ getPending(): Array<IntercomMessage & { id: string }> {
122
+ return [...this.pending.entries()].map(([id, entry]) => ({
123
+ ...entry.message,
124
+ id,
125
+ }));
126
+ }
127
+
128
+ /**
129
+ * Number of pending messages awaiting response.
130
+ */
131
+ get pendingCount(): number {
132
+ return this.pending.size;
133
+ }
134
+
135
+ /**
136
+ * Clean up all pending messages (e.g., on run completion).
137
+ */
138
+ clear(): void {
139
+ for (const [id, entry] of this.pending) {
140
+ this.evict(id, "run_complete");
141
+ }
142
+ this.queue = [];
143
+ }
144
+
145
+ private evict(id: string, reason: string): void {
146
+ const entry = this.pending.get(id);
147
+ if (!entry) return;
148
+
149
+ if (entry.timer) clearTimeout(entry.timer);
150
+
151
+ entry.resolve({
152
+ answer: `Message evicted: ${reason}`,
153
+ source: "timeout",
154
+ timestamp: Date.now(),
155
+ messageId: id,
156
+ });
157
+
158
+ this.pending.delete(id);
159
+ }
160
+ }
161
+
162
+ // ── Singleton per run ────────────────────────────────────────────────────
163
+
164
+ const queues = new Map<string, IntercomQueue>();
165
+
166
+ /**
167
+ * Get or create an intercom queue for a run.
168
+ */
169
+ export function getIntercomQueue(runId: string): IntercomQueue {
170
+ let queue = queues.get(runId);
171
+ if (!queue) {
172
+ queue = new IntercomQueue();
173
+ queues.set(runId, queue);
174
+ }
175
+ return queue;
176
+ }
177
+
178
+ /**
179
+ * Clean up intercom queue for a completed run.
180
+ */
181
+ export function cleanupIntercomQueue(runId: string): void {
182
+ const queue = queues.get(runId);
183
+ if (queue) {
184
+ queue.clear();
185
+ queues.delete(runId);
186
+ }
187
+ }
@@ -0,0 +1,305 @@
1
+ /**
2
+ * Transparent iteration hooks — runs user-supplied before/after task scripts
3
+ * with structured JSON payload on stdin.
4
+ *
5
+ * Distilled from pi-autoresearch's iteration hook pattern.
6
+ */
7
+ import { spawn } from "node:child_process";
8
+ import * as fs from "node:fs";
9
+ import * as path from "node:path";
10
+ import { WINDOWS_ESSENTIAL_ENV_VARS } from "../utils/env-allowlist.ts";
11
+ import { sanitizeEnvSecrets } from "../utils/env-filter.ts";
12
+ import { resolveShellForScript } from "../utils/resolve-shell.ts";
13
+ import { HeadSnapStage } from "./compact-stages/index.ts";
14
+ import { DENIED_METRIC_NAMES } from "./metric-parser.ts";
15
+
16
+ /** Hook execution stage. */
17
+ export type HookStage = "before" | "after";
18
+
19
+ /** Payload sent to the hook script via stdin as JSON. */
20
+ export interface HookPayload {
21
+ event: HookStage;
22
+ cwd: string;
23
+ taskId: string;
24
+ runId: string;
25
+ taskRole: string;
26
+ lastResult?: {
27
+ status: string;
28
+ description: string;
29
+ diagnostics?: Record<string, unknown>;
30
+ } | null;
31
+ session: {
32
+ teamName: string;
33
+ workflowName: string;
34
+ goal: string;
35
+ completedTasks: number;
36
+ totalTasks: number;
37
+ };
38
+ }
39
+
40
+ /** Result of executing an iteration hook. */
41
+ export interface HookResult {
42
+ /** Whether the hook script was actually executed. */
43
+ fired: boolean;
44
+ /** Captured stdout (truncated to 8KB). */
45
+ stdout: string;
46
+ /** Captured stderr. */
47
+ stderr: string;
48
+ /** Exit code of the hook process. */
49
+ exitCode: number | null;
50
+ /** Whether the hook timed out. */
51
+ timedOut: boolean;
52
+ /** Wall-clock duration in milliseconds. */
53
+ durationMs: number;
54
+ }
55
+
56
+ /** Maximum stdout capture size in bytes (8 KB). */
57
+ const MAX_STDOUT_BYTES = 8192;
58
+
59
+ /** Hook execution timeout in milliseconds (30 seconds). */
60
+ const HOOK_TIMEOUT_MS = 30_000;
61
+
62
+ /**
63
+ * Validates that a hook script path is within an allowed directory.
64
+ * Allowed paths:
65
+ * - Relative paths starting with ".hooks/" (case-sensitive)
66
+ * - Absolute paths under $HOME/.pi/hooks/
67
+ * All other paths are rejected to prevent arbitrary script execution.
68
+ * @param hookPath - The hook script path to validate
69
+ * @returns true if the path is allowed, false otherwise
70
+ */
71
+ export function isAllowedHookPath(hookPath: string): boolean {
72
+ if (!hookPath || hookPath.trim().length === 0) return false;
73
+ if (!path.isAbsolute(hookPath)) {
74
+ // Use path.posix.normalize to ensure forward-slash normalization on all platforms.
75
+ // On Windows, path.normalize converts .hooks/hook.sh to .hooks\hook.sh (backslash),
76
+ // breaking the startsWith(".hooks/") check. path.posix.normalize always uses /.
77
+ const normalized = path.posix.normalize(hookPath);
78
+ return normalized === ".hooks" || normalized.startsWith(".hooks/");
79
+ }
80
+ // Normalize to forward slashes for consistent cross-platform comparison.
81
+ // e.g., "C:\\Users\\runner\\.pi\\hooks\\hook.sh" matches
82
+ // "C:\\Users\\runner\\.pi\\hooks/hook.sh" from path.join.
83
+ const normalizedHookPath = hookPath.replace(/\\/g, "/");
84
+ const homeHooksNormalized = (process.env.HOME ?? "").replace(/\\/g, "/") + "/.pi/hooks";
85
+ return normalizedHookPath === homeHooksNormalized || normalizedHookPath.startsWith(homeHooksNormalized + "/");
86
+ }
87
+
88
+ /**
89
+ * Create a not-fired result for when the hook script is absent or not executable.
90
+ */
91
+ function notFiredResult(): HookResult {
92
+ return {
93
+ fired: false,
94
+ stdout: "",
95
+ stderr: "",
96
+ exitCode: null,
97
+ timedOut: false,
98
+ durationMs: 0,
99
+ };
100
+ }
101
+
102
+ /**
103
+ * Check if a script path exists and is executable.
104
+ */
105
+ function isScriptRunnable(scriptPath: string): boolean {
106
+ try {
107
+ if (!fs.existsSync(scriptPath)) return false;
108
+
109
+ // On Windows, X_OK is unreliable — just check F_OK (file exists).
110
+ // On Unix, check both F_OK and X_OK.
111
+ if (process.platform === "win32") {
112
+ fs.accessSync(scriptPath, fs.constants.F_OK);
113
+ } else {
114
+ fs.accessSync(scriptPath, fs.constants.F_OK | fs.constants.X_OK);
115
+ }
116
+ return true;
117
+ } catch {
118
+ return false;
119
+ }
120
+ }
121
+
122
+ /**
123
+ * Run an iteration hook script with JSON payload on stdin.
124
+ *
125
+ * Spawns `bash <script>` with the hook payload as JSON on stdin.
126
+ * Captures stdout (capped at 8KB) and stderr. Enforces a 30-second timeout.
127
+ *
128
+ * **Security note:** Hook paths are restricted to `.hooks/` relative paths
129
+ * or `$HOME/.pi/hooks/` absolute paths. All other paths are rejected before
130
+ * execution.
131
+ *
132
+ * @param payload - Structured hook payload
133
+ * @param hookScriptPath - Absolute or relative path to the hook script
134
+ * @returns HookResult indicating whether the hook fired and its output
135
+ */
136
+ export async function runIterationHook(
137
+ payload: HookPayload,
138
+ hookScriptPath: string,
139
+ options?: { timeoutMs?: number },
140
+ ): Promise<HookResult> {
141
+ if (!isAllowedHookPath(hookScriptPath)) {
142
+ return {
143
+ fired: false,
144
+ stdout: "",
145
+ stderr: "hook path not allowed: " + hookScriptPath,
146
+ exitCode: null,
147
+ timedOut: false,
148
+ durationMs: 0,
149
+ };
150
+ }
151
+ // Resolve relative paths relative to cwd
152
+ const resolvedScript = path.isAbsolute(hookScriptPath) ? hookScriptPath : path.join(payload.cwd, hookScriptPath);
153
+ if (!isScriptRunnable(resolvedScript)) {
154
+ return notFiredResult();
155
+ }
156
+
157
+ const startTime = Date.now();
158
+ const stdinJson = JSON.stringify(payload);
159
+ const stdoutChunks: Buffer[] = [];
160
+ const stderrChunks: Buffer[] = [];
161
+
162
+ return new Promise<HookResult>((resolve) => {
163
+ const { command, args } = resolveShellForScript(resolvedScript);
164
+ const child = spawn(command, args, {
165
+ cwd: payload.cwd,
166
+ env: {
167
+ ...sanitizeEnvSecrets(process.env, {
168
+ allowList: ["PATH", "HOME", "USER", ...WINDOWS_ESSENTIAL_ENV_VARS, "TMPDIR", "LANG", "LC_ALL", "PI_CREW_*"],
169
+ }),
170
+ PI_CREW_HOOK: "1",
171
+ },
172
+ stdio: ["pipe", "pipe", "pipe"],
173
+ });
174
+
175
+ let killed = false;
176
+ const timeoutMs = options?.timeoutMs ?? HOOK_TIMEOUT_MS;
177
+ const timeout = setTimeout(() => {
178
+ killed = true;
179
+ child.kill("SIGKILL");
180
+ }, timeoutMs);
181
+
182
+ child.stdout.on("data", (chunk: Buffer) => {
183
+ stdoutChunks.push(chunk);
184
+ });
185
+
186
+ child.stderr.on("data", (chunk: Buffer) => {
187
+ stderrChunks.push(chunk);
188
+ });
189
+
190
+ child.on("close", (code: number | null) => {
191
+ clearTimeout(timeout);
192
+ const durationMs = Date.now() - startTime;
193
+
194
+ const rawStdout = Buffer.concat(stdoutChunks);
195
+ // Sprint 5: refactored onto HeadSnapStage. Convert to UTF-8 string once,
196
+ // then apply the byte-cap stage with newline-snap so partial lines
197
+ // never appear in the captured preview. HeadSnapStage is byte-cap-safe
198
+ // (walks back partial UTF-8 sequences at the cut boundary).
199
+ const stdoutText = new HeadSnapStage({
200
+ maxBytes: MAX_STDOUT_BYTES,
201
+ }).apply(rawStdout.toString("utf-8"));
202
+
203
+ const rawStderr = Buffer.concat(stderrChunks);
204
+
205
+ resolve({
206
+ fired: true,
207
+ stdout: stdoutText,
208
+ stderr: rawStderr.toString("utf-8"),
209
+ exitCode: code,
210
+ timedOut: killed,
211
+ durationMs,
212
+ });
213
+ });
214
+
215
+ child.on("error", (err: Error) => {
216
+ clearTimeout(timeout);
217
+ const durationMs = Date.now() - startTime;
218
+ resolve({
219
+ fired: true,
220
+ stdout: "",
221
+ stderr: err.message,
222
+ exitCode: null,
223
+ timedOut: false,
224
+ durationMs,
225
+ });
226
+ });
227
+
228
+ // Write payload to stdin and close it.
229
+ // Handle EPIPE errors gracefully (occurs if the hook script exits before
230
+ // reading all of stdin, which is normal for some hook scripts on certain OS).
231
+ child.stdin.on("error", () => {
232
+ /* ignore EPIPE — hook exited early */
233
+ });
234
+ try {
235
+ child.stdin.write(stdinJson, "utf-8");
236
+ child.stdin.end();
237
+ } catch {
238
+ // ignore
239
+ }
240
+ });
241
+ }
242
+
243
+ /**
244
+ * Derive a steer message from the hook result.
245
+ *
246
+ * - Non-zero exit → error steer message
247
+ * - Timeout → timeout steer message
248
+ * - Empty stdout → null (no steer)
249
+ * - Otherwise → trimmed stdout content
250
+ */
251
+ export function steerMessageFromHook(stage: HookStage, result: HookResult): string | null {
252
+ if (!result.fired) return null;
253
+
254
+ if (result.timedOut) {
255
+ return `[${stage}-hook] Hook timed out after ${result.durationMs}ms`;
256
+ }
257
+
258
+ if (result.exitCode !== null && result.exitCode !== 0) {
259
+ const stderrSnippet = result.stderr.trim().slice(0, 200);
260
+ return `[${stage}-hook] Hook exited with code ${result.exitCode}${stderrSnippet ? `: ${stderrSnippet}` : ""}`;
261
+ }
262
+
263
+ const trimmed = result.stdout.trim();
264
+ if (trimmed.length === 0) return null;
265
+
266
+ // Filter out prototype-polluting metric names from hook output
267
+ const lines = trimmed.split("\n");
268
+ const safeLines = lines.filter((line) => {
269
+ const match = /^CREW_METRIC\s+(\w+)=/.exec(line);
270
+ if (match) {
271
+ const name = match[1];
272
+ return !DENIED_METRIC_NAMES.has(name);
273
+ }
274
+ return true;
275
+ });
276
+
277
+ return safeLines.join("\n");
278
+ }
279
+
280
+ /**
281
+ * Build a log entry for recording hook execution in events.jsonl.
282
+ */
283
+ export function hookLogEntry(stage: HookStage, result: HookResult): Record<string, unknown> {
284
+ const entry: Record<string, unknown> = {
285
+ type: "iteration-hook",
286
+ stage,
287
+ fired: result.fired,
288
+ durationMs: result.durationMs,
289
+ };
290
+
291
+ if (result.fired) {
292
+ entry.exitCode = result.exitCode;
293
+ entry.timedOut = result.timedOut;
294
+
295
+ // Include truncated stdout/stderr for diagnostics
296
+ if (result.stdout.length > 0) {
297
+ entry.stdoutPreview = result.stdout.slice(0, 512);
298
+ }
299
+ if (result.stderr.length > 0) {
300
+ entry.stderrPreview = result.stderr.slice(0, 512);
301
+ }
302
+ }
303
+
304
+ return entry;
305
+ }