opencode-subagent-magazine 1.5.0 → 1.5.2

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.
@@ -1 +1 @@
1
- export declare const PLUGIN_VERSION = "1.5.0";
1
+ export declare const PLUGIN_VERSION = "1.5.2";
package/dist/_version.js CHANGED
@@ -1,2 +1,2 @@
1
1
  // auto-generated
2
- export const PLUGIN_VERSION = "1.5.0";
2
+ export const PLUGIN_VERSION = "1.5.2";
package/dist/i18n.d.ts ADDED
@@ -0,0 +1,75 @@
1
+ export type LangCode = "zh" | "en" | "ja" | "ko";
2
+ declare const ZH_T: {
3
+ readonly "panel.title": "子代理";
4
+ readonly "status.none": "暂无子代理";
5
+ readonly "agent.label": "代理";
6
+ readonly "status.label": "状态";
7
+ readonly "time.label": "耗时";
8
+ readonly "tokens.label": "上下文";
9
+ readonly "error.label": "错误";
10
+ readonly "model.label": "模型";
11
+ readonly "todo.label": "进度";
12
+ readonly "session.label": "会话 ID";
13
+ readonly "session.toast.copy": "可手动复制上方 ID";
14
+ readonly "session.toast.copied": "会话 ID 已复制";
15
+ readonly "session.toast.copy_failed": "无法访问系统剪贴板,请手动复制上方 ID";
16
+ readonly "open.label": "进入会话";
17
+ readonly "cost.label": "费用";
18
+ readonly "scroll.more": "更多";
19
+ readonly "scroll.top": "回顶";
20
+ readonly "scroll.bottom": "回底";
21
+ readonly "dismiss.label": "标记完成";
22
+ readonly "cancel.label": "取消";
23
+ readonly "status.running": "运行中";
24
+ readonly "status.done": "已完成";
25
+ readonly "status.cancelled": "已取消";
26
+ readonly "status.error": "错误";
27
+ readonly "order.desc": "降序(最新在前)";
28
+ readonly "order.asc": "升序(最早在前)";
29
+ readonly "scroll.wheel": "滚轮翻页";
30
+ readonly "scroll.click": "点击翻页";
31
+ readonly "ttl.label": "清理周期";
32
+ readonly "ttl.3d": "3 天";
33
+ readonly "ttl.7d": "7 天";
34
+ readonly "ttl.14d": "14 天";
35
+ readonly "ttl.30d": "30 天";
36
+ readonly "ttl.unlimited": "无期限";
37
+ readonly "ttl.toast": "清理周期已设为 {n} 天";
38
+ readonly "ttl.toast_unlimited": "清理周期已设为无期限";
39
+ readonly "clear.title": "确认清除";
40
+ readonly "clear.prompt": "确定清除当前会话所有子代理记录?此操作不可撤销。";
41
+ readonly "clear.prompt_running": "当前有 {n} 个运行中的子代理,清除后将不可恢复。确定继续?";
42
+ readonly "clear.done": "已清除 {n} 条子代理记录";
43
+ readonly "clear.empty": "当前会话无子代理记录";
44
+ readonly "cancel.no_session": "子会话 ID 不可用";
45
+ readonly "cancel.not_child": "目标不是子会话";
46
+ readonly "cancel.read_error": "无法读取会话信息";
47
+ readonly "cancel.outside_tree": "目标不在当前监控会话树中";
48
+ readonly "cancel.already_ended": "会话已结束,无需取消";
49
+ readonly "cancel.status_error": "无法查询会话状态";
50
+ readonly "cancel.sent": "已发送取消指令";
51
+ readonly "cancel.failed": "取消失败";
52
+ };
53
+ /** 结构约束:值放宽为 string,键集合来自中文表(新增语言缺 key 会编译报错)。 */
54
+ export type Translation = {
55
+ [K in keyof typeof ZH_T]: string;
56
+ };
57
+ export declare const LANGS: Record<LangCode, Translation>;
58
+ /** 语言元数据:/subagent-lang 选项与自动检测共用。 */
59
+ export declare const LANG_META: {
60
+ code: LangCode;
61
+ label: string;
62
+ }[];
63
+ /**
64
+ * 模板参数替换:`{key}` 占位符统一在此处理。
65
+ * 未提供的参数保留原占位符,避免静默丢失。
66
+ */
67
+ export declare function applyParams(tpl: string, params?: Record<string, string | number>): string;
68
+ /**
69
+ * 宽松翻译工厂:未知 key 回退为 key 原样(兼容非翻译用途的键名透传)。
70
+ * `getCode` 读取语言信号——在 SolidJS 渲染/memo 上下文中调用时自动建立响应式依赖。
71
+ */
72
+ export declare function createT(getCode: () => LangCode): (key: string, params?: Record<string, string | number>) => string;
73
+ /** 按系统 locale 自动检测语言(zh → 中文,ja → 日语,ko → 韩语,其余 → 英文)。 */
74
+ export declare function detectLang(): LangCode;
75
+ export {};
package/dist/i18n.js ADDED
@@ -0,0 +1,249 @@
1
+ // ---------------------------------------------------------------------------
2
+ // i18n — centralized translations (aligned with opencode-visual-cache).
3
+ // Add a language by appending a table that satisfies `Translation`; the
4
+ // compiler enforces key completeness.
5
+ // ---------------------------------------------------------------------------
6
+ const ZH_T = {
7
+ "panel.title": "子代理",
8
+ "status.none": "暂无子代理",
9
+ "agent.label": "代理",
10
+ "status.label": "状态",
11
+ "time.label": "耗时",
12
+ "tokens.label": "上下文",
13
+ "error.label": "错误",
14
+ "model.label": "模型",
15
+ "todo.label": "进度",
16
+ "session.label": "会话 ID",
17
+ "session.toast.copy": "可手动复制上方 ID",
18
+ "session.toast.copied": "会话 ID 已复制",
19
+ "session.toast.copy_failed": "无法访问系统剪贴板,请手动复制上方 ID",
20
+ "open.label": "进入会话",
21
+ "cost.label": "费用",
22
+ "scroll.more": "更多",
23
+ "scroll.top": "回顶",
24
+ "scroll.bottom": "回底",
25
+ "dismiss.label": "标记完成",
26
+ "cancel.label": "取消",
27
+ "status.running": "运行中",
28
+ "status.done": "已完成",
29
+ "status.cancelled": "已取消",
30
+ "status.error": "错误",
31
+ "order.desc": "降序(最新在前)",
32
+ "order.asc": "升序(最早在前)",
33
+ "scroll.wheel": "滚轮翻页",
34
+ "scroll.click": "点击翻页",
35
+ "ttl.label": "清理周期",
36
+ "ttl.3d": "3 天",
37
+ "ttl.7d": "7 天",
38
+ "ttl.14d": "14 天",
39
+ "ttl.30d": "30 天",
40
+ "ttl.unlimited": "无期限",
41
+ "ttl.toast": "清理周期已设为 {n} 天",
42
+ "ttl.toast_unlimited": "清理周期已设为无期限",
43
+ "clear.title": "确认清除",
44
+ "clear.prompt": "确定清除当前会话所有子代理记录?此操作不可撤销。",
45
+ "clear.prompt_running": "当前有 {n} 个运行中的子代理,清除后将不可恢复。确定继续?",
46
+ "clear.done": "已清除 {n} 条子代理记录",
47
+ "clear.empty": "当前会话无子代理记录",
48
+ "cancel.no_session": "子会话 ID 不可用",
49
+ "cancel.not_child": "目标不是子会话",
50
+ "cancel.read_error": "无法读取会话信息",
51
+ "cancel.outside_tree": "目标不在当前监控会话树中",
52
+ "cancel.already_ended": "会话已结束,无需取消",
53
+ "cancel.status_error": "无法查询会话状态",
54
+ "cancel.sent": "已发送取消指令",
55
+ "cancel.failed": "取消失败",
56
+ };
57
+ const EN_T = {
58
+ "panel.title": "SubAgent",
59
+ "status.none": "No sub-agents yet",
60
+ "agent.label": "agent",
61
+ "status.label": "status",
62
+ "time.label": "time",
63
+ "tokens.label": "tokens",
64
+ "error.label": "error",
65
+ "model.label": "model",
66
+ "todo.label": "todo",
67
+ "session.label": "session ID",
68
+ "session.toast.copy": "Copy the ID above manually",
69
+ "session.toast.copied": "Session ID copied",
70
+ "session.toast.copy_failed": "Cannot access the system clipboard; copy the ID above manually",
71
+ "open.label": "Open session",
72
+ "cost.label": "cost",
73
+ "scroll.more": "more",
74
+ "scroll.top": "Top",
75
+ "scroll.bottom": "Bottom",
76
+ "dismiss.label": "dismiss",
77
+ "cancel.label": "Cancel",
78
+ "status.running": "running",
79
+ "status.done": "done",
80
+ "status.cancelled": "cancelled",
81
+ "status.error": "error",
82
+ "order.desc": "Desc (newest first)",
83
+ "order.asc": "Asc (oldest first)",
84
+ "scroll.wheel": "Wheel Scroll",
85
+ "scroll.click": "Click Scroll",
86
+ "ttl.label": "TTL (Time to Live)",
87
+ "ttl.3d": "3 days",
88
+ "ttl.7d": "7 days",
89
+ "ttl.14d": "14 days",
90
+ "ttl.30d": "30 days",
91
+ "ttl.unlimited": "Never",
92
+ "ttl.toast": "TTL set to {n} days",
93
+ "ttl.toast_unlimited": "TTL set to Never",
94
+ "clear.title": "Confirm",
95
+ "clear.prompt": "Clear all sub-agent records for this session? This cannot be undone.",
96
+ "clear.prompt_running": "{n} sub-agent(s) are still running. Clearing will discard them permanently. Continue?",
97
+ "clear.done": "Cleared {n} sub-agent record(s)",
98
+ "clear.empty": "No sub-agent records in this session",
99
+ "cancel.no_session": "Child session ID is unavailable",
100
+ "cancel.not_child": "Target is not a child session",
101
+ "cancel.read_error": "Cannot read session info",
102
+ "cancel.outside_tree": "Target is outside the monitored session tree",
103
+ "cancel.already_ended": "Session already ended, no need to cancel",
104
+ "cancel.status_error": "Cannot query session status",
105
+ "cancel.sent": "Cancel instruction sent",
106
+ "cancel.failed": "Cancellation failed",
107
+ };
108
+ const JA_T = {
109
+ "panel.title": "サブエージェント",
110
+ "status.none": "サブエージェントなし",
111
+ "agent.label": "エージェント",
112
+ "status.label": "状態",
113
+ "time.label": "時間",
114
+ "tokens.label": "コンテキスト",
115
+ "error.label": "エラー",
116
+ "model.label": "モデル",
117
+ "todo.label": "進捗",
118
+ "session.label": "セッション ID",
119
+ "session.toast.copy": "上の ID を手動でコピーしてください",
120
+ "session.toast.copied": "セッション ID をコピーしました",
121
+ "session.toast.copy_failed": "システムのクリップボードにアクセスできないため、上の ID を手動でコピーしてください",
122
+ "open.label": "セッションを開く",
123
+ "cost.label": "費用",
124
+ "scroll.more": "もっと",
125
+ "scroll.top": "先頭へ",
126
+ "scroll.bottom": "末尾へ",
127
+ "dismiss.label": "完了にする",
128
+ "cancel.label": "キャンセル",
129
+ "status.running": "実行中",
130
+ "status.done": "完了",
131
+ "status.cancelled": "キャンセル済み",
132
+ "status.error": "エラー",
133
+ "order.desc": "降順(新しい順)",
134
+ "order.asc": "昇順(古い順)",
135
+ "scroll.wheel": "ホイールスクロール",
136
+ "scroll.click": "クリックスクロール",
137
+ "ttl.label": "保持期間",
138
+ "ttl.3d": "3 日",
139
+ "ttl.7d": "7 日",
140
+ "ttl.14d": "14 日",
141
+ "ttl.30d": "30 日",
142
+ "ttl.unlimited": "無期限",
143
+ "ttl.toast": "保持期間を {n} 日に設定しました",
144
+ "ttl.toast_unlimited": "保持期間を無期限に設定しました",
145
+ "clear.title": "削除の確認",
146
+ "clear.prompt": "このセッションの全サブエージェント記録を削除しますか?この操作は元に戻せません。",
147
+ "clear.prompt_running": "実行中のサブエージェントが {n} 個あります。削除すると復元できません。続行しますか?",
148
+ "clear.done": "{n} 件のサブエージェント記録を削除しました",
149
+ "clear.empty": "このセッションにサブエージェント記録はありません",
150
+ "cancel.no_session": "子セッション ID が利用できません",
151
+ "cancel.not_child": "対象は子セッションではありません",
152
+ "cancel.read_error": "セッション情報を読み取れません",
153
+ "cancel.outside_tree": "対象は監視対象のセッションツリー外です",
154
+ "cancel.already_ended": "セッションは既に終了しています",
155
+ "cancel.status_error": "セッション状態を照会できません",
156
+ "cancel.sent": "キャンセル指示を送信しました",
157
+ "cancel.failed": "キャンセルに失敗しました",
158
+ };
159
+ const KO_T = {
160
+ "panel.title": "서브 에이전트",
161
+ "status.none": "서브 에이전트 없음",
162
+ "agent.label": "에이전트",
163
+ "status.label": "상태",
164
+ "time.label": "시간",
165
+ "tokens.label": "컨텍스트",
166
+ "error.label": "오류",
167
+ "model.label": "모델",
168
+ "todo.label": "진행도",
169
+ "session.label": "세션 ID",
170
+ "session.toast.copy": "위 ID를 수동으로 복사하세요",
171
+ "session.toast.copied": "세션 ID가 복사되었습니다",
172
+ "session.toast.copy_failed": "시스템 클립보드에 접근할 수 없어 위 ID를 수동으로 복사하세요",
173
+ "open.label": "세션 열기",
174
+ "cost.label": "비용",
175
+ "scroll.more": "더 보기",
176
+ "scroll.top": "맨 위로",
177
+ "scroll.bottom": "맨 아래로",
178
+ "dismiss.label": "완료 표시",
179
+ "cancel.label": "취소",
180
+ "status.running": "실행 중",
181
+ "status.done": "완료",
182
+ "status.cancelled": "취소됨",
183
+ "status.error": "오류",
184
+ "order.desc": "내림차순(최신순)",
185
+ "order.asc": "오름차순(오래된순)",
186
+ "scroll.wheel": "휠 스크롤",
187
+ "scroll.click": "클릭 스크롤",
188
+ "ttl.label": "보존 기간",
189
+ "ttl.3d": "3일",
190
+ "ttl.7d": "7일",
191
+ "ttl.14d": "14일",
192
+ "ttl.30d": "30일",
193
+ "ttl.unlimited": "무기한",
194
+ "ttl.toast": "보존 기간을 {n}일로 설정했습니다",
195
+ "ttl.toast_unlimited": "보존 기간을 무기한으로 설정했습니다",
196
+ "clear.title": "삭제 확인",
197
+ "clear.prompt": "이 세션의 모든 서브 에이전트 기록을 삭제하시겠습니까? 이 작업은 되돌릴 수 없습니다.",
198
+ "clear.prompt_running": "실행 중인 서브 에이전트가 {n}개 있습니다. 삭제하면 복구할 수 없습니다. 계속하시겠습니까?",
199
+ "clear.done": "서브 에이전트 기록 {n}개를 삭제했습니다",
200
+ "clear.empty": "이 세션에 서브 에이전트 기록이 없습니다",
201
+ "cancel.no_session": "하위 세션 ID를 사용할 수 없습니다",
202
+ "cancel.not_child": "대상이 하위 세션이 아닙니다",
203
+ "cancel.read_error": "세션 정보를 읽을 수 없습니다",
204
+ "cancel.outside_tree": "대상이 모니터링 세션 트리 밖에 있습니다",
205
+ "cancel.already_ended": "세션이 이미 종료되었습니다",
206
+ "cancel.status_error": "세션 상태를 조회할 수 없습니다",
207
+ "cancel.sent": "취소 지시를 보냈습니다",
208
+ "cancel.failed": "취소에 실패했습니다",
209
+ };
210
+ export const LANGS = { zh: ZH_T, en: EN_T, ja: JA_T, ko: KO_T };
211
+ /** 语言元数据:/subagent-lang 选项与自动检测共用。 */
212
+ export const LANG_META = [
213
+ { code: "zh", label: "中文" },
214
+ { code: "en", label: "English" },
215
+ { code: "ja", label: "日本語" },
216
+ { code: "ko", label: "한국어" },
217
+ ];
218
+ /**
219
+ * 模板参数替换:`{key}` 占位符统一在此处理。
220
+ * 未提供的参数保留原占位符,避免静默丢失。
221
+ */
222
+ export function applyParams(tpl, params) {
223
+ if (!params)
224
+ return tpl;
225
+ return tpl.replace(/\{(\w+)\}/g, (m, k) => k in params ? String(params[k]) : m);
226
+ }
227
+ /**
228
+ * 宽松翻译工厂:未知 key 回退为 key 原样(兼容非翻译用途的键名透传)。
229
+ * `getCode` 读取语言信号——在 SolidJS 渲染/memo 上下文中调用时自动建立响应式依赖。
230
+ */
231
+ export function createT(getCode) {
232
+ return (key, params) => applyParams(LANGS[getCode()][key] ?? key, params);
233
+ }
234
+ /** 按系统 locale 自动检测语言(zh → 中文,ja → 日语,ko → 韩语,其余 → 英文)。 */
235
+ export function detectLang() {
236
+ try {
237
+ const loc = Intl.DateTimeFormat().resolvedOptions().locale.toLowerCase();
238
+ if (loc.startsWith("zh"))
239
+ return "zh";
240
+ if (loc.startsWith("ja"))
241
+ return "ja";
242
+ if (loc.startsWith("ko"))
243
+ return "ko";
244
+ return "en";
245
+ }
246
+ catch {
247
+ return "en";
248
+ }
249
+ }
package/dist/index.js CHANGED
@@ -2,122 +2,10 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "@opentui/soli
2
2
  import { createMemo, createSignal, createEffect, onMount, onCleanup, untrack, Show, For, } from "solid-js";
3
3
  import { PLUGIN_VERSION } from "./_version";
4
4
  import { copyText } from "./clipboard";
5
+ import { LANG_META, createT, detectLang } from "./i18n";
5
6
  /** OpenCode built-in tool names that spawn sub-agents or delegate tasks. */
6
7
  const SUBAGENT_TOOLS = new Set(["task", "delegate", "call_omo_agent"]);
7
8
  // ===================================================================
8
- // i18n
9
- // ===================================================================
10
- const I18N = {
11
- zh: {
12
- "panel.title": "子代理",
13
- "status.none": "暂无子代理",
14
- "agent.label": "代理",
15
- "status.label": "状态",
16
- "time.label": "耗时",
17
- "tokens.label": "上下文",
18
- "error.label": "错误",
19
- "model.label": "模型",
20
- "todo.label": "进度",
21
- "session.label": "会话 ID",
22
- "session.toast.copy": "可手动复制上方 ID",
23
- "session.toast.copied": "会话 ID 已复制",
24
- "session.toast.copy_failed": "无法访问系统剪贴板,请手动复制上方 ID",
25
- "open.label": "进入会话",
26
- "cost.label": "费用",
27
- "scroll.more": "更多",
28
- "scroll.top": "回顶",
29
- "scroll.bottom": "回底",
30
- "dismiss.label": "标记完成",
31
- "cancel.label": "取消",
32
- "status.running": "运行中",
33
- "status.done": "已完成",
34
- "status.cancelled": "已取消",
35
- "status.error": "错误",
36
- "order.desc": "降序(最新在前)",
37
- "order.asc": "升序(最早在前)",
38
- "scroll.wheel": "滚轮翻页",
39
- "scroll.click": "点击翻页",
40
- "ttl.label": "清理周期",
41
- "ttl.3d": "3 天",
42
- "ttl.7d": "7 天",
43
- "ttl.14d": "14 天",
44
- "ttl.30d": "30 天",
45
- "ttl.unlimited": "无期限",
46
- "ttl.toast": "清理周期已设为 {n} 天",
47
- "ttl.toast_unlimited": "清理周期已设为无期限",
48
- "clear.title": "确认清除",
49
- "clear.prompt": "确定清除当前会话所有子代理记录?此操作不可撤销。",
50
- "clear.prompt_running": "当前有 {n} 个运行中的子代理,清除后将不可恢复。确定继续?",
51
- "clear.done": "已清除 {n} 条子代理记录",
52
- "clear.empty": "当前会话无子代理记录",
53
- "cancel.no_session": "子会话 ID 不可用",
54
- "cancel.not_child": "目标不是子会话",
55
- "cancel.read_error": "无法读取会话信息",
56
- "cancel.outside_tree": "目标不在当前监控会话树中",
57
- "cancel.already_ended": "会话已结束,无需取消",
58
- "cancel.status_error": "无法查询会话状态",
59
- "cancel.sent": "已发送取消指令",
60
- "cancel.failed": "取消失败",
61
- },
62
- en: {
63
- "panel.title": "SubAgent",
64
- "status.none": "No sub-agents yet",
65
- "agent.label": "agent",
66
- "status.label": "status",
67
- "time.label": "time",
68
- "tokens.label": "tokens",
69
- "error.label": "error",
70
- "model.label": "model",
71
- "todo.label": "todo",
72
- "session.label": "session ID",
73
- "session.toast.copy": "Copy the ID above manually",
74
- "session.toast.copied": "Session ID copied",
75
- "session.toast.copy_failed": "Cannot access the system clipboard; copy the ID above manually",
76
- "open.label": "Open session",
77
- "cost.label": "cost",
78
- "scroll.more": "more",
79
- "scroll.top": "Top",
80
- "scroll.bottom": "Bottom",
81
- "dismiss.label": "dismiss",
82
- "cancel.label": "Cancel",
83
- "status.running": "running",
84
- "status.done": "done",
85
- "status.cancelled": "cancelled",
86
- "status.error": "error",
87
- "order.desc": "Desc (newest first)",
88
- "order.asc": "Asc (oldest first)",
89
- "scroll.wheel": "Wheel Scroll",
90
- "scroll.click": "Click Scroll",
91
- "ttl.label": "TTL (Time to Live)",
92
- "ttl.3d": "3 days",
93
- "ttl.7d": "7 days",
94
- "ttl.14d": "14 days",
95
- "ttl.30d": "30 days",
96
- "ttl.unlimited": "Never",
97
- "ttl.toast": "TTL set to {n} days",
98
- "ttl.toast_unlimited": "TTL set to Never",
99
- "clear.title": "Confirm",
100
- "clear.prompt": "Clear all sub-agent records for this session? This cannot be undone.",
101
- "clear.prompt_running": "{n} sub-agent(s) are still running. Clearing will discard them permanently. Continue?",
102
- "clear.done": "Cleared {n} sub-agent record(s)",
103
- "clear.empty": "No sub-agent records in this session",
104
- "cancel.no_session": "Child session ID is unavailable",
105
- "cancel.not_child": "Target is not a child session",
106
- "cancel.read_error": "Cannot read session info",
107
- "cancel.outside_tree": "Target is outside the monitored session tree",
108
- "cancel.already_ended": "Session already ended, no need to cancel",
109
- "cancel.status_error": "Cannot query session status",
110
- "cancel.sent": "Cancel instruction sent",
111
- "cancel.failed": "Cancellation failed",
112
- },
113
- };
114
- function detectLang() {
115
- const env = process?.env?.OPENCODE_LANG ?? process?.env?.LANG ?? "";
116
- if (env.startsWith("zh"))
117
- return "zh";
118
- return "en";
119
- }
120
- // ===================================================================
121
9
  // Helpers — visual width
122
10
  // ===================================================================
123
11
  function charColumns(c) {
@@ -268,7 +156,7 @@ const globalEntryCache = new Map();
268
156
  // 模块级刷新信号:外部(如斜杠命令)触发清除后 +1,组件 scan 依赖它以重扫。
269
157
  const [clearTick, setClearTick] = createSignal(0);
270
158
  function SubAgentPanel(props) {
271
- const t = (key) => I18N[props.lang()][key] ?? key;
159
+ const t = createT(() => props.lang());
272
160
  // ── session data (single-key, true deletion on cleanup) ──
273
161
  const SESSION_DATA_KEY = `${KV_PREFIX}.session_data`;
274
162
  const ttlDaysRaw = parseInt(String(props.api.kv.get(`${KV_PREFIX}.ttl_days`, "3")), 10);
@@ -481,8 +369,9 @@ function SubAgentPanel(props) {
481
369
  const [renderTick, setRenderTick] = createSignal(0);
482
370
  let boxEl;
483
371
  let disposed = false;
484
- /** Total context tokens for a sub-agent session.
485
- * Matches opencode-visual-cache's "总计": last assistant message's input + cache.read. */
372
+ /** Total usage tokens for a sub-agent session.
373
+ * Matches opencode's bottom status bar usage: last assistant message with
374
+ * output > 0, summing input + output + reasoning + cache read/write. */
486
375
  const readSessionTokens = (sid) => {
487
376
  if (!sid)
488
377
  return undefined;
@@ -494,10 +383,13 @@ function SubAgentPanel(props) {
494
383
  if (m.role !== "assistant")
495
384
  continue;
496
385
  const t = m.tokens;
497
- if (!t)
386
+ if (!t || !(Number(t.output) > 0))
498
387
  continue;
499
- const cache = t.cache;
500
- const ctx = (Number(t.input) || 0) + (cache?.read ?? 0);
388
+ const ctx = (Number(t.input) || 0) +
389
+ (Number(t.output) || 0) +
390
+ (Number(t.reasoning) || 0) +
391
+ (Number(t.cache?.read) || 0) +
392
+ (Number(t.cache?.write) || 0);
501
393
  if (ctx > 0)
502
394
  return ctx;
503
395
  }
@@ -616,7 +508,7 @@ function SubAgentPanel(props) {
616
508
  if (!childId) {
617
509
  props.api.ui.toast({
618
510
  title: entry.title || entry.agent,
619
- message: t("cancel.label") + ": " + (I18N[props.lang()]["cancel.no_session"] ?? "Child session ID is unavailable"),
511
+ message: t("cancel.label") + ": " + t("cancel.no_session"),
620
512
  });
621
513
  return;
622
514
  }
@@ -625,7 +517,7 @@ function SubAgentPanel(props) {
625
517
  if (!child?.parentID) {
626
518
  props.api.ui.toast({
627
519
  title: entry.title || entry.agent,
628
- message: t("cancel.label") + ": " + (I18N[props.lang()]["cancel.not_child"] ?? "Target is not a child session"),
520
+ message: t("cancel.label") + ": " + t("cancel.not_child"),
629
521
  });
630
522
  return;
631
523
  }
@@ -633,14 +525,14 @@ function SubAgentPanel(props) {
633
525
  catch {
634
526
  props.api.ui.toast({
635
527
  title: entry.title || entry.agent,
636
- message: t("cancel.label") + ": " + (I18N[props.lang()]["cancel.read_error"] ?? "Cannot read session info"),
528
+ message: t("cancel.label") + ": " + t("cancel.read_error"),
637
529
  });
638
530
  return;
639
531
  }
640
532
  if (!isDescendantOf(childId, props.sessionId)) {
641
533
  props.api.ui.toast({
642
534
  title: entry.title || entry.agent,
643
- message: t("cancel.label") + ": " + (I18N[props.lang()]["cancel.outside_tree"] ?? "Target is outside the monitored session tree"),
535
+ message: t("cancel.label") + ": " + t("cancel.outside_tree"),
644
536
  });
645
537
  return;
646
538
  }
@@ -660,7 +552,7 @@ function SubAgentPanel(props) {
660
552
  catch {
661
553
  props.api.ui.toast({
662
554
  title: entry.title || entry.agent,
663
- message: t("cancel.label") + ": " + (I18N[props.lang()]["cancel.status_error"] ?? "Cannot query session status"),
555
+ message: t("cancel.label") + ": " + t("cancel.status_error"),
664
556
  });
665
557
  return;
666
558
  }
@@ -676,7 +568,7 @@ function SubAgentPanel(props) {
676
568
  status: "cancel_requested", sessionId: entry.sessionId,
677
569
  abortAccepted: true,
678
570
  });
679
- props.api.ui.toast({ message: t("cancel.label") + ": " + (I18N[props.lang()]["cancel.sent"] ?? "Cancel instruction sent") });
571
+ props.api.ui.toast({ message: t("cancel.label") + ": " + t("cancel.sent") });
680
572
  }
681
573
  catch (err) {
682
574
  upsertEntry({
@@ -686,7 +578,7 @@ function SubAgentPanel(props) {
686
578
  });
687
579
  props.api.ui.toast({
688
580
  title: entry.title || entry.agent,
689
- message: t("cancel.label") + ": " + (I18N[props.lang()]["cancel.failed"] ?? "Cancellation failed"),
581
+ message: t("cancel.label") + ": " + t("cancel.failed"),
690
582
  });
691
583
  }
692
584
  };
@@ -1611,7 +1503,7 @@ const KV_PREFIX = "subagent_magazine";
1611
1503
  const tui = async (api) => {
1612
1504
  // ── language ──
1613
1505
  const stored = String(api.kv.get(`${KV_PREFIX}.lang`, ""));
1614
- const initialLang = stored === "zh" || stored === "en" ? stored : detectLang();
1506
+ const initialLang = LANG_META.some((m) => m.code === stored) ? stored : detectLang();
1615
1507
  const [lang, setLang] = createSignal(initialLang);
1616
1508
  const [maxEntries, setMaxEntries] = createSignal(parseInt(String(api.kv.get(`${KV_PREFIX}.max_entries`, "10")), 10) || 10);
1617
1509
  const [sortOrder, setSortOrder] = createSignal(String(api.kv.get(`${KV_PREFIX}.order`, "desc")) === "asc" ? "asc" : "desc");
@@ -1626,15 +1518,12 @@ const tui = async (api) => {
1626
1518
  description: "Switch display language (中文 / English)",
1627
1519
  slash: { name: "subagent-lang" },
1628
1520
  onSelect: (dialog) => {
1629
- dialog?.replace(() => (_jsx(api.ui.DialogSelect, { title: "Language / \u8BED\u8A00", options: [
1630
- { title: "中文", value: "zh" },
1631
- { title: "English", value: "en" },
1632
- ], onSelect: (opt) => {
1521
+ dialog?.replace(() => (_jsx(api.ui.DialogSelect, { title: "Language / \u8BED\u8A00", options: LANG_META.map((m) => ({ title: m.label, value: m.code })), onSelect: (opt) => {
1633
1522
  const l = opt.value;
1634
1523
  setLang(l);
1635
1524
  api.kv.set(`${KV_PREFIX}.lang`, l);
1636
1525
  api.ui.toast({
1637
- message: l === "zh" ? "语言: 中文" : "Language: English",
1526
+ message: "Language: " + (LANG_META.find((m) => m.code === l)?.label ?? l),
1638
1527
  });
1639
1528
  dialog?.clear();
1640
1529
  } })));
@@ -1646,7 +1535,7 @@ const tui = async (api) => {
1646
1535
  description: "Set sub-agent entry sort order (desc / asc)",
1647
1536
  slash: { name: "subagent-order" },
1648
1537
  onSelect: (dialog) => {
1649
- const t = (k) => I18N[lang()][k] ?? k;
1538
+ const t = createT(() => lang());
1650
1539
  dialog?.replace(() => (_jsx(api.ui.DialogSelect, { title: "Sort Order / \u6392\u5E8F\u65B9\u5F0F", options: [
1651
1540
  { title: t("order.desc"), value: "desc" },
1652
1541
  { title: t("order.asc"), value: "asc" },
@@ -1667,7 +1556,7 @@ const tui = async (api) => {
1667
1556
  description: "Set scroll mode (wheel / click)",
1668
1557
  slash: { name: "subagent-scroll" },
1669
1558
  onSelect: (dialog) => {
1670
- const t = (k) => I18N[lang()][k] ?? k;
1559
+ const t = createT(() => lang());
1671
1560
  dialog?.replace(() => (_jsx(api.ui.DialogSelect, { title: "Scroll Mode / \u6EDA\u52A8\u6A21\u5F0F", options: [
1672
1561
  { title: t("scroll.wheel"), value: "wheel" },
1673
1562
  { title: t("scroll.click"), value: "click" },
@@ -1786,7 +1675,7 @@ const tui = async (api) => {
1786
1675
  description: "Set session data retention period (days before auto-cleanup)",
1787
1676
  slash: { name: "subagent-ttl" },
1788
1677
  onSelect: (dialog) => {
1789
- const t = (k) => I18N[signals.lang()][k] ?? k;
1678
+ const t = createT(() => signals.lang());
1790
1679
  const curRaw = parseInt(String(api.kv.get(`${KV_PREFIX}.ttl_days`, "3")), 10);
1791
1680
  const curDays = Number.isNaN(curRaw) ? 3 : curRaw;
1792
1681
  const curLabel = curDays === 0 ? t("ttl.unlimited") : `${curDays}d`;
@@ -1799,7 +1688,7 @@ const tui = async (api) => {
1799
1688
  ], onSelect: (opt) => {
1800
1689
  const days = parseInt(opt.value, 10);
1801
1690
  api.kv.set(`${KV_PREFIX}.ttl_days`, String(days));
1802
- const msg = days === 0 ? t("ttl.toast_unlimited") : t("ttl.toast").replace("{n}", String(days));
1691
+ const msg = days === 0 ? t("ttl.toast_unlimited") : t("ttl.toast", { n: days });
1803
1692
  api.ui.toast({ message: msg });
1804
1693
  dialog?.clear();
1805
1694
  } })));
@@ -1811,7 +1700,7 @@ const tui = async (api) => {
1811
1700
  description: "Delete all sub-agent records for the current session (cannot be undone)",
1812
1701
  slash: { name: "subagent-clear-entries" },
1813
1702
  onSelect: (dialog) => {
1814
- const t = (k) => I18N[signals.lang()][k] ?? k;
1703
+ const t = createT(() => signals.lang());
1815
1704
  const sid = signals.sessionId;
1816
1705
  const sessionObj = api.state.session.get(sid);
1817
1706
  const parentID = sessionObj?.parentID;
@@ -1825,7 +1714,7 @@ const tui = async (api) => {
1825
1714
  }
1826
1715
  }
1827
1716
  const msg = runningCount > 0
1828
- ? t("clear.prompt_running").replace("{n}", String(runningCount))
1717
+ ? t("clear.prompt_running", { n: runningCount })
1829
1718
  : t("clear.prompt");
1830
1719
  dialog?.replace(() => (_jsx(api.ui.DialogConfirm, { title: t("clear.title"), message: msg, onConfirm: () => {
1831
1720
  try {
@@ -1855,7 +1744,7 @@ const tui = async (api) => {
1855
1744
  api.kv.set(`${KV_PREFIX}.session_data`, JSON.stringify(data));
1856
1745
  globalEntryCache.delete(sid);
1857
1746
  setClearTick((v) => v + 1);
1858
- const msg = t("clear.done").replace("{n}", String(count));
1747
+ const msg = t("clear.done", { n: count });
1859
1748
  api.ui.toast({ message: msg });
1860
1749
  }
1861
1750
  catch { }