@mageyuki/opencode-subagent-statusline 0.0.0-stage → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/tui-v2.js ADDED
@@ -0,0 +1,2922 @@
1
+ // src/tui-v2.tsx
2
+ import { insert as _$insert2 } from "@opentui/solid";
3
+ import { createComponent as _$createComponent2 } from "@opentui/solid";
4
+ import { memo as _$memo2 } from "@opentui/solid";
5
+ import { use as _$use2 } from "@opentui/solid";
6
+ import { setProp as _$setProp2 } from "@opentui/solid";
7
+ import { createElement as _$createElement2 } from "@opentui/solid";
8
+ import { Plugin } from "@opencode/plugin/tui";
9
+ import { batch, createEffect as createEffect2, createRoot, createSignal as createSignal2, onCleanup as onCleanup2, Show as Show2, untrack } from "solid-js";
10
+
11
+ // src/state.ts
12
+ import { randomUUID } from "crypto";
13
+ import { mkdir, readFile, rename, rm, writeFile } from "fs/promises";
14
+ import { basename, dirname, join } from "path";
15
+ import os from "os";
16
+
17
+ // src/internal-policy.ts
18
+ var TERMINAL_CHILD_TTL_MS = 3 * 24 * 60 * 60 * 1e3;
19
+ var MAX_TERMINAL_CHILDREN = 1500;
20
+ var V2_RETRY_INITIAL_DELAY_MS = 1e3;
21
+ var V2_RETRY_MAX_DELAY_MS = 3e4;
22
+ var V2_RETRY_MAX_ATTEMPTS = 6;
23
+ var V2_DETAIL_CONCURRENCY = 4;
24
+ var V2_HISTORY_PAGE_SIZE = 100;
25
+ var V2_FOCUS_MAX_ATTEMPTS = 10;
26
+ var V2_FOCUS_RETRY_DELAY_MS = 30;
27
+ var MAX_SYNC_LOG_READ_BYTES = 1024 * 1024;
28
+
29
+ // src/subagent-classification.ts
30
+ function isRealSessionID(value) {
31
+ return typeof value === "string" && value.startsWith("ses_");
32
+ }
33
+ function isTrustedTargetSessionID(value) {
34
+ return isRealSessionID(value);
35
+ }
36
+ function trustedTargetSessionID(item) {
37
+ return isTrustedTargetSessionID(item.targetSessionID) ? item.targetSessionID : void 0;
38
+ }
39
+ function isRealExecution(item) {
40
+ return item.source === "session" || isRealSessionID(item.id);
41
+ }
42
+ function realExecutionID(item) {
43
+ return trustedTargetSessionID(item) ?? item.id;
44
+ }
45
+ function classifySubagentWorkItem(item) {
46
+ if (isRealExecution(item)) {
47
+ const executionID = realExecutionID(item);
48
+ return {
49
+ kind: "real-execution",
50
+ executionID,
51
+ targetSessionID: executionID
52
+ };
53
+ }
54
+ const targetSessionID = trustedTargetSessionID(item);
55
+ if (targetSessionID) {
56
+ return {
57
+ kind: "execution-proxy",
58
+ executionID: targetSessionID,
59
+ targetSessionID
60
+ };
61
+ }
62
+ return { kind: "invocation-wrapper" };
63
+ }
64
+ function uniqueExecutionID(candidates) {
65
+ const executionIDs = new Set(candidates.map((item) => realExecutionID(item)));
66
+ return executionIDs.size === 1 ? [...executionIDs][0] : void 0;
67
+ }
68
+ function realExecutions(items) {
69
+ return items.filter((item) => classifySubagentWorkItem(item).kind === "real-execution");
70
+ }
71
+ function resolveTrustedTargetExecutionID(item, realItems) {
72
+ const targetSessionID = trustedTargetSessionID(item);
73
+ if (!targetSessionID) return void 0;
74
+ return realExecutions(realItems).some(
75
+ (realItem) => realExecutionID(realItem) === targetSessionID
76
+ ) ? targetSessionID : void 0;
77
+ }
78
+ function resolveSharedMessageExecutionID(item, realItems) {
79
+ if (!item.messageID) return void 0;
80
+ return uniqueExecutionID(
81
+ realExecutions(realItems).filter(
82
+ (realItem) => realItem.parentID === item.parentID && realItem.messageID === item.messageID
83
+ )
84
+ );
85
+ }
86
+ function resolveUniqueSameParentExecutionID(item, realItems) {
87
+ return uniqueExecutionID(
88
+ realExecutions(realItems).filter(
89
+ (realItem) => realItem.parentID === item.parentID
90
+ )
91
+ );
92
+ }
93
+ function resolveCorrelatedExecutionID(item, realItems) {
94
+ if (trustedTargetSessionID(item)) {
95
+ return resolveTrustedTargetExecutionID(item, realItems);
96
+ }
97
+ return resolveSharedMessageExecutionID(item, realItems) ?? resolveUniqueSameParentExecutionID(item, realItems);
98
+ }
99
+ function correlateSubagentWorkItems(items) {
100
+ const realItems = realExecutions(items);
101
+ const executions = /* @__PURE__ */ new Map();
102
+ for (const item of realItems) {
103
+ const executionID = realExecutionID(item);
104
+ if (!executions.has(executionID)) {
105
+ executions.set(executionID, { executionID, real: item, proxies: [] });
106
+ }
107
+ }
108
+ for (const item of items) {
109
+ if (classifySubagentWorkItem(item).kind === "real-execution") continue;
110
+ const executionID = resolveCorrelatedExecutionID(item, realItems);
111
+ if (!executionID) continue;
112
+ executions.get(executionID)?.proxies.push(item);
113
+ }
114
+ return [...executions.values()];
115
+ }
116
+ function mergeProxyMetadataWithRealExecution(real, proxy) {
117
+ const executionID = realExecutionID(real);
118
+ return {
119
+ ...real,
120
+ title: proxy.title ?? real.title,
121
+ summary: proxy.summary ?? real.summary,
122
+ agentName: proxy.agentName ?? real.agentName,
123
+ messageID: real.messageID ?? proxy.messageID,
124
+ id: real.id,
125
+ parentID: real.parentID,
126
+ source: "session",
127
+ targetSessionID: real.targetSessionID ?? executionID,
128
+ status: real.status,
129
+ color: real.color,
130
+ startedAt: real.startedAt,
131
+ updatedAt: real.updatedAt,
132
+ endedAt: real.endedAt,
133
+ elapsedMs: real.elapsedMs,
134
+ tokens: real.tokens,
135
+ model: real.model
136
+ };
137
+ }
138
+
139
+ // src/state.ts
140
+ function statusColor(status) {
141
+ if (status === "done") return "green";
142
+ if (status === "error") return "red";
143
+ return "yellow";
144
+ }
145
+ function safeTimestamp(input, fallback) {
146
+ if (typeof input !== "string") return fallback;
147
+ return Number.isNaN(Date.parse(input)) ? fallback : input;
148
+ }
149
+ function toFiniteNumber(value) {
150
+ if (typeof value === "number" && Number.isFinite(value)) {
151
+ return value;
152
+ }
153
+ if (typeof value === "string" && value.trim().length > 0) {
154
+ const parsed = Number(value);
155
+ return Number.isFinite(parsed) ? parsed : void 0;
156
+ }
157
+ return void 0;
158
+ }
159
+ function toNonNegativeInteger(value) {
160
+ const parsed = toFiniteNumber(value);
161
+ if (parsed === void 0) return void 0;
162
+ return Math.max(0, Math.floor(parsed));
163
+ }
164
+ function sanitizeCountedChildIDs(input) {
165
+ if (!input || typeof input !== "object") return {};
166
+ const counted = {};
167
+ for (const [id, value] of Object.entries(input)) {
168
+ if (!id) continue;
169
+ if (value === true) {
170
+ counted[id] = true;
171
+ }
172
+ }
173
+ return counted;
174
+ }
175
+ function normalizeExecutionCounters(state) {
176
+ state.countedChildIDs = sanitizeCountedChildIDs(state.countedChildIDs);
177
+ const countedTotal = Object.keys(state.countedChildIDs).length;
178
+ state.totalExecuted = Math.max(
179
+ toNonNegativeInteger(state.totalExecuted) ?? 0,
180
+ countedTotal
181
+ );
182
+ }
183
+ function resolveExecutionCountIdentity(child) {
184
+ const classification = classifySubagentWorkItem(child);
185
+ return classification.kind === "real-execution" ? classification.executionID : void 0;
186
+ }
187
+ function countHistoricalSubagentExecutions(input) {
188
+ const children = Array.isArray(input.children) ? input.children : Object.values(input.children);
189
+ const scopedChildren = input.parentSessionID ? children.filter((child) => child.parentID === input.parentSessionID) : children;
190
+ return correlateSubagentWorkItems(scopedChildren).length;
191
+ }
192
+ function countCountedSubagentExecutions(input) {
193
+ const children = Array.isArray(input.children) ? input.children : Object.values(input.children);
194
+ const scopedChildren = input.parentSessionID ? children.filter((child) => child.parentID === input.parentSessionID) : children;
195
+ return correlateSubagentWorkItems(scopedChildren).filter(
196
+ (execution) => input.countedChildIDs[execution.executionID]
197
+ ).length;
198
+ }
199
+ function countRetainedSubagentStatuses(input) {
200
+ const children = Array.isArray(input.children) ? input.children : Object.values(input.children);
201
+ const scopedChildren = input.parentSessionID ? children.filter((child) => child.parentID === input.parentSessionID) : children;
202
+ const counts = { running: 0, done: 0, error: 0 };
203
+ for (const { real } of correlateSubagentWorkItems(scopedChildren)) {
204
+ counts[real.status] += 1;
205
+ }
206
+ return counts;
207
+ }
208
+ function reconcileCountedExecutionsWithChildren(state) {
209
+ const executionIDs = correlateSubagentWorkItems(
210
+ Object.values(state.children)
211
+ ).map((execution) => execution.executionID);
212
+ state.countedChildIDs = Object.fromEntries(
213
+ executionIDs.map((id) => [id, true])
214
+ );
215
+ state.totalExecuted = executionIDs.length;
216
+ }
217
+ function countChildExecution(state, child) {
218
+ normalizeExecutionCounters(state);
219
+ const countIdentity = resolveExecutionCountIdentity(child);
220
+ if (!countIdentity) return false;
221
+ if (state.countedChildIDs[countIdentity]) return false;
222
+ const previousTotal = Math.max(
223
+ toNonNegativeInteger(state.totalExecuted) ?? 0,
224
+ Object.keys(state.countedChildIDs).length
225
+ );
226
+ state.countedChildIDs[countIdentity] = true;
227
+ state.totalExecuted = previousTotal + 1;
228
+ return true;
229
+ }
230
+ function sanitizeTokens(input) {
231
+ if (!input || typeof input !== "object") return void 0;
232
+ const raw = input;
233
+ const tokens2 = {
234
+ input: toFiniteNumber(raw.input),
235
+ output: toFiniteNumber(raw.output),
236
+ total: toFiniteNumber(raw.total),
237
+ contextPercent: toFiniteNumber(raw.contextPercent)
238
+ };
239
+ if (tokens2.input === void 0 && tokens2.output === void 0 && tokens2.total === void 0 && tokens2.contextPercent === void 0) {
240
+ return void 0;
241
+ }
242
+ return tokens2;
243
+ }
244
+ function sanitizeModel(input) {
245
+ if (!input || typeof input !== "object") return void 0;
246
+ const raw = input;
247
+ const providerID = typeof raw.providerID === "string" ? raw.providerID.trim() : "";
248
+ const modelID = typeof raw.modelID === "string" ? raw.modelID.trim() : "";
249
+ const variant = typeof raw.variant === "string" ? raw.variant.trim() : "";
250
+ if (!providerID || !modelID) return void 0;
251
+ return { providerID, modelID, ...variant ? { variant } : {} };
252
+ }
253
+ function sanitizeTargetSessionID(value, fallback) {
254
+ if (typeof value === "string" && value.startsWith("ses_")) {
255
+ return value;
256
+ }
257
+ if (typeof fallback === "string" && fallback.startsWith("ses_")) {
258
+ return fallback;
259
+ }
260
+ return void 0;
261
+ }
262
+ function mergeTokens(existing, incoming) {
263
+ if (!existing && !incoming) return void 0;
264
+ return {
265
+ input: incoming?.input ?? existing?.input,
266
+ output: incoming?.output ?? existing?.output,
267
+ total: incoming?.total ?? existing?.total,
268
+ contextPercent: incoming?.contextPercent ?? existing?.contextPercent
269
+ };
270
+ }
271
+ function sameTokens(left, right) {
272
+ return JSON.stringify(left) === JSON.stringify(right);
273
+ }
274
+ function normalizeComparableText(value) {
275
+ return value.replace(/\s+/g, " ").trim().toLowerCase();
276
+ }
277
+ function sanitizeSummary(value, title) {
278
+ if (typeof value !== "string") return void 0;
279
+ const summary = value.replace(/\s+/g, " ").trim();
280
+ if (!summary) return void 0;
281
+ if (normalizeComparableText(summary) === normalizeComparableText(title)) {
282
+ return void 0;
283
+ }
284
+ return summary;
285
+ }
286
+ function sanitizeAgentName(value) {
287
+ if (typeof value !== "string") return void 0;
288
+ const agentName = value.replace(/^\((.*)\)$/, "$1").replace(/\s+/g, " ").trim();
289
+ return agentName || void 0;
290
+ }
291
+ function resolveElapsedMs(child, nowMs) {
292
+ const startedMs = Date.parse(child.startedAt);
293
+ if (Number.isNaN(startedMs)) return 0;
294
+ const endSource = child.endedAt ?? child.updatedAt;
295
+ const endMs = child.endedAt ? Date.parse(endSource) : nowMs;
296
+ if (Number.isNaN(endMs)) return 0;
297
+ return Math.max(0, endMs - startedMs);
298
+ }
299
+ function terminalReferenceMs(child) {
300
+ const parsed = Date.parse(
301
+ child.endedAt ?? child.updatedAt ?? child.startedAt
302
+ );
303
+ return Number.isNaN(parsed) ? 0 : parsed;
304
+ }
305
+ function pruneTerminalChildren(state, now = /* @__PURE__ */ new Date()) {
306
+ const nowMs = now.getTime();
307
+ const terminalChildren = [];
308
+ let pruned = 0;
309
+ for (const child of Object.values(state.children)) {
310
+ if (child.status === "running") continue;
311
+ const referenceMs = terminalReferenceMs(child);
312
+ if (nowMs - referenceMs > TERMINAL_CHILD_TTL_MS) {
313
+ delete state.children[child.id];
314
+ pruned += 1;
315
+ continue;
316
+ }
317
+ terminalChildren.push({ id: child.id, referenceMs });
318
+ }
319
+ if (terminalChildren.length <= MAX_TERMINAL_CHILDREN) {
320
+ return pruned;
321
+ }
322
+ terminalChildren.sort(
323
+ (a, b) => b.referenceMs - a.referenceMs || a.id.localeCompare(b.id)
324
+ );
325
+ for (const child of terminalChildren.slice(MAX_TERMINAL_CHILDREN)) {
326
+ delete state.children[child.id];
327
+ pruned += 1;
328
+ }
329
+ return pruned;
330
+ }
331
+ function refreshDerivedFields(state, now = /* @__PURE__ */ new Date()) {
332
+ const nowISO = now.toISOString();
333
+ const nowMs = now.getTime();
334
+ normalizeExecutionCounters(state);
335
+ for (const [id, child] of Object.entries(state.children)) {
336
+ const startedAt = safeTimestamp(child.startedAt, nowISO);
337
+ const updatedAt = safeTimestamp(child.updatedAt, nowISO);
338
+ const endedAt = child.endedAt ? safeTimestamp(child.endedAt, updatedAt) : void 0;
339
+ const status = child.status === "done" || child.status === "error" || child.status === "running" ? child.status : "running";
340
+ const targetSessionID = sanitizeTargetSessionID(
341
+ child.targetSessionID,
342
+ id.startsWith("ses_") ? id : void 0
343
+ );
344
+ state.children[id] = {
345
+ ...child,
346
+ startedAt,
347
+ updatedAt,
348
+ endedAt,
349
+ status,
350
+ targetSessionID,
351
+ color: statusColor(status),
352
+ tokens: sanitizeTokens(child.tokens),
353
+ model: sanitizeModel(child.model),
354
+ elapsedMs: resolveElapsedMs(
355
+ {
356
+ ...child,
357
+ startedAt,
358
+ updatedAt,
359
+ endedAt,
360
+ status,
361
+ color: statusColor(status)
362
+ },
363
+ nowMs
364
+ )
365
+ };
366
+ }
367
+ reconcileCountedExecutionsWithChildren(state);
368
+ state.updatedAt = safeTimestamp(state.updatedAt, nowISO);
369
+ if (pruneTerminalChildren(state, now) > 0) {
370
+ reconcileCountedExecutionsWithChildren(state);
371
+ state.updatedAt = nowISO;
372
+ }
373
+ }
374
+ var STATUS_DIRNAME = "opencode-subagent-statusline";
375
+ var STATUS_FILENAME = "state.json";
376
+ var STATUS_DIR_MODE = 448;
377
+ var STATUS_FILE_MODE = 384;
378
+ function sanitizeInstanceName(input) {
379
+ return input.replace(/[^A-Za-z0-9._-]/g, "_");
380
+ }
381
+ function resolveDefaultInstanceName() {
382
+ const fromEnv = process.env.OPENCODE_SUBAGENT_STATUSLINE_INSTANCE;
383
+ if (typeof fromEnv === "string" && fromEnv.trim().length > 0) {
384
+ const safe = sanitizeInstanceName(fromEnv);
385
+ if (safe.length > 0) {
386
+ return safe;
387
+ }
388
+ }
389
+ return `pid-${process.pid}`;
390
+ }
391
+ function createEmptyState() {
392
+ return {
393
+ children: {},
394
+ countedChildIDs: {},
395
+ totalExecuted: 0,
396
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
397
+ };
398
+ }
399
+ function resolveStatePath() {
400
+ const fromEnv = process.env.OPENCODE_SUBAGENT_STATUSLINE_STATE;
401
+ if (typeof fromEnv === "string" && fromEnv.trim().length > 0) {
402
+ return fromEnv;
403
+ }
404
+ const runtimeDir = process.env.XDG_RUNTIME_DIR ?? os.tmpdir();
405
+ const instance = resolveDefaultInstanceName();
406
+ return join(runtimeDir, STATUS_DIRNAME, instance, STATUS_FILENAME);
407
+ }
408
+ function resolveTextPath(statePath) {
409
+ return join(dirname(statePath), "status.txt");
410
+ }
411
+ async function writeLocalStatusFile(path, contents, options) {
412
+ if (options?.shouldCommit?.() === false) return;
413
+ const directory = dirname(path);
414
+ await mkdir(directory, { recursive: true, mode: STATUS_DIR_MODE });
415
+ const tempPath = join(
416
+ directory,
417
+ `.${basename(path)}.${process.pid}.${Date.now()}.${randomUUID()}.tmp`
418
+ );
419
+ try {
420
+ await writeFile(tempPath, contents, {
421
+ encoding: "utf8",
422
+ mode: STATUS_FILE_MODE
423
+ });
424
+ if (options?.shouldCommit?.() === false) {
425
+ await rm(tempPath, { force: true });
426
+ return;
427
+ }
428
+ await rename(tempPath, path);
429
+ } catch (error) {
430
+ await rm(tempPath, { force: true }).catch(() => void 0);
431
+ throw error;
432
+ }
433
+ }
434
+ async function saveStatusText(textPath, contents, options) {
435
+ await writeLocalStatusFile(textPath, contents, options);
436
+ }
437
+ async function saveState(statePath, state, options) {
438
+ if (options?.shouldCommit?.() === false) return;
439
+ refreshDerivedFields(state);
440
+ await writeLocalStatusFile(statePath, JSON.stringify(state, null, 2), options);
441
+ }
442
+ function upsertRunningChild(state, input) {
443
+ const now = (/* @__PURE__ */ new Date()).toISOString();
444
+ const observedUpdatedAt = safeTimestamp(input.updatedAt, now);
445
+ const observedStartedAt = safeTimestamp(input.startedAt, observedUpdatedAt);
446
+ const existing = state.children[input.id];
447
+ const targetSessionID = sanitizeTargetSessionID(
448
+ input.targetSessionID ?? existing?.targetSessionID,
449
+ input.id.startsWith("ses_") ? input.id : void 0
450
+ );
451
+ const source = input.source ?? existing?.source ?? "session";
452
+ const counted = existing ? false : countChildExecution(state, {
453
+ id: input.id,
454
+ title: input.title,
455
+ parentID: input.parentID,
456
+ messageID: input.messageID,
457
+ source,
458
+ targetSessionID
459
+ });
460
+ const shouldKeepCompletedTiming = existing?.status === "done" || existing?.status === "error";
461
+ const next = {
462
+ id: input.id,
463
+ title: input.title,
464
+ summary: sanitizeSummary(input.summary, input.title) ?? sanitizeSummary(existing?.summary, input.title),
465
+ agentName: sanitizeAgentName(input.agentName) ?? existing?.agentName,
466
+ parentID: input.parentID,
467
+ messageID: input.messageID ?? existing?.messageID,
468
+ source,
469
+ toolName: input.toolName ?? existing?.toolName,
470
+ targetSessionID,
471
+ status: shouldKeepCompletedTiming ? existing.status : "running",
472
+ color: statusColor(shouldKeepCompletedTiming ? existing.status : "running"),
473
+ startedAt: existing?.startedAt ?? observedStartedAt,
474
+ updatedAt: observedUpdatedAt,
475
+ endedAt: shouldKeepCompletedTiming ? existing.endedAt : void 0,
476
+ elapsedMs: existing?.elapsedMs,
477
+ tokens: existing?.tokens,
478
+ model: existing?.model
479
+ };
480
+ if (existing && next.title === existing.title && next.summary === existing.summary && next.agentName === existing.agentName && next.parentID === existing.parentID && next.messageID === existing.messageID && next.source === existing.source && next.toolName === existing.toolName && next.targetSessionID === existing.targetSessionID && next.status === existing.status && next.color === existing.color && next.startedAt === existing.startedAt && next.endedAt === existing.endedAt && sameTokens(next.tokens, existing.tokens) && JSON.stringify(next.model) === JSON.stringify(existing.model)) {
481
+ return counted;
482
+ }
483
+ state.children[input.id] = next;
484
+ state.updatedAt = observedUpdatedAt;
485
+ return true;
486
+ }
487
+ function markChildStatus(state, childID, status, endedAt) {
488
+ const now = (/* @__PURE__ */ new Date()).toISOString();
489
+ let changed = false;
490
+ let stateUpdatedAt = state.updatedAt;
491
+ for (const child of Object.values(state.children)) {
492
+ if (child.id !== childID && child.targetSessionID !== childID) continue;
493
+ const observedEndedAt = endedAt ? safeTimestamp(endedAt, now) : child.endedAt ?? now;
494
+ if (child.status === status && child.color === statusColor(status) && child.updatedAt === observedEndedAt && child.endedAt === observedEndedAt) {
495
+ continue;
496
+ }
497
+ const nextChild = {
498
+ ...child,
499
+ status,
500
+ color: statusColor(status),
501
+ updatedAt: observedEndedAt,
502
+ endedAt: observedEndedAt
503
+ };
504
+ state.children[child.id] = {
505
+ ...nextChild,
506
+ elapsedMs: resolveElapsedMs(nextChild, Date.now())
507
+ };
508
+ stateUpdatedAt = observedEndedAt;
509
+ changed = true;
510
+ }
511
+ if (changed) {
512
+ state.updatedAt = stateUpdatedAt;
513
+ }
514
+ return changed;
515
+ }
516
+ function upsertChildDetails(state, childID, input) {
517
+ const existing = state.children[childID];
518
+ if (!existing) return false;
519
+ const nextTitle = typeof input.title === "string" && input.title.trim().length > 0 ? input.title : existing.title;
520
+ const nextSummary = sanitizeSummary(input.summary, nextTitle) ?? sanitizeSummary(existing.summary, nextTitle);
521
+ const nextAgentName = sanitizeAgentName(input.agentName) ?? existing.agentName;
522
+ const mergedTokens = mergeTokens(existing.tokens, input.tokens);
523
+ const nextTargetSessionID = sanitizeTargetSessionID(
524
+ input.targetSessionID ?? existing.targetSessionID,
525
+ existing.id.startsWith("ses_") ? existing.id : void 0
526
+ );
527
+ const detailsChanged = nextTitle !== existing.title || nextSummary !== existing.summary || nextAgentName !== existing.agentName || !sameTokens(mergedTokens, existing.tokens) || nextTargetSessionID !== existing.targetSessionID;
528
+ if (!detailsChanged) return false;
529
+ const now = (/* @__PURE__ */ new Date()).toISOString();
530
+ const observedUpdatedAt = safeTimestamp(input.updatedAt, now);
531
+ const next = {
532
+ ...existing,
533
+ title: nextTitle,
534
+ summary: nextSummary,
535
+ agentName: nextAgentName,
536
+ tokens: mergedTokens,
537
+ targetSessionID: nextTargetSessionID,
538
+ updatedAt: observedUpdatedAt
539
+ };
540
+ state.children[childID] = next;
541
+ state.updatedAt = observedUpdatedAt;
542
+ return true;
543
+ }
544
+ function setChildModel(state, sessionID, model, updatedAt) {
545
+ const matches = Object.values(state.children).filter(
546
+ (child) => child.id === sessionID || child.targetSessionID === sessionID
547
+ );
548
+ if (matches.length === 0) return false;
549
+ let changed = false;
550
+ const observedUpdatedAt = safeTimestamp(updatedAt, (/* @__PURE__ */ new Date()).toISOString());
551
+ for (const child of matches) {
552
+ const sanitized = sanitizeModel(model);
553
+ if (JSON.stringify(child.model) === JSON.stringify(sanitized)) continue;
554
+ state.children[child.id] = { ...child, model: sanitized };
555
+ changed = true;
556
+ }
557
+ if (changed) state.updatedAt = observedUpdatedAt;
558
+ return changed;
559
+ }
560
+
561
+ // src/text-width.ts
562
+ var ELLIPSIS = "\u2026";
563
+ function isCombiningCodePoint(codePoint) {
564
+ return codePoint >= 768 && codePoint <= 879 || codePoint >= 6832 && codePoint <= 6911 || codePoint >= 7616 && codePoint <= 7679 || codePoint >= 8400 && codePoint <= 8447 || codePoint >= 65024 && codePoint <= 65039 || codePoint >= 65056 && codePoint <= 65071;
565
+ }
566
+ function isWideCodePoint(codePoint) {
567
+ return codePoint >= 4352 && (codePoint <= 4447 || codePoint === 9001 || codePoint === 9002 || codePoint >= 11904 && codePoint <= 42191 && codePoint !== 12351 || codePoint >= 44032 && codePoint <= 55203 || codePoint >= 63744 && codePoint <= 64255 || codePoint >= 65040 && codePoint <= 65049 || codePoint >= 65072 && codePoint <= 65135 || codePoint >= 65280 && codePoint <= 65376 || codePoint >= 65504 && codePoint <= 65510 || codePoint >= 127744 && codePoint <= 128591 || codePoint >= 128640 && codePoint <= 128767 || codePoint >= 131072 && codePoint <= 262141);
568
+ }
569
+ function characterWidth(character) {
570
+ const codePoint = character.codePointAt(0);
571
+ if (codePoint === void 0) return 0;
572
+ if (codePoint === 0 || codePoint < 32 || codePoint >= 127 && codePoint < 160) {
573
+ return 0;
574
+ }
575
+ if (codePoint === 8205 || isCombiningCodePoint(codePoint)) return 0;
576
+ return isWideCodePoint(codePoint) ? 2 : 1;
577
+ }
578
+ function textColumns(value) {
579
+ let columns = 0;
580
+ for (const character of value) columns += characterWidth(character);
581
+ return columns;
582
+ }
583
+ function takeColumns(value, maxColumns) {
584
+ if (maxColumns <= 0) return "";
585
+ let columns = 0;
586
+ let result = "";
587
+ for (const character of value) {
588
+ const width = characterWidth(character);
589
+ if (columns + width > maxColumns) break;
590
+ columns += width;
591
+ result += character;
592
+ }
593
+ return result;
594
+ }
595
+ function truncateToColumns(value, maxColumns) {
596
+ if (maxColumns <= 0) return "";
597
+ if (textColumns(value) <= maxColumns) return value;
598
+ if (maxColumns <= textColumns(ELLIPSIS)) return ELLIPSIS;
599
+ const prefix = takeColumns(
600
+ value,
601
+ maxColumns - textColumns(ELLIPSIS)
602
+ ).trimEnd();
603
+ return `${prefix}${ELLIPSIS}`;
604
+ }
605
+
606
+ // src/i18n.ts
607
+ var translations = {
608
+ es: {
609
+ subagents: "Subagentes",
610
+ interrupted: "Interrumpido",
611
+ stale: "El estado puede estar desactualizado",
612
+ usage: "Uso de entrada + salida",
613
+ unavailable: "Lista de subagentes no disponible"
614
+ },
615
+ en: {
616
+ subagents: "Subagents",
617
+ interrupted: "Interrupted",
618
+ stale: "Status may be stale",
619
+ usage: "Input + output usage",
620
+ unavailable: "Subagent list unavailable"
621
+ }
622
+ };
623
+ var _cachedLocale = null;
624
+ function detectSystemLocale() {
625
+ const envLang = process.env.LANG ?? process.env.LC_ALL ?? process.env.LANGUAGE ?? Intl.DateTimeFormat().resolvedOptions().locale;
626
+ const lang = envLang.toLowerCase();
627
+ if (lang.startsWith("es")) return "es";
628
+ return "en";
629
+ }
630
+ function getLocale() {
631
+ if (_cachedLocale === null) {
632
+ _cachedLocale = detectSystemLocale();
633
+ }
634
+ return _cachedLocale;
635
+ }
636
+ function t(key) {
637
+ const locale = getLocale();
638
+ const translation = translations[locale][key];
639
+ if (translation === void 0) {
640
+ const fallback = translations.en[key];
641
+ if (fallback === void 0) {
642
+ throw new Error(`Missing translation key: ${key}`);
643
+ }
644
+ return fallback;
645
+ }
646
+ return translation;
647
+ }
648
+
649
+ // src/reconcile.ts
650
+ var DEFAULT_STALE_RUNNING_THRESHOLD_MS = 10 * 60 * 6e4;
651
+ function nextBackoffState(input) {
652
+ const nextBackoffMs = input.cache ? Math.min(
653
+ input.maxBackoffMs,
654
+ Math.max(input.initialBackoffMs, input.cache.backoffMs * 2)
655
+ ) : input.initialBackoffMs;
656
+ return {
657
+ backoffMs: nextBackoffMs,
658
+ nextAllowedAtMs: input.nowMs + nextBackoffMs
659
+ };
660
+ }
661
+
662
+ // src/tui-v2-state.ts
663
+ function tokens(usage) {
664
+ const finite = (value) => Number.isFinite(value) && value >= 0 ? value : void 0;
665
+ const input = finite(usage.input);
666
+ const output = finite(usage.output);
667
+ if (input === void 0 && output === void 0) return void 0;
668
+ const total = (input ?? 0) + (output ?? 0);
669
+ return { input, output, total: Number.isFinite(total) ? total : void 0 };
670
+ }
671
+ var iso = (at) => new Date(at).toISOString();
672
+ function createV2Monitor(input) {
673
+ const current = createEmptyState();
674
+ const tracked = /* @__PURE__ */ new Map();
675
+ let disposed = false;
676
+ let epoch = 0;
677
+ let revision = 0;
678
+ let parentID;
679
+ let failed = false;
680
+ let failureRevision = 0;
681
+ let retries = 0;
682
+ let backoff;
683
+ let retryTimer;
684
+ let hintTimer;
685
+ let flight;
686
+ let reconnectFlight;
687
+ let detailsInFlight = 0;
688
+ let inventoriesInFlight = 0;
689
+ const detailQueue = [];
690
+ function record(id) {
691
+ let item = tracked.get(id);
692
+ if (!item) {
693
+ item = { revision: 0, executionRevision: 0, generation: 0 };
694
+ tracked.set(id, item);
695
+ }
696
+ return item;
697
+ }
698
+ const stale = () => failed || [...tracked.values()].some((item) => !item.deleted && !item.retired && (item.execution?.shutdown || item.needsRefresh));
699
+ function forgetRetired() {
700
+ if (inventoriesInFlight || detailsInFlight || detailQueue.length) return;
701
+ for (const [id, item] of tracked) if (item.retired) tracked.delete(id);
702
+ }
703
+ function publish() {
704
+ if (disposed) return;
705
+ const priorIDs = Object.keys(current.children);
706
+ refreshDerivedFields(current);
707
+ for (const id of priorIDs) {
708
+ const item = tracked.get(id);
709
+ if (item && !current.children[id]) item.retired = true;
710
+ }
711
+ forgetRetired();
712
+ input.onChange(structuredClone(current));
713
+ }
714
+ function clearTimers() {
715
+ clearTimeout(retryTimer);
716
+ retryTimer = void 0;
717
+ clearTimeout(hintTimer);
718
+ hintTimer = void 0;
719
+ }
720
+ function scheduleRetry() {
721
+ if (disposed || retryTimer || retries >= V2_RETRY_MAX_ATTEMPTS) return;
722
+ backoff = nextBackoffState({ cache: backoff, nowMs: Date.now(), initialBackoffMs: V2_RETRY_INITIAL_DELAY_MS, maxBackoffMs: V2_RETRY_MAX_DELAY_MS });
723
+ retryTimer = setTimeout(() => {
724
+ retryTimer = void 0;
725
+ retries++;
726
+ void refresh(parentID);
727
+ }, backoff.backoffMs);
728
+ }
729
+ function issue() {
730
+ if (disposed) return;
731
+ failureRevision++;
732
+ failed = true;
733
+ input.onIssue("refresh-failed");
734
+ publish();
735
+ scheduleRetry();
736
+ }
737
+ async function detail(id, valid) {
738
+ if (!valid()) return void 0;
739
+ if (detailsInFlight >= V2_DETAIL_CONCURRENCY) {
740
+ if (!await new Promise((resolve) => detailQueue.push(resolve))) return void 0;
741
+ } else {
742
+ detailsInFlight++;
743
+ }
744
+ try {
745
+ if (!valid()) return void 0;
746
+ const info = await input.session.get({ sessionID: id });
747
+ return valid() ? info : void 0;
748
+ } finally {
749
+ const next = detailQueue.shift();
750
+ if (next) next(true);
751
+ else detailsInFlight--;
752
+ forgetRetired();
753
+ }
754
+ }
755
+ function applyExecution(id, item) {
756
+ const execution = item.execution;
757
+ const row = current.children[id];
758
+ if (!row || !execution) return;
759
+ if (execution.status === "running") {
760
+ current.children[id] = {
761
+ ...row,
762
+ status: "running",
763
+ color: "yellow",
764
+ endedAt: void 0,
765
+ elapsedMs: void 0,
766
+ updatedAt: iso(execution.at)
767
+ };
768
+ } else {
769
+ markChildStatus(current, id, execution.status, iso(execution.at));
770
+ }
771
+ }
772
+ function applyMetadata(info, item, readRevision) {
773
+ const row = current.children[info.id];
774
+ if (!row) return;
775
+ const assistant = input.cache.message.list(info.id).filter((message) => message.type === "assistant").sort((a, b) => b.time.created - a.time.created)[0];
776
+ const reconcile = (observed, value) => readRevision !== void 0 && (!observed || observed.revision <= readRevision) ? { revision: readRevision, value } : observed;
777
+ item.title = reconcile(item.title, info.title);
778
+ item.agent = reconcile(item.agent, info.agent);
779
+ item.model = reconcile(item.model, info.model);
780
+ item.usage = reconcile(item.usage, tokens(info.tokens));
781
+ if (readRevision !== void 0 && Math.max(item.revision, item.metadataHintRevision ?? 0) <= readRevision) {
782
+ item.metadataPending = false;
783
+ }
784
+ const model = item.model ? item.model.value : info.model ?? assistant?.model;
785
+ upsertChildDetails(current, info.id, {
786
+ title: item.title ? item.title.value : info.title,
787
+ agentName: item.agent ? item.agent.value : info.agent ?? assistant?.agent,
788
+ updatedAt: row.updatedAt
789
+ });
790
+ setChildModel(current, info.id, model ? {
791
+ providerID: model.providerID,
792
+ modelID: model.id,
793
+ variant: model.variant
794
+ } : void 0);
795
+ current.children[info.id].tokens = item.usage ? item.usage.value : tokens(info.tokens);
796
+ }
797
+ function hydrate(info, active, readRevision, metadataReadRevision) {
798
+ if (!info.parentID) return;
799
+ if (!tracked.has(info.id) && !active && !(info.outcome && info.time.idle !== void 0)) return;
800
+ const item = record(info.id);
801
+ if (item.deleted || (active ? item.executionRevision : item.revision) > readRevision) return;
802
+ const idle = info.time.idle;
803
+ if (active) {
804
+ if (item.execution?.status !== "running") item.execution = { status: "running", at: Date.now() };
805
+ item.needsRefresh = false;
806
+ } else if (info.outcome && idle !== void 0 && Number.isFinite(idle) && idle >= info.time.created && (!item.execution || (item.execution.shutdown ? idle > item.execution.at : idle >= item.execution.at))) {
807
+ item.execution = {
808
+ status: info.outcome === "succeeded" ? "done" : "error",
809
+ at: idle,
810
+ interrupted: info.outcome === "interrupted",
811
+ hydrated: true
812
+ };
813
+ item.needsRefresh = false;
814
+ }
815
+ if (active && item.revision > readRevision && current.children[info.id]) {
816
+ applyExecution(info.id, item);
817
+ } else {
818
+ insert(info, item, metadataReadRevision);
819
+ }
820
+ }
821
+ function insert(info, item, metadataReadRevision) {
822
+ if (!info.parentID || item.deleted || !item.execution) return;
823
+ item.retired = false;
824
+ upsertRunningChild(current, {
825
+ id: info.id,
826
+ parentID: info.parentID,
827
+ title: info.title ?? info.id,
828
+ source: "session",
829
+ targetSessionID: info.id,
830
+ startedAt: iso(info.time.created),
831
+ updatedAt: iso(item.execution.at)
832
+ });
833
+ applyMetadata(info, item, metadataReadRevision);
834
+ applyExecution(info.id, item);
835
+ }
836
+ async function readInventory(readEpoch) {
837
+ const valid = () => !disposed && epoch === readEpoch;
838
+ const readRevision = revision;
839
+ const readFailureRevision = failureRevision;
840
+ const selectedParent = parentID;
841
+ let readFailed = false;
842
+ const activeIDs = /* @__PURE__ */ new Set();
843
+ const activeRead = (async () => {
844
+ try {
845
+ const activity = await input.session.active();
846
+ if (!valid()) return;
847
+ Object.keys(activity).forEach((id) => activeIDs.add(id));
848
+ const ids = [.../* @__PURE__ */ new Set([...activeIDs, ...[...tracked].filter(([id, item]) => !item.deleted && !item.retired && item.execution && (!current.children[id] || !selectedParent && item.metadataPending)).map(([id]) => id)])];
849
+ let offset = 0;
850
+ await Promise.all(Array.from({ length: Math.min(V2_DETAIL_CONCURRENCY, ids.length) }, async () => {
851
+ while (offset < ids.length && valid()) {
852
+ const id = ids[offset++];
853
+ const unchanged = () => {
854
+ const item = tracked.get(id);
855
+ const changedAt = activeIDs.has(id) ? item?.executionRevision : item?.revision;
856
+ return valid() && (changedAt ?? 0) <= readRevision && !item?.deleted;
857
+ };
858
+ if (!unchanged()) continue;
859
+ try {
860
+ const cached = tracked.get(id)?.metadataPending ? void 0 : input.cache.get(id);
861
+ const info = cached ?? await detail(id, unchanged);
862
+ if (info && unchanged()) {
863
+ hydrate(info, activeIDs.has(id), readRevision, cached ? void 0 : readRevision);
864
+ publish();
865
+ }
866
+ } catch {
867
+ if (unchanged()) readFailed = true;
868
+ }
869
+ }
870
+ }));
871
+ } catch {
872
+ if (valid()) readFailed = true;
873
+ }
874
+ })();
875
+ const historyRead = (async () => {
876
+ try {
877
+ if (!selectedParent) {
878
+ for (const info of input.cache.list()) hydrate(info, activeIDs.has(info.id), readRevision);
879
+ return;
880
+ }
881
+ let cursor;
882
+ const cursors = /* @__PURE__ */ new Set();
883
+ do {
884
+ const page = await input.session.list({ parentID: selectedParent, limit: V2_HISTORY_PAGE_SIZE, cursor });
885
+ if (!valid()) return;
886
+ for (const info of page.data) {
887
+ if (info.parentID === selectedParent) hydrate(info, activeIDs.has(info.id), readRevision, readRevision);
888
+ }
889
+ publish();
890
+ cursor = page.cursor.next ?? void 0;
891
+ if (cursor && cursors.has(cursor)) {
892
+ readFailed = true;
893
+ break;
894
+ }
895
+ if (cursor) cursors.add(cursor);
896
+ } while (cursor && valid());
897
+ } catch {
898
+ if (valid()) readFailed = true;
899
+ }
900
+ })();
901
+ await Promise.all([activeRead, historyRead]);
902
+ if (!valid()) return;
903
+ if (readFailed) issue();
904
+ else if (failureRevision === readFailureRevision) {
905
+ failed = false;
906
+ retries = 0;
907
+ backoff = void 0;
908
+ clearTimeout(retryTimer);
909
+ retryTimer = void 0;
910
+ publish();
911
+ }
912
+ if ([...tracked.values()].some((item) => !item.deleted && !item.retired && item.metadataPending && Math.max(item.revision, item.metadataHintRevision ?? 0) > readRevision)) requestHint();
913
+ }
914
+ function refresh(selectedParent) {
915
+ if (disposed) return Promise.resolve();
916
+ if (selectedParent !== parentID) {
917
+ parentID = selectedParent;
918
+ epoch++;
919
+ clearTimers();
920
+ retries = 0;
921
+ backoff = void 0;
922
+ }
923
+ if (flight?.epoch === epoch) return flight.promise;
924
+ const readEpoch = epoch;
925
+ inventoriesInFlight++;
926
+ const promise = readInventory(readEpoch).finally(() => {
927
+ inventoriesInFlight--;
928
+ forgetRetired();
929
+ if (flight?.promise === promise) flight = void 0;
930
+ });
931
+ flight = { epoch: readEpoch, promise };
932
+ return promise;
933
+ }
934
+ function requestHint() {
935
+ if (disposed || hintTimer || retryTimer) return;
936
+ hintTimer = setTimeout(() => {
937
+ hintTimer = void 0;
938
+ void refresh(parentID);
939
+ }, 1e3);
940
+ }
941
+ async function accept(event) {
942
+ if (disposed) return;
943
+ switch (event.type) {
944
+ case "session.status":
945
+ case "session.idle":
946
+ case "session.step.ended":
947
+ case "session.step.failed":
948
+ case "session.tool.success":
949
+ case "session.tool.failed":
950
+ case "session.retry.scheduled": {
951
+ const item2 = tracked.get(event.data.sessionID);
952
+ if (item2 && !item2.deleted && !item2.retired) {
953
+ item2.metadataPending = true;
954
+ item2.metadataHintRevision = ++revision;
955
+ }
956
+ requestHint();
957
+ return;
958
+ }
959
+ case "session.created":
960
+ case "session.execution.started":
961
+ case "session.execution.succeeded":
962
+ case "session.execution.failed":
963
+ case "session.execution.interrupted":
964
+ case "session.deleted":
965
+ case "session.renamed":
966
+ case "session.agent.selected":
967
+ case "session.model.selected":
968
+ case "session.usage.updated":
969
+ break;
970
+ default:
971
+ return;
972
+ }
973
+ const id = event.data.sessionID;
974
+ const item = record(id);
975
+ if (item.deleted) return;
976
+ if ("durable" in event) {
977
+ if (item.seq !== void 0 && event.durable.seq <= item.seq) return;
978
+ item.seq = event.durable.seq;
979
+ } else if (item.usageCreated !== void 0 && event.created <= item.usageCreated) return;
980
+ if (event.type.startsWith("session.execution.") && item.execution?.hydrated && event.created < item.execution.at) return;
981
+ item.retired = false;
982
+ const metadataReadRevision = revision;
983
+ item.revision = ++revision;
984
+ if (event.type.startsWith("session.execution.") || event.type === "session.deleted") {
985
+ item.executionRevision = item.revision;
986
+ }
987
+ const generation = ++item.generation;
988
+ const readEpoch = epoch;
989
+ const valid = () => !disposed && epoch === readEpoch && !item.deleted && item.generation === generation;
990
+ switch (event.type) {
991
+ case "session.deleted":
992
+ item.deleted = true;
993
+ item.execution = void 0;
994
+ delete item.title;
995
+ delete item.agent;
996
+ delete item.model;
997
+ delete item.usage;
998
+ delete item.usageCreated;
999
+ delete item.needsRefresh;
1000
+ delete item.metadataPending;
1001
+ delete item.metadataHintRevision;
1002
+ delete current.children[id];
1003
+ input.cache.invalidate(id);
1004
+ publish();
1005
+ return;
1006
+ case "session.execution.started":
1007
+ item.execution = { status: "running", at: event.created };
1008
+ item.needsRefresh = false;
1009
+ break;
1010
+ case "session.execution.succeeded":
1011
+ case "session.execution.failed":
1012
+ item.execution = { status: event.type === "session.execution.succeeded" ? "done" : "error", at: event.created };
1013
+ item.needsRefresh = false;
1014
+ break;
1015
+ case "session.execution.interrupted":
1016
+ item.execution = { status: "error", at: event.created, interrupted: true, shutdown: event.data.reason === "shutdown" };
1017
+ item.needsRefresh = false;
1018
+ break;
1019
+ case "session.renamed":
1020
+ item.title = { revision: item.revision, value: event.data.title };
1021
+ item.metadataPending = true;
1022
+ break;
1023
+ case "session.agent.selected":
1024
+ item.agent = { revision: item.revision, value: event.data.agent };
1025
+ item.metadataPending = true;
1026
+ break;
1027
+ case "session.model.selected":
1028
+ item.model = { revision: item.revision, value: event.data.model };
1029
+ item.metadataPending = true;
1030
+ break;
1031
+ case "session.usage.updated":
1032
+ item.usageCreated = event.created;
1033
+ item.usage = { revision: item.revision, value: tokens(event.data.tokens) };
1034
+ item.metadataPending = true;
1035
+ requestHint();
1036
+ break;
1037
+ }
1038
+ try {
1039
+ if (current.children[id]) {
1040
+ const cached2 = input.cache.get(id);
1041
+ if (cached2) applyMetadata(cached2, item);
1042
+ else {
1043
+ if (event.type === "session.renamed") upsertChildDetails(current, id, { title: event.data.title });
1044
+ if (event.type === "session.agent.selected") upsertChildDetails(current, id, { agentName: event.data.agent });
1045
+ if (event.type === "session.model.selected") setChildModel(current, id, {
1046
+ providerID: event.data.model.providerID,
1047
+ modelID: event.data.model.id,
1048
+ variant: event.data.model.variant
1049
+ });
1050
+ if (event.type === "session.usage.updated") current.children[id].tokens = item.usage?.value;
1051
+ }
1052
+ applyExecution(id, item);
1053
+ publish();
1054
+ return;
1055
+ }
1056
+ const cached = input.cache.get(id);
1057
+ const info = cached ?? await detail(id, valid);
1058
+ if (!valid() || !info) return;
1059
+ if (!info.parentID) {
1060
+ tracked.delete(id);
1061
+ return;
1062
+ }
1063
+ if (!item.execution) hydrate(info, false, item.revision, cached ? void 0 : metadataReadRevision);
1064
+ else insert(info, item, cached ? void 0 : metadataReadRevision);
1065
+ if (!item.execution && !current.children[id]) item.retired = true;
1066
+ publish();
1067
+ } catch {
1068
+ if (valid()) issue();
1069
+ }
1070
+ }
1071
+ function reconnect() {
1072
+ if (disposed) return Promise.resolve();
1073
+ if (reconnectFlight) return reconnectFlight;
1074
+ epoch++;
1075
+ clearTimers();
1076
+ retries = 0;
1077
+ backoff = void 0;
1078
+ failed = true;
1079
+ for (const [id, item] of tracked) {
1080
+ input.cache.invalidate(id);
1081
+ if (!item.deleted && !item.retired && item.execution) item.metadataPending = true;
1082
+ if (item.execution?.status === "running") item.needsRefresh = true;
1083
+ }
1084
+ publish();
1085
+ const promise = refresh(parentID).finally(() => {
1086
+ if (reconnectFlight === promise) reconnectFlight = void 0;
1087
+ });
1088
+ reconnectFlight = promise;
1089
+ return promise;
1090
+ }
1091
+ return {
1092
+ state: () => structuredClone(current),
1093
+ hint: (id) => current.children[id] && tracked.get(id)?.execution?.interrupted ? "interrupted" : void 0,
1094
+ stale,
1095
+ accept,
1096
+ refresh,
1097
+ reconnect,
1098
+ dispose() {
1099
+ if (disposed) return;
1100
+ disposed = true;
1101
+ epoch++;
1102
+ clearTimers();
1103
+ detailQueue.splice(0).forEach((resolve) => resolve(false));
1104
+ tracked.clear();
1105
+ }
1106
+ };
1107
+ }
1108
+
1109
+ // src/tui-v2-snapshot.ts
1110
+ import { dirname as dirname2, join as join2 } from "path";
1111
+
1112
+ // src/render.ts
1113
+ var ansi = {
1114
+ reset: "\x1B[0m",
1115
+ gray: "\x1B[90m",
1116
+ green: "\x1B[32m",
1117
+ yellow: "\x1B[33m",
1118
+ red: "\x1B[31m"
1119
+ };
1120
+ function colorsEnabled() {
1121
+ if (process.env.NO_COLOR) return false;
1122
+ const fromEnv = process.env.OPENCODE_SUBAGENT_STATUSLINE_COLOR;
1123
+ if (fromEnv === "0") return false;
1124
+ return true;
1125
+ }
1126
+ function paint(text, color, enabled) {
1127
+ if (!enabled) return text;
1128
+ return `${color}${text}${ansi.reset}`;
1129
+ }
1130
+ function formatDuration(elapsedMs2) {
1131
+ const totalSeconds = Math.max(0, Math.floor((elapsedMs2 ?? 0) / 1e3));
1132
+ const hours = Math.floor(totalSeconds / 3600);
1133
+ const minutes = Math.floor(totalSeconds % 3600 / 60);
1134
+ const seconds = totalSeconds % 60;
1135
+ if (hours > 0) {
1136
+ return `${String(hours).padStart(2, "0")}:${String(minutes).padStart(2, "0")}:${String(seconds).padStart(2, "0")}`;
1137
+ }
1138
+ return `${String(minutes).padStart(2, "0")}:${String(seconds).padStart(2, "0")}`;
1139
+ }
1140
+ function formatNumber(value) {
1141
+ return Math.max(0, Math.round(value)).toLocaleString("en-US");
1142
+ }
1143
+ function resolveTokenTotal(child) {
1144
+ const total = child.tokens?.total;
1145
+ if (typeof total === "number" && Number.isFinite(total)) {
1146
+ return total;
1147
+ }
1148
+ const inTokens = child.tokens?.input;
1149
+ const outTokens = child.tokens?.output;
1150
+ if (typeof inTokens === "number" || typeof outTokens === "number") {
1151
+ return (inTokens ?? 0) + (outTokens ?? 0);
1152
+ }
1153
+ return void 0;
1154
+ }
1155
+ function formatPercentUsed(percent) {
1156
+ const rounded = Math.round(percent * 10) / 10;
1157
+ if (Math.abs(rounded - Math.round(rounded)) < 0.05) {
1158
+ return `${Math.round(rounded)}% used`;
1159
+ }
1160
+ return `${rounded.toFixed(1)}% used`;
1161
+ }
1162
+ function formatTokenCount(total) {
1163
+ const label = total === 1 ? "token" : "tokens";
1164
+ return `${formatNumber(total)} ${label}`;
1165
+ }
1166
+ function formatContextDetails(child) {
1167
+ const total = resolveTokenTotal(child);
1168
+ const percent = child.tokens?.contextPercent;
1169
+ const hasPercent = typeof percent === "number" && Number.isFinite(percent);
1170
+ const hasTotal = typeof total === "number" && Number.isFinite(total);
1171
+ if (hasTotal && hasPercent) {
1172
+ return `${formatTokenCount(total)} \xB7 ${formatPercentUsed(percent)}`;
1173
+ }
1174
+ if (hasTotal) {
1175
+ return formatTokenCount(total);
1176
+ }
1177
+ if (hasPercent) {
1178
+ return formatPercentUsed(percent);
1179
+ }
1180
+ return void 0;
1181
+ }
1182
+ function formatContext(child) {
1183
+ const details = formatContextDetails(child);
1184
+ if (!details) return "";
1185
+ return `ctx ${details}`;
1186
+ }
1187
+ function childColor(child) {
1188
+ if (child.color === "green") return ansi.green;
1189
+ if (child.color === "red") return ansi.red;
1190
+ return ansi.yellow;
1191
+ }
1192
+ function byPriority(a, b) {
1193
+ const startedDiff = b.startedAt.localeCompare(a.startedAt);
1194
+ if (startedDiff !== 0) return startedDiff;
1195
+ return a.id.localeCompare(b.id);
1196
+ }
1197
+ var RECENT_TERMINAL_VISIBLE_MS = 10 * 60 * 1e3;
1198
+ function collapseSubagentWorkItems(children) {
1199
+ return correlateSubagentWorkItems(children).map(
1200
+ ({ real, proxies }) => proxies.reduce(
1201
+ (current, proxy) => mergeProxyMetadataWithRealExecution(current, proxy),
1202
+ real
1203
+ )
1204
+ );
1205
+ }
1206
+ function isTerminalWorkItem(child) {
1207
+ return child.status === "done" || child.status === "error";
1208
+ }
1209
+ function isVisibleWorkItem(child, nowMs = Date.now()) {
1210
+ if (!isTerminalWorkItem(child)) return true;
1211
+ const endedMs = Date.parse(child.endedAt ?? child.updatedAt);
1212
+ if (Number.isNaN(endedMs)) return false;
1213
+ return nowMs - endedMs <= RECENT_TERMINAL_VISIBLE_MS;
1214
+ }
1215
+ function visibleSubagentWorkItems(children, nowMs = Date.now(), options = {}) {
1216
+ const collapsed = collapseSubagentWorkItems(children);
1217
+ if (options.showCompletedHistory) return collapsed;
1218
+ const visible = collapsed.filter((child) => isVisibleWorkItem(child, nowMs));
1219
+ const hasRunning = visible.some((child) => child.status === "running");
1220
+ const activeMessageIDs = new Set(
1221
+ visible.filter((child) => child.status === "running" && child.messageID).map((child) => child.messageID)
1222
+ );
1223
+ if (!hasRunning) return visible;
1224
+ return visible.filter((child) => {
1225
+ if (child.status === "running") return true;
1226
+ if (!child.messageID) return false;
1227
+ return activeMessageIDs.has(child.messageID);
1228
+ });
1229
+ }
1230
+ function renderStatusLine(state) {
1231
+ const children = visibleSubagentWorkItems(Object.values(state.children)).sort(
1232
+ byPriority
1233
+ );
1234
+ const counts = countRetainedSubagentStatuses({ children: state.children });
1235
+ const totalExecuted = formatNumber(state.totalExecuted ?? 0);
1236
+ const colorOn = colorsEnabled();
1237
+ const aggregate = `\u21B3 ${counts.running} running \xB7 ${counts.done} done \xB7 ${counts.error} error \xB7 \u03A3 ${totalExecuted} total`;
1238
+ if (children.length === 0) return aggregate;
1239
+ const details = children.map((child) => {
1240
+ const context = formatContext(child);
1241
+ const label = [child.title, formatDuration(child.elapsedMs), context].filter((part) => part.length > 0).join(" ");
1242
+ return paint(label, childColor(child), colorOn);
1243
+ }).join(paint(" \xB7 ", ansi.gray, colorOn));
1244
+ return `${aggregate} \xB7 ${details}`;
1245
+ }
1246
+
1247
+ // src/tui-v2-snapshot.ts
1248
+ function resolveV2SnapshotPaths() {
1249
+ const override = process.env.OPENCODE_SUBAGENT_STATUSLINE_STATE;
1250
+ const statePath = override?.trim() ? resolveStatePath() : join2(dirname2(resolveStatePath()), "v2", "state.json");
1251
+ return { statePath, textPath: resolveTextPath(statePath) };
1252
+ }
1253
+ function createV2SnapshotWriter(input) {
1254
+ let disposed = false;
1255
+ let pending;
1256
+ let inFlight;
1257
+ const options = { shouldCommit: () => !disposed };
1258
+ function drain() {
1259
+ if (inFlight || disposed || !pending) return;
1260
+ inFlight = Promise.resolve().then(async () => {
1261
+ while (!disposed && pending) {
1262
+ const state = pending;
1263
+ pending = void 0;
1264
+ try {
1265
+ await saveState(input.statePath, state, options);
1266
+ if (!disposed) await saveStatusText(input.textPath, renderStatusLine(state), options);
1267
+ } catch {
1268
+ if (!disposed) input.onIssue("snapshot-failed");
1269
+ }
1270
+ }
1271
+ }).finally(() => {
1272
+ inFlight = void 0;
1273
+ drain();
1274
+ });
1275
+ }
1276
+ async function flush() {
1277
+ while (inFlight) await inFlight;
1278
+ }
1279
+ return {
1280
+ enqueue(state) {
1281
+ if (disposed) return;
1282
+ pending = structuredClone(state);
1283
+ drain();
1284
+ },
1285
+ flush,
1286
+ async dispose() {
1287
+ disposed = true;
1288
+ pending = void 0;
1289
+ await flush();
1290
+ }
1291
+ };
1292
+ }
1293
+
1294
+ // src/tui-v2-focus.ts
1295
+ function createV2SidebarFocus(input) {
1296
+ let disposed = false;
1297
+ let generation = 0;
1298
+ let timer;
1299
+ let saved;
1300
+ const live = (target) => Boolean(target && !target.isDestroyed && target.visible);
1301
+ function cancel() {
1302
+ generation++;
1303
+ clearTimeout(timer);
1304
+ timer = void 0;
1305
+ }
1306
+ function release(restore) {
1307
+ cancel();
1308
+ const previous = saved;
1309
+ saved = void 0;
1310
+ input.view.blurList();
1311
+ if (restore && previous && previous.route === input.route() && input.mode() === "base" && live(previous.target)) previous.target.focus();
1312
+ }
1313
+ function transfer(excluded) {
1314
+ const target = input.view.target();
1315
+ if (disposed || input.mode() !== "base" || !live(target)) return false;
1316
+ if (input.view.isListFocused()) return true;
1317
+ const candidate = input.renderer.currentFocusedEditor ?? input.renderer.currentFocusedRenderable;
1318
+ if (!live(candidate) || candidate === target || candidate === excluded) return false;
1319
+ const previous = { target: candidate, route: input.route() };
1320
+ if (!input.view.focusList()) return false;
1321
+ saved = previous;
1322
+ return true;
1323
+ }
1324
+ return {
1325
+ request(source) {
1326
+ if (disposed) return;
1327
+ cancel();
1328
+ if (source === "keyboard") {
1329
+ if (input.mode() === "base") transfer();
1330
+ return;
1331
+ }
1332
+ const target = input.view.target();
1333
+ if (!live(target)) {
1334
+ input.unavailable();
1335
+ return;
1336
+ }
1337
+ const route = input.route(), token = generation;
1338
+ const modalTarget = input.mode() !== "base" ? input.renderer.currentFocusedEditor ?? input.renderer.currentFocusedRenderable : void 0;
1339
+ let attempts = 0;
1340
+ const attempt = () => {
1341
+ timer = void 0;
1342
+ if (disposed || token !== generation || route !== input.route()) return;
1343
+ if (input.view.target() !== target || !live(target)) return;
1344
+ if (transfer(modalTarget)) return;
1345
+ if (++attempts === V2_FOCUS_MAX_ATTEMPTS) {
1346
+ input.unavailable();
1347
+ return;
1348
+ }
1349
+ timer = setTimeout(attempt, V2_FOCUS_RETRY_DELAY_MS);
1350
+ };
1351
+ timer = setTimeout(attempt, V2_FOCUS_RETRY_DELAY_MS);
1352
+ },
1353
+ leave: () => release(true),
1354
+ beforeNavigate: () => release(false),
1355
+ routeChanged: () => release(false),
1356
+ dispose() {
1357
+ if (disposed) return;
1358
+ disposed = true;
1359
+ release(true);
1360
+ }
1361
+ };
1362
+ }
1363
+
1364
+ // src/tui-view.tsx
1365
+ import { use as _$use } from "@opentui/solid";
1366
+ import { createTextNode as _$createTextNode } from "@opentui/solid";
1367
+ import { effect as _$effect } from "@opentui/solid";
1368
+ import { createComponent as _$createComponent } from "@opentui/solid";
1369
+ import { insertNode as _$insertNode } from "@opentui/solid";
1370
+ import { insert as _$insert } from "@opentui/solid";
1371
+ import { memo as _$memo } from "@opentui/solid";
1372
+ import { setProp as _$setProp } from "@opentui/solid";
1373
+ import { createElement as _$createElement } from "@opentui/solid";
1374
+ import { createRequire } from "module";
1375
+ import { For, Show, createEffect, createMemo, createSignal, onCleanup } from "solid-js";
1376
+
1377
+ // src/tui-focus.ts
1378
+ function shouldReleaseSidebarListFocus(input) {
1379
+ return input.listFocusModeActive && (input.previousRunningCount ?? 0) > 0 && input.runningCount === 0;
1380
+ }
1381
+
1382
+ // src/tui-view.tsx
1383
+ var FALLBACK_SIDEBAR_WIDTH = 34;
1384
+ var MIN_ROW_WIDTH = 24;
1385
+ var MIN_LABEL_WIDTH = 8;
1386
+ var CLOCK_ICON = "\uF017";
1387
+ var TOKEN_ICON = "\uF51E";
1388
+ var SIDEBAR_ARROW_EXPANDED = "\u25BC";
1389
+ var SIDEBAR_ARROW_COLLAPSED = "\u25B6";
1390
+ var SUBAGENTS_MAX_VISIBLE_ROWS = 5;
1391
+ var SUBAGENTS_RUNNING_ROW_HEIGHT = 3;
1392
+ var SUBAGENTS_TERMINAL_ROW_HEIGHT = 2;
1393
+ var SUBAGENTS_MODEL_ROW_HEIGHT = 1;
1394
+ var SUBAGENTS_ROW_GAP = 0;
1395
+ var SUBAGENTS_ROW_MARKER_WIDTH = 4;
1396
+ var SUBAGENTS_MAX_LIST_HEIGHT = SUBAGENTS_MAX_VISIBLE_ROWS * (SUBAGENTS_RUNNING_ROW_HEIGHT + SUBAGENTS_MODEL_ROW_HEIGHT) + (SUBAGENTS_MAX_VISIBLE_ROWS - 1) * SUBAGENTS_ROW_GAP;
1397
+ var INACTIVE_SUBAGENT_OPACITY = 0.65;
1398
+ var packageRequire = createRequire(import.meta.url);
1399
+ function readPluginVersion() {
1400
+ try {
1401
+ const metadata = packageRequire("../package.json");
1402
+ return typeof metadata.version === "string" && metadata.version.length > 0 ? metadata.version : void 0;
1403
+ } catch {
1404
+ return void 0;
1405
+ }
1406
+ }
1407
+ var PLUGIN_VERSION = readPluginVersion();
1408
+ var SIDEBAR_SCROLL_RESTORE_FRAME_BUDGET = 2;
1409
+ var registerSidebar = /* @__PURE__ */ Symbol("registerSidebar");
1410
+ function createSidebarViewController() {
1411
+ const sidebarScrollRegistrations = /* @__PURE__ */ new Set();
1412
+ const sidebarListFocusRegistrations = /* @__PURE__ */ new Set();
1413
+ const sidebarCompletedHistoryRegistrations = /* @__PURE__ */ new Set();
1414
+ let disposed = false;
1415
+ const controller = {
1416
+ [registerSidebar]({
1417
+ scroll,
1418
+ focus,
1419
+ history
1420
+ }) {
1421
+ if (disposed) return () => {
1422
+ };
1423
+ sidebarScrollRegistrations.add(scroll);
1424
+ sidebarListFocusRegistrations.add(focus);
1425
+ sidebarCompletedHistoryRegistrations.add(history);
1426
+ return () => {
1427
+ sidebarScrollRegistrations.delete(scroll);
1428
+ sidebarListFocusRegistrations.delete(focus);
1429
+ sidebarCompletedHistoryRegistrations.delete(history);
1430
+ };
1431
+ },
1432
+ snapshotScroll() {
1433
+ for (const registration of sidebarScrollRegistrations) {
1434
+ const scrollbox = registration.getScrollbox();
1435
+ if (!scrollbox) continue;
1436
+ registration.offsetTop = clampedScrollTop(scrollbox, scrollbox.scrollTop);
1437
+ registration.anchor = registration.getAnchor();
1438
+ registration.restoreFramesRemaining = SIDEBAR_SCROLL_RESTORE_FRAME_BUDGET;
1439
+ }
1440
+ },
1441
+ focusList(preferredChildID) {
1442
+ for (const registration of [...sidebarListFocusRegistrations].reverse()) {
1443
+ if (registration.focusList(preferredChildID)) return true;
1444
+ }
1445
+ return false;
1446
+ },
1447
+ blurList() {
1448
+ for (const registration of [...sidebarListFocusRegistrations].reverse()) {
1449
+ if (registration.blurList()) return true;
1450
+ }
1451
+ return false;
1452
+ },
1453
+ isListFocused() {
1454
+ return [...sidebarListFocusRegistrations].some((registration) => registration.isListFocusModeActive());
1455
+ },
1456
+ toggleCompletedHistory() {
1457
+ for (const registration of [...sidebarCompletedHistoryRegistrations].reverse()) {
1458
+ if (registration.toggleCompletedHistory()) return true;
1459
+ }
1460
+ return false;
1461
+ },
1462
+ target() {
1463
+ for (const registration of [...sidebarListFocusRegistrations].reverse()) {
1464
+ const target = registration.target();
1465
+ if (target) return target;
1466
+ }
1467
+ return void 0;
1468
+ },
1469
+ dispose() {
1470
+ if (disposed) return;
1471
+ disposed = true;
1472
+ for (const registration of sidebarListFocusRegistrations) registration.blurList();
1473
+ sidebarScrollRegistrations.clear();
1474
+ sidebarListFocusRegistrations.clear();
1475
+ sidebarCompletedHistoryRegistrations.clear();
1476
+ }
1477
+ };
1478
+ return controller;
1479
+ }
1480
+ function maxScrollTop(scrollbox) {
1481
+ return Math.max(0, scrollbox.scrollHeight - scrollbox.viewport.height);
1482
+ }
1483
+ function clampedScrollTop(scrollbox, value) {
1484
+ return Math.max(0, Math.min(value, maxScrollTop(scrollbox)));
1485
+ }
1486
+ function resolveSidebarAnchorScrollTop(input) {
1487
+ if (!input.expanded || !input.anchor || input.anchor.childIDs.length === 0) {
1488
+ return {
1489
+ matched: false
1490
+ };
1491
+ }
1492
+ let top = input.leadingHeight;
1493
+ const rowTops = /* @__PURE__ */ new Map();
1494
+ for (const row of input.rows) {
1495
+ rowTops.set(row.id, top);
1496
+ top += row.height + SUBAGENTS_ROW_GAP;
1497
+ }
1498
+ for (const [index, childID] of input.anchor.childIDs.entries()) {
1499
+ const rowTop = rowTops.get(childID);
1500
+ if (rowTop === void 0) continue;
1501
+ const desiredTop = rowTop + (index === 0 ? input.anchor.intraRowOffset : 0);
1502
+ const maxTop = Math.max(0, input.scrollHeight - input.viewportHeight);
1503
+ const nextTop = Math.max(0, Math.min(desiredTop, maxTop));
1504
+ return {
1505
+ matched: true,
1506
+ offsetTop: nextTop,
1507
+ scrollTop: input.scrollTop !== nextTop ? nextTop : void 0
1508
+ };
1509
+ }
1510
+ return {
1511
+ matched: false
1512
+ };
1513
+ }
1514
+ function preservedSidebarScrollTop(input) {
1515
+ if (!input.expanded) return void 0;
1516
+ const anchorTop = resolveSidebarAnchorScrollTop({
1517
+ expanded: input.expanded,
1518
+ anchor: input.anchor,
1519
+ rows: input.rows ?? [],
1520
+ leadingHeight: input.leadingHeight ?? 0,
1521
+ scrollTop: input.scrollTop,
1522
+ scrollHeight: input.scrollHeight,
1523
+ viewportHeight: input.viewportHeight
1524
+ });
1525
+ if (anchorTop.matched) return anchorTop.scrollTop;
1526
+ const maxTop = Math.max(0, input.scrollHeight - input.viewportHeight);
1527
+ const top = Math.max(0, Math.min(input.offsetTop, maxTop));
1528
+ return top > 0 && input.scrollTop !== top ? top : void 0;
1529
+ }
1530
+ function elapsedMs(child, nowMs) {
1531
+ if (child.status !== "running") {
1532
+ return child.elapsedMs ?? 0;
1533
+ }
1534
+ const started = Date.parse(child.startedAt);
1535
+ if (Number.isNaN(started)) return child.elapsedMs ?? 0;
1536
+ return Math.max(0, nowMs - started);
1537
+ }
1538
+ function taskStatusMarker(status) {
1539
+ if (status === "done") return "[\u2713]";
1540
+ if (status === "error") return "[x]";
1541
+ return "[ ]";
1542
+ }
1543
+ function statusColor2(status, theme) {
1544
+ if (status === "done") return theme.success;
1545
+ if (status === "error") return theme.error;
1546
+ return theme.warning;
1547
+ }
1548
+ function isSessionTarget(value) {
1549
+ return typeof value === "string" && value.startsWith("ses_");
1550
+ }
1551
+ function resolveChildTargetSessionID(child) {
1552
+ if (isSessionTarget(child.targetSessionID)) {
1553
+ return child.targetSessionID;
1554
+ }
1555
+ if (child.id.startsWith("ses_")) {
1556
+ return child.id;
1557
+ }
1558
+ return void 0;
1559
+ }
1560
+ function resolveSyntheticTargetFromHydratedState(state, synthetic) {
1561
+ const messageMatches = Object.values(state.children).filter((candidate) => candidate.id.startsWith("ses_") && candidate.parentID === synthetic.parentID && synthetic.messageID && candidate.messageID === synthetic.messageID);
1562
+ if (messageMatches.length === 1) return messageMatches[0].id;
1563
+ const parentMatches = Object.values(state.children).filter((candidate) => candidate.id.startsWith("ses_") && candidate.parentID === synthetic.parentID);
1564
+ if (parentMatches.length === 1) return parentMatches[0].id;
1565
+ return void 0;
1566
+ }
1567
+ function ellipsize(value, maxColumns) {
1568
+ return truncateToColumns(value, maxColumns);
1569
+ }
1570
+ function splitParentheticalTitle(title) {
1571
+ const match = title.match(/^(.*?)\s*(\([^)]*\))\s*$/);
1572
+ if (!match) return {
1573
+ label: title
1574
+ };
1575
+ const label = match[1]?.trim();
1576
+ const parenthetical = match[2]?.trim();
1577
+ if (!label || !parenthetical) return {
1578
+ label: title
1579
+ };
1580
+ return {
1581
+ label,
1582
+ parenthetical
1583
+ };
1584
+ }
1585
+ function childParenthetical(child) {
1586
+ if (child.agentName?.trim()) return `(${child.agentName.trim()})`;
1587
+ const primary = splitParentheticalTitle(childPrimaryText(child));
1588
+ if (primary.parenthetical) return primary.parenthetical;
1589
+ return splitParentheticalTitle(child.title).parenthetical;
1590
+ }
1591
+ function formatSecondaryLine(continuation, parenthetical, width) {
1592
+ if (!continuation) return parenthetical;
1593
+ if (!parenthetical) return continuation;
1594
+ const parentheticalWidth = Math.min(textColumns(parenthetical), width);
1595
+ const continuationWidth = width - parentheticalWidth - 1;
1596
+ if (continuationWidth >= MIN_LABEL_WIDTH) {
1597
+ return `${ellipsize(continuation, continuationWidth)} ${ellipsize(parenthetical, parentheticalWidth)}`;
1598
+ }
1599
+ return ellipsize(parenthetical, width);
1600
+ }
1601
+ function childPrimaryText(child) {
1602
+ return child.summary?.trim() || child.title;
1603
+ }
1604
+ function resolveTokenTotal2(child) {
1605
+ const total = child.tokens?.total;
1606
+ if (typeof total === "number" && Number.isFinite(total)) {
1607
+ return total;
1608
+ }
1609
+ const input = child.tokens?.input;
1610
+ const output = child.tokens?.output;
1611
+ if (typeof input === "number" || typeof output === "number") {
1612
+ return Math.max(0, (input ?? 0) + (output ?? 0));
1613
+ }
1614
+ return void 0;
1615
+ }
1616
+ function formatCompactTokenCount(total) {
1617
+ const value = Math.max(0, total);
1618
+ if (value >= 1e6) return `${(value / 1e6).toFixed(1)}M ctx`;
1619
+ if (value >= 1e3) return `${(value / 1e3).toFixed(1)}k ctx`;
1620
+ return `${Math.round(value)} ctx`;
1621
+ }
1622
+ function formatCompactPercent(percent) {
1623
+ return `${Math.max(0, Math.round(percent))}%`;
1624
+ }
1625
+ function contextVariants(child, usageLabel) {
1626
+ const total = resolveTokenTotal2(child);
1627
+ const percent = usageLabel ? void 0 : child.tokens?.contextPercent;
1628
+ const hasTotal = typeof total === "number" && Number.isFinite(total);
1629
+ const hasPercent = typeof percent === "number" && Number.isFinite(percent);
1630
+ if (!hasTotal && !hasPercent) return [""];
1631
+ const formattedTokens = hasTotal ? formatCompactTokenCount(total) : "";
1632
+ const tokenPart = usageLabel ? formattedTokens.replace(/ ctx$/, "") : formattedTokens;
1633
+ const percentPart = hasPercent ? formatCompactPercent(percent) : "";
1634
+ if (tokenPart && percentPart) {
1635
+ return [`${tokenPart} ${percentPart}`, percentPart, tokenPart, ""];
1636
+ }
1637
+ return [tokenPart || percentPart, ""];
1638
+ }
1639
+ function rowWidthBudget(sidebarWidth) {
1640
+ const width = sidebarWidth ?? FALLBACK_SIDEBAR_WIDTH;
1641
+ const innerWidth = width - 4;
1642
+ return Math.max(MIN_ROW_WIDTH, Math.min(innerWidth, 52));
1643
+ }
1644
+ function wrapCompactText(value, width, maxLines) {
1645
+ const normalized = value.replace(/\s+/g, " ").trim();
1646
+ if (!normalized) return [""];
1647
+ const lines = [];
1648
+ let remaining = normalized;
1649
+ while (textColumns(remaining) > width && lines.length < maxLines - 1) {
1650
+ const probe = takeColumns(remaining, width + 1);
1651
+ const breakAt = probe.lastIndexOf(" ");
1652
+ const breakPrefix = breakAt >= 0 ? probe.slice(0, breakAt) : "";
1653
+ const fit = takeColumns(remaining, width);
1654
+ const take = breakAt >= 0 && textColumns(breakPrefix) >= MIN_LABEL_WIDTH && textColumns(breakPrefix) <= width ? breakAt : fit.length;
1655
+ if (take <= 0) break;
1656
+ lines.push(remaining.slice(0, take).trimEnd());
1657
+ remaining = remaining.slice(take).trimStart();
1658
+ }
1659
+ lines.push(lines.length === maxLines - 1 ? ellipsize(remaining, Math.max(1, width)) : remaining);
1660
+ return lines;
1661
+ }
1662
+ function formatChildRowLine(input) {
1663
+ const elapsed = formatDuration(elapsedMs(input.child, input.nowMs));
1664
+ const width = Math.max(MIN_ROW_WIDTH, rowWidthBudget(input.sidebarWidth) - (input.reservedWidth ?? 0));
1665
+ const title = splitParentheticalTitle(childPrimaryText(input.child));
1666
+ const parenthetical = childParenthetical(input.child);
1667
+ for (const meta of contextVariants(input.child, input.usageLabel)) {
1668
+ const detailChars = 2 + textColumns(elapsed) + (meta ? 3 + textColumns(meta) : 0);
1669
+ const labelBudget = Math.min(width - 2, width - Math.max(0, detailChars - width));
1670
+ if (labelBudget >= MIN_LABEL_WIDTH || textColumns(meta) === 0) {
1671
+ const labelLines2 = wrapCompactText(title.label, Math.max(1, labelBudget), 2);
1672
+ return {
1673
+ labelLines: labelLines2,
1674
+ secondaryLine: formatSecondaryLine(labelLines2[1], parenthetical, Math.max(1, labelBudget)),
1675
+ elapsed,
1676
+ meta
1677
+ };
1678
+ }
1679
+ }
1680
+ const labelLines = wrapCompactText(title.label, MIN_LABEL_WIDTH, 2);
1681
+ return {
1682
+ labelLines,
1683
+ secondaryLine: formatSecondaryLine(labelLines[1], parenthetical, MIN_LABEL_WIDTH),
1684
+ elapsed,
1685
+ meta: ""
1686
+ };
1687
+ }
1688
+ function formatTerminalChildRowLine(input) {
1689
+ const elapsed = formatDuration(elapsedMs(input.child, input.nowMs));
1690
+ const width = Math.max(MIN_ROW_WIDTH, rowWidthBudget(input.sidebarWidth));
1691
+ const title = splitParentheticalTitle(childPrimaryText(input.child));
1692
+ const parenthetical = childParenthetical(input.child);
1693
+ const labelSource = parenthetical ? `${title.label} ${parenthetical}` : title.label;
1694
+ const context = contextVariants(input.child, input.usageLabel).find((variant) => variant.length > 0);
1695
+ return {
1696
+ label: ellipsize(labelSource, Math.max(1, width - (input.reservedWidth ?? 0))),
1697
+ meta: context ? `${elapsed} ${context}` : elapsed
1698
+ };
1699
+ }
1700
+ function subagentRowHeight(input) {
1701
+ const hintHeight = input.childHint ? 1 : 0;
1702
+ const modelHeight = input.child.model?.variant ? SUBAGENTS_MODEL_ROW_HEIGHT : 0;
1703
+ if (input.child.status !== "running") {
1704
+ return SUBAGENTS_TERMINAL_ROW_HEIGHT + modelHeight + hintHeight;
1705
+ }
1706
+ const line = formatChildRowLine(input);
1707
+ return (line.secondaryLine ? SUBAGENTS_RUNNING_ROW_HEIGHT : SUBAGENTS_RUNNING_ROW_HEIGHT - 1) + modelHeight + hintHeight;
1708
+ }
1709
+ function resolveTuiSubagentSnapshot(input) {
1710
+ const allChildren = Object.values(input.state.children);
1711
+ const options = {
1712
+ showCompletedHistory: input.showCompletedHistory
1713
+ };
1714
+ const nowMs = input.nowMs ?? Date.now();
1715
+ const ownChildren = input.sessionID ? allChildren.filter((child) => child.parentID === input.sessionID) : allChildren;
1716
+ const ownVisibleChildren = visibleSubagentWorkItems(ownChildren, nowMs, options).sort(byPriority);
1717
+ const totalExecuted = input.sessionID ? countCountedSubagentExecutions({
1718
+ children: allChildren,
1719
+ countedChildIDs: input.state.countedChildIDs,
1720
+ parentSessionID: input.sessionID
1721
+ }) : countHistoricalSubagentExecutions({
1722
+ children: allChildren
1723
+ });
1724
+ return {
1725
+ visibleChildren: ownVisibleChildren,
1726
+ visibleCounts: countRetainedSubagentStatuses({
1727
+ children: allChildren,
1728
+ parentSessionID: input.sessionID
1729
+ }),
1730
+ totalExecuted,
1731
+ showingOtherSessions: false
1732
+ };
1733
+ }
1734
+ function resolveSidebarSubagentSnapshot(input) {
1735
+ return resolveTuiSubagentSnapshot(input);
1736
+ }
1737
+ function SidebarSubagents(props) {
1738
+ const [showCompletedHistory, setShowCompletedHistory] = createSignal(props.restoreFromChild?.showCompletedHistory ?? false);
1739
+ const completedHistoryOptions = () => ({
1740
+ showCompletedHistory: showCompletedHistory()
1741
+ });
1742
+ const snapshot = createMemo(() => resolveSidebarSubagentSnapshot({
1743
+ state: props.state(),
1744
+ sessionID: props.sessionID,
1745
+ nowMs: props.nowMs(),
1746
+ ...completedHistoryOptions()
1747
+ }));
1748
+ const visibleChildren = createMemo(() => snapshot().visibleChildren);
1749
+ const counts = createMemo(() => snapshot().visibleCounts);
1750
+ const totalExecuted = createMemo(() => snapshot().totalExecuted);
1751
+ const visibleChildIDs = createMemo(() => visibleChildren().map((child) => child.id));
1752
+ const [selectedChildID, setSelectedChildID] = createSignal(props.restoreFromChild?.childRowID);
1753
+ let restoreChildRowID = props.restoreFromChild?.childRowID;
1754
+ const [mouseDownChildID, setMouseDownChildID] = createSignal();
1755
+ const [listFocused, setListFocused] = createSignal(false);
1756
+ const [listFocusModeActive, setListFocusModeActive] = createSignal(false);
1757
+ const visibleChildLayoutSignature = createMemo(() => visibleChildren().map((child) => JSON.stringify([child.id, child.status, child.title, child.summary ?? "", child.agentName ?? "", child.tokens?.input ?? "", child.tokens?.output ?? "", child.tokens?.total ?? "", child.tokens?.contextPercent ?? "", child.model?.providerID ?? "", child.model?.modelID ?? "", child.model?.variant ?? "", props.childHint?.(child.id) ?? ""])).join("|"));
1758
+ const listHeight = createMemo(() => {
1759
+ const nowMs = props.nowMs();
1760
+ const sidebarWidth = props.sidebarWidth?.();
1761
+ const contentHeight = visibleChildren().reduce((height, child) => height + subagentRowHeight({
1762
+ child,
1763
+ nowMs,
1764
+ sidebarWidth,
1765
+ reservedWidth: SUBAGENTS_ROW_MARKER_WIDTH,
1766
+ usageLabel: props.usageLabel,
1767
+ childHint: props.childHint?.(child.id)
1768
+ }), 0) + Math.max(0, visibleChildren().length - 1) * SUBAGENTS_ROW_GAP;
1769
+ return Math.max(1, Math.min(SUBAGENTS_MAX_LIST_HEIGHT, contentHeight));
1770
+ });
1771
+ let listContainer;
1772
+ let scrollbox;
1773
+ const scrollRegistration = {
1774
+ getScrollbox: () => scrollbox,
1775
+ getAnchor: () => currentSidebarScrollAnchor(),
1776
+ getRows: () => rowLayouts(),
1777
+ getLeadingHeight: () => 0,
1778
+ offsetTop: 0,
1779
+ restoreFramesRemaining: 0
1780
+ };
1781
+ const focusRegistration = {
1782
+ target: () => props.strictInput && visibleChildIDs().length === 0 ? void 0 : listContainer,
1783
+ focusList: (preferredChildID) => {
1784
+ if (!listContainer || props.strictInput && (listContainer.isDestroyed || !listContainer.visible || visibleChildIDs().length === 0)) return false;
1785
+ const ids = visibleChildIDs();
1786
+ if (preferredChildID && ids.includes(preferredChildID)) {
1787
+ setSelectedChildID(preferredChildID);
1788
+ } else if (!selectedChildID() && ids[0]) {
1789
+ setSelectedChildID(ids[0]);
1790
+ }
1791
+ listContainer.focus();
1792
+ setListFocused(true);
1793
+ setListFocusModeActive(true);
1794
+ return true;
1795
+ },
1796
+ blurList: () => {
1797
+ if (!listFocused() && !listFocusModeActive()) return false;
1798
+ listContainer?.blur();
1799
+ setListFocused(false);
1800
+ setListFocusModeActive(false);
1801
+ return true;
1802
+ },
1803
+ isListFocusModeActive: () => listFocusModeActive()
1804
+ };
1805
+ let previousRunningCount;
1806
+ createEffect(() => {
1807
+ const runningCount = counts().running;
1808
+ const shouldReleaseFocus = shouldReleaseSidebarListFocus({
1809
+ previousRunningCount,
1810
+ runningCount,
1811
+ listFocusModeActive: listFocusModeActive()
1812
+ });
1813
+ previousRunningCount = runningCount;
1814
+ if (!shouldReleaseFocus) return;
1815
+ focusRegistration.blurList();
1816
+ props.onReturnFocus();
1817
+ });
1818
+ const completedHistoryRegistration = {
1819
+ toggleCompletedHistory: () => {
1820
+ setShowCompletedHistory((current) => !current);
1821
+ return true;
1822
+ }
1823
+ };
1824
+ onCleanup(props.controller[registerSidebar]({
1825
+ scroll: scrollRegistration,
1826
+ focus: focusRegistration,
1827
+ history: completedHistoryRegistration
1828
+ }));
1829
+ createEffect(() => {
1830
+ const ids = visibleChildIDs();
1831
+ const current = selectedChildID();
1832
+ if (ids.length === 0) {
1833
+ if (current) setSelectedChildID(void 0);
1834
+ return;
1835
+ }
1836
+ if (!current || !ids.includes(current)) setSelectedChildID(ids[0]);
1837
+ });
1838
+ const refreshListFocused = () => {
1839
+ if (listFocused() && !listContainer) {
1840
+ setListFocused(false);
1841
+ return;
1842
+ }
1843
+ const focused = Boolean(listContainer?.focused || listContainer?.hasFocusedDescendant);
1844
+ if (!focused && listFocused()) setListFocused(false);
1845
+ };
1846
+ const rowTopForIndex = (index) => {
1847
+ let top = 0;
1848
+ const nowMs = props.nowMs();
1849
+ const sidebarWidth = props.sidebarWidth?.();
1850
+ for (let i = 0; i < index; i += 1) {
1851
+ const child = visibleChildren()[i];
1852
+ if (child) {
1853
+ top += subagentRowHeight({
1854
+ child,
1855
+ nowMs,
1856
+ sidebarWidth,
1857
+ reservedWidth: SUBAGENTS_ROW_MARKER_WIDTH,
1858
+ usageLabel: props.usageLabel,
1859
+ childHint: props.childHint?.(child.id)
1860
+ }) + SUBAGENTS_ROW_GAP;
1861
+ }
1862
+ }
1863
+ return top;
1864
+ };
1865
+ const rowLayouts = () => {
1866
+ const nowMs = props.nowMs();
1867
+ const sidebarWidth = props.sidebarWidth?.();
1868
+ return visibleChildren().map((child) => ({
1869
+ id: child.id,
1870
+ height: subagentRowHeight({
1871
+ child,
1872
+ nowMs,
1873
+ sidebarWidth,
1874
+ reservedWidth: SUBAGENTS_ROW_MARKER_WIDTH,
1875
+ usageLabel: props.usageLabel,
1876
+ childHint: props.childHint?.(child.id)
1877
+ })
1878
+ }));
1879
+ };
1880
+ const currentSidebarScrollAnchor = () => {
1881
+ if (!scrollbox) return void 0;
1882
+ const rows = rowLayouts();
1883
+ if (rows.length === 0) return void 0;
1884
+ const viewportTop = clampedScrollTop(scrollbox, scrollbox.scrollTop);
1885
+ let top = 0;
1886
+ for (let index = 0; index < rows.length; index += 1) {
1887
+ const row = rows[index];
1888
+ if (!row) continue;
1889
+ const rowBottom = top + row.height;
1890
+ if (rowBottom > viewportTop) {
1891
+ return {
1892
+ childIDs: rows.slice(index).map((candidate) => candidate.id),
1893
+ intraRowOffset: Math.max(0, viewportTop - top)
1894
+ };
1895
+ }
1896
+ top = rowBottom + SUBAGENTS_ROW_GAP;
1897
+ }
1898
+ const lastRow = rows[rows.length - 1];
1899
+ return lastRow ? {
1900
+ childIDs: [lastRow.id],
1901
+ intraRowOffset: 0
1902
+ } : void 0;
1903
+ };
1904
+ const scrollChildIntoView = (childID) => {
1905
+ if (!scrollbox) return;
1906
+ const selectedIndex = visibleChildIDs().findIndex((id) => id === childID);
1907
+ if (selectedIndex < 0) return;
1908
+ const selectedChild = visibleChildren()[selectedIndex];
1909
+ if (!selectedChild) return;
1910
+ const rowTop = rowTopForIndex(selectedIndex);
1911
+ const rowBottom = rowTop + subagentRowHeight({
1912
+ child: selectedChild,
1913
+ nowMs: props.nowMs(),
1914
+ sidebarWidth: props.sidebarWidth?.(),
1915
+ reservedWidth: SUBAGENTS_ROW_MARKER_WIDTH,
1916
+ usageLabel: props.usageLabel,
1917
+ childHint: props.childHint?.(selectedChild.id)
1918
+ });
1919
+ const viewportTop = scrollbox.scrollTop;
1920
+ const viewportBottom = viewportTop + listHeight();
1921
+ if (rowTop < viewportTop) {
1922
+ const nextTop = clampedScrollTop(scrollbox, rowTop);
1923
+ scrollRegistration.offsetTop = nextTop;
1924
+ scrollbox.scrollTop = nextTop;
1925
+ } else if (rowBottom > viewportBottom) {
1926
+ const nextTop = clampedScrollTop(scrollbox, rowBottom - listHeight());
1927
+ scrollRegistration.offsetTop = nextTop;
1928
+ scrollbox.scrollTop = nextTop;
1929
+ }
1930
+ };
1931
+ const scrollSelectedChildIntoView = () => {
1932
+ if (!listFocusModeActive()) return;
1933
+ scrollChildIntoView(selectedChildID());
1934
+ };
1935
+ const moveSelection = (delta) => {
1936
+ const ids = visibleChildIDs();
1937
+ if (ids.length === 0) return;
1938
+ const currentIndex = ids.findIndex((id) => id === selectedChildID());
1939
+ const fallbackIndex = delta > 0 ? 0 : ids.length - 1;
1940
+ const nextIndex = Math.max(0, Math.min(ids.length - 1, currentIndex < 0 ? fallbackIndex : currentIndex + delta));
1941
+ setSelectedChildID(ids[nextIndex]);
1942
+ scrollChildIntoView(ids[nextIndex]);
1943
+ };
1944
+ const rowActivations = /* @__PURE__ */ new Map();
1945
+ const resolveNavigableChildTargetSessionID = (child) => resolveChildTargetSessionID(child) ?? resolveSyntheticTargetFromHydratedState(props.state(), child);
1946
+ const selectedTargetSessionID = () => {
1947
+ const selected = visibleChildren().find((child) => child.id === selectedChildID());
1948
+ return selected ? resolveNavigableChildTargetSessionID(selected) : void 0;
1949
+ };
1950
+ const activateSelectedChild = () => {
1951
+ const selectedID = selectedChildID();
1952
+ const activateRow = selectedID ? rowActivations.get(selectedID) : void 0;
1953
+ if (activateRow) {
1954
+ activateRow();
1955
+ return;
1956
+ }
1957
+ props.navigate(selectedTargetSessionID());
1958
+ };
1959
+ const toggleCompletedHistory = () => {
1960
+ completedHistoryRegistration.toggleCompletedHistory();
1961
+ };
1962
+ createEffect(() => {
1963
+ selectedChildID();
1964
+ listHeight();
1965
+ if (!listFocused()) return;
1966
+ scrollSelectedChildIntoView();
1967
+ });
1968
+ const handleListKeyDown = (event) => {
1969
+ if (!listFocused()) return;
1970
+ const name = event.name.toLowerCase();
1971
+ if (props.strictInput && (event.ctrl || event.shift || event.super || event.hyper || (event.meta || event.option) && name !== "b")) return;
1972
+ if ((event.meta || event.option) && name === "b") {
1973
+ props.onToggleListFocus();
1974
+ } else if (name === "j" || name === "down" || name === "arrowdown") {
1975
+ moveSelection(1);
1976
+ } else if (name === "k" || name === "up" || name === "arrowup") {
1977
+ moveSelection(-1);
1978
+ } else if (name === "return" || name === "enter") {
1979
+ activateSelectedChild();
1980
+ } else if (name === "h" || name === "left" || name === "arrowleft") {
1981
+ if (props.expanded()) props.onSetExpanded(false);
1982
+ } else if (name === "l" || name === "right" || name === "arrowright") {
1983
+ if (!props.expanded()) props.onSetExpanded(true);
1984
+ } else if (name === "c") {
1985
+ toggleCompletedHistory();
1986
+ } else if (name === "escape" || name === "esc") {
1987
+ focusRegistration.blurList();
1988
+ props.onReturnFocus();
1989
+ } else {
1990
+ return;
1991
+ }
1992
+ event.preventDefault();
1993
+ event.stopPropagation();
1994
+ };
1995
+ props.registerListKeys({
1996
+ target: () => listContainer,
1997
+ onKeyDown: handleListKeyDown
1998
+ });
1999
+ const restorePreservedScroll = () => {
2000
+ if (!scrollbox) return;
2001
+ if (scrollRegistration.restoreFramesRemaining <= 0) return;
2002
+ scrollRegistration.restoreFramesRemaining -= 1;
2003
+ if (restoreChildRowID) {
2004
+ const childRowID = restoreChildRowID;
2005
+ restoreChildRowID = void 0;
2006
+ scrollRegistration.restoreFramesRemaining = 0;
2007
+ if (visibleChildIDs().includes(childRowID)) {
2008
+ scrollChildIntoView(childRowID);
2009
+ } else {
2010
+ scrollbox.scrollTop = 0;
2011
+ }
2012
+ return;
2013
+ }
2014
+ const top = preservedSidebarScrollTop({
2015
+ expanded: props.expanded(),
2016
+ offsetTop: scrollRegistration.offsetTop,
2017
+ anchor: scrollRegistration.anchor,
2018
+ rows: scrollRegistration.getRows(),
2019
+ leadingHeight: scrollRegistration.getLeadingHeight(),
2020
+ scrollTop: scrollbox.scrollTop,
2021
+ scrollHeight: scrollbox.scrollHeight,
2022
+ viewportHeight: scrollbox.viewport.height
2023
+ });
2024
+ if (top === void 0) return;
2025
+ scrollRegistration.offsetTop = top;
2026
+ scrollbox.scrollTop = top;
2027
+ };
2028
+ createEffect(() => {
2029
+ props.expanded();
2030
+ visibleChildIDs().join("|");
2031
+ visibleChildLayoutSignature();
2032
+ props.sidebarWidth?.();
2033
+ restorePreservedScroll();
2034
+ });
2035
+ const ChildRow = (rowProps) => {
2036
+ const child = createMemo(() => visibleChildren().find((candidate) => candidate.id === rowProps.childID));
2037
+ const [hovered, setHovered] = createSignal(false);
2038
+ const [focused, setFocused] = createSignal(false);
2039
+ const targetSessionID = createMemo(() => {
2040
+ const currentChild = child();
2041
+ return currentChild ? resolveNavigableChildTargetSessionID(currentChild) : void 0;
2042
+ });
2043
+ const clickable = createMemo(() => isSessionTarget(targetSessionID()));
2044
+ const selected = createMemo(() => listFocused() && selectedChildID() === rowProps.childID);
2045
+ const emphasized = createMemo(() => clickable() && (hovered() || focused() || selected()));
2046
+ const status = createMemo(() => child()?.status ?? "running");
2047
+ const muted = createMemo(() => status() !== "running" && clickable() && !emphasized());
2048
+ const rowOpacity = createMemo(() => status() === "running" ? 1 : INACTIVE_SUBAGENT_OPACITY);
2049
+ const line = createMemo(() => {
2050
+ const currentChild = child();
2051
+ if (!currentChild) {
2052
+ return {
2053
+ labelLines: [""],
2054
+ elapsed: "00:00",
2055
+ meta: ""
2056
+ };
2057
+ }
2058
+ return formatChildRowLine({
2059
+ child: currentChild,
2060
+ nowMs: props.nowMs(),
2061
+ sidebarWidth: props.sidebarWidth?.(),
2062
+ reservedWidth: SUBAGENTS_ROW_MARKER_WIDTH,
2063
+ usageLabel: props.usageLabel
2064
+ });
2065
+ });
2066
+ const terminalLine = createMemo(() => {
2067
+ const currentChild = child();
2068
+ if (!currentChild) return {
2069
+ label: "",
2070
+ meta: "00:00"
2071
+ };
2072
+ return formatTerminalChildRowLine({
2073
+ child: currentChild,
2074
+ nowMs: props.nowMs(),
2075
+ sidebarWidth: props.sidebarWidth?.(),
2076
+ reservedWidth: SUBAGENTS_ROW_MARKER_WIDTH,
2077
+ usageLabel: props.usageLabel
2078
+ });
2079
+ });
2080
+ const rowHeight = createMemo(() => {
2081
+ const currentChild = child();
2082
+ if (!currentChild) return SUBAGENTS_TERMINAL_ROW_HEIGHT;
2083
+ return subagentRowHeight({
2084
+ child: currentChild,
2085
+ nowMs: props.nowMs(),
2086
+ sidebarWidth: props.sidebarWidth?.(),
2087
+ reservedWidth: SUBAGENTS_ROW_MARKER_WIDTH,
2088
+ usageLabel: props.usageLabel,
2089
+ childHint: props.childHint?.(currentChild.id)
2090
+ });
2091
+ });
2092
+ const modelLine = createMemo(() => {
2093
+ const currentChild = child();
2094
+ if (!currentChild) return void 0;
2095
+ return props.modelLine(currentChild, rowWidthBudget(props.sidebarWidth?.()) - SUBAGENTS_ROW_MARKER_WIDTH);
2096
+ });
2097
+ const activate = () => {
2098
+ const target = targetSessionID();
2099
+ if (target) {
2100
+ props.onNavigateToChild({
2101
+ parentSessionID: props.sessionID,
2102
+ childSessionID: target,
2103
+ childRowID: rowProps.childID,
2104
+ showCompletedHistory: showCompletedHistory()
2105
+ });
2106
+ }
2107
+ props.controller.snapshotScroll();
2108
+ props.navigate(target);
2109
+ };
2110
+ rowActivations.set(rowProps.childID, activate);
2111
+ onCleanup(() => {
2112
+ rowActivations.delete(rowProps.childID);
2113
+ });
2114
+ const handleKeyDown = (event) => {
2115
+ if (!clickable()) return;
2116
+ if (props.strictInput && (event.ctrl || event.meta || event.option || event.shift || event.super || event.hyper)) return;
2117
+ setFocused(true);
2118
+ if (event.name === "return" || event.name === "space") {
2119
+ activate();
2120
+ event.preventDefault();
2121
+ event.stopPropagation();
2122
+ }
2123
+ };
2124
+ return (() => {
2125
+ var _el$ = _$createElement("box");
2126
+ _$setProp(_el$, "flexDirection", "column");
2127
+ _$insert(_el$, _$createComponent(Show, {
2128
+ get when() {
2129
+ return status() === "running";
2130
+ },
2131
+ get fallback() {
2132
+ return (() => {
2133
+ var _el$0 = _$createElement("box"), _el$1 = _$createElement("box"), _el$10 = _$createElement("text"), _el$11 = _$createElement("text"), _el$12 = _$createElement("text"), _el$13 = _$createElement("text");
2134
+ _$insertNode(_el$0, _el$1);
2135
+ _$insertNode(_el$0, _el$13);
2136
+ _$setProp(_el$0, "flexDirection", "column");
2137
+ _$insertNode(_el$1, _el$10);
2138
+ _$insertNode(_el$1, _el$11);
2139
+ _$insertNode(_el$1, _el$12);
2140
+ _$setProp(_el$1, "flexDirection", "row");
2141
+ _$insert(_el$10, () => selected() ? "\u203A" : " ");
2142
+ _$insert(_el$11, () => taskStatusMarker(status()));
2143
+ _$insert(_el$12, () => ` ${terminalLine().label}`);
2144
+ _$insert(_el$13, () => ` \u21B3 ${CLOCK_ICON} ${terminalLine().meta}`);
2145
+ _$insert(_el$0, _$createComponent(Show, {
2146
+ get when() {
2147
+ return modelLine();
2148
+ },
2149
+ children: (metadata) => (() => {
2150
+ var _el$14 = _$createElement("text");
2151
+ _$insert(_el$14, () => ` ${metadata()}`);
2152
+ _$effect((_$p) => _$setProp(_el$14, "fg", props.theme.textMuted, _$p));
2153
+ return _el$14;
2154
+ })()
2155
+ }), null);
2156
+ _$effect((_p$) => {
2157
+ var _v$13 = selected() ? props.theme.accent : props.theme.textMuted, _v$14 = statusColor2(status(), props.theme), _v$15 = selected() ? props.theme.text : muted() ? props.theme.textMuted : props.theme.text, _v$16 = emphasized() ? props.theme.text : props.theme.textMuted;
2158
+ _v$13 !== _p$.e && (_p$.e = _$setProp(_el$10, "fg", _v$13, _p$.e));
2159
+ _v$14 !== _p$.t && (_p$.t = _$setProp(_el$11, "fg", _v$14, _p$.t));
2160
+ _v$15 !== _p$.a && (_p$.a = _$setProp(_el$12, "fg", _v$15, _p$.a));
2161
+ _v$16 !== _p$.o && (_p$.o = _$setProp(_el$13, "fg", _v$16, _p$.o));
2162
+ return _p$;
2163
+ }, {
2164
+ e: void 0,
2165
+ t: void 0,
2166
+ a: void 0,
2167
+ o: void 0
2168
+ });
2169
+ return _el$0;
2170
+ })();
2171
+ },
2172
+ get children() {
2173
+ var _el$2 = _$createElement("box"), _el$3 = _$createElement("box"), _el$4 = _$createElement("text"), _el$5 = _$createElement("text"), _el$6 = _$createElement("text"), _el$7 = _$createElement("box"), _el$8 = _$createElement("text");
2174
+ _$insertNode(_el$2, _el$3);
2175
+ _$insertNode(_el$2, _el$7);
2176
+ _$setProp(_el$2, "flexDirection", "column");
2177
+ _$insertNode(_el$3, _el$4);
2178
+ _$insertNode(_el$3, _el$5);
2179
+ _$insertNode(_el$3, _el$6);
2180
+ _$setProp(_el$3, "flexDirection", "row");
2181
+ _$insert(_el$4, () => selected() ? "\u203A" : " ");
2182
+ _$insert(_el$5, () => taskStatusMarker(status()));
2183
+ _$insert(_el$6, () => ` ${line().labelLines[0] ?? ""}`);
2184
+ _$insert(_el$2, _$createComponent(Show, {
2185
+ get when() {
2186
+ return line().secondaryLine;
2187
+ },
2188
+ children: (secondaryLine) => (() => {
2189
+ var _el$15 = _$createElement("text");
2190
+ _$insert(_el$15, () => ` ${secondaryLine()}`);
2191
+ _$effect((_$p) => _$setProp(_el$15, "fg", muted() ? props.theme.textMuted : props.theme.text, _$p));
2192
+ return _el$15;
2193
+ })()
2194
+ }), _el$7);
2195
+ _$insertNode(_el$7, _el$8);
2196
+ _$setProp(_el$7, "flexDirection", "row");
2197
+ _$setProp(_el$7, "paddingLeft", 4);
2198
+ _$insert(_el$8, () => `\u21B3 ${CLOCK_ICON} ${line().elapsed}`);
2199
+ _$insert(_el$7, _$createComponent(Show, {
2200
+ get when() {
2201
+ return line().meta.length > 0;
2202
+ },
2203
+ get children() {
2204
+ var _el$9 = _$createElement("text");
2205
+ _$insert(_el$9, () => ` ${TOKEN_ICON} ${line().meta}`);
2206
+ _$effect((_$p) => _$setProp(_el$9, "fg", emphasized() ? props.theme.text : props.theme.textMuted, _$p));
2207
+ return _el$9;
2208
+ }
2209
+ }), null);
2210
+ _$insert(_el$2, _$createComponent(Show, {
2211
+ get when() {
2212
+ return modelLine();
2213
+ },
2214
+ children: (metadata) => (() => {
2215
+ var _el$16 = _$createElement("text");
2216
+ _$insert(_el$16, () => ` ${metadata()}`);
2217
+ _$effect((_$p) => _$setProp(_el$16, "fg", props.theme.textMuted, _$p));
2218
+ return _el$16;
2219
+ })()
2220
+ }), null);
2221
+ _$effect((_p$) => {
2222
+ var _v$ = selected() ? props.theme.accent : props.theme.textMuted, _v$2 = statusColor2(status(), props.theme), _v$3 = selected() ? props.theme.text : muted() ? props.theme.textMuted : props.theme.text, _v$4 = emphasized() ? props.theme.text : props.theme.textMuted;
2223
+ _v$ !== _p$.e && (_p$.e = _$setProp(_el$4, "fg", _v$, _p$.e));
2224
+ _v$2 !== _p$.t && (_p$.t = _$setProp(_el$5, "fg", _v$2, _p$.t));
2225
+ _v$3 !== _p$.a && (_p$.a = _$setProp(_el$6, "fg", _v$3, _p$.a));
2226
+ _v$4 !== _p$.o && (_p$.o = _$setProp(_el$8, "fg", _v$4, _p$.o));
2227
+ return _p$;
2228
+ }, {
2229
+ e: void 0,
2230
+ t: void 0,
2231
+ a: void 0,
2232
+ o: void 0
2233
+ });
2234
+ return _el$2;
2235
+ }
2236
+ }), null);
2237
+ _$insert(_el$, _$createComponent(Show, {
2238
+ get when() {
2239
+ return props.childHint?.(rowProps.childID);
2240
+ },
2241
+ children: (hint) => (() => {
2242
+ var _el$17 = _$createElement("text");
2243
+ _$insert(_el$17, () => ` ${hint()}`);
2244
+ _$effect((_$p) => _$setProp(_el$17, "fg", props.theme.warning, _$p));
2245
+ return _el$17;
2246
+ })()
2247
+ }), null);
2248
+ _$effect((_p$) => {
2249
+ var _v$5 = rowHeight(), _v$6 = rowOpacity(), _v$7 = selected() ? props.theme.backgroundElement : void 0, _v$8 = clickable() ? () => setHovered(true) : void 0, _v$9 = clickable() ? () => {
2250
+ setHovered(false);
2251
+ setFocused(false);
2252
+ setMouseDownChildID(void 0);
2253
+ } : void 0, _v$0 = clickable() ? (event) => {
2254
+ event.stopPropagation();
2255
+ setSelectedChildID(rowProps.childID);
2256
+ setMouseDownChildID(rowProps.childID);
2257
+ } : void 0, _v$1 = clickable() ? (event) => {
2258
+ if (mouseDownChildID() === rowProps.childID) {
2259
+ event.stopPropagation();
2260
+ activate();
2261
+ }
2262
+ setMouseDownChildID(void 0);
2263
+ } : void 0, _v$10 = clickable() ? handleKeyDown : void 0, _v$11 = clickable(), _v$12 = clickable() && focused();
2264
+ _v$5 !== _p$.e && (_p$.e = _$setProp(_el$, "height", _v$5, _p$.e));
2265
+ _v$6 !== _p$.t && (_p$.t = _$setProp(_el$, "opacity", _v$6, _p$.t));
2266
+ _v$7 !== _p$.a && (_p$.a = _$setProp(_el$, "backgroundColor", _v$7, _p$.a));
2267
+ _v$8 !== _p$.o && (_p$.o = _$setProp(_el$, "onMouseOver", _v$8, _p$.o));
2268
+ _v$9 !== _p$.i && (_p$.i = _$setProp(_el$, "onMouseOut", _v$9, _p$.i));
2269
+ _v$0 !== _p$.n && (_p$.n = _$setProp(_el$, "onMouseDown", _v$0, _p$.n));
2270
+ _v$1 !== _p$.s && (_p$.s = _$setProp(_el$, "onMouseUp", _v$1, _p$.s));
2271
+ _v$10 !== _p$.h && (_p$.h = _$setProp(_el$, "onKeyDown", _v$10, _p$.h));
2272
+ _v$11 !== _p$.r && (_p$.r = _$setProp(_el$, "focusable", _v$11, _p$.r));
2273
+ _v$12 !== _p$.d && (_p$.d = _$setProp(_el$, "focused", _v$12, _p$.d));
2274
+ return _p$;
2275
+ }, {
2276
+ e: void 0,
2277
+ t: void 0,
2278
+ a: void 0,
2279
+ o: void 0,
2280
+ i: void 0,
2281
+ n: void 0,
2282
+ s: void 0,
2283
+ h: void 0,
2284
+ r: void 0,
2285
+ d: void 0
2286
+ });
2287
+ return _el$;
2288
+ })();
2289
+ };
2290
+ const AggregateBar = () => (() => {
2291
+ var _el$18 = _$createElement("box"), _el$19 = _$createElement("text"), _el$20 = _$createElement("text"), _el$22 = _$createElement("text"), _el$23 = _$createElement("text"), _el$25 = _$createElement("text"), _el$26 = _$createElement("text"), _el$28 = _$createElement("text");
2292
+ _$insertNode(_el$18, _el$19);
2293
+ _$insertNode(_el$18, _el$20);
2294
+ _$insertNode(_el$18, _el$22);
2295
+ _$insertNode(_el$18, _el$23);
2296
+ _$insertNode(_el$18, _el$25);
2297
+ _$insertNode(_el$18, _el$26);
2298
+ _$insertNode(_el$18, _el$28);
2299
+ _$setProp(_el$18, "flexDirection", "row");
2300
+ _$setProp(_el$18, "paddingRight", 1);
2301
+ _$insert(_el$19, () => `\u25CF ${counts().running} run`);
2302
+ _$insertNode(_el$20, _$createTextNode(` \xB7 `));
2303
+ _$insert(_el$22, () => `\u2713 ${counts().done} done`);
2304
+ _$insertNode(_el$23, _$createTextNode(` \xB7 `));
2305
+ _$insert(_el$25, () => `\u2715 ${counts().error} err`);
2306
+ _$insertNode(_el$26, _$createTextNode(` \xB7 `));
2307
+ _$setProp(_el$28, "selectable", false);
2308
+ _$setProp(_el$28, "onMouseDown", toggleCompletedHistory);
2309
+ _$insert(_el$28, () => `\u03A3 ${totalExecuted()}`);
2310
+ _$effect((_p$) => {
2311
+ var _v$17 = props.theme.warning, _v$18 = props.theme.textMuted, _v$19 = props.theme.success, _v$20 = props.theme.textMuted, _v$21 = props.theme.error, _v$22 = props.theme.textMuted, _v$23 = showCompletedHistory() ? props.theme.accent : props.theme.text;
2312
+ _v$17 !== _p$.e && (_p$.e = _$setProp(_el$19, "fg", _v$17, _p$.e));
2313
+ _v$18 !== _p$.t && (_p$.t = _$setProp(_el$20, "fg", _v$18, _p$.t));
2314
+ _v$19 !== _p$.a && (_p$.a = _$setProp(_el$22, "fg", _v$19, _p$.a));
2315
+ _v$20 !== _p$.o && (_p$.o = _$setProp(_el$23, "fg", _v$20, _p$.o));
2316
+ _v$21 !== _p$.i && (_p$.i = _$setProp(_el$25, "fg", _v$21, _p$.i));
2317
+ _v$22 !== _p$.n && (_p$.n = _$setProp(_el$26, "fg", _v$22, _p$.n));
2318
+ _v$23 !== _p$.s && (_p$.s = _$setProp(_el$28, "fg", _v$23, _p$.s));
2319
+ return _p$;
2320
+ }, {
2321
+ e: void 0,
2322
+ t: void 0,
2323
+ a: void 0,
2324
+ o: void 0,
2325
+ i: void 0,
2326
+ n: void 0,
2327
+ s: void 0
2328
+ });
2329
+ return _el$18;
2330
+ })();
2331
+ return (() => {
2332
+ var _el$29 = _$createElement("box"), _el$30 = _$createElement("box"), _el$31 = _$createElement("text");
2333
+ _$insertNode(_el$29, _el$30);
2334
+ _$use((element) => {
2335
+ listContainer = element;
2336
+ if (!element) setListFocused(false);
2337
+ }, _el$29);
2338
+ _$setProp(_el$29, "flexDirection", "column");
2339
+ _$setProp(_el$29, "focusable", true);
2340
+ _$setProp(_el$29, "renderBefore", () => {
2341
+ refreshListFocused();
2342
+ restorePreservedScroll();
2343
+ });
2344
+ _$insertNode(_el$30, _el$31);
2345
+ _$setProp(_el$30, "flexDirection", "row");
2346
+ _$setProp(_el$31, "selectable", false);
2347
+ _$insert(_el$31, () => `${props.expanded() ? SIDEBAR_ARROW_EXPANDED : SIDEBAR_ARROW_COLLAPSED} ${t("subagents")}`);
2348
+ _$insert(_el$30, _$createComponent(Show, {
2349
+ when: PLUGIN_VERSION,
2350
+ children: (version) => (() => {
2351
+ var _el$34 = _$createElement("box"), _el$35 = _$createElement("text");
2352
+ _$insertNode(_el$34, _el$35);
2353
+ _$setProp(_el$34, "flexDirection", "row");
2354
+ _$setProp(_el$35, "opacity", 0.7);
2355
+ _$setProp(_el$35, "selectable", false);
2356
+ _$insert(_el$35, () => ` ${version()}`);
2357
+ _$insert(_el$34, _$createComponent(Show, {
2358
+ get when() {
2359
+ return listFocused();
2360
+ },
2361
+ get children() {
2362
+ var _el$36 = _$createElement("text");
2363
+ _$insertNode(_el$36, _$createTextNode(` \u25CF`));
2364
+ _$setProp(_el$36, "selectable", false);
2365
+ _$effect((_p$) => {
2366
+ var _v$28 = props.theme.accent, _v$29 = props.onToggleExpanded;
2367
+ _v$28 !== _p$.e && (_p$.e = _$setProp(_el$36, "fg", _v$28, _p$.e));
2368
+ _v$29 !== _p$.t && (_p$.t = _$setProp(_el$36, "onMouseDown", _v$29, _p$.t));
2369
+ return _p$;
2370
+ }, {
2371
+ e: void 0,
2372
+ t: void 0
2373
+ });
2374
+ return _el$36;
2375
+ }
2376
+ }), null);
2377
+ _$effect((_p$) => {
2378
+ var _v$30 = props.theme.textMuted, _v$31 = props.onToggleExpanded;
2379
+ _v$30 !== _p$.e && (_p$.e = _$setProp(_el$35, "fg", _v$30, _p$.e));
2380
+ _v$31 !== _p$.t && (_p$.t = _$setProp(_el$35, "onMouseDown", _v$31, _p$.t));
2381
+ return _p$;
2382
+ }, {
2383
+ e: void 0,
2384
+ t: void 0
2385
+ });
2386
+ return _el$34;
2387
+ })()
2388
+ }), null);
2389
+ _$insert(_el$29, _$createComponent(AggregateBar, {}), null);
2390
+ _$insert(_el$29, _$createComponent(Show, {
2391
+ get when() {
2392
+ return props.notice?.();
2393
+ },
2394
+ children: (notice) => (() => {
2395
+ var _el$38 = _$createElement("text");
2396
+ _$insert(_el$38, () => wrapCompactText(notice(), rowWidthBudget(props.sidebarWidth?.()), 3).join("\n"));
2397
+ _$effect((_$p) => _$setProp(_el$38, "fg", props.theme.warning, _$p));
2398
+ return _el$38;
2399
+ })()
2400
+ }), null);
2401
+ _$insert(_el$29, _$createComponent(Show, {
2402
+ get when() {
2403
+ return props.usageLabel;
2404
+ },
2405
+ children: (label) => (() => {
2406
+ var _el$39 = _$createElement("text");
2407
+ _$insert(_el$39, () => wrapCompactText(label(), rowWidthBudget(props.sidebarWidth?.()), 3).join("\n"));
2408
+ _$effect((_$p) => _$setProp(_el$39, "fg", props.theme.textMuted, _$p));
2409
+ return _el$39;
2410
+ })()
2411
+ }), null);
2412
+ _$insert(_el$29, _$createComponent(Show, {
2413
+ get when() {
2414
+ return props.expanded();
2415
+ },
2416
+ get children() {
2417
+ var _el$32 = _$createElement("scrollbox"), _el$33 = _$createElement("box");
2418
+ _$insertNode(_el$32, _el$33);
2419
+ _$use((element) => {
2420
+ scrollbox = element;
2421
+ restorePreservedScroll();
2422
+ }, _el$32);
2423
+ _$setProp(_el$32, "scrollY", true);
2424
+ _$setProp(_el$32, "viewportCulling", false);
2425
+ _$setProp(_el$33, "flexDirection", "column");
2426
+ _$setProp(_el$33, "rowGap", 0);
2427
+ _$insert(_el$33, _$createComponent(For, {
2428
+ get each() {
2429
+ return visibleChildIDs();
2430
+ },
2431
+ children: (childID) => _$createComponent(ChildRow, {
2432
+ childID
2433
+ })
2434
+ }));
2435
+ _$effect((_$p) => _$setProp(_el$32, "height", listHeight(), _$p));
2436
+ return _el$32;
2437
+ }
2438
+ }), null);
2439
+ _$effect((_p$) => {
2440
+ var _v$24 = listFocused() ? props.theme.backgroundPanel : void 0, _v$25 = listFocused(), _v$26 = props.theme.text, _v$27 = props.onToggleExpanded;
2441
+ _v$24 !== _p$.e && (_p$.e = _$setProp(_el$29, "backgroundColor", _v$24, _p$.e));
2442
+ _v$25 !== _p$.t && (_p$.t = _$setProp(_el$29, "focused", _v$25, _p$.t));
2443
+ _v$26 !== _p$.a && (_p$.a = _$setProp(_el$31, "fg", _v$26, _p$.a));
2444
+ _v$27 !== _p$.o && (_p$.o = _$setProp(_el$31, "onMouseDown", _v$27, _p$.o));
2445
+ return _p$;
2446
+ }, {
2447
+ e: void 0,
2448
+ t: void 0,
2449
+ a: void 0,
2450
+ o: void 0
2451
+ });
2452
+ return _el$29;
2453
+ })();
2454
+ }
2455
+ function HomeBottomStatus(props) {
2456
+ const snapshot = createMemo(() => resolveTuiSubagentSnapshot({
2457
+ state: props.state()
2458
+ }));
2459
+ const counts = createMemo(() => snapshot().visibleCounts);
2460
+ const totalExecuted = createMemo(() => snapshot().totalExecuted);
2461
+ const visible = createMemo(() => counts().running > 0 || counts().error > 0 || totalExecuted() > 0);
2462
+ return _$createComponent(Show, {
2463
+ get when() {
2464
+ return visible();
2465
+ },
2466
+ get children() {
2467
+ var _el$40 = _$createElement("box"), _el$41 = _$createElement("box"), _el$42 = _$createElement("text"), _el$43 = _$createElement("text"), _el$45 = _$createElement("text"), _el$46 = _$createElement("text"), _el$48 = _$createElement("text"), _el$49 = _$createElement("text"), _el$51 = _$createElement("text");
2468
+ _$insertNode(_el$40, _el$41);
2469
+ _$setProp(_el$40, "paddingLeft", 1);
2470
+ _$setProp(_el$40, "paddingRight", 1);
2471
+ _$insertNode(_el$41, _el$42);
2472
+ _$insertNode(_el$41, _el$43);
2473
+ _$insertNode(_el$41, _el$45);
2474
+ _$insertNode(_el$41, _el$46);
2475
+ _$insertNode(_el$41, _el$48);
2476
+ _$insertNode(_el$41, _el$49);
2477
+ _$insertNode(_el$41, _el$51);
2478
+ _$setProp(_el$41, "flexDirection", "row");
2479
+ _$insert(_el$42, () => `\u25CF ${counts().running}`);
2480
+ _$insertNode(_el$43, _$createTextNode(` \xB7 `));
2481
+ _$insert(_el$45, () => `\u2713 ${counts().done}`);
2482
+ _$insertNode(_el$46, _$createTextNode(` \xB7 `));
2483
+ _$insert(_el$48, () => `\u2715 ${counts().error}`);
2484
+ _$insertNode(_el$49, _$createTextNode(` \xB7 `));
2485
+ _$insert(_el$51, () => `\u03A3 ${totalExecuted()}`);
2486
+ _$effect((_p$) => {
2487
+ var _v$32 = props.theme.warning, _v$33 = props.theme.textMuted, _v$34 = props.theme.success, _v$35 = props.theme.textMuted, _v$36 = props.theme.error, _v$37 = props.theme.textMuted, _v$38 = props.theme.text;
2488
+ _v$32 !== _p$.e && (_p$.e = _$setProp(_el$42, "fg", _v$32, _p$.e));
2489
+ _v$33 !== _p$.t && (_p$.t = _$setProp(_el$43, "fg", _v$33, _p$.t));
2490
+ _v$34 !== _p$.a && (_p$.a = _$setProp(_el$45, "fg", _v$34, _p$.a));
2491
+ _v$35 !== _p$.o && (_p$.o = _$setProp(_el$46, "fg", _v$35, _p$.o));
2492
+ _v$36 !== _p$.i && (_p$.i = _$setProp(_el$48, "fg", _v$36, _p$.i));
2493
+ _v$37 !== _p$.n && (_p$.n = _$setProp(_el$49, "fg", _v$37, _p$.n));
2494
+ _v$38 !== _p$.s && (_p$.s = _$setProp(_el$51, "fg", _v$38, _p$.s));
2495
+ return _p$;
2496
+ }, {
2497
+ e: void 0,
2498
+ t: void 0,
2499
+ a: void 0,
2500
+ o: void 0,
2501
+ i: void 0,
2502
+ n: void 0,
2503
+ s: void 0
2504
+ });
2505
+ return _el$40;
2506
+ }
2507
+ });
2508
+ }
2509
+
2510
+ // src/tui-v2.tsx
2511
+ async function initializeV2(context) {
2512
+ let disposed = false;
2513
+ let cleanupPromise;
2514
+ let disposeRoot;
2515
+ let hide;
2516
+ let tick;
2517
+ let navigationGeneration = 0;
2518
+ let view;
2519
+ let focus;
2520
+ let monitor;
2521
+ let writer;
2522
+ const subscriptions = [];
2523
+ const claims = [];
2524
+ const reported = /* @__PURE__ */ new Set();
2525
+ function showToast(message, variant) {
2526
+ try {
2527
+ context.ui.toast.show({
2528
+ message,
2529
+ variant
2530
+ });
2531
+ } catch {
2532
+ }
2533
+ }
2534
+ function report(message) {
2535
+ if (reported.has(message)) return;
2536
+ reported.add(message);
2537
+ showToast(message, "warning");
2538
+ }
2539
+ function issue(message) {
2540
+ if (!disposed) report(message);
2541
+ }
2542
+ function cleanup() {
2543
+ if (cleanupPromise) return cleanupPromise;
2544
+ disposed = true;
2545
+ navigationGeneration++;
2546
+ cleanupPromise = (async () => {
2547
+ let failed = false;
2548
+ const release = (action) => {
2549
+ try {
2550
+ action?.();
2551
+ } catch {
2552
+ failed = true;
2553
+ }
2554
+ };
2555
+ release(() => monitor?.dispose());
2556
+ release(() => clearInterval(tick));
2557
+ for (const stop of subscriptions.splice(0)) release(stop);
2558
+ release(() => focus?.dispose());
2559
+ release(hide);
2560
+ for (const remove of claims.splice(0).reverse()) release(remove);
2561
+ release(() => view?.dispose());
2562
+ release(disposeRoot);
2563
+ try {
2564
+ await writer?.dispose();
2565
+ } catch {
2566
+ failed = true;
2567
+ }
2568
+ if (failed) report("Subagents: Cleanup incomplete");
2569
+ })();
2570
+ return cleanupPromise;
2571
+ }
2572
+ try {
2573
+ createRoot((dispose) => {
2574
+ disposeRoot = dispose;
2575
+ const [alive, setAlive] = createSignal2(true);
2576
+ hide = () => setAlive(false);
2577
+ const [state, setState] = createSignal2(createEmptyState());
2578
+ const [nowMs, setNowMs] = createSignal2(Date.now());
2579
+ const [stale, setStale] = createSignal2(false);
2580
+ const [preferences, updatePreferences] = context.storage.store("subagents.sidebar", {
2581
+ initial: {
2582
+ enabled: true,
2583
+ expanded: true
2584
+ }
2585
+ });
2586
+ const theme = {
2587
+ get text() {
2588
+ return context.theme.text.base;
2589
+ },
2590
+ get textMuted() {
2591
+ return context.theme.text.muted;
2592
+ },
2593
+ get accent() {
2594
+ return context.theme.hue.accent[500];
2595
+ },
2596
+ get warning() {
2597
+ return context.theme.text.feedback.warning.base;
2598
+ },
2599
+ get success() {
2600
+ return context.theme.text.feedback.success.base;
2601
+ },
2602
+ get error() {
2603
+ return context.theme.text.feedback.error.base;
2604
+ },
2605
+ get backgroundElement() {
2606
+ return context.theme.background.raised.high;
2607
+ },
2608
+ get backgroundPanel() {
2609
+ return context.theme.background.raised.base;
2610
+ }
2611
+ };
2612
+ const ownedView = view = createSidebarViewController();
2613
+ const routeKey = () => {
2614
+ const route = context.ui.router.current();
2615
+ return route.type === "session" ? `session:${route.sessionID}` : route.type === "plugin" ? `plugin:${route.id}:${route.name}` : "home";
2616
+ };
2617
+ const ownedFocus = focus = createV2SidebarFocus({
2618
+ renderer: context.renderer,
2619
+ view: ownedView,
2620
+ route: routeKey,
2621
+ mode: () => context.keymap.mode.current(),
2622
+ unavailable: () => {
2623
+ if (!disposed) showToast(t("unavailable"), "info");
2624
+ }
2625
+ });
2626
+ const ownedWriter = writer = createV2SnapshotWriter({
2627
+ ...resolveV2SnapshotPaths(),
2628
+ onIssue: () => issue("Subagents: Snapshot unavailable")
2629
+ });
2630
+ const ownedMonitor = monitor = createV2Monitor({
2631
+ session: context.client.session,
2632
+ cache: context.data.session,
2633
+ onIssue: () => issue(t("stale")),
2634
+ onChange(next) {
2635
+ if (disposed) return;
2636
+ ownedView.snapshotScroll();
2637
+ batch(() => {
2638
+ setState(next);
2639
+ setStale(ownedMonitor.stale());
2640
+ setNowMs(Date.now());
2641
+ });
2642
+ ownedWriter.enqueue(next);
2643
+ const running = Object.values(next.children).some((child) => child.status === "running");
2644
+ if (running && !tick) tick = setInterval(() => {
2645
+ if (!disposed) setNowMs(Date.now());
2646
+ }, 1e3);
2647
+ if (!running && tick) {
2648
+ clearInterval(tick);
2649
+ tick = void 0;
2650
+ }
2651
+ }
2652
+ });
2653
+ const run = (work) => {
2654
+ void work.catch(() => issue(t("stale")));
2655
+ };
2656
+ let restore;
2657
+ let intent;
2658
+ async function navigate(target) {
2659
+ const selected = intent;
2660
+ intent = void 0;
2661
+ if (disposed || !target || !selected || selected.childSessionID !== target) return;
2662
+ const token = ++navigationGeneration, route = routeKey();
2663
+ const valid = () => !disposed && token === navigationGeneration && routeKey() === route && state().children[selected.childRowID]?.parentID === selected.parentSessionID;
2664
+ if (!valid()) return;
2665
+ try {
2666
+ const info = await context.client.session.get({
2667
+ sessionID: target
2668
+ });
2669
+ if (!valid() || info.id !== target || info.parentID !== selected.parentSessionID) return;
2670
+ restore = selected;
2671
+ ownedFocus.beforeNavigate();
2672
+ context.ui.router.navigate({
2673
+ type: "session",
2674
+ sessionID: target
2675
+ });
2676
+ } catch {
2677
+ }
2678
+ }
2679
+ async function preference(mutate) {
2680
+ if (disposed) return;
2681
+ try {
2682
+ await updatePreferences(mutate);
2683
+ } catch {
2684
+ issue("Subagents: Preferences unavailable");
2685
+ }
2686
+ }
2687
+ const toggleFocus = () => {
2688
+ if (disposed || context.keymap.mode.current() !== "base" || !preferences.enabled) return false;
2689
+ if (ownedView.isListFocused()) ownedFocus.leave();
2690
+ else {
2691
+ const target = ownedView.target();
2692
+ if (!target || target.isDestroyed || !target.visible) return false;
2693
+ ownedFocus.request("keyboard");
2694
+ if (!ownedView.isListFocused()) return false;
2695
+ }
2696
+ };
2697
+ function Commands() {
2698
+ context.keymap.layer(() => ({
2699
+ mode: "global",
2700
+ commands: [{
2701
+ id: "subagent-statusline.toggle-sidebar-section",
2702
+ title: "Subagents: Toggle sidebar section",
2703
+ description: "Toggle the entire subagent sidebar section",
2704
+ group: "Subagents",
2705
+ palette: true,
2706
+ bind: false,
2707
+ run: () => {
2708
+ if (disposed) return;
2709
+ if (preferences.enabled) ownedFocus.leave();
2710
+ return preference((draft) => {
2711
+ draft.enabled = !draft.enabled;
2712
+ });
2713
+ }
2714
+ }, {
2715
+ id: "subagent-statusline.focus-sidebar-list",
2716
+ title: "Subagents: Focus sidebar list",
2717
+ description: "Focus the subagent sidebar list for keyboard navigation",
2718
+ group: "Subagents",
2719
+ palette: true,
2720
+ bind: false,
2721
+ run: () => {
2722
+ if (!disposed) ownedFocus.request("palette");
2723
+ }
2724
+ }, {
2725
+ id: "subagent-statusline.toggle-completed-history",
2726
+ title: "Subagents: Toggle completed history",
2727
+ description: "Toggle retained completed rows in the subagent sidebar. Shortcut: c while the sidebar list is focused.",
2728
+ group: "Subagents",
2729
+ palette: true,
2730
+ bind: false,
2731
+ run: () => {
2732
+ if (!disposed && !ownedView.toggleCompletedHistory()) issue(t("unavailable"));
2733
+ }
2734
+ }]
2735
+ }));
2736
+ context.keymap.layer(() => ({
2737
+ mode: "base",
2738
+ priority: 100,
2739
+ commands: [{
2740
+ // V2 2.0.11 forwards the keyboard event only to named commands.
2741
+ id: "subagent-statusline.internal.toggle-list-focus",
2742
+ bind: "alt+b",
2743
+ run: (_input, event) => {
2744
+ if (event && (event.ctrl || event.shift || event.super || event.hyper || !(event.meta || event.option) || event.name.toLowerCase() !== "b")) return false;
2745
+ if (toggleFocus() === false) return false;
2746
+ event?.preventDefault();
2747
+ event?.stopPropagation();
2748
+ }
2749
+ }]
2750
+ }));
2751
+ return null;
2752
+ }
2753
+ const registerListKeys = ({
2754
+ target,
2755
+ onKeyDown
2756
+ }) => {
2757
+ context.keymap.layer(() => ({
2758
+ mode: "base",
2759
+ priority: 100,
2760
+ target,
2761
+ commands: ["j", "k", "up", "down", "return", "c", "h", "l", "left", "right", "escape", "alt+b"].map((bind) => ({
2762
+ id: `subagent-statusline.internal.list.${bind.replace("+", "-")}`,
2763
+ bind,
2764
+ run: (_input, event) => {
2765
+ if (disposed || context.keymap.mode.current() !== "base" || !ownedView.isListFocused() || !event) return false;
2766
+ onKeyDown(event);
2767
+ if (!event.defaultPrevented) return false;
2768
+ }
2769
+ }))
2770
+ }));
2771
+ };
2772
+ function modelLine(child, width) {
2773
+ if (!child.model?.variant) return void 0;
2774
+ const info = context.data.session.get(child.id);
2775
+ const model = info && context.data.location.model.list(info.location)?.find((candidate) => candidate.id === child.model?.modelID && candidate.providerID === child.model.providerID);
2776
+ return truncateToColumns(`${model?.name || child.model.modelID} \xB7 ${child.model.variant}`, Math.max(1, width));
2777
+ }
2778
+ function Sidebar(props) {
2779
+ const [width, setWidth] = createSignal2();
2780
+ let element;
2781
+ const measure = () => {
2782
+ if (!element || element.isDestroyed || element.width <= 0 || element.width === width()) return;
2783
+ ownedView.snapshotScroll();
2784
+ setWidth(element.width);
2785
+ };
2786
+ onCleanup2(() => {
2787
+ navigationGeneration++;
2788
+ element?.off("layout-changed", measure);
2789
+ element?.off("resized", measure);
2790
+ ownedFocus.leave();
2791
+ });
2792
+ return (() => {
2793
+ var _el$ = _$createElement2("box");
2794
+ _$use2((node) => {
2795
+ element = node;
2796
+ node.on("layout-changed", measure);
2797
+ node.on("resized", measure);
2798
+ measure();
2799
+ }, _el$);
2800
+ _$setProp2(_el$, "flexDirection", "column");
2801
+ _$insert2(_el$, _$createComponent2(SidebarSubagents, {
2802
+ controller: ownedView,
2803
+ get sessionID() {
2804
+ return props.sessionID;
2805
+ },
2806
+ state,
2807
+ nowMs,
2808
+ expanded: () => preferences.expanded,
2809
+ onToggleExpanded: () => {
2810
+ void preference((draft) => {
2811
+ draft.expanded = !draft.expanded;
2812
+ });
2813
+ },
2814
+ onSetExpanded: (expanded) => {
2815
+ void preference((draft) => {
2816
+ draft.expanded = expanded;
2817
+ });
2818
+ },
2819
+ onReturnFocus: () => ownedFocus.leave(),
2820
+ onToggleListFocus: toggleFocus,
2821
+ onNavigateToChild: (next) => {
2822
+ intent = next;
2823
+ },
2824
+ navigate: (target) => {
2825
+ void navigate(target);
2826
+ },
2827
+ modelLine,
2828
+ registerListKeys,
2829
+ sidebarWidth: width,
2830
+ theme,
2831
+ get restoreFromChild() {
2832
+ return restore?.parentSessionID === props.sessionID ? restore : void 0;
2833
+ },
2834
+ notice: () => stale() ? t("stale") : void 0,
2835
+ childHint: (id) => {
2836
+ state();
2837
+ return ownedMonitor.hint(id) ? t("interrupted") : void 0;
2838
+ },
2839
+ get usageLabel() {
2840
+ return t("usage");
2841
+ },
2842
+ strictInput: true
2843
+ }));
2844
+ return _el$;
2845
+ })();
2846
+ }
2847
+ subscriptions.push(context.data.listen(({
2848
+ details
2849
+ }) => {
2850
+ if (disposed) return;
2851
+ if (details.type === "server.connected") run(ownedMonitor.reconnect());
2852
+ else run(ownedMonitor.accept(details));
2853
+ }));
2854
+ claims.push(context.ui.slot({
2855
+ append: "app",
2856
+ render: () => _$createComponent2(Show2, {
2857
+ get when() {
2858
+ return alive();
2859
+ },
2860
+ get children() {
2861
+ return _$createComponent2(Commands, {});
2862
+ }
2863
+ })
2864
+ }));
2865
+ claims.push(context.ui.slot({
2866
+ append: "sidebar.content",
2867
+ render: (input) => _$createComponent2(Show2, {
2868
+ get when() {
2869
+ return _$memo2(() => !!alive())() && preferences.enabled;
2870
+ },
2871
+ get children() {
2872
+ return _$createComponent2(Show2, {
2873
+ get when() {
2874
+ return input.sessionID;
2875
+ },
2876
+ keyed: true,
2877
+ children: (sessionID) => _$createComponent2(Sidebar, {
2878
+ sessionID
2879
+ })
2880
+ });
2881
+ }
2882
+ })
2883
+ }));
2884
+ claims.push(context.ui.slot({
2885
+ append: "home.footer.status",
2886
+ render: () => _$createComponent2(Show2, {
2887
+ get when() {
2888
+ return alive();
2889
+ },
2890
+ get children() {
2891
+ return _$createComponent2(HomeBottomStatus, {
2892
+ state,
2893
+ theme
2894
+ });
2895
+ }
2896
+ })
2897
+ }));
2898
+ let previousRoute;
2899
+ createEffect2(() => {
2900
+ const route = context.ui.router.current(), key = routeKey();
2901
+ if (key === previousRoute) return;
2902
+ previousRoute = key;
2903
+ navigationGeneration++;
2904
+ intent = void 0;
2905
+ ownedFocus.routeChanged();
2906
+ if (restore && (route.type !== "session" || route.sessionID !== restore.parentSessionID && route.sessionID !== restore.childSessionID)) restore = void 0;
2907
+ untrack(() => run(ownedMonitor.refresh(route.type === "session" ? route.sessionID : void 0)));
2908
+ });
2909
+ });
2910
+ return cleanup;
2911
+ } catch (error) {
2912
+ await cleanup();
2913
+ throw error;
2914
+ }
2915
+ }
2916
+ var tui_v2_default = Plugin.define({
2917
+ id: "subagent-statusline.tui",
2918
+ setup: initializeV2
2919
+ });
2920
+ export {
2921
+ tui_v2_default as default
2922
+ };