opencode-subagent-magazine 1.2.1 → 1.4.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 +7 -2
- package/dist/_version.d.ts +1 -1
- package/dist/_version.js +1 -1
- package/dist/index.js +265 -31
- package/dist/tui.js +474 -107
- package/package.json +1 -1
- package/src/_version.ts +1 -1
- package/src/index.tsx +264 -19
package/README.md
CHANGED
|
@@ -46,15 +46,17 @@
|
|
|
46
46
|
- **Token & 费用追踪**:每个子代理的 Token 消耗和费用汇总,标题栏全局显示
|
|
47
47
|
- **展开详情**:点击展开查看 agent 类型、状态、耗时、Token、费用、模型、进度,字段值右对齐
|
|
48
48
|
- **一键进入会话**:展开后点 `→ 进入会话` 跳转子代理的完整对话
|
|
49
|
+
- **会话 ID 展示**:展开明细显示子代理 Session ID,点击弹窗查看完整 ID 便于复制
|
|
49
50
|
- **手动标记完成**:展开区右侧 `- 标记完成` 按钮可手动终结卡住的僵尸条目;`/subagent-clear-running` 批量清理
|
|
50
51
|
- **状态字段显示**:展开区 `状态: 运行中/已完成/错误`,颜色与圆点一致,一目了然
|
|
51
52
|
- **折叠面板**:标题栏点击折叠/展开,状态持久化,重启后保持
|
|
52
53
|
- **滚动回顶/回底**:列表底部 `↑ 回顶` / `↓ 回底` 按钮一键跳转到最新条目位置,方向随排序自适应
|
|
53
54
|
- **可配置排序**:`/subagent-order` 支持「降序(最新在前)」和「升序(最早在前)」两种排列方式
|
|
54
55
|
- **翻页模式切换**:`/subagent-scroll` 可选「滚轮翻页」或「点击 more 翻页」,解决侧边栏与全局滚动冲突
|
|
55
|
-
- **TTL
|
|
56
|
+
- **TTL 自动清理**:可配置数据保留期限(3/7/14/30 天/无期限),每次访问自动续期,过期自动清除
|
|
57
|
+
- **手动清除条目**:`/subagent-clear-entries` 清除当前会话所有记录,防止历史条目扫描重建
|
|
56
58
|
- **语言适配**:支持 `/subagent-lang` 运行时切换中/英文,偏好持久化
|
|
57
|
-
- **斜杠命令**:`/subagent-lang` `/subagent-max` `/subagent-order` `/subagent-scroll` `/subagent-session` `/subagent-version` `/subagent-clear-running` 动态配置
|
|
59
|
+
- **斜杠命令**:`/subagent-lang` `/subagent-max` `/subagent-order` `/subagent-scroll` `/subagent-ttl` `/subagent-clear-entries` `/subagent-session` `/subagent-version` `/subagent-clear-running` 动态配置
|
|
58
60
|
|
|
59
61
|
---
|
|
60
62
|
|
|
@@ -107,6 +109,8 @@ npm install -g opencode-subagent-magazine@latest
|
|
|
107
109
|
| `/subagent-max` | 调整最大可见条目数 | 输入数字(默认 10),控制面板最多显示多少个子代理条目 |
|
|
108
110
|
| `/subagent-order` | 切换排序方式 | 选择「降序(最新在前)」或「升序(最早在前)」,即时重排并跳转到最新条目 |
|
|
109
111
|
| `/subagent-scroll` | 切换翻页模式 | 选择「滚轮翻页」或「点击 more 翻页」,解决与全局滚动冲突 |
|
|
112
|
+
| `/subagent-ttl` | 设置数据保留期限 | 选择 3 天 / 7 天 / 14 天 / 30 天 / 无期限,控制 KV 自动清理间隔 |
|
|
113
|
+
| `/subagent-clear-entries` | 清除当前会话记录 | 确认后删除所有子代理记录,阻止扫描重建历史条目 |
|
|
110
114
|
| `/subagent-session` | 查看当前会话 ID | 弹出当前 OpenCode 会话 ID |
|
|
111
115
|
| `/subagent-version` | 查看插件版本 | 弹出当前插件版本号 |
|
|
112
116
|
| `/subagent-clear-running` | 批量清理僵尸条目 | 一键将所有运行中的条目标记为完成,清理卡住的旧数据 |
|
|
@@ -125,6 +129,7 @@ npm install -g opencode-subagent-magazine@latest
|
|
|
125
129
|
| 滚轮 / 点击 `↑ more` `↓ more` | 翻页查看更多子代理(`/subagent-scroll` 切换翻页模式) |
|
|
126
130
|
| 点击 `↑ 回顶` / `↓ 回底` | 跳转到最新条目位置(方向随排序自适应) |
|
|
127
131
|
| 点击 `→ 进入会话` | 跳转子代理完整对话 |
|
|
132
|
+
| 点击 `ses_xxx… ⎘` | 弹窗显示完整 Session ID,可手动复制 |
|
|
128
133
|
| 点击 `- 标记完成` | 手动终结该条目(仅运行中时显示) |
|
|
129
134
|
|
|
130
135
|
### 4.3 状态颜色
|
package/dist/_version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const PLUGIN_VERSION = "1.
|
|
1
|
+
export declare const PLUGIN_VERSION = "1.4.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.4.0";
|
package/dist/index.js
CHANGED
|
@@ -17,6 +17,8 @@ const I18N = {
|
|
|
17
17
|
"error.label": "错误",
|
|
18
18
|
"model.label": "模型",
|
|
19
19
|
"todo.label": "进度",
|
|
20
|
+
"session.label": "会话 ID",
|
|
21
|
+
"session.toast.copy": "可手动复制上方 ID",
|
|
20
22
|
"open.label": "进入会话",
|
|
21
23
|
"cost.label": "费用",
|
|
22
24
|
"scroll.more": "更多",
|
|
@@ -30,6 +32,19 @@ const I18N = {
|
|
|
30
32
|
"order.asc": "升序(最早在前)",
|
|
31
33
|
"scroll.wheel": "滚轮翻页",
|
|
32
34
|
"scroll.click": "点击翻页",
|
|
35
|
+
"ttl.label": "清理周期",
|
|
36
|
+
"ttl.3d": "3 天",
|
|
37
|
+
"ttl.7d": "7 天",
|
|
38
|
+
"ttl.14d": "14 天",
|
|
39
|
+
"ttl.30d": "30 天",
|
|
40
|
+
"ttl.unlimited": "无期限",
|
|
41
|
+
"ttl.toast": "清理周期已设为 {n} 天",
|
|
42
|
+
"ttl.toast_unlimited": "清理周期已设为无期限",
|
|
43
|
+
"clear.title": "确认清除",
|
|
44
|
+
"clear.prompt": "确定清除当前会话所有子代理记录?此操作不可撤销。",
|
|
45
|
+
"clear.prompt_running": "当前有 {n} 个运行中的子代理,清除后将不可恢复。确定继续?",
|
|
46
|
+
"clear.done": "已清除 {n} 条子代理记录",
|
|
47
|
+
"clear.empty": "当前会话无子代理记录",
|
|
33
48
|
},
|
|
34
49
|
en: {
|
|
35
50
|
"panel.title": "SubAgent",
|
|
@@ -41,6 +56,8 @@ const I18N = {
|
|
|
41
56
|
"error.label": "error",
|
|
42
57
|
"model.label": "model",
|
|
43
58
|
"todo.label": "todo",
|
|
59
|
+
"session.label": "session ID",
|
|
60
|
+
"session.toast.copy": "Copy the ID above manually",
|
|
44
61
|
"open.label": "Open session",
|
|
45
62
|
"cost.label": "cost",
|
|
46
63
|
"scroll.more": "more",
|
|
@@ -54,6 +71,19 @@ const I18N = {
|
|
|
54
71
|
"order.asc": "Asc (oldest first)",
|
|
55
72
|
"scroll.wheel": "Wheel Scroll",
|
|
56
73
|
"scroll.click": "Click Scroll",
|
|
74
|
+
"ttl.label": "TTL (Time to Live)",
|
|
75
|
+
"ttl.3d": "3 days",
|
|
76
|
+
"ttl.7d": "7 days",
|
|
77
|
+
"ttl.14d": "14 days",
|
|
78
|
+
"ttl.30d": "30 days",
|
|
79
|
+
"ttl.unlimited": "Never",
|
|
80
|
+
"ttl.toast": "TTL set to {n} days",
|
|
81
|
+
"ttl.toast_unlimited": "TTL set to Never",
|
|
82
|
+
"clear.title": "Confirm",
|
|
83
|
+
"clear.prompt": "Clear all sub-agent records for this session? This cannot be undone.",
|
|
84
|
+
"clear.prompt_running": "{n} sub-agent(s) are still running. Clearing will discard them permanently. Continue?",
|
|
85
|
+
"clear.done": "Cleared {n} sub-agent record(s)",
|
|
86
|
+
"clear.empty": "No sub-agent records in this session",
|
|
57
87
|
},
|
|
58
88
|
};
|
|
59
89
|
function detectLang() {
|
|
@@ -210,11 +240,15 @@ function safeErrorMsg(err) {
|
|
|
210
240
|
// ===================================================================
|
|
211
241
|
// 模块级缓存:各 session 的 entry 状态独立存储,不随当前视图切换而清除。
|
|
212
242
|
const globalEntryCache = new Map();
|
|
243
|
+
// 模块级刷新信号:外部(如斜杠命令)触发清除后 +1,组件 scan 依赖它以重扫。
|
|
244
|
+
const [clearTick, setClearTick] = createSignal(0);
|
|
213
245
|
function SubAgentPanel(props) {
|
|
214
246
|
const t = (key) => I18N[props.lang()][key] ?? key;
|
|
215
247
|
// ── session data (single-key, true deletion on cleanup) ──
|
|
216
248
|
const SESSION_DATA_KEY = `${KV_PREFIX}.session_data`;
|
|
217
|
-
const
|
|
249
|
+
const ttlDaysRaw = parseInt(String(props.api.kv.get(`${KV_PREFIX}.ttl_days`, "3")), 10);
|
|
250
|
+
const ttlDays = Number.isNaN(ttlDaysRaw) ? 3 : ttlDaysRaw;
|
|
251
|
+
const TTL_MS = ttlDays * 24 * 60 * 60 * 1000;
|
|
218
252
|
const loadSessionData = () => {
|
|
219
253
|
try {
|
|
220
254
|
const raw = props.api.kv.get(SESSION_DATA_KEY, "{}");
|
|
@@ -230,13 +264,29 @@ function SubAgentPanel(props) {
|
|
|
230
264
|
}
|
|
231
265
|
catch { }
|
|
232
266
|
};
|
|
267
|
+
/** 将任意 session ID 解析为父会话 ID + 是否子会话。
|
|
268
|
+
* 通过 SDK session.get(sid).parentID 判断,无 parentID 即为主会话。 */
|
|
269
|
+
const resolveParent = (sid) => {
|
|
270
|
+
try {
|
|
271
|
+
const session = props.api.state.session.get(sid);
|
|
272
|
+
const parentID = session?.parentID;
|
|
273
|
+
if (parentID)
|
|
274
|
+
return { parentSid: parentID, isChild: true };
|
|
275
|
+
}
|
|
276
|
+
catch { }
|
|
277
|
+
return { parentSid: sid, isChild: false };
|
|
278
|
+
};
|
|
233
279
|
const loadEntries = (sid) => {
|
|
234
280
|
const m = new Map();
|
|
235
281
|
try {
|
|
236
|
-
const
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
282
|
+
const { parentSid, isChild } = resolveParent(sid);
|
|
283
|
+
const rec = loadSessionData()[parentSid];
|
|
284
|
+
if (rec) {
|
|
285
|
+
const source = isChild ? rec.children?.[sid]?.entries : rec.entries;
|
|
286
|
+
if (source) {
|
|
287
|
+
for (const e of source)
|
|
288
|
+
m.set(e.id, e);
|
|
289
|
+
}
|
|
240
290
|
}
|
|
241
291
|
}
|
|
242
292
|
catch { }
|
|
@@ -248,7 +298,19 @@ function SubAgentPanel(props) {
|
|
|
248
298
|
persistTimer = setTimeout(() => {
|
|
249
299
|
try {
|
|
250
300
|
const data = loadSessionData();
|
|
251
|
-
|
|
301
|
+
const { parentSid, isChild } = resolveParent(sid);
|
|
302
|
+
if (isChild) {
|
|
303
|
+
if (!data[parentSid])
|
|
304
|
+
data[parentSid] = { ts: Date.now(), entries: [], scroll: 0, expanded: "", children: {} };
|
|
305
|
+
if (!data[parentSid].children)
|
|
306
|
+
data[parentSid].children = {};
|
|
307
|
+
if (!data[parentSid].children[sid])
|
|
308
|
+
data[parentSid].children[sid] = { scroll: 0, expanded: "", entries: [] };
|
|
309
|
+
data[parentSid].children[sid] = { ...data[parentSid].children[sid], entries: [...entries.values()] };
|
|
310
|
+
}
|
|
311
|
+
else {
|
|
312
|
+
data[sid] = { ...data[sid], ts: Date.now(), entries: [...entries.values()], children: data[sid]?.children ?? {} };
|
|
313
|
+
}
|
|
252
314
|
saveSessionData(data);
|
|
253
315
|
}
|
|
254
316
|
catch { }
|
|
@@ -257,7 +319,19 @@ function SubAgentPanel(props) {
|
|
|
257
319
|
const persistScroll = (sid, scroll) => {
|
|
258
320
|
try {
|
|
259
321
|
const data = loadSessionData();
|
|
260
|
-
|
|
322
|
+
const { parentSid, isChild } = resolveParent(sid);
|
|
323
|
+
if (isChild) {
|
|
324
|
+
if (!data[parentSid])
|
|
325
|
+
data[parentSid] = { ts: Date.now(), entries: [], scroll: 0, expanded: "", children: {} };
|
|
326
|
+
if (!data[parentSid].children)
|
|
327
|
+
data[parentSid].children = {};
|
|
328
|
+
if (!data[parentSid].children[sid])
|
|
329
|
+
data[parentSid].children[sid] = { scroll: 0, expanded: "", entries: [] };
|
|
330
|
+
data[parentSid].children[sid] = { ...data[parentSid].children[sid], scroll };
|
|
331
|
+
}
|
|
332
|
+
else {
|
|
333
|
+
data[sid] = { ...data[sid], ts: Date.now(), scroll, children: data[sid]?.children ?? {} };
|
|
334
|
+
}
|
|
261
335
|
saveSessionData(data);
|
|
262
336
|
}
|
|
263
337
|
catch { }
|
|
@@ -265,12 +339,26 @@ function SubAgentPanel(props) {
|
|
|
265
339
|
const persistExpanded = (sid, expanded) => {
|
|
266
340
|
try {
|
|
267
341
|
const data = loadSessionData();
|
|
268
|
-
|
|
342
|
+
const { parentSid, isChild } = resolveParent(sid);
|
|
343
|
+
if (isChild) {
|
|
344
|
+
if (!data[parentSid])
|
|
345
|
+
data[parentSid] = { ts: Date.now(), entries: [], scroll: 0, expanded: "", children: {} };
|
|
346
|
+
if (!data[parentSid].children)
|
|
347
|
+
data[parentSid].children = {};
|
|
348
|
+
if (!data[parentSid].children[sid])
|
|
349
|
+
data[parentSid].children[sid] = { scroll: 0, expanded: "", entries: [] };
|
|
350
|
+
data[parentSid].children[sid] = { ...data[parentSid].children[sid], expanded };
|
|
351
|
+
}
|
|
352
|
+
else {
|
|
353
|
+
data[sid] = { ...data[sid], ts: Date.now(), expanded, children: data[sid]?.children ?? {} };
|
|
354
|
+
}
|
|
269
355
|
saveSessionData(data);
|
|
270
356
|
}
|
|
271
357
|
catch { }
|
|
272
358
|
};
|
|
273
359
|
const cleanupOldSessions = () => {
|
|
360
|
+
if (ttlDays <= 0)
|
|
361
|
+
return; // 无期限,跳过清理
|
|
274
362
|
try {
|
|
275
363
|
const data = loadSessionData();
|
|
276
364
|
const cutoff = Date.now() - TTL_MS;
|
|
@@ -306,7 +394,19 @@ function SubAgentPanel(props) {
|
|
|
306
394
|
clearTimeout(persistTimer);
|
|
307
395
|
try {
|
|
308
396
|
const data = loadSessionData();
|
|
309
|
-
|
|
397
|
+
const { parentSid, isChild } = resolveParent(props.sessionId);
|
|
398
|
+
if (isChild) {
|
|
399
|
+
if (!data[parentSid])
|
|
400
|
+
data[parentSid] = { ts: Date.now(), entries: [], scroll: 0, expanded: "", children: {} };
|
|
401
|
+
if (!data[parentSid].children)
|
|
402
|
+
data[parentSid].children = {};
|
|
403
|
+
if (!data[parentSid].children[props.sessionId])
|
|
404
|
+
data[parentSid].children[props.sessionId] = { scroll: 0, expanded: "", entries: [] };
|
|
405
|
+
data[parentSid].children[props.sessionId] = { ...data[parentSid].children[props.sessionId], entries: [...next.values()] };
|
|
406
|
+
}
|
|
407
|
+
else {
|
|
408
|
+
data[props.sessionId] = { ...data[props.sessionId], ts: Date.now(), entries: [...next.values()], children: data[props.sessionId]?.children ?? {} };
|
|
409
|
+
}
|
|
310
410
|
saveSessionData(data);
|
|
311
411
|
}
|
|
312
412
|
catch { }
|
|
@@ -326,23 +426,31 @@ function SubAgentPanel(props) {
|
|
|
326
426
|
catch {
|
|
327
427
|
return true;
|
|
328
428
|
} })());
|
|
329
|
-
const [expanded, setExpanded] = createSignal((() => {
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
429
|
+
const [expanded, setExpanded] = createSignal((() => {
|
|
430
|
+
try {
|
|
431
|
+
const { parentSid, isChild } = resolveParent(props.sessionId);
|
|
432
|
+
const rec = loadSessionData()[parentSid];
|
|
433
|
+
if (rec)
|
|
434
|
+
return isChild ? rec.children?.[props.sessionId]?.expanded || undefined : rec.expanded || undefined;
|
|
435
|
+
}
|
|
436
|
+
catch { }
|
|
333
437
|
return undefined;
|
|
334
|
-
}
|
|
438
|
+
})());
|
|
335
439
|
const [hoveredOpen, setHoveredOpen] = createSignal(undefined);
|
|
336
440
|
const [hoveredDismiss, setHoveredDismiss] = createSignal(undefined);
|
|
337
441
|
const [hoveredTop, setHoveredTop] = createSignal(false);
|
|
338
442
|
const [hoveredMoreAbove, setHoveredMoreAbove] = createSignal(false);
|
|
339
443
|
const [hoveredMoreBelow, setHoveredMoreBelow] = createSignal(false);
|
|
340
|
-
const [scrollOffset, setScrollOffset] = createSignal((() => {
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
444
|
+
const [scrollOffset, setScrollOffset] = createSignal((() => {
|
|
445
|
+
try {
|
|
446
|
+
const { parentSid, isChild } = resolveParent(props.sessionId);
|
|
447
|
+
const rec = loadSessionData()[parentSid];
|
|
448
|
+
return isChild ? rec?.children?.[props.sessionId]?.scroll ?? 0 : rec?.scroll ?? 0;
|
|
449
|
+
}
|
|
450
|
+
catch {
|
|
451
|
+
return 0;
|
|
452
|
+
}
|
|
453
|
+
})());
|
|
346
454
|
const [now, setNow] = createSignal(Date.now());
|
|
347
455
|
const [renderTick, setRenderTick] = createSignal(0);
|
|
348
456
|
let boxEl;
|
|
@@ -835,23 +943,40 @@ function SubAgentPanel(props) {
|
|
|
835
943
|
// On session change: load from kv (entries survive component unmount), then scan+merge.
|
|
836
944
|
// On same session: only scan+merge (keep event‑driven running entries).
|
|
837
945
|
let lastSid = props.sessionId;
|
|
946
|
+
let lastTick = 0;
|
|
838
947
|
createEffect(() => {
|
|
839
948
|
const sid = props.sessionId;
|
|
840
949
|
const switched = sid !== lastSid;
|
|
841
950
|
lastSid = sid;
|
|
951
|
+
const tick = clearTick(); // 外部触发清除时 +1,effect 重跑
|
|
952
|
+
const forceReload = tick !== lastTick && !switched;
|
|
953
|
+
lastTick = tick;
|
|
842
954
|
const t = setTimeout(() => {
|
|
843
955
|
untrack(() => {
|
|
844
956
|
if (switched) {
|
|
845
|
-
const
|
|
957
|
+
const { parentSid, isChild } = resolveParent(sid);
|
|
958
|
+
const data = loadSessionData();
|
|
959
|
+
const saved = isChild
|
|
960
|
+
? data[parentSid]?.children?.[sid]?.scroll ?? 0
|
|
961
|
+
: data[sid]?.scroll ?? 0;
|
|
846
962
|
setScrollOffset(saved);
|
|
963
|
+
// 刷新父会话的访问时间 TTL,防止活跃会话的数据过期
|
|
964
|
+
if (!isChild && data[sid]?.entries?.length) {
|
|
965
|
+
data[sid].ts = Date.now();
|
|
966
|
+
saveSessionData(data);
|
|
967
|
+
}
|
|
847
968
|
}
|
|
848
969
|
// scan uses setEntryMapRaw — ephemeral data, not persisted to kv.
|
|
849
970
|
// Only event-driven changes (handlePartUpdated, handleSessionEnd) persist.
|
|
850
971
|
setEntryMapRaw((prev) => {
|
|
851
972
|
// 优先从模块级缓存加载,KV 仅作缓存未命中时的回退
|
|
852
|
-
const next = switched
|
|
973
|
+
const next = (switched || forceReload)
|
|
853
974
|
? new Map(globalEntryCache.get(sid) ?? loadEntries(sid))
|
|
854
975
|
: new Map(prev);
|
|
976
|
+
// 从 KV 加载当前会话的清除名单,扫描时跳过被手动清除的历史条目
|
|
977
|
+
const { parentSid: scanPSid, isChild: scanChild } = resolveParent(sid);
|
|
978
|
+
const scanRec = loadSessionData()[scanPSid];
|
|
979
|
+
const clearedIds = new Set(scanChild ? scanRec?.children?.[sid]?.clearedIds : scanRec?.clearedIds);
|
|
855
980
|
try {
|
|
856
981
|
const msgs = props.api.state.session.messages(sid);
|
|
857
982
|
if (msgs && msgs.length) {
|
|
@@ -871,6 +996,9 @@ function SubAgentPanel(props) {
|
|
|
871
996
|
const st = part.state;
|
|
872
997
|
const rawStatus = String(st?.status ?? "");
|
|
873
998
|
const exists = next.get(id);
|
|
999
|
+
// 已手动清除的条目:scan 发现但不在内存 → 跳过重建
|
|
1000
|
+
if (!exists && clearedIds.has(id))
|
|
1001
|
+
continue;
|
|
874
1002
|
// Only create entries for tool calls that entered execution.
|
|
875
1003
|
// "pending" / empty: skip new entries; allow heuristics for existing ones below.
|
|
876
1004
|
if ((rawStatus === "pending" || rawStatus === "") && !exists)
|
|
@@ -1103,7 +1231,7 @@ function SubAgentPanel(props) {
|
|
|
1103
1231
|
const expandedMaxLabelW = createMemo(() => {
|
|
1104
1232
|
const labels = [
|
|
1105
1233
|
t("agent.label"), t("status.label"), t("time.label"), t("tokens.label"),
|
|
1106
|
-
t("error.label"), t("cost.label"), t("model.label"), t("todo.label"),
|
|
1234
|
+
t("error.label"), t("cost.label"), t("model.label"), t("todo.label"), t("session.label"),
|
|
1107
1235
|
];
|
|
1108
1236
|
return Math.max(...labels.map(l => visualWidth(l + ": ")));
|
|
1109
1237
|
});
|
|
@@ -1254,7 +1382,15 @@ function SubAgentPanel(props) {
|
|
|
1254
1382
|
const pad = Math.max(0, max - visualWidth(truncated));
|
|
1255
1383
|
return truncated + " ".repeat(pad);
|
|
1256
1384
|
};
|
|
1257
|
-
return (_jsxs(_Fragment, { children: [_jsxs("text", { onMouseUp: () => toggleExpand(entry.id), children: [_jsx("span", { style: { fg: pal().muted }, children: isExpanded() ? "\u25bc" : "\u25b6" }), " ", _jsx("span", { style: { fg: statusColor() }, children: statusDot() }), " ", _jsx("span", { style: { fg: pal().text }, children: labelText() }), timeText() ? (_jsxs(_Fragment, { children: [" ", _jsx("span", { style: { fg: timeColor() }, children: timeText() })] })) : null, tokenText() ? (_jsx("span", { style: { fg: pal().muted }, children: tokenText() })) : null] }), _jsxs(Show, { when: isExpanded(), children: [_jsxs("text", { children: [" ", _jsxs("span", { style: { fg: pal().primary }, children: [t("agent.label"), ": "] }), _jsx("span", { style: { fg: pal().muted }, children: " ".repeat(expandedPad(t("agent.label"))) }), _jsx("span", { style: { fg: pal().muted }, children: entry.agent })] }), _jsxs("text", { children: [" ", _jsxs("span", { style: { fg: pal().primary }, children: [t("status.label"), ": "] }), _jsx("span", { style: { fg: pal().muted }, children: " ".repeat(expandedPad(t("status.label"))) }), _jsx("span", { style: { fg: isRunning ? pal().warning : isError ? pal().error : pal().success }, children: isRunning ? t("status.running") : isError ? t("status.error") : t("status.done") })] }), _jsx(Show, { when: elapsed() >= 2000 || entry.endedAt !== undefined, children: _jsxs("text", { children: [" ", _jsxs("span", { style: { fg: pal().primary }, children: [t("time.label"), ": "] }), _jsx("span", { style: { fg: pal().muted }, children: " ".repeat(expandedPad(t("time.label"))) }), _jsx("span", { style: { fg: pal().muted }, children: fmtDurationShort(elapsed(), isRunning) })] }) }), _jsx(Show, { when: entry.tokens !== undefined, children: _jsxs("text", { children: [" ", _jsxs("span", { style: { fg: pal().primary }, children: [t("tokens.label"), ": "] }), _jsx("span", { style: { fg: pal().muted }, children: " ".repeat(expandedPad(t("tokens.label"))) }), _jsx("span", { style: { fg: pal().muted }, children: fmtTokens(entry.tokens) })] }) }), _jsx(Show, { when: entry.error, children: _jsxs("text", { children: [" ", _jsxs("span", { style: { fg: pal().error }, children: [t("error.label"), ": "] }), _jsx("span", { style: { fg: pal().muted }, children: " ".repeat(expandedPad(t("error.label"))) }), _jsx("span", { style: { fg: pal().error }, children: truncate(String(entry.error), expandedValAvail()) })] }) }), _jsx(Show, { when: entry.cost !== undefined, children: _jsxs("text", { children: [" ", _jsxs("span", { style: { fg: pal().primary }, children: [t("cost.label"), ": "] }), _jsx("span", { style: { fg: pal().muted }, children: " ".repeat(expandedPad(t("cost.label"))) }), _jsxs("span", { style: { fg: pal().muted }, children: ["$", entry.cost.toFixed(4)] })] }) }), _jsx(Show, { when: entry.model, children: _jsxs("text", { children: [" ", _jsxs("span", { style: { fg: pal().primary }, children: [t("model.label"), ": "] }), _jsx("span", { style: { fg: pal().muted }, children: " ".repeat(expandedPad(t("model.label"))) }), _jsx("span", { style: { fg: pal().muted }, children: truncate(entry.model, expandedValAvail()) })] }) }), _jsx(Show, { when: entry.todoTotal !== undefined, children: _jsxs("text", { children: [" ", _jsxs("span", { style: { fg: pal().primary }, children: [t("todo.label"), ": "] }), _jsx("span", { style: { fg: pal().muted }, children: " ".repeat(expandedPad(t("todo.label"))) }), _jsxs("span", { style: { fg: pal().muted }, children: [entry.todoDone, "/", entry.todoTotal] })] }) }), _jsx(Show, { when: entry.sessionId
|
|
1385
|
+
return (_jsxs(_Fragment, { children: [_jsxs("text", { onMouseUp: () => toggleExpand(entry.id), children: [_jsx("span", { style: { fg: pal().muted }, children: isExpanded() ? "\u25bc" : "\u25b6" }), " ", _jsx("span", { style: { fg: statusColor() }, children: statusDot() }), " ", _jsx("span", { style: { fg: pal().text }, children: labelText() }), timeText() ? (_jsxs(_Fragment, { children: [" ", _jsx("span", { style: { fg: timeColor() }, children: timeText() })] })) : null, tokenText() ? (_jsx("span", { style: { fg: pal().muted }, children: tokenText() })) : null] }), _jsxs(Show, { when: isExpanded(), children: [_jsxs("text", { children: [" ", _jsxs("span", { style: { fg: pal().primary }, children: [t("agent.label"), ": "] }), _jsx("span", { style: { fg: pal().muted }, children: " ".repeat(expandedPad(t("agent.label"))) }), _jsx("span", { style: { fg: pal().muted }, children: entry.agent })] }), _jsxs("text", { children: [" ", _jsxs("span", { style: { fg: pal().primary }, children: [t("status.label"), ": "] }), _jsx("span", { style: { fg: pal().muted }, children: " ".repeat(expandedPad(t("status.label"))) }), _jsx("span", { style: { fg: isRunning ? pal().warning : isError ? pal().error : pal().success }, children: isRunning ? t("status.running") : isError ? t("status.error") : t("status.done") })] }), _jsx(Show, { when: elapsed() >= 2000 || entry.endedAt !== undefined, children: _jsxs("text", { children: [" ", _jsxs("span", { style: { fg: pal().primary }, children: [t("time.label"), ": "] }), _jsx("span", { style: { fg: pal().muted }, children: " ".repeat(expandedPad(t("time.label"))) }), _jsx("span", { style: { fg: pal().muted }, children: fmtDurationShort(elapsed(), isRunning) })] }) }), _jsx(Show, { when: entry.tokens !== undefined, children: _jsxs("text", { children: [" ", _jsxs("span", { style: { fg: pal().primary }, children: [t("tokens.label"), ": "] }), _jsx("span", { style: { fg: pal().muted }, children: " ".repeat(expandedPad(t("tokens.label"))) }), _jsx("span", { style: { fg: pal().muted }, children: fmtTokens(entry.tokens) })] }) }), _jsx(Show, { when: entry.error, children: _jsxs("text", { children: [" ", _jsxs("span", { style: { fg: pal().error }, children: [t("error.label"), ": "] }), _jsx("span", { style: { fg: pal().muted }, children: " ".repeat(expandedPad(t("error.label"))) }), _jsx("span", { style: { fg: pal().error }, children: truncate(String(entry.error), expandedValAvail()) })] }) }), _jsx(Show, { when: entry.cost !== undefined, children: _jsxs("text", { children: [" ", _jsxs("span", { style: { fg: pal().primary }, children: [t("cost.label"), ": "] }), _jsx("span", { style: { fg: pal().muted }, children: " ".repeat(expandedPad(t("cost.label"))) }), _jsxs("span", { style: { fg: pal().muted }, children: ["$", entry.cost.toFixed(4)] })] }) }), _jsx(Show, { when: entry.model, children: _jsxs("text", { children: [" ", _jsxs("span", { style: { fg: pal().primary }, children: [t("model.label"), ": "] }), _jsx("span", { style: { fg: pal().muted }, children: " ".repeat(expandedPad(t("model.label"))) }), _jsx("span", { style: { fg: pal().muted }, children: truncate(entry.model, expandedValAvail()) })] }) }), _jsx(Show, { when: entry.todoTotal !== undefined, children: _jsxs("text", { children: [" ", _jsxs("span", { style: { fg: pal().primary }, children: [t("todo.label"), ": "] }), _jsx("span", { style: { fg: pal().muted }, children: " ".repeat(expandedPad(t("todo.label"))) }), _jsxs("span", { style: { fg: pal().muted }, children: [entry.todoDone, "/", entry.todoTotal] })] }) }), _jsx(Show, { when: entry.sessionId, children: _jsxs("text", { onMouseUp: () => {
|
|
1386
|
+
if (entry.sessionId) {
|
|
1387
|
+
props.api.ui.toast({
|
|
1388
|
+
title: entry.title || entry.agent,
|
|
1389
|
+
message: `${entry.sessionId}\n\n${t("session.toast.copy")}`,
|
|
1390
|
+
duration: 8000,
|
|
1391
|
+
});
|
|
1392
|
+
}
|
|
1393
|
+
}, children: [" ", _jsxs("span", { style: { fg: pal().primary }, children: [t("session.label"), ": "] }), _jsx("span", { style: { fg: pal().muted }, children: " ".repeat(expandedPad(t("session.label"))) }), _jsx("span", { style: { fg: pal().muted }, children: truncate(entry.sessionId, expandedValAvail() - visualWidth(" ⎘")) }), _jsx("span", { style: { fg: pal().warning }, children: " \u2398" })] }) }), _jsx(Show, { when: entry.sessionId || isRunning, children: (() => {
|
|
1258
1394
|
const dismissLabel = () => `- ${t("dismiss.label")}`;
|
|
1259
1395
|
const openPrefix = () => " \u2192 ";
|
|
1260
1396
|
const openFull = () => entry.sessionId ? openPrefix() + t("open.label") : "";
|
|
@@ -1431,7 +1567,8 @@ const tui = async (api) => {
|
|
|
1431
1567
|
description: "Mark all running sub-agent entries as done (for stuck/zombie entries)",
|
|
1432
1568
|
slash: { name: "subagent-clear-running" },
|
|
1433
1569
|
onSelect: (dialog) => {
|
|
1434
|
-
const
|
|
1570
|
+
const sid = signals.sessionId;
|
|
1571
|
+
const entries = globalEntryCache.get(sid);
|
|
1435
1572
|
if (!entries || entries.size === 0) {
|
|
1436
1573
|
const msg = signals.lang() === "zh" ? "暂无子代理条目" : "No sub-agent entries found";
|
|
1437
1574
|
api.ui.toast({ message: msg });
|
|
@@ -1450,12 +1587,26 @@ const tui = async (api) => {
|
|
|
1450
1587
|
// 立即写 KV
|
|
1451
1588
|
try {
|
|
1452
1589
|
const data = JSON.parse(String(api.kv.get(`${KV_PREFIX}.session_data`, "{}")));
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1590
|
+
const sessionObj = api.state.session.get(sid);
|
|
1591
|
+
const parentID = sessionObj?.parentID;
|
|
1592
|
+
if (parentID) {
|
|
1593
|
+
if (!data[parentID])
|
|
1594
|
+
data[parentID] = { ts: Date.now(), entries: [], scroll: 0, expanded: "", children: {} };
|
|
1595
|
+
if (!data[parentID].children)
|
|
1596
|
+
data[parentID].children = {};
|
|
1597
|
+
if (!data[parentID].children[sid])
|
|
1598
|
+
data[parentID].children[sid] = { scroll: 0, expanded: "", entries: [] };
|
|
1599
|
+
data[parentID].children[sid] = { ...data[parentID].children[sid], entries: [...entries.values()] };
|
|
1600
|
+
}
|
|
1601
|
+
else {
|
|
1602
|
+
data[sid] = {
|
|
1603
|
+
ts: Date.now(),
|
|
1604
|
+
entries: [...entries.values()],
|
|
1605
|
+
scroll: data[sid]?.scroll ?? 0,
|
|
1606
|
+
expanded: data[sid]?.expanded ?? "",
|
|
1607
|
+
children: data[sid]?.children ?? {},
|
|
1608
|
+
};
|
|
1609
|
+
}
|
|
1459
1610
|
api.kv.set(`${KV_PREFIX}.session_data`, JSON.stringify(data));
|
|
1460
1611
|
}
|
|
1461
1612
|
catch { }
|
|
@@ -1473,6 +1624,89 @@ const tui = async (api) => {
|
|
|
1473
1624
|
dialog?.clear();
|
|
1474
1625
|
},
|
|
1475
1626
|
},
|
|
1627
|
+
{
|
|
1628
|
+
title: "SubAgent Magazine: TTL",
|
|
1629
|
+
value: "subagent-ttl",
|
|
1630
|
+
description: "Set session data retention period (days before auto-cleanup)",
|
|
1631
|
+
slash: { name: "subagent-ttl" },
|
|
1632
|
+
onSelect: (dialog) => {
|
|
1633
|
+
const t = (k) => I18N[signals.lang()][k] ?? k;
|
|
1634
|
+
const curRaw = parseInt(String(api.kv.get(`${KV_PREFIX}.ttl_days`, "3")), 10);
|
|
1635
|
+
const curDays = Number.isNaN(curRaw) ? 3 : curRaw;
|
|
1636
|
+
const curLabel = curDays === 0 ? t("ttl.unlimited") : `${curDays}d`;
|
|
1637
|
+
dialog?.replace(() => (_jsx(api.ui.DialogSelect, { title: `${t("ttl.label")} (${curLabel})`, options: [
|
|
1638
|
+
{ title: t("ttl.3d"), value: "3" },
|
|
1639
|
+
{ title: t("ttl.7d"), value: "7" },
|
|
1640
|
+
{ title: t("ttl.14d"), value: "14" },
|
|
1641
|
+
{ title: t("ttl.30d"), value: "30" },
|
|
1642
|
+
{ title: t("ttl.unlimited"), value: "0" },
|
|
1643
|
+
], onSelect: (opt) => {
|
|
1644
|
+
const days = parseInt(opt.value, 10);
|
|
1645
|
+
api.kv.set(`${KV_PREFIX}.ttl_days`, String(days));
|
|
1646
|
+
const msg = days === 0 ? t("ttl.toast_unlimited") : t("ttl.toast").replace("{n}", String(days));
|
|
1647
|
+
api.ui.toast({ message: msg });
|
|
1648
|
+
dialog?.clear();
|
|
1649
|
+
} })));
|
|
1650
|
+
},
|
|
1651
|
+
},
|
|
1652
|
+
{
|
|
1653
|
+
title: "SubAgent Magazine: Clear Entries",
|
|
1654
|
+
value: "subagent-clear-entries",
|
|
1655
|
+
description: "Delete all sub-agent records for the current session (cannot be undone)",
|
|
1656
|
+
slash: { name: "subagent-clear-entries" },
|
|
1657
|
+
onSelect: (dialog) => {
|
|
1658
|
+
const t = (k) => I18N[signals.lang()][k] ?? k;
|
|
1659
|
+
const sid = signals.sessionId;
|
|
1660
|
+
const sessionObj = api.state.session.get(sid);
|
|
1661
|
+
const parentID = sessionObj?.parentID;
|
|
1662
|
+
// 检查是否存在运行中的条目
|
|
1663
|
+
const cached = globalEntryCache.get(sid);
|
|
1664
|
+
let runningCount = 0;
|
|
1665
|
+
if (cached) {
|
|
1666
|
+
for (const [, e] of cached) {
|
|
1667
|
+
if (e.status === "running")
|
|
1668
|
+
runningCount++;
|
|
1669
|
+
}
|
|
1670
|
+
}
|
|
1671
|
+
const msg = runningCount > 0
|
|
1672
|
+
? t("clear.prompt_running").replace("{n}", String(runningCount))
|
|
1673
|
+
: t("clear.prompt");
|
|
1674
|
+
dialog?.replace(() => (_jsx(api.ui.DialogConfirm, { title: t("clear.title"), message: msg, onConfirm: () => {
|
|
1675
|
+
try {
|
|
1676
|
+
const data = JSON.parse(String(api.kv.get(`${KV_PREFIX}.session_data`, "{}")));
|
|
1677
|
+
let count = 0;
|
|
1678
|
+
if (parentID) {
|
|
1679
|
+
if (data[parentID]?.children?.[sid]) {
|
|
1680
|
+
const child = data[parentID].children[sid];
|
|
1681
|
+
const ids = child.entries?.map((e) => e.id) ?? [];
|
|
1682
|
+
count = ids.length;
|
|
1683
|
+
child.entries = [];
|
|
1684
|
+
child.scroll = 0;
|
|
1685
|
+
child.expanded = "";
|
|
1686
|
+
child.clearedIds = [...new Set([...(child.clearedIds ?? []), ...ids])];
|
|
1687
|
+
}
|
|
1688
|
+
}
|
|
1689
|
+
else {
|
|
1690
|
+
count = data[sid]?.entries?.length ?? 0;
|
|
1691
|
+
if (data[sid]) {
|
|
1692
|
+
const ids = data[sid].entries?.map((e) => e.id) ?? [];
|
|
1693
|
+
data[sid].entries = [];
|
|
1694
|
+
data[sid].scroll = 0;
|
|
1695
|
+
data[sid].expanded = "";
|
|
1696
|
+
data[sid].clearedIds = [...new Set([...(data[sid].clearedIds ?? []), ...ids])];
|
|
1697
|
+
}
|
|
1698
|
+
}
|
|
1699
|
+
api.kv.set(`${KV_PREFIX}.session_data`, JSON.stringify(data));
|
|
1700
|
+
globalEntryCache.delete(sid);
|
|
1701
|
+
setClearTick((v) => v + 1);
|
|
1702
|
+
const msg = t("clear.done").replace("{n}", String(count));
|
|
1703
|
+
api.ui.toast({ message: msg });
|
|
1704
|
+
}
|
|
1705
|
+
catch { }
|
|
1706
|
+
dialog?.clear();
|
|
1707
|
+
} })));
|
|
1708
|
+
},
|
|
1709
|
+
},
|
|
1476
1710
|
]);
|
|
1477
1711
|
};
|
|
1478
1712
|
const mod = {
|