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