@cesarus777/opencode-cgp 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/tui.js ADDED
@@ -0,0 +1,1874 @@
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
+ function record(x) {
118
+ return typeof x === "object" && x !== null && !Array.isArray(x);
119
+ }
120
+ function matches(value, schema) {
121
+ if (!record(schema))
122
+ return false;
123
+ if ("const" in schema && value !== schema.const)
124
+ return false;
125
+ if (Array.isArray(schema.enum) && !schema.enum.includes(value))
126
+ return false;
127
+ if (schema.type === "number" || schema.type === "integer")
128
+ 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);
129
+ if (schema.type === "string")
130
+ return typeof value === "string" && value.length > 0 && value.length <= 512;
131
+ if (schema.type === "boolean")
132
+ return typeof value === "boolean";
133
+ if (schema.type === "array")
134
+ return Array.isArray(value) && value.length <= Number(schema.maxItems) && (!schema.uniqueItems || new Set(value).size === value.length) && value.every((v) => matches(v, schema.items));
135
+ if (schema.type === "object") {
136
+ if (!record(value) || !record(schema.properties) || !Array.isArray(schema.required))
137
+ return false;
138
+ const properties = schema.properties;
139
+ return schema.required.every((k) => typeof k === "string" && (k in value)) && Object.keys(value).every((k) => (k in properties) && matches(value[k], properties[k]));
140
+ }
141
+ return "const" in schema;
142
+ }
143
+ function valid(value, schema) {
144
+ try {
145
+ return JSON.stringify(value).length <= 524288 && matches(value, schema);
146
+ } catch {
147
+ return false;
148
+ }
149
+ }
150
+ function isRead(value) {
151
+ return valid(value, readSchema);
152
+ }
153
+ function isBatch(value) {
154
+ return valid(value, batchSchema) && uniqueRows(value);
155
+ }
156
+ function isResult(value) {
157
+ return valid(value, resultSchema) && uniqueRows(value);
158
+ }
159
+ function uniqueRows(value) {
160
+ if (!record(value) || !Array.isArray(value.upserts) || !Array.isArray(value.removed))
161
+ return false;
162
+ const ids = [...value.upserts, ...value.removed].map((v) => record(v) ? v.sessionId : undefined);
163
+ return ids.length <= 128 && new Set(ids).size === ids.length;
164
+ }
165
+
166
+ // src/tui.tsx
167
+ import { effect as _$effect4 } from "@opentui/solid";
168
+ import { createTextNode as _$createTextNode4 } from "@opentui/solid";
169
+ import { insertNode as _$insertNode4 } from "@opentui/solid";
170
+ import { setProp as _$setProp4 } from "@opentui/solid";
171
+ import { createElement as _$createElement4 } from "@opentui/solid";
172
+ import { createComponent as _$createComponent4 } from "@opentui/solid";
173
+ import { Plugin } from "@opencode/plugin/tui";
174
+ import { Show as Show3 } from "solid-js";
175
+
176
+ // src/tui/state.ts
177
+ import { createSignal } from "solid-js";
178
+
179
+ // src/family.ts
180
+ function metric(snapshot, now, staleMs) {
181
+ const m = snapshot?.measurement;
182
+ const freshness = !m ? "missing" : m.model !== snapshot?.model || m.at > now ? "invalid" : now - m.at > staleMs ? "stale" : "fresh";
183
+ const t = snapshot?.policy.triggerTokens;
184
+ const valid2 = freshness === "fresh" && m !== undefined;
185
+ const pressureRatio = valid2 && t !== undefined && t > 0 ? m.tokens / t : undefined;
186
+ const crossed = valid2 && t !== undefined && t > 0 && (snapshot?.policy.comparison === ">" ? m.tokens > t : m.tokens >= t);
187
+ return {
188
+ freshness,
189
+ pressure: pressureRatio === undefined ? "unknown" : crossed ? "crossed" : pressureRatio >= 0.9 ? "near" : "normal",
190
+ ...pressureRatio === undefined ? {} : { pressureRatio },
191
+ ...valid2 && snapshot?.capacityTokens && snapshot.capacityTokens > 0 ? { utilizationPercent: m.tokens / snapshot.capacityTokens * 100 } : {},
192
+ ...valid2 && t !== undefined ? { triggerDeltaTokens: t - m.tokens } : {}
193
+ };
194
+ }
195
+ function familyRows(nodes, snapshots, current, now, staleMs, expanded = new Set, limit = 1024, timing, stableOrder = []) {
196
+ const byId = new Map(nodes.map((n) => [n.sessionId, n]));
197
+ const children = new Map;
198
+ for (const n of byId.values()) {
199
+ const parent = n.parentId && byId.has(n.parentId) ? n.parentId : "";
200
+ const list = children.get(parent) ?? [];
201
+ list.push(n);
202
+ children.set(parent, list);
203
+ }
204
+ const metrics = new Map([...byId.keys()].map((id) => [
205
+ id,
206
+ metric(snapshots.get(id), timing?.(id).now ?? now, timing?.(id).staleMs ?? staleMs)
207
+ ]));
208
+ const protectedIds = new Set;
209
+ let ancestor = byId.get(current);
210
+ while (ancestor && !protectedIds.has(ancestor.sessionId)) {
211
+ protectedIds.add(ancestor.sessionId);
212
+ ancestor = ancestor.parentId ? byId.get(ancestor.parentId) : undefined;
213
+ }
214
+ const scores = new Map, sensitive = new Map, sizes = new Map;
215
+ const calculate = (n, seen) => {
216
+ if (seen.has(n.sessionId))
217
+ return -1;
218
+ if (scores.has(n.sessionId))
219
+ return scores.get(n.sessionId) ?? -1;
220
+ const path = new Set(seen).add(n.sessionId), m = metrics.get(n.sessionId);
221
+ let score = m?.pressureRatio ?? -1, size = 1;
222
+ let keep = protectedIds.has(n.sessionId) || n.status === "running" || m?.pressure === "crossed" || ["blocked", "uncertain"].includes(snapshots.get(n.sessionId)?.compaction.phase ?? "");
223
+ for (const child of children.get(n.sessionId) ?? []) {
224
+ score = Math.max(score, calculate(child, path));
225
+ size += sizes.get(child.sessionId) ?? 0;
226
+ keep ||= sensitive.get(child.sessionId) ?? false;
227
+ }
228
+ scores.set(n.sessionId, score);
229
+ sizes.set(n.sessionId, size);
230
+ sensitive.set(n.sessionId, keep);
231
+ return score;
232
+ };
233
+ for (const n of byId.values())
234
+ calculate(n, new Set);
235
+ const visited = new Set, rows = [];
236
+ const visit = (n, depth) => {
237
+ if (visited.has(n.sessionId) || rows.length >= limit || depth > 64)
238
+ return;
239
+ visited.add(n.sessionId);
240
+ const snapshot = snapshots.get(n.sessionId), m = metrics.get(n.sessionId) ?? metric(undefined, now, staleMs);
241
+ const collapsed = n.status === "completed" && !sensitive.get(n.sessionId) && !expanded.has(n.sessionId);
242
+ rows.push({
243
+ node: n,
244
+ ...snapshot ? { snapshot } : {},
245
+ depth,
246
+ current: n.sessionId === current,
247
+ ...m,
248
+ hidden: collapsed ? (sizes.get(n.sessionId) ?? 1) - 1 : 0
249
+ });
250
+ if (collapsed) {
251
+ const pending = [...children.get(n.sessionId) ?? []];
252
+ while (pending.length) {
253
+ const child = pending.pop();
254
+ if (!child || visited.has(child.sessionId))
255
+ continue;
256
+ visited.add(child.sessionId);
257
+ pending.push(...children.get(child.sessionId) ?? []);
258
+ }
259
+ }
260
+ if (!collapsed)
261
+ for (const child of sorted(children.get(n.sessionId) ?? []))
262
+ visit(child, depth + 1);
263
+ };
264
+ const ranks = new Map(stableOrder.map((id, i) => [id, i]));
265
+ 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));
266
+ for (const n of sorted(children.get("") ?? []))
267
+ visit(n, 0);
268
+ for (const n of byId.values())
269
+ if (!visited.has(n.sessionId) && rows.length < limit)
270
+ visit(n, 0);
271
+ return rows;
272
+ }
273
+ function executionStatus(known, nativeRunning, evidence) {
274
+ if (nativeRunning || evidence.startedAt !== undefined && evidence.startedAt > (evidence.terminalAt ?? -1))
275
+ return "running";
276
+ if (evidence.outcome && (evidence.terminalAt ?? -1) >= (evidence.startedAt ?? -1))
277
+ return evidence.outcome === "succeeded" ? "completed" : evidence.outcome === "failed" ? "failed" : "unknown";
278
+ return known ? "idle" : "unknown";
279
+ }
280
+
281
+ // src/tui/transport.ts
282
+ class SnapshotClient {
283
+ transport;
284
+ changed;
285
+ elapsed;
286
+ rows = new Map;
287
+ health;
288
+ connection = "loading";
289
+ epoch;
290
+ sequence = -1;
291
+ revisions = new Map;
292
+ baselines = new Map;
293
+ controller;
294
+ generation = 0;
295
+ ids = [];
296
+ location = "";
297
+ refreshTimer;
298
+ reading;
299
+ buffered = [];
300
+ overflow = false;
301
+ retryTimer;
302
+ failures = 0;
303
+ serverAt = 0;
304
+ receivedAt = 0;
305
+ refreshAgain = false;
306
+ subscriptionActive = false;
307
+ constructor(transport, changed, elapsed = () => performance.now()) {
308
+ this.transport = transport;
309
+ this.changed = changed;
310
+ this.elapsed = elapsed;
311
+ }
312
+ now() {
313
+ return this.serverAt + Math.max(0, this.elapsed() - this.receivedAt);
314
+ }
315
+ setIds(ids) {
316
+ const next = [...new Set(ids)].slice(0, 2048);
317
+ if (JSON.stringify(next) === JSON.stringify(this.ids))
318
+ return;
319
+ this.ids = next;
320
+ const selected = new Set(next);
321
+ for (const id of new Set([
322
+ ...this.revisions.keys(),
323
+ ...this.baselines.keys()
324
+ ]))
325
+ if (!selected.has(id)) {
326
+ this.revisions.delete(id);
327
+ this.baselines.delete(id);
328
+ this.rows.delete(id);
329
+ }
330
+ if (this.reading)
331
+ this.refreshAgain = true;
332
+ else
333
+ this.refresh();
334
+ }
335
+ start(location, ids) {
336
+ this.stop();
337
+ this.location = location;
338
+ this.ids = [...new Set(ids)].slice(0, 2048);
339
+ this.rows.clear();
340
+ this.revisions.clear();
341
+ this.baselines.clear();
342
+ this.health = undefined;
343
+ this.epoch = undefined;
344
+ this.sequence = -1;
345
+ this.failures = 0;
346
+ this.connection = "loading";
347
+ const controller = new AbortController;
348
+ this.controller = controller;
349
+ const generation = ++this.generation;
350
+ this.supervise(generation, controller.signal);
351
+ this.refreshTimer = setInterval(() => {
352
+ this.refresh();
353
+ }, 15000);
354
+ this.refresh();
355
+ }
356
+ merge(batch, baseline = false, requested = []) {
357
+ if (this.epoch !== batch.epoch) {
358
+ if (!baseline) {
359
+ this.connection = "reconnecting";
360
+ this.refreshAgain = true;
361
+ return;
362
+ }
363
+ this.rows.clear();
364
+ this.revisions.clear();
365
+ this.baselines.clear();
366
+ this.epoch = batch.epoch;
367
+ this.sequence = -1;
368
+ }
369
+ if (!baseline && batch.sequence > this.sequence + 1) {
370
+ this.connection = "reconnecting";
371
+ this.refreshAgain = true;
372
+ }
373
+ if (batch.sequence >= this.sequence) {
374
+ this.sequence = batch.sequence;
375
+ this.health = batch.health;
376
+ this.serverAt = batch.generatedAt;
377
+ this.receivedAt = this.elapsed();
378
+ }
379
+ const selected = new Set(baseline ? requested.filter((id) => this.ids.includes(id)) : this.ids);
380
+ for (const row2 of batch.upserts)
381
+ if (selected.has(row2.sessionId) && (baseline || batch.sequence > (this.baselines.get(row2.sessionId) ?? -1)) && row2.revision > (this.revisions.get(row2.sessionId) ?? -1)) {
382
+ this.rows.set(row2.sessionId, row2);
383
+ this.revisions.set(row2.sessionId, row2.revision);
384
+ }
385
+ for (const row2 of batch.removed)
386
+ if (selected.has(row2.sessionId) && (baseline || batch.sequence > (this.baselines.get(row2.sessionId) ?? -1)) && row2.revision > (this.revisions.get(row2.sessionId) ?? -1)) {
387
+ this.rows.delete(row2.sessionId);
388
+ this.revisions.set(row2.sessionId, row2.revision);
389
+ }
390
+ this.changed();
391
+ }
392
+ refresh() {
393
+ if (this.reading)
394
+ return this.reading;
395
+ const controller = this.controller, generation = this.generation, location = this.location;
396
+ if (!controller)
397
+ return Promise.resolve();
398
+ clearTimeout(this.retryTimer);
399
+ const requestedIds = [...this.ids];
400
+ this.reading = (async () => {
401
+ const signal = AbortSignal.any([
402
+ controller.signal,
403
+ AbortSignal.timeout(5000)
404
+ ]);
405
+ const chunks = [];
406
+ for (let i = 0;i < requestedIds.length; i += 128)
407
+ chunks.push(requestedIds.slice(i, i + 128));
408
+ if (!chunks.length)
409
+ chunks.push([]);
410
+ const results = [];
411
+ for (let i = 0;i < chunks.length; i += 2) {
412
+ const reads = await Promise.all(chunks.slice(i, i + 2).map(async (ids) => {
413
+ const value = await abortable(this.transport.read(location, ids, signal), signal);
414
+ if (generation !== this.generation)
415
+ throw new Error("Obsolete read");
416
+ if (!isResult(value)) {
417
+ this.connection = "incompatible";
418
+ throw new Error("Incompatible snapshot");
419
+ }
420
+ const answered = [
421
+ ...value.upserts.map((row2) => row2.sessionId),
422
+ ...value.unavailable.map((row2) => row2.sessionId)
423
+ ];
424
+ if (value.removed.length || answered.length !== ids.length || new Set(answered).size !== ids.length || answered.some((id) => !ids.includes(id)))
425
+ throw new Error("Incomplete snapshot read");
426
+ return { ids, result: value };
427
+ }));
428
+ results.push(...reads);
429
+ }
430
+ if (generation !== this.generation || signal.aborted)
431
+ return;
432
+ if (new Set(results.map((r) => r.result.epoch)).size !== 1)
433
+ throw new Error("Epoch changed during read");
434
+ for (const { ids, result } of results) {
435
+ this.merge(result, true, ids);
436
+ for (const missing of result.unavailable)
437
+ if (this.ids.includes(missing.sessionId))
438
+ this.rows.delete(missing.sessionId);
439
+ for (const id of ids)
440
+ if (this.ids.includes(id))
441
+ this.baselines.set(id, result.sequence);
442
+ }
443
+ const buffered = this.buffered;
444
+ this.buffered = [];
445
+ if (buffered.some((b) => b.epoch !== this.epoch) || this.overflow) {
446
+ this.overflow = false;
447
+ throw new Error("Stream changed during read");
448
+ }
449
+ for (const batch of buffered)
450
+ this.merge(batch);
451
+ this.failures = 0;
452
+ this.connection = this.refreshAgain || !this.subscriptionActive ? "reconnecting" : "live";
453
+ this.changed();
454
+ })().catch(() => {
455
+ if (generation === this.generation && !controller.signal.aborted) {
456
+ this.buffered = [];
457
+ this.overflow = false;
458
+ if (this.connection !== "incompatible")
459
+ this.connection = this.rows.size ? "reconnecting" : "unavailable";
460
+ this.changed();
461
+ this.retryTimer = setTimeout(() => {
462
+ this.refresh();
463
+ }, Math.min(30000, 1000 * 2 ** Math.min(this.failures++, 5)));
464
+ }
465
+ }).finally(() => {
466
+ if (generation === this.generation) {
467
+ this.reading = undefined;
468
+ if (this.refreshAgain) {
469
+ this.refreshAgain = false;
470
+ this.refresh();
471
+ }
472
+ }
473
+ });
474
+ return this.reading;
475
+ }
476
+ async supervise(generation, signal) {
477
+ let retry = 0;
478
+ while (!signal.aborted && generation === this.generation) {
479
+ try {
480
+ this.subscriptionActive = true;
481
+ if (retry)
482
+ queueMicrotask(() => {
483
+ if (generation === this.generation && !signal.aborted)
484
+ this.refresh();
485
+ });
486
+ for await (const value of this.transport.subscribe(this.location, signal)) {
487
+ if (signal.aborted || generation !== this.generation)
488
+ return;
489
+ if (!isBatch(value)) {
490
+ this.connection = "incompatible";
491
+ this.changed();
492
+ continue;
493
+ }
494
+ if (this.reading || !this.epoch) {
495
+ if (this.buffered.length < 256)
496
+ this.buffered.push(value);
497
+ else
498
+ this.overflow = true;
499
+ } else {
500
+ this.merge(value);
501
+ if (this.refreshAgain) {
502
+ this.refreshAgain = false;
503
+ this.refresh();
504
+ }
505
+ }
506
+ retry = 0;
507
+ }
508
+ } catch {
509
+ }
510
+ if (signal.aborted || generation !== this.generation)
511
+ return;
512
+ this.subscriptionActive = false;
513
+ this.connection = "reconnecting";
514
+ this.changed();
515
+ await this.refresh();
516
+ await new Promise((resolve) => {
517
+ const finish = () => {
518
+ clearTimeout(timer);
519
+ signal.removeEventListener("abort", finish);
520
+ resolve();
521
+ };
522
+ const timer = setTimeout(finish, Math.min(30000, 1000 * 2 ** Math.min(retry++, 5)));
523
+ signal.addEventListener("abort", finish, { once: true });
524
+ if (signal.aborted)
525
+ finish();
526
+ });
527
+ }
528
+ }
529
+ stop() {
530
+ this.generation++;
531
+ this.controller?.abort();
532
+ this.controller = undefined;
533
+ clearInterval(this.refreshTimer);
534
+ clearTimeout(this.retryTimer);
535
+ this.reading = undefined;
536
+ this.buffered = [];
537
+ this.overflow = false;
538
+ this.refreshAgain = false;
539
+ }
540
+ }
541
+ function abortable(promise, signal) {
542
+ return new Promise((resolve, reject) => {
543
+ const abort = () => reject(new Error("Snapshot request cancelled"));
544
+ if (signal.aborted) {
545
+ abort();
546
+ return;
547
+ }
548
+ signal.addEventListener("abort", abort, { once: true });
549
+ promise.then(resolve, reject).finally(() => signal.removeEventListener("abort", abort));
550
+ });
551
+ }
552
+
553
+ // src/tui/inventory.ts
554
+ async function discoverFamily(client, currentId, signal, cached, maxNodes = 1024, previous) {
555
+ const sessions = new Map(previous?.sessions);
556
+ let damaged = previous?.damaged ?? false, pages = 0, rootId = previous?.rootId ?? currentId;
557
+ const get = async (id) => cached(id) ?? client.session.get({ sessionID: id }, { signal });
558
+ if (!previous) {
559
+ let current = await get(currentId);
560
+ for (let depth = 0;depth <= 64; depth++) {
561
+ if (signal.aborted)
562
+ throw signal.reason;
563
+ if (sessions.has(current.id) || sessions.size >= maxNodes) {
564
+ damaged = true;
565
+ break;
566
+ }
567
+ sessions.set(current.id, current);
568
+ rootId = current.id;
569
+ if (!current.parentID)
570
+ break;
571
+ if (depth === 64) {
572
+ damaged = true;
573
+ break;
574
+ }
575
+ try {
576
+ current = await get(current.parentID);
577
+ } catch {
578
+ if (signal.aborted)
579
+ throw signal.reason;
580
+ damaged = true;
581
+ break;
582
+ }
583
+ }
584
+ }
585
+ const queue = previous ? [...previous.pending] : [{ id: rootId, depth: 0, cursors: [] }];
586
+ const scanned = new Set(previous?.scanned);
587
+ while (queue.length && pages < 100) {
588
+ const next = queue.shift();
589
+ if (!next)
590
+ break;
591
+ if (!next.cursor && scanned.has(next.id)) {
592
+ damaged = true;
593
+ continue;
594
+ }
595
+ if (signal.aborted)
596
+ throw signal.reason;
597
+ try {
598
+ pages++;
599
+ const response = await client.session.list({
600
+ parentID: next.id,
601
+ limit: 100,
602
+ order: "desc",
603
+ ...next.cursor ? { cursor: next.cursor } : {}
604
+ }, { signal });
605
+ let capped = false;
606
+ for (const child of response.data) {
607
+ if (child.parentID !== next.id || child.id === next.id) {
608
+ damaged = true;
609
+ continue;
610
+ }
611
+ if (!sessions.has(child.id) && sessions.size >= maxNodes) {
612
+ capped = true;
613
+ break;
614
+ }
615
+ sessions.set(child.id, child);
616
+ if (!scanned.has(child.id) && !queue.some((branch) => branch.id === child.id)) {
617
+ if (next.depth < 64)
618
+ queue.push({ id: child.id, depth: next.depth + 1, cursors: [] });
619
+ else
620
+ damaged = true;
621
+ }
622
+ }
623
+ if (capped) {
624
+ queue.unshift(next);
625
+ break;
626
+ }
627
+ const cursor = response.cursor?.next;
628
+ if (cursor) {
629
+ if (next.cursors.includes(cursor) || cursor === next.cursor)
630
+ damaged = true;
631
+ else
632
+ queue.unshift({
633
+ ...next,
634
+ cursor,
635
+ cursors: [...next.cursors, cursor]
636
+ });
637
+ } else
638
+ scanned.add(next.id);
639
+ } catch {
640
+ if (signal.aborted)
641
+ throw signal.reason;
642
+ damaged = true;
643
+ }
644
+ }
645
+ return {
646
+ sessions,
647
+ complete: !damaged && !queue.length,
648
+ rootId,
649
+ pages: (previous?.pages ?? 0) + pages,
650
+ pending: queue,
651
+ scanned,
652
+ damaged
653
+ };
654
+ }
655
+
656
+ // src/tui/state.ts
657
+ function createGuardState(context) {
658
+ const [version, setVersion] = createSignal(0);
659
+ const notify = () => {
660
+ setVersion((n) => n + 1);
661
+ scheduleExpiry();
662
+ };
663
+ const rpc = context.client.rpc(monitorRpc);
664
+ const clients = new Map;
665
+ let inventory, currentId = "", controller;
666
+ let scanGeneration = 0, scanning = false, maxNodes = 1024;
667
+ let scanFailed = false;
668
+ const consumers = new Map;
669
+ const [preferences, savePreferences] = context.storage.store("context-guard.ui.v1", { initial: { expanded: [] } });
670
+ const expanded = new Set(Array.isArray(preferences.expanded) ? preferences.expanded.filter((id) => typeof id === "string" && id.length <= 512).slice(0, 1024) : []);
671
+ const evidence = new Map;
672
+ let expiryTimer;
673
+ function scheduleExpiry() {
674
+ clearTimeout(expiryTimer);
675
+ if (!consumers.size)
676
+ return;
677
+ let delay = Infinity;
678
+ for (const client of clients.values())
679
+ for (const row2 of client.rows.values()) {
680
+ if (!row2.measurement || !client.health)
681
+ continue;
682
+ const remaining = row2.measurement.at + client.health.staleMs + 1 - client.now();
683
+ if (remaining > 0)
684
+ delay = Math.min(delay, remaining);
685
+ }
686
+ if (Number.isFinite(delay))
687
+ expiryTimer = setTimeout(notify, Math.min(2147483647, Math.ceil(delay)));
688
+ }
689
+ let ageTimer, scanTimer, eventTimer;
690
+ let unlisten;
691
+ function select() {
692
+ const id = [...consumers.values()].at(-1);
693
+ if (!id || id === currentId)
694
+ return;
695
+ controller?.abort();
696
+ scanGeneration++;
697
+ scanning = false;
698
+ for (const c of clients.values())
699
+ c.stop();
700
+ clients.clear();
701
+ currentId = id;
702
+ inventory = undefined;
703
+ evidence.clear();
704
+ maxNodes = 1024;
705
+ let scanFailed2 = false;
706
+ notify();
707
+ scan();
708
+ }
709
+ async function scan(more = false) {
710
+ if (scanning || !currentId || !consumers.size)
711
+ return;
712
+ scanning = true;
713
+ const generation = ++scanGeneration;
714
+ controller?.abort();
715
+ controller = new AbortController;
716
+ const signal = AbortSignal.any([
717
+ controller.signal,
718
+ AbortSignal.timeout(15000)
719
+ ]);
720
+ try {
721
+ const next = await discoverFamily(context.client, currentId, signal, (id) => context.data.session.get(id), maxNodes, more ? inventory : undefined);
722
+ if (signal.aborted || generation !== scanGeneration)
723
+ return;
724
+ inventory = next;
725
+ scanFailed = false;
726
+ for (const id of evidence.keys())
727
+ if (!next.sessions.has(id))
728
+ evidence.delete(id);
729
+ const groups = new Map;
730
+ for (const s of next.sessions.values()) {
731
+ const key = locationKey(s.location), group = groups.get(key) ?? { ids: [] };
732
+ group.ids.push(s.id);
733
+ groups.set(key, group);
734
+ }
735
+ for (const [key, c] of clients)
736
+ if (!groups.has(key)) {
737
+ c.stop();
738
+ clients.delete(key);
739
+ }
740
+ for (const [key, group] of groups) {
741
+ const sample = next.sessions.get(group.ids[0] ?? "");
742
+ if (!sample)
743
+ continue;
744
+ let c = clients.get(key);
745
+ if (!c) {
746
+ const location = sample.location;
747
+ c = new SnapshotClient({
748
+ read: (_key, ids, requestSignal) => rpc.read({ schemaVersion: 1, sessionIds: ids }, { location, signal: requestSignal }),
749
+ subscribe: async function* (_key, requestSignal) {
750
+ for await (const event of rpc.events.subscribe("changed", {
751
+ signal: requestSignal
752
+ })) {
753
+ if (event.location && sameLocation(event.location, location))
754
+ yield event.data;
755
+ }
756
+ }
757
+ }, notify);
758
+ clients.set(key, c);
759
+ c.start(key, group.ids);
760
+ } else
761
+ c.setIds(group.ids);
762
+ }
763
+ notify();
764
+ } catch {
765
+ if (generation === scanGeneration && !controller?.signal.aborted) {
766
+ scanFailed = true;
767
+ notify();
768
+ }
769
+ } finally {
770
+ if (generation === scanGeneration)
771
+ scanning = false;
772
+ }
773
+ }
774
+ function acquire(id) {
775
+ const token = Symbol();
776
+ consumers.set(token, id);
777
+ if (consumers.size === 1) {
778
+ ageTimer = setInterval(notify, 1000);
779
+ scanTimer = setInterval(() => {
780
+ scan();
781
+ }, 30000);
782
+ unlisten = context.data.listen(({ details: event }) => {
783
+ if (!("sessionID" in event.data) || !inventory?.sessions.has(event.data.sessionID)) {
784
+ if (event.type !== "session.created")
785
+ return;
786
+ }
787
+ if ("created" in event && "sessionID" in event.data && event.type.startsWith("session.execution.")) {
788
+ const id2 = event.data.sessionID, previous = evidence.get(id2) ?? {};
789
+ if (event.type === "session.execution.started")
790
+ evidence.set(id2, {
791
+ ...previous,
792
+ startedAt: Math.max(previous.startedAt ?? 0, event.created)
793
+ });
794
+ if ([
795
+ "session.execution.succeeded",
796
+ "session.execution.failed",
797
+ "session.execution.interrupted"
798
+ ].includes(event.type) && event.created >= (previous.terminalAt ?? 0)) {
799
+ const outcome = event.type === "session.execution.succeeded" ? "succeeded" : event.type === "session.execution.failed" ? "failed" : "interrupted";
800
+ evidence.set(id2, {
801
+ ...previous,
802
+ terminalAt: event.created,
803
+ outcome
804
+ });
805
+ }
806
+ notify();
807
+ }
808
+ if ([
809
+ "session.created",
810
+ "session.deleted",
811
+ "session.model.selected",
812
+ "session.agent.selected"
813
+ ].includes(event.type)) {
814
+ clearTimeout(eventTimer);
815
+ eventTimer = setTimeout(() => {
816
+ scan();
817
+ }, 100);
818
+ }
819
+ });
820
+ }
821
+ select();
822
+ return {
823
+ update(next) {
824
+ consumers.set(token, next);
825
+ select();
826
+ },
827
+ release() {
828
+ consumers.delete(token);
829
+ if (consumers.size)
830
+ select();
831
+ else
832
+ stop();
833
+ }
834
+ };
835
+ }
836
+ function stop() {
837
+ controller?.abort();
838
+ scanGeneration++;
839
+ scanning = false;
840
+ for (const c of clients.values())
841
+ c.stop();
842
+ clients.clear();
843
+ clearTimeout(expiryTimer);
844
+ clearInterval(ageTimer);
845
+ clearInterval(scanTimer);
846
+ clearTimeout(eventTimer);
847
+ unlisten?.();
848
+ unlisten = undefined;
849
+ inventory = undefined;
850
+ evidence.clear();
851
+ currentId = "";
852
+ }
853
+ function rows(stableOrder = [], allDescendants = false) {
854
+ version();
855
+ const snapshots = new Map([...clients.values()].flatMap((c2) => [...c2.rows]));
856
+ const nodes = [...inventory?.sessions.values() ?? []].map((original) => {
857
+ const s = context.data.session.get(original.id) ?? original;
858
+ const observed = evidence.get(s.id) ?? {};
859
+ const metadataAt = s.time.idle ?? 0;
860
+ const e = s.outcome && metadataAt >= (observed.terminalAt ?? 0) ? { ...observed, outcome: s.outcome, terminalAt: metadataAt } : observed;
861
+ return {
862
+ sessionId: s.id,
863
+ ...s.parentID ? { parentId: s.parentID } : {},
864
+ ...s.agent ? { agent: s.agent } : {},
865
+ ...s.model ? { model: `${s.model.providerID}/${s.model.id}` } : {},
866
+ ...e.outcome === "interrupted" ? { interrupted: true } : {},
867
+ status: executionStatus(true, context.data.session.status(s.id) === "running", e)
868
+ };
869
+ });
870
+ const c = [...clients.values()].find((c2) => c2.rows.has(currentId)) ?? clients.values().next().value;
871
+ return familyRows(nodes, snapshots, currentId, c?.now() ?? Date.now(), c?.health?.staleMs ?? 300000, allDescendants ? new Set(nodes.map((node) => node.sessionId)) : expanded, maxNodes, (id) => {
872
+ const session = inventory?.sessions.get(id);
873
+ const client = session ? clients.get(locationKey(session.location)) : undefined;
874
+ return {
875
+ now: client?.now() ?? Date.now(),
876
+ staleMs: client?.health?.staleMs ?? 300000
877
+ };
878
+ }, stableOrder);
879
+ }
880
+ return {
881
+ version,
882
+ acquire,
883
+ rows,
884
+ dispose: stop,
885
+ age(id) {
886
+ version();
887
+ const c = [...clients.values()].find((c2) => c2.rows.has(id));
888
+ const m = c?.rows.get(id)?.measurement;
889
+ return c && m ? c.now() - m.at : 0;
890
+ },
891
+ health() {
892
+ version();
893
+ return [...clients.values()].find((c) => c.rows.has(currentId))?.health ?? clients.values().next().value?.health;
894
+ },
895
+ connection() {
896
+ version();
897
+ return clients.size ? [...clients.values()].map((c) => c.connection).find((c) => c !== "live") ?? "live" : scanFailed ? "unavailable" : "loading";
898
+ },
899
+ complete() {
900
+ version();
901
+ return inventory?.complete ?? false;
902
+ },
903
+ refresh() {
904
+ scan();
905
+ for (const c of clients.values())
906
+ c.refresh();
907
+ },
908
+ loadMore() {
909
+ maxNodes = Math.min(2048, maxNodes + 1024);
910
+ scan(true);
911
+ },
912
+ expand(id) {
913
+ if (expanded.has(id))
914
+ expanded.delete(id);
915
+ else if (expanded.size < 1024)
916
+ expanded.add(id);
917
+ savePreferences((draft) => {
918
+ draft.expanded = [...expanded];
919
+ }).catch(() => {
920
+ });
921
+ notify();
922
+ }
923
+ };
924
+ }
925
+ function locationKey(location) {
926
+ return JSON.stringify([location.directory, location.workspaceID ?? null]);
927
+ }
928
+ function sameLocation(a, b) {
929
+ if (a.workspaceID && b.workspaceID)
930
+ return a.workspaceID === b.workspaceID;
931
+ return a.directory === b.directory;
932
+ }
933
+
934
+ // src/tui/sidebar.tsx
935
+ import { effect as _$effect2 } from "@opentui/solid";
936
+ import { createComponent as _$createComponent2 } from "@opentui/solid";
937
+ import { insert as _$insert2 } from "@opentui/solid";
938
+ import { createTextNode as _$createTextNode2 } from "@opentui/solid";
939
+ import { insertNode as _$insertNode2 } from "@opentui/solid";
940
+ import { memo as _$memo2 } from "@opentui/solid";
941
+ import { setProp as _$setProp2 } from "@opentui/solid";
942
+ import { createElement as _$createElement2 } from "@opentui/solid";
943
+ import { For as For2, createEffect as createEffect2, createMemo as createMemo2, onCleanup, Show as Show2 } from "solid-js";
944
+
945
+ // src/tui/format.ts
946
+ function quantity(n) {
947
+ 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);
948
+ }
949
+ var segmenter = new Intl.Segmenter(undefined, { granularity: "grapheme" });
950
+ function cells(value) {
951
+ return [...segmenter.segment(value)].reduce((n, { segment }) => n + graphemeWidth(segment), 0);
952
+ }
953
+ function graphemeWidth(s) {
954
+ const cp = s.codePointAt(0) ?? 0;
955
+ if (/^[\p{Mark}\u200d\ufe0f]+$/u.test(s))
956
+ return 0;
957
+ 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;
958
+ }
959
+ function clip(value, width) {
960
+ const clean = sanitize(value);
961
+ if (cells(clean) <= width)
962
+ return clean;
963
+ let result = "", used = 0;
964
+ for (const { segment } of segmenter.segment(clean)) {
965
+ const size = graphemeWidth(segment);
966
+ if (used + size > Math.max(0, width - 3))
967
+ break;
968
+ result += segment;
969
+ used += size;
970
+ }
971
+ return width < 3 ? ".".repeat(Math.max(0, width)) : result + "...";
972
+ }
973
+ var statuses = {
974
+ running: "RUN",
975
+ idle: "IDLE",
976
+ completed: "DONE",
977
+ failed: "FAIL",
978
+ unknown: "?"
979
+ };
980
+ function rowLines(row2, width) {
981
+ const s = row2.snapshot, m = s?.measurement;
982
+ const prefix = row2.current ? "* " : row2.depth > 5 ? ".../" : " ".repeat(Math.min(row2.depth, 5)) + "|-";
983
+ const percent = row2.utilizationPercent === undefined ? "?" : `${row2.utilizationPercent.toFixed(1)}%`;
984
+ const warning = row2.freshness === "stale" ? " STALE" : row2.pressure === "crossed" ? " !! crossed" : row2.pressure === "near" ? " ! near" : "";
985
+ const suffix = ` ${statuses[row2.node.status]} ${m?.confidence === "approximate" ? "~" : ""}${percent}${warning}`;
986
+ const label = clip(`${prefix}${row2.node.agent ?? row2.node.sessionId}`, Math.max(1, width - cells(suffix)));
987
+ return [
988
+ clip(`${label}${suffix}`, width),
989
+ ...wrap(`${quantity(m?.tokens)}/${quantity(s?.capacityTokens)} input; to ${quantity(row2.triggerDeltaTokens)}`, width),
990
+ ...s && s.compaction.phase !== "idle" ? wrap(`compact: ${s.compaction.phase}`, width) : [],
991
+ ...row2.node.interrupted ? [clip("interrupted", width)] : [],
992
+ ...row2.hidden ? wrap(`+ ${row2.hidden} completed descendants`, width) : []
993
+ ];
994
+ }
995
+ function detailLines(row2, width, expanded = false, ageMs = 0) {
996
+ const s = row2?.snapshot, m = s?.measurement;
997
+ if (!row2)
998
+ return wrap("Loading session family...", width);
999
+ if (!s)
1000
+ return [
1001
+ ...wrap(`${row2.node.agent ?? row2.node.sessionId} / ${row2.node.model ?? "?"}`, width),
1002
+ ...wrap("Guard: not monitored / unavailable", width)
1003
+ ];
1004
+ const threshold = s.policy.configured;
1005
+ const headroom = row2.triggerDeltaTokens;
1006
+ const lines = [
1007
+ `${s.agent ?? "?"} / ${s.model ?? "?"}`,
1008
+ `${m?.confidence === "approximate" ? "~" : ""}${quantity(m?.tokens)} / ${quantity(s.capacityTokens)} input`,
1009
+ `${row2.utilizationPercent?.toFixed(1) ?? "?"}% ${row2.pressure === "crossed" ? "!! crossed" : row2.pressure === "near" ? "! near" : row2.freshness.toUpperCase()}`,
1010
+ `Trigger ${threshold.type === "ratio" ? `${threshold.value * 100}%` : `${threshold.value} ${threshold.type}`} (${s.policy.comparison})`,
1011
+ `Resolved trigger ${s.policy.triggerTokens ?? "?"}`,
1012
+ `Until trigger ${quantity(headroom)}`,
1013
+ `Compact: ${s.compaction.phase}`,
1014
+ `${s.compaction.armed ? "armed" : "disarmed"}${s.compaction.blockedReason ? ` / ${s.compaction.blockedReason}` : ""}`,
1015
+ m ? `${m.confidence} ${m.scope}` : "measurement unknown",
1016
+ m ? `${Math.floor(Math.max(0, ageMs) / 1000)}s ago; ${row2.freshness}` : ""
1017
+ ];
1018
+ if (expanded)
1019
+ 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)}`);
1020
+ return lines.filter(Boolean).flatMap((line) => wrap(line, width));
1021
+ }
1022
+ function sanitize(value) {
1023
+ return value.replace(/\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)/g, "").replace(/\x1b\[[0-?]*[ -/]*[@-~]/g, "").replace(/[\x00-\x1f\x7f-\x9f]/g, "");
1024
+ }
1025
+ function wrap(value, width) {
1026
+ const result = [];
1027
+ let part = "";
1028
+ for (const word of sanitize(value).split(" ")) {
1029
+ if (part && cells(part + " " + word) > width) {
1030
+ result.push(part);
1031
+ part = "";
1032
+ }
1033
+ for (const { segment } of segmenter.segment((part ? " " : "") + word)) {
1034
+ if (part && cells(part + segment) > width) {
1035
+ result.push(part);
1036
+ part = "";
1037
+ }
1038
+ part += cells(segment) <= width ? segment : "?";
1039
+ }
1040
+ }
1041
+ if (part)
1042
+ result.push(part);
1043
+ return result;
1044
+ }
1045
+
1046
+ // src/tui/compact.tsx
1047
+ import { createTextNode as _$createTextNode } from "@opentui/solid";
1048
+ import { insertNode as _$insertNode } from "@opentui/solid";
1049
+ import { setProp as _$setProp } from "@opentui/solid";
1050
+ import { effect as _$effect } from "@opentui/solid";
1051
+ import { insert as _$insert } from "@opentui/solid";
1052
+ import { memo as _$memo } from "@opentui/solid";
1053
+ import { createElement as _$createElement } from "@opentui/solid";
1054
+ import { createComponent as _$createComponent } from "@opentui/solid";
1055
+ import { For, Index, Show, createEffect, createMemo, createSignal as createSignal2 } from "solid-js";
1056
+
1057
+ // src/tui/compactformat.ts
1058
+ function tokens(value) {
1059
+ if (value === undefined || !Number.isFinite(value))
1060
+ return "?";
1061
+ const magnitude = Math.abs(value);
1062
+ if (magnitude >= 1e6)
1063
+ return `${+(value / 1e6).toFixed(1)}M`;
1064
+ if (magnitude >= 1000)
1065
+ return `${+(value / 1000).toFixed(1)}K`;
1066
+ return String(value);
1067
+ }
1068
+ function columns(left, right, width) {
1069
+ if (cells(left) + cells(right) + 1 > width)
1070
+ return [...wrap(left, width), ...wrap(right, width)];
1071
+ return [left + " ".repeat(width - cells(left) - cells(right)) + right];
1072
+ }
1073
+ function progress(ratio, width) {
1074
+ const valid2 = ratio !== undefined && Number.isFinite(ratio) && ratio >= 0;
1075
+ const label = valid2 ? `${Math.round(ratio * 100)}%` : "?%";
1076
+ const size = Math.max(1, width - cells(label) - 1);
1077
+ const filled = valid2 ? Math.round(Math.min(1, ratio) * size) : 0;
1078
+ return clip("\u2588".repeat(filled) + "\u2591".repeat(size - filled) + " " + label, width);
1079
+ }
1080
+ function mainSections(row2, width, age) {
1081
+ if (!row2)
1082
+ return [{ tone: "base", lines: wrap("Loading session family...", width) }];
1083
+ const s = row2.snapshot, m = s?.measurement;
1084
+ const valid2 = row2.freshness === "fresh" || row2.freshness === "stale";
1085
+ const measured = valid2 ? m?.tokens : undefined;
1086
+ const ratio = (total) => measured !== undefined && total !== undefined && total > 0 ? measured / total : undefined;
1087
+ const remaining = measured !== undefined && s?.policy.triggerTokens !== undefined ? s.policy.triggerTokens - measured : undefined;
1088
+ return [
1089
+ {
1090
+ tone: "execution",
1091
+ lines: columns(`${row2.node.agent ?? s?.agent ?? "Main"} \xB7 ${row2.node.model ?? s?.model ?? "?"}`, row2.node.status.toUpperCase(), width)
1092
+ },
1093
+ {
1094
+ tone: "base",
1095
+ lines: [
1096
+ "",
1097
+ ...columns("Context", `${tokens(measured)} / ${tokens(s?.capacityTokens)}`, width)
1098
+ ]
1099
+ },
1100
+ { tone: "pressure", lines: [progress(ratio(s?.capacityTokens), width)] },
1101
+ {
1102
+ tone: "base",
1103
+ lines: [
1104
+ "",
1105
+ ...columns("Compact threshold", `${tokens(measured)} / ${tokens(s?.policy.triggerTokens)}`, width)
1106
+ ]
1107
+ },
1108
+ {
1109
+ tone: "pressure",
1110
+ lines: [
1111
+ progress(ratio(s?.policy.triggerTokens), width),
1112
+ ...wrap(remaining === undefined ? "Compaction distance unknown" : remaining < 0 ? `${tokens(-remaining)} tokens over threshold` : `${tokens(remaining)} tokens until compaction`, width)
1113
+ ]
1114
+ },
1115
+ {
1116
+ tone: "measurement",
1117
+ lines: [
1118
+ "",
1119
+ ...wrap(`Measurement: ${m?.confidence ?? "unknown"}${m ? ` \xB7 ${Math.floor(Math.max(0, age) / 1000)}s ago` : ""}${row2.freshness !== "fresh" ? ` \xB7 ${row2.freshness.toUpperCase()}` : ""}`, width)
1120
+ ]
1121
+ },
1122
+ {
1123
+ tone: "base",
1124
+ lines: [
1125
+ ...s && s.compaction.phase !== "idle" ? wrap(`Compact: ${s.compaction.phase}`, width) : [],
1126
+ ...!s ? wrap("Guard: not monitored / unavailable", width) : []
1127
+ ]
1128
+ }
1129
+ ];
1130
+ }
1131
+ var statusSymbol = {
1132
+ running: "\u25CF",
1133
+ idle: "\u25CC",
1134
+ unknown: "\u25CC",
1135
+ completed: "\u2713",
1136
+ failed: "\u2715"
1137
+ };
1138
+ function agentHeading(row2, width) {
1139
+ const name = `${statusSymbol[row2.node.status]} ${row2.node.agent ?? row2.node.sessionId}`;
1140
+ const percent = row2.utilizationPercent === undefined ? "?%" : `${Math.round(row2.utilizationPercent)}%`;
1141
+ return cells(name) + cells(percent) + 1 <= width ? columns(name, percent, width)[0] ?? "" : clip(name, width);
1142
+ }
1143
+ function agentLines(row2, width) {
1144
+ const valid2 = row2.freshness === "fresh" || row2.freshness === "stale";
1145
+ const measured = valid2 ? row2.snapshot?.measurement?.tokens : undefined;
1146
+ const capacity = row2.snapshot?.capacityTokens;
1147
+ const pressure = row2.pressure === "crossed" ? "CRITICAL" : row2.pressure === "near" ? "WARNING" : "";
1148
+ return [
1149
+ progress(measured !== undefined && capacity !== undefined && capacity > 0 ? measured / capacity : undefined, width),
1150
+ ...wrap(`${tokens(measured)} / ${tokens(capacity)}${pressure ? ` \xB7 ${pressure}` : ""}`, width),
1151
+ ...wrap(`${row2.node.status.toUpperCase()}${row2.freshness !== "fresh" ? ` \xB7 ${row2.freshness.toUpperCase()}` : ""}${row2.node.interrupted ? " \xB7 interrupted" : ""}`, width),
1152
+ ...row2.snapshot && row2.snapshot.compaction.phase !== "idle" ? wrap(`Compact: ${row2.snapshot.compaction.phase}`, width) : []
1153
+ ];
1154
+ }
1155
+ function subagentTree(rows, sessionID, collapsed, hideCompleted, byPressure) {
1156
+ const children = new Map;
1157
+ for (const row2 of rows) {
1158
+ if (!row2.node.parentId || row2.node.sessionId === sessionID)
1159
+ continue;
1160
+ const list = children.get(row2.node.parentId) ?? [];
1161
+ list.push(row2);
1162
+ children.set(row2.node.parentId, list);
1163
+ }
1164
+ const seen = new Set([sessionID]);
1165
+ const all = [];
1166
+ const build = (id, depth) => {
1167
+ if (depth > 64)
1168
+ return [];
1169
+ const branches2 = [];
1170
+ for (const row2 of children.get(id) ?? []) {
1171
+ if (seen.has(row2.node.sessionId))
1172
+ continue;
1173
+ seen.add(row2.node.sessionId);
1174
+ all.push(row2);
1175
+ const nested = build(row2.node.sessionId, depth + 1);
1176
+ if (hideCompleted && row2.node.status === "completed" && !nested.length)
1177
+ continue;
1178
+ branches2.push({
1179
+ row: row2,
1180
+ children: nested,
1181
+ active: row2.node.status === "running" || nested.some((b) => b.active),
1182
+ pressure: Math.max(row2.pressureRatio ?? -1, ...nested.map((b) => b.pressure))
1183
+ });
1184
+ }
1185
+ return branches2.sort((a, b) => Number(b.active) - Number(a.active) || (byPressure ? b.pressure - a.pressure : 0));
1186
+ };
1187
+ const branches = build(sessionID, 0);
1188
+ const visible = [];
1189
+ const visit = (items, depth) => {
1190
+ for (const item of items) {
1191
+ visible.push({
1192
+ row: item.row,
1193
+ depth,
1194
+ hasChildren: !!item.children.length
1195
+ });
1196
+ if (!collapsed.has(item.row.node.sessionId))
1197
+ visit(item.children, depth + 1);
1198
+ }
1199
+ };
1200
+ visit(branches, 0);
1201
+ return { all, visible };
1202
+ }
1203
+ function statistics(rows, sessionID, complete, width) {
1204
+ const sum = (value) => {
1205
+ const values = rows.map(value);
1206
+ return complete && values.length && values.every((n) => n !== undefined) ? values.reduce((total, n) => total + (n ?? 0), 0) : undefined;
1207
+ };
1208
+ return [
1209
+ ...columns("Compactions", tokens(sum((r) => r.snapshot?.compaction.count)), width),
1210
+ ...columns("Active subagents", String(rows.filter((r) => r.node.sessionId !== sessionID && r.node.status === "running").length), width),
1211
+ ...columns("Family peaks (sum)", tokens(sum((r) => r.snapshot?.peakTokens)), width),
1212
+ ...wrap("Observed \xB7 separate windows", width)
1213
+ ];
1214
+ }
1215
+
1216
+ // src/tui/compact.tsx
1217
+ function CompactSidebar(props) {
1218
+ const [measuredWidth, setMeasuredWidth] = createSignal2(props.width);
1219
+ const width = () => Math.max(1, Math.min(props.width, measuredWidth()) - 4);
1220
+ const [collapsed, setCollapsed] = createSignal2(new Set);
1221
+ const [hideCompleted, setHideCompleted] = createSignal2(false);
1222
+ const [byPressure, setByPressure] = createSignal2(true);
1223
+ createEffect(() => {
1224
+ props.sessionID;
1225
+ setCollapsed(new Set);
1226
+ });
1227
+ const current = createMemo(() => props.rows.find((r) => r.node.sessionId === props.sessionID));
1228
+ const tree = createMemo(() => subagentTree(props.rows, props.sessionID, collapsed(), hideCompleted(), byPressure()));
1229
+ const entries = createMemo(() => new Map(tree().visible.map((entry) => [entry.row.node.sessionId, entry])));
1230
+ const toggle = (id) => setCollapsed((previous) => {
1231
+ const next = new Set(previous);
1232
+ if (next.has(id))
1233
+ next.delete(id);
1234
+ else
1235
+ next.add(id);
1236
+ return next;
1237
+ });
1238
+ const toggleCompleted = () => setHideCompleted((value) => !value);
1239
+ const toggleSort = () => setByPressure((value) => !value);
1240
+ const toggleGroups = () => setCollapsed((previous) => previous.size ? new Set : new Set(tree().all.map((r) => r.node.sessionId)));
1241
+ props.context.keymap.layer(() => ({
1242
+ mode: "global",
1243
+ commands: [{
1244
+ id: "context-guard.completed",
1245
+ title: "Toggle completed subagents",
1246
+ group: "Context Guard",
1247
+ palette: true,
1248
+ bind: false,
1249
+ run: () => {
1250
+ toggleCompleted();
1251
+ }
1252
+ }, {
1253
+ id: "context-guard.sort",
1254
+ title: "Toggle subagent pressure sorting",
1255
+ group: "Context Guard",
1256
+ palette: true,
1257
+ bind: false,
1258
+ run: () => {
1259
+ toggleSort();
1260
+ }
1261
+ }, {
1262
+ id: "context-guard.groups",
1263
+ title: "Collapse or expand subagent groups",
1264
+ group: "Context Guard",
1265
+ palette: true,
1266
+ bind: false,
1267
+ run: () => {
1268
+ toggleGroups();
1269
+ }
1270
+ }]
1271
+ }));
1272
+ const colors = () => props.context.theme.text;
1273
+ const executionColor = (row2) => row2?.node.status === "running" ? colors().feedback.success.base : row2?.node.status === "failed" ? colors().feedback.error.base : colors().muted;
1274
+ 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;
1275
+ const rowColor = (row2) => row2.node.status === "failed" ? colors().feedback.error.base : row2.node.status === "completed" && row2.pressure === "normal" ? colors().muted : pressureColor(row2);
1276
+ function Lines(input) {
1277
+ return _$createComponent(Index, {
1278
+ get each() {
1279
+ return input.lines;
1280
+ },
1281
+ children: (line) => (() => {
1282
+ var _el$ = _$createElement("text");
1283
+ _$insert(_el$, () => line() || " ");
1284
+ _$effect((_$p) => _$setProp(_el$, "fg", colors().base, _$p));
1285
+ return _el$;
1286
+ })()
1287
+ });
1288
+ }
1289
+ const section = (title) => {
1290
+ const head = cells(`\u2500\u2500 ${title} `) > width() ? title : `\u2500\u2500 ${title} `;
1291
+ return clip(head, width()) + "\u2500".repeat(Math.max(0, width() - cells(head)));
1292
+ };
1293
+ 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()))]);
1294
+ return (() => {
1295
+ 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");
1296
+ _$insertNode(_el$2, _el$3);
1297
+ _$insertNode(_el$2, _el$5);
1298
+ _$insertNode(_el$2, _el$6);
1299
+ _$insertNode(_el$2, _el$8);
1300
+ _$insertNode(_el$2, _el$9);
1301
+ _$insertNode(_el$2, _el$0);
1302
+ _$insertNode(_el$2, _el$1);
1303
+ _$insertNode(_el$2, _el$11);
1304
+ _$insertNode(_el$2, _el$12);
1305
+ _$setProp(_el$2, "width", "100%");
1306
+ _$setProp(_el$2, "flexShrink", 0);
1307
+ _$setProp(_el$2, "border", true);
1308
+ _$setProp(_el$2, "borderStyle", "rounded");
1309
+ _$setProp(_el$2, "title", " CONTEXT GUARD ");
1310
+ _$setProp(_el$2, "paddingX", 1);
1311
+ _$setProp(_el$2, "flexDirection", "column");
1312
+ _$setProp(_el$2, "onSizeChange", function() {
1313
+ setMeasuredWidth(this.width);
1314
+ });
1315
+ _$insert(_el$2, _$createComponent(Index, {
1316
+ get each() {
1317
+ return mainSections(current(), width(), props.state.age(props.sessionID));
1318
+ },
1319
+ children: (section2) => _$createComponent(Index, {
1320
+ get each() {
1321
+ return section2().lines;
1322
+ },
1323
+ children: (line) => (() => {
1324
+ var _el$13 = _$createElement("text");
1325
+ _$insert(_el$13, () => line() || " ");
1326
+ _$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));
1327
+ return _el$13;
1328
+ })()
1329
+ })
1330
+ }), _el$3);
1331
+ _$insert(_el$2, _$createComponent(Index, {
1332
+ get each() {
1333
+ return notices();
1334
+ },
1335
+ children: (line) => (() => {
1336
+ var _el$14 = _$createElement("text");
1337
+ _$insert(_el$14, line);
1338
+ _$effect((_$p) => _$setProp(_el$14, "fg", colors().feedback.warning.base, _$p));
1339
+ return _el$14;
1340
+ })()
1341
+ }), _el$3);
1342
+ _$insertNode(_el$3, _$createTextNode(` `));
1343
+ _$insert(_el$5, () => section(`SUBAGENTS (${tree().all.length})`));
1344
+ _$insert(_el$2, _$createComponent(Lines, {
1345
+ get lines() {
1346
+ 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());
1347
+ }
1348
+ }), _el$6);
1349
+ _$insertNode(_el$6, _$createTextNode(` `));
1350
+ _$insert(_el$2, _$createComponent(For, {
1351
+ get each() {
1352
+ return tree().visible.map((entry) => entry.row.node.sessionId);
1353
+ },
1354
+ children: (id) => {
1355
+ const entry = () => entries().get(id);
1356
+ const row2 = () => entry()?.row;
1357
+ const indent = () => " ".repeat(Math.min(entry()?.depth ?? 0, Math.floor(width() / 6)) * 2);
1358
+ const prefix = () => `${indent()}${entry()?.depth ? "\u2514\u2500 " : ""}${entry()?.hasChildren ? collapsed().has(id) ? "\u25B8 " : "\u25BE " : ""}`;
1359
+ const nameWidth = () => Math.max(1, width() - cells(prefix()));
1360
+ return _$createComponent(Show, {
1361
+ get when() {
1362
+ return row2();
1363
+ },
1364
+ children: (item) => (() => {
1365
+ 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");
1366
+ _$insertNode(_el$15, _el$16);
1367
+ _$insertNode(_el$15, _el$21);
1368
+ _$setProp(_el$15, "flexDirection", "column");
1369
+ _$setProp(_el$15, "flexShrink", 0);
1370
+ _$insertNode(_el$16, _el$18);
1371
+ _$setProp(_el$16, "flexDirection", "row");
1372
+ _$setProp(_el$16, "flexShrink", 0);
1373
+ _$insert(_el$16, _$createComponent(Show, {
1374
+ get when() {
1375
+ return prefix();
1376
+ },
1377
+ get children() {
1378
+ var _el$17 = _$createElement("text");
1379
+ _$setProp(_el$17, "onMouseDown", () => toggle(id));
1380
+ _$insert(_el$17, prefix);
1381
+ _$effect((_$p) => _$setProp(_el$17, "fg", colors().muted, _$p));
1382
+ return _el$17;
1383
+ }
1384
+ }), _el$18);
1385
+ _$insertNode(_el$18, _el$19);
1386
+ _$insertNode(_el$18, _el$20);
1387
+ _$setProp(_el$18, "onMouseDown", () => props.context.ui.router.navigate({
1388
+ type: "session",
1389
+ sessionID: id
1390
+ }));
1391
+ _$insert(_el$19, () => agentHeading(item(), nameWidth()).slice(0, 1));
1392
+ _$insert(_el$20, () => agentHeading(item(), nameWidth()).slice(1));
1393
+ _$insert(_el$15, _$createComponent(Index, {
1394
+ get each() {
1395
+ return agentLines(item(), Math.max(1, width() - cells(indent()) - 2));
1396
+ },
1397
+ children: (line) => (() => {
1398
+ var _el$23 = _$createElement("text"), _el$24 = _$createTextNode(` `);
1399
+ _$insertNode(_el$23, _el$24);
1400
+ _$insert(_el$23, indent, _el$24);
1401
+ _$insert(_el$23, line, null);
1402
+ _$effect((_$p) => _$setProp(_el$23, "fg", rowColor(item()), _$p));
1403
+ return _el$23;
1404
+ })()
1405
+ }), _el$21);
1406
+ _$insertNode(_el$21, _$createTextNode(` `));
1407
+ _$effect((_p$) => {
1408
+ var _v$0 = rowColor(item()), _v$1 = {
1409
+ fg: executionColor(item())
1410
+ };
1411
+ _v$0 !== _p$.e && (_p$.e = _$setProp(_el$18, "fg", _v$0, _p$.e));
1412
+ _v$1 !== _p$.t && (_p$.t = _$setProp(_el$19, "style", _v$1, _p$.t));
1413
+ return _p$;
1414
+ }, {
1415
+ e: undefined,
1416
+ t: undefined
1417
+ });
1418
+ return _el$15;
1419
+ })()
1420
+ });
1421
+ }
1422
+ }), _el$8);
1423
+ _$setProp(_el$8, "onMouseDown", toggleCompleted);
1424
+ _$insert(_el$8, () => clip(hideCompleted() ? "Show completed" : "Hide completed", width()));
1425
+ _$setProp(_el$9, "onMouseDown", toggleSort);
1426
+ _$insert(_el$9, () => clip(byPressure() ? "Sort: pressure" : "Sort: activity", width()));
1427
+ _$setProp(_el$0, "onMouseDown", toggleGroups);
1428
+ _$insert(_el$0, () => clip(collapsed().size ? "Expand groups" : "Collapse groups", width()));
1429
+ _$insertNode(_el$1, _$createTextNode(` `));
1430
+ _$insert(_el$11, () => section("STATISTICS"));
1431
+ _$insert(_el$2, _$createComponent(Lines, {
1432
+ get lines() {
1433
+ return statistics(props.rows.filter((r) => r.node.sessionId === props.sessionID).concat(tree().all), props.sessionID, props.state.complete(), width());
1434
+ }
1435
+ }), _el$12);
1436
+ _$setProp(_el$12, "onMouseDown", () => props.context.ui.panel.open("context-guard.monitor"));
1437
+ _$insert(_el$12, () => clip("Open: /context-guard", width()));
1438
+ _$effect((_p$) => {
1439
+ 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;
1440
+ _v$ !== _p$.e && (_p$.e = _$setProp(_el$2, "maxWidth", _v$, _p$.e));
1441
+ _v$2 !== _p$.t && (_p$.t = _$setProp(_el$2, "borderColor", _v$2, _p$.t));
1442
+ _v$3 !== _p$.a && (_p$.a = _$setProp(_el$2, "titleColor", _v$3, _p$.a));
1443
+ _v$4 !== _p$.o && (_p$.o = _$setProp(_el$5, "fg", _v$4, _p$.o));
1444
+ _v$5 !== _p$.i && (_p$.i = _$setProp(_el$8, "fg", _v$5, _p$.i));
1445
+ _v$6 !== _p$.n && (_p$.n = _$setProp(_el$9, "fg", _v$6, _p$.n));
1446
+ _v$7 !== _p$.s && (_p$.s = _$setProp(_el$0, "fg", _v$7, _p$.s));
1447
+ _v$8 !== _p$.h && (_p$.h = _$setProp(_el$11, "fg", _v$8, _p$.h));
1448
+ _v$9 !== _p$.r && (_p$.r = _$setProp(_el$12, "fg", _v$9, _p$.r));
1449
+ return _p$;
1450
+ }, {
1451
+ e: undefined,
1452
+ t: undefined,
1453
+ a: undefined,
1454
+ o: undefined,
1455
+ i: undefined,
1456
+ n: undefined,
1457
+ s: undefined,
1458
+ h: undefined,
1459
+ r: undefined
1460
+ });
1461
+ return _el$2;
1462
+ })();
1463
+ }
1464
+
1465
+ // src/tui/sidebar.tsx
1466
+ function GuardView(props) {
1467
+ const consumer = props.state.acquire(props.sessionID);
1468
+ createEffect2(() => consumer.update(props.sessionID));
1469
+ onCleanup(() => consumer.release());
1470
+ const rows = createMemo2(() => props.family ?? props.state.rows([], !props.expanded));
1471
+ const current = createMemo2(() => rows().find((row2) => row2.node.sessionId === props.sessionID));
1472
+ const detail = createMemo2(() => {
1473
+ const lines = detailLines(current(), props.width, props.expanded, props.state.age(props.sessionID));
1474
+ if (props.expanded) {
1475
+ const byId = new Map(rows().map((row2) => [row2.node.sessionId, row2.node]));
1476
+ const ancestry = [], seen = new Set;
1477
+ let node = byId.get(props.sessionID);
1478
+ while (node && !seen.has(node.sessionId)) {
1479
+ seen.add(node.sessionId);
1480
+ ancestry.unshift(node.agent ?? node.sessionId);
1481
+ node = node.parentId ? byId.get(node.parentId) : undefined;
1482
+ }
1483
+ lines.push(...wrap(`Ancestry: ${ancestry.join(" / ")}`, props.width));
1484
+ }
1485
+ if (props.inspectId && props.inspectId !== props.sessionID) {
1486
+ const inspected = rows().find((row2) => row2.node.sessionId === props.inspectId);
1487
+ lines.push(...wrap(`Inspect: ${props.inspectId}`, props.width), ...detailLines(inspected, props.width, true, props.state.age(props.inspectId)));
1488
+ }
1489
+ return lines;
1490
+ });
1491
+ const visible = createMemo2(() => {
1492
+ if (!props.expanded)
1493
+ return rows().slice(0, 7);
1494
+ const index = Math.max(0, rows().findIndex((row2) => row2.node.sessionId === props.selectedId));
1495
+ return rows().slice(Math.max(0, index - 10), Math.max(0, index - 10) + 25);
1496
+ });
1497
+ return _$createComponent2(Show2, {
1498
+ get when() {
1499
+ return props.expanded;
1500
+ },
1501
+ get fallback() {
1502
+ return _$createComponent2(CompactSidebar, {
1503
+ get sessionID() {
1504
+ return props.sessionID;
1505
+ },
1506
+ get state() {
1507
+ return props.state;
1508
+ },
1509
+ get context() {
1510
+ return props.context;
1511
+ },
1512
+ get rows() {
1513
+ return rows();
1514
+ },
1515
+ get width() {
1516
+ return props.width;
1517
+ }
1518
+ });
1519
+ },
1520
+ get children() {
1521
+ 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");
1522
+ _$insertNode2(_el$, _el$2);
1523
+ _$insertNode2(_el$, _el$4);
1524
+ _$insertNode2(_el$, _el$5);
1525
+ _$insertNode2(_el$, _el$6);
1526
+ _$insertNode2(_el$, _el$7);
1527
+ _$insertNode2(_el$, _el$0);
1528
+ _$setProp2(_el$, "flexDirection", "column");
1529
+ _$setProp2(_el$, "gap", 0);
1530
+ _$insertNode2(_el$2, _el$3);
1531
+ _$insert2(_el$2, () => props.state.health()?.mode ?? "?", null);
1532
+ _$insert2(_el$4, (() => {
1533
+ var _c$ = _$memo2(() => props.context.app.version === "2.0.29");
1534
+ return () => _c$() ? "" : clip(`Runtime ${props.context.app.version}: unverified (2.0.29 target)`, props.width);
1535
+ })());
1536
+ _$insert2(_el$5, () => clip(`${props.state.connection()} / ${props.state.health()?.enforcement ?? "?"}`, props.width));
1537
+ _$insert2(_el$, _$createComponent2(For2, {
1538
+ get each() {
1539
+ return detail();
1540
+ },
1541
+ children: (line) => (() => {
1542
+ var _el$1 = _$createElement2("text");
1543
+ _$insert2(_el$1, line);
1544
+ _$effect2((_$p) => _$setProp2(_el$1, "fg", props.context.theme.text.base, _$p));
1545
+ return _el$1;
1546
+ })()
1547
+ }), _el$6);
1548
+ _$insert2(_el$, _$createComponent2(For2, {
1549
+ get each() {
1550
+ return props.state.health()?.reasons ?? [];
1551
+ },
1552
+ children: (reason) => (() => {
1553
+ var _el$10 = _$createElement2("text");
1554
+ _$insert2(_el$10, () => clip(reason, props.width));
1555
+ _$effect2((_$p) => _$setProp2(_el$10, "fg", props.context.theme.text.feedback.warning.base, _$p));
1556
+ return _el$10;
1557
+ })()
1558
+ }), _el$6);
1559
+ _$insert2(_el$6, (() => {
1560
+ var _c$2 = _$memo2(() => props.state.health()?.mode === "observe");
1561
+ return () => _c$2() ? current()?.pressure === "crossed" ? "Observe: would compact" : "Observe: no admission" : "";
1562
+ })());
1563
+ _$insertNode2(_el$7, _el$8);
1564
+ _$insertNode2(_el$7, _el$9);
1565
+ _$insert2(_el$7, () => rows().length, _el$9);
1566
+ _$insert2(_el$7, () => props.state.complete() ? "" : " partial", null);
1567
+ _$insert2(_el$, _$createComponent2(For2, {
1568
+ get each() {
1569
+ return visible();
1570
+ },
1571
+ children: (row2) => (() => {
1572
+ var _el$11 = _$createElement2("box");
1573
+ _$setProp2(_el$11, "flexDirection", "column");
1574
+ _$setProp2(_el$11, "onMouseDown", () => {
1575
+ if (row2.hidden)
1576
+ props.state.expand(row2.node.sessionId);
1577
+ else {
1578
+ if (props.expanded)
1579
+ props.context.ui.panel.close();
1580
+ props.context.ui.router.navigate({
1581
+ type: "session",
1582
+ sessionID: row2.node.sessionId
1583
+ });
1584
+ }
1585
+ });
1586
+ _$insert2(_el$11, _$createComponent2(For2, {
1587
+ get each() {
1588
+ return rowLines(row2, Math.max(1, props.width - (props.expanded ? 2 : 0))).map((line) => props.expanded ? `${row2.node.sessionId === props.selectedId ? ">" : " "} ${line}` : line);
1589
+ },
1590
+ children: (line) => (() => {
1591
+ var _el$12 = _$createElement2("text");
1592
+ _$insert2(_el$12, line);
1593
+ _$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));
1594
+ return _el$12;
1595
+ })()
1596
+ }));
1597
+ _$effect2((_$p) => _$setProp2(_el$11, "id", `context-guard.row.${row2.node.sessionId}`, _$p));
1598
+ return _el$11;
1599
+ })()
1600
+ }), _el$0);
1601
+ _$setProp2(_el$0, "onMouseDown", () => props.context.ui.panel.open("context-guard.monitor"));
1602
+ _$insert2(_el$0, (() => {
1603
+ var _c$3 = _$memo2(() => rows().length > visible().length);
1604
+ return () => _c$3() ? `+ ${rows().length - visible().length} rows; open panel` : "Open: /context-guard";
1605
+ })());
1606
+ _$effect2((_p$) => {
1607
+ 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;
1608
+ _v$ !== _p$.e && (_p$.e = _$setProp2(_el$2, "fg", _v$, _p$.e));
1609
+ _v$2 !== _p$.t && (_p$.t = _$setProp2(_el$4, "fg", _v$2, _p$.t));
1610
+ _v$3 !== _p$.a && (_p$.a = _$setProp2(_el$5, "fg", _v$3, _p$.a));
1611
+ _v$4 !== _p$.o && (_p$.o = _$setProp2(_el$6, "fg", _v$4, _p$.o));
1612
+ _v$5 !== _p$.i && (_p$.i = _$setProp2(_el$7, "fg", _v$5, _p$.i));
1613
+ _v$6 !== _p$.n && (_p$.n = _$setProp2(_el$0, "fg", _v$6, _p$.n));
1614
+ return _p$;
1615
+ }, {
1616
+ e: undefined,
1617
+ t: undefined,
1618
+ a: undefined,
1619
+ o: undefined,
1620
+ i: undefined,
1621
+ n: undefined
1622
+ });
1623
+ return _el$;
1624
+ }
1625
+ });
1626
+ }
1627
+
1628
+ // src/tui/panel.tsx
1629
+ import { effect as _$effect3 } from "@opentui/solid";
1630
+ import { insert as _$insert3 } from "@opentui/solid";
1631
+ import { createTextNode as _$createTextNode3 } from "@opentui/solid";
1632
+ import { insertNode as _$insertNode3 } from "@opentui/solid";
1633
+ import { createComponent as _$createComponent3 } from "@opentui/solid";
1634
+ import { setProp as _$setProp3 } from "@opentui/solid";
1635
+ import { use as _$use } from "@opentui/solid";
1636
+ import { createElement as _$createElement3 } from "@opentui/solid";
1637
+ import { createSignal as createSignal3, createMemo as createMemo3, createEffect as createEffect3, onCleanup as onCleanup2 } from "solid-js";
1638
+ function GuardPanel(props) {
1639
+ const [inspectId, setInspectId] = createSignal3();
1640
+ const [selected, setSelected] = createSignal3(props.input.sessionID);
1641
+ const [order, setOrder] = createSignal3([]);
1642
+ const rows = createMemo3(() => props.state.rows(order()));
1643
+ const offset = () => Math.max(0, rows().findIndex((row2) => row2.node.sessionId === selected()));
1644
+ createEffect3(() => {
1645
+ if (!props.input.focused)
1646
+ setOrder([]);
1647
+ });
1648
+ createEffect3(() => {
1649
+ setSelected(props.input.sessionID);
1650
+ setOrder([]);
1651
+ });
1652
+ let scroll;
1653
+ let follow;
1654
+ function revealSelection() {
1655
+ if (follow)
1656
+ props.context.renderer.removeFrameCallback(follow);
1657
+ let frames = 0;
1658
+ follow = async () => {
1659
+ scroll?.scrollChildIntoView(`context-guard.row.${selected()}`);
1660
+ if (++frames >= 2 && follow) {
1661
+ props.context.renderer.removeFrameCallback(follow);
1662
+ follow = undefined;
1663
+ } else
1664
+ props.context.renderer.requestRender();
1665
+ };
1666
+ props.context.renderer.setFrameCallback(follow);
1667
+ props.context.renderer.requestRender();
1668
+ }
1669
+ onCleanup2(() => {
1670
+ if (follow)
1671
+ props.context.renderer.removeFrameCallback(follow);
1672
+ });
1673
+ function move(delta) {
1674
+ setOrder(rows().map((row3) => row3.node.sessionId));
1675
+ const row2 = rows()[Math.max(0, Math.min(rows().length - 1, offset() + delta))];
1676
+ if (row2) {
1677
+ setSelected(row2.node.sessionId);
1678
+ revealSelection();
1679
+ }
1680
+ }
1681
+ props.context.keymap.layer(() => ({
1682
+ enabled: () => props.input.focused,
1683
+ commands: [{
1684
+ id: "context-guard.inspect",
1685
+ title: "Inspect selected session",
1686
+ bind: "i",
1687
+ run: () => setInspectId(inspectId() === selected() ? undefined : selected())
1688
+ }, {
1689
+ id: "context-guard.refresh",
1690
+ title: "Refresh Context Guard",
1691
+ bind: "r",
1692
+ run: () => {
1693
+ setOrder([]);
1694
+ props.state.refresh();
1695
+ }
1696
+ }, {
1697
+ id: "context-guard.load-more",
1698
+ title: "Load more family",
1699
+ bind: "l",
1700
+ run: () => props.state.loadMore()
1701
+ }, {
1702
+ id: "context-guard.next",
1703
+ bind: "down",
1704
+ run: () => move(1)
1705
+ }, {
1706
+ id: "context-guard.previous",
1707
+ bind: "up",
1708
+ run: () => move(-1)
1709
+ }, {
1710
+ id: "context-guard.expand",
1711
+ bind: "space",
1712
+ run: () => {
1713
+ const row2 = rows()[offset()];
1714
+ if (row2)
1715
+ props.state.expand(row2.node.sessionId);
1716
+ }
1717
+ }, {
1718
+ id: "context-guard.open",
1719
+ bind: "return",
1720
+ run: () => {
1721
+ const row2 = rows()[offset()];
1722
+ if (row2) {
1723
+ props.input.close();
1724
+ props.context.ui.router.navigate({
1725
+ type: "session",
1726
+ sessionID: row2.node.sessionId
1727
+ });
1728
+ }
1729
+ }
1730
+ }]
1731
+ }));
1732
+ return (() => {
1733
+ var _el$ = _$createElement3("scrollbox"), _el$2 = _$createElement3("text"), _el$4 = _$createElement3("text"), _el$5 = _$createTextNode3(`up/down select `), _el$6 = _$createTextNode3(`; Enter open`), _el$7 = _$createElement3("text"), _el$9 = _$createElement3("text"), _el$1 = _$createElement3("text");
1734
+ _$insertNode3(_el$, _el$2);
1735
+ _$insertNode3(_el$, _el$4);
1736
+ _$insertNode3(_el$, _el$7);
1737
+ _$insertNode3(_el$, _el$9);
1738
+ _$insertNode3(_el$, _el$1);
1739
+ _$use((value) => {
1740
+ scroll = value;
1741
+ }, _el$);
1742
+ _$setProp3(_el$, "flexGrow", 1);
1743
+ _$insert3(_el$, _$createComponent3(GuardView, {
1744
+ get sessionID() {
1745
+ return props.input.sessionID;
1746
+ },
1747
+ get state() {
1748
+ return props.state;
1749
+ },
1750
+ get context() {
1751
+ return props.context;
1752
+ },
1753
+ get width() {
1754
+ return Math.max(10, props.input.width - 4);
1755
+ },
1756
+ expanded: true,
1757
+ get family() {
1758
+ return rows();
1759
+ },
1760
+ get selectedId() {
1761
+ return selected();
1762
+ },
1763
+ get inspectId() {
1764
+ return inspectId();
1765
+ }
1766
+ }), _el$2);
1767
+ _$insertNode3(_el$2, _$createTextNode3(`r refresh / l load more`));
1768
+ _$insertNode3(_el$4, _el$5);
1769
+ _$insertNode3(_el$4, _el$6);
1770
+ _$insert3(_el$4, () => offset() + 1, _el$6);
1771
+ _$insertNode3(_el$7, _$createTextNode3(`space expand / i inspect selected`));
1772
+ _$insertNode3(_el$9, _$createTextNode3(`Guard input = prompt + caches.`));
1773
+ _$insertNode3(_el$1, _$createTextNode3(`Native Context uses different totals.`));
1774
+ _$effect3((_p$) => {
1775
+ var _v$ = props.context.theme.text.muted, _v$2 = props.context.theme.text.muted, _v$3 = props.context.theme.text.muted, _v$4 = props.context.theme.text.muted, _v$5 = props.context.theme.text.muted;
1776
+ _v$ !== _p$.e && (_p$.e = _$setProp3(_el$2, "fg", _v$, _p$.e));
1777
+ _v$2 !== _p$.t && (_p$.t = _$setProp3(_el$4, "fg", _v$2, _p$.t));
1778
+ _v$3 !== _p$.a && (_p$.a = _$setProp3(_el$7, "fg", _v$3, _p$.a));
1779
+ _v$4 !== _p$.o && (_p$.o = _$setProp3(_el$9, "fg", _v$4, _p$.o));
1780
+ _v$5 !== _p$.i && (_p$.i = _$setProp3(_el$1, "fg", _v$5, _p$.i));
1781
+ return _p$;
1782
+ }, {
1783
+ e: undefined,
1784
+ t: undefined,
1785
+ a: undefined,
1786
+ o: undefined,
1787
+ i: undefined
1788
+ });
1789
+ return _el$;
1790
+ })();
1791
+ }
1792
+
1793
+ // src/tui.tsx
1794
+ var tui_default = Plugin.define({
1795
+ id: "context-guard",
1796
+ setup(context) {
1797
+ if (typeof context.client?.rpc !== "function" || typeof context.ui?.slot !== "function" || typeof context.ui?.panel?.open !== "function" || typeof context.keymap?.layer !== "function") {
1798
+ throw new Error("Context Guard TUI requires OpenCode 2.0.29 public RPC and panel APIs; use the Guard inspect CLI on unsupported hosts.");
1799
+ }
1800
+ const state = createGuardState(context), unregister = [];
1801
+ try {
1802
+ unregister.push(context.ui.slot({
1803
+ append: "sidebar.content",
1804
+ render: (input) => _$createComponent4(GuardView, {
1805
+ get sessionID() {
1806
+ return input.sessionID;
1807
+ },
1808
+ state,
1809
+ context,
1810
+ width: 35
1811
+ })
1812
+ }));
1813
+ unregister.push(context.ui.slot({
1814
+ append: "session.panel",
1815
+ render: (input) => _$createComponent4(Show3, {
1816
+ get when() {
1817
+ return input.name === "context-guard.monitor";
1818
+ },
1819
+ get children() {
1820
+ return _$createComponent4(GuardPanel, {
1821
+ input,
1822
+ state,
1823
+ context
1824
+ });
1825
+ }
1826
+ })
1827
+ }));
1828
+ unregister.push(context.ui.slot({
1829
+ append: "session.composer.top",
1830
+ render: (input) => _$createComponent4(Show3, {
1831
+ get when() {
1832
+ return context.data.session.get(input.sessionID)?.parentID;
1833
+ },
1834
+ get children() {
1835
+ var _el$ = _$createElement4("text");
1836
+ _$insertNode4(_el$, _$createTextNode4(`Context Guard: open panel`));
1837
+ _$setProp4(_el$, "onMouseDown", () => context.ui.panel.open("context-guard.monitor"));
1838
+ _$effect4((_$p) => _$setProp4(_el$, "fg", context.theme.text.muted, _$p));
1839
+ return _el$;
1840
+ }
1841
+ })
1842
+ }));
1843
+ context.keymap.layer(() => ({
1844
+ mode: "global",
1845
+ commands: [{
1846
+ id: "context-guard.show",
1847
+ title: "Open Context Guard",
1848
+ group: "Context Guard",
1849
+ palette: true,
1850
+ bind: false,
1851
+ slash: {
1852
+ name: "context-guard"
1853
+ },
1854
+ run: () => {
1855
+ context.ui.panel.open("context-guard.monitor");
1856
+ }
1857
+ }]
1858
+ }));
1859
+ } catch (error) {
1860
+ for (const dispose of unregister.reverse())
1861
+ dispose();
1862
+ state.dispose();
1863
+ throw error;
1864
+ }
1865
+ return () => {
1866
+ for (const dispose of unregister.reverse())
1867
+ dispose();
1868
+ state.dispose();
1869
+ };
1870
+ }
1871
+ });
1872
+ export {
1873
+ tui_default as default
1874
+ };