dsh-layered-memory 0.8.4 → 0.8.5

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.en.md CHANGED
@@ -21,7 +21,7 @@ Requires Node ≥ 22.16. Two invocation styles — the `npx` prefix can replace
21
21
  any command below:
22
22
 
23
23
  ```bash
24
- # Option 1: run the official CLI directly via npx (no pre-installed dsh; version can be pinned, e.g. dsh-layered-memory@0.8.2)
24
+ # Option 1: run the official CLI directly via npx (no pre-installed dsh; version can be pinned, e.g. dsh-layered-memory@0.8.4)
25
25
  npx -y @deepseek-ai/dsh plugin --profile web add dsh-layered-memory
26
26
 
27
27
  # Option 2: with the dsh CLI installed (dsh is a pnpm forwarder; npm i -g pnpm first if missing)
@@ -129,6 +129,15 @@ trajectory view):
129
129
  - **Control**: the pill next to the mode selector in the input bar (`Memory · Auto`);
130
130
  clicking opens a macOS-style sliding picker above — release to snap to the nearest
131
131
  mode; adapts to light/dark themes;
132
+ - The lower half of the popover is a **per-session info area**: recall hits
133
+ (hit/searched turns plus cumulative items), batching progress (this session's
134
+ slice x/effective threshold; the off mode shows parked slices instead), memories
135
+ produced for this session, and session message count — plus status lines for
136
+ anomalies (storage degraded / vector search unavailable) and a global summary
137
+ (pending distill count, last distill time). Data comes from the
138
+ `dsh-memory/session-stats` endpoint (in-memory registries + an indexed COUNT,
139
+ zero file I/O), adaptively polled while open (2s busy / 5s idle) and stopped on
140
+ close;
132
141
  - Each session's choice is persisted by sessionId to `session-modes.json`, surviving
133
142
  restarts/session restore; stacks with the global switches (global is the master gate);
134
143
  L2/L3 are fully family-isolated — content never leaks across families.
@@ -144,20 +153,24 @@ trajectory view):
144
153
 
145
154
  ## Measured Comparison (DSH-MemBench: Automated Benchmark)
146
155
 
147
- Screenshots show what the plugin looks like — this section answers "**what does enabling it actually buy you?**" with measured numbers from an **automated benchmark** ([`bench/`](./bench/), one command to reproduce). Method: the same scenario bank with verbatim-identical inputs runs in **Group A (memory on)** with 3 merged repetitions and **Group B (memory off)** with 1 repetition (a memory-off long task burns multiples of the tokens per scenario — a cost guardrail); the dialog track now runs Group A only (memory-off probes in independent sessions cannot succeed, so the control carries no information — retired). Workflow-track environment: DeepSeek official `deepseek-v4-flash` (reasoning effort high), judge `glm-5.3`, plugin 0.8.3, Windows; taxonomy adapted from [LongMemEval](https://github.com/xiaowu0162/longmemeval) / [LoCoMo](https://snap-research.github.io/locomo/) / [AMB](https://github.com/vectorize-io/agent-memory-benchmark).
156
+ Screenshots show what the plugin looks like — this section answers "**what does enabling it actually buy you?**" with measured numbers from an **automated benchmark** ([`bench/`](./bench/), one command to reproduce). Method: the same scenario bank with verbatim-identical inputs runs in **Group A (memory on)** with 3 merged repetitions and **Group B (memory off)** with 1 repetition (a memory-off long task burns multiples of the tokens per scenario — a cost guardrail); the dialog track now runs Group A only (memory-off probes in independent sessions cannot succeed, so the control carries no information — retired). Dialog-track environment: DeepSeek official `deepseek-v4-flash`, plugin 0.8.5 (judge same-source as tested; every answer archived for manual audit), Windows; taxonomy adapted from [LongMemEval](https://github.com/xiaowu0162/longmemeval) / [LoCoMo](https://snap-research.github.io/locomo/) / [AMB](https://github.com/vectorize-io/agent-memory-benchmark), with the extended probe types and lifecycle track informed by [MemoryAgentBench](https://arxiv.org/abs/2507.05257) / [GoodAI LTM](https://github.com/GoodAI/goodai-ltm-benchmark) / BEAM.
148
157
 
149
- ### Dialog track (15 scenarios × 6 probe types × 3 reps = 270 questions): does it remember correctly
158
+ > The dialog track below is the **fresh 0.8.5 baseline** (fixed plugin + corrected judging criteria); the workflow-track numbers remain the archived 0.8.3 run (the bank has since grown to 8 scenarios with a prospective-memory addition — re-run pending).
150
159
 
151
- > Archived 0.8.0 baseline (Group A data; the dialog-track B arm has since been retired Group A only).
160
+ ### Dialog track (20 scenarios × 10 probe types × 3 reps = 420 questions): does it remember correctly
161
+
162
+ > 0.8.5 baseline (Group A data; the dialog-track B arm is retired — Group A only).
152
163
 
153
164
  <p align="center">
154
165
  <img src="./assets/readme/bench-dialog.svg" width="100%"
155
- alt="DSH-MemBench dialog track accuracy chart (Group A, memory on): overall accuracy 92.6% (250/270); per probe type, 45 each — extraction 45/45, multi-hop 45/45, temporal 43/45, knowledge updates 31/45, scene recall 41/45, abstention 45/45 with 0 fabricated; the dialog-track B arm is retired (memory-off probes in independent sessions cannot succeed)">
166
+ alt="DSH-MemBench dialog track accuracy chart (Group A, memory on): overall accuracy 95.2% (400/420); six core probe types, 60 questions each — extraction 58/60, multi-hop 60/60, temporal 56/60, updates 55/60, scene recall 52/60, abstention 60/60 with 0 fabricated; four extended probe types, 15 each accretive completion 15/15, update chains 15/15, event ordering 14/15, paraphrase 15/15">
156
167
  </p>
157
168
 
158
- **Dual-channel recall** (Group A): passive injection hit rate **75.1%** (the answer's key points appear in the recall injection, 169/225); most of the rest the model recovered by **actively calling the memory tools** — 84 questions with active queries, **60 rescued by tools**. The end-to-end 92.6% is the composite of both channels plus model utilization. With the memory store accumulating across scenarios for the whole run, 144 probe injections carried other scenarios' memories (honestly counted) — yet overall accuracy held at 92.6%: interference resistance under a growing store, measured.
169
+ **Dual-channel recall** (Group A): passive injection hit rate **78.1%** (the answer's key points appear in the recall injection, 281/360); most of the rest the model recovered by **actively calling the memory tools** — 106 questions with active queries, **75 rescued by tools**. The end-to-end 95.2% is the composite of both channels plus model utilization. With the memory store accumulating across scenarios for the whole run, 295 probe injections carried other scenarios' memories (honestly counted) — yet accuracy actually *rose* from 92.8% (early, small store) to 97.7% (late, largest store), and offline flooding with 600 extra synthetic records moved retrieval recall@5 by only −2.8pp: interference resistance under a growing store, measured.
170
+
171
+ **Layered weaknesses**: offline retrieval metrics (recall@5, controlled replay) total 73.3%, with event ordering at 0% and scene recall at 50% — end-to-end still 93%+ thanks to model robustness over adjacent injected memories. **Efficiency triangle** (the cost of memory): injections add no latency (injected turns respond 210ms *faster* on average), recall text is ~10.3% of per-turn input, and the whole distillation pipeline costs ≈2727 input / 240 output tokens per captured message (1172 calls, 0 failures).
159
172
 
160
- ### Workflow track (7 scenarios · Group A ×3 / Group B ×1, real tool sandbox): does it do it right, and cheaper
173
+ ### Workflow track (archived 0.8.3 · 7-scenario edition · Group A ×3 / Group B ×1, real tool sandbox): does it do it right, and cheaper
161
174
 
162
175
  <p align="center">
163
176
  <img src="./assets/readme/bench-workflow.svg" width="100%"
@@ -173,14 +186,17 @@ Screenshots show what the plugin looks like — this section answers "**what doe
173
186
  ```bash
174
187
  node bench/harness/run.mjs --arm A --repeats 3 --provider deepseek-official --model deepseek-v4-flash # dialog track (Group A only)
175
188
  node bench/harness/run.mjs --track workflow --arm AB --repeats 3 ... # workflow track (A/B arms in parallel)
189
+ node bench/harness/run.mjs --track lifecycle --arm A ... # lifecycle track (gating/off/rebuild/forget)
176
190
  node bench/harness/report.mjs --latest [dialog|workflow] # aggregate report
191
+ node bench/harness/retrieval-metrics.mjs <runDir> --flood 200,600 # retrieval metrics + flooding curve
177
192
  ```
178
193
 
179
- - Scoring: programmatic `contains-all` plus an LLM judge against key points (every answer and verdict is preserved in `result.json` for human audit); workflow completion is verified programmatically from produced files and their contents (four check kinds: positive / forbidden-word / must-not-exist / exists);
194
+ - Scoring: programmatic `contains-all` plus an LLM judge against key points (every answer and verdict is preserved in `result.json` for human audit); for stale-bearing probes (updates/update-chains/forget) an old value only fails when stated *as the current answer*, and abstention probes allow citing real adjacent facts while denying the asked point; workflow completion is verified programmatically from produced files and their contents (four check kinds: positive / forbidden-word / must-not-exist / exists);
195
+ - Metric surface: beyond the per-type accuracy table (6 core + 4 extended types), reports automatically include **offline retrieval metrics** (recall@5 / injection precision / stale leakage), the **efficiency triangle** (injection latency differential / injection share / distillation accounting per captured message), **scale-position analysis** (accuracy & contamination vs store growth), and the lifecycle-track section (family-gating matrix / off-mode dual assertions / rebuild fidelity / forget requests);
180
196
  - Live progress: running the benchmark auto-starts a local progress panel and opens the browser (`--no-panel` to disable) — per-arm scenario/phase/message-level progress, heartbeat & activity freshness (distinguishes "stuck" from "process died"), and cumulative cost as it accrues;
181
- - Metrics come from provider-reported usage (input with cache-hit split) and session-event folding; the steady-state cache rate excludes each session's first request (archived 0.8.0 baseline: A 88.7% vs B 85.4% — memory injection does not hurt caching);
182
- - Regression use: run before/after a plugin change and diff with `compare.mjs` (environment header check including git SHA + Group-B control-drift warning);
183
- - Limitations (stated honestly): single machine; Group A ×3 merged, Group B ×1 (cost guardrail — noisier); judge vs tested model: same model in the archived dialog baseline, heterogeneous in the new workflow run (glm-5.3 judging v4-flash); the scenario bank is author-built (biased toward memory-advantage scenarios — reproduce it yourself); sandbox-file affordances partially leak procedures (Group B can reverse-engineer by reading scripts — discrimination limits honestly noted); dual-tier tool audit (strict violation voids the scenario / loose heuristic flags only), with 0 violations measured on both sides.
197
+ - Metrics come from provider-reported usage (input with cache-hit split) and session-event folding; the steady-state cache rate excludes each session's first request (0.8.5 baseline: 89.1% — memory injection does not hurt caching);
198
+ - Regression use: run before/after a plugin change and diff with `compare.mjs` (environment header check including git SHA + Group-B control-drift warning + retrieval-metric comparison);
199
+ - Limitations (stated honestly): single machine; Group A ×3 merged, Group B ×1 (cost guardrail — noisier); judge vs tested model: same model in the 0.8.5 dialog baseline, heterogeneous in the archived workflow run (glm-5.3 judging v4-flash); the scenario bank is author-built (biased toward memory-advantage scenarios — reproduce it yourself); sandbox-file affordances partially leak procedures (Group B can reverse-engineer by reading scripts — discrimination limits honestly noted); dual-tier tool audit (strict violation voids the scenario / loose heuristic flags only), with 0 violations measured on both sides.
184
200
 
185
201
  Full reports and per-question data: [`bench/baseline/`](./bench/baseline/).
186
202
 
@@ -244,6 +260,7 @@ the bundle layer appends and causes `duplicate loader entry id` startup failure)
244
260
  | `llm.maxInputChars` | `700000` | Input character budget per distillation call (over-budget L1 inputs are chunked automatically); runtime-adjustable in Settings → distillation parameters → input budget (empty/0 = follow this value) |
245
261
  | `llm.timeoutMs` | `120000` | Per-call distillation timeout (ms) |
246
262
  | `tools` | `true` | Whether to register model-callable memory tools |
263
+ | `benchControl` | `false` | Register the in-process bench control service (rebuild trigger / session-mode setting / distillation usage snapshot — used by the benchmark's lifecycle track). Off by default — zero surface in production deployments; do not enable casually |
247
264
 
248
265
  ## Storage Layout
249
266
 
package/README.md CHANGED
@@ -20,7 +20,7 @@
20
20
  需要 Node ≥ 22.16。两种调用方式任选(`npx` 前缀可替换下面任何 `dsh` 命令):
21
21
 
22
22
  ```bash
23
- # 方式一:npx 直接跑官方 CLI(无需预装 dsh;可 pin 版本,如 dsh-layered-memory@0.8.2
23
+ # 方式一:npx 直接跑官方 CLI(无需预装 dsh;可 pin 版本,如 dsh-layered-memory@0.8.4
24
24
  npx -y @deepseek-ai/dsh plugin --profile web add dsh-layered-memory
25
25
 
26
26
  # 方式二:已装 dsh CLI(dsh 是 pnpm 转发器,未装 pnpm 时先 npm i -g pnpm)
@@ -110,6 +110,11 @@ npx tsc src/smoke.ts --outDir dist-smoke --module nodenext --moduleResolution no
110
110
  </p>
111
111
 
112
112
  - **控件**:输入栏内、模式选择器右侧的 pill(`记忆·自动`),点击在上方浮出档位滑块深浅主题自适应;
113
+ - 悬浮板下半部是**会话信息区**:召回命中(命中/检索轮次与累计条数)、攒批进度
114
+ (本会话切片 x/生效阈值;关闭档显示挂起切片数)、本会话产出记忆条数、会话消息数,
115
+ 外加异常状态行(存储降级 / 向量检索不可用)与全局摘要(待蒸馏条数、上次蒸馏时间);
116
+ 数据走 `dsh-memory/session-stats` 端点(纯内存注册表 + 索引 COUNT,零文件 I/O),
117
+ 打开期间自适应轮询(忙 2s / 静 5s),关闭即停;
113
118
  - 每会话的选择按 sessionId 持久化到 `session-modes.json`,重启/恢复会话不丢;
114
119
  与全局开关叠加(全局是总闸);L2/L3 完全分类,分类内容不渗透。
115
120
 
@@ -124,20 +129,24 @@ npx tsc src/smoke.ts --outDir dist-smoke --module nodenext --moduleResolution no
124
129
 
125
130
  ## 实测对比(DSH-MemBench:自动化基准)
126
131
 
127
- 图文回答"长什么样",这一节用**自动化基准**的实测数字回答"**开了到底有什么用**"([`bench/`](./bench/),一条命令可复现)。方法:同场景库、逐字相同输入,**A 组(记忆开)跑 3 次取合并值,B 组(记忆关)跑 1 次**(无记忆的长任务每场景要吞数倍 token,成本护栏);对话赛道只跑 A 组(B 组会话独立无记忆必然失败,对照无信息量,已下线)。工作流赛道环境:DeepSeek 官方 `deepseek-v4-flash`(思考档 high)、判卷 `glm-5.3`、插件 0.8.3、Windows;题型设计借鉴 [LongMemEval](https://github.com/xiaowu0162/longmemeval) / [LoCoMo](https://snap-research.github.io/locomo/) / [AMB](https://github.com/vectorize-io/agent-memory-benchmark)。
132
+ 图文回答"长什么样",这一节用**自动化基准**的实测数字回答"**开了到底有什么用**"([`bench/`](./bench/),一条命令可复现)。方法:同场景库、逐字相同输入,**A 组(记忆开)跑 3 次取合并值,B 组(记忆关)跑 1 次**(无记忆的长任务每场景要吞数倍 token,成本护栏);对话赛道只跑 A 组(B 组会话独立无记忆必然失败,对照无信息量,已下线)。对话赛道环境:DeepSeek 官方 `deepseek-v4-flash`、插件 0.8.5(判卷与被测同源,答案原文全部留痕可人工复核)、Windows;题型设计借鉴 [LongMemEval](https://github.com/xiaowu0162/longmemeval) / [LoCoMo](https://snap-research.github.io/locomo/) / [AMB](https://github.com/vectorize-io/agent-memory-benchmark),扩展题型与生命周期赛道参照 [MemoryAgentBench](https://arxiv.org/abs/2507.05257) / [GoodAI LTM](https://github.com/GoodAI/goodai-ltm-benchmark) / BEAM
128
133
 
129
- ### 对话赛道(15 场景 × 6 题型 × 3 = 270 题):答得准吗
134
+ > 对话赛道为 **0.8.5 新基线**(修复版插件 + 修正后的判卷口径);工作流赛道数字仍为 0.8.3 存档(0.8.5 起场景库扩至 8 个,新增前瞻记忆场景,重跑待做)。
130
135
 
131
- > 0.8.0 留档基线(A 组数据;此后对话赛道 B 组下线,只跑 A 组)。
136
+ ### 对话赛道(20 场景 × 10 题型 × 3 次 = 420 题):答得准吗
137
+
138
+ > 0.8.5 基线(A 组数据;对话赛道 B 组已下线,只跑 A 组)。
132
139
 
133
140
  <p align="center">
134
141
  <img src="./assets/readme/bench-dialog.svg" width="100%"
135
- alt="DSH-MemBench 对话赛道准确率图(A 组·记忆开):总准确率 92.6%(250/270);分题型各 45 题——抽取 45/45、多跳 45/45、时序 43/45、知识更新 31/45、场景回忆 41/45、拒答 45/45 且 0 编造;对话赛道 B 组已下线(会话独立无记忆必然失败)">
142
+ alt="DSH-MemBench 对话赛道准确率图(A 组·记忆开):总准确率 95.2%(400/420);核心六题型各 60 题——抽取 58/60、多跳 60/60、时序 56/60、更新 55/60、场景回忆 52/60、拒答 60/60 且 0 编造;扩展四题型各 15 题——增量积累 15/15、连锁更新 15/15、事件排序 14/15、同义改写 15/15">
136
143
  </p>
137
144
 
138
- **召回双通道**(A 组):被动注入召回率 **75.1%**(该题要点出现在召回注入中,169/225),其余多数由模型**主动调用记忆工具**查回——84 题主动查询、**60 题靠工具兜底答对**;端到端 92.6% 是两通道 + 模型利用的合成结果。记忆库跨场景全程累积下,探针召回注入混入其他场景记忆 144 次(已如实计数),总准确率仍稳在 92.6%——抗干扰能力经受住了膨胀记忆库的考验。
145
+ **召回双通道**(A 组):被动注入召回率 **78.1%**(该题要点出现在召回注入中,281/360),其余多数由模型**主动调用记忆工具**查回——106 题主动查询、**75 题靠工具兜底答对**;端到端 95.2% 是两通道 + 模型利用的合成结果。记忆库跨场景全程累积下,探针召回注入混入其他场景记忆 295 次(已如实计数),总准确率反而前段 92.8% → 后段 97.7%——抗干扰能力经受住了膨胀记忆库的考验(离线灌水再灌 600 条合成噪声,检索层 recall@5 也只降 2.8pp)。
146
+
147
+ **分层看短板**:检索层离线指标(recall@5 受控复现)总 73.3%,其中事件排序 0%、场景回忆 50%——端到端仍 93%+ 靠的是注入邻近记忆后模型的鲁棒性;**效率三角**(记忆的开销):注入非但不加延迟(注入轮响应比无注入轮平均快 210ms)、注入占每轮输入约 10.3%,蒸馏全链路摊到每条捕获消息 ≈2727 输入 / 240 输出 token(1172 次调用 0 失败)。
139
148
 
140
- ### 工作流赛道(7 场景 · A 组 3 次 / B 组 1 次,真实工具沙箱):做得对、做得省吗
149
+ ### 工作流赛道(0.8.3 存档 · 7 场景版 · A 组 3 次 / B 组 1 次,真实工具沙箱):做得对、做得省吗
141
150
 
142
151
  <p align="center">
143
152
  <img src="./assets/readme/bench-workflow.svg" width="100%"
@@ -153,14 +162,17 @@ npx tsc src/smoke.ts --outDir dist-smoke --module nodenext --moduleResolution no
153
162
  ```bash
154
163
  node bench/harness/run.mjs --arm A --repeats 3 --provider deepseek-official --model deepseek-v4-flash # 对话赛道(只跑 A 组)
155
164
  node bench/harness/run.mjs --track workflow --arm AB --repeats 3 ... # 工作流赛道(A/B 双组并行)
165
+ node bench/harness/run.mjs --track lifecycle --arm A ... # 生命周期赛道(门控/off/rebuild/遗忘)
156
166
  node bench/harness/report.mjs --latest [dialog|workflow] # 汇总报告
167
+ node bench/harness/retrieval-metrics.mjs <runDir> --flood 200,600 # 检索层指标 + 灌水曲线
157
168
  ```
158
169
 
159
- - 判分:`contains-all` 程序判 + 判卷模型按要点判(答案原文与判分理由全部留痕 `result.json` 可人工复核);工作流完成度为产物文件 + 关键内容程序化校验(四型判据:正检查/禁词/产物缺席/存在性);
170
+ - 判分:`contains-all` 程序判 + 判卷模型按要点判(答案原文与判分理由全部留痕 `result.json` 可人工复核);带 stale 的题(更新/连锁/遗忘)"旧值当作现状陈述"才判负、拒答题允许引用真实背景解释"不知道被问点";工作流完成度为产物文件 + 关键内容程序化校验(四型判据:正检查/禁词/产物缺席/存在性);
171
+ - 指标面:准确率总表(6 核心 + 4 扩展题型)之外,自动产出**检索层离线指标**(recall@5 / 注入精度 / 作废泄漏)、**效率三角**(注入开销差分 / 注入占比 / 蒸馏记账摊到每消息)、**规模位置分析**(库容膨胀下的准确率/污染)与生命周期赛道专属节(分族门控矩阵 / off 双断言 / rebuild 保真 / 遗忘);
160
172
  - 实时进度:跑基准时自动拉起本地进度面板并打开浏览器(`--no-panel` 关闭)——A/B 双臂场景/阶段/消息粒度进度、心跳与活动新鲜度(直判"卡住 vs 进程挂了")、累计成本随跑随涨;
161
- - 指标全部来自供应商上报 usage(输入含缓存命中拆分)与会话事件折叠;稳态缓存率剔除每会话首请求(0.8.0 留档基线:A 88.7% vs B 85.4%——记忆注入不伤缓存);
162
- - 回归用途:改插件前后各跑一遍,`compare.mjs` 出对比表(环境头校验含 gitSha + B 组对照组漂移告警);
163
- - 局限(诚实声明):单机;A 组 ×3 合并、B 组 ×1(成本护栏,噪声更大);判卷与被测模型:对话留档基线同源、工作流新跑为异构(glm-5.3 判 v4-flash);作者自建场景库(倾向记忆优势场景,欢迎自行复现);沙箱文件的可供性会部分泄露流程(B 组可读脚本逆向,判别力受限处已如实标注);工具审计双档(严格违规判负/宽松提示),实测双方 0 违规。
173
+ - 指标全部来自供应商上报 usage(输入含缓存命中拆分)与会话事件折叠;稳态缓存率剔除每会话首请求(0.8.5 基线:89.1%——记忆注入不伤缓存);
174
+ - 回归用途:改插件前后各跑一遍,`compare.mjs` 出对比表(环境头校验含 gitSha + B 组对照组漂移告警 + 检索层指标对比);
175
+ - 局限(诚实声明):单机;A 组 ×3 合并、B 组 ×1(成本护栏,噪声更大);判卷与被测模型:对话 0.8.5 基线同源、工作流存档跑为异构(glm-5.3 判 v4-flash);作者自建场景库(倾向记忆优势场景,欢迎自行复现);沙箱文件的可供性会部分泄露流程(B 组可读脚本逆向,判别力受限处已如实标注);工具审计双档(严格违规判负/宽松提示),实测双方 0 违规。
164
176
 
165
177
  完整报告与逐题数据:[`bench/baseline/`](./bench/baseline/)。
166
178
 
@@ -261,6 +273,7 @@ ONNX 量化 **CPU 推理**——无需 API Key,数据不出本机)。本地
261
273
  | `llm.maxInputChars` | `700000` | 单次蒸馏输入字符预算(超限的 L1 输入自动分块抽取);运行时可在设置页 → 蒸馏参数 → 输入预算调整(留空/0 = 跟随本值) |
262
274
  | `llm.timeoutMs` | `120000` | 单次蒸馏调用超时(ms) |
263
275
  | `tools` | `true` | 是否注册模型可调用的记忆工具 |
276
+ | `benchControl` | `false` | 注册 bench 控制服务(进程内 rebuild 触发/会话档位设置/蒸馏用量快照,供基准 lifecycle 赛道)。默认关——生产部署零表面积,勿随意开启 |
264
277
 
265
278
  ## 日志与故障排查
266
279
 
Binary file
Binary file
@@ -2,75 +2,69 @@
2
2
  width="1200" height="630" viewBox="0 0 1200 630"
3
3
  role="img" aria-labelledby="benchDialogTitle benchDialogDesc">
4
4
  <title id="benchDialogTitle">DSH-MemBench 对话赛道:A 组(记忆开)准确率</title>
5
- <desc id="benchDialogDesc">A 组(记忆开)总准确率 92.6%(250/27015 场景 × 6 题型 × 3 次)。分题型(每题型 45 题):抽取 45/45;多跳 45/45;时序 43/45;知识更新 31/45;场景回忆 41/45;拒答 45/45、0 编造。对话赛道 B 组已下线(会话独立、无记忆必然失败,对照无信息量)。</desc>
5
+ <desc id="benchDialogDesc">A 组(记忆开)总准确率 95.2%(400/42020 场景 × 10 题型 × 3 次)。核心六题型各 60 题:抽取 58/60、多跳 60/60、时序 56/60、更新 55/60、场景回忆 52/60、拒答 60/60 且 0 编造;扩展四题型各 15 题:增量积累 15/15、连锁更新 15/15、事件排序 14/15、同义改写 15/15。对话赛道 B 组已下线(会话独立、无记忆必然失败,对照无信息量)。</desc>
6
6
 
7
7
  <rect width="1200" height="630" rx="26" fill="#0D1526"/>
8
8
 
9
9
  <g font-family="-apple-system,BlinkMacSystemFont,'Segoe UI','PingFang SC','Microsoft YaHei',sans-serif">
10
10
 
11
- <!-- ── 标题与图例 ── -->
12
11
  <text x="64" y="66" font-size="26" font-weight="700" fill="#EFF3FA">对话赛道:记忆开 · 准确率</text>
13
- <text x="64" y="94" font-size="18" fill="#8296B3">15 场景 × 6 题型 × 3 次 = 270 题(每题型 45 题)· 条长 = 答对率,数值 = 答对题数</text>
12
+ <text x="64" y="94" font-size="18" fill="#8296B3">20 场景 × 10 题型 × 3 次 = 420 题(核心六题型各 60 题、扩展四题型各 15 题)· 条长 = 答对率,数值 = 答对题数</text>
14
13
 
15
14
  <rect x="842" y="50" width="18" height="18" rx="5" fill="#FFD15D"/>
16
- <text x="868" y="65" font-size="18" fill="#C2CDDE">A 组 · 记忆开</text>
15
+ <text x="868" y="65" font-size="17" fill="#C2CDDE">核心六题型</text>
16
+ <rect x="966" y="50" width="18" height="18" rx="5" fill="#6FB1FF"/>
17
+ <text x="992" y="65" font-size="17" fill="#C2CDDE">扩展四题型</text>
17
18
 
18
- <!-- ── 网格线(0/25/50/75/100%)── -->
19
19
  <g stroke="#1C2A47" stroke-width="1">
20
- <line x1="260" y1="122" x2="260" y2="530"/>
21
- <line x1="435" y1="122" x2="435" y2="530"/>
22
- <line x1="610" y1="122" x2="610" y2="530"/>
23
- <line x1="785" y1="122" x2="785" y2="530"/>
24
- <line x1="960" y1="122" x2="960" y2="530"/>
20
+ <line x1="260" y1="120" x2="260" y2="525"/>
21
+ <line x1="435" y1="120" x2="435" y2="525"/>
22
+ <line x1="610" y1="120" x2="610" y2="525"/>
23
+ <line x1="785" y1="120" x2="785" y2="525"/>
24
+ <line x1="960" y1="120" x2="960" y2="525"/>
25
25
  </g>
26
26
  <g font-size="17" fill="#8296B3" text-anchor="middle">
27
- <text x="260" y="558">0%</text>
28
- <text x="435" y="558">25%</text>
29
- <text x="610" y="558">50%</text>
30
- <text x="785" y="558">75%</text>
31
- <text x="960" y="558">100%</text>
27
+ <text x="260" y="552">0%</text>
28
+ <text x="435" y="552">25%</text>
29
+ <text x="610" y="552">50%</text>
30
+ <text x="785" y="552">75%</text>
31
+ <text x="960" y="552">100%</text>
32
32
  </g>
33
33
 
34
- <!-- ── 总准确率(强调行)── -->
35
- <text x="240" y="169" font-size="21" font-weight="700" fill="#EFF3FA" text-anchor="end">总准确率</text>
36
- <rect x="260" y="148" width="648" height="26" rx="6" fill="#FFD15D"/>
37
- <text x="922" y="169" font-size="22" font-weight="700" fill="#FFD15D">92.6%(250/270)</text>
34
+ <text x="240" y="149" font-size="18" font-weight="700" fill="#EFF3FA" text-anchor="end">总准确率</text>
35
+ <rect x="260" y="130" width="667" height="25" rx="6" fill="#FFD15D"/>
36
+ <text x="939" y="149" font-size="17" font-weight="600" fill="#FFD15D">400/420(95.2%)</text>
37
+ <text x="240" y="184" font-size="18" font-weight="400" fill="#C2CDDE" text-anchor="end">抽取</text>
38
+ <rect x="260" y="165" width="677" height="25" rx="6" fill="#FFD15D"/>
39
+ <text x="949" y="184" font-size="17" font-weight="600" fill="#FFD15D">58/60(96.7%)</text>
40
+ <text x="240" y="219" font-size="18" font-weight="400" fill="#C2CDDE" text-anchor="end">多跳</text>
41
+ <rect x="260" y="200" width="700" height="25" rx="6" fill="#FFD15D"/>
42
+ <text x="972" y="219" font-size="17" font-weight="600" fill="#FFD15D">60/60(100.0%)</text>
43
+ <text x="240" y="254" font-size="18" font-weight="400" fill="#C2CDDE" text-anchor="end">时序</text>
44
+ <rect x="260" y="235" width="653" height="25" rx="6" fill="#FFD15D"/>
45
+ <text x="925" y="254" font-size="17" font-weight="600" fill="#FFD15D">56/60(93.3%)</text>
46
+ <text x="240" y="289" font-size="18" font-weight="400" fill="#C2CDDE" text-anchor="end">更新</text>
47
+ <rect x="260" y="270" width="642" height="25" rx="6" fill="#FFD15D"/>
48
+ <text x="914" y="289" font-size="17" font-weight="600" fill="#FFD15D">55/60(91.7%)</text>
49
+ <text x="240" y="324" font-size="18" font-weight="400" fill="#C2CDDE" text-anchor="end">场景回忆</text>
50
+ <rect x="260" y="305" width="607" height="25" rx="6" fill="#FFD15D"/>
51
+ <text x="879" y="324" font-size="17" font-weight="600" fill="#FFD15D">52/60(86.7%)</text>
52
+ <text x="240" y="359" font-size="18" font-weight="400" fill="#C2CDDE" text-anchor="end">拒答(0 编造)</text>
53
+ <rect x="260" y="340" width="700" height="25" rx="6" fill="#FFD15D"/>
54
+ <text x="972" y="359" font-size="17" font-weight="600" fill="#FFD15D">60/60(100.0%)</text>
55
+ <text x="240" y="394" font-size="18" font-weight="400" fill="#C2CDDE" text-anchor="end">增量积累</text>
56
+ <rect x="260" y="375" width="700" height="25" rx="6" fill="#6FB1FF"/>
57
+ <text x="972" y="394" font-size="17" font-weight="600" fill="#6FB1FF">15/15(100.0%)</text>
58
+ <text x="240" y="429" font-size="18" font-weight="400" fill="#C2CDDE" text-anchor="end">连锁更新</text>
59
+ <rect x="260" y="410" width="700" height="25" rx="6" fill="#6FB1FF"/>
60
+ <text x="972" y="429" font-size="17" font-weight="600" fill="#6FB1FF">15/15(100.0%)</text>
61
+ <text x="240" y="464" font-size="18" font-weight="400" fill="#C2CDDE" text-anchor="end">事件排序</text>
62
+ <rect x="260" y="445" width="653" height="25" rx="6" fill="#6FB1FF"/>
63
+ <text x="925" y="464" font-size="17" font-weight="600" fill="#6FB1FF">14/15(93.3%)</text>
64
+ <text x="240" y="499" font-size="18" font-weight="400" fill="#C2CDDE" text-anchor="end">同义改写</text>
65
+ <rect x="260" y="480" width="700" height="25" rx="6" fill="#6FB1FF"/>
66
+ <text x="972" y="499" font-size="17" font-weight="600" fill="#6FB1FF">15/15(100.0%)</text>
38
67
 
39
- <line x1="64" y1="200" x2="1136" y2="200" stroke="#263A5C" stroke-width="1.5"/>
40
-
41
- <!-- ── 分题型行 ── -->
42
- <!-- 抽取 -->
43
- <text x="240" y="249" font-size="19" fill="#C2CDDE" text-anchor="end">抽取</text>
44
- <rect x="260" y="234" width="700" height="18" rx="5" fill="#FFD15D"/>
45
- <text x="974" y="249" font-size="18" font-weight="700" fill="#FFD15D" font-family="ui-monospace,SFMono-Regular,Menlo,Consolas,monospace">45/45</text>
46
-
47
- <!-- 多跳 -->
48
- <text x="240" y="303" font-size="19" fill="#C2CDDE" text-anchor="end">多跳</text>
49
- <rect x="260" y="288" width="700" height="18" rx="5" fill="#FFD15D"/>
50
- <text x="974" y="303" font-size="18" font-weight="700" fill="#FFD15D" font-family="ui-monospace,SFMono-Regular,Menlo,Consolas,monospace">45/45</text>
51
-
52
- <!-- 时序 -->
53
- <text x="240" y="357" font-size="19" fill="#C2CDDE" text-anchor="end">时序</text>
54
- <rect x="260" y="342" width="669" height="18" rx="5" fill="#FFD15D"/>
55
- <text x="943" y="357" font-size="18" font-weight="700" fill="#FFD15D" font-family="ui-monospace,SFMono-Regular,Menlo,Consolas,monospace">43/45</text>
56
-
57
- <!-- 知识更新 -->
58
- <text x="240" y="411" font-size="19" fill="#C2CDDE" text-anchor="end">知识更新 *</text>
59
- <rect x="260" y="396" width="482" height="18" rx="5" fill="#FFD15D"/>
60
- <text x="756" y="411" font-size="18" font-weight="700" fill="#FFD15D" font-family="ui-monospace,SFMono-Regular,Menlo,Consolas,monospace">31/45</text>
61
-
62
- <!-- 场景回忆 -->
63
- <text x="240" y="465" font-size="19" fill="#C2CDDE" text-anchor="end">场景回忆</text>
64
- <rect x="260" y="450" width="638" height="18" rx="5" fill="#FFD15D"/>
65
- <text x="912" y="465" font-size="18" font-weight="700" fill="#FFD15D" font-family="ui-monospace,SFMono-Regular,Menlo,Consolas,monospace">41/45</text>
66
-
67
- <!-- 拒答 -->
68
- <text x="240" y="519" font-size="19" fill="#C2CDDE" text-anchor="end">拒答 *</text>
69
- <rect x="260" y="504" width="700" height="18" rx="5" fill="#FFD15D"/>
70
- <text x="974" y="519" font-size="18" font-weight="700" fill="#FFD15D" font-family="ui-monospace,SFMono-Regular,Menlo,Consolas,monospace">45/45</text>
71
-
72
- <!-- ── 脚注 ── -->
73
- <text x="64" y="600" font-size="16" fill="#8296B3">* 知识更新:改口后仍答旧值记 0 分(直接考核 L1 去重更新);拒答:没发生过的事编造即 0 分(0 编造)。</text>
74
- <text x="64" y="622" font-size="16" fill="#8296B3">对话赛道 B 组已下线:Harness 会话彼此独立,无记忆的探针必然失败,对照无信息量。</text>
68
+ <text x="64" y="600" font-size="16" fill="#8296B3">DSH-MemBench 0.8.5 基线 · DeepSeek v4-flash · 3 次重复合并 · 场景库跨场景累积(越靠后的场景记忆干扰越大)</text>
75
69
  </g>
76
70
  </svg>
@@ -0,0 +1,35 @@
1
+ /**
2
+ * bench 控制服务(config `benchControl` 门控,默认关):为同进程的基准驱动插件
3
+ * (dsh-bench-runner 的 lifecycle 赛道)提供进程内控制面。
4
+ *
5
+ * 为什么不走 RPC:宿主侧 connection.rpc 只有 handle/intercept、没有 call(),
6
+ * 基准驱动包在 dsh 宿主进程内无法调用插件的 loopback RPC 端点;cordis 服务
7
+ * (ctx.provide / ctx.get)是唯一干净的进程内通道。生产部署不开启该配置,
8
+ * 服务不注册、零表面积;即便开启,暴露的也只是既有公开 API 的薄包装,
9
+ * 不引入新逻辑:
10
+ * - RebuildController.start()/getStatus()(重建触发与状态轮询);
11
+ * - SessionModeStore.set()/get()(会话档位——capture/recall 每轮读 Map,
12
+ * agent 建好后、首条消息前设档即可生效,且走 onModeChange 回调的
13
+ * pending 落袋/挂起语义,不是裸改 Map)。
14
+ */
15
+ import type { Context } from '@deepseek-ai/cordis';
16
+ import type { MemoryLogger, MemoryMode } from './types.js';
17
+ import type { RebuildController, RebuildStatus } from './pipeline/rebuild.js';
18
+ import type { SessionModeStore } from './store/session-modes.js';
19
+ import type { DistillUsageSnapshot } from './llm-usage.js';
20
+ /** 服务名(消费方:bench/harness/dsh-bench-runner 的 lifecycle 赛道)。 */
21
+ export declare const BENCH_CONTROL_SERVICE = "dsh-memory-bench";
22
+ export interface BenchControlSurface {
23
+ /** 触发全量重建(从 L0 重导派生层);前置条件不满足时抛错(调用方捕获)。 */
24
+ rebuildStart(): RebuildStatus;
25
+ /** 重建状态快照(phase/running/recordsBuilt/…,轮询至 done/failed/cancelled)。 */
26
+ rebuildStatus(): RebuildStatus;
27
+ /** 设置会话档位(chat/work/off/auto);对全新会话应在首条消息前设置。 */
28
+ setSessionMode(sessionId: string, mode: MemoryMode): void;
29
+ /** 查询会话档位(未设过的会话返回部署默认档)。 */
30
+ getSessionMode(sessionId: string): MemoryMode;
31
+ /** 蒸馏用量快照(按层累计的调用数/输入字符/输出 token——「记忆开销」记账)。 */
32
+ getDistillUsage(): DistillUsageSnapshot;
33
+ }
34
+ /** 注册控制服务,返回注销函数(调用方在插件 dispose 时执行)。 */
35
+ export declare function registerBenchControl(ctx: Context, rebuild: RebuildController, modes: SessionModeStore, logger: MemoryLogger): () => void;
@@ -0,0 +1,16 @@
1
+ import { snapshotDistillUsage } from './llm-usage.js';
2
+ /** 服务名(消费方:bench/harness/dsh-bench-runner 的 lifecycle 赛道)。 */
3
+ export const BENCH_CONTROL_SERVICE = 'dsh-memory-bench';
4
+ /** 注册控制服务,返回注销函数(调用方在插件 dispose 时执行)。 */
5
+ export function registerBenchControl(ctx, rebuild, modes, logger) {
6
+ const surface = {
7
+ rebuildStart: () => rebuild.start(),
8
+ rebuildStatus: () => rebuild.getStatus(),
9
+ setSessionMode: (sessionId, mode) => modes.set(sessionId, mode),
10
+ getSessionMode: (sessionId) => modes.get(sessionId),
11
+ getDistillUsage: () => snapshotDistillUsage(),
12
+ };
13
+ const dispose = ctx.provide(BENCH_CONTROL_SERVICE, surface);
14
+ logger.info('[memory] bench 控制服务已提供(dsh-memory-bench,仅基准/调试部署)');
15
+ return dispose;
16
+ }
package/dist/client.js CHANGED
@@ -244,6 +244,23 @@ window.__ModuleLoader__.load({
244
244
  }
245
245
  }
246
246
 
247
+ /** 相对时间(悬浮卡摘要行用):刚刚 / N 分钟前 / N 小时前 / N 天前;无效返回 null。 */
248
+ function fmtAgo(iso) {
249
+ if (!iso) return null;
250
+ try {
251
+ var t = new Date(iso).getTime();
252
+ if (!t) return null;
253
+ var s = Math.floor((Date.now() - t) / 1000);
254
+ if (s < 0) s = 0;
255
+ if (s < 45) return "刚刚";
256
+ if (s < 3600) return Math.floor(s / 60) + " 分钟前";
257
+ if (s < 86400) return Math.floor(s / 3600) + " 小时前";
258
+ return Math.floor(s / 86400) + " 天前";
259
+ } catch (e) {
260
+ return null;
261
+ }
262
+ }
263
+
247
264
  // ── Switch 组件 ──
248
265
  function Switch(props) {
249
266
  var on = !!props.checked;
@@ -673,6 +690,151 @@ window.__ModuleLoader__.load({
673
690
  props.error
674
691
  ? react.createElement("div", { style: { fontSize: 11, color: "var(--dsh-mem-danger)", marginTop: 10, whiteSpace: "nowrap" } }, props.error)
675
692
  : null,
693
+ // 会话信息区(分隔线 + 2×2 指标 + 状态行):session-stats 热路径端点,
694
+ // 宿主不支持 / 数据缺失时整体不渲染(best-effort 增强,不占位)
695
+ props.rpc && props.sessionId
696
+ ? react.createElement(SessionInfoArea, { rpc: props.rpc, sessionId: props.sessionId })
697
+ : null,
698
+ ),
699
+ );
700
+ }
701
+
702
+ // ── 会话信息区:悬浮卡下半部(召回命中 / 攒批进度 / 会话产出 / 会话消息) ──
703
+ // 数据通道 dsh-memory/session-stats(宿主侧零文件 I/O:内存注册表 + 索引 COUNT,
704
+ // 见 slider-spec 数据策略节);自适应轮询:忙时(攒批/挂起/全局待蒸馏)2s,静默 5s,
705
+ // 浮层卸载即停(ModeSlider 只在 pill 展开期间挂载)。
706
+ function sinfoCell(val, label, title) {
707
+ return react.createElement(
708
+ "div",
709
+ { title: title || null },
710
+ react.createElement("div", { className: "dsh-mem-sinfo-val" }, val),
711
+ react.createElement("div", { className: "dsh-mem-sinfo-label" }, label),
712
+ );
713
+ }
714
+
715
+ function SessionInfoArea(props) {
716
+ var rpc = props.rpc;
717
+ var sessionId = props.sessionId;
718
+ // undefined=首帧加载中;null=宿主不支持(整体隐藏);对象=最新快照
719
+ var stState = react.useState(undefined);
720
+ var stats = stState[0];
721
+ var setStats = stState[1];
722
+ var busyRef = react.useRef(false);
723
+
724
+ react.useEffect(function () {
725
+ if (!rpc || !sessionId) return undefined;
726
+ var alive = true;
727
+ var timer = null;
728
+ var seq = 0;
729
+ var tick = function () {
730
+ var token = ++seq;
731
+ rpc("dsh-memory/session-stats", { sessionId: sessionId })
732
+ .then(function (r) {
733
+ if (!alive || token !== seq) return;
734
+ if (r && r.ok && r.value && r.value.supported === false) {
735
+ setStats(null); // 宿主无数据源:整体隐藏
736
+ } else if (r && r.ok && r.value) {
737
+ setStats(r.value);
738
+ var d = r.value.distill || {};
739
+ var g = r.value.global || {};
740
+ busyRef.current = (d.pendingSlice || 0) > 0 || (d.parkedSlices || 0) > 0 || (g.pendingTotal || 0) > 0;
741
+ }
742
+ // RPC 失败:保持旧快照(信息区不因瞬时错误闪没)
743
+ })
744
+ .catch(function () {})
745
+ .then(function () {
746
+ if (alive) timer = setTimeout(tick, busyRef.current ? 2000 : 5000);
747
+ });
748
+ };
749
+ tick();
750
+ return function () {
751
+ alive = false;
752
+ if (timer) clearTimeout(timer);
753
+ };
754
+ }, [rpc, sessionId]);
755
+
756
+ // 首帧占位骨架(防内容跳变);宿主不支持则整体隐藏
757
+ if (stats === null) return null;
758
+ if (stats === undefined) {
759
+ return react.createElement(
760
+ "div",
761
+ { className: "dsh-mem-sinfo" },
762
+ react.createElement(
763
+ "div",
764
+ { className: "dsh-mem-sinfo-grid" },
765
+ sinfoCell("…", "召回命中"),
766
+ sinfoCell("…", "攒批进度"),
767
+ sinfoCell("…", "本会话记忆"),
768
+ sinfoCell("…", "会话消息"),
769
+ ),
770
+ );
771
+ }
772
+
773
+ var rc = stats.recall || {};
774
+ var di = stats.distill || {};
775
+ var gl = stats.global || {};
776
+ var isOff = stats.mode === "off";
777
+
778
+ // 召回命中:口径是"注入统计"(命中轮次/检索轮次),停用时显示状态而非误导性 0/0
779
+ var rcVal;
780
+ var rcLabel;
781
+ var rcTitle;
782
+ if (rc.enabled === false) {
783
+ rcVal = "停用";
784
+ rcLabel = "召回命中";
785
+ rcTitle = "召回已停用(开关关闭 / 档位关闭 / 部署未启用)";
786
+ } else {
787
+ rcVal = (rc.hitTurns || 0) + "/" + (rc.injectedTurns || 0);
788
+ rcLabel = "召回命中 · " + (rc.totalHits || 0) + " 条";
789
+ rcTitle =
790
+ "最近一轮命中 " + (rc.lastHits || 0) + " 条,耗时 " + (rc.lastDurationMs || 0) + "ms" +
791
+ ((rc.timeouts || 0) > 0 ? ",超时跳过 " + rc.timeouts + " 次" : "");
792
+ }
793
+
794
+ // 攒批进度(x/生效阈值,含 warmup 爬坡);off 档显示挂起切片数
795
+ var dVal;
796
+ var dLabel;
797
+ var dTitle;
798
+ if (isOff) {
799
+ dVal = String(di.parkedSlices || 0);
800
+ dLabel = "挂起切片";
801
+ dTitle = "档位关闭:未蒸馏切片挂起,切回档位后继续";
802
+ } else {
803
+ dVal = (di.pendingSlice || 0) + "/" + (di.threshold != null ? di.threshold : "-");
804
+ dLabel = (di.parkedSlices || 0) > 0 ? "攒批 · 挂起 " + di.parkedSlices : "攒批进度";
805
+ dTitle = "达到阈值后自动蒸馏(阈值随使用渐进爬坡到稳态)";
806
+ }
807
+
808
+ var pTitle = di.lastDistillAt ? "最近蒸馏 " + fmtTime(di.lastDistillAt) : "本会话尚未蒸馏";
809
+ var warn = gl.degraded ? "⚠ 存储不可用,记忆功能已停用" : null;
810
+ var note = null;
811
+ if (!gl.degraded) {
812
+ if (stats.retrieval === "keyword" && !isOff) note = "检索降级:纯关键词(向量不可用)";
813
+ else if (stats.retrieval === "none") note = "检索不可用(FTS 与向量均失效)";
814
+ }
815
+ var ago = fmtAgo(gl.lastExtractAt);
816
+
817
+ return react.createElement(
818
+ "div",
819
+ { className: "dsh-mem-sinfo" },
820
+ warn
821
+ ? react.createElement("div", { className: "dsh-mem-sinfo-warn" }, warn)
822
+ : null,
823
+ react.createElement(
824
+ "div",
825
+ { className: "dsh-mem-sinfo-grid" },
826
+ sinfoCell(rcVal, rcLabel, rcTitle),
827
+ sinfoCell(dVal, dLabel, dTitle),
828
+ sinfoCell(String(di.producedRecords || 0), "本会话记忆", pTitle),
829
+ sinfoCell(stats.l0Count != null ? String(stats.l0Count) : "…", "会话消息"),
830
+ ),
831
+ note
832
+ ? react.createElement("div", { className: "dsh-mem-sinfo-note" }, note)
833
+ : null,
834
+ react.createElement(
835
+ "div",
836
+ { className: "dsh-mem-sinfo-sum" },
837
+ "待蒸馏 " + (gl.pendingTotal || 0) + " · 上次蒸馏 " + (ago || "尚未蒸馏"),
676
838
  ),
677
839
  );
678
840
  }
@@ -930,6 +1092,15 @@ window.__ModuleLoader__.load({
930
1092
  " box-shadow: 0 16px 48px rgba(0,0,0,0.24); }",
931
1093
  "body[data-ds-dark-theme] .dsh-mem-rb-modal { box-shadow: 0 16px 48px rgba(0,0,0,0.6); }",
932
1094
  ".dsh-mem-rb-muted { font-size: 12px; color: var(--dsh-mem-text-3); }",
1095
+ // ── 会话信息区(悬浮卡下半部):分隔线 + 2×2 指标 + 状态行;纯静态 DOM,
1096
+ // 不进粒子层 rAF 循环,轮询数据到达才触发本组件小树 re-render ──
1097
+ ".dsh-mem-sinfo { margin-top: 12px; padding-top: 10px; border-top: 1px solid var(--dsh-mem-border); }",
1098
+ ".dsh-mem-sinfo-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 8px 10px; }",
1099
+ ".dsh-mem-sinfo-val { font-size: 13px; font-weight: 600; color: var(--dsh-mem-text-1); line-height: 18px; font-variant-numeric: tabular-nums; }",
1100
+ ".dsh-mem-sinfo-label { font-size: 11px; color: var(--dsh-mem-text-3); line-height: 15px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }",
1101
+ ".dsh-mem-sinfo-warn { font-size: 11px; color: var(--dsh-mem-danger); line-height: 16px; margin-bottom: 6px; }",
1102
+ ".dsh-mem-sinfo-note { font-size: 11px; color: var(--dsh-mem-text-3); line-height: 16px; margin-top: 8px; }",
1103
+ ".dsh-mem-sinfo-sum { font-size: 11px; color: var(--dsh-mem-text-3); line-height: 16px; margin-top: 8px; }",
933
1104
  // reduced-motion 兜底放样式表末尾:同特异性下后置声明才能压过上面的组件类
934
1105
  "@media (prefers-reduced-motion: reduce) {",
935
1106
  " .dsh-mem-root, .dsh-mem-root *, .dsh-mem-btn, .dsh-mem-input, .dsh-mem-select, .dsh-mem-rb-fill, .dsh-mem-scene-chev, .dsh-mem-sel-chev { transition: none; }",
@@ -1066,6 +1237,8 @@ window.__ModuleLoader__.load({
1066
1237
  mode: mode || "auto",
1067
1238
  onCommit: commit,
1068
1239
  error: error,
1240
+ rpc: rpc,
1241
+ sessionId: sessionId,
1069
1242
  })
1070
1243
  : null,
1071
1244
  );
package/dist/config.d.ts CHANGED
@@ -113,6 +113,9 @@ export interface MemoryConfig {
113
113
  };
114
114
  /** 是否注册模型可调用的记忆工具。 */
115
115
  tools: boolean;
116
+ /** 注册 bench 控制服务(dsh-memory-bench,进程内 rebuild 触发面)。
117
+ * 仅供基准/调试部署(bench profile 的 lifecycle 赛道),默认关——生产零表面积。 */
118
+ benchControl: boolean;
116
119
  }
117
120
  export declare const memorySchema: Schema<Schemastery.ObjectS<{
118
121
  dataDir: Schema<string, string>;
@@ -219,6 +222,7 @@ export declare const memorySchema: Schema<Schemastery.ObjectS<{
219
222
  timeoutMs: Schema<number, number>;
220
223
  }>>;
221
224
  tools: Schema<boolean, boolean>;
225
+ benchControl: Schema<boolean, boolean>;
222
226
  }>, Schemastery.ObjectT<{
223
227
  dataDir: Schema<string, string>;
224
228
  family: Schema<"chat" | "work" | "auto", "chat" | "work" | "auto">;
@@ -324,5 +328,6 @@ export declare const memorySchema: Schema<Schemastery.ObjectS<{
324
328
  timeoutMs: Schema<number, number>;
325
329
  }>>;
326
330
  tools: Schema<boolean, boolean>;
331
+ benchControl: Schema<boolean, boolean>;
327
332
  }>>;
328
333
  export declare function resolveDataDir(cfg: MemoryConfig): string;
package/dist/config.js CHANGED
@@ -78,6 +78,7 @@ export const memorySchema = Schema.object({
78
78
  timeoutMs: Schema.number().min(1000).max(600_000).default(120_000),
79
79
  }),
80
80
  tools: Schema.boolean().default(true),
81
+ benchControl: Schema.boolean().default(false),
81
82
  });
82
83
  export function resolveDataDir(cfg) {
83
84
  if (cfg.dataDir)