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
@@ -1,5 +1,5 @@
1
1
  import * as fs from "node:fs";
2
- import type { TeamRunManifest, TeamTaskState } from "../state/types.ts";
2
+ import type { TeamRunManifest, TeamTaskState } from "../../state/types.ts";
3
3
 
4
4
  // ============================================================================
5
5
  // Phase 1.2: Completion Mutation Guard — detects tasks that claim success but
@@ -1,4 +1,4 @@
1
- import type { GreenLevel, VerificationContract, VerificationEvidence } from "../state/types.ts";
1
+ import type { GreenLevel, VerificationContract, VerificationEvidence } from "../../state/types.ts";
2
2
 
3
3
  const GREEN_ORDER: Record<GreenLevel, number> = {
4
4
  none: 0,
@@ -5,10 +5,10 @@
5
5
  * Distilled from pi-autoresearch's post-check / backpressure pattern.
6
6
  */
7
7
  import { execFileSync } from "node:child_process";
8
- import { WINDOWS_ESSENTIAL_ENV_VARS } from "../utils/env-allowlist.ts";
9
- import { sanitizeEnvSecrets } from "../utils/env-filter.ts";
10
- import { resolveShellForScript } from "../utils/resolve-shell.ts";
11
- import { resolveRealContainedPath } from "../utils/safe-paths.ts";
8
+ import { WINDOWS_ESSENTIAL_ENV_VARS } from "../../utils/env-allowlist.ts";
9
+ import { sanitizeEnvSecrets } from "../../utils/env-filter.ts";
10
+ import { resolveShellForScript } from "../../utils/resolve-shell.ts";
11
+ import { resolveRealContainedPath } from "../../utils/safe-paths.ts";
12
12
 
13
13
  /** Default timeout for post-check scripts (5 minutes). */
14
14
  const DEFAULT_TIMEOUT_MS = 300_000;
@@ -12,11 +12,11 @@
12
12
  import { spawn } from "node:child_process";
13
13
  import * as fs from "node:fs";
14
14
  import * as path from "node:path";
15
- import { writeArtifact } from "../state/artifact-store.ts";
16
- import type { GreenLevel, VerificationCommandResult, VerificationContract } from "../state/types.ts";
17
- import { WINDOWS_ESSENTIAL_ENV_VARS } from "../utils/env-allowlist.ts";
18
- import { sanitizeEnvSecrets } from "../utils/env-filter.ts";
19
- import { redactSecretString } from "../utils/redaction.ts";
15
+ import { writeArtifact } from "../../state/stores/artifact-store.ts";
16
+ import type { GreenLevel, VerificationCommandResult, VerificationContract } from "../../state/types.ts";
17
+ import { WINDOWS_ESSENTIAL_ENV_VARS } from "../../utils/env-allowlist.ts";
18
+ import { sanitizeEnvSecrets } from "../../utils/env-filter.ts";
19
+ import { redactSecretString } from "../../utils/redaction.ts";
20
20
 
21
21
  /**
22
22
  * Phase 1.5 #1 (RFC 13 §6 info-disclosure mitigation): sanitize the env passed
@@ -28,7 +28,7 @@
28
28
  import { createHash } from "node:crypto";
29
29
  import * as fs from "node:fs";
30
30
  import * as path from "node:path";
31
- import { logInternalError } from "../utils/internal-error.ts";
31
+ import { logInternalError } from "../../utils/internal-error.ts";
32
32
 
33
33
  /**
34
34
  * Fixed set of project-manifest files considered by {@link snapshotManifests}.
@@ -72,6 +72,7 @@ export const PiTeamsRuntimeConfigSchema = Type.Object(
72
72
  ),
73
73
  ),
74
74
  excludeContextBash: Type.Optional(Type.Boolean()),
75
+ agentExtensions: Type.Optional(Type.Array(Type.String({ minLength: 1 }))),
75
76
  isolationPolicy: Type.Optional(
76
77
  Type.Object(
77
78
  {
@@ -1,4 +1,25 @@
1
- import { Type } from "@sinclair/typebox";
1
+ import { type TSchema, Type, TypeRegistry } from "@sinclair/typebox";
2
+
3
+ // ─────────────────────────────────────────────────────────────────────────
4
+ // EXT-7: Compact enum action schema. TypeBox has no built-in type that
5
+ // produces JSON Schema `{ enum: [...] }` AND validates via Value.Check.
6
+ // Type.Unsafe defaults to Kind 'Unsafe' which Value.Check throws on.
7
+ // We register a custom 'StringEnum' kind once: JSON Schema consumers (the
8
+ // LLM tool definition) see the compact `{ type: "string", enum: [...] }`
9
+ // (~600 chars vs ~1890 for anyOf+const), and Value.Check validates via
10
+ // the registered predicate.
11
+ // ─────────────────────────────────────────────────────────────────────────
12
+ TypeRegistry.Set("StringEnum", (schema, value) => {
13
+ const s = schema as { enum?: unknown[] };
14
+ return typeof value === "string" && Array.isArray(s.enum) && s.enum.includes(value);
15
+ });
16
+ const KIND = Symbol.for("TypeBox.Kind");
17
+ function stringEnum(values: readonly string[], description: string): TSchema {
18
+ // Empty string accepted: calling models emit "" for unset action; the handler
19
+ // treats it as omitted (defaults to "list"). Must be a plain enum member so
20
+ // JSON-Schema consumers (pi-ai validation) accept it without coercion.
21
+ return Type.Unsafe({ [KIND]: "StringEnum", type: "string", enum: ["", ...values], description });
22
+ }
2
23
 
3
24
  // ───────────────────────────────────────────────────────────────────────────
4
25
  // API-5 facade split: the 54-action mega-tool schema is split into 5 domain
@@ -8,24 +29,18 @@ import { Type } from "@sinclair/typebox";
8
29
  // `TeamToolParams` is re-exported as a TypeBox Union for backward compat.
9
30
  // ───────────────────────────────────────────────────────────────────────────
10
31
 
11
- const SkillOverride = Type.Unsafe({
12
- description:
13
- "Skill name(s) to add to role/default skills, an array of skill names, or false to disable all injected skills for this run.",
14
- anyOf: [
15
- { type: "string", maxLength: 2048 },
16
- {
17
- type: "array",
18
- maxItems: 32,
19
- items: { type: "string", maxLength: 80 },
20
- },
21
- { type: "boolean" },
22
- ],
23
- });
32
+ const SkillOverride = Type.Union(
33
+ [Type.String({ maxLength: 2048 }), Type.Array(Type.String({ maxLength: 80 }), { maxItems: 32 }), Type.Boolean()],
34
+ {
35
+ description:
36
+ "Skill name(s) to add to role/default skills, an array of skill names, or false to disable all injected skills for this run.",
37
+ },
38
+ );
24
39
 
25
- const FreeformConfig = Type.Unsafe({
40
+ // Freeform object: Type.Record + Any is TypeBox-native (no custom Unsafe kind),
41
+ // so Value.Check/Compile can walk it without throwing "Unknown type".
42
+ const FreeformConfig = Type.Record(Type.String(), Type.Any(), {
26
43
  description: "Resource config for management actions.",
27
- type: "object",
28
- additionalProperties: true,
29
44
  });
30
45
 
31
46
  /**
@@ -38,7 +53,7 @@ const FreeformConfig = Type.Unsafe({
38
53
  */
39
54
  const sharedFields = {
40
55
  resource: Type.Optional(
41
- Type.Union([Type.Literal("agent"), Type.Literal("team"), Type.Literal("workflow")], {
56
+ Type.Union([Type.Literal(""), Type.Literal("agent"), Type.Literal("team"), Type.Literal("workflow")], {
42
57
  description: "Resource kind for get/create/update/delete/list. Defaults to all for list.",
43
58
  }),
44
59
  ),
@@ -75,7 +90,9 @@ const sharedFields = {
75
90
  runId: Type.Optional(
76
91
  Type.String({
77
92
  description: "Run ID for status, cancel, or resume.",
78
- pattern: "^[A-Za-z0-9_-]+$",
93
+ // Empty string allowed: calling models emit "" for unset keys; the handler
94
+ // treats it as omitted (see normalizeTeamParams in team-tool.ts).
95
+ pattern: "^$|^[A-Za-z0-9_-]+$",
79
96
  }),
80
97
  ),
81
98
  taskId: Type.Optional(Type.String({ description: "Task ID for respond action." })),
@@ -93,12 +110,12 @@ const sharedFields = {
93
110
  }),
94
111
  ),
95
112
  workspaceMode: Type.Optional(
96
- Type.Union([Type.Literal("single"), Type.Literal("worktree")], {
113
+ Type.Union([Type.Literal(""), Type.Literal("single"), Type.Literal("worktree")], {
97
114
  description: "Workspace isolation mode. Worktree mode is planned after MVP.",
98
115
  }),
99
116
  ),
100
117
  context: Type.Optional(
101
- Type.Union([Type.Literal("fresh"), Type.Literal("fork")], {
118
+ Type.Union([Type.Literal(""), Type.Literal("fresh"), Type.Literal("fork")], {
102
119
  description: "Child context mode for workers.",
103
120
  }),
104
121
  ),
@@ -106,7 +123,7 @@ const sharedFields = {
106
123
  model: Type.Optional(Type.String({ description: "Model override for direct runs." })),
107
124
  skill: Type.Optional(SkillOverride),
108
125
  scope: Type.Optional(
109
- Type.Union([Type.Literal("user"), Type.Literal("project"), Type.Literal("both")], {
126
+ Type.Union([Type.Literal(""), Type.Literal("user"), Type.Literal("project"), Type.Literal("both")], {
110
127
  description: "Resource scope for discovery or management.",
111
128
  }),
112
129
  ),
@@ -127,8 +144,8 @@ const sharedFields = {
127
144
  }),
128
145
  ),
129
146
  keep: Type.Optional(
130
- Type.Integer({
131
- minimum: 0,
147
+ // Boolean false accepted: calling models emit false for unset; treated as omitted.
148
+ Type.Union([Type.Boolean(), Type.Integer({ minimum: 0 })], {
132
149
  description: "Number of finished runs to keep for prune.",
133
150
  }),
134
151
  ),
@@ -162,7 +179,8 @@ const sharedFields = {
162
179
  }),
163
180
  ),
164
181
  once: Type.Optional(
165
- Type.Union([Type.String(), Type.Number()], {
182
+ // Boolean false accepted: calling models emit false for unset; treated as omitted.
183
+ Type.Union([Type.Boolean(), Type.String(), Type.Number()], {
166
184
  description: "ISO timestamp or epoch ms for a one-time scheduled run.",
167
185
  }),
168
186
  ),
@@ -172,11 +190,20 @@ const sharedFields = {
172
190
  }),
173
191
  ),
174
192
  budgetTotal: Type.Optional(
175
- Type.Number({
176
- description:
177
- "Total token budget for the run. When set, enables budget tracking with default 80% warning and 95% abort thresholds. Minimum 1000 — this is a MISCONFIGURATION GUARD (catches typos / silent-abort configs like budgetTotal:1, which would abort on turn 1), NOT a usefulness guarantee; a productive multi-turn goal needs far more than 1000 tokens.",
178
- minimum: 1000,
179
- }),
193
+ // 0 accepted as "unset/disabled" (models emit 0 for off); still rejects 1-999
194
+ // as the MISCONFIGURATION GUARD against typo'd silent-abort configs.
195
+ Type.Union(
196
+ [
197
+ Type.Literal(0),
198
+ Type.Number({
199
+ minimum: 1000,
200
+ }),
201
+ ],
202
+ {
203
+ description:
204
+ "Total token budget for the run. When set, enables budget tracking with default 80% warning and 95% abort thresholds. Minimum 1000 — this is a MISCONFIGURATION GUARD (catches typos / silent-abort configs like budgetTotal:1, which would abort on turn 1), NOT a usefulness guarantee; a productive multi-turn goal needs far more than 1000 tokens. 0 = unset.",
205
+ },
206
+ ),
180
207
  ),
181
208
  budgetUnlimited: Type.Optional(
182
209
  Type.Boolean({
@@ -201,7 +228,7 @@ const sharedFields = {
201
228
  }),
202
229
  ),
203
230
  runKind: Type.Optional(
204
- Type.Union([Type.Literal("team-run"), Type.Literal("goal-loop"), Type.Literal("dynamic-workflow")], {
231
+ Type.Union([Type.Literal(""), Type.Literal("team-run"), Type.Literal("goal-loop"), Type.Literal("dynamic-workflow")], {
205
232
  description:
206
233
  'Background dispatch discriminator. Default "team-run" runs the normal executeTeamRun workflow; "goal-loop" (P0/P1) and "dynamic-workflow" (P2/P3) dispatch to their respective background runners. Absent = "team-run" for backward compatibility.',
207
234
  }),
@@ -239,99 +266,54 @@ const sharedFields = {
239
266
 
240
267
  const ACTION_DESCRIPTION = "Team action. Defaults to 'list' when omitted.";
241
268
 
242
- const runActions = Type.Optional(
243
- Type.Union(
244
- [
245
- Type.Literal("run"),
246
- Type.Literal("parallel"),
247
- Type.Literal("plan"),
248
- Type.Literal("orchestrate"),
249
- Type.Literal("resume"),
250
- Type.Literal("retry"),
251
- Type.Literal("wait"),
252
- Type.Literal("steer"),
253
- Type.Literal("goal"),
254
- ],
255
- { description: ACTION_DESCRIPTION },
256
- ),
257
- );
269
+ const RUN_ACTIONS = ["run", "parallel", "plan", "orchestrate", "resume", "retry", "wait", "steer", "goal"] as const;
270
+ const runActions = Type.Optional(stringEnum(RUN_ACTIONS, ACTION_DESCRIPTION));
258
271
 
259
- const statusActions = Type.Optional(
260
- Type.Union(
261
- [
262
- Type.Literal("status"),
263
- Type.Literal("list"),
264
- Type.Literal("get"),
265
- Type.Literal("events"),
266
- Type.Literal("artifacts"),
267
- Type.Literal("summary"),
268
- Type.Literal("graph"),
269
- Type.Literal("search"),
270
- Type.Literal("health"),
271
- Type.Literal("worktrees"),
272
- Type.Literal("checkpoint"),
273
- Type.Literal("cache"),
274
- Type.Literal("explain"),
275
- Type.Literal("onboard"),
276
- Type.Literal("recommend"),
277
- Type.Literal("help"),
278
- ],
279
- { description: ACTION_DESCRIPTION },
280
- ),
281
- );
272
+ const STATUS_ACTIONS = [
273
+ "status",
274
+ "list",
275
+ "get",
276
+ "events",
277
+ "artifacts",
278
+ "summary",
279
+ "graph",
280
+ "search",
281
+ "health",
282
+ "worktrees",
283
+ "checkpoint",
284
+ "cache",
285
+ "explain",
286
+ "onboard",
287
+ "recommend",
288
+ "help",
289
+ ] as const;
290
+ const statusActions = Type.Optional(stringEnum(STATUS_ACTIONS, ACTION_DESCRIPTION));
282
291
 
283
- const controlActions = Type.Optional(
284
- Type.Union(
285
- [
286
- Type.Literal("cancel"),
287
- Type.Literal("invalidate"),
288
- Type.Literal("respond"),
289
- Type.Literal("cleanup"),
290
- Type.Literal("prune"),
291
- Type.Literal("forget"),
292
- Type.Literal("doctor"),
293
- ],
294
- { description: ACTION_DESCRIPTION },
295
- ),
296
- );
292
+ const CONTROL_ACTIONS = ["cancel", "invalidate", "respond", "cleanup", "prune", "forget", "doctor"] as const;
293
+ const controlActions = Type.Optional(stringEnum(CONTROL_ACTIONS, ACTION_DESCRIPTION));
297
294
 
298
- const manageActions = Type.Optional(
299
- Type.Union(
300
- [
301
- Type.Literal("create"),
302
- Type.Literal("update"),
303
- Type.Literal("delete"),
304
- Type.Literal("init"),
305
- Type.Literal("config"),
306
- Type.Literal("validate"),
307
- Type.Literal("autonomy"),
308
- Type.Literal("settings"),
309
- Type.Literal("workflow-create"),
310
- Type.Literal("workflow-get"),
311
- Type.Literal("workflow-list"),
312
- Type.Literal("workflow-save"),
313
- Type.Literal("workflow-delete"),
314
- Type.Literal("import"),
315
- Type.Literal("imports"),
316
- Type.Literal("export"),
317
- ],
318
- { description: ACTION_DESCRIPTION },
319
- ),
320
- );
295
+ const MANAGE_ACTIONS = [
296
+ "create",
297
+ "update",
298
+ "delete",
299
+ "init",
300
+ "config",
301
+ "validate",
302
+ "autonomy",
303
+ "settings",
304
+ "workflow-create",
305
+ "workflow-get",
306
+ "workflow-list",
307
+ "workflow-save",
308
+ "workflow-delete",
309
+ "import",
310
+ "imports",
311
+ "export",
312
+ ] as const;
313
+ const manageActions = Type.Optional(stringEnum(MANAGE_ACTIONS, ACTION_DESCRIPTION));
321
314
 
322
- const automateActions = Type.Optional(
323
- Type.Union(
324
- [
325
- Type.Literal("schedule"),
326
- Type.Literal("scheduled"),
327
- Type.Literal("anchor"),
328
- Type.Literal("auto-summarize"),
329
- Type.Literal("auto_boomerang"),
330
- Type.Literal("api"),
331
- ],
332
- { description: ACTION_DESCRIPTION },
333
- ),
334
- );
315
+ const AUTOMATE_ACTIONS = ["schedule", "scheduled", "anchor", "auto-summarize", "auto_boomerang", "api"] as const;
316
+ const automateActions = Type.Optional(stringEnum(AUTOMATE_ACTIONS, ACTION_DESCRIPTION));
335
317
 
336
318
  // ─── Domain schemas (additionalProperties: true — Phase 1, not tightened) ────
337
319
 
@@ -346,77 +328,59 @@ export const ManageDomainParams = Type.Object({ action: manageActions, ...shared
346
328
  export const AutomateDomainParams = Type.Object({ action: automateActions, ...sharedFields }, { additionalProperties: true });
347
329
 
348
330
  /**
349
- * Backward-compatible re-export: Union of all 5 domain schemas.
350
- * Replaces the former flat 54-action Object — validation surface is identical
351
- * (all fields optional in every variant, additionalProperties: true).
331
+ * LLM-facing team-tool schema. FLAT single Object (all actions + all shared
332
+ * fields optional) — NOT the former 5-variant Type.Union. The union emitted a
333
+ * giant `anyOf` (each variant repeated all ~30 shared fields) that LLM
334
+ * tool-callers could not reliably satisfy → empty/malformed calls → params
335
+ * dropped → the tool defaulted to `list`. The handler validates per-action
336
+ * fields at runtime (defense-in-depth, team-tool.ts execute()), so the schema
337
+ * only needs to enumerate valid actions + field types. Domain objects above
338
+ * (RunDomainParams etc.) are kept for the facade dispatch + backward compat.
352
339
  */
353
- export const TeamToolParams = Type.Union([
354
- RunDomainParams,
355
- StatusDomainParams,
356
- ControlDomainParams,
357
- ManageDomainParams,
358
- AutomateDomainParams,
359
- ]);
340
+ export const allActionLiterals = ([runActions, statusActions, controlActions, manageActions, automateActions] as TSchema[]).flatMap(
341
+ (set) => {
342
+ const literals =
343
+ (set.anyOf as { const: string }[] | undefined) ??
344
+ (Array.isArray(set.enum) ? set.enum.map((v: unknown) => ({ const: v })) : []) ??
345
+ [];
346
+ // stringEnum accepts "" as an unset marker for model callers; it is NOT an
347
+ // action, so exclude it from the literal/type/suggestion derivations.
348
+ return literals.filter((l) => l.const !== "");
349
+ },
350
+ );
351
+
352
+ /**
353
+ * EXT-8: Single source of truth for the valid `action` type. Derived from the
354
+ * domain action arrays (the same arrays `allActionLiterals` reads), so the
355
+ * `TeamToolParamsValue.action` interface field, schema enum, and suggestions
356
+ * can never drift apart. Adding/removing an action in any `*_ACTIONS` array
357
+ * propagates to every consumer automatically.
358
+ */
359
+ export type TeamAction =
360
+ | (typeof RUN_ACTIONS)[number]
361
+ | (typeof STATUS_ACTIONS)[number]
362
+ | (typeof CONTROL_ACTIONS)[number]
363
+ | (typeof MANAGE_ACTIONS)[number]
364
+ | (typeof AUTOMATE_ACTIONS)[number];
365
+
366
+ export const TeamToolParams = Type.Object(
367
+ {
368
+ action: Type.Optional(
369
+ stringEnum(
370
+ allActionLiterals.map((l) => (l as { const: string }).const),
371
+ ACTION_DESCRIPTION,
372
+ ),
373
+ ),
374
+ ...sharedFields,
375
+ },
376
+ { additionalProperties: true },
377
+ );
360
378
 
361
379
  /** Domain discriminator for the facade dispatch. */
362
380
  export type TeamDomain = "run" | "status" | "control" | "manage" | "automate";
363
381
 
364
382
  export interface TeamToolParamsValue {
365
- action?:
366
- | "run"
367
- | "parallel"
368
- | "plan"
369
- | "status"
370
- | "wait"
371
- | "list"
372
- | "get"
373
- | "cancel"
374
- | "retry"
375
- | "resume"
376
- | "respond"
377
- | "create"
378
- | "update"
379
- | "delete"
380
- | "doctor"
381
- | "cleanup"
382
- | "events"
383
- | "artifacts"
384
- | "worktrees"
385
- | "forget"
386
- | "summary"
387
- | "prune"
388
- | "export"
389
- | "import"
390
- | "imports"
391
- | "help"
392
- | "validate"
393
- | "config"
394
- | "init"
395
- | "recommend"
396
- | "autonomy"
397
- | "api"
398
- | "settings"
399
- | "steer"
400
- | "invalidate"
401
- | "health"
402
- | "graph"
403
- | "onboard"
404
- | "explain"
405
- | "cache"
406
- | "checkpoint"
407
- | "search"
408
- | "orchestrate"
409
- | "schedule"
410
- | "scheduled"
411
- | "anchor"
412
- | "auto-summarize"
413
- | "auto_boomerang"
414
- | "goal"
415
- | "workflow-create"
416
- | "workflow-get"
417
- | "workflow-list"
418
- | "workflow-save"
419
- | "workflow-delete";
383
+ action?: TeamAction;
420
384
  resource?: "agent" | "team" | "workflow";
421
385
  team?: string;
422
386
  workflow?: string;
@@ -0,0 +1,44 @@
1
+ # src/state/ — Cluster Map
2
+
3
+ Persistent state layer for pi-crew runs. Organized into subdirectories by
4
+ responsibility (Phase 7 reorg).
5
+
6
+ ## Root (shared infrastructure)
7
+
8
+ | File | Responsibility |
9
+ |------|---------------|
10
+ | `types.ts` | Core state types (`TeamRunManifest`, `TeamTaskState`, schema versions) |
11
+ | `types-eval.ts` | Evaluation-related types |
12
+ | `contracts.ts` | Status transitions, task-status enums |
13
+ | `tiered-eval.ts` | Tiered evaluation logic |
14
+ | `crew-init.ts` | Crew initialization / bootstrap |
15
+ | `decision-ledger.ts` | Decision recording |
16
+ | `gitignore-manager.ts` | `.gitignore` management for crew dirs |
17
+ | `hook-instinct-bridge.ts` | Bridge between hooks and instinct store |
18
+ | `hook-integrations.ts` | Hook integration points |
19
+ | `session-state-map.ts` | Session → state mapping |
20
+ | `usage.ts` | Token/cost usage tracking |
21
+ | `atomic-write.ts` | Atomic file writes (widely used — kept at root) |
22
+
23
+ ## `event-log/` — Event journaling
24
+
25
+ Event append/read, rotation/compaction, JSONL writing, and worker-thread
26
+ atomic writes.
27
+
28
+ - `event-log.ts`, `event-log-rotation.ts`, `event-reconstructor.ts`,
29
+ `jsonl-writer.ts`, `worker-atomic-writer.ts`
30
+
31
+ ## `stores/` — Persisted data stores
32
+
33
+ Manifest, artifact, blob, observation, health, instinct, run-graph/metrics
34
+ stores, and the active-run registry.
35
+
36
+ - `state-store.ts`, `run-cache.ts`, `artifact-store.ts`, `blob-store.ts`,
37
+ `observation-store.ts`, `health-store.ts`, `instinct-store.ts`,
38
+ `active-run-registry.ts`, `run-graph.ts`, `run-metrics.ts`
39
+
40
+ ## `coordination/` — Concurrency & IPC
41
+
42
+ File locks, mailbox messaging, task scheduling, and task claims.
43
+
44
+ - `locks.ts`, `mailbox.ts`, `schedule.ts`, `task-claims.ts`
@@ -4,7 +4,7 @@ import * as os from "node:os";
4
4
  import * as path from "node:path";
5
5
  import { logInternalError } from "../utils/internal-error.ts";
6
6
  import { sleepSync } from "../utils/sleep.ts";
7
- import { atomicWriteFileViaWorker, isWorkerAtomicWriterEnabled } from "./worker-atomic-writer.ts";
7
+ import { atomicWriteFileViaWorker, isWorkerAtomicWriterEnabled } from "./event-log/worker-atomic-writer.ts";
8
8
 
9
9
  function hashContent(content: string): string {
10
10
  return crypto.createHash("sha256").update(content, "utf-8").digest("hex");
@@ -596,8 +596,16 @@ export function atomicWriteFile(filePath: string, content: string, options?: Ato
596
596
  // Write temp with restrictive permissions
597
597
  const O_NOFOLLOW = typeof fs.constants.O_NOFOLLOW === "number" ? fs.constants.O_NOFOLLOW : 0;
598
598
  let fd: number | undefined;
599
+ // ST-7: track the temp-file lifecycle independently of `fd`. `fd` is cleared
600
+ // (set to undefined) right after the successful close — BEFORE the rename
601
+ // attempt (line ~621) — so gating temp cleanup on `fd !== undefined` leaks
602
+ // the temp file when the rename fails. `tempNeedsCleanup` stays true from
603
+ // open until the rename consumes the temp, so the finally block always
604
+ // removes a leftover instead of being skipped by the stale `fd` guard.
605
+ let tempNeedsCleanup = false;
599
606
  try {
600
607
  fd = fs.openSync(tempPath, fs.constants.O_WRONLY | fs.constants.O_CREAT | fs.constants.O_EXCL | O_NOFOLLOW, mode ?? 0o600);
608
+ tempNeedsCleanup = true; // ST-7: temp file now exists on disk
601
609
  // Post-open verification: on Windows O_NOFOLLOW is 0, so verify FD is a regular file
602
610
  const openedStat = fs.fstatSync(fd);
603
611
  if (!openedStat.isFile()) {
@@ -636,6 +644,10 @@ export function atomicWriteFile(filePath: string, content: string, options?: Ato
636
644
  }
637
645
  // Issue 1 fix: use link+unlink instead of rename to avoid following symlinks
638
646
  renameWithLinkSync(tempPath, filePath);
647
+ // ST-7: rename consumed the temp file — it no longer exists at tempPath, so
648
+ // the finally block must NOT try to remove it (and must not be gated on
649
+ // `fd`, which is already undefined here from the close at line ~621).
650
+ tempNeedsCleanup = false;
639
651
  // FIX (2026-07-01 mailbox-replay CI flake): fsync the parent directory
640
652
  // after rename so the directory entry update is durable. On most Linux
641
653
  // filesystems, rename is journaled but the journal flush isn't always
@@ -679,8 +691,12 @@ export function atomicWriteFile(filePath: string, content: string, options?: Ato
679
691
  throw renameError;
680
692
  }
681
693
  } finally {
682
- // Issue 4 fix: always clean up temp file, regardless of success or error path.
683
- // This ensures no orphaned temp files remain when errors occur at any point.
694
+ // ST-7: temp cleanup must be gated on `tempNeedsCleanup` (whether a leftover
695
+ // temp file still exists), NOT on `fd !== undefined`. `fd` is set to undefined
696
+ // right after the successful close — BEFORE the rename attempt — so the old
697
+ // `fd !== undefined` guard skipped `rmSync(tempPath)` whenever the rename
698
+ // failed, leaking the temp file. `tempNeedsCleanup` is true from open until
699
+ // the rename consumes the temp, so this branch fires exactly when needed.
684
700
  if (fd !== undefined) {
685
701
  // A-02: close fd if still open before cleanup. If an error occurred
686
702
  // between openSync and the normal closeSync (e.g. fstat/write/fsync
@@ -691,6 +707,8 @@ export function atomicWriteFile(filePath: string, content: string, options?: Ato
691
707
  } catch {
692
708
  /* fd may already be closed */
693
709
  }
710
+ }
711
+ if (tempNeedsCleanup) {
694
712
  try {
695
713
  fs.rmSync(tempPath, { force: true });
696
714
  } catch {
@@ -764,6 +782,9 @@ export async function atomicWriteFileAsync(filePath: string, content: string, op
764
782
  // A-02: close fd if still open before cleanup. If an error occurred
765
783
  // between open and the normal close (e.g. stat/writeFile/sync threw),
766
784
  // the FileHandle would leak.
785
+ // ST-7: unlike the sync path, the async `rm(tempPath)` below is
786
+ // UNCONDITIONAL (not gated on `fd`), so a rename failure here does NOT
787
+ // leak the temp file. Verified during the ST-7 audit — no fix needed.
767
788
  if (fd) {
768
789
  try {
769
790
  await fd.close();