@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
@@ -59,9 +59,9 @@ export { runningAgentForPolicy } from "./policy-runtime.ts";
59
59
 
60
60
  export function findMessageByIdempotencyKey(from: string, key: string): Message | null {
61
61
  const row = getDb()
62
- .query(`${MSG_SELECT} WHERE m.from_agent = ? AND m.idempotency_key = ? LIMIT 1`)
63
- .get(from, key) as any;
64
- return row ? rowToMessage(row) : null;
62
+ .query("SELECT m.id FROM messages m WHERE m.from_agent = ? AND m.idempotency_key = ? LIMIT 1")
63
+ .get(from, key) as { id: number } | null;
64
+ return row ? getMessage(row.id) : null;
65
65
  }
66
66
 
67
67
  function sessionStepId(payload: Record<string, unknown> | undefined): string | undefined {
@@ -162,7 +162,7 @@ function findRecentDuplicateReply(input: SendMessageInput, threadId: number | nu
162
162
  if (!input.replyTo || threadId === null || hasAttachments) return null;
163
163
  const kind = inferMessageKind(input);
164
164
  const row = getDb().query(`
165
- ${MSG_SELECT}
165
+ SELECT m.id FROM messages m
166
166
  WHERE m.from_agent = ?
167
167
  AND m.to_target = ?
168
168
  AND m.kind = ?
@@ -183,8 +183,8 @@ function findRecentDuplicateReply(input: SendMessageInput, threadId: number | nu
183
183
  input.subject ?? null,
184
184
  input.channel ?? null,
185
185
  now - REPLY_DUPLICATE_WINDOW_MS,
186
- ) as any;
187
- return row ? rowToMessage(row) : null;
186
+ ) as { id: number } | null;
187
+ return row ? getMessage(row.id) : null;
188
188
  }
189
189
 
190
190
  function sessionMeta(input: Message | SendMessageInput): Record<string, unknown> | undefined {
@@ -252,8 +252,8 @@ function findLineageReportByTurn(input: {
252
252
  lineageKey,
253
253
  `response-report:${input.from}:%:${input.turnId}`,
254
254
  input.turnId,
255
- ) as any;
256
- return row ? rowToMessage(row) : null;
255
+ ) as { id: number } | null;
256
+ return row ? getMessage(row.id) : null;
257
257
  }
258
258
 
259
259
  function findRecentLineageReport(input: SendMessageInput, now: number): Message | null {
@@ -275,8 +275,8 @@ function findRecentLineageReport(input: SendMessageInput, now: number): Message
275
275
  if (duplicate) return duplicate;
276
276
  }
277
277
  if (responseCapture) return null;
278
- const row = getDb().query(RECENT_LINEAGE_REPORT_SQL).get(from, to, input.body, now - LINEAGE_REPORT_DUPLICATE_WINDOW_MS, `response-report:${from}:%`) as any;
279
- return row ? rowToMessage(row) : null;
278
+ const row = getDb().query(RECENT_LINEAGE_REPORT_SQL).get(from, to, input.body, now - LINEAGE_REPORT_DUPLICATE_WINDOW_MS, `response-report:${from}:%`) as { id: number } | null;
279
+ return row ? getMessage(row.id) : null;
280
280
  }
281
281
 
282
282
  // T4458 (Edin's rule #2844 1a) — the sender's currently-open provider turn: the newest
@@ -340,10 +340,10 @@ function findSameTurnExplicitReport(input: {
340
340
  input.to,
341
341
  ...(input.turnId ? [input.from, input.turnId, fallback] : [fallback]),
342
342
  input.at + LINEAGE_REPORT_DUPLICATE_WINDOW_MS,
343
- ) as any[];
343
+ ) as Array<{ id: number; body: string | null }>;
344
344
  for (const row of rows) {
345
345
  const explicit = normBody(String(row?.body ?? ""));
346
- if (explicit.length >= SAME_TURN_MIN_CHARS && explicit.includes(body)) return rowToMessage(row);
346
+ if (explicit.length >= SAME_TURN_MIN_CHARS && explicit.includes(body)) return getMessage(row.id);
347
347
  }
348
348
  return null;
349
349
  }
@@ -361,9 +361,9 @@ function findTurnExplicitReport(input: {
361
361
  captureLen: number;
362
362
  captureOccurredAt: number;
363
363
  }): Message | null {
364
- const row = getDb().query(TURN_EXPLICIT_REPORT_SQL).get(input.from, input.to, input.turnId, input.before) as any;
365
- if (!row) return null;
366
- const message = rowToMessage(row);
364
+ const row = getDb().query(TURN_EXPLICIT_REPORT_SQL).get(input.from, input.to, input.turnId, input.before) as { id: number } | null;
365
+ const message = row ? getMessage(row.id) : null;
366
+ if (!message) return null;
367
367
  const explicitLen = normBody(String(message.body ?? "")).length;
368
368
  if (explicitLen < SAME_TURN_MIN_CHARS || explicitLen < 0.5 * input.captureLen) return null;
369
369
  // MED-1 (r1 review): a stamp is turn inference and can go stale — t1's late final can land
@@ -409,8 +409,8 @@ function findRecentExplicitLineageDuplicate(input: {
409
409
  input.to,
410
410
  input.body,
411
411
  input.since,
412
- ) as any;
413
- return row ? rowToMessage(row) : null;
412
+ ) as { id: number } | null;
413
+ return row ? getMessage(row.id) : null;
414
414
  }
415
415
 
416
416
  // #1084/#3304 — reporting a definitive provider completion does not close its work window.
@@ -418,9 +418,14 @@ function findRecentExplicitLineageDuplicate(input: {
418
418
  const LINEAGE_FINAL_GROUP_RECENCY_MS = 10 * 60 * 1000;
419
419
  export { __resetDeferredLineageReports, restoreDeferredLineageReports, takeDeferredLineageReports } from "./deferred-lineage-reports.ts";
420
420
 
421
+ export interface LineageReportResult {
422
+ message: Message;
423
+ created: boolean;
424
+ lifecycleSignals?: Array<{ recipient: string; subjectKey: string }>;
425
+ }
421
426
  export interface WorkFinalFlushResult {
422
427
  /** The chat report(s) forwarded (or found already-forwarded) to the spawner, oldest first. */
423
- forwarded: Array<{ message: Message; created: boolean }>;
428
+ forwarded: LineageReportResult[];
424
429
  /** The original session-mirror captures retroactively tagged `payload.finality` (for the
425
430
  * `message.finalized` SSE upgrade of already-rendered bubbles). */
426
431
  finalizedOriginals: Message[];
@@ -533,12 +538,11 @@ function edgeFallbackReportScore(body: string): number {
533
538
  // redundant "Task status pulse" for the same child/task is suppressed rather than double-waking.
534
539
  // #1038 — a report-up conveys "the child is progressing", so it is state-keyed to `working`: it
535
540
  // supersedes only a redundant `working` pulse, never a later distinct milestone (committed/landed/done).
536
- function stampReportUpLifecycleSignal(parent: string, child: AgentCard): void {
537
- stampLifecycleSignal(parent, childSignalKey(child.id));
538
- for (const taskId of childTaskIds(child)) stampLifecycleSignal(parent, taskSignalKey(taskId, LIFECYCLE_STATE_WORKING));
541
+ export function applyLineageReportEffects(result: LineageReportResult | null): void {
542
+ for (const signal of result?.lifecycleSignals ?? []) stampLifecycleSignal(signal.recipient, signal.subjectKey);
539
543
  }
540
544
 
541
- export function promoteLineageCapturedResponse(message: Message): { message: Message; created: boolean } | null {
545
+ export function promoteLineageCapturedResponse(message: Message, deferred?: Array<{ childId: string; messageId: number }>): LineageReportResult | null {
542
546
  if (message.kind !== "session" || isReportSuppressed(message)) return null;
543
547
  const session = sessionMeta(message);
544
548
  if (!session) return null;
@@ -556,7 +560,8 @@ export function promoteLineageCapturedResponse(message: Message): { message: Mes
556
560
  // #3304 — provider completion is enough to report a turn, without declaring the Task done.
557
561
  // Keep the ring for later work-final metadata; addressed captures already have a runner reply.
558
562
  if (stream === "responses" && verbosity !== "verbose") {
559
- deferLineageReport(child.id, message.id);
563
+ if (deferred) deferred.push({ childId: child.id, messageId: message.id });
564
+ else deferLineageReport(child.id, message.id);
560
565
  if (session.confidence !== "definitive" || session.endedBy !== "completed" || message.replyTo) return null;
561
566
  if (lineageDeliveryCoversCapture(message, child, parent, message.createdAt)) return null;
562
567
  }
@@ -576,7 +581,7 @@ function forwardLineageStreamCapture(
576
581
  child: AgentCard,
577
582
  parent: string,
578
583
  finality?: MessageWorkFinality,
579
- ): { message: Message; created: boolean } | null {
584
+ ): LineageReportResult | null {
580
585
  const parentAgentId = parent;
581
586
  parent = canonicalLineageReportTarget(child, parent);
582
587
  const idempotencyKey = lineageStreamReportKey(message, session, stream);
@@ -679,7 +684,8 @@ function forwardLineageStreamCapture(
679
684
  occurredAt: message.occurredAt ?? message.createdAt,
680
685
  });
681
686
  if (stream === "responses" && result.created) {
682
- stampReportUpLifecycleSignal(parentAgentId, child);
687
+ return { ...result, lifecycleSignals: [childSignalKey(child.id), ...childTaskIds(child).map(id => taskSignalKey(id, LIFECYCLE_STATE_WORKING))]
688
+ .map(subjectKey => ({ recipient: parentAgentId, subjectKey })) };
683
689
  }
684
690
  return result;
685
691
  }
@@ -1,3 +1,6 @@
1
+ import { COMMAND_RETENTION_SCHEMA, ensureCommandRetentionReferenceIndexes } from "./command-retention";
2
+ import { migrateCheckoutRefreshHeads } from "./checkout-refresh-schema";
3
+ import { TOKEN_USAGE_RETENTION_SCHEMA } from "./token-usage-retention";
1
4
  import { PLAN_DISPATCH_SCHEMA_SQL } from "./plan-dispatch-schema";
2
5
  import { PLAN_IMPORT_SCHEMA_SQL } from "./plan-import-schema";
3
6
  import { ensureDurablePayloadSchema } from "./durable-payloads"; import { redactProviderConfigHistory } from "./provider-config-history"; import { retireMemoryStorage } from "./memory-retirement";
@@ -66,6 +69,7 @@ import { migrateTeamProjectSeats } from "./team-project.ts";
66
69
  import { ensureArtifactCreatorAgentColumn } from "./artifacts.ts";
67
70
  import { rewriteEmptyToolGroupsToAbsent } from "./tool-groups-empty-migration.ts";
68
71
  import { backfillProjectMemoryToolGroup } from "./project-memory-group-migration.ts";
72
+ import { backfillPlanToolsIntoToolIncludes } from "./plan-tools-include-migration.ts";
69
73
  import { removeLegacyProfileProjectInstructions } from "./profile-legacy-project-instructions-migration.ts";
70
74
  import { migrateProfileHostSources } from "./profile-host-sources-migration.ts";
71
75
  import { migrateProfileProjectAcks } from "./profile-project-ack-migration.ts";
@@ -169,7 +173,15 @@ export function applyMigrations(): void {
169
173
  )
170
174
  `);
171
175
  getDb().run("CREATE INDEX IF NOT EXISTS idx_issue_refs_external ON issue_refs(provider, repo, external_key)");
172
- getDb().run("CREATE INDEX IF NOT EXISTS idx_issue_refs_updated ON issue_refs(updated_at DESC)");
176
+ // #3799 — matches the listing ORDER BY exactly, so a list is a bounded index walk with no sort; supersedes (updated_at DESC).
177
+ getDb().run("CREATE INDEX IF NOT EXISTS idx_issue_refs_listing ON issue_refs(updated_at DESC, created_at DESC, id DESC)");
178
+ getDb().run("DROP INDEX IF EXISTS idx_issue_refs_updated");
179
+ // #3799 — automation_runs list/scan indexes: status+created_at supersedes the plain status index, created_at serves the
180
+ // unfiltered listing, and the partial index holds exactly the open task-bound runs the reconcile pass walks.
181
+ getDb().run("CREATE INDEX IF NOT EXISTS idx_automation_runs_status_created ON automation_runs(status, created_at)");
182
+ getDb().run("DROP INDEX IF EXISTS idx_automation_runs_status");
183
+ getDb().run("CREATE INDEX IF NOT EXISTS idx_automation_runs_created ON automation_runs(created_at DESC)");
184
+ getDb().run("CREATE INDEX IF NOT EXISTS idx_automation_runs_open ON automation_runs(created_at) WHERE status IN ('scheduled', 'dispatching', 'waiting_agent', 'running') AND task_id IS NOT NULL");
173
185
  // #2520 — the GitHub close classification (completed|not_planned) cached beside the status so
174
186
  // plan-node terminal propagation can tell a completed close from a not-planned one. The guarded
175
187
  // ALTER converges databases created before the column existed with the CREATE above.
@@ -666,6 +678,7 @@ export function applyMigrations(): void {
666
678
  ensureIngestedMemoryIndex();
667
679
  rewriteEmptyToolGroupsToAbsent(); // #1963 N2 — persisted relay.tools.groups:[] meant ALL; honoring [] as NONE requires rewriting existing [] → absent first
668
680
  backfillProjectMemoryToolGroup(); // #2154 follow-up — stored/overridden profiles do not pick up the built-in `project-memory` group on their own
681
+ backfillPlanToolsIntoToolIncludes(); // #3812 — lean per-tool allowlists (coordinator) never gained relay_plan_start/import
669
682
  removeLegacyProfileProjectInstructions(); // #2133 — remove retired profile policy fields before profile override migration reads legacy rows
670
683
  backfillCustomProfileArtifacts(); // #3171 — Artifact is off by default now; keep the approved custom brainstorm exception
671
684
  migrateAgentProfileOverridesToV2(); // #1963 N10 — back up + migrate persisted override rows to the v2 matrix (D-T=off; G6/G7 leave-untouched-and-report; flag-gated rollback)
@@ -1733,6 +1746,10 @@ export function applyMigrations(): void {
1733
1746
  getDb().run(AGENT_EXIT_REPORT_SCHEMA);
1734
1747
  getDb().run(CONTEXT_RESET_SCHEMA); // #3398
1735
1748
  getDb().run(TASK_TOKEN_TOTALS_SCHEMA);
1749
+ getDb().run(TOKEN_USAGE_RETENTION_SCHEMA);
1750
+ getDb().run(COMMAND_RETENTION_SCHEMA);
1751
+ migrateCheckoutRefreshHeads(getDb());
1752
+ ensureCommandRetentionReferenceIndexes(getDb());
1736
1753
  getDb().run(SHIPPING_STATS_SCHEMA);
1737
1754
  getDb().run(CI_GATE_BACKFILL_SCHEMA); getDb().run(AGENT_GATE_RUNS_SCHEMA); ensureAgentGateCommandSchema(); repairRerunGateTimings(); ensureCiShaObservationsSchema();
1738
1755
  migrateOperatorActionsSchema();
@@ -0,0 +1,19 @@
1
+ import { sendMessageWithResultInternal } from "./messages";
2
+ import { insertAgentNoticeSlot, type AgentNoticeSlot } from "./terminal-events";
3
+ import type { SendMessageInput } from "../types";
4
+ import type { WriterOpContext, WriterOpSpec } from "./write-ops";
5
+
6
+ const send = (inputs: SendMessageInput[], now: number) => inputs.map(input => sendMessageWithResultInternal(input, false, {}, now));
7
+ export const NOTIFICATION_WRITE_OPS: Readonly<Record<string, WriterOpSpec>> = Object.freeze({
8
+ "notifications.route": {
9
+ offLoop: true, idempotent: false,
10
+ fn: ({ inputs }: { inputs: SendMessageInput[] }, ctx: WriterOpContext) => send(inputs, ctx.now),
11
+ },
12
+ "notifications.agentNotice": {
13
+ offLoop: true, idempotent: false,
14
+ fn: ({ slot, inputs }: { slot: AgentNoticeSlot; inputs: SendMessageInput[] }, ctx: WriterOpContext) => {
15
+ const created = insertAgentNoticeSlot(slot, ctx.now);
16
+ return { created, results: created ? send(inputs, ctx.now) : [] };
17
+ },
18
+ },
19
+ });
@@ -0,0 +1,19 @@
1
+ import { answerOpenOperatorDecision, getOperatorDecision, getWorkflowOperatorDecision, insertOperatorDecision, setOperatorDecisionAnswerMessage } from "./operator-decisions";
2
+ import { sendMessageWithResultInternal } from "./messages";
3
+ import type { SendMessageInput } from "../types";
4
+ export interface OperatorAnswerWrite {
5
+ id: number;
6
+ answer: Parameters<typeof answerOpenOperatorDecision>[1];
7
+ message: SendMessageInput;
8
+ }
9
+ export function answerOperatorDecisionBody({ id, answer, message }: OperatorAnswerWrite) {
10
+ if (!answerOpenOperatorDecision(id, answer)) return null;
11
+ const result = sendMessageWithResultInternal(message, false, {}, answer.now);
12
+ setOperatorDecisionAnswerMessage(id, result.message.id);
13
+ return { decision: getOperatorDecision(id)!, result };
14
+ }
15
+
16
+ export function fileWorkflowDecisionBody(input: Parameters<typeof insertOperatorDecision>[0]) {
17
+ const existing = getWorkflowOperatorDecision(input.workflow!.effectId);
18
+ return existing ? { decision: existing, created: false } : { decision: insertOperatorDecision(input), created: true };
19
+ }
@@ -11,7 +11,7 @@ import {
11
11
  type RuntimeContracts,
12
12
  } from "../contracts";
13
13
  import { STALE_TTL_MS } from "../config";
14
- import { deleteAgent, heartbeat, upsertAgent, upsertAgentInternal, type UpsertAgentTxResult } from "./agents.ts";
14
+ import { deleteAgentInternal, heartbeat, upsertAgent, upsertAgentInternal, type UpsertAgentTxResult } from "./agents.ts";
15
15
  import { evictPendingPresence } from "./presence-buffer.ts";
16
16
  import { getDb, writeTransaction } from "./connection.ts";
17
17
  import { fencedColumnSql, nextLifecycleSeq } from "./lifecycle-seq.ts";
@@ -386,11 +386,17 @@ export function reportOrchestratorRosterRow(id: string, agents: ManagedAgent[],
386
386
  }
387
387
 
388
388
  export function deleteOrchestrator(id: string): boolean {
389
+ const result = deleteOrchestratorRow(id, Date.now());
390
+ if (result.agentId) evictPendingPresence(result.agentId);
391
+ return result.deleted;
392
+ }
393
+
394
+ export function deleteOrchestratorRow(id: string, now: number): { deleted: boolean; agentId?: string } {
389
395
  const orch = getOrchestrator(id);
390
- if (!orch) return false;
396
+ if (!orch) return { deleted: false };
391
397
  getDb().query("DELETE FROM orchestrators WHERE id = ?").run(id);
392
- deleteAgent(orch.agentId);
393
- return true;
398
+ deleteAgentInternal(orch.agentId, now);
399
+ return { deleted: true, agentId: orch.agentId };
394
400
  }
395
401
 
396
402
  export function hasStaleOrchestrator(cutoff: number): boolean {
@@ -1,5 +1,6 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import type { createPair, acceptPair, rejectPair, endPair, sendPairMessage, getPair, listPairs } from "./pairs";
1
3
  import { getGateway } from "./gateway";
2
- import type { getPair, listPairs } from "./pairs";
3
4
 
4
5
  export function getPairAsync(id: string): Promise<ReturnType<typeof getPair>> {
5
6
  return getGateway().write("pairs.getCurrent", { id }) as Promise<ReturnType<typeof getPair>>;
@@ -8,3 +9,23 @@ export function getPairAsync(id: string): Promise<ReturnType<typeof getPair>> {
8
9
  export function listPairsAsync(filter?: Parameters<typeof listPairs>[0]): Promise<ReturnType<typeof listPairs>> {
9
10
  return getGateway().write("pairs.listCurrent", filter) as unknown as Promise<ReturnType<typeof listPairs>>;
10
11
  }
12
+
13
+ export function createPairAsync(...args: Parameters<typeof createPair>): Promise<ReturnType<typeof createPair>> {
14
+ return getGateway().write("pairs.createPair", args, { idempotencyKey: randomUUID() }) as unknown as Promise<ReturnType<typeof createPair>>;
15
+ }
16
+
17
+ export function acceptPairAsync(...args: Parameters<typeof acceptPair>): Promise<ReturnType<typeof acceptPair>> {
18
+ return getGateway().write("pairs.acceptPair", args, { idempotencyKey: randomUUID() }) as unknown as Promise<ReturnType<typeof acceptPair>>;
19
+ }
20
+
21
+ export function rejectPairAsync(...args: Parameters<typeof rejectPair>): Promise<ReturnType<typeof rejectPair>> {
22
+ return getGateway().write("pairs.rejectPair", args, { idempotencyKey: randomUUID() }) as unknown as Promise<ReturnType<typeof rejectPair>>;
23
+ }
24
+
25
+ export function endPairAsync(...args: Parameters<typeof endPair>): Promise<ReturnType<typeof endPair>> {
26
+ return getGateway().write("pairs.endPair", args, { idempotencyKey: randomUUID() }) as unknown as Promise<ReturnType<typeof endPair>>;
27
+ }
28
+
29
+ export function sendPairMessageAsync(...args: Parameters<typeof sendPairMessage>): Promise<ReturnType<typeof sendPairMessage>> {
30
+ return getGateway().write("pairs.sendPairMessage", args, { idempotencyKey: randomUUID() }) as unknown as Promise<ReturnType<typeof sendPairMessage>>;
31
+ }
@@ -1,8 +1,13 @@
1
- import { getPair, listPairs } from "./pairs";
1
+ import { createPair, acceptPair, rejectPair, endPair, sendPairMessage, getPair, listPairs } from "./pairs";
2
2
  import type { WriterOpSpec } from "./write-ops";
3
3
 
4
4
  // Pair reads settle expired requests before returning the current view.
5
5
  export const PAIRS_WRITE_OPS: Readonly<Record<string, WriterOpSpec>> = {
6
- "pairs.getCurrent": { idempotent: true, offLoop: true, fn: (args: { id: string }) => getPair(args.id) },
7
- "pairs.listCurrent": { idempotent: true, offLoop: true, fn: (args: Parameters<typeof listPairs>[0]) => listPairs(args) },
6
+ "pairs.getCurrent": { idempotent: true, offLoop: true, fn: (args: { id: string }, ctx) => getPair(args.id, ctx.now) },
7
+ "pairs.listCurrent": { idempotent: true, offLoop: true, fn: (args: Parameters<typeof listPairs>[0], ctx) => listPairs(args, ctx.now) },
8
+ "pairs.createPair": { offLoop: true, idempotent: false, fn: (a: Parameters<typeof createPair>, ctx) => createPair(a[0], a[1] ?? ctx.now) },
9
+ "pairs.acceptPair": { offLoop: true, idempotent: false, fn: (a: Parameters<typeof acceptPair>, ctx) => acceptPair(a[0], a[1], a[2] ?? ctx.now) },
10
+ "pairs.rejectPair": { offLoop: true, idempotent: false, fn: (a: Parameters<typeof rejectPair>, ctx) => rejectPair(a[0], a[1], a[2] ?? ctx.now) },
11
+ "pairs.endPair": { offLoop: true, idempotent: false, fn: (a: Parameters<typeof endPair>, ctx) => endPair(a[0], a[1], a[2] ?? ctx.now) },
12
+ "pairs.sendPairMessage": { offLoop: true, idempotent: false, fn: (a: Parameters<typeof sendPairMessage>, ctx) => sendPairMessage(a[0], a[1], a[2] ?? ctx.now) },
8
13
  };
package/src/db/pairs.ts CHANGED
@@ -4,7 +4,7 @@ import { matchAgents } from "../agent-ref";
4
4
  import { getAgent, listAgents } from "./agents.ts";
5
5
  import { getDb } from "./connection.ts";
6
6
  import { rowToPair } from "./mappers.ts";
7
- import { sendMessage } from "./messages.ts";
7
+ import { sendMessageWithResultInternal } from "./messages.ts";
8
8
  export { closeOpenPairsForAgent } from "./pair-maintenance.ts";
9
9
  import type {
10
10
  AgentCard,
@@ -26,8 +26,8 @@ function expirePendingPairs(now: number = Date.now()): void {
26
26
  .run(now, now, now);
27
27
  }
28
28
 
29
- function getOpenPairForAgent(agentId: string): PairSession | null {
30
- expirePendingPairs();
29
+ function getOpenPairForAgent(agentId: string, now: number = Date.now()): PairSession | null {
30
+ expirePendingPairs(now);
31
31
  const row = getDb().query(`
32
32
  SELECT * FROM pairs
33
33
  WHERE status IN ('pending', 'active') AND (requester_id = ? OR target_id = ?)
@@ -45,7 +45,7 @@ function pairPeer(pair: PairSession, agentId: string): string {
45
45
  return pair.requesterId === agentId ? pair.targetId : pair.requesterId;
46
46
  }
47
47
 
48
- function resolvePairTarget(target: string, requesterId: string): {
48
+ function resolvePairTarget(target: string, requesterId: string, now: number = Date.now()): {
49
49
  ok: true;
50
50
  agent: AgentCard;
51
51
  } | {
@@ -64,7 +64,7 @@ function resolvePairTarget(target: string, requesterId: string): {
64
64
  const busy: Array<{ agent: AgentCard; pair: PairSession }> = [];
65
65
  const available: AgentCard[] = [];
66
66
  for (const agent of live) {
67
- const openPair = getOpenPairForAgent(agent.id);
67
+ const openPair = getOpenPairForAgent(agent.id, now);
68
68
  if (openPair) busy.push({ agent, pair: openPair });
69
69
  else available.push(agent);
70
70
  }
@@ -81,8 +81,8 @@ function resolvePairTarget(target: string, requesterId: string): {
81
81
  return { ok: true, agent: available[0]! };
82
82
  }
83
83
 
84
- function pairSystemMessage(pair: PairSession, to: string, event: string, subject: string, body: string): Message {
85
- return sendMessage({
84
+ function pairSystemMessage(pair: PairSession, to: string, event: string, subject: string, body: string, now: number = Date.now()): Message {
85
+ return sendMessageWithResultInternal({
86
86
  from: "system",
87
87
  to,
88
88
  kind: "pair",
@@ -95,17 +95,17 @@ function pairSystemMessage(pair: PairSession, to: string, event: string, subject
95
95
  targetId: pair.targetId,
96
96
  pairStatus: pair.status,
97
97
  },
98
- });
98
+ }, false, {}, now).message;
99
99
  }
100
100
 
101
- export function getPair(id: string): PairSession | null {
102
- expirePendingPairs();
101
+ export function getPair(id: string, now: number = Date.now()): PairSession | null {
102
+ expirePendingPairs(now);
103
103
  const row = getDb().query("SELECT * FROM pairs WHERE id = ?").get(id) as any;
104
104
  return row ? rowToPair(row) : null;
105
105
  }
106
106
 
107
- export function listPairs(filter?: { agentId?: string; status?: PairStatus }): PairSession[] {
108
- expirePendingPairs();
107
+ export function listPairs(filter?: { agentId?: string; status?: PairStatus }, now: number = Date.now()): PairSession[] {
108
+ expirePendingPairs(now);
109
109
  const conditions: string[] = [];
110
110
  const params: any[] = [];
111
111
  if (filter?.agentId) {
@@ -120,7 +120,7 @@ export function listPairs(filter?: { agentId?: string; status?: PairStatus }): P
120
120
  return (getDb().query(`SELECT * FROM pairs ${where} ORDER BY updated_at DESC LIMIT 100`).all(...params) as any[]).map(rowToPair);
121
121
  }
122
122
 
123
- export function createPair(input: CreatePairInput): {
123
+ export function createPair(input: CreatePairInput, now: number = Date.now()): {
124
124
  ok: true;
125
125
  pair: PairSession;
126
126
  invite: Message;
@@ -131,25 +131,23 @@ export function createPair(input: CreatePairInput): {
131
131
  matches?: AgentCard[];
132
132
  busy?: Array<{ agent: AgentCard; pair: PairSession }>;
133
133
  } {
134
- expirePendingPairs();
134
+ expirePendingPairs(now);
135
135
  const requester = getAgent(input.from);
136
136
  if (!requester) return { ok: false, code: "invalid", error: `requester agent ${input.from} not registered` };
137
137
  if (requester.status === "offline" || !requester.ready) return { ok: false, code: "offline", error: `requester agent ${input.from} is not online and ready` };
138
- const requesterPair = getOpenPairForAgent(input.from);
138
+ const requesterPair = getOpenPairForAgent(input.from, now);
139
139
  if (requesterPair) return { ok: false, code: "busy", error: `requester is already paired`, busy: [{ agent: requester, pair: requesterPair }] };
140
140
 
141
- const resolved = resolvePairTarget(input.target, input.from);
141
+ const resolved = resolvePairTarget(input.target, input.from, now);
142
142
  if (!resolved.ok) return resolved;
143
143
  if (resolved.agent.id === input.from) return { ok: false, code: "invalid", error: "cannot pair with yourself" };
144
-
145
- const now = Date.now();
146
144
  const ttlMs = Math.min(Math.max(input.ttlMs ?? DEFAULT_PAIR_TTL_MS, 10_000), MAX_PAIR_TTL_MS);
147
145
  const id = randomUUID();
148
146
  getDb().query(`
149
147
  INSERT INTO pairs (id, requester_id, target_id, status, objective, meta, created_at, updated_at, expires_at)
150
148
  VALUES (?, ?, ?, 'pending', ?, ?, ?, ?, ?)
151
149
  `).run(id, input.from, resolved.agent.id, input.objective ?? null, JSON.stringify(input.meta ?? {}), now, now, now + ttlMs);
152
- const pair = getPair(id)!;
150
+ const pair = getPair(id, now)!;
153
151
  const objective = pair.objective ? `\n\nObjective:\n${pair.objective}` : "";
154
152
  const invite = pairSystemMessage(
155
153
  pair,
@@ -163,14 +161,14 @@ export function createPair(input: CreatePairInput): {
163
161
  "Accept with POST /api/pairs/{id}/accept using your agentId.",
164
162
  "Reject with POST /api/pairs/{id}/reject using your agentId.",
165
163
  "Pairing is exclusive: each agent can be in at most one pending or active pair.",
166
- ].join("\n"),
164
+ ].join("\n"), now
167
165
  );
168
166
  return { ok: true, pair, invite };
169
167
  }
170
168
 
171
- export function acceptPair(id: string, input: PairActionInput): { ok: true; pair: PairSession; notices: Message[] } | { ok: false; error: string; code: "not_found" | "forbidden" | "busy" | "invalid" } {
172
- expirePendingPairs();
173
- const pair = getPair(id);
169
+ export function acceptPair(id: string, input: PairActionInput, now: number = Date.now()): { ok: true; pair: PairSession; notices: Message[] } | { ok: false; error: string; code: "not_found" | "forbidden" | "busy" | "invalid" } {
170
+ expirePendingPairs(now);
171
+ const pair = getPair(id, now);
174
172
  if (!pair) return { ok: false, code: "not_found", error: "pair not found" };
175
173
  if (pair.status !== "pending") return { ok: false, code: "invalid", error: `pair is ${pair.status}` };
176
174
  if (pair.targetId !== input.agentId) return { ok: false, code: "forbidden", error: "only the target agent can accept this pair" };
@@ -180,61 +178,56 @@ export function acceptPair(id: string, input: PairActionInput): { ok: true; pair
180
178
  if (!requester || requester.status === "offline" || !requester.ready) return { ok: false, code: "invalid", error: "requester agent is no longer online and ready" };
181
179
 
182
180
  for (const agentId of [pair.requesterId, pair.targetId]) {
183
- const open = getOpenPairForAgent(agentId);
181
+ const open = getOpenPairForAgent(agentId, now);
184
182
  if (open && open.id !== pair.id) return { ok: false, code: "busy", error: `${agentId} is already paired` };
185
183
  }
186
-
187
- const now = Date.now();
188
184
  getDb().query("UPDATE pairs SET status = 'active', accepted_at = ?, updated_at = ? WHERE id = ? AND status = 'pending'")
189
185
  .run(now, now, id);
190
- const active = getPair(id)!;
186
+ const active = getPair(id, now)!;
191
187
  const notices = [
192
- pairSystemMessage(active, active.requesterId, "accepted", `Pair accepted by ${active.targetId}`, `Pair ${active.id} is active. Send pair messages with POST /api/pairs/${active.id}/messages.`),
193
- pairSystemMessage(active, active.targetId, "accepted", `Pair active with ${active.requesterId}`, `Pair ${active.id} is active. Send pair messages with POST /api/pairs/${active.id}/messages.`),
188
+ pairSystemMessage(active, active.requesterId, "accepted", `Pair accepted by ${active.targetId}`, `Pair ${active.id} is active. Send pair messages with POST /api/pairs/${active.id}/messages.`, now),
189
+ pairSystemMessage(active, active.targetId, "accepted", `Pair active with ${active.requesterId}`, `Pair ${active.id} is active. Send pair messages with POST /api/pairs/${active.id}/messages.`, now),
194
190
  ];
195
191
  return { ok: true, pair: active, notices };
196
192
  }
197
193
 
198
- export function rejectPair(id: string, input: PairActionInput): { ok: true; pair: PairSession; notice: Message } | { ok: false; error: string; code: "not_found" | "forbidden" | "invalid" } {
199
- expirePendingPairs();
200
- const pair = getPair(id);
194
+ export function rejectPair(id: string, input: PairActionInput, now: number = Date.now()): { ok: true; pair: PairSession; notice: Message } | { ok: false; error: string; code: "not_found" | "forbidden" | "invalid" } {
195
+ expirePendingPairs(now);
196
+ const pair = getPair(id, now);
201
197
  if (!pair) return { ok: false, code: "not_found", error: "pair not found" };
202
198
  if (pair.status !== "pending") return { ok: false, code: "invalid", error: `pair is ${pair.status}` };
203
199
  if (pair.targetId !== input.agentId) return { ok: false, code: "forbidden", error: "only the target agent can reject this pair" };
204
- const now = Date.now();
205
200
  getDb().query("UPDATE pairs SET status = 'rejected', ended_at = ?, ended_by = ?, updated_at = ? WHERE id = ?")
206
201
  .run(now, input.agentId, now, id);
207
- const rejected = getPair(id)!;
202
+ const rejected = getPair(id, now)!;
208
203
  const reason = input.reason ? `\n\nReason:\n${input.reason}` : "";
209
- const notice = pairSystemMessage(rejected, rejected.requesterId, "rejected", `Pair rejected by ${input.agentId}`, `Pair ${rejected.id} was rejected.${reason}`);
204
+ const notice = pairSystemMessage(rejected, rejected.requesterId, "rejected", `Pair rejected by ${input.agentId}`, `Pair ${rejected.id} was rejected.${reason}`, now);
210
205
  return { ok: true, pair: rejected, notice };
211
206
  }
212
207
 
213
- export function endPair(id: string, input: PairActionInput): { ok: true; pair: PairSession; notice?: Message } | { ok: false; error: string; code: "not_found" | "forbidden" | "invalid" } {
214
- expirePendingPairs();
215
- const pair = getPair(id);
208
+ export function endPair(id: string, input: PairActionInput, now: number = Date.now()): { ok: true; pair: PairSession; notice?: Message } | { ok: false; error: string; code: "not_found" | "forbidden" | "invalid" } {
209
+ expirePendingPairs(now);
210
+ const pair = getPair(id, now);
216
211
  if (!pair) return { ok: false, code: "not_found", error: "pair not found" };
217
212
  if (!pairParticipant(pair, input.agentId)) return { ok: false, code: "forbidden", error: "only pair participants can hang up" };
218
213
  if (!OPEN_PAIR_STATUSES.includes(pair.status as any)) return { ok: false, code: "invalid", error: `pair is ${pair.status}` };
219
- const now = Date.now();
220
214
  getDb().query("UPDATE pairs SET status = 'ended', ended_at = ?, ended_by = ?, updated_at = ? WHERE id = ?")
221
215
  .run(now, input.agentId, now, id);
222
- const ended = getPair(id)!;
216
+ const ended = getPair(id, now)!;
223
217
  const reason = input.reason ? `\n\nReason:\n${input.reason}` : "";
224
218
  const peer = pairPeer(ended, input.agentId);
225
- const notice = pairSystemMessage(ended, peer, "ended", `Pair ended by ${input.agentId}`, `Pair ${ended.id} ended.${reason}`);
219
+ const notice = pairSystemMessage(ended, peer, "ended", `Pair ended by ${input.agentId}`, `Pair ${ended.id} ended.${reason}`, now);
226
220
  return { ok: true, pair: ended, notice };
227
221
  }
228
222
 
229
- export function sendPairMessage(id: string, input: PairMessageInput): { ok: true; pair: PairSession; message: Message } | { ok: false; error: string; code: "not_found" | "forbidden" | "invalid" } {
230
- expirePendingPairs();
231
- const pair = getPair(id);
223
+ export function sendPairMessage(id: string, input: PairMessageInput, now: number = Date.now()): { ok: true; pair: PairSession; message: Message } | { ok: false; error: string; code: "not_found" | "forbidden" | "invalid" } {
224
+ expirePendingPairs(now);
225
+ const pair = getPair(id, now);
232
226
  if (!pair) return { ok: false, code: "not_found", error: "pair not found" };
233
227
  if (pair.status !== "active") return { ok: false, code: "invalid", error: `pair is ${pair.status}` };
234
228
  if (!pairParticipant(pair, input.from)) return { ok: false, code: "forbidden", error: "only pair participants can send pair messages" };
235
229
  const to = pairPeer(pair, input.from);
236
- const now = Date.now();
237
- const message = sendMessage({
230
+ const message = sendMessageWithResultInternal({
238
231
  from: input.from,
239
232
  to,
240
233
  kind: "pair",
@@ -246,7 +239,7 @@ export function sendPairMessage(id: string, input: PairMessageInput): { ok: true
246
239
  requesterId: pair.requesterId,
247
240
  targetId: pair.targetId,
248
241
  },
249
- });
242
+ }, false, {}, now).message;
250
243
  getDb().query("UPDATE pairs SET last_message_at = ?, updated_at = ? WHERE id = ?").run(now, now, id);
251
- return { ok: true, pair: getPair(id)!, message };
244
+ return { ok: true, pair: getPair(id, now)!, message };
252
245
  }
@@ -1,4 +1,4 @@
1
- // #3660 — plan autopilot tables (see plan-dispatch-store.ts); dependency-free so the startup catalog can include it.
1
+ // #3660 — plan autopilot tables (see plan-dispatch-store.ts) and ready-node notice state (plan-ready-notices.ts); dependency-free so the startup catalog can include it.
2
2
  export const PLAN_DISPATCH_SCHEMA_SQL = `
3
3
  CREATE TABLE IF NOT EXISTS task_executions (task_id INTEGER PRIMARY KEY, execution TEXT NOT NULL, updated_at INTEGER NOT NULL);
4
4
  CREATE TABLE IF NOT EXISTS plan_dispatch (
@@ -14,4 +14,8 @@ export const PLAN_DISPATCH_SCHEMA_SQL = `
14
14
  CREATE TABLE IF NOT EXISTS plan_ci_blocks (
15
15
  run_id TEXT PRIMARY KEY, plan_id TEXT NOT NULL, task_id INTEGER NOT NULL, sha TEXT NOT NULL,
16
16
  incident_ids TEXT NOT NULL, wait_key TEXT NOT NULL, retry_note TEXT NOT NULL, retried_at INTEGER
17
+ );
18
+ CREATE TABLE IF NOT EXISTS plan_ready_notices (
19
+ plan_id TEXT NOT NULL, node_id TEXT NOT NULL, ready_since INTEGER NOT NULL, notified_at INTEGER,
20
+ PRIMARY KEY (plan_id, node_id)
17
21
  )`;
@@ -0,0 +1,43 @@
1
+ // #3660 — durable "ready since / already told" state per plan node, so a hub restart neither
2
+ // loses the unstarted-ready clock nor repeats a notice. A row exists exactly while the node is
3
+ // ready-and-unstarted; leaving ready deletes it, so re-entering ready starts a fresh clock.
4
+ import { getDb } from "./connection.ts";
5
+
6
+ export interface ReadyPlanScan { planId: string; nodeIds: string[] }
7
+ export interface DueReadyNodes { planId: string; nodes: Array<{ nodeId: string; readySince: number }> }
8
+
9
+ /** Reconcile every scanned plan's ready set; plans absent from `scans` (archived, done, deleted,
10
+ * coordinator-less) lose their rows. Returns the nodes ready for >= `dueMs` and not yet notified. */
11
+ export function syncPlanReadyNotices(args: { scans: ReadyPlanScan[]; now: number; dueMs: number }): DueReadyNodes[] {
12
+ const db = getDb();
13
+ const scanned = new Set(args.scans.map(scan => scan.planId));
14
+ for (const { plan_id } of db.query("SELECT DISTINCT plan_id FROM plan_ready_notices").all() as Array<{ plan_id: string }>) {
15
+ if (!scanned.has(plan_id)) db.query("DELETE FROM plan_ready_notices WHERE plan_id = ?").run(plan_id);
16
+ }
17
+ const due: DueReadyNodes[] = [];
18
+ for (const scan of args.scans) {
19
+ const keep = new Set(scan.nodeIds);
20
+ const rows = db.query("SELECT node_id, ready_since, notified_at FROM plan_ready_notices WHERE plan_id = ?").all(scan.planId) as
21
+ Array<{ node_id: string; ready_since: number; notified_at: number | null }>;
22
+ const nodes: DueReadyNodes["nodes"] = [];
23
+ const known = new Set<string>();
24
+ for (const row of rows) {
25
+ if (!keep.has(row.node_id)) { db.query("DELETE FROM plan_ready_notices WHERE plan_id = ? AND node_id = ?").run(scan.planId, row.node_id); continue; }
26
+ known.add(row.node_id);
27
+ if (row.notified_at === null && row.ready_since <= args.now - args.dueMs) nodes.push({ nodeId: row.node_id, readySince: row.ready_since });
28
+ }
29
+ for (const nodeId of keep) if (!known.has(nodeId)) db.query("INSERT INTO plan_ready_notices(plan_id, node_id, ready_since) VALUES(?, ?, ?)").run(scan.planId, nodeId, args.now);
30
+ if (nodes.length) due.push({ planId: scan.planId, nodes });
31
+ }
32
+ return due;
33
+ }
34
+
35
+ export function markPlanReadyNoticed(args: { planId: string; nodeIds: string[]; now: number }): void {
36
+ const stmt = getDb().query("UPDATE plan_ready_notices SET notified_at = ? WHERE plan_id = ? AND node_id = ? AND notified_at IS NULL");
37
+ for (const nodeId of args.nodeIds) stmt.run(args.now, args.planId, nodeId);
38
+ }
39
+
40
+ export const PLAN_READY_NOTICE_WRITE_OPS = {
41
+ "plans.readyNotices.sync": { offLoop: true, idempotent: true, fn: syncPlanReadyNotices },
42
+ "plans.readyNotices.mark": { offLoop: true, idempotent: true, fn: markPlanReadyNoticed },
43
+ };
@@ -0,0 +1,36 @@
1
+ // #3812 — backfill relay_plan_start/import into stored per-tool allowlists (the lean coordinator
2
+ // from #3472 froze before they existed, so tools/list never carried them).
3
+ // Narrow: only an include list already holding relay_plan_get, and not excluding the tool.
4
+ // json_insert, not parse+reserialize (#1456); patches `$.relay.tools` and its `sources` mirror, and
5
+ // the -v1-backup namespace, whose bytes are served while the matrix flag is off.
6
+ import { getDb } from "./connection.ts";
7
+ import { runMigrationOnce } from "./migration-marker.ts";
8
+
9
+ const PROFILE_NAMESPACES = ["agent-profile", "agent-profile-override", "agent-profile-override-v1-backup"] as const;
10
+ const SELECTION_PATHS = ["$.relay.tools", "$.sources.agentRelay.mcp"] as const;
11
+ const BACKFILLED_TOOLS = ["relay_plan_start", "relay_plan_import"] as const;
12
+ const EVIDENCE_TOOL = "relay_plan_get";
13
+
14
+ export function backfillPlanToolsIntoToolIncludes(): void {
15
+ runMigrationOnce("plan-tools-include-backfill-v1", () => {
16
+ const db = getDb();
17
+ const namespaces = PROFILE_NAMESPACES.map(() => "?").join(", ");
18
+ // The outer table is aliased: inside the subqueries a bare `value` binds to json_each.value.
19
+ const holds = (path: string, field: "include" | "exclude", tool: string) =>
20
+ `EXISTS (SELECT 1 FROM json_each(c.value, '${path}.${field}') je WHERE je.value = '${tool}')`;
21
+ db.transaction(() => {
22
+ for (const path of SELECTION_PATHS) {
23
+ for (const tool of BACKFILLED_TOOLS) {
24
+ db.query(
25
+ `UPDATE config AS c SET value = json_insert(c.value, '${path}.include[#]', ?)
26
+ WHERE c.namespace IN (${namespaces}) AND json_valid(c.value)
27
+ AND json_type(c.value, '${path}.include') = 'array'
28
+ AND ${holds(path, "include", EVIDENCE_TOOL)}
29
+ AND NOT ${holds(path, "include", tool)}
30
+ AND NOT ${holds(path, "exclude", tool)}`,
31
+ ).run(tool, ...PROFILE_NAMESPACES);
32
+ }
33
+ }
34
+ })();
35
+ });
36
+ }