@humwick/server 0.130.2 → 0.130.4

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 (587) hide show
  1. package/bundled/prompts/manifest.json +1 -0
  2. package/bundled/prompts/task.hydration.md +1 -1
  3. package/bundled/roles/builder.md +1 -1
  4. package/docs/guides/sub-coordinators.md +12 -5
  5. package/docs/guides/tools-coordination.md +1 -0
  6. package/docs/guides/workflows.md +16 -0
  7. package/docs/openapi.json +175 -4
  8. package/package.json +8 -7
  9. package/public/assets/{MessageBubble-BWLqMGKG.js → MessageBubble-BOkmEvgZ.js} +2 -2
  10. package/public/assets/{PlanGraphPanel-3TlIQpjk.js → PlanGraphPanel-BkaDJp0r.js} +2 -2
  11. package/public/assets/{TraceView-Wimm0Czv.js → TraceView-B99Uu2yT.js} +2 -2
  12. package/public/assets/{VaultDecisionCard-Bau_vNax.js → VaultDecisionCard-DUIfTTFF.js} +2 -2
  13. package/public/assets/{activity-CrSurn-r.js → activity-ZIX0N1El.js} +2 -2
  14. package/public/assets/{agent-lineage-CknAJKcb.js → agent-lineage-kq9TPYFw.js} +2 -2
  15. package/public/assets/agent-liveness-DSQY1Aa_.js +2 -0
  16. package/public/assets/{agent-name-DKbiIhBo.js → agent-name-C_7F4DFR.js} +2 -2
  17. package/public/assets/{agent-profiles-DwF6EQ4R.js → agent-profiles-BNOcrGcY.js} +2 -2
  18. package/public/assets/{agent-project-badge-Diz4Sjjb.js → agent-project-badge-B6yAh70a.js} +2 -2
  19. package/public/assets/{agent-tools-CRhpRQza.js → agent-tools-CcrhmOzF.js} +2 -2
  20. package/public/assets/{agent-type-icon-LrUEU3lB.js → agent-type-icon-CXSLrKiq.js} +2 -2
  21. package/public/assets/{agents-B-v66C-4.js → agents-DIZG97_X.js} +2 -2
  22. package/public/assets/{analytics-BqCb-LKi.js → analytics-rasmoPjR.js} +2 -2
  23. package/public/assets/{artifact-viewer-kf4pGuAR.js → artifact-viewer-tBpTrEPb.js} +2 -2
  24. package/public/assets/{artifacts-DJevXcvg.js → artifacts-BNkrynP1.js} +2 -2
  25. package/public/assets/{artifacts-D32zyw4Y.js → artifacts-Bjq-ZyBA.js} +2 -2
  26. package/public/assets/{automation-BpRajx_5.js → automation-Cx422L3L.js} +2 -2
  27. package/public/assets/{backlog-D_UjTdzX.js → backlog-RuFQ8PUu.js} +2 -2
  28. package/public/assets/{busy-health-N3VEh4ao.js → busy-health-BvNQ9g5x.js} +2 -2
  29. package/public/assets/{channels-9jopungq.js → channels-DNbKAS0I.js} +2 -2
  30. package/public/assets/{chat-CSXrTB3C.js → chat-Bp3Fx73h.js} +3 -3
  31. package/public/assets/{code-preview-p1NnmqK0.js → code-preview-KIRZA6cz.js} +2 -2
  32. package/public/assets/{connectors-CZdqlDNL.js → connectors-k7vO4vvc.js} +2 -2
  33. package/public/assets/{cosmos-BgdsE4dG.js → cosmos-BuVoq1aw.js} +2 -2
  34. package/public/assets/{crate-agent-30aXTKva.js → crate-agent-irqlt4NH.js} +2 -2
  35. package/public/assets/{dependency-graph-B_jvw2GE.js → dependency-graph-D_rurNBz.js} +2 -2
  36. package/public/assets/{deploys-DoN6bAb5.js → deploys-yzZqIpxE.js} +2 -2
  37. package/public/assets/{display-BrKWFGJz.js → display-BemJG1f_.js} +2 -2
  38. package/public/assets/{files-D6XEu8Ng.js → files-DXWcrxOz.js} +2 -2
  39. package/public/assets/{fleet-C7jQWbw2.js → fleet-TEuOCZXE.js} +2 -2
  40. package/public/assets/{fleet-metrics-BiDs3MLx.js → fleet-metrics-BGoDAFI-.js} +2 -2
  41. package/public/assets/{formatted-body-impl-DyG0WsQS.js → formatted-body-impl-BB9eq9nN.js} +2 -2
  42. package/public/assets/{formatted-body-CQBwc1fb.js → formatted-body-qy2nTD_1.js} +3 -3
  43. package/public/assets/{host-providers-dialog-DKaW31dN.js → host-providers-dialog-BGPHxuwT.js} +2 -2
  44. package/public/assets/{index-CRHep3M9.js → index-BC5XZc0z.js} +5 -5
  45. package/public/assets/{index-MYAIwYTX.css → index-D8Rp9pz2.css} +1 -1
  46. package/public/assets/{insights-CzrtkpOE.js → insights-C-KQG0h-.js} +2 -2
  47. package/public/assets/{integrations-aVsUmigt.js → integrations-ClPRPyKU.js} +2 -2
  48. package/public/assets/{issue-ref-chip-BnSlM_95.js → issue-ref-chip-DbH8J9Ak.js} +2 -2
  49. package/public/assets/{log-viewer-qe7Ee47Z.js → log-viewer-PH4zVn24.js} +2 -2
  50. package/public/assets/{maintenance-NfjqFZAk.js → maintenance-5lV43uTL.js} +2 -2
  51. package/public/assets/{managed-agents-BiH0HgaE.js → managed-agents-BdgU35aX.js} +2 -2
  52. package/public/assets/{markdown-preview-BixaT3Xr.js → markdown-preview-DNT4OUwQ.js} +3 -3
  53. package/public/assets/{markdown-preview-impl-DNGlWIS3.js → markdown-preview-impl-CnCcPy0A.js} +2 -2
  54. package/public/assets/{memory-DNsCl6UX.js → memory--GgWH1gH.js} +2 -2
  55. package/public/assets/{messages-DopU1L1X.js → messages-CzFl_97O.js} +2 -2
  56. package/public/assets/{orchestrators-Cq7ckLQT.js → orchestrators-CVvJVkzv.js} +2 -2
  57. package/public/assets/overview-DsrZvE86.js +2 -0
  58. package/public/assets/{pairs-DE_m6rrE.js → pairs-D2d0d2py.js} +2 -2
  59. package/public/assets/{profile-icon-B3rfh77F.js → profile-icon-_SMuSbYF.js} +2 -2
  60. package/public/assets/{project-profile-instructions-DQWZE0kA.js → project-profile-instructions-Apn15y43.js} +2 -2
  61. package/public/assets/{projects-Br2Bd2h1.js → projects-D3B6kto5.js} +2 -2
  62. package/public/assets/{prompt-settings-BKvFsvGD.js → prompt-settings-DnPB6Ez6.js} +2 -2
  63. package/public/assets/provider-display-bWwiuedV.js +2 -0
  64. package/public/assets/{registry-Cw38_6gW.js → registry-D790cIhN.js} +2 -2
  65. package/public/assets/run-card-BCro-Odj.js +2 -0
  66. package/public/assets/{secrets-DxI-vzSq.js → secrets-CVXvfvbU.js} +2 -2
  67. package/public/assets/{service-access-settings-D9s_VJbd.js → service-access-settings-D3Xxqb9w.js} +2 -2
  68. package/public/assets/{settings-CKZTVGIx.js → settings-CoUC7Jci.js} +2 -2
  69. package/public/assets/shipping-D_Lp9n_J.js +2 -0
  70. package/public/assets/{stats-DyUBKyWI.js → stats-CXiNjB9S.js} +2 -2
  71. package/public/assets/{status-dot-DYqlEUhJ.js → status-dot-C703YIy1.js} +2 -2
  72. package/public/assets/store-BgDALGIX.js +9 -0
  73. package/public/assets/{tasks-Bh7Chr4-.js → tasks-DO0WpJCx.js} +2 -2
  74. package/public/assets/{teams-bowI7qVY.js → teams-FGkdi_tL.js} +2 -2
  75. package/public/assets/{terminal-viewer-BgVLHdsU.js → terminal-viewer-Dh8dSC7e.js} +3 -3
  76. package/public/assets/{terminal-viewer-impl-Dz22UPy6.js → terminal-viewer-impl-B5Zriip3.js} +2 -2
  77. package/public/assets/{use-agent-terminal-DSQQ-iY7.js → use-agent-terminal-CRUVDvLC.js} +2 -2
  78. package/public/assets/{use-decisions-samMQhQf.js → use-decisions-CoaqRh4l.js} +2 -2
  79. package/public/assets/{use-project-list-59LheVP7.js → use-project-list-X-BgT53X.js} +2 -2
  80. package/public/assets/{use-selectors-BZa25xEB.js → use-selectors-0X_UZkk-.js} +2 -2
  81. package/public/assets/{user-avatar-DHxlvUvV.js → user-avatar-CkLhVBGu.js} +2 -2
  82. package/public/assets/{versioned-prompt-editor-BmED5DnJ.js → versioned-prompt-editor-BkTi7eim.js} +2 -2
  83. package/public/assets/{work-queue-CSvpuNLz.js → work-queue-DDOLEyKo.js} +2 -2
  84. package/public/assets/{workflow-marker-DAZcj5qt.js → workflow-marker-CzOsB8BM.js} +2 -2
  85. package/public/assets/{workflows-DdmxRSyg.js → workflows-DJilvmRM.js} +2 -2
  86. package/public/assets/{workspaces-BxeKfVuF.js → workspaces-CHcfbLR3.js} +2 -2
  87. package/public/cosmos.html +4 -4
  88. package/public/index.html +29 -29
  89. package/runner/plugins/claude/.claude-plugin/plugin.json +1 -1
  90. package/runner/src/adapters/claude-api.ts +1 -1
  91. package/runner/src/adapters/claude-headless.ts +1 -1
  92. package/runner/src/adapters/claude-launch-exit.ts +26 -0
  93. package/runner/src/adapters/claude-session-probe.ts +1 -1
  94. package/runner/src/adapters/claude-tmux.ts +38 -10
  95. package/runner/src/adapters/claude.ts +28 -75
  96. package/runner/src/adapters/provider-quota-harvest.ts +20 -71
  97. package/runner/src/agent-runner/lifecycle-milestones.ts +2 -2
  98. package/runner/src/agent-runner/liveness-exchange.ts +104 -0
  99. package/runner/src/claude-system-prompt.ts +49 -0
  100. package/runner/src/launch-assembly.ts +14 -10
  101. package/runner/src/profile-home.ts +1 -1
  102. package/runner/src/project-instructions.ts +2 -2
  103. package/runner/src/provider-command.ts +18 -0
  104. package/runner/src/provider-setup.ts +3 -2
  105. package/runner/src/runner-core.ts +14 -15
  106. package/runner/src/runner-helpers.ts +0 -8
  107. package/runner/src/session-scratch.ts +6 -2
  108. package/runner/test-support/provisioning-context.ts +4 -2
  109. package/runner/test-support/runner-harness.ts +2 -1
  110. package/scripts/steward-policy-cleanup.ts +2 -2
  111. package/src/agent-authz.ts +2 -2
  112. package/src/agent-drive-arbiter.ts +3 -8
  113. package/src/agent-exit-cleanup-async.ts +2 -2
  114. package/src/agent-exit-cleanup.ts +23 -19
  115. package/src/agent-lifecycle-events.ts +112 -85
  116. package/src/agent-ref.ts +3 -2
  117. package/src/agent-status-events.ts +43 -14
  118. package/src/auth/login-link.ts +35 -0
  119. package/src/auth/password.ts +4 -3
  120. package/src/auth/session-refresh.ts +1 -1
  121. package/src/auth/session.test-fixture.ts +2 -2
  122. package/src/auth/session.ts +7 -7
  123. package/src/automations/crud.ts +8 -81
  124. package/src/automations/dispatch.ts +33 -58
  125. package/src/automations/index.ts +1 -1
  126. package/src/automations/persistence.ts +65 -0
  127. package/src/automations/precheck.ts +18 -16
  128. package/src/automations/read-async.ts +11 -0
  129. package/src/automations/reconcile.ts +30 -39
  130. package/src/automations/storage.ts +33 -10
  131. package/src/automations/write-async.ts +33 -0
  132. package/src/branch-land-notice.ts +3 -3
  133. package/src/branch-landed.ts +25 -23
  134. package/src/bus.ts +21 -19
  135. package/src/cd-deploy-notify.ts +2 -2
  136. package/src/ci/notifications.ts +4 -4
  137. package/src/ci/root-missing.ts +3 -3
  138. package/src/ci/transition-notify.ts +5 -5
  139. package/src/claude-app/oauth-http.ts +3 -3
  140. package/src/cli/index.ts +7 -1
  141. package/src/cli/init.ts +53 -0
  142. package/src/cli/orchestrator.ts +1 -7
  143. package/src/cli/setup.ts +6 -4
  144. package/src/cli/workflow.ts +12 -3
  145. package/src/commands-db.ts +4 -4
  146. package/src/commands-write.ts +8 -0
  147. package/src/compaction-watch.ts +10 -8
  148. package/src/config-store-write.ts +2 -2
  149. package/src/config.ts +4 -11
  150. package/src/connectors.ts +10 -8
  151. package/src/context-advisory.ts +22 -21
  152. package/src/db/agent-action-write.ts +36 -0
  153. package/src/db/agent-control-async.ts +9 -0
  154. package/src/db/agent-control-write.ts +49 -0
  155. package/src/db/agent-effort-observe.ts +22 -0
  156. package/src/db/agent-meta.ts +14 -3
  157. package/src/db/agents-async.ts +8 -5
  158. package/src/db/agents.ts +15 -12
  159. package/src/db/auth-identities.ts +2 -2
  160. package/src/db/automation-read-ops.ts +11 -0
  161. package/src/db/automation-write-ops.ts +46 -0
  162. package/src/db/blackboard.ts +2 -2
  163. package/src/db/browser-session-write.ts +32 -0
  164. package/src/db/bus-outbox-write-ops.ts +3 -1
  165. package/src/db/checkout-refresh-schema.ts +47 -0
  166. package/src/db/checkout-refresh.ts +5 -5
  167. package/src/db/command-agent-profile-backfill.ts +39 -0
  168. package/src/db/command-agent-profiles.ts +5 -1
  169. package/src/db/command-retention.ts +126 -0
  170. package/src/db/commands-planner-stats.test-fixture.ts +8 -0
  171. package/src/db/commands-read-ops.ts +16 -0
  172. package/src/db/connection.ts +1 -182
  173. package/src/db/context-carry-write.ts +74 -0
  174. package/src/db/continuations.ts +4 -4
  175. package/src/db/db-error-registry.ts +12 -0
  176. package/src/db/gateway.ts +26 -133
  177. package/src/db/history-retention-ops.ts +13 -0
  178. package/src/db/inbox-async.ts +2 -2
  179. package/src/db/inbox-read-ops.ts +1 -1
  180. package/src/db/inbox.ts +9 -3
  181. package/src/db/issue-associations.ts +2 -2
  182. package/src/db/issue-refs-async.ts +4 -1
  183. package/src/db/issue-refs-read-ops.ts +6 -0
  184. package/src/db/issue-refs.ts +8 -8
  185. package/src/db/kernel-event-log-schema.ts +2 -0
  186. package/src/db/land-boundaries.ts +51 -5
  187. package/src/db/land-gate-attestations.ts +2 -2
  188. package/src/db/landing-receipts.ts +2 -2
  189. package/src/db/lifecycle-context-write.ts +5 -0
  190. package/src/db/lifecycle-tail-async.ts +76 -0
  191. package/src/db/lifecycle-tail-write-ops.ts +62 -0
  192. package/src/db/lifecycle-write-window.ts +2 -1
  193. package/src/db/lineage-pair-dedupe-sql.ts +3 -3
  194. package/src/db/main-connection-mode.ts +11 -0
  195. package/src/db/main-connection-writes.ts +20 -2
  196. package/src/db/maintenance-job-runs-async.ts +2 -2
  197. package/src/db/maintenance-job-write-ops.ts +1 -1
  198. package/src/db/maintenance-pauses.ts +2 -2
  199. package/src/db/message-send.ts +4 -0
  200. package/src/db/messages.ts +31 -25
  201. package/src/db/migrations.ts +18 -1
  202. package/src/db/notification-write-ops.ts +19 -0
  203. package/src/db/operator-answer-write.ts +19 -0
  204. package/src/db/orchestrators.ts +10 -4
  205. package/src/db/pairs-async.ts +22 -1
  206. package/src/db/pairs-write-ops.ts +8 -3
  207. package/src/db/pairs.ts +41 -48
  208. package/src/db/plan-dispatch-schema.ts +5 -1
  209. package/src/db/plan-ready-notices.ts +43 -0
  210. package/src/db/plan-tools-include-migration.ts +36 -0
  211. package/src/db/plans-write-ops.ts +2 -0
  212. package/src/db/presence-buffer.ts +5 -0
  213. package/src/db/presence-loop.ts +18 -23
  214. package/src/db/presence-write.ts +0 -6
  215. package/src/db/project-entries-async.ts +14 -0
  216. package/src/db/project-entries-read-ops.ts +5 -1
  217. package/src/db/project-entries-write-ops.ts +8 -0
  218. package/src/db/project-entries.ts +15 -12
  219. package/src/db/project-journal.ts +2 -2
  220. package/src/db/project-trust-approvals.ts +2 -2
  221. package/src/db/prompt-templates.ts +4 -4
  222. package/src/db/read-op-marshaling-fixtures.ts +18 -0
  223. package/src/db/read-ops.ts +4 -0
  224. package/src/db/retention.ts +13 -2
  225. package/src/db/review-publish-write.ts +8 -0
  226. package/src/db/runtime-admin-async.ts +19 -0
  227. package/src/db/runtime-admin-write-ops.ts +36 -0
  228. package/src/db/runtime-tail-async.ts +288 -0
  229. package/src/db/runtime-tail-write-ops.ts +97 -0
  230. package/src/db/schema.ts +4 -1
  231. package/src/db/spawn-budget.ts +5 -0
  232. package/src/db/spawn-delegation.ts +3 -12
  233. package/src/db/sql-audit-indexes.ts +3 -0
  234. package/src/db/stats-async.ts +5 -3
  235. package/src/db/task-entity.ts +5 -4
  236. package/src/db/team-claim-policy.ts +60 -0
  237. package/src/db/teams-async.ts +4 -0
  238. package/src/db/teams-write-ops.ts +4 -0
  239. package/src/db/terminal-events.ts +15 -0
  240. package/src/db/timer-cutover-bodies.ts +292 -0
  241. package/src/db/timer-cutover-core.ts +37 -278
  242. package/src/db/timer-cutover-state.ts +40 -0
  243. package/src/db/timer-cutover-write-ops.ts +1 -40
  244. package/src/db/token-db-write-ops.ts +2 -0
  245. package/src/db/token-standing-async.ts +12 -0
  246. package/src/db/token-standing.ts +6 -9
  247. package/src/db/token-usage-retention.ts +33 -0
  248. package/src/db/token-usage-write-ops.ts +1 -1
  249. package/src/db/token-usage.ts +4 -1
  250. package/src/db/user-invites-async.ts +13 -0
  251. package/src/db/user-invites.ts +3 -3
  252. package/src/db/users.ts +14 -6
  253. package/src/db/workflow-execution.ts +3 -3
  254. package/src/db/workspaces.ts +8 -5
  255. package/src/db/write-ops.ts +31 -12
  256. package/src/db/writer-fault-seam.ts +2 -9
  257. package/src/db/writer-fault-test-ops.ts +18 -0
  258. package/src/db/writer-gateway.ts +42 -46
  259. package/src/dev.ts +12 -16
  260. package/src/event-loop-lag.ts +4 -2
  261. package/src/event-loop-yield.ts +2 -2
  262. package/src/events.ts +10 -8
  263. package/src/host-provider-store.ts +5 -4
  264. package/src/hub-metrics-report.ts +5 -1
  265. package/src/hub-metrics.ts +38 -18
  266. package/src/init.ts +299 -0
  267. package/src/injection-sweep-pacing.ts +6 -8
  268. package/src/invite-link.ts +16 -9
  269. package/src/land-gate-notifications.ts +11 -10
  270. package/src/land-gate-queue-progress.ts +3 -3
  271. package/src/land-settle-dispatch.ts +52 -92
  272. package/src/lifecycle-context-pressure.ts +11 -10
  273. package/src/lifecycle-manager.ts +104 -98
  274. package/src/maintenance/breaker.ts +18 -16
  275. package/src/maintenance/checkout-refresh.ts +18 -1
  276. package/src/maintenance/critical-job-watchdog.ts +3 -3
  277. package/src/maintenance/events.ts +3 -3
  278. package/src/maintenance/history-retention-job.ts +30 -0
  279. package/src/maintenance/jobs.ts +29 -67
  280. package/src/maintenance/long-running-work.ts +3 -1
  281. package/src/maintenance/offline-agent-prune-job.ts +28 -0
  282. package/src/maintenance/orphaned-sessions.ts +5 -4
  283. package/src/maintenance/pauses.ts +4 -10
  284. package/src/maintenance/plan-ready-notice-job.ts +71 -0
  285. package/src/maintenance/scheduler.ts +18 -13
  286. package/src/maintenance/stalled-delivery-pager.ts +4 -4
  287. package/src/maintenance/teams.ts +19 -30
  288. package/src/maintenance/workspaces.ts +17 -27
  289. package/src/managed-policy-escalation.ts +7 -7
  290. package/src/managed-policy.ts +3 -3
  291. package/src/managed-provider-gate.ts +5 -4
  292. package/src/managed-spawn-reservation.ts +5 -4
  293. package/src/mcp/index.ts +8 -8
  294. package/src/mcp/output-descriptions.ts +1 -1
  295. package/src/mcp/presentation.ts +2 -1
  296. package/src/mcp/runtime-output.ts +6 -1
  297. package/src/mcp/self-care-visibility.ts +2 -2
  298. package/src/mcp/spawn-targets-matrix.ts +10 -1
  299. package/src/mcp/tool-groups.ts +3 -2
  300. package/src/mcp/tool-roles.ts +81 -0
  301. package/src/mcp/tools-workspace.ts +1 -1
  302. package/src/mcp-blackboard-tools.ts +6 -5
  303. package/src/mcp-decision-tools.ts +7 -7
  304. package/src/mcp-egress-tools.ts +10 -9
  305. package/src/mcp-issue-activity-tools.ts +2 -2
  306. package/src/mcp-issue-association-tools.ts +4 -4
  307. package/src/mcp-issue-ref-tools.ts +3 -3
  308. package/src/mcp-merge-tools.ts +5 -5
  309. package/src/mcp-notification-subscription-tools.ts +14 -15
  310. package/src/mcp-output-levels.test-fixture.ts +1 -1
  311. package/src/mcp-profile-trust-tools.ts +1 -1
  312. package/src/mcp-project-journal-tools.ts +4 -12
  313. package/src/mcp-project-tools.ts +17 -18
  314. package/src/mcp-routing-policy-tools.ts +3 -2
  315. package/src/mcp-team-tools.ts +6 -6
  316. package/src/mcp-workflow-tools.ts +5 -3
  317. package/src/memory-area-injection.ts +13 -12
  318. package/src/memory-boundary-capture.ts +6 -6
  319. package/src/memory-injection.ts +45 -43
  320. package/src/merge-pause-maintenance.ts +5 -4
  321. package/src/notification-router.ts +44 -17
  322. package/src/notify.ts +5 -5
  323. package/src/observed-reset-refresh.ts +4 -4
  324. package/src/operator-alarm.ts +2 -5
  325. package/src/project-ingest.ts +6 -6
  326. package/src/project-instructions.ts +8 -1
  327. package/src/project-trust.ts +12 -11
  328. package/src/prompt-resolver.ts +3 -3
  329. package/src/provider-quota-async.ts +5 -7
  330. package/src/provider-timeline-event.ts +4 -7
  331. package/src/quota-band-delivery.ts +2 -2
  332. package/src/recipe-runner.ts +4 -3
  333. package/src/retention-config-store.ts +4 -3
  334. package/src/reviewer-pipeline.ts +11 -10
  335. package/src/routes/_route.ts +4 -4
  336. package/src/routes/_shared.ts +13 -12
  337. package/src/routes/agent-effort.ts +5 -5
  338. package/src/routes/agent-heartbeat-quota.ts +23 -0
  339. package/src/routes/agent-keep.ts +1 -1
  340. package/src/routes/agent-reporting.ts +1 -1
  341. package/src/routes/agent-self-effort.ts +5 -5
  342. package/src/routes/agent-sessions.ts +24 -24
  343. package/src/routes/agents-spawn.ts +1 -1
  344. package/src/routes/agents.ts +17 -119
  345. package/src/routes/auth.ts +44 -17
  346. package/src/routes/authority-audit.ts +2 -2
  347. package/src/routes/automations.ts +6 -5
  348. package/src/routes/channel-binding-delete.ts +1 -1
  349. package/src/routes/channel-output.ts +3 -3
  350. package/src/routes/claude-api-budget.ts +5 -5
  351. package/src/routes/commands-listing.ts +1 -1
  352. package/src/routes/commands.ts +29 -27
  353. package/src/routes/context-resets.ts +2 -2
  354. package/src/routes/coordinator-teams.ts +1 -1
  355. package/src/routes/decisions.ts +2 -2
  356. package/src/routes/host-providers.ts +18 -0
  357. package/src/routes/host-secret-files.ts +1 -1
  358. package/src/routes/hosts.ts +11 -11
  359. package/src/routes/inbox.ts +3 -2
  360. package/src/routes/index.ts +7 -3
  361. package/src/routes/integrations.ts +9 -7
  362. package/src/routes/issue-activity.ts +2 -2
  363. package/src/routes/issue-associations.ts +4 -4
  364. package/src/routes/issue-refs.ts +3 -3
  365. package/src/routes/kernel-timers-operator.ts +1 -1
  366. package/src/routes/memory.ts +1 -1
  367. package/src/routes/merges.ts +11 -11
  368. package/src/routes/messages.ts +23 -17
  369. package/src/routes/orchestrator-bootstrap.ts +3 -1
  370. package/src/routes/orchestrator-host-registration.ts +2 -0
  371. package/src/routes/orchestrator-host-utilization.ts +2 -2
  372. package/src/routes/orchestrator-proxy.ts +2 -2
  373. package/src/routes/orchestrator.ts +27 -18
  374. package/src/routes/pairs.ts +8 -8
  375. package/src/routes/project-profile-instructions.ts +1 -1
  376. package/src/routes/project-trust.ts +1 -1
  377. package/src/routes/projects.ts +16 -16
  378. package/src/routes/prompt-templates.ts +12 -12
  379. package/src/routes/provider-quotas.ts +10 -10
  380. package/src/routes/run-command.ts +1 -1
  381. package/src/routes/secrets-backup.ts +4 -5
  382. package/src/routes/secrets-import.ts +3 -4
  383. package/src/routes/secrets-vault.ts +12 -14
  384. package/src/routes/settings.ts +1 -1
  385. package/src/routes/shipping.ts +9 -1
  386. package/src/routes/spawn-policy.ts +8 -7
  387. package/src/routes/sse.ts +2 -2
  388. package/src/routes/team-templates.ts +7 -7
  389. package/src/routes/teams.ts +10 -10
  390. package/src/routes/tokens.ts +18 -17
  391. package/src/routes/users.ts +24 -24
  392. package/src/routes/viewer-read.ts +3 -3
  393. package/src/routes/web-tool-calls.ts +4 -4
  394. package/src/routes/workflows.ts +2 -2
  395. package/src/routes/workspaces.ts +17 -16
  396. package/src/routing-policy-push.ts +6 -6
  397. package/src/runtime-grant.ts +17 -11
  398. package/src/runtime-install.ts +30 -0
  399. package/src/runtime-tokens.ts +20 -28
  400. package/src/secrets-broker/config.ts +1 -1
  401. package/src/secrets-broker/service.ts +2 -2
  402. package/src/security.ts +3 -1
  403. package/src/self-resume-carry.ts +21 -110
  404. package/src/self-resume-supersede.ts +4 -3
  405. package/src/self-resume.ts +54 -66
  406. package/src/server-background.ts +1 -1
  407. package/src/server.ts +25 -26
  408. package/src/services/agent-action.ts +12 -31
  409. package/src/services/agent-effort.ts +7 -32
  410. package/src/services/agent-hang-watchdog-job.ts +2 -2
  411. package/src/services/agent-heartbeat.ts +166 -0
  412. package/src/services/agent-reporting.ts +1 -1
  413. package/src/services/auth-context.ts +6 -0
  414. package/src/services/browser-inventory.ts +3 -3
  415. package/src/services/browser-session-state.ts +3 -2
  416. package/src/services/browser-session.ts +21 -27
  417. package/src/services/bus-command-update.ts +9 -9
  418. package/src/services/checkout-refresh-state.ts +1 -0
  419. package/src/services/checkout-refresh.test-fixture.ts +3 -2
  420. package/src/services/command-host.ts +7 -2
  421. package/src/services/coordinator-digest.ts +3 -2
  422. package/src/services/dispatch-command.ts +12 -13
  423. package/src/services/effective-launch-driver.ts +14 -9
  424. package/src/services/host-changed-paths.test-fixture.ts +5 -6
  425. package/src/services/host-project-readiness.ts +2 -2
  426. package/src/services/host-providers.ts +1 -1
  427. package/src/services/host-utilization.ts +9 -9
  428. package/src/services/idle-agent-digest.ts +17 -12
  429. package/src/services/idle-epics.ts +4 -4
  430. package/src/services/interrupt-agent.ts +12 -9
  431. package/src/services/issue-activity.ts +3 -3
  432. package/src/services/issue-associations.ts +15 -18
  433. package/src/services/issue-lifecycle-comments.ts +3 -2
  434. package/src/services/issue-lifecycle.ts +14 -13
  435. package/src/services/issue-refs.ts +4 -5
  436. package/src/services/kernel-timers-cutover-chaos-worker.ts +1 -1
  437. package/src/services/kernel-timers-ingress-chaos-worker.ts +6 -1
  438. package/src/services/kernel-timers-ingress.ts +3 -3
  439. package/src/services/kernel-timers-message-authority-chaos-worker.ts +3 -10
  440. package/src/services/kernel-timers-operator-chaos-worker.ts +3 -2
  441. package/src/services/kernel-timers-outbox.ts +1 -1
  442. package/src/services/kernel-timers-stage10-operator.ts +1 -1
  443. package/src/services/kernel-timers.ts +4 -3
  444. package/src/services/land-gate-placement.ts +11 -2
  445. package/src/services/land-issue-relations.ts +5 -4
  446. package/src/services/land-restart-retry.ts +7 -6
  447. package/src/services/landed-ancestry.ts +6 -5
  448. package/src/services/legacy-landing-host.ts +4 -1
  449. package/src/services/legacy-landing-recovery.ts +8 -6
  450. package/src/services/managed-running.ts +17 -17
  451. package/src/services/memory-areas.ts +3 -3
  452. package/src/services/merge-pause.ts +9 -15
  453. package/src/services/operator-decisions.ts +37 -41
  454. package/src/services/orchestrator-registration-recovery.ts +6 -2
  455. package/src/services/orchestrator-upgrade.ts +14 -12
  456. package/src/services/permission-decision.ts +4 -3
  457. package/src/services/plan-ci-notice.ts +1 -1
  458. package/src/services/plan-conformance.ts +4 -4
  459. package/src/services/plan-dispatch-policy.ts +10 -3
  460. package/src/services/plan-dispatch.ts +35 -14
  461. package/src/services/plan-graph.ts +39 -261
  462. package/src/services/plan-import.ts +1 -1
  463. package/src/services/plan-live-bindings.ts +8 -37
  464. package/src/services/plan-task-create.ts +1 -1
  465. package/src/services/plan-terminal-refs.ts +2 -12
  466. package/src/services/plan-wake-digest.ts +8 -3
  467. package/src/services/profile-trust.ts +2 -2
  468. package/src/services/project.ts +11 -10
  469. package/src/services/prompt-templates.ts +6 -5
  470. package/src/services/provider-api-key.ts +73 -0
  471. package/src/services/provider-hold-nudge.ts +9 -9
  472. package/src/services/provider-hold-record.ts +13 -12
  473. package/src/services/provider-state-audit.ts +19 -18
  474. package/src/services/provisioning-harness/driver.ts +1 -1
  475. package/src/services/provisioning-harness/fixture-matrix.ts +3 -3
  476. package/src/services/provisioning-harness/lifecycle.ts +24 -22
  477. package/src/services/provisioning-harness/prepare.ts +16 -17
  478. package/src/services/rate-limit-resume.ts +10 -9
  479. package/src/services/red-main-auto-revert.ts +7 -5
  480. package/src/services/red-main-controller-deps.ts +20 -3
  481. package/src/services/red-main-incident.ts +3 -1
  482. package/src/services/red-main-quarantine.ts +2 -3
  483. package/src/services/register-agent.ts +22 -22
  484. package/src/services/restart-project-role.ts +1 -1
  485. package/src/services/restart-spawn.ts +17 -16
  486. package/src/services/review-publish.ts +7 -24
  487. package/src/services/review-ref-reconcile.ts +29 -38
  488. package/src/services/runtime-dependency-alert.ts +27 -0
  489. package/src/services/scheduler-command.ts +11 -9
  490. package/src/services/scheduler.ts +5 -5
  491. package/src/services/self-effort-grant.ts +5 -25
  492. package/src/services/send-message.ts +16 -33
  493. package/src/services/shutdown-agent.ts +17 -14
  494. package/src/services/spawn-agent.ts +12 -41
  495. package/src/services/spawn-chain-inspector.ts +1 -1
  496. package/src/services/spawn-cwd-placement.ts +21 -5
  497. package/src/services/spawn-issue-activity.ts +8 -6
  498. package/src/services/spawn-launch.ts +17 -1
  499. package/src/services/spawn-loss-escalation.ts +1 -1
  500. package/src/services/spawn-registration-watchdog.ts +2 -2
  501. package/src/services/spawn-repo-probe.ts +7 -5
  502. package/src/services/spawn-tree-cleanup.ts +7 -8
  503. package/src/services/steward-reaper.ts +4 -4
  504. package/src/services/task-context-hydration.ts +25 -1
  505. package/src/services/task-delivery.ts +20 -34
  506. package/src/services/task-entity.ts +40 -203
  507. package/src/services/task-issue-link.ts +13 -20
  508. package/src/services/task-issue-weave.ts +12 -11
  509. package/src/services/task-lifecycle.ts +27 -31
  510. package/src/services/task-repo-cwd.ts +37 -0
  511. package/src/services/task-review-hold.ts +4 -17
  512. package/src/services/task-semantic-events.ts +13 -11
  513. package/src/services/task-state-reconcile.ts +3 -4
  514. package/src/services/task-validation.ts +2 -2
  515. package/src/services/team-templates.ts +5 -5
  516. package/src/services/team.ts +31 -98
  517. package/src/services/terminal-provider-exit.ts +6 -5
  518. package/src/services/timer-message-fire-chaos-worker.ts +7 -3
  519. package/src/services/timer-message-fire-outbox.ts +1 -1
  520. package/src/services/transient-agent-reaper.ts +22 -19
  521. package/src/services/turn-reconcile-audit.ts +9 -9
  522. package/src/services/user-onboarding.ts +8 -6
  523. package/src/services/user-profile.ts +6 -9
  524. package/src/services/work-finality.ts +16 -4
  525. package/src/services/workflow-auto-start.ts +2 -1
  526. package/src/services/workflow-caller.ts +61 -4
  527. package/src/services/workflow-child-proof-worker.ts +1 -1
  528. package/src/services/workflow-control-proof-worker.ts +1 -1
  529. package/src/services/workflow-coordinator.ts +8 -2
  530. package/src/services/workflow-decisions.ts +5 -5
  531. package/src/services/workflow-deferred-cleanup.ts +1 -1
  532. package/src/services/workflow-effect-driver.ts +42 -19
  533. package/src/services/workflow-effects.ts +41 -27
  534. package/src/services/workflow-pins.ts +7 -4
  535. package/src/services/workflow-reconcile.ts +1 -0
  536. package/src/services/workflow-resume-pins.ts +3 -3
  537. package/src/services/workflow-routing-defaults.ts +18 -0
  538. package/src/services/workflow-run-view.ts +4 -3
  539. package/src/services/workflow-run.test-fixture.ts +1 -1
  540. package/src/services/workflow-runs.ts +34 -22
  541. package/src/services/workflow-supervisor-state.ts +21 -1
  542. package/src/services/workflow-supervisor.ts +4 -3
  543. package/src/services/workspace-recovery.ts +4 -3
  544. package/src/setup.ts +47 -8
  545. package/src/spawn-admission.ts +10 -2
  546. package/src/spawn-placement.ts +27 -9
  547. package/src/spawn-targets.ts +8 -8
  548. package/src/sse-agent-status.ts +15 -6
  549. package/src/sse.ts +49 -24
  550. package/src/startup-context-injection.ts +61 -57
  551. package/src/steward-handoff.ts +4 -3
  552. package/src/steward-policy-cleanup.ts +9 -8
  553. package/src/steward.ts +2 -2
  554. package/src/task-dispatch-live.ts +5 -2
  555. package/src/task-dispatcher.ts +2 -2
  556. package/src/token-db.ts +2 -2
  557. package/src/token-usage-async.ts +2 -4
  558. package/src/user-awaiting-reply.ts +5 -5
  559. package/src/utils.ts +7 -0
  560. package/src/workspace-actions.ts +24 -29
  561. package/src/workspace-auto-merge.ts +14 -12
  562. package/src/workspace-changed-paths-client.ts +16 -5
  563. package/src/workspace-cleanup-stale.ts +30 -7
  564. package/src/workspace-command-settle.ts +24 -16
  565. package/src/workspace-config.ts +1 -1
  566. package/src/workspace-conflict-resolver.ts +9 -7
  567. package/src/workspace-dead-owner-recovery.ts +5 -4
  568. package/src/workspace-escalation.ts +12 -11
  569. package/src/workspace-human-escalation.ts +3 -3
  570. package/src/workspace-land-gate-watchdog.ts +3 -3
  571. package/src/workspace-land-refusal.ts +20 -0
  572. package/src/workspace-land-stopped.ts +1 -0
  573. package/src/workspace-merge.ts +10 -26
  574. package/src/workspace-nothing-to-land-notice.ts +2 -2
  575. package/src/workspace-orphans.ts +18 -37
  576. package/src/workspace-pr-completion.ts +10 -8
  577. package/src/workspace-rekey.ts +3 -3
  578. package/src/workspace-steward-failure.ts +2 -2
  579. package/src/workspace-steward-wake.ts +2 -2
  580. package/public/assets/agent-liveness-CaNwK0Zf.js +0 -2
  581. package/public/assets/overview-C1DCTzzV.js +0 -2
  582. package/public/assets/provider-display-DsuWMvVL.js +0 -2
  583. package/public/assets/run-card-CpKqRbhg.js +0 -2
  584. package/public/assets/shipping-B9YtnwtG.js +0 -2
  585. package/public/assets/store-DcClXLH0.js +0 -9
  586. package/runner/src/agent-runner/http-liveness.ts +0 -85
  587. package/src/db/on-loop-write-transaction.ts +0 -34
@@ -1,17 +1,20 @@
1
+ import { withdrawOpenOperatorDecisionAsync, updateOpenOperatorDecisionAsync } from "../db/runtime-tail-async";
2
+ import { randomUUID } from "node:crypto";
3
+ import { getGateway } from "../db/gateway";
4
+ import type { answerOperatorDecisionBody } from "../db/operator-answer-write";
5
+ import type { SendMessageInput } from "../types";
1
6
  import { workflowDecisionAnswer } from "./workflow-decision-answer";
2
7
  import { getWorkflowOperatorDecision } from "../db/operator-decisions";
3
8
  import { actingUserSinkOf } from "./auth-context";
4
9
  import { DecisionNotFoundError, authorizeDecision } from "./decision-audience";
5
10
  // #2885 — operator decisions: a coordinator files a structured question for the operator; the
6
11
  // answer goes back as ONE message to `coordinator:<teamId>`, so whoever holds the seat gets it.
7
- import { coordinatesActiveTeam, getAgent, getOperatorDecision, insertOperatorDecision, listOperatorDecisions, answerOpenOperatorDecision,
8
- setOperatorDecisionAnswerMessage, updateOpenOperatorDecision, withdrawOpenOperatorDecision, writeTransaction, ValidationError,
9
- type OperatorDecisionContent } from "../db";
12
+ import { coordinatesActiveTeam, getAgent, getOperatorDecision, insertOperatorDecision, listOperatorDecisions, answerOpenOperatorDecision, setOperatorDecisionAnswerMessage, writeTransaction, ValidationError, type OperatorDecisionContent } from "../db";
10
13
  import { acquireCoordinatorTeam } from "./team";
11
14
  import { emitRelayEvent } from "../events";
12
15
  import { emitNotificationCreated, notificationForDecision } from "../dashboard-notifications";
13
16
  import { cleanStringArray, optionalEnum, optionalString, stringField } from "../validation";
14
- import { sendMessageService } from "./send-message";
17
+ import { validateSendMessageAuthorization, emitSendResults } from "./send-message";
15
18
  import type { AuthContext } from "./auth-context";
16
19
  import { ServiceAuthError } from "./errors";
17
20
  import { OPERATOR_ACTION_DONE, OPERATOR_ACTION_NOT_DONE, OPERATOR_ACTION_OUTCOME_ID, OPERATOR_DECISION_KINDS, OPERATOR_DECISION_QUESTION_KINDS,
@@ -129,12 +132,12 @@ function emitDecisionChanged(decision: OperatorDecision): void {
129
132
  }
130
133
 
131
134
  /** The team the caller coordinates. `mint` lets a teamless coordinator-capable caller found one. */
132
- export function callerDecisionTeam(ctx: AuthContext, mint: boolean): string {
135
+ export async function callerDecisionTeam(ctx: AuthContext, mint: boolean): Promise<string> {
133
136
  const agentId = ctx.callerAgentId;
134
137
  if (!agentId) throw new ServiceAuthError("relay_decision needs a single caller agent identity");
135
138
  const agent = getAgent(agentId);
136
139
  if (agent?.teamId && coordinatesActiveTeam(agentId, agent.teamId)) return agent.teamId;
137
- if (mint && agent && !agent.teamId && ctx.scopes.includes("command:spawn")) return acquireCoordinatorTeam(agentId).id;
140
+ if (mint && agent && !agent.teamId && ctx.scopes.includes("command:spawn")) return (await acquireCoordinatorTeam(agentId)).id;
138
141
  throw new ServiceAuthError("decisions belong to a team's coordinator; you do not coordinate an active team (a replacement claims it with relay_team_claim)");
139
142
  }
140
143
 
@@ -144,12 +147,12 @@ export function teamDecision(teamId: string, id: number): OperatorDecision {
144
147
  return decision;
145
148
  }
146
149
 
147
- function insertFiledDecision(ctx: AuthContext, args: Record<string, unknown>, projectId: string | undefined, now: number, workflow?: OperatorDecision["workflow"], coordinatorNote?: string): OperatorDecision {
150
+ async function prepareFiledDecision(ctx: AuthContext, args: Record<string, unknown>, projectId: string | undefined, now: number, workflow?: OperatorDecision["workflow"], coordinatorNote?: string): Promise<Parameters<typeof insertOperatorDecision>[0]> {
148
151
  const content = cleanDecisionContent(args, undefined, Boolean(workflow));
149
152
  // Each input keeps its own bound; appending a valid note must not reject a valid context (#3586).
150
153
  if (coordinatorNote) content.context = [content.context, `Coordinator note: ${coordinatorNote}`].filter(Boolean).join("\n\n");
151
- const teamId = callerDecisionTeam(ctx, true);
152
- return insertOperatorDecision({ ...content, teamId, ...(projectId ? { projectId } : {}), filedBy: ctx.callerAgentId!, now, ...(workflow ? { workflow } : {}) });
154
+ const teamId = (await callerDecisionTeam(ctx, true));
155
+ return { ...content, teamId, ...(projectId ? { projectId } : {}), filedBy: ctx.callerAgentId!, now, ...(workflow ? { workflow } : {}) };
153
156
  }
154
157
 
155
158
  function publishFiledDecision(decision: OperatorDecision): OperatorDecision {
@@ -158,22 +161,23 @@ function publishFiledDecision(decision: OperatorDecision): OperatorDecision {
158
161
  return decision;
159
162
  }
160
163
 
161
- export function fileDecision(ctx: AuthContext, args: Record<string, unknown>, projectId: string | undefined, now = Date.now()): OperatorDecision {
162
- return publishFiledDecision(insertFiledDecision(ctx, args, projectId, now));
164
+ export async function fileDecision(ctx: AuthContext, args: Record<string, unknown>, projectId: string | undefined, now = Date.now()): Promise<OperatorDecision> {
165
+ const input = await prepareFiledDecision(ctx, args, projectId, now);
166
+ return publishFiledDecision(await getGateway().write("operatorDecisions.insert", input, { idempotencyKey: randomUUID() }) as unknown as OperatorDecision);
163
167
  }
164
168
 
165
- export function updateDecision(ctx: AuthContext, id: number, args: Record<string, unknown>, now = Date.now()): OperatorDecision {
166
- const current = teamDecision(callerDecisionTeam(ctx, false), id);
169
+ export async function updateDecision(ctx: AuthContext, id: number, args: Record<string, unknown>, now = Date.now()): Promise<OperatorDecision> {
170
+ const current = teamDecision((await callerDecisionTeam(ctx, false)), id);
167
171
  if (current.workflow && args.questions !== undefined) throw new ValidationError("workflow choices are frozen for this step activation");
168
- if (!updateOpenOperatorDecision(id, cleanDecisionContent(args, current), now)) throw new DecisionConflictError(`decision #${id} is ${current.status}, not open`);
172
+ if (!(await updateOpenOperatorDecisionAsync(id, cleanDecisionContent(args, current), now))) throw new DecisionConflictError(`decision #${id} is ${current.status}, not open`);
169
173
  const updated = getOperatorDecision(id)!;
170
174
  emitDecisionChanged(updated);
171
175
  return updated;
172
176
  }
173
177
 
174
- export function withdrawDecision(ctx: AuthContext, id: number, now = Date.now()): OperatorDecision {
175
- const current = teamDecision(callerDecisionTeam(ctx, false), id);
176
- if (!withdrawOpenOperatorDecision(id, now)) throw new DecisionConflictError(`decision #${id} is ${current.status}, not open`);
178
+ export async function withdrawDecision(ctx: AuthContext, id: number, now = Date.now()): Promise<OperatorDecision> {
179
+ const current = teamDecision((await callerDecisionTeam(ctx, false)), id);
180
+ if (!(await withdrawOpenOperatorDecisionAsync(id, now))) throw new DecisionConflictError(`decision #${id} is ${current.status}, not open`);
177
181
  const withdrawn = getOperatorDecision(id)!;
178
182
  emitDecisionChanged(withdrawn);
179
183
  return withdrawn;
@@ -201,7 +205,7 @@ function renderAnswerBody(decision: OperatorDecision, answers: OperatorDecisionA
201
205
  * transaction: a crash or a refused send can never leave an answered row without its message.
202
206
  * The message never expires (maxAgeSeconds null), so a seat vacant for days still receives it.
203
207
  */
204
- export function answerDecision(ctx: AuthContext, id: number, input: { answers: unknown; note?: unknown }, now = Date.now()): { decision: OperatorDecision; messageId: number } {
208
+ export async function answerDecision(ctx: AuthContext, id: number, input: { answers: unknown; note?: unknown }, now = Date.now()): Promise<{ decision: OperatorDecision; messageId: number }> {
205
209
  if (ctx.callerAgentId) throw new ServiceAuthError("operator decisions are answered by the operator, not by an agent");
206
210
  const current = getOperatorDecision(id);
207
211
  if (!current) throw new DecisionNotFoundError(`decision #${id} not found`);
@@ -216,21 +220,16 @@ export function answerDecision(ctx: AuthContext, id: number, input: { answers: u
216
220
  throw new ValidationError(`note required: say why you could not do it (${OPERATOR_ACTION_NOT_DONE})`);
217
221
  }
218
222
  const answeredBy = ctx.actor.kind === "component" ? ctx.actor.id : actingUserSinkOf(ctx);
219
- const sent = writeTransaction(() => {
220
- if (!answerOpenOperatorDecision(id, { answers, ...(note ? { note } : {}), answeredBy, now })) {
221
- throw new DecisionConflictError(`decision #${id} was answered or withdrawn meanwhile`);
222
- }
223
- const { message, publish } = sendMessageService({
224
- from: actingUserSinkOf(ctx), to: current.teamId.startsWith("host-privacy:") || current.teamId === "vault-expiry:global" ? current.filedBy : `coordinator:${current.teamId}`, body: renderAnswerBody(current, answers, note),
225
- idempotencyKey: `decision:${id}:answer`, maxAgeSeconds: null, meta: { decisionId: id },
226
- }, ctx, { deferLiveEffects: true });
227
- setOperatorDecisionAnswerMessage(id, message.id);
228
- return { messageId: message.id, publish };
229
- });
230
- // Live effects only after COMMIT: a rolled-back answer must never have been delivered (#2887).
231
- sent.publish?.();
232
- const { messageId } = sent;
233
- const decision = getOperatorDecision(id)!;
223
+ const message: SendMessageInput = {
224
+ from: actingUserSinkOf(ctx), to: current.teamId.startsWith("host-privacy:") || current.teamId === "vault-expiry:global" ? current.filedBy : `coordinator:${current.teamId}`, body: renderAnswerBody(current, answers, note),
225
+ idempotencyKey: `decision:${id}:answer`, maxAgeSeconds: null, meta: { decisionId: id },
226
+ };
227
+ validateSendMessageAuthorization(message, ctx);
228
+ const written = await getGateway().write("operatorDecisions.answer", { id, answer: { answers, ...(note ? { note } : {}), answeredBy, now }, message }, { critical: true }) as unknown as ReturnType<typeof answerOperatorDecisionBody>;
229
+ if (!written) throw new DecisionConflictError(`decision #${id} was answered or withdrawn meanwhile`);
230
+ const { decision, result } = written;
231
+ (await emitSendResults([result]));
232
+ const messageId = result.message.id;
234
233
  emitDecisionChanged(decision);
235
234
  return { decision, messageId };
236
235
  }
@@ -244,17 +243,14 @@ export function cleanWorkflowAnswers(decision: Pick<OperatorDecision, "questions
244
243
  }
245
244
 
246
245
  /** The effect identity and filing share a transaction, so replay cannot create another card. */
247
- export function fileWorkflowDecision(ctx: AuthContext, args: Record<string, unknown>, workflow: NonNullable<OperatorDecision["workflow"]>, projectId?: string, coordinatorNote?: string): OperatorDecision {
246
+ export async function fileWorkflowDecision(ctx: AuthContext, args: Record<string, unknown>, workflow: NonNullable<OperatorDecision["workflow"]>, projectId?: string, coordinatorNote?: string): Promise<OperatorDecision> {
248
247
  const note = optionalString(coordinatorNote, "note", MAX_TEXT);
249
- const filed = writeTransaction(() => {
250
- const existing = getWorkflowOperatorDecision(workflow.effectId);
251
- if (existing) return { decision: existing, created: false };
252
- return { decision: insertFiledDecision(ctx, args, projectId, Date.now(), workflow, note), created: true };
253
- });
248
+ const input = await prepareFiledDecision(ctx, args, projectId, Date.now(), workflow, note);
249
+ const filed = await getGateway().write("operatorDecisions.fileWorkflow", input) as unknown as { decision: OperatorDecision; created: boolean };
254
250
  return filed.created ? publishFiledDecision(filed.decision) : filed.decision;
255
251
  }
256
252
 
257
- export function withdrawWorkflowDecision(effectId: string): void {
253
+ export async function withdrawWorkflowDecision(effectId: string): Promise<void> {
258
254
  const decision = getWorkflowOperatorDecision(effectId);
259
- if (decision && withdrawOpenOperatorDecision(decision.id, Date.now())) emitDecisionChanged(getOperatorDecision(decision.id)!);
255
+ if (decision && (await withdrawOpenOperatorDecisionAsync(decision.id, Date.now()))) emitDecisionChanged(getOperatorDecision(decision.id)!);
260
256
  }
@@ -4,9 +4,13 @@ import { recoverOrchestratorLegacyLandings } from "./orchestrator-landing-recove
4
4
  import { resumeDeferredWorkflowCleanup } from "./workflow-deferred-cleanup";
5
5
  import { workflowTaskDelivered } from "./workflow-runs";
6
6
 
7
+ /** #3826 — the workflow follow-up waits on the supervisor queue, so it runs after the register reply. Deferred
8
+ * cleanup stays durable: a failed resume is retried by the next registration or hub boot. */
7
9
  export async function recoverRegisteredOrchestrator(orchestratorId: string): Promise<void> {
8
10
  await reconcileOrchestratorSchedulerCommands(orchestratorId);
9
- await resumeDeferredWorkflowCleanup(orchestratorId);
10
- await workflowTaskDelivered();
11
+ void fireAndForget(`[orchestrator] workflow cleanup resume for ${orchestratorId}`, async () => {
12
+ await resumeDeferredWorkflowCleanup(orchestratorId);
13
+ await workflowTaskDelivered();
14
+ });
11
15
  void fireAndForget(`[orchestrator] legacy landing recovery for ${orchestratorId}`, () => recoverOrchestratorLegacyLandings(orchestratorId));
12
16
  }
@@ -1,5 +1,7 @@
1
- import { getOrchestrator, setOrchestratorUpgradeState } from "../db";
2
- import { createCommand, getCommand, updateCommand } from "../commands-db";
1
+ import { updateCommandAsync, createCommandAsync } from "../commands-write";
2
+ import { setOrchestratorUpgradeStateAsync } from "../db/orchestrators-async";
3
+ import { getOrchestrator } from "../db";
4
+ import { getCommand } from "../commands-db";
3
5
  import { emitOrchestratorStatus } from "../sse";
4
6
  import { emitCommandEvent } from "../command-events";
5
7
  import type { ActivityEventInput } from "../types";
@@ -10,18 +12,18 @@ export const ORCH_UPGRADE_DEADLINE_MS = 5 * 60_000;
10
12
  // central provider-config rows (#465). The orchestrator reports its files via the
11
13
  // command result; the server seeds the rows (see patchCommand). Capability-gated so
12
14
  // pre-#465 orchestrators that lack the handler never get a command they'd reject.
13
- export function dispatchProviderConfigMigration(
15
+ export async function dispatchProviderConfigMigration(
14
16
  orch: NonNullable<ReturnType<typeof getOrchestrator>>,
15
17
  requestedBy = "system",
16
- ): ReturnType<typeof createCommand> | null {
18
+ ): Promise<ReturnType<typeof createCommandAsync> | null> {
17
19
  if (!orch.capabilities?.migrateProviderConfig) return null;
18
- const command = createCommand({
20
+ const command = (await createCommandAsync({
19
21
  type: "orchestrator.migrate-provider-config",
20
22
  source: "system",
21
23
  target: orch.agentId,
22
24
  ttlMs: 10 * 60_000,
23
25
  params: { orchestratorId: orch.id, requestedBy },
24
- });
26
+ }));
25
27
  emitCommandEvent(command, "command.requested");
26
28
  return command;
27
29
  }
@@ -42,14 +44,14 @@ export async function reconcileOrchestratorUpgrade(
42
44
  // before retrying. The running process's gitSha is the artifact identity and must match too.
43
45
  if (reachedTarget && up.status === "succeeded" && up.error) {
44
46
  const { error: _previousError, ...successState } = up;
45
- setOrchestratorUpgradeState(orch.id, successState);
47
+ (await setOrchestratorUpgradeStateAsync(orch.id, successState));
46
48
  emitOrchestratorStatus(orch.id);
47
49
  return;
48
50
  }
49
51
  if (reachedTarget && reported && (up.status === "pending" || up.status === "failed")) {
50
- if (up.commandId) updateCommand(up.commandId, { status: "succeeded", result: { version: reported, ...(up.fromVersion ? { fromVersion: up.fromVersion } : {}) } });
52
+ if (up.commandId) (await updateCommandAsync(up.commandId, { status: "succeeded", result: { version: reported, ...(up.fromVersion ? { fromVersion: up.fromVersion } : {}) } }));
51
53
  const { error: _previousError, ...successState } = up;
52
- setOrchestratorUpgradeState(orch.id, { ...successState, status: "succeeded", settledAt: Date.now() });
54
+ (await setOrchestratorUpgradeStateAsync(orch.id, { ...successState, status: "succeeded", settledAt: Date.now() }));
53
55
  await auditEvent({
54
56
  clientId: `server-orchestrator-upgraded-${orch.id}-${Date.now()}`,
55
57
  kind: "state",
@@ -62,12 +64,12 @@ export async function reconcileOrchestratorUpgrade(
62
64
  });
63
65
  // Upgrade settled — the orchestrator is now running new-enough code to handle
64
66
  // the provider-config migration, so seed the central rows from its local files (#465).
65
- dispatchProviderConfigMigration(orch);
67
+ (await dispatchProviderConfigMigration(orch));
66
68
  emitOrchestratorStatus(orch.id);
67
69
  } else if (up.status === "pending" && Date.now() - up.requestedAt > ORCH_UPGRADE_DEADLINE_MS) {
68
70
  const errMsg = `upgrade timed out; orchestrator still reports ${reported ?? "unknown"} (wanted ${up.desiredVersion})`;
69
- if (up.commandId) updateCommand(up.commandId, { status: "failed", error: errMsg });
70
- setOrchestratorUpgradeState(orch.id, { ...up, status: "failed", settledAt: Date.now(), error: errMsg });
71
+ if (up.commandId) (await updateCommandAsync(up.commandId, { status: "failed", error: errMsg }));
72
+ (await setOrchestratorUpgradeStateAsync(orch.id, { ...up, status: "failed", settledAt: Date.now(), error: errMsg }));
71
73
  await auditEvent({
72
74
  clientId: `server-orchestrator-upgrade-failed-${orch.id}-${Date.now()}`,
73
75
  kind: "state",
@@ -1,6 +1,6 @@
1
1
  import { isHumanAgentId } from "@humwick/sdk";
2
2
  import { randomUUID } from "node:crypto";
3
- import { createCommandInternal } from "../commands-db";
3
+ import { createAgentControlAsync } from "../db/agent-control-async";
4
4
  import { emitCommandEvent } from "../command-events";
5
5
  import { ValidationError, createActivityEventAsync, getAgent } from "../db";
6
6
  import { agentProviderState } from "../provider-state";
@@ -66,7 +66,7 @@ export async function dispatchAgentPermissionDecision(
66
66
  ): Promise<AgentPermissionDecisionResult> {
67
67
  const agent = getAgent(input.agentId);
68
68
  if (!agent) throw new PermissionDecisionTargetError(`agent ${input.agentId} not found`);
69
- authorizeShutdown(ctx, { agentId: agent.id, requestedVia: input.requestedVia });
69
+ (await authorizeShutdown(ctx, { agentId: agent.id, requestedVia: input.requestedVia }));
70
70
  assertControlEligible(agent);
71
71
 
72
72
  const pendingApprovalId = currentApprovalId(agent);
@@ -122,7 +122,8 @@ export async function dispatchAgentPermissionDecision(
122
122
  // Audit admission precedes the durable command, matching the other service-layer agent controls.
123
123
  const event = await createActivityEventAsync(activityInput);
124
124
  revalidatePendingApproval(agent, approvalId);
125
- const command = createCommandInternal({ input: commandInput, id: commandId });
125
+ const { command: admitted } = await createAgentControlAsync({ agent, commandInput, commandId, approvalId });
126
+ const command = admitted!;
126
127
  emitCommandEvent(command, "command.requested");
127
128
  emitActivityEvent(event);
128
129
  return { approvalId, decision: input.decision, command };
@@ -46,7 +46,7 @@ export async function planCiNotice(run: WorkflowRunRecord, payload: Record<strin
46
46
  const to = plan && planNoticeTarget(plan);
47
47
  if (!to) return undefined;
48
48
  for (const incidentId of block.incidentIds) {
49
- notifySystemMessage(to, {
49
+ await notifySystemMessage(to, {
50
50
  subject: `Plan ${plan.title ?? plan.id}: CI blocked`,
51
51
  body: `Plan ${plan.id}: T${run.taskId}, run ${run.id}, landed SHA ${block.sha}, is blocked by CI incident ${incidentId}. Fix the incident; Relay automatically retries each blocked run's exact landed SHA after clearance. Slots and dependents wait. relay_plan_get shows every blocked Task and run.`,
52
52
  idempotencyKey: `plan:${plan.id}:ci:${incidentId}`, replyExpected: false,
@@ -71,15 +71,15 @@ function cleanVerdictItems(value: unknown): PlanConformanceItem[] {
71
71
  });
72
72
  }
73
73
 
74
- export function emitPlanTaskEvents(events: PlanGraphTaskEvent[] | undefined): void {
74
+ export async function emitPlanTaskEvents(events: PlanGraphTaskEvent[] | undefined): Promise<void> {
75
75
  for (const event of events ?? []) {
76
76
  emitTaskChanged(event.task, event.type);
77
77
  if (event.type === "task.created") {
78
78
  // A Task is created once, so a replayed aggregate write (its gateway result re-read) notifies once (#3660).
79
- emitTaskSemanticNotification({ task: event.task, transition: "created", actor: event.actor, occurrenceKey: "created" });
79
+ await emitTaskSemanticNotification({ task: event.task, transition: "created", actor: event.actor, occurrenceKey: "created" });
80
80
  } else {
81
81
  const transition = taskTransitionForStatus(event.task.status, event.task.blockedBy);
82
- if (transition) emitTaskSemanticNotification({ task: event.task, transition, actor: event.actor, occurrenceKey: undefined });
82
+ if (transition) await emitTaskSemanticNotification({ task: event.task, transition, actor: event.actor, occurrenceKey: undefined });
83
83
  }
84
84
  }
85
85
  }
@@ -100,6 +100,6 @@ export async function recordPlanConformance(input: { planId?: unknown; mainSha?:
100
100
  const mainShaCheck = await checkMainSha(planId, mainSha);
101
101
  const result = await recordPlanConformanceAsync({ planId, mainSha, mainShaCheck, items, actor });
102
102
  emitRelayEvent({ type: "plan.changed", source: actor, subject: result.plan.id, data: { plan: result.plan, actor } });
103
- emitPlanTaskEvents(result.taskEvents);
103
+ await emitPlanTaskEvents(result.taskEvents);
104
104
  return { planId: result.plan.id, version: result.plan.version, verdict: result.verdict, completion: result.plan.completion, gapTaskIds: result.gapTaskIds, ...(result.reusedGapTaskIds.length ? { reusedGapTaskIds: result.reusedGapTaskIds } : {}), conformance: result.plan.conformance };
105
105
  }
@@ -1,5 +1,5 @@
1
1
  // #3660 — plan autopilot readiness and wake policy as pure rules over durable facts (slots, Tasks, runs).
2
- import { planSlotOccupies, type Plan, type PlanDispatchSlot, type PlanDispatchSlotStatus, type TaskDetail, type TaskTerminalStage } from "../types";
2
+ import { planSlotOccupies, type Plan, type PlanNode, type PlanDispatchSlot, type PlanDispatchSlotStatus, type TaskDetail, type TaskTerminalStage } from "../types";
3
3
  import { taskTerminalStage } from "../task-completion";
4
4
 
5
5
  export interface PlanMember { nodeId: string; taskId: number }
@@ -12,13 +12,13 @@ export function planMembers(plan: Pick<Plan, "nodes">): PlanMember[] {
12
12
  });
13
13
  }
14
14
 
15
- /** Why a plan cannot be started, per node; empty when every node is a bound Task the dispatcher can run. */
15
+ /** Only unfinished automatic work needs a Task; settled facts and manual gates are valid (#3819). */
16
16
  export function unsupportedPlanShape(plan: Pick<Plan, "nodes" | "acceptance">): string[] {
17
17
  const problems: string[] = [];
18
18
  if (!plan.acceptance?.length) problems.push("PLAN_ACCEPTANCE_REQUIRED: the auditor checks the plan's acceptance; add acceptance items");
19
19
  if (!plan.nodes.length) problems.push("UNSUPPORTED_PLAN_SHAPE: the plan has no nodes");
20
20
  const members = new Set(planMembers(plan).map((member) => member.nodeId));
21
- const unbound = plan.nodes.filter((node) => !members.has(node.id)).map((node) => node.id);
21
+ const unbound = plan.nodes.filter((node) => node.status !== "landed" && node.statusSource !== "manual" && !members.has(node.id)).map((node) => node.id);
22
22
  if (unbound.length) problems.push(`UNSUPPORTED_PLAN_SHAPE: nodes ${unbound.join(", ")} are not bound to a Task (status-pinned or unbound nodes cannot be dispatched; create them with relay_task_create plan:{...})`);
23
23
  return problems;
24
24
  }
@@ -48,6 +48,13 @@ export function taskSucceeded(task: PrerequisiteTask | null, slot: Pick<PlanDisp
48
48
  return prerequisiteState(task, requiredStage).state === "satisfied";
49
49
  }
50
50
 
51
+ /** Existing settled nodes are facts; a dispatched run still needs its verified result, including CI. */
52
+ export function planNodeSettled(node: PlanNode, task: PrerequisiteTask | null, slot?: PlanDispatchSlot, requiredStage?: TaskTerminalStage): boolean {
53
+ if (node.statusSource === "manual") return node.status === "landed";
54
+ if (slot) return taskSucceeded(task, slot, requiredStage);
55
+ return node.status === "landed" || task?.status === "done" || task?.status === "canceled";
56
+ }
57
+
51
58
  /** The stage an edge requires of its prerequisite; unnamed edges keep the original landed-on-main rule. */
52
59
  export function dependencyStage(task: Pick<TaskDetail, "dependsOnStages">, prerequisiteId: number): TaskTerminalStage {
53
60
  return task.dependsOnStages?.[String(prerequisiteId)] ?? "landed-on-main";
@@ -18,7 +18,7 @@ import { launchWorkflowRun, prepareWorkflowRun } from "./workflow-runs";
18
18
  import { ensureSupervisor, invoke, type Supervisor } from "./workflow-supervisor";
19
19
  import { stampPins } from "./workflow-pins";
20
20
  import { emitPlan } from "./plan-graph";
21
- import { dependencyStage, memberReady, occupiedSlots, planMembers, prerequisiteState, slotFromRun, taskSucceeded, unsupportedPlanShape } from "./plan-dispatch-policy";
21
+ import { dependencyStage, memberReady, occupiedSlots, planMembers, planNodeSettled, prerequisiteState, slotFromRun, taskSucceeded, unsupportedPlanShape } from "./plan-dispatch-policy";
22
22
  import type { AuthContext } from "./auth-context";
23
23
  import { planSlotOccupies, type Plan, type PlanDispatch, type PlanDispatchSlot, type TaskDetail } from "../types";
24
24
 
@@ -44,9 +44,13 @@ export async function startPlanDispatch(input: { planId?: unknown; expectedVersi
44
44
  if (plan.archived) throw new ValidationError(`plan ${plan.id} is archived`);
45
45
  const problems = unsupportedPlanShape(plan);
46
46
  if (problems.length) throw new ValidationError(problems.join("; "));
47
- const members = planMembers(plan);
48
- const tasks = await Promise.all(members.map((member) => getTaskDetailAsync(member.taskId)));
49
- const memberIds = new Set(members.map((member) => member.taskId));
47
+ const nodes = new Map(plan.nodes.map((node) => [node.id, node]));
48
+ const allMembers = planMembers(plan);
49
+ const allTasks = await Promise.all(allMembers.map((member) => getTaskDetailAsync(member.taskId)));
50
+ const unfinished = allMembers.flatMap((member, index) => planNodeSettled(nodes.get(member.nodeId)!, allTasks[index]!) ? [] : [{ member, task: allTasks[index]! }]);
51
+ const members = unfinished.map(({ member }) => member);
52
+ const tasks = unfinished.map(({ task }) => task);
53
+ const memberIds = new Set(plan.nodes.map((node) => Number(node.refs?.taskId)));
50
54
  for (const member of tasks) {
51
55
  for (const id of member?.dependsOn ?? []) {
52
56
  if (memberIds.has(id)) continue;
@@ -55,7 +59,7 @@ export async function startPlanDispatch(input: { planId?: unknown; expectedVersi
55
59
  }
56
60
  }
57
61
  const repos = new Set(tasks.map((task) => task?.repo).filter(Boolean));
58
- if (repos.size !== 1) throw new ValidationError(`UNSUPPORTED_PLAN_SHAPE: slice 1a dispatches one repo; the plan's Tasks name ${repos.size ? [...repos].join(", ") : "no repo"}`);
62
+ if (tasks.length && repos.size !== 1) throw new ValidationError(`UNSUPPORTED_PLAN_SHAPE: slice 1a dispatches one repo; the plan's Tasks name ${repos.size ? [...repos].join(", ") : "no repo"}`);
59
63
  let cwd: string | undefined;
60
64
  for (const [index, task] of tasks.entries()) {
61
65
  if (!task || task.status === "canceled") throw new ValidationError(`UNSUPPORTED_PLAN_SHAPE: T${members[index]!.taskId} is missing or canceled`);
@@ -115,6 +119,8 @@ function ensureListening(): void {
115
119
  if (event.type === "task.stage" || event.type === "task.status" || event.type === "task.updated") {
116
120
  const plans = watched.get(Number(event.data.id ?? event.subject));
117
121
  if (plans?.size) void fireAndForget("[plan-dispatch] reconcile", () => requestReconcile(plans));
122
+ } else if (event.type === "plan.changed" && event.subject && dispatching.has(event.subject)) {
123
+ void fireAndForget("[plan-dispatch] reconcile", () => requestReconcile([event.subject!]));
118
124
  } else if (dispatching.size && (event.type === "workflow.changed" || event.type === "branch.landed" || event.type === "ci.status")) {
119
125
  void fireAndForget("[plan-dispatch] reconcile", () => requestReconcile());
120
126
  }
@@ -184,9 +190,22 @@ async function reconcilePlan(planId: string): Promise<void> {
184
190
  changed ||= next !== slot;
185
191
  slots.set(slot.taskId, next);
186
192
  }
193
+ const nodes = new Map(plan.nodes.map((node) => [node.id, node]));
194
+ const nodesForTask = new Map(plan.nodes.filter((node) => node.refs?.taskId).map((node) => [Number(node.refs!.taskId), node]));
195
+ const prerequisites = new Map<string, string[]>();
196
+ for (const edge of plan.edges) {
197
+ if (!prerequisites.has(edge.to)) prerequisites.set(edge.to, []);
198
+ prerequisites.get(edge.to)!.push(edge.from);
199
+ }
200
+ const settledNode = async (nodeId: string, requiredStage?: Parameters<typeof taskSucceeded>[2]) => {
201
+ const node = nodes.get(nodeId)!;
202
+ const id = Number(node.refs?.taskId);
203
+ return planNodeSettled(node, Number.isSafeInteger(id) && id > 0 ? await task(id) : null, slots.get(id), requiredStage);
204
+ };
187
205
  if (dispatch.state === "active") {
188
206
  for (const { nodeId, taskId } of members) {
189
207
  const slot = slots.get(taskId);
208
+ if (!slot && await settledNode(nodeId)) continue;
190
209
  if (slot && (slot.status !== "launch-failed" && slot.status !== "failed" && slot.status !== "cancelled")) {
191
210
  const dispatched = planSlotOccupies(slot.status) ? await task(taskId) : null;
192
211
  if (dispatched) await escalateRegressions(plan, nodeId, dispatched, memberTaskIds, task);
@@ -207,14 +226,16 @@ async function reconcilePlan(planId: string): Promise<void> {
207
226
  for (const id of member.dependsOn) {
208
227
  const prerequisite = await task(id);
209
228
  const required = dependencyStage(member, id);
210
- if (taskSucceeded(prerequisite, slots.get(id), required)) succeeded.add(id);
229
+ const node = nodesForTask.get(id);
230
+ if (node ? await settledNode(node.id, required) : taskSucceeded(prerequisite, slots.get(id), required)) succeeded.add(id);
211
231
  else {
212
232
  const state = prerequisiteState(prerequisite, required);
213
233
  if (state.state === "unreachable") {
214
- notifyPlan(plan, `T${taskId} prerequisite unreachable`, `Plan ${plan.id}, node ${nodeId} (T${taskId}) waits on T${id}, which is ${state.reason}. Replace the edge with relay_task_remove_dependency then relay_task_add_dependency, cancel the node, or wait for T${id} to be reopened. The dependent keeps waiting.`, `plan:${plan.id}:prereq:T${id}:${state.reason}`, { kind: "plan.prerequisite-unreachable", planId, nodeId, taskId, prerequisiteTaskId: id, reason: state.reason, nextActions: ["replace-edge", "cancel-node", "wait-for-reopen"] });
234
+ await notifyPlan(plan, `T${taskId} prerequisite unreachable`, `Plan ${plan.id}, node ${nodeId} (T${taskId}) waits on T${id}, which is ${state.reason}. Replace the edge with relay_task_remove_dependency then relay_task_add_dependency, cancel the node, or wait for T${id} to be reopened. The dependent keeps waiting.`, `plan:${plan.id}:prereq:T${id}:${state.reason}`, { kind: "plan.prerequisite-unreachable", planId, nodeId, taskId, prerequisiteTaskId: id, reason: state.reason, nextActions: ["replace-edge", "cancel-node", "wait-for-reopen"] });
215
235
  }
216
236
  }
217
237
  }
238
+ if (!(await Promise.all((prerequisites.get(nodeId) ?? []).map((id) => settledNode(id)))).every(Boolean)) continue;
218
239
  if (!memberReady(member, undefined, (id) => succeeded.has(id))) continue;
219
240
  if (occupiedSlots(slots.values()) >= dispatch.concurrency) continue;
220
241
  const claimed = await write<{ slot?: PlanDispatchSlot; reserved: boolean }>("planDispatch.slot.reserve", { planId, taskId, nodeId });
@@ -223,7 +244,7 @@ async function reconcilePlan(planId: string): Promise<void> {
223
244
  if (fault === "after-reserve") { halted = true; throw new Error("plan dispatch test fault: the process stopped after reserving a slot"); }
224
245
  slots.set(taskId, await launch(plan, claimed.slot));
225
246
  }
226
- const settled = await Promise.all(members.map(async ({ taskId }) => taskSucceeded(await task(taskId), slots.get(taskId))));
247
+ const settled = await Promise.all(plan.nodes.map((node) => settledNode(node.id)));
227
248
  if (settled.every(Boolean)) {
228
249
  await openAudit(plan, dispatch, members.map(({ taskId }) => tasks.get(taskId)).find(Boolean) ?? null);
229
250
  changed = true;
@@ -245,7 +266,7 @@ async function escalateRegressions(plan: Plan, nodeId: string, member: TaskDetai
245
266
  const state = prerequisiteState(prerequisite, required);
246
267
  if (state.state === "satisfied") continue;
247
268
  const now = prerequisite ? `status ${prerequisite.status}, stage ${prerequisite.deliverableStage ?? "none"}${state.state === "unreachable" ? `, ${state.reason}` : ""}` : "missing";
248
- notifyPlan(plan, `T${member.id} prerequisite regressed`, `Plan ${plan.id}, node ${nodeId} (T${member.id}) was dispatched on T${id} reaching ${required}, which no longer holds (${now}). The run continues; check whether its work is still valid, or cancel the node.`, `plan:${plan.id}:prereq:T${id}:regressed`, { kind: "plan.prerequisite-unreachable", planId: plan.id, nodeId, taskId: member.id, prerequisiteTaskId: id, reason: "regressed", requiredStage: required, nextActions: ["check-run", "cancel-node", "wait-for-recovery"] });
269
+ await notifyPlan(plan, `T${member.id} prerequisite regressed`, `Plan ${plan.id}, node ${nodeId} (T${member.id}) was dispatched on T${id} reaching ${required}, which no longer holds (${now}). The run continues; check whether its work is still valid, or cancel the node.`, `plan:${plan.id}:prereq:T${id}:regressed`, { kind: "plan.prerequisite-unreachable", planId: plan.id, nodeId, taskId: member.id, prerequisiteTaskId: id, reason: "regressed", requiredStage: required, nextActions: ["check-run", "cancel-node", "wait-for-recovery"] });
249
270
  }
250
271
  }
251
272
 
@@ -279,7 +300,7 @@ async function launch(plan: Plan, slot: PlanDispatchSlot): Promise<PlanDispatchS
279
300
  const started = await launchWorkflowRun(prepared, { ...(inputs ? { inputs } : {}), ...(pins ? { pins } : {}), requestKey: `plan:${plan.id}:T${task.id}:a${slot.attempt}` }, AUTOPILOT_CTX);
280
301
  return (await write<PlanDispatchSlot | undefined>("planDispatch.slot.update", { taskId: slot.taskId, attempt: slot.attempt, runId: String(started.runId), status: String(started.status) })) ?? slot;
281
302
  } catch (error) {
282
- notifyPlan(plan, `T${slot.taskId} could not start`, `Plan ${plan.id} could not start T${slot.taskId}'s workflow: ${errMessage(error)}. Its slot is free and its dependents wait. Fix the cause, then start its workflow by hand (relay_workflow_start); the plan adopts that run.`, `plan:${plan.id}:launch-failed:T${slot.taskId}:a${slot.attempt}`, { kind: "plan.dispatch-failed", planId: plan.id, taskId: slot.taskId });
303
+ await notifyPlan(plan, `T${slot.taskId} could not start`, `Plan ${plan.id} could not start T${slot.taskId}'s workflow: ${errMessage(error)}. Its slot is free and its dependents wait. Fix the cause, then start its workflow by hand (relay_workflow_start); the plan adopts that run.`, `plan:${plan.id}:launch-failed:T${slot.taskId}:a${slot.attempt}`, { kind: "plan.dispatch-failed", planId: plan.id, taskId: slot.taskId });
283
304
  return (await write<PlanDispatchSlot | undefined>("planDispatch.slot.update", { taskId: slot.taskId, attempt: slot.attempt, status: "launch-failed", error: errMessage(error).slice(0, 2000) })) ?? slot;
284
305
  }
285
306
  }
@@ -310,7 +331,7 @@ async function driveAudit(sup: Supervisor, plan: Plan): Promise<boolean> {
310
331
  const started = await launchWorkflowRun(prepared, { requestKey: `plan:${plan.id}:audit` }, AUTOPILOT_CTX);
311
332
  await write("planDispatch.audit.update", { planId: plan.id, runId: String(started.runId), status: String(started.status) });
312
333
  } catch (error) {
313
- notifyPlan(plan, "audit could not start", `Plan ${plan.id}: every node landed, but its conformance audit (T${audit.taskId}) could not start: ${errMessage(error)}. Start ${PLAN_CONFORMANCE_WORKFLOW} on T${audit.taskId} by hand, or audit and record relay_plan_conformance yourself.`, `plan:${plan.id}:audit-failed`, { kind: "plan.dispatch-failed", planId: plan.id, taskId: audit.taskId });
334
+ await notifyPlan(plan, "audit could not start", `Plan ${plan.id}: every node landed, but its conformance audit (T${audit.taskId}) could not start: ${errMessage(error)}. Start ${PLAN_CONFORMANCE_WORKFLOW} on T${audit.taskId} by hand, or audit and record relay_plan_conformance yourself.`, `plan:${plan.id}:audit-failed`, { kind: "plan.dispatch-failed", planId: plan.id, taskId: audit.taskId });
314
335
  await write("planDispatch.audit.update", { planId: plan.id, status: "launch-failed" });
315
336
  }
316
337
  return true;
@@ -324,14 +345,14 @@ async function driveAudit(sup: Supervisor, plan: Plan): Promise<boolean> {
324
345
  dispatching.delete(plan.id);
325
346
  watchPlan(plan.id, new Set());
326
347
  const slots = plan.dispatch!.slots.map((slot) => `${slot.nodeId} T${slot.taskId} ${slot.runId ?? "no run"} ${slot.landedSha?.slice(0, 12) ?? ""}`.trim());
327
- notifyPlan(plan, "done", [`Plan ${plan.id}${plan.title ? ` "${plan.title}"` : ""} is done: conformance passed on ${audit.mainSha} at version ${plan.version}.`, ...slots, `Audit T${audit.taskId} run ${audit.runId}.`].join("\n"), `plan:${plan.id}:result:v${plan.version}`, { kind: "plan.result", planId: plan.id, planVersion: plan.version, mainSha: audit.mainSha });
348
+ await notifyPlan(plan, "done", [`Plan ${plan.id}${plan.title ? ` "${plan.title}"` : ""} is done: conformance passed on ${audit.mainSha} at version ${plan.version}.`, ...slots, `Audit T${audit.taskId} run ${audit.runId}.`].join("\n"), `plan:${plan.id}:result:v${plan.version}`, { kind: "plan.result", planId: plan.id, planVersion: plan.version, mainSha: audit.mainSha });
328
349
  }
329
350
  return true;
330
351
  }
331
352
 
332
- function notifyPlan(plan: Plan, subject: string, body: string, idempotencyKey: string, payload: Json): void {
353
+ async function notifyPlan(plan: Plan, subject: string, body: string, idempotencyKey: string, payload: Json): Promise<void> {
333
354
  const to = planNoticeTarget(plan);
334
- if (to) notifySystemMessage(to, { subject: `Plan ${plan.title ?? plan.id}: ${subject}`, body, idempotencyKey, replyExpected: false, payload });
355
+ if (to) await notifySystemMessage(to, { subject: `Plan ${plan.title ?? plan.id}: ${subject}`, body, idempotencyKey, replyExpected: false, payload });
335
356
  }
336
357
 
337
358
  // --- serving ---------------------------------------------------------------------------------------