@duckmind/dm-windows-x64 0.61.4 → 0.61.9

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 (279) hide show
  1. package/dm.exe +0 -0
  2. package/extensions/.dm-extensions.json +211 -67
  3. package/extensions/dm-subagents/agents/claude-code-writer.md +15 -0
  4. package/extensions/dm-subagents/agents/claude-code.md +15 -0
  5. package/extensions/dm-subagents/agents/codex-exec-writer.md +15 -0
  6. package/extensions/dm-subagents/agents/codex-exec.md +15 -0
  7. package/extensions/dm-subagents/agents/cursor-agent-writer.md +14 -0
  8. package/extensions/dm-subagents/agents/cursor-agent.md +14 -0
  9. package/extensions/dm-subagents/agents/delegate.md +3 -2
  10. package/extensions/dm-subagents/agents/oracle.md +10 -5
  11. package/extensions/dm-subagents/agents/researcher.md +2 -2
  12. package/extensions/dm-subagents/agents/reviewer.md +17 -7
  13. package/extensions/dm-subagents/agents/scout.md +5 -5
  14. package/extensions/dm-subagents/agents/worker.md +6 -2
  15. package/extensions/dm-subagents/async-retention-discovery-worker.mjs +167 -0
  16. package/extensions/dm-subagents/index.js +4 -0
  17. package/extensions/dm-subagents/inspector-runner.mjs +10 -0
  18. package/extensions/dm-subagents/install.mjs +3 -2
  19. package/extensions/dm-subagents/package.json +2 -2
  20. package/extensions/dm-subagents/prompts/council.md +60 -0
  21. package/extensions/dm-subagents/prompts/parallel-review.md +5 -1
  22. package/extensions/dm-subagents/prompts/review-loop.md +13 -7
  23. package/extensions/dm-subagents/skills/council-mode/SKILL.md +59 -0
  24. package/extensions/dm-subagents/skills/council-mode/references/pass-contracts.md +150 -0
  25. package/extensions/dm-subagents/skills/dm-subagents/SKILL.md +96 -911
  26. package/extensions/dm-subagents/skills/dm-subagents/references/constraints-and-recipes.md +70 -0
  27. package/extensions/dm-subagents/skills/dm-subagents/references/execution-controls.md +539 -0
  28. package/extensions/dm-subagents/skills/dm-subagents/references/management-authoring-rpc.md +161 -0
  29. package/extensions/dm-subagents/skills/dm-subagents/references/multi-lane-orchestration.md +51 -0
  30. package/extensions/dm-subagents/skills/dm-subagents/references/prompting-and-roles.md +295 -0
  31. package/extensions/dm-subagents/skills/dm-subagents/references/review-and-validation.md +73 -0
  32. package/extensions/dm-subagents/src/agents/agent-management.js +716 -484
  33. package/extensions/dm-subagents/src/agents/agent-refinements.js +563 -0
  34. package/extensions/dm-subagents/src/agents/agent-serializer.js +70 -5
  35. package/extensions/dm-subagents/src/agents/agents.js +1447 -299
  36. package/extensions/dm-subagents/src/agents/builtin-names.js +15 -0
  37. package/extensions/dm-subagents/src/agents/chain-serializer.js +12 -7
  38. package/extensions/dm-subagents/src/agents/frontmatter.js +64 -14
  39. package/extensions/dm-subagents/src/agents/identity.js +1 -1
  40. package/extensions/dm-subagents/src/agents/proactive-skills.js +14 -11
  41. package/extensions/dm-subagents/src/agents/runtime-agent-events.js +49 -0
  42. package/extensions/dm-subagents/src/agents/runtime-agent-registry.js +412 -0
  43. package/extensions/dm-subagents/src/agents/skills.js +52 -35
  44. package/extensions/dm-subagents/src/api/agents.js +6 -0
  45. package/extensions/dm-subagents/src/api/background-work.js +151 -0
  46. package/extensions/dm-subagents/src/api/capability-ceiling.js +12 -0
  47. package/extensions/dm-subagents/src/api/control-channel.js +3 -0
  48. package/extensions/dm-subagents/src/api/delegation.js +5 -0
  49. package/extensions/dm-subagents/src/api/dm-args.js +3 -0
  50. package/extensions/dm-subagents/src/api/external-job-provider.js +137 -0
  51. package/extensions/dm-subagents/src/api/external-runs.js +233 -0
  52. package/extensions/dm-subagents/src/api/intercom-bridge.js +3 -0
  53. package/extensions/dm-subagents/src/api/preflight.js +322 -0
  54. package/extensions/dm-subagents/src/api/project-panes.js +11 -0
  55. package/extensions/dm-subagents/src/api/shared-types.js +4 -0
  56. package/extensions/dm-subagents/src/extension/config.js +175 -0
  57. package/extensions/dm-subagents/src/extension/control-notices.js +4 -43
  58. package/extensions/dm-subagents/src/extension/doctor.js +71 -17
  59. package/extensions/dm-subagents/src/extension/fanout-child.js +49 -32
  60. package/extensions/dm-subagents/src/extension/index.js +711 -265
  61. package/extensions/dm-subagents/src/extension/public-execution.js +114 -0
  62. package/extensions/dm-subagents/src/extension/rpc.js +427 -22
  63. package/extensions/dm-subagents/src/extension/schemas.js +158 -65
  64. package/extensions/dm-subagents/src/extension/steering-notices.js +23 -0
  65. package/extensions/dm-subagents/src/extension/subagent-guide.js +31 -0
  66. package/extensions/dm-subagents/src/extension/tool-description.js +92 -74
  67. package/extensions/dm-subagents/src/extension/tool-result.js +7 -0
  68. package/extensions/dm-subagents/src/inspectors/herdr/actions.js +218 -0
  69. package/extensions/dm-subagents/src/inspectors/herdr/client.js +123 -0
  70. package/extensions/dm-subagents/src/inspectors/herdr/focus.js +47 -0
  71. package/extensions/dm-subagents/src/inspectors/herdr/inspector-runner.js +160 -0
  72. package/extensions/dm-subagents/src/inspectors/herdr/project-panes.js +618 -0
  73. package/extensions/dm-subagents/src/inspectors/herdr/session-roots-codec.js +21 -0
  74. package/extensions/dm-subagents/src/inspectors/herdr/shell-command.js +15 -0
  75. package/extensions/dm-subagents/src/integrations/herdr-status.js +377 -0
  76. package/extensions/dm-subagents/src/intercom/intercom-bridge.js +17 -13
  77. package/extensions/dm-subagents/src/intercom/native-supervisor-channel.js +371 -79
  78. package/extensions/dm-subagents/src/intercom/result-intercom.js +47 -7
  79. package/extensions/dm-subagents/src/missions/actions.js +394 -0
  80. package/extensions/dm-subagents/src/missions/goal-driver.js +149 -0
  81. package/extensions/dm-subagents/src/missions/lifecycle.js +331 -0
  82. package/extensions/dm-subagents/src/missions/store.js +548 -0
  83. package/extensions/dm-subagents/src/missions/types.js +9 -0
  84. package/extensions/dm-subagents/src/missions/workflow-state.js +245 -0
  85. package/extensions/dm-subagents/src/policy/authority.js +37 -0
  86. package/extensions/dm-subagents/src/profiles/profiles.js +36 -18
  87. package/extensions/dm-subagents/src/runs/background/active-async-capacity.js +427 -0
  88. package/extensions/dm-subagents/src/runs/background/active-run-index.js +122 -0
  89. package/extensions/dm-subagents/src/runs/background/async-execution.js +925 -137
  90. package/extensions/dm-subagents/src/runs/background/async-job-tracker.js +515 -148
  91. package/extensions/dm-subagents/src/runs/background/async-resume.js +378 -51
  92. package/extensions/dm-subagents/src/runs/background/async-retention.js +828 -0
  93. package/extensions/dm-subagents/src/runs/background/async-status-snapshot.js +31 -0
  94. package/extensions/dm-subagents/src/runs/background/async-status.js +259 -22
  95. package/extensions/dm-subagents/src/runs/background/auto-drain.js +46 -0
  96. package/extensions/dm-subagents/src/runs/background/chain-append.js +50 -15
  97. package/extensions/dm-subagents/src/runs/background/chain-root-attachment.js +67 -12
  98. package/extensions/dm-subagents/src/runs/background/completion-batcher.js +5 -1
  99. package/extensions/dm-subagents/src/runs/background/completion-dedupe.js +3 -11
  100. package/extensions/dm-subagents/src/runs/background/completion-replay.js +245 -0
  101. package/extensions/dm-subagents/src/runs/background/control-channel.js +423 -36
  102. package/extensions/dm-subagents/src/runs/background/fleet-view.js +132 -59
  103. package/extensions/dm-subagents/src/runs/background/index-segment.js +38 -0
  104. package/extensions/dm-subagents/src/runs/background/inspect-rpc.js +373 -0
  105. package/extensions/dm-subagents/src/runs/background/notify.js +357 -57
  106. package/extensions/dm-subagents/src/runs/background/owned-process-tree.js +86 -0
  107. package/extensions/dm-subagents/src/runs/background/process-terminal.js +269 -0
  108. package/extensions/dm-subagents/src/runs/background/result-delivery-ownership.js +34 -0
  109. package/extensions/dm-subagents/src/runs/background/result-files.js +469 -0
  110. package/extensions/dm-subagents/src/runs/background/result-watcher.js +540 -75
  111. package/extensions/dm-subagents/src/runs/background/resume-guidance.js +44 -0
  112. package/extensions/dm-subagents/src/runs/background/retained-children.js +119 -0
  113. package/extensions/dm-subagents/src/runs/background/run-id-query.js +5 -0
  114. package/extensions/dm-subagents/src/runs/background/run-id-resolver.js +93 -9
  115. package/extensions/dm-subagents/src/runs/background/run-status.js +303 -32
  116. package/extensions/dm-subagents/src/runs/background/scheduled-runs.js +784 -376
  117. package/extensions/dm-subagents/src/runs/background/stale-run-reconciler.js +75 -34
  118. package/extensions/dm-subagents/src/runs/background/steering.js +221 -0
  119. package/extensions/dm-subagents/src/runs/background/subagent-runner.js +3106 -844
  120. package/extensions/dm-subagents/src/runs/background/subagent-wait.js +529 -0
  121. package/extensions/dm-subagents/src/runs/background/terminal-run-index.js +106 -0
  122. package/extensions/dm-subagents/src/runs/background/top-level-async.js +1 -1
  123. package/extensions/dm-subagents/src/runs/background/wait-completions.js +155 -0
  124. package/extensions/dm-subagents/src/runs/background/wait-config.js +46 -0
  125. package/extensions/dm-subagents/src/runs/background/wait-subscriptions.js +278 -0
  126. package/extensions/dm-subagents/src/runs/background/wait-tool.js +47 -0
  127. package/extensions/dm-subagents/src/runs/foreground/async-dismiss-action.js +81 -0
  128. package/extensions/dm-subagents/src/runs/foreground/async-steering-action.js +245 -0
  129. package/extensions/dm-subagents/src/runs/foreground/async-stop-action.js +74 -0
  130. package/extensions/dm-subagents/src/runs/foreground/execution.js +1401 -342
  131. package/extensions/dm-subagents/src/runs/foreground/foreground-control.js +133 -0
  132. package/extensions/dm-subagents/src/runs/foreground/foreground-history.js +148 -0
  133. package/extensions/dm-subagents/src/runs/foreground/prompt-audit.js +139 -0
  134. package/extensions/dm-subagents/src/runs/foreground/subagent-executor.js +4278 -1441
  135. package/extensions/dm-subagents/src/runs/foreground/workflow-detach-reconcile.js +278 -0
  136. package/extensions/dm-subagents/src/runs/foreground/workflow-foreground-steering.js +155 -0
  137. package/extensions/dm-subagents/src/runs/shared/abort-recovery.js +97 -0
  138. package/extensions/dm-subagents/src/runs/shared/acceptance.js +597 -148
  139. package/extensions/dm-subagents/src/runs/shared/agent-contract.js +35 -0
  140. package/extensions/dm-subagents/src/runs/shared/async-status-projection.js +472 -0
  141. package/extensions/dm-subagents/src/runs/shared/background-process-options.js +6 -0
  142. package/extensions/dm-subagents/src/runs/shared/capability-ceiling.js +175 -0
  143. package/extensions/dm-subagents/src/runs/shared/child-identity.js +32 -0
  144. package/extensions/dm-subagents/src/runs/shared/child-launch-plan.js +65 -0
  145. package/extensions/dm-subagents/src/runs/shared/child-protocol.js +447 -0
  146. package/extensions/dm-subagents/src/runs/shared/claude-code-adapter.js +120 -0
  147. package/extensions/dm-subagents/src/runs/shared/codex-exec-adapter.js +129 -0
  148. package/extensions/dm-subagents/src/runs/shared/completion-evidence.js +40 -0
  149. package/extensions/dm-subagents/src/runs/shared/completion-guard.js +140 -83
  150. package/extensions/dm-subagents/src/runs/shared/context-mode.js +38 -0
  151. package/extensions/dm-subagents/src/runs/shared/cursor-agent-adapter.js +101 -0
  152. package/extensions/dm-subagents/src/runs/shared/dm-args.js +445 -72
  153. package/extensions/dm-subagents/src/runs/shared/dm-spawn.js +27 -16
  154. package/extensions/dm-subagents/src/runs/shared/dynamic-fanout.js +19 -6
  155. package/extensions/dm-subagents/src/runs/shared/extension-bindings.js +81 -0
  156. package/extensions/dm-subagents/src/runs/shared/external-cli-contract.js +134 -0
  157. package/extensions/dm-subagents/src/runs/shared/external-cli-preflight.js +98 -0
  158. package/extensions/dm-subagents/src/runs/shared/external-cli-runner.js +419 -0
  159. package/extensions/dm-subagents/src/runs/shared/external-job-bridge.js +404 -0
  160. package/extensions/dm-subagents/src/runs/shared/external-job-runner.js +334 -0
  161. package/extensions/dm-subagents/src/runs/shared/fast-mode-extension.js +8 -0
  162. package/extensions/dm-subagents/src/runs/shared/host-step-status.js +228 -0
  163. package/extensions/dm-subagents/src/runs/shared/lane-metadata.js +104 -0
  164. package/extensions/dm-subagents/src/runs/shared/launch-cwd.js +17 -0
  165. package/extensions/dm-subagents/src/runs/shared/llm-intent-arbiter.js +190 -0
  166. package/extensions/dm-subagents/src/runs/shared/long-running-guard.js +48 -3
  167. package/extensions/dm-subagents/src/runs/shared/mcp-config-sources.js +387 -0
  168. package/extensions/dm-subagents/src/runs/shared/mcp-direct-tool-allowlist.js +212 -137
  169. package/extensions/dm-subagents/src/runs/shared/mcp-direct-tool-grant.js +131 -0
  170. package/extensions/dm-subagents/src/runs/shared/model-exclusions.js +207 -0
  171. package/extensions/dm-subagents/src/runs/shared/model-fallback.js +225 -55
  172. package/extensions/dm-subagents/src/runs/shared/model-scope.js +85 -28
  173. package/extensions/dm-subagents/src/runs/shared/mutation-evidence.js +182 -0
  174. package/extensions/dm-subagents/src/runs/shared/nested-events.js +264 -102
  175. package/extensions/dm-subagents/src/runs/shared/nested-render.js +15 -5
  176. package/extensions/dm-subagents/src/runs/shared/orca-progress-tabs.js +505 -0
  177. package/extensions/dm-subagents/src/runs/shared/parallel-handoff.js +653 -0
  178. package/extensions/dm-subagents/src/runs/shared/parallel-utils.js +29 -12
  179. package/extensions/dm-subagents/src/runs/shared/permissions.js +108 -0
  180. package/extensions/dm-subagents/src/runs/shared/process-signal.js +13 -0
  181. package/extensions/dm-subagents/src/runs/shared/run-fanout-budget.js +257 -0
  182. package/extensions/dm-subagents/src/runs/shared/run-history.js +133 -13
  183. package/extensions/dm-subagents/src/runs/shared/runtime-acknowledged-extensions.js +62 -0
  184. package/extensions/dm-subagents/src/runs/shared/session-lease.js +225 -0
  185. package/extensions/dm-subagents/src/runs/shared/single-output.js +129 -27
  186. package/extensions/dm-subagents/src/runs/shared/spawn-budget.js +95 -0
  187. package/extensions/dm-subagents/src/runs/shared/structured-output.js +129 -10
  188. package/extensions/dm-subagents/src/runs/shared/subagent-control.js +66 -11
  189. package/extensions/dm-subagents/src/runs/shared/subagent-prompt-runtime.js +548 -70
  190. package/extensions/dm-subagents/src/runs/shared/subagent-startup-retry.js +50 -0
  191. package/extensions/dm-subagents/src/runs/shared/task-intent.js +130 -0
  192. package/extensions/dm-subagents/src/runs/shared/tool-availability.js +59 -0
  193. package/extensions/dm-subagents/src/runs/shared/tool-budget.js +7 -5
  194. package/extensions/dm-subagents/src/runs/shared/tool-timeout.js +64 -0
  195. package/extensions/dm-subagents/src/runs/shared/usage-budget.js +74 -0
  196. package/extensions/dm-subagents/src/runs/shared/workflow-graph.js +22 -0
  197. package/extensions/dm-subagents/src/runs/shared/worktree-cleanup-plan.js +721 -0
  198. package/extensions/dm-subagents/src/runs/shared/worktree.js +168 -19
  199. package/extensions/dm-subagents/src/shared/accessible-dir.js +35 -0
  200. package/extensions/dm-subagents/src/shared/agent-stream-options.js +3 -0
  201. package/extensions/dm-subagents/src/shared/artifacts.js +170 -11
  202. package/extensions/dm-subagents/src/shared/atomic-json.js +36 -38
  203. package/extensions/dm-subagents/src/shared/capacity-resilient-json.js +77 -0
  204. package/extensions/dm-subagents/src/shared/child-session-name.js +15 -0
  205. package/extensions/dm-subagents/src/shared/child-transcript.js +57 -2
  206. package/extensions/dm-subagents/src/shared/completion-owner.js +7 -0
  207. package/extensions/dm-subagents/src/shared/display-text.js +142 -0
  208. package/extensions/dm-subagents/src/shared/extension-context.js +17 -0
  209. package/extensions/dm-subagents/src/shared/file-coalescer.js +9 -0
  210. package/extensions/dm-subagents/src/shared/file-system-retry.js +56 -0
  211. package/extensions/dm-subagents/src/shared/fork-context.js +96 -33
  212. package/extensions/dm-subagents/src/shared/formatters.js +21 -7
  213. package/extensions/dm-subagents/src/shared/launch-contract.js +94 -0
  214. package/extensions/dm-subagents/src/shared/model-info.js +10 -5
  215. package/extensions/dm-subagents/src/shared/node-executable.js +19 -0
  216. package/extensions/dm-subagents/src/shared/prompt-resources.js +10 -0
  217. package/extensions/dm-subagents/src/shared/pruned-fork.js +427 -0
  218. package/extensions/dm-subagents/src/shared/session-file-trust.js +19 -0
  219. package/extensions/dm-subagents/src/shared/session-tokens.js +14 -3
  220. package/extensions/dm-subagents/src/shared/settings.js +39 -47
  221. package/extensions/dm-subagents/src/shared/shortcuts.js +16 -0
  222. package/extensions/dm-subagents/src/shared/status-format.js +9 -2
  223. package/extensions/dm-subagents/src/shared/thinking-ceiling.js +41 -0
  224. package/extensions/dm-subagents/src/shared/types.js +47 -7
  225. package/extensions/dm-subagents/src/shared/utf8.js +12 -0
  226. package/extensions/dm-subagents/src/shared/utils.js +151 -131
  227. package/extensions/dm-subagents/src/shared/watch-strategy.js +3 -0
  228. package/extensions/dm-subagents/src/shared/workflow-child-permit.js +84 -0
  229. package/extensions/dm-subagents/src/slash/delegation-adapters.js +274 -0
  230. package/extensions/dm-subagents/src/slash/delegation-json.js +113 -0
  231. package/extensions/dm-subagents/src/slash/delegation-request.js +152 -0
  232. package/extensions/dm-subagents/src/slash/prompt-template-bridge.js +294 -243
  233. package/extensions/dm-subagents/src/slash/prompt-workflows.js +35 -73
  234. package/extensions/dm-subagents/src/slash/selector.js +101 -0
  235. package/extensions/dm-subagents/src/slash/slash-bridge.js +17 -1
  236. package/extensions/dm-subagents/src/slash/slash-commands.js +722 -732
  237. package/extensions/dm-subagents/src/slash/slash-live-state.js +37 -19
  238. package/extensions/dm-subagents/src/slash/subagents-admin.js +410 -0
  239. package/extensions/dm-subagents/src/tui/fleet-status.js +824 -0
  240. package/extensions/dm-subagents/src/tui/fleet-transcript.js +479 -0
  241. package/extensions/dm-subagents/src/tui/fleet.js +1326 -0
  242. package/extensions/dm-subagents/src/tui/render-helpers.js +22 -0
  243. package/extensions/dm-subagents/src/tui/render.js +1511 -261
  244. package/extensions/dm-subagents/src/watchdog/change-signature.js +220 -0
  245. package/extensions/dm-subagents/src/watchdog/child-status.js +151 -0
  246. package/extensions/dm-subagents/src/watchdog/emission-guard.js +90 -0
  247. package/extensions/dm-subagents/src/watchdog/lsp-diagnostics.js +484 -0
  248. package/extensions/dm-subagents/src/watchdog/model-selection.js +154 -0
  249. package/extensions/dm-subagents/src/watchdog/permission-arbiter.js +138 -0
  250. package/extensions/dm-subagents/src/watchdog/register-child.js +112 -0
  251. package/extensions/dm-subagents/src/watchdog/register-main.js +419 -0
  252. package/extensions/dm-subagents/src/watchdog/render.js +54 -0
  253. package/extensions/dm-subagents/src/watchdog/review.js +251 -0
  254. package/extensions/dm-subagents/src/watchdog/runtime.js +803 -0
  255. package/extensions/dm-subagents/src/watchdog/scope.js +56 -0
  256. package/extensions/dm-subagents/src/watchdog/settings.js +515 -0
  257. package/extensions/dm-subagents/src/watchdog/tool-actions.js +151 -0
  258. package/extensions/dm-subagents/src/watchdog/turn-delta.js +169 -0
  259. package/extensions/dm-subagents/src/watchdog/types.js +29 -0
  260. package/extensions/dm-subagents/src/watchdog/warning-format.js +58 -0
  261. package/extensions/dm-subagents/src/workflows/chat-progress.js +116 -0
  262. package/extensions/dm-subagents/src/workflows/host-command.js +227 -0
  263. package/extensions/dm-subagents/src/workflows/scripted-workflow.js +2011 -0
  264. package/extensions/dm-subagents/src/workflows/workflow-child-summary.js +116 -0
  265. package/extensions/dm-subagents/src/workflows/workflow-preflight.js +243 -0
  266. package/extensions/dm-subagents/src/workflows/workflow-receipt.js +387 -0
  267. package/extensions/dm-subagents/src/workflows/workflow-settlement.js +189 -0
  268. package/package.json +4 -3
  269. package/extensions/dm-fff/package.json +0 -21
  270. package/extensions/dm-fff/src/index.js +0 -691
  271. package/extensions/dm-fff/src/query.js +0 -60
  272. package/extensions/dm-subagents/agents/context-builder.md +0 -46
  273. package/extensions/dm-subagents/agents/planner.md +0 -55
  274. package/extensions/dm-subagents/prompts/parallel-context-build.md +0 -55
  275. package/extensions/dm-subagents/prompts/parallel-handoff-plan.md +0 -61
  276. package/extensions/dm-subagents/src/runs/background/wait.js +0 -206
  277. package/extensions/dm-subagents/src/runs/foreground/chain-clarify.js +0 -1013
  278. package/extensions/dm-subagents/src/runs/foreground/chain-execution.js +0 -981
  279. package/extensions/dm-subagents/src/runs/shared/turn-budget.js +0 -50
@@ -0,0 +1,225 @@
1
+ import { createHash, randomUUID } from "node:crypto";
2
+ import * as fs from "node:fs";
3
+ import * as os from "node:os";
4
+ import * as path from "node:path";
5
+ import { createAtomicJsonWriter } from "../../shared/atomic-json.js";
6
+ import { TEMP_ROOT_DIR } from "../../shared/types.js";
7
+ export const SESSION_LEASES_DIR = path.join(TEMP_ROOT_DIR, "session-leases");
8
+
9
+ export class SessionLeaseConflictError extends Error {
10
+ owner;
11
+ constructor(message, owner) {
12
+ super(message);
13
+ this.name = "SessionLeaseConflictError";
14
+ this.owner = owner;
15
+ }
16
+ }
17
+ function getProcessStartIdentity(pid) {
18
+ if (process.platform === "linux") {
19
+ try {
20
+ const stat = fs.readFileSync(`/proc/${pid}/stat`, "utf-8");
21
+ const commandEnd = stat.lastIndexOf(")");
22
+ if (commandEnd === -1)
23
+ return;
24
+ const fields = stat.slice(commandEnd + 1).trim().split(/\s+/);
25
+ const startTicks = fields[19];
26
+ return startTicks ? `linux:${startTicks}` : undefined;
27
+ } catch {
28
+ return;
29
+ }
30
+ }
31
+ return;
32
+ }
33
+ function processIsAlive(pid) {
34
+ try {
35
+ process.kill(pid, 0);
36
+ return true;
37
+ } catch (error) {
38
+ const code = error.code;
39
+ if (code === "ESRCH")
40
+ return false;
41
+ if (code === "EPERM")
42
+ return true;
43
+ return;
44
+ }
45
+ }
46
+ export function canonicalSessionFilePath(sessionFile) {
47
+ return fs.realpathSync.native(path.resolve(sessionFile));
48
+ }
49
+ export function canonicalSessionId(sessionFile) {
50
+ const canonical = canonicalSessionFilePath(sessionFile);
51
+ const key = process.platform === "win32" ? canonical.toLowerCase() : canonical;
52
+ return createHash("sha256").update(key).digest("hex");
53
+ }
54
+ export function sessionLeaseDir(sessionFile, rootDir = SESSION_LEASES_DIR) {
55
+ return path.join(rootDir, canonicalSessionId(sessionFile));
56
+ }
57
+ export function inspectSessionLease(sessionFile, rootDir = SESSION_LEASES_DIR) {
58
+ const canonicalSessionFile = canonicalSessionFilePath(sessionFile);
59
+ const canonicalSessionIdValue = canonicalSessionId(canonicalSessionFile);
60
+ const leaseDir = path.join(rootDir, canonicalSessionIdValue);
61
+ if (!fs.existsSync(leaseDir))
62
+ return { state: "free", canonicalSessionFile, canonicalSessionId: canonicalSessionIdValue };
63
+ const owner = readLeaseOwner(leaseDir);
64
+ return owner ? { state: "owned", canonicalSessionFile, canonicalSessionId: canonicalSessionIdValue, owner } : { state: "unreadable", canonicalSessionFile, canonicalSessionId: canonicalSessionIdValue };
65
+ }
66
+ function parseOwner(value) {
67
+ if (!value || typeof value !== "object" || Array.isArray(value))
68
+ return;
69
+ const owner = value;
70
+ if (owner.version !== 1 || typeof owner.token !== "string" || typeof owner.canonicalSessionFile !== "string" || typeof owner.runId !== "string" || typeof owner.sourceRunId !== "string" || typeof owner.pid !== "number" || !Number.isInteger(owner.pid) || owner.pid <= 0 || typeof owner.hostname !== "string" || owner.writerState !== "none" && owner.writerState !== "spawning" && owner.writerState !== "running" || typeof owner.acquiredAt !== "string" || typeof owner.acquiredAtMs !== "number" || typeof owner.updatedAtMs !== "number")
71
+ return;
72
+ if (owner.parentSessionId !== undefined && typeof owner.parentSessionId !== "string")
73
+ return;
74
+ if (owner.processStartIdentity !== undefined && typeof owner.processStartIdentity !== "string")
75
+ return;
76
+ if (owner.writerPid !== undefined && (typeof owner.writerPid !== "number" || !Number.isInteger(owner.writerPid) || owner.writerPid <= 0))
77
+ return;
78
+ if (owner.writerProcessStartIdentity !== undefined && typeof owner.writerProcessStartIdentity !== "string")
79
+ return;
80
+ if (owner.writerState === "running" && owner.writerPid === undefined)
81
+ return;
82
+ if (owner.writerState !== "running" && (owner.writerPid !== undefined || owner.writerProcessStartIdentity !== undefined))
83
+ return;
84
+ return owner;
85
+ }
86
+ function readLeaseOwner(leaseDir) {
87
+ try {
88
+ return parseOwner(JSON.parse(fs.readFileSync(path.join(leaseDir, "owner.json"), "utf-8")));
89
+ } catch {
90
+ return;
91
+ }
92
+ }
93
+ function conflictMessage(canonicalSessionFile, owner) {
94
+ if (!owner) {
95
+ return `Direct revival of session '${canonicalSessionFile}' is blocked by an existing lease with unreadable owner metadata. Refusing to reclaim it without proof that the owner is stale.`;
96
+ }
97
+ const parent = owner.parentSessionId ? `, parent session '${owner.parentSessionId}'` : "";
98
+ return `Direct revival of session '${canonicalSessionFile}' is already owned by run '${owner.runId}' (source run '${owner.sourceRunId}'${parent}, pid ${owner.pid} on ${owner.hostname}). Wait for that revival to finish or start a separate continuation without reusing this session file.`;
99
+ }
100
+ function processDemonstrablyGone(pid, startIdentity, options) {
101
+ const alive = options.isProcessAlive(pid);
102
+ if (alive === false)
103
+ return true;
104
+ if (alive !== true || !startIdentity)
105
+ return false;
106
+ const currentIdentity = options.getProcessStartIdentity(pid);
107
+ return currentIdentity !== undefined && currentIdentity !== startIdentity;
108
+ }
109
+ function demonstrablyStale(owner, options) {
110
+ if (owner.hostname !== options.hostname)
111
+ return false;
112
+ if (!processDemonstrablyGone(owner.pid, owner.processStartIdentity, options))
113
+ return false;
114
+ if (owner.writerState === "spawning")
115
+ return false;
116
+ if (owner.writerState === "none")
117
+ return true;
118
+ return owner.writerPid !== undefined && processDemonstrablyGone(owner.writerPid, owner.writerProcessStartIdentity, options);
119
+ }
120
+ function createLeaseDirectory(leaseDir, owner) {
121
+ const tempDir = `${leaseDir}.candidate-${owner.token}`;
122
+ fs.mkdirSync(path.dirname(leaseDir), { recursive: true, mode: 448 });
123
+ fs.rmSync(tempDir, { recursive: true, force: true });
124
+ fs.mkdirSync(tempDir, { mode: 448 });
125
+ try {
126
+ fs.writeFileSync(path.join(tempDir, "owner.json"), JSON.stringify(owner, null, 2), { encoding: "utf-8", mode: 384 });
127
+ try {
128
+ fs.renameSync(tempDir, leaseDir);
129
+ return true;
130
+ } catch (error) {
131
+ if (fs.existsSync(leaseDir))
132
+ return false;
133
+ throw error;
134
+ }
135
+ } finally {
136
+ fs.rmSync(tempDir, { recursive: true, force: true });
137
+ }
138
+ }
139
+ export function acquireSessionLease(request, options = {}) {
140
+ const canonicalSessionFile = canonicalSessionFilePath(request.sessionFile);
141
+ const rootDir = options.rootDir ?? SESSION_LEASES_DIR;
142
+ const leaseDir = sessionLeaseDir(canonicalSessionFile, rootDir);
143
+ const now = options.now ?? Date.now;
144
+ const pid = options.pid ?? process.pid;
145
+ const hostname = options.hostname ?? os.hostname();
146
+ const getIdentity = options.getProcessStartIdentity ?? getProcessStartIdentity;
147
+ const processStartIdentity = options.processStartIdentity ?? getIdentity(pid) ?? (pid === process.pid ? `runtime:${Math.round(Date.now() - process.uptime() * 1000)}` : undefined);
148
+ const acquiredAtMs = now();
149
+ const owner = {
150
+ version: 1,
151
+ token: options.token?.() ?? randomUUID(),
152
+ canonicalSessionFile,
153
+ runId: request.runId,
154
+ sourceRunId: request.sourceRunId,
155
+ ...request.parentSessionId ? { parentSessionId: request.parentSessionId } : {},
156
+ pid,
157
+ hostname,
158
+ ...processStartIdentity ? { processStartIdentity } : {},
159
+ writerState: "none",
160
+ acquiredAt: new Date(acquiredAtMs).toISOString(),
161
+ acquiredAtMs,
162
+ updatedAtMs: acquiredAtMs
163
+ };
164
+ const staleOptions = {
165
+ hostname,
166
+ isProcessAlive: options.isProcessAlive ?? processIsAlive,
167
+ getProcessStartIdentity: getIdentity
168
+ };
169
+ for (let attempt = 0;attempt < 4; attempt++) {
170
+ if (createLeaseDirectory(leaseDir, owner)) {
171
+ const writeOwner = createAtomicJsonWriter();
172
+ return {
173
+ leaseDir,
174
+ owner,
175
+ updateWriter(writer) {
176
+ const currentOwner = readLeaseOwner(leaseDir);
177
+ if (!currentOwner || currentOwner.token !== owner.token) {
178
+ throw new Error(`Session revival lease ownership changed for run '${owner.runId}'.`);
179
+ }
180
+ const writerProcessStartIdentity = writer.state === "running" ? getIdentity(writer.pid) : undefined;
181
+ const nextOwner = {
182
+ ...owner,
183
+ writerState: writer.state,
184
+ ...writer.state === "running" ? { writerPid: writer.pid } : {},
185
+ ...writerProcessStartIdentity ? { writerProcessStartIdentity } : {},
186
+ updatedAtMs: now()
187
+ };
188
+ delete nextOwner.writerPid;
189
+ delete nextOwner.writerProcessStartIdentity;
190
+ if (writer.state === "running") {
191
+ nextOwner.writerPid = writer.pid;
192
+ if (writerProcessStartIdentity)
193
+ nextOwner.writerProcessStartIdentity = writerProcessStartIdentity;
194
+ }
195
+ writeOwner(path.join(leaseDir, "owner.json"), nextOwner);
196
+ delete owner.writerPid;
197
+ delete owner.writerProcessStartIdentity;
198
+ Object.assign(owner, nextOwner);
199
+ },
200
+ release() {
201
+ const currentOwner = readLeaseOwner(leaseDir);
202
+ if (!currentOwner || currentOwner.token !== owner.token)
203
+ return false;
204
+ fs.rmSync(leaseDir, { recursive: true, force: true });
205
+ return !fs.existsSync(leaseDir);
206
+ }
207
+ };
208
+ }
209
+ const existingOwner = readLeaseOwner(leaseDir);
210
+ if (!existingOwner || !demonstrablyStale(existingOwner, staleOptions)) {
211
+ throw new SessionLeaseConflictError(conflictMessage(canonicalSessionFile, existingOwner), existingOwner);
212
+ }
213
+ const tombstone = `${leaseDir}.stale-${existingOwner.token.replace(/[^A-Za-z0-9._-]/g, "-")}`;
214
+ try {
215
+ fs.renameSync(leaseDir, tombstone);
216
+ } catch (error) {
217
+ const code = error.code;
218
+ if (code === "ENOENT" || fs.existsSync(tombstone))
219
+ continue;
220
+ throw error;
221
+ }
222
+ }
223
+ const existingOwner = readLeaseOwner(leaseDir);
224
+ throw new SessionLeaseConflictError(conflictMessage(canonicalSessionFile, existingOwner), existingOwner);
225
+ }
@@ -1,5 +1,45 @@
1
1
  import * as fs from "node:fs";
2
2
  import * as path from "node:path";
3
+ import { hasMutationToolCapability } from "./completion-guard.js";
4
+ function missingFileStatError(error) {
5
+ const code = error?.code;
6
+ return code === "ENOENT" || code === "ENOTDIR";
7
+ }
8
+ function snapshotFromStatError(error) {
9
+ if (missingFileStatError(error))
10
+ return { exists: false };
11
+ return { exists: false, error: error instanceof Error ? error.message : String(error) };
12
+ }
13
+ export function extractChildWrittenOutput(messages, outputPath, cwd) {
14
+ if (!messages?.length || !outputPath)
15
+ return;
16
+ const resolvedTarget = path.resolve(cwd ?? ".", outputPath);
17
+ const comparableTarget = process.platform === "win32" ? resolvedTarget.toLowerCase() : resolvedTarget;
18
+ const successfulCallIds = new Set;
19
+ for (const message of messages) {
20
+ if (message.role === "toolResult" && message.isError === false && typeof message.toolCallId === "string") {
21
+ successfulCallIds.add(message.toolCallId);
22
+ }
23
+ }
24
+ let content;
25
+ for (const message of messages) {
26
+ if (message.role !== "assistant")
27
+ continue;
28
+ for (const part of message.content) {
29
+ if (part.type !== "toolCall" || part.name !== "write" || !successfulCallIds.has(part.id))
30
+ continue;
31
+ const args = typeof part.arguments === "object" && part.arguments !== null && !Array.isArray(part.arguments) ? part.arguments : {};
32
+ if (typeof args.path !== "string" || typeof args.content !== "string")
33
+ continue;
34
+ const resolvedWritePath = path.resolve(cwd ?? ".", args.path);
35
+ const comparableWritePath = process.platform === "win32" ? resolvedWritePath.toLowerCase() : resolvedWritePath;
36
+ if (comparableWritePath !== comparableTarget)
37
+ continue;
38
+ content = args.content;
39
+ }
40
+ }
41
+ return content;
42
+ }
3
43
  export function normalizeSingleOutputOverride(output, defaultOutput) {
4
44
  if (output === false || output === "false")
5
45
  return false;
@@ -19,28 +59,52 @@ export function resolveSingleOutputPath(output, runtimeCwd, requestedCwd, relati
19
59
  const baseCwd = requestedCwd ? path.isAbsolute(requestedCwd) ? requestedCwd : path.resolve(runtimeCwd, requestedCwd) : runtimeCwd;
20
60
  return path.resolve(baseCwd, output);
21
61
  }
22
- function formatOutputPathInstruction(outputPath) {
62
+ function formatOutputPathInstruction(outputPath, capabilities) {
63
+ const delivery = !capabilities || hasMutationToolCapability(capabilities.tools, capabilities.mcpDirectTools) ? `Write your findings to exactly this path: ${outputPath}` : [
64
+ "Return the complete artifact in your final response.",
65
+ `The runtime will persist it to exactly this path: ${outputPath}`,
66
+ "Do not call contact_supervisor merely because no write-capable tool is available."
67
+ ].join(`
68
+ `);
23
69
  return [
24
- `Write your findings to exactly this path: ${outputPath}`,
70
+ delivery,
25
71
  "This path is authoritative for this run.",
26
72
  "Ignore any other output filename or output path mentioned elsewhere, including output destinations in the base agent prompt, system prompt, or task instructions."
27
73
  ].join(`
28
74
  `);
29
75
  }
30
- export function injectSingleOutputInstruction(task, outputPath) {
76
+ export function requestedOutputPathFromTask(task) {
77
+ for (const line of task.split(/\r?\n/).reverse()) {
78
+ const match = line.match(/^\s*(?:Write your findings to(?: exactly this path)?:|The runtime will persist it to exactly this path:)\s*(.+?)\s*$/i);
79
+ if (!match?.[1])
80
+ continue;
81
+ const requested = match[1].trim();
82
+ return requested.startsWith("`") && requested.endsWith("`") ? requested.slice(1, -1) : requested;
83
+ }
84
+ return;
85
+ }
86
+ export function outputPathMappingFromTask(task, savedPath) {
87
+ const requestedPath = requestedOutputPathFromTask(task);
88
+ if (!requestedPath || !savedPath)
89
+ return;
90
+ if (path.isAbsolute(requestedPath) && path.normalize(requestedPath) === path.normalize(savedPath))
91
+ return;
92
+ return { requestedPath, savedPath };
93
+ }
94
+ export function injectSingleOutputInstruction(task, outputPath, capabilities) {
31
95
  if (!outputPath)
32
96
  return task;
33
97
  return `${task}
34
98
 
35
99
  ---
36
100
  **Output:**
37
- ${formatOutputPathInstruction(outputPath)}`;
101
+ ${formatOutputPathInstruction(outputPath, capabilities)}`;
38
102
  }
39
- export function injectOutputPathSystemPrompt(systemPrompt, outputPath) {
103
+ export function injectOutputPathSystemPrompt(systemPrompt, outputPath, capabilities) {
40
104
  if (!outputPath)
41
105
  return systemPrompt;
42
106
  const instruction = `Runtime output path override:
43
- ${formatOutputPathInstruction(outputPath)}`;
107
+ ${formatOutputPathInstruction(outputPath, capabilities)}`;
44
108
  return systemPrompt ? `${systemPrompt}
45
109
 
46
110
  ${instruction}` : instruction;
@@ -80,20 +144,60 @@ export function validateFileOnlyOutputMode(outputMode, outputPath, context) {
80
144
  }
81
145
  return;
82
146
  }
147
+ export function resolveSingleOutputClaimPath(outputPath) {
148
+ let existing = path.resolve(outputPath);
149
+ const missingSegments = [];
150
+ while (!fs.existsSync(existing)) {
151
+ missingSegments.unshift(path.basename(existing));
152
+ const parent = path.dirname(existing);
153
+ if (parent === existing)
154
+ break;
155
+ existing = parent;
156
+ }
157
+ return path.join(fs.realpathSync(existing), ...missingSegments);
158
+ }
159
+ function outputClaimError(outputPath, expectedClaimPath) {
160
+ if (expectedClaimPath && resolveSingleOutputClaimPath(outputPath) !== expectedClaimPath)
161
+ return "Output path changed after it was claimed.";
162
+ return;
163
+ }
83
164
  export function captureSingleOutputSnapshot(outputPath) {
84
165
  if (!outputPath)
85
166
  return;
86
167
  try {
87
168
  const stat = fs.statSync(outputPath);
88
169
  return { exists: true, mtimeMs: stat.mtimeMs, size: stat.size };
89
- } catch {
90
- return { exists: false };
170
+ } catch (error) {
171
+ return snapshotFromStatError(error);
172
+ }
173
+ }
174
+ function inspectSingleOutputChange(outputPath, beforeRun) {
175
+ if (beforeRun?.error)
176
+ return { changed: false, error: beforeRun.error };
177
+ try {
178
+ const stat = fs.statSync(outputPath);
179
+ return {
180
+ changed: !beforeRun?.exists || stat.mtimeMs !== beforeRun.mtimeMs || stat.size !== beforeRun.size
181
+ };
182
+ } catch (error) {
183
+ if (missingFileStatError(error))
184
+ return { changed: false };
185
+ return { changed: false, error: error instanceof Error ? error.message : String(error) };
91
186
  }
92
187
  }
93
- function persistSingleOutput(outputPath, fullOutput) {
188
+ export function hasSingleOutputChangedSinceSnapshot(outputPath, beforeRun) {
189
+ if (!outputPath)
190
+ return;
191
+ const inspected = inspectSingleOutputChange(outputPath, beforeRun);
192
+ return inspected.error ? undefined : inspected.changed;
193
+ }
194
+ function persistSingleOutput(outputPath, fullOutput, expectedClaimPath) {
94
195
  if (!outputPath)
95
196
  return {};
96
197
  try {
198
+ const claimError = outputClaimError(outputPath, expectedClaimPath);
199
+ if (claimError)
200
+ return { error: claimError, fatalError: true };
97
201
  fs.mkdirSync(path.dirname(outputPath), { recursive: true });
98
202
  fs.writeFileSync(outputPath, fullOutput, "utf-8");
99
203
  return { savedPath: outputPath };
@@ -101,23 +205,20 @@ function persistSingleOutput(outputPath, fullOutput) {
101
205
  return { error: err instanceof Error ? err.message : String(err) };
102
206
  }
103
207
  }
104
- export function resolveSingleOutput(outputPath, fallbackOutput, beforeRun) {
208
+ export function resolveSingleOutput(outputPath, fallbackOutput, beforeRun, expectedClaimPath) {
105
209
  if (!outputPath)
106
210
  return { fullOutput: fallbackOutput };
107
- let changedSinceStart = false;
108
- try {
109
- const stat = fs.statSync(outputPath);
110
- changedSinceStart = !beforeRun?.exists || stat.mtimeMs !== beforeRun.mtimeMs || stat.size !== beforeRun.size;
111
- } catch (error) {
112
- const code = error && typeof error === "object" && "code" in error ? error.code : undefined;
113
- if (code !== "ENOENT" && code !== "ENOTDIR") {
114
- return {
115
- fullOutput: fallbackOutput,
116
- saveError: `Failed to inspect output file: ${error instanceof Error ? error.message : String(error)}`
117
- };
118
- }
211
+ const claimError = outputClaimError(outputPath, expectedClaimPath);
212
+ if (claimError)
213
+ return { fullOutput: fallbackOutput, saveError: claimError, fatalError: true };
214
+ const changedSinceStart = inspectSingleOutputChange(outputPath, beforeRun);
215
+ if (changedSinceStart.error) {
216
+ return {
217
+ fullOutput: fallbackOutput,
218
+ saveError: `Failed to inspect output file: ${changedSinceStart.error}`
219
+ };
119
220
  }
120
- if (changedSinceStart) {
221
+ if (changedSinceStart.changed) {
121
222
  try {
122
223
  return { fullOutput: fs.readFileSync(outputPath, "utf-8"), savedPath: outputPath };
123
224
  } catch (error) {
@@ -127,19 +228,20 @@ export function resolveSingleOutput(outputPath, fallbackOutput, beforeRun) {
127
228
  };
128
229
  }
129
230
  }
130
- const save = persistSingleOutput(outputPath, fallbackOutput);
231
+ const save = persistSingleOutput(outputPath, fallbackOutput, expectedClaimPath);
131
232
  if (save.savedPath)
132
233
  return { fullOutput: fallbackOutput, savedPath: save.savedPath };
133
- return { fullOutput: fallbackOutput, saveError: save.error };
234
+ return { fullOutput: fallbackOutput, saveError: save.error, ...save.fatalError ? { fatalError: true } : {} };
134
235
  }
135
236
  export function finalizeSingleOutput(params) {
136
237
  let displayOutput = params.truncatedOutput || params.fullOutput;
137
- if (params.exitCode === 0 && params.savedPath) {
238
+ if ((params.exitCode === 0 || params.preserveSavedOutput) && params.savedPath) {
138
239
  const outputReference = params.outputReference ?? formatSavedOutputReference(params.savedPath, params.fullOutput);
139
240
  if (params.outputMode === "file-only") {
140
241
  return { displayOutput: outputReference.message, savedPath: params.savedPath, outputReference };
141
242
  }
142
- displayOutput += `
243
+ if (!displayOutput.includes(outputReference.message))
244
+ displayOutput += `
143
245
 
144
246
  ${outputReference.message}`;
145
247
  return { displayOutput, savedPath: params.savedPath, outputReference };
@@ -0,0 +1,95 @@
1
+ import {
2
+ resolveMaxSubagentSpawnsPerSession
3
+ } from "../../shared/types.js";
4
+ const MAX_GRANT_HISTORY = 20;
5
+ function sessionState(state, config, sessionId) {
6
+ let counters = state.subagentSpawns;
7
+ if (!counters || counters.sessionId !== sessionId) {
8
+ counters = {
9
+ sessionId,
10
+ count: 0,
11
+ configuredLimit: resolveMaxSubagentSpawnsPerSession(config.maxSubagentSpawnsPerSession) ?? null,
12
+ granted: 0,
13
+ grantHistory: []
14
+ };
15
+ state.subagentSpawns = counters;
16
+ }
17
+ if (counters.configuredLimit === undefined) {
18
+ counters.configuredLimit = resolveMaxSubagentSpawnsPerSession(config.maxSubagentSpawnsPerSession) ?? null;
19
+ }
20
+ counters.granted ??= 0;
21
+ counters.grantHistory ??= [];
22
+ return counters;
23
+ }
24
+ export function getSpawnBudgetSnapshot(state, config, sessionId = state.currentSessionId) {
25
+ const counters = sessionState(state, config, sessionId);
26
+ const configuredLimit = counters.configuredLimit ?? null;
27
+ const granted = configuredLimit === null ? 0 : counters.granted ?? 0;
28
+ const limit = configuredLimit === null ? null : configuredLimit + granted;
29
+ return {
30
+ used: counters.count,
31
+ configuredLimit,
32
+ granted,
33
+ limit,
34
+ remaining: limit === null ? null : Math.max(0, limit - counters.count),
35
+ grantRemaining: configuredLimit === null ? null : Math.max(0, configuredLimit - granted),
36
+ grantHistory: [...counters.grantHistory ?? []]
37
+ };
38
+ }
39
+ export function formatSpawnBudgetSummary(snapshot) {
40
+ if (snapshot.limit === null)
41
+ return "unlimited";
42
+ return `${snapshot.used}/${snapshot.limit} used, ${snapshot.remaining} remaining (configured ${snapshot.configuredLimit}; granted ${snapshot.granted}; grant allowance ${snapshot.grantRemaining})`;
43
+ }
44
+ export function formatSpawnBudget(snapshot) {
45
+ return `Spawn budget: ${formatSpawnBudgetSummary(snapshot)}`;
46
+ }
47
+ export function preflightSpawnBudget(state, config, sessionId, requested) {
48
+ const snapshot = getSpawnBudgetSnapshot(state, config, sessionId);
49
+ if (requested <= 0 || snapshot.limit === null || requested <= (snapshot.remaining ?? 0))
50
+ return { snapshot };
51
+ return {
52
+ snapshot,
53
+ error: `Subagent spawn limit reached for this session (${snapshot.used}/${snapshot.limit} used, ${requested} requested). ${snapshot.remaining} remaining; the declared run cannot fit, so no children were started. Grant budget explicitly from the root interactive session or start a new session.`
54
+ };
55
+ }
56
+ export function reserveSpawnBudget(state, config, sessionId, requested) {
57
+ const checked = preflightSpawnBudget(state, config, sessionId, requested);
58
+ if (checked.error || requested <= 0 || checked.snapshot.limit === null)
59
+ return checked;
60
+ state.subagentSpawns.count += requested;
61
+ return { snapshot: getSpawnBudgetSnapshot(state, config, sessionId) };
62
+ }
63
+ export function preflightSpawnBudgetGrant(state, config, sessionId, additional) {
64
+ const snapshot = getSpawnBudgetSnapshot(state, config, sessionId);
65
+ if (!Number.isInteger(additional) || additional <= 0) {
66
+ return { snapshot, error: "action='grant-spawn-budget' requires additional to be a positive integer." };
67
+ }
68
+ if (snapshot.configuredLimit === null || snapshot.limit === null) {
69
+ return { snapshot, error: "The current session has no configured spawn cap, so it does not need a budget grant." };
70
+ }
71
+ if (additional > (snapshot.grantRemaining ?? 0)) {
72
+ return {
73
+ snapshot,
74
+ error: `Spawn budget grant rejected: ${additional} requested but only ${snapshot.grantRemaining} of the original configured limit remains grantable.`
75
+ };
76
+ }
77
+ return { snapshot };
78
+ }
79
+ export function grantSpawnBudget(state, config, sessionId, additional, now = Date.now()) {
80
+ const checked = preflightSpawnBudgetGrant(state, config, sessionId, additional);
81
+ if (checked.error)
82
+ return checked;
83
+ const before = checked.snapshot;
84
+ if (before.limit === null) {
85
+ return { snapshot: before, error: "The current session has no configured spawn cap, so it does not need a budget grant." };
86
+ }
87
+ const counters = state.subagentSpawns;
88
+ counters.granted = (counters.granted ?? 0) + additional;
89
+ const limit = before.limit + additional;
90
+ counters.grantHistory = [
91
+ ...counters.grantHistory ?? [],
92
+ { sessionId, amount: additional, grantedAt: now, previousLimit: before.limit, limit }
93
+ ].slice(-MAX_GRANT_HISTORY);
94
+ return { snapshot: getSpawnBudgetSnapshot(state, config, sessionId) };
95
+ }