dsh-rewind-plugin 0.7.2 → 0.7.4
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 +4 -1
- package/README.md +2 -3
- package/docs/README.md +1 -1
- package/docs/compat/audit.md +5 -5
- package/docs/compat/diagnostics.md +55 -52
- package/docs/compat/diagnostics.zh.md +31 -34
- package/docs/release/release.md +3 -0
- package/docs/release/release.zh.md +3 -0
- package/lib/client.js +52 -38
- package/lib/types/client/build-info.d.ts +19 -0
- package/lib/types/client/hidden.d.ts +22 -0
- package/lib/types/client/popover.d.ts +9 -2
- package/lib/types/client/portals.d.ts +10 -3
- package/lib/types/client/styles.d.ts +1 -1
- package/package.json +1 -1
package/README.en.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
Conversation rewind for DeepSeek Harness: **rewind the conversation to any earlier user message in one click, in the same window** — no new branch, no window switch, with optional workspace-file restore (full Claude Code `/rewind` semantics).
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/dsh-rewind-plugin)
|
|
6
|
-
[](https://www.npmjs.com/package/dsh-rewind-plugin)
|
|
7
7
|
[](https://github.com/SiriLee/dsh-rewind/actions/workflows/ci.yml)
|
|
8
8
|
|
|
9
9
|
> English | [中文](README.md)
|
|
@@ -158,6 +158,9 @@ withdrew should consume the stable, locale-independent helpers exported from
|
|
|
158
158
|
4. **Rewinds from `≤ v0.3.3`** — compaction (`/compact`) is unavailable for those sessions. Newer versions are compatible; for affected old sessions, start a new session.
|
|
159
159
|
5. **The turn-rail shows rewound turns** — the right-side rail added in DSH `v0.1.2-alpha.1` keeps ticks for withdrawn messages: clicking does not jump and hovering shows the withdrawn text. Only a display difference; no functional impact.
|
|
160
160
|
|
|
161
|
+
> [!NOTE]
|
|
162
|
+
> Browser diagnostics are available; see [Browser diagnostics](docs/compat/diagnostics.md).
|
|
163
|
+
|
|
161
164
|
## Security
|
|
162
165
|
|
|
163
166
|
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 code"; 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. Delete `~/.dsh/rewind-snapshots/` to wipe file backups only (chat rewinds are unaffected); the plugin rebuilds automatically. 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).
|
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
DeepSeek Harness 插件:**一键就地回退对话到任意更早的用户消息**——同窗口内完成,不新建分支、不换窗口,可一并还原工作区文件(完整 Claude Code `/rewind` 语义)。
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/dsh-rewind-plugin)
|
|
6
|
-
[](https://www.npmjs.com/package/dsh-rewind-plugin)
|
|
7
7
|
[](https://github.com/SiriLee/dsh-rewind/actions/workflows/ci.yml)
|
|
8
8
|
|
|
9
9
|
> [English](README.en.md) | 中文
|
|
@@ -146,8 +146,7 @@ dsh plugin --profile web add dsh-rewind-plugin
|
|
|
146
146
|
5. **导轨显示已回退轮次**——DSH `v0.1.2-alpha.1` 新增的右侧导轨,会为已撤回消息保留刻度:点击不跳转、悬浮显示已撤回正文。仅显示差异,无功能影响。
|
|
147
147
|
|
|
148
148
|
> [!NOTE]
|
|
149
|
-
>
|
|
150
|
-
> [浏览器诊断与详细输出开关](docs/compat/diagnostics.zh.md)。
|
|
149
|
+
> 本插件提供浏览器端诊断输出;详见 [浏览器诊断](docs/compat/diagnostics.zh.md)。
|
|
151
150
|
|
|
152
151
|
## 安全
|
|
153
152
|
|
package/docs/README.md
CHANGED
|
@@ -15,7 +15,7 @@ index/navigation entry point.
|
|
|
15
15
|
| `contract/client-contract.md` | Rewind visibility contract for third-party DOM plugins (`.zh` mirror) | integrators |
|
|
16
16
|
| `compat/audit.md` | Compatibility audit: verified surfaces, recorded findings, probe matrix | maintainers |
|
|
17
17
|
| `compat/troubleshooting.md` | Known issues & repair steps (`.zh` mirror) | users / maintainers |
|
|
18
|
-
| `compat/diagnostics.md` | Browser diagnostics
|
|
18
|
+
| `compat/diagnostics.md` | Browser diagnostics (anomaly alerts; verbose switch gates the startup identity line) (`.zh` mirror) | users / maintainers |
|
|
19
19
|
| `release/release.md` | Release workflow & DSH peer-version alignment (`.zh` mirror) | maintainers |
|
|
20
20
|
|
|
21
21
|
Repo-root docs outside `docs/`: `SECURITY.md` (security model) and
|
package/docs/compat/audit.md
CHANGED
|
@@ -22,15 +22,15 @@
|
|
|
22
22
|
>
|
|
23
23
|
> `0.1.2-alpha.1` was never published to npm, so the peer OR-union is declared only from `0.1.2-alpha.2`; the plugin code still supports the `alpha.1`+ client.
|
|
24
24
|
>
|
|
25
|
-
> `0.1.2-alpha.2` … `0.1.2-alpha.
|
|
25
|
+
> `0.1.2-alpha.2` … `0.1.2-alpha.5` verified-compatible. Built/tested on the rc.2
|
|
26
26
|
> baseline (the `latest` dist-tag); the same published build also drives the alpha
|
|
27
|
-
> line, and `alpha.2`/`alpha.3`/`alpha.4` share the `0.1.2` tuple, so the
|
|
28
|
-
> `^0.1.2-alpha.2` peer member covers all
|
|
29
|
-
> primary baseline;
|
|
27
|
+
> line, and `alpha.2`/`alpha.3`/`alpha.4`/`alpha.5` share the `0.1.2` tuple, so the
|
|
28
|
+
> single `^0.1.2-alpha.2` peer member covers all four. `rc.2` (`latest`) remains the
|
|
29
|
+
> primary baseline; five versions verified: rc.2, alpha.2, alpha.3, alpha.4, alpha.5.
|
|
30
30
|
|
|
31
31
|
### Channeled seams (version × channel)
|
|
32
32
|
|
|
33
|
-
| Seam | rc.2 (`0.1.1-rc.2`) | alpha.2 / alpha.3 | alpha.4 |
|
|
33
|
+
| Seam | rc.2 (`0.1.1-rc.2`) | alpha.2 / alpha.3 | alpha.4 / alpha.5 |
|
|
34
34
|
| --- | --- | --- | --- |
|
|
35
35
|
| Host session log (`eventsOf`) | `Session.events` | `Session.events` | `session.snapshotEvents()` |
|
|
36
36
|
| Client chat snapshot (`chatSnapshotOf`) | session-face `chat` field | `uiConversation` `chat` view | `uiConversation` `chat` view |
|
|
@@ -1,54 +1,57 @@
|
|
|
1
|
-
# Browser diagnostics
|
|
1
|
+
# Browser diagnostics
|
|
2
2
|
|
|
3
3
|
[简体中文](diagnostics.zh.md)
|
|
4
4
|
|
|
5
|
-
Every browser-side diagnostic this plugin emits
|
|
6
|
-
channel, so
|
|
7
|
-
|
|
5
|
+
Every browser-side diagnostic this plugin emits is routed through one small,
|
|
6
|
+
level-filtered channel, so an anomaly surfaces under a single console filter.
|
|
7
|
+
The channel is deliberately narrow: it records only genuine internal anomalies
|
|
8
|
+
the plugin itself detects as off-contract — **not** the normal-path facts the
|
|
9
|
+
user already sees on screen, which carry no attribution.
|
|
8
10
|
|
|
9
|
-
##
|
|
11
|
+
## Always-on anomaly alerts
|
|
10
12
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
+
`error` and `warn` are always printed (no switch, no configuration) so a fault
|
|
14
|
+
surfaces even for a user who never configured anything:
|
|
13
15
|
|
|
14
|
-
|
|
|
16
|
+
| Level | Meaning |
|
|
15
17
|
| --- | --- |
|
|
16
|
-
| `
|
|
17
|
-
| `
|
|
18
|
-
| `portals` | Per-message button mount issues (e.g. no session binding) |
|
|
19
|
-
| `settings` | The snapshot-cleanup settings card |
|
|
18
|
+
| `error` | Unexpected/breaking failure (e.g. the settings card fails to register) |
|
|
19
|
+
| `warn` | Recoverable anomaly guard (e.g. a rewind command/refill throws, a waited-on outcome never settles, an unmet session binding) |
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
These cases are rare, cheap, and off-contract. Normal-path detail (which
|
|
22
|
+
branch a refill took, whether a write matched, which rows a rewind cut) is
|
|
23
|
+
deliberately **not** logged: it re-states what the user already sees and adds
|
|
24
|
+
nothing to attribution.
|
|
22
25
|
|
|
23
|
-
|
|
24
|
-
| --- | --- | --- |
|
|
25
|
-
| `error` | on | Unexpected/breaking; always printed |
|
|
26
|
-
| `warn` | on | Recoverable anomaly guard (`rewind not hidden`, `refill skipped/refused/threw`); always printed |
|
|
27
|
-
| `info` | off | Event-level lifecycle (one line per rewind / refill) |
|
|
28
|
-
| `debug` | off | Additional event-level detail behind the switch (the hide-set line, one per rewind) |
|
|
26
|
+
## Scopes
|
|
29
27
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
refill), never per streaming frame — so a rewound session does not flood the
|
|
34
|
-
console even with verbose output switched on.
|
|
28
|
+
Diagnostics are printed as `[dsh-rewind:<scope>] ...`. The scope names the
|
|
29
|
+
subsystem an anomaly belongs to, so a report can be captured with a single
|
|
30
|
+
console filter:
|
|
35
31
|
|
|
36
|
-
|
|
32
|
+
| Scope | What an anomaly here means |
|
|
33
|
+
| --- | --- |
|
|
34
|
+
| `boot` | Startup identity — `loaded v<version> (build <hash>)`, gated by the verbose switch (see below). Confirms the running bundle matches a fix. |
|
|
35
|
+
| `refill` | The composer refill after a rewind (command/wait/refill throws, an outcome that never settles) |
|
|
36
|
+
| `portals` | Per-message button mount issues (e.g. no session binding) |
|
|
37
|
+
| `settings` | The snapshot-cleanup settings card |
|
|
38
|
+
| `hiding` | **Reserved** — no active alert at present. If a future row-hiding diagnostic is added, it belongs in this region. |
|
|
37
39
|
|
|
38
|
-
|
|
39
|
-
key, so it can never enable another plugin/feature and nothing else can wake
|
|
40
|
-
this one. Set it, reload the page, reproduce, then filter the console for
|
|
41
|
-
`[dsh-rewind]`:
|
|
40
|
+
## Verbose switch
|
|
42
41
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
42
|
+
The `info`/`debug` levels are gated by `localStorage['dsh-rewind.debug']`, read
|
|
43
|
+
once per call and filtered by namespace. After the withdrawal of the restating
|
|
44
|
+
verbose detail, exactly **one** verbose line remains: the `boot` scope's startup
|
|
45
|
+
identity line. It is deliberately **off** by default (a normal user's console
|
|
46
|
+
stays clean, and it is not an anomaly), so a reporter enables the scope to see
|
|
47
|
+
it:
|
|
46
48
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
+
```js
|
|
50
|
+
// Just the startup identity line.
|
|
51
|
+
localStorage['dsh-rewind.debug'] = 'dsh-rewind:boot'
|
|
49
52
|
|
|
50
|
-
//
|
|
51
|
-
localStorage['dsh-rewind.debug'] = 'dsh-rewind
|
|
53
|
+
// Or everything (avoids needing an exact scope).
|
|
54
|
+
localStorage['dsh-rewind.debug'] = 'dsh-rewind*'
|
|
52
55
|
```
|
|
53
56
|
|
|
54
57
|
Reload (`F5`) after setting it. To switch it off:
|
|
@@ -57,26 +60,26 @@ Reload (`F5`) after setting it. To switch it off:
|
|
|
57
60
|
delete localStorage['dsh-rewind.debug']
|
|
58
61
|
```
|
|
59
62
|
|
|
63
|
+
The `error`/`warn` anomaly alerts are **not** gated by this switch — they are
|
|
64
|
+
always printed. The other verbose detail (used write channel, an empty
|
|
65
|
+
hide-set, per-rewind lifecycle lines) has been withdrawn: it re-stated behavior
|
|
66
|
+
the user already sees and carried no attribution.
|
|
67
|
+
|
|
60
68
|
## Capturing a report
|
|
61
69
|
|
|
62
|
-
1.
|
|
63
|
-
|
|
64
|
-
2. Reproduce once.
|
|
65
|
-
3. In DevTools, filter the Console for `[dsh-rewind]` and copy the output
|
|
70
|
+
1. Reproduce once on the affected page.
|
|
71
|
+
2. In DevTools, filter the Console for `[dsh-rewind]` and copy the output
|
|
66
72
|
(with the plugin version and the DSH/kernel version).
|
|
67
73
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
render desync.
|
|
74
|
+
The `error`/`warn` anomaly alerts require no setup — they always print. To also
|
|
75
|
+
capture the startup identity line (`boot`) — useful for ruling out a stale
|
|
76
|
+
bundle / un-restarted host — enable the verbose switch first (see above), then
|
|
77
|
+
reload.
|
|
73
78
|
|
|
74
79
|
## Notes
|
|
75
80
|
|
|
76
|
-
-
|
|
77
|
-
stable public interface; its exact keys and output may change
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
Related: [audit.md](audit.md) for the verified-compatibility matrix and
|
|
82
|
-
[troubleshooting.md](troubleshooting.md) for the legacy repair steps.
|
|
81
|
+
- This diagnostic surface is an aid for maintainers and cooperating reporters,
|
|
82
|
+
**not** a stable public interface; its exact keys and output may change
|
|
83
|
+
without notice.
|
|
84
|
+
- It is scoped to one browser origin and one browser; capture where the problem
|
|
85
|
+
actually occurs.
|
|
@@ -1,44 +1,42 @@
|
|
|
1
|
-
#
|
|
1
|
+
# 浏览器诊断
|
|
2
2
|
|
|
3
3
|
[English](diagnostics.md)
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
本插件在浏览器端输出的所有诊断信息都经由同一条分级通道,因此异常可在单个控制台过滤器下浮现。该通道**刻意收窄**:只记录插件自身检测到的、偏离预期契约的真实内部异常——**不记录**用户早已在屏幕上看到、且无归因价值的普通路径事实。
|
|
6
6
|
|
|
7
|
-
##
|
|
7
|
+
## 常开的异常告警
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
`error` 与 `warn` **总是打印**(无需开关、无需配置),即使从未配置的用户也能看到故障:
|
|
10
10
|
|
|
11
|
-
|
|
|
11
|
+
| 级别 | 含义 |
|
|
12
12
|
| --- | --- |
|
|
13
|
-
| `
|
|
14
|
-
| `
|
|
15
|
-
| `portals` | 每条消息按钮的挂载问题(如无会话绑定) |
|
|
16
|
-
| `settings` | 快照清理设置卡片 |
|
|
13
|
+
| `error` | 意外/破坏性故障(如设置卡片注册失败) |
|
|
14
|
+
| `warn` | 可恢复的异常护栏(如回退命令/回填抛出异常、等待的结果始终未落定、会话绑定缺失) |
|
|
17
15
|
|
|
18
|
-
|
|
16
|
+
这些情形少见、廉价、且偏离契约。普通路径的细节(回填走了哪个分支、写入是否匹配、回退切断哪些行)则**刻意不记录**:它们重复用户已经看到的现象,对归因毫无增量。
|
|
19
17
|
|
|
20
|
-
|
|
21
|
-
| --- | --- | --- |
|
|
22
|
-
| `error` | 开 | 意外/破坏性;总是打印 |
|
|
23
|
-
| `warn` | 开 | 可恢复的异常护栏(`rewind not hidden`、`refill skipped/refused/threw`);总是打印 |
|
|
24
|
-
| `info` | 关 | 事件级生命周期(每次回退/回填一行) |
|
|
25
|
-
| `debug` | 关 | 开关控制下的追加事件级细节(隐藏集一行,每次回退一行) |
|
|
18
|
+
## 作用域
|
|
26
19
|
|
|
27
|
-
`
|
|
20
|
+
诊断以 `[dsh-rewind:<scope>] ...` 形式打印。`scope` 标明异常所属子系统,因此可用单个控制台过滤器抓取报告:
|
|
28
21
|
|
|
29
|
-
|
|
22
|
+
| scope | 这里的异常含义 |
|
|
23
|
+
| --- | --- |
|
|
24
|
+
| `boot` | 启动身份行——`loaded v<version> (build <hash>)`,由详细输出开关控制(见下)。用于确认运行中的 bundle 是否匹配某个修复。 |
|
|
25
|
+
| `refill` | 回退后的输入框回填(命令/等待/回填抛出异常、等待的结果始终未落定) |
|
|
26
|
+
| `portals` | 每条消息按钮的挂载问题(如无会话绑定) |
|
|
27
|
+
| `settings` | 快照清理设置卡片 |
|
|
28
|
+
| `hiding` | **保留区**——当前无活跃告警。若未来新增行隐藏诊断,应归属此区域。 |
|
|
30
29
|
|
|
31
|
-
|
|
30
|
+
## 详细输出开关
|
|
32
31
|
|
|
33
|
-
|
|
34
|
-
// 全部 dsh-rewind 命名空间。
|
|
35
|
-
localStorage['dsh-rewind.debug'] = 'dsh-rewind*'
|
|
32
|
+
`info`/`debug` 级别受 `localStorage['dsh-rewind.debug']` 控制,每次调用读取并按命名空间过滤。在撤下那些复述现象的详细输出后,**恰好剩一条** verbose 行:`boot` 作用域的启动身份行。它**默认关闭**(普通用户控制台保持干净,且它不是异常),排查者需先开启该作用域才能看到:
|
|
36
33
|
|
|
37
|
-
|
|
38
|
-
|
|
34
|
+
```js
|
|
35
|
+
// 仅启动身份行。
|
|
36
|
+
localStorage['dsh-rewind.debug'] = 'dsh-rewind:boot'
|
|
39
37
|
|
|
40
|
-
//
|
|
41
|
-
localStorage['dsh-rewind.debug'] = 'dsh-rewind
|
|
38
|
+
// 或全部开启(省去精确 scope)。
|
|
39
|
+
localStorage['dsh-rewind.debug'] = 'dsh-rewind*'
|
|
42
40
|
```
|
|
43
41
|
|
|
44
42
|
设置后刷新(`F5`)。关闭:
|
|
@@ -47,17 +45,16 @@ localStorage['dsh-rewind.debug'] = 'dsh-rewind:refill,dsh-rewind:hiding'
|
|
|
47
45
|
delete localStorage['dsh-rewind.debug']
|
|
48
46
|
```
|
|
49
47
|
|
|
48
|
+
`error`/`warn` 异常告警**不受**该开关控制——它们总是打印。其余详细输出(所用写入通道、空的隐藏集、每次回退的生命周期行)已撤下:它们重复用户已经看到的现象、无归因增量。
|
|
49
|
+
|
|
50
50
|
## 采集一段报告
|
|
51
51
|
|
|
52
|
-
1.
|
|
53
|
-
2.
|
|
54
|
-
3. 在 DevTools 里把 Console 按 `[dsh-rewind]` 过滤,复制输出(连同插件版本、DSH/内核版本)。
|
|
52
|
+
1. 在出问题的页面上复现一次。
|
|
53
|
+
2. 在 DevTools 里把 Console 按 `[dsh-rewind]` 过滤,复制输出(连同插件版本、DSH/内核版本)。
|
|
55
54
|
|
|
56
|
-
|
|
55
|
+
`error`/`warn` 异常告警无需任何设置——它们总是打印。若要同时采集启动身份行(`boot`,用于排除「旧 bundle / 宿主未重启」),请先开启详细输出开关(见上),再刷新。
|
|
57
56
|
|
|
58
57
|
## 备注
|
|
59
58
|
|
|
60
|
-
-
|
|
61
|
-
- 它受单个浏览器 origin
|
|
62
|
-
|
|
63
|
-
相关:[audit.md](audit.md) 为已验证兼容矩阵,[troubleshooting.md](troubleshooting.md) 为历史修复步骤。
|
|
59
|
+
- 该诊断表面是维护者与配合排查者使用的工具,**不是**稳定公开接口;其具体键与输出可能不经通知而改变。
|
|
60
|
+
- 它受单个浏览器 origin、单个浏览器限定;请在实际出问题的地方采集。
|
package/docs/release/release.md
CHANGED
|
@@ -61,6 +61,9 @@ uses an OR-union covering every published tuple series
|
|
|
61
61
|
(`0.1.1 → 0.1.2 → 0.2.x`); rc rolling within a tuple (`0.1.1-rc.2 → rc.3`)
|
|
62
62
|
needs nothing. All `@deepseek-ai/*` packages release together;
|
|
63
63
|
`npm view @deepseek-ai/dsh version` is the authoritative signal.
|
|
64
|
+
- **Exception — `@deepseek-ai/dsh-client-runtime`**: it never published a
|
|
65
|
+
`0.1.2-alpha.*` (npm `next` is `0.1.1-rc.2`) and is imported `import type`
|
|
66
|
+
only, so keep it at `^0.1.0-rc.6 || ^0.1.1-rc.2` (no `0.1.2` member).
|
|
64
67
|
- **Published-tuple check (optional)**: `node scripts/check-dsh-version.mjs`
|
|
65
68
|
compares the `latest` dist-tag version against the tuples the peers cover
|
|
66
69
|
(exit 0 = nothing to do, exit 1 = update). It reads the `latest` tag only; a
|
|
@@ -55,6 +55,9 @@ OR 并集覆盖 DSH 已发布的每个元组系列(如 `^0.1.0-rc.6 || ^0.1.1-
|
|
|
55
55
|
- **何时需要更新**:仅当 DSH 发布新元组(`0.1.1 → 0.1.2 → 0.2.x`)时;
|
|
56
56
|
同元组内 rc 滚动(`0.1.1-rc.2 → rc.3`)无需动作。DSH 所有包同版本发布,
|
|
57
57
|
`npm view @deepseek-ai/dsh version` 即权威信号。
|
|
58
|
+
- **例外 —— `@deepseek-ai/dsh-client-runtime`**:它从未发布 `0.1.2-alpha.*`
|
|
59
|
+
(npm `next` 为 `0.1.1-rc.2`)且仅被 `import type` 引用,保持
|
|
60
|
+
`^0.1.0-rc.6 || ^0.1.1-rc.2` 即可(无 `0.1.2` 元组项)。
|
|
58
61
|
- **已发布元组检查(可选)**:`node scripts/check-dsh-version.mjs` 用 npm `latest`
|
|
59
62
|
dist-tag 版本对比 peer 覆盖的元组(exit 0 无需动作,exit 1 需要)。它**只读
|
|
60
63
|
`latest` tag**;发布在其它 tag(如 `alpha`)的 pre-release 走**手动发布前检查**
|
package/lib/client.js
CHANGED
|
@@ -35,6 +35,13 @@ __export(index_exports, {
|
|
|
35
35
|
module.exports = __toCommonJS(index_exports);
|
|
36
36
|
|
|
37
37
|
// src/client/hidden.ts
|
|
38
|
+
function resolveChatWatch(resolveView, resolveFace, sessionId, cb) {
|
|
39
|
+
const view = resolveView(sessionId);
|
|
40
|
+
if (view?.subscribe !== void 0) return view.subscribe(cb);
|
|
41
|
+
const face = resolveFace(sessionId);
|
|
42
|
+
return face?.subscribe(cb) ?? (() => {
|
|
43
|
+
});
|
|
44
|
+
}
|
|
38
45
|
function chatSnapshotOf(face, chatView) {
|
|
39
46
|
const legacy = face?.getSnapshot().chat;
|
|
40
47
|
if (legacy !== void 0) return legacy;
|
|
@@ -321,6 +328,12 @@ var STYLE = `
|
|
|
321
328
|
}
|
|
322
329
|
|
|
323
330
|
/* ---- Snapshot-cleanup settings card (mirrors the harness PluginCard look) ---- */
|
|
331
|
+
/* Dual-channel note: the harness card corner/border drifted across versions \u2014
|
|
332
|
+
rc.2 uses border-radius 12px + 1px solid border-l2, while alpha uses 16px +
|
|
333
|
+
0.5px border-l4 (and adds corner-shape: round). We intentionally keep the
|
|
334
|
+
rc.2 values here for now; if the alpha look is wanted, switch these on the
|
|
335
|
+
host version like the other dual-channel surfaces (see the host-version probe
|
|
336
|
+
in src/client/index.ts). */
|
|
324
337
|
.dsh-rewind-cleanup-card {
|
|
325
338
|
list-style: none;
|
|
326
339
|
border: 1px solid var(--dsw-alias-border-l2);
|
|
@@ -619,7 +632,7 @@ function knownCommandSeqs(session, chatOf, match) {
|
|
|
619
632
|
}
|
|
620
633
|
return known;
|
|
621
634
|
}
|
|
622
|
-
function waitForCommand(session, chatOf, match, timeoutMs = 8e3) {
|
|
635
|
+
function waitForCommand(session, chatOf, match, timeoutMs = 8e3, watch) {
|
|
623
636
|
return new Promise((resolve) => {
|
|
624
637
|
let settled = false;
|
|
625
638
|
const settle = (value) => {
|
|
@@ -635,7 +648,7 @@ function waitForCommand(session, chatOf, match, timeoutMs = 8e3) {
|
|
|
635
648
|
settle({ kind: node.outcome.kind, text: node.outcome.text });
|
|
636
649
|
}
|
|
637
650
|
};
|
|
638
|
-
const unsubscribe = session.subscribe(check);
|
|
651
|
+
const unsubscribe = (watch ?? ((cb) => session.subscribe(cb)))(check);
|
|
639
652
|
const timer = setTimeout(() => settle(null), timeoutMs);
|
|
640
653
|
check();
|
|
641
654
|
});
|
|
@@ -644,11 +657,11 @@ function isPreviewFor(node, seq) {
|
|
|
644
657
|
const args = node.args ?? "";
|
|
645
658
|
return node.name === "rewind" && args.includes("preview") && new RegExp(`(?:^|\\s)@${seq}(?=\\s|$)`).test(args);
|
|
646
659
|
}
|
|
647
|
-
async function previewImpact(session, chatOf, seq) {
|
|
660
|
+
async function previewImpact(session, chatOf, seq, watch) {
|
|
648
661
|
const known = knownCommandSeqs(session, chatOf, (node) => isPreviewFor(node, seq));
|
|
649
662
|
const result = await session.command(`/rewind preview @${seq} both`);
|
|
650
663
|
if (!result.ok || result.value?.matched !== true) return null;
|
|
651
|
-
return waitForCommand(session, chatOf, (node) => isPreviewFor(node, seq) && !known.has(node.seq));
|
|
664
|
+
return waitForCommand(session, chatOf, (node) => isPreviewFor(node, seq) && !known.has(node.seq), 8e3, watch);
|
|
652
665
|
}
|
|
653
666
|
function el(tag2, className, text) {
|
|
654
667
|
const node = document.createElement(tag2);
|
|
@@ -699,7 +712,7 @@ function renderImpactStep(root, opts, back, cached) {
|
|
|
699
712
|
root.replaceChildren(impact, actions);
|
|
700
713
|
focusFirst(root);
|
|
701
714
|
void (async () => {
|
|
702
|
-
const outcome = cached ?? await previewImpact(session, opts.chatOf, seq);
|
|
715
|
+
const outcome = cached ?? await previewImpact(session, opts.chatOf, seq, (cb) => opts.watchChat(session.sessionId, cb));
|
|
703
716
|
if (outcome === null) {
|
|
704
717
|
impact.textContent = t("popover.impact.failed", { message: "preview command failed or timed out" });
|
|
705
718
|
return;
|
|
@@ -821,7 +834,7 @@ function openPopover(opts) {
|
|
|
821
834
|
const { session, seq, time, preview, anchor, t, chatOf } = opts;
|
|
822
835
|
const onRewind = opts.onRewind;
|
|
823
836
|
if (seq === void 0 || time === void 0 || onRewind === void 0) return;
|
|
824
|
-
const durableOpts = { session, seq, time, preview, anchor, t, chatOf, onRewind };
|
|
837
|
+
const durableOpts = { session, seq, time, preview, anchor, t, chatOf, watchChat: opts.watchChat, onRewind };
|
|
825
838
|
const root = el("div", CLASS.popover);
|
|
826
839
|
root.setAttribute("role", "dialog");
|
|
827
840
|
root.setAttribute("aria-label", t("popover.title"));
|
|
@@ -891,7 +904,7 @@ function openPopover(opts) {
|
|
|
891
904
|
popoverEl = root;
|
|
892
905
|
disposeOutside = shell.dispose;
|
|
893
906
|
void (async () => {
|
|
894
|
-
const outcome = await previewImpact(session, chatOf, seq);
|
|
907
|
+
const outcome = await previewImpact(session, chatOf, seq, (cb) => opts.watchChat(session.sessionId, cb));
|
|
895
908
|
impactOutcome = outcome;
|
|
896
909
|
if (outcome !== null && outcome.kind === "success") {
|
|
897
910
|
bothState = { state: hasFileImpact(outcome.text) ? "hasChanges" : "noChanges" };
|
|
@@ -1025,7 +1038,7 @@ function userNodeOf(chat, key) {
|
|
|
1025
1038
|
if (node === void 0 || node.kind !== "user" && node.kind !== "steering") return void 0;
|
|
1026
1039
|
return node.data;
|
|
1027
1040
|
}
|
|
1028
|
-
async function runRewindAndFill(session, seq, mode, currentSessionId, chatOf, setComposerText) {
|
|
1041
|
+
async function runRewindAndFill(session, seq, mode, currentSessionId, chatOf, watchChat, setComposerText) {
|
|
1029
1042
|
const known = knownCommandSeqs(session, chatOf, (node) => isExecutedRewindCommand(node, seq));
|
|
1030
1043
|
let result;
|
|
1031
1044
|
try {
|
|
@@ -1035,12 +1048,11 @@ async function runRewindAndFill(session, seq, mode, currentSessionId, chatOf, se
|
|
|
1035
1048
|
return;
|
|
1036
1049
|
}
|
|
1037
1050
|
if (!result.ok || result.value?.matched !== true) {
|
|
1038
|
-
rewindLog.info("refill", `rewind @${seq} not matched, no refill`);
|
|
1039
1051
|
return;
|
|
1040
1052
|
}
|
|
1041
1053
|
let outcome;
|
|
1042
1054
|
try {
|
|
1043
|
-
outcome = await waitForCommand(session, chatOf, (node) => isExecutedRewindCommand(node, seq) && !known.has(node.seq), 2e4);
|
|
1055
|
+
outcome = await waitForCommand(session, chatOf, (node) => isExecutedRewindCommand(node, seq) && !known.has(node.seq), 2e4, (cb) => watchChat(session.sessionId, cb));
|
|
1044
1056
|
} catch (error) {
|
|
1045
1057
|
rewindLog.warn("refill", `waiting for rewind @${seq} outcome threw`, error);
|
|
1046
1058
|
return;
|
|
@@ -1050,25 +1062,10 @@ async function runRewindAndFill(session, seq, mode, currentSessionId, chatOf, se
|
|
|
1050
1062
|
return;
|
|
1051
1063
|
}
|
|
1052
1064
|
if (outcome.kind !== "success") {
|
|
1053
|
-
rewindLog.warn("refill", `rewind @${seq} refused`, outcome.text);
|
|
1054
1065
|
showHint(outcome.text ?? "rewind failed");
|
|
1055
1066
|
return;
|
|
1056
1067
|
}
|
|
1057
|
-
const hidSeqs = (() => {
|
|
1058
|
-
const chat = chatOf(session);
|
|
1059
|
-
return chat === void 0 ? /* @__PURE__ */ new Set() : hiddenSeqsOf(chat);
|
|
1060
|
-
})();
|
|
1061
|
-
if (hidSeqs.size === 0) {
|
|
1062
|
-
rewindLog.warn("hiding", `rewind not hidden (target @${seq})`, { target: seq });
|
|
1063
|
-
} else {
|
|
1064
|
-
rewindLog.debug(
|
|
1065
|
-
"hiding",
|
|
1066
|
-
`rewind hides seqs [${[...hidSeqs].slice(0, 20).join(", ")}${hidSeqs.size > 20 ? "\u2026" : ""}]`,
|
|
1067
|
-
{ target: seq }
|
|
1068
|
-
);
|
|
1069
|
-
}
|
|
1070
1068
|
if (currentSessionId() !== session.sessionId) {
|
|
1071
|
-
rewindLog.info("refill", `skipped refill @${seq}: session switched during rewind`);
|
|
1072
1069
|
return;
|
|
1073
1070
|
}
|
|
1074
1071
|
let text;
|
|
@@ -1079,21 +1076,17 @@ async function runRewindAndFill(session, seq, mode, currentSessionId, chatOf, se
|
|
|
1079
1076
|
return;
|
|
1080
1077
|
}
|
|
1081
1078
|
if (text === void 0 || text === "") {
|
|
1082
|
-
rewindLog.info("refill", `skipped refill @${seq}: no editable text`);
|
|
1083
1079
|
return;
|
|
1084
1080
|
}
|
|
1085
1081
|
if (composerText().trim() !== "") {
|
|
1086
|
-
rewindLog.info("refill", `skipped refill @${seq}: composer already has a draft`);
|
|
1087
1082
|
return;
|
|
1088
1083
|
}
|
|
1089
|
-
let ok = false;
|
|
1090
1084
|
try {
|
|
1091
|
-
|
|
1085
|
+
setComposerText(session.sessionId, text);
|
|
1092
1086
|
} catch (error) {
|
|
1093
1087
|
rewindLog.warn("refill", `composer refill @${seq} threw`, error);
|
|
1094
1088
|
return;
|
|
1095
1089
|
}
|
|
1096
|
-
rewindLog.info("refill", `rewound to @${seq} (${mode})`, { ok, text: text.slice(0, 80) });
|
|
1097
1090
|
}
|
|
1098
1091
|
function composerSurface() {
|
|
1099
1092
|
return document.querySelector(COMPOSER_TEXTAREA_SELECTOR) ?? document.querySelector(COMPOSER_EDITABLE_SELECTOR);
|
|
@@ -1189,7 +1182,7 @@ function sameTargets(left, right) {
|
|
|
1189
1182
|
return other.kind === "pending" && target.itemId === other.itemId;
|
|
1190
1183
|
});
|
|
1191
1184
|
}
|
|
1192
|
-
function RewindPortals({ sessionId, sessionOf, chatOf, currentSessionId, t, subscribeLocale, setComposerText }) {
|
|
1185
|
+
function RewindPortals({ sessionId, sessionOf, chatOf, currentSessionId, watchChat, t, subscribeLocale, setComposerText }) {
|
|
1193
1186
|
const [targets, setTargets] = (0, import_react.useState)([]);
|
|
1194
1187
|
const hidden = (0, import_react.useRef)(/* @__PURE__ */ new WeakSet());
|
|
1195
1188
|
const [, forceRender] = (0, import_react.useReducer)((count) => count + 1, 0);
|
|
@@ -1250,6 +1243,7 @@ function RewindPortals({ sessionId, sessionOf, chatOf, currentSessionId, t, subs
|
|
|
1250
1243
|
sessionId,
|
|
1251
1244
|
sessionOf,
|
|
1252
1245
|
chatOf,
|
|
1246
|
+
watchChat,
|
|
1253
1247
|
setComposerText,
|
|
1254
1248
|
t
|
|
1255
1249
|
},
|
|
@@ -1261,6 +1255,7 @@ function RewindPortals({ sessionId, sessionOf, chatOf, currentSessionId, t, subs
|
|
|
1261
1255
|
sessionId,
|
|
1262
1256
|
sessionOf,
|
|
1263
1257
|
chatOf,
|
|
1258
|
+
watchChat,
|
|
1264
1259
|
currentSessionId,
|
|
1265
1260
|
setComposerText,
|
|
1266
1261
|
t
|
|
@@ -1271,7 +1266,7 @@ function RewindPortals({ sessionId, sessionOf, chatOf, currentSessionId, t, subs
|
|
|
1271
1266
|
target.key
|
|
1272
1267
|
));
|
|
1273
1268
|
}
|
|
1274
|
-
function RewindButton({ target, sessionId, sessionOf, chatOf, currentSessionId, setComposerText, t }) {
|
|
1269
|
+
function RewindButton({ target, sessionId, sessionOf, chatOf, watchChat, currentSessionId, setComposerText, t }) {
|
|
1275
1270
|
const onClick = (event) => {
|
|
1276
1271
|
event.stopPropagation();
|
|
1277
1272
|
const session = sessionOf(sessionId);
|
|
@@ -1284,13 +1279,14 @@ function RewindButton({ target, sessionId, sessionOf, chatOf, currentSessionId,
|
|
|
1284
1279
|
openPopover({
|
|
1285
1280
|
session,
|
|
1286
1281
|
chatOf,
|
|
1282
|
+
watchChat,
|
|
1287
1283
|
seq: node.seq,
|
|
1288
1284
|
time: node.time,
|
|
1289
1285
|
preview: messagePreviewOf(node),
|
|
1290
1286
|
anchor: event.currentTarget,
|
|
1291
1287
|
t,
|
|
1292
1288
|
onRewind: (mode) => {
|
|
1293
|
-
void runRewindAndFill(session, node.seq, mode, currentSessionId, chatOf, setComposerText);
|
|
1289
|
+
void runRewindAndFill(session, node.seq, mode, currentSessionId, chatOf, watchChat, setComposerText);
|
|
1294
1290
|
}
|
|
1295
1291
|
});
|
|
1296
1292
|
};
|
|
@@ -1323,7 +1319,7 @@ async function retractPending(session, itemId, text, setComposerText) {
|
|
|
1323
1319
|
setComposerText(session.sessionId, text);
|
|
1324
1320
|
}
|
|
1325
1321
|
}
|
|
1326
|
-
function RetractButton({ target, sessionId, sessionOf, chatOf, setComposerText, t }) {
|
|
1322
|
+
function RetractButton({ target, sessionId, sessionOf, chatOf, watchChat, setComposerText, t }) {
|
|
1327
1323
|
const onClick = (event) => {
|
|
1328
1324
|
event.stopPropagation();
|
|
1329
1325
|
const session = sessionOf(sessionId);
|
|
@@ -1331,6 +1327,7 @@ function RetractButton({ target, sessionId, sessionOf, chatOf, setComposerText,
|
|
|
1331
1327
|
openPopover({
|
|
1332
1328
|
session,
|
|
1333
1329
|
chatOf,
|
|
1330
|
+
watchChat,
|
|
1334
1331
|
preview: target.preview,
|
|
1335
1332
|
anchor: event.currentTarget,
|
|
1336
1333
|
t,
|
|
@@ -1358,6 +1355,10 @@ function createRewindBridge(deps) {
|
|
|
1358
1355
|
};
|
|
1359
1356
|
}
|
|
1360
1357
|
|
|
1358
|
+
// src/client/build-info.ts
|
|
1359
|
+
var PLUGIN_VERSION = true ? "0.7.4" : "dev";
|
|
1360
|
+
var BUILD_HASH = true ? "73e01232" : "dev";
|
|
1361
|
+
|
|
1361
1362
|
// src/client/locales.ts
|
|
1362
1363
|
var zh = {
|
|
1363
1364
|
"button.aria": "\u56DE\u9000\u5230\u6B64\u6D88\u606F",
|
|
@@ -1617,6 +1618,7 @@ var HEADER_ACTIONS_SLOT = "conversation.session.header.actions";
|
|
|
1617
1618
|
var COMPOSER_TEXTAREA_SELECTOR2 = "[data-input-scroll] textarea, textarea[data-phase]";
|
|
1618
1619
|
var COMPOSER_EDITABLE_SELECTOR2 = "[data-composer-input]";
|
|
1619
1620
|
function apply(ctx) {
|
|
1621
|
+
rewindLog.info("boot", `loaded v${PLUGIN_VERSION} (build ${BUILD_HASH})`);
|
|
1620
1622
|
ctx.effect(function* () {
|
|
1621
1623
|
yield ctx.locale.register(NS2, { zh, en });
|
|
1622
1624
|
const t = ctx.locale.bind(NS2);
|
|
@@ -1650,13 +1652,24 @@ function apply(ctx) {
|
|
|
1650
1652
|
input.for(scope).setDraft(draft);
|
|
1651
1653
|
} } : void 0
|
|
1652
1654
|
);
|
|
1653
|
-
rewindLog.info("refill", "composer write", { channel: facade ? "facade" : "dom", ok });
|
|
1654
1655
|
return ok;
|
|
1655
1656
|
} catch (error) {
|
|
1656
1657
|
rewindLog.warn("refill", "composer write threw", error);
|
|
1657
1658
|
return false;
|
|
1658
1659
|
}
|
|
1659
1660
|
};
|
|
1661
|
+
const watchChat = (sessionId, cb) => resolveChatWatch(
|
|
1662
|
+
(id) => {
|
|
1663
|
+
try {
|
|
1664
|
+
return uiConversation()?.binding(id).target(CHAT_VIEW);
|
|
1665
|
+
} catch {
|
|
1666
|
+
return void 0;
|
|
1667
|
+
}
|
|
1668
|
+
},
|
|
1669
|
+
(id) => sessionOf(id),
|
|
1670
|
+
sessionId,
|
|
1671
|
+
cb
|
|
1672
|
+
);
|
|
1660
1673
|
const slots = ctx.slots;
|
|
1661
1674
|
yield slots.inject(HEADER_ACTIONS_SLOT, () => slots.register(
|
|
1662
1675
|
{
|
|
@@ -1666,7 +1679,7 @@ function apply(ctx) {
|
|
|
1666
1679
|
id: "dsh-rewind-portals",
|
|
1667
1680
|
order: 1e3
|
|
1668
1681
|
},
|
|
1669
|
-
createRewindBridge({ sessionOf, chatOf, currentSessionId, setComposerText, t, subscribeLocale })
|
|
1682
|
+
createRewindBridge({ sessionOf, chatOf, currentSessionId, watchChat, setComposerText, t, subscribeLocale })
|
|
1670
1683
|
));
|
|
1671
1684
|
const clientCtx = ctx;
|
|
1672
1685
|
clientCtx.inject(["settingsScope"], (scoped) => {
|
|
@@ -1712,7 +1725,7 @@ function apply(ctx) {
|
|
|
1712
1725
|
const known = knownCommandSeqs(face, chatOf2, (node) => isCandidateCommand(node));
|
|
1713
1726
|
const result = await face.command("/rewind __candidates");
|
|
1714
1727
|
if (!result.ok || result.value?.matched !== true) return void 0;
|
|
1715
|
-
const outcome = await waitForCommand(face, chatOf2, (node) => isCandidateCommand(node) && !known.has(node.seq));
|
|
1728
|
+
const outcome = await waitForCommand(face, chatOf2, (node) => isCandidateCommand(node) && !known.has(node.seq), 8e3, (cb) => watchChat(face.sessionId, cb));
|
|
1716
1729
|
if (outcome === null || outcome.kind !== "success" || outcome.text === void 0) return void 0;
|
|
1717
1730
|
return rewindCandidatesFromHostText(outcome.text);
|
|
1718
1731
|
};
|
|
@@ -1747,13 +1760,14 @@ function apply(ctx) {
|
|
|
1747
1760
|
openPopover({
|
|
1748
1761
|
session: face,
|
|
1749
1762
|
chatOf,
|
|
1763
|
+
watchChat,
|
|
1750
1764
|
seq: candidate.seq,
|
|
1751
1765
|
time: candidate.time,
|
|
1752
1766
|
preview: candidate.preview,
|
|
1753
1767
|
anchor: composerAnchor(),
|
|
1754
1768
|
t,
|
|
1755
1769
|
onRewind: (mode) => {
|
|
1756
|
-
void runRewindAndFill(face, candidate.seq, mode, currentSessionId, chatOf, setComposerText);
|
|
1770
|
+
void runRewindAndFill(face, candidate.seq, mode, currentSessionId, chatOf, watchChat, setComposerText);
|
|
1757
1771
|
}
|
|
1758
1772
|
});
|
|
1759
1773
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-rewind client build identity.
|
|
3
|
+
*
|
|
4
|
+
* Two constants are injected by `scripts/build.mjs` at esbuild time (see the
|
|
5
|
+
* `define` block there) and are NOT present in the source. They answer the
|
|
6
|
+
* "am I even running the fixed bundle?" question — the most common, cheapest
|
|
7
|
+
* root cause to rule in/out when a report lands:
|
|
8
|
+
* - `__DSH_REWIND_VERSION__` — the plugin version from `package.json`.
|
|
9
|
+
* - `__DSH_REWIND_BUILD__` — a short content hash of the client source.
|
|
10
|
+
*
|
|
11
|
+
* They are read once at module load and surfaced (behind the existing
|
|
12
|
+
* `dsh-rewind.debug` switch, never a new key) by `apply` in `index.ts`.
|
|
13
|
+
*
|
|
14
|
+
* @module dsh-rewind/client/build-info
|
|
15
|
+
*/
|
|
16
|
+
/** Plugin version baked in at build time (from `package.json`). */
|
|
17
|
+
export declare const PLUGIN_VERSION: string;
|
|
18
|
+
/** Short content hash of the client source, for stale-bundle detection. */
|
|
19
|
+
export declare const BUILD_HASH: string;
|
|
@@ -26,6 +26,28 @@ export type ChatOf = (session: {
|
|
|
26
26
|
chat?: unknown;
|
|
27
27
|
};
|
|
28
28
|
} | undefined) => HiddenChat | undefined;
|
|
29
|
+
/**
|
|
30
|
+
* Subscribe to one session's live chat-update signal, for waiting on a chat
|
|
31
|
+
* snapshot change without polling. Dual-channel, mirroring `chatOf`: the
|
|
32
|
+
* alpha.1+ `uiConversation` "chat" view when registered, else the session face
|
|
33
|
+
* (rc.2, whose snapshot still carries `chat`, so its own `subscribe` is the
|
|
34
|
+
* chat-update signal). `cb` fires whenever the chat snapshot invalidates.
|
|
35
|
+
*/
|
|
36
|
+
export type ChatWatch = (sessionId: string, cb: () => void) => () => void;
|
|
37
|
+
/**
|
|
38
|
+
* Choose the chat-update subscription for `waitForCommand`: prefer the
|
|
39
|
+
* `uiConversation` "chat" view's own `subscribe` when available (alpha.1+,
|
|
40
|
+
* where the chat-update signal lives), else the session face's `subscribe`
|
|
41
|
+
* (rc.2, whose snapshot still carries the chat, so its own subscribe is the
|
|
42
|
+
* chat-update signal). Extracted as a pure channel-selection step so the
|
|
43
|
+
* "view vs face" branch is unit-testable; the resolvers are injected by the
|
|
44
|
+
* caller (see `watchChat` in index.ts). Never throws.
|
|
45
|
+
*/
|
|
46
|
+
export declare function resolveChatWatch(resolveView: (sessionId: string) => {
|
|
47
|
+
subscribe?(cb: () => void): () => void;
|
|
48
|
+
} | undefined, resolveFace: (sessionId: string) => {
|
|
49
|
+
subscribe(cb: () => void): () => void;
|
|
50
|
+
} | undefined, sessionId: string, cb: () => void): () => void;
|
|
29
51
|
/**
|
|
30
52
|
* Resolve the chat snapshot across the two harness channels: the session-face
|
|
31
53
|
* snapshot first (rc.2 — on alpha.1+ the face no longer carries `chat`, so the
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
*/
|
|
16
16
|
import type { SessionFace } from '@deepseek-ai/dsh-client-runtime/client';
|
|
17
17
|
import type { CommandNode } from '@deepseek-ai/dsh-client-runtime/client';
|
|
18
|
-
import { type ChatOf } from './hidden.ts';
|
|
18
|
+
import { type ChatOf, type ChatWatch } from './hidden.ts';
|
|
19
19
|
import type { RewindKey } from './locales.ts';
|
|
20
20
|
type Translate = (key: RewindKey, params?: Record<string, unknown>) => string;
|
|
21
21
|
export interface PopoverOptions {
|
|
@@ -38,6 +38,13 @@ export interface PopoverOptions {
|
|
|
38
38
|
* Unused by the pending-retract variant.
|
|
39
39
|
*/
|
|
40
40
|
readonly chatOf: ChatOf;
|
|
41
|
+
/**
|
|
42
|
+
* Subscribe to one session's live chat-update signal, so a probe waiting on
|
|
43
|
+
* a command's chat node can be woken when the chat snapshot changes (alpha.1+
|
|
44
|
+
* the session face no longer fires on a chat update). Passed straight through
|
|
45
|
+
* to `waitForCommand`.
|
|
46
|
+
*/
|
|
47
|
+
readonly watchChat: ChatWatch;
|
|
41
48
|
/** The button that opened the popover (outside-click ignore target). */
|
|
42
49
|
readonly anchor: HTMLElement;
|
|
43
50
|
readonly t: Translate;
|
|
@@ -63,7 +70,7 @@ export declare function knownCommandSeqs(session: SessionFace, chatOf: ChatOf, m
|
|
|
63
70
|
* session snapshot (command/run + command/done land as one CommandNode).
|
|
64
71
|
* @returns the outcome text-bearing node, or null on timeout.
|
|
65
72
|
*/
|
|
66
|
-
export declare function waitForCommand(session: SessionFace, chatOf: ChatOf, match: (node: CommandNode) => boolean, timeoutMs?: number): Promise<{
|
|
73
|
+
export declare function waitForCommand(session: SessionFace, chatOf: ChatOf, match: (node: CommandNode) => boolean, timeoutMs?: number, watch?: (cb: () => void) => () => void): Promise<{
|
|
67
74
|
kind: 'success' | 'error';
|
|
68
75
|
text?: string;
|
|
69
76
|
} | null>;
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
*/
|
|
25
25
|
import { type ReactNode } from 'react';
|
|
26
26
|
import type { SessionFace } from '@deepseek-ai/dsh-client-runtime/client';
|
|
27
|
-
import { type ChatOf, type HiddenChat } from './hidden.ts';
|
|
27
|
+
import { type ChatOf, type ChatWatch, type HiddenChat } from './hidden.ts';
|
|
28
28
|
import type { RewindKey } from './locales.ts';
|
|
29
29
|
type Translate = (key: RewindKey, params?: Record<string, unknown>) => string;
|
|
30
30
|
/** One portal target: the actions row of a user/steering seat + its durable node. */
|
|
@@ -58,6 +58,13 @@ export interface RewindBridgeDeps {
|
|
|
58
58
|
* `chatSnapshotOf` in hidden.ts for the channel precedence.
|
|
59
59
|
*/
|
|
60
60
|
readonly chatOf: ChatOf;
|
|
61
|
+
/**
|
|
62
|
+
* Subscribe to one session's live chat-update signal, so the composer refill
|
|
63
|
+
* waiting on an executed rewind's chat node can be woken when the chat
|
|
64
|
+
* snapshot changes (alpha.1+ the session face no longer fires on a chat
|
|
65
|
+
* update). Passed through to `waitForCommand`.
|
|
66
|
+
*/
|
|
67
|
+
readonly watchChat: ChatWatch;
|
|
61
68
|
readonly currentSessionId: () => string | undefined;
|
|
62
69
|
readonly t: Translate;
|
|
63
70
|
readonly subscribeLocale: (cb: () => void) => () => void;
|
|
@@ -124,7 +131,7 @@ export declare function writeComposer(text: string, facade: ComposerDraftWriter
|
|
|
124
131
|
* the old baseline heuristic refilled withdrawn text into the composer
|
|
125
132
|
* after switching sessions or restarting dsh.
|
|
126
133
|
*/
|
|
127
|
-
export declare function runRewindAndFill(session: SessionFace, seq: number, mode: 'chat' | 'both', currentSessionId: () => string | undefined, chatOf: ChatOf, setComposerText: (sessionId: string, text: string) => boolean): Promise<void>;
|
|
134
|
+
export declare function runRewindAndFill(session: SessionFace, seq: number, mode: 'chat' | 'both', currentSessionId: () => string | undefined, chatOf: ChatOf, watchChat: ChatWatch, setComposerText: (sessionId: string, text: string) => boolean): Promise<void>;
|
|
128
135
|
/**
|
|
129
136
|
* Locate the actions container of a user/steering seat row — the element the
|
|
130
137
|
* ↶ button portals into (the copy/branch IconActions row).
|
|
@@ -163,7 +170,7 @@ interface RewindPortalsProps extends RewindBridgeDeps {
|
|
|
163
170
|
* skipped when the target set is unchanged), so the plugin never runs a
|
|
164
171
|
* synchronous full-transcript scan inside a commit microtask.
|
|
165
172
|
*/
|
|
166
|
-
export declare function RewindPortals({ sessionId, sessionOf, chatOf, currentSessionId, t, subscribeLocale, setComposerText }: RewindPortalsProps): ReactNode;
|
|
173
|
+
export declare function RewindPortals({ sessionId, sessionOf, chatOf, currentSessionId, watchChat, t, subscribeLocale, setComposerText }: RewindPortalsProps): ReactNode;
|
|
167
174
|
/** The current composer draft, on whichever channel: rc.2 textarea `.value`,
|
|
168
175
|
* alpha.1+ contenteditable `textContent`. Empty when the composer is absent.
|
|
169
176
|
* Exported as a test seam (the empty-composer guard in `retractPending`). */
|
|
@@ -23,4 +23,4 @@ export declare const CLASS: {
|
|
|
23
23
|
/** The ↶ glyph, drawn inline so the bundle stays dependency-free. */
|
|
24
24
|
export declare const REWIND_ICON_SVG: string;
|
|
25
25
|
/** One injected stylesheet (scoped under `.dsh-rewind-*`). */
|
|
26
|
-
export declare const STYLE = "\n.dsh-rewind-btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 28px;\n height: 28px;\n padding: 6px;\n border: none;\n border-radius: 28px;\n background: transparent;\n color: var(--dsw-alias-label-tertiary);\n cursor: pointer;\n}\n.dsh-rewind-btn:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n color: var(--dsw-alias-label-secondary);\n}\n\n.dsh-rewind-popover {\n position: fixed;\n z-index: 1000;\n width: 288px;\n padding: 12px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 12px;\n background: var(--dsw-specific-menu, var(--dsw-alias-bg-layer-3));\n box-shadow: var(--dsw-shadow-lv3);\n font-size: 14px;\n line-height: 20px;\n color: var(--dsw-alias-label-primary);\n}\n.dsh-rewind-popover-title {\n font-size: 14px;\n font-weight: 600;\n line-height: 20px;\n}\n.dsh-rewind-popover-target {\n margin: 4px 0 10px;\n font-size: 12px;\n line-height: 16px;\n color: var(--dsw-alias-label-tertiary);\n word-break: break-all;\n}\n.dsh-rewind-popover-option {\n display: flex;\n flex-direction: column;\n gap: 2px;\n width: 100%;\n margin: 0 0 6px;\n padding: 8px 10px;\n border: 1px solid transparent;\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.dsh-rewind-popover-option:disabled {\n opacity: 0.5;\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-actions {\n display: flex;\n justify-content: flex-end;\n gap: 8px;\n}\n.dsh-rewind-popover-primary,\n.dsh-rewind-popover-ghost {\n padding: 5px 12px;\n border: none;\n border-radius: 8px;\n font: inherit;\n font-size: 13px;\n line-height: 18px;\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.5;\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\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: 1px solid var(--dsw-alias-border-l2);\n border-radius: 10px;\n background: var(--dsw-specific-menu, var(--dsw-alias-bg-layer-3));\n box-shadow: var(--dsw-shadow-lv3);\n font-size: 13px;\n line-height: 18px;\n color: var(--dsw-alias-label-primary);\n pointer-events: none;\n}\n\n/* ---- Snapshot-cleanup settings card (mirrors the harness PluginCard look) ---- */\n.dsh-rewind-cleanup-card {\n list-style: none;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 12px;\n background: var(--dsw-alias-bg-layer-3);\n transition: border-color .16s, background .16s;\n}\n.dsh-rewind-cleanup-card:hover {\n border-color: var(--dsw-alias-label-dimmed);\n}\n.dsh-rewind-cleanup-card-open {\n background: var(--dsw-alias-bg-layer-2);\n border-color: var(--dsw-alias-label-dimmed);\n}\n.dsh-rewind-cleanup-header {\n width: 100%;\n appearance: none;\n border: 0;\n background: none;\n font: inherit;\n color: inherit;\n text-align: left;\n cursor: pointer;\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 14px 16px;\n border-radius: 12px;\n}\n.dsh-rewind-cleanup-header:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: -2px;\n}\n.dsh-rewind-cleanup-head-text {\n flex: 1;\n min-width: 0;\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n.dsh-rewind-cleanup-name {\n font-size: 15px;\n font-weight: 600;\n line-height: 1.4;\n color: var(--dsw-alias-label-primary);\n}\n.dsh-rewind-cleanup-desc {\n font-size: 13px;\n line-height: 1.5;\n color: var(--dsw-alias-label-tertiary);\n}\n.dsh-rewind-cleanup-chevron {\n flex: none;\n color: var(--dsw-alias-label-tertiary);\n transition: transform .16s;\n}\n.dsh-rewind-cleanup-chevron-open {\n transform: rotate(180deg);\n}\n.dsh-rewind-cleanup-pending {\n flex: none;\n border-radius: 999px;\n padding: 1px 8px;\n font-size: 11px;\n line-height: 17px;\n font-weight: 500;\n white-space: nowrap;\n background: var(--dsw-alias-bg-module-platform);\n color: var(--dsw-alias-label-secondary);\n}\n.dsh-rewind-cleanup-body {\n border-top: 1px solid var(--dsw-alias-border-l2);\n margin: 0 16px;\n padding: 4px 0 8px;\n}\n.dsh-rewind-cleanup-readonly {\n margin: 12px 0 0;\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-label-tertiary);\n}\n.dsh-rewind-cleanup-permission {\n display: grid;\n gap: 6px;\n padding: 12px 0;\n}\n.dsh-rewind-cleanup-field {\n display: flex;\n flex-direction: column;\n gap: 6px;\n padding: 12px 0;\n}\n.dsh-rewind-cleanup-field + .dsh-rewind-cleanup-field {\n border-top: 1px solid var(--dsw-alias-border-l2);\n}\n.dsh-rewind-cleanup-head {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n.dsh-rewind-cleanup-label {\n flex: 1;\n min-width: 0;\n font-size: 13px;\n font-weight: 500;\n line-height: 1.5;\n color: var(--dsw-alias-label-primary);\n}\n.dsh-rewind-cleanup-hint {\n margin: 0;\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-label-tertiary);\n}\n.dsh-rewind-cleanup-error {\n margin: 0;\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-label-error);\n}\n/* Switch row: label left, role=switch button right, hint below (Subagent module). */\n.dsh-rewind-cleanup-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-cleanup-toggle-label {\n flex: 1;\n min-width: 0;\n}\n.dsh-rewind-cleanup-switch {\n box-sizing: border-box;\n position: relative;\n flex: 0 0 auto;\n width: 36px;\n height: 20px;\n padding: 2px;\n border: 0;\n border-radius: 10px;\n background: var(--dsw-alias-border-l3);\n cursor: pointer;\n}\n.dsh-rewind-cleanup-switch-on {\n background: var(--dsw-alias-brand-primary);\n}\n.dsh-rewind-cleanup-switch:disabled {\n cursor: default;\n opacity: 0.5;\n}\n.dsh-rewind-cleanup-switch:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 2px;\n}\n.dsh-rewind-cleanup-thumb {\n display: block;\n width: 16px;\n height: 16px;\n border-radius: 50%;\n corner-shape: round;\n background: var(--dsw-alias-label-primary-foreground);\n transition: transform 120ms ease;\n}\n.dsh-rewind-cleanup-switch-on .dsh-rewind-cleanup-thumb {\n transform: translateX(16px);\n}\n.dsh-rewind-cleanup-input {\n box-sizing: border-box;\n height: 34px;\n padding: 0 12px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 8px;\n background: var(--dsw-alias-bg-layer-3);\n font: inherit;\n font-size: 13px;\n line-height: 1.5;\n color: var(--dsw-alias-label-primary);\n}\n.dsh-rewind-cleanup-input:focus-visible {\n outline: none;\n border-color: var(--dsw-alias-brand-primary);\n}\n.dsh-rewind-cleanup-input:disabled {\n color: var(--dsw-alias-label-tertiary);\n cursor: default;\n}\n.dsh-rewind-cleanup-input-invalid {\n border-color: var(--dsw-alias-label-error);\n}\n.dsh-rewind-cleanup-footer {\n display: flex;\n align-items: center;\n justify-content: flex-end;\n gap: 8px;\n padding: 12px 0 4px;\n border-top: 1px solid var(--dsw-alias-border-l2);\n}\n.dsh-rewind-cleanup-failed {\n flex: 1;\n min-width: 0;\n margin: 0;\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-label-error);\n}\n.dsh-rewind-cleanup-discard,\n.dsh-rewind-cleanup-save {\n appearance: none;\n border: 1px solid transparent;\n border-radius: 8px;\n padding: 5px 14px;\n font: inherit;\n font-size: 13px;\n line-height: 1.5;\n cursor: pointer;\n}\n.dsh-rewind-cleanup-discard {\n border-color: var(--dsw-alias-border-l2);\n background: none;\n color: var(--dsw-alias-label-secondary);\n}\n.dsh-rewind-cleanup-discard:hover:not(:disabled) {\n color: var(--dsw-alias-label-primary);\n border-color: var(--dsw-alias-label-dimmed);\n}\n.dsh-rewind-cleanup-save {\n background: var(--dsw-alias-label-primary);\n color: var(--dsw-alias-bg-layer-3);\n}\n.dsh-rewind-cleanup-discard:disabled,\n.dsh-rewind-cleanup-save:disabled {\n opacity: 0.4;\n cursor: default;\n}\n.dsh-rewind-cleanup-discard:focus-visible,\n.dsh-rewind-cleanup-save:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n";
|
|
26
|
+
export declare const STYLE = "\n.dsh-rewind-btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 28px;\n height: 28px;\n padding: 6px;\n border: none;\n border-radius: 28px;\n background: transparent;\n color: var(--dsw-alias-label-tertiary);\n cursor: pointer;\n}\n.dsh-rewind-btn:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n color: var(--dsw-alias-label-secondary);\n}\n\n.dsh-rewind-popover {\n position: fixed;\n z-index: 1000;\n width: 288px;\n padding: 12px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 12px;\n background: var(--dsw-specific-menu, var(--dsw-alias-bg-layer-3));\n box-shadow: var(--dsw-shadow-lv3);\n font-size: 14px;\n line-height: 20px;\n color: var(--dsw-alias-label-primary);\n}\n.dsh-rewind-popover-title {\n font-size: 14px;\n font-weight: 600;\n line-height: 20px;\n}\n.dsh-rewind-popover-target {\n margin: 4px 0 10px;\n font-size: 12px;\n line-height: 16px;\n color: var(--dsw-alias-label-tertiary);\n word-break: break-all;\n}\n.dsh-rewind-popover-option {\n display: flex;\n flex-direction: column;\n gap: 2px;\n width: 100%;\n margin: 0 0 6px;\n padding: 8px 10px;\n border: 1px solid transparent;\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.dsh-rewind-popover-option:disabled {\n opacity: 0.5;\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-actions {\n display: flex;\n justify-content: flex-end;\n gap: 8px;\n}\n.dsh-rewind-popover-primary,\n.dsh-rewind-popover-ghost {\n padding: 5px 12px;\n border: none;\n border-radius: 8px;\n font: inherit;\n font-size: 13px;\n line-height: 18px;\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.5;\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\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: 1px solid var(--dsw-alias-border-l2);\n border-radius: 10px;\n background: var(--dsw-specific-menu, var(--dsw-alias-bg-layer-3));\n box-shadow: var(--dsw-shadow-lv3);\n font-size: 13px;\n line-height: 18px;\n color: var(--dsw-alias-label-primary);\n pointer-events: none;\n}\n\n/* ---- Snapshot-cleanup settings card (mirrors the harness PluginCard look) ---- */\n/* Dual-channel note: the harness card corner/border drifted across versions \u2014\n rc.2 uses border-radius 12px + 1px solid border-l2, while alpha uses 16px +\n 0.5px border-l4 (and adds corner-shape: round). We intentionally keep the\n rc.2 values here for now; if the alpha look is wanted, switch these on the\n host version like the other dual-channel surfaces (see the host-version probe\n in src/client/index.ts). */\n.dsh-rewind-cleanup-card {\n list-style: none;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 12px;\n background: var(--dsw-alias-bg-layer-3);\n transition: border-color .16s, background .16s;\n}\n.dsh-rewind-cleanup-card:hover {\n border-color: var(--dsw-alias-label-dimmed);\n}\n.dsh-rewind-cleanup-card-open {\n background: var(--dsw-alias-bg-layer-2);\n border-color: var(--dsw-alias-label-dimmed);\n}\n.dsh-rewind-cleanup-header {\n width: 100%;\n appearance: none;\n border: 0;\n background: none;\n font: inherit;\n color: inherit;\n text-align: left;\n cursor: pointer;\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 14px 16px;\n border-radius: 12px;\n}\n.dsh-rewind-cleanup-header:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: -2px;\n}\n.dsh-rewind-cleanup-head-text {\n flex: 1;\n min-width: 0;\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n.dsh-rewind-cleanup-name {\n font-size: 15px;\n font-weight: 600;\n line-height: 1.4;\n color: var(--dsw-alias-label-primary);\n}\n.dsh-rewind-cleanup-desc {\n font-size: 13px;\n line-height: 1.5;\n color: var(--dsw-alias-label-tertiary);\n}\n.dsh-rewind-cleanup-chevron {\n flex: none;\n color: var(--dsw-alias-label-tertiary);\n transition: transform .16s;\n}\n.dsh-rewind-cleanup-chevron-open {\n transform: rotate(180deg);\n}\n.dsh-rewind-cleanup-pending {\n flex: none;\n border-radius: 999px;\n padding: 1px 8px;\n font-size: 11px;\n line-height: 17px;\n font-weight: 500;\n white-space: nowrap;\n background: var(--dsw-alias-bg-module-platform);\n color: var(--dsw-alias-label-secondary);\n}\n.dsh-rewind-cleanup-body {\n border-top: 1px solid var(--dsw-alias-border-l2);\n margin: 0 16px;\n padding: 4px 0 8px;\n}\n.dsh-rewind-cleanup-readonly {\n margin: 12px 0 0;\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-label-tertiary);\n}\n.dsh-rewind-cleanup-permission {\n display: grid;\n gap: 6px;\n padding: 12px 0;\n}\n.dsh-rewind-cleanup-field {\n display: flex;\n flex-direction: column;\n gap: 6px;\n padding: 12px 0;\n}\n.dsh-rewind-cleanup-field + .dsh-rewind-cleanup-field {\n border-top: 1px solid var(--dsw-alias-border-l2);\n}\n.dsh-rewind-cleanup-head {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n.dsh-rewind-cleanup-label {\n flex: 1;\n min-width: 0;\n font-size: 13px;\n font-weight: 500;\n line-height: 1.5;\n color: var(--dsw-alias-label-primary);\n}\n.dsh-rewind-cleanup-hint {\n margin: 0;\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-label-tertiary);\n}\n.dsh-rewind-cleanup-error {\n margin: 0;\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-label-error);\n}\n/* Switch row: label left, role=switch button right, hint below (Subagent module). */\n.dsh-rewind-cleanup-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-cleanup-toggle-label {\n flex: 1;\n min-width: 0;\n}\n.dsh-rewind-cleanup-switch {\n box-sizing: border-box;\n position: relative;\n flex: 0 0 auto;\n width: 36px;\n height: 20px;\n padding: 2px;\n border: 0;\n border-radius: 10px;\n background: var(--dsw-alias-border-l3);\n cursor: pointer;\n}\n.dsh-rewind-cleanup-switch-on {\n background: var(--dsw-alias-brand-primary);\n}\n.dsh-rewind-cleanup-switch:disabled {\n cursor: default;\n opacity: 0.5;\n}\n.dsh-rewind-cleanup-switch:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 2px;\n}\n.dsh-rewind-cleanup-thumb {\n display: block;\n width: 16px;\n height: 16px;\n border-radius: 50%;\n corner-shape: round;\n background: var(--dsw-alias-label-primary-foreground);\n transition: transform 120ms ease;\n}\n.dsh-rewind-cleanup-switch-on .dsh-rewind-cleanup-thumb {\n transform: translateX(16px);\n}\n.dsh-rewind-cleanup-input {\n box-sizing: border-box;\n height: 34px;\n padding: 0 12px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 8px;\n background: var(--dsw-alias-bg-layer-3);\n font: inherit;\n font-size: 13px;\n line-height: 1.5;\n color: var(--dsw-alias-label-primary);\n}\n.dsh-rewind-cleanup-input:focus-visible {\n outline: none;\n border-color: var(--dsw-alias-brand-primary);\n}\n.dsh-rewind-cleanup-input:disabled {\n color: var(--dsw-alias-label-tertiary);\n cursor: default;\n}\n.dsh-rewind-cleanup-input-invalid {\n border-color: var(--dsw-alias-label-error);\n}\n.dsh-rewind-cleanup-footer {\n display: flex;\n align-items: center;\n justify-content: flex-end;\n gap: 8px;\n padding: 12px 0 4px;\n border-top: 1px solid var(--dsw-alias-border-l2);\n}\n.dsh-rewind-cleanup-failed {\n flex: 1;\n min-width: 0;\n margin: 0;\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-label-error);\n}\n.dsh-rewind-cleanup-discard,\n.dsh-rewind-cleanup-save {\n appearance: none;\n border: 1px solid transparent;\n border-radius: 8px;\n padding: 5px 14px;\n font: inherit;\n font-size: 13px;\n line-height: 1.5;\n cursor: pointer;\n}\n.dsh-rewind-cleanup-discard {\n border-color: var(--dsw-alias-border-l2);\n background: none;\n color: var(--dsw-alias-label-secondary);\n}\n.dsh-rewind-cleanup-discard:hover:not(:disabled) {\n color: var(--dsw-alias-label-primary);\n border-color: var(--dsw-alias-label-dimmed);\n}\n.dsh-rewind-cleanup-save {\n background: var(--dsw-alias-label-primary);\n color: var(--dsw-alias-bg-layer-3);\n}\n.dsh-rewind-cleanup-discard:disabled,\n.dsh-rewind-cleanup-save:disabled {\n opacity: 0.4;\n cursor: default;\n}\n.dsh-rewind-cleanup-discard:focus-visible,\n.dsh-rewind-cleanup-save:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-rewind-plugin",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.4",
|
|
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",
|