opencode-subagent-magazine 1.7.1 → 1.7.3

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.
Files changed (53) hide show
  1. package/dist/_version.d.ts +1 -1
  2. package/dist/panel/entry-map.d.ts +29 -0
  3. package/dist/tui.js +166 -19
  4. package/dist/v2.js +145 -22
  5. package/package.json +17 -4
  6. package/dist/_version.js +0 -2
  7. package/dist/clipboard.js +0 -85
  8. package/dist/core/color.js +0 -68
  9. package/dist/core/format.js +0 -113
  10. package/dist/core/index.js +0 -6
  11. package/dist/core/kv.js +0 -64
  12. package/dist/core/state-machine.js +0 -27
  13. package/dist/core/types.js +0 -9
  14. package/dist/core/usage.js +0 -1
  15. package/dist/i18n.js +0 -353
  16. package/dist/index.js +0 -528
  17. package/dist/panel/SubAgentPanel.js +0 -1644
  18. package/dist/panel/entry-map.js +0 -99
  19. package/dist/panel/panel-api.js +0 -1
  20. package/dist/panel/session-routing.js +0 -4
  21. package/dist/panel/store.js +0 -5
  22. package/dist/server.js +0 -14
  23. package/dist/v2/commands.js +0 -434
  24. package/dist/v2/db.js +0 -341
  25. package/dist/v2/index.js +0 -85
  26. package/dist/v2/settings-menu.js +0 -242
  27. package/dist/v2/theme.js +0 -12
  28. package/dist/v2/types.js +0 -6
  29. package/dist/v2/v2-panel-api.js +0 -623
  30. package/src/_version.ts +0 -2
  31. package/src/clipboard.ts +0 -134
  32. package/src/core/color.ts +0 -70
  33. package/src/core/format.ts +0 -105
  34. package/src/core/index.ts +0 -6
  35. package/src/core/kv.ts +0 -78
  36. package/src/core/state-machine.ts +0 -46
  37. package/src/core/types.ts +0 -85
  38. package/src/core/usage.ts +0 -16
  39. package/src/i18n.ts +0 -365
  40. package/src/index.tsx +0 -584
  41. package/src/panel/SubAgentPanel.tsx +0 -1877
  42. package/src/panel/entry-map.ts +0 -116
  43. package/src/panel/panel-api.ts +0 -100
  44. package/src/panel/session-routing.ts +0 -10
  45. package/src/panel/store.ts +0 -8
  46. package/src/server.ts +0 -19
  47. package/src/v2/commands.ts +0 -422
  48. package/src/v2/db.ts +0 -346
  49. package/src/v2/index.tsx +0 -149
  50. package/src/v2/settings-menu.ts +0 -259
  51. package/src/v2/theme.ts +0 -14
  52. package/src/v2/types.ts +0 -167
  53. package/src/v2/v2-panel-api.ts +0 -520
package/src/index.tsx DELETED
@@ -1,584 +0,0 @@
1
- /** @jsxImportSource @opentui/solid */
2
-
3
- import type { JSX } from "@opentui/solid"
4
- import type {
5
- TuiPlugin,
6
- TuiPluginApi,
7
- TuiSlotContext,
8
- TuiSlotPlugin,
9
- TuiPluginModule,
10
- } from "@opencode-ai/plugin/tui"
11
- import { createSignal } from "solid-js"
12
- import { PLUGIN_VERSION } from "./_version"
13
- import { LANG_META, createT, detectLang } from "./i18n"
14
- import type { Lang, SortOrder, ScrollMode, SubStatus, SharedSignals, TimeFormat } from "./core/types"
15
- import { KV_PREFIX } from "./core/kv"
16
- import { TIME_FORMATS, TIME_FORMAT_SAMPLES } from "./core/format"
17
- import type { PanelApi } from "./panel/panel-api"
18
- import { SubAgentPanel } from "./panel/SubAgentPanel"
19
- import { globalEntryCache, setClearTick } from "./panel/store"
20
-
21
- // Plugin entry
22
- // ===================================================================
23
-
24
- function createSidebarSlot(api: TuiPluginApi, panelApi: PanelApi, sig: SharedSignals): TuiSlotPlugin {
25
- return {
26
- order: 60,
27
- slots: {
28
- sidebar_content(ctx: TuiSlotContext, input: { session_id: string }): JSX.Element {
29
- sig.sessionId = input.session_id
30
- return (
31
- <SubAgentPanel
32
- api={panelApi}
33
- theme={ctx.theme.current as Record<string, unknown>}
34
- lang={sig.lang}
35
- maxEntries={sig.maxEntries}
36
- sortOrder={sig.sortOrder}
37
- scrollMode={sig.scrollMode}
38
- borderVisible={sig.borderVisible}
39
- showEntryCost={sig.showEntryCost}
40
- showEntryTime={sig.showEntryTime}
41
- showEntryTokens={sig.showEntryTokens}
42
- timeFormat={sig.timeFormat}
43
- showOrigin={sig.showOrigin}
44
- sessionId={input.session_id}
45
- />
46
- )
47
- },
48
- },
49
- }
50
- }
51
-
52
- const tui: TuiPlugin = async (api: TuiPluginApi) => {
53
- // ── language ──
54
- const stored = String(api.kv.get(`${KV_PREFIX}.lang`, ""))
55
- const initialLang: Lang =
56
- LANG_META.some((m) => m.code === stored) ? (stored as Lang) : detectLang()
57
- const [lang, setLang] = createSignal<Lang>(initialLang)
58
- const [maxEntries, setMaxEntries] = createSignal(
59
- parseInt(String(api.kv.get(`${KV_PREFIX}.max_entries`, "10")), 10) || 10
60
- )
61
- const [sortOrder, setSortOrder] = createSignal<SortOrder>(
62
- String(api.kv.get(`${KV_PREFIX}.order`, "desc")) === "asc" ? "asc" : "desc"
63
- )
64
- const [scrollMode, setScrollMode] = createSignal<ScrollMode>(
65
- String(api.kv.get(`${KV_PREFIX}.scroll_mode`, "wheel")) === "click" ? "click" : "wheel"
66
- )
67
- const [borderVisible, setBorderVisible] = createSignal<boolean>(
68
- (api.kv.get(`${KV_PREFIX}.border`, false) as boolean) === true
69
- )
70
- const [showEntryCost, setShowEntryCost] = createSignal<boolean>(
71
- (api.kv.get(`${KV_PREFIX}.show_entry_cost`, false) as boolean) === true
72
- )
73
- const [showEntryTime, setShowEntryTime] = createSignal<boolean>(
74
- (api.kv.get(`${KV_PREFIX}.show_entry_time`, true) as boolean) !== false
75
- )
76
- const [showEntryTokens, setShowEntryTokens] = createSignal<boolean>(
77
- (api.kv.get(`${KV_PREFIX}.show_entry_tokens`, true) as boolean) !== false
78
- )
79
- const storedTimeFormat = String(api.kv.get(`${KV_PREFIX}.time_format`, "short"))
80
- const [timeFormat, setTimeFormat] = createSignal<TimeFormat>(
81
- (TIME_FORMATS as readonly string[]).includes(storedTimeFormat) ? (storedTimeFormat as TimeFormat) : "short"
82
- )
83
- const [showOrigin, setShowOrigin] = createSignal<boolean>(
84
- (api.kv.get(`${KV_PREFIX}.show_origin`, false) as boolean) === true
85
- )
86
- const [dbSync, setDbSync] = createSignal<boolean>(
87
- (api.kv.get(`${KV_PREFIX}.db_sync`, true) as boolean) !== false
88
- )
89
-
90
- const signals: SharedSignals = { lang, setLang, maxEntries, setMaxEntries, sortOrder, setSortOrder, scrollMode, setScrollMode, borderVisible, setBorderVisible, showEntryCost, setShowEntryCost, showEntryTime, setShowEntryTime, showEntryTokens, setShowEntryTokens, timeFormat, setTimeFormat, showOrigin, setShowOrigin, dbSync, setDbSync, sessionId: "" }
91
-
92
- // ── V1 PanelApi adapter: wraps the V1 host API into the shared panel contract ──
93
- const v1Api: PanelApi = {
94
- kv: api.kv as any,
95
- usage: {
96
- readSessionTokens: (sid: string): number | undefined => {
97
- if (!sid) return undefined
98
- try {
99
- const msgs = api.state.session.messages(sid)
100
- if (msgs) {
101
- for (let i = (msgs as any[]).length - 1; i >= 0; i--) {
102
- const m = (msgs as any[])[i]
103
- if (m.role !== "assistant") continue
104
- const t = m.tokens
105
- if (!t || !(Number(t.output) > 0)) continue
106
- const ctx =
107
- (Number(t.input) || 0) +
108
- (Number(t.output) || 0) +
109
- (Number(t.reasoning) || 0) +
110
- (Number(t.cache?.read) || 0) +
111
- (Number(t.cache?.write) || 0)
112
- if (ctx > 0) return ctx
113
- }
114
- }
115
- return undefined
116
- } catch {
117
- return undefined
118
- }
119
- },
120
- readSessionCost: (sid: string): number | undefined => {
121
- if (!sid) return undefined
122
- try {
123
- const session = api.state.session.get(sid)
124
- if (session?.cost != null && session.cost > 0) return session.cost
125
- const msgs = api.state.session.messages(sid)
126
- if (!msgs) return undefined
127
- let total = 0
128
- for (const m of msgs as any[]) {
129
- if (m.role === "assistant" && typeof m.cost === "number") total += m.cost
130
- }
131
- return total > 0 ? total : undefined
132
- } catch {
133
- return undefined
134
- }
135
- },
136
- readSessionModel: (sid: string): string | undefined => {
137
- if (!sid) return undefined
138
- try {
139
- const msgs = api.state.session.messages(sid)
140
- if (msgs) {
141
- for (let i = (msgs as any[]).length - 1; i >= 0; i--) {
142
- const m = (msgs as any[])[i]
143
- if (m.role === "assistant" && m.modelID) return String(m.modelID)
144
- }
145
- }
146
- return undefined
147
- } catch {
148
- return undefined
149
- }
150
- },
151
- readSessionTodo: (sid: string): { total: number; done: number } | undefined => {
152
- if (!sid) return undefined
153
- try {
154
- const todos = api.state.session.todo(sid)
155
- if (!todos || todos.length === 0) return undefined
156
- let done = 0
157
- for (const t of todos) {
158
- if (t.status === "completed" || t.status === "cancelled") done++
159
- }
160
- return { total: todos.length, done }
161
- } catch {
162
- return undefined
163
- }
164
- },
165
- },
166
- session: {
167
- get: (sid) => { try { return api.state.session.get(sid) as any } catch { return undefined } },
168
- status: (sid) => { try { return api.state.session.status(sid) as any } catch { return undefined } },
169
- messages: (sid) => { try { return api.state.session.messages(sid) as any[] } catch { return undefined } },
170
- part: (messageID) => { try { return api.state.part(messageID) as any[] } catch { return undefined } },
171
- },
172
- event: {
173
- on: (type, cb) => {
174
- switch (type) {
175
- case "part.updated":
176
- return api.event.on("message.part.updated", (e) =>
177
- cb({ type, payload: { part: (e as any).properties?.part } }))
178
- case "message.updated":
179
- return api.event.on("message.updated", () => cb({ type }))
180
- case "session.idle":
181
- return api.event.on("session.idle", (e) => cb({ type, payload: (e as any).properties }))
182
- case "session.error":
183
- return api.event.on("session.error", (e) => cb({ type, payload: (e as any).properties }))
184
- }
185
- },
186
- },
187
- client: {
188
- abort: (input) => api.client.session.abort(input).then(() => {}),
189
- },
190
- route: {
191
- navigateSession: (sessionID) => api.route.navigate("session", { sessionID }),
192
- },
193
- ui: {
194
- toast: (message, opts) => api.ui.toast({ ...opts, message } as any),
195
- },
196
- settings: {
197
- lang: () => lang(),
198
- maxEntries: () => maxEntries(),
199
- sortOrder: () => sortOrder(),
200
- scrollMode: () => scrollMode(),
201
- },
202
- }
203
-
204
- api.slots.register(createSidebarSlot(api, v1Api, signals))
205
-
206
- // ── slash command: /subagent-lang ──
207
- api.command?.register(() => [
208
- {
209
- title: "SubAgent Magazine: Language",
210
- value: "subagent-lang",
211
- description: "Switch display language (中文 / English)",
212
- slash: { name: "subagent-lang" },
213
- onSelect: (dialog) => {
214
- dialog?.replace(() => (
215
- <api.ui.DialogSelect
216
- title="Language / 语言"
217
- options={LANG_META.map((m) => ({ title: m.label, value: m.code }))}
218
- onSelect={(opt) => {
219
- const l = opt.value as Lang
220
- setLang(l)
221
- api.kv.set(`${KV_PREFIX}.lang`, l)
222
- api.ui.toast({
223
- message: "Language: " + (LANG_META.find((m) => m.code === l)?.label ?? l),
224
- })
225
- dialog?.clear()
226
- }}
227
- />
228
- ))
229
- },
230
- },
231
- {
232
- title: "SubAgent Magazine: Sort Order",
233
- value: "subagent-order",
234
- description: "Set sub-agent entry sort order (desc / asc)",
235
- slash: { name: "subagent-order" },
236
- onSelect: (dialog) => {
237
- const t = createT(() => lang())
238
- dialog?.replace(() => (
239
- <api.ui.DialogSelect
240
- title="Sort Order / 排序方式"
241
- options={[
242
- { title: t("order.desc"), value: "desc" },
243
- { title: t("order.asc"), value: "asc" },
244
- ]}
245
- onSelect={(opt) => {
246
- const o = opt.value as SortOrder
247
- setSortOrder(o)
248
- api.kv.set(`${KV_PREFIX}.order`, o)
249
- api.ui.toast({
250
- message: o === "desc" ? t("order.desc") : t("order.asc"),
251
- })
252
- dialog?.clear()
253
- }}
254
- />
255
- ))
256
- },
257
- },
258
- {
259
- title: "SubAgent Magazine: Scroll Mode",
260
- value: "subagent-scroll",
261
- description: "Set scroll mode (wheel / click)",
262
- slash: { name: "subagent-scroll" },
263
- onSelect: (dialog) => {
264
- const t = createT(() => lang())
265
- dialog?.replace(() => (
266
- <api.ui.DialogSelect
267
- title="Scroll Mode / 滚动模式"
268
- options={[
269
- { title: t("scroll.wheel"), value: "wheel" },
270
- { title: t("scroll.click"), value: "click" },
271
- ]}
272
- onSelect={(opt) => {
273
- const m = opt.value as ScrollMode
274
- setScrollMode(m)
275
- api.kv.set(`${KV_PREFIX}.scroll_mode`, m)
276
- api.ui.toast({
277
- message: m === "wheel" ? t("scroll.wheel") : t("scroll.click"),
278
- })
279
- dialog?.clear()
280
- }}
281
- />
282
- ))
283
- },
284
- },
285
- {
286
- title: "SubAgent Magazine: Max Entries",
287
- value: "subagent-max",
288
- description: "Set max visible sub-agent entries in sidebar",
289
- slash: { name: "subagent-max" },
290
- onSelect: (dialog) => {
291
- dialog?.replace(() => (
292
- <api.ui.DialogPrompt
293
- title="Max Visible Entries"
294
- description={() => (
295
- <text>Number of entries to show in the sidebar (1–50)</text>
296
- )}
297
- value={String(maxEntries())}
298
- onConfirm={(val) => {
299
- const n = Math.max(1, Math.min(50, parseInt(val, 10) || 10))
300
- setMaxEntries(n)
301
- api.kv.set(`${KV_PREFIX}.max_entries`, n)
302
- api.ui.toast({ message: `Max entries: ${n}` })
303
- dialog?.clear()
304
- }}
305
- />
306
- ))
307
- },
308
- },
309
- {
310
- title: "SubAgent Magazine: Version",
311
- value: "subagent-version",
312
- description: "Show plugin version",
313
- slash: { name: "subagent-version" },
314
- onSelect: (dialog) => {
315
- api.ui.toast({ message: `opencode-subagent-magazine v${PLUGIN_VERSION}` })
316
- dialog?.clear()
317
- },
318
- },
319
- {
320
- title: "SubAgent Magazine: Session",
321
- value: "subagent-session",
322
- description: "Show current session ID",
323
- slash: { name: "subagent-session" },
324
- onSelect: (dialog) => {
325
- api.ui.toast({ message: `Session: ${signals.sessionId}` })
326
- dialog?.clear()
327
- },
328
- },
329
- {
330
- title: "SubAgent Magazine: Clear Running",
331
- value: "subagent-clear-running",
332
- description: "Mark all running sub-agent entries as done (for stuck/zombie entries)",
333
- slash: { name: "subagent-clear-running" },
334
- onSelect: (dialog) => {
335
- const sid = signals.sessionId
336
- const entries = globalEntryCache.get(sid)
337
- if (!entries || entries.size === 0) {
338
- const msg = signals.lang() === "zh" ? "暂无子代理条目" : "No sub-agent entries found"
339
- api.ui.toast({ message: msg })
340
- dialog?.clear()
341
- return
342
- }
343
- let count = 0
344
- for (const [, entry] of entries) {
345
- if (entry.status === "running" || entry.status === "cancel_requested") {
346
- entry.status = "done" as SubStatus
347
- entry.endedAt = Date.now()
348
- count++
349
- }
350
- }
351
- if (count > 0) {
352
- // 立即写 KV
353
- try {
354
- const data = JSON.parse(String(api.kv.get(`${KV_PREFIX}.session_data`, "{}")))
355
- const sessionObj = api.state.session.get(sid)
356
- const parentID = (sessionObj as any)?.parentID as string | undefined
357
- if (parentID) {
358
- if (!data[parentID]) data[parentID] = { ts: Date.now(), entries: [], scroll: 0, expanded: "", children: {} }
359
- if (!data[parentID].children) data[parentID].children = {}
360
- if (!data[parentID].children[sid]) data[parentID].children[sid] = { scroll: 0, expanded: "", entries: [] }
361
- data[parentID].children[sid] = { ...data[parentID].children[sid], entries: [...entries.values()] }
362
- } else {
363
- data[sid] = {
364
- ts: Date.now(),
365
- entries: [...entries.values()],
366
- scroll: data[sid]?.scroll ?? 0,
367
- expanded: data[sid]?.expanded ?? "",
368
- children: data[sid]?.children ?? {},
369
- }
370
- }
371
- api.kv.set(`${KV_PREFIX}.session_data`, JSON.stringify(data))
372
- } catch {}
373
- const msg = signals.lang() === "zh"
374
- ? `已标记 ${count} 个运行中的条目为完成`
375
- : `Marked ${count} running entries as done`
376
- api.ui.toast({ message: msg })
377
- } else {
378
- const msg = signals.lang() === "zh"
379
- ? "没有需要清理的运行中条目"
380
- : "No running entries to clear"
381
- api.ui.toast({ message: msg })
382
- }
383
- dialog?.clear()
384
- },
385
- },
386
- {
387
- title: "SubAgent Magazine: TTL",
388
- value: "subagent-ttl",
389
- description: "Set session data retention period (days before auto-cleanup)",
390
- slash: { name: "subagent-ttl" },
391
- onSelect: (dialog) => {
392
- const t = createT(() => signals.lang())
393
- const curRaw = parseInt(String(api.kv.get(`${KV_PREFIX}.ttl_days`, "3")), 10)
394
- const curDays = Number.isNaN(curRaw) ? 3 : curRaw
395
- const curLabel = curDays === 0 ? t("ttl.unlimited") : `${curDays}d`
396
- dialog?.replace(() => (
397
- <api.ui.DialogSelect
398
- title={`${t("ttl.label")} (${curLabel})`}
399
- options={[
400
- { title: t("ttl.3d"), value: "3" },
401
- { title: t("ttl.7d"), value: "7" },
402
- { title: t("ttl.14d"), value: "14" },
403
- { title: t("ttl.30d"), value: "30" },
404
- { title: t("ttl.unlimited"), value: "0" },
405
- ]}
406
- onSelect={(opt) => {
407
- const days = parseInt(opt.value, 10)
408
- api.kv.set(`${KV_PREFIX}.ttl_days`, String(days))
409
- const msg = days === 0 ? t("ttl.toast_unlimited") : t("ttl.toast", { n: days })
410
- api.ui.toast({ message: msg })
411
- dialog?.clear()
412
- }}
413
- />
414
- ))
415
- },
416
- },
417
- {
418
- title: "SubAgent Magazine: Clear Entries",
419
- value: "subagent-clear-entries",
420
- description: "Delete all sub-agent records for the current session (cannot be undone)",
421
- slash: { name: "subagent-clear-entries" },
422
- onSelect: (dialog) => {
423
- const t = createT(() => signals.lang())
424
- const sid = signals.sessionId
425
- const sessionObj = api.state.session.get(sid)
426
- const parentID = (sessionObj as any)?.parentID as string | undefined
427
- // 检查是否存在运行中的条目
428
- const cached = globalEntryCache.get(sid)
429
- let runningCount = 0
430
- if (cached) {
431
- for (const [, e] of cached) { if (e.status === "running") runningCount++ }
432
- }
433
- const msg = runningCount > 0
434
- ? t("clear.prompt_running", { n: runningCount })
435
- : t("clear.prompt")
436
- dialog?.replace(() => (
437
- <api.ui.DialogConfirm
438
- title={t("clear.title")}
439
- message={msg}
440
- onConfirm={() => {
441
- try {
442
- const data = JSON.parse(String(api.kv.get(`${KV_PREFIX}.session_data`, "{}")))
443
- let count = 0
444
- if (parentID) {
445
- if (data[parentID]?.children?.[sid]) {
446
- const child = data[parentID].children[sid]
447
- const ids = child.entries?.map((e: any) => e.id) ?? []
448
- count = ids.length
449
- child.entries = []
450
- child.scroll = 0
451
- child.expanded = ""
452
- child.clearedIds = [...new Set([...(child.clearedIds ?? []), ...ids])]
453
- }
454
- } else {
455
- count = data[sid]?.entries?.length ?? 0
456
- if (data[sid]) {
457
- const ids = data[sid].entries?.map((e: any) => e.id) ?? []
458
- data[sid].entries = []
459
- data[sid].scroll = 0
460
- data[sid].expanded = ""
461
- data[sid].clearedIds = [...new Set([...(data[sid].clearedIds ?? []), ...ids])]
462
- }
463
- }
464
- api.kv.set(`${KV_PREFIX}.session_data`, JSON.stringify(data))
465
- globalEntryCache.delete(sid)
466
- setClearTick((v) => v + 1)
467
- const msg = t("clear.done", { n: count })
468
- api.ui.toast({ message: msg })
469
- } catch {}
470
- dialog?.clear()
471
- }}
472
- />
473
- ))
474
- },
475
- },
476
- {
477
- title: "SubAgent Magazine: Border",
478
- value: "subagent-border",
479
- description: "Show or hide the panel border",
480
- slash: { name: "subagent-border" },
481
- onSelect: (dialog) => {
482
- const t = createT(() => signals.lang())
483
- const cur = Boolean(api.kv.get(`${KV_PREFIX}.border`, false))
484
- api.kv.set(`${KV_PREFIX}.border`, !cur)
485
- signals.setBorderVisible(!cur)
486
- api.ui.toast({ message: !cur ? t("borderShown") : t("borderHidden") })
487
- dialog?.clear()
488
- },
489
- },
490
- {
491
- title: "SubAgent Magazine: Entry Cost",
492
- value: "subagent-cost",
493
- description: "Show or hide the per-sub-agent cost in the sidebar",
494
- slash: { name: "subagent-cost" },
495
- onSelect: (dialog) => {
496
- const t = createT(() => signals.lang())
497
- const cur = Boolean(api.kv.get(`${KV_PREFIX}.show_entry_cost`, false))
498
- api.kv.set(`${KV_PREFIX}.show_entry_cost`, !cur)
499
- signals.setShowEntryCost(!cur)
500
- api.ui.toast({
501
- message: `${t("settings.showEntryCost")}: ${!cur ? t("settings.on") : t("settings.off")}`,
502
- })
503
- dialog?.clear()
504
- },
505
- },
506
- {
507
- title: "SubAgent Magazine: Entry Time",
508
- value: "subagent-time",
509
- description: "Show or hide the elapsed time in the sidebar list",
510
- slash: { name: "subagent-time" },
511
- onSelect: (dialog) => {
512
- const t = createT(() => signals.lang())
513
- const cur = Boolean(api.kv.get(`${KV_PREFIX}.show_entry_time`, true))
514
- api.kv.set(`${KV_PREFIX}.show_entry_time`, !cur)
515
- signals.setShowEntryTime(!cur)
516
- api.ui.toast({
517
- message: `${t("settings.showEntryTime")}: ${!cur ? t("settings.on") : t("settings.off")}`,
518
- })
519
- dialog?.clear()
520
- },
521
- },
522
- {
523
- title: "SubAgent Magazine: Entry Tokens",
524
- value: "subagent-tokens",
525
- description: "Show or hide the token count in the sidebar list",
526
- slash: { name: "subagent-tokens" },
527
- onSelect: (dialog) => {
528
- const t = createT(() => signals.lang())
529
- const cur = Boolean(api.kv.get(`${KV_PREFIX}.show_entry_tokens`, true))
530
- api.kv.set(`${KV_PREFIX}.show_entry_tokens`, !cur)
531
- signals.setShowEntryTokens(!cur)
532
- api.ui.toast({
533
- message: `${t("settings.showEntryTokens")}: ${!cur ? t("settings.on") : t("settings.off")}`,
534
- })
535
- dialog?.clear()
536
- },
537
- },
538
- {
539
- title: "SubAgent Magazine: Time Format",
540
- value: "subagent-time-format",
541
- description: "Set how elapsed time is displayed",
542
- slash: { name: "subagent-time-format" },
543
- onSelect: (dialog) => {
544
- dialog?.replace(() => (
545
- <api.ui.DialogSelect
546
- title="Time Format / 时间格式"
547
- options={TIME_FORMATS.map((f) => ({ title: `${f} — ${TIME_FORMAT_SAMPLES[f]}`, value: f }))}
548
- onSelect={(opt) => {
549
- const f = opt.value as TimeFormat
550
- setTimeFormat(f)
551
- api.kv.set(`${KV_PREFIX}.time_format`, f)
552
- const t = createT(() => lang())
553
- api.ui.toast({ message: `${t("settings.timeFormat")}: ${TIME_FORMAT_SAMPLES[f]}` })
554
- dialog?.clear()
555
- }}
556
- />
557
- ))
558
- },
559
- },
560
- {
561
- title: "SubAgent Magazine: Origin Indicators",
562
- value: "subagent-origin",
563
- description: "Show or hide origin markers (⇢/↳) in the sidebar list",
564
- slash: { name: "subagent-origin" },
565
- onSelect: (dialog) => {
566
- const t = createT(() => signals.lang())
567
- const cur = Boolean(api.kv.get(`${KV_PREFIX}.show_origin`, false))
568
- api.kv.set(`${KV_PREFIX}.show_origin`, !cur)
569
- signals.setShowOrigin(!cur)
570
- api.ui.toast({
571
- message: `${t("settings.showOrigin")}: ${!cur ? t("settings.on") : t("settings.off")}`,
572
- })
573
- dialog?.clear()
574
- },
575
- },
576
- ])
577
- }
578
-
579
- const mod: TuiPluginModule & { id: string } = {
580
- id: "opencode-subagent-magazine",
581
- tui,
582
- }
583
-
584
- export default mod