@khalilgharbaoui/opencode-claude-code-plugin 0.53.5 → 0.54.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/tui.js ADDED
@@ -0,0 +1,689 @@
1
+ // src/tui.ts
2
+ import { RGBA } from "@opentui/core";
3
+ import { createElement, createTextNode, effect, insert, insertNode, setProp } from "@opentui/solid";
4
+ import { createMemo, createSignal, onCleanup } from "solid-js";
5
+
6
+ // src/subagent-sidebar.ts
7
+ var FINISHED_TTL_MS = 10 * 6e4;
8
+ var FINISHED_KEEP = 3;
9
+ var STARTING_GRACE_MS = 1e4;
10
+ var SIDEBAR_ROW_WIDTH = 36;
11
+ var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
12
+ var TITLE_SUFFIX = /\s*\(@([^)\s]+) subagent\)\s*$/;
13
+ function parseChildTitle(title) {
14
+ const text2 = (title ?? "").trim();
15
+ const match = TITLE_SUFFIX.exec(text2);
16
+ if (!match) return { description: text2 };
17
+ return { description: text2.slice(0, match.index).trim(), agent: match[1] };
18
+ }
19
+ function text(value) {
20
+ return typeof value === "string" && value.trim() ? value.trim() : void 0;
21
+ }
22
+ function partChildID(part) {
23
+ const metadata = part.metadata ?? {};
24
+ return text(metadata.sessionId) ?? text(metadata.sessionID);
25
+ }
26
+ function partAgent(part) {
27
+ const input = part.input ?? {};
28
+ return text(input.subagent_type) ?? text(input.agent);
29
+ }
30
+ function partDescription(part) {
31
+ return text(part.input?.description) ?? text(part.title);
32
+ }
33
+ function partBackground(part) {
34
+ return part.input?.background === true || part.metadata?.background === true;
35
+ }
36
+ var COMPLETION_ENVELOPE = /<(?:task\s+id|subagent\s+sessionID)="(ses_[A-Za-z0-9]+)"\s+state="([a-z_]+)"/g;
37
+ function completionsIn(value, at) {
38
+ const found = [];
39
+ for (const match of value.matchAll(COMPLETION_ENVELOPE)) {
40
+ if (match[2] === "running") continue;
41
+ found.push({ sessionID: match[1], state: match[2], at });
42
+ }
43
+ return found;
44
+ }
45
+ function endState(state) {
46
+ return state === "completed" ? "done" : "error";
47
+ }
48
+ function shortModelName(modelID) {
49
+ let id = text(modelID);
50
+ if (!id) return void 0;
51
+ const at = id.indexOf("@");
52
+ if (at > 0) id = id.slice(0, at);
53
+ let suffix = "";
54
+ const window = /\[(\d+[km])\]$/i.exec(id);
55
+ if (window) {
56
+ suffix += ` ${window[1].toLowerCase()}`;
57
+ id = id.slice(0, window.index);
58
+ }
59
+ if (id.endsWith("-fast")) {
60
+ suffix = ` fast${suffix}`;
61
+ id = id.slice(0, -"-fast".length);
62
+ }
63
+ if (!id.startsWith("claude-")) return `${id}${suffix}`;
64
+ id = id.slice("claude-".length).replace(/-\d{8}$/, "");
65
+ const match = /^([a-z]+)-(\d+)(?:-(\d+))?$/.exec(id);
66
+ if (!match) return `${id}${suffix}`;
67
+ const version = match[3] === void 0 ? match[2] : `${match[2]}.${match[3]}`;
68
+ return `${match[1]}-${version}${suffix}`;
69
+ }
70
+ function accountFromProvider(providerID) {
71
+ const id = text(providerID);
72
+ if (!id?.startsWith("claude-code-")) return void 0;
73
+ return id.slice("claude-code-".length) || void 0;
74
+ }
75
+ function subagentLabel(record, part) {
76
+ const asked = part?.metadata?.model;
77
+ const askedModel = typeof asked?.modelID === "string" ? asked.modelID : void 0;
78
+ const askedProvider = typeof asked?.providerID === "string" ? asked.providerID : void 0;
79
+ const model = shortModelName(record?.model ?? askedModel);
80
+ const effort = text(record?.effort);
81
+ const account = record?.model ? text(record.account) : accountFromProvider(askedProvider);
82
+ return [model, effort, account].filter(Boolean).join(" \xB7 ");
83
+ }
84
+ function truncate(value, width) {
85
+ const chars = Array.from(value.replace(/\s+/g, " ").trim());
86
+ if (width <= 0) return "";
87
+ if (chars.length <= width) return chars.join("");
88
+ if (width === 1) return "\u2026";
89
+ return `${chars.slice(0, width - 1).join("").trimEnd()}\u2026`;
90
+ }
91
+ function deriveSubagentRows(input) {
92
+ const completions = /* @__PURE__ */ new Map();
93
+ for (const completion of input.completions) completions.set(completion.sessionID, completion);
94
+ const byChild = /* @__PURE__ */ new Map();
95
+ const unmatched = [];
96
+ for (const part of input.parts) {
97
+ const child = partChildID(part);
98
+ if (child) byChild.set(child, part);
99
+ else unmatched.push(part);
100
+ }
101
+ const rows = [];
102
+ const seen = /* @__PURE__ */ new Set();
103
+ for (const child of input.children) {
104
+ if (seen.has(child.id)) continue;
105
+ seen.add(child.id);
106
+ const parsed = parseChildTitle(child.title);
107
+ let part = byChild.get(child.id);
108
+ if (!part) {
109
+ const index = unmatched.findIndex(
110
+ (candidate) => partDescription(candidate) === parsed.description && (parsed.agent === void 0 || partAgent(candidate) === parsed.agent)
111
+ );
112
+ if (index >= 0) part = unmatched.splice(index, 1)[0];
113
+ }
114
+ rows.push(rowFor(input, child, parsed, part, completions.get(child.id)));
115
+ }
116
+ for (const part of unmatched) {
117
+ if (part.status !== "pending" && part.status !== "running") continue;
118
+ const description = partDescription(part);
119
+ const agent = partAgent(part);
120
+ if (!description && !agent) continue;
121
+ rows.push({
122
+ agent: agent ?? "subagent",
123
+ description: description ?? "",
124
+ background: partBackground(part),
125
+ state: "running",
126
+ label: subagentLabel(void 0, part),
127
+ started: part.start ?? input.now
128
+ });
129
+ }
130
+ const running = rows.filter((row) => row.state === "running").sort((a, b) => a.started - b.started);
131
+ const finished = rows.filter((row) => row.state !== "running").filter((row) => input.now - (row.finished ?? row.started) <= FINISHED_TTL_MS).sort((a, b) => (b.finished ?? b.started) - (a.finished ?? a.started)).slice(0, FINISHED_KEEP);
132
+ return [...running, ...finished];
133
+ }
134
+ function rowFor(input, child, parsed, part, completion) {
135
+ const background = part ? partBackground(part) : false;
136
+ const started = part?.start ?? child.created ?? input.now;
137
+ const status = input.status(child.id);
138
+ let state;
139
+ let finished;
140
+ if (part && !background && part.status !== "pending" && part.status !== "running") {
141
+ state = part.status === "completed" ? "done" : "error";
142
+ finished = part.end;
143
+ } else if (completion) {
144
+ state = endState(completion.state);
145
+ finished = completion.at;
146
+ } else if (status === "busy") {
147
+ state = "running";
148
+ } else if (part && !background) {
149
+ state = "running";
150
+ } else if (status === "idle" && input.now - started < STARTING_GRACE_MS) {
151
+ state = "running";
152
+ } else if (status === "unknown" && background) {
153
+ state = "running";
154
+ } else {
155
+ state = "done";
156
+ }
157
+ if (state !== "running" && finished === void 0) {
158
+ finished = input.stoppedAt(child.id) ?? child.updated ?? started;
159
+ }
160
+ return {
161
+ sessionID: child.id,
162
+ agent: parsed.agent ?? (part ? partAgent(part) : void 0) ?? text(child.agent) ?? "subagent",
163
+ description: (part ? partDescription(part) : void 0) ?? parsed.description,
164
+ background,
165
+ state,
166
+ label: subagentLabel(input.record(child.id), part),
167
+ effort: text(input.record(child.id)?.effort),
168
+ started,
169
+ finished
170
+ };
171
+ }
172
+ function layoutRow(row, width = SIDEBAR_ROW_WIDTH, frame = 0) {
173
+ const finished = row.state !== "running";
174
+ const glyph = row.state === "running" ? SPINNER_FRAMES[frame % SPINNER_FRAMES.length] : row.state === "done" ? "\u2713" : "\u2717";
175
+ const marker = row.background ? "bg" : "";
176
+ const reserved = marker ? marker.length + 1 : 0;
177
+ const agentWidth = Math.max(1, Math.min(Array.from(row.agent).length, width - 2 - reserved - 6));
178
+ const agent = truncate(row.agent, agentWidth);
179
+ const room = width - 2 - Array.from(agent).length - 1 - reserved;
180
+ const description = room > 0 ? truncate(row.description, room) : "";
181
+ const used = 2 + Array.from(agent).length + (description ? 1 + Array.from(description).length : 0);
182
+ const first = [
183
+ { text: glyph, role: row.state === "running" ? "spinner" : row.state === "done" ? "done" : "error" },
184
+ { text: " ", role: "muted" },
185
+ { text: agent, role: finished ? "muted" : "agent" }
186
+ ];
187
+ if (description) {
188
+ first.push({ text: " ", role: "muted" });
189
+ first.push({ text: description, role: finished ? "muted" : "description" });
190
+ }
191
+ if (marker) {
192
+ first.push({ text: " ".repeat(Math.max(1, width - used - marker.length)), role: "muted" });
193
+ first.push({ text: marker, role: "muted" });
194
+ }
195
+ const second = [];
196
+ if (row.label) {
197
+ const pieces = truncate(row.label, width - 2).split(" \xB7 ");
198
+ const effortAt = row.effort ? pieces.indexOf(row.effort) : -1;
199
+ second.push({ text: " ", role: "muted" });
200
+ pieces.forEach((piece, index) => {
201
+ if (index > 0) second.push({ text: " \xB7 ", role: "muted" });
202
+ const role = index !== effortAt ? "muted" : finished ? "effort-faded" : "effort";
203
+ second.push({ text: piece, role });
204
+ });
205
+ }
206
+ return { first, second };
207
+ }
208
+ function collapsedSummary(rows) {
209
+ const running = rows.filter((row) => row.state === "running").length;
210
+ return running > 0 ? `${running} running` : `${rows.length} done`;
211
+ }
212
+
213
+ // src/spawn-record-store.ts
214
+ import { mkdirSync, readFileSync, renameSync, statSync, writeFileSync } from "fs";
215
+ import { homedir } from "os";
216
+ import { join } from "path";
217
+ var SPAWN_RECORD_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1e3;
218
+ var pathOverride = null;
219
+ function spawnRecordStorePath() {
220
+ if (pathOverride) return pathOverride;
221
+ const stateRoot = process.env.XDG_STATE_HOME || join(homedir(), ".local", "state");
222
+ return join(stateRoot, "opencode-claude-code-plugin", "session-spawns.json");
223
+ }
224
+ function isRecord(value) {
225
+ const record = value;
226
+ if (!record || typeof record !== "object" || Array.isArray(record)) return false;
227
+ if (typeof record.at !== "number" || !Number.isFinite(record.at)) return false;
228
+ const ok = (field) => field === void 0 || typeof field === "string";
229
+ if (!ok(record.model) || !ok(record.effort) || !ok(record.account)) return false;
230
+ return !!record.model;
231
+ }
232
+ function readEntries(file) {
233
+ const out = /* @__PURE__ */ new Map();
234
+ try {
235
+ const parsed = JSON.parse(readFileSync(file, "utf8"));
236
+ const entries = parsed && typeof parsed === "object" ? parsed.sessions : null;
237
+ if (!entries || typeof entries !== "object") return out;
238
+ const cutoff = Date.now() - SPAWN_RECORD_MAX_AGE_MS;
239
+ const valid = Object.entries(entries).filter((entry) => isRecord(entry[1])).filter(([, record]) => record.at >= cutoff).sort((a, b) => a[1].at - b[1].at);
240
+ for (const [key, record] of valid) {
241
+ out.set(key, {
242
+ model: record.model,
243
+ ...record.effort ? { effort: record.effort } : {},
244
+ ...record.account ? { account: record.account } : {},
245
+ at: record.at
246
+ });
247
+ }
248
+ } catch {
249
+ }
250
+ return out;
251
+ }
252
+ function createSpawnRecordReader(file = spawnRecordStorePath) {
253
+ let identity = "";
254
+ let cached = /* @__PURE__ */ new Map();
255
+ return {
256
+ read() {
257
+ const path = file();
258
+ let next = "";
259
+ try {
260
+ const stats = statSync(path);
261
+ next = `${stats.mtimeMs}:${stats.size}:${stats.ino}`;
262
+ } catch {
263
+ next = "missing";
264
+ }
265
+ if (next !== identity) {
266
+ identity = next;
267
+ cached = next === "missing" ? /* @__PURE__ */ new Map() : readEntries(path);
268
+ }
269
+ return cached;
270
+ }
271
+ };
272
+ }
273
+
274
+ // src/tui.ts
275
+ var SUBAGENTS_TUI_PLUGIN_ID = "opencode-claude-code-plugin.subagents";
276
+ var SECTION_ORDER = 900;
277
+ var SPINNER_INTERVAL_MS = 100;
278
+ var REFRESH_INTERVAL_MS = 1500;
279
+ var TASK_TOOLS = /* @__PURE__ */ new Set(["task", "subagent"]);
280
+ function attempt(fn, fallback) {
281
+ try {
282
+ return fn();
283
+ } catch {
284
+ return fallback;
285
+ }
286
+ }
287
+ function mix(a, b, weight) {
288
+ return attempt(
289
+ () => RGBA.fromValues(
290
+ a.r + (b.r - a.r) * weight,
291
+ a.g + (b.g - a.g) * weight,
292
+ a.b + (b.b - a.b) * weight,
293
+ a.a
294
+ ),
295
+ a
296
+ );
297
+ }
298
+ function roleColor(colors, role) {
299
+ switch (role) {
300
+ case "spinner":
301
+ return colors.spinner;
302
+ case "done":
303
+ return colors.done;
304
+ case "error":
305
+ return colors.error;
306
+ case "effort":
307
+ return colors.effort;
308
+ case "effort-faded":
309
+ return colors.effortFaded;
310
+ case "agent":
311
+ case "description":
312
+ return colors.text;
313
+ default:
314
+ return colors.muted;
315
+ }
316
+ }
317
+ function createSidebar(host) {
318
+ const [frame, setFrame] = createSignal(0);
319
+ const [now, setNow] = createSignal(Date.now());
320
+ const records = createSpawnRecordReader();
321
+ let recordMap = attempt(() => records.read(), /* @__PURE__ */ new Map());
322
+ const [recordVersion, setRecordVersion] = createSignal(0);
323
+ const sectionsRunning = /* @__PURE__ */ new Set();
324
+ const sectionsShowing = /* @__PURE__ */ new Set();
325
+ let spinner;
326
+ let refresh;
327
+ const syncClocks = () => {
328
+ if (sectionsRunning.size > 0 && !spinner) {
329
+ spinner = setInterval(() => setFrame((value) => (value + 1) % SPINNER_FRAMES.length), SPINNER_INTERVAL_MS);
330
+ } else if (sectionsRunning.size === 0 && spinner) {
331
+ clearInterval(spinner);
332
+ spinner = void 0;
333
+ }
334
+ if (sectionsShowing.size > 0 && !refresh) {
335
+ refresh = setInterval(() => {
336
+ const next = attempt(() => records.read(), recordMap);
337
+ if (next !== recordMap) {
338
+ recordMap = next;
339
+ setRecordVersion((value) => value + 1);
340
+ }
341
+ setNow(Date.now());
342
+ }, REFRESH_INTERVAL_MS);
343
+ } else if (sectionsShowing.size === 0 && refresh) {
344
+ clearInterval(refresh);
345
+ refresh = void 0;
346
+ }
347
+ };
348
+ const deriveRows = (parentID) => {
349
+ recordVersion();
350
+ return deriveSubagentRows({
351
+ ...host.gather(parentID),
352
+ record: (id) => recordMap.get(id),
353
+ now: now()
354
+ });
355
+ };
356
+ const buildRow = (row) => {
357
+ const box = createElement("box");
358
+ const layout = layoutRow(row, SIDEBAR_ROW_WIDTH);
359
+ const first = createElement("text");
360
+ setProp(first, "wrapMode", "none");
361
+ layout.first.forEach((segment, index) => {
362
+ const span = createElement("span");
363
+ if (index === 0 && row.state === "running") {
364
+ insert(span, () => SPINNER_FRAMES[frame() % SPINNER_FRAMES.length]);
365
+ } else {
366
+ insertNode(span, createTextNode(segment.text));
367
+ }
368
+ effect(() => setProp(span, "style", { fg: roleColor(host.colors(), segment.role) }));
369
+ insertNode(first, span);
370
+ });
371
+ insertNode(box, first);
372
+ if (layout.second.length > 0) {
373
+ const second = createElement("text");
374
+ setProp(second, "wrapMode", "none");
375
+ for (const segment of layout.second) {
376
+ const span = createElement("span");
377
+ insertNode(span, createTextNode(segment.text));
378
+ effect(() => setProp(span, "style", { fg: roleColor(host.colors(), segment.role) }));
379
+ insertNode(second, span);
380
+ }
381
+ insertNode(box, second);
382
+ }
383
+ if (row.sessionID) {
384
+ const sessionID = row.sessionID;
385
+ setProp(box, "onMouseDown", () => attempt(() => host.navigate(sessionID), void 0));
386
+ setProp(
387
+ box,
388
+ "onMouseOver",
389
+ () => attempt(() => setProp(box, "backgroundColor", host.colors().hover), void 0)
390
+ );
391
+ setProp(
392
+ box,
393
+ "onMouseOut",
394
+ () => attempt(() => setProp(box, "backgroundColor", "transparent"), void 0)
395
+ );
396
+ }
397
+ return box;
398
+ };
399
+ const buildSection = (list, expanded, setExpanded) => {
400
+ const section = createElement("box");
401
+ const header = createElement("box");
402
+ setProp(header, "flexDirection", "row");
403
+ setProp(header, "gap", 1);
404
+ setProp(header, "onMouseDown", () => list.length > 2 && setExpanded((value) => !value));
405
+ if (list.length > 2) {
406
+ const caret = createElement("text");
407
+ insert(caret, () => expanded() ? "\u25BC" : "\u25B6");
408
+ effect(() => setProp(caret, "fg", host.colors().text));
409
+ insertNode(header, caret);
410
+ }
411
+ const title = createElement("text");
412
+ const bold = createElement("b");
413
+ insertNode(bold, createTextNode("Subagents"));
414
+ insertNode(title, bold);
415
+ const summary = createElement("span");
416
+ insert(summary, () => expanded() ? "" : ` (${collapsedSummary(list)})`);
417
+ effect(() => setProp(summary, "style", { fg: host.colors().muted }));
418
+ insertNode(title, summary);
419
+ effect(() => setProp(title, "fg", host.colors().text));
420
+ insertNode(header, title);
421
+ insertNode(section, header);
422
+ const body = createElement("box");
423
+ insert(body, () => expanded() || list.length <= 2 ? list.map(buildRow) : null);
424
+ insertNode(section, body);
425
+ return section;
426
+ };
427
+ const render = (sessionID) => {
428
+ const root = createElement("box");
429
+ const key = /* @__PURE__ */ Symbol("subagents-section");
430
+ const [expanded, setExpanded] = createSignal(true);
431
+ const rows = createMemo(() => {
432
+ const parentID = sessionID();
433
+ if (!parentID) return [];
434
+ return attempt(() => deriveRows(parentID), []);
435
+ });
436
+ effect(() => {
437
+ const list = rows();
438
+ if (list.some((row) => row.state === "running")) sectionsRunning.add(key);
439
+ else sectionsRunning.delete(key);
440
+ if (list.length > 0) sectionsShowing.add(key);
441
+ else sectionsShowing.delete(key);
442
+ syncClocks();
443
+ });
444
+ onCleanup(() => {
445
+ sectionsRunning.delete(key);
446
+ sectionsShowing.delete(key);
447
+ syncClocks();
448
+ });
449
+ insert(root, () => {
450
+ const list = rows();
451
+ if (list.length === 0) return null;
452
+ return attempt(() => buildSection(list, expanded, setExpanded), null);
453
+ });
454
+ return root;
455
+ };
456
+ const dispose = () => {
457
+ sectionsRunning.clear();
458
+ sectionsShowing.clear();
459
+ syncClocks();
460
+ };
461
+ return { render, dispose };
462
+ }
463
+ function registerV1(api) {
464
+ const childrenByParent = /* @__PURE__ */ new Map();
465
+ const fetched = /* @__PURE__ */ new Set();
466
+ const stoppedAt = /* @__PURE__ */ new Map();
467
+ const lastStatus = /* @__PURE__ */ new Map();
468
+ const [version, setVersion] = createSignal(0);
469
+ const bump = () => setVersion((value) => value + 1);
470
+ const remember = (info) => {
471
+ if (!info?.id || !info.parentID) return false;
472
+ let children = childrenByParent.get(info.parentID);
473
+ if (!children) {
474
+ children = /* @__PURE__ */ new Map();
475
+ childrenByParent.set(info.parentID, children);
476
+ }
477
+ children.set(info.id, {
478
+ id: info.id,
479
+ title: info.title,
480
+ agent: info.agent,
481
+ created: info.time?.created,
482
+ updated: info.time?.updated
483
+ });
484
+ return true;
485
+ };
486
+ const unsubscribe = [];
487
+ const listen = (type, handler) => {
488
+ const off = attempt(
489
+ () => api.event.on(type, (event) => attempt(() => handler(event?.properties ?? {}), void 0)),
490
+ void 0
491
+ );
492
+ if (off) unsubscribe.push(off);
493
+ };
494
+ listen("session.created", (properties) => remember(properties.info) && bump());
495
+ listen("session.updated", (properties) => remember(properties.info) && bump());
496
+ listen("session.deleted", (properties) => {
497
+ const info = properties.info;
498
+ if (info?.parentID && childrenByParent.get(info.parentID)?.delete(info.id)) bump();
499
+ });
500
+ listen("session.status", (properties) => {
501
+ const id = properties.sessionID;
502
+ const type = properties.status?.type;
503
+ if (!id || !type) return;
504
+ const previous = lastStatus.get(id);
505
+ lastStatus.set(id, type);
506
+ if (type === "idle" && previous && previous !== "idle") stoppedAt.set(id, Date.now());
507
+ if (type !== "idle") stoppedAt.delete(id);
508
+ });
509
+ const fetchChildren = (parentID) => {
510
+ if (fetched.has(parentID)) return;
511
+ fetched.add(parentID);
512
+ const session = api.client?.session;
513
+ if (!session?.children) return;
514
+ void Promise.resolve().then(() => session.children.call(session, { sessionID: parentID })).then((response) => {
515
+ let changed = false;
516
+ for (const info of response?.data ?? []) changed = remember(info) || changed;
517
+ if (changed) bump();
518
+ }).catch(() => void 0);
519
+ };
520
+ const host = {
521
+ colors() {
522
+ const theme = api.theme.current;
523
+ return {
524
+ text: theme.text,
525
+ muted: theme.textMuted,
526
+ spinner: theme.accent,
527
+ done: mix(theme.success, theme.textMuted, 0.45),
528
+ error: mix(theme.error, theme.textMuted, 0.35),
529
+ effort: theme.warning ?? theme.textMuted,
530
+ effortFaded: mix(theme.warning ?? theme.textMuted, theme.textMuted, 0.45),
531
+ hover: theme.backgroundElement
532
+ };
533
+ },
534
+ gather(parentID) {
535
+ version();
536
+ fetchChildren(parentID);
537
+ const parts = [];
538
+ const completions = [];
539
+ for (const message of api.state.session.messages(parentID) ?? []) {
540
+ const messageParts = api.state.part(message.id) ?? [];
541
+ if (message.role === "assistant") {
542
+ for (const part of messageParts) {
543
+ if (part.type !== "tool" || !TASK_TOOLS.has(part.tool ?? "")) continue;
544
+ const state = part.state ?? {};
545
+ parts.push({
546
+ status: state.status,
547
+ input: state.input,
548
+ metadata: state.metadata,
549
+ title: state.title,
550
+ start: state.time?.start,
551
+ end: state.time?.end
552
+ });
553
+ }
554
+ } else if (message.role === "user") {
555
+ for (const part of messageParts) {
556
+ if (part.type !== "text" || part.synthetic !== true || !part.text) continue;
557
+ completions.push(...completionsIn(part.text, message.time?.created));
558
+ }
559
+ }
560
+ }
561
+ const known = new Map(childrenByParent.get(parentID) ?? []);
562
+ for (const part of parts) {
563
+ const id = partChildID(part);
564
+ if (!id || known.has(id)) continue;
565
+ const info = attempt(() => api.state.session.get(id), void 0);
566
+ known.set(id, {
567
+ id,
568
+ title: info?.title,
569
+ agent: info?.agent,
570
+ created: info?.time?.created ?? part.start,
571
+ updated: info?.time?.updated
572
+ });
573
+ }
574
+ return {
575
+ children: [...known.values()],
576
+ parts,
577
+ completions,
578
+ status: (id) => {
579
+ const type = attempt(() => api.state.session.status(id)?.type, void 0);
580
+ if (type === "busy" || type === "retry") return "busy";
581
+ if (type === "idle") return "idle";
582
+ return "unknown";
583
+ },
584
+ stoppedAt: (id) => stoppedAt.get(id)
585
+ };
586
+ },
587
+ navigate(sessionID) {
588
+ api.route.navigate("session", { sessionID });
589
+ }
590
+ };
591
+ const sidebar = createSidebar(host);
592
+ api.slots.register({
593
+ order: SECTION_ORDER,
594
+ slots: {
595
+ sidebar_content(_ctx, props) {
596
+ return sidebar.render(() => props.session_id);
597
+ }
598
+ }
599
+ });
600
+ api.lifecycle?.onDispose(() => {
601
+ for (const off of unsubscribe) attempt(off, void 0);
602
+ sidebar.dispose();
603
+ });
604
+ }
605
+ function setupV2(context) {
606
+ const host = {
607
+ colors() {
608
+ const theme = context.theme;
609
+ const muted = theme.text.muted;
610
+ const accent = theme.hue.accent[context.themeMode === "light" ? "500" : "200"] ?? theme.text.base;
611
+ return {
612
+ text: theme.text.base,
613
+ muted,
614
+ spinner: accent,
615
+ done: mix(theme.text.feedback.success.muted ?? theme.text.feedback.success.base, muted, 0.45),
616
+ error: mix(theme.text.feedback.error.muted ?? theme.text.feedback.error.base, muted, 0.35),
617
+ effort: theme.text.feedback.warning?.base ?? muted,
618
+ effortFaded: mix(theme.text.feedback.warning?.base ?? muted, muted, 0.45),
619
+ hover: theme.background.raised.base
620
+ };
621
+ },
622
+ gather(parentID) {
623
+ const children = (context.data.session.list() ?? []).filter((session) => session.parentID === parentID).map((session) => ({
624
+ id: session.id,
625
+ title: session.title,
626
+ agent: session.agent,
627
+ created: session.time?.created,
628
+ updated: session.time?.updated
629
+ }));
630
+ const parts = [];
631
+ const completions = [];
632
+ for (const message of context.data.session.message.list(parentID) ?? []) {
633
+ if (message.type === "assistant") {
634
+ for (const item of message.content ?? []) {
635
+ if (item.type !== "tool" || !TASK_TOOLS.has(item.name ?? "")) continue;
636
+ parts.push({
637
+ status: item.state?.status,
638
+ input: item.state?.input,
639
+ metadata: item.state?.metadata,
640
+ start: item.time?.created,
641
+ end: item.time?.completed
642
+ });
643
+ }
644
+ } else if (message.type === "synthetic" && message.text) {
645
+ completions.push(...completionsIn(message.text, message.time?.created));
646
+ }
647
+ }
648
+ return {
649
+ children,
650
+ parts,
651
+ completions,
652
+ status: (id) => {
653
+ const status = attempt(() => context.data.session.status(id), void 0);
654
+ if (status === "running") return "busy";
655
+ if (status === "idle") return "idle";
656
+ return "unknown";
657
+ },
658
+ stoppedAt: () => void 0
659
+ };
660
+ },
661
+ navigate(sessionID) {
662
+ context.ui.router.navigate({ type: "session", sessionID });
663
+ }
664
+ };
665
+ const sidebar = createSidebar(host);
666
+ const release = attempt(
667
+ () => context.ui.slot({
668
+ append: "sidebar.content",
669
+ render: (input) => sidebar.render(() => input.sessionID)
670
+ }),
671
+ void 0
672
+ );
673
+ return () => {
674
+ if (release) attempt(release, void 0);
675
+ sidebar.dispose();
676
+ };
677
+ }
678
+ var tui = async (api) => {
679
+ attempt(() => registerV1(api), void 0);
680
+ };
681
+ var setup = (context) => attempt(() => setupV2(context), void 0);
682
+ var tui_default = { id: SUBAGENTS_TUI_PLUGIN_ID, tui, setup };
683
+ export {
684
+ SUBAGENTS_TUI_PLUGIN_ID,
685
+ tui_default as default,
686
+ registerV1,
687
+ setupV2
688
+ };
689
+ //# sourceMappingURL=tui.js.map