@humwick/server 0.130.2 → 0.130.3

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 (570) 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 +73 -4
  8. package/package.json +8 -7
  9. package/public/assets/{MessageBubble-BWLqMGKG.js → MessageBubble-Bq9kr21N.js} +2 -2
  10. package/public/assets/{PlanGraphPanel-3TlIQpjk.js → PlanGraphPanel-Dqiroy06.js} +2 -2
  11. package/public/assets/{TraceView-Wimm0Czv.js → TraceView-DYU962-H.js} +2 -2
  12. package/public/assets/{VaultDecisionCard-Bau_vNax.js → VaultDecisionCard-DhPfzioE.js} +2 -2
  13. package/public/assets/{activity-CrSurn-r.js → activity-DzCcQTee.js} +2 -2
  14. package/public/assets/{agent-lineage-CknAJKcb.js → agent-lineage-SgDt9i3T.js} +2 -2
  15. package/public/assets/agent-liveness-Bq-s73jW.js +2 -0
  16. package/public/assets/{agent-name-DKbiIhBo.js → agent-name-CcY3Aijy.js} +2 -2
  17. package/public/assets/{agent-profiles-DwF6EQ4R.js → agent-profiles-cxtRBn6O.js} +2 -2
  18. package/public/assets/{agent-project-badge-Diz4Sjjb.js → agent-project-badge-DGYbIhaI.js} +2 -2
  19. package/public/assets/{agent-tools-CRhpRQza.js → agent-tools-DHbftV0N.js} +2 -2
  20. package/public/assets/{agent-type-icon-LrUEU3lB.js → agent-type-icon-CY113sp-.js} +2 -2
  21. package/public/assets/{agents-B-v66C-4.js → agents-BPzY7IlQ.js} +2 -2
  22. package/public/assets/{analytics-BqCb-LKi.js → analytics-DENFPv_a.js} +2 -2
  23. package/public/assets/{artifact-viewer-kf4pGuAR.js → artifact-viewer-KRvSc7sl.js} +2 -2
  24. package/public/assets/{artifacts-DJevXcvg.js → artifacts-C9gSmL3s.js} +2 -2
  25. package/public/assets/{artifacts-D32zyw4Y.js → artifacts-DT-HkI5O.js} +2 -2
  26. package/public/assets/{automation-BpRajx_5.js → automation-BsmjQhKD.js} +2 -2
  27. package/public/assets/{backlog-D_UjTdzX.js → backlog-8NYH_62t.js} +2 -2
  28. package/public/assets/{busy-health-N3VEh4ao.js → busy-health-D3hwvYb4.js} +2 -2
  29. package/public/assets/{channels-9jopungq.js → channels-DCJ5WL7O.js} +2 -2
  30. package/public/assets/{chat-CSXrTB3C.js → chat-DAIl357U.js} +3 -3
  31. package/public/assets/{code-preview-p1NnmqK0.js → code-preview-CzrZfr9U.js} +2 -2
  32. package/public/assets/{connectors-CZdqlDNL.js → connectors-xTxtlyou.js} +2 -2
  33. package/public/assets/{cosmos-BgdsE4dG.js → cosmos-BpebFLK2.js} +2 -2
  34. package/public/assets/{crate-agent-30aXTKva.js → crate-agent-BHvI5wXC.js} +2 -2
  35. package/public/assets/{dependency-graph-B_jvw2GE.js → dependency-graph-DGT0o3wJ.js} +2 -2
  36. package/public/assets/{deploys-DoN6bAb5.js → deploys-hqWOHSHE.js} +2 -2
  37. package/public/assets/{display-BrKWFGJz.js → display-CDeH4I8T.js} +2 -2
  38. package/public/assets/{files-D6XEu8Ng.js → files-BTeGVmTg.js} +2 -2
  39. package/public/assets/{fleet-C7jQWbw2.js → fleet-DgXtNgCa.js} +2 -2
  40. package/public/assets/{fleet-metrics-BiDs3MLx.js → fleet-metrics-BHEt5yc_.js} +2 -2
  41. package/public/assets/{formatted-body-CQBwc1fb.js → formatted-body-MLtodyPp.js} +3 -3
  42. package/public/assets/{formatted-body-impl-DyG0WsQS.js → formatted-body-impl-CAe664Z8.js} +2 -2
  43. package/public/assets/{host-providers-dialog-DKaW31dN.js → host-providers-dialog-mapF4Fj9.js} +2 -2
  44. package/public/assets/{index-CRHep3M9.js → index-CqqgOI5R.js} +5 -5
  45. package/public/assets/{insights-CzrtkpOE.js → insights-DNyf7IzF.js} +2 -2
  46. package/public/assets/{integrations-aVsUmigt.js → integrations-DlYOHq7T.js} +2 -2
  47. package/public/assets/{issue-ref-chip-BnSlM_95.js → issue-ref-chip-CeWBNWn7.js} +2 -2
  48. package/public/assets/{log-viewer-qe7Ee47Z.js → log-viewer-Cib8nqdE.js} +2 -2
  49. package/public/assets/{maintenance-NfjqFZAk.js → maintenance-DSn1cPm7.js} +2 -2
  50. package/public/assets/{managed-agents-BiH0HgaE.js → managed-agents-C3CkM81m.js} +2 -2
  51. package/public/assets/{markdown-preview-BixaT3Xr.js → markdown-preview-W4VQQPAv.js} +3 -3
  52. package/public/assets/{markdown-preview-impl-DNGlWIS3.js → markdown-preview-impl-CMsTdhjq.js} +2 -2
  53. package/public/assets/{memory-DNsCl6UX.js → memory-BV17qtO4.js} +2 -2
  54. package/public/assets/{messages-DopU1L1X.js → messages-Bm_1AqSA.js} +2 -2
  55. package/public/assets/{orchestrators-Cq7ckLQT.js → orchestrators-Dg-vfP09.js} +2 -2
  56. package/public/assets/{overview-C1DCTzzV.js → overview-DcYCrvlr.js} +2 -2
  57. package/public/assets/{pairs-DE_m6rrE.js → pairs-8o9nuG3V.js} +2 -2
  58. package/public/assets/{profile-icon-B3rfh77F.js → profile-icon-BsRyBMea.js} +2 -2
  59. package/public/assets/{project-profile-instructions-DQWZE0kA.js → project-profile-instructions-DETKFn_E.js} +2 -2
  60. package/public/assets/{projects-Br2Bd2h1.js → projects-M_m6AHws.js} +2 -2
  61. package/public/assets/{prompt-settings-BKvFsvGD.js → prompt-settings-C_mNCgeL.js} +2 -2
  62. package/public/assets/provider-display-BEeLXC_D.js +2 -0
  63. package/public/assets/{registry-Cw38_6gW.js → registry-DDHHHE5N.js} +2 -2
  64. package/public/assets/run-card-Dt4I4jaP.js +2 -0
  65. package/public/assets/{secrets-DxI-vzSq.js → secrets-BkVwxI5O.js} +2 -2
  66. package/public/assets/{service-access-settings-D9s_VJbd.js → service-access-settings-Crq-6a-B.js} +2 -2
  67. package/public/assets/{settings-CKZTVGIx.js → settings-CYcavMFu.js} +2 -2
  68. package/public/assets/shipping-H6gzEeZB.js +2 -0
  69. package/public/assets/{stats-DyUBKyWI.js → stats-BJVeWkpV.js} +2 -2
  70. package/public/assets/{status-dot-DYqlEUhJ.js → status-dot-BcHyw1Vw.js} +2 -2
  71. package/public/assets/store-DKPDIZBe.js +9 -0
  72. package/public/assets/{tasks-Bh7Chr4-.js → tasks-qJjlQPpx.js} +2 -2
  73. package/public/assets/{teams-bowI7qVY.js → teams-Cd5Sk1uX.js} +2 -2
  74. package/public/assets/{terminal-viewer-BgVLHdsU.js → terminal-viewer-CpY5_m7W.js} +3 -3
  75. package/public/assets/{terminal-viewer-impl-Dz22UPy6.js → terminal-viewer-impl-DI_2llFR.js} +2 -2
  76. package/public/assets/{use-agent-terminal-DSQQ-iY7.js → use-agent-terminal-0yJA_buM.js} +2 -2
  77. package/public/assets/{use-decisions-samMQhQf.js → use-decisions-uJXHf34W.js} +2 -2
  78. package/public/assets/{use-project-list-59LheVP7.js → use-project-list-CpWl6eHQ.js} +2 -2
  79. package/public/assets/{use-selectors-BZa25xEB.js → use-selectors-BdxBW8mL.js} +2 -2
  80. package/public/assets/{user-avatar-DHxlvUvV.js → user-avatar-3IndfpOc.js} +2 -2
  81. package/public/assets/{versioned-prompt-editor-BmED5DnJ.js → versioned-prompt-editor-CIVg7XB1.js} +2 -2
  82. package/public/assets/{work-queue-CSvpuNLz.js → work-queue-B9rnLwHZ.js} +2 -2
  83. package/public/assets/{workflow-marker-DAZcj5qt.js → workflow-marker-X8wxyhzC.js} +2 -2
  84. package/public/assets/{workflows-DdmxRSyg.js → workflows-o5YyMFW7.js} +2 -2
  85. package/public/assets/{workspaces-BxeKfVuF.js → workspaces-CzEGoAng.js} +2 -2
  86. package/public/cosmos.html +4 -4
  87. package/public/index.html +28 -28
  88. package/runner/plugins/claude/.claude-plugin/plugin.json +1 -1
  89. package/runner/src/adapters/claude-headless.ts +1 -1
  90. package/runner/src/adapters/claude-launch-exit.ts +26 -0
  91. package/runner/src/adapters/claude-session-probe.ts +1 -1
  92. package/runner/src/adapters/claude-tmux.ts +38 -10
  93. package/runner/src/adapters/claude.ts +28 -75
  94. package/runner/src/adapters/provider-quota-harvest.ts +20 -71
  95. package/runner/src/agent-runner/lifecycle-milestones.ts +2 -2
  96. package/runner/src/agent-runner/liveness-exchange.ts +104 -0
  97. package/runner/src/claude-system-prompt.ts +49 -0
  98. package/runner/src/launch-assembly.ts +14 -10
  99. package/runner/src/profile-home.ts +1 -1
  100. package/runner/src/project-instructions.ts +2 -2
  101. package/runner/src/provider-command.ts +18 -0
  102. package/runner/src/provider-setup.ts +2 -1
  103. package/runner/src/runner-core.ts +14 -15
  104. package/runner/src/runner-helpers.ts +0 -8
  105. package/runner/src/session-scratch.ts +6 -2
  106. package/runner/test-support/provisioning-context.ts +4 -2
  107. package/runner/test-support/runner-harness.ts +2 -1
  108. package/scripts/steward-policy-cleanup.ts +2 -2
  109. package/src/agent-authz.ts +2 -2
  110. package/src/agent-drive-arbiter.ts +3 -8
  111. package/src/agent-exit-cleanup-async.ts +2 -2
  112. package/src/agent-exit-cleanup.ts +23 -19
  113. package/src/agent-lifecycle-events.ts +112 -85
  114. package/src/agent-ref.ts +3 -2
  115. package/src/agent-status-events.ts +43 -14
  116. package/src/auth/password.ts +4 -3
  117. package/src/auth/session-refresh.ts +1 -1
  118. package/src/auth/session.test-fixture.ts +2 -2
  119. package/src/auth/session.ts +7 -7
  120. package/src/automations/crud.ts +8 -81
  121. package/src/automations/dispatch.ts +33 -58
  122. package/src/automations/index.ts +1 -1
  123. package/src/automations/persistence.ts +65 -0
  124. package/src/automations/precheck.ts +18 -16
  125. package/src/automations/read-async.ts +11 -0
  126. package/src/automations/reconcile.ts +30 -39
  127. package/src/automations/storage.ts +33 -10
  128. package/src/automations/write-async.ts +33 -0
  129. package/src/branch-land-notice.ts +3 -3
  130. package/src/branch-landed.ts +25 -23
  131. package/src/bus.ts +21 -19
  132. package/src/cd-deploy-notify.ts +2 -2
  133. package/src/ci/notifications.ts +4 -4
  134. package/src/ci/root-missing.ts +3 -3
  135. package/src/ci/transition-notify.ts +5 -5
  136. package/src/claude-app/oauth-http.ts +3 -3
  137. package/src/cli/workflow.ts +12 -3
  138. package/src/commands-db.ts +4 -4
  139. package/src/commands-write.ts +8 -0
  140. package/src/compaction-watch.ts +10 -8
  141. package/src/config-store-write.ts +2 -2
  142. package/src/config.ts +4 -11
  143. package/src/connectors.ts +10 -8
  144. package/src/context-advisory.ts +22 -21
  145. package/src/db/agent-action-write.ts +36 -0
  146. package/src/db/agent-control-async.ts +9 -0
  147. package/src/db/agent-control-write.ts +49 -0
  148. package/src/db/agent-effort-observe.ts +22 -0
  149. package/src/db/agent-meta.ts +14 -3
  150. package/src/db/agents-async.ts +8 -5
  151. package/src/db/agents.ts +15 -12
  152. package/src/db/auth-identities.ts +2 -2
  153. package/src/db/automation-read-ops.ts +11 -0
  154. package/src/db/automation-write-ops.ts +46 -0
  155. package/src/db/blackboard.ts +2 -2
  156. package/src/db/browser-session-write.ts +32 -0
  157. package/src/db/bus-outbox-write-ops.ts +3 -1
  158. package/src/db/checkout-refresh-schema.ts +47 -0
  159. package/src/db/checkout-refresh.ts +5 -5
  160. package/src/db/command-agent-profile-backfill.ts +39 -0
  161. package/src/db/command-agent-profiles.ts +5 -1
  162. package/src/db/command-retention.ts +126 -0
  163. package/src/db/commands-planner-stats.test-fixture.ts +8 -0
  164. package/src/db/commands-read-ops.ts +16 -0
  165. package/src/db/connection.ts +1 -182
  166. package/src/db/context-carry-write.ts +74 -0
  167. package/src/db/continuations.ts +4 -4
  168. package/src/db/db-error-registry.ts +12 -0
  169. package/src/db/gateway.ts +26 -133
  170. package/src/db/history-retention-ops.ts +13 -0
  171. package/src/db/inbox-async.ts +2 -2
  172. package/src/db/inbox-read-ops.ts +1 -1
  173. package/src/db/inbox.ts +9 -3
  174. package/src/db/issue-associations.ts +2 -2
  175. package/src/db/issue-refs-async.ts +4 -1
  176. package/src/db/issue-refs-read-ops.ts +6 -0
  177. package/src/db/issue-refs.ts +8 -8
  178. package/src/db/kernel-event-log-schema.ts +2 -0
  179. package/src/db/land-boundaries.ts +51 -5
  180. package/src/db/land-gate-attestations.ts +2 -2
  181. package/src/db/landing-receipts.ts +2 -2
  182. package/src/db/lifecycle-context-write.ts +5 -0
  183. package/src/db/lifecycle-tail-async.ts +76 -0
  184. package/src/db/lifecycle-tail-write-ops.ts +62 -0
  185. package/src/db/lifecycle-write-window.ts +2 -1
  186. package/src/db/lineage-pair-dedupe-sql.ts +3 -3
  187. package/src/db/main-connection-mode.ts +11 -0
  188. package/src/db/main-connection-writes.ts +20 -2
  189. package/src/db/maintenance-job-runs-async.ts +2 -2
  190. package/src/db/maintenance-job-write-ops.ts +1 -1
  191. package/src/db/maintenance-pauses.ts +2 -2
  192. package/src/db/message-send.ts +4 -0
  193. package/src/db/messages.ts +31 -25
  194. package/src/db/migrations.ts +18 -1
  195. package/src/db/notification-write-ops.ts +19 -0
  196. package/src/db/operator-answer-write.ts +19 -0
  197. package/src/db/orchestrators.ts +10 -4
  198. package/src/db/pairs-async.ts +22 -1
  199. package/src/db/pairs-write-ops.ts +8 -3
  200. package/src/db/pairs.ts +41 -48
  201. package/src/db/plan-dispatch-schema.ts +5 -1
  202. package/src/db/plan-ready-notices.ts +43 -0
  203. package/src/db/plan-tools-include-migration.ts +36 -0
  204. package/src/db/plans-write-ops.ts +2 -0
  205. package/src/db/presence-buffer.ts +5 -0
  206. package/src/db/presence-loop.ts +18 -23
  207. package/src/db/presence-write.ts +0 -6
  208. package/src/db/project-entries-async.ts +14 -0
  209. package/src/db/project-entries-read-ops.ts +5 -1
  210. package/src/db/project-entries-write-ops.ts +8 -0
  211. package/src/db/project-entries.ts +15 -12
  212. package/src/db/project-journal.ts +2 -2
  213. package/src/db/project-trust-approvals.ts +2 -2
  214. package/src/db/prompt-templates.ts +4 -4
  215. package/src/db/read-op-marshaling-fixtures.ts +18 -0
  216. package/src/db/read-ops.ts +4 -0
  217. package/src/db/retention.ts +13 -2
  218. package/src/db/review-publish-write.ts +8 -0
  219. package/src/db/runtime-admin-async.ts +19 -0
  220. package/src/db/runtime-admin-write-ops.ts +36 -0
  221. package/src/db/runtime-tail-async.ts +288 -0
  222. package/src/db/runtime-tail-write-ops.ts +97 -0
  223. package/src/db/schema.ts +4 -1
  224. package/src/db/spawn-budget.ts +5 -0
  225. package/src/db/spawn-delegation.ts +1 -11
  226. package/src/db/sql-audit-indexes.ts +3 -0
  227. package/src/db/stats-async.ts +5 -3
  228. package/src/db/task-entity.ts +5 -4
  229. package/src/db/team-claim-policy.ts +60 -0
  230. package/src/db/teams-async.ts +4 -0
  231. package/src/db/teams-write-ops.ts +4 -0
  232. package/src/db/terminal-events.ts +15 -0
  233. package/src/db/timer-cutover-bodies.ts +292 -0
  234. package/src/db/timer-cutover-core.ts +37 -278
  235. package/src/db/timer-cutover-state.ts +40 -0
  236. package/src/db/timer-cutover-write-ops.ts +1 -40
  237. package/src/db/token-db-write-ops.ts +2 -0
  238. package/src/db/token-standing-async.ts +12 -0
  239. package/src/db/token-standing.ts +6 -9
  240. package/src/db/token-usage-retention.ts +33 -0
  241. package/src/db/token-usage-write-ops.ts +1 -1
  242. package/src/db/token-usage.ts +4 -1
  243. package/src/db/user-invites-async.ts +13 -0
  244. package/src/db/user-invites.ts +3 -3
  245. package/src/db/users.ts +14 -6
  246. package/src/db/workspaces.ts +8 -5
  247. package/src/db/write-ops.ts +31 -12
  248. package/src/db/writer-fault-seam.ts +2 -9
  249. package/src/db/writer-fault-test-ops.ts +18 -0
  250. package/src/db/writer-gateway.ts +42 -46
  251. package/src/dev.ts +8 -3
  252. package/src/event-loop-lag.ts +4 -2
  253. package/src/event-loop-yield.ts +2 -2
  254. package/src/events.ts +10 -8
  255. package/src/host-provider-store.ts +5 -4
  256. package/src/hub-metrics-report.ts +5 -1
  257. package/src/hub-metrics.ts +38 -18
  258. package/src/injection-sweep-pacing.ts +6 -8
  259. package/src/land-gate-notifications.ts +11 -10
  260. package/src/land-gate-queue-progress.ts +3 -3
  261. package/src/land-settle-dispatch.ts +52 -92
  262. package/src/lifecycle-context-pressure.ts +11 -10
  263. package/src/lifecycle-manager.ts +104 -98
  264. package/src/maintenance/breaker.ts +18 -16
  265. package/src/maintenance/checkout-refresh.ts +18 -1
  266. package/src/maintenance/critical-job-watchdog.ts +3 -3
  267. package/src/maintenance/events.ts +3 -3
  268. package/src/maintenance/history-retention-job.ts +30 -0
  269. package/src/maintenance/jobs.ts +29 -67
  270. package/src/maintenance/long-running-work.ts +3 -1
  271. package/src/maintenance/offline-agent-prune-job.ts +28 -0
  272. package/src/maintenance/orphaned-sessions.ts +5 -4
  273. package/src/maintenance/pauses.ts +4 -10
  274. package/src/maintenance/plan-ready-notice-job.ts +71 -0
  275. package/src/maintenance/scheduler.ts +18 -13
  276. package/src/maintenance/stalled-delivery-pager.ts +4 -4
  277. package/src/maintenance/teams.ts +19 -30
  278. package/src/maintenance/workspaces.ts +17 -27
  279. package/src/managed-policy-escalation.ts +7 -7
  280. package/src/managed-policy.ts +3 -3
  281. package/src/managed-provider-gate.ts +5 -4
  282. package/src/managed-spawn-reservation.ts +5 -4
  283. package/src/mcp/index.ts +8 -8
  284. package/src/mcp/output-descriptions.ts +1 -1
  285. package/src/mcp/presentation.ts +2 -1
  286. package/src/mcp/runtime-output.ts +6 -1
  287. package/src/mcp/self-care-visibility.ts +2 -2
  288. package/src/mcp/spawn-targets-matrix.ts +10 -1
  289. package/src/mcp/tool-groups.ts +3 -2
  290. package/src/mcp/tool-roles.ts +81 -0
  291. package/src/mcp/tools-workspace.ts +1 -1
  292. package/src/mcp-blackboard-tools.ts +6 -5
  293. package/src/mcp-decision-tools.ts +7 -7
  294. package/src/mcp-egress-tools.ts +10 -9
  295. package/src/mcp-issue-activity-tools.ts +2 -2
  296. package/src/mcp-issue-association-tools.ts +4 -4
  297. package/src/mcp-issue-ref-tools.ts +3 -3
  298. package/src/mcp-merge-tools.ts +5 -5
  299. package/src/mcp-notification-subscription-tools.ts +14 -15
  300. package/src/mcp-output-levels.test-fixture.ts +1 -1
  301. package/src/mcp-profile-trust-tools.ts +1 -1
  302. package/src/mcp-project-journal-tools.ts +4 -12
  303. package/src/mcp-project-tools.ts +17 -18
  304. package/src/mcp-routing-policy-tools.ts +3 -2
  305. package/src/mcp-team-tools.ts +6 -6
  306. package/src/mcp-workflow-tools.ts +5 -3
  307. package/src/memory-area-injection.ts +13 -12
  308. package/src/memory-boundary-capture.ts +6 -6
  309. package/src/memory-injection.ts +45 -43
  310. package/src/merge-pause-maintenance.ts +5 -4
  311. package/src/notification-router.ts +44 -17
  312. package/src/notify.ts +5 -5
  313. package/src/observed-reset-refresh.ts +4 -4
  314. package/src/operator-alarm.ts +2 -5
  315. package/src/project-ingest.ts +6 -6
  316. package/src/project-instructions.ts +8 -1
  317. package/src/project-trust.ts +12 -11
  318. package/src/prompt-resolver.ts +3 -3
  319. package/src/provider-quota-async.ts +5 -7
  320. package/src/provider-timeline-event.ts +4 -7
  321. package/src/quota-band-delivery.ts +2 -2
  322. package/src/recipe-runner.ts +4 -3
  323. package/src/retention-config-store.ts +4 -3
  324. package/src/reviewer-pipeline.ts +11 -10
  325. package/src/routes/_route.ts +4 -4
  326. package/src/routes/_shared.ts +13 -12
  327. package/src/routes/agent-effort.ts +5 -5
  328. package/src/routes/agent-heartbeat-quota.ts +23 -0
  329. package/src/routes/agent-keep.ts +1 -1
  330. package/src/routes/agent-reporting.ts +1 -1
  331. package/src/routes/agent-self-effort.ts +5 -5
  332. package/src/routes/agent-sessions.ts +24 -24
  333. package/src/routes/agents-spawn.ts +1 -1
  334. package/src/routes/agents.ts +17 -119
  335. package/src/routes/auth.ts +14 -15
  336. package/src/routes/authority-audit.ts +2 -2
  337. package/src/routes/automations.ts +6 -5
  338. package/src/routes/channel-binding-delete.ts +1 -1
  339. package/src/routes/channel-output.ts +3 -3
  340. package/src/routes/claude-api-budget.ts +5 -5
  341. package/src/routes/commands-listing.ts +1 -1
  342. package/src/routes/commands.ts +29 -27
  343. package/src/routes/context-resets.ts +2 -2
  344. package/src/routes/coordinator-teams.ts +1 -1
  345. package/src/routes/decisions.ts +2 -2
  346. package/src/routes/host-secret-files.ts +1 -1
  347. package/src/routes/hosts.ts +11 -11
  348. package/src/routes/inbox.ts +3 -2
  349. package/src/routes/index.ts +2 -1
  350. package/src/routes/integrations.ts +9 -7
  351. package/src/routes/issue-activity.ts +2 -2
  352. package/src/routes/issue-associations.ts +4 -4
  353. package/src/routes/issue-refs.ts +3 -3
  354. package/src/routes/kernel-timers-operator.ts +1 -1
  355. package/src/routes/memory.ts +1 -1
  356. package/src/routes/merges.ts +11 -11
  357. package/src/routes/messages.ts +23 -17
  358. package/src/routes/orchestrator-bootstrap.ts +1 -1
  359. package/src/routes/orchestrator-host-registration.ts +2 -0
  360. package/src/routes/orchestrator-host-utilization.ts +2 -2
  361. package/src/routes/orchestrator-proxy.ts +2 -2
  362. package/src/routes/orchestrator.ts +27 -18
  363. package/src/routes/pairs.ts +8 -8
  364. package/src/routes/project-profile-instructions.ts +1 -1
  365. package/src/routes/project-trust.ts +1 -1
  366. package/src/routes/projects.ts +16 -16
  367. package/src/routes/prompt-templates.ts +12 -12
  368. package/src/routes/provider-quotas.ts +10 -10
  369. package/src/routes/run-command.ts +1 -1
  370. package/src/routes/secrets-backup.ts +2 -2
  371. package/src/routes/secrets-import.ts +1 -1
  372. package/src/routes/secrets-vault.ts +7 -7
  373. package/src/routes/settings.ts +1 -1
  374. package/src/routes/shipping.ts +9 -1
  375. package/src/routes/spawn-policy.ts +8 -7
  376. package/src/routes/sse.ts +2 -2
  377. package/src/routes/team-templates.ts +7 -7
  378. package/src/routes/teams.ts +10 -10
  379. package/src/routes/tokens.ts +18 -17
  380. package/src/routes/users.ts +24 -24
  381. package/src/routes/viewer-read.ts +3 -3
  382. package/src/routes/web-tool-calls.ts +4 -4
  383. package/src/routes/workflows.ts +2 -2
  384. package/src/routes/workspaces.ts +17 -16
  385. package/src/routing-policy-push.ts +6 -6
  386. package/src/runtime-grant.ts +17 -11
  387. package/src/runtime-tokens.ts +20 -28
  388. package/src/secrets-broker/config.ts +1 -1
  389. package/src/secrets-broker/service.ts +2 -2
  390. package/src/security.ts +3 -1
  391. package/src/self-resume-carry.ts +21 -110
  392. package/src/self-resume-supersede.ts +4 -3
  393. package/src/self-resume.ts +54 -66
  394. package/src/server-background.ts +1 -1
  395. package/src/server.ts +21 -22
  396. package/src/services/agent-action.ts +12 -31
  397. package/src/services/agent-effort.ts +7 -32
  398. package/src/services/agent-hang-watchdog-job.ts +2 -2
  399. package/src/services/agent-heartbeat.ts +166 -0
  400. package/src/services/agent-reporting.ts +1 -1
  401. package/src/services/browser-inventory.ts +3 -3
  402. package/src/services/browser-session-state.ts +3 -2
  403. package/src/services/browser-session.ts +21 -27
  404. package/src/services/bus-command-update.ts +9 -9
  405. package/src/services/checkout-refresh-state.ts +1 -0
  406. package/src/services/checkout-refresh.test-fixture.ts +3 -2
  407. package/src/services/command-host.ts +7 -2
  408. package/src/services/coordinator-digest.ts +3 -2
  409. package/src/services/dispatch-command.ts +12 -13
  410. package/src/services/effective-launch-driver.ts +14 -9
  411. package/src/services/host-changed-paths.test-fixture.ts +5 -6
  412. package/src/services/host-project-readiness.ts +2 -2
  413. package/src/services/host-providers.ts +1 -1
  414. package/src/services/host-utilization.ts +9 -9
  415. package/src/services/idle-agent-digest.ts +17 -12
  416. package/src/services/idle-epics.ts +4 -4
  417. package/src/services/interrupt-agent.ts +12 -9
  418. package/src/services/issue-activity.ts +3 -3
  419. package/src/services/issue-associations.ts +15 -18
  420. package/src/services/issue-lifecycle-comments.ts +3 -2
  421. package/src/services/issue-lifecycle.ts +14 -13
  422. package/src/services/issue-refs.ts +4 -5
  423. package/src/services/kernel-timers-cutover-chaos-worker.ts +1 -1
  424. package/src/services/kernel-timers-ingress-chaos-worker.ts +6 -1
  425. package/src/services/kernel-timers-ingress.ts +3 -3
  426. package/src/services/kernel-timers-message-authority-chaos-worker.ts +3 -10
  427. package/src/services/kernel-timers-operator-chaos-worker.ts +3 -2
  428. package/src/services/kernel-timers-outbox.ts +1 -1
  429. package/src/services/kernel-timers-stage10-operator.ts +1 -1
  430. package/src/services/kernel-timers.ts +4 -3
  431. package/src/services/land-gate-placement.ts +11 -2
  432. package/src/services/land-issue-relations.ts +5 -4
  433. package/src/services/land-restart-retry.ts +7 -6
  434. package/src/services/landed-ancestry.ts +6 -5
  435. package/src/services/legacy-landing-host.ts +4 -1
  436. package/src/services/legacy-landing-recovery.ts +8 -6
  437. package/src/services/managed-running.ts +17 -17
  438. package/src/services/memory-areas.ts +3 -3
  439. package/src/services/merge-pause.ts +9 -15
  440. package/src/services/operator-decisions.ts +37 -41
  441. package/src/services/orchestrator-registration-recovery.ts +6 -2
  442. package/src/services/orchestrator-upgrade.ts +14 -12
  443. package/src/services/permission-decision.ts +4 -3
  444. package/src/services/plan-ci-notice.ts +1 -1
  445. package/src/services/plan-conformance.ts +4 -4
  446. package/src/services/plan-dispatch-policy.ts +10 -3
  447. package/src/services/plan-dispatch.ts +35 -14
  448. package/src/services/plan-graph.ts +39 -261
  449. package/src/services/plan-import.ts +1 -1
  450. package/src/services/plan-live-bindings.ts +8 -37
  451. package/src/services/plan-task-create.ts +1 -1
  452. package/src/services/plan-terminal-refs.ts +2 -12
  453. package/src/services/plan-wake-digest.ts +8 -3
  454. package/src/services/profile-trust.ts +2 -2
  455. package/src/services/project.ts +11 -10
  456. package/src/services/prompt-templates.ts +6 -5
  457. package/src/services/provider-hold-nudge.ts +9 -9
  458. package/src/services/provider-hold-record.ts +13 -12
  459. package/src/services/provider-state-audit.ts +19 -18
  460. package/src/services/provisioning-harness/driver.ts +1 -1
  461. package/src/services/provisioning-harness/fixture-matrix.ts +3 -3
  462. package/src/services/provisioning-harness/lifecycle.ts +24 -22
  463. package/src/services/provisioning-harness/prepare.ts +16 -17
  464. package/src/services/rate-limit-resume.ts +10 -9
  465. package/src/services/red-main-auto-revert.ts +7 -5
  466. package/src/services/red-main-controller-deps.ts +20 -3
  467. package/src/services/red-main-incident.ts +3 -1
  468. package/src/services/red-main-quarantine.ts +2 -3
  469. package/src/services/register-agent.ts +22 -22
  470. package/src/services/restart-project-role.ts +1 -1
  471. package/src/services/restart-spawn.ts +17 -16
  472. package/src/services/review-publish.ts +7 -24
  473. package/src/services/review-ref-reconcile.ts +29 -38
  474. package/src/services/runtime-dependency-alert.ts +27 -0
  475. package/src/services/scheduler-command.ts +11 -9
  476. package/src/services/scheduler.ts +5 -5
  477. package/src/services/self-effort-grant.ts +5 -25
  478. package/src/services/send-message.ts +16 -33
  479. package/src/services/shutdown-agent.ts +17 -14
  480. package/src/services/spawn-agent.ts +12 -41
  481. package/src/services/spawn-chain-inspector.ts +1 -1
  482. package/src/services/spawn-cwd-placement.ts +21 -5
  483. package/src/services/spawn-issue-activity.ts +8 -6
  484. package/src/services/spawn-launch.ts +17 -1
  485. package/src/services/spawn-loss-escalation.ts +1 -1
  486. package/src/services/spawn-registration-watchdog.ts +2 -2
  487. package/src/services/spawn-repo-probe.ts +7 -5
  488. package/src/services/spawn-tree-cleanup.ts +7 -8
  489. package/src/services/steward-reaper.ts +4 -4
  490. package/src/services/task-context-hydration.ts +25 -1
  491. package/src/services/task-delivery.ts +20 -34
  492. package/src/services/task-entity.ts +40 -203
  493. package/src/services/task-issue-link.ts +13 -20
  494. package/src/services/task-issue-weave.ts +12 -11
  495. package/src/services/task-lifecycle.ts +27 -31
  496. package/src/services/task-repo-cwd.ts +37 -0
  497. package/src/services/task-review-hold.ts +4 -17
  498. package/src/services/task-semantic-events.ts +13 -11
  499. package/src/services/task-state-reconcile.ts +3 -4
  500. package/src/services/task-validation.ts +2 -2
  501. package/src/services/team-templates.ts +5 -5
  502. package/src/services/team.ts +31 -98
  503. package/src/services/terminal-provider-exit.ts +6 -5
  504. package/src/services/timer-message-fire-chaos-worker.ts +7 -3
  505. package/src/services/timer-message-fire-outbox.ts +1 -1
  506. package/src/services/transient-agent-reaper.ts +22 -19
  507. package/src/services/turn-reconcile-audit.ts +9 -9
  508. package/src/services/user-onboarding.ts +8 -6
  509. package/src/services/user-profile.ts +6 -9
  510. package/src/services/work-finality.ts +16 -4
  511. package/src/services/workflow-auto-start.ts +2 -1
  512. package/src/services/workflow-caller.ts +61 -4
  513. package/src/services/workflow-child-proof-worker.ts +1 -1
  514. package/src/services/workflow-control-proof-worker.ts +1 -1
  515. package/src/services/workflow-coordinator.ts +8 -2
  516. package/src/services/workflow-decisions.ts +5 -5
  517. package/src/services/workflow-deferred-cleanup.ts +1 -1
  518. package/src/services/workflow-effect-driver.ts +42 -19
  519. package/src/services/workflow-effects.ts +41 -27
  520. package/src/services/workflow-pins.ts +7 -4
  521. package/src/services/workflow-reconcile.ts +1 -0
  522. package/src/services/workflow-resume-pins.ts +3 -3
  523. package/src/services/workflow-routing-defaults.ts +18 -0
  524. package/src/services/workflow-run-view.ts +4 -3
  525. package/src/services/workflow-run.test-fixture.ts +1 -1
  526. package/src/services/workflow-runs.ts +34 -22
  527. package/src/services/workflow-supervisor-state.ts +21 -1
  528. package/src/services/workflow-supervisor.ts +4 -3
  529. package/src/services/workspace-recovery.ts +4 -3
  530. package/src/spawn-admission.ts +10 -2
  531. package/src/spawn-placement.ts +27 -9
  532. package/src/spawn-targets.ts +8 -8
  533. package/src/sse-agent-status.ts +15 -6
  534. package/src/sse.ts +49 -24
  535. package/src/startup-context-injection.ts +61 -57
  536. package/src/steward-handoff.ts +4 -3
  537. package/src/steward-policy-cleanup.ts +9 -8
  538. package/src/steward.ts +2 -2
  539. package/src/task-dispatch-live.ts +5 -2
  540. package/src/task-dispatcher.ts +2 -2
  541. package/src/token-db.ts +2 -2
  542. package/src/token-usage-async.ts +2 -4
  543. package/src/user-awaiting-reply.ts +5 -5
  544. package/src/workspace-actions.ts +24 -29
  545. package/src/workspace-auto-merge.ts +14 -12
  546. package/src/workspace-changed-paths-client.ts +16 -5
  547. package/src/workspace-cleanup-stale.ts +30 -7
  548. package/src/workspace-command-settle.ts +24 -16
  549. package/src/workspace-config.ts +1 -1
  550. package/src/workspace-conflict-resolver.ts +9 -7
  551. package/src/workspace-dead-owner-recovery.ts +5 -4
  552. package/src/workspace-escalation.ts +12 -11
  553. package/src/workspace-human-escalation.ts +3 -3
  554. package/src/workspace-land-gate-watchdog.ts +3 -3
  555. package/src/workspace-land-refusal.ts +20 -0
  556. package/src/workspace-land-stopped.ts +1 -0
  557. package/src/workspace-merge.ts +10 -26
  558. package/src/workspace-nothing-to-land-notice.ts +2 -2
  559. package/src/workspace-orphans.ts +18 -37
  560. package/src/workspace-pr-completion.ts +10 -8
  561. package/src/workspace-rekey.ts +3 -3
  562. package/src/workspace-steward-failure.ts +2 -2
  563. package/src/workspace-steward-wake.ts +2 -2
  564. package/public/assets/agent-liveness-CaNwK0Zf.js +0 -2
  565. package/public/assets/provider-display-DsuWMvVL.js +0 -2
  566. package/public/assets/run-card-CpKqRbhg.js +0 -2
  567. package/public/assets/shipping-B9YtnwtG.js +0 -2
  568. package/public/assets/store-DcClXLH0.js +0 -9
  569. package/runner/src/agent-runner/http-liveness.ts +0 -85
  570. package/src/db/on-loop-write-transaction.ts +0 -34
@@ -60,14 +60,14 @@ export const getMe: Handler = (req) => {
60
60
  return user ? json({ ...user, hasPassword: hasPasswordLogin(user.id) } satisfies CurrentUser) : error("user not found", 404);
61
61
  };
62
62
 
63
- export const patchMe: Handler = (req) => patchUserProfile(req, { id: requestUserId(req) });
63
+ export const patchMe: Handler = async (req) => (await patchUserProfile(req, { id: requestUserId(req) }));
64
64
 
65
- export const patchUserById: Handler = (req, params) => patchUserProfile(req, params);
65
+ export const patchUserById: Handler = async (req, params) => (await patchUserProfile(req, params));
66
66
 
67
67
  async function patchUserProfile(req: Request, params: Record<string, string>): Promise<Response> {
68
68
  try {
69
69
  const userId = userIdParam(req, params);
70
- if (!canManageUser(req, userId)) return authorityDenied(req, "forbidden");
70
+ if (!canManageUser(req, userId)) return (await authorityDenied(req, "forbidden"));
71
71
  const parsed = await parseBody<unknown>(req);
72
72
  if (!parsed.ok) return error(parsed.error, parsed.status);
73
73
  if (!isRecord(parsed.body)) return error("body must be an object");
@@ -76,14 +76,14 @@ async function patchUserProfile(req: Request, params: Record<string, string>): P
76
76
  const role = optionalEnum(body.role, "role", INVITE_ROLES);
77
77
  const status = optionalEnum(body.status, "status", ACCOUNT_STATUSES);
78
78
  if (role || status) {
79
- if (!isAdminRequest(req)) return authorityDenied(req, "only an admin can change a user's role or status");
79
+ if (!isAdminRequest(req)) return (await authorityDenied(req, "only an admin can change a user's role or status"));
80
80
  const before = getUser(userId);
81
81
  const after = await changeUserAccess({ actorUserId: requestUserId(req), userId, role, status });
82
- if (before && after.role !== before.role) recordUserManagement(req, userId, "change role", { from: before.role, to: after.role });
83
- if (before && after.status !== before.status) recordUserManagement(req, userId, after.status === "suspended" ? "suspend" : "reactivate");
82
+ if (before && after.role !== before.role) (await recordUserManagement(req, userId, "change role", { from: before.role, to: after.role }));
83
+ if (before && after.status !== before.status) (await recordUserManagement(req, userId, after.status === "suspended" ? "suspend" : "reactivate"));
84
84
  }
85
85
  const displayName = cleanString(body.displayName, "displayName", { required: !role && !status, max: 120 });
86
- if (displayName) return json(updateUserDisplayName({ userId, displayName }));
86
+ if (displayName) return json((await updateUserDisplayName({ userId, displayName })));
87
87
  const user = userOr404(userId);
88
88
  return user instanceof Response ? user : json(user);
89
89
  } catch (e) {
@@ -91,9 +91,9 @@ async function patchUserProfile(req: Request, params: Record<string, string>): P
91
91
  }
92
92
  }
93
93
 
94
- export const postMyAvatar: Handler = (req) => postUserAvatar(req, { id: requestUserId(req) });
94
+ export const postMyAvatar: Handler = async (req) => (await postUserAvatar(req, { id: requestUserId(req) }));
95
95
 
96
- export const postUserAvatarById: Handler = (req, params) => postUserAvatar(req, params);
96
+ export const postUserAvatarById: Handler = async (req, params) => (await postUserAvatar(req, params));
97
97
 
98
98
  async function avatarUploadInput(req: Request): Promise<{ bytes: Uint8Array; filename?: string; mediaType: string; digest?: string }> {
99
99
  const contentType = req.headers.get("content-type") ?? "";
@@ -123,7 +123,7 @@ async function postUserAvatar(req: Request, params: Record<string, string>): Pro
123
123
  const userId = userIdParam(req, params);
124
124
  const user = userOr404(userId);
125
125
  if (user instanceof Response) return user;
126
- if (!canManageUser(req, userId)) return authorityDenied(req, "forbidden");
126
+ if (!canManageUser(req, userId)) return (await authorityDenied(req, "forbidden"));
127
127
  const input = await avatarUploadInput(req);
128
128
  const result = await storeUserAvatar({ userId, actor: requestUserAddress(req), ...input });
129
129
  return json(result, 201);
@@ -132,25 +132,25 @@ async function postUserAvatar(req: Request, params: Record<string, string>): Pro
132
132
  }
133
133
  }
134
134
 
135
- export const deleteMyAvatar: Handler = (req) => deleteUserAvatar(req, { id: requestUserId(req) });
135
+ export const deleteMyAvatar: Handler = async (req) => (await deleteUserAvatar(req, { id: requestUserId(req) }));
136
136
 
137
- export const deleteUserAvatarById: Handler = (req, params) => deleteUserAvatar(req, params);
137
+ export const deleteUserAvatarById: Handler = async (req, params) => (await deleteUserAvatar(req, params));
138
138
 
139
139
  async function deleteUserAvatar(req: Request, params: Record<string, string>): Promise<Response> {
140
140
  try {
141
141
  const userId = userIdParam(req, params);
142
142
  const user = userOr404(userId);
143
143
  if (user instanceof Response) return user;
144
- if (!canManageUser(req, userId)) return authorityDenied(req, "forbidden");
144
+ if (!canManageUser(req, userId)) return (await authorityDenied(req, "forbidden"));
145
145
  return json(await removeUserAvatar({ userId }));
146
146
  } catch (e) {
147
147
  return profileError(e);
148
148
  }
149
149
  }
150
150
 
151
- export const getUserAvatar: Handler = (req, params) => serveUserAvatar(req, params, false);
151
+ export const getUserAvatar: Handler = async (req, params) => (await serveUserAvatar(req, params, false));
152
152
 
153
- export const headUserAvatar: Handler = (req, params) => serveUserAvatar(req, params, true);
153
+ export const headUserAvatar: Handler = async (req, params) => (await serveUserAvatar(req, params, true));
154
154
 
155
155
  async function serveUserAvatar(req: Request, params: Record<string, string>, head: boolean): Promise<Response> {
156
156
  const userId = userIdParam(req, params);
@@ -158,7 +158,7 @@ async function serveUserAvatar(req: Request, params: Record<string, string>, hea
158
158
  if (!user?.avatarArtifactId) return error("avatar not found", 404);
159
159
  const artifact = await getArtifactAsync(user.avatarArtifactId);
160
160
  if (!artifact) return error("avatar artifact not found", 404);
161
- if (!canReadAvatar(req, userId, artifact)) return authorityDenied(req, "forbidden");
161
+ if (!canReadAvatar(req, userId, artifact)) return (await authorityDenied(req, "forbidden"));
162
162
  const blob = await getArtifactBlobAsync(artifact.blobDigest);
163
163
  if (!blob) return error("avatar blob not found", 404);
164
164
  const headers = {
@@ -183,15 +183,15 @@ export const invitesDisabled = () => error("user invites are disabled", 404);
183
183
  /** POST /api/users/invites — admin only. The code is shown once; share `/invite/<code>` by hand. */
184
184
  export const postUserInvite: Handler = async (req) => {
185
185
  if (!userInvitesEnabled()) return invitesDisabled();
186
- if (!isAdminRequest(req)) return authorityDenied(req, "only an admin can invite users");
186
+ if (!isAdminRequest(req)) return (await authorityDenied(req, "only an admin can invite users"));
187
187
  const parsed = await parseBody<unknown>(req);
188
188
  if (!parsed.ok) return error(parsed.error, parsed.status);
189
189
  const body = isRecord(parsed.body) ? parsed.body : {};
190
190
  try {
191
191
  const role = optionalEnum(body.role, "role", INVITE_ROLES) ?? "operator";
192
192
  const displayName = cleanString(body.displayName, "displayName", { max: 120 });
193
- const { invite, code } = createInvite({ role, displayName, createdBy: requestUserId(req) });
194
- recordUserManagement(req, `invite:${invite.id}`, "create invite", { role, ...(displayName ? { displayName } : {}) });
193
+ const { invite, code } = (await createInvite({ role, displayName, createdBy: requestUserId(req) }));
194
+ (await recordUserManagement(req, `invite:${invite.id}`, "create invite", { role, ...(displayName ? { displayName } : {}) }));
195
195
  return json({ invite, code, path: `/invite/${code}` }, 201);
196
196
  } catch (e) {
197
197
  if (e instanceof ValidationError) return error(e.message, 400);
@@ -202,7 +202,7 @@ export const postUserInvite: Handler = async (req) => {
202
202
  /** PUT /api/users/me/password — a signed-in user, or the admin token for the default admin. */
203
203
  export const putMyPassword: Handler = async (req) => {
204
204
  const acting = resolveActingUser(req);
205
- if (!acting) return authorityDenied(req, "sign in to set a password");
205
+ if (!acting) return (await authorityDenied(req, "sign in to set a password"));
206
206
  const parsed = await parseBody<unknown>(req);
207
207
  if (!parsed.ok) return error(parsed.error, parsed.status);
208
208
  if (!isRecord(parsed.body)) return error("password required");
@@ -219,11 +219,11 @@ export const putMyPassword: Handler = async (req) => {
219
219
 
220
220
  /** DELETE /api/users/:id/sessions — sign the user out everywhere; their next call gets 401. */
221
221
  export const deleteUserSessions: Handler = async (req, params) => {
222
- if (!isAdminRequest(req)) return authorityDenied(req, "only an admin can sign a user out");
222
+ if (!isAdminRequest(req)) return (await authorityDenied(req, "only an admin can sign a user out"));
223
223
  try {
224
224
  const userId = decodeURIComponent(params.id!);
225
225
  const revoked = await revokeUserSessions(userId);
226
- recordUserManagement(req, userId, "revoke sessions", { revoked });
226
+ (await recordUserManagement(req, userId, "revoke sessions", { revoked }));
227
227
  return json({ revoked });
228
228
  } catch (e) {
229
229
  return profileError(e);
@@ -232,7 +232,7 @@ export const deleteUserSessions: Handler = async (req, params) => {
232
232
 
233
233
  /** POST /api/users/:id/handover — every agent of the user gets `toUserId` as its principal. */
234
234
  export const postUserHandover: Handler = async (req, params) => {
235
- if (!isAdminRequest(req)) return authorityDenied(req, "only an admin can hand over agents");
235
+ if (!isAdminRequest(req)) return (await authorityDenied(req, "only an admin can hand over agents"));
236
236
  try {
237
237
  const parsed = await parseBody<unknown>(req);
238
238
  if (!parsed.ok) return error(parsed.error, parsed.status);
@@ -240,7 +240,7 @@ export const postUserHandover: Handler = async (req, params) => {
240
240
  const toUserId = cleanString(body.toUserId, "toUserId", { required: true, max: 64 })!;
241
241
  const fromUserId = decodeURIComponent(params.id!);
242
242
  const agentIds = await handOverAgents({ fromUserId, toUserId });
243
- recordUserManagement(req, fromUserId, "hand over agents", { toUserId, agentIds });
243
+ (await recordUserManagement(req, fromUserId, "hand over agents", { toUserId, agentIds }));
244
244
  return json({ toUserId, agentIds });
245
245
  } catch (e) {
246
246
  return profileError(e);
@@ -12,15 +12,15 @@ export function scopeResourceReads(method: string, path: string, handler: Handle
12
12
  const viewer = viewerScopeForRequest(req);
13
13
  if (!viewer || viewer.seesAll) return handler(req, params);
14
14
  const id = params.id!;
15
- if (resource === "agents" && !agentVisibleTo(viewer, id)) return authorityDenied(req, "agent not found", 404, id, "read agent");
15
+ if (resource === "agents" && !agentVisibleTo(viewer, id)) return (await authorityDenied(req, "agent not found", 404, id, "read agent"));
16
16
  if (resource === "tasks") {
17
17
  const taskId = parseId(id);
18
- if (taskId !== null && !taskVisibleTo(viewer, { id: taskId })) return authorityDenied(req, "task not found", 404, id, "read task");
18
+ if (taskId !== null && !taskVisibleTo(viewer, { id: taskId })) return (await authorityDenied(req, "task not found", 404, id, "read task"));
19
19
  }
20
20
  if (resource === "messages") {
21
21
  const messageId = parseId(id);
22
22
  const message = messageId === null ? null : await getMessageAsync(messageId);
23
- if (message && !messageVisibleTo(viewer, message)) return authorityDenied(req, "you can only read your own messages", 403, id, path.endsWith("/thread") ? "read thread" : "read message");
23
+ if (message && !messageVisibleTo(viewer, message)) return (await authorityDenied(req, "you can only read your own messages", 403, id, path.endsWith("/thread") ? "read thread" : "read message"));
24
24
  }
25
25
  return handler(req, params);
26
26
  };
@@ -5,27 +5,27 @@ import { ingestWebToolCall, queryWebSearchBudget, queryWebToolCalls, queryWebToo
5
5
  import { authorizeRoute, error, json, parseBody, type Handler } from "./_shared";
6
6
 
7
7
  export const getWebToolCalls: Handler = async req => {
8
- const denied = authorizeRoute(req, { scope: "agent:read" });
8
+ const denied = (await authorizeRoute(req, { scope: "agent:read" }));
9
9
  if (denied) return denied;
10
10
  try { return json(await queryWebToolCalls(Object.fromEntries(new URL(req.url).searchParams))); }
11
11
  catch (e) { if (e instanceof ValidationError) return error(e.message, 400); throw e; }
12
12
  };
13
13
 
14
14
  export const getWebToolCallSummary: Handler = async req => {
15
- const denied = authorizeRoute(req, { scope: "agent:read" });
15
+ const denied = (await authorizeRoute(req, { scope: "agent:read" }));
16
16
  if (denied) return denied;
17
17
  try { return json(await queryWebToolCallSummary(Object.fromEntries(new URL(req.url).searchParams))); }
18
18
  catch (e) { if (e instanceof ValidationError) return error(e.message, 400); throw e; }
19
19
  };
20
20
 
21
21
  export const getWebSearchBudget: Handler = async req => {
22
- const denied = authorizeRoute(req, { scope: "agent:read" });
22
+ const denied = (await authorizeRoute(req, { scope: "agent:read" }));
23
23
  if (denied) return denied;
24
24
  return json(await queryWebSearchBudget());
25
25
  };
26
26
 
27
27
  export const postWebToolCall: Handler = async req => {
28
- const denied = authorizeRoute(req, { scope: "agent:write" });
28
+ const denied = (await authorizeRoute(req, { scope: "agent:write" }));
29
29
  if (denied) return denied;
30
30
  const body = await parseBody<unknown>(req);
31
31
  if (!body.ok) return error(body.error, body.status);
@@ -9,6 +9,7 @@ import { McpNotFoundError } from "../mcp-errors";
9
9
  import { authContextFromRequest } from "../services/auth-context";
10
10
  import { ServiceAuthError } from "../services/errors";
11
11
  import { readWorkflowCatalog } from "../services/workflow-catalog";
12
+ import { parseWorkflowPins } from "../services/workflow-pins";
12
13
  import { requireSpawnBudget } from "../spawn-budget";
13
14
  import { amendWorkflowRunBudget, cancelWorkflowRun, getWorkflowRun, pauseWorkflowRun, readWorkflowRunBudget, listWorkflowRuns, resumeWorkflowRun, startWorkflowRun } from "../services/workflow-runs";
14
15
  import { workflowDefinitions, workflowStats } from "../services/workflow-stats";
@@ -123,7 +124,7 @@ export const postWorkflowRunResumeRoute: Handler = async (req, params) => {
123
124
  if (!parsed.ok) return error(parsed.error, parsed.status);
124
125
  if (!isRecord(parsed.body)) return error("body must be a JSON object", 400);
125
126
  try {
126
- return json(await resumeWorkflowRun({ runId: params.runId!, decision: parseWorkflowDecisionResponse(parsed.body.decision), activationId: cleanString(parsed.body.activationId, "activationId", { max: 2000 }), note: cleanString(parsed.body.note, "note", { max: 8000 }) }, authContextFromRequest(req)));
127
+ return json(await resumeWorkflowRun({ runId: params.runId!, pins: parseWorkflowPins(parsed.body.pins), step: cleanString(parsed.body.step, "step", { max: 64 }), decision: parseWorkflowDecisionResponse(parsed.body.decision), activationId: cleanString(parsed.body.activationId, "activationId", { max: 2000 }), note: cleanString(parsed.body.note, "note", { max: 8000 }) }, authContextFromRequest(req)));
127
128
  } catch (e) {
128
129
  if (e instanceof McpNotFoundError) return error(e.message, 404);
129
130
  if (e instanceof ServiceAuthError) return error(e.message, 403);
@@ -146,7 +147,6 @@ export const postWorkflowRunStartRoute: Handler = async (req) => {
146
147
  try {
147
148
  const workflow = cleanString(parsed.body.workflow, "workflow", { max: 64 });
148
149
  if (!workflow) throw new ValidationError("workflow is required");
149
- const { parseWorkflowPins } = await import("../services/workflow-pins");
150
150
  return json(await startWorkflowRun({ workflow, taskId: taskIdField(parsed.body.taskId), pins: parseWorkflowPins(parsed.body.pins), inputs: parsed.body.inputs as Record<string, unknown> | undefined, requestKey: cleanString(parsed.body.requestKey, "requestKey", { max: 200 }), budget: parsed.body.budget }, authContextFromRequest(req)), 202);
151
151
  } catch (e) {
152
152
  if (e instanceof ServiceAuthError) return error(e.message, 403);
@@ -1,19 +1,20 @@
1
+ import { createCommandAsync } from "../commands-write";
2
+ import { deleteWorkspaceAsync } from "../db/lifecycle-tail-async";
1
3
  // Auto-split from routes.ts (#299). Domain: workspaces.
2
4
  import { RELAY_TOKEN_HEADER, isRecord } from "@humwick/sdk";
3
5
  import { TERMINAL_WORKSPACE_STATUSES, describeWorkspacePhase, landReceipt } from "../workspace-phase";
4
6
  import { VALID_WORKSPACE_STATUSES, cleanMeta, cleanString, optionalEnum } from "../validation";
5
- import { ValidationError, deleteWorkspace, getAgent, getWorkspace, listMergeLeases, listOrchestrators, listRepoStewards, listWorkspaces, updateWorkspaceStatus, withResolvedWorkspaceProjectsAsync } from "../db";
7
+ import { ValidationError, getAgent, getWorkspace, listMergeLeases, listOrchestrators, listRepoStewards, listWorkspaces, updateWorkspaceStatus, withResolvedWorkspaceProjectsAsync } from "../db";
6
8
  import { recoverIsolatedWorkspace } from "../services/workspace-recovery";
7
9
  import { WORKSPACE_ACTIONS, applyWorkspaceAction, waitForWorkspaceStatus } from "../workspace-actions";
8
10
  import { buildWorkspaceCleanupCommand, cleanupStaleWorkspaces } from "../workspace-cleanup-stale";
9
11
  import { auditEvent, authAuditMetadata, authorizeRoute, emitCommand, error, isRootCredentialRequest, json, parseBody, type Handler } from "./_shared";
10
12
  import { collectWorkspaceOrphans } from "../workspace-orphans";
11
- import { createCommand, getCommand } from "../commands-db";
12
- import { LAND_STRATEGIES, isOwnerAlive, isOwnerProcessConfirmedGone, withOwnerOnline } from "../workspace-merge";
13
+ import { getCommand } from "../commands-db";
14
+ import { LAND_STRATEGIES, isOwnerAlive, isOwnerProcessConfirmedGone, withOwnerOnline, AUTO_MERGE_POLICIES } from "../workspace-merge";
13
15
  import { isPathWithinBase } from "../utils";
14
16
  import { resolve } from "node:path";
15
17
  import { type WorkspaceAutoMergePolicy, type WorkspaceDiagnostics, type WorkspaceGitState, type WorkspaceLandGateLevel, type WorkspaceLandingPolicy, type WorkspaceMergeStrategy, type WorkspaceRecoveryBranch, type WorkspaceRecord, type WorkspaceStatus } from "../types";
16
- import { AUTO_MERGE_POLICIES } from "../workspace-merge";
17
18
  import { workspaceActiveClaim } from "../workspace-claim";
18
19
  import { resolveCoordinatorAgentId } from "../steward-identity";
19
20
  import { relayToken } from "../config";
@@ -231,7 +232,7 @@ export const getWorkspaceRecoveryBranches: Handler = async (req) => {
231
232
  };
232
233
 
233
234
  export const postWorkspaceRecoveryBranchDiscard: Handler = async (req) => {
234
- const denied = authorizeRoute(req, { scope: "command:write" });
235
+ const denied = (await authorizeRoute(req, { scope: "command:write" }));
235
236
  if (denied) return denied;
236
237
  const parsed = await parseBody<unknown>(req);
237
238
  if (!parsed.ok) return error(parsed.error, parsed.status);
@@ -256,13 +257,13 @@ export const postWorkspaceRecoveryBranchDiscard: Handler = async (req) => {
256
257
  if (target.safeToDelete !== true && !force) {
257
258
  return error(`branch holds work or cannot be proven safe (${target.preserveReason ?? `${target.unmergedAhead ?? target.ahead ?? "?"} unlanded commit(s)`}); land it first or pass force:true with reason to discard`, 409);
258
259
  }
259
- const command = createCommand({
260
+ const command = (await createCommandAsync({
260
261
  type: "workspace.recovery-branch-discard",
261
262
  source: "system",
262
263
  target: orch.agentId,
263
264
  correlationId: branch,
264
265
  params: { action: "recovery-branch-discard", repoRoot, branch, baseRef: baseRef ?? target.baseRef, baseSha: baseSha ?? target.baseSha, force, reason, requestedBy: "steward", requestedAt: Date.now() },
265
- });
266
+ }));
266
267
  emitCommand(command);
267
268
  await auditEvent({
268
269
  clientId: `workspace-recovery-branch-discard-${Date.now()}`,
@@ -282,7 +283,7 @@ export const postWorkspaceRecoveryBranchDiscard: Handler = async (req) => {
282
283
  };
283
284
 
284
285
  export const postWorkspaceOrphanReclaim: Handler = async (req) => {
285
- const denied = authorizeRoute(req, { scope: "command:write" });
286
+ const denied = (await authorizeRoute(req, { scope: "command:write" }));
286
287
  if (denied) return denied;
287
288
  const parsed = await parseBody<unknown>(req);
288
289
  if (!parsed.ok) return error(parsed.error, parsed.status);
@@ -306,12 +307,12 @@ export const postWorkspaceOrphanReclaim: Handler = async (req) => {
306
307
  ? listOrchestrators().find((candidate) => candidate.id === target.orchestratorId && candidate.status === "online")
307
308
  : listOrchestrators().find((candidate) => candidate.status === "online" && isPathWithinBase(repoRoot, candidate.baseDir));
308
309
  if (!orch) return error("no online orchestrator owns this path", 409);
309
- const command = createCommand({
310
+ const command = (await createCommandAsync({
310
311
  type: "workspace.cleanup",
311
312
  source: "system",
312
313
  target: orch.agentId,
313
314
  params: { action: "cleanup", worktreePath, repoRoot, branch, baseRef, baseSha, deleteBranch: true, reclaim: true, requestedBy: "dashboard", requestedAt: Date.now() },
314
- });
315
+ }));
315
316
  emitCommand(command);
316
317
  await auditEvent({
317
318
  clientId: `workspace-orphan-reclaim-${Date.now()}`,
@@ -443,7 +444,7 @@ export const getWorkspaceDiagnostics: Handler = async (_req, params) => {
443
444
  };
444
445
 
445
446
  export const postWorkspaceCleanupStale: Handler = async (req) => {
446
- const denied = authorizeRoute(req, { scope: "command:write" });
447
+ const denied = (await authorizeRoute(req, { scope: "command:write" }));
447
448
  if (denied) return denied;
448
449
  const parsed = await parseBody<unknown>(req);
449
450
  if (!parsed.ok) return error(parsed.error, parsed.status);
@@ -475,7 +476,7 @@ export const postWorkspaceAction: Handler = async (req, params) => {
475
476
  // owner-merge cwd-authz short-circuit below, so a recovered row still runs the standard cwd check.
476
477
  let freshlyRecovered = false;
477
478
  if (!workspace) {
478
- const recovery = recoverIsolatedWorkspace(authContextFromRequest(req), isRecord(parsed.body.workspace) ? parsed.body.workspace : undefined, params.id!);
479
+ const recovery = (await recoverIsolatedWorkspace(authContextFromRequest(req), isRecord(parsed.body.workspace) ? parsed.body.workspace : undefined, params.id!));
479
480
  if (!recovery.ok) return error("workspace not found", 404);
480
481
  workspace = recovery.workspace;
481
482
  freshlyRecovered = recovery.created;
@@ -509,7 +510,7 @@ export const postWorkspaceAction: Handler = async (req, params) => {
509
510
  // fuzzy label match (the 545ca290 impersonation hole); steward authz resolves the actual
510
511
  // elected steward, not the null-prone `workspace.stewardAgentId` mirror.
511
512
  const authorized = isWorkspaceRecoveryAuthorized(authCtx, workspace);
512
- const denied = authorizeRoute(req, { scope: requiresCommand ? "command:write" : "agent:write" });
513
+ const denied = (await authorizeRoute(req, { scope: requiresCommand ? "command:write" : "agent:write" }));
513
514
  if (denied) return denied;
514
515
  if (!authorized) return error("forbidden", 403);
515
516
  // A raw root credential or a VERIFIED component recovery principal (one of the four #1747
@@ -534,7 +535,7 @@ export const postWorkspaceAction: Handler = async (req, params) => {
534
535
  hasComponentScope(component, "command:write");
535
536
  if (!ownsMergeRequest && !isRecoveryAction) {
536
537
  const authzCwd = freshlyRecovered ? workspace.sourceCwd : workspace.worktreePath;
537
- const denied = authorizeRoute(req, { scope: requiresCommand ? "command:write" : "agent:write", resource: { agentId, cwd: authzCwd } });
538
+ const denied = (await authorizeRoute(req, { scope: requiresCommand ? "command:write" : "agent:write", resource: { agentId, cwd: authzCwd } }));
538
539
  if (denied) return denied;
539
540
  }
540
541
 
@@ -617,7 +618,7 @@ export const postWorkspaceAction: Handler = async (req, params) => {
617
618
  };
618
619
 
619
620
  export const deleteWorkspaceById: Handler = async (req, params) => {
620
- const denied = authorizeRoute(req, { scope: "command:write" });
621
+ const denied = (await authorizeRoute(req, { scope: "command:write" }));
621
622
  if (denied) return denied;
622
623
  const workspace = getWorkspace(params.id!);
623
624
  if (!workspace) return error("workspace not found", 404);
@@ -626,7 +627,7 @@ export const deleteWorkspaceById: Handler = async (req, params) => {
626
627
  // outlives this row: once the workspace is gone the reconciler sees the ledger entry, treats the
627
628
  // round as closed, and deletes each leaked ref by its OWN stored value with backoff until origin
628
629
  // confirms. So a purge just drops the workspace row; the ledger owns cleanup.
629
- const removed = deleteWorkspace(workspace.id);
630
+ const removed = (await deleteWorkspaceAsync(workspace.id));
630
631
  if (!removed) return error("workspace not found", 404);
631
632
  await auditEvent({
632
633
  clientId: `workspace-delete-${workspace.id}-${Date.now()}`,
@@ -5,7 +5,7 @@ import { emitRelayEvent } from "./events";
5
5
  import { routeNotification } from "./notification-router";
6
6
  import { renderTemplate } from "./prompt-resolver";
7
7
  import { parseJson } from "./utils";
8
- import { isRecord } from "@humwick/sdk";
8
+ import { fireAndForget, isRecord } from "@humwick/sdk";
9
9
  import type { RoutingPolicy } from "@humwick/sdk";
10
10
 
11
11
  const PUSH_TTL_MS = 15 * 60 * 1000;
@@ -33,16 +33,16 @@ export function notifyRoutingPolicyChanged(policy: RoutingPolicy, previous: Rout
33
33
  if (pending) clearTimeout(pending.timer);
34
34
  pending = {
35
35
  db, previous: baseline, policy: structuredClone(policy), updatedBy: opts.updatedBy,
36
- timer: setTimeout(() => {
36
+ timer: setTimeout(() => void fireAndForget("[routing-policy] FYI", async () => {
37
37
  const settled = pending;
38
38
  pending = undefined;
39
39
  try {
40
40
  if (!settled || settled.db !== getDb()) return;
41
- pushSettledPolicy(settled.previous, settled.policy, settled.updatedBy);
41
+ await pushSettledPolicy(settled.previous, settled.policy, settled.updatedBy);
42
42
  } catch (error) {
43
43
  console.warn("Routing policy FYI failed:", error);
44
44
  }
45
- }, QUIET_WINDOW_MS),
45
+ }), QUIET_WINDOW_MS),
46
46
  };
47
47
  pending.timer.unref();
48
48
  }
@@ -98,13 +98,13 @@ function ruleDelta(previous: unknown[], current: unknown[], path: string): strin
98
98
  return delta;
99
99
  }
100
100
 
101
- function pushSettledPolicy(previous: RoutingPolicy, policy: RoutingPolicy, updatedBy?: string): void {
101
+ async function pushSettledPolicy(previous: RoutingPolicy, policy: RoutingPolicy, updatedBy?: string): Promise<void> {
102
102
  const delta = policyDelta(previous, policy);
103
103
  if (delta.length === 0) return;
104
104
  const recipients = getSpawnCapableAgentIds();
105
105
  if (recipients.length === 0) return;
106
106
 
107
- routeNotification({
107
+ await routeNotification({
108
108
  type: "routing.policy.changed",
109
109
  scope: {},
110
110
  recipients,
@@ -5,11 +5,11 @@ import { getLaunchFact } from "./db/launch-facts";
5
5
  import { getTokenProfile } from "./token-db";
6
6
  import { getAgentProfile, spawnGrantForProfile, toolGroupsForProfile } from "./config-store";
7
7
  import { FLEET_SCOPE } from "./security";
8
- import { stringValue } from "@humwick/sdk";
8
+ import { registryToolName, stringValue } from "@humwick/sdk";
9
9
  import { canonicalAgentProfileName, hasScope } from "@humwick/sdk/types";
10
10
  import { COORDINATOR_PROFILE_NAMES } from "./agent-system-tags";
11
11
  import { SECRETS_DEFAULT_PROFILES, SECRETS_TOOL_GROUP, SECRETS_WRITE_SCOPE } from "./secrets-broker/config";
12
- import { resolveRelayTools, SELF_CARE_TOOL_GROUP } from "./mcp/tool-groups";
12
+ import { RELAY_TOOL_GROUPS, resolveRelayTools, SELF_CARE_TOOL_GROUP } from "./mcp/tool-groups";
13
13
 
14
14
  // Scopes that turn an agent's runtime token into a spawn-capable one (#221). Appended to the
15
15
  // provider-agent base scope only when the resolved profile permits (maxSpawnedAgents > 0).
@@ -27,6 +27,8 @@ const FLEET_OPS_PROFILES = new Set(["coordinator", "ops-assistant"]);
27
27
  const HOST_WRITE_PROFILES = new Set(["coordinator"]);
28
28
  // #3772 — the reboot runbook reads resource_control held/waiter counts from /api/hosts; ops profiles read, never write.
29
29
  const HOST_READ_PROFILES = new Set([...HOST_WRITE_PROFILES, "ops-assistant", "sec-ops"]);
30
+ // #3780 — operations and QA need hub diagnostics; coordinators can delegate the read scope.
31
+ const STATS_READ_PROFILES = new Set(["coordinator", "ops-assistant", "qa"]);
30
32
  // #3073 — coordinator-only groups (not in WORKER_TOOL_GROUPS) a coordinator always gets, even when its stored override predates them.
31
33
  const COORDINATOR_ONLY_TOOL_GROUPS = ["decisions", "workflows", "project", "routing"] as const;
32
34
  // #3327 — the coordinator edits routing preferences, like Edin in the dashboard.
@@ -45,11 +47,10 @@ function callerMayMintHostsScope(callerScopes: string[] | undefined, scope: "hos
45
47
  return callerScopes === undefined || hasScope(callerScopes, scope);
46
48
  }
47
49
 
48
- // #3110 — the secrets broker scope follows the `secrets` tool group: an explicit group list must name
49
- // it; a full-surface profile (no list) gets it only by default (coordinator, ops-assistant). A caller
50
- // cannot confer it onto a child it spawns without holding it.
51
- function grantsSecretsScope(profile: string | undefined, toolGroups: string[] | undefined, callerScopes: string[] | undefined): boolean {
52
- const inGroup = toolGroups ? toolGroups.includes(SECRETS_TOOL_GROUP) : Boolean(profile && SECRETS_DEFAULT_PROFILES.has(profile));
50
+ // #3110/#3817 — broker groups and exact includes request the same scope, capped by the caller.
51
+ function grantsSecretsScope(profile: string | undefined, toolGroups: string[] | undefined, explicitlySelected: boolean, callerScopes: string[] | undefined): boolean {
52
+ const inGroup = (toolGroups ? toolGroups.includes(SECRETS_TOOL_GROUP) : Boolean(profile && SECRETS_DEFAULT_PROFILES.has(profile)))
53
+ || explicitlySelected;
53
54
  return inGroup && (callerScopes === undefined || hasScope(callerScopes, SECRETS_WRITE_SCOPE));
54
55
  }
55
56
 
@@ -74,6 +75,8 @@ function nonTrustedBaseline(agentInitiated: boolean): string[] {
74
75
  interface RunnerGrant {
75
76
  scope: string[];
76
77
  canSpawn: boolean;
78
+ /** A sub-coordinator under its root coordinator's project delegation: it places spawns on any host its parent can (#3804). */
79
+ delegated: boolean;
77
80
  maxSpawnedAgents: number;
78
81
  toolGroups: string[] | undefined;
79
82
  toolInclude?: string[];
@@ -95,7 +98,7 @@ export function deriveRunnerGrant(profile: string | undefined, authority: Deriva
95
98
  const selection = profile ? getAgentProfile(profile)?.value.relay.tools : undefined;
96
99
  let toolGroups = toolGroupsForProfile(profile);
97
100
  // #2897/#3073/#3110 — stored overrides predate newer trusted-profile groups: coordinators always get the coordinator-only
98
- // groups, and the secrets profiles (coordinator, ops-assistant) always get `secrets`. Keep explicit NONE and full-surface semantics.
101
+ // groups, and the coordinator gets `secrets`. Keep explicit NONE and full-surface semantics.
99
102
  if (toolGroups?.length) {
100
103
  const extra: string[] = [];
101
104
  if ((COORDINATOR_PROFILE_NAMES as readonly string[]).includes(profile ?? "")) extra.push(...COORDINATOR_ONLY_TOOL_GROUPS);
@@ -106,19 +109,21 @@ export function deriveRunnerGrant(profile: string | undefined, authority: Deriva
106
109
  }
107
110
  const fleetOps = !agentInitiated && Boolean(profile && FLEET_OPS_PROFILES.has(profile)) && callerMayMintFleetScope(callerScopes);
108
111
  const hostsRead = !agentInitiated && Boolean(profile && HOST_READ_PROFILES.has(profile)) && callerMayMintHostsScope(callerScopes, "hosts:read");
109
- resolveRelayTools({ ...selection, groups: toolGroups });
112
+ const selected = resolveRelayTools({ ...selection, groups: toolGroups });
113
+ const explicitSecrets = selection?.include?.map(registryToolName).some(name => selected.has(name) && RELAY_TOOL_GROUPS[SECRETS_TOOL_GROUP]!.includes(name)) ?? false;
110
114
  let scope = [
111
115
  ...nonTrustedBaseline(agentInitiated),
112
116
  ...(fleetOps ? [FLEET_SCOPE] : []),
113
117
  ...(hostsRead ? ["hosts:read"] : []),
118
+ ...(profile && STATS_READ_PROFILES.has(profile.toLowerCase()) && (callerScopes === undefined || hasScope(callerScopes, "stats:read")) ? ["stats:read"] : []),
114
119
  ...(!agentInitiated && Boolean(profile && HOST_WRITE_PROFILES.has(profile)) && callerMayMintHostsScope(callerScopes, "hosts:write") ? ["hosts:write"] : []),
115
- ...(grantsSecretsScope(profile, toolGroups, callerScopes) ? [SECRETS_WRITE_SCOPE] : []),
120
+ ...(grantsSecretsScope(profile, toolGroups, explicitSecrets, callerScopes) ? [SECRETS_WRITE_SCOPE] : []),
116
121
  ...(!agentInitiated && (COORDINATOR_PROFILE_NAMES as readonly string[]).includes(profile ?? "") && (callerScopes === undefined || hasScope(callerScopes, ROUTING_WRITE_SCOPE)) ? [ROUTING_WRITE_SCOPE] : []),
117
122
  ...(grant.canSpawn ? SPAWN_SCOPES.filter(scope => !agentInitiated || callerScopes === undefined || hasScope(callerScopes, scope)) : []),
118
123
  ...(delegated && callerScopes && hasScope(callerScopes, "teams:write") ? ["teams:write"] : []),
119
124
  ];
120
125
  if (lineage?.participating && callerScopes) scope = scope.filter(scope => hasScope(callerScopes, scope));
121
- return { scope, canSpawn: grant.canSpawn, maxSpawnedAgents: grant.maxSpawnedAgents, toolGroups,
126
+ return { scope, canSpawn: grant.canSpawn, delegated, maxSpawnedAgents: grant.maxSpawnedAgents, toolGroups,
122
127
  ...(selection?.include === undefined ? {} : { toolInclude: selection.include }),
123
128
  ...(selection?.exclude === undefined ? {} : { toolExclude: selection.exclude }),
124
129
  };
@@ -151,6 +156,7 @@ export function successorGrant(agentId: string, profile: string | undefined, dep
151
156
  return {
152
157
  scope: derived.scope.filter((scope) => ceiling.scope.includes(scope)),
153
158
  canSpawn: derived.canSpawn && ceiling.canSpawn,
159
+ delegated: derived.delegated && ceiling.delegated,
154
160
  maxSpawnedAgents: Math.min(derived.maxSpawnedAgents, ceiling.maxSpawnedAgents),
155
161
  toolGroups: derived.toolGroups,
156
162
  toolInclude: derived.toolInclude,
@@ -1,4 +1,4 @@
1
- import { registryToolName } from "@humwick/sdk";
1
+ import { stringValue } from "@humwick/sdk";
2
2
  import { writeChildEnv } from "@humwick/sdk/env";
3
3
  import { deriveRunnerGrant, successorGrant } from "./runtime-grant";
4
4
  export { runnerSpawnGrantForAgent } from "./runtime-grant";
@@ -13,11 +13,9 @@ import { getLaunchFact, recordLaunchFact, type LaunchFact } from "./db/launch-fa
13
13
  import type { ComponentToken, SpawnApprovalMode, TokenConstraints, TokenRecord } from "./types";
14
14
  import { listManagedOrchestratorsForAgent } from "./orchestrator-lookup";
15
15
  import { sameHostIdentity } from "./utils";
16
- import { stringValue } from "@humwick/sdk";
17
- import { canonicalAgentProfileName, hasScope } from "@humwick/sdk/types";
16
+ import { canonicalAgentProfileName } from "@humwick/sdk/types";
18
17
  import { COORDINATOR_PROFILE_NAMES } from "./agent-system-tags";
19
18
  import { SECRETS_DEFAULT_PROFILES, SECRETS_TOOL_GROUP, SECRETS_WRITE_SCOPE } from "./secrets-broker/config";
20
- import { INCLUDED_TOOL_SCOPE_NEEDS, RELAY_TOOL_GROUPS, resolveRelayTools, SELF_CARE_TOOL_GROUP } from "./mcp/tool-groups";
21
19
 
22
20
  /**
23
21
  * Who vouches for a runner launch's authority (#3110 review). A launch without a positively identified
@@ -194,8 +192,8 @@ export function issueOrchestratorRuntimeToken(input: {
194
192
  }
195
193
 
196
194
  /** #1870 S6c — the mint on the writer thread, for the async routes. */
197
- export function issueOrchestratorRuntimeTokenAsync(input: Parameters<typeof issueOrchestratorRuntimeToken>[0]): Promise<RuntimeTokenResult> {
198
- return createTokenAsync(orchestratorTokenSpec(input));
195
+ export async function issueOrchestratorRuntimeTokenAsync(input: Parameters<typeof issueOrchestratorRuntimeToken>[0]): Promise<RuntimeTokenResult> {
196
+ return (await createTokenAsync(orchestratorTokenSpec(input)));
199
197
  }
200
198
 
201
199
  function orchestratorTokenSpec(input: Parameters<typeof issueOrchestratorRuntimeToken>[0]): CreateTokenInput {
@@ -269,21 +267,15 @@ function runnerTokenSpec(input: RunnerRuntimeTokenInput): { token: CreateTokenIn
269
267
  const grant = authority.kind === "successor" ? successorGrant(input.agentId, profile)
270
268
  : { ...deriveRunnerGrant(profile, authority.kind === "delegated" ? { agentInitiated, callerScopes: authority.callerScopes, parentAgentId: input.spawnedBy, projectId: input.projectId, maxSpawnedAgents: input.maxSpawnedAgents } : { agentInitiated }), agentInitiated };
271
269
  const { canSpawn, maxSpawnedAgents, toolGroups, toolInclude, toolExclude } = grant;
272
- const selected = resolveRelayTools({ groups: toolGroups, include: toolInclude, exclude: toolExclude });
273
- for (const authoredName of toolInclude ?? []) {
274
- const name = registryToolName(authoredName);
275
- if (!selected.has(name)) continue;
276
- const needs = INCLUDED_TOOL_SCOPE_NEEDS[name];
277
- if (RELAY_TOOL_GROUPS.kernel!.includes(name) || (needs && !needs.some((need) => hasScope(grant.scope, need)))) {
278
- throw new Error(`relay.tools.include: ${name} requires ${needs?.join(" or ") ?? "kernel control scopes"}; profile ${profile} cannot grant that scope under this launch authority`);
279
- }
280
- }
270
+ // Includes choose the surface, not launch authority. MCP scope filtering hides unavailable tools (#3817).
281
271
  // #1570 — provider-split: a Codex worker eager-loads every listed tool schema every turn (no
282
272
  // client-native deferral), so trim its always-loaded MCP surface to the lean worker set. Classified
283
273
  // by explicit worker profile (not tool-group shape — reviewers share the groups but aren't workers).
284
274
  const toolAllowlist = toolInclude !== undefined || toolExclude !== undefined
285
275
  ? undefined : toolAllowlistForRunner(input.provider, profile);
286
- const hostPinned = grant.agentInitiated ? true : (input.hostPinned ?? hostPinForProfile(input.profile));
276
+ // #3804 — an agent-spawned child stays on its launch host, except a delegated sub-coordinator: its home host is
277
+ // often full, so it places spawns wherever its parent can unless the launch pinned it.
278
+ const hostPinned = grant.agentInitiated && !grant.delegated ? true : (input.hostPinned ?? hostPinForProfile(input.profile));
287
279
  const subject = input.policyName
288
280
  ? `runner:policy:${input.policyName}`
289
281
  : input.spawnRequestId
@@ -339,7 +331,7 @@ export function issueRunnerRuntimeTokenAsync(input: RunnerRuntimeTokenInput): Pr
339
331
  return createRunnerTokenAsync(spec.token, spec.launchFact ? { agentId: input.agentId, fact: spec.launchFact } : undefined);
340
332
  }
341
333
 
342
- export function issueInteractiveRunnerRuntimeToken(input: {
334
+ export async function issueInteractiveRunnerRuntimeToken(input: {
343
335
  provider: string;
344
336
  cwd: string;
345
337
  runnerId: string;
@@ -351,9 +343,9 @@ export function issueInteractiveRunnerRuntimeToken(input: {
351
343
  * authorization, so it must ride the signature rather than the caller-writable agent card. */
352
344
  approvalMode?: SpawnApprovalMode;
353
345
  createdBy?: string;
354
- }): RuntimeTokenResult {
346
+ }): Promise<RuntimeTokenResult> {
355
347
  const agentId = input.agentId ?? input.runnerId;
356
- return createToken({
348
+ return (await createTokenAsync({
357
349
  profileId: "provider-interactive",
358
350
  sub: `runner:interactive:${input.host ?? "local"}:${input.runnerId}`,
359
351
  role: "provider",
@@ -368,22 +360,22 @@ export function issueInteractiveRunnerRuntimeToken(input: {
368
360
  canDelegate: false,
369
361
  },
370
362
  createdBy: input.createdBy ?? "interactive-runner",
371
- });
363
+ }));
372
364
  }
373
365
 
374
- export function issueMcpRuntimeToken(input: {
366
+ export async function issueMcpRuntimeToken(input: {
375
367
  sessionId: string;
376
368
  agentId?: string;
377
369
  targets?: string[];
378
370
  channels?: string[];
379
371
  createdBy?: string;
380
- }): RuntimeTokenResult {
372
+ }): Promise<RuntimeTokenResult> {
381
373
  const targets = input.targets?.length
382
374
  ? input.targets
383
375
  : input.agentId
384
376
  ? ["user", input.agentId]
385
377
  : undefined;
386
- return createToken({
378
+ return (await createTokenAsync({
387
379
  profileId: "mcp-client",
388
380
  sub: input.agentId ? `mcp:${input.agentId}:${input.sessionId}` : `mcp:session:${input.sessionId}`,
389
381
  role: "mcp",
@@ -394,16 +386,16 @@ export function issueMcpRuntimeToken(input: {
394
386
  canDelegate: false,
395
387
  },
396
388
  createdBy: input.createdBy ?? "runtime",
397
- });
389
+ }));
398
390
  }
399
391
 
400
- export function issueIntegrationRuntimeToken(input: {
392
+ export async function issueIntegrationRuntimeToken(input: {
401
393
  name: string;
402
394
  targets?: string[];
403
395
  channels?: string[];
404
396
  createdBy?: string;
405
- }): RuntimeTokenResult {
406
- return createToken({
397
+ }): Promise<RuntimeTokenResult> {
398
+ return (await createTokenAsync({
407
399
  profileId: "integration-client",
408
400
  sub: input.name,
409
401
  role: "integration",
@@ -414,7 +406,7 @@ export function issueIntegrationRuntimeToken(input: {
414
406
  canDelegate: false,
415
407
  },
416
408
  createdBy: input.createdBy ?? "runtime",
417
- });
409
+ }));
418
410
  }
419
411
 
420
412
  export function runnerRuntimeTokenEnv(input: RunnerRuntimeTokenInput): Record<string, string> {