@humwick/server 0.130.1 → 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 (437) hide show
  1. package/README.md +5 -0
  2. package/docs/guides/tools-administration.md +4 -0
  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-B_rFEbnL.js → fleet-C7jQWbw2.js} +2 -2
  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 +8 -0
  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 +3 -3
  128. package/src/config-store-write.ts +13 -1
  129. package/src/config-store.ts +25 -14
  130. package/src/db/activity-async.ts +6 -1
  131. package/src/db/activity-write-ops.ts +12 -1
  132. package/src/db/activity.ts +1 -14
  133. package/src/db/agent-search.ts +1 -15
  134. package/src/db/agents-async.ts +52 -5
  135. package/src/db/agents-write-ops.ts +12 -3
  136. package/src/db/agents.ts +81 -54
  137. package/src/db/ci-incidents-async.ts +5 -5
  138. package/src/db/ci-incidents-write-ops.ts +2 -2
  139. package/src/db/command-row.ts +10 -2
  140. package/src/db/commands-list-query.ts +2 -0
  141. package/src/db/commands-spawn-read.ts +1 -1
  142. package/src/db/config-store-write-ops.ts +7 -2
  143. package/src/db/continuation-archives-async.ts +5 -0
  144. package/src/db/continuation-archives-write-ops.ts +7 -0
  145. package/src/db/continuations.ts +2 -2
  146. package/src/db/db-writer-worker.ts +8 -1
  147. package/src/db/deploy-events-async.ts +1 -0
  148. package/src/db/deploy-events-read-ops.ts +2 -1
  149. package/src/db/deploy-events.ts +35 -4
  150. package/src/db/hosts-registration.ts +10 -2
  151. package/src/db/hosts.ts +9 -6
  152. package/src/db/inbox.ts +0 -14
  153. package/src/db/index.ts +2 -0
  154. package/src/db/integrations-async.ts +8 -0
  155. package/src/db/integrations-ops.ts +15 -0
  156. package/src/db/integrations.ts +15 -21
  157. package/src/db/issue-lifecycle.ts +3 -4
  158. package/src/db/issue-refs-async.ts +20 -0
  159. package/src/db/issue-refs-write-ops.ts +29 -0
  160. package/src/db/issue-refs.ts +7 -8
  161. package/src/db/long-running-work.ts +2 -2
  162. package/src/db/main-connection-writes.ts +23 -2
  163. package/src/db/maintenance-job-reconcile.ts +61 -0
  164. package/src/db/maintenance-job-runs-async.ts +14 -0
  165. package/src/db/maintenance-job-write-ops.ts +9 -0
  166. package/src/db/mappers.ts +2 -14
  167. package/src/db/merge-lease.ts +1 -15
  168. package/src/db/migrations.ts +7 -15
  169. package/src/db/orchestrators.ts +4 -72
  170. package/src/db/pairs.ts +1 -13
  171. package/src/db/project-profile-instructions.ts +50 -0
  172. package/src/db/prompt-versions.ts +9 -2
  173. package/src/db/provider-config-history.ts +8 -3
  174. package/src/db/provider-model-seed.ts +34 -0
  175. package/src/db/read-op-marshaling-fixtures.ts +5 -0
  176. package/src/db/read-ops.ts +4 -0
  177. package/src/db/routing-policy.ts +2 -3
  178. package/src/db/schema.ts +9 -3
  179. package/src/db/spawn-delegation-state.ts +11 -4
  180. package/src/db/stats-async.ts +4 -3
  181. package/src/db/task-entity.ts +23 -2
  182. package/src/db/task-list.ts +3 -1
  183. package/src/db/tasks.ts +1 -17
  184. package/src/db/timer-ingress.ts +7 -1
  185. package/src/db/token-db-write-ops.ts +17 -0
  186. package/src/db/token-standing.ts +15 -11
  187. package/src/db/workflow-contact.ts +1 -1
  188. package/src/db/workflow-task-read-ops.ts +30 -0
  189. package/src/db/write-ops.ts +6 -0
  190. package/src/db/writer-gateway.ts +17 -11
  191. package/src/dispatch-availability.ts +10 -6
  192. package/src/ephemeral/{landing-ingress-proof-worker.ts → landing-ingress-proof.test-fixture.ts} +4 -2
  193. package/src/host/disk-alert.ts +121 -0
  194. package/src/lifecycle-manager.ts +3 -2
  195. package/src/maintenance/cd-watcher.ts +3 -3
  196. package/src/maintenance/critical-job-watchdog.ts +16 -17
  197. package/src/maintenance/index.ts +1 -0
  198. package/src/maintenance/jobs.ts +3 -2
  199. package/src/maintenance/scheduler.ts +84 -128
  200. package/src/maintenance/stalled-delivery-pager.ts +6 -5
  201. package/src/maintenance/workspaces.ts +1 -1
  202. package/src/managed-policy.ts +31 -27
  203. package/src/mcp/index.ts +5 -2
  204. package/src/mcp/output-descriptions.ts +8 -8
  205. package/src/mcp/presentation.ts +4 -4
  206. package/src/mcp/project-profile-instructions.ts +32 -0
  207. package/src/mcp/tool-groups.ts +1 -1
  208. package/src/mcp/tools-spawn.ts +4 -3
  209. package/src/mcp-agent-action.ts +1 -2
  210. package/src/mcp-issue-ref-tools.ts +12 -11
  211. package/src/mcp-maintenance-tools.ts +2 -2
  212. package/src/mcp-profile-trust-tools.ts +4 -4
  213. package/src/mcp-provider-catalog.ts +2 -2
  214. package/src/mcp-task-tools.ts +11 -5
  215. package/src/mcp-team-template-tools.ts +3 -3
  216. package/src/mcp-workflow-tools.ts +10 -3
  217. package/src/notification-types.ts +2 -1
  218. package/src/operator-alarm.ts +26 -11
  219. package/src/project-instructions.ts +4 -0
  220. package/src/project-profile-instruction-text.ts +6 -0
  221. package/src/project-profile-instructions.ts +20 -0
  222. package/src/provider-catalog-store.ts +3 -49
  223. package/src/provider-config-store.ts +12 -11
  224. package/src/provider-model-seed.ts +1 -18
  225. package/src/provider-quota-config-store.ts +5 -4
  226. package/src/provider-secrets.test-fixture.ts +3 -3
  227. package/src/recipe-runner.ts +2 -2
  228. package/src/repo-config-source.ts +10 -4
  229. package/src/routes/agent-profiles.ts +6 -6
  230. package/src/routes/agents-spawn.ts +1 -1
  231. package/src/routes/agents.ts +6 -6
  232. package/src/routes/commands-listing.ts +4 -0
  233. package/src/routes/commands.ts +2 -2
  234. package/src/routes/continuation-archives.ts +2 -2
  235. package/src/routes/index.ts +10 -1
  236. package/src/routes/integrations.ts +24 -50
  237. package/src/routes/issue-refs.ts +7 -7
  238. package/src/routes/orchestrator-bootstrap.ts +1 -1
  239. package/src/routes/orchestrator-host-registration.ts +2 -2
  240. package/src/routes/orchestrator-host-utilization.ts +3 -1
  241. package/src/routes/orchestrator-proxy.ts +2 -2
  242. package/src/routes/orchestrator.ts +2 -2
  243. package/src/routes/project-profile-instructions.ts +27 -0
  244. package/src/routes/project-trust.ts +2 -2
  245. package/src/routes/provider-config.ts +6 -6
  246. package/src/routes/provider-quota-config.ts +2 -2
  247. package/src/routes/settings.ts +5 -5
  248. package/src/routes/stats.ts +6 -6
  249. package/src/routes/task-http.ts +15 -1
  250. package/src/routes/task-issues.ts +11 -26
  251. package/src/routes/tasks.ts +22 -53
  252. package/src/routes/team-templates.ts +5 -5
  253. package/src/routes/teams-config.ts +2 -2
  254. package/src/routes/tokens.ts +12 -5
  255. package/src/routes/workflows.ts +20 -5
  256. package/src/routing-policy-normalize.ts +1 -2
  257. package/src/routing-step-defaults.ts +0 -2
  258. package/src/runner-token-mint-limit.ts +63 -0
  259. package/src/runtime-grant.ts +4 -2
  260. package/src/runtime-tokens.ts +39 -7
  261. package/src/security.ts +13 -20
  262. package/src/server-background.ts +1 -1
  263. package/src/server.ts +5 -2
  264. package/src/services/host-utilization.ts +23 -0
  265. package/src/services/idle-epics.ts +5 -6
  266. package/src/services/issue-lifecycle.ts +57 -61
  267. package/src/services/issue-refs.ts +9 -13
  268. package/src/services/kernel-timers-cutover-chaos-worker.ts +10 -7
  269. package/src/services/kernel-timers-ingress.ts +1 -1
  270. package/src/services/landed-ancestry.ts +19 -3
  271. package/src/services/memory-areas.ts +15 -51
  272. package/src/services/plan-ci-notice.ts +57 -0
  273. package/src/services/plan-ci-recovery.ts +2 -55
  274. package/src/services/plan-wake-digest.ts +1 -0
  275. package/src/services/profile-trust.ts +4 -3
  276. package/src/services/project-config-read.ts +70 -0
  277. package/src/services/project-profile-instructions.ts +54 -0
  278. package/src/services/provisioning-harness/budget-baseline.json +4 -4
  279. package/src/services/restart-spawn.ts +61 -49
  280. package/src/services/self-care-state.ts +2 -1
  281. package/src/services/self-effort.ts +5 -5
  282. package/src/services/send-message.ts +5 -1
  283. package/src/services/spawn-agent.ts +13 -2
  284. package/src/services/spawn-chain-inspector.ts +6 -13
  285. package/src/services/spawn-policy.ts +68 -0
  286. package/src/services/spawn-tree-cleanup.ts +2 -2
  287. package/src/services/task-dispatch.ts +3 -0
  288. package/src/services/task-state-reconcile.ts +6 -14
  289. package/src/services/team-templates.ts +9 -8
  290. package/src/services/transient-agent-reaper.ts +3 -1
  291. package/src/services/workflow-auto-start.ts +25 -13
  292. package/src/services/workflow-ci-main.ts +1 -1
  293. package/src/services/workflow-deferred-cleanup.ts +4 -2
  294. package/src/services/workflow-effect-driver.ts +2 -0
  295. package/src/services/workflow-effects.ts +18 -11
  296. package/src/services/workflow-land-notice.ts +10 -0
  297. package/src/services/workflow-pause-text.ts +43 -0
  298. package/src/services/workflow-reconcile.ts +13 -11
  299. package/src/services/workflow-run-view.ts +2 -0
  300. package/src/services/workflow-runs.ts +29 -13
  301. package/src/services/workflow-supervisor-state.ts +5 -0
  302. package/src/services/workflow-supervisor.ts +7 -3
  303. package/src/settings/registry.ts +1 -2
  304. package/src/spawn-command.ts +4 -4
  305. package/src/spawn-models.ts +10 -12
  306. package/src/spawn-placement.ts +1 -1
  307. package/src/spawn-targets.ts +1 -1
  308. package/src/startup-context-injection.ts +1 -0
  309. package/src/state-adoption/cd-cutover.ts +53 -7
  310. package/src/state-adoption/fleet-cutover.ts +35 -0
  311. package/src/state-adoption/fleet-plan.ts +36 -0
  312. package/src/state-adoption/index.ts +2 -2
  313. package/src/state-adoption/linux-plan.ts +2 -2
  314. package/src/state-adoption/linux.ts +2 -2
  315. package/src/state-adoption/macos-plan.ts +2 -2
  316. package/src/state-adoption/macos.ts +1 -1
  317. package/src/steward.ts +7 -6
  318. package/src/task-dispatch-live.ts +1 -1
  319. package/src/task-dispatcher.ts +1 -1
  320. package/src/teams-config-store.ts +4 -3
  321. package/src/token-db-write.ts +10 -0
  322. package/src/workspace-command-settle.ts +2 -1
  323. package/src/workspace-steward-wake.ts +2 -2
  324. package/node_modules/@humwick/kernel/LICENSE +0 -167
  325. package/node_modules/@humwick/kernel/async-entity-persistence.ts +0 -344
  326. package/node_modules/@humwick/kernel/async-event-log.ts +0 -107
  327. package/node_modules/@humwick/kernel/automations.pilot.ts +0 -147
  328. package/node_modules/@humwick/kernel/config.ts +0 -111
  329. package/node_modules/@humwick/kernel/contract.ts +0 -572
  330. package/node_modules/@humwick/kernel/control-authz.ts +0 -281
  331. package/node_modules/@humwick/kernel/control.ts +0 -645
  332. package/node_modules/@humwick/kernel/effects.ts +0 -493
  333. package/node_modules/@humwick/kernel/event-log.ts +0 -319
  334. package/node_modules/@humwick/kernel/http-transport.ts +0 -384
  335. package/node_modules/@humwick/kernel/identity-grammar.ts +0 -20
  336. package/node_modules/@humwick/kernel/identity.ts +0 -248
  337. package/node_modules/@humwick/kernel/index.ts +0 -28
  338. package/node_modules/@humwick/kernel/kernel-persistence.ts +0 -181
  339. package/node_modules/@humwick/kernel/landing-incident.ts +0 -306
  340. package/node_modules/@humwick/kernel/landing.ts +0 -953
  341. package/node_modules/@humwick/kernel/manifest-validation.ts +0 -460
  342. package/node_modules/@humwick/kernel/mcp-transport.ts +0 -109
  343. package/node_modules/@humwick/kernel/message-authority-chaos-worker.ts +0 -200
  344. package/node_modules/@humwick/kernel/message-authority.ts +0 -281
  345. package/node_modules/@humwick/kernel/module-host.ts +0 -722
  346. package/node_modules/@humwick/kernel/openapi-init.ts +0 -14
  347. package/node_modules/@humwick/kernel/package.json +0 -39
  348. package/node_modules/@humwick/kernel/persistence-schema.ts +0 -248
  349. package/node_modules/@humwick/kernel/persistence.ts +0 -510
  350. package/node_modules/@humwick/kernel/registry.ts +0 -60
  351. package/node_modules/@humwick/kernel/sqlite-kernel-persistence.ts +0 -499
  352. package/node_modules/@humwick/kernel/sqlite-module-host.ts +0 -87
  353. package/node_modules/@humwick/kernel/timers-chaos-worker.ts +0 -186
  354. package/node_modules/@humwick/kernel/timers.pilot.ts +0 -1411
  355. package/node_modules/@humwick/kernel/workflow-activation.ts +0 -25
  356. package/node_modules/@humwick/kernel/workflow-backstop.ts +0 -40
  357. package/node_modules/@humwick/kernel/workflow-cancel-tree.ts +0 -20
  358. package/node_modules/@humwick/kernel/workflow-chaos-worker.ts +0 -213
  359. package/node_modules/@humwick/kernel/workflow-composition.ts +0 -231
  360. package/node_modules/@humwick/kernel/workflow-container.ts +0 -72
  361. package/node_modules/@humwick/kernel/workflow-control.test-fixture.ts +0 -42
  362. package/node_modules/@humwick/kernel/workflow-control.ts +0 -178
  363. package/node_modules/@humwick/kernel/workflow-definition.ts +0 -507
  364. package/node_modules/@humwick/kernel/workflow-dispatch.ts +0 -60
  365. package/node_modules/@humwick/kernel/workflow-foreach.ts +0 -167
  366. package/node_modules/@humwick/kernel/workflow-journal-codec.ts +0 -339
  367. package/node_modules/@humwick/kernel/workflow-journal.ts +0 -922
  368. package/node_modules/@humwick/kernel/workflow-lane.ts +0 -48
  369. package/node_modules/@humwick/kernel/workflow-lease-owner.ts +0 -60
  370. package/node_modules/@humwick/kernel/workflow-recovery.test-fixture.ts +0 -19
  371. package/node_modules/@humwick/kernel/workflow-runtime.ts +0 -938
  372. package/node_modules/@humwick/kernel/workflow-script.ts +0 -11
  373. package/node_modules/@humwick/kernel/workflow-state.ts +0 -299
  374. package/node_modules/@humwick/kernel/workflow-step-commit.ts +0 -195
  375. package/node_modules/@humwick/kernel/workflow-transitions.ts +0 -317
  376. package/node_modules/@humwick/kernel/workflow-values.ts +0 -153
  377. package/node_modules/@humwick/kernel/workflow.pilot.ts +0 -376
  378. package/node_modules/@humwick/offload/LICENSE +0 -167
  379. package/node_modules/@humwick/offload/README.md +0 -57
  380. package/node_modules/@humwick/offload/dist/async-runner.d.ts +0 -18
  381. package/node_modules/@humwick/offload/dist/async-runner.d.ts.map +0 -1
  382. package/node_modules/@humwick/offload/dist/async-runner.js +0 -158
  383. package/node_modules/@humwick/offload/dist/async-runner.js.map +0 -1
  384. package/node_modules/@humwick/offload/dist/envelope.d.ts +0 -25
  385. package/node_modules/@humwick/offload/dist/envelope.d.ts.map +0 -1
  386. package/node_modules/@humwick/offload/dist/envelope.js +0 -80
  387. package/node_modules/@humwick/offload/dist/envelope.js.map +0 -1
  388. package/node_modules/@humwick/offload/dist/fixtures/worker-task.d.ts +0 -10
  389. package/node_modules/@humwick/offload/dist/fixtures/worker-task.d.ts.map +0 -1
  390. package/node_modules/@humwick/offload/dist/fixtures/worker-task.js +0 -59
  391. package/node_modules/@humwick/offload/dist/fixtures/worker-task.js.map +0 -1
  392. package/node_modules/@humwick/offload/dist/index.d.ts +0 -5
  393. package/node_modules/@humwick/offload/dist/index.d.ts.map +0 -1
  394. package/node_modules/@humwick/offload/dist/index.js +0 -5
  395. package/node_modules/@humwick/offload/dist/index.js.map +0 -1
  396. package/node_modules/@humwick/offload/dist/subprocess-runner.d.ts +0 -62
  397. package/node_modules/@humwick/offload/dist/subprocess-runner.d.ts.map +0 -1
  398. package/node_modules/@humwick/offload/dist/subprocess-runner.js +0 -295
  399. package/node_modules/@humwick/offload/dist/subprocess-runner.js.map +0 -1
  400. package/node_modules/@humwick/offload/dist/types.d.ts +0 -111
  401. package/node_modules/@humwick/offload/dist/types.d.ts.map +0 -1
  402. package/node_modules/@humwick/offload/dist/types.js +0 -176
  403. package/node_modules/@humwick/offload/dist/types.js.map +0 -1
  404. package/node_modules/@humwick/offload/dist/worker-pool.d.ts +0 -23
  405. package/node_modules/@humwick/offload/dist/worker-pool.d.ts.map +0 -1
  406. package/node_modules/@humwick/offload/dist/worker-pool.js +0 -435
  407. package/node_modules/@humwick/offload/dist/worker-pool.js.map +0 -1
  408. package/node_modules/@humwick/offload/dist/worker-runtime.d.ts +0 -2
  409. package/node_modules/@humwick/offload/dist/worker-runtime.d.ts.map +0 -1
  410. package/node_modules/@humwick/offload/dist/worker-runtime.js +0 -27
  411. package/node_modules/@humwick/offload/dist/worker-runtime.js.map +0 -1
  412. package/node_modules/@humwick/offload/dist/worker.d.ts +0 -5
  413. package/node_modules/@humwick/offload/dist/worker.d.ts.map +0 -1
  414. package/node_modules/@humwick/offload/dist/worker.js +0 -1
  415. package/node_modules/@humwick/offload/dist/worker.js.map +0 -1
  416. package/node_modules/@humwick/offload/package.json +0 -42
  417. package/node_modules/@humwick/offload/src/async-runner.ts +0 -170
  418. package/node_modules/@humwick/offload/src/envelope.ts +0 -92
  419. package/node_modules/@humwick/offload/src/fixtures/worker-task.ts +0 -49
  420. package/node_modules/@humwick/offload/src/index.ts +0 -4
  421. package/node_modules/@humwick/offload/src/subprocess-runner.ts +0 -334
  422. package/node_modules/@humwick/offload/src/types.ts +0 -235
  423. package/node_modules/@humwick/offload/src/worker-pool.ts +0 -463
  424. package/node_modules/@humwick/offload/src/worker-runtime.ts +0 -32
  425. package/node_modules/@humwick/offload/src/worker.ts +0 -5
  426. package/public/assets/agent-liveness-DyqxfB-t.js +0 -2
  427. package/public/assets/agent-profiles-BpI_jTxq.js +0 -2
  428. package/public/assets/index-B7ZerHLY.css +0 -2
  429. package/public/assets/index-BU3uWo2t.js +0 -15
  430. package/public/assets/projects-B_lmcqfO.js +0 -2
  431. package/public/assets/provider-display-DbLWpP3j.js +0 -2
  432. package/public/assets/run-card-CO_bguQf.js +0 -2
  433. package/public/assets/use-project-list-B-T2Bnnn.js +0 -2
  434. package/public/assets/versioned-prompt-editor-Diu8BIaM.js +0 -8
  435. package/runner/src/gate-run-lease-probe.ts +0 -38
  436. package/src/db/claude-catalog-reconcile.ts +0 -27
  437. package/src/routing-model-aliases.ts +0 -21
package/src/db/agents.ts CHANGED
@@ -479,28 +479,51 @@ export function applyAgentLifecycleUpdate(
479
479
  guard?: AgentSessionGuard,
480
480
  now: number = Date.now(),
481
481
  ): AgentLifecycleUpdateResult {
482
- const miss: AgentLifecycleUpdateResult = { ok: false, changed: false, metaWriteBytes: 0, before: null, after: null };
483
- const before = getPersistedAgentRuntimeCard(id);
484
- if (!before) return miss;
485
- if (!agentSessionGuardResult(before, guard).ok) return miss;
482
+ const seq = nextLifecycleSeq();
483
+ const before = prepareAgentLifecycleUpdate(id, update, guard);
484
+ if (!before) return { ok: false, changed: false, metaWriteBytes: 0, before: null, after: null };
485
+ return { ok: true, before, ...inWriteTransaction(() => applyAgentLifecycleUpdateInternal(agentLifecycleWriteArgs(before, update, seq, now))) };
486
+ }
486
487
 
488
+ /** The writer-op args for one lifecycle delta (#1870 S7). `seq` is taken at the caller's entry (lifecycle-seq.ts
489
+ * rule 1); `expectedEpoch` fences the write to the instance `before` was read from across the writer hop. */
490
+ export interface AgentLifecycleWriteArgs {
491
+ id: string;
492
+ update: AgentLifecycleUpdate;
493
+ seq: number;
494
+ now: number;
495
+ expectedEpoch: number;
496
+ before: { instanceId?: string; spawnRequestId: string; terminalProviderExit?: unknown };
497
+ }
498
+
499
+ export function agentLifecycleWriteArgs(before: AgentCard, update: AgentLifecycleUpdate, seq: number, now: number): AgentLifecycleWriteArgs {
500
+ return { id: before.id, update, seq, now, expectedEpoch: before.epoch, before: { instanceId: before.instanceId, spawnRequestId: String(before.meta?.spawnRequestId ?? ""), terminalProviderExit: before.meta?.terminalProviderExit } };
501
+ }
502
+
503
+ /** The main-thread half of a lifecycle delta: guard, `before` snapshot, transition handlers and presence
504
+ * 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 {
506
+ const before = getPersistedAgentRuntimeCard(id);
507
+ if (!before || !agentSessionGuardResult(before, guard).ok) return null;
487
508
  const nextStatus = update.status;
488
- // Pre-transition handlers are main-thread-only state (see runBeforeAgentStatusTransitionHandlers)
489
- // and must run BEFORE the write lands, exactly as `setStatus` does.
509
+ // Pre-transition handlers must run BEFORE the write lands, exactly as `setStatus` does.
490
510
  if (nextStatus !== undefined && nextStatus !== before.status) runBeforeAgentStatusTransitionHandlers(before, nextStatus);
491
511
  // #1468 — a terminal transition evicts any buffered beat so a coalesced advance cannot later
492
512
  // resurrect a gone agent at flush.
493
513
  if (nextStatus === "offline" || nextStatus === "stale") evictPendingPresence(id);
514
+ return before;
515
+ }
494
516
 
495
- // #2111 review BLOCKER 1 — raise each written column's ordinal so a lifecycle write the coalescing
496
- // window deferred cannot commit afterwards and undo it. This path only ever WINS its own races (it
497
- // commits synchronously, and any submission carrying a higher ordinal has not been allocated yet),
498
- // but it is fenced anyway so every writer of these columns obeys one rule.
499
- const seq = nextLifecycleSeq();
517
+ /** Op body (`agents.applyLifecycleUpdate`): the fenced UPDATE, its effects, and the offline post-commit, in the
518
+ * caller's transaction. #2111 review BLOCKER 1 — each written column's ordinal is raised, so a lifecycle write
519
+ * the coalescing window deferred cannot commit afterwards and undo it. */
520
+ export function applyAgentLifecycleUpdateInternal(args: AgentLifecycleWriteArgs): Omit<AgentLifecycleUpdateResult, "ok" | "before"> {
521
+ const { id, update, seq, now, before } = args;
522
+ const nextStatus = update.status;
500
523
  // Named params throughout: the fenced-column fragments repeat `$seq`, which positional `?` could
501
524
  // only express by counting placeholders across a dynamically-built SET list.
502
525
  const sets: string[] = ["last_seen = $now"];
503
- const params: Record<string, unknown> = { $now: now, $seq: seq, $id: id };
526
+ const params: Record<string, unknown> = { $now: now, $seq: seq, $id: id, $epoch: args.expectedEpoch };
504
527
  let metaWriteBytes = 0;
505
528
  if (nextStatus !== undefined) {
506
529
  sets.push(fencedColumnSql("status", "$status", "$seq"));
@@ -558,16 +581,10 @@ export function applyAgentLifecycleUpdate(
558
581
  params.$qs = runtime?.quota ? JSON.stringify(runtime.quota) : null;
559
582
  }
560
583
 
561
- // BEGIN IMMEDIATE (#1428/#1431) so busy_timeout governs contention, joining an ambient writer-op
562
- // transaction rather than nesting when this body is reached through the gateway.
563
- const changed = inWriteTransaction(() => {
564
- const applied = getDb().query(`UPDATE agents SET ${sets.join(", ")} WHERE id = $id`).run(params as never).changes > 0;
565
- const terminalLaunch = terminalProviderLaunchId(update.meta?.terminalProviderExit ?? before.meta?.terminalProviderExit);
566
- if (applied && nextStatus === "offline" && terminalLaunch) revokeVaultTokens(id, now, "provider_terminal_exit", before.instanceId, String(before.meta?.spawnRequestId ?? ""), terminalLaunch);
567
- if (applied && contextState) recordContextSnapshot(id, contextState, now);
568
- return applied;
569
- });
570
-
584
+ const changed = getDb().query(`UPDATE agents SET ${sets.join(", ")} WHERE id = $id AND epoch = $epoch`).run(params as never).changes > 0;
585
+ const terminalLaunch = terminalProviderLaunchId(update.meta?.terminalProviderExit ?? before.terminalProviderExit);
586
+ if (changed && nextStatus === "offline" && terminalLaunch) revokeVaultTokens(id, now, "provider_terminal_exit", before.instanceId, before.spawnRequestId, terminalLaunch);
587
+ if (changed && contextState) recordContextSnapshot(id, contextState, now);
571
588
  // NOTE (from setStatus): `offline` is a LIVENESS observation, not a confirmed death — it re-elects
572
589
  // stewards and closes pairs but must NOT release a steward's workspace claim (#1789 part 2, F2).
573
590
  // Gated on the requested status, not on a status CHANGE, matching setStatus's own condition.
@@ -575,7 +592,7 @@ export function applyAgentLifecycleUpdate(
575
592
  closeOpenPairsForAgent(id, now);
576
593
  electWorkspaceStewards();
577
594
  }
578
- return { ok: true, changed, metaWriteBytes, before, after: getPersistedAgentRuntimeCard(id) };
595
+ return { changed, metaWriteBytes, after: getPersistedAgentRuntimeCard(id) };
579
596
  }
580
597
 
581
598
  export function mergeAgentMeta(id: string, meta: Record<string, unknown>, guard?: AgentSessionGuard, now: number = Date.now()): boolean {
@@ -828,21 +845,27 @@ export function heartbeat(
828
845
  // withBufferedPresence / bufferedLiveAgentIds) live in the leaf module ./presence-buffer.ts so every
829
846
  // liveness reader can fold in the buffer without an import cycle (agents.ts imports channels.ts).
830
847
 
848
+ /** Heartbeat: buffers the steady-state liveness advance (flushed by `flushPresence`), writes presence-critical
849
+ * transitions at once. False if the session guard rejects or the agent is gone. Sync twin for tests; the
850
+ * transports use `recordHeartbeatAsync`, which writes the bypass on the writer thread (#1870 S7). */
851
+ export function recordHeartbeat(id: string, opts: HeartbeatInput = {}): boolean {
852
+ const beat = bufferOrBypassHeartbeat(id, opts);
853
+ return typeof beat === "boolean" ? beat : applyAgentLifecycleUpdate(id, beat, opts.guard, opts.now ?? Date.now()).changed;
854
+ }
855
+
856
+ export interface HeartbeatInput {
857
+ guard?: AgentSessionGuard;
858
+ /** Semantic status reported by the beat (bus path); undefined for the runtime-less API path. */
859
+ status?: AgentCard["status"];
860
+ runtime?: { providerCapabilities?: ProviderCapabilities; context?: ContextState; quota?: QuotaState };
861
+ now?: number;
862
+ }
863
+
831
864
  /**
832
- * Hot-path heartbeat entry point for the bus/API transports. Coalesces the steady-state liveness
833
- * advance into the in-memory buffer (flushed by `flushPresence`), but writes synchronously for
834
- * presence-critical transitions. Returns false if the session guard rejects or the agent is gone.
865
+ * The shared decision of `recordHeartbeat` and `recordHeartbeatAsync`: buffer a steady-state beat (returns
866
+ * the result) or hand back the lifecycle delta a presence-critical beat must write now.
835
867
  */
836
- export function recordHeartbeat(
837
- id: string,
838
- opts: {
839
- guard?: AgentSessionGuard;
840
- /** Semantic status reported by the beat (bus path); undefined for the runtime-less API path. */
841
- status?: AgentCard["status"];
842
- runtime?: { providerCapabilities?: ProviderCapabilities; context?: ContextState; quota?: QuotaState };
843
- now?: number;
844
- } = {},
845
- ): boolean {
868
+ export function bufferOrBypassHeartbeat(id: string, opts: HeartbeatInput): boolean | AgentLifecycleUpdate {
846
869
  if (!validateAgentSession(id, opts.guard).ok) return false;
847
870
  const current = getAgent(id);
848
871
  if (!current) return false;
@@ -861,9 +884,9 @@ export function recordHeartbeat(
861
884
  opts.runtime.context.lifecycleState !== current.context?.lifecycleState;
862
885
  const prev = pendingPresence.get(id);
863
886
  if (resurrection || statusChanged || lifecycleTransition) {
864
- // Merge any buffered-but-unflushed runtime cols into this synchronous write so the bypass never
865
- // DISCARDS a coalesced advance (a beat that carried context but was buffered, then a status
866
- // change arrives carrying none). Per column: this beat wins, else the pending value.
887
+ // Merge any buffered-but-unflushed runtime cols into this write so the bypass never DISCARDS a
888
+ // coalesced advance (a beat that carried context but was buffered, then a status change arrives
889
+ // carrying none). Per column: this beat wins, else the pending value.
867
890
  const runtime = prev
868
891
  ? {
869
892
  providerCapabilities: opts.runtime?.providerCapabilities ?? prev.providerCapabilities,
@@ -871,16 +894,10 @@ export function recordHeartbeat(
871
894
  quota: opts.runtime?.quota ?? prev.quota,
872
895
  }
873
896
  : opts.runtime;
874
- pendingPresence.delete(id); // this synchronous write supersedes any buffered advance
875
- // #2111 — was `heartbeat()` (one UPDATE) followed by `setStatus()` (a second UPDATE of the SAME
876
- // row inside its own BEGIN IMMEDIATE). One consolidated update now carries both; the explicit
877
- // status overrides the resurrection CASE exactly as the second write used to.
878
- return applyAgentLifecycleUpdate(
879
- id,
880
- { status: statusChanged ? opts.status : undefined, resurrectOnBeat: true, runtime },
881
- opts.guard,
882
- now,
883
- ).changed;
897
+ pendingPresence.delete(id); // this write supersedes any buffered advance
898
+ // #2111 — one consolidated update carries both the beat and the explicit status, which overrides
899
+ // the resurrection CASE.
900
+ return { status: statusChanged ? opts.status : undefined, resurrectOnBeat: true, runtime };
884
901
  }
885
902
 
886
903
  // Steady state: buffer. Merge context advisory markers now (matches heartbeat()'s per-beat merge),
@@ -1192,6 +1209,10 @@ export function deleteAgent(id: string): { ok: boolean; error?: string } {
1192
1209
  return deleted ? { ok: true } : { ok: false, error: "agent not found" };
1193
1210
  }
1194
1211
 
1212
+ export type OrphanedTokenRevocation = { agentId: string; jtis: string[] };
1213
+ /** One page of the orphan sweep: agents after `afterId` in id order, up to `limit` (#3776: one short write each). */
1214
+ export type OrphanedTokenPage = { revoked: OrphanedTokenRevocation[]; nextAfterId: string | null };
1215
+
1195
1216
  // #1057(1) — close the orphan-token window #1051 left open. A dead runner with no replacement
1196
1217
  // used to keep its token valid until the 24h offline prune (~720× the pre-#1051 ~2min horizon).
1197
1218
  // This sweep revokes ("stale", re-mintable — #484) the runtime tokens of agents whose process the
@@ -1204,12 +1225,18 @@ export function deleteAgent(id: string): { ok: boolean; error?: string } {
1204
1225
  // `lagGraceMs` mirrors the stale reaper's #1048/#1055 discipline: while the relay's own event
1205
1226
  // loop was stalled, a live claimant's heartbeats may not have been processed yet, so the window
1206
1227
  // widens by the observed lag rather than misreading the backlog as post-mortem silence.
1207
- export function revokeOrphanedRuntimeTokens(graceMs: number = STALE_TTL_MS, lagGraceMs = 0, now = Date.now()): Array<{ agentId: string; jtis: string[] }> {
1228
+ export function revokeOrphanedRuntimeTokens(
1229
+ graceMs: number = STALE_TTL_MS,
1230
+ lagGraceMs = 0,
1231
+ now = Date.now(),
1232
+ page: { afterId?: string; limit?: number } = {},
1233
+ ): OrphanedTokenPage {
1208
1234
  const cutoff = now - graceMs - Math.max(0, lagGraceMs);
1235
+ const limit = page.limit ?? -1;
1209
1236
  const rows = getDb()
1210
- .query(`SELECT id, meta, last_seen FROM agents WHERE status = 'offline' AND meta LIKE '%lastExitReceivedAt%' AND NOT ${reservedAgentIdSql()}`)
1211
- .all() as Array<{ id: string; meta: string | null; last_seen: number }>;
1212
- const revoked: Array<{ agentId: string; jtis: string[] }> = [];
1237
+ .query(`SELECT id, meta, last_seen FROM agents WHERE status = 'offline' AND meta LIKE '%lastExitReceivedAt%' AND NOT ${reservedAgentIdSql()} AND id > ? ORDER BY id LIMIT ?`)
1238
+ .all(page.afterId ?? "", limit) as Array<{ id: string; meta: string | null; last_seen: number }>;
1239
+ const revoked: OrphanedTokenRevocation[] = [];
1213
1240
  for (const row of rows) {
1214
1241
  const meta = parseJson<Record<string, unknown>>(row.meta ?? "{}", {});
1215
1242
  const receivedAt = typeof meta.lastExitReceivedAt === "number" ? meta.lastExitReceivedAt : undefined;
@@ -1218,7 +1245,7 @@ export function revokeOrphanedRuntimeTokens(graceMs: number = STALE_TTL_MS, lagG
1218
1245
  const jtis = revokeProviderRuntimeTokensByJti(runtimeTokenJtisFromMeta(meta), "stale", now);
1219
1246
  if (jtis.length > 0) revoked.push({ agentId: row.id, jtis });
1220
1247
  }
1221
- return revoked;
1248
+ return { revoked, nextAfterId: limit > 0 && rows.length === limit ? rows[rows.length - 1]!.id : null };
1222
1249
  }
1223
1250
 
1224
1251
  // #484: `reason` is stamped on each revocation so a stale-on-restart revocation
@@ -35,15 +35,15 @@ const write = <T>(op: string, args: unknown, idempotencyKey?: string): Promise<T
35
35
  export function upsertWatchStateAsync(input: Parameters<typeof upsertWatchState>[0]): Promise<ReturnType<typeof upsertWatchState>> {
36
36
  return write("ciIncidents.watch.upsert", input);
37
37
  }
38
- /** One gateway writer transaction for the complete watch + incident lifecycle decision. */
39
- export function ingestCiObservationTransitionAsync(input: CiObservationTransitionInput): Promise<CiObservationTransitionResult> {
40
- return write("ciIncidents.observation.ingest", input);
38
+ /** One gateway writer transaction for the complete watch + incident lifecycle decision. `now` pins a test clock. */
39
+ export function ingestCiObservationTransitionAsync(input: CiObservationTransitionInput, now?: number): Promise<CiObservationTransitionResult> {
40
+ return write("ciIncidents.observation.ingest", now === undefined ? input : { ...input, now });
41
41
  }
42
42
  export function markCiWatchNeverMatchedNotifiedAsync(watchKey: string): Promise<ReturnType<typeof markCiWatchNeverMatchedNotified>> {
43
43
  return write("ciIncidents.watch.markNeverMatched", { watchKey });
44
44
  }
45
- export function markCiRootMissingNotifiedAsync(repository: string): Promise<ReturnType<typeof markCiRootMissingNotified>> {
46
- return write("ciIncidents.root.markMissing", { repository });
45
+ export function markCiRootMissingNotifiedAsync(repository: string, now?: number): Promise<ReturnType<typeof markCiRootMissingNotified>> {
46
+ return write("ciIncidents.root.markMissing", now === undefined ? { repository } : { repository, now });
47
47
  }
48
48
  export function openOrDedupeIncidentAsync(input: Parameters<typeof openOrDedupeIncident>[0]): Promise<ReturnType<typeof openOrDedupeIncident>> {
49
49
  return write("ciIncidents.openOrDedupe", input);
@@ -27,7 +27,7 @@ export const CI_INCIDENTS_WRITE_OPS: Readonly<Record<string, WriterOpSpec>> = Ob
27
27
  "ciIncidents.observation.ingest": {
28
28
  offLoop: true,
29
29
  idempotent: true,
30
- fn: (a: Parameters<typeof applyCiObservationTransitionInternal>[0], ctx) => applyCiObservationTransitionInternal(a, ctx.now),
30
+ fn: (a: Parameters<typeof applyCiObservationTransitionInternal>[0] & { now?: number }, ctx) => applyCiObservationTransitionInternal(a, a.now ?? ctx.now),
31
31
  },
32
32
  "ciIncidents.gateRun.record": { offLoop: true, idempotent: true, fn: (a: Parameters<typeof recordCiGateRuns>[0]) => recordCiGateRuns(a) },
33
33
  "ciIncidents.gateBackfill.start": { offLoop: true, idempotent: true, fn: (a: Parameters<typeof startCiGateBackfill>[0]) => startCiGateBackfill(a) },
@@ -36,7 +36,7 @@ export const CI_INCIDENTS_WRITE_OPS: Readonly<Record<string, WriterOpSpec>> = Ob
36
36
  "ciIncidents.sha.prune": { offLoop: true, idempotent: true, fn: (_a: { now: number }, ctx) => pruneCiShaObservations(ctx.now) },
37
37
  "ciIncidents.watch.upsert": { offLoop: true, idempotent: true, fn: (a: Parameters<typeof upsertWatchState>[0], ctx) => upsertWatchState(a, ctx.now) },
38
38
  "ciIncidents.watch.markNeverMatched": { offLoop: true, idempotent: true, fn: (a: { watchKey: string }, ctx) => markCiWatchNeverMatchedNotified(a.watchKey, ctx.now) },
39
- "ciIncidents.root.markMissing": { offLoop: true, idempotent: true, fn: (a: { repository: string }, ctx) => markCiRootMissingNotified(a.repository, ctx.now) },
39
+ "ciIncidents.root.markMissing": { offLoop: true, idempotent: true, fn: (a: { repository: string; now?: number }, ctx) => markCiRootMissingNotified(a.repository, a.now ?? ctx.now) },
40
40
  "ciIncidents.openOrDedupe": { offLoop: true, idempotent: true, fn: (a: Parameters<typeof openOrDedupeIncidentInternal>[0], ctx) => openOrDedupeIncidentInternal(a, ctx.now, false) },
41
41
  // Guarded transitions throw after they have advanced; use the durable retry ledger rather than
42
42
  // misclassifying that as safe replay. Distinct steward attempts carry distinct request ids.
@@ -41,13 +41,15 @@ export interface CommandRow {
41
41
  }
42
42
 
43
43
 
44
- export function rowToCommand(row: CommandRow): Command {
44
+ /** `slim` (#3391): listings omit `params.agentProfile` (~0.5 MB per spawn row) instead of hydrating it;
45
+ * `GET /api/commands/:id` returns the full row. */
46
+ export function rowToCommand(row: CommandRow, options: { slim?: boolean } = {}): Command {
45
47
  return {
46
48
  id: row.id,
47
49
  type: row.type,
48
50
  source: row.source,
49
51
  target: row.target,
50
- params: hydrateCommandAgentProfile(parseObject(row.params), row.agent_profile_hash),
52
+ params: options.slim ? withoutAgentProfile(parseObject(row.params)) : hydrateCommandAgentProfile(parseObject(row.params), row.agent_profile_hash),
51
53
  status: row.status,
52
54
  result: row.result ? parseObject(row.result) : undefined,
53
55
  error: row.error ?? undefined,
@@ -78,6 +80,12 @@ export function rowToCommand(row: CommandRow): Command {
78
80
  };
79
81
  }
80
82
 
83
+ function withoutAgentProfile(params: Record<string, unknown>): Record<string, unknown> {
84
+ if (!("agentProfile" in params)) return params;
85
+ const { agentProfile: _omitted, ...rest } = params;
86
+ return rest;
87
+ }
88
+
81
89
  function parseObject(raw: string): Record<string, unknown> {
82
90
  try {
83
91
  const parsed = JSON.parse(raw);
@@ -9,6 +9,8 @@ export interface CommandFilters {
9
9
  dispatchableOnly?: boolean;
10
10
  /** #2663 — oldest-first for the executor DISPATCH feed, whose window is capped. See `getCommands`. */
11
11
  oldestFirst?: boolean;
12
+ /** #3391 — omit `params.agentProfile` from rows (listing views; fetch the row by id for the profile). */
13
+ slim?: boolean;
12
14
  }
13
15
 
14
16
  export function buildListCommandsQuery(filters: CommandFilters = {}): { sql: string; params: unknown[] } {
@@ -29,5 +29,5 @@ export const IN_FLIGHT_SPAWNS_BY_PARENT_SQL = `SELECT * FROM commands INDEXED BY
29
29
  /** One parent's agent.spawn commands still in flight, newest first. */
30
30
  export function listInFlightSpawnCommands(parentId: string, limit = 500): Command[] {
31
31
  const rows = getDb().query(IN_FLIGHT_SPAWNS_BY_PARENT_SQL).all(...SPAWN_IN_FLIGHT_STATUSES, parentId, limit) as CommandRow[];
32
- return rows.map(rowToCommand);
32
+ return rows.map((row) => rowToCommand(row));
33
33
  }
@@ -13,14 +13,15 @@
13
13
  // `_writer_idempotency`. That result is a `ConfigEntry` whose `value` is the SAME operator data already
14
14
  // persisted verbatim in the `config` table of the SAME database file — so this introduces NO new
15
15
  // at-rest exposure class (unlike a signed token, which is never otherwise stored). We therefore keep
16
- // the real result in the ledger (redacting it would corrupt a keyed phantom-retry's return value) and
17
- // deliberately omit `serializeIdempotencyResult`.
16
+ // the real result in the ledger, except provider credentials: the ledger is a copy outside the live row,
17
+ // so it masks them like config_history does (#3540, #1870 S7).
18
18
 
19
19
  import {
20
20
  deleteConfigInternal,
21
21
  setConfigInternal,
22
22
  } from "../config-store.ts";
23
23
  import type { ConfigEntry } from "../types.ts";
24
+ import { credentialFreeConfigValue } from "./provider-config-history.ts";
24
25
  import type { WriterOpContext, WriterOpSpec } from "./write-ops.ts";
25
26
 
26
27
  /** Structured-clone-safe args for `config.set` (H6: plain data only). `value` is arbitrary JSON. */
@@ -46,6 +47,10 @@ export const CONFIG_STORE_WRITE_OPS: Readonly<Record<string, WriterOpSpec>> = Ob
46
47
  offLoop: true,
47
48
  idempotent: false,
48
49
  idempotencySnapshotKey: (args: SetConfigOpArgs) => JSON.stringify([args.namespace, args.key]),
50
+ serializeIdempotencyResult: (result: unknown) => {
51
+ const entry = result as ConfigEntry;
52
+ return { ...entry, value: credentialFreeConfigValue(entry) };
53
+ },
49
54
  fn: (args: SetConfigOpArgs, ctx: WriterOpContext): ConfigEntry =>
50
55
  setConfigInternal(args.namespace, args.key, args.value, args.updatedBy, ctx.now),
51
56
  },
@@ -3,6 +3,7 @@ import { getDbPath, runDbMaintenanceWorker } from "./connection.ts";
3
3
  import { purgeContinuationArchives, type ContinuationArchivePurgeInput, type ContinuationArchivePurgeResult } from "./continuation-archive-purge.ts";
4
4
  import { syncInMemoryContinuationArchiveSearchIndex, type ContinuationArchiveSearchSyncInput, type ContinuationArchiveSearchSyncResult } from "./continuation-archive-search.ts";
5
5
  import { getGateway } from "./gateway.ts";
6
+ import type { appendContinuationArchiveRefs } from "./continuations.ts";
6
7
  import type { archiveAndAdvanceContinuation, archiveContinuationSegment, getContinuationArchiveByRef, searchContinuationArchives } from "./continuation-archives.ts";
7
8
 
8
9
  // Anything left after the budget is still removed, by the agent delete's cascade.
@@ -24,6 +25,10 @@ export function archiveAndAdvanceContinuationAsync(input: Parameters<typeof arch
24
25
  return getGateway().write("continuationArchives.compact", { input }, { idempotencyKey: requestId }) as unknown as Promise<ReturnType<typeof archiveAndAdvanceContinuation>>;
25
26
  }
26
27
 
28
+ export function appendContinuationArchiveRefsAsync(agentId: string, refs: string[]): Promise<ReturnType<typeof appendContinuationArchiveRefs>> {
29
+ return getGateway().write("continuationArchives.appendRefs", { agentId, refs }) as unknown as Promise<ReturnType<typeof appendContinuationArchiveRefs>>;
30
+ }
31
+
27
32
  /**
28
33
  * #3012 — catch the side FTS index up. A lock-free reader probe gates the run; the work runs in the
29
34
  * maintenance subprocess against the side file's own write lock, never on the hub's loop. An
@@ -1,4 +1,5 @@
1
1
  import { archiveAndAdvanceContinuation, archiveContinuationSegment } from "./continuation-archives.ts";
2
+ import { appendContinuationArchiveRefs } from "./continuations.ts";
2
3
  import type { WriterOpContext, WriterOpSpec } from "./write-ops.ts";
3
4
 
4
5
  export const CONTINUATION_ARCHIVES_WRITE_OPS: Readonly<Record<string, WriterOpSpec>> = Object.freeze({
@@ -14,4 +15,10 @@ export const CONTINUATION_ARCHIVES_WRITE_OPS: Readonly<Record<string, WriterOpSp
14
15
  fn: (args: { input: Parameters<typeof archiveAndAdvanceContinuation>[0] }, ctx: WriterOpContext) =>
15
16
  archiveAndAdvanceContinuation({ ...args.input, now: args.input.now ?? ctx.now }),
16
17
  },
18
+ // #1870 S7: the envelope's ref list is a set, so a replay converges.
19
+ "continuationArchives.appendRefs": {
20
+ offLoop: true,
21
+ idempotent: true,
22
+ fn: (args: { agentId: string; refs: string[] }, ctx: WriterOpContext) => appendContinuationArchiveRefs(args.agentId, args.refs, ctx.now),
23
+ },
17
24
  });
@@ -143,7 +143,7 @@ function mergeLedger(
143
143
  return { ledger: merged.slice(start), dropped: merged.slice(0, start) };
144
144
  }
145
145
 
146
- export function appendContinuationArchiveRefs(agentId: string, archiveRefs: string[]): ContinuationEnvelope | null {
146
+ export function appendContinuationArchiveRefs(agentId: string, archiveRefs: string[], now: number = Date.now()): ContinuationEnvelope | null {
147
147
  if (archiveRefs.length === 0) return getContinuationEnvelope(agentId);
148
148
  const existing = getContinuationEnvelope(agentId);
149
149
  if (!existing) return null;
@@ -155,7 +155,7 @@ export function appendContinuationArchiveRefs(agentId: string, archiveRefs: stri
155
155
  UPDATE agent_continuations
156
156
  SET archive_refs = ?, updated_at = ?
157
157
  WHERE agent_id = ?
158
- `).run(JSON.stringify(refs), Date.now(), agentId);
158
+ `).run(JSON.stringify(refs), now, agentId);
159
159
  return getContinuationEnvelope(agentId);
160
160
  }
161
161
 
@@ -14,12 +14,19 @@
14
14
  // It default-exports the offload task function; `createWorkerPool` invokes it via the offload
15
15
  // worker-runtime bootstrap.
16
16
 
17
+ import { relayEnv } from "@humwick/sdk/env";
17
18
  import type { OffloadWorkerTask } from "@humwick/offload/worker";
18
19
  import { Database } from "bun:sqlite";
19
20
  import { applyConnectionPragmas, closeDb, getDbPath, setDb } from "./connection.ts";
20
21
  import { serializeDbError } from "./db-error-registry.ts";
21
22
  import { runWriterBatch, runWriterOp, type SerializedBatchEntry, type WriterJobInput, type WriterJobResult } from "./write-ops.ts";
22
23
 
24
+ // A lock-contention test may shorten the wait; production keeps 4 s.
25
+ function writerBusyTimeoutMs(): number {
26
+ const override = Number(relayEnv(process.env, "AGENT_RELAY_WRITER_BUSY_TIMEOUT_MS")?.trim() || Number.NaN);
27
+ return Number.isSafeInteger(override) && override >= 0 ? override : 4000;
28
+ }
29
+
23
30
  /** Ensure this worker's thread-local RW connection points at `dbPath`, opening it once and reusing
24
31
  * it across jobs. A path change (never expected in production) reopens — cheap and defensive. */
25
32
  function ensureConnection(dbPath: string): void {
@@ -33,7 +40,7 @@ function ensureConnection(dbPath: string): void {
33
40
  // per-connection pragmas before it begins executing serialized write operations.
34
41
  applyConnectionPragmas(conn, { ensureWal: false });
35
42
  // #1870: external 1.2 s holds wait here, below the gateway's 5 s deadline, never on the loop.
36
- conn.run("PRAGMA busy_timeout = 4000");
43
+ conn.run(`PRAGMA busy_timeout = ${writerBusyTimeoutMs()}`);
37
44
  setDb(conn, dbPath);
38
45
  }
39
46
 
@@ -25,6 +25,7 @@ export function tryBeginDeployEventAsync(input: TryBeginDeployEventInput): Promi
25
25
  export function backfillDeployEventVersionAsync(id: string, version: string): Promise<DeployEvent | null> {
26
26
  return write("deployEvents.backfillVersion", { id, version });
27
27
  }
28
+ export function getDeployEventForCommitAsync(commitSha: string): Promise<DeployEvent | null> { return read("deployEvents.forCommit", { commitSha }); }
28
29
  export function getDeployEventAsync(id: string): Promise<DeployEvent | null> { return read("deployEvents.get", { id }); }
29
30
  export function listDeployEventsAsync(limit: number, offset: number): Promise<DeployEvent[]> {
30
31
  return read("deployEvents.list", { limit, offset });
@@ -3,7 +3,7 @@
3
3
  import {
4
4
  currentPendingDeployEvent,
5
5
  deployChangesForCommits,
6
- getDeployEvent,
6
+ getDeployEvent, getDeployEventForCommit,
7
7
  latestFailedDeployForCommit,
8
8
  latestSuccessfulDeploy,
9
9
  listDeployEvents,
@@ -14,6 +14,7 @@ import {
14
14
  } from "./deploy-events.ts";
15
15
 
16
16
  export const DEPLOY_EVENTS_READ_OPS = {
17
+ "deployEvents.forCommit": (args: { commitSha: string }): DeployEvent | null => getDeployEventForCommit(args.commitSha),
17
18
  "deployEvents.get": (args: { id: string }): DeployEvent | null => getDeployEvent(args.id),
18
19
  "deployEvents.list": (args: { limit: number; offset: number }): DeployEvent[] => listDeployEvents(args.limit, args.offset),
19
20
  "deployEvents.needingChanges": (args: { limit: number }): DeployEvent[] => listDeployEventsNeedingChanges(args.limit),
@@ -1,14 +1,16 @@
1
+ import { defaultTaskIssueRelation } from "./task-issue-relation";
2
+ import { parseJson, type TaskDispatch } from "@humwick/sdk";
1
3
  import { randomUUID } from "node:crypto";
2
4
  import { getDb, writeTransaction } from "./connection.ts";
3
5
  import { setTaskStageBody } from "./task-entity.ts";
4
6
  import { insertTaskEvent } from "./task-core.ts";
5
7
  import { listIssuesForEntity } from "./issue-associations.ts";
6
- import type { IssueRef } from "../types";
8
+ import type { IssueRef, IssueRefRelation } from "../types";
7
9
 
8
10
  export type DeployStatus = "pending" | "succeeded" | "failed" | "rolled-back";
9
11
  export type DeployHost = { id: string; fromVersion?: string; toVersion?: string; status: string; reason?: string };
10
12
  export type DeployCommit = { sha: string; subject: string };
11
- export type DeployTaskChange = { id: number; label: string; issues: IssueRef[] };
13
+ export type DeployTaskChange = { id: number; label: string; issues: Array<IssueRef & { relation?: IssueRefRelation }>; onDeploy?: string };
12
14
  export type DeployChangesSummary = {
13
15
  range: { previousSha?: string; commitSha: string };
14
16
  tasks: DeployTaskChange[];
@@ -219,6 +221,24 @@ export function getDeployEvent(id: string): DeployEvent | null {
219
221
  return row ? rowToDeployEvent(row) : null;
220
222
  }
221
223
 
224
+ /** The running artifact may boot while its deploy ledger is still pending (#3761). */
225
+ export function getDeployEventForCommit(commitSha: string): DeployEvent | null {
226
+ const row = getDb().query("SELECT * FROM deploy_events WHERE commit_sha = ? ORDER BY started_at DESC LIMIT 1").get(commitSha);
227
+ const event = row ? rowToDeployEvent(row) : null;
228
+ if (event?.changes?.state === "ready" && event.changes.tasks.length) {
229
+ const notes = getDb().query("SELECT id, on_deploy, dispatch, owner_agent_id FROM tasks WHERE id IN (SELECT value FROM json_each(?))")
230
+ .all(JSON.stringify(event.changes.tasks.map(task => task.id))) as Array<{ id: number; on_deploy: string | null; dispatch: string | null; owner_agent_id: string | null }>;
231
+ const byId = new Map(notes.map(task => [task.id, task]));
232
+ event.changes.tasks = event.changes.tasks.map(task => {
233
+ const { onDeploy, ...snapshot } = task;
234
+ const current = byId.get(task.id);
235
+ const note = current ? current.on_deploy : onDeploy;
236
+ return { ...snapshot, ...(current ? { issues: deployTaskIssues(task.id, current.dispatch, current.owner_agent_id) } : {}), ...(note ? { onDeploy: note } : {}) };
237
+ });
238
+ }
239
+ return event;
240
+ }
241
+
222
242
  /** Newest-first page of the deploy ledger for the dashboard timeline (#1327). */
223
243
  export function listDeployEvents(limit: number, offset: number): DeployEvent[] {
224
244
  const rows = getDb()
@@ -250,6 +270,11 @@ export function listDeployEventsNeedingChanges(limit: number): DeployEvent[] {
250
270
  return rows.map(rowToDeployEvent);
251
271
  }
252
272
 
273
+ function deployTaskIssues(id: number, dispatch: string | null, ownerAgentId: string | null): DeployTaskChange["issues"] {
274
+ const relation = defaultTaskIssueRelation(parseJson<TaskDispatch | undefined>(dispatch, undefined), ownerAgentId ?? undefined);
275
+ return listIssuesForEntity("task", String(id)).map(item => ({ ...item.issueRef, relation: item.association.relation ?? relation }));
276
+ }
277
+
253
278
  /**
254
279
  * Match a deploy's ordered commit list to every durable Task with an exact stored
255
280
  * commit SHA. This CD-only query loads the task commit payload once; the HTTP
@@ -258,7 +283,7 @@ export function listDeployEventsNeedingChanges(limit: number): DeployEvent[] {
258
283
  export function deployChangesForCommits(commits: DeployCommit[]): Pick<DeployChangesSummary, "tasks" | "unmatchedCommits"> {
259
284
  const rangeShas = new Set(commits.map((commit) => commit.sha));
260
285
  const matchedShas = new Set<string>();
261
- const rows = getDb().query("SELECT id, title, commits FROM tasks WHERE kind = 'deliverable' AND commits IS NOT NULL ORDER BY id ASC").all() as any[];
286
+ const rows = getDb().query("SELECT id, title, commits, on_deploy, dispatch, owner_agent_id FROM tasks WHERE kind = 'deliverable' AND commits IS NOT NULL ORDER BY id ASC").all() as any[];
262
287
  const tasks: DeployTaskChange[] = [];
263
288
  for (const row of rows) {
264
289
  let taskCommits: Array<{ sha?: unknown }>;
@@ -268,7 +293,8 @@ export function deployChangesForCommits(commits: DeployCommit[]): Pick<DeployCha
268
293
  tasks.push({
269
294
  id: Number(row.id),
270
295
  label: String(row.title),
271
- issues: listIssuesForEntity("task", String(row.id)).map((item) => item.issueRef),
296
+ issues: deployTaskIssues(Number(row.id), row.dispatch, row.owner_agent_id),
297
+ ...(row.on_deploy ? { onDeploy: String(row.on_deploy) } : {}),
272
298
  });
273
299
  }
274
300
  return { tasks, unmatchedCommits: commits.filter((commit) => !matchedShas.has(commit.sha)) };
@@ -316,6 +342,11 @@ export function renewDeployEventLease(input: { id: string; now?: number }): bool
316
342
  return writeTransaction(() => renewDeployEventLeaseBody(input, input.now ?? Date.now()));
317
343
  }
318
344
 
345
+ /** Migration progress shares the existing deploy row and dashboard surface (#3470). */
346
+ export function recordDeployEventHosts(id: string, hosts: DeployHost[]): boolean {
347
+ return getDb().query("UPDATE deploy_events SET hosts = ? WHERE id = ? AND status = 'pending'").run(JSON.stringify(hosts), id).changes > 0;
348
+ }
349
+
319
350
  /**
320
351
  * Emergency operator recovery for a genuinely stranded deploy owner:
321
352
  * `bun run scripts/release.ts --clear-deploy-lease <event-id>` (→ `failed`), or
@@ -139,8 +139,16 @@ export type HardwareReconciliation =
139
139
 
140
140
  /** Capacity is an observed, runtime-varying property, not host identity. WSL2 in particular can
141
141
  * report less RAM after a reboot, and swap may appear/disappear with zram configuration. Keep the
142
- * latest value for placement without making a legitimate capacity change an anti-spoof conflict. */
143
- const TOLERANT_CAPACITY_HARDWARE_FIELDS = new Set(["totalMemMb", "totalStorageMb", "totalSwapMb"]);
142
+ * latest value for placement without making a legitimate capacity change an anti-spoof conflict.
143
+ * `osVersion` (the kernel string) changes on every OS patch; it is not hardware identity (#3770). */
144
+ const TOLERANT_CAPACITY_HARDWARE_FIELDS = new Set(["totalMemMb", "totalStorageMb", "totalSwapMb", "osVersion"]);
145
+
146
+ /** The identity-defining keys present on both sides with different values: what a conflict notice names. */
147
+ export function conflictingHardwareFields(stored: HostHardware, reported: HostHardware): string[] {
148
+ const a = stored as unknown as Record<string, unknown>;
149
+ const b = reported as unknown as Record<string, unknown>;
150
+ return Object.keys(a).filter((key) => !TOLERANT_CAPACITY_HARDWARE_FIELDS.has(key) && a[key] !== undefined && b[key] !== undefined && a[key] !== b[key]).sort();
151
+ }
144
152
 
145
153
  export function reconcileHardwareFields(stored: HostHardware, reported: HostHardware): HardwareReconciliation {
146
154
  const storedRec = stored as unknown as Record<string, unknown>;
package/src/db/hosts.ts CHANGED
@@ -9,6 +9,7 @@ import type { Host, HostDaemonSample, HostHardware, HostMachineSample, HostPolic
9
9
  import { normalizeHostToken, parseJson } from "../utils";
10
10
  import { getDb } from "./connection.ts";
11
11
  import { primeHostAliases, type HostAliasRecord } from "../host/identity.ts";
12
+ import { settleDiskWatch, type DiskAlert } from "../host/disk-alert.ts";
12
13
  import { settleGateArming, type GateCapacityAlert } from "../host/gate-capacity-alert.ts";
13
14
 
14
15
  function rowToHost(row: any): Host {
@@ -99,18 +100,20 @@ function storedSample<T extends { at: number }>(table: "hosts" | "orchestrators"
99
100
  */
100
101
  export function recordHostUtilizationInternal(
101
102
  input: { hostId: string; orchestratorId: string; machine: HostMachineSample; daemon: HostDaemonSample },
102
- ): { hostUpdated: boolean; generationUpdated: boolean; gateAlert?: GateCapacityAlert; hostLabel?: string } {
103
+ ): { hostUpdated: boolean; generationUpdated: boolean; gateAlert?: GateCapacityAlert; diskAlert?: DiskAlert; hostLabel?: string } {
103
104
  // `last_seen_at` is deliberately NOT touched here: it is the Host's freshness clock and the §3.5
104
105
  // seam already bumps it on every beat with the RELAY's clock. `machine.at` is the PRODUCER's wall
105
106
  // clock, so writing it here would let a skewed daemon move a Host's liveness backwards.
106
107
  const prevHost = storedSample<HostMachineSample>("hosts", input.hostId);
107
108
  let hostUpdated = false;
108
109
  let gateAlert: GateCapacityAlert | undefined;
110
+ let diskAlert: DiskAlert | undefined;
109
111
  if (!prevHost || input.machine.at > prevHost.at) {
110
- // #2822 — settled inside this single-writer read-modify-write, so each transition alerts once.
111
- const settled = settleGateArming(prevHost ?? undefined, input.machine);
112
+ // #2822 / #3762 — settled inside this single-writer read-modify-write, so each transition alerts once.
113
+ const gate = settleGateArming(prevHost ?? undefined, input.machine);
114
+ const settled = settleDiskWatch(prevHost ?? undefined, gate.machine);
112
115
  hostUpdated = getDb().query("UPDATE hosts SET utilization = ? WHERE id = ?").run(JSON.stringify(settled.machine), input.hostId).changes > 0;
113
- if (hostUpdated) gateAlert = settled.alert;
116
+ if (hostUpdated) { gateAlert = gate.alert; diskAlert = settled.alert; }
114
117
  }
115
118
 
116
119
  const generationAt = storedSample<HostDaemonSample>("orchestrators", input.orchestratorId)?.at ?? null;
@@ -118,9 +121,9 @@ export function recordHostUtilizationInternal(
118
121
  ? false
119
122
  : getDb().query("UPDATE orchestrators SET utilization = ? WHERE id = ?").run(JSON.stringify(input.daemon), input.orchestratorId).changes > 0;
120
123
 
121
- if (!gateAlert) return { hostUpdated, generationUpdated };
124
+ if (!gateAlert && !diskAlert) return { hostUpdated, generationUpdated };
122
125
  const label = (getDb().query("SELECT label FROM hosts WHERE id = ?").get(input.hostId) as { label: string } | null)?.label;
123
- return { hostUpdated, generationUpdated, gateAlert, hostLabel: label ?? input.hostId };
126
+ return { hostUpdated, generationUpdated, ...(gateAlert ? { gateAlert } : {}), ...(diskAlert ? { diskAlert } : {}), hostLabel: label ?? input.hostId };
124
127
  }
125
128
 
126
129
  export function setHostLabelInternal(id: string, label: string): Host | null {
package/src/db/inbox.ts CHANGED
@@ -1,18 +1,4 @@
1
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";
16
2
  import { ValidationError, getDb, writeTransaction } from "./connection.ts";
17
3
  import { rowToChatHistoryImport, rowToChatHistoryImportEntry, rowToInboxDraft, rowToInboxThreadState } from "./mappers.ts";
18
4
  import { getMessage } from "./messages.ts";