@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
@@ -3,9 +3,28 @@ import * as fs from "node:fs";
3
3
  import * as path from "node:path";
4
4
  import { writeAtomicJson } from "../../shared/atomic-json.js";
5
5
  import { POLL_INTERVAL_MS } from "../../shared/types.js";
6
- export const INTERRUPT_SIGNAL = process.platform === "win32" ? "SIGBREAK" : "SIGUSR2";
6
+ import { shouldUseNativeFsWatch } from "../../shared/watch-strategy.js";
7
+ import { resolveWatchPath } from "../../shared/utils.js";
8
+ function writeJsonToExistingDir(filePath, payload) {
9
+ const tempPath = path.join(path.dirname(filePath), `.${path.basename(filePath)}.${process.pid}.${Date.now()}.${randomUUID()}.tmp`);
10
+ try {
11
+ fs.writeFileSync(tempPath, JSON.stringify(payload, null, 2), { encoding: "utf-8", flag: "wx" });
12
+ fs.renameSync(tempPath, filePath);
13
+ } finally {
14
+ fs.rmSync(tempPath, { force: true });
15
+ }
16
+ }
17
+ const CONTROL_SAFETY_POLL_INTERVAL_MS = 5000;
7
18
  const STEER_REQUESTS_DIR = "steer-requests";
19
+ const STOP_REQUESTS_DIR = "stop-requests";
20
+ const REVIVAL_BRIEFS_DIR = "revival-briefs";
21
+ export const MAX_STEER_QUEUE_SIZE = 20;
8
22
  const STEER_TARGETS_DIR = "steer-targets";
23
+ const STEER_CAPABILITIES_DIR = "steer-capabilities";
24
+ const STEER_ACKS_DIR = "steer-acks";
25
+ const STEER_INBOX_CLOSED_FILE = "steer-inbox-closed.json";
26
+ const MAX_STEER_MESSAGE_BYTES = 128 * 1024;
27
+ const MAX_STEER_REQUEST_ID_LENGTH = 256;
9
28
  export function controlInboxDir(asyncDir) {
10
29
  return path.join(asyncDir, "control");
11
30
  }
@@ -15,20 +34,114 @@ export function interruptRequestPath(asyncDir) {
15
34
  export function timeoutRequestPath(asyncDir) {
16
35
  return path.join(controlInboxDir(asyncDir), "timeout.json");
17
36
  }
37
+ export function stopRequestPath(asyncDir) {
38
+ return path.join(controlInboxDir(asyncDir), "stop.json");
39
+ }
40
+ export function stopRequestsDir(asyncDir) {
41
+ return path.join(controlInboxDir(asyncDir), STOP_REQUESTS_DIR);
42
+ }
18
43
  export function steerRequestsDir(asyncDir) {
19
44
  return path.join(controlInboxDir(asyncDir), STEER_REQUESTS_DIR);
20
45
  }
46
+ export function steerInboxClosedPath(asyncDir) {
47
+ return path.join(controlInboxDir(asyncDir), STEER_INBOX_CLOSED_FILE);
48
+ }
49
+ export function closeSteerInbox(asyncDir, state, write = writeAtomicJson) {
50
+ write(steerInboxClosedPath(asyncDir), { version: 1, closedAt: Date.now(), state });
51
+ }
21
52
  export function stepSteerInboxDir(asyncDir, index) {
53
+ assertChildIndex(index);
22
54
  return path.join(controlInboxDir(asyncDir), STEER_TARGETS_DIR, String(index));
23
55
  }
56
+ export function steerCapabilitiesDir(asyncDir) {
57
+ return path.join(controlInboxDir(asyncDir), STEER_CAPABILITIES_DIR);
58
+ }
59
+ export function steerCapabilityPath(asyncDir, index) {
60
+ assertChildIndex(index);
61
+ return path.join(steerCapabilitiesDir(asyncDir), `${index}.json`);
62
+ }
63
+ export function steerAcksDir(asyncDir, index) {
64
+ assertChildIndex(index);
65
+ return path.join(controlInboxDir(asyncDir), STEER_ACKS_DIR, String(index));
66
+ }
67
+ function steerAckFileName(requestId) {
68
+ return `${Buffer.from(requestId).toString("base64url")}.json`;
69
+ }
70
+ export function steerAckPathFromDir(dir, requestId) {
71
+ if (!/^[^\s]+$/.test(requestId) || requestId.length > 256)
72
+ throw new Error("steer acknowledgment requestId is invalid.");
73
+ return path.join(dir, steerAckFileName(requestId));
74
+ }
75
+ function assertChildIndex(index) {
76
+ if (!Number.isInteger(index) || index < 0 || index > 1e6)
77
+ throw new Error("child index must be a non-negative integer.");
78
+ }
79
+ function validStopChildId(childId) {
80
+ return typeof childId === "string" && Boolean(childId.trim()) && childId.length <= 256 && !/[\r\n]/.test(childId);
81
+ }
24
82
  function steerRequestFileName(request) {
25
83
  return `${String(request.ts).padStart(13, "0")}-${Buffer.from(request.id).toString("base64url")}.json`;
26
84
  }
85
+ function stopRequestFileName(request) {
86
+ return `${String(request.ts ?? 0).padStart(13, "0")}-${randomUUID()}.json`;
87
+ }
88
+ function validSteerRequest(request) {
89
+ return request.type === "steer" && typeof request.id === "string" && /^[^\s]+$/.test(request.id) && request.id.length <= MAX_STEER_REQUEST_ID_LENGTH && typeof request.ts === "number" && Number.isFinite(request.ts) && request.ts > 0 && typeof request.message === "string" && Boolean(request.message.trim()) && Buffer.byteLength(request.message, "utf8") <= MAX_STEER_MESSAGE_BYTES && (request.mode === undefined || request.mode === "steer" || request.mode === "follow_up" || request.mode === "auto") && (request.targetIndex === undefined || Number.isInteger(request.targetIndex) && request.targetIndex >= 0 && request.targetIndex <= 1e6) && (request.targetIndexes === undefined || request.targetIndex === undefined && Array.isArray(request.targetIndexes) && request.targetIndexes.length > 0 && request.targetIndexes.length <= 1000 && request.targetIndexes.every((index) => Number.isInteger(index) && index >= 0 && index <= 1e6) && new Set(request.targetIndexes).size === request.targetIndexes.length) && (request.source === undefined || typeof request.source === "string" && Boolean(request.source.trim()) && request.source.length <= 256);
90
+ }
27
91
  export function writeSteerRequestToDir(dir, request) {
92
+ if (!validSteerRequest(request))
93
+ throw new Error("steer request is malformed or exceeds transport limits.");
28
94
  const requestPath = path.join(dir, steerRequestFileName(request));
29
95
  writeAtomicJson(requestPath, request);
30
96
  return requestPath;
31
97
  }
98
+ export function writeSteerRequestToExistingDir(dir, request) {
99
+ if (!validSteerRequest(request))
100
+ throw new Error("steer request is malformed or exceeds transport limits.");
101
+ const requestPath = path.join(dir, steerRequestFileName(request));
102
+ writeJsonToExistingDir(requestPath, request);
103
+ return requestPath;
104
+ }
105
+ export function writeSteerCapabilityAt(filePath, capability) {
106
+ assertChildIndex(capability.index);
107
+ if (!Number.isInteger(capability.pid) || capability.pid <= 0)
108
+ throw new Error("steer capability pid must be a positive integer.");
109
+ if (!Number.isFinite(capability.readyAt) || capability.readyAt <= 0)
110
+ throw new Error("steer capability readyAt must be a finite timestamp.");
111
+ const record = { type: "steer-capability", protocolVersion: 1, ...capability };
112
+ writeAtomicJson(filePath, record);
113
+ return filePath;
114
+ }
115
+ export function writeSteerCapability(asyncDir, capability) {
116
+ return writeSteerCapabilityAt(steerCapabilityPath(asyncDir, capability.index), capability);
117
+ }
118
+ function steerAckWritePath(filePath, ack) {
119
+ const parsed = path.parse(filePath);
120
+ const stateOrder = ack.state === "queued" ? "0" : ack.state === "delivered" ? "1" : "2";
121
+ const timestamp = String(Math.trunc(ack.ts)).padStart(13, "0");
122
+ for (let suffix = 0;suffix < 1000; suffix += 1) {
123
+ const candidate = path.join(parsed.dir, `${parsed.name}-${timestamp}-${stateOrder}-${ack.state}${suffix === 0 ? "" : `-${suffix}`}${parsed.ext}`);
124
+ if (!fs.existsSync(candidate))
125
+ return candidate;
126
+ }
127
+ throw new Error("steer acknowledgment queue is full.");
128
+ }
129
+ export function writeSteerAckAt(filePath, ack) {
130
+ assertChildIndex(ack.index);
131
+ if (!/^[^\s]+$/.test(ack.requestId) || ack.requestId.length > 256)
132
+ throw new Error("steer acknowledgment requestId is invalid.");
133
+ if (!Number.isFinite(ack.ts) || ack.ts <= 0)
134
+ throw new Error("steer acknowledgment ts must be a finite timestamp.");
135
+ if (!ack.message.trim() || ack.message.length > 1000)
136
+ throw new Error("steer acknowledgment message is invalid.");
137
+ const record = { type: "steer-ack", protocolVersion: 1, ...ack, message: ack.message.trim() };
138
+ const ackPath = steerAckWritePath(filePath, ack);
139
+ writeAtomicJson(ackPath, record);
140
+ return ackPath;
141
+ }
142
+ export function writeSteerAck(asyncDir, ack) {
143
+ return writeSteerAckAt(path.join(steerAcksDir(asyncDir, ack.index), steerAckFileName(ack.requestId)), ack);
144
+ }
32
145
  export function requestAsyncInterrupt(asyncDir, payload = {}, deps = {}) {
33
146
  const requestPath = interruptRequestPath(asyncDir);
34
147
  const request = { ...payload, ts: payload.ts ?? deps.now?.() ?? Date.now(), type: "interrupt" };
@@ -41,56 +154,199 @@ export function requestAsyncTimeout(asyncDir, payload = {}, deps = {}) {
41
154
  writeAtomicJson(requestPath, request);
42
155
  return requestPath;
43
156
  }
157
+ export function requestAsyncStop(asyncDir, payload = {}, deps = {}) {
158
+ if (payload.targetIndex !== undefined)
159
+ assertChildIndex(payload.targetIndex);
160
+ if (payload.childId !== undefined && !validStopChildId(payload.childId)) {
161
+ throw new Error("stop childId must be a non-empty string without newlines and at most 256 characters.");
162
+ }
163
+ const request = { ...payload, ts: payload.ts ?? deps.now?.() ?? Date.now(), type: "stop" };
164
+ const requestPath = path.join(stopRequestsDir(asyncDir), stopRequestFileName(request));
165
+ writeAtomicJson(requestPath, request);
166
+ return requestPath;
167
+ }
44
168
  export function requestAsyncSteer(asyncDir, payload, deps = {}) {
45
169
  const message = payload.message.trim();
46
170
  if (!message)
47
171
  throw new Error("steer message must not be empty.");
48
- if (payload.targetIndex !== undefined && (!Number.isInteger(payload.targetIndex) || payload.targetIndex < 0)) {
49
- throw new Error("steer targetIndex must be a non-negative integer.");
172
+ if (Buffer.byteLength(message, "utf8") > MAX_STEER_MESSAGE_BYTES)
173
+ throw new Error(`steer message exceeds ${MAX_STEER_MESSAGE_BYTES} UTF-8 bytes.`);
174
+ if (payload.targetIndex !== undefined && (!Number.isInteger(payload.targetIndex) || payload.targetIndex < 0 || payload.targetIndex > 1e6)) {
175
+ throw new Error("steer targetIndex must be an integer between 0 and 1000000.");
176
+ }
177
+ if (payload.targetIndexes !== undefined && (!Array.isArray(payload.targetIndexes) || payload.targetIndex !== undefined || payload.targetIndexes.length === 0 || payload.targetIndexes.length > 1000 || payload.targetIndexes.some((index) => !Number.isInteger(index) || index < 0 || index > 1e6) || new Set(payload.targetIndexes).size !== payload.targetIndexes.length)) {
178
+ throw new Error("steer targetIndexes must contain 1-1000 unique non-negative integers and cannot be combined with targetIndex.");
50
179
  }
180
+ const closedPath = steerInboxClosedPath(asyncDir);
181
+ if (fs.existsSync(closedPath))
182
+ throw new Error("Async run no longer accepts steering requests.");
51
183
  const request = {
52
184
  type: "steer",
53
185
  id: payload.id ?? deps.randomId?.() ?? randomUUID(),
54
186
  ts: payload.ts ?? deps.now?.() ?? Date.now(),
55
187
  message,
188
+ ...payload.mode && payload.mode !== "steer" ? { mode: payload.mode } : {},
56
189
  ...payload.targetIndex !== undefined ? { targetIndex: payload.targetIndex } : {},
190
+ ...payload.targetIndexes !== undefined ? { targetIndexes: [...payload.targetIndexes] } : {},
57
191
  ...payload.source ? { source: payload.source } : {}
58
192
  };
59
- return writeSteerRequestToDir(steerRequestsDir(asyncDir), request);
193
+ const requestPath = writeSteerRequestToDir(steerRequestsDir(asyncDir), request);
194
+ if (fs.existsSync(closedPath)) {
195
+ fs.rmSync(requestPath, { force: true });
196
+ throw new Error("Async run stopped accepting steering before the request was committed.");
197
+ }
198
+ return requestPath;
60
199
  }
61
200
  export function enqueueStepSteer(asyncDir, index, request) {
62
- if (!Number.isInteger(index) || index < 0)
63
- throw new Error("steer child index must be a non-negative integer.");
64
- return writeSteerRequestToDir(stepSteerInboxDir(asyncDir, index), { ...request, targetIndex: index, type: "steer" });
201
+ assertChildIndex(index);
202
+ const { targetIndexes: _targetIndexes, ...singleTargetRequest } = request;
203
+ return writeSteerRequestToDir(stepSteerInboxDir(asyncDir, index), { ...singleTargetRequest, targetIndex: index, type: "steer" });
65
204
  }
66
- function parseSteerRequest(raw) {
205
+ function parseSteerCapability(raw) {
67
206
  if (!raw || typeof raw !== "object" || Array.isArray(raw))
68
207
  return;
69
208
  const input = raw;
70
- if (input.type !== "steer")
209
+ if (input.type !== "steer-capability" || input.protocolVersion !== 1)
71
210
  return;
72
- if (typeof input.id !== "string" || !input.id.trim())
211
+ const { index, pid, readyAt, supported } = input;
212
+ if (typeof index !== "number" || !Number.isInteger(index) || index < 0 || index > 1e6)
73
213
  return;
74
- if (typeof input.ts !== "number" || !Number.isFinite(input.ts))
214
+ if (typeof pid !== "number" || typeof readyAt !== "number" || !Number.isInteger(pid) || pid <= 0 || !Number.isFinite(readyAt) || readyAt <= 0 || typeof supported !== "boolean")
75
215
  return;
76
- if (typeof input.message !== "string" || !input.message.trim())
216
+ return { type: "steer-capability", protocolVersion: 1, index, pid, readyAt, supported };
217
+ }
218
+ function parseSteerAck(raw) {
219
+ if (!raw || typeof raw !== "object" || Array.isArray(raw))
220
+ return;
221
+ const input = raw;
222
+ if (input.type !== "steer-ack" || input.protocolVersion !== 1 || typeof input.requestId !== "string" || !/^[^\s]+$/.test(input.requestId) || input.requestId.length > 256)
223
+ return;
224
+ const { index, ts, state, message } = input;
225
+ if (typeof index !== "number" || typeof ts !== "number" || !Number.isInteger(index) || index < 0 || index > 1e6 || !Number.isFinite(ts) || ts <= 0)
226
+ return;
227
+ if (state !== "delivered" && state !== "queued" && state !== "failed")
228
+ return;
229
+ if (input.deliveryStatus !== undefined && input.deliveryStatus !== "delivered" && input.deliveryStatus !== "queued")
230
+ return;
231
+ if (typeof message !== "string" || !message.trim() || message.length > 1000)
232
+ return;
233
+ return { type: "steer-ack", protocolVersion: 1, requestId: input.requestId, index, ts, state, ...input.deliveryStatus ? { deliveryStatus: input.deliveryStatus } : {}, message: message.trim() };
234
+ }
235
+ export function readSteerCapability(asyncDir, index) {
236
+ try {
237
+ return parseSteerCapability(JSON.parse(fs.readFileSync(steerCapabilityPath(asyncDir, index), "utf-8")));
238
+ } catch {
239
+ return;
240
+ }
241
+ }
242
+ export function consumeSteerCapabilities(asyncDir, fsImpl = fs) {
243
+ const dir = steerCapabilitiesDir(asyncDir);
244
+ if (!fsImpl.existsSync(dir))
245
+ return [];
246
+ const capabilities = [];
247
+ for (const entry of fsImpl.readdirSync(dir).filter((name) => /^\d+\.json$/.test(name)).sort()) {
248
+ try {
249
+ const capability = parseSteerCapability(JSON.parse(fsImpl.readFileSync(path.join(dir, entry), "utf-8")));
250
+ if (capability)
251
+ capabilities.push(capability);
252
+ } catch {}
253
+ }
254
+ return capabilities;
255
+ }
256
+ export function consumeSteerAckFromDir(dir, requestId, fsImpl = fs) {
257
+ if (!fsImpl.existsSync(dir))
258
+ return;
259
+ let entries;
260
+ try {
261
+ entries = fsImpl.readdirSync(dir).filter((name) => name.endsWith(".json")).sort();
262
+ } catch {
263
+ return;
264
+ }
265
+ for (const entry of entries) {
266
+ const target = path.join(dir, entry);
267
+ let ack;
268
+ try {
269
+ ack = parseSteerAck(JSON.parse(fsImpl.readFileSync(target, "utf-8")));
270
+ } catch {
271
+ ack = undefined;
272
+ }
273
+ if (ack?.requestId !== requestId)
274
+ continue;
275
+ try {
276
+ fsImpl.rmSync(target, { force: true });
277
+ } catch {
278
+ return;
279
+ }
280
+ return ack;
281
+ }
282
+ return;
283
+ }
284
+ export function consumeSteerAcks(asyncDir, fsImpl = fs) {
285
+ const root = path.join(controlInboxDir(asyncDir), STEER_ACKS_DIR);
286
+ if (!fsImpl.existsSync(root))
287
+ return [];
288
+ const acks = [];
289
+ let indexNames;
290
+ try {
291
+ indexNames = fsImpl.readdirSync(root).filter((name) => /^\d+$/.test(name));
292
+ } catch {
293
+ return [];
294
+ }
295
+ for (const indexName of indexNames) {
296
+ const dir = path.join(root, indexName);
297
+ let entries;
298
+ try {
299
+ entries = fsImpl.readdirSync(dir).filter((name) => name.endsWith(".json")).sort();
300
+ } catch {
301
+ continue;
302
+ }
303
+ for (const entry of entries) {
304
+ const target = path.join(dir, entry);
305
+ let ack;
306
+ try {
307
+ ack = parseSteerAck(JSON.parse(fsImpl.readFileSync(target, "utf-8")));
308
+ } catch {
309
+ ack = undefined;
310
+ }
311
+ try {
312
+ fsImpl.rmSync(target, { force: true });
313
+ } catch {
314
+ continue;
315
+ }
316
+ if (ack)
317
+ acks.push(ack);
318
+ }
319
+ }
320
+ return acks;
321
+ }
322
+ function parseSteerRequest(raw) {
323
+ if (!raw || typeof raw !== "object" || Array.isArray(raw))
77
324
  return;
78
- if (input.targetIndex !== undefined && (!Number.isInteger(input.targetIndex) || input.targetIndex < 0))
325
+ const input = raw;
326
+ if (!validSteerRequest(input))
79
327
  return;
80
328
  return {
81
329
  type: "steer",
82
330
  id: input.id.trim(),
83
331
  ts: input.ts,
84
332
  message: input.message.trim(),
333
+ ...input.mode ? { mode: input.mode } : {},
85
334
  ...input.targetIndex !== undefined ? { targetIndex: input.targetIndex } : {},
335
+ ...input.targetIndexes !== undefined ? { targetIndexes: [...input.targetIndexes] } : {},
86
336
  ...typeof input.source === "string" && input.source.trim() ? { source: input.source } : {}
87
337
  };
88
338
  }
89
339
  export function consumeSteerRequestsFromDir(dir, fsImpl = fs) {
90
340
  if (!fsImpl.existsSync(dir))
91
341
  return [];
342
+ let entries;
343
+ try {
344
+ entries = fsImpl.readdirSync(dir).filter((name) => name.endsWith(".json")).sort();
345
+ } catch {
346
+ return [];
347
+ }
92
348
  const requests = [];
93
- for (const entry of fsImpl.readdirSync(dir).filter((name) => name.endsWith(".json")).sort()) {
349
+ for (const entry of entries) {
94
350
  const requestPath = path.join(dir, entry);
95
351
  let parsed;
96
352
  try {
@@ -111,6 +367,27 @@ export function consumeSteerRequestsFromDir(dir, fsImpl = fs) {
111
367
  export function consumeSteerRequests(asyncDir, fsImpl = fs) {
112
368
  return consumeSteerRequestsFromDir(steerRequestsDir(asyncDir), fsImpl);
113
369
  }
370
+ export function queueRevivalBrief(asyncDir, request) {
371
+ const dir = path.join(controlInboxDir(asyncDir), REVIVAL_BRIEFS_DIR);
372
+ const queued = fs.existsSync(dir) ? fs.readdirSync(dir).filter((entry) => entry.endsWith(".json")).length : 0;
373
+ if (queued >= MAX_STEER_QUEUE_SIZE)
374
+ throw new Error(`Follow-up queue is full (${MAX_STEER_QUEUE_SIZE} messages).`);
375
+ return writeSteerRequestToDir(dir, { ...request, mode: "follow_up" });
376
+ }
377
+ export function readRevivalBriefs(asyncDir) {
378
+ const dir = path.join(controlInboxDir(asyncDir), REVIVAL_BRIEFS_DIR);
379
+ if (!fs.existsSync(dir))
380
+ return [];
381
+ return fs.readdirSync(dir).filter((entry) => entry.endsWith(".json")).sort().flatMap((entry) => {
382
+ const filePath = path.join(dir, entry);
383
+ try {
384
+ const request = parseSteerRequest(JSON.parse(fs.readFileSync(filePath, "utf-8")));
385
+ return request ? [{ request, path: filePath }] : [];
386
+ } catch {
387
+ return [];
388
+ }
389
+ });
390
+ }
114
391
  export function consumeInterruptRequest(asyncDir, fsImpl = fs) {
115
392
  const requestPath = interruptRequestPath(asyncDir);
116
393
  if (!fsImpl.existsSync(requestPath))
@@ -129,25 +406,78 @@ export function consumeTimeoutRequest(asyncDir, fsImpl = fs) {
129
406
  } catch {}
130
407
  return true;
131
408
  }
132
- export function deliverInterruptRequest(input) {
133
- const requestPath = requestAsyncInterrupt(input.asyncDir, input.source ? { source: input.source } : {}, { now: input.now });
134
- if (typeof input.pid === "number" && input.pid > 0) {
409
+ export function consumeStopRequest(asyncDir, fsImpl = fs) {
410
+ return consumeStopRequestPayload(asyncDir, fsImpl) !== undefined;
411
+ }
412
+ function parseStopRequest(raw) {
413
+ if (!raw || typeof raw !== "object" || Array.isArray(raw))
414
+ return;
415
+ const parsed = raw;
416
+ if (parsed.type !== "stop")
417
+ return;
418
+ if (Object.hasOwn(parsed, "targetIndex") && !(Number.isInteger(parsed.targetIndex) && parsed.targetIndex >= 0 && parsed.targetIndex <= 1e6))
419
+ return;
420
+ if (Object.hasOwn(parsed, "childId") && !validStopChildId(parsed.childId))
421
+ return;
422
+ return {
423
+ type: "stop",
424
+ ...typeof parsed.ts === "number" ? { ts: parsed.ts } : {},
425
+ ...typeof parsed.source === "string" ? { source: parsed.source } : {},
426
+ ...typeof parsed.reason === "string" ? { reason: parsed.reason } : {},
427
+ ...parsed.targetIndex !== undefined ? { targetIndex: parsed.targetIndex } : {},
428
+ ...validStopChildId(parsed.childId) ? { childId: parsed.childId } : {}
429
+ };
430
+ }
431
+ function consumeStopRequestFile(requestPath, fsImpl) {
432
+ let request;
433
+ try {
434
+ request = parseStopRequest(JSON.parse(fsImpl.readFileSync(requestPath, "utf-8")));
435
+ } catch {
436
+ request = undefined;
437
+ }
438
+ try {
439
+ fsImpl.rmSync(requestPath, { force: true, recursive: true });
440
+ } catch {
441
+ return;
442
+ }
443
+ return request;
444
+ }
445
+ export function consumeStopRequestPayloads(asyncDir, fsImpl = fs) {
446
+ const dir = stopRequestsDir(asyncDir);
447
+ const requests = [];
448
+ if (fsImpl.existsSync(dir)) {
449
+ let entries;
135
450
  try {
136
- (input.kill ?? process.kill)(input.pid, input.signal ?? INTERRUPT_SIGNAL);
137
- } catch (error) {
138
- if (error?.code === "ENOSYS") {
139
- return;
140
- }
141
- try {
142
- fs.rmSync(requestPath, { force: true });
143
- } catch {}
144
- throw error;
451
+ entries = fsImpl.readdirSync(dir).filter((name) => name.endsWith(".json")).sort();
452
+ } catch {
453
+ entries = [];
454
+ }
455
+ for (const entry of entries) {
456
+ const request = consumeStopRequestFile(path.join(dir, entry), fsImpl);
457
+ if (request)
458
+ requests.push(request);
145
459
  }
146
460
  }
461
+ const legacyPath = stopRequestPath(asyncDir);
462
+ if (fsImpl.existsSync(legacyPath)) {
463
+ const request = consumeStopRequestFile(legacyPath, fsImpl);
464
+ if (request)
465
+ requests.push(request);
466
+ }
467
+ return requests.sort((left, right) => (left.ts ?? 0) - (right.ts ?? 0));
468
+ }
469
+ export function consumeStopRequestPayload(asyncDir, fsImpl = fs) {
470
+ return consumeStopRequestPayloads(asyncDir, fsImpl)[0];
471
+ }
472
+ export function deliverInterruptRequest(input) {
473
+ requestAsyncInterrupt(input.asyncDir, input.source ? { source: input.source } : {}, { now: input.now });
147
474
  }
148
475
  export function deliverTimeoutRequest(input) {
149
476
  requestAsyncTimeout(input.asyncDir, input.source ? { source: input.source } : {}, { now: input.now });
150
477
  }
478
+ export function deliverStopRequest(input) {
479
+ requestAsyncStop(input.asyncDir, { ...input.source ? { source: input.source } : {}, ...input.targetIndex !== undefined ? { targetIndex: input.targetIndex } : {}, ...input.childId ? { childId: input.childId } : {} }, { now: input.now });
480
+ }
151
481
  export function watchAsyncControlInbox(asyncDir, opts) {
152
482
  const fsImpl = opts.fs ?? fs;
153
483
  const timers = opts.timers ?? { setInterval, clearInterval };
@@ -160,31 +490,88 @@ export function watchAsyncControlInbox(asyncDir, opts) {
160
490
  if (disposed)
161
491
  return;
162
492
  try {
493
+ for (const stopRequest of consumeStopRequestPayloads(asyncDir, fsImpl))
494
+ opts.onStop?.(stopRequest);
163
495
  if (consumeTimeoutRequest(asyncDir, fsImpl))
164
496
  opts.onTimeout?.();
165
497
  if (consumeInterruptRequest(asyncDir, fsImpl))
166
498
  opts.onInterrupt();
167
499
  for (const request of consumeSteerRequests(asyncDir, fsImpl))
168
500
  opts.onSteer?.(request);
501
+ for (const capability of consumeSteerCapabilities(asyncDir, fsImpl))
502
+ opts.onSteerCapability?.(capability);
503
+ for (const ack of consumeSteerAcks(asyncDir, fsImpl))
504
+ opts.onSteerAck?.(ack);
169
505
  } catch {}
170
506
  };
171
507
  check();
172
- let watcher;
508
+ const watchers = [];
509
+ const watchedDirs = new Set;
510
+ let interval;
511
+ let safetyInterval;
512
+ const startPolling = () => {
513
+ if (interval || disposed)
514
+ return;
515
+ interval = timers.setInterval(check, opts.pollIntervalMs ?? POLL_INTERVAL_MS);
516
+ interval.unref?.();
517
+ };
518
+ const startSafetyPolling = () => {
519
+ if (safetyInterval || disposed)
520
+ return;
521
+ safetyInterval = timers.setInterval(check, opts.safetyPollIntervalMs ?? CONTROL_SAFETY_POLL_INTERVAL_MS);
522
+ safetyInterval.unref?.();
523
+ };
524
+ const watchDir = (target, create = false) => {
525
+ if (disposed || watchedDirs.has(target))
526
+ return;
527
+ if (create)
528
+ fsImpl.mkdirSync(target, { recursive: true });
529
+ const watcher = fsImpl.watch(resolveWatchPath(target, fsImpl.realpathSync.native), () => {
530
+ watchExistingSteerAckDirs();
531
+ check();
532
+ });
533
+ watcher.on?.("error", startPolling);
534
+ watchers.push(watcher);
535
+ watchedDirs.add(target);
536
+ };
537
+ const watchExistingSteerAckDirs = () => {
538
+ const ackRoot = path.join(dir, STEER_ACKS_DIR);
539
+ let entries;
540
+ try {
541
+ entries = fsImpl.readdirSync(ackRoot).filter((name) => /^\d+$/.test(name));
542
+ } catch {
543
+ return;
544
+ }
545
+ for (const entry of entries)
546
+ watchDir(path.join(ackRoot, entry));
547
+ };
173
548
  try {
174
- watcher = fsImpl.watch(dir, () => check());
175
- watcher.on?.("error", () => {});
549
+ if (shouldUseNativeFsWatch("runner-control-inbox", opts.platform)) {
550
+ watchDir(dir);
551
+ watchDir(stopRequestsDir(asyncDir), true);
552
+ watchDir(steerRequestsDir(asyncDir), true);
553
+ watchDir(steerCapabilitiesDir(asyncDir), true);
554
+ watchDir(path.join(dir, STEER_ACKS_DIR), true);
555
+ watchExistingSteerAckDirs();
556
+ startSafetyPolling();
557
+ } else {
558
+ startPolling();
559
+ }
176
560
  } catch {
177
- watcher = undefined;
561
+ startPolling();
178
562
  }
179
- const interval = timers.setInterval(check, opts.pollIntervalMs ?? POLL_INTERVAL_MS);
180
- interval.unref?.();
181
563
  return () => {
182
564
  if (disposed)
183
565
  return;
184
566
  disposed = true;
185
- try {
186
- watcher?.close();
187
- } catch {}
188
- timers.clearInterval(interval);
567
+ for (const watcher of watchers) {
568
+ try {
569
+ watcher.close();
570
+ } catch {}
571
+ }
572
+ if (interval)
573
+ timers.clearInterval(interval);
574
+ if (safetyInterval)
575
+ timers.clearInterval(safetyInterval);
189
576
  };
190
577
  }