@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/dev.ts CHANGED
@@ -1,7 +1,8 @@
1
1
  import { writeChildEnv } from "@humwick/sdk/env";
2
+ import { instanceDirectoryEnv } from "@humwick/sdk/isolated-env";
2
3
  import { execFile } from "node:child_process";
3
4
  import { shellQuote } from "@humwick/sdk/shell-utils";
4
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
5
+ import { existsSync, readFileSync } from "node:fs";
5
6
  import { mkdir, rm, writeFile } from "node:fs/promises";
6
7
  import { homedir, hostname as osHostname } from "node:os";
7
8
  import { dirname, join, resolve } from "node:path";
@@ -9,6 +10,7 @@ import { promisify } from "node:util";
9
10
  import { packPackage, privateWorkspaceDependencies, workspaceDependencyClosure } from "./package-pack";
10
11
  import { generateToken, renderEnvFile } from "./setup";
11
12
  import { createDaemonPlan, executeDaemonPlan, MANAGED_MARKER } from "./daemon";
13
+ import { npmInstallIntoPrefix } from "./runtime-install";
12
14
 
13
15
  const execFileAsync = promisify(execFile);
14
16
  const DEV_MARKER = "agent-relay-dev-profile";
@@ -148,13 +150,10 @@ export function createDevInstallPlan(options: {
148
150
  export async function executeDevInstallPlan(plan: DevInstallPlan, options: { dryRun?: boolean } = {}): Promise<DevInstallResult> {
149
151
  if (options.dryRun) return { plan, tarballs: [], output: formatDevInstallPlan(plan) };
150
152
  const packed = await executeDevPackPlan(plan.packPlan);
151
- await mkdir(plan.prefix, { recursive: true });
152
- ensureDevPrefixPackageJson(plan.prefix);
153
- const command = ["install", "--prefix", plan.prefix, "--no-audit", "--no-fund", "--force", ...packed.tarballs];
154
- const result = await execFileAsync("npm", command, { cwd: plan.prefix });
153
+ const installed = await npmInstallIntoPrefix(plan.prefix, packed.tarballs);
155
154
  const output = [
156
155
  packed.output,
157
- result.stdout.trim(),
156
+ installed,
158
157
  `Installed into ${plan.prefix}`,
159
158
  ].filter(Boolean).join("\n");
160
159
  return { plan, tarballs: packed.tarballs, output };
@@ -209,7 +208,8 @@ export function createDevServicePlan(options: {
209
208
  }
210
209
  const envContent = `# ${DEV_MARKER}\n` + renderEnvFile(writeChildEnv({}, {
211
210
  AGENT_RELAY_TOKEN: token,
212
- AGENT_RELAY_HOME: rootDir,
211
+ ...instanceDirectoryEnv(rootDir, prefix),
212
+ AGENT_RELAY_DEV_PROFILE: "1",
213
213
  DB_PATH: join(rootDir, "state", "agent-relay.db"),
214
214
  HOST: host,
215
215
  PORT: String(port),
@@ -229,6 +229,7 @@ export function createDevServicePlan(options: {
229
229
  apiPort,
230
230
  tmuxPrefix: "ar-dev",
231
231
  env: writeChildEnv({}, {
232
+ ...instanceDirectoryEnv(rootDir, prefix),
232
233
  AGENT_RELAY_DEV_PROFILE: "1",
233
234
  }),
234
235
  };
@@ -296,8 +297,10 @@ export async function executeDevServicePlan(plan: DevServicePlan, options: { dry
296
297
  const output: string[] = [];
297
298
  if (plan.action === "start" || plan.action === "restart") {
298
299
  // Upgrade installed environments before either service can adopt execution records (#3139).
299
- const existing = readFileSync(plan.envFile, "utf8").replace(/^(?:export\s+)?AGENT_RELAY_HOME=.*$/gm, "").trimEnd();
300
- await writeFile(plan.envFile, `${existing}\n${Object.entries(writeChildEnv({}, { AGENT_RELAY_HOME: plan.rootDir })).map(([key, value]) => `${key}=${shellQuote(value)}\n`).join("")}`, "utf8");
300
+ const paths = writeChildEnv(instanceDirectoryEnv(plan.rootDir, plan.prefix), { AGENT_RELAY_DEV_PROFILE: "1" });
301
+ const keys = Object.keys(paths).map((key) => key.replace("AGENT_RELAY_", "(?:AGENT_RELAY_|HUMWICK_)"));
302
+ const existing = readFileSync(plan.envFile, "utf8").replace(new RegExp(`^(?:export\\s+)?(?:${keys.join("|")})=.*$`, "gm"), "").trimEnd();
303
+ await writeFile(plan.envFile, `${existing}\n${Object.entries(paths).map(([key, value]) => `${key}=${shellQuote(value)}\n`).join("")}`, "utf8");
301
304
  }
302
305
  if (plan.action === "install") {
303
306
  for (const file of plan.files) {
@@ -421,13 +424,6 @@ function devEnvFile(rootDir = defaultDevRoot()): string {
421
424
  return join(rootDir, "env");
422
425
  }
423
426
 
424
- function ensureDevPrefixPackageJson(prefix: string): void {
425
- mkdirSync(prefix, { recursive: true });
426
- const path = join(prefix, "package.json");
427
- if (existsSync(path)) return;
428
- writeFileSync(path, JSON.stringify({ private: true, name: "agent-relay-dev-install" }, null, 2) + "\n", "utf8");
429
- }
430
-
431
427
  function devServiceCommands(action: DevServiceAction, options: {
432
428
  serverUnit: string;
433
429
  orchestratorUnit: string;
@@ -1,3 +1,4 @@
1
+ import { trackRelayEvent } from "./relay-event-drain";
1
2
  import { errMessage } from "@humwick/sdk";
2
3
  import type { HealthCheck } from "./types";
3
4
  import {
@@ -166,7 +167,7 @@ export function setLagRegressionObserver(fn: EventLoopLagRegressionObserver | un
166
167
  * threshold rather than the diagnostic log threshold, and is a separate registration a caller can
167
168
  * leave unset (e.g. ephemeral mode) with no effect.
168
169
  */
169
- export type EventLoopLagBreakerObserver = (gapStartAt: number, gapEndAt: number, lagMs: number) => void;
170
+ export type EventLoopLagBreakerObserver = (gapStartAt: number, gapEndAt: number, lagMs: number) => void | Promise<void>;
170
171
 
171
172
  let breakerObserver: EventLoopLagBreakerObserver | undefined;
172
173
 
@@ -283,7 +284,8 @@ export function recordEventLoopLagSample(
283
284
  // timer callback, so a throw here would become an uncaught exception in the interval — guard it:
284
285
  // the breaker degrading is never worth killing the loop-lag sampler that watches for the freeze.
285
286
  try {
286
- breakerObserver(sampledAt - Math.max(0, elapsedMs), sampledAt, normalized);
287
+ const outcome = breakerObserver(sampledAt - Math.max(0, elapsedMs), sampledAt, normalized);
288
+ if (outcome) trackRelayEvent(outcome, "maintenance.lag-breaker");
287
289
  } catch (error) {
288
290
  console.warn(`[health] event-loop-lag breaker observer threw: ${errMessage(error)}`);
289
291
  }
@@ -11,7 +11,7 @@ export function yieldToEventLoop(): Promise<void> {
11
11
  * `moreWork`, so a backlog drains over runs instead of overrunning the deadline (a breaker strike). */
12
12
  export async function forEachPaced<T>(
13
13
  items: () => Iterable<T>,
14
- unit: (item: T) => void,
14
+ unit: (item: T) => unknown,
15
15
  opts: { signal?: AbortSignal; budgetMs: number; ready?: () => boolean },
16
16
  ): Promise<{ moreWork: boolean }> {
17
17
  await yieldToEventLoop();
@@ -20,7 +20,7 @@ export async function forEachPaced<T>(
20
20
  for (const item of items()) {
21
21
  if (opts.signal?.aborted) return { moreWork: false };
22
22
  if (performance.now() >= stopAt || opts.ready?.() === false) return { moreWork: true };
23
- unit(item);
23
+ await unit(item);
24
24
  await yieldToEventLoop();
25
25
  }
26
26
  return { moreWork: false };
package/src/events.ts CHANGED
@@ -19,7 +19,7 @@ export interface RelayEvent {
19
19
 
20
20
  type Listener = (event: RelayEvent) => void;
21
21
 
22
- const listeners = new Set<Listener>();
22
+ const listeners = new Map<Listener, string>();
23
23
  const fanoutQueue: Array<{ event: RelayEvent; lastSeq: number }> = [];
24
24
  let notifying = false;
25
25
 
@@ -33,12 +33,13 @@ export function emitRelayEvent(input: Omit<RelayEvent, "seq" | "timestamp">): Pr
33
33
  const appended = appendRelayEventInternal(input.type, input.source, input.data, input.subject, timestamp)!;
34
34
  return Promise.resolve(fanOutAppendedEvent(input, appended, timestamp));
35
35
  }
36
- return emitRelayEventAsync(structuredClone(input), { idempotencyKey: crypto.randomUUID() });
36
+ // #3614 — keyless: nothing resubmits this append, so a ledger row would only cost writes.
37
+ return trackRelayEvent(appendAndFanOut(structuredClone(input), {}), input.type);
37
38
  });
38
39
  }
39
40
 
40
41
  /** #3606 — fan out to the current listeners only: no bus_outbox row, no idempotency-ledger row, no
41
- * replay. For state the next tick supersedes anyway (dashboard viewer presence). */
42
+ * replay. For state the next tick supersedes anyway (dashboard viewer presence, agent liveness ticks). */
42
43
  export function emitEphemeralRelayEvent(input: Omit<RelayEvent, "seq" | "timestamp" | "ephemeral">): RelayEvent {
43
44
  return fanOutAppendedEvent({ ...input, ephemeral: true }, { seq: 0, lastSeq: 0 }, Date.now());
44
45
  }
@@ -71,7 +72,8 @@ async function appendAndFanOut(
71
72
  } catch (error) {
72
73
  // H5: a lost acknowledgement says nothing about commit. Re-submit the exact logical append
73
74
  // with the same durable key; the writer returns the first result without a second insert.
74
- if (!(error instanceof WriteIndeterminateError)) throw error;
75
+ // A keyless emit is at-most-once: it surfaces the error, and a committed row stays replayable.
76
+ if (!(error instanceof WriteIndeterminateError) || !options.idempotencyKey) throw error;
75
77
  appended = await getGateway().write("bus.append", args, { critical: true, idempotencyKey: options.idempotencyKey }) as unknown as typeof appended;
76
78
  }
77
79
  // A retry ledger result proves durable storage, not that this process owns a fresh live edge.
@@ -122,19 +124,19 @@ export function fanOutAppendedEvent(
122
124
 
123
125
  function notify(event: RelayEvent): void {
124
126
  measureEmit(event.type, true, () => {
125
- for (const listener of listeners) {
127
+ for (const [listener, name] of listeners) {
126
128
  try {
127
129
  listener(event);
128
130
  } catch (error) {
129
131
  // Event projections are isolated so one transport cannot block another; a failed one (e.g. a
130
132
  // BUSY write in a projection, #3643) is logged, not swallowed.
131
- console.warn(`[events] ${event.type} listener failed: ${errMessage(error)}`);
133
+ console.warn(`[events] ${event.type} listener ${name} failed: ${errMessage(error)}`, error instanceof Error ? error.stack : error);
132
134
  }
133
135
  }
134
136
  });
135
137
  }
136
138
 
137
- export function subscribeRelayEvents(listener: Listener): () => void {
138
- listeners.add(listener);
139
+ export function subscribeRelayEvents(listener: Listener, name = listener.name || "<anonymous>"): () => void {
140
+ listeners.set(listener, name);
139
141
  return () => listeners.delete(listener);
140
142
  }
@@ -1,8 +1,9 @@
1
+ import { setConfigAsync } from "./config-store-write";
1
2
  // #2855 — DB-backed readers for per-host provider enablement. Separate from
2
3
  // host-provider-enablement.ts (a config-store-imported leaf) so this module can
3
4
  // import getConfig/listConfig without closing a cycle.
4
5
  import { errMessage, type SpawnProvider } from "@humwick/sdk";
5
- import { getConfig, listConfig, setConfig } from "./config-store";
6
+ import { getConfig, listConfig } from "./config-store";
6
7
  import { listOrchestratorIds } from "./db";
7
8
  import {
8
9
  HOST_PROVIDER_ENABLEMENT_NAMESPACE,
@@ -75,11 +76,11 @@ export function hostProviderRefusal(hostId: string, provider: SpawnProvider): st
75
76
  /** One-time env migration: missing/unmarked rows merge omitted disables
76
77
  * without touching operator entries. In-env opt-ins store explicit true;
77
78
  * everything else outside env seeds as disabled. */
78
- export function seedHostProvidersFromEnv(
79
+ export async function seedHostProvidersFromEnv(
79
80
  hostId: string,
80
81
  envProviders: readonly string[],
81
82
  switchableCatalog: readonly SpawnProvider[],
82
- ): HostProviderEnablementSetting | null {
83
+ ): Promise<HostProviderEnablementSetting | null> {
83
84
  const existing = getHostProviderSetting(hostId);
84
85
  if (existing?.seeded) return null;
85
86
  const env = new Set(envProviders);
@@ -98,5 +99,5 @@ export function seedHostProvidersFromEnv(
98
99
  providers[provider] = { enabled: false, reason: "migrated from host env list" };
99
100
  }
100
101
  }
101
- return setConfig(HOST_PROVIDER_ENABLEMENT_NAMESPACE, hostId, { providers, seeded: true }, "migration-seed").value;
102
+ return (await setConfigAsync(HOST_PROVIDER_ENABLEMENT_NAMESPACE, hostId, { providers, seeded: true }, "migration-seed")).value;
102
103
  }
@@ -20,6 +20,7 @@ export function hubMetricsReport(now = Date.now()) {
20
20
  const emits = emitTable.snapshot(now);
21
21
  const readerOps = readerOpTable.snapshot(now);
22
22
  const cpuMs = callers.reduce((total, row) => total + row.cpuMs, 0);
23
+ const backgroundCpuMs = callers.find((row) => row.key === BACKGROUND_KEY)?.cpuMs ?? 0;
23
24
  const calls = callers.reduce((total, row) => total + row.count, 0);
24
25
  return {
25
26
  generatedAt: now,
@@ -28,7 +29,9 @@ export function hubMetricsReport(now = Date.now()) {
28
29
  mainThread: {
29
30
  cpuMs: Math.round(cpuMs),
30
31
  cpuPct: Math.round((cpuMs / windowMs) * 10_000) / 100,
31
- backgroundCpuMs: Math.round(callers.find((row) => row.key === BACKGROUND_KEY)?.cpuMs ?? 0),
32
+ backgroundCpuMs: Math.round(backgroundCpuMs),
33
+ // #3798 — the share of main-thread CPU a named frame (route, bus frame, timer, job) accounts for.
34
+ namedCpuPct: cpuMs > 0 ? Math.round(((cpuMs - backgroundCpuMs) / cpuMs) * 10_000) / 100 : 100,
32
35
  },
33
36
  callers: {
34
37
  count: calls,
@@ -62,6 +65,7 @@ export function hubLoadCheck(now = Date.now()): HealthCheck {
62
65
  busConnections: report.connections.bus,
63
66
  callsPerSec: report.callers.perSec,
64
67
  mainThreadCpuPct: report.mainThread.cpuPct,
68
+ mainThreadNamedCpuPct: report.mainThread.namedCpuPct,
65
69
  },
66
70
  };
67
71
  }
@@ -4,7 +4,7 @@
4
4
  // so caller CPU is split at marks (caller start/end, reader resumes): the CPU between two marks goes
5
5
  // to the caller that ran. docs/operations.md "Hub load metrics" describes the model and its limits.
6
6
 
7
- import { runWithLoopCallsite } from "./loop-block-attribution";
7
+ import { resolveLoopCallsite, runWithLoopCallsite, type LoopCallsiteContext } from "./loop-block-attribution";
8
8
 
9
9
  const BUCKET_MS = 10_000;
10
10
  const BUCKETS = 6;
@@ -109,25 +109,45 @@ export function markCallerResume(owner: string | undefined): void {
109
109
  ledgerOwner = owner;
110
110
  }
111
111
 
112
- /** Run `run` as caller `key`: one request or frame, its wall time and its main-thread CPU segments. */
113
- export async function trackCaller<T>(key: string, run: () => T | Promise<T>): Promise<T> {
114
- const startedAt = performance.now();
115
- markCallerResume(key);
116
- try {
117
- return await run();
118
- } finally {
119
- // The code finishing the caller is the caller itself, so the segment since the last mark is its own.
120
- const now = Date.now();
121
- settleCpu(key, now);
122
- ledgerOwner = undefined;
123
- callerTable.add(key, now, 1, performance.now() - startedAt);
124
- }
112
+ /** #3798 — run `run` as named frame `ctx.callsite` (request, bus frame, timer tick, job): its count, wall
113
+ * time and main-thread CPU. Opens the loop callsite scope, so reader and writer results inside it are
114
+ * charged back to it. Returns what `run` returns (a sync body stays sync); on exit the ledger goes back
115
+ * to the enclosing frame (a job inside the scheduler tick). */
116
+ export function runInFrame<T>(ctx: LoopCallsiteContext & { callsite: string }, run: () => T): T {
117
+ const outer = resolveLoopCallsite();
118
+ return runWithLoopCallsite(ctx, () => {
119
+ const key = ctx.callsite;
120
+ const startedAt = performance.now();
121
+ markCallerResume(key);
122
+ const end = () => {
123
+ // The code finishing the frame is the frame itself, so the segment since the last mark is its own.
124
+ const now = Date.now();
125
+ settleCpu(key, now);
126
+ ledgerOwner = outer;
127
+ callerTable.add(key, now, 1, performance.now() - startedAt);
128
+ };
129
+ let result: T;
130
+ try {
131
+ result = run();
132
+ } catch (error) {
133
+ end();
134
+ throw error;
135
+ }
136
+ if (result instanceof Promise) return result.finally(end) as T;
137
+ end();
138
+ return result;
139
+ });
140
+ }
141
+
142
+ /** `runInFrame` as a callback, for a timer or interval body: `setInterval(framed("timer x", tick), ms)`. */
143
+ export function framed<A extends unknown[], T>(key: string, fn: (...args: A) => T): (...args: A) => T {
144
+ const ctx = { callsite: key };
145
+ return (...args: A) => runInFrame(ctx, () => fn(...args));
125
146
  }
126
147
 
127
- /** `trackCaller` for a caller with no request scope of its own (a bus frame): also opens the loop
128
- * callsite scope, so reader resumes and on-loop frames inside it are attributed to it. */
129
- export function runAsCaller<T>(callsite: string, run: () => T | Promise<T>): Promise<T> {
130
- return runWithLoopCallsite({ callsite }, () => trackCaller(callsite, run));
148
+ /** A bus frame: always a promise, so a sync throw reaches the frame queue's `catch`. */
149
+ export async function runAsCaller<T>(callsite: string, run: () => T | Promise<T>): Promise<T> {
150
+ return runInFrame({ callsite }, run);
131
151
  }
132
152
 
133
153
  // ── Emits ─────────────────────────────────────────────────────────────────────────────────────────
package/src/init.ts ADDED
@@ -0,0 +1,299 @@
1
+ // `humwick init` (#2680): one resumable install of a persistent server. It composes the existing
2
+ // primitives (setup env file, runtime install, daemon planner) and ends with a one-time dashboard
3
+ // login link, so nobody copies the admin token. Every failure names its step and a repair command.
4
+ import { execFile } from "node:child_process";
5
+ import { createHash } from "node:crypto";
6
+ import { copyFileSync, existsSync, mkdirSync, readdirSync, readFileSync, rmSync } from "node:fs";
7
+ import { connect } from "node:net";
8
+ import { homedir, hostname, userInfo } from "node:os";
9
+ import { dirname, join, resolve } from "node:path";
10
+ import { promisify } from "node:util";
11
+ import { RELAY_TOKEN_HEADER, errMessage } from "@humwick/sdk";
12
+ import { VERSION } from "./config";
13
+ import { createDaemonPlan, detectDaemonEnvironment, executeDaemonPlan } from "./daemon";
14
+ import { installChannelsHostService } from "./channels-host-service";
15
+ import { executeSetupPlan, resolveSetupPlan, setupPlanToken } from "./setup";
16
+ import { runtimeBinPath, runtimeTarballDir } from "./runtime-prefix";
17
+ import { isServerTarball, npmInstallIntoPrefix } from "./runtime-install";
18
+ import { sanitizeOrchestratorId } from "./utils";
19
+
20
+ const execFileAsync = promisify(execFile);
21
+ const SERVER_PACKAGE = "@humwick/server";
22
+ const ORCHESTRATOR_UNIT = "agent-relay-orchestrator.service";
23
+
24
+ export type InitStep = "preflight" | "setup" | "runtime" | "service" | "health" | "host" | "login";
25
+
26
+ export class InitStepError extends Error {
27
+ constructor(readonly step: InitStep, message: string, readonly repair: string) { super(message); }
28
+ }
29
+
30
+ export type InitOptions = {
31
+ tarballDir?: string;
32
+ runtimePrefix?: string;
33
+ envFile?: string;
34
+ host?: string;
35
+ port?: number;
36
+ dbPath?: string;
37
+ healthTimeoutMs?: number;
38
+ };
39
+
40
+ export type InitResult = {
41
+ ok: true;
42
+ version: string;
43
+ dashboardUrl: string;
44
+ loginUrl: string;
45
+ loginExpiresAt?: string;
46
+ service: { unit: string; kind: string; scope: string; file?: string };
47
+ envFile: string;
48
+ dbPath: string;
49
+ runtimePrefix: string;
50
+ runtime: { source: "registry" | "tarballs"; installed: boolean };
51
+ linger: "yes" | "no" | "unknown";
52
+ resumed: boolean;
53
+ /** This machine, joined as the server's first orchestrator host. */
54
+ host: { id: string; unit: string; joined: boolean };
55
+ warnings: string[];
56
+ next: string[];
57
+ };
58
+
59
+ type Log = (line: string) => void;
60
+
61
+ async function run(command: string[], timeoutMs = 15_000): Promise<{ ok: boolean; out: string }> {
62
+ try {
63
+ const { stdout, stderr } = await execFileAsync(command[0]!, command.slice(1), { timeout: timeoutMs, maxBuffer: 16 * 1024 * 1024 });
64
+ return { ok: true, out: `${stdout}${stderr}`.trim() };
65
+ } catch (e) {
66
+ const err = e as { stdout?: string; stderr?: string; message: string };
67
+ return { ok: false, out: `${err.stdout ?? ""}${err.stderr ?? ""}`.trim() || err.message };
68
+ }
69
+ }
70
+
71
+ function probeHost(host: string): string {
72
+ return host === "0.0.0.0" || host === "::" ? "127.0.0.1" : host;
73
+ }
74
+
75
+ /** True when something accepts TCP connections on host:port. */
76
+ export function portInUse(host: string, port: number, timeoutMs = 2_000): Promise<boolean> {
77
+ return new Promise((done) => {
78
+ const socket = connect({ host: probeHost(host), port });
79
+ const finish = (inUse: boolean) => { socket.destroy(); done(inUse); };
80
+ socket.setTimeout(timeoutMs, () => finish(false));
81
+ socket.once("connect", () => finish(true));
82
+ socket.once("error", () => finish(false));
83
+ });
84
+ }
85
+
86
+ async function healthy(baseUrl: string): Promise<boolean> {
87
+ try {
88
+ return (await fetch(`${baseUrl}/api/health`, { signal: AbortSignal.timeout(5_000) })).ok;
89
+ } catch {
90
+ return false;
91
+ }
92
+ }
93
+
94
+ async function unitActive(unit: string): Promise<boolean> {
95
+ return (await run(["systemctl", "--user", "is-active", unit])).out === "active";
96
+ }
97
+
98
+ async function preflight(port: number, host: string, unit: string, log: Log): Promise<{ linger: InitResult["linger"]; resumed: boolean }> {
99
+ if (process.platform === "win32") throw new InitStepError("preflight", "native Windows is not supported", "Install WSL2 (wsl --install), then run init inside the Linux distribution.");
100
+ if (process.platform !== "linux") throw new InitStepError("preflight", `init installs a systemd user service; ${process.platform} is not supported yet`, "Run the server in the foreground: humwick");
101
+ const bun = Bun.which("bun");
102
+ if (!bun) throw new InitStepError("preflight", "bun is not on PATH", "curl -fsSL https://bun.sh/install | bash, then open a new shell");
103
+ if (!Bun.which("npm")) throw new InitStepError("preflight", "npm is not on PATH (the runtime installs with npm)", "Install Node.js 20 or newer with npm, e.g. sudo apt install nodejs npm");
104
+ const node = await run(["node", "--version"]);
105
+ const nodeMajor = Number(/^v(\d+)/.exec(node.out)?.[1] ?? 0);
106
+ if (!node.ok || nodeMajor < 20) throw new InitStepError("preflight", `Node.js 20 or newer is required (found ${node.ok ? node.out : "none"})`, "Install Node.js 20 or newer with npm");
107
+ const manager = await run(["systemctl", "--user", "show-environment"]);
108
+ if (!manager.ok) throw new InitStepError("preflight", `no systemd user manager is reachable: ${manager.out.split("\n")[0]}`, `Log in through a real session (ssh or console), or run: sudo loginctl enable-linger ${userInfo().username}`);
109
+ const lingerOut = await run(["loginctl", "show-user", userInfo().username, "--property=Linger", "--value"]);
110
+ const linger = lingerOut.ok ? (lingerOut.out === "yes" ? "yes" : "no") : "unknown";
111
+ const resumed = await unitActive(unit);
112
+ if (!resumed && await portInUse(host, port)) {
113
+ const other = await healthy(`http://${probeHost(host)}:${port}`) ? "a Humwick server that init does not manage (a foreground trial?)" : "another program";
114
+ throw new InitStepError("preflight", `port ${port} is in use by ${other}`, `Stop it, or pass --port <free port>. To keep a trial server's data, pass --db-path <its agent-relay.db>.`);
115
+ }
116
+ log(`preflight: bun ${Bun.version}, node ${node.out}, systemd user manager, linger ${linger}, port ${port} ${resumed ? "held by this install" : "free"}`);
117
+ return { linger, resumed };
118
+ }
119
+
120
+ function fileDigest(path: string): string {
121
+ return createHash("sha256").update(readFileSync(path)).digest("hex");
122
+ }
123
+
124
+ /** Copy the tarballs into the runtime's tarball dir; false when the runtime already has these exact files. */
125
+ function stageTarballs(sourceDir: string, prefix: string): { tarballs: string[]; changed: boolean } {
126
+ const names = readdirSync(sourceDir).filter((name) => name.endsWith(".tgz")).sort();
127
+ if (!names.some(isServerTarball)) throw new InitStepError("runtime", `no humwick-server-*.tgz in ${sourceDir}`, "Pack the packages: humwick dev pack --packages sdk,server,runner,orchestrator");
128
+ const target = runtimeTarballDir(prefix);
129
+ const existing = existsSync(target) ? readdirSync(target).filter((name) => name.endsWith(".tgz")).sort() : [];
130
+ const same = existing.join("\n") === names.join("\n")
131
+ && names.every((name) => fileDigest(join(sourceDir, name)) === fileDigest(join(target, name)));
132
+ if (!same) {
133
+ mkdirSync(target, { recursive: true });
134
+ for (const name of existing) rmSync(join(target, name), { force: true });
135
+ for (const name of names) copyFileSync(join(sourceDir, name), join(target, name));
136
+ }
137
+ return { tarballs: names.map((name) => join(target, name)), changed: !same };
138
+ }
139
+
140
+ function installedServerVersion(prefix: string): string | undefined {
141
+ try {
142
+ return (JSON.parse(readFileSync(join(prefix, "node_modules", SERVER_PACKAGE, "package.json"), "utf8")) as { version?: string }).version;
143
+ } catch {
144
+ return undefined;
145
+ }
146
+ }
147
+
148
+ async function installRuntime(prefix: string, tarballDir: string | undefined, log: Log): Promise<boolean> {
149
+ const binPresent = existsSync(runtimeBinPath("agent-relay", prefix));
150
+ const staged = tarballDir ? stageTarballs(resolve(tarballDir), prefix) : undefined;
151
+ const upToDate = binPresent && installedServerVersion(prefix) === VERSION && (staged ? !staged.changed : true);
152
+ if (upToDate) {
153
+ log(`runtime: ${SERVER_PACKAGE}@${VERSION} already installed in ${prefix}`);
154
+ return false;
155
+ }
156
+ const specs = staged ? staged.tarballs : [`${SERVER_PACKAGE}@${VERSION}`];
157
+ try {
158
+ await npmInstallIntoPrefix(prefix, specs);
159
+ } catch (e) {
160
+ throw new InitStepError("runtime", errMessage(e), `Check network access to the npm registry, then rerun: humwick init${tarballDir ? ` --tarball-dir ${tarballDir}` : ""}`);
161
+ }
162
+ if (!existsSync(runtimeBinPath("agent-relay", prefix))) throw new InitStepError("runtime", `the install finished but ${runtimeBinPath("agent-relay", prefix)} is missing`, "Rerun init; if it repeats, remove the runtime prefix and rerun");
163
+ log(`runtime: installed ${staged ? `${staged.tarballs.length} packed tarballs` : `${SERVER_PACKAGE}@${VERSION}`} into ${prefix}`);
164
+ return true;
165
+ }
166
+
167
+ async function waitHealthy(baseUrl: string, timeoutMs: number, unit: string): Promise<void> {
168
+ const deadline = Date.now() + timeoutMs;
169
+ while (Date.now() < deadline) {
170
+ if (await healthy(baseUrl) && await unitActive(unit)) return;
171
+ await Bun.sleep(1_000);
172
+ }
173
+ throw new InitStepError("health", `${unit} is not healthy at ${baseUrl}/api/health after ${Math.round(timeoutMs / 1000)} s`, `journalctl --user -u ${unit} -n 50 --no-pager`);
174
+ }
175
+
176
+ /** A call to the local server with the admin token from the env file; it never leaves this machine. */
177
+ async function adminRequest(step: InitStep, baseUrl: string, token: string, method: string, path: string, body?: unknown): Promise<Response> {
178
+ try {
179
+ return await fetch(`${baseUrl}${path}`, {
180
+ method,
181
+ headers: { [RELAY_TOKEN_HEADER]: token, "content-type": "application/json" },
182
+ ...(body === undefined ? {} : { body: JSON.stringify(body) }),
183
+ signal: AbortSignal.timeout(15_000),
184
+ });
185
+ } catch (e) {
186
+ throw new InitStepError(step, `could not reach ${baseUrl}: ${errMessage(e)}`, "Rerun init; it resumes where it stopped.");
187
+ }
188
+ }
189
+
190
+ /**
191
+ * Join this machine as an orchestrator host (#2680 v1 bar): a host-scoped, single-use bootstrap
192
+ * token from the local server, then the runtime's own `orchestrator install`. Gate slots and the
193
+ * agent cap default to sizes derived from the host's CPUs and RAM.
194
+ */
195
+ async function joinLocalHost(baseUrl: string, token: string, prefix: string, fromTarballs: boolean, log: Log): Promise<InitResult["host"]> {
196
+ const id = sanitizeOrchestratorId(hostname());
197
+ const repair = "Rerun init, or in the dashboard open Orchestrators → Add";
198
+ const joined = !(await unitActive(ORCHESTRATOR_UNIT));
199
+ if (joined) {
200
+ const baseDir = join(homedir(), "projects");
201
+ const minted = await adminRequest("host", baseUrl, token, "POST", "/api/orchestrators/bootstrap", { id, baseDir, relayUrl: baseUrl });
202
+ if (minted.status !== 201) throw new InitStepError("host", `host bootstrap returned ${minted.status}: ${(await minted.text()).slice(0, 200)}`, repair);
203
+ const { bootstrapToken } = await minted.json() as { bootstrapToken: string };
204
+ const install = await run([runtimeBinPath("humwick", prefix), "orchestrator", "install", "--relay-url", baseUrl, "--bootstrap-token", bootstrapToken,
205
+ "--id", id, "--base-dir", baseDir, "--runtime-prefix", prefix, "--yes", ...(fromTarballs ? ["--skip-install"] : ["--version", VERSION])], 10 * 60_000);
206
+ if (!install.ok) throw new InitStepError("host", `orchestrator install failed:\n${install.out.split("\n").slice(-20).join("\n")}`, repair);
207
+ }
208
+ const deadline = Date.now() + 120_000;
209
+ for (;;) {
210
+ const list = await (await adminRequest("host", baseUrl, token, "GET", "/api/orchestrators")).json().catch(() => []) as Array<{ id: string; status?: string }>;
211
+ if (list.some((h) => h.id === id && h.status === "online")) break;
212
+ if (Date.now() > deadline) throw new InitStepError("host", `host ${id} did not come online within 120 s`, `journalctl --user -u ${ORCHESTRATOR_UNIT} -n 50 --no-pager`);
213
+ await Bun.sleep(1_000);
214
+ }
215
+ log(`host: ${id} ${joined ? "joined" : "already joined"} and online`);
216
+ return { id, unit: ORCHESTRATOR_UNIT, joined };
217
+ }
218
+
219
+ async function mintLoginLink(baseUrl: string, token: string): Promise<{ url: string; expiresAt?: string }> {
220
+ const response = await adminRequest("login", baseUrl, token, "POST", "/api/auth/login-links", {});
221
+ if (response.status !== 201) throw new InitStepError("login", `login link request returned ${response.status}: ${(await response.text()).slice(0, 200)}`, "Rerun init to get a fresh login link");
222
+ const body = await response.json() as { path: string; expiresAt?: number };
223
+ return { url: `${baseUrl}${body.path}`, ...(body.expiresAt ? { expiresAt: new Date(body.expiresAt * 1000).toISOString() } : {}) };
224
+ }
225
+
226
+ /** Install or repair a persistent server for this user and return how to open its dashboard. */
227
+ export async function runInit(options: InitOptions = {}, log: Log = () => {}): Promise<InitResult> {
228
+ const plan = await resolveSetupPlan({
229
+ ...(options.envFile ? { envFile: options.envFile } : {}),
230
+ ...(options.host ? { host: options.host } : {}),
231
+ ...(options.port !== undefined ? { port: options.port } : {}),
232
+ ...(options.dbPath ? { dbPath: options.dbPath } : {}),
233
+ ...(options.runtimePrefix ? { runtimePrefix: options.runtimePrefix } : {}),
234
+ });
235
+ const port = Number(plan.values.PORT);
236
+ const host = plan.values.HOST!;
237
+ const daemonEnv = await detectDaemonEnvironment();
238
+ const bunDir = dirname(Bun.which("bun") ?? process.execPath);
239
+ const daemon = createDaemonPlan({ action: "install", envFile: plan.envFile, runtimePrefix: plan.runtimePrefix, port, host, pathPrefix: [bunDir], enable: true, start: true }, daemonEnv);
240
+ const unit = `${daemon.name}.service`;
241
+
242
+ const { linger, resumed } = await preflight(port, host, unit, log);
243
+
244
+ try {
245
+ await executeSetupPlan(plan);
246
+ } catch (e) {
247
+ throw new InitStepError("setup", errMessage(e), `Move the file aside, or rerun with an explicit --env-file`);
248
+ }
249
+ log(`setup: ${plan.reused ? "kept" : "wrote"} ${plan.envFile} (admin token ${plan.reused ? "unchanged" : "generated"})`);
250
+
251
+ const installed = await installRuntime(plan.runtimePrefix, options.tarballDir, log);
252
+
253
+ const previousUnit = daemon.file && existsSync(daemon.file.path) ? readFileSync(daemon.file.path, "utf8") : undefined;
254
+ const restart = resumed && (installed || previousUnit !== daemon.file?.content);
255
+ try {
256
+ await executeDaemonPlan(daemon);
257
+ if (restart) {
258
+ const restarted = await run(["systemctl", "--user", "restart", unit], 60_000);
259
+ if (!restarted.ok) throw new Error(restarted.out);
260
+ }
261
+ // The server supervises a channels-host service; `daemon install` sets it up the same way.
262
+ log(await installChannelsHostService({ ownerServiceName: daemon.name, runtimePrefix: plan.runtimePrefix, legacyEnvFile: plan.envFile, start: true, env: daemonEnv, onlyIfConfigured: false }));
263
+ } catch (e) {
264
+ throw new InitStepError("service", errMessage(e), `systemctl --user status ${unit} agent-relay-channels-host.service`);
265
+ }
266
+ log(`service: ${unit} enabled and ${restart ? "restarted" : resumed ? "running" : "started"}`);
267
+
268
+ const baseUrl = `http://${probeHost(host)}:${port}`;
269
+ await waitHealthy(baseUrl, options.healthTimeoutMs ?? 60_000, unit);
270
+ log(`health: ${baseUrl}/api/health ok`);
271
+
272
+ const token = setupPlanToken(plan);
273
+ if (!token) throw new InitStepError("setup", `${plan.envFile} has no admin token; init needs it to join this host and mint the login link`, `humwick init --env-file <a new path>, or add a token to ${plan.envFile}`);
274
+ const localHost = await joinLocalHost(baseUrl, token, plan.runtimePrefix, Boolean(options.tarballDir), log);
275
+ const login = await mintLoginLink(baseUrl, token);
276
+ const warnings = [...plan.warnings, ...daemon.warnings.filter((w) => !w.startsWith("Runtime binary is not installed"))];
277
+ if (linger !== "yes") warnings.push(`Linger is ${linger}: the service stops when you log out. Keep it running with: sudo loginctl enable-linger ${userInfo().username}`);
278
+ return {
279
+ ok: true,
280
+ version: VERSION,
281
+ dashboardUrl: `${baseUrl}/`,
282
+ loginUrl: login.url,
283
+ ...(login.expiresAt ? { loginExpiresAt: login.expiresAt } : {}),
284
+ service: { unit, kind: daemon.kind, scope: daemon.scope, ...(daemon.serviceFilePath ? { file: daemon.serviceFilePath } : {}) },
285
+ envFile: plan.envFile,
286
+ dbPath: plan.values.DB_PATH!,
287
+ runtimePrefix: plan.runtimePrefix,
288
+ runtime: { source: options.tarballDir ? "tarballs" : "registry", installed },
289
+ linger,
290
+ resumed,
291
+ host: localHost,
292
+ warnings,
293
+ next: [
294
+ `Open the login link (one use, 15 minutes) on this machine, or from yours through: ssh -L ${port}:127.0.0.1:${port} ${userInfo().username}@<this-host>`,
295
+ `This machine runs agents as host ${localHost.id}. Set up a provider in the dashboard, then spawn your first agent. More machines: Orchestrators → Add.`,
296
+ "Lost the link? Rerun init: it repairs the install and prints a fresh one, without changing the admin token.",
297
+ ],
298
+ };
299
+ }
@@ -2,23 +2,21 @@
2
2
  // one boundary per macrotask (`forEachPaced`); a backlog (a long pause, a crash that lost many settles)
3
3
  // is drained in runs of this budget, well inside the 30s job deadline, re-run this soon while a run
4
4
  // reports `moreWork`.
5
- import { isSqliteBusyError, withFastFailConnection, writerLockAvailable } from "./db/connection";
5
+ import { isSqliteBusyError } from "./db/connection";
6
6
  import { forEachPaced } from "./event-loop-yield";
7
7
 
8
8
  export const INJECTION_SWEEP_RUN_BUDGET_MS = 10_000;
9
9
  export const INJECTION_SWEEP_BACKLOG_INTERVAL_MS = 5_000;
10
- const SWEEP_BUSY_TIMEOUT_MS = 40;
11
10
 
12
- /** Each unit runs on the fast-fail connection, so a write under another writer's lock fails in 40ms
13
- * instead of the shared connection's ~1s; the run then stops with `moreWork` and retries soon. */
14
- export async function pacedInjectionSweep<T>(items: () => Iterable<T>, unit: (item: T) => void, signal?: AbortSignal): Promise<{ moreWork: boolean }> {
11
+ /** The gateway owns write contention; wait for each unit before continuing the bounded sweep. */
12
+ export async function pacedInjectionSweep<T>(items: () => Iterable<T>, unit: (item: T) => unknown, signal?: AbortSignal): Promise<{ moreWork: boolean }> {
15
13
  let contended = false;
16
- return forEachPaced(items, (item) => {
14
+ return forEachPaced(items, async (item) => {
17
15
  try {
18
- withFastFailConnection(SWEEP_BUSY_TIMEOUT_MS, () => unit(item));
16
+ await unit(item);
19
17
  } catch (error) {
20
18
  if (!isSqliteBusyError(error)) throw error;
21
19
  contended = true;
22
20
  }
23
- }, { signal, budgetMs: INJECTION_SWEEP_RUN_BUDGET_MS, ready: () => !contended && writerLockAvailable(SWEEP_BUSY_TIMEOUT_MS) });
21
+ }, { signal, budgetMs: INJECTION_SWEEP_RUN_BUDGET_MS, ready: () => !contended });
24
22
  }