@humwick/server 0.130.0 → 0.130.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (486) hide show
  1. package/README.md +5 -0
  2. package/docs/guides/tools-administration.md +6 -1
  3. package/docs/guides/tools-coordination.md +1 -0
  4. package/docs/guides/tools-tasks.md +8 -0
  5. package/docs/guides/workflows.md +58 -0
  6. package/docs/openapi.json +414 -1
  7. package/package.json +8 -17
  8. package/public/assets/{MessageBubble-BWpoBjl4.js → MessageBubble-BWLqMGKG.js} +2 -2
  9. package/public/assets/{PlanGraphPanel-C0auck3n.js → PlanGraphPanel-3TlIQpjk.js} +2 -2
  10. package/public/assets/{TraceView-DBnOx7AQ.js → TraceView-Wimm0Czv.js} +2 -2
  11. package/public/assets/{VaultDecisionCard-0kp2oNBC.js → VaultDecisionCard-Bau_vNax.js} +2 -2
  12. package/public/assets/{activity-yLztXEBt.js → activity-CrSurn-r.js} +2 -2
  13. package/public/assets/{agent-lineage-BbJDR1dW.js → agent-lineage-CknAJKcb.js} +2 -2
  14. package/public/assets/agent-liveness-CaNwK0Zf.js +2 -0
  15. package/public/assets/{agent-name-CTZBrSD9.js → agent-name-DKbiIhBo.js} +2 -2
  16. package/public/assets/agent-profiles-DwF6EQ4R.js +2 -0
  17. package/public/assets/{agent-project-badge-9TqzUUYE.js → agent-project-badge-Diz4Sjjb.js} +2 -2
  18. package/public/assets/{agent-tools-D4n40pn8.js → agent-tools-CRhpRQza.js} +2 -2
  19. package/public/assets/{agent-type-icon-D6FUarOf.js → agent-type-icon-LrUEU3lB.js} +2 -2
  20. package/public/assets/{agents-BYDslkJc.js → agents-B-v66C-4.js} +2 -2
  21. package/public/assets/{analytics-B-XSbEMC.js → analytics-BqCb-LKi.js} +2 -2
  22. package/public/assets/{artifact-viewer-DdWIfV6M.js → artifact-viewer-kf4pGuAR.js} +2 -2
  23. package/public/assets/{artifacts-oBOqks-2.js → artifacts-D32zyw4Y.js} +2 -2
  24. package/public/assets/{artifacts-DPN1q4pA.js → artifacts-DJevXcvg.js} +2 -2
  25. package/public/assets/{automation-4KcRFXEu.js → automation-BpRajx_5.js} +2 -2
  26. package/public/assets/{backlog-Cw9xPTCF.js → backlog-D_UjTdzX.js} +2 -2
  27. package/public/assets/{busy-health-CTu32MZt.js → busy-health-N3VEh4ao.js} +2 -2
  28. package/public/assets/{channels-BiXzg1G9.js → channels-9jopungq.js} +2 -2
  29. package/public/assets/{chat-Ctk2qwvA.js → chat-CSXrTB3C.js} +2 -2
  30. package/public/assets/{code-preview-Bm1wWqAp.js → code-preview-p1NnmqK0.js} +2 -2
  31. package/public/assets/{connectors-C36ax4Vw.js → connectors-CZdqlDNL.js} +2 -2
  32. package/public/assets/{cosmos-Jj9LK2Wf.js → cosmos-BgdsE4dG.js} +2 -2
  33. package/public/assets/{crate-agent-H6S7-B9T.js → crate-agent-30aXTKva.js} +2 -2
  34. package/public/assets/{dependency-graph-Cd1E23wa.js → dependency-graph-B_jvw2GE.js} +2 -2
  35. package/public/assets/{deploys-Bwfq146q.js → deploys-DoN6bAb5.js} +2 -2
  36. package/public/assets/{display-CfO7rTuF.js → display-BrKWFGJz.js} +2 -2
  37. package/public/assets/{files-DhhZI2Ih.js → files-D6XEu8Ng.js} +2 -2
  38. package/public/assets/fleet-C7jQWbw2.js +2 -0
  39. package/public/assets/{fleet-metrics-BkHP-P3R.js → fleet-metrics-BiDs3MLx.js} +2 -2
  40. package/public/assets/{formatted-body-Cm1RrzmN.js → formatted-body-CQBwc1fb.js} +3 -3
  41. package/public/assets/{formatted-body-impl-DQiOuOD1.js → formatted-body-impl-DyG0WsQS.js} +2 -2
  42. package/public/assets/{host-providers-dialog-CHqwXTov.js → host-providers-dialog-DKaW31dN.js} +2 -2
  43. package/public/assets/index-CRHep3M9.js +15 -0
  44. package/public/assets/index-MYAIwYTX.css +2 -0
  45. package/public/assets/{insights-fH1EL-qZ.js → insights-CzrtkpOE.js} +2 -2
  46. package/public/assets/{integrations-C5Oix6ng.js → integrations-aVsUmigt.js} +2 -2
  47. package/public/assets/{issue-ref-chip-ODyondIa.js → issue-ref-chip-BnSlM_95.js} +2 -2
  48. package/public/assets/{log-viewer-Cvfy6PW7.js → log-viewer-qe7Ee47Z.js} +2 -2
  49. package/public/assets/{maintenance-D8CDH5wZ.js → maintenance-NfjqFZAk.js} +2 -2
  50. package/public/assets/{managed-agents-egllbpij.js → managed-agents-BiH0HgaE.js} +2 -2
  51. package/public/assets/{markdown-preview-CYSFgVgK.js → markdown-preview-BixaT3Xr.js} +3 -3
  52. package/public/assets/{markdown-preview-impl-BteVIjX4.js → markdown-preview-impl-DNGlWIS3.js} +2 -2
  53. package/public/assets/{memory-C8WmXsyr.js → memory-DNsCl6UX.js} +2 -2
  54. package/public/assets/{messages-BzQdqlO6.js → messages-DopU1L1X.js} +2 -2
  55. package/public/assets/{orchestrators-B1HxzkNw.js → orchestrators-Cq7ckLQT.js} +2 -2
  56. package/public/assets/{overview-DCUnI4Z7.js → overview-C1DCTzzV.js} +2 -2
  57. package/public/assets/{pairs-ftCd2XcD.js → pairs-DE_m6rrE.js} +2 -2
  58. package/public/assets/{profile-icon-B2zH8DMH.js → profile-icon-B3rfh77F.js} +2 -2
  59. package/public/assets/project-profile-instructions-DQWZE0kA.js +2 -0
  60. package/public/assets/projects-Br2Bd2h1.js +2 -0
  61. package/public/assets/{prompt-settings-Bftq9czX.js → prompt-settings-BKvFsvGD.js} +2 -2
  62. package/public/assets/provider-display-DsuWMvVL.js +2 -0
  63. package/public/assets/{registry-DWCU5c8q.js → registry-Cw38_6gW.js} +2 -2
  64. package/public/assets/run-card-CpKqRbhg.js +2 -0
  65. package/public/assets/scroll-text-BKXS1ahr.js +2 -0
  66. package/public/assets/{secrets-C7GIUATz.js → secrets-DxI-vzSq.js} +2 -2
  67. package/public/assets/{service-access-settings-C3aujqTG.js → service-access-settings-D9s_VJbd.js} +2 -2
  68. package/public/assets/{settings-C6sZ9eBa.js → settings-CKZTVGIx.js} +2 -2
  69. package/public/assets/{shipping-Bu8p1gEw.js → shipping-B9YtnwtG.js} +2 -2
  70. package/public/assets/{stats-V9Z-GxUd.js → stats-DyUBKyWI.js} +2 -2
  71. package/public/assets/{status-dot-CVv0iGHL.js → status-dot-DYqlEUhJ.js} +2 -2
  72. package/public/assets/{store-BvtFdlGX.js → store-DcClXLH0.js} +3 -3
  73. package/public/assets/{tasks-CXU75HRF.js → tasks-Bh7Chr4-.js} +2 -2
  74. package/public/assets/{teams-BPf976zb.js → teams-bowI7qVY.js} +2 -2
  75. package/public/assets/{terminal-viewer-DE9BOe-8.js → terminal-viewer-BgVLHdsU.js} +3 -3
  76. package/public/assets/{terminal-viewer-impl-D04pjmGh.js → terminal-viewer-impl-Dz22UPy6.js} +2 -2
  77. package/public/assets/token-estimate-Co-Jdphh.js +2 -0
  78. package/public/assets/{use-agent-terminal-B1yR456R.js → use-agent-terminal-DSQQ-iY7.js} +2 -2
  79. package/public/assets/{use-decisions-D-S4tpDf.js → use-decisions-samMQhQf.js} +2 -2
  80. package/public/assets/use-project-list-59LheVP7.js +2 -0
  81. package/public/assets/{use-selectors-CnrAk2YP.js → use-selectors-BZa25xEB.js} +2 -2
  82. package/public/assets/{user-avatar-nBFIcR4M.js → user-avatar-DHxlvUvV.js} +2 -2
  83. package/public/assets/versioned-prompt-editor-BmED5DnJ.js +8 -0
  84. package/public/assets/{work-queue-TCjJxeT-.js → work-queue-CSvpuNLz.js} +2 -2
  85. package/public/assets/{workflow-marker-Cf3Hf2fN.js → workflow-marker-DAZcj5qt.js} +2 -2
  86. package/public/assets/{workflows-CXQX20Qj.js → workflows-DdmxRSyg.js} +2 -2
  87. package/public/assets/{workspaces-qSYzKYMA.js → workspaces-BxeKfVuF.js} +2 -2
  88. package/public/cosmos.html +4 -4
  89. package/public/index.html +31 -29
  90. package/runner/plugins/claude/.claude-plugin/plugin.json +1 -1
  91. package/runner/plugins/claude/hooks/gate-run-guard.provisioned.mjs +31 -1
  92. package/runner/src/adapters/claude-effort.ts +2 -4
  93. package/runner/src/adapters/codex-launch-config.ts +4 -5
  94. package/runner/src/adapters/codex.ts +1 -1
  95. package/runner/src/adapters/opencode-provider.ts +7 -22
  96. package/runner/src/adapters/opencode.ts +4 -4
  97. package/runner/src/agent-runner/credentials.ts +42 -12
  98. package/runner/src/agent-runner/inbox.ts +27 -0
  99. package/runner/src/agent-runner/lifecycle-milestones.ts +0 -2
  100. package/runner/src/bin/claude.ts +4 -0
  101. package/runner/src/bin/codex.ts +4 -0
  102. package/runner/src/bin/opencode.ts +4 -0
  103. package/runner/src/index.ts +5 -2
  104. package/runner/src/provider-setup.ts +34 -0
  105. package/runner/src/providers.ts +2 -1
  106. package/runner/src/runner-core.ts +18 -23
  107. package/runner/src/spawn-input.ts +2 -1
  108. package/runner/src/test-run-dedup.ts +59 -56
  109. package/scripts/cd-config.ts +24 -3
  110. package/scripts/gate-run-lease.ts +10 -7
  111. package/scripts/gate.sh +5 -1
  112. package/src/branch-land-notice.ts +93 -0
  113. package/src/branch-landed.ts +7 -76
  114. package/src/bus.ts +19 -20
  115. package/src/cd-deploy-notify.ts +30 -2
  116. package/src/cd-host-lag-notify.ts +7 -6
  117. package/src/ci/incidents.ts +2 -2
  118. package/src/ci/monitor.ts +5 -8
  119. package/src/ci/root-missing.ts +2 -7
  120. package/src/ci/transition-notify.ts +7 -39
  121. package/src/cli/cd-pauses.ts +19 -0
  122. package/src/cli/orchestrator.ts +1 -1
  123. package/src/cli/state.ts +2 -19
  124. package/src/cli/task-edit.ts +22 -0
  125. package/src/cli/task.ts +3 -1
  126. package/src/cli/workflow.ts +8 -2
  127. package/src/commands-db.ts +10 -6
  128. package/src/commands-write.ts +8 -0
  129. package/src/config-store-write.ts +13 -1
  130. package/src/config-store.ts +25 -14
  131. package/src/db/activity-async.ts +6 -1
  132. package/src/db/activity-write-ops.ts +13 -1
  133. package/src/db/activity.ts +1 -14
  134. package/src/db/agent-lifecycle-write-ops.ts +1 -0
  135. package/src/db/agent-meta.ts +2 -2
  136. package/src/db/agent-ownership-write-ops.ts +4 -0
  137. package/src/db/agent-search.ts +1 -15
  138. package/src/db/agents-async.ts +58 -7
  139. package/src/db/agents-write-ops.ts +28 -5
  140. package/src/db/agents.ts +81 -54
  141. package/src/db/artifacts-write-ops.ts +8 -3
  142. package/src/db/bus-outbox-write-ops.ts +1 -0
  143. package/src/db/channels-write-ops.ts +4 -0
  144. package/src/db/ci-incidents-async.ts +5 -5
  145. package/src/db/ci-incidents-write-ops.ts +2 -2
  146. package/src/db/claude-api-budget-ops.ts +5 -5
  147. package/src/db/claude-app-oauth.ts +4 -4
  148. package/src/db/codex-reset-consumes-write-ops.ts +3 -0
  149. package/src/db/command-row.ts +10 -2
  150. package/src/db/commands-list-query.ts +2 -0
  151. package/src/db/commands-spawn-read.ts +1 -1
  152. package/src/db/commands-write-ops.ts +21 -0
  153. package/src/db/config-store-write-ops.ts +9 -2
  154. package/src/db/continuation-archives-async.ts +5 -0
  155. package/src/db/continuation-archives-write-ops.ts +9 -0
  156. package/src/db/continuations.ts +2 -2
  157. package/src/db/db-writer-worker.ts +8 -1
  158. package/src/db/delivery-write-ops.ts +5 -0
  159. package/src/db/deploy-events-async.ts +1 -0
  160. package/src/db/deploy-events-read-ops.ts +2 -1
  161. package/src/db/deploy-events.ts +35 -4
  162. package/src/db/hosts-registration.ts +10 -2
  163. package/src/db/hosts-write-ops.ts +14 -0
  164. package/src/db/hosts.ts +9 -6
  165. package/src/db/inbox-write-ops.ts +4 -0
  166. package/src/db/inbox.ts +0 -14
  167. package/src/db/index.ts +2 -0
  168. package/src/db/insights-write-ops.ts +1 -0
  169. package/src/db/integrations-async.ts +8 -0
  170. package/src/db/integrations-ops.ts +15 -0
  171. package/src/db/integrations.ts +15 -21
  172. package/src/db/issue-lifecycle.ts +3 -4
  173. package/src/db/issue-refs-async.ts +20 -0
  174. package/src/db/issue-refs-write-ops.ts +29 -0
  175. package/src/db/issue-refs.ts +7 -8
  176. package/src/db/long-running-work.ts +2 -2
  177. package/src/db/macos-privacy.ts +1 -1
  178. package/src/db/main-connection-writes.ts +23 -2
  179. package/src/db/maintenance-job-reconcile.ts +61 -0
  180. package/src/db/maintenance-job-runs-async.ts +14 -0
  181. package/src/db/maintenance-job-write-ops.ts +9 -0
  182. package/src/db/mappers.ts +2 -14
  183. package/src/db/merge-lease.ts +1 -15
  184. package/src/db/message-reads-write-ops.ts +3 -0
  185. package/src/db/message-write-ops.ts +4 -0
  186. package/src/db/migrations.ts +7 -15
  187. package/src/db/orchestrators-async.ts +7 -3
  188. package/src/db/orchestrators-write-ops.ts +7 -1
  189. package/src/db/orchestrators.ts +21 -77
  190. package/src/db/pairs.ts +1 -13
  191. package/src/db/project-profile-instructions.ts +50 -0
  192. package/src/db/prompt-versions-ops.ts +1 -0
  193. package/src/db/prompt-versions.ts +9 -2
  194. package/src/db/provider-catalog-write-ops.ts +4 -4
  195. package/src/db/provider-config-history.ts +8 -3
  196. package/src/db/provider-flap-messages-write-ops.ts +1 -0
  197. package/src/db/provider-model-seed.ts +34 -0
  198. package/src/db/provider-quota-lease-write-ops.ts +1 -0
  199. package/src/db/provider-quota-write-ops.ts +2 -0
  200. package/src/db/provisioning-write-ops.ts +9 -8
  201. package/src/db/read-op-marshaling-fixtures.ts +5 -0
  202. package/src/db/read-ops.ts +4 -0
  203. package/src/db/routing-policy.ts +3 -4
  204. package/src/db/schema.ts +9 -3
  205. package/src/db/secrets-broker-write-ops.ts +5 -5
  206. package/src/db/secrets-vault-write.ts +7 -7
  207. package/src/db/services-write-ops.ts +3 -0
  208. package/src/db/spawn-delegation-state.ts +11 -4
  209. package/src/db/spawn-tree-cleanup.ts +3 -0
  210. package/src/db/stats-async.ts +4 -3
  211. package/src/db/task-entity.ts +23 -2
  212. package/src/db/task-list.ts +3 -1
  213. package/src/db/tasks.ts +1 -17
  214. package/src/db/team-spawn-delegation.ts +1 -1
  215. package/src/db/teams-write-ops.ts +4 -4
  216. package/src/db/timer-cutover-write-ops.ts +7 -0
  217. package/src/db/timer-ingress-write-ops.ts +9 -0
  218. package/src/db/timer-ingress.ts +7 -1
  219. package/src/db/timer-operator-write-ops.ts +14 -0
  220. package/src/db/timer-transfer-write-ops.ts +4 -0
  221. package/src/db/timer-write-ops.ts +14 -0
  222. package/src/db/token-db-write-ops.ts +21 -0
  223. package/src/db/token-standing.ts +15 -11
  224. package/src/db/user-settings-write-ops.ts +1 -0
  225. package/src/db/vault-backup.ts +2 -2
  226. package/src/db/vault-expiry.ts +2 -2
  227. package/src/db/vault-health.ts +3 -3
  228. package/src/db/vault-token-ops.ts +8 -8
  229. package/src/db/web-tool-call-ops.ts +3 -3
  230. package/src/db/workflow-contact.ts +1 -1
  231. package/src/db/workflow-task-read-ops.ts +30 -0
  232. package/src/db/write-ops.ts +15 -1
  233. package/src/db/writer-fault-seam.ts +5 -2
  234. package/src/db/writer-gateway.ts +21 -12
  235. package/src/dispatch-availability.ts +10 -6
  236. package/src/ephemeral/{landing-ingress-proof-worker.ts → landing-ingress-proof.test-fixture.ts} +4 -2
  237. package/src/host/disk-alert.ts +121 -0
  238. package/src/lifecycle-manager.ts +3 -2
  239. package/src/maintenance/cd-watcher.ts +3 -3
  240. package/src/maintenance/critical-job-watchdog.ts +16 -17
  241. package/src/maintenance/index.ts +1 -0
  242. package/src/maintenance/jobs.ts +3 -2
  243. package/src/maintenance/scheduler.ts +84 -128
  244. package/src/maintenance/stalled-delivery-pager.ts +6 -5
  245. package/src/maintenance/workspaces.ts +1 -1
  246. package/src/managed-policy.ts +31 -27
  247. package/src/mcp/index.ts +5 -2
  248. package/src/mcp/output-descriptions.ts +8 -8
  249. package/src/mcp/presentation.ts +4 -4
  250. package/src/mcp/project-profile-instructions.ts +32 -0
  251. package/src/mcp/tool-groups.ts +1 -1
  252. package/src/mcp/tools-spawn.ts +4 -3
  253. package/src/mcp-agent-action.ts +1 -2
  254. package/src/mcp-host-tools.ts +1 -1
  255. package/src/mcp-issue-ref-tools.ts +12 -11
  256. package/src/mcp-maintenance-tools.ts +2 -2
  257. package/src/mcp-profile-trust-tools.ts +4 -4
  258. package/src/mcp-provider-catalog.ts +2 -2
  259. package/src/mcp-task-tools.ts +11 -5
  260. package/src/mcp-team-template-tools.ts +3 -3
  261. package/src/mcp-workflow-tools.ts +10 -3
  262. package/src/notification-types.ts +2 -1
  263. package/src/operator-alarm.ts +26 -11
  264. package/src/package-pack.ts +42 -39
  265. package/src/project-instructions.ts +4 -0
  266. package/src/project-profile-instruction-text.ts +6 -0
  267. package/src/project-profile-instructions.ts +20 -0
  268. package/src/provider-catalog-store.ts +3 -49
  269. package/src/provider-config-store.ts +12 -11
  270. package/src/provider-model-seed.ts +1 -18
  271. package/src/provider-quota-config-store.ts +5 -4
  272. package/src/provider-secrets.test-fixture.ts +3 -3
  273. package/src/recipe-runner.ts +2 -2
  274. package/src/repo-config-source.ts +10 -4
  275. package/src/routes/agent-profiles.ts +6 -6
  276. package/src/routes/agents-spawn.ts +1 -1
  277. package/src/routes/agents.ts +6 -6
  278. package/src/routes/commands-listing.ts +4 -0
  279. package/src/routes/commands.ts +2 -2
  280. package/src/routes/continuation-archives.ts +2 -2
  281. package/src/routes/host-resource-control.ts +4 -1
  282. package/src/routes/index.ts +10 -1
  283. package/src/routes/integrations.ts +24 -50
  284. package/src/routes/issue-refs.ts +7 -7
  285. package/src/routes/orchestrator-bootstrap.ts +3 -3
  286. package/src/routes/orchestrator-host-registration.ts +2 -2
  287. package/src/routes/orchestrator-host-utilization.ts +3 -1
  288. package/src/routes/orchestrator-proxy.ts +2 -2
  289. package/src/routes/orchestrator.ts +4 -4
  290. package/src/routes/project-profile-instructions.ts +27 -0
  291. package/src/routes/project-trust.ts +2 -2
  292. package/src/routes/provider-config.ts +9 -8
  293. package/src/routes/provider-quota-config.ts +2 -2
  294. package/src/routes/settings.ts +5 -5
  295. package/src/routes/stats.ts +6 -6
  296. package/src/routes/task-http.ts +15 -1
  297. package/src/routes/task-issues.ts +11 -26
  298. package/src/routes/tasks.ts +22 -53
  299. package/src/routes/team-templates.ts +5 -5
  300. package/src/routes/teams-config.ts +2 -2
  301. package/src/routes/tokens.ts +12 -5
  302. package/src/routes/workflows.ts +20 -5
  303. package/src/routing-policy-normalize.ts +1 -2
  304. package/src/routing-step-defaults.ts +0 -2
  305. package/src/runner-token-mint-limit.ts +63 -0
  306. package/src/runtime-grant.ts +4 -2
  307. package/src/runtime-tokens.ts +50 -9
  308. package/src/security.ts +13 -20
  309. package/src/server-background.ts +1 -1
  310. package/src/server.ts +5 -2
  311. package/src/services/host-utilization.ts +23 -0
  312. package/src/services/idle-epics.ts +5 -6
  313. package/src/services/issue-lifecycle.ts +57 -61
  314. package/src/services/issue-refs.ts +9 -13
  315. package/src/services/kernel-timers-cutover-chaos-worker.ts +12 -7
  316. package/src/services/kernel-timers-ingress.ts +1 -1
  317. package/src/services/kernel-timers-request-key-migration-chaos-worker.ts +4 -1
  318. package/src/services/landed-ancestry.ts +19 -3
  319. package/src/services/memory-areas.ts +15 -51
  320. package/src/services/plan-ci-notice.ts +57 -0
  321. package/src/services/plan-ci-recovery.ts +2 -55
  322. package/src/services/plan-wake-digest.ts +1 -0
  323. package/src/services/profile-trust.ts +4 -3
  324. package/src/services/project-config-read.ts +70 -0
  325. package/src/services/project-profile-instructions.ts +54 -0
  326. package/src/services/provisioning-harness/budget-baseline.json +4 -4
  327. package/src/services/restart-spawn.ts +61 -49
  328. package/src/services/self-care-state.ts +2 -1
  329. package/src/services/self-effort.ts +5 -5
  330. package/src/services/send-message.ts +5 -1
  331. package/src/services/spawn-agent.ts +13 -2
  332. package/src/services/spawn-chain-inspector.ts +6 -13
  333. package/src/services/spawn-policy.ts +68 -0
  334. package/src/services/spawn-tree-cleanup.ts +2 -2
  335. package/src/services/task-dispatch.ts +3 -0
  336. package/src/services/task-state-reconcile.ts +6 -14
  337. package/src/services/team-templates.ts +9 -8
  338. package/src/services/transient-agent-reaper.ts +3 -1
  339. package/src/services/workflow-auto-start.ts +25 -13
  340. package/src/services/workflow-ci-main.ts +1 -1
  341. package/src/services/workflow-deferred-cleanup.ts +4 -2
  342. package/src/services/workflow-effect-driver.ts +2 -0
  343. package/src/services/workflow-effects.ts +18 -11
  344. package/src/services/workflow-land-notice.ts +10 -0
  345. package/src/services/workflow-pause-text.ts +43 -0
  346. package/src/services/workflow-reconcile.ts +13 -11
  347. package/src/services/workflow-run-view.ts +2 -0
  348. package/src/services/workflow-runs.ts +29 -13
  349. package/src/services/workflow-supervisor-state.ts +5 -0
  350. package/src/services/workflow-supervisor.ts +7 -3
  351. package/src/settings/registry.ts +1 -2
  352. package/src/spawn-command.ts +4 -4
  353. package/src/spawn-models.ts +10 -12
  354. package/src/spawn-placement.ts +1 -1
  355. package/src/spawn-targets.ts +1 -1
  356. package/src/startup-context-injection.ts +1 -0
  357. package/src/state-adoption/cd-cutover.ts +53 -7
  358. package/src/state-adoption/fleet-cutover.ts +35 -0
  359. package/src/state-adoption/fleet-plan.ts +36 -0
  360. package/src/state-adoption/index.ts +2 -2
  361. package/src/state-adoption/linux-plan.ts +2 -2
  362. package/src/state-adoption/linux.ts +2 -2
  363. package/src/state-adoption/macos-plan.ts +2 -2
  364. package/src/state-adoption/macos.ts +1 -1
  365. package/src/steward.ts +7 -6
  366. package/src/task-dispatch-live.ts +1 -1
  367. package/src/task-dispatcher.ts +1 -1
  368. package/src/teams-config-store.ts +4 -3
  369. package/src/token-db-write.ts +10 -0
  370. package/src/workspace-command-settle.ts +2 -1
  371. package/src/workspace-steward-wake.ts +2 -2
  372. package/node_modules/@humwick/kernel/LICENSE +0 -167
  373. package/node_modules/@humwick/kernel/async-entity-persistence.ts +0 -344
  374. package/node_modules/@humwick/kernel/async-event-log.ts +0 -107
  375. package/node_modules/@humwick/kernel/automations.pilot.ts +0 -147
  376. package/node_modules/@humwick/kernel/config.ts +0 -111
  377. package/node_modules/@humwick/kernel/contract.ts +0 -572
  378. package/node_modules/@humwick/kernel/control-authz.ts +0 -281
  379. package/node_modules/@humwick/kernel/control.ts +0 -645
  380. package/node_modules/@humwick/kernel/effects.ts +0 -493
  381. package/node_modules/@humwick/kernel/event-log.ts +0 -319
  382. package/node_modules/@humwick/kernel/http-transport.ts +0 -384
  383. package/node_modules/@humwick/kernel/identity-grammar.ts +0 -20
  384. package/node_modules/@humwick/kernel/identity.ts +0 -248
  385. package/node_modules/@humwick/kernel/index.ts +0 -28
  386. package/node_modules/@humwick/kernel/kernel-persistence.ts +0 -181
  387. package/node_modules/@humwick/kernel/landing-incident.ts +0 -306
  388. package/node_modules/@humwick/kernel/landing.ts +0 -953
  389. package/node_modules/@humwick/kernel/manifest-validation.ts +0 -460
  390. package/node_modules/@humwick/kernel/mcp-transport.ts +0 -109
  391. package/node_modules/@humwick/kernel/message-authority-chaos-worker.ts +0 -200
  392. package/node_modules/@humwick/kernel/message-authority.ts +0 -281
  393. package/node_modules/@humwick/kernel/module-host.ts +0 -722
  394. package/node_modules/@humwick/kernel/openapi-init.ts +0 -14
  395. package/node_modules/@humwick/kernel/package.json +0 -47
  396. package/node_modules/@humwick/kernel/persistence-schema.ts +0 -248
  397. package/node_modules/@humwick/kernel/persistence.ts +0 -510
  398. package/node_modules/@humwick/kernel/registry.ts +0 -60
  399. package/node_modules/@humwick/kernel/sqlite-kernel-persistence.ts +0 -499
  400. package/node_modules/@humwick/kernel/sqlite-module-host.ts +0 -87
  401. package/node_modules/@humwick/kernel/timers-chaos-worker.ts +0 -186
  402. package/node_modules/@humwick/kernel/timers.pilot.ts +0 -1411
  403. package/node_modules/@humwick/kernel/workflow-activation.ts +0 -25
  404. package/node_modules/@humwick/kernel/workflow-backstop.ts +0 -40
  405. package/node_modules/@humwick/kernel/workflow-cancel-tree.ts +0 -20
  406. package/node_modules/@humwick/kernel/workflow-chaos-worker.ts +0 -213
  407. package/node_modules/@humwick/kernel/workflow-composition.ts +0 -231
  408. package/node_modules/@humwick/kernel/workflow-container.ts +0 -72
  409. package/node_modules/@humwick/kernel/workflow-control.test-fixture.ts +0 -42
  410. package/node_modules/@humwick/kernel/workflow-control.ts +0 -178
  411. package/node_modules/@humwick/kernel/workflow-definition.ts +0 -507
  412. package/node_modules/@humwick/kernel/workflow-dispatch.ts +0 -60
  413. package/node_modules/@humwick/kernel/workflow-foreach.ts +0 -167
  414. package/node_modules/@humwick/kernel/workflow-journal-codec.ts +0 -339
  415. package/node_modules/@humwick/kernel/workflow-journal.ts +0 -922
  416. package/node_modules/@humwick/kernel/workflow-lane.ts +0 -48
  417. package/node_modules/@humwick/kernel/workflow-lease-owner.ts +0 -60
  418. package/node_modules/@humwick/kernel/workflow-recovery.test-fixture.ts +0 -19
  419. package/node_modules/@humwick/kernel/workflow-runtime.ts +0 -938
  420. package/node_modules/@humwick/kernel/workflow-script.ts +0 -11
  421. package/node_modules/@humwick/kernel/workflow-state.ts +0 -299
  422. package/node_modules/@humwick/kernel/workflow-step-commit.ts +0 -195
  423. package/node_modules/@humwick/kernel/workflow-transitions.ts +0 -317
  424. package/node_modules/@humwick/kernel/workflow-values.ts +0 -153
  425. package/node_modules/@humwick/kernel/workflow.pilot.ts +0 -376
  426. package/node_modules/@humwick/offload/LICENSE +0 -167
  427. package/node_modules/@humwick/offload/README.md +0 -57
  428. package/node_modules/@humwick/offload/dist/async-runner.d.ts +0 -18
  429. package/node_modules/@humwick/offload/dist/async-runner.d.ts.map +0 -1
  430. package/node_modules/@humwick/offload/dist/async-runner.js +0 -158
  431. package/node_modules/@humwick/offload/dist/async-runner.js.map +0 -1
  432. package/node_modules/@humwick/offload/dist/envelope.d.ts +0 -25
  433. package/node_modules/@humwick/offload/dist/envelope.d.ts.map +0 -1
  434. package/node_modules/@humwick/offload/dist/envelope.js +0 -80
  435. package/node_modules/@humwick/offload/dist/envelope.js.map +0 -1
  436. package/node_modules/@humwick/offload/dist/fixtures/worker-task.d.ts +0 -10
  437. package/node_modules/@humwick/offload/dist/fixtures/worker-task.d.ts.map +0 -1
  438. package/node_modules/@humwick/offload/dist/fixtures/worker-task.js +0 -59
  439. package/node_modules/@humwick/offload/dist/fixtures/worker-task.js.map +0 -1
  440. package/node_modules/@humwick/offload/dist/index.d.ts +0 -5
  441. package/node_modules/@humwick/offload/dist/index.d.ts.map +0 -1
  442. package/node_modules/@humwick/offload/dist/index.js +0 -5
  443. package/node_modules/@humwick/offload/dist/index.js.map +0 -1
  444. package/node_modules/@humwick/offload/dist/subprocess-runner.d.ts +0 -62
  445. package/node_modules/@humwick/offload/dist/subprocess-runner.d.ts.map +0 -1
  446. package/node_modules/@humwick/offload/dist/subprocess-runner.js +0 -295
  447. package/node_modules/@humwick/offload/dist/subprocess-runner.js.map +0 -1
  448. package/node_modules/@humwick/offload/dist/types.d.ts +0 -111
  449. package/node_modules/@humwick/offload/dist/types.d.ts.map +0 -1
  450. package/node_modules/@humwick/offload/dist/types.js +0 -176
  451. package/node_modules/@humwick/offload/dist/types.js.map +0 -1
  452. package/node_modules/@humwick/offload/dist/worker-pool.d.ts +0 -23
  453. package/node_modules/@humwick/offload/dist/worker-pool.d.ts.map +0 -1
  454. package/node_modules/@humwick/offload/dist/worker-pool.js +0 -435
  455. package/node_modules/@humwick/offload/dist/worker-pool.js.map +0 -1
  456. package/node_modules/@humwick/offload/dist/worker-runtime.d.ts +0 -2
  457. package/node_modules/@humwick/offload/dist/worker-runtime.d.ts.map +0 -1
  458. package/node_modules/@humwick/offload/dist/worker-runtime.js +0 -27
  459. package/node_modules/@humwick/offload/dist/worker-runtime.js.map +0 -1
  460. package/node_modules/@humwick/offload/dist/worker.d.ts +0 -5
  461. package/node_modules/@humwick/offload/dist/worker.d.ts.map +0 -1
  462. package/node_modules/@humwick/offload/dist/worker.js +0 -1
  463. package/node_modules/@humwick/offload/dist/worker.js.map +0 -1
  464. package/node_modules/@humwick/offload/package.json +0 -46
  465. package/node_modules/@humwick/offload/src/async-runner.ts +0 -170
  466. package/node_modules/@humwick/offload/src/envelope.ts +0 -92
  467. package/node_modules/@humwick/offload/src/fixtures/worker-task.ts +0 -49
  468. package/node_modules/@humwick/offload/src/index.ts +0 -4
  469. package/node_modules/@humwick/offload/src/subprocess-runner.ts +0 -334
  470. package/node_modules/@humwick/offload/src/types.ts +0 -235
  471. package/node_modules/@humwick/offload/src/worker-pool.ts +0 -463
  472. package/node_modules/@humwick/offload/src/worker-runtime.ts +0 -32
  473. package/node_modules/@humwick/offload/src/worker.ts +0 -5
  474. package/public/assets/agent-liveness-DyqxfB-t.js +0 -2
  475. package/public/assets/agent-profiles-BpI_jTxq.js +0 -2
  476. package/public/assets/fleet-BL4UXS_k.js +0 -2
  477. package/public/assets/index-B7ZerHLY.css +0 -2
  478. package/public/assets/index-BK0WV6hF.js +0 -15
  479. package/public/assets/projects-B_lmcqfO.js +0 -2
  480. package/public/assets/provider-display-DbLWpP3j.js +0 -2
  481. package/public/assets/run-card-CO_bguQf.js +0 -2
  482. package/public/assets/use-project-list-B-T2Bnnn.js +0 -2
  483. package/public/assets/versioned-prompt-editor-Diu8BIaM.js +0 -8
  484. package/runner/src/gate-run-lease-probe.ts +0 -38
  485. package/src/db/claude-catalog-reconcile.ts +0 -27
  486. package/src/routing-model-aliases.ts +0 -21
@@ -24,6 +24,8 @@ const releaseFile = process.env.CUTOVER_CHAOS_RELEASE_FILE;
24
24
  const resultFile = process.env.CUTOVER_CHAOS_RESULT_FILE;
25
25
  const owner = process.env.CUTOVER_CHAOS_OWNER ?? `worker-${process.pid}`;
26
26
  const leaseMs = Number(process.env.CUTOVER_CHAOS_LEASE_MS ?? "400");
27
+ let now = Number(required("CUTOVER_CHAOS_NOW"));
28
+ const clock = () => now;
27
29
 
28
30
  function required(name: string): string {
29
31
  const value = relayEnv(process.env, name);
@@ -60,7 +62,7 @@ function timerInput(idSuffix = String(process.pid)): CreateScheduledTimerRowInpu
60
62
  createdByKind: "system",
61
63
  createdByScopes: ["message:send"],
62
64
  deliveryFrom: "system",
63
- nextRunAt: Date.now() + 100_000,
65
+ nextRunAt: now + 100_000,
64
66
  scheduleKind: "once",
65
67
  scheduleDef: { afterMs: 100_000 },
66
68
  body: `cutover-${idSuffix}`,
@@ -87,6 +89,7 @@ async function main(): Promise<void> {
87
89
  };
88
90
  const c = new TimerMessageCutoverCoordinator({
89
91
  ownerId: owner,
92
+ now: clock,
90
93
  leaseMs,
91
94
  tickMs: 25,
92
95
  quiesceTimeoutMs: 5_000,
@@ -94,7 +97,7 @@ async function main(): Promise<void> {
94
97
  faults,
95
98
  });
96
99
  await c.requestQuiesce();
97
- if (boundary === "after-renewal") await Bun.sleep(Math.ceil(leaseMs * 0.75));
100
+ if (boundary === "after-renewal") now += Math.ceil(leaseMs * 0.75);
98
101
  await c.tick();
99
102
  await c.tick();
100
103
  await c.tick();
@@ -112,7 +115,7 @@ async function main(): Promise<void> {
112
115
  const ticks = phaseTicks[boundary];
113
116
  if (ticks === undefined) throw new Error(`unknown deadline-watch phase ${boundary}`);
114
117
  const c = new TimerMessageCutoverCoordinator({
115
- ownerId: owner, leaseMs: 10_000, tickMs: 25, quiesceTimeoutMs: 8_000, worstCaseDrainTransferMs: 10_000,
118
+ ownerId: owner, now: clock, leaseMs: 10_000, tickMs: 25, quiesceTimeoutMs: 8_000, worstCaseDrainTransferMs: 10_000,
116
119
  });
117
120
  await c.requestQuiesce();
118
121
  for (let i = 0; i < ticks; i++) await c.tick();
@@ -124,14 +127,14 @@ async function main(): Promise<void> {
124
127
 
125
128
  if (command === "claim-bound") {
126
129
  const c = new TimerMessageCutoverCoordinator({
127
- ownerId: owner, leaseMs: 1_000, tickMs: 25, quiesceTimeoutMs: 300, worstCaseDrainTransferMs: 1_000,
130
+ ownerId: owner, now: clock, leaseMs: 1_000, tickMs: 25, quiesceTimeoutMs: 300, worstCaseDrainTransferMs: 1_000,
128
131
  });
129
132
  await c.requestQuiesce();
130
133
  await c.tick();
131
134
  await c.tick();
132
135
  const drain = await c.tick();
133
136
  if (boundary === "held") {
134
- await Bun.sleep(340);
137
+ now += 340;
135
138
  await c.tick();
136
139
  await c.tick();
137
140
  }
@@ -141,7 +144,7 @@ async function main(): Promise<void> {
141
144
 
142
145
  if (command === "recover") {
143
146
  const c = new TimerMessageCutoverCoordinator({
144
- ownerId: owner, leaseMs, tickMs: 25, quiesceTimeoutMs: 5_000, worstCaseDrainTransferMs: 6_000,
147
+ ownerId: owner, now: clock, leaseMs, tickMs: 25, quiesceTimeoutMs: 5_000, worstCaseDrainTransferMs: 6_000,
145
148
  });
146
149
  const outcomes = [];
147
150
  for (let i = 0; i < 5; i++) outcomes.push(await c.tick());
@@ -152,7 +155,7 @@ async function main(): Promise<void> {
152
155
  if (command === "close-gate") {
153
156
  marker();
154
157
  const c = new TimerMessageCutoverCoordinator({
155
- ownerId: owner, leaseMs: 5_000, tickMs: 25, quiesceTimeoutMs: 5_000, worstCaseDrainTransferMs: 6_000,
158
+ ownerId: owner, now: clock, leaseMs: 5_000, tickMs: 25, quiesceTimeoutMs: 5_000, worstCaseDrainTransferMs: 6_000,
156
159
  });
157
160
  const acquired = await c.requestQuiesce();
158
161
  const outcome = acquired ? await c.tick() : null;
@@ -207,3 +210,5 @@ async function main(): Promise<void> {
207
210
  }
208
211
 
209
212
  await main();
213
+ // Off-loop timer ops start the file-DB writer thread, which keeps an idle process alive (#1870 S6a).
214
+ process.exit(0);
@@ -322,7 +322,7 @@ export class TimerIngressRejectedError extends Error {
322
322
  }
323
323
 
324
324
  export function createTimerIngress(options: TimerIngressOptions): TimerIngress {
325
- installTimerIngressSchema();
325
+ installTimerIngressSchema({ ifNotInstalled: true });
326
326
  const apply = options.apply;
327
327
  const ownerId = options.ownerId ?? `${process.pid}-${randomUUID()}`;
328
328
  const now = options.now ?? Date.now;
@@ -1,3 +1,4 @@
1
+ import { writeSync } from "node:fs";
1
2
  import { Database } from "bun:sqlite";
2
3
  import { applyConnectionPragmas, setDb } from "../db/connection.ts";
3
4
  import { createTimerIngress, installTimerIngressSchema } from "./kernel-timers-ingress.ts";
@@ -66,7 +67,7 @@ if (command === "migrate") {
66
67
  source: { kind: "snapshot", timer: replayDecoySnapshot() },
67
68
  });
68
69
  const cancelReplay = await ingress.acceptCancel({ timerId: "timer-cancel", requestKey: rawCancelKey });
69
- process.stdout.write(JSON.stringify({
70
+ writeSync(1, JSON.stringify({
70
71
  create: { seq: replay.seq, timerId: replay.bound.id, state: replay.state },
71
72
  cancel: { seq: cancelReplay.seq, timerId: "timer-cancel", state: cancelReplay.state },
72
73
  }));
@@ -75,3 +76,5 @@ if (command === "migrate") {
75
76
  }
76
77
 
77
78
  db.close();
79
+ // The off-loop ingress ops start the file-DB writer thread, which keeps an idle process alive (#1870 S6a).
80
+ process.exit(0);
@@ -1,3 +1,5 @@
1
+ import { repoDefaultBranch } from "@humwick/sdk/land-gates";
2
+ import { withWorkspaceTaskOwnerRoles } from "../task-owner-notices";
1
3
  // #1713 — the coordinator's "landed" signal, derived from GIT ANCESTRY rather than a command's
2
4
  // reported status. The observed bug: a worker delivers `complete`, the merge executor stalls, and
3
5
  // the coordinator is nonetheless told the work is "complete"/"landed" while `origin/main` is
@@ -113,7 +115,8 @@ const realVerifier: AncestryVerifier = async (repoRoot, sha, targetRef) => {
113
115
  // source of truth; a fetch failure/timeout is swallowed — the checks below then run against
114
116
  // whatever refs are already local, preserving the prior fail-closed behavior exactly.
115
117
  const remoteBranch = ref.startsWith("origin/") ? ref.slice("origin/".length) : ref;
116
- if (remoteBranch) await gitStatus(root, ["fetch", "--quiet", "origin", remoteBranch], GIT_FETCH_TIMEOUT_MS);
118
+ const fetched = remoteBranch ? await gitStatus(root, ["fetch", "--quiet", "origin", `+refs/heads/${remoteBranch}:refs/remotes/origin/${remoteBranch}`], GIT_FETCH_TIMEOUT_MS) : null;
119
+ if (ref.startsWith("origin/") && fetched !== 0) return "unresolvable";
117
120
  // The sha itself must resolve to a commit in this repo, else ancestry is unknowable (not a
118
121
  // negative). `--verify --quiet ^{commit}` distinguishes "sha absent" from "sha present but not
119
122
  // on the ref".
@@ -246,6 +249,18 @@ export async function verifyLandedAncestry(repoRoot: string | undefined, sha: st
246
249
  }
247
250
  }
248
251
 
252
+ /** Notice proof requires the freshly fetched remote; a local-only main never qualifies (#3761). */
253
+ export async function verifyPublishedLand(repoRoot: string | undefined, sha: string | undefined, baseRef?: string): Promise<LandedAncestryVerdict> {
254
+ if (!repoRoot || !sha?.trim()) return "unresolvable";
255
+ try {
256
+ const base = baseRef ?? await repoDefaultBranch(async args => {
257
+ const result = await gitOutput(repoRoot, args);
258
+ return { ok: result?.exitCode === 0, stdout: result?.stdout ?? "", unavailable: !result };
259
+ });
260
+ return base ? await verifyLandedAncestry(repoRoot, sha, base.startsWith("origin/") ? base : `origin/${base}`) : "unresolvable";
261
+ } catch { return "unresolvable"; }
262
+ }
263
+
249
264
  // #2718 — the landing host's own fresh-fetch ancestry check (`landVerifiedSha`) is ref-backed proof
250
265
  // that works wherever the repo lives; the hub's check needs a checkout of the same remote on its own
251
266
  // disk. Without a matching host verdict, fall back to the hub check (an absent path never confirms).
@@ -366,6 +381,7 @@ export function notifyLandUnconfirmed(input: LandUnconfirmedInput): void {
366
381
  const { subject, body } = landUnconfirmedText(input);
367
382
 
368
383
  const payload = {
384
+ verified: false,
369
385
  kind: "workspace.land-unconfirmed",
370
386
  notification: "land-unconfirmed",
371
387
  landOutcome: kind,
@@ -397,7 +413,7 @@ export function notifyLandUnconfirmed(input: LandUnconfirmedInput): void {
397
413
  metadata: { source: "server", maintenanceJobId: "workspace-land-unconfirmed", workspaceId: workspace.id, mergeCommandId: input.commandId, landOutcome: kind },
398
414
  });
399
415
 
400
- const recipients = [...new Set([owner, coordinator, fallback].filter((r): r is string => Boolean(r)))];
416
+ const recipients = withWorkspaceTaskOwnerRoles([owner, coordinator, fallback], workspace);
401
417
  if (recipients.length === 0) return;
402
418
  routeNotification({
403
419
  type: "workspace.land_unconfirmed",
@@ -407,7 +423,7 @@ export function notifyLandUnconfirmed(input: LandUnconfirmedInput): void {
407
423
  forceDelivery: true,
408
424
  message: {
409
425
  subject,
410
- body,
426
+ body: `${body} verified: false.`,
411
427
  payload,
412
428
  idempotencyKey: kind === "merge-failed" ? landFailureEventKey(workspace, input.commandId)
413
429
  : `land-unconfirmed:${workspace.id}:${input.commandId ?? "nocmd"}:${kind}`,
@@ -2,15 +2,13 @@
2
2
  // (relay_project_write_entry, relay_store_memory, the project-entry REST routes) resolves the
3
3
  // vocabulary and the stored value here, so the closed vocabulary cannot drift between them.
4
4
 
5
- import { AREAS_FILE, SYSTEM_AREAS, SYSTEM_AREA_DESCRIPTIONS, areasAtCommit, injectsAtStartup, isSystemArea, readAreasAtDefaultBranch, type AreaDefinition, type AreasAtCommit } from "@humwick/sdk/areas";
6
- import { errMessage, stringValue } from "@humwick/sdk";
7
- import { countActivePinnedEntriesInArea, getAgent, rootsForProject, ValidationError } from "../db";
8
- import { localProjectGit, readHostProjectFiles } from "../project-roles";
9
- import { hostIsRelayHost } from "../relay-host-locality";
5
+ import { AREAS_FILE, SYSTEM_AREAS, SYSTEM_AREA_DESCRIPTIONS, areasAtCommit, injectsAtStartup, isSystemArea, type AreaDefinition, type AreasConfig } from "@humwick/sdk/areas";
6
+ import { stringValue } from "@humwick/sdk";
7
+ import { countActivePinnedEntriesInArea, getAgent, ValidationError } from "../db";
10
8
  import type { Project, ProjectEntry } from "../types";
11
9
  import type { AuthContext } from "./auth-context";
12
10
  import { callerHost, callerProjectId } from "./memory-identity";
13
- import { resolveProjectFilesHost } from "./spawn-project-role";
11
+ import { readProjectConfig, readProjectConfigFirst, type ConfigLookup, type ProjectCheckout } from "./project-config-read";
14
12
 
15
13
  export const MAX_ENTRY_AREAS = 4;
16
14
  const PROJECT_WIDE = "project-wide";
@@ -47,42 +45,15 @@ export function areaCallerFromCtx(ctx: AuthContext): AreaCaller {
47
45
  }
48
46
 
49
47
  /** A checkout to read areas.json from: a cwd on a host (a hostname or a registered orchestrator). */
50
- export interface AreasCheckout {
51
- cwd: string;
52
- host?: string;
53
- orchestratorId?: string;
54
- }
55
-
56
- export type AreasLookup = AreasAtCommit | { status: "unreadable"; error: string };
48
+ export type AreasCheckout = ProjectCheckout;
57
49
 
58
- const loggedProblems = new Set<string>();
50
+ export type AreasLookup = ConfigLookup<AreasConfig>;
59
51
 
60
- function logProblemOnce(key: string, problem: string): void {
61
- if (loggedProblems.has(key)) return;
62
- if (loggedProblems.size >= 1000) loggedProblems.clear();
63
- loggedProblems.add(key);
64
- console.warn(`[memory-areas] ${AREAS_FILE} for ${key}: ${problem}; accepting system areas only`);
65
- }
52
+ const ACCEPTING_SYSTEM_AREAS = "accepting system areas only";
66
53
 
67
- /** #3635 — the areas.json at the checkout's default branch (origin/<default>, fetched first), read
68
- * on the checkout's host through the #3043 project-files reader. Never a working tree and never
69
- * cached, so a stale checkout or an old negative answer cannot hide a file that landed. */
70
- export async function readAreasConfig(checkout: AreasCheckout): Promise<AreasLookup> {
71
- const where = `${checkout.orchestratorId ?? checkout.host ?? "local"}:${checkout.cwd}`;
72
- try {
73
- const lookup = !checkout.orchestratorId && hostIsRelayHost(checkout.host)
74
- ? await readAreasAtDefaultBranch(localProjectGit(checkout.cwd))
75
- : areasAtCommit(await readHostProjectFiles(
76
- resolveProjectFilesHost({ subject: AREAS_FILE, cwd: checkout.cwd, orchestratorId: checkout.orchestratorId, preferHost: checkout.host }),
77
- checkout.cwd, { paths: [AREAS_FILE] }, AREAS_FILE,
78
- ));
79
- if (lookup.status === "invalid") logProblemOnce(`${where}@${lookup.sha}`, `invalid: ${lookup.error}`);
80
- return lookup;
81
- } catch (err) {
82
- const error = errMessage(err);
83
- logProblemOnce(where, `unreadable: ${error}`);
84
- return { status: "unreadable", error };
85
- }
54
+ /** #3635 — the areas.json at the checkout's default branch, through the shared project-config reader. */
55
+ export function readAreasConfig(checkout: AreasCheckout): Promise<AreasLookup> {
56
+ return readProjectConfig(checkout, AREAS_FILE, areasAtCommit, ACCEPTING_SYSTEM_AREAS);
86
57
  }
87
58
 
88
59
  function invalidDetail(lookup: Extract<AreasLookup, { status: "invalid" }>): string {
@@ -94,18 +65,11 @@ function invalidDetail(lookup: Extract<AreasLookup, { status: "invalid" }>): str
94
65
  * answers. Every other cell (base/provider/host) takes system areas only, optionally. */
95
66
  export async function resolveAreaVocabulary(anchor: Project, caller: AreaCaller | undefined): Promise<AreaVocabulary> {
96
67
  if (anchor.scope !== "project") return { pathAreas: [], required: false, pathAreasMissing: "not-project" };
97
- const checkouts: AreasCheckout[] = [
98
- ...(caller?.cwd && caller.projectId === anchor.id ? [{ cwd: caller.cwd, host: caller.host }] : []),
99
- ...rootsForProject(anchor.id).map((root) => ({ cwd: root.rootPath, ...(root.orchestratorId ? { orchestratorId: root.orchestratorId } : {}) })),
100
- ];
101
- const failures: string[] = [];
102
- for (const checkout of checkouts) {
103
- const lookup = await readAreasConfig(checkout);
104
- if (lookup.status === "found") return { pathAreas: lookup.config.areas, required: true };
105
- if (lookup.status === "absent") return { pathAreas: [], required: false, pathAreasMissing: "undeclared" };
106
- if (lookup.status === "invalid") return { pathAreas: [], required: false, pathAreasMissing: "invalid", pathAreasDetail: invalidDetail(lookup) };
107
- failures.push(lookup.error);
108
- }
68
+ const preferred = caller?.cwd && caller.projectId === anchor.id ? [{ cwd: caller.cwd, host: caller.host }] : [];
69
+ const { lookup, failures } = await readProjectConfigFirst(anchor.id, preferred, AREAS_FILE, areasAtCommit, ACCEPTING_SYSTEM_AREAS);
70
+ if (lookup?.status === "found") return { pathAreas: lookup.config.areas, required: true };
71
+ if (lookup?.status === "absent") return { pathAreas: [], required: false, pathAreasMissing: "undeclared" };
72
+ if (lookup?.status === "invalid") return { pathAreas: [], required: false, pathAreasMissing: "invalid", pathAreasDetail: invalidDetail(lookup) };
109
73
  return { pathAreas: [], required: false, pathAreasMissing: "unreadable", pathAreasDetail: failures.join("; ") || "the project has no checkout to read it from" };
110
74
  }
111
75
 
@@ -0,0 +1,57 @@
1
+ // #3716 — workflow effects read CI episodes without depending on workflow launch or recovery.
2
+ import { ValidationError } from "../db/connection";
3
+ import { computeCiFailureFingerprint } from "../ci/incidents";
4
+ import type { CiShaObservation } from "../db/ci-incidents";
5
+ import type { StoredPlanCiBlock } from "../db/plan-ci-blocks";
6
+ import { getGateway } from "../db/gateway";
7
+ import { getPlanGraphProjectionAsync } from "../db/plan-graph-async";
8
+ import { planNoticeTarget } from "../db/plan-completion";
9
+ import { notifySystemMessage } from "../notify";
10
+ import type { WorkflowRunRecord } from "@humwick/kernel/workflow.pilot";
11
+
12
+ /** Same watch and landed SHA, or the same failing jobs: an unrelated red main must not quiet this failure. */
13
+ export async function correlatedCiIncidents(rows: Array<{ workflow: string; row?: CiShaObservation }>): Promise<string[]> {
14
+ const ids: string[] = [];
15
+ for (const { workflow, row } of rows) {
16
+ if (!row) return [];
17
+ const incident = await getGateway().read("ciIncidents.activeForWatch", { watchKey: row.watchKey });
18
+ if (!incident) return [];
19
+ if (incident.openedHeadSha !== row.headSha) {
20
+ if (!row.failingJobs?.length) return [];
21
+ const fingerprint = computeCiFailureFingerprint({ workflow, failingRuns: [{ id: "sha", status: "completed", jobs: row.failingJobs.map((job) => ({ id: job.name, name: job.name })) }], conclusion: "failure" });
22
+ if (fingerprint !== incident.fingerprint) return [];
23
+ }
24
+ ids.push(incident.id);
25
+ }
26
+ return [...new Set(ids)].sort();
27
+ }
28
+
29
+ /** Return a quiet receipt only after both the blocked attempt and the actionable plan notice are durable. */
30
+ export async function planCiNotice(run: WorkflowRunRecord, payload: Record<string, unknown>, result = false): Promise<Record<string, unknown> | undefined> {
31
+ const owner = await getGateway().read("planDispatch.owner", { taskId: run.taskId });
32
+ if (!owner || owner.audit) return undefined;
33
+ let block = await getGateway().read("planDispatch.ci.block", { runId: run.id });
34
+ const incidentIds = Array.isArray(payload.incidentIds) ? payload.incidentIds.filter((id): id is string => typeof id === "string") : [];
35
+ if (!result && incidentIds.length && typeof payload.landedSha === "string") {
36
+ try {
37
+ block = await getGateway().write("planDispatch.ci.block", { planId: owner.planId, taskId: run.taskId, runId: run.id, sha: payload.landedSha, incidentIds, waitKey: run.escalation?.waitKey ?? "" }) as unknown as StoredPlanCiBlock;
38
+ } catch (error) {
39
+ // A superseded attempt must still send its normal CI notice (#3660).
40
+ if (error instanceof ValidationError && error.message === "CI blocker no longer owns its plan slot") return undefined;
41
+ throw error;
42
+ }
43
+ }
44
+ if (!block || (result && (block.retriedAt !== undefined || block.waitKey !== run.escalation?.waitKey)) || (!result && !incidentIds.length)) return undefined;
45
+ const plan = await getPlanGraphProjectionAsync({ planId: owner.planId });
46
+ const to = plan && planNoticeTarget(plan);
47
+ if (!to) return undefined;
48
+ for (const incidentId of block.incidentIds) {
49
+ notifySystemMessage(to, {
50
+ subject: `Plan ${plan.title ?? plan.id}: CI blocked`,
51
+ body: `Plan ${plan.id}: T${run.taskId}, run ${run.id}, landed SHA ${block.sha}, is blocked by CI incident ${incidentId}. Fix the incident; Relay automatically retries each blocked run's exact landed SHA after clearance. Slots and dependents wait. relay_plan_get shows every blocked Task and run.`,
52
+ idempotencyKey: `plan:${plan.id}:ci:${incidentId}`, replyExpected: false,
53
+ payload: { kind: "plan.ci-blocked", planId: plan.id, incidentId, taskId: run.taskId, runId: run.id, landedSha: block.sha },
54
+ });
55
+ }
56
+ return { quiet: "plan-ci-episode", planId: owner.planId, incidentIds: block.incidentIds };
57
+ }
@@ -1,62 +1,9 @@
1
- // #3660 — correlate only known CI episodes; clearance reuses the original run's CI resume path.
2
- import { ValidationError } from "../db/connection";
3
- import { computeCiFailureFingerprint } from "../ci/incidents";
4
- import type { CiShaObservation } from "../db/ci-incidents";
1
+ // #3660 — clearance reuses the original run's CI resume path.
2
+ import type { WorkflowRunRecord } from "@humwick/kernel/workflow.pilot";
5
3
  import type { StoredPlanCiBlock } from "../db/plan-ci-blocks";
6
4
  import { getGateway } from "../db/gateway";
7
- import { getPlanGraphProjectionAsync } from "../db/plan-graph-async";
8
- import { planNoticeTarget } from "../db/plan-completion";
9
- import { notifySystemMessage } from "../notify";
10
- import type { WorkflowRunRecord } from "@humwick/kernel/workflow.pilot";
11
5
  import { resumeWorkflowRun } from "./workflow-runs";
12
6
 
13
- /** Same watch and landed SHA, or the same failing jobs: an unrelated red main must not quiet this failure. */
14
- export async function correlatedCiIncidents(rows: Array<{ workflow: string; row?: CiShaObservation }>): Promise<string[]> {
15
- const ids: string[] = [];
16
- for (const { workflow, row } of rows) {
17
- if (!row) return [];
18
- const incident = await getGateway().read("ciIncidents.activeForWatch", { watchKey: row.watchKey });
19
- if (!incident) return [];
20
- if (incident.openedHeadSha !== row.headSha) {
21
- if (!row.failingJobs?.length) return [];
22
- const fingerprint = computeCiFailureFingerprint({ workflow, failingRuns: [{ id: "sha", status: "completed", jobs: row.failingJobs.map((job) => ({ id: job.name, name: job.name })) }], conclusion: "failure" });
23
- if (fingerprint !== incident.fingerprint) return [];
24
- }
25
- ids.push(incident.id);
26
- }
27
- return [...new Set(ids)].sort();
28
- }
29
-
30
- /** Return a quiet receipt only after both the blocked attempt and the actionable plan notice are durable. */
31
- export async function planCiNotice(run: WorkflowRunRecord, payload: Record<string, unknown>, result = false): Promise<Record<string, unknown> | undefined> {
32
- const owner = await getGateway().read("planDispatch.owner", { taskId: run.taskId });
33
- if (!owner || owner.audit) return undefined;
34
- let block = await getGateway().read("planDispatch.ci.block", { runId: run.id });
35
- const incidentIds = Array.isArray(payload.incidentIds) ? payload.incidentIds.filter((id): id is string => typeof id === "string") : [];
36
- if (!result && incidentIds.length && typeof payload.landedSha === "string") {
37
- try {
38
- block = await getGateway().write("planDispatch.ci.block", { planId: owner.planId, taskId: run.taskId, runId: run.id, sha: payload.landedSha, incidentIds, waitKey: run.escalation?.waitKey ?? "" }) as unknown as StoredPlanCiBlock;
39
- } catch (error) {
40
- // A superseded attempt must still send its normal CI notice (#3660).
41
- if (error instanceof ValidationError && error.message === "CI blocker no longer owns its plan slot") return undefined;
42
- throw error;
43
- }
44
- }
45
- if (!block || (result && (block.retriedAt !== undefined || block.waitKey !== run.escalation?.waitKey)) || (!result && !incidentIds.length)) return undefined;
46
- const plan = await getPlanGraphProjectionAsync({ planId: owner.planId });
47
- const to = plan && planNoticeTarget(plan);
48
- if (!to) return undefined;
49
- for (const incidentId of block.incidentIds) {
50
- notifySystemMessage(to, {
51
- subject: `Plan ${plan.title ?? plan.id}: CI blocked`,
52
- body: `Plan ${plan.id}: T${run.taskId}, run ${run.id}, landed SHA ${block.sha}, is blocked by CI incident ${incidentId}. Fix the incident; Relay automatically retries each blocked run's exact landed SHA after clearance. Slots and dependents wait. relay_plan_get shows every blocked Task and run.`,
53
- idempotencyKey: `plan:${plan.id}:ci:${incidentId}`, replyExpected: false,
54
- payload: { kind: "plan.ci-blocked", planId: plan.id, incidentId, taskId: run.taskId, runId: run.id, landedSha: block.sha },
55
- });
56
- }
57
- return { quiet: "plan-ci-episode", planId: owner.planId, incidentIds: block.incidentIds };
58
- }
59
-
60
7
  /** A kernel resume note is the durable receipt if the process dies before marking the plan's row. */
61
8
  export async function recoverPlanCiBlock(run: WorkflowRunRecord): Promise<StoredPlanCiBlock | null> {
62
9
  const block = await getGateway().read("planDispatch.ci.block", { runId: run.id });
@@ -70,6 +70,7 @@ export function renderPlanWakeDigest(agentId: string, now = Date.now()): string
70
70
  do {
71
71
  const page = listPlansByTeam(teamId, { limit: 8, cursor });
72
72
  for (const summary of page.plans) {
73
+ if (!summary.nodeCount) continue;
73
74
  const plan = getPlan(summary.id);
74
75
  if (!plan) continue;
75
76
  const line = planLine(plan, now);
@@ -1,7 +1,8 @@
1
1
  // #3636 — project trust edits without the dashboard: set a profile's review mode, acknowledge its
2
2
  // project hooks/MCP, approve an observed value. One seam for the HTTP routes and the MCP tool.
3
3
  import { ValidationError, type AgentProfile, type ProfileProjectReviewMode } from "@humwick/sdk";
4
- import { getAgentProfile, setAgentProfile, validateAgentProfile } from "../config-store";
4
+ import { getAgentProfile, validateAgentProfile } from "../config-store";
5
+ import { setAgentProfileAsync } from "../config-store-write";
5
6
  import type { ConfigEntry } from "../types";
6
7
  import type { ProjectTrustApproval, ProjectTrustCapability } from "../db/project-trust-approvals";
7
8
  import { approveProjectCapabilityReview, projectCellsHash } from "../project-trust";
@@ -38,9 +39,9 @@ export function withProjectTrust(profile: AgentProfile, update: ProjectTrustUpda
38
39
  return validated;
39
40
  }
40
41
 
41
- export function updateProfileProjectTrust(name: string, update: ProjectTrustUpdate, by: string): ConfigEntry<AgentProfile> {
42
+ export async function updateProfileProjectTrustAsync(name: string, update: ProjectTrustUpdate, by: string): Promise<ConfigEntry<AgentProfile>> {
42
43
  if (!update.review && !update.ack?.length) throw new ValidationError("nothing to update: pass review and/or ack");
43
- const saved = setAgentProfile(withProjectTrust(existingProfile(name).value, update, by), by, { overrideBuiltIn: true });
44
+ const saved = await setAgentProfileAsync(withProjectTrust(existingProfile(name).value, update, by), by, { overrideBuiltIn: true });
44
45
  emitConfigChanged(saved.namespace, saved.key, saved.version);
45
46
  return saved;
46
47
  }
@@ -0,0 +1,70 @@
1
+ // #3635/#3736 — one reader for `.agent-relay/*.json` project config (areas.json, spawn-policy.json): the
2
+ // file at the checkout's default branch (origin/<default>, fetched first), read on the checkout's host
3
+ // through the #3043 project-files reader. Never a working tree and never cached, so a stale checkout or
4
+ // an old negative answer cannot hide a file that landed.
5
+ import { readProjectFiles } from "@humwick/sdk";
6
+ import type { ConfigAtCommit } from "@humwick/sdk/repo-config-file";
7
+ import { errMessage } from "@humwick/sdk";
8
+ import { rootsForProject } from "../db";
9
+ import { localProjectGit, readHostProjectFiles } from "../project-roles";
10
+ import { hostIsRelayHost } from "../relay-host-locality";
11
+ import { resolveProjectFilesHost } from "./spawn-project-role";
12
+
13
+ /** A checkout to read from: a cwd on a host (a hostname or a registered orchestrator). */
14
+ export interface ProjectCheckout {
15
+ cwd: string;
16
+ host?: string;
17
+ orchestratorId?: string;
18
+ }
19
+
20
+ export type ConfigLookup<T> = ConfigAtCommit<T> | { status: "unreadable"; error: string };
21
+
22
+ type Snapshot = { ref: string; sha: string; files: Record<string, string | null> };
23
+
24
+ const warned = new Set<string>();
25
+
26
+ /** One warning per distinct problem, so a broken file does not flood the log on every read. */
27
+ export function warnConfigOnce(key: string, message: string): void {
28
+ if (warned.has(key)) return;
29
+ if (warned.size >= 1000) warned.clear();
30
+ warned.add(key);
31
+ console.warn(message);
32
+ }
33
+
34
+ /** `file` at the checkout's default branch; a read failure is `unreadable`, never a throw. Invalid and
35
+ * unreadable outcomes warn once, naming `file`, the checkout and why, with `consequence` appended. */
36
+ export async function readProjectConfig<T>(checkout: ProjectCheckout, file: string, parse: (snapshot: Snapshot) => ConfigAtCommit<T>, consequence: string): Promise<ConfigLookup<T>> {
37
+ const where = `${checkout.orchestratorId ?? checkout.host ?? "local"}:${checkout.cwd}`;
38
+ try {
39
+ const snapshot = !checkout.orchestratorId && hostIsRelayHost(checkout.host)
40
+ ? await readProjectFiles({ paths: [file] }, localProjectGit(checkout.cwd))
41
+ : await readHostProjectFiles(
42
+ resolveProjectFilesHost({ subject: file, cwd: checkout.cwd, orchestratorId: checkout.orchestratorId, preferHost: checkout.host }),
43
+ checkout.cwd, { paths: [file] }, file,
44
+ );
45
+ const lookup = parse(snapshot);
46
+ if (lookup.status === "invalid") warnConfigOnce(`${file}@${where}@${lookup.sha}`, `[project-config] ${file} for ${where} at ${lookup.sha.slice(0, 12)} is invalid: ${lookup.error}; ${consequence}`);
47
+ return lookup;
48
+ } catch (err) {
49
+ const error = errMessage(err);
50
+ warnConfigOnce(`${file}@${where}`, `[project-config] ${file} for ${where}: unreadable: ${error}; ${consequence}`);
51
+ return { status: "unreadable", error };
52
+ }
53
+ }
54
+
55
+ /** The first checkout that can be read answers: the preferred ones, then each registered root of the project. */
56
+ export async function readProjectConfigFirst<T>(
57
+ projectId: string | undefined, preferred: readonly ProjectCheckout[], file: string, parse: (snapshot: Snapshot) => ConfigAtCommit<T>, consequence: string,
58
+ ): Promise<{ lookup?: ConfigAtCommit<T>; failures: string[] }> {
59
+ const checkouts: ProjectCheckout[] = [
60
+ ...preferred,
61
+ ...(projectId ? rootsForProject(projectId).map((root) => ({ cwd: root.rootPath, ...(root.orchestratorId ? { orchestratorId: root.orchestratorId } : {}) })) : []),
62
+ ];
63
+ const failures: string[] = [];
64
+ for (const checkout of checkouts) {
65
+ const lookup = await readProjectConfig(checkout, file, parse, consequence);
66
+ if (lookup.status !== "unreadable") return { lookup, failures };
67
+ failures.push(lookup.error);
68
+ }
69
+ return { failures };
70
+ }
@@ -0,0 +1,54 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { hasScope, isRecord } from "@humwick/sdk";
3
+ import type { ProjectProfileInstructions } from "../types";
4
+ import { listAgentProfiles, resolveAgentProfileKey } from "../config-store";
5
+ import { getGateway } from "../db/gateway";
6
+ import { ValidationError } from "../db/connection";
7
+ import { promptActorFromContext, promptVersionNumber } from "./prompt-version-writer";
8
+ import type { AuthContext } from "./auth-context";
9
+ import { cleanString } from "../validation";
10
+ import { emitConfigChanged } from "../sse";
11
+
12
+ export async function projectProfileInstructionKey(projectId: string, profile: string) {
13
+ const canonical = resolveAgentProfileKey(profile);
14
+ if (!canonical) throw new ValidationError("profile not found");
15
+ if (!await getGateway().read("projects.get", { id: projectId })) throw new ValidationError("project not found");
16
+ return { projectId, profile: canonical };
17
+ }
18
+
19
+ export async function readProjectProfileInstructions(projectId: string, profile: string) {
20
+ return getGateway().read("projectProfileInstructions.get", await projectProfileInstructionKey(projectId, profile));
21
+ }
22
+
23
+ export async function projectProfileInstructionHistory(projectId: string, profile: string) {
24
+ return { versions: await getGateway().read("projectProfileInstructions.history", await projectProfileInstructionKey(projectId, profile)) };
25
+ }
26
+
27
+ export async function listProjectProfileInstructions(filter: { projectId?: string; profile?: string }) {
28
+ const gateway = getGateway();
29
+ const profile = filter.profile ? resolveAgentProfileKey(filter.profile) : undefined;
30
+ if (filter.profile && !profile) throw new ValidationError("profile not found");
31
+ if (filter.projectId && !await gateway.read("projects.get", { id: filter.projectId })) throw new ValidationError("project not found");
32
+ if (!filter.projectId && !profile) throw new ValidationError("projectId or profile required");
33
+ const stored = await gateway.read("projectProfileInstructions.list", { projectId: filter.projectId, profile });
34
+ const byKey = new Map(stored.map(entry => [JSON.stringify([entry.projectId, entry.profile]), entry]));
35
+ const entry = (projectId: string, profile: string): ProjectProfileInstructions => byKey.get(JSON.stringify([projectId, profile])) ?? { projectId, profile, template: "", version: 0, updatedAt: 0 };
36
+ return { instructions: filter.projectId
37
+ ? listAgentProfiles().filter(item => !profile || item.key === profile).map(item => entry(filter.projectId!, item.key))
38
+ : (await gateway.read("projects.list", { includeArchived: true })).map(project => ({ ...entry(project.id, profile!), projectTitle: project.title, projectSlug: project.slug })) };
39
+ }
40
+
41
+ export async function mutateProjectProfileInstructions(ctx: AuthContext, projectId: string, profile: string, input: unknown, restore = false) {
42
+ if (ctx.actor.kind !== "system" && !hasScope(ctx.scopes, "agent:write")) throw new ValidationError("agent:write scope required");
43
+ const key = await projectProfileInstructionKey(projectId, profile);
44
+ if (!isRecord(input)) throw new ValidationError("JSON object body required");
45
+ if (!Number.isSafeInteger(input.expectedVersion) || (input.expectedVersion as number) < 0) throw new ValidationError("expectedVersion must be a non-negative integer");
46
+ const result = await getGateway().write("projectProfileInstructions.write", {
47
+ ...key, expectedVersion: input.expectedVersion as number,
48
+ template: restore ? undefined : input.template as string,
49
+ restoreVersion: restore ? promptVersionNumber(input.version, "version") : undefined,
50
+ actor: promptActorFromContext(ctx), reason: cleanString(input.reason, "reason", { max: 2000 }),
51
+ }, { idempotencyKey: randomUUID() }) as unknown as ProjectProfileInstructions;
52
+ emitConfigChanged("project-profile-instructions", `${key.projectId}:${key.profile}`, result.version);
53
+ return result;
54
+ }
@@ -19,7 +19,7 @@
19
19
  "unknown": 0
20
20
  },
21
21
  "mcp:relay-schemas": {
22
- "residentKnown": 24523,
22
+ "residentKnown": 24574,
23
23
  "onDemandAvailable": 0,
24
24
  "unknown": 0
25
25
  },
@@ -114,7 +114,7 @@
114
114
  "unknown": 0
115
115
  },
116
116
  "mcp:relay-schemas": {
117
- "residentKnown": 24523,
117
+ "residentKnown": 24574,
118
118
  "onDemandAvailable": 0,
119
119
  "unknown": 0
120
120
  },
@@ -364,11 +364,11 @@
364
364
  "reviewer:codex": 11221,
365
365
  "sec-ops:claude": 11610,
366
366
  "sec-ops:codex": 10644,
367
- "ops-assistant:claude": 25307,
367
+ "ops-assistant:claude": 25390,
368
368
  "ops-assistant:codex": 25562,
369
369
  "sub-coordinator:claude": 20137,
370
370
  "sub-coordinator:codex": 20392,
371
- "coordinator:claude": 26501,
371
+ "coordinator:claude": 26584,
372
372
  "coordinator:codex": 26622,
373
373
  "isolated-research:claude": 2852,
374
374
  "isolated-research:codex": 2790,