dsh-engram 0.5.0 → 0.6.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.
@@ -0,0 +1,54 @@
1
+ ## dsh-engram v0.5.1
2
+
3
+ 补丁版:修复会话日志扫描对**版本化文件名**的硬编码(issue #8,所有会话扫描
4
+ 功能静默返回 0),并补齐记忆查看器的**数据导出/导入**与**替换/冲突可视化**
5
+ (issue #3 收尾)。
6
+
7
+ ### 修复
8
+
9
+ - **会话扫描识别 `session.v<N>.jsonl.zstd`**(#8)—— `lib/usage.js`、
10
+ `lib/boundary-prune.js`、`lib/session-cjk.js` 曾硬编码 `session.jsonl.zstd`;
11
+ 当前 DSH 把会话格式版本写进文件名(`0.1.x` → `session.v3.jsonl.zstd`,
12
+ `0.2.x` → `session.v4.jsonl.zstd`),`statSync` 抛 `ENOENT` 后被循环 `continue`
13
+ 静默吞掉,导致:
14
+
15
+ - 设置页 → 遥测恒为「样本不足(0 次)」,`GET /stats`、`GET /toolstats` 全 0;
16
+ - BoundaryPrune 的 `workspaceHistory()`(L0 偿还输入)看不到任何会话;
17
+ - CJK 跨会话搜索恒返回 `[]`。
18
+
19
+ 新增 `sessionLogFile(dir)`:正则匹配 `session[.v<N>].jsonl.zstd`,取目录内
20
+ **mtime 最新**的一个(迁移会话可能 v3/v4/legacy 并存,最新 = 宿主正在追加的
21
+ 文件),无匹配时回退历史名、调用方保留原有 ENOENT 处理。四处调用点统一改用它。
22
+ 本机实测(14 天窗口):`files 0 → 17`、`events 0 → 643`。
23
+
24
+ ### 功能
25
+
26
+ - **数据备份面板**(#3)—— 设置页 → Engram 记忆 →「数据备份」:一键导出当前
27
+ 工作区(或全部)为 `dsh-engram/export` v1 JSON 并下载;导入支持 **dry-run 预览**
28
+ → `merge`(只补缺失,幂等)/ `replace`(需勾选确认,宿主要求
29
+ `confirm:"restore"`)。宿主端的 `GET /export`、`POST /import` 早已存在,本版只补
30
+ GUI 入口,GUI 与 API 走同一格式。
31
+ - **替换 / 冲突可视化**(#3)—— 记忆表给被替代的旧行挂「已替换」徽标、给新行挂
32
+ 「替代 #id」跳转;记忆详情面板新增「替换 / 冲突」双向链(出边来自记录的
33
+ `supersedes`/`contradicts`,入边由已加载列表反查),元数据露出
34
+ `source`/`conditions`/`filePath`。
35
+
36
+ ### 测试
37
+
38
+ - 334/334(新增 5 个 #8 用例:版本化/纯 legacy/v3+v4+legacy 混合/陈旧 legacy
39
+ 不遮蔽 v4/无关文件名不被误选,以及 CJK 版本化读取与 `workspaceHistory` 计数)。
40
+ - `npm run typecheck`、`npm run prepublish-check` 通过;`lib/client.js` 重新构建。
41
+
42
+ ## DSH 版本支持
43
+
44
+ - **支持的 DSH 范围**:>=0.1.2-alpha.2 <0.3.0-0
45
+ - **peer 声明**:
46
+ - `@deepseek-ai/dsh-compaction`: `>=0.1.2-alpha.2 <0.3.0-0`
47
+ - `@deepseek-ai/dsh-compaction-basic`: `>=0.1.2-alpha.2 <0.3.0-0`
48
+ - `@deepseek-ai/dsh-llm`: `>=0.1.2-alpha.2 <0.3.0-0`
49
+ - `@deepseek-ai/dsh-settings`: `>=0.1.2-alpha.2 <0.3.0-0`
50
+ - `@deepseek-ai/dsh-storage-domain`: `>=0.1.2-alpha.2 <0.3.0-0`
51
+ - **验证版本**:`dsh-v0.2.0-rc.2`(与 0.5.0 相同的宿主依赖集;本版只改会话日志
52
+ 解析与纯客户端界面,未触碰设置/注入路径)
53
+ - **最后验证日期**:2026-10-19
54
+ - **安装方式已验证**:`dsh plugin --profile web add dsh-engram`
@@ -0,0 +1,64 @@
1
+ ## dsh-engram v0.6.0
2
+
3
+ 自 v0.5.1 以来:从「记忆插件」转向**上下文投资组合**——先把省 token 变成可审计的账本,
4
+ 再用账本纠正默认值、否决不划算的剪枝,并按确定性事件把记忆推给模型。
5
+
6
+ ### 功能
7
+
8
+ - **Context Ledger(上下文净收益账本)** — 每个模型请求记一次账:
9
+ `net = avoided − injected − rewriteChars × (1 − cacheDiscount)`。
10
+ 注入的 `[ENGRAM]`/`[RECALL]`/`[ESR]`/`[JIT]` 块按存活请求计成本;BoundaryPrune 剪掉的
11
+ 结果按后续请求计节省;每次剪枝/推送按前缀重写计价(默认缓存折价 0.1)。
12
+ `GET /api/dsh-engram/stats` 与 `/overview` 返回 `ledger`(totals / byWorkspace / byDay),
13
+ 落盘 `~/.dsh/engram/ledger.json`(原子写、按天滚动、delta flush)。
14
+ 设置键:`ledgerEnabled`(默认开)、`ledgerCacheDiscount`(默认 0.1)。
15
+ - **GUI 账本可视化** — 遥测页新增「Context Ledger · 上下文净收益」面板(净 token 大数字、
16
+ 节省/注入/重写罚金三卡、近 14 天红绿柱、JIT 推送次数);记忆概览新增「上下文净 token」卡。
17
+ - **BoundaryPrune 默认开 + 账本否决** — `boundaryPrune` 默认 `true`、`resultPack` 默认 `true`
18
+ (默认剪枝由归档兜底、无损);新增 `boundaryPruneAdaptive`(默认开):某 workspace 真实剪枝
19
+ 后净收益为负且 ≥3 次时,账本自动否决后续剪枝(fail-open,日志一行)。
20
+ - **JIT 推送(v0.6-C)** — 三类确定性事件,命中已有记忆/任务才推,append-only、每会话 ≤3 行、
21
+ 每行 ≤220 字符、无命中沉默:错误复发(同 `cmd:` 第 2 次失败 + 已有 error/procedure)、
22
+ 文件焦点(同文件连续第 2 次编辑 + filePath/dir 锚定记忆)、goal 完成(有 active ESR 任务)。
23
+ 新增 `[JIT]` section(order 40.75)、`/stats.jit` 计数、`jitEnabled` 开关。
24
+ - **设置卡** — 新增「剪枝自适应」「JIT 推送」两个开关。
25
+
26
+ ### 破坏性变更(默认值纠偏)
27
+
28
+ | 键 | v0.5.1 | v0.6.0 |
29
+ |---|---|---|
30
+ | `boundaryPrune` | `false` | **`true`** |
31
+ | `boundaryPruneAdaptive` | — | **`true`** |
32
+ | `resultPack` | `false` | **`true`** |
33
+ | `gcReplacesCompaction` | `true` | **`false`**(opt-in) |
34
+ | `autoWebCompaction` | `true` | **`false`**(opt-in) |
35
+
36
+ - 默认剪枝现在**开启**,但被剪中段默认归档到 `resultpack/`(7 天),可用
37
+ `result_recall(callId)` 召回。要回到纯记忆模式:`boundaryPrune:false`(或
38
+ `resultPack:false` 接受不可召回)。
39
+ - Context GC 接管改为 **opt-in**。已接管的 web 预设保持 wired,但默认挂宿主
40
+ `BasicCompactionEngine`(DSH 自己的 LLM 摘要),除非显式打开
41
+ `gcReplacesCompaction:true`(web 还需 `autoWebCompaction:true`)。
42
+ - 升级后需重启 `dsh web`;账本从下一次会话开始累计。
43
+
44
+ ### 其他
45
+
46
+ - 测试 **359/359**(新增 ledger 13 例、jit 9 例、adaptive gate 4 例、settings/API/client 渲染用例);
47
+ `npm run eval` BENCH PASS;`npm run typecheck` 通过;`lib/client.js` 重建。
48
+ - 文档:`docs/PROPOSAL-evolution.zh.md`(演进提案)、中英 README、`docs/CONTEXT-GC-GUIDE.zh.md`
49
+ (Context GC 改为 opt-in 的 4 步启用)。
50
+ - 真实实例冒烟(`dsh 0.2.0-rc.2`,临时将 profile 的 `dsh-engram` 指向工作树后还原):
51
+ `/overview` 返回 `ledger`、`/stats` 返回 `jit`,defaults 与上表一致,`compaction.host = default`。
52
+
53
+ ## DSH 版本支持
54
+
55
+ - **支持的 DSH 范围**:`>=0.1.2-alpha.2 <0.3.0-0`
56
+ - **peer 声明**:
57
+ - `@deepseek-ai/dsh-compaction`: `>=0.1.2-alpha.2 <0.3.0-0`
58
+ - `@deepseek-ai/dsh-compaction-basic`: `>=0.1.2-alpha.2 <0.3.0-0`
59
+ - `@deepseek-ai/dsh-llm`: `>=0.1.2-alpha.2 <0.3.0-0`
60
+ - `@deepseek-ai/dsh-settings`: `>=0.1.2-alpha.2 <0.3.0-0`
61
+ - `@deepseek-ai/dsh-storage-domain`: `>=0.1.2-alpha.2 <0.3.0-0`
62
+ - **验证版本**:`dsh 0.2.0-rc.2`
63
+ - **最后验证日期**:2026-10-19
64
+ - **安装方式已验证**:`dsh plugin --profile web add dsh-engram`
package/lib/api.js CHANGED
@@ -163,6 +163,7 @@ export function makeEngramRoutes(service) {
163
163
  captures: service.captureStats ?? { total: 0, git: 0, file: 0, error: 0, preference: 0 },
164
164
  gc: service.gcStats ?? { lastRun: 0, archivedMemories: 0, archivedTasks: 0, removedLinks: 0 },
165
165
  activation: service.packTelemetry ? service.packTelemetry() : { boundaryPrune: null, recall: null },
166
+ ledger: service.ledger ? service.ledger() : null,
166
167
  compaction: service.compactionStatus ?? { host: "unknown", web: { action: "unknown", presets: [] } },
167
168
  config: service.config ?? {},
168
169
  anonymous: {
@@ -517,6 +518,10 @@ export function makeEngramRoutes(service) {
517
518
  // activation record (fires actually landed on this profile) + the
518
519
  // [RECALL] injection-funnel leg (blocks actually injected into sessions).
519
520
  activation: service.packTelemetry ? service.packTelemetry() : { boundaryPrune: null, recall: null },
521
+ // Context Ledger (PROPOSAL-evolution §4 v0.6-A): spent vs saved context,
522
+ // net of prefix rewrites. Persisted rollup + unflushed live sessions.
523
+ ledger: service.ledger ? service.ledger() : null,
524
+ jit: service.jitStats ? service.jitStats() : null,
520
525
  });
521
526
  };
522
527
 
@@ -40,6 +40,7 @@ import fs from "node:fs";
40
40
  import os from "node:os";
41
41
  import path from "node:path";
42
42
 
43
+ import { sessionLogFile } from "./usage.js";
43
44
  import { workspaceKey as wk } from "./util.js";
44
45
 
45
46
  /** Fixed fallback for the repayment gate when no history exists yet. */
@@ -93,7 +94,7 @@ export function workspaceHistory(root = path.join(os.homedir(), ".dsh", "session
93
94
  let sessions = 0;
94
95
  const bdir = path.join(root, bucket);
95
96
  for (const sid of fs.readdirSync(bdir)) {
96
- const file = path.join(bdir, sid, "session.jsonl.zstd");
97
+ const file = sessionLogFile(path.join(bdir, sid));
97
98
  try {
98
99
  const st = fs.statSync(file);
99
100
  if (st.size > 0) sessions += 1;
@@ -129,7 +130,8 @@ export function expectedSessionRequests(requestsSoFar, history, workspace, floor
129
130
 
130
131
  /** Install the boundary-prune listener. Returns a disposer. */
131
132
  export function installBoundaryPrune(ctx, config, log, hooks = {}, opts = {}) {
132
- const enabled = config?.boundaryPrune === true;
133
+ // Default ON (v0.6 default correction): explicit `false` is the opt-out.
134
+ const enabled = config?.boundaryPrune !== false;
133
135
  if (!enabled) return () => {};
134
136
  const pruner = ctx.get?.("toolResultPruner");
135
137
  if (!pruner || typeof pruner.pruneSession !== "function") {
@@ -141,6 +143,13 @@ export function installBoundaryPrune(ctx, config, log, hooks = {}, opts = {}) {
141
143
  : DEFAULT_MIN_REMAINING;
142
144
  // History is injectable for tests; production reads the real session corpus.
143
145
  const history = opts?.history ?? workspaceHistory();
146
+ // Adaptive safety valve (PROPOSAL-evolution v0.6-B): the Context Ledger can
147
+ // veto further fires when a workspace's own accounting says pruning is net
148
+ // negative after enough real fires. The repayment gate still runs first;
149
+ // this is purely evidence-based off, never speculative on.
150
+ const adaptive = config?.boundaryPruneAdaptive !== false;
151
+ const allowFire = typeof opts?.allowFire === "function" ? opts.allowFire : () => true;
152
+ const blockedNotified = new Set();
144
153
 
145
154
  // Optional threshold override (ResultPack L0, T2048 landing): the host pruner
146
155
  // ships a fixed threshold (8192) that the plugin cannot change in place. When
@@ -234,6 +243,24 @@ export function installBoundaryPrune(ctx, config, log, hooks = {}, opts = {}) {
234
243
 
235
244
  /** Shared fire path for every boundary source (event + step-cadence). */
236
245
  const fireBoundary = (session, boundary, state, expectedTotal) => {
246
+ if (adaptive) {
247
+ const workspace = wk(session?.header?.cwd);
248
+ let allowed = true;
249
+ try {
250
+ allowed = allowFire(workspace) !== false;
251
+ } catch {
252
+ allowed = true; // a gate bug must never disable pruning
253
+ }
254
+ if (!allowed) {
255
+ if (!blockedNotified.has(workspace)) {
256
+ blockedNotified.add(workspace);
257
+ log?.info?.(
258
+ `engram boundary-prune: ledger gate holds for ${workspace} (net negative) — skipping further prunes`,
259
+ );
260
+ }
261
+ return;
262
+ }
263
+ }
237
264
  const result = activePruner.pruneSession(session);
238
265
  const prunedNow = result?.pruned?.length ?? 0;
239
266
  if (prunedNow > 0) {