opencode-visual-cache 1.2.16 → 1.4.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 +4 -1
- package/README_EN.md +4 -1
- package/dist/_version.d.ts +1 -1
- package/dist/_version.js +1 -1
- package/dist/index.js +343 -9
- package/dist/tui.js +735 -223
- package/package.json +1 -1
- package/src/_version.ts +1 -1
- package/src/index.tsx +417 -3
package/README.md
CHANGED
|
@@ -50,7 +50,8 @@
|
|
|
50
50
|
- **折叠记忆**:折叠状态持久化,重启后保持
|
|
51
51
|
- **语言适配**:自动检测系统语言,支持 `/cache-lang` 运行时切换中/英文,偏好持久化
|
|
52
52
|
- **多币种**:通过 `/cache-currency` 切换货币,费用和节省同步换算
|
|
53
|
-
- **斜杠命令**:`/cache-rate` `/cache-section` `/cache-config` `/cache-lang` 动态配置面板
|
|
53
|
+
- **斜杠命令**:`/cache-session` `/cache-session-back` `/cache-rate` `/cache-section` `/cache-config` `/cache-lang` 动态配置面板
|
|
54
|
+
- **子代理缓存查看**:`/cache-session` 自动扫描并列出子代理,选择一个即可切换面板显示其缓存统计,支持 `/cache-session-back` 返回主会话
|
|
54
55
|
- **已加载技能**:检测 session 中 LLM 调用 `skill` tool 的记录,展示已加载技能名及估算 Token 占用
|
|
55
56
|
|
|
56
57
|
---
|
|
@@ -100,6 +101,8 @@ npm install -g opencode-visual-cache@latest
|
|
|
100
101
|
|
|
101
102
|
| 命令 | 功能 | 使用方式 |
|
|
102
103
|
|------|------|---------|
|
|
104
|
+
| `/cache-session` | 查看子代理缓存统计 | 自动列出子代理供选择,或手动粘贴 Session ID 切换面板数据源 |
|
|
105
|
+
| `/cache-session-back` | 返回主会话统计 | 从子代理缓存视图切回主会话 |
|
|
103
106
|
| `/cache-currency` | 切换货币单位 | 从列表选择货币(USD / CNY / EUR / JPY / GBP / KRW),自动填入默认汇率 |
|
|
104
107
|
| `/cache-rate` | 调整汇率乘数 | 输入自定义汇率(如 `7.2`),用于费用换算 |
|
|
105
108
|
| `/cache-section` | 开关区块与边框 | 独立控制 Token 明细 / 模型与定价 / 估算 Token 分布 / 已加载技能 / 面板边框的显隐 |
|
package/README_EN.md
CHANGED
|
@@ -50,7 +50,8 @@ Interested in sub-agent monitoring? Check out [opencode-subagent-magazine](https
|
|
|
50
50
|
- **Persistent State**: Fold preferences and config remembered across restarts via api.kv
|
|
51
51
|
- **Language**: Auto-detects system locale, with `/cache-lang` for runtime switching between Chinese and English — preference persisted across restarts
|
|
52
52
|
- **Multi-currency**: Switch via `/cache-currency` — costs, savings, and per-million rates convert in real time
|
|
53
|
-
- **Slash Commands**: `/cache-rate` `/cache-section` `/cache-config` `/cache-lang` for live panel configuration
|
|
53
|
+
- **Slash Commands**: `/cache-session` `/cache-session-back` `/cache-rate` `/cache-section` `/cache-config` `/cache-lang` for live panel configuration
|
|
54
|
+
- **Sub-Agent Cache View**: `/cache-session` auto-scans and lists sub-agents; select one to switch the panel stats. Use `/cache-session-back` to return to the main session
|
|
54
55
|
- **Loaded Skills**: Detects `skill` tool calls in the session and displays loaded skill names with estimated token footprint
|
|
55
56
|
|
|
56
57
|
---
|
|
@@ -100,6 +101,8 @@ The plugin supports slash commands and command palette (`Ctrl + P`) for runtime
|
|
|
100
101
|
|
|
101
102
|
| Command | Function | How to use |
|
|
102
103
|
|---------|----------|------------|
|
|
104
|
+
| `/cache-session` | View sub-agent cache stats | Lists sub-agents automatically, or paste a Session ID to switch the panel data source |
|
|
105
|
+
| `/cache-session-back` | Return to main session | Switch back to main session from sub-agent cache view |
|
|
103
106
|
| `/cache-currency` | Switch currency | Pick from a list (USD / CNY / EUR / JPY / GBP / KRW); default exchange rate auto-filled |
|
|
104
107
|
| `/cache-rate` | Adjust exchange rate | Enter a custom rate (e.g. `7.2` for CNY) |
|
|
105
108
|
| `/cache-section` | Toggle sections & border | Independently show/hide Detail, Model & Pricing, Token Distribution, Loaded Skills, or the panel border |
|
package/dist/_version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const PLUGIN_VERSION = "1.
|
|
1
|
+
export declare const PLUGIN_VERSION = "1.4.0";
|
package/dist/_version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// auto-generated
|
|
2
|
-
export const PLUGIN_VERSION = "1.
|
|
2
|
+
export const PLUGIN_VERSION = "1.4.0";
|
package/dist/index.js
CHANGED
|
@@ -94,6 +94,14 @@ const ZH_T = {
|
|
|
94
94
|
secDetail: "明细",
|
|
95
95
|
secModel: "模型",
|
|
96
96
|
secSkills: "已加载技能",
|
|
97
|
+
balTotal: "总余额:",
|
|
98
|
+
balNoKey: "未配置 API Key",
|
|
99
|
+
balLoading: "查询中...",
|
|
100
|
+
balError: "查询失败",
|
|
101
|
+
balErr401: "API Key 无效",
|
|
102
|
+
balErr403: "余额查询被拒绝",
|
|
103
|
+
balErrEmpty: "未获取到余额数据",
|
|
104
|
+
balErrTimeout: "查询超时",
|
|
97
105
|
};
|
|
98
106
|
const EN_T = {
|
|
99
107
|
title: "Token Cache",
|
|
@@ -125,6 +133,14 @@ const EN_T = {
|
|
|
125
133
|
secDetail: "Detail",
|
|
126
134
|
secModel: "Model",
|
|
127
135
|
secSkills: "Loaded Skills",
|
|
136
|
+
balTotal: "Total:",
|
|
137
|
+
balNoKey: "No API Key set",
|
|
138
|
+
balLoading: "Fetching...",
|
|
139
|
+
balError: "Fetch failed",
|
|
140
|
+
balErr401: "Invalid API Key",
|
|
141
|
+
balErr403: "Balance request rejected",
|
|
142
|
+
balErrEmpty: "No balance data",
|
|
143
|
+
balErrTimeout: "Request timed out",
|
|
128
144
|
};
|
|
129
145
|
// ── color helpers ────────────────────────────────────────────────
|
|
130
146
|
/** Extract { r, g, b } (0–255) from a hex string or RGBA-like object. */
|
|
@@ -202,6 +218,16 @@ function desaturateTo(raw, maxSat, fallback) {
|
|
|
202
218
|
const nb = Math.round(c.b + (luma - c.b) * hi);
|
|
203
219
|
return "#" + [nr, ng, nb].map((v) => Math.max(0, Math.min(255, v)).toString(16).padStart(2, "0")).join("");
|
|
204
220
|
}
|
|
221
|
+
/** Darken a hex colour by multiplying each channel by `factor` (0–1). */
|
|
222
|
+
function dimColor(hex, factor = 0.5) {
|
|
223
|
+
const c = rgb(hex);
|
|
224
|
+
if (!c)
|
|
225
|
+
return hex;
|
|
226
|
+
const r = Math.round(c.r * factor);
|
|
227
|
+
const g = Math.round(c.g * factor);
|
|
228
|
+
const b = Math.round(c.b * factor);
|
|
229
|
+
return "#" + [r, g, b].map((v) => Math.max(0, Math.min(255, v)).toString(16).padStart(2, "0")).join("");
|
|
230
|
+
}
|
|
205
231
|
// Morandi fallbacks — used when a theme colour cannot be resolved
|
|
206
232
|
const FALLBACK = {
|
|
207
233
|
primary: "#8B9DAF",
|
|
@@ -289,6 +315,44 @@ function estimateTokens(text) {
|
|
|
289
315
|
const asciiPerToken = jsonLike ? 3.5 : codeLike ? 3.5 : 4;
|
|
290
316
|
return Math.max(1, Math.ceil(ascii / asciiPerToken + cjk / 1.0));
|
|
291
317
|
}
|
|
318
|
+
const BALANCE_POLL_MS = 5 * 60 * 1000; // 5 minutes
|
|
319
|
+
async function fetchDeepSeekBalance(apiKey, signal) {
|
|
320
|
+
const res = await fetch("https://api.deepseek.com/user/balance", {
|
|
321
|
+
headers: { Authorization: `Bearer ${apiKey}`, Accept: "application/json" },
|
|
322
|
+
signal,
|
|
323
|
+
});
|
|
324
|
+
if (!res.ok) {
|
|
325
|
+
if (res.status === 401)
|
|
326
|
+
throw new Error("401");
|
|
327
|
+
if (res.status === 402 || res.status === 403)
|
|
328
|
+
throw new Error("403");
|
|
329
|
+
throw new Error(String(res.status));
|
|
330
|
+
}
|
|
331
|
+
const json = await res.json();
|
|
332
|
+
const infos = json.balance_infos ?? [];
|
|
333
|
+
if (infos.length === 0)
|
|
334
|
+
throw new Error("EMPTY");
|
|
335
|
+
return infos.map((info) => ({
|
|
336
|
+
currency: info.currency ?? "CNY",
|
|
337
|
+
total: info.total_balance ?? "0",
|
|
338
|
+
}));
|
|
339
|
+
}
|
|
340
|
+
/**
|
|
341
|
+
* 将余额从来源币种换算为目标币种。
|
|
342
|
+
* DEFAULT_RATES 以 USD=1 为基准:先折算为 USD,再换算到目标币种。
|
|
343
|
+
*/
|
|
344
|
+
function convertBalance(target, targetRate, amount, from) {
|
|
345
|
+
if (from === target)
|
|
346
|
+
return amount;
|
|
347
|
+
const fromRate = DEFAULT_RATES[from] ?? 1;
|
|
348
|
+
const usd = from === "USD" ? amount : amount / fromRate;
|
|
349
|
+
return target === "USD" ? usd : usd * targetRate;
|
|
350
|
+
}
|
|
351
|
+
/** 货币符号:优先取 /cache-currency 内置映射,未知币种回退为代码。 */
|
|
352
|
+
function balanceSymbol(currency) {
|
|
353
|
+
const sym = CURRENCIES[currency];
|
|
354
|
+
return sym ?? currency + " ";
|
|
355
|
+
}
|
|
292
356
|
const CURRENCIES = {
|
|
293
357
|
USD: "$", CNY: "¥", EUR: "€", JPY: "JP¥", GBP: "£", KRW: "₩",
|
|
294
358
|
};
|
|
@@ -315,7 +379,7 @@ function TokenCachePanel(props) {
|
|
|
315
379
|
const [skillsOpen, setSkillsOpen] = createSignal(true);
|
|
316
380
|
let boxEl;
|
|
317
381
|
// ── shared signals (de-structured so internal code is unchanged) ──
|
|
318
|
-
const { currencySymbol, setCurrencySymbol, exchangeRate, setExchangeRate, langZH, setLangZH, sectionDetail, setSectionDetail, sectionModel, setSectionModel, sectionDist, setSectionDist, sectionSkills, setSectionSkills, borderVisible, setBorderVisible, } = props.signals;
|
|
382
|
+
const { currencySymbol, setCurrencySymbol, exchangeRate, setExchangeRate, langZH, setLangZH, sectionDetail, setSectionDetail, sectionModel, setSectionModel, sectionDist, setSectionDist, sectionSkills, setSectionSkills, sectionBalance, setSectionBalance, balanceRefresh, balanceCurrency, setBalanceCurrency, borderVisible, setBorderVisible, } = props.signals;
|
|
319
383
|
// ── reactive translation (follows langZH signal) ──
|
|
320
384
|
const t = createMemo(() => langZH() ? ZH_T : EN_T);
|
|
321
385
|
// ── scan session messages reactively ──
|
|
@@ -342,8 +406,66 @@ function TokenCachePanel(props) {
|
|
|
342
406
|
hasSkills: false,
|
|
343
407
|
});
|
|
344
408
|
const [refreshTick, setRefreshTick] = createSignal(0);
|
|
409
|
+
// ── balance state + polling ──────────────────────────────────
|
|
410
|
+
const [balanceState, setBalanceState] = createSignal({
|
|
411
|
+
status: "idle", data: null, lastFetch: 0,
|
|
412
|
+
});
|
|
413
|
+
// 请求序号:防止定时轮询与手动刷新并发时,慢的旧请求覆盖新结果
|
|
414
|
+
let balanceSeq = 0;
|
|
415
|
+
const pollBalance = async () => {
|
|
416
|
+
const key = props.api.kv.get(`${KV_PREFIX}.ds_key`, "");
|
|
417
|
+
if (!key) {
|
|
418
|
+
setBalanceState({ status: "idle", data: null, lastFetch: 0, error: undefined, key: undefined });
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
const now = Date.now();
|
|
422
|
+
const prev = balanceState();
|
|
423
|
+
// key 已更换(重新输入)→ 强制重新查询,绕过缓存
|
|
424
|
+
if (prev.status === "ok" && prev.key === key && now - prev.lastFetch < BALANCE_POLL_MS)
|
|
425
|
+
return; // cache still fresh
|
|
426
|
+
const seq = ++balanceSeq;
|
|
427
|
+
setBalanceState({ ...prev, status: "loading", error: undefined, key });
|
|
428
|
+
const controller = new AbortController();
|
|
429
|
+
let timedOut = false;
|
|
430
|
+
const timer = setTimeout(() => { timedOut = true; controller.abort(); }, 10_000);
|
|
431
|
+
try {
|
|
432
|
+
const data = await fetchDeepSeekBalance(key, controller.signal);
|
|
433
|
+
clearTimeout(timer);
|
|
434
|
+
if (seq !== balanceSeq)
|
|
435
|
+
return; // 已被更新的请求取代,丢弃过期结果
|
|
436
|
+
setBalanceState({ status: "ok", data, lastFetch: Date.now(), error: undefined, key });
|
|
437
|
+
}
|
|
438
|
+
catch (err) {
|
|
439
|
+
clearTimeout(timer);
|
|
440
|
+
if (seq !== balanceSeq)
|
|
441
|
+
return;
|
|
442
|
+
const code = timedOut ? "TIMEOUT" : (err instanceof Error ? err.message : "");
|
|
443
|
+
// 失败时清空旧数据,避免显示过期余额
|
|
444
|
+
setBalanceState({ status: "error", data: null, lastFetch: 0, error: code, key });
|
|
445
|
+
}
|
|
446
|
+
};
|
|
447
|
+
// Re-fetch when the API key is (re)configured via /cache-balance-key.
|
|
448
|
+
// 注意:pollBalance 内部读写 balanceState 信号,若不做 untrack 包裹,
|
|
449
|
+
// effect 会追踪 balanceState 的变化并与 pollBalance 的 setBalanceState
|
|
450
|
+
// 形成无限循环(每次重跑都发起新的 fetch 请求)。
|
|
451
|
+
createEffect(() => {
|
|
452
|
+
void balanceRefresh();
|
|
453
|
+
untrack(() => { void pollBalance(); });
|
|
454
|
+
});
|
|
455
|
+
// ── auto-clear override when the user navigates to a different main session ──
|
|
456
|
+
let lastMainSid = props.sessionId;
|
|
345
457
|
createEffect(() => {
|
|
346
458
|
const sid = props.sessionId;
|
|
459
|
+
if (sid !== lastMainSid) {
|
|
460
|
+
lastMainSid = sid;
|
|
461
|
+
if (props.signals.overrideSessionId()) {
|
|
462
|
+
props.signals.setOverrideSessionId(undefined);
|
|
463
|
+
props.api.kv.set(`${KV_PREFIX}.session`, "");
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
});
|
|
467
|
+
createEffect(() => {
|
|
468
|
+
const sid = props.signals.overrideSessionId() ?? props.sessionId;
|
|
347
469
|
void refreshTick();
|
|
348
470
|
void partVersion();
|
|
349
471
|
// 自然追踪 messages 和 provider(SDK 数据就绪时自动重新执行)
|
|
@@ -580,10 +702,14 @@ function TokenCachePanel(props) {
|
|
|
580
702
|
setCurrencySymbol(sym);
|
|
581
703
|
if (typeof rate === "number" && rate > 0)
|
|
582
704
|
setExchangeRate(rate);
|
|
705
|
+
const balCur = props.api.kv.get(`${KV_PREFIX}.balance_currency`);
|
|
706
|
+
if (typeof balCur === "string")
|
|
707
|
+
setBalanceCurrency(balCur);
|
|
583
708
|
setSectionDetail(Boolean(props.api.kv.get(`${KV_PREFIX}.section.detail`, true)));
|
|
584
709
|
setSectionModel(Boolean(props.api.kv.get(`${KV_PREFIX}.section.model`, true)));
|
|
585
710
|
setSectionDist(Boolean(props.api.kv.get(`${KV_PREFIX}.section.dist`, true)));
|
|
586
711
|
setSectionSkills(Boolean(props.api.kv.get(`${KV_PREFIX}.section.skills`, true)));
|
|
712
|
+
setSectionBalance(Boolean(props.api.kv.get(`${KV_PREFIX}.section.balance`, true)));
|
|
587
713
|
const bv = props.api.kv.get(`${KV_PREFIX}.border`, true);
|
|
588
714
|
setBorderVisible(bv !== false);
|
|
589
715
|
// Restore language preference
|
|
@@ -641,7 +767,8 @@ function TokenCachePanel(props) {
|
|
|
641
767
|
const unsubMsg = props.api.event.on("message.updated", () => { bumpPartVersion(); setRefreshTick(v => v + 1); });
|
|
642
768
|
const unsubSession = props.api.event.on("session.updated", () => { setRefreshTick(v => v + 1); });
|
|
643
769
|
setRefreshTick(v => v + 1);
|
|
644
|
-
|
|
770
|
+
const balanceTimer = setInterval(pollBalance, BALANCE_POLL_MS);
|
|
771
|
+
onCleanup(() => { clearTimeout(partTimer); clearInterval(balanceTimer); unsubPart(); unsubMsg(); unsubSession(); });
|
|
645
772
|
});
|
|
646
773
|
// ── colours ──
|
|
647
774
|
// Pull from the current theme, auto-desaturate if too punchy,
|
|
@@ -701,21 +828,63 @@ function TokenCachePanel(props) {
|
|
|
701
828
|
// boxEl.width may be undefined before the first measurement — guard with 0
|
|
702
829
|
const w = boxEl ? Math.max(MIN_PANEL_WIDTH, boxEl.width ?? 0) : DEFAULT_PANEL_WIDTH;
|
|
703
830
|
setPanelWidth((prev) => (prev === w ? prev : w));
|
|
704
|
-
}, children: [_jsxs("text", { onMouseUp: () => setOpen((o) => { const n = !o; persistFold("open", n); return n; }), children: [_jsx("span", { style: { fg: pal().muted }, children: open() ? "\u25bc " : "\u25b6 " }), _jsxs("span", { style: { fg: pal().primary }, children: [_jsx("b", { children: t().title }), _jsx(Show, { when: open(), children: _jsxs("span", { style: { fg: pal().muted }, children: ["
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
831
|
+
}, children: [_jsxs("text", { onMouseUp: () => setOpen((o) => { const n = !o; persistFold("open", n); return n; }), children: [_jsx("span", { style: { fg: pal().muted }, children: open() ? "\u25bc " : "\u25b6 " }), _jsxs("span", { style: { fg: pal().primary }, children: [_jsx("b", { children: t().title }), _jsx(Show, { when: open(), children: _jsxs("span", { style: { fg: dimColor(pal().muted, 0.75) }, children: [" v", PLUGIN_VERSION] }) })] }), _jsxs(Show, { when: !open() && data().hasData, children: [_jsxs(Show, { when: data().hasTrendData, children: [_jsx("span", { children: " ".repeat(Math.max(1, panelWidth() - gutter() - HEADER_PREFIX - visualWidth(t().title) - visualWidth(pct() + " " + t().hitFolded + " " + trendLabel(data().trend)))) }), _jsxs("span", { style: { fg: hitColor() }, children: [pct(), " ", t().hitFolded] }), _jsxs("span", { style: { fg: data().trend !== 0 ? (data().trend > 0 ? pal().success : pal().error) : pal().text }, children: [" ", trendLabel(data().trend)] })] }), _jsxs(Show, { when: !data().hasTrendData, children: [_jsx("span", { children: " ".repeat(Math.max(1, panelWidth() - gutter() - HEADER_PREFIX - visualWidth(t().title) - visualWidth(pct() + " " + t().hitFolded))) }), _jsxs("span", { style: { fg: hitColor() }, children: [pct(), " ", t().hitFolded] })] })] })] }), _jsxs(Show, { when: open(), children: [_jsx(Show, { when: props.signals.overrideSessionId(), children: (() => {
|
|
832
|
+
const prefix = " \u21b3 " + (langZH() ? "\u5B50\u4EE3\u7406: " : "Sub: ");
|
|
833
|
+
const maxSidW = Math.max(6, panelWidth() - visualWidth(prefix));
|
|
834
|
+
return (_jsxs("text", { children: [_jsx("span", { style: { fg: pal().muted }, children: prefix }), _jsx("span", { style: { fg: pal().text }, children: truncateVisual(props.signals.overrideSessionId(), maxSidW) })] }));
|
|
835
|
+
})() }), _jsxs(Show, { when: data().hasData, fallback: _jsxs(_Fragment, { children: [_jsx("text", { fg: pal().muted, children: sep() }), _jsxs("text", { children: [_jsx("span", { style: { fg: pal().muted }, children: "> " }), _jsx("span", { style: { fg: pal().muted }, children: t().noData })] })] }), children: [_jsx("text", { fg: pal().muted, children: sep() }), _jsxs("text", { children: [_jsxs("span", { style: { fg: pal().text }, children: [t().hit, " "] }), _jsxs("span", { style: { fg: hitColor() }, children: ["[", bar(), "] "] }), _jsx("span", { style: { fg: pal().text }, children: pct() }), _jsx(Show, { when: data().hasTrendData, children: _jsxs("span", { style: { fg: data().trend !== 0 ? (data().trend > 0 ? pal().success : pal().error) : pal().text }, children: [" ", trendLabel(data().trend)] }) })] }), _jsx("text", { fg: pal().muted, children: justify(t().totalHit, (Math.floor(data().sessionHitRate * 10) / 10).toFixed(1) + "%") }), _jsxs(Show, { when: sectionDetail(), children: [_jsxs("text", { onMouseUp: () => setDetailOpen((o) => { const n = !o; persistFold("detail", n); return n; }), children: [_jsx("span", { style: { fg: pal().muted }, children: detailOpen() ? "\u25bc " : "\u25b6 " }), _jsx("span", { style: { fg: pal().primary }, children: _jsx("b", { children: t().secDetail }) }), _jsx("span", { style: { fg: pal().muted }, children: sep().slice(visualWidth((detailOpen() ? "\u25bc " : "\u25b6 ") + t().secDetail)) })] }), _jsxs(Show, { when: detailOpen(), children: [_jsx(Show, { when: data().read > 0, children: _jsx("text", { fg: pal().muted, children: justify(t().read, fmt(data().read), t().tok) }) }), _jsx(Show, { when: data().write > 0, children: _jsx("text", { fg: pal().muted, children: justify(t().write, fmt(data().write), t().tok) }) }), _jsx("text", { fg: pal().muted, children: justify(t().miss, fmt(data().freshInput), t().tok) }), _jsx("text", { fg: pal().muted, children: justify(t().out, fmt(data().output), t().tok) }), _jsx(Show, { when: data().saved > 0, children: _jsxs("text", { children: [_jsx("span", { style: { fg: pal().muted }, children: t().saved }), _jsx("span", { children: " ".repeat(Math.max(1, panelWidth() - gutter() - visualWidth(t().saved) - visualWidth("~" + fmtCost(data().saved, currencySymbol(), exchangeRate())))) }), _jsxs("span", { style: { fg: pal().success }, children: ["~", fmtCost(data().saved, currencySymbol(), exchangeRate())] })] }) })] })] }), _jsxs(Show, { when: sectionModel(), children: [_jsxs("text", { onMouseUp: () => setModelOpen((o) => { const n = !o; persistFold("model", n); return n; }), children: [_jsx("span", { style: { fg: pal().muted }, children: modelOpen() ? "\u25bc " : "\u25b6 " }), _jsx("span", { style: { fg: pal().primary }, children: _jsx("b", { children: t().secModel }) }), _jsx("span", { style: { fg: pal().muted }, children: sep().slice(visualWidth((modelOpen() ? "\u25bc " : "\u25b6 ") + t().secModel)) })] }), _jsxs(Show, { when: modelOpen(), children: [_jsx("text", { fg: pal().text, children: justify(t().cost, fmtCost(data().cost, currencySymbol(), exchangeRate())) }), _jsx(Show, { when: data().providerName, children: _jsx("text", { fg: pal().muted, children: justify(t().provider, data().providerName) }) }), _jsx("text", { fg: pal().muted, children: justify(t().model, data().model) }), _jsxs(Show, { when: data().hasPricing, children: [_jsx("text", { fg: pal().muted, children: justify(t().rate, currencySymbol() + (data().inputRate * exchangeRate()).toFixed(2) + "/M " + t().inputRate) }), _jsx(Show, { when: data().cacheReadRate > 0, children: _jsx("text", { fg: pal().muted, children: justify("", currencySymbol() + (data().cacheReadRate * exchangeRate()).toFixed(2) + "/M " + t().cacheRate) }) }), _jsx(Show, { when: data().cacheWriteRate > 0, children: _jsx("text", { fg: pal().muted, children: justify("", currencySymbol() + (data().cacheWriteRate * exchangeRate()).toFixed(2) + "/M " + t().writeRate) }) })] })] })] }), _jsx(Show, { when: sectionDist(), children: _jsxs(Show, { when: data().hasDistData, children: [_jsxs("text", { onMouseUp: () => setDistOpen((o) => { const n = !o; persistFold("dist", n); return n; }), children: [_jsx("span", { style: { fg: pal().muted }, children: distOpen() ? "\u25bc " : "\u25b6 " }), _jsx("span", { style: { fg: pal().primary }, children: _jsx("b", { children: t().distTitle }) }), _jsx("span", { style: { fg: pal().muted }, children: sep().slice(visualWidth((distOpen() ? "\u25bc " : "\u25b6 ") + t().distTitle)) })] }), _jsxs(Show, { when: distOpen(), children: [_jsx(Show, { when: data().dist.system > 0, children: _jsx("text", { fg: pal().muted, children: justify(t().distSys, fmt(data().dist.system), t().tok) }) }), _jsx(Show, { when: data().dist.user > 0, children: _jsx("text", { fg: pal().muted, children: justify(t().distUser, fmt(data().dist.user), t().tok) }) }), _jsx(Show, { when: data().dist.agent > 0, children: _jsx("text", { fg: pal().muted, children: justify(t().distAgent, fmt(data().dist.agent), t().tok) }) }), _jsx(Show, { when: data().dist.toolCall > 0, children: _jsx("text", { fg: pal().muted, children: justify(t().distTool, fmt(data().dist.toolCall), t().tok) }) }), _jsx(Show, { when: data().dist.toolResult > 0, children: _jsx("text", { fg: pal().muted, children: justify(t().distRes, fmt(data().dist.toolResult), t().tok) }) }), _jsx("text", { fg: pal().text, children: justify(t().distTotal, fmt(data().dist.apiInput), t().tok) })] })] }) }), _jsx(Show, { when: sectionSkills(), children: _jsxs(Show, { when: data().hasSkills, children: [_jsxs("text", { onMouseUp: () => setSkillsOpen((o) => { const n = !o; persistFold("skills", n); return n; }), children: [_jsx("span", { style: { fg: pal().muted }, children: skillsOpen() ? "\u25bc " : "\u25b6 " }), _jsx("span", { style: { fg: pal().primary }, children: _jsx("b", { children: t().secSkills }) }), _jsxs("span", { style: { fg: pal().muted }, children: [" (", data().skills.length, ")"] }), _jsx("span", { style: { fg: pal().muted }, children: sep().slice(visualWidth((skillsOpen() ? "\u25bc " : "\u25b6 ") + t().secSkills + ` (${data().skills.length})`)) })] }), _jsx(Show, { when: skillsOpen(), children: data().skills.map((sk) => {
|
|
836
|
+
const rightW = visualWidth(fmt(sk.tokens)) + UNIT_GAP + visualWidth(t().tok);
|
|
837
|
+
const maxLabel = Math.max(4, panelWidth() - gutter() - rightW - 1);
|
|
838
|
+
const label = truncateVisual(sk.name, maxLabel);
|
|
839
|
+
return (_jsx("text", { fg: pal().muted, children: justify(label, fmt(sk.tokens), t().tok) }));
|
|
840
|
+
}) })] }) }), _jsxs(Show, { when: sectionBalance(), children: [_jsx("text", { fg: pal().muted, children: sep() }), _jsx(Show, { when: balanceState().status === "idle", children: _jsxs("text", { fg: pal().muted, children: [_jsx("span", { style: { fg: pal().muted }, children: "> " }), _jsx("span", { children: t().balNoKey })] }) }), _jsx(Show, { when: balanceState().status === "loading", children: _jsxs("text", { fg: pal().muted, children: [_jsx("span", { style: { fg: pal().muted }, children: "> " }), _jsx("span", { children: t().balLoading })] }) }), _jsx(Show, { when: balanceState().status === "error", children: _jsxs("text", { fg: pal().error, children: [_jsx("span", { style: { fg: pal().muted }, children: "> " }), _jsx("span", { children: (() => {
|
|
841
|
+
const code = balanceState().error;
|
|
842
|
+
if (code === "401")
|
|
843
|
+
return t().balErr401;
|
|
844
|
+
if (code === "403")
|
|
845
|
+
return t().balErr403;
|
|
846
|
+
if (code === "EMPTY")
|
|
847
|
+
return t().balErrEmpty;
|
|
848
|
+
if (code === "TIMEOUT")
|
|
849
|
+
return t().balErrTimeout;
|
|
850
|
+
return t().balError + (code ? ` (${code})` : "");
|
|
851
|
+
})() })] }) }), _jsx(Show, { when: balanceState().status === "ok" && balanceState().data, children: (() => {
|
|
852
|
+
const list = balanceState().data;
|
|
853
|
+
const pref = balanceCurrency();
|
|
854
|
+
// 偏好币种是 DeepSeek 原生返回的(CNY/USD)→ 直接显示
|
|
855
|
+
const native = pref ? list.find(x => x.currency === pref) : undefined;
|
|
856
|
+
if (native) {
|
|
857
|
+
return (_jsx("text", { fg: pal().text, children: justify(t().balTotal, balanceSymbol(native.currency) + native.total) }));
|
|
858
|
+
}
|
|
859
|
+
// 非原生币种(EUR/JPY/GBP/KRW…)→ 取第一条余额按汇率换算
|
|
860
|
+
const base = list[0];
|
|
861
|
+
const baseAmt = parseFloat(base.total);
|
|
862
|
+
const converted = Number.isFinite(baseAmt)
|
|
863
|
+
? convertBalance(pref || base.currency, exchangeRate(), baseAmt, base.currency)
|
|
864
|
+
: baseAmt;
|
|
865
|
+
const shown = pref && base.currency !== pref
|
|
866
|
+
? converted.toLocaleString("en-US", { maximumFractionDigits: 2 })
|
|
867
|
+
: base.total;
|
|
868
|
+
return (_jsx("text", { fg: pal().text, children: justify(t().balTotal, balanceSymbol(pref || base.currency) + shown) }));
|
|
869
|
+
})() })] })] })] })] }));
|
|
710
870
|
}
|
|
711
871
|
// ---------------------------------------------------------------------------
|
|
712
872
|
// Plugin entry
|
|
713
873
|
// ---------------------------------------------------------------------------
|
|
714
874
|
function createSidebarSlot(api, signals) {
|
|
875
|
+
let lastSlotSid = "";
|
|
715
876
|
return {
|
|
716
877
|
order: 55,
|
|
717
878
|
slots: {
|
|
718
879
|
sidebar_content(ctx, input) {
|
|
880
|
+
// ── auto-clear override when the user navigates to a different main session ──
|
|
881
|
+
if (input.session_id !== lastSlotSid) {
|
|
882
|
+
lastSlotSid = input.session_id;
|
|
883
|
+
if (signals.overrideSessionId()) {
|
|
884
|
+
signals.setOverrideSessionId(undefined);
|
|
885
|
+
api.kv.set("cache_panel.session", "");
|
|
886
|
+
}
|
|
887
|
+
}
|
|
719
888
|
return (_jsx(TokenCachePanel, { theme: ctx.theme.current, api: api, sessionId: input.session_id, signals: signals }));
|
|
720
889
|
},
|
|
721
890
|
},
|
|
@@ -729,8 +898,12 @@ const tui = async (api) => {
|
|
|
729
898
|
const [sectionModel, setSectionModel] = createSignal(true);
|
|
730
899
|
const [sectionDist, setSectionDist] = createSignal(true);
|
|
731
900
|
const [sectionSkills, setSectionSkills] = createSignal(true);
|
|
901
|
+
const [sectionBalance, setSectionBalance] = createSignal(true);
|
|
902
|
+
const [balanceRefresh, setBalanceRefresh] = createSignal(0);
|
|
903
|
+
const [balanceCurrency, setBalanceCurrency] = createSignal("");
|
|
732
904
|
const [borderVisible, setBorderVisible] = createSignal(true);
|
|
733
905
|
const [langZH, setLangZH] = createSignal(LANG_ZH);
|
|
906
|
+
const [overrideSessionId, setOverrideSessionId] = createSignal(undefined);
|
|
734
907
|
const signals = {
|
|
735
908
|
currencySymbol, setCurrencySymbol,
|
|
736
909
|
exchangeRate, setExchangeRate,
|
|
@@ -739,7 +912,11 @@ const tui = async (api) => {
|
|
|
739
912
|
sectionModel, setSectionModel,
|
|
740
913
|
sectionDist, setSectionDist,
|
|
741
914
|
sectionSkills, setSectionSkills,
|
|
915
|
+
sectionBalance, setSectionBalance,
|
|
916
|
+
balanceRefresh, setBalanceRefresh,
|
|
917
|
+
balanceCurrency, setBalanceCurrency,
|
|
742
918
|
borderVisible, setBorderVisible,
|
|
919
|
+
overrideSessionId, setOverrideSessionId,
|
|
743
920
|
};
|
|
744
921
|
api.slots.register(createSidebarSlot(api, signals));
|
|
745
922
|
// ── slash commands for runtime config ──
|
|
@@ -759,6 +936,9 @@ const tui = async (api) => {
|
|
|
759
936
|
const defRate = DEFAULT_RATES[opt.value] ?? 1;
|
|
760
937
|
api.kv.set(`${KV_PREFIX}.currency`, sym);
|
|
761
938
|
api.kv.set(`${KV_PREFIX}.rate`, defRate);
|
|
939
|
+
// 同步余额显示币种偏好:CNY/USD 原生直显,其余币种按汇率换算
|
|
940
|
+
api.kv.set(`${KV_PREFIX}.balance_currency`, opt.value);
|
|
941
|
+
signals.setBalanceCurrency(opt.value);
|
|
762
942
|
signals.setCurrencySymbol(sym);
|
|
763
943
|
signals.setExchangeRate(defRate);
|
|
764
944
|
api.ui.toast({ message: `Currency: ${opt.value} (${sym}), rate: ${defRate}` });
|
|
@@ -793,12 +973,14 @@ const tui = async (api) => {
|
|
|
793
973
|
const modelOn = Boolean(api.kv.get(`${KV_PREFIX}.section.model`, true));
|
|
794
974
|
const distOn = Boolean(api.kv.get(`${KV_PREFIX}.section.dist`, true));
|
|
795
975
|
const skillsOn = Boolean(api.kv.get(`${KV_PREFIX}.section.skills`, true));
|
|
976
|
+
const balanceOn = Boolean(api.kv.get(`${KV_PREFIX}.section.balance`, true));
|
|
796
977
|
const borderOn = Boolean(api.kv.get(`${KV_PREFIX}.border`, true));
|
|
797
978
|
dialog?.replace(() => (_jsx(api.ui.DialogSelect, { title: "Toggle Section", options: [
|
|
798
979
|
{ title: `Token Detail [${detailOn ? "ON" : "OFF"}]`, value: "detail" },
|
|
799
980
|
{ title: `Model & Pricing [${modelOn ? "ON" : "OFF"}]`, value: "model" },
|
|
800
981
|
{ title: `Token Dist. [${distOn ? "ON" : "OFF"}]`, value: "dist" },
|
|
801
982
|
{ title: `Loaded Skills [${skillsOn ? "ON" : "OFF"}]`, value: "skills" },
|
|
983
|
+
{ title: `DS Balance [${balanceOn ? "ON" : "OFF"}]`, value: "balance" },
|
|
802
984
|
{ title: `Panel Border [${borderOn ? "ON" : "OFF"}]`, value: "border" },
|
|
803
985
|
], onSelect: (opt) => {
|
|
804
986
|
if (opt.value === "border") {
|
|
@@ -819,6 +1001,8 @@ const tui = async (api) => {
|
|
|
819
1001
|
signals.setSectionDist(!cur);
|
|
820
1002
|
if (opt.value === "skills")
|
|
821
1003
|
signals.setSectionSkills(!cur);
|
|
1004
|
+
if (opt.value === "balance")
|
|
1005
|
+
signals.setSectionBalance(!cur);
|
|
822
1006
|
api.ui.toast({ message: `${opt.value} section ${!cur ? "shown" : "hidden"}` });
|
|
823
1007
|
}
|
|
824
1008
|
dialog?.clear();
|
|
@@ -837,9 +1021,10 @@ const tui = async (api) => {
|
|
|
837
1021
|
const model = Boolean(api.kv.get(`${KV_PREFIX}.section.model`, true));
|
|
838
1022
|
const dist = Boolean(api.kv.get(`${KV_PREFIX}.section.dist`, true));
|
|
839
1023
|
const skills = Boolean(api.kv.get(`${KV_PREFIX}.section.skills`, true));
|
|
1024
|
+
const balance = Boolean(api.kv.get(`${KV_PREFIX}.section.balance`, true));
|
|
840
1025
|
api.ui.toast({
|
|
841
1026
|
title: "Cache Panel Config",
|
|
842
|
-
message: `Currency: ${sym} | Rate: ${rate} | Detail: ${detail ? "ON" : "OFF"} | Model: ${model ? "ON" : "OFF"} | Dist: ${dist ? "ON" : "OFF"} | Skills: ${skills ? "ON" : "OFF"}`,
|
|
1027
|
+
message: `Currency: ${sym} | Rate: ${rate} | Detail: ${detail ? "ON" : "OFF"} | Model: ${model ? "ON" : "OFF"} | Dist: ${dist ? "ON" : "OFF"} | Skills: ${skills ? "ON" : "OFF"} | Balance: ${balance ? "ON" : "OFF"}`,
|
|
843
1028
|
duration: 8000,
|
|
844
1029
|
});
|
|
845
1030
|
dialog?.clear();
|
|
@@ -864,6 +1049,50 @@ const tui = async (api) => {
|
|
|
864
1049
|
} })));
|
|
865
1050
|
},
|
|
866
1051
|
},
|
|
1052
|
+
{
|
|
1053
|
+
title: "Cache: Set DeepSeek API Key",
|
|
1054
|
+
value: "cache.balance.key",
|
|
1055
|
+
description: "Set or update the DeepSeek API key for balance display",
|
|
1056
|
+
slash: { name: "cache-balance-key" },
|
|
1057
|
+
onSelect: (dialog) => {
|
|
1058
|
+
const zh = langZH();
|
|
1059
|
+
const current = api.kv.get(`${KV_PREFIX}.ds_key`, "");
|
|
1060
|
+
// 已保存的 key 以脱敏形式预填:保留 "sk-" 前缀 + 头 5 尾 5 字符,中间用 * 填充
|
|
1061
|
+
const maskKey = (k) => {
|
|
1062
|
+
if (!k)
|
|
1063
|
+
return "";
|
|
1064
|
+
const prefix = k.startsWith("sk-") ? "sk-" : "";
|
|
1065
|
+
const body = prefix ? k.slice(3) : k;
|
|
1066
|
+
if (body.length <= 10)
|
|
1067
|
+
return prefix + body.slice(0, 5) + "*".repeat(Math.max(3, body.length - 5));
|
|
1068
|
+
return prefix + body.slice(0, 5) + "*".repeat(Math.max(3, body.length - 10)) + body.slice(-5);
|
|
1069
|
+
};
|
|
1070
|
+
const masked = maskKey(current);
|
|
1071
|
+
dialog?.replace(() => (_jsx(api.ui.DialogPrompt, { title: zh ? "DeepSeek API Key" : "DeepSeek API Key", description: () => _jsx("text", { children: zh ? "输入 DeepSeek API Key 以显示账户余额(留空清除)" : "Enter your DeepSeek API key to show account balance (leave empty to clear)" }), placeholder: "sk-...", value: masked, onConfirm: (val) => {
|
|
1072
|
+
const input = val.trim();
|
|
1073
|
+
// 空 → 清除;含 * (脱敏占位符残留)→ 视为未修改,保留原 key;否则为新 key
|
|
1074
|
+
let key;
|
|
1075
|
+
if (input === "") {
|
|
1076
|
+
key = "";
|
|
1077
|
+
}
|
|
1078
|
+
else if (input.includes("*")) {
|
|
1079
|
+
key = current;
|
|
1080
|
+
}
|
|
1081
|
+
else {
|
|
1082
|
+
key = input;
|
|
1083
|
+
}
|
|
1084
|
+
api.kv.set(`${KV_PREFIX}.ds_key`, key);
|
|
1085
|
+
setBalanceRefresh(v => v + 1);
|
|
1086
|
+
if (key) {
|
|
1087
|
+
api.ui.toast({ message: zh ? "API Key 已保存,正在查询余额..." : "API Key saved, fetching balance..." });
|
|
1088
|
+
}
|
|
1089
|
+
else {
|
|
1090
|
+
api.ui.toast({ message: zh ? "API Key 已清除" : "API Key cleared" });
|
|
1091
|
+
}
|
|
1092
|
+
dialog?.clear();
|
|
1093
|
+
}, onCancel: () => dialog?.clear() })));
|
|
1094
|
+
},
|
|
1095
|
+
},
|
|
867
1096
|
{
|
|
868
1097
|
title: "Cache: Debug Skills Detection",
|
|
869
1098
|
value: "cache.debug-skills",
|
|
@@ -908,6 +1137,111 @@ const tui = async (api) => {
|
|
|
908
1137
|
});
|
|
909
1138
|
},
|
|
910
1139
|
},
|
|
1140
|
+
{
|
|
1141
|
+
title: "Cache: Sub-Agent Stats",
|
|
1142
|
+
value: "cache.session",
|
|
1143
|
+
description: "View token cache statistics for a sub-agent by session ID",
|
|
1144
|
+
slash: { name: "cache-session" },
|
|
1145
|
+
onSelect: (dialog) => {
|
|
1146
|
+
// ── 扫描当前主 session 的子代理 session ID 列表 ──
|
|
1147
|
+
const rt = api.route.current;
|
|
1148
|
+
const parentSid = rt.name === "session" && rt.params ? String(rt.params.sessionID) : "";
|
|
1149
|
+
const SUBAGENT_TOOLS = new Set(["task", "delegate", "call_omo_agent"]);
|
|
1150
|
+
const children = [];
|
|
1151
|
+
if (parentSid) {
|
|
1152
|
+
try {
|
|
1153
|
+
const msgs = api.state.session.messages(parentSid);
|
|
1154
|
+
for (const msg of msgs) {
|
|
1155
|
+
if (msg.role !== "assistant")
|
|
1156
|
+
continue;
|
|
1157
|
+
let parts = [];
|
|
1158
|
+
try {
|
|
1159
|
+
parts = api.state.part(msg.id);
|
|
1160
|
+
}
|
|
1161
|
+
catch { }
|
|
1162
|
+
for (const p of parts) {
|
|
1163
|
+
if (p.type !== "tool")
|
|
1164
|
+
continue;
|
|
1165
|
+
const tool = String(p.tool ?? "");
|
|
1166
|
+
if (!SUBAGENT_TOOLS.has(tool))
|
|
1167
|
+
continue;
|
|
1168
|
+
const st = p.state;
|
|
1169
|
+
const stMeta = st?.metadata;
|
|
1170
|
+
const subSid = stMeta?.session_id ?? stMeta?.sessionId;
|
|
1171
|
+
if (!subSid)
|
|
1172
|
+
continue;
|
|
1173
|
+
const sidStr = String(subSid);
|
|
1174
|
+
const input = st?.input;
|
|
1175
|
+
const agent = String(p.subagent_type ?? input?.subagent_type ?? input?.category ?? tool);
|
|
1176
|
+
const prompt = String(input?.prompt ?? "");
|
|
1177
|
+
const desc = input?.description ? String(input.description) : "";
|
|
1178
|
+
const title = desc || prompt.replace(/\n/g, " ").replace(/\s+/g, " ").trim().slice(0, 40) || agent;
|
|
1179
|
+
children.push({ title, value: sidStr, description: `${agent} · ${sidStr.slice(0, 24)}…` });
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
catch { }
|
|
1184
|
+
}
|
|
1185
|
+
// 去重
|
|
1186
|
+
const seen = new Set();
|
|
1187
|
+
const unique = children.filter(c => { if (seen.has(c.value))
|
|
1188
|
+
return false; seen.add(c.value); return true; });
|
|
1189
|
+
if (unique.length > 0) {
|
|
1190
|
+
// ── 有子代理 → DialogSelect 列表选择 ──
|
|
1191
|
+
const zh = langZH();
|
|
1192
|
+
const currentSid = signals.overrideSessionId() ?? api.kv.get(`${KV_PREFIX}.session`, "");
|
|
1193
|
+
const options = unique.map((c, i) => ({
|
|
1194
|
+
title: `${i + 1}. ${c.title}`,
|
|
1195
|
+
value: c.value,
|
|
1196
|
+
description: c.description,
|
|
1197
|
+
}));
|
|
1198
|
+
// 首尾各放一个"回到主会话",长列表时顶部底部均可直达
|
|
1199
|
+
const backValue = "__main__";
|
|
1200
|
+
const backTitle = `\u2500 ${zh ? "\u56DE\u5230\u4E3B\u4F1A\u8BDD" : "Back to Main"}`;
|
|
1201
|
+
options.unshift({ title: backTitle, value: backValue, description: "" });
|
|
1202
|
+
options.push({ title: backTitle, value: backValue, description: "" });
|
|
1203
|
+
const currentIdx = currentSid ? options.findIndex(o => o.value === currentSid) : -1;
|
|
1204
|
+
dialog?.replace(() => (_jsx(api.ui.DialogSelect, { title: zh ? "选择子代理" : "Select Sub-Agent", options: options, current: currentIdx >= 0 ? options[currentIdx].value : undefined, onSelect: (opt) => {
|
|
1205
|
+
if (opt.value === backValue) {
|
|
1206
|
+
signals.setOverrideSessionId(undefined);
|
|
1207
|
+
api.kv.set(`${KV_PREFIX}.session`, "");
|
|
1208
|
+
api.ui.toast({ message: zh ? "已切回主会话" : "Switched to main session" });
|
|
1209
|
+
}
|
|
1210
|
+
else {
|
|
1211
|
+
signals.setOverrideSessionId(opt.value);
|
|
1212
|
+
api.kv.set(`${KV_PREFIX}.session`, opt.value);
|
|
1213
|
+
api.ui.toast({ message: (zh ? "已切换至子代理: " : "Showing sub-agent: ") + opt.value.slice(0, 24) + "\u2026" });
|
|
1214
|
+
}
|
|
1215
|
+
dialog?.clear();
|
|
1216
|
+
} })));
|
|
1217
|
+
}
|
|
1218
|
+
else {
|
|
1219
|
+
// ── 无子代理 → DialogPrompt 手动粘贴 ──
|
|
1220
|
+
const zh = langZH();
|
|
1221
|
+
dialog?.replace(() => (_jsx(api.ui.DialogPrompt, { title: signals.overrideSessionId() ? zh ? "切换子代理" : "Switch Sub" : zh ? "查看子代理缓存" : "View Sub Cache", description: () => _jsx("text", { children: zh ? "未找到子代理,请手动粘贴 Session ID" : "No sub-agents found. Paste a Session ID manually" }), placeholder: "ses_...", value: signals.overrideSessionId() ?? api.kv.get(`${KV_PREFIX}.session`, "") ?? "", onConfirm: (val) => {
|
|
1222
|
+
const sid = val.trim();
|
|
1223
|
+
if (sid) {
|
|
1224
|
+
signals.setOverrideSessionId(sid);
|
|
1225
|
+
api.kv.set(`${KV_PREFIX}.session`, sid);
|
|
1226
|
+
api.ui.toast({ message: (langZH() ? "已切换至子代理: " : "Showing sub-agent: ") + sid.slice(0, 24) + "\u2026" });
|
|
1227
|
+
}
|
|
1228
|
+
dialog?.clear();
|
|
1229
|
+
}, onCancel: () => dialog?.clear() })));
|
|
1230
|
+
}
|
|
1231
|
+
},
|
|
1232
|
+
},
|
|
1233
|
+
{
|
|
1234
|
+
title: "Cache: Back to Main",
|
|
1235
|
+
value: "cache.session.back",
|
|
1236
|
+
description: "Return to main session stats",
|
|
1237
|
+
slash: { name: "cache-session-back" },
|
|
1238
|
+
onSelect: (dialog) => {
|
|
1239
|
+
signals.setOverrideSessionId(undefined);
|
|
1240
|
+
api.kv.set(`${KV_PREFIX}.session`, "");
|
|
1241
|
+
api.ui.toast({ message: langZH() ? "已切回主会话" : "Switched to main session" });
|
|
1242
|
+
dialog?.clear();
|
|
1243
|
+
},
|
|
1244
|
+
},
|
|
911
1245
|
]);
|
|
912
1246
|
};
|
|
913
1247
|
const mod = {
|