@mirrowel/opencode-subagent-explorer 0.1.0-dev.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,1962 @@
1
+ // @bun
2
+ // src/v2-tui.ts
3
+ import { createSignal as createSignal2 } from "solid-js";
4
+
5
+ // src/palette-category.ts
6
+ var REGISTRY_KEY = Symbol.for("mirrowel.opencode.paletteCategory");
7
+ function registry() {
8
+ const globals = globalThis;
9
+ if (!globals[REGISTRY_KEY])
10
+ globals[REGISTRY_KEY] = { entries: [] };
11
+ return globals[REGISTRY_KEY];
12
+ }
13
+ function joinLabels(labels) {
14
+ if (labels.length <= 1)
15
+ return labels[0] ?? "";
16
+ return `${labels.slice(0, -1).join(", ")} & ${labels[labels.length - 1]}`;
17
+ }
18
+ function joinedCategory() {
19
+ const labels = [...new Set(registry().entries.map((entry) => entry.label))].sort((a, b) => a.localeCompare(b));
20
+ return joinLabels(labels);
21
+ }
22
+ function declarePaletteCategory(label, ...commands) {
23
+ const reg = registry();
24
+ let entry = reg.entries.find((item) => item.label === label);
25
+ if (!entry) {
26
+ entry = { label, commands: [] };
27
+ reg.entries.push(entry);
28
+ }
29
+ for (const command of commands) {
30
+ if (command && !entry.commands.includes(command))
31
+ entry.commands.push(command);
32
+ }
33
+ reconcilePaletteCategories();
34
+ return joinedCategory();
35
+ }
36
+ function currentPaletteCategory() {
37
+ return joinedCategory();
38
+ }
39
+ function reconcilePaletteCategories() {
40
+ const category = joinedCategory();
41
+ for (const entry of registry().entries) {
42
+ for (const command of entry.commands) {
43
+ command.category = category;
44
+ command.group = category;
45
+ }
46
+ }
47
+ }
48
+ function schedulePaletteReconcile(delaysMs = [2000, 6000]) {
49
+ for (const delay of delaysMs) {
50
+ const timer = setTimeout(() => reconcilePaletteCategories(), delay);
51
+ timer.unref?.();
52
+ }
53
+ }
54
+
55
+ // src/wizard.tsx
56
+ import { memo as _$memo } from "@opentui/solid";
57
+ import { use as _$use } from "@opentui/solid";
58
+ import { effect as _$effect } from "@opentui/solid";
59
+ import { createTextNode as _$createTextNode } from "@opentui/solid";
60
+ import { insertNode as _$insertNode } from "@opentui/solid";
61
+ import { insert as _$insert } from "@opentui/solid";
62
+ import { setProp as _$setProp } from "@opentui/solid";
63
+ import { createElement as _$createElement } from "@opentui/solid";
64
+ import { createComponent as _$createComponent } from "@opentui/solid";
65
+ import { useTerminalDimensions } from "@opentui/solid";
66
+ import { createEffect, createMemo, createSignal, For, onCleanup, Show } from "solid-js";
67
+
68
+ // src/sessions.ts
69
+ var CALL_TIMEOUT_MS = 5000;
70
+ var BUILTIN_AGENTS = new Set(["build", "plan"]);
71
+ function withTimeout(fn, ms = CALL_TIMEOUT_MS) {
72
+ return Promise.race([Promise.resolve().then(fn).catch(() => {
73
+ return;
74
+ }), new Promise((resolve) => setTimeout(() => resolve(undefined), ms))]);
75
+ }
76
+ function unwrap(value) {
77
+ if (value && typeof value === "object" && "data" in value)
78
+ return value.data;
79
+ return value;
80
+ }
81
+ function toMillis(value) {
82
+ if (typeof value === "number")
83
+ return value;
84
+ if (typeof value === "string") {
85
+ const parsed = Date.parse(value);
86
+ return Number.isNaN(parsed) ? undefined : parsed;
87
+ }
88
+ return;
89
+ }
90
+ function normalizeSession(raw) {
91
+ if (!raw || typeof raw !== "object")
92
+ return;
93
+ const record = raw;
94
+ const id = typeof record.id === "string" ? record.id : undefined;
95
+ if (!id)
96
+ return;
97
+ const agentRaw = record.agent;
98
+ const agent = typeof agentRaw === "string" ? agentRaw : typeof agentRaw?.id === "string" ? agentRaw.id : typeof agentRaw?.agentID === "string" ? agentRaw.agentID : undefined;
99
+ const time = record.time && typeof record.time === "object" ? record.time : {};
100
+ return {
101
+ id,
102
+ parentID: typeof record.parentID === "string" ? record.parentID : undefined,
103
+ title: typeof record.title === "string" && record.title !== "" ? record.title : undefined,
104
+ agent,
105
+ directory: typeof record.directory === "string" ? record.directory : undefined,
106
+ created: toMillis(time.created ?? record.time_created),
107
+ updated: toMillis(time.updated ?? record.time_updated)
108
+ };
109
+ }
110
+ async function fetchSessions(client, directory) {
111
+ const namespace = client?.session;
112
+ if (!namespace?.list)
113
+ return;
114
+ const calls = [
115
+ () => namespace.list({ directory }),
116
+ () => namespace.list({ query: { directory } })
117
+ ];
118
+ for (const call of calls) {
119
+ const result = unwrap(await withTimeout(call));
120
+ if (Array.isArray(result))
121
+ return result.map(normalizeSession).filter((s) => !!s);
122
+ }
123
+ return;
124
+ }
125
+ async function fetchActiveSessionIDs(client) {
126
+ const namespace = client?.session;
127
+ if (!namespace)
128
+ return;
129
+ if (typeof namespace.active === "function") {
130
+ const active = unwrap(await withTimeout(() => namespace.active()));
131
+ if (active && typeof active === "object") {
132
+ const ids = new Set;
133
+ for (const [id, value] of Object.entries(active)) {
134
+ if (value)
135
+ ids.add(id);
136
+ }
137
+ return ids;
138
+ }
139
+ }
140
+ if (typeof namespace.status === "function") {
141
+ const status = unwrap(await withTimeout(() => namespace.status()));
142
+ if (status && typeof status === "object") {
143
+ const ids = new Set;
144
+ for (const [id, value] of Object.entries(status)) {
145
+ const state = value?.status ?? value;
146
+ if (state === "busy" || state === "retry")
147
+ ids.add(id);
148
+ }
149
+ return ids;
150
+ }
151
+ }
152
+ return;
153
+ }
154
+ function buildSessionTree(sessions, rootID) {
155
+ const byParent = new Map;
156
+ for (const session of sessions) {
157
+ if (!session.parentID)
158
+ continue;
159
+ const list = byParent.get(session.parentID) ?? [];
160
+ list.push(session);
161
+ byParent.set(session.parentID, list);
162
+ }
163
+ const createdOf = (session) => session.created ?? 0;
164
+ const sortSiblings = (list) => [...list].sort((a, b) => createdOf(a) - createdOf(b));
165
+ const visited = new Set([rootID]);
166
+ const build = (list, depth) => sortSiblings(list).map((session) => {
167
+ if (visited.has(session.id))
168
+ return { session, depth, children: [] };
169
+ visited.add(session.id);
170
+ const children = build(byParent.get(session.id) ?? [], depth + 1);
171
+ return { session, depth, children };
172
+ });
173
+ return build(byParent.get(rootID) ?? [], 0);
174
+ }
175
+ function cleanupTargets(nodes, activeIDs) {
176
+ const deletable = [];
177
+ const sparedActive = [];
178
+ const walk = (list) => {
179
+ for (const node of list) {
180
+ walk(node.children);
181
+ if (activeIDs?.has(node.session.id))
182
+ sparedActive.push(node.session);
183
+ else
184
+ deletable.push(node.session);
185
+ }
186
+ };
187
+ walk(nodes);
188
+ return { deletable, sparedActive };
189
+ }
190
+ function currentSessionID(explicit, sessions) {
191
+ if (explicit)
192
+ return explicit;
193
+ let newest;
194
+ for (const session of sessions) {
195
+ const stamp = session.updated ?? session.created ?? 0;
196
+ const newestStamp = newest?.updated ?? newest?.created ?? 0;
197
+ if (!newest || stamp > newestStamp)
198
+ newest = session;
199
+ }
200
+ return newest?.id;
201
+ }
202
+ function hiddenAgentIDs(config) {
203
+ const agents = config?.agent;
204
+ const hidden = new Set;
205
+ if (!agents || typeof agents !== "object")
206
+ return hidden;
207
+ for (const [id, entry] of Object.entries(agents)) {
208
+ if (entry && typeof entry === "object" && entry.hidden === true)
209
+ hidden.add(id);
210
+ }
211
+ return hidden;
212
+ }
213
+ function configAgentIDs(config) {
214
+ const agents = config?.agent;
215
+ if (!agents || typeof agents !== "object")
216
+ return new Set;
217
+ return new Set(Object.keys(agents));
218
+ }
219
+ function isHiddenAgent(agent, hidden, configAgents) {
220
+ if (!agent)
221
+ return false;
222
+ if (hidden.has(agent))
223
+ return true;
224
+ return !configAgents.has(agent) && !BUILTIN_AGENTS.has(agent) && !agent.startsWith("av:");
225
+ }
226
+ async function deleteSession(client, sessionID) {
227
+ const namespace = client?.session;
228
+ if (!namespace)
229
+ return false;
230
+ const shapes = [];
231
+ if (typeof namespace.delete === "function") {
232
+ shapes.push(() => namespace.delete({ sessionID }), () => namespace.delete({ path: { sessionID } }), () => namespace.delete({ path: { id: sessionID } }), () => namespace.delete({ id: sessionID }));
233
+ }
234
+ if (typeof namespace.remove === "function") {
235
+ shapes.push(() => namespace.remove({ sessionID }), () => namespace.remove({ path: { sessionID } }));
236
+ }
237
+ for (const call of shapes) {
238
+ const result = await withTimeout(call);
239
+ if (result !== undefined)
240
+ return true;
241
+ }
242
+ return false;
243
+ }
244
+ async function fetchSessionDetails(client, sessionID) {
245
+ const namespace = client?.session;
246
+ if (!namespace?.messages)
247
+ return;
248
+ const calls = [
249
+ () => namespace.messages({ sessionID, limit: 50 }),
250
+ () => namespace.messages({ path: { id: sessionID }, query: { limit: 50 } })
251
+ ];
252
+ for (const call of calls) {
253
+ const result = unwrap(await withTimeout(call, 4000));
254
+ const messages = Array.isArray(result) ? result : undefined;
255
+ if (!messages)
256
+ continue;
257
+ let model;
258
+ for (let i = messages.length - 1;i >= 0; i -= 1) {
259
+ const info = messages[i]?.info ?? messages[i];
260
+ const modelRef = info?.model;
261
+ if (modelRef?.providerID && modelRef?.modelID) {
262
+ model = `${modelRef.providerID}/${modelRef.modelID}${modelRef.variantID ? ` @${modelRef.variantID}` : ""}`;
263
+ break;
264
+ }
265
+ }
266
+ let firstPrompt;
267
+ for (const message of messages) {
268
+ const info = message?.info ?? message;
269
+ if (info?.role !== "user")
270
+ continue;
271
+ const text = firstTextOf(message.parts);
272
+ if (text) {
273
+ firstPrompt = text.length > 400 ? `${text.slice(0, 400)}\u2026` : text;
274
+ break;
275
+ }
276
+ }
277
+ return { messageCount: messages.length, model, firstPrompt };
278
+ }
279
+ return;
280
+ }
281
+ function firstTextOf(parts) {
282
+ if (!Array.isArray(parts))
283
+ return;
284
+ for (const part of parts) {
285
+ const text = part?.text ?? part?.part?.text;
286
+ if (typeof text === "string" && text.trim() !== "")
287
+ return text.trim();
288
+ }
289
+ return;
290
+ }
291
+ function formatAge(timestamp, now = Date.now()) {
292
+ if (!timestamp)
293
+ return "\u2014";
294
+ const delta = Math.max(0, now - timestamp);
295
+ const minutes = Math.floor(delta / 60000);
296
+ if (minutes < 1)
297
+ return "just now";
298
+ if (minutes < 60)
299
+ return `${minutes}m ago`;
300
+ const hours = Math.floor(minutes / 60);
301
+ if (hours < 24)
302
+ return `${hours}h ago`;
303
+ const days = Math.floor(hours / 24);
304
+ return `${days}d ago`;
305
+ }
306
+ function parentCandidates(sessions) {
307
+ const parents = new Set;
308
+ for (const session of sessions) {
309
+ if (session.parentID)
310
+ parents.add(session.parentID);
311
+ }
312
+ return sessions.filter((session) => parents.has(session.id)).sort((a, b) => (b.updated ?? b.created ?? 0) - (a.updated ?? a.created ?? 0));
313
+ }
314
+
315
+ // src/wizard.tsx
316
+ async function loadExplorerData(api, directory) {
317
+ const sessions = await fetchSessions(api.client, directory);
318
+ if (!sessions)
319
+ return;
320
+ const activeIDs = await fetchActiveSessionIDs(api.client);
321
+ return {
322
+ sessions,
323
+ activeIDs,
324
+ activeKnown: activeIDs !== undefined,
325
+ hidden: hiddenAgentIDs(api.state.config),
326
+ configAgents: configAgentIDs(api.state.config)
327
+ };
328
+ }
329
+ function explorerRows(tree, data) {
330
+ const rows = [];
331
+ const walk = (nodes, depth) => {
332
+ for (const node of nodes) {
333
+ rows.push({
334
+ session: node.session,
335
+ depth,
336
+ running: data.activeIDs?.has(node.session.id) ?? false,
337
+ hidden: isHiddenAgent(node.session.agent, data.hidden, data.configAgents)
338
+ });
339
+ walk(node.children, depth + 1);
340
+ }
341
+ };
342
+ walk(tree, 0);
343
+ return rows;
344
+ }
345
+ function themeOf(api) {
346
+ return api.theme.current;
347
+ }
348
+ var OWN_SIZE_KV = "subagent-explorer.ui-width";
349
+ var OWN_HEIGHT_PERCENT_KV = "subagent-explorer.ui-height-percent";
350
+ var HOST_SIZE_KV = "config-studio.ui-width";
351
+ var HOST_HEIGHT_PERCENT_KV = "config-studio.ui-height-percent";
352
+ var HEIGHT_PERCENT_MIN = 25;
353
+ var HEIGHT_PERCENT_MAX = 100;
354
+ var HEIGHT_PRESETS = [{
355
+ key: "1",
356
+ label: "compact",
357
+ value: 35
358
+ }, {
359
+ key: "2",
360
+ label: "normal",
361
+ value: 50
362
+ }, {
363
+ key: "3",
364
+ label: "tall",
365
+ value: 70
366
+ }, {
367
+ key: "4",
368
+ label: "max",
369
+ value: 100
370
+ }];
371
+ var DIALOG_WIDTH_COLUMNS = {
372
+ medium: 60,
373
+ large: 88,
374
+ xlarge: 116
375
+ };
376
+ function isEmbedded(api) {
377
+ return api.dialogScope === "embedded";
378
+ }
379
+ function sizeKey(api) {
380
+ return isEmbedded(api) ? HOST_SIZE_KV : OWN_SIZE_KV;
381
+ }
382
+ function heightKey(api) {
383
+ return isEmbedded(api) ? HOST_HEIGHT_PERCENT_KV : OWN_HEIGHT_PERCENT_KV;
384
+ }
385
+ function dialogSizeOf(api) {
386
+ const value = api.kv.get(sizeKey(api), "large");
387
+ if (value === "medium" || value === "large" || value === "xlarge")
388
+ return value;
389
+ return "large";
390
+ }
391
+ function setDialogSize(api, size) {
392
+ api.kv.set(sizeKey(api), size);
393
+ api.ui.dialog.setSize(size);
394
+ }
395
+ function nextDialogSize(api) {
396
+ const current = dialogSizeOf(api);
397
+ if (current === "medium")
398
+ return "large";
399
+ if (current === "large")
400
+ return "xlarge";
401
+ return "medium";
402
+ }
403
+ function clampHeightPercent(value) {
404
+ return Math.max(HEIGHT_PERCENT_MIN, Math.min(HEIGHT_PERCENT_MAX, Math.round(value)));
405
+ }
406
+ function dialogHeightPercent(api) {
407
+ const value = api.kv.get(heightKey(api), 50);
408
+ return typeof value === "number" && Number.isFinite(value) ? clampHeightPercent(value) : 50;
409
+ }
410
+ function setDialogHeightPercent(api, value) {
411
+ api.kv.set(heightKey(api), clampHeightPercent(value));
412
+ }
413
+ function useDialogSize(api) {
414
+ createEffect(() => api.ui.dialog.setSize(dialogSizeOf(api)));
415
+ }
416
+ function computeDialogRows(percent, terminalHeight, chromeRows, minRows) {
417
+ const clampedPercent = Math.min(100, Math.max(10, Math.max(1, Math.round(percent)) || 1));
418
+ const backdropBudget = Math.max(minRows + chromeRows, Math.floor(terminalHeight * 0.75) - 1);
419
+ const available = Math.max(minRows, backdropBudget - chromeRows);
420
+ const requested = Math.max(minRows, Math.floor(terminalHeight * clampedPercent / 100) - chromeRows);
421
+ return {
422
+ availableRows: available,
423
+ targetRows: Math.min(requested, available)
424
+ };
425
+ }
426
+ function listRows(api, terminalHeight, chromeRows = 8, minRows = 3) {
427
+ return computeDialogRows(dialogHeightPercent(api), terminalHeight, chromeRows, minRows).targetRows;
428
+ }
429
+ function contentWidth(api) {
430
+ return DIALOG_WIDTH_COLUMNS[dialogSizeOf(api)] - 8;
431
+ }
432
+ function cappedHeight(count, max, min = 1) {
433
+ return Math.max(min, Math.min(count, max));
434
+ }
435
+ function truncate(text, width) {
436
+ return text.length <= width ? text : `${text.slice(0, Math.max(1, width - 1))}\u2026`;
437
+ }
438
+ function rowTitle(row) {
439
+ return row.session.title ?? row.session.id;
440
+ }
441
+ function showTreeOnce(api, props) {
442
+ return new Promise((resolve) => {
443
+ let settled = false;
444
+ const done = (value, clear = true) => {
445
+ if (settled)
446
+ return;
447
+ settled = true;
448
+ resolve(value);
449
+ if (clear)
450
+ api.ui.dialog.clear();
451
+ };
452
+ api.ui.dialog.replace(() => _$createComponent(SubagentTreeDialog, {
453
+ api,
454
+ get rows() {
455
+ return props.rows;
456
+ },
457
+ get rootTitle() {
458
+ return props.rootTitle;
459
+ },
460
+ get rootID() {
461
+ return props.rootID;
462
+ },
463
+ get activeKnown() {
464
+ return props.activeKnown;
465
+ },
466
+ get idleCount() {
467
+ return props.idleCount;
468
+ },
469
+ onDone: done
470
+ }), () => done({
471
+ type: "exit"
472
+ }, false));
473
+ });
474
+ }
475
+ function SubagentTreeDialog(props) {
476
+ const theme = () => themeOf(props.api);
477
+ const dimensions = useTerminalDimensions();
478
+ useDialogSize(props.api);
479
+ const popMode = props.api.mode.push("subagent-explorer.dialog");
480
+ const [selected, setSelected] = createSignal(0);
481
+ const current = createMemo(() => props.rows[selected()]);
482
+ let scroll;
483
+ const listHeight = createMemo(() => cappedHeight(props.rows.length, listRows(props.api, dimensions().height)));
484
+ const agentWidth = 16;
485
+ const badgeWidth = 9;
486
+ const ageWidth = 9;
487
+ const titleWidth = createMemo(() => Math.max(16, contentWidth(props.api) - agentWidth - badgeWidth - ageWidth - 4));
488
+ const move = (delta) => setSelected((value) => {
489
+ const next = Math.max(0, Math.min(props.rows.length - 1, value + delta));
490
+ scroll?.scrollTo(Math.max(0, next - 2));
491
+ return next;
492
+ });
493
+ const choose = () => {
494
+ const row = current();
495
+ if (row)
496
+ props.onDone({
497
+ type: "delete",
498
+ row
499
+ });
500
+ };
501
+ const details = () => {
502
+ const row = current();
503
+ if (row)
504
+ props.onDone({
505
+ type: "details",
506
+ row
507
+ });
508
+ };
509
+ const commandPrefix = `subagent-explorer.tree.${Math.random().toString(36).slice(2)}`;
510
+ const shield = (ctx) => {
511
+ ctx.event?.preventDefault?.();
512
+ ctx.event?.stopPropagation?.();
513
+ };
514
+ const commands = [{
515
+ name: `${commandPrefix}.up`,
516
+ title: "Previous",
517
+ run: (ctx) => {
518
+ shield(ctx);
519
+ move(-1);
520
+ }
521
+ }, {
522
+ name: `${commandPrefix}.down`,
523
+ title: "Next",
524
+ run: (ctx) => {
525
+ shield(ctx);
526
+ move(1);
527
+ }
528
+ }, {
529
+ name: `${commandPrefix}.select`,
530
+ title: "Delete session",
531
+ run: () => choose()
532
+ }, {
533
+ name: `${commandPrefix}.details`,
534
+ title: "Session details",
535
+ run: () => details()
536
+ }, {
537
+ name: `${commandPrefix}.cleanup`,
538
+ title: "Clean up idle sessions",
539
+ run: () => props.onDone({
540
+ type: "cleanup"
541
+ })
542
+ }, {
543
+ name: `${commandPrefix}.refresh`,
544
+ title: "Refresh",
545
+ run: () => props.onDone({
546
+ type: "refresh"
547
+ })
548
+ }, {
549
+ name: `${commandPrefix}.switch`,
550
+ title: "Switch root session",
551
+ run: () => props.onDone({
552
+ type: "switch-root"
553
+ })
554
+ }, ...isEmbedded(props.api) ? [] : [{
555
+ name: `${commandPrefix}.size`,
556
+ title: "Dialog size",
557
+ run: () => props.onDone({
558
+ type: "size"
559
+ })
560
+ }], {
561
+ name: `${commandPrefix}.back`,
562
+ title: "Exit",
563
+ run: () => props.onDone({
564
+ type: "exit"
565
+ })
566
+ }];
567
+ const unregister = props.api.keymap.registerLayer({
568
+ priority: 1e4,
569
+ commands,
570
+ bindings: [{
571
+ key: "up",
572
+ cmd: `${commandPrefix}.up`,
573
+ desc: "Previous"
574
+ }, {
575
+ key: "ctrl+p",
576
+ cmd: `${commandPrefix}.up`,
577
+ desc: "Previous"
578
+ }, {
579
+ key: "down",
580
+ cmd: `${commandPrefix}.down`,
581
+ desc: "Next"
582
+ }, {
583
+ key: "ctrl+n",
584
+ cmd: `${commandPrefix}.down`,
585
+ desc: "Next"
586
+ }, {
587
+ key: "enter",
588
+ cmd: `${commandPrefix}.select`,
589
+ desc: "Delete"
590
+ }, {
591
+ key: "i",
592
+ cmd: `${commandPrefix}.details`,
593
+ desc: "Details"
594
+ }, {
595
+ key: "c",
596
+ cmd: `${commandPrefix}.cleanup`,
597
+ desc: "Cleanup idle"
598
+ }, {
599
+ key: "r",
600
+ cmd: `${commandPrefix}.refresh`,
601
+ desc: "Refresh"
602
+ }, {
603
+ key: "s",
604
+ cmd: `${commandPrefix}.switch`,
605
+ desc: "Switch root"
606
+ }, ...isEmbedded(props.api) ? [] : [{
607
+ key: "o",
608
+ cmd: `${commandPrefix}.size`,
609
+ desc: "Dialog size"
610
+ }], {
611
+ key: "escape",
612
+ cmd: `${commandPrefix}.back`,
613
+ desc: "Exit"
614
+ }]
615
+ });
616
+ onCleanup(() => {
617
+ unregister();
618
+ popMode();
619
+ });
620
+ const runningCount = props.rows.filter((row) => row.running).length;
621
+ const hiddenCount = props.rows.filter((row) => row.hidden).length;
622
+ const statusLabel = props.activeKnown ? runningCount > 0 ? `${runningCount} running` : "none running" : "status unknown";
623
+ return (() => {
624
+ var _el$ = _$createElement("box"), _el$2 = _$createElement("box"), _el$3 = _$createElement("text"), _el$4 = _$createElement("b"), _el$5 = _$createElement("text"), _el$7 = _$createElement("box"), _el$8 = _$createElement("text"), _el$9 = _$createElement("text"), _el$1 = _$createElement("text"), _el$10 = _$createElement("scrollbox"), _el$11 = _$createElement("box"), _el$12 = _$createElement("box"), _el$13 = _$createElement("text"), _el$15 = _$createElement("text"), _el$17 = _$createElement("text"), _el$19 = _$createElement("text"), _el$21 = _$createElement("text");
625
+ _$insertNode(_el$, _el$2);
626
+ _$insertNode(_el$, _el$7);
627
+ _$insertNode(_el$, _el$10);
628
+ _$insertNode(_el$, _el$12);
629
+ _$setProp(_el$, "flexDirection", "column");
630
+ _$setProp(_el$, "width", "100%");
631
+ _$setProp(_el$, "paddingLeft", 2);
632
+ _$setProp(_el$, "paddingRight", 2);
633
+ _$setProp(_el$, "paddingTop", 1);
634
+ _$setProp(_el$, "paddingBottom", 1);
635
+ _$insertNode(_el$2, _el$3);
636
+ _$insertNode(_el$2, _el$5);
637
+ _$setProp(_el$2, "flexDirection", "row");
638
+ _$setProp(_el$2, "justifyContent", "space-between");
639
+ _$setProp(_el$2, "width", "100%");
640
+ _$setProp(_el$2, "marginBottom", 1);
641
+ _$insertNode(_el$3, _el$4);
642
+ _$insert(_el$4, () => `Subagent sessions \u2014 ${truncate(props.rootTitle, 40)}`);
643
+ _$insertNode(_el$5, _$createTextNode(`esc exit`));
644
+ _$setProp(_el$5, "onMouseUp", () => props.onDone({
645
+ type: "exit"
646
+ }));
647
+ _$insertNode(_el$7, _el$8);
648
+ _$insertNode(_el$7, _el$9);
649
+ _$insertNode(_el$7, _el$1);
650
+ _$setProp(_el$7, "flexDirection", "row");
651
+ _$setProp(_el$7, "gap", 3);
652
+ _$setProp(_el$7, "marginBottom", 1);
653
+ _$insert(_el$8, () => `${props.rows.length} session(s)`);
654
+ _$insert(_el$9, statusLabel);
655
+ _$insert(_el$7, _$createComponent(Show, {
656
+ when: hiddenCount > 0,
657
+ get children() {
658
+ var _el$0 = _$createElement("text");
659
+ _$insert(_el$0, `${hiddenCount} hidden`);
660
+ _$effect((_$p) => _$setProp(_el$0, "fg", theme().warning, _$p));
661
+ return _el$0;
662
+ }
663
+ }), _el$1);
664
+ _$insert(_el$1, () => `${props.idleCount} idle`);
665
+ _$insertNode(_el$10, _el$11);
666
+ _$use((element) => scroll = element, _el$10);
667
+ _$setProp(_el$11, "flexDirection", "column");
668
+ _$setProp(_el$11, "gap", 0);
669
+ _$insert(_el$11, _$createComponent(Show, {
670
+ get when() {
671
+ return props.rows.length > 0;
672
+ },
673
+ get fallback() {
674
+ return (() => {
675
+ var _el$25 = _$createElement("box"), _el$26 = _$createElement("text");
676
+ _$insertNode(_el$25, _el$26);
677
+ _$setProp(_el$25, "flexDirection", "row");
678
+ _$setProp(_el$25, "paddingLeft", 1);
679
+ _$setProp(_el$25, "paddingTop", 1);
680
+ _$setProp(_el$25, "paddingBottom", 1);
681
+ _$insertNode(_el$26, _$createTextNode(`No subagent sessions in this session's tree.`));
682
+ _$effect((_$p) => _$setProp(_el$26, "fg", theme().textMuted, _$p));
683
+ return _el$25;
684
+ })();
685
+ },
686
+ get children() {
687
+ return _$createComponent(For, {
688
+ get each() {
689
+ return props.rows;
690
+ },
691
+ children: (row, index) => {
692
+ const active = createMemo(() => selected() === index());
693
+ const titleFg = createMemo(() => active() ? theme().background : row.running ? theme().success : row.hidden ? theme().textMuted : theme().text);
694
+ const badgeFg = createMemo(() => active() ? theme().background : theme().warning);
695
+ return (() => {
696
+ var _el$28 = _$createElement("box"), _el$29 = _$createElement("text"), _el$30 = _$createElement("text"), _el$31 = _$createElement("b"), _el$34 = _$createElement("text"), _el$35 = _$createElement("text");
697
+ _$insertNode(_el$28, _el$29);
698
+ _$insertNode(_el$28, _el$30);
699
+ _$insertNode(_el$28, _el$34);
700
+ _$insertNode(_el$28, _el$35);
701
+ _$setProp(_el$28, "flexDirection", "row");
702
+ _$setProp(_el$28, "width", "100%");
703
+ _$setProp(_el$28, "gap", 1);
704
+ _$setProp(_el$28, "paddingRight", 1);
705
+ _$setProp(_el$28, "onMouseOver", () => setSelected(index()));
706
+ _$setProp(_el$28, "onMouseUp", () => props.onDone({
707
+ type: "delete",
708
+ row
709
+ }));
710
+ _$setProp(_el$29, "flexShrink", 0);
711
+ _$insert(_el$29, () => row.running ? "\u25B6" : "\xB7");
712
+ _$insertNode(_el$30, _el$31);
713
+ _$setProp(_el$30, "width", 16);
714
+ _$setProp(_el$30, "flexShrink", 0);
715
+ _$setProp(_el$30, "wrapMode", "none");
716
+ _$setProp(_el$30, "overflow", "hidden");
717
+ _$insert(_el$31, () => truncate(row.session.agent ?? "agent", agentWidth));
718
+ _$insert(_el$28, _$createComponent(Show, {
719
+ get when() {
720
+ return row.hidden;
721
+ },
722
+ get children() {
723
+ var _el$32 = _$createElement("text");
724
+ _$insertNode(_el$32, _$createTextNode(`[hidden]`));
725
+ _$setProp(_el$32, "width", 9);
726
+ _$setProp(_el$32, "flexShrink", 0);
727
+ _$setProp(_el$32, "wrapMode", "none");
728
+ _$setProp(_el$32, "overflow", "hidden");
729
+ _$effect((_$p) => _$setProp(_el$32, "fg", badgeFg(), _$p));
730
+ return _el$32;
731
+ }
732
+ }), _el$34);
733
+ _$setProp(_el$34, "flexGrow", 1);
734
+ _$setProp(_el$34, "wrapMode", "none");
735
+ _$setProp(_el$34, "overflow", "hidden");
736
+ _$insert(_el$34, () => truncate(rowTitle(row), titleWidth()));
737
+ _$setProp(_el$35, "width", 9);
738
+ _$setProp(_el$35, "flexShrink", 0);
739
+ _$setProp(_el$35, "wrapMode", "none");
740
+ _$setProp(_el$35, "overflow", "hidden");
741
+ _$insert(_el$35, () => formatAge(row.session.updated ?? row.session.created));
742
+ _$effect((_p$) => {
743
+ var _v$10 = 1 + row.depth * 2, _v$11 = active() ? theme().primary : theme().backgroundPanel, _v$12 = titleFg(), _v$13 = active() ? theme().background : theme().textMuted, _v$14 = titleWidth(), _v$15 = titleFg(), _v$16 = active() ? theme().background : theme().textMuted;
744
+ _v$10 !== _p$.e && (_p$.e = _$setProp(_el$28, "paddingLeft", _v$10, _p$.e));
745
+ _v$11 !== _p$.t && (_p$.t = _$setProp(_el$28, "backgroundColor", _v$11, _p$.t));
746
+ _v$12 !== _p$.a && (_p$.a = _$setProp(_el$29, "fg", _v$12, _p$.a));
747
+ _v$13 !== _p$.o && (_p$.o = _$setProp(_el$30, "fg", _v$13, _p$.o));
748
+ _v$14 !== _p$.i && (_p$.i = _$setProp(_el$34, "width", _v$14, _p$.i));
749
+ _v$15 !== _p$.n && (_p$.n = _$setProp(_el$34, "fg", _v$15, _p$.n));
750
+ _v$16 !== _p$.s && (_p$.s = _$setProp(_el$35, "fg", _v$16, _p$.s));
751
+ return _p$;
752
+ }, {
753
+ e: undefined,
754
+ t: undefined,
755
+ a: undefined,
756
+ o: undefined,
757
+ i: undefined,
758
+ n: undefined,
759
+ s: undefined
760
+ });
761
+ return _el$28;
762
+ })();
763
+ }
764
+ });
765
+ }
766
+ }));
767
+ _$insertNode(_el$12, _el$13);
768
+ _$insertNode(_el$12, _el$15);
769
+ _$insertNode(_el$12, _el$17);
770
+ _$insertNode(_el$12, _el$19);
771
+ _$insertNode(_el$12, _el$21);
772
+ _$setProp(_el$12, "flexDirection", "row");
773
+ _$setProp(_el$12, "gap", 3);
774
+ _$setProp(_el$12, "marginTop", 1);
775
+ _$insertNode(_el$13, _$createTextNode(`enter delete`));
776
+ _$insertNode(_el$15, _$createTextNode(`i details`));
777
+ _$insertNode(_el$17, _$createTextNode(`c cleanup idle`));
778
+ _$insertNode(_el$19, _$createTextNode(`r refresh`));
779
+ _$insertNode(_el$21, _$createTextNode(`s switch session`));
780
+ _$insert(_el$12, _$createComponent(Show, {
781
+ get when() {
782
+ return !isEmbedded(props.api);
783
+ },
784
+ get children() {
785
+ var _el$23 = _$createElement("text");
786
+ _$insertNode(_el$23, _$createTextNode(`o size`));
787
+ _$effect((_$p) => _$setProp(_el$23, "fg", theme().textMuted, _$p));
788
+ return _el$23;
789
+ }
790
+ }), null);
791
+ _$effect((_p$) => {
792
+ var _v$ = theme().text, _v$2 = theme().textMuted, _v$3 = theme().textMuted, _v$4 = runningCount > 0 ? theme().success : theme().textMuted, _v$5 = theme().textMuted, _v$6 = listHeight(), _v$7 = theme().textMuted, _v$8 = theme().textMuted, _v$9 = theme().textMuted, _v$0 = theme().textMuted, _v$1 = theme().textMuted;
793
+ _v$ !== _p$.e && (_p$.e = _$setProp(_el$3, "fg", _v$, _p$.e));
794
+ _v$2 !== _p$.t && (_p$.t = _$setProp(_el$5, "fg", _v$2, _p$.t));
795
+ _v$3 !== _p$.a && (_p$.a = _$setProp(_el$8, "fg", _v$3, _p$.a));
796
+ _v$4 !== _p$.o && (_p$.o = _$setProp(_el$9, "fg", _v$4, _p$.o));
797
+ _v$5 !== _p$.i && (_p$.i = _$setProp(_el$1, "fg", _v$5, _p$.i));
798
+ _v$6 !== _p$.n && (_p$.n = _$setProp(_el$10, "maxHeight", _v$6, _p$.n));
799
+ _v$7 !== _p$.s && (_p$.s = _$setProp(_el$13, "fg", _v$7, _p$.s));
800
+ _v$8 !== _p$.h && (_p$.h = _$setProp(_el$15, "fg", _v$8, _p$.h));
801
+ _v$9 !== _p$.r && (_p$.r = _$setProp(_el$17, "fg", _v$9, _p$.r));
802
+ _v$0 !== _p$.d && (_p$.d = _$setProp(_el$19, "fg", _v$0, _p$.d));
803
+ _v$1 !== _p$.l && (_p$.l = _$setProp(_el$21, "fg", _v$1, _p$.l));
804
+ return _p$;
805
+ }, {
806
+ e: undefined,
807
+ t: undefined,
808
+ a: undefined,
809
+ o: undefined,
810
+ i: undefined,
811
+ n: undefined,
812
+ s: undefined,
813
+ h: undefined,
814
+ r: undefined,
815
+ d: undefined,
816
+ l: undefined
817
+ });
818
+ return _el$;
819
+ })();
820
+ }
821
+ function showDangerConfirm(api, props) {
822
+ return new Promise((resolve) => {
823
+ let settled = false;
824
+ const done = (value) => {
825
+ if (settled)
826
+ return;
827
+ settled = true;
828
+ resolve(value);
829
+ api.ui.dialog.clear();
830
+ };
831
+ api.ui.dialog.replace(() => _$createComponent(DangerConfirmDialog, {
832
+ api,
833
+ get title() {
834
+ return props.title;
835
+ },
836
+ get message() {
837
+ return props.message;
838
+ },
839
+ get confirmLabel() {
840
+ return props.confirmLabel;
841
+ },
842
+ onDone: done
843
+ }), () => done(false));
844
+ });
845
+ }
846
+ function DangerConfirmDialog(props) {
847
+ const theme = () => themeOf(props.api);
848
+ useDialogSize(props.api);
849
+ const popMode = props.api.mode.push("subagent-explorer.confirm");
850
+ const commandPrefix = `subagent-explorer.confirm.${Math.random().toString(36).slice(2)}`;
851
+ const unregister = props.api.keymap.registerLayer({
852
+ priority: 1e4,
853
+ commands: [{
854
+ name: `${commandPrefix}.confirm`,
855
+ title: "Confirm",
856
+ run: () => props.onDone(true)
857
+ }, {
858
+ name: `${commandPrefix}.cancel`,
859
+ title: "Cancel",
860
+ run: () => props.onDone(false)
861
+ }],
862
+ bindings: [{
863
+ key: "enter",
864
+ cmd: `${commandPrefix}.confirm`,
865
+ desc: "Confirm"
866
+ }, {
867
+ key: "escape",
868
+ cmd: `${commandPrefix}.cancel`,
869
+ desc: "Cancel"
870
+ }]
871
+ });
872
+ onCleanup(() => {
873
+ unregister();
874
+ popMode();
875
+ });
876
+ return (() => {
877
+ var _el$36 = _$createElement("box"), _el$37 = _$createElement("text"), _el$38 = _$createElement("b"), _el$39 = _$createElement("text"), _el$40 = _$createElement("box"), _el$41 = _$createElement("box"), _el$42 = _$createElement("text"), _el$43 = _$createElement("b"), _el$44 = _$createElement("box"), _el$45 = _$createElement("text");
878
+ _$insertNode(_el$36, _el$37);
879
+ _$insertNode(_el$36, _el$39);
880
+ _$insertNode(_el$36, _el$40);
881
+ _$setProp(_el$36, "flexDirection", "column");
882
+ _$setProp(_el$36, "width", "100%");
883
+ _$setProp(_el$36, "paddingLeft", 2);
884
+ _$setProp(_el$36, "paddingRight", 2);
885
+ _$setProp(_el$36, "paddingTop", 1);
886
+ _$setProp(_el$36, "paddingBottom", 1);
887
+ _$setProp(_el$36, "gap", 1);
888
+ _$insertNode(_el$37, _el$38);
889
+ _$insert(_el$38, () => props.title);
890
+ _$insert(_el$39, () => props.message);
891
+ _$insertNode(_el$40, _el$41);
892
+ _$insertNode(_el$40, _el$44);
893
+ _$setProp(_el$40, "flexDirection", "row");
894
+ _$setProp(_el$40, "gap", 4);
895
+ _$setProp(_el$40, "marginTop", 1);
896
+ _$insertNode(_el$41, _el$42);
897
+ _$setProp(_el$41, "paddingLeft", 1);
898
+ _$setProp(_el$41, "paddingRight", 1);
899
+ _$setProp(_el$41, "onMouseUp", () => props.onDone(true));
900
+ _$insertNode(_el$42, _el$43);
901
+ _$insert(_el$43, () => props.confirmLabel);
902
+ _$insertNode(_el$44, _el$45);
903
+ _$setProp(_el$44, "paddingLeft", 1);
904
+ _$setProp(_el$44, "paddingRight", 1);
905
+ _$setProp(_el$44, "onMouseUp", () => props.onDone(false));
906
+ _$insertNode(_el$45, _$createTextNode(`Cancel (esc)`));
907
+ _$effect((_p$) => {
908
+ var _v$17 = theme().error, _v$18 = theme().text, _v$19 = theme().error, _v$20 = theme().background, _v$21 = theme().textMuted;
909
+ _v$17 !== _p$.e && (_p$.e = _$setProp(_el$37, "fg", _v$17, _p$.e));
910
+ _v$18 !== _p$.t && (_p$.t = _$setProp(_el$39, "fg", _v$18, _p$.t));
911
+ _v$19 !== _p$.a && (_p$.a = _$setProp(_el$41, "backgroundColor", _v$19, _p$.a));
912
+ _v$20 !== _p$.o && (_p$.o = _$setProp(_el$42, "fg", _v$20, _p$.o));
913
+ _v$21 !== _p$.i && (_p$.i = _$setProp(_el$45, "fg", _v$21, _p$.i));
914
+ return _p$;
915
+ }, {
916
+ e: undefined,
917
+ t: undefined,
918
+ a: undefined,
919
+ o: undefined,
920
+ i: undefined
921
+ });
922
+ return _el$36;
923
+ })();
924
+ }
925
+ function showInfo(api, props) {
926
+ return new Promise((resolve) => {
927
+ let settled = false;
928
+ const done = () => {
929
+ if (settled)
930
+ return;
931
+ settled = true;
932
+ resolve();
933
+ api.ui.dialog.clear();
934
+ };
935
+ api.ui.dialog.replace(() => _$createComponent(InfoDialog, {
936
+ api,
937
+ get title() {
938
+ return props.title;
939
+ },
940
+ get message() {
941
+ return props.message;
942
+ },
943
+ onDone: done
944
+ }), done);
945
+ });
946
+ }
947
+ function InfoDialog(props) {
948
+ const theme = () => themeOf(props.api);
949
+ const dimensions = useTerminalDimensions();
950
+ useDialogSize(props.api);
951
+ const popMode = props.api.mode.push("subagent-explorer.info");
952
+ const commandPrefix = `subagent-explorer.info.${Math.random().toString(36).slice(2)}`;
953
+ const unregister = props.api.keymap.registerLayer({
954
+ priority: 1e4,
955
+ commands: [{
956
+ name: `${commandPrefix}.done`,
957
+ title: "Close",
958
+ run: () => props.onDone()
959
+ }],
960
+ bindings: [{
961
+ key: "enter",
962
+ cmd: `${commandPrefix}.done`,
963
+ desc: "Close"
964
+ }, {
965
+ key: "escape",
966
+ cmd: `${commandPrefix}.done`,
967
+ desc: "Close"
968
+ }]
969
+ });
970
+ onCleanup(() => {
971
+ unregister();
972
+ popMode();
973
+ });
974
+ const lines = props.message.split(`
975
+ `);
976
+ const maxHeight = cappedHeight(lines.length, listRows(props.api, dimensions().height, 6, 3));
977
+ return (() => {
978
+ var _el$47 = _$createElement("box"), _el$48 = _$createElement("text"), _el$49 = _$createElement("b"), _el$50 = _$createElement("scrollbox"), _el$51 = _$createElement("box"), _el$52 = _$createElement("box"), _el$53 = _$createElement("text");
979
+ _$insertNode(_el$47, _el$48);
980
+ _$insertNode(_el$47, _el$50);
981
+ _$insertNode(_el$47, _el$52);
982
+ _$setProp(_el$47, "flexDirection", "column");
983
+ _$setProp(_el$47, "width", "100%");
984
+ _$setProp(_el$47, "paddingLeft", 2);
985
+ _$setProp(_el$47, "paddingRight", 2);
986
+ _$setProp(_el$47, "paddingTop", 1);
987
+ _$setProp(_el$47, "paddingBottom", 1);
988
+ _$insertNode(_el$48, _el$49);
989
+ _$insert(_el$49, () => props.title);
990
+ _$insertNode(_el$50, _el$51);
991
+ _$setProp(_el$50, "maxHeight", maxHeight);
992
+ _$setProp(_el$50, "marginTop", 1);
993
+ _$setProp(_el$51, "flexDirection", "column");
994
+ _$setProp(_el$51, "gap", 0);
995
+ _$insert(_el$51, _$createComponent(For, {
996
+ each: lines,
997
+ children: (line) => (() => {
998
+ var _el$55 = _$createElement("text");
999
+ _$insert(_el$55, () => line.replace(/^!!/, ""));
1000
+ _$effect((_$p) => _$setProp(_el$55, "fg", line.startsWith("!!") ? theme().error : theme().text, _$p));
1001
+ return _el$55;
1002
+ })()
1003
+ }));
1004
+ _$insertNode(_el$52, _el$53);
1005
+ _$setProp(_el$52, "flexDirection", "row");
1006
+ _$setProp(_el$52, "gap", 3);
1007
+ _$setProp(_el$52, "marginTop", 1);
1008
+ _$insertNode(_el$53, _$createTextNode(`enter/esc close`));
1009
+ _$effect((_p$) => {
1010
+ var _v$22 = theme().text, _v$23 = theme().textMuted;
1011
+ _v$22 !== _p$.e && (_p$.e = _$setProp(_el$48, "fg", _v$22, _p$.e));
1012
+ _v$23 !== _p$.t && (_p$.t = _$setProp(_el$53, "fg", _v$23, _p$.t));
1013
+ return _p$;
1014
+ }, {
1015
+ e: undefined,
1016
+ t: undefined
1017
+ });
1018
+ return _el$47;
1019
+ })();
1020
+ }
1021
+ function showConfirm(api, props) {
1022
+ return new Promise((resolve) => {
1023
+ let settled = false;
1024
+ const done = (value) => {
1025
+ if (settled)
1026
+ return;
1027
+ settled = true;
1028
+ resolve(value);
1029
+ api.ui.dialog.clear();
1030
+ };
1031
+ api.ui.dialog.replace(() => api.ui.DialogConfirm({
1032
+ title: props.title,
1033
+ message: props.message,
1034
+ onConfirm: () => done(true),
1035
+ onCancel: () => done(false)
1036
+ }), () => done(false));
1037
+ });
1038
+ }
1039
+ async function deleteOne(api, row) {
1040
+ const label = `${rowTitle(row)}${row.hidden ? " [hidden agent]" : ""}`;
1041
+ const summary = `${label}
1042
+ agent: ${row.session.agent ?? "unknown"}
1043
+ last activity: ${formatAge(row.session.updated ?? row.session.created)}
1044
+
1045
+ Deleting a session permanently removes it, its messages, and its history. The parent session keeps the task result text; only the jump target is lost.`;
1046
+ const confirmed = row.running ? await showDangerConfirm(api, {
1047
+ title: "Delete RUNNING subagent session?",
1048
+ message: `${summary}
1049
+
1050
+ !!This session is running right now. Deleting it interrupts the work in progress.`,
1051
+ confirmLabel: "Delete anyway"
1052
+ }) : await showConfirm(api, {
1053
+ title: "Delete subagent session?",
1054
+ message: summary
1055
+ });
1056
+ if (!confirmed)
1057
+ return false;
1058
+ const ok = await deleteSession(api.client, row.session.id);
1059
+ api.ui.toast({
1060
+ variant: ok ? "default" : "error",
1061
+ title: ok ? "Session deleted" : "Delete failed",
1062
+ message: ok ? label : `Could not delete ${label} \u2014 the host client exposes no working session delete.`
1063
+ });
1064
+ return ok;
1065
+ }
1066
+ async function runCleanup(api, plan, rootTitle) {
1067
+ if (plan.deletable.length === 0) {
1068
+ await showInfo(api, {
1069
+ title: "Nothing to clean up",
1070
+ message: plan.sparedActive.length > 0 ? `All ${plan.sparedActive.length} subagent session(s) are running right now.` : "There are no subagent sessions in this session's tree."
1071
+ });
1072
+ return 0;
1073
+ }
1074
+ const runningNote = plan.sparedActive.length > 0 ? `
1075
+
1076
+ ${plan.sparedActive.length} running session(s) will be spared.` : "";
1077
+ const confirmed = await showDangerConfirm(api, {
1078
+ title: `Delete ${plan.deletable.length} idle subagent session(s)?`,
1079
+ message: `Permanently deletes every idle subagent session in "${truncate(rootTitle, 40)}"'s tree (nested children included), with all messages and history.${runningNote}`,
1080
+ confirmLabel: "Delete idle sessions"
1081
+ });
1082
+ if (!confirmed)
1083
+ return 0;
1084
+ let deleted = 0;
1085
+ for (const session of plan.deletable) {
1086
+ if (await deleteSession(api.client, session.id))
1087
+ deleted += 1;
1088
+ }
1089
+ const failed = plan.deletable.length - deleted;
1090
+ api.ui.toast({
1091
+ variant: failed > 0 ? "warning" : "default",
1092
+ title: "Cleanup finished",
1093
+ message: failed > 0 ? `Deleted ${deleted}, failed ${failed}.` : `Deleted ${deleted} session(s).`
1094
+ });
1095
+ return deleted;
1096
+ }
1097
+ function showRootPicker(api, sessions, currentRoot) {
1098
+ return new Promise((resolve) => {
1099
+ let settled = false;
1100
+ const done = (value) => {
1101
+ if (settled)
1102
+ return;
1103
+ settled = true;
1104
+ resolve(value);
1105
+ api.ui.dialog.clear();
1106
+ };
1107
+ const options = parentCandidates(sessions).slice(0, 200).map((session) => ({
1108
+ title: truncate(session.title ?? session.id, 48),
1109
+ value: session.id,
1110
+ description: `${session.agent ?? "agent"} \xB7 ${formatAge(session.updated ?? session.created)}${session.id === currentRoot ? " \xB7 current" : ""}`
1111
+ }));
1112
+ api.ui.dialog.replace(() => api.ui.DialogSelect({
1113
+ title: "Explore which session's subagents?",
1114
+ placeholder: "Sessions that have subagents",
1115
+ options,
1116
+ current: currentRoot,
1117
+ onSelect: (option) => done(option.value)
1118
+ }), () => done(undefined));
1119
+ });
1120
+ }
1121
+ async function showDetails(api, row) {
1122
+ const details = await fetchSessionDetails(api.client, row.session.id);
1123
+ const lines = [`title: ${rowTitle(row)}`, `agent: ${row.session.agent ?? "unknown"}${row.hidden ? " (hidden/background)" : ""}`, `session: ${row.session.id}`, `status: ${row.running ? "running" : "idle"}`, `created: ${formatAge(row.session.created)}`, `last activity: ${formatAge(row.session.updated ?? row.session.created)}`];
1124
+ if (details) {
1125
+ lines.push(`messages: ${details.messageCount}`);
1126
+ if (details.model)
1127
+ lines.push(`last model: ${details.model}`);
1128
+ } else {
1129
+ lines.push("messages: unknown (host client does not expose message listing)");
1130
+ }
1131
+ if (details?.firstPrompt) {
1132
+ lines.push("");
1133
+ lines.push("first prompt:");
1134
+ lines.push(details.firstPrompt);
1135
+ }
1136
+ await showInfo(api, {
1137
+ title: "Subagent session",
1138
+ message: lines.join(`
1139
+ `)
1140
+ });
1141
+ }
1142
+ function showPrompt(api, props) {
1143
+ return new Promise((resolve) => {
1144
+ let settled = false;
1145
+ const done = (value) => {
1146
+ if (settled)
1147
+ return;
1148
+ settled = true;
1149
+ resolve(value);
1150
+ api.ui.dialog.clear();
1151
+ };
1152
+ api.ui.dialog.replace(() => api.ui.DialogPrompt({
1153
+ title: props.title,
1154
+ placeholder: props.placeholder,
1155
+ value: props.value,
1156
+ onConfirm: (value) => done(value),
1157
+ onCancel: () => done(undefined)
1158
+ }), () => done(undefined));
1159
+ });
1160
+ }
1161
+ async function showSizeSlider(api) {
1162
+ let current = dialogHeightPercent(api);
1163
+ while (true) {
1164
+ const choice = await showSizeSliderOnce(api, current);
1165
+ if (!choice)
1166
+ return;
1167
+ current = choice.height;
1168
+ if (choice.action === "save")
1169
+ return choice;
1170
+ const input = await showPrompt(api, {
1171
+ title: "Dialog height percent",
1172
+ placeholder: `${HEIGHT_PERCENT_MIN}-${HEIGHT_PERCENT_MAX}`,
1173
+ value: String(current)
1174
+ });
1175
+ if (input === undefined)
1176
+ continue;
1177
+ const value = Number(input);
1178
+ if (!Number.isFinite(value)) {
1179
+ await showInfo(api, {
1180
+ title: "Invalid height",
1181
+ message: `Enter a number from ${HEIGHT_PERCENT_MIN} to ${HEIGHT_PERCENT_MAX}.`
1182
+ });
1183
+ continue;
1184
+ }
1185
+ current = clampHeightPercent(value);
1186
+ }
1187
+ }
1188
+ function showSizeSliderOnce(api, current) {
1189
+ return new Promise((resolve) => {
1190
+ let settled = false;
1191
+ const done = (value, clear = true) => {
1192
+ if (settled)
1193
+ return;
1194
+ settled = true;
1195
+ resolve(value);
1196
+ if (clear)
1197
+ api.ui.dialog.clear();
1198
+ };
1199
+ api.ui.dialog.replace(() => _$createComponent(SizeSliderDialog, {
1200
+ api,
1201
+ current,
1202
+ onDone: done
1203
+ }), () => done(undefined, false));
1204
+ });
1205
+ }
1206
+ function SizeSliderDialog(props) {
1207
+ const theme = () => themeOf(props.api);
1208
+ const dimensions = useTerminalDimensions();
1209
+ useDialogSize(props.api);
1210
+ const [height, setHeight] = createSignal(clampHeightPercent(props.current));
1211
+ const popMode = props.api.mode.push("subagent-explorer.size");
1212
+ const commandPrefix = `subagent-explorer.size.${Math.random().toString(36).slice(2)}`;
1213
+ const cycleWidth = () => setDialogSize(props.api, nextDialogSize(props.api));
1214
+ const setPreset = (preset) => {
1215
+ const found = HEIGHT_PRESETS.find((item) => item.label === preset);
1216
+ if (found)
1217
+ setHeight(found.value);
1218
+ };
1219
+ const move = (delta) => setHeight((value) => clampHeightPercent(value + delta));
1220
+ const sliderWidth = createMemo(() => dialogSizeOf(props.api) === "xlarge" ? 64 : dialogSizeOf(props.api) === "large" ? 48 : 34);
1221
+ const sliderCells = createMemo(() => {
1222
+ const width = sliderWidth();
1223
+ const selected = Math.round((height() - HEIGHT_PERCENT_MIN) / (HEIGHT_PERCENT_MAX - HEIGHT_PERCENT_MIN) * (width - 1));
1224
+ const presetPositions = new Map(HEIGHT_PRESETS.map((preset) => [Math.round((preset.value - HEIGHT_PERCENT_MIN) / (HEIGHT_PERCENT_MAX - HEIGHT_PERCENT_MIN) * (width - 1)), preset.label]));
1225
+ return Array.from({
1226
+ length: width
1227
+ }, (_, index) => {
1228
+ const isCurrent = index === selected;
1229
+ const preset = presetPositions.get(index);
1230
+ return {
1231
+ char: isCurrent ? "\u25CF" : preset ? "\u2502" : index < selected ? "\u2501" : "\u2500",
1232
+ color: isCurrent ? theme().primary : preset ? theme().accent : index < selected ? theme().success : theme().textMuted
1233
+ };
1234
+ });
1235
+ });
1236
+ const preview = createMemo(() => {
1237
+ const widthColumns = DIALOG_WIDTH_COLUMNS[dialogSizeOf(props.api)];
1238
+ const previewWidth = Math.max(10, Math.min(sliderWidth() + 4, 72));
1239
+ const scale = previewWidth / widthColumns;
1240
+ const metrics = computeDialogRows(height(), dimensions().height, 6, 4);
1241
+ const effective = metrics.targetRows;
1242
+ const requested = Math.floor(dimensions().height * Math.min(100, Math.max(25, height())) / 100);
1243
+ const rows = Math.max(4, Math.round(effective * scale));
1244
+ const fill = (text, width) => {
1245
+ const inner = width - 2;
1246
+ const slice = text.length > inner ? text.slice(0, inner - 1) + "\u2026" : text;
1247
+ return `\u2502${slice}${" ".repeat(Math.max(0, inner - slice.length))}\u2502`;
1248
+ };
1249
+ const lines = [];
1250
+ lines.push(`\u250C${"\u2500".repeat(previewWidth - 2)}\u2510`);
1251
+ lines.push(fill(`Subagent Explorer (preview)${effective < requested ? " [capped]" : ""}`, previewWidth));
1252
+ for (let index = 0;index < rows - 3; index++)
1253
+ lines.push(fill("", previewWidth));
1254
+ lines.push(`\u2514${"\u2500".repeat(previewWidth - 2)}\u2518`);
1255
+ return lines;
1256
+ });
1257
+ const shield = (ctx) => {
1258
+ ctx.event?.preventDefault?.();
1259
+ ctx.event?.stopPropagation?.();
1260
+ };
1261
+ const unregister = props.api.keymap.registerLayer({
1262
+ priority: 1e4,
1263
+ commands: [{
1264
+ name: `${commandPrefix}.width`,
1265
+ title: "Cycle width",
1266
+ run: (ctx) => {
1267
+ shield(ctx);
1268
+ cycleWidth();
1269
+ }
1270
+ }, {
1271
+ name: `${commandPrefix}.left`,
1272
+ title: "Lower height",
1273
+ run: (ctx) => {
1274
+ shield(ctx);
1275
+ move(-1);
1276
+ }
1277
+ }, {
1278
+ name: `${commandPrefix}.right`,
1279
+ title: "Raise height",
1280
+ run: (ctx) => {
1281
+ shield(ctx);
1282
+ move(1);
1283
+ }
1284
+ }, {
1285
+ name: `${commandPrefix}.down`,
1286
+ title: "Lower height faster",
1287
+ run: (ctx) => {
1288
+ shield(ctx);
1289
+ move(-5);
1290
+ }
1291
+ }, {
1292
+ name: `${commandPrefix}.up`,
1293
+ title: "Raise height faster",
1294
+ run: (ctx) => {
1295
+ shield(ctx);
1296
+ move(5);
1297
+ }
1298
+ }, {
1299
+ name: `${commandPrefix}.compact`,
1300
+ title: "Compact preset",
1301
+ run: (ctx) => {
1302
+ shield(ctx);
1303
+ setPreset("compact");
1304
+ }
1305
+ }, {
1306
+ name: `${commandPrefix}.normal`,
1307
+ title: "Normal preset",
1308
+ run: (ctx) => {
1309
+ shield(ctx);
1310
+ setPreset("normal");
1311
+ }
1312
+ }, {
1313
+ name: `${commandPrefix}.tall`,
1314
+ title: "Tall preset",
1315
+ run: (ctx) => {
1316
+ shield(ctx);
1317
+ setPreset("tall");
1318
+ }
1319
+ }, {
1320
+ name: `${commandPrefix}.max`,
1321
+ title: "Max preset",
1322
+ run: (ctx) => {
1323
+ shield(ctx);
1324
+ setPreset("max");
1325
+ }
1326
+ }, {
1327
+ name: `${commandPrefix}.custom`,
1328
+ title: "Custom percent",
1329
+ run: () => props.onDone({
1330
+ action: "custom-height",
1331
+ height: height()
1332
+ })
1333
+ }, {
1334
+ name: `${commandPrefix}.save`,
1335
+ title: "Save",
1336
+ run: () => {
1337
+ setDialogHeightPercent(props.api, height());
1338
+ props.onDone({
1339
+ action: "save",
1340
+ height: height()
1341
+ });
1342
+ }
1343
+ }, {
1344
+ name: `${commandPrefix}.back`,
1345
+ title: "Back",
1346
+ run: () => props.onDone(undefined)
1347
+ }],
1348
+ bindings: [{
1349
+ key: "w",
1350
+ cmd: `${commandPrefix}.width`,
1351
+ desc: "Cycle width"
1352
+ }, {
1353
+ key: "left",
1354
+ cmd: `${commandPrefix}.left`,
1355
+ desc: "Lower height"
1356
+ }, {
1357
+ key: "right",
1358
+ cmd: `${commandPrefix}.right`,
1359
+ desc: "Raise height"
1360
+ }, {
1361
+ key: "down",
1362
+ cmd: `${commandPrefix}.down`,
1363
+ desc: "Lower height faster"
1364
+ }, {
1365
+ key: "up",
1366
+ cmd: `${commandPrefix}.up`,
1367
+ desc: "Raise height faster"
1368
+ }, {
1369
+ key: "1",
1370
+ cmd: `${commandPrefix}.compact`,
1371
+ desc: "Compact preset"
1372
+ }, {
1373
+ key: "2",
1374
+ cmd: `${commandPrefix}.normal`,
1375
+ desc: "Normal preset"
1376
+ }, {
1377
+ key: "3",
1378
+ cmd: `${commandPrefix}.tall`,
1379
+ desc: "Tall preset"
1380
+ }, {
1381
+ key: "4",
1382
+ cmd: `${commandPrefix}.max`,
1383
+ desc: "Max preset"
1384
+ }, {
1385
+ key: "c",
1386
+ cmd: `${commandPrefix}.custom`,
1387
+ desc: "Custom percent"
1388
+ }, {
1389
+ key: "enter",
1390
+ cmd: `${commandPrefix}.save`,
1391
+ desc: "Save"
1392
+ }, {
1393
+ key: "escape",
1394
+ cmd: `${commandPrefix}.back`,
1395
+ desc: "Back"
1396
+ }]
1397
+ });
1398
+ onCleanup(() => {
1399
+ unregister();
1400
+ popMode();
1401
+ });
1402
+ return (() => {
1403
+ var _el$56 = _$createElement("box"), _el$57 = _$createElement("box"), _el$58 = _$createElement("text"), _el$59 = _$createElement("b"), _el$61 = _$createElement("text"), _el$63 = _$createElement("box"), _el$64 = _$createElement("text"), _el$66 = _$createElement("text"), _el$67 = _$createElement("b"), _el$68 = _$createElement("text"), _el$69 = _$createTextNode(` (`), _el$70 = _$createTextNode(` cols, w to cycle) Height: `), _el$71 = _$createElement("text"), _el$72 = _$createElement("b"), _el$73 = _$createTextNode(`%`), _el$74 = _$createElement("box"), _el$75 = _$createElement("text"), _el$76 = _$createTextNode(`25% `), _el$78 = _$createElement("text"), _el$79 = _$createTextNode(` 100%`), _el$82 = _$createElement("box"), _el$83 = _$createElement("box"), _el$84 = _$createElement("text"), _el$86 = _$createElement("text"), _el$88 = _$createElement("text"), _el$90 = _$createElement("text"), _el$92 = _$createElement("box"), _el$93 = _$createElement("box"), _el$94 = _$createElement("text"), _el$96 = _$createElement("box"), _el$97 = _$createElement("text"), _el$98 = _$createElement("b");
1404
+ _$insertNode(_el$56, _el$57);
1405
+ _$insertNode(_el$56, _el$63);
1406
+ _$insertNode(_el$56, _el$74);
1407
+ _$insertNode(_el$56, _el$82);
1408
+ _$insertNode(_el$56, _el$93);
1409
+ _$setProp(_el$56, "flexDirection", "column");
1410
+ _$setProp(_el$56, "width", "100%");
1411
+ _$setProp(_el$56, "paddingLeft", 2);
1412
+ _$setProp(_el$56, "paddingRight", 2);
1413
+ _$setProp(_el$56, "paddingTop", 1);
1414
+ _$setProp(_el$56, "paddingBottom", 1);
1415
+ _$insertNode(_el$57, _el$58);
1416
+ _$insertNode(_el$57, _el$61);
1417
+ _$setProp(_el$57, "flexDirection", "row");
1418
+ _$setProp(_el$57, "justifyContent", "space-between");
1419
+ _$setProp(_el$57, "width", "100%");
1420
+ _$setProp(_el$57, "marginBottom", 1);
1421
+ _$insertNode(_el$58, _el$59);
1422
+ _$insertNode(_el$59, _$createTextNode(`Dialog size`));
1423
+ _$insertNode(_el$61, _$createTextNode(`esc`));
1424
+ _$setProp(_el$61, "onMouseUp", () => props.onDone(undefined));
1425
+ _$insertNode(_el$63, _el$64);
1426
+ _$insertNode(_el$63, _el$66);
1427
+ _$insertNode(_el$63, _el$68);
1428
+ _$insertNode(_el$63, _el$71);
1429
+ _$setProp(_el$63, "flexDirection", "row");
1430
+ _$setProp(_el$63, "gap", 0);
1431
+ _$setProp(_el$63, "width", "100%");
1432
+ _$setProp(_el$63, "marginBottom", 1);
1433
+ _$insertNode(_el$64, _$createTextNode(`Width: `));
1434
+ _$insertNode(_el$66, _el$67);
1435
+ _$insert(_el$67, () => dialogSizeOf(props.api));
1436
+ _$insertNode(_el$68, _el$69);
1437
+ _$insertNode(_el$68, _el$70);
1438
+ _$insert(_el$68, () => DIALOG_WIDTH_COLUMNS[dialogSizeOf(props.api)], _el$70);
1439
+ _$insertNode(_el$71, _el$72);
1440
+ _$insertNode(_el$72, _el$73);
1441
+ _$insert(_el$72, height, _el$73);
1442
+ _$insertNode(_el$74, _el$75);
1443
+ _$insertNode(_el$74, _el$78);
1444
+ _$setProp(_el$74, "flexDirection", "row");
1445
+ _$setProp(_el$74, "width", "100%");
1446
+ _$setProp(_el$74, "marginBottom", 1);
1447
+ _$insertNode(_el$75, _el$76);
1448
+ _$insert(_el$74, _$createComponent(For, {
1449
+ get each() {
1450
+ return sliderCells();
1451
+ },
1452
+ children: (cell) => (() => {
1453
+ var _el$100 = _$createElement("text");
1454
+ _$insert(_el$100, () => cell.char);
1455
+ _$effect((_$p) => _$setProp(_el$100, "fg", cell.color, _$p));
1456
+ return _el$100;
1457
+ })()
1458
+ }), _el$78);
1459
+ _$insertNode(_el$78, _el$79);
1460
+ _$insertNode(_el$82, _el$83);
1461
+ _$insertNode(_el$82, _el$92);
1462
+ _$setProp(_el$82, "flexDirection", "row");
1463
+ _$setProp(_el$82, "gap", 2);
1464
+ _$setProp(_el$82, "marginBottom", 1);
1465
+ _$insertNode(_el$83, _el$84);
1466
+ _$insertNode(_el$83, _el$86);
1467
+ _$insertNode(_el$83, _el$88);
1468
+ _$insertNode(_el$83, _el$90);
1469
+ _$setProp(_el$83, "flexDirection", "column");
1470
+ _$setProp(_el$83, "gap", 0);
1471
+ _$insert(_el$83, _$createComponent(For, {
1472
+ each: HEIGHT_PRESETS,
1473
+ children: (preset) => (() => {
1474
+ var _el$101 = _$createElement("text"), _el$102 = _$createTextNode(` `), _el$103 = _$createTextNode(`: `), _el$104 = _$createTextNode(`%`);
1475
+ _$insertNode(_el$101, _el$102);
1476
+ _$insertNode(_el$101, _el$103);
1477
+ _$insertNode(_el$101, _el$104);
1478
+ _$insert(_el$101, () => preset.key, _el$102);
1479
+ _$insert(_el$101, () => preset.label, _el$103);
1480
+ _$insert(_el$101, () => preset.value, _el$104);
1481
+ _$effect((_$p) => _$setProp(_el$101, "fg", height() === preset.value ? theme().primary : theme().textMuted, _$p));
1482
+ return _el$101;
1483
+ })()
1484
+ }), _el$84);
1485
+ _$insertNode(_el$84, _$createTextNode(` `));
1486
+ _$insertNode(_el$86, _$createTextNode(`left/right 1%`));
1487
+ _$insertNode(_el$88, _$createTextNode(`up/down 5%`));
1488
+ _$insertNode(_el$90, _$createTextNode(`c custom`));
1489
+ _$setProp(_el$92, "flexDirection", "column");
1490
+ _$setProp(_el$92, "gap", 0);
1491
+ _$insert(_el$92, _$createComponent(For, {
1492
+ get each() {
1493
+ return preview();
1494
+ },
1495
+ children: (line) => (() => {
1496
+ var _el$105 = _$createElement("text");
1497
+ _$insert(_el$105, line);
1498
+ _$effect((_$p) => _$setProp(_el$105, "fg", theme().textMuted, _$p));
1499
+ return _el$105;
1500
+ })()
1501
+ }));
1502
+ _$insertNode(_el$93, _el$94);
1503
+ _$insertNode(_el$93, _el$96);
1504
+ _$setProp(_el$93, "flexDirection", "row");
1505
+ _$setProp(_el$93, "justifyContent", "space-between");
1506
+ _$setProp(_el$93, "width", "100%");
1507
+ _$insertNode(_el$94, _$createTextNode(`enter save`));
1508
+ _$insertNode(_el$96, _el$97);
1509
+ _$setProp(_el$96, "paddingLeft", 3);
1510
+ _$setProp(_el$96, "paddingRight", 3);
1511
+ _$setProp(_el$96, "onMouseUp", () => {
1512
+ setDialogHeightPercent(props.api, height());
1513
+ props.onDone({
1514
+ action: "save",
1515
+ height: height()
1516
+ });
1517
+ });
1518
+ _$insertNode(_el$97, _el$98);
1519
+ _$insertNode(_el$98, _$createTextNode(`save`));
1520
+ _$effect((_p$) => {
1521
+ var _v$24 = theme().accent, _v$25 = theme().textMuted, _v$26 = theme().textMuted, _v$27 = theme().primary, _v$28 = theme().textMuted, _v$29 = theme().primary, _v$30 = theme().textMuted, _v$31 = theme().textMuted, _v$32 = theme().textMuted, _v$33 = theme().textMuted, _v$34 = theme().textMuted, _v$35 = theme().textMuted, _v$36 = theme().textMuted, _v$37 = theme().primary, _v$38 = theme().background;
1522
+ _v$24 !== _p$.e && (_p$.e = _$setProp(_el$58, "fg", _v$24, _p$.e));
1523
+ _v$25 !== _p$.t && (_p$.t = _$setProp(_el$61, "fg", _v$25, _p$.t));
1524
+ _v$26 !== _p$.a && (_p$.a = _$setProp(_el$64, "fg", _v$26, _p$.a));
1525
+ _v$27 !== _p$.o && (_p$.o = _$setProp(_el$66, "fg", _v$27, _p$.o));
1526
+ _v$28 !== _p$.i && (_p$.i = _$setProp(_el$68, "fg", _v$28, _p$.i));
1527
+ _v$29 !== _p$.n && (_p$.n = _$setProp(_el$71, "fg", _v$29, _p$.n));
1528
+ _v$30 !== _p$.s && (_p$.s = _$setProp(_el$75, "fg", _v$30, _p$.s));
1529
+ _v$31 !== _p$.h && (_p$.h = _$setProp(_el$78, "fg", _v$31, _p$.h));
1530
+ _v$32 !== _p$.r && (_p$.r = _$setProp(_el$84, "fg", _v$32, _p$.r));
1531
+ _v$33 !== _p$.d && (_p$.d = _$setProp(_el$86, "fg", _v$33, _p$.d));
1532
+ _v$34 !== _p$.l && (_p$.l = _$setProp(_el$88, "fg", _v$34, _p$.l));
1533
+ _v$35 !== _p$.u && (_p$.u = _$setProp(_el$90, "fg", _v$35, _p$.u));
1534
+ _v$36 !== _p$.c && (_p$.c = _$setProp(_el$94, "fg", _v$36, _p$.c));
1535
+ _v$37 !== _p$.w && (_p$.w = _$setProp(_el$96, "backgroundColor", _v$37, _p$.w));
1536
+ _v$38 !== _p$.m && (_p$.m = _$setProp(_el$97, "fg", _v$38, _p$.m));
1537
+ return _p$;
1538
+ }, {
1539
+ e: undefined,
1540
+ t: undefined,
1541
+ a: undefined,
1542
+ o: undefined,
1543
+ i: undefined,
1544
+ n: undefined,
1545
+ s: undefined,
1546
+ h: undefined,
1547
+ r: undefined,
1548
+ d: undefined,
1549
+ l: undefined,
1550
+ u: undefined,
1551
+ c: undefined,
1552
+ w: undefined,
1553
+ m: undefined
1554
+ });
1555
+ return _el$56;
1556
+ })();
1557
+ }
1558
+ async function mainMenu(api) {
1559
+ const directory = api.state.path?.directory ?? api.state.path?.worktree ?? "";
1560
+ if (!directory) {
1561
+ await showInfo(api, {
1562
+ title: "Subagent Explorer",
1563
+ message: "No project directory is available from the host \u2014 cannot list sessions."
1564
+ });
1565
+ return;
1566
+ }
1567
+ let rootOverride;
1568
+ while (true) {
1569
+ const data = await loadExplorerData(api, directory);
1570
+ if (!data) {
1571
+ await showInfo(api, {
1572
+ title: "Subagent Explorer",
1573
+ message: `Could not load the session list from the host.
1574
+
1575
+ The host client does not expose session.list for this directory.`
1576
+ });
1577
+ return;
1578
+ }
1579
+ const rootID = currentSessionID(rootOverride ?? api.currentSessionID?.(), data.sessions);
1580
+ if (!rootID) {
1581
+ await showInfo(api, {
1582
+ title: "Subagent Explorer",
1583
+ message: "No sessions found in this directory."
1584
+ });
1585
+ return;
1586
+ }
1587
+ const tree = buildSessionTree(data.sessions, rootID);
1588
+ const rows = explorerRows(tree, data);
1589
+ const idleCount = rows.filter((row) => !row.running).length;
1590
+ const rootSession = data.sessions.find((session) => session.id === rootID);
1591
+ const action = await showTreeOnce(api, {
1592
+ rows,
1593
+ rootTitle: rootSession?.title ?? rootID,
1594
+ rootID,
1595
+ activeKnown: data.activeKnown,
1596
+ idleCount
1597
+ });
1598
+ if (!action || action.type === "exit")
1599
+ return;
1600
+ if (action.type === "refresh")
1601
+ continue;
1602
+ if (action.type === "size") {
1603
+ await showSizeSlider(api);
1604
+ continue;
1605
+ }
1606
+ if (action.type === "switch-root") {
1607
+ const next = await showRootPicker(api, data.sessions, rootID);
1608
+ if (next)
1609
+ rootOverride = next;
1610
+ continue;
1611
+ }
1612
+ if (action.type === "delete") {
1613
+ await deleteOne(api, action.row);
1614
+ continue;
1615
+ }
1616
+ if (action.type === "details") {
1617
+ await showDetails(api, action.row);
1618
+ continue;
1619
+ }
1620
+ if (action.type === "cleanup") {
1621
+ await runCleanup(api, cleanupTargets(tree, data.activeIDs), rootSession?.title ?? rootID);
1622
+ continue;
1623
+ }
1624
+ }
1625
+ }
1626
+
1627
+ // src/v2-tui.ts
1628
+ function pickThemeToken(source, ...path) {
1629
+ let current = source;
1630
+ for (const key of path) {
1631
+ if (!current || typeof current !== "object")
1632
+ return;
1633
+ current = current[key];
1634
+ }
1635
+ return current;
1636
+ }
1637
+ function wireLocation(ref) {
1638
+ if (!ref)
1639
+ return;
1640
+ return ref.workspaceID !== undefined ? { directory: ref.directory, workspace: ref.workspaceID } : { directory: ref.directory };
1641
+ }
1642
+ function withLocationQualifiedSessions(client, location) {
1643
+ if (!client || typeof client !== "object")
1644
+ return client;
1645
+ const session = client.session;
1646
+ if (!session || typeof session !== "object")
1647
+ return client;
1648
+ const wrapped = {};
1649
+ for (const method of ["list", "active", "status", "messages", "delete", "remove", "get", "wait"]) {
1650
+ const fn = session[method];
1651
+ if (typeof fn !== "function")
1652
+ continue;
1653
+ const bound = fn.bind(session);
1654
+ wrapped[method] = (input) => {
1655
+ const base = input && typeof input === "object" ? input : {};
1656
+ const loc = location();
1657
+ const merged = { ...loc, ...base };
1658
+ return bound(merged);
1659
+ };
1660
+ }
1661
+ return { ...client ?? {}, session: { ...session, ...wrapped } };
1662
+ }
1663
+ function buildV2Theme(context) {
1664
+ return {
1665
+ get current() {
1666
+ const theme = context.theme;
1667
+ const dark = context.themeMode === "dark";
1668
+ const text = pickThemeToken(theme, "text", "default");
1669
+ return {
1670
+ text,
1671
+ textMuted: pickThemeToken(theme, "text", "subdued") ?? text,
1672
+ background: pickThemeToken(theme, "background", "default"),
1673
+ backgroundPanel: pickThemeToken(theme, "background", "surface", "offset") ?? pickThemeToken(theme, "background", "default"),
1674
+ primary: pickThemeToken(theme, "text", "action", "primary", "selected") ?? text,
1675
+ secondary: pickThemeToken(theme, "hue", dark ? "interactive" : "neutral", dark ? 300 : 700) ?? text,
1676
+ accent: pickThemeToken(theme, "hue", "accent", dark ? 200 : 800) ?? text,
1677
+ success: pickThemeToken(theme, "text", "feedback", "success", "default") ?? text,
1678
+ warning: pickThemeToken(theme, "text", "feedback", "warning", "default") ?? text,
1679
+ error: pickThemeToken(theme, "text", "feedback", "error", "default") ?? text,
1680
+ info: pickThemeToken(theme, "text", "feedback", "info", "default") ?? text
1681
+ };
1682
+ }
1683
+ };
1684
+ }
1685
+ function convertKeymapLayer(layer) {
1686
+ const bindingsByName = new Map;
1687
+ for (const binding of layer.bindings ?? []) {
1688
+ if (!binding?.cmd)
1689
+ continue;
1690
+ const keys = bindingsByName.get(binding.cmd) ?? [];
1691
+ keys.push(binding.key);
1692
+ bindingsByName.set(binding.cmd, keys);
1693
+ }
1694
+ const commands = (layer.commands ?? []).map((command) => ({
1695
+ id: command.name,
1696
+ title: command.title ?? command.name,
1697
+ description: command.desc,
1698
+ ...bindingsByName.get(command.name)?.length ? { bind: bindingsByName.get(command.name).join(",") } : {},
1699
+ run: (_input, event) => command.run({ event: event ? { preventDefault: () => event.preventDefault?.(), stopPropagation: () => event.stopPropagation?.() } : undefined })
1700
+ }));
1701
+ return { mode: "global", priority: layer.priority, commands };
1702
+ }
1703
+ function buildV2HostApi(context) {
1704
+ const disposeFns = [];
1705
+ const locationRef = () => {
1706
+ try {
1707
+ return context.location ? context.location : context.data.location.default();
1708
+ } catch {
1709
+ return;
1710
+ }
1711
+ };
1712
+ const wireLoc = () => wireLocation(locationRef());
1713
+ const kvMemory = {};
1714
+ const kv = {
1715
+ get(key, fallback) {
1716
+ return kvMemory[key] === undefined ? fallback : kvMemory[key];
1717
+ },
1718
+ set(key, value) {
1719
+ kvMemory[key] = value;
1720
+ },
1721
+ ready: true
1722
+ };
1723
+ const state = {
1724
+ get config() {
1725
+ const agents = context.data.location.agent.list() ?? [];
1726
+ const agentRecord = {};
1727
+ for (const agent of agents) {
1728
+ agentRecord[agent.id] = {
1729
+ name: agent.name,
1730
+ mode: agent.mode,
1731
+ hidden: agent.hidden,
1732
+ color: agent.color
1733
+ };
1734
+ }
1735
+ return { agent: agentRecord };
1736
+ },
1737
+ get provider() {
1738
+ return [];
1739
+ },
1740
+ get path() {
1741
+ const dir = locationRef()?.directory ?? process.cwd();
1742
+ return { config: dir, directory: dir, worktree: dir };
1743
+ }
1744
+ };
1745
+ const dialog = context.ui.dialog;
1746
+ let nestedOnClose;
1747
+ const settleNested = (dismissed) => {
1748
+ const onClose = nestedOnClose;
1749
+ nestedOnClose = undefined;
1750
+ if (dismissed)
1751
+ onClose?.();
1752
+ };
1753
+ const api = {
1754
+ hostVersion: 2,
1755
+ app: { version: context.app?.version },
1756
+ client: withLocationQualifiedSessions(context.client, wireLoc),
1757
+ kv,
1758
+ lifecycle: {
1759
+ onDispose(fn) {
1760
+ disposeFns.push(fn);
1761
+ }
1762
+ },
1763
+ mode: {
1764
+ push(mode) {
1765
+ try {
1766
+ return context.keymap.mode.push(mode);
1767
+ } catch {
1768
+ return () => {
1769
+ return;
1770
+ };
1771
+ }
1772
+ }
1773
+ },
1774
+ renderer: {
1775
+ get root() {
1776
+ return context.renderer?.root;
1777
+ }
1778
+ },
1779
+ state,
1780
+ theme: buildV2Theme(context),
1781
+ ui: {
1782
+ toast: (input) => {
1783
+ try {
1784
+ context.ui.toast.show({ title: input.title, message: input.message, variant: input.variant, duration: input.duration });
1785
+ } catch {}
1786
+ },
1787
+ dialog: {
1788
+ replace(renderer, onClose) {
1789
+ nestedOnClose = undefined;
1790
+ let content;
1791
+ try {
1792
+ content = typeof renderer === "function" ? renderer() : renderer;
1793
+ } catch {
1794
+ content = undefined;
1795
+ }
1796
+ if (content === undefined || content === null) {
1797
+ nestedOnClose = onClose;
1798
+ return;
1799
+ }
1800
+ const element = content;
1801
+ dialog.show(() => element, onClose);
1802
+ },
1803
+ clear() {
1804
+ nestedOnClose = undefined;
1805
+ dialog.clear();
1806
+ },
1807
+ setSize(size) {
1808
+ try {
1809
+ dialog.set({ size });
1810
+ } catch {}
1811
+ }
1812
+ },
1813
+ DialogSelect(props) {
1814
+ dialog.select({
1815
+ title: props.title,
1816
+ placeholder: props.placeholder,
1817
+ current: props.current,
1818
+ options: props.options.map((option) => ({
1819
+ title: option.title,
1820
+ value: option.value,
1821
+ description: option.description,
1822
+ category: option.category,
1823
+ disabled: option.disabled
1824
+ }))
1825
+ }).then((value) => {
1826
+ if (value === undefined) {
1827
+ settleNested(true);
1828
+ return;
1829
+ }
1830
+ settleNested(false);
1831
+ const option = props.options.find((item) => item.value === value);
1832
+ if (option)
1833
+ props.onSelect(option);
1834
+ }).catch(() => settleNested(true));
1835
+ },
1836
+ DialogPrompt(props) {
1837
+ dialog.prompt({ title: props.title, placeholder: props.placeholder, value: props.value }).then((value) => {
1838
+ settleNested(value === undefined);
1839
+ if (value === undefined)
1840
+ props.onCancel?.();
1841
+ else
1842
+ props.onConfirm(value);
1843
+ }).catch(() => {
1844
+ settleNested(true);
1845
+ props.onCancel?.();
1846
+ });
1847
+ },
1848
+ DialogConfirm(props) {
1849
+ dialog.confirm({ title: props.title, message: props.message, label: { confirm: props.confirmLabel } }).then((result) => {
1850
+ settleNested(result !== true);
1851
+ if (result === true)
1852
+ props.onConfirm();
1853
+ else
1854
+ props.onCancel?.();
1855
+ }).catch(() => {
1856
+ settleNested(true);
1857
+ props.onCancel?.();
1858
+ });
1859
+ },
1860
+ DialogAlert(props) {
1861
+ dialog.alert({ title: props.title, message: props.message }).then(() => {
1862
+ settleNested(false);
1863
+ props.onConfirm?.();
1864
+ }).catch(() => {
1865
+ settleNested(true);
1866
+ props.onConfirm?.();
1867
+ });
1868
+ }
1869
+ },
1870
+ keymap: {
1871
+ registerLayer(layer) {
1872
+ const [active, setActive] = createSignal2(true);
1873
+ try {
1874
+ context.keymap.layer(() => active() ? convertKeymapLayer(layer) : { mode: "global", enabled: false, commands: [], bindings: [] });
1875
+ } catch {
1876
+ return () => {
1877
+ return;
1878
+ };
1879
+ }
1880
+ return () => {
1881
+ setActive(false);
1882
+ };
1883
+ }
1884
+ }
1885
+ };
1886
+ return api;
1887
+ }
1888
+ function createV2TuiSetup() {
1889
+ return async (context) => {
1890
+ const api = buildV2HostApi(context);
1891
+ const command = {
1892
+ id: "subagent-explorer.open",
1893
+ title: "Subagent Explorer: Open",
1894
+ description: "Explore and delete subagent sessions",
1895
+ category: "",
1896
+ group: "",
1897
+ palette: true,
1898
+ slash: { name: "subagent-explorer" },
1899
+ run: () => void mainMenu(api)
1900
+ };
1901
+ command.category = command.group = declarePaletteCategory("Subagent Explorer", command);
1902
+ schedulePaletteReconcile();
1903
+ const [paletteActive, setPaletteActive] = createSignal2(true);
1904
+ try {
1905
+ context.keymap.layer(() => paletteActive() ? { mode: "global", commands: [command] } : { mode: "global", enabled: false, commands: [] });
1906
+ } catch {}
1907
+ return () => {
1908
+ setPaletteActive(false);
1909
+ };
1910
+ };
1911
+ }
1912
+
1913
+ // src/tui.tsx
1914
+ function registerExplorerCommand(api, run) {
1915
+ const command = {
1916
+ namespace: "palette",
1917
+ name: "subagent-explorer.open",
1918
+ title: "Subagent Explorer: Open",
1919
+ desc: "Explore and delete subagent sessions",
1920
+ category: "",
1921
+ slashName: "subagent-explorer",
1922
+ run
1923
+ };
1924
+ command.category = declarePaletteCategory("Subagent Explorer", command);
1925
+ schedulePaletteReconcile();
1926
+ const apiWithKeymap = api;
1927
+ if (typeof apiWithKeymap.keymap?.registerLayer === "function") {
1928
+ return apiWithKeymap.keymap.registerLayer({
1929
+ commands: [command],
1930
+ bindings: []
1931
+ });
1932
+ }
1933
+ return api.command?.register(() => [{
1934
+ title: "Subagent Explorer: Open",
1935
+ value: "subagent-explorer.open",
1936
+ description: "Explore and delete subagent sessions",
1937
+ category: currentPaletteCategory(),
1938
+ slash: {
1939
+ name: "subagent-explorer"
1940
+ },
1941
+ onSelect: run
1942
+ }]);
1943
+ }
1944
+ var tui = async (api) => {
1945
+ const unregister = registerExplorerCommand(api, async () => {
1946
+ const host = api;
1947
+ host.currentSessionID = () => host.route?.current?.name === "session" ? host.route.current.params?.sessionID : undefined;
1948
+ host.dialogScope = "standalone";
1949
+ await mainMenu(host);
1950
+ });
1951
+ api.lifecycle.onDispose(() => {
1952
+ unregister?.();
1953
+ });
1954
+ };
1955
+ var tui_default = {
1956
+ id: "subagent-explorer",
1957
+ tui,
1958
+ setup: createV2TuiSetup()
1959
+ };
1960
+ export {
1961
+ tui_default as default
1962
+ };