@humwick/server 0.130.2 → 0.130.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (587) hide show
  1. package/bundled/prompts/manifest.json +1 -0
  2. package/bundled/prompts/task.hydration.md +1 -1
  3. package/bundled/roles/builder.md +1 -1
  4. package/docs/guides/sub-coordinators.md +12 -5
  5. package/docs/guides/tools-coordination.md +1 -0
  6. package/docs/guides/workflows.md +16 -0
  7. package/docs/openapi.json +175 -4
  8. package/package.json +8 -7
  9. package/public/assets/{MessageBubble-BWLqMGKG.js → MessageBubble-BOkmEvgZ.js} +2 -2
  10. package/public/assets/{PlanGraphPanel-3TlIQpjk.js → PlanGraphPanel-BkaDJp0r.js} +2 -2
  11. package/public/assets/{TraceView-Wimm0Czv.js → TraceView-B99Uu2yT.js} +2 -2
  12. package/public/assets/{VaultDecisionCard-Bau_vNax.js → VaultDecisionCard-DUIfTTFF.js} +2 -2
  13. package/public/assets/{activity-CrSurn-r.js → activity-ZIX0N1El.js} +2 -2
  14. package/public/assets/{agent-lineage-CknAJKcb.js → agent-lineage-kq9TPYFw.js} +2 -2
  15. package/public/assets/agent-liveness-DSQY1Aa_.js +2 -0
  16. package/public/assets/{agent-name-DKbiIhBo.js → agent-name-C_7F4DFR.js} +2 -2
  17. package/public/assets/{agent-profiles-DwF6EQ4R.js → agent-profiles-BNOcrGcY.js} +2 -2
  18. package/public/assets/{agent-project-badge-Diz4Sjjb.js → agent-project-badge-B6yAh70a.js} +2 -2
  19. package/public/assets/{agent-tools-CRhpRQza.js → agent-tools-CcrhmOzF.js} +2 -2
  20. package/public/assets/{agent-type-icon-LrUEU3lB.js → agent-type-icon-CXSLrKiq.js} +2 -2
  21. package/public/assets/{agents-B-v66C-4.js → agents-DIZG97_X.js} +2 -2
  22. package/public/assets/{analytics-BqCb-LKi.js → analytics-rasmoPjR.js} +2 -2
  23. package/public/assets/{artifact-viewer-kf4pGuAR.js → artifact-viewer-tBpTrEPb.js} +2 -2
  24. package/public/assets/{artifacts-DJevXcvg.js → artifacts-BNkrynP1.js} +2 -2
  25. package/public/assets/{artifacts-D32zyw4Y.js → artifacts-Bjq-ZyBA.js} +2 -2
  26. package/public/assets/{automation-BpRajx_5.js → automation-Cx422L3L.js} +2 -2
  27. package/public/assets/{backlog-D_UjTdzX.js → backlog-RuFQ8PUu.js} +2 -2
  28. package/public/assets/{busy-health-N3VEh4ao.js → busy-health-BvNQ9g5x.js} +2 -2
  29. package/public/assets/{channels-9jopungq.js → channels-DNbKAS0I.js} +2 -2
  30. package/public/assets/{chat-CSXrTB3C.js → chat-Bp3Fx73h.js} +3 -3
  31. package/public/assets/{code-preview-p1NnmqK0.js → code-preview-KIRZA6cz.js} +2 -2
  32. package/public/assets/{connectors-CZdqlDNL.js → connectors-k7vO4vvc.js} +2 -2
  33. package/public/assets/{cosmos-BgdsE4dG.js → cosmos-BuVoq1aw.js} +2 -2
  34. package/public/assets/{crate-agent-30aXTKva.js → crate-agent-irqlt4NH.js} +2 -2
  35. package/public/assets/{dependency-graph-B_jvw2GE.js → dependency-graph-D_rurNBz.js} +2 -2
  36. package/public/assets/{deploys-DoN6bAb5.js → deploys-yzZqIpxE.js} +2 -2
  37. package/public/assets/{display-BrKWFGJz.js → display-BemJG1f_.js} +2 -2
  38. package/public/assets/{files-D6XEu8Ng.js → files-DXWcrxOz.js} +2 -2
  39. package/public/assets/{fleet-C7jQWbw2.js → fleet-TEuOCZXE.js} +2 -2
  40. package/public/assets/{fleet-metrics-BiDs3MLx.js → fleet-metrics-BGoDAFI-.js} +2 -2
  41. package/public/assets/{formatted-body-impl-DyG0WsQS.js → formatted-body-impl-BB9eq9nN.js} +2 -2
  42. package/public/assets/{formatted-body-CQBwc1fb.js → formatted-body-qy2nTD_1.js} +3 -3
  43. package/public/assets/{host-providers-dialog-DKaW31dN.js → host-providers-dialog-BGPHxuwT.js} +2 -2
  44. package/public/assets/{index-CRHep3M9.js → index-BC5XZc0z.js} +5 -5
  45. package/public/assets/{index-MYAIwYTX.css → index-D8Rp9pz2.css} +1 -1
  46. package/public/assets/{insights-CzrtkpOE.js → insights-C-KQG0h-.js} +2 -2
  47. package/public/assets/{integrations-aVsUmigt.js → integrations-ClPRPyKU.js} +2 -2
  48. package/public/assets/{issue-ref-chip-BnSlM_95.js → issue-ref-chip-DbH8J9Ak.js} +2 -2
  49. package/public/assets/{log-viewer-qe7Ee47Z.js → log-viewer-PH4zVn24.js} +2 -2
  50. package/public/assets/{maintenance-NfjqFZAk.js → maintenance-5lV43uTL.js} +2 -2
  51. package/public/assets/{managed-agents-BiH0HgaE.js → managed-agents-BdgU35aX.js} +2 -2
  52. package/public/assets/{markdown-preview-BixaT3Xr.js → markdown-preview-DNT4OUwQ.js} +3 -3
  53. package/public/assets/{markdown-preview-impl-DNGlWIS3.js → markdown-preview-impl-CnCcPy0A.js} +2 -2
  54. package/public/assets/{memory-DNsCl6UX.js → memory--GgWH1gH.js} +2 -2
  55. package/public/assets/{messages-DopU1L1X.js → messages-CzFl_97O.js} +2 -2
  56. package/public/assets/{orchestrators-Cq7ckLQT.js → orchestrators-CVvJVkzv.js} +2 -2
  57. package/public/assets/overview-DsrZvE86.js +2 -0
  58. package/public/assets/{pairs-DE_m6rrE.js → pairs-D2d0d2py.js} +2 -2
  59. package/public/assets/{profile-icon-B3rfh77F.js → profile-icon-_SMuSbYF.js} +2 -2
  60. package/public/assets/{project-profile-instructions-DQWZE0kA.js → project-profile-instructions-Apn15y43.js} +2 -2
  61. package/public/assets/{projects-Br2Bd2h1.js → projects-D3B6kto5.js} +2 -2
  62. package/public/assets/{prompt-settings-BKvFsvGD.js → prompt-settings-DnPB6Ez6.js} +2 -2
  63. package/public/assets/provider-display-bWwiuedV.js +2 -0
  64. package/public/assets/{registry-Cw38_6gW.js → registry-D790cIhN.js} +2 -2
  65. package/public/assets/run-card-BCro-Odj.js +2 -0
  66. package/public/assets/{secrets-DxI-vzSq.js → secrets-CVXvfvbU.js} +2 -2
  67. package/public/assets/{service-access-settings-D9s_VJbd.js → service-access-settings-D3Xxqb9w.js} +2 -2
  68. package/public/assets/{settings-CKZTVGIx.js → settings-CoUC7Jci.js} +2 -2
  69. package/public/assets/shipping-D_Lp9n_J.js +2 -0
  70. package/public/assets/{stats-DyUBKyWI.js → stats-CXiNjB9S.js} +2 -2
  71. package/public/assets/{status-dot-DYqlEUhJ.js → status-dot-C703YIy1.js} +2 -2
  72. package/public/assets/store-BgDALGIX.js +9 -0
  73. package/public/assets/{tasks-Bh7Chr4-.js → tasks-DO0WpJCx.js} +2 -2
  74. package/public/assets/{teams-bowI7qVY.js → teams-FGkdi_tL.js} +2 -2
  75. package/public/assets/{terminal-viewer-BgVLHdsU.js → terminal-viewer-Dh8dSC7e.js} +3 -3
  76. package/public/assets/{terminal-viewer-impl-Dz22UPy6.js → terminal-viewer-impl-B5Zriip3.js} +2 -2
  77. package/public/assets/{use-agent-terminal-DSQQ-iY7.js → use-agent-terminal-CRUVDvLC.js} +2 -2
  78. package/public/assets/{use-decisions-samMQhQf.js → use-decisions-CoaqRh4l.js} +2 -2
  79. package/public/assets/{use-project-list-59LheVP7.js → use-project-list-X-BgT53X.js} +2 -2
  80. package/public/assets/{use-selectors-BZa25xEB.js → use-selectors-0X_UZkk-.js} +2 -2
  81. package/public/assets/{user-avatar-DHxlvUvV.js → user-avatar-CkLhVBGu.js} +2 -2
  82. package/public/assets/{versioned-prompt-editor-BmED5DnJ.js → versioned-prompt-editor-BkTi7eim.js} +2 -2
  83. package/public/assets/{work-queue-CSvpuNLz.js → work-queue-DDOLEyKo.js} +2 -2
  84. package/public/assets/{workflow-marker-DAZcj5qt.js → workflow-marker-CzOsB8BM.js} +2 -2
  85. package/public/assets/{workflows-DdmxRSyg.js → workflows-DJilvmRM.js} +2 -2
  86. package/public/assets/{workspaces-BxeKfVuF.js → workspaces-CHcfbLR3.js} +2 -2
  87. package/public/cosmos.html +4 -4
  88. package/public/index.html +29 -29
  89. package/runner/plugins/claude/.claude-plugin/plugin.json +1 -1
  90. package/runner/src/adapters/claude-api.ts +1 -1
  91. package/runner/src/adapters/claude-headless.ts +1 -1
  92. package/runner/src/adapters/claude-launch-exit.ts +26 -0
  93. package/runner/src/adapters/claude-session-probe.ts +1 -1
  94. package/runner/src/adapters/claude-tmux.ts +38 -10
  95. package/runner/src/adapters/claude.ts +28 -75
  96. package/runner/src/adapters/provider-quota-harvest.ts +20 -71
  97. package/runner/src/agent-runner/lifecycle-milestones.ts +2 -2
  98. package/runner/src/agent-runner/liveness-exchange.ts +104 -0
  99. package/runner/src/claude-system-prompt.ts +49 -0
  100. package/runner/src/launch-assembly.ts +14 -10
  101. package/runner/src/profile-home.ts +1 -1
  102. package/runner/src/project-instructions.ts +2 -2
  103. package/runner/src/provider-command.ts +18 -0
  104. package/runner/src/provider-setup.ts +3 -2
  105. package/runner/src/runner-core.ts +14 -15
  106. package/runner/src/runner-helpers.ts +0 -8
  107. package/runner/src/session-scratch.ts +6 -2
  108. package/runner/test-support/provisioning-context.ts +4 -2
  109. package/runner/test-support/runner-harness.ts +2 -1
  110. package/scripts/steward-policy-cleanup.ts +2 -2
  111. package/src/agent-authz.ts +2 -2
  112. package/src/agent-drive-arbiter.ts +3 -8
  113. package/src/agent-exit-cleanup-async.ts +2 -2
  114. package/src/agent-exit-cleanup.ts +23 -19
  115. package/src/agent-lifecycle-events.ts +112 -85
  116. package/src/agent-ref.ts +3 -2
  117. package/src/agent-status-events.ts +43 -14
  118. package/src/auth/login-link.ts +35 -0
  119. package/src/auth/password.ts +4 -3
  120. package/src/auth/session-refresh.ts +1 -1
  121. package/src/auth/session.test-fixture.ts +2 -2
  122. package/src/auth/session.ts +7 -7
  123. package/src/automations/crud.ts +8 -81
  124. package/src/automations/dispatch.ts +33 -58
  125. package/src/automations/index.ts +1 -1
  126. package/src/automations/persistence.ts +65 -0
  127. package/src/automations/precheck.ts +18 -16
  128. package/src/automations/read-async.ts +11 -0
  129. package/src/automations/reconcile.ts +30 -39
  130. package/src/automations/storage.ts +33 -10
  131. package/src/automations/write-async.ts +33 -0
  132. package/src/branch-land-notice.ts +3 -3
  133. package/src/branch-landed.ts +25 -23
  134. package/src/bus.ts +21 -19
  135. package/src/cd-deploy-notify.ts +2 -2
  136. package/src/ci/notifications.ts +4 -4
  137. package/src/ci/root-missing.ts +3 -3
  138. package/src/ci/transition-notify.ts +5 -5
  139. package/src/claude-app/oauth-http.ts +3 -3
  140. package/src/cli/index.ts +7 -1
  141. package/src/cli/init.ts +53 -0
  142. package/src/cli/orchestrator.ts +1 -7
  143. package/src/cli/setup.ts +6 -4
  144. package/src/cli/workflow.ts +12 -3
  145. package/src/commands-db.ts +4 -4
  146. package/src/commands-write.ts +8 -0
  147. package/src/compaction-watch.ts +10 -8
  148. package/src/config-store-write.ts +2 -2
  149. package/src/config.ts +4 -11
  150. package/src/connectors.ts +10 -8
  151. package/src/context-advisory.ts +22 -21
  152. package/src/db/agent-action-write.ts +36 -0
  153. package/src/db/agent-control-async.ts +9 -0
  154. package/src/db/agent-control-write.ts +49 -0
  155. package/src/db/agent-effort-observe.ts +22 -0
  156. package/src/db/agent-meta.ts +14 -3
  157. package/src/db/agents-async.ts +8 -5
  158. package/src/db/agents.ts +15 -12
  159. package/src/db/auth-identities.ts +2 -2
  160. package/src/db/automation-read-ops.ts +11 -0
  161. package/src/db/automation-write-ops.ts +46 -0
  162. package/src/db/blackboard.ts +2 -2
  163. package/src/db/browser-session-write.ts +32 -0
  164. package/src/db/bus-outbox-write-ops.ts +3 -1
  165. package/src/db/checkout-refresh-schema.ts +47 -0
  166. package/src/db/checkout-refresh.ts +5 -5
  167. package/src/db/command-agent-profile-backfill.ts +39 -0
  168. package/src/db/command-agent-profiles.ts +5 -1
  169. package/src/db/command-retention.ts +126 -0
  170. package/src/db/commands-planner-stats.test-fixture.ts +8 -0
  171. package/src/db/commands-read-ops.ts +16 -0
  172. package/src/db/connection.ts +1 -182
  173. package/src/db/context-carry-write.ts +74 -0
  174. package/src/db/continuations.ts +4 -4
  175. package/src/db/db-error-registry.ts +12 -0
  176. package/src/db/gateway.ts +26 -133
  177. package/src/db/history-retention-ops.ts +13 -0
  178. package/src/db/inbox-async.ts +2 -2
  179. package/src/db/inbox-read-ops.ts +1 -1
  180. package/src/db/inbox.ts +9 -3
  181. package/src/db/issue-associations.ts +2 -2
  182. package/src/db/issue-refs-async.ts +4 -1
  183. package/src/db/issue-refs-read-ops.ts +6 -0
  184. package/src/db/issue-refs.ts +8 -8
  185. package/src/db/kernel-event-log-schema.ts +2 -0
  186. package/src/db/land-boundaries.ts +51 -5
  187. package/src/db/land-gate-attestations.ts +2 -2
  188. package/src/db/landing-receipts.ts +2 -2
  189. package/src/db/lifecycle-context-write.ts +5 -0
  190. package/src/db/lifecycle-tail-async.ts +76 -0
  191. package/src/db/lifecycle-tail-write-ops.ts +62 -0
  192. package/src/db/lifecycle-write-window.ts +2 -1
  193. package/src/db/lineage-pair-dedupe-sql.ts +3 -3
  194. package/src/db/main-connection-mode.ts +11 -0
  195. package/src/db/main-connection-writes.ts +20 -2
  196. package/src/db/maintenance-job-runs-async.ts +2 -2
  197. package/src/db/maintenance-job-write-ops.ts +1 -1
  198. package/src/db/maintenance-pauses.ts +2 -2
  199. package/src/db/message-send.ts +4 -0
  200. package/src/db/messages.ts +31 -25
  201. package/src/db/migrations.ts +18 -1
  202. package/src/db/notification-write-ops.ts +19 -0
  203. package/src/db/operator-answer-write.ts +19 -0
  204. package/src/db/orchestrators.ts +10 -4
  205. package/src/db/pairs-async.ts +22 -1
  206. package/src/db/pairs-write-ops.ts +8 -3
  207. package/src/db/pairs.ts +41 -48
  208. package/src/db/plan-dispatch-schema.ts +5 -1
  209. package/src/db/plan-ready-notices.ts +43 -0
  210. package/src/db/plan-tools-include-migration.ts +36 -0
  211. package/src/db/plans-write-ops.ts +2 -0
  212. package/src/db/presence-buffer.ts +5 -0
  213. package/src/db/presence-loop.ts +18 -23
  214. package/src/db/presence-write.ts +0 -6
  215. package/src/db/project-entries-async.ts +14 -0
  216. package/src/db/project-entries-read-ops.ts +5 -1
  217. package/src/db/project-entries-write-ops.ts +8 -0
  218. package/src/db/project-entries.ts +15 -12
  219. package/src/db/project-journal.ts +2 -2
  220. package/src/db/project-trust-approvals.ts +2 -2
  221. package/src/db/prompt-templates.ts +4 -4
  222. package/src/db/read-op-marshaling-fixtures.ts +18 -0
  223. package/src/db/read-ops.ts +4 -0
  224. package/src/db/retention.ts +13 -2
  225. package/src/db/review-publish-write.ts +8 -0
  226. package/src/db/runtime-admin-async.ts +19 -0
  227. package/src/db/runtime-admin-write-ops.ts +36 -0
  228. package/src/db/runtime-tail-async.ts +288 -0
  229. package/src/db/runtime-tail-write-ops.ts +97 -0
  230. package/src/db/schema.ts +4 -1
  231. package/src/db/spawn-budget.ts +5 -0
  232. package/src/db/spawn-delegation.ts +3 -12
  233. package/src/db/sql-audit-indexes.ts +3 -0
  234. package/src/db/stats-async.ts +5 -3
  235. package/src/db/task-entity.ts +5 -4
  236. package/src/db/team-claim-policy.ts +60 -0
  237. package/src/db/teams-async.ts +4 -0
  238. package/src/db/teams-write-ops.ts +4 -0
  239. package/src/db/terminal-events.ts +15 -0
  240. package/src/db/timer-cutover-bodies.ts +292 -0
  241. package/src/db/timer-cutover-core.ts +37 -278
  242. package/src/db/timer-cutover-state.ts +40 -0
  243. package/src/db/timer-cutover-write-ops.ts +1 -40
  244. package/src/db/token-db-write-ops.ts +2 -0
  245. package/src/db/token-standing-async.ts +12 -0
  246. package/src/db/token-standing.ts +6 -9
  247. package/src/db/token-usage-retention.ts +33 -0
  248. package/src/db/token-usage-write-ops.ts +1 -1
  249. package/src/db/token-usage.ts +4 -1
  250. package/src/db/user-invites-async.ts +13 -0
  251. package/src/db/user-invites.ts +3 -3
  252. package/src/db/users.ts +14 -6
  253. package/src/db/workflow-execution.ts +3 -3
  254. package/src/db/workspaces.ts +8 -5
  255. package/src/db/write-ops.ts +31 -12
  256. package/src/db/writer-fault-seam.ts +2 -9
  257. package/src/db/writer-fault-test-ops.ts +18 -0
  258. package/src/db/writer-gateway.ts +42 -46
  259. package/src/dev.ts +12 -16
  260. package/src/event-loop-lag.ts +4 -2
  261. package/src/event-loop-yield.ts +2 -2
  262. package/src/events.ts +10 -8
  263. package/src/host-provider-store.ts +5 -4
  264. package/src/hub-metrics-report.ts +5 -1
  265. package/src/hub-metrics.ts +38 -18
  266. package/src/init.ts +299 -0
  267. package/src/injection-sweep-pacing.ts +6 -8
  268. package/src/invite-link.ts +16 -9
  269. package/src/land-gate-notifications.ts +11 -10
  270. package/src/land-gate-queue-progress.ts +3 -3
  271. package/src/land-settle-dispatch.ts +52 -92
  272. package/src/lifecycle-context-pressure.ts +11 -10
  273. package/src/lifecycle-manager.ts +104 -98
  274. package/src/maintenance/breaker.ts +18 -16
  275. package/src/maintenance/checkout-refresh.ts +18 -1
  276. package/src/maintenance/critical-job-watchdog.ts +3 -3
  277. package/src/maintenance/events.ts +3 -3
  278. package/src/maintenance/history-retention-job.ts +30 -0
  279. package/src/maintenance/jobs.ts +29 -67
  280. package/src/maintenance/long-running-work.ts +3 -1
  281. package/src/maintenance/offline-agent-prune-job.ts +28 -0
  282. package/src/maintenance/orphaned-sessions.ts +5 -4
  283. package/src/maintenance/pauses.ts +4 -10
  284. package/src/maintenance/plan-ready-notice-job.ts +71 -0
  285. package/src/maintenance/scheduler.ts +18 -13
  286. package/src/maintenance/stalled-delivery-pager.ts +4 -4
  287. package/src/maintenance/teams.ts +19 -30
  288. package/src/maintenance/workspaces.ts +17 -27
  289. package/src/managed-policy-escalation.ts +7 -7
  290. package/src/managed-policy.ts +3 -3
  291. package/src/managed-provider-gate.ts +5 -4
  292. package/src/managed-spawn-reservation.ts +5 -4
  293. package/src/mcp/index.ts +8 -8
  294. package/src/mcp/output-descriptions.ts +1 -1
  295. package/src/mcp/presentation.ts +2 -1
  296. package/src/mcp/runtime-output.ts +6 -1
  297. package/src/mcp/self-care-visibility.ts +2 -2
  298. package/src/mcp/spawn-targets-matrix.ts +10 -1
  299. package/src/mcp/tool-groups.ts +3 -2
  300. package/src/mcp/tool-roles.ts +81 -0
  301. package/src/mcp/tools-workspace.ts +1 -1
  302. package/src/mcp-blackboard-tools.ts +6 -5
  303. package/src/mcp-decision-tools.ts +7 -7
  304. package/src/mcp-egress-tools.ts +10 -9
  305. package/src/mcp-issue-activity-tools.ts +2 -2
  306. package/src/mcp-issue-association-tools.ts +4 -4
  307. package/src/mcp-issue-ref-tools.ts +3 -3
  308. package/src/mcp-merge-tools.ts +5 -5
  309. package/src/mcp-notification-subscription-tools.ts +14 -15
  310. package/src/mcp-output-levels.test-fixture.ts +1 -1
  311. package/src/mcp-profile-trust-tools.ts +1 -1
  312. package/src/mcp-project-journal-tools.ts +4 -12
  313. package/src/mcp-project-tools.ts +17 -18
  314. package/src/mcp-routing-policy-tools.ts +3 -2
  315. package/src/mcp-team-tools.ts +6 -6
  316. package/src/mcp-workflow-tools.ts +5 -3
  317. package/src/memory-area-injection.ts +13 -12
  318. package/src/memory-boundary-capture.ts +6 -6
  319. package/src/memory-injection.ts +45 -43
  320. package/src/merge-pause-maintenance.ts +5 -4
  321. package/src/notification-router.ts +44 -17
  322. package/src/notify.ts +5 -5
  323. package/src/observed-reset-refresh.ts +4 -4
  324. package/src/operator-alarm.ts +2 -5
  325. package/src/project-ingest.ts +6 -6
  326. package/src/project-instructions.ts +8 -1
  327. package/src/project-trust.ts +12 -11
  328. package/src/prompt-resolver.ts +3 -3
  329. package/src/provider-quota-async.ts +5 -7
  330. package/src/provider-timeline-event.ts +4 -7
  331. package/src/quota-band-delivery.ts +2 -2
  332. package/src/recipe-runner.ts +4 -3
  333. package/src/retention-config-store.ts +4 -3
  334. package/src/reviewer-pipeline.ts +11 -10
  335. package/src/routes/_route.ts +4 -4
  336. package/src/routes/_shared.ts +13 -12
  337. package/src/routes/agent-effort.ts +5 -5
  338. package/src/routes/agent-heartbeat-quota.ts +23 -0
  339. package/src/routes/agent-keep.ts +1 -1
  340. package/src/routes/agent-reporting.ts +1 -1
  341. package/src/routes/agent-self-effort.ts +5 -5
  342. package/src/routes/agent-sessions.ts +24 -24
  343. package/src/routes/agents-spawn.ts +1 -1
  344. package/src/routes/agents.ts +17 -119
  345. package/src/routes/auth.ts +44 -17
  346. package/src/routes/authority-audit.ts +2 -2
  347. package/src/routes/automations.ts +6 -5
  348. package/src/routes/channel-binding-delete.ts +1 -1
  349. package/src/routes/channel-output.ts +3 -3
  350. package/src/routes/claude-api-budget.ts +5 -5
  351. package/src/routes/commands-listing.ts +1 -1
  352. package/src/routes/commands.ts +29 -27
  353. package/src/routes/context-resets.ts +2 -2
  354. package/src/routes/coordinator-teams.ts +1 -1
  355. package/src/routes/decisions.ts +2 -2
  356. package/src/routes/host-providers.ts +18 -0
  357. package/src/routes/host-secret-files.ts +1 -1
  358. package/src/routes/hosts.ts +11 -11
  359. package/src/routes/inbox.ts +3 -2
  360. package/src/routes/index.ts +7 -3
  361. package/src/routes/integrations.ts +9 -7
  362. package/src/routes/issue-activity.ts +2 -2
  363. package/src/routes/issue-associations.ts +4 -4
  364. package/src/routes/issue-refs.ts +3 -3
  365. package/src/routes/kernel-timers-operator.ts +1 -1
  366. package/src/routes/memory.ts +1 -1
  367. package/src/routes/merges.ts +11 -11
  368. package/src/routes/messages.ts +23 -17
  369. package/src/routes/orchestrator-bootstrap.ts +3 -1
  370. package/src/routes/orchestrator-host-registration.ts +2 -0
  371. package/src/routes/orchestrator-host-utilization.ts +2 -2
  372. package/src/routes/orchestrator-proxy.ts +2 -2
  373. package/src/routes/orchestrator.ts +27 -18
  374. package/src/routes/pairs.ts +8 -8
  375. package/src/routes/project-profile-instructions.ts +1 -1
  376. package/src/routes/project-trust.ts +1 -1
  377. package/src/routes/projects.ts +16 -16
  378. package/src/routes/prompt-templates.ts +12 -12
  379. package/src/routes/provider-quotas.ts +10 -10
  380. package/src/routes/run-command.ts +1 -1
  381. package/src/routes/secrets-backup.ts +4 -5
  382. package/src/routes/secrets-import.ts +3 -4
  383. package/src/routes/secrets-vault.ts +12 -14
  384. package/src/routes/settings.ts +1 -1
  385. package/src/routes/shipping.ts +9 -1
  386. package/src/routes/spawn-policy.ts +8 -7
  387. package/src/routes/sse.ts +2 -2
  388. package/src/routes/team-templates.ts +7 -7
  389. package/src/routes/teams.ts +10 -10
  390. package/src/routes/tokens.ts +18 -17
  391. package/src/routes/users.ts +24 -24
  392. package/src/routes/viewer-read.ts +3 -3
  393. package/src/routes/web-tool-calls.ts +4 -4
  394. package/src/routes/workflows.ts +2 -2
  395. package/src/routes/workspaces.ts +17 -16
  396. package/src/routing-policy-push.ts +6 -6
  397. package/src/runtime-grant.ts +17 -11
  398. package/src/runtime-install.ts +30 -0
  399. package/src/runtime-tokens.ts +20 -28
  400. package/src/secrets-broker/config.ts +1 -1
  401. package/src/secrets-broker/service.ts +2 -2
  402. package/src/security.ts +3 -1
  403. package/src/self-resume-carry.ts +21 -110
  404. package/src/self-resume-supersede.ts +4 -3
  405. package/src/self-resume.ts +54 -66
  406. package/src/server-background.ts +1 -1
  407. package/src/server.ts +25 -26
  408. package/src/services/agent-action.ts +12 -31
  409. package/src/services/agent-effort.ts +7 -32
  410. package/src/services/agent-hang-watchdog-job.ts +2 -2
  411. package/src/services/agent-heartbeat.ts +166 -0
  412. package/src/services/agent-reporting.ts +1 -1
  413. package/src/services/auth-context.ts +6 -0
  414. package/src/services/browser-inventory.ts +3 -3
  415. package/src/services/browser-session-state.ts +3 -2
  416. package/src/services/browser-session.ts +21 -27
  417. package/src/services/bus-command-update.ts +9 -9
  418. package/src/services/checkout-refresh-state.ts +1 -0
  419. package/src/services/checkout-refresh.test-fixture.ts +3 -2
  420. package/src/services/command-host.ts +7 -2
  421. package/src/services/coordinator-digest.ts +3 -2
  422. package/src/services/dispatch-command.ts +12 -13
  423. package/src/services/effective-launch-driver.ts +14 -9
  424. package/src/services/host-changed-paths.test-fixture.ts +5 -6
  425. package/src/services/host-project-readiness.ts +2 -2
  426. package/src/services/host-providers.ts +1 -1
  427. package/src/services/host-utilization.ts +9 -9
  428. package/src/services/idle-agent-digest.ts +17 -12
  429. package/src/services/idle-epics.ts +4 -4
  430. package/src/services/interrupt-agent.ts +12 -9
  431. package/src/services/issue-activity.ts +3 -3
  432. package/src/services/issue-associations.ts +15 -18
  433. package/src/services/issue-lifecycle-comments.ts +3 -2
  434. package/src/services/issue-lifecycle.ts +14 -13
  435. package/src/services/issue-refs.ts +4 -5
  436. package/src/services/kernel-timers-cutover-chaos-worker.ts +1 -1
  437. package/src/services/kernel-timers-ingress-chaos-worker.ts +6 -1
  438. package/src/services/kernel-timers-ingress.ts +3 -3
  439. package/src/services/kernel-timers-message-authority-chaos-worker.ts +3 -10
  440. package/src/services/kernel-timers-operator-chaos-worker.ts +3 -2
  441. package/src/services/kernel-timers-outbox.ts +1 -1
  442. package/src/services/kernel-timers-stage10-operator.ts +1 -1
  443. package/src/services/kernel-timers.ts +4 -3
  444. package/src/services/land-gate-placement.ts +11 -2
  445. package/src/services/land-issue-relations.ts +5 -4
  446. package/src/services/land-restart-retry.ts +7 -6
  447. package/src/services/landed-ancestry.ts +6 -5
  448. package/src/services/legacy-landing-host.ts +4 -1
  449. package/src/services/legacy-landing-recovery.ts +8 -6
  450. package/src/services/managed-running.ts +17 -17
  451. package/src/services/memory-areas.ts +3 -3
  452. package/src/services/merge-pause.ts +9 -15
  453. package/src/services/operator-decisions.ts +37 -41
  454. package/src/services/orchestrator-registration-recovery.ts +6 -2
  455. package/src/services/orchestrator-upgrade.ts +14 -12
  456. package/src/services/permission-decision.ts +4 -3
  457. package/src/services/plan-ci-notice.ts +1 -1
  458. package/src/services/plan-conformance.ts +4 -4
  459. package/src/services/plan-dispatch-policy.ts +10 -3
  460. package/src/services/plan-dispatch.ts +35 -14
  461. package/src/services/plan-graph.ts +39 -261
  462. package/src/services/plan-import.ts +1 -1
  463. package/src/services/plan-live-bindings.ts +8 -37
  464. package/src/services/plan-task-create.ts +1 -1
  465. package/src/services/plan-terminal-refs.ts +2 -12
  466. package/src/services/plan-wake-digest.ts +8 -3
  467. package/src/services/profile-trust.ts +2 -2
  468. package/src/services/project.ts +11 -10
  469. package/src/services/prompt-templates.ts +6 -5
  470. package/src/services/provider-api-key.ts +73 -0
  471. package/src/services/provider-hold-nudge.ts +9 -9
  472. package/src/services/provider-hold-record.ts +13 -12
  473. package/src/services/provider-state-audit.ts +19 -18
  474. package/src/services/provisioning-harness/driver.ts +1 -1
  475. package/src/services/provisioning-harness/fixture-matrix.ts +3 -3
  476. package/src/services/provisioning-harness/lifecycle.ts +24 -22
  477. package/src/services/provisioning-harness/prepare.ts +16 -17
  478. package/src/services/rate-limit-resume.ts +10 -9
  479. package/src/services/red-main-auto-revert.ts +7 -5
  480. package/src/services/red-main-controller-deps.ts +20 -3
  481. package/src/services/red-main-incident.ts +3 -1
  482. package/src/services/red-main-quarantine.ts +2 -3
  483. package/src/services/register-agent.ts +22 -22
  484. package/src/services/restart-project-role.ts +1 -1
  485. package/src/services/restart-spawn.ts +17 -16
  486. package/src/services/review-publish.ts +7 -24
  487. package/src/services/review-ref-reconcile.ts +29 -38
  488. package/src/services/runtime-dependency-alert.ts +27 -0
  489. package/src/services/scheduler-command.ts +11 -9
  490. package/src/services/scheduler.ts +5 -5
  491. package/src/services/self-effort-grant.ts +5 -25
  492. package/src/services/send-message.ts +16 -33
  493. package/src/services/shutdown-agent.ts +17 -14
  494. package/src/services/spawn-agent.ts +12 -41
  495. package/src/services/spawn-chain-inspector.ts +1 -1
  496. package/src/services/spawn-cwd-placement.ts +21 -5
  497. package/src/services/spawn-issue-activity.ts +8 -6
  498. package/src/services/spawn-launch.ts +17 -1
  499. package/src/services/spawn-loss-escalation.ts +1 -1
  500. package/src/services/spawn-registration-watchdog.ts +2 -2
  501. package/src/services/spawn-repo-probe.ts +7 -5
  502. package/src/services/spawn-tree-cleanup.ts +7 -8
  503. package/src/services/steward-reaper.ts +4 -4
  504. package/src/services/task-context-hydration.ts +25 -1
  505. package/src/services/task-delivery.ts +20 -34
  506. package/src/services/task-entity.ts +40 -203
  507. package/src/services/task-issue-link.ts +13 -20
  508. package/src/services/task-issue-weave.ts +12 -11
  509. package/src/services/task-lifecycle.ts +27 -31
  510. package/src/services/task-repo-cwd.ts +37 -0
  511. package/src/services/task-review-hold.ts +4 -17
  512. package/src/services/task-semantic-events.ts +13 -11
  513. package/src/services/task-state-reconcile.ts +3 -4
  514. package/src/services/task-validation.ts +2 -2
  515. package/src/services/team-templates.ts +5 -5
  516. package/src/services/team.ts +31 -98
  517. package/src/services/terminal-provider-exit.ts +6 -5
  518. package/src/services/timer-message-fire-chaos-worker.ts +7 -3
  519. package/src/services/timer-message-fire-outbox.ts +1 -1
  520. package/src/services/transient-agent-reaper.ts +22 -19
  521. package/src/services/turn-reconcile-audit.ts +9 -9
  522. package/src/services/user-onboarding.ts +8 -6
  523. package/src/services/user-profile.ts +6 -9
  524. package/src/services/work-finality.ts +16 -4
  525. package/src/services/workflow-auto-start.ts +2 -1
  526. package/src/services/workflow-caller.ts +61 -4
  527. package/src/services/workflow-child-proof-worker.ts +1 -1
  528. package/src/services/workflow-control-proof-worker.ts +1 -1
  529. package/src/services/workflow-coordinator.ts +8 -2
  530. package/src/services/workflow-decisions.ts +5 -5
  531. package/src/services/workflow-deferred-cleanup.ts +1 -1
  532. package/src/services/workflow-effect-driver.ts +42 -19
  533. package/src/services/workflow-effects.ts +41 -27
  534. package/src/services/workflow-pins.ts +7 -4
  535. package/src/services/workflow-reconcile.ts +1 -0
  536. package/src/services/workflow-resume-pins.ts +3 -3
  537. package/src/services/workflow-routing-defaults.ts +18 -0
  538. package/src/services/workflow-run-view.ts +4 -3
  539. package/src/services/workflow-run.test-fixture.ts +1 -1
  540. package/src/services/workflow-runs.ts +34 -22
  541. package/src/services/workflow-supervisor-state.ts +21 -1
  542. package/src/services/workflow-supervisor.ts +4 -3
  543. package/src/services/workspace-recovery.ts +4 -3
  544. package/src/setup.ts +47 -8
  545. package/src/spawn-admission.ts +10 -2
  546. package/src/spawn-placement.ts +27 -9
  547. package/src/spawn-targets.ts +8 -8
  548. package/src/sse-agent-status.ts +15 -6
  549. package/src/sse.ts +49 -24
  550. package/src/startup-context-injection.ts +61 -57
  551. package/src/steward-handoff.ts +4 -3
  552. package/src/steward-policy-cleanup.ts +9 -8
  553. package/src/steward.ts +2 -2
  554. package/src/task-dispatch-live.ts +5 -2
  555. package/src/task-dispatcher.ts +2 -2
  556. package/src/token-db.ts +2 -2
  557. package/src/token-usage-async.ts +2 -4
  558. package/src/user-awaiting-reply.ts +5 -5
  559. package/src/utils.ts +7 -0
  560. package/src/workspace-actions.ts +24 -29
  561. package/src/workspace-auto-merge.ts +14 -12
  562. package/src/workspace-changed-paths-client.ts +16 -5
  563. package/src/workspace-cleanup-stale.ts +30 -7
  564. package/src/workspace-command-settle.ts +24 -16
  565. package/src/workspace-config.ts +1 -1
  566. package/src/workspace-conflict-resolver.ts +9 -7
  567. package/src/workspace-dead-owner-recovery.ts +5 -4
  568. package/src/workspace-escalation.ts +12 -11
  569. package/src/workspace-human-escalation.ts +3 -3
  570. package/src/workspace-land-gate-watchdog.ts +3 -3
  571. package/src/workspace-land-refusal.ts +20 -0
  572. package/src/workspace-land-stopped.ts +1 -0
  573. package/src/workspace-merge.ts +10 -26
  574. package/src/workspace-nothing-to-land-notice.ts +2 -2
  575. package/src/workspace-orphans.ts +18 -37
  576. package/src/workspace-pr-completion.ts +10 -8
  577. package/src/workspace-rekey.ts +3 -3
  578. package/src/workspace-steward-failure.ts +2 -2
  579. package/src/workspace-steward-wake.ts +2 -2
  580. package/public/assets/agent-liveness-CaNwK0Zf.js +0 -2
  581. package/public/assets/overview-C1DCTzzV.js +0 -2
  582. package/public/assets/provider-display-DsuWMvVL.js +0 -2
  583. package/public/assets/run-card-CpKqRbhg.js +0 -2
  584. package/public/assets/shipping-B9YtnwtG.js +0 -2
  585. package/public/assets/store-DcClXLH0.js +0 -9
  586. package/runner/src/agent-runner/http-liveness.ts +0 -85
  587. package/src/db/on-loop-write-transaction.ts +0 -34
@@ -1,5 +1,6 @@
1
+ import { getGateway } from "./db/gateway";
1
2
  import { deriveContextThresholdFromWindowTokens, findProviderCatalogModel, resolveProviderContextThreshold, type ProviderModelCatalogEntry, type ResolvedProviderContextThreshold } from "@humwick/sdk/provider-catalog";
2
- import { isRecord, isSpawnProvider } from "@humwick/sdk";
3
+ import { fireAndForget, isRecord, isSpawnProvider } from "@humwick/sdk";
3
4
  import { HARD_BACKSTOP_BUSY_GRACE_MS } from "./config";
4
5
  import { getConfig } from "./config-store";
5
6
  import { getToken } from "./token-db";
@@ -39,7 +40,7 @@ interface EffectiveContextThreshold extends ResolvedProviderContextThreshold {
39
40
  * #315 — context pressure is a relay-native signal: durable event for observers,
40
41
  * plus an agent-facing system push when lifecycle notifications are enabled.
41
42
  */
42
- function notifyContextAdvisory(input: ContextAdvisoryInput): void {
43
+ async function notifyContextAdvisory(input: ContextAdvisoryInput): Promise<void> {
43
44
  const payload = {
44
45
  kind: "agent.context_threshold",
45
46
  agentId: input.agentId,
@@ -63,20 +64,20 @@ function notifyContextAdvisory(input: ContextAdvisoryInput): void {
63
64
 
64
65
  // Short default TTL (per-type): a stale context advisory is misleading — the band may have moved
65
66
  // by the time the agent next polls, so old advice shouldn't wake it.
66
- routeNotification({
67
+ await routeNotification({
67
68
  type: "agent.context_threshold",
68
69
  scope: { agentId: input.agentId },
69
70
  recipients: [input.agentId],
70
71
  message: {
71
72
  subject: input.stage === "warn" ? "Context threshold warning" : "Context self-compact threshold reached",
72
- body: contextAdvisoryBody(input),
73
+ body: (await contextAdvisoryBody(input)),
73
74
  payload,
74
75
  replyExpected: false,
75
76
  },
76
77
  });
77
78
  }
78
79
 
79
- function contextAdvisoryBody(input: ContextAdvisoryInput): string {
80
+ async function contextAdvisoryBody(input: ContextAdvisoryInput): Promise<string> {
80
81
  const tokens = `${formatTokens(input.tokens)} / ${formatTokens(input.windowTokens)} tokens used`;
81
82
  const percent = `${input.percent}% of window, ${input.band}% band`;
82
83
  const threshold = `resolved self-compact threshold ${input.selfCompactPercent}% (${input.thresholdSource})`;
@@ -96,7 +97,7 @@ function contextAdvisoryBody(input: ContextAdvisoryInput): string {
96
97
  threshold,
97
98
  hardBackstop: `${input.hardBackstopPercent}%`,
98
99
  });
99
- return withPreBoundaryCapture(advisory, input.agentId);
100
+ return (await withPreBoundaryCapture(advisory, input.agentId));
100
101
  }
101
102
 
102
103
  /**
@@ -110,8 +111,8 @@ function contextAdvisoryBody(input: ContextAdvisoryInput): string {
110
111
  * Appended rather than woven into the advisory templates so a blank slug leaves the advisory body
111
112
  * BYTE-IDENTICAL to the pre-P5 one.
112
113
  */
113
- function withPreBoundaryCapture(advisory: string, agentId: string): string {
114
- const capture = boundaryCaptureClause({ seam: "pre", agentId });
114
+ async function withPreBoundaryCapture(advisory: string, agentId: string): Promise<string> {
115
+ const capture = (await boundaryCaptureClause({ seam: "pre", agentId }));
115
116
  return capture ? `${advisory}\n\n${capture}` : advisory;
116
117
  }
117
118
 
@@ -123,12 +124,12 @@ function contextAdvisoryBand(utilization: number, threshold: EffectiveContextThr
123
124
  return { band: utilizationPercent(threshold.warnUtilization), stage: "warn" };
124
125
  }
125
126
 
126
- export function handleContextPressure(input: {
127
+ export async function handleContextPressure(input: {
127
128
  agent: AgentCard;
128
129
  now: () => number;
129
130
  hasActiveCommand: (target: string, type: string) => boolean;
130
- compact: (reason: string) => Command | null;
131
- }): boolean {
131
+ compact: (reason: string) => Command | null | Promise<Command | null>;
132
+ }): Promise<boolean> {
132
133
  const { agent, now } = input;
133
134
  const context = agent.context;
134
135
  if (!context) return false;
@@ -138,7 +139,7 @@ export function handleContextPressure(input: {
138
139
  const advisory = contextAdvisoryBand(context.utilization, threshold);
139
140
  if (advisory === null) {
140
141
  if (context.lastContextAdvisoryBand !== undefined || context.lastContextAdvisoryAt !== undefined || context.hardBackstopDeferredAt !== undefined) {
141
- persistContextState(agent.id, clearContextAdvisory(context));
142
+ (await persistContextState(agent.id, clearContextAdvisory(context)));
142
143
  }
143
144
  return false;
144
145
  }
@@ -160,8 +161,8 @@ export function handleContextPressure(input: {
160
161
  // mid-turn (the runner waits for the live turn to clear before submitting the keystrokes).
161
162
  const graceElapsed = deferredAt !== undefined && now() - deferredAt >= HARD_BACKSTOP_BUSY_GRACE_MS;
162
163
  if (!busy || graceElapsed) {
163
- input.compact(busy ? "context-hard-backstop-busy" : "context-hard-backstop");
164
- if (deferredAt !== undefined) persistContextState(agent.id, clearHardBackstopDefer(context));
164
+ await input.compact(busy ? "context-hard-backstop-busy" : "context-hard-backstop");
165
+ if (deferredAt !== undefined) (await persistContextState(agent.id, clearHardBackstopDefer(context)));
165
166
  return true;
166
167
  }
167
168
  // Busy and within grace: start the bounded defer clock (once), then fall through so the
@@ -169,19 +170,19 @@ export function handleContextPressure(input: {
169
170
  // the forced backstop.
170
171
  if (deferredAt === undefined) {
171
172
  state = { ...context, hardBackstopDeferredAt: now() };
172
- persistContextState(agent.id, state);
173
+ (await persistContextState(agent.id, state));
173
174
  }
174
175
  } else if (context.hardBackstopDeferredAt !== undefined) {
175
176
  // Dropped below the hard backstop (or a forced compaction is already in flight): reset the clock
176
177
  // so a later re-crossing starts a fresh grace window rather than firing immediately.
177
178
  state = clearHardBackstopDefer(context);
178
- persistContextState(agent.id, state);
179
+ (await persistContextState(agent.id, state));
179
180
  }
180
181
  if (state.lastContextAdvisoryBand === advisory.band) return false;
181
182
  const tokens = finiteNumber(context.tokensUsed);
182
183
  const windowTokens = finiteNumber(context.tokensMax);
183
184
  if (tokens === undefined || windowTokens === undefined) return false;
184
- notifyContextAdvisory({
185
+ await fireAndForget(`[context] advisory ${input.agent.id}`, async () => (await notifyContextAdvisory({
185
186
  agentId: agent.id,
186
187
  agent,
187
188
  tokens,
@@ -193,8 +194,8 @@ export function handleContextPressure(input: {
193
194
  selfCompactPercent: utilizationPercent(threshold.selfCompactUtilization),
194
195
  hardBackstopPercent: utilizationPercent(threshold.hardBackstopUtilization),
195
196
  thresholdSource: threshold.source,
196
- });
197
- persistContextState(agent.id, { ...state, lastContextAdvisoryBand: advisory.band, lastContextAdvisoryAt: now() });
197
+ })));
198
+ (await persistContextState(agent.id, { ...state, lastContextAdvisoryBand: advisory.band, lastContextAdvisoryAt: now() }));
198
199
  return true;
199
200
  }
200
201
 
@@ -258,8 +259,8 @@ function clearHardBackstopDefer(context: ContextState): ContextState {
258
259
  return rest;
259
260
  }
260
261
 
261
- function persistContextState(agentId: string, context: ContextState): void {
262
- getDb().query("UPDATE agents SET context_state = ? WHERE id = ?").run(JSON.stringify(context), agentId);
262
+ async function persistContextState(agentId: string, context: ContextState): Promise<void> {
263
+ await getGateway().write("agents.persistAdvisoryContext", [agentId, context]);
263
264
  }
264
265
 
265
266
  function finiteNumber(value: unknown): number | undefined {
@@ -0,0 +1,36 @@
1
+ import { ValidationError } from "@humwick/sdk";
2
+ import { createCommandInternal, getCommand } from "../commands-db";
3
+ import { assertSelfEffortAllowed } from "../services/self-effort";
4
+ import { admitDesiredEffortInternal, getAgentEffortState } from "./agent-effort-state";
5
+ import { getAgent } from "./agents";
6
+ import { sendMessageWithResultInternal } from "./messages";
7
+ import type { AgentCard, CreateCommandInput, SendMessageInput } from "../types";
8
+ import type { EffortChangeActorRole } from "@humwick/sdk";
9
+
10
+ export interface AgentActionWrite {
11
+ agent: AgentCard;
12
+ commandId: string;
13
+ commandInput: Omit<CreateCommandInput, "params"> & { params: Record<string, unknown> };
14
+ requestId?: string;
15
+ effort?: string;
16
+ role: EffortChangeActorRole;
17
+ selfEffort?: { level: string; efforts?: readonly string[] };
18
+ message?: SendMessageInput;
19
+ }
20
+
21
+ export function admitAgentActionBody(input: AgentActionWrite, now: number) {
22
+ const prior = input.requestId ? getCommand(input.requestId) : null;
23
+ if (prior) {
24
+ if (prior.type !== "agent.setEffort" || prior.target !== input.agent.id) throw new ValidationError("requestId belongs to another request");
25
+ return { command: prior, raced: true, effortState: getAgentEffortState(input.agent.id) };
26
+ }
27
+ const current = getAgent(input.agent.id);
28
+ if (!current || current.instanceId !== input.agent.instanceId || current.epoch !== input.agent.epoch) throw new ValidationError("agent instance changed while the action audit was committing");
29
+ if (input.selfEffort) assertSelfEffortAllowed(current, input.selfEffort.level, input.selfEffort.efforts);
30
+ const sent = input.message ? sendMessageWithResultInternal(input.message, false, {}, now) : undefined;
31
+ if (sent) Object.assign(input.commandInput.params, { messageId: sent.message.id, from: sent.message.from });
32
+ const admitted = input.effort ? admitDesiredEffortInternal({ agentId: current.id, effort: input.effort, role: input.role, commandId: input.commandId, now }) : undefined;
33
+ if (admitted) input.commandInput.params.effortVersion = admitted.state.desiredVersion;
34
+ const command = createCommandInternal({ input: input.commandInput, id: input.commandId }, now);
35
+ return { command, raced: false, admitted, sessionMessage: sent?.message };
36
+ }
@@ -0,0 +1,9 @@
1
+ import { getGateway } from "./gateway";
2
+ import type { AgentControlWrite, SelfEffortGrantWrite, createAgentControlBody, grantSelfEffortBody } from "./agent-control-write";
3
+
4
+ export function createAgentControlAsync(input: AgentControlWrite): Promise<ReturnType<typeof createAgentControlBody>> {
5
+ return getGateway().write("agents.createControl", input, { idempotencyKey: input.commandId, critical: true }) as unknown as Promise<ReturnType<typeof createAgentControlBody>>;
6
+ }
7
+ export function grantSelfEffortAsync(input: SelfEffortGrantWrite): Promise<ReturnType<typeof grantSelfEffortBody>> {
8
+ return getGateway().write("agents.grantSelfEffort", input, { idempotencyKey: input.commandId }) as unknown as Promise<ReturnType<typeof grantSelfEffortBody>>;
9
+ }
@@ -0,0 +1,49 @@
1
+ import { ValidationError } from "@humwick/sdk";
2
+ import { createCommandInternal, updateCommandInternal } from "../commands-db";
3
+ import { agentProviderState } from "../provider-state";
4
+ import { stopRefusal, type SafeCandidate } from "../services/idle-agent-digest";
5
+ import { selfEffortIdentity, type SelfEffortGrant, type SelfEffortPolicy } from "../services/self-effort";
6
+ import { getAgent } from "./agents";
7
+ import { SELF_EFFORT_BAND, setSelfEffortCeilingInternal } from "./self-effort-permissions";
8
+ import type { AgentCard, Command, CreateCommandInput } from "../types";
9
+
10
+ export interface AgentControlWrite {
11
+ agent?: AgentCard | null;
12
+ commandId: string;
13
+ commandInput: CreateCommandInput;
14
+ approvalId?: string;
15
+ idleCandidate?: SafeCandidate;
16
+ }
17
+ export function createAgentControlBody(input: AgentControlWrite, now: number): { command?: Command; refused?: string } {
18
+ if (input.agent) {
19
+ const current = getAgent(input.agent.id);
20
+ if (!current || current.instanceId !== input.agent.instanceId || current.epoch !== input.agent.epoch) throw new ValidationError("agent instance changed while the action audit was committing");
21
+ if (input.approvalId) {
22
+ const state = agentProviderState(current);
23
+ if (state?.state !== "blocked" || state.pendingApproval?.id !== input.approvalId) throw new ValidationError("approval request is no longer current");
24
+ }
25
+ if (input.idleCandidate) {
26
+ const refusal = stopRefusal(input.idleCandidate, current, now);
27
+ if (refusal) return { refused: refusal.reason };
28
+ }
29
+ }
30
+ return { command: createCommandInternal({ input: input.commandInput, id: input.commandId }, now) };
31
+ }
32
+
33
+ export const SELF_EFFORT_GRANT_COMMAND = "agent.grantSelfEffort";
34
+ export interface SelfEffortGrantWrite {
35
+ agent: AgentCard; grant?: SelfEffortGrant; requestedBy: string; commandId: string; spawnRequestId?: string;
36
+ input: { requestedVia: "mcp" | "http"; requestedByUser?: string };
37
+ selfEffort: SelfEffortPolicy | null; previous: SelfEffortPolicy | null;
38
+ }
39
+ export function grantSelfEffortBody({ agent, grant, requestedBy, commandId, spawnRequestId, input, selfEffort, previous }: SelfEffortGrantWrite, now: number): Command {
40
+ const current = getAgent(agent.id);
41
+ if (!current || current.instanceId !== agent.instanceId || current.epoch !== agent.epoch) throw new ValidationError("agent instance changed while the action audit was committing");
42
+ const identity = selfEffortIdentity(current);
43
+ setSelfEffortCeilingInternal(identity, !grant ? null : "band" in grant ? SELF_EFFORT_BAND : grant.ceiling, requestedBy, now);
44
+ createCommandInternal({ id: commandId, input: { type: SELF_EFFORT_GRANT_COMMAND, source: "system", target: agent.id,
45
+ ...(spawnRequestId ? { correlationId: spawnRequestId } : {}), params: { agentId: agent.id,
46
+ ...(spawnRequestId ? { spawnRequestId } : {}), permission: identity, selfEffort, previousSelfEffort: previous, requestedBy,
47
+ ...(input.requestedByUser ? { requestedByUser: input.requestedByUser } : {}), requestedVia: input.requestedVia, requestedAt: now } } }, now);
48
+ return updateCommandInternal(commandId, { status: "succeeded", result: { selfEffort } }, {}, now).command!;
49
+ }
@@ -0,0 +1,22 @@
1
+ import { validateAgentSession } from "./agents";
2
+ import { recordEffortObservedInternal } from "./agent-effort-state";
3
+ import { findMessageByIdempotencyKey, sendMessageWithResultInternal } from "./messages";
4
+ export class EffortReportFenced extends Error {}
5
+ /** #2984 — the prompt that resumes an agent once its own effort change applied. */
6
+ export function selfEffortContinuation(effort: string): string {
7
+ return `Effort is now ${effort}. Continue your work.`;
8
+ }
9
+
10
+
11
+ export function observeEffortBody(input: Parameters<typeof recordEffortObservedInternal>[0]) {
12
+ const session = validateAgentSession(input.agentId, { instanceId: input.instanceId });
13
+ if (!session.ok) throw new EffortReportFenced(session.error ?? "stale agent instance");
14
+ const observed = recordEffortObservedInternal(input);
15
+ const version = observed.outcome !== "stale" ? observed.resumeVersion : undefined;
16
+ const key = `effort-continuation:${input.agentId}:${version}`;
17
+ const continuation = version !== undefined && !findMessageByIdempotencyKey("system", key)
18
+ ? sendMessageWithResultInternal({ from: "system", to: input.agentId, kind: "system", subject: "Effort changed",
19
+ body: selfEffortContinuation(input.effort), replyExpected: false, idempotencyKey: key,
20
+ payload: { kind: "agent.effort_continuation", agentId: input.agentId, effortVersion: version, effort: input.effort } }, false, {}, input.now).message : null;
21
+ return { observed, continuation };
22
+ }
@@ -5,6 +5,8 @@
5
5
  import { isRecord } from "@humwick/sdk";
6
6
 
7
7
  export const AGENT_META_WARN_BYTES = 64 * 1024;
8
+ export const PROFILE_PROJECTION_WARN_BYTES = 192 * 1024;
9
+ export const COMMAND_PROFILE_WARN_BYTES = 1024 * 1024;
8
10
 
9
11
  // Log dedupe only, so a per-thread copy is correct: the writer thread warns once itself (#1870 S6c).
10
12
  const warnedOversizeMeta = new Set<string>();
@@ -62,13 +64,22 @@ export function jsonBytes(value: string): number {
62
64
 
63
65
  /** Runtime tripwire: one warning per agent/source until the payload returns below the ceiling. */
64
66
  export function warnIfAgentMetaOversized(agentId: string, json: string, source: string): void {
65
- const key = `${agentId}:${source}`;
67
+ warnIfOversized(`${agentId}:${source}`, json, AGENT_META_WARN_BYTES, `[agent-meta] ${agentId} ${source}`);
68
+ }
69
+
70
+ export function warnIfOversized(key: string, json: string, threshold: number, label: string): void {
66
71
  const bytes = jsonBytes(json);
67
- if (bytes <= AGENT_META_WARN_BYTES) {
72
+ if (bytes <= threshold) {
68
73
  warnedOversizeMeta.delete(key);
69
74
  return;
70
75
  }
71
76
  if (warnedOversizeMeta.has(key)) return;
72
77
  warnedOversizeMeta.add(key);
73
- console.warn(`[agent-meta] ${agentId} ${source} is ${bytes} bytes (alarm threshold ${AGENT_META_WARN_BYTES})`);
78
+ console.warn(`${label} is ${bytes} bytes (alarm threshold ${threshold})`);
79
+ }
80
+
81
+ export function serializeProfileProjection(agentId: string, projection: unknown): string | null {
82
+ const json = projection == null ? null : JSON.stringify(projection);
83
+ warnIfOversized(`projection:${agentId}`, json ?? "", PROFILE_PROJECTION_WARN_BYTES, `[agent-profile] ${agentId} profile_projection`);
84
+ return json;
74
85
  }
@@ -131,7 +131,7 @@ export async function setStatusAsync(id: string, status: AgentCard["status"], gu
131
131
  const before = await getAgentAsync(id);
132
132
  if (!agentSessionGuardResult(before, guard).ok) return false;
133
133
  if (!before) return false;
134
- if (before.status !== status) runBeforeAgentStatusTransitionHandlers(before, status);
134
+ if (before.status !== status) await runBeforeAgentStatusTransitionHandlers(before, status);
135
135
  if (status === "offline" || status === "stale") evictPendingPresence(id);
136
136
  const now = Date.now();
137
137
  // #2111 — buffered into the short lifecycle window and submitted with its peers as ONE batch job.
@@ -228,8 +228,9 @@ export async function applyAgentLifecycleUpdateAsync(
228
228
  ): Promise<AgentLifecycleUpdateResult> {
229
229
  // Allocated first, per the ordinal-timing rule at the top of setStatusAsync.
230
230
  const seq = nextLifecycleSeq();
231
- const before = prepareAgentLifecycleUpdate(id, update, guard);
231
+ const before = prepareAgentLifecycleUpdate(id, update, guard, false);
232
232
  if (!before) return { ok: false, changed: false, metaWriteBytes: 0, before: null, after: null };
233
+ if (update.status !== undefined && update.status !== before.status) await runBeforeAgentStatusTransitionHandlers(before, update.status);
233
234
  const result = (await getGateway().write("agents.applyLifecycleUpdate", agentLifecycleWriteArgs(before, update, seq, now))) as unknown as Omit<AgentLifecycleUpdateResult, "ok" | "before">;
234
235
  return { ok: true, before, ...result };
235
236
  }
@@ -296,7 +297,7 @@ export async function reapStaleAgentsAsync(ttlMs: number = STALE_TTL_MS, lagGrac
296
297
  */
297
298
  export async function pruneOfflineAgentsAsync(
298
299
  maxOfflineMs: number = DAY_MS,
299
- options: { onFinality?: (result: WorkFinalFlushResult) => void } = {},
300
+ options: { onFinality?: (result: WorkFinalFlushResult) => void; onArchivePurgeFailure?: () => void } = {},
300
301
  ): Promise<string[]> {
301
302
  const cutoff = Date.now() - maxOfflineMs;
302
303
  const candidates = (await getGateway().read("agents.offlinePruneCandidates", {
@@ -304,7 +305,7 @@ export async function pruneOfflineAgentsAsync(
304
305
  })) as unknown as AgentCard[];
305
306
  const candidateIds = candidates.map((candidate) => candidate.id);
306
307
  if (candidateIds.length === 0) return [];
307
- await purgeArchivesBeforeDelete(candidateIds, cutoff);
308
+ if (!await purgeArchivesBeforeDelete(candidateIds, cutoff)) options.onArchivePurgeFailure?.();
308
309
 
309
310
  const finalityQueues = takeDeferredLineageReports(candidateIds);
310
311
  const refreshFinalityCandidates = candidates
@@ -334,11 +335,13 @@ export async function pruneOfflineAgentsAsync(
334
335
  }
335
336
 
336
337
  /** #3012 — best effort: whatever the off-loop purge leaves, the delete's own FK cascade still removes. */
337
- async function purgeArchivesBeforeDelete(agentIds: string[], offlineBefore?: number): Promise<void> {
338
+ async function purgeArchivesBeforeDelete(agentIds: string[], offlineBefore?: number): Promise<boolean> {
338
339
  try {
339
340
  await purgeContinuationArchivesAsync({ agentIds, offlineBefore });
341
+ return true;
340
342
  } catch (error) {
341
343
  console.warn("[agents] continuation archive purge before delete failed; the cascade will remove them:", error);
344
+ return false;
342
345
  }
343
346
  }
344
347
 
package/src/db/agents.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { trackRelayEvent } from "../relay-event-drain";
1
2
  import { mainThreadRegistry } from "./main-thread-state";
2
3
  import { spawnTreeParentIds } from "./spawn-tree";
3
4
  import { recordSpawnProcessExit } from "./spawn-process-evidence";
@@ -8,6 +9,7 @@ import { STALE_TTL_MS, DAY_MS } from "../config";
8
9
  import { withoutAuthzMetaKeys } from "../authz-meta-keys";
9
10
  import {
10
11
  jsonBytes,
12
+ serializeProfileProjection,
11
13
  splitAgentMetaForStorage,
12
14
  splitAgentMetaPatchForStorage,
13
15
  topLevelJsonPath,
@@ -48,10 +50,10 @@ import type {
48
50
  QuotaState,
49
51
  } from "../types";
50
52
 
51
- type BeforeAgentStatusTransition = (agent: AgentCard, nextStatus: AgentCard["status"]) => void;
53
+ type BeforeAgentStatusTransition = (agent: AgentCard, nextStatus: AgentCard["status"]) => unknown;
52
54
  const beforeAgentStatusTransitionHandlers = mainThreadRegistry("agents.ts:beforeAgentStatusTransitionHandlers", new Set<BeforeAgentStatusTransition>());
53
55
 
54
- /** Register synchronous work that must finish before a status transition is persisted. */
56
+ /** Register work that must finish before a status transition is persisted. */
55
57
  export function onBeforeAgentStatusTransition(handler: BeforeAgentStatusTransition): () => void {
56
58
  beforeAgentStatusTransitionHandlers.add(handler);
57
59
  return () => beforeAgentStatusTransitionHandlers.delete(handler);
@@ -62,8 +64,9 @@ export function onBeforeAgentStatusTransition(handler: BeforeAgentStatusTransiti
62
64
  * a writer-worker's own instance), so this must only ever be invoked main-thread-side: from
63
65
  * `setStatus` directly, or from `setStatusAsync`'s pre-write orchestration. Never from the
64
66
  * `agents.setStatus` op body. */
65
- export function runBeforeAgentStatusTransitionHandlers(before: AgentCard, next: AgentCard["status"]): void {
66
- for (const handler of beforeAgentStatusTransitionHandlers) handler(before, next);
67
+ export function runBeforeAgentStatusTransitionHandlers(before: AgentCard, next: AgentCard["status"]): Promise<void> {
68
+ const pending = [...beforeAgentStatusTransitionHandlers].map(handler => handler(before, next));
69
+ return trackRelayEvent(Promise.all(pending).then(() => {}), "agent.before-status");
67
70
  }
68
71
 
69
72
  const CONTEXT_SNAPSHOT_DEBOUNCE_MS = 60_000;
@@ -161,7 +164,7 @@ export function upsertAgentInternal(
161
164
  const contextState = mergeRelayContextMarkers(input.id, input.context);
162
165
  const storedMeta = splitAgentMetaForStorage(withCanonicalSpawnRequestId(input.meta));
163
166
  const metaJson = JSON.stringify(storedMeta.meta);
164
- const profileProjectionJson = storedMeta.profileProjection == null ? null : JSON.stringify(storedMeta.profileProjection);
167
+ const profileProjectionJson = storedMeta.profileProjectionProvided ? serializeProfileProjection(input.id, storedMeta.profileProjection) : null;
165
168
  warnIfAgentMetaOversized(input.id, metaJson, "stored meta");
166
169
  const stmt = getDb().query(`
167
170
  INSERT INTO agents (id, name, kind, label, tags, system_tags, machine, rig, capabilities, ready, status, instance_id, epoch, provider_capabilities, context_state, quota_state, profile_projection, meta, lifecycle, spawned_by, team_id, status_seq, ready_seq, last_seen, created_at)
@@ -502,12 +505,12 @@ export function agentLifecycleWriteArgs(before: AgentCard, update: AgentLifecycl
502
505
 
503
506
  /** The main-thread half of a lifecycle delta: guard, `before` snapshot, transition handlers and presence
504
507
  * eviction (main-thread state), all before the write is submitted. Null when the guard rejects. */
505
- export function prepareAgentLifecycleUpdate(id: string, update: AgentLifecycleUpdate, guard?: AgentSessionGuard): AgentCard | null {
508
+ export function prepareAgentLifecycleUpdate(id: string, update: AgentLifecycleUpdate, guard?: AgentSessionGuard, runHandlers = true): AgentCard | null {
506
509
  const before = getPersistedAgentRuntimeCard(id);
507
510
  if (!before || !agentSessionGuardResult(before, guard).ok) return null;
508
511
  const nextStatus = update.status;
509
512
  // Pre-transition handlers must run BEFORE the write lands, exactly as `setStatus` does.
510
- if (nextStatus !== undefined && nextStatus !== before.status) runBeforeAgentStatusTransitionHandlers(before, nextStatus);
513
+ if (runHandlers && nextStatus !== undefined && nextStatus !== before.status) runBeforeAgentStatusTransitionHandlers(before, nextStatus);
511
514
  // #1468 — a terminal transition evicts any buffered beat so a coalesced advance cannot later
512
515
  // resurrect a gone agent at flush.
513
516
  if (nextStatus === "offline" || nextStatus === "stale") evictPendingPresence(id);
@@ -566,7 +569,7 @@ export function applyAgentLifecycleUpdateInternal(args: AgentLifecycleWriteArgs)
566
569
  }
567
570
  if (patch.profileProjectionProvided) {
568
571
  sets.push("profile_projection = $profileProjection");
569
- params.$profileProjection = patch.profileProjection == null ? null : JSON.stringify(patch.profileProjection);
572
+ params.$profileProjection = serializeProfileProjection(id, patch.profileProjection);
570
573
  }
571
574
  }
572
575
  const runtime = update.runtime;
@@ -615,7 +618,7 @@ export function mergeAgentMeta(id: string, meta: Record<string, unknown>, guard?
615
618
  warnIfAgentMetaOversized(id, metaJson, "stored meta");
616
619
  const result = getDb()
617
620
  .query("UPDATE agents SET meta = ?, profile_projection = ?, last_seen = ? WHERE id = ?")
618
- .run(metaJson, stored.profileProjection == null ? null : JSON.stringify(stored.profileProjection), now, id);
621
+ .run(metaJson, serializeProfileProjection(id, stored.profileProjection), now, id);
619
622
  return result.changes > 0;
620
623
  }
621
624
 
@@ -631,7 +634,7 @@ export function markAgentReadyNotified(id: string, at: number = Date.now()): boo
631
634
  const merged = { ...(agent.meta ?? {}), readyNotifiedAt: at };
632
635
  const stored = splitAgentMetaForStorage(merged);
633
636
  return getDb().query("UPDATE agents SET meta = ?, profile_projection = ? WHERE id = ?")
634
- .run(JSON.stringify(stored.meta), stored.profileProjection == null ? null : JSON.stringify(stored.profileProjection), id).changes > 0;
637
+ .run(JSON.stringify(stored.meta), serializeProfileProjection(id, stored.profileProjection), id).changes > 0;
635
638
  }
636
639
 
637
640
  /** Delete specific keys from an agent's meta blob. Returns true if anything changed. Used to clear
@@ -650,7 +653,7 @@ export function clearAgentMetaKeys(id: string, keys: string[]): boolean {
650
653
  if (!changed) return false;
651
654
  const stored = splitAgentMetaForStorage(meta);
652
655
  return getDb().query("UPDATE agents SET meta = ?, profile_projection = ? WHERE id = ?")
653
- .run(JSON.stringify(stored.meta), stored.profileProjection == null ? null : JSON.stringify(stored.profileProjection), id).changes > 0;
656
+ .run(JSON.stringify(stored.meta), serializeProfileProjection(id, stored.profileProjection), id).changes > 0;
654
657
  }
655
658
 
656
659
  /**
@@ -741,7 +744,7 @@ export function recordAgentExitDiagnosticsInternal(
741
744
  .run({
742
745
  $seq: lifecycleSeq,
743
746
  $meta: JSON.stringify(stored.meta),
744
- $profileProjection: stored.profileProjection == null ? null : JSON.stringify(stored.profileProjection),
747
+ $profileProjection: serializeProfileProjection(id, stored.profileProjection),
745
748
  $id: id,
746
749
  } as never);
747
750
  return result.changes > 0 ? agent.epoch : null;
@@ -93,9 +93,9 @@ export function createAuthIdentity(input: {
93
93
  providerSubject: string;
94
94
  secretHash?: string | null;
95
95
  metadata?: Record<string, unknown>;
96
- }): AuthIdentity {
96
+ }, submittedAt: number = Date.now()): AuthIdentity {
97
97
  const id = randomUUID();
98
- const now = Date.now();
98
+ const now = submittedAt;
99
99
  getDb()
100
100
  .query(
101
101
  `INSERT INTO auth_identities (id, user_id, provider, provider_subject, secret_hash, metadata, created_at)
@@ -0,0 +1,11 @@
1
+ // #3799 — automation scans (run listing, reconcile/precheck/dispatch ticks) on the reader pool.
2
+ // A cold scan of automation_runs once blocked the hub loop for seconds on swap-bound I/O.
3
+ import { listAutomationRuns, listDispatchableQueuedRuns, listDueAutomations, listPendingPrecheckRuns, listReconcilableRuns } from "../automations/storage.ts";
4
+
5
+ export const AUTOMATION_READ_OPS = {
6
+ "automations.listRuns": (args: { automationId?: string; status?: string; limit?: number }) => listAutomationRuns(args),
7
+ "automations.pendingPrecheckRuns": () => listPendingPrecheckRuns(),
8
+ "automations.reconcilableRuns": () => listReconcilableRuns(),
9
+ "automations.dispatchableQueuedRuns": () => listDispatchableQueuedRuns(),
10
+ "automations.dueAutomations": (args: { now: number }) => listDueAutomations(args.now),
11
+ };
@@ -0,0 +1,46 @@
1
+ import { ValidationError } from "@humwick/sdk";
2
+ import { insertAutomation, updateAutomationNormalized, deleteAutomation } from "../automations/persistence";
3
+ import { nextScheduledAt } from "../automations/cron";
4
+ import { getAutomation, getAutomationRun, hasActiveRun, insertRun, enqueueAutomationRun, updateRun, cancelActiveRuns, rescheduleAutomation } from "../automations/storage";
5
+ import type { Automation } from "../types";
6
+ import type { WriterOpSpec } from "./write-ops";
7
+
8
+ function admitRun(input: { automationId: string; now: number; manual?: boolean; queuedRunId?: string }) {
9
+ const automation = getAutomation(input.automationId);
10
+ if (!automation) return null;
11
+ const active = hasActiveRun(automation.id);
12
+ if (input.queuedRunId) {
13
+ const run = getAutomationRun(input.queuedRunId);
14
+ if (!run || run.automationId !== automation.id || run.status !== "scheduled" || active) return null;
15
+ updateRun(run.id, { status: "dispatching" }, input.now);
16
+ return { automation, run: getAutomationRun(run.id)! };
17
+ }
18
+ if (input.manual) {
19
+ if (active) throw new ValidationError("automation already has an active run");
20
+ } else {
21
+ if (!automation.enabled || automation.nextRunAt === undefined || automation.nextRunAt > input.now) return null;
22
+ if (active && automation.concurrencyPolicy === "replace") cancelActiveRuns(automation.id, input.now, "Replaced by newer scheduled run");
23
+ else if (active && automation.concurrencyPolicy === "queue") enqueueAutomationRun(automation, automation.nextRunAt, input.now);
24
+ const next = automation.enabled ? nextScheduledAt(automation.schedule, automation.timezone, input.now) : undefined;
25
+ rescheduleAutomation(automation, next, input.now);
26
+ if (active && automation.concurrencyPolicy !== "replace") return null;
27
+ }
28
+ const run = insertRun(automation, input.manual ? input.now : automation.nextRunAt ?? input.now, input.now);
29
+ return { automation: getAutomation(automation.id)!, run };
30
+ }
31
+
32
+ export const AUTOMATION_WRITE_OPS: Readonly<Record<string, WriterOpSpec>> = Object.freeze({
33
+ "automations.create": { offLoop: true, idempotent: false, fn: (a: { normalized: Parameters<typeof insertAutomation>[0]; now: number }) => insertAutomation(a.normalized, a.now) },
34
+ "automations.update": { offLoop: true, idempotent: false, fn: (a: { id: string; normalized: Parameters<typeof updateAutomationNormalized>[1]; expected: Automation; now: number }) => {
35
+ const current = getAutomation(a.id);
36
+ if (!current || JSON.stringify(current) !== JSON.stringify(a.expected)) throw new ValidationError("automation changed or was deleted during catalog validation");
37
+ return updateAutomationNormalized(a.id, a.normalized, a.now);
38
+ } },
39
+ "automations.delete": { offLoop: true, idempotent: true, fn: (a: { id: string }) => deleteAutomation(a.id) },
40
+ "automations.admitRun": { offLoop: true, idempotent: false, fn: admitRun },
41
+ "automations.insertRun": { offLoop: true, idempotent: false, fn: (a: Parameters<typeof insertRun>) => insertRun(...a) },
42
+ "automations.enqueueRun": { offLoop: true, idempotent: false, fn: (a: Parameters<typeof enqueueAutomationRun>) => enqueueAutomationRun(...a) },
43
+ "automations.updateRun": { offLoop: true, idempotent: false, fn: (a: Parameters<typeof updateRun>) => updateRun(...a) ?? null },
44
+ "automations.cancelRuns": { offLoop: true, idempotent: false, fn: (a: Parameters<typeof cancelActiveRuns>) => cancelActiveRuns(...a) ?? null },
45
+ "automations.reschedule": { offLoop: true, idempotent: false, fn: (a: Parameters<typeof rescheduleAutomation>) => rescheduleAutomation(...a) ?? null },
46
+ });
@@ -161,11 +161,11 @@ export function searchEntries(
161
161
  return rows.map(rowToEntry);
162
162
  }
163
163
 
164
- export function retractEntry(id: string): BlackboardEntry | null {
164
+ export function retractEntry(id: string, submittedAt: number = Date.now()): BlackboardEntry | null {
165
165
  const existing = getEntry(id);
166
166
  if (!existing) return null;
167
167
  assertStatus("retracted");
168
168
  getDb().query("UPDATE blackboard_entries SET status = 'retracted', updated_at = ? WHERE id = ?")
169
- .run(Date.now(), id);
169
+ .run(submittedAt, id);
170
170
  return getEntry(id);
171
171
  }
@@ -0,0 +1,32 @@
1
+ import { getAgent } from "./agents";
2
+ import { createCommandInternal } from "../commands-db";
3
+ import { countActiveBrowserSessions, getBrowserSessionRow, insertBrowserSession, transitionBrowserSession } from "./browser-sessions";
4
+ import { getOrchestrator } from "./orchestrators";
5
+ import type { BrowserLimits } from "@humwick/sdk";
6
+
7
+ export interface StartBrowserWrite {
8
+ id: string; commandId: string; agentId: string; instanceId?: string; epoch?: number;
9
+ orchestratorId: string; limits: BrowserLimits;
10
+ }
11
+ export function startBrowserBody(input: StartBrowserWrite, now: number) {
12
+ const agent = getAgent(input.agentId);
13
+ if (!agent || agent.status === "offline" || agent.instanceId !== input.instanceId || agent.epoch !== input.epoch) return { conflict: "agent" as const };
14
+ const host = getOrchestrator(input.orchestratorId);
15
+ if (!host || host.status !== "online") return { conflict: "host" as const };
16
+ if (countActiveBrowserSessions({ agentId: input.agentId }) >= input.limits.perAgent) return { conflict: "agentLimit" as const };
17
+ if (countActiveBrowserSessions({ orchestratorId: host.id }) >= input.limits.perHost) return { conflict: "hostLimit" as const };
18
+ const command = createCommandInternal({ id: input.commandId, input: { type: "browser.start", source: input.agentId, target: host.agentId, correlationId: input.id,
19
+ params: { sessionId: input.id, agentId: input.agentId, orchestratorId: host.id, idleTimeoutMs: input.limits.idleTimeoutMinutes * 60_000, hostSessionLimit: input.limits.perHost } } }, now);
20
+ const row = insertBrowserSession({ id: input.id, agentId: input.agentId, orchestratorId: host.id, startCommandId: command.id, now });
21
+ return { row, command };
22
+ }
23
+
24
+ export function stopBrowserBody(input: { id: string; reason: string; commandId: string }, now: number) {
25
+ const row = getBrowserSessionRow(input.id);
26
+ if (!row || (row.status !== "starting" && row.status !== "running")) return { row };
27
+ const host = getOrchestrator(row.orchestratorId);
28
+ if (!host) return { row: transitionBrowserSession(row.id, ["starting", "running"], { status: "stopped", stoppedAt: now, error: "orchestrator no longer registered" }, now) };
29
+ const command = createCommandInternal({ id: input.commandId, input: { type: "browser.stop", source: "system", target: host.agentId, correlationId: row.id,
30
+ params: { sessionId: row.id, agentId: row.agentId, orchestratorId: host.id, reason: input.reason } } }, now);
31
+ return { row: transitionBrowserSession(row.id, ["starting", "running"], { status: "stopping", stopCommandId: command.id }, now), command };
32
+ }
@@ -31,8 +31,10 @@ export const BUS_OUTBOX_WRITE_OPS: Readonly<Record<string, WriterOpSpec>> = Obje
31
31
  "bus.append": {
32
32
  offLoop: true,
33
33
  // Appending assigns a new sequence row; retries reconcile by idempotency key rather than
34
- // inserting a second durable event after a phantom acknowledgement.
34
+ // inserting a second durable event after a phantom acknowledgement. A plain emit never retries,
35
+ // so it goes keyless and writes no ledger row (#3614).
35
36
  idempotent: false,
37
+ atMostOnce: true,
36
38
  fn: (args: BusAppendOpArgs, ctx: WriterOpContext) => ({
37
39
  ...appendRelayEventInternal(args.type, args.source, args.data, args.subject, ctx.now)!, replayed: false,
38
40
  }),
@@ -0,0 +1,47 @@
1
+ import type { getDb } from "./connection";
2
+
3
+ type Database = ReturnType<typeof getDb>;
4
+
5
+ // Keep one referenced command per checkout; generic command retention can expire its history (#3797).
6
+ export const CHECKOUT_REFRESH_SCHEMA = `
7
+ CREATE TABLE IF NOT EXISTS checkout_refresh_heads (
8
+ project_id TEXT NOT NULL,
9
+ orchestrator_id TEXT NOT NULL,
10
+ root_path TEXT NOT NULL,
11
+ command_id TEXT NOT NULL REFERENCES commands(id) ON DELETE CASCADE,
12
+ created_at INTEGER NOT NULL,
13
+ PRIMARY KEY (project_id, orchestrator_id, root_path)
14
+ );
15
+ CREATE INDEX IF NOT EXISTS idx_checkout_refresh_heads_command ON checkout_refresh_heads(command_id);
16
+ CREATE TRIGGER IF NOT EXISTS checkout_refresh_head_insert AFTER INSERT ON commands
17
+ WHEN NEW.type = 'project.checkout.refresh' AND json_valid(NEW.params)
18
+ AND json_type(NEW.params, '$.projectId') = 'text'
19
+ AND json_type(NEW.params, '$.orchestratorId') = 'text'
20
+ AND json_type(NEW.params, '$.rootPath') = 'text'
21
+ BEGIN
22
+ INSERT INTO checkout_refresh_heads VALUES (
23
+ json_extract(NEW.params, '$.projectId'), json_extract(NEW.params, '$.orchestratorId'),
24
+ json_extract(NEW.params, '$.rootPath'), NEW.id, NEW.created_at)
25
+ ON CONFLICT(project_id, orchestrator_id, root_path) DO UPDATE SET
26
+ command_id = excluded.command_id, created_at = excluded.created_at
27
+ WHERE excluded.created_at >= checkout_refresh_heads.created_at;
28
+ END;
29
+ `;
30
+
31
+ export function migrateCheckoutRefreshHeads(db: Database): void {
32
+ db.transaction(() => {
33
+ const installed = db.query("SELECT 1 FROM sqlite_schema WHERE type = 'table' AND name = 'checkout_refresh_heads'").get();
34
+ db.run(CHECKOUT_REFRESH_SCHEMA);
35
+ if (installed) return;
36
+ db.run(`INSERT INTO checkout_refresh_heads
37
+ SELECT project_id, orchestrator_id, root_path, id, created_at FROM (
38
+ SELECT id, created_at, json_extract(params, '$.projectId') AS project_id,
39
+ json_extract(params, '$.orchestratorId') AS orchestrator_id, json_extract(params, '$.rootPath') AS root_path,
40
+ row_number() OVER (PARTITION BY json_extract(params, '$.projectId'), json_extract(params, '$.orchestratorId'),
41
+ json_extract(params, '$.rootPath') ORDER BY created_at DESC, rowid DESC) AS ordinal
42
+ FROM commands WHERE type = 'project.checkout.refresh' AND json_valid(params)
43
+ AND json_type(params, '$.projectId') = 'text' AND json_type(params, '$.orchestratorId') = 'text'
44
+ AND json_type(params, '$.rootPath') = 'text'
45
+ ) WHERE ordinal = 1`);
46
+ })();
47
+ }