pi-crew 0.9.55 → 0.9.57

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 (309) hide show
  1. package/AGENTS.md +1 -1
  2. package/CHANGELOG.md +58 -0
  3. package/README.md +53 -5
  4. package/dist/index.mjs +5666 -4370
  5. package/docs/README.md +54 -0
  6. package/package.json +12 -13
  7. package/skills/real-test-pi-crew/SKILL.md +93 -3
  8. package/src/agents/discover-agents.ts +68 -10
  9. package/src/benchmark/feedback-loop.ts +1 -1
  10. package/src/config/config.ts +2 -1
  11. package/src/config/types.ts +12 -0
  12. package/src/extension/action-suggestions.ts +16 -63
  13. package/src/extension/async-notifier.ts +3 -3
  14. package/src/extension/command-completions.ts +2 -2
  15. package/src/extension/crew-cleanup.ts +3 -3
  16. package/src/extension/crew-vibes/footer.ts +4 -2
  17. package/src/extension/cross-extension-rpc.ts +10 -5
  18. package/src/extension/knowledge-injection.ts +57 -10
  19. package/src/extension/register.ts +2 -2
  20. package/src/extension/registration/artifact-cleanup.ts +41 -3
  21. package/src/extension/registration/commands.ts +4 -4
  22. package/src/extension/registration/context-builder.ts +10 -10
  23. package/src/extension/registration/crash-recovery-cache.ts +3 -3
  24. package/src/extension/registration/foreground-run-controller.ts +1 -1
  25. package/src/extension/registration/lazy-configurers.ts +2 -2
  26. package/src/extension/registration/lifecycle-handlers.ts +12 -12
  27. package/src/extension/registration/lifecycle.ts +2 -2
  28. package/src/extension/registration/observability.ts +2 -2
  29. package/src/extension/registration/registration-types.ts +3 -3
  30. package/src/extension/registration/runtime-cleanup.ts +1 -1
  31. package/src/extension/registration/subagent-helpers.ts +2 -2
  32. package/src/extension/registration/subagent-manager-setup.ts +2 -2
  33. package/src/extension/registration/subagent-tools.ts +21 -16
  34. package/src/extension/registration/team-tool.ts +109 -17
  35. package/src/extension/registration/viewers.ts +3 -4
  36. package/src/extension/registration/wire-cross-extension.ts +7 -7
  37. package/src/extension/rpc-hmac.ts +3 -2
  38. package/src/extension/run-bundle-schema.ts +13 -4
  39. package/src/extension/run-export.ts +2 -2
  40. package/src/extension/run-index.ts +2 -3
  41. package/src/extension/run-maintenance.ts +57 -1
  42. package/src/extension/team-manager-command.ts +3 -1
  43. package/src/extension/team-tool/anchor.ts +8 -2
  44. package/src/extension/team-tool/api.ts +117 -39
  45. package/src/extension/team-tool/cancel.ts +23 -8
  46. package/src/extension/team-tool/chain-executor.ts +1 -1
  47. package/src/extension/team-tool/dispatch/automate.ts +17 -2
  48. package/src/extension/team-tool/dispatch/control.ts +17 -2
  49. package/src/extension/team-tool/dispatch/manage.ts +34 -2
  50. package/src/extension/team-tool/dispatch/run.ts +17 -2
  51. package/src/extension/team-tool/dispatch/status.ts +37 -5
  52. package/src/extension/team-tool/doctor.ts +2 -2
  53. package/src/extension/team-tool/explain.ts +1 -1
  54. package/src/extension/team-tool/goal-wrap.ts +5 -5
  55. package/src/extension/team-tool/goal.ts +30 -9
  56. package/src/extension/team-tool/handle-schedule.ts +37 -19
  57. package/src/extension/team-tool/inspect.ts +21 -5
  58. package/src/extension/team-tool/lifecycle-actions.ts +53 -10
  59. package/src/extension/team-tool/parallel-dispatch.ts +14 -5
  60. package/src/extension/team-tool/param-error.ts +138 -0
  61. package/src/extension/team-tool/respond.ts +20 -6
  62. package/src/extension/team-tool/run.ts +27 -17
  63. package/src/extension/team-tool/status.ts +17 -6
  64. package/src/extension/team-tool.ts +26 -19
  65. package/src/hooks/registry.ts +1 -1
  66. package/src/i18n.ts +22 -0
  67. package/src/observability/event-bus.ts +1 -1
  68. package/src/prompt/prompt-runtime.ts +2 -2
  69. package/src/runtime/README.md +35 -0
  70. package/src/runtime/agent-control.ts +1 -1
  71. package/src/runtime/anchor-manager.ts +0 -2
  72. package/src/runtime/async-runner.ts +1 -1
  73. package/src/runtime/attention-events.ts +3 -2
  74. package/src/runtime/auto-summarize.ts +0 -2
  75. package/src/runtime/background-runner.ts +103 -30
  76. package/src/runtime/{crew-broker-child.ts → broker/crew-broker-child.ts} +2 -2
  77. package/src/runtime/{crew-broker-client.ts → broker/crew-broker-client.ts} +4 -7
  78. package/src/runtime/{crew-broker.ts → broker/crew-broker.ts} +13 -21
  79. package/src/runtime/chain-runner.ts +50 -5
  80. package/src/runtime/{child-pi-constants.ts → child-pi/child-pi-constants.ts} +1 -1
  81. package/src/runtime/{child-pi-kill.ts → child-pi/child-pi-kill.ts} +3 -3
  82. package/src/runtime/{child-pi-spawn.ts → child-pi/child-pi-spawn.ts} +28 -12
  83. package/src/runtime/{child-pi-steering.ts → child-pi/child-pi-steering.ts} +1 -1
  84. package/src/runtime/{child-pi-streams.ts → child-pi/child-pi-streams.ts} +23 -4
  85. package/src/runtime/{child-pi-transcript.ts → child-pi/child-pi-transcript.ts} +6 -6
  86. package/src/runtime/{child-pi.ts → child-pi/child-pi.ts} +47 -78
  87. package/src/runtime/compaction/compact-stages/bounded-tail.ts +84 -0
  88. package/src/runtime/{compact-stages → compaction/compact-stages}/index.ts +2 -2
  89. package/src/runtime/{compaction-summary.ts → compaction/compaction-summary.ts} +3 -3
  90. package/src/runtime/{tool-output-pruner.ts → compaction/tool-output-pruner.ts} +1 -1
  91. package/src/runtime/crew-agent-records.ts +48 -2
  92. package/src/runtime/custom-tools/irc-tool.ts +2 -2
  93. package/src/runtime/delivery-coordinator.ts +0 -3
  94. package/src/runtime/diagnostic-export.ts +5 -4
  95. package/src/runtime/foreground-control.ts +1 -1
  96. package/src/runtime/foreground-watchdog.ts +1 -1
  97. package/src/runtime/{adaptive-plan.ts → goal-workflow/adaptive-plan.ts} +8 -8
  98. package/src/runtime/{dynamic-workflow-context.ts → goal-workflow/dynamic-workflow-context.ts} +16 -16
  99. package/src/runtime/{dynamic-workflow-runner.ts → goal-workflow/dynamic-workflow-runner.ts} +10 -10
  100. package/src/runtime/{goal-achievement.ts → goal-workflow/goal-achievement.ts} +3 -3
  101. package/src/runtime/{goal-evaluator.ts → goal-workflow/goal-evaluator.ts} +8 -8
  102. package/src/runtime/{goal-loop-runner.ts → goal-workflow/goal-loop-runner.ts} +27 -25
  103. package/src/runtime/{goal-state-store.ts → goal-workflow/goal-state-store.ts} +8 -8
  104. package/src/runtime/{plan-templates.ts → goal-workflow/plan-templates.ts} +1 -1
  105. package/src/runtime/group-join.ts +3 -3
  106. package/src/runtime/handoff-manager.ts +0 -2
  107. package/src/runtime/{heartbeat-watcher.ts → heartbeat/heartbeat-watcher.ts} +7 -7
  108. package/src/runtime/iteration-hooks.ts +1 -1
  109. package/src/runtime/{live-agent-control.ts → live-session/live-agent-control.ts} +2 -2
  110. package/src/runtime/{live-agent-manager.ts → live-session/live-agent-manager.ts} +4 -4
  111. package/src/runtime/{live-session-runtime.ts → live-session/live-session-runtime.ts} +39 -41
  112. package/src/runtime/manifest-cache.ts +6 -2
  113. package/src/runtime/{model-fallback.ts → model/model-fallback.ts} +1 -1
  114. package/src/runtime/{pi-args.ts → model/pi-args.ts} +20 -5
  115. package/src/runtime/model/provider-extensions.ts +116 -0
  116. package/src/runtime/{runtime-policy.ts → model/runtime-policy.ts} +2 -2
  117. package/src/runtime/{runtime-resolver.ts → model/runtime-resolver.ts} +3 -3
  118. package/src/runtime/{runtime-warmup.ts → model/runtime-warmup.ts} +8 -8
  119. package/src/runtime/orphan-worker-registry.ts +1 -1
  120. package/src/runtime/{progress-tracker.ts → output/progress-tracker.ts} +1 -1
  121. package/src/runtime/{result-extractor.ts → output/result-extractor.ts} +4 -4
  122. package/src/runtime/{sidechain-output.ts → output/sidechain-output.ts} +2 -2
  123. package/src/runtime/{stream-preview.ts → output/stream-preview.ts} +1 -1
  124. package/src/runtime/{streaming-output.ts → output/streaming-output.ts} +2 -2
  125. package/src/runtime/{tool-progress.ts → output/tool-progress.ts} +1 -1
  126. package/src/runtime/parent-guard.ts +30 -10
  127. package/src/runtime/pipeline-runner.ts +2 -2
  128. package/src/runtime/policy-engine.ts +2 -2
  129. package/src/runtime/{cancellation.ts → process/cancellation.ts} +1 -1
  130. package/src/runtime/{post-exit-stdio-guard.ts → process/post-exit-stdio-guard.ts} +1 -1
  131. package/src/runtime/{process-lifecycle.ts → process/process-lifecycle.ts} +3 -3
  132. package/src/runtime/{checkpoint.ts → recovery/checkpoint.ts} +4 -4
  133. package/src/runtime/{crash-recovery.ts → recovery/crash-recovery.ts} +93 -21
  134. package/src/runtime/{overflow-recovery.ts → recovery/overflow-recovery.ts} +1 -1
  135. package/src/runtime/{recovery-recipes.ts → recovery/recovery-recipes.ts} +1 -1
  136. package/src/runtime/{retry-executor.ts → recovery/retry-executor.ts} +2 -2
  137. package/src/runtime/{retry-runner.ts → recovery/retry-runner.ts} +2 -8
  138. package/src/runtime/run-tracker.ts +1 -1
  139. package/src/runtime/run-worker.ts +3 -3
  140. package/src/runtime/{coalesce-tasks.ts → scheduling/coalesce-tasks.ts} +3 -3
  141. package/src/runtime/{concurrency.ts → scheduling/concurrency.ts} +9 -3
  142. package/src/runtime/{parallel-research.ts → scheduling/parallel-research.ts} +4 -2
  143. package/src/runtime/{run-coalesced-task-group.ts → scheduling/run-coalesced-task-group.ts} +95 -36
  144. package/src/runtime/{semaphore.ts → scheduling/semaphore.ts} +4 -1
  145. package/src/runtime/{task-graph-scheduler.ts → scheduling/task-graph-scheduler.ts} +1 -1
  146. package/src/runtime/session-resources.ts +1 -1
  147. package/src/runtime/settings-store.ts +1 -1
  148. package/src/runtime/single-agent-compose.ts +1 -0
  149. package/src/runtime/stale-reconciler.ts +1 -1
  150. package/src/runtime/subagent-manager.ts +1 -1
  151. package/src/runtime/supervisor-contact.ts +28 -13
  152. package/src/runtime/task-output-context.ts +7 -5
  153. package/src/runtime/task-runner/child-executor.ts +67 -31
  154. package/src/runtime/task-runner/live-executor.ts +8 -8
  155. package/src/runtime/task-runner/post-execution.ts +12 -12
  156. package/src/runtime/task-runner/pre-execution.ts +6 -6
  157. package/src/runtime/task-runner/progress.ts +1 -1
  158. package/src/runtime/task-runner/run-projection.ts +1 -1
  159. package/src/runtime/task-runner/scaffold-executor.ts +1 -1
  160. package/src/runtime/task-runner/state-helpers.ts +2 -2
  161. package/src/runtime/task-runner.ts +4 -4
  162. package/src/runtime/team-runner.ts +441 -175
  163. package/src/runtime/{completion-guard.ts → verification/completion-guard.ts} +1 -1
  164. package/src/runtime/{green-contract.ts → verification/green-contract.ts} +1 -1
  165. package/src/runtime/{post-checks.ts → verification/post-checks.ts} +4 -4
  166. package/src/runtime/{verification-gates.ts → verification/verification-gates.ts} +5 -5
  167. package/src/runtime/{verification-integrity.ts → verification/verification-integrity.ts} +1 -1
  168. package/src/schema/config-schema.ts +1 -0
  169. package/src/schema/team-tool-schema.ts +149 -185
  170. package/src/state/README.md +44 -0
  171. package/src/state/atomic-write.ts +24 -3
  172. package/src/state/{locks.ts → coordination/locks.ts} +118 -38
  173. package/src/state/{mailbox.ts → coordination/mailbox.ts} +14 -8
  174. package/src/state/{schedule.ts → coordination/schedule.ts} +3 -3
  175. package/src/state/{task-claims.ts → coordination/task-claims.ts} +1 -1
  176. package/src/state/decision-ledger.ts +1 -1
  177. package/src/state/{event-log-rotation.ts → event-log/event-log-rotation.ts} +189 -31
  178. package/src/state/{event-log.ts → event-log/event-log.ts} +77 -39
  179. package/src/state/{jsonl-writer.ts → event-log/jsonl-writer.ts} +14 -6
  180. package/src/state/hook-instinct-bridge.ts +2 -2
  181. package/src/state/{active-run-registry.ts → stores/active-run-registry.ts} +7 -7
  182. package/src/state/{artifact-store.ts → stores/artifact-store.ts} +62 -4
  183. package/src/state/{blob-store.ts → stores/blob-store.ts} +3 -3
  184. package/src/state/{health-store.ts → stores/health-store.ts} +3 -3
  185. package/src/state/{instinct-store.ts → stores/instinct-store.ts} +3 -3
  186. package/src/state/{observation-store.ts → stores/observation-store.ts} +2 -2
  187. package/src/state/{run-cache.ts → stores/run-cache.ts} +4 -4
  188. package/src/state/{run-graph.ts → stores/run-graph.ts} +4 -4
  189. package/src/state/{run-metrics.ts → stores/run-metrics.ts} +3 -3
  190. package/src/state/{state-store.ts → stores/state-store.ts} +254 -21
  191. package/src/state/types.ts +27 -3
  192. package/src/ui/component.ts +55 -0
  193. package/src/ui/crew-select-list.ts +3 -1
  194. package/src/ui/dashboard-panes/agents-pane.ts +1 -1
  195. package/src/ui/dwf-phase-display.ts +1 -1
  196. package/src/ui/heartbeat-aggregator.ts +1 -1
  197. package/src/ui/keybinding-map.ts +179 -6
  198. package/src/ui/layout-primitives.ts +9 -5
  199. package/src/ui/live-conversation-overlay.ts +1 -1
  200. package/src/ui/live-run-sidebar.ts +1 -1
  201. package/src/ui/overlay-stack.ts +148 -0
  202. package/src/ui/overlays/agent-picker-overlay.ts +1 -1
  203. package/src/ui/overlays/mailbox-compose-overlay.ts +1 -1
  204. package/src/ui/overlays/mailbox-detail-overlay.ts +2 -2
  205. package/src/ui/powerbar-publisher.ts +1 -1
  206. package/src/ui/render-scheduler.ts +1 -1
  207. package/src/ui/run-action-dispatcher.ts +3 -3
  208. package/src/ui/run-dashboard.ts +5 -33
  209. package/src/ui/run-event-bus.ts +3 -3
  210. package/src/ui/run-snapshot-cache.ts +37 -18
  211. package/src/ui/settings-overlay.ts +13 -21
  212. package/src/ui/snapshot-types.ts +1 -1
  213. package/src/ui/status-colors.ts +0 -2
  214. package/src/ui/terminal-status.ts +1 -1
  215. package/src/ui/theme-adapter.ts +33 -13
  216. package/src/ui/tool-renderers/index.ts +8 -0
  217. package/src/ui/transcript-viewer.ts +37 -13
  218. package/src/ui/widget/index.ts +32 -9
  219. package/src/ui/widget/widget-formatters.ts +62 -4
  220. package/src/ui/widget/widget-model.ts +2 -2
  221. package/src/ui/widget/widget-renderer.ts +1 -1
  222. package/src/utils/conflict-detect.ts +58 -5
  223. package/src/utils/ndjson.ts +2 -2
  224. package/src/utils/redaction.ts +39 -0
  225. package/src/utils/socket-path.ts +1 -1
  226. package/src/workflows/workflow-config.ts +1 -1
  227. package/src/worktree/cleanup.ts +1 -1
  228. package/src/worktree/worktree-manager.ts +111 -47
  229. package/workflows/chain.workflow.md +2 -2
  230. package/dist/build-meta.json +0 -16807
  231. package/index.bundle.ts +0 -25
  232. package/scripts/bench-check.mjs +0 -96
  233. package/scripts/bench-cold-start.mjs +0 -216
  234. package/scripts/build-bundle.mjs +0 -92
  235. package/scripts/check-all-skills.ts +0 -294
  236. package/scripts/check-bundle-size.mjs +0 -38
  237. package/scripts/check-bundle-staleness.mjs +0 -97
  238. package/scripts/check-conflict-markers.mjs +0 -91
  239. package/scripts/check-lazy-imports.mjs +0 -28
  240. package/scripts/dev-runner.mjs +0 -106
  241. package/scripts/profile-startup.mjs +0 -125
  242. package/scripts/release-smoke.mjs +0 -74
  243. package/scripts/run-bench.mjs +0 -47
  244. package/scripts/run-real-chain.ts +0 -41
  245. package/scripts/test-issue-29-crash.ts +0 -111
  246. package/scripts/test-issue-29-e2e.ts +0 -183
  247. package/scripts/test-issue-29-real-runtime.ts +0 -330
  248. package/scripts/test-issue-29-real-tasks.ts +0 -387
  249. package/scripts/test-issue-29-team-tool.ts +0 -105
  250. package/scripts/test-runner.mjs +0 -84
  251. package/scripts/verify-flicker-fix.ts +0 -109
  252. package/scripts/verify-skill.ts +0 -550
  253. package/scripts/watch-bundle.mjs +0 -259
  254. package/scripts/watchdog-harness.ts +0 -119
  255. package/src/adapters/claude-adapter.ts +0 -23
  256. package/src/adapters/codex-adapter.ts +0 -21
  257. package/src/adapters/cursor-adapter.ts +0 -17
  258. package/src/adapters/export-util.ts +0 -143
  259. package/src/adapters/index.ts +0 -15
  260. package/src/adapters/registry.ts +0 -18
  261. package/src/adapters/types.ts +0 -23
  262. package/src/runtime/code-summary.ts +0 -292
  263. package/src/runtime/cross-extension-rpc.ts +0 -143
  264. package/src/runtime/hidden-handoff.ts +0 -407
  265. package/src/runtime/prose-compressor.ts +0 -162
  266. package/src/subagents/async-entry.ts +0 -1
  267. package/src/subagents/index.ts +0 -3
  268. package/src/subagents/live/control.ts +0 -1
  269. package/src/subagents/live/manager.ts +0 -1
  270. package/src/subagents/live/realtime.ts +0 -1
  271. package/src/subagents/live/session-runtime.ts +0 -1
  272. package/src/subagents/manager.ts +0 -1
  273. package/src/subagents/spawn.ts +0 -1
  274. package/test-integration-check.ts +0 -118
  275. /package/src/runtime/{broker-issuer.ts → broker/broker-issuer.ts} +0 -0
  276. /package/src/runtime/{crew-broker-tokens.ts → broker/crew-broker-tokens.ts} +0 -0
  277. /package/src/runtime/{child-pi-pool.ts → child-pi/child-pi-pool.ts} +0 -0
  278. /package/src/runtime/{compact-pipeline.ts → compaction/compact-pipeline.ts} +0 -0
  279. /package/src/runtime/{compact-stages → compaction/compact-stages}/ansi-strip-stage.ts +0 -0
  280. /package/src/runtime/{compact-stages → compaction/compact-stages}/blank-collapse-stage.ts +0 -0
  281. /package/src/runtime/{compact-stages → compaction/compact-stages}/deduplicate-stage.ts +0 -0
  282. /package/src/runtime/{compact-stages → compaction/compact-stages}/head-snap-stage.ts +0 -0
  283. /package/src/runtime/{compact-stages → compaction/compact-stages}/tail-capture-stage.ts +0 -0
  284. /package/src/runtime/{compact-stages → compaction/compact-stages}/truncation-stage.ts +0 -0
  285. /package/src/runtime/{important-line-classifier.ts → compaction/important-line-classifier.ts} +0 -0
  286. /package/src/runtime/{heartbeat-gradient.ts → heartbeat/heartbeat-gradient.ts} +0 -0
  287. /package/src/runtime/{worker-heartbeat.ts → heartbeat/worker-heartbeat.ts} +0 -0
  288. /package/src/runtime/{worker-startup.ts → heartbeat/worker-startup.ts} +0 -0
  289. /package/src/runtime/{intercom-bridge.ts → live-session/intercom-bridge.ts} +0 -0
  290. /package/src/runtime/{live-control-realtime.ts → live-session/live-control-realtime.ts} +0 -0
  291. /package/src/runtime/{live-extension-bridge.ts → live-session/live-extension-bridge.ts} +0 -0
  292. /package/src/runtime/{live-irc.ts → live-session/live-irc.ts} +0 -0
  293. /package/src/runtime/{live-session-health.ts → live-session/live-session-health.ts} +0 -0
  294. /package/src/runtime/{model-resolver.ts → model/model-resolver.ts} +0 -0
  295. /package/src/runtime/{model-scope.ts → model/model-scope.ts} +0 -0
  296. /package/src/runtime/{output-validator.ts → output/output-validator.ts} +0 -0
  297. /package/src/runtime/{pi-json-output.ts → output/pi-json-output.ts} +0 -0
  298. /package/src/runtime/{progress-event-coalescer.ts → output/progress-event-coalescer.ts} +0 -0
  299. /package/src/runtime/{cancellation-token.ts → process/cancellation-token.ts} +0 -0
  300. /package/src/runtime/{zombie-scanner.ts → process/zombie-scanner.ts} +0 -0
  301. /package/src/runtime/{crash-classification.ts → recovery/crash-classification.ts} +0 -0
  302. /package/src/runtime/{batch-barrier.ts → scheduling/batch-barrier.ts} +0 -0
  303. /package/src/runtime/{global-worker-cap.ts → scheduling/global-worker-cap.ts} +0 -0
  304. /package/src/runtime/{parallel-utils.ts → scheduling/parallel-utils.ts} +0 -0
  305. /package/src/runtime/{scheduler.ts → scheduling/scheduler.ts} +0 -0
  306. /package/src/runtime/{task-graph.ts → scheduling/task-graph.ts} +0 -0
  307. /package/src/runtime/{verification-worktree.ts → verification/verification-worktree.ts} +0 -0
  308. /package/src/state/{event-reconstructor.ts → event-log/event-reconstructor.ts} +0 -0
  309. /package/src/state/{worker-atomic-writer.ts → event-log/worker-atomic-writer.ts} +0 -0
@@ -3,9 +3,9 @@ import * as path from "node:path";
3
3
  import { allAgents, discoverAgents } from "../agents/discover-agents.ts";
4
4
  import { loadConfig } from "../config/config.ts";
5
5
  import { atomicWriteFile } from "../state/atomic-write.ts";
6
- import { appendEvent } from "../state/event-log.ts";
7
- import { withRunLockSync } from "../state/locks.ts";
8
- import { createRunPaths, loadRunManifestById, saveRunManifestAsync, updateRunStatus } from "../state/state-store.ts";
6
+ import { withRunLockSync } from "../state/coordination/locks.ts";
7
+ import { appendEvent, appendEventFireAndForget } from "../state/event-log/event-log.ts";
8
+ import { createRunPaths, loadRunManifestById, saveRunManifestAsync, updateRunStatus } from "../state/stores/state-store.ts";
9
9
  import type { TeamRunManifest, TeamTaskState } from "../state/types.ts";
10
10
  import { allTeams, discoverTeams } from "../teams/discover-teams.ts";
11
11
  import { errorMessage } from "../utils/guards.ts";
@@ -35,7 +35,7 @@ async function executeTeamRun(...args: Parameters<typeof ExecuteTeamRunFn>): Pro
35
35
  // FIX (split-scope install): prime the ESM peer dep BEFORE team-runner is
36
36
  // imported, so its transitive skill-instructions.ts can read getAgentDir()
37
37
  // from the primed cache instead of crashing on `Cannot find module`.
38
- await primePeerDep().catch(() => {});
38
+ await primePeerDep().catch(() => undefined);
39
39
  // LAZY: avoid pulling team-runner into background-runner at module load time.
40
40
  const mod = await import("./team-runner.ts");
41
41
  _cachedExecuteTeamRun = mod.executeTeamRun;
@@ -45,12 +45,12 @@ async function executeTeamRun(...args: Parameters<typeof ExecuteTeamRunFn>): Pro
45
45
 
46
46
  import { logInternalError } from "../utils/internal-error.ts";
47
47
  import { writeAsyncStartMarker } from "./async-marker.ts";
48
- import { terminateActiveChildPiProcesses } from "./child-pi.ts";
48
+ import { terminateActiveChildPiProcesses } from "./child-pi/child-pi.ts";
49
49
  import { directTeamAndWorkflowFromRun } from "./direct-run.ts";
50
+ import { resolveCrewRuntime, runtimeResolutionState } from "./model/runtime-resolver.ts";
50
51
  import { unregisterWorker } from "./orphan-worker-registry.ts";
51
- import { expandParallelResearchWorkflow } from "./parallel-research.ts";
52
52
  import { startParentGuard, stopParentGuard } from "./parent-guard.ts";
53
- import { resolveCrewRuntime, runtimeResolutionState } from "./runtime-resolver.ts";
53
+ import { expandParallelResearchWorkflow } from "./scheduling/parallel-research.ts";
54
54
 
55
55
  /**
56
56
  * Debug logger gated behind PI_CREW_DEBUG env var. Writes to background.log
@@ -110,7 +110,47 @@ function argValue(name: string): string | undefined {
110
110
  return process.argv[index + 1];
111
111
  }
112
112
 
113
- function startInterruptGuard(
113
+ /**
114
+ * Fire-and-forget event log for signal handlers. Extracted to module level
115
+ * (from inside main()) so the exported SIGINT handler installer (test seam)
116
+ * and the inline signal-handler loop inside main() can both use it.
117
+ * Pure function of its arguments — no closure captures from main().
118
+ */
119
+ function signalLog(sig: string, eventsPath: string): void {
120
+ const runId = argValue("--run-id");
121
+ if (runId && eventsPath) {
122
+ appendEventFireAndForget(eventsPath, {
123
+ type: "async.failed",
124
+ runId,
125
+ message: `Background runner received ${sig} — exiting.`,
126
+ data: { signal: sig, pid: process.pid },
127
+ });
128
+ }
129
+ }
130
+
131
+ /**
132
+ * RT-2 SIGINT handler installer — exported as a test seam so integration tests
133
+ * can exercise the REAL handler logic (process.exitCode = 130, NOT
134
+ * process.exit(130)) without re-implementing it in a harness copy.
135
+ *
136
+ * Behavior is IDENTICAL to the previous inline handler in main() — pure
137
+ * extraction, zero logic change.
138
+ */
139
+ export function installBackgroundRunnerSigintHandler(abortController: AbortController, eventsPath: string): void {
140
+ process.on("SIGINT", () => {
141
+ signalLog("SIGINT", eventsPath);
142
+ // RT-2 FIX: Mirror the CORE-7 pattern at the interrupt guard (:146-151).
143
+ // Do NOT call process.exit(130) — it bypasses the finally/runCleanup block
144
+ // in main(), orphaning child-pi workers (they have no parent-guard, see
145
+ // RT-19). Setting exitCode lets the event loop drain naturally so main()'s
146
+ // finally block runs terminateActiveChildPiProcesses + unregisterWorker.
147
+ abortController.abort();
148
+ stopParentGuard();
149
+ process.exitCode = 130;
150
+ });
151
+ }
152
+
153
+ export function startInterruptGuard(
114
154
  manifest: { runId: string; stateRoot: string; eventsPath: string },
115
155
  abortController: AbortController,
116
156
  stopParentGuard: () => void,
@@ -119,6 +159,13 @@ function startInterruptGuard(
119
159
  // FIX: Made configurable via PI_CREW_INTERRUPT_GUARD_INTERVAL_MS env var.
120
160
  // Default 250ms balances fast SIGINT response against filesystem overhead.
121
161
  const interruptGuardInterval = Number(process.env.PI_CREW_INTERRUPT_GUARD_INTERVAL_MS) || 250;
162
+ // RT-4 FIX: Module-local gate so the interrupt body runs only once per
163
+ // interrupt request. Without this, the guard re-fires every
164
+ // interruptGuardInterval (250ms) — each tick does a full
165
+ // terminateActiveChildPiProcesses sweep + sync appendEvent = ~4×/s steady
166
+ // state. The ack write stops the re-fire; this gate is defense-in-depth if
167
+ // the ack write fails (e.g. transient fs error).
168
+ let interruptHandled = false;
122
169
  const interval = setInterval(() => {
123
170
  try {
124
171
  if (!fs.existsSync(controlPath)) return;
@@ -127,6 +174,25 @@ function startInterruptGuard(
127
174
  };
128
175
  const last = parsed.requests?.at(-1);
129
176
  if (last?.type === "interrupt" && last?.acknowledged !== true) {
177
+ // RT-4 FIX: Gate ensures the interrupt body runs only once even if
178
+ // the synchronous ack write below fails.
179
+ if (interruptHandled) return;
180
+ interruptHandled = true;
181
+
182
+ // RT-4 FIX: Write acknowledged:true back to foreground-control.json
183
+ // SYNCHRONOUSLY. This stops the guard from re-firing on the next tick
184
+ // (250ms). Must be sync because this is a setInterval polling callback
185
+ // — we cannot await in a polling callback.
186
+ try {
187
+ const reqs = parsed.requests ?? [];
188
+ if (reqs.length > 0) {
189
+ reqs[reqs.length - 1].acknowledged = true;
190
+ atomicWriteFile(controlPath, JSON.stringify(parsed, null, 2));
191
+ }
192
+ } catch {
193
+ /* best-effort ack — interruptHandled gate prevents re-fire */
194
+ }
195
+
130
196
  appendEvent(manifest.eventsPath, {
131
197
  type: "async.interrupt_detected",
132
198
  runId: manifest.runId,
@@ -335,19 +401,8 @@ async function main(): Promise<void> {
335
401
 
336
402
  // Scrub macOS malloc vars BEFORE anything else — must be clean for all child processes
337
403
  scrubProcessEnv();
338
- // Install signal handlers EARLY — log events before exiting so we can distinguish
339
- // OOM/SIGKILL (no event) from SIGTERM/SIGINT (event written).
340
- const signalLog = (sig: string, eventsPath: string): void => {
341
- const runId = argValue("--run-id");
342
- if (runId && eventsPath) {
343
- appendEvent(eventsPath, {
344
- type: "async.failed",
345
- runId,
346
- message: `Background runner received ${sig} — exiting.`,
347
- data: { signal: sig, pid: process.pid },
348
- });
349
- }
350
- };
404
+ // signalLog is now defined at module level (shared by the exported SIGINT
405
+ // handler installer and the inline signal-handler loop below).
351
406
  // BUG #17 FIX: Compute exitCodePath at module load time using args,
352
407
  // NOT by referencing `manifest` (declared inside main() and not in scope at module load).
353
408
  const exitCodePath = ((): string | undefined => {
@@ -422,10 +477,7 @@ async function main(): Promise<void> {
422
477
  // Trigger graceful shutdown via abort signal so finally block runs
423
478
  abortController.abort();
424
479
  });
425
- process.on("SIGINT", () => {
426
- signalLog("SIGINT", manifest.eventsPath);
427
- process.exit(130);
428
- });
480
+ installBackgroundRunnerSigintHandler(abortController, manifest.eventsPath);
429
481
  // BUG #17: Catch ALL signals to identify what kills the background runner
430
482
  for (const sig of [
431
483
  "SIGHUP",
@@ -527,7 +579,7 @@ async function main(): Promise<void> {
527
579
  // compilation of team-runner.ts, the event loop must not drain prematurely.
528
580
  // The interval is always cleared in the finally block, so the delay is
529
581
  // bounded by the 5s interval. The event loop exit is deferred at most 5s.
530
- const keepAlive = setInterval(() => {}, 5000);
582
+ const keepAlive = setInterval(() => undefined, 5000);
531
583
 
532
584
  // WATCHDOG (anti-zombie): if the run exceeds MAX_BACKGROUND_RUN_MS without
533
585
  // completing, abort it and force-exit. Without this, a hung team run
@@ -598,9 +650,9 @@ async function main(): Promise<void> {
598
650
  debugLog(`[background-runner] short-circuiting ${manifest.runKind} (synthetic team/workflow)`);
599
651
  if (manifest.runKind === "goal-loop") {
600
652
  // LAZY: defer dynamic import of ./goal-loop-runner.ts to its call site.
601
- const { runGoalLoop } = await import("./goal-loop-runner.ts");
653
+ const { runGoalLoop } = await import("./goal-workflow/goal-loop-runner.ts");
602
654
  // LAZY: defer dynamic import of ./goal-state-store.ts to its call site.
603
- const { GoalStore } = await import("./goal-state-store.ts");
655
+ const { GoalStore } = await import("./goal-workflow/goal-state-store.ts");
604
656
  // LAZY: defer dynamic import of ../agents/discover-agents.ts to its call site.
605
657
  const { discoverAgents, allAgents } = await import("../agents/discover-agents.ts");
606
658
  const store = new GoalStore(manifest.cwd);
@@ -639,7 +691,7 @@ async function main(): Promise<void> {
639
691
  };
640
692
  } else {
641
693
  // LAZY: defer dynamic import of ./dynamic-workflow-runner.ts to its call site.
642
- const { runDynamicWorkflow } = await import("./dynamic-workflow-runner.ts");
694
+ const { runDynamicWorkflow } = await import("./goal-workflow/dynamic-workflow-runner.ts");
643
695
  // LAZY: defer dynamic import of ../workflows/discover-workflows.ts to its call site.
644
696
  const { allWorkflows, discoverWorkflows } = await import("../workflows/discover-workflows.ts");
645
697
  const wf = allWorkflows(discoverWorkflows(manifest.cwd)).find((w) => w.name === manifest.workflow);
@@ -774,7 +826,7 @@ async function main(): Promise<void> {
774
826
  const manifestToUse = loaded?.manifest ?? manifest;
775
827
  if (manifestToUse) {
776
828
  // LAZY: live-agent-manager only needed on failure cleanup path; avoid module load at hot path.
777
- const { terminateLiveAgentsForRun } = await import("./live-agent-manager.ts");
829
+ const { terminateLiveAgentsForRun } = await import("./live-session/live-agent-manager.ts");
778
830
  void terminateLiveAgentsForRun(manifestToUse.runId, "failed", appendEvent, manifestToUse.eventsPath).catch((error) =>
779
831
  logInternalError("background-runner.terminate", error, `runId=${manifestToUse.runId}`),
780
832
  );
@@ -832,6 +884,27 @@ try {
832
884
  // called process.exit(1) directly, bypassing the finally block and leaving
833
885
  // orphaned child processes.
834
886
  exitDueToRejection = true;
887
+ // RT-3 FIX: Startup failures (lock-fail, missing manifest, pre-try throws)
888
+ // previously wrote NO event and left exitCode at 0. The run stayed 'queued'
889
+ // until the stale reconciler reaped it. Now write async.failed so the
890
+ // notifier/foreground detects the failure immediately, and set exitCode=1
891
+ // so the process exits non-zero.
892
+ try {
893
+ const mCwd = argValue("--cwd");
894
+ const mRunId = argValue("--run-id");
895
+ if (mCwd && mRunId) {
896
+ const mEventsPath = createRunPaths(mCwd, mRunId).eventsPath;
897
+ appendEvent(mEventsPath, {
898
+ type: "async.failed",
899
+ runId: mRunId,
900
+ message: errorMessage(err),
901
+ data: { stack: err instanceof Error ? err.stack : undefined },
902
+ });
903
+ }
904
+ } catch {
905
+ /* best-effort — don't let event-write failure mask the original error */
906
+ }
907
+ process.exitCode = 1;
835
908
  // FIX: Call stopParentGuard directly here as a safety net in case the
836
909
  // finally block (which calls runCleanup→stopParentGuard) does not complete.
837
910
  // This ensures the parent guard is stopped in ALL exit paths: normal
@@ -41,7 +41,7 @@ export interface StartChildBrokerClientOptions {
41
41
 
42
42
  const NOOP_HANDLE: ChildBrokerClientHandle = {
43
43
  active: false,
44
- close: async () => {},
44
+ close: async () => undefined,
45
45
  };
46
46
 
47
47
  export function startChildBrokerClient(options: StartChildBrokerClientOptions = {}): ChildBrokerClientHandle {
@@ -75,7 +75,7 @@ export function startChildBrokerClient(options: StartChildBrokerClientOptions =
75
75
  // Establish the persistent connection so the broker can push events. This
76
76
  // is fire-and-forget: on any failure the client becomes sticky-fallback and
77
77
  // the file-poll steering path remains fully functional.
78
- void client.reconnect().catch(() => {});
78
+ void client.reconnect().catch(() => undefined);
79
79
 
80
80
  return {
81
81
  get active() {
@@ -24,9 +24,9 @@
24
24
  import { randomUUID } from "node:crypto";
25
25
  import * as net from "node:net";
26
26
 
27
- import { logInternalError } from "../utils/internal-error.ts";
28
- import { BrokerError, encodeBrokerFrame, NdjsonDecoder } from "../utils/ndjson.ts";
29
- import { redactSecretString } from "../utils/redaction.ts";
27
+ import { logInternalError } from "../../utils/internal-error.ts";
28
+ import { BrokerError, encodeBrokerFrame, NdjsonDecoder } from "../../utils/ndjson.ts";
29
+ import { redactSecretString } from "../../utils/redaction.ts";
30
30
 
31
31
  /** Shape of an unsolicited event frame pushed by the broker (mailbox.message,
32
32
  * team.event, etc.). */
@@ -95,7 +95,6 @@ export class CrewBrokerClient {
95
95
  private socket: net.Socket | null = null;
96
96
  private decoder: NdjsonDecoder | null = null;
97
97
  private readonly pending = new Map<string, PendingRequest>();
98
- private attempts = 0;
99
98
  /** Listeners attached to the current socket; kept for explicit close(). */
100
99
  private readonly socketListeners: Array<{
101
100
  event: string;
@@ -234,7 +233,6 @@ export class CrewBrokerClient {
234
233
  async reconnect(): Promise<boolean> {
235
234
  // Close the current socket if any.
236
235
  this.teardownSocket();
237
- this.attempts = 0;
238
236
  this._mode = "unstarted";
239
237
  const res = await this.request("ping", null);
240
238
  return res.ok;
@@ -309,7 +307,6 @@ export class CrewBrokerClient {
309
307
  if (this.closed) {
310
308
  return { ok: false, fallback: true, errorCode: "closed" };
311
309
  }
312
- this.attempts = attempt + 1;
313
310
  const result = await this.attemptHello(netModule);
314
311
  if (result.ok) {
315
312
  // Belt-and-suspenders: a successful hello can still race
@@ -460,7 +457,7 @@ export class CrewBrokerClient {
460
457
  return;
461
458
  }
462
459
  // Hello ack: must include `result.ok === true` and matching id.
463
- if (frame.id && frame.id.startsWith("hello-")) {
460
+ if (frame.id?.startsWith("hello-")) {
464
461
  if (frame.error) {
465
462
  const code = (frame.error as { code?: string }).code ?? "auth";
466
463
  finish({ ok: false, fallback: true, errorCode: code });
@@ -23,8 +23,6 @@
23
23
 
24
24
  import * as fsp from "node:fs/promises";
25
25
  import * as net from "node:net";
26
-
27
- import { readEventsCursor } from "../state/event-log.ts";
28
26
  import {
29
27
  appendMailboxMessageAsync,
30
28
  type MailboxMessage,
@@ -32,14 +30,15 @@ import {
32
30
  type MailboxMessagePriority,
33
31
  readMailbox,
34
32
  registerMailboxAppendObserver,
35
- } from "../state/mailbox.ts";
36
- import { loadRunManifestById } from "../state/state-store.ts";
37
- import { runEventBus } from "../ui/run-event-bus.ts";
38
- import { logInternalError } from "../utils/internal-error.ts";
39
- import { BrokerError, encodeBrokerFrame, MAX_BROKER_FRAME_BYTES, NdjsonDecoder } from "../utils/ndjson.ts";
40
- import { redactSecretString } from "../utils/redaction.ts";
41
- import { resolveRealContainedPath } from "../utils/safe-paths.ts";
42
- import { getBrokerSocketPath, prepareBrokerSocketDir, removeStaleBrokerSocket } from "../utils/socket-path.ts";
33
+ } from "../../state/coordination/mailbox.ts";
34
+ import { readEventsCursor } from "../../state/event-log/event-log.ts";
35
+ import { loadRunManifestById } from "../../state/stores/state-store.ts";
36
+ import { runEventBus } from "../../ui/run-event-bus.ts";
37
+ import { logInternalError } from "../../utils/internal-error.ts";
38
+ import { BrokerError, encodeBrokerFrame, MAX_BROKER_FRAME_BYTES, NdjsonDecoder } from "../../utils/ndjson.ts";
39
+ import { redactSecretString } from "../../utils/redaction.ts";
40
+ import { resolveRealContainedPath } from "../../utils/safe-paths.ts";
41
+ import { getBrokerSocketPath, prepareBrokerSocketDir, removeStaleBrokerSocket } from "../../utils/socket-path.ts";
43
42
  import { BrokerTokenRegistry } from "./crew-broker-tokens.ts";
44
43
 
45
44
  /** Protocol version negotiated at `hello` time. Bump on breaking change. */
@@ -103,7 +102,6 @@ export class CrewBroker {
103
102
  private server: net.Server | null = null;
104
103
  private resolvedSocketPath: string | null = null;
105
104
  private stopped = false;
106
- private starting = false;
107
105
  private startingPromise: Promise<void> | null = null;
108
106
  private readonly connections = new Set<ServerConnection>();
109
107
  /** Connections indexed by runId for live message fanout (Phase 1.3). */
@@ -186,16 +184,10 @@ export class CrewBroker {
186
184
  }
187
185
  if (this.server) return Promise.resolve();
188
186
  if (this.startingPromise) return this.startingPromise;
189
- this.starting = true;
190
- this.startingPromise = this.doStart()
191
- .then(() => {
192
- this.starting = false;
193
- })
194
- .catch((err) => {
195
- this.starting = false;
196
- this.startingPromise = null;
197
- throw err;
198
- });
187
+ this.startingPromise = this.doStart().catch((err) => {
188
+ this.startingPromise = null;
189
+ throw err;
190
+ });
199
191
  return this.startingPromise;
200
192
  }
201
193
 
@@ -6,8 +6,6 @@
6
6
  * - Supports per-step overrides for model, skill, thinking
7
7
  * - Accumulates handoffs between steps
8
8
  * - Executes steps sequentially with context passing
9
- *
10
- * @see docs/pi-boomerang-integration-plan.md
11
9
  */
12
10
 
13
11
  import { errorMessage } from "../utils/guards.ts";
@@ -148,7 +146,7 @@ export class ChainRunner {
148
146
  }
149
147
  }
150
148
 
151
- const stepStrings = chainString.split("->").map((s) => s.trim());
149
+ const stepStrings = this.splitChainSteps(chainString).map((s) => s.trim());
152
150
 
153
151
  const steps: ChainStep[] = stepStrings.map((step, index) => {
154
152
  return this.parseStep(step, index);
@@ -236,8 +234,8 @@ export class ChainRunner {
236
234
 
237
235
  // Emit progress event if eventsPath provided
238
236
  if (eventsPath) {
239
- // LAZY: defer dynamic import of ../state/event-log.ts to its call site.
240
- const { appendEventAsync } = await import("../state/event-log.ts");
237
+ // LAZY: defer dynamic import of ../state/event-log/event-log.ts to its call site.
238
+ const { appendEventAsync } = await import("../state/event-log/event-log.ts");
241
239
  await appendEventAsync(eventsPath, {
242
240
  type: "chain.step_completed",
243
241
  runId: "chain",
@@ -277,6 +275,53 @@ export class ChainRunner {
277
275
  };
278
276
  }
279
277
 
278
+ /**
279
+ * Split a chain string on top-level `->` arrows, ignoring arrows inside
280
+ * quotes. Prevents inline goals containing `->` (e.g. "Change A -> B")
281
+ * from being incorrectly split into separate steps.
282
+ */
283
+ private splitChainSteps(chainString: string): string[] {
284
+ const parts: string[] = [];
285
+ let current = "";
286
+ let inQuote = false;
287
+ let quoteChar = "";
288
+
289
+ for (let i = 0; i < chainString.length; i++) {
290
+ const ch = chainString[i]!;
291
+
292
+ // Handle escape sequences inside quotes (e.g. \" or \')
293
+ if (inQuote && ch === "\\" && i + 1 < chainString.length) {
294
+ current += ch + (chainString[i + 1] ?? "");
295
+ i++;
296
+ continue;
297
+ }
298
+
299
+ // Toggle quote state on unescaped quote characters
300
+ if (ch === '"' || ch === "'") {
301
+ if (!inQuote) {
302
+ inQuote = true;
303
+ quoteChar = ch;
304
+ } else if (ch === quoteChar) {
305
+ inQuote = false;
306
+ }
307
+ current += ch;
308
+ continue;
309
+ }
310
+
311
+ // Split on -> only when not inside quotes
312
+ if (!inQuote && ch === "-" && chainString[i + 1] === ">") {
313
+ parts.push(current);
314
+ current = "";
315
+ i++; // skip the '>'
316
+ continue;
317
+ }
318
+
319
+ current += ch;
320
+ }
321
+ parts.push(current);
322
+ return parts;
323
+ }
324
+
280
325
  /**
281
326
  * Parse a single step from the chain string.
282
327
  * Includes type safety checks for ChainStep parsing (H3).
@@ -8,7 +8,7 @@
8
8
  * and makes the timing budget configurable from one place.
9
9
  */
10
10
 
11
- import { DEFAULT_CHILD_PI } from "../config/defaults.ts";
11
+ import { DEFAULT_CHILD_PI } from "../../config/defaults.ts";
12
12
 
13
13
  /** Post-exit window during which stdio is guarded against late writes. */
14
14
  export const POST_EXIT_STDIO_GUARD_MS = DEFAULT_CHILD_PI.postExitStdioGuardMs;
@@ -17,10 +17,10 @@
17
17
 
18
18
  import type { ChildProcess } from "node:child_process";
19
19
  import { spawn } from "node:child_process";
20
- import { DEFAULT_CHILD_PI } from "../config/defaults.ts";
21
- import { logInternalError } from "../utils/internal-error.ts";
20
+ import { DEFAULT_CHILD_PI } from "../../config/defaults.ts";
21
+ import { logInternalError } from "../../utils/internal-error.ts";
22
+ import { TailCaptureStage } from "../compaction/compact-stages/tail-capture-stage.ts";
22
23
  import { HARD_KILL_MS } from "./child-pi-constants.ts";
23
- import { TailCaptureStage } from "./compact-stages/tail-capture-stage.ts";
24
24
 
25
25
  const MAX_CAPTURE_BYTES = DEFAULT_CHILD_PI.maxCaptureBytes;
26
26
 
@@ -19,12 +19,12 @@
19
19
  import type { SpawnOptions } from "node:child_process";
20
20
  import * as fs from "node:fs";
21
21
  import * as path from "node:path";
22
- import { WINDOWS_ESSENTIAL_ENV_VARS } from "../utils/env-allowlist.ts";
23
- import { buildScopedAllowList, sanitizeEnvSecrets } from "../utils/env-filter.ts";
24
- import { logInternalError } from "../utils/internal-error.ts";
22
+ import { WINDOWS_ESSENTIAL_ENV_VARS } from "../../utils/env-allowlist.ts";
23
+ import { buildScopedAllowList, sanitizeEnvSecrets } from "../../utils/env-filter.ts";
24
+ import { logInternalError } from "../../utils/internal-error.ts";
25
+ import { buildPiWorkerArgs } from "../model/pi-args.ts";
26
+ import { getPiSpawnCommand } from "../pi-spawn.ts";
25
27
  import type { ChildPiRunInput, ChildPiRunResult } from "./child-pi.ts";
26
- import { buildPiWorkerArgs } from "./pi-args.ts";
27
- import { getPiSpawnCommand } from "./pi-spawn.ts";
28
28
 
29
29
  // ── Env allowlist (base set always passed to children) ──────────────────
30
30
  // Provider API keys are injected dynamically via buildScopedAllowList() only
@@ -68,7 +68,8 @@ export const BASE_ALLOWLIST: string[] = [
68
68
  * 1. Validate cwd (realpath + isDirectory) — fall back to lexical path on ENOENT.
69
69
  * 2. Filter env vars to the allowlist (model-aware provider key scoping).
70
70
  * 3. Validate NODE_PATH against safe prefixes (/opt, /lib, /usr, /home).
71
- * 4. Add PI_CREW_PARENT_PID for the child-side parent-guard.
71
+ * 4. Add PI_CREW_PARENT_PID for the child-side parent-guard (currently UNUSED —
72
+ * see note at the PI_CREW_PARENT_PID assignment below).
72
73
  */
73
74
  export function buildChildPiSpawnOptions(cwd: string, env: NodeJS.ProcessEnv, model?: string): SpawnOptions {
74
75
  // SECURITY FIX (Issue #1): Validate cwd before passing to spawn.
@@ -131,17 +132,32 @@ export function buildChildPiSpawnOptions(cwd: string, env: NodeJS.ProcessEnv, mo
131
132
 
132
133
  return {
133
134
  cwd: validatedCwd,
134
- env: { ...filteredEnv, PI_CREW_PARENT_PID: String(process.pid) },
135
+ env: {
136
+ ...filteredEnv,
137
+ // PI_CREW_PARENT_PID is set so that child pi workers could theoretically
138
+ // run a parent-guard (parent-guard.ts). However, as of RT-19/RT-2, this
139
+ // env var has ZERO consumers: child workers are the external `pi` binary
140
+ // (@earendil-works/pi-coding-agent) which does NOT read
141
+ // PI_CREW_PARENT_PID or call startParentGuard (grep of Pi dist = 0 matches).
142
+ // The deeper fix (wiring startParentGuard into the pi worker entry point)
143
+ // is DEFERRED because workers are an external binary pi-crew doesn't control.
144
+ //
145
+ // Orphan-mitigation for this gap relies on:
146
+ // 1. The RT-2 SIGINT fix in background-runner.ts (abort + exitCode pattern
147
+ // lets the finally/runCleanup block terminate child-pi processes).
148
+ // 2. The reactive zombie-scanner.ts sweep (finds workers whose
149
+ // PI_CREW_PARENT_PID points at a dead PID).
150
+ PI_CREW_PARENT_PID: String(process.pid),
151
+ },
135
152
  stdio: ["ignore", "pipe", "pipe"], // stdin=ignore: child doesn't wait for input; task comes via CLI args
136
153
  detached: process.platform !== "win32",
137
154
  setsid: true,
138
155
  // NOTE: setsid creates a new session; the child process becomes the session leader
139
156
  // and its parent becomes that session leader (still the team-runner in the same
140
- // process group). PI_CREW_PARENT_PID is set before spawn using process.pid (team-runner).
141
- // The parent-guard in the child checks direct parent liveness via process.kill(pid, 0) —
142
- // it does NOT follow the lineage beyond the direct parent. If the team-runner's parent
143
- // (the original pi session) dies, the team-runner becomes an orphan but the child still
144
- // sees its direct parent (team-runner) as alive. This is correct for the parent-guard model.
157
+ // process group). PI_CREW_PARENT_PID is set before spawn using process.pid (team-runner),
158
+ // but see the comment above — child pi workers do NOT actually consume it. The
159
+ // parent-guard model would check direct parent liveness via process.kill(pid, 0),
160
+ // but this is only implemented in background-runner.ts, not in the worker binary.
145
161
  windowsHide: true,
146
162
  } as SpawnOptions;
147
163
  }
@@ -13,7 +13,7 @@
13
13
  */
14
14
 
15
15
  import * as fs from "node:fs";
16
- import { logInternalError } from "../utils/internal-error.ts";
16
+ import { logInternalError } from "../../utils/internal-error.ts";
17
17
  import { killProcessTree } from "./child-pi-kill.ts";
18
18
 
19
19
  /** Action emitted by the controller when a `turn_end` event is processed. */
@@ -8,11 +8,11 @@
8
8
  * child-pi-constants.ts (MAX_*_CHARS, MAX_LINE_BUFFER_BYTES).
9
9
  */
10
10
 
11
- import { logInternalError } from "../utils/internal-error.ts";
11
+ import { logInternalError } from "../../utils/internal-error.ts";
12
+ import { extractText } from "../output/pi-json-output.ts";
12
13
  import type { ChildPiRunInput } from "./child-pi.ts";
13
14
  import { MAX_ASSISTANT_TEXT_CHARS, MAX_LINE_BUFFER_BYTES, MAX_TOOL_INPUT_CHARS, MAX_TOOL_RESULT_CHARS } from "./child-pi-constants.ts";
14
15
  import { appendTranscript, compactString, compactValue, flushPendingTranscriptWrites } from "./child-pi-transcript.ts";
15
- import { extractText } from "./pi-json-output.ts";
16
16
 
17
17
  function asRecord(value: unknown): Record<string, unknown> | undefined {
18
18
  if (!value || typeof value !== "object" || Array.isArray(value)) return undefined;
@@ -87,6 +87,18 @@ function compactChildPiEvent(event: unknown): unknown | undefined {
87
87
  stopReason: record.stopReason,
88
88
  };
89
89
  }
90
+ // P2-25: pass supervisor_contact events through with their full payload. The
91
+ // generic fallthrough below strips everything but `type`, which made the
92
+ // feature dead — the contact never reached recordSupervisorContact.
93
+ if (record.type === "supervisor_contact" || record.type === "crew_supervisor_contact") {
94
+ return {
95
+ type: record.type,
96
+ taskId: record.taskId,
97
+ reason: record.reason,
98
+ message: record.message,
99
+ data: record.data,
100
+ };
101
+ }
90
102
  return record.type ? { type: record.type } : undefined;
91
103
  }
92
104
 
@@ -187,9 +199,16 @@ export class ChildPiLineObserver {
187
199
  new Error(`Line buffer exceeded ${MAX_LINE_BUFFER_BYTES} bytes; force-flushing`),
188
200
  `bufferLen=${this.buffer.length}`,
189
201
  );
190
- const line = this.buffer;
202
+ // RT-9: split on newlines first so each complete \n-delimited JSON event
203
+ // is emitted as its own line. Previously the ENTIRE buffer was flushed
204
+ // as one "line", merging all events → JSON.parse failure → usage/turn/
205
+ // transcript events lost. Now only the trailing partial (no terminating
206
+ // newline) is force-flushed; complete lines keep per-line identity. When
207
+ // there are no newlines at all (the original RT-F8 single-huge-line case)
208
+ // split yields a single element and behavior is unchanged.
209
+ const overflowLines = this.buffer.split(/\r?\n/);
191
210
  this.buffer = "";
192
- this.emitLine(line);
211
+ for (const line of overflowLines) this.emitLine(line);
193
212
  return;
194
213
  }
195
214
  const lines = this.buffer.split(/\r?\n/);
@@ -19,13 +19,13 @@
19
19
  */
20
20
 
21
21
  import * as fs from "node:fs";
22
- import { DEFAULT_CHILD_PI } from "../config/defaults.ts";
23
- import { logInternalError } from "../utils/internal-error.ts";
24
- import { redactJsonLine } from "../utils/redaction.ts";
25
- import { resolveRealContainedPath } from "../utils/safe-paths.ts";
22
+ import { DEFAULT_CHILD_PI } from "../../config/defaults.ts";
23
+ import { logInternalError } from "../../utils/internal-error.ts";
24
+ import { redactJsonLine } from "../../utils/redaction.ts";
25
+ import { resolveRealContainedPath } from "../../utils/safe-paths.ts";
26
+ import { applyCompactPipeline } from "../compaction/compact-pipeline.ts";
27
+ import { TruncationStage } from "../compaction/compact-stages/truncation-stage.ts";
26
28
  import type { ChildPiRunInput } from "./child-pi.ts";
27
- import { applyCompactPipeline } from "./compact-pipeline.ts";
28
- import { TruncationStage } from "./compact-stages/truncation-stage.ts";
29
29
 
30
30
  // ── Transcript batch buffer (OPT-PHASE3) ────────────────────────────────
31
31
  // Instead of open/write/close per line (3 syscalls × N), accumulate lines