@indigoai-us/hq-cli 5.312.2-beta.0 → 5.314.0

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 (1144) hide show
  1. package/CHANGELOG.md +1 -4293
  2. package/assets/scaffold/core/scripts/checkpoint-stop-gate.sh +43 -281
  3. package/assets/scaffold/core/scripts/hook-lib.sh +50 -345
  4. package/assets/scaffold/core/scripts/hq-session.sh +23 -184
  5. package/assets/scaffold/core/scripts/lib/session-id.sh +2 -3
  6. package/assets/scaffold/core/scripts/lib/session-scope-capability.sh +1 -1
  7. package/assets/scaffold/core/scripts/lib/work-mesh-enqueue.sh +1 -2
  8. package/assets/scaffold/core/scripts/qmd-reindex-after-sync.sh +87 -10
  9. package/assets/scaffold/core/scripts/rebuild-company-knowledge-index.sh +3 -36
  10. package/assets/scaffold/core/scripts/rebuild-projects-index.sh +12 -99
  11. package/assets/scaffold/core/scripts/token-usage-report.sh +20 -81
  12. package/dist/bin/hq-auth-refresh.d.ts +0 -2
  13. package/dist/bin/hq-auth-refresh.js +6 -12
  14. package/dist/commands/__fixtures__/access-vault.d.ts +0 -2
  15. package/dist/commands/__fixtures__/access-vault.js +1 -1
  16. package/dist/commands/_patterns.d.ts +0 -16
  17. package/dist/commands/_patterns.js +3 -20
  18. package/dist/commands/access.d.ts +1 -2
  19. package/dist/commands/access.js +49 -58
  20. package/dist/commands/agents.d.ts +4 -106
  21. package/dist/commands/agents.js +45 -924
  22. package/dist/commands/auth.d.ts +0 -1
  23. package/dist/commands/auth.js +19 -189
  24. package/dist/commands/billing.d.ts +0 -3
  25. package/dist/commands/billing.js +1 -6
  26. package/dist/commands/channels.js +1 -56
  27. package/dist/commands/cloud-demote.js +2 -3
  28. package/dist/commands/cloud-provision.d.ts +0 -14
  29. package/dist/commands/cloud-provision.js +58 -232
  30. package/dist/commands/cloud.d.ts +15 -49
  31. package/dist/commands/cloud.js +122 -358
  32. package/dist/commands/company-transfer.d.ts +28 -4
  33. package/dist/commands/company-transfer.js +49 -5
  34. package/dist/commands/company.js +3 -66
  35. package/dist/commands/core-checkpoint.d.ts +9 -19
  36. package/dist/commands/core-checkpoint.js +37 -301
  37. package/dist/commands/core.d.ts +0 -4
  38. package/dist/commands/core.js +80 -270
  39. package/dist/commands/crm.js +3 -52
  40. package/dist/commands/db-provision.js +4 -4
  41. package/dist/commands/db-sql.js +21 -7
  42. package/dist/commands/dm.d.ts +12 -119
  43. package/dist/commands/dm.js +38 -388
  44. package/dist/commands/doctor.d.ts +0 -12
  45. package/dist/commands/doctor.js +12 -158
  46. package/dist/commands/feedback.d.ts +0 -6
  47. package/dist/commands/feedback.js +37 -86
  48. package/dist/commands/files-browse.d.ts +12 -32
  49. package/dist/commands/files-browse.js +60 -176
  50. package/dist/commands/files.d.ts +0 -17
  51. package/dist/commands/files.js +47 -300
  52. package/dist/commands/group-grants.js +2 -3
  53. package/dist/commands/groups.js +5 -6
  54. package/dist/commands/index-cmd.d.ts +1 -19
  55. package/dist/commands/index-cmd.js +7 -74
  56. package/dist/commands/integrations-api.d.ts +10 -15
  57. package/dist/commands/integrations-api.js +22 -27
  58. package/dist/commands/integrations-connect.d.ts +13 -51
  59. package/dist/commands/integrations-connect.js +71 -86
  60. package/dist/commands/integrations-core.d.ts +6 -6
  61. package/dist/commands/integrations-core.js +11 -25
  62. package/dist/commands/integrations-import.d.ts +1 -1
  63. package/dist/commands/integrations-import.js +24 -43
  64. package/dist/commands/integrations-manage.js +13 -20
  65. package/dist/commands/integrations.d.ts +0 -8
  66. package/dist/commands/integrations.js +7 -94
  67. package/dist/commands/login.js +6 -49
  68. package/dist/commands/mcp-registration.d.ts +25 -32
  69. package/dist/commands/mcp-registration.js +23 -256
  70. package/dist/commands/mcp-status.js +0 -1
  71. package/dist/commands/meetings.js +34 -55
  72. package/dist/commands/members.js +1 -5
  73. package/dist/commands/mesh.d.ts +0 -11
  74. package/dist/commands/mesh.js +76 -1086
  75. package/dist/commands/onboard-identity-guard.d.ts +1 -1
  76. package/dist/commands/onboard.js +5 -24
  77. package/dist/commands/pack-install.js +1 -3
  78. package/dist/commands/packs.js +1 -11
  79. package/dist/commands/people.d.ts +0 -4
  80. package/dist/commands/people.js +3 -37
  81. package/dist/commands/publish.d.ts +1 -8
  82. package/dist/commands/publish.js +4 -17
  83. package/dist/commands/reindex.js +6 -56
  84. package/dist/commands/rescue.d.ts +0 -4
  85. package/dist/commands/rescue.js +17 -27
  86. package/dist/commands/run.js +4 -18
  87. package/dist/commands/safe-extract.d.ts +2 -11
  88. package/dist/commands/safe-extract.js +8 -36
  89. package/dist/commands/scaffold-fast.d.ts +41 -0
  90. package/dist/commands/scaffold-fast.js +57 -0
  91. package/dist/commands/secrets.d.ts +2 -20
  92. package/dist/commands/secrets.js +71 -175
  93. package/dist/commands/skill.d.ts +4 -35
  94. package/dist/commands/skill.js +18 -357
  95. package/dist/commands/sync-mode.d.ts +0 -11
  96. package/dist/commands/sync-mode.js +0 -30
  97. package/dist/commands/sync-narrow.js +5 -36
  98. package/dist/commands/team-sync.js +10 -71
  99. package/dist/commands/update.js +0 -6
  100. package/dist/commands/whoami.d.ts +0 -22
  101. package/dist/commands/whoami.js +4 -137
  102. package/dist/commands/workers.d.ts +14 -2
  103. package/dist/commands/workers.js +10 -7
  104. package/dist/fast-core.d.ts +7 -6
  105. package/dist/fast-core.js +24 -80
  106. package/dist/index.d.ts +4 -3
  107. package/dist/index.js +9 -59
  108. package/dist/lazy-commands.d.ts +44 -20
  109. package/dist/lazy-commands.js +58 -60
  110. package/dist/lib/company-skill-wrapper.d.ts +0 -1
  111. package/dist/lib/company-skill-wrapper.js +29 -121
  112. package/dist/lib/core-utils/codex-skill-bridge.d.ts +1 -2
  113. package/dist/lib/core-utils/qmd-reindex-after-sync.d.ts +1 -14
  114. package/dist/lib/core-utils/qmd-reindex-after-sync.js +17 -101
  115. package/dist/lib/core-utils/soft-timeout.js +1 -1
  116. package/dist/lib/core-utils/timeout-guard.d.ts +3 -3
  117. package/dist/lib/core-utils/timeout-guard.js +13 -28
  118. package/dist/lib/core-utils/token-usage-report.js +3 -17
  119. package/dist/lib/db/control-plane.js +0 -6
  120. package/dist/lib/db/local.d.ts +5 -0
  121. package/dist/lib/db/local.js +1 -1
  122. package/dist/lib/db/migrate.d.ts +12 -0
  123. package/dist/lib/db/migrate.js +5 -5
  124. package/dist/lib/db/remote-sql.d.ts +2 -2
  125. package/dist/lib/db/remote-sql.js +3 -3
  126. package/dist/lib/db/sql.d.ts +10 -0
  127. package/dist/lib/db/sql.js +8 -4
  128. package/dist/lib/doctor/__testing__/fake-hq-tree.d.ts +1 -1
  129. package/dist/lib/doctor/__testing__/fake-hq-tree.js +1 -1
  130. package/dist/lib/doctor/checks/claude-wiring.js +1 -70
  131. package/dist/lib/doctor/checks/codex-wiring.d.ts +4 -0
  132. package/dist/lib/doctor/checks/codex-wiring.js +35 -3
  133. package/dist/lib/doctor/checks/grok-wiring.js +34 -1
  134. package/dist/lib/doctor/checks/integrations.js +0 -11
  135. package/dist/lib/doctor/checks/runtime-health.d.ts +17 -4
  136. package/dist/lib/doctor/checks/runtime-health.js +7 -20
  137. package/dist/lib/doctor/checks/sync-health.d.ts +0 -21
  138. package/dist/lib/doctor/checks/sync-health.js +2 -72
  139. package/dist/lib/doctor/checks/work-context.js +6 -12
  140. package/dist/lib/doctor/compat.d.ts +16 -0
  141. package/dist/lib/doctor/compat.js +3 -4
  142. package/dist/lib/doctor/deep/effects.js +3 -1
  143. package/dist/lib/doctor/deep/executor.d.ts +10 -4
  144. package/dist/lib/doctor/deep/executor.js +1 -1
  145. package/dist/lib/doctor/deep/parity.d.ts +29 -2
  146. package/dist/lib/doctor/deep/parity.js +7 -7
  147. package/dist/lib/doctor/deep/sandbox.d.ts +25 -7
  148. package/dist/lib/doctor/deep/sandbox.js +7 -5
  149. package/dist/lib/doctor/fix/apply.d.ts +8 -79
  150. package/dist/lib/doctor/fix/apply.js +80 -292
  151. package/dist/lib/doctor/fix/remediation.d.ts +6 -14
  152. package/dist/lib/doctor/fix/remediation.js +9 -49
  153. package/dist/lib/doctor/fixtures/schema.js +3 -1
  154. package/dist/lib/doctor/json-output.d.ts +1 -17
  155. package/dist/lib/doctor/json-output.js +2 -7
  156. package/dist/lib/doctor/platform.d.ts +26 -0
  157. package/dist/lib/doctor/platform.js +5 -5
  158. package/dist/lib/doctor/registry.d.ts +0 -14
  159. package/dist/lib/doctor/registry.js +0 -71
  160. package/dist/lib/doctor/report.js +1 -1
  161. package/dist/lib/doctor/types.d.ts +0 -5
  162. package/dist/lib/doctor/types.js +0 -20
  163. package/dist/lib/flag-registry-worker.js +1 -12
  164. package/dist/lib/flag-registry.d.ts +6 -26
  165. package/dist/lib/flag-registry.js +16 -133
  166. package/dist/lib/index-render/companies.d.ts +1 -4
  167. package/dist/lib/index-render/companies.js +3 -66
  168. package/dist/lib/index-render/company-knowledge.js +17 -80
  169. package/dist/lib/index-render/index.d.ts +0 -1
  170. package/dist/lib/index-render/index.js +1 -2
  171. package/dist/lib/index-render/projects.js +5 -82
  172. package/dist/lib/index-render/shared.d.ts +1 -13
  173. package/dist/lib/index-render/shared.js +2 -41
  174. package/dist/lib/mesh/api.d.ts +10 -103
  175. package/dist/lib/mesh/api.js +64 -334
  176. package/dist/lib/mesh/client.d.ts +2 -3
  177. package/dist/lib/mesh/client.js +2 -3
  178. package/dist/lib/mesh/live/backfill-held.d.ts +16 -67
  179. package/dist/lib/mesh/live/backfill-held.js +61 -415
  180. package/dist/lib/mesh/live/backoff.d.ts +2 -2
  181. package/dist/lib/mesh/live/backoff.js +3 -16
  182. package/dist/lib/mesh/live/daemon/board-refresh.d.ts +1 -7
  183. package/dist/lib/mesh/live/daemon/board-refresh.js +30 -78
  184. package/dist/lib/mesh/live/daemon/credentials.d.ts +10 -64
  185. package/dist/lib/mesh/live/daemon/credentials.js +20 -270
  186. package/dist/lib/mesh/live/daemon/doctor.d.ts +0 -51
  187. package/dist/lib/mesh/live/daemon/doctor.js +6 -180
  188. package/dist/lib/mesh/live/daemon/index.d.ts +10 -13
  189. package/dist/lib/mesh/live/daemon/index.js +6 -7
  190. package/dist/lib/mesh/live/daemon/install.d.ts +1 -255
  191. package/dist/lib/mesh/live/daemon/install.js +13 -811
  192. package/dist/lib/mesh/live/daemon/paths.d.ts +0 -2
  193. package/dist/lib/mesh/live/daemon/paths.js +0 -2
  194. package/dist/lib/mesh/live/daemon/presence.d.ts +5 -24
  195. package/dist/lib/mesh/live/daemon/presence.js +10 -79
  196. package/dist/lib/mesh/live/daemon/run.d.ts +2 -48
  197. package/dist/lib/mesh/live/daemon/run.js +33 -485
  198. package/dist/lib/mesh/live/daemon/state.d.ts +2 -19
  199. package/dist/lib/mesh/live/daemon/state.js +2 -34
  200. package/dist/lib/mesh/live/daemon/transcript-watch.d.ts +9 -45
  201. package/dist/lib/mesh/live/daemon/transcript-watch.js +31 -227
  202. package/dist/lib/mesh/live/emit-client.d.ts +0 -8
  203. package/dist/lib/mesh/live/emit-client.js +8 -23
  204. package/dist/lib/mesh/live/emit-evidence.d.ts +0 -19
  205. package/dist/lib/mesh/live/emit-evidence.js +2 -20
  206. package/dist/lib/mesh/live/emit.d.ts +0 -9
  207. package/dist/lib/mesh/live/emit.js +2 -28
  208. package/dist/lib/mesh/live/enqueue.d.ts +0 -5
  209. package/dist/lib/mesh/live/enqueue.js +0 -19
  210. package/dist/lib/mesh/live/flush.d.ts +5 -17
  211. package/dist/lib/mesh/live/flush.js +38 -391
  212. package/dist/lib/mesh/live/format-spool-line.d.ts +2 -5
  213. package/dist/lib/mesh/live/format-spool-line.js +0 -10
  214. package/dist/lib/mesh/live/paths.d.ts +0 -2
  215. package/dist/lib/mesh/live/paths.js +0 -4
  216. package/dist/lib/mesh/live/session-event.schema.json +1 -24
  217. package/dist/lib/mesh/live/session-events-client.d.ts +0 -2
  218. package/dist/lib/mesh/live/session-events-client.js +0 -26
  219. package/dist/lib/mesh/live/spool.d.ts +0 -19
  220. package/dist/lib/mesh/live/spool.js +3 -269
  221. package/dist/lib/mesh/live/validate-session-event.d.ts +1 -1
  222. package/dist/lib/mesh/live/validate-session-event.js +1 -1
  223. package/dist/lib/plan-limit-nag.d.ts +6 -102
  224. package/dist/lib/plan-limit-nag.js +43 -348
  225. package/dist/lib/scan-packages/index.d.ts +0 -2
  226. package/dist/lib/scan-packages/index.js +6 -168
  227. package/dist/lib/search-index/background.d.ts +1 -8
  228. package/dist/lib/search-index/background.js +200 -55
  229. package/dist/lib/search-index/index.d.ts +0 -12
  230. package/dist/lib/search-index/index.js +7 -153
  231. package/dist/lib/work-context/company.d.ts +20 -22
  232. package/dist/lib/work-context/company.js +40 -124
  233. package/dist/lib/work-context/config.d.ts +11 -58
  234. package/dist/lib/work-context/config.js +83 -334
  235. package/dist/lib/work-context/contract.d.ts +0 -10
  236. package/dist/lib/work-context/index.d.ts +25 -0
  237. package/dist/lib/work-context/index.js +21 -0
  238. package/dist/lib/work-context/outbox.d.ts +1 -14
  239. package/dist/lib/work-context/outbox.js +1 -87
  240. package/dist/lib/work-context/reconcile.d.ts +0 -15
  241. package/dist/lib/work-context/reconcile.js +25 -380
  242. package/dist/lib/work-context/state.d.ts +10 -93
  243. package/dist/lib/work-context/state.js +29 -342
  244. package/dist/lib/workers-registry/index.js +8 -62
  245. package/dist/main.d.ts +0 -12
  246. package/dist/main.js +73 -266
  247. package/dist/node-network-compat.d.ts +30 -2
  248. package/dist/node-network-compat.js +51 -4
  249. package/dist/node-preflight.d.ts +1 -7
  250. package/dist/node-preflight.js +5 -5
  251. package/dist/register-all.d.ts +29 -4
  252. package/dist/register-all.js +233 -4
  253. package/dist/run/discover-schemas.d.ts +1 -2
  254. package/dist/run/env-graph-guard.d.ts +2 -2
  255. package/dist/run/env-graph-guard.js +2 -5
  256. package/dist/run/hq-plugin.d.ts +0 -4
  257. package/dist/run/hq-plugin.js +8 -36
  258. package/dist/sentry-before-send.js +0 -80
  259. package/dist/sentry.d.ts +2 -8
  260. package/dist/sentry.js +13 -117
  261. package/dist/startup-registration.d.ts +2 -26
  262. package/dist/startup-registration.js +15 -116
  263. package/dist/utils/cli-telemetry.d.ts +1 -74
  264. package/dist/utils/cli-telemetry.js +7 -434
  265. package/dist/utils/client-health.d.ts +4 -0
  266. package/dist/utils/client-health.js +2 -2
  267. package/dist/utils/cognito-session.d.ts +0 -71
  268. package/dist/utils/cognito-session.js +27 -481
  269. package/dist/utils/contribution-table.d.ts +1 -1
  270. package/dist/utils/environmental-error.d.ts +0 -18
  271. package/dist/utils/environmental-error.js +7 -240
  272. package/dist/utils/expected-cli-error.d.ts +0 -2
  273. package/dist/utils/expected-cli-error.js +0 -4
  274. package/dist/utils/feedback-diagnostics.d.ts +1 -2
  275. package/dist/utils/feedback-log-bundle.d.ts +26 -51
  276. package/dist/utils/feedback-log-bundle.js +31 -211
  277. package/dist/utils/feedback-logs.d.ts +47 -18
  278. package/dist/utils/feedback-logs.js +21 -50
  279. package/dist/utils/feedback-screenshots.d.ts +1 -0
  280. package/dist/utils/feedback-screenshots.js +1 -1
  281. package/dist/utils/hq-roots.d.ts +2 -25
  282. package/dist/utils/hq-roots.js +7 -67
  283. package/dist/utils/incomplete-install-error.d.ts +19 -87
  284. package/dist/utils/incomplete-install-error.js +68 -319
  285. package/dist/utils/install-tree-torn.d.ts +24 -260
  286. package/dist/utils/install-tree-torn.js +51 -998
  287. package/dist/utils/large-file-guard.d.ts +10 -0
  288. package/dist/utils/large-file-guard.js +1 -1
  289. package/dist/utils/local-files-overview.d.ts +8 -4
  290. package/dist/utils/local-files-overview.js +7 -48
  291. package/dist/utils/login-provider.d.ts +0 -7
  292. package/dist/utils/login-provider.js +0 -34
  293. package/dist/utils/pack-contributions.d.ts +9 -2
  294. package/dist/utils/pack-contributions.js +43 -6
  295. package/dist/utils/package-root-diagnostics.d.ts +1 -1
  296. package/dist/utils/people.d.ts +11 -7
  297. package/dist/utils/people.js +20 -5
  298. package/dist/utils/plan-gate-error.d.ts +10 -52
  299. package/dist/utils/plan-gate-error.js +41 -364
  300. package/dist/utils/qmd-model-download-error.d.ts +8 -0
  301. package/dist/utils/qmd-model-download-error.js +1 -1
  302. package/dist/utils/qmd-query-document-error.d.ts +6 -0
  303. package/dist/utils/qmd-query-document-error.js +1 -1
  304. package/dist/utils/redact-error-text.js +1 -2
  305. package/dist/utils/run-bundled-script.d.ts +0 -6
  306. package/dist/utils/run-bundled-script.js +4 -117
  307. package/dist/utils/secrets-cache.d.ts +3 -7
  308. package/dist/utils/secrets-cache.js +14 -162
  309. package/dist/utils/self-update.d.ts +1 -34
  310. package/dist/utils/self-update.js +42 -660
  311. package/dist/utils/sentry-fingerprint.d.ts +1 -23
  312. package/dist/utils/sentry-fingerprint.js +31 -223
  313. package/dist/utils/team-upgrade.d.ts +1 -1
  314. package/dist/utils/team-upgrade.js +3 -4
  315. package/dist/utils/vault-api.d.ts +1 -21
  316. package/dist/utils/vault-api.js +84 -251
  317. package/dist/utils/version-check.d.ts +1 -11
  318. package/dist/utils/version-check.js +3 -32
  319. package/dist/utils/version-gate.d.ts +17 -101
  320. package/dist/utils/version-gate.js +70 -645
  321. package/dist/utils/windows-spawn.d.ts +0 -6
  322. package/dist/utils/windows-spawn.js +0 -37
  323. package/package.json +8 -20
  324. package/README.md +0 -113
  325. package/assets/bot-workers/setup/context/USER-GUIDE.md +0 -404
  326. package/assets/bot-workers/setup/context/quick-reference.md +0 -199
  327. package/assets/bot-workers/setup/skills/agents-and-team.md +0 -167
  328. package/assets/bot-workers/setup/skills/explain-hq.md +0 -35
  329. package/assets/bot-workers/setup/skills/first-company.md +0 -74
  330. package/assets/bot-workers/setup/skills/joining-a-team.md +0 -125
  331. package/assets/bot-workers/setup/skills/pricing.md +0 -92
  332. package/assets/bot-workers/setup/skills/standing-help.md +0 -81
  333. package/assets/bot-workers/setup/skills/your-business.md +0 -281
  334. package/assets/bot-workers/setup/worker.yaml +0 -791
  335. package/assets/lanes-hooks/hq-lanes-drain.json +0 -15
  336. package/assets/lanes-hooks/hq-lanes-drain.mjs +0 -275
  337. package/assets/lanes-hooks/hq-lanes-drain.sh +0 -21
  338. package/assets/lanes-reconcile/hq-lanes-reconcile.service +0 -11
  339. package/assets/lanes-reconcile/hq-lanes-reconcile.timer +0 -12
  340. package/assets/scaffold/.claude/hooks/validate-policy-frontmatter.sh +0 -485
  341. package/assets/scaffold/core/scripts/audit-log.sh +0 -384
  342. package/assets/scaffold/core/scripts/clean-worktree-reconcile-handoff.sh +0 -171
  343. package/assets/scaffold/core/scripts/eval-trigger.sh +0 -193
  344. package/assets/scaffold/core/scripts/git-pack-extension-check.sh +0 -164
  345. package/assets/scaffold/core/scripts/handoff-finalize.sh +0 -688
  346. package/assets/scaffold/core/scripts/handoff-open-steps.sh +0 -81
  347. package/assets/scaffold/core/scripts/handoff-post.sh +0 -252
  348. package/assets/scaffold/core/scripts/hq-delegate-bundle.sh +0 -307
  349. package/assets/scaffold/core/scripts/hq-delegate-grant.sh +0 -252
  350. package/assets/scaffold/core/scripts/hq-delegate-pickup.sh +0 -99
  351. package/assets/scaffold/core/scripts/hq-delegate-publish.sh +0 -91
  352. package/assets/scaffold/core/scripts/hq-delegate-repo.sh +0 -158
  353. package/assets/scaffold/core/scripts/hq-delegate-resolve.sh +0 -145
  354. package/assets/scaffold/core/scripts/hq-delegate-secrets.sh +0 -129
  355. package/assets/scaffold/core/scripts/hq-delegate-send.sh +0 -259
  356. package/assets/scaffold/core/scripts/hq-delegate-transfer.sh +0 -175
  357. package/assets/scaffold/core/scripts/hq-delegate-verify.sh +0 -238
  358. package/assets/scaffold/core/scripts/hq-dm-bind.sh +0 -585
  359. package/assets/scaffold/core/scripts/hq-job-notify.sh +0 -389
  360. package/assets/scaffold/core/scripts/hq-job-probe.sh +0 -835
  361. package/assets/scaffold/core/scripts/hq-job-remediate.sh +0 -359
  362. package/assets/scaffold/core/scripts/hq-job-run.sh +0 -1025
  363. package/assets/scaffold/core/scripts/hq-pin.sh +0 -118
  364. package/assets/scaffold/core/scripts/jobs-schedule-parse.sh +0 -310
  365. package/assets/scaffold/core/scripts/jobs-validate.sh +0 -451
  366. package/assets/scaffold/core/scripts/knowledge-prefs.sh +0 -55
  367. package/assets/scaffold/core/scripts/lib/portable.sh +0 -211
  368. package/assets/scaffold/core/scripts/lib/secret-patterns.sh +0 -58
  369. package/assets/scaffold/core/scripts/ontology-audience-grant.sh +0 -81
  370. package/assets/scaffold/core/scripts/ontology-brief.mjs +0 -93
  371. package/assets/scaffold/core/scripts/ontology-candidate.sh +0 -100
  372. package/assets/scaffold/core/scripts/ontology-garden.mjs +0 -238
  373. package/assets/scaffold/core/scripts/ontology-source-worklist.sh +0 -79
  374. package/assets/scaffold/core/scripts/outpost-jobs-reconcile.sh +0 -834
  375. package/assets/scaffold/core/scripts/policy-age-report.sh +0 -156
  376. package/assets/scaffold/core/scripts/policy-retire.sh +0 -199
  377. package/assets/scaffold/core/scripts/qmd-reindex-bg.sh +0 -40
  378. package/assets/scaffold/core/scripts/read-policy-frontmatter.sh +0 -32
  379. package/assets/scaffold/core/scripts/refresh-vault-access.sh +0 -188
  380. package/assets/scaffold/core/scripts/resolve-company.sh +0 -199
  381. package/assets/scaffold/core/scripts/resume-thread-lock.sh +0 -285
  382. package/assets/scaffold/core/scripts/source-yaml-validate.sh +0 -36
  383. package/assets/scaffold/core/scripts/verify-story-deliverables.sh +0 -148
  384. package/assets/scaffold/core/scripts/work-mesh-live-bind-trusted.sh +0 -152
  385. package/assets/scaffold/core/scripts/worktree-gc.sh +0 -362
  386. package/contracts/preflight/v1/fixtures.json +0 -87
  387. package/contracts/preflight/v1/schema.json +0 -58
  388. package/dist/command-catalog.generated.d.ts +0 -10655
  389. package/dist/command-catalog.generated.js +0 -13737
  390. package/dist/command-registration-plan.d.ts +0 -474
  391. package/dist/command-registration-plan.js +0 -136
  392. package/dist/commands/agent-enroll.d.ts +0 -146
  393. package/dist/commands/agent-enroll.js +0 -401
  394. package/dist/commands/agent-inbox.d.ts +0 -9
  395. package/dist/commands/agent-inbox.js +0 -102
  396. package/dist/commands/agent-kit.d.ts +0 -87
  397. package/dist/commands/agent-kit.js +0 -480
  398. package/dist/commands/agent-mcp.d.ts +0 -24
  399. package/dist/commands/agent-mcp.js +0 -127
  400. package/dist/commands/agent-probe.d.ts +0 -77
  401. package/dist/commands/agent-probe.js +0 -335
  402. package/dist/commands/agent.d.ts +0 -13
  403. package/dist/commands/agent.js +0 -40
  404. package/dist/commands/billing-sponsor.d.ts +0 -83
  405. package/dist/commands/billing-sponsor.js +0 -344
  406. package/dist/commands/bot-companies.d.ts +0 -40
  407. package/dist/commands/bot-companies.js +0 -64
  408. package/dist/commands/bot-continuity.d.ts +0 -28
  409. package/dist/commands/bot-continuity.js +0 -68
  410. package/dist/commands/bot-jobs.d.ts +0 -127
  411. package/dist/commands/bot-jobs.js +0 -454
  412. package/dist/commands/bot.d.ts +0 -278
  413. package/dist/commands/bot.js +0 -1982
  414. package/dist/commands/cloud-retire.d.ts +0 -51
  415. package/dist/commands/cloud-retire.js +0 -154
  416. package/dist/commands/core-audit-log-native.d.ts +0 -9
  417. package/dist/commands/core-audit-log-native.js +0 -257
  418. package/dist/commands/core-checkpoint-gate-setting.d.ts +0 -59
  419. package/dist/commands/core-checkpoint-gate-setting.js +0 -163
  420. package/dist/commands/core-checkpoint-stop-gate-native.d.ts +0 -34
  421. package/dist/commands/core-checkpoint-stop-gate-native.js +0 -725
  422. package/dist/commands/core-command-catalog.d.ts +0 -34
  423. package/dist/commands/core-command-catalog.js +0 -62
  424. package/dist/commands/core-company-index-scope-flag.d.ts +0 -3
  425. package/dist/commands/core-company-index-scope-flag.js +0 -6
  426. package/dist/commands/core-delegate-w1b.d.ts +0 -10
  427. package/dist/commands/core-delegate-w1b.js +0 -1219
  428. package/dist/commands/core-delegate.d.ts +0 -46
  429. package/dist/commands/core-delegate.js +0 -1040
  430. package/dist/commands/core-dm-bind-native.d.ts +0 -32
  431. package/dist/commands/core-dm-bind-native.js +0 -668
  432. package/dist/commands/core-git-pack-extension-check-native.d.ts +0 -22
  433. package/dist/commands/core-git-pack-extension-check-native.js +0 -190
  434. package/dist/commands/core-handoff-fast.d.ts +0 -18
  435. package/dist/commands/core-handoff-fast.js +0 -104
  436. package/dist/commands/core-handoff-open-steps.d.ts +0 -21
  437. package/dist/commands/core-handoff-open-steps.js +0 -264
  438. package/dist/commands/core-handoff.d.ts +0 -24
  439. package/dist/commands/core-handoff.js +0 -1144
  440. package/dist/commands/core-hq-session-native.d.ts +0 -39
  441. package/dist/commands/core-hq-session-native.js +0 -579
  442. package/dist/commands/core-job-native-utils.d.ts +0 -6
  443. package/dist/commands/core-job-native-utils.js +0 -80
  444. package/dist/commands/core-job-native.d.ts +0 -5
  445. package/dist/commands/core-job-native.js +0 -16
  446. package/dist/commands/core-job-notify-native.d.ts +0 -36
  447. package/dist/commands/core-job-notify-native.js +0 -359
  448. package/dist/commands/core-job-probe-native.d.ts +0 -45
  449. package/dist/commands/core-job-probe-native.js +0 -564
  450. package/dist/commands/core-job-remediate-native.d.ts +0 -26
  451. package/dist/commands/core-job-remediate-native.js +0 -374
  452. package/dist/commands/core-job-remote-launcher.d.ts +0 -7
  453. package/dist/commands/core-job-remote-launcher.js +0 -51
  454. package/dist/commands/core-job-run-native.d.ts +0 -47
  455. package/dist/commands/core-job-run-native.js +0 -1215
  456. package/dist/commands/core-jobs-native.d.ts +0 -25
  457. package/dist/commands/core-jobs-native.js +0 -1339
  458. package/dist/commands/core-knowledge-prefs.d.ts +0 -12
  459. package/dist/commands/core-knowledge-prefs.js +0 -137
  460. package/dist/commands/core-native-handoff-flag.d.ts +0 -4
  461. package/dist/commands/core-native-handoff-flag.js +0 -7
  462. package/dist/commands/core-native-jobs-flag.d.ts +0 -4
  463. package/dist/commands/core-native-jobs-flag.js +0 -7
  464. package/dist/commands/core-native-policy-flag.d.ts +0 -9
  465. package/dist/commands/core-native-policy-flag.js +0 -12
  466. package/dist/commands/core-native-session-flag.d.ts +0 -4
  467. package/dist/commands/core-native-session-flag.js +0 -7
  468. package/dist/commands/core-native-utilities-flag.d.ts +0 -4
  469. package/dist/commands/core-native-utilities-flag.js +0 -7
  470. package/dist/commands/core-native-utilities.d.ts +0 -29
  471. package/dist/commands/core-native-utilities.js +0 -54
  472. package/dist/commands/core-native-worker-skills-flag.d.ts +0 -4
  473. package/dist/commands/core-native-worker-skills-flag.js +0 -7
  474. package/dist/commands/core-ontology-native.d.ts +0 -17
  475. package/dist/commands/core-ontology-native.js +0 -916
  476. package/dist/commands/core-ontology.d.ts +0 -66
  477. package/dist/commands/core-ontology.js +0 -430
  478. package/dist/commands/core-pin-native.d.ts +0 -14
  479. package/dist/commands/core-pin-native.js +0 -236
  480. package/dist/commands/core-policy-frontmatter.d.ts +0 -8
  481. package/dist/commands/core-policy-frontmatter.js +0 -52
  482. package/dist/commands/core-policy.d.ts +0 -14
  483. package/dist/commands/core-policy.js +0 -1018
  484. package/dist/commands/core-refresh-vault-access-native.d.ts +0 -20
  485. package/dist/commands/core-refresh-vault-access-native.js +0 -229
  486. package/dist/commands/core-resolve-company-command.d.ts +0 -4
  487. package/dist/commands/core-resolve-company-command.js +0 -42
  488. package/dist/commands/core-resolve-company-native.d.ts +0 -28
  489. package/dist/commands/core-resolve-company-native.js +0 -408
  490. package/dist/commands/core-work-mesh-bind-trusted-native.d.ts +0 -32
  491. package/dist/commands/core-work-mesh-bind-trusted-native.js +0 -190
  492. package/dist/commands/core-worker-lint-native.d.ts +0 -4
  493. package/dist/commands/core-worker-lint-native.js +0 -160
  494. package/dist/commands/core-worker-share-native.d.ts +0 -5
  495. package/dist/commands/core-worker-share-native.js +0 -260
  496. package/dist/commands/core-worker-skills-command.d.ts +0 -8
  497. package/dist/commands/core-worker-skills-command.js +0 -33
  498. package/dist/commands/core-worker-skills-runtime.d.ts +0 -33
  499. package/dist/commands/core-worker-skills-runtime.js +0 -86
  500. package/dist/commands/daemon.d.ts +0 -63
  501. package/dist/commands/daemon.js +0 -877
  502. package/dist/commands/deploy-comments.d.ts +0 -3
  503. package/dist/commands/deploy-comments.js +0 -162
  504. package/dist/commands/lanes-accounts.d.ts +0 -28
  505. package/dist/commands/lanes-accounts.js +0 -595
  506. package/dist/commands/lanes-autopilot.d.ts +0 -11
  507. package/dist/commands/lanes-autopilot.js +0 -336
  508. package/dist/commands/lanes-checks.d.ts +0 -51
  509. package/dist/commands/lanes-checks.js +0 -941
  510. package/dist/commands/lanes-hold.d.ts +0 -43
  511. package/dist/commands/lanes-hold.js +0 -655
  512. package/dist/commands/lanes-kpis.d.ts +0 -11
  513. package/dist/commands/lanes-kpis.js +0 -34
  514. package/dist/commands/lanes-message.d.ts +0 -9
  515. package/dist/commands/lanes-message.js +0 -934
  516. package/dist/commands/lanes-move.d.ts +0 -12
  517. package/dist/commands/lanes-move.js +0 -397
  518. package/dist/commands/lanes-questions.d.ts +0 -36
  519. package/dist/commands/lanes-questions.js +0 -1004
  520. package/dist/commands/lanes-report.d.ts +0 -11
  521. package/dist/commands/lanes-report.js +0 -333
  522. package/dist/commands/lanes-resume.d.ts +0 -6
  523. package/dist/commands/lanes-resume.js +0 -561
  524. package/dist/commands/lanes-review.d.ts +0 -10
  525. package/dist/commands/lanes-review.js +0 -56
  526. package/dist/commands/lanes-shared.d.ts +0 -448
  527. package/dist/commands/lanes-shared.js +0 -170
  528. package/dist/commands/lanes-stories.d.ts +0 -15
  529. package/dist/commands/lanes-stories.js +0 -347
  530. package/dist/commands/lanes-watch.d.ts +0 -7
  531. package/dist/commands/lanes-watch.js +0 -216
  532. package/dist/commands/lanes.d.ts +0 -149
  533. package/dist/commands/lanes.js +0 -8843
  534. package/dist/commands/monitor.d.ts +0 -58
  535. package/dist/commands/monitor.js +0 -980
  536. package/dist/commands/onboard-bot-membership.d.ts +0 -32
  537. package/dist/commands/onboard-bot-membership.js +0 -40
  538. package/dist/commands/onboard-create-identity.d.ts +0 -26
  539. package/dist/commands/onboard-create-identity.js +0 -23
  540. package/dist/commands/outpost-pruner.d.ts +0 -3
  541. package/dist/commands/outpost-pruner.js +0 -125
  542. package/dist/commands/personal-connector-settings-flag.d.ts +0 -4
  543. package/dist/commands/personal-connector-settings-flag.js +0 -7
  544. package/dist/commands/personal-targeted-push-flag.d.ts +0 -4
  545. package/dist/commands/personal-targeted-push-flag.js +0 -7
  546. package/dist/commands/personal-targeted-push-paths.d.ts +0 -3
  547. package/dist/commands/personal-targeted-push-paths.js +0 -67
  548. package/dist/commands/report.d.ts +0 -19
  549. package/dist/commands/report.js +0 -537
  550. package/dist/commands/run-feature-telemetry.d.ts +0 -3
  551. package/dist/commands/run-feature-telemetry.js +0 -6
  552. package/dist/commands/staff/reset-test-account.d.ts +0 -110
  553. package/dist/commands/staff/reset-test-account.js +0 -268
  554. package/dist/commands/staff/test-account.d.ts +0 -51
  555. package/dist/commands/staff/test-account.js +0 -196
  556. package/dist/commands/vault-retention-flag.d.ts +0 -3
  557. package/dist/commands/vault-retention-flag.js +0 -9
  558. package/dist/commands/vault-retention.d.ts +0 -11
  559. package/dist/commands/vault-retention.js +0 -69
  560. package/dist/commands/version.d.ts +0 -10
  561. package/dist/commands/version.js +0 -11
  562. package/dist/fast-scaffold.d.ts +0 -84
  563. package/dist/fast-scaffold.js +0 -353
  564. package/dist/lanes-drain-hook.d.ts +0 -14
  565. package/dist/lanes-drain-hook.js +0 -203
  566. package/dist/lib/agent-kit/adopt-identity.d.ts +0 -27
  567. package/dist/lib/agent-kit/adopt-identity.js +0 -60
  568. package/dist/lib/agent-kit/attachment-download.d.ts +0 -24
  569. package/dist/lib/agent-kit/attachment-download.js +0 -47
  570. package/dist/lib/agent-kit/creds.d.ts +0 -106
  571. package/dist/lib/agent-kit/creds.js +0 -163
  572. package/dist/lib/agent-kit/fallback.d.ts +0 -78
  573. package/dist/lib/agent-kit/fallback.js +0 -210
  574. package/dist/lib/agent-kit/inbox-state.d.ts +0 -83
  575. package/dist/lib/agent-kit/inbox-state.js +0 -159
  576. package/dist/lib/agent-kit/kit-config.d.ts +0 -56
  577. package/dist/lib/agent-kit/kit-config.js +0 -65
  578. package/dist/lib/agent-kit/log.d.ts +0 -16
  579. package/dist/lib/agent-kit/log.js +0 -46
  580. package/dist/lib/agent-kit/mcp/attachments.d.ts +0 -126
  581. package/dist/lib/agent-kit/mcp/attachments.js +0 -222
  582. package/dist/lib/agent-kit/mcp/jsonrpc.d.ts +0 -75
  583. package/dist/lib/agent-kit/mcp/jsonrpc.js +0 -164
  584. package/dist/lib/agent-kit/mcp/tools.d.ts +0 -64
  585. package/dist/lib/agent-kit/mcp/tools.js +0 -397
  586. package/dist/lib/agent-kit/paths.d.ts +0 -65
  587. package/dist/lib/agent-kit/paths.js +0 -118
  588. package/dist/lib/agent-kit/private-file.d.ts +0 -29
  589. package/dist/lib/agent-kit/private-file.js +0 -91
  590. package/dist/lib/agent-kit/run/heartbeat.d.ts +0 -72
  591. package/dist/lib/agent-kit/run/heartbeat.js +0 -128
  592. package/dist/lib/agent-kit/run/inbox.d.ts +0 -76
  593. package/dist/lib/agent-kit/run/inbox.js +0 -205
  594. package/dist/lib/agent-kit/run/supervisor.d.ts +0 -35
  595. package/dist/lib/agent-kit/run/supervisor.js +0 -85
  596. package/dist/lib/agent-kit/run/sync.d.ts +0 -33
  597. package/dist/lib/agent-kit/run/sync.js +0 -58
  598. package/dist/lib/agent-kit/services.d.ts +0 -31
  599. package/dist/lib/agent-kit/services.js +0 -55
  600. package/dist/lib/agent-kit/skills.d.ts +0 -18
  601. package/dist/lib/agent-kit/skills.js +0 -151
  602. package/dist/lib/agent-kit/wake.d.ts +0 -83
  603. package/dist/lib/agent-kit/wake.js +0 -243
  604. package/dist/lib/agents/model-catalog.d.ts +0 -62
  605. package/dist/lib/agents/model-catalog.js +0 -275
  606. package/dist/lib/auth-login-provider-state.d.ts +0 -8
  607. package/dist/lib/auth-login-provider-state.js +0 -63
  608. package/dist/lib/aws-sdk-node-warning-output.d.ts +0 -12
  609. package/dist/lib/aws-sdk-node-warning-output.js +0 -22
  610. package/dist/lib/billing/free-month.d.ts +0 -18
  611. package/dist/lib/billing/free-month.js +0 -31
  612. package/dist/lib/billing/plan-limit-refusals.d.ts +0 -69
  613. package/dist/lib/billing/plan-limit-refusals.js +0 -187
  614. package/dist/lib/billing/plan-limit-upgrade-url.d.ts +0 -11
  615. package/dist/lib/billing/plan-limit-upgrade-url.js +0 -66
  616. package/dist/lib/billing/plan-lock.d.ts +0 -143
  617. package/dist/lib/billing/plan-lock.js +0 -439
  618. package/dist/lib/bot/api.d.ts +0 -349
  619. package/dist/lib/bot/api.js +0 -316
  620. package/dist/lib/bot/attachments.d.ts +0 -45
  621. package/dist/lib/bot/attachments.js +0 -89
  622. package/dist/lib/bot/cli-update.d.ts +0 -138
  623. package/dist/lib/bot/cli-update.js +0 -278
  624. package/dist/lib/bot/company-bind.d.ts +0 -27
  625. package/dist/lib/bot/company-bind.js +0 -62
  626. package/dist/lib/bot/config.d.ts +0 -153
  627. package/dist/lib/bot/config.js +0 -233
  628. package/dist/lib/bot/continuity-download.d.ts +0 -28
  629. package/dist/lib/bot/continuity-download.js +0 -75
  630. package/dist/lib/bot/continuity-install.d.ts +0 -14
  631. package/dist/lib/bot/continuity-install.js +0 -101
  632. package/dist/lib/bot/continuity.d.ts +0 -67
  633. package/dist/lib/bot/continuity.js +0 -301
  634. package/dist/lib/bot/creds.d.ts +0 -24
  635. package/dist/lib/bot/creds.js +0 -51
  636. package/dist/lib/bot/daemon.d.ts +0 -173
  637. package/dist/lib/bot/daemon.js +0 -552
  638. package/dist/lib/bot/inbox-poll-backoff-flag.d.ts +0 -24
  639. package/dist/lib/bot/inbox-poll-backoff-flag.js +0 -135
  640. package/dist/lib/bot/inbox-state.d.ts +0 -18
  641. package/dist/lib/bot/inbox-state.js +0 -51
  642. package/dist/lib/bot/index.d.ts +0 -23
  643. package/dist/lib/bot/index.js +0 -23
  644. package/dist/lib/bot/inflight.d.ts +0 -119
  645. package/dist/lib/bot/inflight.js +0 -83
  646. package/dist/lib/bot/job-completions.d.ts +0 -89
  647. package/dist/lib/bot/job-completions.js +0 -132
  648. package/dist/lib/bot/job-run.d.ts +0 -109
  649. package/dist/lib/bot/job-run.js +0 -246
  650. package/dist/lib/bot/local-config.d.ts +0 -61
  651. package/dist/lib/bot/local-config.js +0 -145
  652. package/dist/lib/bot/local-jobs.d.ts +0 -89
  653. package/dist/lib/bot/local-jobs.js +0 -459
  654. package/dist/lib/bot/local-name.d.ts +0 -54
  655. package/dist/lib/bot/local-name.js +0 -114
  656. package/dist/lib/bot/log.d.ts +0 -13
  657. package/dist/lib/bot/log.js +0 -59
  658. package/dist/lib/bot/owner-context.d.ts +0 -79
  659. package/dist/lib/bot/owner-context.js +0 -175
  660. package/dist/lib/bot/paths.d.ts +0 -76
  661. package/dist/lib/bot/paths.js +0 -131
  662. package/dist/lib/bot/progress.d.ts +0 -132
  663. package/dist/lib/bot/progress.js +0 -289
  664. package/dist/lib/bot/promote.d.ts +0 -16
  665. package/dist/lib/bot/promote.js +0 -106
  666. package/dist/lib/bot/promotion-hold.d.ts +0 -24
  667. package/dist/lib/bot/promotion-hold.js +0 -103
  668. package/dist/lib/bot/promotion-receipt.d.ts +0 -9
  669. package/dist/lib/bot/promotion-receipt.js +0 -56
  670. package/dist/lib/bot/promotion-upload.d.ts +0 -16
  671. package/dist/lib/bot/promotion-upload.js +0 -80
  672. package/dist/lib/bot/prompt.d.ts +0 -136
  673. package/dist/lib/bot/prompt.js +0 -433
  674. package/dist/lib/bot/room-policy.d.ts +0 -63
  675. package/dist/lib/bot/room-policy.js +0 -91
  676. package/dist/lib/bot/run.d.ts +0 -217
  677. package/dist/lib/bot/run.js +0 -1869
  678. package/dist/lib/bot/runnable.d.ts +0 -51
  679. package/dist/lib/bot/runnable.js +0 -65
  680. package/dist/lib/bot/runtime/claude.d.ts +0 -50
  681. package/dist/lib/bot/runtime/claude.js +0 -170
  682. package/dist/lib/bot/runtime/codex.d.ts +0 -28
  683. package/dist/lib/bot/runtime/codex.js +0 -153
  684. package/dist/lib/bot/runtime/grok-acp.d.ts +0 -40
  685. package/dist/lib/bot/runtime/grok-acp.js +0 -265
  686. package/dist/lib/bot/runtime/grok.d.ts +0 -41
  687. package/dist/lib/bot/runtime/grok.js +0 -169
  688. package/dist/lib/bot/runtime/index.d.ts +0 -36
  689. package/dist/lib/bot/runtime/index.js +0 -417
  690. package/dist/lib/bot/runtime/messages-stream.d.ts +0 -56
  691. package/dist/lib/bot/runtime/messages-stream.js +0 -194
  692. package/dist/lib/bot/runtime/sanitize.d.ts +0 -18
  693. package/dist/lib/bot/runtime/sanitize.js +0 -28
  694. package/dist/lib/bot/runtime/types.d.ts +0 -159
  695. package/dist/lib/bot/runtime/types.js +0 -51
  696. package/dist/lib/bot/runtime/windows.d.ts +0 -10
  697. package/dist/lib/bot/runtime/windows.js +0 -53
  698. package/dist/lib/bot/runtime-sign-in.d.ts +0 -24
  699. package/dist/lib/bot/runtime-sign-in.js +0 -31
  700. package/dist/lib/bot/scaffold.d.ts +0 -38
  701. package/dist/lib/bot/scaffold.js +0 -94
  702. package/dist/lib/bot/self-heal.d.ts +0 -52
  703. package/dist/lib/bot/self-heal.js +0 -79
  704. package/dist/lib/bot/self-process.d.ts +0 -19
  705. package/dist/lib/bot/self-process.js +0 -35
  706. package/dist/lib/bot/session.d.ts +0 -19
  707. package/dist/lib/bot/session.js +0 -39
  708. package/dist/lib/bot/setup-explain.d.ts +0 -64
  709. package/dist/lib/bot/setup-explain.js +0 -141
  710. package/dist/lib/bot/split.d.ts +0 -32
  711. package/dist/lib/bot/split.js +0 -241
  712. package/dist/lib/bot/status.d.ts +0 -57
  713. package/dist/lib/bot/status.js +0 -66
  714. package/dist/lib/bot/worker-source.d.ts +0 -66
  715. package/dist/lib/bot/worker-source.js +0 -283
  716. package/dist/lib/checkpoint-ontology/aliases.d.ts +0 -17
  717. package/dist/lib/checkpoint-ontology/aliases.js +0 -44
  718. package/dist/lib/checkpoint-ontology/apply.d.ts +0 -110
  719. package/dist/lib/checkpoint-ontology/apply.js +0 -777
  720. package/dist/lib/checkpoint-ontology/company-brief.d.ts +0 -18
  721. package/dist/lib/checkpoint-ontology/company-brief.js +0 -337
  722. package/dist/lib/checkpoint-ontology/company.d.ts +0 -44
  723. package/dist/lib/checkpoint-ontology/company.js +0 -118
  724. package/dist/lib/checkpoint-ontology/drop-aliases.d.ts +0 -41
  725. package/dist/lib/checkpoint-ontology/drop-aliases.js +0 -76
  726. package/dist/lib/checkpoint-ontology/entity-file.d.ts +0 -109
  727. package/dist/lib/checkpoint-ontology/entity-file.js +0 -371
  728. package/dist/lib/checkpoint-ontology/identity.d.ts +0 -38
  729. package/dist/lib/checkpoint-ontology/identity.js +0 -95
  730. package/dist/lib/checkpoint-ontology/merge-copies.d.ts +0 -199
  731. package/dist/lib/checkpoint-ontology/merge-copies.js +0 -1086
  732. package/dist/lib/checkpoint-ontology/multi-writer.d.ts +0 -101
  733. package/dist/lib/checkpoint-ontology/multi-writer.js +0 -394
  734. package/dist/lib/checkpoint-ontology/plan.d.ts +0 -193
  735. package/dist/lib/checkpoint-ontology/plan.js +0 -504
  736. package/dist/lib/checkpoint-ontology/queued-touches.d.ts +0 -29
  737. package/dist/lib/checkpoint-ontology/queued-touches.js +0 -193
  738. package/dist/lib/checkpoint-ontology/sync-client.d.ts +0 -38
  739. package/dist/lib/checkpoint-ontology/sync-client.js +0 -84
  740. package/dist/lib/checkpoint-ontology/tree.d.ts +0 -55
  741. package/dist/lib/checkpoint-ontology/tree.js +0 -198
  742. package/dist/lib/company-errors-sdk-warning-flag.d.ts +0 -5
  743. package/dist/lib/company-errors-sdk-warning-flag.js +0 -8
  744. package/dist/lib/core-utils/hook-timeout-debug-context-flag.d.ts +0 -20
  745. package/dist/lib/core-utils/hook-timeout-debug-context-flag.js +0 -81
  746. package/dist/lib/core-utils/jq-json-serializer.d.ts +0 -5
  747. package/dist/lib/core-utils/jq-json-serializer.js +0 -10
  748. package/dist/lib/core-utils/secret-patterns.d.ts +0 -43
  749. package/dist/lib/core-utils/secret-patterns.js +0 -34
  750. package/dist/lib/core-utils/sentry-report.d.ts +0 -114
  751. package/dist/lib/core-utils/sentry-report.js +0 -1034
  752. package/dist/lib/core-utils/timeout-guard-native.d.ts +0 -7
  753. package/dist/lib/core-utils/timeout-guard-native.js +0 -40
  754. package/dist/lib/core-utils/worktree-resume.d.ts +0 -20
  755. package/dist/lib/core-utils/worktree-resume.js +0 -1198
  756. package/dist/lib/daemon/catalog.d.ts +0 -134
  757. package/dist/lib/daemon/catalog.js +0 -358
  758. package/dist/lib/daemon/config.d.ts +0 -35
  759. package/dist/lib/daemon/config.js +0 -108
  760. package/dist/lib/daemon/flag.d.ts +0 -25
  761. package/dist/lib/daemon/flag.js +0 -59
  762. package/dist/lib/daemon/handoff.d.ts +0 -56
  763. package/dist/lib/daemon/handoff.js +0 -161
  764. package/dist/lib/daemon/hq-cloud.d.ts +0 -79
  765. package/dist/lib/daemon/hq-cloud.js +0 -183
  766. package/dist/lib/daemon/install.d.ts +0 -85
  767. package/dist/lib/daemon/install.js +0 -320
  768. package/dist/lib/daemon/legacy-sync-env.d.ts +0 -10
  769. package/dist/lib/daemon/legacy-sync-env.js +0 -58
  770. package/dist/lib/daemon/legacy.d.ts +0 -45
  771. package/dist/lib/daemon/legacy.js +0 -133
  772. package/dist/lib/daemon/lock.d.ts +0 -47
  773. package/dist/lib/daemon/lock.js +0 -138
  774. package/dist/lib/daemon/machine-type.d.ts +0 -50
  775. package/dist/lib/daemon/machine-type.js +0 -159
  776. package/dist/lib/daemon/migrate.d.ts +0 -77
  777. package/dist/lib/daemon/migrate.js +0 -265
  778. package/dist/lib/daemon/paths.d.ts +0 -24
  779. package/dist/lib/daemon/paths.js +0 -43
  780. package/dist/lib/daemon/protocol-tail.d.ts +0 -33
  781. package/dist/lib/daemon/protocol-tail.js +0 -131
  782. package/dist/lib/daemon/remote-control.d.ts +0 -74
  783. package/dist/lib/daemon/remote-control.js +0 -153
  784. package/dist/lib/daemon/requests.d.ts +0 -28
  785. package/dist/lib/daemon/requests.js +0 -65
  786. package/dist/lib/daemon/run.d.ts +0 -79
  787. package/dist/lib/daemon/run.js +0 -755
  788. package/dist/lib/daemon/succession.d.ts +0 -118
  789. package/dist/lib/daemon/succession.js +0 -263
  790. package/dist/lib/daemon/supervisor.d.ts +0 -132
  791. package/dist/lib/daemon/supervisor.js +0 -836
  792. package/dist/lib/daemon/sync-runner.d.ts +0 -58
  793. package/dist/lib/daemon/sync-runner.js +0 -277
  794. package/dist/lib/daemon/tools.d.ts +0 -52
  795. package/dist/lib/daemon/tools.js +0 -95
  796. package/dist/lib/daemon/updater.d.ts +0 -57
  797. package/dist/lib/daemon/updater.js +0 -163
  798. package/dist/lib/deploy-api.d.ts +0 -5
  799. package/dist/lib/deploy-api.js +0 -21
  800. package/dist/lib/deploy-apps.d.ts +0 -13
  801. package/dist/lib/deploy-apps.js +0 -29
  802. package/dist/lib/doctor/__testing__/run-doctor.d.ts +0 -8
  803. package/dist/lib/doctor/__testing__/run-doctor.js +0 -52
  804. package/dist/lib/doctor/checks/bots.d.ts +0 -13
  805. package/dist/lib/doctor/checks/bots.js +0 -189
  806. package/dist/lib/doctor/checks/scaffold-cli-floor.d.ts +0 -6
  807. package/dist/lib/doctor/checks/scaffold-cli-floor.js +0 -73
  808. package/dist/lib/doctor/checks/sync-runtime.d.ts +0 -11
  809. package/dist/lib/doctor/checks/sync-runtime.js +0 -216
  810. package/dist/lib/doctor/client-health-flag-token.d.ts +0 -15
  811. package/dist/lib/doctor/client-health-flag-token.js +0 -66
  812. package/dist/lib/doctor/client-health-reason-codes-flag.d.ts +0 -27
  813. package/dist/lib/doctor/client-health-reason-codes-flag.js +0 -106
  814. package/dist/lib/doctor/fix/json-output.d.ts +0 -28
  815. package/dist/lib/doctor/fix/json-output.js +0 -33
  816. package/dist/lib/doctor/fix/refresh-sync-outcome.d.ts +0 -12
  817. package/dist/lib/doctor/fix/refresh-sync-outcome.js +0 -25
  818. package/dist/lib/doctor/shared.d.ts +0 -8
  819. package/dist/lib/doctor/shared.js +0 -38
  820. package/dist/lib/doctor/stray-gate-entries.d.ts +0 -35
  821. package/dist/lib/doctor/stray-gate-entries.js +0 -83
  822. package/dist/lib/first-sync-next-step.d.ts +0 -37
  823. package/dist/lib/first-sync-next-step.js +0 -189
  824. package/dist/lib/flag-registry-endpoint.d.ts +0 -5
  825. package/dist/lib/flag-registry-endpoint.js +0 -9
  826. package/dist/lib/flag-registry-worker-source.generated.d.ts +0 -2
  827. package/dist/lib/flag-registry-worker-source.generated.js +0 -3
  828. package/dist/lib/flag-snapshot-file.d.ts +0 -20
  829. package/dist/lib/flag-snapshot-file.js +0 -91
  830. package/dist/lib/heartbeat-identity.d.ts +0 -30
  831. package/dist/lib/heartbeat-identity.js +0 -76
  832. package/dist/lib/lanes/account-home.d.ts +0 -8
  833. package/dist/lib/lanes/account-home.js +0 -10
  834. package/dist/lib/lanes/accounts.d.ts +0 -117
  835. package/dist/lib/lanes/accounts.js +0 -366
  836. package/dist/lib/lanes/admission-host.d.ts +0 -22
  837. package/dist/lib/lanes/admission-host.js +0 -37
  838. package/dist/lib/lanes/admission.d.ts +0 -26
  839. package/dist/lib/lanes/admission.js +0 -210
  840. package/dist/lib/lanes/approval-gate.d.ts +0 -8
  841. package/dist/lib/lanes/approval-gate.js +0 -90
  842. package/dist/lib/lanes/autopilot-metrics.d.ts +0 -99
  843. package/dist/lib/lanes/autopilot-metrics.js +0 -301
  844. package/dist/lib/lanes/autopilot-registry.d.ts +0 -62
  845. package/dist/lib/lanes/autopilot-registry.js +0 -192
  846. package/dist/lib/lanes/autopilot.d.ts +0 -120
  847. package/dist/lib/lanes/autopilot.js +0 -1061
  848. package/dist/lib/lanes/blocker-freshness.d.ts +0 -31
  849. package/dist/lib/lanes/blocker-freshness.js +0 -168
  850. package/dist/lib/lanes/checked-company-path.d.ts +0 -3
  851. package/dist/lib/lanes/checked-company-path.js +0 -39
  852. package/dist/lib/lanes/claude-balance.d.ts +0 -97
  853. package/dist/lib/lanes/claude-balance.js +0 -319
  854. package/dist/lib/lanes/compaction.d.ts +0 -110
  855. package/dist/lib/lanes/compaction.js +0 -293
  856. package/dist/lib/lanes/config.d.ts +0 -91
  857. package/dist/lib/lanes/config.js +0 -592
  858. package/dist/lib/lanes/constants.d.ts +0 -3
  859. package/dist/lib/lanes/constants.js +0 -3
  860. package/dist/lib/lanes/delivery-core.d.ts +0 -15
  861. package/dist/lib/lanes/delivery-core.js +0 -21
  862. package/dist/lib/lanes/delivery.d.ts +0 -180
  863. package/dist/lib/lanes/delivery.js +0 -443
  864. package/dist/lib/lanes/dropbox.d.ts +0 -129
  865. package/dist/lib/lanes/dropbox.js +0 -601
  866. package/dist/lib/lanes/effort.d.ts +0 -51
  867. package/dist/lib/lanes/effort.js +0 -113
  868. package/dist/lib/lanes/envelope-repair-dispatch.d.ts +0 -26
  869. package/dist/lib/lanes/envelope-repair-dispatch.js +0 -74
  870. package/dist/lib/lanes/errors.d.ts +0 -39
  871. package/dist/lib/lanes/errors.js +0 -72
  872. package/dist/lib/lanes/flag-keys.d.ts +0 -13
  873. package/dist/lib/lanes/flag-keys.js +0 -13
  874. package/dist/lib/lanes/grants.d.ts +0 -29
  875. package/dist/lib/lanes/grants.js +0 -166
  876. package/dist/lib/lanes/hire-grant-dispatch.d.ts +0 -29
  877. package/dist/lib/lanes/hire-grant-dispatch.js +0 -316
  878. package/dist/lib/lanes/hooks-install.d.ts +0 -82
  879. package/dist/lib/lanes/hooks-install.js +0 -479
  880. package/dist/lib/lanes/host-capacity.d.ts +0 -69
  881. package/dist/lib/lanes/host-capacity.js +0 -254
  882. package/dist/lib/lanes/hq-root.d.ts +0 -19
  883. package/dist/lib/lanes/hq-root.js +0 -28
  884. package/dist/lib/lanes/index.d.ts +0 -76
  885. package/dist/lib/lanes/index.js +0 -42
  886. package/dist/lib/lanes/keep-alive.d.ts +0 -68
  887. package/dist/lib/lanes/keep-alive.js +0 -343
  888. package/dist/lib/lanes/kr-freshness.d.ts +0 -76
  889. package/dist/lib/lanes/kr-freshness.js +0 -413
  890. package/dist/lib/lanes/lane-company.d.ts +0 -10
  891. package/dist/lib/lanes/lane-company.js +0 -41
  892. package/dist/lib/lanes/liveness.d.ts +0 -108
  893. package/dist/lib/lanes/liveness.js +0 -396
  894. package/dist/lib/lanes/machine-identity.d.ts +0 -34
  895. package/dist/lib/lanes/machine-identity.js +0 -92
  896. package/dist/lib/lanes/manager-status-gate.d.ts +0 -17
  897. package/dist/lib/lanes/manager-status-gate.js +0 -133
  898. package/dist/lib/lanes/merge-command.d.ts +0 -15
  899. package/dist/lib/lanes/merge-command.js +0 -629
  900. package/dist/lib/lanes/mesh-events.d.ts +0 -127
  901. package/dist/lib/lanes/mesh-events.js +0 -564
  902. package/dist/lib/lanes/message-replay.d.ts +0 -21
  903. package/dist/lib/lanes/message-replay.js +0 -122
  904. package/dist/lib/lanes/models.d.ts +0 -86
  905. package/dist/lib/lanes/models.js +0 -234
  906. package/dist/lib/lanes/monitor.d.ts +0 -48
  907. package/dist/lib/lanes/monitor.js +0 -428
  908. package/dist/lib/lanes/move-account.d.ts +0 -56
  909. package/dist/lib/lanes/move-account.js +0 -193
  910. package/dist/lib/lanes/orphans.d.ts +0 -25
  911. package/dist/lib/lanes/orphans.js +0 -102
  912. package/dist/lib/lanes/outpost-dispatch.d.ts +0 -147
  913. package/dist/lib/lanes/outpost-dispatch.js +0 -271
  914. package/dist/lib/lanes/outpost-hire-cache.d.ts +0 -22
  915. package/dist/lib/lanes/outpost-hire-cache.js +0 -153
  916. package/dist/lib/lanes/outpost-identity.d.ts +0 -33
  917. package/dist/lib/lanes/outpost-identity.js +0 -59
  918. package/dist/lib/lanes/outpost-profile-repin.d.ts +0 -17
  919. package/dist/lib/lanes/outpost-profile-repin.js +0 -84
  920. package/dist/lib/lanes/outpost-pruner-install.d.ts +0 -37
  921. package/dist/lib/lanes/outpost-pruner-install.js +0 -238
  922. package/dist/lib/lanes/outpost-registry-worker-approval.d.ts +0 -18
  923. package/dist/lib/lanes/outpost-registry-worker-approval.js +0 -109
  924. package/dist/lib/lanes/outpost-snapshot-memo.d.ts +0 -6
  925. package/dist/lib/lanes/outpost-snapshot-memo.js +0 -14
  926. package/dist/lib/lanes/outpost-worktree-pruner-flag.d.ts +0 -19
  927. package/dist/lib/lanes/outpost-worktree-pruner-flag.js +0 -86
  928. package/dist/lib/lanes/outpost-worktree-pruner.d.ts +0 -62
  929. package/dist/lib/lanes/outpost-worktree-pruner.js +0 -1201
  930. package/dist/lib/lanes/profile.d.ts +0 -69
  931. package/dist/lib/lanes/profile.js +0 -552
  932. package/dist/lib/lanes/provider-rejection.d.ts +0 -86
  933. package/dist/lib/lanes/provider-rejection.js +0 -286
  934. package/dist/lib/lanes/questions.d.ts +0 -90
  935. package/dist/lib/lanes/questions.js +0 -384
  936. package/dist/lib/lanes/quota.d.ts +0 -134
  937. package/dist/lib/lanes/quota.js +0 -471
  938. package/dist/lib/lanes/reconcile-install.d.ts +0 -90
  939. package/dist/lib/lanes/reconcile-install.js +0 -257
  940. package/dist/lib/lanes/reconcile.d.ts +0 -155
  941. package/dist/lib/lanes/reconcile.js +0 -3193
  942. package/dist/lib/lanes/registry-flag.d.ts +0 -29
  943. package/dist/lib/lanes/registry-flag.js +0 -139
  944. package/dist/lib/lanes/registry-persona.d.ts +0 -20
  945. package/dist/lib/lanes/registry-persona.js +0 -329
  946. package/dist/lib/lanes/registry.d.ts +0 -122
  947. package/dist/lib/lanes/registry.js +0 -570
  948. package/dist/lib/lanes/reviews.d.ts +0 -38
  949. package/dist/lib/lanes/reviews.js +0 -395
  950. package/dist/lib/lanes/runaway-tool-guard.d.ts +0 -38
  951. package/dist/lib/lanes/runaway-tool-guard.js +0 -284
  952. package/dist/lib/lanes/session-relink.d.ts +0 -25
  953. package/dist/lib/lanes/session-relink.js +0 -216
  954. package/dist/lib/lanes/spawn.d.ts +0 -350
  955. package/dist/lib/lanes/spawn.js +0 -2643
  956. package/dist/lib/lanes/ssm-command-context.d.ts +0 -12
  957. package/dist/lib/lanes/ssm-command-context.js +0 -52
  958. package/dist/lib/lanes/store.d.ts +0 -91
  959. package/dist/lib/lanes/store.js +0 -1811
  960. package/dist/lib/lanes/stories.d.ts +0 -23
  961. package/dist/lib/lanes/stories.js +0 -92
  962. package/dist/lib/lanes/story-ledger.d.ts +0 -80
  963. package/dist/lib/lanes/story-ledger.js +0 -815
  964. package/dist/lib/lanes/story-switch.d.ts +0 -21
  965. package/dist/lib/lanes/story-switch.js +0 -75
  966. package/dist/lib/lanes/text-scan.d.ts +0 -11
  967. package/dist/lib/lanes/text-scan.js +0 -103
  968. package/dist/lib/lanes/throttle.d.ts +0 -116
  969. package/dist/lib/lanes/throttle.js +0 -437
  970. package/dist/lib/lanes/timestamp.d.ts +0 -3
  971. package/dist/lib/lanes/timestamp.js +0 -26
  972. package/dist/lib/lanes/tracker-checks.d.ts +0 -77
  973. package/dist/lib/lanes/tracker-checks.js +0 -134
  974. package/dist/lib/lanes/tracker-report-state.d.ts +0 -10
  975. package/dist/lib/lanes/tracker-report-state.js +0 -124
  976. package/dist/lib/lanes/tracker-report.d.ts +0 -203
  977. package/dist/lib/lanes/tracker-report.js +0 -522
  978. package/dist/lib/lanes/tracker-store.d.ts +0 -18
  979. package/dist/lib/lanes/tracker-store.js +0 -106
  980. package/dist/lib/lanes/types.d.ts +0 -1020
  981. package/dist/lib/lanes/types.js +0 -417
  982. package/dist/lib/lanes/wait.d.ts +0 -39
  983. package/dist/lib/lanes/wait.js +0 -159
  984. package/dist/lib/lanes/watch.d.ts +0 -39
  985. package/dist/lib/lanes/watch.js +0 -249
  986. package/dist/lib/lanes/watcher-main.d.ts +0 -11
  987. package/dist/lib/lanes/watcher-main.js +0 -108
  988. package/dist/lib/lanes/watcher.d.ts +0 -287
  989. package/dist/lib/lanes/watcher.js +0 -1972
  990. package/dist/lib/mesh/live/daemon/config.d.ts +0 -36
  991. package/dist/lib/mesh/live/daemon/config.js +0 -120
  992. package/dist/lib/mesh/live/daemon/legacy-listen.d.ts +0 -43
  993. package/dist/lib/mesh/live/daemon/legacy-listen.js +0 -89
  994. package/dist/lib/mesh/live/daemon/live-cache.d.ts +0 -26
  995. package/dist/lib/mesh/live/daemon/live-cache.js +0 -116
  996. package/dist/lib/mesh/live/daemon/mqtt-events.d.ts +0 -7
  997. package/dist/lib/mesh/live/daemon/mqtt-events.js +0 -2
  998. package/dist/lib/mesh/live/daemon/mqtt-events.test-d.d.ts +0 -2
  999. package/dist/lib/mesh/live/daemon/mqtt-events.test-d.js +0 -10
  1000. package/dist/lib/mesh/live/daemon/version-drift.d.ts +0 -5
  1001. package/dist/lib/mesh/live/daemon/version-drift.js +0 -47
  1002. package/dist/lib/mesh/live/daemon/work-subscribe.d.ts +0 -32
  1003. package/dist/lib/mesh/live/daemon/work-subscribe.js +0 -262
  1004. package/dist/lib/mesh/live/report-summary.d.ts +0 -45
  1005. package/dist/lib/mesh/live/report-summary.js +0 -234
  1006. package/dist/lib/mesh/live/session-seq.d.ts +0 -4
  1007. package/dist/lib/mesh/live/session-seq.js +0 -33
  1008. package/dist/lib/mesh/project-board.d.ts +0 -65
  1009. package/dist/lib/mesh/project-board.js +0 -181
  1010. package/dist/lib/mesh/story-status.d.ts +0 -22
  1011. package/dist/lib/mesh/story-status.js +0 -49
  1012. package/dist/lib/monitor.d.ts +0 -149
  1013. package/dist/lib/monitor.js +0 -519
  1014. package/dist/lib/node-binary.d.ts +0 -50
  1015. package/dist/lib/node-binary.js +0 -133
  1016. package/dist/lib/notify-recipient.d.ts +0 -7
  1017. package/dist/lib/notify-recipient.js +0 -11
  1018. package/dist/lib/onboarding/checkpoint.d.ts +0 -13
  1019. package/dist/lib/onboarding/checkpoint.js +0 -44
  1020. package/dist/lib/onboarding/cli/onboard.d.ts +0 -53
  1021. package/dist/lib/onboarding/cli/onboard.js +0 -135
  1022. package/dist/lib/onboarding/cli/prompts.d.ts +0 -28
  1023. package/dist/lib/onboarding/cli/prompts.js +0 -83
  1024. package/dist/lib/onboarding/errors.d.ts +0 -34
  1025. package/dist/lib/onboarding/errors.js +0 -59
  1026. package/dist/lib/onboarding/index.d.ts +0 -13
  1027. package/dist/lib/onboarding/index.js +0 -13
  1028. package/dist/lib/onboarding/orchestrator.d.ts +0 -47
  1029. package/dist/lib/onboarding/orchestrator.js +0 -569
  1030. package/dist/lib/onboarding/types.d.ts +0 -79
  1031. package/dist/lib/onboarding/types.js +0 -8
  1032. package/dist/lib/policy-validator.d.ts +0 -7
  1033. package/dist/lib/policy-validator.js +0 -187
  1034. package/dist/lib/process-flag-company-context.d.ts +0 -8
  1035. package/dist/lib/process-flag-company-context.js +0 -67
  1036. package/dist/lib/report-publish.d.ts +0 -175
  1037. package/dist/lib/report-publish.js +0 -421
  1038. package/dist/lib/report-run.d.ts +0 -62
  1039. package/dist/lib/report-run.js +0 -123
  1040. package/dist/lib/report-snapshot.d.ts +0 -12
  1041. package/dist/lib/report-snapshot.js +0 -135
  1042. package/dist/lib/report-templates.d.ts +0 -9
  1043. package/dist/lib/report-templates.js +0 -21
  1044. package/dist/lib/scaffold-cli-floor.d.ts +0 -8
  1045. package/dist/lib/scaffold-cli-floor.js +0 -50
  1046. package/dist/lib/search-index/collection-path-repair-flag.d.ts +0 -4
  1047. package/dist/lib/search-index/collection-path-repair-flag.js +0 -7
  1048. package/dist/lib/search-index/embed-lock.d.ts +0 -32
  1049. package/dist/lib/search-index/embed-lock.js +0 -286
  1050. package/dist/lib/search-index/load-gate.d.ts +0 -57
  1051. package/dist/lib/search-index/load-gate.js +0 -270
  1052. package/dist/lib/search-index/max-doc-bytes.d.ts +0 -219
  1053. package/dist/lib/search-index/max-doc-bytes.js +0 -463
  1054. package/dist/lib/search-index/qmd-exit-diagnostics.d.ts +0 -5
  1055. package/dist/lib/search-index/qmd-exit-diagnostics.js +0 -40
  1056. package/dist/lib/search-index/reindex-single-flight-flag.d.ts +0 -5
  1057. package/dist/lib/search-index/reindex-single-flight-flag.js +0 -8
  1058. package/dist/lib/service-manager/index.d.ts +0 -42
  1059. package/dist/lib/service-manager/index.js +0 -105
  1060. package/dist/lib/service-manager/launchd.d.ts +0 -81
  1061. package/dist/lib/service-manager/launchd.js +0 -212
  1062. package/dist/lib/service-manager/systemd.d.ts +0 -19
  1063. package/dist/lib/service-manager/systemd.js +0 -72
  1064. package/dist/lib/service-manager/types.d.ts +0 -34
  1065. package/dist/lib/service-manager/types.js +0 -26
  1066. package/dist/lib/test-utils/isolated-flag-company-context.d.ts +0 -3
  1067. package/dist/lib/test-utils/isolated-flag-company-context.js +0 -18
  1068. package/dist/lib/work-context/bind-best.d.ts +0 -66
  1069. package/dist/lib/work-context/bind-best.js +0 -344
  1070. package/dist/lib/work-context/bind-calling-session.d.ts +0 -26
  1071. package/dist/lib/work-context/bind-calling-session.js +0 -63
  1072. package/dist/lib/work-context/company-rebind.d.ts +0 -23
  1073. package/dist/lib/work-context/company-rebind.js +0 -59
  1074. package/dist/lib/work-context/default-company.d.ts +0 -43
  1075. package/dist/lib/work-context/default-company.js +0 -83
  1076. package/dist/lib/work-context/outbox-quarantine-status-flag.d.ts +0 -4
  1077. package/dist/lib/work-context/outbox-quarantine-status-flag.js +0 -7
  1078. package/dist/lib/work-context/prd-sync.d.ts +0 -56
  1079. package/dist/lib/work-context/prd-sync.js +0 -197
  1080. package/dist/lib/work-context/preflight-contract.d.ts +0 -28
  1081. package/dist/lib/work-context/preflight-contract.js +0 -85
  1082. package/dist/lib/work-context/prompt-rebind.d.ts +0 -25
  1083. package/dist/lib/work-context/prompt-rebind.js +0 -85
  1084. package/dist/lib/work-context/windows-acl.d.ts +0 -123
  1085. package/dist/lib/work-context/windows-acl.js +0 -425
  1086. package/dist/lib/workers-registry/read.d.ts +0 -15
  1087. package/dist/lib/workers-registry/read.js +0 -17
  1088. package/dist/native-fast-route.d.ts +0 -7
  1089. package/dist/native-fast-route.js +0 -45
  1090. package/dist/node-network-compat-core.d.ts +0 -12
  1091. package/dist/node-network-compat-core.js +0 -41
  1092. package/dist/node-network-compat-fast.d.ts +0 -7
  1093. package/dist/node-network-compat-fast.js +0 -23
  1094. package/dist/secrets-env-fast-path.d.ts +0 -5
  1095. package/dist/secrets-env-fast-path.js +0 -16
  1096. package/dist/secrets-env-fast-route.d.ts +0 -18
  1097. package/dist/secrets-env-fast-route.js +0 -54
  1098. package/dist/utils/checkpoint-stamp-write-error.d.ts +0 -28
  1099. package/dist/utils/checkpoint-stamp-write-error.js +0 -99
  1100. package/dist/utils/cli-network-lifecycle.d.ts +0 -4
  1101. package/dist/utils/cli-network-lifecycle.js +0 -11
  1102. package/dist/utils/cognito-refresh-sentry.d.ts +0 -3
  1103. package/dist/utils/cognito-refresh-sentry.js +0 -49
  1104. package/dist/utils/console-origin.d.ts +0 -9
  1105. package/dist/utils/console-origin.js +0 -28
  1106. package/dist/utils/exclusive-lock.d.ts +0 -32
  1107. package/dist/utils/exclusive-lock.js +0 -75
  1108. package/dist/utils/fast-stream-errors.d.ts +0 -12
  1109. package/dist/utils/fast-stream-errors.js +0 -18
  1110. package/dist/utils/files-browse-acl.d.ts +0 -5
  1111. package/dist/utils/files-browse-acl.js +0 -11
  1112. package/dist/utils/hq-core-yaml.d.ts +0 -10
  1113. package/dist/utils/hq-core-yaml.js +0 -40
  1114. package/dist/utils/hq-root-resolver.d.ts +0 -13
  1115. package/dist/utils/hq-root-resolver.js +0 -103
  1116. package/dist/utils/redact-url.d.ts +0 -14
  1117. package/dist/utils/redact-url.js +0 -218
  1118. package/dist/utils/scaffold-fast-import-guard.d.ts +0 -2
  1119. package/dist/utils/scaffold-fast-import-guard.js +0 -40
  1120. package/dist/utils/scaffold-packlist-guard.d.ts +0 -7
  1121. package/dist/utils/scaffold-packlist-guard.js +0 -23
  1122. package/dist/utils/scaffold-sibling-references.d.ts +0 -22
  1123. package/dist/utils/scaffold-sibling-references.js +0 -156
  1124. package/dist/utils/secret-load-availability.d.ts +0 -8
  1125. package/dist/utils/secret-load-availability.js +0 -54
  1126. package/dist/utils/staged-install.d.ts +0 -34
  1127. package/dist/utils/staged-install.js +0 -346
  1128. package/dist/utils/staged-npm-install.d.ts +0 -16
  1129. package/dist/utils/staged-npm-install.js +0 -264
  1130. package/dist/utils/sync-log-sources.d.ts +0 -15
  1131. package/dist/utils/sync-log-sources.js +0 -36
  1132. package/dist/utils/table-width.d.ts +0 -7
  1133. package/dist/utils/table-width.js +0 -15
  1134. package/dist/utils/update-command-supervisor.cjs +0 -247
  1135. package/dist/utils/upload-author.d.ts +0 -15
  1136. package/dist/utils/upload-author.js +0 -39
  1137. package/dist/utils/version-report.d.ts +0 -27
  1138. package/dist/utils/version-report.js +0 -105
  1139. package/dist/version-request.d.ts +0 -14
  1140. package/dist/version-request.js +0 -36
  1141. package/scripts/mesh-daemon-if-stale.mjs +0 -20
  1142. package/templates/reports/blank.md +0 -3
  1143. package/templates/reports/daily-digest.md +0 -11
  1144. package/templates/runbooks/update-report.md +0 -21
@@ -1,3193 +0,0 @@
1
- /**
2
- * Periodic reconcile pass (spec §5.7, §6.8, §9).
3
- *
4
- * Owned by the machine, not by any lane. Reads never mutate; every write is a
5
- * compare-and-set. The per-lane watcher owns progress/terminal/silence of a
6
- * live process — this pass owns the transitions no single lane can own.
7
- */
8
- import * as fs from "node:fs";
9
- import * as path from "node:path";
10
- import { parseUserYaml } from "../../utils/user-yaml-error.js";
11
- import { compareLaneQueueOrder, hasActiveAdmissionReservation, hasActivePendingResume, laneAdmissionCapReason, laneRunsOnHost, withAllLaneAdmissionLocks, } from "./admission.js";
12
- import { accountStatus, isCredentialProven, resolveAccount } from "./accounts.js";
13
- import { effectiveAccountHome } from "./account-home.js";
14
- import { enforceRegistryApprovalGate } from "./approval-gate.js";
15
- import { loadLanesConfig, providerQuotaDefaultHoldMs } from "./config.js";
16
- import { MAX_LANE_DEPTH } from "./constants.js";
17
- import { AccountResolutionError, LanesError, isLaneVersionConflict } from "./errors.js";
18
- import { loadWorkerProfile } from "./profile.js";
19
- import { terminalChildResumeHireGrantError, withHireGrantAdmission, } from "./grants.js";
20
- import { classifySessionLiveness, defaultLivenessDeps, loadLivenessIndex, readDaemonPresenceConnected, unindexedClaudeSessionLiveness, } from "./liveness.js";
21
- import { ANSWERED_QUESTIONS_FILE, armLaneWatcher, envelopeRepairMode, isPidRecordAlive, laneRunDir, readPidRecord, readProcStarttime, readSpawnManifest, markSpawnFailure, stopLaneProcesses, spawnEnvelopeRepair, spawnLaneWorker, retryLaneOnAlternateAccount, } from "./spawn.js";
22
- import { appendCappedHistory, lanesStoreRoot, parseLaneRecord, parseQuestionRecord, readLane, readQuestion, listQuestionIds, updateLane, updateQuestion, } from "./store.js";
23
- import { applyThrottleDecision, decideAdmission, defaultThrottleDeps, emptyThrottleState, evaluateThrottle, isCpuCheckEnabled, readAvailableMemMb, readThrottleState, resolveCpuBusyFraction, sampleCpuBusyObservation, recordCpuBusyObservation, throttleStatePath, writeThrottleState, } from "./throttle.js";
24
- import { readLanesRegistryFlag } from "./registry-flag.js";
25
- import { resolveLaneCompany } from "./lane-company.js";
26
- import { appendDropboxMessage } from "./dropbox.js";
27
- import { DEFAULT_RUNAWAY_SEARCH_GUARD_CONFIG, listHostProcesses, runRunawayToolGuard, RUNAWAY_TOOL_GUARD_FLAG, } from "./runaway-tool-guard.js";
28
- import { LEAD_LANE_KEEP_ALIVE_FLAG, LEAD_LANE_KEEP_ALIVE_RESUME_BLOCKED_FLAG, LEAD_LANE_KEEP_ALIVE_IMMEDIATE_FIRST_RESUME_FLAG, LEAD_LANE_KEEP_ALIVE_FIRST_RESUME_SETTLE_MS, LEAD_LANE_KEEP_ALIVE_BLOCKED_GRACE_MS, LEAD_LANE_KEEP_ALIVE_BLOCKED_RESUME_TEXT, queueLaneTerminalResume, LEAD_LANE_KEEP_ALIVE_INTERRUPTED_RESUME_TEXT, LEAD_LANE_KEEP_ALIVE_MAX_RESUMES, LEAD_LANE_KEEP_ALIVE_RESUME_TEXT, LANE_SUPERSESSION_LINEAGE_AWARE_FLAG, doneKeepAliveVerdict, hasPendingKeepAliveWork, keepAliveExclusion, loadRetiredLaneIds, leadLaneKeepAliveBackoffMs, undeliveredAnsweredQuestions, } from "./keep-alive.js";
29
- import { providerSupportsSessionResume } from "./delivery.js";
30
- import { applyDefaultQuotaHold, clearAccountQuotaAfterSuccessfulTurnBestEffort, readQuotaSignal, recordAccountQuota, } from "./quota.js";
31
- import { answeredQuestionEntries, questionRecordRefusalCode, raiseEnvelopeQuestions, resolveQuestionCompanyWithFallback, } from "./questions.js";
32
- import { SILENCE_MS, appendWatcherEvent, classifyLaneExit, isPidAlive, missingExpectedEnvelopeFile, readLatestEnvelopeFile, readLatestEnvelopeRecord, watcherEventsPath, watcherOutLogPath, writeWatcherDecision, } from "./watcher.js";
33
- import { createLaneMesh, emitLaneDecisionEvents, emitLaneTerminalStateEvent, laneMeshCompany, } from "./mesh-events.js";
34
- import { activeLaneStories, laneStories, terminalEnvelopeStoryStatus } from "./stories.js";
35
- import { applyStorySwitchEnvelope } from "./story-switch.js";
36
- import { readProjectStoryStates } from "./story-ledger.js";
37
- import { decideEnvelopeRepair, envelopeQuestionIds, routeProviderFailure } from "./watcher.js";
38
- import { PROVIDER_REJECTED_REASON, providerRejectionDetails, readProviderRejection, } from "./provider-rejection.js";
39
- /** Spec §9: unanswered at the current hop for 60 minutes → one hop. */
40
- export const QUESTION_AGE_MS = 60 * 60 * 1000;
41
- /** Spec §6.6 + §9: a depth-3 worker has two hops to age toward the user. */
42
- export const MAX_AGED_ADVANCES = MAX_LANE_DEPTH - 1;
43
- /** Aligns with watcher silence: a host that has not reported for this long is dead. */
44
- export const DEAD_HOST_MS = SILENCE_MS;
45
- export const RECONCILE_LOCK_STALE_MS = 30_000;
46
- export const RECONCILE_LOCK_TIMEOUT_MS = 5_000;
47
- /** A pickup probe that has not cleared its marker within this window is abandoned. */
48
- export const PICKUP_VALIDATION_MAX_AGE_MS = 15 * 60 * 1000;
49
- export const PICKUP_VALIDATION_TIMEOUT_REASON = "pickup_validation_timeout";
50
- export const CLOSE_INTERRUPTED_DONE_STORY_WORKERS_FLAG = "lanes.close-interrupted-done-story-workers";
51
- const INTERRUPTED_DONE_STORY_MIN_AGE_MS = 24 * 60 * 60 * 1000;
52
- export const RECONCILE_UNIT_NAME = "hq-lanes-reconcile.service";
53
- export const RECONCILE_TIMER_NAME = "hq-lanes-reconcile.timer";
54
- function spawnDepsForHost(deps) {
55
- return {
56
- ...(deps.spawnLaneDeps ?? {}),
57
- env: deps.spawnLaneDeps?.env ?? deps.env ?? process.env,
58
- hostname: deps.host.hostname,
59
- admissionHost: deps.host,
60
- };
61
- }
62
- function logSkip(skip) {
63
- process.stderr.write(`hq lanes reconcile: skipped ${skip.kind}${skip.id ? ` ${skip.id}` : ""}: ${skip.why}\n`);
64
- }
65
- function emptySummary(dryRun) {
66
- return {
67
- ok: true,
68
- action: "reconcile",
69
- dry_run: dryRun,
70
- admitted: [],
71
- overdue: [],
72
- aged: [],
73
- advanced: [],
74
- resumed: [],
75
- dead_host: [],
76
- heartbeats: [],
77
- skipped: [],
78
- keep_alive_blocked: [],
79
- errors: [],
80
- heartbeat_at: null,
81
- };
82
- }
83
- function markDirty(summary, skip) {
84
- summary.skipped.push(skip);
85
- summary.ok = false;
86
- logSkip(skip);
87
- }
88
- function terminalizeModelUnsupported(hqRoot, lane, err) {
89
- if (!(err instanceof LanesError) || err.error !== "model_unsupported")
90
- return;
91
- try {
92
- markSpawnFailure(hqRoot, lane.lane_id, err.error, {
93
- ...err.details,
94
- message: err.message,
95
- });
96
- }
97
- catch (writeErr) {
98
- process.stderr.write(`hq lanes reconcile: failed to terminalize ${lane.lane_id} after model validation: ${writeErr instanceof Error ? writeErr.message : String(writeErr)}\n`);
99
- }
100
- }
101
- function markError(summary, err) {
102
- const message = err instanceof Error ? err.message : String(err);
103
- const code = err instanceof LanesError ? err.error : undefined;
104
- summary.errors.push(code ? { message, code } : { message });
105
- summary.ok = false;
106
- process.stderr.write(`hq lanes reconcile: ${message}${err instanceof Error && err.stack ? `\n${err.stack}` : ""}\n`);
107
- }
108
- export function reconcileHeartbeatPath(hqRoot) {
109
- return path.join(hqRoot, "workspace", "lanes-runs", "reconcile-heartbeat.json");
110
- }
111
- export function timeoutPath(hqRoot, laneId) {
112
- return path.join(laneRunDir(hqRoot, laneId), "timeout.json");
113
- }
114
- export function laneRunsHere(lane, host) {
115
- return laneRunsOnHost(lane, host);
116
- }
117
- function hasFreshAdmissionReservation(lane, nowMs) {
118
- return hasActiveAdmissionReservation(lane, nowMs);
119
- }
120
- export function agedAdvanceCount(question) {
121
- return question.routing_history.filter((entry) => entry.reason === "unanswered_timeout").length;
122
- }
123
- export function currentHopStartedAtMs(question) {
124
- const atCurrent = [...question.routing_history]
125
- .reverse()
126
- .find((entry) => entry.hop === question.hop);
127
- const raw = atCurrent?.at ?? question.asked_at;
128
- const ms = Date.parse(raw);
129
- return Number.isFinite(ms) ? ms : Number.NEGATIVE_INFINITY;
130
- }
131
- export function shouldAgeQuestion(question, audience, nowMs, ageWindowMs = QUESTION_AGE_MS) {
132
- if (question.status !== "pending")
133
- return false;
134
- if (audience.kind === "user")
135
- return false;
136
- if (agedAdvanceCount(question) >= MAX_AGED_ADVANCES)
137
- return false;
138
- const started = currentHopStartedAtMs(question);
139
- if (started === Number.NEGATIVE_INFINITY)
140
- return false;
141
- return nowMs - started >= ageWindowMs;
142
- }
143
- export function shouldAdvanceAudienceEnded(audience, liveness) {
144
- if (audience.kind === "user")
145
- return false;
146
- if (audience.kind !== "session")
147
- return false;
148
- return liveness === "ended";
149
- }
150
- export function readTimeoutDeadline(hqRoot, laneId) {
151
- const filePath = timeoutPath(hqRoot, laneId);
152
- let text;
153
- try {
154
- text = fs.readFileSync(filePath, "utf8");
155
- }
156
- catch (err) {
157
- const code = err.code;
158
- if (code === "ENOENT")
159
- return null;
160
- throw err;
161
- }
162
- let parsed;
163
- try {
164
- parsed = JSON.parse(text);
165
- }
166
- catch (err) {
167
- throw new LanesError("timeout_unreadable", `Timeout file for ${laneId} is not valid JSON: ${err instanceof Error ? err.message : String(err)}`, { lane_id: laneId, path: filePath });
168
- }
169
- if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
170
- throw new LanesError("timeout_unreadable", `Timeout file for ${laneId} is not an object`, { lane_id: laneId, path: filePath });
171
- }
172
- const deadline = parsed.deadline_at;
173
- if (typeof deadline !== "string" || !Number.isFinite(Date.parse(deadline))) {
174
- return null;
175
- }
176
- const timeoutSecs = parsed.timeout_secs;
177
- return {
178
- deadline_at: deadline,
179
- timeout_secs: typeof timeoutSecs === "number" && Number.isInteger(timeoutSecs)
180
- ? timeoutSecs
181
- : undefined,
182
- };
183
- }
184
- export function isDeadlinePassed(deadlineAt, nowMs) {
185
- const at = Date.parse(deadlineAt);
186
- if (!Number.isFinite(at))
187
- return false;
188
- return nowMs > at;
189
- }
190
- function readDirJsonIds(dir, label) {
191
- let names;
192
- try {
193
- names = fs.readdirSync(dir);
194
- }
195
- catch (err) {
196
- const code = err.code;
197
- if (code === "ENOENT")
198
- return [];
199
- throw new LanesError("store_unreadable", `Failed to read ${label} directory ${dir}: ${err instanceof Error ? err.message : String(err)}`, { path: dir });
200
- }
201
- return names.filter((name) => typeof name === "string" &&
202
- name.endsWith(".json") &&
203
- !name.startsWith(".") &&
204
- !name.includes(".conflict-"));
205
- }
206
- function loadLaneRecords(hqRoot, summary) {
207
- const dir = path.join(lanesStoreRoot(hqRoot), "lanes");
208
- const names = readDirJsonIds(dir, "lane");
209
- const lanes = [];
210
- for (const name of names) {
211
- const filePath = path.join(dir, name);
212
- const id = name.slice(0, -".json".length);
213
- try {
214
- const text = fs.readFileSync(filePath, "utf8");
215
- const parsed = JSON.parse(text);
216
- lanes.push(parseLaneRecord(parsed, filePath));
217
- }
218
- catch (err) {
219
- markDirty(summary, {
220
- kind: "lane",
221
- id,
222
- why: `unreadable: ${err instanceof Error ? err.message : String(err)}`,
223
- });
224
- }
225
- }
226
- return lanes;
227
- }
228
- function loadQuestionRecords(hqRoot, summary) {
229
- const dir = path.join(lanesStoreRoot(hqRoot), "questions");
230
- const names = readDirJsonIds(dir, "question");
231
- const questions = [];
232
- for (const name of names) {
233
- const filePath = path.join(dir, name);
234
- const id = name.slice(0, -".json".length);
235
- try {
236
- const text = fs.readFileSync(filePath, "utf8");
237
- const parsed = JSON.parse(text);
238
- questions.push(parseQuestionRecord(parsed, filePath));
239
- }
240
- catch (err) {
241
- markDirty(summary, {
242
- kind: "question",
243
- id,
244
- why: `unreadable: ${err instanceof Error ? err.message : String(err)}`,
245
- });
246
- }
247
- }
248
- return questions;
249
- }
250
- function defaultReadSessionSenior(hqRoot, sessionId) {
251
- const filePath = path.join(hqRoot, "workspace", "sessions", sessionId, "meta.yaml");
252
- let text;
253
- try {
254
- text = fs.readFileSync(filePath, "utf8");
255
- }
256
- catch (err) {
257
- const code = err.code;
258
- if (code === "ENOENT")
259
- return { kind: "user" };
260
- throw err;
261
- }
262
- const doc = parseUserYaml(text, filePath);
263
- if (doc === null || typeof doc !== "object" || Array.isArray(doc)) {
264
- return { kind: "user" };
265
- }
266
- const senior = doc.senior;
267
- if (senior === "user" || senior === undefined || senior === null) {
268
- return { kind: "user" };
269
- }
270
- if (typeof senior === "string") {
271
- const colon = senior.indexOf(":");
272
- if (colon <= 0)
273
- return { kind: "user" };
274
- const kind = senior.slice(0, colon);
275
- const id = senior.slice(colon + 1);
276
- if (kind === "session" || kind === "lane")
277
- return { kind, id };
278
- if (kind === "user")
279
- return { kind: "user" };
280
- }
281
- if (typeof senior === "object" && !Array.isArray(senior)) {
282
- const rec = senior;
283
- if (rec.kind === "user")
284
- return { kind: "user" };
285
- if ((rec.kind === "session" || rec.kind === "lane") &&
286
- typeof rec.id === "string" &&
287
- rec.id.length > 0) {
288
- return { kind: rec.kind, id: rec.id };
289
- }
290
- }
291
- return { kind: "user" };
292
- }
293
- export function resolveQuestionAudience(question, readLaneSenior, readSessionSenior) {
294
- let current = { kind: "lane", id: question.origin.id };
295
- for (let hop = 0; hop < question.hop; hop += 1) {
296
- if (current.kind === "user")
297
- return current;
298
- if (current.kind === "lane") {
299
- if (!current.id)
300
- return current;
301
- current = readLaneSenior(current.id);
302
- continue;
303
- }
304
- if (!current.id)
305
- return current;
306
- current = readSessionSenior(current.id);
307
- }
308
- return current;
309
- }
310
- function casLaneIf(hqRoot, laneId, shouldWrite, updater, dryRun) {
311
- for (let i = 0; i < 5; i += 1) {
312
- const current = readLane(hqRoot, laneId);
313
- if (!shouldWrite(current))
314
- return { changed: false, record: current };
315
- if (dryRun)
316
- return { changed: true, record: updater(current) };
317
- try {
318
- return {
319
- changed: true,
320
- record: updateLane(hqRoot, laneId, current.version, "reconcile", updater),
321
- };
322
- }
323
- catch (err) {
324
- if (!isLaneVersionConflict(err) || i === 4)
325
- throw err;
326
- }
327
- }
328
- throw new LanesError("version_conflict", `Exhausted CAS retries for lane ${laneId}`, { lane_id: laneId });
329
- }
330
- function laneStoryLedgerIsClosed(hqRoot, lane) {
331
- const stories = laneStories(lane);
332
- if (stories.length === 0)
333
- return { closed: false, states: [] };
334
- const states = [];
335
- for (const projectId of new Set(stories.map((story) => story.project_id))) {
336
- const projectStories = stories.filter((story) => story.project_id === projectId);
337
- const ledgerStates = readProjectStoryStates(hqRoot, projectId, projectStories.map((story) => story.story_id), lane.company?.slug);
338
- for (const story of projectStories) {
339
- const state = ledgerStates.get(story.story_id);
340
- if (state === undefined)
341
- return { closed: false, states: [...states, "missing"] };
342
- states.push(state);
343
- }
344
- }
345
- return { closed: states.length > 0 && states.every((state) => state === "done" || state === "dropped"), states };
346
- }
347
- async function closeInterruptedDoneStoryWorkers(hqRoot, lanes, questions, summary, deps, now, enabledByCompany) {
348
- if (deps.dryRun)
349
- return;
350
- const nowMs = now.getTime();
351
- const at = now.toISOString();
352
- for (const lane of lanes) {
353
- if (lane.depth < 1 || lane.state !== "interrupted" || lane.keep_alive === true ||
354
- !laneRunsHere(lane, deps.host) || !lane.interrupted_at ||
355
- !Number.isFinite(Date.parse(lane.interrupted_at)) ||
356
- nowMs - Date.parse(lane.interrupted_at) < INTERRUPTED_DONE_STORY_MIN_AGE_MS ||
357
- hasPendingKeepAliveWork(hqRoot, lane, questions))
358
- continue;
359
- let company;
360
- try {
361
- company = resolveLaneCompany(hqRoot, lane).company;
362
- }
363
- catch (err) {
364
- markDirty(summary, { kind: "lane", id: lane.lane_id, why: `story close company lookup failed: ${err instanceof Error ? err.message : String(err)}` });
365
- continue;
366
- }
367
- if (!company || enabledByCompany.get(keepAliveCompanyKey(company)) !== true)
368
- continue;
369
- let ledger;
370
- try {
371
- ledger = laneStoryLedgerIsClosed(hqRoot, lane);
372
- }
373
- catch (err) {
374
- markDirty(summary, { kind: "lane", id: lane.lane_id, why: `story close ledger read failed: ${err instanceof Error ? err.message : String(err)}` });
375
- continue;
376
- }
377
- if (!ledger.closed)
378
- continue;
379
- try {
380
- const result = casLaneIf(hqRoot, lane.lane_id, (current) => {
381
- const currentCompany = resolveLaneCompany(hqRoot, current).company;
382
- if (current.state !== "interrupted" || current.depth < 1 || current.keep_alive === true ||
383
- !currentCompany || enabledByCompany.get(keepAliveCompanyKey(currentCompany)) !== true ||
384
- !laneRunsHere(current, deps.host) || !current.interrupted_at ||
385
- !Number.isFinite(Date.parse(current.interrupted_at)) ||
386
- nowMs - Date.parse(current.interrupted_at) < INTERRUPTED_DONE_STORY_MIN_AGE_MS ||
387
- hasPendingKeepAliveWork(hqRoot, current, questions))
388
- return false;
389
- return laneStoryLedgerIsClosed(hqRoot, current).closed;
390
- }, (current) => ({
391
- ...current,
392
- state: "killed",
393
- queued_reason: undefined,
394
- queued_details: undefined,
395
- updated_at: at,
396
- terminal_reason: "story_closed",
397
- terminal_details: { ...(current.terminal_details ?? {}), operator_reason: "story_closed" },
398
- stop_history: [...(current.stop_history ?? []), { at, reason: "story_closed" }],
399
- }), false);
400
- if (!result.changed)
401
- continue;
402
- stopLaneProcesses(hqRoot, lane.lane_id, {
403
- killProcess: deps.spawnLaneDeps?.killProcess,
404
- isAlive: deps.isAlive,
405
- });
406
- for (const question of questions) {
407
- if (question.origin.id !== lane.lane_id || question.status !== "pending")
408
- continue;
409
- casQuestionIf(hqRoot, question.question_id, (current) => current.status === "pending", (current) => ({
410
- ...current,
411
- status: "cancelled",
412
- routing_history: appendCappedHistory(current.routing_history, {
413
- hop: current.hop,
414
- audience: { kind: "user" },
415
- at,
416
- reason: "cancelled",
417
- }),
418
- }), false);
419
- }
420
- appendWatcherEvent(watcherEventsPath(hqRoot, lane.lane_id), {
421
- event: "story_closed",
422
- at,
423
- lane_id: lane.lane_id,
424
- story_state: [...new Set(ledger.states)].join(","),
425
- terminal_reason: "story_closed",
426
- });
427
- const mesh = deps.mesh === undefined ? createLaneMesh({ interactive: false }) : deps.mesh;
428
- if (mesh) {
429
- const timeoutMs = deps.meshEmitTimeoutMs ?? 10_000;
430
- let timer;
431
- try {
432
- await Promise.race([
433
- emitLaneTerminalStateEvent(mesh, result.record, "killed", "story_closed"),
434
- new Promise((_resolve, reject) => {
435
- timer = setTimeout(() => reject(new Error(`terminal mesh emit timed out after ${timeoutMs}ms`)), timeoutMs);
436
- }),
437
- ]);
438
- }
439
- catch (err) {
440
- markDirty(summary, { kind: "lane", id: lane.lane_id, why: `story close mesh emit failed: ${err instanceof Error ? err.message : String(err)}` });
441
- }
442
- finally {
443
- if (timer)
444
- clearTimeout(timer);
445
- }
446
- }
447
- }
448
- catch (err) {
449
- markDirty(summary, { kind: "lane", id: lane.lane_id, why: `story close failed: ${err instanceof Error ? err.message : String(err)}` });
450
- }
451
- }
452
- }
453
- function writeQueueReason(hqRoot, lane, reason, dryRun) {
454
- if (lane.queued_reason === reason)
455
- return;
456
- casLaneIf(hqRoot, lane.lane_id, (current) => current.state === "queued" && current.queued_reason !== reason, (current) => ({ ...current, queued_reason: reason, queued_details: undefined, admission_reserved_at: undefined }), dryRun);
457
- }
458
- function availableBalancedAccount(hqRoot, lane, occupying, config, host, profileMaxConcurrent, nowMs) {
459
- if (!lane.account || !lane.admission_accounts?.includes(lane.account))
460
- return undefined;
461
- if (laneAdmissionCapReason(occupying, lane, config, host, profileMaxConcurrent, nowMs) !== "account_cap") {
462
- return undefined;
463
- }
464
- const pinnedModel = lane.model_source === undefined || lane.model_source === "cli" || lane.model_source === "preserved"
465
- ? lane.model
466
- : lane.model_source === "profile"
467
- ? lane.model
468
- : undefined;
469
- for (const account of lane.admission_accounts) {
470
- if (account === lane.account)
471
- continue;
472
- if (config.providers[lane.provider].accounts[account]?.enabled !== true)
473
- continue;
474
- const status = accountStatus(lane.provider, account, config, {
475
- hostname: host.hostname,
476
- existsSync: (file) => fs.existsSync(file),
477
- });
478
- if (!isCredentialProven(status))
479
- continue;
480
- try {
481
- resolveAccount(lane.provider, account, config, {
482
- hqRoot,
483
- hostname: host.hostname,
484
- ...(pinnedModel !== undefined ? { model: pinnedModel } : {}),
485
- });
486
- }
487
- catch (err) {
488
- if (err instanceof AccountResolutionError)
489
- continue;
490
- throw err;
491
- }
492
- const candidate = { ...lane, account };
493
- if (laneAdmissionCapReason(occupying, candidate, config, host, profileMaxConcurrent, nowMs) === undefined) {
494
- return account;
495
- }
496
- }
497
- return undefined;
498
- }
499
- function writeAdmissionAccount(hqRoot, lane, account, dryRun) {
500
- return casLaneIf(hqRoot, lane.lane_id, (current) => current.state === "queued" &&
501
- current.account === lane.account &&
502
- current.admission_accounts?.includes(account) === true, (current) => ({ ...current, account }), dryRun).record;
503
- }
504
- function casQuestionIf(hqRoot, questionId, shouldWrite, updater, dryRun) {
505
- for (let i = 0; i < 5; i += 1) {
506
- const current = readQuestion(hqRoot, questionId);
507
- if (!shouldWrite(current))
508
- return { changed: false, record: current };
509
- if (dryRun)
510
- return { changed: true, record: updater(current) };
511
- try {
512
- return {
513
- changed: true,
514
- record: updateQuestion(hqRoot, questionId, current.version, updater),
515
- };
516
- }
517
- catch (err) {
518
- if (!isLaneVersionConflict(err) || i === 4)
519
- throw err;
520
- }
521
- }
522
- throw new LanesError("version_conflict", `Exhausted CAS retries for question ${questionId}`, { question_id: questionId });
523
- }
524
- async function emitAgedQuestionAtUser(summary, deps, lane, question) {
525
- if (deps.dryRun)
526
- return;
527
- const mesh = deps.mesh === undefined ? createLaneMesh({ interactive: false }) : deps.mesh;
528
- const company = laneMeshCompany(lane);
529
- if (!mesh || !company || !lane.thread_id)
530
- return;
531
- const timeoutMs = deps.meshEmitTimeoutMs ?? 10_000;
532
- let timer;
533
- try {
534
- await Promise.race([
535
- mesh.appendThreadEvent(company, lane.thread_id, "lane_question_at_user", { lane_id: lane.lane_id, question_id: question.question_id }),
536
- new Promise((_resolve, reject) => {
537
- timer = setTimeout(() => reject(new Error(`question-at-user mesh emit timed out after ${timeoutMs}ms`)), timeoutMs);
538
- }),
539
- ]);
540
- }
541
- catch (err) {
542
- markDirty(summary, {
543
- kind: "question",
544
- id: question.question_id,
545
- why: `question-at-user mesh emit failed: ${err instanceof Error ? err.message : String(err)}`,
546
- });
547
- }
548
- finally {
549
- if (timer)
550
- clearTimeout(timer);
551
- }
552
- }
553
- function pickupValidationPendingAtMs(lane) {
554
- const raw = lane.pickup_validation_pending_since ?? lane.updated_at;
555
- const at = Date.parse(raw);
556
- return Number.isFinite(at) ? at : null;
557
- }
558
- function pickupValidationExpired(lane, nowMs) {
559
- const pendingAtMs = pickupValidationPendingAtMs(lane);
560
- return pendingAtMs === null || nowMs - pendingAtMs > PICKUP_VALIDATION_MAX_AGE_MS;
561
- }
562
- function expireAbandonedPickupValidation(hqRoot, lanes, summary, deps, now) {
563
- const nowMs = now.getTime();
564
- for (const lane of lanes) {
565
- if (lane.state !== "queued" ||
566
- lane.pickup_validation_pending !== true ||
567
- !laneRunsHere(lane, deps.host) ||
568
- !pickupValidationExpired(lane, nowMs)) {
569
- continue;
570
- }
571
- try {
572
- const result = casLaneIf(hqRoot, lane.lane_id, (current) => current.state === "queued" &&
573
- current.pickup_validation_pending === true &&
574
- pickupValidationExpired(current, nowMs), (current) => {
575
- const pendingAtMs = pickupValidationPendingAtMs(current);
576
- return {
577
- ...current,
578
- state: "failed",
579
- queued_reason: undefined,
580
- queued_details: undefined,
581
- pickup_validation_pending: undefined,
582
- pickup_validation_pending_since: undefined,
583
- terminal_reason: PICKUP_VALIDATION_TIMEOUT_REASON,
584
- terminal_details: {
585
- pickup_validation_pending_since: current.pickup_validation_pending_since ?? current.updated_at,
586
- age_ms: pendingAtMs === null ? null : Math.max(0, nowMs - pendingAtMs),
587
- max_age_ms: PICKUP_VALIDATION_MAX_AGE_MS,
588
- },
589
- };
590
- }, deps.dryRun);
591
- if (result.changed) {
592
- summary.aged.push({
593
- id: lane.lane_id,
594
- from: "queued",
595
- to: "failed",
596
- reason: PICKUP_VALIDATION_TIMEOUT_REASON,
597
- });
598
- }
599
- }
600
- catch (err) {
601
- markDirty(summary, {
602
- kind: "lane",
603
- id: lane.lane_id,
604
- why: `pickup validation expiry failed: ${err instanceof Error ? err.message : String(err)}`,
605
- });
606
- }
607
- }
608
- }
609
- function fileNonEmpty(filePath) {
610
- try {
611
- return fs.statSync(filePath).size > 0;
612
- }
613
- catch (err) {
614
- const code = err.code;
615
- if (code === "ENOENT")
616
- return false;
617
- throw err;
618
- }
619
- }
620
- function lastHostReportMs(lane) {
621
- const raw = lane.last_host_report_at ?? lane.updated_at;
622
- const ms = Date.parse(raw);
623
- return Number.isFinite(ms) ? ms : 0;
624
- }
625
- function writeHeartbeatFile(hqRoot, summary, at) {
626
- const filePath = reconcileHeartbeatPath(hqRoot);
627
- fs.mkdirSync(path.dirname(filePath), { recursive: true });
628
- const tmp = `${filePath}.${process.pid}.${Date.now()}.tmp`;
629
- const body = `${JSON.stringify({ at, ok: summary.ok, action: "reconcile" }, null, 2)}\n`;
630
- try {
631
- fs.writeFileSync(tmp, body, { encoding: "utf8" });
632
- fs.renameSync(tmp, filePath);
633
- }
634
- catch (err) {
635
- try {
636
- fs.unlinkSync(tmp);
637
- }
638
- catch (unlinkErr) {
639
- const code = unlinkErr.code;
640
- if (code !== "ENOENT") {
641
- process.stderr.write(`hq lanes reconcile: unlink heartbeat tmp failed: ${unlinkErr instanceof Error ? unlinkErr.message : String(unlinkErr)}\n`);
642
- }
643
- }
644
- throw err;
645
- }
646
- }
647
- function writeAnsweredQuestions(hqRoot, lane, questions) {
648
- const filePath = path.join(laneRunDir(hqRoot, lane.lane_id), ANSWERED_QUESTIONS_FILE);
649
- fs.mkdirSync(path.dirname(filePath), { recursive: true });
650
- fs.writeFileSync(filePath, `${JSON.stringify(answeredQuestionEntries(hqRoot, lane, questions), null, 2)}\n`, "utf8");
651
- }
652
- async function withReconcileLock(hqRoot, fn) {
653
- const lockPath = path.join(hqRoot, "workspace", "lanes-runs", ".admission-locks", "reconcile.lock");
654
- fs.mkdirSync(path.dirname(lockPath), { recursive: true });
655
- const started = Date.now();
656
- while (true) {
657
- let fd;
658
- try {
659
- fd = fs.openSync(lockPath, "wx");
660
- try {
661
- fs.writeSync(fd, Buffer.from(`${process.pid}\n${Date.now()}\n`, "utf8"));
662
- }
663
- catch (err) {
664
- fs.closeSync(fd);
665
- fd = undefined;
666
- throw err;
667
- }
668
- try {
669
- return await fn();
670
- }
671
- finally {
672
- try {
673
- fs.closeSync(fd);
674
- }
675
- catch (err) {
676
- process.stderr.write(`hq lanes reconcile: lock close failed: ${err instanceof Error ? err.message : String(err)}\n`);
677
- }
678
- try {
679
- fs.unlinkSync(lockPath);
680
- }
681
- catch (err) {
682
- const code = err.code;
683
- if (code !== "ENOENT") {
684
- process.stderr.write(`hq lanes reconcile: lock unlink failed: ${err instanceof Error ? err.message : String(err)}\n`);
685
- }
686
- }
687
- }
688
- }
689
- catch (err) {
690
- const code = err.code;
691
- if (code !== "EEXIST")
692
- throw err;
693
- if (Date.now() - started > RECONCILE_LOCK_TIMEOUT_MS) {
694
- throw new LanesError("store_lock_timeout", `Timed out waiting for reconcile lock ${lockPath}`, { path: lockPath });
695
- }
696
- try {
697
- const raw = fs.readFileSync(lockPath, "utf8");
698
- const tsLine = raw.split("\n")[1];
699
- const ts = tsLine ? Number(tsLine) : NaN;
700
- if (Number.isFinite(ts) && Date.now() - ts > RECONCILE_LOCK_STALE_MS) {
701
- try {
702
- fs.unlinkSync(lockPath);
703
- }
704
- catch (unlinkErr) {
705
- const unlinkCode = unlinkErr.code;
706
- if (unlinkCode !== "ENOENT")
707
- throw unlinkErr;
708
- }
709
- continue;
710
- }
711
- }
712
- catch (statErr) {
713
- const statCode = statErr.code;
714
- if (statCode !== "ENOENT") {
715
- process.stderr.write(`hq lanes reconcile: stale-lock probe failed: ${statErr instanceof Error ? statErr.message : String(statErr)}\n`);
716
- }
717
- }
718
- Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 10);
719
- }
720
- }
721
- }
722
- function classifyDeadLocalLane(hqRoot, laneId, now) {
723
- const runDir = laneRunDir(hqRoot, laneId);
724
- const manifest = readSpawnManifest(runDir);
725
- const minEnvelopeName = manifest.envelope_path ? path.basename(manifest.envelope_path) : undefined;
726
- const latest = readLatestEnvelopeRecord(hqRoot, laneId, minEnvelopeName);
727
- const envelope = latest?.envelope ?? null;
728
- const hasPartialOutput = [
729
- path.join(runDir, "logs", "worker.out"),
730
- path.join(runDir, "logs", "worker.err"),
731
- path.join(runDir, "logs", "last-message.txt"),
732
- ].some((file) => fileNonEmpty(file));
733
- const lane = readLane(hqRoot, laneId);
734
- // The same routing as the watcher's exit path (watcher.ts): a provider
735
- // failure on the stream is read first, a 429 or quota-worded one becomes
736
- // the quota signal, any other becomes provider_rejected, and only a stream
737
- // with no failure object falls back to the quota reader.
738
- const workerOutOffset = readSpawnManifest(runDir).worker_out_offset ?? 0;
739
- const routed = envelope
740
- ? { quota: null, rejection: null }
741
- : routeProviderFailure(readProviderRejection(runDir, lane.provider, workerOutOffset), () => readQuotaSignal(runDir, lane.model, now, workerOutOffset), lane.model, now);
742
- // As in the watcher: a reset-less 429 gets the provider's default hold.
743
- const quota = routed.quota
744
- ? applyDefaultQuotaHold(routed.quota, providerQuotaDefaultHoldMs(loadLanesConfig(hqRoot).providers[lane.provider]), now)
745
- : null;
746
- return {
747
- classification: classifyLaneExit({
748
- envelope,
749
- hasPartialOutput,
750
- quota,
751
- providerRejection: routed.rejection,
752
- }),
753
- latest,
754
- quota,
755
- providerRejection: routed.rejection,
756
- lane,
757
- };
758
- }
759
- function runawayGuardCompanyKey(company) {
760
- return company
761
- ? `${company.uid ?? ""}\u0000${company.slug ?? ""}`
762
- : "unresolved-company";
763
- }
764
- function reportRunawayGuardError(summary, stage, error) {
765
- summary.ok = false;
766
- summary.errors.push({
767
- code: `runaway_tool_guard_${stage}_failed`,
768
- message: `runaway search guard ${stage} failed; no additional search processes were stopped`,
769
- });
770
- process.stderr.write(`${JSON.stringify({
771
- event: "lanes.runaway_tool_guard.failed",
772
- stage,
773
- error: error instanceof Error ? error.name : "UnknownError",
774
- fallback: "continue_without_argv",
775
- })}\n`);
776
- }
777
- async function runRunawayToolGuardPass(hqRoot, lanes, config, deps, now, summary) {
778
- if (deps.dryRun)
779
- return;
780
- const localRunningLanes = lanes.filter((lane) => lane.state === "running" && laneRunsHere(lane, deps.host));
781
- if (localRunningLanes.length === 0)
782
- return;
783
- const groups = new Map();
784
- for (const lane of localRunningLanes) {
785
- let worker;
786
- try {
787
- worker = readPidRecord(path.join(laneRunDir(hqRoot, lane.lane_id), "worker.pid"));
788
- if (!worker ||
789
- worker.starttime === undefined ||
790
- !isPidRecordAlive(worker, deps.isAlive))
791
- continue;
792
- }
793
- catch (error) {
794
- reportRunawayGuardError(summary, "worker_pid_read", error);
795
- continue;
796
- }
797
- let company;
798
- try {
799
- company = resolveLaneCompany(hqRoot, lane).company;
800
- }
801
- catch (error) {
802
- reportRunawayGuardError(summary, "company_resolution", error);
803
- continue;
804
- }
805
- const key = runawayGuardCompanyKey(company);
806
- const group = groups.get(key) ?? { company, lanes: [] };
807
- group.lanes.push({ laneId: lane.lane_id, agentPid: worker.pid, agentStarttime: worker.starttime });
808
- groups.set(key, group);
809
- }
810
- if (groups.size === 0)
811
- return;
812
- const readRegistryFlag = deps.readRegistryFlag ?? readLanesRegistryFlag;
813
- const enabledGroups = new Set();
814
- const env = deps.env ?? process.env;
815
- await Promise.all([...groups.entries()].map(async ([key, group]) => {
816
- try {
817
- const enabled = await readRegistryFlag(RUNAWAY_TOOL_GUARD_FLAG, {
818
- company: group.company ?? undefined,
819
- env,
820
- });
821
- if (enabled)
822
- enabledGroups.add(key);
823
- }
824
- catch (error) {
825
- reportRunawayGuardError(summary, "flag_read", error);
826
- }
827
- }));
828
- if (enabledGroups.size === 0)
829
- return;
830
- const laneProcesses = [...groups.entries()]
831
- .filter(([key]) => enabledGroups.has(key))
832
- .flatMap(([, group]) => group.lanes);
833
- if (laneProcesses.length === 0)
834
- return;
835
- let processes;
836
- try {
837
- processes = (deps.runawayToolProcessLister ?? listHostProcesses)();
838
- }
839
- catch (error) {
840
- reportRunawayGuardError(summary, "process_table_read", error);
841
- return;
842
- }
843
- const runawayConfig = config.runaway_search_guard ?? DEFAULT_RUNAWAY_SEARCH_GUARD_CONFIG;
844
- try {
845
- runRunawayToolGuard({
846
- hqRoot,
847
- lanes: laneProcesses,
848
- processes,
849
- config: runawayConfig,
850
- now,
851
- currentUid: process.getuid?.() ?? -1,
852
- isCurrentProcess: deps.runawayToolProcessIsCurrent ??
853
- ((pid, expectedStarttime) => readProcStarttime(pid) === expectedStarttime),
854
- signal: deps.runawayToolProcessSignal ?? ((pid, signal) => process.kill(pid, signal)),
855
- recordEvent: (laneId, event) => appendWatcherEvent(watcherEventsPath(hqRoot, laneId), event, deps.runawayToolEventAppend),
856
- notifyLane: (laneId, text) => appendDropboxMessage(hqRoot, laneId, {
857
- at: now.toISOString(),
858
- message_id: `runaway-search-guard:${laneId}:${now.getTime()}`,
859
- kind: "system_notice",
860
- text,
861
- }),
862
- reportError: (stage, errorName) => {
863
- summary.ok = false;
864
- summary.errors.push({
865
- code: `runaway_tool_guard_${stage}_failed`,
866
- message: `runaway search guard could not ${stage} a stopped process (${errorName})`,
867
- });
868
- process.stderr.write(`${JSON.stringify({
869
- event: "lanes.runaway_tool_guard.failed",
870
- stage,
871
- error: errorName,
872
- fallback: "continue_without_argv",
873
- })}\n`);
874
- },
875
- });
876
- }
877
- catch (error) {
878
- reportRunawayGuardError(summary, "execution", error);
879
- }
880
- }
881
- /** Unlocked pass body. `runReconcilePass` takes the process lock around this. */
882
- export async function runPassBody(hqRoot, deps) {
883
- const now = deps.now();
884
- const nowMs = now.getTime();
885
- const nowIso = now.toISOString();
886
- const summary = emptySummary(deps.dryRun);
887
- const livenessDeps = deps.livenessDeps ?? defaultLivenessDeps();
888
- let lanes;
889
- let questions;
890
- try {
891
- lanes = loadLaneRecords(hqRoot, summary);
892
- questions = loadQuestionRecords(hqRoot, summary);
893
- }
894
- catch (err) {
895
- markError(summary, err);
896
- return summary;
897
- }
898
- if (deps.terminalKeepAliveCandidate) {
899
- return runTerminalKeepAliveCandidatePass(hqRoot, lanes, summary, deps, now, deps.terminalKeepAliveCandidate);
900
- }
901
- let presenceConnected;
902
- try {
903
- presenceConnected =
904
- deps.presenceConnected ?? readDaemonPresenceConnected(livenessDeps);
905
- }
906
- catch (err) {
907
- markError(summary, err);
908
- return summary;
909
- }
910
- let livenessIndex;
911
- try {
912
- livenessIndex = loadLivenessIndex(hqRoot, undefined, livenessDeps, !deps.dryRun);
913
- }
914
- catch (err) {
915
- markError(summary, err);
916
- return summary;
917
- }
918
- const config = loadLanesConfig(hqRoot);
919
- const laneById = new Map(lanes.map((lane) => [lane.lane_id, lane]));
920
- const readLaneSenior = (laneId) => {
921
- const lane = laneById.get(laneId);
922
- if (lane)
923
- return lane.senior;
924
- try {
925
- return readLane(hqRoot, laneId).senior;
926
- }
927
- catch (err) {
928
- throw new LanesError("lane_not_found", `Cannot resolve senior for missing lane ${laneId}: ${err instanceof Error ? err.message : String(err)}`, { lane_id: laneId });
929
- }
930
- };
931
- const readSessionSenior = deps.readSessionSenior ??
932
- ((sessionId) => defaultReadSessionSenior(hqRoot, sessionId));
933
- const configuredClaudeHomes = Object.values(config.providers.claude.accounts).map((account) => effectiveAccountHome(account, deps.host.hostname).home);
934
- const sessionStateCache = new Map();
935
- const sessionState = (sessionId) => {
936
- if (sessionStateCache.has(sessionId)) {
937
- return sessionStateCache.get(sessionId) ?? null;
938
- }
939
- const entry = livenessIndex.sessions[sessionId];
940
- if (!entry) {
941
- const state = unindexedClaudeSessionLiveness(sessionId, nowMs, config.liveness, livenessDeps, configuredClaudeHomes);
942
- sessionStateCache.set(sessionId, state);
943
- return state;
944
- }
945
- const state = classifySessionLiveness(entry, nowMs, presenceConnected, config.liveness);
946
- sessionStateCache.set(sessionId, state);
947
- return state;
948
- };
949
- const questionAgeWindowMs = (audience) => audience.kind === "session"
950
- ? config.session_question_timeout_secs * 1000
951
- : QUESTION_AGE_MS;
952
- if (deps.afterLoad)
953
- await deps.afterLoad();
954
- await runRunawayToolGuardPass(hqRoot, lanes, config, deps, now, summary);
955
- const startDeps = deps;
956
- markOverdueLanes(hqRoot, lanes, summary, deps, now);
957
- await heartbeatAndDeadHosts(hqRoot, lanes, summary, startDeps, now);
958
- for (const question of questions) {
959
- if (question.status !== "pending")
960
- continue;
961
- let audience;
962
- try {
963
- audience = resolveQuestionAudience(question, readLaneSenior, readSessionSenior);
964
- }
965
- catch (err) {
966
- markDirty(summary, {
967
- kind: "question",
968
- id: question.question_id,
969
- why: `audience unreadable: ${err instanceof Error ? err.message : String(err)}`,
970
- });
971
- continue;
972
- }
973
- try {
974
- let liveness;
975
- try {
976
- liveness =
977
- audience.kind === "session" && audience.id
978
- ? sessionState(audience.id)
979
- : audience.kind === "user"
980
- ? "live"
981
- : null;
982
- }
983
- catch (err) {
984
- // A failed metadata probe cannot prove that the session ended, but it
985
- // must not suppress the independent unanswered-timeout rule below.
986
- markError(summary, err);
987
- liveness = null;
988
- }
989
- if (shouldAdvanceAudienceEnded(audience, liveness)) {
990
- const nextHop = question.hop + 1;
991
- const nextAudience = resolveQuestionAudience({ ...question, hop: nextHop }, readLaneSenior, readSessionSenior);
992
- const result = casQuestionIf(hqRoot, question.question_id, (current) => current.status === "pending" && current.hop === question.hop, (current) => {
993
- const entry = {
994
- hop: nextHop,
995
- audience: nextAudience,
996
- at: nowIso,
997
- reason: "audience_ended",
998
- };
999
- return {
1000
- ...current,
1001
- hop: nextHop,
1002
- routing_history: appendCappedHistory(current.routing_history, entry),
1003
- };
1004
- }, deps.dryRun);
1005
- if (result.changed) {
1006
- summary.advanced.push({
1007
- id: question.question_id,
1008
- from: String(question.hop),
1009
- to: String(nextHop),
1010
- reason: "audience_ended",
1011
- });
1012
- }
1013
- continue;
1014
- }
1015
- if (shouldAgeQuestion(question, audience, nowMs, questionAgeWindowMs(audience))) {
1016
- const nextHop = question.hop + 1;
1017
- const nextAudience = resolveQuestionAudience({ ...question, hop: nextHop }, readLaneSenior, readSessionSenior);
1018
- const result = casQuestionIf(hqRoot, question.question_id, (current) => {
1019
- if (current.status !== "pending" ||
1020
- agedAdvanceCount(current) >= MAX_AGED_ADVANCES) {
1021
- return false;
1022
- }
1023
- const currentAudience = resolveQuestionAudience(current, readLaneSenior, readSessionSenior);
1024
- return shouldAgeQuestion(current, currentAudience, nowMs, questionAgeWindowMs(currentAudience));
1025
- }, (current) => {
1026
- const entry = {
1027
- hop: current.hop + 1,
1028
- audience: nextAudience,
1029
- at: nowIso,
1030
- reason: "unanswered_timeout",
1031
- };
1032
- return {
1033
- ...current,
1034
- hop: current.hop + 1,
1035
- routing_history: appendCappedHistory(current.routing_history, entry),
1036
- };
1037
- }, deps.dryRun);
1038
- if (result.changed) {
1039
- summary.aged.push({
1040
- id: question.question_id,
1041
- from: String(question.hop),
1042
- to: String(nextHop),
1043
- reason: "unanswered_timeout",
1044
- });
1045
- if (nextAudience.kind === "user") {
1046
- const originLane = laneById.get(question.lane_id) ?? readLane(hqRoot, question.lane_id);
1047
- await emitAgedQuestionAtUser(summary, deps, originLane, result.record);
1048
- }
1049
- }
1050
- }
1051
- }
1052
- catch (err) {
1053
- markDirty(summary, {
1054
- kind: "question",
1055
- id: question.question_id,
1056
- why: `advance failed: ${err instanceof LanesError ? `${err.error}: ` : ""}${err instanceof Error ? err.message : String(err)}`,
1057
- });
1058
- }
1059
- }
1060
- const readRegistryFlag = deps.readRegistryFlag ?? readLanesRegistryFlag;
1061
- const storyCloseCompanies = new Map();
1062
- for (const lane of lanes) {
1063
- if (lane.depth < 1 || lane.state !== "interrupted" || lane.keep_alive === true ||
1064
- !laneRunsHere(lane, deps.host) || !lane.interrupted_at ||
1065
- !Number.isFinite(Date.parse(lane.interrupted_at)) ||
1066
- nowMs - Date.parse(lane.interrupted_at) < INTERRUPTED_DONE_STORY_MIN_AGE_MS ||
1067
- hasPendingKeepAliveWork(hqRoot, lane, questions) || laneStories(lane).length === 0)
1068
- continue;
1069
- try {
1070
- const company = resolveLaneCompany(hqRoot, lane).company;
1071
- if (company)
1072
- storyCloseCompanies.set(keepAliveCompanyKey(company), company);
1073
- }
1074
- catch (err) {
1075
- markDirty(summary, { kind: "lane", id: lane.lane_id, why: `story close company lookup failed: ${err instanceof Error ? err.message : String(err)}` });
1076
- }
1077
- }
1078
- const storyCloseEnabledByCompany = new Map();
1079
- const env = deps.env ?? process.env;
1080
- await Promise.all([...storyCloseCompanies.entries()].map(async ([key, company]) => {
1081
- try {
1082
- storyCloseEnabledByCompany.set(key, await readRegistryFlag(CLOSE_INTERRUPTED_DONE_STORY_WORKERS_FLAG, {
1083
- company: company ?? undefined,
1084
- env,
1085
- }));
1086
- }
1087
- catch (err) {
1088
- storyCloseEnabledByCompany.set(key, false);
1089
- markDirty(summary, { kind: "lane", why: `story close flag read failed for ${company?.slug ?? "unresolved company"}: ${err instanceof Error ? err.message : String(err)}` });
1090
- }
1091
- }));
1092
- const keepAliveFlagCompanies = new Map();
1093
- for (const lane of lanes) {
1094
- if (!(lane.state === "done" || lane.state === "interrupted" || lane.state === "blocked" || lane.state === "killed" || lane.state === "cancelled" || lane.state === "failed") ||
1095
- (lane.keep_alive !== true && lane.worker_profile === undefined) ||
1096
- (lane.state === "blocked" && lane.keep_alive !== true) ||
1097
- !laneRunsHere(lane, deps.host) ||
1098
- (lane.state === "done" && (lane.terminal_reason === PROVIDER_REJECTED_REASON ||
1099
- lane.terminal_reason === "account_not_logged_in" ||
1100
- !providerSupportsSessionResume(lane.provider) ||
1101
- !(lane.provider_session_id ?? lane.last_resume?.session_id))))
1102
- continue;
1103
- if (lane.state === "done") {
1104
- const envelope = readLatestEnvelopeRecord(hqRoot, lane.lane_id)?.envelope;
1105
- if (!envelope || envelope.decision !== "done")
1106
- continue;
1107
- const hasQueuedWork = lane.keep_alive_standing === true && hasPendingKeepAliveWork(hqRoot, lane, questions);
1108
- if (doneKeepAliveVerdict(lane, envelope, hasQueuedWork, {
1109
- hasPendingWork: hasPendingKeepAliveWork(hqRoot, lane, questions),
1110
- nowMs: now.getTime(),
1111
- nextCheckAnchorMs: Date.parse(lane.last_envelope?.at ?? lane.updated_at),
1112
- }) !== "eligible")
1113
- continue;
1114
- }
1115
- const company = resolveLaneCompany(hqRoot, lane).company;
1116
- if (company === null)
1117
- continue;
1118
- const key = keepAliveCompanyKey(company);
1119
- keepAliveFlagCompanies.set(key, company);
1120
- }
1121
- const keepAliveFlagByCompany = new Map();
1122
- const blockedResumeFlagByCompany = new Map();
1123
- const immediateFirstResumeFlagByCompany = new Map();
1124
- const supersessionLineageAwareByCompany = new Map();
1125
- await Promise.all([...keepAliveFlagCompanies.entries()].map(async ([key, company]) => {
1126
- const options = {
1127
- company: company ?? undefined,
1128
- env,
1129
- };
1130
- const hasBlockedKeepAliveLane = lanes.some((lane) => lane.state === "blocked" &&
1131
- lane.keep_alive === true &&
1132
- laneRunsHere(lane, startDeps.host) &&
1133
- keepAliveCompanyKey(resolveLaneCompany(hqRoot, lane).company) === key);
1134
- const [keepAliveEnabled, lineageAware, blockedResumeEnabled, immediateFirstResumeEnabled] = await Promise.all([
1135
- readRegistryFlag(LEAD_LANE_KEEP_ALIVE_FLAG, options),
1136
- readRegistryFlag(LANE_SUPERSESSION_LINEAGE_AWARE_FLAG, options),
1137
- hasBlockedKeepAliveLane
1138
- ? readRegistryFlag(LEAD_LANE_KEEP_ALIVE_RESUME_BLOCKED_FLAG, options)
1139
- : Promise.resolve(false),
1140
- readRegistryFlag(LEAD_LANE_KEEP_ALIVE_IMMEDIATE_FIRST_RESUME_FLAG, options),
1141
- ]);
1142
- keepAliveFlagByCompany.set(key, keepAliveEnabled);
1143
- blockedResumeFlagByCompany.set(key, blockedResumeEnabled);
1144
- immediateFirstResumeFlagByCompany.set(key, immediateFirstResumeEnabled);
1145
- supersessionLineageAwareByCompany.set(key, lineageAware);
1146
- }));
1147
- const admissionThrottleDeps = startDeps.throttleDeps ?? defaultThrottleDeps(startDeps.env ?? process.env);
1148
- const hasAdmissionCandidates = lanes.some((lane) => laneRunsHere(lane, startDeps.host) &&
1149
- (lane.state === "queued" || lane.state === "awaiting_input")) || [...keepAliveFlagByCompany.values()].some(Boolean);
1150
- const priorThrottleState = readThrottleState(throttleStatePath(hqRoot), admissionThrottleDeps);
1151
- const priorThrottleSnapshot = priorThrottleState === "missing" || priorThrottleState === "unreadable"
1152
- ? undefined
1153
- : priorThrottleState.cpu_snapshot;
1154
- const cpuObservation = await sampleCpuBusyObservation(admissionThrottleDeps, priorThrottleSnapshot, { measureBusy: hasAdmissionCandidates });
1155
- const admissionDeps = {
1156
- ...startDeps,
1157
- cpuBusyFraction: cpuObservation.busy,
1158
- cpuObservation,
1159
- skipAdmissionWithoutCpuSample: !hasAdmissionCandidates,
1160
- };
1161
- await withAllLaneAdmissionLocks(hqRoot, async () => {
1162
- const freshLanes = loadLaneRecords(hqRoot, summary);
1163
- const freshQuestions = loadQuestionRecords(hqRoot, summary);
1164
- await closeInterruptedDoneStoryWorkers(hqRoot, freshLanes, freshQuestions, summary, admissionDeps, now, storyCloseEnabledByCompany);
1165
- await resumeKeepAlive(hqRoot, freshLanes, freshQuestions, summary, admissionDeps, now, keepAliveFlagByCompany, supersessionLineageAwareByCompany, blockedResumeFlagByCompany, immediateFirstResumeFlagByCompany, deps.terminalKeepAliveCandidate);
1166
- const drainLanes = loadLaneRecords(hqRoot, summary);
1167
- await admitQueued(hqRoot, drainLanes, summary, admissionDeps, now);
1168
- await resumeAwaiting(hqRoot, drainLanes, freshQuestions, summary, admissionDeps);
1169
- });
1170
- if (!deps.dryRun && cpuObservation.snapshot !== undefined) {
1171
- const latestThrottleState = readThrottleState(throttleStatePath(hqRoot), admissionThrottleDeps);
1172
- if (latestThrottleState === "unreadable") {
1173
- markError(summary, new LanesError("throttle_state_unreadable", "Throttle state became unreadable while reconciling"));
1174
- }
1175
- else {
1176
- const currentThrottleState = latestThrottleState === "missing"
1177
- ? emptyThrottleState()
1178
- : latestThrottleState;
1179
- writeThrottleState(throttleStatePath(hqRoot), recordCpuBusyObservation(currentThrottleState, cpuObservation), admissionThrottleDeps);
1180
- }
1181
- }
1182
- if (!deps.dryRun && summary.errors.length === 0) {
1183
- try {
1184
- writeHeartbeatFile(hqRoot, summary, nowIso);
1185
- summary.heartbeat_at = nowIso;
1186
- }
1187
- catch (err) {
1188
- markError(summary, err);
1189
- }
1190
- }
1191
- return summary;
1192
- }
1193
- function markOverdueLanes(hqRoot, lanes, summary, deps, now) {
1194
- const nowMs = now.getTime();
1195
- const nowIso = now.toISOString();
1196
- for (const lane of lanes) {
1197
- if (lane.overdue === true)
1198
- continue;
1199
- if (!laneRunsHere(lane, deps.host))
1200
- continue;
1201
- let deadline;
1202
- try {
1203
- deadline = readTimeoutDeadline(hqRoot, lane.lane_id);
1204
- }
1205
- catch (err) {
1206
- markDirty(summary, {
1207
- kind: "lane",
1208
- id: lane.lane_id,
1209
- why: `timeout unreadable: ${err instanceof Error ? err.message : String(err)}`,
1210
- });
1211
- continue;
1212
- }
1213
- if (!deadline || !isDeadlinePassed(deadline.deadline_at, nowMs))
1214
- continue;
1215
- try {
1216
- const result = casLaneIf(hqRoot, lane.lane_id, (current) => current.overdue !== true, (current) => ({
1217
- ...current,
1218
- overdue: true,
1219
- overdue_at: nowIso,
1220
- }), deps.dryRun);
1221
- if (result.changed) {
1222
- summary.overdue.push({
1223
- id: lane.lane_id,
1224
- from: "ok",
1225
- to: "overdue",
1226
- });
1227
- }
1228
- }
1229
- catch (err) {
1230
- markDirty(summary, {
1231
- kind: "lane",
1232
- id: lane.lane_id,
1233
- why: `overdue CAS failed: ${err instanceof Error ? err.message : String(err)}`,
1234
- });
1235
- }
1236
- }
1237
- }
1238
- async function heartbeatAndDeadHosts(hqRoot, lanes, summary, deps, now) {
1239
- const nowMs = now.getTime();
1240
- const nowIso = now.toISOString();
1241
- const isAlive = deps.isAlive ?? isPidAlive;
1242
- for (const lane of lanes) {
1243
- if (lane.state !== "running")
1244
- continue;
1245
- const local = laneRunsHere(lane, deps.host);
1246
- if (local) {
1247
- const runDir = laneRunDir(hqRoot, lane.lane_id);
1248
- let workerPid;
1249
- let watcherPid;
1250
- let workerAlive;
1251
- let watcherAlive;
1252
- try {
1253
- const workerRec = readPidRecord(path.join(runDir, "worker.pid"));
1254
- const watcherRec = readPidRecord(path.join(runDir, "watcher.pid"));
1255
- workerPid = workerRec?.pid ?? null;
1256
- watcherPid = watcherRec?.pid ?? null;
1257
- workerAlive = workerRec !== null && isPidRecordAlive(workerRec, isAlive);
1258
- watcherAlive = watcherRec !== null && isPidRecordAlive(watcherRec, isAlive);
1259
- }
1260
- catch (err) {
1261
- markDirty(summary, {
1262
- kind: "lane",
1263
- id: lane.lane_id,
1264
- why: `pid file unreadable: ${err instanceof Error ? err.message : String(err)}`,
1265
- });
1266
- continue;
1267
- }
1268
- if (workerAlive) {
1269
- try {
1270
- const result = casLaneIf(hqRoot, lane.lane_id, (current) => current.last_host_report_at !== nowIso, (current) => ({ ...current, last_host_report_at: nowIso }), deps.dryRun);
1271
- if (result.changed)
1272
- summary.heartbeats.push(lane.lane_id);
1273
- }
1274
- catch (err) {
1275
- markDirty(summary, {
1276
- kind: "lane",
1277
- id: lane.lane_id,
1278
- why: `heartbeat CAS failed: ${err instanceof Error ? err.message : String(err)}`,
1279
- });
1280
- }
1281
- if (!watcherAlive && !deps.dryRun) {
1282
- try {
1283
- armLaneWatcher(hqRoot, lane.lane_id, spawnDepsForHost(deps));
1284
- summary.heartbeats.push(`${lane.lane_id}:watcher-rearmed`);
1285
- }
1286
- catch (err) {
1287
- markDirty(summary, {
1288
- kind: "lane",
1289
- id: lane.lane_id,
1290
- why: `watcher re-arm failed: ${err instanceof Error ? err.message : String(err)}`,
1291
- });
1292
- }
1293
- }
1294
- continue;
1295
- }
1296
- if (watcherAlive) {
1297
- // Watcher owns terminal classification of a process that has just exited.
1298
- continue;
1299
- }
1300
- const hadPids = workerPid !== null || watcherPid !== null;
1301
- if (!hadPids && nowMs - lastHostReportMs(lane) < DEAD_HOST_MS) {
1302
- try {
1303
- const result = casLaneIf(hqRoot, lane.lane_id, (current) => current.last_host_report_at !== nowIso, (current) => ({ ...current, last_host_report_at: nowIso }), deps.dryRun);
1304
- if (result.changed)
1305
- summary.heartbeats.push(lane.lane_id);
1306
- }
1307
- catch (err) {
1308
- markDirty(summary, {
1309
- kind: "lane",
1310
- id: lane.lane_id,
1311
- why: `heartbeat CAS failed: ${err instanceof Error ? err.message : String(err)}`,
1312
- });
1313
- }
1314
- continue;
1315
- }
1316
- try {
1317
- const { classification, latest, quota, providerRejection, lane: classified } = classifyDeadLocalLane(hqRoot, lane.lane_id, now);
1318
- // Match the watcher's one-repair decision before classifying an
1319
- // unparseable envelope after both processes have died. As in the
1320
- // watcher, a provider rejection or a quota signal skips the repair.
1321
- const runDir = laneRunDir(hqRoot, lane.lane_id);
1322
- const manifest = readSpawnManifest(runDir);
1323
- const minEnvelopeName = manifest.envelope_path ? path.basename(manifest.envelope_path) : undefined;
1324
- const rawLatest = readLatestEnvelopeFile(hqRoot, lane.lane_id, minEnvelopeName);
1325
- const missingExpected = rawLatest === null
1326
- ? missingExpectedEnvelopeFile(hqRoot, lane.lane_id, manifest.envelope_path)
1327
- : null;
1328
- const repairCandidate = rawLatest ?? missingExpected;
1329
- let invalidEnvelope = null;
1330
- if (!deps.dryRun &&
1331
- !providerRejection &&
1332
- !quota &&
1333
- repairCandidate &&
1334
- repairCandidate.envelope === null &&
1335
- (!repairCandidate.missing || envelopeRepairMode(classified.provider) === "resume")) {
1336
- const spawnRepair = deps.spawnRepair ??
1337
- ((root, repairLane, input) => spawnEnvelopeRepair(root, repairLane, input, spawnDepsForHost(deps)));
1338
- const repair = decideEnvelopeRepair(hqRoot, lane.lane_id, repairCandidate, nowMs, spawnRepair, () => undefined);
1339
- if (repair.action === "spawned") {
1340
- armLaneWatcher(hqRoot, lane.lane_id, spawnDepsForHost(deps));
1341
- summary.heartbeats.push(`${lane.lane_id}:envelope-repair`);
1342
- continue;
1343
- }
1344
- if (!repairCandidate.missing)
1345
- invalidEnvelope = { errors: repair.errors };
1346
- }
1347
- let repairedClassification = invalidEnvelope
1348
- ? classifyLaneExit({ envelope: null, hasPartialOutput: false, invalidEnvelope })
1349
- : classification;
1350
- let questionRefusalCode = null;
1351
- const accountRetryReason = quota
1352
- ? "usage_limit"
1353
- : providerRejection && classification.reason === "account_not_logged_in"
1354
- ? "account_unavailable"
1355
- : null;
1356
- if (!deps.dryRun &&
1357
- quota &&
1358
- classified.account &&
1359
- (classification.reason === "account_quota_exhausted" ||
1360
- classification.reason === "account_model_unavailable")) {
1361
- recordAccountQuota(hqRoot, classified.provider, classified.account, quota);
1362
- }
1363
- if (!deps.dryRun &&
1364
- accountRetryReason &&
1365
- classified.account) {
1366
- try {
1367
- const recovered = await retryLaneOnAlternateAccount(hqRoot, classified, accountRetryReason, "reconcile", {
1368
- ...spawnDepsForHost(deps),
1369
- });
1370
- summary.dead_host.push({
1371
- id: lane.lane_id,
1372
- from: "running",
1373
- to: recovered.state,
1374
- reason: accountRetryReason,
1375
- });
1376
- summary.heartbeats.push(`${lane.lane_id}:account-retry`);
1377
- continue;
1378
- }
1379
- catch (err) {
1380
- const message = err instanceof Error ? err.message : String(err);
1381
- process.stderr.write(`hq lanes reconcile: account failover failed for ${lane.lane_id}: ${message}\n`);
1382
- if (readLane(hqRoot, lane.lane_id).state !== "running") {
1383
- summary.errors.push({ message: `${lane.lane_id}: account failover left a non-running record after ${message}` });
1384
- continue;
1385
- }
1386
- }
1387
- }
1388
- if (!deps.dryRun &&
1389
- classification.state === "awaiting_input" &&
1390
- latest &&
1391
- (latest.envelope.questions?.length ?? 0) > 0) {
1392
- const questionLane = readLane(hqRoot, lane.lane_id);
1393
- try {
1394
- const activeCompany = await resolveQuestionCompanyWithFallback(hqRoot, questionLane, deps.resolveQuestionActiveCompany);
1395
- raiseEnvelopeQuestions(hqRoot, questionLane, latest.envelope, latest.ref, nowIso, undefined, activeCompany);
1396
- }
1397
- catch (err) {
1398
- questionRefusalCode = questionRecordRefusalCode(err);
1399
- if (questionRefusalCode === null)
1400
- throw err;
1401
- repairedClassification = {
1402
- state: "failed",
1403
- reason: `question_record_refused:${questionRefusalCode}`,
1404
- };
1405
- process.stderr.write(`hq lanes reconcile: terminal ask refused for ${lane.lane_id}: ${questionRefusalCode}\n`);
1406
- }
1407
- }
1408
- let terminalEnvelope = latest?.envelope;
1409
- let terminalClassification = repairedClassification;
1410
- const result = casLaneIf(hqRoot, lane.lane_id, (current) => current.state === "running", (current) => {
1411
- const approvalGate = latest?.envelope
1412
- ? enforceRegistryApprovalGate(current, latest.envelope)
1413
- : undefined;
1414
- terminalEnvelope = approvalGate?.envelope ?? latest?.envelope;
1415
- terminalClassification = approvalGate?.violation
1416
- ? { state: "blocked", reason: "approval_gate_violated" }
1417
- : repairedClassification;
1418
- const next = {
1419
- ...current,
1420
- state: terminalClassification.state,
1421
- queued_reason: undefined,
1422
- queued_details: undefined,
1423
- terminal_reason: terminalClassification.reason ?? "host_lost",
1424
- interrupted_at: terminalClassification.state === "interrupted" ? nowIso : undefined,
1425
- ...(providerRejection
1426
- ? { terminal_details: providerRejectionDetails(providerRejection, current) }
1427
- : {}),
1428
- ...(quota?.quota_reset_at ? { quota_reset_at: quota.quota_reset_at } : {}),
1429
- ...(quota?.marker ? { model_quota_marker: quota.marker } : {}),
1430
- last_host_report_at: nowIso,
1431
- };
1432
- if (approvalGate && !approvalGate.violation && approvalGate.approvedIdsToConsume.length > 0) {
1433
- next.registry_worker_approval_history = approvalGate.approvedIdsToConsume.reduce((history, approvalId) => appendCappedHistory(history, {
1434
- event: "consumed",
1435
- approval_id: approvalId,
1436
- envelope_ref: latest.ref,
1437
- at: nowIso,
1438
- }), current.registry_worker_approval_history ?? []);
1439
- }
1440
- if (terminalEnvelope && latest) {
1441
- const envelope = terminalEnvelope;
1442
- next.last_envelope = {
1443
- ref: latest.ref,
1444
- decision: envelope.decision,
1445
- at: nowIso,
1446
- artifacts: envelope.artifacts,
1447
- ...(envelope.blocked_reason !== undefined ? { blocked_reason: envelope.blocked_reason } : {}),
1448
- };
1449
- const storySwitch = applyStorySwitchEnvelope(current, envelope, nowIso, latest.ref);
1450
- if (storySwitch.clearPending) {
1451
- next.story_switch_pending = undefined;
1452
- }
1453
- if (storySwitch.storySwitchUnacknowledged !== undefined) {
1454
- next.story_switch_unacknowledged = storySwitch.storySwitchUnacknowledged;
1455
- next.attribution_history = storySwitch.attributionHistory ?? current.attribution_history;
1456
- }
1457
- const storyStatus = storySwitch.suppressCompletion
1458
- ? "in_progress"
1459
- : terminalEnvelopeStoryStatus(current, envelope.decision, envelope.story_status);
1460
- if (storyStatus !== undefined) {
1461
- next.story_status = storySwitch.suppressCompletion ? "in_progress" : envelope.story_status;
1462
- next.story_complete = storySwitch.suppressCompletion ? false : envelope.story_complete;
1463
- next.story_complete_reason = storySwitch.suppressCompletion ? undefined : envelope.story_complete_reason;
1464
- next.stories = laneStories(current).map((story) => story.project_id === current.project_id && story.story_id === current.story_id
1465
- ? {
1466
- ...story,
1467
- status: storyStatus,
1468
- updated_at: nowIso,
1469
- ...(envelope.decision === "blocked"
1470
- ? { blocked_reason: envelope.blocked_reason ?? "" }
1471
- : { blocked_reason: undefined }),
1472
- }
1473
- : story);
1474
- }
1475
- if (approvalGate?.violation) {
1476
- next.last_envelope = {
1477
- ref: latest.ref,
1478
- decision: envelope.decision,
1479
- at: nowIso,
1480
- artifacts: envelope.artifacts,
1481
- blocked_reason: envelope.blocked_reason,
1482
- };
1483
- }
1484
- }
1485
- return next;
1486
- }, deps.dryRun);
1487
- if (!deps.dryRun &&
1488
- result.changed &&
1489
- result.record.state === "done" &&
1490
- terminalClassification.state === "done" &&
1491
- terminalEnvelope?.decision === "done" &&
1492
- !quota &&
1493
- !providerRejection &&
1494
- result.record.account &&
1495
- result.record.model) {
1496
- clearAccountQuotaAfterSuccessfulTurnBestEffort(hqRoot, result.record.provider, result.record.account, result.record.model, new Date(nowIso));
1497
- }
1498
- if (result.changed) {
1499
- summary.dead_host.push({
1500
- id: lane.lane_id,
1501
- from: "running",
1502
- to: terminalClassification.state,
1503
- reason: terminalClassification.reason ?? "host_lost",
1504
- });
1505
- if (terminalEnvelope && latest && questionRefusalCode === null) {
1506
- const mesh = deps.mesh === undefined ? createLaneMesh({ interactive: false }) : deps.mesh;
1507
- if (mesh) {
1508
- const meshEmitTimeoutMs = deps.meshEmitTimeoutMs ?? 10_000;
1509
- let timer;
1510
- try {
1511
- const questionIds = terminalEnvelope.decision === "ask"
1512
- ? envelopeQuestionIds(lane.lane_id, latest.ref, terminalEnvelope)
1513
- : undefined;
1514
- await Promise.race([
1515
- emitLaneDecisionEvents(mesh, result.record, terminalEnvelope, { questionIds }),
1516
- new Promise((_resolve, reject) => {
1517
- timer = setTimeout(() => reject(new Error(`terminal mesh emit timed out after ${meshEmitTimeoutMs}ms`)), meshEmitTimeoutMs);
1518
- }),
1519
- ]);
1520
- }
1521
- catch (err) {
1522
- markDirty(summary, {
1523
- kind: "lane",
1524
- id: lane.lane_id,
1525
- why: `terminal mesh emit failed: ${err instanceof Error ? err.message : String(err)}`,
1526
- });
1527
- }
1528
- finally {
1529
- if (timer)
1530
- clearTimeout(timer);
1531
- }
1532
- }
1533
- }
1534
- else if (!deps.dryRun &&
1535
- (result.record.state === "failed" || result.record.state === "interrupted" ||
1536
- result.record.state === "cancelled" || result.record.state === "killed")) {
1537
- const mesh = deps.mesh === undefined ? createLaneMesh({ interactive: false }) : deps.mesh;
1538
- if (mesh) {
1539
- const meshEmitTimeoutMs = deps.meshEmitTimeoutMs ?? 10_000;
1540
- let timer;
1541
- try {
1542
- await Promise.race([
1543
- emitLaneTerminalStateEvent(mesh, result.record, result.record.state, result.record.terminal_reason),
1544
- new Promise((_resolve, reject) => {
1545
- timer = setTimeout(() => reject(new Error(`terminal mesh emit timed out after ${meshEmitTimeoutMs}ms`)), meshEmitTimeoutMs);
1546
- }),
1547
- ]);
1548
- }
1549
- catch (err) {
1550
- markDirty(summary, {
1551
- kind: "lane",
1552
- id: lane.lane_id,
1553
- why: `terminal mesh emit failed: ${err instanceof Error ? err.message : String(err)}`,
1554
- });
1555
- }
1556
- finally {
1557
- if (timer)
1558
- clearTimeout(timer);
1559
- }
1560
- }
1561
- }
1562
- if (!deps.dryRun && deps.recoverQueuedMessages) {
1563
- try {
1564
- await deps.recoverQueuedMessages(result.record);
1565
- }
1566
- catch (err) {
1567
- markDirty(summary, {
1568
- kind: "lane",
1569
- id: lane.lane_id,
1570
- why: `queued message recovery failed: ${err instanceof Error ? err.message : String(err)}`,
1571
- });
1572
- }
1573
- }
1574
- }
1575
- }
1576
- catch (err) {
1577
- markDirty(summary, {
1578
- kind: "lane",
1579
- id: lane.lane_id,
1580
- why: `dead-host classify failed: ${err instanceof Error ? err.message : String(err)}`,
1581
- });
1582
- }
1583
- continue;
1584
- }
1585
- if (nowMs - lastHostReportMs(lane) < DEAD_HOST_MS)
1586
- continue;
1587
- try {
1588
- const result = casLaneIf(hqRoot, lane.lane_id, (current) => current.state === "running" &&
1589
- nowMs - lastHostReportMs(current) >= DEAD_HOST_MS, (current) => ({
1590
- ...current,
1591
- state: "interrupted",
1592
- queued_reason: undefined,
1593
- queued_details: undefined,
1594
- terminal_reason: "host_lost",
1595
- interrupted_at: nowIso,
1596
- }), deps.dryRun);
1597
- if (result.changed) {
1598
- summary.dead_host.push({
1599
- id: lane.lane_id,
1600
- from: "running",
1601
- to: "interrupted",
1602
- reason: "host_lost",
1603
- });
1604
- const mesh = !deps.dryRun
1605
- ? deps.mesh === undefined ? createLaneMesh({ interactive: false }) : deps.mesh
1606
- : null;
1607
- if (mesh) {
1608
- const meshEmitTimeoutMs = deps.meshEmitTimeoutMs ?? 10_000;
1609
- let timer;
1610
- try {
1611
- await Promise.race([
1612
- emitLaneTerminalStateEvent(mesh, result.record, "interrupted", "host_lost"),
1613
- new Promise((_resolve, reject) => {
1614
- timer = setTimeout(() => reject(new Error(`terminal mesh emit timed out after ${meshEmitTimeoutMs}ms`)), meshEmitTimeoutMs);
1615
- }),
1616
- ]);
1617
- }
1618
- catch (err) {
1619
- markDirty(summary, {
1620
- kind: "lane",
1621
- id: lane.lane_id,
1622
- why: `terminal mesh emit failed: ${err instanceof Error ? err.message : String(err)}`,
1623
- });
1624
- }
1625
- finally {
1626
- if (timer)
1627
- clearTimeout(timer);
1628
- }
1629
- }
1630
- }
1631
- }
1632
- catch (err) {
1633
- markDirty(summary, {
1634
- kind: "lane",
1635
- id: lane.lane_id,
1636
- why: `dead-host CAS failed: ${err instanceof Error ? err.message : String(err)}`,
1637
- });
1638
- }
1639
- }
1640
- }
1641
- async function admitQueued(hqRoot, lanes, summary, deps, now, onlyLaneIds) {
1642
- if (onlyLaneIds === undefined)
1643
- expireAbandonedPickupValidation(hqRoot, lanes, summary, deps, now);
1644
- // A lane whose pickup probe is still out is not admitted: its record is
1645
- // about to be bound or failed by the pickup, and a worker spawned now
1646
- // would keep running behind that write.
1647
- const localQueued = lanes
1648
- .filter((lane) => lane.state === "queued" &&
1649
- (onlyLaneIds === undefined || onlyLaneIds.has(lane.lane_id)) &&
1650
- lane.pickup_validation_pending !== true &&
1651
- laneRunsHere(lane, deps.host))
1652
- .filter((lane) => !hasFreshAdmissionReservation(lane, now.getTime()))
1653
- .sort(compareLaneQueueOrder);
1654
- if (localQueued.length === 0 || deps.skipAdmissionWithoutCpuSample === true)
1655
- return;
1656
- const config = loadLanesConfig(hqRoot);
1657
- const throttleDeps = deps.throttleDeps ?? defaultThrottleDeps(deps.env ?? process.env);
1658
- const filePath = throttleStatePath(hqRoot);
1659
- const loaded = readThrottleState(filePath, throttleDeps);
1660
- const throttleState = loaded === "unreadable"
1661
- ? "unreadable"
1662
- : loaded === "missing"
1663
- ? emptyThrottleState()
1664
- : loaded;
1665
- const busy = deps.cpuBusyFraction !== undefined
1666
- ? deps.cpuBusyFraction
1667
- : await resolveCpuBusyFraction(throttleDeps);
1668
- const availableMemMb = readAvailableMemMb(throttleDeps);
1669
- const nowMs = now.getTime();
1670
- const decision = evaluateThrottle({
1671
- busy,
1672
- availableMemMb,
1673
- cpuCheckEnabled: isCpuCheckEnabled(throttleDeps.env),
1674
- state: throttleState,
1675
- throttle: config.throttle,
1676
- nowMs,
1677
- });
1678
- const spawnFn = deps.spawn ??
1679
- ((root, lane) => spawnLaneWorker(root, lane, spawnDepsForHost(deps)));
1680
- const running = lanes.filter((lane) => (lane.state === "running" ||
1681
- hasFreshAdmissionReservation(lane, now.getTime()) ||
1682
- hasActivePendingResume(lane, now.getTime())) &&
1683
- laneRunsHere(lane, deps.host));
1684
- const spawned = [];
1685
- if (!decision.admit) {
1686
- for (const lane of localQueued) {
1687
- let profileMaxConcurrent;
1688
- if (lane.worker_profile !== undefined) {
1689
- try {
1690
- profileMaxConcurrent = loadWorkerProfile(hqRoot, lane.worker_profile).meta.max_concurrent;
1691
- }
1692
- catch (err) {
1693
- markDirty(summary, {
1694
- kind: "lane",
1695
- id: lane.lane_id,
1696
- why: `profile admission failed: ${err instanceof Error ? err.message : String(err)}`,
1697
- });
1698
- continue;
1699
- }
1700
- }
1701
- const capReason = laneAdmissionCapReason(running, lane, config, deps.host, profileMaxConcurrent, now.getTime());
1702
- writeQueueReason(hqRoot, lane, capReason ?? "throttle", deps.dryRun);
1703
- }
1704
- if (!deps.dryRun && throttleState !== "unreadable") {
1705
- writeThrottleState(filePath, applyThrottleDecision(throttleState, decision, nowMs), throttleDeps);
1706
- }
1707
- return;
1708
- }
1709
- for (const lane of localQueued) {
1710
- let profileMaxConcurrent;
1711
- if (lane.worker_profile !== undefined) {
1712
- try {
1713
- profileMaxConcurrent = loadWorkerProfile(hqRoot, lane.worker_profile).meta.max_concurrent;
1714
- }
1715
- catch (err) {
1716
- markDirty(summary, {
1717
- kind: "lane",
1718
- id: lane.lane_id,
1719
- why: `profile admission failed: ${err instanceof Error ? err.message : String(err)}`,
1720
- });
1721
- continue;
1722
- }
1723
- }
1724
- const projected = [...running, ...spawned];
1725
- const alternateAccount = availableBalancedAccount(hqRoot, lane, projected, config, deps.host, profileMaxConcurrent, now.getTime());
1726
- const admittedLane = alternateAccount !== undefined
1727
- ? writeAdmissionAccount(hqRoot, lane, alternateAccount, deps.dryRun)
1728
- : lane;
1729
- const baseCap = laneAdmissionCapReason(running, admittedLane, config, deps.host, profileMaxConcurrent, now.getTime());
1730
- const currentCap = laneAdmissionCapReason([...running, ...spawned], admittedLane, config, deps.host, profileMaxConcurrent, now.getTime());
1731
- let blockedBy;
1732
- if (currentCap !== undefined) {
1733
- blockedBy = baseCap ?? "queue_position";
1734
- }
1735
- if (!blockedBy && !decision.admit)
1736
- blockedBy = "throttle";
1737
- if (blockedBy) {
1738
- writeQueueReason(hqRoot, admittedLane, blockedBy, deps.dryRun);
1739
- continue;
1740
- }
1741
- try {
1742
- if (deps.dryRun) {
1743
- summary.admitted.push({
1744
- id: lane.lane_id,
1745
- from: "queued",
1746
- to: "running",
1747
- reason: decision.reason,
1748
- });
1749
- spawned.push({ ...admittedLane, state: "running" });
1750
- continue;
1751
- }
1752
- const started = spawnFn(hqRoot, admittedLane);
1753
- if (decision.reason === "starvation_bound") {
1754
- casLaneIf(hqRoot, lane.lane_id, (current) => current.admitted_reason !== "starvation_bound", (current) => ({ ...current, admitted_reason: "starvation_bound" }), false);
1755
- }
1756
- summary.admitted.push({
1757
- id: started.lane_id,
1758
- from: "queued",
1759
- to: started.state,
1760
- reason: decision.reason,
1761
- ...(started.queued_reason === "provider_binary_missing" &&
1762
- typeof started.terminal_details?.searched_path === "string"
1763
- ? {
1764
- searched_path: started.terminal_details.searched_path,
1765
- }
1766
- : {}),
1767
- });
1768
- spawned.push(started);
1769
- }
1770
- catch (err) {
1771
- if (err instanceof LanesError && err.error === "worker_profile_hash_mismatch") {
1772
- const expectedHash = typeof err.details.expected_hash === "string"
1773
- ? err.details.expected_hash
1774
- : null;
1775
- const actualHash = typeof err.details.actual_hash === "string"
1776
- ? err.details.actual_hash
1777
- : undefined;
1778
- const workerProfile = typeof err.details.worker === "string"
1779
- ? err.details.worker
1780
- : lane.worker_profile;
1781
- if (actualHash !== undefined && workerProfile !== undefined) {
1782
- casLaneIf(hqRoot, lane.lane_id, (current) => current.state === "queued", (current) => ({
1783
- ...current,
1784
- queued_reason: "worker_profile_changed",
1785
- queued_details: {
1786
- reason: "worker_profile_changed",
1787
- expected_hash: expectedHash,
1788
- actual_hash: actualHash,
1789
- recovery_command: `hq lanes resume ${lane.lane_id} --worker ${workerProfile}`,
1790
- },
1791
- admission_reserved_at: undefined,
1792
- }), false);
1793
- summary.skipped.push({
1794
- kind: "lane",
1795
- id: lane.lane_id,
1796
- why: "worker_profile_changed",
1797
- });
1798
- continue;
1799
- }
1800
- }
1801
- terminalizeModelUnsupported(hqRoot, lane, err);
1802
- markDirty(summary, {
1803
- kind: "lane",
1804
- id: lane.lane_id,
1805
- why: `admit failed: ${err instanceof Error ? err.message : String(err)}`,
1806
- });
1807
- }
1808
- }
1809
- if (!deps.dryRun) {
1810
- const admittedAny = spawned.length > 0;
1811
- const applied = admittedAny
1812
- ? { admit: true, reason: decision.reason }
1813
- : decision;
1814
- const base = throttleState === "unreadable" ? emptyThrottleState() : throttleState;
1815
- writeThrottleState(filePath, applyThrottleDecision(base, applied, nowMs), throttleDeps);
1816
- }
1817
- }
1818
- function keepAliveCompanyKey(company) {
1819
- return JSON.stringify([company?.uid ?? null, company?.slug ?? null]);
1820
- }
1821
- function hasKeepAliveProgress(lane) {
1822
- return (lane.keep_alive_resume_count ?? 0) !== 0 ||
1823
- lane.keep_alive_last_resume_at !== undefined ||
1824
- lane.keep_alive_next_eligible_at !== undefined ||
1825
- lane.keep_alive_exhausted_at !== undefined;
1826
- }
1827
- function clearedKeepAliveProgress(lane) {
1828
- return {
1829
- ...lane,
1830
- keep_alive_resume_count: 0,
1831
- keep_alive_last_resume_at: undefined,
1832
- keep_alive_next_eligible_at: undefined,
1833
- keep_alive_exhausted_at: undefined,
1834
- };
1835
- }
1836
- function keepAliveProgressResetByHuman(lane) {
1837
- const count = lane.keep_alive_resume_count ?? 0;
1838
- const lastAutoMs = Date.parse(lane.keep_alive_last_resume_at ?? "");
1839
- if (count > 0 && !Number.isFinite(lastAutoMs))
1840
- return true;
1841
- const messageMs = Date.parse(lane.last_message_at ?? "");
1842
- const resumeMs = lane.last_resume?.text === LEAD_LANE_KEEP_ALIVE_RESUME_TEXT ||
1843
- lane.last_resume?.text === LEAD_LANE_KEEP_ALIVE_INTERRUPTED_RESUME_TEXT ||
1844
- lane.last_resume?.text === LEAD_LANE_KEEP_ALIVE_BLOCKED_RESUME_TEXT
1845
- ? Number.NaN
1846
- : Date.parse(lane.last_resume?.at ?? "");
1847
- return Number.isFinite(lastAutoMs) &&
1848
- ((Number.isFinite(messageMs) && messageMs > lastAutoMs) ||
1849
- (Number.isFinite(resumeMs) && resumeMs > lastAutoMs));
1850
- }
1851
- function recordKeepAliveEvent(hqRoot, laneId, summary, event) {
1852
- try {
1853
- writeWatcherDecision(watcherEventsPath(hqRoot, laneId), {
1854
- lane_id: laneId,
1855
- ...event,
1856
- });
1857
- }
1858
- catch (err) {
1859
- markDirty(summary, {
1860
- kind: "lane",
1861
- id: laneId,
1862
- why: `keep-alive event write failed: ${err instanceof Error ? err.message : String(err)}`,
1863
- });
1864
- }
1865
- }
1866
- function keepAliveAccountSelectionEvidence(selection) {
1867
- if (selection === undefined)
1868
- return undefined;
1869
- return {
1870
- account: selection.account,
1871
- selected_at: selection.selected_at,
1872
- reason: selection.reason,
1873
- considered: selection.considered.map((row) => ({
1874
- account: row.account,
1875
- live_lanes: row.live_lanes,
1876
- eligible: row.eligible,
1877
- reason: row.ineligible_reason ?? null,
1878
- })),
1879
- };
1880
- }
1881
- function keepAliveSkipGuidance(laneId, reason) {
1882
- switch (reason) {
1883
- case "missing_opt_in_time":
1884
- return `if appropriate, run hq lanes keep-alive ${laneId} --on`;
1885
- case "not_opted_in":
1886
- return "explicit opt-in is required before keep-alive can resume this lane";
1887
- case "envelope_before_opt_in":
1888
- return `the latest envelope predates opt-in; review the lane with hq lanes show ${laneId}`;
1889
- case "interruption_before_opt_in":
1890
- return `the interruption predates opt-in; review the lane with hq lanes show ${laneId}`;
1891
- case "missing_interruption_time":
1892
- return "the interruption time is unavailable; inspect the watcher classification before resuming";
1893
- case "retired":
1894
- return "the lane is retired; resolve its retired status before resuming";
1895
- case "superseded":
1896
- return "a newer lead exists for this project; continue on that lane";
1897
- case "retired_list_invalid":
1898
- return "the retired lane list is invalid; repair workspace/lane-briefs/juniors.json before retrying";
1899
- case "terminal_state":
1900
- return `the lane is in a terminal state; review it with hq lanes show ${laneId} before starting replacement work`;
1901
- }
1902
- }
1903
- function recordKeepAliveSkip(hqRoot, lane, reason, summary, deps, detail) {
1904
- summary.skipped.push({
1905
- kind: "lane",
1906
- id: lane.lane_id,
1907
- why: `keep_alive_${reason}${detail ? `: ${detail}` : ""}`,
1908
- });
1909
- if (lane.keep_alive === true) {
1910
- summary.keep_alive_blocked.push({ id: lane.lane_id, reason });
1911
- process.stderr.write(`hq lanes reconcile: keep-alive blocked for lane ${lane.lane_id} (${reason}); ${keepAliveSkipGuidance(lane.lane_id, reason)}\n`);
1912
- }
1913
- if (deps.dryRun)
1914
- return;
1915
- let laneVersion = lane.version;
1916
- if (lane.keep_alive_blocked_reason !== reason) {
1917
- try {
1918
- const update = casLaneIf(hqRoot, lane.lane_id, (current) => current.version === lane.version && current.state === lane.state, (current) => ({ ...current, keep_alive_blocked_reason: reason }), false);
1919
- laneVersion = update.record.version;
1920
- }
1921
- catch (err) {
1922
- markDirty(summary, {
1923
- kind: "lane",
1924
- id: lane.lane_id,
1925
- why: `keep-alive skip state write failed: ${err instanceof Error ? err.message : String(err)}`,
1926
- });
1927
- }
1928
- }
1929
- if (lane.keep_alive === true) {
1930
- recordKeepAliveSkipEvent(hqRoot, lane, reason, laneVersion, summary, deps, detail);
1931
- }
1932
- }
1933
- function recordDoneKeepAlivePolicySkip(hqRoot, summary, lane, verdict, deps) {
1934
- summary.skipped.push({
1935
- kind: "lane",
1936
- id: lane.lane_id,
1937
- why: `keep_alive_${verdict}`,
1938
- });
1939
- if (lane.keep_alive === true && !deps.dryRun) {
1940
- recordKeepAliveSkipEvent(hqRoot, lane, verdict, lane.version, summary, deps);
1941
- }
1942
- }
1943
- function recordKeepAliveSkipEvent(hqRoot, lane, reason, laneVersion, summary, deps, detail) {
1944
- if (lane.keep_alive !== true || deps.dryRun)
1945
- return;
1946
- const eventsPath = watcherEventsPath(hqRoot, lane.lane_id);
1947
- try {
1948
- let lastSkip;
1949
- try {
1950
- const scanBackwards = (body, targetReason, targetVersion) => {
1951
- const lines = body.split(/\r?\n/);
1952
- for (let index = lines.length - 1; index >= 0; index -= 1) {
1953
- if (!lines[index])
1954
- continue;
1955
- try {
1956
- const value = JSON.parse(lines[index]);
1957
- if (typeof value === "object" && value !== null && !Array.isArray(value)) {
1958
- const event = value;
1959
- if (event.event === "keep_alive_skip" &&
1960
- (targetReason === undefined || (event.reason === targetReason && event.lane_version === targetVersion))) {
1961
- return event;
1962
- }
1963
- }
1964
- }
1965
- catch {
1966
- // A partial concurrent append must not prevent future skip events.
1967
- }
1968
- }
1969
- return undefined;
1970
- };
1971
- const maxTailBytes = 64 * 1024;
1972
- const fd = fs.openSync(eventsPath, "r");
1973
- let fileSize;
1974
- let tail;
1975
- let tailOffset;
1976
- try {
1977
- fileSize = fs.fstatSync(fd).size;
1978
- tailOffset = Math.max(0, fileSize - maxTailBytes);
1979
- const buffer = Buffer.alloc(fileSize - tailOffset);
1980
- let bytesRead = 0;
1981
- while (bytesRead < buffer.length) {
1982
- const count = fs.readSync(fd, buffer, bytesRead, buffer.length - bytesRead, tailOffset + bytesRead);
1983
- if (count === 0)
1984
- break;
1985
- bytesRead += count;
1986
- }
1987
- tail = buffer.subarray(0, bytesRead).toString("utf8");
1988
- }
1989
- finally {
1990
- fs.closeSync(fd);
1991
- }
1992
- // When the bounded read starts mid-line, ignore that incomplete prefix.
1993
- if (tailOffset > 0) {
1994
- const firstNewline = tail.indexOf("\n");
1995
- tail = firstNewline < 0 ? "" : tail.slice(firstNewline + 1);
1996
- }
1997
- lastSkip = scanBackwards(tail, reason === "next_check_at" ? reason : undefined, laneVersion);
1998
- // For small logs the tail is not a complete record boundary if a single
1999
- // record exceeded the bound, so allow one full-file scan as a fallback.
2000
- if (!lastSkip && fileSize < maxTailBytes) {
2001
- lastSkip = scanBackwards(fs.readFileSync(eventsPath, "utf8"), reason === "next_check_at" ? reason : undefined, laneVersion);
2002
- }
2003
- }
2004
- catch (err) {
2005
- if (err.code !== "ENOENT")
2006
- throw err;
2007
- }
2008
- if (lastSkip?.reason === reason && lastSkip.lane_version === laneVersion)
2009
- return;
2010
- recordKeepAliveEvent(hqRoot, lane.lane_id, summary, {
2011
- event: "keep_alive_skip",
2012
- at: deps.now().toISOString(),
2013
- reason,
2014
- ...(detail === undefined ? {} : { detail }),
2015
- lane_version: laneVersion,
2016
- lane_state: lane.state,
2017
- });
2018
- }
2019
- catch (err) {
2020
- markDirty(summary, {
2021
- kind: "lane",
2022
- id: lane.lane_id,
2023
- why: `keep-alive skip event dedupe failed: ${err instanceof Error ? err.message : String(err)}`,
2024
- });
2025
- }
2026
- }
2027
- function readLatestInterruptedClassificationAt(hqRoot, lane) {
2028
- const logPath = watcherOutLogPath(hqRoot, lane.lane_id);
2029
- let body;
2030
- try {
2031
- body = fs.readFileSync(logPath, "utf8");
2032
- }
2033
- catch (err) {
2034
- if (err.code === "ENOENT")
2035
- return undefined;
2036
- throw err;
2037
- }
2038
- let latestClassification;
2039
- for (const line of body.split(/\r?\n/).filter(Boolean)) {
2040
- let parsed;
2041
- try {
2042
- parsed = JSON.parse(line);
2043
- }
2044
- catch {
2045
- continue;
2046
- }
2047
- if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed))
2048
- continue;
2049
- const record = parsed;
2050
- if (record.event === "classification")
2051
- latestClassification = record;
2052
- }
2053
- const at = latestClassification?.at;
2054
- if (latestClassification?.state !== "interrupted" ||
2055
- latestClassification.reason !== lane.terminal_reason ||
2056
- typeof at !== "string" ||
2057
- !Number.isFinite(Date.parse(at)) ||
2058
- new Date(Date.parse(at)).toISOString() !== at)
2059
- return undefined;
2060
- return at;
2061
- }
2062
- function latestEnvelopeMtimeMs(hqRoot, lane, ref) {
2063
- const filePath = path.join(laneRunDir(hqRoot, lane.lane_id), ref);
2064
- try {
2065
- return fs.statSync(filePath).mtimeMs;
2066
- }
2067
- catch (err) {
2068
- throw new LanesError("keep_alive_envelope_timestamp_unavailable", `Cannot stat latest envelope for lane ${lane.lane_id}: ${err instanceof Error ? err.message : String(err)}`, { lane_id: lane.lane_id, path: filePath });
2069
- }
2070
- }
2071
- async function resumeBlockedKeepAlive(hqRoot, lane, questions, summary, deps, now, keepAliveFlags, blockedFlags, companyForLane, lanes, retiredLaneIds, lineageAwareByCompany) {
2072
- const company = companyForLane(lane);
2073
- if (company === null) {
2074
- summary.skipped.push({ kind: "lane", id: lane.lane_id, why: "keep_alive_company_unresolved" });
2075
- return;
2076
- }
2077
- const key = keepAliveCompanyKey(company);
2078
- if (keepAliveFlags.get(key) !== true || blockedFlags.get(key) !== true) {
2079
- summary.skipped.push({ kind: "lane", id: lane.lane_id, why: "keep_alive_blocked" });
2080
- return;
2081
- }
2082
- if (lane.terminal_reason === "approval_gate_violated" || lane.last_envelope?.blocked_reason === "approval_gate_violated") {
2083
- summary.skipped.push({ kind: "lane", id: lane.lane_id, why: "keep_alive_approval_gate_violated" });
2084
- return;
2085
- }
2086
- if (lane.last_envelope?.decision !== "blocked") {
2087
- summary.skipped.push({ kind: "lane", id: lane.lane_id, why: "keep_alive_missing_blocked_envelope" });
2088
- return;
2089
- }
2090
- if (retiredLaneIds === null) {
2091
- recordKeepAliveSkip(hqRoot, lane, "retired_list_invalid", summary, deps);
2092
- return;
2093
- }
2094
- const exclusion = keepAliveExclusion(lane, lanes, retiredLaneIds, lineageAwareByCompany.get(key) === true, companyForLane);
2095
- if (exclusion !== undefined) {
2096
- recordKeepAliveSkip(hqRoot, lane, exclusion, summary, deps);
2097
- return;
2098
- }
2099
- const terminalAt = lane.last_envelope.at;
2100
- const terminalMs = Date.parse(terminalAt ?? "");
2101
- if (!Number.isFinite(terminalMs)) {
2102
- recordKeepAliveSkip(hqRoot, lane, "missing_interruption_time", summary, deps, "blocked terminal time is invalid");
2103
- return;
2104
- }
2105
- if (activeLaneStories(lane).length === 0) {
2106
- summary.skipped.push({ kind: "lane", id: lane.lane_id, why: "keep_alive_no_open_stories" });
2107
- return;
2108
- }
2109
- let pendingQuestion = questions.some((question) => question.lane_id === lane.lane_id && question.status === "pending");
2110
- try {
2111
- for (const id of listQuestionIds(hqRoot)) {
2112
- const question = readQuestion(hqRoot, id);
2113
- if (question.lane_id === lane.lane_id && question.status === "pending")
2114
- pendingQuestion = true;
2115
- }
2116
- }
2117
- catch (error) {
2118
- markDirty(summary, { kind: "lane", id: lane.lane_id, why: "blocked keep-alive question read failed: " + (error instanceof Error ? error.message : String(error)) });
2119
- return;
2120
- }
2121
- const blockedHistory = lane.status_gate_history ?? [];
2122
- const priorResumes = lane.keep_alive_blocked_resume_streak ?? 0;
2123
- const alreadyEscalated = blockedHistory.some((entry) => entry.event === "keep_alive_blocked_escalated" && entry.terminal_at === terminalAt);
2124
- if (pendingQuestion) {
2125
- appendBlockedKeepAliveHistory(hqRoot, lane, terminalAt, now.toISOString(), "open_question", deps);
2126
- return;
2127
- }
2128
- if (priorResumes >= 2) {
2129
- if (!alreadyEscalated) {
2130
- casLaneIf(hqRoot, lane.lane_id, (current) => current.version === lane.version && current.state === "blocked" && current.last_envelope?.at === terminalAt, (current) => ({
2131
- ...current,
2132
- status_gate_history: appendCappedHistory(current.status_gate_history ?? [], {
2133
- event: "keep_alive_blocked_escalated",
2134
- at: now.toISOString(),
2135
- terminal_at: terminalAt,
2136
- }),
2137
- }), deps.dryRun);
2138
- }
2139
- summary.skipped.push({ kind: "lane", id: lane.lane_id, why: "keep_alive_blocked_escalated" });
2140
- return;
2141
- }
2142
- if ((lane.keep_alive_resume_count ?? 0) >= LEAD_LANE_KEEP_ALIVE_MAX_RESUMES || lane.keep_alive_exhausted_at !== undefined) {
2143
- if (lane.keep_alive_exhausted_at === undefined && !deps.dryRun) {
2144
- casLaneIf(hqRoot, lane.lane_id, (current) => current.version === lane.version && current.state === "blocked" &&
2145
- (current.keep_alive_resume_count ?? 0) >= LEAD_LANE_KEEP_ALIVE_MAX_RESUMES, (current) => ({ ...current, keep_alive_exhausted_at: now.toISOString() }), false);
2146
- }
2147
- summary.skipped.push({ kind: "lane", id: lane.lane_id, why: "keep_alive_exhausted" });
2148
- return;
2149
- }
2150
- if (now.getTime() - terminalMs < LEAD_LANE_KEEP_ALIVE_BLOCKED_GRACE_MS) {
2151
- appendBlockedKeepAliveHistory(hqRoot, lane, terminalAt, now.toISOString(), "blocked_grace", deps);
2152
- return;
2153
- }
2154
- const latest = deps.readKeepAliveRecheckLane?.(hqRoot, lane.lane_id) ?? readLane(hqRoot, lane.lane_id);
2155
- if (latest.version !== lane.version || latest.state !== "blocked" || latest.last_envelope?.decision !== "blocked" || latest.last_envelope.at !== terminalAt)
2156
- return;
2157
- let hasOpenQuestion;
2158
- try {
2159
- hasOpenQuestion = listQuestionIds(hqRoot).some((id) => {
2160
- const question = readQuestion(hqRoot, id);
2161
- return question.lane_id === lane.lane_id && question.status === "pending";
2162
- });
2163
- }
2164
- catch (error) {
2165
- markDirty(summary, { kind: "lane", id: lane.lane_id, why: "blocked keep-alive question recheck failed: " + (error instanceof Error ? error.message : String(error)) });
2166
- return;
2167
- }
2168
- if (hasOpenQuestion) {
2169
- appendBlockedKeepAliveHistory(hqRoot, latest, terminalAt, now.toISOString(), "open_question", deps);
2170
- return;
2171
- }
2172
- let current = latest;
2173
- let accountSelection;
2174
- if (current.provider === "claude" && deps.prepareKeepAliveClaudeResume !== undefined) {
2175
- try {
2176
- const prepared = await deps.prepareKeepAliveClaudeResume(current);
2177
- current = prepared.lane;
2178
- accountSelection = prepared.accountSelection;
2179
- }
2180
- catch (error) {
2181
- markDirty(summary, { kind: "lane", id: lane.lane_id, why: `blocked keep-alive account preparation failed: ${error instanceof Error ? error.message : String(error)}` });
2182
- return;
2183
- }
2184
- const afterPreparation = deps.readKeepAliveRecheckLane?.(hqRoot, lane.lane_id) ?? readLane(hqRoot, lane.lane_id);
2185
- if (afterPreparation.state !== "blocked" || afterPreparation.last_envelope?.decision !== "blocked" || afterPreparation.last_envelope.at !== terminalAt)
2186
- return;
2187
- current = afterPreparation;
2188
- }
2189
- const expectedVersion = current.version;
2190
- const result = queueLaneTerminalResume(current, now.toISOString(), LEAD_LANE_KEEP_ALIVE_BLOCKED_RESUME_TEXT, { allowBlockedResume: true, trigger: "resume" });
2191
- if (!result.resumeQueued) {
2192
- if (result.resumeSkippedReason !== undefined) {
2193
- summary.skipped.push({ kind: "lane", id: lane.lane_id, why: "keep_alive_" + result.resumeSkippedReason });
2194
- }
2195
- return;
2196
- }
2197
- const queued = casLaneIf(hqRoot, latest.lane_id, (latestRecord) => latestRecord.version === expectedVersion && latestRecord.state === "blocked" && latestRecord.last_envelope?.decision === "blocked" && latestRecord.last_envelope.at === terminalAt, (current) => ({
2198
- ...result.lane,
2199
- version: current.version,
2200
- pending_resume: result.lane.pending_resume === undefined
2201
- ? undefined
2202
- : { ...result.lane.pending_resume, keep_alive: true },
2203
- keep_alive_blocked_resume_streak: priorResumes + 1,
2204
- keep_alive_resume_count: (current.keep_alive_resume_count ?? 0) + 1,
2205
- keep_alive_history: appendCappedHistory(current.keep_alive_history ?? [], {
2206
- at: now.toISOString(),
2207
- resumed_from: "blocked",
2208
- account: current.account ?? null,
2209
- }),
2210
- keep_alive_last_resume_at: now.toISOString(),
2211
- status_gate_history: appendCappedHistory(current.status_gate_history ?? [], {
2212
- event: "keep_alive_blocked_resumed",
2213
- at: now.toISOString(),
2214
- terminal_at: terminalAt,
2215
- resume_queued: true,
2216
- }),
2217
- }), deps.dryRun);
2218
- if (queued.changed) {
2219
- summary.resumed.push({ id: lane.lane_id, from: "blocked", to: "queued", reason: "keep_alive_blocked" });
2220
- if (!deps.dryRun) {
2221
- recordKeepAliveEvent(hqRoot, lane.lane_id, summary, {
2222
- event: "keep_alive_resume",
2223
- at: now.toISOString(),
2224
- attempt: (lane.keep_alive_resume_count ?? 0) + 1,
2225
- resumed_from: "blocked",
2226
- ...(accountSelection !== undefined
2227
- ? { account_selection: keepAliveAccountSelectionEvidence(accountSelection) }
2228
- : {}),
2229
- });
2230
- }
2231
- }
2232
- }
2233
- function appendBlockedKeepAliveHistory(hqRoot, lane, terminalAt, at, reason, deps) {
2234
- if (deps.dryRun || lane.status_gate_history?.some((entry) => entry.event === "keep_alive_blocked_grace" && entry.terminal_at === terminalAt && entry.resume_skipped_reason === reason))
2235
- return;
2236
- casLaneIf(hqRoot, lane.lane_id, (current) => current.version === lane.version && current.state === "blocked" && current.last_envelope?.at === terminalAt, (current) => ({
2237
- ...current,
2238
- status_gate_history: appendCappedHistory(current.status_gate_history ?? [], {
2239
- event: "keep_alive_blocked_grace",
2240
- at,
2241
- terminal_at: terminalAt,
2242
- resume_skipped_reason: reason,
2243
- }),
2244
- }), false);
2245
- }
2246
- async function resumeKeepAlive(hqRoot, lanes, questions, summary, deps, now, flagByCompany, supersessionLineageAwareByCompany, blockedResumeFlagByCompany, immediateFirstResumeFlagByCompany, terminalKeepAliveCandidate) {
2247
- const nowMs = now.getTime();
2248
- const nowIso = now.toISOString();
2249
- const config = loadLanesConfig(hqRoot);
2250
- const resolvedCompanyByLaneVersion = new Map();
2251
- const companyForLane = (candidate) => {
2252
- const key = `${candidate.lane_id}\u0000${candidate.version}`;
2253
- if (resolvedCompanyByLaneVersion.has(key))
2254
- return resolvedCompanyByLaneVersion.get(key) ?? null;
2255
- const company = resolveLaneCompany(hqRoot, candidate).company;
2256
- resolvedCompanyByLaneVersion.set(key, company);
2257
- return company;
2258
- };
2259
- const keepAliveEnabled = [...flagByCompany.values()].some((enabled) => enabled) || [...blockedResumeFlagByCompany.values()].some(Boolean);
2260
- let retiredLaneIds = null;
2261
- if (keepAliveEnabled) {
2262
- try {
2263
- retiredLaneIds = loadRetiredLaneIds(hqRoot);
2264
- }
2265
- catch (err) {
2266
- markError(summary, err);
2267
- }
2268
- }
2269
- for (const originalLane of lanes) {
2270
- if (terminalKeepAliveCandidate && (originalLane.lane_id !== terminalKeepAliveCandidate.laneId ||
2271
- originalLane.version !== terminalKeepAliveCandidate.version ||
2272
- originalLane.state !== "done"))
2273
- continue;
2274
- if (!laneRunsHere(originalLane, deps.host))
2275
- continue;
2276
- if (originalLane.state === "killed" ||
2277
- originalLane.state === "cancelled" ||
2278
- originalLane.state === "failed") {
2279
- if (originalLane.keep_alive === true &&
2280
- flagByCompany.get(keepAliveCompanyKey(companyForLane(originalLane))) === true)
2281
- recordKeepAliveSkip(hqRoot, originalLane, "terminal_state", summary, deps);
2282
- continue;
2283
- }
2284
- if (originalLane.state === "blocked" && originalLane.keep_alive === true) {
2285
- await resumeBlockedKeepAlive(hqRoot, originalLane, questions, summary, deps, now, flagByCompany, blockedResumeFlagByCompany, companyForLane, lanes, retiredLaneIds, supersessionLineageAwareByCompany);
2286
- continue;
2287
- }
2288
- if ((originalLane.state !== "done" && originalLane.state !== "interrupted") ||
2289
- (originalLane.keep_alive !== true && originalLane.worker_profile === undefined))
2290
- continue;
2291
- const initialState = originalLane.state;
2292
- let lane = originalLane;
2293
- if (initialState === "done") {
2294
- const envelope = readLatestEnvelopeRecord(hqRoot, lane.lane_id)?.envelope;
2295
- if (!envelope || envelope.decision !== "done")
2296
- continue;
2297
- const hasPendingWork = hasPendingKeepAliveWork(hqRoot, lane, questions);
2298
- const hasQueuedWork = lane.keep_alive_standing === true && hasPendingWork;
2299
- const verdict = doneKeepAliveVerdict(lane, envelope, hasQueuedWork, {
2300
- hasPendingWork,
2301
- nowMs,
2302
- nextCheckAnchorMs: Date.parse(lane.last_envelope?.at ?? lane.updated_at),
2303
- });
2304
- if (verdict !== "eligible") {
2305
- let skipLane = lane;
2306
- if (envelope.story_complete === true && lane.keep_alive_standing !== true && hasKeepAliveProgress(lane)) {
2307
- skipLane = casLaneIf(hqRoot, lane.lane_id, (current) => current.version === lane.version &&
2308
- current.state === "done" &&
2309
- current.keep_alive === lane.keep_alive, (current) => ({ ...clearedKeepAliveProgress(current), keep_alive_blocked_reason: undefined }), deps.dryRun).record;
2310
- }
2311
- if (verdict !== null)
2312
- recordDoneKeepAlivePolicySkip(hqRoot, summary, skipLane, verdict, deps);
2313
- continue;
2314
- }
2315
- }
2316
- const company = companyForLane(lane);
2317
- if (company === null) {
2318
- if (lane.keep_alive === true) {
2319
- summary.skipped.push({
2320
- kind: "lane",
2321
- id: lane.lane_id,
2322
- why: "keep_alive_company_unresolved",
2323
- });
2324
- recordKeepAliveSkipEvent(hqRoot, lane, "company_unresolved", lane.version, summary, deps);
2325
- }
2326
- continue;
2327
- }
2328
- const companyKey = keepAliveCompanyKey(company);
2329
- if (flagByCompany.get(companyKey) !== true) {
2330
- if (lane.keep_alive === true) {
2331
- summary.skipped.push({
2332
- kind: "lane",
2333
- id: lane.lane_id,
2334
- why: "keep_alive_flag_off",
2335
- });
2336
- recordKeepAliveSkipEvent(hqRoot, lane, "keep_alive_flag_off", lane.version, summary, deps);
2337
- }
2338
- continue;
2339
- }
2340
- // Persist a stable timestamp for legacy interrupted rows before a later
2341
- // keep-alive status write can advance their general updated_at clock. The
2342
- // watcher classification is the interruption time; updated_at is only an
2343
- // upper bound and can postdate explicit opt-in.
2344
- if (initialState === "interrupted" && lane.keep_alive === true && lane.interrupted_at === undefined) {
2345
- let interruptedAt;
2346
- try {
2347
- interruptedAt = readLatestInterruptedClassificationAt(hqRoot, lane);
2348
- }
2349
- catch (err) {
2350
- const detail = `watcher classification read failed: ${err instanceof Error ? err.message : String(err)}`;
2351
- recordKeepAliveSkip(hqRoot, lane, "missing_interruption_time", summary, deps, detail);
2352
- const skip = summary.skipped[summary.skipped.length - 1];
2353
- if (skip) {
2354
- summary.ok = false;
2355
- logSkip(skip);
2356
- }
2357
- continue;
2358
- }
2359
- if (interruptedAt === undefined) {
2360
- recordKeepAliveSkip(hqRoot, lane, "missing_interruption_time", summary, deps, "no latest matching watcher classification event");
2361
- const skip = summary.skipped[summary.skipped.length - 1];
2362
- if (skip) {
2363
- summary.ok = false;
2364
- logSkip(skip);
2365
- }
2366
- continue;
2367
- }
2368
- try {
2369
- const backfill = casLaneIf(hqRoot, lane.lane_id, (current) => current.version === lane.version &&
2370
- current.state === "interrupted" &&
2371
- current.keep_alive === true &&
2372
- current.terminal_reason === lane.terminal_reason &&
2373
- current.interrupted_at === undefined, (current) => ({ ...current, interrupted_at: interruptedAt }), deps.dryRun);
2374
- lane = backfill.record;
2375
- if (lane.interrupted_at === undefined) {
2376
- recordKeepAliveSkip(hqRoot, lane, "missing_interruption_time", summary, deps, "lane changed before watcher classification timestamp could be recorded");
2377
- const skip = summary.skipped[summary.skipped.length - 1];
2378
- if (skip) {
2379
- summary.ok = false;
2380
- logSkip(skip);
2381
- }
2382
- continue;
2383
- }
2384
- }
2385
- catch (err) {
2386
- markDirty(summary, {
2387
- kind: "lane",
2388
- id: lane.lane_id,
2389
- why: `keep-alive interruption timestamp backfill failed: ${err instanceof Error ? err.message : String(err)}`,
2390
- });
2391
- continue;
2392
- }
2393
- }
2394
- if (initialState === "interrupted" && lane.terminal_reason !== "exited_with_partial_output") {
2395
- recordKeepAliveSkip(hqRoot, lane, "terminal_state", summary, deps, lane.terminal_reason ?? "missing interruption reason");
2396
- continue;
2397
- }
2398
- if (initialState === "done" && (lane.terminal_reason === PROVIDER_REJECTED_REASON ||
2399
- lane.terminal_reason === "account_not_logged_in")) {
2400
- recordKeepAliveSkip(hqRoot, lane, "terminal_state", summary, deps, lane.terminal_reason);
2401
- continue;
2402
- }
2403
- if (lane.keep_alive !== true) {
2404
- recordKeepAliveSkip(hqRoot, lane, "not_opted_in", summary, deps);
2405
- continue;
2406
- }
2407
- const optedInAtMs = Date.parse(lane.keep_alive_opted_in_at ?? "");
2408
- if (!Number.isFinite(optedInAtMs)) {
2409
- recordKeepAliveSkip(hqRoot, lane, "missing_opt_in_time", summary, deps);
2410
- continue;
2411
- }
2412
- let eligibilityAtMs;
2413
- if (initialState === "done") {
2414
- const latest = readLatestEnvelopeRecord(hqRoot, lane.lane_id);
2415
- if (!latest || latest.envelope.decision !== "done")
2416
- continue;
2417
- const hasPendingWork = hasPendingKeepAliveWork(hqRoot, lane, questions);
2418
- const hasQueuedWork = lane.keep_alive_standing === true && hasPendingWork;
2419
- const verdict = doneKeepAliveVerdict(lane, latest.envelope, hasQueuedWork, {
2420
- hasPendingWork,
2421
- nowMs,
2422
- nextCheckAnchorMs: Date.parse(lane.last_envelope?.at ?? lane.updated_at),
2423
- });
2424
- if (verdict !== "eligible") {
2425
- let skipLane = lane;
2426
- if (latest.envelope.story_complete === true && lane.keep_alive_standing !== true && hasKeepAliveProgress(lane)) {
2427
- skipLane = casLaneIf(hqRoot, lane.lane_id, (current) => current.version === lane.version && current.state === "done", (current) => ({ ...clearedKeepAliveProgress(current), keep_alive_blocked_reason: undefined }), deps.dryRun).record;
2428
- }
2429
- if (verdict !== null)
2430
- recordDoneKeepAlivePolicySkip(hqRoot, summary, skipLane, verdict, deps);
2431
- continue;
2432
- }
2433
- try {
2434
- eligibilityAtMs = latestEnvelopeMtimeMs(hqRoot, lane, latest.ref);
2435
- }
2436
- catch (err) {
2437
- markDirty(summary, {
2438
- kind: "lane",
2439
- id: lane.lane_id,
2440
- why: `keep-alive envelope timestamp unavailable: ${err instanceof Error ? err.message : String(err)}`,
2441
- });
2442
- continue;
2443
- }
2444
- }
2445
- else {
2446
- eligibilityAtMs = Date.parse(lane.interrupted_at ?? "");
2447
- if (!Number.isFinite(eligibilityAtMs)) {
2448
- recordKeepAliveSkip(hqRoot, lane, "missing_interruption_time", summary, deps);
2449
- continue;
2450
- }
2451
- }
2452
- const initialTimeBlockReason = initialState === "done"
2453
- ? "envelope_before_opt_in"
2454
- : "interruption_before_opt_in";
2455
- if (eligibilityAtMs <= optedInAtMs) {
2456
- recordKeepAliveSkip(hqRoot, lane, initialTimeBlockReason, summary, deps);
2457
- continue;
2458
- }
2459
- if (retiredLaneIds === null) {
2460
- recordKeepAliveSkip(hqRoot, lane, "retired_list_invalid", summary, deps);
2461
- continue;
2462
- }
2463
- const exclusion = keepAliveExclusion(lane, lanes, retiredLaneIds, supersessionLineageAwareByCompany.get(companyKey) === true, companyForLane);
2464
- if (exclusion !== undefined) {
2465
- recordKeepAliveSkip(hqRoot, lane, exclusion, summary, deps);
2466
- continue;
2467
- }
2468
- const providerSessionId = lane.provider_session_id ?? lane.last_resume?.session_id;
2469
- if (!providerSupportsSessionResume(lane.provider) ||
2470
- typeof providerSessionId !== "string" ||
2471
- providerSessionId.trim().length === 0) {
2472
- if (initialState === "interrupted") {
2473
- recordKeepAliveSkip(hqRoot, lane, "terminal_state", summary, deps, "provider session cannot be resumed");
2474
- }
2475
- continue;
2476
- }
2477
- // This second read is intentionally independent of the pass snapshot. The
2478
- // final timestamp comparison below closes the opt-in race before the CAS.
2479
- const rechecked = deps.readKeepAliveRecheckLane?.(hqRoot, lane.lane_id) ?? readLane(hqRoot, lane.lane_id);
2480
- // Dry-run backfills only in memory. Carry that simulated legacy timestamp
2481
- // through the fresh read when the stored lane version has not changed.
2482
- let current = deps.dryRun &&
2483
- initialState === "interrupted" &&
2484
- originalLane.interrupted_at === undefined &&
2485
- lane.interrupted_at !== undefined &&
2486
- rechecked.version === lane.version &&
2487
- rechecked.interrupted_at === undefined
2488
- ? { ...rechecked, interrupted_at: lane.interrupted_at }
2489
- : rechecked;
2490
- if (current.state !== initialState || !laneRunsHere(current, deps.host))
2491
- continue;
2492
- if (current.keep_alive !== true) {
2493
- recordKeepAliveSkip(hqRoot, current, "not_opted_in", summary, deps);
2494
- continue;
2495
- }
2496
- if (initialState === "interrupted" && current.terminal_reason !== "exited_with_partial_output") {
2497
- recordKeepAliveSkip(hqRoot, current, "terminal_state", summary, deps, current.terminal_reason ?? "missing interruption reason");
2498
- continue;
2499
- }
2500
- let currentEligibilityAtMs;
2501
- if (initialState === "done") {
2502
- const currentEnvelope = readLatestEnvelopeRecord(hqRoot, current.lane_id);
2503
- if (!currentEnvelope || currentEnvelope.envelope.decision !== "done")
2504
- continue;
2505
- const hasPendingWork = hasPendingKeepAliveWork(hqRoot, current, questions);
2506
- const hasQueuedWork = current.keep_alive_standing === true && hasPendingWork;
2507
- const verdict = doneKeepAliveVerdict(current, currentEnvelope.envelope, hasQueuedWork, {
2508
- hasPendingWork,
2509
- nowMs,
2510
- nextCheckAnchorMs: Date.parse(current.last_envelope?.at ?? current.updated_at),
2511
- });
2512
- if (verdict !== "eligible") {
2513
- let skipLane = current;
2514
- if (currentEnvelope.envelope.story_complete === true && current.keep_alive_standing !== true && hasKeepAliveProgress(current)) {
2515
- skipLane = casLaneIf(hqRoot, current.lane_id, (latestLane) => latestLane.version === current.version &&
2516
- latestLane.state === "done" &&
2517
- latestLane.keep_alive === true &&
2518
- latestLane.keep_alive_opted_in_at === current.keep_alive_opted_in_at, (latestLane) => ({ ...clearedKeepAliveProgress(latestLane), keep_alive_blocked_reason: undefined }), deps.dryRun).record;
2519
- }
2520
- if (verdict !== null)
2521
- recordDoneKeepAlivePolicySkip(hqRoot, summary, skipLane, verdict, deps);
2522
- continue;
2523
- }
2524
- try {
2525
- currentEligibilityAtMs = latestEnvelopeMtimeMs(hqRoot, current, currentEnvelope.ref);
2526
- }
2527
- catch (err) {
2528
- markDirty(summary, {
2529
- kind: "lane",
2530
- id: current.lane_id,
2531
- why: `keep-alive envelope timestamp unavailable: ${err instanceof Error ? err.message : String(err)}`,
2532
- });
2533
- continue;
2534
- }
2535
- }
2536
- else {
2537
- currentEligibilityAtMs = Date.parse(current.interrupted_at ?? "");
2538
- if (!Number.isFinite(currentEligibilityAtMs)) {
2539
- recordKeepAliveSkip(hqRoot, current, "missing_interruption_time", summary, deps);
2540
- continue;
2541
- }
2542
- }
2543
- const currentOptedInAtMs = Date.parse(current.keep_alive_opted_in_at ?? "");
2544
- if (!Number.isFinite(currentOptedInAtMs)) {
2545
- recordKeepAliveSkip(hqRoot, current, "missing_opt_in_time", summary, deps);
2546
- continue;
2547
- }
2548
- const currentTimeBlockReason = initialState === "done"
2549
- ? "envelope_before_opt_in"
2550
- : "interruption_before_opt_in";
2551
- if (currentEligibilityAtMs <= currentOptedInAtMs) {
2552
- recordKeepAliveSkip(hqRoot, current, currentTimeBlockReason, summary, deps);
2553
- continue;
2554
- }
2555
- if (retiredLaneIds === null) {
2556
- recordKeepAliveSkip(hqRoot, current, "retired_list_invalid", summary, deps);
2557
- continue;
2558
- }
2559
- const currentLanes = loadLaneRecords(hqRoot, summary);
2560
- const currentCompany = companyForLane(current);
2561
- if (currentCompany === null) {
2562
- summary.skipped.push({
2563
- kind: "lane",
2564
- id: current.lane_id,
2565
- why: "keep_alive_company_unresolved",
2566
- });
2567
- recordKeepAliveSkipEvent(hqRoot, current, "company_unresolved", current.version, summary, deps);
2568
- continue;
2569
- }
2570
- const currentCompanyKey = keepAliveCompanyKey(currentCompany);
2571
- if (flagByCompany.get(currentCompanyKey) !== true) {
2572
- summary.skipped.push({
2573
- kind: "lane",
2574
- id: current.lane_id,
2575
- why: "keep_alive_flag_off",
2576
- });
2577
- recordKeepAliveSkipEvent(hqRoot, current, "keep_alive_flag_off", current.version, summary, deps);
2578
- continue;
2579
- }
2580
- const currentExclusion = keepAliveExclusion(current, currentLanes, retiredLaneIds, supersessionLineageAwareByCompany.get(currentCompanyKey) === true, companyForLane);
2581
- if (currentExclusion !== undefined) {
2582
- recordKeepAliveSkip(hqRoot, current, currentExclusion, summary, deps);
2583
- continue;
2584
- }
2585
- if (current.keep_alive_blocked_reason === "superseded") {
2586
- // The fresh exclusion check found no active successor. Clear the stale
2587
- // reason before any later exit (backoff, queue full, retry cap, hire
2588
- // refusal) so the watcher keeps treating the lane as resumable.
2589
- const cleared = casLaneIf(hqRoot, current.lane_id, (latestLane) => latestLane.version === current.version &&
2590
- latestLane.state === current.state &&
2591
- latestLane.keep_alive === true &&
2592
- latestLane.keep_alive_opted_in_at === current.keep_alive_opted_in_at &&
2593
- latestLane.keep_alive_blocked_reason === "superseded", (latestLane) => ({ ...latestLane, keep_alive_blocked_reason: undefined }), deps.dryRun);
2594
- if (!cleared.changed)
2595
- continue;
2596
- current = cleared.record;
2597
- }
2598
- const currentSessionId = current.provider_session_id ?? current.last_resume?.session_id;
2599
- if (!providerSupportsSessionResume(current.provider) ||
2600
- typeof currentSessionId !== "string" ||
2601
- currentSessionId.trim().length === 0)
2602
- continue;
2603
- const resetByHuman = keepAliveProgressResetByHuman(current);
2604
- const attempts = resetByHuman ? 0 : current.keep_alive_resume_count ?? 0;
2605
- if (attempts >= LEAD_LANE_KEEP_ALIVE_MAX_RESUMES) {
2606
- if (current.keep_alive_exhausted_at !== undefined)
2607
- continue;
2608
- const exhausted = casLaneIf(hqRoot, current.lane_id, (latestLane) => latestLane.version === current.version &&
2609
- latestLane.state === current.state &&
2610
- latestLane.keep_alive === true &&
2611
- latestLane.keep_alive_opted_in_at === current.keep_alive_opted_in_at &&
2612
- (latestLane.keep_alive_resume_count ?? 0) >= LEAD_LANE_KEEP_ALIVE_MAX_RESUMES &&
2613
- latestLane.keep_alive_exhausted_at === undefined, (latestLane) => ({ ...latestLane, keep_alive_exhausted_at: nowIso }), deps.dryRun);
2614
- if (exhausted.changed && !deps.dryRun) {
2615
- recordKeepAliveEvent(hqRoot, current.lane_id, summary, {
2616
- event: "keep_alive_exhausted",
2617
- at: nowIso,
2618
- attempt: attempts,
2619
- resumed_from: current.state,
2620
- });
2621
- }
2622
- continue;
2623
- }
2624
- const attempt = attempts + 1;
2625
- // A done turn ends no later than its done envelope. Bookkeeping writes
2626
- // (such as clearing a stale block) move updated_at but not the envelope,
2627
- // so they cannot push the retry clock forward.
2628
- const terminalAtMs = initialState === "done"
2629
- ? Math.min(Date.parse(current.updated_at), currentEligibilityAtMs)
2630
- : currentEligibilityAtMs;
2631
- const immediateFirstResumeEnabled = initialState === "done" &&
2632
- immediateFirstResumeFlagByCompany.get(keepAliveCompanyKey(companyForLane(current))) === true;
2633
- const attemptDelayMs = immediateFirstResumeEnabled && attempt === 1
2634
- ? LEAD_LANE_KEEP_ALIVE_FIRST_RESUME_SETTLE_MS
2635
- : leadLaneKeepAliveBackoffMs(immediateFirstResumeEnabled ? Math.max(1, attempt - 1) : attempt);
2636
- const attemptEligibleMs = terminalAtMs + attemptDelayMs;
2637
- const storedNextMs = resetByHuman
2638
- ? Number.NaN
2639
- : Date.parse(current.keep_alive_next_eligible_at ?? "");
2640
- const nextEligibleMs = Number.isFinite(storedNextMs)
2641
- ? Math.max(attemptEligibleMs, storedNextMs)
2642
- : attemptEligibleMs;
2643
- if (nowMs < nextEligibleMs) {
2644
- let skipLane = current;
2645
- if (resetByHuman && hasKeepAliveProgress(current)) {
2646
- const clearedProgress = casLaneIf(hqRoot, current.lane_id, (latestLane) => latestLane.version === current.version &&
2647
- latestLane.state === current.state &&
2648
- latestLane.keep_alive === true &&
2649
- latestLane.keep_alive_opted_in_at === current.keep_alive_opted_in_at, (latestLane) => clearedKeepAliveProgress(latestLane), deps.dryRun);
2650
- skipLane = clearedProgress.record;
2651
- }
2652
- recordKeepAliveSkipEvent(hqRoot, skipLane, "backoff", skipLane.version, summary, deps, new Date(nextEligibleMs).toISOString());
2653
- continue;
2654
- }
2655
- const nextEligibleAt = new Date(nowMs + leadLaneKeepAliveBackoffMs(immediateFirstResumeEnabled ? attempt : attempt + 1)).toISOString();
2656
- const queuedCount = loadLaneRecords(hqRoot, summary).filter((queuedLane) => queuedLane.state === "queued" &&
2657
- queuedLane.provider === current.provider &&
2658
- laneRunsHere(queuedLane, deps.host) &&
2659
- !hasFreshAdmissionReservation(queuedLane, nowMs)).length;
2660
- const maxQueued = config.providers[current.provider].max_queued;
2661
- if (queuedCount >= maxQueued) {
2662
- summary.skipped.push({
2663
- kind: "lane",
2664
- id: current.lane_id,
2665
- why: `lane_queue_full: ${current.provider} queue has ${queuedCount}/${maxQueued} lanes`,
2666
- });
2667
- recordKeepAliveSkipEvent(hqRoot, current, "lane_queue_full", current.version, summary, deps, `${current.provider} queue has ${queuedCount}/${maxQueued} lanes`);
2668
- continue;
2669
- }
2670
- const seniorId = current.senior.kind === "lane" ? current.senior.id : undefined;
2671
- if (seniorId !== undefined) {
2672
- let grantPreflight;
2673
- try {
2674
- grantPreflight = withHireGrantAdmission(hqRoot, seniorId, (snapshot) => {
2675
- const latestLane = readLane(hqRoot, current.lane_id);
2676
- if (latestLane.version !== current.version ||
2677
- latestLane.state !== current.state ||
2678
- latestLane.keep_alive !== true ||
2679
- latestLane.keep_alive_opted_in_at !== current.keep_alive_opted_in_at ||
2680
- latestLane.senior.kind !== "lane" ||
2681
- latestLane.senior.id !== seniorId)
2682
- return { kind: "stale" };
2683
- const refusal = terminalChildResumeHireGrantError(latestLane, snapshot);
2684
- return refusal === null
2685
- ? { kind: "eligible" }
2686
- : { kind: "refused", error: refusal.error, message: refusal.message };
2687
- });
2688
- }
2689
- catch (err) {
2690
- markDirty(summary, {
2691
- kind: "lane",
2692
- id: current.lane_id,
2693
- why: `keep-alive hire admission preflight failed: ${err instanceof Error ? err.message : String(err)}`,
2694
- });
2695
- continue;
2696
- }
2697
- if (grantPreflight.kind === "stale")
2698
- continue;
2699
- if (grantPreflight.kind === "refused") {
2700
- summary.skipped.push({
2701
- kind: "lane",
2702
- id: current.lane_id,
2703
- why: `keep_alive_hire_admission: ${grantPreflight.error}: ${grantPreflight.message}`,
2704
- });
2705
- recordKeepAliveSkipEvent(hqRoot, current, "hire_admission", current.version, summary, deps, `${grantPreflight.error}: ${grantPreflight.message}`);
2706
- continue;
2707
- }
2708
- }
2709
- const resumedFrom = current.state === "done" ? "done" : "interrupted";
2710
- const resumeText = resumedFrom === "interrupted"
2711
- ? LEAD_LANE_KEEP_ALIVE_INTERRUPTED_RESUME_TEXT
2712
- : LEAD_LANE_KEEP_ALIVE_RESUME_TEXT;
2713
- const carriesAnsweredQuestions = resumedFrom === "done" &&
2714
- current.keep_alive_standing === true &&
2715
- undeliveredAnsweredQuestions(current, questions).length > 0;
2716
- if (carriesAnsweredQuestions && !deps.dryRun) {
2717
- writeAnsweredQuestions(hqRoot, current, questions);
2718
- }
2719
- let accountSelectionFailure;
2720
- let accountSelection;
2721
- if (current.provider === "claude" && deps.prepareKeepAliveClaudeResume !== undefined) {
2722
- const optInBeforePreparation = current.keep_alive_opted_in_at;
2723
- let preparedLane;
2724
- try {
2725
- const prepared = await deps.prepareKeepAliveClaudeResume(current);
2726
- preparedLane = prepared.lane;
2727
- accountSelectionFailure = prepared.selectionFailure;
2728
- accountSelection = prepared.accountSelection;
2729
- }
2730
- catch (err) {
2731
- // Account balancing is best-effort for keep-alive. A failure to read
2732
- // balance inputs must not prevent the existing session from resuming.
2733
- try {
2734
- preparedLane = readLane(hqRoot, current.lane_id);
2735
- }
2736
- catch (readErr) {
2737
- markDirty(summary, {
2738
- kind: "lane",
2739
- id: current.lane_id,
2740
- why: `keep-alive account selection failed and lane could not be re-read: ${readErr instanceof Error ? readErr.message : String(readErr)}`,
2741
- });
2742
- continue;
2743
- }
2744
- accountSelectionFailure = err instanceof Error ? err.message : String(err);
2745
- }
2746
- const preparedSessionId = preparedLane.provider_session_id ?? preparedLane.last_resume?.session_id;
2747
- if (preparedLane.state !== initialState ||
2748
- preparedLane.keep_alive !== true ||
2749
- !laneRunsHere(preparedLane, deps.host) ||
2750
- preparedSessionId !== currentSessionId)
2751
- continue;
2752
- let latest;
2753
- try {
2754
- latest = deps.dryRun
2755
- ? (deps.readKeepAliveRecheckLane?.(hqRoot, current.lane_id) ?? readLane(hqRoot, current.lane_id))
2756
- : readLane(hqRoot, current.lane_id);
2757
- }
2758
- catch (err) {
2759
- markDirty(summary, {
2760
- kind: "lane",
2761
- id: current.lane_id,
2762
- why: `keep-alive eligibility recheck failed after account selection: ${err instanceof Error ? err.message : String(err)}`,
2763
- });
2764
- continue;
2765
- }
2766
- if (latest.state !== initialState || !laneRunsHere(latest, deps.host))
2767
- continue;
2768
- if (latest.keep_alive !== true) {
2769
- recordKeepAliveSkip(hqRoot, latest, "not_opted_in", summary, deps);
2770
- continue;
2771
- }
2772
- if (latest.keep_alive_opted_in_at !== optInBeforePreparation) {
2773
- summary.skipped.push({
2774
- kind: "lane",
2775
- id: latest.lane_id,
2776
- why: "keep_alive_eligibility_changed_during_account_selection",
2777
- });
2778
- continue;
2779
- }
2780
- const latestSessionId = latest.provider_session_id ?? latest.last_resume?.session_id;
2781
- if (latestSessionId !== currentSessionId)
2782
- continue;
2783
- let latestEligibilityAtMs;
2784
- if (initialState === "done") {
2785
- const envelope = readLatestEnvelopeRecord(hqRoot, latest.lane_id);
2786
- if (!envelope || envelope.envelope.decision !== "done")
2787
- continue;
2788
- const hasPendingWork = hasPendingKeepAliveWork(hqRoot, latest, questions);
2789
- const finalDoneVerdict = doneKeepAliveVerdict(latest, envelope.envelope, latest.keep_alive_standing === true && hasPendingWork, {
2790
- hasPendingWork,
2791
- nowMs,
2792
- nextCheckAnchorMs: Date.parse(latest.last_envelope?.at ?? latest.updated_at),
2793
- });
2794
- if (finalDoneVerdict !== "eligible") {
2795
- if (finalDoneVerdict === "next_check_at") {
2796
- recordDoneKeepAlivePolicySkip(hqRoot, summary, latest, finalDoneVerdict, deps);
2797
- }
2798
- continue;
2799
- }
2800
- try {
2801
- latestEligibilityAtMs = latestEnvelopeMtimeMs(hqRoot, latest, envelope.ref);
2802
- }
2803
- catch (err) {
2804
- markDirty(summary, {
2805
- kind: "lane",
2806
- id: latest.lane_id,
2807
- why: `keep-alive envelope timestamp unavailable after account selection: ${err instanceof Error ? err.message : String(err)}`,
2808
- });
2809
- continue;
2810
- }
2811
- }
2812
- else {
2813
- latestEligibilityAtMs = Date.parse(latest.interrupted_at ?? "");
2814
- }
2815
- const latestOptedInAtMs = Date.parse(latest.keep_alive_opted_in_at ?? "");
2816
- if (!Number.isFinite(latestEligibilityAtMs) || !Number.isFinite(latestOptedInAtMs))
2817
- continue;
2818
- if (latestEligibilityAtMs <= latestOptedInAtMs) {
2819
- recordKeepAliveSkip(hqRoot, latest, initialState === "done" ? "envelope_before_opt_in" : "interruption_before_opt_in", summary, deps);
2820
- continue;
2821
- }
2822
- current = deps.dryRun ? preparedLane : latest;
2823
- }
2824
- if (accountSelectionFailure !== undefined) {
2825
- summary.skipped.push({
2826
- kind: "lane",
2827
- id: current.lane_id,
2828
- why: `keep_alive_account_selection_failed: ${accountSelectionFailure}`,
2829
- });
2830
- if (!deps.dryRun) {
2831
- recordKeepAliveSkipEvent(hqRoot, current, "account_selection_failed", current.version, summary, deps, accountSelectionFailure);
2832
- recordKeepAliveEvent(hqRoot, current.lane_id, summary, {
2833
- event: "keep_alive_account_selection_failed",
2834
- at: nowIso,
2835
- account: current.account ?? null,
2836
- reason: accountSelectionFailure,
2837
- });
2838
- }
2839
- }
2840
- const queue = (expectedSeniorId) => casLaneIf(hqRoot, current.lane_id, (latestLane) => latestLane.version === current.version &&
2841
- latestLane.state === current.state &&
2842
- latestLane.keep_alive === true &&
2843
- latestLane.keep_alive_opted_in_at === current.keep_alive_opted_in_at &&
2844
- (expectedSeniorId === undefined ||
2845
- (latestLane.senior.kind === "lane" && latestLane.senior.id === expectedSeniorId)), (latestLane) => ({
2846
- ...latestLane,
2847
- state: "queued",
2848
- queued_reason: undefined,
2849
- queued_details: undefined,
2850
- queued_at: nowIso,
2851
- admission_reserved_at: undefined,
2852
- terminal_reason: undefined,
2853
- terminal_details: undefined,
2854
- pending_resume: {
2855
- session_id: currentSessionId,
2856
- trigger: carriesAnsweredQuestions ? "answered_questions" : "resume",
2857
- keep_alive: true,
2858
- resumed_from: resumedFrom,
2859
- text: resumeText,
2860
- compact: true,
2861
- at: nowIso,
2862
- },
2863
- keep_alive_resume_count: attempt,
2864
- keep_alive_history: appendCappedHistory(latestLane.keep_alive_history ?? [], {
2865
- at: nowIso,
2866
- resumed_from: resumedFrom,
2867
- account: latestLane.account ?? null,
2868
- }),
2869
- keep_alive_last_resume_at: nowIso,
2870
- keep_alive_next_eligible_at: nextEligibleAt,
2871
- keep_alive_exhausted_at: undefined,
2872
- keep_alive_blocked_reason: undefined,
2873
- }), deps.dryRun);
2874
- let queued;
2875
- if (seniorId) {
2876
- try {
2877
- queued = withHireGrantAdmission(hqRoot, seniorId, (snapshot) => {
2878
- const latestLane = readLane(hqRoot, current.lane_id);
2879
- if (latestLane.version !== current.version ||
2880
- latestLane.senior.kind !== "lane" ||
2881
- latestLane.senior.id !== seniorId)
2882
- return { changed: false, record: latestLane };
2883
- const refusal = terminalChildResumeHireGrantError(latestLane, snapshot);
2884
- if (refusal !== null) {
2885
- summary.skipped.push({
2886
- kind: "lane",
2887
- id: current.lane_id,
2888
- why: `keep_alive_hire_admission: ${refusal.error}: ${refusal.message}`,
2889
- });
2890
- return { changed: false, record: latestLane };
2891
- }
2892
- return queue(seniorId);
2893
- });
2894
- }
2895
- catch (err) {
2896
- markDirty(summary, {
2897
- kind: "lane",
2898
- id: current.lane_id,
2899
- why: `keep-alive hire admission failed: ${err instanceof Error ? err.message : String(err)}`,
2900
- });
2901
- continue;
2902
- }
2903
- }
2904
- else {
2905
- queued = queue(seniorId);
2906
- }
2907
- if (!queued.changed)
2908
- continue;
2909
- summary.resumed.push({
2910
- id: current.lane_id,
2911
- from: resumedFrom,
2912
- to: "queued",
2913
- reason: "keep_alive",
2914
- });
2915
- if (!deps.dryRun) {
2916
- recordKeepAliveEvent(hqRoot, current.lane_id, summary, {
2917
- event: "keep_alive_resume",
2918
- at: nowIso,
2919
- attempt,
2920
- next_eligible_at: nextEligibleAt,
2921
- resumed_from: resumedFrom,
2922
- ...(accountSelection !== undefined
2923
- ? { account_selection: keepAliveAccountSelectionEvidence(accountSelection) }
2924
- : {}),
2925
- });
2926
- }
2927
- }
2928
- }
2929
- async function resumeAwaiting(hqRoot, lanes, questions, summary, deps, onlyLaneIds) {
2930
- const config = loadLanesConfig(hqRoot);
2931
- const awaiting = lanes.filter((lane) => lane.state === "awaiting_input" &&
2932
- laneRunsHere(lane, deps.host) &&
2933
- (onlyLaneIds === undefined || onlyLaneIds.has(lane.lane_id)));
2934
- if (awaiting.length === 0 || deps.skipAdmissionWithoutCpuSample === true)
2935
- return;
2936
- const throttleDeps = deps.throttleDeps ?? defaultThrottleDeps(deps.env ?? process.env);
2937
- let throttleDecision;
2938
- if (deps.dryRun) {
2939
- const loaded = readThrottleState(throttleStatePath(hqRoot), throttleDeps);
2940
- const state = loaded === "missing" ? emptyThrottleState() : loaded;
2941
- throttleDecision = evaluateThrottle({
2942
- busy: deps.cpuBusyFraction !== undefined
2943
- ? deps.cpuBusyFraction
2944
- : deps.cpuObservation?.busy ?? await resolveCpuBusyFraction(throttleDeps),
2945
- availableMemMb: readAvailableMemMb(throttleDeps),
2946
- cpuCheckEnabled: isCpuCheckEnabled(throttleDeps.env),
2947
- state,
2948
- throttle: config.throttle,
2949
- nowMs: deps.now().getTime(),
2950
- });
2951
- }
2952
- else {
2953
- const throttle = await decideAdmission(hqRoot, config.throttle, throttleDeps, deps.cpuBusyFraction, deps.cpuObservation);
2954
- throttleDecision = throttle.decision;
2955
- }
2956
- const spawnFn = deps.spawn ?? ((root, lane) => spawnLaneWorker(root, lane, spawnDepsForHost(deps)));
2957
- const nowMs = deps.now().getTime();
2958
- const fallbackAnswerAt = new Date(nowMs).toISOString();
2959
- const answerAtByLane = new Map();
2960
- for (const question of questions) {
2961
- if (question.status !== "answered")
2962
- continue;
2963
- const at = question.answer?.answered_at ?? question.asked_at;
2964
- const previous = answerAtByLane.get(question.origin.id);
2965
- if (previous === undefined || Date.parse(at) > Date.parse(previous)) {
2966
- answerAtByLane.set(question.origin.id, at);
2967
- }
2968
- }
2969
- const latestAnswerAt = (lane) => answerAtByLane.get(lane.lane_id) ?? fallbackAnswerAt;
2970
- awaiting.sort((a, b) => {
2971
- const byAnswerTime = Date.parse(latestAnswerAt(a)) - Date.parse(latestAnswerAt(b));
2972
- return byAnswerTime || compareLaneQueueOrder(a, b);
2973
- });
2974
- const capacityLanes = lanes.map((lane) => {
2975
- try {
2976
- return readLane(hqRoot, lane.lane_id);
2977
- }
2978
- catch {
2979
- return lane;
2980
- }
2981
- });
2982
- const running = capacityLanes.filter((lane) => (lane.state === "running" ||
2983
- hasFreshAdmissionReservation(lane, nowMs) ||
2984
- hasActivePendingResume(lane, nowMs)) &&
2985
- laneRunsHere(lane, deps.host));
2986
- for (const lane of awaiting) {
2987
- const current = readLane(hqRoot, lane.lane_id);
2988
- if (current.state !== "awaiting_input")
2989
- continue;
2990
- const pending = questions.filter((q) => q.origin.id === lane.lane_id && q.status === "pending");
2991
- const answered = questions.filter((q) => q.origin.id === lane.lane_id && q.status === "answered");
2992
- if (pending.length > 0 || answered.length === 0)
2993
- continue;
2994
- try {
2995
- let profileMaxConcurrent;
2996
- if (current.worker_profile !== undefined) {
2997
- profileMaxConcurrent = loadWorkerProfile(hqRoot, current.worker_profile).meta.max_concurrent;
2998
- }
2999
- const capReason = laneAdmissionCapReason(running, current, config, deps.host, profileMaxConcurrent, nowMs);
3000
- const answeredAt = latestAnswerAt(current);
3001
- const queueCandidate = { ...current, state: "queued", queued_at: answeredAt };
3002
- const earlierQueued = capacityLanes.some((queued) => queued.lane_id !== current.lane_id &&
3003
- queued.state === "queued" &&
3004
- queued.provider === current.provider &&
3005
- laneRunsHere(queued, deps.host) &&
3006
- compareLaneQueueOrder(queued, queueCandidate) < 0);
3007
- const queueReason = !throttleDecision.admit
3008
- ? "throttle"
3009
- : capReason ?? (earlierQueued ? "queue_position" : undefined);
3010
- if (queueReason !== undefined) {
3011
- const queuedCount = capacityLanes.filter((queued) => queued.state === "queued" &&
3012
- queued.provider === current.provider &&
3013
- laneRunsHere(queued, deps.host) &&
3014
- !hasFreshAdmissionReservation(queued, nowMs)).length;
3015
- const maxQueued = config.providers[current.provider].max_queued;
3016
- if (queuedCount >= maxQueued) {
3017
- summary.skipped.push({
3018
- kind: "lane",
3019
- id: current.lane_id,
3020
- why: `lane_queue_full: ${current.provider} queue has ${queuedCount}/${maxQueued} lanes`,
3021
- });
3022
- continue;
3023
- }
3024
- }
3025
- if (!deps.dryRun)
3026
- writeAnsweredQuestions(hqRoot, current, questions);
3027
- if (queueReason !== undefined) {
3028
- const answerEnv = deps.spawnLaneDeps?.env ?? deps.env ?? process.env;
3029
- const answerCaller = typeof answerEnv.HQ_LANE_ID === "string"
3030
- ? answerEnv.HQ_LANE_ID.trim() || "user"
3031
- : "user";
3032
- const queued = casLaneIf(hqRoot, current.lane_id, (latest) => latest.state === "awaiting_input", (latest) => ({
3033
- ...latest,
3034
- state: "queued",
3035
- queued_reason: queueReason,
3036
- queued_details: undefined,
3037
- queued_at: answeredAt,
3038
- pending_resume: {
3039
- ...(latest.provider_session_id
3040
- ? { session_id: latest.provider_session_id }
3041
- : {}),
3042
- trigger: "answered_questions",
3043
- caller: answerCaller,
3044
- at: answeredAt,
3045
- },
3046
- }), deps.dryRun);
3047
- summary.resumed.push({
3048
- id: queued.record.lane_id,
3049
- from: "awaiting_input",
3050
- to: "queued",
3051
- reason: queueReason,
3052
- });
3053
- capacityLanes.push(queued.record);
3054
- continue;
3055
- }
3056
- if (deps.dryRun) {
3057
- summary.resumed.push({
3058
- id: lane.lane_id,
3059
- from: "awaiting_input",
3060
- to: "running",
3061
- });
3062
- running.push({ ...current, state: "running" });
3063
- continue;
3064
- }
3065
- const started = spawnFn(hqRoot, lane);
3066
- if (started.state === "running")
3067
- running.push(started);
3068
- summary.resumed.push({
3069
- id: started.lane_id,
3070
- from: "awaiting_input",
3071
- to: started.state,
3072
- });
3073
- }
3074
- catch (err) {
3075
- terminalizeModelUnsupported(hqRoot, lane, err);
3076
- const errorName = err instanceof LanesError ? `${err.error}: ` : "";
3077
- const errorMessage = err instanceof Error ? err.message : String(err);
3078
- markDirty(summary, {
3079
- kind: "lane",
3080
- id: lane.lane_id,
3081
- why: `resume failed: ${errorName}${errorMessage}`,
3082
- });
3083
- }
3084
- }
3085
- }
3086
- /**
3087
- * One idempotent reconcile pass. Throws only when the store directory itself
3088
- * cannot be listed; per-record failures are logged onto the summary and set
3089
- * `ok: false` rather than being swallowed.
3090
- */
3091
- export async function runReconcilePass(hqRoot, deps) {
3092
- return withReconcileLock(hqRoot, async () => runPassBody(hqRoot, deps));
3093
- }
3094
- async function runTerminalKeepAliveCandidatePass(hqRoot, lanes, summary, deps, now, candidate) {
3095
- const lane = lanes.find((row) => row.lane_id === candidate.laneId);
3096
- if (!lane || lane.version !== candidate.version || lane.state !== "done" ||
3097
- lane.keep_alive !== true || !laneRunsHere(lane, deps.host))
3098
- return summary;
3099
- let company;
3100
- try {
3101
- company = resolveLaneCompany(hqRoot, lane).company;
3102
- }
3103
- catch (err) {
3104
- markDirty(summary, { kind: "lane", id: lane.lane_id, why: `keep-alive company lookup failed: ${err instanceof Error ? err.message : String(err)}` });
3105
- return summary;
3106
- }
3107
- if (company === null)
3108
- return summary;
3109
- const readFlag = deps.readRegistryFlag ?? readLanesRegistryFlag;
3110
- const options = { company, env: deps.env ?? process.env };
3111
- let keepAliveEnabled;
3112
- let lineageAware;
3113
- let immediateFirstResumeEnabled;
3114
- try {
3115
- [keepAliveEnabled, lineageAware, immediateFirstResumeEnabled] = await Promise.all([
3116
- readFlag(LEAD_LANE_KEEP_ALIVE_FLAG, options),
3117
- readFlag(LANE_SUPERSESSION_LINEAGE_AWARE_FLAG, options),
3118
- readFlag(LEAD_LANE_KEEP_ALIVE_IMMEDIATE_FIRST_RESUME_FLAG, options),
3119
- ]);
3120
- }
3121
- catch (err) {
3122
- markDirty(summary, { kind: "lane", id: lane.lane_id, why: `keep-alive flag read failed: ${err instanceof Error ? err.message : String(err)}` });
3123
- return summary;
3124
- }
3125
- if (!keepAliveEnabled)
3126
- return summary;
3127
- await withAllLaneAdmissionLocks(hqRoot, async () => {
3128
- const freshLanes = loadLaneRecords(hqRoot, summary);
3129
- const freshQuestions = loadQuestionRecords(hqRoot, summary);
3130
- const current = freshLanes.find((row) => row.lane_id === candidate.laneId);
3131
- if (!current || current.version !== candidate.version || current.state !== "done")
3132
- return;
3133
- await resumeKeepAlive(hqRoot, freshLanes, freshQuestions, summary, deps, now, new Map([[keepAliveCompanyKey(company), true]]), new Map([[keepAliveCompanyKey(company), lineageAware]]), new Map(), new Map([[keepAliveCompanyKey(company), immediateFirstResumeEnabled]]), candidate);
3134
- await admitQueued(hqRoot, loadLaneRecords(hqRoot, summary), summary, deps, now, new Set([candidate.laneId]));
3135
- });
3136
- return summary;
3137
- }
3138
- /** Run the reconcile answer-resume path for one lane before questions answer returns. */
3139
- export async function wakeAnsweredLaneNow(hqRoot, laneId, deps) {
3140
- const summary = emptySummary(deps.dryRun);
3141
- const throttleDeps = deps.throttleDeps ?? defaultThrottleDeps(deps.env ?? process.env);
3142
- await withReconcileLock(hqRoot, async () => {
3143
- const previousState = readThrottleState(throttleStatePath(hqRoot), throttleDeps);
3144
- const previousSnapshot = previousState === "missing" || previousState === "unreadable"
3145
- ? undefined
3146
- : previousState.cpu_snapshot;
3147
- const cpuObservation = await sampleCpuBusyObservation(throttleDeps, previousSnapshot);
3148
- const admissionDeps = {
3149
- ...deps,
3150
- cpuBusyFraction: cpuObservation.busy,
3151
- cpuObservation,
3152
- skipAdmissionWithoutCpuSample: false,
3153
- };
3154
- await withAllLaneAdmissionLocks(hqRoot, async () => {
3155
- const lanes = loadLaneRecords(hqRoot, summary);
3156
- const questions = loadQuestionRecords(hqRoot, summary);
3157
- if (!lanes.some((lane) => lane.lane_id === laneId)) {
3158
- throw new LanesError("lane_not_found", `Lane ${laneId} was not found`, { lane_id: laneId });
3159
- }
3160
- await resumeAwaiting(hqRoot, lanes, questions, summary, admissionDeps, new Set([laneId]));
3161
- });
3162
- });
3163
- const latest = readLane(hqRoot, laneId);
3164
- const laneQuestions = loadQuestionRecords(hqRoot, emptySummary(false)).filter((question) => question.origin.id === laneId);
3165
- const remaining = laneQuestions
3166
- .filter((question) => question.status === "pending")
3167
- .map((question) => question.question_id)
3168
- .sort();
3169
- if (latest.state === "running") {
3170
- return { status: "resumed", lane_state: latest.state };
3171
- }
3172
- if (latest.state === "queued" && latest.pending_resume?.trigger === "answered_questions") {
3173
- return {
3174
- status: "queued",
3175
- lane_state: latest.state,
3176
- ...(latest.queued_reason !== undefined ? { queued_reason: latest.queued_reason } : {}),
3177
- };
3178
- }
3179
- if (latest.state === "awaiting_input" && remaining.length > 0) {
3180
- return {
3181
- status: "awaiting_answers",
3182
- lane_state: latest.state,
3183
- remaining_question_ids: remaining,
3184
- };
3185
- }
3186
- const failure = summary.skipped.find((skip) => skip.kind === "lane" && skip.id === laneId);
3187
- return {
3188
- status: "failed",
3189
- lane_state: latest.state,
3190
- error: failure?.why ?? `Lane ${laneId} did not leave awaiting_input after its questions were answered`,
3191
- };
3192
- }
3193
- //# sourceMappingURL=reconcile.js.map