@cesarus777/opencode-cgp 0.1.0 → 0.2.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/dist/tui.js CHANGED
@@ -114,6 +114,154 @@ var monitorRpc = Rpc.define({
114
114
  methods: { read: { input: readSchema, output: resultSchema } },
115
115
  events: { changed: { schema: batchSchema } }
116
116
  });
117
+ var nonnegative = { type: "number", minimum: 0 };
118
+ var performanceSchema = object({
119
+ observedSince: count,
120
+ at: count,
121
+ sessionCreatedAt: count,
122
+ sessionOutputTokens: count,
123
+ status: enumeration("waiting", "streaming", "idle", "unavailable"),
124
+ partial: { type: "boolean" },
125
+ liveTps: nonnegative,
126
+ averageModelTps: nonnegative,
127
+ averageSessionTps: nonnegative,
128
+ ttftMs: nonnegative,
129
+ latestRequestTps: nonnegative,
130
+ requestId: text,
131
+ requestAgent: text,
132
+ requestModel: text,
133
+ requestOutputTokens: count,
134
+ requestStartedAt: count,
135
+ firstOutputAt: count,
136
+ streamEndedAt: count,
137
+ estimatedOutputTokens: nonnegative,
138
+ modelOutputTokens: nonnegative,
139
+ modelDurationMs: nonnegative,
140
+ completedRequests: count,
141
+ tokenSource: enumeration("estimated", "reported", "unavailable")
142
+ }, [
143
+ "sessionCreatedAt",
144
+ "observedSince",
145
+ "at",
146
+ "status",
147
+ "partial",
148
+ "estimatedOutputTokens",
149
+ "modelOutputTokens",
150
+ "modelDurationMs",
151
+ "completedRequests",
152
+ "tokenSource"
153
+ ]);
154
+ var performanceRow = {
155
+ ...row,
156
+ properties: { ...row.properties, performance: performanceSchema }
157
+ };
158
+ var performanceBatchSchema = object({
159
+ ...batchFields,
160
+ upserts: array(performanceRow)
161
+ });
162
+ var performanceResultSchema = object({
163
+ ...batchFields,
164
+ upserts: array(performanceRow),
165
+ unavailable: resultSchema.properties.unavailable ?? {}
166
+ });
167
+ var performanceRpc = Rpc.define({
168
+ id: "context-guard.monitor.v2",
169
+ methods: { read: { input: readSchema, output: performanceResultSchema } },
170
+ events: { changed: { schema: performanceBatchSchema } }
171
+ });
172
+ function isPerformanceBatch(value) {
173
+ return valid(value, performanceBatchSchema) && uniqueRows(value);
174
+ }
175
+ function isPerformanceResult(value) {
176
+ return valid(value, performanceResultSchema) && uniqueRows(value);
177
+ }
178
+ function isPerformanceSnapshot(value) {
179
+ return valid(value, performanceSchema);
180
+ }
181
+ var chartPointSchema = object({
182
+ time: count,
183
+ agent: text,
184
+ model: text,
185
+ breakBefore: { type: "boolean" },
186
+ partial: { type: "boolean" },
187
+ liveTps: nonnegative,
188
+ averageModelTps: nonnegative,
189
+ averageSessionTps: nonnegative,
190
+ sessionOutputTokens: count
191
+ }, ["time", "breakBefore", "partial"]);
192
+ var chartRequestSchema = object({
193
+ time: count,
194
+ requestId: text,
195
+ started: count,
196
+ agent: text,
197
+ model: text,
198
+ ttftMs: nonnegative,
199
+ outputTokens: count,
200
+ durationMs: nonnegative,
201
+ tps: nonnegative,
202
+ partial: { type: "boolean" }
203
+ }, ["time", "requestId", "started", "partial"]);
204
+ var historyReadSchema = object({
205
+ schemaVersion: { const: 1 },
206
+ sessionId: text,
207
+ from: count,
208
+ to: count,
209
+ maxPoints: { type: "integer", minimum: 16, maximum: 500 }
210
+ });
211
+ var historyResultSchema = object({
212
+ schemaVersion: { const: 1 },
213
+ generatedAt: count,
214
+ enabled: { type: "boolean" },
215
+ truncated: { type: "boolean" },
216
+ metadata: object({
217
+ schema: { const: 1 },
218
+ retainedFrom: count,
219
+ retainedBytes: count,
220
+ skippedBytes: count,
221
+ malformedRecords: count,
222
+ completeness: enumeration("bounded-retention")
223
+ }, [
224
+ "schema",
225
+ "retainedBytes",
226
+ "skippedBytes",
227
+ "malformedRecords",
228
+ "completeness"
229
+ ]),
230
+ data: object({
231
+ schema: { const: 1 },
232
+ omittedSessions: count,
233
+ omittedGaps: count,
234
+ maxPoints: count,
235
+ gaps: array(object({ time: count, sessionId: text, reason: text }), 128),
236
+ series: array(object({
237
+ sessionId: text,
238
+ parentId: text,
239
+ agents: array(text, 1024),
240
+ models: array(text, 1024),
241
+ points: array(chartPointSchema, 500),
242
+ requests: array(chartRequestSchema, 500),
243
+ originalPoints: count
244
+ }, [
245
+ "sessionId",
246
+ "agents",
247
+ "models",
248
+ "points",
249
+ "requests",
250
+ "originalPoints"
251
+ ]), 1)
252
+ }, ["schema", "omittedSessions", "maxPoints", "gaps", "series"])
253
+ });
254
+ var historyRpc = Rpc.define({
255
+ id: "context-guard.history.v1",
256
+ methods: { read: { input: historyReadSchema, output: historyResultSchema } },
257
+ events: {}
258
+ });
259
+ function isHistoryRead(value) {
260
+ return valid(value, historyReadSchema) && record(value) && typeof value.from === "number" && typeof value.to === "number" && value.from <= value.to && value.to - value.from <= 7 * 86400000;
261
+ }
262
+ function isHistoryResult(value) {
263
+ return valid(value, historyResultSchema);
264
+ }
117
265
  function record(x) {
118
266
  return typeof x === "object" && x !== null && !Array.isArray(x);
119
267
  }
@@ -164,14 +312,14 @@ function uniqueRows(value) {
164
312
  }
165
313
 
166
314
  // src/tui.tsx
167
- import { effect as _$effect4 } from "@opentui/solid";
315
+ import { effect as _$effect5 } from "@opentui/solid";
168
316
  import { createTextNode as _$createTextNode4 } from "@opentui/solid";
169
317
  import { insertNode as _$insertNode4 } from "@opentui/solid";
170
- import { setProp as _$setProp4 } from "@opentui/solid";
171
- import { createElement as _$createElement4 } from "@opentui/solid";
172
- import { createComponent as _$createComponent4 } from "@opentui/solid";
318
+ import { setProp as _$setProp5 } from "@opentui/solid";
319
+ import { createElement as _$createElement5 } from "@opentui/solid";
320
+ import { createComponent as _$createComponent5 } from "@opentui/solid";
173
321
  import { Plugin } from "@opencode/plugin/tui";
174
- import { Show as Show3 } from "solid-js";
322
+ import { Show as Show4 } from "solid-js";
175
323
 
176
324
  // src/tui/state.ts
177
325
  import { createSignal } from "solid-js";
@@ -413,7 +561,7 @@ class SnapshotClient {
413
561
  const value = await abortable(this.transport.read(location, ids, signal), signal);
414
562
  if (generation !== this.generation)
415
563
  throw new Error("Obsolete read");
416
- if (!isResult(value)) {
564
+ if (!isPerformanceResult(value)) {
417
565
  this.connection = "incompatible";
418
566
  throw new Error("Incompatible snapshot");
419
567
  }
@@ -486,7 +634,7 @@ class SnapshotClient {
486
634
  for await (const value of this.transport.subscribe(this.location, signal)) {
487
635
  if (signal.aborted || generation !== this.generation)
488
636
  return;
489
- if (!isBatch(value)) {
637
+ if (!isPerformanceBatch(value)) {
490
638
  this.connection = "incompatible";
491
639
  this.changed();
492
640
  continue;
@@ -661,6 +809,7 @@ function createGuardState(context) {
661
809
  scheduleExpiry();
662
810
  };
663
811
  const rpc = context.client.rpc(monitorRpc);
812
+ const liveRpc = context.client.rpc(performanceRpc);
664
813
  const clients = new Map;
665
814
  let inventory, currentId = "", controller;
666
815
  let scanGeneration = 0, scanning = false, maxNodes = 1024;
@@ -744,10 +893,20 @@ function createGuardState(context) {
744
893
  let c = clients.get(key);
745
894
  if (!c) {
746
895
  const location = sample.location;
896
+ let selectedRpc = rpc;
897
+ try {
898
+ const probe = await liveRpc.read({ schemaVersion: 1, sessionIds: [] }, { location, signal });
899
+ if (isPerformanceResult(probe))
900
+ selectedRpc = liveRpc;
901
+ } catch {
902
+ }
903
+ if (signal.aborted || generation !== scanGeneration)
904
+ return;
905
+ const source = selectedRpc;
747
906
  c = new SnapshotClient({
748
- read: (_key, ids, requestSignal) => rpc.read({ schemaVersion: 1, sessionIds: ids }, { location, signal: requestSignal }),
907
+ read: (_key, ids, requestSignal) => source.read({ schemaVersion: 1, sessionIds: ids }, { location, signal: requestSignal }),
749
908
  subscribe: async function* (_key, requestSignal) {
750
- for await (const event of rpc.events.subscribe("changed", {
909
+ for await (const event of source.events.subscribe("changed", {
751
910
  signal: requestSignal
752
911
  })) {
753
912
  if (event.location && sameLocation(event.location, location))
@@ -880,6 +1039,15 @@ function createGuardState(context) {
880
1039
  return {
881
1040
  version,
882
1041
  acquire,
1042
+ async history(id, from, to, signal) {
1043
+ const info = inventory?.sessions.get(id);
1044
+ if (!info)
1045
+ throw new Error("Session location unavailable");
1046
+ const result = await context.client.rpc(historyRpc).read({ schemaVersion: 1, sessionId: id, from, to, maxPoints: 240 }, { location: info.location, signal });
1047
+ if (!isHistoryResult(result))
1048
+ throw new Error("Incompatible history service");
1049
+ return result;
1050
+ },
883
1051
  rows,
884
1052
  dispose: stop,
885
1053
  age(id) {
@@ -986,6 +1154,7 @@ function rowLines(row2, width) {
986
1154
  const label = clip(`${prefix}${row2.node.agent ?? row2.node.sessionId}`, Math.max(1, width - cells(suffix)));
987
1155
  return [
988
1156
  clip(`${label}${suffix}`, width),
1157
+ ...performanceLines(row2, width),
989
1158
  ...wrap(`${quantity(m?.tokens)}/${quantity(s?.capacityTokens)} input; to ${quantity(row2.triggerDeltaTokens)}`, width),
990
1159
  ...s && s.compaction.phase !== "idle" ? wrap(`compact: ${s.compaction.phase}`, width) : [],
991
1160
  ...row2.node.interrupted ? [clip("interrupted", width)] : [],
@@ -1005,6 +1174,7 @@ function detailLines(row2, width, expanded = false, ageMs = 0) {
1005
1174
  const headroom = row2.triggerDeltaTokens;
1006
1175
  const lines = [
1007
1176
  `${s.agent ?? "?"} / ${s.model ?? "?"}`,
1177
+ ...performanceLines(row2, width),
1008
1178
  `${m?.confidence === "approximate" ? "~" : ""}${quantity(m?.tokens)} / ${quantity(s.capacityTokens)} input`,
1009
1179
  `${row2.utilizationPercent?.toFixed(1) ?? "?"}% ${row2.pressure === "crossed" ? "!! crossed" : row2.pressure === "near" ? "! near" : row2.freshness.toUpperCase()}`,
1010
1180
  `Trigger ${threshold.type === "ratio" ? `${threshold.value * 100}%` : `${threshold.value} ${threshold.type}`} (${s.policy.comparison})`,
@@ -1042,6 +1212,17 @@ function wrap(value, width) {
1042
1212
  result.push(part);
1043
1213
  return result;
1044
1214
  }
1215
+ function performanceLines(row2, width) {
1216
+ const p = row2.snapshot?.performance;
1217
+ if (!p)
1218
+ return [];
1219
+ const value = (n) => n === undefined ? "?" : n.toFixed(1);
1220
+ return [
1221
+ ...wrap(`TPS ${p.status === "waiting" ? "waiting" : `${p.status === "streaming" ? "~" : ""}${value(p.liveTps)}`} \xB7 ${p.status}${p.partial ? " \xB7 partial" : ""}`, width),
1222
+ ...wrap(`Avg model ${value(p.averageModelTps)} \xB7 Avg session ${value(p.averageSessionTps)}`, width),
1223
+ ...wrap(`TTFT ${p.ttftMs === undefined ? "?" : `~${(p.ttftMs / 1000).toFixed(2)}s`}${p.latestRequestTps === undefined ? "" : ` \xB7 Last ${value(p.latestRequestTps)} t/s`}`, width)
1224
+ ];
1225
+ }
1045
1226
 
1046
1227
  // src/tui/compact.tsx
1047
1228
  import { createTextNode as _$createTextNode } from "@opentui/solid";
@@ -1086,6 +1267,7 @@ function mainSections(row2, width, age) {
1086
1267
  const ratio = (total) => measured !== undefined && total !== undefined && total > 0 ? measured / total : undefined;
1087
1268
  const remaining = measured !== undefined && s?.policy.triggerTokens !== undefined ? s.policy.triggerTokens - measured : undefined;
1088
1269
  return [
1270
+ ...row2.snapshot?.performance ? [{ tone: "base", lines: performanceLines(row2, width) }] : [],
1089
1271
  {
1090
1272
  tone: "execution",
1091
1273
  lines: columns(`${row2.node.agent ?? s?.agent ?? "Main"} \xB7 ${row2.node.model ?? s?.model ?? "?"}`, row2.node.status.toUpperCase(), width)
@@ -1146,6 +1328,7 @@ function agentLines(row2, width) {
1146
1328
  const capacity = row2.snapshot?.capacityTokens;
1147
1329
  const pressure = row2.pressure === "crossed" ? "CRITICAL" : row2.pressure === "near" ? "WARNING" : "";
1148
1330
  return [
1331
+ ...performanceLines(row2, width),
1149
1332
  progress(measured !== undefined && capacity !== undefined && capacity > 0 ? measured / capacity : undefined, width),
1150
1333
  ...wrap(`${tokens(measured)} / ${tokens(capacity)}${pressure ? ` \xB7 ${pressure}` : ""}`, width),
1151
1334
  ...wrap(`${row2.node.status.toUpperCase()}${row2.freshness !== "fresh" ? ` \xB7 ${row2.freshness.toUpperCase()}` : ""}${row2.node.interrupted ? " \xB7 interrupted" : ""}`, width),
@@ -1626,26 +1809,195 @@ function GuardView(props) {
1626
1809
  }
1627
1810
 
1628
1811
  // src/tui/panel.tsx
1629
- import { effect as _$effect3 } from "@opentui/solid";
1630
- import { insert as _$insert3 } from "@opentui/solid";
1812
+ import { effect as _$effect4 } from "@opentui/solid";
1631
1813
  import { createTextNode as _$createTextNode3 } from "@opentui/solid";
1632
1814
  import { insertNode as _$insertNode3 } from "@opentui/solid";
1815
+ import { memo as _$memo3 } from "@opentui/solid";
1816
+ import { insert as _$insert4 } from "@opentui/solid";
1817
+ import { createComponent as _$createComponent4 } from "@opentui/solid";
1818
+ import { setProp as _$setProp4 } from "@opentui/solid";
1819
+ import { use as _$use } from "@opentui/solid";
1820
+ 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";
1822
+
1823
+ // src/tui/charts.tsx
1824
+ import { effect as _$effect3 } from "@opentui/solid";
1825
+ import { insert as _$insert3 } from "@opentui/solid";
1633
1826
  import { createComponent as _$createComponent3 } from "@opentui/solid";
1634
1827
  import { setProp as _$setProp3 } from "@opentui/solid";
1635
- import { use as _$use } from "@opentui/solid";
1636
1828
  import { createElement as _$createElement3 } from "@opentui/solid";
1637
- import { createSignal as createSignal3, createMemo as createMemo3, createEffect as createEffect3, onCleanup as onCleanup2 } from "solid-js";
1829
+ import { createEffect as createEffect3, createSignal as createSignal3, onCleanup as onCleanup2, For as For3 } from "solid-js";
1830
+
1831
+ // src/tui/chartformat.ts
1832
+ function sparkline(points, from, to, width, gaps = []) {
1833
+ const size = Math.max(1, Math.min(120, Math.floor(width))), values = Array.from({ length: size });
1834
+ const column = (time) => Math.max(0, Math.min(size - 1, Math.floor((time - from) / Math.max(1, to - from) * (size - 1))));
1835
+ let min = Infinity, max = -Infinity;
1836
+ for (const point of points) {
1837
+ if (point.time < from || point.time > to || point.value === undefined || !Number.isFinite(point.value) || point.value < 0)
1838
+ continue;
1839
+ min = Math.min(min, point.value);
1840
+ max = Math.max(max, point.value);
1841
+ const i = column(point.time);
1842
+ values[i] = Math.max(values[i] ?? -Infinity, point.value);
1843
+ }
1844
+ const bars = "\u2581\u2582\u2583\u2584\u2585\u2586\u2587\u2588";
1845
+ const chars = values.map((v) => v === undefined ? " " : bars[Math.min(7, Math.floor(v / Math.max(1, max) * 7))] ?? "\u2581");
1846
+ for (const time of gaps)
1847
+ if (time >= from && time <= to)
1848
+ chars[column(time)] = "\u2506";
1849
+ return { line: chars.join(""), ...min === Infinity ? {} : { min, max } };
1850
+ }
1851
+ function historyChartLines(result, width, from, to) {
1852
+ if (!result.enabled)
1853
+ return wrap("History recording is disabled on this server.", width);
1854
+ const series = result.data.series[0];
1855
+ if (!series)
1856
+ return wrap("No retained measurements in this window.", width);
1857
+ 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
+ const observations = (key) => series.points.map((p) => ({
1866
+ time: p.time,
1867
+ ...p[key] === undefined ? {} : { value: p[key] }
1868
+ }));
1869
+ const requests = (key) => series.requests.map((p) => ({
1870
+ time: p.time,
1871
+ ...p[key] === undefined ? {} : { value: key === "ttftMs" ? p[key] / 1000 : p[key] }
1872
+ }));
1873
+ return [
1874
+ ...chart("Live TPS ~", "t/s", observations("liveTps")),
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")),
1883
+ "",
1884
+ ...chart("Cumulative output", "tokens", observations("sessionOutputTokens")),
1885
+ "",
1886
+ ...wrap("UTC time \u2192 \xB7 observed peaks per column \xB7 blank = no sample \xB7 \u2506 = gap", width),
1887
+ ...wrap(`${new Date(from).toISOString()} \u2192 ${new Date(to).toISOString()}`, width),
1888
+ ...series.points.some((p) => p.partial) ? wrap("Partial timing coverage; averages include only matched model durations.", width) : [],
1889
+ ...result.truncated ? wrap("Downsampled/bounded history; some older requests or gap markers may be omitted.", width) : [],
1890
+ ...result.metadata.malformedRecords ? wrap(`${result.metadata.malformedRecords} malformed/partial records skipped.`, width) : []
1891
+ ];
1892
+ }
1893
+
1894
+ // src/tui/charts.tsx
1895
+ function HistoryCharts(props) {
1896
+ const [result, setResult] = createSignal3();
1897
+ const [status, setStatus] = createSignal3("Loading retained history...");
1898
+ const [range, setRange] = createSignal3({
1899
+ from: 0,
1900
+ to: 0
1901
+ });
1902
+ createEffect3(() => {
1903
+ const { sessionId: id, windowMs } = props;
1904
+ props.refresh;
1905
+ let pending = false, disposed = false;
1906
+ let controller;
1907
+ setResult(undefined);
1908
+ async function read() {
1909
+ if (pending || disposed)
1910
+ return;
1911
+ pending = true;
1912
+ controller = new AbortController;
1913
+ const to = Date.now(), from = Math.max(0, to - windowMs);
1914
+ try {
1915
+ const next = await props.state.history(id, from, to, AbortSignal.any([controller.signal, AbortSignal.timeout(5000)]));
1916
+ if (!disposed) {
1917
+ setResult(next);
1918
+ setRange({
1919
+ from,
1920
+ to
1921
+ });
1922
+ setStatus("");
1923
+ }
1924
+ } catch {
1925
+ if (!disposed)
1926
+ setStatus("History unavailable. The owning server must support chart history RPC. Press r to retry.");
1927
+ } finally {
1928
+ pending = false;
1929
+ }
1930
+ }
1931
+ read();
1932
+ const timer = setInterval(() => {
1933
+ read();
1934
+ }, 5000);
1935
+ onCleanup2(() => {
1936
+ disposed = true;
1937
+ controller?.abort();
1938
+ clearInterval(timer);
1939
+ });
1940
+ });
1941
+ const lines = () => {
1942
+ const current = result();
1943
+ return current ? historyChartLines(current, Math.max(4, props.width), range().from, range().to) : [];
1944
+ };
1945
+ return (() => {
1946
+ var _el$ = _$createElement3("box");
1947
+ _$setProp3(_el$, "flexDirection", "column");
1948
+ _$insert3(_el$, _$createComponent3(For3, {
1949
+ get each() {
1950
+ return wrap(`Performance charts \xB7 ${props.sessionId}`, props.width);
1951
+ },
1952
+ children: (line) => (() => {
1953
+ var _el$2 = _$createElement3("text");
1954
+ _$insert3(_el$2, line);
1955
+ _$effect3((_$p) => _$setProp3(_el$2, "fg", props.context.theme.text.base, _$p));
1956
+ return _el$2;
1957
+ })()
1958
+ }), null);
1959
+ _$insert3(_el$, _$createComponent3(For3, {
1960
+ get each() {
1961
+ return wrap(status(), props.width);
1962
+ },
1963
+ children: (line) => (() => {
1964
+ var _el$3 = _$createElement3("text");
1965
+ _$insert3(_el$3, line);
1966
+ _$effect3((_$p) => _$setProp3(_el$3, "fg", props.context.theme.text.muted, _$p));
1967
+ return _el$3;
1968
+ })()
1969
+ }), null);
1970
+ _$insert3(_el$, _$createComponent3(For3, {
1971
+ get each() {
1972
+ return lines();
1973
+ },
1974
+ children: (line) => (() => {
1975
+ var _el$4 = _$createElement3("text");
1976
+ _$insert3(_el$4, line || " ");
1977
+ _$effect3((_$p) => _$setProp3(_el$4, "fg", props.context.theme.text.base, _$p));
1978
+ return _el$4;
1979
+ })()
1980
+ }), null);
1981
+ return _el$;
1982
+ })();
1983
+ }
1984
+
1985
+ // src/tui/panel.tsx
1638
1986
  function GuardPanel(props) {
1639
- const [inspectId, setInspectId] = createSignal3();
1640
- const [selected, setSelected] = createSignal3(props.input.sessionID);
1641
- const [order, setOrder] = createSignal3([]);
1987
+ const [showCharts, setShowCharts] = createSignal4(false);
1988
+ const windows = [300000, 3600000, 86400000, 7 * 86400000];
1989
+ const [windowIndex, setWindowIndex] = createSignal4(1);
1990
+ const [chartRefresh, setChartRefresh] = createSignal4(0);
1991
+ const [inspectId, setInspectId] = createSignal4();
1992
+ const [selected, setSelected] = createSignal4(props.input.sessionID);
1993
+ const [order, setOrder] = createSignal4([]);
1642
1994
  const rows = createMemo3(() => props.state.rows(order()));
1643
1995
  const offset = () => Math.max(0, rows().findIndex((row2) => row2.node.sessionId === selected()));
1644
- createEffect3(() => {
1996
+ createEffect4(() => {
1645
1997
  if (!props.input.focused)
1646
1998
  setOrder([]);
1647
1999
  });
1648
- createEffect3(() => {
2000
+ createEffect4(() => {
1649
2001
  setSelected(props.input.sessionID);
1650
2002
  setOrder([]);
1651
2003
  });
@@ -1666,7 +2018,7 @@ function GuardPanel(props) {
1666
2018
  props.context.renderer.setFrameCallback(follow);
1667
2019
  props.context.renderer.requestRender();
1668
2020
  }
1669
- onCleanup2(() => {
2021
+ onCleanup3(() => {
1670
2022
  if (follow)
1671
2023
  props.context.renderer.removeFrameCallback(follow);
1672
2024
  });
@@ -1675,12 +2027,34 @@ function GuardPanel(props) {
1675
2027
  const row2 = rows()[Math.max(0, Math.min(rows().length - 1, offset() + delta))];
1676
2028
  if (row2) {
1677
2029
  setSelected(row2.node.sessionId);
1678
- revealSelection();
2030
+ if (!showCharts())
2031
+ revealSelection();
1679
2032
  }
1680
2033
  }
1681
2034
  props.context.keymap.layer(() => ({
1682
2035
  enabled: () => props.input.focused,
1683
2036
  commands: [{
2037
+ id: "context-guard.charts",
2038
+ title: "Toggle performance charts",
2039
+ bind: "c",
2040
+ run: () => setShowCharts((v) => !v)
2041
+ }, {
2042
+ id: "context-guard.chart-shorter",
2043
+ title: "Shorter chart window",
2044
+ bind: "[",
2045
+ run: () => {
2046
+ if (showCharts())
2047
+ setWindowIndex((i) => Math.max(0, i - 1));
2048
+ }
2049
+ }, {
2050
+ id: "context-guard.chart-longer",
2051
+ title: "Longer chart window",
2052
+ bind: "]",
2053
+ run: () => {
2054
+ if (showCharts())
2055
+ setWindowIndex((i) => Math.min(windows.length - 1, i + 1));
2056
+ }
2057
+ }, {
1684
2058
  id: "context-guard.inspect",
1685
2059
  title: "Inspect selected session",
1686
2060
  bind: "i",
@@ -1692,6 +2066,7 @@ function GuardPanel(props) {
1692
2066
  run: () => {
1693
2067
  setOrder([]);
1694
2068
  props.state.refresh();
2069
+ setChartRefresh((n) => n + 1);
1695
2070
  }
1696
2071
  }, {
1697
2072
  id: "context-guard.load-more",
@@ -1730,17 +2105,19 @@ function GuardPanel(props) {
1730
2105
  }]
1731
2106
  }));
1732
2107
  return (() => {
1733
- var _el$ = _$createElement3("scrollbox"), _el$2 = _$createElement3("text"), _el$4 = _$createElement3("text"), _el$5 = _$createTextNode3(`up/down select `), _el$6 = _$createTextNode3(`; Enter open`), _el$7 = _$createElement3("text"), _el$9 = _$createElement3("text"), _el$1 = _$createElement3("text");
2108
+ var _el$ = _$createElement4("scrollbox"), _el$2 = _$createElement4("box"), _el$6 = _$createElement4("text"), _el$8 = _$createElement4("text"), _el$9 = _$createTextNode3(`up/down select `), _el$0 = _$createTextNode3(`; Enter open`), _el$1 = _$createElement4("text"), _el$11 = _$createElement4("text"), _el$13 = _$createElement4("text");
1734
2109
  _$insertNode3(_el$, _el$2);
1735
- _$insertNode3(_el$, _el$4);
1736
- _$insertNode3(_el$, _el$7);
1737
- _$insertNode3(_el$, _el$9);
2110
+ _$insertNode3(_el$, _el$6);
2111
+ _$insertNode3(_el$, _el$8);
1738
2112
  _$insertNode3(_el$, _el$1);
2113
+ _$insertNode3(_el$, _el$11);
2114
+ _$insertNode3(_el$, _el$13);
1739
2115
  _$use((value) => {
1740
2116
  scroll = value;
1741
2117
  }, _el$);
1742
- _$setProp3(_el$, "flexGrow", 1);
1743
- _$insert3(_el$, _$createComponent3(GuardView, {
2118
+ _$setProp4(_el$, "flexGrow", 1);
2119
+ _$setProp4(_el$2, "flexDirection", "column");
2120
+ _$insert4(_el$2, _$createComponent4(GuardView, {
1744
2121
  get sessionID() {
1745
2122
  return props.input.sessionID;
1746
2123
  },
@@ -1763,28 +2140,63 @@ function GuardPanel(props) {
1763
2140
  get inspectId() {
1764
2141
  return inspectId();
1765
2142
  }
1766
- }), _el$2);
1767
- _$insertNode3(_el$2, _$createTextNode3(`r refresh / l load more`));
1768
- _$insertNode3(_el$4, _el$5);
1769
- _$insertNode3(_el$4, _el$6);
1770
- _$insert3(_el$4, () => offset() + 1, _el$6);
1771
- _$insertNode3(_el$7, _$createTextNode3(`space expand / i inspect selected`));
1772
- _$insertNode3(_el$9, _$createTextNode3(`Guard input = prompt + caches.`));
1773
- _$insertNode3(_el$1, _$createTextNode3(`Native Context uses different totals.`));
1774
- _$effect3((_p$) => {
1775
- var _v$ = props.context.theme.text.muted, _v$2 = props.context.theme.text.muted, _v$3 = props.context.theme.text.muted, _v$4 = props.context.theme.text.muted, _v$5 = props.context.theme.text.muted;
1776
- _v$ !== _p$.e && (_p$.e = _$setProp3(_el$2, "fg", _v$, _p$.e));
1777
- _v$2 !== _p$.t && (_p$.t = _$setProp3(_el$4, "fg", _v$2, _p$.t));
1778
- _v$3 !== _p$.a && (_p$.a = _$setProp3(_el$7, "fg", _v$3, _p$.a));
1779
- _v$4 !== _p$.o && (_p$.o = _$setProp3(_el$9, "fg", _v$4, _p$.o));
1780
- _v$5 !== _p$.i && (_p$.i = _$setProp3(_el$1, "fg", _v$5, _p$.i));
2143
+ }));
2144
+ _$insert4(_el$, _$createComponent4(Show3, {
2145
+ get when() {
2146
+ return showCharts();
2147
+ },
2148
+ get children() {
2149
+ return [_$createComponent4(HistoryCharts, {
2150
+ get sessionId() {
2151
+ return selected();
2152
+ },
2153
+ get windowMs() {
2154
+ return windows[windowIndex()] ?? 3600000;
2155
+ },
2156
+ get refresh() {
2157
+ return chartRefresh();
2158
+ },
2159
+ get width() {
2160
+ return Math.max(4, props.input.width - 6);
2161
+ },
2162
+ get state() {
2163
+ return props.state;
2164
+ },
2165
+ get context() {
2166
+ return props.context;
2167
+ }
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
+ })()];
2175
+ }
2176
+ }), _el$6);
2177
+ _$insertNode3(_el$6, _$createTextNode3(`r refresh / l load more`));
2178
+ _$insertNode3(_el$8, _el$9);
2179
+ _$insertNode3(_el$8, _el$0);
2180
+ _$insert4(_el$8, () => offset() + 1, _el$0);
2181
+ _$insertNode3(_el$1, _$createTextNode3(`space expand / i inspect selected / c charts`));
2182
+ _$insertNode3(_el$11, _$createTextNode3(`Guard input = prompt + caches.`));
2183
+ _$insertNode3(_el$13, _$createTextNode3(`Native Context uses different totals.`));
2184
+ _$effect4((_p$) => {
2185
+ var _v$ = !showCharts(), _v$2 = props.context.theme.text.muted, _v$3 = props.context.theme.text.muted, _v$4 = props.context.theme.text.muted, _v$5 = props.context.theme.text.muted, _v$6 = props.context.theme.text.muted;
2186
+ _v$ !== _p$.e && (_p$.e = _$setProp4(_el$2, "visible", _v$, _p$.e));
2187
+ _v$2 !== _p$.t && (_p$.t = _$setProp4(_el$6, "fg", _v$2, _p$.t));
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));
1781
2192
  return _p$;
1782
2193
  }, {
1783
2194
  e: undefined,
1784
2195
  t: undefined,
1785
2196
  a: undefined,
1786
2197
  o: undefined,
1787
- i: undefined
2198
+ i: undefined,
2199
+ n: undefined
1788
2200
  });
1789
2201
  return _el$;
1790
2202
  })();
@@ -1801,7 +2213,7 @@ var tui_default = Plugin.define({
1801
2213
  try {
1802
2214
  unregister.push(context.ui.slot({
1803
2215
  append: "sidebar.content",
1804
- render: (input) => _$createComponent4(GuardView, {
2216
+ render: (input) => _$createComponent5(GuardView, {
1805
2217
  get sessionID() {
1806
2218
  return input.sessionID;
1807
2219
  },
@@ -1812,12 +2224,12 @@ var tui_default = Plugin.define({
1812
2224
  }));
1813
2225
  unregister.push(context.ui.slot({
1814
2226
  append: "session.panel",
1815
- render: (input) => _$createComponent4(Show3, {
2227
+ render: (input) => _$createComponent5(Show4, {
1816
2228
  get when() {
1817
2229
  return input.name === "context-guard.monitor";
1818
2230
  },
1819
2231
  get children() {
1820
- return _$createComponent4(GuardPanel, {
2232
+ return _$createComponent5(GuardPanel, {
1821
2233
  input,
1822
2234
  state,
1823
2235
  context
@@ -1827,15 +2239,15 @@ var tui_default = Plugin.define({
1827
2239
  }));
1828
2240
  unregister.push(context.ui.slot({
1829
2241
  append: "session.composer.top",
1830
- render: (input) => _$createComponent4(Show3, {
2242
+ render: (input) => _$createComponent5(Show4, {
1831
2243
  get when() {
1832
2244
  return context.data.session.get(input.sessionID)?.parentID;
1833
2245
  },
1834
2246
  get children() {
1835
- var _el$ = _$createElement4("text");
2247
+ var _el$ = _$createElement5("text");
1836
2248
  _$insertNode4(_el$, _$createTextNode4(`Context Guard: open panel`));
1837
- _$setProp4(_el$, "onMouseDown", () => context.ui.panel.open("context-guard.monitor"));
1838
- _$effect4((_$p) => _$setProp4(_el$, "fg", context.theme.text.muted, _$p));
2249
+ _$setProp5(_el$, "onMouseDown", () => context.ui.panel.open("context-guard.monitor"));
2250
+ _$effect5((_$p) => _$setProp5(_el$, "fg", context.theme.text.muted, _$p));
1839
2251
  return _el$;
1840
2252
  }
1841
2253
  })