dsh-retrace 0.4.12 → 0.4.14

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.
@@ -457,7 +457,9 @@ var recallMarkerDefinition = {
457
457
  // the action-row suppression check (useShadowed) but empty for legacy
458
458
  // so no row is hidden and no action row is suppressed for legacy
459
459
  // markers (rename must never make visible content disappear).
460
- shadowedSeqs: legacy ? [] : Array.isArray(event.sourceEventSeqs) ? event.sourceEventSeqs.slice() : [],
460
+ // 2026-09-01:优先读 data.shadowedSeqs(host 冗余写入),fallback 顶层
461
+ // sourceEventSeqs——部分客户端事件管道剥离顶层字段导致隐藏失效。
462
+ shadowedSeqs: legacy ? [] : Array.isArray(event.data?.shadowedSeqs) ? event.data.shadowedSeqs.slice() : Array.isArray(event.sourceEventSeqs) ? event.sourceEventSeqs.slice() : [],
461
463
  targetSeq: event.data?.editor?.targetSeq,
462
464
  text: event.data?.editor?.text
463
465
  };
@@ -486,6 +488,10 @@ function useMessageSeq(useSession, messageId) {
486
488
  });
487
489
  }
488
490
  var SHADOW_SAFETY_RATIO = 0.4;
491
+ var SHADOW_MIN_ROWS_FOR_RATIO = 10;
492
+ function shadowDegraded(keys, rowCount) {
493
+ return keys !== null && rowCount > SHADOW_MIN_ROWS_FOR_RATIO && keys.length / rowCount > SHADOW_SAFETY_RATIO;
494
+ }
489
495
  function hiddenKeysFor(shadowedSeqs, nodes) {
490
496
  if (!Array.isArray(shadowedSeqs) || shadowedSeqs.length === 0) return null;
491
497
  const hidden = new Set(shadowedSeqs);
@@ -513,6 +519,7 @@ var EMPTY_HIDE_PLAN = Object.freeze({
513
519
  hiddenFor: () => null,
514
520
  planFor: () => null,
515
521
  unionRatio: 0,
522
+ rowCount: 0,
516
523
  firstMarkerKey: null
517
524
  });
518
525
  var PLUGIN_PSEUDO_KINDS = /* @__PURE__ */ new Set(["user-actions", "retrace-reference", "recall-marker"]);
@@ -543,7 +550,7 @@ function useMarkerHidePlan(useSession) {
543
550
  const union = /* @__PURE__ */ new Set();
544
551
  for (const marker of markers) {
545
552
  const keys = hiddenKeysFor(marker.data?.shadowedSeqs, nodes);
546
- const degraded = keys !== null && rowCount > 0 && keys.length / rowCount > SHADOW_SAFETY_RATIO;
553
+ const degraded = shadowDegraded(keys, rowCount);
547
554
  plans.set(marker.key, { keys: degraded ? null : keys, degraded });
548
555
  if (keys !== null) for (const key of keys) union.add(key);
549
556
  }
@@ -552,6 +559,7 @@ function useMarkerHidePlan(useSession) {
552
559
  planFor: (key) => plans.get(key) ?? null,
553
560
  hiddenFor: (key) => plans.get(key)?.keys ?? null,
554
561
  unionRatio: rowCount > 0 ? union.size / rowCount : 0,
562
+ rowCount,
555
563
  firstMarkerKey: markers[0].key
556
564
  };
557
565
  return hidePlanCacheValue;
@@ -565,7 +573,7 @@ function rowHiddenByKey(snapshot, rowKey) {
565
573
  if (node.kind !== "recall-marker" || node.data?.compact) continue;
566
574
  const keys = hiddenKeysFor(node.data?.shadowedSeqs, nodes);
567
575
  if (keys === null) continue;
568
- if (rowCount > 0 && keys.length / rowCount > SHADOW_SAFETY_RATIO) continue;
576
+ if (shadowDegraded(keys, rowCount)) continue;
569
577
  if (keys.includes(rowKey)) return true;
570
578
  }
571
579
  return false;
@@ -804,7 +812,7 @@ function RecallMarkerRow({ node, useSession, t }) {
804
812
  const count = Array.isArray(shadowedSeqs) ? shadowedSeqs.length : 0;
805
813
  const label = op === "recall" ? count > 1 ? t("marker.recallMany", { count }) : t("marker.recallOne") : op === "regenerate" ? t("marker.regenerate") : t("marker.edit");
806
814
  const degradedHint = !legacy && plan?.degraded === true ? (0, import_react.createElement)("div", { key: "degraded", className: "dsh-rt-marker-hint" }, t("marker.degradedHint")) : null;
807
- const unionHint = !legacy && hidePlan.firstMarkerKey === node.key && hidePlan.unionRatio > SHADOW_SAFETY_RATIO ? (0, import_react.createElement)(
815
+ const unionHint = !legacy && hidePlan.firstMarkerKey === node.key && hidePlan.rowCount > SHADOW_MIN_ROWS_FOR_RATIO && hidePlan.unionRatio > SHADOW_SAFETY_RATIO ? (0, import_react.createElement)(
808
816
  "div",
809
817
  { key: "union", className: "dsh-rt-marker-hint" },
810
818
  t("marker.unionHint", { count: Math.round(hidePlan.unionRatio * 100) })
package/lib/client.js CHANGED
@@ -508,7 +508,9 @@ const recallMarkerDefinition = {
508
508
  // the action-row suppression check (useShadowed) but empty for legacy
509
509
  // so no row is hidden and no action row is suppressed for legacy
510
510
  // markers (rename must never make visible content disappear).
511
- shadowedSeqs: legacy ? [] : (Array.isArray(event.sourceEventSeqs) ? event.sourceEventSeqs.slice() : []),
511
+ // 2026-09-01:优先读 data.shadowedSeqs(host 冗余写入),fallback 顶层
512
+ // sourceEventSeqs——部分客户端事件管道剥离顶层字段导致隐藏失效。
513
+ shadowedSeqs: legacy ? [] : (Array.isArray(event.data?.shadowedSeqs) ? event.data.shadowedSeqs.slice() : (Array.isArray(event.sourceEventSeqs) ? event.sourceEventSeqs.slice() : [])),
512
514
  targetSeq: event.data?.editor?.targetSeq,
513
515
  text: event.data?.editor?.text,
514
516
  }
@@ -563,6 +565,21 @@ function useMessageSeq(useSession, messageId) {
563
565
  */
564
566
  const SHADOW_SAFETY_RATIO = 0.4
565
567
 
568
+ /**
569
+ * 短会话豁免阈值(2026-09-01,独立审查问题 B 的 client 侧另一半):
570
+ * 行数 ≤ 本值的会话永不触发 40% 降级——否则 5-10 行的小会话撤回/编辑
571
+ * 2-4 条消息就 >40%,被误判为「大范围操作」降级为不隐藏(用户实测:
572
+ * 「已撤回 4 条消息 + 此操作涉及大范围对话…」= 小会话误伤)。
573
+ * 与 host 侧 ROLLBACK_MIN_SURFACE=6(surface 节点)同源:短会话豁免只在
574
+ * 大会话按比例判定。10 行 ≈ 5 轮,正常撤回/编辑远小于此。
575
+ */
576
+ const SHADOW_MIN_ROWS_FOR_RATIO = 10
577
+
578
+ /** 40% 降级判定(短会话豁免版):行数足够大才按比例判定。 */
579
+ function shadowDegraded(keys, rowCount) {
580
+ return keys !== null && rowCount > SHADOW_MIN_ROWS_FOR_RATIO && keys.length / rowCount > SHADOW_SAFETY_RATIO
581
+ }
582
+
566
583
  /**
567
584
  * Every chat-node key that should disappear when `shadowedSeqs` are recalled:
568
585
  * the shadowed message rows themselves, plus the per-turn action row (copy /
@@ -615,6 +632,7 @@ const EMPTY_HIDE_PLAN = Object.freeze({
615
632
  hiddenFor: () => null,
616
633
  planFor: () => null,
617
634
  unionRatio: 0,
635
+ rowCount: 0,
618
636
  firstMarkerKey: null,
619
637
  })
620
638
 
@@ -658,7 +676,7 @@ function useMarkerHidePlan(useSession) {
658
676
  const union = new Set()
659
677
  for (const marker of markers) {
660
678
  const keys = hiddenKeysFor(marker.data?.shadowedSeqs, nodes)
661
- const degraded = keys !== null && rowCount > 0 && keys.length / rowCount > SHADOW_SAFETY_RATIO
679
+ const degraded = shadowDegraded(keys, rowCount)
662
680
  plans.set(marker.key, { keys: degraded ? null : keys, degraded })
663
681
  if (keys !== null) for (const key of keys) union.add(key)
664
682
  }
@@ -667,6 +685,7 @@ function useMarkerHidePlan(useSession) {
667
685
  planFor: (key) => plans.get(key) ?? null,
668
686
  hiddenFor: (key) => plans.get(key)?.keys ?? null,
669
687
  unionRatio: rowCount > 0 ? union.size / rowCount : 0,
688
+ rowCount,
670
689
  firstMarkerKey: markers[0].key,
671
690
  }
672
691
  return hidePlanCacheValue
@@ -687,7 +706,7 @@ function rowHiddenByKey(snapshot, rowKey) {
687
706
  if (node.kind !== 'recall-marker' || node.data?.compact) continue // compact markers never hide rows
688
707
  const keys = hiddenKeysFor(node.data?.shadowedSeqs, nodes)
689
708
  if (keys === null) continue
690
- if (rowCount > 0 && keys.length / rowCount > SHADOW_SAFETY_RATIO) continue // degraded: hides nothing
709
+ if (shadowDegraded(keys, rowCount)) continue // degraded: hides nothing
691
710
  if (keys.includes(rowKey)) return true
692
711
  }
693
712
  return false
@@ -994,7 +1013,7 @@ function RecallMarkerRow({ node, useSession, t }) {
994
1013
  const degradedHint = !legacy && plan?.degraded === true
995
1014
  ? createElement('div', { key: 'degraded', className: 'dsh-rt-marker-hint' }, t('marker.degradedHint'))
996
1015
  : null
997
- const unionHint = !legacy && hidePlan.firstMarkerKey === node.key && hidePlan.unionRatio > SHADOW_SAFETY_RATIO
1016
+ const unionHint = !legacy && hidePlan.firstMarkerKey === node.key && hidePlan.rowCount > SHADOW_MIN_ROWS_FOR_RATIO && hidePlan.unionRatio > SHADOW_SAFETY_RATIO
998
1017
  ? createElement('div', { key: 'union', className: 'dsh-rt-marker-hint' },
999
1018
  t('marker.unionHint', { count: Math.round(hidePlan.unionRatio * 100) }))
1000
1019
  : null
@@ -463,7 +463,9 @@ return {
463
463
  // the action-row suppression check (useShadowed) but empty for legacy
464
464
  // so no row is hidden and no action row is suppressed for legacy
465
465
  // markers (rename must never make visible content disappear).
466
- shadowedSeqs: legacy ? [] : Array.isArray(event.sourceEventSeqs) ? event.sourceEventSeqs.slice() : [],
466
+ // 2026-09-01:优先读 data.shadowedSeqs(host 冗余写入),fallback 顶层
467
+ // sourceEventSeqs——部分客户端事件管道剥离顶层字段导致隐藏失效。
468
+ shadowedSeqs: legacy ? [] : Array.isArray(event.data?.shadowedSeqs) ? event.data.shadowedSeqs.slice() : Array.isArray(event.sourceEventSeqs) ? event.sourceEventSeqs.slice() : [],
467
469
  targetSeq: event.data?.editor?.targetSeq,
468
470
  text: event.data?.editor?.text
469
471
  };
@@ -492,6 +494,10 @@ return {
492
494
  });
493
495
  }
494
496
  var SHADOW_SAFETY_RATIO = 0.4;
497
+ var SHADOW_MIN_ROWS_FOR_RATIO = 10;
498
+ function shadowDegraded(keys, rowCount) {
499
+ return keys !== null && rowCount > SHADOW_MIN_ROWS_FOR_RATIO && keys.length / rowCount > SHADOW_SAFETY_RATIO;
500
+ }
495
501
  function hiddenKeysFor(shadowedSeqs, nodes) {
496
502
  if (!Array.isArray(shadowedSeqs) || shadowedSeqs.length === 0) return null;
497
503
  const hidden = new Set(shadowedSeqs);
@@ -519,6 +525,7 @@ return {
519
525
  hiddenFor: () => null,
520
526
  planFor: () => null,
521
527
  unionRatio: 0,
528
+ rowCount: 0,
522
529
  firstMarkerKey: null
523
530
  });
524
531
  var PLUGIN_PSEUDO_KINDS = /* @__PURE__ */ new Set(["user-actions", "retrace-reference", "recall-marker"]);
@@ -549,7 +556,7 @@ return {
549
556
  const union = /* @__PURE__ */ new Set();
550
557
  for (const marker of markers) {
551
558
  const keys = hiddenKeysFor(marker.data?.shadowedSeqs, nodes);
552
- const degraded = keys !== null && rowCount > 0 && keys.length / rowCount > SHADOW_SAFETY_RATIO;
559
+ const degraded = shadowDegraded(keys, rowCount);
553
560
  plans.set(marker.key, { keys: degraded ? null : keys, degraded });
554
561
  if (keys !== null) for (const key of keys) union.add(key);
555
562
  }
@@ -558,6 +565,7 @@ return {
558
565
  planFor: (key) => plans.get(key) ?? null,
559
566
  hiddenFor: (key) => plans.get(key)?.keys ?? null,
560
567
  unionRatio: rowCount > 0 ? union.size / rowCount : 0,
568
+ rowCount,
561
569
  firstMarkerKey: markers[0].key
562
570
  };
563
571
  return hidePlanCacheValue;
@@ -571,7 +579,7 @@ return {
571
579
  if (node.kind !== "recall-marker" || node.data?.compact) continue;
572
580
  const keys = hiddenKeysFor(node.data?.shadowedSeqs, nodes);
573
581
  if (keys === null) continue;
574
- if (rowCount > 0 && keys.length / rowCount > SHADOW_SAFETY_RATIO) continue;
582
+ if (shadowDegraded(keys, rowCount)) continue;
575
583
  if (keys.includes(rowKey)) return true;
576
584
  }
577
585
  return false;
@@ -810,7 +818,7 @@ return {
810
818
  const count = Array.isArray(shadowedSeqs) ? shadowedSeqs.length : 0;
811
819
  const label = op === "recall" ? count > 1 ? t("marker.recallMany", { count }) : t("marker.recallOne") : op === "regenerate" ? t("marker.regenerate") : t("marker.edit");
812
820
  const degradedHint = !legacy && plan?.degraded === true ? (0, import_react.createElement)("div", { key: "degraded", className: "dsh-rt-marker-hint" }, t("marker.degradedHint")) : null;
813
- const unionHint = !legacy && hidePlan.firstMarkerKey === node.key && hidePlan.unionRatio > SHADOW_SAFETY_RATIO ? (0, import_react.createElement)(
821
+ const unionHint = !legacy && hidePlan.firstMarkerKey === node.key && hidePlan.rowCount > SHADOW_MIN_ROWS_FOR_RATIO && hidePlan.unionRatio > SHADOW_SAFETY_RATIO ? (0, import_react.createElement)(
814
822
  "div",
815
823
  { key: "union", className: "dsh-rt-marker-hint" },
816
824
  t("marker.unionHint", { count: Math.round(hidePlan.unionRatio * 100) })
@@ -142,6 +142,10 @@ return {
142
142
  }
143
143
  const surfaceOp = { op: 'replace', start: span.start, end: span.end }
144
144
  const sourceEventSeqs = span.shadowedSeqs.slice()
145
+ // 2026-09-01:把遮蔽 seq 冗余进 data.shadowedSeqs——部分客户端事件管道会剥离
146
+ // event 顶层 sourceEventSeqs(0.4.12 实测:撤回/编辑后消息不隐藏 = client 拿不到
147
+ // shadowedSeqs)。data 里的字段随事件体持久化,client 读 data 更稳(fallback 顶层)。
148
+ data.shadowedSeqs = sourceEventSeqs
145
149
  if (typeof validate === 'function') {
146
150
  const result = await validate(session, { type: 'assistant/message', data, surfaceOp, sourceEventSeqs })
147
151
  if (result && result.t1Ok === false) {
package/lib/host-core.js CHANGED
@@ -133,6 +133,10 @@ export async function appendEditorMarker(session, span, op, targetSeq, originalT
133
133
  }
134
134
  const surfaceOp = { op: 'replace', start: span.start, end: span.end }
135
135
  const sourceEventSeqs = span.shadowedSeqs.slice()
136
+ // 2026-09-01:把遮蔽 seq 冗余进 data.shadowedSeqs——部分客户端事件管道会剥离
137
+ // event 顶层 sourceEventSeqs(0.4.12 实测:撤回/编辑后消息不隐藏 = client 拿不到
138
+ // shadowedSeqs)。data 里的字段随事件体持久化,client 读 data 更稳(fallback 顶层)。
139
+ data.shadowedSeqs = sourceEventSeqs
136
140
  if (typeof validate === 'function') {
137
141
  const result = await validate(session, { type: 'assistant/message', data, surfaceOp, sourceEventSeqs })
138
142
  if (result && result.t1Ok === false) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-retrace",
3
3
  "description": "Retrace · 回溯 — Recall, edit-and-resend, regenerate, and conversation/artifact versioning (timeline, rollback, fork map) for DeepSeek Harness — Web and Desktop",
4
- "version": "0.4.12",
4
+ "version": "0.4.14",
5
5
  "packageManager": "pnpm@11.7.0",
6
6
  "type": "module",
7
7
  "main": "lib/index.js",
@@ -85,7 +85,7 @@
85
85
  "react": "^18.2.0"
86
86
  },
87
87
  "dependencies": {
88
- "dsh-log-contract": "^0.3.0",
88
+ "dsh-log-contract": "^0.3.7",
89
89
  "zod": "^4.4.3"
90
90
  },
91
91
  "repository": {