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