@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.
- package/CHANGELOG.md +8 -0
- package/README.en.md +4 -4
- package/README.i18n.yaml +2 -2
- package/README.md +4 -4
- package/lib/client.js +51 -36
- package/lib/index.js +52 -57
- package/lib/types/client/UserMessageView.js +7 -4
- package/lib/types/client/WithdrawnDividerView.js +2 -2
- package/lib/types/client/withdrawn-node.js +3 -3
- package/lib/types/index.js +2 -2
- package/lib/types/marker.d.ts +2 -2
- package/lib/types/withdraw.js +67 -75
- package/package.json +19 -19
- package/lib/client.js.map +0 -1
- package/lib/tsconfig.client.tsbuildinfo +0 -1
- package/lib/tsconfig.host.tsbuildinfo +0 -1
- package/lib/typert.remote-client.d.ts.map +0 -1
- package/lib/types/client/ModelChip.d.ts.map +0 -1
- package/lib/types/client/RestoredMessageView.d.ts.map +0 -1
- package/lib/types/client/UserMessageView.d.ts.map +0 -1
- package/lib/types/client/WithdrawnDividerView.d.ts.map +0 -1
- package/lib/types/client/backfill.d.ts.map +0 -1
- package/lib/types/client/chat-hook.d.ts.map +0 -1
- package/lib/types/client/dom-hider.d.ts.map +0 -1
- package/lib/types/client/edit-in-place.d.ts.map +0 -1
- package/lib/types/client/icons.d.ts.map +0 -1
- package/lib/types/client/index.d.ts.map +0 -1
- package/lib/types/client/locales.d.ts.map +0 -1
- package/lib/types/client/slots.d.ts.map +0 -1
- package/lib/types/client/withdraw-backfill.d.ts.map +0 -1
- package/lib/types/client/withdrawn-node.d.ts.map +0 -1
- package/lib/types/index.d.ts.map +0 -1
- package/lib/types/invariant.d.ts.map +0 -1
- package/lib/types/marker.d.ts.map +0 -1
- package/lib/types/types.d.ts.map +0 -1
- 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.
|
|
42
|
-
- source line (deepseek-harness master): ✅ (verifiedHost: 0.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
|
|
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
|
|
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:
|
|
6
|
-
packages/message-tools/README.md:
|
|
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.
|
|
42
|
-
- 源码线(deepseek-harness master):✅(verifiedHost: 0.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
|
|
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`
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
"
|
|
5071
|
-
"
|
|
5071
|
+
"triggerEffort": "he-h5G_triggerEffort",
|
|
5072
|
+
"option": "he-h5G_option",
|
|
5072
5073
|
"empty": "he-h5G_empty",
|
|
5073
5074
|
"selectFailed": "he-h5G_selectFailed",
|
|
5074
|
-
"
|
|
5075
|
-
"
|
|
5075
|
+
"groupTitle": "he-h5G_groupTitle",
|
|
5076
|
+
"chip": "he-h5G_chip",
|
|
5076
5077
|
"trigger": "he-h5G_trigger",
|
|
5077
|
-
"
|
|
5078
|
-
"
|
|
5079
|
-
"
|
|
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
|
-
"
|
|
5263
|
-
"
|
|
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
|
-
"
|
|
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
|
-
"
|
|
5273
|
-
"
|
|
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)(
|
|
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)(
|
|
5329
|
-
|
|
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
|
-
"
|
|
5602
|
-
"
|
|
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
|
-
"
|
|
5654
|
-
"marker": "JtQ9hG_marker",
|
|
5655
|
-
"restoreRow": "JtQ9hG_restoreRow",
|
|
5664
|
+
"textRun": "JtQ9hG_textRun",
|
|
5656
5665
|
"badge": "JtQ9hG_badge",
|
|
5657
|
-
"
|
|
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
|
-
"
|
|
5661
|
-
"
|
|
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)(
|
|
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/
|
|
230
|
-
* `assistant/message` ever landed).
|
|
231
|
-
* they are absent from `sourceEventSeqs`. The replacement's own `
|
|
232
|
-
* `seq` delimit the whole withdrawn log interval — `[
|
|
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.
|
|
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
|
|
253
|
-
* turn
|
|
254
|
-
* `
|
|
255
|
-
*
|
|
256
|
-
*
|
|
257
|
-
*
|
|
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
|
|
264
|
-
|
|
265
|
-
if (
|
|
266
|
-
|
|
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/
|
|
316
|
-
|
|
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
|
-
|
|
524
|
-
|
|
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
|
-
|
|
621
|
-
|
|
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,
|
|
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(
|
|
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(
|
|
63
|
+
return _jsx("div", { className: css.textRun, children: text });
|
|
61
64
|
if (cursor < text.length)
|
|
62
|
-
parts.push(_jsx(
|
|
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
|
|
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(
|
|
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
|
-
//
|
|
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.
|
|
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.
|
|
69
|
+
hiddenStartSeq: event.surfaceOp.startSeq,
|
|
70
70
|
};
|
|
71
71
|
},
|
|
72
72
|
update: context => context.state,
|
package/lib/types/index.js
CHANGED
|
@@ -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',
|
|
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',
|
|
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);
|
package/lib/types/marker.d.ts
CHANGED
|
@@ -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
|
|
48
|
-
readonly
|
|
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). */
|