@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
@@ -4,6 +4,7 @@ import { spawnTreeContains } from "../db/spawn-tree";
4
4
  import { cleanSpawnBudget } from "../spawn-budget";
5
5
  import { readWorkflowRunBudgetAsync, reconcileRunBudgetAsync } from "../token-usage-async";
6
6
  import { getRoutingPolicyEnvelope } from "../routing-policy-service";
7
+ import { resolveWorkflowRoutingDefaults } from "./workflow-routing-defaults";
7
8
 
8
9
  // #3045 — the workflow supervisor: mounts the kernel `workflow` module, starts runs, and pumps them.
9
10
  // A pass recovers crashed drivers, signals each waiting run from durable src facts (level-triggered,
@@ -14,10 +15,10 @@ import { errMessage, fireAndForget, stringValue, type WorkflowRunAgent, type Wor
14
15
  import { validateWorkflowValue } from "@humwick/kernel/workflow-values";
15
16
  import { OpError } from "@humwick/kernel";
16
17
 
17
- import { admitWorkflowCaller, assertWorkflowCallerLive } from "./workflow-caller";
18
+ import { admitWorkflowCaller, admitWorkflowTaskOwner, assertWorkflowCallerLive, canControlWorkflowTask } from "./workflow-caller";
18
19
  import { taskVerificationCheck } from "../task-verification";
19
20
 
20
- import { workflow, workflowEffectId, type WorkflowEffect, type WorkflowRunRecord } from "@humwick/kernel/workflow.pilot";
21
+ import { assertWorkflowFuturePins, workflow, workflowEffectId, type WorkflowEffect, type WorkflowRunRecord } from "@humwick/kernel/workflow.pilot";
21
22
  import { getAgent, getTaskDetailAsync, ValidationError } from "../db";
22
23
  import type { WorkflowRunCursor, WorkflowRunTaskRow } from "../db/workflow-task-read-ops";
23
24
  import { getGateway } from "../db/gateway";
@@ -31,7 +32,6 @@ import { McpNotFoundError } from "../mcp-errors";
31
32
  import { ServiceAuthError } from "./errors";
32
33
  import { canSpawn, spawnLineageAgentId, taskLinkedRepoCwd } from "./spawn-agent";
33
34
  import { resolveSpawnProjectWorkflow } from "./spawn-project-role";
34
- import { canControlTaskReviewHold } from "./task-review-hold";
35
35
  import { updateTaskEntityAsync } from "./task-entity";
36
36
 
37
37
  import { releaseRunHold } from "./workflow-effects";
@@ -77,19 +77,19 @@ export async function prepareWorkflowRun(input: { workflow: string; taskId: numb
77
77
  if (!task || task.kind !== "deliverable") throw new ValidationError("task not found");
78
78
  if (task.status === "canceled") throw new ValidationError(`task ${task.id} is canceled; a workflow runs on a Task that is still wanted`);
79
79
  const caller = admitWorkflowCaller(ctx, task);
80
- if (!caller && !canControlTaskReviewHold(ctx, task)) {
80
+ if (!caller && !canControlWorkflowTask(ctx, task)) {
81
81
  throw new ServiceAuthError(`starting a workflow on task ${task.id} is coordinator-only: the run sets the review hold and clears it on GO — ${COORDINATOR_AUTHORITY_HINT}`);
82
82
  }
83
83
  if (!caller && !canSpawn(ctx)) throw new ServiceAuthError("starting a workflow dispatches its roles, so it requires the command:spawn scope");
84
84
  const lineageId = spawnLineageAgentId(ctx, task);
85
85
  const lineage = lineageId ? getAgent(lineageId) : null;
86
- const cwd = input.cwd ?? await taskLinkedRepoCwd(task.id, stringValue(lineage?.meta?.cwd));
86
+ const cwd = input.cwd ?? await taskLinkedRepoCwd(task.id, stringValue(lineage?.meta?.cwd), lineage ?? undefined);
87
87
  const read = await resolveSpawnProjectWorkflow({ workflow: input.workflow, cwd, orchestratorId: input.orchestratorId, preferHost: lineage?.machine, allowedOrchestratorIds: ctx.constraints?.orchestrators });
88
88
  return { task, cwd, read };
89
89
  }
90
90
 
91
91
  /** relay_workflow_start: read the landed workflow and its roles, check every step's pin, start one run on the Task. */
92
- export async function startWorkflowRun(input: { workflow: string; taskId: number; pins?: WorkflowPins; inputs?: Json; requestKey?: string; budget?: unknown }, ctx: AuthContext): Promise<Json> {
92
+ export async function startWorkflowRun(input: { workflow: string; taskId: number; cwd?: string; pins?: WorkflowPins; inputs?: Json; requestKey?: string; budget?: unknown }, ctx: AuthContext): Promise<Json> {
93
93
  return launchWorkflowRun(await prepareWorkflowRun(input, ctx), input, ctx);
94
94
  }
95
95
 
@@ -99,18 +99,17 @@ export async function launchWorkflowRun(prepared: PreparedWorkflowRun, input: {
99
99
  const { task, cwd, read } = prepared;
100
100
  const budget = cleanSpawnBudget(input.budget);
101
101
  const { definition } = read.workflow;
102
+ // #3769 — a Task a delegated sub-coordinator owns runs as its run, whoever starts it: the step spawns' lineage and the run's origin agree.
103
+ const caller = admitWorkflowCaller(ctx, task, read.workflow) ?? admitWorkflowTaskOwner(task, read.workflow);
102
104
  await getGateway().read("spawnTree.validateTarget", { type: "agent.spawn", source: ctx.actor.id, target: read.orchestratorId,
103
- params: { spawnedBy: ctx.callerAgentId, taskId: task.id, cwd, orchestratorId: read.orchestratorId, profile: "worker", workflowRunId: "preflight" } });
104
- const caller = admitWorkflowCaller(ctx, task, read.workflow);
105
+ params: { spawnedBy: caller?.callerAgentId ?? ctx.callerAgentId, taskId: task.id, cwd, orchestratorId: read.orchestratorId, profile: "worker", workflowRunId: "preflight" } });
105
106
  const capabilityError = workflowCapabilityError(definition);
106
107
  if (capabilityError) throw new ValidationError(capabilityError);
107
108
  validateWorkflowValue(definition.inputs, input.inputs ?? {}, "INPUT_INVALID");
108
109
  if (Object.values(definition.steps).some(step => step.verify) && !taskVerificationCheck(task.doneWhen)) throw new ValidationError(`T${task.id} needs a nonempty verify: section in doneWhen`);
109
110
  const pins = input.pins ? stampPins(definition, input.pins, "start") : undefined;
110
111
  const defaults = (await getRoutingPolicyEnvelope()).policy.stepDefaults;
111
- const coordinatorId = ctx.callerAgentId ?? task.createdBy;
112
- const teamId = task.teamId ?? (coordinatorId ? getAgent(coordinatorId)?.teamId : undefined);
113
- const routingDefaults = defaults ? { global: defaults.global, ...(teamId ? { team: defaults.teams?.[teamId], teamId } : {}) } : undefined;
112
+ const routingDefaults = resolveWorkflowRoutingDefaults(defaults, task, ctx.callerAgentId);
114
113
  await assertStepLaunches({ definition, pins, routingDefaults, steps: Object.keys(definition.steps), roles: read.workflow.roles, inputTask: task }, ctx);
115
114
  for (const [name, snapshot] of Object.entries(read.workflow.closure ?? {})) {
116
115
  if (name === read.workflow.name) continue;
@@ -147,7 +146,7 @@ export async function launchWorkflowRun(prepared: PreparedWorkflowRun, input: {
147
146
  });
148
147
  if (!created && run.workflow !== read.workflow.name) throw new ValidationError(`task ${task.id} already runs workflow "${run.workflow}" (${run.id}); a Task has one open run`);
149
148
  if (!created && pins && !sameCallPins(run.pins, pins)) {
150
- throw new ValidationError(`task ${task.id} already runs ${run.id} with other pins; a running run keeps its pins, and relay_workflow_resume changes them after an escalation`);
149
+ throw new ValidationError(`task ${task.id} already runs ${run.id} with other pins; use relay_workflow_resume with pins to change not-yet-started steps`);
151
150
  }
152
151
  if (!created && budget && budget.tokenCap !== run.tokenBudget?.tokenCap) {
153
152
  throw new ValidationError(`task ${task.id} already runs ${run.id} with another budget; a run keeps its original token cap`);
@@ -181,9 +180,9 @@ async function settleRefusable(sup: Supervisor, run: WorkflowRunRecord): Promise
181
180
  throw error;
182
181
  }
183
182
 
184
- /** A run is visible to its coordinator, the input Task's coordinators and admins. */
185
- export function canViewRun(ctx: AuthContext, coordinator: string, task: Parameters<typeof canControlTaskReviewHold>[1] | null): boolean {
186
- return ctx.callerAgentId === coordinator || Boolean(ctx.callerAgentId && spawnTreeContains(ctx.callerAgentId, coordinator)) || Boolean(task && canControlTaskReviewHold(ctx, task));
183
+ /** A run is visible to its coordinator, the input Task's coordinators, its owning sub-coordinator and admins. */
184
+ export function canViewRun(ctx: AuthContext, coordinator: string, task: Parameters<typeof canControlWorkflowTask>[1] | null): boolean {
185
+ return ctx.callerAgentId === coordinator || Boolean(ctx.callerAgentId && spawnTreeContains(ctx.callerAgentId, coordinator)) || Boolean(task && canControlWorkflowTask(ctx, task));
187
186
  }
188
187
 
189
188
  /** #3352 — the run's cap and observed spend, as admission counts them. */
@@ -191,7 +190,7 @@ export async function readWorkflowRunBudget(runId: string, ctx: AuthContext): Pr
191
190
  const sup = await ensureSupervisor();
192
191
  const run = await readRun(sup, runId);
193
192
  if (!canViewRun(ctx, run.coordinator, await getTaskDetailAsync(run.taskId))) {
194
- throw new ServiceAuthError(`workflow run ${runId} is visible to its coordinator, the Task's coordinators and admins — ${COORDINATOR_AUTHORITY_HINT}`);
193
+ throw new ServiceAuthError(`workflow run ${runId} is visible to its coordinator, the Task's coordinators, its owning sub-coordinator and admins — ${COORDINATOR_AUTHORITY_HINT}`);
195
194
  }
196
195
  if (!run.tokenBudget) throw new McpNotFoundError(`workflow run ${runId} has no token budget`);
197
196
  return await readWorkflowRunBudgetAsync(run.id, run.tokenBudget.tokenCap) as unknown as Json;
@@ -218,7 +217,7 @@ async function readRun(sup: Supervisor, runId: string): Promise<WorkflowRunRecor
218
217
  }
219
218
 
220
219
  /** relay_workflow_resume: answer the run's escalation by re-entering it at its escalated step or a named one (#3045 H2),
221
- * replacing the pins of each step `pins` names (#3121). */
220
+ * replacing the pins of each step `pins` names (#3121); pins alone on a running or paused run update future steps (#3806). */
222
221
  export async function resumeWorkflowRun(input: { runId: string; activationId?: string; step?: string; note?: string; pins?: WorkflowPins; decision?: WorkflowDecisionResponse }, ctx: AuthContext): Promise<Json> {
223
222
  const sup = await ensureSupervisor();
224
223
  const run = await readRun(sup, input.runId);
@@ -232,14 +231,27 @@ export async function resumeWorkflowRun(input: { runId: string; activationId?: s
232
231
  return { ...runView(await readRun(sup, run.id)), resumed: input.decision.action === "answer" };
233
232
  }
234
233
  const task = await getTaskDetailAsync(run.taskId);
235
- if (!task || !(ctx.callerAgentId === run.callerAgentId || canControlTaskReviewHold(ctx, task))) {
234
+ if (!task || !(ctx.callerAgentId === run.callerAgentId || canControlWorkflowTask(ctx, task))) {
236
235
  throw new ServiceAuthError(`resuming workflow run ${run.id} is coordinator-only: the run sets the review hold and clears it on GO — ${COORDINATOR_AUTHORITY_HINT}`);
237
236
  }
238
237
  if (run.callerAgentId && ctx.actor.kind !== "system") assertWorkflowCallerLive(run.callerAgentId);
239
238
  if (!run.callerAgentId && !canSpawn(ctx)) throw new ServiceAuthError("resuming a workflow can dispatch its roles, so it requires the command:spawn scope");
240
239
  if (task.status === "canceled") throw new ValidationError(`T${task.id} is canceled, so its run ${run.id} cannot resume`);
241
240
  const pins = input.pins ? stampPins(run.definition, input.pins, "resume") : undefined;
242
- if (pins) await assertResumePins(run, pins, task, ctx, await runAgents(sup, run.id));
241
+ if (pins && !input.step && !input.activationId && run.status === "running") {
242
+ await serialized(sup, async () => {
243
+ const latest = await readRun(sup, run.id);
244
+ assertWorkflowFuturePins(latest, pins);
245
+ await assertResumePins(latest, pins, task, ctx, await runAgents(sup, run.id), (op, args) => invoke(sup, op, args));
246
+ await invoke(sup, "amendPins", { runId: run.id, pins, by: ctx.callerAgentId ?? ctx.actor.id, note: input.note });
247
+ }).catch((error: unknown) => {
248
+ if (error instanceof OpError && (error.code === "conflict" || error.code === "invalid_input")) throw new ValidationError(error.message);
249
+ throw error;
250
+ });
251
+ await requestPump(sup);
252
+ return { ...runView(await readRun(sup, run.id)), resumed: false, pinsUpdated: true };
253
+ }
254
+ if (pins) await assertResumePins(run, pins, task, ctx, await runAgents(sup, run.id), (op, args) => invoke(sup, op, args));
243
255
  // Between passes, so no pass is performing the end-of-run reap the resume voids. A fact that already ends the
244
256
  // current wait (a delivery, a land) is signaled first and wins: the resume then refuses.
245
257
  const resume = serialized(sup, async () => {
@@ -277,12 +289,12 @@ export async function cancelWorkflowRun(input: { runId: string; reason?: string
277
289
  return { ...runView(await readRun(sup, run.id)), cancelled };
278
290
  }
279
291
 
280
- /** The run a coordinator-only control acts on: its caller, the input Task's coordinators and admins may. */
292
+ /** The run a coordinator-only control acts on: its caller, the input Task's coordinators, its owning sub-coordinator and admins may. */
281
293
  async function controlledRun(runId: string, ctx: AuthContext, verb: string): Promise<{ sup: Supervisor; run: WorkflowRunRecord; by: string }> {
282
294
  const sup = await ensureSupervisor();
283
295
  const run = await readRun(sup, runId);
284
296
  const task = await getTaskDetailAsync(run.taskId);
285
- if (!task || !(ctx.callerAgentId === run.callerAgentId || canControlTaskReviewHold(ctx, task))) {
297
+ if (!task || !(ctx.callerAgentId === run.callerAgentId || canControlWorkflowTask(ctx, task))) {
286
298
  throw new ServiceAuthError(`${verb} workflow run ${run.id} is coordinator-only — ${COORDINATOR_AUTHORITY_HINT}`);
287
299
  }
288
300
  return { sup, run, by: ctx.callerAgentId ?? ctx.actor.id };
@@ -310,7 +322,7 @@ export async function getWorkflowRun(runId: string, ctx: AuthContext, options: W
310
322
  await sup.notes;
311
323
  const run = await readRun(sup, runId);
312
324
  if (!canViewRun(ctx, run.coordinator, await getTaskDetailAsync(run.taskId))) {
313
- throw new ServiceAuthError(`workflow run ${runId} is visible to its coordinator, the Task's coordinators and admins — ${COORDINATOR_AUTHORITY_HINT}`);
325
+ throw new ServiceAuthError(`workflow run ${runId} is visible to its coordinator, the Task's coordinators, its owning sub-coordinator and admins — ${COORDINATOR_AUTHORITY_HINT}`);
314
326
  }
315
327
  const scripts = options.verbose && Object.values(run.definition.steps).some(step => step.script)
316
328
  ? await invoke<{ items: Array<WorkflowScriptRun & { id: string; runId: string }>; cursor?: string }>(sup, "scriptAttempts", { runId, ...(options.scriptCursor ? { cursor: options.scriptCursor } : {}) }) : undefined;
@@ -1,7 +1,7 @@
1
1
  import { errMessage } from "@humwick/sdk";
2
2
  import type { ModuleHost, ModuleHostOptions } from "@humwick/kernel";
3
3
  import type { WorkflowRuntime } from "@humwick/kernel/workflow-runtime";
4
- import { workflow, type WorkflowAgent } from "@humwick/kernel/workflow.pilot";
4
+ import { workflow, type WorkflowAgent, type WorkflowEffect } from "@humwick/kernel/workflow.pilot";
5
5
  import { getDbGeneration } from "../db";
6
6
  import { emitRelayEvent } from "../events";
7
7
 
@@ -50,6 +50,26 @@ export function supervisorNow(): number {
50
50
  return (testOptions.now ?? Date.now)();
51
51
  }
52
52
 
53
+ const QUICK_RETRIES = 3;
54
+ const RETRY_BASE_MS = 1000;
55
+ const RETRY_MAX_MS = 60_000;
56
+
57
+ /** #3827 — a retried effect's result with its next attempt. A stop still in flight (`inFlight`) is re-read next
58
+ * pass; consecutive terminal misses (a failed stop, a gone target, a stop without proof) get a few quick retries,
59
+ * then exponential backoff to a minute, so an effect that never converges costs one attempt a minute, not one a pass. */
60
+ export function retryResult(previous: Json | undefined, result: Json, now: number): Json {
61
+ if (result.inFlight === true) return result;
62
+ const failures = (typeof previous?.failures === "number" ? previous.failures : 0) + 1;
63
+ const delay = failures <= QUICK_RETRIES ? 0 : Math.min(RETRY_MAX_MS, RETRY_BASE_MS * 2 ** Math.min(failures - QUICK_RETRIES - 1, 6));
64
+ return { ...result, failures, nextAttemptAt: now + delay };
65
+ }
66
+
67
+ /** Whether a pending effect's backoff has run out. */
68
+ export function effectDue(effect: WorkflowEffect, now: number): boolean {
69
+ const at = effect.result?.nextAttemptAt;
70
+ return typeof at !== "number" || at <= now;
71
+ }
72
+
53
73
  /** The supervisor bound to the current database handle (a test that swaps the handle rebuilds it). */
54
74
  export function live(): Supervisor | undefined {
55
75
  return current && current.generation === getDbGeneration() ? current : undefined;
@@ -1,4 +1,5 @@
1
1
  // #3045 — drives durable workflow transitions, reconciliation and external effects.
2
+ import { framed } from "../hub-metrics";
2
3
  import { errMessage, fireAndForget, isRecord, stringValue } from "@humwick/sdk";
3
4
  import { createModuleHost, type ModuleHost } from "@humwick/kernel";
4
5
  import { workflow } from "@humwick/kernel/workflow.pilot";
@@ -57,10 +58,10 @@ function startSupervisor(): Supervisor {
57
58
  }),
58
59
  landed: new Map(), tail: predecessor, queued: undefined, notes: predecessor, refusable: new Map(),
59
60
  };
60
- sup.ticker = setInterval(() => {
61
- if (live() === sup) void fireAndForget("[workflow] supervisor pump", () => requestPump(sup));
61
+ sup.ticker = setInterval(framed("timer workflow-supervisor", () => {
62
+ if (live() === sup) return fireAndForget("[workflow] supervisor pump", () => requestPump(sup));
62
63
  else if (sup.ticker) clearInterval(sup.ticker);
63
- }, workflowScriptsEnabled() ? 1000 : SCHEDULER_TICK_MS);
64
+ }), workflowScriptsEnabled() ? 1000 : SCHEDULER_TICK_MS);
64
65
  sup.ticker.unref?.();
65
66
  setSupervisor(sup);
66
67
  return sup;
@@ -1,3 +1,4 @@
1
+ import { insertRecoveredWorkspaceAsync } from "../db/runtime-admin-async";
1
2
  // #1502 — server-authoritative self-heal of a dropped/never-written isolated-workspace registration.
2
3
  //
3
4
  // A live worker holds its own workspace descriptor in AGENT_RELAY_WORKSPACE_JSON, but the workspace
@@ -18,7 +19,7 @@
18
19
  // 404), never a best-effort guess, because the output is a landing authorization.
19
20
 
20
21
  import { resolve } from "node:path";
21
- import { getDb, getOrchestrator, insertRecoveredWorkspace, workspaceLandMetadataFrom, type RecoveredWorkspaceInput } from "../db";
22
+ import { getDb, getOrchestrator, workspaceLandMetadataFrom, type RecoveredWorkspaceInput } from "../db";
22
23
  import { parseJson, isPathWithinBase } from "../utils";
23
24
  import { sanitizeFsName } from "@humwick/sdk/fs-name";
24
25
  import { hasScope } from "@humwick/sdk/types";
@@ -69,7 +70,7 @@ function resolveOwningOrchestrator(ctx: AuthContext, params: Record<string, unkn
69
70
  return null;
70
71
  }
71
72
 
72
- export function recoverIsolatedWorkspace(ctx: AuthContext, hint: WorkspaceRecoveryHint | undefined, pathId: string | undefined): WorkspaceRecovery {
73
+ export async function recoverIsolatedWorkspace(ctx: AuthContext, hint: WorkspaceRecoveryHint | undefined, pathId: string | undefined): Promise<WorkspaceRecovery> {
73
74
  const caller = ctx.callerAgentId;
74
75
  if (!caller) return REFUSE;
75
76
  // Minting a land/merge target is a privileged write — require command:write, not baseline identity
@@ -163,6 +164,6 @@ export function recoverIsolatedWorkspace(ctx: AuthContext, hint: WorkspaceRecove
163
164
  ownerAgentId: caller,
164
165
  landMetadata: workspaceLandMetadataFrom(params),
165
166
  };
166
- const result = insertRecoveredWorkspace(input);
167
+ const result = (await insertRecoveredWorkspaceAsync(input));
167
168
  return { ok: true, workspace: result.workspace, created: result.created };
168
169
  }
package/src/setup.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { writeChildEnv } from "@humwick/sdk/env";
1
+ import { relayEnv, writeChildEnv } from "@humwick/sdk/env";
2
2
  import { randomBytes } from "node:crypto";
3
3
  import { shellQuote } from "@humwick/sdk/shell-utils";
4
4
  import { access, mkdir, readFile, writeFile } from "node:fs/promises";
@@ -70,18 +70,24 @@ export function generateToken(): string {
70
70
  return randomBytes(24).toString("hex");
71
71
  }
72
72
 
73
+ /**
74
+ * The setup plan. `existing` holds the values of the managed env file already on disk: a re-run
75
+ * keeps them (above all the admin token) unless an option overrides them (#2680).
76
+ */
73
77
  export function createSetupPlan(
74
78
  options: SetupOptions = {},
75
79
  env: SetupEnvironment = {},
80
+ existing: Record<string, string> = {},
76
81
  ): SetupPlan {
77
82
  const configDir = defaultConfigDir(env);
78
83
  const stateDir = defaultStateDir(env);
79
84
  const logDir = defaultLogDir(env);
80
85
  const envFile = resolve(options.envFile ?? join(configDir, "env"));
81
- const host = options.host ?? DEFAULT_HOST;
82
- const port = normalizePort(options.port);
83
- const token = options.token ?? (options.generateToken === false ? "" : generateToken());
84
- const dbPath = resolve(options.dbPath ?? join(stateDir, "agent-relay.db"));
86
+ const host = options.host ?? existing.HOST ?? DEFAULT_HOST;
87
+ const port = normalizePort(options.port ?? (existing.PORT ? Number(existing.PORT) : undefined));
88
+ const token = options.token
89
+ ?? (options.generateToken === false ? "" : relayEnv(existing, "AGENT_RELAY_TOKEN") || generateToken());
90
+ const dbPath = resolve(options.dbPath ?? existing.DB_PATH ?? join(stateDir, "agent-relay.db"));
85
91
  const runtimePrefix = resolve(options.runtimePrefix ?? defaultRuntimePrefix(env));
86
92
  const tarballDir = runtimeTarballDir(runtimePrefix);
87
93
  const warnings: string[] = [];
@@ -91,12 +97,13 @@ export function createSetupPlan(
91
97
  }
92
98
 
93
99
  const values: Record<string, string> = {
100
+ RETENTION_DAYS: "30",
101
+ ...existing,
94
102
  PORT: String(port),
95
103
  HOST: host,
96
104
  DB_PATH: dbPath,
97
- RETENTION_DAYS: "30",
98
105
  };
99
- if (token) writeChildEnv(values, { "AGENT_RELAY_TOKEN": token });
106
+ writeChildEnv(values, token ? { "AGENT_RELAY_TOKEN": token } : {}, token ? [] : ["AGENT_RELAY_TOKEN"]);
100
107
 
101
108
  return {
102
109
  envFile,
@@ -111,6 +118,38 @@ export function createSetupPlan(
111
118
  };
112
119
  }
113
120
 
121
+ /** The admin token a plan writes, or "" for a token-less relay. */
122
+ export function setupPlanToken(plan: Pick<SetupPlan, "values">): string {
123
+ return relayEnv(plan.values, "AGENT_RELAY_TOKEN") ?? "";
124
+ }
125
+
126
+ /** `createSetupPlan` over the managed env file already at the target path, if any. */
127
+ export async function resolveSetupPlan(options: SetupOptions = {}, env: SetupEnvironment = {}): Promise<SetupPlan & { reused: boolean }> {
128
+ const envFile = resolve(options.envFile ?? join(defaultConfigDir(env), "env"));
129
+ const content = await readIfExists(envFile);
130
+ const existing = content?.includes(SETUP_MARKER) ? parseEnvFile(content) : undefined;
131
+ return { ...createSetupPlan(options, env, existing), reused: existing !== undefined };
132
+ }
133
+
134
+ /** The values of an env file in the format `renderEnvFile` writes. */
135
+ export function parseEnvFile(content: string): Record<string, string> {
136
+ const values: Record<string, string> = {};
137
+ for (const line of content.split("\n")) {
138
+ const match = /^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/.exec(line.trim());
139
+ if (!match) continue;
140
+ const raw = match[2]!;
141
+ values[match[1]!] = raw.startsWith("'") && raw.endsWith("'") && raw.length >= 2 ? raw.slice(1, -1).replaceAll("'\\''", "'") : raw;
142
+ }
143
+ return values;
144
+ }
145
+
146
+ /** The plan without the admin token, for any output a person or a log may see. */
147
+ export function redactSetupPlan<T extends SetupPlan>(plan: T): T {
148
+ const values = { ...plan.values };
149
+ for (const key of ["AGENT_RELAY_TOKEN", "HUMWICK_TOKEN"]) if (values[key]) values[key] = "<redacted>";
150
+ return { ...plan, values, content: redactEnv(plan.content) };
151
+ }
152
+
114
153
  export async function executeSetupPlan(
115
154
  plan: SetupPlan,
116
155
  options: { dryRun?: boolean; force?: boolean } = {},
@@ -195,5 +234,5 @@ function normalizePort(port: number | undefined): number {
195
234
  }
196
235
 
197
236
  function redactEnv(content: string): string {
198
- return content.replace(/^(AGENT_RELAY_TOKEN=).+$/m, "$1'<generated-token>'");
237
+ return content.replace(/^((?:AGENT_RELAY|HUMWICK)_TOKEN=).+$/gm, "$1'<generated-token>'");
199
238
  }
@@ -1,11 +1,18 @@
1
1
  import type { Orchestrator, SpawnProvider } from "./types";
2
+ import { ValidationError } from "@humwick/sdk";
3
+
4
+ export class PinnedHostCapacityError extends ValidationError {
5
+ constructor(message: string, readonly orchestratorId: string) {
6
+ super(message);
7
+ }
8
+ }
2
9
 
3
10
  export interface AdmissionReason {
4
11
  check: "host" | "provider" | "auth" | "capacity";
5
12
  code: string;
6
13
  message: string;
7
14
  }
8
- export interface Admission { eligible: boolean; refusals: AdmissionReason[]; uncertainties: AdmissionReason[] }
15
+ export interface Admission { eligible: boolean; refusals: AdmissionReason[]; uncertainties: AdmissionReason[]; agentCapOnly?: boolean }
9
16
 
10
17
  /** Interpret all probe legs independently: a failure on one never hides another leg's timeout. */
11
18
  export function providerProbeAdmission(item: Orchestrator, provider: SpawnProvider, pinned = false): Admission {
@@ -40,7 +47,8 @@ export function providerProbeAdmission(item: Orchestrator, provider: SpawnProvid
40
47
 
41
48
  export function assessSpawnAdmission(item: Orchestrator, provider: SpawnProvider, checks: AdmissionReason[] = [], pinned = false): Admission {
42
49
  const probe = providerProbeAdmission(item, provider, pinned);
43
- return { eligible: probe.eligible && checks.length === 0, refusals: [...checks, ...probe.refusals], uncertainties: probe.uncertainties };
50
+ return { eligible: probe.eligible && checks.length === 0, refusals: [...checks, ...probe.refusals], uncertainties: probe.uncertainties,
51
+ agentCapOnly: probe.eligible && checks.length > 0 && checks.every(reason => reason.code === "agent_cap") };
44
52
  }
45
53
 
46
54
  export function admissionMessages(admission: Admission): string {
@@ -13,7 +13,7 @@ import { HOST_BAND_CONFIG } from "./config";
13
13
  import { hostProviderRefusal, liveHostDisabledProviders } from "./host-provider-store";
14
14
 
15
15
  import { hostSupportsModel } from "./spawn-models";
16
- import { admissionMessages, assessSpawnAdmission, type AdmissionReason, type Admission } from "./spawn-admission";
16
+ import { admissionMessages, assessSpawnAdmission, PinnedHostCapacityError, type AdmissionReason, type Admission } from "./spawn-admission";
17
17
 
18
18
  const MATCHED_HOST_IDENTITY: HostIdentity = Object.freeze({ matched: true });
19
19
 
@@ -180,10 +180,14 @@ export function selectSpawnPlacement(
180
180
  const orchestrator = getOrchestrator(orchestratorId);
181
181
  if (!orchestrator) throw new McpNotFoundError(`orchestrator ${orchestratorId} not found`);
182
182
  const view = orchestrator.hostId ? byId.get(orchestrator.hostId) : undefined;
183
- const admission = hostSpawnAdmission(orchestrator, view, identities, provider, model, effort, liveHostDisabledProviders(), true, spawnHostCapAdmission(capOpts)(view), repoPlatformAdmission(view, placementPlatforms));
183
+ const admission = hostSpawnAdmission(orchestrator, view, identities, provider, model, effort, liveHostDisabledProviders(), true, spawnHostCapAdmission(capOpts)(view), [
184
+ ...repoPlatformAdmission(view, placementPlatforms), ...workspaceHostAdmission(orchestrator, view, workspaceMode, canLand, capOpts, true),
185
+ ]);
184
186
  const detail = admissionMessages(admission);
185
187
  if (!admission.eligible) {
186
- throw new ValidationError(spawnPlacementRefusal(listOrchestrators(), views, provider, model, effort, orchestratorId, capOpts, admission));
188
+ const message = spawnPlacementRefusal(listOrchestrators(), views, provider, model, effort, orchestratorId, capOpts, admission);
189
+ if (admission.agentCapOnly) throw new PinnedHostCapacityError(message, orchestratorId);
190
+ throw new ValidationError(message);
187
191
  }
188
192
  return { orchestrator, reason: `Explicit host pin: ${orchestrator.hostname} (${orchestrator.id})${detail ? `; ${detail}` : ""}.` };
189
193
  }
@@ -241,7 +245,25 @@ function headroomSummary(view: HostView | undefined): string {
241
245
  return `(${round(cores)} free cores, ${round(freeThreadsOf(view)!)} free threads, band ${view.band})`;
242
246
  }
243
247
 
244
- export interface SpawnCapOptions { callerAgentId?: string; attach?: boolean }
248
+ export interface SpawnCapOptions {
249
+ callerAgentId?: string;
250
+ attach?: boolean;
251
+ buildWork?: boolean;
252
+ workspaceDepsDisabledHosts?: readonly string[];
253
+ }
254
+
255
+ /** Pins and landBlocked cannot turn isolated dependency/build work into a light hub probe (#3805). */
256
+ function workspaceHostAdmission(item: Orchestrator, view: HostView | undefined, mode: WorkspaceMode | undefined, canLand: boolean, opts: SpawnCapOptions, pinned = false): AdmissionReason[] {
257
+ if (mode !== "isolated") return [];
258
+ const hubWork = (opts.buildWork ?? canLand) || (!opts.attach && !opts.workspaceDepsDisabledHosts?.includes(item.id));
259
+ if (hubWork && view?.policy.noLandingWorkspaces !== false && isRelayHost(item, view)) {
260
+ return [{ check: "host", code: "hub_workspace", message: `no builders on the hub: Relay server host ${item.hostname} (${item.id}) refuses isolated build or dependency-install workspaces, including pinned spawns and workflow steps; choose a non-hub host or set Fleet > Edit host > Isolated workspaces to Include this host (noLandingWorkspaces=false)` }];
261
+ }
262
+ if (pinned || !canLand) return [];
263
+ const landing = view?.policy.noLandingWorkspaces === true ? "host excludes landing workspaces"
264
+ : view?.policy.noLandingWorkspaces !== false && isRelayHost(item, view) ? "Relay server host excluded by default" : undefined;
265
+ return landing ? [{ check: "host", code: "landing", message: landing }] : [];
266
+ }
245
267
 
246
268
  // Repository tools must run on the host even when the agent cannot land or pins a host (#3563).
247
269
  function repoPlatformAdmission(view: HostView | undefined, platforms?: readonly string[] | null): AdmissionReason[] {
@@ -282,11 +304,7 @@ export function spawnPlacementCandidates(
282
304
  const extra = repoPlatformAdmission(view, placementPlatforms);
283
305
  const capacity = capBlock(view);
284
306
  if (capacity) extra.push({ check: "capacity", code: "agent_cap", message: capacity });
285
- if (isolatedAutoPlacement) {
286
- const landing = view?.policy.noLandingWorkspaces === true ? "host excludes landing workspaces"
287
- : view?.policy.noLandingWorkspaces !== false && isRelayHost(item, view) ? "Relay server host excluded by default" : undefined;
288
- if (landing) extra.push({ check: "host", code: "landing", message: landing });
289
- }
307
+ extra.push(...workspaceHostAdmission(item, view, workspaceMode, canLand, capOpts));
290
308
  const admission = hostSpawnAdmission(item, view, identities, provider, model, effort, hostDisabled, false, undefined, extra);
291
309
  if (!admission.eligible) exclusions.push(`${item.hostname}: ${admissionMessages(admission)}`);
292
310
  else if (admission.uncertainties.length) warnings.push(`${item.hostname}: ${admissionMessages(admission)}`);
@@ -1,6 +1,6 @@
1
1
  import { apiBudgetStatus } from "./db/claude-api-budget";
2
2
  import { apiBudgetStatusAsync } from "./claude-api-budget-async";
3
- import { isOpenAiApiModel, type ApiBudgetStatus } from "@humwick/sdk";
3
+ import { API_BUDGET_IDS, isOpenAiApiModel, type ApiBudgetStatus } from "@humwick/sdk";
4
4
  import { collapseProviderQuotaRows } from "@humwick/sdk/provider-quota-order";
5
5
  import { resolveQuotaPacing, subscriptionExpiryForWindow, subscriptionExpiryPreference, type ResolvedQuotaPacing } from "./quota-pacing";
6
6
  import { countLiveSpawnedAgents, countLiveSpawnedAgentsAsync, getAgent, getAgentAsync, listOrchestrators, listOrchestratorsAsync, listProviderQuotas, providerQuotaRoutingSnapshotAsync } from "./db";
@@ -53,7 +53,7 @@ export function buildSpawnTargets(input: { callerId?: string; quota: number }):
53
53
  const me = input.callerId ? getAgent(input.callerId) : undefined;
54
54
  return buildSpawnTargetsFrom(input, {
55
55
  me,
56
- openaiBudget: apiBudgetStatus(now, "openai-api"),
56
+ apiBudgets: API_BUDGET_IDS.map((id) => apiBudgetStatus(now, id)),
57
57
  orchestrators: listOrchestrators(),
58
58
  hostViews: listHostViews(now),
59
59
  liveChildren: input.callerId ? countLiveSpawnedAgents(input.callerId) : 0,
@@ -67,17 +67,17 @@ export function buildSpawnTargets(input: { callerId?: string; quota: number }):
67
67
  /** Worker-safe discovery path: every durable input is read through the reader gateway. */
68
68
  export async function buildSpawnTargetsAsync(input: { callerId?: string; quota: number }): Promise<Record<string, unknown>> {
69
69
  const now = Date.now();
70
- const [me, orchestrators, hostViews, liveChildren, snapshot, openaiBudget] = await Promise.all([
70
+ const [me, orchestrators, hostViews, liveChildren, snapshot, apiBudgets] = await Promise.all([
71
71
  input.callerId ? getAgentAsync(input.callerId) : Promise.resolve(null),
72
72
  listOrchestratorsAsync(),
73
73
  listHostViewsAsync(),
74
74
  input.callerId ? countLiveSpawnedAgentsAsync(input.callerId) : Promise.resolve(0),
75
75
  providerQuotaRoutingSnapshotAsync(now),
76
- apiBudgetStatusAsync("openai-api"),
76
+ Promise.all(API_BUDGET_IDS.map((id) => apiBudgetStatusAsync(id))),
77
77
  ]);
78
78
  return buildSpawnTargetsFrom(input, {
79
79
  me: me ?? undefined,
80
- openaiBudget,
80
+ apiBudgets,
81
81
  orchestrators,
82
82
  hostViews,
83
83
  liveChildren,
@@ -90,7 +90,7 @@ export async function buildSpawnTargetsAsync(input: { callerId?: string; quota:
90
90
 
91
91
  function buildSpawnTargetsFrom(input: { callerId?: string; quota: number }, data: {
92
92
  me?: ReturnType<typeof getAgent>;
93
- openaiBudget: ApiBudgetStatus;
93
+ apiBudgets: ApiBudgetStatus[];
94
94
  orchestrators: Orchestrator[];
95
95
  hostViews: readonly HostView[];
96
96
  liveChildren: number;
@@ -130,10 +130,10 @@ function buildSpawnTargetsFrom(input: { callerId?: string; quota: number }, data
130
130
  ...(o.utilization && Number.isFinite(o.utilization.agentSlotsUsedThisDaemon) && Number.isFinite(o.utilization.agentSlotsMaxThisDaemon) && o.utilization.agentSlotsMaxThisDaemon > 0
131
131
  ? { spawn_slots: { used: o.utilization.agentSlotsUsedThisDaemon, max: o.utilization.agentSlotsMaxThisDaemon } }
132
132
  : {}),
133
- providers: providerMatrixFor(o, data.quotaAdvisories, data.catalog, data.profiles, hostDisabled, data.openaiBudget),
133
+ providers: providerMatrixFor(o, data.quotaAdvisories, data.catalog, data.profiles, hostDisabled, data.apiBudgets.find((budget) => budget.budgetId === "openai-api")),
134
134
  };
135
135
  }),
136
- apiBudgets: [data.openaiBudget],
136
+ apiBudgets: data.apiBudgets.map((budget) => ({ ...budget, source: "relay-accounting" })),
137
137
  ...operatorDisabledProviders(data.catalog),
138
138
  profiles: spawnProfilesSummary(data.profiles),
139
139
  // #3327 — preferences steer automatic spawns inside the dispatcher; never copy them into explicit pins.
@@ -2,15 +2,23 @@ import { isRecord } from "@humwick/sdk";
2
2
 
3
3
  const FULL_CARD_KEYS = ["name", "kind", "label", "tags", "systemTags", "instanceId", "epoch", "capabilities", "providerCapabilities"];
4
4
 
5
- /** Per-connection baselines: reconnect/replay starts with full cards, never another client's delta. */
5
+ export interface AgentStatusChange {
6
+ previous?: number;
7
+ seq: number;
8
+ full: Record<string, unknown>;
9
+ delta: Record<string, unknown>;
10
+ }
11
+
12
+ /** Build once in source order; consumers need only the last sequence they received. */
6
13
  export class AgentStatusDeltas {
7
- private readonly agents = new Map<string, Map<string, string | undefined>>();
14
+ private readonly agents = new Map<string, { seq: number; fields: Map<string, string | undefined> }>();
8
15
 
9
16
  remove(id: string): void { this.agents.delete(id); }
10
17
 
11
- project(data: Record<string, unknown>): Record<string, unknown> {
18
+ project(data: Record<string, unknown>, seq: number): AgentStatusChange {
12
19
  const id = data.id as string;
13
- const previous = this.agents.get(id);
20
+ const baseline = this.agents.get(id);
21
+ const previous = baseline?.fields;
14
22
  const fields = new Map<string, string | undefined>();
15
23
  const delta: Record<string, unknown> = { id, status: data.status, statusSeq: data.statusSeq, epoch: data.epoch };
16
24
  for (const [key, value] of Object.entries(data)) {
@@ -29,7 +37,8 @@ export class AgentStatusDeltas {
29
37
  }
30
38
  }
31
39
  if (data.status === "offline") this.remove(id);
32
- else this.agents.set(id, fields);
33
- return !previous || FULL_CARD_KEYS.some((key) => previous.get(key) !== fields.get(key)) ? data : delta;
40
+ else this.agents.set(id, { seq, fields });
41
+ return { previous: baseline?.seq, seq, full: data,
42
+ delta: !previous || FULL_CARD_KEYS.some((key) => previous.get(key) !== fields.get(key)) ? data : delta };
34
43
  }
35
44
  }