dsh-rewind-plugin 0.18.0 → 0.18.1
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 +6 -6
- package/README.md +5 -5
- package/lib/client.js +124 -70
- package/lib/index.js +2 -2
- package/lib/types/client/styles.d.ts +1 -1
- package/lib/types/index.d.ts +2 -2
- package/lib/types/rewind.d.ts +7 -7
- package/package.json +37 -37
package/README.en.md
CHANGED
|
@@ -11,12 +11,12 @@ Conversation rewind for DeepSeek Harness: **rewind the conversation to any earli
|
|
|
11
11
|
A deliberately focused plugin with one job: **rewind to any user message, no matter how far back, in place** — and conveniently **restore the files it changed** along the way.
|
|
12
12
|
|
|
13
13
|
- **In-place rewind, no new session** — the target message and everything after it (replies, tool calls) are withdrawn from the model context *and* the rendered transcript, and the target's text is filled back into the composer for editing and re-sending. It all happens inside the original session: no new branch, no leftover copy.
|
|
14
|
-
- **Conversation and
|
|
14
|
+
- **Conversation and files rewind together** — Claude Code-aligned behavior: files edited by the write tools are tracked and restored by the same rewind, and external changes to already-tracked files are restorable too. **Partial tracking, before-write backup, store only on change**, with no git dependency.
|
|
15
15
|
- **Safety guarantees** — the session log only ever appends rewind markers and is never deleted or rewritten; file backup and restore are hardened; tests are comprehensive, and maintenance continues across DSH upgrades. Security model: [SECURITY.md](SECURITY.md).
|
|
16
16
|
|
|
17
17
|
## Preview
|
|
18
18
|
|
|
19
|
-
Every user message carries a **↶ rewind** button in its action row. Clicking it opens a mode-selection popover — "**rewind conversation only**" or "**rewind conversation and
|
|
19
|
+
Every user message carries a **↶ rewind** button in its action row. Clicking it opens a mode-selection popover — "**rewind conversation only**" or "**rewind conversation and files**", the latter showing the file-change list for confirmation first. You can also pick and rewind conveniently via the **`/rewind` and `/undo` commands**.
|
|
20
20
|
|
|
21
21
|
<table>
|
|
22
22
|
<tr>
|
|
@@ -24,7 +24,7 @@ Every user message carries a **↶ rewind** button in its action row. Clicking i
|
|
|
24
24
|
<td align="center"><img src="assets/screenshots/mode-popover.png" width="440" alt="Mode-selection popover"><br><sub>Mode-selection popover</sub></td>
|
|
25
25
|
</tr>
|
|
26
26
|
<tr>
|
|
27
|
-
<td align="center"><img src="assets/screenshots/impact-list.png" width="440" alt="Impact list"><br><sub>"Conversation and
|
|
27
|
+
<td align="center"><img src="assets/screenshots/impact-list.png" width="440" alt="Impact list"><br><sub>"Conversation and files" impact list</sub></td>
|
|
28
28
|
<td align="center"><img src="assets/screenshots/rewind-candidates.png" width="440" alt="/rewind candidate picker"><br><sub>/rewind candidate picker</sub></td>
|
|
29
29
|
</tr>
|
|
30
30
|
</table>
|
|
@@ -55,7 +55,7 @@ then install the new one.
|
|
|
55
55
|
## Usage
|
|
56
56
|
|
|
57
57
|
1. Find the user message you want to rewind to in the conversation, or type `/rewind` (or its alias `/undo`) to open the candidate picker.
|
|
58
|
-
2. **Select it.** A small popover offers the two modes — "conversation only" or "conversation and
|
|
58
|
+
2. **Select it.** A small popover offers the two modes — "conversation only" or "conversation and files".
|
|
59
59
|
3. The rewind takes effect immediately: the conversation returns to how it looked at the target message, and the target message's text is filled back into the composer — edit and re-send.
|
|
60
60
|
|
|
61
61
|
**Keyboard**: both the candidate picker and the mode popover support ↑↓ to move, Enter to confirm, Esc to cancel/back.
|
|
@@ -121,7 +121,7 @@ The whole design rests on two principles, simple but deliberate: **the conversat
|
|
|
121
121
|
|
|
122
122
|
### 1. Conversation rewind: a single "mask", not a delete
|
|
123
123
|
|
|
124
|
-
`append-only` is a hard rule: the session log only grows and is never rewritten — the foundation of auditability and privacy. A rewind never touches history; it makes a single move: append **one "empty message" marker** to the end of the log and use it to "mask + replace"
|
|
124
|
+
`append-only` is a hard rule: the session log only grows and is never rewritten — the foundation of auditability and privacy. A rewind never touches history; it makes a single move: append **one "empty message" marker** to the end of the log and use it to "mask + replace" the target message and everything after it, so the model and the UI see only what came before it.
|
|
125
125
|
|
|
126
126
|
- **One and the same log** — the append happens only in the current session's log: no new session, no new branch, so no residue or copy is left behind;
|
|
127
127
|
- **The marker is canonical** — the same "hide + replace" as the official `/compact`: `/compact` compresses a span of history into a summary, while `/rewind` swaps in an "empty message" marker. Because it is canonical, DSH's log replay, compaction, and resume preflight all recognize it and never mistake it for a real message;
|
|
@@ -178,7 +178,7 @@ withdrew should consume the stable, locale-independent helpers exported from
|
|
|
178
178
|
|
|
179
179
|
## Security
|
|
180
180
|
|
|
181
|
-
This plugin only appends rewind-marker events to the session log; it never deletes or rewrites logged history. Workspace files are written only when you choose "conversation and
|
|
181
|
+
This plugin only appends rewind-marker events to the session log; it never deletes or rewrites logged history. Workspace files are written only when you choose "conversation and files"; backups are stored under `<dsh home>/rewind-snapshots/`; restores draw only from those backups. It never touches your git repository, makes no network requests, and accesses no credentials. For sessions you've left inactive for a long time, a global auto-cleanup (off by default) can remove their snapshot directory in whole, leaving the active session and the chat log untouched. Full security model: [SECURITY.md](SECURITY.md).
|
|
182
182
|
|
|
183
183
|
## Development
|
|
184
184
|
|
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@ DeepSeek Harness 插件:**一键就地回退对话到任意更早的用户消
|
|
|
16
16
|
|
|
17
17
|
## 效果预览
|
|
18
18
|
|
|
19
|
-
每条用户消息的操作行都有一个 **↶ 回退**
|
|
19
|
+
每条用户消息的操作行都有一个 **↶ 回退** 按钮。点击后弹出模式选择浮层——「**仅回退对话**」或「**回退对话和文件**」,后者会先展示文件变更清单再确认。还可以通过 **`/rewind` 和 `/undo` 命令**便捷地选择和回退。
|
|
20
20
|
|
|
21
21
|
<table>
|
|
22
22
|
<tr>
|
|
@@ -24,7 +24,7 @@ DeepSeek Harness 插件:**一键就地回退对话到任意更早的用户消
|
|
|
24
24
|
<td align="center"><img src="assets/screenshots/mode-popover.png" width="440" alt="模式选择浮层"><br><sub>模式选择浮层</sub></td>
|
|
25
25
|
</tr>
|
|
26
26
|
<tr>
|
|
27
|
-
<td align="center"><img src="assets/screenshots/impact-list.png" width="440" alt="影响清单"><br><sub
|
|
27
|
+
<td align="center"><img src="assets/screenshots/impact-list.png" width="440" alt="影响清单"><br><sub>「回退对话和文件」影响清单</sub></td>
|
|
28
28
|
<td align="center"><img src="assets/screenshots/rewind-candidates.png" width="440" alt="/rewind 候选面板"><br><sub>/rewind 候选面板</sub></td>
|
|
29
29
|
</tr>
|
|
30
30
|
</table>
|
|
@@ -52,7 +52,7 @@ dsh plugin --profile <name> add dsh-rewind-plugin@<版本>
|
|
|
52
52
|
## 使用
|
|
53
53
|
|
|
54
54
|
1. 在对话中找到要回退的那条用户消息,或输入 `/rewind`(或其别名 `/undo`)打开候选列表选择。
|
|
55
|
-
2. **选中它。**
|
|
55
|
+
2. **选中它。** 小浮层提供两种模式——「仅回退对话」或「回退对话和文件」。
|
|
56
56
|
3. 回退立即生效:对话回到目标消息当时的样子,目标消息的文本自动回填输入框——改完直接重发。
|
|
57
57
|
|
|
58
58
|
**键盘操作**:候选列表与模式浮层均支持 ↑↓ 移动、Enter 确认、Esc 取消/返回。
|
|
@@ -105,7 +105,7 @@ rm <dsh home>/snapshot-cleanup-last-sweep.json
|
|
|
105
105
|
|
|
106
106
|
### 1. 对话回退:一次「遮蔽」,而不是「删除」
|
|
107
107
|
|
|
108
|
-
`append-only` 是铁律:会话日志只追加、从不改写——这是可审计与信息安全的地基。回退从不动历史,它只做一步:往日志末尾追加一条
|
|
108
|
+
`append-only` 是铁律:会话日志只追加、从不改写——这是可审计与信息安全的地基。回退从不动历史,它只做一步:往日志末尾追加一条 **“空消息”标记**,把目标消息及其之后的全部内容「遮蔽 + 替换」掉,让模型和界面都只看得到目标之前的部分。
|
|
109
109
|
|
|
110
110
|
- **同一份日志**——只在当前会话的日志做简单追加,不新建会话、不新建分支,因此不会留下残留和副本;
|
|
111
111
|
- **标记是规范的**——采用与官方 `/compact` 相同的「隐藏 + 替换」:`/compact` 把一段历史压缩成摘要,`/rewind` 则换成一条“空消息”标记。由于其规范性,DSH 的日志重放、压缩、续接检查都能正确识别它,绝不会把它误认为真实对话;
|
|
@@ -159,7 +159,7 @@ rm <dsh home>/snapshot-cleanup-last-sweep.json
|
|
|
159
159
|
|
|
160
160
|
## 安全
|
|
161
161
|
|
|
162
|
-
|
|
162
|
+
本插件只向会话日志追加回退标记事件,从不删除或改写已记录的历史。工作区文件仅在「回退对话和文件」时被改写,备份存储于 `<dsh home>/rewind-snapshots/`;还原以备份为唯一来源。不触碰你的 git 仓库,无网络请求,不访问任何凭据。对**长期不活跃**的会话,另有默认关闭的全局自动清理可整目录移除其快照,不影响活动会话与对话日志。完整安全模型:[SECURITY.md](SECURITY.md)。
|
|
163
163
|
|
|
164
164
|
## 开发
|
|
165
165
|
|
package/lib/client.js
CHANGED
|
@@ -278,47 +278,58 @@ var STYLE = `
|
|
|
278
278
|
width: calc(15px + var(--dsh-content-font-delta, 0px));
|
|
279
279
|
height: calc(15px + var(--dsh-content-font-delta, 0px));
|
|
280
280
|
}
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
281
|
+
@media (hover: hover) {
|
|
282
|
+
.dsh-rewind-btn:hover {
|
|
283
|
+
background: var(--dsw-alias-interactive-bg-hover);
|
|
284
|
+
color: var(--dsw-alias-label-secondary);
|
|
285
|
+
}
|
|
284
286
|
}
|
|
285
287
|
|
|
286
288
|
.dsh-rewind-popover {
|
|
287
289
|
position: fixed;
|
|
288
|
-
z-index:
|
|
289
|
-
|
|
290
|
-
|
|
290
|
+
z-index: 1100;
|
|
291
|
+
box-sizing: border-box;
|
|
292
|
+
width: min(300px, calc(100vw - 24px));
|
|
293
|
+
padding: 16px;
|
|
291
294
|
border: 0;
|
|
292
|
-
border-radius: var(--dsw-radius-
|
|
295
|
+
border-radius: var(--dsw-radius-lg);
|
|
293
296
|
background: var(--dsw-specific-menu, var(--dsw-alias-bg-layer-3));
|
|
294
297
|
backdrop-filter: var(--dsw-menu-backdrop-filter);
|
|
295
298
|
isolation: isolate;
|
|
296
299
|
--dsw-elevation-stroke-color: var(--dsw-alias-border-l1);
|
|
300
|
+
/* Elevated surface: match the menu cards' l2 scrollbar tokens. */
|
|
301
|
+
--dsh-scrollbar-thumb: var(--dsw-alias-scrollbar-bg-l2);
|
|
302
|
+
--dsh-scrollbar-thumb-hover: var(--dsw-alias-scrollbar-hover-l2);
|
|
297
303
|
box-shadow: var(--dsw-elevation-prominent);
|
|
298
|
-
font-size:
|
|
299
|
-
line-height:
|
|
304
|
+
font-size: 12px;
|
|
305
|
+
line-height: 18px;
|
|
300
306
|
color: var(--dsw-alias-label-primary);
|
|
301
307
|
}
|
|
302
308
|
.dsh-rewind-popover-title {
|
|
303
|
-
font-size:
|
|
304
|
-
font-weight:
|
|
305
|
-
line-height:
|
|
309
|
+
font-size: 12px;
|
|
310
|
+
font-weight: 500;
|
|
311
|
+
line-height: 18px;
|
|
306
312
|
}
|
|
307
313
|
.dsh-rewind-popover-target {
|
|
308
|
-
margin:
|
|
314
|
+
margin: 2px 0 10px;
|
|
309
315
|
font-size: 12px;
|
|
310
|
-
line-height:
|
|
316
|
+
line-height: 18px;
|
|
311
317
|
color: var(--dsw-alias-label-tertiary);
|
|
312
318
|
word-break: break-all;
|
|
319
|
+
/* The full message sits above the popover; two lines are enough context. */
|
|
320
|
+
display: -webkit-box;
|
|
321
|
+
-webkit-box-orient: vertical;
|
|
322
|
+
-webkit-line-clamp: 2;
|
|
323
|
+
overflow: hidden;
|
|
313
324
|
}
|
|
314
325
|
.dsh-rewind-popover-option {
|
|
315
326
|
display: flex;
|
|
316
327
|
flex-direction: column;
|
|
317
328
|
gap: 2px;
|
|
318
329
|
width: 100%;
|
|
319
|
-
margin: 0 0
|
|
320
|
-
padding: 8px
|
|
321
|
-
border:
|
|
330
|
+
margin: 0 0 2px;
|
|
331
|
+
padding: 6px 8px;
|
|
332
|
+
border: 0;
|
|
322
333
|
border-radius: 8px;
|
|
323
334
|
background: transparent;
|
|
324
335
|
color: inherit;
|
|
@@ -338,7 +349,7 @@ var STYLE = `
|
|
|
338
349
|
outline: none;
|
|
339
350
|
}
|
|
340
351
|
.dsh-rewind-popover-option:disabled {
|
|
341
|
-
opacity: 0.
|
|
352
|
+
opacity: 0.4;
|
|
342
353
|
cursor: default;
|
|
343
354
|
}
|
|
344
355
|
.dsh-rewind-popover-option-label {
|
|
@@ -371,15 +382,15 @@ var STYLE = `
|
|
|
371
382
|
display: flex;
|
|
372
383
|
justify-content: flex-end;
|
|
373
384
|
gap: 8px;
|
|
385
|
+
margin-top: 8px;
|
|
374
386
|
}
|
|
375
387
|
.dsh-rewind-popover-primary,
|
|
376
388
|
.dsh-rewind-popover-ghost {
|
|
377
|
-
|
|
389
|
+
height: 28px;
|
|
390
|
+
padding: 0 10px;
|
|
378
391
|
border: none;
|
|
379
392
|
border-radius: 8px;
|
|
380
393
|
font: inherit;
|
|
381
|
-
font-size: 13px;
|
|
382
|
-
line-height: 18px;
|
|
383
394
|
cursor: pointer;
|
|
384
395
|
}
|
|
385
396
|
.dsh-rewind-popover-primary {
|
|
@@ -390,7 +401,7 @@ var STYLE = `
|
|
|
390
401
|
background: var(--dsw-alias-button-primary-hover);
|
|
391
402
|
}
|
|
392
403
|
.dsh-rewind-popover-primary:disabled {
|
|
393
|
-
opacity: 0.
|
|
404
|
+
opacity: 0.4;
|
|
394
405
|
cursor: default;
|
|
395
406
|
}
|
|
396
407
|
.dsh-rewind-popover-ghost {
|
|
@@ -400,6 +411,9 @@ var STYLE = `
|
|
|
400
411
|
.dsh-rewind-popover-ghost:hover {
|
|
401
412
|
background: var(--dsw-alias-interactive-bg-hover);
|
|
402
413
|
}
|
|
414
|
+
.dsh-rewind-popover-ghost:active {
|
|
415
|
+
background: var(--dsw-alias-interactive-bg-active);
|
|
416
|
+
}
|
|
403
417
|
|
|
404
418
|
.dsh-rewind-guard-hint {
|
|
405
419
|
position: fixed;
|
|
@@ -452,12 +466,21 @@ var STYLE = `
|
|
|
452
466
|
|
|
453
467
|
// src/client/popover.ts
|
|
454
468
|
var popoverEl = null;
|
|
469
|
+
var popoverAnchor = null;
|
|
455
470
|
var disposeOutside = null;
|
|
456
471
|
var liveSessionId = null;
|
|
457
472
|
var composerFocuser = null;
|
|
458
473
|
function registerComposerFocuser(focus) {
|
|
459
474
|
composerFocuser = focus;
|
|
460
475
|
}
|
|
476
|
+
function markOpen(anchor) {
|
|
477
|
+
popoverAnchor = anchor;
|
|
478
|
+
anchor.setAttribute("aria-expanded", "true");
|
|
479
|
+
}
|
|
480
|
+
function markClosed() {
|
|
481
|
+
popoverAnchor?.setAttribute("aria-expanded", "false");
|
|
482
|
+
popoverAnchor = null;
|
|
483
|
+
}
|
|
461
484
|
function closePopover() {
|
|
462
485
|
if (popoverEl !== null) {
|
|
463
486
|
popoverEl.remove();
|
|
@@ -467,6 +490,7 @@ function closePopover() {
|
|
|
467
490
|
disposeOutside();
|
|
468
491
|
disposeOutside = null;
|
|
469
492
|
}
|
|
493
|
+
markClosed();
|
|
470
494
|
const sessionId = liveSessionId;
|
|
471
495
|
liveSessionId = null;
|
|
472
496
|
if (sessionId !== null) {
|
|
@@ -476,13 +500,6 @@ function closePopover() {
|
|
|
476
500
|
}
|
|
477
501
|
}
|
|
478
502
|
}
|
|
479
|
-
function formatTarget(t, seq, time, preview) {
|
|
480
|
-
const d = new Date(time);
|
|
481
|
-
const hh = String(d.getHours()).padStart(2, "0");
|
|
482
|
-
const mm = String(d.getMinutes()).padStart(2, "0");
|
|
483
|
-
const previewText = preview.length > 0 ? preview : t("popover.noText");
|
|
484
|
-
return `seq ${seq} \xB7 ${hh}:${mm} \xB7 ${previewText}`;
|
|
485
|
-
}
|
|
486
503
|
function findCommand(chat, match) {
|
|
487
504
|
if (chat === void 0) return void 0;
|
|
488
505
|
let found;
|
|
@@ -566,7 +583,7 @@ function moveFocus(root, dir) {
|
|
|
566
583
|
const next = index === -1 ? dir === 1 ? 0 : buttons.length - 1 : (index + dir + buttons.length) % buttons.length;
|
|
567
584
|
buttons[next]?.focus();
|
|
568
585
|
}
|
|
569
|
-
function renderImpactStep(root, opts, back, cached) {
|
|
586
|
+
function renderImpactStep(root, opts, back, reposition, cached) {
|
|
570
587
|
const { session, seq, t } = opts;
|
|
571
588
|
const impact = el("div", CLASS.popoverImpact, t("popover.impact.loading"));
|
|
572
589
|
const actions = el("div", CLASS.popoverActions);
|
|
@@ -584,6 +601,7 @@ function renderImpactStep(root, opts, back, cached) {
|
|
|
584
601
|
actions.append(confirm);
|
|
585
602
|
root.replaceChildren(impact, actions);
|
|
586
603
|
focusFirst(root);
|
|
604
|
+
reposition();
|
|
587
605
|
void (async () => {
|
|
588
606
|
const outcome = cached ?? await previewImpact(session.sessionId, seq);
|
|
589
607
|
if (outcome === null) {
|
|
@@ -619,16 +637,44 @@ function renderImpactStep(root, opts, back, cached) {
|
|
|
619
637
|
});
|
|
620
638
|
})().catch(() => {
|
|
621
639
|
impact.textContent = t("popover.impact.failed", { message: "unexpected error" });
|
|
640
|
+
}).finally(() => {
|
|
641
|
+
reposition();
|
|
622
642
|
});
|
|
623
643
|
}
|
|
624
644
|
function mountShell(root, anchor, onKeyDown) {
|
|
645
|
+
const margin = 12;
|
|
646
|
+
const gap = 4;
|
|
647
|
+
let flippedAbove = null;
|
|
625
648
|
const position = () => {
|
|
649
|
+
if (!root.isConnected) return;
|
|
650
|
+
if (!anchor.isConnected) {
|
|
651
|
+
if (popoverEl === root) closePopover();
|
|
652
|
+
return;
|
|
653
|
+
}
|
|
626
654
|
const rect = anchor.getBoundingClientRect();
|
|
627
|
-
const gap = 4;
|
|
628
655
|
const height = root.offsetHeight;
|
|
629
|
-
|
|
656
|
+
if (flippedAbove === null) {
|
|
657
|
+
flippedAbove = rect.bottom + gap + height > window.innerHeight - margin;
|
|
658
|
+
}
|
|
659
|
+
const top = flippedAbove ? Math.max(margin, rect.top - gap - height) : Math.min(Math.max(margin, rect.bottom + gap), Math.max(margin, window.innerHeight - margin - height));
|
|
630
660
|
root.style.top = `${Math.round(top)}px`;
|
|
631
|
-
root.style.left = `${Math.round(Math.min(rect.right, window.innerWidth -
|
|
661
|
+
root.style.left = `${Math.round(Math.max(margin, Math.min(rect.right, window.innerWidth - margin - root.offsetWidth)))}px`;
|
|
662
|
+
};
|
|
663
|
+
let frame = 0;
|
|
664
|
+
const schedule = () => {
|
|
665
|
+
if (typeof requestAnimationFrame !== "function") {
|
|
666
|
+
position();
|
|
667
|
+
return;
|
|
668
|
+
}
|
|
669
|
+
if (frame !== 0) return;
|
|
670
|
+
frame = requestAnimationFrame(() => {
|
|
671
|
+
frame = 0;
|
|
672
|
+
position();
|
|
673
|
+
});
|
|
674
|
+
};
|
|
675
|
+
const onScroll = (event) => {
|
|
676
|
+
if (event.target instanceof Node && root.contains(event.target)) return;
|
|
677
|
+
schedule();
|
|
632
678
|
};
|
|
633
679
|
const onPointerDown = (event) => {
|
|
634
680
|
const target = event.target;
|
|
@@ -639,19 +685,30 @@ function mountShell(root, anchor, onKeyDown) {
|
|
|
639
685
|
document.addEventListener("pointerdown", onPointerDown);
|
|
640
686
|
document.addEventListener("keydown", onKeyDown, true);
|
|
641
687
|
}, 0);
|
|
688
|
+
document.body.append(root);
|
|
689
|
+
position();
|
|
690
|
+
const observer = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(() => {
|
|
691
|
+
schedule();
|
|
692
|
+
});
|
|
693
|
+
observer?.observe(root);
|
|
694
|
+
window.addEventListener("scroll", onScroll, true);
|
|
695
|
+
window.addEventListener("resize", schedule);
|
|
642
696
|
const dispose = () => {
|
|
643
697
|
clearTimeout(deferred);
|
|
644
698
|
document.removeEventListener("pointerdown", onPointerDown);
|
|
645
699
|
document.removeEventListener("keydown", onKeyDown, true);
|
|
700
|
+
window.removeEventListener("scroll", onScroll, true);
|
|
701
|
+
window.removeEventListener("resize", schedule);
|
|
702
|
+
observer?.disconnect();
|
|
703
|
+
if (frame !== 0) cancelAnimationFrame(frame);
|
|
646
704
|
};
|
|
647
|
-
document.body.append(root);
|
|
648
|
-
position();
|
|
649
705
|
return { position, dispose };
|
|
650
706
|
}
|
|
651
707
|
function openRetractPopover(opts) {
|
|
652
708
|
closePopover();
|
|
653
709
|
const { preview, anchor, t, retract, onRetract } = opts;
|
|
654
710
|
if (retract === void 0 || onRetract === void 0) return;
|
|
711
|
+
markOpen(anchor);
|
|
655
712
|
const root = el("div", CLASS.popover);
|
|
656
713
|
root.setAttribute("role", "dialog");
|
|
657
714
|
root.setAttribute("aria-label", t("popover.retract.title"));
|
|
@@ -711,6 +768,7 @@ function openPopover(opts) {
|
|
|
711
768
|
const { session, seq, time, preview, anchor, t } = opts;
|
|
712
769
|
const onRewind = opts.onRewind;
|
|
713
770
|
if (seq === void 0 || time === void 0 || onRewind === void 0) return;
|
|
771
|
+
markOpen(anchor);
|
|
714
772
|
const durableOpts = { session, seq, time, preview, anchor, t, onRewind };
|
|
715
773
|
const root = el("div", CLASS.popover);
|
|
716
774
|
root.setAttribute("role", "dialog");
|
|
@@ -718,11 +776,14 @@ function openPopover(opts) {
|
|
|
718
776
|
let bothState = { state: "loading" };
|
|
719
777
|
let impactOutcome = null;
|
|
720
778
|
let step = "modes";
|
|
779
|
+
let reposition = () => {
|
|
780
|
+
};
|
|
721
781
|
const renderModes = () => {
|
|
722
782
|
step = "modes";
|
|
783
|
+
const previewText = preview.length > 0 ? preview : t("popover.noText");
|
|
723
784
|
const children = [
|
|
724
785
|
el("div", CLASS.popoverTitle, t("popover.title")),
|
|
725
|
-
el("div", CLASS.popoverTarget,
|
|
786
|
+
el("div", CLASS.popoverTarget, previewText),
|
|
726
787
|
modeOption(t("popover.chat"), t("popover.chat.hint"), () => {
|
|
727
788
|
closePopover();
|
|
728
789
|
durableOpts.onRewind("chat");
|
|
@@ -741,20 +802,13 @@ function openPopover(opts) {
|
|
|
741
802
|
if (bothState.state === "loading") option.disabled = true;
|
|
742
803
|
children.push(option);
|
|
743
804
|
}
|
|
744
|
-
const actions = el("div", CLASS.popoverActions);
|
|
745
|
-
const cancel = document.createElement("button");
|
|
746
|
-
cancel.type = "button";
|
|
747
|
-
cancel.className = CLASS.popoverGhost;
|
|
748
|
-
cancel.textContent = t("popover.cancel");
|
|
749
|
-
cancel.addEventListener("click", closePopover);
|
|
750
|
-
actions.append(cancel);
|
|
751
|
-
children.push(actions);
|
|
752
805
|
root.replaceChildren(...children);
|
|
753
806
|
focusFirst(root);
|
|
807
|
+
reposition();
|
|
754
808
|
};
|
|
755
809
|
const renderImpact = () => {
|
|
756
810
|
step = "impact";
|
|
757
|
-
renderImpactStep(root, durableOpts, renderModes, impactOutcome);
|
|
811
|
+
renderImpactStep(root, durableOpts, renderModes, reposition, impactOutcome);
|
|
758
812
|
};
|
|
759
813
|
const onKeyDown = (event) => {
|
|
760
814
|
if (event.key === "ArrowDown") {
|
|
@@ -778,6 +832,7 @@ function openPopover(opts) {
|
|
|
778
832
|
};
|
|
779
833
|
renderModes();
|
|
780
834
|
const shell = mountShell(root, anchor, onKeyDown);
|
|
835
|
+
reposition = shell.position;
|
|
781
836
|
popoverEl = root;
|
|
782
837
|
disposeOutside = shell.dispose;
|
|
783
838
|
void (async () => {
|
|
@@ -789,17 +844,16 @@ function openPopover(opts) {
|
|
|
789
844
|
bothState = { state: "error", message: outcome?.kind === "error" ? outcome.text : "preview request timed out" };
|
|
790
845
|
}
|
|
791
846
|
renderModes();
|
|
792
|
-
shell.position();
|
|
793
847
|
})().catch(() => {
|
|
794
848
|
bothState = { state: "error", message: "unexpected error" };
|
|
795
849
|
renderModes();
|
|
796
|
-
shell.position();
|
|
797
850
|
});
|
|
798
851
|
}
|
|
799
852
|
|
|
800
853
|
// src/client/portals.tsx
|
|
801
854
|
var import_react = require("react");
|
|
802
855
|
var import_react_dom = require("react-dom");
|
|
856
|
+
var import_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
803
857
|
|
|
804
858
|
// src/client/pending.ts
|
|
805
859
|
var QUEUE_PREVIEW_CHARS = 200;
|
|
@@ -1291,17 +1345,17 @@ function RewindButton({ target, sessionId, sessionOf, chatOf, watchChat, isMainV
|
|
|
1291
1345
|
}
|
|
1292
1346
|
});
|
|
1293
1347
|
};
|
|
1294
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1348
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Tooltip, { label: t("button.title"), side: "bottom", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1295
1349
|
"button",
|
|
1296
1350
|
{
|
|
1297
1351
|
type: "button",
|
|
1298
1352
|
className: CLASS.button,
|
|
1299
1353
|
"aria-label": t("button.aria"),
|
|
1300
|
-
|
|
1354
|
+
"aria-haspopup": "dialog",
|
|
1301
1355
|
onClick,
|
|
1302
1356
|
dangerouslySetInnerHTML: { __html: REWIND_ICON_SVG }
|
|
1303
1357
|
}
|
|
1304
|
-
);
|
|
1358
|
+
) });
|
|
1305
1359
|
}
|
|
1306
1360
|
function composerText() {
|
|
1307
1361
|
const surface = composerSurface();
|
|
@@ -1343,17 +1397,17 @@ function RetractButton({ target, sessionId, sessionOf, setComposerText, t }) {
|
|
|
1343
1397
|
}
|
|
1344
1398
|
});
|
|
1345
1399
|
};
|
|
1346
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1400
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Tooltip, { label: t("button.retract.title"), side: "bottom", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1347
1401
|
"button",
|
|
1348
1402
|
{
|
|
1349
1403
|
type: "button",
|
|
1350
1404
|
className: CLASS.button,
|
|
1351
1405
|
"aria-label": t("button.retract.aria"),
|
|
1352
|
-
|
|
1406
|
+
"aria-haspopup": "dialog",
|
|
1353
1407
|
onClick,
|
|
1354
1408
|
dangerouslySetInnerHTML: { __html: REWIND_ICON_SVG }
|
|
1355
1409
|
}
|
|
1356
|
-
);
|
|
1410
|
+
) });
|
|
1357
1411
|
}
|
|
1358
1412
|
function createRewindBridge(deps) {
|
|
1359
1413
|
return function RewindBridge({ sessionId }) {
|
|
@@ -1421,9 +1475,9 @@ async function candidatesOverRpc(connection, sessionId) {
|
|
|
1421
1475
|
}
|
|
1422
1476
|
|
|
1423
1477
|
// src/client/build-info.ts
|
|
1424
|
-
var PLUGIN_VERSION = true ? "0.18.
|
|
1478
|
+
var PLUGIN_VERSION = true ? "0.18.1" : "dev";
|
|
1425
1479
|
var PLUGIN_PACKAGE = "dsh-rewind-plugin";
|
|
1426
|
-
var BUILD_HASH = true ? "
|
|
1480
|
+
var BUILD_HASH = true ? "8a3779b4" : "dev";
|
|
1427
1481
|
|
|
1428
1482
|
// src/client/locales.ts
|
|
1429
1483
|
var zh = {
|
|
@@ -1438,11 +1492,11 @@ var zh = {
|
|
|
1438
1492
|
"popover.retract.hint": "\u64A4\u56DE\u8FD9\u6761\u53CA\u4E4B\u540E\u7684\u63D2\u8BDD\u6D88\u606F\uFF0C\u4E0D\u6253\u65AD\u5F53\u524D\u751F\u6210",
|
|
1439
1493
|
"popover.retract.confirm": "\u786E\u8BA4\u56DE\u9000",
|
|
1440
1494
|
"popover.chat": "\u4EC5\u56DE\u9000\u5BF9\u8BDD",
|
|
1441
|
-
"popover.chat.hint": "\
|
|
1442
|
-
"popover.both": "\u56DE\u9000\u5BF9\u8BDD\u548C\
|
|
1443
|
-
"popover.both.hint": "\
|
|
1495
|
+
"popover.chat.hint": "\u4EC5\u56DE\u9000\u4E0A\u4E0B\u6587\uFF0C\u4E0D\u52A8\u5DE5\u4F5C\u533A\u6587\u4EF6",
|
|
1496
|
+
"popover.both": "\u56DE\u9000\u5BF9\u8BDD\u548C\u6587\u4EF6",
|
|
1497
|
+
"popover.both.hint": "\u56DE\u9000\u4E0A\u4E0B\u6587\u5E76\u8FD8\u539F\u5DE5\u4F5C\u533A\u6587\u4EF6",
|
|
1444
1498
|
"popover.checking": "\u6B63\u5728\u68C0\u67E5\u6587\u4EF6\u53D8\u66F4\u2026",
|
|
1445
|
-
"popover.noChanges": "\
|
|
1499
|
+
"popover.noChanges": "\u65E0\u53EF\u8FD8\u539F\u7684\u6587\u4EF6\u53D8\u66F4",
|
|
1446
1500
|
"popover.backupDisabled": "\u5FEB\u7167\u5907\u4EFD\u5DF2\u5173\u95ED\uFF0C\u8FD8\u539F\u53EF\u80FD\u4E0D\u5145\u5206",
|
|
1447
1501
|
"popover.cancel": "\u53D6\u6D88",
|
|
1448
1502
|
"popover.impact.loading": "\u6B63\u5728\u83B7\u53D6\u5F71\u54CD\u6E05\u5355\u2026",
|
|
@@ -1481,11 +1535,11 @@ var en = {
|
|
|
1481
1535
|
"popover.retract.hint": "Retracts this pending message and the ones after it, without interrupting the run",
|
|
1482
1536
|
"popover.retract.confirm": "Confirm rewind",
|
|
1483
1537
|
"popover.chat": "Rewind conversation only",
|
|
1484
|
-
"popover.chat.hint": "
|
|
1485
|
-
"popover.both": "Rewind conversation and
|
|
1486
|
-
"popover.both.hint": "
|
|
1538
|
+
"popover.chat.hint": "Context only; workspace untouched",
|
|
1539
|
+
"popover.both": "Rewind conversation and files",
|
|
1540
|
+
"popover.both.hint": "Context and restore workspace files",
|
|
1487
1541
|
"popover.checking": "Checking for file changes\u2026",
|
|
1488
|
-
"popover.noChanges": "No
|
|
1542
|
+
"popover.noChanges": "No restorable file changes",
|
|
1489
1543
|
"popover.backupDisabled": "Snapshots off; the restore may be incomplete",
|
|
1490
1544
|
"popover.cancel": "Cancel",
|
|
1491
1545
|
"popover.impact.loading": "Fetching impact list\u2026",
|
|
@@ -1514,7 +1568,7 @@ var en = {
|
|
|
1514
1568
|
};
|
|
1515
1569
|
|
|
1516
1570
|
// src/client/settings-card.tsx
|
|
1517
|
-
var
|
|
1571
|
+
var import_dsh_client_ui_primitives2 = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
1518
1572
|
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
1519
1573
|
var SETTINGS_ENTRY_ID = "dsh-rewind-plugin";
|
|
1520
1574
|
var DEFAULT_MAX_AGE_DAYS = 30;
|
|
@@ -1542,10 +1596,10 @@ function formLabels(t) {
|
|
|
1542
1596
|
};
|
|
1543
1597
|
}
|
|
1544
1598
|
function settingsForm(scope) {
|
|
1545
|
-
const form = new
|
|
1599
|
+
const form = new import_dsh_client_ui_primitives2.SettingsFormModel(scope, [
|
|
1546
1600
|
fileCheckpointingEnabledField(),
|
|
1547
1601
|
autoCleanupEnabledField(),
|
|
1548
|
-
(0,
|
|
1602
|
+
(0, import_dsh_client_ui_primitives2.settingsNumberField)("autoCleanupMaxAgeDays")
|
|
1549
1603
|
]);
|
|
1550
1604
|
return {
|
|
1551
1605
|
form,
|
|
@@ -1565,7 +1619,7 @@ function SettingsCard(props) {
|
|
|
1565
1619
|
const checkpointing = state.checkpointingEnabled.text === "true";
|
|
1566
1620
|
const cleanupEnabled = state.enabled.text === "true";
|
|
1567
1621
|
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
1568
|
-
|
|
1622
|
+
import_dsh_client_ui_primitives2.SettingsForm,
|
|
1569
1623
|
{
|
|
1570
1624
|
labels: formLabels(t),
|
|
1571
1625
|
state,
|
|
@@ -1576,7 +1630,7 @@ function SettingsCard(props) {
|
|
|
1576
1630
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "dsh-rewind-settings-toggle-row", children: [
|
|
1577
1631
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "dsh-rewind-settings-toggle-label", children: t("backup.title") }),
|
|
1578
1632
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1579
|
-
|
|
1633
|
+
import_dsh_client_ui_primitives2.Switch,
|
|
1580
1634
|
{
|
|
1581
1635
|
checked: checkpointing,
|
|
1582
1636
|
label: t("backup.title"),
|
|
@@ -1594,7 +1648,7 @@ function SettingsCard(props) {
|
|
|
1594
1648
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "dsh-rewind-settings-toggle-row", children: [
|
|
1595
1649
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "dsh-rewind-settings-toggle-label", children: t("cleanup.auto") }),
|
|
1596
1650
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1597
|
-
|
|
1651
|
+
import_dsh_client_ui_primitives2.Switch,
|
|
1598
1652
|
{
|
|
1599
1653
|
checked: cleanupEnabled,
|
|
1600
1654
|
label: t("cleanup.auto"),
|
|
@@ -1608,7 +1662,7 @@ function SettingsCard(props) {
|
|
|
1608
1662
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "dsh-rewind-settings-hint", children: t(cleanupEnabled ? "cleanup.auto.on" : "cleanup.auto.off") })
|
|
1609
1663
|
] }),
|
|
1610
1664
|
cleanupEnabled ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1611
|
-
|
|
1665
|
+
import_dsh_client_ui_primitives2.SettingsValueField,
|
|
1612
1666
|
{
|
|
1613
1667
|
id: "dsh-rewind-cleanup-maxage",
|
|
1614
1668
|
label: t("cleanup.maxAge"),
|
package/lib/index.js
CHANGED
|
@@ -28,7 +28,7 @@ var en = {
|
|
|
28
28
|
"storeUnsupported": "file restore unavailable: these snapshots use a newer store format (v{version}) than this plugin understands; nothing was changed. Update the plugin or clear this session's snapshots.",
|
|
29
29
|
"success": "Withdrawn seq {targetSeq} and everything after it (conversation returned to earlier){restore}.",
|
|
30
30
|
"noUserMessages": "This session has no rewindable user messages yet.",
|
|
31
|
-
"command.description": "Rewind the conversation and/or
|
|
31
|
+
"command.description": "Rewind the conversation and/or files to a previous point",
|
|
32
32
|
"command.undoDescription": "Alias of /rewind",
|
|
33
33
|
"engine.description": "dsh-rewind internal command",
|
|
34
34
|
"engine.rejected": "Internal command: use /rewind or /undo.",
|
|
@@ -74,7 +74,7 @@ var zh = {
|
|
|
74
74
|
"storeUnsupported": "\u6587\u4EF6\u8FD8\u539F\u4E0D\u53EF\u7528\uFF1A\u8FD9\u4E9B\u5FEB\u7167\u4F7F\u7528\u4E86\u6BD4\u672C\u63D2\u4EF6\u66F4\u65B0\u7684\u5B58\u50A8\u683C\u5F0F\uFF08v{version}\uFF09\uFF1B\u5DE5\u4F5C\u533A\u672A\u505A\u4EFB\u4F55\u6539\u52A8\u3002\u8BF7\u66F4\u65B0\u63D2\u4EF6\uFF0C\u6216\u6E05\u9664\u8BE5\u4F1A\u8BDD\u7684\u5FEB\u7167\u3002",
|
|
75
75
|
"success": "\u5DF2\u64A4\u56DE seq {targetSeq} \u53CA\u4E4B\u540E\u5185\u5BB9\uFF08\u5BF9\u8BDD\u5DF2\u56DE\u5230\u6B64\u524D\uFF09{restore}\u3002",
|
|
76
76
|
"noUserMessages": "\u5F53\u524D\u4F1A\u8BDD\u8FD8\u6CA1\u6709\u53EF\u56DE\u9000\u7684\u7528\u6237\u6D88\u606F\u3002",
|
|
77
|
-
"command.description": "\
|
|
77
|
+
"command.description": "\u628A\u5BF9\u8BDD\u548C/\u6216\u6587\u4EF6\u56DE\u9000\u5230\u4E4B\u524D\u7684\u67D0\u6761\u6D88\u606F",
|
|
78
78
|
"command.undoDescription": "/rewind \u7684\u522B\u540D",
|
|
79
79
|
"engine.description": "dsh-rewind \u5185\u90E8\u547D\u4EE4",
|
|
80
80
|
"engine.rejected": "\u5185\u90E8\u547D\u4EE4\uFF1A\u8BF7\u4F7F\u7528 /rewind \u6216 /undo\u3002",
|
|
@@ -32,4 +32,4 @@ export declare const CLASS: {
|
|
|
32
32
|
*/
|
|
33
33
|
export declare const REWIND_ICON_SVG: string;
|
|
34
34
|
/** One injected stylesheet (scoped under `.dsh-rewind-*`). */
|
|
35
|
-
export declare const STYLE = "\n.dsh-rewind-btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n /* The neighbouring action buttons scale by this delta too. */\n width: calc(28px + var(--dsh-content-font-delta, 0px));\n height: calc(28px + var(--dsh-content-font-delta, 0px));\n padding: 6px;\n border: none;\n border-radius: var(--dsw-radius-sm, 8px);\n background: transparent;\n color: var(--dsw-alias-label-tertiary);\n cursor: pointer;\n}\n.dsh-rewind-btn svg {\n width: calc(15px + var(--dsh-content-font-delta, 0px));\n height: calc(15px + var(--dsh-content-font-delta, 0px));\n}\n.dsh-rewind-btn:hover {\n
|
|
35
|
+
export declare const STYLE = "\n.dsh-rewind-btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n /* The neighbouring action buttons scale by this delta too. */\n width: calc(28px + var(--dsh-content-font-delta, 0px));\n height: calc(28px + var(--dsh-content-font-delta, 0px));\n padding: 6px;\n border: none;\n border-radius: var(--dsw-radius-sm, 8px);\n background: transparent;\n color: var(--dsw-alias-label-tertiary);\n cursor: pointer;\n}\n.dsh-rewind-btn svg {\n width: calc(15px + var(--dsh-content-font-delta, 0px));\n height: calc(15px + var(--dsh-content-font-delta, 0px));\n}\n@media (hover: hover) {\n .dsh-rewind-btn:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n color: var(--dsw-alias-label-secondary);\n }\n}\n\n.dsh-rewind-popover {\n position: fixed;\n z-index: 1100;\n box-sizing: border-box;\n width: min(300px, calc(100vw - 24px));\n padding: 16px;\n border: 0;\n border-radius: var(--dsw-radius-lg);\n background: var(--dsw-specific-menu, var(--dsw-alias-bg-layer-3));\n backdrop-filter: var(--dsw-menu-backdrop-filter);\n isolation: isolate;\n --dsw-elevation-stroke-color: var(--dsw-alias-border-l1);\n /* Elevated surface: match the menu cards' l2 scrollbar tokens. */\n --dsh-scrollbar-thumb: var(--dsw-alias-scrollbar-bg-l2);\n --dsh-scrollbar-thumb-hover: var(--dsw-alias-scrollbar-hover-l2);\n box-shadow: var(--dsw-elevation-prominent);\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-primary);\n}\n.dsh-rewind-popover-title {\n font-size: 12px;\n font-weight: 500;\n line-height: 18px;\n}\n.dsh-rewind-popover-target {\n margin: 2px 0 10px;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-tertiary);\n word-break: break-all;\n /* The full message sits above the popover; two lines are enough context. */\n display: -webkit-box;\n -webkit-box-orient: vertical;\n -webkit-line-clamp: 2;\n overflow: hidden;\n}\n.dsh-rewind-popover-option {\n display: flex;\n flex-direction: column;\n gap: 2px;\n width: 100%;\n margin: 0 0 2px;\n padding: 6px 8px;\n border: 0;\n border-radius: 8px;\n background: transparent;\n color: inherit;\n font: inherit;\n text-align: left;\n cursor: pointer;\n}\n.dsh-rewind-popover-option:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n/* Arrow navigation moves real focus, so the focused row carries the pointer's\n fill and the browser ring is suppressed: in dark mode the UA two-tone ring\n draws its dark half against the filled row and reads as a residue edge (the\n harness Menu suppresses it the same way). */\n.dsh-rewind-popover-option:focus-visible:not(:disabled) {\n background: var(--dsw-alias-interactive-bg-hover);\n outline: none;\n}\n.dsh-rewind-popover-option:disabled {\n opacity: 0.4;\n cursor: default;\n}\n.dsh-rewind-popover-option-label {\n font-weight: 500;\n}\n.dsh-rewind-popover-option-hint {\n font-size: 12px;\n line-height: 16px;\n color: var(--dsw-alias-label-tertiary);\n}\n.dsh-rewind-popover-impact {\n margin: 4px 0 10px;\n padding: 8px 10px;\n border-radius: 8px;\n background: var(--dsw-alias-interactive-bg-hover);\n font-size: 12px;\n line-height: 16px;\n color: var(--dsw-alias-label-secondary);\n white-space: pre-wrap;\n max-height: 160px;\n overflow: auto;\n}\n.dsh-rewind-popover-note {\n margin-top: 6px;\n font-size: 11px;\n line-height: 15px;\n color: var(--dsw-alias-label-tertiary);\n}\n.dsh-rewind-popover-actions {\n display: flex;\n justify-content: flex-end;\n gap: 8px;\n margin-top: 8px;\n}\n.dsh-rewind-popover-primary,\n.dsh-rewind-popover-ghost {\n height: 28px;\n padding: 0 10px;\n border: none;\n border-radius: 8px;\n font: inherit;\n cursor: pointer;\n}\n.dsh-rewind-popover-primary {\n background: var(--dsw-alias-button-primary-fill);\n color: var(--dsw-alias-label-primary-foreground);\n}\n.dsh-rewind-popover-primary:hover:not(:disabled) {\n background: var(--dsw-alias-button-primary-hover);\n}\n.dsh-rewind-popover-primary:disabled {\n opacity: 0.4;\n cursor: default;\n}\n.dsh-rewind-popover-ghost {\n background: transparent;\n color: var(--dsw-alias-label-secondary);\n}\n.dsh-rewind-popover-ghost:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n.dsh-rewind-popover-ghost:active {\n background: var(--dsw-alias-interactive-bg-active);\n}\n\n.dsh-rewind-guard-hint {\n position: fixed;\n z-index: 1000;\n max-width: min(440px, calc(100vw - 24px));\n padding: 8px 12px;\n border: 0;\n border-radius: var(--dsw-radius-md);\n background: var(--dsw-specific-menu, var(--dsw-alias-bg-layer-3));\n backdrop-filter: var(--dsw-menu-backdrop-filter);\n isolation: isolate;\n --dsw-elevation-stroke-color: var(--dsw-alias-border-l1);\n box-shadow: var(--dsw-elevation-panel);\n font-size: 13px;\n line-height: 18px;\n color: var(--dsw-alias-label-primary);\n pointer-events: none;\n}\n\n/* ---- Plugin configuration form ---- */\n/* Only the card's own controls: the two switch rows, their hints, and the divider. */\n.dsh-rewind-settings-divider {\n border-top: 1px solid var(--dsw-alias-border-l2);\n}\n.dsh-rewind-settings-section {\n display: grid;\n gap: 6px;\n padding: 12px 0;\n}\n.dsh-rewind-settings-toggle-row {\n display: flex;\n align-items: flex-start;\n justify-content: space-between;\n gap: 16px;\n font-size: 13px;\n line-height: 1.5;\n color: var(--dsw-alias-label-primary);\n}\n.dsh-rewind-settings-toggle-label {\n flex: 1;\n min-width: 0;\n}\n.dsh-rewind-settings-hint {\n margin: 0;\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-label-tertiary);\n}\n";
|
package/lib/types/index.d.ts
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
* lives in `src/client/`).
|
|
5
5
|
*
|
|
6
6
|
* Rewind mechanism: planning is pure (`src/rewind.ts`); execution appends a
|
|
7
|
-
* marker `user/message` whose `surfaceOp` replaces
|
|
8
|
-
*
|
|
7
|
+
* marker `user/message` whose `surfaceOp` replaces the target and every surface
|
|
8
|
+
* node after it with itself, cutting only the model-visible surface
|
|
9
9
|
* (`docs/architecture.md`).
|
|
10
10
|
*
|
|
11
11
|
* File restore (mode `both`) follows Claude Code's checkpointing: unless
|
package/lib/types/rewind.d.ts
CHANGED
|
@@ -4,10 +4,10 @@
|
|
|
4
4
|
* and the ordered surface, so this module stays unit-testable.
|
|
5
5
|
*
|
|
6
6
|
* Rewind semantics: rewinding to a user message appends a marker node into the
|
|
7
|
-
* session log whose `surfaceOp` replaces every surface node
|
|
8
|
-
* with itself. The log (audit trail, rendered transcript) is
|
|
9
|
-
* the model-visible surface is cut, so the next request
|
|
10
|
-
* from the target message
|
|
7
|
+
* session log whose `surfaceOp` replaces the target and every surface node
|
|
8
|
+
* after it with itself. The log (audit trail, rendered transcript) is
|
|
9
|
+
* untouched; only the model-visible surface is cut, so the next request
|
|
10
|
+
* derives its context from before the target message. The marker is a `user/message` carrying a
|
|
11
11
|
* replace `surfaceOp` and the producer-owned source `{ kind: 'dsh-rewind' }`
|
|
12
12
|
* (see `marker.ts`); the shape's invariants and history are documented in
|
|
13
13
|
* `docs/architecture.md`.
|
|
@@ -51,13 +51,13 @@ export interface RewindCandidate {
|
|
|
51
51
|
}
|
|
52
52
|
/** A validated rewind: the target plus the exact surface range to shadow. */
|
|
53
53
|
export interface RewindPlan {
|
|
54
|
-
/** Target user message seq (
|
|
54
|
+
/** Target user message seq (withdrawn with the surface range). */
|
|
55
55
|
readonly targetSeq: number;
|
|
56
56
|
/** The target's ordered position in the surface. */
|
|
57
57
|
readonly targetIndex: number;
|
|
58
|
-
/** Ordered surface node seqs the rewind shadows (everything after
|
|
58
|
+
/** Ordered surface node seqs the rewind shadows (the target and everything after it). */
|
|
59
59
|
readonly shadowedSeqs: readonly number[];
|
|
60
|
-
/**
|
|
60
|
+
/** The target — the replace range start (inclusive). */
|
|
61
61
|
readonly surfaceStart: number;
|
|
62
62
|
/** Last surface node — the replace range end (inclusive). */
|
|
63
63
|
readonly surfaceEnd: number;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-rewind-plugin",
|
|
3
|
-
"version": "0.18.
|
|
3
|
+
"version": "0.18.1",
|
|
4
4
|
"description": "In-window conversation rewind with workspace file restore · 同窗口内对话回退并可还原工作区文件",
|
|
5
5
|
"icon": "./assets/icon.svg",
|
|
6
6
|
"keywords": [
|
|
@@ -152,42 +152,42 @@
|
|
|
152
152
|
"devDependencies": {
|
|
153
153
|
"@deepseek-ai/cordis": "4.0.5",
|
|
154
154
|
"@deepseek-ai/cosmokit": "1.8.6",
|
|
155
|
-
"@deepseek-ai/dsh-agent": "0.2.1-rc.
|
|
156
|
-
"@deepseek-ai/dsh-api-session-controller": "0.2.1-rc.
|
|
157
|
-
"@deepseek-ai/dsh-attachment": "0.2.1-rc.
|
|
158
|
-
"@deepseek-ai/dsh-client-connection": "0.2.1-rc.
|
|
159
|
-
"@deepseek-ai/dsh-client-locale": "0.2.1-rc.
|
|
160
|
-
"@deepseek-ai/dsh-client-store": "0.2.1-rc.
|
|
161
|
-
"@deepseek-ai/dsh-client-ui-commands": "0.2.1-rc.
|
|
162
|
-
"@deepseek-ai/dsh-client-ui-conversation": "0.2.1-rc.
|
|
163
|
-
"@deepseek-ai/dsh-client-ui-input-trigger": "0.2.1-rc.
|
|
164
|
-
"@deepseek-ai/dsh-client-ui-plugin-manager": "0.2.1-rc.
|
|
165
|
-
"@deepseek-ai/dsh-client-ui-primitives": "0.2.1-rc.
|
|
166
|
-
"@deepseek-ai/dsh-client-ui-session": "0.2.1-rc.
|
|
167
|
-
"@deepseek-ai/dsh-client-ui-settings": "0.2.1-rc.
|
|
168
|
-
"@deepseek-ai/dsh-client-ui-slots": "0.2.1-rc.
|
|
169
|
-
"@deepseek-ai/dsh-command-compact": "0.2.1-rc.
|
|
170
|
-
"@deepseek-ai/dsh-commands": "0.2.1-rc.
|
|
171
|
-
"@deepseek-ai/dsh-compaction": "0.2.1-rc.
|
|
172
|
-
"@deepseek-ai/dsh-compaction-basic": "0.2.1-rc.
|
|
173
|
-
"@deepseek-ai/dsh-compaction-image-offload": "0.2.1-rc.
|
|
174
|
-
"@deepseek-ai/dsh-fs": "0.2.1-rc.
|
|
175
|
-
"@deepseek-ai/dsh-goal": "0.2.1-rc.
|
|
176
|
-
"@deepseek-ai/dsh-home-paths": "0.2.1-rc.
|
|
177
|
-
"@deepseek-ai/dsh-llm": "0.2.1-rc.
|
|
178
|
-
"@deepseek-ai/dsh-plan-mode": "0.2.1-rc.
|
|
179
|
-
"@deepseek-ai/dsh-sandbox": "0.2.1-rc.
|
|
180
|
-
"@deepseek-ai/dsh-scope": "0.2.1-rc.
|
|
181
|
-
"@deepseek-ai/dsh-session": "0.2.1-rc.
|
|
182
|
-
"@deepseek-ai/dsh-session-projection": "0.2.1-rc.
|
|
183
|
-
"@deepseek-ai/dsh-session-stats": "0.2.1-rc.
|
|
184
|
-
"@deepseek-ai/dsh-session-title": "0.2.1-rc.
|
|
185
|
-
"@deepseek-ai/dsh-settings": "0.2.1-rc.
|
|
186
|
-
"@deepseek-ai/dsh-token-meter": "0.2.1-rc.
|
|
187
|
-
"@deepseek-ai/dsh-tools": "0.2.1-rc.
|
|
188
|
-
"@deepseek-ai/dsh-typert-protocol": "0.2.1-rc.
|
|
189
|
-
"@deepseek-ai/dsh-user-questions": "0.2.1-rc.
|
|
190
|
-
"@deepseek-ai/dsh-working-directory": "0.2.1-rc.
|
|
155
|
+
"@deepseek-ai/dsh-agent": "0.2.1-rc.3",
|
|
156
|
+
"@deepseek-ai/dsh-api-session-controller": "0.2.1-rc.3",
|
|
157
|
+
"@deepseek-ai/dsh-attachment": "0.2.1-rc.3",
|
|
158
|
+
"@deepseek-ai/dsh-client-connection": "0.2.1-rc.3",
|
|
159
|
+
"@deepseek-ai/dsh-client-locale": "0.2.1-rc.3",
|
|
160
|
+
"@deepseek-ai/dsh-client-store": "0.2.1-rc.3",
|
|
161
|
+
"@deepseek-ai/dsh-client-ui-commands": "0.2.1-rc.3",
|
|
162
|
+
"@deepseek-ai/dsh-client-ui-conversation": "0.2.1-rc.3",
|
|
163
|
+
"@deepseek-ai/dsh-client-ui-input-trigger": "0.2.1-rc.3",
|
|
164
|
+
"@deepseek-ai/dsh-client-ui-plugin-manager": "0.2.1-rc.3",
|
|
165
|
+
"@deepseek-ai/dsh-client-ui-primitives": "0.2.1-rc.3",
|
|
166
|
+
"@deepseek-ai/dsh-client-ui-session": "0.2.1-rc.3",
|
|
167
|
+
"@deepseek-ai/dsh-client-ui-settings": "0.2.1-rc.3",
|
|
168
|
+
"@deepseek-ai/dsh-client-ui-slots": "0.2.1-rc.3",
|
|
169
|
+
"@deepseek-ai/dsh-command-compact": "0.2.1-rc.3",
|
|
170
|
+
"@deepseek-ai/dsh-commands": "0.2.1-rc.3",
|
|
171
|
+
"@deepseek-ai/dsh-compaction": "0.2.1-rc.3",
|
|
172
|
+
"@deepseek-ai/dsh-compaction-basic": "0.2.1-rc.3",
|
|
173
|
+
"@deepseek-ai/dsh-compaction-image-offload": "0.2.1-rc.3",
|
|
174
|
+
"@deepseek-ai/dsh-fs": "0.2.1-rc.3",
|
|
175
|
+
"@deepseek-ai/dsh-goal": "0.2.1-rc.3",
|
|
176
|
+
"@deepseek-ai/dsh-home-paths": "0.2.1-rc.3",
|
|
177
|
+
"@deepseek-ai/dsh-llm": "0.2.1-rc.3",
|
|
178
|
+
"@deepseek-ai/dsh-plan-mode": "0.2.1-rc.3",
|
|
179
|
+
"@deepseek-ai/dsh-sandbox": "0.2.1-rc.3",
|
|
180
|
+
"@deepseek-ai/dsh-scope": "0.2.1-rc.3",
|
|
181
|
+
"@deepseek-ai/dsh-session": "0.2.1-rc.3",
|
|
182
|
+
"@deepseek-ai/dsh-session-projection": "0.2.1-rc.3",
|
|
183
|
+
"@deepseek-ai/dsh-session-stats": "0.2.1-rc.3",
|
|
184
|
+
"@deepseek-ai/dsh-session-title": "0.2.1-rc.3",
|
|
185
|
+
"@deepseek-ai/dsh-settings": "0.2.1-rc.3",
|
|
186
|
+
"@deepseek-ai/dsh-token-meter": "0.2.1-rc.3",
|
|
187
|
+
"@deepseek-ai/dsh-tools": "0.2.1-rc.3",
|
|
188
|
+
"@deepseek-ai/dsh-typert-protocol": "0.2.1-rc.3",
|
|
189
|
+
"@deepseek-ai/dsh-user-questions": "0.2.1-rc.3",
|
|
190
|
+
"@deepseek-ai/dsh-working-directory": "0.2.1-rc.3",
|
|
191
191
|
"@deepseek-ai/schemastery": "3.18.5",
|
|
192
192
|
"@types/node": "^24.0.0",
|
|
193
193
|
"@types/react": "^18.3.31",
|