@indigoai-us/hq-cli 5.314.0 → 5.316.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 (1193) hide show
  1. package/CHANGELOG.md +4320 -1
  2. package/README.md +113 -0
  3. package/assets/bot-workers/setup/context/USER-GUIDE.md +404 -0
  4. package/assets/bot-workers/setup/context/quick-reference.md +199 -0
  5. package/assets/bot-workers/setup/skills/agents-and-team.md +167 -0
  6. package/assets/bot-workers/setup/skills/explain-hq.md +35 -0
  7. package/assets/bot-workers/setup/skills/first-company.md +74 -0
  8. package/assets/bot-workers/setup/skills/joining-a-team.md +125 -0
  9. package/assets/bot-workers/setup/skills/pricing.md +92 -0
  10. package/assets/bot-workers/setup/skills/standing-help.md +81 -0
  11. package/assets/bot-workers/setup/skills/your-business.md +281 -0
  12. package/assets/bot-workers/setup/worker.yaml +791 -0
  13. package/assets/lanes-hooks/hq-lanes-drain.json +15 -0
  14. package/assets/lanes-hooks/hq-lanes-drain.mjs +275 -0
  15. package/assets/lanes-hooks/hq-lanes-drain.sh +21 -0
  16. package/assets/lanes-reconcile/hq-lanes-reconcile.service +11 -0
  17. package/assets/lanes-reconcile/hq-lanes-reconcile.timer +12 -0
  18. package/assets/scaffold/.claude/hooks/validate-policy-frontmatter.sh +485 -0
  19. package/assets/scaffold/core/scripts/audit-log.sh +384 -0
  20. package/assets/scaffold/core/scripts/checkpoint-stop-gate.sh +281 -43
  21. package/assets/scaffold/core/scripts/clean-worktree-reconcile-handoff.sh +171 -0
  22. package/assets/scaffold/core/scripts/detect-stale-review-base.sh +199 -0
  23. package/assets/scaffold/core/scripts/eval-trigger.sh +193 -0
  24. package/assets/scaffold/core/scripts/git-pack-extension-check.sh +164 -0
  25. package/assets/scaffold/core/scripts/handoff-finalize.sh +688 -0
  26. package/assets/scaffold/core/scripts/handoff-open-steps.sh +81 -0
  27. package/assets/scaffold/core/scripts/handoff-post.sh +252 -0
  28. package/assets/scaffold/core/scripts/hook-lib.sh +345 -50
  29. package/assets/scaffold/core/scripts/hq-delegate-bundle.sh +307 -0
  30. package/assets/scaffold/core/scripts/hq-delegate-grant.sh +252 -0
  31. package/assets/scaffold/core/scripts/hq-delegate-pickup.sh +99 -0
  32. package/assets/scaffold/core/scripts/hq-delegate-publish.sh +91 -0
  33. package/assets/scaffold/core/scripts/hq-delegate-repo.sh +158 -0
  34. package/assets/scaffold/core/scripts/hq-delegate-resolve.sh +145 -0
  35. package/assets/scaffold/core/scripts/hq-delegate-secrets.sh +129 -0
  36. package/assets/scaffold/core/scripts/hq-delegate-send.sh +259 -0
  37. package/assets/scaffold/core/scripts/hq-delegate-transfer.sh +175 -0
  38. package/assets/scaffold/core/scripts/hq-delegate-verify.sh +238 -0
  39. package/assets/scaffold/core/scripts/hq-dm-bind.sh +585 -0
  40. package/assets/scaffold/core/scripts/hq-job-notify.sh +389 -0
  41. package/assets/scaffold/core/scripts/hq-job-probe.sh +835 -0
  42. package/assets/scaffold/core/scripts/hq-job-remediate.sh +359 -0
  43. package/assets/scaffold/core/scripts/hq-job-run.sh +1025 -0
  44. package/assets/scaffold/core/scripts/hq-pin.sh +118 -0
  45. package/assets/scaffold/core/scripts/hq-session.sh +184 -23
  46. package/assets/scaffold/core/scripts/jobs-schedule-parse.sh +310 -0
  47. package/assets/scaffold/core/scripts/jobs-validate.sh +451 -0
  48. package/assets/scaffold/core/scripts/knowledge-prefs.sh +55 -0
  49. package/assets/scaffold/core/scripts/lib/portable.sh +211 -0
  50. package/assets/scaffold/core/scripts/lib/secret-patterns.sh +58 -0
  51. package/assets/scaffold/core/scripts/lib/session-id.sh +3 -2
  52. package/assets/scaffold/core/scripts/lib/session-scope-capability.sh +1 -1
  53. package/assets/scaffold/core/scripts/lib/work-mesh-enqueue.sh +2 -1
  54. package/assets/scaffold/core/scripts/lib/work-mesh-live-hook.sh +378 -0
  55. package/assets/scaffold/core/scripts/lib/work-mesh-live-rebind.sh +141 -0
  56. package/assets/scaffold/core/scripts/migrate-policy-triggers.sh +378 -0
  57. package/assets/scaffold/core/scripts/ontology-audience-grant.sh +81 -0
  58. package/assets/scaffold/core/scripts/ontology-brief.mjs +93 -0
  59. package/assets/scaffold/core/scripts/ontology-candidate.sh +100 -0
  60. package/assets/scaffold/core/scripts/ontology-garden.mjs +238 -0
  61. package/assets/scaffold/core/scripts/ontology-source-worklist.sh +79 -0
  62. package/assets/scaffold/core/scripts/outpost-jobs-reconcile.sh +834 -0
  63. package/assets/scaffold/core/scripts/policy-age-report.sh +156 -0
  64. package/assets/scaffold/core/scripts/policy-retire.sh +199 -0
  65. package/assets/scaffold/core/scripts/qmd-reindex-after-sync.sh +10 -87
  66. package/assets/scaffold/core/scripts/qmd-reindex-bg.sh +40 -0
  67. package/assets/scaffold/core/scripts/read-policy-frontmatter.sh +32 -0
  68. package/assets/scaffold/core/scripts/rebuild-company-knowledge-index.sh +36 -3
  69. package/assets/scaffold/core/scripts/rebuild-projects-index.sh +99 -12
  70. package/assets/scaffold/core/scripts/refresh-vault-access.sh +188 -0
  71. package/assets/scaffold/core/scripts/register-project.sh +262 -0
  72. package/assets/scaffold/core/scripts/repo-run-registry.sh +408 -0
  73. package/assets/scaffold/core/scripts/resolve-company.sh +199 -0
  74. package/assets/scaffold/core/scripts/resume-thread-lock.sh +285 -0
  75. package/assets/scaffold/core/scripts/share-suggestion-state.sh +541 -0
  76. package/assets/scaffold/core/scripts/source-yaml-validate.sh +36 -0
  77. package/assets/scaffold/core/scripts/token-usage-report.sh +81 -20
  78. package/assets/scaffold/core/scripts/verify-story-deliverables.sh +148 -0
  79. package/assets/scaffold/core/scripts/work-mesh-live-bind-trusted.sh +152 -0
  80. package/assets/scaffold/core/scripts/work-mesh-live-rebind.sh +59 -0
  81. package/assets/scaffold/core/scripts/worktree-gc.sh +362 -0
  82. package/contracts/preflight/v1/fixtures.json +87 -0
  83. package/contracts/preflight/v1/schema.json +58 -0
  84. package/dist/bin/hq-auth-refresh.d.ts +2 -0
  85. package/dist/bin/hq-auth-refresh.js +12 -6
  86. package/dist/command-catalog.generated.d.ts +10798 -0
  87. package/dist/command-catalog.generated.js +13915 -0
  88. package/dist/command-registration-plan.d.ts +474 -0
  89. package/dist/command-registration-plan.js +136 -0
  90. package/dist/commands/__fixtures__/access-vault.d.ts +2 -0
  91. package/dist/commands/__fixtures__/access-vault.js +1 -1
  92. package/dist/commands/_patterns.d.ts +16 -0
  93. package/dist/commands/_patterns.js +20 -3
  94. package/dist/commands/access.d.ts +2 -1
  95. package/dist/commands/access.js +58 -49
  96. package/dist/commands/agent-enroll.d.ts +146 -0
  97. package/dist/commands/agent-enroll.js +401 -0
  98. package/dist/commands/agent-inbox.d.ts +9 -0
  99. package/dist/commands/agent-inbox.js +102 -0
  100. package/dist/commands/agent-kit.d.ts +87 -0
  101. package/dist/commands/agent-kit.js +480 -0
  102. package/dist/commands/agent-mcp.d.ts +24 -0
  103. package/dist/commands/agent-mcp.js +127 -0
  104. package/dist/commands/agent-probe.d.ts +77 -0
  105. package/dist/commands/agent-probe.js +335 -0
  106. package/dist/commands/agent.d.ts +13 -0
  107. package/dist/commands/agent.js +40 -0
  108. package/dist/commands/agents.d.ts +106 -4
  109. package/dist/commands/agents.js +924 -45
  110. package/dist/commands/auth.d.ts +1 -0
  111. package/dist/commands/auth.js +189 -19
  112. package/dist/commands/billing-sponsor.d.ts +83 -0
  113. package/dist/commands/billing-sponsor.js +344 -0
  114. package/dist/commands/billing.d.ts +3 -0
  115. package/dist/commands/billing.js +6 -1
  116. package/dist/commands/bot-companies.d.ts +40 -0
  117. package/dist/commands/bot-companies.js +64 -0
  118. package/dist/commands/bot-continuity.d.ts +28 -0
  119. package/dist/commands/bot-continuity.js +68 -0
  120. package/dist/commands/bot-jobs.d.ts +131 -0
  121. package/dist/commands/bot-jobs.js +474 -0
  122. package/dist/commands/bot.d.ts +278 -0
  123. package/dist/commands/bot.js +1982 -0
  124. package/dist/commands/channels.js +56 -1
  125. package/dist/commands/cloud-demote.js +3 -2
  126. package/dist/commands/cloud-provision.d.ts +14 -0
  127. package/dist/commands/cloud-provision.js +232 -58
  128. package/dist/commands/cloud-retire.d.ts +51 -0
  129. package/dist/commands/cloud-retire.js +154 -0
  130. package/dist/commands/cloud.d.ts +49 -15
  131. package/dist/commands/cloud.js +357 -122
  132. package/dist/commands/company-rename-flag.d.ts +4 -0
  133. package/dist/commands/company-rename-flag.js +7 -0
  134. package/dist/commands/company-rename.d.ts +4 -0
  135. package/dist/commands/company-rename.js +90 -0
  136. package/dist/commands/company-transfer.d.ts +4 -28
  137. package/dist/commands/company-transfer.js +5 -49
  138. package/dist/commands/company.js +68 -3
  139. package/dist/commands/core-audit-log-native.d.ts +9 -0
  140. package/dist/commands/core-audit-log-native.js +257 -0
  141. package/dist/commands/core-checkpoint-gate-setting.d.ts +59 -0
  142. package/dist/commands/core-checkpoint-gate-setting.js +163 -0
  143. package/dist/commands/core-checkpoint-stop-gate-native.d.ts +34 -0
  144. package/dist/commands/core-checkpoint-stop-gate-native.js +725 -0
  145. package/dist/commands/core-checkpoint.d.ts +19 -9
  146. package/dist/commands/core-checkpoint.js +301 -37
  147. package/dist/commands/core-command-catalog.d.ts +34 -0
  148. package/dist/commands/core-command-catalog.js +62 -0
  149. package/dist/commands/core-company-index-scope-flag.d.ts +3 -0
  150. package/dist/commands/core-company-index-scope-flag.js +6 -0
  151. package/dist/commands/core-delegate-w1b.d.ts +10 -0
  152. package/dist/commands/core-delegate-w1b.js +1219 -0
  153. package/dist/commands/core-delegate.d.ts +46 -0
  154. package/dist/commands/core-delegate.js +1040 -0
  155. package/dist/commands/core-detect-stale-review-base-native.d.ts +23 -0
  156. package/dist/commands/core-detect-stale-review-base-native.js +178 -0
  157. package/dist/commands/core-dm-bind-native.d.ts +32 -0
  158. package/dist/commands/core-dm-bind-native.js +668 -0
  159. package/dist/commands/core-eval-trigger-native.d.ts +4 -0
  160. package/dist/commands/core-eval-trigger-native.js +12 -0
  161. package/dist/commands/core-git-pack-extension-check-native.d.ts +22 -0
  162. package/dist/commands/core-git-pack-extension-check-native.js +190 -0
  163. package/dist/commands/core-handoff-fast.d.ts +18 -0
  164. package/dist/commands/core-handoff-fast.js +104 -0
  165. package/dist/commands/core-handoff-open-steps.d.ts +21 -0
  166. package/dist/commands/core-handoff-open-steps.js +264 -0
  167. package/dist/commands/core-handoff.d.ts +24 -0
  168. package/dist/commands/core-handoff.js +1144 -0
  169. package/dist/commands/core-hook-state-helpers-native.d.ts +11 -0
  170. package/dist/commands/core-hook-state-helpers-native.js +74 -0
  171. package/dist/commands/core-hq-session-native.d.ts +39 -0
  172. package/dist/commands/core-hq-session-native.js +579 -0
  173. package/dist/commands/core-job-native-utils.d.ts +6 -0
  174. package/dist/commands/core-job-native-utils.js +80 -0
  175. package/dist/commands/core-job-native.d.ts +5 -0
  176. package/dist/commands/core-job-native.js +16 -0
  177. package/dist/commands/core-job-notify-native.d.ts +36 -0
  178. package/dist/commands/core-job-notify-native.js +359 -0
  179. package/dist/commands/core-job-probe-native.d.ts +45 -0
  180. package/dist/commands/core-job-probe-native.js +564 -0
  181. package/dist/commands/core-job-remediate-native.d.ts +26 -0
  182. package/dist/commands/core-job-remediate-native.js +374 -0
  183. package/dist/commands/core-job-remote-launcher.d.ts +7 -0
  184. package/dist/commands/core-job-remote-launcher.js +51 -0
  185. package/dist/commands/core-job-run-native.d.ts +47 -0
  186. package/dist/commands/core-job-run-native.js +1215 -0
  187. package/dist/commands/core-jobs-native.d.ts +25 -0
  188. package/dist/commands/core-jobs-native.js +1339 -0
  189. package/dist/commands/core-knowledge-prefs.d.ts +11 -0
  190. package/dist/commands/core-knowledge-prefs.js +137 -0
  191. package/dist/commands/core-migrate-policy-triggers-native.d.ts +21 -0
  192. package/dist/commands/core-migrate-policy-triggers-native.js +374 -0
  193. package/dist/commands/core-native-handoff-flag.d.ts +4 -0
  194. package/dist/commands/core-native-handoff-flag.js +7 -0
  195. package/dist/commands/core-native-hook-helper-commands.d.ts +4 -0
  196. package/dist/commands/core-native-hook-helper-commands.js +56 -0
  197. package/dist/commands/core-native-hook-helpers-flag.d.ts +4 -0
  198. package/dist/commands/core-native-hook-helpers-flag.js +7 -0
  199. package/dist/commands/core-native-jobs-flag.d.ts +4 -0
  200. package/dist/commands/core-native-jobs-flag.js +7 -0
  201. package/dist/commands/core-native-policy-flag.d.ts +9 -0
  202. package/dist/commands/core-native-policy-flag.js +12 -0
  203. package/dist/commands/core-native-session-flag.d.ts +4 -0
  204. package/dist/commands/core-native-session-flag.js +7 -0
  205. package/dist/commands/core-native-utilities-flag.d.ts +4 -0
  206. package/dist/commands/core-native-utilities-flag.js +7 -0
  207. package/dist/commands/core-native-utilities.d.ts +29 -0
  208. package/dist/commands/core-native-utilities.js +54 -0
  209. package/dist/commands/core-native-worker-skills-flag.d.ts +4 -0
  210. package/dist/commands/core-native-worker-skills-flag.js +7 -0
  211. package/dist/commands/core-ontology-native.d.ts +17 -0
  212. package/dist/commands/core-ontology-native.js +916 -0
  213. package/dist/commands/core-ontology.d.ts +66 -0
  214. package/dist/commands/core-ontology.js +430 -0
  215. package/dist/commands/core-pin-native.d.ts +14 -0
  216. package/dist/commands/core-pin-native.js +236 -0
  217. package/dist/commands/core-policy-frontmatter.d.ts +8 -0
  218. package/dist/commands/core-policy-frontmatter.js +52 -0
  219. package/dist/commands/core-policy.d.ts +14 -0
  220. package/dist/commands/core-policy.js +1018 -0
  221. package/dist/commands/core-refresh-vault-access-native.d.ts +20 -0
  222. package/dist/commands/core-refresh-vault-access-native.js +229 -0
  223. package/dist/commands/core-register-project-native.d.ts +30 -0
  224. package/dist/commands/core-register-project-native.js +347 -0
  225. package/dist/commands/core-repo-run-registry-native.d.ts +19 -0
  226. package/dist/commands/core-repo-run-registry-native.js +357 -0
  227. package/dist/commands/core-resolve-company-command.d.ts +4 -0
  228. package/dist/commands/core-resolve-company-command.js +42 -0
  229. package/dist/commands/core-resolve-company-native.d.ts +28 -0
  230. package/dist/commands/core-resolve-company-native.js +408 -0
  231. package/dist/commands/core-share-suggestion-state-native.d.ts +18 -0
  232. package/dist/commands/core-share-suggestion-state-native.js +445 -0
  233. package/dist/commands/core-work-mesh-bind-trusted-native.d.ts +32 -0
  234. package/dist/commands/core-work-mesh-bind-trusted-native.js +190 -0
  235. package/dist/commands/core-work-mesh-live-rebind-native.d.ts +18 -0
  236. package/dist/commands/core-work-mesh-live-rebind-native.js +262 -0
  237. package/dist/commands/core-worker-lint-native.d.ts +4 -0
  238. package/dist/commands/core-worker-lint-native.js +160 -0
  239. package/dist/commands/core-worker-share-native.d.ts +4 -0
  240. package/dist/commands/core-worker-share-native.js +260 -0
  241. package/dist/commands/core-worker-skills-command.d.ts +8 -0
  242. package/dist/commands/core-worker-skills-command.js +33 -0
  243. package/dist/commands/core-worker-skills-runtime.d.ts +33 -0
  244. package/dist/commands/core-worker-skills-runtime.js +86 -0
  245. package/dist/commands/core.d.ts +4 -0
  246. package/dist/commands/core.js +282 -80
  247. package/dist/commands/crm.js +52 -3
  248. package/dist/commands/daemon.d.ts +63 -0
  249. package/dist/commands/daemon.js +877 -0
  250. package/dist/commands/db-provision.js +4 -4
  251. package/dist/commands/db-sql.js +7 -21
  252. package/dist/commands/deploy-comments.d.ts +3 -0
  253. package/dist/commands/deploy-comments.js +162 -0
  254. package/dist/commands/dm.d.ts +119 -12
  255. package/dist/commands/dm.js +388 -38
  256. package/dist/commands/doctor.d.ts +12 -0
  257. package/dist/commands/doctor.js +158 -12
  258. package/dist/commands/feedback.d.ts +6 -0
  259. package/dist/commands/feedback.js +86 -37
  260. package/dist/commands/files-browse.d.ts +32 -12
  261. package/dist/commands/files-browse.js +176 -60
  262. package/dist/commands/files.d.ts +17 -0
  263. package/dist/commands/files.js +300 -47
  264. package/dist/commands/group-grants.js +3 -2
  265. package/dist/commands/groups.js +6 -5
  266. package/dist/commands/index-cmd.d.ts +19 -1
  267. package/dist/commands/index-cmd.js +74 -7
  268. package/dist/commands/integrations-api.d.ts +15 -10
  269. package/dist/commands/integrations-api.js +27 -22
  270. package/dist/commands/integrations-connect.d.ts +51 -13
  271. package/dist/commands/integrations-connect.js +86 -71
  272. package/dist/commands/integrations-core.d.ts +6 -6
  273. package/dist/commands/integrations-core.js +25 -11
  274. package/dist/commands/integrations-import.d.ts +1 -1
  275. package/dist/commands/integrations-import.js +43 -24
  276. package/dist/commands/integrations-manage.js +20 -13
  277. package/dist/commands/integrations.d.ts +8 -0
  278. package/dist/commands/integrations.js +94 -7
  279. package/dist/commands/lanes-accounts.d.ts +28 -0
  280. package/dist/commands/lanes-accounts.js +595 -0
  281. package/dist/commands/lanes-autopilot.d.ts +11 -0
  282. package/dist/commands/lanes-autopilot.js +336 -0
  283. package/dist/commands/lanes-checks.d.ts +51 -0
  284. package/dist/commands/lanes-checks.js +941 -0
  285. package/dist/commands/lanes-hold.d.ts +43 -0
  286. package/dist/commands/lanes-hold.js +655 -0
  287. package/dist/commands/lanes-keep-alive-audit.d.ts +10 -0
  288. package/dist/commands/lanes-keep-alive-audit.js +19 -0
  289. package/dist/commands/lanes-kpis.d.ts +11 -0
  290. package/dist/commands/lanes-kpis.js +34 -0
  291. package/dist/commands/lanes-message.d.ts +9 -0
  292. package/dist/commands/lanes-message.js +946 -0
  293. package/dist/commands/lanes-move.d.ts +12 -0
  294. package/dist/commands/lanes-move.js +397 -0
  295. package/dist/commands/lanes-questions.d.ts +36 -0
  296. package/dist/commands/lanes-questions.js +1005 -0
  297. package/dist/commands/lanes-report.d.ts +11 -0
  298. package/dist/commands/lanes-report.js +333 -0
  299. package/dist/commands/lanes-resume.d.ts +6 -0
  300. package/dist/commands/lanes-resume.js +564 -0
  301. package/dist/commands/lanes-review.d.ts +10 -0
  302. package/dist/commands/lanes-review.js +56 -0
  303. package/dist/commands/lanes-shared.d.ts +449 -0
  304. package/dist/commands/lanes-shared.js +170 -0
  305. package/dist/commands/lanes-stories.d.ts +15 -0
  306. package/dist/commands/lanes-stories.js +347 -0
  307. package/dist/commands/lanes-watch.d.ts +7 -0
  308. package/dist/commands/lanes-watch.js +216 -0
  309. package/dist/commands/lanes.d.ts +149 -0
  310. package/dist/commands/lanes.js +9013 -0
  311. package/dist/commands/login.js +49 -6
  312. package/dist/commands/mcp-registration.d.ts +32 -25
  313. package/dist/commands/mcp-registration.js +256 -23
  314. package/dist/commands/mcp-status.js +1 -0
  315. package/dist/commands/meetings.js +55 -34
  316. package/dist/commands/members.js +5 -1
  317. package/dist/commands/mesh.d.ts +11 -0
  318. package/dist/commands/mesh.js +1086 -76
  319. package/dist/commands/monitor.d.ts +58 -0
  320. package/dist/commands/monitor.js +980 -0
  321. package/dist/commands/onboard-bot-membership.d.ts +32 -0
  322. package/dist/commands/onboard-bot-membership.js +40 -0
  323. package/dist/commands/onboard-create-identity.d.ts +26 -0
  324. package/dist/commands/onboard-create-identity.js +23 -0
  325. package/dist/commands/onboard-identity-guard.d.ts +1 -1
  326. package/dist/commands/onboard.js +24 -5
  327. package/dist/commands/outpost-pruner.d.ts +3 -0
  328. package/dist/commands/outpost-pruner.js +125 -0
  329. package/dist/commands/pack-install.js +3 -1
  330. package/dist/commands/packs.js +11 -1
  331. package/dist/commands/people.d.ts +4 -0
  332. package/dist/commands/people.js +37 -3
  333. package/dist/commands/personal-connector-settings-flag.d.ts +4 -0
  334. package/dist/commands/personal-connector-settings-flag.js +7 -0
  335. package/dist/commands/personal-targeted-push-flag.d.ts +4 -0
  336. package/dist/commands/personal-targeted-push-flag.js +7 -0
  337. package/dist/commands/personal-targeted-push-paths.d.ts +3 -0
  338. package/dist/commands/personal-targeted-push-paths.js +67 -0
  339. package/dist/commands/publish.d.ts +8 -1
  340. package/dist/commands/publish.js +17 -4
  341. package/dist/commands/reindex.js +56 -6
  342. package/dist/commands/report.d.ts +19 -0
  343. package/dist/commands/report.js +537 -0
  344. package/dist/commands/rescue.d.ts +4 -0
  345. package/dist/commands/rescue.js +27 -17
  346. package/dist/commands/run-feature-telemetry.d.ts +3 -0
  347. package/dist/commands/run-feature-telemetry.js +6 -0
  348. package/dist/commands/run.js +18 -4
  349. package/dist/commands/safe-extract.d.ts +11 -2
  350. package/dist/commands/safe-extract.js +36 -8
  351. package/dist/commands/secrets.d.ts +20 -2
  352. package/dist/commands/secrets.js +175 -71
  353. package/dist/commands/skill.d.ts +35 -4
  354. package/dist/commands/skill.js +357 -18
  355. package/dist/commands/staff/reset-test-account.d.ts +110 -0
  356. package/dist/commands/staff/reset-test-account.js +268 -0
  357. package/dist/commands/staff/test-account.d.ts +51 -0
  358. package/dist/commands/staff/test-account.js +196 -0
  359. package/dist/commands/sync-mode.d.ts +11 -0
  360. package/dist/commands/sync-mode.js +30 -0
  361. package/dist/commands/sync-narrow.js +36 -5
  362. package/dist/commands/team-sync.js +71 -10
  363. package/dist/commands/update.js +6 -0
  364. package/dist/commands/vault-retention-flag.d.ts +3 -0
  365. package/dist/commands/vault-retention-flag.js +9 -0
  366. package/dist/commands/vault-retention.d.ts +11 -0
  367. package/dist/commands/vault-retention.js +69 -0
  368. package/dist/commands/version.d.ts +10 -0
  369. package/dist/commands/version.js +11 -0
  370. package/dist/commands/whoami.d.ts +22 -0
  371. package/dist/commands/whoami.js +137 -4
  372. package/dist/commands/workers.d.ts +2 -14
  373. package/dist/commands/workers.js +7 -10
  374. package/dist/fast-core.d.ts +7 -7
  375. package/dist/fast-core.js +91 -24
  376. package/dist/fast-scaffold.d.ts +86 -0
  377. package/dist/fast-scaffold.js +381 -0
  378. package/dist/index.d.ts +3 -4
  379. package/dist/index.js +59 -9
  380. package/dist/lanes-drain-hook.d.ts +14 -0
  381. package/dist/lanes-drain-hook.js +203 -0
  382. package/dist/lazy-commands.d.ts +20 -44
  383. package/dist/lazy-commands.js +60 -58
  384. package/dist/lib/agent-kit/adopt-identity.d.ts +27 -0
  385. package/dist/lib/agent-kit/adopt-identity.js +60 -0
  386. package/dist/lib/agent-kit/attachment-download.d.ts +24 -0
  387. package/dist/lib/agent-kit/attachment-download.js +47 -0
  388. package/dist/lib/agent-kit/creds.d.ts +106 -0
  389. package/dist/lib/agent-kit/creds.js +163 -0
  390. package/dist/lib/agent-kit/fallback.d.ts +78 -0
  391. package/dist/lib/agent-kit/fallback.js +210 -0
  392. package/dist/lib/agent-kit/inbox-state.d.ts +83 -0
  393. package/dist/lib/agent-kit/inbox-state.js +159 -0
  394. package/dist/lib/agent-kit/kit-config.d.ts +56 -0
  395. package/dist/lib/agent-kit/kit-config.js +65 -0
  396. package/dist/lib/agent-kit/log.d.ts +16 -0
  397. package/dist/lib/agent-kit/log.js +46 -0
  398. package/dist/lib/agent-kit/mcp/attachments.d.ts +126 -0
  399. package/dist/lib/agent-kit/mcp/attachments.js +222 -0
  400. package/dist/lib/agent-kit/mcp/jsonrpc.d.ts +75 -0
  401. package/dist/lib/agent-kit/mcp/jsonrpc.js +164 -0
  402. package/dist/lib/agent-kit/mcp/tools.d.ts +64 -0
  403. package/dist/lib/agent-kit/mcp/tools.js +397 -0
  404. package/dist/lib/agent-kit/paths.d.ts +65 -0
  405. package/dist/lib/agent-kit/paths.js +118 -0
  406. package/dist/lib/agent-kit/private-file.d.ts +29 -0
  407. package/dist/lib/agent-kit/private-file.js +91 -0
  408. package/dist/lib/agent-kit/run/heartbeat.d.ts +72 -0
  409. package/dist/lib/agent-kit/run/heartbeat.js +128 -0
  410. package/dist/lib/agent-kit/run/inbox.d.ts +76 -0
  411. package/dist/lib/agent-kit/run/inbox.js +205 -0
  412. package/dist/lib/agent-kit/run/supervisor.d.ts +35 -0
  413. package/dist/lib/agent-kit/run/supervisor.js +85 -0
  414. package/dist/lib/agent-kit/run/sync.d.ts +33 -0
  415. package/dist/lib/agent-kit/run/sync.js +58 -0
  416. package/dist/lib/agent-kit/services.d.ts +31 -0
  417. package/dist/lib/agent-kit/services.js +55 -0
  418. package/dist/lib/agent-kit/skills.d.ts +18 -0
  419. package/dist/lib/agent-kit/skills.js +151 -0
  420. package/dist/lib/agent-kit/wake.d.ts +83 -0
  421. package/dist/lib/agent-kit/wake.js +243 -0
  422. package/dist/lib/agents/model-catalog.d.ts +62 -0
  423. package/dist/lib/agents/model-catalog.js +275 -0
  424. package/dist/lib/auth-login-provider-state.d.ts +8 -0
  425. package/dist/lib/auth-login-provider-state.js +63 -0
  426. package/dist/lib/aws-sdk-node-warning-output.d.ts +12 -0
  427. package/dist/lib/aws-sdk-node-warning-output.js +22 -0
  428. package/dist/lib/billing/free-month.d.ts +18 -0
  429. package/dist/lib/billing/free-month.js +31 -0
  430. package/dist/lib/billing/plan-limit-refusals.d.ts +69 -0
  431. package/dist/lib/billing/plan-limit-refusals.js +187 -0
  432. package/dist/lib/billing/plan-limit-upgrade-url.d.ts +11 -0
  433. package/dist/lib/billing/plan-limit-upgrade-url.js +66 -0
  434. package/dist/lib/billing/plan-lock.d.ts +143 -0
  435. package/dist/lib/billing/plan-lock.js +439 -0
  436. package/dist/lib/bot/api.d.ts +349 -0
  437. package/dist/lib/bot/api.js +316 -0
  438. package/dist/lib/bot/attachments.d.ts +45 -0
  439. package/dist/lib/bot/attachments.js +89 -0
  440. package/dist/lib/bot/cli-update.d.ts +138 -0
  441. package/dist/lib/bot/cli-update.js +278 -0
  442. package/dist/lib/bot/company-bind.d.ts +27 -0
  443. package/dist/lib/bot/company-bind.js +62 -0
  444. package/dist/lib/bot/config.d.ts +153 -0
  445. package/dist/lib/bot/config.js +233 -0
  446. package/dist/lib/bot/continuity-download.d.ts +28 -0
  447. package/dist/lib/bot/continuity-download.js +75 -0
  448. package/dist/lib/bot/continuity-install.d.ts +14 -0
  449. package/dist/lib/bot/continuity-install.js +101 -0
  450. package/dist/lib/bot/continuity.d.ts +67 -0
  451. package/dist/lib/bot/continuity.js +301 -0
  452. package/dist/lib/bot/creds.d.ts +24 -0
  453. package/dist/lib/bot/creds.js +51 -0
  454. package/dist/lib/bot/daemon.d.ts +173 -0
  455. package/dist/lib/bot/daemon.js +552 -0
  456. package/dist/lib/bot/inbox-poll-backoff-flag.d.ts +24 -0
  457. package/dist/lib/bot/inbox-poll-backoff-flag.js +135 -0
  458. package/dist/lib/bot/inbox-state.d.ts +18 -0
  459. package/dist/lib/bot/inbox-state.js +51 -0
  460. package/dist/lib/bot/index.d.ts +23 -0
  461. package/dist/lib/bot/index.js +23 -0
  462. package/dist/lib/bot/inflight.d.ts +119 -0
  463. package/dist/lib/bot/inflight.js +83 -0
  464. package/dist/lib/bot/job-completions.d.ts +89 -0
  465. package/dist/lib/bot/job-completions.js +132 -0
  466. package/dist/lib/bot/job-quiet-flag.d.ts +4 -0
  467. package/dist/lib/bot/job-quiet-flag.js +14 -0
  468. package/dist/lib/bot/job-run.d.ts +114 -0
  469. package/dist/lib/bot/job-run.js +266 -0
  470. package/dist/lib/bot/local-config.d.ts +61 -0
  471. package/dist/lib/bot/local-config.js +145 -0
  472. package/dist/lib/bot/local-jobs.d.ts +89 -0
  473. package/dist/lib/bot/local-jobs.js +459 -0
  474. package/dist/lib/bot/local-name.d.ts +54 -0
  475. package/dist/lib/bot/local-name.js +114 -0
  476. package/dist/lib/bot/log.d.ts +13 -0
  477. package/dist/lib/bot/log.js +59 -0
  478. package/dist/lib/bot/owner-context.d.ts +79 -0
  479. package/dist/lib/bot/owner-context.js +175 -0
  480. package/dist/lib/bot/paths.d.ts +76 -0
  481. package/dist/lib/bot/paths.js +131 -0
  482. package/dist/lib/bot/progress.d.ts +132 -0
  483. package/dist/lib/bot/progress.js +289 -0
  484. package/dist/lib/bot/promote.d.ts +16 -0
  485. package/dist/lib/bot/promote.js +106 -0
  486. package/dist/lib/bot/promotion-hold.d.ts +24 -0
  487. package/dist/lib/bot/promotion-hold.js +103 -0
  488. package/dist/lib/bot/promotion-receipt.d.ts +9 -0
  489. package/dist/lib/bot/promotion-receipt.js +56 -0
  490. package/dist/lib/bot/promotion-upload.d.ts +16 -0
  491. package/dist/lib/bot/promotion-upload.js +80 -0
  492. package/dist/lib/bot/prompt.d.ts +136 -0
  493. package/dist/lib/bot/prompt.js +433 -0
  494. package/dist/lib/bot/room-policy.d.ts +63 -0
  495. package/dist/lib/bot/room-policy.js +91 -0
  496. package/dist/lib/bot/run.d.ts +219 -0
  497. package/dist/lib/bot/run.js +1881 -0
  498. package/dist/lib/bot/runnable.d.ts +51 -0
  499. package/dist/lib/bot/runnable.js +65 -0
  500. package/dist/lib/bot/runtime/claude.d.ts +50 -0
  501. package/dist/lib/bot/runtime/claude.js +170 -0
  502. package/dist/lib/bot/runtime/codex.d.ts +28 -0
  503. package/dist/lib/bot/runtime/codex.js +153 -0
  504. package/dist/lib/bot/runtime/grok-acp.d.ts +40 -0
  505. package/dist/lib/bot/runtime/grok-acp.js +265 -0
  506. package/dist/lib/bot/runtime/grok.d.ts +41 -0
  507. package/dist/lib/bot/runtime/grok.js +169 -0
  508. package/dist/lib/bot/runtime/index.d.ts +36 -0
  509. package/dist/lib/bot/runtime/index.js +417 -0
  510. package/dist/lib/bot/runtime/messages-stream.d.ts +56 -0
  511. package/dist/lib/bot/runtime/messages-stream.js +194 -0
  512. package/dist/lib/bot/runtime/sanitize.d.ts +18 -0
  513. package/dist/lib/bot/runtime/sanitize.js +28 -0
  514. package/dist/lib/bot/runtime/types.d.ts +159 -0
  515. package/dist/lib/bot/runtime/types.js +51 -0
  516. package/dist/lib/bot/runtime/windows.d.ts +10 -0
  517. package/dist/lib/bot/runtime/windows.js +53 -0
  518. package/dist/lib/bot/runtime-sign-in.d.ts +24 -0
  519. package/dist/lib/bot/runtime-sign-in.js +31 -0
  520. package/dist/lib/bot/scaffold.d.ts +38 -0
  521. package/dist/lib/bot/scaffold.js +94 -0
  522. package/dist/lib/bot/self-heal.d.ts +52 -0
  523. package/dist/lib/bot/self-heal.js +79 -0
  524. package/dist/lib/bot/self-process.d.ts +19 -0
  525. package/dist/lib/bot/self-process.js +35 -0
  526. package/dist/lib/bot/session.d.ts +19 -0
  527. package/dist/lib/bot/session.js +39 -0
  528. package/dist/lib/bot/setup-explain.d.ts +64 -0
  529. package/dist/lib/bot/setup-explain.js +141 -0
  530. package/dist/lib/bot/split.d.ts +32 -0
  531. package/dist/lib/bot/split.js +241 -0
  532. package/dist/lib/bot/status.d.ts +57 -0
  533. package/dist/lib/bot/status.js +66 -0
  534. package/dist/lib/bot/worker-source.d.ts +66 -0
  535. package/dist/lib/bot/worker-source.js +283 -0
  536. package/dist/lib/checkpoint-ontology/aliases.d.ts +17 -0
  537. package/dist/lib/checkpoint-ontology/aliases.js +44 -0
  538. package/dist/lib/checkpoint-ontology/apply.d.ts +110 -0
  539. package/dist/lib/checkpoint-ontology/apply.js +777 -0
  540. package/dist/lib/checkpoint-ontology/company-brief.d.ts +18 -0
  541. package/dist/lib/checkpoint-ontology/company-brief.js +337 -0
  542. package/dist/lib/checkpoint-ontology/company.d.ts +44 -0
  543. package/dist/lib/checkpoint-ontology/company.js +118 -0
  544. package/dist/lib/checkpoint-ontology/drop-aliases.d.ts +41 -0
  545. package/dist/lib/checkpoint-ontology/drop-aliases.js +76 -0
  546. package/dist/lib/checkpoint-ontology/entity-file.d.ts +109 -0
  547. package/dist/lib/checkpoint-ontology/entity-file.js +371 -0
  548. package/dist/lib/checkpoint-ontology/identity.d.ts +38 -0
  549. package/dist/lib/checkpoint-ontology/identity.js +95 -0
  550. package/dist/lib/checkpoint-ontology/merge-copies.d.ts +199 -0
  551. package/dist/lib/checkpoint-ontology/merge-copies.js +1086 -0
  552. package/dist/lib/checkpoint-ontology/multi-writer.d.ts +101 -0
  553. package/dist/lib/checkpoint-ontology/multi-writer.js +394 -0
  554. package/dist/lib/checkpoint-ontology/plan.d.ts +193 -0
  555. package/dist/lib/checkpoint-ontology/plan.js +504 -0
  556. package/dist/lib/checkpoint-ontology/queued-touches.d.ts +29 -0
  557. package/dist/lib/checkpoint-ontology/queued-touches.js +193 -0
  558. package/dist/lib/checkpoint-ontology/sync-client.d.ts +38 -0
  559. package/dist/lib/checkpoint-ontology/sync-client.js +84 -0
  560. package/dist/lib/checkpoint-ontology/tree.d.ts +55 -0
  561. package/dist/lib/checkpoint-ontology/tree.js +198 -0
  562. package/dist/lib/company-errors-sdk-warning-flag.d.ts +5 -0
  563. package/dist/lib/company-errors-sdk-warning-flag.js +8 -0
  564. package/dist/lib/company-skill-wrapper.d.ts +1 -0
  565. package/dist/lib/company-skill-wrapper.js +121 -29
  566. package/dist/lib/core-utils/codex-skill-bridge.d.ts +2 -1
  567. package/dist/lib/core-utils/common.d.ts +0 -1
  568. package/dist/lib/core-utils/common.js +0 -9
  569. package/dist/lib/core-utils/hook-timeout-debug-context-flag.d.ts +20 -0
  570. package/dist/lib/core-utils/hook-timeout-debug-context-flag.js +81 -0
  571. package/dist/lib/core-utils/jq-json-serializer.d.ts +5 -0
  572. package/dist/lib/core-utils/jq-json-serializer.js +10 -0
  573. package/dist/lib/core-utils/qmd-reindex-after-sync.d.ts +14 -1
  574. package/dist/lib/core-utils/qmd-reindex-after-sync.js +101 -17
  575. package/dist/lib/core-utils/secret-patterns.d.ts +43 -0
  576. package/dist/lib/core-utils/secret-patterns.js +34 -0
  577. package/dist/lib/core-utils/sentry-report.d.ts +114 -0
  578. package/dist/lib/core-utils/sentry-report.js +1055 -0
  579. package/dist/lib/core-utils/soft-timeout.js +1 -1
  580. package/dist/lib/core-utils/timeout-guard-native.d.ts +7 -0
  581. package/dist/lib/core-utils/timeout-guard-native.js +40 -0
  582. package/dist/lib/core-utils/timeout-guard.d.ts +3 -3
  583. package/dist/lib/core-utils/timeout-guard.js +28 -13
  584. package/dist/lib/core-utils/token-usage-report.js +17 -3
  585. package/dist/lib/core-utils/worktree-resume.d.ts +20 -0
  586. package/dist/lib/core-utils/worktree-resume.js +1198 -0
  587. package/dist/lib/daemon/catalog.d.ts +134 -0
  588. package/dist/lib/daemon/catalog.js +358 -0
  589. package/dist/lib/daemon/config.d.ts +35 -0
  590. package/dist/lib/daemon/config.js +108 -0
  591. package/dist/lib/daemon/flag.d.ts +25 -0
  592. package/dist/lib/daemon/flag.js +59 -0
  593. package/dist/lib/daemon/handoff.d.ts +56 -0
  594. package/dist/lib/daemon/handoff.js +161 -0
  595. package/dist/lib/daemon/hq-cloud.d.ts +79 -0
  596. package/dist/lib/daemon/hq-cloud.js +183 -0
  597. package/dist/lib/daemon/install.d.ts +85 -0
  598. package/dist/lib/daemon/install.js +320 -0
  599. package/dist/lib/daemon/legacy-sync-env.d.ts +10 -0
  600. package/dist/lib/daemon/legacy-sync-env.js +58 -0
  601. package/dist/lib/daemon/legacy.d.ts +45 -0
  602. package/dist/lib/daemon/legacy.js +133 -0
  603. package/dist/lib/daemon/lock.d.ts +47 -0
  604. package/dist/lib/daemon/lock.js +138 -0
  605. package/dist/lib/daemon/machine-type.d.ts +50 -0
  606. package/dist/lib/daemon/machine-type.js +159 -0
  607. package/dist/lib/daemon/migrate.d.ts +77 -0
  608. package/dist/lib/daemon/migrate.js +265 -0
  609. package/dist/lib/daemon/paths.d.ts +24 -0
  610. package/dist/lib/daemon/paths.js +43 -0
  611. package/dist/lib/daemon/protocol-tail.d.ts +33 -0
  612. package/dist/lib/daemon/protocol-tail.js +131 -0
  613. package/dist/lib/daemon/remote-control.d.ts +74 -0
  614. package/dist/lib/daemon/remote-control.js +153 -0
  615. package/dist/lib/daemon/requests.d.ts +28 -0
  616. package/dist/lib/daemon/requests.js +65 -0
  617. package/dist/lib/daemon/run.d.ts +79 -0
  618. package/dist/lib/daemon/run.js +755 -0
  619. package/dist/lib/daemon/succession.d.ts +118 -0
  620. package/dist/lib/daemon/succession.js +263 -0
  621. package/dist/lib/daemon/supervisor.d.ts +132 -0
  622. package/dist/lib/daemon/supervisor.js +836 -0
  623. package/dist/lib/daemon/sync-runner.d.ts +58 -0
  624. package/dist/lib/daemon/sync-runner.js +277 -0
  625. package/dist/lib/daemon/tools.d.ts +52 -0
  626. package/dist/lib/daemon/tools.js +95 -0
  627. package/dist/lib/daemon/updater.d.ts +57 -0
  628. package/dist/lib/daemon/updater.js +163 -0
  629. package/dist/lib/db/control-plane.js +6 -0
  630. package/dist/lib/db/local.d.ts +0 -5
  631. package/dist/lib/db/local.js +1 -1
  632. package/dist/lib/db/migrate.d.ts +0 -12
  633. package/dist/lib/db/migrate.js +5 -5
  634. package/dist/lib/db/remote-sql.d.ts +2 -2
  635. package/dist/lib/db/remote-sql.js +3 -3
  636. package/dist/lib/db/sql.d.ts +0 -10
  637. package/dist/lib/db/sql.js +4 -8
  638. package/dist/lib/deploy-api.d.ts +5 -0
  639. package/dist/lib/deploy-api.js +21 -0
  640. package/dist/lib/deploy-apps.d.ts +13 -0
  641. package/dist/lib/deploy-apps.js +29 -0
  642. package/dist/lib/doctor/__testing__/fake-hq-tree.d.ts +1 -1
  643. package/dist/lib/doctor/__testing__/fake-hq-tree.js +1 -1
  644. package/dist/lib/doctor/__testing__/run-doctor.d.ts +8 -0
  645. package/dist/lib/doctor/__testing__/run-doctor.js +52 -0
  646. package/dist/lib/doctor/checks/bots.d.ts +13 -0
  647. package/dist/lib/doctor/checks/bots.js +189 -0
  648. package/dist/lib/doctor/checks/claude-wiring.js +70 -1
  649. package/dist/lib/doctor/checks/codex-wiring.d.ts +0 -4
  650. package/dist/lib/doctor/checks/codex-wiring.js +3 -35
  651. package/dist/lib/doctor/checks/grok-wiring.js +1 -34
  652. package/dist/lib/doctor/checks/integrations.js +11 -0
  653. package/dist/lib/doctor/checks/runtime-health.d.ts +4 -17
  654. package/dist/lib/doctor/checks/runtime-health.js +20 -7
  655. package/dist/lib/doctor/checks/scaffold-cli-floor.d.ts +6 -0
  656. package/dist/lib/doctor/checks/scaffold-cli-floor.js +73 -0
  657. package/dist/lib/doctor/checks/sync-health.d.ts +21 -0
  658. package/dist/lib/doctor/checks/sync-health.js +72 -2
  659. package/dist/lib/doctor/checks/sync-runtime.d.ts +11 -0
  660. package/dist/lib/doctor/checks/sync-runtime.js +216 -0
  661. package/dist/lib/doctor/checks/work-context.js +20 -6
  662. package/dist/lib/doctor/client-health-flag-token.d.ts +15 -0
  663. package/dist/lib/doctor/client-health-flag-token.js +66 -0
  664. package/dist/lib/doctor/client-health-reason-codes-flag.d.ts +27 -0
  665. package/dist/lib/doctor/client-health-reason-codes-flag.js +106 -0
  666. package/dist/lib/doctor/compat.d.ts +0 -16
  667. package/dist/lib/doctor/compat.js +4 -3
  668. package/dist/lib/doctor/deep/effects.js +1 -3
  669. package/dist/lib/doctor/deep/executor.d.ts +4 -10
  670. package/dist/lib/doctor/deep/executor.js +1 -1
  671. package/dist/lib/doctor/deep/parity.d.ts +2 -29
  672. package/dist/lib/doctor/deep/parity.js +7 -7
  673. package/dist/lib/doctor/deep/sandbox.d.ts +7 -25
  674. package/dist/lib/doctor/deep/sandbox.js +5 -7
  675. package/dist/lib/doctor/fix/apply.d.ts +79 -8
  676. package/dist/lib/doctor/fix/apply.js +292 -80
  677. package/dist/lib/doctor/fix/json-output.d.ts +28 -0
  678. package/dist/lib/doctor/fix/json-output.js +33 -0
  679. package/dist/lib/doctor/fix/refresh-sync-outcome.d.ts +12 -0
  680. package/dist/lib/doctor/fix/refresh-sync-outcome.js +25 -0
  681. package/dist/lib/doctor/fix/remediation.d.ts +14 -6
  682. package/dist/lib/doctor/fix/remediation.js +49 -9
  683. package/dist/lib/doctor/fixtures/schema.js +1 -3
  684. package/dist/lib/doctor/json-output.d.ts +17 -1
  685. package/dist/lib/doctor/json-output.js +7 -2
  686. package/dist/lib/doctor/platform.d.ts +0 -26
  687. package/dist/lib/doctor/platform.js +5 -5
  688. package/dist/lib/doctor/registry.d.ts +14 -0
  689. package/dist/lib/doctor/registry.js +71 -0
  690. package/dist/lib/doctor/report.js +1 -1
  691. package/dist/lib/doctor/shared.d.ts +8 -0
  692. package/dist/lib/doctor/shared.js +38 -0
  693. package/dist/lib/doctor/stray-gate-entries.d.ts +35 -0
  694. package/dist/lib/doctor/stray-gate-entries.js +83 -0
  695. package/dist/lib/doctor/types.d.ts +5 -0
  696. package/dist/lib/doctor/types.js +20 -0
  697. package/dist/lib/first-sync-next-step.d.ts +34 -0
  698. package/dist/lib/first-sync-next-step.js +179 -0
  699. package/dist/lib/flag-registry-endpoint.d.ts +5 -0
  700. package/dist/lib/flag-registry-endpoint.js +9 -0
  701. package/dist/lib/flag-registry-worker-source.generated.d.ts +2 -0
  702. package/dist/lib/flag-registry-worker-source.generated.js +3 -0
  703. package/dist/lib/flag-registry-worker.js +12 -1
  704. package/dist/lib/flag-registry.d.ts +26 -6
  705. package/dist/lib/flag-registry.js +133 -16
  706. package/dist/lib/flag-snapshot-file.d.ts +20 -0
  707. package/dist/lib/flag-snapshot-file.js +91 -0
  708. package/dist/lib/heartbeat-identity.d.ts +30 -0
  709. package/dist/lib/heartbeat-identity.js +76 -0
  710. package/dist/lib/index-render/companies.d.ts +4 -1
  711. package/dist/lib/index-render/companies.js +66 -3
  712. package/dist/lib/index-render/company-knowledge.js +80 -17
  713. package/dist/lib/index-render/index.d.ts +1 -0
  714. package/dist/lib/index-render/index.js +2 -1
  715. package/dist/lib/index-render/projects.js +82 -5
  716. package/dist/lib/index-render/shared.d.ts +13 -1
  717. package/dist/lib/index-render/shared.js +41 -2
  718. package/dist/lib/lanes/account-home.d.ts +8 -0
  719. package/dist/lib/lanes/account-home.js +10 -0
  720. package/dist/lib/lanes/accounts.d.ts +117 -0
  721. package/dist/lib/lanes/accounts.js +366 -0
  722. package/dist/lib/lanes/admission-host.d.ts +22 -0
  723. package/dist/lib/lanes/admission-host.js +37 -0
  724. package/dist/lib/lanes/admission.d.ts +26 -0
  725. package/dist/lib/lanes/admission.js +210 -0
  726. package/dist/lib/lanes/approval-gate.d.ts +8 -0
  727. package/dist/lib/lanes/approval-gate.js +90 -0
  728. package/dist/lib/lanes/autopilot-metrics.d.ts +99 -0
  729. package/dist/lib/lanes/autopilot-metrics.js +301 -0
  730. package/dist/lib/lanes/autopilot-registry.d.ts +62 -0
  731. package/dist/lib/lanes/autopilot-registry.js +192 -0
  732. package/dist/lib/lanes/autopilot.d.ts +137 -0
  733. package/dist/lib/lanes/autopilot.js +1291 -0
  734. package/dist/lib/lanes/blocker-freshness.d.ts +31 -0
  735. package/dist/lib/lanes/blocker-freshness.js +168 -0
  736. package/dist/lib/lanes/checked-company-path.d.ts +3 -0
  737. package/dist/lib/lanes/checked-company-path.js +39 -0
  738. package/dist/lib/lanes/claude-balance.d.ts +97 -0
  739. package/dist/lib/lanes/claude-balance.js +319 -0
  740. package/dist/lib/lanes/compaction.d.ts +110 -0
  741. package/dist/lib/lanes/compaction.js +293 -0
  742. package/dist/lib/lanes/config.d.ts +94 -0
  743. package/dist/lib/lanes/config.js +606 -0
  744. package/dist/lib/lanes/constants.d.ts +3 -0
  745. package/dist/lib/lanes/constants.js +3 -0
  746. package/dist/lib/lanes/delivery-core.d.ts +15 -0
  747. package/dist/lib/lanes/delivery-core.js +21 -0
  748. package/dist/lib/lanes/delivery.d.ts +180 -0
  749. package/dist/lib/lanes/delivery.js +443 -0
  750. package/dist/lib/lanes/disk-guard.d.ts +65 -0
  751. package/dist/lib/lanes/disk-guard.js +172 -0
  752. package/dist/lib/lanes/dropbox.d.ts +129 -0
  753. package/dist/lib/lanes/dropbox.js +601 -0
  754. package/dist/lib/lanes/effort.d.ts +51 -0
  755. package/dist/lib/lanes/effort.js +113 -0
  756. package/dist/lib/lanes/envelope-repair-dispatch.d.ts +26 -0
  757. package/dist/lib/lanes/envelope-repair-dispatch.js +74 -0
  758. package/dist/lib/lanes/errors.d.ts +39 -0
  759. package/dist/lib/lanes/errors.js +72 -0
  760. package/dist/lib/lanes/flag-keys.d.ts +25 -0
  761. package/dist/lib/lanes/flag-keys.js +25 -0
  762. package/dist/lib/lanes/grants.d.ts +30 -0
  763. package/dist/lib/lanes/grants.js +171 -0
  764. package/dist/lib/lanes/hire-grant-dispatch.d.ts +29 -0
  765. package/dist/lib/lanes/hire-grant-dispatch.js +315 -0
  766. package/dist/lib/lanes/hooks-install.d.ts +82 -0
  767. package/dist/lib/lanes/hooks-install.js +555 -0
  768. package/dist/lib/lanes/host-capacity.d.ts +69 -0
  769. package/dist/lib/lanes/host-capacity.js +254 -0
  770. package/dist/lib/lanes/hq-root.d.ts +19 -0
  771. package/dist/lib/lanes/hq-root.js +28 -0
  772. package/dist/lib/lanes/index.d.ts +76 -0
  773. package/dist/lib/lanes/index.js +42 -0
  774. package/dist/lib/lanes/keep-alive-audit.d.ts +47 -0
  775. package/dist/lib/lanes/keep-alive-audit.js +370 -0
  776. package/dist/lib/lanes/keep-alive.d.ts +68 -0
  777. package/dist/lib/lanes/keep-alive.js +343 -0
  778. package/dist/lib/lanes/kr-freshness.d.ts +76 -0
  779. package/dist/lib/lanes/kr-freshness.js +413 -0
  780. package/dist/lib/lanes/lane-company.d.ts +10 -0
  781. package/dist/lib/lanes/lane-company.js +41 -0
  782. package/dist/lib/lanes/liveness.d.ts +108 -0
  783. package/dist/lib/lanes/liveness.js +396 -0
  784. package/dist/lib/lanes/machine-identity.d.ts +34 -0
  785. package/dist/lib/lanes/machine-identity.js +92 -0
  786. package/dist/lib/lanes/manager-status-gate.d.ts +17 -0
  787. package/dist/lib/lanes/manager-status-gate.js +133 -0
  788. package/dist/lib/lanes/merge-command.d.ts +15 -0
  789. package/dist/lib/lanes/merge-command.js +629 -0
  790. package/dist/lib/lanes/mesh-events.d.ts +127 -0
  791. package/dist/lib/lanes/mesh-events.js +564 -0
  792. package/dist/lib/lanes/message-replay.d.ts +21 -0
  793. package/dist/lib/lanes/message-replay.js +122 -0
  794. package/dist/lib/lanes/models.d.ts +86 -0
  795. package/dist/lib/lanes/models.js +234 -0
  796. package/dist/lib/lanes/monitor.d.ts +48 -0
  797. package/dist/lib/lanes/monitor.js +428 -0
  798. package/dist/lib/lanes/move-account.d.ts +56 -0
  799. package/dist/lib/lanes/move-account.js +193 -0
  800. package/dist/lib/lanes/orphans.d.ts +25 -0
  801. package/dist/lib/lanes/orphans.js +102 -0
  802. package/dist/lib/lanes/outpost-dispatch.d.ts +156 -0
  803. package/dist/lib/lanes/outpost-dispatch.js +293 -0
  804. package/dist/lib/lanes/outpost-hire-cache.d.ts +22 -0
  805. package/dist/lib/lanes/outpost-hire-cache.js +153 -0
  806. package/dist/lib/lanes/outpost-identity.d.ts +33 -0
  807. package/dist/lib/lanes/outpost-identity.js +59 -0
  808. package/dist/lib/lanes/outpost-profile-repin.d.ts +17 -0
  809. package/dist/lib/lanes/outpost-profile-repin.js +84 -0
  810. package/dist/lib/lanes/outpost-pruner-install.d.ts +37 -0
  811. package/dist/lib/lanes/outpost-pruner-install.js +238 -0
  812. package/dist/lib/lanes/outpost-registry-worker-approval.d.ts +18 -0
  813. package/dist/lib/lanes/outpost-registry-worker-approval.js +109 -0
  814. package/dist/lib/lanes/outpost-snapshot-memo.d.ts +6 -0
  815. package/dist/lib/lanes/outpost-snapshot-memo.js +14 -0
  816. package/dist/lib/lanes/outpost-worktree-pruner-flag.d.ts +19 -0
  817. package/dist/lib/lanes/outpost-worktree-pruner-flag.js +86 -0
  818. package/dist/lib/lanes/outpost-worktree-pruner.d.ts +62 -0
  819. package/dist/lib/lanes/outpost-worktree-pruner.js +1201 -0
  820. package/dist/lib/lanes/profile.d.ts +69 -0
  821. package/dist/lib/lanes/profile.js +552 -0
  822. package/dist/lib/lanes/provider-rejection.d.ts +86 -0
  823. package/dist/lib/lanes/provider-rejection.js +286 -0
  824. package/dist/lib/lanes/questions.d.ts +90 -0
  825. package/dist/lib/lanes/questions.js +384 -0
  826. package/dist/lib/lanes/quota.d.ts +134 -0
  827. package/dist/lib/lanes/quota.js +471 -0
  828. package/dist/lib/lanes/reconcile-install.d.ts +90 -0
  829. package/dist/lib/lanes/reconcile-install.js +257 -0
  830. package/dist/lib/lanes/reconcile.d.ts +161 -0
  831. package/dist/lib/lanes/reconcile.js +3343 -0
  832. package/dist/lib/lanes/registry-flag.d.ts +35 -0
  833. package/dist/lib/lanes/registry-flag.js +164 -0
  834. package/dist/lib/lanes/registry-persona.d.ts +20 -0
  835. package/dist/lib/lanes/registry-persona.js +329 -0
  836. package/dist/lib/lanes/registry.d.ts +122 -0
  837. package/dist/lib/lanes/registry.js +570 -0
  838. package/dist/lib/lanes/reviews.d.ts +38 -0
  839. package/dist/lib/lanes/reviews.js +395 -0
  840. package/dist/lib/lanes/runaway-tool-guard.d.ts +38 -0
  841. package/dist/lib/lanes/runaway-tool-guard.js +284 -0
  842. package/dist/lib/lanes/session-relink.d.ts +25 -0
  843. package/dist/lib/lanes/session-relink.js +216 -0
  844. package/dist/lib/lanes/spawn.d.ts +355 -0
  845. package/dist/lib/lanes/spawn.js +2668 -0
  846. package/dist/lib/lanes/ssm-command-context.d.ts +12 -0
  847. package/dist/lib/lanes/ssm-command-context.js +52 -0
  848. package/dist/lib/lanes/store.d.ts +91 -0
  849. package/dist/lib/lanes/store.js +1822 -0
  850. package/dist/lib/lanes/stories.d.ts +23 -0
  851. package/dist/lib/lanes/stories.js +92 -0
  852. package/dist/lib/lanes/story-ledger.d.ts +80 -0
  853. package/dist/lib/lanes/story-ledger.js +815 -0
  854. package/dist/lib/lanes/story-switch.d.ts +21 -0
  855. package/dist/lib/lanes/story-switch.js +75 -0
  856. package/dist/lib/lanes/text-scan.d.ts +11 -0
  857. package/dist/lib/lanes/text-scan.js +103 -0
  858. package/dist/lib/lanes/throttle.d.ts +116 -0
  859. package/dist/lib/lanes/throttle.js +437 -0
  860. package/dist/lib/lanes/timestamp.d.ts +3 -0
  861. package/dist/lib/lanes/timestamp.js +26 -0
  862. package/dist/lib/lanes/tracker-checks.d.ts +77 -0
  863. package/dist/lib/lanes/tracker-checks.js +134 -0
  864. package/dist/lib/lanes/tracker-report-state.d.ts +10 -0
  865. package/dist/lib/lanes/tracker-report-state.js +124 -0
  866. package/dist/lib/lanes/tracker-report.d.ts +203 -0
  867. package/dist/lib/lanes/tracker-report.js +522 -0
  868. package/dist/lib/lanes/tracker-store.d.ts +18 -0
  869. package/dist/lib/lanes/tracker-store.js +106 -0
  870. package/dist/lib/lanes/types.d.ts +1030 -0
  871. package/dist/lib/lanes/types.js +418 -0
  872. package/dist/lib/lanes/wait.d.ts +39 -0
  873. package/dist/lib/lanes/wait.js +159 -0
  874. package/dist/lib/lanes/watch.d.ts +39 -0
  875. package/dist/lib/lanes/watch.js +249 -0
  876. package/dist/lib/lanes/watcher-main.d.ts +11 -0
  877. package/dist/lib/lanes/watcher-main.js +108 -0
  878. package/dist/lib/lanes/watcher.d.ts +287 -0
  879. package/dist/lib/lanes/watcher.js +1972 -0
  880. package/dist/lib/mesh/api.d.ts +103 -10
  881. package/dist/lib/mesh/api.js +334 -64
  882. package/dist/lib/mesh/client.d.ts +3 -2
  883. package/dist/lib/mesh/client.js +26 -7
  884. package/dist/lib/mesh/denial-codes.d.ts +5 -0
  885. package/dist/lib/mesh/denial-codes.js +14 -0
  886. package/dist/lib/mesh/live/backfill-held.d.ts +67 -16
  887. package/dist/lib/mesh/live/backfill-held.js +415 -61
  888. package/dist/lib/mesh/live/backoff.d.ts +2 -2
  889. package/dist/lib/mesh/live/backoff.js +16 -3
  890. package/dist/lib/mesh/live/daemon/board-refresh.d.ts +7 -1
  891. package/dist/lib/mesh/live/daemon/board-refresh.js +78 -30
  892. package/dist/lib/mesh/live/daemon/config.d.ts +36 -0
  893. package/dist/lib/mesh/live/daemon/config.js +120 -0
  894. package/dist/lib/mesh/live/daemon/credentials.d.ts +64 -10
  895. package/dist/lib/mesh/live/daemon/credentials.js +270 -20
  896. package/dist/lib/mesh/live/daemon/doctor.d.ts +53 -0
  897. package/dist/lib/mesh/live/daemon/doctor.js +190 -6
  898. package/dist/lib/mesh/live/daemon/index.d.ts +13 -10
  899. package/dist/lib/mesh/live/daemon/index.js +7 -6
  900. package/dist/lib/mesh/live/daemon/install.d.ts +255 -1
  901. package/dist/lib/mesh/live/daemon/install.js +811 -13
  902. package/dist/lib/mesh/live/daemon/legacy-listen.d.ts +43 -0
  903. package/dist/lib/mesh/live/daemon/legacy-listen.js +89 -0
  904. package/dist/lib/mesh/live/daemon/live-cache.d.ts +26 -0
  905. package/dist/lib/mesh/live/daemon/live-cache.js +116 -0
  906. package/dist/lib/mesh/live/daemon/mqtt-events.d.ts +7 -0
  907. package/dist/lib/mesh/live/daemon/mqtt-events.js +2 -0
  908. package/dist/lib/mesh/live/daemon/mqtt-events.test-d.d.ts +2 -0
  909. package/dist/lib/mesh/live/daemon/mqtt-events.test-d.js +10 -0
  910. package/dist/lib/mesh/live/daemon/paths.d.ts +2 -0
  911. package/dist/lib/mesh/live/daemon/paths.js +2 -0
  912. package/dist/lib/mesh/live/daemon/presence.d.ts +24 -5
  913. package/dist/lib/mesh/live/daemon/presence.js +79 -10
  914. package/dist/lib/mesh/live/daemon/run.d.ts +48 -2
  915. package/dist/lib/mesh/live/daemon/run.js +485 -33
  916. package/dist/lib/mesh/live/daemon/state.d.ts +19 -2
  917. package/dist/lib/mesh/live/daemon/state.js +34 -2
  918. package/dist/lib/mesh/live/daemon/transcript-watch.d.ts +45 -9
  919. package/dist/lib/mesh/live/daemon/transcript-watch.js +227 -31
  920. package/dist/lib/mesh/live/daemon/version-drift.d.ts +5 -0
  921. package/dist/lib/mesh/live/daemon/version-drift.js +47 -0
  922. package/dist/lib/mesh/live/daemon/work-subscribe.d.ts +32 -0
  923. package/dist/lib/mesh/live/daemon/work-subscribe.js +262 -0
  924. package/dist/lib/mesh/live/emit-client.d.ts +8 -0
  925. package/dist/lib/mesh/live/emit-client.js +23 -8
  926. package/dist/lib/mesh/live/emit-evidence.d.ts +19 -0
  927. package/dist/lib/mesh/live/emit-evidence.js +20 -2
  928. package/dist/lib/mesh/live/emit.d.ts +9 -0
  929. package/dist/lib/mesh/live/emit.js +28 -2
  930. package/dist/lib/mesh/live/enqueue.d.ts +5 -0
  931. package/dist/lib/mesh/live/enqueue.js +19 -0
  932. package/dist/lib/mesh/live/flush.d.ts +17 -5
  933. package/dist/lib/mesh/live/flush.js +391 -38
  934. package/dist/lib/mesh/live/format-spool-line.d.ts +5 -2
  935. package/dist/lib/mesh/live/format-spool-line.js +10 -0
  936. package/dist/lib/mesh/live/paths.d.ts +2 -0
  937. package/dist/lib/mesh/live/paths.js +4 -0
  938. package/dist/lib/mesh/live/report-summary.d.ts +45 -0
  939. package/dist/lib/mesh/live/report-summary.js +234 -0
  940. package/dist/lib/mesh/live/session-event.schema.json +24 -1
  941. package/dist/lib/mesh/live/session-events-client.d.ts +2 -0
  942. package/dist/lib/mesh/live/session-events-client.js +26 -0
  943. package/dist/lib/mesh/live/session-seq.d.ts +4 -0
  944. package/dist/lib/mesh/live/session-seq.js +33 -0
  945. package/dist/lib/mesh/live/spool.d.ts +19 -0
  946. package/dist/lib/mesh/live/spool.js +269 -3
  947. package/dist/lib/mesh/live/validate-session-event.d.ts +1 -1
  948. package/dist/lib/mesh/live/validate-session-event.js +1 -1
  949. package/dist/lib/mesh/project-board.d.ts +65 -0
  950. package/dist/lib/mesh/project-board.js +181 -0
  951. package/dist/lib/mesh/story-status.d.ts +22 -0
  952. package/dist/lib/mesh/story-status.js +49 -0
  953. package/dist/lib/monitor.d.ts +149 -0
  954. package/dist/lib/monitor.js +519 -0
  955. package/dist/lib/node-binary.d.ts +50 -0
  956. package/dist/lib/node-binary.js +133 -0
  957. package/dist/lib/notify-recipient.d.ts +7 -0
  958. package/dist/lib/notify-recipient.js +11 -0
  959. package/dist/lib/onboarding/checkpoint.d.ts +13 -0
  960. package/dist/lib/onboarding/checkpoint.js +44 -0
  961. package/dist/lib/onboarding/cli/onboard.d.ts +53 -0
  962. package/dist/lib/onboarding/cli/onboard.js +135 -0
  963. package/dist/lib/onboarding/cli/prompts.d.ts +28 -0
  964. package/dist/lib/onboarding/cli/prompts.js +83 -0
  965. package/dist/lib/onboarding/errors.d.ts +34 -0
  966. package/dist/lib/onboarding/errors.js +59 -0
  967. package/dist/lib/onboarding/index.d.ts +13 -0
  968. package/dist/lib/onboarding/index.js +13 -0
  969. package/dist/lib/onboarding/orchestrator.d.ts +47 -0
  970. package/dist/lib/onboarding/orchestrator.js +569 -0
  971. package/dist/lib/onboarding/types.d.ts +79 -0
  972. package/dist/lib/onboarding/types.js +8 -0
  973. package/dist/lib/plan-limit-nag.d.ts +102 -6
  974. package/dist/lib/plan-limit-nag.js +348 -43
  975. package/dist/lib/policy-validator.d.ts +7 -0
  976. package/dist/lib/policy-validator.js +187 -0
  977. package/dist/lib/process-flag-company-context.d.ts +8 -0
  978. package/dist/lib/process-flag-company-context.js +67 -0
  979. package/dist/lib/report-publish.d.ts +175 -0
  980. package/dist/lib/report-publish.js +421 -0
  981. package/dist/lib/report-run.d.ts +62 -0
  982. package/dist/lib/report-run.js +123 -0
  983. package/dist/lib/report-snapshot.d.ts +12 -0
  984. package/dist/lib/report-snapshot.js +135 -0
  985. package/dist/lib/report-templates.d.ts +9 -0
  986. package/dist/lib/report-templates.js +21 -0
  987. package/dist/lib/scaffold-cli-floor.d.ts +8 -0
  988. package/dist/lib/scaffold-cli-floor.js +50 -0
  989. package/dist/lib/scan-packages/index.d.ts +2 -0
  990. package/dist/lib/scan-packages/index.js +168 -6
  991. package/dist/lib/search-index/background.d.ts +8 -1
  992. package/dist/lib/search-index/background.js +55 -200
  993. package/dist/lib/search-index/collection-path-repair-flag.d.ts +4 -0
  994. package/dist/lib/search-index/collection-path-repair-flag.js +7 -0
  995. package/dist/lib/search-index/embed-lock.d.ts +34 -0
  996. package/dist/lib/search-index/embed-lock.js +295 -0
  997. package/dist/lib/search-index/index.d.ts +12 -0
  998. package/dist/lib/search-index/index.js +153 -7
  999. package/dist/lib/search-index/load-gate.d.ts +57 -0
  1000. package/dist/lib/search-index/load-gate.js +270 -0
  1001. package/dist/lib/search-index/max-doc-bytes.d.ts +219 -0
  1002. package/dist/lib/search-index/max-doc-bytes.js +463 -0
  1003. package/dist/lib/search-index/qmd-exit-diagnostics.d.ts +5 -0
  1004. package/dist/lib/search-index/qmd-exit-diagnostics.js +40 -0
  1005. package/dist/lib/search-index/reindex-single-flight-flag.d.ts +5 -0
  1006. package/dist/lib/search-index/reindex-single-flight-flag.js +8 -0
  1007. package/dist/lib/service-manager/index.d.ts +42 -0
  1008. package/dist/lib/service-manager/index.js +105 -0
  1009. package/dist/lib/service-manager/launchd.d.ts +81 -0
  1010. package/dist/lib/service-manager/launchd.js +212 -0
  1011. package/dist/lib/service-manager/systemd.d.ts +19 -0
  1012. package/dist/lib/service-manager/systemd.js +72 -0
  1013. package/dist/lib/service-manager/types.d.ts +34 -0
  1014. package/dist/lib/service-manager/types.js +26 -0
  1015. package/dist/lib/test-utils/isolated-flag-company-context.d.ts +3 -0
  1016. package/dist/lib/test-utils/isolated-flag-company-context.js +18 -0
  1017. package/dist/lib/work-context/bind-best.d.ts +66 -0
  1018. package/dist/lib/work-context/bind-best.js +344 -0
  1019. package/dist/lib/work-context/bind-calling-session.d.ts +26 -0
  1020. package/dist/lib/work-context/bind-calling-session.js +63 -0
  1021. package/dist/lib/work-context/company-rebind.d.ts +23 -0
  1022. package/dist/lib/work-context/company-rebind.js +59 -0
  1023. package/dist/lib/work-context/company.d.ts +22 -20
  1024. package/dist/lib/work-context/company.js +124 -40
  1025. package/dist/lib/work-context/config.d.ts +58 -11
  1026. package/dist/lib/work-context/config.js +334 -83
  1027. package/dist/lib/work-context/contract.d.ts +13 -0
  1028. package/dist/lib/work-context/contract.schema.json +3 -1
  1029. package/dist/lib/work-context/default-company.d.ts +43 -0
  1030. package/dist/lib/work-context/default-company.js +83 -0
  1031. package/dist/lib/work-context/outbox-quarantine-status-flag.d.ts +4 -0
  1032. package/dist/lib/work-context/outbox-quarantine-status-flag.js +7 -0
  1033. package/dist/lib/work-context/outbox.d.ts +27 -5
  1034. package/dist/lib/work-context/outbox.js +135 -6
  1035. package/dist/lib/work-context/prd-sync.d.ts +56 -0
  1036. package/dist/lib/work-context/prd-sync.js +197 -0
  1037. package/dist/lib/work-context/preflight-contract.d.ts +28 -0
  1038. package/dist/lib/work-context/preflight-contract.js +85 -0
  1039. package/dist/lib/work-context/prompt-rebind.d.ts +25 -0
  1040. package/dist/lib/work-context/prompt-rebind.js +85 -0
  1041. package/dist/lib/work-context/reconcile.d.ts +15 -0
  1042. package/dist/lib/work-context/reconcile.js +405 -29
  1043. package/dist/lib/work-context/state.d.ts +93 -10
  1044. package/dist/lib/work-context/state.js +342 -29
  1045. package/dist/lib/work-context/windows-acl.d.ts +123 -0
  1046. package/dist/lib/work-context/windows-acl.js +425 -0
  1047. package/dist/lib/workers-registry/index.js +62 -8
  1048. package/dist/lib/workers-registry/read.d.ts +15 -0
  1049. package/dist/lib/workers-registry/read.js +17 -0
  1050. package/dist/main.d.ts +32 -0
  1051. package/dist/main.js +273 -73
  1052. package/dist/native-fast-route.d.ts +7 -0
  1053. package/dist/native-fast-route.js +45 -0
  1054. package/dist/node-network-compat-core.d.ts +12 -0
  1055. package/dist/node-network-compat-core.js +41 -0
  1056. package/dist/node-network-compat-fast.d.ts +7 -0
  1057. package/dist/node-network-compat-fast.js +23 -0
  1058. package/dist/node-network-compat.d.ts +2 -30
  1059. package/dist/node-network-compat.js +4 -51
  1060. package/dist/node-preflight.d.ts +7 -1
  1061. package/dist/node-preflight.js +5 -5
  1062. package/dist/register-all.d.ts +4 -29
  1063. package/dist/register-all.js +4 -233
  1064. package/dist/run/discover-schemas.d.ts +2 -1
  1065. package/dist/run/env-graph-guard.d.ts +2 -2
  1066. package/dist/run/env-graph-guard.js +5 -2
  1067. package/dist/run/hq-plugin.d.ts +4 -0
  1068. package/dist/run/hq-plugin.js +36 -8
  1069. package/dist/secrets-env-fast-path.d.ts +5 -0
  1070. package/dist/secrets-env-fast-path.js +16 -0
  1071. package/dist/secrets-env-fast-route.d.ts +18 -0
  1072. package/dist/secrets-env-fast-route.js +54 -0
  1073. package/dist/sentry-before-send.js +80 -0
  1074. package/dist/sentry.d.ts +16 -4
  1075. package/dist/sentry.js +179 -18
  1076. package/dist/startup-registration.d.ts +26 -2
  1077. package/dist/startup-registration.js +116 -15
  1078. package/dist/utils/checkpoint-stamp-write-error.d.ts +28 -0
  1079. package/dist/utils/checkpoint-stamp-write-error.js +99 -0
  1080. package/dist/utils/cli-network-lifecycle.d.ts +4 -0
  1081. package/dist/utils/cli-network-lifecycle.js +11 -0
  1082. package/dist/utils/cli-telemetry.d.ts +75 -1
  1083. package/dist/utils/cli-telemetry.js +433 -7
  1084. package/dist/utils/client-health.d.ts +0 -4
  1085. package/dist/utils/client-health.js +2 -2
  1086. package/dist/utils/cognito-refresh-sentry.d.ts +3 -0
  1087. package/dist/utils/cognito-refresh-sentry.js +49 -0
  1088. package/dist/utils/cognito-session.d.ts +71 -0
  1089. package/dist/utils/cognito-session.js +481 -27
  1090. package/dist/utils/console-origin.d.ts +9 -0
  1091. package/dist/utils/console-origin.js +28 -0
  1092. package/dist/utils/contribution-table.d.ts +1 -1
  1093. package/dist/utils/core-eval-trigger.d.ts +4 -0
  1094. package/dist/utils/core-eval-trigger.js +88 -0
  1095. package/dist/utils/environmental-error.d.ts +18 -0
  1096. package/dist/utils/environmental-error.js +240 -7
  1097. package/dist/utils/exclusive-lock.d.ts +32 -0
  1098. package/dist/utils/exclusive-lock.js +75 -0
  1099. package/dist/utils/expected-cli-error.d.ts +2 -0
  1100. package/dist/utils/expected-cli-error.js +4 -0
  1101. package/dist/utils/fast-stream-errors.d.ts +12 -0
  1102. package/dist/utils/fast-stream-errors.js +18 -0
  1103. package/dist/utils/feedback-diagnostics.d.ts +2 -1
  1104. package/dist/utils/feedback-log-bundle.d.ts +51 -26
  1105. package/dist/utils/feedback-log-bundle.js +211 -31
  1106. package/dist/utils/feedback-logs.d.ts +18 -47
  1107. package/dist/utils/feedback-logs.js +50 -21
  1108. package/dist/utils/feedback-screenshots.d.ts +0 -1
  1109. package/dist/utils/feedback-screenshots.js +1 -1
  1110. package/dist/utils/files-browse-acl.d.ts +5 -0
  1111. package/dist/utils/files-browse-acl.js +11 -0
  1112. package/dist/utils/hq-core-yaml.d.ts +10 -0
  1113. package/dist/utils/hq-core-yaml.js +40 -0
  1114. package/dist/utils/hq-root-resolver.d.ts +13 -0
  1115. package/dist/utils/hq-root-resolver.js +103 -0
  1116. package/dist/utils/hq-roots.d.ts +25 -2
  1117. package/dist/utils/hq-roots.js +67 -7
  1118. package/dist/utils/incomplete-install-error.d.ts +87 -19
  1119. package/dist/utils/incomplete-install-error.js +319 -68
  1120. package/dist/utils/install-tree-torn.d.ts +260 -24
  1121. package/dist/utils/install-tree-torn.js +998 -51
  1122. package/dist/utils/large-file-guard.d.ts +0 -10
  1123. package/dist/utils/large-file-guard.js +1 -1
  1124. package/dist/utils/local-files-overview.d.ts +4 -8
  1125. package/dist/utils/local-files-overview.js +48 -7
  1126. package/dist/utils/login-provider.d.ts +7 -0
  1127. package/dist/utils/login-provider.js +34 -0
  1128. package/dist/utils/pack-contributions.d.ts +2 -14
  1129. package/dist/utils/pack-contributions.js +7 -44
  1130. package/dist/utils/package-root-diagnostics.d.ts +1 -1
  1131. package/dist/utils/people.d.ts +7 -11
  1132. package/dist/utils/people.js +5 -20
  1133. package/dist/utils/plan-gate-error.d.ts +52 -10
  1134. package/dist/utils/plan-gate-error.js +364 -41
  1135. package/dist/utils/qmd-model-download-error.d.ts +0 -8
  1136. package/dist/utils/qmd-model-download-error.js +1 -1
  1137. package/dist/utils/qmd-query-document-error.d.ts +0 -6
  1138. package/dist/utils/qmd-query-document-error.js +1 -1
  1139. package/dist/utils/redact-error-text.d.ts +0 -2
  1140. package/dist/utils/redact-error-text.js +3 -2
  1141. package/dist/utils/redact-url.d.ts +14 -0
  1142. package/dist/utils/redact-url.js +218 -0
  1143. package/dist/utils/run-bundled-script.d.ts +6 -0
  1144. package/dist/utils/run-bundled-script.js +117 -4
  1145. package/dist/utils/sandbox-runner-client.d.ts +2 -1
  1146. package/dist/utils/scaffold-fast-import-guard.d.ts +2 -0
  1147. package/dist/utils/scaffold-fast-import-guard.js +40 -0
  1148. package/dist/utils/scaffold-packlist-guard.d.ts +7 -0
  1149. package/dist/utils/scaffold-packlist-guard.js +23 -0
  1150. package/dist/utils/scaffold-sibling-references.d.ts +22 -0
  1151. package/dist/utils/scaffold-sibling-references.js +156 -0
  1152. package/dist/utils/secret-load-availability.d.ts +8 -0
  1153. package/dist/utils/secret-load-availability.js +54 -0
  1154. package/dist/utils/secrets-cache.d.ts +7 -3
  1155. package/dist/utils/secrets-cache.js +162 -14
  1156. package/dist/utils/self-update.d.ts +34 -1
  1157. package/dist/utils/self-update.js +660 -42
  1158. package/dist/utils/sentry-fingerprint.d.ts +23 -1
  1159. package/dist/utils/sentry-fingerprint.js +223 -31
  1160. package/dist/utils/staged-install.d.ts +34 -0
  1161. package/dist/utils/staged-install.js +346 -0
  1162. package/dist/utils/staged-npm-install.d.ts +16 -0
  1163. package/dist/utils/staged-npm-install.js +264 -0
  1164. package/dist/utils/sync-log-sources.d.ts +15 -0
  1165. package/dist/utils/sync-log-sources.js +36 -0
  1166. package/dist/utils/table-width.d.ts +7 -0
  1167. package/dist/utils/table-width.js +15 -0
  1168. package/dist/utils/team-upgrade.d.ts +1 -1
  1169. package/dist/utils/team-upgrade.js +4 -3
  1170. package/dist/utils/update-command-supervisor.cjs +247 -0
  1171. package/dist/utils/upload-author.d.ts +15 -0
  1172. package/dist/utils/upload-author.js +39 -0
  1173. package/dist/utils/vault-api.d.ts +21 -1
  1174. package/dist/utils/vault-api.js +251 -84
  1175. package/dist/utils/version-check.d.ts +11 -1
  1176. package/dist/utils/version-check.js +32 -3
  1177. package/dist/utils/version-gate.d.ts +101 -17
  1178. package/dist/utils/version-gate.js +645 -70
  1179. package/dist/utils/version-report.d.ts +27 -0
  1180. package/dist/utils/version-report.js +105 -0
  1181. package/dist/utils/windows-spawn.d.ts +6 -0
  1182. package/dist/utils/windows-spawn.js +37 -0
  1183. package/dist/version-request.d.ts +14 -0
  1184. package/dist/version-request.js +36 -0
  1185. package/package.json +20 -8
  1186. package/scripts/mesh-daemon-if-stale.mjs +20 -0
  1187. package/templates/reports/blank.md +3 -0
  1188. package/templates/reports/daily-digest.md +11 -0
  1189. package/templates/runbooks/update-report.md +21 -0
  1190. package/dist/commands/scaffold-fast.d.ts +0 -41
  1191. package/dist/commands/scaffold-fast.js +0 -57
  1192. package/dist/lib/work-context/index.d.ts +0 -25
  1193. package/dist/lib/work-context/index.js +0 -21
@@ -0,0 +1,1881 @@
1
+ /**
2
+ * The resident bot process: `hq bot run <name>` (local-bots US-003/US-004).
3
+ *
4
+ * - pid lock in ~/.hq/bots/<name>/pid; status.json is the observable state
5
+ * - refuses to start unless the creds file names this bot's agt_ AND the
6
+ * server-side record is owned by the configured owner
7
+ * - heartbeat every 30s (POST /v1/agents/{uid}/heartbeat, agent JWT)
8
+ * - inbox poll every 2s: DMs go to ONE headless model turn (resumed by
9
+ * session id); the reply is sent as the bot; the message is acked only after
10
+ * the reply is sent; processed ids persist so a crash between reply and ack
11
+ * never answers twice
12
+ * - any correspondent can message the bot; each sender has a separate session
13
+ * - first start only: the intro DM, then (when bot.json has `kickoff`) ONE
14
+ * model turn on that prompt as if the owner sent it, answered in the DM, so
15
+ * a guided bot starts working without waiting for the owner to type
16
+ * - job items (channel "job"): a run the cloud fired on an EventBridge
17
+ * schedule. Nothing is scheduled on this computer. The run is one model turn
18
+ * in its own session (one per jobId), reported in the owner's DM with a
19
+ * "scheduled job" header through the same split/recovery path as every other
20
+ * reply, and reported back to the cloud EXACTLY ONCE — nothing else clears
21
+ * the job's in-progress flag. A run whose staleAfter has passed still runs,
22
+ * once, labelled a catch-up; older pending runs of the same job are reported
23
+ * skipped-precondition instead of piling up.
24
+ * - delivered room items (channels / group chats) are eligible for replies; claimed
25
+ * first (first-claim-wins), one session per room, reply posted in the room
26
+ * - conversations run side by side: up to MAX_PARALLEL_TURNS turns at once,
27
+ * one lane per model session (one DM correspondent, or one room). Messages
28
+ * in the same lane run in order, so a session is never resumed by two turns
29
+ * at once and a thread's answers never arrive out of sequence.
30
+ * - while a turn runs, each finished assistant message is posted as its own
31
+ * message (progress.ts); turns have no time limit
32
+ * - every received message gets a reply: a turn that fails after the model
33
+ * started (error, crash, owner stop, restart) posts one "did not finish"
34
+ * message and is NOT run again — it may already have done outward things.
35
+ * inflight.json marks the turn in progress so a restart can say so.
36
+ * - a turn that fails because the coding tool's sign-in no longer works
37
+ * (runtime-sign-in.ts) is not a failure of the message: the bot says once
38
+ * per conversation that it needs a sign-in, keeps the message un-acked,
39
+ * holds new turns, and tries again every SIGN_IN_RETRY_MS (and at once
40
+ * after a restart). A turn that works clears it.
41
+ * - failures before the model starts (missing binary) retry with backoff;
42
+ * 5 model failures in 10 minutes → state `failed` and a clean exit (launchd
43
+ * does not respawn a clean exit). Owner stops never count.
44
+ *
45
+ * Everything with a side effect is injectable so the loop is unit-testable.
46
+ */
47
+ import { ownerContextBlock } from "./owner-context.js";
48
+ import { randomUUID } from "node:crypto";
49
+ import { hasPromotionHold } from "./promotion-hold.js";
50
+ import * as fs from "node:fs";
51
+ import * as path from "node:path";
52
+ import { fullJitterDelayMs } from "../mesh/live/backoff.js";
53
+ import { acquirePidLock, defaultPidLockDeps, releasePidLock } from "../mesh/live/daemon/pid-lock.js";
54
+ import { BotApiError } from "./api.js";
55
+ import { effectiveBotCompanies, effectiveBotKind, patchBotConfig, readBotConfig, resolveBotMemoryDir, SETUP_BOT_WORKER_ID } from "./config.js";
56
+ import { ensureBotSessionMeta } from "./company-bind.js";
57
+ import { readBotCredsIdentity } from "./creds.js";
58
+ import { isProcessed, markProcessed, readInboxState } from "./inbox-state.js";
59
+ import { clearInflight, patchInflight, readInflight, readInflightTurns, writeInflight } from "./inflight.js";
60
+ import { containsNoReplySentinel, ProgressPoster, } from "./progress.js";
61
+ import { splitBody } from "./split.js";
62
+ import { createBotLogger } from "./log.js";
63
+ import { buildSystemPrompt, introDmText, readPromptSources } from "./prompt.js";
64
+ import { loadSetupExplainer, patchSetupIntroState, readSetupIntroState, readSetupProgressIntro, replyOffersExplain, SETUP_EXPLAINED_NOTE, setupExplainDue, } from "./setup-explain.js";
65
+ import { FALLBACK_CLAUDE_PERMISSION_MODE, isClaudeBypassDisabled } from "./runtime/claude.js";
66
+ import { runRuntimeTurn, RuntimeError } from "./runtime/index.js";
67
+ import { botAttachmentsDir, dmSessionScope } from "./paths.js";
68
+ import { attachmentTurnBlock, downloadTurnAttachments } from "./attachments.js";
69
+ import { inboxEntryAttachments } from "../agent-kit/inbox-state.js";
70
+ import { isAgentSender, isDmItem, isMetaNonReply, resolveReplyTarget } from "./room-policy.js";
71
+ import { buildJobTurnPrompt, isCatchUpRun, isJobItem, jobReportHeader, jobRunOutput, machineTimeZone, planJobRuns, stripJobHeader, SUPERSEDED_DISPOSITION } from "./job-run.js";
72
+ import { botJobQuietOptionEnabled } from "./job-quiet-flag.js";
73
+ import { clearPendingJobCompletion, isCompletionDue, isCompletionExpired, isCompletionForOtherAgent, readPendingJobCompletions, recordPendingJobCompletion } from "./job-completions.js";
74
+ import { isRuntimeSignInFailure, SIGN_IN_RETRY_MS, signInNeededText } from "./runtime-sign-in.js";
75
+ import { HQ_BOT_AGENT_UID_ENV } from "../../commands/onboard-bot-membership.js";
76
+ import { captureHeartbeatIdentityMismatch, identityMismatchMessage, isHeartbeatIdentityMismatch, } from "../heartbeat-identity.js";
77
+ import { clearBotSession, readBotSession, writeBotSession } from "./session.js";
78
+ import { defaultBotStatus, patchBotStatus, readBotStatus } from "./status.js";
79
+ import { CLI_VERSION } from "../../cli-version.js";
80
+ import { CLI_UPDATE_RESTART_EXIT_CODE } from "./cli-update.js";
81
+ import { createInboxPollBackoffFlagReader, safeErrorClass } from "./inbox-poll-backoff-flag.js";
82
+ export const INBOX_POLL_INTERVAL_MS = 2_000;
83
+ export const HEARTBEAT_INTERVAL_MS = 30_000;
84
+ /** First 429/5xx wait when Retry-After is absent (inbox polls at 2s). */
85
+ export const API_BACKOFF_BASE_MS = 4_000;
86
+ /** Ceiling for exponential poll/heartbeat backoff. */
87
+ export const API_BACKOFF_CAP_MS = 5 * 60_000;
88
+ /** Honor Retry-After up to this; a bogus 30-day header must not park the bot. */
89
+ export const API_RETRY_AFTER_CAP_MS = 15 * 60_000;
90
+ export const API_RETRY_AFTER_FLOOR_MS = 1_000;
91
+ /**
92
+ * How long to wait after a failed inbox poll or heartbeat.
93
+ * 429/5xx (and transport errors) back off exponentially, honouring Retry-After
94
+ * when present. Other 4xx keep the normal interval so a 401 is not stretched.
95
+ */
96
+ export function apiPollDelayMs(opts) {
97
+ const intervalMs = Math.max(0, opts.intervalMs);
98
+ const status = opts.err instanceof BotApiError ? opts.err.status : undefined;
99
+ const retryable = status === 429 || (typeof status === "number" && status >= 500) || !(opts.err instanceof BotApiError);
100
+ if (!retryable)
101
+ return intervalMs;
102
+ const retryAfter = opts.err instanceof BotApiError ? opts.err.retryAfterMs : undefined;
103
+ const header = typeof retryAfter === "number" && Number.isFinite(retryAfter)
104
+ ? Math.min(API_RETRY_AFTER_CAP_MS, Math.max(API_RETRY_AFTER_FLOOR_MS, retryAfter))
105
+ : 0;
106
+ const base = Math.max(intervalMs * 2, API_BACKOFF_BASE_MS);
107
+ const attempt = Math.max(0, opts.attempt);
108
+ const exp = Math.min(API_BACKOFF_CAP_MS, base * 2 ** attempt);
109
+ const random = opts.random ?? Math.random;
110
+ const jittered = Math.floor(exp * (1 + 0.2 * random()));
111
+ return Math.min(API_BACKOFF_CAP_MS, Math.max(intervalMs, exp, header, jittered));
112
+ }
113
+ export const FAILURE_WINDOW_MS = 10 * 60 * 1000;
114
+ export const FAILURE_LIMIT = 5;
115
+ export const MAX_ATTEMPTS_PER_MESSAGE = 3;
116
+ export const THINKING_EMOJI = "👀";
117
+ export const ROOM_THINKING_STATUS = "is thinking…";
118
+ export const RECENT_MESSAGES_LIMIT = 15;
119
+ export const RECENT_MESSAGE_MAX_CHARS = 400;
120
+ /** Model turns a bot runs at once (each on a different conversation). */
121
+ export const MAX_PARALLEL_TURNS = 2;
122
+ /**
123
+ * Failed attempts to deliver ONE saved message before the bot stops trying.
124
+ * A body the server refuses (too long, malformed) is refused identically every
125
+ * time, so retrying it on every 2s poll only hammers the API and leaves the
126
+ * person with no answer and no explanation.
127
+ */
128
+ export const RECOVERY_ATTEMPT_LIMIT = 5;
129
+ /** What the person sees when a reply could not be delivered at all. */
130
+ export const UNDELIVERABLE_REPLY_TEXT = "I wrote a reply but couldn't deliver it. Ask me again and I'll keep it shorter.";
131
+ /**
132
+ * The same, for a scheduled run. Nobody asked, so "ask me again" is wrong: the
133
+ * run DID finish, and its outcome is still recorded with the job in HQ.
134
+ */
135
+ export const UNDELIVERABLE_JOB_REPORT_TEXT = "This scheduled run finished, but I couldn't deliver its report here. Its result is saved with the job's run history.";
136
+ /**
137
+ * Scheduled work must never crowd out the people the bot talks to. A job turn
138
+ * can run for a long time, so at most ONE runs at a time, and a job never takes
139
+ * the last free slot: a DM or a room message can always start immediately
140
+ * instead of waiting, with no explanation, behind two long jobs.
141
+ */
142
+ export const MAX_PARALLEL_JOB_TURNS = 1;
143
+ export const KICKOFF_MESSAGE_ID = "kickoff";
144
+ /**
145
+ * The environment a model turn's `hq` commands run in.
146
+ *
147
+ * The bot process carries its own machine identity in HQ_MACHINE_CREDS_FILE /
148
+ * HQ_MACHINE_TOKEN_STATE_DIR so its inbox, heartbeat and posts are the bot's.
149
+ * What a turn inherits depends on the bot's kind:
150
+ * - A PERSONAL bot acts as its owner on every turn (DMs, the kickoff, rooms):
151
+ * everything the owner asks for — a company, a bot, an invite, a share —
152
+ * belongs to the owner's account, and the cloud rightly refuses a bot that
153
+ * tries to own it ("No person entity found"). Stripping the marker makes
154
+ * every `hq` command in the turn use the owner's sign-in, the same one the
155
+ * desktop app shares, so no command needs a special case. The bot's id
156
+ * (never a credential) rides along in HQ_BOT_AGENT_UID so what the owner
157
+ * creates in the turn — a company — can add the bot as a member afterwards.
158
+ * - A COMPANY bot acts as itself on every turn, like a cloud agent: the
159
+ * machine identity stays, and the cloud evaluates it through its own
160
+ * company memberships. It never acts as the owner, in DMs or in rooms.
161
+ * Pure so the rule is unit-testable.
162
+ */
163
+ export function turnEnv(base, kind, agentUid, companySlug) {
164
+ const env = { ...base };
165
+ delete env[HQ_BOT_AGENT_UID_ENV];
166
+ if (kind === "personal") {
167
+ delete env.HQ_MACHINE_CREDS_FILE;
168
+ delete env.HQ_MACHINE_TOKEN_STATE_DIR;
169
+ if (agentUid)
170
+ env[HQ_BOT_AGENT_UID_ENV] = agentUid;
171
+ }
172
+ else if (companySlug?.trim()) {
173
+ env.HQ_SPAWN_COMPANY = companySlug.trim();
174
+ }
175
+ return env;
176
+ }
177
+ /** The core worker HQ's setup bot runs. */
178
+ export const SETUP_WORKER_ID = SETUP_BOT_WORKER_ID;
179
+ /** The one message a person gets when a turn on their message did not finish. */
180
+ export function didNotFinishText(reason, progressPosted) {
181
+ const r = reason.trim().replace(/[.\s]+$/, "");
182
+ return `I didn't finish that — ${r}.${progressPosted ? " The messages above show how far I got." : ""}`;
183
+ }
184
+ export { containsNoReplySentinel };
185
+ /** Grok emits is_error with this message when the model produces only reasoning tokens. */
186
+ export function isReasoningOnlyError(excerpt) {
187
+ return excerpt.includes("reasoning_only");
188
+ }
189
+ /** Owner stop, `hq bot stop` or `hq bot restart`: all arrive as the same signal. */
190
+ export const STOPPED_BY_OWNER_REASON = "I was stopped before I was done";
191
+ export const RESTARTED_REASON = "I was restarted while I was working on it";
192
+ /**
193
+ * The lane an inbox item runs in: the model session its turn resumes. Owner
194
+ * DMs retain the legacy DM session; other senders and rooms each have their
195
+ * own, and so does each scheduled job (two different jobs must never share a
196
+ * model session, and a job must never pollute the owner's DM conversation).
197
+ * An item that never reaches a model turn gets a lane of its own.
198
+ */
199
+ export function inboxLaneKey(item, config) {
200
+ if (isDmItem(item))
201
+ return dmSessionScope(item.fromPersonUid ?? "", config.ownerUid);
202
+ const target = resolveReplyTarget(item, config);
203
+ if (target?.kind === "room" || target?.kind === "job")
204
+ return target.sessionScope;
205
+ return `item:${item.messageId}`;
206
+ }
207
+ class FailureWindow {
208
+ windowMs;
209
+ limit;
210
+ stamps = [];
211
+ constructor(windowMs, limit) {
212
+ this.windowMs = windowMs;
213
+ this.limit = limit;
214
+ }
215
+ record(nowMs) {
216
+ this.stamps.push(nowMs);
217
+ while (this.stamps.length && nowMs - this.stamps[0] > this.windowMs)
218
+ this.stamps.shift();
219
+ return this.stamps.length;
220
+ }
221
+ count(nowMs) {
222
+ while (this.stamps.length && nowMs - this.stamps[0] > this.windowMs)
223
+ this.stamps.shift();
224
+ return this.stamps.length;
225
+ }
226
+ exceeded(nowMs) {
227
+ return this.count(nowMs) >= this.limit;
228
+ }
229
+ }
230
+ function defaultSleep(ms, signal) {
231
+ return new Promise((resolve) => {
232
+ if (signal?.aborted) {
233
+ resolve();
234
+ return;
235
+ }
236
+ const timer = setTimeout(done, ms);
237
+ function done() {
238
+ clearTimeout(timer);
239
+ signal?.removeEventListener("abort", done);
240
+ resolve();
241
+ }
242
+ signal?.addEventListener("abort", done, { once: true });
243
+ });
244
+ }
245
+ /**
246
+ * A body posted as several parts where an early part got through and a later
247
+ * one did not: `delivered` is how many parts the person has, so a retry starts
248
+ * where it stopped instead of repeating what they already read.
249
+ */
250
+ class PartialPostError extends Error {
251
+ parts;
252
+ delivered;
253
+ failure;
254
+ constructor(parts, delivered, failure) {
255
+ super(failure instanceof Error ? failure.message : String(failure));
256
+ this.parts = parts;
257
+ this.delivered = delivered;
258
+ this.failure = failure;
259
+ this.name = "PartialPostError";
260
+ }
261
+ }
262
+ export function preflightCreds(dir, config) {
263
+ const identity = readBotCredsIdentity(dir);
264
+ if (!identity)
265
+ return `No machine credentials for bot "${config.name}" — run: hq bot create ${config.name}`;
266
+ if (identity.entityUid !== config.agentUid) {
267
+ return `Credentials in ${dir} belong to ${identity.entityUid}, not this bot (${config.agentUid}); refusing to start`;
268
+ }
269
+ return null;
270
+ }
271
+ export async function runBot(deps) {
272
+ const now = deps.now ?? (() => new Date());
273
+ const sleep = deps.sleep ?? defaultSleep;
274
+ const random = deps.random ?? Math.random;
275
+ const log = deps.log ?? createBotLogger(deps.dir, now);
276
+ const pid = deps.pid ?? process.pid;
277
+ const { dir, config, api, runtime } = deps;
278
+ const runTurn = deps.runTurn ?? ((rt, input) => runRuntimeTurn(rt, input));
279
+ const inboxIntervalMs = deps.inboxIntervalMs ?? INBOX_POLL_INTERVAL_MS;
280
+ const inboxPollBackoffEnabled = deps.inboxPollBackoffEnabled ?? createInboxPollBackoffFlagReader(config);
281
+ const jobQuietOptionEnabled = deps.jobQuietOptionEnabled ?? botJobQuietOptionEnabled;
282
+ const heartbeatIntervalMs = deps.heartbeatIntervalMs ?? HEARTBEAT_INTERVAL_MS;
283
+ const exit = deps.exit ?? ((code) => process.exit(code));
284
+ const fallback = () => defaultBotStatus(pid, config.runtime, now);
285
+ const setStatus = (patch) => patchBotStatus(dir, patch, fallback, now);
286
+ let state = "starting";
287
+ let stopping = false;
288
+ /** Aborts the model turns in progress (owner stop). */
289
+ const currentTurns = new Set();
290
+ const maxParallelTurns = Math.max(1, deps.maxParallelTurns ?? MAX_PARALLEL_TURNS);
291
+ let resolveDone;
292
+ const done = new Promise((r) => (resolveDone = r));
293
+ // Only override isProcessAlive when a caller injected one: spreading an
294
+ // explicit `undefined` over defaultPidLockDeps() would erase the default and
295
+ // crash the daemon at startup ("deps.isProcessAlive is not a function").
296
+ // Model turns run in the owner's HQ root so every skill, policy and `hq`
297
+ // command resolves the way it does for the owner. (A scratch cwd was tried
298
+ // on 2026-09-11 for speed; it cost the bot its skills and memory writes.)
299
+ const turnCwd = config.hqRoot;
300
+ // HQ's setup bot is a guided conversation: no "working on it" filler between steps.
301
+ const isSetupBot = config.workerId === SETUP_WORKER_ID;
302
+ /**
303
+ * Setup bot, owner DM: remember whether the bot's latest reply asked the
304
+ * explain-or-jump question, so a click on "Explain HQ first" can be answered
305
+ * at once (setup-explain.ts). Never lets a bookkeeping error touch the turn.
306
+ */
307
+ const noteSetupReply = (reply) => {
308
+ if (!isSetupBot)
309
+ return;
310
+ try {
311
+ const offered = replyOffersExplain(reply);
312
+ const current = readSetupIntroState(dir);
313
+ if (offered && !current.offeredAt)
314
+ patchSetupIntroState(dir, { offeredAt: now().toISOString() });
315
+ else if (!offered && current.offeredAt)
316
+ patchSetupIntroState(dir, { offeredAt: undefined });
317
+ }
318
+ catch (err) {
319
+ log("warn", `setup intro state not updated: ${err instanceof Error ? err.message : String(err)}`);
320
+ }
321
+ };
322
+ // Scheduled runs are reported in the owner's own zone: "every weekday at 9am"
323
+ // was set up here, so "due 13:00 UTC" would read as the wrong time.
324
+ const timeZone = machineTimeZone();
325
+ // Personal: every turn acts as the owner. Company: every turn acts as the bot.
326
+ const kind = effectiveBotKind(config);
327
+ const companies = effectiveBotCompanies(config);
328
+ const lockDeps = defaultPidLockDeps({
329
+ pid,
330
+ ...(deps.isProcessAlive ? { isProcessAlive: deps.isProcessAlive } : {}),
331
+ });
332
+ const finish = async (final, code, reason, beforeExit) => {
333
+ if (state === "stopped" || state === "failed")
334
+ return;
335
+ state = final;
336
+ setStatus({ state: final, ...(final === "failed" ? { lastError: reason.slice(0, 400) } : {}) });
337
+ log(final === "failed" ? "error" : "info", `bot ${final}: ${reason}`);
338
+ if (!deps.skipPidLock)
339
+ releasePidLock(dir, lockDeps);
340
+ try {
341
+ beforeExit?.();
342
+ }
343
+ catch (err) {
344
+ log("error", `before exit: ${err instanceof Error ? err.message : String(err)}`);
345
+ }
346
+ resolveDone(final);
347
+ exit(code);
348
+ };
349
+ const cliVersion = deps.cliUpdate?.runningVersion ?? CLI_VERSION;
350
+ /** Set once an update restart is due: no new turns, then exit for the supervisor. */
351
+ let cliUpdatePending = null;
352
+ const pollForCliUpdate = () => {
353
+ const update = deps.cliUpdate?.poll(log) ?? null;
354
+ if (!update)
355
+ return false;
356
+ cliUpdatePending = update;
357
+ stopping = true;
358
+ const busy = running.size;
359
+ log("info", `hq-cli updated ${update.from} -> ${update.to}; restarting to load it${busy ? ` after ${busy} running turn${busy === 1 ? "" : "s"} finish${busy === 1 ? "es" : ""}` : ""}`);
360
+ return true;
361
+ };
362
+ // ── Preflight: creds, pid lock, owner verification ─────────────────────────
363
+ const credsProblem = preflightCreds(dir, config);
364
+ if (credsProblem) {
365
+ setStatus({ state: "failed", lastError: credsProblem, pid });
366
+ log("error", credsProblem);
367
+ resolveDone("failed");
368
+ exit(0);
369
+ return { stop: async () => { }, done, status: () => null, idle: () => true };
370
+ }
371
+ if (config.enabled === false) {
372
+ setStatus({ state: "stopped", pid });
373
+ log("info", "bot is disabled (hq bot start to enable)");
374
+ resolveDone("stopped");
375
+ exit(0);
376
+ return { stop: async () => { }, done, status: () => null, idle: () => true };
377
+ }
378
+ if (!deps.skipPidLock) {
379
+ const lock = acquirePidLock(dir, lockDeps);
380
+ if (!lock.ok) {
381
+ const msg = `bot "${config.name}" is already running (pid ${lock.owner.pid})`;
382
+ log("warn", msg);
383
+ resolveDone("stopped");
384
+ exit(0);
385
+ return { stop: async () => { }, done, status: () => null, idle: () => true };
386
+ }
387
+ }
388
+ if (hasPromotionHold(dir)) {
389
+ await finish("stopped", 0, "local bot held for cloud promotion");
390
+ return { stop: async () => { }, done, status: () => null, idle: () => true };
391
+ }
392
+ // A restart is the owner's "try again" after signing in: start clean.
393
+ setStatus({ ...fallback(), pid, state: "starting", canDrainForPromotion: false, runtimeSignIn: null, cliVersion });
394
+ log("info", `starting ${config.name} on hq-cli ${cliVersion} (${config.runtime}, ${kind} bot${companies.length ? ` of ${companies.join(", ")}` : ""}) as ${config.agentUid} for ${config.ownerUid}`);
395
+ deps.cliUpdate?.startup(log);
396
+ try {
397
+ const record = await api.getAgent(config.agentUid);
398
+ // Promotion keeps the identity and owner. Ownership alone must not let
399
+ // an old local installation start consuming the cloud bot's inbox.
400
+ if (record.uid !== config.agentUid || record.computeMode !== "local" || record.botKind !== kind) {
401
+ await finish("failed", 0, `identity ${config.agentUid} is not a ${kind} local bot; this installation cannot run it`);
402
+ return { stop: async () => { }, done, status: () => null, idle: () => true };
403
+ }
404
+ const owner = record.ownerUid ?? null;
405
+ if (owner !== config.ownerUid) {
406
+ await finish("failed", 0, `identity ${config.agentUid} is owned by ${owner ?? "nobody"}, not ${config.ownerUid}`);
407
+ return { stop: async () => { }, done, status: () => null, idle: () => true };
408
+ }
409
+ }
410
+ catch (err) {
411
+ await finish("failed", 0, `could not verify bot identity: ${err instanceof Error ? err.message : String(err)}`);
412
+ return { stop: async () => { }, done, status: () => null, idle: () => true };
413
+ }
414
+ // ── System prompt ──────────────────────────────────────────────────────────
415
+ const systemPrompt = deps.systemPromptOverride ??
416
+ buildSystemPrompt({
417
+ botName: config.name,
418
+ ...(config.displayName ? { displayName: config.displayName } : {}),
419
+ agentUid: config.agentUid,
420
+ ownerUid: config.ownerUid,
421
+ hqRoot: config.hqRoot,
422
+ workerDir: config.workerDir,
423
+ workerSource: config.workerSource,
424
+ workerId: config.workerId,
425
+ companySlug: config.companySlug,
426
+ kind,
427
+ companies,
428
+ memoryDir: config.memoryDir,
429
+ sources: readPromptSources(config.hqRoot, config.workerDir, undefined, {
430
+ memoryDir: config.memoryDir,
431
+ workerSource: config.workerSource,
432
+ companySlug: config.companySlug,
433
+ }),
434
+ });
435
+ const systemPromptFile = path.join(dir, "system-prompt.md");
436
+ fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
437
+ fs.writeFileSync(systemPromptFile, systemPrompt, { mode: 0o600 });
438
+ if (hasPromotionHold(dir)) {
439
+ await finish("stopped", 0, "local bot held for cloud promotion");
440
+ return { stop: async () => { }, done, status: () => null, idle: () => true };
441
+ }
442
+ state = "running";
443
+ setStatus({ state: "running", lastActivityAt: now().toISOString() });
444
+ log("info", "running");
445
+ // Read before the kickoff can write its own marker: whatever is here now was
446
+ // left by a previous run that was cut off mid-turn.
447
+ const leftovers = readInflightTurns(dir);
448
+ // ── Intro DM (once) ────────────────────────────────────────────────────────
449
+ // The kickoff turn is tied to the intro: it runs only in the start that sent
450
+ // the intro, so introSentAt (persisted before the turn) guarantees it never
451
+ // runs again on a restart.
452
+ let kickoffAfterIntro = null;
453
+ if (config.introSentAt && config.kickoffPendingSignIn && config.kickoff?.trim()) {
454
+ // The kickoff was held for a sign-in on an earlier run; this start retries it.
455
+ kickoffAfterIntro = {};
456
+ }
457
+ else if (!config.introSentAt) {
458
+ try {
459
+ const sent = await api.sendDm({ toPersonUid: config.ownerUid, body: introDmText(config.name, config.runtime, config.intro, { kind: effectiveBotKind(config), companies: effectiveBotCompanies(config) }) });
460
+ patchBotConfig(dir, { introSentAt: now().toISOString() });
461
+ log("info", "intro DM sent to owner");
462
+ if (config.kickoff?.trim())
463
+ kickoffAfterIntro = { ...(sent?.eventId ? { introEventId: sent.eventId } : {}) };
464
+ }
465
+ catch (err) {
466
+ log("warn", `intro DM failed (will retry next start): ${err instanceof Error ? err.message : String(err)}`);
467
+ }
468
+ }
469
+ // ── Heartbeat loop ─────────────────────────────────────────────────────────
470
+ const failures = new FailureWindow(FAILURE_WINDOW_MS, FAILURE_LIMIT);
471
+ let modelHealth = "ok";
472
+ // ── Coding tool sign-in ────────────────────────────────────────────────────
473
+ const signInRetryMs = Math.max(0, deps.signInRetryMs ?? SIGN_IN_RETRY_MS);
474
+ let signInIssue = null;
475
+ let nextSignInRetryAt = 0;
476
+ /** Conversations already told this time that the bot needs a sign-in. */
477
+ const toldAboutSignIn = new Set();
478
+ const markSignInBroken = (excerpt) => {
479
+ nextSignInRetryAt = now().getTime() + signInRetryMs;
480
+ modelHealth = "degraded";
481
+ if (signInIssue)
482
+ return;
483
+ signInIssue = { state: "expired", runtime: config.runtime, since: now().toISOString() };
484
+ setStatus({ runtimeSignIn: signInIssue, lastError: excerpt.slice(0, 400) });
485
+ log("warn", `${runtime.id} needs to sign in again on this computer; holding turns and retrying every ${Math.round(signInRetryMs / 1000)}s`);
486
+ };
487
+ const markSignInWorks = () => {
488
+ if (!signInIssue)
489
+ return;
490
+ signInIssue = null;
491
+ toldAboutSignIn.clear();
492
+ setStatus({ runtimeSignIn: null });
493
+ log("info", `${runtime.id} sign-in works again; resuming`);
494
+ };
495
+ /** Say once per conversation that the bot cannot work until its tool signs in. */
496
+ const tellSignInNeeded = async (conversation, post, kickoff = false) => {
497
+ if (toldAboutSignIn.has(conversation))
498
+ return;
499
+ try {
500
+ await post(signInNeededText(config.runtime, kickoff));
501
+ toldAboutSignIn.add(conversation);
502
+ }
503
+ catch (err) {
504
+ log("warn", `could not say that ${runtime.id} needs a sign-in: ${err instanceof Error ? err.message : String(err)}`);
505
+ }
506
+ };
507
+ const heartbeatLoop = (async () => {
508
+ let heartbeatFails = 0;
509
+ while (!stopping && state === "running") {
510
+ try {
511
+ // The server's component vocabulary is the fleet one (sync/model/task…);
512
+ // runtime → model, inbox → task, sync → sync.
513
+ await api.heartbeat(config.agentUid, { model: modelHealth, task: "ok", sync: "ok" });
514
+ heartbeatFails = 0;
515
+ setStatus({
516
+ lastHeartbeatAt: now().toISOString(),
517
+ lastHeartbeatOk: true,
518
+ lastHeartbeatError: undefined,
519
+ lastHeartbeatReason: undefined,
520
+ lastHeartbeatConfiguredAgentUid: undefined,
521
+ lastHeartbeatTokenAgentUid: undefined,
522
+ });
523
+ await sleep(heartbeatIntervalMs);
524
+ }
525
+ catch (err) {
526
+ if (err instanceof BotApiError &&
527
+ isHeartbeatIdentityMismatch({ status: err.status, code: err.code, message: err.detail })) {
528
+ const tokenLabel = err.tokenAgentUid ?? "unknown";
529
+ const reason = identityMismatchMessage("heartbeat", config.agentUid, tokenLabel);
530
+ setStatus({
531
+ lastHeartbeatOk: false,
532
+ lastHeartbeatError: reason.slice(0, 400),
533
+ lastHeartbeatReason: "identity_mismatch",
534
+ lastHeartbeatConfiguredAgentUid: config.agentUid,
535
+ ...(err.tokenAgentUid ? { lastHeartbeatTokenAgentUid: err.tokenAgentUid } : {}),
536
+ });
537
+ log("warn", reason);
538
+ (deps.reportIdentityMismatch ?? captureHeartbeatIdentityMismatch)({
539
+ runner: "bot",
540
+ configuredAgentUid: config.agentUid,
541
+ ...(err.tokenAgentUid ? { tokenAgentUid: err.tokenAgentUid } : {}),
542
+ agentUidSource: deps.agentUidSource ?? "config_file",
543
+ credsSource: deps.credsSource ?? "default",
544
+ });
545
+ break;
546
+ }
547
+ heartbeatFails += 1;
548
+ const message = err instanceof Error ? err.message : String(err);
549
+ setStatus({ lastHeartbeatOk: false, lastHeartbeatError: message.slice(0, 400) });
550
+ log("warn", `heartbeat failed: ${message}`);
551
+ await sleep(apiPollDelayMs({ err, attempt: heartbeatFails - 1, intervalMs: heartbeatIntervalMs, random }));
552
+ }
553
+ }
554
+ })();
555
+ // ── Inbox loop ─────────────────────────────────────────────────────────────
556
+ const attempts = new Map();
557
+ const inboxBackoffSteps = [inboxIntervalMs, 10_000, 20_000, 30_000];
558
+ let inboxBackoffIndex = 0;
559
+ let inboxWaitGeneration = 0;
560
+ let activeInboxWait = null;
561
+ const resetInboxBackoff = () => {
562
+ inboxBackoffIndex = 0;
563
+ inboxWaitGeneration += 1;
564
+ activeInboxWait?.abort();
565
+ };
566
+ const waitForInbox = async (delayMs, adaptive) => {
567
+ if (!adaptive) {
568
+ await sleep(delayMs);
569
+ return;
570
+ }
571
+ let remainingMs = delayMs;
572
+ let observedGeneration = inboxWaitGeneration;
573
+ while (remainingMs > 0 && !stopping && state === "running") {
574
+ const controller = new AbortController();
575
+ activeInboxWait = controller;
576
+ const chunkMs = Math.min(inboxIntervalMs, remainingMs);
577
+ await sleep(chunkMs, controller.signal);
578
+ if (activeInboxWait === controller)
579
+ activeInboxWait = null;
580
+ if (stopping || state !== "running")
581
+ break;
582
+ if (hasPromotionHold(dir))
583
+ break;
584
+ if (observedGeneration !== inboxWaitGeneration || controller.signal.aborted) {
585
+ observedGeneration = inboxWaitGeneration;
586
+ remainingMs = inboxIntervalMs;
587
+ continue;
588
+ }
589
+ remainingMs -= chunkMs;
590
+ // Keep the install watcher at its original two-second cadence even when
591
+ // the inbox itself is waiting longer between empty polls.
592
+ if (remainingMs > 0 && pollForCliUpdate())
593
+ break;
594
+ }
595
+ };
596
+ /** Build the user turn for a room trigger: header + recent transcript + the message. */
597
+ const buildRoomTurn = async (item, target, text) => {
598
+ const name = (item.channelName ?? "").trim() || target.channelId;
599
+ const members = typeof item.memberCount === "number" ? `${item.memberCount} members` : "group";
600
+ const from = (item.fromDisplayName ?? "").trim() || item.fromPersonUid || "unknown";
601
+ const header = `[#${name} · ${members} · from ${from}]\n[HQ sender: ${item.fromPersonUid ?? "unknown"}; owner: ${config.ownerUid}; sender is ${item.fromPersonUid === config.ownerUid ? "the owner" : "not the owner"}.]`;
602
+ let recent = "";
603
+ try {
604
+ const rows = await api.fetchChannelMessages(target.channelId, RECENT_MESSAGES_LIMIT);
605
+ const lines = rows
606
+ .filter((m) => !(item.eventId && m.eventId === item.eventId))
607
+ .map((m) => `- ${m.fromDisplayName ?? "someone"} (${m.fromPersonUid ?? "unknown"}): ${m.body.replace(/\s+/g, " ").slice(0, RECENT_MESSAGE_MAX_CHARS)}`);
608
+ if (lines.length > 0)
609
+ recent = `Recent messages:\n${lines.join("\n")}\n\n`;
610
+ }
611
+ catch (err) {
612
+ log("warn", `recent messages for ${target.channelId} unavailable: ${err instanceof Error ? err.message : String(err)}`);
613
+ }
614
+ return `${header}\n${recent}${from}: ${text}`;
615
+ };
616
+ /**
617
+ * One model turn in the given session scope: company bind, the Claude
618
+ * bypass → auto fallback, and the session file update on success. Throws
619
+ * the runtime error on failure (callers account for it via recordTurnFailure).
620
+ */
621
+ const executeTurn = async (prompt, sessionScope, tReceived, stream = {}) => {
622
+ const session = readBotSession(dir, config.runtime, sessionScope);
623
+ // Company bind (worker-sourced bots): HQ's company hooks key on
624
+ // workspace/sessions/<sid>/meta.yaml, so the session id is fixed up front
625
+ // and the meta file written before every turn (idempotent; covers session
626
+ // resets). Claude gets the full hook set via its payload session id;
627
+ // Codex/Grok only see HQ_SESSION_ID.
628
+ const sid = session?.sessionId ?? randomUUID();
629
+ const env = turnEnv(process.env, kind, config.agentUid, config.companySlug);
630
+ let bind = { env };
631
+ if (config.companySlug) {
632
+ try {
633
+ ensureBotSessionMeta(config.hqRoot, sid, config.companySlug, undefined, now);
634
+ bind = { newSessionId: sid, env: { ...env, HQ_SESSION_ID: sid } };
635
+ }
636
+ catch (err) {
637
+ log("warn", `company bind failed for ${config.companySlug}: ${err instanceof Error ? err.message : String(err)}`);
638
+ }
639
+ }
640
+ // Only a personal bot, and only in the owner's own DM session (DMs and the
641
+ // kickoff). Room prompts must keep their [#channel …] header first, and a
642
+ // room turn can be triggered by other people, who must not be handed the
643
+ // owner's companies. A company bot acts as itself and never gets it.
644
+ // Owner context also goes on the owner's scheduled jobs: `turnEnv` strips
645
+ // the machine credentials on EVERY personal-bot turn, so a job turn runs
646
+ // `hq` as the owner too and needs the same block saying whose account it
647
+ // is on. Rooms stay excluded (see above).
648
+ const actsPurelyForOwner = sessionScope === "dm" || sessionScope.startsWith("job:");
649
+ if (deps.ownerContext && actsPurelyForOwner && kind === "personal") {
650
+ try {
651
+ const owner = await deps.ownerContext();
652
+ if (owner.status === "unavailable") {
653
+ log("warn", `owner context unavailable: ${owner.reason}${owner.detail ? ` (${owner.detail})` : ""}`);
654
+ }
655
+ else if (owner.status === "mismatch") {
656
+ log("warn", `owner context mismatch: signed in as ${owner.signedInAs}`);
657
+ }
658
+ prompt = `${ownerContextBlock(owner, config.agentUid)}\n\n${prompt}`;
659
+ }
660
+ catch (err) {
661
+ log("warn", `owner context unavailable: ${err instanceof Error ? err.message : String(err)}`);
662
+ }
663
+ }
664
+ const tTurnStart = now().getTime();
665
+ // Model and thinking level are read fresh each turn, so a change from the
666
+ // bot's profile (`hq bot set`) applies to the next message without a restart.
667
+ const live = readBotConfig(dir) ?? config;
668
+ const attemptTurn = (permissionMode) => runTurn(runtime, {
669
+ prompt,
670
+ systemPrompt,
671
+ systemPromptFile,
672
+ cwd: turnCwd,
673
+ hqRoot: config.hqRoot,
674
+ autoApprove: config.autoApprove !== false,
675
+ ...(permissionMode ? { permissionMode } : {}),
676
+ sessionId: session?.sessionId,
677
+ ...(live.model ? { model: live.model } : {}),
678
+ ...(live.effort ? { effort: live.effort } : {}),
679
+ ...bind,
680
+ ...stream,
681
+ });
682
+ let turn;
683
+ const remembered = readStatus()?.claudePermissionMode;
684
+ try {
685
+ turn = await attemptTurn(remembered);
686
+ }
687
+ catch (err) {
688
+ // Claude Code can have bypassPermissions switched off by managed
689
+ // settings. Fall back to "auto" (no prompts, classifier-approved) once
690
+ // and remember it, so the bot keeps working without an operator.
691
+ const detail = err instanceof RuntimeError ? `${err.message}\n${err.stderrExcerpt}` : err instanceof Error ? err.message : String(err);
692
+ const aborted = err instanceof RuntimeError && err.aborted;
693
+ if (aborted || runtime.id !== "claude" || remembered || config.autoApprove === false || !isClaudeBypassDisabled(detail)) {
694
+ throw Object.assign(err instanceof Error ? err : new Error(String(err)), { resumedSession: Boolean(session) });
695
+ }
696
+ log("warn", `bypassPermissions is disabled for this Claude Code; falling back to permission mode "${FALLBACK_CLAUDE_PERMISSION_MODE}"`);
697
+ setStatus({ claudePermissionMode: FALLBACK_CLAUDE_PERMISSION_MODE });
698
+ try {
699
+ turn = await attemptTurn(FALLBACK_CLAUDE_PERMISSION_MODE);
700
+ }
701
+ catch (retryErr) {
702
+ throw Object.assign(retryErr instanceof Error ? retryErr : new Error(String(retryErr)), { resumedSession: Boolean(session) });
703
+ }
704
+ }
705
+ modelHealth = "ok";
706
+ markSignInWorks();
707
+ log("info", `model turn done in ${Math.round((now().getTime() - tTurnStart) / 1000)}s (${Math.round((tTurnStart - tReceived) / 1000)}s before it)`);
708
+ if (turn.sessionId) {
709
+ writeBotSession(dir, {
710
+ v: 1,
711
+ runtime: config.runtime,
712
+ sessionId: turn.sessionId,
713
+ turns: (session?.turns ?? 0) + 1,
714
+ updatedAt: now().toISOString(),
715
+ }, sessionScope);
716
+ }
717
+ return turn;
718
+ };
719
+ /** Failure bookkeeping shared by every model turn: window, health, status, log, stale session. */
720
+ const recordTurnFailure = (err, sessionScope) => {
721
+ const excerpt = err instanceof RuntimeError ? err.stderrExcerpt : err instanceof Error ? err.message : String(err);
722
+ const count = failures.record(now().getTime());
723
+ modelHealth = count >= FAILURE_LIMIT ? "failed" : "degraded";
724
+ setStatus({ restarts: (readStatus()?.restarts ?? 0) + 1, recentFailures: count, lastError: excerpt.slice(0, 400) });
725
+ log("error", `model turn failed (${count}/${FAILURE_LIMIT} in window): ${excerpt}`);
726
+ // A resumed session that no longer exists must not poison every turn.
727
+ const resumed = Boolean(err?.resumedSession);
728
+ if (resumed && /session|resume|not found/i.test(excerpt))
729
+ clearBotSession(dir, sessionScope);
730
+ return { excerpt, count };
731
+ };
732
+ /**
733
+ * One model turn that posts its in-between messages as it goes. Writes the
734
+ * in-flight marker first; the caller clears it once the message is answered.
735
+ */
736
+ const runTurnWithProgress = async (args) => {
737
+ const { messageId } = args.marker;
738
+ const silenceable = args.silenceable === true;
739
+ writeInflight(dir, {
740
+ v: 1,
741
+ ...args.marker,
742
+ startedAt: now().toISOString(),
743
+ ...(silenceable ? { silenceable: true } : {}),
744
+ });
745
+ let running = true;
746
+ let posted = 0;
747
+ const makeSendFn = (fn) => async (body) => {
748
+ try {
749
+ await fn(body);
750
+ }
751
+ catch (err) {
752
+ log("warn", `post for ${messageId} failed: ${err instanceof Error ? err.message : String(err)}`);
753
+ return false;
754
+ }
755
+ posted += 1;
756
+ patchInflight(dir, messageId, { progressPosted: posted });
757
+ if (running && args.afterProgressPost)
758
+ await args.afterProgressPost().catch(() => undefined);
759
+ return true;
760
+ };
761
+ const poster = new ProgressPoster({
762
+ // The setup bot talks to someone new to HQ: its notes to itself never
763
+ // reach them, and it posts no "still working" notice either — the
764
+ // desktop app shows that the bot is working.
765
+ ...(isSetupBot ? { dropPreToolMessages: true } : {}),
766
+ ...deps.progress,
767
+ ...(args.workingNotice === false || silenceable || isSetupBot ? { workingNoticeMs: 0 } : {}),
768
+ ...(silenceable || args.suppressOutput ? { buffer: true } : {}),
769
+ send: makeSendFn(args.send),
770
+ ...(args.sendFinal ? { sendFinal: makeSendFn(args.sendFinal) } : {}),
771
+ });
772
+ const controller = new AbortController();
773
+ currentTurns.add(controller);
774
+ poster.start();
775
+ try {
776
+ const turn = await executeTurn(args.prompt, args.sessionScope, args.tReceived, {
777
+ onEvent: (event) => {
778
+ if (event.kind === "message")
779
+ poster.message(event.text);
780
+ else if (event.kind === "tool")
781
+ poster.tool();
782
+ },
783
+ signal: controller.signal,
784
+ ...(args.readDirs?.length ? { readDirs: args.readDirs } : {}),
785
+ });
786
+ running = false;
787
+ // Control tokens were already stripped from turn.text in runRuntimeTurn, so
788
+ // a `<|eos|>`-only reply arrives here as empty. On an unmentioned room turn
789
+ // a stay-quiet phrase ("No response requested." etc.) is also silence — the
790
+ // meta chatter that let two bots loop on 2026-09-25.
791
+ const stayQuiet = silenceable && isMetaNonReply(turn.text);
792
+ const sentinelOrEmpty = containsNoReplySentinel(turn.text) || !turn.text.trim() || stayQuiet;
793
+ if (args.suppressOutput) {
794
+ await poster.abandon();
795
+ const finalText = sentinelOrEmpty ? "" : turn.text;
796
+ return { outcome: { ok: true, reply: finalText.trim(), ...(sentinelOrEmpty ? { silent: true } : {}) }, poster };
797
+ }
798
+ // Unmentioned room turn: a no_reply sentinel (bare, fenced, or with
799
+ // preamble), an empty/whitespace reply, or a stay-quiet phrase means stay
800
+ // quiet. Abandon the poster (it posts nothing) and return a silent outcome;
801
+ // the caller acks without posting anything.
802
+ if (silenceable && sentinelOrEmpty) {
803
+ await poster.abandon();
804
+ return { outcome: { ok: true, reply: "", silent: true }, poster };
805
+ }
806
+ // DM or @-mentioned room: the person asked directly and must get a reply.
807
+ // Strip the silence sentinel so its raw JSON never posts, then let the
808
+ // poster produce its "(no reply)" fallback when there is nothing else.
809
+ const finalText = sentinelOrEmpty ? "" : turn.text;
810
+ await poster.finish(finalText);
811
+ return { outcome: { ok: true, reply: finalText.trim() }, poster };
812
+ }
813
+ catch (err) {
814
+ running = false;
815
+ await poster.abandon();
816
+ const excerpt = err instanceof RuntimeError ? err.stderrExcerpt : err instanceof Error ? err.message : String(err);
817
+ const aborted = (err instanceof RuntimeError && err.aborted) || controller.signal.aborted;
818
+ const started = !(err instanceof RuntimeError) || err.started;
819
+ return { outcome: { ok: false, started, aborted, excerpt, error: err }, poster };
820
+ }
821
+ finally {
822
+ currentTurns.delete(controller);
823
+ }
824
+ };
825
+ const failureReason = (outcome) => outcome.aborted ? STOPPED_BY_OWNER_REASON : `${runtime.id} stopped with an error: ${outcome.excerpt.slice(0, 160)}`;
826
+ const postOne = (target, body, rootEventId, audience) => {
827
+ const root = rootEventId ? { rootEventId } : {};
828
+ return target.kind === "dm"
829
+ ? api.sendDm({ toPersonUid: target.peerUid, body, ...root, ...(audience ? { audience } : {}) })
830
+ : api.sendChannelMessage({ channelId: target.channelId, body, ...root });
831
+ };
832
+ /**
833
+ * Post `parts` in order, starting at `from`. A failure throws a
834
+ * PartialPostError naming how many parts got through, so the caller can save
835
+ * exactly the remainder.
836
+ */
837
+ const sendParts = async (target, parts, from, rootEventId, onDelivered, audience) => {
838
+ for (let i = Math.max(0, from); i < parts.length; i += 1) {
839
+ try {
840
+ await postOne(target, parts[i], rootEventId, audience);
841
+ }
842
+ catch (err) {
843
+ throw new PartialPostError(parts, i, err);
844
+ }
845
+ onDelivered?.(i + 1);
846
+ }
847
+ };
848
+ /** Every outbound body goes through here, so nothing over the limit is ever posted whole. */
849
+ const sendToTarget = (target, body, rootEventId, audience) => sendParts(target, splitBody(body), 0, rootEventId, undefined, audience);
850
+ /**
851
+ * A `send` for one conversation that remembers how much of its last failed
852
+ * body got through, so the in-flight marker can record the remainder.
853
+ */
854
+ const trackingSend = (target, rootEventId) => {
855
+ const last = { value: null };
856
+ const makeSend = (audience) => async (body) => {
857
+ try {
858
+ await sendToTarget(target, body, rootEventId, audience);
859
+ last.value = null;
860
+ }
861
+ catch (err) {
862
+ last.value = err instanceof PartialPostError && err.delivered > 0 ? { parts: err.parts, delivered: err.delivered } : null;
863
+ throw err;
864
+ }
865
+ };
866
+ const send = makeSend(target.kind === "dm" ? "agent" : undefined);
867
+ const sendFinal = makeSend(target.kind === "dm" ? "human" : undefined);
868
+ const saved = (reply) => {
869
+ const parts = splitBody(reply);
870
+ const partial = last.value;
871
+ const sameBody = partial !== null && partial.parts.length === parts.length && partial.parts.every((p, i) => p === parts[i]);
872
+ return { reply, replyParts: parts, replyPartsDelivered: sameBody ? partial.delivered : 0 };
873
+ };
874
+ return { send, sendFinal, saved };
875
+ };
876
+ // ── Scheduled jobs ────────────────────────────────────────────────────────
877
+ /** Newest pending run key already picked up, per jobId (one catch-up per wake). */
878
+ const handledJobFires = new Map();
879
+ /** Runs an older fire of a job that a newer pending run supersedes. */
880
+ const supersededJobRuns = new Set();
881
+ /**
882
+ * Report one fired run as finished. This is the ONLY thing that clears the
883
+ * job's in-progress flag in the cloud, so a failure is logged loudly and the
884
+ * completion is kept for the next poll — the caller still acks, because the
885
+ * work is done and must never run twice.
886
+ */
887
+ const completeJobRun = async (jobId, messageId, completion) => {
888
+ try {
889
+ await api.completeJobRun(config.agentUid, jobId, messageId, completion);
890
+ clearPendingJobCompletion(dir, messageId);
891
+ log("info", `reported run ${messageId} of job ${jobId} as ${completion.status}`);
892
+ return true;
893
+ }
894
+ catch (err) {
895
+ const reason = err instanceof Error ? err.message : String(err);
896
+ const pending = recordPendingJobCompletion(dir, { agentUid: config.agentUid, jobId, messageId, status: completion.status, ...(completion.output !== undefined ? { output: completion.output } : {}), ...(completion.disposition !== undefined ? { disposition: completion.disposition } : {}) }, now());
897
+ log("error", `COULD NOT report run ${messageId} of job ${jobId} as ${completion.status}: ${reason}. ` +
898
+ `Until this is reported the cloud keeps the job marked in progress and skips its next fires. ` +
899
+ `Retry ${pending.attempts} in ${Math.round((pending.nextAttemptAt - now().getTime()) / 1000)}s.`);
900
+ return false;
901
+ }
902
+ };
903
+ /**
904
+ * Re-send completions the cloud has not accepted yet. Never re-runs work.
905
+ * Backed off per entry and given up on by age, so a minute-long network drop
906
+ * recovers instead of burning a fixed attempt budget in twenty seconds.
907
+ */
908
+ const retryPendingJobCompletions = async () => {
909
+ const nowMs = now().getTime();
910
+ for (const pending of readPendingJobCompletions(dir)) {
911
+ // A folder that outlived a deleted, same-named bot: never report another
912
+ // identity's run as this one's (see job-completions.ts, "After a reinstall").
913
+ if (isCompletionForOtherAgent(pending, config.agentUid)) {
914
+ clearPendingJobCompletion(dir, pending.messageId);
915
+ log("warn", `dropped an unreported run ${pending.messageId} of job ${pending.jobId}: it belongs to ${pending.agentUid}, ` +
916
+ `not this bot (${config.agentUid}) — left behind by an earlier bot with the same name`);
917
+ continue;
918
+ }
919
+ if (isCompletionExpired(pending, nowMs)) {
920
+ clearPendingJobCompletion(dir, pending.messageId);
921
+ log("error", `giving up on reporting run ${pending.messageId} of job ${pending.jobId} after ${pending.attempts} attempts ` +
922
+ `over an hour; the cloud has released the job's in-progress slot by itself by now`);
923
+ continue;
924
+ }
925
+ if (!isCompletionDue(pending, nowMs))
926
+ continue;
927
+ await completeJobRun(pending.jobId, pending.messageId, {
928
+ status: pending.status,
929
+ ...(pending.output !== undefined ? { output: pending.output } : {}),
930
+ ...(pending.disposition !== undefined ? { disposition: pending.disposition } : {}),
931
+ });
932
+ }
933
+ };
934
+ /**
935
+ * Kick the completion retries WITHOUT blocking the poll. Awaiting them in the
936
+ * inbox loop meant one hanging completion POST stalled DM delivery behind it.
937
+ * One pass at a time; the tracked promise is drained on shutdown.
938
+ */
939
+ let completionRetryTask = Promise.resolve();
940
+ let completionRetryRunning = false;
941
+ const kickCompletionRetries = () => {
942
+ if (completionRetryRunning)
943
+ return;
944
+ completionRetryRunning = true;
945
+ completionRetryTask = retryPendingJobCompletions()
946
+ .catch((err) => {
947
+ log("warn", `retrying job completions failed: ${err instanceof Error ? err.message : String(err)}`);
948
+ })
949
+ .finally(() => {
950
+ completionRetryRunning = false;
951
+ });
952
+ };
953
+ /** Close a marker out: clear the thinking reaction, ack the message, forget it. */
954
+ const closeMarker = async (marker, isKickoff) => {
955
+ if (marker.target.kind === "dm" && marker.reactionEventId) {
956
+ await api
957
+ .setReaction({ peerUid: marker.target.peerUid, messageId: marker.reactionEventId, emoji: THINKING_EMOJI }, false)
958
+ .catch(() => false);
959
+ }
960
+ if (!isKickoff) {
961
+ markProcessed(dir, marker.messageId, now().toISOString());
962
+ await api.ackInbox(config.agentUid, marker.messageId).catch((err) => {
963
+ log("warn", `ack for recovered ${marker.messageId} failed: ${err instanceof Error ? err.message : String(err)}`);
964
+ });
965
+ }
966
+ clearInflight(dir, marker.messageId);
967
+ };
968
+ /**
969
+ * A scheduled run's marker is about to be closed: report its REAL outcome to
970
+ * the cloud first, and record that it was reported, BEFORE the ack.
971
+ *
972
+ * - A saved reply means the work finished and only delivery failed: that is
973
+ * `succeeded`, with the report as output — including when delivery was
974
+ * given up on as impossible. The owner not getting the DM does not undo
975
+ * the work, and not reporting would jam the job for an hour.
976
+ * - No saved reply means the turn was cut off: `failed`.
977
+ * - A marker that says it was already reported is never reported again: the
978
+ * server's complete is last-write-wins, so a second report after a crash
979
+ * between reporting and acking would overwrite the recorded outcome.
980
+ */
981
+ const reportRecoveredJobRun = async (marker) => {
982
+ if (!marker.jobId || marker.jobCompleted)
983
+ return;
984
+ const finished = typeof marker.reply === "string";
985
+ // A run that failed or was stopped carries its real reason; only a marker
986
+ // with none was genuinely cut off by a restart.
987
+ const failure = marker.jobFailure;
988
+ const completion = finished
989
+ ? { status: "succeeded", output: marker.jobOutput ?? jobRunOutput(stripJobHeader(marker.reply, marker.jobHeader)) }
990
+ : failure
991
+ ? {
992
+ status: "failed",
993
+ disposition: failure.reason.slice(0, 256),
994
+ ...(failure.excerpt ? { output: jobRunOutput(failure.excerpt) } : {}),
995
+ }
996
+ : { status: "failed", disposition: "the run was cut off when the bot restarted" };
997
+ await completeJobRun(marker.jobId, marker.messageId, completion);
998
+ patchInflight(dir, marker.messageId, { jobCompleted: completion.status });
999
+ };
1000
+ /**
1001
+ * The body a recovery posts. A scheduled run's FIRST message carries its
1002
+ * header, so a report delivered by this path still reads as a scheduled run
1003
+ * and not as an unsolicited DM. A body saved in parts already has the header
1004
+ * baked in (the job's own send put it there), so it is never added twice.
1005
+ */
1006
+ const recoveryBody = (marker) => {
1007
+ // A job that failed on its own says why; "I was restarted" only when it was.
1008
+ const reason = marker.jobFailure?.reason ?? RESTARTED_REASON;
1009
+ const body = marker.reply ?? didNotFinishText(reason, (marker.progressPosted ?? 0) > 0);
1010
+ const headerAlreadyOut = (marker.progressPosted ?? 0) > 0 || body.startsWith(marker.jobHeader ?? "\u0000");
1011
+ return marker.jobHeader && !headerAlreadyOut ? `${marker.jobHeader}${body}` : body;
1012
+ };
1013
+ /**
1014
+ * A turn that was cut off by a restart (or whose answer never got posted):
1015
+ * tell the person instead of running it again. Returns false when the post
1016
+ * failed, so the marker stays for the next try.
1017
+ *
1018
+ * The retry is bounded. Some bodies can never be delivered (the server
1019
+ * refuses them however often they are sent), and re-sending one on every
1020
+ * poll used to hammer the API for ever while the person saw nothing at all.
1021
+ * After RECOVERY_ATTEMPT_LIMIT consecutive failures the bot posts one short
1022
+ * apology it can actually deliver, closes the marker, and stops.
1023
+ */
1024
+ const recoverInflight = async (marker) => {
1025
+ const isKickoff = marker.messageId === KICKOFF_MESSAGE_ID;
1026
+ // A scheduled run left in this folder by a DIFFERENT bot (adopt into a
1027
+ // folder that survived without its bot.json). Posting its report would put
1028
+ // another bot's work in this bot's DM, and completing it would be a write
1029
+ // under the wrong identity — the thing job-completions.ts rules out. Drop
1030
+ // it: no post, no completion, no ack (the item is not in this inbox). The
1031
+ // cloud releases that job's slot by itself within the hour. A marker with
1032
+ // no recorded agent predates the field and is recovered as before.
1033
+ if (marker.jobId && marker.agentUid && marker.agentUid !== config.agentUid) {
1034
+ clearInflight(dir, marker.messageId);
1035
+ log("warn", `dropped an unfinished scheduled run ${marker.messageId} of job ${marker.jobId}: it belongs to ${marker.agentUid}, ` +
1036
+ `not this bot (${config.agentUid}) — left behind by an earlier bot in this folder; not posted, not reported`);
1037
+ return true;
1038
+ }
1039
+ if (!isKickoff && isProcessed(readInboxState(dir), marker.messageId)) {
1040
+ clearInflight(dir, marker.messageId);
1041
+ return true;
1042
+ }
1043
+ // An unmentioned room turn (silenceable) that never produced a saved
1044
+ // reply: the person never asked the bot to answer. Posting "I didn't
1045
+ // finish that — I was restarted…" into that room is noise (2026-09-25:
1046
+ // two bots posted the notice in a channel where neither was mentioned).
1047
+ // Ack and clear silently; a saved `reply` still posts below because a
1048
+ // real answer that only failed delivery is worth keeping.
1049
+ if (!isKickoff &&
1050
+ marker.silenceable === true &&
1051
+ !marker.jobId &&
1052
+ (marker.reply === undefined || marker.reply.trim() === "")) {
1053
+ log("info", `recovered unmentioned room turn ${marker.messageId} silently (no saved reply); not posting a restart notice`);
1054
+ await closeMarker(marker, isKickoff);
1055
+ return true;
1056
+ }
1057
+ const parts = marker.replyParts ?? splitBody(recoveryBody(marker));
1058
+ const from = marker.replyParts ? Math.min(Math.max(0, marker.replyPartsDelivered ?? 0), parts.length) : 0;
1059
+ try {
1060
+ await sendParts(marker.target, parts, from, marker.rootEventId, (count) => patchInflight(dir, marker.messageId, { replyParts: parts, replyPartsDelivered: count }));
1061
+ }
1062
+ catch (err) {
1063
+ const reason = err instanceof Error ? err.message : String(err);
1064
+ const attempt = (marker.recoveryFailures ?? 0) + 1;
1065
+ if (attempt >= RECOVERY_ATTEMPT_LIMIT) {
1066
+ log("warn", `gave up delivering the reply to ${marker.messageId} after ${attempt} attempts (${reason}); told the owner instead`);
1067
+ if (marker.jobId) {
1068
+ // Nobody asked, so "ask me again" is wrong. The header goes on only if
1069
+ // no part of this run reached the chat yet.
1070
+ const headerOut = (marker.progressPosted ?? 0) > 0 || (err instanceof PartialPostError ? err.delivered : from) > 0;
1071
+ const text = marker.jobHeader && !headerOut ? `${marker.jobHeader}${UNDELIVERABLE_JOB_REPORT_TEXT}` : UNDELIVERABLE_JOB_REPORT_TEXT;
1072
+ await postOne(marker.target, text, marker.rootEventId).catch(() => undefined);
1073
+ // Giving up on DELIVERY is not giving up on the RUN: report it.
1074
+ await reportRecoveredJobRun(marker);
1075
+ }
1076
+ else {
1077
+ await postOne(marker.target, UNDELIVERABLE_REPLY_TEXT, marker.rootEventId).catch(() => undefined);
1078
+ }
1079
+ await closeMarker(marker, isKickoff);
1080
+ return true;
1081
+ }
1082
+ const delivered = err instanceof PartialPostError ? err.delivered : from;
1083
+ patchInflight(dir, marker.messageId, { replyParts: parts, replyPartsDelivered: delivered, recoveryFailures: attempt });
1084
+ log("warn", `could not tell the owner about unfinished ${marker.messageId} (attempt ${attempt}/${RECOVERY_ATTEMPT_LIMIT}): ${reason}`);
1085
+ return false;
1086
+ }
1087
+ await reportRecoveredJobRun(marker);
1088
+ await closeMarker(marker, isKickoff);
1089
+ log("info", marker.reply
1090
+ ? `posted the saved reply for ${marker.messageId}`
1091
+ : `told the owner ${marker.messageId} did not finish (${marker.jobFailure ? marker.jobFailure.reason : "restart"}); not re-run`);
1092
+ return true;
1093
+ };
1094
+ /**
1095
+ * First-start kickoff: ONE model turn on config.kickoff in the owner DM
1096
+ * session, as if the owner had sent it, answered as a DM (with its
1097
+ * in-between messages). The thinking reaction goes on the intro message
1098
+ * (there is no owner message to mark). A failure never crashes the loop.
1099
+ */
1100
+ const runKickoff = async (introEventId) => {
1101
+ const prompt = (config.kickoff ?? "").trim();
1102
+ if (!prompt || stopping || state !== "running")
1103
+ return;
1104
+ const peerUid = config.ownerUid;
1105
+ const tReceived = now().getTime();
1106
+ log("info", `kickoff turn starting (${prompt.length} chars)`);
1107
+ const thinking = introEventId
1108
+ ? await api.setReaction({ peerUid, messageId: introEventId, emoji: THINKING_EMOJI }, true).catch(() => false)
1109
+ : false;
1110
+ const clearThinking = async () => {
1111
+ if (thinking && introEventId) {
1112
+ await api.setReaction({ peerUid, messageId: introEventId, emoji: THINKING_EMOJI }, false).catch(() => false);
1113
+ }
1114
+ };
1115
+ const target = { kind: "dm", peerUid };
1116
+ const { send, sendFinal, saved } = trackingSend(target);
1117
+ const { outcome, poster } = await runTurnWithProgress({
1118
+ marker: { messageId: KICKOFF_MESSAGE_ID, target, ...(thinking && introEventId ? { reactionEventId: introEventId } : {}) },
1119
+ prompt,
1120
+ sessionScope: "dm",
1121
+ tReceived,
1122
+ send,
1123
+ sendFinal,
1124
+ // The intro already said the bot is starting, and the app shows it thinking.
1125
+ workingNotice: false,
1126
+ });
1127
+ await clearThinking();
1128
+ if (outcome.ok) {
1129
+ // The kickoff never runs silenceable, so outcome.silent is never set here.
1130
+ if (poster.postedCount === 0) {
1131
+ patchInflight(dir, KICKOFF_MESSAGE_ID, saved(outcome.reply));
1132
+ log("warn", "kickoff reply failed to send; it is posted on the next start");
1133
+ return;
1134
+ }
1135
+ clearInflight(dir, KICKOFF_MESSAGE_ID);
1136
+ noteSetupReply(outcome.reply);
1137
+ if (readBotConfig(dir)?.kickoffPendingSignIn)
1138
+ patchBotConfig(dir, { kickoffPendingSignIn: false });
1139
+ const st = readStatus();
1140
+ setStatus({ repliesSent: (st?.repliesSent ?? 0) + 1, lastActivityAt: now().toISOString() });
1141
+ log("info", `kickoff reply sent (${outcome.reply.length} chars)`);
1142
+ return;
1143
+ }
1144
+ if (!outcome.aborted && poster.postedCount === 0 && isRuntimeSignInFailure(outcome.excerpt)) {
1145
+ // Nothing ran: hold the kickoff until the tool can sign in, then run it.
1146
+ clearInflight(dir, KICKOFF_MESSAGE_ID);
1147
+ patchBotConfig(dir, { kickoffPendingSignIn: true });
1148
+ markSignInBroken(outcome.excerpt);
1149
+ await tellSignInNeeded("dm", send, true);
1150
+ return;
1151
+ }
1152
+ if (readBotConfig(dir)?.kickoffPendingSignIn)
1153
+ patchBotConfig(dir, { kickoffPendingSignIn: false });
1154
+ let count = 0;
1155
+ if (!outcome.aborted)
1156
+ count = recordTurnFailure(outcome.error, "dm").count;
1157
+ const body = outcome.started && (outcome.aborted || poster.postedCount > 0)
1158
+ ? `${didNotFinishText(failureReason(outcome), poster.postedCount > 0)} Send me any message and I'll pick up from there.`
1159
+ : `Sorry — I couldn't get started on my own (${runtime.id} failed: ${outcome.excerpt.slice(0, 160)}). Send me any message and I'll pick up from there, or check that ${runtime.id} is signed in on this computer.`;
1160
+ try {
1161
+ await send(body);
1162
+ clearInflight(dir, KICKOFF_MESSAGE_ID);
1163
+ }
1164
+ catch {
1165
+ /* the marker stays: the next start says it did not finish */
1166
+ }
1167
+ log("error", `kickoff turn ${outcome.aborted ? "stopped by the owner" : `failed (${count}/${FAILURE_LIMIT} in window)`}; not retried`);
1168
+ if (!outcome.aborted && failures.exceeded(now().getTime())) {
1169
+ await finish("failed", 0, `${FAILURE_LIMIT} model failures in 10 minutes; last: ${outcome.excerpt}`);
1170
+ }
1171
+ };
1172
+ /**
1173
+ * One fired job: a model turn on the job's own prompt, in the job's own
1174
+ * session, reported in the owner's DM under a "scheduled job" header, and
1175
+ * reported to the cloud exactly once.
1176
+ */
1177
+ const handleJobItem = async (item, target) => {
1178
+ const id = item.messageId;
1179
+ const { job, jobId } = target;
1180
+ const peerUid = target.peerUid;
1181
+ // An older pending run of this job: a newer one runs instead. Reported so
1182
+ // the cloud releases the slot and the history says what happened.
1183
+ if (supersededJobRuns.has(id)) {
1184
+ await completeJobRun(jobId, id, { status: "skipped-precondition", disposition: SUPERSEDED_DISPOSITION });
1185
+ markProcessed(dir, id, now().toISOString());
1186
+ await api.ackInbox(config.agentUid, id);
1187
+ supersededJobRuns.delete(id);
1188
+ log("info", `skipped older run ${id} of job ${jobId}: a newer pending run of the same job runs instead`);
1189
+ return;
1190
+ }
1191
+ const catchUp = isCatchUpRun(job, now());
1192
+ const quietJob = target.job.quiet === true && await jobQuietOptionEnabled();
1193
+ const attempt = (attempts.get(id) ?? 0) + 1;
1194
+ attempts.set(id, attempt);
1195
+ const tReceived = now().getTime();
1196
+ log("info", `received scheduled run ${id} of job ${jobId}${catchUp ? " (catch-up)" : ""}`);
1197
+ // The header goes on the first message of the turn, so the owner sees at a
1198
+ // glance that this is a scheduled run and not a reply to something they
1199
+ // said. It rides on the marker too, so a report posted by the recovery path
1200
+ // after a restart carries it as well.
1201
+ //
1202
+ // Every post goes through the SAME split/track path as any other reply
1203
+ // (trackingSend → splitBody), so a long report is delivered in parts rather
1204
+ // than refused whole. The header is only counted as delivered once a part
1205
+ // actually reached the chat, and `saved` records the body EXACTLY as it was
1206
+ // attempted (header included), so the parts the recovery path resends line
1207
+ // up with the parts already delivered and nothing is repeated or dropped.
1208
+ const header = jobReportHeader(job, catchUp, timeZone);
1209
+ const markerTarget = { kind: "dm", peerUid };
1210
+ const tracking = trackingSend(markerTarget);
1211
+ let headerOut = false;
1212
+ // What actually reached the chat, kept apart from "was anything posted".
1213
+ // `poster.postedCount` counts progress posts too, so a turn that said
1214
+ // "Checking the dashboards…" and then failed to post its REPORT would look
1215
+ // delivered — and for a scheduled run the report is the only thing the
1216
+ // owner sees; nobody will ask again.
1217
+ const deliveredBodies = [];
1218
+ let anySendFailed = false;
1219
+ /** The body of the LAST send, when that send failed (the resume point). */
1220
+ let lastFailedBody = null;
1221
+ const withHeader = (body) => (headerOut ? body : `${header}${body}`);
1222
+ const makeSend = (fn) => async (body) => {
1223
+ const full = withHeader(body);
1224
+ try {
1225
+ await fn(full);
1226
+ headerOut = true;
1227
+ deliveredBodies.push(body);
1228
+ lastFailedBody = null;
1229
+ }
1230
+ catch (err) {
1231
+ anySendFailed = true;
1232
+ lastFailedBody = full;
1233
+ if (err instanceof PartialPostError && err.delivered > 0)
1234
+ headerOut = true;
1235
+ throw err;
1236
+ }
1237
+ };
1238
+ const send = makeSend(tracking.send);
1239
+ const sendFinal = makeSend(tracking.sendFinal);
1240
+ const normalize = (text) => text.replace(/\s+/g, " ").trim();
1241
+ /**
1242
+ * True only when the final report itself reached the chat in full. With no
1243
+ * failed post at all, the poster delivered everything it chose to (it skips
1244
+ * a final that repeats a progress post, so "no failures" is the reliable
1245
+ * signal). After any failure, the report must be inside a fully delivered
1246
+ * post. Erring towards resending: a duplicate beats a report that vanished.
1247
+ */
1248
+ const reportDelivered = (reply) => poster.postedCount > 0 && (!anySendFailed || deliveredBodies.some((b) => normalize(b).includes(normalize(reply))));
1249
+ /**
1250
+ * The report, saved for resending. When the LAST post was the report and
1251
+ * part of it already got through, resume that exact body at the parts
1252
+ * delivered, so nothing is repeated. Otherwise resend the report whole,
1253
+ * under its own header: the resend may come much later (after a restart)
1254
+ * and must still read as a scheduled run.
1255
+ */
1256
+ const savedReport = (reply) => {
1257
+ const jobOutput = jobRunOutput(reply);
1258
+ const failed = lastFailedBody;
1259
+ if (failed !== null && failed.trimEnd().endsWith(reply)) {
1260
+ const resumed = tracking.saved(failed);
1261
+ if ((resumed.replyPartsDelivered ?? 0) > 0)
1262
+ return { ...resumed, jobOutput };
1263
+ }
1264
+ return { ...tracking.saved(`${header}${reply}`), jobOutput };
1265
+ };
1266
+ /** Remember that this run's outcome is reported, before the ack. */
1267
+ const markReported = (status) => {
1268
+ patchInflight(dir, id, { jobCompleted: status });
1269
+ };
1270
+ const { outcome, poster } = await runTurnWithProgress({
1271
+ marker: { messageId: id, target: markerTarget, jobId, jobHeader: header, agentUid: config.agentUid },
1272
+ prompt: buildJobTurnPrompt(job, { now: now(), timeZone }),
1273
+ sessionScope: target.sessionScope,
1274
+ tReceived,
1275
+ send,
1276
+ sendFinal,
1277
+ // A scheduled run has nobody waiting in a chat: no "working on it" filler.
1278
+ workingNotice: false,
1279
+ ...(quietJob ? { suppressOutput: true } : {}),
1280
+ });
1281
+ if (outcome.ok) {
1282
+ if (!quietJob && !reportDelivered(outcome.reply)) {
1283
+ // The work finished but the REPORT did not fully reach the DM — even if
1284
+ // a progress post did. Keep it (and the run un-reported and un-acked)
1285
+ // so the next poll delivers it, resuming at the parts already read,
1286
+ // before the run is completed. Never re-run.
1287
+ patchInflight(dir, id, savedReport(outcome.reply));
1288
+ log("warn", `report for run ${id} of job ${jobId} was not fully delivered; it is resent on the next poll`);
1289
+ return;
1290
+ }
1291
+ await completeJobRun(jobId, id, { status: "succeeded", output: jobRunOutput(outcome.reply) });
1292
+ markReported("succeeded");
1293
+ markProcessed(dir, id, now().toISOString());
1294
+ await api.ackInbox(config.agentUid, id);
1295
+ clearInflight(dir, id);
1296
+ attempts.delete(id);
1297
+ setStatus({
1298
+ ...(!quietJob ? { repliesSent: (readStatus()?.repliesSent ?? 0) + 1 } : {}),
1299
+ lastActivityAt: now().toISOString(),
1300
+ });
1301
+ log("info", `job ${jobId} run ${id} done (${outcome.reply.length} chars${catchUp ? ", catch-up" : ""})`);
1302
+ return;
1303
+ }
1304
+ if (!outcome.aborted && poster.postedCount === 0 && isRuntimeSignInFailure(outcome.excerpt)) {
1305
+ // Nothing ran: hold the run until the coding tool can sign in again. It
1306
+ // is neither acked nor reported succeeded — the work is still owed.
1307
+ clearInflight(dir, id);
1308
+ attempts.delete(id);
1309
+ markSignInBroken(outcome.excerpt);
1310
+ await tellSignInNeeded(`job:${jobId}`, (body) => sendToTarget(markerTarget, body));
1311
+ log("warn", `kept scheduled run ${id} of job ${jobId} until ${runtime.id} can sign in again`);
1312
+ return; // un-acked, un-reported: run once the sign-in works
1313
+ }
1314
+ if (!outcome.started) {
1315
+ // The model never ran, so nothing happened outside this Mac: retry.
1316
+ clearInflight(dir, id);
1317
+ const { excerpt } = recordTurnFailure(outcome.error, target.sessionScope);
1318
+ if (failures.exceeded(now().getTime())) {
1319
+ await finish("failed", 0, `${FAILURE_LIMIT} model failures in 10 minutes; last: ${excerpt}`);
1320
+ return;
1321
+ }
1322
+ if (attempt >= MAX_ATTEMPTS_PER_MESSAGE) {
1323
+ await send(`I couldn't run this one — ${runtime.id} kept failing: ${excerpt.slice(0, 160)}.`).catch(() => undefined);
1324
+ await completeJobRun(jobId, id, { status: "failed", output: jobRunOutput(excerpt), disposition: `${runtime.id} could not be started` });
1325
+ markReported("failed");
1326
+ markProcessed(dir, id, now().toISOString());
1327
+ await api.ackInbox(config.agentUid, id);
1328
+ attempts.delete(id);
1329
+ return;
1330
+ }
1331
+ await sleep(fullJitterDelayMs(failures.count(now().getTime()), { random: deps.random }));
1332
+ return; // un-acked; retried on the next poll
1333
+ }
1334
+ // The model started: it may already have done things, so never re-run.
1335
+ if (!outcome.aborted)
1336
+ recordTurnFailure(outcome.error, target.sessionScope);
1337
+ const reason = failureReason(outcome);
1338
+ // Saved BEFORE the post: if "did not finish" cannot be posted, recovery
1339
+ // reports and says this real reason, not "the bot was restarted".
1340
+ patchInflight(dir, id, { jobFailure: { reason, ...(outcome.excerpt ? { excerpt: outcome.excerpt } : {}) } });
1341
+ try {
1342
+ await send(didNotFinishText(reason, poster.postedCount > 0));
1343
+ }
1344
+ catch (err) {
1345
+ // Leave it un-acked with the marker: the next poll reports it.
1346
+ log("warn", `could not post "did not finish" for run ${id} of job ${jobId}: ${err instanceof Error ? err.message : String(err)}`);
1347
+ return;
1348
+ }
1349
+ await completeJobRun(jobId, id, { status: "failed", output: jobRunOutput(outcome.excerpt), disposition: reason.slice(0, 256) });
1350
+ markReported("failed");
1351
+ markProcessed(dir, id, now().toISOString());
1352
+ await api.ackInbox(config.agentUid, id);
1353
+ clearInflight(dir, id);
1354
+ attempts.delete(id);
1355
+ log("error", `scheduled run ${id} of job ${jobId} ${outcome.aborted ? "stopped by the owner" : "failed"} after it started; not re-run`);
1356
+ if (!outcome.aborted && failures.exceeded(now().getTime())) {
1357
+ await finish("failed", 0, `${FAILURE_LIMIT} model failures in 10 minutes; last: ${outcome.excerpt}`);
1358
+ }
1359
+ };
1360
+ /**
1361
+ * The instant answer to "Explain HQ first" (setup-explain.ts). Returns true
1362
+ * when it answered the message (sent, marked processed, acked); false sends
1363
+ * the message down the normal model path, so nothing here can leave a
1364
+ * message unanswered.
1365
+ */
1366
+ const sendSetupExplanation = async (item, text, attachments) => {
1367
+ const id = item.messageId;
1368
+ let reply;
1369
+ try {
1370
+ const due = setupExplainDue({
1371
+ text,
1372
+ attachments,
1373
+ state: readSetupIntroState(dir),
1374
+ progress: readSetupProgressIntro(resolveBotMemoryDir(config)),
1375
+ });
1376
+ if (!due)
1377
+ return false;
1378
+ const explainer = loadSetupExplainer(config.hqRoot, config.workerDir);
1379
+ if (!explainer) {
1380
+ log("warn", `no usable explain-hq.md in ${config.workerDir}; answering ${id} with a model turn`);
1381
+ return false;
1382
+ }
1383
+ reply = explainer.reply;
1384
+ }
1385
+ catch (err) {
1386
+ log("warn", `setup explanation check failed (${err instanceof Error ? err.message : String(err)}); answering ${id} with a model turn`);
1387
+ return false;
1388
+ }
1389
+ try {
1390
+ await sendToTarget({ kind: "dm", peerUid: config.ownerUid }, reply, item.rootEventId, "human");
1391
+ }
1392
+ catch (err) {
1393
+ log("warn", `setup explanation for ${id} could not be sent (${err instanceof Error ? err.message : String(err)}); answering with a model turn`);
1394
+ return false;
1395
+ }
1396
+ markProcessed(dir, id, now().toISOString());
1397
+ try {
1398
+ patchSetupIntroState(dir, { explainedAt: now().toISOString(), notePending: true, offeredAt: undefined });
1399
+ }
1400
+ catch (err) {
1401
+ log("warn", `setup intro state not updated: ${err instanceof Error ? err.message : String(err)}`);
1402
+ }
1403
+ await api.ackInbox(config.agentUid, id);
1404
+ setStatus({ repliesSent: (readStatus()?.repliesSent ?? 0) + 1, lastActivityAt: now().toISOString() });
1405
+ log("info", `answered ${id} with the setup explanation (no model turn)`);
1406
+ return true;
1407
+ };
1408
+ const handleItem = async (item) => {
1409
+ const id = item.messageId;
1410
+ const inboxState = readInboxState(dir);
1411
+ if (isProcessed(inboxState, id)) {
1412
+ // Replied before a crash; only the ack was lost.
1413
+ await api.ackInbox(config.agentUid, id);
1414
+ clearInflight(dir, id);
1415
+ log("info", `acked already-processed ${id}`);
1416
+ return;
1417
+ }
1418
+ // A turn on this message already started once and was cut off: say so,
1419
+ // never run it again.
1420
+ const inflight = readInflight(dir, id);
1421
+ if (inflight) {
1422
+ await recoverInflight(inflight);
1423
+ return;
1424
+ }
1425
+ const target = resolveReplyTarget(item, config);
1426
+ // A bot/agent sender that did not @mention this bot must never start a model
1427
+ // turn: two bots each answering the other's plain room posts loop forever
1428
+ // (observed 2026-09-25). An explicit @mention still gets answered below.
1429
+ if (target && target.kind === "room" && !target.mentioned && isAgentSender(item.fromPersonUid)) {
1430
+ markProcessed(dir, id, now().toISOString());
1431
+ await api.ackInbox(config.agentUid, id);
1432
+ log("info", `ignored inbox item ${id} (room ${target.channelId}, unmentioned message from bot ${item.fromPersonUid ?? "unknown"}; not starting a turn)`);
1433
+ return;
1434
+ }
1435
+ if (!target) {
1436
+ // A job item the bot cannot run. When it names a job, the cloud is still
1437
+ // holding that job's in-progress slot: acking alone would jam every later
1438
+ // fire of it for an hour, and the next fire would be just as unusable —
1439
+ // the same jam, forever. Report it so the slot is released.
1440
+ const unusableJobId = isJobItem(item) && typeof item.jobId === "string" ? item.jobId.trim() : "";
1441
+ if (unusableJobId) {
1442
+ await completeJobRun(unusableJobId, id, {
1443
+ status: "skipped-precondition",
1444
+ disposition: "the fired run carried no prompt, so there was nothing to run",
1445
+ });
1446
+ }
1447
+ markProcessed(dir, id, now().toISOString());
1448
+ await api.ackInbox(config.agentUid, id);
1449
+ log("info", isJobItem(item)
1450
+ ? `ignored job item ${id}: it carries no ${unusableJobId ? "prompt" : "job id"}`
1451
+ : `ignored inbox item ${id} (${item.channel || "dm"}${item.channelScope ? ` ${item.channelScope}` : ""}, not addressed to me)`);
1452
+ return;
1453
+ }
1454
+ if (target.kind === "job") {
1455
+ await handleJobItem(item, target);
1456
+ return;
1457
+ }
1458
+ const messageText = (item.text ?? "").trim();
1459
+ // A message can be only a file (no text): that is still a message to answer.
1460
+ const attachmentRefs = inboxEntryAttachments(item);
1461
+ if (!messageText && attachmentRefs.length === 0) {
1462
+ markProcessed(dir, id, now().toISOString());
1463
+ await api.ackInbox(config.agentUid, id);
1464
+ return;
1465
+ }
1466
+ // Setup bot: "Explain HQ first" clicked while that question is pending is
1467
+ // answered with the fixed explanation, with no model turn.
1468
+ const ownerDm = target.kind === "dm" && target.peerUid === config.ownerUid;
1469
+ if (isSetupBot && ownerDm && (await sendSetupExplanation(item, messageText, attachmentRefs.length)))
1470
+ return;
1471
+ const attempt = (attempts.get(id) ?? 0) + 1;
1472
+ attempts.set(id, attempt);
1473
+ const reactionId = item.eventId ?? id;
1474
+ const tReceived = now().getTime();
1475
+ const where = target.kind === "room" ? `room ${target.channelId}` : "dm";
1476
+ log("info", `received ${id} (${messageText.length} chars${attachmentRefs.length ? `, ${attachmentRefs.length} attachment${attachmentRefs.length === 1 ? "" : "s"}` : ""}, ${where})`);
1477
+ // Rooms: an UNMENTIONED responder (a plain room message)
1478
+ // claims first so the owner's several bots produce one answer, not one
1479
+ // each. A bot that was @mentioned never claims — it was asked by name and
1480
+ // must answer even if a sibling bot was faster. The same claimer
1481
+ // re-acquires on a retry (server lease), so the claim is safe to repeat
1482
+ // when a failed turn leaves the item un-acked.
1483
+ if (target.kind === "room" && !target.mentioned) {
1484
+ const claimed = await api.claimChannelUnit(target.channelId, target.unitId);
1485
+ if (!claimed) {
1486
+ markProcessed(dir, id, now().toISOString());
1487
+ await api.ackInbox(config.agentUid, id);
1488
+ attempts.delete(id);
1489
+ log("info", `skipped ${id}: claimed by another responder`);
1490
+ return;
1491
+ }
1492
+ }
1493
+ // Thinking indicator: DMs use a reaction; rooms use the ephemeral
1494
+ // agent-status wake (cleared client-side by a post, so it is re-posted
1495
+ // after each in-between message). It stays on until the turn ends.
1496
+ const thinking = target.kind === "dm"
1497
+ ? await api.setReaction({ peerUid: target.peerUid, messageId: reactionId, emoji: THINKING_EMOJI }, true)
1498
+ : false;
1499
+ if (target.kind === "room")
1500
+ await api.postAgentStatus(target.channelId, ROOM_THINKING_STATUS, target.rootEventId);
1501
+ const clearThinking = async () => {
1502
+ if (thinking && target.kind === "dm") {
1503
+ await api.setReaction({ peerUid: target.peerUid, messageId: reactionId, emoji: THINKING_EMOJI }, false);
1504
+ }
1505
+ };
1506
+ // Attachments are fetched here, in the runner, so every runtime sees them
1507
+ // the same way: the turn text names each saved file (or why it is missing).
1508
+ let text = messageText;
1509
+ const readDirs = [];
1510
+ if (attachmentRefs.length > 0) {
1511
+ const attachmentsDir = botAttachmentsDir(dir);
1512
+ const files = await downloadTurnAttachments({
1513
+ attachments: attachmentRefs,
1514
+ botEntityUid: config.agentUid,
1515
+ attachmentsDir,
1516
+ resolveBotCompanyUids: async () => (await api.getAgent(config.agentUid)).companyMemberships ?? [],
1517
+ ...(deps.downloadAttachment ? { download: deps.downloadAttachment } : {}),
1518
+ });
1519
+ for (const f of files) {
1520
+ if (f.error)
1521
+ log("warn", `attachment ${f.name} on ${id} not downloaded: ${f.error}`);
1522
+ else
1523
+ log("info", `attachment ${f.name} on ${id} saved (${f.sizeBytes ?? 0} bytes)`);
1524
+ }
1525
+ if (files.some((f) => f.localPath))
1526
+ readDirs.push(attachmentsDir);
1527
+ const block = attachmentTurnBlock(files);
1528
+ text = text ? `${text}\n\n${block}` : block;
1529
+ }
1530
+ const sessionScope = target.kind === "room" ? target.sessionScope : dmSessionScope(target.peerUid, config.ownerUid);
1531
+ // The turn after the app sent the explanation: tell the model it went out.
1532
+ const setupNote = isSetupBot && ownerDm && readSetupIntroState(dir).notePending === true;
1533
+ const ownerText = setupNote ? `${SETUP_EXPLAINED_NOTE}\n\n${text}` : text;
1534
+ const prompt = target.kind === "room" ? await buildRoomTurn(item, target, text) : target.peerUid === config.ownerUid ? ownerText :
1535
+ `[HQ DM sender: ${target.peerUid}; owner: ${config.ownerUid}; sender is not the owner.]\n\n${text}`;
1536
+ const markerTarget = target.kind === "dm" ? { kind: "dm", peerUid: target.peerUid } : { kind: "room", channelId: target.channelId };
1537
+ const { send, sendFinal, saved } = trackingSend(markerTarget, item.rootEventId);
1538
+ const { outcome, poster } = await runTurnWithProgress({
1539
+ marker: {
1540
+ messageId: id,
1541
+ target: markerTarget,
1542
+ ...(item.rootEventId ? { rootEventId: item.rootEventId } : {}),
1543
+ ...(thinking ? { reactionEventId: reactionId } : {}),
1544
+ },
1545
+ prompt,
1546
+ sessionScope,
1547
+ tReceived,
1548
+ send,
1549
+ sendFinal,
1550
+ silenceable: target.kind === "room" && !target.mentioned,
1551
+ ...(readDirs.length ? { readDirs } : {}),
1552
+ ...(target.kind === "room"
1553
+ ? { afterProgressPost: () => api.postAgentStatus(target.channelId, ROOM_THINKING_STATUS, target.rootEventId) }
1554
+ : {}),
1555
+ });
1556
+ // The model saw the note once its turn ran. A turn that never started, or
1557
+ // one held for a coding-tool sign-in, runs again and keeps the note for it.
1558
+ if (setupNote && (outcome.ok || (outcome.started && !isRuntimeSignInFailure(outcome.excerpt)))) {
1559
+ try {
1560
+ patchSetupIntroState(dir, { notePending: false });
1561
+ }
1562
+ catch (err) {
1563
+ log("warn", `setup intro state not updated: ${err instanceof Error ? err.message : String(err)}`);
1564
+ }
1565
+ }
1566
+ if (outcome.ok && ownerDm)
1567
+ noteSetupReply(outcome.reply);
1568
+ if (outcome.ok) {
1569
+ // A no_reply sentinel or intentionally empty reply: ack silently, post nothing.
1570
+ if (outcome.silent) {
1571
+ markProcessed(dir, id, now().toISOString());
1572
+ await api.ackInbox(config.agentUid, id);
1573
+ clearInflight(dir, id);
1574
+ attempts.delete(id);
1575
+ await clearThinking();
1576
+ log("info", `no_reply sentinel or empty reply for ${id} (${where}); acked silently`);
1577
+ return;
1578
+ }
1579
+ // Known gap (#614, not changed here): a progress post counts, so a final answer that fails after one is not resent.
1580
+ if (poster.postedCount === 0) {
1581
+ // The work finished but nothing reached the chat: keep the answer —
1582
+ // in the parts it will be posted as, and how many of them already got
1583
+ // through — so the next poll (or a restart) delivers the remainder
1584
+ // instead of running the turn again or repeating what was read.
1585
+ patchInflight(dir, id, saved(outcome.reply));
1586
+ await clearThinking();
1587
+ log("warn", `reply to ${id} could not be posted; it is retried on the next poll`);
1588
+ return;
1589
+ }
1590
+ markProcessed(dir, id, now().toISOString());
1591
+ await api.ackInbox(config.agentUid, id);
1592
+ clearInflight(dir, id);
1593
+ attempts.delete(id);
1594
+ await clearThinking();
1595
+ const st = readStatus();
1596
+ setStatus({ repliesSent: (st?.repliesSent ?? 0) + 1, lastActivityAt: now().toISOString() });
1597
+ log("info", `replied to ${id} (${outcome.reply.length} chars, ${where}, ${poster.postedCount} post${poster.postedCount === 1 ? "" : "s"})`);
1598
+ return;
1599
+ }
1600
+ await clearThinking();
1601
+ if (!outcome.aborted && poster.postedCount === 0 && isRuntimeSignInFailure(outcome.excerpt)) {
1602
+ // The tool refused before doing anything: keep the message for when it
1603
+ // can sign in again, and say so once in this conversation.
1604
+ clearInflight(dir, id);
1605
+ attempts.delete(id);
1606
+ markSignInBroken(outcome.excerpt);
1607
+ const conversation = markerTarget.kind === "dm" ? sessionScope : `room:${markerTarget.channelId}:${item.rootEventId ?? ""}`;
1608
+ await tellSignInNeeded(conversation, send);
1609
+ log("warn", `kept ${id} until ${runtime.id} can sign in again`);
1610
+ return; // un-acked: answered once the sign-in works
1611
+ }
1612
+ if (!outcome.started) {
1613
+ // The model never ran (e.g. the binary is missing): nothing happened
1614
+ // outside this Mac, so retrying is safe.
1615
+ clearInflight(dir, id);
1616
+ const { excerpt } = recordTurnFailure(outcome.error, sessionScope);
1617
+ if (failures.exceeded(now().getTime())) {
1618
+ await finish("failed", 0, `${FAILURE_LIMIT} model failures in 10 minutes; last: ${excerpt}`);
1619
+ return;
1620
+ }
1621
+ if (attempt >= MAX_ATTEMPTS_PER_MESSAGE) {
1622
+ try {
1623
+ await send(`Sorry — I couldn't answer that one (${runtime.id} kept failing: ${excerpt.slice(0, 160)}). Try again in a bit, or check that ${runtime.id} is signed in on this computer.`);
1624
+ }
1625
+ catch {
1626
+ /* best-effort */
1627
+ }
1628
+ markProcessed(dir, id, now().toISOString());
1629
+ await api.ackInbox(config.agentUid, id);
1630
+ attempts.delete(id);
1631
+ return;
1632
+ }
1633
+ await sleep(fullJitterDelayMs(failures.count(now().getTime()), { random: deps.random }));
1634
+ return; // leave un-acked; retried on the next poll
1635
+ }
1636
+ // The model started: it may already have done things, so never re-run.
1637
+ if (!outcome.aborted)
1638
+ recordTurnFailure(outcome.error, sessionScope);
1639
+ // reasoning_only means the model produced only reasoning tokens and no text.
1640
+ const reasoningOnly = !outcome.aborted && isReasoningOnlyError(outcome.excerpt);
1641
+ const silenceableTurn = target.kind === "room" && !target.mentioned;
1642
+ // Unmentioned room turn that posted nothing and was not stopped by the
1643
+ // owner: any model failure (reasoning_only or otherwise) is a silent
1644
+ // no-reply. The failure is already recorded above via recordTurnFailure,
1645
+ // so we just ack quietly and never leak the raw error into the room.
1646
+ if (!outcome.aborted && poster.postedCount === 0 && silenceableTurn) {
1647
+ markProcessed(dir, id, now().toISOString());
1648
+ await api.ackInbox(config.agentUid, id);
1649
+ clearInflight(dir, id);
1650
+ attempts.delete(id);
1651
+ await clearThinking();
1652
+ log("info", `unmentioned room turn ${id} failed with no post (${where}); acked silently`);
1653
+ if (failures.exceeded(now().getTime())) {
1654
+ await finish("failed", 0, `${FAILURE_LIMIT} model failures in 10 minutes; last: ${outcome.excerpt}`);
1655
+ }
1656
+ return;
1657
+ }
1658
+ try {
1659
+ // DM or @-mentioned room reasoning_only: the person asked directly, so
1660
+ // still say we did not finish — but with a plain reason, never the raw
1661
+ // internal error string.
1662
+ const reason = reasoningOnly ? "the model returned no answer" : failureReason(outcome);
1663
+ await send(didNotFinishText(reason, poster.postedCount > 0));
1664
+ }
1665
+ catch (err) {
1666
+ // Leave it un-acked with the marker: the next poll says it did not finish.
1667
+ log("warn", `could not post "did not finish" for ${id}: ${err instanceof Error ? err.message : String(err)}`);
1668
+ return;
1669
+ }
1670
+ markProcessed(dir, id, now().toISOString());
1671
+ await api.ackInbox(config.agentUid, id);
1672
+ clearInflight(dir, id);
1673
+ attempts.delete(id);
1674
+ log("error", `turn on ${id} ${outcome.aborted ? "stopped by the owner" : "failed"} after it started; told the owner, not re-run`);
1675
+ if (!outcome.aborted && failures.exceeded(now().getTime())) {
1676
+ await finish("failed", 0, `${FAILURE_LIMIT} model failures in 10 minutes; last: ${outcome.excerpt}`);
1677
+ }
1678
+ };
1679
+ // The kickoff runs alongside the heartbeat, before the first inbox poll, so
1680
+ // an owner message sent meanwhile is answered after it in the same session.
1681
+ const kickoff = kickoffAfterIntro
1682
+ ? runKickoff(kickoffAfterIntro.introEventId).catch((err) => {
1683
+ log("error", `kickoff turn crashed: ${err instanceof Error ? err.message : String(err)}`);
1684
+ })
1685
+ : Promise.resolve();
1686
+ // ── Lanes: conversations side by side, each conversation in order ─────────
1687
+ /** Messages waiting per lane, in arrival order. */
1688
+ const lanes = new Map();
1689
+ /** Lanes with a message being handled right now. */
1690
+ const busyLanes = new Set();
1691
+ /** Every message queued or being handled (the inbox re-delivers until acked). */
1692
+ const pending = new Set();
1693
+ const running = new Set();
1694
+ const canStart = () => !stopping && state === "running" && !hasPromotionHold(dir);
1695
+ /** Job turns yield to people: one at a time, and never the last free slot. */
1696
+ const isJobLane = (lane) => lane.startsWith("job:");
1697
+ const jobLaneBudget = Math.max(1, maxParallelTurns - 1);
1698
+ const canStartJobLane = () => [...busyLanes].filter(isJobLane).length < MAX_PARALLEL_JOB_TURNS && busyLanes.size < jobLaneBudget;
1699
+ const pump = () => {
1700
+ for (const [lane, queue] of lanes) {
1701
+ if (busyLanes.size >= maxParallelTurns || !canStart())
1702
+ return;
1703
+ // While the sign-in is broken, one message at a time tries it, on a timer.
1704
+ if (signInIssue && (busyLanes.size > 0 || now().getTime() < nextSignInRetryAt))
1705
+ return;
1706
+ if (busyLanes.has(lane))
1707
+ continue;
1708
+ // `continue`, never `return`: a job that cannot start yet must not hold
1709
+ // up the owner's DM sitting behind it in the map.
1710
+ if (isJobLane(lane) && !canStartJobLane())
1711
+ continue;
1712
+ const item = queue.shift();
1713
+ if (queue.length === 0)
1714
+ lanes.delete(lane);
1715
+ if (!item)
1716
+ continue;
1717
+ busyLanes.add(lane);
1718
+ if (signInIssue)
1719
+ nextSignInRetryAt = now().getTime() + signInRetryMs;
1720
+ const task = handleItem(item)
1721
+ .catch((err) => {
1722
+ log("warn", `inbox item ${item.messageId} failed: ${err instanceof Error ? err.message : String(err)}`);
1723
+ })
1724
+ .finally(() => {
1725
+ resetInboxBackoff();
1726
+ busyLanes.delete(lane);
1727
+ pending.delete(item.messageId);
1728
+ running.delete(task);
1729
+ pump();
1730
+ });
1731
+ running.add(task);
1732
+ }
1733
+ };
1734
+ const enqueue = (item) => {
1735
+ if (pending.has(item.messageId))
1736
+ return;
1737
+ pending.add(item.messageId);
1738
+ const lane = inboxLaneKey(item, config);
1739
+ const queue = lanes.get(lane);
1740
+ if (queue)
1741
+ queue.push(item);
1742
+ else
1743
+ lanes.set(lane, [item]);
1744
+ };
1745
+ const inboxLoop = (async () => {
1746
+ // Turns cut off by the last restart are reported before anything new runs.
1747
+ for (const leftover of leftovers) {
1748
+ try {
1749
+ await recoverInflight(leftover);
1750
+ }
1751
+ catch (err) {
1752
+ log("warn", `recovering ${leftover.messageId} failed: ${err instanceof Error ? err.message : String(err)}`);
1753
+ }
1754
+ }
1755
+ await kickoff;
1756
+ let inboxFails = 0;
1757
+ while (!stopping && state === "running") {
1758
+ if (hasPromotionHold(dir))
1759
+ break;
1760
+ // The install under this process was updated: take nothing new, let the
1761
+ // turns already running finish, then exit so the supervisor loads it.
1762
+ // Queued items were never started or acked; the next process gets them.
1763
+ if (pollForCliUpdate())
1764
+ break;
1765
+ // A kickoff held for a sign-in retries on the same timer as messages.
1766
+ if (busyLanes.size === 0 &&
1767
+ (!signInIssue || now().getTime() >= nextSignInRetryAt) &&
1768
+ readBotConfig(dir)?.kickoffPendingSignIn) {
1769
+ if (signInIssue)
1770
+ nextSignInRetryAt = now().getTime() + signInRetryMs;
1771
+ await runKickoff().catch((err) => {
1772
+ log("error", `kickoff retry crashed: ${err instanceof Error ? err.message : String(err)}`);
1773
+ });
1774
+ }
1775
+ // Completions the cloud has not accepted yet: re-send the report (never
1776
+ // the work), alongside the poll rather than in front of it.
1777
+ kickCompletionRetries();
1778
+ const useInboxBackoff = await inboxPollBackoffEnabled().catch((error) => {
1779
+ log("warn", `inbox poll backoff flag lookup failed (${safeErrorClass(error)}); using default-off`);
1780
+ return false;
1781
+ });
1782
+ let items;
1783
+ try {
1784
+ items = await api.pullInbox(config.agentUid);
1785
+ inboxFails = 0;
1786
+ setStatus({ lastInboxPollAt: now().toISOString(), lastInboxPollOk: true, lastInboxError: undefined });
1787
+ // One catch-up per job per wake: when several runs of the same job are
1788
+ // pending, the newest runs and the older ones are reported skipped.
1789
+ for (const id of planJobRuns(items, handledJobFires).superseded)
1790
+ supersededJobRuns.add(id);
1791
+ if (items.length > 0)
1792
+ resetInboxBackoff();
1793
+ for (const item of items)
1794
+ enqueue(item);
1795
+ pump();
1796
+ if (stopping || state !== "running")
1797
+ break;
1798
+ }
1799
+ catch (err) {
1800
+ inboxFails += 1;
1801
+ const message = err instanceof Error ? err.message : String(err);
1802
+ setStatus({ lastInboxPollOk: false, lastInboxError: message.slice(0, 400) });
1803
+ log("warn", `inbox poll failed: ${message}`);
1804
+ if (stopping || state !== "running")
1805
+ break;
1806
+ await sleep(apiPollDelayMs({ err, attempt: inboxFails - 1, intervalMs: inboxIntervalMs, random }));
1807
+ continue;
1808
+ }
1809
+ if (stopping || state !== "running")
1810
+ break;
1811
+ if (!useInboxBackoff) {
1812
+ inboxBackoffIndex = 0;
1813
+ await waitForInbox(inboxIntervalMs, false);
1814
+ continue;
1815
+ }
1816
+ const delayMs = items.length > 0
1817
+ ? inboxIntervalMs
1818
+ : inboxBackoffSteps[Math.min(inboxBackoffIndex, inboxBackoffSteps.length - 1)];
1819
+ if (items.length === 0)
1820
+ inboxBackoffIndex = Math.min(inboxBackoffIndex + 1, inboxBackoffSteps.length - 1);
1821
+ await waitForInbox(delayMs, true);
1822
+ }
1823
+ // Stop, drain and promotion wait for the turns already running. Queued
1824
+ // messages were never started or acked, so the inbox delivers them again.
1825
+ while (running.size > 0)
1826
+ await Promise.allSettled([...running]);
1827
+ await completionRetryTask;
1828
+ })();
1829
+ function readStatus() {
1830
+ return readBotStatus(dir);
1831
+ }
1832
+ const stop = async (mode = "abort") => {
1833
+ // An owner stop or a promotion while an update restart drains wins: the
1834
+ // bot then exits 0 and stays down instead of being restarted.
1835
+ cliUpdatePending = null;
1836
+ if (stopping) {
1837
+ if (mode === "abort")
1838
+ for (const turn of currentTurns)
1839
+ turn.abort();
1840
+ return;
1841
+ }
1842
+ stopping = true;
1843
+ activeInboxWait?.abort();
1844
+ // End the turn in progress; its message gets "did not finish", not silence.
1845
+ // Promotion instead drains that turn, including its reply and ACK, before
1846
+ // the coordinator snapshots memory and starts the cloud consumer.
1847
+ if (mode === "abort")
1848
+ for (const turn of currentTurns)
1849
+ turn.abort();
1850
+ await Promise.allSettled([heartbeatLoop, inboxLoop]);
1851
+ await finish("stopped", 0, mode === "drain" ? "drain completed (running turns finished)" : "stop requested");
1852
+ };
1853
+ const onSignal = () => {
1854
+ void stop();
1855
+ };
1856
+ const events = deps.processEvents ?? process;
1857
+ events.once("SIGTERM", onSignal);
1858
+ events.once("SIGINT", onSignal);
1859
+ events.once("SIGUSR2", () => { void stop("drain"); });
1860
+ void inboxLoop.then(() => { if (hasPromotionHold(dir))
1861
+ void stop("drain"); });
1862
+ setStatus({ canDrainForPromotion: true });
1863
+ void Promise.allSettled([heartbeatLoop, inboxLoop]).then(() => {
1864
+ if (state !== "running")
1865
+ return;
1866
+ const update = cliUpdatePending;
1867
+ if (!update) {
1868
+ void finish("stopped", 0, "loops ended");
1869
+ return;
1870
+ }
1871
+ try {
1872
+ deps.cliUpdate?.recordRestart(update);
1873
+ }
1874
+ catch (err) {
1875
+ log("warn", `could not record the update restart: ${err instanceof Error ? err.message : String(err)}`);
1876
+ }
1877
+ void finish("stopped", CLI_UPDATE_RESTART_EXIT_CODE, `hq-cli updated ${update.from} -> ${update.to}; restarting to load it`, () => deps.onCliUpdateRestart?.(update, log));
1878
+ });
1879
+ return { stop, done, status: readStatus, idle: () => pending.size === 0 };
1880
+ }
1881
+ //# sourceMappingURL=run.js.map