@humwick/server 0.130.2 → 0.130.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (570) hide show
  1. package/bundled/prompts/manifest.json +1 -0
  2. package/bundled/prompts/task.hydration.md +1 -1
  3. package/bundled/roles/builder.md +1 -1
  4. package/docs/guides/sub-coordinators.md +12 -5
  5. package/docs/guides/tools-coordination.md +1 -0
  6. package/docs/guides/workflows.md +16 -0
  7. package/docs/openapi.json +73 -4
  8. package/package.json +8 -7
  9. package/public/assets/{MessageBubble-BWLqMGKG.js → MessageBubble-Bq9kr21N.js} +2 -2
  10. package/public/assets/{PlanGraphPanel-3TlIQpjk.js → PlanGraphPanel-Dqiroy06.js} +2 -2
  11. package/public/assets/{TraceView-Wimm0Czv.js → TraceView-DYU962-H.js} +2 -2
  12. package/public/assets/{VaultDecisionCard-Bau_vNax.js → VaultDecisionCard-DhPfzioE.js} +2 -2
  13. package/public/assets/{activity-CrSurn-r.js → activity-DzCcQTee.js} +2 -2
  14. package/public/assets/{agent-lineage-CknAJKcb.js → agent-lineage-SgDt9i3T.js} +2 -2
  15. package/public/assets/agent-liveness-Bq-s73jW.js +2 -0
  16. package/public/assets/{agent-name-DKbiIhBo.js → agent-name-CcY3Aijy.js} +2 -2
  17. package/public/assets/{agent-profiles-DwF6EQ4R.js → agent-profiles-cxtRBn6O.js} +2 -2
  18. package/public/assets/{agent-project-badge-Diz4Sjjb.js → agent-project-badge-DGYbIhaI.js} +2 -2
  19. package/public/assets/{agent-tools-CRhpRQza.js → agent-tools-DHbftV0N.js} +2 -2
  20. package/public/assets/{agent-type-icon-LrUEU3lB.js → agent-type-icon-CY113sp-.js} +2 -2
  21. package/public/assets/{agents-B-v66C-4.js → agents-BPzY7IlQ.js} +2 -2
  22. package/public/assets/{analytics-BqCb-LKi.js → analytics-DENFPv_a.js} +2 -2
  23. package/public/assets/{artifact-viewer-kf4pGuAR.js → artifact-viewer-KRvSc7sl.js} +2 -2
  24. package/public/assets/{artifacts-DJevXcvg.js → artifacts-C9gSmL3s.js} +2 -2
  25. package/public/assets/{artifacts-D32zyw4Y.js → artifacts-DT-HkI5O.js} +2 -2
  26. package/public/assets/{automation-BpRajx_5.js → automation-BsmjQhKD.js} +2 -2
  27. package/public/assets/{backlog-D_UjTdzX.js → backlog-8NYH_62t.js} +2 -2
  28. package/public/assets/{busy-health-N3VEh4ao.js → busy-health-D3hwvYb4.js} +2 -2
  29. package/public/assets/{channels-9jopungq.js → channels-DCJ5WL7O.js} +2 -2
  30. package/public/assets/{chat-CSXrTB3C.js → chat-DAIl357U.js} +3 -3
  31. package/public/assets/{code-preview-p1NnmqK0.js → code-preview-CzrZfr9U.js} +2 -2
  32. package/public/assets/{connectors-CZdqlDNL.js → connectors-xTxtlyou.js} +2 -2
  33. package/public/assets/{cosmos-BgdsE4dG.js → cosmos-BpebFLK2.js} +2 -2
  34. package/public/assets/{crate-agent-30aXTKva.js → crate-agent-BHvI5wXC.js} +2 -2
  35. package/public/assets/{dependency-graph-B_jvw2GE.js → dependency-graph-DGT0o3wJ.js} +2 -2
  36. package/public/assets/{deploys-DoN6bAb5.js → deploys-hqWOHSHE.js} +2 -2
  37. package/public/assets/{display-BrKWFGJz.js → display-CDeH4I8T.js} +2 -2
  38. package/public/assets/{files-D6XEu8Ng.js → files-BTeGVmTg.js} +2 -2
  39. package/public/assets/{fleet-C7jQWbw2.js → fleet-DgXtNgCa.js} +2 -2
  40. package/public/assets/{fleet-metrics-BiDs3MLx.js → fleet-metrics-BHEt5yc_.js} +2 -2
  41. package/public/assets/{formatted-body-CQBwc1fb.js → formatted-body-MLtodyPp.js} +3 -3
  42. package/public/assets/{formatted-body-impl-DyG0WsQS.js → formatted-body-impl-CAe664Z8.js} +2 -2
  43. package/public/assets/{host-providers-dialog-DKaW31dN.js → host-providers-dialog-mapF4Fj9.js} +2 -2
  44. package/public/assets/{index-CRHep3M9.js → index-CqqgOI5R.js} +5 -5
  45. package/public/assets/{insights-CzrtkpOE.js → insights-DNyf7IzF.js} +2 -2
  46. package/public/assets/{integrations-aVsUmigt.js → integrations-DlYOHq7T.js} +2 -2
  47. package/public/assets/{issue-ref-chip-BnSlM_95.js → issue-ref-chip-CeWBNWn7.js} +2 -2
  48. package/public/assets/{log-viewer-qe7Ee47Z.js → log-viewer-Cib8nqdE.js} +2 -2
  49. package/public/assets/{maintenance-NfjqFZAk.js → maintenance-DSn1cPm7.js} +2 -2
  50. package/public/assets/{managed-agents-BiH0HgaE.js → managed-agents-C3CkM81m.js} +2 -2
  51. package/public/assets/{markdown-preview-BixaT3Xr.js → markdown-preview-W4VQQPAv.js} +3 -3
  52. package/public/assets/{markdown-preview-impl-DNGlWIS3.js → markdown-preview-impl-CMsTdhjq.js} +2 -2
  53. package/public/assets/{memory-DNsCl6UX.js → memory-BV17qtO4.js} +2 -2
  54. package/public/assets/{messages-DopU1L1X.js → messages-Bm_1AqSA.js} +2 -2
  55. package/public/assets/{orchestrators-Cq7ckLQT.js → orchestrators-Dg-vfP09.js} +2 -2
  56. package/public/assets/{overview-C1DCTzzV.js → overview-DcYCrvlr.js} +2 -2
  57. package/public/assets/{pairs-DE_m6rrE.js → pairs-8o9nuG3V.js} +2 -2
  58. package/public/assets/{profile-icon-B3rfh77F.js → profile-icon-BsRyBMea.js} +2 -2
  59. package/public/assets/{project-profile-instructions-DQWZE0kA.js → project-profile-instructions-DETKFn_E.js} +2 -2
  60. package/public/assets/{projects-Br2Bd2h1.js → projects-M_m6AHws.js} +2 -2
  61. package/public/assets/{prompt-settings-BKvFsvGD.js → prompt-settings-C_mNCgeL.js} +2 -2
  62. package/public/assets/provider-display-BEeLXC_D.js +2 -0
  63. package/public/assets/{registry-Cw38_6gW.js → registry-DDHHHE5N.js} +2 -2
  64. package/public/assets/run-card-Dt4I4jaP.js +2 -0
  65. package/public/assets/{secrets-DxI-vzSq.js → secrets-BkVwxI5O.js} +2 -2
  66. package/public/assets/{service-access-settings-D9s_VJbd.js → service-access-settings-Crq-6a-B.js} +2 -2
  67. package/public/assets/{settings-CKZTVGIx.js → settings-CYcavMFu.js} +2 -2
  68. package/public/assets/shipping-H6gzEeZB.js +2 -0
  69. package/public/assets/{stats-DyUBKyWI.js → stats-BJVeWkpV.js} +2 -2
  70. package/public/assets/{status-dot-DYqlEUhJ.js → status-dot-BcHyw1Vw.js} +2 -2
  71. package/public/assets/store-DKPDIZBe.js +9 -0
  72. package/public/assets/{tasks-Bh7Chr4-.js → tasks-qJjlQPpx.js} +2 -2
  73. package/public/assets/{teams-bowI7qVY.js → teams-Cd5Sk1uX.js} +2 -2
  74. package/public/assets/{terminal-viewer-BgVLHdsU.js → terminal-viewer-CpY5_m7W.js} +3 -3
  75. package/public/assets/{terminal-viewer-impl-Dz22UPy6.js → terminal-viewer-impl-DI_2llFR.js} +2 -2
  76. package/public/assets/{use-agent-terminal-DSQQ-iY7.js → use-agent-terminal-0yJA_buM.js} +2 -2
  77. package/public/assets/{use-decisions-samMQhQf.js → use-decisions-uJXHf34W.js} +2 -2
  78. package/public/assets/{use-project-list-59LheVP7.js → use-project-list-CpWl6eHQ.js} +2 -2
  79. package/public/assets/{use-selectors-BZa25xEB.js → use-selectors-BdxBW8mL.js} +2 -2
  80. package/public/assets/{user-avatar-DHxlvUvV.js → user-avatar-3IndfpOc.js} +2 -2
  81. package/public/assets/{versioned-prompt-editor-BmED5DnJ.js → versioned-prompt-editor-CIVg7XB1.js} +2 -2
  82. package/public/assets/{work-queue-CSvpuNLz.js → work-queue-B9rnLwHZ.js} +2 -2
  83. package/public/assets/{workflow-marker-DAZcj5qt.js → workflow-marker-X8wxyhzC.js} +2 -2
  84. package/public/assets/{workflows-DdmxRSyg.js → workflows-o5YyMFW7.js} +2 -2
  85. package/public/assets/{workspaces-BxeKfVuF.js → workspaces-CzEGoAng.js} +2 -2
  86. package/public/cosmos.html +4 -4
  87. package/public/index.html +28 -28
  88. package/runner/plugins/claude/.claude-plugin/plugin.json +1 -1
  89. package/runner/src/adapters/claude-headless.ts +1 -1
  90. package/runner/src/adapters/claude-launch-exit.ts +26 -0
  91. package/runner/src/adapters/claude-session-probe.ts +1 -1
  92. package/runner/src/adapters/claude-tmux.ts +38 -10
  93. package/runner/src/adapters/claude.ts +28 -75
  94. package/runner/src/adapters/provider-quota-harvest.ts +20 -71
  95. package/runner/src/agent-runner/lifecycle-milestones.ts +2 -2
  96. package/runner/src/agent-runner/liveness-exchange.ts +104 -0
  97. package/runner/src/claude-system-prompt.ts +49 -0
  98. package/runner/src/launch-assembly.ts +14 -10
  99. package/runner/src/profile-home.ts +1 -1
  100. package/runner/src/project-instructions.ts +2 -2
  101. package/runner/src/provider-command.ts +18 -0
  102. package/runner/src/provider-setup.ts +2 -1
  103. package/runner/src/runner-core.ts +14 -15
  104. package/runner/src/runner-helpers.ts +0 -8
  105. package/runner/src/session-scratch.ts +6 -2
  106. package/runner/test-support/provisioning-context.ts +4 -2
  107. package/runner/test-support/runner-harness.ts +2 -1
  108. package/scripts/steward-policy-cleanup.ts +2 -2
  109. package/src/agent-authz.ts +2 -2
  110. package/src/agent-drive-arbiter.ts +3 -8
  111. package/src/agent-exit-cleanup-async.ts +2 -2
  112. package/src/agent-exit-cleanup.ts +23 -19
  113. package/src/agent-lifecycle-events.ts +112 -85
  114. package/src/agent-ref.ts +3 -2
  115. package/src/agent-status-events.ts +43 -14
  116. package/src/auth/password.ts +4 -3
  117. package/src/auth/session-refresh.ts +1 -1
  118. package/src/auth/session.test-fixture.ts +2 -2
  119. package/src/auth/session.ts +7 -7
  120. package/src/automations/crud.ts +8 -81
  121. package/src/automations/dispatch.ts +33 -58
  122. package/src/automations/index.ts +1 -1
  123. package/src/automations/persistence.ts +65 -0
  124. package/src/automations/precheck.ts +18 -16
  125. package/src/automations/read-async.ts +11 -0
  126. package/src/automations/reconcile.ts +30 -39
  127. package/src/automations/storage.ts +33 -10
  128. package/src/automations/write-async.ts +33 -0
  129. package/src/branch-land-notice.ts +3 -3
  130. package/src/branch-landed.ts +25 -23
  131. package/src/bus.ts +21 -19
  132. package/src/cd-deploy-notify.ts +2 -2
  133. package/src/ci/notifications.ts +4 -4
  134. package/src/ci/root-missing.ts +3 -3
  135. package/src/ci/transition-notify.ts +5 -5
  136. package/src/claude-app/oauth-http.ts +3 -3
  137. package/src/cli/workflow.ts +12 -3
  138. package/src/commands-db.ts +4 -4
  139. package/src/commands-write.ts +8 -0
  140. package/src/compaction-watch.ts +10 -8
  141. package/src/config-store-write.ts +2 -2
  142. package/src/config.ts +4 -11
  143. package/src/connectors.ts +10 -8
  144. package/src/context-advisory.ts +22 -21
  145. package/src/db/agent-action-write.ts +36 -0
  146. package/src/db/agent-control-async.ts +9 -0
  147. package/src/db/agent-control-write.ts +49 -0
  148. package/src/db/agent-effort-observe.ts +22 -0
  149. package/src/db/agent-meta.ts +14 -3
  150. package/src/db/agents-async.ts +8 -5
  151. package/src/db/agents.ts +15 -12
  152. package/src/db/auth-identities.ts +2 -2
  153. package/src/db/automation-read-ops.ts +11 -0
  154. package/src/db/automation-write-ops.ts +46 -0
  155. package/src/db/blackboard.ts +2 -2
  156. package/src/db/browser-session-write.ts +32 -0
  157. package/src/db/bus-outbox-write-ops.ts +3 -1
  158. package/src/db/checkout-refresh-schema.ts +47 -0
  159. package/src/db/checkout-refresh.ts +5 -5
  160. package/src/db/command-agent-profile-backfill.ts +39 -0
  161. package/src/db/command-agent-profiles.ts +5 -1
  162. package/src/db/command-retention.ts +126 -0
  163. package/src/db/commands-planner-stats.test-fixture.ts +8 -0
  164. package/src/db/commands-read-ops.ts +16 -0
  165. package/src/db/connection.ts +1 -182
  166. package/src/db/context-carry-write.ts +74 -0
  167. package/src/db/continuations.ts +4 -4
  168. package/src/db/db-error-registry.ts +12 -0
  169. package/src/db/gateway.ts +26 -133
  170. package/src/db/history-retention-ops.ts +13 -0
  171. package/src/db/inbox-async.ts +2 -2
  172. package/src/db/inbox-read-ops.ts +1 -1
  173. package/src/db/inbox.ts +9 -3
  174. package/src/db/issue-associations.ts +2 -2
  175. package/src/db/issue-refs-async.ts +4 -1
  176. package/src/db/issue-refs-read-ops.ts +6 -0
  177. package/src/db/issue-refs.ts +8 -8
  178. package/src/db/kernel-event-log-schema.ts +2 -0
  179. package/src/db/land-boundaries.ts +51 -5
  180. package/src/db/land-gate-attestations.ts +2 -2
  181. package/src/db/landing-receipts.ts +2 -2
  182. package/src/db/lifecycle-context-write.ts +5 -0
  183. package/src/db/lifecycle-tail-async.ts +76 -0
  184. package/src/db/lifecycle-tail-write-ops.ts +62 -0
  185. package/src/db/lifecycle-write-window.ts +2 -1
  186. package/src/db/lineage-pair-dedupe-sql.ts +3 -3
  187. package/src/db/main-connection-mode.ts +11 -0
  188. package/src/db/main-connection-writes.ts +20 -2
  189. package/src/db/maintenance-job-runs-async.ts +2 -2
  190. package/src/db/maintenance-job-write-ops.ts +1 -1
  191. package/src/db/maintenance-pauses.ts +2 -2
  192. package/src/db/message-send.ts +4 -0
  193. package/src/db/messages.ts +31 -25
  194. package/src/db/migrations.ts +18 -1
  195. package/src/db/notification-write-ops.ts +19 -0
  196. package/src/db/operator-answer-write.ts +19 -0
  197. package/src/db/orchestrators.ts +10 -4
  198. package/src/db/pairs-async.ts +22 -1
  199. package/src/db/pairs-write-ops.ts +8 -3
  200. package/src/db/pairs.ts +41 -48
  201. package/src/db/plan-dispatch-schema.ts +5 -1
  202. package/src/db/plan-ready-notices.ts +43 -0
  203. package/src/db/plan-tools-include-migration.ts +36 -0
  204. package/src/db/plans-write-ops.ts +2 -0
  205. package/src/db/presence-buffer.ts +5 -0
  206. package/src/db/presence-loop.ts +18 -23
  207. package/src/db/presence-write.ts +0 -6
  208. package/src/db/project-entries-async.ts +14 -0
  209. package/src/db/project-entries-read-ops.ts +5 -1
  210. package/src/db/project-entries-write-ops.ts +8 -0
  211. package/src/db/project-entries.ts +15 -12
  212. package/src/db/project-journal.ts +2 -2
  213. package/src/db/project-trust-approvals.ts +2 -2
  214. package/src/db/prompt-templates.ts +4 -4
  215. package/src/db/read-op-marshaling-fixtures.ts +18 -0
  216. package/src/db/read-ops.ts +4 -0
  217. package/src/db/retention.ts +13 -2
  218. package/src/db/review-publish-write.ts +8 -0
  219. package/src/db/runtime-admin-async.ts +19 -0
  220. package/src/db/runtime-admin-write-ops.ts +36 -0
  221. package/src/db/runtime-tail-async.ts +288 -0
  222. package/src/db/runtime-tail-write-ops.ts +97 -0
  223. package/src/db/schema.ts +4 -1
  224. package/src/db/spawn-budget.ts +5 -0
  225. package/src/db/spawn-delegation.ts +1 -11
  226. package/src/db/sql-audit-indexes.ts +3 -0
  227. package/src/db/stats-async.ts +5 -3
  228. package/src/db/task-entity.ts +5 -4
  229. package/src/db/team-claim-policy.ts +60 -0
  230. package/src/db/teams-async.ts +4 -0
  231. package/src/db/teams-write-ops.ts +4 -0
  232. package/src/db/terminal-events.ts +15 -0
  233. package/src/db/timer-cutover-bodies.ts +292 -0
  234. package/src/db/timer-cutover-core.ts +37 -278
  235. package/src/db/timer-cutover-state.ts +40 -0
  236. package/src/db/timer-cutover-write-ops.ts +1 -40
  237. package/src/db/token-db-write-ops.ts +2 -0
  238. package/src/db/token-standing-async.ts +12 -0
  239. package/src/db/token-standing.ts +6 -9
  240. package/src/db/token-usage-retention.ts +33 -0
  241. package/src/db/token-usage-write-ops.ts +1 -1
  242. package/src/db/token-usage.ts +4 -1
  243. package/src/db/user-invites-async.ts +13 -0
  244. package/src/db/user-invites.ts +3 -3
  245. package/src/db/users.ts +14 -6
  246. package/src/db/workspaces.ts +8 -5
  247. package/src/db/write-ops.ts +31 -12
  248. package/src/db/writer-fault-seam.ts +2 -9
  249. package/src/db/writer-fault-test-ops.ts +18 -0
  250. package/src/db/writer-gateway.ts +42 -46
  251. package/src/dev.ts +8 -3
  252. package/src/event-loop-lag.ts +4 -2
  253. package/src/event-loop-yield.ts +2 -2
  254. package/src/events.ts +10 -8
  255. package/src/host-provider-store.ts +5 -4
  256. package/src/hub-metrics-report.ts +5 -1
  257. package/src/hub-metrics.ts +38 -18
  258. package/src/injection-sweep-pacing.ts +6 -8
  259. package/src/land-gate-notifications.ts +11 -10
  260. package/src/land-gate-queue-progress.ts +3 -3
  261. package/src/land-settle-dispatch.ts +52 -92
  262. package/src/lifecycle-context-pressure.ts +11 -10
  263. package/src/lifecycle-manager.ts +104 -98
  264. package/src/maintenance/breaker.ts +18 -16
  265. package/src/maintenance/checkout-refresh.ts +18 -1
  266. package/src/maintenance/critical-job-watchdog.ts +3 -3
  267. package/src/maintenance/events.ts +3 -3
  268. package/src/maintenance/history-retention-job.ts +30 -0
  269. package/src/maintenance/jobs.ts +29 -67
  270. package/src/maintenance/long-running-work.ts +3 -1
  271. package/src/maintenance/offline-agent-prune-job.ts +28 -0
  272. package/src/maintenance/orphaned-sessions.ts +5 -4
  273. package/src/maintenance/pauses.ts +4 -10
  274. package/src/maintenance/plan-ready-notice-job.ts +71 -0
  275. package/src/maintenance/scheduler.ts +18 -13
  276. package/src/maintenance/stalled-delivery-pager.ts +4 -4
  277. package/src/maintenance/teams.ts +19 -30
  278. package/src/maintenance/workspaces.ts +17 -27
  279. package/src/managed-policy-escalation.ts +7 -7
  280. package/src/managed-policy.ts +3 -3
  281. package/src/managed-provider-gate.ts +5 -4
  282. package/src/managed-spawn-reservation.ts +5 -4
  283. package/src/mcp/index.ts +8 -8
  284. package/src/mcp/output-descriptions.ts +1 -1
  285. package/src/mcp/presentation.ts +2 -1
  286. package/src/mcp/runtime-output.ts +6 -1
  287. package/src/mcp/self-care-visibility.ts +2 -2
  288. package/src/mcp/spawn-targets-matrix.ts +10 -1
  289. package/src/mcp/tool-groups.ts +3 -2
  290. package/src/mcp/tool-roles.ts +81 -0
  291. package/src/mcp/tools-workspace.ts +1 -1
  292. package/src/mcp-blackboard-tools.ts +6 -5
  293. package/src/mcp-decision-tools.ts +7 -7
  294. package/src/mcp-egress-tools.ts +10 -9
  295. package/src/mcp-issue-activity-tools.ts +2 -2
  296. package/src/mcp-issue-association-tools.ts +4 -4
  297. package/src/mcp-issue-ref-tools.ts +3 -3
  298. package/src/mcp-merge-tools.ts +5 -5
  299. package/src/mcp-notification-subscription-tools.ts +14 -15
  300. package/src/mcp-output-levels.test-fixture.ts +1 -1
  301. package/src/mcp-profile-trust-tools.ts +1 -1
  302. package/src/mcp-project-journal-tools.ts +4 -12
  303. package/src/mcp-project-tools.ts +17 -18
  304. package/src/mcp-routing-policy-tools.ts +3 -2
  305. package/src/mcp-team-tools.ts +6 -6
  306. package/src/mcp-workflow-tools.ts +5 -3
  307. package/src/memory-area-injection.ts +13 -12
  308. package/src/memory-boundary-capture.ts +6 -6
  309. package/src/memory-injection.ts +45 -43
  310. package/src/merge-pause-maintenance.ts +5 -4
  311. package/src/notification-router.ts +44 -17
  312. package/src/notify.ts +5 -5
  313. package/src/observed-reset-refresh.ts +4 -4
  314. package/src/operator-alarm.ts +2 -5
  315. package/src/project-ingest.ts +6 -6
  316. package/src/project-instructions.ts +8 -1
  317. package/src/project-trust.ts +12 -11
  318. package/src/prompt-resolver.ts +3 -3
  319. package/src/provider-quota-async.ts +5 -7
  320. package/src/provider-timeline-event.ts +4 -7
  321. package/src/quota-band-delivery.ts +2 -2
  322. package/src/recipe-runner.ts +4 -3
  323. package/src/retention-config-store.ts +4 -3
  324. package/src/reviewer-pipeline.ts +11 -10
  325. package/src/routes/_route.ts +4 -4
  326. package/src/routes/_shared.ts +13 -12
  327. package/src/routes/agent-effort.ts +5 -5
  328. package/src/routes/agent-heartbeat-quota.ts +23 -0
  329. package/src/routes/agent-keep.ts +1 -1
  330. package/src/routes/agent-reporting.ts +1 -1
  331. package/src/routes/agent-self-effort.ts +5 -5
  332. package/src/routes/agent-sessions.ts +24 -24
  333. package/src/routes/agents-spawn.ts +1 -1
  334. package/src/routes/agents.ts +17 -119
  335. package/src/routes/auth.ts +14 -15
  336. package/src/routes/authority-audit.ts +2 -2
  337. package/src/routes/automations.ts +6 -5
  338. package/src/routes/channel-binding-delete.ts +1 -1
  339. package/src/routes/channel-output.ts +3 -3
  340. package/src/routes/claude-api-budget.ts +5 -5
  341. package/src/routes/commands-listing.ts +1 -1
  342. package/src/routes/commands.ts +29 -27
  343. package/src/routes/context-resets.ts +2 -2
  344. package/src/routes/coordinator-teams.ts +1 -1
  345. package/src/routes/decisions.ts +2 -2
  346. package/src/routes/host-secret-files.ts +1 -1
  347. package/src/routes/hosts.ts +11 -11
  348. package/src/routes/inbox.ts +3 -2
  349. package/src/routes/index.ts +2 -1
  350. package/src/routes/integrations.ts +9 -7
  351. package/src/routes/issue-activity.ts +2 -2
  352. package/src/routes/issue-associations.ts +4 -4
  353. package/src/routes/issue-refs.ts +3 -3
  354. package/src/routes/kernel-timers-operator.ts +1 -1
  355. package/src/routes/memory.ts +1 -1
  356. package/src/routes/merges.ts +11 -11
  357. package/src/routes/messages.ts +23 -17
  358. package/src/routes/orchestrator-bootstrap.ts +1 -1
  359. package/src/routes/orchestrator-host-registration.ts +2 -0
  360. package/src/routes/orchestrator-host-utilization.ts +2 -2
  361. package/src/routes/orchestrator-proxy.ts +2 -2
  362. package/src/routes/orchestrator.ts +27 -18
  363. package/src/routes/pairs.ts +8 -8
  364. package/src/routes/project-profile-instructions.ts +1 -1
  365. package/src/routes/project-trust.ts +1 -1
  366. package/src/routes/projects.ts +16 -16
  367. package/src/routes/prompt-templates.ts +12 -12
  368. package/src/routes/provider-quotas.ts +10 -10
  369. package/src/routes/run-command.ts +1 -1
  370. package/src/routes/secrets-backup.ts +2 -2
  371. package/src/routes/secrets-import.ts +1 -1
  372. package/src/routes/secrets-vault.ts +7 -7
  373. package/src/routes/settings.ts +1 -1
  374. package/src/routes/shipping.ts +9 -1
  375. package/src/routes/spawn-policy.ts +8 -7
  376. package/src/routes/sse.ts +2 -2
  377. package/src/routes/team-templates.ts +7 -7
  378. package/src/routes/teams.ts +10 -10
  379. package/src/routes/tokens.ts +18 -17
  380. package/src/routes/users.ts +24 -24
  381. package/src/routes/viewer-read.ts +3 -3
  382. package/src/routes/web-tool-calls.ts +4 -4
  383. package/src/routes/workflows.ts +2 -2
  384. package/src/routes/workspaces.ts +17 -16
  385. package/src/routing-policy-push.ts +6 -6
  386. package/src/runtime-grant.ts +17 -11
  387. package/src/runtime-tokens.ts +20 -28
  388. package/src/secrets-broker/config.ts +1 -1
  389. package/src/secrets-broker/service.ts +2 -2
  390. package/src/security.ts +3 -1
  391. package/src/self-resume-carry.ts +21 -110
  392. package/src/self-resume-supersede.ts +4 -3
  393. package/src/self-resume.ts +54 -66
  394. package/src/server-background.ts +1 -1
  395. package/src/server.ts +21 -22
  396. package/src/services/agent-action.ts +12 -31
  397. package/src/services/agent-effort.ts +7 -32
  398. package/src/services/agent-hang-watchdog-job.ts +2 -2
  399. package/src/services/agent-heartbeat.ts +166 -0
  400. package/src/services/agent-reporting.ts +1 -1
  401. package/src/services/browser-inventory.ts +3 -3
  402. package/src/services/browser-session-state.ts +3 -2
  403. package/src/services/browser-session.ts +21 -27
  404. package/src/services/bus-command-update.ts +9 -9
  405. package/src/services/checkout-refresh-state.ts +1 -0
  406. package/src/services/checkout-refresh.test-fixture.ts +3 -2
  407. package/src/services/command-host.ts +7 -2
  408. package/src/services/coordinator-digest.ts +3 -2
  409. package/src/services/dispatch-command.ts +12 -13
  410. package/src/services/effective-launch-driver.ts +14 -9
  411. package/src/services/host-changed-paths.test-fixture.ts +5 -6
  412. package/src/services/host-project-readiness.ts +2 -2
  413. package/src/services/host-providers.ts +1 -1
  414. package/src/services/host-utilization.ts +9 -9
  415. package/src/services/idle-agent-digest.ts +17 -12
  416. package/src/services/idle-epics.ts +4 -4
  417. package/src/services/interrupt-agent.ts +12 -9
  418. package/src/services/issue-activity.ts +3 -3
  419. package/src/services/issue-associations.ts +15 -18
  420. package/src/services/issue-lifecycle-comments.ts +3 -2
  421. package/src/services/issue-lifecycle.ts +14 -13
  422. package/src/services/issue-refs.ts +4 -5
  423. package/src/services/kernel-timers-cutover-chaos-worker.ts +1 -1
  424. package/src/services/kernel-timers-ingress-chaos-worker.ts +6 -1
  425. package/src/services/kernel-timers-ingress.ts +3 -3
  426. package/src/services/kernel-timers-message-authority-chaos-worker.ts +3 -10
  427. package/src/services/kernel-timers-operator-chaos-worker.ts +3 -2
  428. package/src/services/kernel-timers-outbox.ts +1 -1
  429. package/src/services/kernel-timers-stage10-operator.ts +1 -1
  430. package/src/services/kernel-timers.ts +4 -3
  431. package/src/services/land-gate-placement.ts +11 -2
  432. package/src/services/land-issue-relations.ts +5 -4
  433. package/src/services/land-restart-retry.ts +7 -6
  434. package/src/services/landed-ancestry.ts +6 -5
  435. package/src/services/legacy-landing-host.ts +4 -1
  436. package/src/services/legacy-landing-recovery.ts +8 -6
  437. package/src/services/managed-running.ts +17 -17
  438. package/src/services/memory-areas.ts +3 -3
  439. package/src/services/merge-pause.ts +9 -15
  440. package/src/services/operator-decisions.ts +37 -41
  441. package/src/services/orchestrator-registration-recovery.ts +6 -2
  442. package/src/services/orchestrator-upgrade.ts +14 -12
  443. package/src/services/permission-decision.ts +4 -3
  444. package/src/services/plan-ci-notice.ts +1 -1
  445. package/src/services/plan-conformance.ts +4 -4
  446. package/src/services/plan-dispatch-policy.ts +10 -3
  447. package/src/services/plan-dispatch.ts +35 -14
  448. package/src/services/plan-graph.ts +39 -261
  449. package/src/services/plan-import.ts +1 -1
  450. package/src/services/plan-live-bindings.ts +8 -37
  451. package/src/services/plan-task-create.ts +1 -1
  452. package/src/services/plan-terminal-refs.ts +2 -12
  453. package/src/services/plan-wake-digest.ts +8 -3
  454. package/src/services/profile-trust.ts +2 -2
  455. package/src/services/project.ts +11 -10
  456. package/src/services/prompt-templates.ts +6 -5
  457. package/src/services/provider-hold-nudge.ts +9 -9
  458. package/src/services/provider-hold-record.ts +13 -12
  459. package/src/services/provider-state-audit.ts +19 -18
  460. package/src/services/provisioning-harness/driver.ts +1 -1
  461. package/src/services/provisioning-harness/fixture-matrix.ts +3 -3
  462. package/src/services/provisioning-harness/lifecycle.ts +24 -22
  463. package/src/services/provisioning-harness/prepare.ts +16 -17
  464. package/src/services/rate-limit-resume.ts +10 -9
  465. package/src/services/red-main-auto-revert.ts +7 -5
  466. package/src/services/red-main-controller-deps.ts +20 -3
  467. package/src/services/red-main-incident.ts +3 -1
  468. package/src/services/red-main-quarantine.ts +2 -3
  469. package/src/services/register-agent.ts +22 -22
  470. package/src/services/restart-project-role.ts +1 -1
  471. package/src/services/restart-spawn.ts +17 -16
  472. package/src/services/review-publish.ts +7 -24
  473. package/src/services/review-ref-reconcile.ts +29 -38
  474. package/src/services/runtime-dependency-alert.ts +27 -0
  475. package/src/services/scheduler-command.ts +11 -9
  476. package/src/services/scheduler.ts +5 -5
  477. package/src/services/self-effort-grant.ts +5 -25
  478. package/src/services/send-message.ts +16 -33
  479. package/src/services/shutdown-agent.ts +17 -14
  480. package/src/services/spawn-agent.ts +12 -41
  481. package/src/services/spawn-chain-inspector.ts +1 -1
  482. package/src/services/spawn-cwd-placement.ts +21 -5
  483. package/src/services/spawn-issue-activity.ts +8 -6
  484. package/src/services/spawn-launch.ts +17 -1
  485. package/src/services/spawn-loss-escalation.ts +1 -1
  486. package/src/services/spawn-registration-watchdog.ts +2 -2
  487. package/src/services/spawn-repo-probe.ts +7 -5
  488. package/src/services/spawn-tree-cleanup.ts +7 -8
  489. package/src/services/steward-reaper.ts +4 -4
  490. package/src/services/task-context-hydration.ts +25 -1
  491. package/src/services/task-delivery.ts +20 -34
  492. package/src/services/task-entity.ts +40 -203
  493. package/src/services/task-issue-link.ts +13 -20
  494. package/src/services/task-issue-weave.ts +12 -11
  495. package/src/services/task-lifecycle.ts +27 -31
  496. package/src/services/task-repo-cwd.ts +37 -0
  497. package/src/services/task-review-hold.ts +4 -17
  498. package/src/services/task-semantic-events.ts +13 -11
  499. package/src/services/task-state-reconcile.ts +3 -4
  500. package/src/services/task-validation.ts +2 -2
  501. package/src/services/team-templates.ts +5 -5
  502. package/src/services/team.ts +31 -98
  503. package/src/services/terminal-provider-exit.ts +6 -5
  504. package/src/services/timer-message-fire-chaos-worker.ts +7 -3
  505. package/src/services/timer-message-fire-outbox.ts +1 -1
  506. package/src/services/transient-agent-reaper.ts +22 -19
  507. package/src/services/turn-reconcile-audit.ts +9 -9
  508. package/src/services/user-onboarding.ts +8 -6
  509. package/src/services/user-profile.ts +6 -9
  510. package/src/services/work-finality.ts +16 -4
  511. package/src/services/workflow-auto-start.ts +2 -1
  512. package/src/services/workflow-caller.ts +61 -4
  513. package/src/services/workflow-child-proof-worker.ts +1 -1
  514. package/src/services/workflow-control-proof-worker.ts +1 -1
  515. package/src/services/workflow-coordinator.ts +8 -2
  516. package/src/services/workflow-decisions.ts +5 -5
  517. package/src/services/workflow-deferred-cleanup.ts +1 -1
  518. package/src/services/workflow-effect-driver.ts +42 -19
  519. package/src/services/workflow-effects.ts +41 -27
  520. package/src/services/workflow-pins.ts +7 -4
  521. package/src/services/workflow-reconcile.ts +1 -0
  522. package/src/services/workflow-resume-pins.ts +3 -3
  523. package/src/services/workflow-routing-defaults.ts +18 -0
  524. package/src/services/workflow-run-view.ts +4 -3
  525. package/src/services/workflow-run.test-fixture.ts +1 -1
  526. package/src/services/workflow-runs.ts +34 -22
  527. package/src/services/workflow-supervisor-state.ts +21 -1
  528. package/src/services/workflow-supervisor.ts +4 -3
  529. package/src/services/workspace-recovery.ts +4 -3
  530. package/src/spawn-admission.ts +10 -2
  531. package/src/spawn-placement.ts +27 -9
  532. package/src/spawn-targets.ts +8 -8
  533. package/src/sse-agent-status.ts +15 -6
  534. package/src/sse.ts +49 -24
  535. package/src/startup-context-injection.ts +61 -57
  536. package/src/steward-handoff.ts +4 -3
  537. package/src/steward-policy-cleanup.ts +9 -8
  538. package/src/steward.ts +2 -2
  539. package/src/task-dispatch-live.ts +5 -2
  540. package/src/task-dispatcher.ts +2 -2
  541. package/src/token-db.ts +2 -2
  542. package/src/token-usage-async.ts +2 -4
  543. package/src/user-awaiting-reply.ts +5 -5
  544. package/src/workspace-actions.ts +24 -29
  545. package/src/workspace-auto-merge.ts +14 -12
  546. package/src/workspace-changed-paths-client.ts +16 -5
  547. package/src/workspace-cleanup-stale.ts +30 -7
  548. package/src/workspace-command-settle.ts +24 -16
  549. package/src/workspace-config.ts +1 -1
  550. package/src/workspace-conflict-resolver.ts +9 -7
  551. package/src/workspace-dead-owner-recovery.ts +5 -4
  552. package/src/workspace-escalation.ts +12 -11
  553. package/src/workspace-human-escalation.ts +3 -3
  554. package/src/workspace-land-gate-watchdog.ts +3 -3
  555. package/src/workspace-land-refusal.ts +20 -0
  556. package/src/workspace-land-stopped.ts +1 -0
  557. package/src/workspace-merge.ts +10 -26
  558. package/src/workspace-nothing-to-land-notice.ts +2 -2
  559. package/src/workspace-orphans.ts +18 -37
  560. package/src/workspace-pr-completion.ts +10 -8
  561. package/src/workspace-rekey.ts +3 -3
  562. package/src/workspace-steward-failure.ts +2 -2
  563. package/src/workspace-steward-wake.ts +2 -2
  564. package/public/assets/agent-liveness-CaNwK0Zf.js +0 -2
  565. package/public/assets/provider-display-DsuWMvVL.js +0 -2
  566. package/public/assets/run-card-CpKqRbhg.js +0 -2
  567. package/public/assets/shipping-B9YtnwtG.js +0 -2
  568. package/public/assets/store-DcClXLH0.js +0 -9
  569. package/runner/src/agent-runner/http-liveness.ts +0 -85
  570. package/src/db/on-loop-write-transaction.ts +0 -34
@@ -0,0 +1,13 @@
1
+ import { pruneKernelReplayResults } from "@humwick/kernel/sqlite-kernel-persistence";
2
+ import { getDb } from "./connection";
3
+ import { pruneTerminalCommands } from "./command-retention";
4
+ import { pruneTokenUsage } from "./token-usage-retention";
5
+ import type { WriterOpSpec } from "./write-ops";
6
+
7
+ // One gateway call is one small transaction; never drain a backlog inside the writer job (#3619).
8
+ const BATCH = 25;
9
+ export const HISTORY_RETENTION_WRITE_OPS: Readonly<Record<string, WriterOpSpec>> = {
10
+ "historyRetention.kernel": { offLoop: true, idempotent: true, fn: (_args, ctx) => pruneKernelReplayResults(getDb(), ctx.now, BATCH) },
11
+ "historyRetention.tokens": { offLoop: true, idempotent: true, fn: (_args, ctx) => pruneTokenUsage(ctx.now - 90 * 24 * 60 * 60_000, BATCH) },
12
+ "historyRetention.commands": { offLoop: true, idempotent: true, fn: (_args, ctx) => pruneTerminalCommands(ctx.now - 30 * 24 * 60 * 60_000, 1_000, ctx.now - 60 * 60_000) },
13
+ };
@@ -9,8 +9,8 @@ import type {
9
9
  setInboxThreadState,
10
10
  } from "./inbox.ts";
11
11
 
12
- export function getInboxStateAsync(operatorId: string): Promise<ReturnType<typeof getInboxState>> {
13
- return getGateway().read("inbox.state", { operatorId }) as unknown as Promise<ReturnType<typeof getInboxState>>;
12
+ export function getInboxStateAsync(operatorId: string, options: { excludeOffline?: boolean } = {}): Promise<ReturnType<typeof getInboxState>> {
13
+ return getGateway().read("inbox.state", { operatorId, ...(options.excludeOffline ? { excludeOffline: true } : {}) }) as unknown as Promise<ReturnType<typeof getInboxState>>;
14
14
  }
15
15
  export function listChatHistoryImportsAsync(input: Parameters<typeof listChatHistoryImports>[0] = {}): Promise<ReturnType<typeof listChatHistoryImports>> {
16
16
  return getGateway().read("inbox.historyImports", input) as unknown as Promise<ReturnType<typeof listChatHistoryImports>>;
@@ -3,6 +3,6 @@
3
3
  import { getInboxState, listChatHistoryImports } from "./inbox.ts";
4
4
 
5
5
  export const INBOX_READ_OPS = {
6
- "inbox.state": (args: { operatorId: string }) => getInboxState(args.operatorId),
6
+ "inbox.state": (args: { operatorId: string; excludeOffline?: boolean }) => getInboxState(args.operatorId, { excludeOffline: args.excludeOffline }),
7
7
  "inbox.historyImports": (args: Parameters<typeof listChatHistoryImports>[0]) => listChatHistoryImports(args),
8
8
  };
package/src/db/inbox.ts CHANGED
@@ -10,12 +10,18 @@ import type {
10
10
  InboxThreadState,
11
11
  } from "../types";
12
12
 
13
- export function getInboxState(operatorId: string): InboxState {
13
+ /**
14
+ * #2410 — `excludeOffline` (opt-in, the #2396 contract) keeps only peers whose agent is registered
15
+ * and not offline: the dashboard's "Show offline" switch hides every other thread, and the table
16
+ * accrues a row per agent id ever chatted with, so the full read is mostly long-dead peers.
17
+ */
18
+ export function getInboxState(operatorId: string, options: { excludeOffline?: boolean } = {}): InboxState {
19
+ const live = options.excludeOffline ? " AND peer_id IN (SELECT id FROM agents WHERE status <> 'offline')" : "";
14
20
  const threads = (getDb().query(
15
- "SELECT * FROM inbox_thread_state WHERE operator_id = ? ORDER BY updated_at DESC",
21
+ `SELECT * FROM inbox_thread_state WHERE operator_id = ?${live} ORDER BY updated_at DESC`,
16
22
  ).all(operatorId) as any[]).map(rowToInboxThreadState);
17
23
  const drafts = (getDb().query(
18
- "SELECT * FROM inbox_drafts WHERE operator_id = ? ORDER BY updated_at DESC",
24
+ `SELECT * FROM inbox_drafts WHERE operator_id = ?${live} ORDER BY updated_at DESC`,
19
25
  ).all(operatorId) as any[]).map(rowToInboxDraft);
20
26
  return { operatorId, threads, drafts };
21
27
  }
@@ -40,13 +40,13 @@ function cleanText(value: string | undefined, field: string, max: number): strin
40
40
  return cleaned;
41
41
  }
42
42
 
43
- export function associateIssue(issueRefId: string, entityType: IssueAssociationEntityType, entityId: string, relation?: IssueRefRelation): IssueAssociation {
43
+ export function associateIssue(issueRefId: string, entityType: IssueAssociationEntityType, entityId: string, relation?: IssueRefRelation, submittedAt: number = Date.now()): IssueAssociation {
44
44
  const cleanedIssueRefId = cleanText(issueRefId, "issueRefId", 120);
45
45
  if (!getIssueRef(cleanedIssueRefId)) throw new ValidationError(`issue ref ${cleanedIssueRefId} not found`);
46
46
  const cleanedEntityType = cleanEntityType(entityType);
47
47
  const cleanedEntityId = cleanText(entityId, "entityId", 240);
48
48
  const cleanedRelation = optionalEnum(relation, "relation", ISSUE_REF_RELATIONS);
49
- const now = Date.now();
49
+ const now = submittedAt;
50
50
  getDb().query(`
51
51
  INSERT INTO issue_associations (issue_ref_id, entity_type, entity_id, created_at, relation)
52
52
  VALUES (?, ?, ?, ?, ?)
@@ -3,11 +3,14 @@ import { randomUUID } from "node:crypto";
3
3
  import { getGateway } from "./gateway.ts";
4
4
  import type { IssueRef } from "../types";
5
5
  import type { IssueTrackerIssueSummary } from "../issue-tracker/adapter";
6
- import type { ResolveIssueRefByExternalKeyInput, ResolveIssueRefFromStringInput, UpdateIssueRefInput, UpsertIssueRefInput } from "./issue-refs.ts";
6
+ import type { ListIssueRefsOptions, ResolveIssueRefByExternalKeyInput, ResolveIssueRefFromStringInput, UpdateIssueRefInput, UpsertIssueRefInput } from "./issue-refs.ts";
7
7
  import type { IssueLifecycleActionRecord, UpsertIssueLifecycleActionInput } from "./issue-lifecycle.ts";
8
8
 
9
9
  const write = <T>(op: string, args: unknown, opts?: { idempotencyKey?: string }) => getGateway().write(op, args as never, opts) as unknown as Promise<T>;
10
10
 
11
+ const read = <T>(op: string, args: unknown = {}) => getGateway().read(op as never, args as never) as unknown as Promise<T>;
12
+
13
+ export const listIssueRefsAsync = (opts: ListIssueRefsOptions = {}) => read<IssueRef[]>("issueRefs.list", opts);
11
14
  export const upsertIssueRefAsync = (input: UpsertIssueRefInput) => write<IssueRef>("issueRefs.upsert", input);
12
15
  export const resolveIssueRefByExternalKeyAsync = (input: ResolveIssueRefByExternalKeyInput) => write<IssueRef>("issueRefs.resolveExternalKey", input);
13
16
  export const resolveIssueRefFromStringAsync = (input: ResolveIssueRefFromStringInput) => write<IssueRef>("issueRefs.resolveFromString", input);
@@ -0,0 +1,6 @@
1
+ // #3799 — the issue-ref listing on the reader pool (it ran as a cold main-thread scan, 10 s on swap-bound I/O).
2
+ import { listIssueRefs, type ListIssueRefsOptions } from "./issue-refs.ts";
3
+
4
+ export const ISSUE_REFS_READ_OPS = {
5
+ "issueRefs.list": (args: ListIssueRefsOptions) => listIssueRefs(args),
6
+ };
@@ -252,7 +252,8 @@ export function resolveIssueRefFromString(input: ResolveIssueRefFromStringInput,
252
252
  }, now);
253
253
  }
254
254
 
255
- export function listIssueRefs(opts: ListIssueRefsOptions = {}): IssueRef[] {
255
+ /** Listing SQL, exported so the query-plan gate pins the production statement (#3799). */
256
+ export function buildListIssueRefsQuery(opts: ListIssueRefsOptions = {}): { sql: string; params: Array<string | number> } {
256
257
  const conditions: string[] = [];
257
258
  const params: Array<string | number> = [];
258
259
  if (opts.provider) {
@@ -271,13 +272,12 @@ export function listIssueRefs(opts: ListIssueRefsOptions = {}): IssueRef[] {
271
272
  if (!Number.isSafeInteger(limit) || limit <= 0 || limit > 500) throw new ValidationError("limit must be between 1 and 500");
272
273
  params.push(limit);
273
274
  const where = conditions.length ? `WHERE ${conditions.join(" AND ")}` : "";
274
- const rows = getDb().query(`
275
- SELECT * FROM issue_refs
276
- ${where}
277
- ORDER BY updated_at DESC, created_at DESC, id DESC
278
- LIMIT ?
279
- `).all(...params) as IssueRefRow[];
280
- return rows.map(rowToIssueRef);
275
+ return { sql: `SELECT * FROM issue_refs ${where} ORDER BY updated_at DESC, created_at DESC, id DESC LIMIT ?`, params };
276
+ }
277
+
278
+ export function listIssueRefs(opts: ListIssueRefsOptions = {}): IssueRef[] {
279
+ const { sql, params } = buildListIssueRefsQuery(opts);
280
+ return (getDb().query(sql).all(...params) as IssueRefRow[]).map(rowToIssueRef);
281
281
  }
282
282
 
283
283
  export function updateIssueRef(id: string, input: UpdateIssueRefInput, now: number = Date.now()): IssueRef | null {
@@ -1,4 +1,5 @@
1
1
  import { ensureEventLogSchema } from "@humwick/kernel/event-log";
2
+ import { ensureKernelReplayRetentionSchema } from "@humwick/kernel/sqlite-kernel-persistence";
2
3
  import { getDb } from "./connection.ts";
3
4
 
4
5
  /**
@@ -11,4 +12,5 @@ import { getDb } from "./connection.ts";
11
12
  */
12
13
  export function ensureKernelEventLogSchema(): void {
13
14
  ensureEventLogSchema(getDb());
15
+ ensureKernelReplayRetentionSchema(getDb(), Date.now());
14
16
  }
@@ -1,7 +1,8 @@
1
1
  // #3363 — reads of the Kernel landing boundary and incident entities (kernel/landing-incident.ts) for
2
2
  // the red-main controller. Writes go through the Kernel ops (src/services/red-main-incident.ts).
3
3
  import type { LandBoundary, RedMainIncident } from "@humwick/kernel/landing-incident";
4
- import type { ShippingRedMain } from "@humwick/sdk";
4
+ import { ciRunUrl, type ShippingFailingTest, type ShippingRedMain } from "@humwick/sdk";
5
+ import type { BaseKnownFailure } from "@humwick/sdk/base-known-failures";
5
6
  import { getDb } from "./connection.ts";
6
7
  import { LAND_INCIDENT_TABLE, OPEN_INCIDENT_STATES } from "./land-incident-eligibility.ts";
7
8
 
@@ -81,12 +82,57 @@ export function openRedMainIncidentForRemote(remote: string): RedMainIncident |
81
82
  return row ? incident(row) : null;
82
83
  }
83
84
 
84
- /** The Shipping page's "lane stopped" lines: each open incident with its red sha. */
85
- export function openRedMainLines(): ShippingRedMain[] {
85
+ type OpenIncidentRow = Record<string, unknown> & { redRemote: string; redSha: string };
86
+
87
+ /** Open incidents with their red boundary. `i.*`: the Kernel adds a newer field's column only when the
88
+ * landing module starts, so a read right after deploy must not name it (#3362 §11 failingTests). */
89
+ function openIncidentRows(remote?: string): OpenIncidentRow[] {
86
90
  if (!tablesReady()) return [];
87
- return getDb().query(`SELECT i.id AS incidentId, i.laneKey, i.state, i.mainFailurePolicy, b.remote, b.landedTipSha AS sha
91
+ return getDb().query(`SELECT i.*, b.remote AS redRemote, b.landedTipSha AS redSha
88
92
  FROM ${LAND_INCIDENT_TABLE} i JOIN ${BOUNDARY_TABLE} b ON b.id = i.redBoundaryId
89
- WHERE i.state IN ${OPEN_STATES_SQL} ORDER BY i.openedAt, i.id`).all() as ShippingRedMain[];
93
+ WHERE i.state IN ${OPEN_STATES_SQL} AND (? IS NULL OR b.remote = ?) ORDER BY i.openedAt, i.id`).all(remote ?? null, remote ?? null) as OpenIncidentRow[];
94
+ }
95
+
96
+ function failingTestsOf(row: Record<string, unknown>): ShippingFailingTest[] {
97
+ if (typeof row.failingTests !== "string") return [];
98
+ try {
99
+ const parsed: unknown = JSON.parse(row.failingTests);
100
+ return Array.isArray(parsed) ? parsed.filter((entry): entry is ShippingFailingTest =>
101
+ typeof entry?.file === "string" && typeof entry?.name === "string") : [];
102
+ } catch {
103
+ console.warn(`[red-main] incident ${String(row.id)} has unreadable failingTests; showing none`);
104
+ return [];
105
+ }
106
+ }
107
+
108
+ function culpritTaskIds(row: Record<string, unknown>): number[] {
109
+ const ids = JSON.parse(String(row.culpritBoundaryIds ?? "[]")) as string[];
110
+ if (!ids.length) return [];
111
+ const rows = getDb().query(`SELECT DISTINCT taskId FROM ${BOUNDARY_TABLE} WHERE taskId IS NOT NULL AND id IN (${ids.map(() => "?").join(",")}) ORDER BY taskId`)
112
+ .all(...ids) as { taskId: number }[];
113
+ return rows.map((entry) => entry.taskId);
114
+ }
115
+
116
+ /** The Shipping page's red-main lines: each open incident with its red sha, failing cases, owners and run. */
117
+ export function openRedMainLines(): ShippingRedMain[] {
118
+ return openIncidentRows().map((row) => {
119
+ const failingTests = failingTestsOf(row);
120
+ const ownerTaskIds = culpritTaskIds(row);
121
+ return {
122
+ incidentId: String(row.id), laneKey: String(row.laneKey), state: String(row.state),
123
+ ...(row.mainFailurePolicy ? { mainFailurePolicy: row.mainFailurePolicy as "stop" | "advisory" } : {}),
124
+ remote: row.redRemote, sha: row.redSha,
125
+ runUrl: ciRunUrl(row.redRemote, Number(row.ciRunId), Number(row.ciAttempt)),
126
+ ...(failingTests.length ? { failingTests } : {}),
127
+ ...(ownerTaskIds.length ? { ownerTaskIds } : {}),
128
+ };
129
+ });
130
+ }
131
+
132
+ /** #3362 §11: failing cases already present on `remote`'s main, which delivery gates hold out of unrelated runs. */
133
+ export function baseKnownFailures(remote: string): BaseKnownFailure[] {
134
+ return openIncidentRows(remote).flatMap((row) =>
135
+ failingTestsOf(row).map((test) => ({ ...test, incidentId: String(row.id), sha: row.redSha })));
90
136
  }
91
137
 
92
138
  /** #3381 — whether any partial land happened (for `remote`, or anywhere when omitted): a boundary or a
@@ -110,7 +110,7 @@ export interface InsertLandGateAttestationInput extends Omit<LandGateAttestation
110
110
  * can legitimately land twice (a revert-and-reland, a retried push), and the FIRST green attestation
111
111
  * is as true as the second — so a conflict keeps the original rather than churning the row.
112
112
  */
113
- export function insertLandGateAttestation(input: InsertLandGateAttestationInput): LandGateAttestationRecord {
113
+ export function insertLandGateAttestation(input: InsertLandGateAttestationInput, submittedAt: number = Date.now()): LandGateAttestationRecord {
114
114
  const record = decodeLandGateAttestationRow({
115
115
  repo: input.repo,
116
116
  tree_sha: input.treeSha,
@@ -121,7 +121,7 @@ export function insertLandGateAttestation(input: InsertLandGateAttestationInput)
121
121
  gate_names: JSON.stringify(input.gateNames),
122
122
  gates_ran: input.gatesRan,
123
123
  workspace_id: input.workspaceId ?? null,
124
- attested_at: input.attestedAt ?? Date.now(),
124
+ attested_at: input.attestedAt ?? submittedAt,
125
125
  suite_coverage: input.suiteCoverage ?? null,
126
126
  });
127
127
  getDb().query(`
@@ -110,7 +110,7 @@ function receiptId(input: InsertLandingReceiptInput): string {
110
110
  return `landing_${createHash("sha256").update(key).digest("hex").slice(0, 24)}`;
111
111
  }
112
112
 
113
- export function insertLandingReceipt(input: InsertLandingReceiptInput): LandingReceipt {
113
+ export function insertLandingReceipt(input: InsertLandingReceiptInput, submittedAt: number = Date.now()): LandingReceipt {
114
114
  const receipt: LandingReceipt = decodeLandingReceiptRow({
115
115
  id: receiptId(input),
116
116
  project_id: input.projectId ?? null,
@@ -127,7 +127,7 @@ export function insertLandingReceipt(input: InsertLandingReceiptInput): LandingR
127
127
  team_id: input.teamId ?? null,
128
128
  orchestrator_id: input.orchestratorId ?? null,
129
129
  host: input.host ?? null,
130
- landed_at: input.landedAt ?? Date.now(),
130
+ landed_at: input.landedAt ?? submittedAt,
131
131
  });
132
132
  getDb().query(`
133
133
  INSERT INTO landing_receipts (
@@ -0,0 +1,5 @@
1
+ import { getDb } from "./connection";
2
+ import type { ContextState } from "../types";
3
+ export function persistAdvisoryContext(agentId: string, context: ContextState): void {
4
+ getDb().query("UPDATE agents SET context_state = ? WHERE id = ?").run(JSON.stringify(context), agentId);
5
+ }
@@ -0,0 +1,76 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { getGateway } from "./gateway";
3
+ import type { clearAgentNotice, recordTerminalEvent } from "./terminal-events";
4
+ import type { recordCrashReport } from "./crash-reports";
5
+ import type { stampSpawnPrincipal } from "./agent-ownership";
6
+ import type { markSpawnRegistrationConfirmed } from "../commands-db";
7
+ import type { seedSpawnPromptMirror } from "./message-reads";
8
+ import type { resolveQueuedPolicyMessages, resolveQueuedDirectMessages } from "./delivery";
9
+ import type { resolveQueuedCoordinatorMessages } from "./coordinator-delivery";
10
+ import type { updateManagedAgentState } from "../config-store";
11
+ import type { deleteWorkspace } from "./workspaces";
12
+ import type { addSubscription, removeSubscription, removeSubscriptionsForTask } from "./notification-subscriptions";
13
+
14
+ export function clearAgentNoticeAsync(...args: Parameters<typeof clearAgentNotice>): Promise<ReturnType<typeof clearAgentNotice>> {
15
+ return getGateway().write("lifecycleTail.clearAgentNotice", args) as unknown as Promise<ReturnType<typeof clearAgentNotice>>;
16
+ }
17
+
18
+ export function recordTerminalEventAsync(...args: Parameters<typeof recordTerminalEvent>): Promise<ReturnType<typeof recordTerminalEvent>> {
19
+ return getGateway().write("lifecycleTail.recordTerminalEvent", args, { idempotencyKey: randomUUID() }) as unknown as Promise<ReturnType<typeof recordTerminalEvent>>;
20
+ }
21
+
22
+ export function recordCrashReportAsync(...args: Parameters<typeof recordCrashReport>): Promise<ReturnType<typeof recordCrashReport>> {
23
+ return getGateway().write("lifecycleTail.recordCrashReport", args, { idempotencyKey: randomUUID() }) as unknown as Promise<ReturnType<typeof recordCrashReport>>;
24
+ }
25
+
26
+ export function stampSpawnPrincipalAsync(...args: Parameters<typeof stampSpawnPrincipal>): Promise<ReturnType<typeof stampSpawnPrincipal>> {
27
+ return getGateway().write("lifecycleTail.stampSpawnPrincipal", args) as unknown as Promise<ReturnType<typeof stampSpawnPrincipal>>;
28
+ }
29
+
30
+ export function markSpawnRegistrationConfirmedAsync(...args: Parameters<typeof markSpawnRegistrationConfirmed>): Promise<ReturnType<typeof markSpawnRegistrationConfirmed>> {
31
+ return getGateway().write("lifecycleTail.markSpawnRegistrationConfirmed", args) as unknown as Promise<ReturnType<typeof markSpawnRegistrationConfirmed>>;
32
+ }
33
+
34
+ export function seedSpawnPromptMirrorAsync(...args: Parameters<typeof seedSpawnPromptMirror>): Promise<ReturnType<typeof seedSpawnPromptMirror>> {
35
+ return getGateway().write("lifecycleTail.seedSpawnPromptMirror", args) as unknown as Promise<ReturnType<typeof seedSpawnPromptMirror>>;
36
+ }
37
+
38
+ export function resolveQueuedPolicyMessagesAsync(...args: Parameters<typeof resolveQueuedPolicyMessages>): Promise<ReturnType<typeof resolveQueuedPolicyMessages>> {
39
+ return getGateway().write("lifecycleTail.resolveQueuedPolicyMessages", args) as unknown as Promise<ReturnType<typeof resolveQueuedPolicyMessages>>;
40
+ }
41
+
42
+ export function resolveQueuedDirectMessagesAsync(...args: Parameters<typeof resolveQueuedDirectMessages>): Promise<ReturnType<typeof resolveQueuedDirectMessages>> {
43
+ return getGateway().write("lifecycleTail.resolveQueuedDirectMessages", args) as unknown as Promise<ReturnType<typeof resolveQueuedDirectMessages>>;
44
+ }
45
+
46
+ export function resolveQueuedCoordinatorMessagesAsync(...args: Parameters<typeof resolveQueuedCoordinatorMessages>): Promise<ReturnType<typeof resolveQueuedCoordinatorMessages>> {
47
+ return getGateway().write("lifecycleTail.resolveQueuedCoordinatorMessages", args) as unknown as Promise<ReturnType<typeof resolveQueuedCoordinatorMessages>>;
48
+ }
49
+
50
+ export function updateManagedAgentStateAsync(...args: Parameters<typeof updateManagedAgentState>): Promise<ReturnType<typeof updateManagedAgentState>> {
51
+ return getGateway().write("lifecycleTail.updateManagedAgentState", args) as unknown as Promise<ReturnType<typeof updateManagedAgentState>>;
52
+ }
53
+
54
+ export function deleteWorkspaceAsync(...args: Parameters<typeof deleteWorkspace>): Promise<ReturnType<typeof deleteWorkspace>> {
55
+ return getGateway().write("lifecycleTail.deleteWorkspace", args) as unknown as Promise<ReturnType<typeof deleteWorkspace>>;
56
+ }
57
+
58
+ export function addSubscriptionAsync(...args: Parameters<typeof addSubscription>): Promise<ReturnType<typeof addSubscription>> {
59
+ return getGateway().write("lifecycleTail.addSubscription", args, { idempotencyKey: randomUUID() }) as unknown as Promise<ReturnType<typeof addSubscription>>;
60
+ }
61
+
62
+ export function removeSubscriptionAsync(...args: Parameters<typeof removeSubscription>): Promise<ReturnType<typeof removeSubscription>> {
63
+ return getGateway().write("lifecycleTail.removeSubscription", args) as unknown as Promise<ReturnType<typeof removeSubscription>>;
64
+ }
65
+
66
+ export function removeSubscriptionsForTaskAsync(...args: Parameters<typeof removeSubscriptionsForTask>): Promise<ReturnType<typeof removeSubscriptionsForTask>> {
67
+ return getGateway().write("lifecycleTail.removeSubscriptionsForTask", args) as unknown as Promise<ReturnType<typeof removeSubscriptionsForTask>>;
68
+ }
69
+
70
+ export function addSubscriptionsAsync(inputs: Array<Parameters<typeof addSubscription>[0]>): Promise<Array<ReturnType<typeof addSubscription>>> {
71
+ return getGateway().write("notifications.subscriptions.addBatch", { inputs }, { idempotencyKey: randomUUID() }) as unknown as Promise<Array<ReturnType<typeof addSubscription>>>;
72
+ }
73
+
74
+ export function upsertManagedAgentStateAsync(...args: Parameters<typeof import("../config-store").upsertManagedAgentState>): Promise<ReturnType<typeof import("../config-store").upsertManagedAgentState>> {
75
+ return getGateway().write("lifecycleTail.upsertManagedAgentState", args) as unknown as Promise<ReturnType<typeof import("../config-store").upsertManagedAgentState>>;
76
+ }
@@ -0,0 +1,62 @@
1
+ import { createAgentControlBody, grantSelfEffortBody, type AgentControlWrite, type SelfEffortGrantWrite } from "./agent-control-write";
2
+ import { redactCommandSecrets } from "../command-secrets";
3
+ import { admitAgentActionBody, type AgentActionWrite } from "./agent-action-write";
4
+ import { startBrowserBody, stopBrowserBody, type StartBrowserWrite } from "./browser-session-write";
5
+ import { transitionBrowserSession } from "./browser-sessions";
6
+ import { flushWorkFinalReportsFromQueue } from "./messages";
7
+ import type { MessageWorkFinalitySource, Command } from "../types";
8
+ import { insertOperatorDecision } from "./operator-decisions";
9
+ import { persistAdvisoryContext } from "./lifecycle-context-write";
10
+ import { createReviewPublishBody } from "./review-publish-write";
11
+ import { answerOperatorDecisionBody, fileWorkflowDecisionBody } from "./operator-answer-write";
12
+ import { observeEffortBody } from "./agent-effort-observe";
13
+ import { recordContextResetBody, settleContextResetCarryBody } from "./context-carry-write";
14
+ import { openRecoveryObligation, attachRecoveryCommand, type ContextResetFact } from "./context-reset-carry";
15
+ import { clearAgentNotice, recordTerminalEvent } from "./terminal-events";
16
+ import { recordCrashReport } from "./crash-reports";
17
+ import { stampSpawnPrincipal } from "./agent-ownership";
18
+ import { markSpawnRegistrationConfirmed } from "../commands-db";
19
+ import { seedSpawnPromptMirror } from "./message-reads";
20
+ import { resolveQueuedPolicyMessages, resolveQueuedDirectMessages } from "./delivery";
21
+ import { resolveQueuedCoordinatorMessages } from "./coordinator-delivery";
22
+ import { upsertManagedAgentState, updateManagedAgentState } from "../config-store";
23
+ import { deleteWorkspace } from "./workspaces";
24
+ import { addSubscription, removeSubscription, removeSubscriptionsForTask } from "./notification-subscriptions";
25
+ import type { WriterOpSpec } from "./write-ops";
26
+
27
+ export const LIFECYCLE_TAIL_WRITE_OPS: Readonly<Record<string, WriterOpSpec>> = Object.freeze({
28
+ "lifecycleTail.upsertManagedAgentState": { offLoop: true, idempotent: true, fn: (args: Parameters<typeof upsertManagedAgentState>) => upsertManagedAgentState(...args) },
29
+ "agents.createControl": { offLoop: true, idempotent: false, fn: (a: AgentControlWrite, ctx) => createAgentControlBody(a, ctx.now), serializeIdempotencyResult: value => { const result = value as ReturnType<typeof createAgentControlBody>; return { ...result, ...(result.command ? { command: redactCommandSecrets(result.command) } : {}) }; } },
30
+ "agents.grantSelfEffort": { offLoop: true, idempotent: false, fn: (a: SelfEffortGrantWrite, ctx) => grantSelfEffortBody(a, ctx.now), serializeIdempotencyResult: value => redactCommandSecrets(value as import("../types").Command) },
31
+ "agents.admitAction": { offLoop: true, idempotent: false, serializeIdempotencyResult: value => { const result = value as ReturnType<typeof admitAgentActionBody>; return { ...result, command: redactCommandSecrets(result.command) }; }, fn: (args: AgentActionWrite, ctx) => admitAgentActionBody(args, ctx.now) },
32
+ "browser.start": { offLoop: true, idempotent: false, fn: (args: StartBrowserWrite, ctx) => startBrowserBody(args, ctx.now) },
33
+ "browser.stop": { offLoop: true, idempotent: false, fn: (args: Parameters<typeof stopBrowserBody>[0], ctx) => stopBrowserBody(args, ctx.now) },
34
+ "browser.transition": { offLoop: true, idempotent: true, fn: (args: Parameters<typeof transitionBrowserSession>) => transitionBrowserSession(...args) ?? null },
35
+ "notifications.subscriptions.addBatch": { offLoop: true, idempotent: false,
36
+ fn: (a: { inputs: Array<Parameters<typeof addSubscription>[0]> }, ctx) => a.inputs.map(input => addSubscription({ ...input, now: ctx.now })) },
37
+ "messages.flushWorkFinal": { offLoop: true, idempotent: true, fn: (a: { agentId: string; queue: number[]; source: MessageWorkFinalitySource; options: { includeStaleCompletedFinals?: boolean } }, ctx) => flushWorkFinalReportsFromQueue(a.agentId, a.queue, a.source, a.options, ctx.now) },
38
+ "operatorDecisions.insert": { offLoop: true, idempotent: false, fn: insertOperatorDecision },
39
+ "operatorDecisions.fileWorkflow": { offLoop: true, idempotent: true, fn: fileWorkflowDecisionBody },
40
+ "agents.persistAdvisoryContext": { offLoop: true, idempotent: true, fn: (a: Parameters<typeof persistAdvisoryContext>) => persistAdvisoryContext(...a) ?? null },
41
+ "commands.createReviewPublish": { offLoop: true, idempotent: true, fn: (args: Parameters<typeof createReviewPublishBody>[0], ctx) => createReviewPublishBody(args, ctx.now) },
42
+ "operatorDecisions.answer": { offLoop: true, idempotent: true, fn: answerOperatorDecisionBody },
43
+ "agents.observeEffort": { offLoop: true, idempotent: true, fn: observeEffortBody },
44
+ "contextCarry.open": { offLoop: true, idempotent: true, fn: (a: { agentId: string; generation: number; now: number }) => openRecoveryObligation(a.agentId, a.generation, a.now) ?? null },
45
+ "contextCarry.attach": { offLoop: true, idempotent: true, fn: (a: { agentId: string; generation: number; commandId: string }) => attachRecoveryCommand(a.agentId, a.generation, a.commandId) ?? null },
46
+ "contextCarry.reset": { offLoop: true, idempotent: true, fn: (a: { fact: ContextResetFact }, ctx) => recordContextResetBody(a.fact, ctx.now) },
47
+ "contextCarry.settle": { offLoop: true, idempotent: true, fn: (a: { command: Command }, ctx) => settleContextResetCarryBody(a.command, ctx.now) },
48
+ "lifecycleTail.clearAgentNotice": { offLoop: true, idempotent: true, fn: (args: Parameters<typeof clearAgentNotice>) => clearAgentNotice(...args) ?? null },
49
+ "lifecycleTail.recordTerminalEvent": { offLoop: true, idempotent: false, fn: (args: Parameters<typeof recordTerminalEvent>) => recordTerminalEvent(...args) ?? null },
50
+ "lifecycleTail.recordCrashReport": { offLoop: true, idempotent: false, fn: (args: Parameters<typeof recordCrashReport>) => recordCrashReport(...args) ?? null },
51
+ "lifecycleTail.stampSpawnPrincipal": { offLoop: true, idempotent: true, fn: (args: Parameters<typeof stampSpawnPrincipal>) => stampSpawnPrincipal(...args) ?? null },
52
+ "lifecycleTail.markSpawnRegistrationConfirmed": { offLoop: true, idempotent: true, fn: (args: Parameters<typeof markSpawnRegistrationConfirmed>) => markSpawnRegistrationConfirmed(...args) ?? null },
53
+ "lifecycleTail.seedSpawnPromptMirror": { offLoop: true, idempotent: true, fn: (args: Parameters<typeof seedSpawnPromptMirror>) => seedSpawnPromptMirror(...args) ?? null },
54
+ "lifecycleTail.resolveQueuedPolicyMessages": { offLoop: true, idempotent: true, fn: (args: Parameters<typeof resolveQueuedPolicyMessages>) => resolveQueuedPolicyMessages(...args) ?? null },
55
+ "lifecycleTail.resolveQueuedDirectMessages": { offLoop: true, idempotent: true, fn: (args: Parameters<typeof resolveQueuedDirectMessages>) => resolveQueuedDirectMessages(...args) ?? null },
56
+ "lifecycleTail.resolveQueuedCoordinatorMessages": { offLoop: true, idempotent: true, fn: (args: Parameters<typeof resolveQueuedCoordinatorMessages>) => resolveQueuedCoordinatorMessages(...args) ?? null },
57
+ "lifecycleTail.updateManagedAgentState": { offLoop: true, idempotent: true, fn: (args: Parameters<typeof updateManagedAgentState>) => updateManagedAgentState(...args) ?? null },
58
+ "lifecycleTail.deleteWorkspace": { offLoop: true, idempotent: true, fn: (args: Parameters<typeof deleteWorkspace>) => deleteWorkspace(...args) ?? null },
59
+ "lifecycleTail.addSubscription": { offLoop: true, idempotent: false, fn: (args: Parameters<typeof addSubscription>) => addSubscription(...args) ?? null },
60
+ "lifecycleTail.removeSubscription": { offLoop: true, idempotent: true, fn: (args: Parameters<typeof removeSubscription>) => removeSubscription(...args) ?? null },
61
+ "lifecycleTail.removeSubscriptionsForTask": { offLoop: true, idempotent: true, fn: (args: Parameters<typeof removeSubscriptionsForTask>) => removeSubscriptionsForTask(...args) ?? null },
62
+ });
@@ -1,3 +1,4 @@
1
+ import { framed } from "../hub-metrics";
1
2
  import { assertMainThreadState, mainThreadRegistry } from "./main-thread-state";
2
3
  // #2111 — short-window write coalescing for the hot agent lifecycle / presence / registration writes.
3
4
  //
@@ -116,7 +117,7 @@ export function coalesceLifecycleWrite(op: WriteOp, args: unknown, options: Coal
116
117
  return;
117
118
  }
118
119
  if (!window.timer && windowMs() !== Infinity) {
119
- window.timer = setTimeout(() => flushWindow(op), windowMs());
120
+ window.timer = setTimeout(framed("timer lifecycle-write-window", () => flushWindow(op)), windowMs());
120
121
  window.timer.unref?.();
121
122
  }
122
123
  });
@@ -1,11 +1,11 @@
1
- import { MSG_SELECT } from "./mappers.ts";
2
1
  import { MIRROR_LOOKBACK_ROWS, notReportSuppressedSql } from "./report-suppression.ts";
3
2
 
4
3
  // #3644 — the child→parent chat dedupe reads behind a turn-final forward (db/messages.ts). Pinned
5
4
  // to the pair index: unpinned, the planner ranges idx_msg_to_id over the parent's whole inbox
6
5
  // (600k rows for `user`) and json_extracts every payload on the main thread. Each query must keep
7
- // `m.from_agent = ?`, `m.to_target = ?` and `m.kind = 'chat'` (the partial index predicate).
8
- const PAIR_CHAT_SELECT = `${MSG_SELECT} INDEXED BY idx_msg_pair_chat
6
+ // `m.from_agent = ?`, `m.to_target = ?`, `m.kind = 'chat'` (the partial index predicate).
7
+ // #3617: id + body only, no read/reaction rollups per candidate row; callers hydrate the winner via getMessage.
8
+ const PAIR_CHAT_SELECT = `SELECT m.id, m.body FROM messages m INDEXED BY idx_msg_pair_chat
9
9
  WHERE m.from_agent = ?
10
10
  AND m.to_target = ?
11
11
  AND m.kind = 'chat'`;
@@ -0,0 +1,11 @@
1
+ import { relayEnv } from "@humwick/sdk/env";
2
+ import { getDb, getDbPath } from "./connection";
3
+
4
+ /** Seal the hub handle after all boot writes, immediately before bind (#1870 S8).
5
+ * Opt in during the soak; standalone tools and :memory: fixtures retain their own write handles. */
6
+ export function configureMainConnectionForServing(env = process.env): boolean {
7
+ if (!Bun.isMainThread || getDbPath() === ":memory:" || !getDbPath()) return false;
8
+ if (relayEnv(env, "AGENT_RELAY_MAIN_DB_QUERY_ONLY") !== "1") return false;
9
+ getDb().run("PRAGMA query_only = ON");
10
+ return true;
11
+ }
@@ -1,4 +1,5 @@
1
1
  // #1870: bounded, minute-bucketed census of main-connection write attempts, including failed SQL.
2
+ import { configureMainConnectionForServing } from "./main-connection-mode";
2
3
  const MINUTE = 60_000;
3
4
  const WINDOW_BUCKETS = 60;
4
5
  const MAX_LABELS = 512;
@@ -42,7 +43,7 @@ export function resetMainConnectionWrites(): void {
42
43
  let booting = false;
43
44
  let bootWrites = 0;
44
45
  export function beginMainWriteBootPhase(): void { booting = true; }
45
- export function endMainWriteBootPhase(): void { booting = false; }
46
+ export function endMainWriteBootPhase(): void { configureMainConnectionForServing(); booting = false; }
46
47
  export function mainConnectionBootWrites(): number { return bootWrites; }
47
48
  // The first sighting of each label per process logs its caller once, so the remaining raw writers are named
48
49
  // from hub logs without a debugger. Bounded by the counter's own label cap.
@@ -52,14 +53,31 @@ function logFirstSighting(label: string): void {
52
53
  loggedLabels.add(label);
53
54
  const site = (new Error().stack ?? "").split("\n").filter((line) => line.includes(" at ") && !/connection|main-connection-writes/.test(line)).slice(0, 4)
54
55
  .map((line) => line.trim().replace(/^at /, "").replace(/\(?\/[^)]*\/(src\/[^):]+:\d+)[^)]*\)?/, "$1")).join(" < ");
55
- console.log(`[relay] #1870 main-connection write: ${label} :: ${site}`);
56
+ // Maintenance subprocesses reserve stdout for their JSON result (#3800).
57
+ console.error(`[relay] #1870 main-connection write: ${label} :: ${site}`);
56
58
  }
57
59
  export function recordMainConnectionWrite(label: string | undefined): void {
58
60
  if (!Bun.isMainThread || !label) return;
59
61
  if (booting) { bootWrites++; return; }
60
62
  counter.record(label, Date.now());
63
+ if (forbidRawWrites) { forbiddenWriteAttempts++; throw new Error(`#1870 raw main-connection write: ${label}`); }
61
64
  logFirstSighting(label);
62
65
  }
63
66
  export function mainConnectionWrites(now: number = Date.now()): Record<string, number> {
64
67
  return counter.snapshot(now);
65
68
  }
69
+
70
+ let forbidRawWrites = false;
71
+ let forbiddenWriteAttempts = 0;
72
+ /** Test ratchet for real service/route replay on the file-backed gateway. */
73
+ export async function withRawMainWritesForbidden<T>(run: () => Promise<T>): Promise<T> {
74
+ const previous = forbidRawWrites;
75
+ const attempts = forbiddenWriteAttempts;
76
+ forbidRawWrites = true;
77
+ try {
78
+ const result = await run();
79
+ if (forbiddenWriteAttempts !== attempts) throw new Error("#1870 raw main-connection write was caught inside the replay");
80
+ return result;
81
+ }
82
+ finally { forbidRawWrites = previous; }
83
+ }
@@ -19,8 +19,8 @@ export const finishMaintenanceRunAsync = (args: MaintenanceRunOutcome) =>
19
19
  export async function ensureMaintenanceJobsAsync(jobs: MaintenanceJobSpec[]): Promise<void> {
20
20
  await getGateway().write("maintenanceJobs.ensure", { jobs }, { critical: true });
21
21
  }
22
- export async function rescheduleRunOnStartJobsAsync(): Promise<void> {
23
- await getGateway().write("maintenanceJobs.rescheduleRunOnStart", {}, { critical: true });
22
+ export async function rescheduleRunOnStartJobsAsync(now?: number): Promise<void> {
23
+ await getGateway().write("maintenanceJobs.rescheduleRunOnStart", { now }, { critical: true });
24
24
  }
25
25
  export async function reclaimDeadLeasesAsync(leases: DeadLease[]): Promise<void> {
26
26
  if (leases.length) await getGateway().write("maintenanceJobs.reclaimDeadLeases", { leases }, { critical: true });
@@ -11,7 +11,7 @@ export const MAINTENANCE_JOB_WRITE_OPS: Readonly<Record<string, WriterOpSpec>> =
11
11
  "maintenanceJobs.heartbeat": { offLoop: true, idempotent: true, fn: (args: { id: string; runToken: string }, ctx) => heartbeatMaintenanceRun(args, ctx.now) },
12
12
  // Reconcile is a re-assertion of code-owned columns, so a replay changes nothing.
13
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) },
14
+ "maintenanceJobs.rescheduleRunOnStart": { offLoop: true, idempotent: true, fn: (args: { now?: number }, ctx) => rescheduleRunOnStartJobs(args.now ?? ctx.now) },
15
15
  "maintenanceJobs.reclaimDeadLeases": { offLoop: true, idempotent: true, fn: (args: { leases: DeadLease[] }, ctx) => reclaimDeadLeases(args.leases, ctx.now) },
16
16
  // A compare-and-set on the observed lease, so a replay finds nothing left to reset.
17
17
  "maintenanceJobs.resetExpiredCritical": { offLoop: true, idempotent: true, fn: (args: { now: number }) => resetExpiredCriticalMaintenanceRuns(args.now) },
@@ -123,8 +123,8 @@ export interface PauseMaintenanceJobInput {
123
123
  /** Acquire a pause. An existing ACTIVE pause is returned as a conflict rather than having its
124
124
  * holder replaced — same contract as acquireMergePause, so one operator's hold plus its reason
125
125
  * cannot be quietly overwritten by another's. A LAPSED row is replaced. */
126
- export function pauseMaintenanceJob(input: PauseMaintenanceJobInput): { ok: boolean; pause: MaintenanceJobPauseRecord } {
127
- const now = input.now ?? Date.now();
126
+ export function pauseMaintenanceJob(input: PauseMaintenanceJobInput, submittedAt: number = Date.now()): { ok: boolean; pause: MaintenanceJobPauseRecord } {
127
+ const now = input.now ?? submittedAt;
128
128
  const expiresAt = input.indefinite ? null : now + (input.ttlMs ?? DEFAULT_MAINTENANCE_PAUSE_TTL_MS);
129
129
  return writeTransaction(() => {
130
130
  const existing = getMaintenanceJobPause(input.jobId, now);
@@ -74,3 +74,7 @@ export function sendMessagesBatchWithResultAsync(
74
74
  SendMessageResult[]
75
75
  >;
76
76
  }
77
+
78
+ export async function sendMessageAsync(input: SendMessageInput) {
79
+ return (await sendMessageWithResultAsync(input)).message;
80
+ }