@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.
- package/CHANGELOG.md +38 -0
- package/README.md +23 -9
- package/dist/cli.js +1179 -92
- package/dist/core.js +49 -4
- package/dist/index.js +570 -9
- package/dist/rpc.js +129 -2
- package/dist/tui.js +1543 -707
- package/dist/types/adapter.d.ts +2 -0
- package/dist/types/core.d.ts +1 -0
- package/dist/types/dashboard/command.d.ts +1 -1
- package/dist/types/dashboard/connection.d.ts +2 -0
- package/dist/types/dashboard/controller.d.ts +4 -0
- package/dist/types/dashboard/models.d.ts +42 -0
- package/dist/types/dashboard/modelview.d.ts +30 -0
- package/dist/types/dashboard/port.d.ts +6 -0
- package/dist/types/dashboard/terminal.d.ts +7 -0
- package/dist/types/dashboard/terminalview.d.ts +7 -2
- package/dist/types/dashboard/view.d.ts +6 -1
- package/dist/types/history.d.ts +1 -0
- package/dist/types/historyclient.d.ts +15 -0
- package/dist/types/modelanalytics.d.ts +8 -0
- package/dist/types/modelanalyticsserver.d.ts +16 -0
- package/dist/types/modelseries.d.ts +43 -0
- package/dist/types/rpc.d.ts +28 -0
- package/dist/types/tui/chartformat.d.ts +1 -1
- package/dist/types/tui/charts.d.ts +2 -3
- package/dist/types/tui/compactformat.d.ts +12 -3
- package/dist/types/tui/historystate.d.ts +49 -0
- package/dist/types/tui/inventory.d.ts +12 -0
- package/dist/types/tui/panelcontrols.d.ts +8 -0
- package/dist/types/tui/panelformat.d.ts +12 -0
- package/dist/types/tui/panelview.d.ts +22 -0
- package/dist/types/tui/quantity.d.ts +1 -0
- package/dist/types/tui/sidebar.d.ts +0 -5
- package/dist/types/tui/state.d.ts +3 -1
- package/dist/types/tui/statusformat.d.ts +19 -0
- 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.
|
|
675
|
-
3. In the
|
|
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; /
|
|
699
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
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
|
|
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
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
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
|
|
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
|
|
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} |
|
|
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}
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
2210
|
-
|
|
2211
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
2467
|
-
|
|
2468
|
-
|
|
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
|
|
2482
|
-
...
|
|
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
|
|
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
|
|
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
|
-
|
|
2804
|
-
|
|
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
|
|