opencode-subagent-magazine 1.6.1 → 1.7.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 +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 +21 -1
- package/dist/core/kv.js +33 -1
- package/dist/core/types.d.ts +12 -0
- package/dist/i18n.d.ts +19 -0
- package/dist/i18n.js +76 -0
- package/dist/index.js +73 -2
- package/dist/panel/SubAgentPanel.d.ts +5 -1
- package/dist/panel/SubAgentPanel.js +568 -261
- package/dist/panel/entry-map.d.ts +16 -0
- package/dist/panel/entry-map.js +74 -0
- package/dist/panel/panel-api.d.ts +8 -0
- package/dist/tui.js +927 -431
- package/dist/v2/commands.js +188 -44
- package/dist/v2/db.d.ts +47 -0
- package/dist/v2/db.js +267 -0
- package/dist/v2/index.js +26 -6
- package/dist/v2/settings-menu.d.ts +9 -0
- package/dist/v2/settings-menu.js +225 -0
- package/dist/v2/v2-panel-api.js +304 -44
- package/dist/v2.js +1741 -560
- package/package.json +1 -1
- package/src/_version.ts +1 -1
- package/src/core/format.ts +44 -0
- package/src/core/kv.ts +42 -2
- package/src/core/types.ts +12 -0
- package/src/i18n.ts +76 -0
- package/src/index.tsx +93 -2
- package/src/panel/SubAgentPanel.tsx +541 -233
- package/src/panel/entry-map.ts +77 -1
- package/src/panel/panel-api.ts +8 -0
- package/src/v2/commands.ts +187 -43
- package/src/v2/db.ts +274 -0
- package/src/v2/index.tsx +49 -7
- package/src/v2/settings-menu.ts +241 -0
- package/src/v2/types.ts +3 -1
- package/src/v2/v2-panel-api.ts +241 -33
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.7.0";
|
package/src/core/format.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { TimeFormat } from "./types"
|
|
2
|
+
|
|
1
3
|
/** Visual width of a single character (CJK/wide chars count as 2). */
|
|
2
4
|
function charColumns(c: string): number {
|
|
3
5
|
const code = c.codePointAt(0) ?? 0
|
|
@@ -47,6 +49,48 @@ export function fmtDurationShort(ms: number, running: boolean): string {
|
|
|
47
49
|
return `${m}m${s}s`
|
|
48
50
|
}
|
|
49
51
|
|
|
52
|
+
/** All supported elapsed-time display modes, in picker order. */
|
|
53
|
+
export const TIME_FORMATS: readonly TimeFormat[] = ["short", "decimal", "clock", "compact", "seconds"]
|
|
54
|
+
|
|
55
|
+
/** Neutral samples shown in the time-format picker (language independent). */
|
|
56
|
+
export const TIME_FORMAT_SAMPLES: Record<TimeFormat, string> = {
|
|
57
|
+
decimal: "45.3s · 9.5m · 1.9h",
|
|
58
|
+
short: "45.32s · 112m35s",
|
|
59
|
+
clock: "0:45 · 1:52:35",
|
|
60
|
+
compact: "45s · 1h52m",
|
|
61
|
+
seconds: "45s · 6755s",
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Formats elapsed milliseconds using the configured display mode. */
|
|
65
|
+
export function fmtDuration(ms: number, running: boolean, mode: TimeFormat = "short"): string {
|
|
66
|
+
if (running && ms < 2000) return ""
|
|
67
|
+
const t = Number.isFinite(ms) && ms > 0 ? ms : 0
|
|
68
|
+
switch (mode) {
|
|
69
|
+
case "short":
|
|
70
|
+
return fmtDurationShort(t, false)
|
|
71
|
+
case "compact": {
|
|
72
|
+
if (t < 60000) return `${Math.floor(t / 1000)}s`
|
|
73
|
+
if (t < 3600000) return `${Math.floor(t / 60000)}m${Math.floor((t % 60000) / 1000)}s`
|
|
74
|
+
return `${Math.floor(t / 3600000)}h${Math.floor((t % 3600000) / 60000)}m`
|
|
75
|
+
}
|
|
76
|
+
case "clock": {
|
|
77
|
+
const s = Math.floor((t % 60000) / 1000)
|
|
78
|
+
if (t < 3600000) return `${Math.floor(t / 60000)}:${String(s).padStart(2, "0")}`
|
|
79
|
+
const m = Math.floor((t % 3600000) / 60000)
|
|
80
|
+
return `${Math.floor(t / 3600000)}:${String(m).padStart(2, "0")}:${String(s).padStart(2, "0")}`
|
|
81
|
+
}
|
|
82
|
+
case "seconds":
|
|
83
|
+
return `${Math.floor(t / 1000)}s`
|
|
84
|
+
case "decimal":
|
|
85
|
+
default: {
|
|
86
|
+
// Promote units when rounding would show "60.0s" / "60.0m".
|
|
87
|
+
if (t < 59950) return `${(t / 1000).toFixed(1)}s`
|
|
88
|
+
if (t < 3597000) return `${(t / 60000).toFixed(1)}m`
|
|
89
|
+
return `${(t / 3600000).toFixed(1)}h`
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
50
94
|
export function fmtTokens(n: number): string {
|
|
51
95
|
if (n < 1000) return `${n}`
|
|
52
96
|
if (n < 1000000) return `${(n / 1000).toFixed(1)}k`
|
package/src/core/kv.ts
CHANGED
|
@@ -7,6 +7,13 @@ export const SESSION_DATA_KEY = `${KV_PREFIX}.session_data`
|
|
|
7
7
|
export interface KVApi {
|
|
8
8
|
get(key: string, fallback?: unknown): unknown
|
|
9
9
|
set(key: string, value: unknown): void
|
|
10
|
+
/**
|
|
11
|
+
* Optional atomic read-modify-write. When the host exposes it (V2), the
|
|
12
|
+
* updater runs under the host storage lock against the latest on-disk value,
|
|
13
|
+
* which is what makes concurrent TUI instances safe: no instance can
|
|
14
|
+
* overwrite another one's fresh writes with a stale in-memory snapshot.
|
|
15
|
+
*/
|
|
16
|
+
update?(key: string, updater: (current: unknown) => unknown): void | Promise<void>
|
|
10
17
|
}
|
|
11
18
|
|
|
12
19
|
/** Setting keys shared by V1/V2 (values are raw strings in KV). */
|
|
@@ -18,6 +25,11 @@ export const SETTING_KEYS = {
|
|
|
18
25
|
open: `${KV_PREFIX}.open`,
|
|
19
26
|
ttlDays: `${KV_PREFIX}.ttl_days`,
|
|
20
27
|
border: `${KV_PREFIX}.border`,
|
|
28
|
+
showEntryCost: `${KV_PREFIX}.show_entry_cost`,
|
|
29
|
+
showEntryTime: `${KV_PREFIX}.show_entry_time`,
|
|
30
|
+
showEntryTokens: `${KV_PREFIX}.show_entry_tokens`,
|
|
31
|
+
timeFormat: `${KV_PREFIX}.time_format`,
|
|
32
|
+
dbSync: `${KV_PREFIX}.db_sync`,
|
|
21
33
|
} as const
|
|
22
34
|
|
|
23
35
|
export function loadSessionData(kv: KVApi): Record<string, SessionRecord> {
|
|
@@ -27,8 +39,36 @@ export function loadSessionData(kv: KVApi): Record<string, SessionRecord> {
|
|
|
27
39
|
} catch { return {} }
|
|
28
40
|
}
|
|
29
41
|
|
|
30
|
-
|
|
31
|
-
|
|
42
|
+
/**
|
|
43
|
+
* Atomic read-merge-write of the session store.
|
|
44
|
+
*
|
|
45
|
+
* `mutator` receives the latest persisted map (read under the host lock when
|
|
46
|
+
* `kv.update` is available) and may mutate it freely; the result is written
|
|
47
|
+
* back. Every writer must go through here: writing a whole in-memory snapshot
|
|
48
|
+
* directly would silently drop entries created by the other TUIs.
|
|
49
|
+
*/
|
|
50
|
+
export function updateSessionData(
|
|
51
|
+
kv: KVApi,
|
|
52
|
+
mutator: (data: Record<string, SessionRecord>) => void,
|
|
53
|
+
): void | Promise<void> {
|
|
54
|
+
try {
|
|
55
|
+
if (typeof kv.update === "function") {
|
|
56
|
+
return kv.update(SESSION_DATA_KEY, (raw) => {
|
|
57
|
+
const data = parseSessionData(raw)
|
|
58
|
+
mutator(data)
|
|
59
|
+
return JSON.stringify(data)
|
|
60
|
+
})
|
|
61
|
+
}
|
|
62
|
+
// Fallback (V1 hosts without atomic update): read-modify-write. The window
|
|
63
|
+
// is small and the mutation is merged against the value we just read.
|
|
64
|
+
const data = loadSessionData(kv)
|
|
65
|
+
mutator(data)
|
|
66
|
+
kv.set(SESSION_DATA_KEY, JSON.stringify(data))
|
|
67
|
+
} catch {}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function parseSessionData(raw: unknown): Record<string, SessionRecord> {
|
|
71
|
+
try { return JSON.parse(String(raw ?? "{}")) } catch { return {} }
|
|
32
72
|
}
|
|
33
73
|
|
|
34
74
|
export function readTTLDays(kv: KVApi): number {
|
package/src/core/types.ts
CHANGED
|
@@ -25,6 +25,8 @@ export interface SubEntry {
|
|
|
25
25
|
export type Lang = LangCode
|
|
26
26
|
export type SortOrder = "desc" | "asc"
|
|
27
27
|
export type ScrollMode = "wheel" | "click"
|
|
28
|
+
/** How elapsed time is rendered in the sidebar (see TIME_FORMAT_SAMPLES). */
|
|
29
|
+
export type TimeFormat = "decimal" | "short" | "clock" | "compact" | "seconds"
|
|
28
30
|
|
|
29
31
|
/** OpenCode built-in tool names that spawn sub-agents or delegate tasks. */
|
|
30
32
|
export const SUBAGENT_TOOLS = new Set(["task", "subagent", "delegate", "call_omo_agent"])
|
|
@@ -56,5 +58,15 @@ export interface SharedSignals {
|
|
|
56
58
|
setScrollMode: (m: ScrollMode) => void
|
|
57
59
|
borderVisible: () => boolean
|
|
58
60
|
setBorderVisible: (v: boolean) => void
|
|
61
|
+
showEntryCost: () => boolean
|
|
62
|
+
setShowEntryCost: (v: boolean) => void
|
|
63
|
+
showEntryTime: () => boolean
|
|
64
|
+
setShowEntryTime: (v: boolean) => void
|
|
65
|
+
showEntryTokens: () => boolean
|
|
66
|
+
setShowEntryTokens: (v: boolean) => void
|
|
67
|
+
timeFormat: () => TimeFormat
|
|
68
|
+
setTimeFormat: (f: TimeFormat) => void
|
|
69
|
+
dbSync: () => boolean
|
|
70
|
+
setDbSync: (v: boolean) => void
|
|
59
71
|
sessionId: string
|
|
60
72
|
}
|
package/src/i18n.ts
CHANGED
|
@@ -47,6 +47,7 @@ const ZH_T = {
|
|
|
47
47
|
"clear.prompt": "确定清除当前会话所有子代理记录?此操作不可撤销。",
|
|
48
48
|
"clear.prompt_running": "当前有 {n} 个运行中的子代理,清除后将不可恢复。确定继续?",
|
|
49
49
|
"clear.done": "已清除 {n} 条子代理记录",
|
|
50
|
+
"clear.failed": "清除失败",
|
|
50
51
|
"clear.empty": "当前会话无子代理记录",
|
|
51
52
|
"cancel.no_session": "子会话 ID 不可用",
|
|
52
53
|
"cancel.not_child": "目标不是子会话",
|
|
@@ -58,6 +59,24 @@ const ZH_T = {
|
|
|
58
59
|
"cancel.failed": "取消失败",
|
|
59
60
|
"borderShown": "面板边框 已显示",
|
|
60
61
|
"borderHidden": "面板边框 已隐藏",
|
|
62
|
+
"settings.title": "SubAgent Magazine — 设置",
|
|
63
|
+
"settings.section.general": "通用",
|
|
64
|
+
"settings.lang": "语言",
|
|
65
|
+
"settings.max": "最大条目数",
|
|
66
|
+
"settings.order": "排序方式",
|
|
67
|
+
"settings.scroll": "滚动模式",
|
|
68
|
+
"settings.ttl": "保留期限",
|
|
69
|
+
"settings.border": "面板边框",
|
|
70
|
+
"settings.showEntryCost": "每条子代理显示费用",
|
|
71
|
+
"settings.showEntryTime": "列表中显示耗时",
|
|
72
|
+
"settings.showEntryTokens": "列表中显示 Token 数",
|
|
73
|
+
"settings.timeFormat": "时间格式",
|
|
74
|
+
"settings.dbSync": "本地数据库补全",
|
|
75
|
+
"settings.on": "开",
|
|
76
|
+
"settings.off": "关",
|
|
77
|
+
"settings.saved": "已保存 {item}:{value}",
|
|
78
|
+
"settings.invalid": "无效的数值",
|
|
79
|
+
"settings.close": "关闭",
|
|
61
80
|
} as const
|
|
62
81
|
|
|
63
82
|
/** 结构约束:值放宽为 string,键集合来自中文表(新增语言缺 key 会编译报错)。 */
|
|
@@ -104,6 +123,7 @@ const EN_T: Translation = {
|
|
|
104
123
|
"clear.prompt": "Clear all sub-agent records for this session? This cannot be undone.",
|
|
105
124
|
"clear.prompt_running": "{n} sub-agent(s) are still running. Clearing will discard them permanently. Continue?",
|
|
106
125
|
"clear.done": "Cleared {n} sub-agent record(s)",
|
|
126
|
+
"clear.failed": "Clear failed",
|
|
107
127
|
"clear.empty": "No sub-agent records in this session",
|
|
108
128
|
"cancel.no_session": "Child session ID is unavailable",
|
|
109
129
|
"cancel.not_child": "Target is not a child session",
|
|
@@ -115,6 +135,24 @@ const EN_T: Translation = {
|
|
|
115
135
|
"cancel.failed": "Cancellation failed",
|
|
116
136
|
"borderShown": "Panel border shown",
|
|
117
137
|
"borderHidden": "Panel border hidden",
|
|
138
|
+
"settings.title": "SubAgent Magazine — Settings",
|
|
139
|
+
"settings.section.general": "General",
|
|
140
|
+
"settings.lang": "Language",
|
|
141
|
+
"settings.max": "Max entries",
|
|
142
|
+
"settings.order": "Sort order",
|
|
143
|
+
"settings.scroll": "Scroll mode",
|
|
144
|
+
"settings.ttl": "Retention",
|
|
145
|
+
"settings.border": "Panel border",
|
|
146
|
+
"settings.showEntryCost": "Cost per sub-agent",
|
|
147
|
+
"settings.showEntryTime": "Elapsed time in list",
|
|
148
|
+
"settings.showEntryTokens": "Token count in list",
|
|
149
|
+
"settings.timeFormat": "Time format",
|
|
150
|
+
"settings.dbSync": "Local database enrichment",
|
|
151
|
+
"settings.on": "on",
|
|
152
|
+
"settings.off": "off",
|
|
153
|
+
"settings.saved": "Saved {item}: {value}",
|
|
154
|
+
"settings.invalid": "Invalid value",
|
|
155
|
+
"settings.close": "Close",
|
|
118
156
|
}
|
|
119
157
|
|
|
120
158
|
const JA_T: Translation = {
|
|
@@ -158,6 +196,7 @@ const JA_T: Translation = {
|
|
|
158
196
|
"clear.prompt": "このセッションの全サブエージェント記録を削除しますか?この操作は元に戻せません。",
|
|
159
197
|
"clear.prompt_running": "実行中のサブエージェントが {n} 個あります。削除すると復元できません。続行しますか?",
|
|
160
198
|
"clear.done": "{n} 件のサブエージェント記録を削除しました",
|
|
199
|
+
"clear.failed": "クリアに失敗しました",
|
|
161
200
|
"clear.empty": "このセッションにサブエージェント記録はありません",
|
|
162
201
|
"cancel.no_session": "子セッション ID が利用できません",
|
|
163
202
|
"cancel.not_child": "対象は子セッションではありません",
|
|
@@ -169,6 +208,24 @@ const JA_T: Translation = {
|
|
|
169
208
|
"cancel.failed": "キャンセルに失敗しました",
|
|
170
209
|
"borderShown": "パネル枠線 表示",
|
|
171
210
|
"borderHidden": "パネル枠線 非表示",
|
|
211
|
+
"settings.title": "SubAgent Magazine — 設定",
|
|
212
|
+
"settings.section.general": "一般",
|
|
213
|
+
"settings.lang": "言語",
|
|
214
|
+
"settings.max": "最大表示数",
|
|
215
|
+
"settings.order": "並び順",
|
|
216
|
+
"settings.scroll": "スクロール",
|
|
217
|
+
"settings.ttl": "保持期間",
|
|
218
|
+
"settings.border": "パネル枠",
|
|
219
|
+
"settings.showEntryCost": "サブエージェント毎の費用",
|
|
220
|
+
"settings.showEntryTime": "一覧に所要時間を表示",
|
|
221
|
+
"settings.showEntryTokens": "一覧にトークン数を表示",
|
|
222
|
+
"settings.timeFormat": "時間形式",
|
|
223
|
+
"settings.dbSync": "ローカルDB補完",
|
|
224
|
+
"settings.on": "オン",
|
|
225
|
+
"settings.off": "オフ",
|
|
226
|
+
"settings.saved": "保存しました {item}:{value}",
|
|
227
|
+
"settings.invalid": "無効な値です",
|
|
228
|
+
"settings.close": "閉じる",
|
|
172
229
|
}
|
|
173
230
|
|
|
174
231
|
const KO_T: Translation = {
|
|
@@ -212,6 +269,7 @@ const KO_T: Translation = {
|
|
|
212
269
|
"clear.prompt": "이 세션의 모든 서브 에이전트 기록을 삭제하시겠습니까? 이 작업은 되돌릴 수 없습니다.",
|
|
213
270
|
"clear.prompt_running": "실행 중인 서브 에이전트가 {n}개 있습니다. 삭제하면 복구할 수 없습니다. 계속하시겠습니까?",
|
|
214
271
|
"clear.done": "서브 에이전트 기록 {n}개를 삭제했습니다",
|
|
272
|
+
"clear.failed": "지우기에 실패했습니다",
|
|
215
273
|
"clear.empty": "이 세션에 서브 에이전트 기록이 없습니다",
|
|
216
274
|
"cancel.no_session": "하위 세션 ID를 사용할 수 없습니다",
|
|
217
275
|
"cancel.not_child": "대상이 하위 세션이 아닙니다",
|
|
@@ -223,6 +281,24 @@ const KO_T: Translation = {
|
|
|
223
281
|
"cancel.failed": "취소에 실패했습니다",
|
|
224
282
|
"borderShown": "패널 테두리 표시",
|
|
225
283
|
"borderHidden": "패널 테두리 숨김",
|
|
284
|
+
"settings.title": "SubAgent Magazine — 설정",
|
|
285
|
+
"settings.section.general": "일반",
|
|
286
|
+
"settings.lang": "언어",
|
|
287
|
+
"settings.max": "최대 항목 수",
|
|
288
|
+
"settings.order": "정렬 순서",
|
|
289
|
+
"settings.scroll": "스크롤 모드",
|
|
290
|
+
"settings.ttl": "보존 기간",
|
|
291
|
+
"settings.border": "패널 테두리",
|
|
292
|
+
"settings.showEntryCost": "하위 에이전트별 비용",
|
|
293
|
+
"settings.showEntryTime": "목록에 소요 시간 표시",
|
|
294
|
+
"settings.showEntryTokens": "목록에 토큰 수 표시",
|
|
295
|
+
"settings.timeFormat": "시간 형식",
|
|
296
|
+
"settings.dbSync": "로컬 DB 보완",
|
|
297
|
+
"settings.on": "켬",
|
|
298
|
+
"settings.off": "끔",
|
|
299
|
+
"settings.saved": "저장됨 {item}: {value}",
|
|
300
|
+
"settings.invalid": "잘못된 값",
|
|
301
|
+
"settings.close": "닫기",
|
|
226
302
|
}
|
|
227
303
|
|
|
228
304
|
export const LANGS: Record<LangCode, Translation> = { zh: ZH_T, en: EN_T, ja: JA_T, ko: KO_T }
|
package/src/index.tsx
CHANGED
|
@@ -11,8 +11,9 @@ import type {
|
|
|
11
11
|
import { createSignal } from "solid-js"
|
|
12
12
|
import { PLUGIN_VERSION } from "./_version"
|
|
13
13
|
import { LANG_META, createT, detectLang } from "./i18n"
|
|
14
|
-
import type { Lang, SortOrder, ScrollMode, SubStatus, SharedSignals } from "./core/types"
|
|
14
|
+
import type { Lang, SortOrder, ScrollMode, SubStatus, SharedSignals, TimeFormat } from "./core/types"
|
|
15
15
|
import { KV_PREFIX } from "./core/kv"
|
|
16
|
+
import { TIME_FORMATS, TIME_FORMAT_SAMPLES } from "./core/format"
|
|
16
17
|
import type { PanelApi } from "./panel/panel-api"
|
|
17
18
|
import { SubAgentPanel } from "./panel/SubAgentPanel"
|
|
18
19
|
import { globalEntryCache, setClearTick } from "./panel/store"
|
|
@@ -35,6 +36,10 @@ function createSidebarSlot(api: TuiPluginApi, panelApi: PanelApi, sig: SharedSig
|
|
|
35
36
|
sortOrder={sig.sortOrder}
|
|
36
37
|
scrollMode={sig.scrollMode}
|
|
37
38
|
borderVisible={sig.borderVisible}
|
|
39
|
+
showEntryCost={sig.showEntryCost}
|
|
40
|
+
showEntryTime={sig.showEntryTime}
|
|
41
|
+
showEntryTokens={sig.showEntryTokens}
|
|
42
|
+
timeFormat={sig.timeFormat}
|
|
38
43
|
sessionId={input.session_id}
|
|
39
44
|
/>
|
|
40
45
|
)
|
|
@@ -61,8 +66,24 @@ const tui: TuiPlugin = async (api: TuiPluginApi) => {
|
|
|
61
66
|
const [borderVisible, setBorderVisible] = createSignal<boolean>(
|
|
62
67
|
(api.kv.get(`${KV_PREFIX}.border`, false) as boolean) === true
|
|
63
68
|
)
|
|
69
|
+
const [showEntryCost, setShowEntryCost] = createSignal<boolean>(
|
|
70
|
+
(api.kv.get(`${KV_PREFIX}.show_entry_cost`, false) as boolean) === true
|
|
71
|
+
)
|
|
72
|
+
const [showEntryTime, setShowEntryTime] = createSignal<boolean>(
|
|
73
|
+
(api.kv.get(`${KV_PREFIX}.show_entry_time`, true) as boolean) !== false
|
|
74
|
+
)
|
|
75
|
+
const [showEntryTokens, setShowEntryTokens] = createSignal<boolean>(
|
|
76
|
+
(api.kv.get(`${KV_PREFIX}.show_entry_tokens`, true) as boolean) !== false
|
|
77
|
+
)
|
|
78
|
+
const storedTimeFormat = String(api.kv.get(`${KV_PREFIX}.time_format`, "short"))
|
|
79
|
+
const [timeFormat, setTimeFormat] = createSignal<TimeFormat>(
|
|
80
|
+
(TIME_FORMATS as readonly string[]).includes(storedTimeFormat) ? (storedTimeFormat as TimeFormat) : "short"
|
|
81
|
+
)
|
|
82
|
+
const [dbSync, setDbSync] = createSignal<boolean>(
|
|
83
|
+
(api.kv.get(`${KV_PREFIX}.db_sync`, true) as boolean) !== false
|
|
84
|
+
)
|
|
64
85
|
|
|
65
|
-
const signals: SharedSignals = { lang, setLang, maxEntries, setMaxEntries, sortOrder, setSortOrder, scrollMode, setScrollMode, borderVisible, setBorderVisible, sessionId: "" }
|
|
86
|
+
const signals: SharedSignals = { lang, setLang, maxEntries, setMaxEntries, sortOrder, setSortOrder, scrollMode, setScrollMode, borderVisible, setBorderVisible, showEntryCost, setShowEntryCost, showEntryTime, setShowEntryTime, showEntryTokens, setShowEntryTokens, timeFormat, setTimeFormat, dbSync, setDbSync, sessionId: "" }
|
|
66
87
|
|
|
67
88
|
// ── V1 PanelApi adapter: wraps the V1 host API into the shared panel contract ──
|
|
68
89
|
const v1Api: PanelApi = {
|
|
@@ -462,6 +483,76 @@ const tui: TuiPlugin = async (api: TuiPluginApi) => {
|
|
|
462
483
|
dialog?.clear()
|
|
463
484
|
},
|
|
464
485
|
},
|
|
486
|
+
{
|
|
487
|
+
title: "SubAgent Magazine: Entry Cost",
|
|
488
|
+
value: "subagent-cost",
|
|
489
|
+
description: "Show or hide the per-sub-agent cost in the sidebar",
|
|
490
|
+
slash: { name: "subagent-cost" },
|
|
491
|
+
onSelect: (dialog) => {
|
|
492
|
+
const t = createT(() => signals.lang())
|
|
493
|
+
const cur = Boolean(api.kv.get(`${KV_PREFIX}.show_entry_cost`, false))
|
|
494
|
+
api.kv.set(`${KV_PREFIX}.show_entry_cost`, !cur)
|
|
495
|
+
signals.setShowEntryCost(!cur)
|
|
496
|
+
api.ui.toast({
|
|
497
|
+
message: `${t("settings.showEntryCost")}: ${!cur ? t("settings.on") : t("settings.off")}`,
|
|
498
|
+
})
|
|
499
|
+
dialog?.clear()
|
|
500
|
+
},
|
|
501
|
+
},
|
|
502
|
+
{
|
|
503
|
+
title: "SubAgent Magazine: Entry Time",
|
|
504
|
+
value: "subagent-time",
|
|
505
|
+
description: "Show or hide the elapsed time in the sidebar list",
|
|
506
|
+
slash: { name: "subagent-time" },
|
|
507
|
+
onSelect: (dialog) => {
|
|
508
|
+
const t = createT(() => signals.lang())
|
|
509
|
+
const cur = Boolean(api.kv.get(`${KV_PREFIX}.show_entry_time`, true))
|
|
510
|
+
api.kv.set(`${KV_PREFIX}.show_entry_time`, !cur)
|
|
511
|
+
signals.setShowEntryTime(!cur)
|
|
512
|
+
api.ui.toast({
|
|
513
|
+
message: `${t("settings.showEntryTime")}: ${!cur ? t("settings.on") : t("settings.off")}`,
|
|
514
|
+
})
|
|
515
|
+
dialog?.clear()
|
|
516
|
+
},
|
|
517
|
+
},
|
|
518
|
+
{
|
|
519
|
+
title: "SubAgent Magazine: Entry Tokens",
|
|
520
|
+
value: "subagent-tokens",
|
|
521
|
+
description: "Show or hide the token count in the sidebar list",
|
|
522
|
+
slash: { name: "subagent-tokens" },
|
|
523
|
+
onSelect: (dialog) => {
|
|
524
|
+
const t = createT(() => signals.lang())
|
|
525
|
+
const cur = Boolean(api.kv.get(`${KV_PREFIX}.show_entry_tokens`, true))
|
|
526
|
+
api.kv.set(`${KV_PREFIX}.show_entry_tokens`, !cur)
|
|
527
|
+
signals.setShowEntryTokens(!cur)
|
|
528
|
+
api.ui.toast({
|
|
529
|
+
message: `${t("settings.showEntryTokens")}: ${!cur ? t("settings.on") : t("settings.off")}`,
|
|
530
|
+
})
|
|
531
|
+
dialog?.clear()
|
|
532
|
+
},
|
|
533
|
+
},
|
|
534
|
+
{
|
|
535
|
+
title: "SubAgent Magazine: Time Format",
|
|
536
|
+
value: "subagent-time-format",
|
|
537
|
+
description: "Set how elapsed time is displayed",
|
|
538
|
+
slash: { name: "subagent-time-format" },
|
|
539
|
+
onSelect: (dialog) => {
|
|
540
|
+
dialog?.replace(() => (
|
|
541
|
+
<api.ui.DialogSelect
|
|
542
|
+
title="Time Format / 时间格式"
|
|
543
|
+
options={TIME_FORMATS.map((f) => ({ title: `${f} — ${TIME_FORMAT_SAMPLES[f]}`, value: f }))}
|
|
544
|
+
onSelect={(opt) => {
|
|
545
|
+
const f = opt.value as TimeFormat
|
|
546
|
+
setTimeFormat(f)
|
|
547
|
+
api.kv.set(`${KV_PREFIX}.time_format`, f)
|
|
548
|
+
const t = createT(() => lang())
|
|
549
|
+
api.ui.toast({ message: `${t("settings.timeFormat")}: ${TIME_FORMAT_SAMPLES[f]}` })
|
|
550
|
+
dialog?.clear()
|
|
551
|
+
}}
|
|
552
|
+
/>
|
|
553
|
+
))
|
|
554
|
+
},
|
|
555
|
+
},
|
|
465
556
|
])
|
|
466
557
|
}
|
|
467
558
|
|