pi-crew 0.9.56 → 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 (307) hide show
  1. package/AGENTS.md +1 -1
  2. package/CHANGELOG.md +29 -0
  3. package/README.md +53 -5
  4. package/dist/index.mjs +5350 -4350
  5. package/docs/README.md +54 -0
  6. package/package.json +12 -12
  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 +108 -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 +24 -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 +11 -5
  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 +1 -1
  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} +11 -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} +28 -72
  87. package/src/runtime/{compact-stages → compaction/compact-stages}/bounded-tail.ts +1 -1
  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/custom-tools/irc-tool.ts +2 -2
  92. package/src/runtime/delivery-coordinator.ts +0 -3
  93. package/src/runtime/diagnostic-export.ts +3 -3
  94. package/src/runtime/foreground-control.ts +1 -1
  95. package/src/runtime/foreground-watchdog.ts +1 -1
  96. package/src/runtime/{adaptive-plan.ts → goal-workflow/adaptive-plan.ts} +8 -8
  97. package/src/runtime/{dynamic-workflow-context.ts → goal-workflow/dynamic-workflow-context.ts} +16 -16
  98. package/src/runtime/{dynamic-workflow-runner.ts → goal-workflow/dynamic-workflow-runner.ts} +10 -10
  99. package/src/runtime/{goal-achievement.ts → goal-workflow/goal-achievement.ts} +3 -3
  100. package/src/runtime/{goal-evaluator.ts → goal-workflow/goal-evaluator.ts} +8 -8
  101. package/src/runtime/{goal-loop-runner.ts → goal-workflow/goal-loop-runner.ts} +27 -25
  102. package/src/runtime/{goal-state-store.ts → goal-workflow/goal-state-store.ts} +8 -8
  103. package/src/runtime/{plan-templates.ts → goal-workflow/plan-templates.ts} +1 -1
  104. package/src/runtime/group-join.ts +3 -3
  105. package/src/runtime/handoff-manager.ts +0 -2
  106. package/src/runtime/{heartbeat-watcher.ts → heartbeat/heartbeat-watcher.ts} +7 -7
  107. package/src/runtime/iteration-hooks.ts +1 -1
  108. package/src/runtime/{live-agent-control.ts → live-session/live-agent-control.ts} +2 -2
  109. package/src/runtime/{live-agent-manager.ts → live-session/live-agent-manager.ts} +4 -4
  110. package/src/runtime/{live-session-runtime.ts → live-session/live-session-runtime.ts} +39 -41
  111. package/src/runtime/manifest-cache.ts +1 -1
  112. package/src/runtime/{model-fallback.ts → model/model-fallback.ts} +1 -1
  113. package/src/runtime/{pi-args.ts → model/pi-args.ts} +20 -5
  114. package/src/runtime/model/provider-extensions.ts +116 -0
  115. package/src/runtime/{runtime-policy.ts → model/runtime-policy.ts} +2 -2
  116. package/src/runtime/{runtime-resolver.ts → model/runtime-resolver.ts} +3 -3
  117. package/src/runtime/{runtime-warmup.ts → model/runtime-warmup.ts} +8 -8
  118. package/src/runtime/orphan-worker-registry.ts +1 -1
  119. package/src/runtime/{progress-tracker.ts → output/progress-tracker.ts} +1 -1
  120. package/src/runtime/{result-extractor.ts → output/result-extractor.ts} +4 -4
  121. package/src/runtime/{sidechain-output.ts → output/sidechain-output.ts} +2 -2
  122. package/src/runtime/{stream-preview.ts → output/stream-preview.ts} +1 -1
  123. package/src/runtime/{streaming-output.ts → output/streaming-output.ts} +2 -2
  124. package/src/runtime/{tool-progress.ts → output/tool-progress.ts} +1 -1
  125. package/src/runtime/parent-guard.ts +30 -10
  126. package/src/runtime/pipeline-runner.ts +2 -2
  127. package/src/runtime/policy-engine.ts +2 -2
  128. package/src/runtime/{cancellation.ts → process/cancellation.ts} +1 -1
  129. package/src/runtime/{post-exit-stdio-guard.ts → process/post-exit-stdio-guard.ts} +1 -1
  130. package/src/runtime/{process-lifecycle.ts → process/process-lifecycle.ts} +3 -3
  131. package/src/runtime/{checkpoint.ts → recovery/checkpoint.ts} +4 -4
  132. package/src/runtime/{crash-recovery.ts → recovery/crash-recovery.ts} +93 -21
  133. package/src/runtime/{overflow-recovery.ts → recovery/overflow-recovery.ts} +1 -1
  134. package/src/runtime/{recovery-recipes.ts → recovery/recovery-recipes.ts} +1 -1
  135. package/src/runtime/{retry-executor.ts → recovery/retry-executor.ts} +2 -2
  136. package/src/runtime/{retry-runner.ts → recovery/retry-runner.ts} +2 -8
  137. package/src/runtime/run-tracker.ts +1 -1
  138. package/src/runtime/run-worker.ts +3 -3
  139. package/src/runtime/{coalesce-tasks.ts → scheduling/coalesce-tasks.ts} +3 -3
  140. package/src/runtime/{concurrency.ts → scheduling/concurrency.ts} +1 -1
  141. package/src/runtime/{parallel-research.ts → scheduling/parallel-research.ts} +4 -2
  142. package/src/runtime/{run-coalesced-task-group.ts → scheduling/run-coalesced-task-group.ts} +95 -36
  143. package/src/runtime/{task-graph-scheduler.ts → scheduling/task-graph-scheduler.ts} +1 -1
  144. package/src/runtime/session-resources.ts +1 -1
  145. package/src/runtime/settings-store.ts +1 -1
  146. package/src/runtime/single-agent-compose.ts +1 -0
  147. package/src/runtime/stale-reconciler.ts +1 -1
  148. package/src/runtime/subagent-manager.ts +1 -1
  149. package/src/runtime/supervisor-contact.ts +1 -1
  150. package/src/runtime/task-output-context.ts +7 -5
  151. package/src/runtime/task-runner/child-executor.ts +62 -22
  152. package/src/runtime/task-runner/live-executor.ts +8 -8
  153. package/src/runtime/task-runner/post-execution.ts +12 -12
  154. package/src/runtime/task-runner/pre-execution.ts +6 -6
  155. package/src/runtime/task-runner/progress.ts +1 -1
  156. package/src/runtime/task-runner/run-projection.ts +1 -1
  157. package/src/runtime/task-runner/scaffold-executor.ts +1 -1
  158. package/src/runtime/task-runner/state-helpers.ts +2 -2
  159. package/src/runtime/task-runner.ts +4 -4
  160. package/src/runtime/team-runner.ts +380 -139
  161. package/src/runtime/{completion-guard.ts → verification/completion-guard.ts} +1 -1
  162. package/src/runtime/{green-contract.ts → verification/green-contract.ts} +1 -1
  163. package/src/runtime/{post-checks.ts → verification/post-checks.ts} +4 -4
  164. package/src/runtime/{verification-gates.ts → verification/verification-gates.ts} +5 -5
  165. package/src/runtime/{verification-integrity.ts → verification/verification-integrity.ts} +1 -1
  166. package/src/schema/config-schema.ts +1 -0
  167. package/src/schema/team-tool-schema.ts +133 -177
  168. package/src/state/README.md +44 -0
  169. package/src/state/atomic-write.ts +24 -3
  170. package/src/state/{locks.ts → coordination/locks.ts} +118 -38
  171. package/src/state/{mailbox.ts → coordination/mailbox.ts} +14 -8
  172. package/src/state/{schedule.ts → coordination/schedule.ts} +3 -3
  173. package/src/state/{task-claims.ts → coordination/task-claims.ts} +1 -1
  174. package/src/state/decision-ledger.ts +1 -1
  175. package/src/state/{event-log-rotation.ts → event-log/event-log-rotation.ts} +161 -31
  176. package/src/state/{event-log.ts → event-log/event-log.ts} +38 -26
  177. package/src/state/{jsonl-writer.ts → event-log/jsonl-writer.ts} +14 -6
  178. package/src/state/hook-instinct-bridge.ts +2 -2
  179. package/src/state/{active-run-registry.ts → stores/active-run-registry.ts} +7 -7
  180. package/src/state/{artifact-store.ts → stores/artifact-store.ts} +62 -4
  181. package/src/state/{blob-store.ts → stores/blob-store.ts} +3 -3
  182. package/src/state/{health-store.ts → stores/health-store.ts} +3 -3
  183. package/src/state/{instinct-store.ts → stores/instinct-store.ts} +3 -3
  184. package/src/state/{observation-store.ts → stores/observation-store.ts} +2 -2
  185. package/src/state/{run-cache.ts → stores/run-cache.ts} +4 -4
  186. package/src/state/{run-graph.ts → stores/run-graph.ts} +4 -4
  187. package/src/state/{run-metrics.ts → stores/run-metrics.ts} +3 -3
  188. package/src/state/{state-store.ts → stores/state-store.ts} +236 -17
  189. package/src/state/types.ts +27 -3
  190. package/src/ui/component.ts +55 -0
  191. package/src/ui/crew-select-list.ts +3 -1
  192. package/src/ui/dashboard-panes/agents-pane.ts +1 -1
  193. package/src/ui/dwf-phase-display.ts +1 -1
  194. package/src/ui/heartbeat-aggregator.ts +1 -1
  195. package/src/ui/keybinding-map.ts +179 -6
  196. package/src/ui/layout-primitives.ts +9 -5
  197. package/src/ui/live-conversation-overlay.ts +1 -1
  198. package/src/ui/live-run-sidebar.ts +1 -1
  199. package/src/ui/overlay-stack.ts +148 -0
  200. package/src/ui/overlays/agent-picker-overlay.ts +1 -1
  201. package/src/ui/overlays/mailbox-compose-overlay.ts +1 -1
  202. package/src/ui/overlays/mailbox-detail-overlay.ts +2 -2
  203. package/src/ui/powerbar-publisher.ts +1 -1
  204. package/src/ui/render-scheduler.ts +1 -1
  205. package/src/ui/run-action-dispatcher.ts +3 -3
  206. package/src/ui/run-dashboard.ts +5 -33
  207. package/src/ui/run-event-bus.ts +3 -3
  208. package/src/ui/run-snapshot-cache.ts +37 -18
  209. package/src/ui/settings-overlay.ts +13 -21
  210. package/src/ui/snapshot-types.ts +1 -1
  211. package/src/ui/status-colors.ts +0 -2
  212. package/src/ui/terminal-status.ts +1 -1
  213. package/src/ui/theme-adapter.ts +33 -13
  214. package/src/ui/tool-renderers/index.ts +8 -0
  215. package/src/ui/transcript-viewer.ts +37 -13
  216. package/src/ui/widget/index.ts +32 -9
  217. package/src/ui/widget/widget-formatters.ts +62 -4
  218. package/src/ui/widget/widget-model.ts +2 -2
  219. package/src/ui/widget/widget-renderer.ts +1 -1
  220. package/src/utils/conflict-detect.ts +58 -5
  221. package/src/utils/ndjson.ts +2 -2
  222. package/src/utils/socket-path.ts +1 -1
  223. package/src/workflows/workflow-config.ts +1 -1
  224. package/src/worktree/cleanup.ts +1 -1
  225. package/src/worktree/worktree-manager.ts +88 -39
  226. package/workflows/chain.workflow.md +2 -2
  227. package/index.bundle.ts +0 -25
  228. package/scripts/bench-check.mjs +0 -96
  229. package/scripts/bench-cold-start.mjs +0 -216
  230. package/scripts/build-bundle.mjs +0 -92
  231. package/scripts/check-all-skills.ts +0 -294
  232. package/scripts/check-bundle-size.mjs +0 -38
  233. package/scripts/check-bundle-staleness.mjs +0 -97
  234. package/scripts/check-conflict-markers.mjs +0 -91
  235. package/scripts/check-lazy-imports.mjs +0 -28
  236. package/scripts/dev-runner.mjs +0 -106
  237. package/scripts/profile-startup.mjs +0 -125
  238. package/scripts/release-smoke.mjs +0 -74
  239. package/scripts/run-bench.mjs +0 -47
  240. package/scripts/run-real-chain.ts +0 -41
  241. package/scripts/smoke-team-perf.ts +0 -43
  242. package/scripts/test-issue-29-crash.ts +0 -111
  243. package/scripts/test-issue-29-e2e.ts +0 -183
  244. package/scripts/test-issue-29-real-runtime.ts +0 -330
  245. package/scripts/test-issue-29-real-tasks.ts +0 -387
  246. package/scripts/test-issue-29-team-tool.ts +0 -105
  247. package/scripts/test-runner.mjs +0 -84
  248. package/scripts/verify-flicker-fix.ts +0 -109
  249. package/scripts/verify-skill.ts +0 -550
  250. package/scripts/watch-bundle.mjs +0 -259
  251. package/scripts/watchdog-harness.ts +0 -119
  252. package/src/adapters/claude-adapter.ts +0 -23
  253. package/src/adapters/codex-adapter.ts +0 -21
  254. package/src/adapters/cursor-adapter.ts +0 -17
  255. package/src/adapters/export-util.ts +0 -143
  256. package/src/adapters/index.ts +0 -15
  257. package/src/adapters/registry.ts +0 -18
  258. package/src/adapters/types.ts +0 -23
  259. package/src/runtime/code-summary.ts +0 -292
  260. package/src/runtime/cross-extension-rpc.ts +0 -143
  261. package/src/runtime/hidden-handoff.ts +0 -407
  262. package/src/runtime/prose-compressor.ts +0 -162
  263. package/src/subagents/async-entry.ts +0 -1
  264. package/src/subagents/index.ts +0 -3
  265. package/src/subagents/live/control.ts +0 -1
  266. package/src/subagents/live/manager.ts +0 -1
  267. package/src/subagents/live/realtime.ts +0 -1
  268. package/src/subagents/live/session-runtime.ts +0 -1
  269. package/src/subagents/manager.ts +0 -1
  270. package/src/subagents/spawn.ts +0 -1
  271. package/test-integration-check.ts +0 -118
  272. /package/src/runtime/{broker-issuer.ts → broker/broker-issuer.ts} +0 -0
  273. /package/src/runtime/{crew-broker-tokens.ts → broker/crew-broker-tokens.ts} +0 -0
  274. /package/src/runtime/{child-pi-pool.ts → child-pi/child-pi-pool.ts} +0 -0
  275. /package/src/runtime/{compact-pipeline.ts → compaction/compact-pipeline.ts} +0 -0
  276. /package/src/runtime/{compact-stages → compaction/compact-stages}/ansi-strip-stage.ts +0 -0
  277. /package/src/runtime/{compact-stages → compaction/compact-stages}/blank-collapse-stage.ts +0 -0
  278. /package/src/runtime/{compact-stages → compaction/compact-stages}/deduplicate-stage.ts +0 -0
  279. /package/src/runtime/{compact-stages → compaction/compact-stages}/head-snap-stage.ts +0 -0
  280. /package/src/runtime/{compact-stages → compaction/compact-stages}/tail-capture-stage.ts +0 -0
  281. /package/src/runtime/{compact-stages → compaction/compact-stages}/truncation-stage.ts +0 -0
  282. /package/src/runtime/{important-line-classifier.ts → compaction/important-line-classifier.ts} +0 -0
  283. /package/src/runtime/{heartbeat-gradient.ts → heartbeat/heartbeat-gradient.ts} +0 -0
  284. /package/src/runtime/{worker-heartbeat.ts → heartbeat/worker-heartbeat.ts} +0 -0
  285. /package/src/runtime/{worker-startup.ts → heartbeat/worker-startup.ts} +0 -0
  286. /package/src/runtime/{intercom-bridge.ts → live-session/intercom-bridge.ts} +0 -0
  287. /package/src/runtime/{live-control-realtime.ts → live-session/live-control-realtime.ts} +0 -0
  288. /package/src/runtime/{live-extension-bridge.ts → live-session/live-extension-bridge.ts} +0 -0
  289. /package/src/runtime/{live-irc.ts → live-session/live-irc.ts} +0 -0
  290. /package/src/runtime/{live-session-health.ts → live-session/live-session-health.ts} +0 -0
  291. /package/src/runtime/{model-resolver.ts → model/model-resolver.ts} +0 -0
  292. /package/src/runtime/{model-scope.ts → model/model-scope.ts} +0 -0
  293. /package/src/runtime/{output-validator.ts → output/output-validator.ts} +0 -0
  294. /package/src/runtime/{pi-json-output.ts → output/pi-json-output.ts} +0 -0
  295. /package/src/runtime/{progress-event-coalescer.ts → output/progress-event-coalescer.ts} +0 -0
  296. /package/src/runtime/{cancellation-token.ts → process/cancellation-token.ts} +0 -0
  297. /package/src/runtime/{zombie-scanner.ts → process/zombie-scanner.ts} +0 -0
  298. /package/src/runtime/{crash-classification.ts → recovery/crash-classification.ts} +0 -0
  299. /package/src/runtime/{batch-barrier.ts → scheduling/batch-barrier.ts} +0 -0
  300. /package/src/runtime/{global-worker-cap.ts → scheduling/global-worker-cap.ts} +0 -0
  301. /package/src/runtime/{parallel-utils.ts → scheduling/parallel-utils.ts} +0 -0
  302. /package/src/runtime/{scheduler.ts → scheduling/scheduler.ts} +0 -0
  303. /package/src/runtime/{semaphore.ts → scheduling/semaphore.ts} +0 -0
  304. /package/src/runtime/{task-graph.ts → scheduling/task-graph.ts} +0 -0
  305. /package/src/runtime/{verification-worktree.ts → verification/verification-worktree.ts} +0 -0
  306. /package/src/state/{event-reconstructor.ts → event-log/event-reconstructor.ts} +0 -0
  307. /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 TSchema, 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 TSchema, 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
 
@@ -356,11 +338,39 @@ export const AutomateDomainParams = Type.Object({ action: automateActions, ...sh
356
338
  * (RunDomainParams etc.) are kept for the facade dispatch + backward compat.
357
339
  */
358
340
  export const allActionLiterals = ([runActions, statusActions, controlActions, manageActions, automateActions] as TSchema[]).flatMap(
359
- (set) => set.anyOf ?? [],
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
+ },
360
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
+
361
366
  export const TeamToolParams = Type.Object(
362
367
  {
363
- action: Type.Optional(Type.Union(allActionLiterals, { description: ACTION_DESCRIPTION })),
368
+ action: Type.Optional(
369
+ stringEnum(
370
+ allActionLiterals.map((l) => (l as { const: string }).const),
371
+ ACTION_DESCRIPTION,
372
+ ),
373
+ ),
364
374
  ...sharedFields,
365
375
  },
366
376
  { additionalProperties: true },
@@ -370,61 +380,7 @@ export const TeamToolParams = Type.Object(
370
380
  export type TeamDomain = "run" | "status" | "control" | "manage" | "automate";
371
381
 
372
382
  export interface TeamToolParamsValue {
373
- action?:
374
- | "run"
375
- | "parallel"
376
- | "plan"
377
- | "status"
378
- | "wait"
379
- | "list"
380
- | "get"
381
- | "cancel"
382
- | "retry"
383
- | "resume"
384
- | "respond"
385
- | "create"
386
- | "update"
387
- | "delete"
388
- | "doctor"
389
- | "cleanup"
390
- | "events"
391
- | "artifacts"
392
- | "worktrees"
393
- | "forget"
394
- | "summary"
395
- | "prune"
396
- | "export"
397
- | "import"
398
- | "imports"
399
- | "help"
400
- | "validate"
401
- | "config"
402
- | "init"
403
- | "recommend"
404
- | "autonomy"
405
- | "api"
406
- | "settings"
407
- | "steer"
408
- | "invalidate"
409
- | "health"
410
- | "graph"
411
- | "onboard"
412
- | "explain"
413
- | "cache"
414
- | "checkpoint"
415
- | "search"
416
- | "orchestrate"
417
- | "schedule"
418
- | "scheduled"
419
- | "anchor"
420
- | "auto-summarize"
421
- | "auto_boomerang"
422
- | "goal"
423
- | "workflow-create"
424
- | "workflow-get"
425
- | "workflow-list"
426
- | "workflow-save"
427
- | "workflow-delete";
383
+ action?: TeamAction;
428
384
  resource?: "agent" | "team" | "workflow";
429
385
  team?: string;
430
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();