@telora/daemon 0.21.98 → 0.21.134

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 (226) hide show
  1. package/build-info.json +6 -3
  2. package/dist/audit/audit-hooks.d.ts +106 -0
  3. package/dist/audit/audit-hooks.d.ts.map +1 -1
  4. package/dist/audit/audit-hooks.js +149 -17
  5. package/dist/audit/audit-hooks.js.map +1 -1
  6. package/dist/ci/ci-sensor-loop.d.ts +6 -0
  7. package/dist/ci/ci-sensor-loop.d.ts.map +1 -1
  8. package/dist/ci/ci-sensor-loop.js +50 -3
  9. package/dist/ci/ci-sensor-loop.js.map +1 -1
  10. package/dist/completion/coverage-drift-state.d.ts +29 -2
  11. package/dist/completion/coverage-drift-state.d.ts.map +1 -1
  12. package/dist/completion/coverage-drift-state.js +42 -2
  13. package/dist/completion/coverage-drift-state.js.map +1 -1
  14. package/dist/completion/coverage-stabilized.d.ts +58 -0
  15. package/dist/completion/coverage-stabilized.d.ts.map +1 -0
  16. package/dist/completion/coverage-stabilized.js +84 -0
  17. package/dist/completion/coverage-stabilized.js.map +1 -0
  18. package/dist/completion/delivery-advancement-guard.d.ts +21 -0
  19. package/dist/completion/delivery-advancement-guard.d.ts.map +1 -1
  20. package/dist/completion/delivery-advancement-guard.js +31 -3
  21. package/dist/completion/delivery-advancement-guard.js.map +1 -1
  22. package/dist/completion/event-escalations.d.ts +15 -0
  23. package/dist/completion/event-escalations.d.ts.map +1 -1
  24. package/dist/completion/event-escalations.js +42 -4
  25. package/dist/completion/event-escalations.js.map +1 -1
  26. package/dist/completion/event-phases.d.ts.map +1 -1
  27. package/dist/completion/event-phases.js +5 -2
  28. package/dist/completion/event-phases.js.map +1 -1
  29. package/dist/completion/machine-remediable-demotion.d.ts +74 -0
  30. package/dist/completion/machine-remediable-demotion.d.ts.map +1 -0
  31. package/dist/completion/machine-remediable-demotion.js +147 -0
  32. package/dist/completion/machine-remediable-demotion.js.map +1 -0
  33. package/dist/completion/review-exit-phase.d.ts.map +1 -1
  34. package/dist/completion/review-exit-phase.js +10 -6
  35. package/dist/completion/review-exit-phase.js.map +1 -1
  36. package/dist/completion/settle-injection-sweep.d.ts +55 -0
  37. package/dist/completion/settle-injection-sweep.d.ts.map +1 -0
  38. package/dist/completion/settle-injection-sweep.js +88 -0
  39. package/dist/completion/settle-injection-sweep.js.map +1 -0
  40. package/dist/completion/state-cascade.d.ts.map +1 -1
  41. package/dist/completion/state-cascade.js +41 -0
  42. package/dist/completion/state-cascade.js.map +1 -1
  43. package/dist/completion/status-advance-phase.d.ts.map +1 -1
  44. package/dist/completion/status-advance-phase.js +18 -1
  45. package/dist/completion/status-advance-phase.js.map +1 -1
  46. package/dist/completion/team-lifecycle.d.ts +42 -0
  47. package/dist/completion/team-lifecycle.d.ts.map +1 -1
  48. package/dist/completion/team-lifecycle.js +71 -0
  49. package/dist/completion/team-lifecycle.js.map +1 -1
  50. package/dist/delivery/delivery-lifecycle.d.ts +19 -33
  51. package/dist/delivery/delivery-lifecycle.d.ts.map +1 -1
  52. package/dist/delivery/delivery-lifecycle.js +19 -147
  53. package/dist/delivery/delivery-lifecycle.js.map +1 -1
  54. package/dist/delivery/delivery-merge-ops.d.ts +46 -0
  55. package/dist/delivery/delivery-merge-ops.d.ts.map +1 -0
  56. package/dist/delivery/delivery-merge-ops.js +154 -0
  57. package/dist/delivery/delivery-merge-ops.js.map +1 -0
  58. package/dist/delivery/delivery-reconciler-apply.d.ts +28 -0
  59. package/dist/delivery/delivery-reconciler-apply.d.ts.map +1 -1
  60. package/dist/delivery/delivery-reconciler-apply.js +30 -0
  61. package/dist/delivery/delivery-reconciler-apply.js.map +1 -1
  62. package/dist/delivery/delivery-reconciler.d.ts +7 -2
  63. package/dist/delivery/delivery-reconciler.d.ts.map +1 -1
  64. package/dist/delivery/delivery-reconciler.js +34 -5
  65. package/dist/delivery/delivery-reconciler.js.map +1 -1
  66. package/dist/delivery/external-hold-surface.d.ts +32 -0
  67. package/dist/delivery/external-hold-surface.d.ts.map +1 -0
  68. package/dist/delivery/external-hold-surface.js +67 -0
  69. package/dist/delivery/external-hold-surface.js.map +1 -0
  70. package/dist/delivery/external-hold.d.ts +21 -0
  71. package/dist/delivery/external-hold.d.ts.map +1 -1
  72. package/dist/delivery/external-hold.js +40 -5
  73. package/dist/delivery/external-hold.js.map +1 -1
  74. package/dist/delivery/paused-revival.d.ts +140 -0
  75. package/dist/delivery/paused-revival.d.ts.map +1 -0
  76. package/dist/delivery/paused-revival.js +229 -0
  77. package/dist/delivery/paused-revival.js.map +1 -0
  78. package/dist/delivery/wait-reachability.d.ts.map +1 -1
  79. package/dist/delivery/wait-reachability.js +11 -0
  80. package/dist/delivery/wait-reachability.js.map +1 -1
  81. package/dist/directive/close-loop-dispatcher.d.ts +16 -263
  82. package/dist/directive/close-loop-dispatcher.d.ts.map +1 -1
  83. package/dist/directive/close-loop-dispatcher.js +21 -259
  84. package/dist/directive/close-loop-dispatcher.js.map +1 -1
  85. package/dist/directive/directive-dispatch.d.ts +19 -0
  86. package/dist/directive/directive-dispatch.d.ts.map +1 -1
  87. package/dist/directive/directive-dispatch.js +18 -0
  88. package/dist/directive/directive-dispatch.js.map +1 -1
  89. package/dist/directive/directive-executor.d.ts +1 -3
  90. package/dist/directive/directive-executor.d.ts.map +1 -1
  91. package/dist/directive/directive-executor.js +23 -21
  92. package/dist/directive/directive-executor.js.map +1 -1
  93. package/dist/directive/phase-sync.d.ts +28 -15
  94. package/dist/directive/phase-sync.d.ts.map +1 -1
  95. package/dist/directive/phase-sync.js +28 -36
  96. package/dist/directive/phase-sync.js.map +1 -1
  97. package/dist/directive/resolver-dispatch.d.ts +19 -92
  98. package/dist/directive/resolver-dispatch.d.ts.map +1 -1
  99. package/dist/directive/resolver-dispatch.js +19 -65
  100. package/dist/directive/resolver-dispatch.js.map +1 -1
  101. package/dist/focus/focus-engine.d.ts.map +1 -1
  102. package/dist/focus/focus-engine.js +26 -0
  103. package/dist/focus/focus-engine.js.map +1 -1
  104. package/dist/focus/focus-executor.d.ts.map +1 -1
  105. package/dist/focus/focus-executor.js +290 -256
  106. package/dist/focus/focus-executor.js.map +1 -1
  107. package/dist/focus/focus-loop-registry.d.ts.map +1 -1
  108. package/dist/focus/focus-loop-registry.js +43 -1
  109. package/dist/focus/focus-loop-registry.js.map +1 -1
  110. package/dist/focus/focus-respawn-guard.d.ts.map +1 -1
  111. package/dist/focus/focus-respawn-guard.js +11 -2
  112. package/dist/focus/focus-respawn-guard.js.map +1 -1
  113. package/dist/focus/focus-team-state.d.ts +11 -0
  114. package/dist/focus/focus-team-state.d.ts.map +1 -1
  115. package/dist/focus/focus-team-state.js +17 -0
  116. package/dist/focus/focus-team-state.js.map +1 -1
  117. package/dist/focus/listener-auto-advance.d.ts +13 -1
  118. package/dist/focus/listener-auto-advance.d.ts.map +1 -1
  119. package/dist/focus/listener-auto-advance.js +103 -22
  120. package/dist/focus/listener-auto-advance.js.map +1 -1
  121. package/dist/focus/listener.d.ts.map +1 -1
  122. package/dist/focus/listener.js +125 -75
  123. package/dist/focus/listener.js.map +1 -1
  124. package/dist/focus/tick-deadline.d.ts +55 -0
  125. package/dist/focus/tick-deadline.d.ts.map +1 -0
  126. package/dist/focus/tick-deadline.js +91 -0
  127. package/dist/focus/tick-deadline.js.map +1 -0
  128. package/dist/focus-stage-lifecycle/index.d.ts +18 -25
  129. package/dist/focus-stage-lifecycle/index.d.ts.map +1 -1
  130. package/dist/focus-stage-lifecycle/index.js +26 -38
  131. package/dist/focus-stage-lifecycle/index.js.map +1 -1
  132. package/dist/focus-stage-lifecycle/wedge-cause-arbiter.d.ts +52 -0
  133. package/dist/focus-stage-lifecycle/wedge-cause-arbiter.d.ts.map +1 -0
  134. package/dist/focus-stage-lifecycle/wedge-cause-arbiter.js +94 -0
  135. package/dist/focus-stage-lifecycle/wedge-cause-arbiter.js.map +1 -0
  136. package/dist/focus-stage-lifecycle/wedge-watchdog.d.ts +18 -1
  137. package/dist/focus-stage-lifecycle/wedge-watchdog.d.ts.map +1 -1
  138. package/dist/focus-stage-lifecycle/wedge-watchdog.js +57 -7
  139. package/dist/focus-stage-lifecycle/wedge-watchdog.js.map +1 -1
  140. package/dist/guard/condition-evaluators.d.ts +9 -0
  141. package/dist/guard/condition-evaluators.d.ts.map +1 -1
  142. package/dist/guard/condition-evaluators.js +15 -0
  143. package/dist/guard/condition-evaluators.js.map +1 -1
  144. package/dist/guard/guard-evaluator.d.ts +17 -1
  145. package/dist/guard/guard-evaluator.d.ts.map +1 -1
  146. package/dist/guard/guard-evaluator.js +61 -2
  147. package/dist/guard/guard-evaluator.js.map +1 -1
  148. package/dist/guard/trigger-executor.d.ts +30 -18
  149. package/dist/guard/trigger-executor.d.ts.map +1 -1
  150. package/dist/guard/trigger-executor.js +55 -31
  151. package/dist/guard/trigger-executor.js.map +1 -1
  152. package/dist/prompt/team-prompt-base.d.ts +1 -1
  153. package/dist/prompt/team-prompt-base.d.ts.map +1 -1
  154. package/dist/prompt/team-prompt-base.js +1 -1
  155. package/dist/prompt/team-prompt-base.js.map +1 -1
  156. package/dist/prompt-sections/persona-sections.d.ts +0 -32
  157. package/dist/prompt-sections/persona-sections.d.ts.map +1 -1
  158. package/dist/prompt-sections/persona-sections.js +19 -88
  159. package/dist/prompt-sections/persona-sections.js.map +1 -1
  160. package/dist/queries/deliveries.d.ts +23 -0
  161. package/dist/queries/deliveries.d.ts.map +1 -1
  162. package/dist/queries/deliveries.js +6 -0
  163. package/dist/queries/deliveries.js.map +1 -1
  164. package/dist/queries/focuses.d.ts +36 -0
  165. package/dist/queries/focuses.d.ts.map +1 -1
  166. package/dist/queries/focuses.js +45 -0
  167. package/dist/queries/focuses.js.map +1 -1
  168. package/dist/queries/guards.d.ts +10 -0
  169. package/dist/queries/guards.d.ts.map +1 -1
  170. package/dist/queries/guards.js +14 -1
  171. package/dist/queries/guards.js.map +1 -1
  172. package/dist/queries/issues.d.ts +65 -1
  173. package/dist/queries/issues.d.ts.map +1 -1
  174. package/dist/queries/issues.js +135 -2
  175. package/dist/queries/issues.js.map +1 -1
  176. package/dist/queries/schemas.d.ts +51 -0
  177. package/dist/queries/schemas.d.ts.map +1 -1
  178. package/dist/queries/schemas.js +34 -0
  179. package/dist/queries/schemas.js.map +1 -1
  180. package/dist/queries/verification.d.ts +5 -6
  181. package/dist/queries/verification.d.ts.map +1 -1
  182. package/dist/queries/verification.js +5 -6
  183. package/dist/queries/verification.js.map +1 -1
  184. package/dist/resolvers/loop-persona.d.ts +1 -11
  185. package/dist/resolvers/loop-persona.d.ts.map +1 -1
  186. package/dist/resolvers/loop-persona.js +2 -16
  187. package/dist/resolvers/loop-persona.js.map +1 -1
  188. package/dist/shell/unified-shell-provisioning.d.ts +40 -0
  189. package/dist/shell/unified-shell-provisioning.d.ts.map +1 -1
  190. package/dist/shell/unified-shell-provisioning.js +72 -0
  191. package/dist/shell/unified-shell-provisioning.js.map +1 -1
  192. package/dist/shell/unified-shell.d.ts.map +1 -1
  193. package/dist/shell/unified-shell.js +18 -1
  194. package/dist/shell/unified-shell.js.map +1 -1
  195. package/dist/spawn/helpers.d.ts.map +1 -1
  196. package/dist/spawn/helpers.js +6 -1
  197. package/dist/spawn/helpers.js.map +1 -1
  198. package/dist/spawn/process-spawn.d.ts.map +1 -1
  199. package/dist/spawn/process-spawn.js +7 -0
  200. package/dist/spawn/process-spawn.js.map +1 -1
  201. package/dist/spawn/spawn-cooldown.d.ts +41 -7
  202. package/dist/spawn/spawn-cooldown.d.ts.map +1 -1
  203. package/dist/spawn/spawn-cooldown.js +47 -0
  204. package/dist/spawn/spawn-cooldown.js.map +1 -1
  205. package/dist/spawn/spawn-decision.d.ts.map +1 -1
  206. package/dist/spawn/spawn-decision.js +33 -1
  207. package/dist/spawn/spawn-decision.js.map +1 -1
  208. package/dist/verify/stall-detectors.d.ts +20 -8
  209. package/dist/verify/stall-detectors.d.ts.map +1 -1
  210. package/dist/verify/stall-detectors.js +27 -46
  211. package/dist/verify/stall-detectors.js.map +1 -1
  212. package/dist/workflow/gate-failure-policy.d.ts +48 -0
  213. package/dist/workflow/gate-failure-policy.d.ts.map +1 -0
  214. package/dist/workflow/gate-failure-policy.js +57 -0
  215. package/dist/workflow/gate-failure-policy.js.map +1 -0
  216. package/dist/workflow/pipeline-config.d.ts +2 -2
  217. package/dist/workflow/pipeline-config.js +2 -2
  218. package/package.json +1 -1
  219. package/dist/directive/close-loop-stage.d.ts +0 -50
  220. package/dist/directive/close-loop-stage.d.ts.map +0 -1
  221. package/dist/directive/close-loop-stage.js +0 -210
  222. package/dist/directive/close-loop-stage.js.map +0 -1
  223. package/dist/focus-stage-lifecycle/review-convergence.d.ts +0 -133
  224. package/dist/focus-stage-lifecycle/review-convergence.d.ts.map +0 -1
  225. package/dist/focus-stage-lifecycle/review-convergence.js +0 -237
  226. package/dist/focus-stage-lifecycle/review-convergence.js.map +0 -1
@@ -1,50 +0,0 @@
1
- /**
2
- * Close-loop stage dispatcher.
3
- *
4
- * Drives the `close_loop` lifecycle stage for a focus: classify anchored
5
- * injections by terminal state, then short-circuit to done, spawn the
6
- * one-shot bookkeeper team, advance with an injection sweep, or hold.
7
- * The pure decision logic lives in close-loop-dispatcher.ts; this module
8
- * is the impure dispatch around it. Extracted from directive-executor.ts;
9
- * the executor re-exports the public surface so existing importers are
10
- * unaffected.
11
- */
12
- import type { DaemonConfig, StageDirective } from '../types/index.js';
13
- import { type CloseLoopDecision } from './close-loop-dispatcher.js';
14
- /** Test helper: clear the bookkeeper-spawned set. */
15
- export declare function __resetCloseLoopStateForTesting(): void;
16
- /**
17
- * Inspect open escalations and return the set of injection ids that have
18
- * an active `injection_unverified` escalation (status pending or
19
- * in_review). Used by the close_loop dispatcher to classify "escalated"
20
- * injections as terminal.
21
- *
22
- * The `metadata.injection_id` field is set by the bookkeeper persona when
23
- * filing the escalation -- see the close_loop directive prompt.
24
- */
25
- export declare function getOpenInjectionUnverifiedEscalationIds(): Promise<Set<string>>;
26
- /**
27
- * Dispatch the close_loop stage for one focus.
28
- *
29
- * Reads the focus's anchored deliveries + injection statuses + open
30
- * escalations, then runs the pure `decideCloseLoop` to determine whether
31
- * to short-circuit, spawn a bookkeeper, advance to done, or hold.
32
- *
33
- * Returns the decision so the caller (or a test) can log/assert.
34
- *
35
- * Idempotent across poll cycles -- the `closeLoopBookkeeperSpawned` set
36
- * ensures the bookkeeper is spawned at most once per focus visit to
37
- * close_loop. Tests reset this via `__resetCloseLoopStateForTesting`.
38
- */
39
- export declare function dispatchCloseLoopStage(config: DaemonConfig, focus: {
40
- focus_id: string;
41
- focus_name: string;
42
- }): Promise<CloseLoopDecision>;
43
- /**
44
- * Pure helper exported for tests: build the close_loop directive and verify
45
- * it carries the expected assembly + model. The buildCloseLoopDirective
46
- * function in close-loop-dispatcher.ts already does this; this is a
47
- * dispatcher-side smoke export so the tests can grep through it.
48
- */
49
- export declare function getCloseLoopDirectiveForTesting(): StageDirective;
50
- //# sourceMappingURL=close-loop-stage.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"close-loop-stage.d.ts","sourceRoot":"","sources":["../../src/directive/close-loop-stage.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAGtE,OAAO,EAML,KAAK,iBAAiB,EAEvB,MAAM,4BAA4B,CAAC;AA2BpC,qDAAqD;AACrD,wBAAgB,+BAA+B,IAAI,IAAI,CAEtD;AAED;;;;;;;;GAQG;AACH,wBAAsB,uCAAuC,IAAI,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAWpF;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,sBAAsB,CAC1C,MAAM,EAAE,YAAY,EACpB,KAAK,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GAC9C,OAAO,CAAC,iBAAiB,CAAC,CAsK5B;AAED;;;;;GAKG;AACH,wBAAgB,+BAA+B,IAAI,cAAc,CAEhE"}
@@ -1,210 +0,0 @@
1
- /**
2
- * Close-loop stage dispatcher.
3
- *
4
- * Drives the `close_loop` lifecycle stage for a focus: classify anchored
5
- * injections by terminal state, then short-circuit to done, spawn the
6
- * one-shot bookkeeper team, advance with an injection sweep, or hold.
7
- * The pure decision logic lives in close-loop-dispatcher.ts; this module
8
- * is the impure dispatch around it. Extracted from directive-executor.ts;
9
- * the executor re-exports the public surface so existing importers are
10
- * unaffected.
11
- */
12
- import { getActiveTeams } from '../focus/focus-team-state.js';
13
- import { ESCALATION_KINDS } from '@telora/daemon-core';
14
- import { CLOSE_LOOP_MODEL, advanceFocusToDoneWithSweep, buildCloseLoopDirective, decideCloseLoop, getAnchoringDeliveries, } from './close-loop-dispatcher.js';
15
- import { buildWritebackVerify } from '../queries/verification.js';
16
- import { getFocusDeliveries, updateFocusStage } from '../queries/focuses.js';
17
- import { listPendingEscalations } from '../queries/issues.js';
18
- import { callApi } from '../queries/shared.js';
19
- import { terminateTeam, waitForTeamExit } from '../completion/index.js';
20
- import { pendingSpawnDirectives, computeDirectiveHash } from './directive-queue.js';
21
- import { assembleDirectiveContent } from './directive-assembly.js';
22
- import { createLogger } from '../shell/log.js';
23
- const log = createLogger({ module: 'close-loop-dispatcher' });
24
- /**
25
- * Per-focus tracker for whether the close_loop bookkeeper has been spawned
26
- * at least once. Used to avoid respawning the bookkeeper on every poll
27
- * cycle while non-terminal injections are still being worked.
28
- *
29
- * Cleared when the focus leaves close_loop (advances to done or is
30
- * manually reset). The set is in-process state -- on daemon restart it is
31
- * empty, which is fine because the dispatcher's decideCloseLoop function
32
- * will see the bookkeeper has not run, respawn it, and the new bookkeeper
33
- * will sweep idempotently (already-verified injections stay verified;
34
- * already-escalated ones still surface their open escalation).
35
- */
36
- const closeLoopBookkeeperSpawned = new Set();
37
- /** Test helper: clear the bookkeeper-spawned set. */
38
- export function __resetCloseLoopStateForTesting() {
39
- closeLoopBookkeeperSpawned.clear();
40
- }
41
- /**
42
- * Inspect open escalations and return the set of injection ids that have
43
- * an active `injection_unverified` escalation (status pending or
44
- * in_review). Used by the close_loop dispatcher to classify "escalated"
45
- * injections as terminal.
46
- *
47
- * The `metadata.injection_id` field is set by the bookkeeper persona when
48
- * filing the escalation -- see the close_loop directive prompt.
49
- */
50
- export async function getOpenInjectionUnverifiedEscalationIds() {
51
- const escalations = await listPendingEscalations();
52
- const ids = new Set();
53
- for (const e of escalations) {
54
- if (e.escalationKind !== ESCALATION_KINDS.INJECTION_UNVERIFIED)
55
- continue;
56
- const injectionId = e.metadata?.injection_id;
57
- if (typeof injectionId === 'string' && injectionId.length > 0) {
58
- ids.add(injectionId);
59
- }
60
- }
61
- return ids;
62
- }
63
- /**
64
- * Dispatch the close_loop stage for one focus.
65
- *
66
- * Reads the focus's anchored deliveries + injection statuses + open
67
- * escalations, then runs the pure `decideCloseLoop` to determine whether
68
- * to short-circuit, spawn a bookkeeper, advance to done, or hold.
69
- *
70
- * Returns the decision so the caller (or a test) can log/assert.
71
- *
72
- * Idempotent across poll cycles -- the `closeLoopBookkeeperSpawned` set
73
- * ensures the bookkeeper is spawned at most once per focus visit to
74
- * close_loop. Tests reset this via `__resetCloseLoopStateForTesting`.
75
- */
76
- export async function dispatchCloseLoopStage(config, focus) {
77
- const deliveries = await getFocusDeliveries(focus.focus_id);
78
- const anchored = getAnchoringDeliveries(deliveries);
79
- // Build the per-injection terminal-state snapshot.
80
- //
81
- // `statusById` is hoisted to function scope so the close_loop sweep (in
82
- // the advance_to_done branch below) can reuse the snapshot without a
83
- // second round-trip to reality_tree_focus_injections.
84
- const states = [];
85
- const statusById = new Map();
86
- // injectionId -> reality_tree id, captured from the focus-injections snapshot
87
- // so the writeback step can resolve each injection's targeted UDE node ids
88
- // (via reality_tree_edge_list) without a second injection round-trip.
89
- const treeIdByInjection = new Map();
90
- if (anchored.length > 0) {
91
- let openEscalationIds;
92
- try {
93
- openEscalationIds = await getOpenInjectionUnverifiedEscalationIds();
94
- }
95
- catch (err) {
96
- log.warn(`[close-loop-dispatcher] Failed to fetch open injection_unverified escalations ` +
97
- `for focus ${focus.focus_id.slice(0, 8)}: ${err.message}`);
98
- openEscalationIds = new Set();
99
- }
100
- let snapshots = [];
101
- try {
102
- const result = await callApi('reality_tree_focus_injections', { focusId: focus.focus_id });
103
- snapshots = result.items ?? [];
104
- }
105
- catch (err) {
106
- log.warn(`[close-loop-dispatcher] Failed to fetch focus injections for ${focus.focus_id.slice(0, 8)}: ` +
107
- `${err.message}`);
108
- }
109
- for (const item of snapshots) {
110
- statusById.set(item.injection.id, item.injection.injectionStatus);
111
- const treeId = item.injection.realityTreeId;
112
- if (typeof treeId === 'string' && treeId.length > 0) {
113
- treeIdByInjection.set(item.injection.id, treeId);
114
- }
115
- }
116
- for (const delivery of anchored) {
117
- const injectionId = delivery.injectionId;
118
- states.push({
119
- injectionId,
120
- injectionStatus: statusById.get(injectionId) ?? null,
121
- hasOpenUnverifiedEscalation: openEscalationIds.has(injectionId),
122
- });
123
- }
124
- }
125
- const bookkeeperHasRun = closeLoopBookkeeperSpawned.has(focus.focus_id);
126
- const decision = decideCloseLoop(deliveries, states, bookkeeperHasRun);
127
- switch (decision.kind) {
128
- case 'short_circuit_to_done': {
129
- try {
130
- await updateFocusStage(focus.focus_id, 'done');
131
- log.info(`[close-loop-dispatcher] Focus "${focus.focus_name}" has no anchoring injections -- ` +
132
- `short-circuiting close_loop -> done`);
133
- }
134
- catch (err) {
135
- log.warn(`[close-loop-dispatcher] Failed to short-circuit focus ${focus.focus_id.slice(0, 8)} to done: ` +
136
- `${err.message}`);
137
- }
138
- closeLoopBookkeeperSpawned.delete(focus.focus_id);
139
- return decision;
140
- }
141
- case 'advance_to_done': {
142
- // Focus-level injection sweep + stage advance. The helper realizes
143
- // anchored injections whose anchoring deliveries closed successfully
144
- // but whose status hasn't yet flipped to 'verified' (e.g. deliveries
145
- // that exited via review instead of the per-delivery verify gate),
146
- // then advances close_loop -> done. On sweep failure, the helper
147
- // returns early WITHOUT calling updateFocusStage -- the focus stays
148
- // at close_loop and the next poll cycle retries.
149
- const outcome = await advanceFocusToDoneWithSweep(focus, anchored, statusById, { verify: buildWritebackVerify(treeIdByInjection), updateFocusStage });
150
- // Clear the bookkeeper-spawned flag only when the focus has
151
- // terminally exited close_loop -- either advanced, or attempted to
152
- // advance and the stage-update itself failed. A sweep failure leaves
153
- // the flag in place so the same bookkeeper context is reused on the
154
- // next poll cycle's retry.
155
- if (outcome.advanced || outcome.advanceError) {
156
- closeLoopBookkeeperSpawned.delete(focus.focus_id);
157
- }
158
- return decision;
159
- }
160
- case 'spawn_bookkeeper': {
161
- const team = getActiveTeams().get(focus.focus_id);
162
- const worktreePath = team?.worktreePath ?? null;
163
- try {
164
- const message = await assembleDirectiveContent(config, focus.focus_id, decision.directive, worktreePath);
165
- const contentHash = computeDirectiveHash(decision.directive);
166
- pendingSpawnDirectives.set(focus.focus_id, {
167
- message,
168
- model: decision.directive.model ?? CLOSE_LOOP_MODEL,
169
- // The close_loop bookkeeper is a fixed-purpose one-shot session pinned
170
- // to CLOSE_LOOP_MODEL; the focus selector must NOT override it. Null
171
- // override leaves the model slot (CLOSE_LOOP_MODEL) winning.
172
- focusOverrideModel: null,
173
- sessionType: 'coding',
174
- // The close_loop bookkeeper is a distinct one-shot session: its own
175
- // lineage slot, always fresh -- it must not resume (or clobber) the
176
- // coder's context (INJ-B).
177
- lineage: 'close_loop',
178
- continuity: 'fresh',
179
- contentHash,
180
- });
181
- if (team) {
182
- terminateTeam(focus.focus_id, 'handoff');
183
- await waitForTeamExit(focus.focus_id, 30000);
184
- }
185
- closeLoopBookkeeperSpawned.add(focus.focus_id);
186
- log.info(`[close-loop-dispatcher] Spawned bookkeeper team for focus "${focus.focus_name}" ` +
187
- `(${anchored.length} anchored injection(s), model: ${CLOSE_LOOP_MODEL})`);
188
- }
189
- catch (err) {
190
- log.warn(`[close-loop-dispatcher] Failed to spawn bookkeeper for focus ${focus.focus_id.slice(0, 8)}: ` +
191
- `${err.message}`);
192
- }
193
- return decision;
194
- }
195
- case 'hold': {
196
- log.info(`[close-loop-dispatcher] Focus "${focus.focus_name}" held at close_loop: ${decision.reason}`);
197
- return decision;
198
- }
199
- }
200
- }
201
- /**
202
- * Pure helper exported for tests: build the close_loop directive and verify
203
- * it carries the expected assembly + model. The buildCloseLoopDirective
204
- * function in close-loop-dispatcher.ts already does this; this is a
205
- * dispatcher-side smoke export so the tests can grep through it.
206
- */
207
- export function getCloseLoopDirectiveForTesting() {
208
- return buildCloseLoopDirective();
209
- }
210
- //# sourceMappingURL=close-loop-stage.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"close-loop-stage.js","sourceRoot":"","sources":["../../src/directive/close-loop-stage.ts"],"names":[],"mappings":"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"}
@@ -1,133 +0,0 @@
1
- /**
2
- * Focus review-convergence transitions (extracted from focus-stage-lifecycle).
3
- *
4
- * Owns the two review-axis daemon-side transitions:
5
- * verify -> review (Issue 4: auto-enter review on verify-stage entry)
6
- * review -> close_loop (Issue 2 + 3: state-based convergence / nonconvergent
7
- * escalation)
8
- *
9
- * Plus the pure helpers (`hasReviewConverged`, `getReviewIterationCap`,
10
- * `hasInFlightRemediations`) these transitions rely on.
11
- *
12
- * @module focus-stage-lifecycle/review-convergence
13
- */
14
- import type { PipelineConfig } from '../types/index.js';
15
- import { getLatestCompletedFocusReview as defaultGetLatestCompletedFocusReview, getInFlightRemediationCount as defaultGetInFlightRemediationCount, getFocusReviewIterationCount as defaultGetFocusReviewIterationCount, updateFocusStage as defaultUpdateFocusStage } from '../queries/focuses.js';
16
- import { createEscalation as defaultCreateEscalation } from '../queries/issues.js';
17
- import { emitLoopTrigger } from '../loops/index.js';
18
- /**
19
- * Default cap on review iterations before the daemon files a
20
- * `review_nonconvergent` escalation and pauses the focus.
21
- *
22
- * Configurable per focus via `pipeline_config.review.maxIterations`.
23
- */
24
- export declare const DEFAULT_REVIEW_MAX_ITERATIONS = 3;
25
- /**
26
- * Decide whether a focus has converged out of review. Convergence is
27
- * deterministic and state-based:
28
- *
29
- * - If there are no in-flight remediation deliveries created during/after
30
- * the latest review pass, the focus has converged.
31
- *
32
- * Pure function: takes the in-flight count and returns the decision.
33
- * Exported so tests can drive it with synthetic counts.
34
- */
35
- export declare function hasReviewConverged(inFlightRemediationCount: number): boolean;
36
- /**
37
- * Compute the configured review iteration cap for a focus. Pulls
38
- * `pipeline_config.review.maxIterations` and falls back to
39
- * `DEFAULT_REVIEW_MAX_ITERATIONS` when unset or non-positive.
40
- *
41
- * Exported for unit testing.
42
- */
43
- export declare function getReviewIterationCap(pipelineConfig: PipelineConfig | null | undefined): number;
44
- /**
45
- * Returns true when the focus has any in-flight remediation deliveries
46
- * created during/after the latest review pass.
47
- *
48
- * "Latest review pass" is the `started_at` of the most recent focus_reviews
49
- * row; when no review has run yet, this returns false (no remediation
50
- * deliveries can exist before a review pass).
51
- *
52
- * In-flight means execution_status is NOT one of done/cancelled/verify.
53
- *
54
- * Exported for unit testing the convergence query independently of the
55
- * surrounding transition logic.
56
- */
57
- export declare function hasInFlightRemediations(focusId: string, latestReviewPassTimestamp: string | null, deps?: {
58
- getInFlightRemediationCount: typeof defaultGetInFlightRemediationCount;
59
- }): Promise<boolean>;
60
- /**
61
- * Dependencies for `advanceVerifyToReviewIfEnabled`. Defaults reach for the
62
- * real query module; tests can pass stub implementations.
63
- */
64
- export interface VerifyToReviewDeps {
65
- updateFocusStage: typeof defaultUpdateFocusStage;
66
- emitLoopTrigger: typeof emitLoopTrigger;
67
- }
68
- /**
69
- * When a focus's stage is `verify` and auto-review is enabled, advance it to
70
- * `review`. Auto-review is default-on (`isReviewEnabled`): only an explicit
71
- * `pipeline_config.review.enabled === false` keeps the focus at `verify` until
72
- * a human signals review. A focus with no stored review key auto-advances.
73
- *
74
- * Returns true when an advance was performed.
75
- */
76
- export declare function advanceVerifyToReviewIfEnabled(focus: {
77
- focusId: string;
78
- focusName: string;
79
- stage: string;
80
- pipelineConfig: PipelineConfig | null;
81
- }, deps?: VerifyToReviewDeps): Promise<boolean>;
82
- /**
83
- * Dependencies for `advanceReviewToCloseLoopIfConverged`. Defaults reach for
84
- * the real query modules; tests can pass stub implementations.
85
- */
86
- export interface ReviewConvergeDeps {
87
- getLatestCompletedFocusReview: typeof defaultGetLatestCompletedFocusReview;
88
- getInFlightRemediationCount: typeof defaultGetInFlightRemediationCount;
89
- getFocusReviewIterationCount: typeof defaultGetFocusReviewIterationCount;
90
- updateFocusStage: typeof defaultUpdateFocusStage;
91
- createEscalation: typeof defaultCreateEscalation;
92
- emitLoopTrigger: typeof emitLoopTrigger;
93
- }
94
- /**
95
- * Outcome of an attempt to advance a focus past the review stage.
96
- */
97
- export type ReviewTransitionOutcome = {
98
- kind: 'no_action';
99
- reason: string;
100
- } | {
101
- kind: 'converged';
102
- from: 'review';
103
- to: 'close_loop';
104
- } | {
105
- kind: 'nonconvergent';
106
- iterations: number;
107
- cap: number;
108
- };
109
- /**
110
- * Evaluate the review -> close_loop transition for a focus and apply the
111
- * appropriate action. Returns a structured outcome so callers can log /
112
- * test the decision path.
113
- *
114
- * Logic:
115
- * 1. If stage is not `review`, do nothing.
116
- * 2. If the review iteration count is >= the cap, file a
117
- * `review_nonconvergent` escalation (idempotent best-effort) and
118
- * LEAVE the focus stage at `review`. The focus is paused until a
119
- * human intervenes.
120
- * 3. Otherwise, check in-flight remediations against the latest review
121
- * pass. If zero, advance stage `review -> close_loop`.
122
- * 4. Otherwise, do nothing -- remediations are still in flight.
123
- *
124
- * Exported with injectable deps for unit testing.
125
- */
126
- export declare function advanceReviewToCloseLoopIfConverged(focus: {
127
- focusId: string;
128
- focusName: string;
129
- organizationId: string;
130
- stage: string;
131
- pipelineConfig: PipelineConfig | null;
132
- }, deps?: ReviewConvergeDeps): Promise<ReviewTransitionOutcome>;
133
- //# sourceMappingURL=review-convergence.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"review-convergence.d.ts","sourceRoot":"","sources":["../../src/focus-stage-lifecycle/review-convergence.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EACL,6BAA6B,IAAI,oCAAoC,EACrE,2BAA2B,IAAI,kCAAkC,EACjE,4BAA4B,IAAI,mCAAmC,EACnE,gBAAgB,IAAI,uBAAuB,EAC5C,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,gBAAgB,IAAI,uBAAuB,EAAE,MAAM,sBAAsB,CAAC;AAOnF,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AASpD;;;;;GAKG;AACH,eAAO,MAAM,6BAA6B,IAAI,CAAC;AAI/C;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAAC,wBAAwB,EAAE,MAAM,GAAG,OAAO,CAE5E;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CACnC,cAAc,EAAE,cAAc,GAAG,IAAI,GAAG,SAAS,GAChD,MAAM,CAMR;AAID;;;;;;;;;;;;GAYG;AACH,wBAAsB,uBAAuB,CAC3C,OAAO,EAAE,MAAM,EACf,yBAAyB,EAAE,MAAM,GAAG,IAAI,EACxC,IAAI,GAAE;IACJ,2BAA2B,EAAE,OAAO,kCAAkC,CAAC;CACF,GACtE,OAAO,CAAC,OAAO,CAAC,CASlB;AAID;;;GAGG;AACH,MAAM,WAAW,kBAAkB;IACjC,gBAAgB,EAAE,OAAO,uBAAuB,CAAC;IACjD,eAAe,EAAE,OAAO,eAAe,CAAC;CACzC;AAOD;;;;;;;GAOG;AACH,wBAAsB,8BAA8B,CAClD,KAAK,EAAE;IACL,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,cAAc,EAAE,cAAc,GAAG,IAAI,CAAC;CACvC,EACD,IAAI,GAAE,kBAA8C,GACnD,OAAO,CAAC,OAAO,CAAC,CAyBlB;AAID;;;GAGG;AACH,MAAM,WAAW,kBAAkB;IACjC,6BAA6B,EAAE,OAAO,oCAAoC,CAAC;IAC3E,2BAA2B,EAAE,OAAO,kCAAkC,CAAC;IACvE,4BAA4B,EAAE,OAAO,mCAAmC,CAAC;IACzE,gBAAgB,EAAE,OAAO,uBAAuB,CAAC;IACjD,gBAAgB,EAAE,OAAO,uBAAuB,CAAC;IACjD,eAAe,EAAE,OAAO,eAAe,CAAC;CACzC;AAWD;;GAEG;AACH,MAAM,MAAM,uBAAuB,GAC/B;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACrC;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,IAAI,EAAE,QAAQ,CAAC;IAAC,EAAE,EAAE,YAAY,CAAA;CAAE,GACvD;IAAE,IAAI,EAAE,eAAe,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC;AAE/D;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,mCAAmC,CACvD,KAAK,EAAE;IACL,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,cAAc,EAAE,cAAc,GAAG,IAAI,CAAC;CACvC,EACD,IAAI,GAAE,kBAA8C,GACnD,OAAO,CAAC,uBAAuB,CAAC,CAqHlC"}
@@ -1,237 +0,0 @@
1
- /**
2
- * Focus review-convergence transitions (extracted from focus-stage-lifecycle).
3
- *
4
- * Owns the two review-axis daemon-side transitions:
5
- * verify -> review (Issue 4: auto-enter review on verify-stage entry)
6
- * review -> close_loop (Issue 2 + 3: state-based convergence / nonconvergent
7
- * escalation)
8
- *
9
- * Plus the pure helpers (`hasReviewConverged`, `getReviewIterationCap`,
10
- * `hasInFlightRemediations`) these transitions rely on.
11
- *
12
- * @module focus-stage-lifecycle/review-convergence
13
- */
14
- import { getLatestCompletedFocusReview as defaultGetLatestCompletedFocusReview, getInFlightRemediationCount as defaultGetInFlightRemediationCount, getFocusReviewIterationCount as defaultGetFocusReviewIterationCount, updateFocusStage as defaultUpdateFocusStage, } from '../queries/focuses.js';
15
- import { createEscalation as defaultCreateEscalation } from '../queries/issues.js';
16
- import { ESCALATION_REASONS, ESCALATION_KINDS, ESCALATION_DISPOSITIONS, FOCUS_STAGE, } from '@telora/daemon-core';
17
- import { emitLoopTrigger } from '../loops/index.js';
18
- import { isReviewEnabled } from '../workflow/pipeline-config.js';
19
- import { createLogger } from '../shell/log.js';
20
- const log = createLogger({ module: 'focus-stage-lifecycle' });
21
- // ── Constants ─────────────────────────────────────────────────────
22
- /**
23
- * Default cap on review iterations before the daemon files a
24
- * `review_nonconvergent` escalation and pauses the focus.
25
- *
26
- * Configurable per focus via `pipeline_config.review.maxIterations`.
27
- */
28
- export const DEFAULT_REVIEW_MAX_ITERATIONS = 3;
29
- // ── Pure helpers (Issue 2 + Issue 3) ──────────────────────────────
30
- /**
31
- * Decide whether a focus has converged out of review. Convergence is
32
- * deterministic and state-based:
33
- *
34
- * - If there are no in-flight remediation deliveries created during/after
35
- * the latest review pass, the focus has converged.
36
- *
37
- * Pure function: takes the in-flight count and returns the decision.
38
- * Exported so tests can drive it with synthetic counts.
39
- */
40
- export function hasReviewConverged(inFlightRemediationCount) {
41
- return inFlightRemediationCount <= 0;
42
- }
43
- /**
44
- * Compute the configured review iteration cap for a focus. Pulls
45
- * `pipeline_config.review.maxIterations` and falls back to
46
- * `DEFAULT_REVIEW_MAX_ITERATIONS` when unset or non-positive.
47
- *
48
- * Exported for unit testing.
49
- */
50
- export function getReviewIterationCap(pipelineConfig) {
51
- const raw = pipelineConfig?.review?.maxIterations;
52
- if (typeof raw !== 'number' || !Number.isFinite(raw) || raw <= 0) {
53
- return DEFAULT_REVIEW_MAX_ITERATIONS;
54
- }
55
- return Math.floor(raw);
56
- }
57
- // ── Convergence check (Issue 2) ───────────────────────────────────
58
- /**
59
- * Returns true when the focus has any in-flight remediation deliveries
60
- * created during/after the latest review pass.
61
- *
62
- * "Latest review pass" is the `started_at` of the most recent focus_reviews
63
- * row; when no review has run yet, this returns false (no remediation
64
- * deliveries can exist before a review pass).
65
- *
66
- * In-flight means execution_status is NOT one of done/cancelled/verify.
67
- *
68
- * Exported for unit testing the convergence query independently of the
69
- * surrounding transition logic.
70
- */
71
- export async function hasInFlightRemediations(focusId, latestReviewPassTimestamp, deps = { getInFlightRemediationCount: defaultGetInFlightRemediationCount }) {
72
- if (!latestReviewPassTimestamp) {
73
- return false;
74
- }
75
- const count = await deps.getInFlightRemediationCount(focusId, latestReviewPassTimestamp);
76
- return count > 0;
77
- }
78
- const defaultVerifyToReviewDeps = {
79
- updateFocusStage: defaultUpdateFocusStage,
80
- emitLoopTrigger,
81
- };
82
- /**
83
- * When a focus's stage is `verify` and auto-review is enabled, advance it to
84
- * `review`. Auto-review is default-on (`isReviewEnabled`): only an explicit
85
- * `pipeline_config.review.enabled === false` keeps the focus at `verify` until
86
- * a human signals review. A focus with no stored review key auto-advances.
87
- *
88
- * Returns true when an advance was performed.
89
- */
90
- export async function advanceVerifyToReviewIfEnabled(focus, deps = defaultVerifyToReviewDeps) {
91
- if (focus.stage !== FOCUS_STAGE.VERIFY)
92
- return false;
93
- // Default-on: only an explicit review.enabled === false keeps the focus at
94
- // verify. Missing / undefined review reads as enabled (mirrors isCiEnabled).
95
- if (!isReviewEnabled(focus.pipelineConfig))
96
- return false;
97
- try {
98
- await deps.updateFocusStage(focus.focusId, 'review');
99
- log.info(`[focus-stage-lifecycle] Focus "${focus.focusName}" stage verify -> review ` +
100
- `(auto: pipeline_config.review.enabled)`);
101
- deps.emitLoopTrigger({
102
- type: 'focus_status_changed',
103
- focusId: focus.focusId,
104
- detail: 'stage:verify->review',
105
- });
106
- return true;
107
- }
108
- catch (err) {
109
- log.warn(`[focus-stage-lifecycle] Failed to advance "${focus.focusName}" ` +
110
- `verify -> review: ${err.message}`);
111
- return false;
112
- }
113
- }
114
- const defaultReviewConvergeDeps = {
115
- getLatestCompletedFocusReview: defaultGetLatestCompletedFocusReview,
116
- getInFlightRemediationCount: defaultGetInFlightRemediationCount,
117
- getFocusReviewIterationCount: defaultGetFocusReviewIterationCount,
118
- updateFocusStage: defaultUpdateFocusStage,
119
- createEscalation: defaultCreateEscalation,
120
- emitLoopTrigger,
121
- };
122
- /**
123
- * Evaluate the review -> close_loop transition for a focus and apply the
124
- * appropriate action. Returns a structured outcome so callers can log /
125
- * test the decision path.
126
- *
127
- * Logic:
128
- * 1. If stage is not `review`, do nothing.
129
- * 2. If the review iteration count is >= the cap, file a
130
- * `review_nonconvergent` escalation (idempotent best-effort) and
131
- * LEAVE the focus stage at `review`. The focus is paused until a
132
- * human intervenes.
133
- * 3. Otherwise, check in-flight remediations against the latest review
134
- * pass. If zero, advance stage `review -> close_loop`.
135
- * 4. Otherwise, do nothing -- remediations are still in flight.
136
- *
137
- * Exported with injectable deps for unit testing.
138
- */
139
- export async function advanceReviewToCloseLoopIfConverged(focus, deps = defaultReviewConvergeDeps) {
140
- if (focus.stage !== FOCUS_STAGE.REVIEW) {
141
- return { kind: 'no_action', reason: 'stage_not_review' };
142
- }
143
- const cap = getReviewIterationCap(focus.pipelineConfig);
144
- let iterations = 0;
145
- try {
146
- iterations = await deps.getFocusReviewIterationCount(focus.focusId);
147
- }
148
- catch (err) {
149
- log.warn(`[focus-stage-lifecycle] Failed to read review iteration count for ` +
150
- `"${focus.focusName}": ${err.message}`);
151
- // On read failure, fall through to the convergence check rather than
152
- // false-escalating; if iterations were actually high the next tick
153
- // will catch it.
154
- }
155
- if (iterations >= cap) {
156
- // N-iteration cap exceeded -- file an escalation and leave the focus
157
- // at review. The escalation is best-effort; downstream dedup keeps it
158
- // from spamming via the agent_escalations idx_kind_status partial index
159
- // (status in ('pending', 'in_review')).
160
- try {
161
- await deps.createEscalation({
162
- organizationId: focus.organizationId,
163
- sessionId: '',
164
- issueId: null,
165
- reasonType: ESCALATION_REASONS.DECISION_NEEDED,
166
- // The intent owner must decide: descope, extend the cap, or close.
167
- disposition: ESCALATION_DISPOSITIONS.NEEDS_INTENT,
168
- anchor: { focusId: focus.focusId },
169
- description: `Focus "${focus.focusName}" has not converged after ${iterations} ` +
170
- `review iteration(s) (cap: ${cap}). The review self-loop has been ` +
171
- `paused; a human must decide whether to descope remaining gaps, ` +
172
- `extend the cap via pipeline_config.review.maxIterations, or close ` +
173
- `the focus.`,
174
- escalationKind: ESCALATION_KINDS.REVIEW_NONCONVERGENT,
175
- // Evidence only -- focus provenance lives on the columns via the anchor.
176
- metadata: {
177
- iterations,
178
- cap,
179
- },
180
- });
181
- log.warn(`[focus-stage-lifecycle] review_nonconvergent escalation filed for ` +
182
- `"${focus.focusName}" (iterations=${iterations}, cap=${cap}); focus ` +
183
- `remains at stage=review.`);
184
- }
185
- catch (err) {
186
- log.warn(`[focus-stage-lifecycle] Failed to file review_nonconvergent ` +
187
- `escalation for "${focus.focusName}": ${err.message}`);
188
- }
189
- return { kind: 'nonconvergent', iterations, cap };
190
- }
191
- // Look up the latest completed review's started_at. If no review has
192
- // completed yet, there are no remediations created against it; treat as
193
- // "not yet converged" rather than "converged on zero" -- the agent must
194
- // produce a review before we can claim convergence.
195
- let latestReviewStartedAt = null;
196
- try {
197
- const latest = await deps.getLatestCompletedFocusReview(focus.focusId);
198
- latestReviewStartedAt = latest?.startedAt ?? null;
199
- }
200
- catch (err) {
201
- log.warn(`[focus-stage-lifecycle] Failed to read latest focus_review for ` +
202
- `"${focus.focusName}": ${err.message}`);
203
- return { kind: 'no_action', reason: 'review_read_failed' };
204
- }
205
- if (!latestReviewStartedAt) {
206
- return { kind: 'no_action', reason: 'no_completed_review_yet' };
207
- }
208
- let inFlightCount = 0;
209
- try {
210
- inFlightCount = await deps.getInFlightRemediationCount(focus.focusId, latestReviewStartedAt);
211
- }
212
- catch (err) {
213
- log.warn(`[focus-stage-lifecycle] Failed to count in-flight remediations for ` +
214
- `"${focus.focusName}": ${err.message}`);
215
- return { kind: 'no_action', reason: 'remediation_query_failed' };
216
- }
217
- if (!hasReviewConverged(inFlightCount)) {
218
- return { kind: 'no_action', reason: 'remediations_in_flight' };
219
- }
220
- try {
221
- await deps.updateFocusStage(focus.focusId, 'close_loop');
222
- log.info(`[focus-stage-lifecycle] Focus "${focus.focusName}" stage review -> ` +
223
- `close_loop (converged after ${iterations} iteration(s))`);
224
- deps.emitLoopTrigger({
225
- type: 'focus_status_changed',
226
- focusId: focus.focusId,
227
- detail: 'stage:review->close_loop',
228
- });
229
- return { kind: 'converged', from: 'review', to: 'close_loop' };
230
- }
231
- catch (err) {
232
- log.warn(`[focus-stage-lifecycle] Failed to advance "${focus.focusName}" ` +
233
- `review -> close_loop: ${err.message}`);
234
- return { kind: 'no_action', reason: 'stage_write_failed' };
235
- }
236
- }
237
- //# sourceMappingURL=review-convergence.js.map