dsh-compact-relay 0.3.6 → 0.4.1
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/CHANGELOG.md +92 -2
- package/README.en.md +22 -5
- package/README.md +23 -6
- package/ROADMAP.md +10 -3
- package/TUTORIAL.md +2 -2
- package/lib/actions/better-compact.js +38 -1
- package/lib/actions/compact-run.js +29 -3
- package/lib/actions/compress-hint.js +21 -1
- package/lib/actions/diagnostics.js +130 -0
- package/lib/actions/handoff.js +90 -11
- package/lib/actions/pin.js +6 -23
- package/lib/actions/state.js +47 -5
- package/lib/adapters/http.js +70 -7
- package/lib/adapters/prompt.js +71 -10
- package/lib/adapters/tool.js +9 -4
- package/lib/client.js +609 -32
- package/lib/core/compress-cue.js +48 -16
- package/lib/core/handoff-doc.js +4 -0
- package/lib/core/handoff-edit.js +86 -0
- package/lib/core/handoff-facts.js +17 -2
- package/lib/core/handoff-spec.js +18 -0
- package/lib/core/ledger.js +176 -0
- package/lib/core/native-line.js +65 -0
- package/lib/core/pin-anchors.js +93 -0
- package/lib/core/pin-suggest.js +129 -0
- package/lib/core/policy.js +16 -0
- package/lib/dsh/diag-counters.js +204 -0
- package/lib/dsh/diagnostics.js +527 -0
- package/lib/dsh/handoff-writer.js +7 -5
- package/lib/dsh/ledger.js +108 -0
- package/lib/dsh/native-compaction.js +128 -0
- package/lib/dsh/pressure.js +62 -10
- package/lib/dsh/relay.js +23 -6
- package/lib/dsh/session-view.js +32 -4
- package/lib/dsh/sessions.js +1 -1
- package/lib/index.js +6 -0
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,8 +1,98 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
##
|
|
3
|
+
## 0.4.1(2026-10-25)
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
### 修复:原生压缩线显示口径不一致(设置页仍写 80%,指示灯写实际触发 67.8%)
|
|
6
|
+
|
|
7
|
+
- **现象**:0.4.0 把指示灯/悬停卡改为「实际触发线」(DSH 按 `min(窗口×thresholdRatio, 窗口−输出预留−headroomTokens)` 触发),但设置页的「作用 / 调整后 / 原生阈值」三处和「指示灯怎么看」教学卡仍按 `thresholdRatio` 显示 80% —— 同一插件两处数字互相矛盾,而且设置页那句「80%:DSH 会自己压历史」本身是错的(实际 67.8% 就压)。
|
|
8
|
+
- **修复**:客户端抽出纯函数 `nativeLineInfo()`(实际线优先;给出配置阈值、实际触发点与差异原因);设置页三处 + 教学卡刻度/文案 + 指示灯悬停卡统一为「实际触发 X%(配置阈值 Y%)」,并说明「模型输出预留 + DSH 自身余量」使实际触发更早;交接提醒线上限也改用实际触发线(原按配置 80% 算,允许把交接线设在实际触发点之后、永远走不到)。
|
|
9
|
+
- **不改**:宿主计算、触发条件、压缩/交接行为;纯显示文案与校验上限。
|
|
10
|
+
- **验证**:Mac 真机(0.4.1 客户端):设置页与悬停卡同显 **67.8%**(678.5K / 1.00M,deepseek-flash),配置阈值 80% 作为注释保留;交接线上限随实际线收紧。
|
|
11
|
+
|
|
12
|
+
### 显示层补强(dev-god 独立审查后,同属 0.4.1)
|
|
13
|
+
|
|
14
|
+
- **关闭态空数字**:`autoEnabled=false` 时原生线信息原本连「实际会触发多少」一起丢,设置页会出现「原生阈值 %」、确认弹层出现「容量到 %」。现在关闭态显示「配置 80%(已关闭;开启后实际触发 67.8%)」,恢复确认也用实际值。
|
|
15
|
+
- **差异归因**:`why` 只在「预算分支确实更小」时才归因为输出预留/余量;`modelPolicies` 路由覆盖改说「该模型单独配了阈值」;实际触发晚于配置阈值时不再套用「理论上限」说法(原实现会自相矛盾)。
|
|
16
|
+
- **交接线遗留值**:设置页对「已保存交接线 > 实际触发线」给出显式提示(不静默改写);交接线输入上限与保存口径统一(按实际触发线向下取整)。服务端保存校验仍以配置阈值做上限(防呆不阻断),本版仅在 UI 收紧并提示如何调整。
|
|
17
|
+
- **文档口径**:README badge / README.en 同步到 0.4.1 与实际触发线说明;ROADMAP 标注已失效参考仓库。
|
|
18
|
+
- 测试:`test/native-line.mjs` 新增 `nativeLineInfo` 行为回归(正常/关闭/算不出/路由覆盖/实际晚于配置五组输入,从客户端源码切出函数真跑)。
|
|
19
|
+
|
|
20
|
+
## 0.4.0(2026-10-24)
|
|
21
|
+
|
|
22
|
+
### 修复:压缩/交接后「压缩判断记录」从未失效(BUG-013,0.3.1 起存在)
|
|
23
|
+
|
|
24
|
+
- **问题(真机发现,2026-10-24)**:压缩/交接后旧的 `compressHint` 本该自动失效(环上断点消失、不再抑制 cue),实际一直判定「有效」。P0-1 上线后真机诊断包里 `counters.cueInjected=0` 的直接原因就是它——旧 hint 的 ratio(26.4%)高于当前 ratio(18.5%),`ratio - hint.ratio < 10%` 的 hint 抑制规则把 cue 全部挡掉;指示灯环上也残留旧断点(26%)。
|
|
25
|
+
- **根因**:`lib/actions/state.js` 调 `lastCheckpointSurfaceSeq(session)` 时**漏传第二个参数** `isCheckpointSource`;`lib/dsh/session-view.js` 里对 `undefined` 调用判定函数抛 TypeError,又被事件循环里的 try/catch 吞成「不是 checkpoint」→ 永远返回 -1 → `checkpointSeq = undefined` → `compressHintIsActive(hint, undefined)` 恒为 true。(对照:`lib/dsh/select-range.js` 传参正确,所以选段/不二压一直正常。)
|
|
26
|
+
- **修复**:① `state.js` 显式传 `deps.engine?.isCompactCheckpointSource`;② `session-view.js` 的 `lastCheckpointSurfaceIdx` / `lastCheckpointSurfaceSeq` 给 `isCheckpointSource` 加默认值 `engine.isCompactCheckpointSource`(dsh→dsh 同层、显式传参优先;纯 node 单测不注册 DSH 包解析钩子时安全降级为「无 checkpoint」,不抛);③ 回归测试加进 `test/state-passive.mjs`(旧 hint + 更新的 checkpoint → `compressHint: null`;hint 比 checkpoint 新 → 保留)。
|
|
27
|
+
- **影响面**:只影响「判断记录新鲜度」的显示与 cue 抑制;不改压缩/交接行为、不改拒绝条件、不动 client。此 bug 自 0.3.1 起存在,随 0.4.0 修复(不回溯 0.3.6)。
|
|
28
|
+
|
|
29
|
+
### P2-3 原生自动压缩「真实触发线」(按路由容量算,不再只显示窗口比例)
|
|
30
|
+
|
|
31
|
+
- **问题**:指示灯一直把 `compaction-basic` 的 `thresholdRatio`(默认 80%)当触发线显示,但 DSH 实际按 token 数触发:`thresholdTokens = floor(min(W × rate, W − 输出预留 − headroom))`,触发点 = `thresholdTokens / W`。当路由请求给输出预留较多或 `headroomTokens` 被改过时,真实触发点会明显低于 80%(作者在 Windows 真实会话里问过「原生压缩线为什么降到了 50」)。
|
|
32
|
+
- **修复**:新增 `lib/core/native-line.js`(纯函数,公式与 dsh-compaction-basic 源码一致);`lib/dsh/native-compaction.js` 增读 `config.headroomTokens`(默认 65536)与嵌套 `modelPolicies`(按 provider+model 精确覆盖 `thresholdRatio`/`headroomTokens`,支持列表与映射两种 YAML 写法;**BUG-012 保持**:嵌套值不会变成全局线);`lib/dsh/pressure.js` 新增 `routeBudgetOf()`:输出预留 = `session.requestHeader().config.maxTokens ?? 适配器 defaultMaxTokens ?? 0`,窗口复用 `llm.resolveModelInfo` 缓存;`readState` 的 `native` 新增 `ratio/thresholdTokens/contextWindow/reservedCompletionTokens/headroomTokens/overridden/route/unavailable`(`thresholdRatio` 仍下发生效比例,老客户端可回落;算不出时 `ratio=null + unavailable=原因`)。
|
|
33
|
+
- 客户端:环上原生刻度/越线闪烁改用真实线;悬停标题显示 `原生自动压缩线 X%(thresholdTokens / 窗口,provider/model,按路由覆盖)`;首次引导文案同步。
|
|
34
|
+
- 测试:`test/native-line.mjs`(公式 80% vs 67.8%、边界/无预算、解析与 BUG-012 回归、路由覆盖匹配、readState 下发与老字段兼容);并入 `npm test`。
|
|
35
|
+
- 不改任何触发/拒绝条件,只改「显示与数据」;版本仍 0.3.6,未发布。
|
|
36
|
+
|
|
37
|
+
### P2-1 交接预览「两段可编辑」(只允许 约束 / 下一步)
|
|
38
|
+
|
|
39
|
+
- 预览接口新增 `editable: { titles: ['约束','下一步'], maxChars: 4000, note, sections }` 与 `ordinal`(第 N 次交接);客户端交接预览卡只给这两段 textarea(`data-cr-handoff-edit`),其余段只读并显示「程序生成段不可改」。
|
|
40
|
+
- 确认时客户端只提交「用户真的改过且非空」的段;宿主在压缩/写盘前校验:非法字段(程序生成段 / 拼错段名 / 非对象)→ `code='edits'`(HTTP 400,消息点名不能改的段);单段 >4000 字符拒绝(不静默截断);空值/未改回落重新生成的原文。应用方式:生成九段后覆盖这两段再渲染(`composeHandoffText` 仍按九段固定顺序),程序段永远来自程序/模型。
|
|
41
|
+
- 九段结构不变;必须用户点「确认交接」才写盘(预览/校验过程不写盘)。action 路由 body 上限从 8KB 放宽到 48KB(容纳两段编辑)。
|
|
42
|
+
- 测试:`test/handoff-p2.mjs`(白名单/空值/超长/非法字段/九段顺序/客户端入口静态守卫)+ `test/handoff.mjs` 集成(编辑后交接单落盘内容生效、程序段被拒且零副作用、空编辑回落)。
|
|
43
|
+
|
|
44
|
+
### P2-2 接力链元数据补全
|
|
45
|
+
|
|
46
|
+
- relay 记录 canonical 字段补齐:`parentId` / `childId` / `ordinal` / `at`(ms)/ `atIso`(本地 ISO);0.3 旧字段 `parentSessionId` / `index` 继续写入,读侧兼容旧记录并补齐 canonical 字段。
|
|
47
|
+
- 链式序号:首次交接 `ordinal=1`;从子会话继续交接 `ordinal=2` 且 `parentId` 指向真正父会话;下一次交接标题序号改用 `ordinal` 推导。
|
|
48
|
+
- 交接单头新增 `- 交接序号:第 N 次交接`(预览草稿里也能看到);接力链视图留 0.5,不做。
|
|
49
|
+
- 测试:`test/handoff-p2.mjs`(canonical/旧记录兼容)+ `test/handoff.mjs`(首次 ordinal=1 + 文档头、链式第 2 次 ordinal=2 / parent 正确)。
|
|
50
|
+
|
|
51
|
+
### P1-3 压缩/交接可审计流水(轻账本)+ 设置页累计数字
|
|
52
|
+
|
|
53
|
+
- 新增轻账本 `~/.dsh/compact-relay-ledger.jsonl`(JSONL,一行一条,schema `dcr-ledger/v1`):每次**压缩/交接成功**后追加一条——`kind`、本地时间、耗时 ms、压前/压后 tokens + ratio、腾出 tokens、pin 命中(`inRange/total`)、交接单落盘路径(仅路径)与 `childId`;交接内部那次压缩不重复记账(一次 `/handoff` 一条)。
|
|
54
|
+
- 隐私:allowlist 构造,只记数值/枚举/时间/路径,**不含消息原文、摘要、交接单正文、sessionId、cwd/用户名**;测试用敏感 fixture 断言产物中不存在。
|
|
55
|
+
- 有界与降级:只保留最近 200 条(超限整文件重写为最近 200 条,tmp+rename 原子替换);文件损坏按行跳过;任何读写失败静默返回 `{ok:false}`,**绝不影响压缩/交接本体**(测试覆盖:账本 record 抛错时交接仍成功;账本路径不可写时动作照常、文件损坏仍可读可写)。
|
|
56
|
+
- 设置页:`GET /api/compact-relay-settings`(含保存/恢复默认响应)新增 `ledger` 累计字段(`count/compactCount/handoffCount/freedTokens/avgDurationMs/lastAt`),设置页「关于与危险操作」显示一行 `累计:已压缩 X 次 · 交接 Y 次 · 腾出约 Z tokens · 平均耗时 W`;无记录时显示引导文案。数字与账本文件同源(测试断言相等),不做大盘。
|
|
57
|
+
- 新增 `test/ledger.mjs`(字段完整 / allowlist + 敏感 fixture / JSONL 容错 / 上限裁剪 / 不可写降级 / 设置数字一致 / 客户端累计行静态守卫)+ `test/handoff.mjs` 压缩与交接成功后的账本落盘断言 + `test/plugin-load.mjs` 设置端点累计断言;并入 `npm test`。
|
|
58
|
+
- 不改压缩引擎/拒绝条件/阈值;版本仍 0.3.6,未发布。
|
|
59
|
+
|
|
60
|
+
### P1-1 压缩预览「建议锁定」提示(只提示、不自动锁)
|
|
61
|
+
|
|
62
|
+
- 预览接口新增 `preview.pinSuggest = { count, items: [{seq, role, reason, snippet}] }`:宿主侧纯本地启发式(`lib/core/pin-suggest.js`)对「本次将被压掉的历史」识别三类信号——`明确要求别改`(用户消息里「不要改 / 别动 / 保持原样 / 必须保留」等)、`报错堆栈`(异常类名 / 堆栈帧 / 堆栈 / 报错等)、`关键决策`(决定 / 拍板 / 采用 / 改用 / 结论等);最多 5 条、每条一句摘要 ≤60 字符、不贴全文;短句、系统消息、运行时合成的 user/message(checkpoint 等)、普通工具输出不进入候选。
|
|
63
|
+
- 客户端预览卡只渲染「建议锁定 N 条(先 /pin 再压)」+ 每条理由/摘要;**绝不自动锁定**(预览不写任何会话事件,测试断言 `session.log` 不变)。`/bc` 预览 message 里也带一句建议。
|
|
64
|
+
- 收敛实现:消息角色/摘要抽取挪到 `lib/core/pin-anchors.js`,`/pin list`、建议锁定、交接单锚点共用一份(删除 `actions/pin.js` 内私有 `previewOf`)。
|
|
65
|
+
|
|
66
|
+
### P1-2 pin 作为「保留契约」(声明与展示,不承诺第三方引擎)
|
|
67
|
+
|
|
68
|
+
- 交接单九段结构不变:锚点写进程序段「当前进度」——`- 锁定锚点(保留契约,接手者请原样保留这些消息)` + 每条 `seq N · 角色 · 「一句摘要(≤60 字符)」`(最多列 10 条,其余提示 `/pin list` 看全)+ 一行「跨引擎压缩器是否遵守取决于其实现」。
|
|
69
|
+
- 压缩预览新增 `preview.pinHits = { inRange, total }`:`inRange` = 本次候选范围里被锁定保护而跳过的条数,`total` = 会话仍活着的锁定总数;客户端预览卡显示「pin 命中 N 条」并区分范围外锁定。
|
|
70
|
+
- 不回归:锁定消息仍在压缩范围之前截断(`clipRangeByPins` 既有逻辑),压缩后原样保留在 surface。
|
|
71
|
+
- 测试:新增 `test/pin-contract.mjs`(候选识别含反例、建议条数/上限、交接单锚点、pin 命中数、预览只读不自动锁)+ `test/handoff.mjs` A1b 预览断言补锚点;并入 `npm test`。
|
|
72
|
+
- 边界:只做「声明与展示」,不承诺其他压缩器遵守 pin;不改压缩引擎/拒绝条件/阈值;版本仍 0.3.6,未发布。
|
|
73
|
+
|
|
74
|
+
### P0-1 判断机制加强(未发布,代码已落地,待 3 次真实任务验收)
|
|
75
|
+
|
|
76
|
+
- **背景(BUG-010 后续)**:Windows 173 个真实会话里 cue 注入 18 次、`mark_compress_point` 调用 **0 次**;Mac 仅在「脚本写完 + 真机验证通过」这种明确节点自发调用过。归因:旧 cue 的「不确定就不用调」让模型倾向保守跳过、工具描述强调拒绝风险、每轮注入变成背景噪声。
|
|
77
|
+
- **cue 文案 v2**(`lib/core/compress-cue.js`):把抽象「自然节点」换成 4 个可自检的完成信号——功能已实现并验证 / 文档已交付 / 问题已定位并修复 / 准备交接;明确「不确定也建议调一次让工具判断:被拒绝不会改动历史、不影响会话」;保留「不处于阶段收尾就不用管」边界,删除「不确定就不用调」。
|
|
78
|
+
- **注入门控**(`lib/adapters/prompt.js` + `lib/core/policy.js` 常量 `CUE_MIN_RATIO=0.10` / `CUE_MAX_RATIO=0.40` / `CUE_MIN_TURNS=3`):只有全满足才注入——① 容量落在 `[max(10%, 动态最小线), min(40%, 压缩线))`(到压缩线不注入,避免诱导注定被拒的调用);② 距上次注入 ≥3 轮(按 text 回调调用次数计,进程内 Map、不新增持久状态、不落盘);③ 一轮内最多一次。异常/无 agent/无缓存行为不变(返回空串)。
|
|
79
|
+
- **工具描述 v2**(`lib/adapters/tool.js`):明确「用户和模型都可以调用」;「被拒不需要致歉、不会改动历史、也不影响会话,不需要重试」;补充典型调用时机(阶段完成 / 准备交接前 / 长任务中途确认可回退点);把「不需要每个小步骤都调用」弱化为句尾的「小步骤不用调用」。
|
|
80
|
+
- **计量接入**(`lib/dsh/diag-counters.js`、`lib/actions/compress-hint.js`、`lib/adapters/prompt.js`):新增 `cueInjected` / `markCalls` / `markAccepted` / `markRejected` 四个计数(被拒也算调用)。只记次数与结果、不记 cue 原文/任务内容(隐私口径与 P0-2 一致);P0-2 诊断包的 `counters` 与 Markdown 直接带出这四个数 → 真实使用后看数字即可判断「标准 B:3 次真实任务 ≥2 次模型自发调用」,不用翻会话日志。
|
|
81
|
+
- **测试**:`test/compress-cue.mjs` 重写覆盖 U1(文案 v2:4 信号 + 被拒无副作用 + 不含旧措辞)、U2(区间 0.08 不注入 / 0.12 注入 / 0.40、0.45 不注入 + 第 4 轮再注入)、U3(无 agent 不注入 / 同轮不重复 / 异常降级)、U6(5% 桶 / 动态最小线 / hint 抑制 / state-hub 原有断言随 v2 同步);`test/smoke.mjs` 增 U4(markCompressHint 成功/被拒计数 + 不变式);`test/plugin-load.mjs` 增 U5(工具描述关键词)+ cue 门控下的 20% 注入断言;`test/diagnostics.mjs` 同步 counters 白名单/渲染。
|
|
82
|
+
- **范围红线(未违反)**:不改压缩/交接行为、不改拒绝条件(最小线 5% / 压缩线 40% 不动)、不自动动作、不动指示灯客户端、零新依赖零网络、版本号仍 **0.3.6**。
|
|
83
|
+
- **未完成(发布前)**:需 3 次真实长任务验证标准 B;soak 记录模板已补「判断机制读数方法」。
|
|
84
|
+
|
|
85
|
+
### P0-2 诊断包最小版(已完成)
|
|
86
|
+
|
|
87
|
+
- **新增「诊断信息」入口**(设置 → 压缩接力 → 帮助):【生成并预览】在本机生成一份 Markdown + JSON 诊断信息,弹层里先预览,再【复制 Markdown】/【复制 JSON】;【打开问题报告页】打开预填版本/平台短字段的 GitHub issue 页。**不做任何自动上传、遥测或后台外发,也不加网络能力。**
|
|
88
|
+
- **新增只读端点** `GET /api/compact-relay-diagnostics?sessionId=`:沿用宿主 `connection.requestRejection` 鉴权(401/403);只支持 GET(非 GET 405);无活动会话时 `state: null`;返回 `{ok, schema, version, generatedAt, markdown, json, issueUrl}`,响应不含正文/堆栈(失败只回短文案)。
|
|
89
|
+
- **新增 `lib/dsh/diag-counters.js`**:进程内计数/耗时/错误码(重启清零)。压缩/交接成功失败计数、最近 20 个耗时样本的 p50/p95、按 `(code, phase)` 聚合的最近 20 条错误码(只收 code/phase/at/count,**不收 message**),锁定动作计数。
|
|
90
|
+
- **新增 `lib/dsh/diagnostics.js`**:`dcr-diagnostics/v1` 产物构造。allowlist 逐字段取值(禁止整对象透传),产物只含数值/枚举/布尔/错误码;JSON ≤ 64KB、errors ≤ 20 条且单条 ≤ 200 字符;额外正则脱敏(GitHub token / `sk-` / Bearer / cookie / 邮箱 / 绝对路径 / 内网 IP / session id 等命中替换为 `[REDACTED]`,只记数量不保留原文);issue URL 只预填短字段、长度 < 7500。
|
|
91
|
+
- **埋点(不改行为与文案)**:`actions/compact-run.js` 与 `actions/handoff.js` 记成功/失败/耗时/错误码;`actions/pin.js` 只在成功锁定时计数(解锁不计)。
|
|
92
|
+
- **新增 `lib/actions/diagnostics.js`**:诊断组装的动作层入口(仓库 A9 架构守卫要求 adapters 不直接 import `dsh/`,故组装落在 actions,HTTP 适配层只做协议转换)。
|
|
93
|
+
- **文档/模板**:README(中英)新增「诊断信息与隐私 / Diagnostics and privacy」节;`.github/ISSUE_TEMPLATE/bug_report.yml` 文案对齐「生成 → 预览 → 复制」;设置页按钮只用「生成 / 预览 / 复制」,不出现「发送 / 上传 / 上报」。
|
|
94
|
+
- **测试**:新增 `test/diagnostics.mjs`(T1–T9:形状白名单、敏感 fixture 脱敏、禁止字段、零网络(fetch 抛错 + 源码扫描)、URL < 7500、64KB/20 条/200 字符上限、p50/p95、计数器、文案),并入 `npm test`;`test/plugin-load.mjs` 增加只读端点用例(鉴权 / 405 / 白名单产物 / 无会话 `state=null`);`test/e2e/settings-ui.mjs` 增加诊断信息真机检查(帮助区入口按钮、点【生成并预览】后的弹层内容含 `dcr-diagnostics/v1` 与「脱敏」、不含 sessionId/绝对路径、点【复制 Markdown】必须有成功或失败反馈)。
|
|
95
|
+
- 版本号仍保持 **0.3.6**(0.4.0 未发布、不 bump);`package.json` 无新增依赖,`capabilities` 未新增 network。
|
|
6
96
|
|
|
7
97
|
## 0.3.6(2026-10-24,原生压缩线解析作用域修复)
|
|
8
98
|
|
package/README.en.md
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
[](https://www.npmjs.com/package/dsh-compact-relay)
|
|
10
10
|
[](https://www.deepseek.com/harness/)
|
|
11
11
|
[](LICENSE)
|
|
12
|
-
[](CHANGELOG.md)
|
|
13
13
|
|
|
14
14
|
</div>
|
|
15
15
|
|
|
@@ -30,7 +30,7 @@ The plugin mounts beside the existing composer. Default parameters are tuned for
|
|
|
30
30
|
|
|
31
31
|
### 1. Occupancy visualization
|
|
32
32
|
|
|
33
|
-
The ring is colored in segments according to the compression-assessment logic: the suggested compression point determined by the model or configured threshold, the positions of pinned messages, and the DSH native auto-compaction line (
|
|
33
|
+
The ring is colored in segments according to the compression-assessment logic: the suggested compression point determined by the model or configured threshold, the positions of pinned messages, and the DSH native auto-compaction line (the actual trigger point computed from the host token budget: ~67.8% for deepseek-flash, while the configured threshold defaults to 80%). When occupancy crosses the actual trigger point, the ring flashes red.
|
|
34
34
|
|
|
35
35
|
When occupancy enters the compressible band (between the minimum compression line and the compression line), the plugin proactively tells the model the current occupancy and band through a dynamic system-prompt context, so it can decide whether to record a compression node after finishing a natural milestone (since 0.3.4; before that the model could not see occupancy, so the condition was not actionable).
|
|
36
36
|
|
|
@@ -61,7 +61,7 @@ Running `/pin` marks messages that must be preserved during compaction. Pinned m
|
|
|
61
61
|
| Amber arc | The compression point has been passed; compaction is suitable |
|
|
62
62
|
| Red arc | The handoff threshold has been passed; switching to a new session is recommended |
|
|
63
63
|
| Ring notch | Suggested compression point (the system proactively prompts the model to assess inside the compressible band; shown after the model records one or the compression line is reached) |
|
|
64
|
-
| Violet tick | DSH native auto-compaction line (
|
|
64
|
+
| Violet tick | DSH native auto-compaction line: the actual trigger point computed as `min(window × thresholdRatio, window − reserved output − headroomTokens)` (~67.8% for deepseek-flash; the configured `thresholdRatio` defaults to 80% and is only a theoretical ceiling) |
|
|
65
65
|
| Magenta blocks | Distribution of pinned messages in the history |
|
|
66
66
|
| Full-ring red flash | The native auto-compaction line has been crossed; DSH native auto-compaction is imminent |
|
|
67
67
|
|
|
@@ -91,7 +91,7 @@ Configuration is available under **Settings → 压缩接力** (in the account m
|
|
|
91
91
|
- **Compaction behaviour**: configure the summary retention limit, the recent-token retention count, and the handoff document directory.
|
|
92
92
|
- **DSH native auto-compaction**: read the host's current threshold and enable or disable DSH native auto-compaction (a backup is created before the profile configuration is modified; a restart is required for the change to take effect).
|
|
93
93
|
- **Colors & appearance**: configure five base colors; the system derives adapted shades for the light and dark themes automatically.
|
|
94
|
-
- **Help**: ring legend, usage notes, feedback links,
|
|
94
|
+
- **Help**: ring legend, usage notes, feedback links, a "show the onboarding again" button, and **Diagnostics** (generated locally, preview-then-copy — see the next section).
|
|
95
95
|
- **About / dangerous operations**: version number, configuration file path, and factory reset.
|
|
96
96
|
|
|
97
97
|

|
|
@@ -104,6 +104,23 @@ The first time a session is opened, the ring plays a brief highlight animation w
|
|
|
104
104
|
|
|
105
105
|

|
|
106
106
|
|
|
107
|
+
## Diagnostics and privacy
|
|
108
|
+
|
|
109
|
+
**Settings → 压缩接力 → Help → Diagnostics**:
|
|
110
|
+
|
|
111
|
+
- **Generate and preview**: creates a Markdown + JSON diagnostics report **locally**, shows it in a preview dialog first; then **Copy Markdown** / **Copy JSON** and paste it wherever you choose (for example a GitHub issue).
|
|
112
|
+
- **Open issue page**: opens a GitHub issue page with the short version/platform fields pre-filled. It does **not** paste the diagnostics report for you — you decide whether and where to paste it.
|
|
113
|
+
|
|
114
|
+
**What the report contains**: plugin version, DSH version (empty when unavailable), Node / OS / architecture / locale; configured thresholds, retention, timeouts and color mode; a numeric snapshot of the current state (window, used tokens, summaries, pinned count); per-load counts for compaction/handoff/pinning, p50/p95 durations, and the last 20 error codes.
|
|
115
|
+
|
|
116
|
+
**What it never contains**: chat content, summaries, handoff document bodies, tool arguments/results, file paths, usernames, hostname, session ids, keys/tokens/cookies, other plugin lists or custom links. The report is built field-by-field from an allowlist inside the host process, then run through a second regex redaction pass (matches are replaced with `[REDACTED]` and only the count is recorded — never the original text).
|
|
117
|
+
|
|
118
|
+
**Three privacy notes**:
|
|
119
|
+
|
|
120
|
+
1. The report is **generated locally only**: the plugin has no automatic upload, telemetry or outbound network requests, and the diagnostics endpoint is a read-only GET with no upload channel.
|
|
121
|
+
2. **No conversation content is touched**: only numbers, enums, booleans and error codes are collected; messages, summaries and handoff bodies are never read.
|
|
122
|
+
3. Opening the issue page only opens a browser. Issues are public by default; whether and where you paste is up to you. The plugin adds no network capability and reports nothing.
|
|
123
|
+
|
|
107
124
|
## Commands
|
|
108
125
|
|
|
109
126
|
| Command | Description |
|
|
@@ -126,7 +143,7 @@ The first time a session is opened, the ring plays a brief highlight animation w
|
|
|
126
143
|
|
|
127
144
|
## Version and license
|
|
128
145
|
|
|
129
|
-
- **Current version**: 0.
|
|
146
|
+
- **Current version**: 0.4.1 (0.3.1 was the first public release; 0.3.2 fixed settings links and contact details; 0.3.3 added Windows support; 0.3.4 fixed the compression-point judgment mechanism; 0.3.5 fixed the disabled compress button; 0.3.6 fixed the native-line parsing scope; 0.4.0 hardened the judgment mechanism, added diagnostics, pin retention, ledger, handoff metadata and the real native trigger line; 0.4.1 unified the native-line display: the ring, the settings page and the teaching card all show the actual trigger point with the configured threshold annotated). Tested on macOS (DSH 0.2.1-rc.2) and Windows 11 (Node 24.22), using only public DSH exports. See [CHANGELOG.md](CHANGELOG.md) for changes, [ROADMAP.md](ROADMAP.md) for plans and known limitations, and [DEVELOPMENT.md](https://github.com/cfanzx/dsh-compact-relay/blob/main/DEVELOPMENT.md) for build and release workflows.
|
|
130
147
|
- **License**: [MIT](LICENSE). Feedback and Pull Requests are welcome via [GitHub Issues](https://github.com/cfanzx/dsh-compact-relay/issues).
|
|
131
148
|
|
|
132
149
|
## Credits
|
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
[](https://www.npmjs.com/package/dsh-compact-relay)
|
|
10
10
|
[](https://www.deepseek.com/harness/)
|
|
11
11
|
[](LICENSE)
|
|
12
|
-
[](CHANGELOG.md)
|
|
13
13
|
|
|
14
14
|
</div>
|
|
15
15
|
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
|
|
31
31
|
### 1. 状态可视化监控
|
|
32
32
|
|
|
33
|
-
指示环按压缩评估逻辑分段显色,分别标识模型或阈值判定的建议压缩点、已锁定消息位置,以及 DSH
|
|
33
|
+
指示环按压缩评估逻辑分段显色,分别标识模型或阈值判定的建议压缩点、已锁定消息位置,以及 DSH 原生自动压缩线(按宿主实际 token 预算算出的触发点:deepseek-flash 约 67.8%,配置阈值默认 80%)。当上下文占用越过原生自动压缩线时,整环呈红色闪烁告警。
|
|
34
34
|
|
|
35
35
|
上下文进入「可压缩区间」(最小压缩线到压缩线之间)时,插件会在系统提示里主动告诉模型当前容量与区间,模型据此判断「刚完成一个自然节点」时是否记录压缩节点(0.3.4 起;此前模型看不到容量,判断条件不可执行)。
|
|
36
36
|
|
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
| 琥珀色弧 | 已越过压缩判断点,适合执行压缩 |
|
|
62
62
|
| 红色弧 | 已越过交接阈值,建议切换新会话 |
|
|
63
63
|
| 环形缺口 | 建议压缩点(系统在可压缩区间主动提示模型判断;模型记录或到达压缩线时自动出现) |
|
|
64
|
-
| 紫色刻度 | DSH
|
|
64
|
+
| 紫色刻度 | DSH 原生自动压缩线:按 `min(窗口 × thresholdRatio, 窗口 − 输出预留 − headroomTokens)` 算出的**实际触发点**(deepseek-flash 约 67.8%;`thresholdRatio` 配置默认 80%,只是理论上限) |
|
|
65
65
|
| 品红色块 | 已锁定消息在历史记录中的分布位置 |
|
|
66
66
|
| 整环红色闪烁 | 已越过原生自动压缩线,即将触发 DSH 原生自动压缩 |
|
|
67
67
|
|
|
@@ -89,9 +89,9 @@ dsh plugin --profile desktop add github:cfanzx/dsh-compact-relay
|
|
|
89
89
|
|
|
90
90
|
- **压缩提示**:配置最小压缩线、压缩提醒线与交接提醒线,控制告警触发时机。
|
|
91
91
|
- **压缩行为**:配置摘要保留份数上限、近期原文保留 Token 数及交接文档存储目录。
|
|
92
|
-
- **DSH
|
|
92
|
+
- **DSH 原生自动压缩**:读取宿主阈值并换算实际触发线,支持开启或关闭 DSH 原生自动压缩(修改 profile 配置前自动生成备份,重启后生效)。
|
|
93
93
|
- **颜色与外观**:配置五项基准色,系统将根据浅色/深色主题自动派生适配色阶。
|
|
94
|
-
-
|
|
94
|
+
- **帮助**:提供指示环图例、使用说明、问题反馈入口、「再看一次初次引导」按钮,以及「诊断信息」(只在本机生成、可预览/复制,见下节)。
|
|
95
95
|
- **关于与危险操作**:查看版本号、配置文件路径,支持恢复出厂默认设置。
|
|
96
96
|
|
|
97
97
|

|
|
@@ -104,6 +104,23 @@ dsh plugin --profile desktop add github:cfanzx/dsh-compact-relay
|
|
|
104
104
|
|
|
105
105
|

|
|
106
106
|
|
|
107
|
+
## 诊断信息与隐私
|
|
108
|
+
|
|
109
|
+
设置 → 压缩接力 → **帮助** →「诊断信息」:
|
|
110
|
+
|
|
111
|
+
- 点【生成并预览】:**只在本机**生成一份 Markdown + JSON 诊断信息,先弹层预览;再点【复制 Markdown】/【复制 JSON】复制,自己决定粘贴到哪(例如 GitHub issue)。
|
|
112
|
+
- 点【打开问题报告页】:打开预填了版本/平台短字段的 GitHub issue 页,**不会**把诊断信息直接贴进去——是否粘贴、粘贴什么由你决定。
|
|
113
|
+
|
|
114
|
+
**诊断包里有什么**:插件版本、DSH 版本(拿不到就留空)、Node / 操作系统 / 架构 / 语言环境;配置的阈值、保留量、超时与颜色模式;当前状态的数值摘要(窗口、已用、摘要数、锁定数等);本次加载后的压缩/交接/锁定计数、耗时 p50/p95,以及最近 20 条错误码。
|
|
115
|
+
|
|
116
|
+
**绝不包含**:聊天内容、摘要、交接单正文、工具参数/结果、文件路径、用户名、hostname、会话 id、密钥/token/cookie、其他插件清单或自定义链接。产物在宿主进程内按白名单逐字段构造,并额外做一遍正则脱敏(命中只替换为 `[REDACTED]`、只记数量,不保留原文)。
|
|
117
|
+
|
|
118
|
+
**三条隐私说明**:
|
|
119
|
+
|
|
120
|
+
1. 诊断信息**只在本机生成**:插件没有任何自动上传、遥测或外发网络请求;诊断端点只提供只读 GET,没有上传通道。
|
|
121
|
+
2. **不碰对话内容**:只采集数值、枚举、布尔与错误码,不读取消息、摘要或交接单正文。
|
|
122
|
+
3. 打开 issue 页只是打开浏览器;issue 默认公开,是否粘贴、粘贴到哪由你决定。插件不加网络能力,也不会上报任何数据。
|
|
123
|
+
|
|
107
124
|
## 命令列表
|
|
108
125
|
|
|
109
126
|
| 命令 | 功能说明 |
|
|
@@ -126,7 +143,7 @@ dsh plugin --profile desktop add github:cfanzx/dsh-compact-relay
|
|
|
126
143
|
|
|
127
144
|
## 版本状态与开源许可
|
|
128
145
|
|
|
129
|
-
- **当前版本**:0.
|
|
146
|
+
- **当前版本**:0.4.0(0.3.1 为首个公开版本;0.3.2 修正设置页链接与联系方式;0.3.3 完成 Windows 适配;0.3.4 修复判断机制并补交接单渲染;0.3.5 修无活 agent 时压缩按钮误禁;0.3.6 修原生压缩线解析作用域(BUG-012);**0.4.0** 判断机制加强(cue 文案 v2 + 注入门控 + 计量)、诊断包(脱敏诊断信息,一键复制/预览)、压缩预览建议锁定、pin 保留契约、轻账本累计、交接预览两段可编辑、接力链元数据、原生自动压缩真实触发线;**0.4.1** 统一原生压缩线显示口径:指示灯 / 设置页 / 教学卡都显示实际触发点,并注明配置阈值与输出预留)。已在 macOS(DSH 0.2.1-rc.2)与 Windows 11(Node 24.22)完成测试,仅调用 DSH 公开导出接口。变更记录详见 [CHANGELOG.md](CHANGELOG.md),后续规划与已知限制详见 [ROADMAP.md](ROADMAP.md),构建与发布流程详见 [DEVELOPMENT.md](https://github.com/cfanzx/dsh-compact-relay/blob/main/DEVELOPMENT.md)。
|
|
130
147
|
- **开源许可**:采用 [MIT](LICENSE) 许可证,欢迎通过 [GitHub Issues](https://github.com/cfanzx/dsh-compact-relay/issues) 与 Pull Requests 提交反馈。
|
|
131
148
|
|
|
132
149
|
## 参考与致谢
|
package/ROADMAP.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# 压缩接力 · 目标与路线图
|
|
2
2
|
|
|
3
|
-
> 版本基准:0.
|
|
3
|
+
> 版本基准:0.4.0(2026-10-25)|状态:0.3 已发布(npm + GitHub),0.3.6 已发布,0.4.0 已发布(npm latest + GitHub tag/Release)
|
|
4
4
|
> 维护方式:每次发布前更新"当前状态"与勾选进度;方向变化必须同时改本文件和 memory 项目页。
|
|
5
5
|
|
|
6
6
|
## 一、最终目标(North Star)
|
|
@@ -54,6 +54,11 @@
|
|
|
54
54
|
|
|
55
55
|
**这是我们的差异化,不碰压缩算法。**
|
|
56
56
|
|
|
57
|
+
**0.4.0 已发布(2026-10-25)**
|
|
58
|
+
|
|
59
|
+
- ✅ **P0-2 诊断包最小版**:本机生成、白名单 + 脱敏的诊断信息(设置页「生成并预览 / 复制 / 打开问题报告页」+ 只读 `GET /api/compact-relay-diagnostics`),并接入压缩/交接/锁定计数与耗时(commit `41699c8`;`npm test` exit 0,含 T1–T9 与真机 e2e 断言)。
|
|
60
|
+
- 🚧 **P0-1 判断机制加强**(代码已落地,待真实任务验收):cue 文案 v2(4 类完成信号 + 被拒无副作用)、注入门控(10%–40%、距上次注入 ≥3 轮、按会话不落盘)、工具描述 v2(用户/模型都可调用、被拒不需要致歉、典型调用时机)、cue/mark 计量(`cueInjected` / `markCalls` / `markAccepted` / `markRejected` 直接进诊断包 counters)。验收口径不变(标准 B):3 次真实长任务里 ≥2 次模型自发调用 `mark_compress_point`。
|
|
61
|
+
|
|
57
62
|
1. ✅ **交接单六段 → 九段**(0.3 已提前交付):总体目标 / 约束 / 已完成 / 当前进度 / 改过的文件 / 关键决策 / 已知问题 / 未完成 / 下一步。
|
|
58
63
|
- 文件改动、关键决策由程序收集(`core/handoff-facts.js`),模型只写 7 段、编不了。
|
|
59
64
|
2. ✅ **交接就绪检查**(0.3 已提前交付):九段齐全/占位阻断,路径核对只警告;预览卡里先看再确认。
|
|
@@ -129,7 +134,7 @@
|
|
|
129
134
|
| [dsh-command-context-trim](https://www.npmjs.com/package/dsh-command-context-trim) | 0.6.6 / 10-03 | 无模型 `/trim`、小窗口切换 | 切掉 |
|
|
130
135
|
| [context-vista](https://www.npmjs.com/package/context-vista) | 0.1.0 / 08-14 | 环图、成本估算 | 仪表盘不做 |
|
|
131
136
|
| [dsh-compressor](https://www.npmjs.com/package/dsh-compressor) | 0.1.0 / 08-15 | 工具结果压缩 + retrieve | 压缩不做 |
|
|
132
|
-
|
|
|
137
|
+
| dsh-context-guard(原 https://github.com/wood20100715-collab/dsh-context-guard 已失效,2026-10-25 核查 404) | 仓库 | 内容类型压缩 + CCR + 高保真摘要 | 智能保留不自研 |
|
|
133
138
|
| [dsh-session-handoff](https://github.com/snow-The/dsh-session-handoff) | GitHub | 结构化交接文档 + 官方 compaction API | 交接同赛道;我们做"检查 + 链 + 记忆" |
|
|
134
139
|
|
|
135
140
|
## 六、已定决策
|
|
@@ -144,7 +149,9 @@
|
|
|
144
149
|
- ✅ 0.3.1 已发布:npm `dsh-compact-relay@0.3.1` + GitHub tag/Release;0.3.2 补设置页链接与联系方式;0.3.3 完成 Windows 适配(Win11 + Node 24.22 全测试通过)。
|
|
145
150
|
- ✅ 0.3.4 判断机制修复(BUG-010:`mark_compress_point` + systemPrompt 可压缩区间提示 + 状态缓存;427 会话数据佐证旧工具 0 调用)+ BUG-005/BUG-006 小 bug;npm latest 待本次发布。
|
|
146
151
|
- ✅ 0.3.5 补丁:修复「无活 agent 时压缩按钮误禁 + 文案误导」(state 增加 `compactRangeReason='no_agent'`;客户端提示「会话未激活:发一条消息唤醒 agent 后可压 N 条」)。
|
|
147
|
-
-
|
|
152
|
+
- ✅ 0.4.0 已发布(2026-10-25,npm latest + GitHub tag/Release):P0-1 判断机制加强(cue 文案 v2 + 10%–40% 注入门控 + cue/mark 计量)、P0-2 诊断包(脱敏诊断信息预览/复制/问题报告页,零网络)、P1-1 压缩预览建议锁定、P1-2 pin 保留契约、P1-3 轻账本累计、P2-1 交接预览两段可编辑、P2-2 接力链元数据、P2-3 原生自动压缩真实触发线(按 route 算)。真机验证全绿(Mac 0.3.6→0.4.0);Mac/Win 均已装 0.4.0 并核对版本。
|
|
153
|
+
- ✅ 0.3.6 已发布(BUG-012):原生压缩线解析只认 `config:` 直接子键(按缩进判断),修复 `modelPolicies` 嵌套 `thresholdRatio` 被误报为全局线(Windows 真实会话误显示 50%);真实触发线按模型 route 计算留 0.4。
|
|
154
|
+
- ✅ 0.4.0 判断机制与诊断包(2026-10-24 完成):P0-2 诊断包最小版已交付(commit `41699c8`,`npm test` 全绿 + 隔离 e2e 真机验证);P0-1 判断机制加强(cue 文案 v2 + 注入门控 10%–40%/≥3 轮 + 工具描述 v2 + cue/mark 计量)代码已落地、测试全绿,标准 B 已达标(3/3)并随 0.4.0 发布。
|
|
148
155
|
- ✅ 真实压缩 e2e:`test/e2e/real-compact.mjs`(真实模型 + 真实压缩事务 + `maxSummaries=1` 合并旧摘要分支)6 项断言通过。
|
|
149
156
|
- ✅ P3-01~14(13 修/1 确认无需修)+ BUG-004 + P2-T1 已收口。
|
|
150
157
|
- ⏳ 市场收录 PR:仓库满 1 天(2026-10-21 19:09 后)提,yml 与说明已备。
|
package/TUTORIAL.md
CHANGED
|
@@ -14,12 +14,12 @@
|
|
|
14
14
|
| 蓝(填满色) | 正常区走满了(到了断点1) | 这里是「该压缩」的位置 |
|
|
15
15
|
| 琥珀(调整色) | 压缩区(断点1 → 交接线) | 点指示灯 →「压缩旧会话」,或输入 `/bc` |
|
|
16
16
|
| 红(上限色 / 内圆红点) | 交接区(交接线之后),或摘要太多 | 点指示灯 →「交接新会话」,或输入 `/handoff` |
|
|
17
|
-
| 警示红(原生线) |
|
|
17
|
+
| 警示红(原生线) | DSH 原生自动压缩线的**实际触发点**(紫色径向刻度;deepseek-flash 约 67.8%,配置阈值 80%);越过实际触发点后整环红色闪烁 | 再往前 DSH 会自己压历史,先压缩或交接 |
|
|
18
18
|
| 品红(锁定色) | 这里锁了一条重要消息(按它在对话里的位置显示,可多个) | 知道「我锁的东西在哪」 |
|
|
19
19
|
|
|
20
20
|
**断点**(环上的小缺口)=「我判断这里适合压缩」的位置:上下文进入可压缩区间(最小压缩线到压缩线之间)时,系统会主动在提示里告诉模型当前容量,模型判断「刚完成一个自然节点」时**可能**会记录一个压缩节点;到了压缩线也会自动出现。压缩后环变短,断点自然消失。
|
|
21
21
|
|
|
22
|
-
把鼠标放到指示灯上,会显示具体数字:`会话容量已用 xx.x%(xxxK / 1.00M),已压缩 n 次|建议压缩 / 建议交接 / 正常|压缩判断点 x%|原生自动压缩线 80
|
|
22
|
+
把鼠标放到指示灯上,会显示具体数字:`会话容量已用 xx.x%(xxxK / 1.00M),已压缩 n 次|建议压缩 / 建议交接 / 正常|压缩判断点 x%|原生自动压缩线 67.8%(配置阈值 80%)|已锁定 n 条`。
|
|
23
23
|
|
|
24
24
|
**第一次用**会看到一条提示:点指示灯能压缩旧会话、**锁定重要消息**、交接新会话;会顺带告诉你 DSH 原生自动压缩线是多少,以及环上的小缺口什么意思。重要的结论/路径先锁定,压缩时才不会被压掉。
|
|
25
25
|
|
|
@@ -18,6 +18,7 @@ import { createHash } from 'node:crypto';
|
|
|
18
18
|
import { actionError } from '../core/errors.js';
|
|
19
19
|
import { livePinnedSeqs } from '../dsh/session-view.js';
|
|
20
20
|
import { compactionPolicy } from '../core/policy.js';
|
|
21
|
+
import { suggestPinCandidates } from '../core/pin-suggest.js';
|
|
21
22
|
import { SUMMARY_INSTRUCTION } from '../core/prompts.js';
|
|
22
23
|
import { sumShadowedTokens, tokenOf } from '../core/tokens.js';
|
|
23
24
|
import { selectCompactableRange } from '../dsh/select-range.js';
|
|
@@ -126,7 +127,8 @@ function noRangeMessage(pinned) {
|
|
|
126
127
|
* @param {{ctx:object, cfg:object, lock:object, engine:object}} deps
|
|
127
128
|
* @param {{agent:object, signal?:AbortSignal, commandId?:string}} input
|
|
128
129
|
*/
|
|
129
|
-
export async function betterCompactLocked(deps, { agent, signal, commandId, expected }) {
|
|
130
|
+
export async function betterCompactLocked(deps, { agent, signal, commandId, expected, ledgerRecord = true }) {
|
|
131
|
+
const startedAt = Date.now();
|
|
130
132
|
if (agent === undefined || agent === null) throw actionError('nosession', '没有活动会话');
|
|
131
133
|
const session = agent.session;
|
|
132
134
|
|
|
@@ -169,11 +171,31 @@ export async function betterCompactLocked(deps, { agent, signal, commandId, expe
|
|
|
169
171
|
? ',旧摘要保持原样'
|
|
170
172
|
: '';
|
|
171
173
|
const pinNote = range.pinnedCut === true ? `,已跳过 ${range.pinnedKept} 条锁定的消息` : '';
|
|
174
|
+
// 0.4 P1-3:轻账本(只记数值,不含消息/摘要/交接单正文)。任何异常静默——
|
|
175
|
+
// 账本绝不能影响压缩本体。交接内部复用本函数时传 ledgerRecord=false,避免一次交接记两条。
|
|
176
|
+
if (ledgerRecord === true) {
|
|
177
|
+
try {
|
|
178
|
+
const afterMeasured = await measurePressure(deps.ctx, agent, { windowFallback: deps.cfg.contextWindowFallback }).catch(() => null);
|
|
179
|
+
deps.ledger?.record?.({
|
|
180
|
+
kind: 'compact',
|
|
181
|
+
durationMs: Date.now() - startedAt,
|
|
182
|
+
beforeTokens: plan.measured.tokens,
|
|
183
|
+
afterTokens: afterMeasured === null ? null : afterMeasured.tokens,
|
|
184
|
+
beforeRatio: plan.measured.ratio,
|
|
185
|
+
afterRatio: afterMeasured === null ? null : afterMeasured.ratio,
|
|
186
|
+
pinHits: { inRange: range.pinnedCut === true ? range.pinnedKept : 0, total: pinned.length },
|
|
187
|
+
});
|
|
188
|
+
} catch {
|
|
189
|
+
/* 账本静默降级:磁盘满/不可写都不影响压缩结果 */
|
|
190
|
+
}
|
|
191
|
+
}
|
|
172
192
|
return {
|
|
173
193
|
ok: true,
|
|
174
194
|
compacted: true,
|
|
175
195
|
// A3/A5:把被批准的范围 id 带出动作层(交接链记录/0.4 diff 可引用)。
|
|
176
196
|
planId,
|
|
197
|
+
// 0.4 P1-3:交接记录要复用「压缩时跳过了多少锁定消息」。
|
|
198
|
+
...(range.pinnedCut === true ? { pinnedKept: range.pinnedKept } : {}),
|
|
177
199
|
...outcome,
|
|
178
200
|
message: `压缩完成:${outcome.shadowedSeqs.length} 条旧会话 → 1 条摘要(腾出约 ${outcome.shadowedTokenCount} 容量${note}${pinNote})。`,
|
|
179
201
|
};
|
|
@@ -201,6 +223,14 @@ export async function previewBetterCompact(deps, { agent } = {}) {
|
|
|
201
223
|
}
|
|
202
224
|
|
|
203
225
|
const shadowedTokenCount = sumShadowedTokens(nodes, range.shadowedSeqs);
|
|
226
|
+
// 0.4 P1-1:对「本次将被压掉的历史」做本地启发式,指出值得先 /pin 的消息。
|
|
227
|
+
// 只提示、绝不自动锁:这里不写任何 pin 记录,锁定动作仍由用户 /pin 触发。
|
|
228
|
+
const pinSuggestItems = suggestPinCandidates(
|
|
229
|
+
range.shadowedSeqs.map((seq) => (typeof agent.session?.eventAt === 'function' ? agent.session.eventAt(seq) : undefined)),
|
|
230
|
+
);
|
|
231
|
+
// 0.4 P1-2:pin 保留契约命中数——inRange = 本次候选范围里被锁定保护而跳过的条数;
|
|
232
|
+
// total = 当前会话仍活着的锁定总数(范围外也可能有锁定)。
|
|
233
|
+
const pinHits = { inRange: range.pinnedCut === true ? range.pinnedKept : 0, total: pinned.length };
|
|
204
234
|
rememberPreviewPlan(agent.id, {
|
|
205
235
|
planId,
|
|
206
236
|
startSeq: range.startSeq,
|
|
@@ -220,11 +250,18 @@ export async function previewBetterCompact(deps, { agent } = {}) {
|
|
|
220
250
|
endSeq: range.endSeq,
|
|
221
251
|
planId,
|
|
222
252
|
tooSmall,
|
|
253
|
+
// 0.4 P1-1:建议锁定候选(客户端只渲染;不含原文全文,snippet ≤60 字符)。
|
|
254
|
+
pinSuggest: { count: pinSuggestItems.length, items: pinSuggestItems },
|
|
255
|
+
// 0.4 P1-2:pin 命中数(范围内/范围外分开说明)。
|
|
256
|
+
pinHits,
|
|
223
257
|
...(range.pinnedCut === true ? { pinnedKept: range.pinnedKept } : {}),
|
|
224
258
|
},
|
|
225
259
|
message:
|
|
226
260
|
`压缩预估:将把 ${range.shadowedSeqs.length} 条旧会话压成 1 条摘要` +
|
|
227
261
|
`(腾出约 ${fmtTokens(shadowedTokenCount)} tokens${range.pinnedCut === true ? `,已跳过 ${range.pinnedKept} 条锁定的消息` : ''})。` +
|
|
262
|
+
(pinSuggestItems.length > 0
|
|
263
|
+
? `建议先 /pin 再压:这 ${pinSuggestItems.length} 条看起来是关键决策/报错堆栈/明确要求别改(只提示,不会自动锁定)。`
|
|
264
|
+
: '') +
|
|
228
265
|
(tooSmall
|
|
229
266
|
? `这段偏小,压了可能反而更大;建议再聊几轮或把「保留近期原文」调小后再压缩。`
|
|
230
267
|
: '只估算范围和容量,摘要内容确认后才生成。') +
|
|
@@ -16,13 +16,19 @@
|
|
|
16
16
|
* 参数的拼装),改一处漏一处就会让 /bctest 与正式压缩行为分叉。
|
|
17
17
|
*/
|
|
18
18
|
import { replaceRangeWithSummary } from '../dsh/compact-transaction.js';
|
|
19
|
+
import { errorCodeOf } from '../dsh/diag-counters.js';
|
|
19
20
|
import { runIdle } from '../dsh/idle.js';
|
|
20
21
|
import { summarize } from '../dsh/summarizer.js';
|
|
21
22
|
import { combinedSignal } from './support.js';
|
|
22
23
|
|
|
23
24
|
/**
|
|
24
|
-
*
|
|
25
|
-
*
|
|
25
|
+
* 执行一次压缩替换事务(对外入口 = 诊断埋点的唯一采集点)。
|
|
26
|
+
*
|
|
27
|
+
* 0.4 P0-2:只在这里数「压缩成功/失败 / 耗时 / 错误码」——正式精压与 /bctest
|
|
28
|
+
* 走的是同一条执行路径,所以两边的计数器口径天然一致;失败原因只记 code,
|
|
29
|
+
* **不记 message**(message 可能带路径/会话内容)。
|
|
30
|
+
*
|
|
31
|
+
* @param {{ctx:object, cfg:object, engine:object, diag?:object}} deps
|
|
26
32
|
* @param {object} input
|
|
27
33
|
* @param {object} input.agent
|
|
28
34
|
* @param {object} input.session
|
|
@@ -35,7 +41,27 @@ import { combinedSignal } from './support.js';
|
|
|
35
41
|
* @param {number} [input.timeoutMs] 单次模型调用超时;**由调用方决定**(省略 = llm-call 的 180s 默认)
|
|
36
42
|
* @returns {Promise<{compactionId:string, startSeq:number, summarySeq:number, endSeq:number, shadowedSeqs:number[], shadowedTokenCount:number}>}
|
|
37
43
|
*/
|
|
38
|
-
export async function runCompaction(
|
|
44
|
+
export async function runCompaction(deps, input) {
|
|
45
|
+
const startedAt = Date.now();
|
|
46
|
+
try {
|
|
47
|
+
const outcome = await runCompactionProcess(deps, input);
|
|
48
|
+
deps.diag?.bumpAction('compact', true);
|
|
49
|
+
return outcome;
|
|
50
|
+
} catch (error) {
|
|
51
|
+
deps.diag?.bumpAction('compact', false);
|
|
52
|
+
deps.diag?.recordError(errorCodeOf(error), 'compact');
|
|
53
|
+
throw error;
|
|
54
|
+
} finally {
|
|
55
|
+
deps.diag?.recordDuration('compact', Date.now() - startedAt);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* 压缩执行流程本体(跑 idle 事务 → 替换历史 + 调摘要)。
|
|
61
|
+
* @param {{ctx:object, cfg:object, engine:object}} deps
|
|
62
|
+
* @param {object} input 见 runCompaction
|
|
63
|
+
*/
|
|
64
|
+
async function runCompactionProcess(
|
|
39
65
|
deps,
|
|
40
66
|
{ agent, session, range, instruction, requiredSections, signal, commandId, inputTokens, timeoutMs },
|
|
41
67
|
) {
|
|
@@ -6,18 +6,38 @@
|
|
|
6
6
|
*
|
|
7
7
|
* 服务端仍复核区间:太低(压缩不划算)/已经到压缩线都拒绝,不改历史。
|
|
8
8
|
* 返回文案是给模型看的,措辞要和工具描述一致(0.3.4 起不再说「系统会告诉你」)。
|
|
9
|
+
*
|
|
10
|
+
* 0.4 P0-1 计量(方案 §3.4):每次调用(无论接受/被拒)都记一笔到 deps.diag:
|
|
11
|
+
* `markCalls` / `markAccepted` / `markRejected` —— 诊断包直接可见,真实使用后
|
|
12
|
+
* 看数字就能判断「标准 B:3 次真实任务里 ≥2 次模型自发调用」是否达标。
|
|
13
|
+
* 只记次数与结果,不记参数/任务内容(隐私口径与 P0-2 一致)。
|
|
9
14
|
*/
|
|
10
15
|
import { COMPRESS_HINT_RECORD_TYPE } from '../core/compress-hint.js';
|
|
11
16
|
import { compressionMinRatio } from '../core/policy.js';
|
|
12
17
|
import { measurePressure } from '../dsh/pressure.js';
|
|
13
18
|
|
|
14
19
|
/**
|
|
15
|
-
*
|
|
20
|
+
* 记录一次「适合压缩」的判断(对外入口 = 诊断计量采集点)。
|
|
21
|
+
*
|
|
22
|
+
* 计量在包装层做:所有 return 路径统一记 `markCalls`,并按 `ok` 记
|
|
23
|
+
* accepted/rejected;不改任何拒绝条件、不改返回文案与历史写入行为。
|
|
24
|
+
*
|
|
16
25
|
* @param {object} deps
|
|
17
26
|
* @param {{agent: object}} input
|
|
18
27
|
* @returns {Promise<{ok:boolean, ratio:number, at:number, seq?:number, reason?:string, message:string}>}
|
|
19
28
|
*/
|
|
20
29
|
export async function markCompressHint(deps, { agent } = {}) {
|
|
30
|
+
const result = await markCompressHintProcess(deps, { agent });
|
|
31
|
+
deps.diag?.bumpMark?.(result.ok === true);
|
|
32
|
+
return result;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* 判断并落记录本体(成功/被拒语义见上方文件头)。
|
|
37
|
+
* @param {object} deps
|
|
38
|
+
* @param {{agent: object}} input
|
|
39
|
+
*/
|
|
40
|
+
async function markCompressHintProcess(deps, { agent } = {}) {
|
|
21
41
|
const session = agent?.session;
|
|
22
42
|
if (session === null || session === undefined) {
|
|
23
43
|
return { ok: false, ratio: 0, at: 0, reason: 'no-session', message: '没有活动会话,无法记录压缩节点' };
|