@humwick/server 0.130.2 → 0.130.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (587) hide show
  1. package/bundled/prompts/manifest.json +1 -0
  2. package/bundled/prompts/task.hydration.md +1 -1
  3. package/bundled/roles/builder.md +1 -1
  4. package/docs/guides/sub-coordinators.md +12 -5
  5. package/docs/guides/tools-coordination.md +1 -0
  6. package/docs/guides/workflows.md +16 -0
  7. package/docs/openapi.json +175 -4
  8. package/package.json +8 -7
  9. package/public/assets/{MessageBubble-BWLqMGKG.js → MessageBubble-BOkmEvgZ.js} +2 -2
  10. package/public/assets/{PlanGraphPanel-3TlIQpjk.js → PlanGraphPanel-BkaDJp0r.js} +2 -2
  11. package/public/assets/{TraceView-Wimm0Czv.js → TraceView-B99Uu2yT.js} +2 -2
  12. package/public/assets/{VaultDecisionCard-Bau_vNax.js → VaultDecisionCard-DUIfTTFF.js} +2 -2
  13. package/public/assets/{activity-CrSurn-r.js → activity-ZIX0N1El.js} +2 -2
  14. package/public/assets/{agent-lineage-CknAJKcb.js → agent-lineage-kq9TPYFw.js} +2 -2
  15. package/public/assets/agent-liveness-DSQY1Aa_.js +2 -0
  16. package/public/assets/{agent-name-DKbiIhBo.js → agent-name-C_7F4DFR.js} +2 -2
  17. package/public/assets/{agent-profiles-DwF6EQ4R.js → agent-profiles-BNOcrGcY.js} +2 -2
  18. package/public/assets/{agent-project-badge-Diz4Sjjb.js → agent-project-badge-B6yAh70a.js} +2 -2
  19. package/public/assets/{agent-tools-CRhpRQza.js → agent-tools-CcrhmOzF.js} +2 -2
  20. package/public/assets/{agent-type-icon-LrUEU3lB.js → agent-type-icon-CXSLrKiq.js} +2 -2
  21. package/public/assets/{agents-B-v66C-4.js → agents-DIZG97_X.js} +2 -2
  22. package/public/assets/{analytics-BqCb-LKi.js → analytics-rasmoPjR.js} +2 -2
  23. package/public/assets/{artifact-viewer-kf4pGuAR.js → artifact-viewer-tBpTrEPb.js} +2 -2
  24. package/public/assets/{artifacts-DJevXcvg.js → artifacts-BNkrynP1.js} +2 -2
  25. package/public/assets/{artifacts-D32zyw4Y.js → artifacts-Bjq-ZyBA.js} +2 -2
  26. package/public/assets/{automation-BpRajx_5.js → automation-Cx422L3L.js} +2 -2
  27. package/public/assets/{backlog-D_UjTdzX.js → backlog-RuFQ8PUu.js} +2 -2
  28. package/public/assets/{busy-health-N3VEh4ao.js → busy-health-BvNQ9g5x.js} +2 -2
  29. package/public/assets/{channels-9jopungq.js → channels-DNbKAS0I.js} +2 -2
  30. package/public/assets/{chat-CSXrTB3C.js → chat-Bp3Fx73h.js} +3 -3
  31. package/public/assets/{code-preview-p1NnmqK0.js → code-preview-KIRZA6cz.js} +2 -2
  32. package/public/assets/{connectors-CZdqlDNL.js → connectors-k7vO4vvc.js} +2 -2
  33. package/public/assets/{cosmos-BgdsE4dG.js → cosmos-BuVoq1aw.js} +2 -2
  34. package/public/assets/{crate-agent-30aXTKva.js → crate-agent-irqlt4NH.js} +2 -2
  35. package/public/assets/{dependency-graph-B_jvw2GE.js → dependency-graph-D_rurNBz.js} +2 -2
  36. package/public/assets/{deploys-DoN6bAb5.js → deploys-yzZqIpxE.js} +2 -2
  37. package/public/assets/{display-BrKWFGJz.js → display-BemJG1f_.js} +2 -2
  38. package/public/assets/{files-D6XEu8Ng.js → files-DXWcrxOz.js} +2 -2
  39. package/public/assets/{fleet-C7jQWbw2.js → fleet-TEuOCZXE.js} +2 -2
  40. package/public/assets/{fleet-metrics-BiDs3MLx.js → fleet-metrics-BGoDAFI-.js} +2 -2
  41. package/public/assets/{formatted-body-impl-DyG0WsQS.js → formatted-body-impl-BB9eq9nN.js} +2 -2
  42. package/public/assets/{formatted-body-CQBwc1fb.js → formatted-body-qy2nTD_1.js} +3 -3
  43. package/public/assets/{host-providers-dialog-DKaW31dN.js → host-providers-dialog-BGPHxuwT.js} +2 -2
  44. package/public/assets/{index-CRHep3M9.js → index-BC5XZc0z.js} +5 -5
  45. package/public/assets/{index-MYAIwYTX.css → index-D8Rp9pz2.css} +1 -1
  46. package/public/assets/{insights-CzrtkpOE.js → insights-C-KQG0h-.js} +2 -2
  47. package/public/assets/{integrations-aVsUmigt.js → integrations-ClPRPyKU.js} +2 -2
  48. package/public/assets/{issue-ref-chip-BnSlM_95.js → issue-ref-chip-DbH8J9Ak.js} +2 -2
  49. package/public/assets/{log-viewer-qe7Ee47Z.js → log-viewer-PH4zVn24.js} +2 -2
  50. package/public/assets/{maintenance-NfjqFZAk.js → maintenance-5lV43uTL.js} +2 -2
  51. package/public/assets/{managed-agents-BiH0HgaE.js → managed-agents-BdgU35aX.js} +2 -2
  52. package/public/assets/{markdown-preview-BixaT3Xr.js → markdown-preview-DNT4OUwQ.js} +3 -3
  53. package/public/assets/{markdown-preview-impl-DNGlWIS3.js → markdown-preview-impl-CnCcPy0A.js} +2 -2
  54. package/public/assets/{memory-DNsCl6UX.js → memory--GgWH1gH.js} +2 -2
  55. package/public/assets/{messages-DopU1L1X.js → messages-CzFl_97O.js} +2 -2
  56. package/public/assets/{orchestrators-Cq7ckLQT.js → orchestrators-CVvJVkzv.js} +2 -2
  57. package/public/assets/overview-DsrZvE86.js +2 -0
  58. package/public/assets/{pairs-DE_m6rrE.js → pairs-D2d0d2py.js} +2 -2
  59. package/public/assets/{profile-icon-B3rfh77F.js → profile-icon-_SMuSbYF.js} +2 -2
  60. package/public/assets/{project-profile-instructions-DQWZE0kA.js → project-profile-instructions-Apn15y43.js} +2 -2
  61. package/public/assets/{projects-Br2Bd2h1.js → projects-D3B6kto5.js} +2 -2
  62. package/public/assets/{prompt-settings-BKvFsvGD.js → prompt-settings-DnPB6Ez6.js} +2 -2
  63. package/public/assets/provider-display-bWwiuedV.js +2 -0
  64. package/public/assets/{registry-Cw38_6gW.js → registry-D790cIhN.js} +2 -2
  65. package/public/assets/run-card-BCro-Odj.js +2 -0
  66. package/public/assets/{secrets-DxI-vzSq.js → secrets-CVXvfvbU.js} +2 -2
  67. package/public/assets/{service-access-settings-D9s_VJbd.js → service-access-settings-D3Xxqb9w.js} +2 -2
  68. package/public/assets/{settings-CKZTVGIx.js → settings-CoUC7Jci.js} +2 -2
  69. package/public/assets/shipping-D_Lp9n_J.js +2 -0
  70. package/public/assets/{stats-DyUBKyWI.js → stats-CXiNjB9S.js} +2 -2
  71. package/public/assets/{status-dot-DYqlEUhJ.js → status-dot-C703YIy1.js} +2 -2
  72. package/public/assets/store-BgDALGIX.js +9 -0
  73. package/public/assets/{tasks-Bh7Chr4-.js → tasks-DO0WpJCx.js} +2 -2
  74. package/public/assets/{teams-bowI7qVY.js → teams-FGkdi_tL.js} +2 -2
  75. package/public/assets/{terminal-viewer-BgVLHdsU.js → terminal-viewer-Dh8dSC7e.js} +3 -3
  76. package/public/assets/{terminal-viewer-impl-Dz22UPy6.js → terminal-viewer-impl-B5Zriip3.js} +2 -2
  77. package/public/assets/{use-agent-terminal-DSQQ-iY7.js → use-agent-terminal-CRUVDvLC.js} +2 -2
  78. package/public/assets/{use-decisions-samMQhQf.js → use-decisions-CoaqRh4l.js} +2 -2
  79. package/public/assets/{use-project-list-59LheVP7.js → use-project-list-X-BgT53X.js} +2 -2
  80. package/public/assets/{use-selectors-BZa25xEB.js → use-selectors-0X_UZkk-.js} +2 -2
  81. package/public/assets/{user-avatar-DHxlvUvV.js → user-avatar-CkLhVBGu.js} +2 -2
  82. package/public/assets/{versioned-prompt-editor-BmED5DnJ.js → versioned-prompt-editor-BkTi7eim.js} +2 -2
  83. package/public/assets/{work-queue-CSvpuNLz.js → work-queue-DDOLEyKo.js} +2 -2
  84. package/public/assets/{workflow-marker-DAZcj5qt.js → workflow-marker-CzOsB8BM.js} +2 -2
  85. package/public/assets/{workflows-DdmxRSyg.js → workflows-DJilvmRM.js} +2 -2
  86. package/public/assets/{workspaces-BxeKfVuF.js → workspaces-CHcfbLR3.js} +2 -2
  87. package/public/cosmos.html +4 -4
  88. package/public/index.html +29 -29
  89. package/runner/plugins/claude/.claude-plugin/plugin.json +1 -1
  90. package/runner/src/adapters/claude-api.ts +1 -1
  91. package/runner/src/adapters/claude-headless.ts +1 -1
  92. package/runner/src/adapters/claude-launch-exit.ts +26 -0
  93. package/runner/src/adapters/claude-session-probe.ts +1 -1
  94. package/runner/src/adapters/claude-tmux.ts +38 -10
  95. package/runner/src/adapters/claude.ts +28 -75
  96. package/runner/src/adapters/provider-quota-harvest.ts +20 -71
  97. package/runner/src/agent-runner/lifecycle-milestones.ts +2 -2
  98. package/runner/src/agent-runner/liveness-exchange.ts +104 -0
  99. package/runner/src/claude-system-prompt.ts +49 -0
  100. package/runner/src/launch-assembly.ts +14 -10
  101. package/runner/src/profile-home.ts +1 -1
  102. package/runner/src/project-instructions.ts +2 -2
  103. package/runner/src/provider-command.ts +18 -0
  104. package/runner/src/provider-setup.ts +3 -2
  105. package/runner/src/runner-core.ts +14 -15
  106. package/runner/src/runner-helpers.ts +0 -8
  107. package/runner/src/session-scratch.ts +6 -2
  108. package/runner/test-support/provisioning-context.ts +4 -2
  109. package/runner/test-support/runner-harness.ts +2 -1
  110. package/scripts/steward-policy-cleanup.ts +2 -2
  111. package/src/agent-authz.ts +2 -2
  112. package/src/agent-drive-arbiter.ts +3 -8
  113. package/src/agent-exit-cleanup-async.ts +2 -2
  114. package/src/agent-exit-cleanup.ts +23 -19
  115. package/src/agent-lifecycle-events.ts +112 -85
  116. package/src/agent-ref.ts +3 -2
  117. package/src/agent-status-events.ts +43 -14
  118. package/src/auth/login-link.ts +35 -0
  119. package/src/auth/password.ts +4 -3
  120. package/src/auth/session-refresh.ts +1 -1
  121. package/src/auth/session.test-fixture.ts +2 -2
  122. package/src/auth/session.ts +7 -7
  123. package/src/automations/crud.ts +8 -81
  124. package/src/automations/dispatch.ts +33 -58
  125. package/src/automations/index.ts +1 -1
  126. package/src/automations/persistence.ts +65 -0
  127. package/src/automations/precheck.ts +18 -16
  128. package/src/automations/read-async.ts +11 -0
  129. package/src/automations/reconcile.ts +30 -39
  130. package/src/automations/storage.ts +33 -10
  131. package/src/automations/write-async.ts +33 -0
  132. package/src/branch-land-notice.ts +3 -3
  133. package/src/branch-landed.ts +25 -23
  134. package/src/bus.ts +21 -19
  135. package/src/cd-deploy-notify.ts +2 -2
  136. package/src/ci/notifications.ts +4 -4
  137. package/src/ci/root-missing.ts +3 -3
  138. package/src/ci/transition-notify.ts +5 -5
  139. package/src/claude-app/oauth-http.ts +3 -3
  140. package/src/cli/index.ts +7 -1
  141. package/src/cli/init.ts +53 -0
  142. package/src/cli/orchestrator.ts +1 -7
  143. package/src/cli/setup.ts +6 -4
  144. package/src/cli/workflow.ts +12 -3
  145. package/src/commands-db.ts +4 -4
  146. package/src/commands-write.ts +8 -0
  147. package/src/compaction-watch.ts +10 -8
  148. package/src/config-store-write.ts +2 -2
  149. package/src/config.ts +4 -11
  150. package/src/connectors.ts +10 -8
  151. package/src/context-advisory.ts +22 -21
  152. package/src/db/agent-action-write.ts +36 -0
  153. package/src/db/agent-control-async.ts +9 -0
  154. package/src/db/agent-control-write.ts +49 -0
  155. package/src/db/agent-effort-observe.ts +22 -0
  156. package/src/db/agent-meta.ts +14 -3
  157. package/src/db/agents-async.ts +8 -5
  158. package/src/db/agents.ts +15 -12
  159. package/src/db/auth-identities.ts +2 -2
  160. package/src/db/automation-read-ops.ts +11 -0
  161. package/src/db/automation-write-ops.ts +46 -0
  162. package/src/db/blackboard.ts +2 -2
  163. package/src/db/browser-session-write.ts +32 -0
  164. package/src/db/bus-outbox-write-ops.ts +3 -1
  165. package/src/db/checkout-refresh-schema.ts +47 -0
  166. package/src/db/checkout-refresh.ts +5 -5
  167. package/src/db/command-agent-profile-backfill.ts +39 -0
  168. package/src/db/command-agent-profiles.ts +5 -1
  169. package/src/db/command-retention.ts +126 -0
  170. package/src/db/commands-planner-stats.test-fixture.ts +8 -0
  171. package/src/db/commands-read-ops.ts +16 -0
  172. package/src/db/connection.ts +1 -182
  173. package/src/db/context-carry-write.ts +74 -0
  174. package/src/db/continuations.ts +4 -4
  175. package/src/db/db-error-registry.ts +12 -0
  176. package/src/db/gateway.ts +26 -133
  177. package/src/db/history-retention-ops.ts +13 -0
  178. package/src/db/inbox-async.ts +2 -2
  179. package/src/db/inbox-read-ops.ts +1 -1
  180. package/src/db/inbox.ts +9 -3
  181. package/src/db/issue-associations.ts +2 -2
  182. package/src/db/issue-refs-async.ts +4 -1
  183. package/src/db/issue-refs-read-ops.ts +6 -0
  184. package/src/db/issue-refs.ts +8 -8
  185. package/src/db/kernel-event-log-schema.ts +2 -0
  186. package/src/db/land-boundaries.ts +51 -5
  187. package/src/db/land-gate-attestations.ts +2 -2
  188. package/src/db/landing-receipts.ts +2 -2
  189. package/src/db/lifecycle-context-write.ts +5 -0
  190. package/src/db/lifecycle-tail-async.ts +76 -0
  191. package/src/db/lifecycle-tail-write-ops.ts +62 -0
  192. package/src/db/lifecycle-write-window.ts +2 -1
  193. package/src/db/lineage-pair-dedupe-sql.ts +3 -3
  194. package/src/db/main-connection-mode.ts +11 -0
  195. package/src/db/main-connection-writes.ts +20 -2
  196. package/src/db/maintenance-job-runs-async.ts +2 -2
  197. package/src/db/maintenance-job-write-ops.ts +1 -1
  198. package/src/db/maintenance-pauses.ts +2 -2
  199. package/src/db/message-send.ts +4 -0
  200. package/src/db/messages.ts +31 -25
  201. package/src/db/migrations.ts +18 -1
  202. package/src/db/notification-write-ops.ts +19 -0
  203. package/src/db/operator-answer-write.ts +19 -0
  204. package/src/db/orchestrators.ts +10 -4
  205. package/src/db/pairs-async.ts +22 -1
  206. package/src/db/pairs-write-ops.ts +8 -3
  207. package/src/db/pairs.ts +41 -48
  208. package/src/db/plan-dispatch-schema.ts +5 -1
  209. package/src/db/plan-ready-notices.ts +43 -0
  210. package/src/db/plan-tools-include-migration.ts +36 -0
  211. package/src/db/plans-write-ops.ts +2 -0
  212. package/src/db/presence-buffer.ts +5 -0
  213. package/src/db/presence-loop.ts +18 -23
  214. package/src/db/presence-write.ts +0 -6
  215. package/src/db/project-entries-async.ts +14 -0
  216. package/src/db/project-entries-read-ops.ts +5 -1
  217. package/src/db/project-entries-write-ops.ts +8 -0
  218. package/src/db/project-entries.ts +15 -12
  219. package/src/db/project-journal.ts +2 -2
  220. package/src/db/project-trust-approvals.ts +2 -2
  221. package/src/db/prompt-templates.ts +4 -4
  222. package/src/db/read-op-marshaling-fixtures.ts +18 -0
  223. package/src/db/read-ops.ts +4 -0
  224. package/src/db/retention.ts +13 -2
  225. package/src/db/review-publish-write.ts +8 -0
  226. package/src/db/runtime-admin-async.ts +19 -0
  227. package/src/db/runtime-admin-write-ops.ts +36 -0
  228. package/src/db/runtime-tail-async.ts +288 -0
  229. package/src/db/runtime-tail-write-ops.ts +97 -0
  230. package/src/db/schema.ts +4 -1
  231. package/src/db/spawn-budget.ts +5 -0
  232. package/src/db/spawn-delegation.ts +3 -12
  233. package/src/db/sql-audit-indexes.ts +3 -0
  234. package/src/db/stats-async.ts +5 -3
  235. package/src/db/task-entity.ts +5 -4
  236. package/src/db/team-claim-policy.ts +60 -0
  237. package/src/db/teams-async.ts +4 -0
  238. package/src/db/teams-write-ops.ts +4 -0
  239. package/src/db/terminal-events.ts +15 -0
  240. package/src/db/timer-cutover-bodies.ts +292 -0
  241. package/src/db/timer-cutover-core.ts +37 -278
  242. package/src/db/timer-cutover-state.ts +40 -0
  243. package/src/db/timer-cutover-write-ops.ts +1 -40
  244. package/src/db/token-db-write-ops.ts +2 -0
  245. package/src/db/token-standing-async.ts +12 -0
  246. package/src/db/token-standing.ts +6 -9
  247. package/src/db/token-usage-retention.ts +33 -0
  248. package/src/db/token-usage-write-ops.ts +1 -1
  249. package/src/db/token-usage.ts +4 -1
  250. package/src/db/user-invites-async.ts +13 -0
  251. package/src/db/user-invites.ts +3 -3
  252. package/src/db/users.ts +14 -6
  253. package/src/db/workflow-execution.ts +3 -3
  254. package/src/db/workspaces.ts +8 -5
  255. package/src/db/write-ops.ts +31 -12
  256. package/src/db/writer-fault-seam.ts +2 -9
  257. package/src/db/writer-fault-test-ops.ts +18 -0
  258. package/src/db/writer-gateway.ts +42 -46
  259. package/src/dev.ts +12 -16
  260. package/src/event-loop-lag.ts +4 -2
  261. package/src/event-loop-yield.ts +2 -2
  262. package/src/events.ts +10 -8
  263. package/src/host-provider-store.ts +5 -4
  264. package/src/hub-metrics-report.ts +5 -1
  265. package/src/hub-metrics.ts +38 -18
  266. package/src/init.ts +299 -0
  267. package/src/injection-sweep-pacing.ts +6 -8
  268. package/src/invite-link.ts +16 -9
  269. package/src/land-gate-notifications.ts +11 -10
  270. package/src/land-gate-queue-progress.ts +3 -3
  271. package/src/land-settle-dispatch.ts +52 -92
  272. package/src/lifecycle-context-pressure.ts +11 -10
  273. package/src/lifecycle-manager.ts +104 -98
  274. package/src/maintenance/breaker.ts +18 -16
  275. package/src/maintenance/checkout-refresh.ts +18 -1
  276. package/src/maintenance/critical-job-watchdog.ts +3 -3
  277. package/src/maintenance/events.ts +3 -3
  278. package/src/maintenance/history-retention-job.ts +30 -0
  279. package/src/maintenance/jobs.ts +29 -67
  280. package/src/maintenance/long-running-work.ts +3 -1
  281. package/src/maintenance/offline-agent-prune-job.ts +28 -0
  282. package/src/maintenance/orphaned-sessions.ts +5 -4
  283. package/src/maintenance/pauses.ts +4 -10
  284. package/src/maintenance/plan-ready-notice-job.ts +71 -0
  285. package/src/maintenance/scheduler.ts +18 -13
  286. package/src/maintenance/stalled-delivery-pager.ts +4 -4
  287. package/src/maintenance/teams.ts +19 -30
  288. package/src/maintenance/workspaces.ts +17 -27
  289. package/src/managed-policy-escalation.ts +7 -7
  290. package/src/managed-policy.ts +3 -3
  291. package/src/managed-provider-gate.ts +5 -4
  292. package/src/managed-spawn-reservation.ts +5 -4
  293. package/src/mcp/index.ts +8 -8
  294. package/src/mcp/output-descriptions.ts +1 -1
  295. package/src/mcp/presentation.ts +2 -1
  296. package/src/mcp/runtime-output.ts +6 -1
  297. package/src/mcp/self-care-visibility.ts +2 -2
  298. package/src/mcp/spawn-targets-matrix.ts +10 -1
  299. package/src/mcp/tool-groups.ts +3 -2
  300. package/src/mcp/tool-roles.ts +81 -0
  301. package/src/mcp/tools-workspace.ts +1 -1
  302. package/src/mcp-blackboard-tools.ts +6 -5
  303. package/src/mcp-decision-tools.ts +7 -7
  304. package/src/mcp-egress-tools.ts +10 -9
  305. package/src/mcp-issue-activity-tools.ts +2 -2
  306. package/src/mcp-issue-association-tools.ts +4 -4
  307. package/src/mcp-issue-ref-tools.ts +3 -3
  308. package/src/mcp-merge-tools.ts +5 -5
  309. package/src/mcp-notification-subscription-tools.ts +14 -15
  310. package/src/mcp-output-levels.test-fixture.ts +1 -1
  311. package/src/mcp-profile-trust-tools.ts +1 -1
  312. package/src/mcp-project-journal-tools.ts +4 -12
  313. package/src/mcp-project-tools.ts +17 -18
  314. package/src/mcp-routing-policy-tools.ts +3 -2
  315. package/src/mcp-team-tools.ts +6 -6
  316. package/src/mcp-workflow-tools.ts +5 -3
  317. package/src/memory-area-injection.ts +13 -12
  318. package/src/memory-boundary-capture.ts +6 -6
  319. package/src/memory-injection.ts +45 -43
  320. package/src/merge-pause-maintenance.ts +5 -4
  321. package/src/notification-router.ts +44 -17
  322. package/src/notify.ts +5 -5
  323. package/src/observed-reset-refresh.ts +4 -4
  324. package/src/operator-alarm.ts +2 -5
  325. package/src/project-ingest.ts +6 -6
  326. package/src/project-instructions.ts +8 -1
  327. package/src/project-trust.ts +12 -11
  328. package/src/prompt-resolver.ts +3 -3
  329. package/src/provider-quota-async.ts +5 -7
  330. package/src/provider-timeline-event.ts +4 -7
  331. package/src/quota-band-delivery.ts +2 -2
  332. package/src/recipe-runner.ts +4 -3
  333. package/src/retention-config-store.ts +4 -3
  334. package/src/reviewer-pipeline.ts +11 -10
  335. package/src/routes/_route.ts +4 -4
  336. package/src/routes/_shared.ts +13 -12
  337. package/src/routes/agent-effort.ts +5 -5
  338. package/src/routes/agent-heartbeat-quota.ts +23 -0
  339. package/src/routes/agent-keep.ts +1 -1
  340. package/src/routes/agent-reporting.ts +1 -1
  341. package/src/routes/agent-self-effort.ts +5 -5
  342. package/src/routes/agent-sessions.ts +24 -24
  343. package/src/routes/agents-spawn.ts +1 -1
  344. package/src/routes/agents.ts +17 -119
  345. package/src/routes/auth.ts +44 -17
  346. package/src/routes/authority-audit.ts +2 -2
  347. package/src/routes/automations.ts +6 -5
  348. package/src/routes/channel-binding-delete.ts +1 -1
  349. package/src/routes/channel-output.ts +3 -3
  350. package/src/routes/claude-api-budget.ts +5 -5
  351. package/src/routes/commands-listing.ts +1 -1
  352. package/src/routes/commands.ts +29 -27
  353. package/src/routes/context-resets.ts +2 -2
  354. package/src/routes/coordinator-teams.ts +1 -1
  355. package/src/routes/decisions.ts +2 -2
  356. package/src/routes/host-providers.ts +18 -0
  357. package/src/routes/host-secret-files.ts +1 -1
  358. package/src/routes/hosts.ts +11 -11
  359. package/src/routes/inbox.ts +3 -2
  360. package/src/routes/index.ts +7 -3
  361. package/src/routes/integrations.ts +9 -7
  362. package/src/routes/issue-activity.ts +2 -2
  363. package/src/routes/issue-associations.ts +4 -4
  364. package/src/routes/issue-refs.ts +3 -3
  365. package/src/routes/kernel-timers-operator.ts +1 -1
  366. package/src/routes/memory.ts +1 -1
  367. package/src/routes/merges.ts +11 -11
  368. package/src/routes/messages.ts +23 -17
  369. package/src/routes/orchestrator-bootstrap.ts +3 -1
  370. package/src/routes/orchestrator-host-registration.ts +2 -0
  371. package/src/routes/orchestrator-host-utilization.ts +2 -2
  372. package/src/routes/orchestrator-proxy.ts +2 -2
  373. package/src/routes/orchestrator.ts +27 -18
  374. package/src/routes/pairs.ts +8 -8
  375. package/src/routes/project-profile-instructions.ts +1 -1
  376. package/src/routes/project-trust.ts +1 -1
  377. package/src/routes/projects.ts +16 -16
  378. package/src/routes/prompt-templates.ts +12 -12
  379. package/src/routes/provider-quotas.ts +10 -10
  380. package/src/routes/run-command.ts +1 -1
  381. package/src/routes/secrets-backup.ts +4 -5
  382. package/src/routes/secrets-import.ts +3 -4
  383. package/src/routes/secrets-vault.ts +12 -14
  384. package/src/routes/settings.ts +1 -1
  385. package/src/routes/shipping.ts +9 -1
  386. package/src/routes/spawn-policy.ts +8 -7
  387. package/src/routes/sse.ts +2 -2
  388. package/src/routes/team-templates.ts +7 -7
  389. package/src/routes/teams.ts +10 -10
  390. package/src/routes/tokens.ts +18 -17
  391. package/src/routes/users.ts +24 -24
  392. package/src/routes/viewer-read.ts +3 -3
  393. package/src/routes/web-tool-calls.ts +4 -4
  394. package/src/routes/workflows.ts +2 -2
  395. package/src/routes/workspaces.ts +17 -16
  396. package/src/routing-policy-push.ts +6 -6
  397. package/src/runtime-grant.ts +17 -11
  398. package/src/runtime-install.ts +30 -0
  399. package/src/runtime-tokens.ts +20 -28
  400. package/src/secrets-broker/config.ts +1 -1
  401. package/src/secrets-broker/service.ts +2 -2
  402. package/src/security.ts +3 -1
  403. package/src/self-resume-carry.ts +21 -110
  404. package/src/self-resume-supersede.ts +4 -3
  405. package/src/self-resume.ts +54 -66
  406. package/src/server-background.ts +1 -1
  407. package/src/server.ts +25 -26
  408. package/src/services/agent-action.ts +12 -31
  409. package/src/services/agent-effort.ts +7 -32
  410. package/src/services/agent-hang-watchdog-job.ts +2 -2
  411. package/src/services/agent-heartbeat.ts +166 -0
  412. package/src/services/agent-reporting.ts +1 -1
  413. package/src/services/auth-context.ts +6 -0
  414. package/src/services/browser-inventory.ts +3 -3
  415. package/src/services/browser-session-state.ts +3 -2
  416. package/src/services/browser-session.ts +21 -27
  417. package/src/services/bus-command-update.ts +9 -9
  418. package/src/services/checkout-refresh-state.ts +1 -0
  419. package/src/services/checkout-refresh.test-fixture.ts +3 -2
  420. package/src/services/command-host.ts +7 -2
  421. package/src/services/coordinator-digest.ts +3 -2
  422. package/src/services/dispatch-command.ts +12 -13
  423. package/src/services/effective-launch-driver.ts +14 -9
  424. package/src/services/host-changed-paths.test-fixture.ts +5 -6
  425. package/src/services/host-project-readiness.ts +2 -2
  426. package/src/services/host-providers.ts +1 -1
  427. package/src/services/host-utilization.ts +9 -9
  428. package/src/services/idle-agent-digest.ts +17 -12
  429. package/src/services/idle-epics.ts +4 -4
  430. package/src/services/interrupt-agent.ts +12 -9
  431. package/src/services/issue-activity.ts +3 -3
  432. package/src/services/issue-associations.ts +15 -18
  433. package/src/services/issue-lifecycle-comments.ts +3 -2
  434. package/src/services/issue-lifecycle.ts +14 -13
  435. package/src/services/issue-refs.ts +4 -5
  436. package/src/services/kernel-timers-cutover-chaos-worker.ts +1 -1
  437. package/src/services/kernel-timers-ingress-chaos-worker.ts +6 -1
  438. package/src/services/kernel-timers-ingress.ts +3 -3
  439. package/src/services/kernel-timers-message-authority-chaos-worker.ts +3 -10
  440. package/src/services/kernel-timers-operator-chaos-worker.ts +3 -2
  441. package/src/services/kernel-timers-outbox.ts +1 -1
  442. package/src/services/kernel-timers-stage10-operator.ts +1 -1
  443. package/src/services/kernel-timers.ts +4 -3
  444. package/src/services/land-gate-placement.ts +11 -2
  445. package/src/services/land-issue-relations.ts +5 -4
  446. package/src/services/land-restart-retry.ts +7 -6
  447. package/src/services/landed-ancestry.ts +6 -5
  448. package/src/services/legacy-landing-host.ts +4 -1
  449. package/src/services/legacy-landing-recovery.ts +8 -6
  450. package/src/services/managed-running.ts +17 -17
  451. package/src/services/memory-areas.ts +3 -3
  452. package/src/services/merge-pause.ts +9 -15
  453. package/src/services/operator-decisions.ts +37 -41
  454. package/src/services/orchestrator-registration-recovery.ts +6 -2
  455. package/src/services/orchestrator-upgrade.ts +14 -12
  456. package/src/services/permission-decision.ts +4 -3
  457. package/src/services/plan-ci-notice.ts +1 -1
  458. package/src/services/plan-conformance.ts +4 -4
  459. package/src/services/plan-dispatch-policy.ts +10 -3
  460. package/src/services/plan-dispatch.ts +35 -14
  461. package/src/services/plan-graph.ts +39 -261
  462. package/src/services/plan-import.ts +1 -1
  463. package/src/services/plan-live-bindings.ts +8 -37
  464. package/src/services/plan-task-create.ts +1 -1
  465. package/src/services/plan-terminal-refs.ts +2 -12
  466. package/src/services/plan-wake-digest.ts +8 -3
  467. package/src/services/profile-trust.ts +2 -2
  468. package/src/services/project.ts +11 -10
  469. package/src/services/prompt-templates.ts +6 -5
  470. package/src/services/provider-api-key.ts +73 -0
  471. package/src/services/provider-hold-nudge.ts +9 -9
  472. package/src/services/provider-hold-record.ts +13 -12
  473. package/src/services/provider-state-audit.ts +19 -18
  474. package/src/services/provisioning-harness/driver.ts +1 -1
  475. package/src/services/provisioning-harness/fixture-matrix.ts +3 -3
  476. package/src/services/provisioning-harness/lifecycle.ts +24 -22
  477. package/src/services/provisioning-harness/prepare.ts +16 -17
  478. package/src/services/rate-limit-resume.ts +10 -9
  479. package/src/services/red-main-auto-revert.ts +7 -5
  480. package/src/services/red-main-controller-deps.ts +20 -3
  481. package/src/services/red-main-incident.ts +3 -1
  482. package/src/services/red-main-quarantine.ts +2 -3
  483. package/src/services/register-agent.ts +22 -22
  484. package/src/services/restart-project-role.ts +1 -1
  485. package/src/services/restart-spawn.ts +17 -16
  486. package/src/services/review-publish.ts +7 -24
  487. package/src/services/review-ref-reconcile.ts +29 -38
  488. package/src/services/runtime-dependency-alert.ts +27 -0
  489. package/src/services/scheduler-command.ts +11 -9
  490. package/src/services/scheduler.ts +5 -5
  491. package/src/services/self-effort-grant.ts +5 -25
  492. package/src/services/send-message.ts +16 -33
  493. package/src/services/shutdown-agent.ts +17 -14
  494. package/src/services/spawn-agent.ts +12 -41
  495. package/src/services/spawn-chain-inspector.ts +1 -1
  496. package/src/services/spawn-cwd-placement.ts +21 -5
  497. package/src/services/spawn-issue-activity.ts +8 -6
  498. package/src/services/spawn-launch.ts +17 -1
  499. package/src/services/spawn-loss-escalation.ts +1 -1
  500. package/src/services/spawn-registration-watchdog.ts +2 -2
  501. package/src/services/spawn-repo-probe.ts +7 -5
  502. package/src/services/spawn-tree-cleanup.ts +7 -8
  503. package/src/services/steward-reaper.ts +4 -4
  504. package/src/services/task-context-hydration.ts +25 -1
  505. package/src/services/task-delivery.ts +20 -34
  506. package/src/services/task-entity.ts +40 -203
  507. package/src/services/task-issue-link.ts +13 -20
  508. package/src/services/task-issue-weave.ts +12 -11
  509. package/src/services/task-lifecycle.ts +27 -31
  510. package/src/services/task-repo-cwd.ts +37 -0
  511. package/src/services/task-review-hold.ts +4 -17
  512. package/src/services/task-semantic-events.ts +13 -11
  513. package/src/services/task-state-reconcile.ts +3 -4
  514. package/src/services/task-validation.ts +2 -2
  515. package/src/services/team-templates.ts +5 -5
  516. package/src/services/team.ts +31 -98
  517. package/src/services/terminal-provider-exit.ts +6 -5
  518. package/src/services/timer-message-fire-chaos-worker.ts +7 -3
  519. package/src/services/timer-message-fire-outbox.ts +1 -1
  520. package/src/services/transient-agent-reaper.ts +22 -19
  521. package/src/services/turn-reconcile-audit.ts +9 -9
  522. package/src/services/user-onboarding.ts +8 -6
  523. package/src/services/user-profile.ts +6 -9
  524. package/src/services/work-finality.ts +16 -4
  525. package/src/services/workflow-auto-start.ts +2 -1
  526. package/src/services/workflow-caller.ts +61 -4
  527. package/src/services/workflow-child-proof-worker.ts +1 -1
  528. package/src/services/workflow-control-proof-worker.ts +1 -1
  529. package/src/services/workflow-coordinator.ts +8 -2
  530. package/src/services/workflow-decisions.ts +5 -5
  531. package/src/services/workflow-deferred-cleanup.ts +1 -1
  532. package/src/services/workflow-effect-driver.ts +42 -19
  533. package/src/services/workflow-effects.ts +41 -27
  534. package/src/services/workflow-pins.ts +7 -4
  535. package/src/services/workflow-reconcile.ts +1 -0
  536. package/src/services/workflow-resume-pins.ts +3 -3
  537. package/src/services/workflow-routing-defaults.ts +18 -0
  538. package/src/services/workflow-run-view.ts +4 -3
  539. package/src/services/workflow-run.test-fixture.ts +1 -1
  540. package/src/services/workflow-runs.ts +34 -22
  541. package/src/services/workflow-supervisor-state.ts +21 -1
  542. package/src/services/workflow-supervisor.ts +4 -3
  543. package/src/services/workspace-recovery.ts +4 -3
  544. package/src/setup.ts +47 -8
  545. package/src/spawn-admission.ts +10 -2
  546. package/src/spawn-placement.ts +27 -9
  547. package/src/spawn-targets.ts +8 -8
  548. package/src/sse-agent-status.ts +15 -6
  549. package/src/sse.ts +49 -24
  550. package/src/startup-context-injection.ts +61 -57
  551. package/src/steward-handoff.ts +4 -3
  552. package/src/steward-policy-cleanup.ts +9 -8
  553. package/src/steward.ts +2 -2
  554. package/src/task-dispatch-live.ts +5 -2
  555. package/src/task-dispatcher.ts +2 -2
  556. package/src/token-db.ts +2 -2
  557. package/src/token-usage-async.ts +2 -4
  558. package/src/user-awaiting-reply.ts +5 -5
  559. package/src/utils.ts +7 -0
  560. package/src/workspace-actions.ts +24 -29
  561. package/src/workspace-auto-merge.ts +14 -12
  562. package/src/workspace-changed-paths-client.ts +16 -5
  563. package/src/workspace-cleanup-stale.ts +30 -7
  564. package/src/workspace-command-settle.ts +24 -16
  565. package/src/workspace-config.ts +1 -1
  566. package/src/workspace-conflict-resolver.ts +9 -7
  567. package/src/workspace-dead-owner-recovery.ts +5 -4
  568. package/src/workspace-escalation.ts +12 -11
  569. package/src/workspace-human-escalation.ts +3 -3
  570. package/src/workspace-land-gate-watchdog.ts +3 -3
  571. package/src/workspace-land-refusal.ts +20 -0
  572. package/src/workspace-land-stopped.ts +1 -0
  573. package/src/workspace-merge.ts +10 -26
  574. package/src/workspace-nothing-to-land-notice.ts +2 -2
  575. package/src/workspace-orphans.ts +18 -37
  576. package/src/workspace-pr-completion.ts +10 -8
  577. package/src/workspace-rekey.ts +3 -3
  578. package/src/workspace-steward-failure.ts +2 -2
  579. package/src/workspace-steward-wake.ts +2 -2
  580. package/public/assets/agent-liveness-CaNwK0Zf.js +0 -2
  581. package/public/assets/overview-C1DCTzzV.js +0 -2
  582. package/public/assets/provider-display-DsuWMvVL.js +0 -2
  583. package/public/assets/run-card-CpKqRbhg.js +0 -2
  584. package/public/assets/shipping-B9YtnwtG.js +0 -2
  585. package/public/assets/store-DcClXLH0.js +0 -9
  586. package/runner/src/agent-runner/http-liveness.ts +0 -85
  587. package/src/db/on-loop-write-transaction.ts +0 -34
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
+ }
@@ -1,7 +1,9 @@
1
1
  import { createPlanRow, deletePlan, replacePlanIfVersion, setPlanArchived } from "./plans.ts";
2
+ import { PLAN_READY_NOTICE_WRITE_OPS } from "./plan-ready-notices.ts";
2
3
  import type { WriterOpContext, WriterOpSpec } from "./write-ops.ts";
3
4
 
4
5
  export const PLANS_WRITE_OPS: Readonly<Record<string, WriterOpSpec>> = Object.freeze({
6
+ ...PLAN_READY_NOTICE_WRITE_OPS,
5
7
  "plans.create": { offLoop: true, idempotent: false, fn: (args: { input: Parameters<typeof createPlanRow>[0] }, ctx: WriterOpContext) => createPlanRow(args.input, ctx.now) },
6
8
  "plans.replace": { offLoop: true, idempotent: false, fn: (args: { input: Parameters<typeof replacePlanIfVersion>[0]; changedBy?: string }, ctx: WriterOpContext) => replacePlanIfVersion(args.input, args.changedBy, ctx.now, false) },
7
9
  "plans.setArchived": { offLoop: true, idempotent: true, fn: (args: { id: string; archived: boolean }, ctx: WriterOpContext) => setPlanArchived(args.id, args.archived, ctx.now) },
@@ -104,6 +104,11 @@ export function snapshotBufferedPresence(): PresenceSnapshot {
104
104
  * current, and the caller's surrounding status/ready predicates constrain the match. */
105
105
  export function bufferedLiveAgentIds(cutoff: number): string[] {
106
106
  const ids: string[] = [];
107
+ const snapshot = writerPresenceSnapshot();
108
+ if (snapshot) {
109
+ for (const beat of snapshot) if (beat.lastSeen > cutoff) ids.push(beat.id);
110
+ return ids;
111
+ }
107
112
  for (const [id, pending] of pendingPresence) {
108
113
  if (pending.lastSeen > cutoff) ids.push(id);
109
114
  }
@@ -1,3 +1,4 @@
1
+ import { framed } from "../hub-metrics";
1
2
  import { assertMainThreadState } from "./main-thread-state";
2
3
  // Lifecycle glue for the coalesced presence buffer. Kept outside agents.ts so its writer-op body
3
4
  // never depends on the gateway-facing facade (ADR 0001 §3.1).
@@ -6,53 +7,47 @@ import { errMessage } from "@humwick/sdk";
6
7
  import { PRESENCE_FLUSH_MS } from "../config";
7
8
  import { drainPendingLandSettleSideEffects } from "../land-settle-dispatch";
8
9
  import { onShutdown } from "../shutdown";
9
- import { flushPresence } from "./agents.ts";
10
+ import { flushPresenceAsync } from "./presence-write.ts";
10
11
  import { pendingPresence } from "./presence-buffer.ts";
11
12
 
12
13
  let presenceFlushTimer: ReturnType<typeof setInterval> | undefined;
13
- let asyncPresenceFlusher: (() => Promise<{ flushed: number }>) | undefined;
14
14
  let removeShutdownHook: (() => void) | undefined;
15
15
 
16
- /** Attach the gateway-backed normal-tick flusher without importing the gateway into agents.ts. */
17
- export function setAsyncPresenceFlusher(flusher: () => Promise<{ flushed: number }>): void {
18
- assertMainThreadState("presence-loop.ts");
19
- asyncPresenceFlusher = flusher;
20
- }
21
-
22
16
  /** Start the periodic presence-buffer flush loop (server boot). */
23
17
  export function startPresenceFlushLoop(intervalMs: number = PRESENCE_FLUSH_MS): void {
24
18
  assertMainThreadState("presence-loop.ts");
25
19
  if (presenceFlushTimer) return;
26
- presenceFlushTimer = setInterval(() => {
27
- const flush = asyncPresenceFlusher;
28
- if (!flush) return flushPresenceQuietly(); // Bootstrap/isolated compatibility fallback.
29
- void flush().catch((err) => console.warn(`[presence] flush failed: ${errMessage(err)}`));
30
- }, intervalMs);
20
+ presenceFlushTimer = setInterval(framed("timer presence-flush", () =>
21
+ flushPresenceAsync().catch((err) => console.warn(`[presence] flush failed: ${errMessage(err)}`))), intervalMs);
31
22
  presenceFlushTimer.unref?.();
32
- process.once("beforeExit", flushPresenceQuietly);
33
- removeShutdownHook = onShutdown("presence", () => {
34
- drainPendingLandSettleSideEffects();
35
- stopPresenceFlushLoop();
23
+ process.once("beforeExit", flushPresenceBeforeExit);
24
+ removeShutdownHook = onShutdown("presence", async () => {
25
+ await drainPendingLandSettleSideEffects();
26
+ await stopPresenceFlushLoop();
36
27
  });
37
28
  }
38
29
 
39
30
  /** Stop the loop and persist whatever is buffered (graceful shutdown / test teardown). */
40
- export function stopPresenceFlushLoop(): void {
31
+ function detachPresenceLoop(): void {
41
32
  assertMainThreadState("presence-loop.ts");
42
33
  removeShutdownHook?.();
43
34
  removeShutdownHook = undefined;
44
- process.removeListener("beforeExit", flushPresenceQuietly);
35
+ process.removeListener("beforeExit", flushPresenceBeforeExit);
45
36
  if (presenceFlushTimer) clearInterval(presenceFlushTimer);
46
37
  presenceFlushTimer = undefined;
47
- flushPresenceQuietly();
48
38
  }
49
39
 
50
- function flushPresenceQuietly(): void {
51
- try { flushPresence(); } catch (err) { console.warn(`[presence] flush failed: ${errMessage(err)}`); }
40
+ export async function stopPresenceFlushLoop(): Promise<void> {
41
+ detachPresenceLoop();
42
+ await flushPresenceAsync();
43
+ }
44
+
45
+ function flushPresenceBeforeExit(): void {
46
+ void flushPresenceAsync().catch(err => console.warn(`[presence] flush failed: ${errMessage(err)}`));
52
47
  }
53
48
 
54
49
  /** Test-only: drop the buffered presence and stop the flush loop. */
55
50
  export function resetPresenceBufferForTests(): void {
56
51
  pendingPresence.clear();
57
- stopPresenceFlushLoop();
52
+ detachPresenceLoop();
58
53
  }
@@ -3,7 +3,6 @@ import { assertMainThreadState } from "./main-thread-state";
3
3
  // Kept outside agents.ts because the writer registry imports the op body from that module; importing
4
4
  // the gateway there would make the writer-stack cycle gateway → write-ops → agents → gateway.
5
5
 
6
- import { setAsyncPresenceFlusher } from "./presence-loop.ts";
7
6
  import { settlePendingPresenceFlush, snapshotPendingPresenceFlush, type PresenceFlushPayload } from "./presence-buffer.ts";
8
7
  import { getGateway } from "./gateway.ts";
9
8
 
@@ -31,8 +30,3 @@ export function flushPresenceAsync(now: number = Date.now()): Promise<{ flushed:
31
30
  .finally(() => { activeFlush = undefined; });
32
31
  return activeFlush;
33
32
  }
34
-
35
- // The timer remains owned by presence-loop.ts for its synchronous shutdown guarantee, while normal ticks
36
- // arrive here through an injected callback. This direction deliberately avoids a writer-op → gateway
37
- // dependency cycle; the callback carries no DB closure across the worker seam.
38
- setAsyncPresenceFlusher(flushPresenceAsync);
@@ -1,3 +1,5 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import type { writeProjectEntry, promoteBlackboardEntry } from "./project-entries";
1
3
  // Async project-entries facades (T4060 hub on-loop). Kept outside
2
4
  // project-entries.ts to avoid a gateway cycle.
3
5
  import { getGateway } from "./gateway.ts";
@@ -16,3 +18,15 @@ export const searchProjectEntriesAsync = (
16
18
  query: string,
17
19
  opts: ProjectEntrySearchOptions = {},
18
20
  ): Promise<ProjectEntrySearchResult[]> => read("projectEntries.search", { projectId, query, ...opts });
21
+
22
+ export const getProjectEntryAsync = (id: string): Promise<ProjectEntry | null> => read("projectEntries.get", { id });
23
+ export const getProjectEntryChainAsync = (id: string): Promise<ProjectEntry[]> => read("projectEntries.chain", { id });
24
+ export const listActiveEntriesInAreaAsync = (projectId: string, area: string): Promise<ProjectEntry[]> => read("projectEntries.inArea", { projectId, area });
25
+ export const countActivePinnedEntriesInAreaAsync = (projectId: string, area: string): Promise<number> => read("projectEntries.countPinnedInArea", { projectId, area });
26
+
27
+ export const writeProjectEntryAsync = (input: Parameters<typeof writeProjectEntry>[0], requestId: string = randomUUID()): Promise<ProjectEntry> =>
28
+ getGateway().write("projectEntries.write", input, { idempotencyKey: requestId }) as unknown as Promise<ProjectEntry>;
29
+ export const retractProjectEntryAsync = (id: string): Promise<ProjectEntry | null> =>
30
+ getGateway().write("projectEntries.retract", { id }) as unknown as Promise<ProjectEntry | null>;
31
+ export const promoteBlackboardEntryAsync = (input: Parameters<typeof promoteBlackboardEntry>[0], requestId: string = randomUUID()): Promise<ProjectEntry> =>
32
+ getGateway().write("projectEntries.promoteBlackboard", input, { idempotencyKey: requestId }) as unknown as Promise<ProjectEntry>;
@@ -1,10 +1,14 @@
1
1
  // Project-entries reader operations (T4060 hub on-loop). The MCP memory/project
2
2
  // read tools ran these scans synchronously on the event loop — including the
3
3
  // temp-B-TREE display ORDER BY over cells whose rows are ~95% retracted.
4
- import { listProjectEntries, searchProjectEntries } from "./project-entries.ts";
4
+ import { countActivePinnedEntriesInArea, getProjectEntry, getProjectEntryChain, listActiveEntriesInArea, listProjectEntries, searchProjectEntries } from "./project-entries.ts";
5
5
  import type { ProjectEntryKind, ProjectEntryStatus, ProjectEntrySearchOptions } from "../types";
6
6
 
7
7
  export const PROJECT_ENTRIES_READ_OPS = {
8
+ "projectEntries.get": (args: { id: string }) => getProjectEntry(args.id),
9
+ "projectEntries.chain": (args: { id: string }) => getProjectEntryChain(args.id),
10
+ "projectEntries.inArea": (args: { projectId: string; area: string }) => listActiveEntriesInArea(args.projectId, args.area),
11
+ "projectEntries.countPinnedInArea": (args: { projectId: string; area: string }) => countActivePinnedEntriesInArea(args.projectId, args.area),
8
12
  "projectEntries.list": (args: {
9
13
  projectId: string;
10
14
  kind?: ProjectEntryKind;
@@ -0,0 +1,8 @@
1
+ import { promoteBlackboardEntry, retractProjectEntry, writeProjectEntry } from "./project-entries";
2
+ import type { WriterOpSpec } from "./write-ops";
3
+
4
+ export const PROJECT_ENTRIES_WRITE_OPS: Readonly<Record<string, WriterOpSpec>> = {
5
+ "projectEntries.write": { offLoop: true, idempotent: false, fn: (args: Parameters<typeof writeProjectEntry>[0], ctx) => writeProjectEntry(args, ctx.now) },
6
+ "projectEntries.retract": { offLoop: true, idempotent: true, fn: (args: { id: string }, ctx) => retractProjectEntry(args.id, ctx.now) },
7
+ "projectEntries.promoteBlackboard": { offLoop: true, idempotent: false, fn: (args: Parameters<typeof promoteBlackboardEntry>[0], ctx) => promoteBlackboardEntry(args, ctx.now) },
8
+ };
@@ -1,7 +1,7 @@
1
1
  import { isolateMemoryEntry } from "../memory-entry-safety";
2
2
  import { validateProjectEntryText } from "../validation";
3
3
  import { randomUUID } from "node:crypto";
4
- import { getDb, writeTransaction, ValidationError } from "./connection.ts";
4
+ import { getDb, inWriteTransaction, ValidationError } from "./connection.ts";
5
5
  import { getEntry as getBlackboardEntry, listEntries as listBlackboardEntries } from "./blackboard.ts";
6
6
  import { parseJson } from "../utils";
7
7
  import {
@@ -211,7 +211,7 @@ function assertProjectKnowledge(projectId: string): void {
211
211
  }
212
212
  }
213
213
 
214
- export function writeProjectEntry(input: WriteProjectEntryInput): ProjectEntry {
214
+ export function writeProjectEntry(input: WriteProjectEntryInput, now: number = Date.now()): ProjectEntry {
215
215
  validateProjectEntryText(input);
216
216
  assertProjectKnowledge(input.projectId);
217
217
  assertKind(input.kind);
@@ -234,8 +234,7 @@ export function writeProjectEntry(input: WriteProjectEntryInput): ProjectEntry {
234
234
  supersedes = latestEntryByTitle(input.projectId, input.kind, input.title)?.id;
235
235
  }
236
236
  const id = projectEntryId();
237
- const now = Date.now();
238
- writeTransaction(() => {
237
+ inWriteTransaction(() => {
239
238
  let inheritedAreas: string[] | null = null;
240
239
  if (supersedes) {
241
240
  const previous = getProjectEntry(supersedes);
@@ -404,21 +403,25 @@ export function countActivePinnedEntriesInArea(projectId: string, area: string):
404
403
  }
405
404
 
406
405
  /** #3410 — active entries of one cell that name `area`, newest first. Same index as the count above. */
407
- export function listActiveEntriesInArea(projectId: string, area: string): ProjectEntry[] {
408
- const rows = getDb().query(`
406
+ export function buildActiveEntriesInAreaQuery(projectId: string, area: string): { sql: string; params: string[] } {
407
+ return { sql: `
409
408
  SELECT * FROM project_entries
410
409
  WHERE project_id = ? AND status = 'active' AND areas LIKE ? ESCAPE '\\'
411
410
  ORDER BY updated_at DESC, created_at DESC, id DESC
412
- `).all(projectId, `%${escapeLike(JSON.stringify(area))}%`) as ProjectEntryRow[];
413
- return rows.map(rowToEntry);
411
+ `, params: [projectId, `%${escapeLike(JSON.stringify(area))}%`] };
414
412
  }
415
413
 
416
- export function retractProjectEntry(id: string): ProjectEntry | null {
414
+ export function listActiveEntriesInArea(projectId: string, area: string): ProjectEntry[] {
415
+ const { sql, params } = buildActiveEntriesInAreaQuery(projectId, area);
416
+ return (getDb().query(sql).all(...params) as ProjectEntryRow[]).map(rowToEntry);
417
+ }
418
+
419
+ export function retractProjectEntry(id: string, now: number = Date.now()): ProjectEntry | null {
417
420
  const existing = getProjectEntry(id);
418
421
  if (!existing) return null;
419
422
  assertStatus("retracted");
420
423
  getDb().query("UPDATE project_entries SET status = 'retracted', updated_at = ? WHERE id = ?")
421
- .run(Date.now(), id);
424
+ .run(now, id);
422
425
  return getProjectEntry(id);
423
426
  }
424
427
 
@@ -472,7 +475,7 @@ function activePromotedEntry(projectId: string, originRef: string): ProjectEntry
472
475
  * project supersedes the prior promoted copy rather than duplicating it, so the durable
473
476
  * ledger holds at most one active entry per promoted finding while keeping the full
474
477
  * supersede chain for audit. */
475
- export function promoteBlackboardEntry(input: PromoteBlackboardEntryInput): ProjectEntry {
478
+ export function promoteBlackboardEntry(input: PromoteBlackboardEntryInput, now: number = Date.now()): ProjectEntry {
476
479
  const source = getBlackboardEntry(input.blackboardEntryId);
477
480
  if (!source) throw new ValidationError(`blackboard entry ${input.blackboardEntryId} not found`);
478
481
  if (source.status !== "active") {
@@ -489,7 +492,7 @@ export function promoteBlackboardEntry(input: PromoteBlackboardEntryInput): Proj
489
492
  source: "promoted",
490
493
  originRef: source.id,
491
494
  ...(existing ? { supersedes: existing.id } : {}),
492
- });
495
+ }, now);
493
496
  }
494
497
 
495
498
  export interface UnpromotedBlackboardSummary {
@@ -203,7 +203,7 @@ export function setProjectJournalFields(input: {
203
203
  fields: Record<string, string | null>;
204
204
  changedBy: string;
205
205
  now?: number;
206
- }): ProjectJournalFieldChange[] {
206
+ }, submittedAt: number = Date.now()): ProjectJournalFieldChange[] {
207
207
  const entries = Object.entries(input.fields);
208
208
  if (!entries.length) throw new ValidationError("fields must name at least one journal field");
209
209
  const plan: Array<{ field: ProjectJournalField; value: string | null; previous: string | null }> = [];
@@ -222,7 +222,7 @@ export function setProjectJournalFields(input: {
222
222
  plan.push({ field, value: value === "" ? null : value, previous });
223
223
  }
224
224
 
225
- const now = input.now ?? Date.now();
225
+ const now = input.now ?? submittedAt;
226
226
  const changes: ProjectJournalFieldChange[] = [];
227
227
  writeTransaction(() => {
228
228
  for (const { field, value, previous } of plan) {
@@ -68,9 +68,9 @@ export interface UpsertProjectTrustApprovalInput {
68
68
  /** Records `approvedValue` as the trusted content for this (profile, repo, capability), replacing
69
69
  * any prior approval — used both by the operator-triggered dashboard approve action and by the
70
70
  * gate's own grandfather-on-first-encounter path (decision #4). */
71
- export function upsertProjectTrustApproval(input: UpsertProjectTrustApprovalInput): ProjectTrustApproval {
71
+ export function upsertProjectTrustApproval(input: UpsertProjectTrustApprovalInput, submittedAt: number = Date.now()): ProjectTrustApproval {
72
72
  return writeTransaction(() => {
73
- const now = Date.now();
73
+ const now = submittedAt;
74
74
  const existing = getDb().query(`
75
75
  SELECT id FROM project_trust_approvals WHERE profile_name = ? AND repo_key = ? AND capability = ?
76
76
  `).get(input.profileName, input.repoKey, input.capability) as { id: string } | null;
@@ -74,8 +74,8 @@ export function listTemplates(category?: string): PromptTemplateRecord[] {
74
74
  return templates;
75
75
  }
76
76
 
77
- export function upsertTemplate(slug: string, template: string): void {
78
- const now = Date.now();
77
+ export function upsertTemplate(slug: string, template: string, submittedAt: number = Date.now()): void {
78
+ const now = submittedAt;
79
79
  if (versionedPromptSlot(slug)) {
80
80
  throw new ValidationError("Use the versioned prompt writer with expectedVersion and actor");
81
81
  }
@@ -83,11 +83,11 @@ export function upsertTemplate(slug: string, template: string): void {
83
83
  if ((result.changes ?? 0) === 0) throw new ValidationError(`prompt template ${slug} not found`);
84
84
  }
85
85
 
86
- export function resetTemplate(slug: string): void {
86
+ export function resetTemplate(slug: string, submittedAt: number = Date.now()): void {
87
87
  if (versionedPromptSlot(slug)) {
88
88
  throw new ValidationError("Use the versioned prompt writer with expectedVersion and actor");
89
89
  }
90
- const now = Date.now();
90
+ const now = submittedAt;
91
91
  const result = getDb().query("UPDATE prompt_templates SET template = default_template, updated_at = ? WHERE slug = ?").run(now, slug);
92
92
  if ((result.changes ?? 0) === 0) throw new ValidationError(`prompt template ${slug} not found`);
93
93
  }