@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-v1.js ADDED
@@ -0,0 +1,4380 @@
1
+ // src/tui-v1.tsx
2
+ import { createComponent as _$createComponent2 } from "@opentui/solid";
3
+ import { useKeyboard } from "@opentui/solid";
4
+ import { execFileSync } from "child_process";
5
+ import { appendFileSync, existsSync, mkdirSync, readdirSync } from "fs";
6
+ import os2 from "os";
7
+ import { dirname as dirname2, join as join2 } from "path";
8
+ import { Show as Show2, createRoot, createEffect as createEffect2, createSignal as createSignal2, onCleanup as onCleanup2 } from "solid-js";
9
+
10
+ // src/reconcile.ts
11
+ var DEFAULT_STALE_RUNNING_THRESHOLD_MS = 10 * 60 * 6e4;
12
+ var RUNNING_SESSION_STATUS_VALUES = /* @__PURE__ */ new Set([
13
+ "busy",
14
+ "running",
15
+ "pending",
16
+ "queued",
17
+ "in_progress",
18
+ "working",
19
+ "compacting",
20
+ "retry"
21
+ ]);
22
+ var DONE_SESSION_STATUS_VALUES = /* @__PURE__ */ new Set([
23
+ "idle",
24
+ "done",
25
+ "completed",
26
+ "complete",
27
+ "success",
28
+ "succeeded"
29
+ ]);
30
+ var ERROR_SESSION_STATUS_VALUES = /* @__PURE__ */ new Set([
31
+ "error",
32
+ "failed",
33
+ "failure",
34
+ "cancelled",
35
+ "canceled",
36
+ "aborted"
37
+ ]);
38
+ function parseStaleRunningThresholdMs(value) {
39
+ if (typeof value !== "string" || value.trim().length === 0) {
40
+ return DEFAULT_STALE_RUNNING_THRESHOLD_MS;
41
+ }
42
+ const parsed = Number(value);
43
+ if (!Number.isFinite(parsed) || parsed < 0) {
44
+ return DEFAULT_STALE_RUNNING_THRESHOLD_MS;
45
+ }
46
+ return Math.floor(parsed);
47
+ }
48
+ function deriveOpenCodeSessionStatus(value) {
49
+ if (hasStructuredErrorEvidence(value)) {
50
+ return "error";
51
+ }
52
+ const values = collectOpenCodeSessionStatusValues(value);
53
+ if (values.some((status) => ERROR_SESSION_STATUS_VALUES.has(status))) {
54
+ return "error";
55
+ }
56
+ if (values.some((status) => RUNNING_SESSION_STATUS_VALUES.has(status))) {
57
+ return "running";
58
+ }
59
+ if (values.some((status) => DONE_SESSION_STATUS_VALUES.has(status))) {
60
+ return "done";
61
+ }
62
+ return void 0;
63
+ }
64
+ function hasStructuredErrorEvidence(value, depth = 0) {
65
+ if (depth > 4) return false;
66
+ const record = asRecord(value);
67
+ if (!record) return false;
68
+ if (record.error) return true;
69
+ for (const nested of Object.values(record)) {
70
+ if (Array.isArray(nested)) {
71
+ if (nested.some((item) => hasStructuredErrorEvidence(item, depth + 1))) {
72
+ return true;
73
+ }
74
+ continue;
75
+ }
76
+ if (hasStructuredErrorEvidence(nested, depth + 1)) return true;
77
+ }
78
+ return false;
79
+ }
80
+ function resolveSessionStatusWithMessageSummary(input) {
81
+ const summary = input.summary;
82
+ if (input.status === "error") {
83
+ return { status: "error", endedAt: summary?.evidenceAt };
84
+ }
85
+ if (input.status === "running") {
86
+ return { status: "running" };
87
+ }
88
+ if (summary && !summary.fetchFailed && summary.hasError) {
89
+ return { status: "error", endedAt: summary.evidenceAt };
90
+ }
91
+ if (input.status === "done") {
92
+ return {
93
+ status: "done",
94
+ endedAt: summary?.completedAt ?? summary?.evidenceAt
95
+ };
96
+ }
97
+ if (summary && !summary.fetchFailed && typeof summary.completedAt === "string") {
98
+ return { status: "done", endedAt: summary.completedAt };
99
+ }
100
+ return {};
101
+ }
102
+ function summarizeSessionMessages(messages) {
103
+ let completedAt;
104
+ let evidenceAt;
105
+ let hasError = false;
106
+ let latestAssistantActivityAt;
107
+ let latestAssistantActivityAtMs;
108
+ let latestMessageActivityAt;
109
+ let latestMessageActivityAtMs;
110
+ const messageInfos = messages.map((rawMessage) => asRecord(rawMessage)).map((message) => asRecord(message?.info));
111
+ for (const info of messageInfos) {
112
+ if (!info) continue;
113
+ const activityMs = messageTimeMillis(info);
114
+ if (activityMs > 0 && (latestMessageActivityAtMs === void 0 || activityMs > latestMessageActivityAtMs)) {
115
+ latestMessageActivityAtMs = activityMs;
116
+ latestMessageActivityAt = new Date(activityMs).toISOString();
117
+ }
118
+ }
119
+ const assistantMessages = messageInfos.filter(
120
+ (info) => info?.role === "assistant"
121
+ ).sort((left, right) => messageTimeMillis(left) - messageTimeMillis(right));
122
+ for (const info of assistantMessages) {
123
+ const time = asRecord(info.time);
124
+ const activityMs = messageTimeMillis(info);
125
+ if (activityMs > 0 && (latestAssistantActivityAtMs === void 0 || activityMs > latestAssistantActivityAtMs)) {
126
+ latestAssistantActivityAtMs = activityMs;
127
+ latestAssistantActivityAt = new Date(activityMs).toISOString();
128
+ }
129
+ const candidate = timestampFromUnknown(time?.completed);
130
+ const errorAt = timestampFromUnknown(time?.updated) ?? timestampFromUnknown(time?.completed) ?? timestampFromUnknown(time?.created);
131
+ if (info.error) {
132
+ hasError = true;
133
+ evidenceAt = errorAt ?? evidenceAt;
134
+ } else if (candidate) {
135
+ completedAt = candidate;
136
+ evidenceAt = candidate;
137
+ hasError = false;
138
+ }
139
+ }
140
+ return {
141
+ completedAt,
142
+ evidenceAt,
143
+ hasError,
144
+ latestAssistantActivityAt,
145
+ latestAssistantActivityAtMs,
146
+ latestMessageActivityAt,
147
+ latestMessageActivityAtMs
148
+ };
149
+ }
150
+ function hasRecentMessageActivity(input) {
151
+ return input.latestMessageActivityAtMs !== void 0 && input.nowMs - input.latestMessageActivityAtMs < input.staleThresholdMs;
152
+ }
153
+ function canSafelyCloseNoTargetPersistedCandidate(input) {
154
+ if (input.staleThresholdMs <= 0) return false;
155
+ if (input.startedMs < input.staleThresholdMs || input.updatedMs < input.staleThresholdMs) {
156
+ return false;
157
+ }
158
+ return !hasRecentMessageActivity({
159
+ nowMs: input.nowMs,
160
+ latestMessageActivityAtMs: input.latestMessageActivityAtMs,
161
+ staleThresholdMs: input.staleThresholdMs
162
+ });
163
+ }
164
+ function shouldApplyStaleRunningFallback(input) {
165
+ return input.staleThresholdMs > 0 && input.evidence.canApplyStaleFallback === true && input.evidence.probeFailed !== true && input.startedMs >= input.staleThresholdMs && input.updatedMs >= input.staleThresholdMs;
166
+ }
167
+ function shouldSkipCandidateForBackoff(cache, nowMs) {
168
+ return cache !== void 0 && nowMs < cache.nextAllowedAtMs;
169
+ }
170
+ function nextBackoffState(input) {
171
+ const nextBackoffMs = input.cache ? Math.min(
172
+ input.maxBackoffMs,
173
+ Math.max(input.initialBackoffMs, input.cache.backoffMs * 2)
174
+ ) : input.initialBackoffMs;
175
+ return {
176
+ backoffMs: nextBackoffMs,
177
+ nextAllowedAtMs: input.nowMs + nextBackoffMs
178
+ };
179
+ }
180
+ function capCandidates(candidates, maxCandidates) {
181
+ if (maxCandidates <= 0) return [];
182
+ return candidates.length <= maxCandidates ? candidates : candidates.slice(0, maxCandidates);
183
+ }
184
+ function resolvePersistedStaleSubtaskFromParentMessages(input) {
185
+ const matches = [];
186
+ for (const rawMessage of input.messages) {
187
+ const message = asRecord(rawMessage);
188
+ const info = asRecord(message?.info);
189
+ if (!info || info.role !== "assistant") continue;
190
+ const assistantParentID = asString(
191
+ info.parentID ?? message?.parentID ?? message?.parentMessageID
192
+ );
193
+ const parts = Array.isArray(message?.parts) ? message.parts : [];
194
+ for (const rawPart of parts) {
195
+ const part = asRecord(rawPart);
196
+ if (!part || part.type !== "tool" || part.tool !== "task") continue;
197
+ const state = asRecord(part.state);
198
+ const rawStatus = asString(state?.status);
199
+ const status = rawStatus === "completed" ? "done" : rawStatus === "error" ? "error" : void 0;
200
+ if (!status) continue;
201
+ const metadata = asRecord(state?.metadata);
202
+ const targetSessionID = sessionIDFromUnknown(metadata?.sessionId) ?? sessionIDFromUnknown(metadata?.sessionID) ?? parseTaskSessionIDFromOutput(state?.output);
203
+ const partTitle = asString(state?.input && asRecord(state.input)?.description) ?? asString(state?.title) ?? asString(part.description);
204
+ const partSummary = asString(state?.input && asRecord(state.input)?.prompt) ?? asString(state?.description);
205
+ const partAgent = asString(state?.input && asRecord(state.input)?.subagent_type) ?? asString(part.agent);
206
+ const parentMessageMatch = assistantParentID !== void 0 && assistantParentID === input.candidate.messageID;
207
+ const titleMatch = sameDisplayText(partTitle, input.candidate.title);
208
+ const summaryMatch = sameDisplayText(partSummary, input.candidate.summary);
209
+ const agentMatch = sameDisplayText(partAgent, input.candidate.agentName);
210
+ const metadataCompositeMatch = summaryMatch || titleMatch && agentMatch && !!input.candidate.summary;
211
+ if (!parentMessageMatch && !metadataCompositeMatch) {
212
+ continue;
213
+ }
214
+ const score = (parentMessageMatch ? 100 : 0) + (summaryMatch ? 40 : 0) + (titleMatch ? 20 : 0) + (agentMatch ? 10 : 0);
215
+ const endedAt = timestampFromUnknown(
216
+ asRecord(state?.time)?.end ?? asRecord(state?.time)?.completed ?? asRecord(state?.time)?.updated
217
+ ) ?? timestampFromUnknown(
218
+ asRecord(info?.time)?.completed ?? asRecord(info?.time)?.updated ?? asRecord(info?.time)?.created
219
+ );
220
+ matches.push({
221
+ status,
222
+ endedAt,
223
+ targetSessionID,
224
+ score
225
+ });
226
+ }
227
+ }
228
+ if (matches.length === 0) return void 0;
229
+ if (matches.length === 1) {
230
+ const [only] = matches;
231
+ return {
232
+ status: only.status,
233
+ endedAt: only.endedAt,
234
+ targetSessionID: only.targetSessionID
235
+ };
236
+ }
237
+ const ranked = [...matches].sort((left, right) => right.score - left.score);
238
+ const [best, second] = ranked;
239
+ if (!best || second && best.score === second.score) return void 0;
240
+ return {
241
+ status: best.status,
242
+ endedAt: best.endedAt,
243
+ targetSessionID: best.targetSessionID
244
+ };
245
+ }
246
+ function asRecord(value) {
247
+ return value && typeof value === "object" ? value : void 0;
248
+ }
249
+ function collectOpenCodeSessionStatusValues(value) {
250
+ if (typeof value === "string") {
251
+ const normalized = normalizeStatusValue(value);
252
+ return normalized ? [normalized] : [];
253
+ }
254
+ const record = asRecord(value);
255
+ if (!record) return [];
256
+ const values = [
257
+ normalizeStatusValue(record.type),
258
+ normalizeStatusValue(record.status),
259
+ normalizeStatusValue(record.state),
260
+ normalizeStatusValue(record.phase),
261
+ normalizeStatusValue(record.result)
262
+ ].filter((status) => Boolean(status));
263
+ if (record.error) values.push("error");
264
+ if (record.busy === true || record.running === true) values.push("busy");
265
+ return values;
266
+ }
267
+ function normalizeStatusValue(value) {
268
+ if (typeof value !== "string") return void 0;
269
+ const normalized = value.trim().toLowerCase();
270
+ return normalized.length > 0 ? normalized : void 0;
271
+ }
272
+ function asString(value) {
273
+ return typeof value === "string" && value.length > 0 ? value : void 0;
274
+ }
275
+ function sameDisplayText(left, right) {
276
+ if (!left || !right) return false;
277
+ return left.trim().toLowerCase() === right.trim().toLowerCase();
278
+ }
279
+ function sessionIDFromUnknown(value) {
280
+ return typeof value === "string" && value.startsWith("ses_") ? value : void 0;
281
+ }
282
+ function parseTaskSessionIDFromOutput(value) {
283
+ if (typeof value !== "string") return void 0;
284
+ const match = value.match(/\b(?:task_id\s*:\s*)?(ses_[A-Za-z0-9_-]+)\b/i);
285
+ if (!match) return void 0;
286
+ return match[1];
287
+ }
288
+ function messageTimeMillis(info) {
289
+ const time = asRecord(info?.time);
290
+ return timestampMillisFromUnknown(time?.completed) ?? timestampMillisFromUnknown(time?.updated) ?? timestampMillisFromUnknown(time?.created) ?? 0;
291
+ }
292
+ function timestampFromUnknown(value) {
293
+ const millis = timestampMillisFromUnknown(value);
294
+ return millis === void 0 ? void 0 : new Date(millis).toISOString();
295
+ }
296
+ function timestampMillisFromUnknown(value) {
297
+ if (typeof value === "string") {
298
+ const parsed = Date.parse(value);
299
+ return Number.isNaN(parsed) ? void 0 : parsed;
300
+ }
301
+ if (typeof value === "number" && Number.isFinite(value) && value > 0) {
302
+ const millis = value < 1e10 ? value * 1e3 : value;
303
+ const parsed = new Date(millis);
304
+ return Number.isNaN(parsed.getTime()) ? void 0 : millis;
305
+ }
306
+ return void 0;
307
+ }
308
+
309
+ // src/state.ts
310
+ import { randomUUID } from "crypto";
311
+ import { mkdir, readFile, rename, rm, writeFile } from "fs/promises";
312
+ import { basename, dirname, join } from "path";
313
+ import os from "os";
314
+
315
+ // src/internal-policy.ts
316
+ var TERMINAL_CHILD_TTL_MS = 3 * 24 * 60 * 60 * 1e3;
317
+ var MAX_TERMINAL_CHILDREN = 1500;
318
+ var ELAPSED_TICK_MS = 1e3;
319
+ var DONE_TOKEN_REHYDRATE_THROTTLE_MS = 2e3;
320
+ var MAINTENANCE_TICK_MS = DONE_TOKEN_REHYDRATE_THROTTLE_MS;
321
+ var MAX_SYNC_LOG_READ_BYTES = 1024 * 1024;
322
+
323
+ // src/subagent-classification.ts
324
+ function isRealSessionID(value) {
325
+ return typeof value === "string" && value.startsWith("ses_");
326
+ }
327
+ function isTrustedTargetSessionID(value) {
328
+ return isRealSessionID(value);
329
+ }
330
+ function trustedTargetSessionID(item) {
331
+ return isTrustedTargetSessionID(item.targetSessionID) ? item.targetSessionID : void 0;
332
+ }
333
+ function isRealExecution(item) {
334
+ return item.source === "session" || isRealSessionID(item.id);
335
+ }
336
+ function realExecutionID(item) {
337
+ return trustedTargetSessionID(item) ?? item.id;
338
+ }
339
+ function classifySubagentWorkItem(item) {
340
+ if (isRealExecution(item)) {
341
+ const executionID = realExecutionID(item);
342
+ return {
343
+ kind: "real-execution",
344
+ executionID,
345
+ targetSessionID: executionID
346
+ };
347
+ }
348
+ const targetSessionID = trustedTargetSessionID(item);
349
+ if (targetSessionID) {
350
+ return {
351
+ kind: "execution-proxy",
352
+ executionID: targetSessionID,
353
+ targetSessionID
354
+ };
355
+ }
356
+ return { kind: "invocation-wrapper" };
357
+ }
358
+ function uniqueExecutionID(candidates) {
359
+ const executionIDs = new Set(candidates.map((item) => realExecutionID(item)));
360
+ return executionIDs.size === 1 ? [...executionIDs][0] : void 0;
361
+ }
362
+ function realExecutions(items) {
363
+ return items.filter((item) => classifySubagentWorkItem(item).kind === "real-execution");
364
+ }
365
+ function resolveTrustedTargetExecutionID(item, realItems) {
366
+ const targetSessionID = trustedTargetSessionID(item);
367
+ if (!targetSessionID) return void 0;
368
+ return realExecutions(realItems).some(
369
+ (realItem) => realExecutionID(realItem) === targetSessionID
370
+ ) ? targetSessionID : void 0;
371
+ }
372
+ function resolveSharedMessageExecutionID(item, realItems) {
373
+ if (!item.messageID) return void 0;
374
+ return uniqueExecutionID(
375
+ realExecutions(realItems).filter(
376
+ (realItem) => realItem.parentID === item.parentID && realItem.messageID === item.messageID
377
+ )
378
+ );
379
+ }
380
+ function resolveUniqueSameParentExecutionID(item, realItems) {
381
+ return uniqueExecutionID(
382
+ realExecutions(realItems).filter(
383
+ (realItem) => realItem.parentID === item.parentID
384
+ )
385
+ );
386
+ }
387
+ function resolveCorrelatedExecutionID(item, realItems) {
388
+ if (trustedTargetSessionID(item)) {
389
+ return resolveTrustedTargetExecutionID(item, realItems);
390
+ }
391
+ return resolveSharedMessageExecutionID(item, realItems) ?? resolveUniqueSameParentExecutionID(item, realItems);
392
+ }
393
+ function correlateSubagentWorkItems(items) {
394
+ const realItems = realExecutions(items);
395
+ const executions = /* @__PURE__ */ new Map();
396
+ for (const item of realItems) {
397
+ const executionID = realExecutionID(item);
398
+ if (!executions.has(executionID)) {
399
+ executions.set(executionID, { executionID, real: item, proxies: [] });
400
+ }
401
+ }
402
+ for (const item of items) {
403
+ if (classifySubagentWorkItem(item).kind === "real-execution") continue;
404
+ const executionID = resolveCorrelatedExecutionID(item, realItems);
405
+ if (!executionID) continue;
406
+ executions.get(executionID)?.proxies.push(item);
407
+ }
408
+ return [...executions.values()];
409
+ }
410
+ function mergeProxyMetadataWithRealExecution(real, proxy) {
411
+ const executionID = realExecutionID(real);
412
+ return {
413
+ ...real,
414
+ title: proxy.title ?? real.title,
415
+ summary: proxy.summary ?? real.summary,
416
+ agentName: proxy.agentName ?? real.agentName,
417
+ messageID: real.messageID ?? proxy.messageID,
418
+ id: real.id,
419
+ parentID: real.parentID,
420
+ source: "session",
421
+ targetSessionID: real.targetSessionID ?? executionID,
422
+ status: real.status,
423
+ color: real.color,
424
+ startedAt: real.startedAt,
425
+ updatedAt: real.updatedAt,
426
+ endedAt: real.endedAt,
427
+ elapsedMs: real.elapsedMs,
428
+ tokens: real.tokens,
429
+ model: real.model
430
+ };
431
+ }
432
+
433
+ // src/state.ts
434
+ function statusColor(status) {
435
+ if (status === "done") return "green";
436
+ if (status === "error") return "red";
437
+ return "yellow";
438
+ }
439
+ function safeTimestamp(input, fallback) {
440
+ if (typeof input !== "string") return fallback;
441
+ return Number.isNaN(Date.parse(input)) ? fallback : input;
442
+ }
443
+ function toFiniteNumber(value) {
444
+ if (typeof value === "number" && Number.isFinite(value)) {
445
+ return value;
446
+ }
447
+ if (typeof value === "string" && value.trim().length > 0) {
448
+ const parsed = Number(value);
449
+ return Number.isFinite(parsed) ? parsed : void 0;
450
+ }
451
+ return void 0;
452
+ }
453
+ function toNonNegativeInteger(value) {
454
+ const parsed = toFiniteNumber(value);
455
+ if (parsed === void 0) return void 0;
456
+ return Math.max(0, Math.floor(parsed));
457
+ }
458
+ function sanitizeCountedChildIDs(input) {
459
+ if (!input || typeof input !== "object") return {};
460
+ const counted = {};
461
+ for (const [id, value] of Object.entries(input)) {
462
+ if (!id) continue;
463
+ if (value === true) {
464
+ counted[id] = true;
465
+ }
466
+ }
467
+ return counted;
468
+ }
469
+ function normalizeExecutionCounters(state) {
470
+ state.countedChildIDs = sanitizeCountedChildIDs(state.countedChildIDs);
471
+ const countedTotal = Object.keys(state.countedChildIDs).length;
472
+ state.totalExecuted = Math.max(
473
+ toNonNegativeInteger(state.totalExecuted) ?? 0,
474
+ countedTotal
475
+ );
476
+ }
477
+ function resolveExecutionCountIdentity(child) {
478
+ const classification = classifySubagentWorkItem(child);
479
+ return classification.kind === "real-execution" ? classification.executionID : void 0;
480
+ }
481
+ function countHistoricalSubagentExecutions(input) {
482
+ const children = Array.isArray(input.children) ? input.children : Object.values(input.children);
483
+ const scopedChildren = input.parentSessionID ? children.filter((child) => child.parentID === input.parentSessionID) : children;
484
+ return correlateSubagentWorkItems(scopedChildren).length;
485
+ }
486
+ function countCountedSubagentExecutions(input) {
487
+ const children = Array.isArray(input.children) ? input.children : Object.values(input.children);
488
+ const scopedChildren = input.parentSessionID ? children.filter((child) => child.parentID === input.parentSessionID) : children;
489
+ return correlateSubagentWorkItems(scopedChildren).filter(
490
+ (execution) => input.countedChildIDs[execution.executionID]
491
+ ).length;
492
+ }
493
+ function countRetainedSubagentStatuses(input) {
494
+ const children = Array.isArray(input.children) ? input.children : Object.values(input.children);
495
+ const scopedChildren = input.parentSessionID ? children.filter((child) => child.parentID === input.parentSessionID) : children;
496
+ const counts = { running: 0, done: 0, error: 0 };
497
+ for (const { real } of correlateSubagentWorkItems(scopedChildren)) {
498
+ counts[real.status] += 1;
499
+ }
500
+ return counts;
501
+ }
502
+ function reconcileCountedExecutionsWithChildren(state) {
503
+ const executionIDs = correlateSubagentWorkItems(
504
+ Object.values(state.children)
505
+ ).map((execution) => execution.executionID);
506
+ state.countedChildIDs = Object.fromEntries(
507
+ executionIDs.map((id) => [id, true])
508
+ );
509
+ state.totalExecuted = executionIDs.length;
510
+ }
511
+ function countChildExecution(state, child) {
512
+ normalizeExecutionCounters(state);
513
+ const countIdentity = resolveExecutionCountIdentity(child);
514
+ if (!countIdentity) return false;
515
+ if (state.countedChildIDs[countIdentity]) return false;
516
+ const previousTotal = Math.max(
517
+ toNonNegativeInteger(state.totalExecuted) ?? 0,
518
+ Object.keys(state.countedChildIDs).length
519
+ );
520
+ state.countedChildIDs[countIdentity] = true;
521
+ state.totalExecuted = previousTotal + 1;
522
+ return true;
523
+ }
524
+ function sanitizeTokens(input) {
525
+ if (!input || typeof input !== "object") return void 0;
526
+ const raw = input;
527
+ const tokens = {
528
+ input: toFiniteNumber(raw.input),
529
+ output: toFiniteNumber(raw.output),
530
+ total: toFiniteNumber(raw.total),
531
+ contextPercent: toFiniteNumber(raw.contextPercent)
532
+ };
533
+ if (tokens.input === void 0 && tokens.output === void 0 && tokens.total === void 0 && tokens.contextPercent === void 0) {
534
+ return void 0;
535
+ }
536
+ return tokens;
537
+ }
538
+ function sanitizeModel(input) {
539
+ if (!input || typeof input !== "object") return void 0;
540
+ const raw = input;
541
+ const providerID = typeof raw.providerID === "string" ? raw.providerID.trim() : "";
542
+ const modelID = typeof raw.modelID === "string" ? raw.modelID.trim() : "";
543
+ const variant = typeof raw.variant === "string" ? raw.variant.trim() : "";
544
+ if (!providerID || !modelID) return void 0;
545
+ return { providerID, modelID, ...variant ? { variant } : {} };
546
+ }
547
+ function sanitizeTargetSessionID(value, fallback) {
548
+ if (typeof value === "string" && value.startsWith("ses_")) {
549
+ return value;
550
+ }
551
+ if (typeof fallback === "string" && fallback.startsWith("ses_")) {
552
+ return fallback;
553
+ }
554
+ return void 0;
555
+ }
556
+ function mergeTokens(existing, incoming) {
557
+ if (!existing && !incoming) return void 0;
558
+ return {
559
+ input: incoming?.input ?? existing?.input,
560
+ output: incoming?.output ?? existing?.output,
561
+ total: incoming?.total ?? existing?.total,
562
+ contextPercent: incoming?.contextPercent ?? existing?.contextPercent
563
+ };
564
+ }
565
+ function sameTokens(left, right) {
566
+ return JSON.stringify(left) === JSON.stringify(right);
567
+ }
568
+ function normalizeComparableText(value) {
569
+ return value.replace(/\s+/g, " ").trim().toLowerCase();
570
+ }
571
+ function sanitizeSummary(value, title) {
572
+ if (typeof value !== "string") return void 0;
573
+ const summary = value.replace(/\s+/g, " ").trim();
574
+ if (!summary) return void 0;
575
+ if (normalizeComparableText(summary) === normalizeComparableText(title)) {
576
+ return void 0;
577
+ }
578
+ return summary;
579
+ }
580
+ function sanitizeAgentName(value) {
581
+ if (typeof value !== "string") return void 0;
582
+ const agentName = value.replace(/^\((.*)\)$/, "$1").replace(/\s+/g, " ").trim();
583
+ return agentName || void 0;
584
+ }
585
+ function resolveElapsedMs(child, nowMs) {
586
+ const startedMs = Date.parse(child.startedAt);
587
+ if (Number.isNaN(startedMs)) return 0;
588
+ const endSource = child.endedAt ?? child.updatedAt;
589
+ const endMs = child.endedAt ? Date.parse(endSource) : nowMs;
590
+ if (Number.isNaN(endMs)) return 0;
591
+ return Math.max(0, endMs - startedMs);
592
+ }
593
+ function terminalReferenceMs(child) {
594
+ const parsed = Date.parse(
595
+ child.endedAt ?? child.updatedAt ?? child.startedAt
596
+ );
597
+ return Number.isNaN(parsed) ? 0 : parsed;
598
+ }
599
+ function pruneTerminalChildren(state, now = /* @__PURE__ */ new Date()) {
600
+ const nowMs = now.getTime();
601
+ const terminalChildren = [];
602
+ let pruned = 0;
603
+ for (const child of Object.values(state.children)) {
604
+ if (child.status === "running") continue;
605
+ const referenceMs = terminalReferenceMs(child);
606
+ if (nowMs - referenceMs > TERMINAL_CHILD_TTL_MS) {
607
+ delete state.children[child.id];
608
+ pruned += 1;
609
+ continue;
610
+ }
611
+ terminalChildren.push({ id: child.id, referenceMs });
612
+ }
613
+ if (terminalChildren.length <= MAX_TERMINAL_CHILDREN) {
614
+ return pruned;
615
+ }
616
+ terminalChildren.sort(
617
+ (a, b) => b.referenceMs - a.referenceMs || a.id.localeCompare(b.id)
618
+ );
619
+ for (const child of terminalChildren.slice(MAX_TERMINAL_CHILDREN)) {
620
+ delete state.children[child.id];
621
+ pruned += 1;
622
+ }
623
+ return pruned;
624
+ }
625
+ function refreshDerivedFields(state, now = /* @__PURE__ */ new Date()) {
626
+ const nowISO = now.toISOString();
627
+ const nowMs = now.getTime();
628
+ normalizeExecutionCounters(state);
629
+ for (const [id, child] of Object.entries(state.children)) {
630
+ const startedAt = safeTimestamp(child.startedAt, nowISO);
631
+ const updatedAt = safeTimestamp(child.updatedAt, nowISO);
632
+ const endedAt = child.endedAt ? safeTimestamp(child.endedAt, updatedAt) : void 0;
633
+ const status = child.status === "done" || child.status === "error" || child.status === "running" ? child.status : "running";
634
+ const targetSessionID = sanitizeTargetSessionID(
635
+ child.targetSessionID,
636
+ id.startsWith("ses_") ? id : void 0
637
+ );
638
+ state.children[id] = {
639
+ ...child,
640
+ startedAt,
641
+ updatedAt,
642
+ endedAt,
643
+ status,
644
+ targetSessionID,
645
+ color: statusColor(status),
646
+ tokens: sanitizeTokens(child.tokens),
647
+ model: sanitizeModel(child.model),
648
+ elapsedMs: resolveElapsedMs(
649
+ {
650
+ ...child,
651
+ startedAt,
652
+ updatedAt,
653
+ endedAt,
654
+ status,
655
+ color: statusColor(status)
656
+ },
657
+ nowMs
658
+ )
659
+ };
660
+ }
661
+ reconcileCountedExecutionsWithChildren(state);
662
+ state.updatedAt = safeTimestamp(state.updatedAt, nowISO);
663
+ if (pruneTerminalChildren(state, now) > 0) {
664
+ reconcileCountedExecutionsWithChildren(state);
665
+ state.updatedAt = nowISO;
666
+ }
667
+ }
668
+ var STATUS_DIRNAME = "opencode-subagent-statusline";
669
+ var STATUS_FILENAME = "state.json";
670
+ var STATUS_DIR_MODE = 448;
671
+ var STATUS_FILE_MODE = 384;
672
+ function sanitizeInstanceName(input) {
673
+ return input.replace(/[^A-Za-z0-9._-]/g, "_");
674
+ }
675
+ function resolveDefaultInstanceName() {
676
+ const fromEnv = process.env.OPENCODE_SUBAGENT_STATUSLINE_INSTANCE;
677
+ if (typeof fromEnv === "string" && fromEnv.trim().length > 0) {
678
+ const safe = sanitizeInstanceName(fromEnv);
679
+ if (safe.length > 0) {
680
+ return safe;
681
+ }
682
+ }
683
+ return `pid-${process.pid}`;
684
+ }
685
+ function createEmptyState() {
686
+ return {
687
+ children: {},
688
+ countedChildIDs: {},
689
+ totalExecuted: 0,
690
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
691
+ };
692
+ }
693
+ function resolveStatePath() {
694
+ const fromEnv = process.env.OPENCODE_SUBAGENT_STATUSLINE_STATE;
695
+ if (typeof fromEnv === "string" && fromEnv.trim().length > 0) {
696
+ return fromEnv;
697
+ }
698
+ const runtimeDir = process.env.XDG_RUNTIME_DIR ?? os.tmpdir();
699
+ const instance = resolveDefaultInstanceName();
700
+ return join(runtimeDir, STATUS_DIRNAME, instance, STATUS_FILENAME);
701
+ }
702
+ function resolveTextPath(statePath) {
703
+ return join(dirname(statePath), "status.txt");
704
+ }
705
+ async function writeLocalStatusFile(path, contents, options) {
706
+ if (options?.shouldCommit?.() === false) return;
707
+ const directory = dirname(path);
708
+ await mkdir(directory, { recursive: true, mode: STATUS_DIR_MODE });
709
+ const tempPath = join(
710
+ directory,
711
+ `.${basename(path)}.${process.pid}.${Date.now()}.${randomUUID()}.tmp`
712
+ );
713
+ try {
714
+ await writeFile(tempPath, contents, {
715
+ encoding: "utf8",
716
+ mode: STATUS_FILE_MODE
717
+ });
718
+ if (options?.shouldCommit?.() === false) {
719
+ await rm(tempPath, { force: true });
720
+ return;
721
+ }
722
+ await rename(tempPath, path);
723
+ } catch (error) {
724
+ await rm(tempPath, { force: true }).catch(() => void 0);
725
+ throw error;
726
+ }
727
+ }
728
+ async function saveStatusText(textPath, contents, options) {
729
+ await writeLocalStatusFile(textPath, contents, options);
730
+ }
731
+ async function saveState(statePath, state, options) {
732
+ if (options?.shouldCommit?.() === false) return;
733
+ refreshDerivedFields(state);
734
+ await writeLocalStatusFile(statePath, JSON.stringify(state, null, 2), options);
735
+ }
736
+ function upsertRunningChild(state, input) {
737
+ const now = (/* @__PURE__ */ new Date()).toISOString();
738
+ const observedUpdatedAt = safeTimestamp(input.updatedAt, now);
739
+ const observedStartedAt = safeTimestamp(input.startedAt, observedUpdatedAt);
740
+ const existing = state.children[input.id];
741
+ const targetSessionID = sanitizeTargetSessionID(
742
+ input.targetSessionID ?? existing?.targetSessionID,
743
+ input.id.startsWith("ses_") ? input.id : void 0
744
+ );
745
+ const source = input.source ?? existing?.source ?? "session";
746
+ const counted = existing ? false : countChildExecution(state, {
747
+ id: input.id,
748
+ title: input.title,
749
+ parentID: input.parentID,
750
+ messageID: input.messageID,
751
+ source,
752
+ targetSessionID
753
+ });
754
+ const shouldKeepCompletedTiming = existing?.status === "done" || existing?.status === "error";
755
+ const next = {
756
+ id: input.id,
757
+ title: input.title,
758
+ summary: sanitizeSummary(input.summary, input.title) ?? sanitizeSummary(existing?.summary, input.title),
759
+ agentName: sanitizeAgentName(input.agentName) ?? existing?.agentName,
760
+ parentID: input.parentID,
761
+ messageID: input.messageID ?? existing?.messageID,
762
+ source,
763
+ toolName: input.toolName ?? existing?.toolName,
764
+ targetSessionID,
765
+ status: shouldKeepCompletedTiming ? existing.status : "running",
766
+ color: statusColor(shouldKeepCompletedTiming ? existing.status : "running"),
767
+ startedAt: existing?.startedAt ?? observedStartedAt,
768
+ updatedAt: observedUpdatedAt,
769
+ endedAt: shouldKeepCompletedTiming ? existing.endedAt : void 0,
770
+ elapsedMs: existing?.elapsedMs,
771
+ tokens: existing?.tokens,
772
+ model: existing?.model
773
+ };
774
+ 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)) {
775
+ return counted;
776
+ }
777
+ state.children[input.id] = next;
778
+ state.updatedAt = observedUpdatedAt;
779
+ return true;
780
+ }
781
+ function markChildStatus(state, childID, status, endedAt) {
782
+ const now = (/* @__PURE__ */ new Date()).toISOString();
783
+ let changed = false;
784
+ let stateUpdatedAt = state.updatedAt;
785
+ for (const child of Object.values(state.children)) {
786
+ if (child.id !== childID && child.targetSessionID !== childID) continue;
787
+ const observedEndedAt = endedAt ? safeTimestamp(endedAt, now) : child.endedAt ?? now;
788
+ if (child.status === status && child.color === statusColor(status) && child.updatedAt === observedEndedAt && child.endedAt === observedEndedAt) {
789
+ continue;
790
+ }
791
+ const nextChild = {
792
+ ...child,
793
+ status,
794
+ color: statusColor(status),
795
+ updatedAt: observedEndedAt,
796
+ endedAt: observedEndedAt
797
+ };
798
+ state.children[child.id] = {
799
+ ...nextChild,
800
+ elapsedMs: resolveElapsedMs(nextChild, Date.now())
801
+ };
802
+ stateUpdatedAt = observedEndedAt;
803
+ changed = true;
804
+ }
805
+ if (changed) {
806
+ state.updatedAt = stateUpdatedAt;
807
+ }
808
+ return changed;
809
+ }
810
+ function upsertChildDetails(state, childID, input) {
811
+ const existing = state.children[childID];
812
+ if (!existing) return false;
813
+ const nextTitle = typeof input.title === "string" && input.title.trim().length > 0 ? input.title : existing.title;
814
+ const nextSummary = sanitizeSummary(input.summary, nextTitle) ?? sanitizeSummary(existing.summary, nextTitle);
815
+ const nextAgentName = sanitizeAgentName(input.agentName) ?? existing.agentName;
816
+ const mergedTokens = mergeTokens(existing.tokens, input.tokens);
817
+ const nextTargetSessionID = sanitizeTargetSessionID(
818
+ input.targetSessionID ?? existing.targetSessionID,
819
+ existing.id.startsWith("ses_") ? existing.id : void 0
820
+ );
821
+ const detailsChanged = nextTitle !== existing.title || nextSummary !== existing.summary || nextAgentName !== existing.agentName || !sameTokens(mergedTokens, existing.tokens) || nextTargetSessionID !== existing.targetSessionID;
822
+ if (!detailsChanged) return false;
823
+ const now = (/* @__PURE__ */ new Date()).toISOString();
824
+ const observedUpdatedAt = safeTimestamp(input.updatedAt, now);
825
+ const next = {
826
+ ...existing,
827
+ title: nextTitle,
828
+ summary: nextSummary,
829
+ agentName: nextAgentName,
830
+ tokens: mergedTokens,
831
+ targetSessionID: nextTargetSessionID,
832
+ updatedAt: observedUpdatedAt
833
+ };
834
+ state.children[childID] = next;
835
+ state.updatedAt = observedUpdatedAt;
836
+ return true;
837
+ }
838
+ function setChildModel(state, sessionID, model, updatedAt) {
839
+ const matches = Object.values(state.children).filter(
840
+ (child) => child.id === sessionID || child.targetSessionID === sessionID
841
+ );
842
+ if (matches.length === 0) return false;
843
+ let changed = false;
844
+ const observedUpdatedAt = safeTimestamp(updatedAt, (/* @__PURE__ */ new Date()).toISOString());
845
+ for (const child of matches) {
846
+ const sanitized = sanitizeModel(model);
847
+ if (JSON.stringify(child.model) === JSON.stringify(sanitized)) continue;
848
+ state.children[child.id] = { ...child, model: sanitized };
849
+ changed = true;
850
+ }
851
+ if (changed) state.updatedAt = observedUpdatedAt;
852
+ return changed;
853
+ }
854
+
855
+ // src/events.ts
856
+ function asString2(value) {
857
+ return typeof value === "string" && value.length > 0 ? value : void 0;
858
+ }
859
+ function normalizeMessage(value) {
860
+ const message = isRecord(value) ? value : void 0;
861
+ if (!message) return void 0;
862
+ return isRecord(message.info) ? message.info : message;
863
+ }
864
+ function messageActivityMs(message) {
865
+ const time = isRecord(message.time) ? message.time : void 0;
866
+ const value = time?.completed ?? time?.updated ?? time?.created;
867
+ if (typeof value === "number" && Number.isFinite(value)) return value;
868
+ if (typeof value === "string") {
869
+ const parsed = Date.parse(value);
870
+ return Number.isNaN(parsed) ? 0 : parsed;
871
+ }
872
+ return 0;
873
+ }
874
+ function extractLatestAssistantModel(input) {
875
+ const values = Array.isArray(input) ? input : [input];
876
+ const assistants = values.map((value, index) => ({ message: normalizeMessage(value), index })).filter(
877
+ (entry) => entry.message?.role === "assistant"
878
+ ).sort(
879
+ (left, right) => messageActivityMs(left.message) - messageActivityMs(right.message) || left.index - right.index
880
+ );
881
+ const latest = assistants.at(-1)?.message;
882
+ if (!latest) return void 0;
883
+ const sessionID = asString2(latest.sessionID);
884
+ if (!sessionID) return void 0;
885
+ const providerID = asString2(latest.providerID)?.trim();
886
+ const modelID = asString2(latest.modelID)?.trim();
887
+ const variant = asString2(latest.variant)?.trim();
888
+ const model = providerID && modelID ? { providerID, modelID, ...variant ? { variant } : {} } : void 0;
889
+ const activity = messageActivityMs(latest);
890
+ return {
891
+ sessionID,
892
+ model,
893
+ updatedAt: activity > 0 ? new Date(activity).toISOString() : void 0
894
+ };
895
+ }
896
+ function conciseText(value) {
897
+ if (typeof value !== "string") return void 0;
898
+ const text = value.replace(/\s+/g, " ").trim();
899
+ if (!text) return void 0;
900
+ return text.length > 180 ? `${text.slice(0, 179)}\u2026` : text;
901
+ }
902
+ function sameDisplayText2(a, b) {
903
+ if (!a || !b) return false;
904
+ return a.replace(/\s+/g, " ").trim().toLowerCase() === b.replace(/\s+/g, " ").trim().toLowerCase();
905
+ }
906
+ function firstDistinctSummary(candidates, title) {
907
+ for (const candidate of candidates) {
908
+ const summary = conciseText(candidate);
909
+ if (summary && !sameDisplayText2(summary, title)) return summary;
910
+ }
911
+ return void 0;
912
+ }
913
+ function isTechnicalDelegationTitle(value) {
914
+ if (!value) return false;
915
+ return /^delegation:\s+/i.test(value.trim());
916
+ }
917
+ function promptTitle(value) {
918
+ const text = conciseText(value);
919
+ if (!text) return void 0;
920
+ const sentence = text.match(/^(.+?[.!?])\s/)?.[1]?.trim();
921
+ const title = sentence && sentence.length <= 100 ? sentence : text;
922
+ return title.length > 100 ? `${title.slice(0, 99)}\u2026` : title;
923
+ }
924
+ function firstUsefulTitle(candidates) {
925
+ for (const candidate of candidates) {
926
+ const title = promptTitle(candidate);
927
+ if (title && !isTechnicalDelegationTitle(title)) return title;
928
+ }
929
+ return void 0;
930
+ }
931
+ function extractCreatedChild(event) {
932
+ const info = event.properties?.info;
933
+ const parentID = asString2(info?.parentID);
934
+ if (!parentID) return null;
935
+ const id = asString2(info?.id) ?? asString2(event.properties?.id);
936
+ if (!id) return null;
937
+ const title = asString2(info?.title) ?? "subagent";
938
+ const agentName = asString2(info?.agent) ?? asString2(info?.subagent_type);
939
+ const startedAt = extractEventTimestamp(event, [
940
+ "started",
941
+ "start",
942
+ "created",
943
+ "updated"
944
+ ]);
945
+ const updatedAt = extractEventTimestamp(event, ["updated", "created", "started", "start"]) ?? startedAt;
946
+ return { id, title, agentName, parentID, startedAt, updatedAt };
947
+ }
948
+ function extractSessionID(event) {
949
+ return asString2(event.properties?.sessionID) ?? asString2(event.properties?.sessionId) ?? asString2(event.properties?.info?.sessionID) ?? asString2(event.properties?.info?.sessionId) ?? asString2(event.sessionID) ?? asString2(event.sessionId) ?? asString2(event.properties?.info?.id) ?? asString2(event.properties?.id);
950
+ }
951
+ function isRecord(value) {
952
+ return !!value && typeof value === "object";
953
+ }
954
+ function isSessionID(value) {
955
+ return typeof value === "string" && value.startsWith("ses_");
956
+ }
957
+ function collectSessionIDs(input, target, depth = 0) {
958
+ if (depth > 4 || !input) return;
959
+ if (isSessionID(input)) {
960
+ target.add(input);
961
+ return;
962
+ }
963
+ if (!isRecord(input) && !Array.isArray(input)) return;
964
+ if (Array.isArray(input)) {
965
+ for (const value of input) {
966
+ collectSessionIDs(value, target, depth + 1);
967
+ }
968
+ return;
969
+ }
970
+ for (const [key, value] of Object.entries(input)) {
971
+ if (!key.toLowerCase().includes("session")) continue;
972
+ collectSessionIDs(value, target, depth + 1);
973
+ }
974
+ }
975
+ function resolveSyntheticTargetSessionID(state, synthetic, explicitCandidates = []) {
976
+ const candidates = new Set(explicitCandidates.filter(isSessionID));
977
+ const byMessage = Object.values(state.children).filter(
978
+ (child) => child.id.startsWith("ses_") && child.parentID === synthetic.parentID && child.messageID && synthetic.messageID && child.messageID === synthetic.messageID
979
+ );
980
+ if (byMessage.length === 1) {
981
+ candidates.add(byMessage[0].id);
982
+ }
983
+ const byParent = Object.values(state.children).filter(
984
+ (child) => child.id.startsWith("ses_") && child.parentID === synthetic.parentID
985
+ );
986
+ if (byParent.length === 1) {
987
+ candidates.add(byParent[0].id);
988
+ }
989
+ if (candidates.size !== 1) return void 0;
990
+ return [...candidates][0];
991
+ }
992
+ function extractPartTargetSessionCandidates(event) {
993
+ const part = isRecord(event.properties?.part) ? event.properties.part : void 0;
994
+ if (!part) return [];
995
+ const candidates = /* @__PURE__ */ new Set();
996
+ collectSessionIDs(part, candidates);
997
+ const parentSessionID = asString2(part.sessionID) ?? extractSessionID(event);
998
+ if (parentSessionID) candidates.delete(parentSessionID);
999
+ return [...candidates];
1000
+ }
1001
+ function parseTaskSessionIDFromOutput2(value, parentSessionID) {
1002
+ if (typeof value !== "string") return void 0;
1003
+ const matches = [...value.matchAll(/\b(?:task_id\s*:\s*)?(ses_[a-zA-Z0-9_-]+)\b/gi)];
1004
+ const candidates = new Set(matches.map((match) => match[1]));
1005
+ if (parentSessionID) candidates.delete(parentSessionID);
1006
+ return candidates.size === 1 ? [...candidates][0] : void 0;
1007
+ }
1008
+ function backfillSyntheticTargetsForSession(state, session) {
1009
+ const targetlessSynthetic = Object.values(state.children).filter(
1010
+ (child) => (child.source === "tool" || child.source === "subtask") && !child.targetSessionID && child.parentID === session.parentID
1011
+ );
1012
+ const messageMatches = session.messageID ? targetlessSynthetic.filter(
1013
+ (child) => child.messageID === session.messageID
1014
+ ) : [];
1015
+ const existingSessionSiblings = Object.values(state.children).filter(
1016
+ (child) => child.id !== session.id && (child.source === "session" || child.id.startsWith("ses_")) && child.parentID === session.parentID
1017
+ );
1018
+ const candidates = messageMatches.length > 0 ? messageMatches : targetlessSynthetic;
1019
+ if (candidates.length !== 1) return false;
1020
+ if (messageMatches.length === 0 && existingSessionSiblings.length > 0) {
1021
+ return false;
1022
+ }
1023
+ const synthetic = candidates[0];
1024
+ const targetSessionID = resolveSyntheticTargetSessionID(
1025
+ state,
1026
+ {
1027
+ id: synthetic.id,
1028
+ parentID: synthetic.parentID,
1029
+ messageID: synthetic.messageID
1030
+ },
1031
+ [session.id]
1032
+ );
1033
+ if (targetSessionID !== session.id) return false;
1034
+ return upsertChildDetails(state, synthetic.id, {
1035
+ targetSessionID,
1036
+ updatedAt: session.updatedAt
1037
+ });
1038
+ }
1039
+ function extractTaskToolEvidence(event) {
1040
+ const part = event.properties?.part;
1041
+ if (!isRecord(part) || part.type !== "tool") return null;
1042
+ if (asString2(part.tool) !== "task") return null;
1043
+ const state = isRecord(part.state) ? part.state : void 0;
1044
+ if (!state) return null;
1045
+ const rawStatus = asString2(state.status);
1046
+ const status = rawStatus === "completed" ? "done" : rawStatus === "error" ? "error" : "running";
1047
+ const metadata = isRecord(state.metadata) ? state.metadata : void 0;
1048
+ const targetFromMetadata = asString2(metadata?.sessionId);
1049
+ const parentSessionID = asString2(part.sessionID) ?? extractSessionID(event);
1050
+ const targetFromOutput = parseTaskSessionIDFromOutput2(
1051
+ state.output,
1052
+ parentSessionID
1053
+ );
1054
+ const targetCandidates = extractPartTargetSessionCandidates(event);
1055
+ const targetSessionID = targetFromMetadata ?? targetFromOutput ?? (targetCandidates.length === 1 ? targetCandidates[0] : void 0);
1056
+ const endedAt = status === "done" || status === "error" ? extractEventTimestamp(event, ["completed", "end", "ended", "updated"]) : void 0;
1057
+ return {
1058
+ status,
1059
+ targetSessionID,
1060
+ endedAt
1061
+ };
1062
+ }
1063
+ function mapTaskToolToSubtaskID(state, task) {
1064
+ const runningSubtasks = Object.values(state.children).filter(
1065
+ (child) => child.source === "subtask" && child.status === "running" && child.parentID === task.parentID
1066
+ );
1067
+ const primaryCandidates = runningSubtasks.filter(
1068
+ (child) => child.messageID === task.messageID
1069
+ );
1070
+ const legacyCandidates = task.parentMessageID ? runningSubtasks.filter(
1071
+ (child) => child.messageID === task.parentMessageID
1072
+ ) : [];
1073
+ const candidates = primaryCandidates.length > 0 ? primaryCandidates : legacyCandidates;
1074
+ if (candidates.length === 0) return void 0;
1075
+ if (task.targetSessionID) {
1076
+ const byTarget = candidates.filter(
1077
+ (child) => child.targetSessionID === task.targetSessionID
1078
+ );
1079
+ if (byTarget.length === 1) return byTarget[0].id;
1080
+ }
1081
+ const byTitle = candidates.filter(
1082
+ (child) => sameDisplayText2(child.title, task.title)
1083
+ );
1084
+ if (byTitle.length === 1) return byTitle[0].id;
1085
+ const bySummary = candidates.filter(
1086
+ (child) => sameDisplayText2(child.summary, task.summary)
1087
+ );
1088
+ if (bySummary.length === 1) return bySummary[0].id;
1089
+ const byAgent = task.agentName ? candidates.filter(
1090
+ (child) => sameDisplayText2(child.agentName, task.agentName)
1091
+ ) : [];
1092
+ if (byAgent.length === 1) return byAgent[0].id;
1093
+ if (candidates.length === 1) return candidates[0].id;
1094
+ return void 0;
1095
+ }
1096
+ function extractParentMessageID(event) {
1097
+ return asString2(event.properties?.info?.parentID) ?? asString2(event.properties?.parentID) ?? asString2(event.parentID);
1098
+ }
1099
+ function toIsoTimestamp(value) {
1100
+ if (typeof value === "string") {
1101
+ if (value.trim().length === 0) return void 0;
1102
+ const parsed = Date.parse(value);
1103
+ if (Number.isNaN(parsed)) return void 0;
1104
+ return new Date(parsed).toISOString();
1105
+ }
1106
+ if (typeof value === "number" && Number.isFinite(value)) {
1107
+ if (value <= 0) return void 0;
1108
+ const millis = value < 1e10 ? value * 1e3 : value;
1109
+ const parsed = new Date(millis);
1110
+ return Number.isNaN(parsed.getTime()) ? void 0 : parsed.toISOString();
1111
+ }
1112
+ return void 0;
1113
+ }
1114
+ function extractEventTimestamp(event, keys) {
1115
+ const part = isRecord(event.properties?.part) ? event.properties?.part : void 0;
1116
+ const state = isRecord(part?.state) ? part?.state : void 0;
1117
+ const sources = [
1118
+ isRecord(event.properties?.info?.time) ? event.properties?.info?.time : void 0,
1119
+ isRecord(part?.time) ? part?.time : void 0,
1120
+ isRecord(part?.timestamps) ? part?.timestamps : void 0,
1121
+ isRecord(state?.time) ? state?.time : void 0,
1122
+ isRecord(state?.timestamps) ? state?.timestamps : void 0,
1123
+ state,
1124
+ part
1125
+ ];
1126
+ for (const source of sources) {
1127
+ if (!source) continue;
1128
+ for (const key of keys) {
1129
+ const candidate = toIsoTimestamp(source[key]);
1130
+ if (candidate) return candidate;
1131
+ }
1132
+ }
1133
+ return void 0;
1134
+ }
1135
+ function extractSubtaskChild(event) {
1136
+ const part = event.properties?.part;
1137
+ if (!isRecord(part) || part.type !== "subtask") return null;
1138
+ const partID = asString2(part.id);
1139
+ const parentID = asString2(part.sessionID) ?? extractSessionID(event);
1140
+ const messageID = asString2(part.messageID);
1141
+ if (!partID || !parentID || !messageID) return null;
1142
+ const description = asString2(part.description);
1143
+ const command = asString2(part.command);
1144
+ const agent = asString2(part.agent);
1145
+ const title = description || command || agent || "subtask";
1146
+ const state = isRecord(part.state) ? part.state : void 0;
1147
+ const input = isRecord(state?.input) ? state.input : void 0;
1148
+ const summary = firstDistinctSummary(
1149
+ [input?.prompt, input?.description, part.description, state?.description],
1150
+ title
1151
+ );
1152
+ const startedAt = extractEventTimestamp(event, [
1153
+ "started",
1154
+ "start",
1155
+ "created",
1156
+ "updated"
1157
+ ]);
1158
+ const updatedAt = extractEventTimestamp(event, ["updated", "created", "started", "start"]) ?? startedAt;
1159
+ const targetCandidates = extractPartTargetSessionCandidates(event);
1160
+ const targetSessionID = targetCandidates.length === 1 ? targetCandidates[0] : void 0;
1161
+ return {
1162
+ id: `subtask:${partID}`,
1163
+ title,
1164
+ summary,
1165
+ agentName: agent,
1166
+ parentID,
1167
+ messageID,
1168
+ targetSessionID,
1169
+ startedAt,
1170
+ updatedAt
1171
+ };
1172
+ }
1173
+ function extractToolChild(event) {
1174
+ const part = event.properties?.part;
1175
+ if (!isRecord(part) || part.type !== "tool") return null;
1176
+ const tool = asString2(part.tool);
1177
+ if (tool !== "delegate" && tool !== "task") return null;
1178
+ const partID = asString2(part.id);
1179
+ const parentID = asString2(part.sessionID) ?? extractSessionID(event);
1180
+ const messageID = asString2(part.messageID);
1181
+ const state = isRecord(part.state) ? part.state : void 0;
1182
+ if (!partID || !parentID || !messageID || !state) return null;
1183
+ const taskEvidence = extractTaskToolEvidence(event);
1184
+ const rawStatus = asString2(state.status);
1185
+ const status = taskEvidence?.status ?? (rawStatus === "completed" ? "done" : rawStatus === "error" ? "error" : "running");
1186
+ const input = isRecord(state.input) ? state.input : {};
1187
+ const description = asString2(input.description);
1188
+ const subagentType = asString2(input.subagent_type);
1189
+ const rawTitle = asString2(state.title);
1190
+ const title = (isTechnicalDelegationTitle(rawTitle) ? void 0 : rawTitle) || description || firstUsefulTitle([input.prompt, part.description, state.description]) || subagentType || tool;
1191
+ const summary = firstDistinctSummary(
1192
+ [input.prompt, input.description, part.description, state.description],
1193
+ title
1194
+ );
1195
+ const startedAt = extractEventTimestamp(event, [
1196
+ "started",
1197
+ "start",
1198
+ "created",
1199
+ "updated"
1200
+ ]);
1201
+ const updatedAt = extractEventTimestamp(event, [
1202
+ "updated",
1203
+ "completed",
1204
+ "created",
1205
+ "started",
1206
+ "start"
1207
+ ]) ?? startedAt;
1208
+ const endedAt = status === "done" || status === "error" ? extractEventTimestamp(event, ["completed", "end", "ended", "updated"]) : void 0;
1209
+ const targetCandidates = extractPartTargetSessionCandidates(event);
1210
+ const targetSessionID = taskEvidence?.targetSessionID ?? (targetCandidates.length === 1 ? targetCandidates[0] : void 0);
1211
+ return {
1212
+ id: `tool:${partID}`,
1213
+ title,
1214
+ summary,
1215
+ agentName: subagentType,
1216
+ parentID,
1217
+ messageID,
1218
+ toolName: tool,
1219
+ targetSessionID,
1220
+ status,
1221
+ startedAt,
1222
+ updatedAt,
1223
+ endedAt
1224
+ };
1225
+ }
1226
+ function extractCompletedAssistantMessage(event) {
1227
+ const info = event.properties?.info;
1228
+ if (!isRecord(info)) return null;
1229
+ if (info.role !== "assistant") return null;
1230
+ const time = info.time;
1231
+ if (!isRecord(time) || typeof time.completed !== "number") return null;
1232
+ const sessionID = asString2(info.sessionID) ?? extractSessionID(event);
1233
+ const messageID = asString2(info.id);
1234
+ if (!sessionID || !messageID) return null;
1235
+ return { sessionID, messageID };
1236
+ }
1237
+ function extractDetailTargetIDs(event) {
1238
+ const ids = /* @__PURE__ */ new Set();
1239
+ const part = event.properties?.part;
1240
+ if (isRecord(part)) {
1241
+ const partID = asString2(part.id);
1242
+ if (part.type === "subtask" && partID) {
1243
+ ids.add(`subtask:${partID}`);
1244
+ }
1245
+ if (part.type === "tool") {
1246
+ const tool = asString2(part.tool);
1247
+ if ((tool === "delegate" || tool === "task") && partID) {
1248
+ ids.add(`tool:${partID}`);
1249
+ }
1250
+ }
1251
+ }
1252
+ const sessionID = extractSessionID(event);
1253
+ if (sessionID) ids.add(sessionID);
1254
+ return [...ids];
1255
+ }
1256
+ function normalizePercent(value) {
1257
+ if (value > 0 && value <= 1) {
1258
+ return value * 100;
1259
+ }
1260
+ return value;
1261
+ }
1262
+ function extractChildDetails(event) {
1263
+ const details = {};
1264
+ details.updatedAt = extractEventTimestamp(event, [
1265
+ "updated",
1266
+ "completed",
1267
+ "created",
1268
+ "started",
1269
+ "start"
1270
+ ]);
1271
+ const titleCandidates = [
1272
+ event.properties?.info?.title,
1273
+ event.properties?.title,
1274
+ event.properties?.info?.name,
1275
+ event.properties?.name,
1276
+ event.title,
1277
+ event.name
1278
+ ];
1279
+ for (const candidate of titleCandidates) {
1280
+ const title = asString2(candidate);
1281
+ if (title) {
1282
+ details.title = title;
1283
+ break;
1284
+ }
1285
+ }
1286
+ const part = isRecord(event.properties?.part) ? event.properties.part : void 0;
1287
+ const partState = isRecord(part?.state) ? part.state : void 0;
1288
+ const partInput = isRecord(partState?.input) ? partState.input : void 0;
1289
+ details.agentName = asString2(partInput?.subagent_type) ?? asString2(partInput?.agent) ?? asString2(part?.agent) ?? asString2(event.properties?.info?.agent) ?? asString2(event.properties?.info?.subagent_type);
1290
+ details.summary = firstDistinctSummary(
1291
+ [
1292
+ partInput?.prompt,
1293
+ partInput?.description,
1294
+ part?.description,
1295
+ partState?.description
1296
+ ],
1297
+ details.title
1298
+ );
1299
+ if (isTechnicalDelegationTitle(details.title)) {
1300
+ const replacementTitle = asString2(partInput?.description) ?? firstUsefulTitle([
1301
+ partInput?.prompt,
1302
+ part?.description,
1303
+ partState?.description
1304
+ ]);
1305
+ if (replacementTitle) {
1306
+ details.title = replacementTitle;
1307
+ }
1308
+ }
1309
+ const tokenHints = {};
1310
+ const visited = /* @__PURE__ */ new Set();
1311
+ const walk = (node, depth) => {
1312
+ if (!isRecord(node) || depth > 6) return;
1313
+ if (visited.has(node)) return;
1314
+ visited.add(node);
1315
+ for (const [rawKey, rawValue] of Object.entries(node)) {
1316
+ const key = rawKey.toLowerCase();
1317
+ const asNumber = typeof rawValue === "number" ? rawValue : typeof rawValue === "string" && rawValue.trim().length > 0 ? Number(rawValue) : void 0;
1318
+ if (typeof asNumber === "number" && Number.isFinite(asNumber)) {
1319
+ if (key.includes("context") && key.includes("percent")) {
1320
+ tokenHints.contextPercent = normalizePercent(asNumber);
1321
+ } else if (key.includes("context") && key.includes("usage")) {
1322
+ tokenHints.contextPercent = normalizePercent(asNumber);
1323
+ } else if ((key.includes("input") || key.includes("prompt")) && key.includes("token")) {
1324
+ tokenHints.input = asNumber;
1325
+ } else if ((key.includes("output") || key.includes("completion")) && key.includes("token")) {
1326
+ tokenHints.output = asNumber;
1327
+ } else if (key.includes("total") && key.includes("token")) {
1328
+ tokenHints.total = asNumber;
1329
+ } else if (key === "tokens" || key === "token") {
1330
+ tokenHints.total = asNumber;
1331
+ }
1332
+ }
1333
+ if (isRecord(rawValue)) {
1334
+ walk(rawValue, depth + 1);
1335
+ }
1336
+ }
1337
+ };
1338
+ walk(event, 0);
1339
+ if (tokenHints.input !== void 0 || tokenHints.output !== void 0 || tokenHints.total !== void 0 || tokenHints.contextPercent !== void 0) {
1340
+ details.tokens = tokenHints;
1341
+ }
1342
+ return details;
1343
+ }
1344
+ function applySubagentEvent(state, event) {
1345
+ const e = event ?? {};
1346
+ const type = asString2(e.type);
1347
+ if (!type) return false;
1348
+ if (type === "session.created" || type === "session.updated") {
1349
+ const child = extractCreatedChild(e);
1350
+ if (child) {
1351
+ const details = extractChildDetails(e);
1352
+ let changed2 = upsertRunningChild(state, {
1353
+ ...child,
1354
+ source: "session",
1355
+ targetSessionID: child.id
1356
+ });
1357
+ changed2 = upsertChildDetails(state, child.id, details) || changed2;
1358
+ changed2 = backfillSyntheticTargetsForSession(state, {
1359
+ id: child.id,
1360
+ parentID: child.parentID,
1361
+ updatedAt: child.updatedAt
1362
+ }) || changed2;
1363
+ const sessionStatusFromUpdate = type === "session.updated" ? hasStructuredErrorEvidence(e.properties ?? e) ? "error" : deriveOpenCodeSessionStatus(
1364
+ e.properties?.status ?? e.properties?.state ?? e.properties?.info?.status ?? e.status ?? e.state
1365
+ ) : void 0;
1366
+ if (sessionStatusFromUpdate === "done" || sessionStatusFromUpdate === "error") {
1367
+ const endedAt = extractEventTimestamp(e, [
1368
+ "completed",
1369
+ "end",
1370
+ "ended",
1371
+ "updated"
1372
+ ]);
1373
+ changed2 = markChildStatus(state, child.id, sessionStatusFromUpdate, endedAt) || changed2;
1374
+ }
1375
+ return changed2;
1376
+ }
1377
+ return false;
1378
+ }
1379
+ if (type === "session.idle") {
1380
+ const childID = extractSessionID(e);
1381
+ if (!childID) return false;
1382
+ const endedAt = extractEventTimestamp(e, [
1383
+ "completed",
1384
+ "end",
1385
+ "ended",
1386
+ "updated"
1387
+ ]);
1388
+ const details = extractChildDetails(e);
1389
+ const status = deriveOpenCodeSessionStatus(e.properties ?? e) === "error" || hasStructuredErrorEvidence(e.properties ?? e) ? "error" : "done";
1390
+ let changed2 = markChildStatus(state, childID, status, endedAt);
1391
+ changed2 = upsertChildDetails(state, childID, details) || changed2;
1392
+ return changed2;
1393
+ }
1394
+ if (type === "session.error") {
1395
+ const childID = extractSessionID(e);
1396
+ if (!childID) return false;
1397
+ const endedAt = extractEventTimestamp(e, [
1398
+ "completed",
1399
+ "end",
1400
+ "ended",
1401
+ "updated"
1402
+ ]);
1403
+ const details = extractChildDetails(e);
1404
+ let changed2 = markChildStatus(state, childID, "error", endedAt);
1405
+ changed2 = upsertChildDetails(state, childID, details) || changed2;
1406
+ return changed2;
1407
+ }
1408
+ if (type === "session.status") {
1409
+ const childID = extractSessionID(e);
1410
+ if (!childID) return false;
1411
+ const status = hasStructuredErrorEvidence(e.properties ?? e) ? "error" : deriveOpenCodeSessionStatus(
1412
+ e.properties?.status ?? e.properties?.state ?? e.properties?.info?.status ?? e.status ?? e.state ?? e.properties
1413
+ );
1414
+ if (!status) return false;
1415
+ const endedAt = status === "done" || status === "error" ? extractEventTimestamp(e, ["completed", "end", "ended", "updated"]) : void 0;
1416
+ const details = extractChildDetails(e);
1417
+ let changed2 = status === "running" ? false : markChildStatus(state, childID, status, endedAt);
1418
+ changed2 = upsertChildDetails(state, childID, details) || changed2;
1419
+ return changed2;
1420
+ }
1421
+ let changed = false;
1422
+ if (type === "message.part.updated") {
1423
+ const subtask = extractSubtaskChild(e);
1424
+ if (subtask) {
1425
+ const targetSessionID = resolveSyntheticTargetSessionID(
1426
+ state,
1427
+ {
1428
+ id: subtask.id,
1429
+ parentID: subtask.parentID,
1430
+ messageID: subtask.messageID
1431
+ },
1432
+ subtask.targetSessionID ? [subtask.targetSessionID] : []
1433
+ );
1434
+ changed = upsertRunningChild(state, {
1435
+ ...subtask,
1436
+ source: "subtask",
1437
+ targetSessionID,
1438
+ startedAt: subtask.startedAt,
1439
+ updatedAt: subtask.updatedAt
1440
+ }) || changed;
1441
+ }
1442
+ const tool = extractToolChild(e);
1443
+ if (tool) {
1444
+ const targetSessionID = resolveSyntheticTargetSessionID(
1445
+ state,
1446
+ {
1447
+ id: tool.id,
1448
+ parentID: tool.parentID,
1449
+ messageID: tool.messageID
1450
+ },
1451
+ tool.targetSessionID ? [tool.targetSessionID] : []
1452
+ );
1453
+ const childChanged = upsertRunningChild(state, {
1454
+ ...tool,
1455
+ source: "tool",
1456
+ targetSessionID,
1457
+ startedAt: tool.startedAt,
1458
+ updatedAt: tool.updatedAt
1459
+ });
1460
+ changed = childChanged || changed;
1461
+ if (tool.status === "done" || tool.status === "error") {
1462
+ changed = markChildStatus(
1463
+ state,
1464
+ tool.id,
1465
+ tool.status,
1466
+ tool.endedAt ?? tool.updatedAt
1467
+ ) || changed;
1468
+ if (asString2(
1469
+ e.properties?.part?.tool
1470
+ ) === "task") {
1471
+ const subtaskID = mapTaskToolToSubtaskID(state, {
1472
+ parentID: tool.parentID,
1473
+ messageID: tool.messageID,
1474
+ parentMessageID: extractParentMessageID(e),
1475
+ title: tool.title,
1476
+ summary: tool.summary,
1477
+ agentName: tool.agentName,
1478
+ targetSessionID
1479
+ });
1480
+ if (subtaskID) {
1481
+ if (targetSessionID) {
1482
+ changed = upsertChildDetails(state, subtaskID, {
1483
+ targetSessionID,
1484
+ updatedAt: tool.updatedAt
1485
+ }) || changed;
1486
+ }
1487
+ changed = markChildStatus(
1488
+ state,
1489
+ subtaskID,
1490
+ tool.status,
1491
+ tool.endedAt ?? tool.updatedAt
1492
+ ) || changed;
1493
+ }
1494
+ }
1495
+ }
1496
+ }
1497
+ }
1498
+ if (type === "message.updated") {
1499
+ const assistantModel = extractLatestAssistantModel(e.properties?.info ?? e);
1500
+ if (assistantModel) {
1501
+ changed = setChildModel(
1502
+ state,
1503
+ assistantModel.sessionID,
1504
+ assistantModel.model,
1505
+ assistantModel.updatedAt
1506
+ ) || changed;
1507
+ }
1508
+ const completed = extractCompletedAssistantMessage(e);
1509
+ if (completed) {
1510
+ for (const child of Object.values(state.children)) {
1511
+ if (child.source === "subtask" && child.status === "running" && child.parentID === completed.sessionID && child.messageID === completed.messageID) {
1512
+ changed = markChildStatus(state, child.id, "done") || changed;
1513
+ }
1514
+ }
1515
+ }
1516
+ }
1517
+ if (type === "message.updated" || type === "message.part.updated") {
1518
+ const details = extractChildDetails(e);
1519
+ for (const childID of extractDetailTargetIDs(e)) {
1520
+ if (state.children[childID]) {
1521
+ changed = upsertChildDetails(state, childID, details) || changed;
1522
+ }
1523
+ }
1524
+ }
1525
+ return changed;
1526
+ }
1527
+
1528
+ // src/logs.ts
1529
+ import { readFileSync, statSync } from "fs";
1530
+ function safeRead(reader) {
1531
+ try {
1532
+ return reader();
1533
+ } catch {
1534
+ return void 0;
1535
+ }
1536
+ }
1537
+ function readOpenCodeLogFileIfSmall(path) {
1538
+ const stats = safeRead(() => statSync(path));
1539
+ if (!stats?.isFile() || stats.size > MAX_SYNC_LOG_READ_BYTES) {
1540
+ return void 0;
1541
+ }
1542
+ return safeRead(() => readFileSync(path, "utf8"));
1543
+ }
1544
+
1545
+ // src/render.ts
1546
+ var ansi = {
1547
+ reset: "\x1B[0m",
1548
+ gray: "\x1B[90m",
1549
+ green: "\x1B[32m",
1550
+ yellow: "\x1B[33m",
1551
+ red: "\x1B[31m"
1552
+ };
1553
+ function colorsEnabled() {
1554
+ if (process.env.NO_COLOR) return false;
1555
+ const fromEnv = process.env.OPENCODE_SUBAGENT_STATUSLINE_COLOR;
1556
+ if (fromEnv === "0") return false;
1557
+ return true;
1558
+ }
1559
+ function paint(text, color, enabled) {
1560
+ if (!enabled) return text;
1561
+ return `${color}${text}${ansi.reset}`;
1562
+ }
1563
+ function formatDuration(elapsedMs2) {
1564
+ const totalSeconds = Math.max(0, Math.floor((elapsedMs2 ?? 0) / 1e3));
1565
+ const hours = Math.floor(totalSeconds / 3600);
1566
+ const minutes = Math.floor(totalSeconds % 3600 / 60);
1567
+ const seconds = totalSeconds % 60;
1568
+ if (hours > 0) {
1569
+ return `${String(hours).padStart(2, "0")}:${String(minutes).padStart(2, "0")}:${String(seconds).padStart(2, "0")}`;
1570
+ }
1571
+ return `${String(minutes).padStart(2, "0")}:${String(seconds).padStart(2, "0")}`;
1572
+ }
1573
+ function formatNumber(value) {
1574
+ return Math.max(0, Math.round(value)).toLocaleString("en-US");
1575
+ }
1576
+ function resolveTokenTotal(child) {
1577
+ const total = child.tokens?.total;
1578
+ if (typeof total === "number" && Number.isFinite(total)) {
1579
+ return total;
1580
+ }
1581
+ const inTokens = child.tokens?.input;
1582
+ const outTokens = child.tokens?.output;
1583
+ if (typeof inTokens === "number" || typeof outTokens === "number") {
1584
+ return (inTokens ?? 0) + (outTokens ?? 0);
1585
+ }
1586
+ return void 0;
1587
+ }
1588
+ function formatPercentUsed(percent) {
1589
+ const rounded = Math.round(percent * 10) / 10;
1590
+ if (Math.abs(rounded - Math.round(rounded)) < 0.05) {
1591
+ return `${Math.round(rounded)}% used`;
1592
+ }
1593
+ return `${rounded.toFixed(1)}% used`;
1594
+ }
1595
+ function formatTokenCount(total) {
1596
+ const label = total === 1 ? "token" : "tokens";
1597
+ return `${formatNumber(total)} ${label}`;
1598
+ }
1599
+ function formatContextDetails(child) {
1600
+ const total = resolveTokenTotal(child);
1601
+ const percent = child.tokens?.contextPercent;
1602
+ const hasPercent = typeof percent === "number" && Number.isFinite(percent);
1603
+ const hasTotal = typeof total === "number" && Number.isFinite(total);
1604
+ if (hasTotal && hasPercent) {
1605
+ return `${formatTokenCount(total)} \xB7 ${formatPercentUsed(percent)}`;
1606
+ }
1607
+ if (hasTotal) {
1608
+ return formatTokenCount(total);
1609
+ }
1610
+ if (hasPercent) {
1611
+ return formatPercentUsed(percent);
1612
+ }
1613
+ return void 0;
1614
+ }
1615
+ function formatContext(child) {
1616
+ const details = formatContextDetails(child);
1617
+ if (!details) return "";
1618
+ return `ctx ${details}`;
1619
+ }
1620
+ function childColor(child) {
1621
+ if (child.color === "green") return ansi.green;
1622
+ if (child.color === "red") return ansi.red;
1623
+ return ansi.yellow;
1624
+ }
1625
+ function byPriority(a, b) {
1626
+ const startedDiff = b.startedAt.localeCompare(a.startedAt);
1627
+ if (startedDiff !== 0) return startedDiff;
1628
+ return a.id.localeCompare(b.id);
1629
+ }
1630
+ var RECENT_TERMINAL_VISIBLE_MS = 10 * 60 * 1e3;
1631
+ function collapseSubagentWorkItems(children) {
1632
+ return correlateSubagentWorkItems(children).map(
1633
+ ({ real, proxies }) => proxies.reduce(
1634
+ (current, proxy) => mergeProxyMetadataWithRealExecution(current, proxy),
1635
+ real
1636
+ )
1637
+ );
1638
+ }
1639
+ function isTerminalWorkItem(child) {
1640
+ return child.status === "done" || child.status === "error";
1641
+ }
1642
+ function isVisibleWorkItem(child, nowMs = Date.now()) {
1643
+ if (!isTerminalWorkItem(child)) return true;
1644
+ const endedMs = Date.parse(child.endedAt ?? child.updatedAt);
1645
+ if (Number.isNaN(endedMs)) return false;
1646
+ return nowMs - endedMs <= RECENT_TERMINAL_VISIBLE_MS;
1647
+ }
1648
+ function visibleSubagentWorkItems(children, nowMs = Date.now(), options = {}) {
1649
+ const collapsed = collapseSubagentWorkItems(children);
1650
+ if (options.showCompletedHistory) return collapsed;
1651
+ const visible = collapsed.filter((child) => isVisibleWorkItem(child, nowMs));
1652
+ const hasRunning = visible.some((child) => child.status === "running");
1653
+ const activeMessageIDs = new Set(
1654
+ visible.filter((child) => child.status === "running" && child.messageID).map((child) => child.messageID)
1655
+ );
1656
+ if (!hasRunning) return visible;
1657
+ return visible.filter((child) => {
1658
+ if (child.status === "running") return true;
1659
+ if (!child.messageID) return false;
1660
+ return activeMessageIDs.has(child.messageID);
1661
+ });
1662
+ }
1663
+ function renderStatusLine(state) {
1664
+ const children = visibleSubagentWorkItems(Object.values(state.children)).sort(
1665
+ byPriority
1666
+ );
1667
+ const counts = countRetainedSubagentStatuses({ children: state.children });
1668
+ const totalExecuted = formatNumber(state.totalExecuted ?? 0);
1669
+ const colorOn = colorsEnabled();
1670
+ const aggregate = `\u21B3 ${counts.running} running \xB7 ${counts.done} done \xB7 ${counts.error} error \xB7 \u03A3 ${totalExecuted} total`;
1671
+ if (children.length === 0) return aggregate;
1672
+ const details = children.map((child) => {
1673
+ const context = formatContext(child);
1674
+ const label = [child.title, formatDuration(child.elapsedMs), context].filter((part) => part.length > 0).join(" ");
1675
+ return paint(label, childColor(child), colorOn);
1676
+ }).join(paint(" \xB7 ", ansi.gray, colorOn));
1677
+ return `${aggregate} \xB7 ${details}`;
1678
+ }
1679
+
1680
+ // src/tui-focus.ts
1681
+ function shouldReleaseSidebarListFocus(input) {
1682
+ return input.listFocusModeActive && (input.previousRunningCount ?? 0) > 0 && input.runningCount === 0;
1683
+ }
1684
+ function resolveSiblingSidebarRefocus(input) {
1685
+ const { pendingSidebarRefocus, routeSessionID, children } = input;
1686
+ if (!pendingSidebarRefocus || !routeSessionID || routeSessionID === pendingSidebarRefocus.parentSessionID || routeSessionID === pendingSidebarRefocus.childSessionID) {
1687
+ return void 0;
1688
+ }
1689
+ const sibling = Object.values(children).find(
1690
+ (child) => child.parentID === pendingSidebarRefocus.parentSessionID && child.targetSessionID === routeSessionID
1691
+ );
1692
+ if (!sibling) return void 0;
1693
+ return {
1694
+ childSessionID: routeSessionID,
1695
+ childRowID: sibling.id
1696
+ };
1697
+ }
1698
+ function resolveSidebarReturnFocusAction(input) {
1699
+ const { pendingSidebarRefocus, previousRouteSessionID, routeSessionID } = input;
1700
+ if (!pendingSidebarRefocus || previousRouteSessionID === routeSessionID) {
1701
+ return "none";
1702
+ }
1703
+ if (previousRouteSessionID === pendingSidebarRefocus.childSessionID && routeSessionID === pendingSidebarRefocus.parentSessionID) {
1704
+ return "focus-prompt";
1705
+ }
1706
+ if (routeSessionID !== pendingSidebarRefocus.childSessionID) {
1707
+ return "clear-pending";
1708
+ }
1709
+ return "none";
1710
+ }
1711
+ function focusPromptWithDeferredRetry(tryFocusPrompt, schedule = (callback) => {
1712
+ setTimeout(callback, 0);
1713
+ }) {
1714
+ schedule(() => {
1715
+ if (tryFocusPrompt()) return;
1716
+ schedule(() => {
1717
+ void tryFocusPrompt();
1718
+ });
1719
+ });
1720
+ }
1721
+
1722
+ // src/text-width.ts
1723
+ var ELLIPSIS = "\u2026";
1724
+ function isCombiningCodePoint(codePoint) {
1725
+ 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;
1726
+ }
1727
+ function isWideCodePoint(codePoint) {
1728
+ 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);
1729
+ }
1730
+ function characterWidth(character) {
1731
+ const codePoint = character.codePointAt(0);
1732
+ if (codePoint === void 0) return 0;
1733
+ if (codePoint === 0 || codePoint < 32 || codePoint >= 127 && codePoint < 160) {
1734
+ return 0;
1735
+ }
1736
+ if (codePoint === 8205 || isCombiningCodePoint(codePoint)) return 0;
1737
+ return isWideCodePoint(codePoint) ? 2 : 1;
1738
+ }
1739
+ function textColumns(value) {
1740
+ let columns = 0;
1741
+ for (const character of value) columns += characterWidth(character);
1742
+ return columns;
1743
+ }
1744
+ function takeColumns(value, maxColumns) {
1745
+ if (maxColumns <= 0) return "";
1746
+ let columns = 0;
1747
+ let result = "";
1748
+ for (const character of value) {
1749
+ const width = characterWidth(character);
1750
+ if (columns + width > maxColumns) break;
1751
+ columns += width;
1752
+ result += character;
1753
+ }
1754
+ return result;
1755
+ }
1756
+ function truncateToColumns(value, maxColumns) {
1757
+ if (maxColumns <= 0) return "";
1758
+ if (textColumns(value) <= maxColumns) return value;
1759
+ if (maxColumns <= textColumns(ELLIPSIS)) return ELLIPSIS;
1760
+ const prefix = takeColumns(
1761
+ value,
1762
+ maxColumns - textColumns(ELLIPSIS)
1763
+ ).trimEnd();
1764
+ return `${prefix}${ELLIPSIS}`;
1765
+ }
1766
+
1767
+ // src/tui-commands.ts
1768
+ var TOGGLE_SECTION_COMMAND = "subagent-statusline.toggle-sidebar-section";
1769
+ var FOCUS_SIDEBAR_LIST_COMMAND = "subagent-statusline.focus-sidebar-list";
1770
+ var TOGGLE_COMPLETED_HISTORY_COMMAND = "subagent-statusline.toggle-completed-history";
1771
+ var COMMAND_CATEGORY = "Subagents";
1772
+ var SHARED_COMMAND_METADATA = {
1773
+ toggle: {
1774
+ id: TOGGLE_SECTION_COMMAND,
1775
+ title: "Subagents: Toggle sidebar section",
1776
+ description: "Toggle the entire subagent sidebar section",
1777
+ category: COMMAND_CATEGORY
1778
+ },
1779
+ focus: {
1780
+ id: FOCUS_SIDEBAR_LIST_COMMAND,
1781
+ title: "Subagents: Focus sidebar list",
1782
+ description: "Focus the subagent sidebar list for keyboard navigation",
1783
+ category: COMMAND_CATEGORY
1784
+ },
1785
+ toggleCompletedHistory: {
1786
+ id: TOGGLE_COMPLETED_HISTORY_COMMAND,
1787
+ title: "Subagents: Toggle completed history",
1788
+ description: "Toggle retained completed rows in the subagent sidebar. Shortcut: c while the sidebar list is focused.",
1789
+ category: COMMAND_CATEGORY
1790
+ }
1791
+ };
1792
+ function createToggleSelectionTitle(sectionEnabled) {
1793
+ return sectionEnabled ? "Subagents: Disable sidebar section" : "Subagents: Enable sidebar section";
1794
+ }
1795
+ function createCompositeDispose(disposers) {
1796
+ let disposed = false;
1797
+ return () => {
1798
+ if (disposed) return;
1799
+ disposed = true;
1800
+ for (const dispose of disposers) {
1801
+ try {
1802
+ dispose();
1803
+ } catch {
1804
+ }
1805
+ }
1806
+ };
1807
+ }
1808
+ function registerSubagentCommands({
1809
+ api,
1810
+ sectionEnabled,
1811
+ toggleSection,
1812
+ focusSidebarList,
1813
+ toggleCompletedHistory
1814
+ }) {
1815
+ const disposers = [];
1816
+ if (api.keymap?.registerLayer) {
1817
+ disposers.push(
1818
+ api.keymap.registerLayer({
1819
+ commands: [
1820
+ {
1821
+ name: SHARED_COMMAND_METADATA.toggle.id,
1822
+ title: SHARED_COMMAND_METADATA.toggle.title,
1823
+ description: SHARED_COMMAND_METADATA.toggle.description,
1824
+ category: SHARED_COMMAND_METADATA.toggle.category,
1825
+ run: () => toggleSection(!sectionEnabled())
1826
+ },
1827
+ {
1828
+ name: SHARED_COMMAND_METADATA.focus.id,
1829
+ title: SHARED_COMMAND_METADATA.focus.title,
1830
+ description: SHARED_COMMAND_METADATA.focus.description,
1831
+ category: SHARED_COMMAND_METADATA.focus.category,
1832
+ run: focusSidebarList
1833
+ },
1834
+ {
1835
+ name: SHARED_COMMAND_METADATA.toggleCompletedHistory.id,
1836
+ title: SHARED_COMMAND_METADATA.toggleCompletedHistory.title,
1837
+ description: SHARED_COMMAND_METADATA.toggleCompletedHistory.description,
1838
+ category: SHARED_COMMAND_METADATA.toggleCompletedHistory.category,
1839
+ run: toggleCompletedHistory
1840
+ }
1841
+ ],
1842
+ bindings: [
1843
+ {
1844
+ key: "alt+b",
1845
+ cmd: SHARED_COMMAND_METADATA.focus.id
1846
+ }
1847
+ ]
1848
+ })
1849
+ );
1850
+ }
1851
+ if (api.command?.register) {
1852
+ disposers.push(
1853
+ api.command.register(() => [
1854
+ {
1855
+ title: createToggleSelectionTitle(sectionEnabled()),
1856
+ value: SHARED_COMMAND_METADATA.toggle.id,
1857
+ description: SHARED_COMMAND_METADATA.toggle.description,
1858
+ category: SHARED_COMMAND_METADATA.toggle.category,
1859
+ onSelect: () => toggleSection(!sectionEnabled())
1860
+ },
1861
+ {
1862
+ title: SHARED_COMMAND_METADATA.focus.title,
1863
+ value: SHARED_COMMAND_METADATA.focus.id,
1864
+ description: SHARED_COMMAND_METADATA.focus.description,
1865
+ category: SHARED_COMMAND_METADATA.focus.category,
1866
+ keybind: "alt+b",
1867
+ onSelect: focusSidebarList
1868
+ },
1869
+ {
1870
+ title: SHARED_COMMAND_METADATA.toggleCompletedHistory.title,
1871
+ value: SHARED_COMMAND_METADATA.toggleCompletedHistory.id,
1872
+ description: SHARED_COMMAND_METADATA.toggleCompletedHistory.description,
1873
+ category: SHARED_COMMAND_METADATA.toggleCompletedHistory.category,
1874
+ onSelect: toggleCompletedHistory
1875
+ }
1876
+ ])
1877
+ );
1878
+ }
1879
+ return createCompositeDispose(disposers);
1880
+ }
1881
+
1882
+ // src/tui-view.tsx
1883
+ import { use as _$use } from "@opentui/solid";
1884
+ import { createTextNode as _$createTextNode } from "@opentui/solid";
1885
+ import { effect as _$effect } from "@opentui/solid";
1886
+ import { createComponent as _$createComponent } from "@opentui/solid";
1887
+ import { insertNode as _$insertNode } from "@opentui/solid";
1888
+ import { insert as _$insert } from "@opentui/solid";
1889
+ import { memo as _$memo } from "@opentui/solid";
1890
+ import { setProp as _$setProp } from "@opentui/solid";
1891
+ import { createElement as _$createElement } from "@opentui/solid";
1892
+ import { createRequire } from "module";
1893
+ import { For, Show, createEffect, createMemo, createSignal, onCleanup } from "solid-js";
1894
+
1895
+ // src/i18n.ts
1896
+ var translations = {
1897
+ es: {
1898
+ subagents: "Subagentes",
1899
+ interrupted: "Interrumpido",
1900
+ stale: "El estado puede estar desactualizado",
1901
+ usage: "Uso de entrada + salida",
1902
+ unavailable: "Lista de subagentes no disponible"
1903
+ },
1904
+ en: {
1905
+ subagents: "Subagents",
1906
+ interrupted: "Interrupted",
1907
+ stale: "Status may be stale",
1908
+ usage: "Input + output usage",
1909
+ unavailable: "Subagent list unavailable"
1910
+ }
1911
+ };
1912
+ var _cachedLocale = null;
1913
+ function detectSystemLocale() {
1914
+ const envLang = process.env.LANG ?? process.env.LC_ALL ?? process.env.LANGUAGE ?? Intl.DateTimeFormat().resolvedOptions().locale;
1915
+ const lang = envLang.toLowerCase();
1916
+ if (lang.startsWith("es")) return "es";
1917
+ return "en";
1918
+ }
1919
+ function getLocale() {
1920
+ if (_cachedLocale === null) {
1921
+ _cachedLocale = detectSystemLocale();
1922
+ }
1923
+ return _cachedLocale;
1924
+ }
1925
+ function t(key) {
1926
+ const locale = getLocale();
1927
+ const translation = translations[locale][key];
1928
+ if (translation === void 0) {
1929
+ const fallback = translations.en[key];
1930
+ if (fallback === void 0) {
1931
+ throw new Error(`Missing translation key: ${key}`);
1932
+ }
1933
+ return fallback;
1934
+ }
1935
+ return translation;
1936
+ }
1937
+
1938
+ // src/tui-view.tsx
1939
+ var FALLBACK_SIDEBAR_WIDTH = 34;
1940
+ var MIN_ROW_WIDTH = 24;
1941
+ var MIN_LABEL_WIDTH = 8;
1942
+ var CLOCK_ICON = "\uF017";
1943
+ var TOKEN_ICON = "\uF51E";
1944
+ var SIDEBAR_ARROW_EXPANDED = "\u25BC";
1945
+ var SIDEBAR_ARROW_COLLAPSED = "\u25B6";
1946
+ var SUBAGENTS_MAX_VISIBLE_ROWS = 5;
1947
+ var SUBAGENTS_RUNNING_ROW_HEIGHT = 3;
1948
+ var SUBAGENTS_TERMINAL_ROW_HEIGHT = 2;
1949
+ var SUBAGENTS_MODEL_ROW_HEIGHT = 1;
1950
+ var SUBAGENTS_ROW_GAP = 0;
1951
+ var SUBAGENTS_ROW_MARKER_WIDTH = 4;
1952
+ 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;
1953
+ var INACTIVE_SUBAGENT_OPACITY = 0.65;
1954
+ var packageRequire = createRequire(import.meta.url);
1955
+ function readPluginVersion() {
1956
+ try {
1957
+ const metadata = packageRequire("../package.json");
1958
+ return typeof metadata.version === "string" && metadata.version.length > 0 ? metadata.version : void 0;
1959
+ } catch {
1960
+ return void 0;
1961
+ }
1962
+ }
1963
+ var PLUGIN_VERSION = readPluginVersion();
1964
+ var SIDEBAR_SCROLL_RESTORE_FRAME_BUDGET = 2;
1965
+ var registerSidebar = /* @__PURE__ */ Symbol("registerSidebar");
1966
+ function createSidebarViewController() {
1967
+ const sidebarScrollRegistrations = /* @__PURE__ */ new Set();
1968
+ const sidebarListFocusRegistrations = /* @__PURE__ */ new Set();
1969
+ const sidebarCompletedHistoryRegistrations = /* @__PURE__ */ new Set();
1970
+ let disposed = false;
1971
+ const controller = {
1972
+ [registerSidebar]({
1973
+ scroll,
1974
+ focus,
1975
+ history
1976
+ }) {
1977
+ if (disposed) return () => {
1978
+ };
1979
+ sidebarScrollRegistrations.add(scroll);
1980
+ sidebarListFocusRegistrations.add(focus);
1981
+ sidebarCompletedHistoryRegistrations.add(history);
1982
+ return () => {
1983
+ sidebarScrollRegistrations.delete(scroll);
1984
+ sidebarListFocusRegistrations.delete(focus);
1985
+ sidebarCompletedHistoryRegistrations.delete(history);
1986
+ };
1987
+ },
1988
+ snapshotScroll() {
1989
+ for (const registration of sidebarScrollRegistrations) {
1990
+ const scrollbox = registration.getScrollbox();
1991
+ if (!scrollbox) continue;
1992
+ registration.offsetTop = clampedScrollTop(scrollbox, scrollbox.scrollTop);
1993
+ registration.anchor = registration.getAnchor();
1994
+ registration.restoreFramesRemaining = SIDEBAR_SCROLL_RESTORE_FRAME_BUDGET;
1995
+ }
1996
+ },
1997
+ focusList(preferredChildID) {
1998
+ for (const registration of [...sidebarListFocusRegistrations].reverse()) {
1999
+ if (registration.focusList(preferredChildID)) return true;
2000
+ }
2001
+ return false;
2002
+ },
2003
+ blurList() {
2004
+ for (const registration of [...sidebarListFocusRegistrations].reverse()) {
2005
+ if (registration.blurList()) return true;
2006
+ }
2007
+ return false;
2008
+ },
2009
+ isListFocused() {
2010
+ return [...sidebarListFocusRegistrations].some((registration) => registration.isListFocusModeActive());
2011
+ },
2012
+ toggleCompletedHistory() {
2013
+ for (const registration of [...sidebarCompletedHistoryRegistrations].reverse()) {
2014
+ if (registration.toggleCompletedHistory()) return true;
2015
+ }
2016
+ return false;
2017
+ },
2018
+ target() {
2019
+ for (const registration of [...sidebarListFocusRegistrations].reverse()) {
2020
+ const target = registration.target();
2021
+ if (target) return target;
2022
+ }
2023
+ return void 0;
2024
+ },
2025
+ dispose() {
2026
+ if (disposed) return;
2027
+ disposed = true;
2028
+ for (const registration of sidebarListFocusRegistrations) registration.blurList();
2029
+ sidebarScrollRegistrations.clear();
2030
+ sidebarListFocusRegistrations.clear();
2031
+ sidebarCompletedHistoryRegistrations.clear();
2032
+ }
2033
+ };
2034
+ return controller;
2035
+ }
2036
+ function maxScrollTop(scrollbox) {
2037
+ return Math.max(0, scrollbox.scrollHeight - scrollbox.viewport.height);
2038
+ }
2039
+ function clampedScrollTop(scrollbox, value) {
2040
+ return Math.max(0, Math.min(value, maxScrollTop(scrollbox)));
2041
+ }
2042
+ function resolveSidebarAnchorScrollTop(input) {
2043
+ if (!input.expanded || !input.anchor || input.anchor.childIDs.length === 0) {
2044
+ return {
2045
+ matched: false
2046
+ };
2047
+ }
2048
+ let top = input.leadingHeight;
2049
+ const rowTops = /* @__PURE__ */ new Map();
2050
+ for (const row of input.rows) {
2051
+ rowTops.set(row.id, top);
2052
+ top += row.height + SUBAGENTS_ROW_GAP;
2053
+ }
2054
+ for (const [index, childID] of input.anchor.childIDs.entries()) {
2055
+ const rowTop = rowTops.get(childID);
2056
+ if (rowTop === void 0) continue;
2057
+ const desiredTop = rowTop + (index === 0 ? input.anchor.intraRowOffset : 0);
2058
+ const maxTop = Math.max(0, input.scrollHeight - input.viewportHeight);
2059
+ const nextTop = Math.max(0, Math.min(desiredTop, maxTop));
2060
+ return {
2061
+ matched: true,
2062
+ offsetTop: nextTop,
2063
+ scrollTop: input.scrollTop !== nextTop ? nextTop : void 0
2064
+ };
2065
+ }
2066
+ return {
2067
+ matched: false
2068
+ };
2069
+ }
2070
+ function preservedSidebarAnchorScrollTop(input) {
2071
+ return resolveSidebarAnchorScrollTop({
2072
+ ...input,
2073
+ leadingHeight: input.leadingHeight ?? 0
2074
+ }).scrollTop;
2075
+ }
2076
+ function preservedSidebarScrollTop(input) {
2077
+ if (!input.expanded) return void 0;
2078
+ const anchorTop = resolveSidebarAnchorScrollTop({
2079
+ expanded: input.expanded,
2080
+ anchor: input.anchor,
2081
+ rows: input.rows ?? [],
2082
+ leadingHeight: input.leadingHeight ?? 0,
2083
+ scrollTop: input.scrollTop,
2084
+ scrollHeight: input.scrollHeight,
2085
+ viewportHeight: input.viewportHeight
2086
+ });
2087
+ if (anchorTop.matched) return anchorTop.scrollTop;
2088
+ const maxTop = Math.max(0, input.scrollHeight - input.viewportHeight);
2089
+ const top = Math.max(0, Math.min(input.offsetTop, maxTop));
2090
+ return top > 0 && input.scrollTop !== top ? top : void 0;
2091
+ }
2092
+ function elapsedMs(child, nowMs) {
2093
+ if (child.status !== "running") {
2094
+ return child.elapsedMs ?? 0;
2095
+ }
2096
+ const started = Date.parse(child.startedAt);
2097
+ if (Number.isNaN(started)) return child.elapsedMs ?? 0;
2098
+ return Math.max(0, nowMs - started);
2099
+ }
2100
+ function taskStatusMarker(status) {
2101
+ if (status === "done") return "[\u2713]";
2102
+ if (status === "error") return "[x]";
2103
+ return "[ ]";
2104
+ }
2105
+ function statusColor2(status, theme) {
2106
+ if (status === "done") return theme.success;
2107
+ if (status === "error") return theme.error;
2108
+ return theme.warning;
2109
+ }
2110
+ function isSessionTarget(value) {
2111
+ return typeof value === "string" && value.startsWith("ses_");
2112
+ }
2113
+ function resolveChildTargetSessionID(child) {
2114
+ if (isSessionTarget(child.targetSessionID)) {
2115
+ return child.targetSessionID;
2116
+ }
2117
+ if (child.id.startsWith("ses_")) {
2118
+ return child.id;
2119
+ }
2120
+ return void 0;
2121
+ }
2122
+ function resolveSyntheticTargetFromHydratedState(state, synthetic) {
2123
+ const messageMatches = Object.values(state.children).filter((candidate) => candidate.id.startsWith("ses_") && candidate.parentID === synthetic.parentID && synthetic.messageID && candidate.messageID === synthetic.messageID);
2124
+ if (messageMatches.length === 1) return messageMatches[0].id;
2125
+ const parentMatches = Object.values(state.children).filter((candidate) => candidate.id.startsWith("ses_") && candidate.parentID === synthetic.parentID);
2126
+ if (parentMatches.length === 1) return parentMatches[0].id;
2127
+ return void 0;
2128
+ }
2129
+ function ellipsize(value, maxColumns) {
2130
+ return truncateToColumns(value, maxColumns);
2131
+ }
2132
+ function splitParentheticalTitle(title) {
2133
+ const match = title.match(/^(.*?)\s*(\([^)]*\))\s*$/);
2134
+ if (!match) return {
2135
+ label: title
2136
+ };
2137
+ const label = match[1]?.trim();
2138
+ const parenthetical = match[2]?.trim();
2139
+ if (!label || !parenthetical) return {
2140
+ label: title
2141
+ };
2142
+ return {
2143
+ label,
2144
+ parenthetical
2145
+ };
2146
+ }
2147
+ function childParenthetical(child) {
2148
+ if (child.agentName?.trim()) return `(${child.agentName.trim()})`;
2149
+ const primary = splitParentheticalTitle(childPrimaryText(child));
2150
+ if (primary.parenthetical) return primary.parenthetical;
2151
+ return splitParentheticalTitle(child.title).parenthetical;
2152
+ }
2153
+ function formatSecondaryLine(continuation, parenthetical, width) {
2154
+ if (!continuation) return parenthetical;
2155
+ if (!parenthetical) return continuation;
2156
+ const parentheticalWidth = Math.min(textColumns(parenthetical), width);
2157
+ const continuationWidth = width - parentheticalWidth - 1;
2158
+ if (continuationWidth >= MIN_LABEL_WIDTH) {
2159
+ return `${ellipsize(continuation, continuationWidth)} ${ellipsize(parenthetical, parentheticalWidth)}`;
2160
+ }
2161
+ return ellipsize(parenthetical, width);
2162
+ }
2163
+ function childPrimaryText(child) {
2164
+ return child.summary?.trim() || child.title;
2165
+ }
2166
+ function resolveTokenTotal2(child) {
2167
+ const total = child.tokens?.total;
2168
+ if (typeof total === "number" && Number.isFinite(total)) {
2169
+ return total;
2170
+ }
2171
+ const input = child.tokens?.input;
2172
+ const output = child.tokens?.output;
2173
+ if (typeof input === "number" || typeof output === "number") {
2174
+ return Math.max(0, (input ?? 0) + (output ?? 0));
2175
+ }
2176
+ return void 0;
2177
+ }
2178
+ function formatCompactTokenCount(total) {
2179
+ const value = Math.max(0, total);
2180
+ if (value >= 1e6) return `${(value / 1e6).toFixed(1)}M ctx`;
2181
+ if (value >= 1e3) return `${(value / 1e3).toFixed(1)}k ctx`;
2182
+ return `${Math.round(value)} ctx`;
2183
+ }
2184
+ function formatCompactPercent(percent) {
2185
+ return `${Math.max(0, Math.round(percent))}%`;
2186
+ }
2187
+ function contextVariants(child, usageLabel) {
2188
+ const total = resolveTokenTotal2(child);
2189
+ const percent = usageLabel ? void 0 : child.tokens?.contextPercent;
2190
+ const hasTotal = typeof total === "number" && Number.isFinite(total);
2191
+ const hasPercent = typeof percent === "number" && Number.isFinite(percent);
2192
+ if (!hasTotal && !hasPercent) return [""];
2193
+ const formattedTokens = hasTotal ? formatCompactTokenCount(total) : "";
2194
+ const tokenPart = usageLabel ? formattedTokens.replace(/ ctx$/, "") : formattedTokens;
2195
+ const percentPart = hasPercent ? formatCompactPercent(percent) : "";
2196
+ if (tokenPart && percentPart) {
2197
+ return [`${tokenPart} ${percentPart}`, percentPart, tokenPart, ""];
2198
+ }
2199
+ return [tokenPart || percentPart, ""];
2200
+ }
2201
+ function rowWidthBudget(sidebarWidth) {
2202
+ const width = sidebarWidth ?? FALLBACK_SIDEBAR_WIDTH;
2203
+ const innerWidth = width - 4;
2204
+ return Math.max(MIN_ROW_WIDTH, Math.min(innerWidth, 52));
2205
+ }
2206
+ function wrapCompactText(value, width, maxLines) {
2207
+ const normalized = value.replace(/\s+/g, " ").trim();
2208
+ if (!normalized) return [""];
2209
+ const lines = [];
2210
+ let remaining = normalized;
2211
+ while (textColumns(remaining) > width && lines.length < maxLines - 1) {
2212
+ const probe = takeColumns(remaining, width + 1);
2213
+ const breakAt = probe.lastIndexOf(" ");
2214
+ const breakPrefix = breakAt >= 0 ? probe.slice(0, breakAt) : "";
2215
+ const fit = takeColumns(remaining, width);
2216
+ const take = breakAt >= 0 && textColumns(breakPrefix) >= MIN_LABEL_WIDTH && textColumns(breakPrefix) <= width ? breakAt : fit.length;
2217
+ if (take <= 0) break;
2218
+ lines.push(remaining.slice(0, take).trimEnd());
2219
+ remaining = remaining.slice(take).trimStart();
2220
+ }
2221
+ lines.push(lines.length === maxLines - 1 ? ellipsize(remaining, Math.max(1, width)) : remaining);
2222
+ return lines;
2223
+ }
2224
+ function formatChildRowLine(input) {
2225
+ const elapsed = formatDuration(elapsedMs(input.child, input.nowMs));
2226
+ const width = Math.max(MIN_ROW_WIDTH, rowWidthBudget(input.sidebarWidth) - (input.reservedWidth ?? 0));
2227
+ const title = splitParentheticalTitle(childPrimaryText(input.child));
2228
+ const parenthetical = childParenthetical(input.child);
2229
+ for (const meta of contextVariants(input.child, input.usageLabel)) {
2230
+ const detailChars = 2 + textColumns(elapsed) + (meta ? 3 + textColumns(meta) : 0);
2231
+ const labelBudget = Math.min(width - 2, width - Math.max(0, detailChars - width));
2232
+ if (labelBudget >= MIN_LABEL_WIDTH || textColumns(meta) === 0) {
2233
+ const labelLines2 = wrapCompactText(title.label, Math.max(1, labelBudget), 2);
2234
+ return {
2235
+ labelLines: labelLines2,
2236
+ secondaryLine: formatSecondaryLine(labelLines2[1], parenthetical, Math.max(1, labelBudget)),
2237
+ elapsed,
2238
+ meta
2239
+ };
2240
+ }
2241
+ }
2242
+ const labelLines = wrapCompactText(title.label, MIN_LABEL_WIDTH, 2);
2243
+ return {
2244
+ labelLines,
2245
+ secondaryLine: formatSecondaryLine(labelLines[1], parenthetical, MIN_LABEL_WIDTH),
2246
+ elapsed,
2247
+ meta: ""
2248
+ };
2249
+ }
2250
+ function formatTerminalChildRowLine(input) {
2251
+ const elapsed = formatDuration(elapsedMs(input.child, input.nowMs));
2252
+ const width = Math.max(MIN_ROW_WIDTH, rowWidthBudget(input.sidebarWidth));
2253
+ const title = splitParentheticalTitle(childPrimaryText(input.child));
2254
+ const parenthetical = childParenthetical(input.child);
2255
+ const labelSource = parenthetical ? `${title.label} ${parenthetical}` : title.label;
2256
+ const context = contextVariants(input.child, input.usageLabel).find((variant) => variant.length > 0);
2257
+ return {
2258
+ label: ellipsize(labelSource, Math.max(1, width - (input.reservedWidth ?? 0))),
2259
+ meta: context ? `${elapsed} ${context}` : elapsed
2260
+ };
2261
+ }
2262
+ function subagentRowHeight(input) {
2263
+ const hintHeight = input.childHint ? 1 : 0;
2264
+ const modelHeight = input.child.model?.variant ? SUBAGENTS_MODEL_ROW_HEIGHT : 0;
2265
+ if (input.child.status !== "running") {
2266
+ return SUBAGENTS_TERMINAL_ROW_HEIGHT + modelHeight + hintHeight;
2267
+ }
2268
+ const line = formatChildRowLine(input);
2269
+ return (line.secondaryLine ? SUBAGENTS_RUNNING_ROW_HEIGHT : SUBAGENTS_RUNNING_ROW_HEIGHT - 1) + modelHeight + hintHeight;
2270
+ }
2271
+ function resolveTuiSubagentSnapshot(input) {
2272
+ const allChildren = Object.values(input.state.children);
2273
+ const options = {
2274
+ showCompletedHistory: input.showCompletedHistory
2275
+ };
2276
+ const nowMs = input.nowMs ?? Date.now();
2277
+ const ownChildren = input.sessionID ? allChildren.filter((child) => child.parentID === input.sessionID) : allChildren;
2278
+ const ownVisibleChildren = visibleSubagentWorkItems(ownChildren, nowMs, options).sort(byPriority);
2279
+ const totalExecuted = input.sessionID ? countCountedSubagentExecutions({
2280
+ children: allChildren,
2281
+ countedChildIDs: input.state.countedChildIDs,
2282
+ parentSessionID: input.sessionID
2283
+ }) : countHistoricalSubagentExecutions({
2284
+ children: allChildren
2285
+ });
2286
+ return {
2287
+ visibleChildren: ownVisibleChildren,
2288
+ visibleCounts: countRetainedSubagentStatuses({
2289
+ children: allChildren,
2290
+ parentSessionID: input.sessionID
2291
+ }),
2292
+ totalExecuted,
2293
+ showingOtherSessions: false
2294
+ };
2295
+ }
2296
+ function resolveSidebarSubagentSnapshot(input) {
2297
+ return resolveTuiSubagentSnapshot(input);
2298
+ }
2299
+ function SidebarSubagents(props) {
2300
+ const [showCompletedHistory, setShowCompletedHistory] = createSignal(props.restoreFromChild?.showCompletedHistory ?? false);
2301
+ const completedHistoryOptions = () => ({
2302
+ showCompletedHistory: showCompletedHistory()
2303
+ });
2304
+ const snapshot = createMemo(() => resolveSidebarSubagentSnapshot({
2305
+ state: props.state(),
2306
+ sessionID: props.sessionID,
2307
+ nowMs: props.nowMs(),
2308
+ ...completedHistoryOptions()
2309
+ }));
2310
+ const visibleChildren = createMemo(() => snapshot().visibleChildren);
2311
+ const counts = createMemo(() => snapshot().visibleCounts);
2312
+ const totalExecuted = createMemo(() => snapshot().totalExecuted);
2313
+ const visibleChildIDs = createMemo(() => visibleChildren().map((child) => child.id));
2314
+ const [selectedChildID, setSelectedChildID] = createSignal(props.restoreFromChild?.childRowID);
2315
+ let restoreChildRowID = props.restoreFromChild?.childRowID;
2316
+ const [mouseDownChildID, setMouseDownChildID] = createSignal();
2317
+ const [listFocused, setListFocused] = createSignal(false);
2318
+ const [listFocusModeActive, setListFocusModeActive] = createSignal(false);
2319
+ 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("|"));
2320
+ const listHeight = createMemo(() => {
2321
+ const nowMs = props.nowMs();
2322
+ const sidebarWidth = props.sidebarWidth?.();
2323
+ const contentHeight = visibleChildren().reduce((height, child) => height + subagentRowHeight({
2324
+ child,
2325
+ nowMs,
2326
+ sidebarWidth,
2327
+ reservedWidth: SUBAGENTS_ROW_MARKER_WIDTH,
2328
+ usageLabel: props.usageLabel,
2329
+ childHint: props.childHint?.(child.id)
2330
+ }), 0) + Math.max(0, visibleChildren().length - 1) * SUBAGENTS_ROW_GAP;
2331
+ return Math.max(1, Math.min(SUBAGENTS_MAX_LIST_HEIGHT, contentHeight));
2332
+ });
2333
+ let listContainer;
2334
+ let scrollbox;
2335
+ const scrollRegistration = {
2336
+ getScrollbox: () => scrollbox,
2337
+ getAnchor: () => currentSidebarScrollAnchor(),
2338
+ getRows: () => rowLayouts(),
2339
+ getLeadingHeight: () => 0,
2340
+ offsetTop: 0,
2341
+ restoreFramesRemaining: 0
2342
+ };
2343
+ const focusRegistration = {
2344
+ target: () => props.strictInput && visibleChildIDs().length === 0 ? void 0 : listContainer,
2345
+ focusList: (preferredChildID) => {
2346
+ if (!listContainer || props.strictInput && (listContainer.isDestroyed || !listContainer.visible || visibleChildIDs().length === 0)) return false;
2347
+ const ids = visibleChildIDs();
2348
+ if (preferredChildID && ids.includes(preferredChildID)) {
2349
+ setSelectedChildID(preferredChildID);
2350
+ } else if (!selectedChildID() && ids[0]) {
2351
+ setSelectedChildID(ids[0]);
2352
+ }
2353
+ listContainer.focus();
2354
+ setListFocused(true);
2355
+ setListFocusModeActive(true);
2356
+ return true;
2357
+ },
2358
+ blurList: () => {
2359
+ if (!listFocused() && !listFocusModeActive()) return false;
2360
+ listContainer?.blur();
2361
+ setListFocused(false);
2362
+ setListFocusModeActive(false);
2363
+ return true;
2364
+ },
2365
+ isListFocusModeActive: () => listFocusModeActive()
2366
+ };
2367
+ let previousRunningCount;
2368
+ createEffect(() => {
2369
+ const runningCount = counts().running;
2370
+ const shouldReleaseFocus = shouldReleaseSidebarListFocus({
2371
+ previousRunningCount,
2372
+ runningCount,
2373
+ listFocusModeActive: listFocusModeActive()
2374
+ });
2375
+ previousRunningCount = runningCount;
2376
+ if (!shouldReleaseFocus) return;
2377
+ focusRegistration.blurList();
2378
+ props.onReturnFocus();
2379
+ });
2380
+ const completedHistoryRegistration = {
2381
+ toggleCompletedHistory: () => {
2382
+ setShowCompletedHistory((current) => !current);
2383
+ return true;
2384
+ }
2385
+ };
2386
+ onCleanup(props.controller[registerSidebar]({
2387
+ scroll: scrollRegistration,
2388
+ focus: focusRegistration,
2389
+ history: completedHistoryRegistration
2390
+ }));
2391
+ createEffect(() => {
2392
+ const ids = visibleChildIDs();
2393
+ const current = selectedChildID();
2394
+ if (ids.length === 0) {
2395
+ if (current) setSelectedChildID(void 0);
2396
+ return;
2397
+ }
2398
+ if (!current || !ids.includes(current)) setSelectedChildID(ids[0]);
2399
+ });
2400
+ const refreshListFocused = () => {
2401
+ if (listFocused() && !listContainer) {
2402
+ setListFocused(false);
2403
+ return;
2404
+ }
2405
+ const focused = Boolean(listContainer?.focused || listContainer?.hasFocusedDescendant);
2406
+ if (!focused && listFocused()) setListFocused(false);
2407
+ };
2408
+ const rowTopForIndex = (index) => {
2409
+ let top = 0;
2410
+ const nowMs = props.nowMs();
2411
+ const sidebarWidth = props.sidebarWidth?.();
2412
+ for (let i = 0; i < index; i += 1) {
2413
+ const child = visibleChildren()[i];
2414
+ if (child) {
2415
+ top += subagentRowHeight({
2416
+ child,
2417
+ nowMs,
2418
+ sidebarWidth,
2419
+ reservedWidth: SUBAGENTS_ROW_MARKER_WIDTH,
2420
+ usageLabel: props.usageLabel,
2421
+ childHint: props.childHint?.(child.id)
2422
+ }) + SUBAGENTS_ROW_GAP;
2423
+ }
2424
+ }
2425
+ return top;
2426
+ };
2427
+ const rowLayouts = () => {
2428
+ const nowMs = props.nowMs();
2429
+ const sidebarWidth = props.sidebarWidth?.();
2430
+ return visibleChildren().map((child) => ({
2431
+ id: child.id,
2432
+ height: subagentRowHeight({
2433
+ child,
2434
+ nowMs,
2435
+ sidebarWidth,
2436
+ reservedWidth: SUBAGENTS_ROW_MARKER_WIDTH,
2437
+ usageLabel: props.usageLabel,
2438
+ childHint: props.childHint?.(child.id)
2439
+ })
2440
+ }));
2441
+ };
2442
+ const currentSidebarScrollAnchor = () => {
2443
+ if (!scrollbox) return void 0;
2444
+ const rows = rowLayouts();
2445
+ if (rows.length === 0) return void 0;
2446
+ const viewportTop = clampedScrollTop(scrollbox, scrollbox.scrollTop);
2447
+ let top = 0;
2448
+ for (let index = 0; index < rows.length; index += 1) {
2449
+ const row = rows[index];
2450
+ if (!row) continue;
2451
+ const rowBottom = top + row.height;
2452
+ if (rowBottom > viewportTop) {
2453
+ return {
2454
+ childIDs: rows.slice(index).map((candidate) => candidate.id),
2455
+ intraRowOffset: Math.max(0, viewportTop - top)
2456
+ };
2457
+ }
2458
+ top = rowBottom + SUBAGENTS_ROW_GAP;
2459
+ }
2460
+ const lastRow = rows[rows.length - 1];
2461
+ return lastRow ? {
2462
+ childIDs: [lastRow.id],
2463
+ intraRowOffset: 0
2464
+ } : void 0;
2465
+ };
2466
+ const scrollChildIntoView = (childID) => {
2467
+ if (!scrollbox) return;
2468
+ const selectedIndex = visibleChildIDs().findIndex((id) => id === childID);
2469
+ if (selectedIndex < 0) return;
2470
+ const selectedChild = visibleChildren()[selectedIndex];
2471
+ if (!selectedChild) return;
2472
+ const rowTop = rowTopForIndex(selectedIndex);
2473
+ const rowBottom = rowTop + subagentRowHeight({
2474
+ child: selectedChild,
2475
+ nowMs: props.nowMs(),
2476
+ sidebarWidth: props.sidebarWidth?.(),
2477
+ reservedWidth: SUBAGENTS_ROW_MARKER_WIDTH,
2478
+ usageLabel: props.usageLabel,
2479
+ childHint: props.childHint?.(selectedChild.id)
2480
+ });
2481
+ const viewportTop = scrollbox.scrollTop;
2482
+ const viewportBottom = viewportTop + listHeight();
2483
+ if (rowTop < viewportTop) {
2484
+ const nextTop = clampedScrollTop(scrollbox, rowTop);
2485
+ scrollRegistration.offsetTop = nextTop;
2486
+ scrollbox.scrollTop = nextTop;
2487
+ } else if (rowBottom > viewportBottom) {
2488
+ const nextTop = clampedScrollTop(scrollbox, rowBottom - listHeight());
2489
+ scrollRegistration.offsetTop = nextTop;
2490
+ scrollbox.scrollTop = nextTop;
2491
+ }
2492
+ };
2493
+ const scrollSelectedChildIntoView = () => {
2494
+ if (!listFocusModeActive()) return;
2495
+ scrollChildIntoView(selectedChildID());
2496
+ };
2497
+ const moveSelection = (delta) => {
2498
+ const ids = visibleChildIDs();
2499
+ if (ids.length === 0) return;
2500
+ const currentIndex = ids.findIndex((id) => id === selectedChildID());
2501
+ const fallbackIndex = delta > 0 ? 0 : ids.length - 1;
2502
+ const nextIndex = Math.max(0, Math.min(ids.length - 1, currentIndex < 0 ? fallbackIndex : currentIndex + delta));
2503
+ setSelectedChildID(ids[nextIndex]);
2504
+ scrollChildIntoView(ids[nextIndex]);
2505
+ };
2506
+ const rowActivations = /* @__PURE__ */ new Map();
2507
+ const resolveNavigableChildTargetSessionID = (child) => resolveChildTargetSessionID(child) ?? resolveSyntheticTargetFromHydratedState(props.state(), child);
2508
+ const selectedTargetSessionID = () => {
2509
+ const selected = visibleChildren().find((child) => child.id === selectedChildID());
2510
+ return selected ? resolveNavigableChildTargetSessionID(selected) : void 0;
2511
+ };
2512
+ const activateSelectedChild = () => {
2513
+ const selectedID = selectedChildID();
2514
+ const activateRow = selectedID ? rowActivations.get(selectedID) : void 0;
2515
+ if (activateRow) {
2516
+ activateRow();
2517
+ return;
2518
+ }
2519
+ props.navigate(selectedTargetSessionID());
2520
+ };
2521
+ const toggleCompletedHistory = () => {
2522
+ completedHistoryRegistration.toggleCompletedHistory();
2523
+ };
2524
+ createEffect(() => {
2525
+ selectedChildID();
2526
+ listHeight();
2527
+ if (!listFocused()) return;
2528
+ scrollSelectedChildIntoView();
2529
+ });
2530
+ const handleListKeyDown = (event) => {
2531
+ if (!listFocused()) return;
2532
+ const name = event.name.toLowerCase();
2533
+ if (props.strictInput && (event.ctrl || event.shift || event.super || event.hyper || (event.meta || event.option) && name !== "b")) return;
2534
+ if ((event.meta || event.option) && name === "b") {
2535
+ props.onToggleListFocus();
2536
+ } else if (name === "j" || name === "down" || name === "arrowdown") {
2537
+ moveSelection(1);
2538
+ } else if (name === "k" || name === "up" || name === "arrowup") {
2539
+ moveSelection(-1);
2540
+ } else if (name === "return" || name === "enter") {
2541
+ activateSelectedChild();
2542
+ } else if (name === "h" || name === "left" || name === "arrowleft") {
2543
+ if (props.expanded()) props.onSetExpanded(false);
2544
+ } else if (name === "l" || name === "right" || name === "arrowright") {
2545
+ if (!props.expanded()) props.onSetExpanded(true);
2546
+ } else if (name === "c") {
2547
+ toggleCompletedHistory();
2548
+ } else if (name === "escape" || name === "esc") {
2549
+ focusRegistration.blurList();
2550
+ props.onReturnFocus();
2551
+ } else {
2552
+ return;
2553
+ }
2554
+ event.preventDefault();
2555
+ event.stopPropagation();
2556
+ };
2557
+ props.registerListKeys({
2558
+ target: () => listContainer,
2559
+ onKeyDown: handleListKeyDown
2560
+ });
2561
+ const restorePreservedScroll = () => {
2562
+ if (!scrollbox) return;
2563
+ if (scrollRegistration.restoreFramesRemaining <= 0) return;
2564
+ scrollRegistration.restoreFramesRemaining -= 1;
2565
+ if (restoreChildRowID) {
2566
+ const childRowID = restoreChildRowID;
2567
+ restoreChildRowID = void 0;
2568
+ scrollRegistration.restoreFramesRemaining = 0;
2569
+ if (visibleChildIDs().includes(childRowID)) {
2570
+ scrollChildIntoView(childRowID);
2571
+ } else {
2572
+ scrollbox.scrollTop = 0;
2573
+ }
2574
+ return;
2575
+ }
2576
+ const top = preservedSidebarScrollTop({
2577
+ expanded: props.expanded(),
2578
+ offsetTop: scrollRegistration.offsetTop,
2579
+ anchor: scrollRegistration.anchor,
2580
+ rows: scrollRegistration.getRows(),
2581
+ leadingHeight: scrollRegistration.getLeadingHeight(),
2582
+ scrollTop: scrollbox.scrollTop,
2583
+ scrollHeight: scrollbox.scrollHeight,
2584
+ viewportHeight: scrollbox.viewport.height
2585
+ });
2586
+ if (top === void 0) return;
2587
+ scrollRegistration.offsetTop = top;
2588
+ scrollbox.scrollTop = top;
2589
+ };
2590
+ createEffect(() => {
2591
+ props.expanded();
2592
+ visibleChildIDs().join("|");
2593
+ visibleChildLayoutSignature();
2594
+ props.sidebarWidth?.();
2595
+ restorePreservedScroll();
2596
+ });
2597
+ const ChildRow = (rowProps) => {
2598
+ const child = createMemo(() => visibleChildren().find((candidate) => candidate.id === rowProps.childID));
2599
+ const [hovered, setHovered] = createSignal(false);
2600
+ const [focused, setFocused] = createSignal(false);
2601
+ const targetSessionID = createMemo(() => {
2602
+ const currentChild = child();
2603
+ return currentChild ? resolveNavigableChildTargetSessionID(currentChild) : void 0;
2604
+ });
2605
+ const clickable = createMemo(() => isSessionTarget(targetSessionID()));
2606
+ const selected = createMemo(() => listFocused() && selectedChildID() === rowProps.childID);
2607
+ const emphasized = createMemo(() => clickable() && (hovered() || focused() || selected()));
2608
+ const status = createMemo(() => child()?.status ?? "running");
2609
+ const muted = createMemo(() => status() !== "running" && clickable() && !emphasized());
2610
+ const rowOpacity = createMemo(() => status() === "running" ? 1 : INACTIVE_SUBAGENT_OPACITY);
2611
+ const line = createMemo(() => {
2612
+ const currentChild = child();
2613
+ if (!currentChild) {
2614
+ return {
2615
+ labelLines: [""],
2616
+ elapsed: "00:00",
2617
+ meta: ""
2618
+ };
2619
+ }
2620
+ return formatChildRowLine({
2621
+ child: currentChild,
2622
+ nowMs: props.nowMs(),
2623
+ sidebarWidth: props.sidebarWidth?.(),
2624
+ reservedWidth: SUBAGENTS_ROW_MARKER_WIDTH,
2625
+ usageLabel: props.usageLabel
2626
+ });
2627
+ });
2628
+ const terminalLine = createMemo(() => {
2629
+ const currentChild = child();
2630
+ if (!currentChild) return {
2631
+ label: "",
2632
+ meta: "00:00"
2633
+ };
2634
+ return formatTerminalChildRowLine({
2635
+ child: currentChild,
2636
+ nowMs: props.nowMs(),
2637
+ sidebarWidth: props.sidebarWidth?.(),
2638
+ reservedWidth: SUBAGENTS_ROW_MARKER_WIDTH,
2639
+ usageLabel: props.usageLabel
2640
+ });
2641
+ });
2642
+ const rowHeight = createMemo(() => {
2643
+ const currentChild = child();
2644
+ if (!currentChild) return SUBAGENTS_TERMINAL_ROW_HEIGHT;
2645
+ return subagentRowHeight({
2646
+ child: currentChild,
2647
+ nowMs: props.nowMs(),
2648
+ sidebarWidth: props.sidebarWidth?.(),
2649
+ reservedWidth: SUBAGENTS_ROW_MARKER_WIDTH,
2650
+ usageLabel: props.usageLabel,
2651
+ childHint: props.childHint?.(currentChild.id)
2652
+ });
2653
+ });
2654
+ const modelLine = createMemo(() => {
2655
+ const currentChild = child();
2656
+ if (!currentChild) return void 0;
2657
+ return props.modelLine(currentChild, rowWidthBudget(props.sidebarWidth?.()) - SUBAGENTS_ROW_MARKER_WIDTH);
2658
+ });
2659
+ const activate = () => {
2660
+ const target = targetSessionID();
2661
+ if (target) {
2662
+ props.onNavigateToChild({
2663
+ parentSessionID: props.sessionID,
2664
+ childSessionID: target,
2665
+ childRowID: rowProps.childID,
2666
+ showCompletedHistory: showCompletedHistory()
2667
+ });
2668
+ }
2669
+ props.controller.snapshotScroll();
2670
+ props.navigate(target);
2671
+ };
2672
+ rowActivations.set(rowProps.childID, activate);
2673
+ onCleanup(() => {
2674
+ rowActivations.delete(rowProps.childID);
2675
+ });
2676
+ const handleKeyDown = (event) => {
2677
+ if (!clickable()) return;
2678
+ if (props.strictInput && (event.ctrl || event.meta || event.option || event.shift || event.super || event.hyper)) return;
2679
+ setFocused(true);
2680
+ if (event.name === "return" || event.name === "space") {
2681
+ activate();
2682
+ event.preventDefault();
2683
+ event.stopPropagation();
2684
+ }
2685
+ };
2686
+ return (() => {
2687
+ var _el$ = _$createElement("box");
2688
+ _$setProp(_el$, "flexDirection", "column");
2689
+ _$insert(_el$, _$createComponent(Show, {
2690
+ get when() {
2691
+ return status() === "running";
2692
+ },
2693
+ get fallback() {
2694
+ return (() => {
2695
+ 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");
2696
+ _$insertNode(_el$0, _el$1);
2697
+ _$insertNode(_el$0, _el$13);
2698
+ _$setProp(_el$0, "flexDirection", "column");
2699
+ _$insertNode(_el$1, _el$10);
2700
+ _$insertNode(_el$1, _el$11);
2701
+ _$insertNode(_el$1, _el$12);
2702
+ _$setProp(_el$1, "flexDirection", "row");
2703
+ _$insert(_el$10, () => selected() ? "\u203A" : " ");
2704
+ _$insert(_el$11, () => taskStatusMarker(status()));
2705
+ _$insert(_el$12, () => ` ${terminalLine().label}`);
2706
+ _$insert(_el$13, () => ` \u21B3 ${CLOCK_ICON} ${terminalLine().meta}`);
2707
+ _$insert(_el$0, _$createComponent(Show, {
2708
+ get when() {
2709
+ return modelLine();
2710
+ },
2711
+ children: (metadata) => (() => {
2712
+ var _el$14 = _$createElement("text");
2713
+ _$insert(_el$14, () => ` ${metadata()}`);
2714
+ _$effect((_$p) => _$setProp(_el$14, "fg", props.theme.textMuted, _$p));
2715
+ return _el$14;
2716
+ })()
2717
+ }), null);
2718
+ _$effect((_p$) => {
2719
+ 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;
2720
+ _v$13 !== _p$.e && (_p$.e = _$setProp(_el$10, "fg", _v$13, _p$.e));
2721
+ _v$14 !== _p$.t && (_p$.t = _$setProp(_el$11, "fg", _v$14, _p$.t));
2722
+ _v$15 !== _p$.a && (_p$.a = _$setProp(_el$12, "fg", _v$15, _p$.a));
2723
+ _v$16 !== _p$.o && (_p$.o = _$setProp(_el$13, "fg", _v$16, _p$.o));
2724
+ return _p$;
2725
+ }, {
2726
+ e: void 0,
2727
+ t: void 0,
2728
+ a: void 0,
2729
+ o: void 0
2730
+ });
2731
+ return _el$0;
2732
+ })();
2733
+ },
2734
+ get children() {
2735
+ 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");
2736
+ _$insertNode(_el$2, _el$3);
2737
+ _$insertNode(_el$2, _el$7);
2738
+ _$setProp(_el$2, "flexDirection", "column");
2739
+ _$insertNode(_el$3, _el$4);
2740
+ _$insertNode(_el$3, _el$5);
2741
+ _$insertNode(_el$3, _el$6);
2742
+ _$setProp(_el$3, "flexDirection", "row");
2743
+ _$insert(_el$4, () => selected() ? "\u203A" : " ");
2744
+ _$insert(_el$5, () => taskStatusMarker(status()));
2745
+ _$insert(_el$6, () => ` ${line().labelLines[0] ?? ""}`);
2746
+ _$insert(_el$2, _$createComponent(Show, {
2747
+ get when() {
2748
+ return line().secondaryLine;
2749
+ },
2750
+ children: (secondaryLine) => (() => {
2751
+ var _el$15 = _$createElement("text");
2752
+ _$insert(_el$15, () => ` ${secondaryLine()}`);
2753
+ _$effect((_$p) => _$setProp(_el$15, "fg", muted() ? props.theme.textMuted : props.theme.text, _$p));
2754
+ return _el$15;
2755
+ })()
2756
+ }), _el$7);
2757
+ _$insertNode(_el$7, _el$8);
2758
+ _$setProp(_el$7, "flexDirection", "row");
2759
+ _$setProp(_el$7, "paddingLeft", 4);
2760
+ _$insert(_el$8, () => `\u21B3 ${CLOCK_ICON} ${line().elapsed}`);
2761
+ _$insert(_el$7, _$createComponent(Show, {
2762
+ get when() {
2763
+ return line().meta.length > 0;
2764
+ },
2765
+ get children() {
2766
+ var _el$9 = _$createElement("text");
2767
+ _$insert(_el$9, () => ` ${TOKEN_ICON} ${line().meta}`);
2768
+ _$effect((_$p) => _$setProp(_el$9, "fg", emphasized() ? props.theme.text : props.theme.textMuted, _$p));
2769
+ return _el$9;
2770
+ }
2771
+ }), null);
2772
+ _$insert(_el$2, _$createComponent(Show, {
2773
+ get when() {
2774
+ return modelLine();
2775
+ },
2776
+ children: (metadata) => (() => {
2777
+ var _el$16 = _$createElement("text");
2778
+ _$insert(_el$16, () => ` ${metadata()}`);
2779
+ _$effect((_$p) => _$setProp(_el$16, "fg", props.theme.textMuted, _$p));
2780
+ return _el$16;
2781
+ })()
2782
+ }), null);
2783
+ _$effect((_p$) => {
2784
+ 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;
2785
+ _v$ !== _p$.e && (_p$.e = _$setProp(_el$4, "fg", _v$, _p$.e));
2786
+ _v$2 !== _p$.t && (_p$.t = _$setProp(_el$5, "fg", _v$2, _p$.t));
2787
+ _v$3 !== _p$.a && (_p$.a = _$setProp(_el$6, "fg", _v$3, _p$.a));
2788
+ _v$4 !== _p$.o && (_p$.o = _$setProp(_el$8, "fg", _v$4, _p$.o));
2789
+ return _p$;
2790
+ }, {
2791
+ e: void 0,
2792
+ t: void 0,
2793
+ a: void 0,
2794
+ o: void 0
2795
+ });
2796
+ return _el$2;
2797
+ }
2798
+ }), null);
2799
+ _$insert(_el$, _$createComponent(Show, {
2800
+ get when() {
2801
+ return props.childHint?.(rowProps.childID);
2802
+ },
2803
+ children: (hint) => (() => {
2804
+ var _el$17 = _$createElement("text");
2805
+ _$insert(_el$17, () => ` ${hint()}`);
2806
+ _$effect((_$p) => _$setProp(_el$17, "fg", props.theme.warning, _$p));
2807
+ return _el$17;
2808
+ })()
2809
+ }), null);
2810
+ _$effect((_p$) => {
2811
+ 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() ? () => {
2812
+ setHovered(false);
2813
+ setFocused(false);
2814
+ setMouseDownChildID(void 0);
2815
+ } : void 0, _v$0 = clickable() ? (event) => {
2816
+ event.stopPropagation();
2817
+ setSelectedChildID(rowProps.childID);
2818
+ setMouseDownChildID(rowProps.childID);
2819
+ } : void 0, _v$1 = clickable() ? (event) => {
2820
+ if (mouseDownChildID() === rowProps.childID) {
2821
+ event.stopPropagation();
2822
+ activate();
2823
+ }
2824
+ setMouseDownChildID(void 0);
2825
+ } : void 0, _v$10 = clickable() ? handleKeyDown : void 0, _v$11 = clickable(), _v$12 = clickable() && focused();
2826
+ _v$5 !== _p$.e && (_p$.e = _$setProp(_el$, "height", _v$5, _p$.e));
2827
+ _v$6 !== _p$.t && (_p$.t = _$setProp(_el$, "opacity", _v$6, _p$.t));
2828
+ _v$7 !== _p$.a && (_p$.a = _$setProp(_el$, "backgroundColor", _v$7, _p$.a));
2829
+ _v$8 !== _p$.o && (_p$.o = _$setProp(_el$, "onMouseOver", _v$8, _p$.o));
2830
+ _v$9 !== _p$.i && (_p$.i = _$setProp(_el$, "onMouseOut", _v$9, _p$.i));
2831
+ _v$0 !== _p$.n && (_p$.n = _$setProp(_el$, "onMouseDown", _v$0, _p$.n));
2832
+ _v$1 !== _p$.s && (_p$.s = _$setProp(_el$, "onMouseUp", _v$1, _p$.s));
2833
+ _v$10 !== _p$.h && (_p$.h = _$setProp(_el$, "onKeyDown", _v$10, _p$.h));
2834
+ _v$11 !== _p$.r && (_p$.r = _$setProp(_el$, "focusable", _v$11, _p$.r));
2835
+ _v$12 !== _p$.d && (_p$.d = _$setProp(_el$, "focused", _v$12, _p$.d));
2836
+ return _p$;
2837
+ }, {
2838
+ e: void 0,
2839
+ t: void 0,
2840
+ a: void 0,
2841
+ o: void 0,
2842
+ i: void 0,
2843
+ n: void 0,
2844
+ s: void 0,
2845
+ h: void 0,
2846
+ r: void 0,
2847
+ d: void 0
2848
+ });
2849
+ return _el$;
2850
+ })();
2851
+ };
2852
+ const AggregateBar = () => (() => {
2853
+ 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");
2854
+ _$insertNode(_el$18, _el$19);
2855
+ _$insertNode(_el$18, _el$20);
2856
+ _$insertNode(_el$18, _el$22);
2857
+ _$insertNode(_el$18, _el$23);
2858
+ _$insertNode(_el$18, _el$25);
2859
+ _$insertNode(_el$18, _el$26);
2860
+ _$insertNode(_el$18, _el$28);
2861
+ _$setProp(_el$18, "flexDirection", "row");
2862
+ _$setProp(_el$18, "paddingRight", 1);
2863
+ _$insert(_el$19, () => `\u25CF ${counts().running} run`);
2864
+ _$insertNode(_el$20, _$createTextNode(` \xB7 `));
2865
+ _$insert(_el$22, () => `\u2713 ${counts().done} done`);
2866
+ _$insertNode(_el$23, _$createTextNode(` \xB7 `));
2867
+ _$insert(_el$25, () => `\u2715 ${counts().error} err`);
2868
+ _$insertNode(_el$26, _$createTextNode(` \xB7 `));
2869
+ _$setProp(_el$28, "selectable", false);
2870
+ _$setProp(_el$28, "onMouseDown", toggleCompletedHistory);
2871
+ _$insert(_el$28, () => `\u03A3 ${totalExecuted()}`);
2872
+ _$effect((_p$) => {
2873
+ 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;
2874
+ _v$17 !== _p$.e && (_p$.e = _$setProp(_el$19, "fg", _v$17, _p$.e));
2875
+ _v$18 !== _p$.t && (_p$.t = _$setProp(_el$20, "fg", _v$18, _p$.t));
2876
+ _v$19 !== _p$.a && (_p$.a = _$setProp(_el$22, "fg", _v$19, _p$.a));
2877
+ _v$20 !== _p$.o && (_p$.o = _$setProp(_el$23, "fg", _v$20, _p$.o));
2878
+ _v$21 !== _p$.i && (_p$.i = _$setProp(_el$25, "fg", _v$21, _p$.i));
2879
+ _v$22 !== _p$.n && (_p$.n = _$setProp(_el$26, "fg", _v$22, _p$.n));
2880
+ _v$23 !== _p$.s && (_p$.s = _$setProp(_el$28, "fg", _v$23, _p$.s));
2881
+ return _p$;
2882
+ }, {
2883
+ e: void 0,
2884
+ t: void 0,
2885
+ a: void 0,
2886
+ o: void 0,
2887
+ i: void 0,
2888
+ n: void 0,
2889
+ s: void 0
2890
+ });
2891
+ return _el$18;
2892
+ })();
2893
+ return (() => {
2894
+ var _el$29 = _$createElement("box"), _el$30 = _$createElement("box"), _el$31 = _$createElement("text");
2895
+ _$insertNode(_el$29, _el$30);
2896
+ _$use((element) => {
2897
+ listContainer = element;
2898
+ if (!element) setListFocused(false);
2899
+ }, _el$29);
2900
+ _$setProp(_el$29, "flexDirection", "column");
2901
+ _$setProp(_el$29, "focusable", true);
2902
+ _$setProp(_el$29, "renderBefore", () => {
2903
+ refreshListFocused();
2904
+ restorePreservedScroll();
2905
+ });
2906
+ _$insertNode(_el$30, _el$31);
2907
+ _$setProp(_el$30, "flexDirection", "row");
2908
+ _$setProp(_el$31, "selectable", false);
2909
+ _$insert(_el$31, () => `${props.expanded() ? SIDEBAR_ARROW_EXPANDED : SIDEBAR_ARROW_COLLAPSED} ${t("subagents")}`);
2910
+ _$insert(_el$30, _$createComponent(Show, {
2911
+ when: PLUGIN_VERSION,
2912
+ children: (version) => (() => {
2913
+ var _el$34 = _$createElement("box"), _el$35 = _$createElement("text");
2914
+ _$insertNode(_el$34, _el$35);
2915
+ _$setProp(_el$34, "flexDirection", "row");
2916
+ _$setProp(_el$35, "opacity", 0.7);
2917
+ _$setProp(_el$35, "selectable", false);
2918
+ _$insert(_el$35, () => ` ${version()}`);
2919
+ _$insert(_el$34, _$createComponent(Show, {
2920
+ get when() {
2921
+ return listFocused();
2922
+ },
2923
+ get children() {
2924
+ var _el$36 = _$createElement("text");
2925
+ _$insertNode(_el$36, _$createTextNode(` \u25CF`));
2926
+ _$setProp(_el$36, "selectable", false);
2927
+ _$effect((_p$) => {
2928
+ var _v$28 = props.theme.accent, _v$29 = props.onToggleExpanded;
2929
+ _v$28 !== _p$.e && (_p$.e = _$setProp(_el$36, "fg", _v$28, _p$.e));
2930
+ _v$29 !== _p$.t && (_p$.t = _$setProp(_el$36, "onMouseDown", _v$29, _p$.t));
2931
+ return _p$;
2932
+ }, {
2933
+ e: void 0,
2934
+ t: void 0
2935
+ });
2936
+ return _el$36;
2937
+ }
2938
+ }), null);
2939
+ _$effect((_p$) => {
2940
+ var _v$30 = props.theme.textMuted, _v$31 = props.onToggleExpanded;
2941
+ _v$30 !== _p$.e && (_p$.e = _$setProp(_el$35, "fg", _v$30, _p$.e));
2942
+ _v$31 !== _p$.t && (_p$.t = _$setProp(_el$35, "onMouseDown", _v$31, _p$.t));
2943
+ return _p$;
2944
+ }, {
2945
+ e: void 0,
2946
+ t: void 0
2947
+ });
2948
+ return _el$34;
2949
+ })()
2950
+ }), null);
2951
+ _$insert(_el$29, _$createComponent(AggregateBar, {}), null);
2952
+ _$insert(_el$29, _$createComponent(Show, {
2953
+ get when() {
2954
+ return props.notice?.();
2955
+ },
2956
+ children: (notice) => (() => {
2957
+ var _el$38 = _$createElement("text");
2958
+ _$insert(_el$38, () => wrapCompactText(notice(), rowWidthBudget(props.sidebarWidth?.()), 3).join("\n"));
2959
+ _$effect((_$p) => _$setProp(_el$38, "fg", props.theme.warning, _$p));
2960
+ return _el$38;
2961
+ })()
2962
+ }), null);
2963
+ _$insert(_el$29, _$createComponent(Show, {
2964
+ get when() {
2965
+ return props.usageLabel;
2966
+ },
2967
+ children: (label) => (() => {
2968
+ var _el$39 = _$createElement("text");
2969
+ _$insert(_el$39, () => wrapCompactText(label(), rowWidthBudget(props.sidebarWidth?.()), 3).join("\n"));
2970
+ _$effect((_$p) => _$setProp(_el$39, "fg", props.theme.textMuted, _$p));
2971
+ return _el$39;
2972
+ })()
2973
+ }), null);
2974
+ _$insert(_el$29, _$createComponent(Show, {
2975
+ get when() {
2976
+ return props.expanded();
2977
+ },
2978
+ get children() {
2979
+ var _el$32 = _$createElement("scrollbox"), _el$33 = _$createElement("box");
2980
+ _$insertNode(_el$32, _el$33);
2981
+ _$use((element) => {
2982
+ scrollbox = element;
2983
+ restorePreservedScroll();
2984
+ }, _el$32);
2985
+ _$setProp(_el$32, "scrollY", true);
2986
+ _$setProp(_el$32, "viewportCulling", false);
2987
+ _$setProp(_el$33, "flexDirection", "column");
2988
+ _$setProp(_el$33, "rowGap", 0);
2989
+ _$insert(_el$33, _$createComponent(For, {
2990
+ get each() {
2991
+ return visibleChildIDs();
2992
+ },
2993
+ children: (childID) => _$createComponent(ChildRow, {
2994
+ childID
2995
+ })
2996
+ }));
2997
+ _$effect((_$p) => _$setProp(_el$32, "height", listHeight(), _$p));
2998
+ return _el$32;
2999
+ }
3000
+ }), null);
3001
+ _$effect((_p$) => {
3002
+ var _v$24 = listFocused() ? props.theme.backgroundPanel : void 0, _v$25 = listFocused(), _v$26 = props.theme.text, _v$27 = props.onToggleExpanded;
3003
+ _v$24 !== _p$.e && (_p$.e = _$setProp(_el$29, "backgroundColor", _v$24, _p$.e));
3004
+ _v$25 !== _p$.t && (_p$.t = _$setProp(_el$29, "focused", _v$25, _p$.t));
3005
+ _v$26 !== _p$.a && (_p$.a = _$setProp(_el$31, "fg", _v$26, _p$.a));
3006
+ _v$27 !== _p$.o && (_p$.o = _$setProp(_el$31, "onMouseDown", _v$27, _p$.o));
3007
+ return _p$;
3008
+ }, {
3009
+ e: void 0,
3010
+ t: void 0,
3011
+ a: void 0,
3012
+ o: void 0
3013
+ });
3014
+ return _el$29;
3015
+ })();
3016
+ }
3017
+ function HomeBottomStatus(props) {
3018
+ const snapshot = createMemo(() => resolveTuiSubagentSnapshot({
3019
+ state: props.state()
3020
+ }));
3021
+ const counts = createMemo(() => snapshot().visibleCounts);
3022
+ const totalExecuted = createMemo(() => snapshot().totalExecuted);
3023
+ const visible = createMemo(() => counts().running > 0 || counts().error > 0 || totalExecuted() > 0);
3024
+ return _$createComponent(Show, {
3025
+ get when() {
3026
+ return visible();
3027
+ },
3028
+ get children() {
3029
+ 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");
3030
+ _$insertNode(_el$40, _el$41);
3031
+ _$setProp(_el$40, "paddingLeft", 1);
3032
+ _$setProp(_el$40, "paddingRight", 1);
3033
+ _$insertNode(_el$41, _el$42);
3034
+ _$insertNode(_el$41, _el$43);
3035
+ _$insertNode(_el$41, _el$45);
3036
+ _$insertNode(_el$41, _el$46);
3037
+ _$insertNode(_el$41, _el$48);
3038
+ _$insertNode(_el$41, _el$49);
3039
+ _$insertNode(_el$41, _el$51);
3040
+ _$setProp(_el$41, "flexDirection", "row");
3041
+ _$insert(_el$42, () => `\u25CF ${counts().running}`);
3042
+ _$insertNode(_el$43, _$createTextNode(` \xB7 `));
3043
+ _$insert(_el$45, () => `\u2713 ${counts().done}`);
3044
+ _$insertNode(_el$46, _$createTextNode(` \xB7 `));
3045
+ _$insert(_el$48, () => `\u2715 ${counts().error}`);
3046
+ _$insertNode(_el$49, _$createTextNode(` \xB7 `));
3047
+ _$insert(_el$51, () => `\u03A3 ${totalExecuted()}`);
3048
+ _$effect((_p$) => {
3049
+ 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;
3050
+ _v$32 !== _p$.e && (_p$.e = _$setProp(_el$42, "fg", _v$32, _p$.e));
3051
+ _v$33 !== _p$.t && (_p$.t = _$setProp(_el$43, "fg", _v$33, _p$.t));
3052
+ _v$34 !== _p$.a && (_p$.a = _$setProp(_el$45, "fg", _v$34, _p$.a));
3053
+ _v$35 !== _p$.o && (_p$.o = _$setProp(_el$46, "fg", _v$35, _p$.o));
3054
+ _v$36 !== _p$.i && (_p$.i = _$setProp(_el$48, "fg", _v$36, _p$.i));
3055
+ _v$37 !== _p$.n && (_p$.n = _$setProp(_el$49, "fg", _v$37, _p$.n));
3056
+ _v$38 !== _p$.s && (_p$.s = _$setProp(_el$51, "fg", _v$38, _p$.s));
3057
+ return _p$;
3058
+ }, {
3059
+ e: void 0,
3060
+ t: void 0,
3061
+ a: void 0,
3062
+ o: void 0,
3063
+ i: void 0,
3064
+ n: void 0,
3065
+ s: void 0
3066
+ });
3067
+ return _el$40;
3068
+ }
3069
+ });
3070
+ }
3071
+
3072
+ // src/tui-v1.tsx
3073
+ var TUI_PLUGIN_ID = "subagent-statusline.tui";
3074
+ var DONE_TOKEN_REHYDRATE_MAX_ATTEMPTS = 15;
3075
+ var HYDRATE_RETRY_BASE_DELAY_MS = 1e3;
3076
+ var HYDRATE_RETRY_MAX_DELAY_MS = 3e4;
3077
+ var HYDRATE_RETRY_MAX_ATTEMPTS = 6;
3078
+ var RUNNING_RECONCILE_MAINTENANCE_INTERVAL_MS = 10 * 6e4;
3079
+ var RUNNING_RECONCILE_MAX_CANDIDATES = 8;
3080
+ var RUNNING_RECONCILE_INITIAL_BACKOFF_MS = 15e3;
3081
+ var RUNNING_RECONCILE_MAX_BACKOFF_MS = 5 * 6e4;
3082
+ var RUNNING_RECONCILE_MESSAGE_AGE_GATE_MS = 6e4;
3083
+ var RUNNING_RECONCILE_OLD_CANDIDATE_AGE_MS = 5 * 6e4;
3084
+ var SUBAGENTS_EXPANDED_KV_KEY = "subagents.sidebar.expanded";
3085
+ var SUBAGENTS_SECTION_ENABLED_KV_KEY = "subagents.sidebar.enabled";
3086
+ var doneTokenCache = /* @__PURE__ */ new Map();
3087
+ function debugLog(input) {
3088
+ if (!process.env.OPENCODE_SUBAGENT_STATUSLINE_DEBUG_EVENTS) return;
3089
+ try {
3090
+ const path = join2(process.env.XDG_RUNTIME_DIR ?? os2.tmpdir(), "opencode-subagent-statusline", "tui-events.log");
3091
+ mkdirSync(dirname2(path), {
3092
+ recursive: true
3093
+ });
3094
+ const line = JSON.stringify({
3095
+ time: (/* @__PURE__ */ new Date()).toISOString(),
3096
+ ...input
3097
+ });
3098
+ appendFileSync(path, `${line}
3099
+ `, "utf8");
3100
+ } catch {
3101
+ }
3102
+ }
3103
+ function debugEvent(event) {
3104
+ const e = event;
3105
+ const part = e.properties?.part;
3106
+ debugLog({
3107
+ kind: "event",
3108
+ type: e.type,
3109
+ sessionID: e.properties?.sessionID,
3110
+ partType: part?.type,
3111
+ tool: part?.tool,
3112
+ toolStatus: part?.state?.status
3113
+ });
3114
+ }
3115
+ function cloneState(state) {
3116
+ return {
3117
+ updatedAt: state.updatedAt,
3118
+ totalExecuted: state.totalExecuted,
3119
+ countedChildIDs: {
3120
+ ...state.countedChildIDs
3121
+ },
3122
+ children: Object.fromEntries(Object.entries(state.children).map(([id, child]) => [id, {
3123
+ ...child,
3124
+ tokens: child.tokens ? {
3125
+ ...child.tokens
3126
+ } : void 0,
3127
+ model: child.model ? {
3128
+ ...child.model
3129
+ } : void 0
3130
+ }]))
3131
+ };
3132
+ }
3133
+ function mergeTokenState(existing, incoming) {
3134
+ if (!existing && !incoming) return void 0;
3135
+ return {
3136
+ input: incoming?.input ?? existing?.input,
3137
+ output: incoming?.output ?? existing?.output,
3138
+ total: incoming?.total ?? existing?.total,
3139
+ contextPercent: incoming?.contextPercent ?? existing?.contextPercent
3140
+ };
3141
+ }
3142
+ function hasTokenTotal(tokens) {
3143
+ return typeof tokens?.total === "number" && Number.isFinite(tokens.total);
3144
+ }
3145
+ function sameTokens2(left, right) {
3146
+ return JSON.stringify(left) === JSON.stringify(right);
3147
+ }
3148
+ function asRecord2(value) {
3149
+ return value && typeof value === "object" ? value : void 0;
3150
+ }
3151
+ function tokenStateFromMessageData(data) {
3152
+ const parsed = safeRead2(() => JSON.parse(data));
3153
+ return parsed?.tokens;
3154
+ }
3155
+ function resolveOpenCodeDataDir() {
3156
+ return join2(process.env.XDG_DATA_HOME ?? join2(os2.homedir(), ".local", "share"), "opencode");
3157
+ }
3158
+ function resolveOpenCodeDbPath() {
3159
+ return process.env.OPENCODE_SUBAGENT_STATUSLINE_OPENCODE_DB ?? join2(resolveOpenCodeDataDir(), "opencode.db");
3160
+ }
3161
+ function escapeSqlString(value) {
3162
+ return value.replace(/'/g, "''");
3163
+ }
3164
+ function readDoneTokensFromOpenCodeDb(sessionID) {
3165
+ const dbPath = resolveOpenCodeDbPath();
3166
+ if (!existsSync(dbPath)) return void 0;
3167
+ const output = safeRead2(() => execFileSync("sqlite3", [dbPath, `select data from message where session_id='${escapeSqlString(sessionID)}' order by time_created desc limit 50;`], {
3168
+ encoding: "utf8",
3169
+ timeout: 1e3,
3170
+ maxBuffer: 1024 * 1024
3171
+ }));
3172
+ if (!output) return void 0;
3173
+ let tokens;
3174
+ for (const line of output.split("\n")) {
3175
+ const hydrated = tokenStateFromMessageData(line.trim());
3176
+ tokens = mergeTokenState(tokens, hydrated);
3177
+ if (hasTokenTotal(tokens)) break;
3178
+ }
3179
+ return tokens;
3180
+ }
3181
+ function readDoneTokensFromOpenCodeLogs(sessionID) {
3182
+ const logDir = join2(resolveOpenCodeDataDir(), "log");
3183
+ if (!existsSync(logDir)) return void 0;
3184
+ const files = safeRead2(() => readdirSync(logDir).filter((file) => file.endsWith(".log")).sort().reverse().slice(0, 8));
3185
+ if (!files) return void 0;
3186
+ const tokenPattern = /"tokens"\s*:\s*(\{[^\n]*?\})/g;
3187
+ let tokens;
3188
+ for (const file of files) {
3189
+ const contents = readOpenCodeLogFileIfSmall(join2(logDir, file));
3190
+ if (!contents || !contents.includes(sessionID)) continue;
3191
+ for (const line of contents.split("\n")) {
3192
+ if (!line.includes(sessionID) || !line.includes('"tokens"')) continue;
3193
+ for (const match of line.matchAll(tokenPattern)) {
3194
+ const hydrated = safeRead2(() => JSON.parse(match[1] ?? "{}"));
3195
+ tokens = mergeTokenState(tokens, hydrated);
3196
+ if (hasTokenTotal(tokens)) return tokens;
3197
+ }
3198
+ }
3199
+ }
3200
+ return tokens;
3201
+ }
3202
+ function rehydrateDoneChildTokens(child) {
3203
+ if (child.status !== "done") return void 0;
3204
+ if (hasTokenTotal(child.tokens)) return void 0;
3205
+ if (!child.id.startsWith("ses_")) return void 0;
3206
+ const nowMs = Date.now();
3207
+ const cached = doneTokenCache.get(child.id);
3208
+ if (cached?.tokens) return cached.tokens;
3209
+ if (cached && cached.attempts >= DONE_TOKEN_REHYDRATE_MAX_ATTEMPTS) {
3210
+ return void 0;
3211
+ }
3212
+ if (cached && nowMs - cached.checkedAtMs < DONE_TOKEN_REHYDRATE_THROTTLE_MS) {
3213
+ return void 0;
3214
+ }
3215
+ const tokens = readDoneTokensFromOpenCodeDb(child.id) ?? readDoneTokensFromOpenCodeLogs(child.id);
3216
+ doneTokenCache.set(child.id, {
3217
+ attempts: (cached?.attempts ?? 0) + 1,
3218
+ checkedAtMs: nowMs,
3219
+ tokens
3220
+ });
3221
+ if (tokens) {
3222
+ debugLog({
3223
+ kind: "state.tokens.rehydrated.done",
3224
+ id: child.id,
3225
+ title: child.title,
3226
+ tokens
3227
+ });
3228
+ }
3229
+ return tokens;
3230
+ }
3231
+ function safeRead2(read) {
3232
+ try {
3233
+ return read();
3234
+ } catch {
3235
+ return void 0;
3236
+ }
3237
+ }
3238
+ function messageIDOf(message) {
3239
+ const record = asRecord2(message);
3240
+ if (!record) return void 0;
3241
+ const id = record.id ?? record.messageID ?? record.messageId;
3242
+ return typeof id === "string" && id.length > 0 ? id : void 0;
3243
+ }
3244
+ function pushSessionCandidates(api, sessionID, candidates) {
3245
+ if (!sessionID) return;
3246
+ const status = safeRead2(() => api.state.session.status(sessionID));
3247
+ if (status) candidates.push(status);
3248
+ const messages = safeRead2(() => api.state.session.messages(sessionID));
3249
+ if (!messages) return;
3250
+ candidates.push(messages);
3251
+ for (const message of messages) {
3252
+ const messageID = messageIDOf(message);
3253
+ if (!messageID) continue;
3254
+ const parts = safeRead2(() => api.state.part(messageID));
3255
+ if (parts) candidates.push(parts);
3256
+ }
3257
+ }
3258
+ function hydrateChildTokensFromTuiState(api, child) {
3259
+ const candidates = [];
3260
+ pushSessionCandidates(api, child.id, candidates);
3261
+ if (child.messageID) {
3262
+ const parentParts = safeRead2(() => api.state.part(child.messageID));
3263
+ if (parentParts) candidates.push(parentParts);
3264
+ const parentMessages = safeRead2(() => api.state.session.messages(child.parentID));
3265
+ const parentMessage = parentMessages?.find((message) => messageIDOf(message) === child.messageID);
3266
+ if (parentMessage) candidates.push(parentMessage);
3267
+ }
3268
+ let tokens;
3269
+ for (const candidate of candidates) {
3270
+ tokens = mergeTokenState(tokens, extractChildDetails(candidate).tokens);
3271
+ }
3272
+ tokens = mergeTokenState(tokens, rehydrateDoneChildTokens(child));
3273
+ return tokens;
3274
+ }
3275
+ function hydrateStateTokensFromTuiState(api, state) {
3276
+ let changed = false;
3277
+ for (const child of Object.values(state.children)) {
3278
+ if (child.status !== "running" && hasTokenTotal(child.tokens)) continue;
3279
+ const hydrated = hydrateChildTokensFromTuiState(api, child);
3280
+ const nextTokens = mergeTokenState(child.tokens, hydrated);
3281
+ if (!sameTokens2(child.tokens, nextTokens)) {
3282
+ child.tokens = nextTokens;
3283
+ child.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
3284
+ changed = true;
3285
+ }
3286
+ }
3287
+ if (changed) {
3288
+ state.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
3289
+ debugLog({
3290
+ kind: "state.tokens.hydrated",
3291
+ children: Object.values(state.children).map((child) => ({
3292
+ id: child.id,
3293
+ title: child.title,
3294
+ tokens: child.tokens
3295
+ }))
3296
+ });
3297
+ }
3298
+ return changed;
3299
+ }
3300
+ function persistStateSnapshot(statePath, textPath, state) {
3301
+ const snapshot = cloneState(state);
3302
+ void (async () => {
3303
+ try {
3304
+ await saveState(statePath, snapshot);
3305
+ await saveStatusText(textPath, renderStatusLine(snapshot));
3306
+ } catch {
3307
+ }
3308
+ })();
3309
+ }
3310
+ function refreshLiveState(state) {
3311
+ const beforeChildIDs = new Set(Object.keys(state.children));
3312
+ refreshDerivedFields(state);
3313
+ if (Object.keys(state.children).length !== beforeChildIDs.size) {
3314
+ return true;
3315
+ }
3316
+ for (const childID of beforeChildIDs) {
3317
+ if (!state.children[childID]) return true;
3318
+ }
3319
+ return false;
3320
+ }
3321
+ function runTuiStateMaintenance(api, current) {
3322
+ const next = cloneState(current);
3323
+ const hydrated = hydrateStateTokensFromTuiState(api, next);
3324
+ const refreshed = refreshLiveState(next);
3325
+ return hydrated || refreshed ? next : current;
3326
+ }
3327
+ function createTuiMaintenanceTimers(input) {
3328
+ let elapsedTimer;
3329
+ const maintenanceTimer = setInterval(input.onMaintenanceTick, MAINTENANCE_TICK_MS);
3330
+ return {
3331
+ syncElapsedTimer(hasRunningChild) {
3332
+ if (hasRunningChild && !elapsedTimer) {
3333
+ elapsedTimer = setInterval(input.onElapsedTick, ELAPSED_TICK_MS);
3334
+ } else if (!hasRunningChild && elapsedTimer) {
3335
+ clearInterval(elapsedTimer);
3336
+ elapsedTimer = void 0;
3337
+ }
3338
+ },
3339
+ dispose() {
3340
+ if (elapsedTimer) clearInterval(elapsedTimer);
3341
+ clearInterval(maintenanceTimer);
3342
+ elapsedTimer = void 0;
3343
+ }
3344
+ };
3345
+ }
3346
+ function backfillHydratedTargetSessionIDs(state, parentSessionID) {
3347
+ let changed = false;
3348
+ for (const child of Object.values(state.children)) {
3349
+ if (child.parentID !== parentSessionID) continue;
3350
+ if (resolveChildTargetSessionID(child)) continue;
3351
+ if (child.source === "session" || child.id.startsWith("ses_")) {
3352
+ child.targetSessionID = child.id;
3353
+ changed = true;
3354
+ continue;
3355
+ }
3356
+ const syntheticTarget = resolveSyntheticTargetFromHydratedState(state, child);
3357
+ if (syntheticTarget) {
3358
+ child.targetSessionID = syntheticTarget;
3359
+ changed = true;
3360
+ }
3361
+ }
3362
+ if (changed) {
3363
+ state.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
3364
+ }
3365
+ return changed;
3366
+ }
3367
+ function navigateToSessionTarget(api, targetSessionID) {
3368
+ if (!isSessionTarget(targetSessionID)) return;
3369
+ api.route.navigate("session", {
3370
+ sessionID: targetSessionID
3371
+ });
3372
+ }
3373
+ function toFinitePositiveInt(value) {
3374
+ if (typeof value !== "number" || !Number.isFinite(value)) return void 0;
3375
+ const rounded = Math.floor(value);
3376
+ return rounded > 0 ? rounded : void 0;
3377
+ }
3378
+ function parseStaleRunningThresholdMs2() {
3379
+ return parseStaleRunningThresholdMs(process.env.OPENCODE_SUBAGENT_STATUSLINE_STALE_RUNNING_MS);
3380
+ }
3381
+ var STALE_RUNNING_THRESHOLD_MS = parseStaleRunningThresholdMs2();
3382
+ function resolveSidebarWidth(ctx) {
3383
+ const source = asRecord2(ctx);
3384
+ if (!source) return void 0;
3385
+ const direct = toFinitePositiveInt(source.width) ?? toFinitePositiveInt(source.columns) ?? toFinitePositiveInt(source.cols);
3386
+ if (direct) return direct;
3387
+ const size = asRecord2(source.size);
3388
+ const viewport = asRecord2(source.viewport);
3389
+ const bounds = asRecord2(source.bounds);
3390
+ return toFinitePositiveInt(size?.width) ?? toFinitePositiveInt(viewport?.width) ?? toFinitePositiveInt(bounds?.width);
3391
+ }
3392
+ function formatChildModelLine(child, providers, width) {
3393
+ if (!child.model?.variant) return void 0;
3394
+ const provider = providers.find((candidate) => candidate.id === child.model?.providerID);
3395
+ const name = provider?.models[child.model.modelID]?.name || child.model.modelID;
3396
+ return truncateToColumns(`${name} \xB7 ${child.model.variant}`, Math.max(1, width));
3397
+ }
3398
+ async function hydratePreviousSubagents(api, currentSessionID, statePath, textPath, setState) {
3399
+ if (!currentSessionID) return false;
3400
+ try {
3401
+ const directory = api.state.path.directory;
3402
+ const sessionClient = api.client.session;
3403
+ let topLevelHydrationFailed = false;
3404
+ let statusHydrationFailed = false;
3405
+ let parentMessageHydrationFailed = false;
3406
+ const [childrenResp, messagesResp, statusResp] = await Promise.all([(async () => {
3407
+ const response = await safeReadAsync(() => sessionClient?.children?.({
3408
+ sessionID: currentSessionID,
3409
+ directory
3410
+ }) ?? Promise.resolve({
3411
+ data: []
3412
+ }));
3413
+ if (!response) topLevelHydrationFailed = true;
3414
+ return response;
3415
+ })(), (async () => {
3416
+ const response = await safeReadAsync(() => sessionClient?.messages?.({
3417
+ sessionID: currentSessionID,
3418
+ directory
3419
+ }) ?? Promise.resolve({
3420
+ data: []
3421
+ }));
3422
+ if (!response) {
3423
+ topLevelHydrationFailed = true;
3424
+ parentMessageHydrationFailed = true;
3425
+ }
3426
+ return response;
3427
+ })(), (async () => {
3428
+ const response = await safeReadAsync(() => sessionClient?.status?.({
3429
+ directory
3430
+ }) ?? Promise.resolve({
3431
+ data: {}
3432
+ }));
3433
+ if (!response) {
3434
+ topLevelHydrationFailed = true;
3435
+ statusHydrationFailed = true;
3436
+ }
3437
+ return response;
3438
+ })()]);
3439
+ const children = Array.isArray(childrenResp?.data) ? childrenResp.data : [];
3440
+ const messages = Array.isArray(messagesResp?.data) ? messagesResp.data : [];
3441
+ const allStatuses = asRecord2(statusResp?.data) ?? {};
3442
+ const parentTaskEvidenceByChildID = collectParentTaskEvidenceByChildSessionID(messages, currentSessionID);
3443
+ let childHydrationFailed = false;
3444
+ const childMessageResults = await Promise.all(children.map(async (child) => {
3445
+ const session = asRecord2(child);
3446
+ const childID = typeof session?.id === "string" ? session.id : void 0;
3447
+ if (!childID) {
3448
+ return {
3449
+ childID: void 0,
3450
+ completedAt: void 0,
3451
+ evidenceAt: void 0,
3452
+ hasError: false,
3453
+ fetchFailed: false
3454
+ };
3455
+ }
3456
+ const childMessagesResp = await safeReadAsync(() => sessionClient?.messages?.({
3457
+ sessionID: childID,
3458
+ directory
3459
+ }) ?? Promise.resolve({
3460
+ data: []
3461
+ }));
3462
+ let fetchFailed = false;
3463
+ if (!childMessagesResp) {
3464
+ childHydrationFailed = true;
3465
+ fetchFailed = true;
3466
+ }
3467
+ const childMessages = Array.isArray(childMessagesResp?.data) ? childMessagesResp.data : [];
3468
+ return {
3469
+ childID,
3470
+ ...summarizeSessionMessages(childMessages),
3471
+ model: extractLatestAssistantModel(childMessages),
3472
+ fetchFailed
3473
+ };
3474
+ }));
3475
+ const childMessageSummaryByID = new Map(childMessageResults.filter((result) => result.childID).map((result) => [result.childID, result]));
3476
+ setState((current) => {
3477
+ const next = cloneState(current);
3478
+ let changed = false;
3479
+ for (const rawSession of children) {
3480
+ const session = asRecord2(rawSession);
3481
+ if (!session || typeof session.id !== "string") continue;
3482
+ const status = allStatuses[session.id];
3483
+ const sessionStatus = deriveSessionChildStatus(status);
3484
+ const childSummary = childMessageSummaryByID.get(session.id);
3485
+ const hasHydrationEvidence = shouldHydrateSessionChild({
3486
+ childID: session.id,
3487
+ sessionStatus,
3488
+ childSummary,
3489
+ parentTaskEvidenceByChildID
3490
+ });
3491
+ const parentTaskEvidence = parentTaskEvidenceByChildID.get(session.id);
3492
+ const explicitCompletionEvidence = !!childSummary && !childSummary.fetchFailed && (typeof childSummary.completedAt === "string" || childSummary.hasError);
3493
+ const fallbackEndedAt = childSummary?.completedAt ?? childSummary?.evidenceAt;
3494
+ const statusEndedAt = fallbackEndedAt ?? sessionTimestamp(session, "completed") ?? sessionTimestamp(session, "updated");
3495
+ const shouldHydrateChildFromSession = hasHydrationEvidence;
3496
+ if (!shouldHydrateChildFromSession) {
3497
+ const existing = next.children[session.id];
3498
+ if (!statusHydrationFailed && !parentMessageHydrationFailed && !!childSummary && !childSummary.fetchFailed && existing?.parentID === currentSessionID && existing.source === "session" && existing.status === "running") {
3499
+ delete next.children[session.id];
3500
+ changed = true;
3501
+ }
3502
+ continue;
3503
+ }
3504
+ const fakeEvent = {
3505
+ type: "session.created",
3506
+ properties: {
3507
+ sessionID: session.id,
3508
+ info: session
3509
+ }
3510
+ };
3511
+ if (applySubagentEvent(next, fakeEvent)) changed = true;
3512
+ if (childSummary?.model) {
3513
+ changed = setChildModel(next, session.id, childSummary.model.model, childSummary.model.updatedAt) || changed;
3514
+ }
3515
+ const resolvedStatus = resolveSessionStatusWithMessageSummary({
3516
+ status: sessionStatus ?? parentTaskEvidence?.status,
3517
+ summary: childSummary
3518
+ });
3519
+ if (resolvedStatus.status === "done" || resolvedStatus.status === "error") {
3520
+ if (markChildStatus(next, session.id, resolvedStatus.status, resolvedStatus.endedAt ?? parentTaskEvidence?.endedAt ?? statusEndedAt)) changed = true;
3521
+ continue;
3522
+ }
3523
+ if (!sessionStatus && !statusHydrationFailed && explicitCompletionEvidence) {
3524
+ const childStatus = childSummary?.hasError ? "error" : "done";
3525
+ if (markChildStatus(next, session.id, childStatus, fallbackEndedAt)) changed = true;
3526
+ }
3527
+ }
3528
+ for (const rawMessage of messages) {
3529
+ const message = asRecord2(rawMessage);
3530
+ const info = asRecord2(message?.info);
3531
+ const parts = Array.isArray(message?.parts) ? message.parts : [];
3532
+ const parentMessageID = messageIDOf(message);
3533
+ const isAssistant = info?.role === "assistant";
3534
+ const time = asRecord2(info?.time);
3535
+ const eventInfo = {
3536
+ id: typeof info?.id === "string" ? info.id : void 0,
3537
+ role: typeof info?.role === "string" ? info.role : void 0,
3538
+ parentID: typeof info?.parentID === "string" ? info.parentID : void 0,
3539
+ time
3540
+ };
3541
+ const completedAt = timestampFromUnknown2(time?.completed);
3542
+ const isCompleted = typeof completedAt === "string";
3543
+ const hasError = !!info?.error;
3544
+ for (const rawPart of parts) {
3545
+ const part = asRecord2(rawPart);
3546
+ if (!part) continue;
3547
+ const partWithMessageID = typeof part.messageID === "string" && part.messageID.length > 0 ? part : parentMessageID ? {
3548
+ ...part,
3549
+ messageID: parentMessageID
3550
+ } : part;
3551
+ if (part.type === "subtask" || part.type === "tool" && (part.tool === "delegate" || part.tool === "task")) {
3552
+ const fakeEvent = {
3553
+ type: "message.part.updated",
3554
+ properties: {
3555
+ sessionID: currentSessionID,
3556
+ info: eventInfo,
3557
+ part: partWithMessageID
3558
+ }
3559
+ };
3560
+ if (applySubagentEvent(next, fakeEvent)) changed = true;
3561
+ if (part.type === "subtask" && isAssistant && isCompleted) {
3562
+ const childID = `subtask:${part.id}`;
3563
+ const status = hasError ? "error" : "done";
3564
+ if (markChildStatus(next, childID, status, completedAt)) changed = true;
3565
+ }
3566
+ }
3567
+ }
3568
+ }
3569
+ if (backfillHydratedTargetSessionIDs(next, currentSessionID)) {
3570
+ changed = true;
3571
+ }
3572
+ const refreshed = refreshLiveState(next);
3573
+ if (!changed && !refreshed) return current;
3574
+ persistStateSnapshot(statePath, textPath, next);
3575
+ return next;
3576
+ });
3577
+ if (topLevelHydrationFailed || childHydrationFailed) return false;
3578
+ return true;
3579
+ } catch (err) {
3580
+ debugLog({
3581
+ kind: "hydration.error",
3582
+ sessionID: currentSessionID,
3583
+ error: String(err)
3584
+ });
3585
+ return false;
3586
+ }
3587
+ }
3588
+ function shouldHydrateSessionChild(input) {
3589
+ if (input.sessionStatus) return true;
3590
+ if (input.parentTaskEvidenceByChildID.has(input.childID)) return true;
3591
+ const summary = input.childSummary;
3592
+ if (!summary || summary.fetchFailed) return false;
3593
+ return summary.hasError === true || typeof summary.completedAt === "string" || typeof summary.evidenceAt === "string" || typeof summary.latestAssistantActivityAt === "string" || typeof summary.latestMessageActivityAt === "string";
3594
+ }
3595
+ function collectParentTaskEvidenceByChildSessionID(messages, parentSessionID) {
3596
+ const evidenceByID = /* @__PURE__ */ new Map();
3597
+ for (const rawMessage of messages) {
3598
+ const message = asRecord2(rawMessage);
3599
+ const info = asRecord2(message?.info);
3600
+ const parts = Array.isArray(message?.parts) ? message.parts : [];
3601
+ for (const rawPart of parts) {
3602
+ const part = asRecord2(rawPart);
3603
+ if (!part || part.type !== "tool" || part.tool !== "task") continue;
3604
+ const state = asRecord2(part.state);
3605
+ const metadata = asRecord2(state?.metadata);
3606
+ const childID = typeof metadata?.sessionId === "string" ? metadata.sessionId : void 0;
3607
+ if (!childID || childID === parentSessionID) continue;
3608
+ const taskEvidence = extractTaskToolEvidence({
3609
+ type: "message.part.updated",
3610
+ properties: {
3611
+ sessionID: parentSessionID,
3612
+ info: {
3613
+ time: info?.time
3614
+ },
3615
+ part: rawPart
3616
+ }
3617
+ });
3618
+ evidenceByID.set(childID, {
3619
+ status: taskEvidence?.status ?? "running",
3620
+ endedAt: taskEvidence?.endedAt
3621
+ });
3622
+ }
3623
+ }
3624
+ return evidenceByID;
3625
+ }
3626
+ async function safeReadAsync(read) {
3627
+ try {
3628
+ return await read();
3629
+ } catch {
3630
+ return void 0;
3631
+ }
3632
+ }
3633
+ function deriveSessionChildStatus(status) {
3634
+ return deriveOpenCodeSessionStatus(status);
3635
+ }
3636
+ function sessionTimestamp(session, key) {
3637
+ const time = asRecord2(session.time);
3638
+ return timestampFromUnknown2(time?.[key]);
3639
+ }
3640
+ function timestampFromUnknown2(value) {
3641
+ const millis = timestampMillisFromUnknown2(value);
3642
+ return millis === void 0 ? void 0 : new Date(millis).toISOString();
3643
+ }
3644
+ function timestampMillisFromUnknown2(value) {
3645
+ if (typeof value === "string") {
3646
+ const parsed = Date.parse(value);
3647
+ return Number.isNaN(parsed) ? void 0 : parsed;
3648
+ }
3649
+ if (typeof value === "number" && Number.isFinite(value) && value > 0) {
3650
+ const millis = value < 1e10 ? value * 1e3 : value;
3651
+ const parsed = new Date(millis);
3652
+ return Number.isNaN(parsed.getTime()) ? void 0 : millis;
3653
+ }
3654
+ return void 0;
3655
+ }
3656
+ function resolveRouteSessionID(api) {
3657
+ return api.route.current.name === "session" && typeof api.route.current.params?.sessionID === "string" ? api.route.current.params.sessionID : void 0;
3658
+ }
3659
+ function resolveRunningChildAgeMillis(child, nowMs) {
3660
+ const startedMs = Date.parse(child.startedAt);
3661
+ const updatedMs = Date.parse(child.updatedAt);
3662
+ return {
3663
+ startedMs: Number.isNaN(startedMs) ? 0 : Math.max(0, nowMs - startedMs),
3664
+ updatedMs: Number.isNaN(updatedMs) ? 0 : Math.max(0, nowMs - updatedMs)
3665
+ };
3666
+ }
3667
+ function resolveReconcileTargetSessionID(state, child) {
3668
+ return resolveChildTargetSessionID(child) ?? resolveSyntheticTargetFromHydratedState(state, child);
3669
+ }
3670
+ function selectRunningReconcileCandidates(input) {
3671
+ const runningChildren = Object.values(input.state.children).filter((child) => child.status === "running");
3672
+ if (runningChildren.length === 0) return [];
3673
+ const prioritized = visibleSubagentWorkItems(runningChildren, input.nowMs).sort(byPriority);
3674
+ const prioritizedForSession = prioritized.filter((child) => input.currentSessionID ? child.parentID === input.currentSessionID : true);
3675
+ const veryOldIDs = new Set(runningChildren.filter((child) => {
3676
+ const age = resolveRunningChildAgeMillis(child, input.nowMs);
3677
+ return age.startedMs >= RUNNING_RECONCILE_OLD_CANDIDATE_AGE_MS || age.updatedMs >= RUNNING_RECONCILE_OLD_CANDIDATE_AGE_MS;
3678
+ }).map((child) => child.id));
3679
+ const ordered = [...prioritizedForSession, ...runningChildren.filter((child) => veryOldIDs.has(child.id))];
3680
+ const selected = [];
3681
+ const seen = /* @__PURE__ */ new Set();
3682
+ for (const child of ordered) {
3683
+ if (seen.has(child.id)) continue;
3684
+ seen.add(child.id);
3685
+ const age = resolveRunningChildAgeMillis(child, input.nowMs);
3686
+ const targetSessionID = resolveReconcileTargetSessionID(input.state, child);
3687
+ const canProbePersistedSubtask = child.source === "subtask" && !targetSessionID && typeof child.parentID === "string" && child.parentID.length > 0 && typeof child.messageID === "string" && child.messageID.length > 0 && (age.startedMs >= RUNNING_RECONCILE_OLD_CANDIDATE_AGE_MS || age.updatedMs >= RUNNING_RECONCILE_OLD_CANDIDATE_AGE_MS);
3688
+ if (!targetSessionID && !canProbePersistedSubtask) continue;
3689
+ selected.push({
3690
+ childID: child.id,
3691
+ targetSessionID,
3692
+ parentID: child.parentID,
3693
+ messageID: child.messageID,
3694
+ source: child.source,
3695
+ title: child.title,
3696
+ summary: child.summary,
3697
+ agentName: child.agentName,
3698
+ startedMs: age.startedMs,
3699
+ updatedMs: age.updatedMs
3700
+ });
3701
+ if (selected.length >= input.maxCandidates) break;
3702
+ }
3703
+ return capCandidates(selected, input.maxCandidates);
3704
+ }
3705
+ async function probeRunningEvidence(input) {
3706
+ let probeFailed = false;
3707
+ const directStatus = safeRead2(() => input.api.state.session.status(input.targetSessionID));
3708
+ if (directStatus === void 0) probeFailed = true;
3709
+ const statusFromState = deriveSessionChildStatus(directStatus);
3710
+ if (statusFromState === "error") {
3711
+ return {
3712
+ status: statusFromState,
3713
+ endedAt: (/* @__PURE__ */ new Date()).toISOString()
3714
+ };
3715
+ }
3716
+ if (statusFromState === "running") {
3717
+ return {
3718
+ status: "running",
3719
+ sawRunningEvidence: true
3720
+ };
3721
+ }
3722
+ const doneFromState = statusFromState === "done";
3723
+ let doneFromClient = false;
3724
+ const statusResp = await safeReadAsync(() => input.api.client.session.status({
3725
+ directory: input.directory
3726
+ }));
3727
+ if (statusResp === void 0) probeFailed = true;
3728
+ const statuses = asRecord2(statusResp?.data);
3729
+ const statusFromClient = deriveSessionChildStatus(statuses?.[input.targetSessionID]);
3730
+ if (statusFromClient === "error") {
3731
+ return {
3732
+ status: statusFromClient,
3733
+ endedAt: (/* @__PURE__ */ new Date()).toISOString()
3734
+ };
3735
+ }
3736
+ if (statusFromClient === "running") {
3737
+ return {
3738
+ status: "running",
3739
+ sawRunningEvidence: true
3740
+ };
3741
+ }
3742
+ doneFromClient = statusFromClient === "done";
3743
+ const hasDoneStatus = doneFromState || doneFromClient;
3744
+ if (!hasDoneStatus && input.candidateAgeMs < RUNNING_RECONCILE_MESSAGE_AGE_GATE_MS) {
3745
+ return {
3746
+ probeFailed,
3747
+ canApplyStaleFallback: false
3748
+ };
3749
+ }
3750
+ const messagesResp = await safeReadAsync(() => input.api.client.session.messages({
3751
+ sessionID: input.targetSessionID,
3752
+ directory: input.directory
3753
+ }));
3754
+ if (messagesResp === void 0 || !Array.isArray(messagesResp?.data)) {
3755
+ if (hasDoneStatus) {
3756
+ return {
3757
+ status: "done",
3758
+ endedAt: (/* @__PURE__ */ new Date()).toISOString(),
3759
+ checkedMessages: false,
3760
+ probeFailed: true,
3761
+ canApplyStaleFallback: false
3762
+ };
3763
+ }
3764
+ return {
3765
+ checkedMessages: false,
3766
+ probeFailed: true,
3767
+ canApplyStaleFallback: false
3768
+ };
3769
+ }
3770
+ const messages = Array.isArray(messagesResp?.data) ? messagesResp.data : [];
3771
+ const summary = summarizeSessionMessages(messages);
3772
+ const resolvedStatus = resolveSessionStatusWithMessageSummary({
3773
+ status: hasDoneStatus ? "done" : void 0,
3774
+ summary
3775
+ });
3776
+ if (resolvedStatus.status === "error") {
3777
+ return {
3778
+ status: "error",
3779
+ endedAt: resolvedStatus.endedAt,
3780
+ checkedMessages: true,
3781
+ canApplyStaleFallback: false
3782
+ };
3783
+ }
3784
+ if (resolvedStatus.status === "done") {
3785
+ return {
3786
+ status: "done",
3787
+ endedAt: resolvedStatus.endedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
3788
+ checkedMessages: true,
3789
+ canApplyStaleFallback: false
3790
+ };
3791
+ }
3792
+ if (hasRecentMessageActivity({
3793
+ nowMs: input.nowMs,
3794
+ latestMessageActivityAtMs: summary.latestMessageActivityAtMs,
3795
+ staleThresholdMs: STALE_RUNNING_THRESHOLD_MS
3796
+ })) {
3797
+ return {
3798
+ checkedMessages: true,
3799
+ sawRunningEvidence: true,
3800
+ endedAt: summary.latestMessageActivityAt,
3801
+ probeFailed,
3802
+ canApplyStaleFallback: false
3803
+ };
3804
+ }
3805
+ return {
3806
+ checkedMessages: true,
3807
+ probeFailed,
3808
+ canApplyStaleFallback: !probeFailed
3809
+ };
3810
+ }
3811
+ function initializeTui(api, disposeRoot) {
3812
+ const view = createSidebarViewController();
3813
+ onCleanup2(() => view.dispose());
3814
+ const statePath = resolveStatePath();
3815
+ const textPath = resolveTextPath(statePath);
3816
+ const [state, setState] = createSignal2(createEmptyState());
3817
+ const [nowMs, setNowMs] = createSignal2(Date.now());
3818
+ const [hydratedSessions, setHydratedSessions] = createSignal2(/* @__PURE__ */ new Set());
3819
+ const [hydratingSessions, setHydratingSessions] = createSignal2(/* @__PURE__ */ new Set());
3820
+ const [hydrateRetryPendingSessions, setHydrateRetryPendingSessions] = createSignal2(/* @__PURE__ */ new Set());
3821
+ const [hydrateRetryAttempts, setHydrateRetryAttempts] = createSignal2(/* @__PURE__ */ new Map());
3822
+ const [hydrateRetryTick, setHydrateRetryTick] = createSignal2(0);
3823
+ const [subagentsExpanded, setSubagentsExpanded] = createSignal2(api.kv.get(SUBAGENTS_EXPANDED_KV_KEY, true) !== false);
3824
+ const [subagentsSectionEnabled, setSubagentsSectionEnabled] = createSignal2(api.kv.get(SUBAGENTS_SECTION_ENABLED_KV_KEY, true) !== false);
3825
+ const hydrateRetryTimeouts = /* @__PURE__ */ new Map();
3826
+ const runningReconcileBackoff = /* @__PURE__ */ new Map();
3827
+ let reconcileInFlight = false;
3828
+ let lastRunningReconcileAtMs = 0;
3829
+ let disposed = false;
3830
+ let previousRouteSessionID;
3831
+ let pendingSidebarRefocus;
3832
+ let pendingRefocusConsumed = false;
3833
+ let activePromptRef;
3834
+ const consumePendingSidebarRefocus = () => {
3835
+ if (pendingRefocusConsumed) return void 0;
3836
+ pendingRefocusConsumed = true;
3837
+ return pendingSidebarRefocus;
3838
+ };
3839
+ const setActivePromptRef = (ref) => {
3840
+ activePromptRef = ref;
3841
+ };
3842
+ const composePromptRef = (slotRef) => {
3843
+ return (ref) => {
3844
+ setActivePromptRef(ref);
3845
+ if (typeof slotRef === "function") {
3846
+ slotRef(ref);
3847
+ } else if (slotRef && "current" in slotRef) {
3848
+ slotRef.current = ref;
3849
+ }
3850
+ };
3851
+ };
3852
+ const focusActivePrompt = () => {
3853
+ focusPromptWithDeferredRetry(() => {
3854
+ if (!activePromptRef) return false;
3855
+ activePromptRef.focus();
3856
+ return true;
3857
+ });
3858
+ };
3859
+ const rememberSidebarChildNavigation = (input) => {
3860
+ pendingSidebarRefocus = input;
3861
+ };
3862
+ const setSubagentsExpandedPreference = (expanded) => {
3863
+ setSubagentsExpanded(expanded);
3864
+ api.kv.set(SUBAGENTS_EXPANDED_KV_KEY, expanded);
3865
+ api.ui.toast({
3866
+ variant: "info",
3867
+ message: expanded ? "Subagent list expanded" : "Subagent list collapsed"
3868
+ });
3869
+ };
3870
+ const setSubagentsExpandedSilently = (expanded) => {
3871
+ setSubagentsExpanded(expanded);
3872
+ api.kv.set(SUBAGENTS_EXPANDED_KV_KEY, expanded);
3873
+ };
3874
+ const setSubagentsSectionEnabledPreference = (enabled) => {
3875
+ setSubagentsSectionEnabled(enabled);
3876
+ api.kv.set(SUBAGENTS_SECTION_ENABLED_KV_KEY, enabled);
3877
+ api.ui.toast({
3878
+ variant: "info",
3879
+ message: enabled ? "Subagent section enabled" : "Subagent section disabled"
3880
+ });
3881
+ };
3882
+ const toggleSidebarListFocus = () => {
3883
+ api.ui.dialog.clear();
3884
+ if (view.isListFocused()) {
3885
+ view.blurList();
3886
+ focusActivePrompt();
3887
+ return;
3888
+ }
3889
+ setSubagentsSectionEnabled(true);
3890
+ setSubagentsExpanded(true);
3891
+ api.kv.set(SUBAGENTS_SECTION_ENABLED_KV_KEY, true);
3892
+ api.kv.set(SUBAGENTS_EXPANDED_KV_KEY, true);
3893
+ setTimeout(() => {
3894
+ view.focusList();
3895
+ }, 0);
3896
+ };
3897
+ const toggleSidebarCompletedHistory = () => {
3898
+ api.ui.dialog.clear();
3899
+ setSubagentsSectionEnabled(true);
3900
+ setSubagentsExpanded(true);
3901
+ api.kv.set(SUBAGENTS_SECTION_ENABLED_KV_KEY, true);
3902
+ api.kv.set(SUBAGENTS_EXPANDED_KV_KEY, true);
3903
+ setTimeout(() => {
3904
+ view.toggleCompletedHistory();
3905
+ }, 0);
3906
+ };
3907
+ const commandDispose = registerSubagentCommands({
3908
+ api,
3909
+ sectionEnabled: subagentsSectionEnabled,
3910
+ toggleSection: setSubagentsSectionEnabledPreference,
3911
+ focusSidebarList: toggleSidebarListFocus,
3912
+ toggleCompletedHistory: toggleSidebarCompletedHistory
3913
+ });
3914
+ const clearHydrateRetryTimeout = (sessionID) => {
3915
+ const timeout = hydrateRetryTimeouts.get(sessionID);
3916
+ if (timeout) {
3917
+ clearTimeout(timeout);
3918
+ hydrateRetryTimeouts.delete(sessionID);
3919
+ }
3920
+ };
3921
+ const resetHydrateRetry = (sessionID) => {
3922
+ if (!sessionID) return;
3923
+ clearHydrateRetryTimeout(sessionID);
3924
+ setHydrateRetryPendingSessions((prev) => {
3925
+ if (!prev.has(sessionID)) return prev;
3926
+ const next = new Set(prev);
3927
+ next.delete(sessionID);
3928
+ return next;
3929
+ });
3930
+ setHydrateRetryAttempts((prev) => {
3931
+ if (!prev.has(sessionID)) return prev;
3932
+ const next = new Map(prev);
3933
+ next.delete(sessionID);
3934
+ return next;
3935
+ });
3936
+ };
3937
+ createEffect2(() => {
3938
+ hydrateRetryTick();
3939
+ void api.route.current;
3940
+ const routeSessionID = resolveRouteSessionID(api);
3941
+ if (previousRouteSessionID && previousRouteSessionID !== routeSessionID) {
3942
+ resetHydrateRetry(previousRouteSessionID);
3943
+ }
3944
+ const siblingRefocus = resolveSiblingSidebarRefocus({
3945
+ pendingSidebarRefocus,
3946
+ routeSessionID,
3947
+ children: state().children
3948
+ });
3949
+ if (siblingRefocus && pendingSidebarRefocus) {
3950
+ pendingSidebarRefocus = {
3951
+ ...pendingSidebarRefocus,
3952
+ ...siblingRefocus
3953
+ };
3954
+ }
3955
+ const sidebarReturnAction = resolveSidebarReturnFocusAction({
3956
+ pendingSidebarRefocus,
3957
+ previousRouteSessionID,
3958
+ routeSessionID
3959
+ });
3960
+ pendingRefocusConsumed = false;
3961
+ if (sidebarReturnAction === "focus-prompt") {
3962
+ view.blurList();
3963
+ focusActivePrompt();
3964
+ } else if (sidebarReturnAction === "clear-pending") {
3965
+ pendingSidebarRefocus = void 0;
3966
+ }
3967
+ previousRouteSessionID = routeSessionID;
3968
+ if (!routeSessionID) return;
3969
+ const sessionID = routeSessionID;
3970
+ if (hydratedSessions().has(sessionID) || hydratingSessions().has(sessionID) || hydrateRetryPendingSessions().has(sessionID)) {
3971
+ return;
3972
+ }
3973
+ setHydratingSessions((prev) => {
3974
+ const next = new Set(prev);
3975
+ next.add(sessionID);
3976
+ return next;
3977
+ });
3978
+ void (async () => {
3979
+ const finishHydrating = () => {
3980
+ setHydratingSessions((prev) => {
3981
+ const next = new Set(prev);
3982
+ next.delete(sessionID);
3983
+ return next;
3984
+ });
3985
+ };
3986
+ const hydrated = await hydratePreviousSubagents(api, sessionID, statePath, textPath, (update) => {
3987
+ view.snapshotScroll();
3988
+ setState(update);
3989
+ });
3990
+ if (disposed) {
3991
+ clearHydrateRetryTimeout(sessionID);
3992
+ finishHydrating();
3993
+ return;
3994
+ }
3995
+ if (hydrated) {
3996
+ resetHydrateRetry(sessionID);
3997
+ setHydratedSessions((prev) => {
3998
+ const next = new Set(prev);
3999
+ next.add(sessionID);
4000
+ return next;
4001
+ });
4002
+ finishHydrating();
4003
+ return;
4004
+ }
4005
+ const attempts = hydrateRetryAttempts().get(sessionID) ?? 0;
4006
+ const delayMs = Math.min(HYDRATE_RETRY_MAX_DELAY_MS, HYDRATE_RETRY_BASE_DELAY_MS * 2 ** attempts);
4007
+ setHydrateRetryAttempts((prev) => {
4008
+ const next = new Map(prev);
4009
+ next.set(sessionID, Math.min(attempts + 1, HYDRATE_RETRY_MAX_ATTEMPTS));
4010
+ return next;
4011
+ });
4012
+ setHydrateRetryPendingSessions((prev) => {
4013
+ const next = new Set(prev);
4014
+ next.add(sessionID);
4015
+ return next;
4016
+ });
4017
+ finishHydrating();
4018
+ clearHydrateRetryTimeout(sessionID);
4019
+ const timeout = setTimeout(() => {
4020
+ hydrateRetryTimeouts.delete(sessionID);
4021
+ setHydrateRetryPendingSessions((prev) => {
4022
+ if (!prev.has(sessionID)) return prev;
4023
+ const next = new Set(prev);
4024
+ next.delete(sessionID);
4025
+ return next;
4026
+ });
4027
+ if (disposed) return;
4028
+ setHydrateRetryTick((value) => value + 1);
4029
+ }, delayMs);
4030
+ hydrateRetryTimeouts.set(sessionID, timeout);
4031
+ })();
4032
+ });
4033
+ const reconcileRunningChildren = async () => {
4034
+ if (reconcileInFlight || disposed) return;
4035
+ reconcileInFlight = true;
4036
+ lastRunningReconcileAtMs = Date.now();
4037
+ try {
4038
+ const snapshot = cloneState(state());
4039
+ const nowMs2 = Date.now();
4040
+ const currentSessionID = resolveRouteSessionID(api);
4041
+ const directory = api.state.path.directory;
4042
+ const selected = selectRunningReconcileCandidates({
4043
+ state: snapshot,
4044
+ currentSessionID,
4045
+ nowMs: nowMs2,
4046
+ maxCandidates: RUNNING_RECONCILE_MAX_CANDIDATES
4047
+ });
4048
+ const mutations = [];
4049
+ const parentMessagesCache = /* @__PURE__ */ new Map();
4050
+ for (const candidate of selected) {
4051
+ const key = candidate.targetSessionID ?? candidate.childID;
4052
+ const cache = runningReconcileBackoff.get(key);
4053
+ if (shouldSkipCandidateForBackoff(cache, nowMs2)) continue;
4054
+ if (!candidate.targetSessionID) {
4055
+ const isPersistedSubtaskCandidate = candidate.source === "subtask" && typeof candidate.parentID === "string" && candidate.parentID.length > 0 && typeof candidate.messageID === "string" && candidate.messageID.length > 0;
4056
+ if (!isPersistedSubtaskCandidate) continue;
4057
+ const parentSessionID = candidate.parentID;
4058
+ let parentMessages = parentMessagesCache.get(parentSessionID);
4059
+ if (parentMessages === void 0) {
4060
+ const parentMessagesResp = await safeReadAsync(() => api.client.session.messages({
4061
+ sessionID: parentSessionID,
4062
+ directory
4063
+ }));
4064
+ parentMessages = Array.isArray(parentMessagesResp?.data) ? parentMessagesResp.data : null;
4065
+ parentMessagesCache.set(parentSessionID, parentMessages);
4066
+ }
4067
+ if (parentMessages === null) {
4068
+ runningReconcileBackoff.set(key, nextBackoffState({
4069
+ cache,
4070
+ nowMs: nowMs2,
4071
+ initialBackoffMs: RUNNING_RECONCILE_INITIAL_BACKOFF_MS,
4072
+ maxBackoffMs: RUNNING_RECONCILE_MAX_BACKOFF_MS
4073
+ }));
4074
+ continue;
4075
+ }
4076
+ const evidence2 = resolvePersistedStaleSubtaskFromParentMessages({
4077
+ candidate: {
4078
+ childID: candidate.childID,
4079
+ parentID: candidate.parentID,
4080
+ messageID: candidate.messageID,
4081
+ title: candidate.title,
4082
+ summary: candidate.summary,
4083
+ agentName: candidate.agentName
4084
+ },
4085
+ messages: parentMessages
4086
+ });
4087
+ if (!evidence2) {
4088
+ const parentSummary = summarizeSessionMessages(parentMessages);
4089
+ const canSafelyFallbackByParentInactivity = canSafelyCloseNoTargetPersistedCandidate({
4090
+ nowMs: nowMs2,
4091
+ staleThresholdMs: STALE_RUNNING_THRESHOLD_MS,
4092
+ startedMs: candidate.startedMs,
4093
+ updatedMs: candidate.updatedMs,
4094
+ latestMessageActivityAtMs: parentSummary.latestMessageActivityAtMs
4095
+ });
4096
+ if (canSafelyFallbackByParentInactivity) {
4097
+ mutations.push({
4098
+ childID: candidate.childID,
4099
+ targetSessionID: candidate.childID,
4100
+ status: "done",
4101
+ endedAt: parentSummary.latestMessageActivityAt ?? new Date(nowMs2 - candidate.updatedMs).toISOString(),
4102
+ reconcileWithoutTargetSessionID: true
4103
+ });
4104
+ runningReconcileBackoff.delete(key);
4105
+ continue;
4106
+ }
4107
+ runningReconcileBackoff.set(key, nextBackoffState({
4108
+ cache,
4109
+ nowMs: nowMs2,
4110
+ initialBackoffMs: RUNNING_RECONCILE_INITIAL_BACKOFF_MS,
4111
+ maxBackoffMs: RUNNING_RECONCILE_MAX_BACKOFF_MS
4112
+ }));
4113
+ continue;
4114
+ }
4115
+ mutations.push({
4116
+ childID: candidate.childID,
4117
+ targetSessionID: evidence2.targetSessionID ?? candidate.childID,
4118
+ status: evidence2.status,
4119
+ endedAt: evidence2.endedAt,
4120
+ reconcileWithoutTargetSessionID: true
4121
+ });
4122
+ runningReconcileBackoff.delete(key);
4123
+ continue;
4124
+ }
4125
+ const evidence = await probeRunningEvidence({
4126
+ api,
4127
+ targetSessionID: candidate.targetSessionID,
4128
+ directory,
4129
+ candidateAgeMs: Math.max(candidate.startedMs, candidate.updatedMs),
4130
+ nowMs: nowMs2
4131
+ });
4132
+ if (evidence.status === "done" || evidence.status === "error") {
4133
+ mutations.push({
4134
+ childID: candidate.childID,
4135
+ targetSessionID: candidate.targetSessionID,
4136
+ status: evidence.status,
4137
+ endedAt: evidence.endedAt
4138
+ });
4139
+ runningReconcileBackoff.delete(key);
4140
+ continue;
4141
+ }
4142
+ if (evidence.sawRunningEvidence) {
4143
+ runningReconcileBackoff.set(key, {
4144
+ backoffMs: RUNNING_RECONCILE_INITIAL_BACKOFF_MS,
4145
+ nextAllowedAtMs: nowMs2 + RUNNING_RECONCILE_INITIAL_BACKOFF_MS
4146
+ });
4147
+ continue;
4148
+ }
4149
+ const shouldApplyFallback = shouldApplyStaleRunningFallback({
4150
+ staleThresholdMs: STALE_RUNNING_THRESHOLD_MS,
4151
+ evidence,
4152
+ startedMs: candidate.startedMs,
4153
+ updatedMs: candidate.updatedMs
4154
+ });
4155
+ if (shouldApplyFallback) {
4156
+ mutations.push({
4157
+ childID: candidate.childID,
4158
+ targetSessionID: candidate.targetSessionID,
4159
+ status: "done",
4160
+ endedAt: new Date(nowMs2 - candidate.updatedMs).toISOString()
4161
+ });
4162
+ runningReconcileBackoff.delete(key);
4163
+ continue;
4164
+ }
4165
+ runningReconcileBackoff.set(key, nextBackoffState({
4166
+ cache,
4167
+ nowMs: nowMs2,
4168
+ initialBackoffMs: RUNNING_RECONCILE_INITIAL_BACKOFF_MS,
4169
+ maxBackoffMs: RUNNING_RECONCILE_MAX_BACKOFF_MS
4170
+ }));
4171
+ }
4172
+ if (mutations.length === 0) return;
4173
+ view.snapshotScroll();
4174
+ setState((current) => {
4175
+ const next = cloneState(current);
4176
+ let changed = false;
4177
+ for (const mutation of mutations) {
4178
+ if (mutation.reconcileWithoutTargetSessionID && mutation.targetSessionID.startsWith("ses_")) {
4179
+ changed = upsertChildDetails(next, mutation.childID, {
4180
+ targetSessionID: mutation.targetSessionID,
4181
+ updatedAt: mutation.endedAt
4182
+ }) || changed;
4183
+ }
4184
+ if (markChildStatus(next, mutation.reconcileWithoutTargetSessionID ? mutation.childID : mutation.targetSessionID, mutation.status, mutation.endedAt)) {
4185
+ changed = true;
4186
+ }
4187
+ }
4188
+ const refreshed = refreshLiveState(next);
4189
+ if (!changed && !refreshed) return current;
4190
+ persistStateSnapshot(statePath, textPath, next);
4191
+ return next;
4192
+ });
4193
+ } finally {
4194
+ reconcileInFlight = false;
4195
+ }
4196
+ };
4197
+ const timers = createTuiMaintenanceTimers({
4198
+ onElapsedTick: () => {
4199
+ view.snapshotScroll();
4200
+ setNowMs(Date.now());
4201
+ },
4202
+ onMaintenanceTick: () => {
4203
+ const currentNowMs = Date.now();
4204
+ if (currentNowMs - lastRunningReconcileAtMs >= RUNNING_RECONCILE_MAINTENANCE_INTERVAL_MS) {
4205
+ void reconcileRunningChildren();
4206
+ }
4207
+ setState((current) => {
4208
+ const next = runTuiStateMaintenance(api, current);
4209
+ if (next === current) return current;
4210
+ view.snapshotScroll();
4211
+ persistStateSnapshot(statePath, textPath, next);
4212
+ return next;
4213
+ });
4214
+ }
4215
+ });
4216
+ createEffect2(() => {
4217
+ timers.syncElapsedTimer(Object.values(state().children).some((child) => child.status === "running"));
4218
+ });
4219
+ const applyEvent = (event) => {
4220
+ debugEvent(event);
4221
+ view.snapshotScroll();
4222
+ setState((current) => {
4223
+ const next = cloneState(current);
4224
+ const changed = applySubagentEvent(next, event);
4225
+ const hydrated = hydrateStateTokensFromTuiState(api, next);
4226
+ if (changed) {
4227
+ debugLog({
4228
+ kind: "state.changed",
4229
+ children: Object.values(next.children).map((child) => ({
4230
+ id: child.id,
4231
+ parentID: child.parentID,
4232
+ title: child.title,
4233
+ status: child.status,
4234
+ source: child.source
4235
+ }))
4236
+ });
4237
+ }
4238
+ const refreshed = refreshLiveState(next);
4239
+ if (!changed && !hydrated && !refreshed) return current;
4240
+ persistStateSnapshot(statePath, textPath, next);
4241
+ return next;
4242
+ });
4243
+ };
4244
+ const disposers = [api.event.on("session.created", applyEvent), api.event.on("session.updated", applyEvent), api.event.on("session.status", applyEvent), api.event.on("session.idle", applyEvent), api.event.on("session.error", applyEvent), api.event.on("message.updated", applyEvent), api.event.on("message.part.updated", applyEvent)];
4245
+ api.lifecycle.onDispose(() => {
4246
+ disposed = true;
4247
+ timers.dispose();
4248
+ for (const timeout of hydrateRetryTimeouts.values()) {
4249
+ clearTimeout(timeout);
4250
+ }
4251
+ hydrateRetryTimeouts.clear();
4252
+ commandDispose();
4253
+ for (const dispose of disposers) {
4254
+ dispose();
4255
+ }
4256
+ disposeRoot();
4257
+ });
4258
+ function SidebarContent(props) {
4259
+ const {
4260
+ ctx,
4261
+ sessionID
4262
+ } = props;
4263
+ debugLog({
4264
+ kind: "slot.sidebar_content",
4265
+ ctxSessionID: ctx.session_id,
4266
+ resolvedSessionID: sessionID,
4267
+ route: api.route.current,
4268
+ childCount: Object.keys(state().children).length
4269
+ });
4270
+ const restoreFromChild = (() => {
4271
+ const pending = consumePendingSidebarRefocus();
4272
+ if (pending?.parentSessionID !== sessionID) return void 0;
4273
+ return {
4274
+ childRowID: pending.childRowID,
4275
+ showCompletedHistory: pending.showCompletedHistory ?? false
4276
+ };
4277
+ })();
4278
+ return _$createComponent2(Show2, {
4279
+ get when() {
4280
+ return subagentsSectionEnabled();
4281
+ },
4282
+ get children() {
4283
+ return _$createComponent2(SidebarSubagents, {
4284
+ controller: view,
4285
+ navigate: (id) => navigateToSessionTarget(api, id),
4286
+ modelLine: (child, width) => formatChildModelLine(child, api.state.provider, width),
4287
+ registerListKeys: ({
4288
+ onKeyDown
4289
+ }) => useKeyboard(onKeyDown),
4290
+ sessionID,
4291
+ state,
4292
+ nowMs,
4293
+ expanded: subagentsExpanded,
4294
+ onToggleExpanded: () => setSubagentsExpandedPreference(!subagentsExpanded()),
4295
+ onSetExpanded: setSubagentsExpandedSilently,
4296
+ onReturnFocus: focusActivePrompt,
4297
+ onToggleListFocus: toggleSidebarListFocus,
4298
+ onNavigateToChild: rememberSidebarChildNavigation,
4299
+ sidebarWidth: () => resolveSidebarWidth(ctx),
4300
+ get theme() {
4301
+ return ctx.theme.current;
4302
+ },
4303
+ restoreFromChild
4304
+ });
4305
+ }
4306
+ });
4307
+ }
4308
+ api.slots.register({
4309
+ order: 90,
4310
+ slots: {
4311
+ sidebar_content(ctx) {
4312
+ const routeSessionID = resolveRouteSessionID(api);
4313
+ const sessionID = ctx.session_id ?? routeSessionID ?? "";
4314
+ return _$createComponent2(SidebarContent, {
4315
+ ctx,
4316
+ sessionID
4317
+ });
4318
+ },
4319
+ home_bottom(ctx) {
4320
+ return _$createComponent2(HomeBottomStatus, {
4321
+ state,
4322
+ get theme() {
4323
+ return ctx.theme.current;
4324
+ }
4325
+ });
4326
+ },
4327
+ home_prompt(_ctx, props) {
4328
+ const promptProps = {
4329
+ ...props,
4330
+ ...props.workspaceID === void 0 && props.workspace_id !== void 0 ? {
4331
+ workspaceID: props.workspace_id
4332
+ } : {},
4333
+ ref: composePromptRef(props.ref)
4334
+ };
4335
+ return _$createComponent2(api.ui.Prompt, promptProps);
4336
+ },
4337
+ session_prompt(_ctx, props) {
4338
+ const sessionID = props.sessionID ?? props.session_id;
4339
+ const promptProps = {
4340
+ ...props,
4341
+ ...props.sessionID === void 0 && props.session_id !== void 0 ? {
4342
+ sessionID: props.session_id
4343
+ } : {},
4344
+ ...props.onSubmit === void 0 && props.on_submit !== void 0 ? {
4345
+ onSubmit: props.on_submit
4346
+ } : {},
4347
+ right: props.right ?? (sessionID ? _$createComponent2(api.ui.Slot, {
4348
+ name: "session_prompt_right",
4349
+ session_id: sessionID
4350
+ }) : void 0),
4351
+ ref: composePromptRef(props.ref)
4352
+ };
4353
+ return _$createComponent2(api.ui.Prompt, promptProps);
4354
+ }
4355
+ }
4356
+ });
4357
+ }
4358
+ var tui = async (api) => {
4359
+ createRoot((disposeRoot) => initializeTui(api, disposeRoot));
4360
+ };
4361
+ var plugin = {
4362
+ id: TUI_PLUGIN_ID,
4363
+ tui
4364
+ };
4365
+ var tui_v1_default = plugin;
4366
+ export {
4367
+ backfillHydratedTargetSessionIDs,
4368
+ createTuiMaintenanceTimers,
4369
+ tui_v1_default as default,
4370
+ formatChildModelLine,
4371
+ hydratePreviousSubagents,
4372
+ preservedSidebarAnchorScrollTop,
4373
+ preservedSidebarScrollTop,
4374
+ probeRunningEvidence,
4375
+ resolveSidebarSubagentSnapshot,
4376
+ resolveTuiSubagentSnapshot,
4377
+ runTuiStateMaintenance,
4378
+ subagentRowHeight,
4379
+ wrapCompactText
4380
+ };