@kangyishuai/dsh-session-rewind 0.4.0 → 0.5.0

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 CHANGED
@@ -8,7 +8,7 @@
8
8
 
9
9
  Unofficial plugin for [DeepSeek Harness (DSH)](https://github.com/deepseek-ai/deepseek-harness). Not affiliated with DeepSeek.
10
10
 
11
- > **Status: early.** Verified end to end on DSH 0.2.2-rc.1: 31 GUI assertions in a throwaway DSH home, 158 unit tests, and a CDP-driven desktop pass (conflict refusal, conversation + file rewind, created-file trash, modified-file restore, rescue restore, panel/audit/diagnostics). Young software — keep backups of anything you cannot lose.
11
+ > **Status: early.** Verified end to end on DSH 0.2.2-rc.1: 185 unit tests, 34 GUI assertions in a throwaway DSH home, and **36 assertions against the real desktop app** (read-only surface plus file mode: created-file trash, modified-file restore, rescue restore, timeline). `pnpm gate:session-rewind` runs the whole release gate in one command. Young software — keep backups of anything you cannot lose.
12
12
 
13
13
  ## What it does
14
14
 
@@ -21,6 +21,7 @@ Long conversations drift. A few turns later you wish you had answered a prompt d
21
21
  - **Conversation.** The target message and everything after it are withdrawn from the model-visible context and from the transcript. The session id, title and earlier history are untouched. Nothing is deleted from the log — one marker event is appended.
22
22
  - **Files (optional).** Files changed by `write` / `edit` / `apply_patch` in the withdrawn turns are restored to their pre-change content. Files created after the target are moved to the plugin's trash instead of deleted. A rescue point of the current files is created before anything is written; failures roll back from it.
23
23
  - **Composer.** The target message's text (and images) is put back into the composer, ready to edit and send again. An existing draft is never overwritten. Tick **Send this message again right after rewinding** in the confirmation dialog to retry the turn in one click (conversation-only rewinds).
24
+ - **Every view stays in sync.** Rewind in one tab or window and other open views of the same session hide the withdrawn rows immediately, without a reload (the notice travels through `localStorage`; the host's cuts remain the only source of truth).
24
25
  - **Rewind panel.** A new header action opens a panel with four tabs: **Withdrawn** (every message each rewind removed, searchable, copy or put back in the composer), **History** (a timeline of each rewind and each rescue-point file restore, newest first: scope, message count, tokens, file results), **Rescue points** (restore files from an earlier rescue), and **Settings** (default scope, page size, confirm-on-list, diagnostics).
25
26
 
26
27
  ## Read-only agent tool
@@ -105,9 +106,9 @@ On startup the plugin also removes data for sessions that no longer exist (they
105
106
 
106
107
  ## Compatibility
107
108
 
108
- | DSH version | Status |
109
- | ----------- | --------------------------------------------------------------------------------------------------------------------------- |
110
- | 0.2.2-rc.1 | Verified: `pnpm smoke:session-rewind` (31/31) + 158 unit tests + CDP-driven desktop pass (conversation-only and file modes) |
109
+ | DSH version | Status |
110
+ | ----------- | ------------------------------------------------------------------------------------------------------ |
111
+ | 0.2.2-rc.1 | Verified: 185 unit tests + `smoke:session-rewind` 34/34 + desktop 36/36 (`--deep`, file mode included) |
111
112
 
112
113
  The plugin declares no `engines.dsh` and no `@deepseek-ai/*` peer range: DSH is pre-release and semver ranges over prereleases are unreliable (see [ADR 0005](../../docs/adr/0005-dsh-version-compatibility.md)). Treat the table as the compatibility statement. After a DSH upgrade run `pnpm probe:runtime`, `pnpm smoke:session-rewind`, and check the plugin's diagnostics tab; if a private seam changed, the plugin degrades instead of breaking the session.
113
114
 
@@ -117,7 +118,7 @@ The plugin declares no `engines.dsh` and no `@deepseek-ai/*` peer range: DSH is
117
118
  pnpm test # unit tests (host engine, host RPC, client helpers)
118
119
  pnpm verify # repo-wide gates
119
120
  pnpm smoke:session-rewind # temp DSH home + real GUI end-to-end (needs dsh & ego-browser)
120
- pnpm smoke:session-rewind-desktop # read-only desktop check (needs --remote-debugging-port=9222)
121
+ pnpm smoke:session-rewind-desktop # desktop (Electron) check: 21 read-only + 12 with --deep --workspace <dir>
121
122
  ```
122
123
 
123
124
  Design notes: [ADR 0006](../../docs/adr/0006-session-rewind-architecture.md) and [ADR 0007](../../docs/adr/0007-session-rewind-file-restore.md) (Chinese; the repository docs are written in Chinese).
package/README.zh-CN.md CHANGED
@@ -8,7 +8,7 @@
8
8
 
9
9
  非官方插件,与 DeepSeek 无关。面向 [DeepSeek Harness (DSH)](https://github.com/deepseek-ai/deepseek-harness)。
10
10
 
11
- > **状态:早期。** 已在 DSH 0.2.2-rc.1 上端到端验证:一次性 DSH 目录里 31 项 GUI 断言 + 158 个单测 + CDP 驱动的桌面版实测(冲突拒绝、对话+文件回退、新建文件进回收站、修改文件还原、救援点恢复、面板/审计/诊断)。插件还年轻,重要数据请自行备份。
11
+ > **状态:早期。** 已在 DSH 0.2.2-rc.1 上端到端验证:185 个单测、一次性 DSH 目录里 34 项 GUI 断言、以及**真实桌面版 36 项断言**(只读界面 + 文件模式:新建文件进回收站、修改文件还原、救援点恢复、时间线)。`pnpm gate:session-rewind` 一条命令跑完发版门禁。插件还年轻,重要数据请自行备份。
12
12
 
13
13
  ## 它做什么
14
14
 
@@ -21,6 +21,7 @@
21
21
  - **状态变更提示**:如果要撤回的区间里改过会话状态(plan 模式、goal、待办、模型选择、权限/沙箱预设、智能体预设、定时任务、工作目录),确认框会列出来并明确说明——回溯只撤回对话,**不会**回滚这些状态。
22
22
  - **运行中也能回退**:确认回退时如果这一轮正在跑,会先取消它(保留队列)、等它空闲,再对新历史重新求值。
23
23
  - **输入框**:目标消息文本(以及图片)放回输入框,改完可直接重发;已有草稿绝不覆盖。勾选「回退后立即重发」可一键重试这一轮(仅对话模式)。
24
+ - **多视图自动同步**:在一个标签页/窗口里回溯后,其它已打开的同会话视图会立刻隐藏被撤回的行,无需刷新(通知走 `localStorage`,渲染只认宿主 cuts)。
24
25
  - **回溯面板**:会话头部新增一个入口,四个页签:**已撤回**(每次回溯撤回的全部消息,可搜索、复制、放回输入框)、**回溯记录**(时间线:每次回溯与每次救援点恢复,新在上;范围、条数、token、文件结果)、**救援点**(手动恢复文件)、**设置**(默认范围、每页数量、列表确认、诊断)。
25
26
 
26
27
  ## 只读 agent 工具
@@ -105,9 +106,9 @@ dsh plugin --profile desktop add /tmp/kangyishuai-dsh-session-rewind-*.tgz
105
106
 
106
107
  ## 兼容性
107
108
 
108
- | DSH 版本 | 状态 |
109
- | ---------- | ------------------------------------------------------------------------------------------------- |
110
- | 0.2.2-rc.1 | 已验证:`pnpm smoke:session-rewind` 31/31 + 158 个单测 + CDP 驱动的桌面版实测(仅对话与文件模式) |
109
+ | DSH 版本 | 状态 |
110
+ | ---------- | ---------------------------------------------------------------------------------------- |
111
+ | 0.2.2-rc.1 | 已验证:185 个单测 + `smoke:session-rewind` 34/34 + 桌面版 36/36(`--deep`,含文件模式) |
111
112
 
112
113
  插件不声明 `engines.dsh`,也不声明 `@deepseek-ai/*` 的 peer 范围:DSH 还在预发布阶段,预发布版本的 semver 范围不可靠(见 [ADR 0005](../../docs/adr/0005-dsh-version-compatibility.md))。以这张表为准。DSH 升级后跑 `pnpm probe:runtime`、`pnpm smoke:session-rewind`,并看插件诊断页签;私有 seam 变了会降级而不是让会话出错。
113
114
 
@@ -117,7 +118,7 @@ dsh plugin --profile desktop add /tmp/kangyishuai-dsh-session-rewind-*.tgz
117
118
  pnpm test # 单测(宿主文件引擎、宿主 RPC、客户端 helper)
118
119
  pnpm verify # 仓库统一门禁
119
120
  pnpm smoke:session-rewind # 临时 DSH 目录 + 真实 GUI 端到端(需要 dsh 与 ego-browser)
120
- pnpm smoke:session-rewind-desktop # 只读桌面回归(需要以 --remote-debugging-port=9222 重启)
121
+ pnpm smoke:session-rewind-desktop # 桌面(Electron)回归:只读 21 项,加 --deep --workspace <dir> 再 +12 项
121
122
  ```
122
123
 
123
124
  设计记录:[ADR 0006](../../docs/adr/0006-session-rewind-architecture.md)、[ADR 0007](../../docs/adr/0007-session-rewind-file-restore.md)。
package/lib/client.js CHANGED
@@ -574,6 +574,33 @@ window.__ModuleLoader__.load({
574
574
  /** localStorage 偏好键;版本化,方便未来迁移。 */
575
575
  let PREF_STORAGE_KEY = "dsh-session-rewind.prefs.v1";
576
576
 
577
+ /** 跨视图广播键:某个视图回溯成功后写入,其它视图据此重新拉宿主 cuts。 */
578
+ let CUT_BROADCAST_KEY = "dsh-session-rewind.cut-broadcast.v1";
579
+
580
+ /**
581
+ * 解析跨视图广播载荷(脏数据一律返回 null)。
582
+ * @param {any} raw localStorage 里的字符串
583
+ * @returns {{sessionId: string, targetSeq: number|null, markerSeq: number|null, at: number} | null}
584
+ */
585
+ function parseCutBroadcast(raw) {
586
+ if (typeof raw !== "string" || raw === "") return null;
587
+ let value;
588
+ try {
589
+ value = JSON.parse(raw);
590
+ } catch (_error) {
591
+ return null;
592
+ }
593
+ if (!value || typeof value !== "object") return null;
594
+ let sessionId = typeof value.sessionId === "string" ? value.sessionId : "";
595
+ if (sessionId === "") return null;
596
+ return {
597
+ sessionId: sessionId,
598
+ targetSeq: typeof value.targetSeq === "number" ? value.targetSeq : null,
599
+ markerSeq: typeof value.markerSeq === "number" ? value.markerSeq : null,
600
+ at: typeof value.at === "number" ? value.at : 0,
601
+ };
602
+ }
603
+
577
604
  /** 客户端偏好默认值:默认仅回退对话、每页 100 条、列表选择先确认。 */
578
605
  let PREF_DEFAULTS = Object.freeze({
579
606
  defaultMode: "chat",
@@ -3770,6 +3797,8 @@ window.__ModuleLoader__.load({
3770
3797
  });
3771
3798
  rebuildSessionById(sessionId);
3772
3799
  let outcome = fillComposer(sessionId, summary.text || target.text || "");
3800
+ // 同源其它视图也要立刻隐藏这些行(它们不会被这次 append 唤醒)。
3801
+ broadcastCut(sessionId, summary);
3773
3802
  let text = t("toast.rewound", {
3774
3803
  n: summary.removedCount,
3775
3804
  tokens: formatCount(summary.tokens),
@@ -4250,6 +4279,57 @@ window.__ModuleLoader__.load({
4250
4279
  }
4251
4280
 
4252
4281
  /** 拉一次宿主 cuts 作为可见性兜底(静默失败)。 */
4282
+ /**
4283
+ * 把「这个会话刚回溯过」广播给同源其它视图(多标签/多窗口一致性)。
4284
+ * localStorage 的 storage 事件只在**其它** document 触发,不会回环到自己。
4285
+ * @param {any} sessionId
4286
+ * @param {any} summary
4287
+ */
4288
+ function broadcastCut(sessionId, summary) {
4289
+ try {
4290
+ if (!window.localStorage) return;
4291
+ window.localStorage.setItem(
4292
+ CUT_BROADCAST_KEY,
4293
+ JSON.stringify({
4294
+ sessionId: sessionId,
4295
+ targetSeq:
4296
+ summary && typeof summary.targetSeq === "number" ? summary.targetSeq : null,
4297
+ markerSeq:
4298
+ summary && typeof summary.markerSeq === "number" ? summary.markerSeq : null,
4299
+ at: Date.now(),
4300
+ }),
4301
+ );
4302
+ } catch (_error) {
4303
+ /* 存储不可用时退化为「只有本视图知道」 */
4304
+ }
4305
+ }
4306
+
4307
+ /**
4308
+ * 监听其它视图的广播:收到就按宿主 cuts 重新隐藏(宿主是权威来源,不信任广播内容)。
4309
+ * @returns {() => void} disposer
4310
+ */
4311
+ function installCutBroadcast() {
4312
+ /** @param {any} event */
4313
+ function onStorage(event) {
4314
+ if (!event || event.key !== CUT_BROADCAST_KEY) return;
4315
+ let payload = parseCutBroadcast(event.newValue);
4316
+ if (!payload) return;
4317
+ seedCuts(payload.sessionId);
4318
+ }
4319
+ try {
4320
+ window.addEventListener("storage", onStorage);
4321
+ } catch (_error) {
4322
+ return function noop() {};
4323
+ }
4324
+ return function dispose() {
4325
+ try {
4326
+ window.removeEventListener("storage", onStorage);
4327
+ } catch (_error) {
4328
+ /* ignore */
4329
+ }
4330
+ };
4331
+ }
4332
+
4253
4333
  function seedCuts(/** @type {any} */ sessionId) {
4254
4334
  return call("cuts", { sessionId: sessionId })
4255
4335
  .then((/** @type {any} */ value) => {
@@ -4303,6 +4383,9 @@ window.__ModuleLoader__.load({
4303
4383
 
4304
4384
  installChatVisibility(ctx, cutStore);
4305
4385
 
4386
+ // 多视图一致性:别的标签页回溯后,本视图按宿主 cuts 重新隐藏。
4387
+ ctx.effect(() => installCutBroadcast(), "session-rewind: cut broadcast");
4388
+
4306
4389
  ctx.effect(
4307
4390
  () =>
4308
4391
  installRowAction(ctx.slots, {
@@ -4535,6 +4618,8 @@ window.__ModuleLoader__.load({
4535
4618
  normalizeFiles: normalizeFiles,
4536
4619
  normalizePreview: normalizePreview,
4537
4620
  normalizeStateChanges: normalizeStateChanges,
4621
+ parseCutBroadcast: parseCutBroadcast,
4622
+ CUT_BROADCAST_KEY: CUT_BROADCAST_KEY,
4538
4623
  fileModeState: fileModeState,
4539
4624
  effectiveMode: effectiveMode,
4540
4625
  conflictPathsOf: conflictPathsOf,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kangyishuai/dsh-session-rewind",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "In-place conversation rewind for DSH: withdraw a user message and everything after it, then refill the composer",
5
5
  "keywords": [
6
6
  "dsh",