@telora/daemon 0.21.134 → 0.21.146

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 (276) hide show
  1. package/build-info.json +2 -2
  2. package/dist/audit/audit-hooks.d.ts +15 -6
  3. package/dist/audit/audit-hooks.d.ts.map +1 -1
  4. package/dist/audit/audit-hooks.js +56 -28
  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 +24 -0
  9. package/dist/ci/ci-sensor-loop.d.ts.map +1 -1
  10. package/dist/ci/ci-sensor-loop.js +35 -9
  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-stabilized.d.ts +1 -1
  25. package/dist/completion/coverage-stabilized.d.ts.map +1 -1
  26. package/dist/completion/coverage-stabilized.js +1 -1
  27. package/dist/completion/coverage-stabilized.js.map +1 -1
  28. package/dist/completion/escalation-provenance.d.ts +2 -1
  29. package/dist/completion/escalation-provenance.d.ts.map +1 -1
  30. package/dist/completion/escalation-provenance.js +2 -1
  31. package/dist/completion/escalation-provenance.js.map +1 -1
  32. package/dist/completion/escalations.js +1 -1
  33. package/dist/completion/escalations.js.map +1 -1
  34. package/dist/completion/event-escalations.d.ts +1 -1
  35. package/dist/completion/event-escalations.d.ts.map +1 -1
  36. package/dist/completion/event-escalations.js +1 -1
  37. package/dist/completion/event-escalations.js.map +1 -1
  38. package/dist/completion/event.d.ts +1 -1
  39. package/dist/completion/event.d.ts.map +1 -1
  40. package/dist/completion/event.js +2 -2
  41. package/dist/completion/event.js.map +1 -1
  42. package/dist/completion/machine-remediable-demotion.d.ts +1 -1
  43. package/dist/completion/machine-remediable-demotion.d.ts.map +1 -1
  44. package/dist/completion/machine-remediable-demotion.js +2 -1
  45. package/dist/completion/machine-remediable-demotion.js.map +1 -1
  46. package/dist/completion/read-only-pristine.d.ts +1 -1
  47. package/dist/completion/read-only-pristine.d.ts.map +1 -1
  48. package/dist/completion/read-only-pristine.js +1 -1
  49. package/dist/completion/read-only-pristine.js.map +1 -1
  50. package/dist/completion/review-exit-escalations.d.ts +1 -1
  51. package/dist/completion/review-exit-escalations.d.ts.map +1 -1
  52. package/dist/completion/review-exit-phase.d.ts +5 -2
  53. package/dist/completion/review-exit-phase.d.ts.map +1 -1
  54. package/dist/completion/review-exit-phase.js +5 -2
  55. package/dist/completion/review-exit-phase.js.map +1 -1
  56. package/dist/completion/settle-injection-sweep.d.ts +9 -7
  57. package/dist/completion/settle-injection-sweep.d.ts.map +1 -1
  58. package/dist/completion/settle-injection-sweep.js +17 -15
  59. package/dist/completion/settle-injection-sweep.js.map +1 -1
  60. package/dist/completion/state-cascade.js +2 -2
  61. package/dist/completion/state-cascade.js.map +1 -1
  62. package/dist/completion/status-advance-phase.d.ts.map +1 -1
  63. package/dist/completion/status-advance-phase.js +2 -1
  64. package/dist/completion/status-advance-phase.js.map +1 -1
  65. package/dist/completion/team-lifecycle.d.ts.map +1 -1
  66. package/dist/completion/team-lifecycle.js +2 -1
  67. package/dist/completion/team-lifecycle.js.map +1 -1
  68. package/dist/delivery/delivery-advance-block.d.ts +1 -1
  69. package/dist/delivery/delivery-advance-block.d.ts.map +1 -1
  70. package/dist/delivery/delivery-advance-block.js +1 -1
  71. package/dist/delivery/delivery-advance-block.js.map +1 -1
  72. package/dist/delivery/delivery-lifecycle.js +1 -1
  73. package/dist/delivery/delivery-lifecycle.js.map +1 -1
  74. package/dist/delivery/delivery-merge-ops.js +1 -1
  75. package/dist/delivery/delivery-merge-ops.js.map +1 -1
  76. package/dist/delivery/delivery-reconciler-shadow.d.ts +1 -1
  77. package/dist/delivery/delivery-reconciler-shadow.d.ts.map +1 -1
  78. package/dist/delivery/delivery-reconciler-shadow.js +1 -1
  79. package/dist/delivery/delivery-reconciler-shadow.js.map +1 -1
  80. package/dist/delivery/operator-hold.d.ts +1 -1
  81. package/dist/delivery/operator-hold.d.ts.map +1 -1
  82. package/dist/delivery/paused-revival.d.ts.map +1 -1
  83. package/dist/delivery/paused-revival.js +2 -1
  84. package/dist/delivery/paused-revival.js.map +1 -1
  85. package/dist/directive/close-loop-dispatcher.d.ts +35 -12
  86. package/dist/directive/close-loop-dispatcher.d.ts.map +1 -1
  87. package/dist/directive/close-loop-dispatcher.js +31 -11
  88. package/dist/directive/close-loop-dispatcher.js.map +1 -1
  89. package/dist/directive/directive-dispatch.d.ts +1 -1
  90. package/dist/directive/directive-dispatch.d.ts.map +1 -1
  91. package/dist/directive/directive-dispatch.js +1 -1
  92. package/dist/directive/directive-dispatch.js.map +1 -1
  93. package/dist/directive/directive-executor.js +1 -1
  94. package/dist/directive/directive-executor.js.map +1 -1
  95. package/dist/drift/staleness-sensor.js +1 -1
  96. package/dist/drift/staleness-sensor.js.map +1 -1
  97. package/dist/focus/focus-engine.js +3 -2
  98. package/dist/focus/focus-engine.js.map +1 -1
  99. package/dist/focus/focus-lifecycle.d.ts.map +1 -1
  100. package/dist/focus/focus-lifecycle.js +2 -1
  101. package/dist/focus/focus-lifecycle.js.map +1 -1
  102. package/dist/focus/focus-respawn-guard.d.ts +1 -1
  103. package/dist/focus/focus-respawn-guard.d.ts.map +1 -1
  104. package/dist/focus/focus-respawn-guard.js +1 -1
  105. package/dist/focus/focus-respawn-guard.js.map +1 -1
  106. package/dist/focus/listener-auto-advance.d.ts +4 -45
  107. package/dist/focus/listener-auto-advance.d.ts.map +1 -1
  108. package/dist/focus/listener-auto-advance.js +5 -238
  109. package/dist/focus/listener-auto-advance.js.map +1 -1
  110. package/dist/focus/listener-spawn.d.ts +62 -0
  111. package/dist/focus/listener-spawn.d.ts.map +1 -0
  112. package/dist/focus/listener-spawn.js +418 -0
  113. package/dist/focus/listener-spawn.js.map +1 -0
  114. package/dist/focus/listener-state.d.ts +57 -0
  115. package/dist/focus/listener-state.d.ts.map +1 -0
  116. package/dist/focus/listener-state.js +63 -0
  117. package/dist/focus/listener-state.js.map +1 -0
  118. package/dist/focus/listener.d.ts +0 -58
  119. package/dist/focus/listener.d.ts.map +1 -1
  120. package/dist/focus/listener.js +25 -453
  121. package/dist/focus/listener.js.map +1 -1
  122. package/dist/focus/verify-open-issues.d.ts +62 -0
  123. package/dist/focus/verify-open-issues.d.ts.map +1 -0
  124. package/dist/focus/verify-open-issues.js +265 -0
  125. package/dist/focus/verify-open-issues.js.map +1 -0
  126. package/dist/focus-stage-lifecycle/index.d.ts +1 -1
  127. package/dist/focus-stage-lifecycle/index.d.ts.map +1 -1
  128. package/dist/focus-stage-lifecycle/index.js +1 -1
  129. package/dist/focus-stage-lifecycle/index.js.map +1 -1
  130. package/dist/focus-stage-lifecycle/wedge-watchdog.d.ts +1 -1
  131. package/dist/focus-stage-lifecycle/wedge-watchdog.d.ts.map +1 -1
  132. package/dist/focus-stage-lifecycle/wedge-watchdog.js +1 -1
  133. package/dist/focus-stage-lifecycle/wedge-watchdog.js.map +1 -1
  134. package/dist/git/index.d.ts +2 -0
  135. package/dist/git/index.d.ts.map +1 -1
  136. package/dist/git/index.js +2 -0
  137. package/dist/git/index.js.map +1 -1
  138. package/dist/git/merge-strategies.d.ts +80 -0
  139. package/dist/git/merge-strategies.d.ts.map +1 -0
  140. package/dist/git/merge-strategies.js +218 -0
  141. package/dist/git/merge-strategies.js.map +1 -0
  142. package/dist/git/merge-to-main.d.ts +76 -0
  143. package/dist/git/merge-to-main.d.ts.map +1 -0
  144. package/dist/git/merge-to-main.js +256 -0
  145. package/dist/git/merge-to-main.js.map +1 -0
  146. package/dist/git/merge.d.ts +27 -81
  147. package/dist/git/merge.d.ts.map +1 -1
  148. package/dist/git/merge.js +4 -448
  149. package/dist/git/merge.js.map +1 -1
  150. package/dist/git/state-detector.js +1 -1
  151. package/dist/git/state-detector.js.map +1 -1
  152. package/dist/loops/engine.js +1 -1
  153. package/dist/loops/engine.js.map +1 -1
  154. package/dist/merge/focus-merge.d.ts +1 -1
  155. package/dist/merge/focus-merge.d.ts.map +1 -1
  156. package/dist/merge/focus-merge.js +1 -1
  157. package/dist/merge/focus-merge.js.map +1 -1
  158. package/dist/merge/merge-back-loop.d.ts +1 -1
  159. package/dist/merge/merge-back-loop.d.ts.map +1 -1
  160. package/dist/merge/merge-back-loop.js +2 -2
  161. package/dist/merge/merge-back-loop.js.map +1 -1
  162. package/dist/merge/merge-detector.js +1 -1
  163. package/dist/merge/merge-detector.js.map +1 -1
  164. package/dist/nexus/return.js +1 -1
  165. package/dist/nexus/return.js.map +1 -1
  166. package/dist/prd/controller-shared.d.ts +223 -0
  167. package/dist/prd/controller-shared.d.ts.map +1 -0
  168. package/dist/prd/controller-shared.js +77 -0
  169. package/dist/prd/controller-shared.js.map +1 -0
  170. package/dist/prd/controller.d.ts +5 -256
  171. package/dist/prd/controller.d.ts.map +1 -1
  172. package/dist/prd/controller.js +9 -166
  173. package/dist/prd/controller.js.map +1 -1
  174. package/dist/prd/done-sweep.d.ts +55 -0
  175. package/dist/prd/done-sweep.d.ts.map +1 -0
  176. package/dist/prd/done-sweep.js +112 -0
  177. package/dist/prd/done-sweep.js.map +1 -0
  178. package/dist/prompt-sections/execution-sections.d.ts +1 -1
  179. package/dist/prompt-sections/execution-sections.d.ts.map +1 -1
  180. package/dist/prompt-sections/execution-sections.js +12 -2
  181. package/dist/prompt-sections/execution-sections.js.map +1 -1
  182. package/dist/prompt-sections/shared.d.ts +1 -1
  183. package/dist/prompt-sections/shared.d.ts.map +1 -1
  184. package/dist/queries/agent-resolvable-promotion.d.ts +1 -1
  185. package/dist/queries/agent-resolvable-promotion.d.ts.map +1 -1
  186. package/dist/queries/agent-resolvable-promotion.js +2 -1
  187. package/dist/queries/agent-resolvable-promotion.js.map +1 -1
  188. package/dist/queries/deliveries.d.ts +0 -195
  189. package/dist/queries/deliveries.d.ts.map +1 -1
  190. package/dist/queries/deliveries.js +1 -83
  191. package/dist/queries/deliveries.js.map +1 -1
  192. package/dist/queries/escalation-consumption.d.ts +120 -0
  193. package/dist/queries/escalation-consumption.d.ts.map +1 -0
  194. package/dist/queries/escalation-consumption.js +143 -0
  195. package/dist/queries/escalation-consumption.js.map +1 -0
  196. package/dist/queries/escalation-dispositions.d.ts +19 -0
  197. package/dist/queries/escalation-dispositions.d.ts.map +1 -0
  198. package/dist/queries/escalation-dispositions.js +18 -0
  199. package/dist/queries/escalation-dispositions.js.map +1 -0
  200. package/dist/queries/escalation-suppression.d.ts +1 -1
  201. package/dist/queries/escalation-suppression.d.ts.map +1 -1
  202. package/dist/queries/escalation-suppression.js +1 -1
  203. package/dist/queries/escalation-suppression.js.map +1 -1
  204. package/dist/queries/escalations.d.ts +343 -0
  205. package/dist/queries/escalations.d.ts.map +1 -0
  206. package/dist/queries/escalations.js +493 -0
  207. package/dist/queries/escalations.js.map +1 -0
  208. package/dist/queries/focus-discovery.d.ts +204 -0
  209. package/dist/queries/focus-discovery.d.ts.map +1 -0
  210. package/dist/queries/focus-discovery.js +92 -0
  211. package/dist/queries/focus-discovery.js.map +1 -0
  212. package/dist/queries/index.d.ts +4 -2
  213. package/dist/queries/index.d.ts.map +1 -1
  214. package/dist/queries/index.js +4 -2
  215. package/dist/queries/index.js.map +1 -1
  216. package/dist/queries/issues.d.ts +8 -436
  217. package/dist/queries/issues.d.ts.map +1 -1
  218. package/dist/queries/issues.js +7 -615
  219. package/dist/queries/issues.js.map +1 -1
  220. package/dist/resolvers/agent-escalations.d.ts +20 -0
  221. package/dist/resolvers/agent-escalations.d.ts.map +1 -1
  222. package/dist/resolvers/agent-escalations.js +40 -10
  223. package/dist/resolvers/agent-escalations.js.map +1 -1
  224. package/dist/resolvers/index.d.ts +2 -2
  225. package/dist/resolvers/index.d.ts.map +1 -1
  226. package/dist/resolvers/index.js +1 -1
  227. package/dist/resolvers/index.js.map +1 -1
  228. package/dist/resolvers/shared/git-diff.d.ts +99 -18
  229. package/dist/resolvers/shared/git-diff.d.ts.map +1 -1
  230. package/dist/resolvers/shared/git-diff.js +98 -27
  231. package/dist/resolvers/shared/git-diff.js.map +1 -1
  232. package/dist/shell/control-state.d.ts +1 -1
  233. package/dist/shell/control-state.d.ts.map +1 -1
  234. package/dist/shell/control-state.js +1 -1
  235. package/dist/shell/control-state.js.map +1 -1
  236. package/dist/shell/crash-recovery.d.ts +1 -1
  237. package/dist/shell/crash-recovery.d.ts.map +1 -1
  238. package/dist/shell/crash-recovery.js +2 -2
  239. package/dist/shell/crash-recovery.js.map +1 -1
  240. package/dist/shell/install-probe-tick.d.ts +1 -1
  241. package/dist/shell/install-probe-tick.d.ts.map +1 -1
  242. package/dist/shell/install-probe-tick.js +1 -1
  243. package/dist/shell/install-probe-tick.js.map +1 -1
  244. package/dist/shell/unified-shell.js +1 -1
  245. package/dist/shell/unified-shell.js.map +1 -1
  246. package/dist/spawn/model-routing.d.ts +1 -1
  247. package/dist/spawn/model-routing.d.ts.map +1 -1
  248. package/dist/spawn/model-routing.js +1 -1
  249. package/dist/spawn/model-routing.js.map +1 -1
  250. package/dist/spawn/prompt-assembly.d.ts +1 -1
  251. package/dist/spawn/prompt-assembly.d.ts.map +1 -1
  252. package/dist/spawn/prompt-assembly.js +1 -1
  253. package/dist/spawn/prompt-assembly.js.map +1 -1
  254. package/dist/spawn/spawn-decision.js +1 -1
  255. package/dist/spawn/spawn-decision.js.map +1 -1
  256. package/dist/spawn/worktree-session.d.ts +1 -1
  257. package/dist/spawn/worktree-session.d.ts.map +1 -1
  258. package/dist/spawn/worktree-session.js +1 -1
  259. package/dist/spawn/worktree-session.js.map +1 -1
  260. package/dist/templates/claude-md.d.ts +1 -1
  261. package/dist/templates/claude-md.d.ts.map +1 -1
  262. package/dist/templates/claude-md.js +12 -0
  263. package/dist/templates/claude-md.js.map +1 -1
  264. package/dist/verify/verification-default-on.d.ts +1 -1
  265. package/dist/verify/verification-default-on.d.ts.map +1 -1
  266. package/dist/verify/verification-deps.d.ts +1 -1
  267. package/dist/verify/verification-deps.d.ts.map +1 -1
  268. package/dist/verify/verification-engine.d.ts.map +1 -1
  269. package/dist/verify/verification-engine.js +20 -12
  270. package/dist/verify/verification-engine.js.map +1 -1
  271. package/dist/worktree/provision.js +1 -1
  272. package/dist/worktree/provision.js.map +1 -1
  273. package/dist/worktree/scratch-guard.d.ts.map +1 -1
  274. package/dist/worktree/scratch-guard.js +23 -2
  275. package/dist/worktree/scratch-guard.js.map +1 -1
  276. package/package.json +1 -1
@@ -0,0 +1,418 @@
1
+ /**
2
+ * Focus poll-loop spawn processing.
3
+ *
4
+ * decideFocusSpawn (the per-focus spawn-gate decision) + processReadyFocuses
5
+ * (ready-focus spawns) + processPendingSpawnFocuses (pending-directive spawns for
6
+ * focuses getReadyFocuses does not surface). Split out of focus/listener.ts
7
+ * (file-size limit). Shares the poll-loop singletons via focus/listener-state.ts
8
+ * (live-binding reads; setters for reassignments); listener.ts calls these and
9
+ * both import listener-state -- no cycle.
10
+ */
11
+ import { config, roles, pollState, idleLoggedFocuses, nullRoleSkipWarnedFocuses, lastRolesRefreshAt, healthCheckConsecutiveFailures, POLL_INTERVAL_MS, ROLES_REFRESH_INTERVAL_MS, setPollState, setLastRolesRefreshAt, setLastReadyFocusCount, } from './listener-state.js';
12
+ import { getAgentRoles } from '../queries/roles.js';
13
+ import { spawnFocusTeam, hasActiveTeam, getActiveTeamCount, getActiveTeams } from './focus-executor.js';
14
+ import { applyRespawnGuard } from './focus-respawn-guard.js';
15
+ import { recordActivity } from '../shell/heartbeat.js';
16
+ import { configForProduct, findProduct } from '../shell/config.js';
17
+ import { isDaemonShuttingDown } from '../shell/shutdown-state.js';
18
+ import { getPendingSpawnFocusIds, getPendingSpawnDirective, clearPendingSpawnDirective, shouldReapPendingSpawnDirective } from '../directive/directive-executor.js';
19
+ import { decideNullRoleSpawn, resolveReviewerFallbackRole, shouldWarnNullRoleSkip, DEFAULT_REVIEWER_ROLE_NAME } from '../spawn/review-role-fallback.js';
20
+ import { getActiveFocuses, getReadyFocuses, clearAnswerRearm } from '../queries/focus-discovery.js';
21
+ import { isAuditPhaseFocusRow } from '../audit/audit-shape.js';
22
+ import { isIntakeDraining } from '../shell/intake-control.js';
23
+ import { isInSpawnCooldown, isRespawnCycleTerminated } from '../spawn/spawn-cooldown.js';
24
+ import { shouldSkipPendingSpawn, recordPendingSpawnAttempt } from '../spawn/index.js';
25
+ import { fileRespawnCycleExhaustedEscalation } from '../spawn/respawn-cycle-escalation.js';
26
+ import { createLogger } from '../shell/log.js';
27
+ const log = createLogger({ module: 'listener' });
28
+ export function decideFocusSpawn(gate) {
29
+ if (!gate.hasRole)
30
+ return 'skip_unknown_role';
31
+ if (gate.hasActiveTeam)
32
+ return 'skip_active_team';
33
+ if (gate.inSpawnCooldown)
34
+ return 'skip_cooldown';
35
+ if (gate.activeTeamCount >= gate.maxTotalSessions)
36
+ return 'stop_capacity';
37
+ return 'spawn';
38
+ }
39
+ /**
40
+ * Process any focuses ready for agent work.
41
+ *
42
+ * For each ready focus without an active team, spawns a single
43
+ * Agent Team that manages all deliveries within that focus.
44
+ * Also detects focuses that have been deactivated and shuts down
45
+ * their teams.
46
+ */
47
+ export async function processReadyFocuses() {
48
+ if (!config)
49
+ return;
50
+ // Prevent concurrent processing, and skip cycles during health check backoff
51
+ if (pollState === 'processing' || pollState === 'backing_off') {
52
+ return;
53
+ }
54
+ setPollState('processing');
55
+ try {
56
+ // Periodically refresh agent roles to pick up changes
57
+ if (Date.now() - lastRolesRefreshAt > ROLES_REFRESH_INTERVAL_MS) {
58
+ try {
59
+ const roleList = await getAgentRoles(config.organizationId);
60
+ roles.clear();
61
+ for (const role of roleList) {
62
+ roles.set(role.id, role);
63
+ }
64
+ setLastRolesRefreshAt(Date.now());
65
+ log.info(`Refreshed agent roles (${roles.size} roles loaded)`);
66
+ }
67
+ catch (err) {
68
+ log.warn(`Failed to refresh agent roles: ${err.message}`);
69
+ }
70
+ }
71
+ // Record activity: proves the poll ran even if no work was found
72
+ recordActivity();
73
+ // Snapshot active teams before processing (for idle detection)
74
+ const previouslyActiveIds = new Set(getActiveTeams().keys());
75
+ // Deactivation detection is now handled by checkForDeactivatedFocuses()
76
+ // in the poll loop (called before processReadyFocuses).
77
+ // Get ready focuses across all configured products
78
+ const readyFocuses = [];
79
+ for (const product of config.products) {
80
+ try {
81
+ const focuses = await getReadyFocuses(config.organizationId, product.id);
82
+ readyFocuses.push(...focuses);
83
+ }
84
+ catch (err) {
85
+ log.warn(`Failed to fetch ready focuses for product ${product.id}: ${err.message}`);
86
+ }
87
+ }
88
+ // Publish the queue depth for the auto-updater's batch-safe idle predicate.
89
+ // Set before any early-return below so a drained queue is observable.
90
+ setLastReadyFocusCount(readyFocuses.length);
91
+ // Track which focuses are ready this cycle (for idle logging)
92
+ const readyFocusIds = new Set(readyFocuses.map(s => s.focus_id));
93
+ // Idle queue logging: detect focuses that were previously active
94
+ // but now have no ready deliveries. Only log on state change.
95
+ for (const focusId of previouslyActiveIds) {
96
+ if (!readyFocusIds.has(focusId) && !hasActiveTeam(focusId) && !idleLoggedFocuses.has(focusId)) {
97
+ const nextPollSec = POLL_INTERVAL_MS / 1000;
98
+ log.info(`Focus queue idle: "${focusId}" -- no actionable deliveries, next poll in ${nextPollSec}s`);
99
+ idleLoggedFocuses.add(focusId);
100
+ }
101
+ }
102
+ if (readyFocuses.length === 0) {
103
+ return;
104
+ }
105
+ // Intake drain (auto-update delivery C): a pending update has been stale
106
+ // past threshold while the daemon stays busy, so we stop accepting new focus
107
+ // teams to let the current batch become the safe seam. Active teams continue
108
+ // to completion; we simply do not spawn more. The held queue does not block
109
+ // the batch boundary because isBatchSafeIdle honors isIntakeDraining().
110
+ if (isIntakeDraining()) {
111
+ log.info(`[listener] Intake drained for pending update -- holding ${readyFocuses.length} ready focus(es); no new teams will spawn`);
112
+ return;
113
+ }
114
+ const productLabel = config.products.length > 1
115
+ ? ` across ${config.products.length} product(s)`
116
+ : '';
117
+ log.info(`Found ${readyFocuses.length} ready focuses${productLabel}`);
118
+ for (const focus of readyFocuses) {
119
+ const role = roles.get(focus.assigned_agent_role_id);
120
+ // Resolve the per-focus spawn-gate decision (pure; see decideFocusSpawn).
121
+ // The capacity input is the LIVE team count sampled this iteration, so a
122
+ // spawn earlier in this loop is reflected for the next focus.
123
+ const action = decideFocusSpawn({
124
+ hasRole: role !== undefined,
125
+ hasActiveTeam: hasActiveTeam(focus.focus_id),
126
+ inSpawnCooldown: isInSpawnCooldown(focus.focus_id),
127
+ activeTeamCount: getActiveTeamCount(),
128
+ maxTotalSessions: config.maxTotalSessions,
129
+ });
130
+ if (action === 'skip_unknown_role') {
131
+ log.error(`Unknown agent role: ${focus.assigned_agent_role_id}`);
132
+ continue;
133
+ }
134
+ if (action === 'skip_active_team') {
135
+ // Focus is active -- clear idle state so we re-log if it goes idle again
136
+ idleLoggedFocuses.delete(focus.focus_id);
137
+ // A re-arm flag may have landed while a team is up. Do NOT clear it here:
138
+ // the answer is consumed only at (re)spawn time via the escalation.answers
139
+ // resolver, and a still-live team does not re-run assembly mid-session
140
+ // (the wake-message handoff does not carry answers). Leaving the flag set
141
+ // lets the force-spawn deliver the answer once this team exits; clearing
142
+ // it now would strand the answer. The clear happens on the force-spawn
143
+ // below (one answer -> one re-spawn).
144
+ continue;
145
+ }
146
+ // Skip focuses that were recently torn down (spawn cooldown).
147
+ if (action === 'skip_cooldown') {
148
+ continue;
149
+ }
150
+ // Total capacity reached -- stop the whole loop, not just this focus.
151
+ if (action === 'stop_capacity') {
152
+ log.info(`Total session limit reached, stopping`);
153
+ break;
154
+ }
155
+ // action === 'spawn'. role is guaranteed defined (hasRole was true);
156
+ // this guard narrows the type and is otherwise unreachable.
157
+ if (!role)
158
+ continue;
159
+ const isRearm = focus.answer_rearm_requested_at != null;
160
+ // Clear idle state before spawn attempt -- if spawn succeeds,
161
+ // the focus is active. If it skips (no actionable deliveries),
162
+ // spawnFocusTeam logs the skip and we mark it idle here.
163
+ const wasIdle = idleLoggedFocuses.has(focus.focus_id);
164
+ idleLoggedFocuses.delete(focus.focus_id);
165
+ // Use product-scoped config so the team uses the correct repoPath
166
+ const productEntry = focus.product_id ? findProduct(config, focus.product_id) : undefined;
167
+ const spawnConfig = productEntry ? configForProduct(config, productEntry) : config;
168
+ // Spawn a focus team (one team per focus). forceSpawn lets a re-armed
169
+ // focus spawn past findActionableDeliveries' 'skip' to consume the
170
+ // pending escalation answer.
171
+ // Audit-phase focus (explicit read_only=true OR the second structural
172
+ // signal: audit_origin && not-yet-ratified, TEL-3) spawns read-only, so a
173
+ // flag-absent audit focus is not driven into scoping/write/commit ahead of
174
+ // ratification (2f253da6). See isAuditPhaseFocusRow.
175
+ 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 });
176
+ // Clear the re-arm flag only once a team is actually live to consume the
177
+ // answer: one answer -> one re-spawn. If the force-spawn did NOT produce an
178
+ // active team (worktree/governor failure, etc.), leave the flag set so a
179
+ // later poll retries delivery rather than stranding the answer. (Cooldown
180
+ // and capacity exit above without reaching here, so they also keep the
181
+ // flag.) Done before the idle check below so a consumed re-arm is not
182
+ // re-surfaced next poll.
183
+ if (isRearm && hasActiveTeam(focus.focus_id)) {
184
+ await clearAnswerRearm(focus.focus_id).catch((err) => log.warn(`Failed to clear answer re-arm for "${focus.focus_name}": ${err.message}`));
185
+ }
186
+ // If the spawn was skipped (no active team after spawn), mark as idle
187
+ if (!hasActiveTeam(focus.focus_id) && !wasIdle) {
188
+ const nextPollSec = POLL_INTERVAL_MS / 1000;
189
+ log.info(`Focus queue idle: "${focus.focus_name}" -- no actionable deliveries after pre-spawn check, next poll in ${nextPollSec}s`);
190
+ idleLoggedFocuses.add(focus.focus_id);
191
+ }
192
+ }
193
+ }
194
+ catch (error) {
195
+ log.error('Error processing ready focuses:', error);
196
+ }
197
+ finally {
198
+ setPollState(healthCheckConsecutiveFailures > 0 ? 'backing_off' : 'idle');
199
+ }
200
+ }
201
+ /**
202
+ * Spawn teams for focuses that have a pending spawn directive but were
203
+ * not surfaced by getReadyFocuses.
204
+ *
205
+ * getReadyFocuses (daemon-focus-handlers.ts) only returns focuses with
206
+ * queued/coding deliveries (or zero deliveries). A review-stage spawn
207
+ * directive on a focus whose deliveries are all in verify never makes
208
+ * it to processReadyFocuses -- the pending directive sits forever and
209
+ * the team never starts. This function bridges that gap: scan the
210
+ * pending-spawn map directly and spawn for any focus that's not
211
+ * already running.
212
+ */
213
+ export async function processPendingSpawnFocuses() {
214
+ if (!config)
215
+ return;
216
+ if (isDaemonShuttingDown())
217
+ return;
218
+ const pendingIds = getPendingSpawnFocusIds().filter(id => !hasActiveTeam(id));
219
+ if (pendingIds.length === 0)
220
+ return;
221
+ // Fetch focus details for all configured products. getActiveFocuses
222
+ // returns the metadata spawnFocusTeam needs.
223
+ const focusDetailsById = new Map();
224
+ // Whether ANY product's getActiveFocuses read failed this tick. A fetch failure
225
+ // makes the active set incomplete, so a pending focus can look absent purely
226
+ // because its product's read failed -- absence is only trustworthy when this is
227
+ // false. Gates the zombie-directive reap below.
228
+ let anyFetchFailed = false;
229
+ for (const product of config.products) {
230
+ try {
231
+ const focuses = await getActiveFocuses(config.organizationId, product.id);
232
+ for (const f of focuses) {
233
+ if (pendingIds.includes(f.focus_id)) {
234
+ focusDetailsById.set(f.focus_id, f);
235
+ }
236
+ }
237
+ }
238
+ catch (err) {
239
+ anyFetchFailed = true;
240
+ log.warn(`[listener] processPendingSpawnFocuses: failed to fetch active focuses for product ${product.id}: ${err.message}`);
241
+ }
242
+ }
243
+ for (const focusId of pendingIds) {
244
+ const focus = focusDetailsById.get(focusId);
245
+ if (!focus) {
246
+ // Focus is absent from the active set. If the read was complete this tick
247
+ // (no fetch failure), the focus has genuinely left the active set -- its
248
+ // directive is a zombie: reap it once instead of retrying forever. On a
249
+ // fetch-failure tick the absence is untrustworthy, so keep retrying.
250
+ if (shouldReapPendingSpawnDirective({ anyFetchFailed, focusPresentInActiveSet: false })) {
251
+ clearPendingSpawnDirective(focusId);
252
+ // Focus left the active set -- the blocked episode is over. Clear its
253
+ // once-per-episode warn marker so a future re-entry warns again.
254
+ nullRoleSkipWarnedFocuses.delete(focusId);
255
+ log.info(`[listener] Dropping stale spawn directive for focus ${focusId.slice(0, 8)}: ` +
256
+ `confirmed absent from every product's active set (focus completed/deactivated).`);
257
+ }
258
+ else {
259
+ log.warn(`[listener] Pending spawn directive for focus ${focusId.slice(0, 8)} but focus not found in active set; ` +
260
+ `active-set read incomplete this tick (fetch failure) -- retrying next poll.`);
261
+ }
262
+ continue;
263
+ }
264
+ let role;
265
+ if (!focus.assigned_agent_role_id) {
266
+ // A null assigned_agent_role_id now means ONE OF TWO things, and they are
267
+ // NOT interchangeable:
268
+ // - operator_stopped_at set => an operator pulled the Play toggle to
269
+ // halt the focus. Authoritative: it wins over the review fallback.
270
+ // - operator_stopped_at null => auto_terminate_focus_team() cleared the
271
+ // role at completion (lifecycle). Here the review fallback applies:
272
+ // a review is a SECOND team that must spawn AFTER completion; a coding
273
+ // directive is the intended off-state and must NOT auto-spawn.
274
+ // decideNullRoleSpawn disambiguates via operatorStopped; the listener then
275
+ // acts on the decision (drop the directive on a stop, throttle the
276
+ // off-state warning, or spawn the lifecycle review fallback).
277
+ const operatorStopped = !!focus.operator_stopped_at;
278
+ const pending = getPendingSpawnDirective(focus.focus_id);
279
+ const decision = decideNullRoleSpawn({
280
+ sessionType: pending?.sessionType,
281
+ fallbackRole: resolveReviewerFallbackRole(roles.values()),
282
+ operatorStopped,
283
+ });
284
+ if (decision.action === 'skip') {
285
+ if (operatorStopped) {
286
+ // Operator STOP wins over the review fallback. Drop any directive
287
+ // queued while stopped so it does not silently survive to resume and
288
+ // fire the path the stop was meant to prevent. Dropping also removes
289
+ // the focus from the pending set, so this suppression is logged once
290
+ // per focus (per stop), never once per poll.
291
+ if (pending)
292
+ clearPendingSpawnDirective(focus.focus_id);
293
+ nullRoleSkipWarnedFocuses.delete(focus.focus_id);
294
+ log.info(`[listener] Focus ${focusId.slice(0, 8)} ("${focus.focus_name}") is held by an operator stop; ` +
295
+ `suppressing the queued ${pending?.sessionType ?? 'spawn'} directive (dropped, not carried to resume).`);
296
+ continue;
297
+ }
298
+ // Coding off-state (Play/queue toggle cleared, no operator stop): the
299
+ // directive legitimately stays queued. Bound the warning to once per
300
+ // blocked episode so a stopped focus does not spin-log every 30s poll.
301
+ if (shouldWarnNullRoleSkip(nullRoleSkipWarnedFocuses, focus.focus_id)) {
302
+ log.warn(`[listener] Pending spawn directive for focus ${focusId.slice(0, 8)} ("${focus.focus_name}") but no assigned_agent_role_id; ` +
303
+ `skipping spawn (coding off-state). Suppressing further per-poll warnings until it resumes.`);
304
+ }
305
+ continue;
306
+ }
307
+ // Past the skip paths: this is a genuine lifecycle clear (operatorStopped
308
+ // is false here). The episode is over either way, so clear the marker.
309
+ nullRoleSkipWarnedFocuses.delete(focus.focus_id);
310
+ if (decision.action === 'clear') {
311
+ // Review strand with no resolvable fallback role: drop the directive so
312
+ // the stranded-review re-fire suppression (directive-executor) cannot
313
+ // spin-log forever. review_window_stalled remains the escalation path,
314
+ // and now fires promptly rather than after 6h of churn.
315
+ clearPendingSpawnDirective(focus.focus_id);
316
+ log.warn(`[listener] Stranded review directive for focus ${focusId.slice(0, 8)} ("${focus.focus_name}") has a null assigned_agent_role_id ` +
317
+ `and no seeded '${DEFAULT_REVIEWER_ROLE_NAME}' fallback role resolved -- dropping the directive so it stops re-firing; ` +
318
+ `review_window_stalled will escalate.`);
319
+ continue;
320
+ }
321
+ role = decision.role;
322
+ // Reachable only when operatorStopped is false (an operator stop takes the
323
+ // skip path above), so "(coding team completed)" is a true provenance
324
+ // claim here rather than an assumption stamped on every null-role spawn.
325
+ log.info(`[listener] Review directive for focus ${focusId.slice(0, 8)} ("${focus.focus_name}") has a null assigned_agent_role_id ` +
326
+ `(coding team completed); spawning review with fallback role "${role.name}".`);
327
+ }
328
+ else {
329
+ // Focus has a role again -- any prior null-role blocked episode is over.
330
+ nullRoleSkipWarnedFocuses.delete(focus.focus_id);
331
+ role = roles.get(focus.assigned_agent_role_id);
332
+ if (!role) {
333
+ log.warn(`[listener] Pending spawn directive for focus ${focusId.slice(0, 8)} ("${focus.focus_name}") references unknown role ${focus.assigned_agent_role_id}; ` +
334
+ `skipping spawn.`);
335
+ continue;
336
+ }
337
+ }
338
+ // Park focuses with an unhealed team_failure escalation BEFORE attempting
339
+ // the spawn. spawnFocusTeam applies the same guard, but it returns after
340
+ // the guard fires without consuming the pending directive -- so the
341
+ // directive stays queued and this loop re-attempts (and the guard re-logs)
342
+ // every poll. Checking here skips the misleading "Spawning team" log and
343
+ // the wasted spawn call; the directive intentionally stays queued so the
344
+ // (re)review resumes once a human resolves the escalation. The guard
345
+ // throttles its own log to once per blocked episode.
346
+ if (await applyRespawnGuard(focus.focus_id, focus.focus_name)) {
347
+ continue;
348
+ }
349
+ // Spawn cooldown: this path spawns teams for focuses that getReadyFocuses
350
+ // does NOT surface -- focuses with no actionable/queued deliveries (e.g. a
351
+ // reviewing-stage spawn over a focus whose deliveries are all in verify).
352
+ // Such a team idle-exits if its directive can't make progress; without a
353
+ // gate the directive re-fires every poll and the daemon respawns an
354
+ // immediately-idle team every cycle (the reinforcing auto-review/idle-spawn
355
+ // loop). See pending-spawn-guard.ts.
356
+ // Terminated respawn cycle: the spawn/teardown loop for this focus has
357
+ // exhausted its total-duration/count bounds (spawn-cooldown.ts). Respawns are
358
+ // halted permanently (isInSpawnCooldown returns true indefinitely), so the
359
+ // spawn is skipped either way -- but a silent halt is a strand. File the
360
+ // one-per-focus respawn_cycle_exhausted escalation (idempotent, so filing on
361
+ // each poll while terminated yields a single open row) so the terminated
362
+ // cycle is a legible state a human can see and clear.
363
+ if (isRespawnCycleTerminated(focus.focus_id)) {
364
+ await fileRespawnCycleExhaustedEscalation({
365
+ organizationId: config.organizationId,
366
+ focusId: focus.focus_id,
367
+ focusName: focus.focus_name,
368
+ });
369
+ continue;
370
+ }
371
+ if (shouldSkipPendingSpawn(focus.focus_id)) {
372
+ continue;
373
+ }
374
+ if (getActiveTeamCount() >= config.maxTotalSessions) {
375
+ log.info(`Total session limit reached, deferring pending spawn for "${focus.focus_name}"`);
376
+ break;
377
+ }
378
+ const productEntry = focus.product_id ? findProduct(config, focus.product_id) : undefined;
379
+ const spawnConfig = productEntry ? configForProduct(config, productEntry) : config;
380
+ log.info(`[listener] Spawning team for focus "${focus.focus_name}" via pending spawn directive ` +
381
+ `(no actionable deliveries, but directive is queued)`);
382
+ // Audit-phase => read-only spawn (see the processReadyFocuses spawn above).
383
+ // The pending-spawn focus comes from getActiveFocuses, which carries
384
+ // audit_origin + ratified_at, so the second signal (TEL-3) resolves here too.
385
+ await spawnFocusTeam({
386
+ config: spawnConfig,
387
+ focusId: focus.focus_id,
388
+ focusName: focus.focus_name,
389
+ role,
390
+ pipelineConfig: focus.pipeline_config,
391
+ readOnly: isAuditPhaseFocusRow(focus),
392
+ lastClaudeSessionId: null,
393
+ claudeSessionIds: focus.claude_session_ids,
394
+ engineOverride: focus.engine_override,
395
+ localModelOverride: focus.local_model_override,
396
+ orgDefaultEngine: focus.org_default_engine,
397
+ pinnedBranch: focus.branch_name,
398
+ });
399
+ // Record a teardown so repeated spawns through this no-actionable-work path
400
+ // back off exponentially instead of recurring every poll. A team that
401
+ // genuinely makes progress moves the focus into a state getReadyFocuses
402
+ // surfaces (and clearSpawnCooldown can reset it); a team that idle-exits is
403
+ // rate-limited by the growing cooldown. This bounds spawn count on a
404
+ // wedged-in-verify focus. See pending-spawn-guard.ts.
405
+ //
406
+ // ONLY when a team actually spawned (TEL, delivery 5): if spawnFocusTeam
407
+ // SKIPPED (e.g. a terminal focus whose deliveries are all terminal now
408
+ // returns 'skip', or the pre-spawn guards declined), no team exists to tear
409
+ // down, so recording a teardown here would accrue the respawn streak against
410
+ // a focus that correctly spawned nothing -- penalising quiet correctness. The
411
+ // idle-exit backoff is preserved: a genuine spawn leaves hasActiveTeam true at
412
+ // this point (the team runs asynchronously and exits later).
413
+ if (hasActiveTeam(focus.focus_id)) {
414
+ recordPendingSpawnAttempt(focus.focus_id);
415
+ }
416
+ }
417
+ }
418
+ //# sourceMappingURL=listener-spawn.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"listener-spawn.js","sourceRoot":"","sources":["../../src/focus/listener-spawn.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Shared mutable state for the focus poll loop.
3
+ *
4
+ * The listener's singletons live here so the lifecycle (focus/listener.ts) and
5
+ * the spawn-processing pass (focus/listener-spawn.ts) can both read/write them
6
+ * without a cycle -- this is the LEAF of the listener family (it imports neither).
7
+ *
8
+ * Reads use the exported live bindings directly (an `export let` is a read-only
9
+ * live view in importers); REASSIGNMENTS go through the setters below (an
10
+ * importer cannot reassign an imported binding). The `const` Set/Map singletons
11
+ * are mutated in place, so they need no setter.
12
+ */
13
+ import type { DaemonConfig, AgentRole } from '../types/index.js';
14
+ /**
15
+ * Explicit poll state machine for the focus listener.
16
+ * - idle: waiting for next poll cycle
17
+ * - processing: currently executing a poll iteration
18
+ * - backing_off: health check has consecutive failures, skipping cycles
19
+ */
20
+ export type PollState = 'idle' | 'processing' | 'backing_off';
21
+ /** Polling interval in milliseconds (default: 30 seconds). */
22
+ export declare const POLL_INTERVAL_MS = 30000;
23
+ /** Roles refresh: re-fetch from DB periodically to pick up changes (5 minutes). */
24
+ export declare const ROLES_REFRESH_INTERVAL_MS: number;
25
+ /** Health check backoff: max cycles skipped after consecutive failures (10 * 30s = 5 min). */
26
+ export declare const HEALTH_CHECK_MAX_SKIP_CYCLES = 10;
27
+ export declare let config: DaemonConfig | null;
28
+ export declare const roles: Map<string, AgentRole>;
29
+ export declare let pollInterval: ReturnType<typeof setInterval> | null;
30
+ export declare let pollState: PollState;
31
+ export declare let lastRolesRefreshAt: number;
32
+ export declare let healthCheckConsecutiveFailures: number;
33
+ /**
34
+ * Depth of the ready-focus queue as of the most recent completed poll. Read by
35
+ * the auto-updater's batch-safe idle predicate: a drained queue (0) plus no
36
+ * active team is a safe restart seam. Stale-but-recent is fine.
37
+ */
38
+ export declare let lastReadyFocusCount: number;
39
+ /**
40
+ * Focuses logged as idle (no actionable deliveries). Only log on state change:
41
+ * first skip after an active session or after startup.
42
+ */
43
+ export declare const idleLoggedFocuses: Set<string>;
44
+ /**
45
+ * Focuses already warned this blocked episode for a null-role coding off-state
46
+ * pending directive. Warn once on entry, suppress until the focus resumes.
47
+ */
48
+ export declare const nullRoleSkipWarnedFocuses: Set<string>;
49
+ export declare function setConfig(value: DaemonConfig | null): void;
50
+ export declare function setPollInterval(value: ReturnType<typeof setInterval> | null): void;
51
+ export declare function setPollState(value: PollState): void;
52
+ export declare function setLastRolesRefreshAt(value: number): void;
53
+ export declare function setHealthCheckConsecutiveFailures(value: number): void;
54
+ export declare function setLastReadyFocusCount(value: number): void;
55
+ /** Number of ready focuses as of the last poll. Zero means the queue is drained. */
56
+ export declare function getReadyFocusQueueDepth(): number;
57
+ //# sourceMappingURL=listener-state.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"listener-state.d.ts","sourceRoot":"","sources":["../../src/focus/listener-state.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAEjE;;;;;GAKG;AACH,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,YAAY,GAAG,aAAa,CAAC;AAE9D,8DAA8D;AAC9D,eAAO,MAAM,gBAAgB,QAAQ,CAAC;AAEtC,mFAAmF;AACnF,eAAO,MAAM,yBAAyB,QAAgB,CAAC;AAEvD,8FAA8F;AAC9F,eAAO,MAAM,4BAA4B,KAAK,CAAC;AAE/C,eAAO,IAAI,MAAM,EAAE,YAAY,GAAG,IAAW,CAAC;AAC9C,eAAO,MAAM,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,SAAS,CAAa,CAAC;AACvD,eAAO,IAAI,YAAY,EAAE,UAAU,CAAC,OAAO,WAAW,CAAC,GAAG,IAAW,CAAC;AACtE,eAAO,IAAI,SAAS,EAAE,SAAkB,CAAC;AACzC,eAAO,IAAI,kBAAkB,QAAI,CAAC;AAClC,eAAO,IAAI,8BAA8B,QAAI,CAAC;AAE9C;;;;GAIG;AACH,eAAO,IAAI,mBAAmB,QAAI,CAAC;AAEnC;;;GAGG;AACH,eAAO,MAAM,iBAAiB,aAAoB,CAAC;AAEnD;;;GAGG;AACH,eAAO,MAAM,yBAAyB,aAAoB,CAAC;AAE3D,wBAAgB,SAAS,CAAC,KAAK,EAAE,YAAY,GAAG,IAAI,GAAG,IAAI,CAE1D;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,UAAU,CAAC,OAAO,WAAW,CAAC,GAAG,IAAI,GAAG,IAAI,CAElF;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,SAAS,GAAG,IAAI,CAEnD;AAED,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAEzD;AAED,wBAAgB,iCAAiC,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAErE;AAED,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAE1D;AAED,oFAAoF;AACpF,wBAAgB,uBAAuB,IAAI,MAAM,CAEhD"}
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Shared mutable state for the focus poll loop.
3
+ *
4
+ * The listener's singletons live here so the lifecycle (focus/listener.ts) and
5
+ * the spawn-processing pass (focus/listener-spawn.ts) can both read/write them
6
+ * without a cycle -- this is the LEAF of the listener family (it imports neither).
7
+ *
8
+ * Reads use the exported live bindings directly (an `export let` is a read-only
9
+ * live view in importers); REASSIGNMENTS go through the setters below (an
10
+ * importer cannot reassign an imported binding). The `const` Set/Map singletons
11
+ * are mutated in place, so they need no setter.
12
+ */
13
+ /** Polling interval in milliseconds (default: 30 seconds). */
14
+ export const POLL_INTERVAL_MS = 30000;
15
+ /** Roles refresh: re-fetch from DB periodically to pick up changes (5 minutes). */
16
+ export const ROLES_REFRESH_INTERVAL_MS = 5 * 60 * 1000;
17
+ /** Health check backoff: max cycles skipped after consecutive failures (10 * 30s = 5 min). */
18
+ export const HEALTH_CHECK_MAX_SKIP_CYCLES = 10;
19
+ export let config = null;
20
+ export const roles = new Map();
21
+ export let pollInterval = null;
22
+ export let pollState = 'idle';
23
+ export let lastRolesRefreshAt = 0;
24
+ export let healthCheckConsecutiveFailures = 0;
25
+ /**
26
+ * Depth of the ready-focus queue as of the most recent completed poll. Read by
27
+ * the auto-updater's batch-safe idle predicate: a drained queue (0) plus no
28
+ * active team is a safe restart seam. Stale-but-recent is fine.
29
+ */
30
+ export let lastReadyFocusCount = 0;
31
+ /**
32
+ * Focuses logged as idle (no actionable deliveries). Only log on state change:
33
+ * first skip after an active session or after startup.
34
+ */
35
+ export const idleLoggedFocuses = new Set();
36
+ /**
37
+ * Focuses already warned this blocked episode for a null-role coding off-state
38
+ * pending directive. Warn once on entry, suppress until the focus resumes.
39
+ */
40
+ export const nullRoleSkipWarnedFocuses = new Set();
41
+ export function setConfig(value) {
42
+ config = value;
43
+ }
44
+ export function setPollInterval(value) {
45
+ pollInterval = value;
46
+ }
47
+ export function setPollState(value) {
48
+ pollState = value;
49
+ }
50
+ export function setLastRolesRefreshAt(value) {
51
+ lastRolesRefreshAt = value;
52
+ }
53
+ export function setHealthCheckConsecutiveFailures(value) {
54
+ healthCheckConsecutiveFailures = value;
55
+ }
56
+ export function setLastReadyFocusCount(value) {
57
+ lastReadyFocusCount = value;
58
+ }
59
+ /** Number of ready focuses as of the last poll. Zero means the queue is drained. */
60
+ export function getReadyFocusQueueDepth() {
61
+ return lastReadyFocusCount;
62
+ }
63
+ //# sourceMappingURL=listener-state.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"listener-state.js","sourceRoot":"","sources":["../../src/focus/listener-state.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAYH,8DAA8D;AAC9D,MAAM,CAAC,MAAM,gBAAgB,GAAG,KAAK,CAAC;AAEtC,mFAAmF;AACnF,MAAM,CAAC,MAAM,yBAAyB,GAAG,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC;AAEvD,8FAA8F;AAC9F,MAAM,CAAC,MAAM,4BAA4B,GAAG,EAAE,CAAC;AAE/C,MAAM,CAAC,IAAI,MAAM,GAAwB,IAAI,CAAC;AAC9C,MAAM,CAAC,MAAM,KAAK,GAA2B,IAAI,GAAG,EAAE,CAAC;AACvD,MAAM,CAAC,IAAI,YAAY,GAA0C,IAAI,CAAC;AACtE,MAAM,CAAC,IAAI,SAAS,GAAc,MAAM,CAAC;AACzC,MAAM,CAAC,IAAI,kBAAkB,GAAG,CAAC,CAAC;AAClC,MAAM,CAAC,IAAI,8BAA8B,GAAG,CAAC,CAAC;AAE9C;;;;GAIG;AACH,MAAM,CAAC,IAAI,mBAAmB,GAAG,CAAC,CAAC;AAEnC;;;GAGG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,IAAI,GAAG,EAAU,CAAC;AAEnD;;;GAGG;AACH,MAAM,CAAC,MAAM,yBAAyB,GAAG,IAAI,GAAG,EAAU,CAAC;AAE3D,MAAM,UAAU,SAAS,CAAC,KAA0B;IAClD,MAAM,GAAG,KAAK,CAAC;AACjB,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,KAA4C;IAC1E,YAAY,GAAG,KAAK,CAAC;AACvB,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,KAAgB;IAC3C,SAAS,GAAG,KAAK,CAAC;AACpB,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,KAAa;IACjD,kBAAkB,GAAG,KAAK,CAAC;AAC7B,CAAC;AAED,MAAM,UAAU,iCAAiC,CAAC,KAAa;IAC7D,8BAA8B,GAAG,KAAK,CAAC;AACzC,CAAC;AAED,MAAM,UAAU,sBAAsB,CAAC,KAAa;IAClD,mBAAmB,GAAG,KAAK,CAAC;AAC9B,CAAC;AAED,oFAAoF;AACpF,MAAM,UAAU,uBAAuB;IACrC,OAAO,mBAAmB,CAAC;AAC7B,CAAC"}
@@ -9,13 +9,6 @@
9
9
  import type { DaemonConfig } from '../types/index.js';
10
10
  import { triggerCheck } from '../poll-trigger.js';
11
11
  export { triggerCheck };
12
- /**
13
- * Number of focuses with actionable (queued) work as of the last poll. Zero
14
- * means the focus queue is drained. Used by the batch-safe idle predicate so a
15
- * transient between-delivery gap (which still has queued focuses) does not read
16
- * as idle.
17
- */
18
- export declare function getReadyFocusQueueDepth(): number;
19
12
  /**
20
13
  * Initialize the listener with configuration.
21
14
  */
@@ -29,57 +22,6 @@ export declare function startDeliveryListener(): void;
29
22
  * Kept for API compatibility with the original implementation.
30
23
  */
31
24
  export declare function startSessionListener(): void;
32
- /**
33
- * The per-focus spawn-gate decision -- the heart of the event/advance loop
34
- * dispatch. Given a single ready focus's resolved gate state, decide what the
35
- * processReadyFocuses loop should do with it. Extracted as a pure function so
36
- * the dispatch ordering (which a regression here turns into a missed spawn, a
37
- * double-spawn, or a capacity-overrun) can be tested directly.
38
- *
39
- * Order matters and mirrors the original inline checks exactly:
40
- * 1. unknown role -> skip this focus (logged error)
41
- * 2. already has a team -> skip (do not double-spawn)
42
- * 3. in spawn cooldown -> skip (recently torn down)
43
- * 4. at total capacity -> STOP the whole loop (break)
44
- * 5. otherwise -> spawn
45
- *
46
- * The capacity check reads the LIVE active-team count, so the caller passes a
47
- * freshly-sampled count per focus (a successful spawn raises it for the next
48
- * focus; a skipped spawn does not).
49
- */
50
- export type FocusSpawnAction = 'spawn' | 'skip_unknown_role' | 'skip_active_team' | 'skip_cooldown' | 'stop_capacity';
51
- export interface FocusSpawnGate {
52
- /** Whether the focus's assigned_agent_role_id resolves to a known role. */
53
- hasRole: boolean;
54
- hasActiveTeam: boolean;
55
- inSpawnCooldown: boolean;
56
- /** Live count of active teams as of this focus's turn. */
57
- activeTeamCount: number;
58
- maxTotalSessions: number;
59
- }
60
- export declare function decideFocusSpawn(gate: FocusSpawnGate): FocusSpawnAction;
61
- /**
62
- * Process any focuses ready for agent work.
63
- *
64
- * For each ready focus without an active team, spawns a single
65
- * Agent Team that manages all deliveries within that focus.
66
- * Also detects focuses that have been deactivated and shuts down
67
- * their teams.
68
- */
69
- export declare function processReadyFocuses(): Promise<void>;
70
- /**
71
- * Spawn teams for focuses that have a pending spawn directive but were
72
- * not surfaced by getReadyFocuses.
73
- *
74
- * getReadyFocuses (daemon-focus-handlers.ts) only returns focuses with
75
- * queued/coding deliveries (or zero deliveries). A review-stage spawn
76
- * directive on a focus whose deliveries are all in verify never makes
77
- * it to processReadyFocuses -- the pending directive sits forever and
78
- * the team never starts. This function bridges that gap: scan the
79
- * pending-spawn map directly and spawn for any focus that's not
80
- * already running.
81
- */
82
- export declare function processPendingSpawnFocuses(): Promise<void>;
83
25
  /**
84
26
  * Check that every session recorded as 'starting' or 'running' in the DB
85
27
  * has a corresponding active team in memory.