From 891edaafe7723b906d45bb049b546e2dd3f789ff Mon Sep 17 00:00:00 2001 From: jiangchen <1703935164@qq.com> Date: Sun, 27 Sep 2026 18:35:21 +0800 Subject: [PATCH] =?UTF-8?q?fix(bridge):=20=E4=B8=8D=E5=86=8D=E6=8A=A2?= =?UTF-8?q?=E5=86=99=20snapshot.json=EF=BC=88=E4=BC=9A=E6=8A=8A=E5=88=AB?= =?UTF-8?q?=E7=9A=84=E4=BC=9A=E8=AF=9D=E4=B8=B2=E8=BF=9B=E6=B5=AE=E7=AA=97?= =?UTF-8?q?=EF=BC=89=EF=BC=9B=E9=95=9C=E5=83=8F=E7=BC=93=E5=AD=98=E6=B7=B1?= =?UTF-8?q?=E5=BA=A6=2040=E2=86=9280?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 实测串台:插件 tick 一停顿,桥就把另一个会话的 40 行写进 snapshot.json,浮窗显示成别人的对话。 现在桥只写 snapshot-bridge.json,浮窗按 sessionId 匹配后才用它补缺字段,永远不会串会话。 镜像缓存每会话保留 80 行(原来 40),仍然每 3 秒落盘一次。 --- lib/host.js | 17 +++++++++++++++-- tools/mirror-bridge.mjs | 11 +++-------- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/lib/host.js b/lib/host.js index b3ccfdf..44827f0 100644 --- a/lib/host.js +++ b/lib/host.js @@ -252,7 +252,7 @@ export function apply(ctx, config) { const out = { at: Date.now(), lastSessionId, sessions: {} } const ids = [...rows.keys()].slice(-8) for (const id of ids) { - out.sessions[id] = { title: titles.get(id) || null, rows: (rows.get(id) || []).slice(-40), counts: counts.get(id) || null } + out.sessions[id] = { title: titles.get(id) || null, rows: (rows.get(id) || []).slice(-80), counts: counts.get(id) || null } } fs.mkdirSync(path.dirname(mirrorCacheFile), { recursive: true }) fs.writeFileSync(mirrorCacheFile, JSON.stringify(out)) @@ -889,12 +889,25 @@ border:1px solid rgba(120,140,180,.35);background:rgba(22,26,34,.92);color:#e6ea log('services: ' + ['settings', 'sessions', 'tools', 'tokenMeter', 'sessionProjection', 'llm', 'shell', 'sandboxPolicy', 'webServer'] .map((n) => `${n}=${(() => { try { return ctx.get(n) ? 'y' : 'n' } catch { return '?' } })()}`).join(' ')) // 诊断:把 sessionController / apiProxy 暴露的方法名列出来(回填历史要用其中一个) + // 注意:这些对象的方法常常是 getter / 非枚举,Object.keys 看不到 → 用 getOwnPropertyNames try { + const names = (o, proto = false) => { + try { + const out = new Set(Object.getOwnPropertyNames(o)) + if (proto) for (const k of Object.getOwnPropertyNames(Object.getPrototypeOf(o) || {})) out.add(k) + return [...out].filter((k) => !['constructor', 'ctx', 'name', 'store', 'counter', 'projections'].includes(k)).slice(0, 60) + } catch { return [] } + } const faces = {} for (const n of ['sessionController', 'apiProxy', 'sessions', 'sessionProjection']) { try { const f = ctx.get(n) - if (f) faces[n] = Object.keys(f).slice(0, 80) + if (!f) continue + faces[n] = names(f) + if (f.sessions) faces[n + '.sessions'] = names(f.sessions) + if (f.store) faces[n + '.store'] = names(f.store) + if (f.projections) faces[n + '.projections'] = names(f.projections) + if (f.counter) faces[n + '.counter'] = names(f.counter) } catch { } } status.apiMethods = faces diff --git a/tools/mirror-bridge.mjs b/tools/mirror-bridge.mjs index 608a9ea..736faab 100644 --- a/tools/mirror-bridge.mjs +++ b/tools/mirror-bridge.mjs @@ -236,15 +236,10 @@ function writeSnapshot() { const snap = buildSnapshot() if (!snap) return fs.mkdirSync(STATE_DIR, { recursive: true }) - // 兜底文件总是写:插件缺的字段(token/上下文估算、标题)由浮窗用它补齐 + // 只写兜底文件:snapshot.json 是插件的"正本"。桥去写它会把**别的会话**塞给浮窗(实测串台过: + // 插件 tick 一停顿,桥就把另一个会话的 40 行写了进去)。浮窗按 sessionId 匹配后再用它补缺字段。 fs.writeFileSync(SNAPSHOT_BRIDGE, JSON.stringify(snap)) - // 插件在写就不抢 snapshot.json:mtime 3 秒内视为它的 - try { - const st = fs.statSync(SNAPSHOT) - if (Date.now() - st.mtimeMs < 3000 && readJson(SNAPSHOT)?.source !== 'mirror-bridge') return - } catch { } - fs.writeFileSync(SNAPSHOT, JSON.stringify(snap)) - log(`snapshot: ${snap.sessionId} "${(snap.title || '').slice(0, 24)}" turns=${snap.metrics.turns} steps=${snap.metrics.steps} rows=${snap.messages.length} ctx=${snap.metrics.contextUsed == null ? '-' : Math.round(snap.metrics.contextUsed * 100) + '%'}`) + log(`bridge: ${snap.sessionId} "${(snap.title || '').slice(0, 20)}" rows=${snap.messages.length} cache=${snap.metrics.cacheHit == null ? '-' : Math.round(snap.metrics.cacheHit * 100) + '%'}`) } catch (e) { log('write failed:', String(e?.message || e)) } }