@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/CHANGELOG.md +8 -0
- package/README.md +7 -7
- package/dist/cli.js +415 -180
- package/dist/core.js +88 -59
- package/dist/index.js +710 -166
- package/dist/tui.js +3 -2
- package/dist/types/archive.d.ts +40 -0
- package/dist/types/core.d.ts +1 -1
- package/dist/types/dashboard/controller.d.ts +1 -0
- package/dist/types/dashboard/inventory.d.ts +1 -1
- package/dist/types/dashboard/terminal.d.ts +6 -0
- package/dist/types/dashboard/terminalview.d.ts +2 -0
- package/dist/types/dashboard/view.d.ts +1 -1
- package/dist/types/history.d.ts +39 -2
- package/dist/types/historyserver.d.ts +2 -1
- package/dist/types/modelanalytics.d.ts +11 -0
- package/dist/types/modelanalyticsserver.d.ts +2 -1
- package/package.json +2 -1
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
|
-
|
|
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
|
|
673
|
-
const
|
|
674
|
-
|
|
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
|
-
|
|
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
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
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
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
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
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
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
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
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
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
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
|
|
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
|
|
3508
|
-
|
|
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 =
|
|
3511
|
-
const end = start + (
|
|
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
|
|
3517
|
-
this.rowOffset = Math.max(0, Math.min(this.rowOffset, Math.max(0,
|
|
3518
|
-
|
|
3519
|
-
|
|
3520
|
-
|
|
3521
|
-
|
|
3522
|
-
|
|
3523
|
-
|
|
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
|
-
...
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
|
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
|
-
|
|
3765
|
-
|
|
3766
|
-
|
|
3767
|
-
|
|
3768
|
-
|
|
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
|
-
|
|
3801
|
-
|
|
3802
|
-
|
|
3803
|
-
|
|
3804
|
-
|
|
3805
|
-
|
|
3806
|
-
|
|
3807
|
-
|
|
3808
|
-
|
|
3809
|
-
|
|
3810
|
-
|
|
3811
|
-
|
|
3812
|
-
|
|
3813
|
-
|
|
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
|
-
|
|
3816
|
-
|
|
3817
|
-
|
|
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
|
-
|
|
3820
|
-
|
|
3821
|
-
|
|
3822
|
-
|
|
3823
|
-
|
|
3824
|
-
|
|
3825
|
-
|
|
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) {
|