@cesarus777/opencode-cgp 0.4.1 → 0.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -474,15 +474,109 @@ async function segments(directory) {
474
474
  return (await Promise.all(names.filter((n) => filename.test(n)).map(async (name) => ({
475
475
  name,
476
476
  time: Number(filename.exec(name)?.[1]),
477
- bytes: (await stat(join(directory, name))).size
477
+ ...fileIdentity(await stat(join(directory, name)))
478
478
  })))).sort((a, b) => a.time - b.time || a.name.localeCompare(b.name));
479
479
  }
480
+ function fileIdentity(info) {
481
+ return {
482
+ bytes: info.size,
483
+ dev: info.dev,
484
+ ino: info.ino,
485
+ mtimeMs: info.mtimeMs,
486
+ ctimeMs: info.ctimeMs
487
+ };
488
+ }
489
+ function scanScheduler(signal, options = {}) {
490
+ const now = options.now ?? (() => performance.now());
491
+ const pause = options.yield ?? (() => new Promise((done) => setTimeout(done, 0)));
492
+ let started = now();
493
+ return () => {
494
+ signal?.throwIfAborted();
495
+ if (now() - started >= 4)
496
+ return pause().then(() => {
497
+ signal?.throwIfAborted();
498
+ started = now();
499
+ });
500
+ };
501
+ }
502
+ function matchesHistory(value, filter) {
503
+ return !(filter.sessionId !== undefined && value.sessionId !== filter.sessionId && value.sessionId !== "__history__" || value.type !== "gap" && filter.agent !== undefined && (value.performance?.requestAgent ?? value.agent) !== filter.agent || value.type !== "gap" && filter.model !== undefined && (value.performance?.requestModel ?? value.model) !== filter.model || filter.from !== undefined && value.time < filter.from || filter.to !== undefined && value.time > filter.to);
504
+ }
505
+ async function scanHistorySegment(handle, size, filter = {}, options = {}, start = 0) {
506
+ const records = [];
507
+ let malformedRecords = 0, position = start, boundary = start;
508
+ let pending = Buffer.alloc(0);
509
+ const checkpoint = scanScheduler(filter.signal, options);
510
+ function parse(line) {
511
+ if (!line.length)
512
+ return;
513
+ options.onParse?.();
514
+ let value;
515
+ try {
516
+ value = JSON.parse(line.toString("utf8"));
517
+ } catch {
518
+ malformedRecords++;
519
+ return;
520
+ }
521
+ if (!isHistoryRecord(value)) {
522
+ malformedRecords++;
523
+ return;
524
+ }
525
+ if (matchesHistory(value, filter))
526
+ records.push(value);
527
+ }
528
+ while (position < size) {
529
+ {
530
+ const waiting = checkpoint();
531
+ if (waiting)
532
+ await waiting;
533
+ }
534
+ const buffer = Buffer.alloc(Math.min(options.chunkBytes ?? 65536, size - position));
535
+ const { bytesRead } = await handle.read(buffer, 0, buffer.length, position);
536
+ if (!bytesRead)
537
+ throw new Error("Archive changed during read");
538
+ options.onRead?.(bytesRead);
539
+ position += bytesRead;
540
+ const chunk = pending.length ? Buffer.concat([pending, buffer.subarray(0, bytesRead)]) : buffer.subarray(0, bytesRead);
541
+ let offset = 0;
542
+ for (;; ) {
543
+ const end = chunk.indexOf(10, offset);
544
+ if (end < 0)
545
+ break;
546
+ {
547
+ const waiting = checkpoint();
548
+ if (waiting)
549
+ await waiting;
550
+ }
551
+ parse(chunk.subarray(offset, end));
552
+ offset = end + 1;
553
+ }
554
+ pending = Buffer.from(chunk.subarray(offset));
555
+ boundary = position - pending.length;
556
+ }
557
+ const completeRecords = records.length, completeMalformed = malformedRecords;
558
+ {
559
+ const waiting = checkpoint();
560
+ if (waiting)
561
+ await waiting;
562
+ }
563
+ parse(pending);
564
+ filter.signal?.throwIfAborted();
565
+ return {
566
+ records,
567
+ malformedRecords,
568
+ boundary,
569
+ completeRecords,
570
+ completeMalformed
571
+ };
572
+ }
480
573
 
481
574
  class PerformanceHistory {
482
575
  path;
483
576
  config;
484
577
  now;
485
578
  report;
579
+ changed;
486
580
  pending = [];
487
581
  working;
488
582
  active;
@@ -492,11 +586,13 @@ class PerformanceHistory {
492
586
  dropped = 0;
493
587
  droppedTotal = 0;
494
588
  constructor(path, config, now = Date.now, report = () => {
589
+ }, changed = () => {
495
590
  }) {
496
591
  this.path = path;
497
592
  this.config = config;
498
593
  this.now = now;
499
594
  this.report = report;
595
+ this.changed = changed;
500
596
  }
501
597
  emit(record2) {
502
598
  if (!this.config.enabled || this.disabled)
@@ -545,6 +641,7 @@ class PerformanceHistory {
545
641
  if (file.time >= this.now() - this.config.retentionDays * 86400000 && total + incoming <= this.config.maxBytes)
546
642
  break;
547
643
  await unlink(join(this.path, file.name));
644
+ this.changed(file.name);
548
645
  if (file.name === this.active) {
549
646
  this.active = undefined;
550
647
  this.bytes = 0;
@@ -595,6 +692,7 @@ class PerformanceHistory {
595
692
  const file = await open(join(this.path, this.active), "a", 384);
596
693
  try {
597
694
  await file.writeFile(line);
695
+ this.changed(this.active);
598
696
  } finally {
599
697
  await file.close();
600
698
  }
@@ -649,7 +747,8 @@ function scalarPerformance(p) {
649
747
  }))
650
748
  };
651
749
  }
652
- async function readPerformanceHistory(directory, filter = {}) {
750
+ async function readPerformanceHistory(directory, filter = {}, options = {}) {
751
+ filter.signal?.throwIfAborted();
653
752
  const records = [];
654
753
  const files = await segments(directory);
655
754
  filter.signal?.throwIfAborted();
@@ -667,36 +766,16 @@ async function readPerformanceHistory(directory, filter = {}) {
667
766
  for (const file of selected) {
668
767
  filter.signal?.throwIfAborted();
669
768
  const handle = await open(join(directory, file.name), "r");
670
- let source;
671
769
  try {
672
- const buffer = Buffer.alloc(file.bytes);
673
- const { bytesRead } = await handle.read(buffer, 0, buffer.length, 0);
674
- source = buffer.subarray(0, bytesRead).toString("utf8");
770
+ const result = await scanHistorySegment(handle, file.bytes, filter, options);
771
+ for (const record2 of result.records)
772
+ records.push(record2);
773
+ malformedRecords += result.malformedRecords;
675
774
  } finally {
676
775
  await handle.close();
677
776
  }
678
- let scannedLines = 0;
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
- }
684
- if (!line)
685
- continue;
686
- try {
687
- const value = JSON.parse(line);
688
- if (!isHistoryRecord(value)) {
689
- malformedRecords++;
690
- continue;
691
- }
692
- if (filter.sessionId !== undefined && value.sessionId !== filter.sessionId && value.sessionId !== "__history__" || value.type !== "gap" && filter.agent !== undefined && (value.performance?.requestAgent ?? value.agent) !== filter.agent || value.type !== "gap" && filter.model !== undefined && (value.performance?.requestModel ?? value.model) !== filter.model || filter.from !== undefined && value.time < filter.from || filter.to !== undefined && value.time > filter.to)
693
- continue;
694
- records.push(value);
695
- } catch {
696
- malformedRecords++;
697
- }
698
- }
699
777
  }
778
+ filter.signal?.throwIfAborted();
700
779
  const metadata = {
701
780
  schema: 1,
702
781
  ...files[0] ? { retainedFrom: files[0].time } : {},
@@ -714,7 +793,13 @@ function isHistoryRecord(value) {
714
793
  if (!isRecord(value))
715
794
  return false;
716
795
  const r = value;
717
- const allowed = new Set([
796
+ return Object.keys(r).every((k) => historyFields.has(k)) && r.schema === 1 && typeof r.recordId === "string" && typeof r.time === "number" && Number.isSafeInteger(r.time) && r.time >= 0 && typeof r.sessionId === "string" && typeof r.type === "string" && ["sample", "request", "lifecycle", "gap", "terminal"].includes(r.type) && ["parentId", "agent", "model", "event"].every((k) => r[k] === undefined || typeof r[k] === "string") && (r.performance === undefined || isPerformanceSnapshot(r.performance)) && (r.droppedRecords === undefined || typeof r.droppedRecords === "number" && Number.isSafeInteger(r.droppedRecords) && r.droppedRecords >= 0);
797
+ }
798
+ var filename, historyFields;
799
+ var init_history = __esm(() => {
800
+ init_rpc();
801
+ filename = /^([0-9]+)-[a-f0-9-]+\.jsonl$/;
802
+ historyFields = new Set([
718
803
  "schema",
719
804
  "recordId",
720
805
  "time",
@@ -727,12 +812,6 @@ function isHistoryRecord(value) {
727
812
  "performance",
728
813
  "droppedRecords"
729
814
  ]);
730
- return Object.keys(r).every((k) => allowed.has(k)) && r.schema === 1 && typeof r.recordId === "string" && typeof r.time === "number" && Number.isSafeInteger(r.time) && r.time >= 0 && typeof r.sessionId === "string" && typeof r.type === "string" && ["sample", "request", "lifecycle", "gap", "terminal"].includes(r.type) && ["parentId", "agent", "model", "event"].every((k) => r[k] === undefined || typeof r[k] === "string") && (r.performance === undefined || isPerformanceSnapshot(r.performance)) && (r.droppedRecords === undefined || typeof r.droppedRecords === "number" && Number.isSafeInteger(r.droppedRecords) && r.droppedRecords >= 0);
731
- }
732
- var filename;
733
- var init_history = __esm(() => {
734
- init_rpc();
735
- filename = /^([0-9]+)-[a-f0-9-]+\.jsonl$/;
736
815
  });
737
816
 
738
817
  // src/dashboard/command.ts
@@ -1261,7 +1340,7 @@ function validateInventoryLimits(limits) {
1261
1340
  if (!Number.isInteger(limits.maxNodes) || limits.maxNodes < 1 || limits.maxNodes > 2048 || !Number.isInteger(limits.maxPages) || limits.maxPages < 1 || limits.maxPages > 100 || !Number.isInteger(limits.maxDepth) || limits.maxDepth < 0 || limits.maxDepth > 64)
1262
1341
  throw new Error("Invalid dashboard inventory limits");
1263
1342
  }
1264
- async function discoverInventory(port, signal, limits = inventoryLimits) {
1343
+ async function discoverInventory(port, signal, limits = inventoryLimits, progress) {
1265
1344
  validateInventoryLimits(limits);
1266
1345
  const sessions = new Map;
1267
1346
  const notices = new Set;
@@ -1269,59 +1348,75 @@ async function discoverInventory(port, signal, limits = inventoryLimits) {
1269
1348
  let pages = 0;
1270
1349
  while (queue.length && pages < limits.maxPages) {
1271
1350
  signal.throwIfAborted();
1272
- const branch = queue.shift();
1273
- if (!branch)
1274
- break;
1275
- try {
1276
- pages++;
1277
- const page = await port.listSessions({
1278
- parentID: branch.parent,
1279
- limit: 100,
1280
- ...branch.cursor ? { cursor: branch.cursor } : {}
1281
- }, signal);
1282
- signal.throwIfAborted();
1283
- for (const session of page.data) {
1284
- if (session.directory !== port.location.directory) {
1285
- notices.add("foreign-location");
1286
- continue;
1287
- }
1288
- if ((session.parentId ?? null) !== branch.parent || sessions.has(session.sessionId)) {
1289
- notices.add("invalid-hierarchy");
1290
- continue;
1351
+ const batch = queue.splice(0, Math.min(queue[0]?.parent === null ? 1 : 4, limits.maxPages - pages));
1352
+ pages += batch.length;
1353
+ const results = await Promise.allSettled(batch.map(async (branch) => port.listSessions({
1354
+ parentID: branch.parent,
1355
+ limit: 100,
1356
+ ...branch.cursor ? { cursor: branch.cursor } : {}
1357
+ }, signal)));
1358
+ signal.throwIfAborted();
1359
+ for (const [index, branch] of batch.entries()) {
1360
+ try {
1361
+ const result = results[index];
1362
+ if (!result || result.status === "rejected")
1363
+ throw new Error("Inventory unavailable");
1364
+ const page = result.value;
1365
+ signal.throwIfAborted();
1366
+ for (const session of page.data) {
1367
+ if (session.directory !== port.location.directory) {
1368
+ notices.add("foreign-location");
1369
+ continue;
1370
+ }
1371
+ if ((session.parentId ?? null) !== branch.parent || sessions.has(session.sessionId)) {
1372
+ notices.add("invalid-hierarchy");
1373
+ continue;
1374
+ }
1375
+ if (sessions.size >= limits.maxNodes) {
1376
+ notices.add("node-limit");
1377
+ return {
1378
+ sessions: [...sessions.values()],
1379
+ complete: false,
1380
+ pages,
1381
+ notices: [...notices]
1382
+ };
1383
+ }
1384
+ sessions.set(session.sessionId, session);
1385
+ if (branch.depth < limits.maxDepth)
1386
+ queue.push({
1387
+ parent: session.sessionId,
1388
+ depth: branch.depth + 1,
1389
+ cursors: []
1390
+ });
1391
+ else
1392
+ notices.add("depth-limit");
1291
1393
  }
1292
- if (sessions.size >= limits.maxNodes) {
1293
- notices.add("node-limit");
1294
- return {
1295
- sessions: [...sessions.values()],
1296
- complete: false,
1297
- pages,
1298
- notices: [...notices]
1299
- };
1394
+ const cursor = page.cursor?.next;
1395
+ if (cursor) {
1396
+ if (cursor === branch.cursor || branch.cursors.includes(cursor))
1397
+ notices.add("repeated-cursor");
1398
+ else
1399
+ queue.unshift({
1400
+ ...branch,
1401
+ cursor,
1402
+ cursors: [...branch.cursors, cursor]
1403
+ });
1300
1404
  }
1301
- sessions.set(session.sessionId, session);
1302
- if (branch.depth < limits.maxDepth)
1303
- queue.push({
1304
- parent: session.sessionId,
1305
- depth: branch.depth + 1,
1306
- cursors: []
1307
- });
1308
- else
1309
- notices.add("depth-limit");
1405
+ } catch {
1406
+ signal.throwIfAborted();
1407
+ notices.add("inventory-unavailable");
1310
1408
  }
1311
- const cursor = page.cursor?.next;
1312
- if (cursor) {
1313
- if (cursor === branch.cursor || branch.cursors.includes(cursor))
1314
- notices.add("repeated-cursor");
1315
- else
1316
- queue.unshift({
1317
- ...branch,
1318
- cursor,
1319
- cursors: [...branch.cursors, cursor]
1320
- });
1409
+ }
1410
+ if (progress) {
1411
+ try {
1412
+ progress({
1413
+ sessions: [...sessions.values()],
1414
+ complete: false,
1415
+ pages,
1416
+ notices: [...notices]
1417
+ });
1418
+ } catch {
1321
1419
  }
1322
- } catch {
1323
- signal.throwIfAborted();
1324
- notices.add("inventory-unavailable");
1325
1420
  }
1326
1421
  }
1327
1422
  if (queue.length)
@@ -1587,16 +1682,13 @@ var init_models = __esm(() => {
1587
1682
 
1588
1683
  // src/dashboard/view.ts
1589
1684
  function immutableView(value) {
1590
- const copy = structuredClone(value);
1591
- const freeze = (item) => {
1592
- if (item === null || typeof item !== "object")
1593
- return;
1594
- for (const child of Object.values(item))
1595
- freeze(child);
1596
- Object.freeze(item);
1597
- };
1598
- freeze(copy);
1599
- return copy;
1685
+ if (value === null || typeof value !== "object" || owned.has(value))
1686
+ return value;
1687
+ const copy = Object.assign(Array.isArray(value) ? [] : {}, value);
1688
+ for (const key in copy)
1689
+ copy[key] = immutableView(copy[key]);
1690
+ owned.add(copy);
1691
+ return Object.freeze(copy);
1600
1692
  }
1601
1693
  function projectHistory(result, filters) {
1602
1694
  const match = (point) => (!filters.agent || point.agent === filters.agent) && (!filters.model || point.model === filters.model);
@@ -1648,13 +1740,15 @@ function projectHistory(result, filters) {
1648
1740
  notices
1649
1741
  };
1650
1742
  }
1651
- var historyWindows;
1743
+ var historyWindows, owned;
1652
1744
  var init_view = __esm(() => {
1653
1745
  historyWindows = [300000, 3600000, 86400000, 604800000];
1746
+ owned = new WeakSet;
1654
1747
  });
1655
1748
 
1656
1749
  // src/dashboard/controller.ts
1657
1750
  class DashboardController {
1751
+ snapshots = new WeakMap;
1658
1752
  locations = new Map;
1659
1753
  cache = new Map;
1660
1754
  listeners = new Set;
@@ -1860,7 +1954,33 @@ class DashboardController {
1860
1954
  signal,
1861
1955
  AbortSignal.timeout(15000)
1862
1956
  ]);
1863
- const result = await discoverInventory(state.port, scanSignal, this.limits);
1957
+ const result = await discoverInventory(state.port, scanSignal, this.limits, (partial) => {
1958
+ if (!this.current(state, generation, scanSignal))
1959
+ return;
1960
+ for (const session of partial.sessions) {
1961
+ const previous = state.nodes.get(session.sessionId);
1962
+ if (!previous && state.nodes.size >= this.limits.maxNodes)
1963
+ continue;
1964
+ state.nodes.set(session.sessionId, {
1965
+ ...session,
1966
+ status: previous?.status ?? "unknown",
1967
+ inventoryStale: true
1968
+ });
1969
+ }
1970
+ state.complete = false;
1971
+ state.stale = true;
1972
+ state.pages = partial.pages;
1973
+ state.notices = partial.notices;
1974
+ if (!state.selected) {
1975
+ const first = state.nodes.keys().next().value;
1976
+ if (first !== undefined) {
1977
+ state.selected = first;
1978
+ this.queueHistory();
1979
+ }
1980
+ }
1981
+ state.client?.setIds([...state.nodes.keys()]);
1982
+ this.publish();
1983
+ });
1864
1984
  let active;
1865
1985
  try {
1866
1986
  active = new Set(await state.port.activeSessions(result.sessions.map((session) => session.sessionId), scanSignal));
@@ -1935,7 +2055,14 @@ class DashboardController {
1935
2055
  return;
1936
2056
  state.connection = client.connection;
1937
2057
  if (client.health) {
1938
- state.rows = new Map(client.rows);
2058
+ state.rows = new Map([...client.rows].map(([id, row2]) => {
2059
+ let snapshot = this.snapshots.get(row2);
2060
+ if (!snapshot) {
2061
+ snapshot = immutableView(row2);
2062
+ this.snapshots.set(row2, snapshot);
2063
+ }
2064
+ return [id, snapshot];
2065
+ }));
1939
2066
  state.health = client.health;
1940
2067
  state.serverAt = client.now();
1941
2068
  state.receivedAt = this.elapsed();
@@ -2968,12 +3095,30 @@ function overviewLabels(rows) {
2968
3095
  counts.set(suffix, (counts.get(suffix) ?? 0) + 1);
2969
3096
  return suffixes.map((suffix, index) => counts.get(suffix) === 1 ? suffix : `#${index + 1}`);
2970
3097
  }
2971
- function overviewRowLines(row2, width, selected, label, position, maxLines = 3) {
3098
+ function overviewMetrics(row2) {
2972
3099
  const valid2 = row2.freshness === "fresh" || row2.freshness === "stale";
2973
3100
  const percent = `${valid2 && row2.snapshot?.measurement?.confidence === "approximate" ? "~" : ""}${valid2 && row2.utilizationPercent !== undefined ? Math.round(row2.utilizationPercent) : "?"}%`;
2974
3101
  const context = `${row2.freshness === "stale" ? "Last measured " : ""}${percent}`;
2975
3102
  const distance = thresholdDistance(row2, true);
2976
3103
  const threshold = (distance === "At threshold \xB7 requires more" ? "At threshold; requires more" : distance).replace(" \xB7 Threshold reached", " \xB7 reached");
3104
+ const metric2 = `${context} context \xB7 ${threshold}`;
3105
+ 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");
3106
+ const shortMetric = `${context} \xB7 T ${shortThreshold}`;
3107
+ 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";
3108
+ const metrics = [metric2, shortMetric, `${context} \xB7 ${pressure}`];
3109
+ return { percent, context, threshold, pressure, metrics };
3110
+ }
3111
+ function overviewRowHeight(row2, width, maxLines) {
3112
+ const base = width >= 72 ? 1 : 2;
3113
+ const exception = row2.freshness !== "fresh" || row2.node.status === "failed" || row2.node.interrupted || !!compactionLabel(row2) || row2.inventoryStale || row2.liveStale || row2.hidden > 0;
3114
+ if (exception)
3115
+ return Math.min(maxLines, base + 1);
3116
+ if (width < 72 && !overviewMetrics(row2).metrics.some((line) => cells(line) <= width))
3117
+ return Math.min(maxLines, base + 1);
3118
+ return Math.min(maxLines, base);
3119
+ }
3120
+ function overviewRowLines(row2, width, selected, label, position, maxLines = 3) {
3121
+ const { percent, context, threshold, pressure, metrics } = overviewMetrics(row2);
2977
3122
  const state = width < 28 ? {
2978
3123
  running: "RUN",
2979
3124
  idle: "IDLE",
@@ -2987,11 +3132,6 @@ function overviewRowLines(row2, width, selected, label, position, maxLines = 3)
2987
3132
  const indent = row2.depth && identityWidth >= 22 ? `${" ".repeat(Math.min(row2.depth, Math.max(0, Math.floor(identityWidth / 8) - 1)))}\u2514 ` : "";
2988
3133
  const name = `${row2.node.agent ?? "unknown agent"}${width >= 40 ? ` / ${row2.node.model ?? "unknown model"}` : ""}`;
2989
3134
  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
3135
  const fittedMetric = metrics.find((line) => cells(line) <= width);
2996
3136
  const lines = width >= 72 ? [
2997
3137
  overviewColumns(identity, state, row2.freshness === "stale" ? "Last" : percent, threshold, width)
@@ -3234,6 +3374,7 @@ import { isAbsolute as isAbsolute3 } from "path";
3234
3374
  class TerminalShell {
3235
3375
  controller;
3236
3376
  quit;
3377
+ work = { formattedOverviewRows: 0 };
3237
3378
  scroll = 0;
3238
3379
  metric = 0;
3239
3380
  sample = 0;
@@ -3249,6 +3390,9 @@ class TerminalShell {
3249
3390
  rowOffset = 0;
3250
3391
  overviewScope = "";
3251
3392
  overviewPage = 1;
3393
+ labelKey = "";
3394
+ labels = [];
3395
+ layouts = new Map;
3252
3396
  historyIdentity = "";
3253
3397
  constructor(controller, quit) {
3254
3398
  this.controller = controller;
@@ -3504,27 +3648,75 @@ class TerminalShell {
3504
3648
  const noticeBudget = Math.min(notices.length, Math.floor((available - heading.length) / 4));
3505
3649
  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
3650
  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));
3651
+ const labelKey = view.rows.map((row2) => row2.node.sessionId).join("\0");
3652
+ if (labelKey !== this.labelKey) {
3653
+ this.labels = overviewLabels(view.rows);
3654
+ this.labelKey = labelKey;
3655
+ }
3656
+ const ids = new Set(view.rows.map((row2) => row2.node.sessionId));
3657
+ for (const id of this.layouts.keys())
3658
+ if (!ids.has(id))
3659
+ this.layouts.delete(id);
3660
+ const heights = view.rows.map((row2) => {
3661
+ const key = JSON.stringify([
3662
+ width,
3663
+ budget,
3664
+ row2.node,
3665
+ row2.depth,
3666
+ row2.freshness,
3667
+ row2.pressure,
3668
+ row2.triggerDeltaTokens,
3669
+ row2.utilizationPercent,
3670
+ row2.snapshot?.measurement?.confidence,
3671
+ row2.snapshot?.compaction,
3672
+ row2.inventoryStale,
3673
+ row2.liveStale,
3674
+ row2.hidden
3675
+ ]);
3676
+ let layout = this.layouts.get(row2.node.sessionId);
3677
+ if (!layout || layout.key !== key) {
3678
+ layout = { key, height: overviewRowHeight(row2, width, budget) };
3679
+ this.layouts.set(row2.node.sessionId, layout);
3680
+ }
3681
+ return layout.height;
3682
+ });
3509
3683
  const selected = view.rows.findIndex((row2) => row2.node.sessionId === view.selectedSessionId);
3510
- const start = rows.slice(0, Math.max(0, selected)).reduce((n, row2) => n + row2.length, 0);
3511
- const end = start + (rows[selected]?.length ?? 0);
3684
+ const start = heights.slice(0, Math.max(0, selected)).reduce((n, h) => n + h, 0);
3685
+ const end = start + (heights[selected] ?? 0);
3512
3686
  if (start < this.rowOffset)
3513
3687
  this.rowOffset = start;
3514
3688
  if (end > this.rowOffset + budget)
3515
3689
  this.rowOffset = Math.max(start, end - budget);
3516
- const flat = rows.flat();
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);
3690
+ const total = heights.reduce((n, h) => n + h, 0);
3691
+ this.rowOffset = Math.max(0, Math.min(this.rowOffset, Math.max(0, total - budget)));
3692
+ const visible = [];
3693
+ let cursor = 0, page = 0;
3694
+ for (const [index, row2] of view.rows.entries()) {
3695
+ const rowHeight = heights[index] ?? 1;
3696
+ if (cursor >= this.rowOffset && cursor + rowHeight <= this.rowOffset + budget)
3697
+ page++;
3698
+ if (cursor + rowHeight > this.rowOffset && cursor < this.rowOffset + budget) {
3699
+ const layout = this.layouts.get(row2.node.sessionId);
3700
+ const chosen = row2.node.sessionId === view.selectedSessionId;
3701
+ const label = this.labels[index] ?? `#${index + 1}`;
3702
+ if (layout) {
3703
+ if (!layout.lines || layout.selected !== chosen || layout.label !== label || layout.position !== index) {
3704
+ this.work.formattedOverviewRows++;
3705
+ layout.lines = overviewRowLines(row2, width, chosen, label, index, budget);
3706
+ layout.selected = chosen;
3707
+ layout.label = label;
3708
+ layout.position = index;
3709
+ }
3710
+ visible.push(...layout.lines.slice(Math.max(0, this.rowOffset - cursor), this.rowOffset + budget - cursor));
3711
+ }
3712
+ }
3713
+ cursor += rowHeight;
3714
+ }
3715
+ this.overviewPage = Math.max(1, page);
3524
3716
  body = [
3525
3717
  ...status,
3526
3718
  ...heading,
3527
- ...flat.length ? flat.slice(this.rowOffset, this.rowOffset + budget) : [
3719
+ ...total ? visible : [
3528
3720
  clip("No matching sessions; / search, f filters, r refresh.", width)
3529
3721
  ]
3530
3722
  ];
@@ -3569,7 +3761,7 @@ class TerminalShell {
3569
3761
  }
3570
3762
  }
3571
3763
  function mountTerminal(controller, renderer, text2, finished) {
3572
- let closed = false;
3764
+ let closed = false, pendingDraw = false, drawing = false;
3573
3765
  let unsubscribe = () => {
3574
3766
  };
3575
3767
  const shutdown = (failed = false) => {
@@ -3600,25 +3792,38 @@ function mountTerminal(controller, renderer, text2, finished) {
3600
3792
  const onSignal = () => shutdown();
3601
3793
  const signals = ["SIGINT", "SIGTERM", "SIGHUP"];
3602
3794
  const draw = () => {
3603
- if (closed)
3795
+ if (closed || drawing)
3604
3796
  return;
3797
+ pendingDraw = false;
3798
+ drawing = true;
3605
3799
  try {
3606
3800
  text2.content = shell.frame(renderer.width, renderer.height);
3607
3801
  } catch {
3608
3802
  fail();
3803
+ } finally {
3804
+ drawing = false;
3609
3805
  }
3610
3806
  };
3807
+ const scheduleDraw = () => {
3808
+ if (pendingDraw || closed || drawing)
3809
+ return;
3810
+ pendingDraw = true;
3811
+ queueMicrotask(() => {
3812
+ if (pendingDraw)
3813
+ draw();
3814
+ });
3815
+ };
3611
3816
  const key = (event) => {
3612
3817
  try {
3613
3818
  shell.key(event, Math.max(1, renderer.height - 4));
3614
- draw();
3819
+ scheduleDraw();
3615
3820
  } catch {
3616
3821
  fail();
3617
3822
  }
3618
3823
  };
3619
3824
  const paste = (event) => {
3620
3825
  shell.paste(new TextDecoder().decode(event.bytes));
3621
- draw();
3826
+ scheduleDraw();
3622
3827
  };
3623
3828
  try {
3624
3829
  renderer.root.add(text2);
@@ -3632,7 +3837,8 @@ function mountTerminal(controller, renderer, text2, finished) {
3632
3837
  process.on(signal, onSignal);
3633
3838
  process.on("uncaughtException", fail);
3634
3839
  process.on("unhandledRejection", fail);
3635
- unsubscribe = controller.subscribe(draw);
3840
+ unsubscribe = controller.subscribe(scheduleDraw);
3841
+ draw();
3636
3842
  if (closed) {
3637
3843
  unsubscribe();
3638
3844
  return { shell, shutdown };
@@ -3737,6 +3943,11 @@ function reconcile(a, b) {
3737
3943
  };
3738
3944
  }
3739
3945
  function analyzeModelRequests(records, options) {
3946
+ const accumulator = createModelAccumulator(options);
3947
+ accumulator.append(records);
3948
+ return accumulator.result();
3949
+ }
3950
+ function createModelAccumulator(options) {
3740
3951
  const maxRequests = options.maxRequests ?? 1e5;
3741
3952
  if (!Number.isSafeInteger(maxRequests) || maxRequests < 1 || maxRequests > 1e6)
3742
3953
  throw new Error("maxRequests must be an integer from 1 to 1000000");
@@ -3747,7 +3958,7 @@ function analyzeModelRequests(records, options) {
3747
3958
  throw new Error("Invalid analytics timestamp");
3748
3959
  if (options.from !== undefined && options.to !== undefined && options.from > options.to)
3749
3960
  throw new Error("--from must precede --to");
3750
- const coverage = {
3961
+ const counters = {
3751
3962
  source: "retained-request-records",
3752
3963
  scannedRecords: 0,
3753
3964
  unscannedRecords: 0,
@@ -3761,74 +3972,98 @@ function analyzeModelRequests(records, options) {
3761
3972
  partial: false
3762
3973
  };
3763
3974
  const requests = new Map;
3764
- for (const record2 of records) {
3765
- coverage.scannedRecords++;
3766
- if (record2.type === "gap") {
3767
- coverage.gapRecords++;
3768
- continue;
3769
- }
3770
- if (record2.type !== "request")
3771
- continue;
3772
- const fact = requestFact(record2);
3773
- if (!fact) {
3774
- coverage.missingIdentityRecords++;
3775
- continue;
3776
- }
3777
- fact.location = options.location;
3778
- const key = JSON.stringify([
3779
- fact.sessionId,
3780
- fact.requestId,
3781
- fact.startedAt
3782
- ]);
3783
- const previous = requests.get(key);
3784
- if (previous) {
3785
- coverage.duplicateRecords++;
3786
- const merged = reconcile(previous.fact, fact);
3787
- if (!merged)
3788
- previous.conflict = true;
3789
- else
3790
- previous.fact = merged;
3791
- } else {
3792
- if (requests.size === maxRequests) {
3793
- coverage.scannedRecords--;
3794
- coverage.unscannedRecords = records.length - coverage.scannedRecords;
3975
+ let totalRecords = 0, stopped = false;
3976
+ function append(records) {
3977
+ totalRecords += records.length;
3978
+ for (const record2 of records) {
3979
+ if (stopped)
3795
3980
  break;
3981
+ counters.scannedRecords++;
3982
+ if (record2.type === "gap") {
3983
+ counters.gapRecords++;
3984
+ continue;
3985
+ }
3986
+ if (record2.type !== "request")
3987
+ continue;
3988
+ const fact = requestFact(record2);
3989
+ if (!fact) {
3990
+ counters.missingIdentityRecords++;
3991
+ continue;
3992
+ }
3993
+ fact.location = options.location;
3994
+ const key = JSON.stringify([
3995
+ fact.sessionId,
3996
+ fact.requestId,
3997
+ fact.startedAt
3998
+ ]);
3999
+ const previous = requests.get(key);
4000
+ if (previous) {
4001
+ counters.duplicateRecords++;
4002
+ const merged = reconcile(previous.fact, fact);
4003
+ if (!merged)
4004
+ previous.conflict = true;
4005
+ else
4006
+ previous.fact = merged;
4007
+ } else {
4008
+ if (requests.size === maxRequests) {
4009
+ counters.scannedRecords--;
4010
+ stopped = true;
4011
+ break;
4012
+ }
4013
+ requests.set(key, { fact, conflict: false });
3796
4014
  }
3797
- requests.set(key, { fact, conflict: false });
3798
4015
  }
3799
4016
  }
3800
- const accepted = [];
3801
- for (const { fact: r, conflict } of requests.values()) {
3802
- if (conflict) {
3803
- coverage.conflictingRequests++;
3804
- continue;
3805
- }
3806
- if (r.completedAt === undefined) {
3807
- coverage.incompleteRequests++;
3808
- continue;
3809
- }
3810
- const duration = r.completedAt - r.startedAt;
3811
- if (!nonnegative2(r.completedAt) || !Number.isFinite(duration) || duration <= 0) {
3812
- coverage.invalidTimingRequests++;
3813
- continue;
4017
+ function* steps() {
4018
+ const coverage = {
4019
+ ...counters,
4020
+ unscannedRecords: totalRecords - counters.scannedRecords
4021
+ };
4022
+ const accepted = [];
4023
+ for (const { fact: r, conflict } of requests.values()) {
4024
+ yield;
4025
+ if (conflict) {
4026
+ coverage.conflictingRequests++;
4027
+ continue;
4028
+ }
4029
+ if (r.completedAt === undefined) {
4030
+ coverage.incompleteRequests++;
4031
+ continue;
4032
+ }
4033
+ const duration = r.completedAt - r.startedAt;
4034
+ if (!nonnegative2(r.completedAt) || !Number.isFinite(duration) || duration <= 0) {
4035
+ coverage.invalidTimingRequests++;
4036
+ continue;
4037
+ }
4038
+ 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)
4039
+ continue;
4040
+ accepted.push({ ...r, completedAt: r.completedAt });
3814
4041
  }
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)
3816
- continue;
3817
- accepted.push({ ...r, completedAt: r.completedAt });
4042
+ const models = yield* summarizeModelRequestSteps(accepted);
4043
+ 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);
4044
+ return {
4045
+ schemaVersion: 1,
4046
+ location: options.location,
4047
+ models,
4048
+ coverage,
4049
+ requests: accepted
4050
+ };
3818
4051
  }
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
- };
4052
+ return { append, steps, result: () => finishSteps(steps()) };
4053
+ }
4054
+ function finishSteps(steps) {
4055
+ let step = steps.next();
4056
+ while (!step.done)
4057
+ step = steps.next();
4058
+ return step.value;
3828
4059
  }
3829
4060
  function summarizeModelRequests(requests) {
4061
+ return finishSteps(summarizeModelRequestSteps(requests));
4062
+ }
4063
+ function* summarizeModelRequestSteps(requests) {
3830
4064
  const groups = new Map;
3831
4065
  for (const r of requests) {
4066
+ yield;
3832
4067
  const duration = r.completedAt - r.startedAt;
3833
4068
  let group = groups.get(r.model);
3834
4069
  if (!group) {