@cesarus777/opencode-cgp 0.0.0-stage → 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.
Files changed (40) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +131 -2
  3. package/dist/cli.js +1411 -0
  4. package/dist/core.js +1247 -0
  5. package/dist/index.js +2565 -0
  6. package/dist/rpc.js +332 -0
  7. package/dist/tui.js +2286 -0
  8. package/dist/types/adapter.d.ts +20 -0
  9. package/dist/types/chartreport.d.ts +14 -0
  10. package/dist/types/charts.d.ts +54 -0
  11. package/dist/types/config.d.ts +2 -0
  12. package/dist/types/core.d.ts +10 -0
  13. package/dist/types/estimate.d.ts +1 -0
  14. package/dist/types/family.d.ts +39 -0
  15. package/dist/types/history.d.ts +59 -0
  16. package/dist/types/historycharts.d.ts +17 -0
  17. package/dist/types/historyserver.d.ts +7 -0
  18. package/dist/types/index.d.ts +3 -0
  19. package/dist/types/monitor.d.ts +20 -0
  20. package/dist/types/performance.d.ts +57 -0
  21. package/dist/types/policy.d.ts +12 -0
  22. package/dist/types/recovery.d.ts +22 -0
  23. package/dist/types/rpc.d.ts +129 -0
  24. package/dist/types/scheduler.d.ts +18 -0
  25. package/dist/types/snapshot.d.ts +98 -0
  26. package/dist/types/snapshotserver.d.ts +7 -0
  27. package/dist/types/telemetry.d.ts +17 -0
  28. package/dist/types/tui/chartformat.d.ts +11 -0
  29. package/dist/types/tui/charts.d.ts +10 -0
  30. package/dist/types/tui/compact.d.ts +10 -0
  31. package/dist/types/tui/compactformat.d.ts +28 -0
  32. package/dist/types/tui/format.d.ts +8 -0
  33. package/dist/types/tui/inventory.d.ts +18 -0
  34. package/dist/types/tui/panel.d.ts +8 -0
  35. package/dist/types/tui/sidebar.d.ts +13 -0
  36. package/dist/types/tui/state.d.ts +30 -0
  37. package/dist/types/tui/transport.d.ts +40 -0
  38. package/dist/types/tui.d.ts +3 -0
  39. package/dist/types/types.d.ts +153 -0
  40. package/package.json +110 -4
package/dist/tui.js ADDED
@@ -0,0 +1,2286 @@
1
+ // @bun
2
+ // src/rpc.ts
3
+ import { Rpc } from "@opencode/plugin/rpc";
4
+ var count = {
5
+ type: "integer",
6
+ minimum: 0,
7
+ maximum: Number.MAX_SAFE_INTEGER
8
+ };
9
+ var text = { type: "string", minLength: 1, maxLength: 512 };
10
+ var enumeration = (...values) => ({ type: "string", enum: values });
11
+ var object = (properties, required = Object.keys(properties)) => ({
12
+ type: "object",
13
+ properties,
14
+ required,
15
+ additionalProperties: false
16
+ });
17
+ var array = (items, maxItems = 128) => ({
18
+ type: "array",
19
+ items,
20
+ maxItems
21
+ });
22
+ var measurement = object({
23
+ tokens: count,
24
+ at: count,
25
+ model: text,
26
+ requestId: text,
27
+ provenance: enumeration("provider-normalized", "serialized-estimate"),
28
+ confidence: enumeration("reported", "approximate"),
29
+ scope: enumeration("completed-request", "prepared-request")
30
+ }, ["tokens", "at", "model", "provenance", "confidence", "scope"]);
31
+ var row = object({
32
+ sessionId: text,
33
+ revision: count,
34
+ observedAt: count,
35
+ parentId: text,
36
+ agent: text,
37
+ model: text,
38
+ measurement,
39
+ limits: object({ context: count, input: count, output: count }, [
40
+ "context",
41
+ "output"
42
+ ]),
43
+ capacityTokens: count,
44
+ outputReserveTokens: count,
45
+ peakTokens: count,
46
+ policy: object({
47
+ id: text,
48
+ configured: object({
49
+ type: enumeration("tokens", "ratio", "remaining"),
50
+ value: { type: "number", minimum: 0 }
51
+ }),
52
+ triggerTokens: { type: "number" },
53
+ comparison: enumeration(">=", ">"),
54
+ reason: enumeration("missing-usage", "model-changed", "invalid-usage", "stale-usage", "missing-model-budget", "incompatible-model-budget", "threshold-crossed", "below-threshold"),
55
+ crossed: { type: "boolean" }
56
+ }, ["id", "configured", "comparison", "reason", "crossed"]),
57
+ compaction: object({
58
+ phase: enumeration("idle", "requesting", "accepted", "running", "awaiting-measurement", "blocked", "uncertain"),
59
+ count,
60
+ armed: { type: "boolean" },
61
+ blockedReason: enumeration("session-removed", "compaction-failed", "reclaim-unverifiable", "insufficient-reclaim", "completion-event-timeout", "admission-outcome-unknown", "restart-needs-lifecycle-evidence"),
62
+ lastAttempt: count,
63
+ lastCompleted: count,
64
+ cooldownUntil: count
65
+ }, ["phase", "armed"]),
66
+ cumulative: object({
67
+ input: count,
68
+ output: count,
69
+ reasoning: count,
70
+ cache: object({ read: count, write: count })
71
+ }),
72
+ cumulativeScope: enumeration("session", "unknown"),
73
+ lastObservedActive: { type: "boolean" }
74
+ }, [
75
+ "sessionId",
76
+ "revision",
77
+ "observedAt",
78
+ "policy",
79
+ "compaction",
80
+ "cumulativeScope",
81
+ "lastObservedActive"
82
+ ]);
83
+ var batchFields = {
84
+ schemaVersion: { const: 1 },
85
+ epoch: text,
86
+ sequence: count,
87
+ generatedAt: count,
88
+ health: object({
89
+ enabled: { type: "boolean" },
90
+ mode: enumeration("observe", "enforce"),
91
+ enforcement: enumeration("observing", "available", "suspended", "disabled"),
92
+ reasons: array(enumeration("event-stream-gap", "persistence-unavailable", "capacity-exceeded", "model-metadata-unavailable"), 8),
93
+ staleMs: count,
94
+ maxSessions: count
95
+ }),
96
+ upserts: array(row),
97
+ removed: array(object({
98
+ sessionId: text,
99
+ revision: count,
100
+ reason: enumeration("deleted", "evicted")
101
+ }))
102
+ };
103
+ var readSchema = object({
104
+ schemaVersion: { const: 1 },
105
+ sessionIds: { ...array(text), uniqueItems: true }
106
+ });
107
+ var batchSchema = object(batchFields);
108
+ var resultSchema = object({
109
+ ...batchFields,
110
+ unavailable: array(object({ sessionId: text, reason: enumeration("not-monitored") }))
111
+ });
112
+ var monitorRpc = Rpc.define({
113
+ id: "context-guard.monitor.v1",
114
+ methods: { read: { input: readSchema, output: resultSchema } },
115
+ events: { changed: { schema: batchSchema } }
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
+ }
265
+ function record(x) {
266
+ return typeof x === "object" && x !== null && !Array.isArray(x);
267
+ }
268
+ function matches(value, schema) {
269
+ if (!record(schema))
270
+ return false;
271
+ if ("const" in schema && value !== schema.const)
272
+ return false;
273
+ if (Array.isArray(schema.enum) && !schema.enum.includes(value))
274
+ return false;
275
+ if (schema.type === "number" || schema.type === "integer")
276
+ return typeof value === "number" && Number.isFinite(value) && (schema.type !== "integer" || Number.isSafeInteger(value)) && (typeof schema.minimum !== "number" || value >= schema.minimum) && (typeof schema.maximum !== "number" || value <= schema.maximum);
277
+ if (schema.type === "string")
278
+ return typeof value === "string" && value.length > 0 && value.length <= 512;
279
+ if (schema.type === "boolean")
280
+ return typeof value === "boolean";
281
+ if (schema.type === "array")
282
+ return Array.isArray(value) && value.length <= Number(schema.maxItems) && (!schema.uniqueItems || new Set(value).size === value.length) && value.every((v) => matches(v, schema.items));
283
+ if (schema.type === "object") {
284
+ if (!record(value) || !record(schema.properties) || !Array.isArray(schema.required))
285
+ return false;
286
+ const properties = schema.properties;
287
+ return schema.required.every((k) => typeof k === "string" && (k in value)) && Object.keys(value).every((k) => (k in properties) && matches(value[k], properties[k]));
288
+ }
289
+ return "const" in schema;
290
+ }
291
+ function valid(value, schema) {
292
+ try {
293
+ return JSON.stringify(value).length <= 524288 && matches(value, schema);
294
+ } catch {
295
+ return false;
296
+ }
297
+ }
298
+ function isRead(value) {
299
+ return valid(value, readSchema);
300
+ }
301
+ function isBatch(value) {
302
+ return valid(value, batchSchema) && uniqueRows(value);
303
+ }
304
+ function isResult(value) {
305
+ return valid(value, resultSchema) && uniqueRows(value);
306
+ }
307
+ function uniqueRows(value) {
308
+ if (!record(value) || !Array.isArray(value.upserts) || !Array.isArray(value.removed))
309
+ return false;
310
+ const ids = [...value.upserts, ...value.removed].map((v) => record(v) ? v.sessionId : undefined);
311
+ return ids.length <= 128 && new Set(ids).size === ids.length;
312
+ }
313
+
314
+ // src/tui.tsx
315
+ import { effect as _$effect5 } from "@opentui/solid";
316
+ import { createTextNode as _$createTextNode4 } from "@opentui/solid";
317
+ import { insertNode as _$insertNode4 } 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";
321
+ import { Plugin } from "@opencode/plugin/tui";
322
+ import { Show as Show4 } from "solid-js";
323
+
324
+ // src/tui/state.ts
325
+ import { createSignal } from "solid-js";
326
+
327
+ // src/family.ts
328
+ function metric(snapshot, now, staleMs) {
329
+ const m = snapshot?.measurement;
330
+ const freshness = !m ? "missing" : m.model !== snapshot?.model || m.at > now ? "invalid" : now - m.at > staleMs ? "stale" : "fresh";
331
+ const t = snapshot?.policy.triggerTokens;
332
+ const valid2 = freshness === "fresh" && m !== undefined;
333
+ const pressureRatio = valid2 && t !== undefined && t > 0 ? m.tokens / t : undefined;
334
+ const crossed = valid2 && t !== undefined && t > 0 && (snapshot?.policy.comparison === ">" ? m.tokens > t : m.tokens >= t);
335
+ return {
336
+ freshness,
337
+ pressure: pressureRatio === undefined ? "unknown" : crossed ? "crossed" : pressureRatio >= 0.9 ? "near" : "normal",
338
+ ...pressureRatio === undefined ? {} : { pressureRatio },
339
+ ...valid2 && snapshot?.capacityTokens && snapshot.capacityTokens > 0 ? { utilizationPercent: m.tokens / snapshot.capacityTokens * 100 } : {},
340
+ ...valid2 && t !== undefined ? { triggerDeltaTokens: t - m.tokens } : {}
341
+ };
342
+ }
343
+ function familyRows(nodes, snapshots, current, now, staleMs, expanded = new Set, limit = 1024, timing, stableOrder = []) {
344
+ const byId = new Map(nodes.map((n) => [n.sessionId, n]));
345
+ const children = new Map;
346
+ for (const n of byId.values()) {
347
+ const parent = n.parentId && byId.has(n.parentId) ? n.parentId : "";
348
+ const list = children.get(parent) ?? [];
349
+ list.push(n);
350
+ children.set(parent, list);
351
+ }
352
+ const metrics = new Map([...byId.keys()].map((id) => [
353
+ id,
354
+ metric(snapshots.get(id), timing?.(id).now ?? now, timing?.(id).staleMs ?? staleMs)
355
+ ]));
356
+ const protectedIds = new Set;
357
+ let ancestor = byId.get(current);
358
+ while (ancestor && !protectedIds.has(ancestor.sessionId)) {
359
+ protectedIds.add(ancestor.sessionId);
360
+ ancestor = ancestor.parentId ? byId.get(ancestor.parentId) : undefined;
361
+ }
362
+ const scores = new Map, sensitive = new Map, sizes = new Map;
363
+ const calculate = (n, seen) => {
364
+ if (seen.has(n.sessionId))
365
+ return -1;
366
+ if (scores.has(n.sessionId))
367
+ return scores.get(n.sessionId) ?? -1;
368
+ const path = new Set(seen).add(n.sessionId), m = metrics.get(n.sessionId);
369
+ let score = m?.pressureRatio ?? -1, size = 1;
370
+ let keep = protectedIds.has(n.sessionId) || n.status === "running" || m?.pressure === "crossed" || ["blocked", "uncertain"].includes(snapshots.get(n.sessionId)?.compaction.phase ?? "");
371
+ for (const child of children.get(n.sessionId) ?? []) {
372
+ score = Math.max(score, calculate(child, path));
373
+ size += sizes.get(child.sessionId) ?? 0;
374
+ keep ||= sensitive.get(child.sessionId) ?? false;
375
+ }
376
+ scores.set(n.sessionId, score);
377
+ sizes.set(n.sessionId, size);
378
+ sensitive.set(n.sessionId, keep);
379
+ return score;
380
+ };
381
+ for (const n of byId.values())
382
+ calculate(n, new Set);
383
+ const visited = new Set, rows = [];
384
+ const visit = (n, depth) => {
385
+ if (visited.has(n.sessionId) || rows.length >= limit || depth > 64)
386
+ return;
387
+ visited.add(n.sessionId);
388
+ const snapshot = snapshots.get(n.sessionId), m = metrics.get(n.sessionId) ?? metric(undefined, now, staleMs);
389
+ const collapsed = n.status === "completed" && !sensitive.get(n.sessionId) && !expanded.has(n.sessionId);
390
+ rows.push({
391
+ node: n,
392
+ ...snapshot ? { snapshot } : {},
393
+ depth,
394
+ current: n.sessionId === current,
395
+ ...m,
396
+ hidden: collapsed ? (sizes.get(n.sessionId) ?? 1) - 1 : 0
397
+ });
398
+ if (collapsed) {
399
+ const pending = [...children.get(n.sessionId) ?? []];
400
+ while (pending.length) {
401
+ const child = pending.pop();
402
+ if (!child || visited.has(child.sessionId))
403
+ continue;
404
+ visited.add(child.sessionId);
405
+ pending.push(...children.get(child.sessionId) ?? []);
406
+ }
407
+ }
408
+ if (!collapsed)
409
+ for (const child of sorted(children.get(n.sessionId) ?? []))
410
+ visit(child, depth + 1);
411
+ };
412
+ const ranks = new Map(stableOrder.map((id, i) => [id, i]));
413
+ const sorted = (items) => [...items].sort((a, b) => (stableOrder.length ? (ranks.get(a.sessionId) ?? Infinity) - (ranks.get(b.sessionId) ?? Infinity) : 0) || (scores.get(b.sessionId) ?? -1) - (scores.get(a.sessionId) ?? -1) || a.sessionId.localeCompare(b.sessionId));
414
+ for (const n of sorted(children.get("") ?? []))
415
+ visit(n, 0);
416
+ for (const n of byId.values())
417
+ if (!visited.has(n.sessionId) && rows.length < limit)
418
+ visit(n, 0);
419
+ return rows;
420
+ }
421
+ function executionStatus(known, nativeRunning, evidence) {
422
+ if (nativeRunning || evidence.startedAt !== undefined && evidence.startedAt > (evidence.terminalAt ?? -1))
423
+ return "running";
424
+ if (evidence.outcome && (evidence.terminalAt ?? -1) >= (evidence.startedAt ?? -1))
425
+ return evidence.outcome === "succeeded" ? "completed" : evidence.outcome === "failed" ? "failed" : "unknown";
426
+ return known ? "idle" : "unknown";
427
+ }
428
+
429
+ // src/tui/transport.ts
430
+ class SnapshotClient {
431
+ transport;
432
+ changed;
433
+ elapsed;
434
+ rows = new Map;
435
+ health;
436
+ connection = "loading";
437
+ epoch;
438
+ sequence = -1;
439
+ revisions = new Map;
440
+ baselines = new Map;
441
+ controller;
442
+ generation = 0;
443
+ ids = [];
444
+ location = "";
445
+ refreshTimer;
446
+ reading;
447
+ buffered = [];
448
+ overflow = false;
449
+ retryTimer;
450
+ failures = 0;
451
+ serverAt = 0;
452
+ receivedAt = 0;
453
+ refreshAgain = false;
454
+ subscriptionActive = false;
455
+ constructor(transport, changed, elapsed = () => performance.now()) {
456
+ this.transport = transport;
457
+ this.changed = changed;
458
+ this.elapsed = elapsed;
459
+ }
460
+ now() {
461
+ return this.serverAt + Math.max(0, this.elapsed() - this.receivedAt);
462
+ }
463
+ setIds(ids) {
464
+ const next = [...new Set(ids)].slice(0, 2048);
465
+ if (JSON.stringify(next) === JSON.stringify(this.ids))
466
+ return;
467
+ this.ids = next;
468
+ const selected = new Set(next);
469
+ for (const id of new Set([
470
+ ...this.revisions.keys(),
471
+ ...this.baselines.keys()
472
+ ]))
473
+ if (!selected.has(id)) {
474
+ this.revisions.delete(id);
475
+ this.baselines.delete(id);
476
+ this.rows.delete(id);
477
+ }
478
+ if (this.reading)
479
+ this.refreshAgain = true;
480
+ else
481
+ this.refresh();
482
+ }
483
+ start(location, ids) {
484
+ this.stop();
485
+ this.location = location;
486
+ this.ids = [...new Set(ids)].slice(0, 2048);
487
+ this.rows.clear();
488
+ this.revisions.clear();
489
+ this.baselines.clear();
490
+ this.health = undefined;
491
+ this.epoch = undefined;
492
+ this.sequence = -1;
493
+ this.failures = 0;
494
+ this.connection = "loading";
495
+ const controller = new AbortController;
496
+ this.controller = controller;
497
+ const generation = ++this.generation;
498
+ this.supervise(generation, controller.signal);
499
+ this.refreshTimer = setInterval(() => {
500
+ this.refresh();
501
+ }, 15000);
502
+ this.refresh();
503
+ }
504
+ merge(batch, baseline = false, requested = []) {
505
+ if (this.epoch !== batch.epoch) {
506
+ if (!baseline) {
507
+ this.connection = "reconnecting";
508
+ this.refreshAgain = true;
509
+ return;
510
+ }
511
+ this.rows.clear();
512
+ this.revisions.clear();
513
+ this.baselines.clear();
514
+ this.epoch = batch.epoch;
515
+ this.sequence = -1;
516
+ }
517
+ if (!baseline && batch.sequence > this.sequence + 1) {
518
+ this.connection = "reconnecting";
519
+ this.refreshAgain = true;
520
+ }
521
+ if (batch.sequence >= this.sequence) {
522
+ this.sequence = batch.sequence;
523
+ this.health = batch.health;
524
+ this.serverAt = batch.generatedAt;
525
+ this.receivedAt = this.elapsed();
526
+ }
527
+ const selected = new Set(baseline ? requested.filter((id) => this.ids.includes(id)) : this.ids);
528
+ for (const row2 of batch.upserts)
529
+ if (selected.has(row2.sessionId) && (baseline || batch.sequence > (this.baselines.get(row2.sessionId) ?? -1)) && row2.revision > (this.revisions.get(row2.sessionId) ?? -1)) {
530
+ this.rows.set(row2.sessionId, row2);
531
+ this.revisions.set(row2.sessionId, row2.revision);
532
+ }
533
+ for (const row2 of batch.removed)
534
+ if (selected.has(row2.sessionId) && (baseline || batch.sequence > (this.baselines.get(row2.sessionId) ?? -1)) && row2.revision > (this.revisions.get(row2.sessionId) ?? -1)) {
535
+ this.rows.delete(row2.sessionId);
536
+ this.revisions.set(row2.sessionId, row2.revision);
537
+ }
538
+ this.changed();
539
+ }
540
+ refresh() {
541
+ if (this.reading)
542
+ return this.reading;
543
+ const controller = this.controller, generation = this.generation, location = this.location;
544
+ if (!controller)
545
+ return Promise.resolve();
546
+ clearTimeout(this.retryTimer);
547
+ const requestedIds = [...this.ids];
548
+ this.reading = (async () => {
549
+ const signal = AbortSignal.any([
550
+ controller.signal,
551
+ AbortSignal.timeout(5000)
552
+ ]);
553
+ const chunks = [];
554
+ for (let i = 0;i < requestedIds.length; i += 128)
555
+ chunks.push(requestedIds.slice(i, i + 128));
556
+ if (!chunks.length)
557
+ chunks.push([]);
558
+ const results = [];
559
+ for (let i = 0;i < chunks.length; i += 2) {
560
+ const reads = await Promise.all(chunks.slice(i, i + 2).map(async (ids) => {
561
+ const value = await abortable(this.transport.read(location, ids, signal), signal);
562
+ if (generation !== this.generation)
563
+ throw new Error("Obsolete read");
564
+ if (!isPerformanceResult(value)) {
565
+ this.connection = "incompatible";
566
+ throw new Error("Incompatible snapshot");
567
+ }
568
+ const answered = [
569
+ ...value.upserts.map((row2) => row2.sessionId),
570
+ ...value.unavailable.map((row2) => row2.sessionId)
571
+ ];
572
+ if (value.removed.length || answered.length !== ids.length || new Set(answered).size !== ids.length || answered.some((id) => !ids.includes(id)))
573
+ throw new Error("Incomplete snapshot read");
574
+ return { ids, result: value };
575
+ }));
576
+ results.push(...reads);
577
+ }
578
+ if (generation !== this.generation || signal.aborted)
579
+ return;
580
+ if (new Set(results.map((r) => r.result.epoch)).size !== 1)
581
+ throw new Error("Epoch changed during read");
582
+ for (const { ids, result } of results) {
583
+ this.merge(result, true, ids);
584
+ for (const missing of result.unavailable)
585
+ if (this.ids.includes(missing.sessionId))
586
+ this.rows.delete(missing.sessionId);
587
+ for (const id of ids)
588
+ if (this.ids.includes(id))
589
+ this.baselines.set(id, result.sequence);
590
+ }
591
+ const buffered = this.buffered;
592
+ this.buffered = [];
593
+ if (buffered.some((b) => b.epoch !== this.epoch) || this.overflow) {
594
+ this.overflow = false;
595
+ throw new Error("Stream changed during read");
596
+ }
597
+ for (const batch of buffered)
598
+ this.merge(batch);
599
+ this.failures = 0;
600
+ this.connection = this.refreshAgain || !this.subscriptionActive ? "reconnecting" : "live";
601
+ this.changed();
602
+ })().catch(() => {
603
+ if (generation === this.generation && !controller.signal.aborted) {
604
+ this.buffered = [];
605
+ this.overflow = false;
606
+ if (this.connection !== "incompatible")
607
+ this.connection = this.rows.size ? "reconnecting" : "unavailable";
608
+ this.changed();
609
+ this.retryTimer = setTimeout(() => {
610
+ this.refresh();
611
+ }, Math.min(30000, 1000 * 2 ** Math.min(this.failures++, 5)));
612
+ }
613
+ }).finally(() => {
614
+ if (generation === this.generation) {
615
+ this.reading = undefined;
616
+ if (this.refreshAgain) {
617
+ this.refreshAgain = false;
618
+ this.refresh();
619
+ }
620
+ }
621
+ });
622
+ return this.reading;
623
+ }
624
+ async supervise(generation, signal) {
625
+ let retry = 0;
626
+ while (!signal.aborted && generation === this.generation) {
627
+ try {
628
+ this.subscriptionActive = true;
629
+ if (retry)
630
+ queueMicrotask(() => {
631
+ if (generation === this.generation && !signal.aborted)
632
+ this.refresh();
633
+ });
634
+ for await (const value of this.transport.subscribe(this.location, signal)) {
635
+ if (signal.aborted || generation !== this.generation)
636
+ return;
637
+ if (!isPerformanceBatch(value)) {
638
+ this.connection = "incompatible";
639
+ this.changed();
640
+ continue;
641
+ }
642
+ if (this.reading || !this.epoch) {
643
+ if (this.buffered.length < 256)
644
+ this.buffered.push(value);
645
+ else
646
+ this.overflow = true;
647
+ } else {
648
+ this.merge(value);
649
+ if (this.refreshAgain) {
650
+ this.refreshAgain = false;
651
+ this.refresh();
652
+ }
653
+ }
654
+ retry = 0;
655
+ }
656
+ } catch {
657
+ }
658
+ if (signal.aborted || generation !== this.generation)
659
+ return;
660
+ this.subscriptionActive = false;
661
+ this.connection = "reconnecting";
662
+ this.changed();
663
+ await this.refresh();
664
+ await new Promise((resolve) => {
665
+ const finish = () => {
666
+ clearTimeout(timer);
667
+ signal.removeEventListener("abort", finish);
668
+ resolve();
669
+ };
670
+ const timer = setTimeout(finish, Math.min(30000, 1000 * 2 ** Math.min(retry++, 5)));
671
+ signal.addEventListener("abort", finish, { once: true });
672
+ if (signal.aborted)
673
+ finish();
674
+ });
675
+ }
676
+ }
677
+ stop() {
678
+ this.generation++;
679
+ this.controller?.abort();
680
+ this.controller = undefined;
681
+ clearInterval(this.refreshTimer);
682
+ clearTimeout(this.retryTimer);
683
+ this.reading = undefined;
684
+ this.buffered = [];
685
+ this.overflow = false;
686
+ this.refreshAgain = false;
687
+ }
688
+ }
689
+ function abortable(promise, signal) {
690
+ return new Promise((resolve, reject) => {
691
+ const abort = () => reject(new Error("Snapshot request cancelled"));
692
+ if (signal.aborted) {
693
+ abort();
694
+ return;
695
+ }
696
+ signal.addEventListener("abort", abort, { once: true });
697
+ promise.then(resolve, reject).finally(() => signal.removeEventListener("abort", abort));
698
+ });
699
+ }
700
+
701
+ // src/tui/inventory.ts
702
+ async function discoverFamily(client, currentId, signal, cached, maxNodes = 1024, previous) {
703
+ const sessions = new Map(previous?.sessions);
704
+ let damaged = previous?.damaged ?? false, pages = 0, rootId = previous?.rootId ?? currentId;
705
+ const get = async (id) => cached(id) ?? client.session.get({ sessionID: id }, { signal });
706
+ if (!previous) {
707
+ let current = await get(currentId);
708
+ for (let depth = 0;depth <= 64; depth++) {
709
+ if (signal.aborted)
710
+ throw signal.reason;
711
+ if (sessions.has(current.id) || sessions.size >= maxNodes) {
712
+ damaged = true;
713
+ break;
714
+ }
715
+ sessions.set(current.id, current);
716
+ rootId = current.id;
717
+ if (!current.parentID)
718
+ break;
719
+ if (depth === 64) {
720
+ damaged = true;
721
+ break;
722
+ }
723
+ try {
724
+ current = await get(current.parentID);
725
+ } catch {
726
+ if (signal.aborted)
727
+ throw signal.reason;
728
+ damaged = true;
729
+ break;
730
+ }
731
+ }
732
+ }
733
+ const queue = previous ? [...previous.pending] : [{ id: rootId, depth: 0, cursors: [] }];
734
+ const scanned = new Set(previous?.scanned);
735
+ while (queue.length && pages < 100) {
736
+ const next = queue.shift();
737
+ if (!next)
738
+ break;
739
+ if (!next.cursor && scanned.has(next.id)) {
740
+ damaged = true;
741
+ continue;
742
+ }
743
+ if (signal.aborted)
744
+ throw signal.reason;
745
+ try {
746
+ pages++;
747
+ const response = await client.session.list({
748
+ parentID: next.id,
749
+ limit: 100,
750
+ order: "desc",
751
+ ...next.cursor ? { cursor: next.cursor } : {}
752
+ }, { signal });
753
+ let capped = false;
754
+ for (const child of response.data) {
755
+ if (child.parentID !== next.id || child.id === next.id) {
756
+ damaged = true;
757
+ continue;
758
+ }
759
+ if (!sessions.has(child.id) && sessions.size >= maxNodes) {
760
+ capped = true;
761
+ break;
762
+ }
763
+ sessions.set(child.id, child);
764
+ if (!scanned.has(child.id) && !queue.some((branch) => branch.id === child.id)) {
765
+ if (next.depth < 64)
766
+ queue.push({ id: child.id, depth: next.depth + 1, cursors: [] });
767
+ else
768
+ damaged = true;
769
+ }
770
+ }
771
+ if (capped) {
772
+ queue.unshift(next);
773
+ break;
774
+ }
775
+ const cursor = response.cursor?.next;
776
+ if (cursor) {
777
+ if (next.cursors.includes(cursor) || cursor === next.cursor)
778
+ damaged = true;
779
+ else
780
+ queue.unshift({
781
+ ...next,
782
+ cursor,
783
+ cursors: [...next.cursors, cursor]
784
+ });
785
+ } else
786
+ scanned.add(next.id);
787
+ } catch {
788
+ if (signal.aborted)
789
+ throw signal.reason;
790
+ damaged = true;
791
+ }
792
+ }
793
+ return {
794
+ sessions,
795
+ complete: !damaged && !queue.length,
796
+ rootId,
797
+ pages: (previous?.pages ?? 0) + pages,
798
+ pending: queue,
799
+ scanned,
800
+ damaged
801
+ };
802
+ }
803
+
804
+ // src/tui/state.ts
805
+ function createGuardState(context) {
806
+ const [version, setVersion] = createSignal(0);
807
+ const notify = () => {
808
+ setVersion((n) => n + 1);
809
+ scheduleExpiry();
810
+ };
811
+ const rpc = context.client.rpc(monitorRpc);
812
+ const liveRpc = context.client.rpc(performanceRpc);
813
+ const clients = new Map;
814
+ let inventory, currentId = "", controller;
815
+ let scanGeneration = 0, scanning = false, maxNodes = 1024;
816
+ let scanFailed = false;
817
+ const consumers = new Map;
818
+ const [preferences, savePreferences] = context.storage.store("context-guard.ui.v1", { initial: { expanded: [] } });
819
+ const expanded = new Set(Array.isArray(preferences.expanded) ? preferences.expanded.filter((id) => typeof id === "string" && id.length <= 512).slice(0, 1024) : []);
820
+ const evidence = new Map;
821
+ let expiryTimer;
822
+ function scheduleExpiry() {
823
+ clearTimeout(expiryTimer);
824
+ if (!consumers.size)
825
+ return;
826
+ let delay = Infinity;
827
+ for (const client of clients.values())
828
+ for (const row2 of client.rows.values()) {
829
+ if (!row2.measurement || !client.health)
830
+ continue;
831
+ const remaining = row2.measurement.at + client.health.staleMs + 1 - client.now();
832
+ if (remaining > 0)
833
+ delay = Math.min(delay, remaining);
834
+ }
835
+ if (Number.isFinite(delay))
836
+ expiryTimer = setTimeout(notify, Math.min(2147483647, Math.ceil(delay)));
837
+ }
838
+ let ageTimer, scanTimer, eventTimer;
839
+ let unlisten;
840
+ function select() {
841
+ const id = [...consumers.values()].at(-1);
842
+ if (!id || id === currentId)
843
+ return;
844
+ controller?.abort();
845
+ scanGeneration++;
846
+ scanning = false;
847
+ for (const c of clients.values())
848
+ c.stop();
849
+ clients.clear();
850
+ currentId = id;
851
+ inventory = undefined;
852
+ evidence.clear();
853
+ maxNodes = 1024;
854
+ let scanFailed2 = false;
855
+ notify();
856
+ scan();
857
+ }
858
+ async function scan(more = false) {
859
+ if (scanning || !currentId || !consumers.size)
860
+ return;
861
+ scanning = true;
862
+ const generation = ++scanGeneration;
863
+ controller?.abort();
864
+ controller = new AbortController;
865
+ const signal = AbortSignal.any([
866
+ controller.signal,
867
+ AbortSignal.timeout(15000)
868
+ ]);
869
+ try {
870
+ const next = await discoverFamily(context.client, currentId, signal, (id) => context.data.session.get(id), maxNodes, more ? inventory : undefined);
871
+ if (signal.aborted || generation !== scanGeneration)
872
+ return;
873
+ inventory = next;
874
+ scanFailed = false;
875
+ for (const id of evidence.keys())
876
+ if (!next.sessions.has(id))
877
+ evidence.delete(id);
878
+ const groups = new Map;
879
+ for (const s of next.sessions.values()) {
880
+ const key = locationKey(s.location), group = groups.get(key) ?? { ids: [] };
881
+ group.ids.push(s.id);
882
+ groups.set(key, group);
883
+ }
884
+ for (const [key, c] of clients)
885
+ if (!groups.has(key)) {
886
+ c.stop();
887
+ clients.delete(key);
888
+ }
889
+ for (const [key, group] of groups) {
890
+ const sample = next.sessions.get(group.ids[0] ?? "");
891
+ if (!sample)
892
+ continue;
893
+ let c = clients.get(key);
894
+ if (!c) {
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;
906
+ c = new SnapshotClient({
907
+ read: (_key, ids, requestSignal) => source.read({ schemaVersion: 1, sessionIds: ids }, { location, signal: requestSignal }),
908
+ subscribe: async function* (_key, requestSignal) {
909
+ for await (const event of source.events.subscribe("changed", {
910
+ signal: requestSignal
911
+ })) {
912
+ if (event.location && sameLocation(event.location, location))
913
+ yield event.data;
914
+ }
915
+ }
916
+ }, notify);
917
+ clients.set(key, c);
918
+ c.start(key, group.ids);
919
+ } else
920
+ c.setIds(group.ids);
921
+ }
922
+ notify();
923
+ } catch {
924
+ if (generation === scanGeneration && !controller?.signal.aborted) {
925
+ scanFailed = true;
926
+ notify();
927
+ }
928
+ } finally {
929
+ if (generation === scanGeneration)
930
+ scanning = false;
931
+ }
932
+ }
933
+ function acquire(id) {
934
+ const token = Symbol();
935
+ consumers.set(token, id);
936
+ if (consumers.size === 1) {
937
+ ageTimer = setInterval(notify, 1000);
938
+ scanTimer = setInterval(() => {
939
+ scan();
940
+ }, 30000);
941
+ unlisten = context.data.listen(({ details: event }) => {
942
+ if (!("sessionID" in event.data) || !inventory?.sessions.has(event.data.sessionID)) {
943
+ if (event.type !== "session.created")
944
+ return;
945
+ }
946
+ if ("created" in event && "sessionID" in event.data && event.type.startsWith("session.execution.")) {
947
+ const id2 = event.data.sessionID, previous = evidence.get(id2) ?? {};
948
+ if (event.type === "session.execution.started")
949
+ evidence.set(id2, {
950
+ ...previous,
951
+ startedAt: Math.max(previous.startedAt ?? 0, event.created)
952
+ });
953
+ if ([
954
+ "session.execution.succeeded",
955
+ "session.execution.failed",
956
+ "session.execution.interrupted"
957
+ ].includes(event.type) && event.created >= (previous.terminalAt ?? 0)) {
958
+ const outcome = event.type === "session.execution.succeeded" ? "succeeded" : event.type === "session.execution.failed" ? "failed" : "interrupted";
959
+ evidence.set(id2, {
960
+ ...previous,
961
+ terminalAt: event.created,
962
+ outcome
963
+ });
964
+ }
965
+ notify();
966
+ }
967
+ if ([
968
+ "session.created",
969
+ "session.deleted",
970
+ "session.model.selected",
971
+ "session.agent.selected"
972
+ ].includes(event.type)) {
973
+ clearTimeout(eventTimer);
974
+ eventTimer = setTimeout(() => {
975
+ scan();
976
+ }, 100);
977
+ }
978
+ });
979
+ }
980
+ select();
981
+ return {
982
+ update(next) {
983
+ consumers.set(token, next);
984
+ select();
985
+ },
986
+ release() {
987
+ consumers.delete(token);
988
+ if (consumers.size)
989
+ select();
990
+ else
991
+ stop();
992
+ }
993
+ };
994
+ }
995
+ function stop() {
996
+ controller?.abort();
997
+ scanGeneration++;
998
+ scanning = false;
999
+ for (const c of clients.values())
1000
+ c.stop();
1001
+ clients.clear();
1002
+ clearTimeout(expiryTimer);
1003
+ clearInterval(ageTimer);
1004
+ clearInterval(scanTimer);
1005
+ clearTimeout(eventTimer);
1006
+ unlisten?.();
1007
+ unlisten = undefined;
1008
+ inventory = undefined;
1009
+ evidence.clear();
1010
+ currentId = "";
1011
+ }
1012
+ function rows(stableOrder = [], allDescendants = false) {
1013
+ version();
1014
+ const snapshots = new Map([...clients.values()].flatMap((c2) => [...c2.rows]));
1015
+ const nodes = [...inventory?.sessions.values() ?? []].map((original) => {
1016
+ const s = context.data.session.get(original.id) ?? original;
1017
+ const observed = evidence.get(s.id) ?? {};
1018
+ const metadataAt = s.time.idle ?? 0;
1019
+ const e = s.outcome && metadataAt >= (observed.terminalAt ?? 0) ? { ...observed, outcome: s.outcome, terminalAt: metadataAt } : observed;
1020
+ return {
1021
+ sessionId: s.id,
1022
+ ...s.parentID ? { parentId: s.parentID } : {},
1023
+ ...s.agent ? { agent: s.agent } : {},
1024
+ ...s.model ? { model: `${s.model.providerID}/${s.model.id}` } : {},
1025
+ ...e.outcome === "interrupted" ? { interrupted: true } : {},
1026
+ status: executionStatus(true, context.data.session.status(s.id) === "running", e)
1027
+ };
1028
+ });
1029
+ const c = [...clients.values()].find((c2) => c2.rows.has(currentId)) ?? clients.values().next().value;
1030
+ return familyRows(nodes, snapshots, currentId, c?.now() ?? Date.now(), c?.health?.staleMs ?? 300000, allDescendants ? new Set(nodes.map((node) => node.sessionId)) : expanded, maxNodes, (id) => {
1031
+ const session = inventory?.sessions.get(id);
1032
+ const client = session ? clients.get(locationKey(session.location)) : undefined;
1033
+ return {
1034
+ now: client?.now() ?? Date.now(),
1035
+ staleMs: client?.health?.staleMs ?? 300000
1036
+ };
1037
+ }, stableOrder);
1038
+ }
1039
+ return {
1040
+ version,
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
+ },
1051
+ rows,
1052
+ dispose: stop,
1053
+ age(id) {
1054
+ version();
1055
+ const c = [...clients.values()].find((c2) => c2.rows.has(id));
1056
+ const m = c?.rows.get(id)?.measurement;
1057
+ return c && m ? c.now() - m.at : 0;
1058
+ },
1059
+ health() {
1060
+ version();
1061
+ return [...clients.values()].find((c) => c.rows.has(currentId))?.health ?? clients.values().next().value?.health;
1062
+ },
1063
+ connection() {
1064
+ version();
1065
+ return clients.size ? [...clients.values()].map((c) => c.connection).find((c) => c !== "live") ?? "live" : scanFailed ? "unavailable" : "loading";
1066
+ },
1067
+ complete() {
1068
+ version();
1069
+ return inventory?.complete ?? false;
1070
+ },
1071
+ refresh() {
1072
+ scan();
1073
+ for (const c of clients.values())
1074
+ c.refresh();
1075
+ },
1076
+ loadMore() {
1077
+ maxNodes = Math.min(2048, maxNodes + 1024);
1078
+ scan(true);
1079
+ },
1080
+ expand(id) {
1081
+ if (expanded.has(id))
1082
+ expanded.delete(id);
1083
+ else if (expanded.size < 1024)
1084
+ expanded.add(id);
1085
+ savePreferences((draft) => {
1086
+ draft.expanded = [...expanded];
1087
+ }).catch(() => {
1088
+ });
1089
+ notify();
1090
+ }
1091
+ };
1092
+ }
1093
+ function locationKey(location) {
1094
+ return JSON.stringify([location.directory, location.workspaceID ?? null]);
1095
+ }
1096
+ function sameLocation(a, b) {
1097
+ if (a.workspaceID && b.workspaceID)
1098
+ return a.workspaceID === b.workspaceID;
1099
+ return a.directory === b.directory;
1100
+ }
1101
+
1102
+ // src/tui/sidebar.tsx
1103
+ import { effect as _$effect2 } from "@opentui/solid";
1104
+ import { createComponent as _$createComponent2 } from "@opentui/solid";
1105
+ import { insert as _$insert2 } from "@opentui/solid";
1106
+ import { createTextNode as _$createTextNode2 } from "@opentui/solid";
1107
+ import { insertNode as _$insertNode2 } from "@opentui/solid";
1108
+ import { memo as _$memo2 } from "@opentui/solid";
1109
+ import { setProp as _$setProp2 } from "@opentui/solid";
1110
+ import { createElement as _$createElement2 } from "@opentui/solid";
1111
+ import { For as For2, createEffect as createEffect2, createMemo as createMemo2, onCleanup, Show as Show2 } from "solid-js";
1112
+
1113
+ // src/tui/format.ts
1114
+ function quantity(n) {
1115
+ return n === undefined || !Number.isFinite(n) ? "?" : Math.abs(n) >= 1e6 ? `${(n / 1e6).toFixed(1)}m` : Math.abs(n) >= 1000 ? `${(n / 1000).toFixed(1)}k` : String(n);
1116
+ }
1117
+ var segmenter = new Intl.Segmenter(undefined, { granularity: "grapheme" });
1118
+ function cells(value) {
1119
+ return [...segmenter.segment(value)].reduce((n, { segment }) => n + graphemeWidth(segment), 0);
1120
+ }
1121
+ function graphemeWidth(s) {
1122
+ const cp = s.codePointAt(0) ?? 0;
1123
+ if (/^[\p{Mark}\u200d\ufe0f]+$/u.test(s))
1124
+ return 0;
1125
+ return /[\p{Extended_Pictographic}\p{Regional_Indicator}\u20e3]/u.test(s) || cp >= 4352 && (cp <= 4447 || cp >= 9001 && cp <= 9002 || cp >= 11904 && cp <= 42191 || cp >= 44032 && cp <= 55203 || cp >= 63744 && cp <= 64255 || cp >= 65040 && cp <= 65135 || cp >= 65281 && cp <= 65376 || cp >= 131072) ? 2 : 1;
1126
+ }
1127
+ function clip(value, width) {
1128
+ const clean = sanitize(value);
1129
+ if (cells(clean) <= width)
1130
+ return clean;
1131
+ let result = "", used = 0;
1132
+ for (const { segment } of segmenter.segment(clean)) {
1133
+ const size = graphemeWidth(segment);
1134
+ if (used + size > Math.max(0, width - 3))
1135
+ break;
1136
+ result += segment;
1137
+ used += size;
1138
+ }
1139
+ return width < 3 ? ".".repeat(Math.max(0, width)) : result + "...";
1140
+ }
1141
+ var statuses = {
1142
+ running: "RUN",
1143
+ idle: "IDLE",
1144
+ completed: "DONE",
1145
+ failed: "FAIL",
1146
+ unknown: "?"
1147
+ };
1148
+ function rowLines(row2, width) {
1149
+ const s = row2.snapshot, m = s?.measurement;
1150
+ const prefix = row2.current ? "* " : row2.depth > 5 ? ".../" : " ".repeat(Math.min(row2.depth, 5)) + "|-";
1151
+ const percent = row2.utilizationPercent === undefined ? "?" : `${row2.utilizationPercent.toFixed(1)}%`;
1152
+ const warning = row2.freshness === "stale" ? " STALE" : row2.pressure === "crossed" ? " !! crossed" : row2.pressure === "near" ? " ! near" : "";
1153
+ const suffix = ` ${statuses[row2.node.status]} ${m?.confidence === "approximate" ? "~" : ""}${percent}${warning}`;
1154
+ const label = clip(`${prefix}${row2.node.agent ?? row2.node.sessionId}`, Math.max(1, width - cells(suffix)));
1155
+ return [
1156
+ clip(`${label}${suffix}`, width),
1157
+ ...performanceLines(row2, width),
1158
+ ...wrap(`${quantity(m?.tokens)}/${quantity(s?.capacityTokens)} input; to ${quantity(row2.triggerDeltaTokens)}`, width),
1159
+ ...s && s.compaction.phase !== "idle" ? wrap(`compact: ${s.compaction.phase}`, width) : [],
1160
+ ...row2.node.interrupted ? [clip("interrupted", width)] : [],
1161
+ ...row2.hidden ? wrap(`+ ${row2.hidden} completed descendants`, width) : []
1162
+ ];
1163
+ }
1164
+ function detailLines(row2, width, expanded = false, ageMs = 0) {
1165
+ const s = row2?.snapshot, m = s?.measurement;
1166
+ if (!row2)
1167
+ return wrap("Loading session family...", width);
1168
+ if (!s)
1169
+ return [
1170
+ ...wrap(`${row2.node.agent ?? row2.node.sessionId} / ${row2.node.model ?? "?"}`, width),
1171
+ ...wrap("Guard: not monitored / unavailable", width)
1172
+ ];
1173
+ const threshold = s.policy.configured;
1174
+ const headroom = row2.triggerDeltaTokens;
1175
+ const lines = [
1176
+ `${s.agent ?? "?"} / ${s.model ?? "?"}`,
1177
+ ...performanceLines(row2, width),
1178
+ `${m?.confidence === "approximate" ? "~" : ""}${quantity(m?.tokens)} / ${quantity(s.capacityTokens)} input`,
1179
+ `${row2.utilizationPercent?.toFixed(1) ?? "?"}% ${row2.pressure === "crossed" ? "!! crossed" : row2.pressure === "near" ? "! near" : row2.freshness.toUpperCase()}`,
1180
+ `Trigger ${threshold.type === "ratio" ? `${threshold.value * 100}%` : `${threshold.value} ${threshold.type}`} (${s.policy.comparison})`,
1181
+ `Resolved trigger ${s.policy.triggerTokens ?? "?"}`,
1182
+ `Until trigger ${quantity(headroom)}`,
1183
+ `Compact: ${s.compaction.phase}`,
1184
+ `${s.compaction.armed ? "armed" : "disarmed"}${s.compaction.blockedReason ? ` / ${s.compaction.blockedReason}` : ""}`,
1185
+ m ? `${m.confidence} ${m.scope}` : "measurement unknown",
1186
+ m ? `${Math.floor(Math.max(0, ageMs) / 1000)}s ago; ${row2.freshness}` : ""
1187
+ ];
1188
+ if (expanded)
1189
+ 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)}`);
1190
+ return lines.filter(Boolean).flatMap((line) => wrap(line, width));
1191
+ }
1192
+ function sanitize(value) {
1193
+ return value.replace(/\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)/g, "").replace(/\x1b\[[0-?]*[ -/]*[@-~]/g, "").replace(/[\x00-\x1f\x7f-\x9f]/g, "");
1194
+ }
1195
+ function wrap(value, width) {
1196
+ const result = [];
1197
+ let part = "";
1198
+ for (const word of sanitize(value).split(" ")) {
1199
+ if (part && cells(part + " " + word) > width) {
1200
+ result.push(part);
1201
+ part = "";
1202
+ }
1203
+ for (const { segment } of segmenter.segment((part ? " " : "") + word)) {
1204
+ if (part && cells(part + segment) > width) {
1205
+ result.push(part);
1206
+ part = "";
1207
+ }
1208
+ part += cells(segment) <= width ? segment : "?";
1209
+ }
1210
+ }
1211
+ if (part)
1212
+ result.push(part);
1213
+ return result;
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
+ }
1226
+
1227
+ // src/tui/compact.tsx
1228
+ import { createTextNode as _$createTextNode } from "@opentui/solid";
1229
+ import { insertNode as _$insertNode } from "@opentui/solid";
1230
+ import { setProp as _$setProp } from "@opentui/solid";
1231
+ import { effect as _$effect } from "@opentui/solid";
1232
+ import { insert as _$insert } from "@opentui/solid";
1233
+ import { memo as _$memo } from "@opentui/solid";
1234
+ import { createElement as _$createElement } from "@opentui/solid";
1235
+ import { createComponent as _$createComponent } from "@opentui/solid";
1236
+ import { For, Index, Show, createEffect, createMemo, createSignal as createSignal2 } from "solid-js";
1237
+
1238
+ // src/tui/compactformat.ts
1239
+ function tokens(value) {
1240
+ if (value === undefined || !Number.isFinite(value))
1241
+ return "?";
1242
+ const magnitude = Math.abs(value);
1243
+ if (magnitude >= 1e6)
1244
+ return `${+(value / 1e6).toFixed(1)}M`;
1245
+ if (magnitude >= 1000)
1246
+ return `${+(value / 1000).toFixed(1)}K`;
1247
+ return String(value);
1248
+ }
1249
+ function columns(left, right, width) {
1250
+ if (cells(left) + cells(right) + 1 > width)
1251
+ return [...wrap(left, width), ...wrap(right, width)];
1252
+ return [left + " ".repeat(width - cells(left) - cells(right)) + right];
1253
+ }
1254
+ function progress(ratio, width) {
1255
+ const valid2 = ratio !== undefined && Number.isFinite(ratio) && ratio >= 0;
1256
+ const label = valid2 ? `${Math.round(ratio * 100)}%` : "?%";
1257
+ const size = Math.max(1, width - cells(label) - 1);
1258
+ const filled = valid2 ? Math.round(Math.min(1, ratio) * size) : 0;
1259
+ return clip("\u2588".repeat(filled) + "\u2591".repeat(size - filled) + " " + label, width);
1260
+ }
1261
+ function mainSections(row2, width, age) {
1262
+ if (!row2)
1263
+ return [{ tone: "base", lines: wrap("Loading session family...", width) }];
1264
+ const s = row2.snapshot, m = s?.measurement;
1265
+ const valid2 = row2.freshness === "fresh" || row2.freshness === "stale";
1266
+ const measured = valid2 ? m?.tokens : undefined;
1267
+ const ratio = (total) => measured !== undefined && total !== undefined && total > 0 ? measured / total : undefined;
1268
+ const remaining = measured !== undefined && s?.policy.triggerTokens !== undefined ? s.policy.triggerTokens - measured : undefined;
1269
+ return [
1270
+ ...row2.snapshot?.performance ? [{ tone: "base", lines: performanceLines(row2, width) }] : [],
1271
+ {
1272
+ tone: "execution",
1273
+ lines: columns(`${row2.node.agent ?? s?.agent ?? "Main"} \xB7 ${row2.node.model ?? s?.model ?? "?"}`, row2.node.status.toUpperCase(), width)
1274
+ },
1275
+ {
1276
+ tone: "base",
1277
+ lines: [
1278
+ "",
1279
+ ...columns("Context", `${tokens(measured)} / ${tokens(s?.capacityTokens)}`, width)
1280
+ ]
1281
+ },
1282
+ { tone: "pressure", lines: [progress(ratio(s?.capacityTokens), width)] },
1283
+ {
1284
+ tone: "base",
1285
+ lines: [
1286
+ "",
1287
+ ...columns("Compact threshold", `${tokens(measured)} / ${tokens(s?.policy.triggerTokens)}`, width)
1288
+ ]
1289
+ },
1290
+ {
1291
+ tone: "pressure",
1292
+ lines: [
1293
+ progress(ratio(s?.policy.triggerTokens), width),
1294
+ ...wrap(remaining === undefined ? "Compaction distance unknown" : remaining < 0 ? `${tokens(-remaining)} tokens over threshold` : `${tokens(remaining)} tokens until compaction`, width)
1295
+ ]
1296
+ },
1297
+ {
1298
+ tone: "measurement",
1299
+ lines: [
1300
+ "",
1301
+ ...wrap(`Measurement: ${m?.confidence ?? "unknown"}${m ? ` \xB7 ${Math.floor(Math.max(0, age) / 1000)}s ago` : ""}${row2.freshness !== "fresh" ? ` \xB7 ${row2.freshness.toUpperCase()}` : ""}`, width)
1302
+ ]
1303
+ },
1304
+ {
1305
+ tone: "base",
1306
+ lines: [
1307
+ ...s && s.compaction.phase !== "idle" ? wrap(`Compact: ${s.compaction.phase}`, width) : [],
1308
+ ...!s ? wrap("Guard: not monitored / unavailable", width) : []
1309
+ ]
1310
+ }
1311
+ ];
1312
+ }
1313
+ var statusSymbol = {
1314
+ running: "\u25CF",
1315
+ idle: "\u25CC",
1316
+ unknown: "\u25CC",
1317
+ completed: "\u2713",
1318
+ failed: "\u2715"
1319
+ };
1320
+ function agentHeading(row2, width) {
1321
+ const name = `${statusSymbol[row2.node.status]} ${row2.node.agent ?? row2.node.sessionId}`;
1322
+ const percent = row2.utilizationPercent === undefined ? "?%" : `${Math.round(row2.utilizationPercent)}%`;
1323
+ return cells(name) + cells(percent) + 1 <= width ? columns(name, percent, width)[0] ?? "" : clip(name, width);
1324
+ }
1325
+ function agentLines(row2, width) {
1326
+ const valid2 = row2.freshness === "fresh" || row2.freshness === "stale";
1327
+ const measured = valid2 ? row2.snapshot?.measurement?.tokens : undefined;
1328
+ const capacity = row2.snapshot?.capacityTokens;
1329
+ const pressure = row2.pressure === "crossed" ? "CRITICAL" : row2.pressure === "near" ? "WARNING" : "";
1330
+ return [
1331
+ ...performanceLines(row2, width),
1332
+ progress(measured !== undefined && capacity !== undefined && capacity > 0 ? measured / capacity : undefined, width),
1333
+ ...wrap(`${tokens(measured)} / ${tokens(capacity)}${pressure ? ` \xB7 ${pressure}` : ""}`, width),
1334
+ ...wrap(`${row2.node.status.toUpperCase()}${row2.freshness !== "fresh" ? ` \xB7 ${row2.freshness.toUpperCase()}` : ""}${row2.node.interrupted ? " \xB7 interrupted" : ""}`, width),
1335
+ ...row2.snapshot && row2.snapshot.compaction.phase !== "idle" ? wrap(`Compact: ${row2.snapshot.compaction.phase}`, width) : []
1336
+ ];
1337
+ }
1338
+ function subagentTree(rows, sessionID, collapsed, hideCompleted, byPressure) {
1339
+ const children = new Map;
1340
+ for (const row2 of rows) {
1341
+ if (!row2.node.parentId || row2.node.sessionId === sessionID)
1342
+ continue;
1343
+ const list = children.get(row2.node.parentId) ?? [];
1344
+ list.push(row2);
1345
+ children.set(row2.node.parentId, list);
1346
+ }
1347
+ const seen = new Set([sessionID]);
1348
+ const all = [];
1349
+ const build = (id, depth) => {
1350
+ if (depth > 64)
1351
+ return [];
1352
+ const branches2 = [];
1353
+ for (const row2 of children.get(id) ?? []) {
1354
+ if (seen.has(row2.node.sessionId))
1355
+ continue;
1356
+ seen.add(row2.node.sessionId);
1357
+ all.push(row2);
1358
+ const nested = build(row2.node.sessionId, depth + 1);
1359
+ if (hideCompleted && row2.node.status === "completed" && !nested.length)
1360
+ continue;
1361
+ branches2.push({
1362
+ row: row2,
1363
+ children: nested,
1364
+ active: row2.node.status === "running" || nested.some((b) => b.active),
1365
+ pressure: Math.max(row2.pressureRatio ?? -1, ...nested.map((b) => b.pressure))
1366
+ });
1367
+ }
1368
+ return branches2.sort((a, b) => Number(b.active) - Number(a.active) || (byPressure ? b.pressure - a.pressure : 0));
1369
+ };
1370
+ const branches = build(sessionID, 0);
1371
+ const visible = [];
1372
+ const visit = (items, depth) => {
1373
+ for (const item of items) {
1374
+ visible.push({
1375
+ row: item.row,
1376
+ depth,
1377
+ hasChildren: !!item.children.length
1378
+ });
1379
+ if (!collapsed.has(item.row.node.sessionId))
1380
+ visit(item.children, depth + 1);
1381
+ }
1382
+ };
1383
+ visit(branches, 0);
1384
+ return { all, visible };
1385
+ }
1386
+ function statistics(rows, sessionID, complete, width) {
1387
+ const sum = (value) => {
1388
+ const values = rows.map(value);
1389
+ return complete && values.length && values.every((n) => n !== undefined) ? values.reduce((total, n) => total + (n ?? 0), 0) : undefined;
1390
+ };
1391
+ return [
1392
+ ...columns("Compactions", tokens(sum((r) => r.snapshot?.compaction.count)), width),
1393
+ ...columns("Active subagents", String(rows.filter((r) => r.node.sessionId !== sessionID && r.node.status === "running").length), width),
1394
+ ...columns("Family peaks (sum)", tokens(sum((r) => r.snapshot?.peakTokens)), width),
1395
+ ...wrap("Observed \xB7 separate windows", width)
1396
+ ];
1397
+ }
1398
+
1399
+ // src/tui/compact.tsx
1400
+ function CompactSidebar(props) {
1401
+ const [measuredWidth, setMeasuredWidth] = createSignal2(props.width);
1402
+ const width = () => Math.max(1, Math.min(props.width, measuredWidth()) - 4);
1403
+ const [collapsed, setCollapsed] = createSignal2(new Set);
1404
+ const [hideCompleted, setHideCompleted] = createSignal2(false);
1405
+ const [byPressure, setByPressure] = createSignal2(true);
1406
+ createEffect(() => {
1407
+ props.sessionID;
1408
+ setCollapsed(new Set);
1409
+ });
1410
+ const current = createMemo(() => props.rows.find((r) => r.node.sessionId === props.sessionID));
1411
+ const tree = createMemo(() => subagentTree(props.rows, props.sessionID, collapsed(), hideCompleted(), byPressure()));
1412
+ const entries = createMemo(() => new Map(tree().visible.map((entry) => [entry.row.node.sessionId, entry])));
1413
+ const toggle = (id) => setCollapsed((previous) => {
1414
+ const next = new Set(previous);
1415
+ if (next.has(id))
1416
+ next.delete(id);
1417
+ else
1418
+ next.add(id);
1419
+ return next;
1420
+ });
1421
+ const toggleCompleted = () => setHideCompleted((value) => !value);
1422
+ const toggleSort = () => setByPressure((value) => !value);
1423
+ const toggleGroups = () => setCollapsed((previous) => previous.size ? new Set : new Set(tree().all.map((r) => r.node.sessionId)));
1424
+ props.context.keymap.layer(() => ({
1425
+ mode: "global",
1426
+ commands: [{
1427
+ id: "context-guard.completed",
1428
+ title: "Toggle completed subagents",
1429
+ group: "Context Guard",
1430
+ palette: true,
1431
+ bind: false,
1432
+ run: () => {
1433
+ toggleCompleted();
1434
+ }
1435
+ }, {
1436
+ id: "context-guard.sort",
1437
+ title: "Toggle subagent pressure sorting",
1438
+ group: "Context Guard",
1439
+ palette: true,
1440
+ bind: false,
1441
+ run: () => {
1442
+ toggleSort();
1443
+ }
1444
+ }, {
1445
+ id: "context-guard.groups",
1446
+ title: "Collapse or expand subagent groups",
1447
+ group: "Context Guard",
1448
+ palette: true,
1449
+ bind: false,
1450
+ run: () => {
1451
+ toggleGroups();
1452
+ }
1453
+ }]
1454
+ }));
1455
+ const colors = () => props.context.theme.text;
1456
+ const executionColor = (row2) => row2?.node.status === "running" ? colors().feedback.success.base : row2?.node.status === "failed" ? colors().feedback.error.base : colors().muted;
1457
+ const pressureColor = (row2) => row2?.freshness !== "fresh" || row2.pressure === "unknown" ? colors().muted : row2.pressure === "crossed" ? colors().feedback.error.base : row2.pressure === "near" ? colors().feedback.warning.base : colors().feedback.success.base;
1458
+ const rowColor = (row2) => row2.node.status === "failed" ? colors().feedback.error.base : row2.node.status === "completed" && row2.pressure === "normal" ? colors().muted : pressureColor(row2);
1459
+ function Lines(input) {
1460
+ return _$createComponent(Index, {
1461
+ get each() {
1462
+ return input.lines;
1463
+ },
1464
+ children: (line) => (() => {
1465
+ var _el$ = _$createElement("text");
1466
+ _$insert(_el$, () => line() || " ");
1467
+ _$effect((_$p) => _$setProp(_el$, "fg", colors().base, _$p));
1468
+ return _el$;
1469
+ })()
1470
+ });
1471
+ }
1472
+ const section = (title) => {
1473
+ const head = cells(`\u2500\u2500 ${title} `) > width() ? title : `\u2500\u2500 ${title} `;
1474
+ return clip(head, width()) + "\u2500".repeat(Math.max(0, width() - cells(head)));
1475
+ };
1476
+ const notices = createMemo(() => [...props.context.app.version !== "2.0.29" ? wrap(`Runtime ${props.context.app.version}: unverified`, width()) : [], ...props.state.connection() !== "live" ? wrap(`Connection: ${props.state.connection()}`, width()) : [], ...props.state.health()?.mode === "observe" ? ["Observe mode"] : [], ...props.state.health()?.enforcement === "disabled" || props.state.health()?.enforcement === "suspended" ? wrap(`Guard: ${props.state.health()?.enforcement}`, width()) : [], ...(props.state.health()?.reasons ?? []).flatMap((reason) => wrap(reason, width()))]);
1477
+ return (() => {
1478
+ var _el$2 = _$createElement("box"), _el$3 = _$createElement("text"), _el$5 = _$createElement("text"), _el$6 = _$createElement("text"), _el$8 = _$createElement("text"), _el$9 = _$createElement("text"), _el$0 = _$createElement("text"), _el$1 = _$createElement("text"), _el$11 = _$createElement("text"), _el$12 = _$createElement("text");
1479
+ _$insertNode(_el$2, _el$3);
1480
+ _$insertNode(_el$2, _el$5);
1481
+ _$insertNode(_el$2, _el$6);
1482
+ _$insertNode(_el$2, _el$8);
1483
+ _$insertNode(_el$2, _el$9);
1484
+ _$insertNode(_el$2, _el$0);
1485
+ _$insertNode(_el$2, _el$1);
1486
+ _$insertNode(_el$2, _el$11);
1487
+ _$insertNode(_el$2, _el$12);
1488
+ _$setProp(_el$2, "width", "100%");
1489
+ _$setProp(_el$2, "flexShrink", 0);
1490
+ _$setProp(_el$2, "border", true);
1491
+ _$setProp(_el$2, "borderStyle", "rounded");
1492
+ _$setProp(_el$2, "title", " CONTEXT GUARD ");
1493
+ _$setProp(_el$2, "paddingX", 1);
1494
+ _$setProp(_el$2, "flexDirection", "column");
1495
+ _$setProp(_el$2, "onSizeChange", function() {
1496
+ setMeasuredWidth(this.width);
1497
+ });
1498
+ _$insert(_el$2, _$createComponent(Index, {
1499
+ get each() {
1500
+ return mainSections(current(), width(), props.state.age(props.sessionID));
1501
+ },
1502
+ children: (section2) => _$createComponent(Index, {
1503
+ get each() {
1504
+ return section2().lines;
1505
+ },
1506
+ children: (line) => (() => {
1507
+ var _el$13 = _$createElement("text");
1508
+ _$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));
1510
+ return _el$13;
1511
+ })()
1512
+ })
1513
+ }), _el$3);
1514
+ _$insert(_el$2, _$createComponent(Index, {
1515
+ get each() {
1516
+ return notices();
1517
+ },
1518
+ children: (line) => (() => {
1519
+ var _el$14 = _$createElement("text");
1520
+ _$insert(_el$14, line);
1521
+ _$effect((_$p) => _$setProp(_el$14, "fg", colors().feedback.warning.base, _$p));
1522
+ return _el$14;
1523
+ })()
1524
+ }), _el$3);
1525
+ _$insertNode(_el$3, _$createTextNode(` `));
1526
+ _$insert(_el$5, () => section(`SUBAGENTS (${tree().all.length})`));
1527
+ _$insert(_el$2, _$createComponent(Lines, {
1528
+ get lines() {
1529
+ return wrap(`${tree().all.filter((r) => r.node.status === "running").length} running \xB7 ${tree().all.filter((r) => r.node.status === "completed").length} completed${props.state.complete() ? "" : " \xB7 partial"}`, width());
1530
+ }
1531
+ }), _el$6);
1532
+ _$insertNode(_el$6, _$createTextNode(` `));
1533
+ _$insert(_el$2, _$createComponent(For, {
1534
+ get each() {
1535
+ return tree().visible.map((entry) => entry.row.node.sessionId);
1536
+ },
1537
+ children: (id) => {
1538
+ const entry = () => entries().get(id);
1539
+ const row2 = () => entry()?.row;
1540
+ const indent = () => " ".repeat(Math.min(entry()?.depth ?? 0, Math.floor(width() / 6)) * 2);
1541
+ const prefix = () => `${indent()}${entry()?.depth ? "\u2514\u2500 " : ""}${entry()?.hasChildren ? collapsed().has(id) ? "\u25B8 " : "\u25BE " : ""}`;
1542
+ const nameWidth = () => Math.max(1, width() - cells(prefix()));
1543
+ return _$createComponent(Show, {
1544
+ get when() {
1545
+ return row2();
1546
+ },
1547
+ children: (item) => (() => {
1548
+ var _el$15 = _$createElement("box"), _el$16 = _$createElement("box"), _el$18 = _$createElement("text"), _el$19 = _$createElement("span"), _el$20 = _$createElement("span"), _el$21 = _$createElement("text");
1549
+ _$insertNode(_el$15, _el$16);
1550
+ _$insertNode(_el$15, _el$21);
1551
+ _$setProp(_el$15, "flexDirection", "column");
1552
+ _$setProp(_el$15, "flexShrink", 0);
1553
+ _$insertNode(_el$16, _el$18);
1554
+ _$setProp(_el$16, "flexDirection", "row");
1555
+ _$setProp(_el$16, "flexShrink", 0);
1556
+ _$insert(_el$16, _$createComponent(Show, {
1557
+ get when() {
1558
+ return prefix();
1559
+ },
1560
+ get children() {
1561
+ var _el$17 = _$createElement("text");
1562
+ _$setProp(_el$17, "onMouseDown", () => toggle(id));
1563
+ _$insert(_el$17, prefix);
1564
+ _$effect((_$p) => _$setProp(_el$17, "fg", colors().muted, _$p));
1565
+ return _el$17;
1566
+ }
1567
+ }), _el$18);
1568
+ _$insertNode(_el$18, _el$19);
1569
+ _$insertNode(_el$18, _el$20);
1570
+ _$setProp(_el$18, "onMouseDown", () => props.context.ui.router.navigate({
1571
+ type: "session",
1572
+ sessionID: id
1573
+ }));
1574
+ _$insert(_el$19, () => agentHeading(item(), nameWidth()).slice(0, 1));
1575
+ _$insert(_el$20, () => agentHeading(item(), nameWidth()).slice(1));
1576
+ _$insert(_el$15, _$createComponent(Index, {
1577
+ get each() {
1578
+ return agentLines(item(), Math.max(1, width() - cells(indent()) - 2));
1579
+ },
1580
+ children: (line) => (() => {
1581
+ var _el$23 = _$createElement("text"), _el$24 = _$createTextNode(` `);
1582
+ _$insertNode(_el$23, _el$24);
1583
+ _$insert(_el$23, indent, _el$24);
1584
+ _$insert(_el$23, line, null);
1585
+ _$effect((_$p) => _$setProp(_el$23, "fg", rowColor(item()), _$p));
1586
+ return _el$23;
1587
+ })()
1588
+ }), _el$21);
1589
+ _$insertNode(_el$21, _$createTextNode(` `));
1590
+ _$effect((_p$) => {
1591
+ var _v$0 = rowColor(item()), _v$1 = {
1592
+ fg: executionColor(item())
1593
+ };
1594
+ _v$0 !== _p$.e && (_p$.e = _$setProp(_el$18, "fg", _v$0, _p$.e));
1595
+ _v$1 !== _p$.t && (_p$.t = _$setProp(_el$19, "style", _v$1, _p$.t));
1596
+ return _p$;
1597
+ }, {
1598
+ e: undefined,
1599
+ t: undefined
1600
+ });
1601
+ return _el$15;
1602
+ })()
1603
+ });
1604
+ }
1605
+ }), _el$8);
1606
+ _$setProp(_el$8, "onMouseDown", toggleCompleted);
1607
+ _$insert(_el$8, () => clip(hideCompleted() ? "Show completed" : "Hide completed", width()));
1608
+ _$setProp(_el$9, "onMouseDown", toggleSort);
1609
+ _$insert(_el$9, () => clip(byPressure() ? "Sort: pressure" : "Sort: activity", width()));
1610
+ _$setProp(_el$0, "onMouseDown", toggleGroups);
1611
+ _$insert(_el$0, () => clip(collapsed().size ? "Expand groups" : "Collapse groups", width()));
1612
+ _$insertNode(_el$1, _$createTextNode(` `));
1613
+ _$insert(_el$11, () => section("STATISTICS"));
1614
+ _$insert(_el$2, _$createComponent(Lines, {
1615
+ get lines() {
1616
+ return statistics(props.rows.filter((r) => r.node.sessionId === props.sessionID).concat(tree().all), props.sessionID, props.state.complete(), width());
1617
+ }
1618
+ }), _el$12);
1619
+ _$setProp(_el$12, "onMouseDown", () => props.context.ui.panel.open("context-guard.monitor"));
1620
+ _$insert(_el$12, () => clip("Open: /context-guard", width()));
1621
+ _$effect((_p$) => {
1622
+ var _v$ = props.width, _v$2 = colors().muted, _v$3 = colors().base, _v$4 = colors().muted, _v$5 = colors().muted, _v$6 = colors().muted, _v$7 = colors().muted, _v$8 = colors().muted, _v$9 = colors().muted;
1623
+ _v$ !== _p$.e && (_p$.e = _$setProp(_el$2, "maxWidth", _v$, _p$.e));
1624
+ _v$2 !== _p$.t && (_p$.t = _$setProp(_el$2, "borderColor", _v$2, _p$.t));
1625
+ _v$3 !== _p$.a && (_p$.a = _$setProp(_el$2, "titleColor", _v$3, _p$.a));
1626
+ _v$4 !== _p$.o && (_p$.o = _$setProp(_el$5, "fg", _v$4, _p$.o));
1627
+ _v$5 !== _p$.i && (_p$.i = _$setProp(_el$8, "fg", _v$5, _p$.i));
1628
+ _v$6 !== _p$.n && (_p$.n = _$setProp(_el$9, "fg", _v$6, _p$.n));
1629
+ _v$7 !== _p$.s && (_p$.s = _$setProp(_el$0, "fg", _v$7, _p$.s));
1630
+ _v$8 !== _p$.h && (_p$.h = _$setProp(_el$11, "fg", _v$8, _p$.h));
1631
+ _v$9 !== _p$.r && (_p$.r = _$setProp(_el$12, "fg", _v$9, _p$.r));
1632
+ return _p$;
1633
+ }, {
1634
+ e: undefined,
1635
+ t: undefined,
1636
+ a: undefined,
1637
+ o: undefined,
1638
+ i: undefined,
1639
+ n: undefined,
1640
+ s: undefined,
1641
+ h: undefined,
1642
+ r: undefined
1643
+ });
1644
+ return _el$2;
1645
+ })();
1646
+ }
1647
+
1648
+ // src/tui/sidebar.tsx
1649
+ function GuardView(props) {
1650
+ const consumer = props.state.acquire(props.sessionID);
1651
+ createEffect2(() => consumer.update(props.sessionID));
1652
+ onCleanup(() => consumer.release());
1653
+ const rows = createMemo2(() => props.family ?? props.state.rows([], !props.expanded));
1654
+ const current = createMemo2(() => rows().find((row2) => row2.node.sessionId === props.sessionID));
1655
+ const detail = createMemo2(() => {
1656
+ const lines = detailLines(current(), props.width, props.expanded, props.state.age(props.sessionID));
1657
+ if (props.expanded) {
1658
+ const byId = new Map(rows().map((row2) => [row2.node.sessionId, row2.node]));
1659
+ const ancestry = [], seen = new Set;
1660
+ let node = byId.get(props.sessionID);
1661
+ while (node && !seen.has(node.sessionId)) {
1662
+ seen.add(node.sessionId);
1663
+ ancestry.unshift(node.agent ?? node.sessionId);
1664
+ node = node.parentId ? byId.get(node.parentId) : undefined;
1665
+ }
1666
+ lines.push(...wrap(`Ancestry: ${ancestry.join(" / ")}`, props.width));
1667
+ }
1668
+ if (props.inspectId && props.inspectId !== props.sessionID) {
1669
+ const inspected = rows().find((row2) => row2.node.sessionId === props.inspectId);
1670
+ lines.push(...wrap(`Inspect: ${props.inspectId}`, props.width), ...detailLines(inspected, props.width, true, props.state.age(props.inspectId)));
1671
+ }
1672
+ return lines;
1673
+ });
1674
+ const visible = createMemo2(() => {
1675
+ if (!props.expanded)
1676
+ return rows().slice(0, 7);
1677
+ const index = Math.max(0, rows().findIndex((row2) => row2.node.sessionId === props.selectedId));
1678
+ return rows().slice(Math.max(0, index - 10), Math.max(0, index - 10) + 25);
1679
+ });
1680
+ return _$createComponent2(Show2, {
1681
+ get when() {
1682
+ return props.expanded;
1683
+ },
1684
+ get fallback() {
1685
+ return _$createComponent2(CompactSidebar, {
1686
+ get sessionID() {
1687
+ return props.sessionID;
1688
+ },
1689
+ get state() {
1690
+ return props.state;
1691
+ },
1692
+ get context() {
1693
+ return props.context;
1694
+ },
1695
+ get rows() {
1696
+ return rows();
1697
+ },
1698
+ get width() {
1699
+ return props.width;
1700
+ }
1701
+ });
1702
+ },
1703
+ get children() {
1704
+ var _el$ = _$createElement2("box"), _el$2 = _$createElement2("text"), _el$3 = _$createTextNode2(`Context Guard / `), _el$4 = _$createElement2("text"), _el$5 = _$createElement2("text"), _el$6 = _$createElement2("text"), _el$7 = _$createElement2("text"), _el$8 = _$createTextNode2(`Family (`), _el$9 = _$createTextNode2(`)`), _el$0 = _$createElement2("text");
1705
+ _$insertNode2(_el$, _el$2);
1706
+ _$insertNode2(_el$, _el$4);
1707
+ _$insertNode2(_el$, _el$5);
1708
+ _$insertNode2(_el$, _el$6);
1709
+ _$insertNode2(_el$, _el$7);
1710
+ _$insertNode2(_el$, _el$0);
1711
+ _$setProp2(_el$, "flexDirection", "column");
1712
+ _$setProp2(_el$, "gap", 0);
1713
+ _$insertNode2(_el$2, _el$3);
1714
+ _$insert2(_el$2, () => props.state.health()?.mode ?? "?", null);
1715
+ _$insert2(_el$4, (() => {
1716
+ var _c$ = _$memo2(() => props.context.app.version === "2.0.29");
1717
+ return () => _c$() ? "" : clip(`Runtime ${props.context.app.version}: unverified (2.0.29 target)`, props.width);
1718
+ })());
1719
+ _$insert2(_el$5, () => clip(`${props.state.connection()} / ${props.state.health()?.enforcement ?? "?"}`, props.width));
1720
+ _$insert2(_el$, _$createComponent2(For2, {
1721
+ get each() {
1722
+ return detail();
1723
+ },
1724
+ children: (line) => (() => {
1725
+ var _el$1 = _$createElement2("text");
1726
+ _$insert2(_el$1, line);
1727
+ _$effect2((_$p) => _$setProp2(_el$1, "fg", props.context.theme.text.base, _$p));
1728
+ return _el$1;
1729
+ })()
1730
+ }), _el$6);
1731
+ _$insert2(_el$, _$createComponent2(For2, {
1732
+ get each() {
1733
+ return props.state.health()?.reasons ?? [];
1734
+ },
1735
+ children: (reason) => (() => {
1736
+ var _el$10 = _$createElement2("text");
1737
+ _$insert2(_el$10, () => clip(reason, props.width));
1738
+ _$effect2((_$p) => _$setProp2(_el$10, "fg", props.context.theme.text.feedback.warning.base, _$p));
1739
+ return _el$10;
1740
+ })()
1741
+ }), _el$6);
1742
+ _$insert2(_el$6, (() => {
1743
+ var _c$2 = _$memo2(() => props.state.health()?.mode === "observe");
1744
+ return () => _c$2() ? current()?.pressure === "crossed" ? "Observe: would compact" : "Observe: no admission" : "";
1745
+ })());
1746
+ _$insertNode2(_el$7, _el$8);
1747
+ _$insertNode2(_el$7, _el$9);
1748
+ _$insert2(_el$7, () => rows().length, _el$9);
1749
+ _$insert2(_el$7, () => props.state.complete() ? "" : " partial", null);
1750
+ _$insert2(_el$, _$createComponent2(For2, {
1751
+ get each() {
1752
+ return visible();
1753
+ },
1754
+ children: (row2) => (() => {
1755
+ var _el$11 = _$createElement2("box");
1756
+ _$setProp2(_el$11, "flexDirection", "column");
1757
+ _$setProp2(_el$11, "onMouseDown", () => {
1758
+ if (row2.hidden)
1759
+ props.state.expand(row2.node.sessionId);
1760
+ else {
1761
+ if (props.expanded)
1762
+ props.context.ui.panel.close();
1763
+ props.context.ui.router.navigate({
1764
+ type: "session",
1765
+ sessionID: row2.node.sessionId
1766
+ });
1767
+ }
1768
+ });
1769
+ _$insert2(_el$11, _$createComponent2(For2, {
1770
+ get each() {
1771
+ return rowLines(row2, Math.max(1, props.width - (props.expanded ? 2 : 0))).map((line) => props.expanded ? `${row2.node.sessionId === props.selectedId ? ">" : " "} ${line}` : line);
1772
+ },
1773
+ children: (line) => (() => {
1774
+ var _el$12 = _$createElement2("text");
1775
+ _$insert2(_el$12, line);
1776
+ _$effect2((_$p) => _$setProp2(_el$12, "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));
1777
+ return _el$12;
1778
+ })()
1779
+ }));
1780
+ _$effect2((_$p) => _$setProp2(_el$11, "id", `context-guard.row.${row2.node.sessionId}`, _$p));
1781
+ return _el$11;
1782
+ })()
1783
+ }), _el$0);
1784
+ _$setProp2(_el$0, "onMouseDown", () => props.context.ui.panel.open("context-guard.monitor"));
1785
+ _$insert2(_el$0, (() => {
1786
+ var _c$3 = _$memo2(() => rows().length > visible().length);
1787
+ return () => _c$3() ? `+ ${rows().length - visible().length} rows; open panel` : "Open: /context-guard";
1788
+ })());
1789
+ _$effect2((_p$) => {
1790
+ var _v$ = props.context.theme.text.base, _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.base, _v$6 = props.context.theme.text.muted;
1791
+ _v$ !== _p$.e && (_p$.e = _$setProp2(_el$2, "fg", _v$, _p$.e));
1792
+ _v$2 !== _p$.t && (_p$.t = _$setProp2(_el$4, "fg", _v$2, _p$.t));
1793
+ _v$3 !== _p$.a && (_p$.a = _$setProp2(_el$5, "fg", _v$3, _p$.a));
1794
+ _v$4 !== _p$.o && (_p$.o = _$setProp2(_el$6, "fg", _v$4, _p$.o));
1795
+ _v$5 !== _p$.i && (_p$.i = _$setProp2(_el$7, "fg", _v$5, _p$.i));
1796
+ _v$6 !== _p$.n && (_p$.n = _$setProp2(_el$0, "fg", _v$6, _p$.n));
1797
+ return _p$;
1798
+ }, {
1799
+ e: undefined,
1800
+ t: undefined,
1801
+ a: undefined,
1802
+ o: undefined,
1803
+ i: undefined,
1804
+ n: undefined
1805
+ });
1806
+ return _el$;
1807
+ }
1808
+ });
1809
+ }
1810
+
1811
+ // src/tui/panel.tsx
1812
+ import { effect as _$effect4 } from "@opentui/solid";
1813
+ import { createTextNode as _$createTextNode3 } from "@opentui/solid";
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";
1826
+ import { createComponent as _$createComponent3 } from "@opentui/solid";
1827
+ import { setProp as _$setProp3 } from "@opentui/solid";
1828
+ import { createElement as _$createElement3 } from "@opentui/solid";
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
1986
+ function GuardPanel(props) {
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([]);
1994
+ const rows = createMemo3(() => props.state.rows(order()));
1995
+ const offset = () => Math.max(0, rows().findIndex((row2) => row2.node.sessionId === selected()));
1996
+ createEffect4(() => {
1997
+ if (!props.input.focused)
1998
+ setOrder([]);
1999
+ });
2000
+ createEffect4(() => {
2001
+ setSelected(props.input.sessionID);
2002
+ setOrder([]);
2003
+ });
2004
+ let scroll;
2005
+ let follow;
2006
+ function revealSelection() {
2007
+ if (follow)
2008
+ props.context.renderer.removeFrameCallback(follow);
2009
+ let frames = 0;
2010
+ follow = async () => {
2011
+ scroll?.scrollChildIntoView(`context-guard.row.${selected()}`);
2012
+ if (++frames >= 2 && follow) {
2013
+ props.context.renderer.removeFrameCallback(follow);
2014
+ follow = undefined;
2015
+ } else
2016
+ props.context.renderer.requestRender();
2017
+ };
2018
+ props.context.renderer.setFrameCallback(follow);
2019
+ props.context.renderer.requestRender();
2020
+ }
2021
+ onCleanup3(() => {
2022
+ if (follow)
2023
+ props.context.renderer.removeFrameCallback(follow);
2024
+ });
2025
+ function move(delta) {
2026
+ setOrder(rows().map((row3) => row3.node.sessionId));
2027
+ const row2 = rows()[Math.max(0, Math.min(rows().length - 1, offset() + delta))];
2028
+ if (row2) {
2029
+ setSelected(row2.node.sessionId);
2030
+ if (!showCharts())
2031
+ revealSelection();
2032
+ }
2033
+ }
2034
+ props.context.keymap.layer(() => ({
2035
+ enabled: () => props.input.focused,
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
+ }, {
2058
+ id: "context-guard.inspect",
2059
+ title: "Inspect selected session",
2060
+ bind: "i",
2061
+ run: () => setInspectId(inspectId() === selected() ? undefined : selected())
2062
+ }, {
2063
+ id: "context-guard.refresh",
2064
+ title: "Refresh Context Guard",
2065
+ bind: "r",
2066
+ run: () => {
2067
+ setOrder([]);
2068
+ props.state.refresh();
2069
+ setChartRefresh((n) => n + 1);
2070
+ }
2071
+ }, {
2072
+ id: "context-guard.load-more",
2073
+ title: "Load more family",
2074
+ bind: "l",
2075
+ run: () => props.state.loadMore()
2076
+ }, {
2077
+ id: "context-guard.next",
2078
+ bind: "down",
2079
+ run: () => move(1)
2080
+ }, {
2081
+ id: "context-guard.previous",
2082
+ bind: "up",
2083
+ run: () => move(-1)
2084
+ }, {
2085
+ id: "context-guard.expand",
2086
+ bind: "space",
2087
+ run: () => {
2088
+ const row2 = rows()[offset()];
2089
+ if (row2)
2090
+ props.state.expand(row2.node.sessionId);
2091
+ }
2092
+ }, {
2093
+ id: "context-guard.open",
2094
+ bind: "return",
2095
+ run: () => {
2096
+ const row2 = rows()[offset()];
2097
+ if (row2) {
2098
+ props.input.close();
2099
+ props.context.ui.router.navigate({
2100
+ type: "session",
2101
+ sessionID: row2.node.sessionId
2102
+ });
2103
+ }
2104
+ }
2105
+ }]
2106
+ }));
2107
+ return (() => {
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");
2109
+ _$insertNode3(_el$, _el$2);
2110
+ _$insertNode3(_el$, _el$6);
2111
+ _$insertNode3(_el$, _el$8);
2112
+ _$insertNode3(_el$, _el$1);
2113
+ _$insertNode3(_el$, _el$11);
2114
+ _$insertNode3(_el$, _el$13);
2115
+ _$use((value) => {
2116
+ scroll = value;
2117
+ }, _el$);
2118
+ _$setProp4(_el$, "flexGrow", 1);
2119
+ _$setProp4(_el$2, "flexDirection", "column");
2120
+ _$insert4(_el$2, _$createComponent4(GuardView, {
2121
+ get sessionID() {
2122
+ return props.input.sessionID;
2123
+ },
2124
+ get state() {
2125
+ return props.state;
2126
+ },
2127
+ get context() {
2128
+ return props.context;
2129
+ },
2130
+ get width() {
2131
+ return Math.max(10, props.input.width - 4);
2132
+ },
2133
+ expanded: true,
2134
+ get family() {
2135
+ return rows();
2136
+ },
2137
+ get selectedId() {
2138
+ return selected();
2139
+ },
2140
+ get inspectId() {
2141
+ return inspectId();
2142
+ }
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));
2192
+ return _p$;
2193
+ }, {
2194
+ e: undefined,
2195
+ t: undefined,
2196
+ a: undefined,
2197
+ o: undefined,
2198
+ i: undefined,
2199
+ n: undefined
2200
+ });
2201
+ return _el$;
2202
+ })();
2203
+ }
2204
+
2205
+ // src/tui.tsx
2206
+ var tui_default = Plugin.define({
2207
+ id: "context-guard",
2208
+ setup(context) {
2209
+ if (typeof context.client?.rpc !== "function" || typeof context.ui?.slot !== "function" || typeof context.ui?.panel?.open !== "function" || typeof context.keymap?.layer !== "function") {
2210
+ throw new Error("Context Guard TUI requires OpenCode 2.0.29 public RPC and panel APIs; use the Guard inspect CLI on unsupported hosts.");
2211
+ }
2212
+ const state = createGuardState(context), unregister = [];
2213
+ try {
2214
+ unregister.push(context.ui.slot({
2215
+ append: "sidebar.content",
2216
+ render: (input) => _$createComponent5(GuardView, {
2217
+ get sessionID() {
2218
+ return input.sessionID;
2219
+ },
2220
+ state,
2221
+ context,
2222
+ width: 35
2223
+ })
2224
+ }));
2225
+ unregister.push(context.ui.slot({
2226
+ append: "session.panel",
2227
+ render: (input) => _$createComponent5(Show4, {
2228
+ get when() {
2229
+ return input.name === "context-guard.monitor";
2230
+ },
2231
+ get children() {
2232
+ return _$createComponent5(GuardPanel, {
2233
+ input,
2234
+ state,
2235
+ context
2236
+ });
2237
+ }
2238
+ })
2239
+ }));
2240
+ unregister.push(context.ui.slot({
2241
+ append: "session.composer.top",
2242
+ render: (input) => _$createComponent5(Show4, {
2243
+ get when() {
2244
+ return context.data.session.get(input.sessionID)?.parentID;
2245
+ },
2246
+ get children() {
2247
+ var _el$ = _$createElement5("text");
2248
+ _$insertNode4(_el$, _$createTextNode4(`Context Guard: open panel`));
2249
+ _$setProp5(_el$, "onMouseDown", () => context.ui.panel.open("context-guard.monitor"));
2250
+ _$effect5((_$p) => _$setProp5(_el$, "fg", context.theme.text.muted, _$p));
2251
+ return _el$;
2252
+ }
2253
+ })
2254
+ }));
2255
+ context.keymap.layer(() => ({
2256
+ mode: "global",
2257
+ commands: [{
2258
+ id: "context-guard.show",
2259
+ title: "Open Context Guard",
2260
+ group: "Context Guard",
2261
+ palette: true,
2262
+ bind: false,
2263
+ slash: {
2264
+ name: "context-guard"
2265
+ },
2266
+ run: () => {
2267
+ context.ui.panel.open("context-guard.monitor");
2268
+ }
2269
+ }]
2270
+ }));
2271
+ } catch (error) {
2272
+ for (const dispose of unregister.reverse())
2273
+ dispose();
2274
+ state.dispose();
2275
+ throw error;
2276
+ }
2277
+ return () => {
2278
+ for (const dispose of unregister.reverse())
2279
+ dispose();
2280
+ state.dispose();
2281
+ };
2282
+ }
2283
+ });
2284
+ export {
2285
+ tui_default as default
2286
+ };