@khorsheed/dsh-client-message-tools 0.2.0 → 0.3.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.
Files changed (36) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/README.en.md +4 -4
  3. package/README.i18n.yaml +2 -2
  4. package/README.md +4 -4
  5. package/lib/client.js +51 -36
  6. package/lib/index.js +52 -57
  7. package/lib/types/client/UserMessageView.js +7 -4
  8. package/lib/types/client/WithdrawnDividerView.js +2 -2
  9. package/lib/types/client/withdrawn-node.js +3 -3
  10. package/lib/types/index.js +2 -2
  11. package/lib/types/marker.d.ts +2 -2
  12. package/lib/types/withdraw.js +67 -75
  13. package/package.json +19 -19
  14. package/lib/client.js.map +0 -1
  15. package/lib/tsconfig.client.tsbuildinfo +0 -1
  16. package/lib/tsconfig.host.tsbuildinfo +0 -1
  17. package/lib/typert.remote-client.d.ts.map +0 -1
  18. package/lib/types/client/ModelChip.d.ts.map +0 -1
  19. package/lib/types/client/RestoredMessageView.d.ts.map +0 -1
  20. package/lib/types/client/UserMessageView.d.ts.map +0 -1
  21. package/lib/types/client/WithdrawnDividerView.d.ts.map +0 -1
  22. package/lib/types/client/backfill.d.ts.map +0 -1
  23. package/lib/types/client/chat-hook.d.ts.map +0 -1
  24. package/lib/types/client/dom-hider.d.ts.map +0 -1
  25. package/lib/types/client/edit-in-place.d.ts.map +0 -1
  26. package/lib/types/client/icons.d.ts.map +0 -1
  27. package/lib/types/client/index.d.ts.map +0 -1
  28. package/lib/types/client/locales.d.ts.map +0 -1
  29. package/lib/types/client/slots.d.ts.map +0 -1
  30. package/lib/types/client/withdraw-backfill.d.ts.map +0 -1
  31. package/lib/types/client/withdrawn-node.d.ts.map +0 -1
  32. package/lib/types/index.d.ts.map +0 -1
  33. package/lib/types/invariant.d.ts.map +0 -1
  34. package/lib/types/marker.d.ts.map +0 -1
  35. package/lib/types/types.d.ts.map +0 -1
  36. package/lib/types/withdraw.d.ts.map +0 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # 变更记录
2
2
 
3
+ ## 0.3.0(2026-09-11)
4
+
5
+ 适配宿主 0.1.5 线。
6
+
7
+ - **BREAKING**:minHost 前移至 `0.1.5-rc.1`;宿主 `0.1.2-rc.1` ~ `0.1.4.x` 的用户请停留在 0.2.x 线(末版 `0.2.0`)
8
+ - 撤回/恢复投影适配 0.1.5 的会话格式:`assistant/attempt`(每次模型尝试一条聚合 settlement)取代已退役的 `assistant/chunk`;surfaceOp replace 字段 `start`/`end` 更名 `startSeq`/`endSeq`,撤回与就地编辑写出的替换 op 同步改用新名
9
+ - 开发基线随仓内 pin 对齐官方 `0.1.5-rc.1` / cordis `4.0.2`;peer 范围保持宽松
10
+
3
11
  ## 0.2.0(2026-09-10)
4
12
 
5
13
  适配宿主 0.1.2 线。
package/README.en.md CHANGED
@@ -38,10 +38,10 @@ dsh plugin --profile web remove @khorsheed/dsh-client-message-tools
38
38
 
39
39
  ## Compatibility
40
40
 
41
- - npm release line (`@deepseek-ai/dsh@0.1.2-rc.1`): ✅ full — baseline moved to the 0.1.2-rc.1 API surface (single-arm 0.1.2 API consumption; the 0.1.1-rc.2 runtime arm is retired), full build+test green; minHost moves up to 0.1.2-rc.1 — older hosts stay on the previous release line.
42
- - source line (deepseek-harness master): ✅ (verifiedHost: 0.1.2-rc.1)
41
+ - npm release line (`@deepseek-ai/dsh@0.1.5-rc.1`): ✅ full — adapted to the 0.1.5-rc.1 format v2/v3 (`assistant/attempt` replaces `assistant/chunk`; the surfaceOp replace fields `start`/`end` are renamed `startSeq`/`endSeq`), full build+test green; minHost moves up to 0.1.5-rc.1 — older hosts stay on the previous release line.
42
+ - source line (deepseek-harness master): ✅ (verifiedHost: 0.1.5-rc.1)
43
43
 
44
- **Version line mapping**: 0.2.0 and up support host `0.1.2-rc.1` and later; hosts on `0.1.0-rc.6` ~ `0.1.1-rc.2` stay on the 0.1.x release line (last release `0.1.0`).
44
+ **Version line mapping**: the first release after 0.2.0 supports host `0.1.5-rc.1` and later; hosts on `0.1.2-rc.1` stay on `0.2.0`, hosts on `0.1.0-rc.6` ~ `0.1.1-rc.2` stay on the 0.1.x release line (last release `0.1.0`).
45
45
 
46
46
  ## Known Limitations
47
47
 
@@ -63,7 +63,7 @@ dsh plugin --profile web remove @khorsheed/dsh-client-message-tools
63
63
 
64
64
  **Projection and hiding.** The plugin's Definition claims the replacement into a divider anchored at its seq. Hiding is two-layer: the shadowed user renderer renders nothing inside a hidden span, and a dynamic stylesheet drops every chat row whose `data-chat-flow-key` (`ChatNodeSeat.tsx`) anchors inside the span, covering assistant steps, tool calls, and turn tails. The hider probes that undocumented attribute on its first non-empty rule set and, on a miss, enters a bounded retry (MutationObserver plus deadline) so a not-yet-mounted chat doesn't disable it; only an expired window disables it — one `console.warn`, renderer-only hiding — and the observer outlives the disable, so late-mounting rows reactivate it. It never throws. The divider expands in place to a read-only replay of the span (user originals plus assistant text, folded from the live node store) with the 「恢复到对话末尾」 action; a span whose rows fell out of the loaded window shows 「撤回的内容不在当前已加载的历史中」 instead.
65
65
 
66
- **Restore** is a tail replay of the whole withdrawn span, never an in-place repair: the surface fold is positional — a replaced span splices into exactly one node (`applySurfacePlan`) — so the span cannot re-enter the model context where it was, and its model-side hiding is never undone. The host walks the log interval `[start, seq)` delimited by the replacement itself and appends every replayable entry in original order: user messages verbatim (an edit replacement's content IS the last edit's text, so it replays as such) and each assistant reply's text as a framed plugin-sourced user message — `assistant/message` cannot carry a plugin source and the trace forbids assistant appends outside a step, so role fidelity is carried by the frame `(以下是先前被撤回、现随恢复放回的助手回复)`, stripped from display. An interrupted step without `assistant/message` is merged from its `assistant/chunk` rows (reasoning-only fragments preserved). Tool calls/results never replay: the pairing cannot be re-entered and side effects are not replayable. Replayed rows render as the 「已恢复」 group — user bubbles with the full action row, assistant text via the official `MarkdownText` — and the divider carries a 「已恢复」 badge while a live restore row cites the span; withdrawing the restored rows again clears the badge and re-enables the action (restore events stay in the log).
66
+ **Restore** is a tail replay of the whole withdrawn span, never an in-place repair: the surface fold is positional — a replaced span splices into exactly one node (`applySurfacePlan`) — so the span cannot re-enter the model context where it was, and its model-side hiding is never undone. The host walks the log interval `[start, seq)` delimited by the replacement itself and appends every replayable entry in original order: user messages verbatim (an edit replacement's content IS the last edit's text, so it replays as such) and each assistant reply's text as a framed plugin-sourced user message — `assistant/message` cannot carry a plugin source and the trace forbids assistant appends outside a step, so role fidelity is carried by the frame `(以下是先前被撤回、现随恢复放回的助手回复)`, stripped from display. An interrupted attempt without `assistant/message` is merged from its `assistant/attempt` event's embedded compact stream (reasoning-only content preserved). Tool calls/results never replay: the pairing cannot be re-entered and side effects are not replayable. Replayed rows render as the 「已恢复」 group — user bubbles with the full action row, assistant text via the official `MarkdownText` — and the divider carries a 「已恢复」 badge while a live restore row cites the span; withdrawing the restored rows again clears the badge and re-enables the action (restore events stay in the log).
67
67
 
68
68
  **Why the projection cannot hide the span (the upstream seam a durable fix needs).** The assembler runs every Definition's `match` per event with no veto (`conversation-assembler.ts:370`), and `match(event)` reads only the current event; shadowed events keep `surfaceOp: 'append'` (the range metadata lives on the replacement only), so they still match the built-in Definitions, which exclude *replacement* events, not shadowed ones. Node `visibility` is set only by the owning Definition, the assembler forbids withdrawing a materialized node, and a node key is bound to its Definition's kind — a plugin can neither flip nor impersonate an official node. Shadowing the other `conversation.chat.node` keys fails too: the official components are not exported, and `command`/`turn-tail`/`tool-call` declare child slots a shadow cannot re-declare (`tool-call` owns the unbounded per-tool-name `tool.call.toolview` slot). The official compaction pipeline makes the same choice by design — a replaced span stays in the transcript.
69
69
 
package/README.i18n.yaml CHANGED
@@ -2,5 +2,5 @@
2
2
  # last confirmed-consistent state. Both languages carry equal authority; after
3
3
  # editing either side, bring the other along and re-record with:
4
4
  # pnpm run verify-translation-pairing --write packages/message-tools/README.en.md
5
- packages/message-tools/README.en.md: 9ae5fdaef06c3bbf8bd3640abfaba0a92f822fe9
6
- packages/message-tools/README.md: c5c2d33dcf61d9a6e9be0736a011c5c90c61def5
5
+ packages/message-tools/README.en.md: bd272494cd2c60cb8845549496c103da8c3826ea
6
+ packages/message-tools/README.md: 594842e812e8c0e9eba3670926bcb09881576b57
package/README.md CHANGED
@@ -38,10 +38,10 @@ dsh plugin --profile web remove @khorsheed/dsh-client-message-tools
38
38
 
39
39
  ## Compatibility
40
40
 
41
- - npm 发布线(`@deepseek-ai/dsh@0.1.2-rc.1`):✅ 完整——基线迁移至 0.1.2-rc.1 API 面(单臂消费 0.1.2 API,0.1.1-rc.2 运行臂已退役),全量构建测试通过;minHost 前移至 0.1.2-rc.1,旧宿主请停留在旧发布线。
42
- - 源码线(deepseek-harness master):✅(verifiedHost: 0.1.2-rc.1)
41
+ - npm 发布线(`@deepseek-ai/dsh@0.1.5-rc.1`):✅ 完整——适配 0.1.5-rc.1 的 format v2/v3(`assistant/attempt` 取代 `assistant/chunk`;surfaceOp replace 字段 `start`/`end` 更名 `startSeq`/`endSeq`),全量构建测试通过;minHost 前移至 0.1.5-rc.1,旧宿主请停留在旧发布线。
42
+ - 源码线(deepseek-harness master):✅(verifiedHost: 0.1.5-rc.1)
43
43
 
44
- **版本线对照**:0.2.0 起支持宿主 `0.1.2-rc.1` 及以后;宿主 `0.1.0-rc.6` ~ `0.1.1-rc.2` 的用户请停留在 0.1.x 发布线(末版 `0.1.0`)。
44
+ **版本线对照**:0.2.0 之后的首个发布起支持宿主 `0.1.5-rc.1` 及以后;宿主 `0.1.2-rc.1` 请停留在 `0.2.0`,宿主 `0.1.0-rc.6` ~ `0.1.1-rc.2` 请停留在 0.1.x 发布线(末版 `0.1.0`)。
45
45
 
46
46
  ## 已知限制
47
47
 
@@ -63,7 +63,7 @@ dsh plugin --profile web remove @khorsheed/dsh-client-message-tools
63
63
 
64
64
  **投影与隐藏。** 插件的 Definition 把 replacement 认领为锚定在其 seq 上的分隔线节点。隐藏分两层:被遮蔽的用户渲染器对区间内用户消息渲染为空;一张动态样式表把 `data-chat-flow-key`(`ChatNodeSeat.tsx`)锚点落在区间内的聊天行一律隐藏,覆盖助手步骤、工具调用、turn 尾部。隐藏器在首个非空规则集探测这个未文档化属性,探测落空进入有界重试(MutationObserver 加截止时限),避免聊天区尚未挂载时误停用;只有窗口耗尽仍探不到行才停用——`console.warn` 一次,退化为仅渲染器隐藏——且 observer 在停用后仍存活,行晚到会重新生效,绝不报错。分隔线可就地展开只读回放撤回区间(用户原文加助手文本,从实时节点存储折叠)并提供「恢复到对话末尾」;行已掉出加载窗口的区间显示「撤回的内容不在当前已加载的历史中」。
65
65
 
66
- **恢复**是整个被撤回区间的尾部重放,不是原位修复:surface 折叠是位置性的——被替换区间只接续成一个节点(`applySurfacePlan`)——区间无法回到模型上下文原位,其模型侧隐藏也永不回退。host 沿撤回 replacement 自身划定的日志区间 `[start, seq)` 把可重放内容按原始顺序逐条追加到尾部:用户消息逐字重放(编辑替换的内容就是最后一次编辑的新文本,恢复时即以此为准),每条助手回复的文本以带框架的插件来源用户消息重放——`assistant/message` 无法携带插件来源,且轨迹不允许在 step 之外追加助手消息,因此角色保真由框架 `(以下是先前被撤回、现随恢复放回的助手回复)` 承担,UI 上不显示该框架。未落成 `assistant/message` 的中断步骤从其 `assistant/chunk` 片段合并重放(只有 reasoning 时保留 reasoning)。工具调用/结果永不重放:配对无法重新进入,副作用不可重放。重放行渲染为「已恢复」组——用户气泡带完整操作行,助手文本走官方 `MarkdownText`——分隔线在存在引用该区间的存活恢复行期间显示「已恢复」徽标;再次撤回这些恢复行会清掉徽标并重新启用恢复操作(恢复事件始终留在日志里)。
66
+ **恢复**是整个被撤回区间的尾部重放,不是原位修复:surface 折叠是位置性的——被替换区间只接续成一个节点(`applySurfacePlan`)——区间无法回到模型上下文原位,其模型侧隐藏也永不回退。host 沿撤回 replacement 自身划定的日志区间 `[start, seq)` 把可重放内容按原始顺序逐条追加到尾部:用户消息逐字重放(编辑替换的内容就是最后一次编辑的新文本,恢复时即以此为准),每条助手回复的文本以带框架的插件来源用户消息重放——`assistant/message` 无法携带插件来源,且轨迹不允许在 step 之外追加助手消息,因此角色保真由框架 `(以下是先前被撤回、现随恢复放回的助手回复)` 承担,UI 上不显示该框架。未落成 `assistant/message` 的中断尝试从其 `assistant/attempt` 事件内嵌的紧凑流合并重放(只有 reasoning 时保留 reasoning)。工具调用/结果永不重放:配对无法重新进入,副作用不可重放。重放行渲染为「已恢复」组——用户气泡带完整操作行,助手文本走官方 `MarkdownText`——分隔线在存在引用该区间的存活恢复行期间显示「已恢复」徽标;再次撤回这些恢复行会清掉徽标并重新启用恢复操作(恢复事件始终留在日志里)。
67
67
 
68
68
  **为什么投影层做不到(彻底修复所需的上游 seam)。** 组装器对每个事件运行所有已注册 Definition 的 `match`,没有否决机制(`conversation-assembler.ts:370`),且 `match(event)` 只能读当前事件;被遮蔽事件在日志里仍保持 `surfaceOp: 'append'`(区间元数据只存在于 replacement 自身),因此依然命中各内置 Definition——内置 Definition 排除的是 *replacement* 事件,不是被遮蔽事件。节点的 `visibility` 只能由产出它的 Definition 设置,组装器禁止把已物化节点撤回为 null,节点 key 又与其 Definition 的 kind 绑定——插件既无法翻转也无法冒充官方节点。遮蔽其余 `conversation.chat.node` key 同样行不通:官方组件没有导出,`command`/`turn-tail`/`tool-call` 声明了子槽位而遮蔽无法重复声明(`tool-call` 还持有 key 空间无界的按工具名 keyed 的 `tool.call.toolview` 槽位)。官方 compaction 流水线在设计上就做了同样选择——被替换的区间保留在 transcript 里。
69
69
 
package/lib/client.js CHANGED
@@ -4270,9 +4270,10 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4270
4270
  }
4271
4271
  }
4272
4272
  //#endregion
4273
- //#region ../../node_modules/.pnpm/@deepseek-ai+dsh-session@0.1.2-rc.1_@deepseek-ai+cordis@4.0.1_@deepseek-ai+dsh-scope@0._5663fee33a080ce0d83159e01d0d0704/node_modules/@deepseek-ai/dsh-session/lib/types/surface.js
4273
+ //#region ../../node_modules/.pnpm/@deepseek-ai+dsh-session@0.1.5-rc.1_@deepseek-ai+cordis@4.0.2_@deepseek-ai+dsh-scope@0._19a3f679910325ead5ac78e2fac662f7/node_modules/@deepseek-ai/dsh-session/lib/types/surface.js
4274
4274
  /** Runtime counterpart of the message-producing event union. */
4275
4275
  const SURFACE_EVENT_TYPES = /* @__PURE__ */ new Set([
4276
+ "system/message",
4276
4277
  "user/message",
4277
4278
  "assistant/message",
4278
4279
  "tool/result"
@@ -4397,7 +4398,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4397
4398
  if (!isMessageToolsReplacement(event)) throw new Error("message-tools-withdrawn start requires a message-tools replacement event");
4398
4399
  return {
4399
4400
  seq: event.seq,
4400
- hiddenStartSeq: event.surfaceOp.start
4401
+ hiddenStartSeq: event.surfaceOp.startSeq
4401
4402
  };
4402
4403
  },
4403
4404
  update: (context) => context.state,
@@ -4426,7 +4427,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4426
4427
  seq: event.seq,
4427
4428
  time: event.time,
4428
4429
  content: event.data.content,
4429
- hiddenStartSeq: event.surfaceOp.start
4430
+ hiddenStartSeq: event.surfaceOp.startSeq
4430
4431
  };
4431
4432
  },
4432
4433
  update: (context) => context.state,
@@ -5003,7 +5004,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
5003
5004
  return true;
5004
5005
  }
5005
5006
  //#endregion
5006
- //#region ../../node_modules/.pnpm/@deepseek-ai+dsh-client-store@0.1.2-rc.1_@deepseek-ai+cordis@4.0.1_@types+react@18.3.31_react@18.3.1/node_modules/@deepseek-ai/dsh-client-store/lib/index.js
5007
+ //#region ../../node_modules/.pnpm/@deepseek-ai+dsh-client-store@0.1.5-rc.1_@deepseek-ai+cordis@4.0.2/node_modules/@deepseek-ai/dsh-client-store/lib/index.js
5007
5008
  /**
5008
5009
  * Shallow equality for selector slices (zustand/shallow semantics; travels
5009
5010
  * with the engine so hook consumers need no zustand dependency).
@@ -5067,16 +5068,16 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
5067
5068
  document.head.appendChild(tag);
5068
5069
  }
5069
5070
  var ModelChip_module_css_default = {
5070
- "chip": "he-h5G_chip",
5071
- "groupTitle": "he-h5G_groupTitle",
5071
+ "triggerEffort": "he-h5G_triggerEffort",
5072
+ "option": "he-h5G_option",
5072
5073
  "empty": "he-h5G_empty",
5073
5074
  "selectFailed": "he-h5G_selectFailed",
5074
- "triggerEffort": "he-h5G_triggerEffort",
5075
- "menu": "he-h5G_menu",
5075
+ "groupTitle": "he-h5G_groupTitle",
5076
+ "chip": "he-h5G_chip",
5076
5077
  "trigger": "he-h5G_trigger",
5077
- "check": "he-h5G_check",
5078
- "option": "he-h5G_option",
5079
- "group": "he-h5G_group"
5078
+ "menu": "he-h5G_menu",
5079
+ "group": "he-h5G_group",
5080
+ "check": "he-h5G_check"
5080
5081
  };
5081
5082
  //#endregion
5082
5083
  //#region src/client/ModelChip.tsx
@@ -5249,7 +5250,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
5249
5250
  }
5250
5251
  //#endregion
5251
5252
  //#region \0dsh-css:src/client/UserMessageView.module.css.mjs
5252
- const css$2 = ".kCWYAa_userRow{flex-direction:column;align-items:flex-end;gap:6px;display:flex}.kCWYAa_userStack{flex-direction:column;align-items:flex-end;gap:8px;min-width:0;max-width:min(525px,82%);display:flex}.kCWYAa_bubble{background:var(--dsw-specific-bubble,var(--dsw-alias-bg-layer-1));max-width:100%;color:var(--dsw-alias-label-primary,var(--dsw-alias-label-secondary));border-radius:22px;padding:10px 16px;font-size:16px;line-height:24px}.kCWYAa_refChip{color:var(--dsw-alias-label-primary,var(--dsw-alias-label-secondary));white-space:nowrap;vertical-align:baseline;background:#6187d838;border-radius:6px;margin:0 2px;padding:0 8px;font-size:.85em;line-height:1.6;display:inline-block}.kCWYAa_actions{align-items:center;gap:10px;height:28px;display:flex}.kCWYAa_action{width:28px;height:28px;color:var(--dsw-alias-label-tertiary,var(--dsw-alias-label-primary-dimmed));cursor:pointer;background:0 0;border:none;border-radius:28px;justify-content:center;align-items:center;padding:6px;display:inline-flex}.kCWYAa_action:hover{background:var(--dsw-alias-interactive-bg-hover,var(--dsw-alias-bg-layer-1));color:var(--dsw-alias-label-secondary,var(--dsw-alias-label-primary))}.kCWYAa_editor{border:1px solid var(--dsw-alias-border-l1,var(--dsw-alias-border-l2,var(--dsw-alias-label-primary-dimmed)));background:var(--dsw-alias-bg-base,var(--dsw-alias-bg-layer-1));border-radius:8px;flex-direction:column;gap:8px;width:100%;padding:12px;display:flex}.kCWYAa_editorInput{border:1px solid var(--dsw-alias-border-l2,var(--dsw-alias-border-l1,var(--dsw-alias-label-primary-dimmed)));resize:none;width:100%;max-height:240px;color:var(--dsw-alias-label-primary,var(--dsw-alias-label-secondary));font:inherit;background:0 0;border-radius:6px;outline:none;padding:8px;overflow-y:auto}.kCWYAa_editorActions{justify-content:flex-end;align-items:center;gap:8px;display:flex}.kCWYAa_editorTrailing{align-items:center;margin-right:auto;display:flex}.kCWYAa_actionError{color:var(--dsw-alias-state-error-primary,var(--dsw-alias-label-secondary));align-self:flex-end;padding:2px 4px;font-size:13px;line-height:20px}.kCWYAa_editedLabel{color:var(--dsw-alias-label-tertiary,var(--dsw-alias-label-primary-dimmed));align-self:flex-end;font-size:12px;line-height:18px}";
5253
+ const css$2 = ".kCWYAa_userRow{flex-direction:column;align-items:flex-end;gap:6px;display:flex}.kCWYAa_userStack{flex-direction:column;align-items:flex-end;gap:8px;min-width:0;max-width:min(525px,82%);display:flex}.kCWYAa_bubble{background:var(--dsw-specific-bubble,var(--dsw-alias-bg-layer-1));max-width:100%;color:var(--dsw-alias-label-primary,var(--dsw-alias-label-secondary));border-radius:22px;padding:10px 16px;font-size:16px;line-height:24px}.kCWYAa_textRun{white-space:pre-wrap;word-break:break-word;font-size:inherit;line-height:inherit}.kCWYAa_refChip{color:var(--dsw-alias-label-primary,var(--dsw-alias-label-secondary));white-space:nowrap;vertical-align:baseline;background:#6187d838;border-radius:6px;margin:0 2px;padding:0 8px;font-size:.85em;line-height:1.6;display:inline-block}.kCWYAa_actions{align-items:center;gap:10px;height:28px;display:flex}.kCWYAa_action{width:28px;height:28px;color:var(--dsw-alias-label-tertiary,var(--dsw-alias-label-primary-dimmed));cursor:pointer;background:0 0;border:none;border-radius:28px;justify-content:center;align-items:center;padding:6px;display:inline-flex}.kCWYAa_action:hover{background:var(--dsw-alias-interactive-bg-hover,var(--dsw-alias-bg-layer-1));color:var(--dsw-alias-label-secondary,var(--dsw-alias-label-primary))}.kCWYAa_editor{border:1px solid var(--dsw-alias-border-l1,var(--dsw-alias-border-l2,var(--dsw-alias-label-primary-dimmed)));background:var(--dsw-alias-bg-base,var(--dsw-alias-bg-layer-1));border-radius:8px;flex-direction:column;gap:8px;width:100%;padding:12px;display:flex}.kCWYAa_editorInput{border:1px solid var(--dsw-alias-border-l2,var(--dsw-alias-border-l1,var(--dsw-alias-label-primary-dimmed)));resize:none;width:100%;max-height:240px;color:var(--dsw-alias-label-primary,var(--dsw-alias-label-secondary));font:inherit;background:0 0;border-radius:6px;outline:none;padding:8px;overflow-y:auto}.kCWYAa_editorActions{justify-content:flex-end;align-items:center;gap:8px;display:flex}.kCWYAa_editorTrailing{align-items:center;margin-right:auto;display:flex}.kCWYAa_actionError{color:var(--dsw-alias-state-error-primary,var(--dsw-alias-label-secondary));align-self:flex-end;padding:2px 4px;font-size:13px;line-height:20px}.kCWYAa_editedLabel{color:var(--dsw-alias-label-tertiary,var(--dsw-alias-label-primary-dimmed));align-self:flex-end;font-size:12px;line-height:18px}";
5253
5254
  const tagId$2 = "@khorsheed/dsh-client-message-tools/UserMessageView.module.css";
5254
5255
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$2) + "]") === null) {
5255
5256
  const tag = document.createElement("style");
@@ -5259,18 +5260,19 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
5259
5260
  document.head.appendChild(tag);
5260
5261
  }
5261
5262
  var UserMessageView_module_css_default = {
5262
- "userRow": "kCWYAa_userRow",
5263
- "bubble": "kCWYAa_bubble",
5263
+ "actionError": "kCWYAa_actionError",
5264
+ "textRun": "kCWYAa_textRun",
5265
+ "userStack": "kCWYAa_userStack",
5264
5266
  "action": "kCWYAa_action",
5267
+ "bubble": "kCWYAa_bubble",
5268
+ "editorInput": "kCWYAa_editorInput",
5269
+ "editorTrailing": "kCWYAa_editorTrailing",
5265
5270
  "editedLabel": "kCWYAa_editedLabel",
5266
- "refChip": "kCWYAa_refChip",
5271
+ "userRow": "kCWYAa_userRow",
5267
5272
  "editorActions": "kCWYAa_editorActions",
5268
- "editorInput": "kCWYAa_editorInput",
5269
- "editor": "kCWYAa_editor",
5270
- "userStack": "kCWYAa_userStack",
5271
5273
  "actions": "kCWYAa_actions",
5272
- "editorTrailing": "kCWYAa_editorTrailing",
5273
- "actionError": "kCWYAa_actionError"
5274
+ "refChip": "kCWYAa_refChip",
5275
+ "editor": "kCWYAa_editor"
5274
5276
  };
5275
5277
  //#endregion
5276
5278
  //#region src/client/UserMessageView.tsx
@@ -5317,7 +5319,10 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
5317
5319
  const tokenStart = m.index + (m[1]?.length ?? 0);
5318
5320
  /* v8 ignore next -- the token group always participates in a match, so m[2] is never undefined. */
5319
5321
  const label = m[2] ?? "";
5320
- if (tokenStart > cursor) parts.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.MessageText, { text: text.slice(cursor, tokenStart) }, cursor));
5322
+ if (tokenStart > cursor) parts.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5323
+ className: UserMessageView_module_css_default.textRun,
5324
+ children: text.slice(cursor, tokenStart)
5325
+ }, cursor));
5321
5326
  parts.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5322
5327
  className: UserMessageView_module_css_default.refChip,
5323
5328
  "data-ref-chip": label.startsWith("@") ? "subagent" : "skill",
@@ -5325,8 +5330,14 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
5325
5330
  }, tokenStart));
5326
5331
  cursor = tokenStart + label.length;
5327
5332
  }
5328
- if (parts.length === 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.MessageText, { text });
5329
- if (cursor < text.length) parts.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.MessageText, { text: text.slice(cursor) }, cursor));
5333
+ if (parts.length === 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5334
+ className: UserMessageView_module_css_default.textRun,
5335
+ children: text
5336
+ });
5337
+ if (cursor < text.length) parts.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5338
+ className: UserMessageView_module_css_default.textRun,
5339
+ children: text.slice(cursor)
5340
+ }, cursor));
5330
5341
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: parts });
5331
5342
  }
5332
5343
  /** Copy action: the copy icon swaps to a short-lived check after a successful write. */
@@ -5598,8 +5609,8 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
5598
5609
  document.head.appendChild(tag);
5599
5610
  }
5600
5611
  var RestoredMessageView_module_css_default = {
5601
- "restoredLabel": "hDMXAG_restoredLabel",
5602
- "restoredAssistantRow": "hDMXAG_restoredAssistantRow"
5612
+ "restoredAssistantRow": "hDMXAG_restoredAssistantRow",
5613
+ "restoredLabel": "hDMXAG_restoredLabel"
5603
5614
  };
5604
5615
  //#endregion
5605
5616
  //#region src/client/RestoredMessageView.tsx
@@ -5640,7 +5651,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
5640
5651
  }
5641
5652
  //#endregion
5642
5653
  //#region \0dsh-css:src/client/WithdrawnDividerView.module.css.mjs
5643
- const css = ".JtQ9hG_row{padding:2px 0}.JtQ9hG_marker{width:100%;min-width:0;height:24px;color:inherit;font:inherit;text-align:left;cursor:pointer;background:0 0;border:none;border-radius:6px;align-items:center;padding:0;display:flex}.JtQ9hG_marker:hover{background:var(--dsw-alias-interactive-bg-hover,var(--dsw-alias-bg-layer-1))}.JtQ9hG_leading{width:16px;height:16px;color:var(--dsw-alias-label-secondary,var(--dsw-alias-label-primary));flex:none;justify-content:center;align-items:center;margin-right:6px;display:inline-flex}.JtQ9hG_title{color:var(--dsw-alias-label-primary-dimmed,var(--dsw-alias-label-tertiary));flex:none;font-size:14px;line-height:24px}.JtQ9hG_badge{color:var(--dsw-alias-label-tertiary,var(--dsw-alias-label-primary-dimmed));flex:none;align-items:center;gap:4px;margin-left:10px;font-size:12px;line-height:18px;display:inline-flex}.JtQ9hG_body{padding:4px 0 4px 22px}.JtQ9hG_empty{color:var(--dsw-alias-label-tertiary,var(--dsw-alias-label-primary-dimmed));font-size:13px;line-height:20px}.JtQ9hG_entryUser,.JtQ9hG_entryAssistant{color:var(--dsw-alias-label-secondary,var(--dsw-alias-label-primary));padding:4px 0;font-size:14px;line-height:22px}.JtQ9hG_entryKind{color:var(--dsw-alias-label-tertiary,var(--dsw-alias-label-primary-dimmed));margin-bottom:2px;font-size:12px;line-height:18px}.JtQ9hG_entryUser{border-left:2px solid var(--dsw-alias-border-l2,var(--dsw-alias-label-primary-dimmed));padding-left:8px}.JtQ9hG_entryAssistant{border-left:2px solid var(--dsw-alias-border-l1,var(--dsw-alias-border-l2,var(--dsw-alias-label-primary-dimmed)));padding-left:8px}.JtQ9hG_restoreRow{align-items:center;gap:8px;padding-top:8px;display:flex}.JtQ9hG_restoreFailed{color:var(--dsw-alias-state-error-primary,var(--dsw-alias-label-secondary));font-size:12px}";
5654
+ const css = ".JtQ9hG_row{padding:2px 0}.JtQ9hG_marker{width:100%;min-width:0;height:24px;color:inherit;font:inherit;text-align:left;cursor:pointer;background:0 0;border:none;border-radius:6px;align-items:center;padding:0;display:flex}.JtQ9hG_marker:hover{background:var(--dsw-alias-interactive-bg-hover,var(--dsw-alias-bg-layer-1))}.JtQ9hG_leading{width:16px;height:16px;color:var(--dsw-alias-label-secondary,var(--dsw-alias-label-primary));flex:none;justify-content:center;align-items:center;margin-right:6px;display:inline-flex}.JtQ9hG_title{color:var(--dsw-alias-label-primary-dimmed,var(--dsw-alias-label-tertiary));flex:none;font-size:14px;line-height:24px}.JtQ9hG_badge{color:var(--dsw-alias-label-tertiary,var(--dsw-alias-label-primary-dimmed));flex:none;align-items:center;gap:4px;margin-left:10px;font-size:12px;line-height:18px;display:inline-flex}.JtQ9hG_body{padding:4px 0 4px 22px}.JtQ9hG_empty{color:var(--dsw-alias-label-tertiary,var(--dsw-alias-label-primary-dimmed));font-size:13px;line-height:20px}.JtQ9hG_textRun{white-space:pre-wrap;word-break:break-word;font-size:inherit;line-height:inherit}.JtQ9hG_entryUser,.JtQ9hG_entryAssistant{color:var(--dsw-alias-label-secondary,var(--dsw-alias-label-primary));padding:4px 0;font-size:14px;line-height:22px}.JtQ9hG_entryKind{color:var(--dsw-alias-label-tertiary,var(--dsw-alias-label-primary-dimmed));margin-bottom:2px;font-size:12px;line-height:18px}.JtQ9hG_entryUser{border-left:2px solid var(--dsw-alias-border-l2,var(--dsw-alias-label-primary-dimmed));padding-left:8px}.JtQ9hG_entryAssistant{border-left:2px solid var(--dsw-alias-border-l1,var(--dsw-alias-border-l2,var(--dsw-alias-label-primary-dimmed)));padding-left:8px}.JtQ9hG_restoreRow{align-items:center;gap:8px;padding-top:8px;display:flex}.JtQ9hG_restoreFailed{color:var(--dsw-alias-state-error-primary,var(--dsw-alias-label-secondary));font-size:12px}";
5644
5655
  const tagId = "@khorsheed/dsh-client-message-tools/WithdrawnDividerView.module.css";
5645
5656
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
5646
5657
  const tag = document.createElement("style");
@@ -5650,18 +5661,19 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
5650
5661
  document.head.appendChild(tag);
5651
5662
  }
5652
5663
  var WithdrawnDividerView_module_css_default = {
5653
- "row": "JtQ9hG_row",
5654
- "marker": "JtQ9hG_marker",
5655
- "restoreRow": "JtQ9hG_restoreRow",
5664
+ "textRun": "JtQ9hG_textRun",
5656
5665
  "badge": "JtQ9hG_badge",
5657
- "restoreFailed": "JtQ9hG_restoreFailed",
5666
+ "entryUser": "JtQ9hG_entryUser",
5667
+ "entryAssistant": "JtQ9hG_entryAssistant",
5668
+ "entryKind": "JtQ9hG_entryKind",
5669
+ "restoreRow": "JtQ9hG_restoreRow",
5670
+ "body": "JtQ9hG_body",
5658
5671
  "leading": "JtQ9hG_leading",
5672
+ "restoreFailed": "JtQ9hG_restoreFailed",
5673
+ "marker": "JtQ9hG_marker",
5659
5674
  "title": "JtQ9hG_title",
5660
- "body": "JtQ9hG_body",
5661
- "entryAssistant": "JtQ9hG_entryAssistant",
5662
- "empty": "JtQ9hG_empty",
5663
- "entryUser": "JtQ9hG_entryUser",
5664
- "entryKind": "JtQ9hG_entryKind"
5675
+ "row": "JtQ9hG_row",
5676
+ "empty": "JtQ9hG_empty"
5665
5677
  };
5666
5678
  //#endregion
5667
5679
  //#region src/client/WithdrawnDividerView.tsx
@@ -5745,7 +5757,10 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
5745
5757
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5746
5758
  className: WithdrawnDividerView_module_css_default.entryKind,
5747
5759
  children: entry.kind === "user" ? t("withdrawn.entryUser") : t("withdrawn.entryAssistant")
5748
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.MessageText, { text: entry.text })]
5760
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5761
+ className: WithdrawnDividerView_module_css_default.textRun,
5762
+ children: entry.text
5763
+ })]
5749
5764
  }, index)), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5750
5765
  className: WithdrawnDividerView_module_css_default.restoreRow,
5751
5766
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
package/lib/index.js CHANGED
@@ -226,10 +226,10 @@ function joinAssistantMessageText(blocks) {
226
226
  * (the message left the surface through another producer, e.g. compaction).
227
227
  *
228
228
  * `sourceEventSeqs` is authoritative for the shadowed *surface* nodes, but an
229
- * interrupted assistant step may exist only as `assistant/chunk` events (no
230
- * `assistant/message` ever landed). Those chunks are not surface nodes, so
231
- * they are absent from `sourceEventSeqs`. The replacement's own `start` and
232
- * `seq` delimit the whole withdrawn log interval — `[start, seq)` — and are
229
+ * interrupted assistant step may exist only as `assistant/attempt` events (no
230
+ * `assistant/message` ever landed). Attempts are not surface nodes, so
231
+ * they are absent from `sourceEventSeqs`. The replacement's own `startSeq` and
232
+ * `seq` delimit the whole withdrawn log interval — `[startSeq, seq)` — and are
233
233
  * still read from the replacement itself, never guessed from another producer.
234
234
  */
235
235
  function findWithdrawnSpan(events, targetSeq) {
@@ -238,7 +238,7 @@ function findWithdrawnSpan(events, targetSeq) {
238
238
  if (event === void 0 || !isMessageToolsReplacement(event)) continue;
239
239
  if (event.sourceEventSeqs?.includes(targetSeq) !== true) continue;
240
240
  const span = [];
241
- for (let seq = event.surfaceOp.start; seq < event.seq; seq++) span.push(seq);
241
+ for (let seq = event.surfaceOp.startSeq; seq < event.seq; seq++) span.push(seq);
242
242
  return span;
243
243
  }
244
244
  }
@@ -249,50 +249,23 @@ function findWithdrawnSpan(events, targetSeq) {
249
249
  * restore-assistant replays already carry the frame). Skipped: withdrawal
250
250
  * placeholders, edit triggers, other plugin context, and tool calls/results.
251
251
  *
252
- * Assistant text normally comes from `assistant/message` surface events. A
253
- * turn that was interrupted before finalization may never emit
254
- * `assistant/message`; its visible content survives only as `assistant/chunk`
255
- * rows. Those chunks are folded into one assistant replay per step when no
256
- * final `assistant/message` exists for that step, so withdrawing/restoring an
257
- * interrupted reply does not lose the partial assistant content.
252
+ * Assistant text normally comes from `assistant/message` surface events (a
253
+ * cancelled turn finalizes its delivered prefix there with `interrupted:
254
+ * true`). An attempt that committed no surface message — failed, retried, or
255
+ * stream-error — settles as an `assistant/attempt` log event carrying its
256
+ * exact compact stream. Each such attempt folds into one assistant replay
257
+ * when no final `assistant/message` exists for that step, so
258
+ * withdrawing/restoring an interrupted reply does not lose the partial
259
+ * assistant content. A retried step's earlier attempts are superseded by the
260
+ * step's final message and do not replay.
258
261
  */
259
262
  function replayEntries(events, spanSeqs) {
260
263
  const entries = [];
261
- let pending;
262
264
  const finalizedSteps = /* @__PURE__ */ new Set();
263
- const flushPending = () => {
264
- if (pending === void 0) return;
265
- if (!pending.finalized) {
266
- const text = [...pending.text.values()].join("");
267
- const reasoning = [...pending.reasoning.values()].join("");
268
- const replay = text !== "" ? text : reasoning;
269
- if (replay !== "") entries.push({
270
- role: "assistant",
271
- text: `${RESTORED_ASSISTANT_NOTICE}\n${replay}`,
272
- sourceSeq: pending.sourceSeq
273
- });
274
- }
275
- pending = void 0;
276
- };
277
- const pushChunk = (event) => {
278
- if (finalizedSteps.has(`${event.data.turn}:${event.data.step}`)) return;
279
- if (pending === void 0 || pending.turn !== event.data.turn || pending.step !== event.data.step) {
280
- flushPending();
281
- pending = {
282
- turn: event.data.turn,
283
- step: event.data.step,
284
- sourceSeq: event.seq,
285
- text: /* @__PURE__ */ new Map(),
286
- reasoning: /* @__PURE__ */ new Map(),
287
- finalized: false
288
- };
289
- }
290
- const chunk = event.data.chunk;
291
- if (chunk.type === "text-delta") pending.text.set(chunk.index, (pending.text.get(chunk.index) ?? "") + chunk.text);
292
- else if (chunk.type === "block-end" && chunk.block.type === "text") pending.text.set(chunk.index, chunk.block.text);
293
- else if (chunk.type === "reasoning-delta") pending.reasoning.set(chunk.index, (pending.reasoning.get(chunk.index) ?? "") + chunk.text);
294
- else if (chunk.type === "block-end" && chunk.block.type === "reasoning") pending.reasoning.set(chunk.index, chunk.block.text);
295
- };
265
+ for (const seq of spanSeqs) {
266
+ const event = events[seq];
267
+ if (event?.type === "assistant/message") finalizedSteps.add(`${event.data.turn}:${event.data.step}`);
268
+ }
296
269
  const pushAssistantText = (text, sourceSeq) => {
297
270
  if (text !== "") entries.push({
298
271
  role: "assistant",
@@ -304,7 +277,6 @@ function replayEntries(events, spanSeqs) {
304
277
  const event = events[seq];
305
278
  if (event === void 0) continue;
306
279
  if (isMessageToolsEdit(event)) {
307
- flushPending();
308
280
  entries.push({
309
281
  role: "user",
310
282
  content: event.data.content,
@@ -312,12 +284,12 @@ function replayEntries(events, spanSeqs) {
312
284
  });
313
285
  continue;
314
286
  }
315
- if (event.type === "assistant/chunk") {
316
- pushChunk(event);
287
+ if (event.type === "assistant/attempt") {
288
+ if (finalizedSteps.has(`${event.data.turn}:${event.data.step}`)) continue;
289
+ pushAssistantText(joinAttemptStreamText(event.data.stream), seq);
317
290
  continue;
318
291
  }
319
292
  if (event.type === "user/message") {
320
- flushPending();
321
293
  if (event.surfaceOp !== "append") continue;
322
294
  if (event.data.source.kind === "user" || isMessageToolsRestore(event)) entries.push({
323
295
  role: "user",
@@ -332,17 +304,40 @@ function replayEntries(events, spanSeqs) {
332
304
  continue;
333
305
  }
334
306
  if (event.type === "assistant/message") {
335
- finalizedSteps.add(`${event.data.turn}:${event.data.step}`);
336
- if (pending !== void 0 && pending.turn === event.data.turn && pending.step === event.data.step) pending.finalized = true;
337
- flushPending();
338
307
  pushAssistantText(joinAssistantMessageText(event.data.message.content), seq);
339
308
  continue;
340
309
  }
341
310
  }
342
- flushPending();
343
311
  return entries;
344
312
  }
345
313
  /**
314
+ * Join one attempt's compact stream into its visible text. Text deltas win;
315
+ * reasoning is the fallback so an attempt that streamed only reasoning does
316
+ * not lose the only assistant content the user saw (the same preference
317
+ * {@link joinAssistantMessageText} applies to finalized messages). Packed
318
+ * delta runs append per block index; a raw `block-end` record carries the
319
+ * assembled block and replaces its index's accumulated deltas. Tool-call
320
+ * runs never replay (their side effects are not replayable).
321
+ */
322
+ function joinAttemptStreamText(stream) {
323
+ const text = /* @__PURE__ */ new Map();
324
+ const reasoning = /* @__PURE__ */ new Map();
325
+ const append = (map, index, fragment) => {
326
+ map.set(index, (map.get(index) ?? "") + fragment);
327
+ };
328
+ for (const record of stream) if (record.type === "text-chunks") append(text, record.index, record.texts.join(""));
329
+ else if (record.type === "reasoning-chunks") append(reasoning, record.index, record.texts.join(""));
330
+ else if (record.type === "chunk") {
331
+ const chunk = record.chunk;
332
+ if (chunk.type === "text-delta") append(text, chunk.index, chunk.text);
333
+ else if (chunk.type === "reasoning-delta") append(reasoning, chunk.index, chunk.text);
334
+ else if (chunk.type === "block-end" && chunk.block.type === "text") text.set(chunk.index, chunk.block.text);
335
+ else if (chunk.type === "block-end" && chunk.block.type === "reasoning") reasoning.set(chunk.index, chunk.block.text);
336
+ }
337
+ const joined = [...text.values()].join("");
338
+ return joined !== "" ? joined : [...reasoning.values()].join("");
339
+ }
340
+ /**
346
341
  * Plan a restore: the target must be an editable user message (the same
347
342
  * vocabulary as withdrawal — original, restore replay, or edit replacement)
348
343
  * that is no longer on the live surface (i.e. withdrawn). Restoring an edit
@@ -520,8 +515,8 @@ let MessageToolsService = (() => {
520
515
  }), {
521
516
  surfaceOp: {
522
517
  op: "replace",
523
- start: SessionSeq(planned.plan.start),
524
- end: SessionSeq(planned.plan.end)
518
+ startSeq: SessionSeq(planned.plan.start),
519
+ endSeq: SessionSeq(planned.plan.end)
525
520
  },
526
521
  sourceEventSeqs: planned.plan.sourceEventSeqs.map((seq) => SessionSeq(seq))
527
522
  });
@@ -617,8 +612,8 @@ let MessageToolsService = (() => {
617
612
  }), {
618
613
  surfaceOp: {
619
614
  op: "replace",
620
- start: SessionSeq(planned.plan.start),
621
- end: SessionSeq(planned.plan.end)
615
+ startSeq: SessionSeq(planned.plan.start),
616
+ endSeq: SessionSeq(planned.plan.end)
622
617
  },
623
618
  sourceEventSeqs: planned.plan.sourceEventSeqs.map((seq) => SessionSeq(seq))
624
619
  });
@@ -12,7 +12,7 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
12
12
  */
13
13
  import { memo, useEffect, useRef, useState } from 'react';
14
14
  import { shallowEqual } from '@deepseek-ai/dsh-client-store';
15
- import { Button, IconCheckOutline16, IconCopyOutline16, IconEditOutline16, JsonBlock, MessageText, RiskConfirmation, Tooltip, writeClipboard, } from '@deepseek-ai/dsh-client-ui-primitives';
15
+ import { Button, IconCheckOutline16, IconCopyOutline16, IconEditOutline16, JsonBlock, RiskConfirmation, Tooltip, writeClipboard, } from '@deepseek-ai/dsh-client-ui-primitives';
16
16
  import { IconUndoOutline16 } from './icons.js';
17
17
  import { foldHiddenRanges, isSeqHidden } from './withdrawn-node.js';
18
18
  import { chatHookOf } from './chat-hook.js';
@@ -51,15 +51,18 @@ function projectUserText(text) {
51
51
  const tokenStart = m.index + (m[1]?.length ?? 0);
52
52
  /* v8 ignore next -- the token group always participates in a match, so m[2] is never undefined. */
53
53
  const label = m[2] ?? '';
54
+ // Plain runs render through the local text-run block (the official
55
+ // `MessageText` primitive was retired in host 0.1.5; `.textRun` keeps its
56
+ // exact metrics).
54
57
  if (tokenStart > cursor)
55
- parts.push(_jsx(MessageText, { text: text.slice(cursor, tokenStart) }, cursor));
58
+ parts.push(_jsx("div", { className: css.textRun, children: text.slice(cursor, tokenStart) }, cursor));
56
59
  parts.push(_jsx("span", { className: css.refChip, "data-ref-chip": label.startsWith('@') ? 'subagent' : 'skill', children: label }, tokenStart));
57
60
  cursor = tokenStart + label.length;
58
61
  }
59
62
  if (parts.length === 0)
60
- return _jsx(MessageText, { text: text });
63
+ return _jsx("div", { className: css.textRun, children: text });
61
64
  if (cursor < text.length)
62
- parts.push(_jsx(MessageText, { text: text.slice(cursor) }, cursor));
65
+ parts.push(_jsx("div", { className: css.textRun, children: text.slice(cursor) }, cursor));
63
66
  return _jsx(_Fragment, { children: parts });
64
67
  }
65
68
  /** Copy action: the copy icon swaps to a short-lived check after a successful write. */
@@ -12,7 +12,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
12
12
  * once withdrawals backfill the draft automatically.
13
13
  */
14
14
  import { useState } from 'react';
15
- import { Button, IconChevronDownOutline14, IconChevronRightOutline14, MessageText } from '@deepseek-ai/dsh-client-ui-primitives';
15
+ import { Button, IconChevronDownOutline14, IconChevronRightOutline14 } from '@deepseek-ai/dsh-client-ui-primitives';
16
16
  import { IconUndoOutline16 } from './icons.js';
17
17
  import { collectWithdrawnEntries, countHiddenInSpan, foldHiddenRanges, hasRestoreForSpan, isRestoreSuperseded, } from './withdrawn-node.js';
18
18
  import { chatHookOf } from './chat-hook.js';
@@ -59,5 +59,5 @@ export function WithdrawnDividerView({ node, t, restoreMessage, ...standard }) {
59
59
  .catch(() => { setFailed(true); })
60
60
  .finally(() => { setBusy(false); });
61
61
  };
62
- return (_jsxs("div", { className: css.row, children: [_jsxs("button", { type: "button", className: css.marker, "aria-expanded": expanded, "aria-label": t('withdrawn.expand'), onClick: toggle, children: [_jsx("span", { className: css.leading, "aria-hidden": true, children: expanded ? _jsx(IconChevronDownOutline14, {}) : _jsx(IconChevronRightOutline14, {}) }), _jsx("span", { className: css.title, children: t('withdrawn.divider', { count }) }), restored && (_jsxs("span", { className: css.badge, children: [_jsx(IconUndoOutline16, {}), t('withdrawn.restored')] }))] }), expanded && (_jsxs("div", { className: css.body, children: [entries === null || entries.length === 0 ? (_jsx("div", { className: css.empty, children: t('withdrawn.empty') })) : (entries.map((entry, index) => (_jsxs("div", { className: entry.kind === 'user' ? css.entryUser : css.entryAssistant, children: [_jsx("div", { className: css.entryKind, children: entry.kind === 'user' ? t('withdrawn.entryUser') : t('withdrawn.entryAssistant') }), _jsx(MessageText, { text: entry.text })] }, index)))), _jsxs("div", { className: css.restoreRow, children: [_jsx(Button, { variant: "outline", disabled: busy || restored || superseded, onClick: restore, children: t('withdrawn.restore') }), failed && _jsx("span", { className: css.restoreFailed, role: "status", children: t('withdrawn.restoreFailed') })] })] }))] }));
62
+ return (_jsxs("div", { className: css.row, children: [_jsxs("button", { type: "button", className: css.marker, "aria-expanded": expanded, "aria-label": t('withdrawn.expand'), onClick: toggle, children: [_jsx("span", { className: css.leading, "aria-hidden": true, children: expanded ? _jsx(IconChevronDownOutline14, {}) : _jsx(IconChevronRightOutline14, {}) }), _jsx("span", { className: css.title, children: t('withdrawn.divider', { count }) }), restored && (_jsxs("span", { className: css.badge, children: [_jsx(IconUndoOutline16, {}), t('withdrawn.restored')] }))] }), expanded && (_jsxs("div", { className: css.body, children: [entries === null || entries.length === 0 ? (_jsx("div", { className: css.empty, children: t('withdrawn.empty') })) : (entries.map((entry, index) => (_jsxs("div", { className: entry.kind === 'user' ? css.entryUser : css.entryAssistant, children: [_jsx("div", { className: css.entryKind, children: entry.kind === 'user' ? t('withdrawn.entryUser') : t('withdrawn.entryAssistant') }), _jsx("div", { className: css.textRun, children: entry.text })] }, index)))), _jsxs("div", { className: css.restoreRow, children: [_jsx(Button, { variant: "outline", disabled: busy || restored || superseded, onClick: restore, children: t('withdrawn.restore') }), failed && _jsx("span", { className: css.restoreFailed, role: "status", children: t('withdrawn.restoreFailed') })] })] }))] }));
63
63
  }
@@ -26,7 +26,7 @@ export const withdrawnDividerDefinition = {
26
26
  kind: 'message-tools-withdrawn',
27
27
  target: 'chat',
28
28
  match: (event) => {
29
- // Packed chunk rows share the bus; the guards speak SessionEvent only.
29
+ // Attempt and boundary rows share the bus; the guards speak SessionEvent only.
30
30
  if (event.type !== 'user/message')
31
31
  return null;
32
32
  return isMessageToolsReplacement(event) ? { id: String(event.seq), role: 'start' } : null;
@@ -36,7 +36,7 @@ export const withdrawnDividerDefinition = {
36
36
  if (!isMessageToolsReplacement(event)) {
37
37
  throw new Error('message-tools-withdrawn start requires a message-tools replacement event');
38
38
  }
39
- return { seq: event.seq, hiddenStartSeq: event.surfaceOp.start };
39
+ return { seq: event.seq, hiddenStartSeq: event.surfaceOp.startSeq };
40
40
  },
41
41
  update: context => context.state,
42
42
  buildViewNode: context => context.state === undefined
@@ -66,7 +66,7 @@ export const editedMessageDefinition = {
66
66
  seq: event.seq,
67
67
  time: event.time,
68
68
  content: event.data.content,
69
- hiddenStartSeq: event.surfaceOp.start,
69
+ hiddenStartSeq: event.surfaceOp.startSeq,
70
70
  };
71
71
  },
72
72
  update: context => context.state,
@@ -86,7 +86,7 @@ let MessageToolsService = (() => {
86
86
  content: [{ type: 'text', text: WITHDRAWN_NOTICE }],
87
87
  source: { kind: 'plugin', plugin: MESSAGE_TOOLS_PLUGIN },
88
88
  }), {
89
- surfaceOp: { op: 'replace', start: SessionSeq(planned.plan.start), end: SessionSeq(planned.plan.end) },
89
+ surfaceOp: { op: 'replace', startSeq: SessionSeq(planned.plan.start), endSeq: SessionSeq(planned.plan.end) },
90
90
  sourceEventSeqs: planned.plan.sourceEventSeqs.map(seq => SessionSeq(seq)),
91
91
  });
92
92
  await this.ctx.sessions.flush(session);
@@ -158,7 +158,7 @@ let MessageToolsService = (() => {
158
158
  content: [{ type: 'text', text: request.text }],
159
159
  source: editReplacementSource(),
160
160
  }), {
161
- surfaceOp: { op: 'replace', start: SessionSeq(planned.plan.start), end: SessionSeq(planned.plan.end) },
161
+ surfaceOp: { op: 'replace', startSeq: SessionSeq(planned.plan.start), endSeq: SessionSeq(planned.plan.end) },
162
162
  sourceEventSeqs: planned.plan.sourceEventSeqs.map(seq => SessionSeq(seq)),
163
163
  });
164
164
  await this.ctx.sessions.flush(session);
@@ -44,8 +44,8 @@ export declare function stripRestoreAssistantFrame(text: string): string;
44
44
  export type MessageToolsReplacementEvent = SessionEvent<'user/message'> & {
45
45
  readonly surfaceOp: {
46
46
  readonly op: 'replace';
47
- readonly start: number;
48
- readonly end: number;
47
+ readonly startSeq: number;
48
+ readonly endSeq: number;
49
49
  };
50
50
  };
51
51
  /** One message-tools edit replacement event, narrowed (same shape, `op: 'edit'` source). */