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.
@@ -1 +1 @@
1
- export declare const PLUGIN_VERSION = "1.2.16";
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.16";
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: [" (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] })] })] })] }), _jsx(Show, { when: open(), children: _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) => {
705
- const rightW = visualWidth(fmt(sk.tokens)) + UNIT_GAP + visualWidth(t().tok);
706
- const maxLabel = Math.max(4, panelWidth() - gutter() - rightW - 1);
707
- const label = truncateVisual(sk.name, maxLabel);
708
- return (_jsx("text", { fg: pal().muted, children: justify(label, fmt(sk.tokens), t().tok) }));
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 = {