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/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.1";
|
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,12 @@ 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
|
+
showOrigin: `${KV_PREFIX}.show_origin`,
|
|
33
|
+
dbSync: `${KV_PREFIX}.db_sync`,
|
|
21
34
|
} as const
|
|
22
35
|
|
|
23
36
|
export function loadSessionData(kv: KVApi): Record<string, SessionRecord> {
|
|
@@ -27,8 +40,36 @@ export function loadSessionData(kv: KVApi): Record<string, SessionRecord> {
|
|
|
27
40
|
} catch { return {} }
|
|
28
41
|
}
|
|
29
42
|
|
|
30
|
-
|
|
31
|
-
|
|
43
|
+
/**
|
|
44
|
+
* Atomic read-merge-write of the session store.
|
|
45
|
+
*
|
|
46
|
+
* `mutator` receives the latest persisted map (read under the host lock when
|
|
47
|
+
* `kv.update` is available) and may mutate it freely; the result is written
|
|
48
|
+
* back. Every writer must go through here: writing a whole in-memory snapshot
|
|
49
|
+
* directly would silently drop entries created by the other TUIs.
|
|
50
|
+
*/
|
|
51
|
+
export function updateSessionData(
|
|
52
|
+
kv: KVApi,
|
|
53
|
+
mutator: (data: Record<string, SessionRecord>) => void,
|
|
54
|
+
): void | Promise<void> {
|
|
55
|
+
try {
|
|
56
|
+
if (typeof kv.update === "function") {
|
|
57
|
+
return kv.update(SESSION_DATA_KEY, (raw) => {
|
|
58
|
+
const data = parseSessionData(raw)
|
|
59
|
+
mutator(data)
|
|
60
|
+
return JSON.stringify(data)
|
|
61
|
+
})
|
|
62
|
+
}
|
|
63
|
+
// Fallback (V1 hosts without atomic update): read-modify-write. The window
|
|
64
|
+
// is small and the mutation is merged against the value we just read.
|
|
65
|
+
const data = loadSessionData(kv)
|
|
66
|
+
mutator(data)
|
|
67
|
+
kv.set(SESSION_DATA_KEY, JSON.stringify(data))
|
|
68
|
+
} catch {}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function parseSessionData(raw: unknown): Record<string, SessionRecord> {
|
|
72
|
+
try { return JSON.parse(String(raw ?? "{}")) } catch { return {} }
|
|
32
73
|
}
|
|
33
74
|
|
|
34
75
|
export function readTTLDays(kv: KVApi): number {
|
package/src/core/types.ts
CHANGED
|
@@ -20,15 +20,28 @@ export interface SubEntry {
|
|
|
20
20
|
cancelRequestedAt?: number
|
|
21
21
|
abortAccepted?: boolean
|
|
22
22
|
cancelReason?: "manual"
|
|
23
|
+
/** 来源:工具调用条目 / 衍生会话条目(spawn)。 */
|
|
24
|
+
origin?: "tool" | "sub"
|
|
25
|
+
/** 工具输入带 background/run_in_background(后台衍生会话)。 */
|
|
26
|
+
background?: boolean
|
|
23
27
|
}
|
|
24
28
|
|
|
25
29
|
export type Lang = LangCode
|
|
26
30
|
export type SortOrder = "desc" | "asc"
|
|
27
31
|
export type ScrollMode = "wheel" | "click"
|
|
32
|
+
/** How elapsed time is rendered in the sidebar (see TIME_FORMAT_SAMPLES). */
|
|
33
|
+
export type TimeFormat = "decimal" | "short" | "clock" | "compact" | "seconds"
|
|
28
34
|
|
|
29
35
|
/** OpenCode built-in tool names that spawn sub-agents or delegate tasks. */
|
|
30
36
|
export const SUBAGENT_TOOLS = new Set(["task", "subagent", "delegate", "call_omo_agent"])
|
|
31
37
|
|
|
38
|
+
/** 工具输入是否声明后台执行(V1/V2 两种字段名)。 */
|
|
39
|
+
export function isBackgroundInput(input: unknown): boolean {
|
|
40
|
+
if (!input || typeof input !== "object") return false
|
|
41
|
+
const i = input as Record<string, unknown>
|
|
42
|
+
return i.background === true || i.run_in_background === true
|
|
43
|
+
}
|
|
44
|
+
|
|
32
45
|
export interface ChildRecord {
|
|
33
46
|
scroll: number
|
|
34
47
|
expanded: string
|
|
@@ -56,5 +69,17 @@ export interface SharedSignals {
|
|
|
56
69
|
setScrollMode: (m: ScrollMode) => void
|
|
57
70
|
borderVisible: () => boolean
|
|
58
71
|
setBorderVisible: (v: boolean) => void
|
|
72
|
+
showEntryCost: () => boolean
|
|
73
|
+
setShowEntryCost: (v: boolean) => void
|
|
74
|
+
showEntryTime: () => boolean
|
|
75
|
+
setShowEntryTime: (v: boolean) => void
|
|
76
|
+
showEntryTokens: () => boolean
|
|
77
|
+
setShowEntryTokens: (v: boolean) => void
|
|
78
|
+
timeFormat: () => TimeFormat
|
|
79
|
+
setTimeFormat: (f: TimeFormat) => void
|
|
80
|
+
showOrigin: () => boolean
|
|
81
|
+
setShowOrigin: (v: boolean) => void
|
|
82
|
+
dbSync: () => boolean
|
|
83
|
+
setDbSync: (v: boolean) => void
|
|
59
84
|
sessionId: string
|
|
60
85
|
}
|
package/src/i18n.ts
CHANGED
|
@@ -10,6 +10,10 @@ const ZH_T = {
|
|
|
10
10
|
"panel.title": "子代理",
|
|
11
11
|
"status.none": "暂无子代理",
|
|
12
12
|
"agent.label": "代理",
|
|
13
|
+
"origin.label": "来源",
|
|
14
|
+
"origin.tool": "子代理调用",
|
|
15
|
+
"origin.sub": "衍生会话",
|
|
16
|
+
"origin.background": "后台",
|
|
13
17
|
"status.label": "状态",
|
|
14
18
|
"time.label": "耗时",
|
|
15
19
|
"tokens.label": "上下文",
|
|
@@ -47,6 +51,7 @@ const ZH_T = {
|
|
|
47
51
|
"clear.prompt": "确定清除当前会话所有子代理记录?此操作不可撤销。",
|
|
48
52
|
"clear.prompt_running": "当前有 {n} 个运行中的子代理,清除后将不可恢复。确定继续?",
|
|
49
53
|
"clear.done": "已清除 {n} 条子代理记录",
|
|
54
|
+
"clear.failed": "清除失败",
|
|
50
55
|
"clear.empty": "当前会话无子代理记录",
|
|
51
56
|
"cancel.no_session": "子会话 ID 不可用",
|
|
52
57
|
"cancel.not_child": "目标不是子会话",
|
|
@@ -58,6 +63,25 @@ const ZH_T = {
|
|
|
58
63
|
"cancel.failed": "取消失败",
|
|
59
64
|
"borderShown": "面板边框 已显示",
|
|
60
65
|
"borderHidden": "面板边框 已隐藏",
|
|
66
|
+
"settings.title": "SubAgent Magazine — 设置",
|
|
67
|
+
"settings.section.general": "通用",
|
|
68
|
+
"settings.lang": "语言",
|
|
69
|
+
"settings.max": "最大条目数",
|
|
70
|
+
"settings.order": "排序方式",
|
|
71
|
+
"settings.scroll": "滚动模式",
|
|
72
|
+
"settings.ttl": "保留期限",
|
|
73
|
+
"settings.border": "面板边框",
|
|
74
|
+
"settings.showEntryCost": "每条子代理显示费用",
|
|
75
|
+
"settings.showEntryTime": "列表中显示耗时",
|
|
76
|
+
"settings.showEntryTokens": "列表中显示 Token 数",
|
|
77
|
+
"settings.timeFormat": "时间格式",
|
|
78
|
+
"settings.showOrigin": "列表显示来源标记",
|
|
79
|
+
"settings.dbSync": "本地数据库补全",
|
|
80
|
+
"settings.on": "开",
|
|
81
|
+
"settings.off": "关",
|
|
82
|
+
"settings.saved": "已保存 {item}:{value}",
|
|
83
|
+
"settings.invalid": "无效的数值",
|
|
84
|
+
"settings.close": "关闭",
|
|
61
85
|
} as const
|
|
62
86
|
|
|
63
87
|
/** 结构约束:值放宽为 string,键集合来自中文表(新增语言缺 key 会编译报错)。 */
|
|
@@ -67,6 +91,10 @@ const EN_T: Translation = {
|
|
|
67
91
|
"panel.title": "SubAgent",
|
|
68
92
|
"status.none": "No sub-agents yet",
|
|
69
93
|
"agent.label": "agent",
|
|
94
|
+
"origin.label": "origin",
|
|
95
|
+
"origin.tool": "sub-agent call",
|
|
96
|
+
"origin.sub": "spawned session",
|
|
97
|
+
"origin.background": "background",
|
|
70
98
|
"status.label": "status",
|
|
71
99
|
"time.label": "time",
|
|
72
100
|
"tokens.label": "tokens",
|
|
@@ -104,6 +132,7 @@ const EN_T: Translation = {
|
|
|
104
132
|
"clear.prompt": "Clear all sub-agent records for this session? This cannot be undone.",
|
|
105
133
|
"clear.prompt_running": "{n} sub-agent(s) are still running. Clearing will discard them permanently. Continue?",
|
|
106
134
|
"clear.done": "Cleared {n} sub-agent record(s)",
|
|
135
|
+
"clear.failed": "Clear failed",
|
|
107
136
|
"clear.empty": "No sub-agent records in this session",
|
|
108
137
|
"cancel.no_session": "Child session ID is unavailable",
|
|
109
138
|
"cancel.not_child": "Target is not a child session",
|
|
@@ -115,12 +144,35 @@ const EN_T: Translation = {
|
|
|
115
144
|
"cancel.failed": "Cancellation failed",
|
|
116
145
|
"borderShown": "Panel border shown",
|
|
117
146
|
"borderHidden": "Panel border hidden",
|
|
147
|
+
"settings.title": "SubAgent Magazine — Settings",
|
|
148
|
+
"settings.section.general": "General",
|
|
149
|
+
"settings.lang": "Language",
|
|
150
|
+
"settings.max": "Max entries",
|
|
151
|
+
"settings.order": "Sort order",
|
|
152
|
+
"settings.scroll": "Scroll mode",
|
|
153
|
+
"settings.ttl": "Retention",
|
|
154
|
+
"settings.border": "Panel border",
|
|
155
|
+
"settings.showEntryCost": "Cost per sub-agent",
|
|
156
|
+
"settings.showEntryTime": "Elapsed time in list",
|
|
157
|
+
"settings.showEntryTokens": "Token count in list",
|
|
158
|
+
"settings.showOrigin": "Origin indicators in list",
|
|
159
|
+
"settings.timeFormat": "Time format",
|
|
160
|
+
"settings.dbSync": "Local database enrichment",
|
|
161
|
+
"settings.on": "on",
|
|
162
|
+
"settings.off": "off",
|
|
163
|
+
"settings.saved": "Saved {item}: {value}",
|
|
164
|
+
"settings.invalid": "Invalid value",
|
|
165
|
+
"settings.close": "Close",
|
|
118
166
|
}
|
|
119
167
|
|
|
120
168
|
const JA_T: Translation = {
|
|
121
169
|
"panel.title": "サブエージェント",
|
|
122
170
|
"status.none": "サブエージェントなし",
|
|
123
171
|
"agent.label": "エージェント",
|
|
172
|
+
"origin.label": "発生元",
|
|
173
|
+
"origin.tool": "サブエージェント呼び出し",
|
|
174
|
+
"origin.sub": "生成されたセッション",
|
|
175
|
+
"origin.background": "バックグラウンド",
|
|
124
176
|
"status.label": "状態",
|
|
125
177
|
"time.label": "時間",
|
|
126
178
|
"tokens.label": "コンテキスト",
|
|
@@ -158,6 +210,7 @@ const JA_T: Translation = {
|
|
|
158
210
|
"clear.prompt": "このセッションの全サブエージェント記録を削除しますか?この操作は元に戻せません。",
|
|
159
211
|
"clear.prompt_running": "実行中のサブエージェントが {n} 個あります。削除すると復元できません。続行しますか?",
|
|
160
212
|
"clear.done": "{n} 件のサブエージェント記録を削除しました",
|
|
213
|
+
"clear.failed": "クリアに失敗しました",
|
|
161
214
|
"clear.empty": "このセッションにサブエージェント記録はありません",
|
|
162
215
|
"cancel.no_session": "子セッション ID が利用できません",
|
|
163
216
|
"cancel.not_child": "対象は子セッションではありません",
|
|
@@ -169,12 +222,35 @@ const JA_T: Translation = {
|
|
|
169
222
|
"cancel.failed": "キャンセルに失敗しました",
|
|
170
223
|
"borderShown": "パネル枠線 表示",
|
|
171
224
|
"borderHidden": "パネル枠線 非表示",
|
|
225
|
+
"settings.title": "SubAgent Magazine — 設定",
|
|
226
|
+
"settings.section.general": "一般",
|
|
227
|
+
"settings.lang": "言語",
|
|
228
|
+
"settings.max": "最大表示数",
|
|
229
|
+
"settings.order": "並び順",
|
|
230
|
+
"settings.scroll": "スクロール",
|
|
231
|
+
"settings.ttl": "保持期間",
|
|
232
|
+
"settings.border": "パネル枠",
|
|
233
|
+
"settings.showEntryCost": "サブエージェント毎の費用",
|
|
234
|
+
"settings.showEntryTime": "一覧に所要時間を表示",
|
|
235
|
+
"settings.showEntryTokens": "一覧にトークン数を表示",
|
|
236
|
+
"settings.showOrigin": "一覧に由来マークを表示",
|
|
237
|
+
"settings.timeFormat": "時間形式",
|
|
238
|
+
"settings.dbSync": "ローカルDB補完",
|
|
239
|
+
"settings.on": "オン",
|
|
240
|
+
"settings.off": "オフ",
|
|
241
|
+
"settings.saved": "保存しました {item}:{value}",
|
|
242
|
+
"settings.invalid": "無効な値です",
|
|
243
|
+
"settings.close": "閉じる",
|
|
172
244
|
}
|
|
173
245
|
|
|
174
246
|
const KO_T: Translation = {
|
|
175
247
|
"panel.title": "서브 에이전트",
|
|
176
248
|
"status.none": "서브 에이전트 없음",
|
|
177
249
|
"agent.label": "에이전트",
|
|
250
|
+
"origin.label": "출처",
|
|
251
|
+
"origin.tool": "하위 에이전트 호출",
|
|
252
|
+
"origin.sub": "생성된 세션",
|
|
253
|
+
"origin.background": "백그라운드",
|
|
178
254
|
"status.label": "상태",
|
|
179
255
|
"time.label": "시간",
|
|
180
256
|
"tokens.label": "컨텍스트",
|
|
@@ -212,6 +288,7 @@ const KO_T: Translation = {
|
|
|
212
288
|
"clear.prompt": "이 세션의 모든 서브 에이전트 기록을 삭제하시겠습니까? 이 작업은 되돌릴 수 없습니다.",
|
|
213
289
|
"clear.prompt_running": "실행 중인 서브 에이전트가 {n}개 있습니다. 삭제하면 복구할 수 없습니다. 계속하시겠습니까?",
|
|
214
290
|
"clear.done": "서브 에이전트 기록 {n}개를 삭제했습니다",
|
|
291
|
+
"clear.failed": "지우기에 실패했습니다",
|
|
215
292
|
"clear.empty": "이 세션에 서브 에이전트 기록이 없습니다",
|
|
216
293
|
"cancel.no_session": "하위 세션 ID를 사용할 수 없습니다",
|
|
217
294
|
"cancel.not_child": "대상이 하위 세션이 아닙니다",
|
|
@@ -223,6 +300,25 @@ const KO_T: Translation = {
|
|
|
223
300
|
"cancel.failed": "취소에 실패했습니다",
|
|
224
301
|
"borderShown": "패널 테두리 표시",
|
|
225
302
|
"borderHidden": "패널 테두리 숨김",
|
|
303
|
+
"settings.title": "SubAgent Magazine — 설정",
|
|
304
|
+
"settings.section.general": "일반",
|
|
305
|
+
"settings.lang": "언어",
|
|
306
|
+
"settings.max": "최대 항목 수",
|
|
307
|
+
"settings.order": "정렬 순서",
|
|
308
|
+
"settings.scroll": "스크롤 모드",
|
|
309
|
+
"settings.ttl": "보존 기간",
|
|
310
|
+
"settings.border": "패널 테두리",
|
|
311
|
+
"settings.showEntryCost": "하위 에이전트별 비용",
|
|
312
|
+
"settings.showEntryTime": "목록에 소요 시간 표시",
|
|
313
|
+
"settings.showEntryTokens": "목록에 토큰 수 표시",
|
|
314
|
+
"settings.showOrigin": "목록에 출처 아이콘 표시",
|
|
315
|
+
"settings.timeFormat": "시간 형식",
|
|
316
|
+
"settings.dbSync": "로컬 DB 보완",
|
|
317
|
+
"settings.on": "켬",
|
|
318
|
+
"settings.off": "끔",
|
|
319
|
+
"settings.saved": "저장됨 {item}: {value}",
|
|
320
|
+
"settings.invalid": "잘못된 값",
|
|
321
|
+
"settings.close": "닫기",
|
|
226
322
|
}
|
|
227
323
|
|
|
228
324
|
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,11 @@ 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}
|
|
43
|
+
showOrigin={sig.showOrigin}
|
|
38
44
|
sessionId={input.session_id}
|
|
39
45
|
/>
|
|
40
46
|
)
|
|
@@ -61,8 +67,27 @@ const tui: TuiPlugin = async (api: TuiPluginApi) => {
|
|
|
61
67
|
const [borderVisible, setBorderVisible] = createSignal<boolean>(
|
|
62
68
|
(api.kv.get(`${KV_PREFIX}.border`, false) as boolean) === true
|
|
63
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
|
+
)
|
|
64
89
|
|
|
65
|
-
const signals: SharedSignals = { lang, setLang, maxEntries, setMaxEntries, sortOrder, setSortOrder, scrollMode, setScrollMode, borderVisible, setBorderVisible, sessionId: "" }
|
|
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: "" }
|
|
66
91
|
|
|
67
92
|
// ── V1 PanelApi adapter: wraps the V1 host API into the shared panel contract ──
|
|
68
93
|
const v1Api: PanelApi = {
|
|
@@ -462,6 +487,92 @@ const tui: TuiPlugin = async (api: TuiPluginApi) => {
|
|
|
462
487
|
dialog?.clear()
|
|
463
488
|
},
|
|
464
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
|
+
},
|
|
465
576
|
])
|
|
466
577
|
}
|
|
467
578
|
|