opencode-subagent-magazine 1.6.1 → 1.7.1
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 -1
- package/dist/_version.d.ts +1 -1
- package/dist/_version.js +1 -1
- package/dist/core/format.d.ts +7 -0
- package/dist/core/format.js +45 -0
- package/dist/core/kv.d.ts +22 -1
- package/dist/core/kv.js +34 -1
- package/dist/core/types.d.ts +20 -0
- package/dist/core/types.js +7 -0
- package/dist/i18n.d.ts +24 -0
- package/dist/i18n.js +96 -0
- package/dist/index.js +90 -2
- package/dist/panel/SubAgentPanel.d.ts +6 -1
- package/dist/panel/SubAgentPanel.js +645 -266
- package/dist/panel/entry-map.d.ts +17 -0
- package/dist/panel/entry-map.js +75 -1
- package/dist/panel/panel-api.d.ts +24 -0
- package/dist/tui.js +1230 -567
- package/dist/v2/commands.js +202 -44
- package/dist/v2/db.d.ts +60 -0
- package/dist/v2/db.js +341 -0
- package/dist/v2/index.js +28 -6
- package/dist/v2/settings-menu.d.ts +9 -0
- package/dist/v2/settings-menu.js +242 -0
- package/dist/v2/v2-panel-api.js +315 -44
- package/dist/v2.js +2139 -708
- package/package.json +1 -1
- package/src/_version.ts +1 -1
- package/src/core/format.ts +44 -0
- package/src/core/kv.ts +43 -2
- package/src/core/types.ts +25 -0
- package/src/i18n.ts +96 -0
- package/src/index.tsx +113 -2
- package/src/panel/SubAgentPanel.tsx +621 -238
- package/src/panel/entry-map.ts +79 -2
- package/src/panel/panel-api.ts +25 -0
- package/src/v2/commands.ts +201 -43
- package/src/v2/db.ts +346 -0
- package/src/v2/index.tsx +54 -7
- package/src/v2/settings-menu.ts +259 -0
- package/src/v2/types.ts +3 -1
- package/src/v2/v2-panel-api.ts +246 -33
package/src/panel/entry-map.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import type { SubEntry } from "../core/types"
|
|
1
|
+
import type { SubEntry, SubStatus } from "../core/types"
|
|
2
2
|
|
|
3
3
|
export type SubEntryPatch = Omit<SubEntry, "startedAt" | "endedAt"> & {
|
|
4
4
|
startedAt?: number
|
|
5
|
+
endedAt?: number
|
|
5
6
|
}
|
|
6
7
|
|
|
7
8
|
export function findSubEntryKey(
|
|
@@ -33,7 +34,83 @@ export function upsertSubEntry(
|
|
|
33
34
|
...partial,
|
|
34
35
|
id: existing?.id ?? partial.id,
|
|
35
36
|
startedAt: existing?.startedAt || partial.startedAt || now,
|
|
36
|
-
endedAt: ended ? (existing?.endedAt
|
|
37
|
+
endedAt: ended ? (existing?.endedAt ?? partial.endedAt ?? now) : undefined,
|
|
37
38
|
})
|
|
38
39
|
return next
|
|
39
40
|
}
|
|
41
|
+
|
|
42
|
+
/** running < cancel_requested < 终态(done/error/cancelled)。 */
|
|
43
|
+
function statusRank(status: SubStatus): number {
|
|
44
|
+
if (status === "running") return 0
|
|
45
|
+
if (status === "cancel_requested") return 1
|
|
46
|
+
return 2
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const isTerminalStatus = (status: SubStatus) => statusRank(status) === 2
|
|
50
|
+
|
|
51
|
+
function minDefined(a: number | undefined, b: number | undefined): number | undefined {
|
|
52
|
+
if (a === undefined) return b
|
|
53
|
+
if (b === undefined) return a
|
|
54
|
+
return Math.min(a, b)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function maxDefined(a: number | undefined, b: number | undefined): number | undefined {
|
|
58
|
+
if (a === undefined) return b
|
|
59
|
+
if (b === undefined) return a
|
|
60
|
+
return Math.max(a, b)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* 合并同一子代理条目的两个视图,绝不回退状态。
|
|
65
|
+
*
|
|
66
|
+
* 持久化存储由多个 TUI 实例写入,每个写入者都可能
|
|
67
|
+
* 持有过期快照。终态始终优先于 running /
|
|
68
|
+
* cancel_requested,进度计数只增不减,描述性字段
|
|
69
|
+
* 则回退到仍保有它的一侧。
|
|
70
|
+
*/
|
|
71
|
+
export function mergeSubEntry(base: SubEntry, incoming: SubEntry): SubEntry {
|
|
72
|
+
const successor = statusRank(incoming.status) > statusRank(base.status) ? incoming : base
|
|
73
|
+
const status = successor.status
|
|
74
|
+
const ended = isTerminalStatus(status)
|
|
75
|
+
return {
|
|
76
|
+
...base,
|
|
77
|
+
...incoming,
|
|
78
|
+
id: base.id,
|
|
79
|
+
status,
|
|
80
|
+
sessionId: incoming.sessionId ?? base.sessionId,
|
|
81
|
+
startedAt: minDefined(base.startedAt, incoming.startedAt) ?? base.startedAt,
|
|
82
|
+
endedAt: ended ? (base.endedAt ?? incoming.endedAt ?? Date.now()) : undefined,
|
|
83
|
+
tokens: maxDefined(base.tokens, incoming.tokens),
|
|
84
|
+
cost: maxDefined(base.cost, incoming.cost),
|
|
85
|
+
model: incoming.model ?? base.model,
|
|
86
|
+
todoTotal: maxDefined(base.todoTotal, incoming.todoTotal),
|
|
87
|
+
todoDone: maxDefined(base.todoDone, incoming.todoDone),
|
|
88
|
+
error: incoming.error ?? base.error,
|
|
89
|
+
cancelRequestedAt: minDefined(base.cancelRequestedAt, incoming.cancelRequestedAt),
|
|
90
|
+
abortAccepted: base.abortAccepted || incoming.abortAccepted || undefined,
|
|
91
|
+
cancelReason: incoming.cancelReason ?? base.cancelReason,
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* 合并两个条目集合,去重方式与 `upsertSubEntry` 相同
|
|
97
|
+
* (先子会话、后条目 id),冲突用 `mergeSubEntry` 合并。
|
|
98
|
+
* 所有持久化路径都走这里,避免并发实例以整表写入
|
|
99
|
+
* 互相丢弃对方的条目。
|
|
100
|
+
*/
|
|
101
|
+
export function mergeSubEntries(
|
|
102
|
+
base: Iterable<SubEntry>,
|
|
103
|
+
incoming: Iterable<SubEntry>,
|
|
104
|
+
): Map<string, SubEntry> {
|
|
105
|
+
const out = new Map<string, SubEntry>()
|
|
106
|
+
for (const entry of base) out.set(entry.id, entry)
|
|
107
|
+
for (const entry of incoming) {
|
|
108
|
+
const key = findSubEntryKey(out, entry.id, entry.sessionId)
|
|
109
|
+
if (!key) {
|
|
110
|
+
out.set(entry.id, entry)
|
|
111
|
+
continue
|
|
112
|
+
}
|
|
113
|
+
out.set(key, mergeSubEntry(out.get(key)!, entry))
|
|
114
|
+
}
|
|
115
|
+
return out
|
|
116
|
+
}
|
package/src/panel/panel-api.ts
CHANGED
|
@@ -10,6 +10,21 @@ export interface SessionLike {
|
|
|
10
10
|
cost?: number
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
+
/** Spawned child session summary (V2 reads it from the local database). */
|
|
14
|
+
export interface ChildSessionLike {
|
|
15
|
+
id: string
|
|
16
|
+
agent?: string
|
|
17
|
+
title?: string
|
|
18
|
+
model?: string
|
|
19
|
+
cost?: number
|
|
20
|
+
tokens?: number
|
|
21
|
+
timeCreated?: number
|
|
22
|
+
timeIdle?: number
|
|
23
|
+
idleOutcome?: string
|
|
24
|
+
/** Still running: last message is newer than time_idle (resume keeps the old value). */
|
|
25
|
+
active?: boolean
|
|
26
|
+
}
|
|
27
|
+
|
|
13
28
|
export interface SessionStatusLike {
|
|
14
29
|
type: string
|
|
15
30
|
}
|
|
@@ -51,6 +66,16 @@ export interface PanelApi {
|
|
|
51
66
|
messages(sid: string): unknown[] | undefined
|
|
52
67
|
/** Raw parts of a message (scan). */
|
|
53
68
|
part(messageID: string): unknown[] | undefined
|
|
69
|
+
/** Optional host-side fallback: map a subagent tool call to its child session
|
|
70
|
+
* when live events/history lack the link (V2 reads the local database). */
|
|
71
|
+
resolveChild?(input: {
|
|
72
|
+
parentId: string
|
|
73
|
+
callId?: string
|
|
74
|
+
agent?: string
|
|
75
|
+
startedAt?: number
|
|
76
|
+
}): string | undefined
|
|
77
|
+
/** Optional host-side listing of spawned child sessions. */
|
|
78
|
+
listChildren?(parentId: string): ChildSessionLike[] | undefined
|
|
54
79
|
}
|
|
55
80
|
event: {
|
|
56
81
|
on(type: PanelEventType, cb: (e: PanelEvent) => void): () => void
|
package/src/v2/commands.ts
CHANGED
|
@@ -1,16 +1,24 @@
|
|
|
1
1
|
import type { Context, KeymapCommand } from "./types"
|
|
2
2
|
import type { PanelApi } from "../panel/panel-api"
|
|
3
|
-
import type { Lang, SharedSignals, SubStatus } from "../core/types"
|
|
4
|
-
import {
|
|
3
|
+
import type { Lang, SharedSignals, SubStatus, TimeFormat } from "../core/types"
|
|
4
|
+
import { TIME_FORMATS, TIME_FORMAT_SAMPLES } from "../core/format"
|
|
5
|
+
import { KV_PREFIX, SETTING_KEYS, updateSessionData, readTTLDays } from "../core/kv"
|
|
5
6
|
import { PLUGIN_VERSION } from "../_version"
|
|
6
7
|
import { LANG_META, createT } from "../i18n"
|
|
7
8
|
import { globalEntryCache, setClearTick } from "../panel/store"
|
|
9
|
+
import { mergeSubEntries } from "../panel/entry-map"
|
|
10
|
+
import { openSettingsMenu } from "./settings-menu"
|
|
8
11
|
|
|
9
12
|
/** V2 命令(对齐 V1 的 9 个斜杠命令——promise 式对话框)。 */
|
|
10
|
-
export function makeCommands(
|
|
13
|
+
export function makeCommands(
|
|
14
|
+
context: Context,
|
|
15
|
+
api: PanelApi,
|
|
16
|
+
signals: SharedSignals,
|
|
17
|
+
): KeymapCommand[] {
|
|
11
18
|
const t = createT(() => signals.lang())
|
|
12
19
|
const kv = api.kv
|
|
13
20
|
const clampMax = (n: number) => Math.max(1, Math.min(50, n))
|
|
21
|
+
const onOff = (v: boolean) => (v ? t("settings.on") : t("settings.off"))
|
|
14
22
|
|
|
15
23
|
const resolveParent = (sid: string): { parentSid: string; isChild: boolean } => {
|
|
16
24
|
try {
|
|
@@ -98,6 +106,125 @@ export function makeCommands(context: Context, api: PanelApi, signals: SharedSig
|
|
|
98
106
|
api.ui.toast(`Max entries: ${n}`)
|
|
99
107
|
},
|
|
100
108
|
},
|
|
109
|
+
{
|
|
110
|
+
id: "opencode-subagent-magazine.subagent.config",
|
|
111
|
+
title: "SubAgent Magazine: Settings",
|
|
112
|
+
description: "Show current settings (change each via its own command)",
|
|
113
|
+
slash: { name: "subagent-config" },
|
|
114
|
+
palette: true,
|
|
115
|
+
run: () => {
|
|
116
|
+
const ttl = readTTLDays(kv)
|
|
117
|
+
const langLabel = LANG_META.find((m) => m.code === signals.lang())?.label ?? signals.lang()
|
|
118
|
+
const message = [
|
|
119
|
+
`${t("settings.lang")}: ${langLabel}`,
|
|
120
|
+
`${t("settings.max")}: ${signals.maxEntries()}`,
|
|
121
|
+
`${t("settings.order")}: ${signals.sortOrder() === "desc" ? t("order.desc") : t("order.asc")}`,
|
|
122
|
+
`${t("settings.scroll")}: ${signals.scrollMode() === "wheel" ? t("scroll.wheel") : t("scroll.click")}`,
|
|
123
|
+
`${t("settings.ttl")}: ${ttl === 0 ? t("ttl.unlimited") : `${ttl}d`}`,
|
|
124
|
+
`${t("settings.border")}: ${onOff(signals.borderVisible())}`,
|
|
125
|
+
`${t("settings.showEntryCost")}: ${onOff(signals.showEntryCost())}`,
|
|
126
|
+
`${t("settings.showEntryTime")}: ${onOff(signals.showEntryTime())}`,
|
|
127
|
+
`${t("settings.showEntryTokens")}: ${onOff(signals.showEntryTokens())}`,
|
|
128
|
+
`${t("settings.showOrigin")}: ${onOff(signals.showOrigin())}`,
|
|
129
|
+
`${t("settings.timeFormat")}: ${TIME_FORMAT_SAMPLES[signals.timeFormat()]}`,
|
|
130
|
+
`${t("settings.dbSync")}: ${onOff(signals.dbSync())}`,
|
|
131
|
+
].join("\n")
|
|
132
|
+
context.ui.toast.show({ title: t("settings.title"), message })
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
id: "opencode-subagent-magazine.subagent.sections",
|
|
137
|
+
title: "SubAgent Magazine: Settings Menu",
|
|
138
|
+
description: "Open the interactive settings menu (Esc to close)",
|
|
139
|
+
slash: { name: "subagent-sections" },
|
|
140
|
+
palette: true,
|
|
141
|
+
run: () => {
|
|
142
|
+
openSettingsMenu(context, api, signals)
|
|
143
|
+
},
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
id: "opencode-subagent-magazine.subagent.cost",
|
|
147
|
+
title: "SubAgent Magazine: Toggle Entry Cost",
|
|
148
|
+
description: "Show or hide the per-sub-agent cost in the sidebar",
|
|
149
|
+
slash: { name: "subagent-cost" },
|
|
150
|
+
palette: true,
|
|
151
|
+
run: () => {
|
|
152
|
+
const v = !signals.showEntryCost()
|
|
153
|
+
signals.setShowEntryCost(v)
|
|
154
|
+
kv.set(SETTING_KEYS.showEntryCost, v)
|
|
155
|
+
api.ui.toast(`${t("settings.showEntryCost")}: ${onOff(v)}`)
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
id: "opencode-subagent-magazine.subagent.time",
|
|
160
|
+
title: "SubAgent Magazine: Toggle Entry Time",
|
|
161
|
+
description: "Show or hide the elapsed time in the sidebar list",
|
|
162
|
+
slash: { name: "subagent-time" },
|
|
163
|
+
palette: true,
|
|
164
|
+
run: () => {
|
|
165
|
+
const v = !signals.showEntryTime()
|
|
166
|
+
signals.setShowEntryTime(v)
|
|
167
|
+
kv.set(SETTING_KEYS.showEntryTime, v)
|
|
168
|
+
api.ui.toast(`${t("settings.showEntryTime")}: ${onOff(v)}`)
|
|
169
|
+
},
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
id: "opencode-subagent-magazine.subagent.time-format",
|
|
173
|
+
title: "SubAgent Magazine: Time Format",
|
|
174
|
+
description: "Set how elapsed time is displayed (short / decimal / clock / compact / seconds)",
|
|
175
|
+
slash: { name: "subagent-time-format" },
|
|
176
|
+
palette: true,
|
|
177
|
+
run: async () => {
|
|
178
|
+
const picked = await context.ui.dialog.select<TimeFormat>({
|
|
179
|
+
title: t("settings.timeFormat"),
|
|
180
|
+
options: TIME_FORMATS.map((f) => ({ title: `${f} — ${TIME_FORMAT_SAMPLES[f]}`, value: f })),
|
|
181
|
+
current: signals.timeFormat(),
|
|
182
|
+
})
|
|
183
|
+
if (!picked) return
|
|
184
|
+
signals.setTimeFormat(picked)
|
|
185
|
+
kv.set(SETTING_KEYS.timeFormat, picked)
|
|
186
|
+
api.ui.toast(`${t("settings.timeFormat")}: ${TIME_FORMAT_SAMPLES[picked]}`)
|
|
187
|
+
},
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
id: "opencode-subagent-magazine.subagent.tokens",
|
|
191
|
+
title: "SubAgent Magazine: Toggle Entry Tokens",
|
|
192
|
+
description: "Show or hide the token count in the sidebar list",
|
|
193
|
+
slash: { name: "subagent-tokens" },
|
|
194
|
+
palette: true,
|
|
195
|
+
run: () => {
|
|
196
|
+
const v = !signals.showEntryTokens()
|
|
197
|
+
signals.setShowEntryTokens(v)
|
|
198
|
+
kv.set(SETTING_KEYS.showEntryTokens, v)
|
|
199
|
+
api.ui.toast(`${t("settings.showEntryTokens")}: ${onOff(v)}`)
|
|
200
|
+
},
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
id: "opencode-subagent-magazine.subagent.origin",
|
|
204
|
+
title: "SubAgent Magazine: Toggle Origin Indicators",
|
|
205
|
+
description: "Show or hide origin markers (⇢ background / ↳ spawned) in the sidebar list",
|
|
206
|
+
slash: { name: "subagent-origin" },
|
|
207
|
+
palette: true,
|
|
208
|
+
run: () => {
|
|
209
|
+
const v = !signals.showOrigin()
|
|
210
|
+
signals.setShowOrigin(v)
|
|
211
|
+
kv.set(SETTING_KEYS.showOrigin, v)
|
|
212
|
+
api.ui.toast(`${t("settings.showOrigin")}: ${onOff(v)}`)
|
|
213
|
+
},
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
id: "opencode-subagent-magazine.subagent.db",
|
|
217
|
+
title: "SubAgent Magazine: Toggle Local DB",
|
|
218
|
+
description: "Enrich sub-agent entries from the local OpenCode database (V2)",
|
|
219
|
+
slash: { name: "subagent-db" },
|
|
220
|
+
palette: true,
|
|
221
|
+
run: () => {
|
|
222
|
+
const v = !signals.dbSync()
|
|
223
|
+
signals.setDbSync(v)
|
|
224
|
+
kv.set(SETTING_KEYS.dbSync, v)
|
|
225
|
+
api.ui.toast(`${t("settings.dbSync")}: ${onOff(v)}`)
|
|
226
|
+
},
|
|
227
|
+
},
|
|
101
228
|
{
|
|
102
229
|
id: "opencode-subagent-magazine.subagent.version",
|
|
103
230
|
title: "SubAgent Magazine: Version",
|
|
@@ -140,28 +267,49 @@ export function makeCommands(context: Context, api: PanelApi, signals: SharedSig
|
|
|
140
267
|
}
|
|
141
268
|
}
|
|
142
269
|
if (count > 0) {
|
|
270
|
+
const ok = () => api.ui.toast(signals.lang() === "zh"
|
|
271
|
+
? `已标记 ${count} 个运行中的条目为完成`
|
|
272
|
+
: `Marked ${count} running entries as done`)
|
|
273
|
+
const fail = () => {
|
|
274
|
+
try { api.ui.toast(t("clear.failed")) } catch {}
|
|
275
|
+
}
|
|
143
276
|
try {
|
|
144
|
-
const data = loadSessionData(kv)
|
|
145
277
|
const { parentSid, isChild } = resolveParent(sid)
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
278
|
+
const done = updateSessionData(kv, (data) => {
|
|
279
|
+
// 落定前先把(可能过期的)模块缓存与持久化记录
|
|
280
|
+
// merge:其他实例可能有本实例从未见过的条目,
|
|
281
|
+
// 这次写入不能把它们丢掉。
|
|
282
|
+
if (isChild) {
|
|
283
|
+
if (!data[parentSid]) data[parentSid] = { ts: Date.now(), entries: [], scroll: 0, expanded: "", children: {} }
|
|
284
|
+
if (!data[parentSid].children) data[parentSid].children = {}
|
|
285
|
+
const prev = data[parentSid].children[sid]?.entries ?? []
|
|
286
|
+
const merged = [...mergeSubEntries(prev, entries.values()).values()].map((e) =>
|
|
287
|
+
e.status === "running" || e.status === "cancel_requested"
|
|
288
|
+
? { ...e, status: "done" as SubStatus, endedAt: e.endedAt ?? Date.now() }
|
|
289
|
+
: e
|
|
290
|
+
)
|
|
291
|
+
data[parentSid].children[sid] = {
|
|
292
|
+
...(data[parentSid].children[sid] ?? { scroll: 0, expanded: "" }),
|
|
293
|
+
entries: merged,
|
|
294
|
+
}
|
|
295
|
+
} else {
|
|
296
|
+
const rec = data[parentSid] ?? { ts: Date.now(), entries: [], scroll: 0, expanded: "", children: {} }
|
|
297
|
+
const merged = [...mergeSubEntries(rec.entries ?? [], entries.values()).values()].map((e) =>
|
|
298
|
+
e.status === "running" || e.status === "cancel_requested"
|
|
299
|
+
? { ...e, status: "done" as SubStatus, endedAt: e.endedAt ?? Date.now() }
|
|
300
|
+
: e
|
|
301
|
+
)
|
|
302
|
+
data[parentSid] = { ...rec, ts: Date.now(), entries: merged, children: rec.children ?? {} }
|
|
158
303
|
}
|
|
304
|
+
})
|
|
305
|
+
if (done && typeof (done as Promise<void>).then === "function") {
|
|
306
|
+
void (done as Promise<void>).then(ok, fail)
|
|
307
|
+
} else {
|
|
308
|
+
ok()
|
|
159
309
|
}
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
? `已标记 ${count} 个运行中的条目为完成`
|
|
164
|
-
: `Marked ${count} running entries as done`)
|
|
310
|
+
} catch {
|
|
311
|
+
fail()
|
|
312
|
+
}
|
|
165
313
|
} else {
|
|
166
314
|
api.ui.toast(signals.lang() === "zh" ? "没有需要清理的运行中条目" : "No running entries to clear")
|
|
167
315
|
}
|
|
@@ -216,32 +364,42 @@ export function makeCommands(context: Context, api: PanelApi, signals: SharedSig
|
|
|
216
364
|
})
|
|
217
365
|
if (choice !== "yes") return
|
|
218
366
|
try {
|
|
219
|
-
const data = loadSessionData(kv)
|
|
220
367
|
let count = 0
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
child
|
|
229
|
-
|
|
368
|
+
const finish = () => {
|
|
369
|
+
globalEntryCache.delete(sid)
|
|
370
|
+
setClearTick((v) => v + 1)
|
|
371
|
+
api.ui.toast(t("clear.done", { n: count }))
|
|
372
|
+
}
|
|
373
|
+
const done = updateSessionData(kv, (data) => {
|
|
374
|
+
if (parentID) {
|
|
375
|
+
const child = data[parentID]?.children?.[sid]
|
|
376
|
+
if (child) {
|
|
377
|
+
const ids = child.entries?.map((e) => e.id) ?? []
|
|
378
|
+
count = ids.length
|
|
379
|
+
child.entries = []
|
|
380
|
+
child.scroll = 0
|
|
381
|
+
child.expanded = ""
|
|
382
|
+
child.clearedIds = [...new Set([...(child.clearedIds ?? []), ...ids])]
|
|
383
|
+
}
|
|
384
|
+
} else {
|
|
385
|
+
const rec = data[sid]
|
|
386
|
+
if (rec) {
|
|
387
|
+
const ids = rec.entries?.map((e) => e.id) ?? []
|
|
388
|
+
count = ids.length
|
|
389
|
+
rec.entries = []
|
|
390
|
+
rec.scroll = 0
|
|
391
|
+
rec.expanded = ""
|
|
392
|
+
rec.clearedIds = [...new Set([...(rec.clearedIds ?? []), ...ids])]
|
|
393
|
+
}
|
|
230
394
|
}
|
|
395
|
+
})
|
|
396
|
+
if (done && typeof (done as Promise<void>).then === "function") {
|
|
397
|
+
void (done as Promise<void>).then(finish, () => {
|
|
398
|
+
try { api.ui.toast(t("clear.failed")) } catch {}
|
|
399
|
+
})
|
|
231
400
|
} else {
|
|
232
|
-
|
|
233
|
-
if (data[sid]) {
|
|
234
|
-
const ids = data[sid].entries?.map((e) => e.id) ?? []
|
|
235
|
-
data[sid].entries = []
|
|
236
|
-
data[sid].scroll = 0
|
|
237
|
-
data[sid].expanded = ""
|
|
238
|
-
data[sid].clearedIds = [...new Set([...(data[sid].clearedIds ?? []), ...ids])]
|
|
239
|
-
}
|
|
401
|
+
finish()
|
|
240
402
|
}
|
|
241
|
-
saveSessionData(kv, data)
|
|
242
|
-
globalEntryCache.delete(sid)
|
|
243
|
-
setClearTick((v) => v + 1)
|
|
244
|
-
api.ui.toast(t("clear.done", { n: count }))
|
|
245
403
|
} catch {}
|
|
246
404
|
},
|
|
247
405
|
},
|