opencode-subagent-magazine 1.1.3 → 1.2.0

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
@@ -20,6 +20,10 @@
20
20
 
21
21
  ---
22
22
 
23
+ 对 Token 缓存可视化感兴趣吗?可以试试 [opencode-visual-cache](https://github.com/Hotakus/opencode-visual-cache) 这个插件!
24
+
25
+ ---
26
+
23
27
  ## 1. 图片展示
24
28
 
25
29
  <div align="center">
@@ -45,10 +49,12 @@
45
49
  - **手动标记完成**:展开区右侧 `- 标记完成` 按钮可手动终结卡住的僵尸条目;`/subagent-clear-running` 批量清理
46
50
  - **状态字段显示**:展开区 `状态: 运行中/已完成/错误`,颜色与圆点一致,一目了然
47
51
  - **折叠面板**:标题栏点击折叠/展开,状态持久化,重启后保持
48
- - **滚动回顶**:列表底部 `↑ 回顶` 按钮一键回到顶部
52
+ - **滚动回顶/回底**:列表底部 `↑ 回顶` / `↓ 回底` 按钮一键跳转到最新条目位置,方向随排序自适应
53
+ - **可配置排序**:`/subagent-order` 支持「降序(最新在前)」和「升序(最早在前)」两种排列方式
54
+ - **翻页模式切换**:`/subagent-scroll` 可选「滚轮翻页」或「点击 more 翻页」,解决侧边栏与全局滚动冲突
49
55
  - **TTL 自动清理**:超过 3 天的旧数据自动从 KV 中清除
50
56
  - **语言适配**:支持 `/subagent-lang` 运行时切换中/英文,偏好持久化
51
- - **斜杠命令**:`/subagent-lang` `/subagent-max` `/subagent-session` `/subagent-version` `/subagent-clear-running` 动态配置
57
+ - **斜杠命令**:`/subagent-lang` `/subagent-max` `/subagent-order` `/subagent-scroll` `/subagent-session` `/subagent-version` `/subagent-clear-running` 动态配置
52
58
 
53
59
  ---
54
60
 
@@ -99,6 +105,8 @@ npm install -g opencode-subagent-magazine@latest
99
105
  |------|------|---------|
100
106
  | `/subagent-lang` | 切换显示语言 | 从列表选择中文或 English,界面即时切换,无需重启 |
101
107
  | `/subagent-max` | 调整最大可见条目数 | 输入数字(默认 10),控制面板最多显示多少个子代理条目 |
108
+ | `/subagent-order` | 切换排序方式 | 选择「降序(最新在前)」或「升序(最早在前)」,即时重排并跳转到最新条目 |
109
+ | `/subagent-scroll` | 切换翻页模式 | 选择「滚轮翻页」或「点击 more 翻页」,解决与全局滚动冲突 |
102
110
  | `/subagent-session` | 查看当前会话 ID | 弹出当前 OpenCode 会话 ID |
103
111
  | `/subagent-version` | 查看插件版本 | 弹出当前插件版本号 |
104
112
  | `/subagent-clear-running` | 批量清理僵尸条目 | 一键将所有运行中的条目标记为完成,清理卡住的旧数据 |
@@ -114,8 +122,8 @@ npm install -g opencode-subagent-magazine@latest
114
122
  |------|------|
115
123
  | 点击标题栏 | 折叠 / 展开面板 |
116
124
  | 点击条目行 | 展开 / 收起详情 |
117
- | 滚动列表 | 翻页查看更多子代理 |
118
- | 点击 `↑ 回顶` | 跳转到列表顶部 |
125
+ | 滚轮 / 点击 `↑ more` `↓ more` | 翻页查看更多子代理(`/subagent-scroll` 切换翻页模式) |
126
+ | 点击 `↑ 回顶` / `↓ 回底` | 跳转到最新条目位置(方向随排序自适应) |
119
127
  | 点击 `→ 进入会话` | 跳转子代理完整对话 |
120
128
  | 点击 `- 标记完成` | 手动终结该条目(仅运行中时显示) |
121
129
 
@@ -1 +1 @@
1
- export declare const PLUGIN_VERSION = "1.1.3";
1
+ export declare const PLUGIN_VERSION = "1.2.0";
package/dist/_version.js CHANGED
@@ -1,2 +1,2 @@
1
1
  // auto-generated
2
- export const PLUGIN_VERSION = "1.1.3";
2
+ export const PLUGIN_VERSION = "1.2.0";
package/dist/index.js CHANGED
@@ -21,10 +21,15 @@ const I18N = {
21
21
  "cost.label": "费用",
22
22
  "scroll.more": "更多",
23
23
  "scroll.top": "回顶",
24
+ "scroll.bottom": "回底",
24
25
  "dismiss.label": "标记完成",
25
26
  "status.running": "运行中",
26
27
  "status.done": "已完成",
27
28
  "status.error": "错误",
29
+ "order.desc": "降序(最新在前)",
30
+ "order.asc": "升序(最早在前)",
31
+ "scroll.wheel": "滚轮翻页",
32
+ "scroll.click": "点击翻页",
28
33
  },
29
34
  en: {
30
35
  "panel.title": "SubAgent",
@@ -40,10 +45,15 @@ const I18N = {
40
45
  "cost.label": "cost",
41
46
  "scroll.more": "more",
42
47
  "scroll.top": "Top",
48
+ "scroll.bottom": "Bottom",
43
49
  "dismiss.label": "dismiss",
44
50
  "status.running": "running",
45
51
  "status.done": "done",
46
52
  "status.error": "error",
53
+ "order.desc": "Desc (newest first)",
54
+ "order.asc": "Asc (oldest first)",
55
+ "scroll.wheel": "Wheel Scroll",
56
+ "scroll.click": "Click Scroll",
47
57
  },
48
58
  };
49
59
  function detectLang() {
@@ -325,6 +335,8 @@ function SubAgentPanel(props) {
325
335
  const [hoveredOpen, setHoveredOpen] = createSignal(undefined);
326
336
  const [hoveredDismiss, setHoveredDismiss] = createSignal(undefined);
327
337
  const [hoveredTop, setHoveredTop] = createSignal(false);
338
+ const [hoveredMoreAbove, setHoveredMoreAbove] = createSignal(false);
339
+ const [hoveredMoreBelow, setHoveredMoreBelow] = createSignal(false);
328
340
  const [scrollOffset, setScrollOffset] = createSignal((() => { try {
329
341
  return loadSessionData()[props.sessionId]?.scroll ?? 0;
330
342
  }
@@ -982,7 +994,11 @@ function SubAgentPanel(props) {
982
994
  // ── derived signals ──
983
995
  // Stable list — only changes when entryMap changes
984
996
  const entryList = createMemo(() => {
985
- return [...entryMap().values()].sort((a, b) => b.startedAt - a.startedAt);
997
+ const entries = [...entryMap().values()];
998
+ if (props.sortOrder() === "desc") {
999
+ return entries.sort((a, b) => b.startedAt - a.startedAt);
1000
+ }
1001
+ return entries.sort((a, b) => a.startedAt - b.startedAt);
986
1002
  });
987
1003
  const max = props.maxEntries;
988
1004
  const clampedOffset = createMemo(() => {
@@ -995,6 +1011,56 @@ function SubAgentPanel(props) {
995
1011
  const visibleList = createMemo(() => entryList().slice(clampedOffset(), clampedOffset() + max()));
996
1012
  const hiddenAbove = createMemo(() => clampedOffset());
997
1013
  const hiddenBelow = createMemo(() => Math.max(0, entryList().length - clampedOffset() - max()));
1014
+ // Drop hover state when ↑ more disappears (hiddenAbove hits zero)
1015
+ createEffect(() => {
1016
+ if (hiddenAbove() === 0)
1017
+ setHoveredMoreAbove(false);
1018
+ });
1019
+ // Reset scroll on sort order change: jump to newest in view
1020
+ let sortInitialized = false;
1021
+ createEffect(() => {
1022
+ props.sortOrder();
1023
+ if (!sortInitialized) {
1024
+ sortInitialized = true;
1025
+ return;
1026
+ }
1027
+ const total = untrack(() => entryList().length);
1028
+ const m = untrack(() => max());
1029
+ const target = props.sortOrder() === "desc" ? 0 : Math.max(0, total - m);
1030
+ setScrollOffset(target);
1031
+ setTimeout(() => {
1032
+ try {
1033
+ persistScroll(props.sessionId, target);
1034
+ }
1035
+ catch { }
1036
+ }, 0);
1037
+ });
1038
+ // When new entries arrive while viewing the newest end, keep the view at newest
1039
+ let prevEntryCount = 0;
1040
+ createEffect(() => {
1041
+ const total = entryList().length;
1042
+ if (prevEntryCount === 0) {
1043
+ prevEntryCount = total;
1044
+ return;
1045
+ }
1046
+ if (total === prevEntryCount)
1047
+ return;
1048
+ const m = max();
1049
+ const wasAtNewest = props.sortOrder() === "desc"
1050
+ ? untrack(() => scrollOffset() === 0)
1051
+ : untrack(() => scrollOffset() >= prevEntryCount - m);
1052
+ prevEntryCount = total;
1053
+ if (wasAtNewest) {
1054
+ const target = props.sortOrder() === "desc" ? 0 : Math.max(0, total - m);
1055
+ setScrollOffset(target);
1056
+ setTimeout(() => {
1057
+ try {
1058
+ persistScroll(props.sessionId, target);
1059
+ }
1060
+ catch { }
1061
+ }, 0);
1062
+ }
1063
+ });
998
1064
  const entries = createMemo(() => {
999
1065
  const nowVal = now();
1000
1066
  return entryList().map((e) => ({
@@ -1107,6 +1173,8 @@ function SubAgentPanel(props) {
1107
1173
  });
1108
1174
  bump();
1109
1175
  }, children: [_jsx("span", { style: { fg: pal().muted }, children: renderTick() >= 0 && open() ? "\u25bc " : "\u25b6 " }), _jsx("span", { style: { fg: pal().primary }, children: t("panel.title") }), _jsx(Show, { when: showVersion(), children: _jsx("span", { style: { fg: dimColor(pal().muted, 0.75) }, children: versionText }) }), anyEntry() ? (_jsxs(_Fragment, { children: [_jsx("span", { style: { fg: pal().muted }, children: " ".repeat(spacerCols()) }), _jsx("span", { style: { fg: pal().success }, children: summaryParts().done }), runningCount() > 0 && (_jsxs("span", { style: { fg: pal().warning }, children: [" ", summaryParts().running] })), errCount() > 0 && (_jsxs("span", { style: { fg: pal().error }, children: [" ", summaryParts().err] })), summaryParts().duration ? (_jsxs("span", { style: { fg: pal().muted }, children: [" ", summaryParts().duration] })) : null, summaryParts().cost ? (_jsxs("span", { style: { fg: pal().warning }, children: [" ", summaryParts().cost] })) : null] })) : null] }), _jsxs(Show, { when: open(), children: [_jsx("text", { fg: pal().muted, children: sep() }), _jsx(Show, { when: anyEntry(), fallback: _jsxs("text", { style: { fg: pal().muted }, children: [" ", "> ", t("status.none"), " "] }), children: _jsxs("box", { onMouseScroll: (e) => {
1176
+ if (props.scrollMode() === "click")
1177
+ return;
1110
1178
  const total = entryList().length;
1111
1179
  const m = max();
1112
1180
  if (total <= m)
@@ -1120,7 +1188,29 @@ function SubAgentPanel(props) {
1120
1188
  catch { }
1121
1189
  return next;
1122
1190
  });
1123
- }, children: [_jsx(Show, { when: hiddenAbove() > 0, children: _jsxs("text", { style: { fg: pal().muted }, children: [" ", "\u2191 ", hiddenAbove(), " ", t("scroll.more")] }) }), _jsx(For, { each: visibleList(), children: (entry) => {
1191
+ }, children: [_jsx(Show, { when: hiddenAbove() > 0, children: _jsx("text", { onMouseOver: () => setHoveredMoreAbove(true), onMouseOut: () => setHoveredMoreAbove(false), onMouseUp: () => {
1192
+ const total = entryList().length;
1193
+ const m = max();
1194
+ if (total <= m)
1195
+ return;
1196
+ const next = Math.max(0, scrollOffset() - m);
1197
+ if (next === 0) {
1198
+ setTimeout(() => {
1199
+ setScrollOffset(next);
1200
+ try {
1201
+ persistScroll(props.sessionId, next);
1202
+ }
1203
+ catch { }
1204
+ }, 0);
1205
+ }
1206
+ else {
1207
+ setScrollOffset(next);
1208
+ try {
1209
+ persistScroll(props.sessionId, next);
1210
+ }
1211
+ catch { }
1212
+ }
1213
+ }, children: _jsxs("span", { style: { fg: hoveredMoreAbove() ? pal().warning : pal().muted }, children: [" ", "\u2191 ", hiddenAbove(), " ", t("scroll.more")] }) }) }), _jsx(For, { each: visibleList(), children: (entry) => {
1124
1214
  const isExpanded = () => expanded() === entry.id;
1125
1215
  const isRunning = entry.status === "running";
1126
1216
  const isError = entry.status === "error";
@@ -1178,13 +1268,40 @@ function SubAgentPanel(props) {
1178
1268
  upsertEntry({ id: entry.id, title: entry.title, agent: entry.agent, prompt: entry.prompt, status: "done" });
1179
1269
  }, children: _jsx("span", { style: { fg: hoveredDismiss() === entry.id ? pal().warning : pal().muted }, children: dismissLabel() }) })] }) })] }));
1180
1270
  })() })] })] }));
1181
- } }), _jsx(Show, { when: hiddenBelow() > 0 || scrollOffset() > 0, children: (() => {
1271
+ } }), _jsx(Show, { when: hiddenBelow() > 0 || (props.sortOrder() === "desc" ? scrollOffset() > 0 : entryList().length > max() && clampedOffset() < entryList().length - max()), children: (() => {
1182
1272
  const showMore = hiddenBelow() > 0;
1183
- const showTop = scrollOffset() > 0;
1273
+ const showTop = props.sortOrder() === "desc"
1274
+ ? scrollOffset() > 0
1275
+ : entryList().length > max() && clampedOffset() < entryList().length - max();
1184
1276
  const left = showMore ? ` \u2193 ${hiddenBelow()} ${t("scroll.more")}` : " ";
1185
- const right = `\u2191 ${t("scroll.top")}`;
1277
+ const right = props.sortOrder() === "desc"
1278
+ ? `\u2191 ${t("scroll.top")}`
1279
+ : `\u2193 ${t("scroll.bottom")}`;
1186
1280
  const pad = showTop ? Math.max(1, panelWidth() - visualWidth(left) - visualWidth(right)) : 0;
1187
- return (_jsxs("box", { flexDirection: "row", children: [_jsx("text", { style: { fg: pal().muted }, children: left }), showTop ? (_jsxs(_Fragment, { children: [_jsx("text", { style: { fg: pal().muted }, children: " ".repeat(pad) }), _jsx("text", { onMouseOver: () => setHoveredTop(true), onMouseOut: () => setHoveredTop(false), onMouseUp: () => { setScrollOffset(0); setHoveredTop(false); }, children: _jsx("span", { style: { fg: hoveredTop() ? pal().warning : pal().muted }, children: right }) })] })) : null] }));
1281
+ return (_jsxs("box", { flexDirection: "row", children: [_jsx("text", { onMouseOver: () => showMore && setHoveredMoreBelow(true), onMouseOut: () => setHoveredMoreBelow(false), onMouseUp: () => {
1282
+ if (!showMore)
1283
+ return;
1284
+ const total = entryList().length;
1285
+ const m = max();
1286
+ if (total <= m)
1287
+ return;
1288
+ setScrollOffset((prev) => Math.min(total - m, prev + m));
1289
+ try {
1290
+ persistScroll(props.sessionId, scrollOffset());
1291
+ }
1292
+ catch { }
1293
+ setHoveredMoreBelow(false);
1294
+ }, children: _jsx("span", { style: { fg: showMore && hoveredMoreBelow() ? pal().warning : pal().muted }, children: left }) }), showTop ? (_jsxs(_Fragment, { children: [_jsx("text", { style: { fg: pal().muted }, children: " ".repeat(pad) }), _jsx("text", { onMouseOver: () => setHoveredTop(true), onMouseOut: () => setHoveredTop(false), onMouseUp: () => {
1295
+ const total = entryList().length;
1296
+ const m = max();
1297
+ if (props.sortOrder() === "desc") {
1298
+ setScrollOffset(0);
1299
+ }
1300
+ else {
1301
+ setScrollOffset(Math.max(0, total - m));
1302
+ }
1303
+ setHoveredTop(false);
1304
+ }, children: _jsx("span", { style: { fg: hoveredTop() ? pal().warning : pal().muted }, children: right }) })] })) : null] }));
1188
1305
  })() })] }) })] })] }));
1189
1306
  }
1190
1307
  function createSidebarSlot(api, sig) {
@@ -1193,7 +1310,7 @@ function createSidebarSlot(api, sig) {
1193
1310
  slots: {
1194
1311
  sidebar_content(ctx, input) {
1195
1312
  sig.sessionId = input.session_id;
1196
- return (_jsx(SubAgentPanel, { theme: ctx.theme.current, api: api, lang: sig.lang, maxEntries: sig.maxEntries, sessionId: input.session_id }));
1313
+ 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 }));
1197
1314
  },
1198
1315
  },
1199
1316
  };
@@ -1205,7 +1322,9 @@ const tui = async (api) => {
1205
1322
  const initialLang = stored === "zh" || stored === "en" ? stored : detectLang();
1206
1323
  const [lang, setLang] = createSignal(initialLang);
1207
1324
  const [maxEntries, setMaxEntries] = createSignal(parseInt(String(api.kv.get(`${KV_PREFIX}.max_entries`, "10")), 10) || 10);
1208
- const signals = { lang, setLang, maxEntries, setMaxEntries, sessionId: "" };
1325
+ const [sortOrder, setSortOrder] = createSignal(String(api.kv.get(`${KV_PREFIX}.order`, "desc")) === "asc" ? "asc" : "desc");
1326
+ const [scrollMode, setScrollMode] = createSignal(String(api.kv.get(`${KV_PREFIX}.scroll_mode`, "wheel")) === "click" ? "click" : "wheel");
1327
+ const signals = { lang, setLang, maxEntries, setMaxEntries, sortOrder, setSortOrder, scrollMode, setScrollMode, sessionId: "" };
1209
1328
  api.slots.register(createSidebarSlot(api, signals));
1210
1329
  // ── slash command: /subagent-lang ──
1211
1330
  api.command?.register(() => [
@@ -1229,6 +1348,48 @@ const tui = async (api) => {
1229
1348
  } })));
1230
1349
  },
1231
1350
  },
1351
+ {
1352
+ title: "SubAgent Magazine: Sort Order",
1353
+ value: "subagent-order",
1354
+ description: "Set sub-agent entry sort order (desc / asc)",
1355
+ slash: { name: "subagent-order" },
1356
+ onSelect: (dialog) => {
1357
+ const t = (k) => I18N[lang()][k] ?? k;
1358
+ dialog?.replace(() => (_jsx(api.ui.DialogSelect, { title: "Sort Order / \u6392\u5E8F\u65B9\u5F0F", options: [
1359
+ { title: t("order.desc"), value: "desc" },
1360
+ { title: t("order.asc"), value: "asc" },
1361
+ ], onSelect: (opt) => {
1362
+ const o = opt.value;
1363
+ setSortOrder(o);
1364
+ api.kv.set(`${KV_PREFIX}.order`, o);
1365
+ api.ui.toast({
1366
+ message: o === "desc" ? t("order.desc") : t("order.asc"),
1367
+ });
1368
+ dialog?.clear();
1369
+ } })));
1370
+ },
1371
+ },
1372
+ {
1373
+ title: "SubAgent Magazine: Scroll Mode",
1374
+ value: "subagent-scroll",
1375
+ description: "Set scroll mode (wheel / click)",
1376
+ slash: { name: "subagent-scroll" },
1377
+ onSelect: (dialog) => {
1378
+ const t = (k) => I18N[lang()][k] ?? k;
1379
+ dialog?.replace(() => (_jsx(api.ui.DialogSelect, { title: "Scroll Mode / \u6EDA\u52A8\u6A21\u5F0F", options: [
1380
+ { title: t("scroll.wheel"), value: "wheel" },
1381
+ { title: t("scroll.click"), value: "click" },
1382
+ ], onSelect: (opt) => {
1383
+ const m = opt.value;
1384
+ setScrollMode(m);
1385
+ api.kv.set(`${KV_PREFIX}.scroll_mode`, m);
1386
+ api.ui.toast({
1387
+ message: m === "wheel" ? t("scroll.wheel") : t("scroll.click"),
1388
+ });
1389
+ dialog?.clear();
1390
+ } })));
1391
+ },
1392
+ },
1232
1393
  {
1233
1394
  title: "SubAgent Magazine: Max Entries",
1234
1395
  value: "subagent-max",