@telora/daemon 0.21.98 → 0.21.144

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 (416) hide show
  1. package/build-info.json +6 -3
  2. package/dist/audit/audit-hooks.d.ts +120 -5
  3. package/dist/audit/audit-hooks.d.ts.map +1 -1
  4. package/dist/audit/audit-hooks.js +205 -45
  5. package/dist/audit/audit-hooks.js.map +1 -1
  6. package/dist/ci/ci-escalation.js +1 -1
  7. package/dist/ci/ci-escalation.js.map +1 -1
  8. package/dist/ci/ci-sensor-loop.d.ts +30 -0
  9. package/dist/ci/ci-sensor-loop.d.ts.map +1 -1
  10. package/dist/ci/ci-sensor-loop.js +85 -12
  11. package/dist/ci/ci-sensor-loop.js.map +1 -1
  12. package/dist/cli/connect/config-write.d.ts +87 -0
  13. package/dist/cli/connect/config-write.d.ts.map +1 -0
  14. package/dist/cli/connect/config-write.js +255 -0
  15. package/dist/cli/connect/config-write.js.map +1 -0
  16. package/dist/cli/connect/registration.d.ts +76 -0
  17. package/dist/cli/connect/registration.d.ts.map +1 -0
  18. package/dist/cli/connect/registration.js +207 -0
  19. package/dist/cli/connect/registration.js.map +1 -0
  20. package/dist/cli/connect.d.ts +3 -141
  21. package/dist/cli/connect.d.ts.map +1 -1
  22. package/dist/cli/connect.js +9 -441
  23. package/dist/cli/connect.js.map +1 -1
  24. package/dist/completion/coverage-drift-state.d.ts +29 -2
  25. package/dist/completion/coverage-drift-state.d.ts.map +1 -1
  26. package/dist/completion/coverage-drift-state.js +42 -2
  27. package/dist/completion/coverage-drift-state.js.map +1 -1
  28. package/dist/completion/coverage-stabilized.d.ts +58 -0
  29. package/dist/completion/coverage-stabilized.d.ts.map +1 -0
  30. package/dist/completion/coverage-stabilized.js +84 -0
  31. package/dist/completion/coverage-stabilized.js.map +1 -0
  32. package/dist/completion/delivery-advancement-guard.d.ts +21 -0
  33. package/dist/completion/delivery-advancement-guard.d.ts.map +1 -1
  34. package/dist/completion/delivery-advancement-guard.js +31 -3
  35. package/dist/completion/delivery-advancement-guard.js.map +1 -1
  36. package/dist/completion/escalation-provenance.d.ts +2 -1
  37. package/dist/completion/escalation-provenance.d.ts.map +1 -1
  38. package/dist/completion/escalation-provenance.js +2 -1
  39. package/dist/completion/escalation-provenance.js.map +1 -1
  40. package/dist/completion/escalations.js +1 -1
  41. package/dist/completion/escalations.js.map +1 -1
  42. package/dist/completion/event-escalations.d.ts +16 -1
  43. package/dist/completion/event-escalations.d.ts.map +1 -1
  44. package/dist/completion/event-escalations.js +43 -5
  45. package/dist/completion/event-escalations.js.map +1 -1
  46. package/dist/completion/event-phases.d.ts.map +1 -1
  47. package/dist/completion/event-phases.js +5 -2
  48. package/dist/completion/event-phases.js.map +1 -1
  49. package/dist/completion/event.d.ts +1 -1
  50. package/dist/completion/event.d.ts.map +1 -1
  51. package/dist/completion/event.js +2 -2
  52. package/dist/completion/event.js.map +1 -1
  53. package/dist/completion/machine-remediable-demotion.d.ts +74 -0
  54. package/dist/completion/machine-remediable-demotion.d.ts.map +1 -0
  55. package/dist/completion/machine-remediable-demotion.js +148 -0
  56. package/dist/completion/machine-remediable-demotion.js.map +1 -0
  57. package/dist/completion/read-only-pristine.d.ts +1 -1
  58. package/dist/completion/read-only-pristine.d.ts.map +1 -1
  59. package/dist/completion/read-only-pristine.js +1 -1
  60. package/dist/completion/read-only-pristine.js.map +1 -1
  61. package/dist/completion/review-exit-escalations.d.ts +1 -1
  62. package/dist/completion/review-exit-escalations.d.ts.map +1 -1
  63. package/dist/completion/review-exit-phase.d.ts +5 -2
  64. package/dist/completion/review-exit-phase.d.ts.map +1 -1
  65. package/dist/completion/review-exit-phase.js +15 -8
  66. package/dist/completion/review-exit-phase.js.map +1 -1
  67. package/dist/completion/settle-injection-sweep.d.ts +55 -0
  68. package/dist/completion/settle-injection-sweep.d.ts.map +1 -0
  69. package/dist/completion/settle-injection-sweep.js +88 -0
  70. package/dist/completion/settle-injection-sweep.js.map +1 -0
  71. package/dist/completion/state-cascade.d.ts.map +1 -1
  72. package/dist/completion/state-cascade.js +42 -1
  73. package/dist/completion/state-cascade.js.map +1 -1
  74. package/dist/completion/status-advance-phase.d.ts.map +1 -1
  75. package/dist/completion/status-advance-phase.js +18 -0
  76. package/dist/completion/status-advance-phase.js.map +1 -1
  77. package/dist/completion/team-lifecycle.d.ts +42 -0
  78. package/dist/completion/team-lifecycle.d.ts.map +1 -1
  79. package/dist/completion/team-lifecycle.js +73 -1
  80. package/dist/completion/team-lifecycle.js.map +1 -1
  81. package/dist/delivery/delivery-advance-block.d.ts +1 -1
  82. package/dist/delivery/delivery-advance-block.d.ts.map +1 -1
  83. package/dist/delivery/delivery-advance-block.js +1 -1
  84. package/dist/delivery/delivery-advance-block.js.map +1 -1
  85. package/dist/delivery/delivery-lifecycle.d.ts +19 -33
  86. package/dist/delivery/delivery-lifecycle.d.ts.map +1 -1
  87. package/dist/delivery/delivery-lifecycle.js +20 -148
  88. package/dist/delivery/delivery-lifecycle.js.map +1 -1
  89. package/dist/delivery/delivery-merge-ops.d.ts +46 -0
  90. package/dist/delivery/delivery-merge-ops.d.ts.map +1 -0
  91. package/dist/delivery/delivery-merge-ops.js +154 -0
  92. package/dist/delivery/delivery-merge-ops.js.map +1 -0
  93. package/dist/delivery/delivery-reconciler-apply.d.ts +28 -0
  94. package/dist/delivery/delivery-reconciler-apply.d.ts.map +1 -1
  95. package/dist/delivery/delivery-reconciler-apply.js +30 -0
  96. package/dist/delivery/delivery-reconciler-apply.js.map +1 -1
  97. package/dist/delivery/delivery-reconciler-shadow.d.ts +1 -1
  98. package/dist/delivery/delivery-reconciler-shadow.d.ts.map +1 -1
  99. package/dist/delivery/delivery-reconciler-shadow.js +1 -1
  100. package/dist/delivery/delivery-reconciler-shadow.js.map +1 -1
  101. package/dist/delivery/delivery-reconciler.d.ts +7 -2
  102. package/dist/delivery/delivery-reconciler.d.ts.map +1 -1
  103. package/dist/delivery/delivery-reconciler.js +34 -5
  104. package/dist/delivery/delivery-reconciler.js.map +1 -1
  105. package/dist/delivery/external-hold-surface.d.ts +32 -0
  106. package/dist/delivery/external-hold-surface.d.ts.map +1 -0
  107. package/dist/delivery/external-hold-surface.js +67 -0
  108. package/dist/delivery/external-hold-surface.js.map +1 -0
  109. package/dist/delivery/external-hold.d.ts +21 -0
  110. package/dist/delivery/external-hold.d.ts.map +1 -1
  111. package/dist/delivery/external-hold.js +40 -5
  112. package/dist/delivery/external-hold.js.map +1 -1
  113. package/dist/delivery/operator-hold.d.ts +1 -1
  114. package/dist/delivery/operator-hold.d.ts.map +1 -1
  115. package/dist/delivery/paused-revival.d.ts +140 -0
  116. package/dist/delivery/paused-revival.d.ts.map +1 -0
  117. package/dist/delivery/paused-revival.js +230 -0
  118. package/dist/delivery/paused-revival.js.map +1 -0
  119. package/dist/delivery/wait-reachability.d.ts.map +1 -1
  120. package/dist/delivery/wait-reachability.js +11 -0
  121. package/dist/delivery/wait-reachability.js.map +1 -1
  122. package/dist/directive/close-loop-dispatcher.d.ts +16 -263
  123. package/dist/directive/close-loop-dispatcher.d.ts.map +1 -1
  124. package/dist/directive/close-loop-dispatcher.js +21 -259
  125. package/dist/directive/close-loop-dispatcher.js.map +1 -1
  126. package/dist/directive/directive-dispatch.d.ts +20 -1
  127. package/dist/directive/directive-dispatch.d.ts.map +1 -1
  128. package/dist/directive/directive-dispatch.js +19 -1
  129. package/dist/directive/directive-dispatch.js.map +1 -1
  130. package/dist/directive/directive-executor.d.ts +1 -3
  131. package/dist/directive/directive-executor.d.ts.map +1 -1
  132. package/dist/directive/directive-executor.js +24 -22
  133. package/dist/directive/directive-executor.js.map +1 -1
  134. package/dist/directive/phase-sync.d.ts +28 -15
  135. package/dist/directive/phase-sync.d.ts.map +1 -1
  136. package/dist/directive/phase-sync.js +28 -36
  137. package/dist/directive/phase-sync.js.map +1 -1
  138. package/dist/directive/resolver-dispatch.d.ts +19 -92
  139. package/dist/directive/resolver-dispatch.d.ts.map +1 -1
  140. package/dist/directive/resolver-dispatch.js +19 -65
  141. package/dist/directive/resolver-dispatch.js.map +1 -1
  142. package/dist/drift/staleness-sensor.js +1 -1
  143. package/dist/drift/staleness-sensor.js.map +1 -1
  144. package/dist/focus/focus-engine.d.ts.map +1 -1
  145. package/dist/focus/focus-engine.js +29 -2
  146. package/dist/focus/focus-engine.js.map +1 -1
  147. package/dist/focus/focus-executor.d.ts.map +1 -1
  148. package/dist/focus/focus-executor.js +290 -256
  149. package/dist/focus/focus-executor.js.map +1 -1
  150. package/dist/focus/focus-lifecycle.d.ts.map +1 -1
  151. package/dist/focus/focus-lifecycle.js +2 -1
  152. package/dist/focus/focus-lifecycle.js.map +1 -1
  153. package/dist/focus/focus-loop-registry.d.ts.map +1 -1
  154. package/dist/focus/focus-loop-registry.js +43 -1
  155. package/dist/focus/focus-loop-registry.js.map +1 -1
  156. package/dist/focus/focus-respawn-guard.d.ts +1 -1
  157. package/dist/focus/focus-respawn-guard.d.ts.map +1 -1
  158. package/dist/focus/focus-respawn-guard.js +12 -3
  159. package/dist/focus/focus-respawn-guard.js.map +1 -1
  160. package/dist/focus/focus-team-state.d.ts +11 -0
  161. package/dist/focus/focus-team-state.d.ts.map +1 -1
  162. package/dist/focus/focus-team-state.js +17 -0
  163. package/dist/focus/focus-team-state.js.map +1 -1
  164. package/dist/focus/listener-auto-advance.d.ts +17 -46
  165. package/dist/focus/listener-auto-advance.d.ts.map +1 -1
  166. package/dist/focus/listener-auto-advance.js +104 -256
  167. package/dist/focus/listener-auto-advance.js.map +1 -1
  168. package/dist/focus/listener-spawn.d.ts +62 -0
  169. package/dist/focus/listener-spawn.d.ts.map +1 -0
  170. package/dist/focus/listener-spawn.js +418 -0
  171. package/dist/focus/listener-spawn.js.map +1 -0
  172. package/dist/focus/listener-state.d.ts +57 -0
  173. package/dist/focus/listener-state.d.ts.map +1 -0
  174. package/dist/focus/listener-state.js +63 -0
  175. package/dist/focus/listener-state.js.map +1 -0
  176. package/dist/focus/listener.d.ts +0 -58
  177. package/dist/focus/listener.d.ts.map +1 -1
  178. package/dist/focus/listener.js +138 -516
  179. package/dist/focus/listener.js.map +1 -1
  180. package/dist/focus/tick-deadline.d.ts +55 -0
  181. package/dist/focus/tick-deadline.d.ts.map +1 -0
  182. package/dist/focus/tick-deadline.js +91 -0
  183. package/dist/focus/tick-deadline.js.map +1 -0
  184. package/dist/focus/verify-open-issues.d.ts +62 -0
  185. package/dist/focus/verify-open-issues.d.ts.map +1 -0
  186. package/dist/focus/verify-open-issues.js +265 -0
  187. package/dist/focus/verify-open-issues.js.map +1 -0
  188. package/dist/focus-stage-lifecycle/index.d.ts +19 -26
  189. package/dist/focus-stage-lifecycle/index.d.ts.map +1 -1
  190. package/dist/focus-stage-lifecycle/index.js +27 -39
  191. package/dist/focus-stage-lifecycle/index.js.map +1 -1
  192. package/dist/focus-stage-lifecycle/wedge-cause-arbiter.d.ts +52 -0
  193. package/dist/focus-stage-lifecycle/wedge-cause-arbiter.d.ts.map +1 -0
  194. package/dist/focus-stage-lifecycle/wedge-cause-arbiter.js +94 -0
  195. package/dist/focus-stage-lifecycle/wedge-cause-arbiter.js.map +1 -0
  196. package/dist/focus-stage-lifecycle/wedge-watchdog.d.ts +18 -1
  197. package/dist/focus-stage-lifecycle/wedge-watchdog.d.ts.map +1 -1
  198. package/dist/focus-stage-lifecycle/wedge-watchdog.js +57 -7
  199. package/dist/focus-stage-lifecycle/wedge-watchdog.js.map +1 -1
  200. package/dist/git/index.d.ts +2 -0
  201. package/dist/git/index.d.ts.map +1 -1
  202. package/dist/git/index.js +2 -0
  203. package/dist/git/index.js.map +1 -1
  204. package/dist/git/merge-strategies.d.ts +80 -0
  205. package/dist/git/merge-strategies.d.ts.map +1 -0
  206. package/dist/git/merge-strategies.js +218 -0
  207. package/dist/git/merge-strategies.js.map +1 -0
  208. package/dist/git/merge-to-main.d.ts +76 -0
  209. package/dist/git/merge-to-main.d.ts.map +1 -0
  210. package/dist/git/merge-to-main.js +256 -0
  211. package/dist/git/merge-to-main.js.map +1 -0
  212. package/dist/git/merge.d.ts +27 -81
  213. package/dist/git/merge.d.ts.map +1 -1
  214. package/dist/git/merge.js +4 -448
  215. package/dist/git/merge.js.map +1 -1
  216. package/dist/git/state-detector.js +1 -1
  217. package/dist/git/state-detector.js.map +1 -1
  218. package/dist/guard/condition-evaluators.d.ts +9 -0
  219. package/dist/guard/condition-evaluators.d.ts.map +1 -1
  220. package/dist/guard/condition-evaluators.js +15 -0
  221. package/dist/guard/condition-evaluators.js.map +1 -1
  222. package/dist/guard/guard-evaluator.d.ts +17 -1
  223. package/dist/guard/guard-evaluator.d.ts.map +1 -1
  224. package/dist/guard/guard-evaluator.js +61 -2
  225. package/dist/guard/guard-evaluator.js.map +1 -1
  226. package/dist/guard/trigger-executor.d.ts +30 -18
  227. package/dist/guard/trigger-executor.d.ts.map +1 -1
  228. package/dist/guard/trigger-executor.js +55 -31
  229. package/dist/guard/trigger-executor.js.map +1 -1
  230. package/dist/loops/engine.js +1 -1
  231. package/dist/loops/engine.js.map +1 -1
  232. package/dist/merge/focus-merge.d.ts +1 -1
  233. package/dist/merge/focus-merge.d.ts.map +1 -1
  234. package/dist/merge/focus-merge.js +1 -1
  235. package/dist/merge/focus-merge.js.map +1 -1
  236. package/dist/merge/merge-back-loop.d.ts +1 -1
  237. package/dist/merge/merge-back-loop.d.ts.map +1 -1
  238. package/dist/merge/merge-back-loop.js +2 -2
  239. package/dist/merge/merge-back-loop.js.map +1 -1
  240. package/dist/merge/merge-detector.js +1 -1
  241. package/dist/merge/merge-detector.js.map +1 -1
  242. package/dist/nexus/return.js +1 -1
  243. package/dist/nexus/return.js.map +1 -1
  244. package/dist/prd/controller-shared.d.ts +223 -0
  245. package/dist/prd/controller-shared.d.ts.map +1 -0
  246. package/dist/prd/controller-shared.js +77 -0
  247. package/dist/prd/controller-shared.js.map +1 -0
  248. package/dist/prd/controller.d.ts +5 -256
  249. package/dist/prd/controller.d.ts.map +1 -1
  250. package/dist/prd/controller.js +9 -166
  251. package/dist/prd/controller.js.map +1 -1
  252. package/dist/prd/done-sweep.d.ts +55 -0
  253. package/dist/prd/done-sweep.d.ts.map +1 -0
  254. package/dist/prd/done-sweep.js +112 -0
  255. package/dist/prd/done-sweep.js.map +1 -0
  256. package/dist/prompt/team-prompt-base.d.ts +1 -1
  257. package/dist/prompt/team-prompt-base.d.ts.map +1 -1
  258. package/dist/prompt/team-prompt-base.js +1 -1
  259. package/dist/prompt/team-prompt-base.js.map +1 -1
  260. package/dist/prompt-sections/execution-sections.d.ts +1 -1
  261. package/dist/prompt-sections/execution-sections.d.ts.map +1 -1
  262. package/dist/prompt-sections/execution-sections.js +12 -2
  263. package/dist/prompt-sections/execution-sections.js.map +1 -1
  264. package/dist/prompt-sections/persona-sections.d.ts +0 -32
  265. package/dist/prompt-sections/persona-sections.d.ts.map +1 -1
  266. package/dist/prompt-sections/persona-sections.js +19 -88
  267. package/dist/prompt-sections/persona-sections.js.map +1 -1
  268. package/dist/prompt-sections/shared.d.ts +1 -1
  269. package/dist/prompt-sections/shared.d.ts.map +1 -1
  270. package/dist/queries/agent-resolvable-promotion.d.ts +1 -1
  271. package/dist/queries/agent-resolvable-promotion.d.ts.map +1 -1
  272. package/dist/queries/agent-resolvable-promotion.js +2 -1
  273. package/dist/queries/agent-resolvable-promotion.js.map +1 -1
  274. package/dist/queries/deliveries.d.ts +0 -172
  275. package/dist/queries/deliveries.d.ts.map +1 -1
  276. package/dist/queries/deliveries.js +1 -77
  277. package/dist/queries/deliveries.js.map +1 -1
  278. package/dist/queries/escalation-consumption.d.ts +120 -0
  279. package/dist/queries/escalation-consumption.d.ts.map +1 -0
  280. package/dist/queries/escalation-consumption.js +143 -0
  281. package/dist/queries/escalation-consumption.js.map +1 -0
  282. package/dist/queries/escalation-dispositions.d.ts +19 -0
  283. package/dist/queries/escalation-dispositions.d.ts.map +1 -0
  284. package/dist/queries/escalation-dispositions.js +18 -0
  285. package/dist/queries/escalation-dispositions.js.map +1 -0
  286. package/dist/queries/escalation-suppression.d.ts +1 -1
  287. package/dist/queries/escalation-suppression.d.ts.map +1 -1
  288. package/dist/queries/escalation-suppression.js +1 -1
  289. package/dist/queries/escalation-suppression.js.map +1 -1
  290. package/dist/queries/escalations.d.ts +343 -0
  291. package/dist/queries/escalations.d.ts.map +1 -0
  292. package/dist/queries/escalations.js +493 -0
  293. package/dist/queries/escalations.js.map +1 -0
  294. package/dist/queries/focus-discovery.d.ts +204 -0
  295. package/dist/queries/focus-discovery.d.ts.map +1 -0
  296. package/dist/queries/focus-discovery.js +92 -0
  297. package/dist/queries/focus-discovery.js.map +1 -0
  298. package/dist/queries/focuses.d.ts +36 -0
  299. package/dist/queries/focuses.d.ts.map +1 -1
  300. package/dist/queries/focuses.js +45 -0
  301. package/dist/queries/focuses.js.map +1 -1
  302. package/dist/queries/guards.d.ts +10 -0
  303. package/dist/queries/guards.d.ts.map +1 -1
  304. package/dist/queries/guards.js +14 -1
  305. package/dist/queries/guards.js.map +1 -1
  306. package/dist/queries/index.d.ts +4 -2
  307. package/dist/queries/index.d.ts.map +1 -1
  308. package/dist/queries/index.js +4 -2
  309. package/dist/queries/index.js.map +1 -1
  310. package/dist/queries/issues.d.ts +8 -372
  311. package/dist/queries/issues.d.ts.map +1 -1
  312. package/dist/queries/issues.js +7 -482
  313. package/dist/queries/issues.js.map +1 -1
  314. package/dist/queries/schemas.d.ts +51 -0
  315. package/dist/queries/schemas.d.ts.map +1 -1
  316. package/dist/queries/schemas.js +34 -0
  317. package/dist/queries/schemas.js.map +1 -1
  318. package/dist/queries/verification.d.ts +5 -6
  319. package/dist/queries/verification.d.ts.map +1 -1
  320. package/dist/queries/verification.js +5 -6
  321. package/dist/queries/verification.js.map +1 -1
  322. package/dist/resolvers/agent-escalations.d.ts +20 -0
  323. package/dist/resolvers/agent-escalations.d.ts.map +1 -1
  324. package/dist/resolvers/agent-escalations.js +40 -10
  325. package/dist/resolvers/agent-escalations.js.map +1 -1
  326. package/dist/resolvers/index.d.ts +2 -2
  327. package/dist/resolvers/index.d.ts.map +1 -1
  328. package/dist/resolvers/index.js +1 -1
  329. package/dist/resolvers/index.js.map +1 -1
  330. package/dist/resolvers/loop-persona.d.ts +1 -11
  331. package/dist/resolvers/loop-persona.d.ts.map +1 -1
  332. package/dist/resolvers/loop-persona.js +2 -16
  333. package/dist/resolvers/loop-persona.js.map +1 -1
  334. package/dist/resolvers/shared/git-diff.d.ts +99 -18
  335. package/dist/resolvers/shared/git-diff.d.ts.map +1 -1
  336. package/dist/resolvers/shared/git-diff.js +98 -27
  337. package/dist/resolvers/shared/git-diff.js.map +1 -1
  338. package/dist/shell/control-state.d.ts +1 -1
  339. package/dist/shell/control-state.d.ts.map +1 -1
  340. package/dist/shell/control-state.js +1 -1
  341. package/dist/shell/control-state.js.map +1 -1
  342. package/dist/shell/crash-recovery.d.ts +1 -1
  343. package/dist/shell/crash-recovery.d.ts.map +1 -1
  344. package/dist/shell/crash-recovery.js +2 -2
  345. package/dist/shell/crash-recovery.js.map +1 -1
  346. package/dist/shell/install-probe-tick.d.ts +1 -1
  347. package/dist/shell/install-probe-tick.d.ts.map +1 -1
  348. package/dist/shell/install-probe-tick.js +1 -1
  349. package/dist/shell/install-probe-tick.js.map +1 -1
  350. package/dist/shell/unified-shell-provisioning.d.ts +40 -0
  351. package/dist/shell/unified-shell-provisioning.d.ts.map +1 -1
  352. package/dist/shell/unified-shell-provisioning.js +72 -0
  353. package/dist/shell/unified-shell-provisioning.js.map +1 -1
  354. package/dist/shell/unified-shell.d.ts.map +1 -1
  355. package/dist/shell/unified-shell.js +19 -2
  356. package/dist/shell/unified-shell.js.map +1 -1
  357. package/dist/spawn/helpers.d.ts.map +1 -1
  358. package/dist/spawn/helpers.js +6 -1
  359. package/dist/spawn/helpers.js.map +1 -1
  360. package/dist/spawn/model-routing.d.ts +1 -1
  361. package/dist/spawn/model-routing.d.ts.map +1 -1
  362. package/dist/spawn/model-routing.js +1 -1
  363. package/dist/spawn/model-routing.js.map +1 -1
  364. package/dist/spawn/process-spawn.d.ts.map +1 -1
  365. package/dist/spawn/process-spawn.js +7 -0
  366. package/dist/spawn/process-spawn.js.map +1 -1
  367. package/dist/spawn/prompt-assembly.d.ts +1 -1
  368. package/dist/spawn/prompt-assembly.d.ts.map +1 -1
  369. package/dist/spawn/prompt-assembly.js +1 -1
  370. package/dist/spawn/prompt-assembly.js.map +1 -1
  371. package/dist/spawn/spawn-cooldown.d.ts +41 -7
  372. package/dist/spawn/spawn-cooldown.d.ts.map +1 -1
  373. package/dist/spawn/spawn-cooldown.js +47 -0
  374. package/dist/spawn/spawn-cooldown.js.map +1 -1
  375. package/dist/spawn/spawn-decision.d.ts.map +1 -1
  376. package/dist/spawn/spawn-decision.js +34 -2
  377. package/dist/spawn/spawn-decision.js.map +1 -1
  378. package/dist/spawn/worktree-session.d.ts +1 -1
  379. package/dist/spawn/worktree-session.d.ts.map +1 -1
  380. package/dist/spawn/worktree-session.js +1 -1
  381. package/dist/spawn/worktree-session.js.map +1 -1
  382. package/dist/templates/claude-md.d.ts +1 -1
  383. package/dist/templates/claude-md.d.ts.map +1 -1
  384. package/dist/templates/claude-md.js +12 -0
  385. package/dist/templates/claude-md.js.map +1 -1
  386. package/dist/verify/stall-detectors.d.ts +20 -8
  387. package/dist/verify/stall-detectors.d.ts.map +1 -1
  388. package/dist/verify/stall-detectors.js +27 -46
  389. package/dist/verify/stall-detectors.js.map +1 -1
  390. package/dist/verify/verification-default-on.d.ts +1 -1
  391. package/dist/verify/verification-default-on.d.ts.map +1 -1
  392. package/dist/verify/verification-deps.d.ts +1 -1
  393. package/dist/verify/verification-deps.d.ts.map +1 -1
  394. package/dist/verify/verification-engine.d.ts.map +1 -1
  395. package/dist/verify/verification-engine.js +20 -12
  396. package/dist/verify/verification-engine.js.map +1 -1
  397. package/dist/workflow/gate-failure-policy.d.ts +48 -0
  398. package/dist/workflow/gate-failure-policy.d.ts.map +1 -0
  399. package/dist/workflow/gate-failure-policy.js +57 -0
  400. package/dist/workflow/gate-failure-policy.js.map +1 -0
  401. package/dist/workflow/pipeline-config.d.ts +2 -2
  402. package/dist/workflow/pipeline-config.js +2 -2
  403. package/dist/worktree/provision.js +1 -1
  404. package/dist/worktree/provision.js.map +1 -1
  405. package/dist/worktree/scratch-guard.d.ts.map +1 -1
  406. package/dist/worktree/scratch-guard.js +23 -2
  407. package/dist/worktree/scratch-guard.js.map +1 -1
  408. package/package.json +1 -1
  409. package/dist/directive/close-loop-stage.d.ts +0 -50
  410. package/dist/directive/close-loop-stage.d.ts.map +0 -1
  411. package/dist/directive/close-loop-stage.js +0 -210
  412. package/dist/directive/close-loop-stage.js.map +0 -1
  413. package/dist/focus-stage-lifecycle/review-convergence.d.ts +0 -133
  414. package/dist/focus-stage-lifecycle/review-convergence.d.ts.map +0 -1
  415. package/dist/focus-stage-lifecycle/review-convergence.js +0 -237
  416. package/dist/focus-stage-lifecycle/review-convergence.js.map +0 -1
@@ -7,14 +7,14 @@
7
7
  * single Agent Team per focus that manages all deliveries.
8
8
  */
9
9
  import { getAgentRoles } from '../queries/roles.js';
10
- import { checkDeliveryAutoAdvance, checkVerifyDeliveriesForOpenIssues, checkDeliveryWorkStart } from './listener-auto-advance.js';
10
+ import { checkDeliveryAutoAdvance, checkDeliveryWorkStart } from './listener-auto-advance.js';
11
+ import { checkVerifyDeliveriesForOpenIssues } from './verify-open-issues.js';
12
+ import { checkPausedDeliveriesForRevival, buildDefaultPausedRevivalDeps } from '../delivery/paused-revival.js';
11
13
  import { runReconcilerShadow } from '../delivery/delivery-reconciler-shadow.js';
12
- import { spawnFocusTeam, hasActiveTeam, getActiveTeamCount, getActiveTeams, checkAndMergeCompletedDeliveries } from './focus-executor.js';
13
- import { applyRespawnGuard } from './focus-respawn-guard.js';
14
+ import { getActiveTeams, checkAndMergeCompletedDeliveries } from './focus-executor.js';
14
15
  import { checkResolvedMergeConflicts } from '../merge/focus-merge.js';
15
16
  import { runCascadeChecks } from '../completion/state-cascade.js';
16
17
  import { runStageLifecycleTransitions } from '../focus-stage-lifecycle/index.js';
17
- import { recordActivity } from '../shell/heartbeat.js';
18
18
  import { safeInterval, withRetry } from '@telora/daemon-core';
19
19
  import { getActiveSessions, updateSession } from '../queries/sessions.js';
20
20
  import { ensureFocusWorktrees, checkForDeactivatedFocuses } from './focus-lifecycle.js';
@@ -25,74 +25,40 @@ import { isDaemonShuttingDown } from '../shell/shutdown-state.js';
25
25
  import { registerPollTrigger, triggerCheck } from '../poll-trigger.js';
26
26
  // Re-exported for existing importers; the live trigger is registered below.
27
27
  export { triggerCheck };
28
- import { configForProduct, findProduct } from '../shell/config.js';
28
+ import { configForProduct } from '../shell/config.js';
29
29
  import { checkIncomingRequests } from '../nexus/index.js';
30
30
  import { checkIntakePendingRequests } from '../nexus/index.js';
31
31
  import { checkResolvedRequests } from '../nexus/index.js';
32
32
  import { checkEndpointClearance } from '../nexus/index.js';
33
- import { checkDirectives, seedLastProcessedStage, getPendingSpawnFocusIds, getPendingSpawnDirective, clearPendingSpawnDirective, shouldReapPendingSpawnDirective } from '../directive/directive-executor.js';
34
- import { decideNullRoleSpawn, resolveReviewerFallbackRole, shouldWarnNullRoleSkip, DEFAULT_REVIEWER_ROLE_NAME } from '../spawn/review-role-fallback.js';
35
- import { getActiveFocuses, getReadyFocuses, clearAnswerRearm } from '../queries/deliveries.js';
36
- import { isAuditPhaseFocusRow } from '../audit/audit-shape.js';
37
- import { isIntakeDraining } from '../shell/intake-control.js';
33
+ import { checkDirectives, seedLastProcessedStage } from '../directive/directive-executor.js';
34
+ import { getActiveFocuses } from '../queries/focus-discovery.js';
38
35
  import { checkDiscoveryRequests } from '../host-control/discovery-poll.js';
39
- // Track focuses that have been logged as idle (no actionable deliveries).
40
- // Only log on state change: first skip after an active session or after startup.
41
- const idleLoggedFocuses = new Set();
42
- // Track focuses already warned this blocked episode for a null-role coding
43
- // off-state pending directive (Play/queue toggle cleared, no operator stop).
44
- // Without this, the "no assigned_agent_role_id; skipping spawn (coding off-state)"
45
- // warning fires every 30s poll for as long as the directive sits queued. We warn
46
- // once on entry and suppress until the focus resumes (role assigned, directive
47
- // cleared, or focus leaves the active set), at which point it is removed so a
48
- // later re-entry warns again. Operator-stop suppression does NOT use this set --
49
- // it drops the directive, so it is inherently once-per-episode.
50
- const nullRoleSkipWarnedFocuses = new Set();
51
- // Depth of the ready-focus queue as of the most recent completed poll. Read by
52
- // the auto-updater's batch-safe idle predicate (auto-update-idle.ts): a drained
53
- // queue (0) plus no active team is a safe restart seam. Stale-but-recent is fine
54
- // -- the idle gate is debounced well past the poll interval.
55
- let lastReadyFocusCount = 0;
56
- /**
57
- * Number of focuses with actionable (queued) work as of the last poll. Zero
58
- * means the focus queue is drained. Used by the batch-safe idle predicate so a
59
- * transient between-delivery gap (which still has queued focuses) does not read
60
- * as idle.
61
- */
62
- export function getReadyFocusQueueDepth() {
63
- return lastReadyFocusCount;
64
- }
65
- let config = null;
66
- const roles = new Map();
67
- let pollInterval = null;
68
- let pollState = 'idle';
69
- // Polling interval in milliseconds (default: 30 seconds)
70
- const POLL_INTERVAL_MS = 30000;
71
- // Roles refresh: re-fetch from DB periodically to pick up changes
72
- const ROLES_REFRESH_INTERVAL_MS = 5 * 60 * 1000; // 5 minutes
73
- let lastRolesRefreshAt = 0;
74
- // Health check backoff: track consecutive failures to avoid hammering a broken API
75
- let healthCheckConsecutiveFailures = 0;
76
- const HEALTH_CHECK_MAX_SKIP_CYCLES = 10; // max backoff = 10 * 30s = 5 minutes
77
36
  // Spawn cooldown imported from dedicated module (avoids circular deps)
78
- import { isInSpawnCooldown, isRespawnCycleTerminated } from '../spawn/spawn-cooldown.js';
79
- import { shouldSkipPendingSpawn, recordPendingSpawnAttempt } from '../spawn/index.js';
80
- import { fileRespawnCycleExhaustedEscalation } from '../spawn/respawn-cycle-escalation.js';
37
+ import { hydrateSpawnCooldownEntry } from '../spawn/spawn-cooldown.js';
38
+ import { hydrateDeliveryAdvancementState } from '../completion/delivery-advancement-guard.js';
39
+ import { hydrateBelowCoverageDriftCount } from '../completion/coverage-drift-state.js';
81
40
  import { createLogger } from '../shell/log.js';
41
+ import { runWithTickDeadline, DEFAULT_TICK_DEADLINE_MS } from './tick-deadline.js';
42
+ // Poll-loop singletons live in listener-state.ts (the leaf); the spawn-processing
43
+ // pass in listener-spawn.ts. Reads use the live bindings; reassignments go
44
+ // through the setters. Both this module and listener-spawn.ts import the state
45
+ // leaf -- no cycle.
46
+ import { config, roles, pollInterval, healthCheckConsecutiveFailures, POLL_INTERVAL_MS, HEALTH_CHECK_MAX_SKIP_CYCLES, setConfig, setPollInterval, setPollState, setHealthCheckConsecutiveFailures, setLastRolesRefreshAt, } from './listener-state.js';
47
+ import { processReadyFocuses, processPendingSpawnFocuses } from './listener-spawn.js';
82
48
  const log = createLogger({ module: 'listener' });
83
49
  /**
84
50
  * Initialize the listener with configuration.
85
51
  */
86
52
  export async function initListener(daemonConfig) {
87
- config = daemonConfig;
53
+ setConfig(daemonConfig);
88
54
  // Load all roles
89
55
  try {
90
- const roleList = await getAgentRoles(config.organizationId);
56
+ const roleList = await getAgentRoles(daemonConfig.organizationId);
91
57
  roles.clear();
92
58
  for (const role of roleList) {
93
59
  roles.set(role.id, role);
94
60
  }
95
- lastRolesRefreshAt = Date.now();
61
+ setLastRolesRefreshAt(Date.now());
96
62
  log.info(`Loaded ${roles.size} agent roles`);
97
63
  // Seed directive executor with current stage IDs to prevent
98
64
  // re-firing directives for stages already processed before restart.
@@ -111,6 +77,19 @@ export async function initListener(daemonConfig) {
111
77
  try {
112
78
  const focuses = await getActiveFocuses(daemonConfig.organizationId, product.id);
113
79
  for (const focus of focuses) {
80
+ // Restore the durable spawn-cooldown streak + `exhausted` latch from the
81
+ // persisted columns the first time we see each focus after a (re-)start,
82
+ // BEFORE any stage/review skip -- a halted focus must stay halted across
83
+ // a re-exec regardless of its stage. Non-clobbering + first-sight-only.
84
+ hydrateSpawnCooldownEntry(focus.focus_id, {
85
+ firstAt: focus.spawn_teardown_first_at,
86
+ lastAt: focus.spawn_teardown_last_at,
87
+ count: focus.spawn_teardown_count,
88
+ exhausted: focus.spawn_cooldown_exhausted,
89
+ });
90
+ // Same restart-recovery for the other two terminating bounds (TEL-2).
91
+ hydrateDeliveryAdvancementState(focus.focus_id, focus.delivery_advancement_state);
92
+ hydrateBelowCoverageDriftCount(focus.focus_id, focus.below_coverage_drift_count);
114
93
  if (!focus.current_workflow_stage_id)
115
94
  continue;
116
95
  if (focus.review_requested_at) {
@@ -155,81 +134,104 @@ export function startDeliveryListener() {
155
134
  // Start polling with error boundary — check for work and main advancement
156
135
  // Per-product functions are called with product-scoped configs so that
157
136
  // config.productId and config.repoPath resolve to the correct product.
158
- pollInterval = safeInterval(async () => {
159
- for (const product of config.products) {
160
- const pc = configForProduct(config, product);
161
- await ensureFocusWorktrees(pc, roles);
162
- await checkForDeactivatedFocuses(pc);
163
- await checkControlStateChanges(pc);
164
- // Disarm reaper: clearing assigned_agent_role_id (dashboard Stop) only
165
- // removes the focus from the spawn set; this stops the in-flight team via
166
- // the same cooperative-stop -> SIGTERM path so Stop means stop. Runs after
167
- // checkForDeactivatedFocuses (which owns genuine status!=active teardown).
168
- await reapDisarmedTeams(pc);
169
- await checkDeliveryAutoAdvance(pc);
170
- await checkVerifyDeliveriesForOpenIssues(pc);
171
- // Work-START edge: cross queued -> coding the moment a work issue is In
172
- // Progress, during active execution (restores the deleted
173
- // auto_advance_delivery_on_issue_active trigger via the single-writer
174
- // reconciler). Runs for active-team focuses too, unlike checkDeliveryAutoAdvance.
175
- await checkDeliveryWorkStart(pc);
176
- // Single-writer consolidation (injection #10), SHADOW step: compute the
177
- // reconciler's target for each delivery and log divergence from the live
178
- // status. Writes nothing; default-OFF (opt-in via TELORA_RECONCILER_SHADOW),
179
- // so this is a no-op until an operator enables it.
180
- await runReconcilerShadow(pc);
181
- // Nexus: pick up incoming cross-product requests and apply the accept gate.
182
- await checkIncomingRequests(pc);
183
- // Nexus: spawn a target-side scoping agent for each accepted, not-yet-
184
- // materialized request so the receiving team authors a per-request focus
185
- // (no server-side authoring, no dead-letter inbox).
186
- await checkIntakePendingRequests(pc);
187
- // Nexus: close the loop -- resolve done deliveries + notify blocked sources.
188
- await checkResolvedRequests(pc);
189
- // Nexus: clear externally-owned obstacles whose endpoint-typed request has
190
- // been fulfilled (registry-shape), advancing the consumer's frontier.
191
- await checkEndpointClearance(pc);
192
- }
193
- // Endpoint discovery (D5): org-scoped, once per cycle. Best-effort -- the
194
- // daemon is the only LAN-reachable component, so it resolves the add-model
195
- // form's pending {baseUrl}/models lookups. FIRE-AND-FORGET (TEL-7): never
196
- // awaited, so a slow/blocked endpoint fetch can't delay spawn/merge work.
197
- // Its own module-level re-entrancy guard drops overlapping cycles.
198
- void checkDiscoveryRequests().catch((err) => log.warn('discovery poll failed:', err.message));
199
- await processReadyFocuses();
200
- await checkAndMergeCompletedDeliveries(config);
201
- // checkDirectives runs syncFocusPhase (writing
202
- // focus.current_workflow_stage_id) and fires stage-change directives
203
- // -- including the spawn directive that starts the review team on
204
- // entry to the `reviewing` phase. Run it before the cascade so phase
205
- // and directive state are settled when the cascade reads them.
206
- await checkDirectives(config);
207
- // Pick up focuses that have a pending spawn directive but weren't
208
- // surfaced by getReadyFocuses (e.g., review-stage spawns on focuses
209
- // whose deliveries are all in verify).
210
- await processPendingSpawnFocuses();
211
- // The cascade no longer auto-completes reviews -- closing out a
212
- // review and routing verify deliveries to done/verify_failed is the
213
- // exclusive responsibility of focus-completion.ts's review exit
214
- // handler, which gates on the exiting team's sessionType === 'review'
215
- // (positive signal that an actual review ran).
216
- await runCascadeChecks(config);
217
- // New focus workflow lifecycle transitions (D2):
218
- // - verify -> review when pipeline_config.review.enabled
219
- // - review -> close_loop on state-based convergence
220
- // - review -> review_nonconvergent escalation past the N-iter cap
221
- // Runs after the legacy cascade so the new authoritative `stage`
222
- // column reflects any updates the cascade may have made.
223
- await runStageLifecycleTransitions(config);
224
- await checkResolvedMergeConflicts(config);
225
- await detectGitStates(config);
226
- await detectFocusMerges(config);
227
- // NOTE: the targeted daemon-owned stall detectors (verify_failed re-code cap
228
- // + review-window staleness) run INSIDE runCascadeChecks above -- they ride
229
- // its existing per-focus review-handling loop (reusing the deliveries +
230
- // review phase it already fetched) rather than fanning out a second pass.
231
- await checkSessionHealth();
232
- }, POLL_INTERVAL_MS, 'focus-poll', { skipIfRunning: true });
137
+ setPollInterval(safeInterval(async () => {
138
+ // Deadline watchdog (TEL-3): surface a poll tick that stops making progress.
139
+ // safeInterval has no deadline and recordActivity() (heartbeat) is emitted
140
+ // inside this body, so a hung tick is silent AND stops the heartbeat -- from
141
+ // outside the process it looks idle, not stalled. runWithTickDeadline emits a
142
+ // warning-level `poll.stalled` health event if the body overruns and pairs a
143
+ // recovery when it settles; skipIfRunning above keeps overlapping ticks from
144
+ // piling up while a hung one drains.
145
+ await runWithTickDeadline('focus-poll', DEFAULT_TICK_DEADLINE_MS, async () => {
146
+ for (const product of config.products) {
147
+ const pc = configForProduct(config, product);
148
+ await ensureFocusWorktrees(pc, roles);
149
+ await checkForDeactivatedFocuses(pc);
150
+ await checkControlStateChanges(pc);
151
+ // Disarm reaper: clearing assigned_agent_role_id (dashboard Stop) only
152
+ // removes the focus from the spawn set; this stops the in-flight team via
153
+ // the same cooperative-stop -> SIGTERM path so Stop means stop. Runs after
154
+ // checkForDeactivatedFocuses (which owns genuine status!=active teardown).
155
+ await reapDisarmedTeams(pc);
156
+ await checkDeliveryAutoAdvance(pc);
157
+ await checkVerifyDeliveriesForOpenIssues(pc);
158
+ // Paused-revival (TEL-1): the daemon-traversable EXIT from `paused`. A
159
+ // guard-blocked delivery wedges the ranked queue and, before this, was
160
+ // never re-evaluated -- it waited for a human re-queue that never comes on
161
+ // an armed focus. This re-reads each paused delivery's open transition
162
+ // block, re-evaluates the SAME guard, and revives to queued when it has
163
+ // cleared (single-writer reconciler); a still-blocked pause stays a named
164
+ // human-act hold the wedge watchdog surfaces. Best-effort: never throws.
165
+ {
166
+ const revival = await checkPausedDeliveriesForRevival({ organizationId: pc.organizationId, productId: pc.productId }, buildDefaultPausedRevivalDeps());
167
+ if (revival.revived > 0) {
168
+ log.info(`[listener] Paused-revival: revived ${revival.revived} delivery(ies) whose blocking ` +
169
+ `guard cleared (held=${revival.held}, no-block=${revival.noBlock}).`);
170
+ }
171
+ }
172
+ // Work-START edge: cross queued -> coding the moment a work issue is In
173
+ // Progress, during active execution (restores the deleted
174
+ // auto_advance_delivery_on_issue_active trigger via the single-writer
175
+ // reconciler). Runs for active-team focuses too, unlike checkDeliveryAutoAdvance.
176
+ await checkDeliveryWorkStart(pc);
177
+ // Single-writer consolidation (injection #10), SHADOW step: compute the
178
+ // reconciler's target for each delivery and log divergence from the live
179
+ // status. Writes nothing; default-OFF (opt-in via TELORA_RECONCILER_SHADOW),
180
+ // so this is a no-op until an operator enables it.
181
+ await runReconcilerShadow(pc);
182
+ // Nexus: pick up incoming cross-product requests and apply the accept gate.
183
+ await checkIncomingRequests(pc);
184
+ // Nexus: spawn a target-side scoping agent for each accepted, not-yet-
185
+ // materialized request so the receiving team authors a per-request focus
186
+ // (no server-side authoring, no dead-letter inbox).
187
+ await checkIntakePendingRequests(pc);
188
+ // Nexus: close the loop -- resolve done deliveries + notify blocked sources.
189
+ await checkResolvedRequests(pc);
190
+ // Nexus: clear externally-owned obstacles whose endpoint-typed request has
191
+ // been fulfilled (registry-shape), advancing the consumer's frontier.
192
+ await checkEndpointClearance(pc);
193
+ }
194
+ // Endpoint discovery (D5): org-scoped, once per cycle. Best-effort -- the
195
+ // daemon is the only LAN-reachable component, so it resolves the add-model
196
+ // form's pending {baseUrl}/models lookups. FIRE-AND-FORGET (TEL-7): never
197
+ // awaited, so a slow/blocked endpoint fetch can't delay spawn/merge work.
198
+ // Its own module-level re-entrancy guard drops overlapping cycles.
199
+ void checkDiscoveryRequests().catch((err) => log.warn('discovery poll failed:', err.message));
200
+ await processReadyFocuses();
201
+ await checkAndMergeCompletedDeliveries(config);
202
+ // checkDirectives runs syncFocusPhase (writing
203
+ // focus.current_workflow_stage_id) and fires stage-change directives
204
+ // -- including the spawn directive that starts the review team on
205
+ // entry to the `reviewing` phase. Run it before the cascade so phase
206
+ // and directive state are settled when the cascade reads them.
207
+ await checkDirectives(config);
208
+ // Pick up focuses that have a pending spawn directive but weren't
209
+ // surfaced by getReadyFocuses (e.g., review-stage spawns on focuses
210
+ // whose deliveries are all in verify).
211
+ await processPendingSpawnFocuses();
212
+ // The cascade no longer auto-completes reviews -- closing out a
213
+ // review and routing verify deliveries to done/verify_failed is the
214
+ // exclusive responsibility of focus-completion.ts's review exit
215
+ // handler, which gates on the exiting team's sessionType === 'review'
216
+ // (positive signal that an actual review ran).
217
+ await runCascadeChecks(config);
218
+ // New focus workflow lifecycle transitions (D2):
219
+ // - verify -> review when pipeline_config.review.enabled
220
+ // - review -> close_loop on state-based convergence
221
+ // - review -> review_nonconvergent escalation past the N-iter cap
222
+ // Runs after the legacy cascade so the new authoritative `stage`
223
+ // column reflects any updates the cascade may have made.
224
+ await runStageLifecycleTransitions(config);
225
+ await checkResolvedMergeConflicts(config);
226
+ await detectGitStates(config);
227
+ await detectFocusMerges(config);
228
+ // NOTE: the targeted daemon-owned stall detectors (verify_failed re-code cap
229
+ // + review-window staleness) run INSIDE runCascadeChecks above -- they ride
230
+ // its existing per-focus review-handling loop (reusing the deliveries +
231
+ // review phase it already fetched) rather than fanning out a second pass.
232
+ await checkSessionHealth();
233
+ });
234
+ }, POLL_INTERVAL_MS, 'focus-poll', { skipIfRunning: true }));
233
235
  log.info(`Started focus polling (every ${POLL_INTERVAL_MS / 1000}s)`);
234
236
  }
235
237
  /**
@@ -243,386 +245,6 @@ export function startSessionListener() {
243
245
  // In polling mode, session changes are handled by checking ready focuses
244
246
  log.info('Session monitoring via polling (included in focus polling)');
245
247
  }
246
- export function decideFocusSpawn(gate) {
247
- if (!gate.hasRole)
248
- return 'skip_unknown_role';
249
- if (gate.hasActiveTeam)
250
- return 'skip_active_team';
251
- if (gate.inSpawnCooldown)
252
- return 'skip_cooldown';
253
- if (gate.activeTeamCount >= gate.maxTotalSessions)
254
- return 'stop_capacity';
255
- return 'spawn';
256
- }
257
- /**
258
- * Process any focuses ready for agent work.
259
- *
260
- * For each ready focus without an active team, spawns a single
261
- * Agent Team that manages all deliveries within that focus.
262
- * Also detects focuses that have been deactivated and shuts down
263
- * their teams.
264
- */
265
- export async function processReadyFocuses() {
266
- if (!config)
267
- return;
268
- // Prevent concurrent processing, and skip cycles during health check backoff
269
- if (pollState === 'processing' || pollState === 'backing_off') {
270
- return;
271
- }
272
- pollState = 'processing';
273
- try {
274
- // Periodically refresh agent roles to pick up changes
275
- if (Date.now() - lastRolesRefreshAt > ROLES_REFRESH_INTERVAL_MS) {
276
- try {
277
- const roleList = await getAgentRoles(config.organizationId);
278
- roles.clear();
279
- for (const role of roleList) {
280
- roles.set(role.id, role);
281
- }
282
- lastRolesRefreshAt = Date.now();
283
- log.info(`Refreshed agent roles (${roles.size} roles loaded)`);
284
- }
285
- catch (err) {
286
- log.warn(`Failed to refresh agent roles: ${err.message}`);
287
- }
288
- }
289
- // Record activity: proves the poll ran even if no work was found
290
- recordActivity();
291
- // Snapshot active teams before processing (for idle detection)
292
- const previouslyActiveIds = new Set(getActiveTeams().keys());
293
- // Deactivation detection is now handled by checkForDeactivatedFocuses()
294
- // in the poll loop (called before processReadyFocuses).
295
- // Get ready focuses across all configured products
296
- const readyFocuses = [];
297
- for (const product of config.products) {
298
- try {
299
- const focuses = await getReadyFocuses(config.organizationId, product.id);
300
- readyFocuses.push(...focuses);
301
- }
302
- catch (err) {
303
- log.warn(`Failed to fetch ready focuses for product ${product.id}: ${err.message}`);
304
- }
305
- }
306
- // Publish the queue depth for the auto-updater's batch-safe idle predicate.
307
- // Set before any early-return below so a drained queue is observable.
308
- lastReadyFocusCount = readyFocuses.length;
309
- // Track which focuses are ready this cycle (for idle logging)
310
- const readyFocusIds = new Set(readyFocuses.map(s => s.focus_id));
311
- // Idle queue logging: detect focuses that were previously active
312
- // but now have no ready deliveries. Only log on state change.
313
- for (const focusId of previouslyActiveIds) {
314
- if (!readyFocusIds.has(focusId) && !hasActiveTeam(focusId) && !idleLoggedFocuses.has(focusId)) {
315
- const nextPollSec = POLL_INTERVAL_MS / 1000;
316
- log.info(`Focus queue idle: "${focusId}" -- no actionable deliveries, next poll in ${nextPollSec}s`);
317
- idleLoggedFocuses.add(focusId);
318
- }
319
- }
320
- if (readyFocuses.length === 0) {
321
- return;
322
- }
323
- // Intake drain (auto-update delivery C): a pending update has been stale
324
- // past threshold while the daemon stays busy, so we stop accepting new focus
325
- // teams to let the current batch become the safe seam. Active teams continue
326
- // to completion; we simply do not spawn more. The held queue does not block
327
- // the batch boundary because isBatchSafeIdle honors isIntakeDraining().
328
- if (isIntakeDraining()) {
329
- log.info(`[listener] Intake drained for pending update -- holding ${readyFocuses.length} ready focus(es); no new teams will spawn`);
330
- return;
331
- }
332
- const productLabel = config.products.length > 1
333
- ? ` across ${config.products.length} product(s)`
334
- : '';
335
- log.info(`Found ${readyFocuses.length} ready focuses${productLabel}`);
336
- for (const focus of readyFocuses) {
337
- const role = roles.get(focus.assigned_agent_role_id);
338
- // Resolve the per-focus spawn-gate decision (pure; see decideFocusSpawn).
339
- // The capacity input is the LIVE team count sampled this iteration, so a
340
- // spawn earlier in this loop is reflected for the next focus.
341
- const action = decideFocusSpawn({
342
- hasRole: role !== undefined,
343
- hasActiveTeam: hasActiveTeam(focus.focus_id),
344
- inSpawnCooldown: isInSpawnCooldown(focus.focus_id),
345
- activeTeamCount: getActiveTeamCount(),
346
- maxTotalSessions: config.maxTotalSessions,
347
- });
348
- if (action === 'skip_unknown_role') {
349
- log.error(`Unknown agent role: ${focus.assigned_agent_role_id}`);
350
- continue;
351
- }
352
- if (action === 'skip_active_team') {
353
- // Focus is active -- clear idle state so we re-log if it goes idle again
354
- idleLoggedFocuses.delete(focus.focus_id);
355
- // A re-arm flag may have landed while a team is up. Do NOT clear it here:
356
- // the answer is consumed only at (re)spawn time via the escalation.answers
357
- // resolver, and a still-live team does not re-run assembly mid-session
358
- // (the wake-message handoff does not carry answers). Leaving the flag set
359
- // lets the force-spawn deliver the answer once this team exits; clearing
360
- // it now would strand the answer. The clear happens on the force-spawn
361
- // below (one answer -> one re-spawn).
362
- continue;
363
- }
364
- // Skip focuses that were recently torn down (spawn cooldown).
365
- if (action === 'skip_cooldown') {
366
- continue;
367
- }
368
- // Total capacity reached -- stop the whole loop, not just this focus.
369
- if (action === 'stop_capacity') {
370
- log.info(`Total session limit reached, stopping`);
371
- break;
372
- }
373
- // action === 'spawn'. role is guaranteed defined (hasRole was true);
374
- // this guard narrows the type and is otherwise unreachable.
375
- if (!role)
376
- continue;
377
- const isRearm = focus.answer_rearm_requested_at != null;
378
- // Clear idle state before spawn attempt -- if spawn succeeds,
379
- // the focus is active. If it skips (no actionable deliveries),
380
- // spawnFocusTeam logs the skip and we mark it idle here.
381
- const wasIdle = idleLoggedFocuses.has(focus.focus_id);
382
- idleLoggedFocuses.delete(focus.focus_id);
383
- // Use product-scoped config so the team uses the correct repoPath
384
- const productEntry = focus.product_id ? findProduct(config, focus.product_id) : undefined;
385
- const spawnConfig = productEntry ? configForProduct(config, productEntry) : config;
386
- // Spawn a focus team (one team per focus). forceSpawn lets a re-armed
387
- // focus spawn past findActionableDeliveries' 'skip' to consume the
388
- // pending escalation answer.
389
- // Audit-phase focus (explicit read_only=true OR the second structural
390
- // signal: audit_origin && not-yet-ratified, TEL-3) spawns read-only, so a
391
- // flag-absent audit focus is not driven into scoping/write/commit ahead of
392
- // ratification (2f253da6). See isAuditPhaseFocusRow.
393
- await spawnFocusTeam({ config: spawnConfig, focusId: focus.focus_id, focusName: focus.focus_name, role, pipelineConfig: focus.pipeline_config, readOnly: isAuditPhaseFocusRow(focus), lastClaudeSessionId: focus.last_claude_session_id, claudeSessionIds: focus.claude_session_ids, engineOverride: focus.engine_override, localModelOverride: focus.local_model_override, orgDefaultEngine: focus.org_default_engine, pinnedBranch: focus.branch_name, forceSpawn: isRearm });
394
- // Clear the re-arm flag only once a team is actually live to consume the
395
- // answer: one answer -> one re-spawn. If the force-spawn did NOT produce an
396
- // active team (worktree/governor failure, etc.), leave the flag set so a
397
- // later poll retries delivery rather than stranding the answer. (Cooldown
398
- // and capacity exit above without reaching here, so they also keep the
399
- // flag.) Done before the idle check below so a consumed re-arm is not
400
- // re-surfaced next poll.
401
- if (isRearm && hasActiveTeam(focus.focus_id)) {
402
- await clearAnswerRearm(focus.focus_id).catch((err) => log.warn(`Failed to clear answer re-arm for "${focus.focus_name}": ${err.message}`));
403
- }
404
- // If the spawn was skipped (no active team after spawn), mark as idle
405
- if (!hasActiveTeam(focus.focus_id) && !wasIdle) {
406
- const nextPollSec = POLL_INTERVAL_MS / 1000;
407
- log.info(`Focus queue idle: "${focus.focus_name}" -- no actionable deliveries after pre-spawn check, next poll in ${nextPollSec}s`);
408
- idleLoggedFocuses.add(focus.focus_id);
409
- }
410
- }
411
- }
412
- catch (error) {
413
- log.error('Error processing ready focuses:', error);
414
- }
415
- finally {
416
- pollState = healthCheckConsecutiveFailures > 0 ? 'backing_off' : 'idle';
417
- }
418
- }
419
- /**
420
- * Spawn teams for focuses that have a pending spawn directive but were
421
- * not surfaced by getReadyFocuses.
422
- *
423
- * getReadyFocuses (daemon-focus-handlers.ts) only returns focuses with
424
- * queued/coding deliveries (or zero deliveries). A review-stage spawn
425
- * directive on a focus whose deliveries are all in verify never makes
426
- * it to processReadyFocuses -- the pending directive sits forever and
427
- * the team never starts. This function bridges that gap: scan the
428
- * pending-spawn map directly and spawn for any focus that's not
429
- * already running.
430
- */
431
- export async function processPendingSpawnFocuses() {
432
- if (!config)
433
- return;
434
- if (isDaemonShuttingDown())
435
- return;
436
- const pendingIds = getPendingSpawnFocusIds().filter(id => !hasActiveTeam(id));
437
- if (pendingIds.length === 0)
438
- return;
439
- // Fetch focus details for all configured products. getActiveFocuses
440
- // returns the metadata spawnFocusTeam needs.
441
- const focusDetailsById = new Map();
442
- // Whether ANY product's getActiveFocuses read failed this tick. A fetch failure
443
- // makes the active set incomplete, so a pending focus can look absent purely
444
- // because its product's read failed -- absence is only trustworthy when this is
445
- // false. Gates the zombie-directive reap below.
446
- let anyFetchFailed = false;
447
- for (const product of config.products) {
448
- try {
449
- const focuses = await getActiveFocuses(config.organizationId, product.id);
450
- for (const f of focuses) {
451
- if (pendingIds.includes(f.focus_id)) {
452
- focusDetailsById.set(f.focus_id, f);
453
- }
454
- }
455
- }
456
- catch (err) {
457
- anyFetchFailed = true;
458
- log.warn(`[listener] processPendingSpawnFocuses: failed to fetch active focuses for product ${product.id}: ${err.message}`);
459
- }
460
- }
461
- for (const focusId of pendingIds) {
462
- const focus = focusDetailsById.get(focusId);
463
- if (!focus) {
464
- // Focus is absent from the active set. If the read was complete this tick
465
- // (no fetch failure), the focus has genuinely left the active set -- its
466
- // directive is a zombie: reap it once instead of retrying forever. On a
467
- // fetch-failure tick the absence is untrustworthy, so keep retrying.
468
- if (shouldReapPendingSpawnDirective({ anyFetchFailed, focusPresentInActiveSet: false })) {
469
- clearPendingSpawnDirective(focusId);
470
- // Focus left the active set -- the blocked episode is over. Clear its
471
- // once-per-episode warn marker so a future re-entry warns again.
472
- nullRoleSkipWarnedFocuses.delete(focusId);
473
- log.info(`[listener] Dropping stale spawn directive for focus ${focusId.slice(0, 8)}: ` +
474
- `confirmed absent from every product's active set (focus completed/deactivated).`);
475
- }
476
- else {
477
- log.warn(`[listener] Pending spawn directive for focus ${focusId.slice(0, 8)} but focus not found in active set; ` +
478
- `active-set read incomplete this tick (fetch failure) -- retrying next poll.`);
479
- }
480
- continue;
481
- }
482
- let role;
483
- if (!focus.assigned_agent_role_id) {
484
- // A null assigned_agent_role_id now means ONE OF TWO things, and they are
485
- // NOT interchangeable:
486
- // - operator_stopped_at set => an operator pulled the Play toggle to
487
- // halt the focus. Authoritative: it wins over the review fallback.
488
- // - operator_stopped_at null => auto_terminate_focus_team() cleared the
489
- // role at completion (lifecycle). Here the review fallback applies:
490
- // a review is a SECOND team that must spawn AFTER completion; a coding
491
- // directive is the intended off-state and must NOT auto-spawn.
492
- // decideNullRoleSpawn disambiguates via operatorStopped; the listener then
493
- // acts on the decision (drop the directive on a stop, throttle the
494
- // off-state warning, or spawn the lifecycle review fallback).
495
- const operatorStopped = !!focus.operator_stopped_at;
496
- const pending = getPendingSpawnDirective(focus.focus_id);
497
- const decision = decideNullRoleSpawn({
498
- sessionType: pending?.sessionType,
499
- fallbackRole: resolveReviewerFallbackRole(roles.values()),
500
- operatorStopped,
501
- });
502
- if (decision.action === 'skip') {
503
- if (operatorStopped) {
504
- // Operator STOP wins over the review fallback. Drop any directive
505
- // queued while stopped so it does not silently survive to resume and
506
- // fire the path the stop was meant to prevent. Dropping also removes
507
- // the focus from the pending set, so this suppression is logged once
508
- // per focus (per stop), never once per poll.
509
- if (pending)
510
- clearPendingSpawnDirective(focus.focus_id);
511
- nullRoleSkipWarnedFocuses.delete(focus.focus_id);
512
- log.info(`[listener] Focus ${focusId.slice(0, 8)} ("${focus.focus_name}") is held by an operator stop; ` +
513
- `suppressing the queued ${pending?.sessionType ?? 'spawn'} directive (dropped, not carried to resume).`);
514
- continue;
515
- }
516
- // Coding off-state (Play/queue toggle cleared, no operator stop): the
517
- // directive legitimately stays queued. Bound the warning to once per
518
- // blocked episode so a stopped focus does not spin-log every 30s poll.
519
- if (shouldWarnNullRoleSkip(nullRoleSkipWarnedFocuses, focus.focus_id)) {
520
- log.warn(`[listener] Pending spawn directive for focus ${focusId.slice(0, 8)} ("${focus.focus_name}") but no assigned_agent_role_id; ` +
521
- `skipping spawn (coding off-state). Suppressing further per-poll warnings until it resumes.`);
522
- }
523
- continue;
524
- }
525
- // Past the skip paths: this is a genuine lifecycle clear (operatorStopped
526
- // is false here). The episode is over either way, so clear the marker.
527
- nullRoleSkipWarnedFocuses.delete(focus.focus_id);
528
- if (decision.action === 'clear') {
529
- // Review strand with no resolvable fallback role: drop the directive so
530
- // the stranded-review re-fire suppression (directive-executor) cannot
531
- // spin-log forever. review_window_stalled remains the escalation path,
532
- // and now fires promptly rather than after 6h of churn.
533
- clearPendingSpawnDirective(focus.focus_id);
534
- log.warn(`[listener] Stranded review directive for focus ${focusId.slice(0, 8)} ("${focus.focus_name}") has a null assigned_agent_role_id ` +
535
- `and no seeded '${DEFAULT_REVIEWER_ROLE_NAME}' fallback role resolved -- dropping the directive so it stops re-firing; ` +
536
- `review_window_stalled will escalate.`);
537
- continue;
538
- }
539
- role = decision.role;
540
- // Reachable only when operatorStopped is false (an operator stop takes the
541
- // skip path above), so "(coding team completed)" is a true provenance
542
- // claim here rather than an assumption stamped on every null-role spawn.
543
- log.info(`[listener] Review directive for focus ${focusId.slice(0, 8)} ("${focus.focus_name}") has a null assigned_agent_role_id ` +
544
- `(coding team completed); spawning review with fallback role "${role.name}".`);
545
- }
546
- else {
547
- // Focus has a role again -- any prior null-role blocked episode is over.
548
- nullRoleSkipWarnedFocuses.delete(focus.focus_id);
549
- role = roles.get(focus.assigned_agent_role_id);
550
- if (!role) {
551
- log.warn(`[listener] Pending spawn directive for focus ${focusId.slice(0, 8)} ("${focus.focus_name}") references unknown role ${focus.assigned_agent_role_id}; ` +
552
- `skipping spawn.`);
553
- continue;
554
- }
555
- }
556
- // Park focuses with an unhealed team_failure escalation BEFORE attempting
557
- // the spawn. spawnFocusTeam applies the same guard, but it returns after
558
- // the guard fires without consuming the pending directive -- so the
559
- // directive stays queued and this loop re-attempts (and the guard re-logs)
560
- // every poll. Checking here skips the misleading "Spawning team" log and
561
- // the wasted spawn call; the directive intentionally stays queued so the
562
- // (re)review resumes once a human resolves the escalation. The guard
563
- // throttles its own log to once per blocked episode.
564
- if (await applyRespawnGuard(focus.focus_id, focus.focus_name)) {
565
- continue;
566
- }
567
- // Spawn cooldown: this path spawns teams for focuses that getReadyFocuses
568
- // does NOT surface -- focuses with no actionable/queued deliveries (e.g. a
569
- // reviewing-stage spawn over a focus whose deliveries are all in verify).
570
- // Such a team idle-exits if its directive can't make progress; without a
571
- // gate the directive re-fires every poll and the daemon respawns an
572
- // immediately-idle team every cycle (the reinforcing auto-review/idle-spawn
573
- // loop). See pending-spawn-guard.ts.
574
- // Terminated respawn cycle: the spawn/teardown loop for this focus has
575
- // exhausted its total-duration/count bounds (spawn-cooldown.ts). Respawns are
576
- // halted permanently (isInSpawnCooldown returns true indefinitely), so the
577
- // spawn is skipped either way -- but a silent halt is a strand. File the
578
- // one-per-focus respawn_cycle_exhausted escalation (idempotent, so filing on
579
- // each poll while terminated yields a single open row) so the terminated
580
- // cycle is a legible state a human can see and clear.
581
- if (isRespawnCycleTerminated(focus.focus_id)) {
582
- await fileRespawnCycleExhaustedEscalation({
583
- organizationId: config.organizationId,
584
- focusId: focus.focus_id,
585
- focusName: focus.focus_name,
586
- });
587
- continue;
588
- }
589
- if (shouldSkipPendingSpawn(focus.focus_id)) {
590
- continue;
591
- }
592
- if (getActiveTeamCount() >= config.maxTotalSessions) {
593
- log.info(`Total session limit reached, deferring pending spawn for "${focus.focus_name}"`);
594
- break;
595
- }
596
- const productEntry = focus.product_id ? findProduct(config, focus.product_id) : undefined;
597
- const spawnConfig = productEntry ? configForProduct(config, productEntry) : config;
598
- log.info(`[listener] Spawning team for focus "${focus.focus_name}" via pending spawn directive ` +
599
- `(no actionable deliveries, but directive is queued)`);
600
- // Audit-phase => read-only spawn (see the processReadyFocuses spawn above).
601
- // The pending-spawn focus comes from getActiveFocuses, which carries
602
- // audit_origin + ratified_at, so the second signal (TEL-3) resolves here too.
603
- await spawnFocusTeam({
604
- config: spawnConfig,
605
- focusId: focus.focus_id,
606
- focusName: focus.focus_name,
607
- role,
608
- pipelineConfig: focus.pipeline_config,
609
- readOnly: isAuditPhaseFocusRow(focus),
610
- lastClaudeSessionId: null,
611
- claudeSessionIds: focus.claude_session_ids,
612
- engineOverride: focus.engine_override,
613
- localModelOverride: focus.local_model_override,
614
- orgDefaultEngine: focus.org_default_engine,
615
- pinnedBranch: focus.branch_name,
616
- });
617
- // Record a teardown so repeated spawns through this no-actionable-work path
618
- // back off exponentially instead of recurring every poll. A team that
619
- // genuinely makes progress moves the focus into a state getReadyFocuses
620
- // surfaces (and clearSpawnCooldown can reset it); a team that idle-exits is
621
- // rate-limited by the growing cooldown. This bounds spawn count on a
622
- // wedged-in-verify focus. See pending-spawn-guard.ts.
623
- recordPendingSpawnAttempt(focus.focus_id);
624
- }
625
- }
626
248
  /**
627
249
  * Check that every session recorded as 'starting' or 'running' in the DB
628
250
  * has a corresponding active team in memory.
@@ -639,7 +261,7 @@ export async function checkSessionHealth() {
639
261
  if (healthCheckConsecutiveFailures > 0) {
640
262
  // Use the failure count as the number of cycles to skip
641
263
  const skipCycles = Math.min(healthCheckConsecutiveFailures, HEALTH_CHECK_MAX_SKIP_CYCLES);
642
- healthCheckConsecutiveFailures++;
264
+ setHealthCheckConsecutiveFailures(healthCheckConsecutiveFailures + 1);
643
265
  if (healthCheckConsecutiveFailures % skipCycles !== 0) {
644
266
  return; // skip this cycle
645
267
  }
@@ -655,13 +277,13 @@ export async function checkSessionHealth() {
655
277
  // Reset backoff on success
656
278
  if (healthCheckConsecutiveFailures > 0) {
657
279
  log.info(`[health] Session health check recovered after ${healthCheckConsecutiveFailures} failures`);
658
- healthCheckConsecutiveFailures = 0;
659
- pollState = 'idle';
280
+ setHealthCheckConsecutiveFailures(0);
281
+ setPollState('idle');
660
282
  }
661
283
  }
662
284
  catch (err) {
663
- healthCheckConsecutiveFailures++;
664
- pollState = 'backing_off';
285
+ setHealthCheckConsecutiveFailures(healthCheckConsecutiveFailures + 1);
286
+ setPollState('backing_off');
665
287
  log.warn(`[health] Failed to fetch active sessions (failure #${healthCheckConsecutiveFailures}): ${err.message}`);
666
288
  return;
667
289
  }
@@ -746,7 +368,7 @@ export async function checkSessionHealth() {
746
368
  export async function stopListeners() {
747
369
  if (pollInterval) {
748
370
  clearInterval(pollInterval);
749
- pollInterval = null;
371
+ setPollInterval(null);
750
372
  }
751
373
  log.info('Stopped polling');
752
374
  }
@@ -761,7 +383,7 @@ export async function refreshRoles() {
761
383
  for (const role of roleList) {
762
384
  roles.set(role.id, role);
763
385
  }
764
- lastRolesRefreshAt = Date.now();
386
+ setLastRolesRefreshAt(Date.now());
765
387
  log.info(`Refreshed ${roles.size} agent roles`);
766
388
  }
767
389
  /**