@humwick/server 0.130.0 → 0.130.2

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 (486) hide show
  1. package/README.md +5 -0
  2. package/docs/guides/tools-administration.md +6 -1
  3. package/docs/guides/tools-coordination.md +1 -0
  4. package/docs/guides/tools-tasks.md +8 -0
  5. package/docs/guides/workflows.md +58 -0
  6. package/docs/openapi.json +414 -1
  7. package/package.json +8 -17
  8. package/public/assets/{MessageBubble-BWpoBjl4.js → MessageBubble-BWLqMGKG.js} +2 -2
  9. package/public/assets/{PlanGraphPanel-C0auck3n.js → PlanGraphPanel-3TlIQpjk.js} +2 -2
  10. package/public/assets/{TraceView-DBnOx7AQ.js → TraceView-Wimm0Czv.js} +2 -2
  11. package/public/assets/{VaultDecisionCard-0kp2oNBC.js → VaultDecisionCard-Bau_vNax.js} +2 -2
  12. package/public/assets/{activity-yLztXEBt.js → activity-CrSurn-r.js} +2 -2
  13. package/public/assets/{agent-lineage-BbJDR1dW.js → agent-lineage-CknAJKcb.js} +2 -2
  14. package/public/assets/agent-liveness-CaNwK0Zf.js +2 -0
  15. package/public/assets/{agent-name-CTZBrSD9.js → agent-name-DKbiIhBo.js} +2 -2
  16. package/public/assets/agent-profiles-DwF6EQ4R.js +2 -0
  17. package/public/assets/{agent-project-badge-9TqzUUYE.js → agent-project-badge-Diz4Sjjb.js} +2 -2
  18. package/public/assets/{agent-tools-D4n40pn8.js → agent-tools-CRhpRQza.js} +2 -2
  19. package/public/assets/{agent-type-icon-D6FUarOf.js → agent-type-icon-LrUEU3lB.js} +2 -2
  20. package/public/assets/{agents-BYDslkJc.js → agents-B-v66C-4.js} +2 -2
  21. package/public/assets/{analytics-B-XSbEMC.js → analytics-BqCb-LKi.js} +2 -2
  22. package/public/assets/{artifact-viewer-DdWIfV6M.js → artifact-viewer-kf4pGuAR.js} +2 -2
  23. package/public/assets/{artifacts-oBOqks-2.js → artifacts-D32zyw4Y.js} +2 -2
  24. package/public/assets/{artifacts-DPN1q4pA.js → artifacts-DJevXcvg.js} +2 -2
  25. package/public/assets/{automation-4KcRFXEu.js → automation-BpRajx_5.js} +2 -2
  26. package/public/assets/{backlog-Cw9xPTCF.js → backlog-D_UjTdzX.js} +2 -2
  27. package/public/assets/{busy-health-CTu32MZt.js → busy-health-N3VEh4ao.js} +2 -2
  28. package/public/assets/{channels-BiXzg1G9.js → channels-9jopungq.js} +2 -2
  29. package/public/assets/{chat-Ctk2qwvA.js → chat-CSXrTB3C.js} +2 -2
  30. package/public/assets/{code-preview-Bm1wWqAp.js → code-preview-p1NnmqK0.js} +2 -2
  31. package/public/assets/{connectors-C36ax4Vw.js → connectors-CZdqlDNL.js} +2 -2
  32. package/public/assets/{cosmos-Jj9LK2Wf.js → cosmos-BgdsE4dG.js} +2 -2
  33. package/public/assets/{crate-agent-H6S7-B9T.js → crate-agent-30aXTKva.js} +2 -2
  34. package/public/assets/{dependency-graph-Cd1E23wa.js → dependency-graph-B_jvw2GE.js} +2 -2
  35. package/public/assets/{deploys-Bwfq146q.js → deploys-DoN6bAb5.js} +2 -2
  36. package/public/assets/{display-CfO7rTuF.js → display-BrKWFGJz.js} +2 -2
  37. package/public/assets/{files-DhhZI2Ih.js → files-D6XEu8Ng.js} +2 -2
  38. package/public/assets/fleet-C7jQWbw2.js +2 -0
  39. package/public/assets/{fleet-metrics-BkHP-P3R.js → fleet-metrics-BiDs3MLx.js} +2 -2
  40. package/public/assets/{formatted-body-Cm1RrzmN.js → formatted-body-CQBwc1fb.js} +3 -3
  41. package/public/assets/{formatted-body-impl-DQiOuOD1.js → formatted-body-impl-DyG0WsQS.js} +2 -2
  42. package/public/assets/{host-providers-dialog-CHqwXTov.js → host-providers-dialog-DKaW31dN.js} +2 -2
  43. package/public/assets/index-CRHep3M9.js +15 -0
  44. package/public/assets/index-MYAIwYTX.css +2 -0
  45. package/public/assets/{insights-fH1EL-qZ.js → insights-CzrtkpOE.js} +2 -2
  46. package/public/assets/{integrations-C5Oix6ng.js → integrations-aVsUmigt.js} +2 -2
  47. package/public/assets/{issue-ref-chip-ODyondIa.js → issue-ref-chip-BnSlM_95.js} +2 -2
  48. package/public/assets/{log-viewer-Cvfy6PW7.js → log-viewer-qe7Ee47Z.js} +2 -2
  49. package/public/assets/{maintenance-D8CDH5wZ.js → maintenance-NfjqFZAk.js} +2 -2
  50. package/public/assets/{managed-agents-egllbpij.js → managed-agents-BiH0HgaE.js} +2 -2
  51. package/public/assets/{markdown-preview-CYSFgVgK.js → markdown-preview-BixaT3Xr.js} +3 -3
  52. package/public/assets/{markdown-preview-impl-BteVIjX4.js → markdown-preview-impl-DNGlWIS3.js} +2 -2
  53. package/public/assets/{memory-C8WmXsyr.js → memory-DNsCl6UX.js} +2 -2
  54. package/public/assets/{messages-BzQdqlO6.js → messages-DopU1L1X.js} +2 -2
  55. package/public/assets/{orchestrators-B1HxzkNw.js → orchestrators-Cq7ckLQT.js} +2 -2
  56. package/public/assets/{overview-DCUnI4Z7.js → overview-C1DCTzzV.js} +2 -2
  57. package/public/assets/{pairs-ftCd2XcD.js → pairs-DE_m6rrE.js} +2 -2
  58. package/public/assets/{profile-icon-B2zH8DMH.js → profile-icon-B3rfh77F.js} +2 -2
  59. package/public/assets/project-profile-instructions-DQWZE0kA.js +2 -0
  60. package/public/assets/projects-Br2Bd2h1.js +2 -0
  61. package/public/assets/{prompt-settings-Bftq9czX.js → prompt-settings-BKvFsvGD.js} +2 -2
  62. package/public/assets/provider-display-DsuWMvVL.js +2 -0
  63. package/public/assets/{registry-DWCU5c8q.js → registry-Cw38_6gW.js} +2 -2
  64. package/public/assets/run-card-CpKqRbhg.js +2 -0
  65. package/public/assets/scroll-text-BKXS1ahr.js +2 -0
  66. package/public/assets/{secrets-C7GIUATz.js → secrets-DxI-vzSq.js} +2 -2
  67. package/public/assets/{service-access-settings-C3aujqTG.js → service-access-settings-D9s_VJbd.js} +2 -2
  68. package/public/assets/{settings-C6sZ9eBa.js → settings-CKZTVGIx.js} +2 -2
  69. package/public/assets/{shipping-Bu8p1gEw.js → shipping-B9YtnwtG.js} +2 -2
  70. package/public/assets/{stats-V9Z-GxUd.js → stats-DyUBKyWI.js} +2 -2
  71. package/public/assets/{status-dot-CVv0iGHL.js → status-dot-DYqlEUhJ.js} +2 -2
  72. package/public/assets/{store-BvtFdlGX.js → store-DcClXLH0.js} +3 -3
  73. package/public/assets/{tasks-CXU75HRF.js → tasks-Bh7Chr4-.js} +2 -2
  74. package/public/assets/{teams-BPf976zb.js → teams-bowI7qVY.js} +2 -2
  75. package/public/assets/{terminal-viewer-DE9BOe-8.js → terminal-viewer-BgVLHdsU.js} +3 -3
  76. package/public/assets/{terminal-viewer-impl-D04pjmGh.js → terminal-viewer-impl-Dz22UPy6.js} +2 -2
  77. package/public/assets/token-estimate-Co-Jdphh.js +2 -0
  78. package/public/assets/{use-agent-terminal-B1yR456R.js → use-agent-terminal-DSQQ-iY7.js} +2 -2
  79. package/public/assets/{use-decisions-D-S4tpDf.js → use-decisions-samMQhQf.js} +2 -2
  80. package/public/assets/use-project-list-59LheVP7.js +2 -0
  81. package/public/assets/{use-selectors-CnrAk2YP.js → use-selectors-BZa25xEB.js} +2 -2
  82. package/public/assets/{user-avatar-nBFIcR4M.js → user-avatar-DHxlvUvV.js} +2 -2
  83. package/public/assets/versioned-prompt-editor-BmED5DnJ.js +8 -0
  84. package/public/assets/{work-queue-TCjJxeT-.js → work-queue-CSvpuNLz.js} +2 -2
  85. package/public/assets/{workflow-marker-Cf3Hf2fN.js → workflow-marker-DAZcj5qt.js} +2 -2
  86. package/public/assets/{workflows-CXQX20Qj.js → workflows-DdmxRSyg.js} +2 -2
  87. package/public/assets/{workspaces-qSYzKYMA.js → workspaces-BxeKfVuF.js} +2 -2
  88. package/public/cosmos.html +4 -4
  89. package/public/index.html +31 -29
  90. package/runner/plugins/claude/.claude-plugin/plugin.json +1 -1
  91. package/runner/plugins/claude/hooks/gate-run-guard.provisioned.mjs +31 -1
  92. package/runner/src/adapters/claude-effort.ts +2 -4
  93. package/runner/src/adapters/codex-launch-config.ts +4 -5
  94. package/runner/src/adapters/codex.ts +1 -1
  95. package/runner/src/adapters/opencode-provider.ts +7 -22
  96. package/runner/src/adapters/opencode.ts +4 -4
  97. package/runner/src/agent-runner/credentials.ts +42 -12
  98. package/runner/src/agent-runner/inbox.ts +27 -0
  99. package/runner/src/agent-runner/lifecycle-milestones.ts +0 -2
  100. package/runner/src/bin/claude.ts +4 -0
  101. package/runner/src/bin/codex.ts +4 -0
  102. package/runner/src/bin/opencode.ts +4 -0
  103. package/runner/src/index.ts +5 -2
  104. package/runner/src/provider-setup.ts +34 -0
  105. package/runner/src/providers.ts +2 -1
  106. package/runner/src/runner-core.ts +18 -23
  107. package/runner/src/spawn-input.ts +2 -1
  108. package/runner/src/test-run-dedup.ts +59 -56
  109. package/scripts/cd-config.ts +24 -3
  110. package/scripts/gate-run-lease.ts +10 -7
  111. package/scripts/gate.sh +5 -1
  112. package/src/branch-land-notice.ts +93 -0
  113. package/src/branch-landed.ts +7 -76
  114. package/src/bus.ts +19 -20
  115. package/src/cd-deploy-notify.ts +30 -2
  116. package/src/cd-host-lag-notify.ts +7 -6
  117. package/src/ci/incidents.ts +2 -2
  118. package/src/ci/monitor.ts +5 -8
  119. package/src/ci/root-missing.ts +2 -7
  120. package/src/ci/transition-notify.ts +7 -39
  121. package/src/cli/cd-pauses.ts +19 -0
  122. package/src/cli/orchestrator.ts +1 -1
  123. package/src/cli/state.ts +2 -19
  124. package/src/cli/task-edit.ts +22 -0
  125. package/src/cli/task.ts +3 -1
  126. package/src/cli/workflow.ts +8 -2
  127. package/src/commands-db.ts +10 -6
  128. package/src/commands-write.ts +8 -0
  129. package/src/config-store-write.ts +13 -1
  130. package/src/config-store.ts +25 -14
  131. package/src/db/activity-async.ts +6 -1
  132. package/src/db/activity-write-ops.ts +13 -1
  133. package/src/db/activity.ts +1 -14
  134. package/src/db/agent-lifecycle-write-ops.ts +1 -0
  135. package/src/db/agent-meta.ts +2 -2
  136. package/src/db/agent-ownership-write-ops.ts +4 -0
  137. package/src/db/agent-search.ts +1 -15
  138. package/src/db/agents-async.ts +58 -7
  139. package/src/db/agents-write-ops.ts +28 -5
  140. package/src/db/agents.ts +81 -54
  141. package/src/db/artifacts-write-ops.ts +8 -3
  142. package/src/db/bus-outbox-write-ops.ts +1 -0
  143. package/src/db/channels-write-ops.ts +4 -0
  144. package/src/db/ci-incidents-async.ts +5 -5
  145. package/src/db/ci-incidents-write-ops.ts +2 -2
  146. package/src/db/claude-api-budget-ops.ts +5 -5
  147. package/src/db/claude-app-oauth.ts +4 -4
  148. package/src/db/codex-reset-consumes-write-ops.ts +3 -0
  149. package/src/db/command-row.ts +10 -2
  150. package/src/db/commands-list-query.ts +2 -0
  151. package/src/db/commands-spawn-read.ts +1 -1
  152. package/src/db/commands-write-ops.ts +21 -0
  153. package/src/db/config-store-write-ops.ts +9 -2
  154. package/src/db/continuation-archives-async.ts +5 -0
  155. package/src/db/continuation-archives-write-ops.ts +9 -0
  156. package/src/db/continuations.ts +2 -2
  157. package/src/db/db-writer-worker.ts +8 -1
  158. package/src/db/delivery-write-ops.ts +5 -0
  159. package/src/db/deploy-events-async.ts +1 -0
  160. package/src/db/deploy-events-read-ops.ts +2 -1
  161. package/src/db/deploy-events.ts +35 -4
  162. package/src/db/hosts-registration.ts +10 -2
  163. package/src/db/hosts-write-ops.ts +14 -0
  164. package/src/db/hosts.ts +9 -6
  165. package/src/db/inbox-write-ops.ts +4 -0
  166. package/src/db/inbox.ts +0 -14
  167. package/src/db/index.ts +2 -0
  168. package/src/db/insights-write-ops.ts +1 -0
  169. package/src/db/integrations-async.ts +8 -0
  170. package/src/db/integrations-ops.ts +15 -0
  171. package/src/db/integrations.ts +15 -21
  172. package/src/db/issue-lifecycle.ts +3 -4
  173. package/src/db/issue-refs-async.ts +20 -0
  174. package/src/db/issue-refs-write-ops.ts +29 -0
  175. package/src/db/issue-refs.ts +7 -8
  176. package/src/db/long-running-work.ts +2 -2
  177. package/src/db/macos-privacy.ts +1 -1
  178. package/src/db/main-connection-writes.ts +23 -2
  179. package/src/db/maintenance-job-reconcile.ts +61 -0
  180. package/src/db/maintenance-job-runs-async.ts +14 -0
  181. package/src/db/maintenance-job-write-ops.ts +9 -0
  182. package/src/db/mappers.ts +2 -14
  183. package/src/db/merge-lease.ts +1 -15
  184. package/src/db/message-reads-write-ops.ts +3 -0
  185. package/src/db/message-write-ops.ts +4 -0
  186. package/src/db/migrations.ts +7 -15
  187. package/src/db/orchestrators-async.ts +7 -3
  188. package/src/db/orchestrators-write-ops.ts +7 -1
  189. package/src/db/orchestrators.ts +21 -77
  190. package/src/db/pairs.ts +1 -13
  191. package/src/db/project-profile-instructions.ts +50 -0
  192. package/src/db/prompt-versions-ops.ts +1 -0
  193. package/src/db/prompt-versions.ts +9 -2
  194. package/src/db/provider-catalog-write-ops.ts +4 -4
  195. package/src/db/provider-config-history.ts +8 -3
  196. package/src/db/provider-flap-messages-write-ops.ts +1 -0
  197. package/src/db/provider-model-seed.ts +34 -0
  198. package/src/db/provider-quota-lease-write-ops.ts +1 -0
  199. package/src/db/provider-quota-write-ops.ts +2 -0
  200. package/src/db/provisioning-write-ops.ts +9 -8
  201. package/src/db/read-op-marshaling-fixtures.ts +5 -0
  202. package/src/db/read-ops.ts +4 -0
  203. package/src/db/routing-policy.ts +3 -4
  204. package/src/db/schema.ts +9 -3
  205. package/src/db/secrets-broker-write-ops.ts +5 -5
  206. package/src/db/secrets-vault-write.ts +7 -7
  207. package/src/db/services-write-ops.ts +3 -0
  208. package/src/db/spawn-delegation-state.ts +11 -4
  209. package/src/db/spawn-tree-cleanup.ts +3 -0
  210. package/src/db/stats-async.ts +4 -3
  211. package/src/db/task-entity.ts +23 -2
  212. package/src/db/task-list.ts +3 -1
  213. package/src/db/tasks.ts +1 -17
  214. package/src/db/team-spawn-delegation.ts +1 -1
  215. package/src/db/teams-write-ops.ts +4 -4
  216. package/src/db/timer-cutover-write-ops.ts +7 -0
  217. package/src/db/timer-ingress-write-ops.ts +9 -0
  218. package/src/db/timer-ingress.ts +7 -1
  219. package/src/db/timer-operator-write-ops.ts +14 -0
  220. package/src/db/timer-transfer-write-ops.ts +4 -0
  221. package/src/db/timer-write-ops.ts +14 -0
  222. package/src/db/token-db-write-ops.ts +21 -0
  223. package/src/db/token-standing.ts +15 -11
  224. package/src/db/user-settings-write-ops.ts +1 -0
  225. package/src/db/vault-backup.ts +2 -2
  226. package/src/db/vault-expiry.ts +2 -2
  227. package/src/db/vault-health.ts +3 -3
  228. package/src/db/vault-token-ops.ts +8 -8
  229. package/src/db/web-tool-call-ops.ts +3 -3
  230. package/src/db/workflow-contact.ts +1 -1
  231. package/src/db/workflow-task-read-ops.ts +30 -0
  232. package/src/db/write-ops.ts +15 -1
  233. package/src/db/writer-fault-seam.ts +5 -2
  234. package/src/db/writer-gateway.ts +21 -12
  235. package/src/dispatch-availability.ts +10 -6
  236. package/src/ephemeral/{landing-ingress-proof-worker.ts → landing-ingress-proof.test-fixture.ts} +4 -2
  237. package/src/host/disk-alert.ts +121 -0
  238. package/src/lifecycle-manager.ts +3 -2
  239. package/src/maintenance/cd-watcher.ts +3 -3
  240. package/src/maintenance/critical-job-watchdog.ts +16 -17
  241. package/src/maintenance/index.ts +1 -0
  242. package/src/maintenance/jobs.ts +3 -2
  243. package/src/maintenance/scheduler.ts +84 -128
  244. package/src/maintenance/stalled-delivery-pager.ts +6 -5
  245. package/src/maintenance/workspaces.ts +1 -1
  246. package/src/managed-policy.ts +31 -27
  247. package/src/mcp/index.ts +5 -2
  248. package/src/mcp/output-descriptions.ts +8 -8
  249. package/src/mcp/presentation.ts +4 -4
  250. package/src/mcp/project-profile-instructions.ts +32 -0
  251. package/src/mcp/tool-groups.ts +1 -1
  252. package/src/mcp/tools-spawn.ts +4 -3
  253. package/src/mcp-agent-action.ts +1 -2
  254. package/src/mcp-host-tools.ts +1 -1
  255. package/src/mcp-issue-ref-tools.ts +12 -11
  256. package/src/mcp-maintenance-tools.ts +2 -2
  257. package/src/mcp-profile-trust-tools.ts +4 -4
  258. package/src/mcp-provider-catalog.ts +2 -2
  259. package/src/mcp-task-tools.ts +11 -5
  260. package/src/mcp-team-template-tools.ts +3 -3
  261. package/src/mcp-workflow-tools.ts +10 -3
  262. package/src/notification-types.ts +2 -1
  263. package/src/operator-alarm.ts +26 -11
  264. package/src/package-pack.ts +42 -39
  265. package/src/project-instructions.ts +4 -0
  266. package/src/project-profile-instruction-text.ts +6 -0
  267. package/src/project-profile-instructions.ts +20 -0
  268. package/src/provider-catalog-store.ts +3 -49
  269. package/src/provider-config-store.ts +12 -11
  270. package/src/provider-model-seed.ts +1 -18
  271. package/src/provider-quota-config-store.ts +5 -4
  272. package/src/provider-secrets.test-fixture.ts +3 -3
  273. package/src/recipe-runner.ts +2 -2
  274. package/src/repo-config-source.ts +10 -4
  275. package/src/routes/agent-profiles.ts +6 -6
  276. package/src/routes/agents-spawn.ts +1 -1
  277. package/src/routes/agents.ts +6 -6
  278. package/src/routes/commands-listing.ts +4 -0
  279. package/src/routes/commands.ts +2 -2
  280. package/src/routes/continuation-archives.ts +2 -2
  281. package/src/routes/host-resource-control.ts +4 -1
  282. package/src/routes/index.ts +10 -1
  283. package/src/routes/integrations.ts +24 -50
  284. package/src/routes/issue-refs.ts +7 -7
  285. package/src/routes/orchestrator-bootstrap.ts +3 -3
  286. package/src/routes/orchestrator-host-registration.ts +2 -2
  287. package/src/routes/orchestrator-host-utilization.ts +3 -1
  288. package/src/routes/orchestrator-proxy.ts +2 -2
  289. package/src/routes/orchestrator.ts +4 -4
  290. package/src/routes/project-profile-instructions.ts +27 -0
  291. package/src/routes/project-trust.ts +2 -2
  292. package/src/routes/provider-config.ts +9 -8
  293. package/src/routes/provider-quota-config.ts +2 -2
  294. package/src/routes/settings.ts +5 -5
  295. package/src/routes/stats.ts +6 -6
  296. package/src/routes/task-http.ts +15 -1
  297. package/src/routes/task-issues.ts +11 -26
  298. package/src/routes/tasks.ts +22 -53
  299. package/src/routes/team-templates.ts +5 -5
  300. package/src/routes/teams-config.ts +2 -2
  301. package/src/routes/tokens.ts +12 -5
  302. package/src/routes/workflows.ts +20 -5
  303. package/src/routing-policy-normalize.ts +1 -2
  304. package/src/routing-step-defaults.ts +0 -2
  305. package/src/runner-token-mint-limit.ts +63 -0
  306. package/src/runtime-grant.ts +4 -2
  307. package/src/runtime-tokens.ts +50 -9
  308. package/src/security.ts +13 -20
  309. package/src/server-background.ts +1 -1
  310. package/src/server.ts +5 -2
  311. package/src/services/host-utilization.ts +23 -0
  312. package/src/services/idle-epics.ts +5 -6
  313. package/src/services/issue-lifecycle.ts +57 -61
  314. package/src/services/issue-refs.ts +9 -13
  315. package/src/services/kernel-timers-cutover-chaos-worker.ts +12 -7
  316. package/src/services/kernel-timers-ingress.ts +1 -1
  317. package/src/services/kernel-timers-request-key-migration-chaos-worker.ts +4 -1
  318. package/src/services/landed-ancestry.ts +19 -3
  319. package/src/services/memory-areas.ts +15 -51
  320. package/src/services/plan-ci-notice.ts +57 -0
  321. package/src/services/plan-ci-recovery.ts +2 -55
  322. package/src/services/plan-wake-digest.ts +1 -0
  323. package/src/services/profile-trust.ts +4 -3
  324. package/src/services/project-config-read.ts +70 -0
  325. package/src/services/project-profile-instructions.ts +54 -0
  326. package/src/services/provisioning-harness/budget-baseline.json +4 -4
  327. package/src/services/restart-spawn.ts +61 -49
  328. package/src/services/self-care-state.ts +2 -1
  329. package/src/services/self-effort.ts +5 -5
  330. package/src/services/send-message.ts +5 -1
  331. package/src/services/spawn-agent.ts +13 -2
  332. package/src/services/spawn-chain-inspector.ts +6 -13
  333. package/src/services/spawn-policy.ts +68 -0
  334. package/src/services/spawn-tree-cleanup.ts +2 -2
  335. package/src/services/task-dispatch.ts +3 -0
  336. package/src/services/task-state-reconcile.ts +6 -14
  337. package/src/services/team-templates.ts +9 -8
  338. package/src/services/transient-agent-reaper.ts +3 -1
  339. package/src/services/workflow-auto-start.ts +25 -13
  340. package/src/services/workflow-ci-main.ts +1 -1
  341. package/src/services/workflow-deferred-cleanup.ts +4 -2
  342. package/src/services/workflow-effect-driver.ts +2 -0
  343. package/src/services/workflow-effects.ts +18 -11
  344. package/src/services/workflow-land-notice.ts +10 -0
  345. package/src/services/workflow-pause-text.ts +43 -0
  346. package/src/services/workflow-reconcile.ts +13 -11
  347. package/src/services/workflow-run-view.ts +2 -0
  348. package/src/services/workflow-runs.ts +29 -13
  349. package/src/services/workflow-supervisor-state.ts +5 -0
  350. package/src/services/workflow-supervisor.ts +7 -3
  351. package/src/settings/registry.ts +1 -2
  352. package/src/spawn-command.ts +4 -4
  353. package/src/spawn-models.ts +10 -12
  354. package/src/spawn-placement.ts +1 -1
  355. package/src/spawn-targets.ts +1 -1
  356. package/src/startup-context-injection.ts +1 -0
  357. package/src/state-adoption/cd-cutover.ts +53 -7
  358. package/src/state-adoption/fleet-cutover.ts +35 -0
  359. package/src/state-adoption/fleet-plan.ts +36 -0
  360. package/src/state-adoption/index.ts +2 -2
  361. package/src/state-adoption/linux-plan.ts +2 -2
  362. package/src/state-adoption/linux.ts +2 -2
  363. package/src/state-adoption/macos-plan.ts +2 -2
  364. package/src/state-adoption/macos.ts +1 -1
  365. package/src/steward.ts +7 -6
  366. package/src/task-dispatch-live.ts +1 -1
  367. package/src/task-dispatcher.ts +1 -1
  368. package/src/teams-config-store.ts +4 -3
  369. package/src/token-db-write.ts +10 -0
  370. package/src/workspace-command-settle.ts +2 -1
  371. package/src/workspace-steward-wake.ts +2 -2
  372. package/node_modules/@humwick/kernel/LICENSE +0 -167
  373. package/node_modules/@humwick/kernel/async-entity-persistence.ts +0 -344
  374. package/node_modules/@humwick/kernel/async-event-log.ts +0 -107
  375. package/node_modules/@humwick/kernel/automations.pilot.ts +0 -147
  376. package/node_modules/@humwick/kernel/config.ts +0 -111
  377. package/node_modules/@humwick/kernel/contract.ts +0 -572
  378. package/node_modules/@humwick/kernel/control-authz.ts +0 -281
  379. package/node_modules/@humwick/kernel/control.ts +0 -645
  380. package/node_modules/@humwick/kernel/effects.ts +0 -493
  381. package/node_modules/@humwick/kernel/event-log.ts +0 -319
  382. package/node_modules/@humwick/kernel/http-transport.ts +0 -384
  383. package/node_modules/@humwick/kernel/identity-grammar.ts +0 -20
  384. package/node_modules/@humwick/kernel/identity.ts +0 -248
  385. package/node_modules/@humwick/kernel/index.ts +0 -28
  386. package/node_modules/@humwick/kernel/kernel-persistence.ts +0 -181
  387. package/node_modules/@humwick/kernel/landing-incident.ts +0 -306
  388. package/node_modules/@humwick/kernel/landing.ts +0 -953
  389. package/node_modules/@humwick/kernel/manifest-validation.ts +0 -460
  390. package/node_modules/@humwick/kernel/mcp-transport.ts +0 -109
  391. package/node_modules/@humwick/kernel/message-authority-chaos-worker.ts +0 -200
  392. package/node_modules/@humwick/kernel/message-authority.ts +0 -281
  393. package/node_modules/@humwick/kernel/module-host.ts +0 -722
  394. package/node_modules/@humwick/kernel/openapi-init.ts +0 -14
  395. package/node_modules/@humwick/kernel/package.json +0 -47
  396. package/node_modules/@humwick/kernel/persistence-schema.ts +0 -248
  397. package/node_modules/@humwick/kernel/persistence.ts +0 -510
  398. package/node_modules/@humwick/kernel/registry.ts +0 -60
  399. package/node_modules/@humwick/kernel/sqlite-kernel-persistence.ts +0 -499
  400. package/node_modules/@humwick/kernel/sqlite-module-host.ts +0 -87
  401. package/node_modules/@humwick/kernel/timers-chaos-worker.ts +0 -186
  402. package/node_modules/@humwick/kernel/timers.pilot.ts +0 -1411
  403. package/node_modules/@humwick/kernel/workflow-activation.ts +0 -25
  404. package/node_modules/@humwick/kernel/workflow-backstop.ts +0 -40
  405. package/node_modules/@humwick/kernel/workflow-cancel-tree.ts +0 -20
  406. package/node_modules/@humwick/kernel/workflow-chaos-worker.ts +0 -213
  407. package/node_modules/@humwick/kernel/workflow-composition.ts +0 -231
  408. package/node_modules/@humwick/kernel/workflow-container.ts +0 -72
  409. package/node_modules/@humwick/kernel/workflow-control.test-fixture.ts +0 -42
  410. package/node_modules/@humwick/kernel/workflow-control.ts +0 -178
  411. package/node_modules/@humwick/kernel/workflow-definition.ts +0 -507
  412. package/node_modules/@humwick/kernel/workflow-dispatch.ts +0 -60
  413. package/node_modules/@humwick/kernel/workflow-foreach.ts +0 -167
  414. package/node_modules/@humwick/kernel/workflow-journal-codec.ts +0 -339
  415. package/node_modules/@humwick/kernel/workflow-journal.ts +0 -922
  416. package/node_modules/@humwick/kernel/workflow-lane.ts +0 -48
  417. package/node_modules/@humwick/kernel/workflow-lease-owner.ts +0 -60
  418. package/node_modules/@humwick/kernel/workflow-recovery.test-fixture.ts +0 -19
  419. package/node_modules/@humwick/kernel/workflow-runtime.ts +0 -938
  420. package/node_modules/@humwick/kernel/workflow-script.ts +0 -11
  421. package/node_modules/@humwick/kernel/workflow-state.ts +0 -299
  422. package/node_modules/@humwick/kernel/workflow-step-commit.ts +0 -195
  423. package/node_modules/@humwick/kernel/workflow-transitions.ts +0 -317
  424. package/node_modules/@humwick/kernel/workflow-values.ts +0 -153
  425. package/node_modules/@humwick/kernel/workflow.pilot.ts +0 -376
  426. package/node_modules/@humwick/offload/LICENSE +0 -167
  427. package/node_modules/@humwick/offload/README.md +0 -57
  428. package/node_modules/@humwick/offload/dist/async-runner.d.ts +0 -18
  429. package/node_modules/@humwick/offload/dist/async-runner.d.ts.map +0 -1
  430. package/node_modules/@humwick/offload/dist/async-runner.js +0 -158
  431. package/node_modules/@humwick/offload/dist/async-runner.js.map +0 -1
  432. package/node_modules/@humwick/offload/dist/envelope.d.ts +0 -25
  433. package/node_modules/@humwick/offload/dist/envelope.d.ts.map +0 -1
  434. package/node_modules/@humwick/offload/dist/envelope.js +0 -80
  435. package/node_modules/@humwick/offload/dist/envelope.js.map +0 -1
  436. package/node_modules/@humwick/offload/dist/fixtures/worker-task.d.ts +0 -10
  437. package/node_modules/@humwick/offload/dist/fixtures/worker-task.d.ts.map +0 -1
  438. package/node_modules/@humwick/offload/dist/fixtures/worker-task.js +0 -59
  439. package/node_modules/@humwick/offload/dist/fixtures/worker-task.js.map +0 -1
  440. package/node_modules/@humwick/offload/dist/index.d.ts +0 -5
  441. package/node_modules/@humwick/offload/dist/index.d.ts.map +0 -1
  442. package/node_modules/@humwick/offload/dist/index.js +0 -5
  443. package/node_modules/@humwick/offload/dist/index.js.map +0 -1
  444. package/node_modules/@humwick/offload/dist/subprocess-runner.d.ts +0 -62
  445. package/node_modules/@humwick/offload/dist/subprocess-runner.d.ts.map +0 -1
  446. package/node_modules/@humwick/offload/dist/subprocess-runner.js +0 -295
  447. package/node_modules/@humwick/offload/dist/subprocess-runner.js.map +0 -1
  448. package/node_modules/@humwick/offload/dist/types.d.ts +0 -111
  449. package/node_modules/@humwick/offload/dist/types.d.ts.map +0 -1
  450. package/node_modules/@humwick/offload/dist/types.js +0 -176
  451. package/node_modules/@humwick/offload/dist/types.js.map +0 -1
  452. package/node_modules/@humwick/offload/dist/worker-pool.d.ts +0 -23
  453. package/node_modules/@humwick/offload/dist/worker-pool.d.ts.map +0 -1
  454. package/node_modules/@humwick/offload/dist/worker-pool.js +0 -435
  455. package/node_modules/@humwick/offload/dist/worker-pool.js.map +0 -1
  456. package/node_modules/@humwick/offload/dist/worker-runtime.d.ts +0 -2
  457. package/node_modules/@humwick/offload/dist/worker-runtime.d.ts.map +0 -1
  458. package/node_modules/@humwick/offload/dist/worker-runtime.js +0 -27
  459. package/node_modules/@humwick/offload/dist/worker-runtime.js.map +0 -1
  460. package/node_modules/@humwick/offload/dist/worker.d.ts +0 -5
  461. package/node_modules/@humwick/offload/dist/worker.d.ts.map +0 -1
  462. package/node_modules/@humwick/offload/dist/worker.js +0 -1
  463. package/node_modules/@humwick/offload/dist/worker.js.map +0 -1
  464. package/node_modules/@humwick/offload/package.json +0 -46
  465. package/node_modules/@humwick/offload/src/async-runner.ts +0 -170
  466. package/node_modules/@humwick/offload/src/envelope.ts +0 -92
  467. package/node_modules/@humwick/offload/src/fixtures/worker-task.ts +0 -49
  468. package/node_modules/@humwick/offload/src/index.ts +0 -4
  469. package/node_modules/@humwick/offload/src/subprocess-runner.ts +0 -334
  470. package/node_modules/@humwick/offload/src/types.ts +0 -235
  471. package/node_modules/@humwick/offload/src/worker-pool.ts +0 -463
  472. package/node_modules/@humwick/offload/src/worker-runtime.ts +0 -32
  473. package/node_modules/@humwick/offload/src/worker.ts +0 -5
  474. package/public/assets/agent-liveness-DyqxfB-t.js +0 -2
  475. package/public/assets/agent-profiles-BpI_jTxq.js +0 -2
  476. package/public/assets/fleet-BL4UXS_k.js +0 -2
  477. package/public/assets/index-B7ZerHLY.css +0 -2
  478. package/public/assets/index-BK0WV6hF.js +0 -15
  479. package/public/assets/projects-B_lmcqfO.js +0 -2
  480. package/public/assets/provider-display-DbLWpP3j.js +0 -2
  481. package/public/assets/run-card-CO_bguQf.js +0 -2
  482. package/public/assets/use-project-list-B-T2Bnnn.js +0 -2
  483. package/public/assets/versioned-prompt-editor-Diu8BIaM.js +0 -8
  484. package/runner/src/gate-run-lease-probe.ts +0 -38
  485. package/src/db/claude-catalog-reconcile.ts +0 -27
  486. package/src/routing-model-aliases.ts +0 -21
@@ -1,7 +1,7 @@
1
1
  import { stewardFallbackTarget } from "../config.ts";
2
2
  import { emitRelayEvent } from "../events.ts";
3
3
  import type { WorkspaceHumanEscalationResult } from "../workspace-human-escalation.ts";
4
- import { ingestCiObservation, ingestCiObservationAsync, type CiIncidentIngestResult } from "./incidents.ts";
4
+ import { ingestCiObservationAsync, type CiIncidentIngestResult } from "./incidents.ts";
5
5
  import {
6
6
  routeCiIncidentNotification,
7
7
  type CiFailedNotificationPayload,
@@ -41,7 +41,7 @@ import type { CiObservation, CiRunRef } from "./provider.ts";
41
41
  export const CI_MAIN_WATCH_COORDINATOR = "cd-watcher";
42
42
 
43
43
  export interface CiTransitionNotifyDeps {
44
- ingest?: (observation: CiObservation, now?: number) => CiIncidentIngestResult;
44
+ ingest?: (observation: CiObservation, now?: number) => CiIncidentIngestResult | Promise<CiIncidentIngestResult>;
45
45
  route?: (input: Parameters<typeof routeCiIncidentNotification>[0]) => RouteCiIncidentNotificationResult;
46
46
  /** Resolve the coordinator/owner agent the relay message addresses. Defaults to the configured
47
47
  * steward fallback, then the system watch actor. */
@@ -65,16 +65,17 @@ export interface CiTransitionNotifyResult {
65
65
  * Detect and (if a flip occurred) alert on a CI red/green transition for a single observed watch.
66
66
  * Idempotent per transition — safe to call every poll tick.
67
67
  */
68
- export function notifyCiTransition(
68
+ export async function notifyCiTransition(
69
69
  observation: CiObservation | null | undefined,
70
70
  deps: CiTransitionNotifyDeps = {},
71
- ): CiTransitionNotifyResult {
71
+ ): Promise<CiTransitionNotifyResult> {
72
72
  if (!observation) return { transition: "none", notified: false, messages: 0 };
73
- const ingest = deps.ingest ?? ingestCiObservation;
73
+ const ingest = deps.ingest ?? ingestCiObservationAsync;
74
74
  const route = deps.route ?? routeCiIncidentNotification;
75
75
  const now = deps.now ?? Date.now();
76
76
 
77
- const result = ingest(observation, now);
77
+ // Notification emission follows the committed transition (one writer op, #1870), never a pre-write observation.
78
+ const result = await ingest(observation, deps.now);
78
79
  emitCiStatus(result);
79
80
 
80
81
  if (result.kind === "opened") {
@@ -155,39 +156,6 @@ export function notifyCiTransition(
155
156
  return { transition: "none", ...(result.kind === "deduped" ? { incidentId: result.incident.id } : {}), notified: false, messages: 0 };
156
157
  }
157
158
 
158
- /** Gateway-routed companion for async maintenance polling. Notification emission follows the
159
- * committed transition, never a pre-write observation. */
160
- export async function notifyCiTransitionAsync(
161
- observation: CiObservation | null | undefined,
162
- deps: Omit<CiTransitionNotifyDeps, "ingest"> = {},
163
- ): Promise<CiTransitionNotifyResult> {
164
- if (!observation) return { transition: "none", notified: false, messages: 0 };
165
- const route = deps.route ?? routeCiIncidentNotification;
166
- const now = deps.now ?? Date.now();
167
- const result = await ingestCiObservationAsync(observation);
168
- emitCiStatus(result);
169
- if (result.kind === "opened") {
170
- const coordinatorAgentId = resolveCoordinator(deps);
171
- const routed = route({ type: "ci.failed", payload: { incidentId: result.incident.id, provider: observation.provider, repository: observation.repository, branch: observation.branch, workflow: observation.workflow, failingHeadSha: observation.headSha, coordinatorAgentId, observedAt: observation.observedAt, ...(observation.conclusion ? { conclusion: observation.conclusion } : {}), failingRunIds: observation.failingRuns.map((run) => run.id), failingJobNames: failingJobNames(observation) }, alertHuman: true, subject: redSubject(observation), body: redBody(observation, result.incident.id) });
172
- return { transition: "went_red", incidentId: result.incident.id, notified: true, messages: routed.messages.length, ...(routed.humanFallback ? { humanFallback: routed.humanFallback } : {}) };
173
- }
174
- if (result.kind === "resolved") {
175
- const coordinatorAgentId = resolveCoordinator(deps);
176
- const routed = route({ type: "ci.recovered", payload: { incidentId: result.incident.id, provider: observation.provider, repository: observation.repository, branch: observation.branch, workflow: observation.workflow, failingHeadSha: result.incident.openedHeadSha, coordinatorAgentId, recoveredAt: now, greenHeadSha: observation.headSha, ...(result.incident.resolution ? { resolution: result.incident.resolution } : {}) }, alertHuman: true, subject: greenSubject(observation), body: greenBody(observation, result.incident.id, result.incident.openedHeadSha) });
177
- return { transition: "back_green", incidentId: result.incident.id, notified: true, messages: routed.messages.length, ...(routed.humanFallback ? { humanFallback: routed.humanFallback } : {}) };
178
- }
179
- if (result.kind === "never_matched") {
180
- const coordinatorAgentId = resolveCoordinator(deps);
181
- const routed = route({ type: "ci.never_matched", payload: { provider: observation.provider, repository: observation.repository, branch: observation.branch, workflow: observation.workflow, coordinatorAgentId, firstSeenAt: result.firstSeenAt, observedAt: observation.observedAt }, alertHuman: true, subject: neverMatchedSubject(observation), body: neverMatchedBody(observation) });
182
- return { transition: "never_matched", notified: true, messages: routed.messages.length, ...(routed.humanFallback ? { humanFallback: routed.humanFallback } : {}) };
183
- }
184
- if (result.kind === "config_mismatch") {
185
- const routed = route(configMismatchRoute(observation, resolveCoordinator(deps), result.unmatchedJobs));
186
- return { transition: "config_mismatch", notified: true, messages: routed.messages.length, ...(routed.humanFallback ? { humanFallback: routed.humanFallback } : {}) };
187
- }
188
- return { transition: "none", ...(result.kind === "deduped" ? { incidentId: result.incident.id } : {}), notified: false, messages: 0 };
189
- }
190
-
191
159
  /** Live CI view (#3011): one `ci.status` event per watch status transition, never per poll. */
192
160
  function emitCiStatus(result: CiIncidentIngestResult): void {
193
161
  if (result.statusChange) emitRelayEvent({ type: "ci.status", source: "server", subject: result.watchKey, data: { ...result.statusChange } });
@@ -0,0 +1,19 @@
1
+ import type { CdPause, CdPauseControl } from "../state-adoption/cd-cutover";
2
+ import { apiRequest, ApiRequestError } from "./_shared";
3
+
4
+ /** The audited maintenance pause API (#1645) shared by local and fleet cutovers (#3470). */
5
+ export const cdWatcherPauses: CdPauseControl = {
6
+ async pause(holder, reason) {
7
+ try {
8
+ return ((await apiRequest("POST", "/api/maintenance/jobs/cd-watcher/pause", { holder, reason, indefinite: true })) as { pause: CdPause }).pause;
9
+ } catch (error) {
10
+ if (error instanceof ApiRequestError && error.status === 409) return (error.responseBody as { pause: CdPause }).pause;
11
+ throw error;
12
+ }
13
+ },
14
+ async current() {
15
+ const { pauses } = (await apiRequest("GET", "/api/maintenance/pauses")) as { pauses: Array<CdPause & { jobId: string }> };
16
+ return pauses.find((pause) => pause.jobId === "cd-watcher") ?? null;
17
+ },
18
+ async resume() { await apiRequest("POST", "/api/maintenance/jobs/cd-watcher/resume"); },
19
+ };
@@ -118,7 +118,7 @@ export async function handleOrchestratorCommand(args: string[]): Promise<void> {
118
118
  // Install the server package too so a real `humwick` bin lands in the
119
119
  // runtime .bin dir (#1421): under bunx there is no global install, so without
120
120
  // this the darwin shim below would point at a bin that does not exist.
121
- const packageCommand = skipInstall ? [] : ["npm", "install", "--prefix", runtimePrefix, `agent-relay-runner@${version}`, `agent-relay-orchestrator@${version}`, `agent-relay-server@${version}`, OPENCODE_HARNESS_PACKAGE];
121
+ const packageCommand = skipInstall ? [] : ["npm", "install", "--prefix", runtimePrefix, `@humwick/runner@${version}`, `@humwick/orchestrator@${version}`, `@humwick/server@${version}`, OPENCODE_HARNESS_PACKAGE];
122
122
  // W1: on macOS, put `humwick` on the interactive PATH (shim + profile
123
123
  // block). Gated to darwin — Linux global-npm installs already get the shim via
124
124
  // the postinstall, and we avoid editing Linux user profiles unnecessarily.
package/src/cli/state.ts CHANGED
@@ -2,8 +2,7 @@ import { readFileSync } from "node:fs";
2
2
  import { adoptMacos } from "../state-adoption/macos";
3
3
  import { adoptLinux } from "../state-adoption/linux";
4
4
  import { adoptState } from "../state-adoption";
5
- import type { CdPause, CdPauseControl } from "../state-adoption/cd-cutover";
6
- import { apiRequest, ApiRequestError } from "./_shared";
5
+ import { cdWatcherPauses } from "./cd-pauses";
7
6
 
8
7
  export const STATE_USAGE = "humwick state adopt|linux-adopt|macos-adopt|cd-cutover|cd-resume --manifest PATH [--interrupt-after STEP] [--rollback] [--systemctl PATH] [--launchctl PATH]";
9
8
  const COMMANDS = ["adopt", "linux-adopt", "macos-adopt", "cd-cutover", "cd-resume"];
@@ -43,6 +42,7 @@ export async function handleStateCommand(args: string[]): Promise<void> {
43
42
  async function cdCutover(command: string, input: any, options: { rollback: boolean; systemctl?: string; launchctl?: string; interrupt: (step: string) => void }) {
44
43
  const { runCutover, resumeCutoverCd } = await import("../state-adoption/cd-cutover");
45
44
  if (command === "cd-resume") return resumeCutoverCd(input, cdWatcherPauses);
45
+ if (input?.fleet) throw new Error("Fleet activation uses bun run release --deploy-only --cutover-manifest PATH under the existing executor");
46
46
  const [{ resolveCanonicalRelayDbPath, withDeployLedgerDb }, { resolveReleaseLocalOrchestratorId }] = await Promise.all([
47
47
  import("../../scripts/cd-config"), import("../../scripts/release-upgrade")]);
48
48
  const dbPath = resolveCanonicalRelayDbPath();
@@ -54,20 +54,3 @@ async function cdCutover(command: string, input: any, options: { rollback: boole
54
54
  : adoptLinux(input.handoff, { rollback, systemctl: options.systemctl, interrupt: options.interrupt }),
55
55
  });
56
56
  }
57
-
58
- /** The audited maintenance pause API (#1645) for cd-watcher; an existing pause is returned, ours or not. */
59
- const cdWatcherPauses: CdPauseControl = {
60
- async pause(holder, reason) {
61
- try {
62
- return ((await apiRequest("POST", "/api/maintenance/jobs/cd-watcher/pause", { holder, reason, indefinite: true })) as { pause: CdPause }).pause;
63
- } catch (error) {
64
- if (error instanceof ApiRequestError && error.status === 409) return (error.responseBody as { pause: CdPause }).pause;
65
- throw error;
66
- }
67
- },
68
- async current() {
69
- const { pauses } = (await apiRequest("GET", "/api/maintenance/pauses")) as { pauses: Array<CdPause & { jobId: string }> };
70
- return pauses.find((pause) => pause.jobId === "cd-watcher") ?? null;
71
- },
72
- async resume() { await apiRequest("POST", "/api/maintenance/jobs/cd-watcher/resume"); },
73
- };
@@ -0,0 +1,22 @@
1
+ import { parseTaskIdValue } from "../validation";
2
+ import { apiRequest } from "./_shared";
3
+
4
+ const fields: Record<string, string> = { "--title": "title", "--intent": "intent", "--done-when": "doneWhen", "--on-deploy": "onDeploy", "--team": "teamId", "--repo": "repo" };
5
+
6
+ export async function editTaskCommand(args: string[]): Promise<void> {
7
+ const create = args[0] === "create";
8
+ const id = create ? undefined : parseTaskIdValue(args[1] ?? "");
9
+ if (!create && !id) throw new Error("taskId must be a positive integer (TNNNN accepted)");
10
+ const body: Record<string, string> = {};
11
+ for (let i = create ? 1 : 2; i < args.length; i++) {
12
+ const flag = args[i]!;
13
+ if (flag === "--json") continue;
14
+ const field = fields[flag];
15
+ if (!field || i + 1 >= args.length) throw new Error(`Unknown or missing task ${args[0]} option: ${flag}`);
16
+ body[field] = args[++i]!;
17
+ }
18
+ if (create && (!body.title || !body.intent)) throw new Error("task create requires --title and --intent");
19
+ if (!Object.keys(body).length) throw new Error("task update requires a field such as --on-deploy");
20
+ const result = await apiRequest(create ? "POST" : "PATCH", create ? "/api/tasks" : `/api/tasks/${id}`, body);
21
+ console.log(JSON.stringify(result, null, 2));
22
+ }
package/src/cli/task.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { editTaskCommand } from "./task-edit";
1
2
  // Task commands — #2518 review-hold control (`humwick task review-hold …`) + #2312
2
3
  // withdraw-land (`humwick task withdraw-land …`). Thin HTTP clients (same apiRequest seam
3
4
  // as workspace.ts): all policy — coordinator-only authorization, the expected-HEAD CAS,
@@ -11,7 +12,7 @@ function parseTaskId(raw: string | undefined): number | null {
11
12
  return raw === undefined ? null : parseTaskIdValue(raw);
12
13
  }
13
14
 
14
- export const TASK_USAGE = "Usage: humwick task get <taskId> [--verbose] [--json] | humwick task review-hold <taskId> <status|set|clear> [--expected-head SHA] [--reason TEXT] [--json] | humwick task withdraw-land <workspaceId> [--reason TEXT] [--json]";
15
+ export const TASK_USAGE = "Usage: humwick task create --title TEXT --intent TEXT [--on-deploy TEXT] | humwick task update <taskId> [--on-deploy TEXT] | humwick task get <taskId> [--verbose] [--json] | humwick task review-hold <taskId> <status|set|clear> [--expected-head SHA] [--reason TEXT] [--json] | humwick task withdraw-land <workspaceId> [--reason TEXT] [--json]";
15
16
 
16
17
  interface ReviewHoldStatusView {
17
18
  id: number;
@@ -48,6 +49,7 @@ function renderStatus(task: ReviewHoldStatusView): string {
48
49
 
49
50
  export async function handleTaskCommand(args: string[]): Promise<void> {
50
51
  const sub = args[0];
52
+ if (sub === "create" || sub === "update") return editTaskCommand(args);
51
53
  if (sub === "get") {
52
54
  const taskId = parseTaskId(args[1]);
53
55
  if (taskId === null) throw new Error(`taskId must be a positive integer (TNNNN accepted). ${TASK_USAGE}`);
@@ -1,7 +1,7 @@
1
1
  import { apiRequest } from "./_shared";
2
2
  import { probeWorkflowDeployment } from "../../scripts/wait-for-deploy";
3
3
 
4
- export const WORKFLOW_USAGE = "Usage: humwick workflow start <workflow> <taskId> [--json] | workflow get <runId> [--verbose] [--include-held] [--results-ref <id>] [--results-cursor <cursor>] [--json] | workflow resume <runId> [--activation <id>]";
4
+ export const WORKFLOW_USAGE = "Usage: humwick workflow start <workflow> <taskId> [--json] | workflow get <runId> [--verbose] [--include-held] [--results-ref <id>] [--results-cursor <cursor>] [--json] | workflow resume <runId> [--activation <id>] | workflow pause <runId> --reason <text> | workflow unpause <runId>";
5
5
 
6
6
  export async function handleWorkflowCommand(args: string[]): Promise<void> {
7
7
  const [command, runId, ...flags] = args;
@@ -25,7 +25,13 @@ export async function handleWorkflowCommand(args: string[]): Promise<void> {
25
25
  }
26
26
  return;
27
27
  }
28
- if (!["get", "resume"].includes(command ?? "") || !runId || runId.startsWith("--")) throw new Error(WORKFLOW_USAGE);
28
+ if (!["get", "resume", "pause", "unpause"].includes(command ?? "") || !runId || runId.startsWith("--")) throw new Error(WORKFLOW_USAGE);
29
+ if (command === "pause" || command === "unpause") {
30
+ // #3757 — a pause says why the run waits; the server refuses an empty reason.
31
+ if (command === "pause" ? flags.length !== 2 || flags[0] !== "--reason" || !flags[1]?.trim() : flags.length) throw new Error(WORKFLOW_USAGE);
32
+ console.log(JSON.stringify(await apiRequest("POST", `/api/workflows/runs/${encodeURIComponent(runId)}/${command}`, command === "pause" ? { reason: flags[1] } : {}), null, 2));
33
+ return;
34
+ }
29
35
  if (command === "resume") {
30
36
  if (flags.length && (flags[0] !== "--activation" || flags.length !== 2 || !flags[1])) throw new Error(WORKFLOW_USAGE);
31
37
  console.log(JSON.stringify(await apiRequest("POST", `/api/workflows/runs/${encodeURIComponent(runId)}/resume`, { ...(flags[1] ? { activationId: flags[1] } : {}) }), null, 2));
@@ -305,7 +305,7 @@ export { buildListCommandsQuery } from "./db/commands-list-query";
305
305
 
306
306
  export function listCommands(filters: CommandFilters = {}): Command[] {
307
307
  const { sql, params } = buildListCommandsQuery(filters);
308
- return (getDb().query(sql).all(...params as any[]) as CommandRow[]).map(rowToCommand);
308
+ return (getDb().query(sql).all(...params as any[]) as CommandRow[]).map((row) => rowToCommand(row, { slim: filters.slim }));
309
309
  }
310
310
 
311
311
 
@@ -814,8 +814,8 @@ export function updateCommand(id: string, input: UpdateCommandInput, opts: Updat
814
814
  return applyCommandUpdatePostCommit(result.previousStatus, result.command);
815
815
  }
816
816
 
817
- export function deleteCommand(id: string): boolean {
818
- const existing = getCommand(id);
817
+ /** Whether the public DELETE may cancel this command. Shared by the sync and async delete paths. */
818
+ export function commandDeletable(existing: Command | null): existing is Command {
819
819
  if (!existing) return false;
820
820
  if (existing.type === "workspace.merge" && !isLegacyUnfencedWorkspaceMerge(existing)) return false;
821
821
  if (!["pending", "accepted"].includes(existing.status)) return false;
@@ -823,7 +823,11 @@ export function deleteCommand(id: string): boolean {
823
823
  // cancelable (see updateCommand's post-claim-cancel guard). Report "cannot cancel" honestly rather
824
824
  // than a false success: without this the updateCommand guard would no-op the cancel yet we'd still
825
825
  // return true, telling the caller a claimed push was canceled when it was not.
826
- if (existing.status === "accepted" && isClaimGatedReviewCommand(existing.type)) return false;
826
+ return !(existing.status === "accepted" && isClaimGatedReviewCommand(existing.type));
827
+ }
828
+
829
+ export function deleteCommand(id: string): boolean {
830
+ if (!commandDeletable(getCommand(id))) return false;
827
831
  updateCommand(id, { status: "canceled", error: "canceled" });
828
832
  return true;
829
833
  }
@@ -916,7 +920,7 @@ export const ACCEPTED_BY_OWNER_SQL = `SELECT * FROM commands INDEXED BY idx_comm
916
920
  export function listAcceptedCommandsByOwner(target: string, ownerToken: string): Command[] {
917
921
  if (!target || !ownerToken) return [];
918
922
  const rows = getDb().query(ACCEPTED_BY_OWNER_SQL).all(target, ownerToken, Date.now()) as CommandRow[];
919
- return rows.map(rowToCommand);
923
+ return rows.map((row) => rowToCommand(row));
920
924
  }
921
925
 
922
926
  // #1452 round-12 HIGH#2 — RE-FENCE a claimed review-publish command IMMEDIATELY before the host's
@@ -1138,7 +1142,7 @@ export function activeRestartSensitiveCommands(now: number = Date.now()): Comman
1138
1142
  AND (type != 'agent.spawn' OR coalesce(json_extract(params, '$.registrationConfirmed'), 0) != 1)
1139
1143
  ORDER BY updated_at ASC`,
1140
1144
  )
1141
- .all(...CLAIMED_ACTIVE_STATUSES, now, now) as CommandRow[]).map(rowToCommand);
1145
+ .all(...CLAIMED_ACTIVE_STATUSES, now, now) as CommandRow[]).map((row) => rowToCommand(row));
1142
1146
  }
1143
1147
 
1144
1148
  // #1746 RC-2 / issue ask 4 — when a shutdown command settles WITHOUT a confirmed kill (the
@@ -11,6 +11,7 @@ import { STALE_TTL_MS } from "./config.ts";
11
11
  import { bufferedLiveAgentIds } from "./db/presence-buffer.ts";
12
12
  import { getGateway } from "./db/gateway.ts";
13
13
  import {
14
+ commandDeletable,
14
15
  commandUpdateHasPostCommitEffects,
15
16
  type CreateCommandOptions,
16
17
  type CreateCommandWriteArgs,
@@ -120,6 +121,13 @@ export async function updateCommandAsync(
120
121
  return result.command;
121
122
  }
122
123
 
124
+ /** Public DELETE (#1870 S6a): the cancel is a normal off-loop patch, so it never writes on the main thread. */
125
+ export async function deleteCommandAsync(id: string): Promise<boolean> {
126
+ if (!commandDeletable(await getCommandAsync(id))) return false;
127
+ await updateCommandAsync(id, { status: "canceled", error: "canceled" });
128
+ return true;
129
+ }
130
+
123
131
  /** Atomically cancel only if the command is still in the caller's expected live statuses. */
124
132
  export function cancelCommandIfStatusAsync(
125
133
  args: CancelCommandIfStatusWriteArgs,
@@ -6,7 +6,8 @@
6
6
 
7
7
  import { randomUUID } from "node:crypto";
8
8
  import { getGateway } from "./db/gateway.ts";
9
- import type { ConfigEntry } from "./types.ts";
9
+ import { agentProfileDeleteTarget, agentProfileWriteNamespace, savedAgentProfileEntry } from "./config-store.ts";
10
+ import type { AgentProfile, ConfigEntry } from "./types.ts";
10
11
 
11
12
  /**
12
13
  * Set a config entry off-loop (landmine #2). Routes through a single serialized `config.set` writer op
@@ -37,3 +38,14 @@ export function setConfigAsync<T = unknown>(
37
38
  export function deleteConfigAsync(namespace: string, key: string, updatedBy?: string): Promise<boolean> {
38
39
  return getGateway().write("config.delete", { namespace, key, updatedBy }) as unknown as Promise<boolean>;
39
40
  }
41
+
42
+ /** `setAgentProfile` on the writer thread (#1870 S7). */
43
+ export async function setAgentProfileAsync(profile: AgentProfile, updatedBy?: string, opts: { overrideBuiltIn?: boolean } = {}): Promise<ConfigEntry<AgentProfile>> {
44
+ return savedAgentProfileEntry(await setConfigAsync(agentProfileWriteNamespace(profile, opts), profile.name, profile, updatedBy));
45
+ }
46
+
47
+ /** `deleteAgentProfile` on the writer thread (#1870 S7). */
48
+ export function deleteAgentProfileAsync(name: string, updatedBy?: string, opts: { resetBuiltInOverride?: boolean } = {}): Promise<boolean> {
49
+ const target = agentProfileDeleteTarget(name, opts);
50
+ return deleteConfigAsync(target.namespace, target.key, updatedBy);
51
+ }
@@ -1120,24 +1120,35 @@ export function listAgentProfiles(): ConfigEntry<AgentProfile>[] {
1120
1120
  ].sort((a, b) => Number(Boolean(b.value.builtIn)) - Number(Boolean(a.value.builtIn)) || a.key.localeCompare(b.key));
1121
1121
  }
1122
1122
 
1123
+ /** Where a profile write lands: a built-in's override row, else its own row. The rollback snapshot is
1124
+ * dropped by setConfigInternal on an override write (#1963 N10). */
1125
+ export function agentProfileWriteNamespace(profile: AgentProfile, opts: { overrideBuiltIn?: boolean } = {}): string {
1126
+ return BUILT_IN_AGENT_PROFILE_NAMES.has(profile.name) && opts.overrideBuiltIn ? AGENT_PROFILE_OVERRIDE_NAMESPACE : AGENT_PROFILE_NAMESPACE;
1127
+ }
1128
+
1129
+ /** The stored entry a profile write returns: an override reads back merged over its built-in. */
1130
+ export function savedAgentProfileEntry(saved: ConfigEntry<AgentProfile>): ConfigEntry<AgentProfile> {
1131
+ return saved.namespace === AGENT_PROFILE_OVERRIDE_NAMESPACE ? getAgentProfile(saved.key)! : applyProfilePromptEntry(saved)!;
1132
+ }
1133
+
1134
+ /** Sync twin for tests and boot; runtime callers use `setAgentProfileAsync` (#1870 S7). */
1123
1135
  export function setAgentProfile(profile: AgentProfile, updatedBy?: string, opts: { overrideBuiltIn?: boolean } = {}): ConfigEntry<AgentProfile> {
1124
- if (BUILT_IN_AGENT_PROFILE_NAMES.has(profile.name) && opts.overrideBuiltIn) {
1125
- // The rollback snapshot is dropped by setConfigInternal on this override write (#1963 N10).
1126
- setConfig(AGENT_PROFILE_OVERRIDE_NAMESPACE, profile.name, profile, updatedBy);
1127
- return getAgentProfile(profile.name)!;
1128
- }
1129
- return applyProfilePromptEntry(setConfig(AGENT_PROFILE_NAMESPACE, profile.name, profile, updatedBy))!;
1136
+ return savedAgentProfileEntry(setConfig(agentProfileWriteNamespace(profile, opts), profile.name, profile, updatedBy));
1130
1137
  }
1131
1138
 
1132
- export function deleteAgentProfile(name: string, updatedBy?: string, opts: { resetBuiltInOverride?: boolean } = {}): boolean {
1133
- // #2791 — delete resolves case-insensitively to the stored key, so DELETE /qa drops "QA".
1139
+ /** The row a profile delete removes. #2791 — resolves case-insensitively, so DELETE /qa drops "QA". The
1140
+ * rollback snapshot is dropped by deleteConfigInternal on an override delete (#1963 N10). */
1141
+ export function agentProfileDeleteTarget(name: string, opts: { resetBuiltInOverride?: boolean } = {}): { namespace: string; key: string } {
1134
1142
  const canonical = resolveAgentProfileKey(name) ?? name;
1135
- if (BUILT_IN_AGENT_PROFILE_NAMES.has(canonical)) {
1136
- // The rollback snapshot is dropped by deleteConfigInternal on this override delete (#1963 N10).
1137
- if (opts.resetBuiltInOverride) return deleteConfig(AGENT_PROFILE_OVERRIDE_NAMESPACE, canonical, updatedBy);
1138
- throw new ValidationError("built-in agent profiles are managed by Relay");
1139
- }
1140
- return deleteConfig(AGENT_PROFILE_NAMESPACE, canonical, updatedBy);
1143
+ if (!BUILT_IN_AGENT_PROFILE_NAMES.has(canonical)) return { namespace: AGENT_PROFILE_NAMESPACE, key: canonical };
1144
+ if (opts.resetBuiltInOverride) return { namespace: AGENT_PROFILE_OVERRIDE_NAMESPACE, key: canonical };
1145
+ throw new ValidationError("built-in agent profiles are managed by Relay");
1146
+ }
1147
+
1148
+ /** Sync twin for tests; runtime callers use `deleteAgentProfileAsync` (#1870 S7). */
1149
+ export function deleteAgentProfile(name: string, updatedBy?: string, opts: { resetBuiltInOverride?: boolean } = {}): boolean {
1150
+ const target = agentProfileDeleteTarget(name, opts);
1151
+ return deleteConfig(target.namespace, target.key, updatedBy);
1141
1152
  }
1142
1153
 
1143
1154
  export function getManagedAgentState(policyName: string): ManagedAgentState | null {
@@ -4,7 +4,7 @@
4
4
  import { activityWriteIdempotencyKey } from "./activity-write-ops.ts";
5
5
  import { getGateway } from "./gateway.ts";
6
6
  import type { ActivityEvent, ActivityEventInput } from "../types";
7
- import type { AgentIssueActivitySummary, AgentTimelineEntry, IssueActivityAction, IssueActivityAgentSummary, RecordIssueActivityInput } from "./activity.ts";
7
+ import type { AgentIssueActivitySummary, CreateActivityEventResult, AgentTimelineEntry, IssueActivityAction, IssueActivityAgentSummary, RecordIssueActivityInput } from "./activity.ts";
8
8
 
9
9
  export async function listActivityEventsAsync(input: {
10
10
  operatorId?: string;
@@ -43,6 +43,11 @@ export async function createActivityEventAsync(input: ActivityEventInput, idempo
43
43
  return (await getGateway().write("activity.create", { input }, { idempotencyKey: key })) as unknown as ActivityEvent;
44
44
  }
45
45
 
46
+ /** Creates by `clientId` and reports whether this call created the row (false: the id already existed). */
47
+ export async function createActivityEventByClientIdAsync(input: ActivityEventInput & { clientId: string }): Promise<CreateActivityEventResult> {
48
+ return (await getGateway().write("activity.createByClientId", { input })) as unknown as CreateActivityEventResult;
49
+ }
50
+
46
51
  /** See `createActivityEventAsync` — same `idempotent: false` / key-derivation contract. */
47
52
  export async function recordIssueActivityAsync(input: RecordIssueActivityInput, idempotencyKey?: string): Promise<ActivityEvent> {
48
53
  const key = activityWriteIdempotencyKey(idempotencyKey, input.clientId);
@@ -14,7 +14,8 @@
14
14
  // "do not blindly retry").
15
15
 
16
16
  import { randomUUID } from "node:crypto";
17
- import { createActivityEvent, recordIssueActivity } from "./activity.ts";
17
+ import { createActivityEvent, createActivityEventWithResult, recordIssueActivity } from "./activity.ts";
18
+ import type { CreateActivityEventResult } from "./activity.ts";
18
19
  import type { ActivityEvent, ActivityEventInput } from "../types";
19
20
  import type { RecordIssueActivityInput } from "./activity.ts";
20
21
  import type { WriterOpContext, WriterOpSpec } from "./write-ops.ts";
@@ -28,7 +29,18 @@ export const ACTIVITY_WRITE_OPS: Readonly<Record<string, WriterOpSpec>> = Object
28
29
  idempotent: false,
29
30
  fn: (args: ActivityCreateOpArgs, ctx: WriterOpContext): ActivityEvent => createActivityEvent(args.input, ctx.now),
30
31
  },
32
+ // Dedups on `clientId` inside the body, so a replay reports `created: false` truthfully; the idempotency
33
+ // ledger would instead replay the first `created: true` (#1870 S7, operator alarms).
34
+ "activity.createByClientId": {
35
+ offLoop: true,
36
+ idempotent: true,
37
+ fn: (args: ActivityCreateOpArgs, ctx: WriterOpContext): CreateActivityEventResult => {
38
+ if (!args.input.clientId) throw new Error("activity.createByClientId requires input.clientId");
39
+ return createActivityEventWithResult(args.input, ctx.now);
40
+ },
41
+ },
31
42
  "activity.recordIssueActivity": {
43
+ offLoop: true,
32
44
  idempotent: false,
33
45
  fn: (args: ActivityRecordIssueOpArgs, ctx: WriterOpContext): ActivityEvent => recordIssueActivity(args.input, ctx.now),
34
46
  },
@@ -1,18 +1,5 @@
1
- import { randomUUID } from "node:crypto";
2
- import { isRecord, stringValue, isMechanicalMessageKind } from "@humwick/sdk";
3
- import { ORCHESTRATOR_PROTOCOL_VERSION, VERSION } from "../config.ts";
1
+ import { isRecord, stringValue } from "@humwick/sdk";
4
2
  import { parseJson } from "../utils";
5
- import { isLiveIsolatedWorkspace } from "../workspace-phase";
6
- import {
7
- CONTRACT_REQUIREMENTS,
8
- contractCompatibility,
9
- parseRuntimeCapabilities,
10
- parseRuntimeContracts,
11
- parseRuntimePackage,
12
- type RuntimeContracts,
13
- } from "../contracts";
14
- import { STALE_TTL_MS, DAY_MS, CLAIM_LEASE_MS, POOL_CLAIM_LEASE_MS, WORKSPACE_MERGE_LEASE_MS } from "../config";
15
- import { matchAgents } from "../agent-ref";
16
3
  import { getDb, ValidationError } from "./connection.ts";
17
4
  import { rowToActivityEvent } from "./mappers.ts";
18
5
  import { getIssueRef, resolveIssueRefFromString } from "./issue-refs.ts";
@@ -66,6 +66,7 @@ export const AGENT_LIFECYCLE_WRITE_OPS: Readonly<Record<string, WriterOpSpec>> =
66
66
  instanceSuperseded(args.agentId, args.expectedEpoch) ? [] : releaseWorkspaceClaimsForAgent(args.agentId, args.now),
67
67
  },
68
68
  "agents.seedContinuationObjective": {
69
+ offLoop: true,
69
70
  idempotent: true,
70
71
  fn: (args: AgentsSeedContinuationOpArgs, ctx: WriterOpContext): null => {
71
72
  seedContinuationObjectiveFromSpawnPrompt(args.agentId, args.spawnRequestId, ctx.now);
@@ -1,4 +1,3 @@
1
- import { mainThreadRegistry } from "./main-thread-state";
2
1
  // Agent meta has two storage temperatures: small runtime fields change on status frames, while the
3
2
  // launch projection is fixed for the life of a runner instance. Keep the latter out of `agents.meta`
4
3
  // so a hot status update never has to carry it through its JSON patch.
@@ -7,7 +6,8 @@ import { isRecord } from "@humwick/sdk";
7
6
 
8
7
  export const AGENT_META_WARN_BYTES = 64 * 1024;
9
8
 
10
- const warnedOversizeMeta = mainThreadRegistry("agent-meta.ts:warnedOversizeMeta", new Set<string>());
9
+ // Log dedupe only, so a per-thread copy is correct: the writer thread warns once itself (#1870 S6c).
10
+ const warnedOversizeMeta = new Set<string>();
11
11
 
12
12
  export interface StoredAgentMeta {
13
13
  meta: Record<string, unknown>;
@@ -23,10 +23,12 @@ export interface AgentOwnershipReassignOpArgs { fromUserId: string; toUserId: st
23
23
 
24
24
  export const AGENT_OWNERSHIP_WRITE_OPS: Readonly<Record<string, WriterOpSpec>> = Object.freeze({
25
25
  "agent-ownership.set": {
26
+ offLoop: true,
26
27
  idempotent: true,
27
28
  fn: (args: AgentOwnershipSetOpArgs, ctx: WriterOpContext): AgentOwnership => setAgentOwnership(args.input, ctx.now),
28
29
  },
29
30
  "agent-ownership.delete": {
31
+ offLoop: true,
30
32
  idempotent: true,
31
33
  fn: (args: AgentOwnershipDeleteOpArgs): null => {
32
34
  deleteAgentOwnership(args.agentId);
@@ -36,6 +38,7 @@ export const AGENT_OWNERSHIP_WRITE_OPS: Readonly<Record<string, WriterOpSpec>> =
36
38
  // Back-compat migration sweep (#392) — re-running converges (`INSERT ... WHERE NOT EXISTS`), so no
37
39
  // args and idempotent: true.
38
40
  "agent-ownership.seedDefault": {
41
+ offLoop: true,
39
42
  idempotent: true,
40
43
  fn: (_args: unknown, ctx: WriterOpContext): null => {
41
44
  seedDefaultAgentOwnership(ctx.now);
@@ -44,6 +47,7 @@ export const AGENT_OWNERSHIP_WRITE_OPS: Readonly<Record<string, WriterOpSpec>> =
44
47
  },
45
48
  // #3567 H4 admin handover — re-applying finds nothing left on the old owner, so it converges.
46
49
  "agent-ownership.reassign": {
50
+ offLoop: true,
47
51
  idempotent: true,
48
52
  fn: (args: AgentOwnershipReassignOpArgs, ctx: WriterOpContext): string[] => reassignAgentOwnership(args.fromUserId, args.toUserId, ctx.now),
49
53
  },
@@ -1,18 +1,4 @@
1
- import { randomUUID } from "node:crypto";
2
- import { isRecord, stringValue, isMechanicalMessageKind } from "@humwick/sdk";
3
- import { ORCHESTRATOR_PROTOCOL_VERSION, VERSION } from "../config.ts";
4
- import { parseJson } from "../utils";
5
- import { isLiveIsolatedWorkspace } from "../workspace-phase";
6
- import {
7
- CONTRACT_REQUIREMENTS,
8
- contractCompatibility,
9
- parseRuntimeCapabilities,
10
- parseRuntimeContracts,
11
- parseRuntimePackage,
12
- type RuntimeContracts,
13
- } from "../contracts";
14
- import { STALE_TTL_MS, DAY_MS, CLAIM_LEASE_MS, POOL_CLAIM_LEASE_MS, WORKSPACE_MERGE_LEASE_MS } from "../config";
15
- import { matchAgents } from "../agent-ref";
1
+ import { STALE_TTL_MS } from "../config";
16
2
  import { ROSTER_AGENT_COLUMNS } from "./agent-card-read.ts";
17
3
  import { getDb } from "./connection.ts";
18
4
  import { rowToAgent } from "./mappers.ts";
@@ -11,8 +11,16 @@
11
11
 
12
12
  import { agentSessionGuardResult } from "./agent-predicates.ts";
13
13
  import {
14
+ agentLifecycleWriteArgs,
15
+ bufferOrBypassHeartbeat,
14
16
  getAgent,
17
+ prepareAgentLifecycleUpdate,
15
18
  runBeforeAgentStatusTransitionHandlers,
19
+ type AgentLifecycleUpdate,
20
+ type AgentLifecycleUpdateResult,
21
+ type HeartbeatInput,
22
+ type OrphanedTokenPage,
23
+ type OrphanedTokenRevocation,
16
24
  validateAgentSession,
17
25
  type UpsertAgentTxResult,
18
26
  type UpsertAgentOptions,
@@ -52,8 +60,12 @@ export async function upsertAgentAsync(
52
60
  ): Promise<AgentCard> {
53
61
  // #2111 — allocated before the await, per the ordinal-timing rule at the top of setStatusAsync.
54
62
  const seq = nextLifecycleSeq();
55
- const { agent, contextState } = (await getGateway().write("agents.upsert", { input, opts, seq })) as unknown as UpsertAgentTxResult;
56
- if (contextState) await recordContextSnapshotAsync(input.id, contextState);
63
+ return finishAgentUpsertAsync(input.id, (await getGateway().write("agents.upsert", { input, opts, seq })) as unknown as UpsertAgentTxResult);
64
+ }
65
+
66
+ /** The registration's follow-ups after its row committed. Shared with `upsertOrchestratorAsync`. */
67
+ export async function finishAgentUpsertAsync(inputId: string, { agent, contextState }: UpsertAgentTxResult): Promise<AgentCard> {
68
+ if (contextState) await recordContextSnapshotAsync(inputId, contextState);
57
69
  evictPendingPresence(agent.id);
58
70
  // Both effects settle on the writer. The `spawnRequestId` guard is `seedContinuationObjectiveFromSpawnPrompt`'s
59
71
  // own first condition, hoisted here so a registration without one costs no thread hop at all.
@@ -205,6 +217,31 @@ export async function recordAgentExitDiagnosticsAsync(
205
217
  return getAgentAsync(id);
206
218
  }
207
219
 
220
+ /** `applyAgentLifecycleUpdate` with the write on the writer thread (#1870 S7). The guard, the `before` read
221
+ * and the transition handlers stay on the main thread ahead of submission; the op CAS-fences on
222
+ * `before.epoch`, so a takeover that lands in the hop turns this into a no-op instead of a clobber. */
223
+ export async function applyAgentLifecycleUpdateAsync(
224
+ id: string,
225
+ update: AgentLifecycleUpdate,
226
+ guard?: AgentSessionGuard,
227
+ now: number = Date.now(),
228
+ ): Promise<AgentLifecycleUpdateResult> {
229
+ // Allocated first, per the ordinal-timing rule at the top of setStatusAsync.
230
+ const seq = nextLifecycleSeq();
231
+ const before = prepareAgentLifecycleUpdate(id, update, guard);
232
+ if (!before) return { ok: false, changed: false, metaWriteBytes: 0, before: null, after: null };
233
+ const result = (await getGateway().write("agents.applyLifecycleUpdate", agentLifecycleWriteArgs(before, update, seq, now))) as unknown as Omit<AgentLifecycleUpdateResult, "ok" | "before">;
234
+ return { ok: true, before, ...result };
235
+ }
236
+
237
+ /** `recordHeartbeat` for the transports: a steady-state beat is buffered at once; a presence-critical one
238
+ * writes on the writer thread (#1870 S7). */
239
+ export async function recordHeartbeatAsync(id: string, opts: HeartbeatInput = {}): Promise<boolean> {
240
+ const beat = bufferOrBypassHeartbeat(id, opts);
241
+ if (typeof beat === "boolean") return beat;
242
+ return (await applyAgentLifecycleUpdateAsync(id, beat, opts.guard, opts.now ?? Date.now())).changed;
243
+ }
244
+
208
245
  export async function heartbeatAsync(
209
246
  id: string,
210
247
  guard?: AgentSessionGuard,
@@ -315,14 +352,28 @@ export async function deleteAgentAsync(id: string): Promise<{ ok: boolean; error
315
352
  return deleted ? { ok: true } : { ok: false, error: "agent not found" };
316
353
  }
317
354
 
355
+ // #3776 — the sweep is one short writer op per page of agents (each agent's lineage revoke stays atomic), so
356
+ // other writes interleave between pages. It stops when aborted or past its budget; the next run starts over.
357
+ const ORPHAN_SWEEP_PAGE_SIZE = 8;
358
+ const ORPHAN_SWEEP_BUDGET_MS = 30_000;
359
+
318
360
  export async function revokeOrphanedRuntimeTokensAsync(
319
361
  graceMs: number = STALE_TTL_MS,
320
362
  lagGraceMs = 0,
321
- ): Promise<Array<{ agentId: string; jtis: string[] }>> {
322
- return (await getGateway().write("agents.revokeOrphanedTokens", { graceMs, lagGraceMs })) as unknown as Array<{
323
- agentId: string;
324
- jtis: string[];
325
- }>;
363
+ options: { signal?: AbortSignal; budgetMs?: number; pageSize?: number } = {},
364
+ ): Promise<{ revoked: OrphanedTokenRevocation[]; complete: boolean }> {
365
+ const { signal, budgetMs = ORPHAN_SWEEP_BUDGET_MS, pageSize = ORPHAN_SWEEP_PAGE_SIZE } = options;
366
+ const deadline = Date.now() + budgetMs;
367
+ const revoked: OrphanedTokenRevocation[] = [];
368
+ let afterId: string | undefined;
369
+ do {
370
+ if (signal?.aborted) return { revoked, complete: false };
371
+ const page = (await getGateway().write("agents.revokeOrphanedTokens", { graceMs, lagGraceMs, afterId, limit: pageSize })) as unknown as OrphanedTokenPage;
372
+ revoked.push(...page.revoked);
373
+ if (page.nextAfterId === null) return { revoked, complete: true };
374
+ afterId = page.nextAfterId;
375
+ } while (Date.now() < deadline);
376
+ return { revoked, complete: false };
326
377
  }
327
378
 
328
379
  export async function revokeRuntimeTokensForAgentAsync(