@cesarus777/opencode-cgp 0.2.1 → 0.2.3
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/README.md +5 -3
- package/dist/core.js +2 -1
- package/dist/index.js +80 -8
- package/dist/tui.js +272 -112
- package/dist/types/adapter.d.ts +1 -1
- package/dist/types/snapshotserver.d.ts +1 -1
- package/dist/types/tui/chartformat.d.ts +37 -2
- package/dist/types/tui/charts.d.ts +1 -0
- package/dist/types/tui/compactformat.d.ts +3 -2
- package/dist/types/tui/contextchart.d.ts +19 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -41,7 +41,7 @@ Global configuration is `~/.config/opencode/opencode.jsonc` (or the OpenCode con
|
|
|
41
41
|
|
|
42
42
|
Restart/reload OpenCode and look for `context-guard: observe; diagnostics ...`. Observe mode records policy decisions and never requests compaction; OpenCode's own automatic compaction remains active. Inspect diagnostics first, then set `mode` to `enforce` to admit native compaction at runtime step boundaries. The complete [example](examples/opencode.jsonc) includes model, role, and agent policies. **Last matching rule wins**; selectors within a rule are ANDed.
|
|
43
43
|
|
|
44
|
-
Pin the exact published version in the existing entry's `package` field or use the manager. Version `0.2.
|
|
44
|
+
Pin the exact published version in the existing entry's `package` field or use the manager. Version `0.2.3` restores context usage for existing sessions when the plugin is enabled and keeps stale occupancy visible.
|
|
45
45
|
|
|
46
46
|
```sh
|
|
47
47
|
opencode plugin add @cesarus777/opencode-cgp@0.2.1
|
|
@@ -96,13 +96,15 @@ A provider measurement refers to its **completed request**, not the next assembl
|
|
|
96
96
|
|
|
97
97
|
## Native TUI
|
|
98
98
|
|
|
99
|
-
Open `/context-guard` or **Open Context Guard** in the command palette for current prompt occupancy, effective input capacity, trigger/headroom, measurement freshness, compaction phase and nested agent status. A native sidebar section appears where the host mounts its sidebar; the panel also works on child routes and narrow terminals. Estimates, unknown values, stale measurements and uncertain compaction remain explicit.
|
|
99
|
+
Open `/context-guard` or **Open Context Guard** in the command palette for current prompt occupancy, effective input capacity, trigger/headroom, measurement freshness, compaction phase and nested agent status. A native sidebar section appears where the host mounts its sidebar; the panel also works on child routes and narrow terminals. The main session includes a live context chart scaled to its full effective input capacity, with a horizontal compaction threshold and recent usage samples. Key values stay visible as text. Estimates, unknown values, stale measurements and uncertain compaction remain explicit.
|
|
100
100
|
|
|
101
101
|
The TUI reads bounded snapshots over public OpenCode RPC, including when telemetry is disabled. Remote clients also need a client-side package registration in `cli.json`; see [the example](examples/cli.jsonc) and [controls, installation and bounds](docs/configuration.md#native-context-guard-view). It targets the inspected 2.0.29 API. The actual local host passes wide root, child and narrow grandchild panel checks. Use **Context Guard: open panel** above a child composer, where the host hides its usual prompt and palette shortcut. tmux, Neovim and cross-machine remote validation remain open.
|
|
102
102
|
|
|
103
|
+
Enabling Context Guard after work has already started also shows the last reported prompt usage for idle sessions and subagents. Opening the sidebar or panel restores it from the public session context, preserving its original age and **STALE** label. Stale usage still shows tokens and occupancy percentage. Restoration respects model changes, reverts and completed compactions; unavailable provider usage stays unknown. These reads never request compaction, and transcript content is never retained or sent through the plugin RPC.
|
|
104
|
+
|
|
103
105
|
Per-session live TPS, average model throughput, average session throughput and observed TTFT appear in the selected session and child rows. Live TPS and TTFT use `~` to mark estimates; completed rates use provider totals. Configurable local history defaults to seven days with a 64 MiB total cap, independently of diagnostic telemetry. See [metric definitions and history exports](docs/configuration.md#live-performance-and-retained-history).
|
|
104
106
|
|
|
105
|
-
Press **c** in the Context Guard panel for retained-history terminal charts
|
|
107
|
+
Press **c** in the Context Guard panel for retained-history terminal charts. View one metric at a time with **Left / Right**, change the time window with **[ / ]**, and select an agent instance with **Up / Down**. Press **c** to return or **q** to close the panel; controls stay visible below the scrollable content. Generate a self-contained interactive browser report with `bun run charts /absolute/history-directory --output report.html`. It includes filters, UTC timestamp tooltips, request statistics and SVG export. Installed packages expose the same command as `context-guard charts ...` (Bun required). See [chart controls and limits](docs/configuration.md#performance-charts).
|
|
106
108
|
|
|
107
109
|
## Inspect
|
|
108
110
|
|
package/dist/core.js
CHANGED
|
@@ -748,13 +748,14 @@ function metric(snapshot, now, staleMs) {
|
|
|
748
748
|
const freshness = !m ? "missing" : m.model !== snapshot?.model || m.at > now ? "invalid" : now - m.at > staleMs ? "stale" : "fresh";
|
|
749
749
|
const t = snapshot?.policy.triggerTokens;
|
|
750
750
|
const valid = freshness === "fresh" && m !== undefined;
|
|
751
|
+
const displayable = valid || freshness === "stale" && m !== undefined;
|
|
751
752
|
const pressureRatio = valid && t !== undefined && t > 0 ? m.tokens / t : undefined;
|
|
752
753
|
const crossed = valid && t !== undefined && t > 0 && (snapshot?.policy.comparison === ">" ? m.tokens > t : m.tokens >= t);
|
|
753
754
|
return {
|
|
754
755
|
freshness,
|
|
755
756
|
pressure: pressureRatio === undefined ? "unknown" : crossed ? "crossed" : pressureRatio >= 0.9 ? "near" : "normal",
|
|
756
757
|
...pressureRatio === undefined ? {} : { pressureRatio },
|
|
757
|
-
...
|
|
758
|
+
...displayable && snapshot?.capacityTokens && snapshot.capacityTokens > 0 ? { utilizationPercent: m.tokens / snapshot.capacityTokens * 100 } : {},
|
|
758
759
|
...valid && t !== undefined ? { triggerDeltaTokens: t - m.tokens } : {}
|
|
759
760
|
};
|
|
760
761
|
}
|
package/dist/index.js
CHANGED
|
@@ -1330,7 +1330,7 @@ function freezeSnapshot(row2) {
|
|
|
1330
1330
|
}
|
|
1331
1331
|
|
|
1332
1332
|
// src/snapshotserver.ts
|
|
1333
|
-
async function serveSnapshots(rpc, store, performance = false) {
|
|
1333
|
+
async function serveSnapshots(rpc, store, performance = false, prepareRead) {
|
|
1334
1334
|
if (!rpc)
|
|
1335
1335
|
return () => {
|
|
1336
1336
|
};
|
|
@@ -1341,9 +1341,10 @@ async function serveSnapshots(rpc, store, performance = false) {
|
|
|
1341
1341
|
const batchValid = performance ? isPerformanceBatch : isBatch;
|
|
1342
1342
|
const resultValid = performance ? isPerformanceResult : isResult;
|
|
1343
1343
|
const registration = await rpc.register(performance ? performanceRpc : monitorRpc, {
|
|
1344
|
-
read: async (input) => {
|
|
1344
|
+
read: async (input, context) => {
|
|
1345
1345
|
if (!isRead(input))
|
|
1346
1346
|
throw new Error("Invalid snapshot request");
|
|
1347
|
+
await prepareRead?.(input.sessionIds, context.signal);
|
|
1347
1348
|
const raw = store.read(input);
|
|
1348
1349
|
const result = performance ? raw : stripPerformance(raw);
|
|
1349
1350
|
if (!resultValid(result))
|
|
@@ -2053,7 +2054,10 @@ async function setup(ctx, injectedSink) {
|
|
|
2053
2054
|
diagnostics.emit(event);
|
|
2054
2055
|
}
|
|
2055
2056
|
};
|
|
2056
|
-
|
|
2057
|
+
let prepareRead = async (_ids, _signal) => {
|
|
2058
|
+
};
|
|
2059
|
+
const onRead = (ids, signal) => prepareRead(ids, signal);
|
|
2060
|
+
const stopV1 = await serveSnapshots(ctx.rpc, store, false, onRead).catch(() => {
|
|
2057
2061
|
diagnostics.emit({
|
|
2058
2062
|
time: Date.now(),
|
|
2059
2063
|
type: "snapshot-service-unavailable"
|
|
@@ -2061,7 +2065,7 @@ async function setup(ctx, injectedSink) {
|
|
|
2061
2065
|
return () => {
|
|
2062
2066
|
};
|
|
2063
2067
|
});
|
|
2064
|
-
const stopV2 = await serveSnapshots(ctx.rpc, store, true).catch(() => {
|
|
2068
|
+
const stopV2 = await serveSnapshots(ctx.rpc, store, true, onRead).catch(() => {
|
|
2065
2069
|
diagnostics.emit({
|
|
2066
2070
|
time: Date.now(),
|
|
2067
2071
|
type: "performance-service-unavailable"
|
|
@@ -2179,8 +2183,73 @@ async function setup(ctx, injectedSink) {
|
|
|
2179
2183
|
}, sink);
|
|
2180
2184
|
monitor = new ContextMonitor(config, scheduler, sink);
|
|
2181
2185
|
const activeMonitor = monitor;
|
|
2186
|
+
const usageReads = new Map;
|
|
2187
|
+
prepareRead = (ids, signal) => serialized(async () => {
|
|
2188
|
+
if (stopped || signal.aborted || !ctx.session.context || !ids.length)
|
|
2189
|
+
return;
|
|
2190
|
+
const readSignal = AbortSignal.any([
|
|
2191
|
+
signal,
|
|
2192
|
+
controller.signal,
|
|
2193
|
+
AbortSignal.timeout(5000)
|
|
2194
|
+
]);
|
|
2195
|
+
for (const id of ids) {
|
|
2196
|
+
if (readSignal.aborted)
|
|
2197
|
+
break;
|
|
2198
|
+
const existing = activeMonitor.sessions.get(id);
|
|
2199
|
+
if (existing?.usage.current || existing?.active || (existing?.usage.modelRequests ?? 0) > 0 || recoveries.has(id))
|
|
2200
|
+
continue;
|
|
2201
|
+
if (existing && (existing.compaction.phase !== "idle" || existing.compaction.lastAttempt !== undefined || existing.compaction.lastCompleted !== undefined || existing.compaction.failures > 0))
|
|
2202
|
+
continue;
|
|
2203
|
+
if (Date.now() - (usageReads.get(id) ?? 0) < 30000)
|
|
2204
|
+
continue;
|
|
2205
|
+
if (usageReads.size >= config.safety.maxSessions) {
|
|
2206
|
+
const oldest = usageReads.keys().next().value;
|
|
2207
|
+
if (oldest !== undefined)
|
|
2208
|
+
usageReads.delete(oldest);
|
|
2209
|
+
}
|
|
2210
|
+
usageReads.set(id, Date.now());
|
|
2211
|
+
try {
|
|
2212
|
+
const info = await ctx.session.get({ sessionID: id }, { signal: readSignal });
|
|
2213
|
+
if (info.location.directory !== ctx.location.directory)
|
|
2214
|
+
continue;
|
|
2215
|
+
const messages = await ctx.session.context({ sessionID: id }, { signal: readSignal });
|
|
2216
|
+
const boundary = info.revert?.messageID;
|
|
2217
|
+
const end = boundary ? messages.findIndex((m) => m.id === boundary) : messages.length;
|
|
2218
|
+
if (end < 0)
|
|
2219
|
+
continue;
|
|
2220
|
+
const compacted = messages.findLastIndex((m, index) => index < end && m.type === "compaction" && m.status === "completed");
|
|
2221
|
+
const message = messages.findLast((m, index) => index > compacted && index < end && m.type === "assistant" && m.tokens !== undefined);
|
|
2222
|
+
if (message?.type !== "assistant" || !message.tokens)
|
|
2223
|
+
continue;
|
|
2224
|
+
const tokens = promptTokens(message.tokens);
|
|
2225
|
+
const model = `${message.model.providerID}/${message.model.id}`;
|
|
2226
|
+
const at = message.time.completed ?? message.time.streamed;
|
|
2227
|
+
if (tokens === undefined || at === undefined || !Number.isFinite(at) || at < 0 || at > Date.now())
|
|
2228
|
+
continue;
|
|
2229
|
+
if (info.model && model !== `${info.model.providerID}/${info.model.id}`)
|
|
2230
|
+
continue;
|
|
2231
|
+
await refreshModels(!models.has(model), readSignal);
|
|
2232
|
+
const s = await discover(id, readSignal);
|
|
2233
|
+
if (!s || s.model && s.model !== model || s.usage.current || readSignal.aborted)
|
|
2234
|
+
continue;
|
|
2235
|
+
activeMonitor.touch({ sessionId: id, model }, models.get(model));
|
|
2236
|
+
s.usage.current = {
|
|
2237
|
+
tokens,
|
|
2238
|
+
at,
|
|
2239
|
+
model,
|
|
2240
|
+
provenance: "provider-normalized",
|
|
2241
|
+
confidence: "reported",
|
|
2242
|
+
scope: "completed-request",
|
|
2243
|
+
requestId: message.id
|
|
2244
|
+
};
|
|
2245
|
+
s.usage.peak = Math.max(s.usage.peak, tokens);
|
|
2246
|
+
store.mark(id);
|
|
2247
|
+
} catch {
|
|
2248
|
+
}
|
|
2249
|
+
}
|
|
2250
|
+
});
|
|
2182
2251
|
let models = new Map, modelAt = 0, modelRetryAt = 0, modelRetryMs = 1000;
|
|
2183
|
-
async function refreshModels(retryMissing = false) {
|
|
2252
|
+
async function refreshModels(retryMissing = false, signal = controller.signal) {
|
|
2184
2253
|
const now = Date.now();
|
|
2185
2254
|
if (now - modelAt < 60000 && (!retryMissing || now < modelRetryAt))
|
|
2186
2255
|
return;
|
|
@@ -2188,7 +2257,10 @@ async function setup(ctx, injectedSink) {
|
|
|
2188
2257
|
modelRetryAt = now + modelRetryMs;
|
|
2189
2258
|
modelRetryMs = Math.min(60000, modelRetryMs * 2);
|
|
2190
2259
|
try {
|
|
2191
|
-
models = new Map((await ctx.model.list(undefined, { signal
|
|
2260
|
+
models = new Map((await ctx.model.list(undefined, { signal })).data.map((m) => [
|
|
2261
|
+
`${m.providerID}/${m.id}`,
|
|
2262
|
+
{ ...m.limit }
|
|
2263
|
+
]));
|
|
2192
2264
|
let missing = false;
|
|
2193
2265
|
for (const s of activeMonitor.sessions.values()) {
|
|
2194
2266
|
const limits = s.model ? models.get(s.model) : undefined;
|
|
@@ -2212,8 +2284,8 @@ async function setup(ctx, injectedSink) {
|
|
|
2212
2284
|
sink.emit({ time: Date.now(), type: "model-metadata-unavailable" });
|
|
2213
2285
|
}
|
|
2214
2286
|
}
|
|
2215
|
-
async function discover(sessionId) {
|
|
2216
|
-
const info = await ctx.session.get({ sessionID: sessionId }, { signal
|
|
2287
|
+
async function discover(sessionId, signal = controller.signal) {
|
|
2288
|
+
const info = await ctx.session.get({ sessionID: sessionId }, { signal });
|
|
2217
2289
|
if (info.location.directory !== ctx.location.directory)
|
|
2218
2290
|
return;
|
|
2219
2291
|
const identity = {
|
package/dist/tui.js
CHANGED
|
@@ -313,8 +313,8 @@ function uniqueRows(value) {
|
|
|
313
313
|
|
|
314
314
|
// src/tui.tsx
|
|
315
315
|
import { effect as _$effect5 } from "@opentui/solid";
|
|
316
|
-
import { createTextNode as _$
|
|
317
|
-
import { insertNode as _$
|
|
316
|
+
import { createTextNode as _$createTextNode3 } from "@opentui/solid";
|
|
317
|
+
import { insertNode as _$insertNode5 } from "@opentui/solid";
|
|
318
318
|
import { setProp as _$setProp5 } from "@opentui/solid";
|
|
319
319
|
import { createElement as _$createElement5 } from "@opentui/solid";
|
|
320
320
|
import { createComponent as _$createComponent5 } from "@opentui/solid";
|
|
@@ -330,13 +330,14 @@ function metric(snapshot, now, staleMs) {
|
|
|
330
330
|
const freshness = !m ? "missing" : m.model !== snapshot?.model || m.at > now ? "invalid" : now - m.at > staleMs ? "stale" : "fresh";
|
|
331
331
|
const t = snapshot?.policy.triggerTokens;
|
|
332
332
|
const valid2 = freshness === "fresh" && m !== undefined;
|
|
333
|
+
const displayable = valid2 || freshness === "stale" && m !== undefined;
|
|
333
334
|
const pressureRatio = valid2 && t !== undefined && t > 0 ? m.tokens / t : undefined;
|
|
334
335
|
const crossed = valid2 && t !== undefined && t > 0 && (snapshot?.policy.comparison === ">" ? m.tokens > t : m.tokens >= t);
|
|
335
336
|
return {
|
|
336
337
|
freshness,
|
|
337
338
|
pressure: pressureRatio === undefined ? "unknown" : crossed ? "crossed" : pressureRatio >= 0.9 ? "near" : "normal",
|
|
338
339
|
...pressureRatio === undefined ? {} : { pressureRatio },
|
|
339
|
-
...
|
|
340
|
+
...displayable && snapshot?.capacityTokens && snapshot.capacityTokens > 0 ? { utilizationPercent: m.tokens / snapshot.capacityTokens * 100 } : {},
|
|
340
341
|
...valid2 && t !== undefined ? { triggerDeltaTokens: t - m.tokens } : {}
|
|
341
342
|
};
|
|
342
343
|
}
|
|
@@ -1224,6 +1225,61 @@ function performanceLines(row2, width) {
|
|
|
1224
1225
|
];
|
|
1225
1226
|
}
|
|
1226
1227
|
|
|
1228
|
+
// src/tui/contextchart.ts
|
|
1229
|
+
function updateContextTrace(previous, row2) {
|
|
1230
|
+
const snapshot = row2?.snapshot;
|
|
1231
|
+
const model = snapshot?.model ?? row2?.node.model;
|
|
1232
|
+
const capacity = snapshot?.capacityTokens;
|
|
1233
|
+
const sessionId = row2?.node.sessionId ?? "";
|
|
1234
|
+
const samples = previous?.sessionId === sessionId && previous.model === model && previous.capacity === capacity ? previous.samples : [];
|
|
1235
|
+
const measurement2 = snapshot?.measurement;
|
|
1236
|
+
const valid2 = (row2?.freshness === "fresh" || row2?.freshness === "stale") && measurement2 && Number.isFinite(measurement2.tokens) && measurement2.tokens >= 0;
|
|
1237
|
+
const at = measurement2?.at ?? snapshot?.observedAt;
|
|
1238
|
+
let next = samples;
|
|
1239
|
+
if (at !== undefined && Number.isFinite(at)) {
|
|
1240
|
+
const sample = {
|
|
1241
|
+
at,
|
|
1242
|
+
...valid2 ? { tokens: measurement2.tokens } : {}
|
|
1243
|
+
};
|
|
1244
|
+
const last = samples.at(-1);
|
|
1245
|
+
if (!last || at > last.at)
|
|
1246
|
+
next = [...samples, sample].slice(-96);
|
|
1247
|
+
else if (at === last.at && sample.tokens !== last.tokens)
|
|
1248
|
+
next = [...samples.slice(0, -1), sample];
|
|
1249
|
+
}
|
|
1250
|
+
return { sessionId, model, capacity, samples: next };
|
|
1251
|
+
}
|
|
1252
|
+
function contextChart(samples, capacity, trigger, width) {
|
|
1253
|
+
if (capacity === undefined || !Number.isFinite(capacity) || capacity <= 0 || width < 4)
|
|
1254
|
+
return [];
|
|
1255
|
+
const height = 8;
|
|
1256
|
+
const gutter = width >= 16 ? 4 : 0;
|
|
1257
|
+
const size = Math.max(1, Math.min(96, width - gutter - 2));
|
|
1258
|
+
const values = samples.slice(-size);
|
|
1259
|
+
const offset = size - values.length;
|
|
1260
|
+
const thresholdRow = trigger !== undefined && Number.isFinite(trigger) && trigger >= 0 && trigger <= capacity ? Math.round((1 - trigger / capacity) * (height - 1)) : -1;
|
|
1261
|
+
const blocks = " \u2581\u2582\u2583\u2584\u2585\u2586\u2587\u2588";
|
|
1262
|
+
const lines = Array.from({ length: height }, (_, row2) => {
|
|
1263
|
+
const threshold = row2 === thresholdRow;
|
|
1264
|
+
const axis = gutter ? (row2 === 0 ? "100%" : row2 === height - 1 ? "0%" : "").padStart(gutter) : "";
|
|
1265
|
+
const plot = Array.from({ length: size }, (_2, col) => {
|
|
1266
|
+
const value = values[col - offset]?.tokens;
|
|
1267
|
+
const fill = value === undefined ? 0 : Math.max(0, Math.min(8, Math.ceil((Math.min(value / capacity, 1) * height - (height - row2 - 1)) * 8)));
|
|
1268
|
+
if (threshold)
|
|
1269
|
+
return fill > 0 ? "\u2542" : "\u2504";
|
|
1270
|
+
if (value === 0 && row2 === height - 1)
|
|
1271
|
+
return "\u2581";
|
|
1272
|
+
return blocks[fill] ?? " ";
|
|
1273
|
+
}).join("");
|
|
1274
|
+
return { line: `${axis}\u2502${plot}\u2502`, threshold };
|
|
1275
|
+
});
|
|
1276
|
+
lines.push({
|
|
1277
|
+
line: `${" ".repeat(gutter)}\u2514${"\u2500".repeat(size)}\u2518`,
|
|
1278
|
+
threshold: false
|
|
1279
|
+
});
|
|
1280
|
+
return lines;
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1227
1283
|
// src/tui/compact.tsx
|
|
1228
1284
|
import { createTextNode as _$createTextNode } from "@opentui/solid";
|
|
1229
1285
|
import { insertNode as _$insertNode } from "@opentui/solid";
|
|
@@ -1258,7 +1314,7 @@ function progress(ratio, width) {
|
|
|
1258
1314
|
const filled = valid2 ? Math.round(Math.min(1, ratio) * size) : 0;
|
|
1259
1315
|
return clip("\u2588".repeat(filled) + "\u2591".repeat(size - filled) + " " + label, width);
|
|
1260
1316
|
}
|
|
1261
|
-
function mainSections(row2, width, age) {
|
|
1317
|
+
function mainSections(row2, width, age, samples) {
|
|
1262
1318
|
if (!row2)
|
|
1263
1319
|
return [{ tone: "base", lines: wrap("Loading session family...", width) }];
|
|
1264
1320
|
const s = row2.snapshot, m = s?.measurement;
|
|
@@ -1279,7 +1335,14 @@ function mainSections(row2, width, age) {
|
|
|
1279
1335
|
...columns("Context", `${tokens(measured)} / ${tokens(s?.capacityTokens)}`, width)
|
|
1280
1336
|
]
|
|
1281
1337
|
},
|
|
1282
|
-
{
|
|
1338
|
+
...contextChart(samples ?? (m && valid2 ? [{ at: m.at, tokens: m.tokens }] : []), s?.capacityTokens, s?.policy.triggerTokens, width).map(({ line, threshold }) => ({
|
|
1339
|
+
tone: threshold ? "threshold" : "pressure",
|
|
1340
|
+
lines: [line]
|
|
1341
|
+
})),
|
|
1342
|
+
{
|
|
1343
|
+
tone: "base",
|
|
1344
|
+
lines: wrap(`${ratio(s?.capacityTokens) === undefined ? "?" : Math.round((ratio(s?.capacityTokens) ?? 0) * 100)}% used \xB7 recent samples \u2192 now`, width)
|
|
1345
|
+
},
|
|
1283
1346
|
{
|
|
1284
1347
|
tone: "base",
|
|
1285
1348
|
lines: [
|
|
@@ -1290,7 +1353,8 @@ function mainSections(row2, width, age) {
|
|
|
1290
1353
|
{
|
|
1291
1354
|
tone: "pressure",
|
|
1292
1355
|
lines: [
|
|
1293
|
-
|
|
1356
|
+
...wrap(`${ratio(s?.policy.triggerTokens) === undefined ? "?" : Math.round((ratio(s?.policy.triggerTokens) ?? 0) * 100)}% of threshold \xB7 \u2504 trigger`, width),
|
|
1357
|
+
...s?.capacityTokens !== undefined && s.policy.triggerTokens !== undefined && s.policy.triggerTokens > s.capacityTokens ? wrap("Threshold above chart capacity", width) : [],
|
|
1294
1358
|
...wrap(remaining === undefined ? "Compaction distance unknown" : remaining < 0 ? `${tokens(-remaining)} tokens over threshold` : `${tokens(remaining)} tokens until compaction`, width)
|
|
1295
1359
|
]
|
|
1296
1360
|
},
|
|
@@ -1408,6 +1472,7 @@ function CompactSidebar(props) {
|
|
|
1408
1472
|
setCollapsed(new Set);
|
|
1409
1473
|
});
|
|
1410
1474
|
const current = createMemo(() => props.rows.find((r) => r.node.sessionId === props.sessionID));
|
|
1475
|
+
const trace = createMemo((previous) => updateContextTrace(previous, current()));
|
|
1411
1476
|
const tree = createMemo(() => subagentTree(props.rows, props.sessionID, collapsed(), hideCompleted(), byPressure()));
|
|
1412
1477
|
const entries = createMemo(() => new Map(tree().visible.map((entry) => [entry.row.node.sessionId, entry])));
|
|
1413
1478
|
const toggle = (id) => setCollapsed((previous) => {
|
|
@@ -1497,7 +1562,7 @@ function CompactSidebar(props) {
|
|
|
1497
1562
|
});
|
|
1498
1563
|
_$insert(_el$2, _$createComponent(Index, {
|
|
1499
1564
|
get each() {
|
|
1500
|
-
return mainSections(current(), width(), props.state.age(props.sessionID));
|
|
1565
|
+
return mainSections(current(), width(), props.state.age(props.sessionID), trace().samples);
|
|
1501
1566
|
},
|
|
1502
1567
|
children: (section2) => _$createComponent(Index, {
|
|
1503
1568
|
get each() {
|
|
@@ -1506,7 +1571,7 @@ function CompactSidebar(props) {
|
|
|
1506
1571
|
children: (line) => (() => {
|
|
1507
1572
|
var _el$13 = _$createElement("text");
|
|
1508
1573
|
_$insert(_el$13, () => line() || " ");
|
|
1509
|
-
_$effect((_$p) => _$setProp(_el$13, "fg", section2().tone === "execution" ? executionColor(current()) : section2().tone === "pressure" ? pressureColor(current()) : section2().tone === "measurement" ? current()?.freshness === "fresh" ? colors().muted : colors().feedback.warning.base : colors().base, _$p));
|
|
1574
|
+
_$effect((_$p) => _$setProp(_el$13, "fg", section2().tone === "execution" ? executionColor(current()) : section2().tone === "threshold" ? colors().feedback.warning.base : section2().tone === "pressure" ? pressureColor(current()) : section2().tone === "measurement" ? current()?.freshness === "fresh" ? colors().muted : colors().feedback.warning.base : colors().base, _$p));
|
|
1510
1575
|
return _el$13;
|
|
1511
1576
|
})()
|
|
1512
1577
|
})
|
|
@@ -1652,6 +1717,8 @@ function GuardView(props) {
|
|
|
1652
1717
|
onCleanup(() => consumer.release());
|
|
1653
1718
|
const rows = createMemo2(() => props.family ?? props.state.rows([], !props.expanded));
|
|
1654
1719
|
const current = createMemo2(() => rows().find((row2) => row2.node.sessionId === props.sessionID));
|
|
1720
|
+
const trace = createMemo2((previous) => updateContextTrace(previous, props.expanded ? current() : undefined));
|
|
1721
|
+
const chart = createMemo2(() => contextChart(trace().samples, current()?.snapshot?.capacityTokens, current()?.snapshot?.policy.triggerTokens, props.width));
|
|
1655
1722
|
const detail = createMemo2(() => {
|
|
1656
1723
|
const lines = detailLines(current(), props.width, props.expanded, props.state.age(props.sessionID));
|
|
1657
1724
|
if (props.expanded) {
|
|
@@ -1719,26 +1786,48 @@ function GuardView(props) {
|
|
|
1719
1786
|
_$insert2(_el$5, () => clip(`${props.state.connection()} / ${props.state.health()?.enforcement ?? "?"}`, props.width));
|
|
1720
1787
|
_$insert2(_el$, _$createComponent2(For2, {
|
|
1721
1788
|
get each() {
|
|
1722
|
-
return
|
|
1789
|
+
return chart();
|
|
1723
1790
|
},
|
|
1724
|
-
children: (
|
|
1791
|
+
children: (item) => (() => {
|
|
1725
1792
|
var _el$1 = _$createElement2("text");
|
|
1726
|
-
_$insert2(_el$1, line);
|
|
1727
|
-
_$effect2((_$p) => _$setProp2(_el$1, "fg", props.context.theme.text.base, _$p));
|
|
1793
|
+
_$insert2(_el$1, () => item.line);
|
|
1794
|
+
_$effect2((_$p) => _$setProp2(_el$1, "fg", item.threshold ? props.context.theme.text.feedback.warning.base : props.context.theme.text.muted, _$p));
|
|
1728
1795
|
return _el$1;
|
|
1729
1796
|
})()
|
|
1730
1797
|
}), _el$6);
|
|
1731
1798
|
_$insert2(_el$, _$createComponent2(For2, {
|
|
1732
1799
|
get each() {
|
|
1733
|
-
return props.
|
|
1800
|
+
return wrap("Recent context samples \u2192 now \xB7 \u2504 trigger", props.width);
|
|
1734
1801
|
},
|
|
1735
|
-
children: (
|
|
1802
|
+
children: (line) => (() => {
|
|
1736
1803
|
var _el$10 = _$createElement2("text");
|
|
1737
|
-
_$insert2(_el$10,
|
|
1738
|
-
_$effect2((_$p) => _$setProp2(_el$10, "fg", props.context.theme.text.
|
|
1804
|
+
_$insert2(_el$10, line);
|
|
1805
|
+
_$effect2((_$p) => _$setProp2(_el$10, "fg", props.context.theme.text.muted, _$p));
|
|
1739
1806
|
return _el$10;
|
|
1740
1807
|
})()
|
|
1741
1808
|
}), _el$6);
|
|
1809
|
+
_$insert2(_el$, _$createComponent2(For2, {
|
|
1810
|
+
get each() {
|
|
1811
|
+
return detail();
|
|
1812
|
+
},
|
|
1813
|
+
children: (line) => (() => {
|
|
1814
|
+
var _el$11 = _$createElement2("text");
|
|
1815
|
+
_$insert2(_el$11, line);
|
|
1816
|
+
_$effect2((_$p) => _$setProp2(_el$11, "fg", props.context.theme.text.base, _$p));
|
|
1817
|
+
return _el$11;
|
|
1818
|
+
})()
|
|
1819
|
+
}), _el$6);
|
|
1820
|
+
_$insert2(_el$, _$createComponent2(For2, {
|
|
1821
|
+
get each() {
|
|
1822
|
+
return props.state.health()?.reasons ?? [];
|
|
1823
|
+
},
|
|
1824
|
+
children: (reason) => (() => {
|
|
1825
|
+
var _el$12 = _$createElement2("text");
|
|
1826
|
+
_$insert2(_el$12, () => clip(reason, props.width));
|
|
1827
|
+
_$effect2((_$p) => _$setProp2(_el$12, "fg", props.context.theme.text.feedback.warning.base, _$p));
|
|
1828
|
+
return _el$12;
|
|
1829
|
+
})()
|
|
1830
|
+
}), _el$6);
|
|
1742
1831
|
_$insert2(_el$6, (() => {
|
|
1743
1832
|
var _c$2 = _$memo2(() => props.state.health()?.mode === "observe");
|
|
1744
1833
|
return () => _c$2() ? current()?.pressure === "crossed" ? "Observe: would compact" : "Observe: no admission" : "";
|
|
@@ -1752,9 +1841,9 @@ function GuardView(props) {
|
|
|
1752
1841
|
return visible();
|
|
1753
1842
|
},
|
|
1754
1843
|
children: (row2) => (() => {
|
|
1755
|
-
var _el$
|
|
1756
|
-
_$setProp2(_el$
|
|
1757
|
-
_$setProp2(_el$
|
|
1844
|
+
var _el$13 = _$createElement2("box");
|
|
1845
|
+
_$setProp2(_el$13, "flexDirection", "column");
|
|
1846
|
+
_$setProp2(_el$13, "onMouseDown", () => {
|
|
1758
1847
|
if (row2.hidden)
|
|
1759
1848
|
props.state.expand(row2.node.sessionId);
|
|
1760
1849
|
else {
|
|
@@ -1766,19 +1855,19 @@ function GuardView(props) {
|
|
|
1766
1855
|
});
|
|
1767
1856
|
}
|
|
1768
1857
|
});
|
|
1769
|
-
_$insert2(_el$
|
|
1858
|
+
_$insert2(_el$13, _$createComponent2(For2, {
|
|
1770
1859
|
get each() {
|
|
1771
1860
|
return rowLines(row2, Math.max(1, props.width - (props.expanded ? 2 : 0))).map((line) => props.expanded ? `${row2.node.sessionId === props.selectedId ? ">" : " "} ${line}` : line);
|
|
1772
1861
|
},
|
|
1773
1862
|
children: (line) => (() => {
|
|
1774
|
-
var _el$
|
|
1775
|
-
_$insert2(_el$
|
|
1776
|
-
_$effect2((_$p) => _$setProp2(_el$
|
|
1777
|
-
return _el$
|
|
1863
|
+
var _el$14 = _$createElement2("text");
|
|
1864
|
+
_$insert2(_el$14, line);
|
|
1865
|
+
_$effect2((_$p) => _$setProp2(_el$14, "fg", row2.pressure === "crossed" ? props.context.theme.text.feedback.error.base : row2.pressure === "near" ? props.context.theme.text.feedback.warning.base : props.context.theme.text.base, _$p));
|
|
1866
|
+
return _el$14;
|
|
1778
1867
|
})()
|
|
1779
1868
|
}));
|
|
1780
|
-
_$effect2((_$p) => _$setProp2(_el$
|
|
1781
|
-
return _el$
|
|
1869
|
+
_$effect2((_$p) => _$setProp2(_el$13, "id", `context-guard.row.${row2.node.sessionId}`, _$p));
|
|
1870
|
+
return _el$13;
|
|
1782
1871
|
})()
|
|
1783
1872
|
}), _el$0);
|
|
1784
1873
|
_$setProp2(_el$0, "onMouseDown", () => props.context.ui.panel.open("context-guard.monitor"));
|
|
@@ -1810,18 +1899,18 @@ function GuardView(props) {
|
|
|
1810
1899
|
|
|
1811
1900
|
// src/tui/panel.tsx
|
|
1812
1901
|
import { effect as _$effect4 } from "@opentui/solid";
|
|
1813
|
-
import { createTextNode as _$createTextNode3 } from "@opentui/solid";
|
|
1814
|
-
import { insertNode as _$insertNode3 } from "@opentui/solid";
|
|
1815
1902
|
import { memo as _$memo3 } from "@opentui/solid";
|
|
1903
|
+
import { insertNode as _$insertNode4 } from "@opentui/solid";
|
|
1816
1904
|
import { insert as _$insert4 } from "@opentui/solid";
|
|
1817
1905
|
import { createComponent as _$createComponent4 } from "@opentui/solid";
|
|
1818
|
-
import { setProp as _$setProp4 } from "@opentui/solid";
|
|
1819
1906
|
import { use as _$use } from "@opentui/solid";
|
|
1907
|
+
import { setProp as _$setProp4 } from "@opentui/solid";
|
|
1820
1908
|
import { createElement as _$createElement4 } from "@opentui/solid";
|
|
1821
|
-
import { createSignal as createSignal4, createMemo as createMemo3, createEffect as createEffect4, onCleanup as onCleanup3, Show as Show3 } from "solid-js";
|
|
1909
|
+
import { createSignal as createSignal4, createMemo as createMemo3, createEffect as createEffect4, onCleanup as onCleanup3, Show as Show3, For as For4 } from "solid-js";
|
|
1822
1910
|
|
|
1823
1911
|
// src/tui/charts.tsx
|
|
1824
1912
|
import { effect as _$effect3 } from "@opentui/solid";
|
|
1913
|
+
import { insertNode as _$insertNode3 } from "@opentui/solid";
|
|
1825
1914
|
import { insert as _$insert3 } from "@opentui/solid";
|
|
1826
1915
|
import { createComponent as _$createComponent3 } from "@opentui/solid";
|
|
1827
1916
|
import { setProp as _$setProp3 } from "@opentui/solid";
|
|
@@ -1829,7 +1918,7 @@ import { createElement as _$createElement3 } from "@opentui/solid";
|
|
|
1829
1918
|
import { createEffect as createEffect3, createSignal as createSignal3, onCleanup as onCleanup2, For as For3 } from "solid-js";
|
|
1830
1919
|
|
|
1831
1920
|
// src/tui/chartformat.ts
|
|
1832
|
-
function
|
|
1921
|
+
function columns2(points, from, to, width, gaps = []) {
|
|
1833
1922
|
const size = Math.max(1, Math.min(120, Math.floor(width))), values = Array.from({ length: size });
|
|
1834
1923
|
const column = (time) => Math.max(0, Math.min(size - 1, Math.floor((time - from) / Math.max(1, to - from) * (size - 1))));
|
|
1835
1924
|
let min = Infinity, max = -Infinity;
|
|
@@ -1841,6 +1930,13 @@ function sparkline(points, from, to, width, gaps = []) {
|
|
|
1841
1930
|
const i = column(point.time);
|
|
1842
1931
|
values[i] = Math.max(values[i] ?? -Infinity, point.value);
|
|
1843
1932
|
}
|
|
1933
|
+
for (const time of gaps)
|
|
1934
|
+
if (time >= from && time <= to)
|
|
1935
|
+
values[column(time)] = undefined;
|
|
1936
|
+
return { values, column, min, max };
|
|
1937
|
+
}
|
|
1938
|
+
function sparkline(points, from, to, width, gaps = []) {
|
|
1939
|
+
const { values, column, min, max } = columns2(points, from, to, width, gaps);
|
|
1844
1940
|
const bars = "\u2581\u2582\u2583\u2584\u2585\u2586\u2587\u2588";
|
|
1845
1941
|
const chars = values.map((v) => v === undefined ? " " : bars[Math.min(7, Math.floor(v / Math.max(1, max) * 7))] ?? "\u2581");
|
|
1846
1942
|
for (const time of gaps)
|
|
@@ -1848,20 +1944,58 @@ function sparkline(points, from, to, width, gaps = []) {
|
|
|
1848
1944
|
chars[column(time)] = "\u2506";
|
|
1849
1945
|
return { line: chars.join(""), ...min === Infinity ? {} : { min, max } };
|
|
1850
1946
|
}
|
|
1851
|
-
|
|
1947
|
+
var chartMetrics = [
|
|
1948
|
+
{ label: "Live TPS ~", unit: "t/s", key: "liveTps", source: "points" },
|
|
1949
|
+
{ label: "Avg model", unit: "t/s", key: "averageModelTps", source: "points" },
|
|
1950
|
+
{
|
|
1951
|
+
label: "Avg session",
|
|
1952
|
+
unit: "t/s",
|
|
1953
|
+
key: "averageSessionTps",
|
|
1954
|
+
source: "points"
|
|
1955
|
+
},
|
|
1956
|
+
{ label: "TTFT ~", unit: "s", key: "ttftMs", source: "requests" },
|
|
1957
|
+
{ label: "Request throughput", unit: "t/s", key: "tps", source: "requests" },
|
|
1958
|
+
{
|
|
1959
|
+
label: "Cumulative output",
|
|
1960
|
+
unit: "tokens",
|
|
1961
|
+
key: "sessionOutputTokens",
|
|
1962
|
+
source: "points"
|
|
1963
|
+
}
|
|
1964
|
+
];
|
|
1965
|
+
function metricPlot(points, from, to, width, gaps = []) {
|
|
1966
|
+
const label = (value) => value >= 1e6 ? `${(value / 1e6).toFixed(1)}M` : value >= 1000 ? `${(value / 1000).toFixed(1)}k` : value.toFixed(1);
|
|
1967
|
+
const peak = Math.max(0, ...points.filter((p) => p.time >= from && p.time <= to && Number.isFinite(p.value)).map((p) => p.value ?? 0));
|
|
1968
|
+
const scale = Math.max(1, peak);
|
|
1969
|
+
const gutter = width >= 20 ? Math.max(label(scale).length, 3) : 0;
|
|
1970
|
+
const { values, column } = columns2(points, from, to, Math.max(1, width - gutter - 2), gaps);
|
|
1971
|
+
const gapColumns = new Set(gaps.filter((time) => time >= from && time <= to).map(column));
|
|
1972
|
+
const height = 6;
|
|
1973
|
+
const blocks = " \u2581\u2582\u2583\u2584\u2585\u2586\u2587\u2588";
|
|
1974
|
+
const lines = Array.from({ length: height }, (_, row2) => {
|
|
1975
|
+
const axis = gutter ? (row2 === 0 ? label(scale) : row2 === height - 1 ? "0.0" : "").padStart(gutter) : "";
|
|
1976
|
+
return `${axis}\u2502${values.map((value, i) => {
|
|
1977
|
+
if (gapColumns.has(i))
|
|
1978
|
+
return "\u2506";
|
|
1979
|
+
if (value === undefined)
|
|
1980
|
+
return " ";
|
|
1981
|
+
const fill = Math.max(0, Math.min(8, Math.ceil((value / scale * height - (height - row2 - 1)) * 8)));
|
|
1982
|
+
return value === 0 && row2 === height - 1 ? "\u2581" : blocks[fill];
|
|
1983
|
+
}).join("")}\u2502`;
|
|
1984
|
+
});
|
|
1985
|
+
lines.push(`${" ".repeat(gutter)}\u2514${"\u2500".repeat(values.length)}\u2518`);
|
|
1986
|
+
const start = new Date(from).toISOString().slice(11, 16), end = new Date(to).toISOString().slice(11, 16);
|
|
1987
|
+
const axisWidth = values.length + 2;
|
|
1988
|
+
if (axisWidth >= 11)
|
|
1989
|
+
lines.push(`${" ".repeat(gutter)}${start}${" ".repeat(axisWidth - 10)}${end}`);
|
|
1990
|
+
return lines;
|
|
1991
|
+
}
|
|
1992
|
+
function historyChartLines(result, width, from, to, metricIndex = 0) {
|
|
1852
1993
|
if (!result.enabled)
|
|
1853
1994
|
return wrap("History recording is disabled on this server.", width);
|
|
1854
1995
|
const series = result.data.series[0];
|
|
1855
1996
|
if (!series)
|
|
1856
1997
|
return wrap("No retained measurements in this window.", width);
|
|
1857
1998
|
const gaps = result.data.gaps.map((g) => g.time);
|
|
1858
|
-
const chart = (label, unit, points) => {
|
|
1859
|
-
const plotted = sparkline(points, from, to, width - 2, gaps);
|
|
1860
|
-
return [
|
|
1861
|
-
...wrap(`${label}${plotted.min === undefined ? ": unavailable" : ` \xB7 ${plotted.min.toFixed(1)}\u2013${plotted.max?.toFixed(1)} ${unit}`}`, width),
|
|
1862
|
-
`\u2502${plotted.line}\u2502`
|
|
1863
|
-
];
|
|
1864
|
-
};
|
|
1865
1999
|
const observations = (key) => series.points.map((p) => ({
|
|
1866
2000
|
time: p.time,
|
|
1867
2001
|
...p[key] === undefined ? {} : { value: p[key] }
|
|
@@ -1870,20 +2004,17 @@ function historyChartLines(result, width, from, to) {
|
|
|
1870
2004
|
time: p.time,
|
|
1871
2005
|
...p[key] === undefined ? {} : { value: key === "ttftMs" ? p[key] / 1000 : p[key] }
|
|
1872
2006
|
}));
|
|
2007
|
+
const metric2 = chartMetrics[metricIndex] ?? chartMetrics[0];
|
|
2008
|
+
const points = metric2.source === "points" ? observations(metric2.key) : requests(metric2.key);
|
|
2009
|
+
const plotted = sparkline(points, from, to, width - 2, gaps);
|
|
1873
2010
|
return [
|
|
1874
|
-
...
|
|
1875
|
-
"",
|
|
1876
|
-
...chart("Avg model", "t/s", observations("averageModelTps")),
|
|
1877
|
-
"",
|
|
1878
|
-
...chart("Avg session", "t/s", observations("averageSessionTps")),
|
|
1879
|
-
"",
|
|
1880
|
-
...chart("TTFT ~", "s", requests("ttftMs")),
|
|
1881
|
-
"",
|
|
1882
|
-
...chart("Request throughput", "t/s", requests("tps")),
|
|
2011
|
+
...wrap(`\u2039 ${metric2.label} \u203A \xB7 ${metricIndex + 1}/${chartMetrics.length}`, width),
|
|
2012
|
+
...wrap(plotted.min === undefined ? "No measurements for this metric." : `${plotted.min.toFixed(1)}\u2013${plotted.max?.toFixed(1)} ${metric2.unit} \xB7 observed range`, width),
|
|
1883
2013
|
"",
|
|
1884
|
-
...
|
|
2014
|
+
...metricPlot(points, from, to, width, gaps),
|
|
1885
2015
|
"",
|
|
1886
|
-
...wrap("UTC time \u2192 \xB7
|
|
2016
|
+
...wrap("UTC time \u2192 \xB7 peaks per column", width),
|
|
2017
|
+
...wrap("blank = no sample \xB7 \u2506 = gap", width),
|
|
1887
2018
|
...wrap(`${new Date(from).toISOString()} \u2192 ${new Date(to).toISOString()}`, width),
|
|
1888
2019
|
...series.points.some((p) => p.partial) ? wrap("Partial timing coverage; averages include only matched model durations.", width) : [],
|
|
1889
2020
|
...result.truncated ? wrap("Downsampled/bounded history; some older requests or gap markers may be omitted.", width) : [],
|
|
@@ -1940,44 +2071,47 @@ function HistoryCharts(props) {
|
|
|
1940
2071
|
});
|
|
1941
2072
|
const lines = () => {
|
|
1942
2073
|
const current = result();
|
|
1943
|
-
return current ? historyChartLines(current, Math.max(4, props.width), range().from, range().to) : [];
|
|
2074
|
+
return current ? historyChartLines(current, Math.max(4, props.width), range().from, range().to, props.metricIndex) : [];
|
|
1944
2075
|
};
|
|
1945
2076
|
return (() => {
|
|
1946
|
-
var _el$ = _$createElement3("box");
|
|
2077
|
+
var _el$ = _$createElement3("box"), _el$2 = _$createElement3("box");
|
|
2078
|
+
_$insertNode3(_el$, _el$2);
|
|
1947
2079
|
_$setProp3(_el$, "flexDirection", "column");
|
|
1948
2080
|
_$insert3(_el$, _$createComponent3(For3, {
|
|
1949
2081
|
get each() {
|
|
1950
2082
|
return wrap(`Performance charts \xB7 ${props.sessionId}`, props.width);
|
|
1951
2083
|
},
|
|
1952
2084
|
children: (line) => (() => {
|
|
1953
|
-
var _el$
|
|
1954
|
-
_$insert3(_el$
|
|
1955
|
-
_$effect3((_$p) => _$setProp3(_el$
|
|
1956
|
-
return _el$
|
|
2085
|
+
var _el$3 = _$createElement3("text");
|
|
2086
|
+
_$insert3(_el$3, line);
|
|
2087
|
+
_$effect3((_$p) => _$setProp3(_el$3, "fg", props.context.theme.text.base, _$p));
|
|
2088
|
+
return _el$3;
|
|
1957
2089
|
})()
|
|
1958
|
-
}),
|
|
2090
|
+
}), _el$2);
|
|
1959
2091
|
_$insert3(_el$, _$createComponent3(For3, {
|
|
1960
2092
|
get each() {
|
|
1961
2093
|
return wrap(status(), props.width);
|
|
1962
2094
|
},
|
|
1963
2095
|
children: (line) => (() => {
|
|
1964
|
-
var _el$
|
|
1965
|
-
_$insert3(_el$
|
|
1966
|
-
_$effect3((_$p) => _$setProp3(_el$
|
|
1967
|
-
return _el$
|
|
2096
|
+
var _el$4 = _$createElement3("text");
|
|
2097
|
+
_$insert3(_el$4, line);
|
|
2098
|
+
_$effect3((_$p) => _$setProp3(_el$4, "fg", props.context.theme.text.muted, _$p));
|
|
2099
|
+
return _el$4;
|
|
1968
2100
|
})()
|
|
1969
|
-
}),
|
|
1970
|
-
_$
|
|
2101
|
+
}), _el$2);
|
|
2102
|
+
_$setProp3(_el$2, "flexDirection", "column");
|
|
2103
|
+
_$setProp3(_el$2, "paddingTop", 1);
|
|
2104
|
+
_$insert3(_el$2, _$createComponent3(For3, {
|
|
1971
2105
|
get each() {
|
|
1972
2106
|
return lines();
|
|
1973
2107
|
},
|
|
1974
2108
|
children: (line) => (() => {
|
|
1975
|
-
var _el$
|
|
1976
|
-
_$insert3(_el$
|
|
1977
|
-
_$effect3((_$p) => _$setProp3(_el$
|
|
1978
|
-
return _el$
|
|
2109
|
+
var _el$5 = _$createElement3("text");
|
|
2110
|
+
_$insert3(_el$5, line || " ");
|
|
2111
|
+
_$effect3((_$p) => _$setProp3(_el$5, "fg", props.context.theme.text.base, _$p));
|
|
2112
|
+
return _el$5;
|
|
1979
2113
|
})()
|
|
1980
|
-
})
|
|
2114
|
+
}));
|
|
1981
2115
|
return _el$;
|
|
1982
2116
|
})();
|
|
1983
2117
|
}
|
|
@@ -1985,6 +2119,8 @@ function HistoryCharts(props) {
|
|
|
1985
2119
|
// src/tui/panel.tsx
|
|
1986
2120
|
function GuardPanel(props) {
|
|
1987
2121
|
const [showCharts, setShowCharts] = createSignal4(false);
|
|
2122
|
+
const [metricIndex, setMetricIndex] = createSignal4(0);
|
|
2123
|
+
const width = () => Math.max(4, props.input.width - 4);
|
|
1988
2124
|
const windows = [300000, 3600000, 86400000, 7 * 86400000];
|
|
1989
2125
|
const [windowIndex, setWindowIndex] = createSignal4(1);
|
|
1990
2126
|
const [chartRefresh, setChartRefresh] = createSignal4(0);
|
|
@@ -2034,6 +2170,27 @@ function GuardPanel(props) {
|
|
|
2034
2170
|
props.context.keymap.layer(() => ({
|
|
2035
2171
|
enabled: () => props.input.focused,
|
|
2036
2172
|
commands: [{
|
|
2173
|
+
id: "context-guard.close",
|
|
2174
|
+
title: "Close Context Guard",
|
|
2175
|
+
bind: "q",
|
|
2176
|
+
run: () => props.input.close()
|
|
2177
|
+
}, {
|
|
2178
|
+
id: "context-guard.chart-previous",
|
|
2179
|
+
title: "Previous chart metric",
|
|
2180
|
+
bind: "left",
|
|
2181
|
+
run: () => {
|
|
2182
|
+
if (showCharts())
|
|
2183
|
+
setMetricIndex((i) => (i + chartMetrics.length - 1) % chartMetrics.length);
|
|
2184
|
+
}
|
|
2185
|
+
}, {
|
|
2186
|
+
id: "context-guard.chart-next",
|
|
2187
|
+
title: "Next chart metric",
|
|
2188
|
+
bind: "right",
|
|
2189
|
+
run: () => {
|
|
2190
|
+
if (showCharts())
|
|
2191
|
+
setMetricIndex((i) => (i + 1) % chartMetrics.length);
|
|
2192
|
+
}
|
|
2193
|
+
}, {
|
|
2037
2194
|
id: "context-guard.charts",
|
|
2038
2195
|
title: "Toggle performance charts",
|
|
2039
2196
|
bind: "c",
|
|
@@ -2104,20 +2261,26 @@ function GuardPanel(props) {
|
|
|
2104
2261
|
}
|
|
2105
2262
|
}]
|
|
2106
2263
|
}));
|
|
2264
|
+
const hints = () => showCharts() ? ["c back \xB7 q close", "\u2190 / \u2192 metric \xB7 \u2191 / \u2193 session", `[ / ] window \xB7 ${["5 minutes", "1 hour", "24 hours", "7 days"][windowIndex()]}`, "r refresh \xB7 Enter open session"] : ["c charts \xB7 q close", `\u2191 / \u2193 select ${offset() + 1} \xB7 Enter open`, "space expand \xB7 i inspect", "r refresh \xB7 l load more"];
|
|
2107
2265
|
return (() => {
|
|
2108
|
-
var _el$ = _$createElement4("
|
|
2109
|
-
_$
|
|
2110
|
-
_$
|
|
2111
|
-
_$
|
|
2112
|
-
_$
|
|
2113
|
-
_$
|
|
2114
|
-
_$
|
|
2266
|
+
var _el$ = _$createElement4("box"), _el$2 = _$createElement4("scrollbox"), _el$3 = _$createElement4("box"), _el$4 = _$createElement4("text"), _el$5 = _$createElement4("box");
|
|
2267
|
+
_$insertNode4(_el$, _el$2);
|
|
2268
|
+
_$insertNode4(_el$, _el$5);
|
|
2269
|
+
_$setProp4(_el$, "flexDirection", "column");
|
|
2270
|
+
_$setProp4(_el$, "flexGrow", 1);
|
|
2271
|
+
_$setProp4(_el$, "paddingLeft", 2);
|
|
2272
|
+
_$setProp4(_el$, "paddingRight", 2);
|
|
2273
|
+
_$setProp4(_el$, "paddingTop", 1);
|
|
2274
|
+
_$setProp4(_el$, "paddingBottom", 1);
|
|
2275
|
+
_$insertNode4(_el$2, _el$3);
|
|
2115
2276
|
_$use((value) => {
|
|
2116
2277
|
scroll = value;
|
|
2117
|
-
}, _el$);
|
|
2118
|
-
_$setProp4(_el
|
|
2119
|
-
_$setProp4(_el$2, "
|
|
2120
|
-
_$
|
|
2278
|
+
}, _el$2);
|
|
2279
|
+
_$setProp4(_el$2, "flexGrow", 1);
|
|
2280
|
+
_$setProp4(_el$2, "minHeight", 0);
|
|
2281
|
+
_$insertNode4(_el$3, _el$4);
|
|
2282
|
+
_$setProp4(_el$3, "flexDirection", "column");
|
|
2283
|
+
_$insert4(_el$3, _$createComponent4(GuardView, {
|
|
2121
2284
|
get sessionID() {
|
|
2122
2285
|
return props.input.sessionID;
|
|
2123
2286
|
},
|
|
@@ -2128,7 +2291,7 @@ function GuardPanel(props) {
|
|
|
2128
2291
|
return props.context;
|
|
2129
2292
|
},
|
|
2130
2293
|
get width() {
|
|
2131
|
-
return
|
|
2294
|
+
return width();
|
|
2132
2295
|
},
|
|
2133
2296
|
expanded: true,
|
|
2134
2297
|
get family() {
|
|
@@ -2140,24 +2303,28 @@ function GuardPanel(props) {
|
|
|
2140
2303
|
get inspectId() {
|
|
2141
2304
|
return inspectId();
|
|
2142
2305
|
}
|
|
2143
|
-
}));
|
|
2144
|
-
_$insert4(_el
|
|
2306
|
+
}), _el$4);
|
|
2307
|
+
_$insert4(_el$4, () => wrap("Guard input = prompt + caches. Native Context uses different totals.", width()).join("\n"));
|
|
2308
|
+
_$insert4(_el$2, _$createComponent4(Show3, {
|
|
2145
2309
|
get when() {
|
|
2146
2310
|
return showCharts();
|
|
2147
2311
|
},
|
|
2148
2312
|
get children() {
|
|
2149
|
-
return
|
|
2313
|
+
return _$createComponent4(HistoryCharts, {
|
|
2150
2314
|
get sessionId() {
|
|
2151
2315
|
return selected();
|
|
2152
2316
|
},
|
|
2153
2317
|
get windowMs() {
|
|
2154
2318
|
return windows[windowIndex()] ?? 3600000;
|
|
2155
2319
|
},
|
|
2320
|
+
get metricIndex() {
|
|
2321
|
+
return metricIndex();
|
|
2322
|
+
},
|
|
2156
2323
|
get refresh() {
|
|
2157
2324
|
return chartRefresh();
|
|
2158
2325
|
},
|
|
2159
2326
|
get width() {
|
|
2160
|
-
return
|
|
2327
|
+
return width();
|
|
2161
2328
|
},
|
|
2162
2329
|
get state() {
|
|
2163
2330
|
return props.state;
|
|
@@ -2165,38 +2332,31 @@ function GuardPanel(props) {
|
|
|
2165
2332
|
get context() {
|
|
2166
2333
|
return props.context;
|
|
2167
2334
|
}
|
|
2168
|
-
})
|
|
2169
|
-
var _el$3 = _$createElement4("text"), _el$4 = _$createTextNode3(`[ / ] window \xB7 `);
|
|
2170
|
-
_$insertNode3(_el$3, _el$4);
|
|
2171
|
-
_$insert4(_el$3, () => ["5 minutes", "1 hour", "24 hours", "7 days"][windowIndex()], null);
|
|
2172
|
-
_$effect4((_$p) => _$setProp4(_el$3, "fg", props.context.theme.text.muted, _$p));
|
|
2173
|
-
return _el$3;
|
|
2174
|
-
})()];
|
|
2335
|
+
});
|
|
2175
2336
|
}
|
|
2176
|
-
}),
|
|
2177
|
-
_$
|
|
2178
|
-
_$
|
|
2179
|
-
_$
|
|
2180
|
-
_$insert4(_el$
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2337
|
+
}), null);
|
|
2338
|
+
_$setProp4(_el$5, "flexDirection", "column");
|
|
2339
|
+
_$setProp4(_el$5, "flexShrink", 0);
|
|
2340
|
+
_$setProp4(_el$5, "paddingTop", 1);
|
|
2341
|
+
_$insert4(_el$5, _$createComponent4(For4, {
|
|
2342
|
+
get each() {
|
|
2343
|
+
return hints().flatMap((hint) => wrap(hint, width()));
|
|
2344
|
+
},
|
|
2345
|
+
children: (line) => (() => {
|
|
2346
|
+
var _el$6 = _$createElement4("text");
|
|
2347
|
+
_$insert4(_el$6, line);
|
|
2348
|
+
_$effect4((_$p) => _$setProp4(_el$6, "fg", props.context.theme.text.muted, _$p));
|
|
2349
|
+
return _el$6;
|
|
2350
|
+
})()
|
|
2351
|
+
}));
|
|
2184
2352
|
_$effect4((_p$) => {
|
|
2185
|
-
var _v$ = !showCharts(), _v$2 = props.context.theme.text.muted
|
|
2186
|
-
_v$ !== _p$.e && (_p$.e = _$setProp4(_el$
|
|
2187
|
-
_v$2 !== _p$.t && (_p$.t = _$setProp4(_el$
|
|
2188
|
-
_v$3 !== _p$.a && (_p$.a = _$setProp4(_el$8, "fg", _v$3, _p$.a));
|
|
2189
|
-
_v$4 !== _p$.o && (_p$.o = _$setProp4(_el$1, "fg", _v$4, _p$.o));
|
|
2190
|
-
_v$5 !== _p$.i && (_p$.i = _$setProp4(_el$11, "fg", _v$5, _p$.i));
|
|
2191
|
-
_v$6 !== _p$.n && (_p$.n = _$setProp4(_el$13, "fg", _v$6, _p$.n));
|
|
2353
|
+
var _v$ = !showCharts(), _v$2 = props.context.theme.text.muted;
|
|
2354
|
+
_v$ !== _p$.e && (_p$.e = _$setProp4(_el$3, "visible", _v$, _p$.e));
|
|
2355
|
+
_v$2 !== _p$.t && (_p$.t = _$setProp4(_el$4, "fg", _v$2, _p$.t));
|
|
2192
2356
|
return _p$;
|
|
2193
2357
|
}, {
|
|
2194
2358
|
e: undefined,
|
|
2195
|
-
t: undefined
|
|
2196
|
-
a: undefined,
|
|
2197
|
-
o: undefined,
|
|
2198
|
-
i: undefined,
|
|
2199
|
-
n: undefined
|
|
2359
|
+
t: undefined
|
|
2200
2360
|
});
|
|
2201
2361
|
return _el$;
|
|
2202
2362
|
})();
|
|
@@ -2245,7 +2405,7 @@ var tui_default = Plugin.define({
|
|
|
2245
2405
|
},
|
|
2246
2406
|
get children() {
|
|
2247
2407
|
var _el$ = _$createElement5("text");
|
|
2248
|
-
_$
|
|
2408
|
+
_$insertNode5(_el$, _$createTextNode3(`Context Guard: open panel`));
|
|
2249
2409
|
_$setProp5(_el$, "onMouseDown", () => context.ui.panel.open("context-guard.monitor"));
|
|
2250
2410
|
_$effect5((_$p) => _$setProp5(_el$, "fg", context.theme.text.muted, _$p));
|
|
2251
2411
|
return _el$;
|
package/dist/types/adapter.d.ts
CHANGED
|
@@ -12,7 +12,7 @@ export interface Host {
|
|
|
12
12
|
location: {
|
|
13
13
|
directory: string;
|
|
14
14
|
};
|
|
15
|
-
session: Pick<Plugin.Context["session"], "get" | "compact" | "hook"
|
|
15
|
+
session: Pick<Plugin.Context["session"], "get" | "compact" | "hook"> & Partial<Pick<Plugin.Context["session"], "context">>;
|
|
16
16
|
model: Pick<Plugin.Context["model"], "list">;
|
|
17
17
|
event: Plugin.Context["event"];
|
|
18
18
|
storage: Pick<Plugin.Context["storage"], "get" | "set">;
|
|
@@ -4,4 +4,4 @@ export interface SnapshotRpcPort {
|
|
|
4
4
|
}
|
|
5
5
|
import { monitorRpc, performanceRpc } from "./rpc.js";
|
|
6
6
|
import type { SnapshotStore } from "./snapshot.js";
|
|
7
|
-
export declare function serveSnapshots(rpc: SnapshotRpcPort | undefined, store: SnapshotStore, performance?: boolean): Promise<() => void>;
|
|
7
|
+
export declare function serveSnapshots(rpc: SnapshotRpcPort | undefined, store: SnapshotStore, performance?: boolean, prepareRead?: (ids: readonly string[], signal: AbortSignal) => Promise<void>): Promise<() => void>;
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import type { HistoryChartResult } from "../historycharts.js";
|
|
2
|
-
/** Each cell contains an observed peak, never an interpolated or invented zero. */
|
|
3
2
|
export declare function sparkline(points: readonly {
|
|
4
3
|
time: number;
|
|
5
4
|
value?: number;
|
|
@@ -8,4 +7,40 @@ export declare function sparkline(points: readonly {
|
|
|
8
7
|
min?: number;
|
|
9
8
|
max?: number;
|
|
10
9
|
};
|
|
11
|
-
export declare
|
|
10
|
+
export declare const chartMetrics: readonly [{
|
|
11
|
+
readonly label: "Live TPS ~";
|
|
12
|
+
readonly unit: "t/s";
|
|
13
|
+
readonly key: "liveTps";
|
|
14
|
+
readonly source: "points";
|
|
15
|
+
}, {
|
|
16
|
+
readonly label: "Avg model";
|
|
17
|
+
readonly unit: "t/s";
|
|
18
|
+
readonly key: "averageModelTps";
|
|
19
|
+
readonly source: "points";
|
|
20
|
+
}, {
|
|
21
|
+
readonly label: "Avg session";
|
|
22
|
+
readonly unit: "t/s";
|
|
23
|
+
readonly key: "averageSessionTps";
|
|
24
|
+
readonly source: "points";
|
|
25
|
+
}, {
|
|
26
|
+
readonly label: "TTFT ~";
|
|
27
|
+
readonly unit: "s";
|
|
28
|
+
readonly key: "ttftMs";
|
|
29
|
+
readonly source: "requests";
|
|
30
|
+
}, {
|
|
31
|
+
readonly label: "Request throughput";
|
|
32
|
+
readonly unit: "t/s";
|
|
33
|
+
readonly key: "tps";
|
|
34
|
+
readonly source: "requests";
|
|
35
|
+
}, {
|
|
36
|
+
readonly label: "Cumulative output";
|
|
37
|
+
readonly unit: "tokens";
|
|
38
|
+
readonly key: "sessionOutputTokens";
|
|
39
|
+
readonly source: "points";
|
|
40
|
+
}];
|
|
41
|
+
/** Plot only observed column peaks; leave missing data and gaps disconnected. */
|
|
42
|
+
export declare function metricPlot(points: readonly {
|
|
43
|
+
time: number;
|
|
44
|
+
value?: number;
|
|
45
|
+
}[], from: number, to: number, width: number, gaps?: readonly number[]): string[];
|
|
46
|
+
export declare function historyChartLines(result: HistoryChartResult, width: number, from: number, to: number, metricIndex?: number): string[];
|
|
@@ -1,12 +1,13 @@
|
|
|
1
|
+
import { type ContextSample } from "./contextchart.js";
|
|
1
2
|
import type { GuardRow } from "../family.js";
|
|
2
3
|
export declare function tokens(value: number | undefined): string;
|
|
3
4
|
export declare function columns(left: string, right: string, width: number): string[];
|
|
4
5
|
export declare function progress(ratio: number | undefined, width: number): string;
|
|
5
6
|
export interface MainSection {
|
|
6
|
-
readonly tone: "base" | "execution" | "pressure" | "measurement";
|
|
7
|
+
readonly tone: "base" | "execution" | "pressure" | "measurement" | "threshold";
|
|
7
8
|
readonly lines: readonly string[];
|
|
8
9
|
}
|
|
9
|
-
export declare function mainSections(row: GuardRow | undefined, width: number, age: number): MainSection[];
|
|
10
|
+
export declare function mainSections(row: GuardRow | undefined, width: number, age: number, samples?: readonly ContextSample[]): MainSection[];
|
|
10
11
|
export declare function mainLines(row: GuardRow | undefined, width: number, age: number): string[];
|
|
11
12
|
export declare const statusSymbol: {
|
|
12
13
|
readonly running: "●";
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { GuardRow } from "../family.js";
|
|
2
|
+
export interface ContextSample {
|
|
3
|
+
readonly at: number;
|
|
4
|
+
readonly tokens?: number;
|
|
5
|
+
}
|
|
6
|
+
export interface ContextTrace {
|
|
7
|
+
readonly sessionId: string;
|
|
8
|
+
readonly model: string | undefined;
|
|
9
|
+
readonly capacity: number | undefined;
|
|
10
|
+
readonly samples: readonly ContextSample[];
|
|
11
|
+
}
|
|
12
|
+
/** Keep only actual measurements, never copies made by age/render ticks. */
|
|
13
|
+
export declare function updateContextTrace(previous: ContextTrace | undefined, row: GuardRow | undefined): ContextTrace;
|
|
14
|
+
export interface ContextChartLine {
|
|
15
|
+
readonly line: string;
|
|
16
|
+
readonly threshold: boolean;
|
|
17
|
+
}
|
|
18
|
+
/** Fixed capacity scale and observation order; no interpolation across missing samples. */
|
|
19
|
+
export declare function contextChart(samples: readonly ContextSample[], capacity: number | undefined, trigger: number | undefined, width: number): ContextChartLine[];
|