dsh-rewind-plugin 0.16.0 → 0.17.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.en.md CHANGED
@@ -72,11 +72,14 @@ then install the new one.
72
72
 
73
73
  ## Snapshot management
74
74
 
75
- Snapshots (the before-write backups) are stored under `<dsh home>/rewind-snapshots/`
75
+ **Snapshot backups** are on by default: they power file restore and can be
76
+ switched off in this plugin's configuration card. When off, no new snapshots
77
+ are created; file changes made while off cannot be restored. Snapshots (the
78
+ before-write backups) are stored under `<dsh home>/rewind-snapshots/`
76
79
  (the default is `~/.dsh/rewind-snapshots/`). For the **same session**, the plugin
77
80
  deduplicates snapshots by content and keeps the newest 100 anchor groups.
78
81
  **Deleting that directory manually** only clears the file backups (chat rewinds are
79
- unaffected) and the plugin rebuilds them automatically.
82
+ unaffected); the plugin can rebuild them automatically.
80
83
 
81
84
  A **global auto-cleanup** (off by default) removes the snapshot directories of
82
85
  long-inactive sessions, leaving the active session and chat log untouched. Configure
@@ -85,7 +88,7 @@ auto-cleanup switch and the idle-day cutoff), or use the
85
88
  `/snapshot-auto-cleanup` command to **view, configure, and run** it. See:
86
89
  [Snapshot cleanup](docs/snapshot-auto-cleanup.md).
87
90
 
88
- <img src="assets/screenshots/cleanup-setting.png" alt="Plugin configuration card: auto cleanup and idle time (days)" width="600">
91
+ <img src="assets/screenshots/plugin-setting.png" alt="Plugin configuration card: snapshot backups and auto cleanup" width="600">
89
92
 
90
93
  ## Uninstall
91
94
 
@@ -150,7 +153,7 @@ This plugin deliberately stays lightweight and focused on one thing — "convers
150
153
  ## Compatibility
151
154
 
152
155
  - Node.js `^22.19.0 || >=24.0.0`.
153
- - Compatibility definition, verification method, and version alignment: see [docs/compat/audit.md](docs/compat/audit.md); supported DSH versions are declared by `package.json` `peerDependencies`.
156
+ - Compatibility definition, verification method, and version alignment: see [docs/compat/audit.md](docs/compat/audit.md); supported DSH versions are declared by `package.json` `engines.dsh`.
154
157
 
155
158
  > [!WARNING]
156
159
  > This project and DeepSeek Harness are both in developer preview. Pin exact
@@ -169,7 +172,6 @@ withdrew should consume the stable, locale-independent helpers exported from
169
172
  1. **Exported logs are complete** — a rewind only removes messages from the model context and the view; the exported session log (`/export`) still contains **withdrawn messages**. This plugin cannot alter exports.
170
173
  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).
171
174
  3. **The turn-rail shows rewound turns** — the right-side rail added in DSH `v0.1.2` keeps ticks for withdrawn messages, and hovering shows the withdrawn text. Only a display difference; no functional impact.
172
- 4. **Old rewind markers are no longer compatible** — DSH `v0.1.3` rejects the rewind markers from the old plugin (≤ 0.8.0). Later versions resolve this and provide an in-session update. See the [update guide](docs/rewind-fix.md).
173
175
 
174
176
  > [!NOTE]
175
177
  > Browser diagnostics are available; see [Browser diagnostics](docs/compat/diagnostics.md).
package/README.md CHANGED
@@ -69,11 +69,11 @@ dsh plugin --profile <name> add dsh-rewind-plugin@<版本>
69
69
 
70
70
  ## 存储管理
71
71
 
72
- 快照(写前备份)存储于 `<dsh home>/rewind-snapshots/`(默认 `~/.dsh/rewind-snapshots/`)。插件对**同一会话**的快照做内容去重并保留最近 100 组锚点;**手动删除该目录**仅清除文件备份(对话回退不受影响),插件会自动重建。
72
+ **快照备份**(默认开启):用于文件还原;可在本插件的配置卡片中关闭。关闭后不再创建新的快照,关闭期间的文件改动无法还原。快照存储于 `<dsh home>/rewind-snapshots/`(默认 `~/.dsh/rewind-snapshots/`)。手动删除该目录仅清除文件备份(对话回退不受影响),插件可自动重建。
73
73
 
74
- 另提供**全局自动清理**(默认关闭):把长期不活跃的会话快照整目录移除,不影响活动会话与对话日志。可在**插件**页本插件的配置卡片查看与配置(自动清理开关、失活天数),也可用 `/snapshot-auto-cleanup` 命令查看、设置和运行。详见:[快照自动清理](docs/snapshot-auto-cleanup.zh.md)。
74
+ **全局自动清理**(默认关闭):把长期不活跃的会话快照整目录移除,不影响活动会话与对话日志。可在配置卡片查看与配置(自动清理开关、失活天数),也可用 `/snapshot-auto-cleanup` 命令查看、设置和运行。详见:[快照自动清理](docs/snapshot-auto-cleanup.zh.md)。
75
75
 
76
- <img src="assets/screenshots/cleanup-setting.png" alt="插件配置卡片:自动清理与不活跃时间(天)" width="600">
76
+ <img src="assets/screenshots/plugin-setting.png" alt="插件配置卡片:快照备份与自动清理" width="600">
77
77
 
78
78
  ## 卸载
79
79
 
@@ -137,7 +137,7 @@ rm <dsh home>/snapshot-cleanup-last-sweep.json
137
137
  ## 兼容性
138
138
 
139
139
  - Node.js `^22.19.0 || >=24.0.0`。
140
- - 兼容性定义、验证方法与版本对齐详见 [docs/compat/audit.md](docs/compat/audit.md);支持的 DSH 版本由 `package.json` 的 `peerDependencies` 声明。
140
+ - 兼容性定义、验证方法与版本对齐详见 [docs/compat/audit.md](docs/compat/audit.md);支持的 DSH 版本由 `package.json` 的 `engines.dsh` 声明。
141
141
 
142
142
  > [!WARNING]
143
143
  > 本项目与 DeepSeek Harness 均处于开发者预览阶段。可复现环境请 pin 精确版本,
@@ -153,7 +153,6 @@ rm <dsh home>/snapshot-cleanup-last-sweep.json
153
153
  1. **导出的日志是完整内容**——回退只是把消息从模型上下文和视图中移除,`/export` 导出的会话日志包含**已撤回的消息**。本插件无法改动导出。
154
154
  2. **轻量文件回退存在代价**——特定情况可能无法回退所有修改。行为与 Claude Code 一致。详见:[文件回退的追踪边界](docs/compat/tracking-boundary.zh.md)。
155
155
  3. **导轨显示已回退轮次**——DSH `v0.1.2` 新增右侧导轨,为已撤回消息保留刻度,悬浮显示已撤回正文。仅显示差异,无功能影响。
156
- 4. **旧回退标记不再兼容**——DSH `v0.1.3` 拒绝旧版插件(≤ 0.8.0)的回退标记。后续版本已解决,并提供会话更新功能。详见:[更新指南](docs/rewind-fix.zh.md)。
157
156
 
158
157
  > [!NOTE]
159
158
  > 本插件提供浏览器端诊断输出;详见 [浏览器诊断](docs/compat/diagnostics.zh.md)。
package/SECURITY.md CHANGED
@@ -26,10 +26,11 @@ re-verify the host's own authority boundaries.
26
26
 
27
27
  ## Mutation gates
28
28
 
29
- The plugin **automatically captures** before-backups of tracked mutations, but
30
- **never automatically applies** one. A workspace restore happens only through
31
- an explicit user-invoked `/rewind-plugin @<seq> both` (the per-message ↶ button or the
32
- command channel), and only when all of the following hold:
29
+ When `fileCheckpointingEnabled` is on, the plugin **automatically captures**
30
+ before-backups of tracked mutations, but **never automatically applies** one.
31
+ A workspace restore happens only through an explicit user-invoked
32
+ `/rewind-plugin @<seq> both` (the per-message ↶ button or the command channel),
33
+ and only when all of the following hold:
33
34
 
34
35
  1. **A validated target**: `planRewind` accepts only a `user/message` seq that
35
36
  is currently on the session surface (`parseRewindTarget` → `RewindPlan`).
package/lib/client.js CHANGED
@@ -99,6 +99,9 @@ function hasFileImpact(text) {
99
99
  if (match !== null) return Number(match[1]) > 0;
100
100
  return false;
101
101
  }
102
+ function hasBackupDisabled(text) {
103
+ return text !== void 0 && /(?:^|\n)backup=off(?:\n|$)/.test(text);
104
+ }
102
105
  function isPreviewCommand(command) {
103
106
  return (command.args ?? "").includes("preview");
104
107
  }
@@ -307,6 +310,7 @@ var CLASS = {
307
310
  popoverOptionLabel: "dsh-rewind-popover-option-label",
308
311
  popoverOptionHint: "dsh-rewind-popover-option-hint",
309
312
  popoverImpact: "dsh-rewind-popover-impact",
313
+ popoverNote: "dsh-rewind-popover-note",
310
314
  popoverActions: "dsh-rewind-popover-actions",
311
315
  popoverPrimary: "dsh-rewind-popover-primary",
312
316
  popoverGhost: "dsh-rewind-popover-ghost",
@@ -417,6 +421,12 @@ var STYLE = `
417
421
  max-height: 160px;
418
422
  overflow: auto;
419
423
  }
424
+ .dsh-rewind-popover-note {
425
+ margin-top: 6px;
426
+ font-size: 11px;
427
+ line-height: 15px;
428
+ color: var(--dsw-alias-label-tertiary);
429
+ }
420
430
  .dsh-rewind-popover-actions {
421
431
  display: flex;
422
432
  justify-content: flex-end;
@@ -466,16 +476,17 @@ var STYLE = `
466
476
  pointer-events: none;
467
477
  }
468
478
 
469
- /* ---- Snapshot-cleanup configuration form ---- */
470
- /* The Plugins page draws the bundle's title, icon, crumb, description, and the
471
- shared settings form frame (labels, save, read-only and failure lines); this
472
- styles only the one control the card owns: the auto-cleanup toggle row. */
473
- .dsh-rewind-cleanup-permission {
479
+ /* ---- Plugin configuration form ---- */
480
+ /* Only the card's own controls: the two switch rows, their hints, and the divider. */
481
+ .dsh-rewind-settings-divider {
482
+ border-top: 1px solid var(--dsw-alias-border-l2);
483
+ }
484
+ .dsh-rewind-settings-section {
474
485
  display: grid;
475
486
  gap: 6px;
476
487
  padding: 12px 0;
477
488
  }
478
- .dsh-rewind-cleanup-toggle-row {
489
+ .dsh-rewind-settings-toggle-row {
479
490
  display: flex;
480
491
  align-items: flex-start;
481
492
  justify-content: space-between;
@@ -484,11 +495,11 @@ var STYLE = `
484
495
  line-height: 1.5;
485
496
  color: var(--dsw-alias-label-primary);
486
497
  }
487
- .dsh-rewind-cleanup-toggle-label {
498
+ .dsh-rewind-settings-toggle-label {
488
499
  flex: 1;
489
500
  min-width: 0;
490
501
  }
491
- .dsh-rewind-cleanup-hint {
502
+ .dsh-rewind-settings-hint {
492
503
  margin: 0;
493
504
  font-size: 12px;
494
505
  line-height: 1.5;
@@ -676,7 +687,13 @@ function renderImpactStep(root, opts, back, cached) {
676
687
  ...restores.map((path) => t("popover.impact.restore", { path })),
677
688
  ...deletes.map((path) => t("popover.impact.delete", { path }))
678
689
  ];
679
- impact.textContent = lines.join("\n");
690
+ if (hasBackupDisabled(outcome.text)) {
691
+ const list = document.createElement("div");
692
+ list.textContent = lines.join("\n");
693
+ impact.replaceChildren(list, el("div", CLASS.popoverNote, t("popover.backupDisabled")));
694
+ } else {
695
+ impact.textContent = lines.join("\n");
696
+ }
680
697
  }
681
698
  }
682
699
  confirm.disabled = false;
@@ -1368,9 +1385,9 @@ function createRewindBridge(deps) {
1368
1385
  }
1369
1386
 
1370
1387
  // src/client/build-info.ts
1371
- var PLUGIN_VERSION = true ? "0.16.0" : "dev";
1388
+ var PLUGIN_VERSION = true ? "0.17.0" : "dev";
1372
1389
  var PLUGIN_PACKAGE = "dsh-rewind-plugin";
1373
- var BUILD_HASH = true ? "c6cfbc55" : "dev";
1390
+ var BUILD_HASH = true ? "bae5081f" : "dev";
1374
1391
 
1375
1392
  // src/client/locales.ts
1376
1393
  var zh = {
@@ -1390,6 +1407,7 @@ var zh = {
1390
1407
  "popover.both.hint": "\u5BF9\u8BDD\u56DE\u9000\u5E76\u8FD8\u539F\u5DE5\u4F5C\u533A\u6587\u4EF6",
1391
1408
  "popover.checking": "\u6B63\u5728\u68C0\u67E5\u6587\u4EF6\u53D8\u66F4\u2026",
1392
1409
  "popover.noChanges": "\u6B64\u6D88\u606F\u4E4B\u540E\u6CA1\u6709\u53EF\u8FD8\u539F\u7684\u6587\u4EF6\u53D8\u66F4\uFF0C\u4EC5\u53EF\u56DE\u9000\u5BF9\u8BDD",
1410
+ "popover.backupDisabled": "\u5FEB\u7167\u5907\u4EFD\u5DF2\u5173\u95ED\uFF0C\u8FD8\u539F\u53EF\u80FD\u4E0D\u5145\u5206",
1393
1411
  "popover.cancel": "\u53D6\u6D88",
1394
1412
  "popover.impact.loading": "\u6B63\u5728\u83B7\u53D6\u5F71\u54CD\u6E05\u5355\u2026",
1395
1413
  "popover.impact.failed": "\u65E0\u6CD5\u83B7\u53D6\u5F71\u54CD\u6E05\u5355\uFF1A{message}",
@@ -1398,11 +1416,14 @@ var zh = {
1398
1416
  "popover.impact.delete": "\u5220\u9664 {path}",
1399
1417
  "popover.confirm": "\u786E\u8BA4\u56DE\u9000",
1400
1418
  "popover.back": "\u8FD4\u56DE",
1419
+ "backup.title": "\u5FEB\u7167\u5907\u4EFD",
1420
+ "backup.on": "\u5F00\u542F\u540E\u81EA\u52A8\u521B\u5EFA\u6587\u4EF6\u5FEB\u7167\uFF0C\u7528\u4E8E\u6587\u4EF6\u8FD8\u539F\u3002",
1421
+ "backup.off": "\u5173\u95ED\u540E\u4E0D\u518D\u521B\u5EFA\u6587\u4EF6\u5FEB\u7167\uFF1B\u5173\u95ED\u671F\u95F4\u7684\u6587\u4EF6\u6539\u52A8\u65E0\u6CD5\u8FD8\u539F\u3002",
1401
1422
  "cleanup.auto": "\u81EA\u52A8\u6E05\u7406",
1402
- "cleanup.auto.on": "\u5F00\u542F\u540E\u6309\u4E0D\u6D3B\u8DC3\u5929\u6570\u81EA\u52A8\u6E05\u7406\u8FC7\u671F\u4F1A\u8BDD\u5FEB\u7167",
1403
- "cleanup.auto.off": "\u5173\u95ED\u540E\u4FDD\u7559\u5168\u90E8\u5FEB\u7167\uFF0C\u4E0D\u81EA\u52A8\u6E05\u7406",
1423
+ "cleanup.auto.on": "\u5F00\u542F\u540E\u6309\u4E0D\u6D3B\u8DC3\u5929\u6570\u81EA\u52A8\u6E05\u7406\u8FC7\u671F\u4F1A\u8BDD\u5FEB\u7167\u3002",
1424
+ "cleanup.auto.off": "\u5173\u95ED\u540E\u4E0D\u4F1A\u81EA\u52A8\u6E05\u7406\u5FEB\u7167\u3002",
1404
1425
  "cleanup.maxAge": "\u4E0D\u6D3B\u8DC3\u65F6\u95F4\uFF08\u5929\uFF09",
1405
- "cleanup.maxAge.hint": "\u8D85\u8FC7\u8BE5\u5929\u6570\u672A\u6D3B\u52A8\u7684\u4F1A\u8BDD\u5FEB\u7167\u4F1A\u88AB\u6E05\u7406",
1426
+ "cleanup.maxAge.hint": "\u8D85\u8FC7\u8BE5\u5929\u6570\u672A\u6D3B\u52A8\u7684\u4F1A\u8BDD\u5FEB\u7167\u4F1A\u88AB\u6E05\u7406\u3002",
1406
1427
  "cleanup.invalid": "\u8BF7\u586B\u6B63\u6574\u6570\uFF1B\u7559\u7A7A\u8868\u793A\u4F7F\u7528\u9ED8\u8BA4\u503C",
1407
1428
  "cleanup.overridden": "\u5DF2\u8986\u76D6",
1408
1429
  "cleanup.reset": "\u6062\u590D\u9ED8\u8BA4",
@@ -1429,6 +1450,7 @@ var en = {
1429
1450
  "popover.both.hint": "Cut the context and restore workspace files",
1430
1451
  "popover.checking": "Checking for file changes\u2026",
1431
1452
  "popover.noChanges": "No tracked file changes after this message; conversation-only rewind",
1453
+ "popover.backupDisabled": "Snapshots off; the restore may be incomplete",
1432
1454
  "popover.cancel": "Cancel",
1433
1455
  "popover.impact.loading": "Fetching impact list\u2026",
1434
1456
  "popover.impact.failed": "Could not fetch the impact list: {message}",
@@ -1437,11 +1459,14 @@ var en = {
1437
1459
  "popover.impact.delete": "Delete {path}",
1438
1460
  "popover.confirm": "Confirm rewind",
1439
1461
  "popover.back": "Back",
1462
+ "backup.title": "Snapshot backups",
1463
+ "backup.on": "When on, file snapshots are created automatically for file restore.",
1464
+ "backup.off": "When off, no file snapshots are created; file changes made while off cannot be restored.",
1440
1465
  "cleanup.auto": "Auto cleanup",
1441
- "cleanup.auto.on": "When enabled, session snapshots idle past the cutoff are cleaned automatically",
1442
- "cleanup.auto.off": "When disabled, all snapshot backups are kept; nothing is cleaned automatically.",
1466
+ "cleanup.auto.on": "When enabled, session snapshots idle past the cutoff are cleaned automatically.",
1467
+ "cleanup.auto.off": "When off, snapshots are not cleaned automatically.",
1443
1468
  "cleanup.maxAge": "Idle time (days)",
1444
- "cleanup.maxAge.hint": "Snapshot backups of sessions idle longer than this many days are cleaned",
1469
+ "cleanup.maxAge.hint": "Snapshot backups of sessions idle longer than this many days are cleaned.",
1445
1470
  "cleanup.invalid": "Enter a positive integer, or leave blank to use the default",
1446
1471
  "cleanup.overridden": "Overridden",
1447
1472
  "cleanup.reset": "Reset to default",
@@ -1455,7 +1480,7 @@ var en = {
1455
1480
  // src/client/settings-card.tsx
1456
1481
  var import_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
1457
1482
  var import_jsx_runtime2 = require("react/jsx-runtime");
1458
- var CLEANUP_ENTRY_ID = "dsh-rewind-plugin";
1483
+ var SETTINGS_ENTRY_ID = "dsh-rewind-plugin";
1459
1484
  var DEFAULT_MAX_AGE_DAYS = 30;
1460
1485
  function autoCleanupEnabledField() {
1461
1486
  return {
@@ -1464,6 +1489,13 @@ function autoCleanupEnabledField() {
1464
1489
  parse: (text) => ({ kind: "set", value: text === "true" })
1465
1490
  };
1466
1491
  }
1492
+ function fileCheckpointingEnabledField() {
1493
+ return {
1494
+ field: "fileCheckpointingEnabled",
1495
+ format: (value) => String(value !== false),
1496
+ parse: (text) => text === "true" ? { kind: "clear" } : { kind: "set", value: false }
1497
+ };
1498
+ }
1467
1499
  function formLabels(t) {
1468
1500
  return {
1469
1501
  unavailable: t("cleanup.unavailable"),
@@ -1473,8 +1505,9 @@ function formLabels(t) {
1473
1505
  saving: t("cleanup.saving")
1474
1506
  };
1475
1507
  }
1476
- function cleanupForm(scope) {
1508
+ function settingsForm(scope) {
1477
1509
  const form = new import_dsh_client_ui_primitives.SettingsFormModel(scope, [
1510
+ fileCheckpointingEnabledField(),
1478
1511
  autoCleanupEnabledField(),
1479
1512
  (0, import_dsh_client_ui_primitives.settingsNumberField)("autoCleanupMaxAgeDays")
1480
1513
  ]);
@@ -1482,17 +1515,19 @@ function cleanupForm(scope) {
1482
1515
  form,
1483
1516
  store: form.bind(() => ({
1484
1517
  ...form.shell(),
1518
+ checkpointingEnabled: form.field("fileCheckpointingEnabled"),
1485
1519
  enabled: form.field("autoCleanupEnabled"),
1486
1520
  maxAgeDays: form.field("autoCleanupMaxAgeDays")
1487
1521
  }))
1488
1522
  };
1489
1523
  }
1490
- function SettingsCleanupCard(props) {
1491
- const { t, useCleanupCard } = props;
1492
- const state = useCleanupCard((snapshot) => snapshot);
1524
+ function SettingsCard(props) {
1525
+ const { t, useSettingsCard } = props;
1526
+ const state = useSettingsCard((snapshot) => snapshot);
1493
1527
  if (props.view === "summary") return null;
1494
1528
  const disabled = !state.writable || state.saving;
1495
- const enabled = state.enabled.text === "true";
1529
+ const checkpointing = state.checkpointingEnabled.text === "true";
1530
+ const cleanupEnabled = state.enabled.text === "true";
1496
1531
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
1497
1532
  import_dsh_client_ui_primitives.SettingsForm,
1498
1533
  {
@@ -1501,13 +1536,31 @@ function SettingsCleanupCard(props) {
1501
1536
  onSave: props.save,
1502
1537
  onDiscard: props.discard,
1503
1538
  children: [
1504
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "dsh-rewind-cleanup-permission", children: [
1505
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "dsh-rewind-cleanup-toggle-row", children: [
1506
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "dsh-rewind-cleanup-toggle-label", children: t("cleanup.auto") }),
1539
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "dsh-rewind-settings-section", children: [
1540
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "dsh-rewind-settings-toggle-row", children: [
1541
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "dsh-rewind-settings-toggle-label", children: t("backup.title") }),
1542
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1543
+ import_dsh_client_ui_primitives.Switch,
1544
+ {
1545
+ checked: checkpointing,
1546
+ label: t("backup.title"),
1547
+ disabled,
1548
+ onChange: (next) => {
1549
+ props.edit("fileCheckpointingEnabled", String(next));
1550
+ }
1551
+ }
1552
+ )
1553
+ ] }),
1554
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "dsh-rewind-settings-hint", children: t(checkpointing ? "backup.on" : "backup.off") })
1555
+ ] }),
1556
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "dsh-rewind-settings-divider" }),
1557
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "dsh-rewind-settings-section", children: [
1558
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "dsh-rewind-settings-toggle-row", children: [
1559
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "dsh-rewind-settings-toggle-label", children: t("cleanup.auto") }),
1507
1560
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1508
1561
  import_dsh_client_ui_primitives.Switch,
1509
1562
  {
1510
- checked: enabled,
1563
+ checked: cleanupEnabled,
1511
1564
  label: t("cleanup.auto"),
1512
1565
  disabled,
1513
1566
  onChange: (next) => {
@@ -1516,9 +1569,9 @@ function SettingsCleanupCard(props) {
1516
1569
  }
1517
1570
  )
1518
1571
  ] }),
1519
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "dsh-rewind-cleanup-hint", children: t(enabled ? "cleanup.auto.on" : "cleanup.auto.off") })
1572
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "dsh-rewind-settings-hint", children: t(cleanupEnabled ? "cleanup.auto.on" : "cleanup.auto.off") })
1520
1573
  ] }),
1521
- enabled ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1574
+ cleanupEnabled ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1522
1575
  import_dsh_client_ui_primitives.SettingsValueField,
1523
1576
  {
1524
1577
  id: "dsh-rewind-cleanup-maxage",
@@ -1668,20 +1721,20 @@ function apply(ctx) {
1668
1721
  createRewindBridge({ sessionOf, chatOf, isMainViewSession, watchChat, setComposerText, restoreImages, t, subscribeLocale })
1669
1722
  ));
1670
1723
  try {
1671
- const { store, form } = cleanupForm(
1672
- ctx.configForms.get(CLEANUP_ENTRY_ID)
1724
+ const { store, form } = settingsForm(
1725
+ ctx.configForms.get(SETTINGS_ENTRY_ID)
1673
1726
  );
1674
1727
  ctx.effect(() => () => {
1675
1728
  form.dispose();
1676
- }, "dsh-rewind cleanup form");
1729
+ }, "dsh-rewind settings form");
1677
1730
  yield slots.inject("plugins.bundle.config", () => slots.register(
1678
1731
  {
1679
1732
  name: "plugins.bundle.config",
1680
1733
  key: PLUGIN_PACKAGE,
1681
1734
  locale: NS2,
1682
- inject: () => ({ hooks: { cleanupCard: store }, ...form.actions() })
1735
+ inject: () => ({ hooks: { settingsCard: store }, ...form.actions() })
1683
1736
  },
1684
- SettingsCleanupCard
1737
+ SettingsCard
1685
1738
  ));
1686
1739
  } catch (error) {
1687
1740
  rewindLog.error("settings", "settings card register failed", error);
package/lib/index.js CHANGED
@@ -14,6 +14,7 @@ var en = {
14
14
  "plan.restore": "restore {path}",
15
15
  "plan.delete": "delete {path}",
16
16
  "plan.noChanges": "No restorable changes after the target.",
17
+ "plan.backupOff": "Snapshot backups are off; this restore may be incomplete.",
17
18
  "plan.skip": " not restored: {path} ({reason})",
18
19
  "skipReason.oversized": "over the {limit} backup limit, so the recorded state predates untracked edits",
19
20
  "uncovered.header": "{count} tracked file(s) exceed the {limit} backup limit and are not covered:",
@@ -30,6 +31,7 @@ var en = {
30
31
  "delete.count": "deleted {count} file(s)",
31
32
  "skip.count": "skipped {count} path(s)",
32
33
  "noRestorable": "; no restorable write-class changes after the target",
34
+ "backupOff": "snapshot backups are off, so the restore may be incomplete",
33
35
  "storeUnsupported": "file restore unavailable: these snapshots use a newer store format (v{version}) than this plugin understands; nothing was changed. Update the plugin or clear this session's snapshots.",
34
36
  "success": "Withdrawn seq {targetSeq} and everything after it (conversation returned to earlier){restore}.",
35
37
  "noUserMessages": "This session has no rewindable user messages yet.",
@@ -65,6 +67,7 @@ var zh = {
65
67
  "plan.restore": "\u8FD8\u539F {path}",
66
68
  "plan.delete": "\u5220\u9664 {path}",
67
69
  "plan.noChanges": "\u76EE\u6807\u4E4B\u540E\u6CA1\u6709\u9700\u8981\u8FD8\u539F\u7684\u53D8\u66F4\u3002",
70
+ "plan.backupOff": "\u5FEB\u7167\u5907\u4EFD\u5DF2\u5173\u95ED\uFF1B\u672C\u6B21\u8FD8\u539F\u53EF\u80FD\u4E0D\u5145\u5206\u3002",
68
71
  "plan.skip": " \u672A\u8FD8\u539F\uFF1A{path}\uFF08{reason}\uFF09",
69
72
  "skipReason.oversized": "\u5DF2\u8D85\u8FC7 {limit} \u7684\u5907\u4EFD\u4E0A\u9650\uFF0C\u8BB0\u5F55\u7684\u72B6\u6001\u65E9\u4E8E\u672A\u88AB\u8DDF\u8E2A\u7684\u4FEE\u6539",
70
73
  "uncovered.header": "{count} \u4E2A\u5DF2\u8DDF\u8E2A\u6587\u4EF6\u8D85\u8FC7 {limit} \u7684\u5907\u4EFD\u4E0A\u9650\uFF0C\u672A\u7EB3\u5165\u8BB0\u5F55\uFF1A",
@@ -81,6 +84,7 @@ var zh = {
81
84
  "delete.count": "\u5220\u9664 {count} \u4E2A\u6587\u4EF6",
82
85
  "skip.count": "\u8DF3\u8FC7 {count} \u4E2A\u8DEF\u5F84",
83
86
  "noRestorable": "\uFF1B\u76EE\u6807\u4E4B\u540E\u6CA1\u6709\u53EF\u8FD8\u539F\u7684\u5199\u7C7B\u53D8\u66F4",
87
+ "backupOff": "\u5FEB\u7167\u5907\u4EFD\u5DF2\u5173\u95ED\uFF0C\u8FD8\u539F\u53EF\u80FD\u4E0D\u5145\u5206",
84
88
  "storeUnsupported": "\u6587\u4EF6\u8FD8\u539F\u4E0D\u53EF\u7528\uFF1A\u8FD9\u4E9B\u5FEB\u7167\u4F7F\u7528\u4E86\u6BD4\u672C\u63D2\u4EF6\u66F4\u65B0\u7684\u5B58\u50A8\u683C\u5F0F\uFF08v{version}\uFF09\uFF1B\u5DE5\u4F5C\u533A\u672A\u505A\u4EFB\u4F55\u6539\u52A8\u3002\u8BF7\u66F4\u65B0\u63D2\u4EF6\uFF0C\u6216\u6E05\u9664\u8BE5\u4F1A\u8BDD\u7684\u5FEB\u7167\u3002",
85
89
  "success": "\u5DF2\u64A4\u56DE seq {targetSeq} \u53CA\u4E4B\u540E\u5185\u5BB9\uFF08\u5BF9\u8BDD\u5DF2\u56DE\u5230\u6B64\u524D\uFF09{restore}\u3002",
86
90
  "noUserMessages": "\u5F53\u524D\u4F1A\u8BDD\u8FD8\u6CA1\u6709\u53EF\u56DE\u9000\u7684\u7528\u6237\u6D88\u606F\u3002",
@@ -2315,6 +2319,7 @@ function shouldRunAutoSweep(lastAtMs, nowMs, intervalMs = AUTO_SWEEP_INTERVAL_MS
2315
2319
  var name = "dsh-rewind";
2316
2320
  var inject = ["commands", "tools", "settings"];
2317
2321
  var ENGINE_COMMAND = "rewind-plugin";
2322
+ var DEFAULT_FILE_CHECKPOINTING_ENABLED = true;
2318
2323
  var Config = z.object({
2319
2324
  snapshotDir: z.string().required(false),
2320
2325
  dshHome: z.string().required(false),
@@ -2322,9 +2327,13 @@ var Config = z.object({
2322
2327
  maxFileBytes: z.number().step(1).min(0).default(DEFAULT_MAX_FILE_BYTES),
2323
2328
  maxAnchorGroups: z.number().step(1).min(1).default(MAX_ANCHOR_GROUPS),
2324
2329
  autoSweepIntervalMs: z.number().step(1).min(1).default(AUTO_SWEEP_INTERVAL_MS),
2330
+ fileCheckpointingEnabled: z.boolean().default(DEFAULT_FILE_CHECKPOINTING_ENABLED).volatile(),
2325
2331
  autoCleanupEnabled: z.boolean().default(DEFAULT_CLEANUP_CONFIG.enabled).volatile(),
2326
2332
  autoCleanupMaxAgeDays: z.number().step(1).min(1).default(DEFAULT_CLEANUP_CONFIG.maxAgeDays).volatile()
2327
2333
  });
2334
+ function isFileCheckpointingEnabled(config) {
2335
+ return config?.fileCheckpointingEnabled?.get() !== false;
2336
+ }
2328
2337
  var TRACKED_TOOLS = /* @__PURE__ */ new Set(["write", "edit"]);
2329
2338
  function isSubagentSession(session) {
2330
2339
  const header = session.header;
@@ -2428,11 +2437,15 @@ async function captureBefore(fs, workingDirectory, store, exec, pending) {
2428
2437
  }
2429
2438
  pending.set(key, { path: target.displayPath, backup });
2430
2439
  }
2431
- async function commitEntry(store, pending, anchorCache, trackedBySession, exec, result) {
2440
+ async function commitEntry(store, pending, anchorCache, trackedBySession, exec, result, checkpointingEnabled) {
2432
2441
  const key = `${exec.agent?.id ?? "anon"}:${exec.callId}`;
2433
2442
  const capture = pending.get(key);
2434
2443
  if (capture === void 0) return;
2435
2444
  pending.delete(key);
2445
+ if (!checkpointingEnabled) {
2446
+ await discardCapture(capture);
2447
+ return;
2448
+ }
2436
2449
  if (result.isError) {
2437
2450
  await discardCapture(capture);
2438
2451
  return;
@@ -2476,7 +2489,7 @@ function formatBytes(bytes) {
2476
2489
  if (bytes >= 1024) return `${Math.round(bytes / 1024)} KiB`;
2477
2490
  return `${bytes} B`;
2478
2491
  }
2479
- function formatPlan(plan, files, uncovered = [], maxFileBytes = 0) {
2492
+ function formatPlan(plan, files, uncovered = [], maxFileBytes = 0, checkpointingEnabled = true) {
2480
2493
  const lines = [
2481
2494
  t("plan.rewinding", { targetSeq: plan.targetSeq, count: plan.shadowedSeqs.length })
2482
2495
  ];
@@ -2493,10 +2506,12 @@ function formatPlan(plan, files, uncovered = [], maxFileBytes = 0) {
2493
2506
  for (const file of uncovered.slice(0, 5)) lines.push(` ${file.path}`);
2494
2507
  if (uncovered.length > 5) lines.push(" \u2026");
2495
2508
  }
2509
+ if (!checkpointingEnabled) lines.push(t("plan.backupOff"));
2496
2510
  lines.push(`impact=${files.length}`);
2497
2511
  for (const file of files) {
2498
2512
  lines.push(`${file.action}:${file.path}`);
2499
2513
  }
2514
+ if (!checkpointingEnabled) lines.push("backup=off");
2500
2515
  return lines.join("\n");
2501
2516
  }
2502
2517
  function resolveOrError(events, surface, raw) {
@@ -2567,7 +2582,7 @@ function dropPendingSteering(agent) {
2567
2582
  agent.inbox.remove(message.id);
2568
2583
  }
2569
2584
  }
2570
- async function executeRewind(ctx, store, fs, invocation, rawTarget, mode, inflight) {
2585
+ async function executeRewind(ctx, store, fs, invocation, rawTarget, mode, inflight, checkpointingEnabled) {
2571
2586
  const { agent } = invocation;
2572
2587
  const sessionId = agent.session.id;
2573
2588
  if (inflight.has(sessionId)) {
@@ -2626,6 +2641,7 @@ async function executeRewind(ctx, store, fs, invocation, rawTarget, mode, inflig
2626
2641
  const skippedLines = renderSkipped(outcome, capped, store.fileCapBytes);
2627
2642
  if (skippedLines !== "") parts.push(skippedLines);
2628
2643
  }
2644
+ if (!checkpointingEnabled) parts.push(t("backupOff"));
2629
2645
  restore = parts.length > 0 ? `${t("sep.clause")}${parts.join(t("sep.list"))}` : t("noRestorable");
2630
2646
  restore += renderFailures(outcome.failed);
2631
2647
  }
@@ -2651,7 +2667,7 @@ function rewindErrorResult(error) {
2651
2667
  }
2652
2668
  throw error;
2653
2669
  }
2654
- async function handleRewind(ctx, store, fs, invocation, inflight) {
2670
+ async function handleRewind(ctx, store, fs, invocation, inflight, checkpointingEnabled) {
2655
2671
  const session = invocation.agent.session;
2656
2672
  const input = invocation.rawInput.trim();
2657
2673
  if (input === "") {
@@ -2659,7 +2675,7 @@ async function handleRewind(ctx, store, fs, invocation, inflight) {
2659
2675
  if (candidates.length === 0) {
2660
2676
  return { kind: "error", text: t("noUserMessages") };
2661
2677
  }
2662
- return executeRewind(ctx, store, fs, invocation, `@${candidates[0].seq}`, "chat", inflight);
2678
+ return executeRewind(ctx, store, fs, invocation, `@${candidates[0].seq}`, "chat", inflight, checkpointingEnabled);
2663
2679
  }
2664
2680
  const parts = input.split(/\s+/);
2665
2681
  if (parts[0] === "preview") {
@@ -2679,7 +2695,7 @@ async function handleRewind(ctx, store, fs, invocation, inflight) {
2679
2695
  return { kind: "error", text: t("storeUnsupported", { version: await store.readStoreVersion(session.id) ?? 0 }) };
2680
2696
  }
2681
2697
  const uncovered = await store.uncoveredPaths(session.id).catch(() => []);
2682
- return { kind: "success", text: formatPlan(plan, impacts, uncovered, store.fileCapBytes) };
2698
+ return { kind: "success", text: formatPlan(plan, impacts, uncovered, store.fileCapBytes, checkpointingEnabled) };
2683
2699
  }
2684
2700
  if (parts[0] === "__candidates") {
2685
2701
  const candidates = listRewindCandidates(session.snapshotEvents(), session.surface.nodes);
@@ -2691,7 +2707,7 @@ async function handleRewind(ctx, store, fs, invocation, inflight) {
2691
2707
  return { kind: "error", text: t("engine.rejected") };
2692
2708
  }
2693
2709
  if (mode === void 0) return { kind: "error", text: t("engine.rejected") };
2694
- return executeRewind(ctx, store, fs, invocation, target, mode, inflight);
2710
+ return executeRewind(ctx, store, fs, invocation, target, mode, inflight, checkpointingEnabled);
2695
2711
  }
2696
2712
  var autoSweepChecked = false;
2697
2713
  async function maybeRunAutoCleanup(ctx, store, sessionId, dshHome, sweepIntervalMs, signal) {
@@ -2825,7 +2841,7 @@ var INCLUDE_ENTRY_PREFIX = "include:";
2825
2841
  function rewindConfigKey(ctx) {
2826
2842
  return ctx.fiber?.entry?.id;
2827
2843
  }
2828
- function cleanupConfigKey(entryId) {
2844
+ function profileEntryKey(entryId) {
2829
2845
  if (entryId === void 0) return void 0;
2830
2846
  return entryId.startsWith(INCLUDE_ENTRY_PREFIX) ? entryId.slice(INCLUDE_ENTRY_PREFIX.length) : entryId;
2831
2847
  }
@@ -2847,7 +2863,7 @@ function apply(ctx, config) {
2847
2863
  maxFileBytes: config?.maxFileBytes,
2848
2864
  maxAnchorGroups: config?.maxAnchorGroups
2849
2865
  });
2850
- const configKey = cleanupConfigKey(rewindConfigKey(ctx));
2866
+ const configKey = profileEntryKey(rewindConfigKey(ctx));
2851
2867
  if (config !== void 0 && configKey !== void 0) {
2852
2868
  const writer = {
2853
2869
  entryId: configKey,
@@ -2872,10 +2888,10 @@ function apply(ctx, config) {
2872
2888
  return handler(invocation);
2873
2889
  };
2874
2890
  const rewindHandler = withHostLocale(
2875
- (invocation) => handleRewind(ctx, store, fsService, invocation, inflight)
2891
+ (invocation) => handleRewind(ctx, store, fsService, invocation, inflight, isFileCheckpointingEnabled(config))
2876
2892
  );
2877
2893
  const engineHandler = withHostLocale(
2878
- (invocation) => invocation.rawInput.trim() === "" ? Promise.resolve({ kind: "error", text: t("engine.rejected") }) : handleRewind(ctx, store, fsService, invocation, inflight)
2894
+ (invocation) => invocation.rawInput.trim() === "" ? Promise.resolve({ kind: "error", text: t("engine.rejected") }) : handleRewind(ctx, store, fsService, invocation, inflight, isFileCheckpointingEnabled(config))
2879
2895
  );
2880
2896
  try {
2881
2897
  yield ctx.commands.register({
@@ -2948,6 +2964,7 @@ function apply(ctx, config) {
2948
2964
  if (lifecycle.signal.aborted) return;
2949
2965
  const sessionId = session.id;
2950
2966
  void maybeRunAutoCleanup(ctx, store, sessionId, dshHome, config?.autoSweepIntervalMs, lifecycle.signal);
2967
+ if (!isFileCheckpointingEnabled(config)) return;
2951
2968
  let tracked = trackedBySession.get(sessionId);
2952
2969
  if (tracked === void 0) {
2953
2970
  tracked = await store.trackedPaths(sessionId);
@@ -2970,7 +2987,9 @@ function apply(ctx, config) {
2970
2987
  }, "dsh-rewind fs capture");
2971
2988
  scope.on("tools/execute", async (exec, next) => {
2972
2989
  try {
2973
- await captureBefore(fs, workingDirectory, store, exec, pending);
2990
+ if (isFileCheckpointingEnabled(config)) {
2991
+ await captureBefore(fs, workingDirectory, store, exec, pending);
2992
+ }
2974
2993
  } catch (error) {
2975
2994
  ctx.logger.warn(`[dsh-rewind] before-capture failed for ${exec.name}: ${error instanceof Error ? error.message : String(error)}`);
2976
2995
  }
@@ -2982,7 +3001,7 @@ function apply(ctx, config) {
2982
3001
  if (session !== void 0 && !isSubagentSession(session)) {
2983
3002
  void maybeRunAutoCleanup(ctx, store, session.id, dshHome, config?.autoSweepIntervalMs, lifecycle.signal);
2984
3003
  }
2985
- await commitEntry(store, pending, anchorCache, trackedBySession, exec, result);
3004
+ await commitEntry(store, pending, anchorCache, trackedBySession, exec, result, isFileCheckpointingEnabled(config));
2986
3005
  } catch (error) {
2987
3006
  ctx.logger.warn(`[dsh-rewind] checkpoint commit failed for ${exec.name}: ${error instanceof Error ? error.message : String(error)}`);
2988
3007
  }
@@ -2999,10 +3018,11 @@ function apply(ctx, config) {
2999
3018
  }
3000
3019
  export {
3001
3020
  Config,
3021
+ DEFAULT_FILE_CHECKPOINTING_ENABLED,
3002
3022
  ENGINE_COMMAND,
3003
3023
  SnapshotStore,
3004
3024
  apply,
3005
- cleanupConfigKey,
3006
3025
  inject,
3007
- name
3026
+ name,
3027
+ profileEntryKey
3008
3028
  };
@@ -144,6 +144,8 @@ export declare function isExecutedRewindCommand(node: CommandNode, seq: number):
144
144
  * always-show so a working option is never hidden on a failed probe.
145
145
  */
146
146
  export declare function hasFileImpact(text: string | undefined): boolean;
147
+ /** Whether the preview reports that new file checkpoints are disabled. */
148
+ export declare function hasBackupDisabled(text: string | undefined): boolean;
147
149
  /**
148
150
  * True when an engine command node is the internal candidate-list probe
149
151
  * (`__candidates`) the popupSelect runs to fetch the FULL candidate
@@ -17,6 +17,7 @@ export declare const zh: {
17
17
  'popover.both.hint': string;
18
18
  'popover.checking': string;
19
19
  'popover.noChanges': string;
20
+ 'popover.backupDisabled': string;
20
21
  'popover.cancel': string;
21
22
  'popover.impact.loading': string;
22
23
  'popover.impact.failed': string;
@@ -25,6 +26,9 @@ export declare const zh: {
25
26
  'popover.impact.delete': string;
26
27
  'popover.confirm': string;
27
28
  'popover.back': string;
29
+ 'backup.title': string;
30
+ 'backup.on': string;
31
+ 'backup.off': string;
28
32
  'cleanup.auto': string;
29
33
  'cleanup.auto.on': string;
30
34
  'cleanup.auto.off': string;
@@ -65,6 +69,7 @@ export declare const en: {
65
69
  'popover.both.hint': string;
66
70
  'popover.checking': string;
67
71
  'popover.noChanges': string;
72
+ 'popover.backupDisabled': string;
68
73
  'popover.cancel': string;
69
74
  'popover.impact.loading': string;
70
75
  'popover.impact.failed': string;
@@ -73,6 +78,9 @@ export declare const en: {
73
78
  'popover.impact.delete': string;
74
79
  'popover.confirm': string;
75
80
  'popover.back': string;
81
+ 'backup.title': string;
82
+ 'backup.on': string;
83
+ 'backup.off': string;
76
84
  'cleanup.auto': string;
77
85
  'cleanup.auto.on': string;
78
86
  'cleanup.auto.off': string;
@@ -1,7 +1,7 @@
1
1
  /**
2
- * This bundle's configuration form for the snapshot-cleanup policy, rendered
3
- * on its Plugins-page card (`plugins.bundle.config`, keyed by the package
4
- * name).
2
+ * This bundle's configuration form for the file-checkpointing and snapshot
3
+ * cleanup switches, rendered on its Plugins-page card (`plugins.bundle.config`,
4
+ * keyed by the package name).
5
5
  *
6
6
  * The staging model, the field controls, and the form frame are the harness's
7
7
  * own (`SettingsFormModel` + `SettingsValueField` + `SettingsForm`); the one
@@ -31,28 +31,30 @@ export declare const CARD_LOCALE_NS = "rewind";
31
31
  * The plugin entry whose form this is: the bundle's package name, which is the
32
32
  * profile entry id the host addresses the configuration by.
33
33
  */
34
- export declare const CLEANUP_ENTRY_ID = "dsh-rewind-plugin";
34
+ export declare const SETTINGS_ENTRY_ID = "dsh-rewind-plugin";
35
35
  /** The default max age; an empty numeric draft means "use this". */
36
36
  export declare const DEFAULT_MAX_AGE_DAYS = 30;
37
- /** The two editable knobs, exactly as the entry's configuration document names them. */
38
- export interface CleanupPolicy {
37
+ /** The editable knobs, exactly as the entry's configuration document names them. */
38
+ export interface SettingsPolicy {
39
+ readonly fileCheckpointingEnabled: boolean;
39
40
  readonly autoCleanupEnabled: boolean;
40
41
  readonly autoCleanupMaxAgeDays: number;
41
42
  }
42
43
  /**
43
44
  * One form snapshot as this card reads it: the shared form frame's own state
44
- * (`SettingsFormShell`) plus the two field drafts.
45
+ * (`SettingsFormShell`) plus the three field drafts.
45
46
  */
46
- export interface CleanupCardSnapshot extends SettingsFormShell {
47
+ export interface SettingsCardSnapshot extends SettingsFormShell {
48
+ readonly checkpointingEnabled: SettingsFieldState;
47
49
  readonly enabled: SettingsFieldState;
48
50
  readonly maxAgeDays: SettingsFieldState;
49
51
  }
50
52
  /**
51
53
  * The bound snapshot store the slot renderer rebinds as the card's
52
- * `useCleanupCard` selector hook.
54
+ * `useSettingsCard` selector hook.
53
55
  */
54
- export interface SettingsCleanupStore {
55
- getSnapshot(): CleanupCardSnapshot;
56
+ export interface SettingsCardStore {
57
+ getSnapshot(): SettingsCardSnapshot;
56
58
  subscribe(listener: () => void): () => void;
57
59
  }
58
60
  /**
@@ -60,7 +62,7 @@ export interface SettingsCleanupStore {
60
62
  * `SettingsFormScope` read/write face, declared structurally so the card never
61
63
  * couples to an internal declaration file's path.
62
64
  */
63
- export interface CleanupFormScope<T> {
65
+ export interface SettingsFormScope<T> {
64
66
  getSnapshot(): {
65
67
  readonly status: 'loading' | 'ready' | 'unavailable';
66
68
  readonly value: T | undefined;
@@ -84,22 +86,22 @@ export interface CleanupFormScope<T> {
84
86
  * runtime owner share (`view`), the locale `t` seat, and the injected face with
85
87
  * its `hooks` compartment rebound as a `use<Name>` selector hook.
86
88
  */
87
- export type SettingsCleanupCardProps = PropsRuntime<'plugins.bundle.config'> & PropsLocale<typeof CARD_LOCALE_NS> & InjectFace<SettingsCleanupFace>;
89
+ export type SettingsCardProps = PropsRuntime<'plugins.bundle.config'> & PropsLocale<typeof CARD_LOCALE_NS> & InjectFace<SettingsCardFace>;
88
90
  /**
89
91
  * Every dictionary key this card reads: the switch row, the day field, and the
90
92
  * five labels the shared form frame renders.
91
93
  */
92
- export type CleanupLabelKey = 'cleanup.auto' | 'cleanup.auto.on' | 'cleanup.auto.off' | 'cleanup.maxAge' | 'cleanup.maxAge.hint' | 'cleanup.overridden' | 'cleanup.reset' | 'cleanup.invalid' | 'cleanup.unavailable' | 'cleanup.readonly' | 'cleanup.saveFailed' | 'cleanup.save' | 'cleanup.saving';
94
+ export type SettingsLabelKey = 'backup.title' | 'backup.on' | 'backup.off' | 'cleanup.auto' | 'cleanup.auto.on' | 'cleanup.auto.off' | 'cleanup.maxAge' | 'cleanup.maxAge.hint' | 'cleanup.overridden' | 'cleanup.reset' | 'cleanup.invalid' | 'cleanup.unavailable' | 'cleanup.readonly' | 'cleanup.saveFailed' | 'cleanup.save' | 'cleanup.saving';
93
95
  /** The form frame's copy, read from this plugin's dictionary. */
94
- export declare function formLabels(t: (key: CleanupLabelKey) => string): SettingsFormLabels;
96
+ export declare function formLabels(t: (key: SettingsLabelKey) => string): SettingsFormLabels;
95
97
  /**
96
98
  * Build the staged form over one entry's configuration.
97
99
  * @param scope - the shared per-entry configuration form.
98
100
  * @returns the model plus the card's bound read hook.
99
101
  */
100
- export declare function cleanupForm(scope: CleanupFormScope<CleanupPolicy>): {
101
- form: SettingsFormModel<CleanupPolicy>;
102
- store: import("@deepseek-ai/dsh-client-store").SnapshotStore<CleanupCardSnapshot>;
102
+ export declare function settingsForm(scope: SettingsFormScope<SettingsPolicy>): {
103
+ form: SettingsFormModel<SettingsPolicy>;
104
+ store: import("@deepseek-ai/dsh-client-store").SnapshotStore<SettingsCardSnapshot>;
103
105
  };
104
106
  /**
105
107
  * The bundle's configuration form; `summary` renders nothing (the page only
@@ -107,10 +109,10 @@ export declare function cleanupForm(scope: CleanupFormScope<CleanupPolicy>): {
107
109
  * @param props - the view, the card's read hook, locale seat, and form actions.
108
110
  * @returns the form element, or null for the summary view.
109
111
  */
110
- export declare function SettingsCleanupCard(props: SettingsCleanupCardProps): import("react").JSX.Element | null;
112
+ export declare function SettingsCard(props: SettingsCardProps): import("react").JSX.Element | null;
111
113
  /** The face the slot registration injects into the card. */
112
- export interface SettingsCleanupFace extends SettingsFormActions {
114
+ export interface SettingsCardFace extends SettingsFormActions {
113
115
  readonly hooks: {
114
- readonly cleanupCard: SettingsCleanupStore;
116
+ readonly settingsCard: SettingsCardStore;
115
117
  };
116
118
  }
@@ -15,6 +15,7 @@ export declare const CLASS: {
15
15
  readonly popoverOptionLabel: 'dsh-rewind-popover-option-label';
16
16
  readonly popoverOptionHint: 'dsh-rewind-popover-option-hint';
17
17
  readonly popoverImpact: 'dsh-rewind-popover-impact';
18
+ readonly popoverNote: 'dsh-rewind-popover-note';
18
19
  readonly popoverActions: 'dsh-rewind-popover-actions';
19
20
  readonly popoverPrimary: 'dsh-rewind-popover-primary';
20
21
  readonly popoverGhost: 'dsh-rewind-popover-ghost';
@@ -31,4 +32,4 @@ export declare const CLASS: {
31
32
  */
32
33
  export declare const REWIND_ICON_SVG: string;
33
34
  /** One injected stylesheet (scoped under `.dsh-rewind-*`). */
34
- export declare const STYLE = "\n.dsh-rewind-btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n /* The neighbouring action buttons scale by this delta too. */\n width: calc(28px + var(--dsh-content-font-delta, 0px));\n height: calc(28px + var(--dsh-content-font-delta, 0px));\n padding: 6px;\n border: none;\n border-radius: var(--dsw-radius-sm, 8px);\n background: transparent;\n color: var(--dsw-alias-label-tertiary);\n cursor: pointer;\n}\n.dsh-rewind-btn svg {\n width: calc(15px + var(--dsh-content-font-delta, 0px));\n height: calc(15px + var(--dsh-content-font-delta, 0px));\n}\n.dsh-rewind-btn:hover {\n 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/* Arrow navigation moves real focus, so the focused row carries the pointer's\n fill and the browser ring is suppressed: in dark mode the UA two-tone ring\n draws its dark half against the filled row and reads as a residue edge (the\n harness Menu suppresses it the same way). */\n.dsh-rewind-popover-option:focus-visible:not(:disabled) {\n background: var(--dsw-alias-interactive-bg-hover);\n outline: none;\n}\n.dsh-rewind-popover-option:disabled {\n opacity: 0.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 configuration form ---- */\n/* The Plugins page draws the bundle's title, icon, crumb, description, and the\n shared settings form frame (labels, save, read-only and failure lines); this\n styles only the one control the card owns: the auto-cleanup toggle row. */\n.dsh-rewind-cleanup-permission {\n display: grid;\n gap: 6px;\n padding: 12px 0;\n}\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-hint {\n margin: 0;\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-label-tertiary);\n}\n";
35
+ export declare const STYLE = "\n.dsh-rewind-btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n /* The neighbouring action buttons scale by this delta too. */\n width: calc(28px + var(--dsh-content-font-delta, 0px));\n height: calc(28px + var(--dsh-content-font-delta, 0px));\n padding: 6px;\n border: none;\n border-radius: var(--dsw-radius-sm, 8px);\n background: transparent;\n color: var(--dsw-alias-label-tertiary);\n cursor: pointer;\n}\n.dsh-rewind-btn svg {\n width: calc(15px + var(--dsh-content-font-delta, 0px));\n height: calc(15px + var(--dsh-content-font-delta, 0px));\n}\n.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/* Arrow navigation moves real focus, so the focused row carries the pointer's\n fill and the browser ring is suppressed: in dark mode the UA two-tone ring\n draws its dark half against the filled row and reads as a residue edge (the\n harness Menu suppresses it the same way). */\n.dsh-rewind-popover-option:focus-visible:not(:disabled) {\n background: var(--dsw-alias-interactive-bg-hover);\n outline: none;\n}\n.dsh-rewind-popover-option:disabled {\n opacity: 0.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-note {\n margin-top: 6px;\n font-size: 11px;\n line-height: 15px;\n color: var(--dsw-alias-label-tertiary);\n}\n.dsh-rewind-popover-actions {\n display: flex;\n justify-content: flex-end;\n gap: 8px;\n}\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/* ---- Plugin configuration form ---- */\n/* Only the card's own controls: the two switch rows, their hints, and the divider. */\n.dsh-rewind-settings-divider {\n border-top: 1px solid var(--dsw-alias-border-l2);\n}\n.dsh-rewind-settings-section {\n display: grid;\n gap: 6px;\n padding: 12px 0;\n}\n.dsh-rewind-settings-toggle-row {\n display: flex;\n align-items: flex-start;\n justify-content: space-between;\n gap: 16px;\n font-size: 13px;\n line-height: 1.5;\n color: var(--dsw-alias-label-primary);\n}\n.dsh-rewind-settings-toggle-label {\n flex: 1;\n min-width: 0;\n}\n.dsh-rewind-settings-hint {\n margin: 0;\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-label-tertiary);\n}\n";
@@ -8,8 +8,9 @@
8
8
  * target with itself, cutting only the model-visible surface
9
9
  * (`docs/architecture.md`).
10
10
  *
11
- * File restore (mode `both`) follows Claude Code's checkpointing: the plugin
12
- * backs up each tracked write-class edit BEFORE it happens (at the
11
+ * File restore (mode `both`) follows Claude Code's checkpointing: unless
12
+ * `fileCheckpointingEnabled` is off, the plugin backs up each tracked
13
+ * write-class edit BEFORE it happens (at the
13
14
  * `tools/execute` around-dispatch stage, so an approval short-circuit cannot
14
15
  * skip the capture and a denied call never records), commits the backup under
15
16
  * the turn's anchor message seq at `tools/post-execute`, and a rewind to
@@ -22,7 +23,7 @@
22
23
  *
23
24
  * @module dsh-rewind
24
25
  */
25
- import type { Context } from '@deepseek-ai/cordis';
26
+ import type { Context, Volatile } from '@deepseek-ai/cordis';
26
27
  import z from '@deepseek-ai/schemastery';
27
28
  import { type CleanupSettings } from './snapshot-cleanup.ts';
28
29
  export { SnapshotStore } from './snapshot.ts';
@@ -50,18 +51,22 @@ export interface RewindConfig {
50
51
  /** Automatic snapshot-sweep cadence in ms. Default {@link AUTO_SWEEP_INTERVAL_MS}. */
51
52
  readonly autoSweepIntervalMs?: number;
52
53
  }
54
+ /** The default for {@link FileCheckpointingSettings.fileCheckpointingEnabled}. */
55
+ export declare const DEFAULT_FILE_CHECKPOINTING_ENABLED = true;
53
56
  /**
54
- * The plugin's full configuration: the ordinary store options plus the
55
- * user-editable cleanup policy. The two policy fields are `.volatile()`, so the
56
- * host resolves them into live references before `apply` and the Plugins page
57
- * exposes exactly them.
58
- *
59
- * The policy fields are named for WHAT they gate (the automatic sweep; the
60
- * `/snapshot-auto-cleanup` command's manual `run` ignores the switch), because
61
- * this entry's own `disabled:` flag sits beside them in the same document and a
62
- * bare `enabled` reads like the plugin switch.
57
+ * The live file-checkpointing switch (Claude Code's
58
+ * `fileCheckpointingEnabled`): whether NEW before-backups may be recorded.
59
+ */
60
+ export interface FileCheckpointingSettings {
61
+ readonly fileCheckpointingEnabled: Volatile<boolean>;
62
+ }
63
+ /**
64
+ * The plugin's full configuration: store options plus the three user-editable
65
+ * policy fields. `.volatile()` resolves them into live references for the
66
+ * Plugins page; the names say what they gate, because a bare `enabled` would
67
+ * read like the entry's own `disabled:` flag.
63
68
  */
64
- export interface Config extends RewindConfig, CleanupSettings {
69
+ export interface Config extends RewindConfig, CleanupSettings, FileCheckpointingSettings {
65
70
  }
66
71
  /** The `Config` schema the host resolves and projects as this entry's form. */
67
72
  export declare const Config: z<Schemastery.ObjectS<NoInfer<{
@@ -71,6 +76,7 @@ export declare const Config: z<Schemastery.ObjectS<NoInfer<{
71
76
  maxFileBytes: z<number, number, "defined">;
72
77
  maxAnchorGroups: z<number, number, "defined">;
73
78
  autoSweepIntervalMs: z<number, number, "defined">;
79
+ fileCheckpointingEnabled: z<boolean, boolean, "volatile-defined">;
74
80
  autoCleanupEnabled: z<boolean, boolean, "volatile-defined">;
75
81
  autoCleanupMaxAgeDays: z<number, number, "volatile-defined">;
76
82
  }>>, Schemastery.ObjectT<NoInfer<{
@@ -80,6 +86,7 @@ export declare const Config: z<Schemastery.ObjectS<NoInfer<{
80
86
  maxFileBytes: z<number, number, "defined">;
81
87
  maxAnchorGroups: z<number, number, "defined">;
82
88
  autoSweepIntervalMs: z<number, number, "defined">;
89
+ fileCheckpointingEnabled: z<boolean, boolean, "volatile-defined">;
83
90
  autoCleanupEnabled: z<boolean, boolean, "volatile-defined">;
84
91
  autoCleanupMaxAgeDays: z<number, number, "volatile-defined">;
85
92
  }>>, "plain">;
@@ -92,7 +99,7 @@ export declare const Config: z<Schemastery.ObjectS<NoInfer<{
92
99
  * @param entryId - this fiber's Loader entry id, when it has one.
93
100
  * @returns the configuration key, or undefined without an entry.
94
101
  */
95
- export declare function cleanupConfigKey(entryId: string | undefined): string | undefined;
102
+ export declare function profileEntryKey(entryId: string | undefined): string | undefined;
96
103
  /**
97
104
  * Register the `/rewind` command and the checkpoint pipeline (before-capture
98
105
  * at `tools/execute`, disk commit at `tools/post-execute`).
@@ -113,6 +120,7 @@ export declare function cleanupConfigKey(entryId: string | undefined): string |
113
120
  *
114
121
  * @param ctx - context carrying `commands`, `tools`, and an optional `fs`.
115
122
  * @param config - resolved plugin config: the store options (`snapshotDir`,
116
- * `dshHome`, `dedup`) plus the live cleanup policy (`enabled`, `maxAgeDays`).
123
+ * `dshHome`, `dedup`) plus the live policy (`fileCheckpointingEnabled`, cleanup
124
+ * `enabled`/`maxAgeDays`).
117
125
  */
118
126
  export declare function apply(ctx: Context, config?: Config): void;
@@ -50,6 +50,7 @@ export declare const en: {
50
50
  'plan.restore': string;
51
51
  'plan.delete': string;
52
52
  'plan.noChanges': string;
53
+ 'plan.backupOff': string;
53
54
  'plan.skip': string;
54
55
  'skipReason.oversized': string;
55
56
  'uncovered.header': string;
@@ -66,6 +67,7 @@ export declare const en: {
66
67
  'delete.count': string;
67
68
  'skip.count': string;
68
69
  noRestorable: string;
70
+ backupOff: string;
69
71
  storeUnsupported: string;
70
72
  success: string;
71
73
  noUserMessages: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-rewind-plugin",
3
- "version": "0.16.0",
3
+ "version": "0.17.0",
4
4
  "description": "In-window conversation rewind with workspace file restore · 同窗口内对话回退并可还原工作区文件",
5
5
  "icon": "./assets/icon.svg",
6
6
  "keywords": [
@@ -147,42 +147,42 @@
147
147
  "devDependencies": {
148
148
  "@deepseek-ai/cordis": "4.0.5",
149
149
  "@deepseek-ai/cosmokit": "1.8.6",
150
- "@deepseek-ai/dsh-agent": "0.2.1-rc.1",
151
- "@deepseek-ai/dsh-api-session-controller": "0.2.1-rc.1",
152
- "@deepseek-ai/dsh-attachment": "0.2.1-rc.1",
153
- "@deepseek-ai/dsh-client-connection": "0.2.1-rc.1",
154
- "@deepseek-ai/dsh-client-locale": "0.2.1-rc.1",
155
- "@deepseek-ai/dsh-client-store": "0.2.1-rc.1",
156
- "@deepseek-ai/dsh-client-ui-commands": "0.2.1-rc.1",
157
- "@deepseek-ai/dsh-client-ui-conversation": "0.2.1-rc.1",
158
- "@deepseek-ai/dsh-client-ui-input-trigger": "0.2.1-rc.1",
159
- "@deepseek-ai/dsh-client-ui-plugin-manager": "0.2.1-rc.1",
160
- "@deepseek-ai/dsh-client-ui-primitives": "0.2.1-rc.1",
161
- "@deepseek-ai/dsh-client-ui-session": "0.2.1-rc.1",
162
- "@deepseek-ai/dsh-client-ui-settings": "0.2.1-rc.1",
163
- "@deepseek-ai/dsh-client-ui-slots": "0.2.1-rc.1",
164
- "@deepseek-ai/dsh-command-compact": "0.2.1-rc.1",
165
- "@deepseek-ai/dsh-commands": "0.2.1-rc.1",
166
- "@deepseek-ai/dsh-compaction": "0.2.1-rc.1",
167
- "@deepseek-ai/dsh-compaction-basic": "0.2.1-rc.1",
168
- "@deepseek-ai/dsh-compaction-image-offload": "0.2.1-rc.1",
169
- "@deepseek-ai/dsh-fs": "0.2.1-rc.1",
170
- "@deepseek-ai/dsh-goal": "0.2.1-rc.1",
171
- "@deepseek-ai/dsh-home-paths": "0.2.1-rc.1",
172
- "@deepseek-ai/dsh-llm": "0.2.1-rc.1",
173
- "@deepseek-ai/dsh-plan-mode": "0.2.1-rc.1",
174
- "@deepseek-ai/dsh-sandbox": "0.2.1-rc.1",
175
- "@deepseek-ai/dsh-scope": "0.2.1-rc.1",
176
- "@deepseek-ai/dsh-session": "0.2.1-rc.1",
177
- "@deepseek-ai/dsh-session-projection": "0.2.1-rc.1",
178
- "@deepseek-ai/dsh-session-stats": "0.2.1-rc.1",
179
- "@deepseek-ai/dsh-session-title": "0.2.1-rc.1",
180
- "@deepseek-ai/dsh-settings": "0.2.1-rc.1",
181
- "@deepseek-ai/dsh-token-meter": "0.2.1-rc.1",
182
- "@deepseek-ai/dsh-tools": "0.2.1-rc.1",
183
- "@deepseek-ai/dsh-typert-protocol": "0.2.1-rc.1",
184
- "@deepseek-ai/dsh-user-questions": "0.2.1-rc.1",
185
- "@deepseek-ai/dsh-working-directory": "0.2.1-rc.1",
150
+ "@deepseek-ai/dsh-agent": "0.2.1-rc.2",
151
+ "@deepseek-ai/dsh-api-session-controller": "0.2.1-rc.2",
152
+ "@deepseek-ai/dsh-attachment": "0.2.1-rc.2",
153
+ "@deepseek-ai/dsh-client-connection": "0.2.1-rc.2",
154
+ "@deepseek-ai/dsh-client-locale": "0.2.1-rc.2",
155
+ "@deepseek-ai/dsh-client-store": "0.2.1-rc.2",
156
+ "@deepseek-ai/dsh-client-ui-commands": "0.2.1-rc.2",
157
+ "@deepseek-ai/dsh-client-ui-conversation": "0.2.1-rc.2",
158
+ "@deepseek-ai/dsh-client-ui-input-trigger": "0.2.1-rc.2",
159
+ "@deepseek-ai/dsh-client-ui-plugin-manager": "0.2.1-rc.2",
160
+ "@deepseek-ai/dsh-client-ui-primitives": "0.2.1-rc.2",
161
+ "@deepseek-ai/dsh-client-ui-session": "0.2.1-rc.2",
162
+ "@deepseek-ai/dsh-client-ui-settings": "0.2.1-rc.2",
163
+ "@deepseek-ai/dsh-client-ui-slots": "0.2.1-rc.2",
164
+ "@deepseek-ai/dsh-command-compact": "0.2.1-rc.2",
165
+ "@deepseek-ai/dsh-commands": "0.2.1-rc.2",
166
+ "@deepseek-ai/dsh-compaction": "0.2.1-rc.2",
167
+ "@deepseek-ai/dsh-compaction-basic": "0.2.1-rc.2",
168
+ "@deepseek-ai/dsh-compaction-image-offload": "0.2.1-rc.2",
169
+ "@deepseek-ai/dsh-fs": "0.2.1-rc.2",
170
+ "@deepseek-ai/dsh-goal": "0.2.1-rc.2",
171
+ "@deepseek-ai/dsh-home-paths": "0.2.1-rc.2",
172
+ "@deepseek-ai/dsh-llm": "0.2.1-rc.2",
173
+ "@deepseek-ai/dsh-plan-mode": "0.2.1-rc.2",
174
+ "@deepseek-ai/dsh-sandbox": "0.2.1-rc.2",
175
+ "@deepseek-ai/dsh-scope": "0.2.1-rc.2",
176
+ "@deepseek-ai/dsh-session": "0.2.1-rc.2",
177
+ "@deepseek-ai/dsh-session-projection": "0.2.1-rc.2",
178
+ "@deepseek-ai/dsh-session-stats": "0.2.1-rc.2",
179
+ "@deepseek-ai/dsh-session-title": "0.2.1-rc.2",
180
+ "@deepseek-ai/dsh-settings": "0.2.1-rc.2",
181
+ "@deepseek-ai/dsh-token-meter": "0.2.1-rc.2",
182
+ "@deepseek-ai/dsh-tools": "0.2.1-rc.2",
183
+ "@deepseek-ai/dsh-typert-protocol": "0.2.1-rc.2",
184
+ "@deepseek-ai/dsh-user-questions": "0.2.1-rc.2",
185
+ "@deepseek-ai/dsh-working-directory": "0.2.1-rc.2",
186
186
  "@deepseek-ai/schemastery": "3.18.5",
187
187
  "@types/node": "^24.0.0",
188
188
  "@types/react": "^18.3.31",