@humwick/server 0.130.0 → 0.130.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (486) hide show
  1. package/README.md +5 -0
  2. package/docs/guides/tools-administration.md +6 -1
  3. package/docs/guides/tools-coordination.md +1 -0
  4. package/docs/guides/tools-tasks.md +8 -0
  5. package/docs/guides/workflows.md +58 -0
  6. package/docs/openapi.json +414 -1
  7. package/package.json +8 -17
  8. package/public/assets/{MessageBubble-BWpoBjl4.js → MessageBubble-BWLqMGKG.js} +2 -2
  9. package/public/assets/{PlanGraphPanel-C0auck3n.js → PlanGraphPanel-3TlIQpjk.js} +2 -2
  10. package/public/assets/{TraceView-DBnOx7AQ.js → TraceView-Wimm0Czv.js} +2 -2
  11. package/public/assets/{VaultDecisionCard-0kp2oNBC.js → VaultDecisionCard-Bau_vNax.js} +2 -2
  12. package/public/assets/{activity-yLztXEBt.js → activity-CrSurn-r.js} +2 -2
  13. package/public/assets/{agent-lineage-BbJDR1dW.js → agent-lineage-CknAJKcb.js} +2 -2
  14. package/public/assets/agent-liveness-CaNwK0Zf.js +2 -0
  15. package/public/assets/{agent-name-CTZBrSD9.js → agent-name-DKbiIhBo.js} +2 -2
  16. package/public/assets/agent-profiles-DwF6EQ4R.js +2 -0
  17. package/public/assets/{agent-project-badge-9TqzUUYE.js → agent-project-badge-Diz4Sjjb.js} +2 -2
  18. package/public/assets/{agent-tools-D4n40pn8.js → agent-tools-CRhpRQza.js} +2 -2
  19. package/public/assets/{agent-type-icon-D6FUarOf.js → agent-type-icon-LrUEU3lB.js} +2 -2
  20. package/public/assets/{agents-BYDslkJc.js → agents-B-v66C-4.js} +2 -2
  21. package/public/assets/{analytics-B-XSbEMC.js → analytics-BqCb-LKi.js} +2 -2
  22. package/public/assets/{artifact-viewer-DdWIfV6M.js → artifact-viewer-kf4pGuAR.js} +2 -2
  23. package/public/assets/{artifacts-oBOqks-2.js → artifacts-D32zyw4Y.js} +2 -2
  24. package/public/assets/{artifacts-DPN1q4pA.js → artifacts-DJevXcvg.js} +2 -2
  25. package/public/assets/{automation-4KcRFXEu.js → automation-BpRajx_5.js} +2 -2
  26. package/public/assets/{backlog-Cw9xPTCF.js → backlog-D_UjTdzX.js} +2 -2
  27. package/public/assets/{busy-health-CTu32MZt.js → busy-health-N3VEh4ao.js} +2 -2
  28. package/public/assets/{channels-BiXzg1G9.js → channels-9jopungq.js} +2 -2
  29. package/public/assets/{chat-Ctk2qwvA.js → chat-CSXrTB3C.js} +2 -2
  30. package/public/assets/{code-preview-Bm1wWqAp.js → code-preview-p1NnmqK0.js} +2 -2
  31. package/public/assets/{connectors-C36ax4Vw.js → connectors-CZdqlDNL.js} +2 -2
  32. package/public/assets/{cosmos-Jj9LK2Wf.js → cosmos-BgdsE4dG.js} +2 -2
  33. package/public/assets/{crate-agent-H6S7-B9T.js → crate-agent-30aXTKva.js} +2 -2
  34. package/public/assets/{dependency-graph-Cd1E23wa.js → dependency-graph-B_jvw2GE.js} +2 -2
  35. package/public/assets/{deploys-Bwfq146q.js → deploys-DoN6bAb5.js} +2 -2
  36. package/public/assets/{display-CfO7rTuF.js → display-BrKWFGJz.js} +2 -2
  37. package/public/assets/{files-DhhZI2Ih.js → files-D6XEu8Ng.js} +2 -2
  38. package/public/assets/fleet-C7jQWbw2.js +2 -0
  39. package/public/assets/{fleet-metrics-BkHP-P3R.js → fleet-metrics-BiDs3MLx.js} +2 -2
  40. package/public/assets/{formatted-body-Cm1RrzmN.js → formatted-body-CQBwc1fb.js} +3 -3
  41. package/public/assets/{formatted-body-impl-DQiOuOD1.js → formatted-body-impl-DyG0WsQS.js} +2 -2
  42. package/public/assets/{host-providers-dialog-CHqwXTov.js → host-providers-dialog-DKaW31dN.js} +2 -2
  43. package/public/assets/index-CRHep3M9.js +15 -0
  44. package/public/assets/index-MYAIwYTX.css +2 -0
  45. package/public/assets/{insights-fH1EL-qZ.js → insights-CzrtkpOE.js} +2 -2
  46. package/public/assets/{integrations-C5Oix6ng.js → integrations-aVsUmigt.js} +2 -2
  47. package/public/assets/{issue-ref-chip-ODyondIa.js → issue-ref-chip-BnSlM_95.js} +2 -2
  48. package/public/assets/{log-viewer-Cvfy6PW7.js → log-viewer-qe7Ee47Z.js} +2 -2
  49. package/public/assets/{maintenance-D8CDH5wZ.js → maintenance-NfjqFZAk.js} +2 -2
  50. package/public/assets/{managed-agents-egllbpij.js → managed-agents-BiH0HgaE.js} +2 -2
  51. package/public/assets/{markdown-preview-CYSFgVgK.js → markdown-preview-BixaT3Xr.js} +3 -3
  52. package/public/assets/{markdown-preview-impl-BteVIjX4.js → markdown-preview-impl-DNGlWIS3.js} +2 -2
  53. package/public/assets/{memory-C8WmXsyr.js → memory-DNsCl6UX.js} +2 -2
  54. package/public/assets/{messages-BzQdqlO6.js → messages-DopU1L1X.js} +2 -2
  55. package/public/assets/{orchestrators-B1HxzkNw.js → orchestrators-Cq7ckLQT.js} +2 -2
  56. package/public/assets/{overview-DCUnI4Z7.js → overview-C1DCTzzV.js} +2 -2
  57. package/public/assets/{pairs-ftCd2XcD.js → pairs-DE_m6rrE.js} +2 -2
  58. package/public/assets/{profile-icon-B2zH8DMH.js → profile-icon-B3rfh77F.js} +2 -2
  59. package/public/assets/project-profile-instructions-DQWZE0kA.js +2 -0
  60. package/public/assets/projects-Br2Bd2h1.js +2 -0
  61. package/public/assets/{prompt-settings-Bftq9czX.js → prompt-settings-BKvFsvGD.js} +2 -2
  62. package/public/assets/provider-display-DsuWMvVL.js +2 -0
  63. package/public/assets/{registry-DWCU5c8q.js → registry-Cw38_6gW.js} +2 -2
  64. package/public/assets/run-card-CpKqRbhg.js +2 -0
  65. package/public/assets/scroll-text-BKXS1ahr.js +2 -0
  66. package/public/assets/{secrets-C7GIUATz.js → secrets-DxI-vzSq.js} +2 -2
  67. package/public/assets/{service-access-settings-C3aujqTG.js → service-access-settings-D9s_VJbd.js} +2 -2
  68. package/public/assets/{settings-C6sZ9eBa.js → settings-CKZTVGIx.js} +2 -2
  69. package/public/assets/{shipping-Bu8p1gEw.js → shipping-B9YtnwtG.js} +2 -2
  70. package/public/assets/{stats-V9Z-GxUd.js → stats-DyUBKyWI.js} +2 -2
  71. package/public/assets/{status-dot-CVv0iGHL.js → status-dot-DYqlEUhJ.js} +2 -2
  72. package/public/assets/{store-BvtFdlGX.js → store-DcClXLH0.js} +3 -3
  73. package/public/assets/{tasks-CXU75HRF.js → tasks-Bh7Chr4-.js} +2 -2
  74. package/public/assets/{teams-BPf976zb.js → teams-bowI7qVY.js} +2 -2
  75. package/public/assets/{terminal-viewer-DE9BOe-8.js → terminal-viewer-BgVLHdsU.js} +3 -3
  76. package/public/assets/{terminal-viewer-impl-D04pjmGh.js → terminal-viewer-impl-Dz22UPy6.js} +2 -2
  77. package/public/assets/token-estimate-Co-Jdphh.js +2 -0
  78. package/public/assets/{use-agent-terminal-B1yR456R.js → use-agent-terminal-DSQQ-iY7.js} +2 -2
  79. package/public/assets/{use-decisions-D-S4tpDf.js → use-decisions-samMQhQf.js} +2 -2
  80. package/public/assets/use-project-list-59LheVP7.js +2 -0
  81. package/public/assets/{use-selectors-CnrAk2YP.js → use-selectors-BZa25xEB.js} +2 -2
  82. package/public/assets/{user-avatar-nBFIcR4M.js → user-avatar-DHxlvUvV.js} +2 -2
  83. package/public/assets/versioned-prompt-editor-BmED5DnJ.js +8 -0
  84. package/public/assets/{work-queue-TCjJxeT-.js → work-queue-CSvpuNLz.js} +2 -2
  85. package/public/assets/{workflow-marker-Cf3Hf2fN.js → workflow-marker-DAZcj5qt.js} +2 -2
  86. package/public/assets/{workflows-CXQX20Qj.js → workflows-DdmxRSyg.js} +2 -2
  87. package/public/assets/{workspaces-qSYzKYMA.js → workspaces-BxeKfVuF.js} +2 -2
  88. package/public/cosmos.html +4 -4
  89. package/public/index.html +31 -29
  90. package/runner/plugins/claude/.claude-plugin/plugin.json +1 -1
  91. package/runner/plugins/claude/hooks/gate-run-guard.provisioned.mjs +31 -1
  92. package/runner/src/adapters/claude-effort.ts +2 -4
  93. package/runner/src/adapters/codex-launch-config.ts +4 -5
  94. package/runner/src/adapters/codex.ts +1 -1
  95. package/runner/src/adapters/opencode-provider.ts +7 -22
  96. package/runner/src/adapters/opencode.ts +4 -4
  97. package/runner/src/agent-runner/credentials.ts +42 -12
  98. package/runner/src/agent-runner/inbox.ts +27 -0
  99. package/runner/src/agent-runner/lifecycle-milestones.ts +0 -2
  100. package/runner/src/bin/claude.ts +4 -0
  101. package/runner/src/bin/codex.ts +4 -0
  102. package/runner/src/bin/opencode.ts +4 -0
  103. package/runner/src/index.ts +5 -2
  104. package/runner/src/provider-setup.ts +34 -0
  105. package/runner/src/providers.ts +2 -1
  106. package/runner/src/runner-core.ts +18 -23
  107. package/runner/src/spawn-input.ts +2 -1
  108. package/runner/src/test-run-dedup.ts +59 -56
  109. package/scripts/cd-config.ts +24 -3
  110. package/scripts/gate-run-lease.ts +10 -7
  111. package/scripts/gate.sh +5 -1
  112. package/src/branch-land-notice.ts +93 -0
  113. package/src/branch-landed.ts +7 -76
  114. package/src/bus.ts +19 -20
  115. package/src/cd-deploy-notify.ts +30 -2
  116. package/src/cd-host-lag-notify.ts +7 -6
  117. package/src/ci/incidents.ts +2 -2
  118. package/src/ci/monitor.ts +5 -8
  119. package/src/ci/root-missing.ts +2 -7
  120. package/src/ci/transition-notify.ts +7 -39
  121. package/src/cli/cd-pauses.ts +19 -0
  122. package/src/cli/orchestrator.ts +1 -1
  123. package/src/cli/state.ts +2 -19
  124. package/src/cli/task-edit.ts +22 -0
  125. package/src/cli/task.ts +3 -1
  126. package/src/cli/workflow.ts +8 -2
  127. package/src/commands-db.ts +10 -6
  128. package/src/commands-write.ts +8 -0
  129. package/src/config-store-write.ts +13 -1
  130. package/src/config-store.ts +25 -14
  131. package/src/db/activity-async.ts +6 -1
  132. package/src/db/activity-write-ops.ts +13 -1
  133. package/src/db/activity.ts +1 -14
  134. package/src/db/agent-lifecycle-write-ops.ts +1 -0
  135. package/src/db/agent-meta.ts +2 -2
  136. package/src/db/agent-ownership-write-ops.ts +4 -0
  137. package/src/db/agent-search.ts +1 -15
  138. package/src/db/agents-async.ts +58 -7
  139. package/src/db/agents-write-ops.ts +28 -5
  140. package/src/db/agents.ts +81 -54
  141. package/src/db/artifacts-write-ops.ts +8 -3
  142. package/src/db/bus-outbox-write-ops.ts +1 -0
  143. package/src/db/channels-write-ops.ts +4 -0
  144. package/src/db/ci-incidents-async.ts +5 -5
  145. package/src/db/ci-incidents-write-ops.ts +2 -2
  146. package/src/db/claude-api-budget-ops.ts +5 -5
  147. package/src/db/claude-app-oauth.ts +4 -4
  148. package/src/db/codex-reset-consumes-write-ops.ts +3 -0
  149. package/src/db/command-row.ts +10 -2
  150. package/src/db/commands-list-query.ts +2 -0
  151. package/src/db/commands-spawn-read.ts +1 -1
  152. package/src/db/commands-write-ops.ts +21 -0
  153. package/src/db/config-store-write-ops.ts +9 -2
  154. package/src/db/continuation-archives-async.ts +5 -0
  155. package/src/db/continuation-archives-write-ops.ts +9 -0
  156. package/src/db/continuations.ts +2 -2
  157. package/src/db/db-writer-worker.ts +8 -1
  158. package/src/db/delivery-write-ops.ts +5 -0
  159. package/src/db/deploy-events-async.ts +1 -0
  160. package/src/db/deploy-events-read-ops.ts +2 -1
  161. package/src/db/deploy-events.ts +35 -4
  162. package/src/db/hosts-registration.ts +10 -2
  163. package/src/db/hosts-write-ops.ts +14 -0
  164. package/src/db/hosts.ts +9 -6
  165. package/src/db/inbox-write-ops.ts +4 -0
  166. package/src/db/inbox.ts +0 -14
  167. package/src/db/index.ts +2 -0
  168. package/src/db/insights-write-ops.ts +1 -0
  169. package/src/db/integrations-async.ts +8 -0
  170. package/src/db/integrations-ops.ts +15 -0
  171. package/src/db/integrations.ts +15 -21
  172. package/src/db/issue-lifecycle.ts +3 -4
  173. package/src/db/issue-refs-async.ts +20 -0
  174. package/src/db/issue-refs-write-ops.ts +29 -0
  175. package/src/db/issue-refs.ts +7 -8
  176. package/src/db/long-running-work.ts +2 -2
  177. package/src/db/macos-privacy.ts +1 -1
  178. package/src/db/main-connection-writes.ts +23 -2
  179. package/src/db/maintenance-job-reconcile.ts +61 -0
  180. package/src/db/maintenance-job-runs-async.ts +14 -0
  181. package/src/db/maintenance-job-write-ops.ts +9 -0
  182. package/src/db/mappers.ts +2 -14
  183. package/src/db/merge-lease.ts +1 -15
  184. package/src/db/message-reads-write-ops.ts +3 -0
  185. package/src/db/message-write-ops.ts +4 -0
  186. package/src/db/migrations.ts +7 -15
  187. package/src/db/orchestrators-async.ts +7 -3
  188. package/src/db/orchestrators-write-ops.ts +7 -1
  189. package/src/db/orchestrators.ts +21 -77
  190. package/src/db/pairs.ts +1 -13
  191. package/src/db/project-profile-instructions.ts +50 -0
  192. package/src/db/prompt-versions-ops.ts +1 -0
  193. package/src/db/prompt-versions.ts +9 -2
  194. package/src/db/provider-catalog-write-ops.ts +4 -4
  195. package/src/db/provider-config-history.ts +8 -3
  196. package/src/db/provider-flap-messages-write-ops.ts +1 -0
  197. package/src/db/provider-model-seed.ts +34 -0
  198. package/src/db/provider-quota-lease-write-ops.ts +1 -0
  199. package/src/db/provider-quota-write-ops.ts +2 -0
  200. package/src/db/provisioning-write-ops.ts +9 -8
  201. package/src/db/read-op-marshaling-fixtures.ts +5 -0
  202. package/src/db/read-ops.ts +4 -0
  203. package/src/db/routing-policy.ts +3 -4
  204. package/src/db/schema.ts +9 -3
  205. package/src/db/secrets-broker-write-ops.ts +5 -5
  206. package/src/db/secrets-vault-write.ts +7 -7
  207. package/src/db/services-write-ops.ts +3 -0
  208. package/src/db/spawn-delegation-state.ts +11 -4
  209. package/src/db/spawn-tree-cleanup.ts +3 -0
  210. package/src/db/stats-async.ts +4 -3
  211. package/src/db/task-entity.ts +23 -2
  212. package/src/db/task-list.ts +3 -1
  213. package/src/db/tasks.ts +1 -17
  214. package/src/db/team-spawn-delegation.ts +1 -1
  215. package/src/db/teams-write-ops.ts +4 -4
  216. package/src/db/timer-cutover-write-ops.ts +7 -0
  217. package/src/db/timer-ingress-write-ops.ts +9 -0
  218. package/src/db/timer-ingress.ts +7 -1
  219. package/src/db/timer-operator-write-ops.ts +14 -0
  220. package/src/db/timer-transfer-write-ops.ts +4 -0
  221. package/src/db/timer-write-ops.ts +14 -0
  222. package/src/db/token-db-write-ops.ts +21 -0
  223. package/src/db/token-standing.ts +15 -11
  224. package/src/db/user-settings-write-ops.ts +1 -0
  225. package/src/db/vault-backup.ts +2 -2
  226. package/src/db/vault-expiry.ts +2 -2
  227. package/src/db/vault-health.ts +3 -3
  228. package/src/db/vault-token-ops.ts +8 -8
  229. package/src/db/web-tool-call-ops.ts +3 -3
  230. package/src/db/workflow-contact.ts +1 -1
  231. package/src/db/workflow-task-read-ops.ts +30 -0
  232. package/src/db/write-ops.ts +15 -1
  233. package/src/db/writer-fault-seam.ts +5 -2
  234. package/src/db/writer-gateway.ts +21 -12
  235. package/src/dispatch-availability.ts +10 -6
  236. package/src/ephemeral/{landing-ingress-proof-worker.ts → landing-ingress-proof.test-fixture.ts} +4 -2
  237. package/src/host/disk-alert.ts +121 -0
  238. package/src/lifecycle-manager.ts +3 -2
  239. package/src/maintenance/cd-watcher.ts +3 -3
  240. package/src/maintenance/critical-job-watchdog.ts +16 -17
  241. package/src/maintenance/index.ts +1 -0
  242. package/src/maintenance/jobs.ts +3 -2
  243. package/src/maintenance/scheduler.ts +84 -128
  244. package/src/maintenance/stalled-delivery-pager.ts +6 -5
  245. package/src/maintenance/workspaces.ts +1 -1
  246. package/src/managed-policy.ts +31 -27
  247. package/src/mcp/index.ts +5 -2
  248. package/src/mcp/output-descriptions.ts +8 -8
  249. package/src/mcp/presentation.ts +4 -4
  250. package/src/mcp/project-profile-instructions.ts +32 -0
  251. package/src/mcp/tool-groups.ts +1 -1
  252. package/src/mcp/tools-spawn.ts +4 -3
  253. package/src/mcp-agent-action.ts +1 -2
  254. package/src/mcp-host-tools.ts +1 -1
  255. package/src/mcp-issue-ref-tools.ts +12 -11
  256. package/src/mcp-maintenance-tools.ts +2 -2
  257. package/src/mcp-profile-trust-tools.ts +4 -4
  258. package/src/mcp-provider-catalog.ts +2 -2
  259. package/src/mcp-task-tools.ts +11 -5
  260. package/src/mcp-team-template-tools.ts +3 -3
  261. package/src/mcp-workflow-tools.ts +10 -3
  262. package/src/notification-types.ts +2 -1
  263. package/src/operator-alarm.ts +26 -11
  264. package/src/package-pack.ts +42 -39
  265. package/src/project-instructions.ts +4 -0
  266. package/src/project-profile-instruction-text.ts +6 -0
  267. package/src/project-profile-instructions.ts +20 -0
  268. package/src/provider-catalog-store.ts +3 -49
  269. package/src/provider-config-store.ts +12 -11
  270. package/src/provider-model-seed.ts +1 -18
  271. package/src/provider-quota-config-store.ts +5 -4
  272. package/src/provider-secrets.test-fixture.ts +3 -3
  273. package/src/recipe-runner.ts +2 -2
  274. package/src/repo-config-source.ts +10 -4
  275. package/src/routes/agent-profiles.ts +6 -6
  276. package/src/routes/agents-spawn.ts +1 -1
  277. package/src/routes/agents.ts +6 -6
  278. package/src/routes/commands-listing.ts +4 -0
  279. package/src/routes/commands.ts +2 -2
  280. package/src/routes/continuation-archives.ts +2 -2
  281. package/src/routes/host-resource-control.ts +4 -1
  282. package/src/routes/index.ts +10 -1
  283. package/src/routes/integrations.ts +24 -50
  284. package/src/routes/issue-refs.ts +7 -7
  285. package/src/routes/orchestrator-bootstrap.ts +3 -3
  286. package/src/routes/orchestrator-host-registration.ts +2 -2
  287. package/src/routes/orchestrator-host-utilization.ts +3 -1
  288. package/src/routes/orchestrator-proxy.ts +2 -2
  289. package/src/routes/orchestrator.ts +4 -4
  290. package/src/routes/project-profile-instructions.ts +27 -0
  291. package/src/routes/project-trust.ts +2 -2
  292. package/src/routes/provider-config.ts +9 -8
  293. package/src/routes/provider-quota-config.ts +2 -2
  294. package/src/routes/settings.ts +5 -5
  295. package/src/routes/stats.ts +6 -6
  296. package/src/routes/task-http.ts +15 -1
  297. package/src/routes/task-issues.ts +11 -26
  298. package/src/routes/tasks.ts +22 -53
  299. package/src/routes/team-templates.ts +5 -5
  300. package/src/routes/teams-config.ts +2 -2
  301. package/src/routes/tokens.ts +12 -5
  302. package/src/routes/workflows.ts +20 -5
  303. package/src/routing-policy-normalize.ts +1 -2
  304. package/src/routing-step-defaults.ts +0 -2
  305. package/src/runner-token-mint-limit.ts +63 -0
  306. package/src/runtime-grant.ts +4 -2
  307. package/src/runtime-tokens.ts +50 -9
  308. package/src/security.ts +13 -20
  309. package/src/server-background.ts +1 -1
  310. package/src/server.ts +5 -2
  311. package/src/services/host-utilization.ts +23 -0
  312. package/src/services/idle-epics.ts +5 -6
  313. package/src/services/issue-lifecycle.ts +57 -61
  314. package/src/services/issue-refs.ts +9 -13
  315. package/src/services/kernel-timers-cutover-chaos-worker.ts +12 -7
  316. package/src/services/kernel-timers-ingress.ts +1 -1
  317. package/src/services/kernel-timers-request-key-migration-chaos-worker.ts +4 -1
  318. package/src/services/landed-ancestry.ts +19 -3
  319. package/src/services/memory-areas.ts +15 -51
  320. package/src/services/plan-ci-notice.ts +57 -0
  321. package/src/services/plan-ci-recovery.ts +2 -55
  322. package/src/services/plan-wake-digest.ts +1 -0
  323. package/src/services/profile-trust.ts +4 -3
  324. package/src/services/project-config-read.ts +70 -0
  325. package/src/services/project-profile-instructions.ts +54 -0
  326. package/src/services/provisioning-harness/budget-baseline.json +4 -4
  327. package/src/services/restart-spawn.ts +61 -49
  328. package/src/services/self-care-state.ts +2 -1
  329. package/src/services/self-effort.ts +5 -5
  330. package/src/services/send-message.ts +5 -1
  331. package/src/services/spawn-agent.ts +13 -2
  332. package/src/services/spawn-chain-inspector.ts +6 -13
  333. package/src/services/spawn-policy.ts +68 -0
  334. package/src/services/spawn-tree-cleanup.ts +2 -2
  335. package/src/services/task-dispatch.ts +3 -0
  336. package/src/services/task-state-reconcile.ts +6 -14
  337. package/src/services/team-templates.ts +9 -8
  338. package/src/services/transient-agent-reaper.ts +3 -1
  339. package/src/services/workflow-auto-start.ts +25 -13
  340. package/src/services/workflow-ci-main.ts +1 -1
  341. package/src/services/workflow-deferred-cleanup.ts +4 -2
  342. package/src/services/workflow-effect-driver.ts +2 -0
  343. package/src/services/workflow-effects.ts +18 -11
  344. package/src/services/workflow-land-notice.ts +10 -0
  345. package/src/services/workflow-pause-text.ts +43 -0
  346. package/src/services/workflow-reconcile.ts +13 -11
  347. package/src/services/workflow-run-view.ts +2 -0
  348. package/src/services/workflow-runs.ts +29 -13
  349. package/src/services/workflow-supervisor-state.ts +5 -0
  350. package/src/services/workflow-supervisor.ts +7 -3
  351. package/src/settings/registry.ts +1 -2
  352. package/src/spawn-command.ts +4 -4
  353. package/src/spawn-models.ts +10 -12
  354. package/src/spawn-placement.ts +1 -1
  355. package/src/spawn-targets.ts +1 -1
  356. package/src/startup-context-injection.ts +1 -0
  357. package/src/state-adoption/cd-cutover.ts +53 -7
  358. package/src/state-adoption/fleet-cutover.ts +35 -0
  359. package/src/state-adoption/fleet-plan.ts +36 -0
  360. package/src/state-adoption/index.ts +2 -2
  361. package/src/state-adoption/linux-plan.ts +2 -2
  362. package/src/state-adoption/linux.ts +2 -2
  363. package/src/state-adoption/macos-plan.ts +2 -2
  364. package/src/state-adoption/macos.ts +1 -1
  365. package/src/steward.ts +7 -6
  366. package/src/task-dispatch-live.ts +1 -1
  367. package/src/task-dispatcher.ts +1 -1
  368. package/src/teams-config-store.ts +4 -3
  369. package/src/token-db-write.ts +10 -0
  370. package/src/workspace-command-settle.ts +2 -1
  371. package/src/workspace-steward-wake.ts +2 -2
  372. package/node_modules/@humwick/kernel/LICENSE +0 -167
  373. package/node_modules/@humwick/kernel/async-entity-persistence.ts +0 -344
  374. package/node_modules/@humwick/kernel/async-event-log.ts +0 -107
  375. package/node_modules/@humwick/kernel/automations.pilot.ts +0 -147
  376. package/node_modules/@humwick/kernel/config.ts +0 -111
  377. package/node_modules/@humwick/kernel/contract.ts +0 -572
  378. package/node_modules/@humwick/kernel/control-authz.ts +0 -281
  379. package/node_modules/@humwick/kernel/control.ts +0 -645
  380. package/node_modules/@humwick/kernel/effects.ts +0 -493
  381. package/node_modules/@humwick/kernel/event-log.ts +0 -319
  382. package/node_modules/@humwick/kernel/http-transport.ts +0 -384
  383. package/node_modules/@humwick/kernel/identity-grammar.ts +0 -20
  384. package/node_modules/@humwick/kernel/identity.ts +0 -248
  385. package/node_modules/@humwick/kernel/index.ts +0 -28
  386. package/node_modules/@humwick/kernel/kernel-persistence.ts +0 -181
  387. package/node_modules/@humwick/kernel/landing-incident.ts +0 -306
  388. package/node_modules/@humwick/kernel/landing.ts +0 -953
  389. package/node_modules/@humwick/kernel/manifest-validation.ts +0 -460
  390. package/node_modules/@humwick/kernel/mcp-transport.ts +0 -109
  391. package/node_modules/@humwick/kernel/message-authority-chaos-worker.ts +0 -200
  392. package/node_modules/@humwick/kernel/message-authority.ts +0 -281
  393. package/node_modules/@humwick/kernel/module-host.ts +0 -722
  394. package/node_modules/@humwick/kernel/openapi-init.ts +0 -14
  395. package/node_modules/@humwick/kernel/package.json +0 -47
  396. package/node_modules/@humwick/kernel/persistence-schema.ts +0 -248
  397. package/node_modules/@humwick/kernel/persistence.ts +0 -510
  398. package/node_modules/@humwick/kernel/registry.ts +0 -60
  399. package/node_modules/@humwick/kernel/sqlite-kernel-persistence.ts +0 -499
  400. package/node_modules/@humwick/kernel/sqlite-module-host.ts +0 -87
  401. package/node_modules/@humwick/kernel/timers-chaos-worker.ts +0 -186
  402. package/node_modules/@humwick/kernel/timers.pilot.ts +0 -1411
  403. package/node_modules/@humwick/kernel/workflow-activation.ts +0 -25
  404. package/node_modules/@humwick/kernel/workflow-backstop.ts +0 -40
  405. package/node_modules/@humwick/kernel/workflow-cancel-tree.ts +0 -20
  406. package/node_modules/@humwick/kernel/workflow-chaos-worker.ts +0 -213
  407. package/node_modules/@humwick/kernel/workflow-composition.ts +0 -231
  408. package/node_modules/@humwick/kernel/workflow-container.ts +0 -72
  409. package/node_modules/@humwick/kernel/workflow-control.test-fixture.ts +0 -42
  410. package/node_modules/@humwick/kernel/workflow-control.ts +0 -178
  411. package/node_modules/@humwick/kernel/workflow-definition.ts +0 -507
  412. package/node_modules/@humwick/kernel/workflow-dispatch.ts +0 -60
  413. package/node_modules/@humwick/kernel/workflow-foreach.ts +0 -167
  414. package/node_modules/@humwick/kernel/workflow-journal-codec.ts +0 -339
  415. package/node_modules/@humwick/kernel/workflow-journal.ts +0 -922
  416. package/node_modules/@humwick/kernel/workflow-lane.ts +0 -48
  417. package/node_modules/@humwick/kernel/workflow-lease-owner.ts +0 -60
  418. package/node_modules/@humwick/kernel/workflow-recovery.test-fixture.ts +0 -19
  419. package/node_modules/@humwick/kernel/workflow-runtime.ts +0 -938
  420. package/node_modules/@humwick/kernel/workflow-script.ts +0 -11
  421. package/node_modules/@humwick/kernel/workflow-state.ts +0 -299
  422. package/node_modules/@humwick/kernel/workflow-step-commit.ts +0 -195
  423. package/node_modules/@humwick/kernel/workflow-transitions.ts +0 -317
  424. package/node_modules/@humwick/kernel/workflow-values.ts +0 -153
  425. package/node_modules/@humwick/kernel/workflow.pilot.ts +0 -376
  426. package/node_modules/@humwick/offload/LICENSE +0 -167
  427. package/node_modules/@humwick/offload/README.md +0 -57
  428. package/node_modules/@humwick/offload/dist/async-runner.d.ts +0 -18
  429. package/node_modules/@humwick/offload/dist/async-runner.d.ts.map +0 -1
  430. package/node_modules/@humwick/offload/dist/async-runner.js +0 -158
  431. package/node_modules/@humwick/offload/dist/async-runner.js.map +0 -1
  432. package/node_modules/@humwick/offload/dist/envelope.d.ts +0 -25
  433. package/node_modules/@humwick/offload/dist/envelope.d.ts.map +0 -1
  434. package/node_modules/@humwick/offload/dist/envelope.js +0 -80
  435. package/node_modules/@humwick/offload/dist/envelope.js.map +0 -1
  436. package/node_modules/@humwick/offload/dist/fixtures/worker-task.d.ts +0 -10
  437. package/node_modules/@humwick/offload/dist/fixtures/worker-task.d.ts.map +0 -1
  438. package/node_modules/@humwick/offload/dist/fixtures/worker-task.js +0 -59
  439. package/node_modules/@humwick/offload/dist/fixtures/worker-task.js.map +0 -1
  440. package/node_modules/@humwick/offload/dist/index.d.ts +0 -5
  441. package/node_modules/@humwick/offload/dist/index.d.ts.map +0 -1
  442. package/node_modules/@humwick/offload/dist/index.js +0 -5
  443. package/node_modules/@humwick/offload/dist/index.js.map +0 -1
  444. package/node_modules/@humwick/offload/dist/subprocess-runner.d.ts +0 -62
  445. package/node_modules/@humwick/offload/dist/subprocess-runner.d.ts.map +0 -1
  446. package/node_modules/@humwick/offload/dist/subprocess-runner.js +0 -295
  447. package/node_modules/@humwick/offload/dist/subprocess-runner.js.map +0 -1
  448. package/node_modules/@humwick/offload/dist/types.d.ts +0 -111
  449. package/node_modules/@humwick/offload/dist/types.d.ts.map +0 -1
  450. package/node_modules/@humwick/offload/dist/types.js +0 -176
  451. package/node_modules/@humwick/offload/dist/types.js.map +0 -1
  452. package/node_modules/@humwick/offload/dist/worker-pool.d.ts +0 -23
  453. package/node_modules/@humwick/offload/dist/worker-pool.d.ts.map +0 -1
  454. package/node_modules/@humwick/offload/dist/worker-pool.js +0 -435
  455. package/node_modules/@humwick/offload/dist/worker-pool.js.map +0 -1
  456. package/node_modules/@humwick/offload/dist/worker-runtime.d.ts +0 -2
  457. package/node_modules/@humwick/offload/dist/worker-runtime.d.ts.map +0 -1
  458. package/node_modules/@humwick/offload/dist/worker-runtime.js +0 -27
  459. package/node_modules/@humwick/offload/dist/worker-runtime.js.map +0 -1
  460. package/node_modules/@humwick/offload/dist/worker.d.ts +0 -5
  461. package/node_modules/@humwick/offload/dist/worker.d.ts.map +0 -1
  462. package/node_modules/@humwick/offload/dist/worker.js +0 -1
  463. package/node_modules/@humwick/offload/dist/worker.js.map +0 -1
  464. package/node_modules/@humwick/offload/package.json +0 -46
  465. package/node_modules/@humwick/offload/src/async-runner.ts +0 -170
  466. package/node_modules/@humwick/offload/src/envelope.ts +0 -92
  467. package/node_modules/@humwick/offload/src/fixtures/worker-task.ts +0 -49
  468. package/node_modules/@humwick/offload/src/index.ts +0 -4
  469. package/node_modules/@humwick/offload/src/subprocess-runner.ts +0 -334
  470. package/node_modules/@humwick/offload/src/types.ts +0 -235
  471. package/node_modules/@humwick/offload/src/worker-pool.ts +0 -463
  472. package/node_modules/@humwick/offload/src/worker-runtime.ts +0 -32
  473. package/node_modules/@humwick/offload/src/worker.ts +0 -5
  474. package/public/assets/agent-liveness-DyqxfB-t.js +0 -2
  475. package/public/assets/agent-profiles-BpI_jTxq.js +0 -2
  476. package/public/assets/fleet-BL4UXS_k.js +0 -2
  477. package/public/assets/index-B7ZerHLY.css +0 -2
  478. package/public/assets/index-BK0WV6hF.js +0 -15
  479. package/public/assets/projects-B_lmcqfO.js +0 -2
  480. package/public/assets/provider-display-DbLWpP3j.js +0 -2
  481. package/public/assets/run-card-CO_bguQf.js +0 -2
  482. package/public/assets/use-project-list-B-T2Bnnn.js +0 -2
  483. package/public/assets/versioned-prompt-editor-Diu8BIaM.js +0 -8
  484. package/runner/src/gate-run-lease-probe.ts +0 -38
  485. package/src/db/claude-catalog-reconcile.ts +0 -27
  486. package/src/routing-model-aliases.ts +0 -21
@@ -0,0 +1,15 @@
1
+ import { listIntegrationTaskStats, reconcileIntegrationRegistry, type IntegrationRegistryInput } from "./integrations.ts";
2
+ import type { WriterOpSpec } from "./write-ops.ts";
3
+
4
+ export const INTEGRATIONS_READ_OPS = {
5
+ "integrations.taskStats": () => listIntegrationTaskStats(),
6
+ };
7
+
8
+ export const INTEGRATIONS_WRITE_OPS: Readonly<Record<string, WriterOpSpec>> = {
9
+ "integrations.reconcileRegistry": {
10
+ offLoop: true,
11
+ idempotent: true,
12
+ fn: (args: { configured: IntegrationRegistryInput[]; observedSources: string[] }, ctx) =>
13
+ reconcileIntegrationRegistry(args.configured, args.observedSources, ctx.now),
14
+ },
15
+ };
@@ -1,18 +1,5 @@
1
- import { randomUUID } from "node:crypto";
2
- import { isRecord, stringValue, isMechanicalMessageKind } from "@humwick/sdk";
3
- import { ORCHESTRATOR_PROTOCOL_VERSION, VERSION } from "../config.ts";
1
+ import { stringValue } from "@humwick/sdk";
4
2
  import { parseJson } from "../utils";
5
- import { isLiveIsolatedWorkspace } from "../workspace-phase";
6
- import {
7
- CONTRACT_REQUIREMENTS,
8
- contractCompatibility,
9
- parseRuntimeCapabilities,
10
- parseRuntimeContracts,
11
- parseRuntimePackage,
12
- type RuntimeContracts,
13
- } from "../contracts";
14
- import { STALE_TTL_MS, DAY_MS, CLAIM_LEASE_MS, POOL_CLAIM_LEASE_MS, WORKSPACE_MERGE_LEASE_MS } from "../config";
15
- import { matchAgents } from "../agent-ref";
16
3
  import { normalizeTags, parseStringArray } from "./agents.ts";
17
4
  import { linkAttachmentRefs, validateAttachmentRefs } from "./artifacts.ts";
18
5
  import { ValidationError, getDb, writeTransaction } from "./connection.ts";
@@ -143,9 +130,7 @@ function integrationTaskMetadata(
143
130
  };
144
131
  }
145
132
 
146
-
147
- export function listIntegrationTaskStats(): IntegrationTaskStats[] {
148
- const rows = getDb().query(`
133
+ export const INTEGRATION_TASK_STATS_SQL = `
149
134
  SELECT
150
135
  coalesce(json_extract(metadata, '$.relayIntegrationName'), source) AS source,
151
136
  COUNT(*) AS tasks,
@@ -157,7 +142,10 @@ export function listIntegrationTaskStats(): IntegrationTaskStats[] {
157
142
  FROM tasks
158
143
  GROUP BY coalesce(json_extract(metadata, '$.relayIntegrationName'), source)
159
144
  ORDER BY last_seen_at DESC, source ASC
160
- `).all() as any[];
145
+ `;
146
+
147
+ export function listIntegrationTaskStats(): IntegrationTaskStats[] {
148
+ const rows = getDb().query(INTEGRATION_TASK_STATS_SQL).all() as any[];
161
149
 
162
150
  return rows.map((row) => ({
163
151
  source: row.source,
@@ -170,7 +158,7 @@ export function listIntegrationTaskStats(): IntegrationTaskStats[] {
170
158
  }));
171
159
  }
172
160
 
173
- type IntegrationRegistryInput = {
161
+ export type IntegrationRegistryInput = {
174
162
  name: string;
175
163
  displayName?: string;
176
164
  description?: string;
@@ -220,10 +208,9 @@ function rowToIntegrationRegistry(row: any): Omit<IntegrationSummary, "configure
220
208
  };
221
209
  }
222
210
 
223
- export function upsertIntegrationRegistry(input: IntegrationRegistryInput): ReturnType<typeof rowToIntegrationRegistry> {
211
+ export function upsertIntegrationRegistry(input: IntegrationRegistryInput, now = Date.now()): ReturnType<typeof rowToIntegrationRegistry> {
224
212
  const name = stringValue(input.name);
225
213
  if (!name) throw new ValidationError("integration name required");
226
- const now = Date.now();
227
214
  getDb().query(`
228
215
  INSERT INTO integration_registry (
229
216
  name, display_name, description, enabled, scopes, targets, channels, type, icon, accent_color,
@@ -295,3 +282,10 @@ export function isIntegrationRegistryEnabled(name: string): boolean {
295
282
  const registry = getIntegrationRegistry(name);
296
283
  return registry?.enabled !== false;
297
284
  }
285
+
286
+ // GET reconciliation preserves registry precedence, but runs entirely on the writer (#3740).
287
+ export function reconcileIntegrationRegistry(configured: IntegrationRegistryInput[], observedSources: string[], now: number): ReturnType<typeof listIntegrationRegistry> {
288
+ for (const input of configured) upsertIntegrationRegistry({ ...input, source: "env" }, now);
289
+ for (const name of observedSources) upsertIntegrationRegistry({ name, enabled: true, source: "observed" }, now);
290
+ return listIntegrationRegistry();
291
+ }
@@ -5,7 +5,7 @@ const ISSUE_LIFECYCLE_STATUSES = ["succeeded", "failed"] as const;
5
5
  type IssueLifecycleAction = (typeof ISSUE_LIFECYCLE_ACTIONS)[number];
6
6
  type IssueLifecycleStatus = (typeof ISSUE_LIFECYCLE_STATUSES)[number];
7
7
 
8
- interface IssueLifecycleActionRecord {
8
+ export interface IssueLifecycleActionRecord {
9
9
  id: number;
10
10
  issueRefId: string;
11
11
  action: IssueLifecycleAction;
@@ -24,7 +24,7 @@ interface IssueLifecycleActionRecord {
24
24
  updatedAt: number;
25
25
  }
26
26
 
27
- interface UpsertIssueLifecycleActionInput {
27
+ export interface UpsertIssueLifecycleActionInput {
28
28
  issueRefId: string;
29
29
  action: IssueLifecycleAction;
30
30
  landKey: string;
@@ -150,8 +150,7 @@ export function listSuccessfulCloseActionsForCommit(input: { commitSha?: string;
150
150
  return rows.map(rowToIssueLifecycleAction);
151
151
  }
152
152
 
153
- export function upsertIssueLifecycleAction(input: UpsertIssueLifecycleActionInput): IssueLifecycleActionRecord {
154
- const now = Date.now();
153
+ export function upsertIssueLifecycleAction(input: UpsertIssueLifecycleActionInput, now: number = Date.now()): IssueLifecycleActionRecord {
155
154
  const issueRefId = cleanRequiredText(input.issueRefId, "issueRefId", 120);
156
155
  const action = cleanAction(input.action);
157
156
  const landKey = cleanRequiredText(input.landKey, "landKey", 240);
@@ -0,0 +1,20 @@
1
+ // #1870 S7 — async facades for the issue-ref writer ops (`issue-refs-write-ops.ts`).
2
+ import { randomUUID } from "node:crypto";
3
+ import { getGateway } from "./gateway.ts";
4
+ import type { IssueRef } from "../types";
5
+ import type { IssueTrackerIssueSummary } from "../issue-tracker/adapter";
6
+ import type { ResolveIssueRefByExternalKeyInput, ResolveIssueRefFromStringInput, UpdateIssueRefInput, UpsertIssueRefInput } from "./issue-refs.ts";
7
+ import type { IssueLifecycleActionRecord, UpsertIssueLifecycleActionInput } from "./issue-lifecycle.ts";
8
+
9
+ const write = <T>(op: string, args: unknown, opts?: { idempotencyKey?: string }) => getGateway().write(op, args as never, opts) as unknown as Promise<T>;
10
+
11
+ export const upsertIssueRefAsync = (input: UpsertIssueRefInput) => write<IssueRef>("issueRefs.upsert", input);
12
+ export const resolveIssueRefByExternalKeyAsync = (input: ResolveIssueRefByExternalKeyInput) => write<IssueRef>("issueRefs.resolveExternalKey", input);
13
+ export const resolveIssueRefFromStringAsync = (input: ResolveIssueRefFromStringInput) => write<IssueRef>("issueRefs.resolveFromString", input);
14
+ export const updateIssueRefAsync = (id: string, input: UpdateIssueRefInput) => write<IssueRef | null>("issueRefs.update", { id, input });
15
+ export const deleteIssueRefAsync = (id: string) => write<boolean>("issueRefs.delete", { id });
16
+ /** The tracker answered: clear the not-found streak and cache its status, in one write. */
17
+ export const recordIssueRefLiveStatusAsync = (id: string, input: UpdateIssueRefInput) => write<IssueRef | null>("issueRefs.recordLiveStatus", { id, input });
18
+ export const putIssueHierarchyAsync = (id: string, children: IssueTrackerIssueSummary[]) => write<void>("issueRefs.putHierarchy", { id, children });
19
+ export const recordIssueTrackerNotFoundAsync = (id: string) => write<number>("issueRefs.recordNotFound", { id }, { idempotencyKey: randomUUID() });
20
+ export const upsertIssueLifecycleActionAsync = (input: UpsertIssueLifecycleActionInput) => write<IssueLifecycleActionRecord>("issueLifecycle.upsertAction", input);
@@ -0,0 +1,29 @@
1
+ // #1870 S7 — issue-ref cache, tracker refresh state and lifecycle ledger writes run on the writer thread.
2
+ // Every body is plain SQL against its own tables. All but `recordNotFound` converge on replay: upserts are
3
+ // keyed by (provider, repo, externalKey) or (issue, action, landKey), updates re-set the same columns.
4
+ import { resolveIssueRefByExternalKey, resolveIssueRefFromString, updateIssueRef, upsertIssueRef, deleteIssueRef, type ResolveIssueRefByExternalKeyInput, type ResolveIssueRefFromStringInput, type UpdateIssueRefInput, type UpsertIssueRefInput } from "./issue-refs.ts";
5
+ import { clearIssueTrackerNotFound, putIssueHierarchy, recordIssueTrackerNotFound } from "./long-running-work.ts";
6
+ import { upsertIssueLifecycleAction, type UpsertIssueLifecycleActionInput } from "./issue-lifecycle.ts";
7
+ import type { IssueTrackerIssueSummary } from "../issue-tracker/adapter";
8
+ import type { WriterOpContext, WriterOpSpec } from "./write-ops.ts";
9
+
10
+ export const ISSUE_REFS_WRITE_OPS: Readonly<Record<string, WriterOpSpec>> = Object.freeze({
11
+ "issueRefs.upsert": { offLoop: true, idempotent: true, fn: (args: UpsertIssueRefInput, ctx: WriterOpContext) => upsertIssueRef(args, ctx.now) },
12
+ "issueRefs.resolveExternalKey": { offLoop: true, idempotent: true, fn: (args: ResolveIssueRefByExternalKeyInput, ctx: WriterOpContext) => resolveIssueRefByExternalKey(args, ctx.now) },
13
+ "issueRefs.resolveFromString": { offLoop: true, idempotent: true, fn: (args: ResolveIssueRefFromStringInput, ctx: WriterOpContext) => resolveIssueRefFromString(args, ctx.now) },
14
+ "issueRefs.update": { offLoop: true, idempotent: true, fn: (args: { id: string; input: UpdateIssueRefInput }, ctx: WriterOpContext) => updateIssueRef(args.id, args.input, ctx.now) },
15
+ "issueRefs.delete": { offLoop: true, idempotent: true, fn: (args: { id: string }) => deleteIssueRef(args.id) },
16
+ // A tracker answered for this ref: the not-found streak ends and the cached status follows the tracker.
17
+ "issueRefs.recordLiveStatus": {
18
+ offLoop: true,
19
+ idempotent: true,
20
+ fn: (args: { id: string; input: UpdateIssueRefInput }, ctx: WriterOpContext) => {
21
+ clearIssueTrackerNotFound(args.id);
22
+ return updateIssueRef(args.id, args.input, ctx.now);
23
+ },
24
+ },
25
+ "issueRefs.putHierarchy": { offLoop: true, idempotent: true, fn: (args: { id: string; children: IssueTrackerIssueSummary[] }, ctx: WriterOpContext) => putIssueHierarchy(args.id, args.children, ctx.now) },
26
+ // Increments a counter, so a replay would double-count: keyed.
27
+ "issueRefs.recordNotFound": { offLoop: true, idempotent: false, fn: (args: { id: string }) => recordIssueTrackerNotFound(args.id) },
28
+ "issueLifecycle.upsertAction": { offLoop: true, idempotent: true, fn: (args: UpsertIssueLifecycleActionInput, ctx: WriterOpContext) => upsertIssueLifecycleAction(args, ctx.now) },
29
+ });
@@ -151,7 +151,7 @@ export function getIssueRefByExternalKey(provider: IssueProvider, repo: string,
151
151
  return row ? rowToIssueRef(row) : null;
152
152
  }
153
153
 
154
- export function upsertIssueRef(input: UpsertIssueRefInput): IssueRef {
154
+ export function upsertIssueRef(input: UpsertIssueRefInput, now: number = Date.now()): IssueRef {
155
155
  const provider = cleanProvider(input.provider);
156
156
  const repo = requiredText(input.repo, "repo", 500);
157
157
  const externalKey = requiredText(input.externalKey, "externalKey", 240);
@@ -162,7 +162,6 @@ export function upsertIssueRef(input: UpsertIssueRefInput): IssueRef {
162
162
  // An open/unknown upsert clears a stale closeReason; an explicit closeReason requires closed.
163
163
  const closeReason = input.closeReason === undefined && status !== "closed" ? undefined : cleanCloseReason(input.closeReason, status) ?? (status === "closed" ? existing?.closeReason : undefined);
164
164
  const statusSyncedAt = cleanSyncedAt(input.statusSyncedAt);
165
- const now = Date.now();
166
165
  if (existing) {
167
166
  getDb().query(`
168
167
  UPDATE issue_refs
@@ -179,7 +178,7 @@ export function upsertIssueRef(input: UpsertIssueRefInput): IssueRef {
179
178
  return getIssueRef(id)!;
180
179
  }
181
180
 
182
- export function resolveIssueRefByExternalKey(input: ResolveIssueRefByExternalKeyInput): IssueRef {
181
+ export function resolveIssueRefByExternalKey(input: ResolveIssueRefByExternalKeyInput, now: number = Date.now()): IssueRef {
183
182
  const provider = cleanProvider(input.provider);
184
183
  const repo = requiredText(input.repo, "repo", 500);
185
184
  const externalKey = requiredText(input.externalKey, "externalKey", 240);
@@ -196,7 +195,7 @@ export function resolveIssueRefByExternalKey(input: ResolveIssueRefByExternalKey
196
195
  status: input.status ?? existing?.status,
197
196
  closeReason: input.closeReason ?? (input.status !== undefined ? undefined : existing?.closeReason),
198
197
  statusSyncedAt: input.statusSyncedAt ?? existing?.statusSyncedAt,
199
- });
198
+ }, now);
200
199
  }
201
200
 
202
201
  // A BARE `#123` ref carries no repo, so it only resolves when the caller supplies a
@@ -231,7 +230,7 @@ export function issueRefForRefString(ref: string, defaultRepo?: string): IssueRe
231
230
  return getIssueRefByExternalKey("github", parsed.repo, parsed.externalKey);
232
231
  }
233
232
 
234
- export function resolveIssueRefFromString(input: ResolveIssueRefFromStringInput): IssueRef {
233
+ export function resolveIssueRefFromString(input: ResolveIssueRefFromStringInput, now: number = Date.now()): IssueRef {
235
234
  const ref = requiredText(input.ref, "ref", 1000);
236
235
  if (ref.startsWith("issue_")) {
237
236
  const existing = getIssueRef(ref);
@@ -250,7 +249,7 @@ export function resolveIssueRefFromString(input: ResolveIssueRefFromStringInput)
250
249
  status: input.status,
251
250
  closeReason: input.closeReason,
252
251
  statusSyncedAt: input.statusSyncedAt,
253
- });
252
+ }, now);
254
253
  }
255
254
 
256
255
  export function listIssueRefs(opts: ListIssueRefsOptions = {}): IssueRef[] {
@@ -281,7 +280,7 @@ export function listIssueRefs(opts: ListIssueRefsOptions = {}): IssueRef[] {
281
280
  return rows.map(rowToIssueRef);
282
281
  }
283
282
 
284
- export function updateIssueRef(id: string, input: UpdateIssueRefInput): IssueRef | null {
283
+ export function updateIssueRef(id: string, input: UpdateIssueRefInput, now: number = Date.now()): IssueRef | null {
285
284
  const existing = getIssueRef(requiredText(id, "id", 120));
286
285
  if (!existing) return null;
287
286
  const url = optionalText(input.url, "url", 2000) ?? existing.url;
@@ -295,7 +294,7 @@ export function updateIssueRef(id: string, input: UpdateIssueRefInput): IssueRef
295
294
  UPDATE issue_refs
296
295
  SET url = ?, title = ?, status = ?, close_reason = ?, status_synced_at = ?, updated_at = ?
297
296
  WHERE id = ?
298
- `).run(url, title ?? null, status, closeReason ?? null, statusSyncedAt ?? null, Date.now(), existing.id);
297
+ `).run(url, title ?? null, status, closeReason ?? null, statusSyncedAt ?? null, now, existing.id);
299
298
  return getIssueRef(existing.id);
300
299
  }
301
300
 
@@ -49,10 +49,10 @@ export function settlePendingIssueClose(args: PendingIssueCloseKey & { finished:
49
49
  else getDb().query("UPDATE pending_issue_closes SET updated_at = ? WHERE issue_ref_id = ? AND land_key = ?").run(now, args.issueRefId, args.landKey);
50
50
  }
51
51
 
52
- export function putIssueHierarchy(issueRefId: string, children: IssueTrackerIssueSummary[]): void {
52
+ export function putIssueHierarchy(issueRefId: string, children: IssueTrackerIssueSummary[], now: number = Date.now()): void {
53
53
  getDb().query(`INSERT INTO issue_hierarchy_snapshots(issue_ref_id, children, updated_at) VALUES (?, ?, ?)
54
54
  ON CONFLICT(issue_ref_id) DO UPDATE SET children = excluded.children, updated_at = excluded.updated_at`)
55
- .run(issueRefId, JSON.stringify(children), Date.now());
55
+ .run(issueRefId, JSON.stringify(children), now);
56
56
  }
57
57
 
58
58
  export function getIssueHierarchy(issueRefId: string): IssueTrackerIssueSummary[] | undefined {
@@ -68,5 +68,5 @@ export function reconcileMacosPrivacy(args: { orchestratorId: string; prompts: M
68
68
  }
69
69
 
70
70
  export const MACOS_PRIVACY_WRITE_OPS = {
71
- "macosPrivacy.reconcile": { idempotent: true, fn: reconcileMacosPrivacy },
71
+ "macosPrivacy.reconcile": { offLoop: true, idempotent: true, fn: reconcileMacosPrivacy },
72
72
  };
@@ -34,10 +34,31 @@ export class MainConnectionWriteCounter {
34
34
  }
35
35
  let counter = new MainConnectionWriteCounter();
36
36
  export function resetMainConnectionWrites(): void {
37
- if (Bun.isMainThread) counter = new MainConnectionWriteCounter();
37
+ if (Bun.isMainThread) { counter = new MainConnectionWriteCounter(); bootWrites = 0; loggedLabels.clear(); }
38
+ }
39
+ // #1870 S7: schema, migrations, seeds and pre-bind reconciles write on the main connection before the
40
+ // server binds. That is the documented boot exception (S8 flips query_only after it), so it is tallied
41
+ // apart and never enters the 1 h label window. Only `startServer` opens the phase; tests count from zero.
42
+ let booting = false;
43
+ let bootWrites = 0;
44
+ export function beginMainWriteBootPhase(): void { booting = true; }
45
+ export function endMainWriteBootPhase(): void { booting = false; }
46
+ export function mainConnectionBootWrites(): number { return bootWrites; }
47
+ // The first sighting of each label per process logs its caller once, so the remaining raw writers are named
48
+ // from hub logs without a debugger. Bounded by the counter's own label cap.
49
+ const loggedLabels = new Set<string>();
50
+ function logFirstSighting(label: string): void {
51
+ if (process.env.NODE_ENV === "test" || loggedLabels.has(label) || loggedLabels.size >= MAX_LABELS) return;
52
+ loggedLabels.add(label);
53
+ const site = (new Error().stack ?? "").split("\n").filter((line) => line.includes(" at ") && !/connection|main-connection-writes/.test(line)).slice(0, 4)
54
+ .map((line) => line.trim().replace(/^at /, "").replace(/\(?\/[^)]*\/(src\/[^):]+:\d+)[^)]*\)?/, "$1")).join(" < ");
55
+ console.log(`[relay] #1870 main-connection write: ${label} :: ${site}`);
38
56
  }
39
57
  export function recordMainConnectionWrite(label: string | undefined): void {
40
- if (Bun.isMainThread && label) counter.record(label, Date.now());
58
+ if (!Bun.isMainThread || !label) return;
59
+ if (booting) { bootWrites++; return; }
60
+ counter.record(label, Date.now());
61
+ logFirstSighting(label);
41
62
  }
42
63
  export function mainConnectionWrites(now: number = Date.now()): Record<string, number> {
43
64
  return counter.snapshot(now);
@@ -0,0 +1,61 @@
1
+ import { getDb } from "./connection.ts";
2
+
3
+ // #1870 S7 — the maintenance job-table reconcile, run-on-start rescheduling and dead-lease reclaim.
4
+ // The scheduler runs the reconcile on every tick, so these were its largest raw main-connection writes.
5
+
6
+ export interface MaintenanceJobSpec {
7
+ id: string;
8
+ title: string;
9
+ description: string;
10
+ intervalMs: number;
11
+ deadlineMs: number;
12
+ enabled: boolean;
13
+ critical: boolean;
14
+ runOnStart: boolean;
15
+ }
16
+
17
+ /** #1645: `enabled` is code-owned and re-asserted on every reconcile; operator stops live in `maintenance_job_pauses`.
18
+ * PRECONDITION: convertManualJobDisablesToPauses() (migrations.ts) ran first. Runs inside the caller's write transaction. */
19
+ export function upsertMaintenanceJobs(jobs: readonly MaintenanceJobSpec[], now: number): void {
20
+ const stmt = getDb().query(`
21
+ INSERT INTO maintenance_jobs (id, title, description, interval_ms, timeout_ms, enabled, critical, run_on_start, next_run_at, updated_at)
22
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
23
+ ON CONFLICT(id) DO UPDATE SET
24
+ title = excluded.title,
25
+ description = excluded.description,
26
+ interval_ms = excluded.interval_ms,
27
+ timeout_ms = excluded.timeout_ms,
28
+ run_on_start = excluded.run_on_start,
29
+ enabled = excluded.enabled, -- #1645: code-owned, see the note above this query
30
+ critical = excluded.critical,
31
+ next_run_at = COALESCE(maintenance_jobs.next_run_at, excluded.next_run_at),
32
+ updated_at = excluded.updated_at
33
+ `);
34
+ // The DB column stays `timeout_ms` (#1728): a rename is a migration. Code-side it is `deadlineMs`.
35
+ for (const job of jobs) {
36
+ stmt.run(job.id, job.title, job.description, job.intervalMs, job.deadlineMs, job.enabled ? 1 : 0, job.critical ? 1 : 0, job.runOnStart ? 1 : 0,
37
+ job.runOnStart ? now : now + job.intervalMs, now);
38
+ }
39
+ }
40
+
41
+ /** `runOnStart` means every process start; this is boot-only, never part of the periodic reconcile. */
42
+ export function rescheduleRunOnStartJobs(now: number): void {
43
+ getDb().query(`
44
+ UPDATE maintenance_jobs
45
+ SET next_run_at = ?, updated_at = ?
46
+ WHERE enabled = 1 AND run_on_start = 1
47
+ `).run(now, now);
48
+ }
49
+
50
+ export interface DeadLease { id: string; owner: string; pid: number }
51
+
52
+ /** #1493/#3645: a confirmed-dead owner is a crash, not a job failure — no failure streak bump, no last_error overwrite. */
53
+ export function reclaimDeadLeases(leases: readonly DeadLease[], now: number): void {
54
+ const stmt = getDb().query(`
55
+ UPDATE maintenance_jobs
56
+ SET lease_owner = NULL, lease_until = NULL, last_status = 'failed',
57
+ last_error = COALESCE(last_error, ?), run_token = NULL, last_progress_at = ?, updated_at = ?
58
+ WHERE id = ? AND lease_owner = ?
59
+ `);
60
+ for (const lease of leases) stmt.run(`reclaimed stale lease at startup: owner pid ${lease.pid} is confirmed dead`, now, now, lease.id, lease.owner);
61
+ }
@@ -1,4 +1,6 @@
1
1
  import { getGateway } from "./gateway.ts";
2
+ import type { ResetCriticalMaintenanceRun } from "./maintenance-health.ts";
3
+ import type { DeadLease, MaintenanceJobSpec } from "./maintenance-job-reconcile.ts";
2
4
  import type { MaintenanceRunClaim, MaintenanceRunOutcome } from "./maintenance-job-runs.ts";
3
5
 
4
6
  // #1870 S3 — lease and outcome writes are critical: a shed claim skips a due job and a shed outcome
@@ -13,3 +15,15 @@ export async function heartbeatMaintenanceRunAsync(args: { id: string; runToken:
13
15
 
14
16
  export const finishMaintenanceRunAsync = (args: MaintenanceRunOutcome) =>
15
17
  getGateway().write("maintenanceJobs.finish", args, { critical: true }) as unknown as Promise<boolean>;
18
+
19
+ export async function ensureMaintenanceJobsAsync(jobs: MaintenanceJobSpec[]): Promise<void> {
20
+ await getGateway().write("maintenanceJobs.ensure", { jobs }, { critical: true });
21
+ }
22
+ export async function rescheduleRunOnStartJobsAsync(): Promise<void> {
23
+ await getGateway().write("maintenanceJobs.rescheduleRunOnStart", {}, { critical: true });
24
+ }
25
+ export async function reclaimDeadLeasesAsync(leases: DeadLease[]): Promise<void> {
26
+ if (leases.length) await getGateway().write("maintenanceJobs.reclaimDeadLeases", { leases }, { critical: true });
27
+ }
28
+ export const resetExpiredCriticalRunsAsync = (now: number) =>
29
+ getGateway().write("maintenanceJobs.resetExpiredCritical", { now }, { critical: true }) as unknown as Promise<ResetCriticalMaintenanceRun[]>;
@@ -1,10 +1,19 @@
1
1
  import { claimMaintenanceRun, finishMaintenanceRun, heartbeatMaintenanceRun } from "./maintenance-job-runs.ts";
2
2
  import type { MaintenanceRunClaim, MaintenanceRunOutcome } from "./maintenance-job-runs.ts";
3
+ import { resetExpiredCriticalMaintenanceRuns } from "./maintenance-health.ts";
4
+ import { reclaimDeadLeases, rescheduleRunOnStartJobs, upsertMaintenanceJobs } from "./maintenance-job-reconcile.ts";
5
+ import type { DeadLease, MaintenanceJobSpec } from "./maintenance-job-reconcile.ts";
3
6
  import type { WriterOpSpec } from "./write-ops.ts";
4
7
 
5
8
  // #1870 S3 — each op is a compare-and-set on the run's own owner/token, so a replay changes nothing.
6
9
  export const MAINTENANCE_JOB_WRITE_OPS: Readonly<Record<string, WriterOpSpec>> = Object.freeze({
7
10
  "maintenanceJobs.claim": { offLoop: true, idempotent: true, fn: (args: MaintenanceRunClaim) => claimMaintenanceRun(args) },
8
11
  "maintenanceJobs.heartbeat": { offLoop: true, idempotent: true, fn: (args: { id: string; runToken: string }, ctx) => heartbeatMaintenanceRun(args, ctx.now) },
12
+ // Reconcile is a re-assertion of code-owned columns, so a replay changes nothing.
13
+ "maintenanceJobs.ensure": { offLoop: true, idempotent: true, fn: (args: { jobs: MaintenanceJobSpec[] }, ctx) => upsertMaintenanceJobs(args.jobs, ctx.now) },
14
+ "maintenanceJobs.rescheduleRunOnStart": { offLoop: true, idempotent: true, fn: (_args: Record<string, never>, ctx) => rescheduleRunOnStartJobs(ctx.now) },
15
+ "maintenanceJobs.reclaimDeadLeases": { offLoop: true, idempotent: true, fn: (args: { leases: DeadLease[] }, ctx) => reclaimDeadLeases(args.leases, ctx.now) },
16
+ // A compare-and-set on the observed lease, so a replay finds nothing left to reset.
17
+ "maintenanceJobs.resetExpiredCritical": { offLoop: true, idempotent: true, fn: (args: { now: number }) => resetExpiredCriticalMaintenanceRuns(args.now) },
9
18
  "maintenanceJobs.finish": { offLoop: true, idempotent: true, fn: (args: MaintenanceRunOutcome) => finishMaintenanceRun(args) },
10
19
  });
package/src/db/mappers.ts CHANGED
@@ -1,17 +1,4 @@
1
- import { randomUUID } from "node:crypto";
2
- import { isRecord, stringValue, isMechanicalMessageKind } from "@humwick/sdk";
3
- import { ORCHESTRATOR_PROTOCOL_VERSION, VERSION } from "../config.ts";
4
1
  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
2
  import { parseStringArray } from "./shared.ts";
16
3
  import type {
17
4
  AgentCard,
@@ -291,12 +278,14 @@ export function rowToTask(row: any): Task {
291
278
  ...(row.dispatch != null ? { dispatch: parseJson(row.dispatch, {}) } : {}),
292
279
  ...(row.reaped_at != null ? { reapedAt: row.reaped_at } : {}),
293
280
  ...(row.tags != null ? { tags: parseStringArray(row.tags) } : {}),
281
+ ...(row.paths != null ? { paths: parseStringArray(row.paths) } : {}),
294
282
  ...(row.priority != null ? { priority: row.priority as Task["priority"] } : {}),
295
283
  ...(row.parent_task_id != null ? { parentTaskId: row.parent_task_id } : {}),
296
284
  ...(row.backlog_state != null ? { backlogState: row.backlog_state as Task["backlogState"] } : {}),
297
285
  // #2518 — omit false on the wire so legacy payload shapes stay byte-identical for unheld Tasks.
298
286
  ...(row.validation_items != null ? { validationItems: parseJson(row.validation_items, []) } : {}),
299
287
  ...(row.completed_at != null ? { completedAt: row.completed_at } : {}),
288
+ ...(row.on_deploy ? { onDeploy: row.on_deploy } : {}),
300
289
  ...(row.terminal_stage != null ? { terminalStage: row.terminal_stage as Task["terminalStage"] } : {}),
301
290
  ...(row.stale_owner_gone === 1 ? { staleOwnerGone: true } : {}),
302
291
  ...(row.review_hold === 1 ? { reviewHold: true } : {}),
@@ -406,7 +395,6 @@ export function rowToActivityEvent(row: any): ActivityEvent {
406
395
  };
407
396
  }
408
397
 
409
-
410
398
  export const MSG_SELECT = `SELECT m.*, (
411
399
  SELECT json_group_array(agent_id) FROM message_reads WHERE message_id = m.id
412
400
  ) AS read_by_agents, (
@@ -1,19 +1,5 @@
1
- import { Database } from "bun:sqlite";
2
1
  import { randomUUID } from "node:crypto";
3
- import { isRecord, stringValue, isMechanicalMessageKind } from "@humwick/sdk";
4
- import { ORCHESTRATOR_PROTOCOL_VERSION, VERSION } from "../config.ts";
5
- import { parseJson } from "../utils";
6
- import { isLiveIsolatedWorkspace } from "../workspace-phase";
7
- import {
8
- CONTRACT_REQUIREMENTS,
9
- contractCompatibility,
10
- parseRuntimeCapabilities,
11
- parseRuntimeContracts,
12
- parseRuntimePackage,
13
- type RuntimeContracts,
14
- } from "../contracts";
15
- import { STALE_TTL_MS, DAY_MS, CLAIM_LEASE_MS, POOL_CLAIM_LEASE_MS, WORKSPACE_MERGE_LEASE_MS, MAIN_MERGE_PAUSE_TTL_MS } from "../config";
16
- import { matchAgents } from "../agent-ref";
2
+ import { WORKSPACE_MERGE_LEASE_MS, MAIN_MERGE_PAUSE_TTL_MS } from "../config";
17
3
  import { incidentEligibleSql } from "./land-incident-eligibility";
18
4
  import { getDb, writeTransaction } from "./connection.ts";
19
5
 
@@ -6,15 +6,18 @@ export const MESSAGE_READS_WRITE_OPS: Readonly<Record<string, WriterOpSpec>> = O
6
6
  // Both writes converge on retry: INSERT OR IGNORE/read-state updates and deleting an absent row
7
7
  // are no-ops, so a phantom-failure retry is safe without a caller key.
8
8
  "messageReads.markRead": {
9
+ offLoop: true,
9
10
  idempotent: true,
10
11
  fn: (args: { messageId: number; agentId: string }, ctx: WriterOpContext) => markRead(args.messageId, args.agentId, ctx.now),
11
12
  },
12
13
  "messageReads.delete": {
14
+ offLoop: true,
13
15
  idempotent: true,
14
16
  fn: (args: { id: number }) => deleteMessage(args.id, false),
15
17
  },
16
18
  // #3054 — re-derives each drifted pair from the source tables at write time, so a stale snapshot is harmless.
17
19
  "messageReads.reconcileReplyObligations": {
20
+ offLoop: true,
18
21
  idempotent: true,
19
22
  fn: (args: { pairs: Array<{ messageId: number; agentId: string }> }) => {
20
23
  for (const { messageId, agentId } of args.pairs) reconcileReplyObligation(messageId, agentId);
@@ -52,6 +52,7 @@ export interface MessageClaimOpArgs {
52
52
 
53
53
  export const MESSAGE_WRITE_OPS: Readonly<Record<string, WriterOpSpec>> = Object.freeze({
54
54
  "messages.flushCoordinatorDigests": {
55
+ offLoop: true,
55
56
  idempotent: true,
56
57
  fn: (_args: unknown, ctx: WriterOpContext) => flushCoordinatorDigestsBody(ctx.now),
57
58
  },
@@ -77,6 +78,7 @@ export const MESSAGE_WRITE_OPS: Readonly<Record<string, WriterOpSpec>> = Object.
77
78
  // A keyed reaction row is an upsert/delete, so replaying the same request converges on the
78
79
  // requested state. The lookup and mutation execute in the same writer transaction.
79
80
  "messages.reaction": {
81
+ offLoop: true,
80
82
  idempotent: true,
81
83
  fn: (args: { input: Parameters<typeof setMessageReaction>[0] }, ctx: WriterOpContext) =>
82
84
  setMessageReaction(args.input, ctx.now),
@@ -84,11 +86,13 @@ export const MESSAGE_WRITE_OPS: Readonly<Record<string, WriterOpSpec>> = Object.
84
86
  // The expiry sweep, eligibility reads, message CAS, linked-task transition, and renewal all run
85
87
  // in this single writer transaction. A separate release/read hop would race a concurrent claimer.
86
88
  "messages.claim": {
89
+ offLoop: true,
87
90
  idempotent: true,
88
91
  fn: (args: MessageClaimOpArgs, ctx: WriterOpContext) =>
89
92
  claimMessageBody(args.messageId, args.agentId, args.guard, ctx.now),
90
93
  },
91
94
  "messages.renewClaim": {
95
+ offLoop: true,
92
96
  idempotent: true,
93
97
  fn: (args: MessageClaimOpArgs, ctx: WriterOpContext) =>
94
98
  renewMessageClaimBody(args.messageId, args.agentId, args.guard, ctx.now),
@@ -21,7 +21,6 @@ import { backfillLegacyMessages } from "./legacy-message-backfill";
21
21
  import { installHistoryDeliverySettlement } from "./history-delivery-settle";
22
22
  import { installReplyObligations } from "./reply-obligations";
23
23
  import { beginStartupPhase, timeStartupPhase } from "../startup-timing";
24
- import { Database } from "bun:sqlite";
25
24
  import { AGENT_EXIT_REPORT_SCHEMA } from "./agent-exit-report";
26
25
  import { CONTEXT_RESET_SCHEMA } from "./context-reset-carry";
27
26
  import { TASK_TOKEN_TOTALS_SCHEMA } from "./token-usage";
@@ -30,20 +29,8 @@ import { migrateOperatorActionsSchema } from "./operator-actions-schema";
30
29
  import { SELF_EFFORT_PERMISSIONS_SCHEMA } from "./self-effort-permissions";
31
30
  import { AGENT_EFFORT_STATE_SCHEMA, importLegacyEffortCommands } from "./agent-effort-state";
32
31
  import { randomUUID } from "node:crypto";
33
- import { isRecord, stringValue, isMechanicalMessageKind } from "@humwick/sdk";
34
- import { ORCHESTRATOR_PROTOCOL_VERSION, VERSION } from "../config.ts";
35
- import { parseJson } from "../utils";
36
- import { isLiveIsolatedWorkspace } from "../workspace-phase";
37
- import {
38
- CONTRACT_REQUIREMENTS,
39
- contractCompatibility,
40
- parseRuntimeCapabilities,
41
- parseRuntimeContracts,
42
- parseRuntimePackage,
43
- type RuntimeContracts,
44
- } from "../contracts";
45
- import { STALE_TTL_MS, DAY_MS, CLAIM_LEASE_MS, POOL_CLAIM_LEASE_MS, WORKSPACE_MERGE_LEASE_MS } from "../config";
46
- import { matchAgents } from "../agent-ref";
32
+ import { PROJECT_PROFILE_INSTRUCTIONS_SCHEMA_SQL } from "./project-profile-instructions";
33
+ import { CLAIM_LEASE_MS } from "../config";
47
34
  import { COORDINATOR_PROFILE_NAMES } from "../agent-system-tags";
48
35
  import { getDb } from "./connection.ts";
49
36
  import { normalizeExistingAgentSpawnRequestIds } from "./spawn-request-id-migration.ts";
@@ -154,6 +141,7 @@ function backfillProjectRootRemoteUrls(): void {
154
141
  // shape, so they must run in historical order. Built-in agent seeding and the
155
142
  // read_by backfill live here too — both run unconditionally and idempotently.
156
143
  export function applyMigrations(): void {
144
+ getDb().run(PROJECT_PROFILE_INSTRUCTIONS_SCHEMA_SQL);
157
145
  getDb().run(PLAN_IMPORT_SCHEMA_SQL);
158
146
  ensureVaultTokensSchema();
159
147
  ensureVaultHealthSchema();
@@ -1281,6 +1269,7 @@ export function applyMigrations(): void {
1281
1269
  // addTaskCol guard as the rest of the #834 batch: existing rows read back as absent.
1282
1270
  addTaskCol("tags", "tags TEXT");
1283
1271
  addTaskCol("priority", "priority TEXT");
1272
+ addTaskCol("paths", "paths TEXT"); // #3736
1284
1273
  // #1535 WS1 — parent/epic tree edge. Self-referential FK via ALTER TABLE ADD COLUMN is
1285
1274
  // proven safe in this codebase already (messages.reply_to above); cycle-guarding lives
1286
1275
  // in application code (task-entity.ts), not a DB constraint.
@@ -1294,10 +1283,13 @@ export function applyMigrations(): void {
1294
1283
  addTaskCol("review_hold", "review_hold INTEGER NOT NULL DEFAULT 0 CHECK (review_hold IN (0, 1))");
1295
1284
  addTaskCol("validation_items", "validation_items TEXT");
1296
1285
  addTaskCol("terminal_stage", "terminal_stage TEXT");
1286
+ addTaskCol("on_deploy", "on_deploy TEXT");
1297
1287
  addTaskCol("completed_at", "completed_at INTEGER");
1298
1288
  addTaskCol("owner", "owner TEXT"); // #2862 — backfilled by backfillTaskOwners below
1299
1289
  migratePlanBlurbsOnStartup(); // #2082 S5 — fold legacy bound-node blurbs before stripping them
1300
1290
  getDb().run("CREATE INDEX IF NOT EXISTS idx_tasks_message_status ON tasks(message_id, status) WHERE message_id IS NOT NULL");
1291
+ // #3740: existing databases need the same covering expression index as fresh schemas.
1292
+ getDb().run("CREATE INDEX IF NOT EXISTS idx_tasks_integration_stats ON tasks(coalesce(json_extract(metadata, '$.relayIntegrationName'), source), status, claimed_by, last_seen_at, updated_at, source)");
1301
1293
  getDb().run("CREATE INDEX IF NOT EXISTS idx_tasks_kind ON tasks(kind, status, updated_at)");
1302
1294
  getDb().run("CREATE INDEX IF NOT EXISTS idx_tasks_branch ON tasks(branch) WHERE branch IS NOT NULL");
1303
1295
  getDb().run("CREATE INDEX IF NOT EXISTS idx_tasks_team ON tasks(team_id) WHERE team_id IS NOT NULL");
@@ -4,13 +4,17 @@ import { getGateway } from "./gateway.ts";
4
4
  import { coalesceLifecycleWrite } from "./lifecycle-write-window.ts";
5
5
  import { nextLifecycleSeq } from "./lifecycle-seq.ts";
6
6
  import { evictPendingPresence } from "./presence-buffer.ts";
7
- import type { getOrchestrator, listOrchestrators, OrchestratorRosterResult, upsertOrchestrator } from "./orchestrators.ts";
7
+ import type { getOrchestrator, listOrchestrators, OrchestratorRosterResult, registerOrchestratorOp, upsertOrchestrator } from "./orchestrators.ts";
8
+ import { finishAgentUpsertAsync } from "./agents-async.ts";
8
9
  import { emitWorkspaceUpsert } from "./workspaces.ts";
9
10
  import type { ManagedAgent, OrchestratorRuntimeInput, OrchestratorUpgradeState, QueuedSpawn } from "../types.ts";
10
11
  import { STALE_TTL_MS } from "../config.ts";
11
12
 
12
- export function upsertOrchestratorAsync(input: Parameters<typeof upsertOrchestrator>[0]): Promise<ReturnType<typeof upsertOrchestrator>> {
13
- return getGateway().write("orchestrators.register", input, { critical: true }) as unknown as Promise<ReturnType<typeof upsertOrchestrator>>;
13
+ export async function upsertOrchestratorAsync(input: Parameters<typeof upsertOrchestrator>[0]): Promise<ReturnType<typeof upsertOrchestrator>> {
14
+ const seq = nextLifecycleSeq(); // before the await, per the ordinal-timing rule (lifecycle-seq.ts)
15
+ const { orchestrator, agent, contextState } = await getGateway().write("orchestrators.register", { input, seq }, { critical: true }) as unknown as ReturnType<typeof registerOrchestratorOp>;
16
+ await finishAgentUpsertAsync(agent.id, { agent, contextState });
17
+ return orchestrator;
14
18
  }
15
19
 
16
20
  export function getOrchestratorAsync(id: string): Promise<ReturnType<typeof getOrchestrator>> {
@@ -3,6 +3,7 @@
3
3
  import {
4
4
  orchestratorHeartbeatInternal,
5
5
  reapStaleOrchestratorsInternal,
6
+ registerOrchestratorOp,
6
7
  reportOrchestratorRosterRow,
7
8
  setOrchestratorUpgradeState,
8
9
  upsertOrchestrator,
@@ -14,10 +15,13 @@ import type { WriterOpContext, WriterOpSpec } from "./write-ops.ts";
14
15
 
15
16
  export const ORCHESTRATORS_WRITE_OPS: Readonly<Record<string, WriterOpSpec>> = Object.freeze({
16
17
  "orchestrators.register": {
18
+ offLoop: true,
17
19
  idempotent: true,
18
- fn: (input: Parameters<typeof upsertOrchestrator>[0]) => upsertOrchestrator(input),
20
+ // The agent row's follow-ups (presence eviction, pool binding, steward election) run in the facade.
21
+ fn: (a: { input: Parameters<typeof upsertOrchestrator>[0]; seq: number }, ctx) => registerOrchestratorOp(a.input, ctx.now, a.seq),
19
22
  },
20
23
  "orchestrators.heartbeat": {
24
+ offLoop: true,
21
25
  idempotent: true,
22
26
  fn: (a: { id: string; runtime: OrchestratorRuntimeInput; seq?: number }, ctx) => {
23
27
  const orchestrator = orchestratorHeartbeatInternal(a.id, a.runtime, ctx.now);
@@ -42,10 +46,12 @@ export const ORCHESTRATORS_WRITE_OPS: Readonly<Record<string, WriterOpSpec>> = O
42
46
  fn: (a: { id: string; agents: ManagedAgent[]; queuedSpawns?: QueuedSpawn[] }, ctx) => reportOrchestratorRosterRow(a.id, a.agents, a.queuedSpawns, ctx.now),
43
47
  },
44
48
  "orchestrators.upgrade.set": {
49
+ offLoop: true,
45
50
  idempotent: true,
46
51
  fn: (a: { id: string; state: OrchestratorUpgradeState | null }) => setOrchestratorUpgradeState(a.id, a.state),
47
52
  },
48
53
  "orchestrators.reap": {
54
+ offLoop: true,
49
55
  idempotent: true,
50
56
  fn: (a: { cutoff: number; seq: number }, ctx) => reapStaleOrchestratorsInternal(ctx.now - STALE_TTL_MS, a.seq),
51
57
  },