@llm-dev-ops/agentics-cli 2.7.26 → 2.7.28

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.
@@ -0,0 +1,88 @@
1
+ /**
2
+ * ADR-PIPELINE-099 D3 + D4 — Process-wide pipeline heartbeat.
3
+ *
4
+ * Singleton that emits a continuous "still alive" signal for the entire
5
+ * `agentics ask` lifecycle, not just Phase 1. Replaces the per-call heartbeat
6
+ * inside `executeNaturalLanguageRoute` which died as soon as auto-chain
7
+ * took over Phases 2–7, leaving the user staring at a silent terminal for
8
+ * minutes at a stretch.
9
+ *
10
+ * Two render modes:
11
+ *
12
+ * - **TTY** (`stderr.isTTY` and not opted out): a single status line is
13
+ * repainted in place every 250ms at the cursor position using a four-frame
14
+ * spinner (`◐ ◓ ◑ ◒`). The line shows the current phase, total elapsed
15
+ * time, time-since-phase-entry, and the most recent activity hint set by
16
+ * the pipeline. Other writers (`console.error`, `process.stderr.write`)
17
+ * print above the status line via cursor save/restore.
18
+ *
19
+ * - **Non-TTY** (CI, piped output, `AGENTICS_NO_HEARTBEAT=1`): the spinner
20
+ * is suppressed; instead a plain `[agentics] ⏳ still working — Phase X
21
+ * (MM:SS) — <activity>` line prints every 10 seconds, throttled to skip
22
+ * any tick that happened within 9s of another stderr write.
23
+ *
24
+ * The singleton is crash-safe: `process.on('exit' | 'SIGINT' | 'SIGTERM')`
25
+ * handlers call `stop()` to clear the live status line so it doesn't leave a
26
+ * stale frame on the user's terminal after Ctrl-C.
27
+ *
28
+ * Usage:
29
+ * ```ts
30
+ * import { pipelineHeartbeat } from './cli/ui/heartbeat.js';
31
+ * pipelineHeartbeat.start('agentics ask');
32
+ * pipelineHeartbeat.setPhase('Phase 4 — ADRs + DDDs');
33
+ * pipelineHeartbeat.setActivity('ruflo: writing adr-007');
34
+ * // ... long-running work ...
35
+ * pipelineHeartbeat.stop();
36
+ * ```
37
+ */
38
+ declare class PipelineHeartbeat {
39
+ private readonly started;
40
+ private phase;
41
+ private phaseStartedAt;
42
+ private activity;
43
+ private timer;
44
+ private mode;
45
+ private lastStderrWriteAt;
46
+ private exitHandlersInstalled;
47
+ /**
48
+ * Begin emitting heartbeat ticks. Idempotent — repeated `start()` calls
49
+ * with a different label update the label but do not restart the timer.
50
+ *
51
+ * Always uses plain-line mode (no stderr interception, no in-place
52
+ * cursor manipulation). The previous TTY-with-interceptor design risked
53
+ * swallowing other writers' output when the wrapped function chain
54
+ * misbehaved (observed in 2.7.27 — the user saw NO output for 6 min
55
+ * because the interceptor ate every console.error). Plain-line mode
56
+ * interleaves with other logs but is provably safe.
57
+ */
58
+ start(_label: string): void;
59
+ /**
60
+ * Update the current phase label. Emits an immediate `[agentics] ▶ <phase>`
61
+ * line to stderr so the user sees the boundary even before the next 10s
62
+ * tick. Resets the per-phase timer.
63
+ */
64
+ setPhase(phase: string): void;
65
+ /**
66
+ * Update the latest activity hint. Stored for the next 10s tick to print
67
+ * if no other writer beat it. Does NOT print on its own — that's what
68
+ * caused 2.7.27's silence.
69
+ */
70
+ setActivity(activity: string): void;
71
+ /** Stop the timer. */
72
+ stop(): void;
73
+ private tickPlain;
74
+ private installExitHandlers;
75
+ }
76
+ /** Process-wide singleton. */
77
+ export declare const pipelineHeartbeat: PipelineHeartbeat;
78
+ /**
79
+ * Bracket a phase with `[BEGIN]` / `[END]` log markers and update the
80
+ * heartbeat's phase label. Call at the top of each phase in auto-chain.
81
+ *
82
+ * Returns a `done()` closure that emits the matching `[END]` line when the
83
+ * phase finishes. Always pair `phaseBoundary` with its returned `done` to
84
+ * keep the trace greppable.
85
+ */
86
+ export declare function phaseBoundary(phaseLabel: string, totalElapsedFromStart: number): () => void;
87
+ export {};
88
+ //# sourceMappingURL=heartbeat.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"heartbeat.d.ts","sourceRoot":"","sources":["../../../src/cli/ui/heartbeat.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AAYH,cAAM,iBAAiB;IACrB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAc;IACtC,OAAO,CAAC,KAAK,CAAc;IAC3B,OAAO,CAAC,cAAc,CAAc;IACpC,OAAO,CAAC,QAAQ,CAAM;IACtB,OAAO,CAAC,KAAK,CAA+B;IAC5C,OAAO,CAAC,IAAI,CAA0B;IACtC,OAAO,CAAC,iBAAiB,CAAc;IACvC,OAAO,CAAC,qBAAqB,CAAS;IAEtC;;;;;;;;;;OAUG;IACH,KAAK,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAU3B;;;;OAIG;IACH,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAY7B;;;;OAIG;IACH,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAInC,sBAAsB;IACtB,IAAI,IAAI,IAAI;IAUZ,OAAO,CAAC,SAAS;IAYjB,OAAO,CAAC,mBAAmB;CAQ5B;AAED,8BAA8B;AAC9B,eAAO,MAAM,iBAAiB,mBAA0B,CAAC;AAEzD;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,UAAU,EAAE,MAAM,EAAE,qBAAqB,EAAE,MAAM,GAAG,MAAM,IAAI,CAkB3F"}
@@ -0,0 +1,158 @@
1
+ /**
2
+ * ADR-PIPELINE-099 D3 + D4 — Process-wide pipeline heartbeat.
3
+ *
4
+ * Singleton that emits a continuous "still alive" signal for the entire
5
+ * `agentics ask` lifecycle, not just Phase 1. Replaces the per-call heartbeat
6
+ * inside `executeNaturalLanguageRoute` which died as soon as auto-chain
7
+ * took over Phases 2–7, leaving the user staring at a silent terminal for
8
+ * minutes at a stretch.
9
+ *
10
+ * Two render modes:
11
+ *
12
+ * - **TTY** (`stderr.isTTY` and not opted out): a single status line is
13
+ * repainted in place every 250ms at the cursor position using a four-frame
14
+ * spinner (`◐ ◓ ◑ ◒`). The line shows the current phase, total elapsed
15
+ * time, time-since-phase-entry, and the most recent activity hint set by
16
+ * the pipeline. Other writers (`console.error`, `process.stderr.write`)
17
+ * print above the status line via cursor save/restore.
18
+ *
19
+ * - **Non-TTY** (CI, piped output, `AGENTICS_NO_HEARTBEAT=1`): the spinner
20
+ * is suppressed; instead a plain `[agentics] ⏳ still working — Phase X
21
+ * (MM:SS) — <activity>` line prints every 10 seconds, throttled to skip
22
+ * any tick that happened within 9s of another stderr write.
23
+ *
24
+ * The singleton is crash-safe: `process.on('exit' | 'SIGINT' | 'SIGTERM')`
25
+ * handlers call `stop()` to clear the live status line so it doesn't leave a
26
+ * stale frame on the user's terminal after Ctrl-C.
27
+ *
28
+ * Usage:
29
+ * ```ts
30
+ * import { pipelineHeartbeat } from './cli/ui/heartbeat.js';
31
+ * pipelineHeartbeat.start('agentics ask');
32
+ * pipelineHeartbeat.setPhase('Phase 4 — ADRs + DDDs');
33
+ * pipelineHeartbeat.setActivity('ruflo: writing adr-007');
34
+ * // ... long-running work ...
35
+ * pipelineHeartbeat.stop();
36
+ * ```
37
+ */
38
+ const PLAIN_TICK_MS = 10_000;
39
+ const PLAIN_QUIET_THRESHOLD_MS = 9_000;
40
+ function fmtElapsed(ms) {
41
+ const totalSec = Math.floor(ms / 1000);
42
+ const m = Math.floor(totalSec / 60);
43
+ const s = totalSec % 60;
44
+ return `${String(m).padStart(2, '0')}:${String(s).padStart(2, '0')}`;
45
+ }
46
+ class PipelineHeartbeat {
47
+ started = Date.now();
48
+ phase = 'starting';
49
+ phaseStartedAt = Date.now();
50
+ activity = '';
51
+ timer = null;
52
+ mode = 'off';
53
+ lastStderrWriteAt = Date.now();
54
+ exitHandlersInstalled = false;
55
+ /**
56
+ * Begin emitting heartbeat ticks. Idempotent — repeated `start()` calls
57
+ * with a different label update the label but do not restart the timer.
58
+ *
59
+ * Always uses plain-line mode (no stderr interception, no in-place
60
+ * cursor manipulation). The previous TTY-with-interceptor design risked
61
+ * swallowing other writers' output when the wrapped function chain
62
+ * misbehaved (observed in 2.7.27 — the user saw NO output for 6 min
63
+ * because the interceptor ate every console.error). Plain-line mode
64
+ * interleaves with other logs but is provably safe.
65
+ */
66
+ start(_label) {
67
+ if (this.timer)
68
+ return;
69
+ if (process.env['AGENTICS_HEARTBEAT_OFF'] === '1')
70
+ return;
71
+ this.mode = 'plain';
72
+ this.installExitHandlers();
73
+ this.timer = setInterval(() => this.tickPlain(), PLAIN_TICK_MS);
74
+ this.timer.unref();
75
+ }
76
+ /**
77
+ * Update the current phase label. Emits an immediate `[agentics] ▶ <phase>`
78
+ * line to stderr so the user sees the boundary even before the next 10s
79
+ * tick. Resets the per-phase timer.
80
+ */
81
+ setPhase(phase) {
82
+ if (this.phase === phase)
83
+ return;
84
+ this.phase = phase;
85
+ this.phaseStartedAt = Date.now();
86
+ this.activity = '';
87
+ if (this.mode !== 'off') {
88
+ const elapsed = fmtElapsed(Date.now() - this.started);
89
+ process.stderr.write(`[agentics] ▶ ${phase} · T+${elapsed}\n`);
90
+ this.lastStderrWriteAt = Date.now();
91
+ }
92
+ }
93
+ /**
94
+ * Update the latest activity hint. Stored for the next 10s tick to print
95
+ * if no other writer beat it. Does NOT print on its own — that's what
96
+ * caused 2.7.27's silence.
97
+ */
98
+ setActivity(activity) {
99
+ this.activity = activity;
100
+ }
101
+ /** Stop the timer. */
102
+ stop() {
103
+ if (this.timer) {
104
+ clearInterval(this.timer);
105
+ this.timer = null;
106
+ }
107
+ this.mode = 'off';
108
+ }
109
+ // ── Internal ─────────────────────────────────────────────────────────────
110
+ tickPlain() {
111
+ if (this.mode === 'off')
112
+ return;
113
+ // Skip if some other writer just printed something — the user already
114
+ // has a fresh signal that we're alive.
115
+ if (Date.now() - this.lastStderrWriteAt < PLAIN_QUIET_THRESHOLD_MS)
116
+ return;
117
+ const elapsed = fmtElapsed(Date.now() - this.started);
118
+ const phaseSegment = this.phase ? `${this.phase} (${fmtElapsed(Date.now() - this.phaseStartedAt)})` : '';
119
+ const activitySegment = this.activity ? ` — ${this.activity}` : '';
120
+ process.stderr.write(`[agentics] ⏳ still working — ${elapsed} — ${phaseSegment}${activitySegment}\n`);
121
+ this.lastStderrWriteAt = Date.now();
122
+ }
123
+ installExitHandlers() {
124
+ if (this.exitHandlersInstalled)
125
+ return;
126
+ this.exitHandlersInstalled = true;
127
+ const cleanup = () => this.stop();
128
+ process.on('exit', cleanup);
129
+ process.on('SIGINT', () => { cleanup(); process.exit(130); });
130
+ process.on('SIGTERM', () => { cleanup(); process.exit(143); });
131
+ }
132
+ }
133
+ /** Process-wide singleton. */
134
+ export const pipelineHeartbeat = new PipelineHeartbeat();
135
+ /**
136
+ * Bracket a phase with `[BEGIN]` / `[END]` log markers and update the
137
+ * heartbeat's phase label. Call at the top of each phase in auto-chain.
138
+ *
139
+ * Returns a `done()` closure that emits the matching `[END]` line when the
140
+ * phase finishes. Always pair `phaseBoundary` with its returned `done` to
141
+ * keep the trace greppable.
142
+ */
143
+ export function phaseBoundary(phaseLabel, totalElapsedFromStart) {
144
+ const tStart = Date.now();
145
+ const t = fmtElapsed(totalElapsedFromStart);
146
+ pipelineHeartbeat.setPhase(phaseLabel);
147
+ // ANSI bold for the [BEGIN] marker so it stands out in scrollback.
148
+ process.stderr.write(`\n${'='.repeat(72)}\n` +
149
+ ` ▶ ${phaseLabel} [BEGIN] T+${t}\n` +
150
+ `${'='.repeat(72)}\n`);
151
+ return () => {
152
+ const dur = fmtElapsed(Date.now() - tStart);
153
+ process.stderr.write(`${'='.repeat(72)}\n` +
154
+ ` ▼ ${phaseLabel} ✓ completed (${dur}) [END]\n` +
155
+ `${'='.repeat(72)}\n\n`);
156
+ };
157
+ }
158
+ //# sourceMappingURL=heartbeat.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"heartbeat.js","sourceRoot":"","sources":["../../../src/cli/ui/heartbeat.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"agents.d.ts","sourceRoot":"","sources":["../../src/commands/agents.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"agents.d.ts","sourceRoot":"","sources":["../../src/commands/agents.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAIH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAC;AAC/D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAMxD,OAAO,EAAwB,KAAK,mBAAmB,EAAE,MAAM,+BAA+B,CAAC;AA2L/F,UAAU,WAAW;IACnB,WAAW,EAAE,WAAW,CAAC;IACzB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,eAAO,MAAM,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAwIrD,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,eAAO,MAAM,uBAAuB,EAAE,WAAW,CAAC,MAAM,CAGtD,CAAC;AAEH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAE9D;AAMD,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,EAAE,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IACtG,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,OAAO,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,OAAO,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;CAChB;AAMD,wBAAsB,wBAAwB,CAC5C,QAAQ,EAAE,cAAc,GACvB,OAAO,CAAC,gBAAgB,CAAC,CAkB3B;AAED,wBAAsB,0BAA0B,CAC9C,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC,kBAAkB,CAAC,CA0B7B;AAED,wBAAsB,0BAA0B,CAC9C,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,OAAO,EAChB,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC,kBAAkB,CAAC,CAwgB7B;AA+zCD,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,gBAAgB,GAAG,MAAM,CAgB3E;AAED,wBAAgB,2BAA2B,CAAC,MAAM,EAAE,kBAAkB,GAAG,MAAM,CAQ9E;AAMD,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,iBAAiB,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC3C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,KAAK,CAAC;QACnB,MAAM,EAAE,MAAM,CAAC;QACf,KAAK,EAAE,MAAM,CAAC;QACd,UAAU,EAAE,MAAM,CAAC;KACpB,CAAC,CAAC;CACJ;AAED,MAAM,WAAW,0BAA0B;IACzC,MAAM,EAAE,oBAAoB,CAAC;IAC7B,UAAU,EAAE,kBAAkB,CAAC;IAC/B,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;GAIG;AACH,MAAM,WAAW,sBAAsB;IACrC,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,qBAAqB;IACpC,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,oBAAoB,EAAE,CAAC;IAChC,WAAW,EAAE,KAAK,CAAC;QACjB,MAAM,EAAE,oBAAoB,CAAC;QAC7B,MAAM,EAAE,kBAAkB,GAAG;YAAE,KAAK,EAAE,MAAM,CAAA;SAAE,CAAC;KAChD,CAAC,CAAC;IACH;;;;OAIG;IACH,OAAO,CAAC,EAAE;QACR,UAAU,EAAE,sBAAsB,EAAE,CAAC;KACtC,CAAC;IACF;;;;OAIG;IACH,aAAa,CAAC,EAAE,mBAAmB,CAAC;IACpC,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,MAAM,WAAW,GACnB;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,MAAM,EAAE,0BAA0B,CAAA;CAAE,GACtD;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,qBAAqB,CAAA;CAAE,CAAC;AAqGrD;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,EACb,aAAa,EAAE,MAAM,GACpB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAkjDzB;AAMD,wBAAsB,2BAA2B,CAC/C,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC,WAAW,CAAC,CAksBtB;AAED,wBAAgB,oCAAoC,CAAC,MAAM,EAAE,0BAA0B,GAAG,MAAM,CAY/F;AAED,wBAAgB,+BAA+B,CAAC,MAAM,EAAE,qBAAqB,GAAG,MAAM,CAmDrF;AAED,wBAAgB,2BAA2B,CAAC,WAAW,EAAE,WAAW,GAAG,MAAM,CAK5E;AAED,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CA2BlF"}
@@ -17,6 +17,7 @@ import { execFileSync } from 'node:child_process';
17
17
  import { isTransientFailure, isTerminalFailure } from '../errors/transient.js';
18
18
  import { recordDegradation, drainDegradations } from '../observability/degradations.js';
19
19
  import { computePhase1Verdict } from '../pipeline/phase1-verdict.js';
20
+ import { pipelineHeartbeat } from '../cli/ui/heartbeat.js';
20
21
  // ============================================================================
21
22
  // ADR-066: Copilot agents via claude --print (Claude Max — no API key needed)
22
23
  // ============================================================================
@@ -3779,26 +3780,14 @@ export async function executeNaturalLanguageRoute(query, options) {
3779
3780
  // the user sees the route is executing even if subsequent operations
3780
3781
  // (graph load, ruvector simulation, fleet dispatch) take a while.
3781
3782
  process.stderr.write(`[agentics] route entered — query="${query.slice(0, 100)}${query.length > 100 ? '…' : ''}" depth=${options.depth ?? 'lite'} trace=${correlationId.slice(0, 8)}\n`);
3782
- // Global 10-second heartbeat. Ticks until process exit (`unref()` keeps it
3783
- // from blocking termination). When any other code emits its own output the
3784
- // tick still fires but that's acceptable better noisy than silent. The
3785
- // heartbeat suppresses itself via `lastOutputAt` if other writes have
3786
- // happened recently.
3787
- let lastOutputAt = Date.now();
3788
- const realStderrWrite = process.stderr.write.bind(process.stderr);
3789
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
3790
- process.stderr.write = (chunk, ...rest) => {
3791
- lastOutputAt = Date.now();
3792
- return realStderrWrite(chunk, ...rest);
3793
- };
3794
- const heartbeat = setInterval(() => {
3795
- if (Date.now() - lastOutputAt < 9_000)
3796
- return; // something else just wrote
3797
- const elapsedSec = Math.floor((Date.now() - start) / 1000);
3798
- realStderrWrite(`[agentics] ⏳ still working — ${elapsedSec}s elapsed\n`);
3799
- lastOutputAt = Date.now();
3800
- }, 10_000);
3801
- heartbeat.unref();
3783
+ // ADR-PIPELINE-099 D3 process-wide heartbeat. Survives across the
3784
+ // executeNaturalLanguageRoute executeAutoChain handoff (the previous
3785
+ // per-call heartbeat died at the function boundary, leaving Phases 2–7
3786
+ // silent for minutes at a time). Idempotent auto-chain calls start()
3787
+ // again with no effect. Stop happens at the end of executeAutoChain or
3788
+ // via the singleton's exit handlers.
3789
+ pipelineHeartbeat.start('agentics ask');
3790
+ pipelineHeartbeat.setPhase('Phase 1 — Fleet Dispatch + Simulation');
3802
3791
  // ========================================================================
3803
3792
  // ADR-030: Capability Graph Routing
3804
3793
  //
@@ -3841,6 +3830,7 @@ export async function executeNaturalLanguageRoute(query, options) {
3841
3830
  console.error(graphResult.routing.explanation);
3842
3831
  // Run ruvector simulation first (same as full-fleet path, ADR-016)
3843
3832
  console.error(' [RUVECTOR] Running simulation before graph-routed dispatch (ADR-016)...');
3833
+ pipelineHeartbeat.setActivity(`ruvector: simulating ${graphResult.routing.totalAgents}-agent graph route`);
3844
3834
  const simResult = await (async () => {
3845
3835
  const MAX_RETRIES = 3;
3846
3836
  let lastErr;
@@ -3879,8 +3869,21 @@ export async function executeNaturalLanguageRoute(query, options) {
3879
3869
  const REPO_LOCAL_DOMAINS = new Set(['copilot']);
3880
3870
  const prevLocalAgents = process.env['AGENTICS_LOCAL_AGENTS'];
3881
3871
  console.error(` Dispatching ${graphResult.agents.length} graph-routed agents...`);
3872
+ pipelineHeartbeat.setActivity(`fleet: dispatching ${graphResult.agents.length} graph-routed agents`);
3882
3873
  const agentPromises = [];
3883
- for (const ref of graphResult.agents) {
3874
+ // ADR-PIPELINE-100 D2 emit a `[FLEET-CALL]` line as each agent
3875
+ // STARTS and a result line when each FINISHES. The fleet runs in
3876
+ // parallel via Promise.allSettled, so without these lines the user
3877
+ // sees nothing for 1-3 minutes between the dispatch banner and the
3878
+ // aggregate result. Pre-2.7.27 builds had this same silence — it
3879
+ // never went away by itself when the heartbeat singleton was added.
3880
+ const fleetTotal = graphResult.agents.length;
3881
+ let fleetStartedCount = 0;
3882
+ let fleetFinishedCount = 0;
3883
+ graphResult.agents.forEach((ref, idx) => {
3884
+ const agentStart = Date.now();
3885
+ fleetStartedCount++;
3886
+ process.stderr.write(` [FLEET-CALL] [${String(idx + 1).padStart(3, ' ')}/${fleetTotal}] → ${ref.domain}/${ref.agent}\n`);
3884
3887
  agentPromises.push((async () => {
3885
3888
  try {
3886
3889
  if (!REPO_LOCAL_DOMAINS.has(ref.domain)) {
@@ -3894,12 +3897,25 @@ export async function executeNaturalLanguageRoute(query, options) {
3894
3897
  }
3895
3898
  const payload = buildDomainPayload(ref.domain, ref.agent, query, correlationId);
3896
3899
  const result = await executeAgentsInvokeCommand(ref.domain, ref.agent, payload, options);
3897
- return { kind: 'agent', domain: ref.domain, agent: ref.agent, status: result.status ?? 200, response: result.response };
3900
+ const elapsed = ((Date.now() - agentStart) / 1000).toFixed(1);
3901
+ fleetFinishedCount++;
3902
+ const status = result.status ?? 200;
3903
+ const ok = status >= 200 && status < 300;
3904
+ process.stderr.write(` [FLEET-CALL] [${String(idx + 1).padStart(3, ' ')}/${fleetTotal}] ${ok ? '✓' : '✗'} ${ref.domain}/${ref.agent} (${elapsed}s, ${fleetFinishedCount}/${fleetTotal} done)\n`);
3905
+ pipelineHeartbeat.setActivity(`fleet: ${fleetFinishedCount}/${fleetTotal} agents complete`);
3906
+ return { kind: 'agent', domain: ref.domain, agent: ref.agent, status, response: result.response };
3898
3907
  }
3899
3908
  catch (err) {
3909
+ fleetFinishedCount++;
3910
+ const elapsed = ((Date.now() - agentStart) / 1000).toFixed(1);
3911
+ const errMsg = err instanceof Error ? err.message.slice(0, 80) : String(err).slice(0, 80);
3912
+ process.stderr.write(` [FLEET-CALL] [${String(idx + 1).padStart(3, ' ')}/${fleetTotal}] ✗ ${ref.domain}/${ref.agent} (${elapsed}s) — ${errMsg}\n`);
3900
3913
  return { kind: 'agent', domain: ref.domain, agent: ref.agent, status: 502, response: { error: err instanceof Error ? err.message : String(err) } };
3901
3914
  }
3902
3915
  })());
3916
+ });
3917
+ if (fleetStartedCount > 0) {
3918
+ process.stderr.write(` [FLEET-CALL] all ${fleetStartedCount} agents in flight — awaiting responses...\n`);
3903
3919
  }
3904
3920
  const allResults = await Promise.allSettled([Promise.resolve(simResult), ...agentPromises]);
3905
3921
  const elapsed = Date.now() - start;
@@ -4189,6 +4205,7 @@ export async function executeNaturalLanguageRoute(query, options) {
4189
4205
  { domain: 'platform', agent: 'risk-score' },
4190
4206
  ];
4191
4207
  console.error(`Dispatching ${fleetAgents.length} agents across ALL 27 domains + ruvector simulation`);
4208
+ pipelineHeartbeat.setActivity(`fleet: dispatching ${fleetAgents.length} agents across 27 domains`);
4192
4209
  // ========================================================================
4193
4210
  // ADR-016: Run ruvector simulation BEFORE fleet dispatch (sequential).
4194
4211
  //
@@ -4201,6 +4218,7 @@ export async function executeNaturalLanguageRoute(query, options) {
4201
4218
  // start the fleet. Adds ~5s latency but guarantees simulation success.
4202
4219
  // ========================================================================
4203
4220
  console.error(' [RUVECTOR] Running simulation before fleet dispatch (ADR-016)...');
4221
+ pipelineHeartbeat.setActivity('ruvector: pre-fleet simulation');
4204
4222
  const simResult = await (async () => {
4205
4223
  const MAX_RETRIES = 3;
4206
4224
  let lastErr;
@@ -4247,12 +4265,24 @@ export async function executeNaturalLanguageRoute(query, options) {
4247
4265
  const WAVE_SIZE = 15;
4248
4266
  const WAVE_DELAY_MS = 2000;
4249
4267
  const agentPromises = [];
4268
+ // ADR-PIPELINE-100 D2 — per-agent visibility for the full-fleet path.
4269
+ // Without these per-call lines the user sees a "Wave N/M" banner then
4270
+ // 1-3 minutes of silence per wave, then nothing until ALL waves
4271
+ // finish. The user explicitly demanded "I should see them being
4272
+ // called" — these are the lines that satisfy that.
4273
+ const fleetTotal = fleetAgents.length;
4274
+ let fleetFinishedCount = 0;
4275
+ let agentDispatchIdx = 0;
4250
4276
  for (let i = 0; i < fleetAgents.length; i += WAVE_SIZE) {
4251
4277
  const wave = fleetAgents.slice(i, i + WAVE_SIZE);
4252
4278
  const waveNum = Math.floor(i / WAVE_SIZE) + 1;
4253
4279
  const totalWaves = Math.ceil(fleetAgents.length / WAVE_SIZE);
4254
- console.error(` Wave ${waveNum}/${totalWaves}: ${wave.length} agents (${wave.map(a => `${a.domain}/${a.agent}`).slice(0, 3).join(', ')}${wave.length > 3 ? '...' : ''})`);
4280
+ console.error(` Wave ${waveNum}/${totalWaves}: dispatching ${wave.length} agents (${wave.map(a => `${a.domain}/${a.agent}`).slice(0, 3).join(', ')}${wave.length > 3 ? '...' : ''})`);
4255
4281
  for (const { domain, agent } of wave) {
4282
+ agentDispatchIdx++;
4283
+ const myIdx = agentDispatchIdx;
4284
+ const agentStart = Date.now();
4285
+ process.stderr.write(` [FLEET-CALL] [${String(myIdx).padStart(3, ' ')}/${fleetTotal}] → ${domain}/${agent}\n`);
4256
4286
  agentPromises.push((async () => {
4257
4287
  try {
4258
4288
  // Toggle repo-local per-agent: only copilot uses repo-local
@@ -4267,9 +4297,19 @@ export async function executeNaturalLanguageRoute(query, options) {
4267
4297
  }
4268
4298
  const payload = buildDomainPayload(domain, agent, query, correlationId);
4269
4299
  const result = await executeAgentsInvokeCommand(domain, agent, payload, options);
4270
- return { kind: 'agent', domain, agent, status: result.status ?? 200, response: result.response };
4300
+ fleetFinishedCount++;
4301
+ const elapsed = ((Date.now() - agentStart) / 1000).toFixed(1);
4302
+ const status = result.status ?? 200;
4303
+ const ok = status >= 200 && status < 300;
4304
+ process.stderr.write(` [FLEET-CALL] [${String(myIdx).padStart(3, ' ')}/${fleetTotal}] ${ok ? '✓' : '✗'} ${domain}/${agent} (${elapsed}s, ${fleetFinishedCount}/${fleetTotal} done)\n`);
4305
+ pipelineHeartbeat.setActivity(`fleet: ${fleetFinishedCount}/${fleetTotal} agents complete`);
4306
+ return { kind: 'agent', domain, agent, status, response: result.response };
4271
4307
  }
4272
4308
  catch (err) {
4309
+ fleetFinishedCount++;
4310
+ const elapsed = ((Date.now() - agentStart) / 1000).toFixed(1);
4311
+ const errMsg = err instanceof Error ? err.message.slice(0, 80) : String(err).slice(0, 80);
4312
+ process.stderr.write(` [FLEET-CALL] [${String(myIdx).padStart(3, ' ')}/${fleetTotal}] ✗ ${domain}/${agent} (${elapsed}s) — ${errMsg}\n`);
4273
4313
  return { kind: 'agent', domain, agent, status: 502, response: { error: err instanceof Error ? err.message : String(err) } };
4274
4314
  }
4275
4315
  })());
@@ -4279,6 +4319,7 @@ export async function executeNaturalLanguageRoute(query, options) {
4279
4319
  await new Promise(resolve => setTimeout(resolve, WAVE_DELAY_MS));
4280
4320
  }
4281
4321
  }
4322
+ process.stderr.write(` [FLEET-CALL] all ${fleetTotal} agents in flight — awaiting final responses...\n`);
4282
4323
  const allResults = await Promise.allSettled([Promise.resolve(simResult), ...agentPromises]);
4283
4324
  const elapsed = Date.now() - start;
4284
4325
  // Restore repo-local mode setting