@cesarus777/opencode-cgp 0.3.1 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/README.md +23 -9
  3. package/dist/cli.js +1179 -92
  4. package/dist/core.js +49 -4
  5. package/dist/index.js +570 -9
  6. package/dist/rpc.js +129 -2
  7. package/dist/tui.js +1543 -707
  8. package/dist/types/adapter.d.ts +2 -0
  9. package/dist/types/core.d.ts +1 -0
  10. package/dist/types/dashboard/command.d.ts +1 -1
  11. package/dist/types/dashboard/connection.d.ts +2 -0
  12. package/dist/types/dashboard/controller.d.ts +4 -0
  13. package/dist/types/dashboard/models.d.ts +42 -0
  14. package/dist/types/dashboard/modelview.d.ts +30 -0
  15. package/dist/types/dashboard/port.d.ts +6 -0
  16. package/dist/types/dashboard/terminal.d.ts +7 -0
  17. package/dist/types/dashboard/terminalview.d.ts +7 -2
  18. package/dist/types/dashboard/view.d.ts +6 -1
  19. package/dist/types/history.d.ts +1 -0
  20. package/dist/types/historyclient.d.ts +15 -0
  21. package/dist/types/modelanalytics.d.ts +8 -0
  22. package/dist/types/modelanalyticsserver.d.ts +16 -0
  23. package/dist/types/modelseries.d.ts +43 -0
  24. package/dist/types/rpc.d.ts +28 -0
  25. package/dist/types/tui/chartformat.d.ts +1 -1
  26. package/dist/types/tui/charts.d.ts +2 -3
  27. package/dist/types/tui/compactformat.d.ts +12 -3
  28. package/dist/types/tui/historystate.d.ts +49 -0
  29. package/dist/types/tui/inventory.d.ts +12 -0
  30. package/dist/types/tui/panelcontrols.d.ts +8 -0
  31. package/dist/types/tui/panelformat.d.ts +12 -0
  32. package/dist/types/tui/panelview.d.ts +22 -0
  33. package/dist/types/tui/quantity.d.ts +1 -0
  34. package/dist/types/tui/sidebar.d.ts +0 -5
  35. package/dist/types/tui/state.d.ts +3 -1
  36. package/dist/types/tui/statusformat.d.ts +19 -0
  37. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -44,6 +44,12 @@ function isHistoryRead(value) {
44
44
  function isHistoryResult(value) {
45
45
  return valid(value, historyResultSchema);
46
46
  }
47
+ function isAnalyticsRead(value) {
48
+ return valid(value, analyticsReadSchema) && record(value) && typeof value.from === "number" && typeof value.to === "number" && value.from < value.to && value.to - value.from <= 604800000 && !(value.model !== undefined && value.unknownModel === true) && (!(value.modelCursor || value.requestCursor) || typeof value.snapshotId === "string");
49
+ }
50
+ function isAnalyticsResult(value) {
51
+ return valid(value, analyticsResultSchema);
52
+ }
47
53
  function record(x) {
48
54
  return typeof x === "object" && x !== null && !Array.isArray(x);
49
55
  }
@@ -101,7 +107,7 @@ var count, text, enumeration = (...values) => ({ type: "string", enum: values })
101
107
  type: "array",
102
108
  items,
103
109
  maxItems
104
- }), measurement, row, batchFields, readSchema, batchSchema, resultSchema, monitorRpc, nonnegative, performanceSchema, performanceRow, performanceBatchSchema, performanceResultSchema, performanceRpc, chartPointSchema, chartRequestSchema, historyReadSchema, historyResultSchema, historyRpc;
110
+ }), measurement, row, batchFields, readSchema, batchSchema, resultSchema, monitorRpc, nonnegative, performanceSchema, performanceRow, performanceBatchSchema, performanceResultSchema, performanceRpc, chartPointSchema, chartRequestSchema, historyReadSchema, historyResultSchema, historyRpc, modelSummarySchema, analyticsReadSchema, analyticsResultSchema, analyticsRpc;
105
111
  var init_rpc = __esm(() => {
106
112
  count = {
107
113
  type: "integer",
@@ -337,6 +343,123 @@ var init_rpc = __esm(() => {
337
343
  methods: { read: { input: historyReadSchema, output: historyResultSchema } },
338
344
  events: {}
339
345
  });
346
+ modelSummarySchema = object({
347
+ model: text,
348
+ sessions: count,
349
+ completedRequests: count,
350
+ partialRequests: count,
351
+ legacyAttributionRequests: count,
352
+ tpsRequests: count,
353
+ ttftRequests: count,
354
+ excludedTpsRequests: count,
355
+ excludedTtftRequests: count,
356
+ outputTokens: nonnegative,
357
+ durationMs: nonnegative,
358
+ averageTps: nonnegative,
359
+ meanTtftMs: nonnegative,
360
+ medianTtftMs: nonnegative,
361
+ p95TtftMs: nonnegative
362
+ }, [
363
+ "sessions",
364
+ "completedRequests",
365
+ "partialRequests",
366
+ "legacyAttributionRequests",
367
+ "tpsRequests",
368
+ "ttftRequests",
369
+ "excludedTpsRequests",
370
+ "excludedTtftRequests",
371
+ "outputTokens",
372
+ "durationMs"
373
+ ]);
374
+ analyticsReadSchema = object({
375
+ schemaVersion: { const: 1 },
376
+ from: count,
377
+ to: count,
378
+ buckets: { type: "integer", minimum: 1, maximum: 240 },
379
+ agent: text,
380
+ model: text,
381
+ unknownModel: { type: "boolean" },
382
+ sort: enumeration("requests", "tps", "ttft", "identity"),
383
+ snapshotId: text,
384
+ modelCursor: text,
385
+ requestCursor: text
386
+ }, ["schemaVersion", "from", "to", "buckets"]);
387
+ analyticsResultSchema = object({
388
+ schemaVersion: { const: 1 },
389
+ generatedAt: count,
390
+ expiresAt: count,
391
+ snapshotId: text,
392
+ enabled: { type: "boolean" },
393
+ from: count,
394
+ to: count,
395
+ agent: text,
396
+ models: array(modelSummarySchema, 50),
397
+ totalModels: count,
398
+ nextModelsCursor: text,
399
+ selected: modelSummarySchema,
400
+ buckets: array(object({ from: nonnegative, to: nonnegative, summary: modelSummarySchema }, ["from", "to"]), 240),
401
+ requests: array(object({
402
+ location: text,
403
+ sessionId: text,
404
+ requestId: text,
405
+ startedAt: count,
406
+ completedAt: count,
407
+ model: text,
408
+ agent: text,
409
+ attribution: enumeration("request", "legacy-record", "unknown"),
410
+ partial: { type: "boolean" },
411
+ outputTokens: nonnegative,
412
+ ttftMs: nonnegative
413
+ }, [
414
+ "location",
415
+ "sessionId",
416
+ "requestId",
417
+ "startedAt",
418
+ "completedAt",
419
+ "attribution",
420
+ "partial"
421
+ ]), 50),
422
+ nextRequestsCursor: text,
423
+ coverage: object({
424
+ source: enumeration("retained-request-records"),
425
+ scannedRecords: count,
426
+ unscannedRecords: count,
427
+ duplicateRecords: count,
428
+ conflictingRequests: count,
429
+ missingIdentityRecords: count,
430
+ incompleteRequests: count,
431
+ invalidTimingRequests: count,
432
+ gapRecords: count,
433
+ maxRequests: count,
434
+ partial: { type: "boolean" }
435
+ }),
436
+ metadata: historyResultSchema.properties.metadata ?? {},
437
+ gaps: array(count, 128),
438
+ omittedGaps: count
439
+ }, [
440
+ "schemaVersion",
441
+ "generatedAt",
442
+ "expiresAt",
443
+ "snapshotId",
444
+ "enabled",
445
+ "from",
446
+ "to",
447
+ "models",
448
+ "totalModels",
449
+ "buckets",
450
+ "requests",
451
+ "coverage",
452
+ "metadata",
453
+ "gaps",
454
+ "omittedGaps"
455
+ ]);
456
+ analyticsRpc = Rpc.define({
457
+ id: "context-guard.analytics.v1",
458
+ methods: {
459
+ read: { input: analyticsReadSchema, output: analyticsResultSchema }
460
+ },
461
+ events: {}
462
+ });
340
463
  });
341
464
 
342
465
  // src/history.ts
@@ -529,6 +652,7 @@ function scalarPerformance(p) {
529
652
  async function readPerformanceHistory(directory, filter = {}) {
530
653
  const records = [];
531
654
  const files = await segments(directory);
655
+ filter.signal?.throwIfAborted();
532
656
  const maxBytes = filter.maxBytes ?? 64 * 1024 * 1024;
533
657
  let bytes = 0, skippedBytes = 0, malformedRecords = 0;
534
658
  const selected = [];
@@ -541,6 +665,7 @@ async function readPerformanceHistory(directory, filter = {}) {
541
665
  selected.unshift(file);
542
666
  }
543
667
  for (const file of selected) {
668
+ filter.signal?.throwIfAborted();
544
669
  const handle = await open(join(directory, file.name), "r");
545
670
  let source;
546
671
  try {
@@ -550,7 +675,12 @@ async function readPerformanceHistory(directory, filter = {}) {
550
675
  } finally {
551
676
  await handle.close();
552
677
  }
678
+ let scannedLines = 0;
553
679
  for (const line of source.split("\n")) {
680
+ if (filter.signal && ++scannedLines % 256 === 0) {
681
+ await new Promise((done) => setTimeout(done, 0));
682
+ filter.signal.throwIfAborted();
683
+ }
554
684
  if (!line)
555
685
  continue;
556
686
  try {
@@ -671,8 +801,9 @@ Open a read-only live terminal dashboard for an existing OpenCode server.
671
801
 
672
802
  Start here
673
803
  1. Keep an OpenCode server running with Context Guard registered.
674
- 2. Close OpenCode's native TUI before opening the standalone dashboard.
675
- 3. In the owning project directory, run: context-guard dashboard tui
804
+ 2. Open a separate terminal tab, window or pane for the dashboard.
805
+ 3. In the same project directory, run: context-guard dashboard tui
806
+ Keep working in OpenCode; the dashboard reads live updates from its server.
676
807
 
677
808
  Options
678
809
  --directory PATH Owning project directory (local default: current directory)
@@ -695,8 +826,10 @@ Authentication (explicit servers)
695
826
  CONTEXT_GUARD_USERNAME Server username (default: opencode)
696
827
 
697
828
  Keys
698
- 1/2/3 overview/detail/history; arrows or j/k move; / search; f filters;
699
- l location; w history window; r refresh; ? help; q or Ctrl+C quit.`;
829
+ 1/2/3/4 overview/detail/history/models; arrows or j/k move; Enter detail/chart;
830
+ Models: Left/Right metric, [/] bucket, d requests, n/p pages, s sort;
831
+ / session search; f filters (Models: agent); l location; w time window;
832
+ r refresh; ? help; q or Ctrl+C quit.`;
700
833
  var init_command = () => {
701
834
  };
702
835
 
@@ -803,6 +936,51 @@ function executionStatus(known, nativeRunning, evidence) {
803
936
  return known ? "idle" : "unknown";
804
937
  }
805
938
 
939
+ // src/historyclient.ts
940
+ function historyFailure(error, signal) {
941
+ if (signal?.aborted && signal.reason instanceof DOMException && signal.reason.name === "TimeoutError" || error instanceof DOMException && error.name === "TimeoutError")
942
+ return new HistoryFailure("timeout");
943
+ if (error instanceof HistoryFailure)
944
+ return error;
945
+ const code = typeof error === "object" && error !== null && "type" in error ? error.type : undefined;
946
+ return new HistoryFailure(code === "rpc.unavailable" || code === "rpc.method_not_found" ? "unsupported" : code === "rpc.invalid_output" ? "incompatible" : code === "rpc.invalid_input" ? "rejected" : "unavailable");
947
+ }
948
+ function checkedHistory(value, sessionId) {
949
+ if (!isHistoryResult(value) || value.data.series.some((series) => series.sessionId !== sessionId))
950
+ throw new HistoryFailure("incompatible");
951
+ return value;
952
+ }
953
+ function historyStatusLines(input) {
954
+ const messages = {
955
+ unsupported: "History service is unavailable in this server location. Check that the owning server has a plugin version with chart history. Press r to check again.",
956
+ timeout: "History request timed out. Press r to retry.",
957
+ incompatible: "History response is incompatible. Check client/server plugin versions. Press r to retry.",
958
+ rejected: "History request was rejected. Check client/server plugin versions. Press r to retry.",
959
+ unavailable: "History could not be loaded. Press r to retry."
960
+ };
961
+ return [
962
+ ...input.status === "loading" ? [
963
+ input.loadedAt === undefined ? "Loading retained history\u2026" : "Updating history \xB7 showing previous results"
964
+ ] : input.status === "disabled" ? [
965
+ "History recording is disabled in this server location. Press r to check again after configuration changes."
966
+ ] : input.status === "unavailable" ? [messages[input.reason ?? "unavailable"]] : [],
967
+ ...input.stale && input.loadedAt !== undefined ? [
968
+ `Showing previous results \xB7 last loaded ${new Date(input.loadedAt).toISOString()}`
969
+ ] : []
970
+ ];
971
+ }
972
+ var HistoryFailure;
973
+ var init_historyclient = __esm(() => {
974
+ init_rpc();
975
+ HistoryFailure = class HistoryFailure extends Error {
976
+ reason;
977
+ constructor(reason) {
978
+ super(`History ${reason}`);
979
+ this.reason = reason;
980
+ }
981
+ };
982
+ });
983
+
806
984
  // src/tui/transport.ts
807
985
  class SnapshotClient {
808
986
  transport;
@@ -1164,6 +1342,249 @@ var init_inventory = __esm(() => {
1164
1342
  });
1165
1343
  });
1166
1344
 
1345
+ // src/dashboard/port.ts
1346
+ var AnalyticsUnavailable;
1347
+ var init_port = __esm(() => {
1348
+ AnalyticsUnavailable = class AnalyticsUnavailable extends Error {
1349
+ reason;
1350
+ constructor(reason) {
1351
+ super(`Model analytics ${reason}`);
1352
+ this.reason = reason;
1353
+ }
1354
+ };
1355
+ });
1356
+
1357
+ // src/dashboard/models.ts
1358
+ class ModelsController {
1359
+ changed;
1360
+ port;
1361
+ active = false;
1362
+ key = "";
1363
+ agent = "";
1364
+ seedModel = "";
1365
+ selection;
1366
+ resolution = 120;
1367
+ from = 0;
1368
+ to = 1;
1369
+ generation = 0;
1370
+ abort;
1371
+ pending;
1372
+ modelPages = [undefined];
1373
+ requestPages = [undefined];
1374
+ state = {
1375
+ status: "idle",
1376
+ stale: false,
1377
+ sort: "requests",
1378
+ windowMs: 300000,
1379
+ modelPage: 0,
1380
+ requestPage: 0
1381
+ };
1382
+ constructor(changed) {
1383
+ this.changed = changed;
1384
+ }
1385
+ getView() {
1386
+ return this.state;
1387
+ }
1388
+ configure(port, active, windowMs, agent, seedModel, now) {
1389
+ const key = JSON.stringify([
1390
+ port.location.directory,
1391
+ windowMs,
1392
+ agent,
1393
+ seedModel
1394
+ ]);
1395
+ const different = key !== this.key;
1396
+ const preserveSelection = this.port?.location.directory === port.location.directory && this.agent === agent && this.seedModel === seedModel;
1397
+ const entering = active && !this.active;
1398
+ if (different || !active)
1399
+ this.cancel();
1400
+ this.port = port;
1401
+ this.active = active;
1402
+ if (different) {
1403
+ this.key = key;
1404
+ this.agent = agent;
1405
+ this.seedModel = seedModel;
1406
+ if (!preserveSelection)
1407
+ this.selection = seedModel || undefined;
1408
+ this.modelPages = [undefined];
1409
+ this.requestPages = [undefined];
1410
+ this.state = {
1411
+ status: "idle",
1412
+ stale: false,
1413
+ sort: this.state.sort,
1414
+ windowMs,
1415
+ modelPage: 0,
1416
+ requestPage: 0
1417
+ };
1418
+ }
1419
+ if (active && (different || entering))
1420
+ this.refresh(now);
1421
+ }
1422
+ cancel() {
1423
+ this.generation++;
1424
+ this.abort?.abort();
1425
+ this.abort = undefined;
1426
+ this.pending = undefined;
1427
+ }
1428
+ stop() {
1429
+ this.cancel();
1430
+ this.active = false;
1431
+ this.state = { ...this.state, stale: Boolean(this.state.result) };
1432
+ }
1433
+ async refresh(now) {
1434
+ if (!this.active || !this.port)
1435
+ return;
1436
+ if (this.pending)
1437
+ return this.pending;
1438
+ this.to = Math.max(1, Math.floor(now));
1439
+ this.from = Math.max(0, this.to - this.state.windowMs);
1440
+ this.modelPages = [undefined];
1441
+ this.requestPages = [undefined];
1442
+ this.state = { ...this.state, modelPage: 0, requestPage: 0 };
1443
+ return this.query(false);
1444
+ }
1445
+ setWidth(width) {
1446
+ const next = Math.max(1, Math.min(120, Math.floor(width) - 10));
1447
+ if (next === this.resolution)
1448
+ return;
1449
+ this.resolution = next;
1450
+ if (this.active && this.state.result)
1451
+ this.query(true);
1452
+ }
1453
+ select(model) {
1454
+ if (!this.active || model === this.selection)
1455
+ return;
1456
+ this.selection = model;
1457
+ this.requestPages = [undefined];
1458
+ this.state = { ...this.state, requestPage: 0 };
1459
+ this.query(true);
1460
+ }
1461
+ sort() {
1462
+ const sorts = ["requests", "tps", "ttft", "identity"];
1463
+ this.modelPages = [undefined];
1464
+ this.state = {
1465
+ ...this.state,
1466
+ sort: sorts[(sorts.indexOf(this.state.sort) + 1) % sorts.length] ?? "requests",
1467
+ modelPage: 0
1468
+ };
1469
+ this.query(true);
1470
+ }
1471
+ page(kind, direction) {
1472
+ if (this.state.status !== "ready")
1473
+ return;
1474
+ const pages = kind === "models" ? this.modelPages : this.requestPages;
1475
+ const index = kind === "models" ? this.state.modelPage : this.state.requestPage;
1476
+ const next = kind === "models" ? this.state.result?.nextModelsCursor : this.state.result?.nextRequestsCursor;
1477
+ if (direction < 0 && index === 0 || direction > 0 && !next)
1478
+ return;
1479
+ if (direction > 0)
1480
+ pages[index + 1] = next;
1481
+ this.state = {
1482
+ ...this.state,
1483
+ ...kind === "models" ? { modelPage: index + direction, requestPage: 0 } : { requestPage: index + direction }
1484
+ };
1485
+ if (kind === "models") {
1486
+ this.selection = undefined;
1487
+ this.requestPages = [undefined];
1488
+ }
1489
+ this.query(true);
1490
+ }
1491
+ query(reuse) {
1492
+ this.cancel();
1493
+ const port = this.port;
1494
+ if (!this.active || !port)
1495
+ return Promise.resolve();
1496
+ if (!port.analytics) {
1497
+ this.state = { ...this.state, status: "unsupported", stale: false };
1498
+ this.changed();
1499
+ return Promise.resolve();
1500
+ }
1501
+ const read = port.analytics.bind(port);
1502
+ const controller = new AbortController;
1503
+ this.abort = controller;
1504
+ const generation = this.generation;
1505
+ const signal = AbortSignal.any([
1506
+ controller.signal,
1507
+ AbortSignal.timeout(15000)
1508
+ ]);
1509
+ const modelCursor = this.modelPages[this.state.modelPage];
1510
+ const requestCursor = this.requestPages[this.state.requestPage];
1511
+ const input = {
1512
+ schemaVersion: 1,
1513
+ from: this.from,
1514
+ to: this.to,
1515
+ buckets: this.resolution,
1516
+ sort: this.state.sort,
1517
+ ...this.agent ? { agent: this.agent } : {},
1518
+ ...this.selection === "" ? { unknownModel: true } : this.selection ? { model: this.selection } : {},
1519
+ ...reuse && this.state.result ? { snapshotId: this.state.result.snapshotId } : {},
1520
+ ...reuse && modelCursor ? { modelCursor } : {},
1521
+ ...reuse && requestCursor ? { requestCursor } : {}
1522
+ };
1523
+ this.state = {
1524
+ ...this.state,
1525
+ status: "loading",
1526
+ stale: Boolean(this.state.result)
1527
+ };
1528
+ this.changed();
1529
+ const task = Promise.resolve().then(async () => {
1530
+ try {
1531
+ let result;
1532
+ try {
1533
+ result = await read(input, signal);
1534
+ } catch (error) {
1535
+ if (!input.snapshotId || signal.aborted || error instanceof AnalyticsUnavailable && error.reason === "unsupported")
1536
+ throw error;
1537
+ const {
1538
+ snapshotId: _id,
1539
+ modelCursor: _m,
1540
+ requestCursor: _r,
1541
+ ...fresh
1542
+ } = input;
1543
+ result = await read(fresh, signal);
1544
+ if (generation !== this.generation)
1545
+ return;
1546
+ signal.throwIfAborted();
1547
+ this.modelPages = [undefined];
1548
+ this.requestPages = [undefined];
1549
+ this.state = { ...this.state, modelPage: 0, requestPage: 0 };
1550
+ }
1551
+ if (generation !== this.generation)
1552
+ return;
1553
+ signal.throwIfAborted();
1554
+ if (!isAnalyticsResult(result) || result.from !== input.from || result.to !== input.to || result.agent !== input.agent || result.requests.some((r) => r.location !== port.location.directory))
1555
+ throw new Error("Invalid analytics response");
1556
+ if (result.selected)
1557
+ this.selection = result.selected.model ?? "";
1558
+ this.state = {
1559
+ ...this.state,
1560
+ result: structuredClone(result),
1561
+ status: result.enabled ? "ready" : "disabled",
1562
+ stale: false
1563
+ };
1564
+ } catch (error) {
1565
+ if (generation !== this.generation || controller.signal.aborted)
1566
+ return;
1567
+ this.state = {
1568
+ ...this.state,
1569
+ status: error instanceof AnalyticsUnavailable && error.reason === "unsupported" && !this.state.result ? "unsupported" : "unavailable",
1570
+ stale: Boolean(this.state.result)
1571
+ };
1572
+ } finally {
1573
+ if (generation === this.generation) {
1574
+ this.pending = undefined;
1575
+ this.changed();
1576
+ }
1577
+ }
1578
+ });
1579
+ this.pending = task;
1580
+ return task;
1581
+ }
1582
+ }
1583
+ var init_models = __esm(() => {
1584
+ init_rpc();
1585
+ init_port();
1586
+ });
1587
+
1167
1588
  // src/dashboard/view.ts
1168
1589
  function immutableView(value) {
1169
1590
  const copy = structuredClone(value);
@@ -1252,10 +1673,12 @@ class DashboardController {
1252
1673
  pendingHistory;
1253
1674
  historyAbort;
1254
1675
  historyBusy = false;
1676
+ historyReason;
1255
1677
  historyStatus = "idle";
1256
1678
  refreshTimer;
1257
1679
  ageTimer;
1258
1680
  view;
1681
+ models;
1259
1682
  constructor(port, options = {}) {
1260
1683
  this.limits = { ...options.limits ?? inventoryLimits };
1261
1684
  validateInventoryLimits(this.limits);
@@ -1266,6 +1689,7 @@ class DashboardController {
1266
1689
  throw new Error("Invalid dashboard refresh interval");
1267
1690
  this.state = this.createLocation(port);
1268
1691
  this.locations.set(port.location.directory, this.state);
1692
+ this.models = new ModelsController(() => this.publish());
1269
1693
  this.view = this.buildView();
1270
1694
  }
1271
1695
  createLocation(port) {
@@ -1315,6 +1739,7 @@ class DashboardController {
1315
1739
  await this.refresh();
1316
1740
  }
1317
1741
  stop() {
1742
+ this.models.stop();
1318
1743
  this.running = false;
1319
1744
  this.generation++;
1320
1745
  this.scope?.abort();
@@ -1349,6 +1774,7 @@ class DashboardController {
1349
1774
  this.locations.delete(directory);
1350
1775
  this.locations.set(directory, next);
1351
1776
  this.state = next;
1777
+ this.configureModels();
1352
1778
  if (this.locations.size > 4) {
1353
1779
  const oldest = this.locations.keys().next().value;
1354
1780
  if (oldest !== undefined)
@@ -1370,11 +1796,12 @@ class DashboardController {
1370
1796
  this.publish();
1371
1797
  }
1372
1798
  setScreen(screen) {
1373
- if (!["overview", "detail", "history"].includes(screen))
1799
+ if (!["overview", "detail", "history", "models"].includes(screen))
1374
1800
  throw new Error("Invalid dashboard screen");
1375
1801
  if (screen === this.screen)
1376
1802
  return;
1377
1803
  this.screen = screen;
1804
+ this.configureModels();
1378
1805
  this.queueHistory();
1379
1806
  this.publish();
1380
1807
  }
@@ -1384,6 +1811,7 @@ class DashboardController {
1384
1811
  if (windowMs === this.windowMs)
1385
1812
  return;
1386
1813
  this.windowMs = windowMs;
1814
+ this.configureModels();
1387
1815
  this.queueHistory();
1388
1816
  this.publish();
1389
1817
  }
@@ -1396,11 +1824,18 @@ class DashboardController {
1396
1824
  agent: filters.agent ?? this.filters.agent,
1397
1825
  model: filters.model ?? this.filters.model
1398
1826
  };
1827
+ this.configureModels();
1399
1828
  this.publish();
1400
1829
  }
1830
+ configureModels() {
1831
+ this.models.configure(this.state.port, this.running && this.screen === "models", this.windowMs, this.filters.agent, this.filters.model, this.serverNow());
1832
+ }
1401
1833
  async refresh() {
1402
1834
  if (!this.running || !this.scope)
1403
1835
  return;
1836
+ this.configureModels();
1837
+ if (this.screen === "models")
1838
+ this.models.refresh(this.serverNow());
1404
1839
  for (const cached of this.cache.values())
1405
1840
  if (cached.directory === this.state.port.location.directory)
1406
1841
  cached.valid = false;
@@ -1530,6 +1965,7 @@ class DashboardController {
1530
1965
  }
1531
1966
  cancelHistory() {
1532
1967
  this.historyGeneration++;
1968
+ this.historyReason = undefined;
1533
1969
  this.pendingHistory = undefined;
1534
1970
  this.historyAbort?.abort();
1535
1971
  }
@@ -1583,10 +2019,10 @@ class DashboardController {
1583
2019
  }, signal);
1584
2020
  if (signal.aborted || task.generation !== this.historyGeneration)
1585
2021
  return;
1586
- if (!isHistoryResult(result) || result.data.series.some((series) => series.sessionId !== task.sessionId))
1587
- throw new Error("Invalid history response");
2022
+ checkedHistory(result, task.sessionId);
1588
2023
  this.cache.set(task.key, {
1589
2024
  directory: task.state.port.location.directory,
2025
+ loadedAt: this.now(),
1590
2026
  valid: true,
1591
2027
  result: structuredClone(result),
1592
2028
  from,
@@ -1598,12 +2034,16 @@ class DashboardController {
1598
2034
  this.cache.delete(oldest);
1599
2035
  }
1600
2036
  this.historyStatus = result.enabled ? "ready" : "disabled";
1601
- } catch {
1602
- if (task.generation === this.historyGeneration && !task.scope.aborted)
2037
+ } catch (error) {
2038
+ if (task.generation === this.historyGeneration && !task.scope.aborted) {
1603
2039
  this.historyStatus = "unavailable";
2040
+ this.historyReason = historyFailure(error, signal).reason;
2041
+ }
1604
2042
  } finally {
1605
- if (task.generation === this.historyGeneration && signal.aborted && !task.scope.aborted)
2043
+ if (task.generation === this.historyGeneration && signal.aborted && !task.scope.aborted) {
1606
2044
  this.historyStatus = "unavailable";
2045
+ this.historyReason = historyFailure(undefined, signal).reason;
2046
+ }
1607
2047
  this.historyBusy = false;
1608
2048
  if (this.historyAbort === controller)
1609
2049
  this.historyAbort = undefined;
@@ -1688,12 +2128,13 @@ class DashboardController {
1688
2128
  ...selected ? { selected } : {},
1689
2129
  history: {
1690
2130
  status: this.historyStatus,
2131
+ ...this.historyReason ? { reason: this.historyReason } : {},
1691
2132
  windowMs: this.windowMs,
1692
2133
  maxWindowMs: 604800000,
1693
2134
  maxPoints: 500,
1694
2135
  stale: this.historyStatus === "loading" || this.historyStatus === "unavailable" || !this.running,
1695
2136
  ...state.selected ? { sessionId: state.selected } : {},
1696
- ...cached ? { from: cached.from, to: cached.to } : {},
2137
+ ...cached ? { from: cached.from, to: cached.to, loadedAt: cached.loadedAt } : {},
1697
2138
  ...projected ? { result: projected.result, summary: projected.summary } : {},
1698
2139
  notices: projected?.notices ?? [
1699
2140
  "bounded-retention",
@@ -1701,6 +2142,7 @@ class DashboardController {
1701
2142
  "maximum-500-points-and-requests"
1702
2143
  ]
1703
2144
  },
2145
+ models: this.models.getView(),
1704
2146
  notices: [
1705
2147
  ...!state.complete ? ["partial-inventory"] : [],
1706
2148
  ...state.connection !== "live" ? ["live-data-stale"] : [],
@@ -1710,9 +2152,10 @@ class DashboardController {
1710
2152
  }
1711
2153
  }
1712
2154
  var init_controller = __esm(() => {
1713
- init_rpc();
2155
+ init_historyclient();
1714
2156
  init_transport();
1715
2157
  init_inventory();
2158
+ init_models();
1716
2159
  init_view();
1717
2160
  });
1718
2161
 
@@ -1865,16 +2308,28 @@ async function connectDashboard(options, discovery = Service) {
1865
2308
  }
1866
2309
  };
1867
2310
  },
2311
+ async analytics(input, signal) {
2312
+ if (!isAnalyticsRead(input))
2313
+ throw new Error("Invalid analytics request");
2314
+ try {
2315
+ const result = await client.rpc(analyticsRpc).read(input, { location, signal: deadline(signal) });
2316
+ if (!isAnalyticsResult(result))
2317
+ throw new Error("Invalid analytics response");
2318
+ return result;
2319
+ } catch (error) {
2320
+ const missing = typeof error === "object" && error !== null && "type" in error && (error.type === "rpc.unavailable" || error.type === "rpc.method_not_found");
2321
+ throw new AnalyticsUnavailable(missing ? "unsupported" : "unavailable");
2322
+ }
2323
+ },
1868
2324
  async history(input, signal) {
1869
2325
  if (!isHistoryRead(input))
1870
2326
  throw new Error("History requires one session, at most seven days and 16\u2013500 points");
2327
+ const requestSignal = deadline(signal);
1871
2328
  try {
1872
- const result = await client.rpc(historyRpc).read(input, { location, signal: deadline(signal) });
1873
- if (!isHistoryResult(result))
1874
- throw new Error;
1875
- return result;
1876
- } catch {
1877
- throw new Error("Context Guard history unavailable or incompatible in this location");
2329
+ const result = await client.rpc(historyRpc).read(input, { location, signal: requestSignal });
2330
+ return checkedHistory(result, input.sessionId);
2331
+ } catch (error) {
2332
+ throw historyFailure(error, requestSignal);
1878
2333
  }
1879
2334
  }
1880
2335
  };
@@ -1891,7 +2346,212 @@ async function connectDashboard(options, discovery = Service) {
1891
2346
  };
1892
2347
  }
1893
2348
  var init_connection = __esm(() => {
2349
+ init_historyclient();
1894
2350
  init_rpc();
2351
+ init_port();
2352
+ });
2353
+
2354
+ // src/tui/quantity.ts
2355
+ function tokens(value) {
2356
+ if (value === undefined || !Number.isFinite(value))
2357
+ return "?";
2358
+ const magnitude = Math.abs(value);
2359
+ if (magnitude >= 1e6)
2360
+ return `${+(value / 1e6).toFixed(1)}M`;
2361
+ if (magnitude >= 1000)
2362
+ return `${+(value / 1000).toFixed(1)}K`;
2363
+ return String(value);
2364
+ }
2365
+
2366
+ // src/tui/statusformat.ts
2367
+ function freshnessLabel(row2) {
2368
+ switch (row2?.freshness) {
2369
+ case "fresh":
2370
+ return;
2371
+ case "stale":
2372
+ return "Measurement out of date";
2373
+ case "invalid":
2374
+ return "Usage unavailable";
2375
+ default:
2376
+ return "Usage unknown";
2377
+ }
2378
+ }
2379
+ function measurementLabel(row2, ageMs) {
2380
+ const m = row2?.snapshot?.measurement;
2381
+ if (!m || row2?.freshness === "missing")
2382
+ return "Usage unknown";
2383
+ if (row2?.freshness === "invalid")
2384
+ return "Usage unavailable";
2385
+ const age = ageMs === undefined ? "" : ` \xB7 measured ${Math.floor(Math.max(0, ageMs) / 1000)}s ago`;
2386
+ return `${m.confidence === "reported" ? "Provider reported" : "Estimated"}${age}${row2?.freshness === "stale" ? " \xB7 out of date" : ""}`;
2387
+ }
2388
+ function measurementExplanation(row2) {
2389
+ const m = row2.snapshot?.measurement;
2390
+ if (!m || row2.freshness === "missing")
2391
+ return ["No measurement available."];
2392
+ if (row2.freshness === "invalid")
2393
+ return ["Measurement cannot be used for this session."];
2394
+ return [
2395
+ m.scope === "completed-request" ? "Usage from the last completed request; not the next request's size." : "Usage for the prepared request; not a completed request.",
2396
+ m.provenance === "serialized-estimate" ? "Estimate from serialized request content." : "Provider usage includes input and cache tokens."
2397
+ ];
2398
+ }
2399
+ function compactionLabel(row2, compact = false) {
2400
+ const phase = row2?.snapshot?.compaction.phase;
2401
+ if (!phase || phase === "idle")
2402
+ return;
2403
+ if (compact)
2404
+ return {
2405
+ requesting: "Requesting",
2406
+ accepted: "Waiting",
2407
+ running: "Compacting",
2408
+ "awaiting-measurement": "Checking",
2409
+ blocked: "Retry held",
2410
+ uncertain: "Unconfirmed"
2411
+ }[phase];
2412
+ return {
2413
+ requesting: "Requesting compaction",
2414
+ accepted: "Compaction request accepted \xB7 waiting to start",
2415
+ running: "Compaction running",
2416
+ "awaiting-measurement": "Compaction ended \xB7 checking context reduction",
2417
+ blocked: "Automatic compaction retry suppressed",
2418
+ uncertain: "Completion unconfirmed \xB7 automatic retry suppressed"
2419
+ }[phase];
2420
+ }
2421
+ function reasonExplanation(reason) {
2422
+ switch (reason) {
2423
+ case "below-threshold":
2424
+ return "Context is below the policy threshold.";
2425
+ case "threshold-crossed":
2426
+ return "Context reached the policy threshold; this does not guarantee compaction.";
2427
+ case "missing-usage":
2428
+ return "No measurement available for the policy decision.";
2429
+ case "model-changed":
2430
+ return "The measurement belongs to a different model.";
2431
+ case "invalid-usage":
2432
+ return "The measurement cannot be used for the policy decision.";
2433
+ case "stale-usage":
2434
+ return "The policy measurement is out of date.";
2435
+ case "missing-model-budget":
2436
+ return "Model capacity is unavailable for this threshold.";
2437
+ case "incompatible-model-budget":
2438
+ return "The threshold is incompatible with model capacity.";
2439
+ case "compaction-failed":
2440
+ return "Compaction failed; automatic retries are limited after repeated failures.";
2441
+ case "reclaim-unverifiable":
2442
+ return "Context reduction could not be verified with comparable provider readings.";
2443
+ case "insufficient-reclaim":
2444
+ return "Measured context reduction was below the required minimum.";
2445
+ case "admission-outcome-unknown":
2446
+ return "Whether the compaction request was accepted is unconfirmed.";
2447
+ case "completion-event-timeout":
2448
+ return "No confirming completion event arrived within the waiting period.";
2449
+ case "restart-needs-lifecycle-evidence":
2450
+ return "The previous request needs confirming lifecycle evidence after restart.";
2451
+ case "event-stream-gap":
2452
+ return "Automatic requests are suspended after an event gap; reload required.";
2453
+ case "persistence-unavailable":
2454
+ return "Safety storage is unavailable; repair storage and reload.";
2455
+ case "capacity-exceeded":
2456
+ return "Monitoring limit reached; some sessions are unavailable.";
2457
+ case "model-metadata-unavailable":
2458
+ return "Some model limits are unavailable.";
2459
+ default:
2460
+ return "See diagnostic codes.";
2461
+ }
2462
+ }
2463
+ function pressureLabel(row2) {
2464
+ if (row2?.freshness !== "fresh")
2465
+ return "Threshold pressure unknown";
2466
+ switch (row2.pressure) {
2467
+ case "crossed":
2468
+ return "Threshold reached";
2469
+ case "near":
2470
+ return "Near threshold";
2471
+ case "normal":
2472
+ return "Below threshold";
2473
+ default:
2474
+ return "Threshold pressure unknown";
2475
+ }
2476
+ }
2477
+ function thresholdDistance(row2, compact = false) {
2478
+ const delta = row2?.triggerDeltaTokens;
2479
+ if (row2?.freshness !== "fresh" || row2.pressure === "unknown" || delta === undefined || !Number.isFinite(delta))
2480
+ return "pressure unknown";
2481
+ if (delta === 0)
2482
+ return row2.pressure === "crossed" ? "Threshold reached" : "At threshold \xB7 requires more";
2483
+ if (compact)
2484
+ return `${tokens(Math.abs(delta))} ${delta < 0 ? "over" : "below"}${row2.pressure === "near" ? " \xB7 Near threshold" : delta < 0 ? " \xB7 Threshold reached" : ""}`;
2485
+ return `${tokens(Math.abs(delta))} ${Math.abs(delta) === 1 ? "token" : "tokens"} ${delta < 0 ? "over" : "below"} threshold`;
2486
+ }
2487
+ function guardStatusLines(health, compact = false) {
2488
+ switch (health?.enforcement) {
2489
+ case "observing":
2490
+ return compact ? ["Observing", "No Guard compact"] : ["Observing \xB7 Guard won't request compaction"];
2491
+ case "available":
2492
+ return compact ? ["Guard requests", "enabled"] : ["Guard automatic requests enabled"];
2493
+ case "disabled":
2494
+ return ["Guard disabled"];
2495
+ case "suspended": {
2496
+ const gap = health.reasons.includes("event-stream-gap");
2497
+ const storage = health.reasons.includes("persistence-unavailable");
2498
+ return compact ? [
2499
+ "Guard suspended",
2500
+ ...storage ? ["Storage: repair"] : [],
2501
+ ...gap ? ["Reload required"] : storage ? ["Then reload"] : ["See Details"]
2502
+ ] : [
2503
+ "Guard suspended" + (storage ? " \xB7 safety storage unavailable; repair storage and reload" : "") + (gap ? " \xB7 reload required" : storage ? "" : " \xB7 see Details")
2504
+ ];
2505
+ }
2506
+ default:
2507
+ return ["Guard status unknown"];
2508
+ }
2509
+ }
2510
+ function panelStatus(health) {
2511
+ return guardStatusLines(health).join(" \xB7 ");
2512
+ }
2513
+ function connectionLabel(connection, compact = false) {
2514
+ switch (connection) {
2515
+ case "loading":
2516
+ return "Connecting";
2517
+ case "live":
2518
+ return "Live";
2519
+ case "reconnecting":
2520
+ return "Reconnecting";
2521
+ case "unavailable":
2522
+ return compact ? "Unavailable" : "Connection unavailable";
2523
+ case "incompatible":
2524
+ return compact ? "Unsupported" : "Monitoring unsupported";
2525
+ }
2526
+ }
2527
+ function connectionNotice(connection, retainedMeasurements, compact = false) {
2528
+ if (connection === "live")
2529
+ return;
2530
+ return retainedMeasurements ? compact ? "Last data shown" : "Showing last received measurements" : compact ? "No measurements" : "No measurements available";
2531
+ }
2532
+ function monitorStatusLines(connection, health, retainedMeasurements, compact = false) {
2533
+ const notice = connectionNotice(connection, retainedMeasurements, compact);
2534
+ const reasons = health?.reasons ?? [];
2535
+ const limited = reasons.includes("capacity-exceeded");
2536
+ const model = reasons.includes("model-metadata-unavailable");
2537
+ const attention = health?.enforcement !== "suspended" && reasons.length > 0;
2538
+ return [
2539
+ ...health && connection !== "live" ? [compact ? "Last known:" : "Last known Guard:"] : [],
2540
+ ...guardStatusLines(health, compact),
2541
+ ...attention ? compact ? [limited ? "Monitor limit" : model ? "Limits unknown" : "See Details"] : [
2542
+ ...limited ? ["Monitoring limit reached \xB7 some sessions unavailable"] : [],
2543
+ ...model ? ["Some model limits unavailable \xB7 context may be unknown"] : [],
2544
+ ...!limited && !model ? ["Guard needs attention \xB7 see Details"] : []
2545
+ ] : [],
2546
+ ...notice ? [notice] : []
2547
+ ];
2548
+ }
2549
+ var contextHistoryLegend;
2550
+ var init_statusformat = __esm(() => {
2551
+ contextHistoryLegend = [
2552
+ "Recent samples \u2192 now \xB7 \u2504 threshold",
2553
+ "Dim = last known context"
2554
+ ];
1895
2555
  });
1896
2556
 
1897
2557
  // src/tui/format.ts
@@ -1921,22 +2581,6 @@ function clip(value, width) {
1921
2581
  }
1922
2582
  return width < 3 ? ".".repeat(Math.max(0, width)) : result + "...";
1923
2583
  }
1924
- function rowLines(row2, width) {
1925
- const s = row2.snapshot, m = s?.measurement;
1926
- const prefix = row2.current ? "* " : row2.depth > 5 ? ".../" : " ".repeat(Math.min(row2.depth, 5)) + "|-";
1927
- const percent = row2.utilizationPercent === undefined ? "?" : `${row2.utilizationPercent.toFixed(1)}%`;
1928
- const warning = row2.freshness === "stale" ? " STALE" : row2.pressure === "crossed" ? " !! crossed" : row2.pressure === "near" ? " ! near" : "";
1929
- const suffix = ` ${statuses[row2.node.status]} ${m?.confidence === "approximate" ? "~" : ""}${percent}${warning}`;
1930
- const label = clip(`${prefix}${row2.node.agent ?? row2.node.sessionId}`, Math.max(1, width - cells(suffix)));
1931
- return [
1932
- clip(`${label}${suffix}`, width),
1933
- ...performanceLines(row2, width),
1934
- ...wrap(`${quantity(m?.tokens)}/${quantity(s?.capacityTokens)} input; to ${quantity(row2.triggerDeltaTokens)}`, width),
1935
- ...s && s.compaction.phase !== "idle" ? wrap(`compact: ${s.compaction.phase}`, width) : [],
1936
- ...row2.node.interrupted ? [clip("interrupted", width)] : [],
1937
- ...row2.hidden ? wrap(`+ ${row2.hidden} completed descendants`, width) : []
1938
- ];
1939
- }
1940
2584
  function detailLines(row2, width, expanded = false, ageMs = 0) {
1941
2585
  const s = row2?.snapshot, m = s?.measurement;
1942
2586
  if (!row2)
@@ -1947,22 +2591,29 @@ function detailLines(row2, width, expanded = false, ageMs = 0) {
1947
2591
  ...wrap("Guard: not monitored / unavailable", width)
1948
2592
  ];
1949
2593
  const threshold = s.policy.configured;
1950
- const headroom = row2.triggerDeltaTokens;
2594
+ const valid2 = row2.freshness === "fresh" || row2.freshness === "stale";
1951
2595
  const lines = [
1952
2596
  `${s.agent ?? "?"} / ${s.model ?? "?"}`,
2597
+ `${row2.freshness === "stale" ? "Last measured" : "Context"} ${valid2 && m?.confidence === "approximate" ? "~" : ""}${quantity(valid2 ? m?.tokens : undefined)} / ${quantity(s.capacityTokens)} input`,
2598
+ `${valid2 ? row2.utilizationPercent?.toFixed(1) ?? "?" : "?"}% capacity \xB7 ${pressureLabel(row2)}`,
2599
+ `Threshold ${quantity(s.policy.triggerTokens)} \xB7 ${thresholdDistance(row2)}`,
2600
+ measurementLabel(row2, ageMs),
2601
+ ...measurementExplanation(row2),
2602
+ compactionLabel(row2) ?? "No compaction request in progress.",
2603
+ ...s.compaction.blockedReason ? [reasonExplanation(s.compaction.blockedReason)] : [],
2604
+ `Recorded policy decision: ${reasonExplanation(s.policy.reason)}`,
1953
2605
  ...performanceLines(row2, width),
1954
- `${m?.confidence === "approximate" ? "~" : ""}${quantity(m?.tokens)} / ${quantity(s.capacityTokens)} input`,
1955
- `${row2.utilizationPercent?.toFixed(1) ?? "?"}% ${row2.pressure === "crossed" ? "!! crossed" : row2.pressure === "near" ? "! near" : row2.freshness.toUpperCase()}`,
1956
- `Trigger ${threshold.type === "ratio" ? `${threshold.value * 100}%` : `${threshold.value} ${threshold.type}`} (${s.policy.comparison})`,
1957
- `Resolved trigger ${s.policy.triggerTokens ?? "?"}`,
1958
- `Until trigger ${quantity(headroom)}`,
1959
- `Compact: ${s.compaction.phase}`,
1960
- `${s.compaction.armed ? "armed" : "disarmed"}${s.compaction.blockedReason ? ` / ${s.compaction.blockedReason}` : ""}`,
1961
- m ? `${m.confidence} ${m.scope}` : "measurement unknown",
1962
- m ? `${Math.floor(Math.max(0, ageMs) / 1000)}s ago; ${row2.freshness}` : ""
2606
+ ...s.performance ? [
2607
+ "TPS: tokens per second. TTFT: time to first output (text or reasoning); approximate.",
2608
+ "Live TPS is estimated; completed rates use provider output including reasoning and matched timing.",
2609
+ "Model average uses matched request durations; session average includes idle and tool time.",
2610
+ ...s.performance.partial ? [
2611
+ "Incomplete timing coverage for model averages; only matched observed durations are included."
2612
+ ] : []
2613
+ ] : []
1963
2614
  ];
1964
2615
  if (expanded)
1965
- lines.push(`Policy ${s.policy.id}: ${s.policy.reason}`, `Limits in/context/out ${quantity(s.limits?.input)}/${quantity(s.limits?.context)}/${quantity(s.limits?.output)}`, `Output reserve ${quantity(s.outputReserveTokens)}`, `Provenance ${m?.provenance ?? "?"}`, `Cooldown until ${s.compaction.cooldownUntil ?? "?"}`, `Cumulative (${s.cumulativeScope})`, `Input ${quantity(s.cumulative?.input)} output ${quantity(s.cumulative?.output)}`, `Reasoning ${quantity(s.cumulative?.reasoning)}`, `Cache read ${quantity(s.cumulative?.cache.read)} write ${quantity(s.cumulative?.cache.write)}`);
2616
+ lines.push("Diagnostic codes and accounting:", `Compaction phase: ${s.compaction.phase}`, `Armed: ${s.compaction.armed} (one eligibility condition, not permission to compact)`, `Blocked reason: ${s.compaction.blockedReason ?? "?"}`, `Measurement confidence: ${m?.confidence ?? "?"}`, `Measurement scope: ${m?.scope ?? "?"}`, `Measurement freshness: ${row2.freshness}`, `Trigger ${threshold.type === "ratio" ? `${threshold.value * 100}%` : `${threshold.value} ${threshold.type}`} (${s.policy.comparison})`, `Resolved trigger ${s.policy.triggerTokens ?? "?"}`, `Policy ${s.policy.id}: ${s.policy.reason}`, `Pressure code: ${row2.pressure}`, `Recorded tokens: ${quantity(m?.tokens)}`, `Limits in/context/out ${quantity(s.limits?.input)}/${quantity(s.limits?.context)}/${quantity(s.limits?.output)}`, `Output reserve ${quantity(s.outputReserveTokens)}`, `Provenance ${m?.provenance ?? "?"}`, `Cooldown until ${s.compaction.cooldownUntil ?? "?"}`, `Cumulative (${s.cumulativeScope})`, `Input ${quantity(s.cumulative?.input)} output ${quantity(s.cumulative?.output)}`, `Reasoning ${quantity(s.cumulative?.reasoning)}`, `Cache read ${quantity(s.cumulative?.cache.read)} write ${quantity(s.cumulative?.cache.write)}`);
1966
2617
  return lines.filter(Boolean).flatMap((line) => wrap(line, width));
1967
2618
  }
1968
2619
  function sanitize(value) {
@@ -1999,22 +2650,16 @@ function performanceLines(row2, width) {
1999
2650
  ...wrap(`TTFT ${p.ttftMs === undefined ? "?" : `~${(p.ttftMs / 1000).toFixed(2)}s`}${p.latestRequestTps === undefined ? "" : ` \xB7 Last ${value(p.latestRequestTps)} t/s`}`, width)
2000
2651
  ];
2001
2652
  }
2002
- var segmenter, statuses;
2653
+ var segmenter;
2003
2654
  var init_format = __esm(() => {
2655
+ init_statusformat();
2004
2656
  segmenter = new Intl.Segmenter(undefined, { granularity: "grapheme" });
2005
- statuses = {
2006
- running: "RUN",
2007
- idle: "IDLE",
2008
- completed: "DONE",
2009
- failed: "FAIL",
2010
- unknown: "?"
2011
- };
2012
2657
  });
2013
2658
 
2014
2659
  // src/tui/chartformat.ts
2015
- function columns(points, from, to, width, gaps = []) {
2660
+ function columns(points, from, to, width, gaps = [], halfOpen = false) {
2016
2661
  const size = Math.max(1, Math.min(120, Math.floor(width))), values = Array.from({ length: size });
2017
- const column = (time) => Math.max(0, Math.min(size - 1, Math.floor((time - from) / Math.max(1, to - from) * (size - 1))));
2662
+ const column = (time) => Math.max(0, Math.min(size - 1, Math.floor((time - from) / Math.max(1, to - from) * (halfOpen ? size : size - 1))));
2018
2663
  let min = Infinity, max = -Infinity;
2019
2664
  for (const point of points) {
2020
2665
  if (point.time < from || point.time > to || point.value === undefined || !Number.isFinite(point.value) || point.value < 0)
@@ -2038,12 +2683,12 @@ function sparkline(points, from, to, width, gaps = []) {
2038
2683
  chars[column(time)] = "\u2506";
2039
2684
  return { line: chars.join(""), ...min === Infinity ? {} : { min, max } };
2040
2685
  }
2041
- function metricPlot(points, from, to, width, gaps = []) {
2686
+ function metricPlot(points, from, to, width, gaps = [], halfOpen = false) {
2042
2687
  const label = (value) => value >= 1e6 ? `${(value / 1e6).toFixed(1)}M` : value >= 1000 ? `${(value / 1000).toFixed(1)}k` : value.toFixed(1);
2043
2688
  const peak = Math.max(0, ...points.filter((p) => p.time >= from && p.time <= to && Number.isFinite(p.value)).map((p) => p.value ?? 0));
2044
2689
  const scale = Math.max(1, peak);
2045
2690
  const gutter = width >= 20 ? Math.max(label(scale).length, 3) : 0;
2046
- const { values, column } = columns(points, from, to, Math.max(1, width - gutter - 2), gaps);
2691
+ const { values, column } = columns(points, from, to, Math.max(1, halfOpen ? Math.min(points.length, width - gutter - 2) : width - gutter - 2), gaps, halfOpen);
2047
2692
  const gapColumns = new Set(gaps.filter((time) => time >= from && time <= to).map(column));
2048
2693
  const height = 6;
2049
2694
  const blocks = " \u2581\u2582\u2583\u2584\u2585\u2586\u2587\u2588";
@@ -2120,11 +2765,259 @@ var init_chartformat = __esm(() => {
2120
2765
  ];
2121
2766
  });
2122
2767
 
2768
+ // src/dashboard/modelview.ts
2769
+ function metricValue(summary, metric2) {
2770
+ if (metric2.key === "outputTokens" && !summary?.tpsRequests)
2771
+ return;
2772
+ const value = summary?.[metric2.key];
2773
+ return value === undefined ? undefined : value / metric2.divisor;
2774
+ }
2775
+ function terminalModelRow(model, focused) {
2776
+ return [
2777
+ `${focused ? "> " : " "}${model.model ?? "unknown model attribution"}`,
2778
+ ` ${stats(model)}`,
2779
+ ` TPS eligible ${model.tpsRequests}; TTFT eligible ${model.ttftRequests}; partial ${model.partialRequests}; legacy attribution ${model.legacyAttributionRequests}`
2780
+ ];
2781
+ }
2782
+ function terminalModels(view, width, detail, metricIndex, sample, focus, requests) {
2783
+ const state = view.models;
2784
+ const result = state.result;
2785
+ const lines = [
2786
+ `Models \xB7 ${state.status}${state.stale ? " \xB7 STALE cached results" : ""} \xB7 ${state.windowMs / 60000} minutes \xB7 retained history`,
2787
+ `Agent: ${(result?.agent ?? view.filters.agent) || "all"} \xB7 sort ${state.sort} \xB7 location ${view.location}`,
2788
+ "4 Models \xB7 Enter chart \xB7 Esc model list/overview \xB7 w window \xB7 f agent \xB7 r refresh"
2789
+ ];
2790
+ if (state.status === "unsupported")
2791
+ return [
2792
+ ...lines,
2793
+ "Models requires a server with context-guard.analytics.v1; update the server plugin. Overview and session history remain available."
2794
+ ];
2795
+ if (state.status === "disabled")
2796
+ return [
2797
+ ...lines,
2798
+ "Retained performance recording is disabled on this server."
2799
+ ];
2800
+ if (!result)
2801
+ return [
2802
+ ...lines,
2803
+ state.status === "loading" ? "Loading model aggregates\u2026" : "Model analytics unavailable; press r to retry."
2804
+ ];
2805
+ lines.push(`${new Date(result.from).toISOString()} \u2192 ${new Date(result.to).toISOString()} (UTC; completion time)`);
2806
+ const partial = result.coverage.partial || result.metadata.skippedBytes > 0 || result.metadata.malformedRecords > 0 || result.gaps.length > 0 || result.omittedGaps > 0;
2807
+ lines.push(`Coverage: ${partial ? "PARTIAL" : "bounded observations"}; 64 MiB scan limit; ${result.coverage.maxRequests} request identities maximum; no pre-observation reconstruction.`);
2808
+ lines.push(`Archive retained since ${result.metadata.retainedFrom === undefined ? "unknown" : new Date(result.metadata.retainedFrom).toISOString()}; snapshot observed at ${new Date(result.generatedAt).toISOString()}.`);
2809
+ lines.push(`Scan: ${result.coverage.scannedRecords} records; ${result.metadata.skippedBytes} bytes skipped; ${result.metadata.malformedRecords} malformed; ${result.coverage.conflictingRequests} conflicting; ${result.coverage.incompleteRequests} incomplete; ${result.coverage.invalidTimingRequests} invalid timing; gaps ${result.gaps.length}${result.omittedGaps ? ` (+${result.omittedGaps} omitted)` : ""}.`);
2810
+ if (!detail) {
2811
+ lines.push(`Model page ${state.modelPage + 1} \xB7 ${result.totalModels} models \xB7 Up/Down scroll/select \xB7 n/p pages \xB7 s sort`);
2812
+ if (!result.models.length)
2813
+ lines.push("No completed requests match this range and agent filter.");
2814
+ result.models.forEach((model, index) => {
2815
+ lines.push(...terminalModelRow(model, index === focus));
2816
+ });
2817
+ return lines;
2818
+ }
2819
+ const selected = result.selected;
2820
+ if (!selected)
2821
+ return [
2822
+ ...lines,
2823
+ "No completed requests for this model in the selected range."
2824
+ ];
2825
+ lines.push(`Model: ${selected.model ?? "unknown model attribution"}`, stats(selected));
2826
+ lines.push(`Reported output ${number(selected.tpsRequests ? selected.outputTokens : undefined)} tokens; matched duration ${number(selected.tpsRequests ? selected.durationMs / 1000 : undefined)}s; TPS eligible/excluded ${selected.tpsRequests}/${selected.excludedTpsRequests}; TTFT eligible/excluded ${selected.ttftRequests}/${selected.excludedTtftRequests}; partial ${selected.partialRequests}; legacy attribution ${selected.legacyAttributionRequests}`);
2827
+ if (requests) {
2828
+ lines.push(`Request page ${state.requestPage + 1} \xB7 n/p pages \xB7 d chart \xB7 completed stream \u2260 task success`);
2829
+ result.requests.forEach((r) => lines.push(`Request ${r.requestId} \xB7 session ${r.sessionId}`, `Completed ${new Date(r.completedAt).toISOString()} \xB7 ${r.agent ?? "unknown agent"} \xB7 output ${number(r.outputTokens)} \xB7 duration ${number((r.completedAt - r.startedAt) / 1000)}s \xB7 TTFT ${number(r.ttftMs === undefined ? undefined : r.ttftMs / 1000)}s ~ \xB7 ${r.attribution}${r.partial ? " \xB7 partial" : ""}`));
2830
+ return lines;
2831
+ }
2832
+ const metric2 = modelMetrics[metricIndex] ?? modelMetrics[0];
2833
+ lines.push(`\u2039 ${metric2.label} (${metric2.unit}) \u203A \xB7 Left/Right metric \xB7 [/] bucket \xB7 d requests`);
2834
+ lines.push(...metricPlot(result.buckets.map((bucket2) => {
2835
+ const value = metricValue(bucket2.summary, metric2);
2836
+ return {
2837
+ time: (bucket2.from + bucket2.to) / 2,
2838
+ ...value === undefined ? {} : { value }
2839
+ };
2840
+ }), result.from, result.to, width, result.gaps, true));
2841
+ lines.push("UTC buckets \xB7 blank = no eligible observations \xB7 \u2506 = retained gap; partial coverage may hide other gaps.");
2842
+ lines.push("TPS = reported output / summed matched request durations (includes TTFT); concurrent durations add. In-flight requests enter after stream completion.");
2843
+ const bucket = result.buckets[Math.min(sample, Math.max(0, result.buckets.length - 1))];
2844
+ if (bucket) {
2845
+ lines.push(`Bucket ${Math.min(sample + 1, result.buckets.length)}/${result.buckets.length}: ${new Date(bucket.from).toISOString()} \u2192 ${new Date(bucket.to).toISOString()}`);
2846
+ lines.push(bucket.summary ? stats(bucket.summary) : "No eligible observations; measurements unknown.");
2847
+ lines.push(`Selected ${metric2.label}: ${number(metricValue(bucket.summary, metric2))} ${metric2.unit}`);
2848
+ if (bucket.summary)
2849
+ lines.push(`Bucket eligibility: TPS ${bucket.summary.tpsRequests}/${bucket.summary.completedRequests}; TTFT ${bucket.summary.ttftRequests}/${bucket.summary.completedRequests}; partial ${bucket.summary.partialRequests}`);
2850
+ }
2851
+ return lines;
2852
+ }
2853
+ var modelMetrics, number = (n) => n === undefined ? "?" : Number.isInteger(n) ? String(n) : n.toFixed(2), stats = (m) => `${m.sessions} sessions \xB7 ${m.completedRequests} completed \xB7 weighted TPS ${number(m.averageTps)} \xB7 TTFT median/p95 ${number(m.medianTtftMs === undefined ? undefined : m.medianTtftMs / 1000)}/${number(m.p95TtftMs === undefined ? undefined : m.p95TtftMs / 1000)}s ~`;
2854
+ var init_modelview = __esm(() => {
2855
+ init_chartformat();
2856
+ modelMetrics = [
2857
+ { key: "averageTps", label: "Weighted request TPS", unit: "t/s", divisor: 1 },
2858
+ { key: "medianTtftMs", label: "TTFT median ~", unit: "s", divisor: 1000 },
2859
+ { key: "p95TtftMs", label: "TTFT p95 ~", unit: "s", divisor: 1000 },
2860
+ {
2861
+ key: "completedRequests",
2862
+ label: "Completed requests",
2863
+ unit: "requests",
2864
+ divisor: 1
2865
+ },
2866
+ { key: "outputTokens", label: "Reported output", unit: "tokens", divisor: 1 }
2867
+ ];
2868
+ });
2869
+
2870
+ // src/tui/panelformat.ts
2871
+ function panelSummary(row2, width, age, compact = false) {
2872
+ if (!row2)
2873
+ return [clip("No sessions available \xB7 r refresh", width)];
2874
+ const snapshot = row2.snapshot;
2875
+ const valid2 = row2.freshness === "fresh" || row2.freshness === "stale";
2876
+ const prefix = row2.freshness === "stale" ? "Last measured" : "Context";
2877
+ const percent = `${row2.utilizationPercent === undefined ? "?" : Math.round(row2.utilizationPercent)}%`;
2878
+ const estimate = valid2 && snapshot?.measurement?.confidence === "approximate" ? "~" : "";
2879
+ const identity = `${width < 28 ? ">" : "Selected:"} ${row2.node.agent ?? row2.node.sessionId}${row2.current ? width < 28 ? " *" : " (current)" : ""}`;
2880
+ const threshold = `Threshold ${tokens(snapshot?.policy.triggerTokens)} \xB7 ${thresholdDistance(row2)}`;
2881
+ const occupancy = compact ? `${prefix} ${estimate}${percent}` : `${prefix} ${estimate}${tokens(valid2 ? snapshot?.measurement?.tokens : undefined)}/${tokens(snapshot?.capacityTokens)} \xB7 ${percent}`;
2882
+ return [
2883
+ clip(identity, width),
2884
+ clip(occupancy, width),
2885
+ ...!compact ? [
2886
+ clip(cells(threshold) <= width ? threshold : thresholdDistance(row2), width),
2887
+ clip(`${row2.node.model ?? snapshot?.model ?? "Model unknown"} \xB7 ${panelStatuses[row2.node.status]}`, width),
2888
+ ...wrap(measurementLabel(row2, age), width)
2889
+ ] : []
2890
+ ];
2891
+ }
2892
+ function tableLine(identity, status, context, threshold, width) {
2893
+ const pad = (value, size) => {
2894
+ const text2 = clip(value, size);
2895
+ return text2 + " ".repeat(Math.max(0, size - cells(text2)));
2896
+ };
2897
+ return `${pad(identity, width - 47)} ${pad(status, 8)} ${pad(context, 7)} ${pad(threshold, 29)}`;
2898
+ }
2899
+ function panelRowHeading(label, width) {
2900
+ return width >= 72 ? tableLine(label, "State", "Context", "Threshold", width) : clip(label, width);
2901
+ }
2902
+ function panelRowLines(row2, width, selected) {
2903
+ const marker = `${selected ? ">" : " "}${row2.current ? "*" : " "}`;
2904
+ const indent = " ".repeat(Math.min(row2.depth, Math.floor(width / 24))) + (row2.depth ? "\u2514 " : "");
2905
+ const identity = `${marker} ${indent}${row2.node.agent ?? row2.node.sessionId}`;
2906
+ const percent = `${(row2.freshness === "fresh" || row2.freshness === "stale") && row2.snapshot?.measurement?.confidence === "approximate" ? "~" : ""}${row2.utilizationPercent === undefined ? "?" : Math.round(row2.utilizationPercent)}%`;
2907
+ const status = width < 28 ? {
2908
+ running: "RUN",
2909
+ idle: "IDLE",
2910
+ completed: "DONE",
2911
+ failed: "FAIL",
2912
+ unknown: "?"
2913
+ }[row2.node.status] : panelStatuses[row2.node.status];
2914
+ const distance = thresholdDistance(row2, true);
2915
+ const pad = (value, size) => {
2916
+ const text2 = clip(value, size);
2917
+ return text2 + " ".repeat(Math.max(0, size - cells(text2)));
2918
+ };
2919
+ const lines = width >= 72 ? [tableLine(identity, status, percent, distance, width)] : [
2920
+ `${pad(identity, Math.max(1, width - status.length - 1))} ${status}`,
2921
+ clip(`${percent} context \xB7 ${distance}`, width)
2922
+ ];
2923
+ const exception = [
2924
+ freshnessLabel(row2),
2925
+ compactionLabel(row2),
2926
+ row2.node.interrupted ? "interrupted" : "",
2927
+ row2.hidden ? `+${row2.hidden} hidden \xB7 Space expands` : ""
2928
+ ].filter(Boolean).join(" \xB7 ");
2929
+ if (exception)
2930
+ lines.push(...wrap(exception, width));
2931
+ return lines.map((line) => clip(line, width));
2932
+ }
2933
+ var panelStatuses;
2934
+ var init_panelformat = __esm(() => {
2935
+ init_format();
2936
+ init_statusformat();
2937
+ init_statusformat();
2938
+ panelStatuses = {
2939
+ running: "RUNNING",
2940
+ idle: "IDLE",
2941
+ completed: "DONE",
2942
+ failed: "FAILED",
2943
+ unknown: "UNKNOWN"
2944
+ };
2945
+ });
2946
+
2123
2947
  // src/dashboard/terminalview.ts
2124
- function overviewRow(row2) {
2948
+ function diagnosticRow(row2) {
2125
2949
  const s = row2.snapshot;
2126
2950
  const valid2 = row2.freshness === "fresh" || row2.freshness === "stale";
2127
- return `${" ".repeat(Math.min(row2.depth, 12))}${row2.depth ? "\u2514 " : ""}${row2.node.agent ?? "unknown agent"} / ${row2.node.model ?? "unknown model"} [${row2.node.sessionId}] ${row2.node.status} | prompt ${quantity(valid2 ? s?.measurement?.tokens : undefined)}/${quantity(s?.capacityTokens)} | occupancy ${row2.utilizationPercent?.toFixed(1) ?? "?"}% | trigger ${quantity(s?.policy.triggerTokens)} headroom ${quantity(row2.triggerDeltaTokens)} | pressure ${row2.pressure} | compact ${s?.compaction.phase ?? "unavailable"} | performance ${s?.performance?.status ?? "unavailable"} | live TPS ${quantity(row2.performanceAvailable && row2.performanceFreshness !== "invalid" ? s?.performance?.liveTps : undefined)} | age ${row2.measurementAgeMs === undefined ? "unknown" : `${Math.floor(row2.measurementAgeMs / 1000)}s`} | inventory ${row2.inventoryStale ? "stale" : "fresh"}, live ${row2.liveStale ? "stale" : "fresh"}, usage ${row2.freshness}, performance ${row2.performanceFreshness}${s?.measurement?.confidence === "approximate" ? " estimated" : ""}${s?.performance?.tokenSource === "estimated" ? " estimated TPS" : ""}${s?.performance?.partial ? " partial" : ""}${row2.hidden ? ` | ${row2.hidden} hidden descendants` : ""}`;
2951
+ return `${" ".repeat(Math.min(row2.depth, 12))}${row2.depth ? "\u2514 " : ""}${row2.node.agent ?? "unknown agent"} / ${row2.node.model ?? "unknown model"} [${row2.node.sessionId}] ${row2.node.status} | ${row2.freshness === "stale" ? "Last measured" : "Context"} ${quantity(valid2 ? s?.measurement?.tokens : undefined)}/${quantity(s?.capacityTokens)} | occupancy ${row2.utilizationPercent?.toFixed(1) ?? "?"}% | threshold ${quantity(s?.policy.triggerTokens)} \xB7 ${thresholdDistance(row2)} | ${pressureLabel(row2)} | compact ${s?.compaction.phase ?? "unavailable"} | performance ${s?.performance?.status ?? "unavailable"} | live TPS ${quantity(row2.performanceAvailable && row2.performanceFreshness !== "invalid" ? s?.performance?.liveTps : undefined)} | age ${row2.measurementAgeMs === undefined ? "unknown" : `${Math.floor(row2.measurementAgeMs / 1000)}s`} | inventory ${row2.inventoryStale ? "stale" : "fresh"}, live ${row2.liveStale ? "stale" : "fresh"}, usage ${row2.freshness}, performance ${row2.performanceFreshness}${s?.measurement?.confidence === "approximate" ? " estimated" : ""}${s?.performance?.tokenSource === "estimated" ? " estimated TPS" : ""}${s?.performance?.partial ? " partial" : ""}${row2.hidden ? ` | ${row2.hidden} hidden descendants` : ""}`;
2952
+ }
2953
+ function pad(value, width) {
2954
+ const text2 = clip(value, width);
2955
+ return text2 + " ".repeat(Math.max(0, width - cells(text2)));
2956
+ }
2957
+ function overviewColumns(identity, state, context, threshold, width) {
2958
+ return `${pad(identity, width - 44)} ${pad(state, 7)} ${pad(context, 7)} ${pad(threshold, 27)}`;
2959
+ }
2960
+ function overviewHeading(width) {
2961
+ return width >= 72 ? [overviewColumns("Agent / model", "State", "Context", "Threshold", width)] : [];
2962
+ }
2963
+ function overviewLabels(rows) {
2964
+ const segmenter2 = new Intl.Segmenter(undefined, { granularity: "grapheme" });
2965
+ const suffixes = rows.map((row2) => [...segmenter2.segment(clip(row2.node.sessionId, 4096))].slice(-8).map((part) => part.segment).join(""));
2966
+ const counts = new Map;
2967
+ for (const suffix of suffixes)
2968
+ counts.set(suffix, (counts.get(suffix) ?? 0) + 1);
2969
+ return suffixes.map((suffix, index) => counts.get(suffix) === 1 ? suffix : `#${index + 1}`);
2970
+ }
2971
+ function overviewRowLines(row2, width, selected, label, position, maxLines = 3) {
2972
+ const valid2 = row2.freshness === "fresh" || row2.freshness === "stale";
2973
+ const percent = `${valid2 && row2.snapshot?.measurement?.confidence === "approximate" ? "~" : ""}${valid2 && row2.utilizationPercent !== undefined ? Math.round(row2.utilizationPercent) : "?"}%`;
2974
+ const context = `${row2.freshness === "stale" ? "Last measured " : ""}${percent}`;
2975
+ const distance = thresholdDistance(row2, true);
2976
+ const threshold = (distance === "At threshold \xB7 requires more" ? "At threshold; requires more" : distance).replace(" \xB7 Threshold reached", " \xB7 reached");
2977
+ const state = width < 28 ? {
2978
+ running: "RUN",
2979
+ idle: "IDLE",
2980
+ completed: "DONE",
2981
+ failed: "FAIL",
2982
+ unknown: "?"
2983
+ }[row2.node.status] : panelStatuses[row2.node.status];
2984
+ const identityWidth = width >= 72 ? width - 44 : Math.max(1, width - cells(state) - 1);
2985
+ const marker = selected ? "> " : " ";
2986
+ const tag = width >= 100 ? ` [${label}]` : ` #${position + 1}`;
2987
+ const indent = row2.depth && identityWidth >= 22 ? `${" ".repeat(Math.min(row2.depth, Math.max(0, Math.floor(identityWidth / 8) - 1)))}\u2514 ` : "";
2988
+ const name = `${row2.node.agent ?? "unknown agent"}${width >= 40 ? ` / ${row2.node.model ?? "unknown model"}` : ""}`;
2989
+ const identity = marker + indent + clip(name, Math.max(1, identityWidth - cells(marker + indent + tag))) + tag;
2990
+ const metric2 = `${context} context \xB7 ${threshold}`;
2991
+ const shortThreshold = threshold.replace("pressure unknown", "?").replace("At threshold; requires more", "At; needs more").replace(" \xB7 Near threshold", " near").replace(" \xB7 reached", " reached").replace("Threshold reached", "Reached");
2992
+ const shortMetric = `${context} \xB7 T ${shortThreshold}`;
2993
+ const pressure = row2.freshness !== "fresh" || row2.pressure === "unknown" ? "Threshold ?" : row2.pressure === "crossed" ? "Reached" : row2.triggerDeltaTokens === 0 ? "At; needs more" : row2.pressure === "near" ? "Near threshold" : "Below threshold";
2994
+ const metrics = [metric2, shortMetric, `${context} \xB7 ${pressure}`];
2995
+ const fittedMetric = metrics.find((line) => cells(line) <= width);
2996
+ const lines = width >= 72 ? [
2997
+ overviewColumns(identity, state, row2.freshness === "stale" ? "Last" : percent, threshold, width)
2998
+ ] : [`${pad(identity, identityWidth)} ${state}`, fittedMetric ?? context];
2999
+ const lifecycle = compactionLabel(row2);
3000
+ const exceptions = [
3001
+ ...width >= 72 && row2.freshness === "stale" ? [context] : [],
3002
+ ...width < 72 && !fittedMetric ? [pressure] : [],
3003
+ ...row2.node.status === "failed" ? ["Agent failed"] : [],
3004
+ ...row2.node.interrupted ? ["Interrupted"] : [],
3005
+ ...lifecycle ? [cells(lifecycle) <= width - 6 ? lifecycle : compactionLabel(row2, true)] : [],
3006
+ ...row2.freshness !== "fresh" ? [freshnessLabel(row2)] : [],
3007
+ ...row2.inventoryStale ? ["Inventory stale"] : [],
3008
+ ...row2.liveStale ? ["Live data stale"] : [],
3009
+ ...row2.hidden ? [`${row2.hidden} hidden descendants`] : []
3010
+ ].filter((value) => value !== undefined);
3011
+ if (exceptions.length) {
3012
+ let shown = 1;
3013
+ let text2 = exceptions[0] ?? "";
3014
+ while (shown < exceptions.length && cells(text2 + " \xB7 " + exceptions[shown]) <= width - 6) {
3015
+ text2 += " \xB7 " + exceptions[shown++];
3016
+ }
3017
+ const more = shown < exceptions.length ? ` +${exceptions.length - shown}` : "";
3018
+ lines.push(clip(text2, Math.max(0, width - cells(more))) + more);
3019
+ }
3020
+ return lines.slice(0, Math.max(1, maxLines)).map((line) => clip(line, width));
2128
3021
  }
2129
3022
  function pathValue(object2, path) {
2130
3023
  let value = object2;
@@ -2139,8 +3032,11 @@ function terminalDetail(row2) {
2139
3032
  if (!row2)
2140
3033
  return ["No selected session; choose one in overview."];
2141
3034
  return [
2142
- overviewRow(row2),
2143
3035
  `Session ID: ${row2.node.sessionId}`,
3036
+ ...detailLines(row2, 4096, false, row2.measurementAgeMs),
3037
+ ...row2.node.interrupted ? ["Agent execution interrupted"] : [],
3038
+ "Diagnostic codes and accounting:",
3039
+ diagnosticRow(row2),
2144
3040
  `Parent ID: ${row2.node.parentId ?? "unknown/unavailable"}`,
2145
3041
  "All snapshot fields (timestamps are Unix milliseconds):",
2146
3042
  ...snapshotFields.map((path) => `${path}: ${scalar(pathValue(row2.snapshot, path))}`),
@@ -2149,6 +3045,8 @@ function terminalDetail(row2) {
2149
3045
  }
2150
3046
  function historyObservations(view, metricIndex) {
2151
3047
  const metric2 = chartMetrics[metricIndex] ?? chartMetrics[0];
3048
+ if (!view.history.result?.enabled)
3049
+ return [];
2152
3050
  return view.history.result?.data.series.flatMap((series) => metric2.source === "points" ? series.points.map((point) => ({
2153
3051
  time: point.time,
2154
3052
  value: point[metric2.key],
@@ -2177,14 +3075,15 @@ function terminalHistory(view, width, metricIndex, sample) {
2177
3075
  ];
2178
3076
  const selected = observations[Math.min(sample, Math.max(0, observations.length - 1))];
2179
3077
  return [
2180
- `History: ${h.status}${h.stale ? " \xB7 stale cached results" : ""} \xB7 ${h.windowMs / 60000} minutes \xB7 ${metric2.label} (${metric2.unit})`,
3078
+ `History: ${h.status} \xB7 ${h.windowMs / 60000} minutes \xB7 ${metric2.label} (${metric2.unit})`,
3079
+ ...historyStatusLines(h),
2181
3080
  "Left/Right metric \xB7 w window \xB7 [/] exact sample/request \xB7 Up/Down scroll",
2182
3081
  "Retention bounded to seven days; maximum 500 points and 500 requests per session.",
2183
3082
  ...h.notices,
2184
- ...h.summary ? [
3083
+ ...h.result?.enabled && h.summary ? [
2185
3084
  `Matched requests ${h.summary.matchedRequests}/${h.summary.observedRequests}; output ${scalar(h.summary.outputTokens)}; duration ms ${scalar(h.summary.durationMs)}; average model TPS ${scalar(h.summary.averageModelTps)}${h.summary.partial ? " \xB7 partial" : ""}`
2186
3085
  ] : [],
2187
- ...h.from === undefined || h.to === undefined ? [] : [
3086
+ ...!h.result?.enabled || h.from === undefined || h.to === undefined ? [] : [
2188
3087
  `${new Date(h.from).toISOString()} \u2192 ${new Date(h.to).toISOString()}`,
2189
3088
  ...metricPlot(observations.map((p) => ({
2190
3089
  time: p.time,
@@ -2196,25 +3095,45 @@ function terminalHistory(view, width, metricIndex, sample) {
2196
3095
  `Exact ${metric2.source === "points" ? "sample" : "request"} ${Math.min(sample + 1, observations.length)}/${observations.length}:`,
2197
3096
  ...Object.entries(selected.details).map(([key, value]) => `${key}: ${scalar(value)}`),
2198
3097
  `Selected ${metric2.label}: ${scalar(selected.value)} ${metric2.unit}`
2199
- ] : [
3098
+ ] : h.status === "ready" && h.result?.enabled ? [
2200
3099
  "No retained measurements for this metric; values unknown/unavailable."
2201
- ]
3100
+ ] : []
2202
3101
  ];
2203
3102
  }
2204
3103
  function wrapped(lines, width) {
2205
- return lines.flatMap((line) => line ? wrap(line, Math.max(1, width)) : [""]);
3104
+ const available = Math.max(1, width);
3105
+ return lines.flatMap((line) => {
3106
+ const clean = clip(line, Math.max(available, cells(line)));
3107
+ return cells(clean) <= available ? [clean] : wrap(clean, available);
3108
+ });
2206
3109
  }
2207
- function headerLines(view, width) {
3110
+ function headerLines(view, width, height = 24) {
3111
+ const compact = width < 28;
3112
+ const small = height < 18;
3113
+ if (width < 16 || height < 8)
3114
+ return [
3115
+ clip(connectionLabel(view.connection, true), width),
3116
+ clip(panelStatus(view.health), width),
3117
+ clip(`Location: ${view.location}`, width)
3118
+ ];
2208
3119
  return [
2209
- clip(`Context Guard \xB7 ${view.screen} \xB7 ${view.connection} \xB7 enforcement ${view.health?.enforcement ?? "unknown"}`, width),
2210
- clip(`Known ${view.inventory.total} \xB7 monitored ${view.inventory.monitored} \xB7 active ${view.inventory.active} \xB7 inventory ${view.inventory.stale ? "stale" : "fresh"}${view.inventory.complete ? "" : " / partial"}`, width),
2211
- clip(`Location: ${view.location}`, width)
3120
+ clip(`${connectionLabel(view.connection, compact)} \xB7 ${compact ? width >= 20 ? "Context Guard" : "Guard" : `Context Guard \xB7 ${view.screen}`}`, width),
3121
+ ...monitorStatusLines(view.connection, view.health, view.rows.some((row2) => row2.snapshot?.measurement !== undefined), compact).flatMap((line) => wrap(line, width)),
3122
+ ...small ? [
3123
+ clip(`Selected: ${view.selected?.node.agent ?? view.selectedSessionId ?? "none"}`, width)
3124
+ ] : [
3125
+ clip(`Known ${view.inventory.total} \xB7 monitored ${view.inventory.monitored} \xB7 active ${view.inventory.active} \xB7 inventory ${view.inventory.stale ? "stale" : "fresh"}${view.inventory.complete ? "" : " / partial"}`, width),
3126
+ clip(`Location: ${view.location}`, width)
3127
+ ]
2212
3128
  ];
2213
3129
  }
2214
3130
  var snapshotFields, scalar = (value) => value === undefined || value === null ? "unknown/unavailable" : typeof value === "number" && !Number.isFinite(value) ? "invalid" : String(value), terminalHelp;
2215
3131
  var init_terminalview = __esm(() => {
3132
+ init_historyclient();
2216
3133
  init_chartformat();
2217
3134
  init_format();
3135
+ init_statusformat();
3136
+ init_panelformat();
2218
3137
  snapshotFields = [
2219
3138
  "sessionId",
2220
3139
  "parentId",
@@ -2282,8 +3201,14 @@ var init_terminalview = __esm(() => {
2282
3201
  ];
2283
3202
  terminalHelp = [
2284
3203
  "Context Guard standalone dashboard \u2014 keyboard help",
2285
- "1 overview \xB7 2 detail \xB7 3 history \xB7 Enter detail",
3204
+ "1 overview \xB7 2 detail \xB7 3 history \xB7 4 models \xB7 Enter detail/chart",
3205
+ "Models: Up/Down scroll tall rows, then select \xB7 Enter aggregate chart \xB7 Esc model list \xB7 s sort",
3206
+ "Models charts: Left/Right metric \xB7 w window \xB7 [/] bucket \xB7 d request details",
3207
+ "Models list/request details: n/p pages \xB7 f exact agent filter \xB7 r refresh",
2286
3208
  "Up/Down or j/k: select session in overview; scroll other screens",
3209
+ "Overview: compact columns/cards; Enter or 2 for full identity and diagnostics",
3210
+ "Overview PageUp/PageDown selects by visible rows; #N identifies a row in narrow layouts",
3211
+ "Overview T means threshold; +N means more diagnostics in Details",
2287
3212
  "PageUp/PageDown: move by a page \xB7 Home/End: first/last",
2288
3213
  "Left/Right: history metric (six metrics) \xB7 w: cycle 5m/1h/24h/7d",
2289
3214
  "[ / ]: previous/next exact history sample or request",
@@ -2312,11 +3237,18 @@ class TerminalShell {
2312
3237
  scroll = 0;
2313
3238
  metric = 0;
2314
3239
  sample = 0;
3240
+ modelDetail = false;
3241
+ modelRequests = false;
3242
+ modelFocus = 0;
3243
+ modelFocusId;
3244
+ modelViewport;
2315
3245
  help = false;
2316
3246
  editor;
2317
3247
  input = "";
2318
3248
  notice = "";
2319
3249
  rowOffset = 0;
3250
+ overviewScope = "";
3251
+ overviewPage = 1;
2320
3252
  historyIdentity = "";
2321
3253
  constructor(controller, quit) {
2322
3254
  this.controller = controller;
@@ -2357,7 +3289,7 @@ class TerminalShell {
2357
3289
  this.task(this.controller.selectLocation(this.input));
2358
3290
  } else
2359
3291
  this.controller.setFilters({ [editor]: this.input });
2360
- this.editor = editor === "agent" ? "model" : undefined;
3292
+ this.editor = editor === "agent" && view.screen !== "models" ? "model" : undefined;
2361
3293
  this.input = this.editor === "model" ? view.filters.model : "";
2362
3294
  this.scroll = 0;
2363
3295
  this.notice = "";
@@ -2377,6 +3309,11 @@ class TerminalShell {
2377
3309
  if (name === "escape") {
2378
3310
  this.help = false;
2379
3311
  this.scroll = 0;
3312
+ if (view.screen === "models" && this.modelDetail) {
3313
+ this.modelDetail = false;
3314
+ this.modelRequests = false;
3315
+ return;
3316
+ }
2380
3317
  this.controller.setScreen("overview");
2381
3318
  return;
2382
3319
  }
@@ -2399,6 +3336,81 @@ class TerminalShell {
2399
3336
  this.task(this.controller.refresh());
2400
3337
  return;
2401
3338
  }
3339
+ if (name === "4") {
3340
+ this.help = false;
3341
+ this.scroll = 0;
3342
+ this.modelDetail = false;
3343
+ this.modelRequests = false;
3344
+ this.controller.setScreen("models");
3345
+ return;
3346
+ }
3347
+ if (view.screen === "models" && !this.help) {
3348
+ if (name === "enter" || name === "return") {
3349
+ const model = view.models.result?.models[this.modelFocus];
3350
+ if (model) {
3351
+ this.controller.models.select(model.model ?? "");
3352
+ this.modelDetail = true;
3353
+ this.modelRequests = false;
3354
+ this.sample = 0;
3355
+ this.scroll = 0;
3356
+ }
3357
+ return;
3358
+ }
3359
+ if (name === "w") {
3360
+ this.controller.setWindow(historyWindows[(historyWindows.indexOf(view.models.windowMs) + 1) % historyWindows.length] ?? historyWindows[0]);
3361
+ this.sample = 0;
3362
+ this.scroll = 0;
3363
+ return;
3364
+ }
3365
+ if (name === "s" && !this.modelDetail) {
3366
+ this.controller.models.sort();
3367
+ this.scroll = 0;
3368
+ return;
3369
+ }
3370
+ if (name === "n" || name === "p") {
3371
+ if (!this.modelDetail || this.modelRequests) {
3372
+ this.controller.models.page(this.modelDetail ? "requests" : "models", name === "n" ? 1 : -1);
3373
+ if (!this.modelDetail) {
3374
+ this.modelFocus = 0;
3375
+ this.modelFocusId = undefined;
3376
+ }
3377
+ this.scroll = 0;
3378
+ }
3379
+ return;
3380
+ }
3381
+ if (this.modelDetail) {
3382
+ if (name === "d") {
3383
+ this.modelRequests = !this.modelRequests;
3384
+ this.scroll = 0;
3385
+ return;
3386
+ }
3387
+ if (name === "left" || name === "right") {
3388
+ this.metric = (this.metric + modelMetrics.length + (name === "left" ? -1 : 1)) % modelMetrics.length;
3389
+ this.sample = 0;
3390
+ this.scroll = 0;
3391
+ return;
3392
+ }
3393
+ if (name === "[" || name === "]") {
3394
+ this.sample = Math.max(0, Math.min((view.models.result?.buckets.length ?? 1) - 1, this.sample + (name === "[" ? -1 : 1)));
3395
+ return;
3396
+ }
3397
+ } else if (["up", "down", "j", "k", "home", "end", "pageup", "pagedown"].includes(name)) {
3398
+ const models = view.models.result?.models ?? [];
3399
+ const delta2 = name === "down" || name === "j" ? 1 : name === "up" || name === "k" ? -1 : name === "pagedown" ? page : -page;
3400
+ const viewport = this.modelViewport;
3401
+ if (viewport?.focus === this.modelFocus && viewport.end - viewport.start > viewport.height && name !== "home" && name !== "end" && (delta2 > 0 ? this.scroll + viewport.height < viewport.end : this.scroll > viewport.start)) {
3402
+ this.scroll = Math.max(viewport.start, Math.min(viewport.end - viewport.height, this.scroll + delta2));
3403
+ return;
3404
+ }
3405
+ if (name === "home")
3406
+ this.scroll = 0;
3407
+ if (name === "end")
3408
+ this.scroll = Number.MAX_SAFE_INTEGER;
3409
+ this.modelFocus = Math.max(0, Math.min(models.length - 1, name === "home" ? 0 : name === "end" ? models.length - 1 : this.modelFocus + delta2));
3410
+ this.modelFocusId = models[this.modelFocus]?.model ?? "";
3411
+ return;
3412
+ }
3413
+ }
2402
3414
  if (["1", "2", "3", "return", "enter"].includes(name)) {
2403
3415
  this.help = false;
2404
3416
  this.scroll = 0;
@@ -2425,7 +3437,7 @@ class TerminalShell {
2425
3437
  const delta = name === "down" || name === "j" ? 1 : name === "up" || name === "k" ? -1 : name === "pagedown" ? page : name === "pageup" ? -page : 0;
2426
3438
  if (view.screen === "overview" && !this.help) {
2427
3439
  const index = view.rows.findIndex((row3) => row3.node.sessionId === view.selectedSessionId);
2428
- const next = name === "home" ? 0 : name === "end" ? view.rows.length - 1 : Math.max(0, Math.min(view.rows.length - 1, index + delta));
3440
+ const next = name === "home" ? 0 : name === "end" ? view.rows.length - 1 : Math.max(0, Math.min(view.rows.length - 1, index + (name === "pagedown" ? this.overviewPage : name === "pageup" ? -this.overviewPage : delta)));
2429
3441
  const row2 = view.rows[next];
2430
3442
  if (row2 && (delta || name === "home" || name === "end"))
2431
3443
  this.controller.selectSession(row2.node.sessionId);
@@ -2435,27 +3447,47 @@ class TerminalShell {
2435
3447
  frame(width, height) {
2436
3448
  width = Math.max(1, width);
2437
3449
  height = Math.max(1, height);
3450
+ this.controller.models.setWidth(width);
2438
3451
  const view = this.controller.getView();
2439
- const identity = `${view.location}\0${view.selectedSessionId}\0${view.history.windowMs}`;
3452
+ if (view.screen === "models") {
3453
+ const models = view.models.result?.models ?? [];
3454
+ const identity2 = this.modelFocusId ?? view.models.result?.selected?.model ?? "";
3455
+ const focused = models.findIndex((m) => (m.model ?? "") === identity2);
3456
+ this.modelFocus = Math.max(0, focused < 0 ? Math.min(this.modelFocus, models.length - 1) : focused);
3457
+ if (view.models.status === "ready" && models.length)
3458
+ this.modelFocusId = models[this.modelFocus]?.model ?? "";
3459
+ }
3460
+ const identity = view.screen === "models" ? JSON.stringify([
3461
+ view.location,
3462
+ view.models.windowMs,
3463
+ view.filters.agent,
3464
+ view.models.result?.selected?.model
3465
+ ]) : `${view.location}\0${view.selectedSessionId}\0${view.history.windowMs}`;
2440
3466
  if (identity !== this.historyIdentity) {
2441
3467
  this.sample = 0;
2442
3468
  this.scroll = 0;
2443
3469
  this.historyIdentity = identity;
2444
3470
  }
2445
- const footer = this.editor ? `${this.editor}> ${this.input} [Enter apply; Esc cancel${this.editor === "location" ? "; Tab remembered" : ""}]` : "? help q quit \xB7 1 overview 2 detail 3 history \xB7 / search f filters l location r refresh";
3471
+ const footer = this.editor ? `${this.editor}> ${this.input} [Enter apply; Esc cancel${this.editor === "location" ? "; Tab remembered" : ""}]` : view.screen === "overview" && !this.help ? width < 40 ? width >= 22 ? "2 detail ? help q quit" : "2 detail ? q quit" : width < 72 ? "2 detail \xB7 q quit \xB7 ? help \xB7 \u2191\u2193 select" : "Enter detail \xB7 q quit \xB7 ? help \xB7 \u2191\u2193 select \xB7 3 history 4 models / search f filters l location r refresh" : width < 40 ? "? help q quit" : "? help q quit \xB7 1 overview 2 detail 3 history 4 models \xB7 / search f filters l location r refresh";
2446
3472
  if (height < 5)
2447
3473
  return [
2448
3474
  clip("Context Guard \xB7 ? help \xB7 q quit", width),
2449
3475
  ...wrapped([footer], width)
2450
3476
  ].slice(0, height).join("\n");
2451
- const header = headerLines(view, width);
2452
- const available = height - header.length - 1;
3477
+ const header = headerLines(view, width, height);
3478
+ if (view.screen === "overview" && !this.help) {
3479
+ while (header.length > height - 2 && /^(Known |Location: |Selected: )/.test(header.at(-1) ?? ""))
3480
+ header.pop();
3481
+ header.splice(Math.max(0, height - 2));
3482
+ }
3483
+ const available = Math.max(0, height - header.length - 1);
2453
3484
  const notices = [
3485
+ ...view.screen === "overview" && view.selectedSessionId && !view.rows.some((row2) => row2.node.sessionId === view.selectedSessionId) ? ["Selection hidden by filters \xB7 \u2191\u2193 selects a visible session"] : [],
2454
3486
  ...view.notices,
2455
3487
  ...view.inventory.notices,
2456
- ...view.health?.reasons ?? [],
3488
+ ...view.screen === "detail" ? view.health?.reasons.map((reason) => `Health reason code: ${reason}`) ?? [] : [],
2457
3489
  ...this.notice ? [this.notice] : [],
2458
- ...Object.values(view.filters).some(Boolean) ? [
3490
+ ...view.screen !== "models" && Object.values(view.filters).some(Boolean) ? [
2459
3491
  `Filters: search=${view.filters.search} agent=${view.filters.agent} model=${view.filters.model}`
2460
3492
  ] : []
2461
3493
  ];
@@ -2463,23 +3495,38 @@ class TerminalShell {
2463
3495
  if (this.help)
2464
3496
  body = wrapped(terminalHelp, width);
2465
3497
  else if (view.screen === "overview") {
2466
- const rows = view.rows.map((row2) => wrapped([
2467
- `${row2.node.sessionId === view.selectedSessionId ? "> " : " "}${overviewRow(row2)}`
2468
- ], width));
3498
+ const scope = JSON.stringify([view.location, view.filters]);
3499
+ if (scope !== this.overviewScope) {
3500
+ this.rowOffset = 0;
3501
+ this.overviewScope = scope;
3502
+ }
3503
+ const heading = available >= 4 ? overviewHeading(width) : [];
3504
+ const noticeBudget = Math.min(notices.length, Math.floor((available - heading.length) / 4));
3505
+ const status = notices.slice(0, noticeBudget).map((notice, index) => clip(index === noticeBudget - 1 && notices.length > noticeBudget ? `${notices.length} notices \xB7 see Details` : notice, width));
3506
+ const budget = Math.max(1, available - heading.length - status.length);
3507
+ const labels = overviewLabels(view.rows);
3508
+ const rows = view.rows.map((row2, index) => overviewRowLines(row2, width, row2.node.sessionId === view.selectedSessionId, labels[index] ?? `#${index + 1}`, index, budget));
2469
3509
  const selected = view.rows.findIndex((row2) => row2.node.sessionId === view.selectedSessionId);
2470
3510
  const start = rows.slice(0, Math.max(0, selected)).reduce((n, row2) => n + row2.length, 0);
2471
3511
  const end = start + (rows[selected]?.length ?? 0);
2472
- const status = wrapped(notices, width);
2473
- const budget = Math.max(1, available - Math.min(status.length, Math.floor(available / 3)));
2474
3512
  if (start < this.rowOffset)
2475
3513
  this.rowOffset = start;
2476
3514
  if (end > this.rowOffset + budget)
2477
3515
  this.rowOffset = Math.max(start, end - budget);
2478
3516
  const flat = rows.flat();
2479
3517
  this.rowOffset = Math.max(0, Math.min(this.rowOffset, Math.max(0, flat.length - budget)));
3518
+ let cursor = 0;
3519
+ this.overviewPage = Math.max(1, rows.filter((row2) => {
3520
+ const start2 = cursor;
3521
+ cursor += row2.length;
3522
+ return start2 >= this.rowOffset && cursor <= this.rowOffset + budget;
3523
+ }).length);
2480
3524
  body = [
2481
- ...status.slice(0, available - budget),
2482
- ...flat.length ? flat.slice(this.rowOffset, this.rowOffset + budget) : ["No matching sessions; / search, f filters, r refresh."]
3525
+ ...status,
3526
+ ...heading,
3527
+ ...flat.length ? flat.slice(this.rowOffset, this.rowOffset + budget) : [
3528
+ clip("No matching sessions; / search, f filters, r refresh.", width)
3529
+ ]
2483
3530
  ];
2484
3531
  return [
2485
3532
  ...header,
@@ -2490,8 +3537,28 @@ class TerminalShell {
2490
3537
  } else
2491
3538
  body = wrapped([
2492
3539
  ...notices,
2493
- ...view.screen === "detail" ? terminalDetail(view.selected) : terminalHistory(view, width, this.metric, this.sample)
3540
+ ...view.screen === "detail" ? terminalDetail(view.selected) : view.screen === "models" ? terminalModels(view, width, this.modelDetail, this.metric, this.sample, this.modelFocus, this.modelRequests) : terminalHistory(view, width, this.metric, this.sample)
2494
3541
  ], width);
3542
+ this.modelViewport = undefined;
3543
+ if (view.screen === "models" && !this.modelDetail && !this.help && view.models.result?.models.length) {
3544
+ const heights = (view.models.result?.models ?? []).map((model, index) => wrapped(terminalModelRow(model, index === this.modelFocus), width).length);
3545
+ const start = body.length - heights.slice(this.modelFocus).reduce((a, b) => a + b, 0);
3546
+ const end = start + (heights[this.modelFocus] ?? 0);
3547
+ this.modelViewport = {
3548
+ focus: this.modelFocus,
3549
+ start,
3550
+ end,
3551
+ height: available
3552
+ };
3553
+ if (end - start > available) {
3554
+ this.scroll = Math.max(start, Math.min(this.scroll, end - available));
3555
+ } else {
3556
+ if (start < this.scroll)
3557
+ this.scroll = start;
3558
+ if (end > this.scroll + available)
3559
+ this.scroll = end - available;
3560
+ }
3561
+ }
2495
3562
  this.scroll = Math.max(0, Math.min(this.scroll, Math.max(0, body.length - available)));
2496
3563
  return [
2497
3564
  ...header,
@@ -2621,6 +3688,7 @@ var init_terminal = __esm(() => {
2621
3688
  init_connection();
2622
3689
  init_command();
2623
3690
  init_view();
3691
+ init_modelview();
2624
3692
  init_format();
2625
3693
  init_terminalview();
2626
3694
  });
@@ -2668,7 +3736,7 @@ function reconcile(a, b) {
2668
3736
  partial: a.partial || b.partial
2669
3737
  };
2670
3738
  }
2671
- function aggregateModels(records, options) {
3739
+ function analyzeModelRequests(records, options) {
2672
3740
  const maxRequests = options.maxRequests ?? 1e5;
2673
3741
  if (!Number.isSafeInteger(maxRequests) || maxRequests < 1 || maxRequests > 1e6)
2674
3742
  throw new Error("maxRequests must be an integer from 1 to 1000000");
@@ -2729,7 +3797,7 @@ function aggregateModels(records, options) {
2729
3797
  requests.set(key, { fact, conflict: false });
2730
3798
  }
2731
3799
  }
2732
- const groups = new Map;
3800
+ const accepted = [];
2733
3801
  for (const { fact: r, conflict } of requests.values()) {
2734
3802
  if (conflict) {
2735
3803
  coverage.conflictingRequests++;
@@ -2746,6 +3814,22 @@ function aggregateModels(records, options) {
2746
3814
  }
2747
3815
  if (options.from !== undefined && r.completedAt < options.from || options.to !== undefined && r.completedAt >= options.to || options.model !== undefined && r.model !== options.model || options.agent !== undefined && r.agent !== options.agent)
2748
3816
  continue;
3817
+ accepted.push({ ...r, completedAt: r.completedAt });
3818
+ }
3819
+ const models = summarizeModelRequests(accepted);
3820
+ coverage.partial = coverage.unscannedRecords > 0 || coverage.conflictingRequests > 0 || coverage.missingIdentityRecords > 0 || coverage.incompleteRequests > 0 || coverage.invalidTimingRequests > 0 || coverage.gapRecords > 0 || models.some((s) => s.partialRequests > 0 || s.legacyAttributionRequests > 0 || s.excludedTpsRequests > 0 || s.excludedTtftRequests > 0 || !s.model);
3821
+ return {
3822
+ schemaVersion: 1,
3823
+ location: options.location,
3824
+ models,
3825
+ coverage,
3826
+ requests: accepted
3827
+ };
3828
+ }
3829
+ function summarizeModelRequests(requests) {
3830
+ const groups = new Map;
3831
+ for (const r of requests) {
3832
+ const duration = r.completedAt - r.startedAt;
2749
3833
  let group = groups.get(r.model);
2750
3834
  if (!group) {
2751
3835
  group = {
@@ -2800,8 +3884,11 @@ function aggregateModels(records, options) {
2800
3884
  }
2801
3885
  return s;
2802
3886
  }).sort((a, b) => (a.model ?? "").localeCompare(b.model ?? ""));
2803
- coverage.partial = coverage.unscannedRecords > 0 || coverage.conflictingRequests > 0 || coverage.missingIdentityRecords > 0 || coverage.incompleteRequests > 0 || coverage.invalidTimingRequests > 0 || coverage.gapRecords > 0 || models.some((s) => s.partialRequests > 0 || s.legacyAttributionRequests > 0 || s.excludedTpsRequests > 0 || s.excludedTtftRequests > 0 || !s.model);
2804
- return { schemaVersion: 1, location: options.location, models, coverage };
3887
+ return models;
3888
+ }
3889
+ function aggregateModels(records, options) {
3890
+ const { requests: _requests, ...result } = analyzeModelRequests(records, options);
3891
+ return result;
2805
3892
  }
2806
3893
  var nonnegative2 = (n) => n !== undefined && Number.isFinite(n) && n >= 0;
2807
3894