@telora/daemon 0.22.208 → 0.22.227
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.
- package/build-info.json +4 -3
- package/dist/backends/index.d.ts.map +1 -1
- package/dist/backends/index.js +3 -1
- package/dist/backends/index.js.map +1 -1
- package/dist/completion/advancement-stall-watchdog.d.ts.map +1 -1
- package/dist/completion/advancement-stall-watchdog.js +6 -2
- package/dist/completion/advancement-stall-watchdog.js.map +1 -1
- package/dist/completion/completion-decision.d.ts +10 -1
- package/dist/completion/completion-decision.d.ts.map +1 -1
- package/dist/completion/completion-decision.js +1 -1
- package/dist/completion/completion-decision.js.map +1 -1
- package/dist/completion/delivery-advancement-guard.d.ts +31 -16
- package/dist/completion/delivery-advancement-guard.d.ts.map +1 -1
- package/dist/completion/delivery-advancement-guard.js +22 -37
- package/dist/completion/delivery-advancement-guard.js.map +1 -1
- package/dist/completion/detector.d.ts.map +1 -1
- package/dist/completion/detector.js +3 -1
- package/dist/completion/detector.js.map +1 -1
- package/dist/completion/event-phases.d.ts +19 -8
- package/dist/completion/event-phases.d.ts.map +1 -1
- package/dist/completion/event-phases.js +67 -88
- package/dist/completion/event-phases.js.map +1 -1
- package/dist/completion/event.d.ts +12 -9
- package/dist/completion/event.d.ts.map +1 -1
- package/dist/completion/event.js +6 -3
- package/dist/completion/event.js.map +1 -1
- package/dist/completion/index.js +1 -1
- package/dist/completion/progress-snapshot.d.ts +13 -9
- package/dist/completion/progress-snapshot.d.ts.map +1 -1
- package/dist/completion/progress-snapshot.js +16 -13
- package/dist/completion/progress-snapshot.js.map +1 -1
- package/dist/completion/reengagement-redrive.d.ts +8 -1
- package/dist/completion/reengagement-redrive.d.ts.map +1 -1
- package/dist/completion/reengagement-redrive.js +16 -2
- package/dist/completion/reengagement-redrive.js.map +1 -1
- package/dist/completion/review-defect-detector.d.ts +58 -18
- package/dist/completion/review-defect-detector.d.ts.map +1 -1
- package/dist/completion/review-defect-detector.js +62 -27
- package/dist/completion/review-defect-detector.js.map +1 -1
- package/dist/completion/review-exit-attribution.d.ts +6 -3
- package/dist/completion/review-exit-attribution.d.ts.map +1 -1
- package/dist/completion/review-exit-attribution.js +9 -7
- package/dist/completion/review-exit-attribution.js.map +1 -1
- package/dist/completion/review-exit-deps.d.ts +2 -0
- package/dist/completion/review-exit-deps.d.ts.map +1 -1
- package/dist/completion/review-exit-deps.js +3 -0
- package/dist/completion/review-exit-deps.js.map +1 -1
- package/dist/completion/review-exit-evidence.d.ts +8 -0
- package/dist/completion/review-exit-evidence.d.ts.map +1 -1
- package/dist/completion/review-exit-evidence.js +39 -27
- package/dist/completion/review-exit-evidence.js.map +1 -1
- package/dist/completion/review-exit-foreign-findings.d.ts +5 -1
- package/dist/completion/review-exit-foreign-findings.d.ts.map +1 -1
- package/dist/completion/review-exit-foreign-findings.js +4 -4
- package/dist/completion/review-exit-foreign-findings.js.map +1 -1
- package/dist/completion/review-exit-phase.js +4 -4
- package/dist/completion/review-exit-phase.js.map +1 -1
- package/dist/completion/state-cascade.d.ts.map +1 -1
- package/dist/completion/state-cascade.js +11 -19
- package/dist/completion/state-cascade.js.map +1 -1
- package/dist/completion/team-progress-measure.d.ts +70 -0
- package/dist/completion/team-progress-measure.d.ts.map +1 -0
- package/dist/completion/team-progress-measure.js +285 -0
- package/dist/completion/team-progress-measure.js.map +1 -0
- package/dist/completion/team-stillness.d.ts +76 -0
- package/dist/completion/team-stillness.d.ts.map +1 -0
- package/dist/completion/team-stillness.js +96 -0
- package/dist/completion/team-stillness.js.map +1 -0
- package/dist/completion/worker-wait.d.ts +84 -0
- package/dist/completion/worker-wait.d.ts.map +1 -0
- package/dist/completion/worker-wait.js +89 -0
- package/dist/completion/worker-wait.js.map +1 -0
- package/dist/delivery/delivery-advance-block.d.ts.map +1 -1
- package/dist/delivery/delivery-advance-block.js +1 -2
- package/dist/delivery/delivery-advance-block.js.map +1 -1
- package/dist/delivery/delivery-reconciler.d.ts +3 -3
- package/dist/focus/focus-lifecycle.d.ts.map +1 -1
- package/dist/focus/focus-lifecycle.js +7 -1
- package/dist/focus/focus-lifecycle.js.map +1 -1
- package/dist/focus/focus-team-state.js +1 -1
- package/dist/focus/focus-worktree-state.d.ts.map +1 -1
- package/dist/focus/focus-worktree-state.js +2 -1
- package/dist/focus/focus-worktree-state.js.map +1 -1
- package/dist/git/focus-integration-sync.d.ts +42 -4
- package/dist/git/focus-integration-sync.d.ts.map +1 -1
- package/dist/git/focus-integration-sync.js +53 -9
- package/dist/git/focus-integration-sync.js.map +1 -1
- package/dist/git/types.d.ts.map +1 -1
- package/dist/git/types.js +4 -2
- package/dist/git/types.js.map +1 -1
- package/dist/merge/merge-back-loop.d.ts.map +1 -1
- package/dist/merge/merge-back-loop.js +0 -1
- package/dist/merge/merge-back-loop.js.map +1 -1
- package/dist/prompt-sections/persona-sections.d.ts +14 -0
- package/dist/prompt-sections/persona-sections.d.ts.map +1 -1
- package/dist/prompt-sections/persona-sections.js +50 -19
- package/dist/prompt-sections/persona-sections.js.map +1 -1
- package/dist/prompt-sections/testing-standard.d.ts +4 -1
- package/dist/prompt-sections/testing-standard.d.ts.map +1 -1
- package/dist/prompt-sections/testing-standard.js +14 -4
- package/dist/prompt-sections/testing-standard.js.map +1 -1
- package/dist/queries/deliveries.d.ts.map +1 -1
- package/dist/queries/deliveries.js +3 -1
- package/dist/queries/deliveries.js.map +1 -1
- package/dist/queries/delivery-gate-state.js +1 -1
- package/dist/queries/escalation-suppression.js +1 -1
- package/dist/queries/escalations.js +1 -1
- package/dist/queries/focus-integration-conflict.d.ts +7 -4
- package/dist/queries/focus-integration-conflict.d.ts.map +1 -1
- package/dist/queries/focus-integration-conflict.js +6 -3
- package/dist/queries/focus-integration-conflict.js.map +1 -1
- package/dist/queries/review-finding-facts.d.ts +38 -0
- package/dist/queries/review-finding-facts.d.ts.map +1 -0
- package/dist/queries/review-finding-facts.js +43 -0
- package/dist/queries/review-finding-facts.js.map +1 -0
- package/dist/queries/schemas.d.ts +15 -0
- package/dist/queries/schemas.d.ts.map +1 -1
- package/dist/queries/schemas.js +12 -1
- package/dist/queries/schemas.js.map +1 -1
- package/dist/queries/shortfall-park-record.d.ts +6 -1
- package/dist/queries/shortfall-park-record.d.ts.map +1 -1
- package/dist/queries/shortfall-park-record.js.map +1 -1
- package/dist/queries/shortfall-rulings.d.ts +14 -2
- package/dist/queries/shortfall-rulings.d.ts.map +1 -1
- package/dist/queries/shortfall-rulings.js +17 -3
- package/dist/queries/shortfall-rulings.js.map +1 -1
- package/dist/queries/verification.js +1 -1
- package/dist/queries/verify-own-holds.d.ts.map +1 -1
- package/dist/queries/verify-own-holds.js +1 -0
- package/dist/queries/verify-own-holds.js.map +1 -1
- package/dist/spawn/helpers.d.ts.map +1 -1
- package/dist/spawn/helpers.js +9 -1
- package/dist/spawn/helpers.js.map +1 -1
- package/dist/spawn/lead-continuation.d.ts +10 -2
- package/dist/spawn/lead-continuation.d.ts.map +1 -1
- package/dist/spawn/lead-continuation.js +39 -22
- package/dist/spawn/lead-continuation.js.map +1 -1
- package/dist/spawn/one-shot-model.d.ts.map +1 -1
- package/dist/spawn/one-shot-model.js +1 -0
- package/dist/spawn/one-shot-model.js.map +1 -1
- package/dist/spawn/worktree-session.d.ts.map +1 -1
- package/dist/spawn/worktree-session.js +11 -1
- package/dist/spawn/worktree-session.js.map +1 -1
- package/dist/testing/fixtures/conflicting-repo.d.ts +17 -0
- package/dist/testing/fixtures/conflicting-repo.d.ts.map +1 -1
- package/dist/testing/fixtures/conflicting-repo.js +31 -0
- package/dist/testing/fixtures/conflicting-repo.js.map +1 -1
- package/dist/testing/fixtures/index.d.ts.map +1 -1
- package/dist/testing/fixtures/index.js +1 -2
- package/dist/testing/fixtures/index.js.map +1 -1
- package/dist/types/focus.d.ts +59 -26
- package/dist/types/focus.d.ts.map +1 -1
- package/dist/types/index.js +1 -1
- package/dist/verify/commit-verdict-pull.d.ts +28 -3
- package/dist/verify/commit-verdict-pull.d.ts.map +1 -1
- package/dist/verify/commit-verdict-pull.js +160 -23
- package/dist/verify/commit-verdict-pull.js.map +1 -1
- package/dist/verify/credit-rearm.d.ts.map +1 -1
- package/dist/verify/credit-rearm.js +72 -17
- package/dist/verify/credit-rearm.js.map +1 -1
- package/dist/verify/default-on-guards.d.ts +54 -8
- package/dist/verify/default-on-guards.d.ts.map +1 -1
- package/dist/verify/default-on-guards.js +132 -17
- package/dist/verify/default-on-guards.js.map +1 -1
- package/dist/verify/explicit-rearm.d.ts.map +1 -1
- package/dist/verify/explicit-rearm.js +1 -0
- package/dist/verify/explicit-rearm.js.map +1 -1
- package/dist/verify/explicit-row-selection.d.ts +47 -0
- package/dist/verify/explicit-row-selection.d.ts.map +1 -0
- package/dist/verify/explicit-row-selection.js +37 -0
- package/dist/verify/explicit-row-selection.js.map +1 -0
- package/dist/verify/gate-run-registry.d.ts.map +1 -1
- package/dist/verify/gate-run-registry.js +2 -0
- package/dist/verify/gate-run-registry.js.map +1 -1
- package/dist/verify/parked-shortfall-reuse.d.ts +194 -0
- package/dist/verify/parked-shortfall-reuse.d.ts.map +1 -0
- package/dist/verify/parked-shortfall-reuse.js +310 -0
- package/dist/verify/parked-shortfall-reuse.js.map +1 -0
- package/dist/verify/reselection-log.d.ts +22 -0
- package/dist/verify/reselection-log.d.ts.map +1 -0
- package/dist/verify/reselection-log.js +87 -0
- package/dist/verify/reselection-log.js.map +1 -0
- package/dist/verify/shortfall-park.d.ts +8 -1
- package/dist/verify/shortfall-park.d.ts.map +1 -1
- package/dist/verify/shortfall-park.js +17 -3
- package/dist/verify/shortfall-park.js.map +1 -1
- package/dist/verify/shortfall-repeat.d.ts.map +1 -1
- package/dist/verify/shortfall-repeat.js +7 -2
- package/dist/verify/shortfall-repeat.js.map +1 -1
- package/dist/verify/shortfall-rulings.d.ts +93 -6
- package/dist/verify/shortfall-rulings.d.ts.map +1 -1
- package/dist/verify/shortfall-rulings.js +69 -12
- package/dist/verify/shortfall-rulings.js.map +1 -1
- package/dist/verify/stall-detector-config.d.ts.map +1 -1
- package/dist/verify/stall-detector-config.js +2 -1
- package/dist/verify/stall-detector-config.js.map +1 -1
- package/dist/verify/stall-detectors.d.ts +6 -4
- package/dist/verify/stall-detectors.d.ts.map +1 -1
- package/dist/verify/stall-detectors.js +9 -6
- package/dist/verify/stall-detectors.js.map +1 -1
- package/dist/verify/stalled-team-backstop.d.ts +8 -2
- package/dist/verify/stalled-team-backstop.d.ts.map +1 -1
- package/dist/verify/stalled-team-backstop.js +15 -11
- package/dist/verify/stalled-team-backstop.js.map +1 -1
- package/dist/verify/team-stall-reapers.d.ts +28 -0
- package/dist/verify/team-stall-reapers.d.ts.map +1 -0
- package/dist/verify/team-stall-reapers.js +23 -0
- package/dist/verify/team-stall-reapers.js.map +1 -0
- package/dist/verify/verification-attribution-deps.d.ts +1 -1
- package/dist/verify/verification-attribution-deps.d.ts.map +1 -1
- package/dist/verify/verification-attribution-deps.js +20 -1
- package/dist/verify/verification-attribution-deps.js.map +1 -1
- package/dist/verify/verification-boundary-env.d.ts.map +1 -1
- package/dist/verify/verification-boundary-env.js +23 -9
- package/dist/verify/verification-boundary-env.js.map +1 -1
- package/dist/verify/verification-checkout.d.ts +1 -1
- package/dist/verify/verification-checkout.d.ts.map +1 -1
- package/dist/verify/verification-checkout.js.map +1 -1
- package/dist/verify/verification-commit-sharing.d.ts.map +1 -1
- package/dist/verify/verification-commit-sharing.js +14 -3
- package/dist/verify/verification-commit-sharing.js.map +1 -1
- package/dist/verify/verification-config.d.ts.map +1 -1
- package/dist/verify/verification-config.js +1 -20
- package/dist/verify/verification-config.js.map +1 -1
- package/dist/verify/verification-default-on.d.ts +4 -1
- package/dist/verify/verification-default-on.d.ts.map +1 -1
- package/dist/verify/verification-default-on.js +29 -2
- package/dist/verify/verification-default-on.js.map +1 -1
- package/dist/verify/verification-deps-default.d.ts.map +1 -1
- package/dist/verify/verification-deps-default.js +13 -0
- package/dist/verify/verification-deps-default.js.map +1 -1
- package/dist/verify/verification-deps.d.ts +38 -0
- package/dist/verify/verification-deps.d.ts.map +1 -1
- package/dist/verify/verification-deps.js +2 -1
- package/dist/verify/verification-deps.js.map +1 -1
- package/dist/verify/verification-engine.d.ts.map +1 -1
- package/dist/verify/verification-engine.js +44 -15
- package/dist/verify/verification-engine.js.map +1 -1
- package/dist/verify/verification-feedback.d.ts.map +1 -1
- package/dist/verify/verification-feedback.js +1 -19
- package/dist/verify/verification-feedback.js.map +1 -1
- package/dist/verify/verify-restatement.d.ts.map +1 -1
- package/dist/verify/verify-restatement.js +5 -1
- package/dist/verify/verify-restatement.js.map +1 -1
- package/dist/worktree/provision.d.ts.map +1 -1
- package/dist/worktree/provision.js +3 -1
- package/dist/worktree/provision.js.map +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
// PINNED-BY-BEGIN (generated by gen:pinned-by -- do not hand-edit; checked by lint:pinned-by)
|
|
2
|
+
// Pinned by: packages/daemon/src/completion/team-progress-measure.test.ts,
|
|
3
|
+
// packages/daemon/src/spawn/progress-measure-wiring.test.ts,
|
|
4
|
+
// packages/daemon/src/verify/team-stall-reapers.test.ts
|
|
5
|
+
// PINNED-BY-END
|
|
6
|
+
/**
|
|
7
|
+
* The ONE measure of whether a focus team made progress.
|
|
8
|
+
*
|
|
9
|
+
* Every no-progress decision that can end a team reads it: the scope-coverage
|
|
10
|
+
* counter (completion/event-phases.ts, through progress-snapshot.ts), the
|
|
11
|
+
* cross-respawn delivery-advancement guard (delivery-advancement-guard.ts), the
|
|
12
|
+
* continuation budget (spawn/lead-continuation.ts) and the unproductive-handoff
|
|
13
|
+
* backstop (event-phases.ts). A cycle made progress when ANY of these moved since
|
|
14
|
+
* the previous one:
|
|
15
|
+
*
|
|
16
|
+
* - a delivery execution_status (or the delivery set);
|
|
17
|
+
* - the issue fingerprint ({issueId -> status});
|
|
18
|
+
* - the focus worktree HEAD (a commit);
|
|
19
|
+
* - the team's agent activity (TeamActivityMarks.agentActivitySeq);
|
|
20
|
+
* - the worktree's uncommitted diff (TeamActivityMarks.worktreeDiffFingerprint).
|
|
21
|
+
*
|
|
22
|
+
* WHY the last two (2026-09-24, "A ruling reaches the gate it rules on"): the
|
|
23
|
+
* lead parked while its background workers edited, the daemon re-woke it every
|
|
24
|
+
* ~20s, and each wake returned in seconds as a completion cycle. The watchdogs
|
|
25
|
+
* counted only status, issues and HEAD, so uncommitted edits and live workers
|
|
26
|
+
* read as zero progress and the team was killed as team_failure ~3 minutes in,
|
|
27
|
+
* with the log reading "last agent event ~0m ago".
|
|
28
|
+
*
|
|
29
|
+
* WHICH agent events count. Worker events always count: a background agent's
|
|
30
|
+
* `system/task_*` events (keyed by a task the lead started with a backgrounded
|
|
31
|
+
* or agent task type) and a teammate's subagent_activity. The lead's own events
|
|
32
|
+
* count only on a turn the daemon did NOT start by re-waking it (a mid-focus
|
|
33
|
+
* handoff, a re-engagement, a continuation turn -- noteDaemonPoke), and never on
|
|
34
|
+
* its kickoff turn. A lead that answers each re-wake with a few tool calls and
|
|
35
|
+
* changes nothing is exactly the team the no-progress guards exist to end, so its
|
|
36
|
+
* replies to the daemon are not activity; whatever such a reply CHANGES is still
|
|
37
|
+
* seen by the diff, HEAD, issue and status axes. A heartbeat (one long tool call
|
|
38
|
+
* still running) counts only while the team has a worker that is live when it
|
|
39
|
+
* arrives: it is then the signal that keeps the worker live, and liveness and
|
|
40
|
+
* progress must not disagree -- a worker the daemon waits on is never reaped as
|
|
41
|
+
* still (TEL-8). A heartbeat with no live worker -- none started, or one that
|
|
42
|
+
* went quiet -- is the lead's own call: never progress, and it does not revive
|
|
43
|
+
* a quiet worker (TEL-9), or a lead's heartbeats would keep an idle team alive.
|
|
44
|
+
*
|
|
45
|
+
* The activity counter is per FOCUS, not per team, so it survives a respawn and
|
|
46
|
+
* the cross-respawn guard can compare it; a fresh counter after a daemon restart
|
|
47
|
+
* reads as one moved cycle against a hydrated prior, which errs toward patience
|
|
48
|
+
* once and never toward a kill.
|
|
49
|
+
*
|
|
50
|
+
* @module completion/team-progress-measure
|
|
51
|
+
*/
|
|
52
|
+
import { createHash } from 'node:crypto';
|
|
53
|
+
import { lstatSync, readFileSync, readlinkSync } from 'node:fs';
|
|
54
|
+
import { join } from 'node:path';
|
|
55
|
+
import { runGitSync } from '../git/types.js';
|
|
56
|
+
/**
|
|
57
|
+
* Stable, key-order-independent fingerprint of the work-issue state. Sorting by
|
|
58
|
+
* issue id makes it depend only on the set of issues and their statuses, so a
|
|
59
|
+
* fetch that returns issues in a different order does not manufacture progress.
|
|
60
|
+
*/
|
|
61
|
+
export function computeIssueFingerprint(issues) {
|
|
62
|
+
return issues.map(i => `${i.id}:${i.status ?? ''}`).sort().join('|');
|
|
63
|
+
}
|
|
64
|
+
function statusesEqual(a, b) {
|
|
65
|
+
const aKeys = Object.keys(a);
|
|
66
|
+
if (aKeys.length !== Object.keys(b).length)
|
|
67
|
+
return false;
|
|
68
|
+
return aKeys.every(key => Object.prototype.hasOwnProperty.call(b, key) && a[key] === b[key]);
|
|
69
|
+
}
|
|
70
|
+
/** True when the team's activity moved between two readings (either unreadable axis is not compared). */
|
|
71
|
+
export function teamActivityMoved(prior, current) {
|
|
72
|
+
if (!prior || !current)
|
|
73
|
+
return false;
|
|
74
|
+
if (prior.agentActivitySeq !== current.agentActivitySeq)
|
|
75
|
+
return true;
|
|
76
|
+
return prior.worktreeDiffFingerprint !== null
|
|
77
|
+
&& current.worktreeDiffFingerprint !== null
|
|
78
|
+
&& prior.worktreeDiffFingerprint !== current.worktreeDiffFingerprint;
|
|
79
|
+
}
|
|
80
|
+
/** THE measure: true when any axis moved between two readings (module doc). */
|
|
81
|
+
export function teamProgressMoved(prior, current) {
|
|
82
|
+
if (!statusesEqual(prior.deliveryStatuses, current.deliveryStatuses))
|
|
83
|
+
return true;
|
|
84
|
+
if (prior.focusHeadSha !== current.focusHeadSha)
|
|
85
|
+
return true;
|
|
86
|
+
if (prior.issueFingerprint !== undefined && current.issueFingerprint !== undefined
|
|
87
|
+
&& prior.issueFingerprint !== current.issueFingerprint)
|
|
88
|
+
return true;
|
|
89
|
+
return teamActivityMoved(prior.activity, current.activity);
|
|
90
|
+
}
|
|
91
|
+
const activityByFocus = new Map();
|
|
92
|
+
function focusActivity(focusId) {
|
|
93
|
+
let entry = activityByFocus.get(focusId);
|
|
94
|
+
if (!entry) {
|
|
95
|
+
entry = { seq: 0, liveWorkerTasks: new Set(), lastWorkerEventAt: null };
|
|
96
|
+
activityByFocus.set(focusId, entry);
|
|
97
|
+
}
|
|
98
|
+
return entry;
|
|
99
|
+
}
|
|
100
|
+
/** The focus's activity count now (0 before any activity). */
|
|
101
|
+
export function readTeamActivitySeq(focusId) {
|
|
102
|
+
return activityByFocus.get(focusId)?.seq ?? 0;
|
|
103
|
+
}
|
|
104
|
+
/** Count one unit of team activity for the focus. */
|
|
105
|
+
export function recordTeamProgressActivity(focusId) {
|
|
106
|
+
focusActivity(focusId).seq += 1;
|
|
107
|
+
}
|
|
108
|
+
function recordWorkerEvent(focusId, nowMs, countsAsProgress) {
|
|
109
|
+
const entry = focusActivity(focusId);
|
|
110
|
+
entry.lastWorkerEventAt = nowMs;
|
|
111
|
+
if (countsAsProgress)
|
|
112
|
+
entry.seq += 1;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* The daemon is about to re-wake the lead (handoff, re-engagement, continuation).
|
|
116
|
+
* The lead's events until its next result are its reply to the daemon, not
|
|
117
|
+
* activity of its own (module doc).
|
|
118
|
+
*/
|
|
119
|
+
export function noteDaemonPoke(teamState, nowMs = Date.now()) {
|
|
120
|
+
teamState.lastDaemonPokeAt = nowMs;
|
|
121
|
+
}
|
|
122
|
+
/** True while the lead's current turn is its kickoff or a reply to a daemon re-wake. */
|
|
123
|
+
export function leadTurnIsDaemonStarted(teamState) {
|
|
124
|
+
if (teamState.lastLeadResultAt == null)
|
|
125
|
+
return true;
|
|
126
|
+
return teamState.lastDaemonPokeAt != null && teamState.lastDaemonPokeAt >= teamState.lastLeadResultAt;
|
|
127
|
+
}
|
|
128
|
+
/** Classify one neutral event from the team's stream (module doc). */
|
|
129
|
+
export function noteTeamAgentEvent(teamState, focusId, event, nowMs = Date.now()) {
|
|
130
|
+
switch (event.kind) {
|
|
131
|
+
case 'result':
|
|
132
|
+
teamState.lastLeadResultAt = nowMs;
|
|
133
|
+
return;
|
|
134
|
+
case 'heartbeat':
|
|
135
|
+
// A long call still running. While a worker is live AT THE HEARTBEAT, the
|
|
136
|
+
// heartbeat is what keeps it live (teamWorkersLive), and a worker the
|
|
137
|
+
// daemon holds its wakes for is a working team: the same signal is
|
|
138
|
+
// progress. Otherwise -- no worker, or one that went quiet -- it is the
|
|
139
|
+
// lead's own call: it neither counts nor revives a quiet worker (TEL-9).
|
|
140
|
+
if (event.parentToolUseId && teamWorkersLive(focusId, nowMs))
|
|
141
|
+
recordWorkerEvent(focusId, nowMs, true);
|
|
142
|
+
return;
|
|
143
|
+
case 'subagent_activity': {
|
|
144
|
+
const entry = focusActivity(focusId);
|
|
145
|
+
if (event.phase === 'completed')
|
|
146
|
+
entry.liveWorkerTasks.delete(event.agentId);
|
|
147
|
+
else
|
|
148
|
+
entry.liveWorkerTasks.add(event.agentId);
|
|
149
|
+
recordWorkerEvent(focusId, nowMs, true);
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
default:
|
|
153
|
+
if (!leadTurnIsDaemonStarted(teamState))
|
|
154
|
+
recordTeamProgressActivity(focusId);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
/** Task types whose task is a worker running beside the lead rather than one of the lead's own calls. */
|
|
158
|
+
function isWorkerTask(raw) {
|
|
159
|
+
return raw['is_backgrounded'] === true || raw['task_type'] === 'local_agent' || raw['task_type'] === 'remote_agent';
|
|
160
|
+
}
|
|
161
|
+
const ENDED_TASK_STATUSES = new Set(['completed', 'failed', 'stopped', 'killed', 'cancelled']);
|
|
162
|
+
/**
|
|
163
|
+
* Classify one raw engine event. Background workers surface in the Claude
|
|
164
|
+
* stream only as `system/task_*` events (their tool calls are not attributed),
|
|
165
|
+
* so they are read here, off the raw channel, keyed by the task id the
|
|
166
|
+
* task_started event declared a worker.
|
|
167
|
+
*/
|
|
168
|
+
export function noteTeamRawEvent(focusId, raw, nowMs = Date.now()) {
|
|
169
|
+
if (raw === null || typeof raw !== 'object')
|
|
170
|
+
return;
|
|
171
|
+
const event = raw;
|
|
172
|
+
if (event['type'] !== 'system')
|
|
173
|
+
return;
|
|
174
|
+
const subtype = event['subtype'];
|
|
175
|
+
const taskId = event['task_id'];
|
|
176
|
+
if (typeof subtype !== 'string' || typeof taskId !== 'string')
|
|
177
|
+
return;
|
|
178
|
+
const entry = focusActivity(focusId);
|
|
179
|
+
if (subtype === 'task_started') {
|
|
180
|
+
if (!isWorkerTask(event))
|
|
181
|
+
return;
|
|
182
|
+
entry.liveWorkerTasks.add(taskId);
|
|
183
|
+
recordWorkerEvent(focusId, nowMs, true);
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
// task_updated carries its changes in a patch: a call the lead moved to the
|
|
187
|
+
// background becomes a worker there.
|
|
188
|
+
const patch = subtype === 'task_updated' && event['patch'] !== null && typeof event['patch'] === 'object'
|
|
189
|
+
? event['patch']
|
|
190
|
+
: null;
|
|
191
|
+
if (patch?.['is_backgrounded'] === true)
|
|
192
|
+
entry.liveWorkerTasks.add(taskId);
|
|
193
|
+
if (!entry.liveWorkerTasks.has(taskId))
|
|
194
|
+
return;
|
|
195
|
+
const status = patch ? patch['status'] : subtype === 'task_notification' ? event['status'] : undefined;
|
|
196
|
+
if (typeof status === 'string' && ENDED_TASK_STATUSES.has(status))
|
|
197
|
+
entry.liveWorkerTasks.delete(taskId);
|
|
198
|
+
recordWorkerEvent(focusId, nowMs, true);
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* How long a started worker may go without a single event before it is treated
|
|
202
|
+
* as quiet. A worker inside one long tool call still heartbeats (~30s), so this
|
|
203
|
+
* only lapses for a worker that has genuinely stopped reporting.
|
|
204
|
+
*/
|
|
205
|
+
export const WORKER_QUIET_MS = 5 * 60_000;
|
|
206
|
+
/** True while the team has a started worker that has not ended and is still reporting. */
|
|
207
|
+
export function teamWorkersLive(focusId, nowMs = Date.now()) {
|
|
208
|
+
const entry = activityByFocus.get(focusId);
|
|
209
|
+
if (!entry || entry.liveWorkerTasks.size === 0 || entry.lastWorkerEventAt === null)
|
|
210
|
+
return false;
|
|
211
|
+
return nowMs - entry.lastWorkerEventAt < WORKER_QUIET_MS;
|
|
212
|
+
}
|
|
213
|
+
/** Ms until a live team's workers would go quiet if they stopped reporting now; null when none are live. */
|
|
214
|
+
export function teamWorkersQuietInMs(focusId, nowMs = Date.now()) {
|
|
215
|
+
if (!teamWorkersLive(focusId, nowMs))
|
|
216
|
+
return null;
|
|
217
|
+
const last = activityByFocus.get(focusId)?.lastWorkerEventAt ?? nowMs;
|
|
218
|
+
return Math.max(1, last + WORKER_QUIET_MS - nowMs);
|
|
219
|
+
}
|
|
220
|
+
/** How many started workers have not ended (0 when none). */
|
|
221
|
+
export function liveWorkerCount(focusId) {
|
|
222
|
+
return activityByFocus.get(focusId)?.liveWorkerTasks.size ?? 0;
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* A new team starts with no live workers of its own: a background worker does
|
|
226
|
+
* not survive the session that spawned it. The activity COUNT is kept (module doc).
|
|
227
|
+
*/
|
|
228
|
+
export function resetTeamWorkers(focusId) {
|
|
229
|
+
const entry = activityByFocus.get(focusId);
|
|
230
|
+
if (!entry)
|
|
231
|
+
return;
|
|
232
|
+
entry.liveWorkerTasks.clear();
|
|
233
|
+
entry.lastWorkerEventAt = null;
|
|
234
|
+
}
|
|
235
|
+
/** Test-only: forget every focus. */
|
|
236
|
+
export function __resetTeamProgressMeasureForTesting() {
|
|
237
|
+
activityByFocus.clear();
|
|
238
|
+
}
|
|
239
|
+
/** Content of one worktree path for the fingerprint: its bytes, a symlink's target, or its absence. */
|
|
240
|
+
function worktreePathContent(absPath) {
|
|
241
|
+
try {
|
|
242
|
+
const st = lstatSync(absPath);
|
|
243
|
+
if (st.isSymbolicLink())
|
|
244
|
+
return `symlink:${readlinkSync(absPath)}`;
|
|
245
|
+
if (!st.isFile())
|
|
246
|
+
return `kind:${st.isDirectory() ? 'dir' : 'other'}`;
|
|
247
|
+
return readFileSync(absPath);
|
|
248
|
+
}
|
|
249
|
+
catch {
|
|
250
|
+
return 'absent'; // deleted in the worktree
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Fingerprint of the worktree's uncommitted CONTENT: the bytes of every path
|
|
255
|
+
* that differs from HEAD or is untracked (its absence, for a deletion), plus the
|
|
256
|
+
* staged index entries (`git diff --cached --raw`: the index blob of each staged
|
|
257
|
+
* change), so a staged-only change moves it too. Content, not metadata: two
|
|
258
|
+
* different edits of the same size under the same mtime still move it. Reads
|
|
259
|
+
* only the changed files -- cheap enough for every completion. Null when git
|
|
260
|
+
* cannot be read, which the measure does not compare.
|
|
261
|
+
*/
|
|
262
|
+
export function readWorktreeDiffFingerprint(worktreePath, run = runGitSync) {
|
|
263
|
+
const changed = run(['diff', 'HEAD', '--name-only', '-z'], worktreePath);
|
|
264
|
+
const untracked = run(['ls-files', '--others', '--exclude-standard', '-z'], worktreePath);
|
|
265
|
+
const staged = run(['diff', '--cached', '--raw', '-z', '--no-abbrev'], worktreePath);
|
|
266
|
+
if (!changed.success || !untracked.success || !staged.success)
|
|
267
|
+
return null;
|
|
268
|
+
const paths = [...new Set(`${changed.output}\0${untracked.output}`.split('\0').filter(p => p.length > 0))].sort();
|
|
269
|
+
const hash = createHash('sha1');
|
|
270
|
+
hash.update(`staged\0${staged.output}\n`);
|
|
271
|
+
for (const path of paths) {
|
|
272
|
+
hash.update(`${path}\0`);
|
|
273
|
+
hash.update(worktreePathContent(join(worktreePath, path)));
|
|
274
|
+
hash.update('\n');
|
|
275
|
+
}
|
|
276
|
+
return hash.digest('hex');
|
|
277
|
+
}
|
|
278
|
+
/** The focus's two activity axes, read now. */
|
|
279
|
+
export function readTeamActivityMarks(focusId, worktreePath, readDiff = readWorktreeDiffFingerprint) {
|
|
280
|
+
return {
|
|
281
|
+
agentActivitySeq: readTeamActivitySeq(focusId),
|
|
282
|
+
worktreeDiffFingerprint: worktreePath ? readDiff(worktreePath) : null,
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
//# sourceMappingURL=team-progress-measure.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"team-progress-measure.js","sourceRoot":"","sources":["../../src/completion/team-progress-measure.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How long a focus team has been STILL, by the one progress measure -- the
|
|
3
|
+
* reading the completion-event-independent reapers act on.
|
|
4
|
+
*
|
|
5
|
+
* The stalled-team backstop (verify/stalled-team-backstop.ts) and the blind
|
|
6
|
+
* idle-team timer (verify/stall-detectors.ts checkIdleTeamStallForFocus) exist
|
|
7
|
+
* for teams whose completion event never fires, so they cannot read the
|
|
8
|
+
* completion path's per-cycle snapshot. They used to read only the raw
|
|
9
|
+
* last-agent-event clock, which moves on EVERY event -- a lead's reply to a
|
|
10
|
+
* daemon re-wake and a heartbeat included, both of which the measure says are
|
|
11
|
+
* not progress -- and which cannot see a moving diff, HEAD, issue or status.
|
|
12
|
+
* They now read this module instead: the per-focus cascade pass takes a reading
|
|
13
|
+
* of the measure's axes for every focus with a live team on each poll
|
|
14
|
+
* (observeLiveTeamProgress -- its ONE writer; the agent-activity axis is the
|
|
15
|
+
* measure's FILTERED count, not the raw clock), and readTeamLastProgressAt
|
|
16
|
+
* answers when any axis last moved.
|
|
17
|
+
*
|
|
18
|
+
* Two answers are withheld (null: "not stalled -- do not reap"):
|
|
19
|
+
* - a team that has produced no event yet (still initializing), exactly as
|
|
20
|
+
* the raw clock read;
|
|
21
|
+
* - a team whose latest reading could not read a required axis (the issue
|
|
22
|
+
* list, or HEAD / the diff of a registered worktree). Stillness is only
|
|
23
|
+
* established by a reading that saw every axis; an unreadable one proves
|
|
24
|
+
* neither movement nor stillness, so it authorizes no reap, and the first
|
|
25
|
+
* readable reading after it starts a fresh window.
|
|
26
|
+
*
|
|
27
|
+
* In memory, per daemon process: after a restart the first reading is a
|
|
28
|
+
* baseline (it counts as movement), which errs toward patience once and never
|
|
29
|
+
* toward a reap.
|
|
30
|
+
*
|
|
31
|
+
* @module completion/team-stillness
|
|
32
|
+
*/
|
|
33
|
+
import type { FocusDeliveryInfo, FocusIssueInfo, TeamProgressAxes } from '../types/index.js';
|
|
34
|
+
/** A reading of the measure's axes, and whether every required axis was read. */
|
|
35
|
+
export interface ProgressReading {
|
|
36
|
+
axes: TeamProgressAxes;
|
|
37
|
+
readable: boolean;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* The measure's axes for a focus now, read from its registered worktree. HEAD
|
|
41
|
+
* and the diff are required when a worktree is registered: either unreadable
|
|
42
|
+
* makes the reading unreadable.
|
|
43
|
+
*/
|
|
44
|
+
export declare function readFocusProgress(focusId: string, deliveries: Pick<FocusDeliveryInfo, 'id' | 'executionStatus'>[], issues: Pick<FocusIssueInfo, 'id' | 'status'>[]): ProgressReading;
|
|
45
|
+
/**
|
|
46
|
+
* Record a reading of the measure for a focus. A readable reading stamps
|
|
47
|
+
* `nowMs` as the last movement when it is the first, the first after an
|
|
48
|
+
* unreadable one (a fresh window), or when any axis moved since the last
|
|
49
|
+
* readable one. An unreadable reading changes no movement
|
|
50
|
+
* instant; it only marks the latest reading unreadable.
|
|
51
|
+
*/
|
|
52
|
+
export declare function observeTeamProgress(focusId: string, reading: ProgressReading, nowMs: number): void;
|
|
53
|
+
/**
|
|
54
|
+
* When the team last showed progress by the ONE measure, for the reapers.
|
|
55
|
+
* Null -- never stalled -- while the team has produced no event yet, before its
|
|
56
|
+
* first reading, or while its latest reading was unreadable.
|
|
57
|
+
*/
|
|
58
|
+
export declare function readTeamLastProgressAt(focusId: string): number | null;
|
|
59
|
+
/** What a cascade-pass reading needs; defaults are production, tests inject. */
|
|
60
|
+
export interface ObserveLiveTeamDeps {
|
|
61
|
+
isTeamLive: (focusId: string) => boolean;
|
|
62
|
+
getFocusIssues: (focusId: string) => Promise<Pick<FocusIssueInfo, 'id' | 'status'>[]>;
|
|
63
|
+
readProgress: typeof readFocusProgress;
|
|
64
|
+
now: () => number;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* The cascade pass's reading (the ONE writer): for a focus with a live team,
|
|
68
|
+
* read the measure's axes and record them; for a focus without one, forget its
|
|
69
|
+
* readings. An unreadable issue list is an unreadable reading (fail closed).
|
|
70
|
+
*/
|
|
71
|
+
export declare function observeLiveTeamProgress(focusId: string, deliveries: Pick<FocusDeliveryInfo, 'id' | 'executionStatus'>[], deps?: Partial<ObserveLiveTeamDeps>): Promise<void>;
|
|
72
|
+
/** Forget a focus's readings (its team is gone; the next team starts a new baseline). */
|
|
73
|
+
export declare function forgetTeamProgress(focusId: string): void;
|
|
74
|
+
/** Test-only: forget every focus. */
|
|
75
|
+
export declare function __resetTeamStillnessForTesting(): void;
|
|
76
|
+
//# sourceMappingURL=team-stillness.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"team-stillness.d.ts","sourceRoot":"","sources":["../../src/completion/team-stillness.ts"],"names":[],"mappings":"AAKA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAkB7F,iFAAiF;AACjF,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,gBAAgB,CAAC;IACvB,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,IAAI,CAAC,iBAAiB,EAAE,IAAI,GAAG,iBAAiB,CAAC,EAAE,EAC/D,MAAM,EAAE,IAAI,CAAC,cAAc,EAAE,IAAI,GAAG,QAAQ,CAAC,EAAE,GAC9C,eAAe,CAQjB;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,eAAe,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAWlG;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAKrE;AAED,gFAAgF;AAChF,MAAM,WAAW,mBAAmB;IAClC,UAAU,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC;IACzC,cAAc,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,cAAc,EAAE,IAAI,GAAG,QAAQ,CAAC,EAAE,CAAC,CAAC;IACtF,YAAY,EAAE,OAAO,iBAAiB,CAAC;IACvC,GAAG,EAAE,MAAM,MAAM,CAAC;CACnB;AASD;;;;GAIG;AACH,wBAAsB,uBAAuB,CAC3C,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,IAAI,CAAC,iBAAiB,EAAE,IAAI,GAAG,iBAAiB,CAAC,EAAE,EAC/D,IAAI,GAAE,OAAO,CAAC,mBAAmB,CAAM,GACtC,OAAO,CAAC,IAAI,CAAC,CAef;AAED,yFAAyF;AACzF,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAExD;AAED,qCAAqC;AACrC,wBAAgB,8BAA8B,IAAI,IAAI,CAErD"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
// PINNED-BY-BEGIN (generated by gen:pinned-by -- do not hand-edit; checked by lint:pinned-by)
|
|
2
|
+
// Pinned by: packages/daemon/src/verify/stall-detectors-clock-wiring.test.ts,
|
|
3
|
+
// packages/daemon/src/verify/team-stall-reapers.test.ts
|
|
4
|
+
// PINNED-BY-END
|
|
5
|
+
import { getFocusWorktree } from '../focus/focus-worktree-state.js';
|
|
6
|
+
import { getActiveTeams, getTeamLastActivityAt } from '../focus/focus-team-state.js';
|
|
7
|
+
import { getFocusIssues } from '../queries/focuses.js';
|
|
8
|
+
import { readWorktreeHeadSha } from '../git/index.js';
|
|
9
|
+
import { takeDeliveryAdvancementSnapshot } from './delivery-advancement-guard.js';
|
|
10
|
+
import { readTeamActivityMarks, teamProgressMoved } from './team-progress-measure.js';
|
|
11
|
+
const observations = new Map();
|
|
12
|
+
/**
|
|
13
|
+
* The measure's axes for a focus now, read from its registered worktree. HEAD
|
|
14
|
+
* and the diff are required when a worktree is registered: either unreadable
|
|
15
|
+
* makes the reading unreadable.
|
|
16
|
+
*/
|
|
17
|
+
export function readFocusProgress(focusId, deliveries, issues) {
|
|
18
|
+
const worktreePath = getFocusWorktree(focusId)?.worktreePath ?? null;
|
|
19
|
+
const head = worktreePath ? readWorktreeHeadSha(worktreePath) : null;
|
|
20
|
+
const activity = readTeamActivityMarks(focusId, worktreePath);
|
|
21
|
+
return {
|
|
22
|
+
axes: takeDeliveryAdvancementSnapshot(deliveries, issues, head, activity),
|
|
23
|
+
readable: worktreePath === null || (head !== null && activity.worktreeDiffFingerprint !== null),
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Record a reading of the measure for a focus. A readable reading stamps
|
|
28
|
+
* `nowMs` as the last movement when it is the first, the first after an
|
|
29
|
+
* unreadable one (a fresh window), or when any axis moved since the last
|
|
30
|
+
* readable one. An unreadable reading changes no movement
|
|
31
|
+
* instant; it only marks the latest reading unreadable.
|
|
32
|
+
*/
|
|
33
|
+
export function observeTeamProgress(focusId, reading, nowMs) {
|
|
34
|
+
const prior = observations.get(focusId);
|
|
35
|
+
if (!reading.readable) {
|
|
36
|
+
if (prior)
|
|
37
|
+
prior.readable = false;
|
|
38
|
+
else
|
|
39
|
+
observations.set(focusId, { axes: reading.axes, movedAt: nowMs, readable: false });
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
// A reading after an unreadable one is a fresh baseline: the blind interval
|
|
43
|
+
// proved nothing, so it must not count toward a stall window (TEL-8).
|
|
44
|
+
const movedAt = !prior || !prior.readable || teamProgressMoved(prior.axes, reading.axes) ? nowMs : prior.movedAt;
|
|
45
|
+
observations.set(focusId, { axes: reading.axes, movedAt, readable: true });
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* When the team last showed progress by the ONE measure, for the reapers.
|
|
49
|
+
* Null -- never stalled -- while the team has produced no event yet, before its
|
|
50
|
+
* first reading, or while its latest reading was unreadable.
|
|
51
|
+
*/
|
|
52
|
+
export function readTeamLastProgressAt(focusId) {
|
|
53
|
+
if (getTeamLastActivityAt(focusId) === null)
|
|
54
|
+
return null;
|
|
55
|
+
const observation = observations.get(focusId);
|
|
56
|
+
if (!observation || !observation.readable)
|
|
57
|
+
return null;
|
|
58
|
+
return observation.movedAt;
|
|
59
|
+
}
|
|
60
|
+
const defaultObserveDeps = {
|
|
61
|
+
isTeamLive: (focusId) => getActiveTeams().has(focusId),
|
|
62
|
+
getFocusIssues,
|
|
63
|
+
readProgress: readFocusProgress,
|
|
64
|
+
now: () => Date.now(),
|
|
65
|
+
};
|
|
66
|
+
/**
|
|
67
|
+
* The cascade pass's reading (the ONE writer): for a focus with a live team,
|
|
68
|
+
* read the measure's axes and record them; for a focus without one, forget its
|
|
69
|
+
* readings. An unreadable issue list is an unreadable reading (fail closed).
|
|
70
|
+
*/
|
|
71
|
+
export async function observeLiveTeamProgress(focusId, deliveries, deps = {}) {
|
|
72
|
+
const d = { ...defaultObserveDeps, ...deps };
|
|
73
|
+
if (!d.isTeamLive(focusId)) {
|
|
74
|
+
forgetTeamProgress(focusId);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
let issues;
|
|
78
|
+
try {
|
|
79
|
+
issues = await d.getFocusIssues(focusId);
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
const prior = observations.get(focusId);
|
|
83
|
+
observeTeamProgress(focusId, { axes: prior?.axes ?? { deliveryStatuses: {}, focusHeadSha: null }, readable: false }, d.now());
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
observeTeamProgress(focusId, d.readProgress(focusId, deliveries, issues), d.now());
|
|
87
|
+
}
|
|
88
|
+
/** Forget a focus's readings (its team is gone; the next team starts a new baseline). */
|
|
89
|
+
export function forgetTeamProgress(focusId) {
|
|
90
|
+
observations.delete(focusId);
|
|
91
|
+
}
|
|
92
|
+
/** Test-only: forget every focus. */
|
|
93
|
+
export function __resetTeamStillnessForTesting() {
|
|
94
|
+
observations.clear();
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=team-stillness.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"team-stillness.js","sourceRoot":"","sources":["../../src/completion/team-stillness.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A lead waiting on its workers is left to wait.
|
|
3
|
+
*
|
|
4
|
+
* On 2026-09-24 ("A ruling reaches the gate it rules on") a lead handed its
|
|
5
|
+
* edits to background workers and ended its turn while they ran. The daemon
|
|
6
|
+
* re-woke it with a mid-focus handoff every ~20s; each wake returned in seconds
|
|
7
|
+
* and was counted as an empty completion cycle, until the team was killed as a
|
|
8
|
+
* team_failure with its workers still editing. The daemon's own pokes were the
|
|
9
|
+
* cycles.
|
|
10
|
+
*
|
|
11
|
+
* So while the team's workers are live (completion/team-progress-measure.ts
|
|
12
|
+
* teamWorkersLive: a started worker that has not ended and is still reporting),
|
|
13
|
+
* the daemon sends the lead no handoff, no re-engagement and no continuation
|
|
14
|
+
* turn. The lead is next woken by:
|
|
15
|
+
* - its workers' completion -- the engine itself resumes the lead on a
|
|
16
|
+
* worker's task notification, and that turn's result is an ordinary cycle;
|
|
17
|
+
* - genuinely new work: a delivery the team has not been shown, or an issue
|
|
18
|
+
* that appeared since the team was last handed its work;
|
|
19
|
+
* - the normal handoff path once the workers go quiet (WORKER_QUIET_MS with no
|
|
20
|
+
* event) -- a re-check is scheduled for exactly then.
|
|
21
|
+
* A lead whose workers have all finished is re-engaged as before, and a team
|
|
22
|
+
* with no events at all is still reaped by the stalled-team backstop.
|
|
23
|
+
*
|
|
24
|
+
* The wait is logged ONCE per park episode (teamState.waitingOnWorkersSince).
|
|
25
|
+
*
|
|
26
|
+
* @module completion/worker-wait
|
|
27
|
+
*/
|
|
28
|
+
import type { FocusDeliveryInfo, FocusIssueInfo, FocusTeamState } from '../types/index.js';
|
|
29
|
+
type WaitState = Pick<FocusTeamState, 'waitingOnWorkersSince' | 'shownIssueIds'>;
|
|
30
|
+
/**
|
|
31
|
+
* Record the issues the lead has now been handed (at every wake actually sent),
|
|
32
|
+
* so an issue that appears later is recognisably new.
|
|
33
|
+
*/
|
|
34
|
+
export declare function recordIssuesShown(teamState: WaitState, issues: Pick<FocusIssueInfo, 'id'>[]): void;
|
|
35
|
+
/** True when an issue exists that the lead has not been handed (none, before any record). */
|
|
36
|
+
export declare function hasUnshownIssue(teamState: WaitState, issues: Pick<FocusIssueInfo, 'id'>[]): boolean;
|
|
37
|
+
/**
|
|
38
|
+
* Genuinely new work for a lead waiting on its workers: an agent-actionable
|
|
39
|
+
* delivery the team has never been told about, or an issue on a delivery the
|
|
40
|
+
* running arm owes that the lead has not been handed. Work RECORDED as new
|
|
41
|
+
* intent under the arm is invisible to the team and wakes nothing.
|
|
42
|
+
*/
|
|
43
|
+
export declare function hasUnshownWork(teamState: FocusTeamState, deliveries: FocusDeliveryInfo[], issues: Pick<FocusIssueInfo, 'id' | 'deliveryId'>[]): boolean;
|
|
44
|
+
/**
|
|
45
|
+
* While the lead waits on live workers, what the waiting re-check does next:
|
|
46
|
+
* a number is "still waiting -- check again in that many ms" (no completion
|
|
47
|
+
* cycle, no wake); null is "re-drive the completion decision now", because the
|
|
48
|
+
* workers are done or quiet, or because work the lead has not been shown has
|
|
49
|
+
* ARRIVED -- that arrival is itself the trigger, not the next unrelated event.
|
|
50
|
+
*/
|
|
51
|
+
export declare function nextWorkerWaitCheck(args: {
|
|
52
|
+
teamState: FocusTeamState;
|
|
53
|
+
focusId: string;
|
|
54
|
+
quietInMs: number | null;
|
|
55
|
+
readWork: () => Promise<{
|
|
56
|
+
deliveries: FocusDeliveryInfo[];
|
|
57
|
+
issues: FocusIssueInfo[];
|
|
58
|
+
}>;
|
|
59
|
+
}): Promise<number | null>;
|
|
60
|
+
/**
|
|
61
|
+
* How often a waiting lead's focus is re-read for newly arrived work -- the
|
|
62
|
+
* daemon's own poll cadence, so new work reaches a waiting lead no later than
|
|
63
|
+
* it would reach an idle one.
|
|
64
|
+
*/
|
|
65
|
+
export declare const WORKER_WAIT_RECHECK_MS = 30000;
|
|
66
|
+
/**
|
|
67
|
+
* Decide whether to HOLD a wake to the lead because its workers are live.
|
|
68
|
+
* Returns true to hold (send nothing). Logs once per park episode, and ends the
|
|
69
|
+
* episode (so the next one logs again) whenever a wake is let through.
|
|
70
|
+
*/
|
|
71
|
+
export declare function holdWakeForLiveWorkers(args: {
|
|
72
|
+
teamState: WaitState;
|
|
73
|
+
focusId: string;
|
|
74
|
+
focusName: string;
|
|
75
|
+
nowMs: number;
|
|
76
|
+
workersLive: boolean;
|
|
77
|
+
/** Genuinely new work the lead has not been shown (a delivery or an issue). */
|
|
78
|
+
hasUnshownWork: boolean;
|
|
79
|
+
issues: Pick<FocusIssueInfo, 'id'>[];
|
|
80
|
+
/** What the held wake would have been, for the log line. */
|
|
81
|
+
wakeKind: string;
|
|
82
|
+
}): boolean;
|
|
83
|
+
export {};
|
|
84
|
+
//# sourceMappingURL=worker-wait.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worker-wait.d.ts","sourceRoot":"","sources":["../../src/completion/worker-wait.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAQ3F,KAAK,SAAS,GAAG,IAAI,CAAC,cAAc,EAAE,uBAAuB,GAAG,eAAe,CAAC,CAAC;AAEjF;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,CAAC,cAAc,EAAE,IAAI,CAAC,EAAE,GAAG,IAAI,CAElG;AAED,6FAA6F;AAC7F,wBAAgB,eAAe,CAAC,SAAS,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,CAAC,cAAc,EAAE,IAAI,CAAC,EAAE,GAAG,OAAO,CAGnG;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAC5B,SAAS,EAAE,cAAc,EACzB,UAAU,EAAE,iBAAiB,EAAE,EAC/B,MAAM,EAAE,IAAI,CAAC,cAAc,EAAE,IAAI,GAAG,YAAY,CAAC,EAAE,GAClD,OAAO,CAOT;AAED;;;;;;GAMG;AACH,wBAAsB,mBAAmB,CAAC,IAAI,EAAE;IAC9C,SAAS,EAAE,cAAc,CAAC;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,QAAQ,EAAE,MAAM,OAAO,CAAC;QAAE,UAAU,EAAE,iBAAiB,EAAE,CAAC;QAAC,MAAM,EAAE,cAAc,EAAE,CAAA;KAAE,CAAC,CAAC;CACxF,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAWzB;AAED;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,QAAS,CAAC;AAE7C;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE;IAC3C,SAAS,EAAE,SAAS,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,OAAO,CAAC;IACrB,+EAA+E;IAC/E,cAAc,EAAE,OAAO,CAAC;IACxB,MAAM,EAAE,IAAI,CAAC,cAAc,EAAE,IAAI,CAAC,EAAE,CAAC;IACrC,4DAA4D;IAC5D,QAAQ,EAAE,MAAM,CAAC;CAClB,GAAG,OAAO,CAuBV"}
|