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/package.json
CHANGED
package/src/_version.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// auto-generated
|
|
2
|
-
export const PLUGIN_VERSION="1.
|
|
2
|
+
export const PLUGIN_VERSION="1.4.0";
|
package/src/index.tsx
CHANGED
|
@@ -66,6 +66,8 @@ const I18N: Record<Lang, Record<string, string>> = {
|
|
|
66
66
|
"error.label": "错误",
|
|
67
67
|
"model.label": "模型",
|
|
68
68
|
"todo.label": "进度",
|
|
69
|
+
"session.label": "会话 ID",
|
|
70
|
+
"session.toast.copy": "可手动复制上方 ID",
|
|
69
71
|
"open.label": "进入会话",
|
|
70
72
|
"cost.label": "费用",
|
|
71
73
|
"scroll.more": "更多",
|
|
@@ -79,6 +81,19 @@ const I18N: Record<Lang, Record<string, string>> = {
|
|
|
79
81
|
"order.asc": "升序(最早在前)",
|
|
80
82
|
"scroll.wheel": "滚轮翻页",
|
|
81
83
|
"scroll.click": "点击翻页",
|
|
84
|
+
"ttl.label": "清理周期",
|
|
85
|
+
"ttl.3d": "3 天",
|
|
86
|
+
"ttl.7d": "7 天",
|
|
87
|
+
"ttl.14d": "14 天",
|
|
88
|
+
"ttl.30d": "30 天",
|
|
89
|
+
"ttl.unlimited": "无期限",
|
|
90
|
+
"ttl.toast": "清理周期已设为 {n} 天",
|
|
91
|
+
"ttl.toast_unlimited": "清理周期已设为无期限",
|
|
92
|
+
"clear.title": "确认清除",
|
|
93
|
+
"clear.prompt": "确定清除当前会话所有子代理记录?此操作不可撤销。",
|
|
94
|
+
"clear.prompt_running": "当前有 {n} 个运行中的子代理,清除后将不可恢复。确定继续?",
|
|
95
|
+
"clear.done": "已清除 {n} 条子代理记录",
|
|
96
|
+
"clear.empty": "当前会话无子代理记录",
|
|
82
97
|
},
|
|
83
98
|
en: {
|
|
84
99
|
"panel.title": "SubAgent",
|
|
@@ -90,6 +105,8 @@ const I18N: Record<Lang, Record<string, string>> = {
|
|
|
90
105
|
"error.label": "error",
|
|
91
106
|
"model.label": "model",
|
|
92
107
|
"todo.label": "todo",
|
|
108
|
+
"session.label": "session ID",
|
|
109
|
+
"session.toast.copy": "Copy the ID above manually",
|
|
93
110
|
"open.label": "Open session",
|
|
94
111
|
"cost.label": "cost",
|
|
95
112
|
"scroll.more": "more",
|
|
@@ -103,6 +120,19 @@ const I18N: Record<Lang, Record<string, string>> = {
|
|
|
103
120
|
"order.asc": "Asc (oldest first)",
|
|
104
121
|
"scroll.wheel": "Wheel Scroll",
|
|
105
122
|
"scroll.click": "Click Scroll",
|
|
123
|
+
"ttl.label": "TTL (Time to Live)",
|
|
124
|
+
"ttl.3d": "3 days",
|
|
125
|
+
"ttl.7d": "7 days",
|
|
126
|
+
"ttl.14d": "14 days",
|
|
127
|
+
"ttl.30d": "30 days",
|
|
128
|
+
"ttl.unlimited": "Never",
|
|
129
|
+
"ttl.toast": "TTL set to {n} days",
|
|
130
|
+
"ttl.toast_unlimited": "TTL set to Never",
|
|
131
|
+
"clear.title": "Confirm",
|
|
132
|
+
"clear.prompt": "Clear all sub-agent records for this session? This cannot be undone.",
|
|
133
|
+
"clear.prompt_running": "{n} sub-agent(s) are still running. Clearing will discard them permanently. Continue?",
|
|
134
|
+
"clear.done": "Cleared {n} sub-agent record(s)",
|
|
135
|
+
"clear.empty": "No sub-agent records in this session",
|
|
106
136
|
},
|
|
107
137
|
}
|
|
108
138
|
|
|
@@ -262,6 +292,9 @@ function safeErrorMsg(err: unknown): string {
|
|
|
262
292
|
// 模块级缓存:各 session 的 entry 状态独立存储,不随当前视图切换而清除。
|
|
263
293
|
const globalEntryCache = new Map<string, Map<string, SubEntry>>()
|
|
264
294
|
|
|
295
|
+
// 模块级刷新信号:外部(如斜杠命令)触发清除后 +1,组件 scan 依赖它以重扫。
|
|
296
|
+
const [clearTick, setClearTick] = createSignal(0)
|
|
297
|
+
|
|
265
298
|
function SubAgentPanel(props: {
|
|
266
299
|
theme: TuiThemeCurrent
|
|
267
300
|
api: TuiPluginApi
|
|
@@ -275,13 +308,24 @@ function SubAgentPanel(props: {
|
|
|
275
308
|
|
|
276
309
|
// ── session data (single-key, true deletion on cleanup) ──
|
|
277
310
|
const SESSION_DATA_KEY = `${KV_PREFIX}.session_data`
|
|
278
|
-
const
|
|
311
|
+
const ttlDaysRaw = parseInt(String(props.api.kv.get(`${KV_PREFIX}.ttl_days`, "3")), 10)
|
|
312
|
+
const ttlDays = Number.isNaN(ttlDaysRaw) ? 3 : ttlDaysRaw
|
|
313
|
+
const TTL_MS = ttlDays * 24 * 60 * 60 * 1000
|
|
314
|
+
|
|
315
|
+
interface ChildRecord {
|
|
316
|
+
scroll: number
|
|
317
|
+
expanded: string
|
|
318
|
+
entries: SubEntry[]
|
|
319
|
+
clearedIds?: string[]
|
|
320
|
+
}
|
|
279
321
|
|
|
280
322
|
interface SessionRecord {
|
|
281
323
|
ts: number
|
|
282
324
|
entries: SubEntry[]
|
|
283
325
|
scroll: number
|
|
284
326
|
expanded: string
|
|
327
|
+
children: Record<string, ChildRecord>
|
|
328
|
+
clearedIds?: string[]
|
|
285
329
|
}
|
|
286
330
|
|
|
287
331
|
const loadSessionData = (): Record<string, SessionRecord> => {
|
|
@@ -295,12 +339,27 @@ function SubAgentPanel(props: {
|
|
|
295
339
|
try { props.api.kv.set(SESSION_DATA_KEY, JSON.stringify(data)) } catch {}
|
|
296
340
|
}
|
|
297
341
|
|
|
342
|
+
/** 将任意 session ID 解析为父会话 ID + 是否子会话。
|
|
343
|
+
* 通过 SDK session.get(sid).parentID 判断,无 parentID 即为主会话。 */
|
|
344
|
+
const resolveParent = (sid: string): { parentSid: string; isChild: boolean } => {
|
|
345
|
+
try {
|
|
346
|
+
const session = props.api.state.session.get(sid)
|
|
347
|
+
const parentID = (session as any)?.parentID as string | undefined
|
|
348
|
+
if (parentID) return { parentSid: parentID, isChild: true }
|
|
349
|
+
} catch {}
|
|
350
|
+
return { parentSid: sid, isChild: false }
|
|
351
|
+
}
|
|
352
|
+
|
|
298
353
|
const loadEntries = (sid: string): Map<string, SubEntry> => {
|
|
299
354
|
const m = new Map<string, SubEntry>()
|
|
300
355
|
try {
|
|
301
|
-
const
|
|
302
|
-
|
|
303
|
-
|
|
356
|
+
const { parentSid, isChild } = resolveParent(sid)
|
|
357
|
+
const rec = loadSessionData()[parentSid]
|
|
358
|
+
if (rec) {
|
|
359
|
+
const source = isChild ? rec.children?.[sid]?.entries : rec.entries
|
|
360
|
+
if (source) {
|
|
361
|
+
for (const e of source) m.set(e.id, e)
|
|
362
|
+
}
|
|
304
363
|
}
|
|
305
364
|
} catch {}
|
|
306
365
|
return m
|
|
@@ -312,7 +371,15 @@ function SubAgentPanel(props: {
|
|
|
312
371
|
persistTimer = setTimeout(() => {
|
|
313
372
|
try {
|
|
314
373
|
const data = loadSessionData()
|
|
315
|
-
|
|
374
|
+
const { parentSid, isChild } = resolveParent(sid)
|
|
375
|
+
if (isChild) {
|
|
376
|
+
if (!data[parentSid]) data[parentSid] = { ts: Date.now(), entries: [], scroll: 0, expanded: "", children: {} }
|
|
377
|
+
if (!data[parentSid].children) data[parentSid].children = {}
|
|
378
|
+
if (!data[parentSid].children[sid]) data[parentSid].children[sid] = { scroll: 0, expanded: "", entries: [] }
|
|
379
|
+
data[parentSid].children[sid] = { ...data[parentSid].children[sid], entries: [...entries.values()] }
|
|
380
|
+
} else {
|
|
381
|
+
data[sid] = { ...data[sid], ts: Date.now(), entries: [...entries.values()], children: data[sid]?.children ?? {} }
|
|
382
|
+
}
|
|
316
383
|
saveSessionData(data)
|
|
317
384
|
} catch {}
|
|
318
385
|
}, 200)
|
|
@@ -321,7 +388,15 @@ function SubAgentPanel(props: {
|
|
|
321
388
|
const persistScroll = (sid: string, scroll: number) => {
|
|
322
389
|
try {
|
|
323
390
|
const data = loadSessionData()
|
|
324
|
-
|
|
391
|
+
const { parentSid, isChild } = resolveParent(sid)
|
|
392
|
+
if (isChild) {
|
|
393
|
+
if (!data[parentSid]) data[parentSid] = { ts: Date.now(), entries: [], scroll: 0, expanded: "", children: {} }
|
|
394
|
+
if (!data[parentSid].children) data[parentSid].children = {}
|
|
395
|
+
if (!data[parentSid].children[sid]) data[parentSid].children[sid] = { scroll: 0, expanded: "", entries: [] }
|
|
396
|
+
data[parentSid].children[sid] = { ...data[parentSid].children[sid], scroll }
|
|
397
|
+
} else {
|
|
398
|
+
data[sid] = { ...data[sid], ts: Date.now(), scroll, children: data[sid]?.children ?? {} }
|
|
399
|
+
}
|
|
325
400
|
saveSessionData(data)
|
|
326
401
|
} catch {}
|
|
327
402
|
}
|
|
@@ -329,12 +404,21 @@ function SubAgentPanel(props: {
|
|
|
329
404
|
const persistExpanded = (sid: string, expanded: string) => {
|
|
330
405
|
try {
|
|
331
406
|
const data = loadSessionData()
|
|
332
|
-
|
|
407
|
+
const { parentSid, isChild } = resolveParent(sid)
|
|
408
|
+
if (isChild) {
|
|
409
|
+
if (!data[parentSid]) data[parentSid] = { ts: Date.now(), entries: [], scroll: 0, expanded: "", children: {} }
|
|
410
|
+
if (!data[parentSid].children) data[parentSid].children = {}
|
|
411
|
+
if (!data[parentSid].children[sid]) data[parentSid].children[sid] = { scroll: 0, expanded: "", entries: [] }
|
|
412
|
+
data[parentSid].children[sid] = { ...data[parentSid].children[sid], expanded }
|
|
413
|
+
} else {
|
|
414
|
+
data[sid] = { ...data[sid], ts: Date.now(), expanded, children: data[sid]?.children ?? {} }
|
|
415
|
+
}
|
|
333
416
|
saveSessionData(data)
|
|
334
417
|
} catch {}
|
|
335
418
|
}
|
|
336
419
|
|
|
337
420
|
const cleanupOldSessions = () => {
|
|
421
|
+
if (ttlDays <= 0) return // 无期限,跳过清理
|
|
338
422
|
try {
|
|
339
423
|
const data = loadSessionData()
|
|
340
424
|
const cutoff = Date.now() - TTL_MS
|
|
@@ -375,7 +459,15 @@ function SubAgentPanel(props: {
|
|
|
375
459
|
clearTimeout(persistTimer)
|
|
376
460
|
try {
|
|
377
461
|
const data = loadSessionData()
|
|
378
|
-
|
|
462
|
+
const { parentSid, isChild } = resolveParent(props.sessionId)
|
|
463
|
+
if (isChild) {
|
|
464
|
+
if (!data[parentSid]) data[parentSid] = { ts: Date.now(), entries: [], scroll: 0, expanded: "", children: {} }
|
|
465
|
+
if (!data[parentSid].children) data[parentSid].children = {}
|
|
466
|
+
if (!data[parentSid].children[props.sessionId]) data[parentSid].children[props.sessionId] = { scroll: 0, expanded: "", entries: [] }
|
|
467
|
+
data[parentSid].children[props.sessionId] = { ...data[parentSid].children[props.sessionId], entries: [...next.values()] }
|
|
468
|
+
} else {
|
|
469
|
+
data[props.sessionId] = { ...data[props.sessionId], ts: Date.now(), entries: [...next.values()], children: data[props.sessionId]?.children ?? {} }
|
|
470
|
+
}
|
|
379
471
|
saveSessionData(data)
|
|
380
472
|
} catch {}
|
|
381
473
|
} else {
|
|
@@ -394,7 +486,14 @@ function SubAgentPanel(props: {
|
|
|
394
486
|
(() => { try { return props.api.kv.get(`${KV_PREFIX}.open`, true) as boolean } catch { return true } })()
|
|
395
487
|
)
|
|
396
488
|
const [expanded, setExpanded] = createSignal<string | undefined>(
|
|
397
|
-
(() => {
|
|
489
|
+
(() => {
|
|
490
|
+
try {
|
|
491
|
+
const { parentSid, isChild } = resolveParent(props.sessionId)
|
|
492
|
+
const rec = loadSessionData()[parentSid]
|
|
493
|
+
if (rec) return isChild ? rec.children?.[props.sessionId]?.expanded || undefined : rec.expanded || undefined
|
|
494
|
+
} catch {}
|
|
495
|
+
return undefined
|
|
496
|
+
})(),
|
|
398
497
|
)
|
|
399
498
|
const [hoveredOpen, setHoveredOpen] = createSignal<string | undefined>(undefined)
|
|
400
499
|
const [hoveredDismiss, setHoveredDismiss] = createSignal<string | undefined>(undefined)
|
|
@@ -402,7 +501,13 @@ function SubAgentPanel(props: {
|
|
|
402
501
|
const [hoveredMoreAbove, setHoveredMoreAbove] = createSignal(false)
|
|
403
502
|
const [hoveredMoreBelow, setHoveredMoreBelow] = createSignal(false)
|
|
404
503
|
const [scrollOffset, setScrollOffset] = createSignal(
|
|
405
|
-
(() => {
|
|
504
|
+
(() => {
|
|
505
|
+
try {
|
|
506
|
+
const { parentSid, isChild } = resolveParent(props.sessionId)
|
|
507
|
+
const rec = loadSessionData()[parentSid]
|
|
508
|
+
return isChild ? rec?.children?.[props.sessionId]?.scroll ?? 0 : rec?.scroll ?? 0
|
|
509
|
+
} catch { return 0 }
|
|
510
|
+
})(),
|
|
406
511
|
)
|
|
407
512
|
const [now, setNow] = createSignal(Date.now())
|
|
408
513
|
const [renderTick, setRenderTick] = createSignal(0)
|
|
@@ -879,23 +984,40 @@ function SubAgentPanel(props: {
|
|
|
879
984
|
// On session change: load from kv (entries survive component unmount), then scan+merge.
|
|
880
985
|
// On same session: only scan+merge (keep event‑driven running entries).
|
|
881
986
|
let lastSid = props.sessionId
|
|
987
|
+
let lastTick = 0
|
|
882
988
|
createEffect(() => {
|
|
883
989
|
const sid = props.sessionId
|
|
884
990
|
const switched = sid !== lastSid
|
|
885
991
|
lastSid = sid
|
|
992
|
+
const tick = clearTick() // 外部触发清除时 +1,effect 重跑
|
|
993
|
+
const forceReload = tick !== lastTick && !switched
|
|
994
|
+
lastTick = tick
|
|
886
995
|
const t = setTimeout(() => {
|
|
887
996
|
untrack(() => {
|
|
888
997
|
if (switched) {
|
|
889
|
-
const
|
|
998
|
+
const { parentSid, isChild } = resolveParent(sid)
|
|
999
|
+
const data = loadSessionData()
|
|
1000
|
+
const saved = isChild
|
|
1001
|
+
? data[parentSid]?.children?.[sid]?.scroll ?? 0
|
|
1002
|
+
: data[sid]?.scroll ?? 0
|
|
890
1003
|
setScrollOffset(saved)
|
|
1004
|
+
// 刷新父会话的访问时间 TTL,防止活跃会话的数据过期
|
|
1005
|
+
if (!isChild && data[sid]?.entries?.length) {
|
|
1006
|
+
data[sid].ts = Date.now()
|
|
1007
|
+
saveSessionData(data)
|
|
1008
|
+
}
|
|
891
1009
|
}
|
|
892
1010
|
// scan uses setEntryMapRaw — ephemeral data, not persisted to kv.
|
|
893
1011
|
// Only event-driven changes (handlePartUpdated, handleSessionEnd) persist.
|
|
894
1012
|
setEntryMapRaw((prev) => {
|
|
895
1013
|
// 优先从模块级缓存加载,KV 仅作缓存未命中时的回退
|
|
896
|
-
const next = switched
|
|
1014
|
+
const next = (switched || forceReload)
|
|
897
1015
|
? new Map(globalEntryCache.get(sid) ?? loadEntries(sid))
|
|
898
1016
|
: new Map(prev)
|
|
1017
|
+
// 从 KV 加载当前会话的清除名单,扫描时跳过被手动清除的历史条目
|
|
1018
|
+
const { parentSid: scanPSid, isChild: scanChild } = resolveParent(sid)
|
|
1019
|
+
const scanRec = loadSessionData()[scanPSid]
|
|
1020
|
+
const clearedIds = new Set(scanChild ? scanRec?.children?.[sid]?.clearedIds : scanRec?.clearedIds)
|
|
899
1021
|
try {
|
|
900
1022
|
const msgs = props.api.state.session.messages(sid)
|
|
901
1023
|
if (msgs && (msgs as any[]).length) {
|
|
@@ -916,6 +1038,9 @@ function SubAgentPanel(props: {
|
|
|
916
1038
|
const rawStatus = String(st?.status ?? "")
|
|
917
1039
|
const exists = next.get(id)
|
|
918
1040
|
|
|
1041
|
+
// 已手动清除的条目:scan 发现但不在内存 → 跳过重建
|
|
1042
|
+
if (!exists && clearedIds.has(id)) continue
|
|
1043
|
+
|
|
919
1044
|
// Only create entries for tool calls that entered execution.
|
|
920
1045
|
// "pending" / empty: skip new entries; allow heuristics for existing ones below.
|
|
921
1046
|
if ((rawStatus === "pending" || rawStatus === "") && !exists) continue
|
|
@@ -1135,7 +1260,7 @@ function SubAgentPanel(props: {
|
|
|
1135
1260
|
const expandedMaxLabelW = createMemo(() => {
|
|
1136
1261
|
const labels = [
|
|
1137
1262
|
t("agent.label"), t("status.label"), t("time.label"), t("tokens.label"),
|
|
1138
|
-
t("error.label"), t("cost.label"), t("model.label"), t("todo.label"),
|
|
1263
|
+
t("error.label"), t("cost.label"), t("model.label"), t("todo.label"), t("session.label"),
|
|
1139
1264
|
]
|
|
1140
1265
|
return Math.max(...labels.map(l => visualWidth(l + ": ")))
|
|
1141
1266
|
})
|
|
@@ -1428,6 +1553,25 @@ function SubAgentPanel(props: {
|
|
|
1428
1553
|
<span style={{ fg: pal().muted }}>{entry.todoDone}/{entry.todoTotal}</span>
|
|
1429
1554
|
</text>
|
|
1430
1555
|
</Show>
|
|
1556
|
+
<Show when={entry.sessionId}>
|
|
1557
|
+
<text
|
|
1558
|
+
onMouseUp={() => {
|
|
1559
|
+
if (entry.sessionId) {
|
|
1560
|
+
props.api.ui.toast({
|
|
1561
|
+
title: entry.title || entry.agent,
|
|
1562
|
+
message: `${entry.sessionId}\n\n${t("session.toast.copy")}`,
|
|
1563
|
+
duration: 8000,
|
|
1564
|
+
})
|
|
1565
|
+
}
|
|
1566
|
+
}}
|
|
1567
|
+
>
|
|
1568
|
+
{" "}
|
|
1569
|
+
<span style={{ fg: pal().primary }}>{t("session.label")}: </span>
|
|
1570
|
+
<span style={{ fg: pal().muted }}>{" ".repeat(expandedPad(t("session.label")))}</span>
|
|
1571
|
+
<span style={{ fg: pal().muted }}>{truncate(entry.sessionId!, expandedValAvail() - visualWidth(" ⎘"))}</span>
|
|
1572
|
+
<span style={{ fg: pal().warning }}> ⎘</span>
|
|
1573
|
+
</text>
|
|
1574
|
+
</Show>
|
|
1431
1575
|
{/* 进入会话 + 标记完成:同排左右两端,空间隔离防误触 */}
|
|
1432
1576
|
<Show when={entry.sessionId || isRunning}>
|
|
1433
1577
|
{(() => {
|
|
@@ -1731,7 +1875,8 @@ const tui: TuiPlugin = async (api: TuiPluginApi) => {
|
|
|
1731
1875
|
description: "Mark all running sub-agent entries as done (for stuck/zombie entries)",
|
|
1732
1876
|
slash: { name: "subagent-clear-running" },
|
|
1733
1877
|
onSelect: (dialog) => {
|
|
1734
|
-
const
|
|
1878
|
+
const sid = signals.sessionId
|
|
1879
|
+
const entries = globalEntryCache.get(sid)
|
|
1735
1880
|
if (!entries || entries.size === 0) {
|
|
1736
1881
|
const msg = signals.lang() === "zh" ? "暂无子代理条目" : "No sub-agent entries found"
|
|
1737
1882
|
api.ui.toast({ message: msg })
|
|
@@ -1750,11 +1895,21 @@ const tui: TuiPlugin = async (api: TuiPluginApi) => {
|
|
|
1750
1895
|
// 立即写 KV
|
|
1751
1896
|
try {
|
|
1752
1897
|
const data = JSON.parse(String(api.kv.get(`${KV_PREFIX}.session_data`, "{}")))
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1898
|
+
const sessionObj = api.state.session.get(sid)
|
|
1899
|
+
const parentID = (sessionObj as any)?.parentID as string | undefined
|
|
1900
|
+
if (parentID) {
|
|
1901
|
+
if (!data[parentID]) data[parentID] = { ts: Date.now(), entries: [], scroll: 0, expanded: "", children: {} }
|
|
1902
|
+
if (!data[parentID].children) data[parentID].children = {}
|
|
1903
|
+
if (!data[parentID].children[sid]) data[parentID].children[sid] = { scroll: 0, expanded: "", entries: [] }
|
|
1904
|
+
data[parentID].children[sid] = { ...data[parentID].children[sid], entries: [...entries.values()] }
|
|
1905
|
+
} else {
|
|
1906
|
+
data[sid] = {
|
|
1907
|
+
ts: Date.now(),
|
|
1908
|
+
entries: [...entries.values()],
|
|
1909
|
+
scroll: data[sid]?.scroll ?? 0,
|
|
1910
|
+
expanded: data[sid]?.expanded ?? "",
|
|
1911
|
+
children: data[sid]?.children ?? {},
|
|
1912
|
+
}
|
|
1758
1913
|
}
|
|
1759
1914
|
api.kv.set(`${KV_PREFIX}.session_data`, JSON.stringify(data))
|
|
1760
1915
|
} catch {}
|
|
@@ -1771,6 +1926,96 @@ const tui: TuiPlugin = async (api: TuiPluginApi) => {
|
|
|
1771
1926
|
dialog?.clear()
|
|
1772
1927
|
},
|
|
1773
1928
|
},
|
|
1929
|
+
{
|
|
1930
|
+
title: "SubAgent Magazine: TTL",
|
|
1931
|
+
value: "subagent-ttl",
|
|
1932
|
+
description: "Set session data retention period (days before auto-cleanup)",
|
|
1933
|
+
slash: { name: "subagent-ttl" },
|
|
1934
|
+
onSelect: (dialog) => {
|
|
1935
|
+
const t = (k: string) => I18N[signals.lang()][k] ?? k
|
|
1936
|
+
const curRaw = parseInt(String(api.kv.get(`${KV_PREFIX}.ttl_days`, "3")), 10)
|
|
1937
|
+
const curDays = Number.isNaN(curRaw) ? 3 : curRaw
|
|
1938
|
+
const curLabel = curDays === 0 ? t("ttl.unlimited") : `${curDays}d`
|
|
1939
|
+
dialog?.replace(() => (
|
|
1940
|
+
<api.ui.DialogSelect
|
|
1941
|
+
title={`${t("ttl.label")} (${curLabel})`}
|
|
1942
|
+
options={[
|
|
1943
|
+
{ title: t("ttl.3d"), value: "3" },
|
|
1944
|
+
{ title: t("ttl.7d"), value: "7" },
|
|
1945
|
+
{ title: t("ttl.14d"), value: "14" },
|
|
1946
|
+
{ title: t("ttl.30d"), value: "30" },
|
|
1947
|
+
{ title: t("ttl.unlimited"), value: "0" },
|
|
1948
|
+
]}
|
|
1949
|
+
onSelect={(opt) => {
|
|
1950
|
+
const days = parseInt(opt.value, 10)
|
|
1951
|
+
api.kv.set(`${KV_PREFIX}.ttl_days`, String(days))
|
|
1952
|
+
const msg = days === 0 ? t("ttl.toast_unlimited") : t("ttl.toast").replace("{n}", String(days))
|
|
1953
|
+
api.ui.toast({ message: msg })
|
|
1954
|
+
dialog?.clear()
|
|
1955
|
+
}}
|
|
1956
|
+
/>
|
|
1957
|
+
))
|
|
1958
|
+
},
|
|
1959
|
+
},
|
|
1960
|
+
{
|
|
1961
|
+
title: "SubAgent Magazine: Clear Entries",
|
|
1962
|
+
value: "subagent-clear-entries",
|
|
1963
|
+
description: "Delete all sub-agent records for the current session (cannot be undone)",
|
|
1964
|
+
slash: { name: "subagent-clear-entries" },
|
|
1965
|
+
onSelect: (dialog) => {
|
|
1966
|
+
const t = (k: string) => I18N[signals.lang()][k] ?? k
|
|
1967
|
+
const sid = signals.sessionId
|
|
1968
|
+
const sessionObj = api.state.session.get(sid)
|
|
1969
|
+
const parentID = (sessionObj as any)?.parentID as string | undefined
|
|
1970
|
+
// 检查是否存在运行中的条目
|
|
1971
|
+
const cached = globalEntryCache.get(sid)
|
|
1972
|
+
let runningCount = 0
|
|
1973
|
+
if (cached) {
|
|
1974
|
+
for (const [, e] of cached) { if (e.status === "running") runningCount++ }
|
|
1975
|
+
}
|
|
1976
|
+
const msg = runningCount > 0
|
|
1977
|
+
? t("clear.prompt_running").replace("{n}", String(runningCount))
|
|
1978
|
+
: t("clear.prompt")
|
|
1979
|
+
dialog?.replace(() => (
|
|
1980
|
+
<api.ui.DialogConfirm
|
|
1981
|
+
title={t("clear.title")}
|
|
1982
|
+
message={msg}
|
|
1983
|
+
onConfirm={() => {
|
|
1984
|
+
try {
|
|
1985
|
+
const data = JSON.parse(String(api.kv.get(`${KV_PREFIX}.session_data`, "{}")))
|
|
1986
|
+
let count = 0
|
|
1987
|
+
if (parentID) {
|
|
1988
|
+
if (data[parentID]?.children?.[sid]) {
|
|
1989
|
+
const child = data[parentID].children[sid]
|
|
1990
|
+
const ids = child.entries?.map((e: any) => e.id) ?? []
|
|
1991
|
+
count = ids.length
|
|
1992
|
+
child.entries = []
|
|
1993
|
+
child.scroll = 0
|
|
1994
|
+
child.expanded = ""
|
|
1995
|
+
child.clearedIds = [...new Set([...(child.clearedIds ?? []), ...ids])]
|
|
1996
|
+
}
|
|
1997
|
+
} else {
|
|
1998
|
+
count = data[sid]?.entries?.length ?? 0
|
|
1999
|
+
if (data[sid]) {
|
|
2000
|
+
const ids = data[sid].entries?.map((e: any) => e.id) ?? []
|
|
2001
|
+
data[sid].entries = []
|
|
2002
|
+
data[sid].scroll = 0
|
|
2003
|
+
data[sid].expanded = ""
|
|
2004
|
+
data[sid].clearedIds = [...new Set([...(data[sid].clearedIds ?? []), ...ids])]
|
|
2005
|
+
}
|
|
2006
|
+
}
|
|
2007
|
+
api.kv.set(`${KV_PREFIX}.session_data`, JSON.stringify(data))
|
|
2008
|
+
globalEntryCache.delete(sid)
|
|
2009
|
+
setClearTick((v) => v + 1)
|
|
2010
|
+
const msg = t("clear.done").replace("{n}", String(count))
|
|
2011
|
+
api.ui.toast({ message: msg })
|
|
2012
|
+
} catch {}
|
|
2013
|
+
dialog?.clear()
|
|
2014
|
+
}}
|
|
2015
|
+
/>
|
|
2016
|
+
))
|
|
2017
|
+
},
|
|
2018
|
+
},
|
|
1774
2019
|
])
|
|
1775
2020
|
}
|
|
1776
2021
|
|