dsh-edit-turn 0.2.19 → 0.2.20

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/README.md CHANGED
@@ -57,7 +57,7 @@ dsh plugin --profile web add /path/to/dsh-edit-turn
57
57
  编辑器是**平台输入框那样的一个圆角盒子**:输入区在上、取消与主按钮在右下,聚焦时整圈描边高亮;**随文字长高**(空的时候不留空白),没有标题栏、没有常驻说明、没有拖拽手柄。键盘也和输入框一致:**Enter 保存、Shift+Enter 换行、Esc 取消**(确认步里 Esc 是退回上一步)。**输入法优先**:组合中的 Enter 与"候选选词的 Enter"(`isComposing` 或 `keyCode 229`,平台自己的输入框也是这两条一起挡)都不会被当成保存,组合期间也不重排输入框——此前少挡了 229 那半,拼音选词按回车会直接保存/关闭,看起来就是"打不了中文"。
58
58
 
59
59
  编辑器里,消息带的图片与文件**各显示成一个芯片**(缩略图或文件名 + 大小 + 删除按钮):不碰就原样保留,点删除就是不要它了;旁边还有一个「**添加附件**」(这条消息本来没有附件时也能用)。缩略图由本插件的只读路由按需取回;取不回来、或那些字节不是图片,芯片退回显示名字。**只有当这个部署没有附件存储时**,编辑器才会改回旧的那句话——「这条消息包含图片或文件附件,改写会丢弃它们,只保留文字」——并且不出现添加入口。
60
- **一字不改就按保存,什么都不会发生。** 编辑器预填的就是这条消息**现在**的文本,所以「打开编辑器、不输入、直接保存」等于没提出任何修改:宿主不写任何事件(不落替换、不开合成轮次、不追加回答),编辑器关闭并提示「内容没有变化,未做任何修改」。会话日志、轮次导轨、轨迹视图都保持原样——此前它照样落一条替换(一次会话里三条),改回答时还会多出一整个轮次(用户只发了两条消息,轨迹视图里却有四轮)。**「重跑」不受这条守卫影响**:文本没变但用户点的是「重跑」,宿主同样不写,重跑照常发起(用户的意图就是让这一轮重新生成)。
60
+ **一字不改就按保存,什么都不会发生。** 编辑器预填的就是这条消息**现在**的文本,所以「打开编辑器、不输入、直接保存」等于没提出任何修改:宿主不写任何事件(不落替换、不开合成轮次、不追加回答),编辑器照常关闭、**一条提示都不显示**(0.2.20 起;这条提示此前压在输入框的占位文字上)。会话日志、轮次导轨、轨迹视图都保持原样——此前它照样落一条替换(一次会话里三条),改回答时还会多出一整个轮次(用户只发了两条消息,轨迹视图里却有四轮)。**「重跑」不受这条守卫影响**:文本没变但用户点的是「重跑」,宿主同样不写,重跑照常发起(用户的意图就是让这一轮重新生成)。
61
61
 
62
62
  **编辑模型的回答**:悬停任意一条模型回复,同样会出现编辑入口。改完点「保存」——这条回答被替换为你写的内容,它之后的内容一并移除,模型从此把你写的内容当成自己说过的话,对话可以继续下去。
63
63
 
@@ -98,7 +98,7 @@ session.append('developer/message', { turn, step, message: { role: 'developer',
98
98
 
99
99
  **重跑不由本插件做。** "改完立刻重答"是姊妹插件 `dsh-rerun-turn` 的操作:它遮蔽该轮、用表面现在显示的提示词(也就是你改后的文本)重新生成,再把后续轮次逐事件重放回来。本插件只负责把编辑落地;编辑器里的「重跑」按钮仅在检测到它挂载时出现,点击后先保存、再调用它的 `POST /dsh-rerun-turn/apply`。它不在(例如桌面版没装)时,没有这个按钮。
100
100
 
101
- **文本没变就不写(0.2.18)。** 决定「写什么」的那一层在落任何事件之前,把请求里的文本与**当前活节点的文本**做**逐字**比较(`readMessageText`——`/state` 的 `turns[].text` / `replies[].text` 用的就是它,所以编辑器预填的文本与这里的比较对象天然同源)。相同则直接返回 `{ applied:false, unchanged:true, shadowed:[] }`:不落替换、不 flush、不开轮次、不动活循环的轮次计数器。比较**不 trim**:只有空白差异(哪怕多一个尾随空格)也算真修改,走原来的写入路径,行为与本版之前完全一致。`unchanged` 是给调用方的**标志**而不是拒绝——路由照常 200 返回,编辑器「重跑」的链式调用照常发起;所以这道守卫只拦得住「写」,拦不住用户要的重跑。
101
+ **文本没变就不写(0.2.18)。** 决定「写什么」的那一层在落任何事件之前,把请求里的文本与**当前活节点的文本**做**逐字**比较(`readMessageText`——`/state` 的 `turns[].text` / `replies[].text` 用的就是它,所以编辑器预填的文本与这里的比较对象天然同源)。相同则直接返回 `{ applied:false, unchanged:true, shadowed:[] }`:不落替换、不 flush、不开轮次、不动活循环的轮次计数器。比较**不 trim**:只有空白差异(哪怕多一个尾随空格)也算真修改,走原来的写入路径,行为与本版之前完全一致。`unchanged` 是给调用方的**标志**而不是拒绝——路由照常 200 返回,编辑器「重跑」的链式调用照常发起;所以这道守卫只拦得住「写」,拦不住用户要的重跑。客户端读到这个标志只做一件事:不把 no-op 当成 `applied:false` 的失败(否则会误报「已回退,但替换没有落地」);**它不显示任何提示**——0.2.20 删掉了那条「内容没有变化,未做任何修改。」,原样保存就是编辑器照常关闭、屏幕上不多一个字。
102
102
 
103
103
  **块随消息一起改(0.2.19)。** 载体不再写死成一个 text 块,而是照**提交的块列表**重建:列表里只有两种条目——`{ keep: <index> }`(原文里那一块,原样搬)与 `{ add: {...} }`(用户刚选的字节,录用后写进载体)。"原样搬"是 `structuredClone` 出来的原文块对象,**逐字节一致**;删掉的块就是**不在列表里**,因此不写;新文字落在原文**第一个文字块**的位置,其余块维持相对顺序(`planBlockLayout` / `assembleBlocks`)——真实日志里 `text → image` 与 `image → text` 两种顺序都常见,所以不重排。整个模块里**只有一处**块判断(`isTextBlock`:这个块里有文字吗),别的什么类型都不认识。录用走平台公开的附件服务:`ctx.get('attachments')`(`dsh-attachment` 的 `AttachmentStore`,`dsh-acp` 用的同一个 seam;另接受 `attachment` / `attachment-local` 两个包名拼写)的 `saveImage` / `saveFile`,**每次请求现场探测**;服务不在就退回只写文字,响应带 `dropped: true`,**一个引用都不会写进日志**。录用发生在任何 `session.append` 之前:被拒(超限、字节非法)时返回 400 `attachment-refused`,日志与轮次一个字节都不动。
104
104
 
@@ -177,7 +177,7 @@ ln -s /path/to/dsh-install/node_modules ./node_modules
177
177
  - 编辑一条用户消息**只替换那一条**,**保存永不调用模型**;编辑一条模型回答只替换内容,同样不问模型。想立刻重答,用 `dsh-rerun-turn` 的「重跑」。
178
178
  - **提示词编辑只动那一条**:它下面的回复与之后的对话**原样保留**。**回复编辑**才会移除该回答及其后的内容——回答变了,建立在它之上的一切都不再成立。想保留原文形成分支,需要走 `sessionController.fork({ sessionId, atSeq })`,尚未实现。
179
179
  - **消息里的块随消息一起改,回答里的块不随**。用户消息的图片/文件(以及平台以后加的块类型)会原样保留、可单个删除、可新增(见「工作原理」);**模型回答里的工具调用与思考过程无法保留**——它们一旦离开原轮次就不再成立,所以回答编辑仍是纯文本,编辑器照旧提示。
180
- - **文本一字不差时保存不写任何记录**(0.2.18):编辑器预填的就是这条消息**现在**的文本,原样保存不会写入任何事件(不落替换、不开轮次),编辑器关闭并提示「内容没有变化」,日志与轨迹都保持原样;**「重跑」例外,它照常发起**。副作用:一条带**工具调用或思考过程**的模型回答,如果你一字不改地保存(本意只是想借替换把它们丢掉),本版不会再替换它,那些调用会留在上下文里——要丢掉它们,请把文本改成与原文不同的内容再保存。
180
+ - **文本一字不差时保存不写任何记录**(0.2.18):编辑器预填的就是这条消息**现在**的文本,原样保存不会写入任何事件(不落替换、不开轮次),编辑器照常关闭、**不弹任何提示**(0.2.20 起;此前那条「内容没有变化」就压在输入框的占位文字上),日志与轨迹都保持原样;**「重跑」例外,它照常发起**。副作用:一条带**工具调用或思考过程**的模型回答,如果你一字不改地保存(本意只是想借替换把它们丢掉),本版不会再替换它,那些调用会留在上下文里——要丢掉它们,请把文本改成与原文不同的内容再保存。
181
181
  - **会话必须当前在 DSH 中打开**,否则返回 `409 session-not-active`。
182
182
  - **进行中拒绝编辑**:未闭合的轮次或正在压缩时返回 `409 busy`。
183
183
  - **回退是持久的,隐藏不是**。回退写进日志后,模型上下文永久改变;转录里那些行的隐藏是本插件客户端半部做的。卸载插件后,旧行会重新显示出来(而模型上下文里的回退仍然生效)——因为替换事件是官方事件,不会随插件消失。
@@ -225,6 +225,16 @@ ln -s /path/to/dsh-install/node_modules ./node_modules
225
225
 
226
226
  ### 更新日志
227
227
 
228
+ **0.2.20** —— 删掉「原样保存」时那条提示。只改提示,行为不变。
229
+
230
+ - **症状**:一字不改按保存,宿主本来就不写任何事件(0.2.18 的守卫),但客户端还会弹一条「内容没有变化,未做任何修改。」——而它在真机上是**压在输入框的占位文字上**渲染的(截图可见重叠)。这条提示既没有信息量(用户刚做的动作就是"什么都没做"),位置也不对。
231
+ - **改法**:`confirm()` 里那句 `if (!rerunIntent) this.notify('unchanged')` 删除。**`unchanged` 标志本身原样保留**——它唯一的职责是"不把 no-op 当成 `applied:false` 的失败"(否则会误报「已回退,但替换没有落地」):判断改为 `if (data.unchanged !== true) { …失败分支… }`(`lib/client.js:768`),语义与之前逐字等价。字典里已经没人引用的 `error.unchanged`(中英各一条)一并删除,**别的 `error.*` 未动**。
232
+ - **重跑链路一行未动**:`if (rerunIntent) this.startSiblingRerun(target.turn)`(`lib/client.js:783`)仍在那个 if **之外**。文本没变点「重跑」:宿主不写(它只是标志)、重跑照常发起——只是不再有横幅。宿主侧 `applyEdit` 也一字未改:no-op 仍返回 `{ applied:false, unchanged:true, shadowed:[] }`(`lib/index.js:1427`),`/dsh-edit-turn/debug` 仍如实记录 `unchanged`(`lib/index.js:1897`)。
233
+ - **原样保存,现在用户看到什么**:编辑器照常关闭,屏幕上**不多一个字**——没有横幅、没有浮动节点,消息行、铅笔、日志、轮次导轨、轨迹视图全部保持原样。
234
+ - **English**: the no-op guard still answers `unchanged: true`, but the browser half no longer announces it. The flag keeps its only job - telling a no-op apart from a failed apply, so the old "rolled back, but the replacement did not land" wording can never fire for it - while the notice it used to draw (on top of the composer's placeholder) is gone from the client and from both dictionaries. The re-run chain is untouched: an unchanged draft plus "Re-run" still writes nothing and re-runs the turn.
235
+ - 本版:`npm test` **182/182**、`npm run verify:contract` 80/80、`npm run verify:client` **184/184**(`npm run check` exit 0)。原来那条钉「原样保存会提示」的 DOM 用例改写为「**不出现任何提示**」——`notice === null`、组件树里没有 `dshet-notice`、DOM 里也没有;「文本没变但点重跑 → 重跑照常发起」那条一行未动,仍然绿。
236
+ - **负向验证**(`/tmp/dshet-noop-notice` 副本,仓库文件未动):把 `notify('unchanged')` 放回 `confirm()`(连同 0.2.20 之前那个 if/else 形状)→ **恰好 1 条**转红,正是改写后的那条「原样保存不弹任何提示」;「重跑照常发起」不受影响(放回的调用只在**没有**重跑意图时执行)。
237
+
228
238
  **0.2.19** —— 改写消息对所有内容块通用:图片、文件、以及平台以后加的块都随消息一起改。
229
239
 
230
240
  - **症状**:改写一条带图/带文件的消息,**非文字块必然丢**。`readMessageText` 只取 text 块(其余只累加 `attachments` 计数),`buildCarrier` 写死 `content: [{ type: 'text', text }]`,宿主里也没有拒绝路径——那个计数唯一的用途就是让客户端画一行「改写会丢弃它们」,等于把数据丢失写进了说明文案。
@@ -245,10 +255,10 @@ ln -s /path/to/dsh-install/node_modules ./node_modules
245
255
  - **守卫的位置**:`applyEdit`(宿主半部)里、算出回退窗口并确认目标仍在 surface 上之后、**任何 `session.append` 之前**。文本没变则直接返回 `{ kind, applied:false, unchanged:true, shadowed:[], original }`——不落替换、不 flush、不开轮次、不动活循环的 `lastTurn`。
246
256
  - **怎么比较**:`text === plan.original`,而 `plan.original` 就是 `readMessageText(target)` 的结果,也就是 `/state` 的 `turns[].text` / `replies[].text` 用的**同一份解析**(编辑器预填的正是它)。**逐字比较、不 trim**:只有空白差异(多一个尾随空格)仍算真修改,走原来的写入路径,行为与旧版**完全一致**——这是防回归的关键一条。
247
257
  - **两条路都走同一个守卫**:改提问(`user/message` 载体)不写替换;改回答(`developer/message` 空载体 + 合成轮次 + 新 `assistant/message`)既不写替换,也**不开轮次、不追加回答**。
248
- - **「重跑」不被守卫拦掉(关键陷阱)**:编辑器的「重跑」= 先保存、再链式调用 `POST /dsh-rerun-turn/apply`,所以守卫只能拦「写」。`unchanged` 是**标志**而非拒绝:路由照常 200,客户端读到它以后,`confirm()` 里那句 `if (rerunIntent) this.startSiblingRerun(target.turn)` 仍然执行(它与「写没写」无关,不被 `applied === false` 的报错分支吃到),只在**没有**重跑意图时才提示「内容没有变化」。
258
+ - **「重跑」不被守卫拦掉(关键陷阱)**:编辑器的「重跑」= 先保存、再链式调用 `POST /dsh-rerun-turn/apply`,所以守卫只能拦「写」。`unchanged` 是**标志**而非拒绝:路由照常 200,客户端读到它以后,`confirm()` 里那句 `if (rerunIntent) this.startSiblingRerun(target.turn)` 仍然执行(它与「写没写」无关,不被 `applied === false` 的报错分支吃到),只在**没有**重跑意图时才提示「内容没有变化」(该提示已在 0.2.20 删除)。
249
259
  - **客户端只做三件事**:不把 `unchanged` 当成 `applied:false` 的失败(旧文案会说「已回退,但替换没有落地」)、不隐藏任何行(`shadowed` 为空,消息留在屏上、铅笔还在)、照常把重跑交出去。
250
260
  - **English**: saving a draft that is verbatim the text the message already carries now writes NOTHING - no replacement event, no synthetic turn, no appended answer - and answers `unchanged:true`. The decision lives in the host, in the layer that decides what to write, and compares against the same parse the editor was filled with (`readMessageText`), verbatim and without trimming. The flag is not a refusal: the editor still starts the sibling re-run when that is what the user pressed.
251
- - 本版:`npm test` **147/147**、`npm run verify:contract` 80/80、`npm run verify:client` **163/163**(`npm run check` exit 0);新增 4 条宿主用例(改提问不写、改回答不写且不新增轮次、真改了仍逐字写入且随后同样的保存成为 no-op、no-op 不越过 busy/not-editable 等既有拒绝)与 2 条 DOM 用例(原样保存 → 提示且不隐藏行、不发重跑;文本没变但点「重跑」→ 不写但重跑照常发起)。
261
+ - 本版:`npm test` **147/147**、`npm run verify:contract` 80/80、`npm run verify:client` **163/163**(`npm run check` exit 0);新增 4 条宿主用例(改提问不写、改回答不写且不新增轮次、真改了仍逐字写入且随后同样的保存成为 no-op、no-op 不越过 busy/not-editable 等既有拒绝)与 2 条 DOM 用例(原样保存 → 提示且不隐藏行、不发重跑;文本没变但点「重跑」→ 不写但重跑照常发起)。**其中「原样保存 → 提示」这一条已在 0.2.20 改写为「原样保存 → 不出现任何提示」**,因为那条提示本身被删掉了。
252
262
  - **负向验证**(`/tmp` 副本,仓库文件未动):去掉宿主守卫 → **恰好 3 条**新用例转红(144/147);把客户端的 `data.unchanged === true` 改成 `false` → **恰好 2 条**新用例转红;把 `unchanged` 分支改成提前 return(即“在路由/客户端直接 return 掉整个 apply”那种错法)→ **恰好 1 条**转红,正是「文本没变但走重跑 → 重跑仍发起」。
253
263
 
254
264
  **0.2.17** —— 修「改写过的提示词,复制出来还是旧文本」。只修 Bug,交互语义不变。
@@ -416,7 +426,7 @@ This plugin adds it:
416
426
  - **The smallest window that is correct.** A prompt edit shadows exactly one node (`shadowed = [target.seq]`); a reply edit shadows the reply through the end of the surface - a changed answer invalidates everything built on it - which also keeps an assistant message (carrying its own tool_use blocks) and the tool/result it produced together. A dangling call/result pair is impossible.
417
427
  - **A rewritten message stays editable.** It still shows where the original stood, and hovering it offers the pencil again.
418
428
  - **Saving never calls the model.** It only writes the context. Regenerating a turn after the edit is a different operation, owned by the sister plugin **dsh-rerun-turn** (it shadows the turn, regenerates from the prompt the surface now shows, and replays the turns that followed); this plugin only forwards to it, through the "Re-run" button, when it is mounted.
419
- - **A save that changes nothing writes nothing** (0.2.18). The layer that decides what to write compares the draft with the live text **verbatim**, through the same parse the editor was prefilled from (`readMessageText`, the one behind `/state`’s `turns[].text`), and answers `unchanged: true` before appending anything: no replacement event, no synthetic turn, no appended answer, no loop-counter sync. The flag is not a refusal - the route still answers 200, and the editor still starts the re-run when that was the button the user pressed.
429
+ - **A save that changes nothing writes nothing** (0.2.18). The layer that decides what to write compares the draft with the live text **verbatim**, through the same parse the editor was prefilled from (`readMessageText`, the one behind `/state`’s `turns[].text`), and answers `unchanged: true` before appending anything: no replacement event, no synthetic turn, no appended answer, no loop-counter sync. The flag is not a refusal - the route still answers 200, and the editor still starts the re-run when that was the button the user pressed. Nothing is announced for it either (0.2.20 removed the notice this used to draw): the editor closes like any save, and the screen is exactly as it was.
420
430
  - **The model's replies are editable too.** A reply cannot be swapped in place: the format refuses `sourceEventSeqs` on an `assistant/message` (verified against the real validator). The answer is rolled back together with everything after it, and the corrected text is **appended** as a fresh reply - the model goes on treating it as its own. `source.editedBy` records honestly that the plugin wrote those words.
421
431
  - **One click applies** - no second confirmation by default. The editor is already an explicit action the user opened; with `confirm: true` the confirmation step states what this save will discard, while the input box itself stays clean.
422
432
  - **Bilingual UI** that follows the current DSH locale.
@@ -463,8 +473,9 @@ attachments; rewriting keeps the text only" - and no picker at all.
463
473
  **Saving a draft that is word-for-word what the message already says does
464
474
  nothing at all.** The editor was prefilled with that text, so an untouched save
465
475
  is not a request to change anything: the host writes no event - no replacement,
466
- no synthetic turn, no fresh answer - closes the editor and says
467
- "nothing changed". The log, the turn rail and the Trajectory view stay exactly
476
+ no synthetic turn, no fresh answer - and the editor closes in silence, with no
477
+ banner at all (0.2.20; that notice used to be drawn on top of the composer's
478
+ placeholder text). The log, the turn rail and the Trajectory view stay exactly
468
479
  as they were. **"Re-run" is not affected**: an unchanged draft plus that button
469
480
  still re-runs the turn, because regenerating it is what the user asked for.
470
481
 
@@ -505,7 +516,7 @@ node, then:
505
516
  the corrected text is appended as an assistant message inside that turn, and
506
517
  `step/end` + `turn/end` close it behind the correction.
507
518
 
508
- **An unchanged draft is not written at all** (0.2.18): the layer that decides what to write compares the request’s text with the live node’s text - through the same `readMessageText` the editor was prefilled from - and returns `{ applied: false, unchanged: true, shadowed: [] }` **before** appending anything: no replacement, no flush, no turn, no loop-counter sync. The comparison is verbatim and does not trim, so any real difference (a trailing space included) takes the old path unchanged. `unchanged` is a flag for the caller, never a refusal: the route still answers 200, and the editor’s "Re-run" chain still starts (its whole point is to regenerate the turn even when the wording is what it already was).
519
+ **An unchanged draft is not written at all** (0.2.18): the layer that decides what to write compares the request’s text with the live node’s text - through the same `readMessageText` the editor was prefilled from - and returns `{ applied: false, unchanged: true, shadowed: [] }` **before** appending anything: no replacement, no flush, no turn, no loop-counter sync. The comparison is verbatim and does not trim, so any real difference (a trailing space included) takes the old path unchanged. `unchanged` is a flag for the caller, never a refusal: the route still answers 200, and the editor’s "Re-run" chain still starts (its whole point is to regenerate the turn even when the wording is what it already was). The browser half reads that flag only to keep a no-op out of the failed-apply wording, and draws nothing for it (0.2.20).
509
520
 
510
521
  Then append:
511
522
 
@@ -811,7 +822,7 @@ ln -s /path/to/dsh-install/node_modules ./node_modules
811
822
  - **Editing a user message replaces that one message and leaves the rest of the conversation untouched; saving never calls the model.** For an immediate answer, use `dsh-rerun-turn`'s "Re-run"; editing a model reply only replaces the text, too.
812
823
  - **A prompt edit touches only that message.** The reply under it and the whole conversation after it are kept; saving never calls the model. A **reply edit** removes that answer and everything built on it, because a changed answer invalidates what followed. Keeping the original as a branch needs `sessionController.fork({ sessionId, atSeq })`, which is not implemented.
813
824
  - **Blocks travel with a user message, not with a reply.** The images and files on your own message are kept as they are, can be removed one by one and new ones can be added (see "How it works"); the **tool calls and reasoning inside a model reply cannot survive** - they stop being true the moment the reply is replaced - so a reply edit is still text only, and the editor says so.
814
- - **A draft identical to the live text writes nothing** (0.2.18): saving it as-is closes the editor with a "nothing changed" notice and leaves both the log and the rail alone; "Re-run" is the exception and still starts. The consequence worth knowing: a reply edit that is *textually* identical - the one case where you might have wanted the save only to drop that reply’s tool calls or reasoning - is now a no-op, and those calls stay in the context.
825
+ - **A draft identical to the live text writes nothing** (0.2.18): saving it as-is closes the editor in silence - no notice at all since 0.2.20, before that a "nothing changed" banner that landed on the composer's placeholder - and leaves both the log and the rail alone; "Re-run" is the exception and still starts. The consequence worth knowing: a reply edit that is *textually* identical - the one case where you might have wanted the save only to drop that reply’s tool calls or reasoning - is now a no-op, and those calls stay in the context.
815
826
  - **The session must be open in DSH**, otherwise the route answers `409 session-not-active`.
816
827
  - **Running work is refused**: an unclosed turn or an in-flight compaction answers `409 busy`.
817
828
  - **The rollback is durable; the hiding is not.** Once written, the rollback permanently changes the model context. Hiding those rows in the transcript is this plugin's browser half. Uninstall the plugin and the old rows reappear - while the rollback in the model context still stands, because the replacement is an official event that outlives the plugin.
package/lib/client.js CHANGED
@@ -42,7 +42,7 @@ window.__ModuleLoader__.load({
42
42
  const RERUN_PREFIX = '/dsh-rerun-turn'
43
43
 
44
44
  /** Keep in sync with package.json and lib/index.js. */
45
- const PLUGIN_VERSION = '0.2.19'
45
+ const PLUGIN_VERSION = '0.2.20'
46
46
 
47
47
  // --- copy -----------------------------------------------------------------
48
48
 
@@ -85,7 +85,6 @@ window.__ModuleLoader__.load({
85
85
  'error.prompt': '已回退,但改后的内容没能写进上下文。请重试。',
86
86
  'error.reply': '已回退,但替换没有落地。请重试。',
87
87
  'error.empty': '改写后的内容不能为空。',
88
- 'error.unchanged': '内容没有变化,未做任何修改。',
89
88
  'error.rerun-nothing': '已保存,但这一轮没有可重跑的作答。',
90
89
  'error.rerun-unavailable': '已保存,但重跑没有开始,请稍后重试。',
91
90
  'error.not-rerunnable': '这一轮没有可重跑的作答。',
@@ -133,7 +132,6 @@ window.__ModuleLoader__.load({
133
132
  'error.prompt': 'Rolled back, but the re-run did not start. Send the revised text manually.',
134
133
  'error.reply': 'Rolled back, but the replacement did not land. Try again.',
135
134
  'error.empty': 'The revised message cannot be empty.',
136
- 'error.unchanged': 'Nothing changed, so no edit was made.',
137
135
  'error.rerun-nothing': 'Saved, but this turn has no reply that can be re-run.',
138
136
  'error.rerun-unavailable': 'Saved, but the re-run did not start; try again.',
139
137
  'error.not-rerunnable': 'This turn has no reply that can be re-run.',
@@ -748,10 +746,18 @@ window.__ModuleLoader__.load({
748
746
  failure: null,
749
747
  })
750
748
  // `unchanged` means the host wrote NOTHING: the draft is (verbatim)
751
- // the text the message already carries. There is no failure to report -
752
- // the edit was simply not an edit - so it is noticed as such, and
753
- // nothing else about this pass changes: no row was shadowed, so none is
754
- // hidden, and the message stays on screen with the pencil it had.
749
+ // the text the message already carries. The flag is read here for one
750
+ // reason, and it is not the user's: a no-op is not a failure. The
751
+ // `applied === false` wording below would claim the rollback landed
752
+ // and the replacement did not, which is a lie about an edit that was
753
+ // never attempted.
754
+ //
755
+ // Nothing is announced for it, deliberately (0.2.20 removed the
756
+ // notice this used to draw): the edit was simply not an edit, so the
757
+ // editor closes like any save and the screen is exactly as it was -
758
+ // no row was shadowed, so none is hidden, and the message keeps the
759
+ // pencil it had. (The banner also landed on top of the composer's
760
+ // placeholder text, which is not a place to report anything from.)
755
761
  //
756
762
  // The re-run intent survives it. "Re-run" saves first only because the
757
763
  // sibling regenerates the turn from the prompt the surface shows; a
@@ -759,9 +765,7 @@ window.__ModuleLoader__.load({
759
765
  // wording moved, so the chain below is NOT gated on a write having
760
766
  // happened. (The decision itself is the host's - the text is parsed
761
767
  // there, once - and this half only reads the flag it sends back.)
762
- if (data.unchanged === true) {
763
- if (!rerunIntent) this.notify('unchanged')
764
- } else {
768
+ if (data.unchanged !== true) {
765
769
  // The rollback has landed, so the editor is gone with its row: a
766
770
  // failed second half has to be reported somewhere that survives that.
767
771
  if (data.kind === 'reply' && data.applied === false) this.notify('reply')
package/lib/index.js CHANGED
@@ -69,7 +69,7 @@ export const PLUGIN_ID = 'dsh-edit-turn'
69
69
  export const name = PLUGIN_ID
70
70
 
71
71
  /** Keep in sync with package.json and lib/client.js. */
72
- export const PLUGIN_VERSION = '0.2.19'
72
+ export const PLUGIN_VERSION = '0.2.20'
73
73
 
74
74
  // A ring of the last few requests, answering "did the browser half even ask, and
75
75
  // what did it get?" without needing its console. Read-only diagnostics: the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-edit-turn",
3
- "version": "0.2.19",
3
+ "version": "0.2.20",
4
4
  "description": "Edit any user turn in DeepSeek Harness and re-run it: an inline editor on the message row rolls the conversation back to just before that turn through the official surface-replace contract, then re-prompts the agent with the revised text. The append-only session log is never rewritten.",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",