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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-subagent-magazine",
3
- "version": "1.2.1",
3
+ "version": "1.4.0",
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.2.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 TTL_MS = 3 * 24 * 60 * 60 * 1000
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 rec = loadSessionData()[sid]
302
- if (rec?.entries) {
303
- for (const e of rec.entries) m.set(e.id, e)
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
- data[sid] = { ...data[sid], ts: Date.now(), entries: [...entries.values()] }
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
- data[sid] = { ...data[sid], ts: Date.now(), scroll }
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
- data[sid] = { ...data[sid], ts: Date.now(), expanded }
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
- data[props.sessionId] = { ...data[props.sessionId], ts: Date.now(), entries: [...next.values()] }
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
- (() => { try { return loadSessionData()[props.sessionId]?.expanded || undefined } catch { return undefined } })()
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
- (() => { try { return loadSessionData()[props.sessionId]?.scroll ?? 0 } catch { return 0 } })()
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 saved = loadSessionData()[sid]?.scroll ?? 0
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 entries = globalEntryCache.get(signals.sessionId)
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
- data[signals.sessionId] = {
1754
- ts: Date.now(),
1755
- entries: [...entries.values()],
1756
- scroll: data[signals.sessionId]?.scroll ?? 0,
1757
- expanded: data[signals.sessionId]?.expanded ?? "",
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