dsh-rewind-plugin 0.12.1 → 0.12.2

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.en.md CHANGED
@@ -57,8 +57,8 @@ dsh plugin --profile web add dsh-rewind-plugin@<version>
57
57
 
58
58
  - Rewinds can be repeated — with no limit on stage or count.
59
59
  - A rewind itself **cannot be undone**, but the withdrawn content stays in the session log.
60
- - **Interruptions rewind too** — a `steering` interruption message the model hasn't read yet is also a valid rewind target.
61
- - **A rewind interrupts the running turn** — to ensure the rewind runs safely.
60
+ - **Interruptions rewind too** — a `steering` message the model hasn't read yet is also a valid rewind target, and withdrawing it does not interrupt the current run.
61
+ - **Rewinding a read message interrupts the running turn** — to ensure the rewind runs safely.
62
62
 
63
63
  </details>
64
64
 
package/README.md CHANGED
@@ -56,8 +56,8 @@ dsh plugin --profile web add dsh-rewind-plugin@<版本>
56
56
 
57
57
  - 回退可以反复进行——没有阶段或次数限制。
58
58
  - 回退本身**无法撤销**,但被撤回的内容仍保留在会话日志中。
59
- - **插话也能回退**——模型尚未读取的 `steering` 插话消息,同样可作为回退目标。
60
- - **回退会打断当前正在运行的回合**——确保回退的安全执行。
59
+ - **插话也能回退**——模型尚未读取的 `steering` 插话消息,同样可作为回退目标,不会打断当前生成。
60
+ - **回退已读消息会打断当前正在运行的回合**——确保回退的安全执行。
61
61
 
62
62
  </details>
63
63
 
package/lib/client.js CHANGED
@@ -1323,13 +1323,22 @@ function composerText() {
1323
1323
  return surface.textContent ?? "";
1324
1324
  }
1325
1325
  async function retractPending(session, itemId, text, setComposerText) {
1326
- await session.cancel();
1327
1326
  const queue = session.getSnapshot().queue;
1328
1327
  const steering = queue.filter((item) => item.placement === "steering");
1328
+ let removedTarget = false;
1329
1329
  for (const id of retractSpan(steering, itemId)) {
1330
- await session.updateQueue(id, { kind: "remove" });
1330
+ let accepted = false;
1331
+ try {
1332
+ const result = await session.updateQueue(id, { kind: "remove" });
1333
+ accepted = result.ok;
1334
+ if (!result.ok) rewindLog.info("retract", `pending retract lost the race for ${id}`, result.error);
1335
+ } catch (error) {
1336
+ rewindLog.warn("retract", `pending retract remove threw for ${id}`, error);
1337
+ }
1338
+ if (!accepted) break;
1339
+ if (id === itemId) removedTarget = true;
1331
1340
  }
1332
- if (text !== null && text !== "" && composerText().trim() === "") {
1341
+ if (removedTarget && text !== null && text !== "" && composerText().trim() === "") {
1333
1342
  setComposerText(session.sessionId, text);
1334
1343
  }
1335
1344
  }
@@ -1370,8 +1379,8 @@ function createRewindBridge(deps) {
1370
1379
  }
1371
1380
 
1372
1381
  // src/client/build-info.ts
1373
- var PLUGIN_VERSION = true ? "0.12.1" : "dev";
1374
- var BUILD_HASH = true ? "248f750a" : "dev";
1382
+ var PLUGIN_VERSION = true ? "0.12.2" : "dev";
1383
+ var BUILD_HASH = true ? "c9f4bb5f" : "dev";
1375
1384
 
1376
1385
  // src/client/locales.ts
1377
1386
  var zh = {
@@ -1383,7 +1392,7 @@ var zh = {
1383
1392
  "popover.noText": "\uFF08\u65E0\u6587\u672C\uFF09",
1384
1393
  "popover.retract.title": "\u56DE\u9000\u5230\u8FD9\u6761\u63D2\u8BDD\u6D88\u606F",
1385
1394
  "popover.retract.target": "\u63D2\u8BDD\u4E2D \xB7 {preview}",
1386
- "popover.retract.hint": "\u5C06\u505C\u6B62\u5F53\u524D\u751F\u6210\uFF0C\u5E76\u56DE\u9000\u5230\u8BE5\u6D88\u606F\u4E4B\u524D",
1395
+ "popover.retract.hint": "\u64A4\u56DE\u8FD9\u6761\u53CA\u4E4B\u540E\u7684\u63D2\u8BDD\u6D88\u606F\uFF0C\u4E0D\u6253\u65AD\u5F53\u524D\u751F\u6210",
1387
1396
  "popover.retract.confirm": "\u786E\u8BA4\u56DE\u9000",
1388
1397
  "popover.chat": "\u4EC5\u56DE\u9000\u5BF9\u8BDD",
1389
1398
  "popover.chat.hint": "\u53EA\u56DE\u9000\u6A21\u578B\u4E0A\u4E0B\u6587\uFF0C\u4E0D\u52A8\u5DE5\u4F5C\u533A\u6587\u4EF6",
@@ -1426,7 +1435,7 @@ var en = {
1426
1435
  "popover.noText": "(no text)",
1427
1436
  "popover.retract.title": "Rewind to this pending message",
1428
1437
  "popover.retract.target": "Pending \xB7 {preview}",
1429
- "popover.retract.hint": "Stops the current run and rewinds to before this message",
1438
+ "popover.retract.hint": "Retracts this pending message and the ones after it, without interrupting the run",
1430
1439
  "popover.retract.confirm": "Confirm rewind",
1431
1440
  "popover.chat": "Rewind conversation only",
1432
1441
  "popover.chat.hint": "Cut the model context only; workspace files stay untouched",
@@ -212,6 +212,30 @@ export declare function RewindPortals({ sessionId, sessionOf, chatOf, currentSes
212
212
  * Empty when the composer is absent. Exported as a test seam (the
213
213
  * empty-composer guard in `retractPending`). */
214
214
  export declare function composerText(): string;
215
+ /**
216
+ * Withdraw one pre-sent (pending steering) message and every steering message
217
+ * after it (the rollback point's "future"), WITHOUT interrupting the run.
218
+ *
219
+ * The message is still in the agent's next-step inbox, so the loop has not
220
+ * claimed it into any request: removing it cannot change what the model is
221
+ * generating. Stopping the run first (the durable-rewind rule) would throw
222
+ * away in-flight work — a long tool call, a partial answer — for an edit the
223
+ * model can never observe (SiriLee/dsh-rewind#27). Only the durable rewind
224
+ * needs a stop, because it cuts the model-visible surface.
225
+ *
226
+ * 1. Remove the target and its future (steering only; queued stays) through
227
+ * the session's own `updateQueue` channel, oldest first.
228
+ * 2. Put the target's text back in the composer ONLY when the target was
229
+ * actually removed and the composer is empty (Claude Code's auto-restore
230
+ * guard, so a draft the user is typing is never clobbered).
231
+ *
232
+ * The realistic failure is `queue-item-not-found`: a step boundary claimed
233
+ * the whole next-step batch a moment ago, so the message is now durable and
234
+ * the durable row's regular rewind button takes over with no gap. Nothing is
235
+ * removed then, and the composer is NOT refilled — refilling would invite a
236
+ * duplicate send of a message the model has already received.
237
+ */
238
+ export declare function retractPending(session: SessionFace, itemId: string, text: string | null, setComposerText: (sessionId: string, text: string) => boolean): Promise<void>;
215
239
  /**
216
240
  * Build the slot-entry component for the plugin apply(): a tiny bridge that
217
241
  * injects the apply-time capabilities (session resolution, locale, rewind
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-rewind-plugin",
3
- "version": "0.12.1",
3
+ "version": "0.12.2",
4
4
  "description": "DSH 插件:真正便捷无感的同窗口内对话回退,从不新建分支;自带轻量工作区备份,可一并还原文件(完整 Claude Code /rewind 语义)。 · DSH plugin: genuinely effortless in-window conversation rewind — never forking a new session; ships a lightweight workspace backup that restores files together with the rewind (full Claude Code /rewind semantics).",
5
5
  "keywords": [
6
6
  "deepseek-harness",