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/package.json
CHANGED
package/src/_version.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// auto-generated
|
|
2
|
-
export const PLUGIN_VERSION="1.
|
|
2
|
+
export const PLUGIN_VERSION="1.3.0";
|
package/src/index.tsx
CHANGED
|
@@ -223,6 +223,16 @@ function desaturateTo(raw: unknown, maxSat: number, fallback: string): string {
|
|
|
223
223
|
return "#" + [nr, ng, nb].map((v) => Math.max(0, Math.min(255, v)).toString(16).padStart(2, "0")).join("")
|
|
224
224
|
}
|
|
225
225
|
|
|
226
|
+
/** Darken a hex colour by multiplying each channel by `factor` (0–1). */
|
|
227
|
+
function dimColor(hex: string, factor = 0.5): string {
|
|
228
|
+
const c = rgb(hex)
|
|
229
|
+
if (!c) return hex
|
|
230
|
+
const r = Math.round(c.r * factor)
|
|
231
|
+
const g = Math.round(c.g * factor)
|
|
232
|
+
const b = Math.round(c.b * factor)
|
|
233
|
+
return "#" + [r, g, b].map((v) => Math.max(0, Math.min(255, v)).toString(16).padStart(2, "0")).join("")
|
|
234
|
+
}
|
|
235
|
+
|
|
226
236
|
// Morandi fallbacks — used when a theme colour cannot be resolved
|
|
227
237
|
const FALLBACK = {
|
|
228
238
|
primary: "#8B9DAF",
|
|
@@ -345,6 +355,9 @@ interface PanelSignals {
|
|
|
345
355
|
setSectionSkills: (v: boolean) => void
|
|
346
356
|
borderVisible: () => boolean
|
|
347
357
|
setBorderVisible: (v: boolean) => void
|
|
358
|
+
/** When set, the panel renders stats for this session instead of the main one. */
|
|
359
|
+
overrideSessionId: () => string | undefined
|
|
360
|
+
setOverrideSessionId: (v: string | undefined) => void
|
|
348
361
|
}
|
|
349
362
|
|
|
350
363
|
const CURRENCIES: Record<string, string> = {
|
|
@@ -424,8 +437,21 @@ function TokenCachePanel(props: {
|
|
|
424
437
|
})
|
|
425
438
|
const [refreshTick, setRefreshTick] = createSignal(0)
|
|
426
439
|
|
|
440
|
+
// ── auto-clear override when the user navigates to a different main session ──
|
|
441
|
+
let lastMainSid = props.sessionId
|
|
427
442
|
createEffect(() => {
|
|
428
443
|
const sid = props.sessionId
|
|
444
|
+
if (sid !== lastMainSid) {
|
|
445
|
+
lastMainSid = sid
|
|
446
|
+
if (props.signals.overrideSessionId()) {
|
|
447
|
+
props.signals.setOverrideSessionId(undefined)
|
|
448
|
+
props.api.kv.set(`${KV_PREFIX}.session`, "")
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
})
|
|
452
|
+
|
|
453
|
+
createEffect(() => {
|
|
454
|
+
const sid = props.signals.overrideSessionId() ?? props.sessionId
|
|
429
455
|
void refreshTick()
|
|
430
456
|
void partVersion()
|
|
431
457
|
|
|
@@ -751,7 +777,7 @@ function TokenCachePanel(props: {
|
|
|
751
777
|
<span style={{ fg: pal().primary }}>
|
|
752
778
|
<b>{t().title}</b>
|
|
753
779
|
<Show when={open()}>
|
|
754
|
-
<span style={{ fg: pal().muted }}>
|
|
780
|
+
<span style={{ fg: dimColor(pal().muted, 0.75) }}> v{PLUGIN_VERSION}</span>
|
|
755
781
|
</Show>
|
|
756
782
|
</span>
|
|
757
783
|
<Show when={!open() && data().hasData}>
|
|
@@ -774,6 +800,18 @@ function TokenCachePanel(props: {
|
|
|
774
800
|
</text>
|
|
775
801
|
|
|
776
802
|
<Show when={open()}>
|
|
803
|
+
<Show when={props.signals.overrideSessionId()}>
|
|
804
|
+
{(() => {
|
|
805
|
+
const prefix = " \u21b3 " + (langZH() ? "\u5B50\u4EE3\u7406: " : "Sub: ")
|
|
806
|
+
const maxSidW = Math.max(6, panelWidth() - visualWidth(prefix))
|
|
807
|
+
return (
|
|
808
|
+
<text>
|
|
809
|
+
<span style={{ fg: pal().muted }}>{prefix}</span>
|
|
810
|
+
<span style={{ fg: pal().text }}>{truncateVisual(props.signals.overrideSessionId()!, maxSidW)}</span>
|
|
811
|
+
</text>
|
|
812
|
+
)
|
|
813
|
+
})()}
|
|
814
|
+
</Show>
|
|
777
815
|
<Show when={data().hasData} fallback={
|
|
778
816
|
<>
|
|
779
817
|
<text fg={pal().muted}>{sep()}</text>
|
|
@@ -950,10 +988,19 @@ function TokenCachePanel(props: {
|
|
|
950
988
|
// ---------------------------------------------------------------------------
|
|
951
989
|
|
|
952
990
|
function createSidebarSlot(api: TuiPluginApi, signals: PanelSignals): TuiSlotPlugin {
|
|
991
|
+
let lastSlotSid = ""
|
|
953
992
|
return {
|
|
954
993
|
order: 55,
|
|
955
994
|
slots: {
|
|
956
995
|
sidebar_content(ctx: TuiSlotContext, input: { session_id: string }): JSX.Element {
|
|
996
|
+
// ── auto-clear override when the user navigates to a different main session ──
|
|
997
|
+
if (input.session_id !== lastSlotSid) {
|
|
998
|
+
lastSlotSid = input.session_id
|
|
999
|
+
if (signals.overrideSessionId()) {
|
|
1000
|
+
signals.setOverrideSessionId(undefined)
|
|
1001
|
+
api.kv.set("cache_panel.session", "")
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
957
1004
|
return (
|
|
958
1005
|
<TokenCachePanel
|
|
959
1006
|
theme={ctx.theme.current}
|
|
@@ -977,6 +1024,7 @@ const tui: TuiPlugin = async (api: TuiPluginApi) => {
|
|
|
977
1024
|
const [sectionSkills, setSectionSkills] = createSignal(true)
|
|
978
1025
|
const [borderVisible, setBorderVisible] = createSignal(true)
|
|
979
1026
|
const [langZH, setLangZH] = createSignal(LANG_ZH)
|
|
1027
|
+
const [overrideSessionId, setOverrideSessionId] = createSignal<string | undefined>(undefined)
|
|
980
1028
|
|
|
981
1029
|
const signals: PanelSignals = {
|
|
982
1030
|
currencySymbol, setCurrencySymbol,
|
|
@@ -987,6 +1035,7 @@ const tui: TuiPlugin = async (api: TuiPluginApi) => {
|
|
|
987
1035
|
sectionDist, setSectionDist,
|
|
988
1036
|
sectionSkills, setSectionSkills,
|
|
989
1037
|
borderVisible, setBorderVisible,
|
|
1038
|
+
overrideSessionId, setOverrideSessionId,
|
|
990
1039
|
}
|
|
991
1040
|
|
|
992
1041
|
api.slots.register(createSidebarSlot(api, signals))
|
|
@@ -1174,6 +1223,120 @@ const tui: TuiPlugin = async (api: TuiPluginApi) => {
|
|
|
1174
1223
|
})
|
|
1175
1224
|
},
|
|
1176
1225
|
},
|
|
1226
|
+
{
|
|
1227
|
+
title: "Cache: Sub-Agent Stats",
|
|
1228
|
+
value: "cache.session",
|
|
1229
|
+
description: "View token cache statistics for a sub-agent by session ID",
|
|
1230
|
+
slash: { name: "cache-session" },
|
|
1231
|
+
onSelect: (dialog) => {
|
|
1232
|
+
// ── 扫描当前主 session 的子代理 session ID 列表 ──
|
|
1233
|
+
const rt = api.route.current
|
|
1234
|
+
const parentSid = rt.name === "session" && rt.params ? String(rt.params.sessionID) : ""
|
|
1235
|
+
const SUBAGENT_TOOLS = new Set(["task", "delegate", "call_omo_agent"])
|
|
1236
|
+
|
|
1237
|
+
interface ChildEntry { title: string; value: string; description: string }
|
|
1238
|
+
const children: ChildEntry[] = []
|
|
1239
|
+
if (parentSid) {
|
|
1240
|
+
try {
|
|
1241
|
+
const msgs = api.state.session.messages(parentSid)
|
|
1242
|
+
for (const msg of msgs) {
|
|
1243
|
+
if (msg.role !== "assistant") continue
|
|
1244
|
+
let parts: readonly Part[] = []
|
|
1245
|
+
try { parts = api.state.part(msg.id) } catch {}
|
|
1246
|
+
for (const p of parts) {
|
|
1247
|
+
if (p.type !== "tool") continue
|
|
1248
|
+
const tool = String((p as ToolPart).tool ?? "")
|
|
1249
|
+
if (!SUBAGENT_TOOLS.has(tool)) continue
|
|
1250
|
+
const st = (p as any).state as Record<string, unknown> | undefined
|
|
1251
|
+
const stMeta = st?.metadata as Record<string, unknown> | undefined
|
|
1252
|
+
const subSid = stMeta?.session_id ?? stMeta?.sessionId
|
|
1253
|
+
if (!subSid) continue
|
|
1254
|
+
const sidStr = String(subSid)
|
|
1255
|
+
const input = st?.input as Record<string, unknown> | undefined
|
|
1256
|
+
const agent = String((p as any).subagent_type ?? input?.subagent_type ?? input?.category ?? tool)
|
|
1257
|
+
const prompt = String(input?.prompt ?? "")
|
|
1258
|
+
const desc = input?.description ? String(input.description) : ""
|
|
1259
|
+
const title = desc || prompt.replace(/\n/g, " ").replace(/\s+/g, " ").trim().slice(0, 40) || agent
|
|
1260
|
+
children.push({ title, value: sidStr, description: `${agent} · ${sidStr.slice(0, 24)}…` })
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
1263
|
+
} catch {}
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
// 去重
|
|
1267
|
+
const seen = new Set<string>()
|
|
1268
|
+
const unique = children.filter(c => { if (seen.has(c.value)) return false; seen.add(c.value); return true })
|
|
1269
|
+
|
|
1270
|
+
if (unique.length > 0) {
|
|
1271
|
+
// ── 有子代理 → DialogSelect 列表选择 ──
|
|
1272
|
+
const zh = langZH()
|
|
1273
|
+
const currentSid = signals.overrideSessionId() ?? api.kv.get<string>(`${KV_PREFIX}.session`, "")
|
|
1274
|
+
const options = unique.map((c, i) => ({
|
|
1275
|
+
title: `${i + 1}. ${c.title}`,
|
|
1276
|
+
value: c.value,
|
|
1277
|
+
description: c.description,
|
|
1278
|
+
}))
|
|
1279
|
+
// 首尾各放一个"回到主会话",长列表时顶部底部均可直达
|
|
1280
|
+
const backValue = "__main__"
|
|
1281
|
+
const backTitle = `\u2500 ${zh ? "\u56DE\u5230\u4E3B\u4F1A\u8BDD" : "Back to Main"}`
|
|
1282
|
+
options.unshift({ title: backTitle, value: backValue, description: "" })
|
|
1283
|
+
options.push({ title: backTitle, value: backValue, description: "" })
|
|
1284
|
+
const currentIdx = currentSid ? options.findIndex(o => o.value === currentSid) : -1
|
|
1285
|
+
dialog?.replace(() => (
|
|
1286
|
+
<api.ui.DialogSelect
|
|
1287
|
+
title={zh ? "选择子代理" : "Select Sub-Agent"}
|
|
1288
|
+
options={options}
|
|
1289
|
+
current={currentIdx >= 0 ? options[currentIdx].value : undefined}
|
|
1290
|
+
onSelect={(opt) => {
|
|
1291
|
+
if (opt.value === backValue) {
|
|
1292
|
+
signals.setOverrideSessionId(undefined)
|
|
1293
|
+
api.kv.set(`${KV_PREFIX}.session`, "")
|
|
1294
|
+
api.ui.toast({ message: zh ? "已切回主会话" : "Switched to main session" })
|
|
1295
|
+
} else {
|
|
1296
|
+
signals.setOverrideSessionId(opt.value)
|
|
1297
|
+
api.kv.set(`${KV_PREFIX}.session`, opt.value)
|
|
1298
|
+
api.ui.toast({ message: (zh ? "已切换至子代理: " : "Showing sub-agent: ") + opt.value.slice(0, 24) + "\u2026" })
|
|
1299
|
+
}
|
|
1300
|
+
dialog?.clear()
|
|
1301
|
+
}}
|
|
1302
|
+
/>
|
|
1303
|
+
))
|
|
1304
|
+
} else {
|
|
1305
|
+
// ── 无子代理 → DialogPrompt 手动粘贴 ──
|
|
1306
|
+
const zh = langZH()
|
|
1307
|
+
dialog?.replace(() => (
|
|
1308
|
+
<api.ui.DialogPrompt
|
|
1309
|
+
title={signals.overrideSessionId() ? zh ? "切换子代理" : "Switch Sub" : zh ? "查看子代理缓存" : "View Sub Cache"}
|
|
1310
|
+
description={() => <text>{zh ? "未找到子代理,请手动粘贴 Session ID" : "No sub-agents found. Paste a Session ID manually"}</text>}
|
|
1311
|
+
placeholder="ses_..."
|
|
1312
|
+
value={signals.overrideSessionId() ?? api.kv.get<string>(`${KV_PREFIX}.session`, "") ?? ""}
|
|
1313
|
+
onConfirm={(val) => {
|
|
1314
|
+
const sid = val.trim()
|
|
1315
|
+
if (sid) {
|
|
1316
|
+
signals.setOverrideSessionId(sid)
|
|
1317
|
+
api.kv.set(`${KV_PREFIX}.session`, sid)
|
|
1318
|
+
api.ui.toast({ message: (langZH() ? "已切换至子代理: " : "Showing sub-agent: ") + sid.slice(0, 24) + "\u2026" })
|
|
1319
|
+
}
|
|
1320
|
+
dialog?.clear()
|
|
1321
|
+
}}
|
|
1322
|
+
onCancel={() => dialog?.clear()}
|
|
1323
|
+
/>
|
|
1324
|
+
))
|
|
1325
|
+
}
|
|
1326
|
+
},
|
|
1327
|
+
},
|
|
1328
|
+
{
|
|
1329
|
+
title: "Cache: Back to Main",
|
|
1330
|
+
value: "cache.session.back",
|
|
1331
|
+
description: "Return to main session stats",
|
|
1332
|
+
slash: { name: "cache-session-back" },
|
|
1333
|
+
onSelect: (dialog) => {
|
|
1334
|
+
signals.setOverrideSessionId(undefined)
|
|
1335
|
+
api.kv.set(`${KV_PREFIX}.session`, "")
|
|
1336
|
+
api.ui.toast({ message: langZH() ? "已切回主会话" : "Switched to main session" })
|
|
1337
|
+
dialog?.clear()
|
|
1338
|
+
},
|
|
1339
|
+
},
|
|
1177
1340
|
])
|
|
1178
1341
|
}
|
|
1179
1342
|
|