@telora/daemon 0.22.289 → 0.22.290
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 +2 -2
- package/dist/backends/agent-backend.d.ts +2 -0
- package/dist/backends/agent-backend.d.ts.map +1 -1
- package/dist/backends/availability.d.ts +14 -0
- package/dist/backends/availability.d.ts.map +1 -1
- package/dist/backends/availability.js +37 -0
- package/dist/backends/availability.js.map +1 -1
- package/dist/backends/claude/claude-backend.d.ts.map +1 -1
- package/dist/backends/claude/claude-backend.js +2 -0
- package/dist/backends/claude/claude-backend.js.map +1 -1
- package/dist/backends/codex/codex-backend.d.ts.map +1 -1
- package/dist/backends/codex/codex-backend.js +2 -0
- package/dist/backends/codex/codex-backend.js.map +1 -1
- package/dist/backends/index.d.ts +1 -1
- package/dist/backends/index.d.ts.map +1 -1
- package/dist/backends/index.js +2 -17
- package/dist/backends/index.js.map +1 -1
- package/dist/cli/status-commands.d.ts +7 -0
- package/dist/cli/status-commands.d.ts.map +1 -1
- package/dist/cli/status-commands.js +17 -0
- package/dist/cli/status-commands.js.map +1 -1
- package/dist/completion/completion-event/event.d.ts +1 -0
- package/dist/completion/completion-event/event.d.ts.map +1 -1
- package/dist/completion/completion-event/event.js +1 -0
- package/dist/completion/completion-event/event.js.map +1 -1
- package/dist/completion/review-exit/review-census-record.d.ts +4 -2
- package/dist/completion/review-exit/review-census-record.d.ts.map +1 -1
- package/dist/completion/review-exit/review-census-record.js +11 -3
- package/dist/completion/review-exit/review-census-record.js.map +1 -1
- package/dist/completion/review-exit/review-exit-deps.js +1 -1
- package/dist/completion/review-exit/review-exit-phase.d.ts +9 -0
- package/dist/completion/review-exit/review-exit-phase.d.ts.map +1 -1
- package/dist/completion/review-exit/review-exit-phase.js +1 -1
- package/dist/completion/review-exit/review-exit-phase.js.map +1 -1
- package/dist/completion/review-exit-holds/commit-verdict-release.d.ts.map +1 -1
- package/dist/completion/review-exit-holds/commit-verdict-release.js +2 -1
- package/dist/completion/review-exit-holds/commit-verdict-release.js.map +1 -1
- package/dist/completion/review-exit-holds/review-exit-verdict-gate.d.ts +1 -0
- package/dist/completion/review-exit-holds/review-exit-verdict-gate.d.ts.map +1 -1
- package/dist/completion/review-exit-holds/review-exit-verdict-gate.js +1 -0
- package/dist/completion/review-exit-holds/review-exit-verdict-gate.js.map +1 -1
- package/dist/completion/team-exit/team-completion.d.ts.map +1 -1
- package/dist/completion/team-exit/team-completion.js +3 -1
- package/dist/completion/team-exit/team-completion.js.map +1 -1
- package/dist/completion/team-liveness/liveness-firing.js +3 -3
- package/dist/completion/team-liveness/team-liveness-phase.d.ts +6 -1
- package/dist/completion/team-liveness/team-liveness-phase.d.ts.map +1 -1
- package/dist/completion/team-liveness/team-liveness-phase.js.map +1 -1
- package/dist/completion/team-outcome/apply-close.d.ts.map +1 -1
- package/dist/completion/team-outcome/apply-close.js +4 -1
- package/dist/completion/team-outcome/apply-close.js.map +1 -1
- package/dist/completion/team-outcome/apply-turn.d.ts.map +1 -1
- package/dist/completion/team-outcome/apply-turn.js +3 -1
- package/dist/completion/team-outcome/apply-turn.js.map +1 -1
- package/dist/completion/team-outcome/apply.d.ts.map +1 -1
- package/dist/completion/team-outcome/apply.js +3 -1
- package/dist/completion/team-outcome/apply.js.map +1 -1
- package/dist/completion/team-outcome/decide-shared.d.ts.map +1 -1
- package/dist/completion/team-outcome/decide-shared.js +3 -2
- package/dist/completion/team-outcome/decide-shared.js.map +1 -1
- package/dist/completion/team-outcome/exit-pipeline-state.d.ts.map +1 -1
- package/dist/completion/team-outcome/exit-pipeline-state.js +2 -1
- package/dist/completion/team-outcome/exit-pipeline-state.js.map +1 -1
- package/dist/completion/team-outcome/team-outcome.d.ts.map +1 -1
- package/dist/completion/team-outcome/team-outcome.js +3 -1
- package/dist/completion/team-outcome/team-outcome.js.map +1 -1
- package/dist/composition/execution-migrations-context.d.ts.map +1 -1
- package/dist/composition/execution-migrations-context.js +2 -0
- package/dist/composition/execution-migrations-context.js.map +1 -1
- package/dist/composition/execution-process-context.js +1 -1
- package/dist/composition/execution-spawn-context.d.ts.map +1 -1
- package/dist/composition/execution-spawn-context.js +2 -1
- package/dist/composition/execution-spawn-context.js.map +1 -1
- package/dist/composition/execution-spawn-process-context.d.ts.map +1 -1
- package/dist/composition/execution-spawn-process-context.js +3 -2
- package/dist/composition/execution-spawn-process-context.js.map +1 -1
- package/dist/composition/focus-activity-live-sources.d.ts +4 -0
- package/dist/composition/focus-activity-live-sources.d.ts.map +1 -0
- package/dist/composition/focus-activity-live-sources.js +65 -0
- package/dist/composition/focus-activity-live-sources.js.map +1 -0
- package/dist/composition/runtime-delivery-advance-context.d.ts +6 -1
- package/dist/composition/runtime-delivery-advance-context.d.ts.map +1 -1
- package/dist/composition/runtime-delivery-advance-context.js +14 -3
- package/dist/composition/runtime-delivery-advance-context.js.map +1 -1
- package/dist/composition/runtime-heartbeat-context.d.ts.map +1 -1
- package/dist/composition/runtime-heartbeat-context.js +3 -1
- package/dist/composition/runtime-heartbeat-context.js.map +1 -1
- package/dist/composition/team-lifecycle-context.d.ts.map +1 -1
- package/dist/composition/team-lifecycle-context.js +3 -1
- package/dist/composition/team-lifecycle-context.js.map +1 -1
- package/dist/composition/team-liveness-phase-sources.d.ts.map +1 -1
- package/dist/composition/team-liveness-phase-sources.js +1 -0
- package/dist/composition/team-liveness-phase-sources.js.map +1 -1
- package/dist/composition/verification-context.d.ts.map +1 -1
- package/dist/composition/verification-context.js +4 -1
- package/dist/composition/verification-context.js.map +1 -1
- package/dist/daemon-state/awaiting-slot.d.ts +8 -2
- package/dist/daemon-state/awaiting-slot.d.ts.map +1 -1
- package/dist/daemon-state/awaiting-slot.js +21 -8
- package/dist/daemon-state/awaiting-slot.js.map +1 -1
- package/dist/daemon-state/engine-readiness.d.ts +73 -0
- package/dist/daemon-state/engine-readiness.d.ts.map +1 -0
- package/dist/daemon-state/engine-readiness.js +191 -0
- package/dist/daemon-state/engine-readiness.js.map +1 -0
- package/dist/daemon-state/focus-progress-clock.d.ts +34 -0
- package/dist/daemon-state/focus-progress-clock.d.ts.map +1 -0
- package/dist/daemon-state/focus-progress-clock.js +167 -0
- package/dist/daemon-state/focus-progress-clock.js.map +1 -0
- package/dist/daemon-state/focus-team-state.d.ts +12 -9
- package/dist/daemon-state/focus-team-state.d.ts.map +1 -1
- package/dist/daemon-state/focus-team-state.js +15 -9
- package/dist/daemon-state/focus-team-state.js.map +1 -1
- package/dist/daemon-state/provider-window.d.ts.map +1 -1
- package/dist/daemon-state/provider-window.js +5 -1
- package/dist/daemon-state/provider-window.js.map +1 -1
- package/dist/daemon-state/review-park-record.d.ts +36 -0
- package/dist/daemon-state/review-park-record.d.ts.map +1 -0
- package/dist/daemon-state/review-park-record.js +27 -0
- package/dist/daemon-state/review-park-record.js.map +1 -0
- package/dist/daemon-state/worker-pidfile.d.ts.map +1 -1
- package/dist/daemon-state/worker-pidfile.js +3 -1
- package/dist/daemon-state/worker-pidfile.js.map +1 -1
- package/dist/delivery/commit-verdict-hold.d.ts.map +1 -1
- package/dist/delivery/commit-verdict-hold.js +3 -1
- package/dist/delivery/commit-verdict-hold.js.map +1 -1
- package/dist/directive/directive-queue.js +1 -1
- package/dist/focus/focus-activity-held.d.ts +37 -0
- package/dist/focus/focus-activity-held.d.ts.map +1 -0
- package/dist/focus/focus-activity-held.js +89 -0
- package/dist/focus/focus-activity-held.js.map +1 -0
- package/dist/focus/focus-activity-poll-facts.d.ts +39 -0
- package/dist/focus/focus-activity-poll-facts.d.ts.map +1 -0
- package/dist/focus/focus-activity-poll-facts.js +109 -0
- package/dist/focus/focus-activity-poll-facts.js.map +1 -0
- package/dist/focus/focus-activity-producers.d.ts +37 -0
- package/dist/focus/focus-activity-producers.d.ts.map +1 -0
- package/dist/focus/focus-activity-producers.js +118 -0
- package/dist/focus/focus-activity-producers.js.map +1 -0
- package/dist/focus/focus-activity-record.d.ts +58 -0
- package/dist/focus/focus-activity-record.d.ts.map +1 -0
- package/dist/focus/focus-activity-record.js +138 -0
- package/dist/focus/focus-activity-record.js.map +1 -0
- package/dist/focus/focus-activity-sources.d.ts +95 -0
- package/dist/focus/focus-activity-sources.d.ts.map +1 -0
- package/dist/focus/focus-activity-sources.js +45 -0
- package/dist/focus/focus-activity-sources.js.map +1 -0
- package/dist/focus/focus-executor.js +1 -1
- package/dist/focus/focus-executor.js.map +1 -1
- package/dist/focus/focus-poller-auto-advance.d.ts.map +1 -1
- package/dist/focus/focus-poller-auto-advance.js +2 -0
- package/dist/focus/focus-poller-auto-advance.js.map +1 -1
- package/dist/focus/heartbeat.d.ts +3 -0
- package/dist/focus/heartbeat.d.ts.map +1 -1
- package/dist/focus/heartbeat.js +31 -31
- package/dist/focus/heartbeat.js.map +1 -1
- package/dist/focus/pending-review-approved-head.d.ts.map +1 -1
- package/dist/focus/pending-review-approved-head.js +4 -2
- package/dist/focus/pending-review-approved-head.js.map +1 -1
- package/dist/focus/unchanged-reviewed-head.d.ts.map +1 -1
- package/dist/focus/unchanged-reviewed-head.js +4 -2
- package/dist/focus/unchanged-reviewed-head.js.map +1 -1
- package/dist/queries/deliveries/deliveries.d.ts +4 -0
- package/dist/queries/deliveries/deliveries.d.ts.map +1 -1
- package/dist/queries/deliveries/deliveries.js +6 -0
- package/dist/queries/deliveries/deliveries.js.map +1 -1
- package/dist/queries/deliveries/issues.d.ts +3 -0
- package/dist/queries/deliveries/issues.d.ts.map +1 -1
- package/dist/queries/deliveries/issues.js +10 -2
- package/dist/queries/deliveries/issues.js.map +1 -1
- package/dist/queries/delivery-gate-state/delivery-gate-holds.d.ts +2 -0
- package/dist/queries/delivery-gate-state/delivery-gate-holds.d.ts.map +1 -1
- package/dist/queries/focuses/focus-discovery.d.ts.map +1 -1
- package/dist/queries/focuses/focus-discovery.js +2 -0
- package/dist/queries/focuses/focus-discovery.js.map +1 -1
- package/dist/queries/focuses/focuses.d.ts.map +1 -1
- package/dist/queries/focuses/focuses.js +2 -0
- package/dist/queries/focuses/focuses.js.map +1 -1
- package/dist/queries/sessions/daemon-connection.d.ts.map +1 -1
- package/dist/queries/sessions/daemon-connection.js +1 -0
- package/dist/queries/sessions/daemon-connection.js.map +1 -1
- package/dist/queries/workflow-transitions/workflows.d.ts.map +1 -1
- package/dist/queries/workflow-transitions/workflows.js +1 -0
- package/dist/queries/workflow-transitions/workflows.js.map +1 -1
- package/dist/shell/unified-shell-status.d.ts +11 -1
- package/dist/shell/unified-shell-status.d.ts.map +1 -1
- package/dist/shell/unified-shell-status.js +14 -0
- package/dist/shell/unified-shell-status.js.map +1 -1
- package/dist/shell/unified-shell.d.ts.map +1 -1
- package/dist/shell/unified-shell.js +23 -4
- package/dist/shell/unified-shell.js.map +1 -1
- package/dist/spawn/codex-auth.d.ts +32 -0
- package/dist/spawn/codex-auth.d.ts.map +1 -0
- package/dist/spawn/codex-auth.js +55 -0
- package/dist/spawn/codex-auth.js.map +1 -0
- package/dist/spawn/helpers.d.ts.map +1 -1
- package/dist/spawn/helpers.js +15 -3
- package/dist/spawn/helpers.js.map +1 -1
- package/dist/spawn/model-reached.d.ts +36 -0
- package/dist/spawn/model-reached.d.ts.map +1 -0
- package/dist/spawn/model-reached.js +43 -0
- package/dist/spawn/model-reached.js.map +1 -0
- package/dist/spawn/next-spawn-cooldown.d.ts +21 -2
- package/dist/spawn/next-spawn-cooldown.d.ts.map +1 -1
- package/dist/spawn/next-spawn-cooldown.js +65 -7
- package/dist/spawn/next-spawn-cooldown.js.map +1 -1
- package/dist/spawn/process-spawn.d.ts.map +1 -1
- package/dist/spawn/process-spawn.js +8 -5
- package/dist/spawn/process-spawn.js.map +1 -1
- package/dist/spawn/review-hold-park.d.ts +12 -1
- package/dist/spawn/review-hold-park.d.ts.map +1 -1
- package/dist/spawn/review-hold-park.js +25 -9
- package/dist/spawn/review-hold-park.js.map +1 -1
- package/dist/spawn/worktree-session.d.ts +15 -0
- package/dist/spawn/worktree-session.d.ts.map +1 -1
- package/dist/spawn/worktree-session.js +25 -2
- package/dist/spawn/worktree-session.js.map +1 -1
- package/dist/supervision/spawn-cooldown-window.d.ts +15 -0
- package/dist/supervision/spawn-cooldown-window.d.ts.map +1 -0
- package/dist/supervision/spawn-cooldown-window.js +46 -0
- package/dist/supervision/spawn-cooldown-window.js.map +1 -0
- package/dist/supervision/spawn-cooldown.d.ts +3 -2
- package/dist/supervision/spawn-cooldown.d.ts.map +1 -1
- package/dist/supervision/spawn-cooldown.js +6 -1
- package/dist/supervision/spawn-cooldown.js.map +1 -1
- package/dist/supervision/stop-registry.d.ts +1 -0
- package/dist/supervision/stop-registry.d.ts.map +1 -1
- package/dist/supervision/stop-rows-spawn.d.ts +8 -0
- package/dist/supervision/stop-rows-spawn.d.ts.map +1 -1
- package/dist/supervision/stop-rows-spawn.js +28 -1
- package/dist/supervision/stop-rows-spawn.js.map +1 -1
- package/dist/testing/fixtures/approved-head-inert-deps.d.ts.map +1 -1
- package/dist/testing/fixtures/approved-head-inert-deps.js +4 -2
- package/dist/testing/fixtures/approved-head-inert-deps.js.map +1 -1
- package/dist/testing/fixtures/codex-stream-session.d.ts +4 -0
- package/dist/testing/fixtures/codex-stream-session.d.ts.map +1 -1
- package/dist/testing/fixtures/codex-stream-session.js +21 -5
- package/dist/testing/fixtures/codex-stream-session.js.map +1 -1
- package/dist/testing/fixtures/completion-event-deps.js +1 -1
- package/dist/testing/fixtures/index.js +1 -1
- package/dist/testing/fixtures/process-close-context.d.ts.map +1 -1
- package/dist/testing/fixtures/process-close-context.js +2 -0
- package/dist/testing/fixtures/process-close-context.js.map +1 -1
- package/dist/testing/fixtures/recording-api-client.js +1 -1
- package/dist/testing/fixtures/review-exit-deps.js +1 -1
- package/dist/testing/fixtures/session-wire.d.ts.map +1 -1
- package/dist/testing/fixtures/session-wire.js +2 -1
- package/dist/testing/fixtures/session-wire.js.map +1 -1
- package/dist/testing/fixtures/verification-deps.d.ts.map +1 -1
- package/dist/testing/fixtures/verification-deps.js +4 -0
- package/dist/testing/fixtures/verification-deps.js.map +1 -1
- package/dist/types/focus-team-state.d.ts +14 -2
- package/dist/types/focus-team-state.d.ts.map +1 -1
- package/dist/types/focus-team-state.js.map +1 -1
- package/dist/types/index.js +1 -1
- package/dist/verify/deterministic-gate/verification-checkout.d.ts +8 -4
- package/dist/verify/deterministic-gate/verification-checkout.d.ts.map +1 -1
- package/dist/verify/deterministic-gate/verification-checkout.js +50 -13
- package/dist/verify/deterministic-gate/verification-checkout.js.map +1 -1
- package/dist/verify/deterministic-gate/verification-deterministic.d.ts.map +1 -1
- package/dist/verify/deterministic-gate/verification-deterministic.js +3 -1
- package/dist/verify/deterministic-gate/verification-deterministic.js.map +1 -1
- package/dist/verify/engine/verification-deps.d.ts +7 -0
- package/dist/verify/engine/verification-deps.d.ts.map +1 -1
- package/dist/verify/engine/verification-deps.js +1 -1
- package/dist/verify/engine/verification-engine.d.ts.map +1 -1
- package/dist/verify/engine/verification-engine.js +107 -8
- package/dist/verify/engine/verification-engine.js.map +1 -1
- package/dist/verify/gate-config/verification-config.js +1 -1
- package/dist/verify/gate-verdicts/commit-verdict-record.js +1 -1
- package/dist/verify/gate-verdicts/gate-run-registry.d.ts +74 -3
- package/dist/verify/gate-verdicts/gate-run-registry.d.ts.map +1 -1
- package/dist/verify/gate-verdicts/gate-run-registry.js +41 -20
- package/dist/verify/gate-verdicts/gate-run-registry.js.map +1 -1
- package/dist/verify/run-selection/default-on-guards.d.ts.map +1 -1
- package/dist/verify/run-selection/default-on-guards.js +1 -19
- package/dist/verify/run-selection/default-on-guards.js.map +1 -1
- package/dist/worktree/verify-interlock.d.ts +23 -2
- package/dist/worktree/verify-interlock.d.ts.map +1 -1
- package/dist/worktree/verify-interlock.js +79 -2
- package/dist/worktree/verify-interlock.js.map +1 -1
- package/package.json +3 -3
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
// PINNED-BY-BEGIN (generated by gen:pinned-by -- do not hand-edit; checked by lint:pinned-by)
|
|
2
|
+
// Pinned by: packages/daemon/src/focus/focus-activity-producers.test.ts,
|
|
3
|
+
// packages/daemon/src/focus/focus-activity-progress-clock.test.ts,
|
|
4
|
+
// packages/daemon/src/focus/focus-activity-team-clock.test.ts,
|
|
5
|
+
// packages/daemon/src/focus/heartbeat-focus-activity.test.ts
|
|
6
|
+
// PINNED-BY-END
|
|
7
|
+
/**
|
|
8
|
+
* The focus and delivery rows the poll loop already reads, kept for the
|
|
9
|
+
* heartbeat's focus activity record (focus/focus-activity-record.ts).
|
|
10
|
+
*
|
|
11
|
+
* The teamless auto-advance pass (focus/focus-poller-auto-advance.ts
|
|
12
|
+
* checkDeliveryAutoAdvance) reads every active focus of every product and every
|
|
13
|
+
* such focus's deliveries on each poll. Its two reads are wrapped at the
|
|
14
|
+
* composition root (composition/runtime-delivery-advance-context.ts) with the
|
|
15
|
+
* observers below, so the record knows which focuses are ARMED and what their
|
|
16
|
+
* deliveries hold at NO extra query: the heartbeat reads the latest poll's rows.
|
|
17
|
+
*
|
|
18
|
+
* Replacement, not accumulation: a product's focus list is replaced wholesale
|
|
19
|
+
* by each successful read of it, and a focus's deliveries by each read of them,
|
|
20
|
+
* so a focus that stops being active drops out on the next poll. A failed read
|
|
21
|
+
* records nothing and leaves the previous rows standing (the observed-at time
|
|
22
|
+
* says how old they are): a delivery read that fails after one succeeded keeps
|
|
23
|
+
* the last good rows, it never blanks them.
|
|
24
|
+
*
|
|
25
|
+
* The same reads (and the pass's per-delivery issue reads, observeDeliveryIssues)
|
|
26
|
+
* feed the per-focus progress clock (daemon-state/focus-progress-clock.ts) as its
|
|
27
|
+
* RECOVERY path: the daemon's own status writes stamp the clock when they land,
|
|
28
|
+
* and a read stamps it only for movement written elsewhere (an agent closing an
|
|
29
|
+
* issue over MCP). They also tell the clock which focus a delivery or issue
|
|
30
|
+
* belongs to, which is how a landed write is attributed.
|
|
31
|
+
*
|
|
32
|
+
* @module focus/focus-activity-poll-facts
|
|
33
|
+
*/
|
|
34
|
+
import { forgetFocusProgress, observeDeliveryProgress, observeIssueProgress } from '../daemon-state/focus-progress-clock.js';
|
|
35
|
+
const focusesByProduct = new Map();
|
|
36
|
+
const deliveriesByFocus = new Map();
|
|
37
|
+
/** Record one product's active focus list (replaces that product's previous list). */
|
|
38
|
+
export function recordPolledFocuses(productKey, rows) {
|
|
39
|
+
focusesByProduct.set(productKey, rows.map((r) => ({ focus_id: r.focus_id, assigned_agent_role_id: r.assigned_agent_role_id })));
|
|
40
|
+
}
|
|
41
|
+
/** Record one focus's deliveries (replaces its previous rows). */
|
|
42
|
+
export function recordPolledDeliveries(focusId, rows, now = Date.now()) {
|
|
43
|
+
observeDeliveryProgress(focusId, rows, now);
|
|
44
|
+
deliveriesByFocus.set(focusId, {
|
|
45
|
+
rows: rows.map((d) => ({ id: d.id, executionStatus: d.executionStatus ?? null, gateState: d.gateState ?? null })),
|
|
46
|
+
at: now,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
/** Every active focus the latest poll of each product saw, with its latest deliveries. Prunes rows of focuses no longer listed. */
|
|
50
|
+
export function polledFocusSnapshot() {
|
|
51
|
+
const out = new Map();
|
|
52
|
+
for (const rows of focusesByProduct.values()) {
|
|
53
|
+
for (const row of rows) {
|
|
54
|
+
const d = deliveriesByFocus.get(row.focus_id);
|
|
55
|
+
out.set(row.focus_id, {
|
|
56
|
+
focusId: row.focus_id,
|
|
57
|
+
armed: row.assigned_agent_role_id != null,
|
|
58
|
+
deliveries: d?.rows ?? null,
|
|
59
|
+
deliveriesObservedAt: d?.at ?? null,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
for (const focusId of deliveriesByFocus.keys()) {
|
|
64
|
+
if (out.has(focusId))
|
|
65
|
+
continue;
|
|
66
|
+
deliveriesByFocus.delete(focusId);
|
|
67
|
+
forgetFocusProgress(focusId);
|
|
68
|
+
}
|
|
69
|
+
return [...out.values()];
|
|
70
|
+
}
|
|
71
|
+
/** Wrap an active-focus read so each successful result is recorded under the product it was read for. */
|
|
72
|
+
export function observeActiveFocuses(read) {
|
|
73
|
+
return async (organizationId, productId) => {
|
|
74
|
+
const rows = await read(organizationId, productId);
|
|
75
|
+
recordPolledFocuses(productId ?? '', rows);
|
|
76
|
+
return rows;
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
/** Wrap a focus-deliveries read so each successful result is recorded for the focus. */
|
|
80
|
+
export function observeFocusDeliveries(read) {
|
|
81
|
+
return async (focusId) => {
|
|
82
|
+
const rows = await read(focusId);
|
|
83
|
+
recordPolledDeliveries(focusId, rows);
|
|
84
|
+
return rows;
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
/** The focus whose latest delivery read listed `deliveryId`, or null. */
|
|
88
|
+
function polledFocusOfDelivery(deliveryId) {
|
|
89
|
+
for (const [focusId, { rows }] of deliveriesByFocus)
|
|
90
|
+
if (rows.some((d) => d.id === deliveryId))
|
|
91
|
+
return focusId;
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
/** Wrap a delivery-issues read so each successful result feeds the focus's progress clock (an issue moving to Done). */
|
|
95
|
+
export function observeDeliveryIssues(read) {
|
|
96
|
+
return async (deliveryId) => {
|
|
97
|
+
const rows = await read(deliveryId);
|
|
98
|
+
const focusId = polledFocusOfDelivery(deliveryId);
|
|
99
|
+
if (focusId !== null)
|
|
100
|
+
observeIssueProgress(focusId, deliveryId, rows, Date.now());
|
|
101
|
+
return rows;
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
/** Test-only: forget every polled row. */
|
|
105
|
+
export function __resetPolledFocusFactsForTests() {
|
|
106
|
+
focusesByProduct.clear();
|
|
107
|
+
deliveriesByFocus.clear();
|
|
108
|
+
}
|
|
109
|
+
//# sourceMappingURL=focus-activity-poll-facts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"focus-activity-poll-facts.js","sourceRoot":"","sources":["../../src/focus/focus-activity-poll-facts.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { type FocusActivityClaim, type FocusActivitySources } from './focus-activity-sources.js';
|
|
2
|
+
/** team_coding / team_reviewing: one claim per live team. */
|
|
3
|
+
export declare function teamActivityClaims(sources: FocusActivitySources): FocusActivityClaim[];
|
|
4
|
+
/** awaiting_slot: a spawn pass would start a team but every session slot is in use. */
|
|
5
|
+
export declare function awaitingSlotClaims(sources: FocusActivitySources): FocusActivityClaim[];
|
|
6
|
+
/** spawn_pending: a team spawn is queued and starts on the next spawn pass (dated by first sight). */
|
|
7
|
+
export declare function spawnPendingClaims(sources: FocusActivitySources): FocusActivityClaim[];
|
|
8
|
+
/** review_parked: the review-hold park is withholding the focus's queued review spawn. */
|
|
9
|
+
export declare function reviewParkedClaims(sources: FocusActivitySources): FocusActivityClaim[];
|
|
10
|
+
/**
|
|
11
|
+
* cooldown: the focus's next spawn is held by its spawn cooldown -- until the
|
|
12
|
+
* time it ends, or with no end when the respawn cycle is exhausted. `since` is
|
|
13
|
+
* the latest teardown; a hold with no teardown of its own (the provider's
|
|
14
|
+
* rate-limit window alone) is dated by first sight.
|
|
15
|
+
*/
|
|
16
|
+
export declare function cooldownClaims(sources: FocusActivitySources): FocusActivityClaim[];
|
|
17
|
+
/** The statuses in which a delivery still owes work (a team would pick it up). */
|
|
18
|
+
export declare const OWING_WORK_STATUSES: ReadonlySet<string>;
|
|
19
|
+
/**
|
|
20
|
+
* idle: an armed focus the daemon is doing nothing else for, with the count of
|
|
21
|
+
* the deliveries it has READ that still owe work. The lowest precedence: every
|
|
22
|
+
* other entry wins over it. Dated by first sight of the idle entry.
|
|
23
|
+
*
|
|
24
|
+
* EVERY armed focus is claimed, so every armed focus of every product has an
|
|
25
|
+
* entry. The count comes from the latest SUCCESSFUL read of its deliveries: a
|
|
26
|
+
* read that fails after one succeeded leaves those rows standing
|
|
27
|
+
* (focus-activity-poll-facts.ts), so the count stays the last one the daemon
|
|
28
|
+
* saw. A focus whose deliveries have not been read yet -- listed by this poll,
|
|
29
|
+
* its delivery read still to come or failing since discovery -- publishes a
|
|
30
|
+
* null count: the inventory is UNKNOWN, so 0 always means a read that found
|
|
31
|
+
* nothing owed, never "not read". Dropping the focus instead would make an
|
|
32
|
+
* armed focus vanish from the record exactly when its reads fail. The unread
|
|
33
|
+
* entry is dated by its own first sight, so the first successful read starts
|
|
34
|
+
* the idle clock afresh rather than inheriting the time spent unread.
|
|
35
|
+
*/
|
|
36
|
+
export declare function idleClaims(sources: FocusActivitySources): FocusActivityClaim[];
|
|
37
|
+
//# sourceMappingURL=focus-activity-producers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"focus-activity-producers.d.ts","sourceRoot":"","sources":["../../src/focus/focus-activity-producers.ts"],"names":[],"mappings":"AAqBA,OAAO,EAIL,KAAK,kBAAkB,EACvB,KAAK,oBAAoB,EAC1B,MAAM,6BAA6B,CAAC;AAErC,6DAA6D;AAC7D,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,oBAAoB,GAAG,kBAAkB,EAAE,CAUtF;AAED,uFAAuF;AACvF,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,oBAAoB,GAAG,kBAAkB,EAAE,CAKtF;AAED,sGAAsG;AACtG,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,oBAAoB,GAAG,kBAAkB,EAAE,CAMtF;AAED,0FAA0F;AAC1F,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,oBAAoB,GAAG,kBAAkB,EAAE,CAKtF;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,oBAAoB,GAAG,kBAAkB,EAAE,CAkBlF;AAED,kFAAkF;AAClF,eAAO,MAAM,mBAAmB,EAAE,WAAW,CAAC,MAAM,CAIlD,CAAC;AAEH;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,oBAAoB,GAAG,kBAAkB,EAAE,CAa9E"}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
// PINNED-BY-BEGIN (generated by gen:pinned-by -- do not hand-edit; checked by lint:pinned-by)
|
|
2
|
+
// Pinned by: packages/daemon/src/focus/focus-activity-producers.test.ts
|
|
3
|
+
// PINNED-BY-END
|
|
4
|
+
/**
|
|
5
|
+
* The focus activity record's team and between-sessions producers: a live
|
|
6
|
+
* coding or review team, a spawn waiting for a session slot, a queued spawn, a
|
|
7
|
+
* spawn cooldown, a parked review, and idle. (Gate entries:
|
|
8
|
+
* focus/focus-activity-record.ts; held deliveries: focus/focus-activity-held.ts.)
|
|
9
|
+
*
|
|
10
|
+
* Each producer reports FACTS WITH TIMESTAMPS -- when a wait began, when a hold
|
|
11
|
+
* ends, how many deliveries still owe work. None classifies a focus as stalled:
|
|
12
|
+
* that judgement belongs to whoever reads the record. Which entry a focus
|
|
13
|
+
* publishes when several apply is the daemon-core precedence rule, applied by
|
|
14
|
+
* the builder; which focuses may carry a between-sessions entry at all (armed
|
|
15
|
+
* ones) is the builder's admission rule too.
|
|
16
|
+
*
|
|
17
|
+
* @module focus/focus-activity-producers
|
|
18
|
+
*/
|
|
19
|
+
import { DELIVERY_STATUS } from '@telora/daemon-core';
|
|
20
|
+
import { armedPolledFocuses, commonFields, iso, } from './focus-activity-sources.js';
|
|
21
|
+
/** team_coding / team_reviewing: one claim per live team. */
|
|
22
|
+
export function teamActivityClaims(sources) {
|
|
23
|
+
return sources.teams.map((team) => ({
|
|
24
|
+
focusId: team.focusId,
|
|
25
|
+
activity: {
|
|
26
|
+
...commonFields(sources, team.focusId),
|
|
27
|
+
kind: team.sessionType === 'review' ? 'team_reviewing' : 'team_coding',
|
|
28
|
+
sessionId: team.sessionId,
|
|
29
|
+
lastAgentEventAt: team.lastAgentEventAt === null ? null : iso(team.lastAgentEventAt),
|
|
30
|
+
},
|
|
31
|
+
}));
|
|
32
|
+
}
|
|
33
|
+
/** awaiting_slot: a spawn pass would start a team but every session slot is in use. */
|
|
34
|
+
export function awaitingSlotClaims(sources) {
|
|
35
|
+
return sources.awaitingSlot.map(({ focusId, sinceMs }) => ({
|
|
36
|
+
focusId,
|
|
37
|
+
activity: { ...commonFields(sources, focusId), kind: 'awaiting_slot', since: iso(sinceMs) },
|
|
38
|
+
}));
|
|
39
|
+
}
|
|
40
|
+
/** spawn_pending: a team spawn is queued and starts on the next spawn pass (dated by first sight). */
|
|
41
|
+
export function spawnPendingClaims(sources) {
|
|
42
|
+
return sources.pendingSpawns.map(({ focusId, sessionType }) => ({
|
|
43
|
+
focusId,
|
|
44
|
+
activity: { ...commonFields(sources, focusId), kind: 'spawn_pending', sessionType, since: iso(sources.now) },
|
|
45
|
+
sinceKey: `${focusId}|spawn_pending|${sessionType}`,
|
|
46
|
+
}));
|
|
47
|
+
}
|
|
48
|
+
/** review_parked: the review-hold park is withholding the focus's queued review spawn. */
|
|
49
|
+
export function reviewParkedClaims(sources) {
|
|
50
|
+
return [...sources.reviewParks].map(([focusId, park]) => ({
|
|
51
|
+
focusId,
|
|
52
|
+
activity: { ...commonFields(sources, focusId), kind: 'review_parked', reason: park.reason, since: iso(park.sinceMs) },
|
|
53
|
+
}));
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* cooldown: the focus's next spawn is held by its spawn cooldown -- until the
|
|
57
|
+
* time it ends, or with no end when the respawn cycle is exhausted. `since` is
|
|
58
|
+
* the latest teardown; a hold with no teardown of its own (the provider's
|
|
59
|
+
* rate-limit window alone) is dated by first sight.
|
|
60
|
+
*/
|
|
61
|
+
export function cooldownClaims(sources) {
|
|
62
|
+
const claims = [];
|
|
63
|
+
for (const { focusId } of armedPolledFocuses(sources)) {
|
|
64
|
+
const window = sources.readCooldownWindow(focusId);
|
|
65
|
+
if (!window)
|
|
66
|
+
continue;
|
|
67
|
+
claims.push({
|
|
68
|
+
focusId,
|
|
69
|
+
activity: {
|
|
70
|
+
...commonFields(sources, focusId),
|
|
71
|
+
kind: 'cooldown',
|
|
72
|
+
until: window.untilMs === null ? null : iso(window.untilMs),
|
|
73
|
+
since: iso(window.sinceMs ?? sources.now),
|
|
74
|
+
exhausted: window.exhausted,
|
|
75
|
+
},
|
|
76
|
+
...(window.sinceMs === null ? { sinceKey: `${focusId}|cooldown` } : {}),
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
return claims;
|
|
80
|
+
}
|
|
81
|
+
/** The statuses in which a delivery still owes work (a team would pick it up). */
|
|
82
|
+
export const OWING_WORK_STATUSES = new Set([
|
|
83
|
+
DELIVERY_STATUS.QUEUED,
|
|
84
|
+
DELIVERY_STATUS.CODING,
|
|
85
|
+
DELIVERY_STATUS.VERIFY_FAILED,
|
|
86
|
+
]);
|
|
87
|
+
/**
|
|
88
|
+
* idle: an armed focus the daemon is doing nothing else for, with the count of
|
|
89
|
+
* the deliveries it has READ that still owe work. The lowest precedence: every
|
|
90
|
+
* other entry wins over it. Dated by first sight of the idle entry.
|
|
91
|
+
*
|
|
92
|
+
* EVERY armed focus is claimed, so every armed focus of every product has an
|
|
93
|
+
* entry. The count comes from the latest SUCCESSFUL read of its deliveries: a
|
|
94
|
+
* read that fails after one succeeded leaves those rows standing
|
|
95
|
+
* (focus-activity-poll-facts.ts), so the count stays the last one the daemon
|
|
96
|
+
* saw. A focus whose deliveries have not been read yet -- listed by this poll,
|
|
97
|
+
* its delivery read still to come or failing since discovery -- publishes a
|
|
98
|
+
* null count: the inventory is UNKNOWN, so 0 always means a read that found
|
|
99
|
+
* nothing owed, never "not read". Dropping the focus instead would make an
|
|
100
|
+
* armed focus vanish from the record exactly when its reads fail. The unread
|
|
101
|
+
* entry is dated by its own first sight, so the first successful read starts
|
|
102
|
+
* the idle clock afresh rather than inheriting the time spent unread.
|
|
103
|
+
*/
|
|
104
|
+
export function idleClaims(sources) {
|
|
105
|
+
const claims = [];
|
|
106
|
+
for (const { focusId, deliveries } of armedPolledFocuses(sources)) {
|
|
107
|
+
const owing = deliveries === null
|
|
108
|
+
? null
|
|
109
|
+
: deliveries.filter((d) => OWING_WORK_STATUSES.has(d.executionStatus ?? '')).length;
|
|
110
|
+
claims.push({
|
|
111
|
+
focusId,
|
|
112
|
+
activity: { ...commonFields(sources, focusId), kind: 'idle', deliveriesOwingWork: owing, since: iso(sources.now) },
|
|
113
|
+
sinceKey: owing === null ? `${focusId}|idle_unread` : `${focusId}|idle`,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
return claims;
|
|
117
|
+
}
|
|
118
|
+
//# sourceMappingURL=focus-activity-producers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"focus-activity-producers.js","sourceRoot":"","sources":["../../src/focus/focus-activity-producers.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The focus activity record the heartbeat publishes as `metadata.focusActivity`
|
|
3
|
+
* ({ [focusId]: FocusActivity }) -- the ONE statement of what this daemon is
|
|
4
|
+
* doing for each focus it manages. The kinds, their fields and the precedence
|
|
5
|
+
* rule are declared in `@telora/daemon-core` (focus-activity.ts); this module
|
|
6
|
+
* only BUILDS the map from in-process facts.
|
|
7
|
+
*
|
|
8
|
+
* Pull model, like the narrations map: recomputed from live in-memory state every beat,
|
|
9
|
+
* so a change surfaces within one interval and a finished gate or torn-down team
|
|
10
|
+
* drops out on its own. Nothing here is written to the database.
|
|
11
|
+
*
|
|
12
|
+
* Shape: {@link buildFocusActivityMap} runs every producer in
|
|
13
|
+
* {@link FOCUS_ACTIVITY_PRODUCERS} over the sources and keeps, per focus, the
|
|
14
|
+
* entry the daemon-core precedence rule picks (gate entries win over team and
|
|
15
|
+
* idle entries). A new kind is added as a new producer -- the builder and the
|
|
16
|
+
* heartbeat wiring do not change.
|
|
17
|
+
*
|
|
18
|
+
* Who gets an entry: a focus the daemon is DOING something for -- a live team
|
|
19
|
+
* or a gate run -- whether or not it is armed; and every ARMED focus of every
|
|
20
|
+
* product (as the latest poll read them, focus/focus-activity-poll-facts.ts),
|
|
21
|
+
* which always has at least `idle`. The between-sessions kinds (held, parked,
|
|
22
|
+
* cooldown, awaiting a slot, a pending spawn, idle) are admitted only for armed
|
|
23
|
+
* focuses, so an idle un-armed focus gets no entry.
|
|
24
|
+
*
|
|
25
|
+
* The record states facts with timestamps; it never classifies a focus as
|
|
26
|
+
* stalled. Every entry carries the spawn-teardown streak and the last-progress
|
|
27
|
+
* time, so churn is visible to whoever reads it.
|
|
28
|
+
*
|
|
29
|
+
* @module focus/focus-activity-record
|
|
30
|
+
*/
|
|
31
|
+
import { type FocusActivityMap } from '@telora/daemon-core';
|
|
32
|
+
import { type FocusActivityClaim, type FocusActivityProducer, type FocusActivitySources } from './focus-activity-sources.js';
|
|
33
|
+
/**
|
|
34
|
+
* gate_running / gate_queued, one claim per selected run with a focus. A run whose
|
|
35
|
+
* command has started is running, with the commit and timeout its registration
|
|
36
|
+
* fixed at that start (verify/gate-verdicts/gate-run-registry.ts) -- a start always
|
|
37
|
+
* names its commit. Every other run is queued on what its registration says: its
|
|
38
|
+
* focus worktree's verify interlock, naming the focus and delivery the hold
|
|
39
|
+
* judges; the daemon-wide verification serialisation, naming the run that holds
|
|
40
|
+
* the slot (running, or in its preflight) as the one ahead of it (null when none
|
|
41
|
+
* does yet); or its own preflight -- it holds its slot and is preparing the run,
|
|
42
|
+
* with nothing ahead.
|
|
43
|
+
*/
|
|
44
|
+
export declare function gateActivityClaims(sources: FocusActivitySources): FocusActivityClaim[];
|
|
45
|
+
/** Every producer the record is built from. Order does not decide the winner; precedence does. */
|
|
46
|
+
export declare const FOCUS_ACTIVITY_PRODUCERS: readonly FocusActivityProducer[];
|
|
47
|
+
/** Build the per-focus map: the precedence winner among every admitted claim. Pure but for sources.firstSeen. */
|
|
48
|
+
export declare function buildFocusActivityMap(sources: FocusActivitySources, producers: readonly FocusActivityProducer[]): FocusActivityMap;
|
|
49
|
+
/** The record from every declared producer (FOCUS_ACTIVITY_PRODUCERS). */
|
|
50
|
+
export declare function buildFocusActivityRecord(sources: FocusActivitySources): FocusActivityMap;
|
|
51
|
+
/**
|
|
52
|
+
* Set `metadata.focusActivity` from the beat's sources; the key is absent when
|
|
53
|
+
* no focus has an entry. The live sources are bound at the composition root
|
|
54
|
+
* (composition/focus-activity-live-sources.ts) and reach here through the
|
|
55
|
+
* heartbeat's deps.
|
|
56
|
+
*/
|
|
57
|
+
export declare function applyFocusActivity(metadata: Record<string, unknown>, sources: FocusActivitySources): void;
|
|
58
|
+
//# sourceMappingURL=focus-activity-record.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"focus-activity-record.d.ts","sourceRoot":"","sources":["../../src/focus/focus-activity-record.ts"],"names":[],"mappings":"AAQA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH,OAAO,EAIL,KAAK,gBAAgB,EACtB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAGL,KAAK,kBAAkB,EACvB,KAAK,qBAAqB,EAC1B,KAAK,oBAAoB,EAC1B,MAAM,6BAA6B,CAAC;AAmBrC;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,oBAAoB,GAAG,kBAAkB,EAAE,CAgBtF;AAED,kGAAkG;AAClG,eAAO,MAAM,wBAAwB,EAAE,SAAS,qBAAqB,EASpE,CAAC;AAqBF,iHAAiH;AACjH,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,oBAAoB,EAC7B,SAAS,EAAE,SAAS,qBAAqB,EAAE,GAC1C,gBAAgB,CAWlB;AAED,0EAA0E;AAC1E,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,oBAAoB,GAAG,gBAAgB,CAExF;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,EAAE,oBAAoB,GAAG,IAAI,CAGzG"}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
// PINNED-BY-BEGIN (generated by gen:pinned-by -- do not hand-edit; checked by lint:pinned-by)
|
|
2
|
+
// Pinned by: packages/daemon/src/focus/focus-activity-producers.test.ts,
|
|
3
|
+
// packages/daemon/src/focus/focus-activity-progress-clock.test.ts,
|
|
4
|
+
// packages/daemon/src/focus/focus-activity-record.test.ts,
|
|
5
|
+
// packages/daemon/src/focus/focus-activity-team-clock.test.ts,
|
|
6
|
+
// packages/daemon/src/verify/gate-verdicts/gate-run-activity.test.ts
|
|
7
|
+
// PINNED-BY-END
|
|
8
|
+
/**
|
|
9
|
+
* The focus activity record the heartbeat publishes as `metadata.focusActivity`
|
|
10
|
+
* ({ [focusId]: FocusActivity }) -- the ONE statement of what this daemon is
|
|
11
|
+
* doing for each focus it manages. The kinds, their fields and the precedence
|
|
12
|
+
* rule are declared in `@telora/daemon-core` (focus-activity.ts); this module
|
|
13
|
+
* only BUILDS the map from in-process facts.
|
|
14
|
+
*
|
|
15
|
+
* Pull model, like the narrations map: recomputed from live in-memory state every beat,
|
|
16
|
+
* so a change surfaces within one interval and a finished gate or torn-down team
|
|
17
|
+
* drops out on its own. Nothing here is written to the database.
|
|
18
|
+
*
|
|
19
|
+
* Shape: {@link buildFocusActivityMap} runs every producer in
|
|
20
|
+
* {@link FOCUS_ACTIVITY_PRODUCERS} over the sources and keeps, per focus, the
|
|
21
|
+
* entry the daemon-core precedence rule picks (gate entries win over team and
|
|
22
|
+
* idle entries). A new kind is added as a new producer -- the builder and the
|
|
23
|
+
* heartbeat wiring do not change.
|
|
24
|
+
*
|
|
25
|
+
* Who gets an entry: a focus the daemon is DOING something for -- a live team
|
|
26
|
+
* or a gate run -- whether or not it is armed; and every ARMED focus of every
|
|
27
|
+
* product (as the latest poll read them, focus/focus-activity-poll-facts.ts),
|
|
28
|
+
* which always has at least `idle`. The between-sessions kinds (held, parked,
|
|
29
|
+
* cooldown, awaiting a slot, a pending spawn, idle) are admitted only for armed
|
|
30
|
+
* focuses, so an idle un-armed focus gets no entry.
|
|
31
|
+
*
|
|
32
|
+
* The record states facts with timestamps; it never classifies a focus as
|
|
33
|
+
* stalled. Every entry carries the spawn-teardown streak and the last-progress
|
|
34
|
+
* time, so churn is visible to whoever reads it.
|
|
35
|
+
*
|
|
36
|
+
* @module focus/focus-activity-record
|
|
37
|
+
*/
|
|
38
|
+
import { pickFocusActivity, } from '@telora/daemon-core';
|
|
39
|
+
import { commonFields, iso, } from './focus-activity-sources.js';
|
|
40
|
+
import { awaitingSlotClaims, cooldownClaims, idleClaims, reviewParkedClaims, spawnPendingClaims, teamActivityClaims, } from './focus-activity-producers.js';
|
|
41
|
+
import { deliveryHeldClaims } from './focus-activity-held.js';
|
|
42
|
+
/** The run ahead of a queued one, by what it waits on (see gateActivityClaims). */
|
|
43
|
+
function aheadOf(run, slotHolder) {
|
|
44
|
+
if (run.wait.on === 'worktree_interlock')
|
|
45
|
+
return { focusId: run.wait.aheadFocusId, deliveryId: run.wait.aheadDeliveryId };
|
|
46
|
+
if (run.wait.on === 'preflight')
|
|
47
|
+
return { focusId: null, deliveryId: null };
|
|
48
|
+
return { focusId: slotHolder?.focusId ?? null, deliveryId: slotHolder?.deliveryId ?? null };
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* gate_running / gate_queued, one claim per selected run with a focus. A run whose
|
|
52
|
+
* command has started is running, with the commit and timeout its registration
|
|
53
|
+
* fixed at that start (verify/gate-verdicts/gate-run-registry.ts) -- a start always
|
|
54
|
+
* names its commit. Every other run is queued on what its registration says: its
|
|
55
|
+
* focus worktree's verify interlock, naming the focus and delivery the hold
|
|
56
|
+
* judges; the daemon-wide verification serialisation, naming the run that holds
|
|
57
|
+
* the slot (running, or in its preflight) as the one ahead of it (null when none
|
|
58
|
+
* does yet); or its own preflight -- it holds its slot and is preparing the run,
|
|
59
|
+
* with nothing ahead.
|
|
60
|
+
*/
|
|
61
|
+
export function gateActivityClaims(sources) {
|
|
62
|
+
const slotHolder = sources.gateRuns.find((r) => r.startedAt !== null || r.wait.on === 'preflight') ?? null;
|
|
63
|
+
const claims = [];
|
|
64
|
+
for (const run of sources.gateRuns) {
|
|
65
|
+
if (run.focusId === null)
|
|
66
|
+
continue;
|
|
67
|
+
const common = commonFields(sources, run.focusId);
|
|
68
|
+
const ahead = aheadOf(run, slotHolder);
|
|
69
|
+
const activity = run.startedAt !== null && run.sha !== null
|
|
70
|
+
? { ...common, kind: 'gate_running', deliveryId: run.deliveryId, sha: run.sha, startedAt: iso(run.startedAt), timeoutMs: run.timeoutMs }
|
|
71
|
+
: {
|
|
72
|
+
...common, kind: 'gate_queued', deliveryId: run.deliveryId, queuedAt: iso(run.selectedAt),
|
|
73
|
+
waitingOn: run.wait.on, aheadFocusId: ahead.focusId, aheadDeliveryId: ahead.deliveryId,
|
|
74
|
+
};
|
|
75
|
+
claims.push({ focusId: run.focusId, activity });
|
|
76
|
+
}
|
|
77
|
+
return claims;
|
|
78
|
+
}
|
|
79
|
+
/** Every producer the record is built from. Order does not decide the winner; precedence does. */
|
|
80
|
+
export const FOCUS_ACTIVITY_PRODUCERS = [
|
|
81
|
+
gateActivityClaims,
|
|
82
|
+
teamActivityClaims,
|
|
83
|
+
deliveryHeldClaims,
|
|
84
|
+
reviewParkedClaims,
|
|
85
|
+
cooldownClaims,
|
|
86
|
+
awaitingSlotClaims,
|
|
87
|
+
spawnPendingClaims,
|
|
88
|
+
idleClaims,
|
|
89
|
+
];
|
|
90
|
+
/** The kinds any focus may carry (the daemon is doing something for it); every other kind needs an armed focus. */
|
|
91
|
+
const DOING_KINDS = new Set(['gate_running', 'gate_queued', 'team_reviewing', 'team_coding']);
|
|
92
|
+
/** Date each winner that carries no start of its own by the first beat it was seen, and forget the rest. */
|
|
93
|
+
function stampObservedSince(winners, sources) {
|
|
94
|
+
const map = {};
|
|
95
|
+
const observed = new Set();
|
|
96
|
+
for (const [focusId, { activity, sinceKey }] of winners) {
|
|
97
|
+
if (sinceKey !== undefined && 'since' in activity) {
|
|
98
|
+
observed.add(sinceKey);
|
|
99
|
+
map[focusId] = { ...activity, since: iso(sources.firstSeen.observe(sinceKey, sources.now)) };
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
map[focusId] = activity;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
sources.firstSeen.retainOnly(observed);
|
|
106
|
+
return map;
|
|
107
|
+
}
|
|
108
|
+
/** Build the per-focus map: the precedence winner among every admitted claim. Pure but for sources.firstSeen. */
|
|
109
|
+
export function buildFocusActivityMap(sources, producers) {
|
|
110
|
+
const armed = new Set(sources.polledFocuses.filter((f) => f.armed).map((f) => f.focusId));
|
|
111
|
+
const winners = new Map();
|
|
112
|
+
for (const produce of producers) {
|
|
113
|
+
for (const claim of produce(sources)) {
|
|
114
|
+
if (!DOING_KINDS.has(claim.activity.kind) && !armed.has(claim.focusId))
|
|
115
|
+
continue;
|
|
116
|
+
const current = winners.get(claim.focusId);
|
|
117
|
+
if (pickFocusActivity(current?.activity, claim.activity) === claim.activity)
|
|
118
|
+
winners.set(claim.focusId, claim);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return stampObservedSince(winners, sources);
|
|
122
|
+
}
|
|
123
|
+
/** The record from every declared producer (FOCUS_ACTIVITY_PRODUCERS). */
|
|
124
|
+
export function buildFocusActivityRecord(sources) {
|
|
125
|
+
return buildFocusActivityMap(sources, FOCUS_ACTIVITY_PRODUCERS);
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Set `metadata.focusActivity` from the beat's sources; the key is absent when
|
|
129
|
+
* no focus has an entry. The live sources are bound at the composition root
|
|
130
|
+
* (composition/focus-activity-live-sources.ts) and reach here through the
|
|
131
|
+
* heartbeat's deps.
|
|
132
|
+
*/
|
|
133
|
+
export function applyFocusActivity(metadata, sources) {
|
|
134
|
+
const focusActivity = buildFocusActivityRecord(sources);
|
|
135
|
+
if (Object.keys(focusActivity).length > 0)
|
|
136
|
+
metadata.focusActivity = focusActivity;
|
|
137
|
+
}
|
|
138
|
+
//# sourceMappingURL=focus-activity-record.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"focus-activity-record.js","sourceRoot":"","sources":["../../src/focus/focus-activity-record.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The in-process facts the focus activity record (focus/focus-activity-record.ts)
|
|
3
|
+
* is built from, the claim a producer makes, and the fields every entry
|
|
4
|
+
* carries. Split out so the producers (focus/focus-activity-producers.ts,
|
|
5
|
+
* focus/focus-activity-held.ts) and the builder share one declaration without
|
|
6
|
+
* an import cycle.
|
|
7
|
+
*
|
|
8
|
+
* Every source is a snapshot taken at the beat: the builder and producers are
|
|
9
|
+
* pure over it. The one piece of carried state is the {@link FirstSeenTracker}:
|
|
10
|
+
* a fact with no recorded start time (an idle focus, a pending spawn, an
|
|
11
|
+
* operator hold marker) is dated by the first beat the daemon saw it standing.
|
|
12
|
+
*
|
|
13
|
+
* @module focus/focus-activity-sources
|
|
14
|
+
*/
|
|
15
|
+
import type { FocusActivity, FocusActivityCommon } from '@telora/daemon-core';
|
|
16
|
+
import type { GateRunView } from '../verify/gate-verdicts/gate-run-registry.js';
|
|
17
|
+
import type { ReviewPark } from '../daemon-state/review-park-record.js';
|
|
18
|
+
import type { SpawnCooldownWindow } from '../supervision/spawn-cooldown-window.js';
|
|
19
|
+
import type { PolledFocus } from './focus-activity-poll-facts.js';
|
|
20
|
+
/** A live team as the record reads it. */
|
|
21
|
+
export interface TeamView {
|
|
22
|
+
focusId: string;
|
|
23
|
+
sessionType: 'coding' | 'review';
|
|
24
|
+
sessionId: string | null;
|
|
25
|
+
/** Epoch ms of the team's last agent event (focus-team-state's in-memory activity clock), or null. */
|
|
26
|
+
lastAgentEventAt: number | null;
|
|
27
|
+
}
|
|
28
|
+
/** A queued team spawn as the record reads it. */
|
|
29
|
+
export interface PendingSpawnView {
|
|
30
|
+
focusId: string;
|
|
31
|
+
sessionType: 'coding' | 'review';
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Dates a standing fact by the first beat it was seen. `observe` returns the
|
|
35
|
+
* first time the key was observed in the current unbroken run of beats;
|
|
36
|
+
* `retainOnly` forgets every key not observed this beat, so a fact that stops
|
|
37
|
+
* and starts again is dated from its restart.
|
|
38
|
+
*/
|
|
39
|
+
export declare class FirstSeenTracker {
|
|
40
|
+
private readonly seen;
|
|
41
|
+
observe(key: string, now: number): number;
|
|
42
|
+
retainOnly(keys: ReadonlySet<string>): void;
|
|
43
|
+
}
|
|
44
|
+
/** The in-process facts the record is built from, as of one beat. */
|
|
45
|
+
export interface FocusActivitySources {
|
|
46
|
+
/** The daemon clock at this beat (epoch ms). */
|
|
47
|
+
now: number;
|
|
48
|
+
/** Every selected, unfinished gate run (verify/gate-verdicts/gate-run-registry.ts). */
|
|
49
|
+
gateRuns: readonly GateRunView[];
|
|
50
|
+
/**
|
|
51
|
+
* When the focus's work last moved forward -- a delivery advancing or an issue
|
|
52
|
+
* moving to Done (daemon-state/focus-progress-clock.ts) -- in epoch ms, or null.
|
|
53
|
+
* Kept per focus, not per team, so it survives teardowns and the gaps between sessions.
|
|
54
|
+
*/
|
|
55
|
+
readLastProgressAt: (focusId: string) => number | null;
|
|
56
|
+
/** The focus's spawn-teardown streak (supervision/spawn-cooldown.ts getRespawnCycleStreak), or null. */
|
|
57
|
+
readTeardownStreak: (focusId: string) => {
|
|
58
|
+
firstTime: number;
|
|
59
|
+
count: number;
|
|
60
|
+
} | null;
|
|
61
|
+
/** Every live team (daemon-state/focus-team-state.ts). */
|
|
62
|
+
teams: readonly TeamView[];
|
|
63
|
+
/** Focuses a spawn pass left waiting for a session slot, with when the wait began (daemon-state/awaiting-slot.ts). */
|
|
64
|
+
awaitingSlot: readonly {
|
|
65
|
+
focusId: string;
|
|
66
|
+
sinceMs: number;
|
|
67
|
+
}[];
|
|
68
|
+
/** Queued team spawns (directive/directive-queue.ts). */
|
|
69
|
+
pendingSpawns: readonly PendingSpawnView[];
|
|
70
|
+
/** Standing review parks (daemon-state/review-park-record.ts). */
|
|
71
|
+
reviewParks: ReadonlyMap<string, ReviewPark>;
|
|
72
|
+
/** The cooldown window holding the focus's next spawn now, or null (spawn/next-spawn-cooldown.ts). */
|
|
73
|
+
readCooldownWindow: (focusId: string) => SpawnCooldownWindow | null;
|
|
74
|
+
/** The active focuses and their deliveries as the latest poll read them (focus/focus-activity-poll-facts.ts). */
|
|
75
|
+
polledFocuses: readonly PolledFocus[];
|
|
76
|
+
/** Dates facts that carry no start time of their own. */
|
|
77
|
+
firstSeen: FirstSeenTracker;
|
|
78
|
+
}
|
|
79
|
+
/** One producer's claim about one focus. */
|
|
80
|
+
export interface FocusActivityClaim {
|
|
81
|
+
focusId: string;
|
|
82
|
+
activity: FocusActivity;
|
|
83
|
+
/**
|
|
84
|
+
* Present when the fact has no recorded start: the builder replaces the
|
|
85
|
+
* entry's `since` with the first beat this key was seen (FirstSeenTracker).
|
|
86
|
+
*/
|
|
87
|
+
sinceKey?: string;
|
|
88
|
+
}
|
|
89
|
+
export type FocusActivityProducer = (sources: FocusActivitySources) => FocusActivityClaim[];
|
|
90
|
+
export declare const iso: (ms: number) => string;
|
|
91
|
+
/** The fields every entry carries: the beat's clock, the teardown streak, the last progress. */
|
|
92
|
+
export declare function commonFields(sources: FocusActivitySources, focusId: string): FocusActivityCommon;
|
|
93
|
+
/** The armed focuses the latest poll saw. */
|
|
94
|
+
export declare function armedPolledFocuses(sources: FocusActivitySources): PolledFocus[];
|
|
95
|
+
//# sourceMappingURL=focus-activity-sources.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"focus-activity-sources.d.ts","sourceRoot":"","sources":["../../src/focus/focus-activity-sources.ts"],"names":[],"mappings":"AAKA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,mBAAmB,EAAuB,MAAM,qBAAqB,CAAC;AACnG,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,8CAA8C,CAAC;AAChF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uCAAuC,CAAC;AACxE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,yCAAyC,CAAC;AACnF,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gCAAgC,CAAC;AAElE,0CAA0C;AAC1C,MAAM,WAAW,QAAQ;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,QAAQ,GAAG,QAAQ,CAAC;IACjC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,sGAAsG;IACtG,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;CACjC;AAED,kDAAkD;AAClD,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,QAAQ,GAAG,QAAQ,CAAC;CAClC;AAED;;;;;GAKG;AACH,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAAC,IAAI,CAA6B;IAElD,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM;IAOzC,UAAU,CAAC,IAAI,EAAE,WAAW,CAAC,MAAM,CAAC,GAAG,IAAI;CAG5C;AAED,qEAAqE;AACrE,MAAM,WAAW,oBAAoB;IACnC,gDAAgD;IAChD,GAAG,EAAE,MAAM,CAAC;IACZ,uFAAuF;IACvF,QAAQ,EAAE,SAAS,WAAW,EAAE,CAAC;IACjC;;;;OAIG;IACH,kBAAkB,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAC;IACvD,wGAAwG;IACxG,kBAAkB,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IACrF,0DAA0D;IAC1D,KAAK,EAAE,SAAS,QAAQ,EAAE,CAAC;IAC3B,sHAAsH;IACtH,YAAY,EAAE,SAAS;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC9D,yDAAyD;IACzD,aAAa,EAAE,SAAS,gBAAgB,EAAE,CAAC;IAC3C,kEAAkE;IAClE,WAAW,EAAE,WAAW,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IAC7C,sGAAsG;IACtG,kBAAkB,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,mBAAmB,GAAG,IAAI,CAAC;IACpE,iHAAiH;IACjH,aAAa,EAAE,SAAS,WAAW,EAAE,CAAC;IACtC,yDAAyD;IACzD,SAAS,EAAE,gBAAgB,CAAC;CAC7B;AAED,4CAA4C;AAC5C,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,aAAa,CAAC;IACxB;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,MAAM,qBAAqB,GAAG,CAAC,OAAO,EAAE,oBAAoB,KAAK,kBAAkB,EAAE,CAAC;AAE5F,eAAO,MAAM,GAAG,GAAI,IAAI,MAAM,KAAG,MAAoC,CAAC;AAQtE,gGAAgG;AAChG,wBAAgB,YAAY,CAAC,OAAO,EAAE,oBAAoB,EAAE,OAAO,EAAE,MAAM,GAAG,mBAAmB,CAOhG;AAED,6CAA6C;AAC7C,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,oBAAoB,GAAG,WAAW,EAAE,CAE/E"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// PINNED-BY-BEGIN (generated by gen:pinned-by -- do not hand-edit; checked by lint:pinned-by)
|
|
2
|
+
// Pinned by: packages/daemon/src/focus/focus-activity-producers.test.ts,
|
|
3
|
+
// packages/daemon/src/focus/focus-activity-record.test.ts
|
|
4
|
+
// PINNED-BY-END
|
|
5
|
+
/**
|
|
6
|
+
* Dates a standing fact by the first beat it was seen. `observe` returns the
|
|
7
|
+
* first time the key was observed in the current unbroken run of beats;
|
|
8
|
+
* `retainOnly` forgets every key not observed this beat, so a fact that stops
|
|
9
|
+
* and starts again is dated from its restart.
|
|
10
|
+
*/
|
|
11
|
+
export class FirstSeenTracker {
|
|
12
|
+
seen = new Map();
|
|
13
|
+
observe(key, now) {
|
|
14
|
+
const first = this.seen.get(key);
|
|
15
|
+
if (first !== undefined)
|
|
16
|
+
return first;
|
|
17
|
+
this.seen.set(key, now);
|
|
18
|
+
return now;
|
|
19
|
+
}
|
|
20
|
+
retainOnly(keys) {
|
|
21
|
+
for (const key of this.seen.keys())
|
|
22
|
+
if (!keys.has(key))
|
|
23
|
+
this.seen.delete(key);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
export const iso = (ms) => new Date(ms).toISOString();
|
|
27
|
+
function streakField(sources, focusId) {
|
|
28
|
+
const streak = sources.readTeardownStreak(focusId);
|
|
29
|
+
// A count-0 entry is a rate-limit hold only: no teardown has happened.
|
|
30
|
+
return streak && streak.count > 0 ? { count: streak.count, firstAt: iso(streak.firstTime) } : null;
|
|
31
|
+
}
|
|
32
|
+
/** The fields every entry carries: the beat's clock, the teardown streak, the last progress. */
|
|
33
|
+
export function commonFields(sources, focusId) {
|
|
34
|
+
const progress = sources.readLastProgressAt(focusId);
|
|
35
|
+
return {
|
|
36
|
+
computedAt: iso(sources.now),
|
|
37
|
+
spawnTeardownStreak: streakField(sources, focusId),
|
|
38
|
+
lastProgressAt: progress === null ? null : iso(progress),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
/** The armed focuses the latest poll saw. */
|
|
42
|
+
export function armedPolledFocuses(sources) {
|
|
43
|
+
return sources.polledFocuses.filter((f) => f.armed);
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=focus-activity-sources.js.map
|