pantheon-opencode 1.1.1-beta.10 → 1.1.1-beta.11
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/package.json +1 -1
- package/src/plugins/tui/dist/tui.d.ts.map +1 -1
- package/src/plugins/tui/dist/tui.js +187 -141
- package/src/plugins/tui/dist/tui.tsx +120 -45
- package/src/plugins/tui/src/index.tsx +120 -45
package/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tui.d.ts","names":[],"sources":["../src/index.tsx"],"mappings":";;
|
|
1
|
+
{"version":3,"file":"tui.d.ts","names":[],"sources":["../src/index.tsx"],"mappings":";;cAsbM,QAAQ;EAAoB"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createComponent, createElement, createTextNode, effect, insert, insertNode, memo, setProp } from "@opentui/solid";
|
|
2
|
-
import { For, Show, createMemo, createResource, createSignal } from "solid-js";
|
|
2
|
+
import { For, Show, createMemo, createResource, createSignal, onCleanup, onMount } from "solid-js";
|
|
3
3
|
//#region src/index.tsx
|
|
4
4
|
/** @jsxImportSource @opentui/solid */
|
|
5
5
|
const BAR_WIDTH = 20;
|
|
@@ -172,6 +172,18 @@ async function detectVersion(api) {
|
|
|
172
172
|
if (tag && tag !== "5.0.0") return tag;
|
|
173
173
|
}
|
|
174
174
|
} catch {}
|
|
175
|
+
try {
|
|
176
|
+
const proc = api.client?.process;
|
|
177
|
+
if (typeof proc?.exec === "function") {
|
|
178
|
+
const r = await proc.exec({
|
|
179
|
+
command: "opencode",
|
|
180
|
+
args: ["--version"],
|
|
181
|
+
timeoutMs: 3e3
|
|
182
|
+
});
|
|
183
|
+
const ver = (r.stdout ?? r.output ?? "").trim().replace(/^v/, "");
|
|
184
|
+
if (ver) return ver;
|
|
185
|
+
}
|
|
186
|
+
} catch {}
|
|
175
187
|
return null;
|
|
176
188
|
}
|
|
177
189
|
function ContextBar(props) {
|
|
@@ -244,29 +256,28 @@ function ContextBar(props) {
|
|
|
244
256
|
}
|
|
245
257
|
});
|
|
246
258
|
}
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
"session",
|
|
255
|
-
"list",
|
|
256
|
-
"--format",
|
|
257
|
-
"json",
|
|
258
|
-
"--max-count",
|
|
259
|
-
"20"
|
|
260
|
-
],
|
|
261
|
-
timeoutMs: 5e3
|
|
262
|
-
});
|
|
263
|
-
const stdout = result.stdout ?? result.output ?? "";
|
|
264
|
-
const sessions = JSON.parse(stdout);
|
|
265
|
-
if (!Array.isArray(sessions)) return null;
|
|
266
|
-
return sessions.filter((s) => s.id !== sessionID).sort((a, b) => (b.updated ?? 0) - (a.updated ?? 0)).slice(0, 8);
|
|
267
|
-
} catch {
|
|
259
|
+
function SessionRow(props) {
|
|
260
|
+
const theme = () => props.api.theme.current;
|
|
261
|
+
const status = createMemo(() => {
|
|
262
|
+
try {
|
|
263
|
+
const s = props.api.state.session?.status?.(props.session.id);
|
|
264
|
+
if (s?.type) return s.type;
|
|
265
|
+
} catch {}
|
|
268
266
|
return null;
|
|
269
|
-
}
|
|
267
|
+
});
|
|
268
|
+
const statusIcon = createMemo(() => {
|
|
269
|
+
switch (status()) {
|
|
270
|
+
case "busy": return "● ";
|
|
271
|
+
case "retry": return "⚠ ";
|
|
272
|
+
default: return "";
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
return (() => {
|
|
276
|
+
var _el$8 = createElement("text");
|
|
277
|
+
insert(_el$8, () => `${statusIcon()}${props.session?.id?.slice(0, 8) ?? "????"}... ${props.session?.title?.slice(0, 26) ?? "(untitled)"}`);
|
|
278
|
+
effect((_$p) => setProp(_el$8, "fg", theme().textMuted, _$p));
|
|
279
|
+
return _el$8;
|
|
280
|
+
})();
|
|
270
281
|
}
|
|
271
282
|
function View(props) {
|
|
272
283
|
const [showCommands, setShowCommands] = createSignal(false);
|
|
@@ -275,16 +286,47 @@ function View(props) {
|
|
|
275
286
|
const [showSessions, setShowSessions] = createSignal(false);
|
|
276
287
|
const theme = () => props.api.theme.current;
|
|
277
288
|
const branch = createMemo(() => props.api.state.vcs?.branch ? `\u2387 ${props.api.state.vcs.branch}` : null);
|
|
278
|
-
const
|
|
289
|
+
const [sessionList, { refetch: refetchSessions }] = createResource(async () => {
|
|
279
290
|
try {
|
|
280
|
-
return props.api.
|
|
291
|
+
return await props.api.client?.session?.list?.({ limit: 100 }) ?? { data: [] };
|
|
281
292
|
} catch {
|
|
282
|
-
return
|
|
293
|
+
return { data: [] };
|
|
283
294
|
}
|
|
284
295
|
});
|
|
285
|
-
const
|
|
286
|
-
const
|
|
287
|
-
|
|
296
|
+
const totalSessions = createMemo(() => {
|
|
297
|
+
const result = sessionList();
|
|
298
|
+
if (!result) return 0;
|
|
299
|
+
const data = result.data ?? result;
|
|
300
|
+
if (!Array.isArray(data)) return 0;
|
|
301
|
+
return data.filter((s) => !s.parentID).length;
|
|
302
|
+
});
|
|
303
|
+
const recentSessions = createMemo(() => {
|
|
304
|
+
const result = sessionList();
|
|
305
|
+
if (!result) return [];
|
|
306
|
+
const data = result.data ?? result;
|
|
307
|
+
if (!Array.isArray(data)) return [];
|
|
308
|
+
return data.filter((s) => !s.parentID && s.id !== props.sessionID).sort((a, b) => {
|
|
309
|
+
const ta = a.time?.updated ?? a.updated ?? 0;
|
|
310
|
+
return (b.time?.updated ?? b.updated ?? 0) - ta;
|
|
311
|
+
}).slice(0, 8);
|
|
312
|
+
});
|
|
313
|
+
const [memoryCount, setMemoryCount] = createSignal(null);
|
|
314
|
+
onMount(() => {
|
|
315
|
+
const cleanup = [];
|
|
316
|
+
try {
|
|
317
|
+
cleanup.push(props.api.event.on("session.status", refetchSessions));
|
|
318
|
+
cleanup.push(props.api.event.on("session.created", refetchSessions));
|
|
319
|
+
cleanup.push(props.api.event.on("session.updated", refetchSessions));
|
|
320
|
+
cleanup.push(props.api.event.on("session.deleted", refetchSessions));
|
|
321
|
+
} catch {}
|
|
322
|
+
try {
|
|
323
|
+
const ev = props.api.event;
|
|
324
|
+
if (typeof ev?.on === "function") {
|
|
325
|
+
cleanup.push(ev.on("memory.created", () => setMemoryCount((c) => c !== null ? c + 1 : 1)));
|
|
326
|
+
cleanup.push(ev.on("memory.deleted", () => setMemoryCount((c) => c !== null ? Math.max(0, c - 1) : 0)));
|
|
327
|
+
}
|
|
328
|
+
} catch {}
|
|
329
|
+
onCleanup(() => cleanup.forEach((fn) => fn()));
|
|
288
330
|
});
|
|
289
331
|
const configSummary = createMemo(() => {
|
|
290
332
|
const cfg = props.api.state.config;
|
|
@@ -295,104 +337,105 @@ function View(props) {
|
|
|
295
337
|
autoCompaction: cfg.compaction?.auto === true
|
|
296
338
|
};
|
|
297
339
|
});
|
|
298
|
-
const [sessions] = createResource(() => showSessions(), () => fetchRecentSessions(props.api, props.sessionID));
|
|
299
340
|
const HR = () => (() => {
|
|
300
|
-
var _el$
|
|
301
|
-
insertNode(_el$
|
|
302
|
-
effect((_$p) => setProp(_el$
|
|
303
|
-
return _el$
|
|
341
|
+
var _el$9 = createElement("text");
|
|
342
|
+
insertNode(_el$9, createTextNode(`────────────────────────────`));
|
|
343
|
+
effect((_$p) => setProp(_el$9, "fg", theme().textMuted, _$p));
|
|
344
|
+
return _el$9;
|
|
304
345
|
})();
|
|
305
346
|
return (() => {
|
|
306
|
-
var _el$
|
|
307
|
-
insertNode(_el$
|
|
308
|
-
insertNode(_el$
|
|
309
|
-
insertNode(_el$
|
|
310
|
-
insertNode(_el$
|
|
311
|
-
insertNode(_el$
|
|
312
|
-
insertNode(_el$
|
|
313
|
-
setProp(_el$
|
|
314
|
-
setProp(_el$
|
|
315
|
-
setProp(_el$
|
|
316
|
-
insert(_el$
|
|
317
|
-
insert(_el$
|
|
347
|
+
var _el$1 = createElement("box"), _el$10 = createElement("text"), _el$11 = createElement("box"), _el$12 = createElement("text"), _el$13 = createElement("text"), _el$15 = createElement("box"), _el$16 = createElement("text"), _el$17 = createElement("text"), _el$19 = createElement("box"), _el$20 = createElement("text"), _el$21 = createElement("text"), _el$23 = createElement("box"), _el$24 = createElement("text"), _el$25 = createElement("box"), _el$26 = createElement("text");
|
|
348
|
+
insertNode(_el$1, _el$10);
|
|
349
|
+
insertNode(_el$1, _el$11);
|
|
350
|
+
insertNode(_el$1, _el$15);
|
|
351
|
+
insertNode(_el$1, _el$19);
|
|
352
|
+
insertNode(_el$1, _el$23);
|
|
353
|
+
insertNode(_el$1, _el$25);
|
|
354
|
+
setProp(_el$1, "flexDirection", "column");
|
|
355
|
+
setProp(_el$1, "width", "100%");
|
|
356
|
+
setProp(_el$10, "attributes", { bold: true });
|
|
357
|
+
insert(_el$10, () => `Pantheon${props.version ? ` v${props.version}` : ""}`);
|
|
358
|
+
insert(_el$1, createComponent(Show, {
|
|
318
359
|
get when() {
|
|
319
360
|
return branch();
|
|
320
361
|
},
|
|
321
362
|
children: (b) => (() => {
|
|
322
|
-
var _el$
|
|
323
|
-
insert(_el$
|
|
324
|
-
effect((_$p) => setProp(_el$
|
|
325
|
-
return _el$
|
|
363
|
+
var _el$27 = createElement("text");
|
|
364
|
+
insert(_el$27, b);
|
|
365
|
+
effect((_$p) => setProp(_el$27, "fg", theme().textMuted, _$p));
|
|
366
|
+
return _el$27;
|
|
326
367
|
})()
|
|
327
|
-
}), _el$
|
|
328
|
-
insert(_el$
|
|
329
|
-
insert(_el$
|
|
368
|
+
}), _el$11);
|
|
369
|
+
insert(_el$1, createComponent(HR, {}), _el$11);
|
|
370
|
+
insert(_el$1, createComponent(ContextBar, {
|
|
330
371
|
get api() {
|
|
331
372
|
return props.api;
|
|
332
373
|
},
|
|
333
374
|
get sessionID() {
|
|
334
375
|
return props.sessionID;
|
|
335
376
|
}
|
|
336
|
-
}), _el$
|
|
337
|
-
insert(_el$
|
|
338
|
-
insertNode(_el$
|
|
339
|
-
insertNode(_el$
|
|
340
|
-
setProp(_el$
|
|
341
|
-
setProp(_el$
|
|
342
|
-
insert(_el$
|
|
343
|
-
insert(_el$
|
|
344
|
-
insert(_el$
|
|
377
|
+
}), _el$11);
|
|
378
|
+
insert(_el$1, createComponent(HR, {}), _el$11);
|
|
379
|
+
insertNode(_el$11, _el$12);
|
|
380
|
+
insertNode(_el$11, _el$13);
|
|
381
|
+
setProp(_el$11, "onMouseDown", () => setShowSessions((x) => !x));
|
|
382
|
+
setProp(_el$12, "attributes", { bold: true });
|
|
383
|
+
insert(_el$12, () => `${showSessions() ? "▼" : "▶"} Sessions`);
|
|
384
|
+
insert(_el$13, () => ` (${String(totalSessions())})`);
|
|
385
|
+
insert(_el$1, createComponent(Show, {
|
|
345
386
|
get when() {
|
|
346
|
-
return
|
|
387
|
+
return showSessions();
|
|
347
388
|
},
|
|
348
|
-
children
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
insert(_el$
|
|
389
|
+
get children() {
|
|
390
|
+
return createComponent(Show, {
|
|
391
|
+
get when() {
|
|
392
|
+
return recentSessions().length > 0;
|
|
393
|
+
},
|
|
394
|
+
get fallback() {
|
|
395
|
+
return (() => {
|
|
396
|
+
var _el$28 = createElement("box"), _el$29 = createElement("text");
|
|
397
|
+
insertNode(_el$28, _el$29);
|
|
398
|
+
setProp(_el$28, "marginLeft", 1);
|
|
399
|
+
insertNode(_el$29, createTextNode(`No recent sessions`));
|
|
400
|
+
effect((_$p) => setProp(_el$29, "fg", theme().textMuted, _$p));
|
|
401
|
+
return _el$28;
|
|
402
|
+
})();
|
|
403
|
+
},
|
|
404
|
+
get children() {
|
|
405
|
+
var _el$14 = createElement("box");
|
|
406
|
+
setProp(_el$14, "marginLeft", 1);
|
|
407
|
+
setProp(_el$14, "flexDirection", "column");
|
|
408
|
+
insert(_el$14, createComponent(For, {
|
|
409
|
+
get each() {
|
|
410
|
+
return recentSessions();
|
|
411
|
+
},
|
|
412
|
+
children: (ses) => createComponent(SessionRow, {
|
|
413
|
+
get api() {
|
|
414
|
+
return props.api;
|
|
415
|
+
},
|
|
416
|
+
session: ses
|
|
417
|
+
})
|
|
418
|
+
}));
|
|
419
|
+
return _el$14;
|
|
420
|
+
}
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
}), _el$15);
|
|
424
|
+
insertNode(_el$15, _el$16);
|
|
425
|
+
insertNode(_el$15, _el$17);
|
|
426
|
+
setProp(_el$15, "onMouseDown", () => setShowCommands((x) => !x));
|
|
427
|
+
setProp(_el$16, "attributes", { bold: true });
|
|
428
|
+
insert(_el$16, () => `${showCommands() ? "▼" : "▶"} Commands`);
|
|
429
|
+
insert(_el$17, () => ` (${String(COMMANDS.length)})`);
|
|
430
|
+
insert(_el$1, createComponent(Show, {
|
|
388
431
|
get when() {
|
|
389
432
|
return showCommands();
|
|
390
433
|
},
|
|
391
434
|
get children() {
|
|
392
|
-
var _el$
|
|
393
|
-
setProp(_el$
|
|
394
|
-
setProp(_el$
|
|
395
|
-
insert(_el$
|
|
435
|
+
var _el$18 = createElement("box");
|
|
436
|
+
setProp(_el$18, "marginLeft", 1);
|
|
437
|
+
setProp(_el$18, "flexDirection", "column");
|
|
438
|
+
insert(_el$18, createComponent(For, {
|
|
396
439
|
each: COMMANDS,
|
|
397
440
|
children: (cmd) => (() => {
|
|
398
441
|
var _el$31 = createElement("box"), _el$32 = createElement("text"), _el$33 = createElement("text");
|
|
@@ -424,24 +467,24 @@ function View(props) {
|
|
|
424
467
|
return _el$31;
|
|
425
468
|
})()
|
|
426
469
|
}));
|
|
427
|
-
return _el$
|
|
470
|
+
return _el$18;
|
|
428
471
|
}
|
|
429
|
-
}), _el$
|
|
430
|
-
insertNode(_el$
|
|
431
|
-
insertNode(_el$
|
|
432
|
-
setProp(_el$
|
|
433
|
-
setProp(_el$
|
|
434
|
-
insert(_el$
|
|
435
|
-
insert(_el$
|
|
436
|
-
insert(_el$
|
|
472
|
+
}), _el$19);
|
|
473
|
+
insertNode(_el$19, _el$20);
|
|
474
|
+
insertNode(_el$19, _el$21);
|
|
475
|
+
setProp(_el$19, "onMouseDown", () => setShowAgents((x) => !x));
|
|
476
|
+
setProp(_el$20, "attributes", { bold: true });
|
|
477
|
+
insert(_el$20, () => `${showAgents() ? "▼" : "▶"} Agents`);
|
|
478
|
+
insert(_el$21, () => ` (${String(AGENTS.length)})`);
|
|
479
|
+
insert(_el$1, createComponent(Show, {
|
|
437
480
|
get when() {
|
|
438
481
|
return showAgents();
|
|
439
482
|
},
|
|
440
483
|
get children() {
|
|
441
|
-
var _el$
|
|
442
|
-
setProp(_el$
|
|
443
|
-
setProp(_el$
|
|
444
|
-
insert(_el$
|
|
484
|
+
var _el$22 = createElement("box");
|
|
485
|
+
setProp(_el$22, "marginLeft", 1);
|
|
486
|
+
setProp(_el$22, "flexDirection", "column");
|
|
487
|
+
insert(_el$22, createComponent(For, {
|
|
445
488
|
each: AGENTS,
|
|
446
489
|
children: (agent) => (() => {
|
|
447
490
|
var _el$34 = createElement("box"), _el$35 = createElement("text"), _el$36 = createElement("text");
|
|
@@ -461,14 +504,14 @@ function View(props) {
|
|
|
461
504
|
return _el$34;
|
|
462
505
|
})()
|
|
463
506
|
}));
|
|
464
|
-
return _el$
|
|
507
|
+
return _el$22;
|
|
465
508
|
}
|
|
466
|
-
}), _el$
|
|
467
|
-
insertNode(_el$
|
|
468
|
-
setProp(_el$
|
|
469
|
-
setProp(_el$
|
|
470
|
-
insert(_el$
|
|
471
|
-
insert(_el$
|
|
509
|
+
}), _el$23);
|
|
510
|
+
insertNode(_el$23, _el$24);
|
|
511
|
+
setProp(_el$23, "onMouseDown", () => setShowConfig((x) => !x));
|
|
512
|
+
setProp(_el$24, "attributes", { bold: true });
|
|
513
|
+
insert(_el$24, () => `${showConfig() ? "▼" : "▶"} Config`);
|
|
514
|
+
insert(_el$1, createComponent(Show, {
|
|
472
515
|
get when() {
|
|
473
516
|
return showConfig();
|
|
474
517
|
},
|
|
@@ -508,21 +551,24 @@ function View(props) {
|
|
|
508
551
|
})()
|
|
509
552
|
});
|
|
510
553
|
}
|
|
511
|
-
}), _el$
|
|
512
|
-
insertNode(_el$
|
|
513
|
-
setProp(_el$
|
|
514
|
-
insert(_el$
|
|
554
|
+
}), _el$25);
|
|
555
|
+
insertNode(_el$25, _el$26);
|
|
556
|
+
setProp(_el$25, "marginTop", 1);
|
|
557
|
+
insert(_el$26, (() => {
|
|
558
|
+
var _c$ = memo(() => memoryCount() !== null);
|
|
559
|
+
return () => _c$() ? `Memory: ${fmtInt(memoryCount())} entries` : "Memory: N/A";
|
|
560
|
+
})());
|
|
515
561
|
effect((_p$) => {
|
|
516
562
|
var _v$3 = theme().accent, _v$4 = theme().text, _v$5 = theme().textMuted, _v$6 = theme().text, _v$7 = theme().textMuted, _v$8 = theme().text, _v$9 = theme().textMuted, _v$0 = theme().text, _v$1 = theme().textMuted;
|
|
517
|
-
_v$3 !== _p$.e && (_p$.e = setProp(_el$
|
|
518
|
-
_v$4 !== _p$.t && (_p$.t = setProp(_el$
|
|
519
|
-
_v$5 !== _p$.a && (_p$.a = setProp(_el$
|
|
520
|
-
_v$6 !== _p$.o && (_p$.o = setProp(_el$
|
|
521
|
-
_v$7 !== _p$.i && (_p$.i = setProp(_el$
|
|
522
|
-
_v$8 !== _p$.n && (_p$.n = setProp(_el$
|
|
523
|
-
_v$9 !== _p$.s && (_p$.s = setProp(_el$
|
|
524
|
-
_v$0 !== _p$.h && (_p$.h = setProp(_el$
|
|
525
|
-
_v$1 !== _p$.r && (_p$.r = setProp(_el$
|
|
563
|
+
_v$3 !== _p$.e && (_p$.e = setProp(_el$10, "fg", _v$3, _p$.e));
|
|
564
|
+
_v$4 !== _p$.t && (_p$.t = setProp(_el$12, "fg", _v$4, _p$.t));
|
|
565
|
+
_v$5 !== _p$.a && (_p$.a = setProp(_el$13, "fg", _v$5, _p$.a));
|
|
566
|
+
_v$6 !== _p$.o && (_p$.o = setProp(_el$16, "fg", _v$6, _p$.o));
|
|
567
|
+
_v$7 !== _p$.i && (_p$.i = setProp(_el$17, "fg", _v$7, _p$.i));
|
|
568
|
+
_v$8 !== _p$.n && (_p$.n = setProp(_el$20, "fg", _v$8, _p$.n));
|
|
569
|
+
_v$9 !== _p$.s && (_p$.s = setProp(_el$21, "fg", _v$9, _p$.s));
|
|
570
|
+
_v$0 !== _p$.h && (_p$.h = setProp(_el$24, "fg", _v$0, _p$.h));
|
|
571
|
+
_v$1 !== _p$.r && (_p$.r = setProp(_el$26, "fg", _v$1, _p$.r));
|
|
526
572
|
return _p$;
|
|
527
573
|
}, {
|
|
528
574
|
e: void 0,
|
|
@@ -535,7 +581,7 @@ function View(props) {
|
|
|
535
581
|
h: void 0,
|
|
536
582
|
r: void 0
|
|
537
583
|
});
|
|
538
|
-
return _el$
|
|
584
|
+
return _el$1;
|
|
539
585
|
})();
|
|
540
586
|
}
|
|
541
587
|
const tui = async (api, _options, _meta) => {
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
/** @jsxImportSource @opentui/solid */
|
|
4
4
|
|
|
5
5
|
import type { TuiPlugin, TuiPluginApi, TuiPluginModule } from '@opencode-ai/plugin/tui'
|
|
6
|
-
import { createMemo, createResource, createSignal, For, Show } from 'solid-js'
|
|
6
|
+
import { createMemo, createResource, createSignal, For, onCleanup, onMount, Show } from 'solid-js'
|
|
7
7
|
|
|
8
8
|
/* ─── Constants ─────────────────────────────────────────── */
|
|
9
9
|
|
|
@@ -77,7 +77,13 @@ function buildBar(pct: number): string {
|
|
|
77
77
|
|
|
78
78
|
/* ─── Version Detection ────────────────────────────────────
|
|
79
79
|
* 100% dinâmico — zero fallback hardcoded.
|
|
80
|
-
* Se falhar, retorna null e a View omite a versão.
|
|
80
|
+
* Se falhar, retorna null e a View omite a versão.
|
|
81
|
+
*
|
|
82
|
+
* Try order:
|
|
83
|
+
* 1. api.client.file.read package.json
|
|
84
|
+
* 2. git describe --tags
|
|
85
|
+
* 3. opencode --version
|
|
86
|
+
* 4. null */
|
|
81
87
|
|
|
82
88
|
async function detectVersion(api: TuiPluginApi): Promise<string | null> {
|
|
83
89
|
// Try 1: package.json do projeto (via worktree path)
|
|
@@ -100,6 +106,17 @@ async function detectVersion(api: TuiPluginApi): Promise<string | null> {
|
|
|
100
106
|
}
|
|
101
107
|
} catch { /* fall through */ }
|
|
102
108
|
|
|
109
|
+
// Try 3: opencode --version
|
|
110
|
+
try {
|
|
111
|
+
const proc = (api as any).client?.process
|
|
112
|
+
if (typeof proc?.exec === 'function') {
|
|
113
|
+
const r = await proc.exec({ command: 'opencode', args: ['--version'], timeoutMs: 3000 })
|
|
114
|
+
const stdout = (r.stdout ?? r.output ?? '') as string
|
|
115
|
+
const ver = stdout.trim().replace(/^v/, '')
|
|
116
|
+
if (ver) return ver
|
|
117
|
+
}
|
|
118
|
+
} catch { /* fall through */ }
|
|
119
|
+
|
|
103
120
|
return null // sem fallback — versão não aparece
|
|
104
121
|
}
|
|
105
122
|
|
|
@@ -165,25 +182,32 @@ function ContextBar(props: { api: TuiPluginApi; sessionID: string }) {
|
|
|
165
182
|
)
|
|
166
183
|
}
|
|
167
184
|
|
|
168
|
-
/* ───
|
|
185
|
+
/* ─── Session Row (single session with live status indicator) ─ */
|
|
169
186
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
+
function SessionRow(props: { api: TuiPluginApi; session: any }) {
|
|
188
|
+
const theme = () => props.api.theme.current
|
|
189
|
+
|
|
190
|
+
const status = createMemo(() => {
|
|
191
|
+
try {
|
|
192
|
+
const s = (props.api.state as any).session?.status?.(props.session.id)
|
|
193
|
+
if (s?.type) return s.type as string
|
|
194
|
+
} catch { /* ignore */ }
|
|
195
|
+
return null
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
const statusIcon = createMemo(() => {
|
|
199
|
+
switch (status()) {
|
|
200
|
+
case 'busy': return '\u25cf ' // ● — running
|
|
201
|
+
case 'retry': return '\u26a0 ' // ⚠ — needs attention
|
|
202
|
+
default: return '' // idle/unknown — no icon
|
|
203
|
+
}
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
return (
|
|
207
|
+
<text fg={theme().textMuted}>
|
|
208
|
+
{`${statusIcon()}${props.session?.id?.slice(0, 8) ?? '????'}... ${props.session?.title?.slice(0, 26) ?? '(untitled)'}`}
|
|
209
|
+
</text>
|
|
210
|
+
)
|
|
187
211
|
}
|
|
188
212
|
|
|
189
213
|
/* ─── Main Sidebar View ──────────────────────────────────── */
|
|
@@ -200,15 +224,73 @@ function View(props: { api: TuiPluginApi; sessionID: string; version: string | n
|
|
|
200
224
|
props.api.state.vcs?.branch ? `\u2387 ${props.api.state.vcs.branch}` : null,
|
|
201
225
|
)
|
|
202
226
|
|
|
227
|
+
// ── Sessions: fetch via api.client.session.list (not proc.exec) ──
|
|
228
|
+
const [sessionList, { refetch: refetchSessions }] = createResource(async () => {
|
|
229
|
+
try {
|
|
230
|
+
const result = await (props.api.client as any)?.session?.list?.({ limit: 100 })
|
|
231
|
+
return result ?? { data: [] }
|
|
232
|
+
} catch {
|
|
233
|
+
return { data: [] }
|
|
234
|
+
}
|
|
235
|
+
})
|
|
236
|
+
|
|
237
|
+
// Total top-level sessions (excludes subagent sessions)
|
|
203
238
|
const totalSessions = createMemo(() => {
|
|
204
|
-
|
|
239
|
+
const result = sessionList()
|
|
240
|
+
if (!result) return 0
|
|
241
|
+
const data: any[] = (result as any).data ?? result
|
|
242
|
+
if (!Array.isArray(data)) return 0
|
|
243
|
+
return data.filter((s: any) => !s.parentID).length
|
|
205
244
|
})
|
|
206
245
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
246
|
+
// Recent top-level sessions, sorted by time.updated descending
|
|
247
|
+
const recentSessions = createMemo(() => {
|
|
248
|
+
const result = sessionList()
|
|
249
|
+
if (!result) return []
|
|
250
|
+
const data: any[] = (result as any).data ?? result
|
|
251
|
+
if (!Array.isArray(data)) return []
|
|
252
|
+
return data
|
|
253
|
+
.filter((s: any) => !s.parentID && s.id !== props.sessionID)
|
|
254
|
+
.sort((a: any, b: any) => {
|
|
255
|
+
const ta = a.time?.updated ?? a.updated ?? 0
|
|
256
|
+
const tb = b.time?.updated ?? b.updated ?? 0
|
|
257
|
+
return tb - ta
|
|
258
|
+
})
|
|
259
|
+
.slice(0, 8)
|
|
210
260
|
})
|
|
211
261
|
|
|
262
|
+
// ── Memory: starts at null (= "N/A"), tracks via events ──
|
|
263
|
+
const [memoryCount, setMemoryCount] = createSignal<number | null>(null)
|
|
264
|
+
|
|
265
|
+
// ── Event subscriptions for live session/memory updates ──
|
|
266
|
+
onMount(() => {
|
|
267
|
+
const cleanup: (() => void)[] = []
|
|
268
|
+
|
|
269
|
+
// Session events — triggers resource refetch
|
|
270
|
+
try {
|
|
271
|
+
cleanup.push(props.api.event.on('session.status', refetchSessions))
|
|
272
|
+
cleanup.push(props.api.event.on('session.created', refetchSessions))
|
|
273
|
+
cleanup.push(props.api.event.on('session.updated', refetchSessions))
|
|
274
|
+
cleanup.push(props.api.event.on('session.deleted', refetchSessions))
|
|
275
|
+
} catch { /* events API not available in this runtime */ }
|
|
276
|
+
|
|
277
|
+
// Memory events — tracks running counter (no api.state.memory)
|
|
278
|
+
try {
|
|
279
|
+
const ev = (props.api.event as any)
|
|
280
|
+
if (typeof ev?.on === 'function') {
|
|
281
|
+
cleanup.push(
|
|
282
|
+
ev.on('memory.created', () => setMemoryCount((c) => (c !== null ? c + 1 : 1))),
|
|
283
|
+
)
|
|
284
|
+
cleanup.push(
|
|
285
|
+
ev.on('memory.deleted', () => setMemoryCount((c) => (c !== null ? Math.max(0, c - 1) : 0))),
|
|
286
|
+
)
|
|
287
|
+
}
|
|
288
|
+
} catch { /* memory events not available */ }
|
|
289
|
+
|
|
290
|
+
onCleanup(() => cleanup.forEach((fn) => fn()))
|
|
291
|
+
})
|
|
292
|
+
|
|
293
|
+
// ── Config summary ──
|
|
212
294
|
const configSummary = createMemo(() => {
|
|
213
295
|
const cfg = (props.api.state as any).config
|
|
214
296
|
if (!cfg) return null
|
|
@@ -219,12 +301,6 @@ function View(props: { api: TuiPluginApi; sessionID: string; version: string | n
|
|
|
219
301
|
}
|
|
220
302
|
})
|
|
221
303
|
|
|
222
|
-
// Load session list only when expanded
|
|
223
|
-
const [sessions] = createResource(
|
|
224
|
-
() => showSessions(),
|
|
225
|
-
() => fetchRecentSessions(props.api, props.sessionID),
|
|
226
|
-
)
|
|
227
|
-
|
|
228
304
|
const HR = () => <text fg={theme().textMuted}>{'\u2500'.repeat(28)}</text>
|
|
229
305
|
|
|
230
306
|
return (
|
|
@@ -251,20 +327,17 @@ function View(props: { api: TuiPluginApi; sessionID: string; version: string | n
|
|
|
251
327
|
</text>
|
|
252
328
|
<text fg={theme().textMuted}>{` (${String(totalSessions())})`}</text>
|
|
253
329
|
</box>
|
|
254
|
-
<Show when={showSessions()
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
</box>
|
|
266
|
-
</Show>
|
|
267
|
-
)}
|
|
330
|
+
<Show when={showSessions()}>
|
|
331
|
+
<Show
|
|
332
|
+
when={recentSessions().length > 0}
|
|
333
|
+
fallback={<box marginLeft={1}><text fg={theme().textMuted}>No recent sessions</text></box>}
|
|
334
|
+
>
|
|
335
|
+
<box marginLeft={1} flexDirection="column">
|
|
336
|
+
<For each={recentSessions()}>
|
|
337
|
+
{(ses: any) => <SessionRow api={props.api} session={ses} />}
|
|
338
|
+
</For>
|
|
339
|
+
</box>
|
|
340
|
+
</Show>
|
|
268
341
|
</Show>
|
|
269
342
|
|
|
270
343
|
{/* ── Commands ── */}
|
|
@@ -339,7 +412,9 @@ function View(props: { api: TuiPluginApi; sessionID: string; version: string | n
|
|
|
339
412
|
{/* ── Memory (always visible summary) ── */}
|
|
340
413
|
<box marginTop={1}>
|
|
341
414
|
<text fg={theme().textMuted}>
|
|
342
|
-
{
|
|
415
|
+
{memoryCount() !== null
|
|
416
|
+
? `Memory: ${fmtInt(memoryCount()!)} entries`
|
|
417
|
+
: 'Memory: N/A'}
|
|
343
418
|
</text>
|
|
344
419
|
</box>
|
|
345
420
|
</box>
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
/** @jsxImportSource @opentui/solid */
|
|
4
4
|
|
|
5
5
|
import type { TuiPlugin, TuiPluginApi, TuiPluginModule } from '@opencode-ai/plugin/tui'
|
|
6
|
-
import { createMemo, createResource, createSignal, For, Show } from 'solid-js'
|
|
6
|
+
import { createMemo, createResource, createSignal, For, onCleanup, onMount, Show } from 'solid-js'
|
|
7
7
|
|
|
8
8
|
/* ─── Constants ─────────────────────────────────────────── */
|
|
9
9
|
|
|
@@ -77,7 +77,13 @@ function buildBar(pct: number): string {
|
|
|
77
77
|
|
|
78
78
|
/* ─── Version Detection ────────────────────────────────────
|
|
79
79
|
* 100% dinâmico — zero fallback hardcoded.
|
|
80
|
-
* Se falhar, retorna null e a View omite a versão.
|
|
80
|
+
* Se falhar, retorna null e a View omite a versão.
|
|
81
|
+
*
|
|
82
|
+
* Try order:
|
|
83
|
+
* 1. api.client.file.read package.json
|
|
84
|
+
* 2. git describe --tags
|
|
85
|
+
* 3. opencode --version
|
|
86
|
+
* 4. null */
|
|
81
87
|
|
|
82
88
|
async function detectVersion(api: TuiPluginApi): Promise<string | null> {
|
|
83
89
|
// Try 1: package.json do projeto (via worktree path)
|
|
@@ -100,6 +106,17 @@ async function detectVersion(api: TuiPluginApi): Promise<string | null> {
|
|
|
100
106
|
}
|
|
101
107
|
} catch { /* fall through */ }
|
|
102
108
|
|
|
109
|
+
// Try 3: opencode --version
|
|
110
|
+
try {
|
|
111
|
+
const proc = (api as any).client?.process
|
|
112
|
+
if (typeof proc?.exec === 'function') {
|
|
113
|
+
const r = await proc.exec({ command: 'opencode', args: ['--version'], timeoutMs: 3000 })
|
|
114
|
+
const stdout = (r.stdout ?? r.output ?? '') as string
|
|
115
|
+
const ver = stdout.trim().replace(/^v/, '')
|
|
116
|
+
if (ver) return ver
|
|
117
|
+
}
|
|
118
|
+
} catch { /* fall through */ }
|
|
119
|
+
|
|
103
120
|
return null // sem fallback — versão não aparece
|
|
104
121
|
}
|
|
105
122
|
|
|
@@ -165,25 +182,32 @@ function ContextBar(props: { api: TuiPluginApi; sessionID: string }) {
|
|
|
165
182
|
)
|
|
166
183
|
}
|
|
167
184
|
|
|
168
|
-
/* ───
|
|
185
|
+
/* ─── Session Row (single session with live status indicator) ─ */
|
|
169
186
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
+
function SessionRow(props: { api: TuiPluginApi; session: any }) {
|
|
188
|
+
const theme = () => props.api.theme.current
|
|
189
|
+
|
|
190
|
+
const status = createMemo(() => {
|
|
191
|
+
try {
|
|
192
|
+
const s = (props.api.state as any).session?.status?.(props.session.id)
|
|
193
|
+
if (s?.type) return s.type as string
|
|
194
|
+
} catch { /* ignore */ }
|
|
195
|
+
return null
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
const statusIcon = createMemo(() => {
|
|
199
|
+
switch (status()) {
|
|
200
|
+
case 'busy': return '\u25cf ' // ● — running
|
|
201
|
+
case 'retry': return '\u26a0 ' // ⚠ — needs attention
|
|
202
|
+
default: return '' // idle/unknown — no icon
|
|
203
|
+
}
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
return (
|
|
207
|
+
<text fg={theme().textMuted}>
|
|
208
|
+
{`${statusIcon()}${props.session?.id?.slice(0, 8) ?? '????'}... ${props.session?.title?.slice(0, 26) ?? '(untitled)'}`}
|
|
209
|
+
</text>
|
|
210
|
+
)
|
|
187
211
|
}
|
|
188
212
|
|
|
189
213
|
/* ─── Main Sidebar View ──────────────────────────────────── */
|
|
@@ -200,15 +224,73 @@ function View(props: { api: TuiPluginApi; sessionID: string; version: string | n
|
|
|
200
224
|
props.api.state.vcs?.branch ? `\u2387 ${props.api.state.vcs.branch}` : null,
|
|
201
225
|
)
|
|
202
226
|
|
|
227
|
+
// ── Sessions: fetch via api.client.session.list (not proc.exec) ──
|
|
228
|
+
const [sessionList, { refetch: refetchSessions }] = createResource(async () => {
|
|
229
|
+
try {
|
|
230
|
+
const result = await (props.api.client as any)?.session?.list?.({ limit: 100 })
|
|
231
|
+
return result ?? { data: [] }
|
|
232
|
+
} catch {
|
|
233
|
+
return { data: [] }
|
|
234
|
+
}
|
|
235
|
+
})
|
|
236
|
+
|
|
237
|
+
// Total top-level sessions (excludes subagent sessions)
|
|
203
238
|
const totalSessions = createMemo(() => {
|
|
204
|
-
|
|
239
|
+
const result = sessionList()
|
|
240
|
+
if (!result) return 0
|
|
241
|
+
const data: any[] = (result as any).data ?? result
|
|
242
|
+
if (!Array.isArray(data)) return 0
|
|
243
|
+
return data.filter((s: any) => !s.parentID).length
|
|
205
244
|
})
|
|
206
245
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
246
|
+
// Recent top-level sessions, sorted by time.updated descending
|
|
247
|
+
const recentSessions = createMemo(() => {
|
|
248
|
+
const result = sessionList()
|
|
249
|
+
if (!result) return []
|
|
250
|
+
const data: any[] = (result as any).data ?? result
|
|
251
|
+
if (!Array.isArray(data)) return []
|
|
252
|
+
return data
|
|
253
|
+
.filter((s: any) => !s.parentID && s.id !== props.sessionID)
|
|
254
|
+
.sort((a: any, b: any) => {
|
|
255
|
+
const ta = a.time?.updated ?? a.updated ?? 0
|
|
256
|
+
const tb = b.time?.updated ?? b.updated ?? 0
|
|
257
|
+
return tb - ta
|
|
258
|
+
})
|
|
259
|
+
.slice(0, 8)
|
|
210
260
|
})
|
|
211
261
|
|
|
262
|
+
// ── Memory: starts at null (= "N/A"), tracks via events ──
|
|
263
|
+
const [memoryCount, setMemoryCount] = createSignal<number | null>(null)
|
|
264
|
+
|
|
265
|
+
// ── Event subscriptions for live session/memory updates ──
|
|
266
|
+
onMount(() => {
|
|
267
|
+
const cleanup: (() => void)[] = []
|
|
268
|
+
|
|
269
|
+
// Session events — triggers resource refetch
|
|
270
|
+
try {
|
|
271
|
+
cleanup.push(props.api.event.on('session.status', refetchSessions))
|
|
272
|
+
cleanup.push(props.api.event.on('session.created', refetchSessions))
|
|
273
|
+
cleanup.push(props.api.event.on('session.updated', refetchSessions))
|
|
274
|
+
cleanup.push(props.api.event.on('session.deleted', refetchSessions))
|
|
275
|
+
} catch { /* events API not available in this runtime */ }
|
|
276
|
+
|
|
277
|
+
// Memory events — tracks running counter (no api.state.memory)
|
|
278
|
+
try {
|
|
279
|
+
const ev = (props.api.event as any)
|
|
280
|
+
if (typeof ev?.on === 'function') {
|
|
281
|
+
cleanup.push(
|
|
282
|
+
ev.on('memory.created', () => setMemoryCount((c) => (c !== null ? c + 1 : 1))),
|
|
283
|
+
)
|
|
284
|
+
cleanup.push(
|
|
285
|
+
ev.on('memory.deleted', () => setMemoryCount((c) => (c !== null ? Math.max(0, c - 1) : 0))),
|
|
286
|
+
)
|
|
287
|
+
}
|
|
288
|
+
} catch { /* memory events not available */ }
|
|
289
|
+
|
|
290
|
+
onCleanup(() => cleanup.forEach((fn) => fn()))
|
|
291
|
+
})
|
|
292
|
+
|
|
293
|
+
// ── Config summary ──
|
|
212
294
|
const configSummary = createMemo(() => {
|
|
213
295
|
const cfg = (props.api.state as any).config
|
|
214
296
|
if (!cfg) return null
|
|
@@ -219,12 +301,6 @@ function View(props: { api: TuiPluginApi; sessionID: string; version: string | n
|
|
|
219
301
|
}
|
|
220
302
|
})
|
|
221
303
|
|
|
222
|
-
// Load session list only when expanded
|
|
223
|
-
const [sessions] = createResource(
|
|
224
|
-
() => showSessions(),
|
|
225
|
-
() => fetchRecentSessions(props.api, props.sessionID),
|
|
226
|
-
)
|
|
227
|
-
|
|
228
304
|
const HR = () => <text fg={theme().textMuted}>{'\u2500'.repeat(28)}</text>
|
|
229
305
|
|
|
230
306
|
return (
|
|
@@ -251,20 +327,17 @@ function View(props: { api: TuiPluginApi; sessionID: string; version: string | n
|
|
|
251
327
|
</text>
|
|
252
328
|
<text fg={theme().textMuted}>{` (${String(totalSessions())})`}</text>
|
|
253
329
|
</box>
|
|
254
|
-
<Show when={showSessions()
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
</box>
|
|
266
|
-
</Show>
|
|
267
|
-
)}
|
|
330
|
+
<Show when={showSessions()}>
|
|
331
|
+
<Show
|
|
332
|
+
when={recentSessions().length > 0}
|
|
333
|
+
fallback={<box marginLeft={1}><text fg={theme().textMuted}>No recent sessions</text></box>}
|
|
334
|
+
>
|
|
335
|
+
<box marginLeft={1} flexDirection="column">
|
|
336
|
+
<For each={recentSessions()}>
|
|
337
|
+
{(ses: any) => <SessionRow api={props.api} session={ses} />}
|
|
338
|
+
</For>
|
|
339
|
+
</box>
|
|
340
|
+
</Show>
|
|
268
341
|
</Show>
|
|
269
342
|
|
|
270
343
|
{/* ── Commands ── */}
|
|
@@ -339,7 +412,9 @@ function View(props: { api: TuiPluginApi; sessionID: string; version: string | n
|
|
|
339
412
|
{/* ── Memory (always visible summary) ── */}
|
|
340
413
|
<box marginTop={1}>
|
|
341
414
|
<text fg={theme().textMuted}>
|
|
342
|
-
{
|
|
415
|
+
{memoryCount() !== null
|
|
416
|
+
? `Memory: ${fmtInt(memoryCount()!)} entries`
|
|
417
|
+
: 'Memory: N/A'}
|
|
343
418
|
</text>
|
|
344
419
|
</box>
|
|
345
420
|
</box>
|