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 CHANGED
@@ -57,7 +57,12 @@
57
57
  - **手动清除条目**:`/subagent-clear-entries` 清除当前会话所有记录,防止历史条目扫描重建
58
58
  - **语言适配**:支持 `/subagent-lang` 运行时切换中/英文,偏好持久化
59
59
  - **可切换边框**:`/subagent-border` 控制面板外边框显隐(默认隐藏),开启后与缓存统计面板风格统一
60
- - **斜杠命令**:`/subagent-lang` `/subagent-max` `/subagent-order` `/subagent-scroll` `/subagent-ttl` `/subagent-clear-entries` `/subagent-session` `/subagent-version` `/subagent-clear-running` `/subagent-border` 动态配置
60
+ - **多实例安全**:多开 TUI 时条目不再互相覆盖(原子合并写入)
61
+ - **漏事件兜底**:周期性校正 + 历史扫描 + 发现轮询,掉事件的子代理也能恢复显示
62
+ - **条目显示开关**:`/subagent-cost` 控制列表内条目费用(4 位小数,默认关;展开区始终显示);`/subagent-time`、`/subagent-tokens` 控制耗时与 Token(默认开,保持原有显示)
63
+ - **耗时格式**:`/subagent-time-format` 可选 short(默认)/ decimal / clock / compact / seconds
64
+ - **设置菜单**:`/subagent-sections` 打开原生循环设置菜单(V2);`/subagent-config` 查看当前设置摘要
65
+ - **斜杠命令**:`/subagent-lang` `/subagent-max` `/subagent-order` `/subagent-scroll` `/subagent-ttl` `/subagent-clear-entries` `/subagent-session` `/subagent-version` `/subagent-clear-running` `/subagent-border` `/subagent-config` `/subagent-sections` `/subagent-cost` `/subagent-time` `/subagent-tokens` `/subagent-time-format` 动态配置
61
66
 
62
67
  ---
63
68
 
@@ -152,6 +157,12 @@ npm install -g opencode-subagent-magazine@latest
152
157
  | `/subagent-version` | 查看插件版本 | 弹出当前插件版本号 |
153
158
  | `/subagent-clear-running` | 批量清理僵尸条目 | 一键将所有运行中的条目标记为完成,清理卡住的旧数据 |
154
159
  | `/subagent-border` | 开关面板边框 | 切换面板外边框显隐(默认隐藏),即时生效并持久化 |
160
+ | `/subagent-config` | 查看设置摘要 | 弹窗列出语言、条目数、排序、翻页、保留期、边框与各显示开关的当前值 |
161
+ | `/subagent-sections` | 打开设置菜单 | 原生选择列表逐项修改,修改后自动回到菜单(Esc 关闭) |
162
+ | `/subagent-cost` | 开关条目费用 | 列表内条目费用显示(4 位小数,默认关;展开区始终显示) |
163
+ | `/subagent-time` | 开关条目耗时 | 列表内耗时显示(默认开) |
164
+ | `/subagent-tokens` | 开关条目 Token | 列表内 Token 显示(默认开) |
165
+ | `/subagent-time-format` | 切换耗时格式 | short(默认 45.32s / 112m35s)/ decimal / clock / compact / seconds |
155
166
 
156
167
  <div align="center">
157
168
  <img src="https://raw.githubusercontent.com/Hotakus/opencode-subagent-magazine/master/assets/slash_cmds.png" alt="斜杠命令" width="49%"></img>
@@ -1 +1 @@
1
- export declare const PLUGIN_VERSION = "1.6.1";
1
+ export declare const PLUGIN_VERSION = "1.7.1";
package/dist/_version.js CHANGED
@@ -1,2 +1,2 @@
1
1
  // auto-generated
2
- export const PLUGIN_VERSION = "1.6.1";
2
+ export const PLUGIN_VERSION = "1.7.1";
@@ -1,5 +1,12 @@
1
+ import type { TimeFormat } from "./types";
1
2
  export declare function visualWidth(s: string): number;
2
3
  export declare function truncate(text: string, maxCols: number): string;
3
4
  export declare function fmtDurationShort(ms: number, running: boolean): string;
5
+ /** All supported elapsed-time display modes, in picker order. */
6
+ export declare const TIME_FORMATS: readonly TimeFormat[];
7
+ /** Neutral samples shown in the time-format picker (language independent). */
8
+ export declare const TIME_FORMAT_SAMPLES: Record<TimeFormat, string>;
9
+ /** Formats elapsed milliseconds using the configured display mode. */
10
+ export declare function fmtDuration(ms: number, running: boolean, mode?: TimeFormat): string;
4
11
  export declare function fmtTokens(n: number): string;
5
12
  export declare function safeErrorMsg(err: unknown): string;
@@ -50,6 +50,51 @@ export function fmtDurationShort(ms, running) {
50
50
  const s = Math.round((ms % 60000) / 1000);
51
51
  return `${m}m${s}s`;
52
52
  }
53
+ /** All supported elapsed-time display modes, in picker order. */
54
+ export const TIME_FORMATS = ["short", "decimal", "clock", "compact", "seconds"];
55
+ /** Neutral samples shown in the time-format picker (language independent). */
56
+ export const TIME_FORMAT_SAMPLES = {
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
+ /** Formats elapsed milliseconds using the configured display mode. */
64
+ export function fmtDuration(ms, running, mode = "short") {
65
+ if (running && ms < 2000)
66
+ 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)
73
+ return `${Math.floor(t / 1000)}s`;
74
+ if (t < 3600000)
75
+ return `${Math.floor(t / 60000)}m${Math.floor((t % 60000) / 1000)}s`;
76
+ return `${Math.floor(t / 3600000)}h${Math.floor((t % 3600000) / 60000)}m`;
77
+ }
78
+ case "clock": {
79
+ const s = Math.floor((t % 60000) / 1000);
80
+ if (t < 3600000)
81
+ return `${Math.floor(t / 60000)}:${String(s).padStart(2, "0")}`;
82
+ const m = Math.floor((t % 3600000) / 60000);
83
+ return `${Math.floor(t / 3600000)}:${String(m).padStart(2, "0")}:${String(s).padStart(2, "0")}`;
84
+ }
85
+ case "seconds":
86
+ return `${Math.floor(t / 1000)}s`;
87
+ case "decimal":
88
+ default: {
89
+ // Promote units when rounding would show "60.0s" / "60.0m".
90
+ if (t < 59950)
91
+ return `${(t / 1000).toFixed(1)}s`;
92
+ if (t < 3597000)
93
+ return `${(t / 60000).toFixed(1)}m`;
94
+ return `${(t / 3600000).toFixed(1)}h`;
95
+ }
96
+ }
97
+ }
53
98
  export function fmtTokens(n) {
54
99
  if (n < 1000)
55
100
  return `${n}`;
package/dist/core/kv.d.ts CHANGED
@@ -5,6 +5,13 @@ export declare const SESSION_DATA_KEY = "subagent_magazine.session_data";
5
5
  export interface KVApi {
6
6
  get(key: string, fallback?: unknown): unknown;
7
7
  set(key: string, value: unknown): void;
8
+ /**
9
+ * Optional atomic read-modify-write. When the host exposes it (V2), the
10
+ * updater runs under the host storage lock against the latest on-disk value,
11
+ * which is what makes concurrent TUI instances safe: no instance can
12
+ * overwrite another one's fresh writes with a stale in-memory snapshot.
13
+ */
14
+ update?(key: string, updater: (current: unknown) => unknown): void | Promise<void>;
8
15
  }
9
16
  /** Setting keys shared by V1/V2 (values are raw strings in KV). */
10
17
  export declare const SETTING_KEYS: {
@@ -15,7 +22,21 @@ export declare const SETTING_KEYS: {
15
22
  readonly open: "subagent_magazine.open";
16
23
  readonly ttlDays: "subagent_magazine.ttl_days";
17
24
  readonly border: "subagent_magazine.border";
25
+ readonly showEntryCost: "subagent_magazine.show_entry_cost";
26
+ readonly showEntryTime: "subagent_magazine.show_entry_time";
27
+ readonly showEntryTokens: "subagent_magazine.show_entry_tokens";
28
+ readonly timeFormat: "subagent_magazine.time_format";
29
+ readonly showOrigin: "subagent_magazine.show_origin";
30
+ readonly dbSync: "subagent_magazine.db_sync";
18
31
  };
19
32
  export declare function loadSessionData(kv: KVApi): Record<string, SessionRecord>;
20
- export declare function saveSessionData(kv: KVApi, data: Record<string, SessionRecord>): void;
33
+ /**
34
+ * Atomic read-merge-write of the session store.
35
+ *
36
+ * `mutator` receives the latest persisted map (read under the host lock when
37
+ * `kv.update` is available) and may mutate it freely; the result is written
38
+ * back. Every writer must go through here: writing a whole in-memory snapshot
39
+ * directly would silently drop entries created by the other TUIs.
40
+ */
41
+ export declare function updateSessionData(kv: KVApi, mutator: (data: Record<string, SessionRecord>) => void): void | Promise<void>;
21
42
  export declare function readTTLDays(kv: KVApi): number;
package/dist/core/kv.js CHANGED
@@ -9,6 +9,12 @@ export const SETTING_KEYS = {
9
9
  open: `${KV_PREFIX}.open`,
10
10
  ttlDays: `${KV_PREFIX}.ttl_days`,
11
11
  border: `${KV_PREFIX}.border`,
12
+ showEntryCost: `${KV_PREFIX}.show_entry_cost`,
13
+ showEntryTime: `${KV_PREFIX}.show_entry_time`,
14
+ showEntryTokens: `${KV_PREFIX}.show_entry_tokens`,
15
+ timeFormat: `${KV_PREFIX}.time_format`,
16
+ showOrigin: `${KV_PREFIX}.show_origin`,
17
+ dbSync: `${KV_PREFIX}.db_sync`,
12
18
  };
13
19
  export function loadSessionData(kv) {
14
20
  try {
@@ -19,12 +25,39 @@ export function loadSessionData(kv) {
19
25
  return {};
20
26
  }
21
27
  }
22
- export function saveSessionData(kv, data) {
28
+ /**
29
+ * Atomic read-merge-write of the session store.
30
+ *
31
+ * `mutator` receives the latest persisted map (read under the host lock when
32
+ * `kv.update` is available) and may mutate it freely; the result is written
33
+ * back. Every writer must go through here: writing a whole in-memory snapshot
34
+ * directly would silently drop entries created by the other TUIs.
35
+ */
36
+ export function updateSessionData(kv, mutator) {
23
37
  try {
38
+ if (typeof kv.update === "function") {
39
+ return kv.update(SESSION_DATA_KEY, (raw) => {
40
+ const data = parseSessionData(raw);
41
+ mutator(data);
42
+ return JSON.stringify(data);
43
+ });
44
+ }
45
+ // Fallback (V1 hosts without atomic update): read-modify-write. The window
46
+ // is small and the mutation is merged against the value we just read.
47
+ const data = loadSessionData(kv);
48
+ mutator(data);
24
49
  kv.set(SESSION_DATA_KEY, JSON.stringify(data));
25
50
  }
26
51
  catch { }
27
52
  }
53
+ function parseSessionData(raw) {
54
+ try {
55
+ return JSON.parse(String(raw ?? "{}"));
56
+ }
57
+ catch {
58
+ return {};
59
+ }
60
+ }
28
61
  export function readTTLDays(kv) {
29
62
  const ttlDaysRaw = parseInt(String(kv.get(SETTING_KEYS.ttlDays, "3")), 10);
30
63
  return Number.isNaN(ttlDaysRaw) ? 3 : ttlDaysRaw;
@@ -18,12 +18,20 @@ export interface SubEntry {
18
18
  cancelRequestedAt?: number;
19
19
  abortAccepted?: boolean;
20
20
  cancelReason?: "manual";
21
+ /** 来源:工具调用条目 / 衍生会话条目(spawn)。 */
22
+ origin?: "tool" | "sub";
23
+ /** 工具输入带 background/run_in_background(后台衍生会话)。 */
24
+ background?: boolean;
21
25
  }
22
26
  export type Lang = LangCode;
23
27
  export type SortOrder = "desc" | "asc";
24
28
  export type ScrollMode = "wheel" | "click";
29
+ /** How elapsed time is rendered in the sidebar (see TIME_FORMAT_SAMPLES). */
30
+ export type TimeFormat = "decimal" | "short" | "clock" | "compact" | "seconds";
25
31
  /** OpenCode built-in tool names that spawn sub-agents or delegate tasks. */
26
32
  export declare const SUBAGENT_TOOLS: Set<string>;
33
+ /** 工具输入是否声明后台执行(V1/V2 两种字段名)。 */
34
+ export declare function isBackgroundInput(input: unknown): boolean;
27
35
  export interface ChildRecord {
28
36
  scroll: number;
29
37
  expanded: string;
@@ -49,5 +57,17 @@ export interface SharedSignals {
49
57
  setScrollMode: (m: ScrollMode) => void;
50
58
  borderVisible: () => boolean;
51
59
  setBorderVisible: (v: boolean) => void;
60
+ showEntryCost: () => boolean;
61
+ setShowEntryCost: (v: boolean) => void;
62
+ showEntryTime: () => boolean;
63
+ setShowEntryTime: (v: boolean) => void;
64
+ showEntryTokens: () => boolean;
65
+ setShowEntryTokens: (v: boolean) => void;
66
+ timeFormat: () => TimeFormat;
67
+ setTimeFormat: (f: TimeFormat) => void;
68
+ showOrigin: () => boolean;
69
+ setShowOrigin: (v: boolean) => void;
70
+ dbSync: () => boolean;
71
+ setDbSync: (v: boolean) => void;
52
72
  sessionId: string;
53
73
  }
@@ -1,2 +1,9 @@
1
1
  /** OpenCode built-in tool names that spawn sub-agents or delegate tasks. */
2
2
  export const SUBAGENT_TOOLS = new Set(["task", "subagent", "delegate", "call_omo_agent"]);
3
+ /** 工具输入是否声明后台执行(V1/V2 两种字段名)。 */
4
+ export function isBackgroundInput(input) {
5
+ if (!input || typeof input !== "object")
6
+ return false;
7
+ const i = input;
8
+ return i.background === true || i.run_in_background === true;
9
+ }
package/dist/i18n.d.ts CHANGED
@@ -3,6 +3,10 @@ declare const ZH_T: {
3
3
  readonly "panel.title": "子代理";
4
4
  readonly "status.none": "暂无子代理";
5
5
  readonly "agent.label": "代理";
6
+ readonly "origin.label": "来源";
7
+ readonly "origin.tool": "子代理调用";
8
+ readonly "origin.sub": "衍生会话";
9
+ readonly "origin.background": "后台";
6
10
  readonly "status.label": "状态";
7
11
  readonly "time.label": "耗时";
8
12
  readonly "tokens.label": "上下文";
@@ -40,6 +44,7 @@ declare const ZH_T: {
40
44
  readonly "clear.prompt": "确定清除当前会话所有子代理记录?此操作不可撤销。";
41
45
  readonly "clear.prompt_running": "当前有 {n} 个运行中的子代理,清除后将不可恢复。确定继续?";
42
46
  readonly "clear.done": "已清除 {n} 条子代理记录";
47
+ readonly "clear.failed": "清除失败";
43
48
  readonly "clear.empty": "当前会话无子代理记录";
44
49
  readonly "cancel.no_session": "子会话 ID 不可用";
45
50
  readonly "cancel.not_child": "目标不是子会话";
@@ -51,6 +56,25 @@ declare const ZH_T: {
51
56
  readonly "cancel.failed": "取消失败";
52
57
  readonly borderShown: "面板边框 已显示";
53
58
  readonly borderHidden: "面板边框 已隐藏";
59
+ readonly "settings.title": "SubAgent Magazine — 设置";
60
+ readonly "settings.section.general": "通用";
61
+ readonly "settings.lang": "语言";
62
+ readonly "settings.max": "最大条目数";
63
+ readonly "settings.order": "排序方式";
64
+ readonly "settings.scroll": "滚动模式";
65
+ readonly "settings.ttl": "保留期限";
66
+ readonly "settings.border": "面板边框";
67
+ readonly "settings.showEntryCost": "每条子代理显示费用";
68
+ readonly "settings.showEntryTime": "列表中显示耗时";
69
+ readonly "settings.showEntryTokens": "列表中显示 Token 数";
70
+ readonly "settings.timeFormat": "时间格式";
71
+ readonly "settings.showOrigin": "列表显示来源标记";
72
+ readonly "settings.dbSync": "本地数据库补全";
73
+ readonly "settings.on": "开";
74
+ readonly "settings.off": "关";
75
+ readonly "settings.saved": "已保存 {item}:{value}";
76
+ readonly "settings.invalid": "无效的数值";
77
+ readonly "settings.close": "关闭";
54
78
  };
55
79
  /** 结构约束:值放宽为 string,键集合来自中文表(新增语言缺 key 会编译报错)。 */
56
80
  export type Translation = {
package/dist/i18n.js CHANGED
@@ -7,6 +7,10 @@ const ZH_T = {
7
7
  "panel.title": "子代理",
8
8
  "status.none": "暂无子代理",
9
9
  "agent.label": "代理",
10
+ "origin.label": "来源",
11
+ "origin.tool": "子代理调用",
12
+ "origin.sub": "衍生会话",
13
+ "origin.background": "后台",
10
14
  "status.label": "状态",
11
15
  "time.label": "耗时",
12
16
  "tokens.label": "上下文",
@@ -44,6 +48,7 @@ const ZH_T = {
44
48
  "clear.prompt": "确定清除当前会话所有子代理记录?此操作不可撤销。",
45
49
  "clear.prompt_running": "当前有 {n} 个运行中的子代理,清除后将不可恢复。确定继续?",
46
50
  "clear.done": "已清除 {n} 条子代理记录",
51
+ "clear.failed": "清除失败",
47
52
  "clear.empty": "当前会话无子代理记录",
48
53
  "cancel.no_session": "子会话 ID 不可用",
49
54
  "cancel.not_child": "目标不是子会话",
@@ -55,11 +60,34 @@ const ZH_T = {
55
60
  "cancel.failed": "取消失败",
56
61
  "borderShown": "面板边框 已显示",
57
62
  "borderHidden": "面板边框 已隐藏",
63
+ "settings.title": "SubAgent Magazine — 设置",
64
+ "settings.section.general": "通用",
65
+ "settings.lang": "语言",
66
+ "settings.max": "最大条目数",
67
+ "settings.order": "排序方式",
68
+ "settings.scroll": "滚动模式",
69
+ "settings.ttl": "保留期限",
70
+ "settings.border": "面板边框",
71
+ "settings.showEntryCost": "每条子代理显示费用",
72
+ "settings.showEntryTime": "列表中显示耗时",
73
+ "settings.showEntryTokens": "列表中显示 Token 数",
74
+ "settings.timeFormat": "时间格式",
75
+ "settings.showOrigin": "列表显示来源标记",
76
+ "settings.dbSync": "本地数据库补全",
77
+ "settings.on": "开",
78
+ "settings.off": "关",
79
+ "settings.saved": "已保存 {item}:{value}",
80
+ "settings.invalid": "无效的数值",
81
+ "settings.close": "关闭",
58
82
  };
59
83
  const EN_T = {
60
84
  "panel.title": "SubAgent",
61
85
  "status.none": "No sub-agents yet",
62
86
  "agent.label": "agent",
87
+ "origin.label": "origin",
88
+ "origin.tool": "sub-agent call",
89
+ "origin.sub": "spawned session",
90
+ "origin.background": "background",
63
91
  "status.label": "status",
64
92
  "time.label": "time",
65
93
  "tokens.label": "tokens",
@@ -97,6 +125,7 @@ const EN_T = {
97
125
  "clear.prompt": "Clear all sub-agent records for this session? This cannot be undone.",
98
126
  "clear.prompt_running": "{n} sub-agent(s) are still running. Clearing will discard them permanently. Continue?",
99
127
  "clear.done": "Cleared {n} sub-agent record(s)",
128
+ "clear.failed": "Clear failed",
100
129
  "clear.empty": "No sub-agent records in this session",
101
130
  "cancel.no_session": "Child session ID is unavailable",
102
131
  "cancel.not_child": "Target is not a child session",
@@ -108,11 +137,34 @@ const EN_T = {
108
137
  "cancel.failed": "Cancellation failed",
109
138
  "borderShown": "Panel border shown",
110
139
  "borderHidden": "Panel border hidden",
140
+ "settings.title": "SubAgent Magazine — Settings",
141
+ "settings.section.general": "General",
142
+ "settings.lang": "Language",
143
+ "settings.max": "Max entries",
144
+ "settings.order": "Sort order",
145
+ "settings.scroll": "Scroll mode",
146
+ "settings.ttl": "Retention",
147
+ "settings.border": "Panel border",
148
+ "settings.showEntryCost": "Cost per sub-agent",
149
+ "settings.showEntryTime": "Elapsed time in list",
150
+ "settings.showEntryTokens": "Token count in list",
151
+ "settings.showOrigin": "Origin indicators in list",
152
+ "settings.timeFormat": "Time format",
153
+ "settings.dbSync": "Local database enrichment",
154
+ "settings.on": "on",
155
+ "settings.off": "off",
156
+ "settings.saved": "Saved {item}: {value}",
157
+ "settings.invalid": "Invalid value",
158
+ "settings.close": "Close",
111
159
  };
112
160
  const JA_T = {
113
161
  "panel.title": "サブエージェント",
114
162
  "status.none": "サブエージェントなし",
115
163
  "agent.label": "エージェント",
164
+ "origin.label": "発生元",
165
+ "origin.tool": "サブエージェント呼び出し",
166
+ "origin.sub": "生成されたセッション",
167
+ "origin.background": "バックグラウンド",
116
168
  "status.label": "状態",
117
169
  "time.label": "時間",
118
170
  "tokens.label": "コンテキスト",
@@ -150,6 +202,7 @@ const JA_T = {
150
202
  "clear.prompt": "このセッションの全サブエージェント記録を削除しますか?この操作は元に戻せません。",
151
203
  "clear.prompt_running": "実行中のサブエージェントが {n} 個あります。削除すると復元できません。続行しますか?",
152
204
  "clear.done": "{n} 件のサブエージェント記録を削除しました",
205
+ "clear.failed": "クリアに失敗しました",
153
206
  "clear.empty": "このセッションにサブエージェント記録はありません",
154
207
  "cancel.no_session": "子セッション ID が利用できません",
155
208
  "cancel.not_child": "対象は子セッションではありません",
@@ -161,11 +214,34 @@ const JA_T = {
161
214
  "cancel.failed": "キャンセルに失敗しました",
162
215
  "borderShown": "パネル枠線 表示",
163
216
  "borderHidden": "パネル枠線 非表示",
217
+ "settings.title": "SubAgent Magazine — 設定",
218
+ "settings.section.general": "一般",
219
+ "settings.lang": "言語",
220
+ "settings.max": "最大表示数",
221
+ "settings.order": "並び順",
222
+ "settings.scroll": "スクロール",
223
+ "settings.ttl": "保持期間",
224
+ "settings.border": "パネル枠",
225
+ "settings.showEntryCost": "サブエージェント毎の費用",
226
+ "settings.showEntryTime": "一覧に所要時間を表示",
227
+ "settings.showEntryTokens": "一覧にトークン数を表示",
228
+ "settings.showOrigin": "一覧に由来マークを表示",
229
+ "settings.timeFormat": "時間形式",
230
+ "settings.dbSync": "ローカルDB補完",
231
+ "settings.on": "オン",
232
+ "settings.off": "オフ",
233
+ "settings.saved": "保存しました {item}:{value}",
234
+ "settings.invalid": "無効な値です",
235
+ "settings.close": "閉じる",
164
236
  };
165
237
  const KO_T = {
166
238
  "panel.title": "서브 에이전트",
167
239
  "status.none": "서브 에이전트 없음",
168
240
  "agent.label": "에이전트",
241
+ "origin.label": "출처",
242
+ "origin.tool": "하위 에이전트 호출",
243
+ "origin.sub": "생성된 세션",
244
+ "origin.background": "백그라운드",
169
245
  "status.label": "상태",
170
246
  "time.label": "시간",
171
247
  "tokens.label": "컨텍스트",
@@ -203,6 +279,7 @@ const KO_T = {
203
279
  "clear.prompt": "이 세션의 모든 서브 에이전트 기록을 삭제하시겠습니까? 이 작업은 되돌릴 수 없습니다.",
204
280
  "clear.prompt_running": "실행 중인 서브 에이전트가 {n}개 있습니다. 삭제하면 복구할 수 없습니다. 계속하시겠습니까?",
205
281
  "clear.done": "서브 에이전트 기록 {n}개를 삭제했습니다",
282
+ "clear.failed": "지우기에 실패했습니다",
206
283
  "clear.empty": "이 세션에 서브 에이전트 기록이 없습니다",
207
284
  "cancel.no_session": "하위 세션 ID를 사용할 수 없습니다",
208
285
  "cancel.not_child": "대상이 하위 세션이 아닙니다",
@@ -214,6 +291,25 @@ const KO_T = {
214
291
  "cancel.failed": "취소에 실패했습니다",
215
292
  "borderShown": "패널 테두리 표시",
216
293
  "borderHidden": "패널 테두리 숨김",
294
+ "settings.title": "SubAgent Magazine — 설정",
295
+ "settings.section.general": "일반",
296
+ "settings.lang": "언어",
297
+ "settings.max": "최대 항목 수",
298
+ "settings.order": "정렬 순서",
299
+ "settings.scroll": "스크롤 모드",
300
+ "settings.ttl": "보존 기간",
301
+ "settings.border": "패널 테두리",
302
+ "settings.showEntryCost": "하위 에이전트별 비용",
303
+ "settings.showEntryTime": "목록에 소요 시간 표시",
304
+ "settings.showEntryTokens": "목록에 토큰 수 표시",
305
+ "settings.showOrigin": "목록에 출처 아이콘 표시",
306
+ "settings.timeFormat": "시간 형식",
307
+ "settings.dbSync": "로컬 DB 보완",
308
+ "settings.on": "켬",
309
+ "settings.off": "끔",
310
+ "settings.saved": "저장됨 {item}: {value}",
311
+ "settings.invalid": "잘못된 값",
312
+ "settings.close": "닫기",
217
313
  };
218
314
  export const LANGS = { zh: ZH_T, en: EN_T, ja: JA_T, ko: KO_T };
219
315
  /** 语言元数据:/subagent-lang 选项与自动检测共用。 */
package/dist/index.js CHANGED
@@ -3,6 +3,7 @@ import { createSignal } from "solid-js";
3
3
  import { PLUGIN_VERSION } from "./_version";
4
4
  import { LANG_META, createT, detectLang } from "./i18n";
5
5
  import { KV_PREFIX } from "./core/kv";
6
+ import { TIME_FORMATS, TIME_FORMAT_SAMPLES } from "./core/format";
6
7
  import { SubAgentPanel } from "./panel/SubAgentPanel";
7
8
  import { globalEntryCache, setClearTick } from "./panel/store";
8
9
  // Plugin entry
@@ -13,7 +14,7 @@ function createSidebarSlot(api, panelApi, sig) {
13
14
  slots: {
14
15
  sidebar_content(ctx, input) {
15
16
  sig.sessionId = input.session_id;
16
- return (_jsx(SubAgentPanel, { api: panelApi, theme: ctx.theme.current, lang: sig.lang, maxEntries: sig.maxEntries, sortOrder: sig.sortOrder, scrollMode: sig.scrollMode, borderVisible: sig.borderVisible, sessionId: input.session_id }));
17
+ return (_jsx(SubAgentPanel, { api: panelApi, theme: ctx.theme.current, lang: sig.lang, maxEntries: sig.maxEntries, sortOrder: sig.sortOrder, scrollMode: sig.scrollMode, borderVisible: sig.borderVisible, showEntryCost: sig.showEntryCost, showEntryTime: sig.showEntryTime, showEntryTokens: sig.showEntryTokens, timeFormat: sig.timeFormat, showOrigin: sig.showOrigin, sessionId: input.session_id }));
17
18
  },
18
19
  },
19
20
  };
@@ -27,7 +28,14 @@ const tui = async (api) => {
27
28
  const [sortOrder, setSortOrder] = createSignal(String(api.kv.get(`${KV_PREFIX}.order`, "desc")) === "asc" ? "asc" : "desc");
28
29
  const [scrollMode, setScrollMode] = createSignal(String(api.kv.get(`${KV_PREFIX}.scroll_mode`, "wheel")) === "click" ? "click" : "wheel");
29
30
  const [borderVisible, setBorderVisible] = createSignal(api.kv.get(`${KV_PREFIX}.border`, false) === true);
30
- const signals = { lang, setLang, maxEntries, setMaxEntries, sortOrder, setSortOrder, scrollMode, setScrollMode, borderVisible, setBorderVisible, sessionId: "" };
31
+ const [showEntryCost, setShowEntryCost] = createSignal(api.kv.get(`${KV_PREFIX}.show_entry_cost`, false) === true);
32
+ const [showEntryTime, setShowEntryTime] = createSignal(api.kv.get(`${KV_PREFIX}.show_entry_time`, true) !== false);
33
+ const [showEntryTokens, setShowEntryTokens] = createSignal(api.kv.get(`${KV_PREFIX}.show_entry_tokens`, true) !== false);
34
+ const storedTimeFormat = String(api.kv.get(`${KV_PREFIX}.time_format`, "short"));
35
+ const [timeFormat, setTimeFormat] = createSignal(TIME_FORMATS.includes(storedTimeFormat) ? storedTimeFormat : "short");
36
+ const [showOrigin, setShowOrigin] = createSignal(api.kv.get(`${KV_PREFIX}.show_origin`, false) === true);
37
+ const [dbSync, setDbSync] = createSignal(api.kv.get(`${KV_PREFIX}.db_sync`, true) !== false);
38
+ const signals = { lang, setLang, maxEntries, setMaxEntries, sortOrder, setSortOrder, scrollMode, setScrollMode, borderVisible, setBorderVisible, showEntryCost, setShowEntryCost, showEntryTime, setShowEntryTime, showEntryTokens, setShowEntryTokens, timeFormat, setTimeFormat, showOrigin, setShowOrigin, dbSync, setDbSync, sessionId: "" };
31
39
  // ── V1 PanelApi adapter: wraps the V1 host API into the shared panel contract ──
32
40
  const v1Api = {
33
41
  kv: api.kv,
@@ -431,6 +439,86 @@ const tui = async (api) => {
431
439
  dialog?.clear();
432
440
  },
433
441
  },
442
+ {
443
+ title: "SubAgent Magazine: Entry Cost",
444
+ value: "subagent-cost",
445
+ description: "Show or hide the per-sub-agent cost in the sidebar",
446
+ slash: { name: "subagent-cost" },
447
+ onSelect: (dialog) => {
448
+ const t = createT(() => signals.lang());
449
+ const cur = Boolean(api.kv.get(`${KV_PREFIX}.show_entry_cost`, false));
450
+ api.kv.set(`${KV_PREFIX}.show_entry_cost`, !cur);
451
+ signals.setShowEntryCost(!cur);
452
+ api.ui.toast({
453
+ message: `${t("settings.showEntryCost")}: ${!cur ? t("settings.on") : t("settings.off")}`,
454
+ });
455
+ dialog?.clear();
456
+ },
457
+ },
458
+ {
459
+ title: "SubAgent Magazine: Entry Time",
460
+ value: "subagent-time",
461
+ description: "Show or hide the elapsed time in the sidebar list",
462
+ slash: { name: "subagent-time" },
463
+ onSelect: (dialog) => {
464
+ const t = createT(() => signals.lang());
465
+ const cur = Boolean(api.kv.get(`${KV_PREFIX}.show_entry_time`, true));
466
+ api.kv.set(`${KV_PREFIX}.show_entry_time`, !cur);
467
+ signals.setShowEntryTime(!cur);
468
+ api.ui.toast({
469
+ message: `${t("settings.showEntryTime")}: ${!cur ? t("settings.on") : t("settings.off")}`,
470
+ });
471
+ dialog?.clear();
472
+ },
473
+ },
474
+ {
475
+ title: "SubAgent Magazine: Entry Tokens",
476
+ value: "subagent-tokens",
477
+ description: "Show or hide the token count in the sidebar list",
478
+ slash: { name: "subagent-tokens" },
479
+ onSelect: (dialog) => {
480
+ const t = createT(() => signals.lang());
481
+ const cur = Boolean(api.kv.get(`${KV_PREFIX}.show_entry_tokens`, true));
482
+ api.kv.set(`${KV_PREFIX}.show_entry_tokens`, !cur);
483
+ signals.setShowEntryTokens(!cur);
484
+ api.ui.toast({
485
+ message: `${t("settings.showEntryTokens")}: ${!cur ? t("settings.on") : t("settings.off")}`,
486
+ });
487
+ dialog?.clear();
488
+ },
489
+ },
490
+ {
491
+ title: "SubAgent Magazine: Time Format",
492
+ value: "subagent-time-format",
493
+ description: "Set how elapsed time is displayed",
494
+ slash: { name: "subagent-time-format" },
495
+ onSelect: (dialog) => {
496
+ dialog?.replace(() => (_jsx(api.ui.DialogSelect, { title: "Time Format / \u65F6\u95F4\u683C\u5F0F", options: TIME_FORMATS.map((f) => ({ title: `${f} — ${TIME_FORMAT_SAMPLES[f]}`, value: f })), onSelect: (opt) => {
497
+ const f = opt.value;
498
+ setTimeFormat(f);
499
+ api.kv.set(`${KV_PREFIX}.time_format`, f);
500
+ const t = createT(() => lang());
501
+ api.ui.toast({ message: `${t("settings.timeFormat")}: ${TIME_FORMAT_SAMPLES[f]}` });
502
+ dialog?.clear();
503
+ } })));
504
+ },
505
+ },
506
+ {
507
+ title: "SubAgent Magazine: Origin Indicators",
508
+ value: "subagent-origin",
509
+ description: "Show or hide origin markers (⇢/↳) in the sidebar list",
510
+ slash: { name: "subagent-origin" },
511
+ onSelect: (dialog) => {
512
+ const t = createT(() => signals.lang());
513
+ const cur = Boolean(api.kv.get(`${KV_PREFIX}.show_origin`, false));
514
+ api.kv.set(`${KV_PREFIX}.show_origin`, !cur);
515
+ signals.setShowOrigin(!cur);
516
+ api.ui.toast({
517
+ message: `${t("settings.showOrigin")}: ${!cur ? t("settings.on") : t("settings.off")}`,
518
+ });
519
+ dialog?.clear();
520
+ },
521
+ },
434
522
  ]);
435
523
  };
436
524
  const mod = {
@@ -1,6 +1,6 @@
1
1
  /** @jsxImportSource @opentui/solid */
2
2
  import type { JSX } from "@opentui/solid";
3
- import type { Lang, SortOrder, ScrollMode } from "../core/types";
3
+ import type { Lang, SortOrder, ScrollMode, TimeFormat } from "../core/types";
4
4
  import type { PanelApi } from "./panel-api";
5
5
  export declare function SubAgentPanel(props: {
6
6
  api: PanelApi;
@@ -10,5 +10,10 @@ export declare function SubAgentPanel(props: {
10
10
  sortOrder: () => SortOrder;
11
11
  scrollMode: () => ScrollMode;
12
12
  borderVisible: () => boolean;
13
+ showEntryCost: () => boolean;
14
+ showEntryTime: () => boolean;
15
+ showEntryTokens: () => boolean;
16
+ timeFormat: () => TimeFormat;
17
+ showOrigin: () => boolean;
13
18
  sessionId: string;
14
19
  }): JSX.Element;