dsh-rewind-plugin 0.20.0-alpha.1 → 0.20.0-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +28 -31
- package/README.md +25 -26
- package/lib/client.js +35 -21
- package/lib/types/client/hidden.d.ts +19 -0
- package/lib/types/client/pending.d.ts +5 -2
- package/lib/types/client/portals.d.ts +3 -4
- package/package.json +5 -2
package/README.en.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# dsh-rewind
|
|
2
2
|
|
|
3
|
-
Conversation rewind for DeepSeek Harness: **rewind the conversation to any earlier user message in one click, in the same window
|
|
4
|
-
|
|
3
|
+
Conversation rewind for DeepSeek Harness: **rewind the conversation to any earlier user message in one click, in the same window**.<br />
|
|
4
|
+
No new branch, no window switch, with optional workspace-file restore — the full Claude Code `/rewind` semantics.
|
|
5
5
|
|
|
6
6
|
English | [中文](README.md)
|
|
7
7
|
|
|
@@ -9,7 +9,7 @@ English | [中文](README.md)
|
|
|
9
9
|
[](https://www.npmjs.com/package/dsh-rewind-plugin)
|
|
10
10
|
[](https://github.com/SiriLee/dsh-rewind/actions/workflows/ci.yml)
|
|
11
11
|
|
|
12
|
-

|
|
13
13
|
|
|
14
14
|
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.
|
|
15
15
|
|
|
@@ -43,15 +43,11 @@ Check your local DSH version, then find the matching plugin version in
|
|
|
43
43
|
dsh plugin --profile <name> add dsh-rewind-plugin@<version>
|
|
44
44
|
```
|
|
45
45
|
|
|
46
|
-
**Graphical interface**: the **Plugins** page in the sidebar → **Add plugin** → enter `dsh-rewind-plugin@<version>` → install → restart DSH
|
|
46
|
+
**Graphical interface**: the **Plugins** page in the sidebar → **Add plugin** → enter `dsh-rewind-plugin@<version>` → install → restart DSH.
|
|
47
47
|
|
|
48
48
|
<img src="assets/screenshots/add-plugin.png" width="600" alt="Add plugin dialog: enter dsh-rewind-plugin@version in the field"><br>
|
|
49
49
|
<sub>The screenshot is an example only — use the version that matches your DSH version.</sub>
|
|
50
50
|
|
|
51
|
-
**Updating**: the command line overwrites the installed version directly; the
|
|
52
|
-
graphical interface rejects a re-install, so uninstall the old version first,
|
|
53
|
-
then install the new one.
|
|
54
|
-
|
|
55
51
|
> [!WARNING]
|
|
56
52
|
> The npm name `dsh-rewind` belongs to another author's package — install with `dsh-rewind-plugin`.
|
|
57
53
|
|
|
@@ -66,8 +62,8 @@ then install the new one.
|
|
|
66
62
|
<details>
|
|
67
63
|
<summary><b>Edge notes</b></summary>
|
|
68
64
|
|
|
69
|
-
-
|
|
70
|
-
- A rewind
|
|
65
|
+
- **The withdrawn content is preserved** — it stays in the session log and can be inspected in the **Trajectory** panel; you can also disable the plugin to show it again (display only).
|
|
66
|
+
- **A rewind cannot be undone in place** — to carry the content forward, first disable the plugin so the withdrawn content shows again, then click **Branch into a new conversation** on the message just before the rewind; the new conversation's context will include it.
|
|
71
67
|
- **Interruptions rewind too** — a `steering` message the model hasn't read yet is also a valid rewind target, and withdrawing it does not interrupt the current run.
|
|
72
68
|
- **Rewinding a read message interrupts the running turn** — to ensure the rewind runs safely.
|
|
73
69
|
|
|
@@ -79,17 +75,16 @@ then install the new one.
|
|
|
79
75
|
switched off in this plugin's configuration card. When off, no new snapshots
|
|
80
76
|
are created; file changes made while off cannot be restored. Snapshots (the
|
|
81
77
|
before-write backups) are stored under `<dsh home>/rewind-snapshots/`
|
|
82
|
-
(the default is `~/.dsh/rewind-snapshots/`).
|
|
83
|
-
deduplicates snapshots by content and keeps the newest 100 anchor groups.
|
|
78
|
+
(the default is `~/.dsh/rewind-snapshots/`).
|
|
84
79
|
**Deleting that directory manually** only clears the file backups (chat rewinds are
|
|
85
|
-
unaffected); the plugin can rebuild
|
|
80
|
+
unaffected); the plugin can rebuild that directory automatically.
|
|
86
81
|
|
|
87
|
-
A **
|
|
82
|
+
A **snapshot auto-cleanup** (off by default) removes the snapshot directories of
|
|
88
83
|
long-inactive sessions, leaving the active session and chat log untouched. Configure
|
|
89
84
|
and review it on the **Plugins** page, in this plugin's configuration card (the
|
|
90
85
|
auto-cleanup switch and the idle-day cutoff), or use the
|
|
91
86
|
`/snapshot-auto-cleanup` command to **view, configure, and run** it. See:
|
|
92
|
-
[Snapshot cleanup](docs/snapshot-auto-cleanup.md).
|
|
87
|
+
[Snapshot auto-cleanup](docs/snapshot-auto-cleanup.md).
|
|
93
88
|
|
|
94
89
|
<img src="assets/screenshots/plugin-setting.png" alt="Plugin configuration card: snapshot backups and auto cleanup" width="600">
|
|
95
90
|
|
|
@@ -104,19 +99,19 @@ rm -rf <dsh home>/rewind-snapshots
|
|
|
104
99
|
rm <dsh home>/snapshot-cleanup-last-sweep.json
|
|
105
100
|
```
|
|
106
101
|
|
|
107
|
-
The plugin's
|
|
108
|
-
|
|
102
|
+
The plugin's configuration entries are stored in the current Profile's config
|
|
103
|
+
file `cordis.patch.yml`. To clean up completely, delete the corresponding entries
|
|
104
|
+
manually.
|
|
109
105
|
|
|
110
|
-
##
|
|
106
|
+
## What this plugin changes
|
|
111
107
|
|
|
112
|
-
Compared with the
|
|
108
|
+
Compared with the default behavior:
|
|
113
109
|
|
|
114
|
-
| Dimension |
|
|
110
|
+
| Dimension | Default behavior | This plugin |
|
|
115
111
|
| --- | --- | --- |
|
|
116
|
-
| Conversation rewind |
|
|
117
|
-
| File restore |
|
|
118
|
-
|
|
|
119
|
-
| Storage footprint | Whole-tree snapshots take space | **Lightweight** — nothing is stored unless it changed, and only files touched by write tools are tracked |
|
|
112
|
+
| Conversation rewind | Branch into a new conversation | **In-place rewind** — no new session branch, no window switch, no leftover session copies, and rewinding is more convenient |
|
|
113
|
+
| File restore | Manual restore / Git management | **One-click restore** — lightweight before-write backups; optionally restore files together with the conversation, fitting agent workflows |
|
|
114
|
+
| Restore dependencies | Often needs a Git repository or a full snapshot engine | **None** — no Git repository and no code project required; works in any directory |
|
|
120
115
|
|
|
121
116
|
## How it works
|
|
122
117
|
|
|
@@ -131,7 +126,7 @@ The whole design rests on two principles, simple but deliberate: **the conversat
|
|
|
131
126
|
- **The replacement is imperceptible** — the model ignores the marker, with no effect (verified empirically). Together with the plugin's UI handling, what you and the model see is exactly how the conversation looked at the target;
|
|
132
127
|
- **Content is preserved** — because this is "masking, not deleting", the withdrawn content stays in the log — auditable, traceable, and in principle manually recoverable.
|
|
133
128
|
|
|
134
|
-
> **Design highlight**: the entire conversation rewind is **a single append**. It's deterministic, auditable, and — because the log was never broken — a "clean" time-travel. Minimal action, complete semantics.
|
|
129
|
+
> **Design highlight**: the entire conversation rewind is **a single append**. It's deterministic, auditable, and — because the log was never broken — a "clean" time-travel. Minimal action, complete semantics.
|
|
135
130
|
|
|
136
131
|
### 2. File restore: lightweight checkpointing, "before-write backup"
|
|
137
132
|
|
|
@@ -155,8 +150,9 @@ This plugin deliberately stays lightweight and focused on one thing — "convers
|
|
|
155
150
|
|
|
156
151
|
## Compatibility
|
|
157
152
|
|
|
158
|
-
- Node.js
|
|
159
|
-
-
|
|
153
|
+
- Node.js `>=24.9.0`.
|
|
154
|
+
- Supported DSH versions are declared by `package.json` `engines.dsh`.
|
|
155
|
+
- Compatibility definition, verification method, and version alignment: see [docs/compat/audit.md](docs/compat/audit.md).
|
|
160
156
|
|
|
161
157
|
> [!WARNING]
|
|
162
158
|
> This project and DeepSeek Harness are both in developer preview. Pin exact
|
|
@@ -164,16 +160,17 @@ This plugin deliberately stays lightweight and focused on one thing — "convers
|
|
|
164
160
|
|
|
165
161
|
## Known issues
|
|
166
162
|
|
|
167
|
-
1. **Exported logs are complete** —
|
|
163
|
+
1. **Exported logs are complete** — `/export` exports the whole session log (raw JSONL), **withdrawn content included**; this plugin cannot alter exports.
|
|
168
164
|
2. **Lightweight file rewind has a cost** — in specific cases not all changes can be rewound. Consistent with Claude Code. See: [File-rewind tracking boundary](docs/compat/tracking-boundary.md).
|
|
169
|
-
3. **
|
|
165
|
+
3. **Turn navigation keeps rewound turns** — the right-side turn navigation keeps an "unloaded" tick for each rewound turn, and hovering previews the withdrawn content. Only a display difference; no functional impact.
|
|
166
|
+
4. **A far rewind may show a blank transcript** — when the target predates the loaded history, the conversation area goes blank; click **Load earlier** to page back until the history appears.
|
|
170
167
|
|
|
171
168
|
> [!NOTE]
|
|
172
169
|
> Browser diagnostics are available; see [Browser diagnostics](docs/compat/diagnostics.md).
|
|
173
170
|
|
|
174
171
|
## Security
|
|
175
172
|
|
|
176
|
-
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
|
|
173
|
+
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 snapshot 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).
|
|
177
174
|
|
|
178
175
|
## Development
|
|
179
176
|
|
|
@@ -186,7 +183,7 @@ npm run build # esbuild: lib/index.js (host ESM) + lib/client.js (loade
|
|
|
186
183
|
node scripts/verify-host.mjs # end-to-end verification of the built artifact
|
|
187
184
|
```
|
|
188
185
|
|
|
189
|
-
`
|
|
186
|
+
`prepack` runs the full build, so `npm pack` / `npm publish` always ship a complete `lib/`. Install from npm or a packed tarball — a git checkout has no built `lib/`.
|
|
190
187
|
|
|
191
188
|
Maintainers: the module map and harness interface reference live in [docs/harness-reference.md](docs/harness-reference.md).
|
|
192
189
|
|
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# dsh-rewind
|
|
2
2
|
|
|
3
|
-
DeepSeek Harness
|
|
4
|
-
|
|
3
|
+
DeepSeek Harness 插件:**一键就地回退对话到任意更早的用户消息**。<br />
|
|
4
|
+
同窗口内完成,不新建分支、不换窗口,可一并还原工作区文件——即完整的 Claude Code `/rewind` 语义。
|
|
5
5
|
|
|
6
6
|
[English](README.en.md) | 中文
|
|
7
7
|
|
|
@@ -9,7 +9,7 @@ DeepSeek Harness 插件:**一键就地回退对话到任意更早的用户消
|
|
|
9
9
|
[](https://www.npmjs.com/package/dsh-rewind-plugin)
|
|
10
10
|
[](https://github.com/SiriLee/dsh-rewind/actions/workflows/ci.yml)
|
|
11
11
|
|
|
12
|
-

|
|
13
13
|
|
|
14
14
|
刻意聚焦、保持极简,只做一件事:**就地回退到任意远的用户消息**,还能**顺手还原改过的文件**。
|
|
15
15
|
|
|
@@ -42,13 +42,11 @@ DeepSeek Harness 插件:**一键就地回退对话到任意更早的用户消
|
|
|
42
42
|
dsh plugin --profile <name> add dsh-rewind-plugin@<版本>
|
|
43
43
|
```
|
|
44
44
|
|
|
45
|
-
**图形界面**:侧栏「插件」页 →「添加插件」→ 填 `dsh-rewind-plugin@<版本>` → 安装 → 重启 DSH
|
|
45
|
+
**图形界面**:侧栏「插件」页 →「添加插件」→ 填 `dsh-rewind-plugin@<版本>` → 安装 → 重启 DSH。
|
|
46
46
|
|
|
47
47
|
<img src="assets/screenshots/add-plugin.png" width="600" alt="「添加插件」弹窗:在输入框填入 dsh-rewind-plugin@版本号"><br>
|
|
48
48
|
<sub>图片仅为示例,请以实际适配版本号为准。</sub>
|
|
49
49
|
|
|
50
|
-
**更新**:命令行可直接覆盖安装;图形界面会拒绝重复安装,需先卸载旧版本,再装新版本。
|
|
51
|
-
|
|
52
50
|
> [!WARNING]
|
|
53
51
|
> npm 上的 `dsh-rewind` 属于其他作者,请用 `dsh-rewind-plugin` 安装。
|
|
54
52
|
|
|
@@ -63,8 +61,8 @@ dsh plugin --profile <name> add dsh-rewind-plugin@<版本>
|
|
|
63
61
|
<details>
|
|
64
62
|
<summary><b>边界说明</b></summary>
|
|
65
63
|
|
|
66
|
-
-
|
|
67
|
-
-
|
|
64
|
+
- **回退内容完整保存**——被撤回的内容仍在日志中,可在「轨迹」面板查看;也可禁用插件让它们重新显示(仅界面)。
|
|
65
|
+
- **回退无法原地撤销**——但可以先禁用插件让被撤回的内容重新显示,再在回退前的位置上点「在新对话中分支」,新会话的上下文即可包含这些内容。
|
|
68
66
|
- **插话也能回退**——模型尚未读取的 `steering` 插话消息,同样可作为回退目标,不会打断当前生成。
|
|
69
67
|
- **回退已读消息会打断当前正在运行的回合**——确保回退的安全执行。
|
|
70
68
|
|
|
@@ -72,9 +70,9 @@ dsh plugin --profile <name> add dsh-rewind-plugin@<版本>
|
|
|
72
70
|
|
|
73
71
|
## 存储管理
|
|
74
72
|
|
|
75
|
-
**快照备份**(默认开启):用于文件还原;可在本插件的配置卡片中关闭。关闭后不再创建新的快照,关闭期间的文件改动无法还原。快照存储于 `<dsh home>/rewind-snapshots/`(默认 `~/.dsh/rewind-snapshots
|
|
73
|
+
**快照备份**(默认开启):用于文件还原;可在本插件的配置卡片中关闭。关闭后不再创建新的快照,关闭期间的文件改动无法还原。快照存储于 `<dsh home>/rewind-snapshots/`(默认 `~/.dsh/rewind-snapshots/`)。手动删除该目录仅清除文件备份,对话回退不受影响,插件可自动重建。
|
|
76
74
|
|
|
77
|
-
|
|
75
|
+
**快照自动清理**(默认关闭):把长期不活跃的会话快照整目录移除,不影响活动会话与对话日志。可在配置卡片查看与配置(自动清理开关、失活天数),也可用 `/snapshot-auto-cleanup` 命令查看、设置和运行。详见:[快照自动清理](docs/snapshot-auto-cleanup.zh.md)。
|
|
78
76
|
|
|
79
77
|
<img src="assets/screenshots/plugin-setting.png" alt="插件配置卡片:快照备份与自动清理" width="600">
|
|
80
78
|
|
|
@@ -89,18 +87,17 @@ rm -rf <dsh home>/rewind-snapshots
|
|
|
89
87
|
rm <dsh home>/snapshot-cleanup-last-sweep.json
|
|
90
88
|
```
|
|
91
89
|
|
|
92
|
-
|
|
90
|
+
本插件的配置项保存在当前 Profile 的配置文件 `cordis.patch.yml` 中。如需彻底清理,可手动删除对应条目。
|
|
93
91
|
|
|
94
|
-
##
|
|
92
|
+
## 本插件的作用
|
|
95
93
|
|
|
96
|
-
|
|
94
|
+
相较于默认行为,本插件带来的变化:
|
|
97
95
|
|
|
98
|
-
| 维度 |
|
|
96
|
+
| 维度 | 默认行为 | 本插件 |
|
|
99
97
|
| --- | --- | --- |
|
|
100
|
-
| 对话回退 |
|
|
101
|
-
| 文件还原 |
|
|
102
|
-
|
|
|
103
|
-
| 存储开销 | 整树快照占空间大 | **轻量**——不变不存,且只追踪写类工具改动过的文件 |
|
|
98
|
+
| 对话回退 | 新建分支对话 | **就地回退**——不用新建会话分支、不切窗口,也不产生残留会话副本,回退更便捷 |
|
|
99
|
+
| 文件还原 | 手动还原 / Git 管理 | **一键还原**——写前轻量备份,回退对话时可选一并还原文件,贴合 Agent 工作流 |
|
|
100
|
+
| 还原依赖 | 常依赖 Git 仓库或完整快照引擎 | **无依赖**——不依赖 Git 仓库,也不要求是代码项目,普通目录即可用 |
|
|
104
101
|
|
|
105
102
|
## 原理
|
|
106
103
|
|
|
@@ -115,7 +112,7 @@ rm <dsh home>/snapshot-cleanup-last-sweep.json
|
|
|
115
112
|
- **替换内容无感**——模型对标记忽略、无感(实测验证)。配合插件对界面显示的处理,模型和你看到的对话就是目标消息当时的样子;
|
|
116
113
|
- **记录完整保留**——因为是「遮蔽」而非「删除」,被撤回的内容完整留在日志里,可审计、可追溯,原则上也能手动恢复。
|
|
117
114
|
|
|
118
|
-
>
|
|
115
|
+
> **设计点睛**:整个对话回退就是**一条**追加。它确定、可审计,且因为日志从未被破坏,回溯是「干净的」——用最小的动作,实现最完整的语义。
|
|
119
116
|
|
|
120
117
|
### 2. 文件还原:轻量检查点,「写前备份」
|
|
121
118
|
|
|
@@ -131,7 +128,7 @@ rm <dsh home>/snapshot-cleanup-last-sweep.json
|
|
|
131
128
|
|
|
132
129
|
## 明确不做的事
|
|
133
130
|
|
|
134
|
-
|
|
131
|
+
本插件刻意保持轻量、聚焦「对话回退」这一件事,以下场景**不属于它的职责**:
|
|
135
132
|
|
|
136
133
|
- **整树 / Git 级快照**——只跟踪写类工具编辑 + 已跟踪文件的外部改动,从未被工具碰过的文件不还原。需要工作树级的完整快照回退时,请交给更专业的快照工具(git)。
|
|
137
134
|
- **子代理的编辑**——不追踪,子代理会话内也不提供回退(同 Claude Code):子代理运行在自己的会话里,其备份无法由父会话的回退还原,因此也不会为子会话保留备份。
|
|
@@ -139,8 +136,9 @@ rm <dsh home>/snapshot-cleanup-last-sweep.json
|
|
|
139
136
|
|
|
140
137
|
## 兼容性
|
|
141
138
|
|
|
142
|
-
- Node.js
|
|
143
|
-
-
|
|
139
|
+
- Node.js `>=24.9.0`。
|
|
140
|
+
- 支持的 DSH 版本由 `package.json` 的 `engines.dsh` 声明。
|
|
141
|
+
- 兼容性定义、验证方法与版本对齐详见 [docs/compat/audit.md](docs/compat/audit.md)。
|
|
144
142
|
|
|
145
143
|
> [!WARNING]
|
|
146
144
|
> 本项目与 DeepSeek Harness 均处于开发者预览阶段。可复现环境请 pin 精确版本,
|
|
@@ -148,16 +146,17 @@ rm <dsh home>/snapshot-cleanup-last-sweep.json
|
|
|
148
146
|
|
|
149
147
|
## 已知问题
|
|
150
148
|
|
|
151
|
-
1.
|
|
149
|
+
1. **导出的日志是完整内容**——`/export` 导出整个会话日志(原始 JSONL),包含**已撤回的内容**;本插件无法改动导出。
|
|
152
150
|
2. **轻量文件回退存在代价**——特定情况可能无法回退所有修改。行为与 Claude Code 一致。详见:[文件回退的追踪边界](docs/compat/tracking-boundary.zh.md)。
|
|
153
|
-
3.
|
|
151
|
+
3. **轮次导航保留已回退轮次**——右侧轮次导航为已回退轮次保留「未加载」刻度,悬浮可预览已撤回内容。仅显示差异,无功能影响。
|
|
152
|
+
4. **较远回退可能显示空白**——回退目标早于已加载的历史时,对话区会显示空白;需点击「加载更早」往前翻,才能看到历史对话。
|
|
154
153
|
|
|
155
154
|
> [!NOTE]
|
|
156
155
|
> 本插件提供浏览器端诊断输出;详见 [浏览器诊断](docs/compat/diagnostics.zh.md)。
|
|
157
156
|
|
|
158
157
|
## 安全
|
|
159
158
|
|
|
160
|
-
本插件只向会话日志追加回退标记事件,从不删除或改写已记录的历史。工作区文件仅在「回退对话和文件」时被改写,备份存储于 `<dsh home>/rewind-snapshots/`;还原以备份为唯一来源。不触碰你的 git
|
|
159
|
+
本插件只向会话日志追加回退标记事件,从不删除或改写已记录的历史。工作区文件仅在「回退对话和文件」时被改写,备份存储于 `<dsh home>/rewind-snapshots/`;还原以备份为唯一来源。不触碰你的 git 仓库,无网络请求,不访问任何凭据。对**长期不活跃**的会话,另有默认关闭的快照自动清理可整目录移除其快照,不影响活动会话与对话日志。完整安全模型:[SECURITY.md](SECURITY.md)。
|
|
161
160
|
|
|
162
161
|
## 开发
|
|
163
162
|
|
|
@@ -170,7 +169,7 @@ npm run build # esbuild:lib/index.js(host ESM)+ lib/client.js(l
|
|
|
170
169
|
node scripts/verify-host.mjs # 端到端验证构建产物
|
|
171
170
|
```
|
|
172
171
|
|
|
173
|
-
`
|
|
172
|
+
`prepack` 在打包前执行完整构建,所以 `npm pack` / `npm publish` 总会产出完整的 `lib/`。请从 npm 或打好的 tarball 安装——git 检出的源码里没有构建好的 `lib/`。
|
|
174
173
|
|
|
175
174
|
维护者:模块地图与 harness 接口参考见 [docs/harness-reference.md](docs/harness-reference.md)
|
|
176
175
|
|
package/lib/client.js
CHANGED
|
@@ -108,27 +108,30 @@ function messageTextAt(chat, seq) {
|
|
|
108
108
|
}
|
|
109
109
|
return void 0;
|
|
110
110
|
}
|
|
111
|
+
function imageRefsOf(content) {
|
|
112
|
+
const images = [];
|
|
113
|
+
for (const block of content ?? []) {
|
|
114
|
+
if (block.type !== "image") continue;
|
|
115
|
+
const attachment = block.attachment;
|
|
116
|
+
if (typeof attachment?.attachmentId !== "string" || typeof attachment.mediaType !== "string") continue;
|
|
117
|
+
images.push({
|
|
118
|
+
attachmentId: attachment.attachmentId,
|
|
119
|
+
mediaType: attachment.mediaType,
|
|
120
|
+
bytes: typeof attachment.bytes === "number" ? attachment.bytes : void 0,
|
|
121
|
+
width: typeof attachment.width === "number" ? attachment.width : void 0,
|
|
122
|
+
height: typeof attachment.height === "number" ? attachment.height : void 0,
|
|
123
|
+
name: typeof attachment.name === "string" ? attachment.name : void 0
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
return images;
|
|
127
|
+
}
|
|
111
128
|
function messageAttachmentsAt(chat, seq) {
|
|
112
129
|
if (chat === void 0) return void 0;
|
|
113
130
|
for (const node of chatNodesOf(chat)) {
|
|
114
131
|
if (node.kind !== "user" && node.kind !== "steering") continue;
|
|
115
132
|
const data = node.data;
|
|
116
133
|
if (data.seq !== seq) continue;
|
|
117
|
-
|
|
118
|
-
for (const block of data.content ?? []) {
|
|
119
|
-
if (block.type !== "image") continue;
|
|
120
|
-
const attachment = block.attachment;
|
|
121
|
-
if (typeof attachment?.attachmentId !== "string" || typeof attachment.mediaType !== "string") continue;
|
|
122
|
-
images.push({
|
|
123
|
-
attachmentId: attachment.attachmentId,
|
|
124
|
-
mediaType: attachment.mediaType,
|
|
125
|
-
bytes: typeof attachment.bytes === "number" ? attachment.bytes : void 0,
|
|
126
|
-
width: typeof attachment.width === "number" ? attachment.width : void 0,
|
|
127
|
-
height: typeof attachment.height === "number" ? attachment.height : void 0,
|
|
128
|
-
name: typeof attachment.name === "string" ? attachment.name : void 0
|
|
129
|
-
});
|
|
130
|
-
}
|
|
131
|
-
return images;
|
|
134
|
+
return imageRefsOf(data.content);
|
|
132
135
|
}
|
|
133
136
|
return void 0;
|
|
134
137
|
}
|
|
@@ -1089,6 +1092,7 @@ function steeringItemsOf(nextStep) {
|
|
|
1089
1092
|
id: item.id,
|
|
1090
1093
|
text,
|
|
1091
1094
|
attachments: attachmentsCountOf(item.content),
|
|
1095
|
+
images: imageRefsOf(item.content),
|
|
1092
1096
|
preview: previewOf(text)
|
|
1093
1097
|
};
|
|
1094
1098
|
});
|
|
@@ -1402,6 +1406,7 @@ function RewindPortals({ sessionId, sessionOf, chatOf, isMainViewSession, watchC
|
|
|
1402
1406
|
sessionId,
|
|
1403
1407
|
sessionOf,
|
|
1404
1408
|
setComposerText,
|
|
1409
|
+
restoreImages,
|
|
1405
1410
|
t
|
|
1406
1411
|
},
|
|
1407
1412
|
target.key
|
|
@@ -1463,8 +1468,9 @@ function composerText() {
|
|
|
1463
1468
|
if (surface === null) return "";
|
|
1464
1469
|
return surface.textContent ?? "";
|
|
1465
1470
|
}
|
|
1466
|
-
async function retractPending(session, itemId, text, setComposerText) {
|
|
1471
|
+
async function retractPending(session, itemId, text, setComposerText, restoreImages) {
|
|
1467
1472
|
const steering = steeringItemsOf(inboxOf(session)?.["next-step"]);
|
|
1473
|
+
const images = steering.find((item) => item.id === itemId)?.images ?? [];
|
|
1468
1474
|
let removedTarget = false;
|
|
1469
1475
|
for (const id of retractSpan(steering, itemId)) {
|
|
1470
1476
|
let accepted = false;
|
|
@@ -1478,11 +1484,19 @@ async function retractPending(session, itemId, text, setComposerText) {
|
|
|
1478
1484
|
if (!accepted) break;
|
|
1479
1485
|
if (id === itemId) removedTarget = true;
|
|
1480
1486
|
}
|
|
1481
|
-
if (removedTarget
|
|
1487
|
+
if (!removedTarget) return;
|
|
1488
|
+
if (images.length > 0 && restoreImages !== void 0) {
|
|
1489
|
+
try {
|
|
1490
|
+
await restoreImages(session.sessionId, images);
|
|
1491
|
+
} catch (error) {
|
|
1492
|
+
rewindLog.warn("retract", `image restore for ${itemId} threw`, error);
|
|
1493
|
+
}
|
|
1494
|
+
}
|
|
1495
|
+
if (text !== null && text !== "" && composerText().trim() === "") {
|
|
1482
1496
|
setComposerText(session.sessionId, text);
|
|
1483
1497
|
}
|
|
1484
1498
|
}
|
|
1485
|
-
function RetractButton({ target, sessionId, sessionOf, setComposerText, t }) {
|
|
1499
|
+
function RetractButton({ target, sessionId, sessionOf, setComposerText, restoreImages, t }) {
|
|
1486
1500
|
const onClick = (event) => {
|
|
1487
1501
|
event.stopPropagation();
|
|
1488
1502
|
const session = sessionOf(sessionId);
|
|
@@ -1494,7 +1508,7 @@ function RetractButton({ target, sessionId, sessionOf, setComposerText, t }) {
|
|
|
1494
1508
|
t,
|
|
1495
1509
|
retract: { itemId: target.itemId, text: target.text },
|
|
1496
1510
|
onRetract: () => {
|
|
1497
|
-
void retractPending(session, target.itemId, target.text, setComposerText);
|
|
1511
|
+
void retractPending(session, target.itemId, target.text, setComposerText, restoreImages);
|
|
1498
1512
|
}
|
|
1499
1513
|
});
|
|
1500
1514
|
};
|
|
@@ -1576,9 +1590,9 @@ async function candidatesOverRpc(connection, sessionId) {
|
|
|
1576
1590
|
}
|
|
1577
1591
|
|
|
1578
1592
|
// src/client/build-info.ts
|
|
1579
|
-
var PLUGIN_VERSION = true ? "0.20.0-alpha.
|
|
1593
|
+
var PLUGIN_VERSION = true ? "0.20.0-alpha.2" : "dev";
|
|
1580
1594
|
var PLUGIN_PACKAGE = "dsh-rewind-plugin";
|
|
1581
|
-
var BUILD_HASH = true ? "
|
|
1595
|
+
var BUILD_HASH = true ? "46008907" : "dev";
|
|
1582
1596
|
|
|
1583
1597
|
// src/client/locales.ts
|
|
1584
1598
|
var en = {
|
|
@@ -103,6 +103,25 @@ export interface MessageImageRef {
|
|
|
103
103
|
readonly height?: number;
|
|
104
104
|
readonly name?: string;
|
|
105
105
|
}
|
|
106
|
+
/** One wire content block that may carry a durable image reference. */
|
|
107
|
+
export interface ImageBlockLike {
|
|
108
|
+
readonly type?: string;
|
|
109
|
+
readonly attachment?: {
|
|
110
|
+
readonly attachmentId?: unknown;
|
|
111
|
+
readonly mediaType?: unknown;
|
|
112
|
+
readonly bytes?: unknown;
|
|
113
|
+
readonly width?: unknown;
|
|
114
|
+
readonly height?: unknown;
|
|
115
|
+
readonly name?: unknown;
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Image references carried by a message's content blocks, in order.
|
|
120
|
+
* Malformed and non-image blocks are skipped; `file` blocks are not restorable.
|
|
121
|
+
* @param content - one message's content blocks.
|
|
122
|
+
* @returns the image references, in message order.
|
|
123
|
+
*/
|
|
124
|
+
export declare function imageRefsOf(content: readonly ImageBlockLike[] | undefined): readonly MessageImageRef[];
|
|
106
125
|
/**
|
|
107
126
|
* The durable image references of the human message at `seq` in the chat
|
|
108
127
|
* snapshot, in message order. The composer-refill counterpart of
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
*
|
|
18
18
|
* @module dsh-rewind/client/pending
|
|
19
19
|
*/
|
|
20
|
+
import { type ImageBlockLike, type MessageImageRef } from './hidden.ts';
|
|
20
21
|
/** One rendered pending-steering bubble row (only the fields matching reads). */
|
|
21
22
|
export interface PendingRow {
|
|
22
23
|
/** The bubble's message text, excluding the actions container (see module doc). */
|
|
@@ -24,8 +25,8 @@ export interface PendingRow {
|
|
|
24
25
|
/** Message-attachment blocks the row renders; compared against the item's count. */
|
|
25
26
|
readonly attachments: number;
|
|
26
27
|
}
|
|
27
|
-
/** One wire content block of an inbox row
|
|
28
|
-
export interface InboxBlockLike {
|
|
28
|
+
/** One wire content block of an inbox row. */
|
|
29
|
+
export interface InboxBlockLike extends ImageBlockLike {
|
|
29
30
|
/** Wire block type (`text` | `image` | `file` | …). */
|
|
30
31
|
readonly type: string;
|
|
31
32
|
/** The block's text, on a `text` block. */
|
|
@@ -59,6 +60,8 @@ export interface PendingSteeringItem {
|
|
|
59
60
|
readonly text: string | null;
|
|
60
61
|
/** Non-text blocks (image/file/…) the message carries, in prompt order. */
|
|
61
62
|
readonly attachments: number;
|
|
63
|
+
/** Image references the message carries, in prompt order. */
|
|
64
|
+
readonly images: readonly MessageImageRef[];
|
|
62
65
|
/** Space-collapsed preview with image/file blocks excluded (the harness QueueDock rule). */
|
|
63
66
|
readonly preview: string;
|
|
64
67
|
}
|
|
@@ -280,9 +280,8 @@ export declare function composerText(): string;
|
|
|
280
280
|
*
|
|
281
281
|
* 1. Remove the target and its future (steering only; queued stays) through
|
|
282
282
|
* the session's own `updateQueue` channel, oldest first.
|
|
283
|
-
* 2. Put the target's
|
|
284
|
-
*
|
|
285
|
-
* guard, so a draft the user is typing is never clobbered).
|
|
283
|
+
* 2. Put the target's own images back, then its text when the composer is
|
|
284
|
+
* empty (the empty-composer guard, so a draft is never clobbered).
|
|
286
285
|
*
|
|
287
286
|
* The realistic failure is `queue-item-not-found`: a step boundary claimed
|
|
288
287
|
* the whole next-step batch a moment ago, so the message is now durable and
|
|
@@ -290,7 +289,7 @@ export declare function composerText(): string;
|
|
|
290
289
|
* removed then, and the composer is NOT refilled — refilling would invite a
|
|
291
290
|
* duplicate send of a message the model has already received.
|
|
292
291
|
*/
|
|
293
|
-
export declare function retractPending(session: SessionFace, itemId: string, text: string | null, setComposerText: (sessionId: string, text: string) => boolean): Promise<void>;
|
|
292
|
+
export declare function retractPending(session: SessionFace, itemId: string, text: string | null, setComposerText: (sessionId: string, text: string) => boolean, restoreImages?: (sessionId: string, images: readonly MessageImageRef[]) => Promise<void>): Promise<void>;
|
|
294
293
|
/**
|
|
295
294
|
* Build the slot-entry component for the plugin apply(): a tiny bridge that
|
|
296
295
|
* injects the apply-time capabilities (session resolution, locale, rewind
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-rewind-plugin",
|
|
3
|
-
"version": "0.20.0-alpha.
|
|
3
|
+
"version": "0.20.0-alpha.2",
|
|
4
4
|
"description": "In-window conversation rewind with workspace file restore",
|
|
5
5
|
"icon": "./assets/icon.svg",
|
|
6
6
|
"keywords": [
|
|
@@ -17,6 +17,9 @@
|
|
|
17
17
|
"url": "git+https://github.com/SiriLee/dsh-rewind.git"
|
|
18
18
|
},
|
|
19
19
|
"homepage": "https://github.com/SiriLee/dsh-rewind#readme",
|
|
20
|
+
"bugs": {
|
|
21
|
+
"url": "https://github.com/SiriLee/dsh-rewind/issues"
|
|
22
|
+
},
|
|
20
23
|
"license": "MIT",
|
|
21
24
|
"engines": {
|
|
22
25
|
"node": ">=24.9.0",
|
|
@@ -70,7 +73,7 @@
|
|
|
70
73
|
"verify:host": "node scripts/verify-host.mjs",
|
|
71
74
|
"verify:previews": "node scripts/verify-preview-assets.mjs",
|
|
72
75
|
"check": "npm run typecheck && npm run build && npm test && node scripts/verify-host.mjs && npm pack --dry-run",
|
|
73
|
-
"
|
|
76
|
+
"prepack": "npm run build"
|
|
74
77
|
},
|
|
75
78
|
"publishConfig": {
|
|
76
79
|
"access": "public"
|