opencode-subagent-magazine 1.6.0-beta.2 → 1.6.0-beta.4
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 +3 -1
- package/dist/_version.d.ts +1 -1
- package/dist/_version.js +1 -1
- package/dist/core/kv.d.ts +1 -0
- package/dist/core/kv.js +1 -0
- package/dist/core/types.d.ts +2 -0
- package/dist/i18n.d.ts +2 -0
- package/dist/i18n.js +8 -0
- package/dist/index.js +17 -2
- package/dist/panel/SubAgentPanel.d.ts +1 -0
- package/dist/panel/SubAgentPanel.js +54 -37
- package/dist/panel/entry-map.d.ts +6 -0
- package/dist/panel/entry-map.js +25 -0
- package/dist/tui.js +128 -56
- package/dist/v2/commands.js +13 -0
- package/dist/v2/index.js +3 -2
- package/dist/v2.js +124 -56
- package/package.json +1 -1
- package/src/_version.ts +1 -1
- package/src/core/color.ts +70 -70
- package/src/core/format.ts +61 -61
- package/src/core/index.ts +6 -6
- package/src/core/kv.ts +37 -36
- package/src/core/state-machine.ts +46 -46
- package/src/core/types.ts +2 -0
- package/src/core/usage.ts +16 -16
- package/src/i18n.ts +8 -0
- package/src/index.tsx +472 -454
- package/src/panel/SubAgentPanel.tsx +59 -37
- package/src/panel/entry-map.ts +39 -0
- package/src/panel/store.ts +8 -8
- package/src/v2/commands.ts +264 -251
- package/src/v2/index.tsx +102 -98
- package/src/v2/theme.ts +14 -14
- package/src/v2/types.ts +165 -165
- package/tui/index.js +11 -11
package/src/v2/commands.ts
CHANGED
|
@@ -1,251 +1,264 @@
|
|
|
1
|
-
import type { Context, KeymapCommand } from "./types"
|
|
2
|
-
import type { PanelApi } from "../panel/panel-api"
|
|
3
|
-
import type { Lang, SharedSignals, SubStatus } from "../core/types"
|
|
4
|
-
import { KV_PREFIX, SETTING_KEYS, loadSessionData, saveSessionData, readTTLDays } from "../core/kv"
|
|
5
|
-
import { PLUGIN_VERSION } from "../_version"
|
|
6
|
-
import { LANG_META, createT } from "../i18n"
|
|
7
|
-
import { globalEntryCache, setClearTick } from "../panel/store"
|
|
8
|
-
|
|
9
|
-
/** V2 命令(对齐 V1 的 9 个斜杠命令——promise 式对话框)。 */
|
|
10
|
-
export function makeCommands(context: Context, api: PanelApi, signals: SharedSignals): KeymapCommand[] {
|
|
11
|
-
const t = createT(() => signals.lang())
|
|
12
|
-
const kv = api.kv
|
|
13
|
-
const clampMax = (n: number) => Math.max(1, Math.min(50, n))
|
|
14
|
-
|
|
15
|
-
const resolveParent = (sid: string): { parentSid: string; isChild: boolean } => {
|
|
16
|
-
try {
|
|
17
|
-
const session = api.session.get(sid) as any
|
|
18
|
-
const parentID = session?.parentID as string | undefined
|
|
19
|
-
if (parentID) return { parentSid: parentID, isChild: true }
|
|
20
|
-
} catch {}
|
|
21
|
-
return { parentSid: sid, isChild: false }
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
return [
|
|
25
|
-
{
|
|
26
|
-
id: "opencode-subagent-magazine.subagent.lang",
|
|
27
|
-
title: "SubAgent Magazine: Language",
|
|
28
|
-
description: "Switch display language (中文 / English / 日本語 / 한국어)",
|
|
29
|
-
slash: { name: "subagent-lang" },
|
|
30
|
-
palette: true,
|
|
31
|
-
run: async () => {
|
|
32
|
-
const lang = await context.ui.dialog.select<Lang>({
|
|
33
|
-
title: "Language / 语言",
|
|
34
|
-
options: LANG_META.map((m) => ({ title: m.label, value: m.code })),
|
|
35
|
-
})
|
|
36
|
-
if (!lang) return
|
|
37
|
-
signals.setLang(lang)
|
|
38
|
-
kv.set(SETTING_KEYS.lang, lang)
|
|
39
|
-
api.ui.toast("Language: " + (LANG_META.find((m) => m.code === lang)?.label ?? lang))
|
|
40
|
-
},
|
|
41
|
-
},
|
|
42
|
-
{
|
|
43
|
-
id: "opencode-subagent-magazine.subagent.order",
|
|
44
|
-
title: "SubAgent Magazine: Sort Order",
|
|
45
|
-
description: "Set sub-agent entry sort order (desc / asc)",
|
|
46
|
-
slash: { name: "subagent-order" },
|
|
47
|
-
palette: true,
|
|
48
|
-
run: async () => {
|
|
49
|
-
const order = await context.ui.dialog.select<"desc" | "asc">({
|
|
50
|
-
title: "Sort Order / 排序方式",
|
|
51
|
-
options: [
|
|
52
|
-
{ title: t("order.desc"), value: "desc" },
|
|
53
|
-
{ title: t("order.asc"), value: "asc" },
|
|
54
|
-
],
|
|
55
|
-
})
|
|
56
|
-
if (!order) return
|
|
57
|
-
signals.setSortOrder(order)
|
|
58
|
-
kv.set(SETTING_KEYS.order, order)
|
|
59
|
-
api.ui.toast(order === "desc" ? t("order.desc") : t("order.asc"))
|
|
60
|
-
},
|
|
61
|
-
},
|
|
62
|
-
{
|
|
63
|
-
id: "opencode-subagent-magazine.subagent.scroll",
|
|
64
|
-
title: "SubAgent Magazine: Scroll Mode",
|
|
65
|
-
description: "Set scroll mode (wheel / click)",
|
|
66
|
-
slash: { name: "subagent-scroll" },
|
|
67
|
-
palette: true,
|
|
68
|
-
run: async () => {
|
|
69
|
-
const mode = await context.ui.dialog.select<"wheel" | "click">({
|
|
70
|
-
title: "Scroll Mode / 滚动模式",
|
|
71
|
-
options: [
|
|
72
|
-
{ title: t("scroll.wheel"), value: "wheel" },
|
|
73
|
-
{ title: t("scroll.click"), value: "click" },
|
|
74
|
-
],
|
|
75
|
-
})
|
|
76
|
-
if (!mode) return
|
|
77
|
-
signals.setScrollMode(mode)
|
|
78
|
-
kv.set(SETTING_KEYS.scrollMode, mode)
|
|
79
|
-
api.ui.toast(mode === "wheel" ? t("scroll.wheel") : t("scroll.click"))
|
|
80
|
-
},
|
|
81
|
-
},
|
|
82
|
-
{
|
|
83
|
-
id: "opencode-subagent-magazine.subagent.max",
|
|
84
|
-
title: "SubAgent Magazine: Max Entries",
|
|
85
|
-
description: "Set max visible sub-agent entries in sidebar",
|
|
86
|
-
slash: { name: "subagent-max" },
|
|
87
|
-
palette: true,
|
|
88
|
-
run: async () => {
|
|
89
|
-
const val = await context.ui.dialog.prompt({
|
|
90
|
-
title: "Max Visible Entries",
|
|
91
|
-
message: "Number of entries to show in the sidebar (1–50)",
|
|
92
|
-
placeholder: String(signals.maxEntries()),
|
|
93
|
-
})
|
|
94
|
-
if (val === undefined) return
|
|
95
|
-
const n = clampMax(parseInt(val, 10) || 10)
|
|
96
|
-
signals.setMaxEntries(n)
|
|
97
|
-
kv.set(SETTING_KEYS.maxEntries, n)
|
|
98
|
-
api.ui.toast(`Max entries: ${n}`)
|
|
99
|
-
},
|
|
100
|
-
},
|
|
101
|
-
{
|
|
102
|
-
id: "opencode-subagent-magazine.subagent.version",
|
|
103
|
-
title: "SubAgent Magazine: Version",
|
|
104
|
-
description: "Show plugin version",
|
|
105
|
-
slash: { name: "subagent-version" },
|
|
106
|
-
palette: true,
|
|
107
|
-
run: () => {
|
|
108
|
-
api.ui.toast(`opencode-subagent-magazine v${PLUGIN_VERSION}`)
|
|
109
|
-
},
|
|
110
|
-
},
|
|
111
|
-
{
|
|
112
|
-
id: "opencode-subagent-magazine.subagent.session",
|
|
113
|
-
title: "SubAgent Magazine: Session",
|
|
114
|
-
description: "Show current session ID",
|
|
115
|
-
slash: { name: "subagent-session" },
|
|
116
|
-
palette: true,
|
|
117
|
-
run: () => {
|
|
118
|
-
api.ui.toast(`Session: ${signals.sessionId}`)
|
|
119
|
-
},
|
|
120
|
-
},
|
|
121
|
-
{
|
|
122
|
-
id: "opencode-subagent-magazine.subagent.clear-running",
|
|
123
|
-
title: "SubAgent Magazine: Clear Running",
|
|
124
|
-
description: "Mark all running sub-agent entries as done (for stuck/zombie entries)",
|
|
125
|
-
slash: { name: "subagent-clear-running" },
|
|
126
|
-
palette: true,
|
|
127
|
-
run: () => {
|
|
128
|
-
const sid = signals.sessionId
|
|
129
|
-
const entries = globalEntryCache.get(sid)
|
|
130
|
-
if (!entries || entries.size === 0) {
|
|
131
|
-
api.ui.toast(signals.lang() === "zh" ? "暂无子代理条目" : "No sub-agent entries found")
|
|
132
|
-
return
|
|
133
|
-
}
|
|
134
|
-
let count = 0
|
|
135
|
-
for (const [, entry] of entries) {
|
|
136
|
-
if (entry.status === "running" || entry.status === "cancel_requested") {
|
|
137
|
-
entry.status = "done" as SubStatus
|
|
138
|
-
entry.endedAt = Date.now()
|
|
139
|
-
count++
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
if (count > 0) {
|
|
143
|
-
try {
|
|
144
|
-
const data = loadSessionData(kv)
|
|
145
|
-
const { parentSid, isChild } = resolveParent(sid)
|
|
146
|
-
if (isChild) {
|
|
147
|
-
if (!data[parentSid]) data[parentSid] = { ts: Date.now(), entries: [], scroll: 0, expanded: "", children: {} }
|
|
148
|
-
if (!data[parentSid].children) data[parentSid].children = {}
|
|
149
|
-
if (!data[parentSid].children[sid]) data[parentSid].children[sid] = { scroll: 0, expanded: "", entries: [] }
|
|
150
|
-
data[parentSid].children[sid] = { ...data[parentSid].children[sid], entries: [...entries.values()] }
|
|
151
|
-
} else {
|
|
152
|
-
data[sid] = {
|
|
153
|
-
ts: Date.now(),
|
|
154
|
-
entries: [...entries.values()],
|
|
155
|
-
scroll: data[sid]?.scroll ?? 0,
|
|
156
|
-
expanded: data[sid]?.expanded ?? "",
|
|
157
|
-
children: data[sid]?.children ?? {},
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
saveSessionData(kv, data)
|
|
161
|
-
} catch {}
|
|
162
|
-
api.ui.toast(signals.lang() === "zh"
|
|
163
|
-
? `已标记 ${count} 个运行中的条目为完成`
|
|
164
|
-
: `Marked ${count} running entries as done`)
|
|
165
|
-
} else {
|
|
166
|
-
api.ui.toast(signals.lang() === "zh" ? "没有需要清理的运行中条目" : "No running entries to clear")
|
|
167
|
-
}
|
|
168
|
-
},
|
|
169
|
-
},
|
|
170
|
-
{
|
|
171
|
-
id: "opencode-subagent-magazine.subagent.ttl",
|
|
172
|
-
title: "SubAgent Magazine: TTL",
|
|
173
|
-
description: "Set session data retention period (days before auto-cleanup)",
|
|
174
|
-
slash: { name: "subagent-ttl" },
|
|
175
|
-
palette: true,
|
|
176
|
-
run: async () => {
|
|
177
|
-
const curDays = readTTLDays(kv)
|
|
178
|
-
const curLabel = curDays === 0 ? t("ttl.unlimited") : `${curDays}d`
|
|
179
|
-
const days = await context.ui.dialog.select<number>({
|
|
180
|
-
title: `${t("ttl.label")} (${curLabel})`,
|
|
181
|
-
options: [
|
|
182
|
-
{ title: t("ttl.3d"), value: 3 },
|
|
183
|
-
{ title: t("ttl.7d"), value: 7 },
|
|
184
|
-
{ title: t("ttl.14d"), value: 14 },
|
|
185
|
-
{ title: t("ttl.30d"), value: 30 },
|
|
186
|
-
{ title: t("ttl.unlimited"), value: 0 },
|
|
187
|
-
],
|
|
188
|
-
})
|
|
189
|
-
if (days === undefined) return
|
|
190
|
-
kv.set(SETTING_KEYS.ttlDays, String(days))
|
|
191
|
-
api.ui.toast(days === 0 ? t("ttl.toast_unlimited") : t("ttl.toast", { n: days }))
|
|
192
|
-
},
|
|
193
|
-
},
|
|
194
|
-
{
|
|
195
|
-
id: "opencode-subagent-magazine.subagent.clear-entries",
|
|
196
|
-
title: "SubAgent Magazine: Clear Entries",
|
|
197
|
-
description: "Delete all sub-agent records for the current session (cannot be undone)",
|
|
198
|
-
slash: { name: "subagent-clear-entries" },
|
|
199
|
-
palette: true,
|
|
200
|
-
run: async () => {
|
|
201
|
-
const sid = signals.sessionId
|
|
202
|
-
const sessionObj = api.session.get(sid)
|
|
203
|
-
const parentID = (sessionObj as any)?.parentID as string | undefined
|
|
204
|
-
const cached = globalEntryCache.get(sid)
|
|
205
|
-
let runningCount = 0
|
|
206
|
-
if (cached) {
|
|
207
|
-
for (const [, e] of cached) { if (e.status === "running") runningCount++ }
|
|
208
|
-
}
|
|
209
|
-
const msg = runningCount > 0 ? t("clear.prompt_running", { n: runningCount }) : t("clear.prompt")
|
|
210
|
-
const choice = await context.ui.dialog.select<"yes" | "no">({
|
|
211
|
-
title: t("clear.title"),
|
|
212
|
-
options: [
|
|
213
|
-
{ title: t("clear.title"), value: "yes" },
|
|
214
|
-
{ title: t("cancel.label"), value: "no" },
|
|
215
|
-
],
|
|
216
|
-
})
|
|
217
|
-
if (choice !== "yes") return
|
|
218
|
-
try {
|
|
219
|
-
const data = loadSessionData(kv)
|
|
220
|
-
let count = 0
|
|
221
|
-
if (parentID) {
|
|
222
|
-
if (data[parentID]?.children?.[sid]) {
|
|
223
|
-
const child = data[parentID].children[sid]
|
|
224
|
-
const ids = child.entries?.map((e) => e.id) ?? []
|
|
225
|
-
count = ids.length
|
|
226
|
-
child.entries = []
|
|
227
|
-
child.scroll = 0
|
|
228
|
-
child.expanded = ""
|
|
229
|
-
child.clearedIds = [...new Set([...(child.clearedIds ?? []), ...ids])]
|
|
230
|
-
}
|
|
231
|
-
} else {
|
|
232
|
-
count = data[sid]?.entries?.length ?? 0
|
|
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
|
-
}
|
|
240
|
-
}
|
|
241
|
-
saveSessionData(kv, data)
|
|
242
|
-
globalEntryCache.delete(sid)
|
|
243
|
-
setClearTick((v) => v + 1)
|
|
244
|
-
api.ui.toast(t("clear.done", { n: count }))
|
|
245
|
-
} catch {}
|
|
246
|
-
},
|
|
247
|
-
},
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
1
|
+
import type { Context, KeymapCommand } from "./types"
|
|
2
|
+
import type { PanelApi } from "../panel/panel-api"
|
|
3
|
+
import type { Lang, SharedSignals, SubStatus } from "../core/types"
|
|
4
|
+
import { KV_PREFIX, SETTING_KEYS, loadSessionData, saveSessionData, readTTLDays } from "../core/kv"
|
|
5
|
+
import { PLUGIN_VERSION } from "../_version"
|
|
6
|
+
import { LANG_META, createT } from "../i18n"
|
|
7
|
+
import { globalEntryCache, setClearTick } from "../panel/store"
|
|
8
|
+
|
|
9
|
+
/** V2 命令(对齐 V1 的 9 个斜杠命令——promise 式对话框)。 */
|
|
10
|
+
export function makeCommands(context: Context, api: PanelApi, signals: SharedSignals): KeymapCommand[] {
|
|
11
|
+
const t = createT(() => signals.lang())
|
|
12
|
+
const kv = api.kv
|
|
13
|
+
const clampMax = (n: number) => Math.max(1, Math.min(50, n))
|
|
14
|
+
|
|
15
|
+
const resolveParent = (sid: string): { parentSid: string; isChild: boolean } => {
|
|
16
|
+
try {
|
|
17
|
+
const session = api.session.get(sid) as any
|
|
18
|
+
const parentID = session?.parentID as string | undefined
|
|
19
|
+
if (parentID) return { parentSid: parentID, isChild: true }
|
|
20
|
+
} catch {}
|
|
21
|
+
return { parentSid: sid, isChild: false }
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return [
|
|
25
|
+
{
|
|
26
|
+
id: "opencode-subagent-magazine.subagent.lang",
|
|
27
|
+
title: "SubAgent Magazine: Language",
|
|
28
|
+
description: "Switch display language (中文 / English / 日本語 / 한국어)",
|
|
29
|
+
slash: { name: "subagent-lang" },
|
|
30
|
+
palette: true,
|
|
31
|
+
run: async () => {
|
|
32
|
+
const lang = await context.ui.dialog.select<Lang>({
|
|
33
|
+
title: "Language / 语言",
|
|
34
|
+
options: LANG_META.map((m) => ({ title: m.label, value: m.code })),
|
|
35
|
+
})
|
|
36
|
+
if (!lang) return
|
|
37
|
+
signals.setLang(lang)
|
|
38
|
+
kv.set(SETTING_KEYS.lang, lang)
|
|
39
|
+
api.ui.toast("Language: " + (LANG_META.find((m) => m.code === lang)?.label ?? lang))
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
id: "opencode-subagent-magazine.subagent.order",
|
|
44
|
+
title: "SubAgent Magazine: Sort Order",
|
|
45
|
+
description: "Set sub-agent entry sort order (desc / asc)",
|
|
46
|
+
slash: { name: "subagent-order" },
|
|
47
|
+
palette: true,
|
|
48
|
+
run: async () => {
|
|
49
|
+
const order = await context.ui.dialog.select<"desc" | "asc">({
|
|
50
|
+
title: "Sort Order / 排序方式",
|
|
51
|
+
options: [
|
|
52
|
+
{ title: t("order.desc"), value: "desc" },
|
|
53
|
+
{ title: t("order.asc"), value: "asc" },
|
|
54
|
+
],
|
|
55
|
+
})
|
|
56
|
+
if (!order) return
|
|
57
|
+
signals.setSortOrder(order)
|
|
58
|
+
kv.set(SETTING_KEYS.order, order)
|
|
59
|
+
api.ui.toast(order === "desc" ? t("order.desc") : t("order.asc"))
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
id: "opencode-subagent-magazine.subagent.scroll",
|
|
64
|
+
title: "SubAgent Magazine: Scroll Mode",
|
|
65
|
+
description: "Set scroll mode (wheel / click)",
|
|
66
|
+
slash: { name: "subagent-scroll" },
|
|
67
|
+
palette: true,
|
|
68
|
+
run: async () => {
|
|
69
|
+
const mode = await context.ui.dialog.select<"wheel" | "click">({
|
|
70
|
+
title: "Scroll Mode / 滚动模式",
|
|
71
|
+
options: [
|
|
72
|
+
{ title: t("scroll.wheel"), value: "wheel" },
|
|
73
|
+
{ title: t("scroll.click"), value: "click" },
|
|
74
|
+
],
|
|
75
|
+
})
|
|
76
|
+
if (!mode) return
|
|
77
|
+
signals.setScrollMode(mode)
|
|
78
|
+
kv.set(SETTING_KEYS.scrollMode, mode)
|
|
79
|
+
api.ui.toast(mode === "wheel" ? t("scroll.wheel") : t("scroll.click"))
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
id: "opencode-subagent-magazine.subagent.max",
|
|
84
|
+
title: "SubAgent Magazine: Max Entries",
|
|
85
|
+
description: "Set max visible sub-agent entries in sidebar",
|
|
86
|
+
slash: { name: "subagent-max" },
|
|
87
|
+
palette: true,
|
|
88
|
+
run: async () => {
|
|
89
|
+
const val = await context.ui.dialog.prompt({
|
|
90
|
+
title: "Max Visible Entries",
|
|
91
|
+
message: "Number of entries to show in the sidebar (1–50)",
|
|
92
|
+
placeholder: String(signals.maxEntries()),
|
|
93
|
+
})
|
|
94
|
+
if (val === undefined) return
|
|
95
|
+
const n = clampMax(parseInt(val, 10) || 10)
|
|
96
|
+
signals.setMaxEntries(n)
|
|
97
|
+
kv.set(SETTING_KEYS.maxEntries, n)
|
|
98
|
+
api.ui.toast(`Max entries: ${n}`)
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
id: "opencode-subagent-magazine.subagent.version",
|
|
103
|
+
title: "SubAgent Magazine: Version",
|
|
104
|
+
description: "Show plugin version",
|
|
105
|
+
slash: { name: "subagent-version" },
|
|
106
|
+
palette: true,
|
|
107
|
+
run: () => {
|
|
108
|
+
api.ui.toast(`opencode-subagent-magazine v${PLUGIN_VERSION}`)
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
id: "opencode-subagent-magazine.subagent.session",
|
|
113
|
+
title: "SubAgent Magazine: Session",
|
|
114
|
+
description: "Show current session ID",
|
|
115
|
+
slash: { name: "subagent-session" },
|
|
116
|
+
palette: true,
|
|
117
|
+
run: () => {
|
|
118
|
+
api.ui.toast(`Session: ${signals.sessionId}`)
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
id: "opencode-subagent-magazine.subagent.clear-running",
|
|
123
|
+
title: "SubAgent Magazine: Clear Running",
|
|
124
|
+
description: "Mark all running sub-agent entries as done (for stuck/zombie entries)",
|
|
125
|
+
slash: { name: "subagent-clear-running" },
|
|
126
|
+
palette: true,
|
|
127
|
+
run: () => {
|
|
128
|
+
const sid = signals.sessionId
|
|
129
|
+
const entries = globalEntryCache.get(sid)
|
|
130
|
+
if (!entries || entries.size === 0) {
|
|
131
|
+
api.ui.toast(signals.lang() === "zh" ? "暂无子代理条目" : "No sub-agent entries found")
|
|
132
|
+
return
|
|
133
|
+
}
|
|
134
|
+
let count = 0
|
|
135
|
+
for (const [, entry] of entries) {
|
|
136
|
+
if (entry.status === "running" || entry.status === "cancel_requested") {
|
|
137
|
+
entry.status = "done" as SubStatus
|
|
138
|
+
entry.endedAt = Date.now()
|
|
139
|
+
count++
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
if (count > 0) {
|
|
143
|
+
try {
|
|
144
|
+
const data = loadSessionData(kv)
|
|
145
|
+
const { parentSid, isChild } = resolveParent(sid)
|
|
146
|
+
if (isChild) {
|
|
147
|
+
if (!data[parentSid]) data[parentSid] = { ts: Date.now(), entries: [], scroll: 0, expanded: "", children: {} }
|
|
148
|
+
if (!data[parentSid].children) data[parentSid].children = {}
|
|
149
|
+
if (!data[parentSid].children[sid]) data[parentSid].children[sid] = { scroll: 0, expanded: "", entries: [] }
|
|
150
|
+
data[parentSid].children[sid] = { ...data[parentSid].children[sid], entries: [...entries.values()] }
|
|
151
|
+
} else {
|
|
152
|
+
data[sid] = {
|
|
153
|
+
ts: Date.now(),
|
|
154
|
+
entries: [...entries.values()],
|
|
155
|
+
scroll: data[sid]?.scroll ?? 0,
|
|
156
|
+
expanded: data[sid]?.expanded ?? "",
|
|
157
|
+
children: data[sid]?.children ?? {},
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
saveSessionData(kv, data)
|
|
161
|
+
} catch {}
|
|
162
|
+
api.ui.toast(signals.lang() === "zh"
|
|
163
|
+
? `已标记 ${count} 个运行中的条目为完成`
|
|
164
|
+
: `Marked ${count} running entries as done`)
|
|
165
|
+
} else {
|
|
166
|
+
api.ui.toast(signals.lang() === "zh" ? "没有需要清理的运行中条目" : "No running entries to clear")
|
|
167
|
+
}
|
|
168
|
+
},
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
id: "opencode-subagent-magazine.subagent.ttl",
|
|
172
|
+
title: "SubAgent Magazine: TTL",
|
|
173
|
+
description: "Set session data retention period (days before auto-cleanup)",
|
|
174
|
+
slash: { name: "subagent-ttl" },
|
|
175
|
+
palette: true,
|
|
176
|
+
run: async () => {
|
|
177
|
+
const curDays = readTTLDays(kv)
|
|
178
|
+
const curLabel = curDays === 0 ? t("ttl.unlimited") : `${curDays}d`
|
|
179
|
+
const days = await context.ui.dialog.select<number>({
|
|
180
|
+
title: `${t("ttl.label")} (${curLabel})`,
|
|
181
|
+
options: [
|
|
182
|
+
{ title: t("ttl.3d"), value: 3 },
|
|
183
|
+
{ title: t("ttl.7d"), value: 7 },
|
|
184
|
+
{ title: t("ttl.14d"), value: 14 },
|
|
185
|
+
{ title: t("ttl.30d"), value: 30 },
|
|
186
|
+
{ title: t("ttl.unlimited"), value: 0 },
|
|
187
|
+
],
|
|
188
|
+
})
|
|
189
|
+
if (days === undefined) return
|
|
190
|
+
kv.set(SETTING_KEYS.ttlDays, String(days))
|
|
191
|
+
api.ui.toast(days === 0 ? t("ttl.toast_unlimited") : t("ttl.toast", { n: days }))
|
|
192
|
+
},
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
id: "opencode-subagent-magazine.subagent.clear-entries",
|
|
196
|
+
title: "SubAgent Magazine: Clear Entries",
|
|
197
|
+
description: "Delete all sub-agent records for the current session (cannot be undone)",
|
|
198
|
+
slash: { name: "subagent-clear-entries" },
|
|
199
|
+
palette: true,
|
|
200
|
+
run: async () => {
|
|
201
|
+
const sid = signals.sessionId
|
|
202
|
+
const sessionObj = api.session.get(sid)
|
|
203
|
+
const parentID = (sessionObj as any)?.parentID as string | undefined
|
|
204
|
+
const cached = globalEntryCache.get(sid)
|
|
205
|
+
let runningCount = 0
|
|
206
|
+
if (cached) {
|
|
207
|
+
for (const [, e] of cached) { if (e.status === "running") runningCount++ }
|
|
208
|
+
}
|
|
209
|
+
const msg = runningCount > 0 ? t("clear.prompt_running", { n: runningCount }) : t("clear.prompt")
|
|
210
|
+
const choice = await context.ui.dialog.select<"yes" | "no">({
|
|
211
|
+
title: t("clear.title"),
|
|
212
|
+
options: [
|
|
213
|
+
{ title: t("clear.title"), value: "yes" },
|
|
214
|
+
{ title: t("cancel.label"), value: "no" },
|
|
215
|
+
],
|
|
216
|
+
})
|
|
217
|
+
if (choice !== "yes") return
|
|
218
|
+
try {
|
|
219
|
+
const data = loadSessionData(kv)
|
|
220
|
+
let count = 0
|
|
221
|
+
if (parentID) {
|
|
222
|
+
if (data[parentID]?.children?.[sid]) {
|
|
223
|
+
const child = data[parentID].children[sid]
|
|
224
|
+
const ids = child.entries?.map((e) => e.id) ?? []
|
|
225
|
+
count = ids.length
|
|
226
|
+
child.entries = []
|
|
227
|
+
child.scroll = 0
|
|
228
|
+
child.expanded = ""
|
|
229
|
+
child.clearedIds = [...new Set([...(child.clearedIds ?? []), ...ids])]
|
|
230
|
+
}
|
|
231
|
+
} else {
|
|
232
|
+
count = data[sid]?.entries?.length ?? 0
|
|
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
|
+
}
|
|
240
|
+
}
|
|
241
|
+
saveSessionData(kv, data)
|
|
242
|
+
globalEntryCache.delete(sid)
|
|
243
|
+
setClearTick((v) => v + 1)
|
|
244
|
+
api.ui.toast(t("clear.done", { n: count }))
|
|
245
|
+
} catch {}
|
|
246
|
+
},
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
id: "opencode-subagent-magazine.subagent.border",
|
|
250
|
+
title: "SubAgent Magazine: Border",
|
|
251
|
+
description: "Show or hide the panel border",
|
|
252
|
+
slash: { name: "subagent-border" },
|
|
253
|
+
palette: true,
|
|
254
|
+
run: () => {
|
|
255
|
+
const cur = Boolean(kv.get(SETTING_KEYS.border, false))
|
|
256
|
+
kv.set(SETTING_KEYS.border, !cur)
|
|
257
|
+
signals.setBorderVisible(!cur)
|
|
258
|
+
api.ui.toast(!cur ? t("borderShown") : t("borderHidden"))
|
|
259
|
+
},
|
|
260
|
+
},
|
|
261
|
+
]
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
export { KV_PREFIX }
|