dsh-compact-relay 0.5.3 → 0.5.4

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 CHANGED
@@ -1,6 +1,20 @@
1
1
  # Changelog
2
2
 
3
- ## 0.5.3(2026-10-29,未发布)
3
+ ## 0.5.4(2026-10-29,0.5.3 的 debug 版)
4
+
5
+ > 0.5.3 发布后的针对性修复,只动收藏/推荐两处数据面与预览卡文案,**压缩主流程零改动**。
6
+ > 测试 **391 → 396 PASS**。
7
+
8
+ - **D1 建议候选过滤工具角色**:预览卡「建议先收藏」只保留用户/助手消息;工具结果里的报错堆栈
9
+ 不再占名额(`suggestPinCandidates` 的 limit 先于角色过滤,工具占满时会用同一函数对用户/助手
10
+ 事件重跑一遍,不新写识别)。`favorited` 列表不过滤(工具输出仍可手动收藏)。
11
+ - **D2 无正文标题兜底**:预览收藏条目里「(无文本)」显示为「第 <seq> 条」,可按序号找回;
12
+ `core/favorites.buildFavoriteTitle` 未动(压缩摘要的收藏目录口径不变)。
13
+ - **D3 压缩后清理推荐建议**:watcher 收到 `compaction/summary`/`compaction/end` 即清空本会话
14
+ 建议、取消待跑去抖、用世代号作废在跑打分结果——被压走的消息不再残留在 `/favorite suggest`
15
+ 和星标建议上;`compaction/start` 不清(事务可能失败)。压缩后新回合照常产生建议。
16
+
17
+ ## 0.5.3(2026-10-29,已发布)
4
18
 
5
19
  > 从 0.5.2 发布点 `e4bd0b2` 拉出的分支 `fix/0.5.3`。主题:**收藏(pin 退役)+ 推荐收藏**,
6
20
  > 外加 0.5.2 真机验证带出的两个 P2 语义修正。测试 **375 → 385 PASS**(含 build-client --check)。
package/README.md CHANGED
@@ -196,7 +196,7 @@ dsh plugin --profile desktop add github:cfanzx/dsh-compact-relay
196
196
 
197
197
  ## 版本状态与开源许可
198
198
 
199
- - **当前版本**:**0.5.3**(本次发布)。版本线: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** 统一原生压缩线显示口径;**0.4.2** 交接线保命补丁(硬上限 = 实际触发线 −5pp、三处告警 + 一键重算);**0.5.0** 无痕交接(命名反转「历史 N」+ 新会话继承原名、输入框上方接力条、交接后自动归档旧会话 + 恢复入口);**0.5.1** 收尾(祖先会话交接后接力条消失、0.5.0 前老链无条、归档入口死路与文案;pin 失效标注、原生压缩提前保护、置顶确认、归档开关、完整交接链、父→子入口;完整链在祖先未加载时只显示 1 层——改为服务端只读持久化日志一次读全,老宿主保留「打开并继续」兜底;设计定稿环只表达容量,pin 位置标记归 0.6 时间线);**0.5.2** 契约快照 golden、record 形状唯一构造器、强制归档旧会话入口 + 二次确认/置顶确认、摘要缓存对齐(对话模型沿用主循环推理档位,生产命中 2–5% → 99.7%);**0.5.3** 收藏(pin 退役;标记/落档两步解耦、无活会话可标、压缩后补标写快照)、推荐收藏(模型打分 + 三档阈值 + 必亮/屏蔽词 + 设置页试算;只点亮不自动收藏)、P2 语义修正(already 顺序、错误码 404/503/500、归档响应瘦身、防漏杀加固)。已在 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)。
199
+ - **当前版本**:**0.5.4**(0.5.3 的 debug 版,已发布)。版本线: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** 统一原生压缩线显示口径;**0.4.2** 交接线保命补丁(硬上限 = 实际触发线 −5pp、三处告警 + 一键重算);**0.5.0** 无痕交接(命名反转「历史 N」+ 新会话继承原名、输入框上方接力条、交接后自动归档旧会话 + 恢复入口);**0.5.1** 收尾(祖先会话交接后接力条消失、0.5.0 前老链无条、归档入口死路与文案;pin 失效标注、原生压缩提前保护、置顶确认、归档开关、完整交接链、父→子入口;完整链在祖先未加载时只显示 1 层——改为服务端只读持久化日志一次读全,老宿主保留「打开并继续」兜底;设计定稿环只表达容量,pin 位置标记归 0.6 时间线);**0.5.2** 契约快照 golden、record 形状唯一构造器、强制归档旧会话入口 + 二次确认/置顶确认、摘要缓存对齐(对话模型沿用主循环推理档位,生产命中 2–5% → 99.7%);**0.5.3** 收藏(pin 退役;标记/落档两步解耦、无活会话可标、压缩后补标写快照)、推荐收藏(模型打分 + 三档阈值 + 必亮/屏蔽词 + 设置页试算;只点亮不自动收藏)、压缩预览卡补收藏区、P2 语义修正(already 顺序、错误码 404/503/500、归档响应瘦身、防漏杀加固);**0.5.4** 0.5.3 debug:建议候选过滤工具角色、无正文条目标题兜底「第 N 条」、压缩后清理推荐建议投影。已在 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)。
200
200
  - **开源许可**:采用 [MIT](LICENSE) 许可证,欢迎通过 [GitHub Issues](https://github.com/cfanzx/dsh-compact-relay/issues) 与 Pull Requests 提交反馈。
201
201
 
202
202
  ## 参考与致谢
@@ -19,7 +19,7 @@ import { actionError } from '../core/errors.js';
19
19
  import { livePinnedSeqs } from '../dsh/session-view.js';
20
20
  import { compactionPolicy } from '../core/policy.js';
21
21
  import { suggestPinCandidates } from '../core/pin-suggest.js';
22
- import { roleLabelOf } from '../core/pin-anchors.js';
22
+ import { ROLE_LABELS, roleLabelOf } from '../core/pin-anchors.js';
23
23
  import { DIRECTORY_MAX, buildFavoriteTitle, favoriteSeqsOf } from '../core/favorites.js';
24
24
  import { favoriteMapOf } from '../dsh/favorites-file.js';
25
25
  import { SUMMARY_INSTRUCTION } from '../core/prompts.js';
@@ -34,6 +34,18 @@ import { beginInflight, cancelErrorOf, combinedSignal, finishInflight } from './
34
34
  /** 范围小于这个 token 数时,摘要大概率比原文大:预览阶段就提示、前端据此禁用确认(0.3.1)。 */
35
35
  const MIN_MEANINGFUL_TOKENS = 800;
36
36
 
37
+ /**
38
+ * 无正文条目的占位标题(与 core/favorites.buildFavoriteTitle 的占位一致)。
39
+ * 预览卡里替换成「第 <seq> 条」——不改 buildFavoriteTitle 本身,避免影响压缩摘要的收藏目录。
40
+ */
41
+ const NO_TEXT_TITLE = '(无文本)';
42
+
43
+ /**
44
+ * 预览「建议先收藏」只保留用户/助手消息:工具结果里的报错堆栈噪声太大(真机候选全是工具)。
45
+ * 工具输出不进建议,但用户仍可手动点星收藏(favorited 列表不过滤)。
46
+ */
47
+ const SUGGEST_ROLE_LABELS = new Set([ROLE_LABELS['user/message'], ROLE_LABELS['assistant/message']]);
48
+
37
49
  /** 容量数字的紧凑写法(与浏览器半 `20-format.js` 的 fmt 保持同一口径)。 */
38
50
  function fmtTokens(tokens) {
39
51
  if (!Number.isFinite(tokens)) return '—';
@@ -229,9 +241,10 @@ export async function previewBetterCompact(deps, { agent } = {}) {
229
241
  // 0.4 P1-1 / 0.6:对「本次将被压掉的历史」做本地启发式,挑出值得先收藏的消息。
230
242
  // 只提示、绝不自动收藏:这里不写任何收藏记录,收藏动作仍由用户点星/点卡内按钮触发。
231
243
  // (字段名沿用 pinSuggest 只为不扩散契约;语义已从「建议锁定」转成「建议收藏」。)
232
- const pinSuggestItems = suggestPinCandidates(
233
- range.shadowedSeqs.map((seq) => (typeof agent.session?.eventAt === 'function' ? agent.session.eventAt(seq) : undefined)),
244
+ const suggestEvents = range.shadowedSeqs.map((seq) =>
245
+ typeof agent.session?.eventAt === 'function' ? agent.session.eventAt(seq) : undefined,
234
246
  );
247
+ const pinSuggestItems = suggestPinCandidates(suggestEvents);
235
248
  // 0.4 P1-2:pin 保留契约命中数——inRange = 本次候选范围里被锁定保护而跳过的条数;
236
249
  // total = 当前会话仍活着的锁定总数(范围外也可能有锁定)。
237
250
  const pinHits = { inRange: range.pinnedCut === true ? range.pinnedKept : 0, total: pinned.length };
@@ -240,16 +253,29 @@ export async function previewBetterCompact(deps, { agent } = {}) {
240
253
  // 0.6 预览卡收藏区:本段已收藏的「条目」(不只是个数)——角色复用 pin-anchors 的
241
254
  // roleLabelOf,标题复用 favorites 的 buildFavoriteTitle(与 /favorite list 同一口径)。
242
255
  // 只读:这里不写任何收藏记录;列表条数按 DIRECTORY_MAX 截断(favoritedCount 仍是全量数)。
256
+ // 0.5.4 D2:无正文条目(图/纯工具输出等)的标题兜底成「第 <seq> 条」,让人能按序号找到;
257
+ // 不改 core/favorites.buildFavoriteTitle(那里是摘要目录口径,动了会影响压缩摘要)。
243
258
  const favoriteItems = range.shadowedSeqs
244
259
  .filter((seq) => favoriteSeqs.has(seq))
245
260
  .map((seq) => {
246
261
  const event = typeof agent.session?.eventAt === 'function' ? agent.session.eventAt(seq) : undefined;
247
- return { seq, role: roleLabelOf(event?.type), title: buildFavoriteTitle(event) };
262
+ const title = buildFavoriteTitle(event);
263
+ return { seq, role: roleLabelOf(event?.type), title: title === NO_TEXT_TITLE ? `第 ${seq} 条` : title };
248
264
  });
249
265
  const favoriteInRange = favoriteItems.length;
250
266
  // 「建议补收藏」= 同一套 pinSuggest 识别,只去掉本段里已经收藏过的 seq(语义从
251
267
  // 「建议锁定」改成「建议收藏」,不新写启发式)。仍是只提示:收藏动作由用户点按钮触发。
252
- const favoriteSuggestItems = pinSuggestItems.filter((item) => !favoriteSeqs.has(item.seq));
268
+ // 0.5.4 D1:只保留用户/助手——工具结果里的报错堆栈噪声太大(真机 5 条候选全是工具),
269
+ // 工具输出仍可由用户手动收藏(favorited 列表不过滤,只过滤「建议」)。
270
+ // 注意 suggestPinCandidates 的 limit 在角色过滤之前生效:工具候选可能占满 5 个名额,
271
+ // 直接过滤会把本可入选的用户/助手候选挤掉。所以混进工具时,用**同一个函数**对
272
+ // 「只看用户/助手的事件」再跑一遍拿足候选(不新写启发式;没有工具时不重跑,零额外开销)。
273
+ const humanSuggestItems = pinSuggestItems.some((item) => !SUGGEST_ROLE_LABELS.has(item.role))
274
+ ? suggestPinCandidates(suggestEvents.filter((event) => SUGGEST_ROLE_LABELS.has(roleLabelOf(event?.type))))
275
+ : pinSuggestItems;
276
+ const favoriteSuggestItems = humanSuggestItems.filter(
277
+ (item) => !favoriteSeqs.has(item.seq) && SUGGEST_ROLE_LABELS.has(item.role),
278
+ );
253
279
  rememberPreviewPlan(agent.id, {
254
280
  planId,
255
281
  startSeq: range.startSeq,
@@ -9,6 +9,8 @@
9
9
  * 多次 assistant/message 收成一次,只对最后一条打分;
10
10
  * 2. **每回合最多一次**:同一会话同时只有一个打分在跑;
11
11
  * 3. **红线:绝不收藏**。这里只写内存建议,不碰 `dcr-favorites` 文件。
12
+ * 0.5.4 D3 补第 4 条:**压缩后作废旧建议**——收到 compaction/summary|end 就清空本会话
13
+ * 建议、取消待跑打分、作废在跑结果,避免被压走的 seq 残留在 /favorite suggest 与星标上。
12
14
  *
13
15
  * 失败静默降级(不亮),不重试、不阻塞事件处理。
14
16
  */
@@ -53,6 +55,12 @@ export function createFavoriteWatcher(deps) {
53
55
  const inflight = new Set();
54
56
  /** @type {Map<string, object[]>} sessionId → 建议列表(新的在前) */
55
57
  const suggestions = new Map();
58
+ /**
59
+ * @type {Map<string, number>} sessionId → 世代号(0.5.4 D3)。
60
+ * 压缩会替换本会话历史:清空建议的同时 +1,正在跑的打分回来发现世代变了就直接丢弃,
61
+ * 避免被压走的 seq 又被写回建议里(真机残留过 seq 2737)。
62
+ */
63
+ const generations = new Map();
56
64
  /** @type {{refreshAll:Function}|null} */
57
65
  let hub = null;
58
66
 
@@ -61,6 +69,9 @@ export function createFavoriteWatcher(deps) {
61
69
  return cfg !== null && typeof cfg === 'object' ? cfg : {};
62
70
  };
63
71
 
72
+ /** 当前世代号(没有记录 = 0)。 */
73
+ const generationOf = (sessionId) => generations.get(sessionId) ?? 0;
74
+
64
75
  /** 当前会话已收藏的 seq 集合(判断「已收藏的不再推荐」)。 */
65
76
  const favoritedSeqsOf = (session) => {
66
77
  try {
@@ -77,6 +88,27 @@ export function createFavoriteWatcher(deps) {
77
88
  hub?.refreshAll?.();
78
89
  };
79
90
 
91
+ /**
92
+ * 0.5.4 D3:本会话发生过压缩(历史被替换)→ 作废建议投影,避免被压走的 seq 残留。
93
+ *
94
+ * 三件事一起做:
95
+ * 1. 清掉该会话已有建议(readState 投影立即为空,星标不再亮在已压走的消息上);
96
+ * 2. 取消待跑的去抖定时器(否则压缩后 1.2s 内还会给旧消息打分);
97
+ * 3. 世代号 +1(正在跑的打分回来也会被丢弃,见 runScore)。
98
+ * 之后的回合照常:每回合最多一次/去抖/只判最后一条均不变。
99
+ */
100
+ const dropStaleSuggestions = (session) => {
101
+ const sessionId = session?.id;
102
+ if (typeof sessionId !== 'string' || sessionId.length === 0) return;
103
+ const timer = timers.get(sessionId);
104
+ if (timer !== undefined) {
105
+ clearTimeout(timer);
106
+ timers.delete(sessionId);
107
+ }
108
+ generations.set(sessionId, generationOf(sessionId) + 1);
109
+ if (suggestions.delete(sessionId)) hub?.refreshAll?.();
110
+ };
111
+
80
112
  const scoreLater = (session, event) => {
81
113
  const sessionId = session?.id;
82
114
  if (typeof sessionId !== 'string' || sessionId.length === 0) return;
@@ -124,9 +156,12 @@ export function createFavoriteWatcher(deps) {
124
156
  }
125
157
 
126
158
  inflight.add(sessionId);
159
+ // 0.5.4 D3:记下打分开始时的世代号;期间发生压缩(历史被替换)就丢弃这次结果。
160
+ const generationAtStart = generationOf(sessionId);
127
161
  try {
128
162
  const scored = await scoreFavoriteMessage(deps, { agent: { id: sessionId, session }, text });
129
163
  if (scored === null) return; // 模型失败:不亮(fail closed)
164
+ if (generationOf(sessionId) !== generationAtStart) return; // 期间压缩过:旧 seq 不再建议
130
165
  const decision = decideFavoriteSuggest({
131
166
  text,
132
167
  score: scored.score,
@@ -148,13 +183,20 @@ export function createFavoriteWatcher(deps) {
148
183
  }
149
184
  };
150
185
 
186
+ /** 0.5.4 D3:压缩事务的事件(summary 先到、end 收尾;native 压缩同样会发)。 */
187
+ const COMPACTION_EVENT_TYPES = new Set(['compaction/summary', 'compaction/end']);
188
+
151
189
  /**
152
- * 会话事件入口。只关心助手消息;其它事件直接返回。
190
+ * 会话事件入口。只关心助手消息(打分)与压缩事件(清建议);其它事件直接返回。
153
191
  * @param {object} session
154
192
  * @param {object} event
155
193
  */
156
194
  const onEvent = (session, event) => {
157
195
  if (event === null || typeof event !== 'object') return;
196
+ if (COMPACTION_EVENT_TYPES.has(event.type)) {
197
+ dropStaleSuggestions(session);
198
+ return;
199
+ }
158
200
  if (event.type !== 'assistant/message') return;
159
201
  scoreLater(session, event);
160
202
  };
@@ -193,6 +235,7 @@ export function createFavoriteWatcher(deps) {
193
235
  timers.clear();
194
236
  inflight.clear();
195
237
  suggestions.clear();
238
+ generations.clear();
196
239
  };
197
240
 
198
241
  return { onEvent, suggestionOf, setHub, clear, close };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-compact-relay",
3
- "version": "0.5.3",
3
+ "version": "0.5.4",
4
4
  "description": "DeepSeek Harness 上下文指示器插件:提供容量监控、压缩预览与确认、九段式交接单生成及消息锁定功能。 · Context indicator for DSH: preview-then-confirm compaction, 9-section session handoff, and pinned messages.",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",