@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,334 @@
1
+ import { createHash } from "node:crypto";
2
+ import * as fs from "node:fs";
3
+ import * as path from "node:path";
4
+ import { ExternalJobProviderError } from "../../api/external-job-provider.js";
5
+ import { requestExternalJobOperation } from "./external-job-bridge.js";
6
+ const STATUS_POLL_INTERVAL_MS = 1000;
7
+ export function externalJobPromptDigest(prompt) {
8
+ return createHash("sha256").update(prompt).digest("hex");
9
+ }
10
+ export function externalJobStableJson(value) {
11
+ if (value === null || typeof value !== "object")
12
+ return JSON.stringify(value);
13
+ if (Array.isArray(value))
14
+ return `[${value.map(externalJobStableJson).join(",")}]`;
15
+ const record = value;
16
+ return `{${Object.keys(record).sort().map((key) => `${JSON.stringify(key)}:${externalJobStableJson(record[key])}`).join(",")}}`;
17
+ }
18
+ export function externalJobFollowUpRequestDigest(input) {
19
+ return createHash("sha256").update(externalJobStableJson({ provider: input.provider, parentProviderJobId: input.parentProviderJobId, promptDigest: input.promptDigest, options: input.options })).digest("hex");
20
+ }
21
+ export function externalJobFollowUpRequestId(requestDigest) {
22
+ return `follow-up-${requestDigest.slice(0, 48)}`;
23
+ }
24
+ export function externalJobFollowUpRunId(requestDigest) {
25
+ const hex = requestDigest.padEnd(32, "0").slice(0, 32).split("");
26
+ hex[12] = "4";
27
+ hex[16] = (Number.parseInt(hex[16], 16) & 3 | 8).toString(16);
28
+ const value = hex.join("");
29
+ return `${value.slice(0, 8)}-${value.slice(8, 12)}-${value.slice(12, 16)}-${value.slice(16, 20)}-${value.slice(20, 32)}`;
30
+ }
31
+ function sleep(ms) {
32
+ return new Promise((resolve) => setTimeout(resolve, ms));
33
+ }
34
+ function terminal(state) {
35
+ return state === "completed" || state === "failed" || state === "stopped" || state === "blocked";
36
+ }
37
+ function formatError(error, provider) {
38
+ const blocking = error.blockingJobId ? ` Blocking provider job: ${error.blockingJobId}.` : "";
39
+ return `External-job provider '${provider}' failed closed (${error.code}): ${error.message}.${blocking}`;
40
+ }
41
+ function isNotFound(error) {
42
+ return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
43
+ }
44
+ function isRecord(value) {
45
+ return typeof value === "object" && value !== null && !Array.isArray(value);
46
+ }
47
+ function malformedStatus(statusPath) {
48
+ return new ExternalJobProviderError(`Malformed external-job status '${statusPath}'. Refusing to redispatch the prompt.`, { code: "status-unreadable" });
49
+ }
50
+ function parseExternalJobStatus(value, statusPath) {
51
+ if (!isRecord(value))
52
+ throw malformedStatus(statusPath);
53
+ const provider = value.provider;
54
+ const promptDigest = value.promptDigest;
55
+ const state = value.state;
56
+ if (typeof provider !== "string" || provider.length === 0)
57
+ throw malformedStatus(statusPath);
58
+ if (typeof promptDigest !== "string" || promptDigest.length === 0)
59
+ throw malformedStatus(statusPath);
60
+ if (state !== "queued" && state !== "running" && state !== "completed" && state !== "failed" && state !== "stopped" && state !== "blocked")
61
+ throw malformedStatus(statusPath);
62
+ if (value.options !== undefined && !isRecord(value.options))
63
+ throw malformedStatus(statusPath);
64
+ const providerJobId = value.providerJobId;
65
+ const handleUrl = value.handleUrl;
66
+ const conversationUrl = value.conversationUrl;
67
+ const resultArtifactPath = value.resultArtifactPath;
68
+ const failureCode = value.failureCode;
69
+ const failureMessage = value.failureMessage;
70
+ const blockingJobId = value.blockingJobId;
71
+ const startedAt = value.startedAt;
72
+ const updatedAt = value.updatedAt;
73
+ if (providerJobId !== undefined && (typeof providerJobId !== "string" || providerJobId.length === 0))
74
+ throw malformedStatus(statusPath);
75
+ if (handleUrl !== undefined && typeof handleUrl !== "string")
76
+ throw malformedStatus(statusPath);
77
+ if (conversationUrl !== undefined && typeof conversationUrl !== "string")
78
+ throw malformedStatus(statusPath);
79
+ if (resultArtifactPath !== undefined && typeof resultArtifactPath !== "string")
80
+ throw malformedStatus(statusPath);
81
+ if (failureCode !== undefined && typeof failureCode !== "string")
82
+ throw malformedStatus(statusPath);
83
+ if (failureMessage !== undefined && typeof failureMessage !== "string")
84
+ throw malformedStatus(statusPath);
85
+ if (blockingJobId !== undefined && typeof blockingJobId !== "string")
86
+ throw malformedStatus(statusPath);
87
+ const operation = value.operation;
88
+ const sourceRunId = value.sourceRunId;
89
+ const sourceStepIndex = value.sourceStepIndex;
90
+ const parentProviderJobId = value.parentProviderJobId;
91
+ const requestId = value.requestId;
92
+ const requestDigest = value.requestDigest;
93
+ if (operation !== undefined && operation !== "start" && operation !== "follow-up")
94
+ throw malformedStatus(statusPath);
95
+ if (sourceRunId !== undefined && (typeof sourceRunId !== "string" || sourceRunId.length === 0))
96
+ throw malformedStatus(statusPath);
97
+ if (sourceStepIndex !== undefined && (typeof sourceStepIndex !== "number" || !Number.isInteger(sourceStepIndex) || sourceStepIndex < 0))
98
+ throw malformedStatus(statusPath);
99
+ if (parentProviderJobId !== undefined && (typeof parentProviderJobId !== "string" || parentProviderJobId.length === 0))
100
+ throw malformedStatus(statusPath);
101
+ if (requestId !== undefined && (typeof requestId !== "string" || requestId.length === 0))
102
+ throw malformedStatus(statusPath);
103
+ if (requestDigest !== undefined && (typeof requestDigest !== "string" || requestDigest.length === 0))
104
+ throw malformedStatus(statusPath);
105
+ if (startedAt !== undefined && typeof startedAt !== "number")
106
+ throw malformedStatus(statusPath);
107
+ if (updatedAt !== undefined && typeof updatedAt !== "number")
108
+ throw malformedStatus(statusPath);
109
+ return {
110
+ provider,
111
+ promptDigest,
112
+ ...operation ? { operation } : {},
113
+ ...sourceRunId ? { sourceRunId } : {},
114
+ ...sourceStepIndex !== undefined ? { sourceStepIndex } : {},
115
+ ...parentProviderJobId ? { parentProviderJobId } : {},
116
+ ...requestId ? { requestId } : {},
117
+ ...requestDigest ? { requestDigest } : {},
118
+ options: value.options ?? {},
119
+ state,
120
+ ...providerJobId ? { providerJobId } : {},
121
+ ...handleUrl ? { handleUrl } : {},
122
+ ...conversationUrl ? { conversationUrl } : {},
123
+ ...resultArtifactPath ? { resultArtifactPath } : {},
124
+ ...failureCode ? { failureCode } : {},
125
+ ...failureMessage ? { failureMessage } : {},
126
+ ...blockingJobId ? { blockingJobId } : {},
127
+ ...startedAt !== undefined ? { startedAt } : {},
128
+ ...updatedAt !== undefined ? { updatedAt } : {}
129
+ };
130
+ }
131
+ function readExistingExternalJob(asyncDir, stepIndex) {
132
+ const statusPath = path.join(asyncDir, "status.json");
133
+ let raw;
134
+ try {
135
+ raw = fs.readFileSync(statusPath, "utf-8");
136
+ } catch (error) {
137
+ if (isNotFound(error))
138
+ return;
139
+ throw new ExternalJobProviderError(`Unreadable external-job status '${statusPath}'. Refusing to redispatch the prompt.`, { code: "status-unreadable", cause: error });
140
+ }
141
+ let parsed;
142
+ try {
143
+ parsed = JSON.parse(raw);
144
+ } catch (error) {
145
+ throw new ExternalJobProviderError(`Malformed external-job status '${statusPath}'. Refusing to redispatch the prompt.`, { code: "status-unreadable", cause: error });
146
+ }
147
+ if (!isRecord(parsed) || !Array.isArray(parsed.steps))
148
+ throw malformedStatus(statusPath);
149
+ const step = parsed.steps[stepIndex];
150
+ if (step === undefined || !isRecord(step))
151
+ throw malformedStatus(statusPath);
152
+ if (step.externalJob === undefined)
153
+ return;
154
+ return parseExternalJobStatus(step.externalJob, statusPath);
155
+ }
156
+ function externalJobLineage(followUp, previous) {
157
+ return {
158
+ operation: followUp ? "follow-up" : previous?.operation,
159
+ ...followUp ? {
160
+ sourceRunId: followUp.sourceRunId,
161
+ sourceStepIndex: followUp.sourceStepIndex,
162
+ parentProviderJobId: followUp.parentProviderJobId,
163
+ requestId: followUp.requestId,
164
+ requestDigest: followUp.requestDigest
165
+ } : {
166
+ ...previous?.sourceRunId ? { sourceRunId: previous.sourceRunId } : {},
167
+ ...previous?.sourceStepIndex !== undefined ? { sourceStepIndex: previous.sourceStepIndex } : {},
168
+ ...previous?.parentProviderJobId ? { parentProviderJobId: previous.parentProviderJobId } : {},
169
+ ...previous?.requestId ? { requestId: previous.requestId } : {},
170
+ ...previous?.requestDigest ? { requestDigest: previous.requestDigest } : {}
171
+ }
172
+ };
173
+ }
174
+ function statusFromHandle(input) {
175
+ return {
176
+ provider: input.provider,
177
+ providerJobId: input.handle.providerJobId,
178
+ promptDigest: input.promptDigest,
179
+ ...externalJobLineage(input.followUp, input.previous),
180
+ options: input.options,
181
+ handleUrl: input.handle.handleUrl ?? input.previous?.handleUrl,
182
+ conversationUrl: input.handle.conversationUrl ?? input.previous?.conversationUrl,
183
+ resultArtifactPath: input.resultArtifactPath ?? input.previous?.resultArtifactPath,
184
+ state: input.handle.state,
185
+ failureCode: input.handle.failureCode,
186
+ failureMessage: input.handle.failureMessage,
187
+ blockingJobId: input.handle.blockingJobId,
188
+ startedAt: input.previous?.startedAt ?? input.startedAt,
189
+ updatedAt: Date.now()
190
+ };
191
+ }
192
+ function failureStatus(input) {
193
+ return {
194
+ provider: input.provider,
195
+ providerJobId: input.previous?.providerJobId,
196
+ promptDigest: input.promptDigest,
197
+ ...externalJobLineage(input.followUp, input.previous),
198
+ options: input.options,
199
+ handleUrl: input.previous?.handleUrl,
200
+ conversationUrl: input.previous?.conversationUrl,
201
+ resultArtifactPath: input.previous?.resultArtifactPath,
202
+ state: input.blockingJobId ? "blocked" : "failed",
203
+ failureCode: input.code,
204
+ failureMessage: input.message,
205
+ blockingJobId: input.blockingJobId,
206
+ startedAt: input.previous?.startedAt,
207
+ updatedAt: Date.now()
208
+ };
209
+ }
210
+ function resultOutput(result, artifactPath) {
211
+ if (result.output?.trim())
212
+ return result.output.trim();
213
+ if (artifactPath)
214
+ return `External job finished. Result artifact: ${artifactPath}`;
215
+ return "External job finished without text output.";
216
+ }
217
+ function blocksStartRedispatch(status, provider, promptDigest) {
218
+ return status.provider === provider && status.promptDigest === promptDigest && !status.providerJobId && status.failureCode !== "provider-unavailable";
219
+ }
220
+ function sameFollowUpLineage(status, followUp) {
221
+ if (!followUp)
222
+ return status.operation !== "follow-up";
223
+ return status.operation === "follow-up" && status.sourceRunId === followUp.sourceRunId && status.sourceStepIndex === followUp.sourceStepIndex && status.parentProviderJobId === followUp.parentProviderJobId && status.requestId === followUp.requestId && status.requestDigest === followUp.requestDigest;
224
+ }
225
+ export async function runExternalJob(input) {
226
+ const provider = input.provider;
227
+ const options = input.options ?? {};
228
+ const promptDigest = externalJobPromptDigest(input.prompt);
229
+ const startedAt = Date.now();
230
+ let timedOut = false;
231
+ let stopped = false;
232
+ let current;
233
+ const timeout = () => {
234
+ timedOut = true;
235
+ };
236
+ const stop = () => {
237
+ stopped = true;
238
+ };
239
+ input.registerTimeout?.(timeout);
240
+ input.registerStop?.(stop);
241
+ const publish = (status) => {
242
+ current = status;
243
+ input.onExternalJob?.(status);
244
+ };
245
+ const localCancellation = () => {
246
+ if (!timedOut && !stopped)
247
+ return;
248
+ const message = stopped ? input.stopMessage ?? `Subagent stopped locally before external provider '${provider}' returned a job id. The provider start may still be running.` : input.timeoutMessage ?? `Subagent timed out locally before external provider '${provider}' returned a job id. The provider start may still be running.`;
249
+ return new ExternalJobProviderError(message, { code: stopped ? "local-stop" : "local-timeout" });
250
+ };
251
+ try {
252
+ current = readExistingExternalJob(input.asyncDir, input.stepIndex);
253
+ let handle;
254
+ if (current?.providerJobId) {
255
+ if (current.provider !== provider || current.promptDigest !== promptDigest || !sameFollowUpLineage(current, input.followUp)) {
256
+ const message = `Existing external job '${current.providerJobId}' does not match provider, prompt digest, or follow-up lineage. Refusing to redispatch prompt.`;
257
+ const status = failureStatus({ provider, promptDigest, options, followUp: input.followUp, previous: current, code: "recovery-mismatch", message });
258
+ publish(status);
259
+ return { output: message, exitCode: 1, error: message, externalJob: status };
260
+ }
261
+ handle = await requestExternalJobOperation(input.asyncDir, { operation: "reattach", provider, providerJobId: current.providerJobId });
262
+ } else {
263
+ if (current && blocksStartRedispatch(current, provider, promptDigest)) {
264
+ const message = `External-job ${input.followUp ? "follow-up" : "start"} for provider '${provider}' previously ended without a durable provider job id. Refusing to redispatch the prompt automatically.`;
265
+ const status = failureStatus({ provider, promptDigest, options, followUp: input.followUp, previous: current, code: input.followUp ? "dispatch-redispatch-blocked" : "start-redispatch-blocked", message });
266
+ publish(status);
267
+ return { output: message, exitCode: 1, error: message, externalJob: status };
268
+ }
269
+ publish({ provider, promptDigest, ...input.followUp ? { operation: "follow-up", ...input.followUp } : {}, options, state: "queued", startedAt, updatedAt: Date.now() });
270
+ if (input.followUp) {
271
+ handle = await requestExternalJobOperation(input.asyncDir, {
272
+ operation: "follow-up",
273
+ provider,
274
+ followUp: {
275
+ prompt: input.prompt,
276
+ promptDigest,
277
+ cwd: input.cwd,
278
+ runId: input.runId,
279
+ stepIndex: input.stepIndex,
280
+ agent: input.agent,
281
+ options,
282
+ ...input.sessionId ? { sessionId: input.sessionId } : {},
283
+ ...input.followUp
284
+ }
285
+ }, undefined, localCancellation);
286
+ } else {
287
+ handle = await requestExternalJobOperation(input.asyncDir, {
288
+ operation: "start",
289
+ provider,
290
+ start: {
291
+ prompt: input.prompt,
292
+ promptDigest,
293
+ cwd: input.cwd,
294
+ runId: input.runId,
295
+ stepIndex: input.stepIndex,
296
+ agent: input.agent,
297
+ options,
298
+ ...input.sessionId ? { sessionId: input.sessionId } : {}
299
+ }
300
+ }, undefined, localCancellation);
301
+ }
302
+ }
303
+ publish(statusFromHandle({ provider, promptDigest, options, followUp: input.followUp, startedAt, previous: current, handle }));
304
+ while (!terminal(handle.state)) {
305
+ if (timedOut || stopped) {
306
+ const message = stopped ? input.stopMessage ?? `Subagent stopped locally; external provider job '${handle.providerJobId}' may still be running.` : input.timeoutMessage ?? `Subagent timed out locally; external provider job '${handle.providerJobId}' may still be running.`;
307
+ return { output: message, exitCode: 1, error: message, ...timedOut ? { timedOut: true } : {}, ...stopped ? { stopped: true } : {}, externalJob: current };
308
+ }
309
+ await sleep(STATUS_POLL_INTERVAL_MS);
310
+ handle = await requestExternalJobOperation(input.asyncDir, { operation: "status", provider, providerJobId: handle.providerJobId });
311
+ publish(statusFromHandle({ provider, promptDigest, options, followUp: input.followUp, previous: current, handle }));
312
+ }
313
+ const result = await requestExternalJobOperation(input.asyncDir, { operation: "result", provider, providerJobId: handle.providerJobId });
314
+ let artifactPath = result.artifactPath;
315
+ if (!artifactPath && result.output !== undefined) {
316
+ artifactPath = path.join(input.asyncDir, `external-job-${input.stepIndex}.result.md`);
317
+ fs.writeFileSync(artifactPath, result.output, "utf-8");
318
+ }
319
+ const finalStatus = statusFromHandle({ provider, promptDigest, options, followUp: input.followUp, previous: current, handle: result, resultArtifactPath: artifactPath });
320
+ publish(finalStatus);
321
+ const output = resultOutput(result, artifactPath);
322
+ const error = result.state === "completed" ? undefined : result.failureMessage ?? `External job ${result.state}.`;
323
+ return { output, exitCode: result.state === "completed" ? 0 : 1, ...error ? { error } : {}, externalJob: finalStatus };
324
+ } catch (error) {
325
+ const providerError = error instanceof ExternalJobProviderError ? error : new ExternalJobProviderError(error instanceof Error ? error.message : String(error), { code: "provider-error", cause: error });
326
+ const message = formatError(providerError, provider);
327
+ const status = failureStatus({ provider, promptDigest, options, followUp: input.followUp, previous: current, code: providerError.code, message, ...providerError.blockingJobId ? { blockingJobId: providerError.blockingJobId } : {} });
328
+ publish(status);
329
+ return { output: message, exitCode: 1, error: message, ...timedOut ? { timedOut: true } : {}, ...stopped ? { stopped: true } : {}, externalJob: status };
330
+ } finally {
331
+ input.registerTimeout?.(undefined);
332
+ input.registerStop?.(undefined);
333
+ }
334
+ }
@@ -0,0 +1,8 @@
1
+ export function rewriteFastModeProviderRequest(event) {
2
+ if (!event.payload || typeof event.payload !== "object" || Array.isArray(event.payload))
3
+ return event.payload;
4
+ return { ...event.payload, service_tier: "priority" };
5
+ }
6
+ export default function registerSubagentFastModeExtension(dm) {
7
+ dm.on("before_provider_request", rewriteFastModeProviderRequest);
8
+ }
@@ -0,0 +1,228 @@
1
+ export const HOST_STEP_MAX_ID_CHARS = 128;
2
+ export const HOST_STEP_MAX_LABEL_CHARS = 80;
3
+ export const HOST_STEP_MAX_ROLE_CHARS = 32;
4
+ export const HOST_STEP_MAX_PROVIDER_CHARS = 64;
5
+ export const HOST_STEP_MAX_REASON_CHARS = 64;
6
+ export const HOST_STEP_MAX_DETAIL_CHARS = 200;
7
+ export const HOST_STEP_MAX_TARGET_CHARS = 128;
8
+ export const HOST_STEP_MAX_REF_CHARS = 128;
9
+ export const HOST_STEP_MAX_REPORT_PATH_CHARS = 240;
10
+ export const HOST_STEP_MAX_COUNT = 32;
11
+ const HOST_STEP_FIELDS = new Set([
12
+ "version",
13
+ "kind",
14
+ "monitorKind",
15
+ "id",
16
+ "label",
17
+ "role",
18
+ "provider",
19
+ "state",
20
+ "verdict",
21
+ "reasonCode",
22
+ "detail",
23
+ "target",
24
+ "freshness",
25
+ "reportPath",
26
+ "exitCode",
27
+ "updatedAt",
28
+ "deadlineAt"
29
+ ]);
30
+ const FRESHNESS_FIELDS = new Set(["expectedRef", "observedRef", "stale"]);
31
+ function isRecord(value) {
32
+ return typeof value === "object" && value !== null && !Array.isArray(value);
33
+ }
34
+ function assertBoundedString(value, field, maxChars, source, required = false) {
35
+ if (value === undefined && !required)
36
+ return;
37
+ if (typeof value !== "string" || !value.trim())
38
+ throw new Error(`Invalid host step '${source}': ${field} must be a non-empty string.`);
39
+ if (value.length > maxChars)
40
+ throw new Error(`Invalid host step '${source}': ${field} exceeds ${maxChars} characters.`);
41
+ if (/\r|\n/.test(value))
42
+ throw new Error(`Invalid host step '${source}': ${field} must be single-line.`);
43
+ }
44
+ function assertTimestamp(value, field, source, required = false) {
45
+ if (value === undefined && !required)
46
+ return;
47
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0)
48
+ throw new Error(`Invalid host step '${source}': ${field} must be a non-negative safe integer.`);
49
+ }
50
+ function assertFreshness(value, source) {
51
+ if (!isRecord(value))
52
+ throw new Error(`Invalid host step '${source}': freshness must be an object.`);
53
+ const unknownFields = Object.keys(value).filter((field) => !FRESHNESS_FIELDS.has(field));
54
+ if (unknownFields.length > 0)
55
+ throw new Error(`Invalid host step '${source}': freshness has unsupported fields: ${unknownFields.join(", ")}.`);
56
+ assertBoundedString(value.expectedRef, "freshness.expectedRef", HOST_STEP_MAX_REF_CHARS, source, true);
57
+ assertBoundedString(value.observedRef, "freshness.observedRef", HOST_STEP_MAX_REF_CHARS, source);
58
+ if (value.stale !== undefined && typeof value.stale !== "boolean")
59
+ throw new Error(`Invalid host step '${source}': freshness.stale must be a boolean.`);
60
+ }
61
+ export function assertHostStepNode(value, source = "status") {
62
+ if (!isRecord(value))
63
+ throw new Error(`Invalid host step '${source}': expected an object.`);
64
+ const unknownFields = Object.keys(value).filter((field) => !HOST_STEP_FIELDS.has(field));
65
+ if (unknownFields.length > 0)
66
+ throw new Error(`Invalid host step '${source}': unsupported fields: ${unknownFields.join(", ")}.`);
67
+ if (value.version !== 1)
68
+ throw new Error(`Invalid host step '${source}': version must be 1.`);
69
+ if (value.kind !== "host-step")
70
+ throw new Error(`Invalid host step '${source}': kind must be 'host-step'.`);
71
+ if (value.monitorKind !== "command" && value.monitorKind !== "ci" && value.monitorKind !== "gate")
72
+ throw new Error(`Invalid host step '${source}': monitorKind must be 'command', 'ci', or 'gate'.`);
73
+ assertBoundedString(value.id, "id", HOST_STEP_MAX_ID_CHARS, source, true);
74
+ assertBoundedString(value.label, "label", HOST_STEP_MAX_LABEL_CHARS, source, true);
75
+ assertBoundedString(value.role, "role", HOST_STEP_MAX_ROLE_CHARS, source);
76
+ assertBoundedString(value.provider, "provider", HOST_STEP_MAX_PROVIDER_CHARS, source);
77
+ if (value.state !== "pending" && value.state !== "running" && value.state !== "done" && value.state !== "cancelled" && value.state !== "error") {
78
+ throw new Error(`Invalid host step '${source}': state is invalid.`);
79
+ }
80
+ if (value.verdict !== undefined && value.verdict !== "pass" && value.verdict !== "fail" && value.verdict !== "inconclusive") {
81
+ throw new Error(`Invalid host step '${source}': verdict is invalid.`);
82
+ }
83
+ if (value.state !== "done" && value.verdict !== undefined)
84
+ throw new Error(`Invalid host step '${source}': verdict is only valid for done state.`);
85
+ assertBoundedString(value.reasonCode, "reasonCode", HOST_STEP_MAX_REASON_CHARS, source);
86
+ assertBoundedString(value.detail, "detail", HOST_STEP_MAX_DETAIL_CHARS, source);
87
+ assertBoundedString(value.target, "target", HOST_STEP_MAX_TARGET_CHARS, source);
88
+ if (value.freshness !== undefined)
89
+ assertFreshness(value.freshness, source);
90
+ if (value.freshness && value.freshness.stale === true && (value.state !== "done" || value.verdict !== "inconclusive")) {
91
+ throw new Error(`Invalid host step '${source}': stale freshness requires done/inconclusive state.`);
92
+ }
93
+ assertBoundedString(value.reportPath, "reportPath", HOST_STEP_MAX_REPORT_PATH_CHARS, source);
94
+ const reportName = hostStepReportName(value.reportPath);
95
+ if (value.reportPath !== undefined && (!reportName || reportName === "." || reportName === ".."))
96
+ throw new Error(`Invalid host step '${source}': reportPath must name a report.`);
97
+ if (value.exitCode !== undefined && value.exitCode !== null && (typeof value.exitCode !== "number" || !Number.isSafeInteger(value.exitCode) || value.exitCode < 0))
98
+ throw new Error(`Invalid host step '${source}': exitCode must be a non-negative safe integer or null.`);
99
+ if (value.monitorKind !== "command" && value.exitCode !== undefined)
100
+ throw new Error(`Invalid host step '${source}': exitCode is only valid for command steps.`);
101
+ if ((value.state === "pending" || value.state === "running") && value.exitCode !== undefined)
102
+ throw new Error(`Invalid host step '${source}': exitCode is only valid after command settlement.`);
103
+ assertTimestamp(value.updatedAt, "updatedAt", source, true);
104
+ assertTimestamp(value.deadlineAt, "deadlineAt", source);
105
+ }
106
+ export function parseHostStepNode(value, source = "status") {
107
+ assertHostStepNode(value, source);
108
+ return {
109
+ ...value,
110
+ ...value.freshness ? { freshness: { ...value.freshness } } : {}
111
+ };
112
+ }
113
+ export function assertUniqueHostStepIds(hostSteps, source = "status") {
114
+ const ids = new Set;
115
+ for (const hostStep of hostSteps) {
116
+ if (ids.has(hostStep.id))
117
+ throw new Error(`Invalid host step '${source}': duplicate host step id '${hostStep.id}'.`);
118
+ ids.add(hostStep.id);
119
+ }
120
+ }
121
+ export function validHostStepNodes(graph) {
122
+ const nodes = graph?.nodes ?? [];
123
+ const nodeIdCounts = new Map;
124
+ for (const node of nodes)
125
+ nodeIdCounts.set(node.id, (nodeIdCounts.get(node.id) ?? 0) + 1);
126
+ const hostSteps = [];
127
+ for (const [index, node] of nodes.entries()) {
128
+ if (node.kind !== "host-step")
129
+ continue;
130
+ try {
131
+ const hostStep = parseHostStepNode(node.hostStep, `workflowGraph.nodes[${index}].hostStep`);
132
+ if (node.id !== hostStep.id || node.label !== hostStep.label)
133
+ continue;
134
+ if (nodeIdCounts.get(hostStep.id) !== 1)
135
+ continue;
136
+ hostSteps.push(hostStep);
137
+ } catch {}
138
+ }
139
+ return hostSteps.slice(0, HOST_STEP_MAX_COUNT);
140
+ }
141
+ export function assertWorkflowGraphHostSteps(graph, source = "status", expectedRunId) {
142
+ if (!graph)
143
+ return;
144
+ if (expectedRunId !== undefined && graph.runId !== expectedRunId)
145
+ throw new Error(`Invalid host step '${source}': workflowGraph.runId does not match the status run id.`);
146
+ const hostStepCount = graph.nodes.filter((node) => node.kind === "host-step").length;
147
+ if (hostStepCount > HOST_STEP_MAX_COUNT)
148
+ throw new Error(`Invalid host step '${source}': workflowGraph contains more than ${HOST_STEP_MAX_COUNT} host steps.`);
149
+ const hostSteps = [];
150
+ for (const [index, node] of graph.nodes.entries()) {
151
+ if (node.kind !== "host-step")
152
+ continue;
153
+ const hostStep = parseHostStepNode(node.hostStep, `${source}.workflowGraph.nodes[${index}].hostStep`);
154
+ if (node.id !== hostStep.id)
155
+ throw new Error(`Invalid host step '${source}': workflowGraph.nodes[${index}].id does not match hostStep.id.`);
156
+ if (node.label !== hostStep.label)
157
+ throw new Error(`Invalid host step '${source}': workflowGraph.nodes[${index}].label does not match hostStep.label.`);
158
+ hostSteps.push(hostStep);
159
+ }
160
+ assertUniqueHostStepIds(hostSteps, source);
161
+ for (const hostStep of hostSteps) {
162
+ if (graph.nodes.filter((node) => node.id === hostStep.id).length !== 1)
163
+ throw new Error(`Invalid host step '${source}': workflowGraph id '${hostStep.id}' is not unique.`);
164
+ }
165
+ }
166
+ export function hostStepWorkflowNode(hostStep) {
167
+ assertHostStepNode(hostStep, hostStep.id);
168
+ return {
169
+ id: hostStep.id,
170
+ kind: "host-step",
171
+ label: hostStep.label,
172
+ status: workflowNodeStatus(hostStep),
173
+ hostStep: { ...hostStep, ...hostStep.freshness ? { freshness: { ...hostStep.freshness } } : {} }
174
+ };
175
+ }
176
+ function workflowNodeStatus(hostStep) {
177
+ if (hostStep.state === "pending")
178
+ return "pending";
179
+ if (hostStep.state === "running")
180
+ return "running";
181
+ if (hostStep.state === "cancelled")
182
+ return "stopped";
183
+ if (hostStep.state === "error")
184
+ return "failed";
185
+ if (hostStep.verdict === undefined || hostStep.verdict === "inconclusive")
186
+ return "partial";
187
+ return hostStep.verdict === "fail" ? "failed" : "completed";
188
+ }
189
+ export function upsertHostStep(input) {
190
+ const hostStep = parseHostStepNode(input.hostStep, input.hostStep.id);
191
+ assertWorkflowGraphHostSteps(input.status.workflowGraph, "host-step", input.status.runId);
192
+ const graph = input.status.workflowGraph ? {
193
+ ...input.status.workflowGraph,
194
+ nodes: [...input.status.workflowGraph.nodes]
195
+ } : {
196
+ runId: input.status.runId,
197
+ mode: input.status.mode,
198
+ phases: [],
199
+ nodes: []
200
+ };
201
+ const existingIndex = graph.nodes.findIndex((node) => node.id === hostStep.id);
202
+ const existing = existingIndex >= 0 ? graph.nodes[existingIndex] : undefined;
203
+ if (existing && existing.kind !== "host-step")
204
+ throw new Error(`Host step '${hostStep.id}' conflicts with an existing workflow node.`);
205
+ if (existingIndex < 0 && graph.nodes.filter((candidate) => candidate.kind === "host-step").length >= HOST_STEP_MAX_COUNT) {
206
+ throw new Error(`Host step limit of ${HOST_STEP_MAX_COUNT} has been reached.`);
207
+ }
208
+ const node = hostStepWorkflowNode(hostStep);
209
+ if (existingIndex >= 0)
210
+ graph.nodes[existingIndex] = node;
211
+ else
212
+ graph.nodes.push(node);
213
+ const nextStatus = {
214
+ ...input.status,
215
+ workflowGraph: graph,
216
+ lastUpdate: Math.max(input.status.lastUpdate ?? 0, hostStep.updatedAt)
217
+ };
218
+ input.persist(nextStatus);
219
+ return nextStatus;
220
+ }
221
+ export function hostStepVerdictLabel(state, verdict) {
222
+ return state === "done" ? verdict ?? "inconclusive" : state;
223
+ }
224
+ export function hostStepReportName(reportPath) {
225
+ if (!reportPath)
226
+ return;
227
+ return reportPath.split(/[\\/]/).filter(Boolean).at(-1);
228
+ }
@@ -0,0 +1,104 @@
1
+ export const WORKFLOW_LANE_KEY_MAX_BYTES = 128;
2
+ export const WORKFLOW_LANE_SOURCE_REF_MAX_BYTES = 128;
3
+ export const WORKFLOW_LANE_CLAIM_MAX_BYTES = 160;
4
+ export const WORKFLOW_LANE_CLAIMS_MAX = 20;
5
+ export const WORKFLOW_LANE_OUTPUT_PATH_MAX_BYTES = 256;
6
+ export const WORKFLOW_LANE_OUTPUT_PATHS_MAX = 10;
7
+ export const WORKTREE_STATUS_PATH_MAX_BYTES = 4096;
8
+ export const WORKTREE_STATUS_BRANCH_MAX_BYTES = 256;
9
+ const WORKFLOW_LANE_KEY_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
10
+ const WORKFLOW_LANE_MODES = new Set(["mutation", "review", "scout", "gate"]);
11
+ function byteLength(value) {
12
+ return Buffer.byteLength(value, "utf8");
13
+ }
14
+ function boundedNonEmptyString(value, label, maxBytes) {
15
+ if (typeof value !== "string" || !value.trim())
16
+ throw new Error(`${label} must be a non-empty string.`);
17
+ const normalized = value.trim();
18
+ if (byteLength(normalized) > maxBytes)
19
+ throw new Error(`${label} exceeds ${maxBytes} UTF-8 bytes.`);
20
+ if ([...normalized].some((character) => character === `
21
+ ` || character === "\r" || character === "\x00"))
22
+ throw new Error(`${label} must not contain newlines or NUL bytes.`);
23
+ return normalized;
24
+ }
25
+ function assertPlainObject(value, label) {
26
+ if (!value || typeof value !== "object" || Array.isArray(value) || Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null) {
27
+ throw new Error(`${label} must be a plain JSON object.`);
28
+ }
29
+ }
30
+ function assertKnownFields(value, fields, label) {
31
+ const unknown = Object.keys(value).filter((field) => !fields.includes(field));
32
+ if (unknown.length > 0)
33
+ throw new Error(`${label} has unsupported fields: ${unknown.join(", ")}.`);
34
+ }
35
+ function boundedStringArray(value, label, maxItems, maxBytes) {
36
+ if (!Array.isArray(value))
37
+ throw new Error(`${label} must be an array.`);
38
+ if (value.length > maxItems)
39
+ throw new Error(`${label} supports at most ${maxItems} entries.`);
40
+ return value.map((entry, index) => {
41
+ if (!Object.hasOwn(value, index))
42
+ throw new Error(`${label} must not contain sparse entries.`);
43
+ return boundedNonEmptyString(entry, `${label}[${index}]`, maxBytes);
44
+ });
45
+ }
46
+ export function normalizeWorkflowLaneMetadata(value, label = "lane") {
47
+ if (value === undefined)
48
+ return;
49
+ assertPlainObject(value, label);
50
+ assertKnownFields(value, ["version", "key", "mode", "sourceRef", "claims", "outputPaths"], label);
51
+ if (value.version !== 1)
52
+ throw new Error(`${label}.version must be 1.`);
53
+ const key = boundedNonEmptyString(value.key, `${label}.key`, WORKFLOW_LANE_KEY_MAX_BYTES);
54
+ if (!WORKFLOW_LANE_KEY_PATTERN.test(key))
55
+ throw new Error(`${label}.key is invalid.`);
56
+ const mode = value.mode;
57
+ if (mode !== undefined && (typeof mode !== "string" || !WORKFLOW_LANE_MODES.has(mode)))
58
+ throw new Error(`${label}.mode is invalid.`);
59
+ const sourceRef = value.sourceRef === undefined ? undefined : boundedNonEmptyString(value.sourceRef, `${label}.sourceRef`, WORKFLOW_LANE_SOURCE_REF_MAX_BYTES);
60
+ const claims = value.claims === undefined ? undefined : boundedStringArray(value.claims, `${label}.claims`, WORKFLOW_LANE_CLAIMS_MAX, WORKFLOW_LANE_CLAIM_MAX_BYTES);
61
+ const outputPaths = value.outputPaths === undefined ? undefined : boundedStringArray(value.outputPaths, `${label}.outputPaths`, WORKFLOW_LANE_OUTPUT_PATHS_MAX, WORKFLOW_LANE_OUTPUT_PATH_MAX_BYTES);
62
+ return {
63
+ version: 1,
64
+ key,
65
+ ...mode !== undefined ? { mode } : {},
66
+ ...sourceRef !== undefined ? { sourceRef } : {},
67
+ ...claims !== undefined ? { claims } : {},
68
+ ...outputPaths !== undefined ? { outputPaths } : {}
69
+ };
70
+ }
71
+ export function assertWorkflowLaneKey(lane, workflowKey, label = "lane") {
72
+ if (!lane || workflowKey === undefined)
73
+ return;
74
+ if (lane.key !== workflowKey)
75
+ throw new Error(`${label}.key '${lane.key}' does not match workflow key '${workflowKey}'.`);
76
+ }
77
+ export function normalizeWorktreeStatusReference(value, label = "worktree status reference") {
78
+ if (value === undefined)
79
+ return;
80
+ assertPlainObject(value, label);
81
+ assertKnownFields(value, ["worktreePath", "branch"], label);
82
+ return {
83
+ worktreePath: boundedNonEmptyString(value.worktreePath, `${label}.worktreePath`, WORKTREE_STATUS_PATH_MAX_BYTES),
84
+ branch: boundedNonEmptyString(value.branch, `${label}.branch`, WORKTREE_STATUS_BRANCH_MAX_BYTES)
85
+ };
86
+ }
87
+ export function validateAsyncStatusLaneMetadata(status, label = "async status") {
88
+ const lane = normalizeWorkflowLaneMetadata(status.lane, `${label}.lane`);
89
+ assertWorkflowLaneKey(lane, status.workflowKey, `${label}.lane`);
90
+ if (lane)
91
+ status.lane = lane;
92
+ for (const [index, step] of (status.steps ?? []).entries()) {
93
+ const stepLane = normalizeWorkflowLaneMetadata(step.lane, `${label}.steps[${index}].lane`);
94
+ assertWorkflowLaneKey(stepLane, step.workflowKey, `${label}.steps[${index}].lane`);
95
+ if (stepLane)
96
+ step.lane = stepLane;
97
+ const hasPath = step.worktreePath !== undefined;
98
+ const hasBranch = step.branch !== undefined;
99
+ if (hasPath !== hasBranch)
100
+ throw new Error(`${label}.steps[${index}] must include both worktreePath and branch.`);
101
+ if (hasPath)
102
+ normalizeWorktreeStatusReference({ worktreePath: step.worktreePath, branch: step.branch }, `${label}.steps[${index}]`);
103
+ }
104
+ }