@telora/daemon 0.20.28 → 0.20.53

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 (231) hide show
  1. package/build-info.json +2 -2
  2. package/dist/agent-state.d.ts +4 -6
  3. package/dist/agent-state.d.ts.map +1 -1
  4. package/dist/agent-state.js +4 -10
  5. package/dist/agent-state.js.map +1 -1
  6. package/dist/auto-update.d.ts +87 -0
  7. package/dist/auto-update.d.ts.map +1 -1
  8. package/dist/auto-update.js +142 -8
  9. package/dist/auto-update.js.map +1 -1
  10. package/dist/branch-status.d.ts +25 -3
  11. package/dist/branch-status.d.ts.map +1 -1
  12. package/dist/branch-status.js +130 -59
  13. package/dist/branch-status.js.map +1 -1
  14. package/dist/completion/completion-decision.d.ts +29 -3
  15. package/dist/completion/completion-decision.d.ts.map +1 -1
  16. package/dist/completion/completion-decision.js +15 -3
  17. package/dist/completion/completion-decision.js.map +1 -1
  18. package/dist/completion/event-entry-phases.d.ts +57 -0
  19. package/dist/completion/event-entry-phases.d.ts.map +1 -0
  20. package/dist/completion/event-entry-phases.js +205 -0
  21. package/dist/completion/event-entry-phases.js.map +1 -0
  22. package/dist/completion/event-phases.d.ts +14 -42
  23. package/dist/completion/event-phases.d.ts.map +1 -1
  24. package/dist/completion/event-phases.js +63 -190
  25. package/dist/completion/event-phases.js.map +1 -1
  26. package/dist/completion/event.d.ts +11 -1
  27. package/dist/completion/event.d.ts.map +1 -1
  28. package/dist/completion/event.js +5 -2
  29. package/dist/completion/event.js.map +1 -1
  30. package/dist/completion/index.d.ts +0 -3
  31. package/dist/completion/index.d.ts.map +1 -1
  32. package/dist/completion/index.js +0 -3
  33. package/dist/completion/index.js.map +1 -1
  34. package/dist/completion/review-exit-phase.d.ts +2 -1
  35. package/dist/completion/review-exit-phase.d.ts.map +1 -1
  36. package/dist/completion/review-exit-phase.js +41 -1
  37. package/dist/completion/review-exit-phase.js.map +1 -1
  38. package/dist/completion/status-advance-phase.d.ts +2 -0
  39. package/dist/completion/status-advance-phase.d.ts.map +1 -1
  40. package/dist/completion/status-advance-phase.js +14 -28
  41. package/dist/completion/status-advance-phase.js.map +1 -1
  42. package/dist/completion/team-completion.js +1 -1
  43. package/dist/completion/team-completion.js.map +1 -1
  44. package/dist/completion/terminated-work-reconcile.d.ts.map +1 -1
  45. package/dist/completion/terminated-work-reconcile.js +10 -0
  46. package/dist/completion/terminated-work-reconcile.js.map +1 -1
  47. package/dist/constants.d.ts +0 -8
  48. package/dist/constants.d.ts.map +1 -1
  49. package/dist/constants.js +0 -8
  50. package/dist/constants.js.map +1 -1
  51. package/dist/delivery-close-actions.d.ts +39 -0
  52. package/dist/delivery-close-actions.d.ts.map +1 -0
  53. package/dist/delivery-close-actions.js +53 -0
  54. package/dist/delivery-close-actions.js.map +1 -0
  55. package/dist/delivery-lifecycle.d.ts +11 -0
  56. package/dist/delivery-lifecycle.d.ts.map +1 -1
  57. package/dist/delivery-lifecycle.js +13 -14
  58. package/dist/delivery-lifecycle.js.map +1 -1
  59. package/dist/delivery-reconciler.d.ts +18 -4
  60. package/dist/delivery-reconciler.d.ts.map +1 -1
  61. package/dist/delivery-reconciler.js +18 -4
  62. package/dist/delivery-reconciler.js.map +1 -1
  63. package/dist/directive-executor.d.ts +1 -1
  64. package/dist/directive-executor.js +1 -1
  65. package/dist/discovery-fetch.d.ts +76 -0
  66. package/dist/discovery-fetch.d.ts.map +1 -0
  67. package/dist/discovery-fetch.js +206 -0
  68. package/dist/discovery-fetch.js.map +1 -0
  69. package/dist/discovery-poll.d.ts +1 -51
  70. package/dist/discovery-poll.d.ts.map +1 -1
  71. package/dist/discovery-poll.js +10 -192
  72. package/dist/discovery-poll.js.map +1 -1
  73. package/dist/drain-teardown.d.ts +11 -6
  74. package/dist/drain-teardown.d.ts.map +1 -1
  75. package/dist/drain-teardown.js +3 -2
  76. package/dist/drain-teardown.js.map +1 -1
  77. package/dist/focus-branch-ahead.d.ts +42 -0
  78. package/dist/focus-branch-ahead.d.ts.map +1 -0
  79. package/dist/focus-branch-ahead.js +45 -0
  80. package/dist/focus-branch-ahead.js.map +1 -0
  81. package/dist/focus-engine.d.ts.map +1 -1
  82. package/dist/focus-engine.js +21 -43
  83. package/dist/focus-engine.js.map +1 -1
  84. package/dist/focus-executor.js +2 -2
  85. package/dist/focus-executor.js.map +1 -1
  86. package/dist/focus-lifecycle.d.ts.map +1 -1
  87. package/dist/focus-lifecycle.js +32 -3
  88. package/dist/focus-lifecycle.js.map +1 -1
  89. package/dist/focus-loop-registry.d.ts.map +1 -1
  90. package/dist/focus-loop-registry.js +27 -0
  91. package/dist/focus-loop-registry.js.map +1 -1
  92. package/dist/focus-loop.d.ts +34 -12
  93. package/dist/focus-loop.d.ts.map +1 -1
  94. package/dist/focus-loop.js +78 -15
  95. package/dist/focus-loop.js.map +1 -1
  96. package/dist/focus-merge.d.ts.map +1 -1
  97. package/dist/focus-merge.js +6 -1
  98. package/dist/focus-merge.js.map +1 -1
  99. package/dist/focus-phase.d.ts +18 -3
  100. package/dist/focus-phase.d.ts.map +1 -1
  101. package/dist/focus-phase.js +85 -31
  102. package/dist/focus-phase.js.map +1 -1
  103. package/dist/focus-prompt-builder.d.ts.map +1 -1
  104. package/dist/focus-prompt-builder.js +13 -1
  105. package/dist/focus-prompt-builder.js.map +1 -1
  106. package/dist/focus-provisioning.d.ts.map +1 -1
  107. package/dist/focus-provisioning.js +4 -0
  108. package/dist/focus-provisioning.js.map +1 -1
  109. package/dist/focus-stage-lifecycle/planning-release.d.ts +35 -17
  110. package/dist/focus-stage-lifecycle/planning-release.d.ts.map +1 -1
  111. package/dist/focus-stage-lifecycle/planning-release.js +53 -22
  112. package/dist/focus-stage-lifecycle/planning-release.js.map +1 -1
  113. package/dist/focus-worktree-state.d.ts +17 -6
  114. package/dist/focus-worktree-state.d.ts.map +1 -1
  115. package/dist/focus-worktree-state.js +21 -11
  116. package/dist/focus-worktree-state.js.map +1 -1
  117. package/dist/guard-engine.d.ts +1 -2
  118. package/dist/guard-engine.d.ts.map +1 -1
  119. package/dist/guard-engine.js +1 -2
  120. package/dist/guard-engine.js.map +1 -1
  121. package/dist/health-events.d.ts +10 -0
  122. package/dist/health-events.d.ts.map +1 -0
  123. package/dist/health-events.js +10 -0
  124. package/dist/health-events.js.map +1 -0
  125. package/dist/heartbeat.d.ts.map +1 -1
  126. package/dist/heartbeat.js +98 -21
  127. package/dist/heartbeat.js.map +1 -1
  128. package/dist/host-control/catalog.d.ts.map +1 -1
  129. package/dist/host-control/catalog.js +4 -1
  130. package/dist/host-control/catalog.js.map +1 -1
  131. package/dist/host-control/client.d.ts +3 -4
  132. package/dist/host-control/client.d.ts.map +1 -1
  133. package/dist/host-control/client.js +4 -5
  134. package/dist/host-control/client.js.map +1 -1
  135. package/dist/listener-auto-advance.d.ts +2 -1
  136. package/dist/listener-auto-advance.d.ts.map +1 -1
  137. package/dist/listener-auto-advance.js +36 -40
  138. package/dist/listener-auto-advance.js.map +1 -1
  139. package/dist/listener.d.ts +2 -7
  140. package/dist/listener.d.ts.map +1 -1
  141. package/dist/listener.js +11 -9
  142. package/dist/listener.js.map +1 -1
  143. package/dist/operator-hold.d.ts +66 -0
  144. package/dist/operator-hold.d.ts.map +1 -0
  145. package/dist/operator-hold.js +99 -0
  146. package/dist/operator-hold.js.map +1 -0
  147. package/dist/poll-trigger.d.ts +28 -0
  148. package/dist/poll-trigger.d.ts.map +1 -0
  149. package/dist/poll-trigger.js +31 -0
  150. package/dist/poll-trigger.js.map +1 -0
  151. package/dist/prompt-listing.d.ts +25 -0
  152. package/dist/prompt-listing.d.ts.map +1 -1
  153. package/dist/prompt-listing.js +67 -0
  154. package/dist/prompt-listing.js.map +1 -1
  155. package/dist/prompt-sections/execution-sections.d.ts.map +1 -1
  156. package/dist/prompt-sections/execution-sections.js +24 -0
  157. package/dist/prompt-sections/execution-sections.js.map +1 -1
  158. package/dist/queries/context-lock-queries.d.ts.map +1 -1
  159. package/dist/queries/context-lock-queries.js +4 -1
  160. package/dist/queries/context-lock-queries.js.map +1 -1
  161. package/dist/queries/deliveries.d.ts +17 -0
  162. package/dist/queries/deliveries.d.ts.map +1 -1
  163. package/dist/queries/deliveries.js.map +1 -1
  164. package/dist/queries/issues.d.ts +98 -0
  165. package/dist/queries/issues.d.ts.map +1 -1
  166. package/dist/queries/issues.js +135 -0
  167. package/dist/queries/issues.js.map +1 -1
  168. package/dist/queries/schemas.d.ts +3 -3
  169. package/dist/security-scan-engine.d.ts +9 -1
  170. package/dist/security-scan-engine.d.ts.map +1 -1
  171. package/dist/security-scan-engine.js +25 -2
  172. package/dist/security-scan-engine.js.map +1 -1
  173. package/dist/spawn/worktree-session.d.ts.map +1 -1
  174. package/dist/spawn/worktree-session.js +2 -0
  175. package/dist/spawn/worktree-session.js.map +1 -1
  176. package/dist/spawner/index.d.ts +7 -9
  177. package/dist/spawner/index.d.ts.map +1 -1
  178. package/dist/spawner/index.js +13 -11
  179. package/dist/spawner/index.js.map +1 -1
  180. package/dist/spawner/lifecycle.d.ts +1 -18
  181. package/dist/spawner/lifecycle.d.ts.map +1 -1
  182. package/dist/spawner/lifecycle.js +1 -54
  183. package/dist/spawner/lifecycle.js.map +1 -1
  184. package/dist/spawner/liveness.d.ts +5 -14
  185. package/dist/spawner/liveness.d.ts.map +1 -1
  186. package/dist/spawner/liveness.js +5 -67
  187. package/dist/spawner/liveness.js.map +1 -1
  188. package/dist/spawner/state.d.ts +8 -20
  189. package/dist/spawner/state.d.ts.map +1 -1
  190. package/dist/spawner/state.js +8 -34
  191. package/dist/spawner/state.js.map +1 -1
  192. package/dist/staleness-sensor.d.ts +153 -0
  193. package/dist/staleness-sensor.d.ts.map +1 -0
  194. package/dist/staleness-sensor.js +308 -0
  195. package/dist/staleness-sensor.js.map +1 -0
  196. package/dist/types/focus.d.ts +9 -0
  197. package/dist/types/focus.d.ts.map +1 -1
  198. package/dist/types/session.d.ts +0 -44
  199. package/dist/types/session.d.ts.map +1 -1
  200. package/dist/unified-shell.d.ts.map +1 -1
  201. package/dist/unified-shell.js +19 -0
  202. package/dist/unified-shell.js.map +1 -1
  203. package/dist/verification-engine.d.ts +6 -4
  204. package/dist/verification-engine.d.ts.map +1 -1
  205. package/dist/verification-engine.js +9 -43
  206. package/dist/verification-engine.js.map +1 -1
  207. package/dist/verification-feedback.d.ts +42 -0
  208. package/dist/verification-feedback.d.ts.map +1 -1
  209. package/dist/verification-feedback.js +93 -0
  210. package/dist/verification-feedback.js.map +1 -1
  211. package/dist/version-check.d.ts +6 -0
  212. package/dist/version-check.d.ts.map +1 -1
  213. package/dist/version-check.js +1 -1
  214. package/dist/version-check.js.map +1 -1
  215. package/package.json +2 -3
  216. package/dist/completion/agent-completion.d.ts +0 -48
  217. package/dist/completion/agent-completion.d.ts.map +0 -1
  218. package/dist/completion/agent-completion.js +0 -205
  219. package/dist/completion/agent-completion.js.map +0 -1
  220. package/dist/delivery-guards.d.ts +0 -41
  221. package/dist/delivery-guards.d.ts.map +0 -1
  222. package/dist/delivery-guards.js +0 -200
  223. package/dist/delivery-guards.js.map +0 -1
  224. package/dist/spawner/spawn-team.d.ts +0 -20
  225. package/dist/spawner/spawn-team.d.ts.map +0 -1
  226. package/dist/spawner/spawn-team.js +0 -19
  227. package/dist/spawner/spawn-team.js.map +0 -1
  228. package/dist/spawner/timeout.d.ts +0 -32
  229. package/dist/spawner/timeout.d.ts.map +0 -1
  230. package/dist/spawner/timeout.js +0 -126
  231. package/dist/spawner/timeout.js.map +0 -1
@@ -0,0 +1,205 @@
1
+ /**
2
+ * Entry phases of the completion-event pipeline -- the three that run BEFORE
3
+ * any factual picture is computed, each able to short-circuit the pipeline:
4
+ *
5
+ * 1. applyPlanningRefresh planning-team delivery discovery / failure
6
+ * 2. routeReviewWindowCompletion review routing (review pass vs coding pass)
7
+ * 3. runWakeCycleSelfHeal pre-guard advanceDeliveryStatuses self-heal
8
+ *
9
+ * Split out of event-phases.ts (which kept phases 4-6: the factual picture,
10
+ * the advancement watchdog, and action side effects) purely to hold both
11
+ * files under the 800-line daemon module limit. Behavior is unchanged: same
12
+ * reads, same order, same log lines, same early returns, and delivery
13
+ * execution_status transition semantics remain owned by the single-writer
14
+ * reconciler.
15
+ */
16
+ import { DELIVERY_STATUS } from '@telora/daemon-core';
17
+ import { isStatusCascadable, isStatusAgentActionable } from '../stage-classifier.js';
18
+ import { OPEN_ISSUE_STATUSES } from '../constants.js';
19
+ import { isInReviewWindow } from './advanceability.js';
20
+ import { escalatePlanningFailure } from './event-escalations.js';
21
+ import { createLogger } from '../log.js';
22
+ const log = createLogger({ module: 'focus-executor' });
23
+ // ── Phase 1: planning refresh ───────────────────────────────────────────
24
+ /**
25
+ * Wake-cycle refresh (Option A): planning teams spawn with knownDeliveryIds
26
+ * empty. The MCP-driven planning turn writes deliveries to the DB, so we
27
+ * re-snapshot them here before any downstream merge/completion logic looks
28
+ * at the set. WHY Option A: keeps the refresh on the existing wake path
29
+ * instead of polling, so it runs exactly when new deliveries become visible.
30
+ *
31
+ * Returns true when the team was terminated (planning produced nothing).
32
+ */
33
+ export async function applyPlanningRefresh(ctx, deps, currentDeliveries) {
34
+ const { focusId, focusName, teamState } = ctx;
35
+ if (!teamState.planningPhase)
36
+ return false;
37
+ if (currentDeliveries.length === 0) {
38
+ log.warn(`Planning team for "${focusName}" produced no deliveries ` +
39
+ `-- escalating and terminating`);
40
+ await escalatePlanningFailure({
41
+ organizationId: teamState.organizationId,
42
+ sessionId: teamState.leadSessionId ?? '',
43
+ focusId,
44
+ focusName,
45
+ agentRoleId: teamState.roleId,
46
+ }, deps);
47
+ teamState.shutdownReason = 'work_complete';
48
+ deps.terminateTeam(focusId);
49
+ return true;
50
+ }
51
+ for (const d of currentDeliveries) {
52
+ teamState.knownDeliveryIds.add(d.id);
53
+ teamState.deliveryStageIds.set(d.id, d.currentWorkflowStageId);
54
+ }
55
+ teamState.planningPhase = false;
56
+ log.info(`Planning refresh for "${focusName}": ` +
57
+ `${currentDeliveries.length} delivery(ies) discovered`);
58
+ return false;
59
+ }
60
+ /**
61
+ * Review-window handling. Resolves review_requested_at FRESH because it can
62
+ * be set/cleared between spawn and this event.
63
+ *
64
+ * - A completing REVIEW pass routes verify deliveries deterministically
65
+ * (runReviewExitHandler) and terminates -> 'terminated'.
66
+ * - A completing CODING pass during a review window is left alive so the
67
+ * review's own completion closes the focus -> 'leave_running'.
68
+ * - Otherwise -> 'not_in_review' and the pipeline continues.
69
+ */
70
+ export async function routeReviewWindowCompletion(ctx, deps, currentDeliveries) {
71
+ const { focusId, focusName, teamState } = ctx;
72
+ let reviewRequestedAt = null;
73
+ if (deps.getReviewRequestedAt) {
74
+ try {
75
+ reviewRequestedAt = await deps.getReviewRequestedAt(ctx.config, focusId);
76
+ }
77
+ catch (err) {
78
+ log.warn(`getReviewRequestedAt failed for "${focusName}": ${err.message}`);
79
+ // Fail open: a read failure must not block the existing teardown logic.
80
+ }
81
+ }
82
+ if (!isInReviewWindow(reviewRequestedAt, currentDeliveries))
83
+ return 'not_in_review';
84
+ const verifyCount = currentDeliveries.filter(d => d.executionStatus === DELIVERY_STATUS.VERIFY).length;
85
+ if (teamState.sessionType === 'review') {
86
+ // A REVIEW pass just completed -- it emitted its result, which IS the
87
+ // "a review cycle ran" signal. Close the focus DETERMINISTICALLY now,
88
+ // keyed on THIS completion event rather than on process exit. A review
89
+ // resumes its prior session to preserve context (INJ-D) and runs as an
90
+ // interactive stream-json process that emits a result but never exits;
91
+ // the old exit-keyed close therefore waited forever (the review-resume
92
+ // deadlock). runReviewExitHandler routes verify deliveries by the
93
+ // deterministic signal -- a pass that filed zero new issues and has no
94
+ // open work closes verify->done; open work routes verify->verify_failed
95
+ // to re-iterate -- and clears review_requested_at. The review team is then
96
+ // torn down: closure has ALREADY happened, so it no longer matters whether
97
+ // the process exits cleanly or is SIGTERM'd, and the next review cycle
98
+ // resumes the same session id to review only the new delta.
99
+ //
100
+ // succeeded is the ACTUAL result of the review turn (!isError), NOT a
101
+ // hardcoded true. A review that erred (e.g. the CLI failed to start, or a
102
+ // model/engine mismatch) emits a result event with isError=true and files
103
+ // no defects -- the old `succeeded: true` made that indistinguishable from
104
+ // a genuinely clean review, so an errored/no-op review auto-approved
105
+ // verify->done WITHOUT a real review. With the real flag, an errored
106
+ // review leaves the delivery in verify; the next auto-review poll
107
+ // re-triggers, and a persistently failing review is bounded by the
108
+ // review_window_stalled escalation.
109
+ const reviewSucceeded = !ctx.completionInfo.result.isError;
110
+ log.info(`Review pass completed for "${focusName}" (${verifyCount} ` +
111
+ `delivery(ies) in verify, succeeded=${reviewSucceeded}) -- routing deterministically ` +
112
+ `by filed issues, then terminating.`);
113
+ // Coverage evidence: spawn-tip (what the review saw, carried on the team
114
+ // state) is stamped as the watermark; exit-tip (HEAD now) is compared to
115
+ // it to detect mid-review drift. Exit-tip is best-effort via injected git
116
+ // deps; null makes coverage unconfirmable (auto-approve withheld).
117
+ const reviewSpawnTip = teamState.reviewSpawnTip;
118
+ let currentBranchTip = null;
119
+ try {
120
+ const wt = deps.getFocusWorktree?.(focusId);
121
+ if (wt?.worktreePath) {
122
+ currentBranchTip = deps.readWorktreeHeadSha?.(wt.worktreePath) ?? null;
123
+ }
124
+ }
125
+ catch (err) {
126
+ log.warn(`Could not resolve exit-tip for "${focusName}" coverage gate: ${err.message}`);
127
+ }
128
+ try {
129
+ await deps.runReviewExitHandler({
130
+ focusId,
131
+ focusName,
132
+ organizationId: teamState.organizationId,
133
+ productId: teamState.productId,
134
+ sessionId: teamState.leadSessionId ?? '',
135
+ sessionType: 'review',
136
+ succeeded: reviewSucceeded,
137
+ reviewSpawnTip,
138
+ currentBranchTip,
139
+ });
140
+ }
141
+ catch (err) {
142
+ log.warn(`Deterministic review routing failed for "${focusName}": ${err.message}`);
143
+ }
144
+ teamState.shutdownReason = 'work_complete';
145
+ deps.terminateTeam(focusId);
146
+ return 'terminated';
147
+ }
148
+ // A non-review (coding) team completed a cycle while a review is in flight.
149
+ // Do NOT tear it down on the all-terminal / no-actionable-work branches
150
+ // below: `verify` is cascadable, so they would SIGTERM the team mid-review.
151
+ // Leave it running; the review team's own completion closes the focus.
152
+ log.info(`"${focusName}" is in a review window (review_requested_at set, ` +
153
+ `${verifyCount} delivery(ies) in verify) and the completing session is a ` +
154
+ `${teamState.sessionType} pass -- not tearing down; the review pass completion routes ` +
155
+ `the verify deliveries.`);
156
+ return 'leave_running';
157
+ }
158
+ // ── Phase 3: wake-cycle self-heal advance ───────────────────────────────
159
+ /**
160
+ * Self-heal BEFORE the no-progress guard. When actionable deliveries exist
161
+ * but none carry open issues, the team has nothing left to code. The common
162
+ * cause is a review that bounced a delivery to verify_failed while all its
163
+ * issues are already Done. The daemon's transparent
164
+ * verify_failed -> coding -> verify stepping (advanceDeliveryStatuses) clears
165
+ * that. Running it here -- before snapshotting -- means a benign "no work to
166
+ * do" cycle either terminates the team cleanly (all deliveries now terminal)
167
+ * or shows up as real progress (the delivery status moved), instead of being
168
+ * counted as coder no-progress. Either way it can no longer accrue toward a
169
+ * spurious team_failure escalation. (Previously this advance ran only AFTER
170
+ * the guard, so the counter could reach the limit and escalate first.)
171
+ */
172
+ export async function runWakeCycleSelfHeal(ctx, deps, currentDeliveries, currentIssues) {
173
+ const { focusId, focusName, teamState } = ctx;
174
+ const actionableDeliveryIds = new Set(currentDeliveries
175
+ .filter(d => isStatusAgentActionable(d.executionStatus ?? ''))
176
+ .map(d => d.id));
177
+ const hasActionableWithOpenIssues = currentIssues.some(i => actionableDeliveryIds.has(i.deliveryId) && OPEN_ISSUE_STATUSES.has(i.status));
178
+ let effectiveDeliveries = currentDeliveries;
179
+ if (actionableDeliveryIds.size > 0 && !hasActionableWithOpenIssues) {
180
+ try {
181
+ await deps.advanceDeliveryStatuses(ctx.config, teamState, teamState.leadSessionId ?? '');
182
+ }
183
+ catch (err) {
184
+ log.warn(`Wake-cycle advanceDeliveryStatuses failed for "${focusName}": ${err.message}`);
185
+ }
186
+ // Re-fetch and re-check termination after the advance -- if every delivery
187
+ // is now terminal-for-the-team, exit cleanly and hand off to review
188
+ // routing / verification instead of sending a stale re-engagement.
189
+ try {
190
+ effectiveDeliveries = await deps.getFocusDeliveries(focusId);
191
+ const stillNonTerminal = effectiveDeliveries.filter(d => !isStatusCascadable(d.executionStatus ?? ''));
192
+ if (stillNonTerminal.length === 0) {
193
+ log.info(`All deliveries terminal after wake-cycle advance for "${focusName}" -- terminating team`);
194
+ teamState.shutdownReason = 'work_complete';
195
+ deps.terminateTeam(focusId);
196
+ return { terminated: true, effectiveDeliveries };
197
+ }
198
+ }
199
+ catch (err) {
200
+ log.warn(`Failed to re-fetch deliveries after wake-cycle advance for "${focusName}": ${err.message}`);
201
+ }
202
+ }
203
+ return { terminated: false, effectiveDeliveries };
204
+ }
205
+ //# sourceMappingURL=event-entry-phases.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"event-entry-phases.js","sourceRoot":"","sources":["../../src/completion/event-entry-phases.ts"],"names":[],"mappings":"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"}
@@ -5,13 +5,16 @@
5
5
  * (completion/event.ts) so each sub-decision is independently testable
6
6
  * without constructing the whole completion world:
7
7
  *
8
- * 1. applyPlanningRefresh planning-team delivery discovery / failure
9
- * 2. routeReviewWindowCompletion review routing (review pass vs coding pass)
10
- * 3. runWakeCycleSelfHeal pre-guard advanceDeliveryStatuses self-heal
11
8
  * 4. computeCompletionFacts the post-advance factual picture
12
9
  * 5. checkDeliveryAdvancementStall cross-respawn advancement watchdog
13
10
  * 6. executeCompletionAction side effects of the chosen action
14
11
  *
12
+ * Phases 1-3 -- the pre-decision entry phases that can short-circuit the
13
+ * pipeline (applyPlanningRefresh, routeReviewWindowCompletion,
14
+ * runWakeCycleSelfHeal) -- live in event-entry-phases.ts. The split is purely
15
+ * to hold both files under the 800-line daemon module limit; the pipeline
16
+ * order and semantics are unchanged.
17
+ *
15
18
  * The decision itself stays pure in completion-decision.ts. Behavior is
16
19
  * preserved exactly: same reads, same order, same log lines, same early
17
20
  * returns; delivery execution_status transition semantics are untouched
@@ -20,45 +23,6 @@
20
23
  import type { FocusDeliveryInfo, FocusIssueInfo, ProgressSnapshot } from '../types.js';
21
24
  import { type CompletionAction } from './completion-decision.js';
22
25
  import type { CompletionEventContext, CompletionEventDeps } from './event.js';
23
- /**
24
- * Wake-cycle refresh (Option A): planning teams spawn with knownDeliveryIds
25
- * empty. The MCP-driven planning turn writes deliveries to the DB, so we
26
- * re-snapshot them here before any downstream merge/completion logic looks
27
- * at the set. WHY Option A: keeps the refresh on the existing wake path
28
- * instead of polling, so it runs exactly when new deliveries become visible.
29
- *
30
- * Returns true when the team was terminated (planning produced nothing).
31
- */
32
- export declare function applyPlanningRefresh(ctx: CompletionEventContext, deps: CompletionEventDeps, currentDeliveries: FocusDeliveryInfo[]): Promise<boolean>;
33
- export type ReviewWindowOutcome = 'terminated' | 'leave_running' | 'not_in_review';
34
- /**
35
- * Review-window handling. Resolves review_requested_at FRESH because it can
36
- * be set/cleared between spawn and this event.
37
- *
38
- * - A completing REVIEW pass routes verify deliveries deterministically
39
- * (runReviewExitHandler) and terminates -> 'terminated'.
40
- * - A completing CODING pass during a review window is left alive so the
41
- * review's own completion closes the focus -> 'leave_running'.
42
- * - Otherwise -> 'not_in_review' and the pipeline continues.
43
- */
44
- export declare function routeReviewWindowCompletion(ctx: CompletionEventContext, deps: CompletionEventDeps, currentDeliveries: FocusDeliveryInfo[]): Promise<ReviewWindowOutcome>;
45
- /**
46
- * Self-heal BEFORE the no-progress guard. When actionable deliveries exist
47
- * but none carry open issues, the team has nothing left to code. The common
48
- * cause is a review that bounced a delivery to verify_failed while all its
49
- * issues are already Done. The daemon's transparent
50
- * verify_failed -> coding -> verify stepping (advanceDeliveryStatuses) clears
51
- * that. Running it here -- before snapshotting -- means a benign "no work to
52
- * do" cycle either terminates the team cleanly (all deliveries now terminal)
53
- * or shows up as real progress (the delivery status moved), instead of being
54
- * counted as coder no-progress. Either way it can no longer accrue toward a
55
- * spurious team_failure escalation. (Previously this advance ran only AFTER
56
- * the guard, so the counter could reach the limit and escalate first.)
57
- */
58
- export declare function runWakeCycleSelfHeal(ctx: CompletionEventContext, deps: CompletionEventDeps, currentDeliveries: FocusDeliveryInfo[], currentIssues: FocusIssueInfo[]): Promise<{
59
- terminated: boolean;
60
- effectiveDeliveries: FocusDeliveryInfo[];
61
- }>;
62
26
  /** Everything the decision + action execution need, computed once. */
63
27
  export interface CompletionFacts {
64
28
  /** The ORIGINAL pre-advance delivery list (tracking-state update uses it). */
@@ -76,6 +40,14 @@ export interface CompletionFacts {
76
40
  advancedThisCycle: boolean;
77
41
  stuck: FocusDeliveryInfo | null;
78
42
  updatedNoProgressCycles: number;
43
+ /**
44
+ * True when the team is parked on an open, unanswered directed escalation
45
+ * (needs_intent / needs_clarification) anchored to a delivery it is working.
46
+ * Fail-open (a lookup error resolves to false). Routes the decision to the
47
+ * `hold_blocked_on_directed_escalation` action and suppresses the cross-respawn
48
+ * advancement watchdog so a blocked team is neither escalated nor re-engaged.
49
+ */
50
+ blockedOnDirectedEscalation: boolean;
79
51
  }
80
52
  /**
81
53
  * Recompute the picture on the post-advance state. The team owns exactly two
@@ -1 +1 @@
1
- {"version":3,"file":"event-phases.d.ts","sourceRoot":"","sources":["../../src/completion/event-phases.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAMvF,OAAO,EAA6B,KAAK,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAkB5F,OAAO,KAAK,EAAE,sBAAsB,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAO9E;;;;;;;;GAQG;AACH,wBAAsB,oBAAoB,CACxC,GAAG,EAAE,sBAAsB,EAC3B,IAAI,EAAE,mBAAmB,EACzB,iBAAiB,EAAE,iBAAiB,EAAE,GACrC,OAAO,CAAC,OAAO,CAAC,CA8BlB;AAID,MAAM,MAAM,mBAAmB,GAAG,YAAY,GAAG,eAAe,GAAG,eAAe,CAAC;AAEnF;;;;;;;;;GASG;AACH,wBAAsB,2BAA2B,CAC/C,GAAG,EAAE,sBAAsB,EAC3B,IAAI,EAAE,mBAAmB,EACzB,iBAAiB,EAAE,iBAAiB,EAAE,GACrC,OAAO,CAAC,mBAAmB,CAAC,CA+F9B;AAID;;;;;;;;;;;;GAYG;AACH,wBAAsB,oBAAoB,CACxC,GAAG,EAAE,sBAAsB,EAC3B,IAAI,EAAE,mBAAmB,EACzB,iBAAiB,EAAE,iBAAiB,EAAE,EACtC,aAAa,EAAE,cAAc,EAAE,GAC9B,OAAO,CAAC;IAAE,UAAU,EAAE,OAAO,CAAC;IAAC,mBAAmB,EAAE,iBAAiB,EAAE,CAAA;CAAE,CAAC,CAiD5E;AAID,sEAAsE;AACtE,MAAM,WAAW,eAAe;IAC9B,8EAA8E;IAC9E,iBAAiB,EAAE,iBAAiB,EAAE,CAAC;IACvC,mBAAmB,EAAE,iBAAiB,EAAE,CAAC;IACzC,aAAa,EAAE,cAAc,EAAE,CAAC;IAChC,eAAe,EAAE,iBAAiB,EAAE,CAAC;IACrC,qBAAqB,EAAE,OAAO,CAAC;IAC/B,qBAAqB,EAAE,OAAO,CAAC;IAC/B,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,QAAQ,EAAE,gBAAgB,CAAC;IAC3B,aAAa,EAAE,gBAAgB,GAAG,IAAI,CAAC;IACvC,YAAY,EAAE,OAAO,CAAC;IACtB,iBAAiB,EAAE,OAAO,CAAC;IAC3B,KAAK,EAAE,iBAAiB,GAAG,IAAI,CAAC;IAChC,uBAAuB,EAAE,MAAM,CAAC;CACjC;AAED;;;;;;;GAOG;AACH,wBAAsB,sBAAsB,CAC1C,GAAG,EAAE,sBAAsB,EAC3B,IAAI,EAAE,mBAAmB,EACzB,iBAAiB,EAAE,iBAAiB,EAAE,EACtC,mBAAmB,EAAE,iBAAiB,EAAE,EACxC,aAAa,EAAE,cAAc,EAAE,GAC9B,OAAO,CAAC,eAAe,CAAC,CAyI1B;AAID;;;;;;GAMG;AACH,wBAAsB,6BAA6B,CACjD,GAAG,EAAE,sBAAsB,EAC3B,IAAI,EAAE,mBAAmB,EACzB,KAAK,EAAE,eAAe,EACtB,MAAM,EAAE,gBAAgB,EACxB,eAAe,EAAE,MAAM,GACtB,OAAO,CAAC,OAAO,CAAC,CAiDlB;AAID;;;;;GAKG;AACH,wBAAsB,uBAAuB,CAC3C,GAAG,EAAE,sBAAsB,EAC3B,IAAI,EAAE,mBAAmB,EACzB,KAAK,EAAE,eAAe,EACtB,MAAM,EAAE,gBAAgB,EACxB,eAAe,EAAE,MAAM,GACtB,OAAO,CAAC,IAAI,CAAC,CA6Mf"}
1
+ {"version":3,"file":"event-phases.d.ts","sourceRoot":"","sources":["../../src/completion/event-phases.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAMvF,OAAO,EAA6B,KAAK,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAiB5F,OAAO,KAAK,EAAE,sBAAsB,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAO9E,sEAAsE;AACtE,MAAM,WAAW,eAAe;IAC9B,8EAA8E;IAC9E,iBAAiB,EAAE,iBAAiB,EAAE,CAAC;IACvC,mBAAmB,EAAE,iBAAiB,EAAE,CAAC;IACzC,aAAa,EAAE,cAAc,EAAE,CAAC;IAChC,eAAe,EAAE,iBAAiB,EAAE,CAAC;IACrC,qBAAqB,EAAE,OAAO,CAAC;IAC/B,qBAAqB,EAAE,OAAO,CAAC;IAC/B,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,QAAQ,EAAE,gBAAgB,CAAC;IAC3B,aAAa,EAAE,gBAAgB,GAAG,IAAI,CAAC;IACvC,YAAY,EAAE,OAAO,CAAC;IACtB,iBAAiB,EAAE,OAAO,CAAC;IAC3B,KAAK,EAAE,iBAAiB,GAAG,IAAI,CAAC;IAChC,uBAAuB,EAAE,MAAM,CAAC;IAChC;;;;;;OAMG;IACH,2BAA2B,EAAE,OAAO,CAAC;CACtC;AAED;;;;;;;GAOG;AACH,wBAAsB,sBAAsB,CAC1C,GAAG,EAAE,sBAAsB,EAC3B,IAAI,EAAE,mBAAmB,EACzB,iBAAiB,EAAE,iBAAiB,EAAE,EACtC,mBAAmB,EAAE,iBAAiB,EAAE,EACxC,aAAa,EAAE,cAAc,EAAE,GAC9B,OAAO,CAAC,eAAe,CAAC,CA2K1B;AAID;;;;;;GAMG;AACH,wBAAsB,6BAA6B,CACjD,GAAG,EAAE,sBAAsB,EAC3B,IAAI,EAAE,mBAAmB,EACzB,KAAK,EAAE,eAAe,EACtB,MAAM,EAAE,gBAAgB,EACxB,eAAe,EAAE,MAAM,GACtB,OAAO,CAAC,OAAO,CAAC,CAwDlB;AAID;;;;;GAKG;AACH,wBAAsB,uBAAuB,CAC3C,GAAG,EAAE,sBAAsB,EAC3B,IAAI,EAAE,mBAAmB,EACzB,KAAK,EAAE,eAAe,EACtB,MAAM,EAAE,gBAAgB,EACxB,eAAe,EAAE,MAAM,GACtB,OAAO,CAAC,IAAI,CAAC,CA8Nf"}
@@ -5,217 +5,38 @@
5
5
  * (completion/event.ts) so each sub-decision is independently testable
6
6
  * without constructing the whole completion world:
7
7
  *
8
- * 1. applyPlanningRefresh planning-team delivery discovery / failure
9
- * 2. routeReviewWindowCompletion review routing (review pass vs coding pass)
10
- * 3. runWakeCycleSelfHeal pre-guard advanceDeliveryStatuses self-heal
11
8
  * 4. computeCompletionFacts the post-advance factual picture
12
9
  * 5. checkDeliveryAdvancementStall cross-respawn advancement watchdog
13
10
  * 6. executeCompletionAction side effects of the chosen action
14
11
  *
12
+ * Phases 1-3 -- the pre-decision entry phases that can short-circuit the
13
+ * pipeline (applyPlanningRefresh, routeReviewWindowCompletion,
14
+ * runWakeCycleSelfHeal) -- live in event-entry-phases.ts. The split is purely
15
+ * to hold both files under the 800-line daemon module limit; the pipeline
16
+ * order and semantics are unchanged.
17
+ *
15
18
  * The decision itself stays pure in completion-decision.ts. Behavior is
16
19
  * preserved exactly: same reads, same order, same log lines, same early
17
20
  * returns; delivery execution_status transition semantics are untouched
18
21
  * (single-writer reconciler).
19
22
  */
20
- import { sendMessage, DELIVERY_STATUS } from '@telora/daemon-core';
23
+ import { sendMessage } from '@telora/daemon-core';
21
24
  import { isStatusCascadable, isStatusAgentActionable } from '../stage-classifier.js';
22
25
  import { buildWakeMessage, buildReEngagementMessage } from '../focus-prompt-builder.js';
23
26
  import { OPEN_ISSUE_STATUSES } from '../constants.js';
24
27
  import { isAuditPhaseFocus } from '../audit-shape.js';
25
28
  import { shouldCheckNonAdvanceable } from './completion-decision.js';
26
29
  import { takeProgressSnapshot, snapshotsEqual } from './progress-snapshot.js';
27
- import { isInReviewWindow, findNonAdvanceableDelivery } from './advanceability.js';
30
+ import { findNonAdvanceableDelivery } from './advanceability.js';
28
31
  import { getFocusWorktree as defaultGetFocusWorktree } from '../focus-worktree-state.js';
29
32
  import { worktreeHasUncommittedChanges as defaultWorktreeHasUncommittedChanges } from '../worktree/index.js';
30
33
  import { readWorktreeHeadSha as defaultReadWorktreeHeadSha } from '../git/index.js';
31
34
  import { resolveAutoRemediationBudget } from '../delivery-advance-block.js';
32
- import { escalatePlanningFailure, escalateNoProgress, escalateNonAdvanceable, escalateDeliveryAdvancementStalled, } from './event-escalations.js';
35
+ import { escalateNoProgress, escalateNonAdvanceable, escalateDeliveryAdvancementStalled, } from './event-escalations.js';
33
36
  import { recordDeliveryAdvancementCycle } from './delivery-advancement-guard.js';
34
37
  import { reconcileTerminatedTeamWork as defaultReconcileTerminatedTeamWork, } from './terminated-work-reconcile.js';
35
38
  import { createLogger } from '../log.js';
36
39
  const log = createLogger({ module: 'focus-executor' });
37
- // ── Phase 1: planning refresh ───────────────────────────────────────────
38
- /**
39
- * Wake-cycle refresh (Option A): planning teams spawn with knownDeliveryIds
40
- * empty. The MCP-driven planning turn writes deliveries to the DB, so we
41
- * re-snapshot them here before any downstream merge/completion logic looks
42
- * at the set. WHY Option A: keeps the refresh on the existing wake path
43
- * instead of polling, so it runs exactly when new deliveries become visible.
44
- *
45
- * Returns true when the team was terminated (planning produced nothing).
46
- */
47
- export async function applyPlanningRefresh(ctx, deps, currentDeliveries) {
48
- const { focusId, focusName, teamState } = ctx;
49
- if (!teamState.planningPhase)
50
- return false;
51
- if (currentDeliveries.length === 0) {
52
- log.warn(`Planning team for "${focusName}" produced no deliveries ` +
53
- `-- escalating and terminating`);
54
- await escalatePlanningFailure({
55
- organizationId: teamState.organizationId,
56
- sessionId: teamState.leadSessionId ?? '',
57
- focusId,
58
- focusName,
59
- agentRoleId: teamState.roleId,
60
- }, deps);
61
- teamState.shutdownReason = 'work_complete';
62
- deps.terminateTeam(focusId);
63
- return true;
64
- }
65
- for (const d of currentDeliveries) {
66
- teamState.knownDeliveryIds.add(d.id);
67
- teamState.deliveryStageIds.set(d.id, d.currentWorkflowStageId);
68
- }
69
- teamState.planningPhase = false;
70
- log.info(`Planning refresh for "${focusName}": ` +
71
- `${currentDeliveries.length} delivery(ies) discovered`);
72
- return false;
73
- }
74
- /**
75
- * Review-window handling. Resolves review_requested_at FRESH because it can
76
- * be set/cleared between spawn and this event.
77
- *
78
- * - A completing REVIEW pass routes verify deliveries deterministically
79
- * (runReviewExitHandler) and terminates -> 'terminated'.
80
- * - A completing CODING pass during a review window is left alive so the
81
- * review's own completion closes the focus -> 'leave_running'.
82
- * - Otherwise -> 'not_in_review' and the pipeline continues.
83
- */
84
- export async function routeReviewWindowCompletion(ctx, deps, currentDeliveries) {
85
- const { focusId, focusName, teamState } = ctx;
86
- let reviewRequestedAt = null;
87
- if (deps.getReviewRequestedAt) {
88
- try {
89
- reviewRequestedAt = await deps.getReviewRequestedAt(ctx.config, focusId);
90
- }
91
- catch (err) {
92
- log.warn(`getReviewRequestedAt failed for "${focusName}": ${err.message}`);
93
- // Fail open: a read failure must not block the existing teardown logic.
94
- }
95
- }
96
- if (!isInReviewWindow(reviewRequestedAt, currentDeliveries))
97
- return 'not_in_review';
98
- const verifyCount = currentDeliveries.filter(d => d.executionStatus === DELIVERY_STATUS.VERIFY).length;
99
- if (teamState.sessionType === 'review') {
100
- // A REVIEW pass just completed -- it emitted its result, which IS the
101
- // "a review cycle ran" signal. Close the focus DETERMINISTICALLY now,
102
- // keyed on THIS completion event rather than on process exit. A review
103
- // resumes its prior session to preserve context (INJ-D) and runs as an
104
- // interactive stream-json process that emits a result but never exits;
105
- // the old exit-keyed close therefore waited forever (the review-resume
106
- // deadlock). runReviewExitHandler routes verify deliveries by the
107
- // deterministic signal -- a pass that filed zero new issues and has no
108
- // open work closes verify->done; open work routes verify->verify_failed
109
- // to re-iterate -- and clears review_requested_at. The review team is then
110
- // torn down: closure has ALREADY happened, so it no longer matters whether
111
- // the process exits cleanly or is SIGTERM'd, and the next review cycle
112
- // resumes the same session id to review only the new delta.
113
- //
114
- // succeeded is the ACTUAL result of the review turn (!isError), NOT a
115
- // hardcoded true. A review that erred (e.g. the CLI failed to start, or a
116
- // model/engine mismatch) emits a result event with isError=true and files
117
- // no defects -- the old `succeeded: true` made that indistinguishable from
118
- // a genuinely clean review, so an errored/no-op review auto-approved
119
- // verify->done WITHOUT a real review. With the real flag, an errored
120
- // review leaves the delivery in verify; the next auto-review poll
121
- // re-triggers, and a persistently failing review is bounded by the
122
- // review_window_stalled escalation.
123
- const reviewSucceeded = !ctx.completionInfo.result.isError;
124
- log.info(`Review pass completed for "${focusName}" (${verifyCount} ` +
125
- `delivery(ies) in verify, succeeded=${reviewSucceeded}) -- routing deterministically ` +
126
- `by filed issues, then terminating.`);
127
- // Coverage evidence: spawn-tip (what the review saw, carried on the team
128
- // state) is stamped as the watermark; exit-tip (HEAD now) is compared to
129
- // it to detect mid-review drift. Exit-tip is best-effort via injected git
130
- // deps; null makes coverage unconfirmable (auto-approve withheld).
131
- const reviewSpawnTip = teamState.reviewSpawnTip;
132
- let currentBranchTip = null;
133
- try {
134
- const wt = deps.getFocusWorktree?.(focusId);
135
- if (wt?.worktreePath) {
136
- currentBranchTip = deps.readWorktreeHeadSha?.(wt.worktreePath) ?? null;
137
- }
138
- }
139
- catch (err) {
140
- log.warn(`Could not resolve exit-tip for "${focusName}" coverage gate: ${err.message}`);
141
- }
142
- try {
143
- await deps.runReviewExitHandler({
144
- focusId,
145
- focusName,
146
- organizationId: teamState.organizationId,
147
- productId: teamState.productId,
148
- sessionId: teamState.leadSessionId ?? '',
149
- sessionType: 'review',
150
- succeeded: reviewSucceeded,
151
- reviewSpawnTip,
152
- currentBranchTip,
153
- });
154
- }
155
- catch (err) {
156
- log.warn(`Deterministic review routing failed for "${focusName}": ${err.message}`);
157
- }
158
- teamState.shutdownReason = 'work_complete';
159
- deps.terminateTeam(focusId);
160
- return 'terminated';
161
- }
162
- // A non-review (coding) team completed a cycle while a review is in flight.
163
- // Do NOT tear it down on the all-terminal / no-actionable-work branches
164
- // below: `verify` is cascadable, so they would SIGTERM the team mid-review.
165
- // Leave it running; the review team's own completion closes the focus.
166
- log.info(`"${focusName}" is in a review window (review_requested_at set, ` +
167
- `${verifyCount} delivery(ies) in verify) and the completing session is a ` +
168
- `${teamState.sessionType} pass -- not tearing down; the review pass completion routes ` +
169
- `the verify deliveries.`);
170
- return 'leave_running';
171
- }
172
- // ── Phase 3: wake-cycle self-heal advance ───────────────────────────────
173
- /**
174
- * Self-heal BEFORE the no-progress guard. When actionable deliveries exist
175
- * but none carry open issues, the team has nothing left to code. The common
176
- * cause is a review that bounced a delivery to verify_failed while all its
177
- * issues are already Done. The daemon's transparent
178
- * verify_failed -> coding -> verify stepping (advanceDeliveryStatuses) clears
179
- * that. Running it here -- before snapshotting -- means a benign "no work to
180
- * do" cycle either terminates the team cleanly (all deliveries now terminal)
181
- * or shows up as real progress (the delivery status moved), instead of being
182
- * counted as coder no-progress. Either way it can no longer accrue toward a
183
- * spurious team_failure escalation. (Previously this advance ran only AFTER
184
- * the guard, so the counter could reach the limit and escalate first.)
185
- */
186
- export async function runWakeCycleSelfHeal(ctx, deps, currentDeliveries, currentIssues) {
187
- const { focusId, focusName, teamState } = ctx;
188
- const actionableDeliveryIds = new Set(currentDeliveries
189
- .filter(d => isStatusAgentActionable(d.executionStatus ?? ''))
190
- .map(d => d.id));
191
- const hasActionableWithOpenIssues = currentIssues.some(i => actionableDeliveryIds.has(i.deliveryId) && OPEN_ISSUE_STATUSES.has(i.status));
192
- let effectiveDeliveries = currentDeliveries;
193
- if (actionableDeliveryIds.size > 0 && !hasActionableWithOpenIssues) {
194
- try {
195
- await deps.advanceDeliveryStatuses(ctx.config, teamState, teamState.leadSessionId ?? '');
196
- }
197
- catch (err) {
198
- log.warn(`Wake-cycle advanceDeliveryStatuses failed for "${focusName}": ${err.message}`);
199
- }
200
- // Re-fetch and re-check termination after the advance -- if every delivery
201
- // is now terminal-for-the-team, exit cleanly and hand off to review
202
- // routing / verification instead of sending a stale re-engagement.
203
- try {
204
- effectiveDeliveries = await deps.getFocusDeliveries(focusId);
205
- const stillNonTerminal = effectiveDeliveries.filter(d => !isStatusCascadable(d.executionStatus ?? ''));
206
- if (stillNonTerminal.length === 0) {
207
- log.info(`All deliveries terminal after wake-cycle advance for "${focusName}" -- terminating team`);
208
- teamState.shutdownReason = 'work_complete';
209
- deps.terminateTeam(focusId);
210
- return { terminated: true, effectiveDeliveries };
211
- }
212
- }
213
- catch (err) {
214
- log.warn(`Failed to re-fetch deliveries after wake-cycle advance for "${focusName}": ${err.message}`);
215
- }
216
- }
217
- return { terminated: false, effectiveDeliveries };
218
- }
219
40
  /**
220
41
  * Recompute the picture on the post-advance state. The team owns exactly two
221
42
  * kinds of non-terminal work: planning deliveries (it scopes them) and
@@ -310,11 +131,40 @@ export async function computeCompletionFacts(ctx, deps, currentDeliveries, effec
310
131
  const candidates = postNonTerminal.filter(d => isStatusAgentActionable(d.executionStatus ?? ''));
311
132
  stuck = await findNonAdvanceableDelivery(candidates, deps.fetchEffectiveWorkflow);
312
133
  }
134
+ // A team parked on its own unanswered DIRECTED escalation (needs_intent /
135
+ // needs_clarification) anchored to a delivery it is working is BLOCKED on a
136
+ // human answer, not stalling. Consult the lookup ONLY on the cycle where the
137
+ // team would otherwise accrue a no-progress increment (has actionable work AND
138
+ // did not move state), so a healthy cycle never pays the query. FAIL-OPEN: any
139
+ // lookup error resolves to not-blocked, so a genuine stall is never masked by
140
+ // a failed check -- the OPPOSITE of the operator-hold gate, which fails closed.
141
+ let blockedOnDirectedEscalation = false;
142
+ if (teamHasActionableWork && !madeProgress && deps.findOpenDirectedHold) {
143
+ const actionableDeliveries = effectiveDeliveries.filter(d => isStatusAgentActionable(d.executionStatus ?? ''));
144
+ for (const d of actionableDeliveries) {
145
+ try {
146
+ if (await deps.findOpenDirectedHold(d.id, focusId)) {
147
+ blockedOnDirectedEscalation = true;
148
+ break;
149
+ }
150
+ }
151
+ catch (err) {
152
+ log.warn(`Directed-escalation hold lookup failed for "${focusName}" delivery ${d.id}, ` +
153
+ `treating as not-blocked: ${err.message}`);
154
+ }
155
+ }
156
+ }
313
157
  // The no-progress counter value AFTER this cycle's reset-or-increment.
314
158
  // Only committed to teamState on the actionable-work paths in
315
159
  // executeCompletionAction -- the no-actionable-work terminations deliberately
316
- // do not touch the counter.
317
- const updatedNoProgressCycles = madeProgress ? 0 : (teamState.noProgressCycles ?? 0) + 1;
160
+ // do not touch the counter. A blocked team FREEZES the counter (neither reset
161
+ // nor increment): the wait is not progress, but it is not a stall either, so
162
+ // the count is preserved to resume exactly where it was once the block clears.
163
+ const updatedNoProgressCycles = madeProgress
164
+ ? 0
165
+ : blockedOnDirectedEscalation
166
+ ? (teamState.noProgressCycles ?? 0)
167
+ : (teamState.noProgressCycles ?? 0) + 1;
318
168
  // Measurable progress this cycle means the prior dirty episode -- if any --
319
169
  // is no longer stalled, so the commit-nudge budget resets alongside the
320
170
  // no-progress counter.
@@ -336,6 +186,7 @@ export async function computeCompletionFacts(ctx, deps, currentDeliveries, effec
336
186
  advancedThisCycle,
337
187
  stuck,
338
188
  updatedNoProgressCycles,
189
+ blockedOnDirectedEscalation,
339
190
  };
340
191
  }
341
192
  // ── Phase 5: cross-respawn delivery-advancement watchdog ────────────────
@@ -348,7 +199,14 @@ export async function computeCompletionFacts(ctx, deps, currentDeliveries, effec
348
199
  */
349
200
  export async function checkDeliveryAdvancementStall(ctx, deps, facts, action, noProgressLimit) {
350
201
  const { focusId, focusName, teamState } = ctx;
202
+ // A team blocked on an unanswered directed escalation must not accrue on THIS
203
+ // watchdog either: it is the same false-positive the primary no-progress guard
204
+ // avoids one layer up, and recording it would let the cross-respawn guard file
205
+ // its own delivery-advancement-stalled team_failure on top of the open question
206
+ // (criterion 3). Skip recording entirely while blocked -- the counter simply
207
+ // holds until the block clears.
351
208
  const shouldRecord = facts.teamHasActionableWork &&
209
+ !facts.blockedOnDirectedEscalation &&
352
210
  action.kind !== 'escalate_no_progress_and_terminate' &&
353
211
  (facts.priorSnapshot === null || !facts.madeProgress);
354
212
  const deliveryAdvancement = shouldRecord
@@ -541,6 +399,21 @@ export async function executeCompletionAction(ctx, deps, facts, action, noProgre
541
399
  deps.terminateTeam(focusId);
542
400
  return;
543
401
  }
402
+ case 'hold_blocked_on_directed_escalation': {
403
+ // The team is parked on its own unanswered directed escalation. Do NOT
404
+ // escalate (the open question is already the single signal a human sees),
405
+ // do NOT send a wake/handoff message (that re-engagement is the model spend
406
+ // this branch exists to stop), and do NOT terminate (the team resumes in
407
+ // place when the answer read-back re-engages it). Just commit the FROZEN
408
+ // no-progress counter + the fresh snapshot and fall silent -- with no
409
+ // message sent, the team emits no further 'complete', so the completion
410
+ // loop stops burning cycles until the escalation is answered.
411
+ teamState.noProgressCycles = updatedNoProgressCycles;
412
+ teamState.lastProgressSnapshot = snapshot;
413
+ log.info(`Team for "${focusName}" is blocked on an unanswered directed escalation ` +
414
+ `-- holding in place (no re-engagement, no team_failure) until it is answered.`);
415
+ return;
416
+ }
544
417
  case 'handoff_new_work':
545
418
  case 'reengage': {
546
419
  teamState.noProgressCycles = updatedNoProgressCycles;