opencode-visual-cache 1.2.16 → 1.3.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/dist/_version.d.ts +1 -1
- package/dist/_version.js +1 -1
- package/dist/index.js +148 -6
- package/dist/tui.js +416 -215
- package/package.json +1 -1
- package/src/_version.ts +1 -1
- package/src/index.tsx +164 -1
package/dist/_version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const PLUGIN_VERSION = "1.
|
|
1
|
+
export declare const PLUGIN_VERSION = "1.3.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.3.0";
|
package/dist/index.js
CHANGED
|
@@ -202,6 +202,16 @@ function desaturateTo(raw, maxSat, fallback) {
|
|
|
202
202
|
const nb = Math.round(c.b + (luma - c.b) * hi);
|
|
203
203
|
return "#" + [nr, ng, nb].map((v) => Math.max(0, Math.min(255, v)).toString(16).padStart(2, "0")).join("");
|
|
204
204
|
}
|
|
205
|
+
/** Darken a hex colour by multiplying each channel by `factor` (0–1). */
|
|
206
|
+
function dimColor(hex, factor = 0.5) {
|
|
207
|
+
const c = rgb(hex);
|
|
208
|
+
if (!c)
|
|
209
|
+
return hex;
|
|
210
|
+
const r = Math.round(c.r * factor);
|
|
211
|
+
const g = Math.round(c.g * factor);
|
|
212
|
+
const b = Math.round(c.b * factor);
|
|
213
|
+
return "#" + [r, g, b].map((v) => Math.max(0, Math.min(255, v)).toString(16).padStart(2, "0")).join("");
|
|
214
|
+
}
|
|
205
215
|
// Morandi fallbacks — used when a theme colour cannot be resolved
|
|
206
216
|
const FALLBACK = {
|
|
207
217
|
primary: "#8B9DAF",
|
|
@@ -342,8 +352,20 @@ function TokenCachePanel(props) {
|
|
|
342
352
|
hasSkills: false,
|
|
343
353
|
});
|
|
344
354
|
const [refreshTick, setRefreshTick] = createSignal(0);
|
|
355
|
+
// ── auto-clear override when the user navigates to a different main session ──
|
|
356
|
+
let lastMainSid = props.sessionId;
|
|
345
357
|
createEffect(() => {
|
|
346
358
|
const sid = props.sessionId;
|
|
359
|
+
if (sid !== lastMainSid) {
|
|
360
|
+
lastMainSid = sid;
|
|
361
|
+
if (props.signals.overrideSessionId()) {
|
|
362
|
+
props.signals.setOverrideSessionId(undefined);
|
|
363
|
+
props.api.kv.set(`${KV_PREFIX}.session`, "");
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
});
|
|
367
|
+
createEffect(() => {
|
|
368
|
+
const sid = props.signals.overrideSessionId() ?? props.sessionId;
|
|
347
369
|
void refreshTick();
|
|
348
370
|
void partVersion();
|
|
349
371
|
// 自然追踪 messages 和 provider(SDK 数据就绪时自动重新执行)
|
|
@@ -701,21 +723,34 @@ function TokenCachePanel(props) {
|
|
|
701
723
|
// boxEl.width may be undefined before the first measurement — guard with 0
|
|
702
724
|
const w = boxEl ? Math.max(MIN_PANEL_WIDTH, boxEl.width ?? 0) : DEFAULT_PANEL_WIDTH;
|
|
703
725
|
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
|
-
|
|
726
|
+
}, 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: (() => {
|
|
727
|
+
const prefix = " \u21b3 " + (langZH() ? "\u5B50\u4EE3\u7406: " : "Sub: ");
|
|
728
|
+
const maxSidW = Math.max(6, panelWidth() - visualWidth(prefix));
|
|
729
|
+
return (_jsxs("text", { children: [_jsx("span", { style: { fg: pal().muted }, children: prefix }), _jsx("span", { style: { fg: pal().text }, children: truncateVisual(props.signals.overrideSessionId(), maxSidW) })] }));
|
|
730
|
+
})() }), _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) => {
|
|
731
|
+
const rightW = visualWidth(fmt(sk.tokens)) + UNIT_GAP + visualWidth(t().tok);
|
|
732
|
+
const maxLabel = Math.max(4, panelWidth() - gutter() - rightW - 1);
|
|
733
|
+
const label = truncateVisual(sk.name, maxLabel);
|
|
734
|
+
return (_jsx("text", { fg: pal().muted, children: justify(label, fmt(sk.tokens), t().tok) }));
|
|
735
|
+
}) })] }) })] })] })] }));
|
|
710
736
|
}
|
|
711
737
|
// ---------------------------------------------------------------------------
|
|
712
738
|
// Plugin entry
|
|
713
739
|
// ---------------------------------------------------------------------------
|
|
714
740
|
function createSidebarSlot(api, signals) {
|
|
741
|
+
let lastSlotSid = "";
|
|
715
742
|
return {
|
|
716
743
|
order: 55,
|
|
717
744
|
slots: {
|
|
718
745
|
sidebar_content(ctx, input) {
|
|
746
|
+
// ── auto-clear override when the user navigates to a different main session ──
|
|
747
|
+
if (input.session_id !== lastSlotSid) {
|
|
748
|
+
lastSlotSid = input.session_id;
|
|
749
|
+
if (signals.overrideSessionId()) {
|
|
750
|
+
signals.setOverrideSessionId(undefined);
|
|
751
|
+
api.kv.set("cache_panel.session", "");
|
|
752
|
+
}
|
|
753
|
+
}
|
|
719
754
|
return (_jsx(TokenCachePanel, { theme: ctx.theme.current, api: api, sessionId: input.session_id, signals: signals }));
|
|
720
755
|
},
|
|
721
756
|
},
|
|
@@ -731,6 +766,7 @@ const tui = async (api) => {
|
|
|
731
766
|
const [sectionSkills, setSectionSkills] = createSignal(true);
|
|
732
767
|
const [borderVisible, setBorderVisible] = createSignal(true);
|
|
733
768
|
const [langZH, setLangZH] = createSignal(LANG_ZH);
|
|
769
|
+
const [overrideSessionId, setOverrideSessionId] = createSignal(undefined);
|
|
734
770
|
const signals = {
|
|
735
771
|
currencySymbol, setCurrencySymbol,
|
|
736
772
|
exchangeRate, setExchangeRate,
|
|
@@ -740,6 +776,7 @@ const tui = async (api) => {
|
|
|
740
776
|
sectionDist, setSectionDist,
|
|
741
777
|
sectionSkills, setSectionSkills,
|
|
742
778
|
borderVisible, setBorderVisible,
|
|
779
|
+
overrideSessionId, setOverrideSessionId,
|
|
743
780
|
};
|
|
744
781
|
api.slots.register(createSidebarSlot(api, signals));
|
|
745
782
|
// ── slash commands for runtime config ──
|
|
@@ -908,6 +945,111 @@ const tui = async (api) => {
|
|
|
908
945
|
});
|
|
909
946
|
},
|
|
910
947
|
},
|
|
948
|
+
{
|
|
949
|
+
title: "Cache: Sub-Agent Stats",
|
|
950
|
+
value: "cache.session",
|
|
951
|
+
description: "View token cache statistics for a sub-agent by session ID",
|
|
952
|
+
slash: { name: "cache-session" },
|
|
953
|
+
onSelect: (dialog) => {
|
|
954
|
+
// ── 扫描当前主 session 的子代理 session ID 列表 ──
|
|
955
|
+
const rt = api.route.current;
|
|
956
|
+
const parentSid = rt.name === "session" && rt.params ? String(rt.params.sessionID) : "";
|
|
957
|
+
const SUBAGENT_TOOLS = new Set(["task", "delegate", "call_omo_agent"]);
|
|
958
|
+
const children = [];
|
|
959
|
+
if (parentSid) {
|
|
960
|
+
try {
|
|
961
|
+
const msgs = api.state.session.messages(parentSid);
|
|
962
|
+
for (const msg of msgs) {
|
|
963
|
+
if (msg.role !== "assistant")
|
|
964
|
+
continue;
|
|
965
|
+
let parts = [];
|
|
966
|
+
try {
|
|
967
|
+
parts = api.state.part(msg.id);
|
|
968
|
+
}
|
|
969
|
+
catch { }
|
|
970
|
+
for (const p of parts) {
|
|
971
|
+
if (p.type !== "tool")
|
|
972
|
+
continue;
|
|
973
|
+
const tool = String(p.tool ?? "");
|
|
974
|
+
if (!SUBAGENT_TOOLS.has(tool))
|
|
975
|
+
continue;
|
|
976
|
+
const st = p.state;
|
|
977
|
+
const stMeta = st?.metadata;
|
|
978
|
+
const subSid = stMeta?.session_id ?? stMeta?.sessionId;
|
|
979
|
+
if (!subSid)
|
|
980
|
+
continue;
|
|
981
|
+
const sidStr = String(subSid);
|
|
982
|
+
const input = st?.input;
|
|
983
|
+
const agent = String(p.subagent_type ?? input?.subagent_type ?? input?.category ?? tool);
|
|
984
|
+
const prompt = String(input?.prompt ?? "");
|
|
985
|
+
const desc = input?.description ? String(input.description) : "";
|
|
986
|
+
const title = desc || prompt.replace(/\n/g, " ").replace(/\s+/g, " ").trim().slice(0, 40) || agent;
|
|
987
|
+
children.push({ title, value: sidStr, description: `${agent} · ${sidStr.slice(0, 24)}…` });
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
catch { }
|
|
992
|
+
}
|
|
993
|
+
// 去重
|
|
994
|
+
const seen = new Set();
|
|
995
|
+
const unique = children.filter(c => { if (seen.has(c.value))
|
|
996
|
+
return false; seen.add(c.value); return true; });
|
|
997
|
+
if (unique.length > 0) {
|
|
998
|
+
// ── 有子代理 → DialogSelect 列表选择 ──
|
|
999
|
+
const zh = langZH();
|
|
1000
|
+
const currentSid = signals.overrideSessionId() ?? api.kv.get(`${KV_PREFIX}.session`, "");
|
|
1001
|
+
const options = unique.map((c, i) => ({
|
|
1002
|
+
title: `${i + 1}. ${c.title}`,
|
|
1003
|
+
value: c.value,
|
|
1004
|
+
description: c.description,
|
|
1005
|
+
}));
|
|
1006
|
+
// 首尾各放一个"回到主会话",长列表时顶部底部均可直达
|
|
1007
|
+
const backValue = "__main__";
|
|
1008
|
+
const backTitle = `\u2500 ${zh ? "\u56DE\u5230\u4E3B\u4F1A\u8BDD" : "Back to Main"}`;
|
|
1009
|
+
options.unshift({ title: backTitle, value: backValue, description: "" });
|
|
1010
|
+
options.push({ title: backTitle, value: backValue, description: "" });
|
|
1011
|
+
const currentIdx = currentSid ? options.findIndex(o => o.value === currentSid) : -1;
|
|
1012
|
+
dialog?.replace(() => (_jsx(api.ui.DialogSelect, { title: zh ? "选择子代理" : "Select Sub-Agent", options: options, current: currentIdx >= 0 ? options[currentIdx].value : undefined, onSelect: (opt) => {
|
|
1013
|
+
if (opt.value === backValue) {
|
|
1014
|
+
signals.setOverrideSessionId(undefined);
|
|
1015
|
+
api.kv.set(`${KV_PREFIX}.session`, "");
|
|
1016
|
+
api.ui.toast({ message: zh ? "已切回主会话" : "Switched to main session" });
|
|
1017
|
+
}
|
|
1018
|
+
else {
|
|
1019
|
+
signals.setOverrideSessionId(opt.value);
|
|
1020
|
+
api.kv.set(`${KV_PREFIX}.session`, opt.value);
|
|
1021
|
+
api.ui.toast({ message: (zh ? "已切换至子代理: " : "Showing sub-agent: ") + opt.value.slice(0, 24) + "\u2026" });
|
|
1022
|
+
}
|
|
1023
|
+
dialog?.clear();
|
|
1024
|
+
} })));
|
|
1025
|
+
}
|
|
1026
|
+
else {
|
|
1027
|
+
// ── 无子代理 → DialogPrompt 手动粘贴 ──
|
|
1028
|
+
const zh = langZH();
|
|
1029
|
+
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) => {
|
|
1030
|
+
const sid = val.trim();
|
|
1031
|
+
if (sid) {
|
|
1032
|
+
signals.setOverrideSessionId(sid);
|
|
1033
|
+
api.kv.set(`${KV_PREFIX}.session`, sid);
|
|
1034
|
+
api.ui.toast({ message: (langZH() ? "已切换至子代理: " : "Showing sub-agent: ") + sid.slice(0, 24) + "\u2026" });
|
|
1035
|
+
}
|
|
1036
|
+
dialog?.clear();
|
|
1037
|
+
}, onCancel: () => dialog?.clear() })));
|
|
1038
|
+
}
|
|
1039
|
+
},
|
|
1040
|
+
},
|
|
1041
|
+
{
|
|
1042
|
+
title: "Cache: Back to Main",
|
|
1043
|
+
value: "cache.session.back",
|
|
1044
|
+
description: "Return to main session stats",
|
|
1045
|
+
slash: { name: "cache-session-back" },
|
|
1046
|
+
onSelect: (dialog) => {
|
|
1047
|
+
signals.setOverrideSessionId(undefined);
|
|
1048
|
+
api.kv.set(`${KV_PREFIX}.session`, "");
|
|
1049
|
+
api.ui.toast({ message: langZH() ? "已切回主会话" : "Switched to main session" });
|
|
1050
|
+
dialog?.clear();
|
|
1051
|
+
},
|
|
1052
|
+
},
|
|
911
1053
|
]);
|
|
912
1054
|
};
|
|
913
1055
|
const mod = {
|