opencode-subagent-magazine 1.5.1 → 1.5.3

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/README.md CHANGED
@@ -56,7 +56,8 @@
56
56
  - **TTL 自动清理**:可配置数据保留期限(3/7/14/30 天/无期限),每次访问自动续期,过期自动清除
57
57
  - **手动清除条目**:`/subagent-clear-entries` 清除当前会话所有记录,防止历史条目扫描重建
58
58
  - **语言适配**:支持 `/subagent-lang` 运行时切换中/英文,偏好持久化
59
- - **斜杠命令**:`/subagent-lang` `/subagent-max` `/subagent-order` `/subagent-scroll` `/subagent-ttl` `/subagent-clear-entries` `/subagent-session` `/subagent-version` `/subagent-clear-running` 动态配置
59
+ - **可切换边框**:`/subagent-border` 控制面板外边框显隐(默认隐藏),开启后与缓存统计面板风格统一
60
+ - **斜杠命令**:`/subagent-lang` `/subagent-max` `/subagent-order` `/subagent-scroll` `/subagent-ttl` `/subagent-clear-entries` `/subagent-session` `/subagent-version` `/subagent-clear-running` `/subagent-border` 动态配置
60
61
 
61
62
  ---
62
63
 
@@ -114,6 +115,7 @@ npm install -g opencode-subagent-magazine@latest
114
115
  | `/subagent-session` | 查看当前会话 ID | 弹出当前 OpenCode 会话 ID |
115
116
  | `/subagent-version` | 查看插件版本 | 弹出当前插件版本号 |
116
117
  | `/subagent-clear-running` | 批量清理僵尸条目 | 一键将所有运行中的条目标记为完成,清理卡住的旧数据 |
118
+ | `/subagent-border` | 开关面板边框 | 切换面板外边框显隐(默认隐藏),即时生效并持久化 |
117
119
 
118
120
  <div align="center">
119
121
  <img src="https://raw.githubusercontent.com/Hotakus/opencode-subagent-magazine/master/assets/slash_cmds.png" alt="斜杠命令" width="49%"></img>
@@ -1 +1 @@
1
- export declare const PLUGIN_VERSION = "1.5.1";
1
+ export declare const PLUGIN_VERSION = "1.5.3";
package/dist/_version.js CHANGED
@@ -1,2 +1,2 @@
1
1
  // auto-generated
2
- export const PLUGIN_VERSION = "1.5.1";
2
+ export const PLUGIN_VERSION = "1.5.3";
package/dist/i18n.d.ts CHANGED
@@ -49,6 +49,8 @@ declare const ZH_T: {
49
49
  readonly "cancel.status_error": "无法查询会话状态";
50
50
  readonly "cancel.sent": "已发送取消指令";
51
51
  readonly "cancel.failed": "取消失败";
52
+ readonly borderShown: "面板边框 已显示";
53
+ readonly borderHidden: "面板边框 已隐藏";
52
54
  };
53
55
  /** 结构约束:值放宽为 string,键集合来自中文表(新增语言缺 key 会编译报错)。 */
54
56
  export type Translation = {
package/dist/i18n.js CHANGED
@@ -53,6 +53,8 @@ const ZH_T = {
53
53
  "cancel.status_error": "无法查询会话状态",
54
54
  "cancel.sent": "已发送取消指令",
55
55
  "cancel.failed": "取消失败",
56
+ "borderShown": "面板边框 已显示",
57
+ "borderHidden": "面板边框 已隐藏",
56
58
  };
57
59
  const EN_T = {
58
60
  "panel.title": "SubAgent",
@@ -104,6 +106,8 @@ const EN_T = {
104
106
  "cancel.status_error": "Cannot query session status",
105
107
  "cancel.sent": "Cancel instruction sent",
106
108
  "cancel.failed": "Cancellation failed",
109
+ "borderShown": "Panel border shown",
110
+ "borderHidden": "Panel border hidden",
107
111
  };
108
112
  const JA_T = {
109
113
  "panel.title": "サブエージェント",
@@ -155,6 +159,8 @@ const JA_T = {
155
159
  "cancel.status_error": "セッション状態を照会できません",
156
160
  "cancel.sent": "キャンセル指示を送信しました",
157
161
  "cancel.failed": "キャンセルに失敗しました",
162
+ "borderShown": "パネル枠線 表示",
163
+ "borderHidden": "パネル枠線 非表示",
158
164
  };
159
165
  const KO_T = {
160
166
  "panel.title": "서브 에이전트",
@@ -206,6 +212,8 @@ const KO_T = {
206
212
  "cancel.status_error": "세션 상태를 조회할 수 없습니다",
207
213
  "cancel.sent": "취소 지시를 보냈습니다",
208
214
  "cancel.failed": "취소에 실패했습니다",
215
+ "borderShown": "패널 테두리 표시",
216
+ "borderHidden": "패널 테두리 숨김",
209
217
  };
210
218
  export const LANGS = { zh: ZH_T, en: EN_T, ja: JA_T, ko: KO_T };
211
219
  /** 语言元数据:/subagent-lang 选项与自动检测共用。 */
package/dist/index.js CHANGED
@@ -369,8 +369,9 @@ function SubAgentPanel(props) {
369
369
  const [renderTick, setRenderTick] = createSignal(0);
370
370
  let boxEl;
371
371
  let disposed = false;
372
- /** Total context tokens for a sub-agent session.
373
- * Matches opencode-visual-cache's "总计": last assistant message's input + cache.read. */
372
+ /** Total usage tokens for a sub-agent session.
373
+ * Matches opencode's bottom status bar usage: last assistant message with
374
+ * output > 0, summing input + output + reasoning + cache read/write. */
374
375
  const readSessionTokens = (sid) => {
375
376
  if (!sid)
376
377
  return undefined;
@@ -382,10 +383,13 @@ function SubAgentPanel(props) {
382
383
  if (m.role !== "assistant")
383
384
  continue;
384
385
  const t = m.tokens;
385
- if (!t)
386
+ if (!t || !(Number(t.output) > 0))
386
387
  continue;
387
- const cache = t.cache;
388
- const ctx = (Number(t.input) || 0) + (cache?.read ?? 0);
388
+ const ctx = (Number(t.input) || 0) +
389
+ (Number(t.output) || 0) +
390
+ (Number(t.reasoning) || 0) +
391
+ (Number(t.cache?.read) || 0) +
392
+ (Number(t.cache?.write) || 0);
389
393
  if (ctx > 0)
390
394
  return ctx;
391
395
  }
@@ -1248,7 +1252,19 @@ function SubAgentPanel(props) {
1248
1252
  return next;
1249
1253
  });
1250
1254
  };
1251
- const sep = () => "\u2500".repeat(Math.max(1, panelWidth()));
1255
+ /** Horizontal space eaten by the panel border (1+1) and its padding (2+2). */
1256
+ const gutter = createMemo(() => (props.borderVisible() ? 6 : 0));
1257
+ const sep = () => "\u2500".repeat(Math.max(1, panelWidth() - gutter()));
1258
+ // The border toggling shifts the box dimensions, which may not reliably
1259
+ // re-fire onSizeChange across (re)mount cycles; re-sync panelWidth with the
1260
+ // live box so the content width stays correct.
1261
+ createEffect(() => {
1262
+ props.borderVisible();
1263
+ if (boxEl && typeof boxEl.width === "number" && boxEl.width > 0) {
1264
+ const w = Math.max(20, boxEl.width);
1265
+ setPanelWidth((prev) => (prev === w ? prev : w));
1266
+ }
1267
+ });
1252
1268
  // ── expanded detail right-align ──
1253
1269
  const expandedMaxLabelW = createMemo(() => {
1254
1270
  const labels = [
@@ -1258,7 +1274,7 @@ function SubAgentPanel(props) {
1258
1274
  return Math.max(...labels.map(l => visualWidth(l + ": ")));
1259
1275
  });
1260
1276
  const expandedPad = (label) => Math.max(0, expandedMaxLabelW() - visualWidth(label + ": "));
1261
- const expandedValAvail = () => Math.max(6, panelWidth() - INDENT - expandedMaxLabelW());
1277
+ const expandedValAvail = () => Math.max(6, panelWidth() - gutter() - INDENT - expandedMaxLabelW());
1262
1278
  // ── header parts for colored spans ──
1263
1279
  const summaryParts = createMemo(() => {
1264
1280
  if (!anyEntry())
@@ -1293,7 +1309,7 @@ function SubAgentPanel(props) {
1293
1309
  return false;
1294
1310
  const icon = "\u25bc";
1295
1311
  const need = visualWidth(icon) + 1 + visualWidth(t("panel.title")) + versionW + summaryCols();
1296
- return need <= panelWidth();
1312
+ return need <= panelWidth() - gutter();
1297
1313
  });
1298
1314
  const leftCols = createMemo(() => {
1299
1315
  const icon = open() ? "\u25bc" : "\u25b6";
@@ -1305,11 +1321,11 @@ function SubAgentPanel(props) {
1305
1321
  const spacerCols = createMemo(() => {
1306
1322
  if (!anyEntry())
1307
1323
  return 0;
1308
- return Math.max(0, panelWidth() - leftCols() - summaryCols());
1324
+ return Math.max(0, panelWidth() - gutter() - leftCols() - summaryCols());
1309
1325
  });
1310
- const valueCols = (label) => Math.max(4, panelWidth() - INDENT - visualWidth(label + ": "));
1326
+ const valueCols = (label) => Math.max(4, panelWidth() - gutter() - INDENT - visualWidth(label + ": "));
1311
1327
  // ── render ──
1312
- return (_jsxs("box", { border: false, paddingTop: 0, paddingBottom: 0, paddingLeft: 0, paddingRight: 0, flexDirection: "column", gap: 0, ref: boxEl, onSizeChange: () => {
1328
+ return (_jsxs("box", { border: props.borderVisible(), ...(props.borderVisible() ? { borderColor: pal().border } : {}), paddingTop: 0, paddingBottom: 0, paddingLeft: props.borderVisible() ? 2 : 0, paddingRight: props.borderVisible() ? 2 : 0, flexDirection: "column", gap: 0, ref: boxEl, onSizeChange: () => {
1313
1329
  const w = boxEl ? Math.max(20, boxEl.width ?? 0) : 28;
1314
1330
  setPanelWidth((prev) => (prev === w ? prev : w));
1315
1331
  }, children: [_jsxs("text", { onMouseUp: () => {
@@ -1401,7 +1417,7 @@ function SubAgentPanel(props) {
1401
1417
  w += visualWidth(tk);
1402
1418
  return w;
1403
1419
  };
1404
- const labelAvail = () => Math.max(6, panelWidth() - LEFT_PAD - suffixW());
1420
+ const labelAvail = () => Math.max(6, panelWidth() - gutter() - LEFT_PAD - suffixW());
1405
1421
  const labelText = () => {
1406
1422
  const max = labelAvail();
1407
1423
  const text = entry.title || entry.agent;
@@ -1439,7 +1455,7 @@ function SubAgentPanel(props) {
1439
1455
  const cancelLabel = () => ` ${t("cancel.label")}`;
1440
1456
  const dismissLabel = () => ` ${t("dismiss.label")}`;
1441
1457
  const rightW = (isRunning ? visualWidth(dismissLabel()) : 0) + (isRunning && entry.sessionId ? visualWidth(cancelLabel()) : 0);
1442
- const spacerW = () => Math.max(1, panelWidth() - openW() - rightW - 2);
1458
+ const spacerW = () => Math.max(1, panelWidth() - gutter() - openW() - rightW - 2);
1443
1459
  return (_jsxs("box", { flexDirection: "row", children: [_jsx(Show, { when: entry.sessionId, children: _jsxs("text", { onMouseOver: () => setHoveredOpen(entry.id), onMouseOut: () => setHoveredOpen(undefined), onMouseUp: () => {
1444
1460
  if (entry.sessionId) {
1445
1461
  props.api.route.navigate("session", { sessionID: entry.sessionId });
@@ -1457,7 +1473,7 @@ function SubAgentPanel(props) {
1457
1473
  const right = props.sortOrder() === "desc"
1458
1474
  ? `\u2191 ${t("scroll.top")}`
1459
1475
  : `\u2193 ${t("scroll.bottom")}`;
1460
- const pad = showTop ? Math.max(1, panelWidth() - visualWidth(left) - visualWidth(right)) : 0;
1476
+ const pad = showTop ? Math.max(1, panelWidth() - gutter() - visualWidth(left) - visualWidth(right)) : 0;
1461
1477
  return (_jsxs("box", { flexDirection: "row", children: [_jsx("text", { onMouseOver: () => showMore && setHoveredMoreBelow(true), onMouseOut: () => setHoveredMoreBelow(false), onMouseUp: () => {
1462
1478
  if (!showMore)
1463
1479
  return;
@@ -1490,7 +1506,7 @@ function createSidebarSlot(api, sig) {
1490
1506
  slots: {
1491
1507
  sidebar_content(ctx, input) {
1492
1508
  sig.sessionId = input.session_id;
1493
- return (_jsx(SubAgentPanel, { theme: ctx.theme.current, api: api, lang: sig.lang, maxEntries: sig.maxEntries, sortOrder: sig.sortOrder, scrollMode: sig.scrollMode, sessionId: input.session_id }));
1509
+ return (_jsx(SubAgentPanel, { theme: ctx.theme.current, api: api, lang: sig.lang, maxEntries: sig.maxEntries, sortOrder: sig.sortOrder, scrollMode: sig.scrollMode, borderVisible: sig.borderVisible, sessionId: input.session_id }));
1494
1510
  },
1495
1511
  },
1496
1512
  };
@@ -1504,7 +1520,8 @@ const tui = async (api) => {
1504
1520
  const [maxEntries, setMaxEntries] = createSignal(parseInt(String(api.kv.get(`${KV_PREFIX}.max_entries`, "10")), 10) || 10);
1505
1521
  const [sortOrder, setSortOrder] = createSignal(String(api.kv.get(`${KV_PREFIX}.order`, "desc")) === "asc" ? "asc" : "desc");
1506
1522
  const [scrollMode, setScrollMode] = createSignal(String(api.kv.get(`${KV_PREFIX}.scroll_mode`, "wheel")) === "click" ? "click" : "wheel");
1507
- const signals = { lang, setLang, maxEntries, setMaxEntries, sortOrder, setSortOrder, scrollMode, setScrollMode, sessionId: "" };
1523
+ const [borderVisible, setBorderVisible] = createSignal(api.kv.get(`${KV_PREFIX}.border`, false) === true);
1524
+ const signals = { lang, setLang, maxEntries, setMaxEntries, sortOrder, setSortOrder, scrollMode, setScrollMode, borderVisible, setBorderVisible, sessionId: "" };
1508
1525
  api.slots.register(createSidebarSlot(api, signals));
1509
1526
  // ── slash command: /subagent-lang ──
1510
1527
  api.command?.register(() => [
@@ -1748,6 +1765,20 @@ const tui = async (api) => {
1748
1765
  } })));
1749
1766
  },
1750
1767
  },
1768
+ {
1769
+ title: "SubAgent Magazine: Border",
1770
+ value: "subagent-border",
1771
+ description: "Show or hide the panel border",
1772
+ slash: { name: "subagent-border" },
1773
+ onSelect: (dialog) => {
1774
+ const t = createT(() => signals.lang());
1775
+ const cur = Boolean(api.kv.get(`${KV_PREFIX}.border`, false));
1776
+ api.kv.set(`${KV_PREFIX}.border`, !cur);
1777
+ signals.setBorderVisible(!cur);
1778
+ api.ui.toast({ message: !cur ? t("borderShown") : t("borderHidden") });
1779
+ dialog?.clear();
1780
+ },
1781
+ },
1751
1782
  ]);
1752
1783
  };
1753
1784
  const mod = {
package/dist/tui.js CHANGED
@@ -4,14 +4,16 @@ import { insertNode as _$insertNode } from "@opentui/solid";
4
4
  import { createComponent as _$createComponent } from "@opentui/solid";
5
5
  import { effect as _$effect } from "@opentui/solid";
6
6
  import { insert as _$insert } from "@opentui/solid";
7
- import { memo as _$memo } from "@opentui/solid";
8
- import { use as _$use } from "@opentui/solid";
9
7
  import { setProp as _$setProp } from "@opentui/solid";
8
+ import { use as _$use } from "@opentui/solid";
9
+ import { spread as _$spread } from "@opentui/solid";
10
+ import { mergeProps as _$mergeProps } from "@opentui/solid";
11
+ import { memo as _$memo } from "@opentui/solid";
10
12
  import { createElement as _$createElement } from "@opentui/solid";
11
13
  import { createMemo, createSignal, createEffect, onMount, onCleanup, untrack, Show, For } from "solid-js";
12
14
 
13
15
  // src/_version.ts
14
- var PLUGIN_VERSION = "1.5.1";
16
+ var PLUGIN_VERSION = "1.5.3";
15
17
 
16
18
  // src/clipboard.ts
17
19
  import { spawn } from "node:child_process";
@@ -159,7 +161,9 @@ var ZH_T = {
159
161
  "cancel.already_ended": "\u4F1A\u8BDD\u5DF2\u7ED3\u675F\uFF0C\u65E0\u9700\u53D6\u6D88",
160
162
  "cancel.status_error": "\u65E0\u6CD5\u67E5\u8BE2\u4F1A\u8BDD\u72B6\u6001",
161
163
  "cancel.sent": "\u5DF2\u53D1\u9001\u53D6\u6D88\u6307\u4EE4",
162
- "cancel.failed": "\u53D6\u6D88\u5931\u8D25"
164
+ "cancel.failed": "\u53D6\u6D88\u5931\u8D25",
165
+ "borderShown": "\u9762\u677F\u8FB9\u6846 \u5DF2\u663E\u793A",
166
+ "borderHidden": "\u9762\u677F\u8FB9\u6846 \u5DF2\u9690\u85CF"
163
167
  };
164
168
  var EN_T = {
165
169
  "panel.title": "SubAgent",
@@ -210,7 +214,9 @@ var EN_T = {
210
214
  "cancel.already_ended": "Session already ended, no need to cancel",
211
215
  "cancel.status_error": "Cannot query session status",
212
216
  "cancel.sent": "Cancel instruction sent",
213
- "cancel.failed": "Cancellation failed"
217
+ "cancel.failed": "Cancellation failed",
218
+ "borderShown": "Panel border shown",
219
+ "borderHidden": "Panel border hidden"
214
220
  };
215
221
  var JA_T = {
216
222
  "panel.title": "\u30B5\u30D6\u30A8\u30FC\u30B8\u30A7\u30F3\u30C8",
@@ -261,7 +267,9 @@ var JA_T = {
261
267
  "cancel.already_ended": "\u30BB\u30C3\u30B7\u30E7\u30F3\u306F\u65E2\u306B\u7D42\u4E86\u3057\u3066\u3044\u307E\u3059",
262
268
  "cancel.status_error": "\u30BB\u30C3\u30B7\u30E7\u30F3\u72B6\u614B\u3092\u7167\u4F1A\u3067\u304D\u307E\u305B\u3093",
263
269
  "cancel.sent": "\u30AD\u30E3\u30F3\u30BB\u30EB\u6307\u793A\u3092\u9001\u4FE1\u3057\u307E\u3057\u305F",
264
- "cancel.failed": "\u30AD\u30E3\u30F3\u30BB\u30EB\u306B\u5931\u6557\u3057\u307E\u3057\u305F"
270
+ "cancel.failed": "\u30AD\u30E3\u30F3\u30BB\u30EB\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
271
+ "borderShown": "\u30D1\u30CD\u30EB\u67A0\u7DDA \u8868\u793A",
272
+ "borderHidden": "\u30D1\u30CD\u30EB\u67A0\u7DDA \u975E\u8868\u793A"
265
273
  };
266
274
  var KO_T = {
267
275
  "panel.title": "\uC11C\uBE0C \uC5D0\uC774\uC804\uD2B8",
@@ -312,7 +320,9 @@ var KO_T = {
312
320
  "cancel.already_ended": "\uC138\uC158\uC774 \uC774\uBBF8 \uC885\uB8CC\uB418\uC5C8\uC2B5\uB2C8\uB2E4",
313
321
  "cancel.status_error": "\uC138\uC158 \uC0C1\uD0DC\uB97C \uC870\uD68C\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4",
314
322
  "cancel.sent": "\uCDE8\uC18C \uC9C0\uC2DC\uB97C \uBCF4\uB0C8\uC2B5\uB2C8\uB2E4",
315
- "cancel.failed": "\uCDE8\uC18C\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4"
323
+ "cancel.failed": "\uCDE8\uC18C\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4",
324
+ "borderShown": "\uD328\uB110 \uD14C\uB450\uB9AC \uD45C\uC2DC",
325
+ "borderHidden": "\uD328\uB110 \uD14C\uB450\uB9AC \uC228\uAE40"
316
326
  };
317
327
  var LANGS = { zh: ZH_T, en: EN_T, ja: JA_T, ko: KO_T };
318
328
  var LANG_META = [
@@ -755,9 +765,8 @@ function SubAgentPanel(props) {
755
765
  const m = msgs[i];
756
766
  if (m.role !== "assistant") continue;
757
767
  const t2 = m.tokens;
758
- if (!t2) continue;
759
- const cache = t2.cache;
760
- const ctx = (Number(t2.input) || 0) + (cache?.read ?? 0);
768
+ if (!t2 || !(Number(t2.output) > 0)) continue;
769
+ const ctx = (Number(t2.input) || 0) + (Number(t2.output) || 0) + (Number(t2.reasoning) || 0) + (Number(t2.cache?.read) || 0) + (Number(t2.cache?.write) || 0);
761
770
  if (ctx > 0) return ctx;
762
771
  }
763
772
  }
@@ -1569,13 +1578,21 @@ function SubAgentPanel(props) {
1569
1578
  return next;
1570
1579
  });
1571
1580
  };
1572
- const sep = () => "\u2500".repeat(Math.max(1, panelWidth()));
1581
+ const gutter = createMemo(() => props.borderVisible() ? 6 : 0);
1582
+ const sep = () => "\u2500".repeat(Math.max(1, panelWidth() - gutter()));
1583
+ createEffect(() => {
1584
+ props.borderVisible();
1585
+ if (boxEl && typeof boxEl.width === "number" && boxEl.width > 0) {
1586
+ const w = Math.max(20, boxEl.width);
1587
+ setPanelWidth((prev) => prev === w ? prev : w);
1588
+ }
1589
+ });
1573
1590
  const expandedMaxLabelW = createMemo(() => {
1574
1591
  const labels = [t("agent.label"), t("status.label"), t("time.label"), t("tokens.label"), t("error.label"), t("cost.label"), t("model.label"), t("todo.label"), t("session.label")];
1575
1592
  return Math.max(...labels.map((l) => visualWidth(l + ": ")));
1576
1593
  });
1577
1594
  const expandedPad = (label) => Math.max(0, expandedMaxLabelW() - visualWidth(label + ": "));
1578
- const expandedValAvail = () => Math.max(6, panelWidth() - INDENT - expandedMaxLabelW());
1595
+ const expandedValAvail = () => Math.max(6, panelWidth() - gutter() - INDENT - expandedMaxLabelW());
1579
1596
  const summaryParts = createMemo(() => {
1580
1597
  if (!anyEntry()) return null;
1581
1598
  const dot = "\u25CF";
@@ -1604,7 +1621,7 @@ function SubAgentPanel(props) {
1604
1621
  if (!open()) return false;
1605
1622
  const icon = "\u25BC";
1606
1623
  const need = visualWidth(icon) + 1 + visualWidth(t("panel.title")) + versionW + summaryCols();
1607
- return need <= panelWidth();
1624
+ return need <= panelWidth() - gutter();
1608
1625
  });
1609
1626
  const leftCols = createMemo(() => {
1610
1627
  const icon = open() ? "\u25BC" : "\u25B6";
@@ -1614,25 +1631,36 @@ function SubAgentPanel(props) {
1614
1631
  });
1615
1632
  const spacerCols = createMemo(() => {
1616
1633
  if (!anyEntry()) return 0;
1617
- return Math.max(0, panelWidth() - leftCols() - summaryCols());
1634
+ return Math.max(0, panelWidth() - gutter() - leftCols() - summaryCols());
1618
1635
  });
1619
- const valueCols = (label) => Math.max(4, panelWidth() - INDENT - visualWidth(label + ": "));
1636
+ const valueCols = (label) => Math.max(4, panelWidth() - gutter() - INDENT - visualWidth(label + ": "));
1620
1637
  return (() => {
1621
1638
  var _el$ = _$createElement("box"), _el$2 = _$createElement("text"), _el$3 = _$createElement("span"), _el$4 = _$createElement("span");
1622
1639
  _$insertNode(_el$, _el$2);
1623
1640
  var _ref$ = boxEl;
1624
1641
  typeof _ref$ === "function" ? _$use(_ref$, _el$) : boxEl = _el$;
1625
- _$setProp(_el$, "border", false);
1626
- _$setProp(_el$, "paddingTop", 0);
1627
- _$setProp(_el$, "paddingBottom", 0);
1628
- _$setProp(_el$, "paddingLeft", 0);
1629
- _$setProp(_el$, "paddingRight", 0);
1630
- _$setProp(_el$, "flexDirection", "column");
1631
- _$setProp(_el$, "gap", 0);
1632
- _$setProp(_el$, "onSizeChange", () => {
1633
- const w = boxEl ? Math.max(20, boxEl.width ?? 0) : 28;
1634
- setPanelWidth((prev) => prev === w ? prev : w);
1635
- });
1642
+ _$spread(_el$, _$mergeProps({
1643
+ get border() {
1644
+ return props.borderVisible();
1645
+ }
1646
+ }, () => props.borderVisible() ? {
1647
+ borderColor: pal().border
1648
+ } : {}, {
1649
+ "paddingTop": 0,
1650
+ "paddingBottom": 0,
1651
+ get paddingLeft() {
1652
+ return props.borderVisible() ? 2 : 0;
1653
+ },
1654
+ get paddingRight() {
1655
+ return props.borderVisible() ? 2 : 0;
1656
+ },
1657
+ "flexDirection": "column",
1658
+ "gap": 0,
1659
+ "onSizeChange": () => {
1660
+ const w = boxEl ? Math.max(20, boxEl.width ?? 0) : 28;
1661
+ setPanelWidth((prev) => prev === w ? prev : w);
1662
+ }
1663
+ }), true);
1636
1664
  _$insertNode(_el$2, _el$3);
1637
1665
  _$insertNode(_el$2, _el$4);
1638
1666
  _$setProp(_el$2, "onMouseUp", () => {
@@ -1829,7 +1857,7 @@ function SubAgentPanel(props) {
1829
1857
  if (tk) w += visualWidth(tk);
1830
1858
  return w;
1831
1859
  };
1832
- const labelAvail = () => Math.max(6, panelWidth() - LEFT_PAD - suffixW());
1860
+ const labelAvail = () => Math.max(6, panelWidth() - gutter() - LEFT_PAD - suffixW());
1833
1861
  const labelText = () => {
1834
1862
  const max2 = labelAvail();
1835
1863
  const text = entry.title || entry.agent;
@@ -2228,7 +2256,7 @@ ${t("session.toast.copy_failed")}`,
2228
2256
  const cancelLabel = () => ` ${t("cancel.label")}`;
2229
2257
  const dismissLabel = () => ` ${t("dismiss.label")}`;
2230
2258
  const rightW = (isRunning ? visualWidth(dismissLabel()) : 0) + (isRunning && entry.sessionId ? visualWidth(cancelLabel()) : 0);
2231
- const spacerW = () => Math.max(1, panelWidth() - openW() - rightW - 2);
2259
+ const spacerW = () => Math.max(1, panelWidth() - gutter() - openW() - rightW - 2);
2232
2260
  return (() => {
2233
2261
  var _el$91 = _$createElement("box"), _el$95 = _$createElement("text");
2234
2262
  _$insertNode(_el$91, _el$95);
@@ -2331,7 +2359,7 @@ ${t("session.toast.copy_failed")}`,
2331
2359
  const showTop = props.sortOrder() === "desc" ? scrollOffset() > 0 : entryList().length > max() && clampedOffset() < entryList().length - max();
2332
2360
  const left = showMore ? ` \u2193 ${hiddenBelow()} ${t("scroll.more")}` : " ";
2333
2361
  const right = props.sortOrder() === "desc" ? `\u2191 ${t("scroll.top")}` : `\u2193 ${t("scroll.bottom")}`;
2334
- const pad = showTop ? Math.max(1, panelWidth() - visualWidth(left) - visualWidth(right)) : 0;
2362
+ const pad = showTop ? Math.max(1, panelWidth() - gutter() - visualWidth(left) - visualWidth(right)) : 0;
2335
2363
  return (() => {
2336
2364
  var _el$100 = _$createElement("box"), _el$101 = _$createElement("text"), _el$102 = _$createElement("span");
2337
2365
  _$insertNode(_el$100, _el$101);
@@ -2432,6 +2460,9 @@ function createSidebarSlot(api, sig) {
2432
2460
  get scrollMode() {
2433
2461
  return sig.scrollMode;
2434
2462
  },
2463
+ get borderVisible() {
2464
+ return sig.borderVisible;
2465
+ },
2435
2466
  get sessionId() {
2436
2467
  return input.session_id;
2437
2468
  }
@@ -2448,6 +2479,7 @@ var tui = async (api) => {
2448
2479
  const [maxEntries, setMaxEntries] = createSignal(parseInt(String(api.kv.get(`${KV_PREFIX}.max_entries`, "10")), 10) || 10);
2449
2480
  const [sortOrder, setSortOrder] = createSignal(String(api.kv.get(`${KV_PREFIX}.order`, "desc")) === "asc" ? "asc" : "desc");
2450
2481
  const [scrollMode, setScrollMode] = createSignal(String(api.kv.get(`${KV_PREFIX}.scroll_mode`, "wheel")) === "click" ? "click" : "wheel");
2482
+ const [borderVisible, setBorderVisible] = createSignal(api.kv.get(`${KV_PREFIX}.border`, false) === true);
2451
2483
  const signals = {
2452
2484
  lang,
2453
2485
  setLang,
@@ -2457,6 +2489,8 @@ var tui = async (api) => {
2457
2489
  setSortOrder,
2458
2490
  scrollMode,
2459
2491
  setScrollMode,
2492
+ borderVisible,
2493
+ setBorderVisible,
2460
2494
  sessionId: ""
2461
2495
  };
2462
2496
  api.slots.register(createSidebarSlot(api, signals));
@@ -2790,6 +2824,23 @@ var tui = async (api) => {
2790
2824
  }
2791
2825
  }));
2792
2826
  }
2827
+ }, {
2828
+ title: "SubAgent Magazine: Border",
2829
+ value: "subagent-border",
2830
+ description: "Show or hide the panel border",
2831
+ slash: {
2832
+ name: "subagent-border"
2833
+ },
2834
+ onSelect: (dialog) => {
2835
+ const t = createT(() => signals.lang());
2836
+ const cur = Boolean(api.kv.get(`${KV_PREFIX}.border`, false));
2837
+ api.kv.set(`${KV_PREFIX}.border`, !cur);
2838
+ signals.setBorderVisible(!cur);
2839
+ api.ui.toast({
2840
+ message: !cur ? t("borderShown") : t("borderHidden")
2841
+ });
2842
+ dialog?.clear();
2843
+ }
2793
2844
  }]);
2794
2845
  };
2795
2846
  var mod = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-subagent-magazine",
3
- "version": "1.5.1",
3
+ "version": "1.5.3",
4
4
  "description": "OpenCode TUI plugin monitoring sub-agent invocation status in real time",
5
5
  "type": "module",
6
6
  "types": "dist/index.d.ts",
package/src/_version.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  // auto-generated
2
- export const PLUGIN_VERSION="1.5.1";
2
+ export const PLUGIN_VERSION="1.5.3";
package/src/i18n.ts CHANGED
@@ -56,6 +56,8 @@ const ZH_T = {
56
56
  "cancel.status_error": "无法查询会话状态",
57
57
  "cancel.sent": "已发送取消指令",
58
58
  "cancel.failed": "取消失败",
59
+ "borderShown": "面板边框 已显示",
60
+ "borderHidden": "面板边框 已隐藏",
59
61
  } as const
60
62
 
61
63
  /** 结构约束:值放宽为 string,键集合来自中文表(新增语言缺 key 会编译报错)。 */
@@ -111,6 +113,8 @@ const EN_T: Translation = {
111
113
  "cancel.status_error": "Cannot query session status",
112
114
  "cancel.sent": "Cancel instruction sent",
113
115
  "cancel.failed": "Cancellation failed",
116
+ "borderShown": "Panel border shown",
117
+ "borderHidden": "Panel border hidden",
114
118
  }
115
119
 
116
120
  const JA_T: Translation = {
@@ -163,6 +167,8 @@ const JA_T: Translation = {
163
167
  "cancel.status_error": "セッション状態を照会できません",
164
168
  "cancel.sent": "キャンセル指示を送信しました",
165
169
  "cancel.failed": "キャンセルに失敗しました",
170
+ "borderShown": "パネル枠線 表示",
171
+ "borderHidden": "パネル枠線 非表示",
166
172
  }
167
173
 
168
174
  const KO_T: Translation = {
@@ -215,6 +221,8 @@ const KO_T: Translation = {
215
221
  "cancel.status_error": "세션 상태를 조회할 수 없습니다",
216
222
  "cancel.sent": "취소 지시를 보냈습니다",
217
223
  "cancel.failed": "취소에 실패했습니다",
224
+ "borderShown": "패널 테두리 표시",
225
+ "borderHidden": "패널 테두리 숨김",
218
226
  }
219
227
 
220
228
  export const LANGS: Record<LangCode, Translation> = { zh: ZH_T, en: EN_T, ja: JA_T, ko: KO_T }
package/src/index.tsx CHANGED
@@ -214,6 +214,7 @@ function SubAgentPanel(props: {
214
214
  maxEntries: () => number
215
215
  sortOrder: () => SortOrder
216
216
  scrollMode: () => ScrollMode
217
+ borderVisible: () => boolean
217
218
  sessionId: string
218
219
  }): JSX.Element {
219
220
  const t = createT(() => props.lang())
@@ -428,8 +429,9 @@ function SubAgentPanel(props: {
428
429
  let boxEl: any
429
430
  let disposed = false
430
431
 
431
- /** Total context tokens for a sub-agent session.
432
- * Matches opencode-visual-cache's "总计": last assistant message's input + cache.read. */
432
+ /** Total usage tokens for a sub-agent session.
433
+ * Matches opencode's bottom status bar usage: last assistant message with
434
+ * output > 0, summing input + output + reasoning + cache read/write. */
433
435
  const readSessionTokens = (sid: string): number | undefined => {
434
436
  if (!sid) return undefined
435
437
  try {
@@ -439,9 +441,13 @@ function SubAgentPanel(props: {
439
441
  const m = (msgs as any[])[i]
440
442
  if (m.role !== "assistant") continue
441
443
  const t = m.tokens
442
- if (!t) continue
443
- const cache = t.cache as { read?: number; write?: number } | undefined
444
- const ctx = (Number(t.input) || 0) + (cache?.read ?? 0)
444
+ if (!t || !(Number(t.output) > 0)) continue
445
+ const ctx =
446
+ (Number(t.input) || 0) +
447
+ (Number(t.output) || 0) +
448
+ (Number(t.reasoning) || 0) +
449
+ (Number(t.cache?.read) || 0) +
450
+ (Number(t.cache?.write) || 0)
445
451
  if (ctx > 0) return ctx
446
452
  }
447
453
  }
@@ -1276,7 +1282,21 @@ function SubAgentPanel(props: {
1276
1282
  })
1277
1283
  }
1278
1284
 
1279
- const sep = () => "\u2500".repeat(Math.max(1, panelWidth()))
1285
+ /** Horizontal space eaten by the panel border (1+1) and its padding (2+2). */
1286
+ const gutter = createMemo(() => (props.borderVisible() ? 6 : 0))
1287
+
1288
+ const sep = () => "\u2500".repeat(Math.max(1, panelWidth() - gutter()))
1289
+
1290
+ // The border toggling shifts the box dimensions, which may not reliably
1291
+ // re-fire onSizeChange across (re)mount cycles; re-sync panelWidth with the
1292
+ // live box so the content width stays correct.
1293
+ createEffect(() => {
1294
+ props.borderVisible()
1295
+ if (boxEl && typeof boxEl.width === "number" && boxEl.width > 0) {
1296
+ const w = Math.max(20, boxEl.width)
1297
+ setPanelWidth((prev) => (prev === w ? prev : w))
1298
+ }
1299
+ })
1280
1300
 
1281
1301
  // ── expanded detail right-align ──
1282
1302
  const expandedMaxLabelW = createMemo(() => {
@@ -1289,7 +1309,7 @@ function SubAgentPanel(props: {
1289
1309
 
1290
1310
  const expandedPad = (label: string) => Math.max(0, expandedMaxLabelW() - visualWidth(label + ": "))
1291
1311
 
1292
- const expandedValAvail = () => Math.max(6, panelWidth() - INDENT - expandedMaxLabelW())
1312
+ const expandedValAvail = () => Math.max(6, panelWidth() - gutter() - INDENT - expandedMaxLabelW())
1293
1313
 
1294
1314
  // ── header parts for colored spans ──
1295
1315
  const summaryParts = createMemo(() => {
@@ -1323,7 +1343,7 @@ function SubAgentPanel(props: {
1323
1343
  if (!open()) return false
1324
1344
  const icon = "\u25bc"
1325
1345
  const need = visualWidth(icon) + 1 + visualWidth(t("panel.title")) + versionW + summaryCols()
1326
- return need <= panelWidth()
1346
+ return need <= panelWidth() - gutter()
1327
1347
  })
1328
1348
 
1329
1349
  const leftCols = createMemo(() => {
@@ -1335,17 +1355,19 @@ function SubAgentPanel(props: {
1335
1355
 
1336
1356
  const spacerCols = createMemo(() => {
1337
1357
  if (!anyEntry()) return 0
1338
- return Math.max(0, panelWidth() - leftCols() - summaryCols())
1358
+ return Math.max(0, panelWidth() - gutter() - leftCols() - summaryCols())
1339
1359
  })
1340
1360
 
1341
1361
  const valueCols = (label: string) =>
1342
- Math.max(4, panelWidth() - INDENT - visualWidth(label + ": "))
1362
+ Math.max(4, panelWidth() - gutter() - INDENT - visualWidth(label + ": "))
1343
1363
 
1344
1364
  // ── render ──
1345
1365
  return (
1346
1366
  <box
1347
- border={false}
1348
- paddingTop={0} paddingBottom={0} paddingLeft={0} paddingRight={0}
1367
+ border={props.borderVisible()}
1368
+ {...(props.borderVisible() ? { borderColor: pal().border } : {})}
1369
+ paddingTop={0} paddingBottom={0}
1370
+ paddingLeft={props.borderVisible() ? 2 : 0} paddingRight={props.borderVisible() ? 2 : 0}
1349
1371
  flexDirection="column" gap={0}
1350
1372
  ref={boxEl}
1351
1373
  onSizeChange={() => {
@@ -1482,7 +1504,7 @@ function SubAgentPanel(props: {
1482
1504
  if (tk) w += visualWidth(tk)
1483
1505
  return w
1484
1506
  }
1485
- const labelAvail = () => Math.max(6, panelWidth() - LEFT_PAD - suffixW())
1507
+ const labelAvail = () => Math.max(6, panelWidth() - gutter() - LEFT_PAD - suffixW())
1486
1508
  const labelText = () => {
1487
1509
  const max = labelAvail()
1488
1510
  const text = entry.title || entry.agent
@@ -1626,7 +1648,7 @@ function SubAgentPanel(props: {
1626
1648
  const cancelLabel = () => ` ${t("cancel.label")}`
1627
1649
  const dismissLabel = () => ` ${t("dismiss.label")}`
1628
1650
  const rightW = (isRunning ? visualWidth(dismissLabel()) : 0) + (isRunning && entry.sessionId ? visualWidth(cancelLabel()) : 0)
1629
- const spacerW = () => Math.max(1, panelWidth() - openW() - rightW - 2)
1651
+ const spacerW = () => Math.max(1, panelWidth() - gutter() - openW() - rightW - 2)
1630
1652
  return (
1631
1653
  <box flexDirection="row">
1632
1654
  <Show when={entry.sessionId}>
@@ -1683,7 +1705,7 @@ function SubAgentPanel(props: {
1683
1705
  const right = props.sortOrder() === "desc"
1684
1706
  ? `\u2191 ${t("scroll.top")}`
1685
1707
  : `\u2193 ${t("scroll.bottom")}`
1686
- const pad = showTop ? Math.max(1, panelWidth() - visualWidth(left) - visualWidth(right)) : 0
1708
+ const pad = showTop ? Math.max(1, panelWidth() - gutter() - visualWidth(left) - visualWidth(right)) : 0
1687
1709
  return (
1688
1710
  <box flexDirection="row">
1689
1711
  <text
@@ -1748,6 +1770,8 @@ interface SharedSignals {
1748
1770
  setSortOrder: (o: SortOrder) => void
1749
1771
  scrollMode: () => ScrollMode
1750
1772
  setScrollMode: (m: ScrollMode) => void
1773
+ borderVisible: () => boolean
1774
+ setBorderVisible: (v: boolean) => void
1751
1775
  sessionId: string
1752
1776
  }
1753
1777
 
@@ -1765,6 +1789,7 @@ function createSidebarSlot(api: TuiPluginApi, sig: SharedSignals): TuiSlotPlugin
1765
1789
  maxEntries={sig.maxEntries}
1766
1790
  sortOrder={sig.sortOrder}
1767
1791
  scrollMode={sig.scrollMode}
1792
+ borderVisible={sig.borderVisible}
1768
1793
  sessionId={input.session_id}
1769
1794
  />
1770
1795
  )
@@ -1790,8 +1815,11 @@ const tui: TuiPlugin = async (api: TuiPluginApi) => {
1790
1815
  const [scrollMode, setScrollMode] = createSignal<ScrollMode>(
1791
1816
  String(api.kv.get(`${KV_PREFIX}.scroll_mode`, "wheel")) === "click" ? "click" : "wheel"
1792
1817
  )
1818
+ const [borderVisible, setBorderVisible] = createSignal<boolean>(
1819
+ (api.kv.get(`${KV_PREFIX}.border`, false) as boolean) === true
1820
+ )
1793
1821
 
1794
- const signals: SharedSignals = { lang, setLang, maxEntries, setMaxEntries, sortOrder, setSortOrder, scrollMode, setScrollMode, sessionId: "" }
1822
+ const signals: SharedSignals = { lang, setLang, maxEntries, setMaxEntries, sortOrder, setSortOrder, scrollMode, setScrollMode, borderVisible, setBorderVisible, sessionId: "" }
1795
1823
 
1796
1824
  api.slots.register(createSidebarSlot(api, signals))
1797
1825
 
@@ -2065,6 +2093,20 @@ const tui: TuiPlugin = async (api: TuiPluginApi) => {
2065
2093
  ))
2066
2094
  },
2067
2095
  },
2096
+ {
2097
+ title: "SubAgent Magazine: Border",
2098
+ value: "subagent-border",
2099
+ description: "Show or hide the panel border",
2100
+ slash: { name: "subagent-border" },
2101
+ onSelect: (dialog) => {
2102
+ const t = createT(() => signals.lang())
2103
+ const cur = Boolean(api.kv.get(`${KV_PREFIX}.border`, false))
2104
+ api.kv.set(`${KV_PREFIX}.border`, !cur)
2105
+ signals.setBorderVisible(!cur)
2106
+ api.ui.toast({ message: !cur ? t("borderShown") : t("borderHidden") })
2107
+ dialog?.clear()
2108
+ },
2109
+ },
2068
2110
  ])
2069
2111
  }
2070
2112