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