dsh-rewind-plugin 0.1.4 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -3
- package/lib/index.js +13 -1
- package/package.json +1 -1
- package/scripts/verify-host.mjs +13 -3
package/README.md
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
DeepSeek Harness 插件:**同一会话窗口的 in-place 对话回退**(Claude Code `/rewind` 语义)。主交互为**用户消息旁的「回退」按钮**,点击后选择回退模式;命令仅作辅助。
|
|
4
4
|
|
|
5
|
-
> 状态:v0.1.
|
|
5
|
+
> 状态:v0.1.5 已实现并发布(`dsh-rewind-plugin`,npm + GitHub Actions Trusted Publishing)。交互以 Claude Code 行为为参考,并贴合 dsh Web 实际 UI(利用现有 DOM 锚点与运行时快照,纯插件、不改仓库核心)。
|
|
6
6
|
|
|
7
|
-
## 实现状态(v0.1.
|
|
7
|
+
## 实现状态(v0.1.5)
|
|
8
8
|
|
|
9
9
|
- ✅ host 端 `/rewind` 命令(两步文本引导 + 直接执行 + `preview` 影响清单)
|
|
10
10
|
- ✅ host 端变更台账(`tools/execute` 捕获 before、`tools/post-execute` 提交),按会话隔离
|
|
@@ -143,7 +143,8 @@ git push --tags # push v<version> tag → 触发 .github/workflows/publish.
|
|
|
143
143
|
|
|
144
144
|
### 5. 安全守卫
|
|
145
145
|
|
|
146
|
-
- agent
|
|
146
|
+
- agent 运行中(LLM 思考/输出)执行回退时**自动强制停止**当前回合(`cancel({kind:'user'})`),
|
|
147
|
+
等待 quiescence 后回退;停止超时/失败则中止并报错。无需先手动停止。
|
|
147
148
|
- 文件还原是破坏性操作:UI 选择「对话和代码」时需经影响清单确认;命令路径用 `preview` 先行查看。
|
|
148
149
|
- 回退本身可再回退(回退动作同样进入台账/日志)。
|
|
149
150
|
|
package/lib/index.js
CHANGED
|
@@ -318,10 +318,22 @@ function renderFailures(failed) {
|
|
|
318
318
|
if (failed.length === 0) return "";
|
|
319
319
|
return `\uFF1B${failed.length} \u4E2A\u6587\u4EF6\u8FD8\u539F\u5931\u8D25\uFF1A${failed.map((f) => `${f.path}\uFF08${f.message}\uFF09`).join("\u3001")}`;
|
|
320
320
|
}
|
|
321
|
+
async function waitForAgentIdle(agent, signal, timeoutMs = 15e3) {
|
|
322
|
+
const deadline = Date.now() + timeoutMs;
|
|
323
|
+
while (agent.status !== "idle") {
|
|
324
|
+
if (signal.aborted || Date.now() > deadline) return false;
|
|
325
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
326
|
+
}
|
|
327
|
+
return true;
|
|
328
|
+
}
|
|
321
329
|
async function executeRewind(ctx, ledger, invocation, rawTarget, mode) {
|
|
322
330
|
const { agent } = invocation;
|
|
323
331
|
if (agent.status !== "idle") {
|
|
324
|
-
|
|
332
|
+
agent.cancel({ kind: "user" });
|
|
333
|
+
const stopped = await waitForAgentIdle(agent, invocation.signal);
|
|
334
|
+
if (!stopped) {
|
|
335
|
+
return { kind: "error", text: "\u65E0\u6CD5\u505C\u6B62\u8FD0\u884C\u4E2D\u7684 agent\uFF0C\u56DE\u9000\u5DF2\u53D6\u6D88\u3002\u8BF7\u7A0D\u540E\u518D\u8BD5\u3002" };
|
|
336
|
+
}
|
|
325
337
|
}
|
|
326
338
|
let plan;
|
|
327
339
|
try {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-rewind-plugin",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "DeepSeek Harness plugin: in-place conversation rewind in the same session window (Claude Code /rewind semantics) with optional workspace file restore",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"deepseek-harness",
|
package/scripts/verify-host.mjs
CHANGED
|
@@ -150,10 +150,20 @@ const bothResult = await call(agent, '@2 both')
|
|
|
150
150
|
check('rewind both restores file', bothResult.kind === 'success' && bothResult.text.includes('还原 1 个文件'), bothResult.text)
|
|
151
151
|
check('file content restored', fs.files.get('/workspace/a.txt') === 'original content', fs.files.get('/workspace/a.txt'))
|
|
152
152
|
|
|
153
|
-
// 9.
|
|
154
|
-
|
|
153
|
+
// 9. a running agent is force-stopped before the rewind (not refused)
|
|
154
|
+
let cancelled = false
|
|
155
|
+
const running = {
|
|
156
|
+
...agent, status: 'running',
|
|
157
|
+
cancel: () => { cancelled = true; running.status = 'idle' },
|
|
158
|
+
}
|
|
155
159
|
const runningResult = await registered.handler({ commandId: Symbol('cid'), agent: running, rawInput: '@2 chat', signal: aborted() })
|
|
156
|
-
check('running agent
|
|
160
|
+
check('running agent is cancelled first', cancelled === true, `cancelled=${cancelled}`)
|
|
161
|
+
check('rewind succeeds after stop', runningResult.kind === 'success', runningResult.text)
|
|
162
|
+
|
|
163
|
+
// 9b. a cancel that never quiesces aborts the rewind (timeout path)
|
|
164
|
+
const stuck = { ...agent, status: 'running', cancel: () => {} }
|
|
165
|
+
const stuckResult = await registered.handler({ commandId: Symbol('cid'), agent: stuck, rawInput: '@2 chat', signal: aborted() })
|
|
166
|
+
check('stuck agent aborts rewind', stuckResult.kind === 'error', stuckResult.text)
|
|
157
167
|
|
|
158
168
|
console.log(failures === 0 ? '\nverify-host: all checks passed' : `\nverify-host: ${failures} check(s) FAILED`)
|
|
159
169
|
process.exit(failures === 0 ? 0 : 1)
|