@newrelic/preflight 1.5.4 → 1.5.6

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.
@@ -5,7 +5,7 @@ import { type SessionLikeForCostOutcome } from '../../metrics/cost-per-outcome.j
5
5
  import type { ToolCallRecord } from '../../storage/types.js';
6
6
  import type { FullSessionSummary, SessionFileInfo } from '../../storage/session-store.js';
7
7
  import type { WorkflowRunRow } from '../workflow-store.js';
8
- import type { SubagentTimeline, AgentCall } from '../subagent-timeline-store.js';
8
+ import type { SubagentTimeline, AgentCall, LiveWorkflowRunDetail } from '../subagent-timeline-store.js';
9
9
  import type { CostForecast } from '../../metrics/cost-forecast.js';
10
10
  import type { AlertEvent } from '../live-event-bus.js';
11
11
  import type { BudgetStatus } from '../../metrics/budget-tracker.js';
@@ -109,6 +109,13 @@ export interface ApiHandlerDeps {
109
109
  getAgentCalls: (sessionId: string, agentId: string) => {
110
110
  calls: readonly AgentCall[];
111
111
  };
112
+ /**
113
+ * Live-run fallback for the workflow-detail route: a still-running workflow
114
+ * has no on-disk rollup, so `workflowStore.getRun` returns null; this
115
+ * assembles the detail from the run's live subagent transcripts instead.
116
+ * Optional so older fakes / mocks without it still type-check.
117
+ */
118
+ getRunLive?: (runId: string) => LiveWorkflowRunDetail | null;
112
119
  };
113
120
  /**
114
121
  * Snapshot of latest watcher health frames so the dashboard can render
@@ -1 +1 @@
1
- {"version":3,"file":"api-handler.d.ts","sourceRoot":"","sources":["../../../src/dashboard/routes/api-handler.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"api-handler.d.ts","sourceRoot":"","sources":["../../../src/dashboard/routes/api-handler.ts"],"names":[],"mappings":"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"}
@@ -134,6 +134,55 @@ function toWorkflowAgentDto(agent) {
134
134
  startedAt: a.started_at ?? null,
135
135
  };
136
136
  }
137
+ // Serialize a still-running workflow (assembled from live subagent transcripts,
138
+ // no on-disk rollup) into the SAME `{ run, agents, topology }` wire shape the
139
+ // completed path emits, so WorkflowRunDetail.tsx renders it unchanged. Fields
140
+ // that only exist post-rollup are filled with running-run defaults: status
141
+ // 'running', incomplete true, no error/task/reconciliation, and per-agent
142
+ // phase/state placeholders (phaseIndex -1, phaseTitle '', state 'running').
143
+ function toLiveWorkflowDetail(live) {
144
+ const run = {
145
+ runId: live.runId,
146
+ parentSessionId: live.parentSessionId,
147
+ taskId: null,
148
+ // Fall back to the runId when the script (and thus meta.name) can't be read.
149
+ workflowName: live.workflowName ?? live.runId,
150
+ status: 'running',
151
+ errorReason: null,
152
+ defaultModel: live.defaultModel,
153
+ startedAt: live.startedAt,
154
+ durationMs: live.durationMs,
155
+ agentCount: live.agentCount,
156
+ totalTokens: live.totalTokens,
157
+ totalUsd: live.totalUsd,
158
+ declaredPhases: live.topology?.declaredPhases ?? null,
159
+ // No phase-title telemetry exists mid-run (that comes from the rollup's
160
+ // workflowProgress), so observed phases are unknown → 0.
161
+ observedPhases: 0,
162
+ declaredParallelWidths: live.topology?.declaredParallelWidths ?? [],
163
+ tokenReconciliationDelta: 0,
164
+ incomplete: true,
165
+ // wf_<hex> runIds are script/Workflow-tool orchestrations (agent_tool runs
166
+ // key off toolu_* ids), so this is always a 'script' run.
167
+ runSource: 'script',
168
+ scriptPath: live.scriptPath,
169
+ workflowJsonPath: '',
170
+ };
171
+ const agents = live.agents.map((a) => ({
172
+ agentId: a.agentId,
173
+ label: a.label,
174
+ phaseIndex: -1,
175
+ phaseTitle: '',
176
+ model: a.model,
177
+ state: 'running',
178
+ attempt: 1,
179
+ durationMs: a.durationMs,
180
+ tokens: a.tokens,
181
+ toolCalls: a.toolCalls,
182
+ startedAt: a.startedAt,
183
+ }));
184
+ return { run, agents, topology: live.topology ?? null };
185
+ }
137
186
  function jsonOk(res, body) {
138
187
  const payload = JSON.stringify(body);
139
188
  res.writeHead(200, {
@@ -189,78 +238,78 @@ function mergeActivityWindows(timeline) {
189
238
  // computeTodayConcurrencyBuckets() below.
190
239
  const CONCURRENCY_BUCKET_SIZE_MS = 15 * 60 * 1000;
191
240
  const CONCURRENCY_BUCKET_COUNT = 96;
192
- // Build [startTime, endTime] intervals for every session that has any activity
193
- // today. Persisted sessions contribute their stored startTime/endTime.
194
- // Currently-live sessions (in the registry but not yet persisted) derive
195
- // startTime from the in-memory tool-call buffer (oldest observed timestamp)
196
- // and use `now` as endTime matching the convention used by /api/sessions/live.
197
- // Sessions that appear in both the persisted store and the live registry are
198
- // deduped by sessionId; the live (extended-to-now) interval wins.
199
- function collectTodaySessionIntervals(todaySessions, liveSessionIds, bufferRecords, now) {
200
- const byId = new Map();
241
+ // Build activity windows for every session with activity today, using the SAME
242
+ // 3-minute activity-window model as computeTodayPeakConcurrency() (the source of
243
+ // the headline `peak`). Each session contributes one or more windows: contiguous
244
+ // tool-call activity merges into a single window, while an idle gap longer than
245
+ // ACTIVITY_WINDOW_MS splits a session into separate windows.
246
+ //
247
+ // This is what keeps the chart's tallest column equal to the headline peak. The
248
+ // earlier version used whole-session [startTime, endTime] spans (extending live
249
+ // sessions to `now`), which counted a long-idle-but-not-yet-ended session as
250
+ // continuously concurrent and inflated recent buckets well above the peak — the
251
+ // chart could read 7 while the headline read 4.
252
+ //
253
+ // Activity timestamps per session are unioned from BOTH the persisted timeline
254
+ // and the in-memory tool-call buffer (for live sessions whose latest activity
255
+ // hasn't been flushed to disk), then merged once per session id so a single
256
+ // session can never overlap itself.
257
+ function collectTodayActivityWindows(todaySessions, liveSessionIds, bufferRecords) {
258
+ const timestampsById = new Map();
259
+ const addTimestamps = (id, times) => {
260
+ const existing = timestampsById.get(id);
261
+ if (existing)
262
+ existing.push(...times);
263
+ else
264
+ timestampsById.set(id, [...times]);
265
+ };
201
266
  for (const s of todaySessions) {
202
- if (typeof s.startTime !== 'number')
203
- continue;
204
- const end = typeof s.endTime === 'number' ? s.endTime : now;
205
- if (end <= s.startTime)
267
+ if (!s.timeline || s.timeline.length === 0)
206
268
  continue;
207
- if (typeof s.sessionId === 'string' && s.sessionId.length > 0) {
208
- byId.set(s.sessionId, { startTime: s.startTime, endTime: end });
209
- }
210
- else {
211
- // No id use a synthetic key so it still contributes.
212
- byId.set(`__anon_${byId.size}`, { startTime: s.startTime, endTime: end });
213
- }
269
+ const times = s.timeline.map((t) => t.timestamp);
270
+ // `sessionId` is a required field on FullSessionSummary; the synthetic-key
271
+ // fallback only guards against malformed on-disk JSON (these records are
272
+ // read from disk, not schema-validated).
273
+ const id = typeof s.sessionId === 'string' && s.sessionId.length > 0
274
+ ? s.sessionId
275
+ : `__anon_${timestampsById.size}`;
276
+ addTimestamps(id, times);
214
277
  }
278
+ // Fold in live sessions' buffered (not-yet-persisted) activity, keyed by the
279
+ // same session id so it merges with the persisted timeline rather than
280
+ // double-counting a session that appears in both.
215
281
  if (liveSessionIds.length > 0) {
216
- const firstTsBySession = new Map();
282
+ const liveSet = new Set(liveSessionIds);
217
283
  for (const r of bufferRecords) {
218
284
  const sid = r.sessionId;
219
- if (!sid)
285
+ if (!sid || !liveSet.has(sid))
220
286
  continue;
221
287
  const ts = r.timestamp ?? 0;
222
288
  if (!ts)
223
289
  continue;
224
- const prev = firstTsBySession.get(sid);
225
- if (prev === undefined || ts < prev)
226
- firstTsBySession.set(sid, ts);
290
+ addTimestamps(sid, [ts]);
227
291
  }
228
- for (const sid of liveSessionIds) {
229
- const existing = byId.get(sid);
230
- const bufferStart = firstTsBySession.get(sid);
231
- // Prefer the EARLIER startTime: a persisted entry's startTime is
232
- // authoritative (e.g. 09:00) and would be clobbered if we keyed solely
233
- // off the buffer's earliest record (which may be much later if the
234
- // buffer was drained between flushes). Extend endTime to `now` —
235
- // that's the original intent of this loop (a live session is, by
236
- // definition, ongoing).
237
- const candidates = [];
238
- if (existing !== undefined)
239
- candidates.push(existing.startTime);
240
- if (bufferStart !== undefined)
241
- candidates.push(bufferStart);
242
- if (candidates.length === 0)
243
- continue;
244
- const startTime = Math.min(...candidates);
245
- if (now <= startTime)
246
- continue;
247
- byId.set(sid, { startTime, endTime: now });
292
+ }
293
+ const windows = [];
294
+ for (const times of timestampsById.values()) {
295
+ for (const w of mergeActivityWindows(times.map((t) => ({ timestamp: t })))) {
296
+ windows.push({ startTime: w.start, endTime: w.end });
248
297
  }
249
298
  }
250
- return Array.from(byId.values());
299
+ return windows;
251
300
  }
252
- function computeTodayConcurrencyBuckets(intervals, startTimestamp) {
253
- // Single pass O(N log N) — build delta events from every interval,
301
+ function computeTodayConcurrencyBuckets(windows, startTimestamp) {
302
+ // Single pass O(N log N) — build delta events from every activity window,
254
303
  // clamped to today's window, then sort once and sweep across the 96
255
304
  // buckets in lockstep with the event cursor. Buckets containing no
256
305
  // events still need a count: the value carried over from the previous
257
- // bucket's tail (a session that spans many buckets without a delta in
306
+ // bucket's tail (a window that spans many buckets without a delta in
258
307
  // between must propagate as count=1+ in those buckets).
259
308
  const dayEnd = startTimestamp + CONCURRENCY_BUCKET_COUNT * CONCURRENCY_BUCKET_SIZE_MS;
260
309
  const events = [];
261
- for (const interval of intervals) {
262
- const start = Math.max(interval.startTime, startTimestamp);
263
- const end = Math.min(interval.endTime, dayEnd);
310
+ for (const window of windows) {
311
+ const start = Math.max(window.startTime, startTimestamp);
312
+ const end = Math.min(window.endTime, dayEnd);
264
313
  if (start < end) {
265
314
  events.push({ ts: start, delta: 1 }, { ts: end, delta: -1 });
266
315
  }
@@ -277,19 +326,29 @@ function computeTodayConcurrencyBuckets(intervals, startTimestamp) {
277
326
  for (let b = 0; b < CONCURRENCY_BUCKET_COUNT; b++) {
278
327
  const bucketStart = startTimestamp + b * CONCURRENCY_BUCKET_SIZE_MS;
279
328
  const bucketEnd = bucketStart + CONCURRENCY_BUCKET_SIZE_MS;
280
- // Flush any boundary events whose ts falls AT OR BEFORE bucketStart.
281
- // Without this, a session ending at exactly t=bucketStart (e.g. a
282
- // 15-min-aligned endTime) would still be carried as current=1 into
283
- // this bucket before the -1 fires, inflating the bucket's initial peak
284
- // by 1. Flushing first means peak is initialised from the correct
285
- // post-close concurrency level.
329
+ // Peak defaults to the carried-over level so a session spanning many
330
+ // buckets with no events exactly at this boundary propagates as
331
+ // count=N+ here. But if one or more events DO land exactly on
332
+ // bucketStart, that default must be discarded rather than treated as a
333
+ // peak candidate it reflects state from strictly BEFORE this bucket's
334
+ // range, not a value actually attained within it. (A session closing
335
+ // at exactly t=bucketStart is no longer active as of bucketStart; using
336
+ // the pre-close `current` as this bucket's floor would wrongly count it
337
+ // as still open here.) Once a boundary event fires, peak is instead
338
+ // sampled fresh after each one — same as the mid-bucket sweep below and
339
+ // as computeTodayPeakConcurrency's continuous sweep — so an exact-touch
340
+ // pair (one session's close coinciding with another's open, both at
341
+ // bucketStart) still registers the momentary overlap via the
342
+ // open-before-close tiebreaker, without resurrecting an already-closed
343
+ // session that has no such coincident open.
344
+ let peak = current;
345
+ let flushedAny = false;
286
346
  while (cursor < events.length && events[cursor].ts <= bucketStart) {
287
347
  current += events[cursor].delta;
348
+ peak = flushedAny ? Math.max(peak, current) : current;
349
+ flushedAny = true;
288
350
  cursor++;
289
351
  }
290
- // Peak starts at the carried-over (post-flush) level. Sessions spanning
291
- // many buckets with no mid-bucket events propagate as count=N+ here.
292
- let peak = current;
293
352
  while (cursor < events.length && events[cursor].ts < bucketEnd) {
294
353
  current += events[cursor].delta;
295
354
  if (current > peak)
@@ -1061,8 +1120,6 @@ export function createApiHandler(deps) {
1061
1120
  if (!deps.concurrencyTracker)
1062
1121
  return unavailable(res, 'concurrencyTracker');
1063
1122
  try {
1064
- const todaySessions = deps.sessionStore?.loadTodaySessions() ?? [];
1065
- const historicalPeak = computeTodayPeakConcurrency(todaySessions);
1066
1123
  const livePeak = deps.concurrencyTracker.getPeakConcurrent();
1067
1124
  const url = new URL(req.url ?? '/', 'http://localhost');
1068
1125
  const view = url.searchParams.get('view');
@@ -1086,8 +1143,20 @@ export function createApiHandler(deps) {
1086
1143
  jsonOk(res, { dailyPeaks });
1087
1144
  return;
1088
1145
  }
1146
+ // Synthetic session ids (`local-*`, `proxy-*`) are hidden from
1147
+ // /api/sessions and /api/sessions/live. Exclude them here too so
1148
+ // `allTimePeak` can't be inflated by sessions that never appear
1149
+ // anywhere else in the UI — it must stay comparable to `peak`, which
1150
+ // is filtered the same way below.
1089
1151
  const allSessions = deps.sessionStore?.loadAllSessions?.() ?? [];
1090
- const allTimePeak = computeTodayPeakConcurrency(allSessions);
1152
+ const filteredAllSessions = allSessions.filter((s) => !isSyntheticSessionId(s.sessionId));
1153
+ const allTimePeak = computeTodayPeakConcurrency(filteredAllSessions);
1154
+ // Synthetic session ids (`local-*`, `proxy-*`) are hidden from
1155
+ // /api/sessions and /api/sessions/live. Exclude them from the chart
1156
+ // buckets below so they agree with the session list shown alongside
1157
+ // the chart.
1158
+ const todaySessions = deps.sessionStore?.loadTodaySessions() ?? [];
1159
+ const filteredTodaySessions = todaySessions.filter((s) => !isSyntheticSessionId(s.sessionId));
1091
1160
  // 96 × 15-minute fixed-grid buckets covering today (local midnight →
1092
1161
  // local midnight + 24h). Each bucket holds the peak concurrent
1093
1162
  // session count within its window. Bounded payload (~10 KB) and
@@ -1096,20 +1165,27 @@ export function createApiHandler(deps) {
1096
1165
  const startTimestamp = localStartOfDay();
1097
1166
  const liveIds = deps.liveSessionRegistry?.getLiveSessions() ?? [];
1098
1167
  const bufferRecords = deps.toolCallBuffer?.getRecords() ?? [];
1099
- // Synthetic session ids (`local-*`, `proxy-*`) are hidden from
1100
- // /api/sessions and /api/sessions/live. Apply the same filter here
1101
- // so persisted synthetic records on disk don't contribute to bucket
1102
- // counts either otherwise the buckets disagree with the session
1103
- // list shown alongside the chart.
1104
- const filteredTodaySessions = todaySessions.filter((s) => {
1105
- const sid = s.sessionId;
1106
- return !isSyntheticSessionId(sid);
1107
- });
1108
- const intervals = collectTodaySessionIntervals(filteredTodaySessions, liveIds.filter((id) => !isSyntheticSessionId(id)), bufferRecords, Date.now());
1109
- const buckets = computeTodayConcurrencyBuckets(intervals, startTimestamp);
1168
+ const windows = collectTodayActivityWindows(filteredTodaySessions, liveIds.filter((id) => !isSyntheticSessionId(id)), bufferRecords);
1169
+ const buckets = computeTodayConcurrencyBuckets(windows, startTimestamp);
1170
+ // Derive the headline "today peak" from the SAME buckets shown in the
1171
+ // chartrather than from an independent computation over a
1172
+ // differently-scoped session set — so the two numbers can never
1173
+ // disagree by construction. (This was the root cause of the bug this
1174
+ // fix addresses: the headline and the chart used to be computed from
1175
+ // two different concurrency models that could silently drift apart.)
1176
+ //
1177
+ // Deliberately NOT folded with `livePeak` here (unlike allTimePeak
1178
+ // below): `livePeak` is a synthetic-id-unfiltered, never-reset
1179
+ // lifetime max from LiveSessionRegistry, so mixing it into today's
1180
+ // headline can reintroduce a chart/headline disagreement — the exact
1181
+ // class of bug this fix exists to eliminate. It's unnecessary anyway:
1182
+ // `windows` already unions each live session's buffered (not-yet-
1183
+ // persisted) activity into the same buckets, so a currently-live
1184
+ // session with any tool-call activity is already reflected here.
1185
+ const historicalPeak = buckets.reduce((max, b) => Math.max(max, b.count), 0);
1110
1186
  jsonOk(res, {
1111
1187
  current: deps.concurrencyTracker.getConcurrentCount(),
1112
- peak: Math.max(livePeak, historicalPeak),
1188
+ peak: historicalPeak,
1113
1189
  allTimePeak: Math.max(livePeak, historicalPeak, allTimePeak),
1114
1190
  bucketSizeMs: CONCURRENCY_BUCKET_SIZE_MS,
1115
1191
  startTimestamp,
@@ -1490,6 +1566,17 @@ export function createApiHandler(deps) {
1490
1566
  return unavailable(res, 'workflowStore');
1491
1567
  const run = deps.workflowStore.getRun(runId);
1492
1568
  if (run === null) {
1569
+ // Live fallback: a still-running workflow has no on-disk `wf_*.json`
1570
+ // rollup yet (that is written only at termination), but its subagent
1571
+ // transcripts already exist and the swimlane surfaces the run as
1572
+ // clickable. Synthesize a 'running' detail from those transcripts so
1573
+ // the drawer shows live progress instead of "Failed to load run
1574
+ // details." Falls through to 404 only when no live data exists either.
1575
+ const live = deps.subagentTimeline?.getRunLive?.(runId) ?? null;
1576
+ if (live !== null) {
1577
+ jsonOk(res, toLiveWorkflowDetail(live));
1578
+ return;
1579
+ }
1493
1580
  res.writeHead(404, { 'content-type': 'application/json' });
1494
1581
  res.end(JSON.stringify({ error: 'not_found' }));
1495
1582
  return;