opencode-subagent-magazine 1.1.2 → 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 +12 -4
- package/dist/_version.d.ts +1 -1
- package/dist/_version.js +1 -1
- package/dist/index.js +180 -21
- package/dist/tui.js +2038 -0
- package/package.json +6 -3
- package/src/_version.ts +1 -1
- package/src/index.tsx +198 -22
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
|
|
package/dist/_version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const PLUGIN_VERSION = "1.
|
|
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.
|
|
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() {
|
|
@@ -198,8 +208,7 @@ function safeErrorMsg(err) {
|
|
|
198
208
|
// ===================================================================
|
|
199
209
|
// Sidebar component
|
|
200
210
|
// ===================================================================
|
|
201
|
-
//
|
|
202
|
-
// 当用户在子 session 内部时,handleSessionEnd 可通过此缓存找到父 session 的 entries。
|
|
211
|
+
// 模块级缓存:各 session 的 entry 状态独立存储,不随当前视图切换而清除。
|
|
203
212
|
const globalEntryCache = new Map();
|
|
204
213
|
function SubAgentPanel(props) {
|
|
205
214
|
const t = (key) => I18N[props.lang()][key] ?? key;
|
|
@@ -283,8 +292,8 @@ function SubAgentPanel(props) {
|
|
|
283
292
|
const setEntryMap = (arg) => {
|
|
284
293
|
setEntryMapRaw((prev) => {
|
|
285
294
|
const next = typeof arg === "function" ? arg(prev) : arg;
|
|
286
|
-
//
|
|
287
|
-
//
|
|
295
|
+
// entry 状态落定(done/error)时立即持久化到 KV,跳过常规 debounce,
|
|
296
|
+
// 确保跨视图的状态一致性。
|
|
288
297
|
let needsImmediateFlush = false;
|
|
289
298
|
for (const [id, entry] of next) {
|
|
290
299
|
const prevEntry = prev.get(id);
|
|
@@ -305,7 +314,7 @@ function SubAgentPanel(props) {
|
|
|
305
314
|
else {
|
|
306
315
|
persistEntries(props.sessionId, next);
|
|
307
316
|
}
|
|
308
|
-
//
|
|
317
|
+
// 同步到模块级缓存,供其他视图读取当前 session 的最新状态
|
|
309
318
|
globalEntryCache.set(props.sessionId, new Map(next));
|
|
310
319
|
return next;
|
|
311
320
|
});
|
|
@@ -326,6 +335,8 @@ function SubAgentPanel(props) {
|
|
|
326
335
|
const [hoveredOpen, setHoveredOpen] = createSignal(undefined);
|
|
327
336
|
const [hoveredDismiss, setHoveredDismiss] = createSignal(undefined);
|
|
328
337
|
const [hoveredTop, setHoveredTop] = createSignal(false);
|
|
338
|
+
const [hoveredMoreAbove, setHoveredMoreAbove] = createSignal(false);
|
|
339
|
+
const [hoveredMoreBelow, setHoveredMoreBelow] = createSignal(false);
|
|
329
340
|
const [scrollOffset, setScrollOffset] = createSignal((() => { try {
|
|
330
341
|
return loadSessionData()[props.sessionId]?.scroll ?? 0;
|
|
331
342
|
}
|
|
@@ -678,21 +689,20 @@ function SubAgentPanel(props) {
|
|
|
678
689
|
}
|
|
679
690
|
return changed ? next : prev;
|
|
680
691
|
});
|
|
681
|
-
//
|
|
682
|
-
//
|
|
683
|
-
// 通过全局缓存(切换前保留的父 session entries)查找并更新,再同步回 KV。
|
|
692
|
+
// 当子代理所属的父 session 与当前视图不同时,通过模块级缓存定位
|
|
693
|
+
// 并更新父 session 的 entry 状态,随后写回 KV。
|
|
684
694
|
try {
|
|
685
695
|
const sessionObj = props.api.state.session.get(sid);
|
|
686
696
|
const parentSid = sessionObj?.parentID;
|
|
687
697
|
if (parentSid && parentSid !== props.sessionId) {
|
|
688
|
-
//
|
|
698
|
+
// 优先从模块级缓存获取父 session 的 entries,不受当前视图切换影响
|
|
689
699
|
const parentCache = globalEntryCache.get(parentSid);
|
|
690
700
|
const nowTs = Date.now();
|
|
691
701
|
let found = false;
|
|
692
702
|
if (parentCache) {
|
|
693
703
|
found = tryMatchAndUpdate(parentCache, sid, status, nowTs);
|
|
694
704
|
}
|
|
695
|
-
// 缓存未命中时回退到 KV
|
|
705
|
+
// 缓存未命中时回退到 KV 读取
|
|
696
706
|
if (!found) {
|
|
697
707
|
const data = loadSessionData();
|
|
698
708
|
const rec = data[parentSid];
|
|
@@ -700,14 +710,14 @@ function SubAgentPanel(props) {
|
|
|
700
710
|
const fallbackMap = new Map(rec.entries.map((e) => [e.id, e]));
|
|
701
711
|
found = tryMatchAndUpdate(fallbackMap, sid, status, nowTs);
|
|
702
712
|
if (found) {
|
|
703
|
-
//
|
|
713
|
+
// 回退命中后写入 KV 并回填缓存
|
|
704
714
|
data[parentSid] = { ...rec, ts: nowTs, entries: [...fallbackMap.values()] };
|
|
705
715
|
saveSessionData(data);
|
|
706
716
|
globalEntryCache.set(parentSid, fallbackMap);
|
|
707
717
|
}
|
|
708
718
|
}
|
|
709
719
|
}
|
|
710
|
-
//
|
|
720
|
+
// 将模块级缓存中的最新状态同步到 KV
|
|
711
721
|
if (found && parentCache) {
|
|
712
722
|
const data = loadSessionData();
|
|
713
723
|
data[parentSid] = { ...data[parentSid], ts: nowTs, entries: [...parentCache.values()] };
|
|
@@ -838,7 +848,7 @@ function SubAgentPanel(props) {
|
|
|
838
848
|
// scan uses setEntryMapRaw — ephemeral data, not persisted to kv.
|
|
839
849
|
// Only event-driven changes (handlePartUpdated, handleSessionEnd) persist.
|
|
840
850
|
setEntryMapRaw((prev) => {
|
|
841
|
-
//
|
|
851
|
+
// 优先从模块级缓存加载,KV 仅作缓存未命中时的回退
|
|
842
852
|
const next = switched
|
|
843
853
|
? new Map(globalEntryCache.get(sid) ?? loadEntries(sid))
|
|
844
854
|
: new Map(prev);
|
|
@@ -984,7 +994,11 @@ function SubAgentPanel(props) {
|
|
|
984
994
|
// ── derived signals ──
|
|
985
995
|
// Stable list — only changes when entryMap changes
|
|
986
996
|
const entryList = createMemo(() => {
|
|
987
|
-
|
|
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);
|
|
988
1002
|
});
|
|
989
1003
|
const max = props.maxEntries;
|
|
990
1004
|
const clampedOffset = createMemo(() => {
|
|
@@ -997,6 +1011,56 @@ function SubAgentPanel(props) {
|
|
|
997
1011
|
const visibleList = createMemo(() => entryList().slice(clampedOffset(), clampedOffset() + max()));
|
|
998
1012
|
const hiddenAbove = createMemo(() => clampedOffset());
|
|
999
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
|
+
});
|
|
1000
1064
|
const entries = createMemo(() => {
|
|
1001
1065
|
const nowVal = now();
|
|
1002
1066
|
return entryList().map((e) => ({
|
|
@@ -1109,6 +1173,8 @@ function SubAgentPanel(props) {
|
|
|
1109
1173
|
});
|
|
1110
1174
|
bump();
|
|
1111
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;
|
|
1112
1178
|
const total = entryList().length;
|
|
1113
1179
|
const m = max();
|
|
1114
1180
|
if (total <= m)
|
|
@@ -1122,7 +1188,29 @@ function SubAgentPanel(props) {
|
|
|
1122
1188
|
catch { }
|
|
1123
1189
|
return next;
|
|
1124
1190
|
});
|
|
1125
|
-
}, children: [_jsx(Show, { when: hiddenAbove() > 0, children:
|
|
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) => {
|
|
1126
1214
|
const isExpanded = () => expanded() === entry.id;
|
|
1127
1215
|
const isRunning = entry.status === "running";
|
|
1128
1216
|
const isError = entry.status === "error";
|
|
@@ -1180,13 +1268,40 @@ function SubAgentPanel(props) {
|
|
|
1180
1268
|
upsertEntry({ id: entry.id, title: entry.title, agent: entry.agent, prompt: entry.prompt, status: "done" });
|
|
1181
1269
|
}, children: _jsx("span", { style: { fg: hoveredDismiss() === entry.id ? pal().warning : pal().muted }, children: dismissLabel() }) })] }) })] }));
|
|
1182
1270
|
})() })] })] }));
|
|
1183
|
-
} }), _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: (() => {
|
|
1184
1272
|
const showMore = hiddenBelow() > 0;
|
|
1185
|
-
const showTop =
|
|
1273
|
+
const showTop = props.sortOrder() === "desc"
|
|
1274
|
+
? scrollOffset() > 0
|
|
1275
|
+
: entryList().length > max() && clampedOffset() < entryList().length - max();
|
|
1186
1276
|
const left = showMore ? ` \u2193 ${hiddenBelow()} ${t("scroll.more")}` : " ";
|
|
1187
|
-
const right =
|
|
1277
|
+
const right = props.sortOrder() === "desc"
|
|
1278
|
+
? `\u2191 ${t("scroll.top")}`
|
|
1279
|
+
: `\u2193 ${t("scroll.bottom")}`;
|
|
1188
1280
|
const pad = showTop ? Math.max(1, panelWidth() - visualWidth(left) - visualWidth(right)) : 0;
|
|
1189
|
-
return (_jsxs("box", { flexDirection: "row", children: [_jsx("text", {
|
|
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] }));
|
|
1190
1305
|
})() })] }) })] })] }));
|
|
1191
1306
|
}
|
|
1192
1307
|
function createSidebarSlot(api, sig) {
|
|
@@ -1195,7 +1310,7 @@ function createSidebarSlot(api, sig) {
|
|
|
1195
1310
|
slots: {
|
|
1196
1311
|
sidebar_content(ctx, input) {
|
|
1197
1312
|
sig.sessionId = input.session_id;
|
|
1198
|
-
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 }));
|
|
1199
1314
|
},
|
|
1200
1315
|
},
|
|
1201
1316
|
};
|
|
@@ -1207,7 +1322,9 @@ const tui = async (api) => {
|
|
|
1207
1322
|
const initialLang = stored === "zh" || stored === "en" ? stored : detectLang();
|
|
1208
1323
|
const [lang, setLang] = createSignal(initialLang);
|
|
1209
1324
|
const [maxEntries, setMaxEntries] = createSignal(parseInt(String(api.kv.get(`${KV_PREFIX}.max_entries`, "10")), 10) || 10);
|
|
1210
|
-
const
|
|
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: "" };
|
|
1211
1328
|
api.slots.register(createSidebarSlot(api, signals));
|
|
1212
1329
|
// ── slash command: /subagent-lang ──
|
|
1213
1330
|
api.command?.register(() => [
|
|
@@ -1231,6 +1348,48 @@ const tui = async (api) => {
|
|
|
1231
1348
|
} })));
|
|
1232
1349
|
},
|
|
1233
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
|
+
},
|
|
1234
1393
|
{
|
|
1235
1394
|
title: "SubAgent Magazine: Max Entries",
|
|
1236
1395
|
value: "subagent-max",
|