dsh-config-manager 0.1.72 → 0.1.73
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 +40 -4
- package/README.zh-CN.md +40 -4
- package/lib/adapters/index.d.ts +1 -0
- package/lib/adapters/index.js +5 -0
- package/lib/adapters/messages.d.ts +3 -0
- package/lib/adapters/messages.js +7 -0
- package/lib/adapters/plugin-data.d.ts +114 -0
- package/lib/adapters/plugin-data.js +309 -0
- package/lib/adapters/self.d.ts +1 -1
- package/lib/adapters/self.js +2 -1
- package/lib/adapters/test-helpers.d.ts +9 -0
- package/lib/adapters/test-helpers.js +14 -0
- package/lib/client.d.ts +95 -6
- package/lib/client.js +10630 -10133
- package/lib/core/analyzer.d.ts +11 -2
- package/lib/core/analyzer.js +31 -8
- package/lib/core/backup-plan.d.ts +10 -0
- package/lib/core/backup-plan.js +21 -3
- package/lib/core/backup.d.ts +20 -1
- package/lib/core/backup.js +48 -3
- package/lib/core/command-rollback.d.ts +6 -2
- package/lib/core/command-rollback.js +6 -2
- package/lib/core/messages-core.d.ts +2 -0
- package/lib/core/messages-core.js +4 -0
- package/lib/core/messages.d.ts +2 -1
- package/lib/core/messages.js +4 -2
- package/lib/core/plugin-data-registry.d.ts +124 -0
- package/lib/core/plugin-data-registry.js +198 -0
- package/lib/core/rollback-gate.d.ts +33 -0
- package/lib/core/rollback-gate.js +57 -0
- package/lib/core/rollback-ledger.d.ts +248 -0
- package/lib/core/rollback-ledger.js +1282 -0
- package/lib/core/rollback-point.d.ts +187 -4
- package/lib/core/rollback-point.js +689 -179
- package/lib/core/rollback.js +6 -4
- package/lib/core/types.d.ts +19 -0
- package/lib/foreign/registry.d.ts +1 -1
- package/lib/foreign/registry.js +1 -1
- package/lib/index.d.ts +5 -1
- package/lib/index.js +83 -13
- package/lib/routes/messages.d.ts +1 -0
- package/lib/routes/messages.js +2 -0
- package/lib/routes/prefs.js +26 -3
- package/lib/routes/recovery.d.ts +21 -0
- package/lib/routes/recovery.js +127 -7
- package/lib/routes/sync.js +11 -1
- package/lib/schema/section-registry.d.ts +1 -1
- package/lib/schema/section-registry.js +32 -3
- package/lib/schema/types.d.ts +1 -1
- package/lib/sync/sync-engine.d.ts +21 -7
- package/lib/sync/sync-engine.js +52 -11
- package/lib/sync/ui-prefs.d.ts +22 -0
- package/lib/sync/ui-prefs.js +28 -0
- package/lib/ui/diff-view.d.ts +10 -0
- package/lib/ui/diff-view.js +17 -0
- package/lib/ui/export-flow.js +2 -0
- package/lib/ui/i18n.d.ts +4 -2
- package/lib/ui/i18n.js +8 -4
- package/lib/ui/rollback-view.d.ts +59 -0
- package/lib/ui/rollback-view.js +14 -0
- package/lib/ui/test-helpers.js +2 -1
- package/package.json +1 -1
- package/src/adapters/index.ts +5 -0
- package/src/adapters/messages.ts +7 -0
- package/src/adapters/plugin-data.test.ts +225 -0
- package/src/adapters/plugin-data.ts +351 -0
- package/src/adapters/self.ts +2 -1
- package/src/adapters/test-helpers.ts +15 -0
- package/src/client/chat/ChatEntryView.tsx +21 -1
- package/src/client/chat/RollbackDiffTab.tsx +49 -2
- package/src/client/chat/RollbackReviewDialog.tsx +20 -4
- package/src/client/chat/chat-point-window.ts +11 -1
- package/src/client/chat/rollback-diff-tab.ts +13 -3
- package/src/client/chat/rollback-entry-view.ts +5 -0
- package/src/client/chat/sidebar-slots.ts +24 -2
- package/src/client/common/DiffTable.tsx +44 -8
- package/src/client/common/client-route-parity.test.ts +6 -0
- package/src/client/common/routes.ts +10 -0
- package/src/client/common/section-labels.test.ts +1 -0
- package/src/client/common/section-labels.ts +2 -0
- package/src/client/config-manager.module.css +113 -10
- package/src/client/index.ts +47 -1
- package/src/client/locales.ts +8 -4
- package/src/client/recovery/RollbackCard.tsx +201 -7
- package/src/client/recovery/RollbackDiffPane.tsx +13 -2
- package/src/client/recovery/recovery-api.ts +28 -0
- package/src/client/recovery/recovery-locales.ts +34 -2
- package/src/client/recovery/rollback-enabled.ts +41 -0
- package/src/client/recovery/rollback-file-changes.test.ts +41 -0
- package/src/client/sync/sync-view.test.ts +1 -1
- package/src/core/analyzer.ts +31 -9
- package/src/core/backup-plan.test.ts +20 -5
- package/src/core/backup-plan.ts +22 -3
- package/src/core/backup.ts +50 -4
- package/src/core/command-rollback.ts +6 -2
- package/src/core/messages-core.ts +4 -0
- package/src/core/messages.ts +4 -2
- package/src/core/plugin-data-registry.test.ts +113 -0
- package/src/core/plugin-data-registry.ts +261 -0
- package/src/core/rollback-gate.test.ts +66 -0
- package/src/core/rollback-gate.ts +61 -0
- package/src/core/rollback-ledger.test.ts +655 -0
- package/src/core/rollback-ledger.ts +1314 -0
- package/src/core/rollback-point.test.ts +274 -24
- package/src/core/rollback-point.ts +745 -160
- package/src/core/rollback.ts +5 -3
- package/src/core/types.ts +19 -0
- package/src/foreign/registry.test.ts +1 -1
- package/src/foreign/registry.ts +1 -1
- package/src/index.ts +73 -10
- package/src/routes/messages.ts +2 -0
- package/src/routes/prefs.ts +31 -2
- package/src/routes/recovery.ts +122 -7
- package/src/routes/sync.ts +15 -1
- package/src/schema/manifest-schema.test.ts +4 -4
- package/src/schema/registry.test.ts +9 -2
- package/src/schema/section-registry.ts +32 -3
- package/src/schema/types.ts +2 -1
- package/src/sync/sync-credentials.test.ts +119 -0
- package/src/sync/sync-engine.ts +51 -11
- package/src/sync/transport.test.ts +1 -0
- package/src/sync/ui-prefs.test.ts +44 -1
- package/src/sync/ui-prefs.ts +39 -0
- package/src/ui/diff-view.test.ts +9 -1
- package/src/ui/diff-view.ts +22 -0
- package/src/ui/export-flow.ts +2 -0
- package/src/ui/i18n.ts +8 -4
- package/src/ui/rollback-view.test.ts +17 -0
- package/src/ui/rollback-view.ts +66 -0
- package/src/ui/test-helpers.ts +2 -1
package/README.md
CHANGED
|
@@ -73,6 +73,10 @@ Browse the built-in official market for ready-made configurations (model provide
|
|
|
73
73
|
|
|
74
74
|
Your setup — and your chat history — already live in another AI coding agent on this machine? Pick the source and DSH Config Manager **translates it into a standard bundle** (MCP servers, skills, global instructions, conversations and the workspaces they belong to) and hands it to the same review-first import flow: preview → per-item conflict decisions → automatic snapshot → apply → rollback. 30 sources are recognized and 29 of them can migrate **chat history** too. Secret **values** are never read — only the key names, so the import plan can ask you to re-enter them.
|
|
75
75
|
|
|
76
|
+
### Go back to a moment in a conversation (rewind)
|
|
77
|
+
|
|
78
|
+
Mid-conversation realisation — "that message sent us down the wrong path, I want to start again from there"? Every user message carries **↺ rewind to here**: it opens a **new branch conversation** from that moment and leaves the **original intact (archived, never rewritten)**; if you want, the **workspace files** are reverted along with it — you see the per-file diff and line counts before anything happens. Rollback points are captured automatically per turn, so there is no manual snapshot to take.
|
|
79
|
+
|
|
76
80
|
---
|
|
77
81
|
|
|
78
82
|
## 🆚 How it differs from the other DSH backup / sync plugins
|
|
@@ -102,6 +106,7 @@ Several DSH plugins live in this space and they solve different problems — pic
|
|
|
102
106
|
| 🔒 | **Secret safety** | API Keys are not exported by default — non-encrypted imports ask you to re-enter; encrypted backups restore them with the password |
|
|
103
107
|
| ↩️ | **Automatic rollback** | Failed import restores everything automatically |
|
|
104
108
|
| 📸 | **Snapshot restore** | In the Library, hit "Restore" on a pre-import snapshot: review the line-by-line restore plan, then whole-file restore + uninstall added plugins (CLI & GUI) |
|
|
109
|
+
| ↺ | **Rewind a conversation** | Every user message carries "rewind to here": a new branch conversation takes over while the original is archived intact; tick the checkbox to revert workspace files too, with a per-file diff before you confirm (this is **not** the same as "Snapshot restore") |
|
|
105
110
|
| 🔄 | **Remote Sync** | Push/pull portable config via **Git private repo / WebDAV / S3-compatible object storage / GitHub Gist** — each channel is configured on its own (secrets do not sync by default; encrypted snapshots can optionally carry encrypted credentials) |
|
|
106
111
|
| ⏰ | **Scheduled backups** | Full backup on a fixed cadence (6h / 12h / 24h / 7d, or a custom weekly time) — set-and-forget, secrets never included |
|
|
107
112
|
| 🛒 | **Config Marketplace** | Browse & one-click install community configs — supply-chain warnings + per-item content selection (change summary + in-place high-risk flags); entry: Library footer "Browse market / Publish to market", or ⌘K |
|
|
@@ -123,9 +128,9 @@ Several DSH plugins live in this space and they solve different problems — pic
|
|
|
123
128
|
|:---:|:---:|
|
|
124
129
|
|  |  |
|
|
125
130
|
|
|
126
|
-
| Sync (four channel cards) | Environment ·
|
|
131
|
+
| Sync (four channel cards) | Environment · Maintenance & Diagnostics |
|
|
127
132
|
|:---:|:---:|
|
|
128
|
-
|  |  |  |
|
|
129
134
|
|
|
130
135
|
---
|
|
131
136
|
|
|
@@ -150,6 +155,16 @@ Select ZIP → validate file → check integrity → check schema → compatibil
|
|
|
150
155
|
└─ failed midway? → automatically restored (rollback, can be switched off on the confirm page)
|
|
151
156
|
```
|
|
152
157
|
|
|
158
|
+
### Rewind (go back to a moment in a conversation)
|
|
159
|
+
|
|
160
|
+
One **rollback point** is captured automatically **per turn** (its workspace state is the one from *before* that turn), so you can always go back to that moment:
|
|
161
|
+
|
|
162
|
+
```
|
|
163
|
+
Click "↺ rewind" on a user message → preview which files change and how many lines
|
|
164
|
+
→ confirm → a new branch conversation continues from that moment, the original is archived
|
|
165
|
+
→ rewound too far? the "rescue point" captured before the rewind can undo it
|
|
166
|
+
```
|
|
167
|
+
|
|
153
168
|
---
|
|
154
169
|
|
|
155
170
|
## 📥 Installation
|
|
@@ -394,6 +409,26 @@ launched with `dsh --profile <name>`. **Environment → Profiles** reads and wri
|
|
|
394
409
|
> stopped; third-party plugins must be installed into that profile separately (`dsh plugin --profile <name> add <pkg>`).
|
|
395
410
|
> The view lives at Settings → "Backup & Migration" → **Environment → Profiles**.
|
|
396
411
|
|
|
412
|
+
### ↺ Rewind (go back to a moment)
|
|
413
|
+
|
|
414
|
+
Every **user message** in the chat carries a "↺ rewind to here" — no trip to Settings, and nothing to export first:
|
|
415
|
+
|
|
416
|
+
| Action | What it does |
|
|
417
|
+
|---|---|
|
|
418
|
+
| Back to that moment | Continue from before that message: the plugin opens a **new branch conversation** to take over while the **original is archived intact** (its content is never rewritten; the conversation list shows only the rewound one) |
|
|
419
|
+
| Conversation only / files too | By default only the conversation is rewound; tick "revert workspace files too" to touch the disk, and you get a **per-file diff** with line counts first |
|
|
420
|
+
| Rewind preview | States up front how many files change, how many lines are added or removed and how many are deleted; **clicking a file name shows its "now → after rewind" diff** (the same screen appears in the settings rollback panel and in the chat bubble) |
|
|
421
|
+
| Automatic rescue points | A protective point is captured before every rewind so a wrong rewind can be undone; 20 per conversation / 50 globally, oldest dropped first |
|
|
422
|
+
| Rollback ledger | Environment → Maintenance & Diagnostics → "View rollback points" opens the **rollback ledger** dialog: every rewindable moment grouped by conversation with real titles, removable one by one |
|
|
423
|
+
|
|
424
|
+
**Capture is automatic**: one point (workspace + conversation) is recorded per turn — its workspace state is the one from *before* that turn — and there is no manual "take a snapshot" step. Points live under `$DSH_HOME/dsh-config-manager/rollback-points/`; pre-upgrade three-state checkpoint data stays on disk, is no longer read, and is never deleted by the plugin.
|
|
425
|
+
|
|
426
|
+
**Skip directories you choose**: drop a `.dsh-rollback-ignore` file in the project (the usual `.gitignore` subset) and capture plus rewind skip the matching directories and files (the built-in dependency directory cannot be re-included). When a whole directory is skipped, deleting files created after that point is **refused outright** rather than done partially.
|
|
427
|
+
|
|
428
|
+
**Two honest boundaries**: ① while a conversation is **running**, only the conversation is rewound by default, never the files (the plugin will not race a running tool for the disk) — reopen the dialog after the turn finishes to revert files too; ② when a point's capture is incomplete the plugin **never guesses** — only files it can prove did not exist at capture time are deleted, otherwise nothing is deleted and it says so.
|
|
429
|
+
|
|
430
|
+
**Straight from the chat**: type `/rollback` to list this conversation's rewindable moments, then run it again with one of those ids to rewind without leaving the chat.
|
|
431
|
+
|
|
397
432
|
### 📸 Snapshot restore (undo an import)
|
|
398
433
|
|
|
399
434
|
Every import creates a **safety snapshot** first. If something feels off afterwards, restore the target back to its pre-import state:
|
|
@@ -402,7 +437,7 @@ Every import creates a **safety snapshot** first. If something feels off afterwa
|
|
|
402
437
|
|---|---|
|
|
403
438
|
| Whole-file restore | settings.yaml / settings.json / cordis.patch.yml blobs are written back to `$DSH_HOME`; files that didn't exist at snapshot time but appeared after import are removed |
|
|
404
439
|
| Plugin uninstall | Plugins added during import are removed via the official `dsh plugin remove` (baseline comparison; old snapshots without a baseline only get a hint) |
|
|
405
|
-
| File compensation | skills / agentPresets / agentInstructions / pluginFiles / sessions blobs are written back to their original paths |
|
|
440
|
+
| File compensation | skills / agentPresets / agentInstructions / pluginFiles / pluginData / pluginAssets / sessions blobs are written back to their original paths |
|
|
406
441
|
| Credentials | DSH never reads credential values back — you get a manual re-entry hint instead |
|
|
407
442
|
|
|
408
443
|
**GUI**: Library → switch the source filter to **"Local snapshots"** → the row's ⋯ menu → **"Restore"** → review the plan (dry-run, zero writes; a git-style line-by-line comparison) → confirm. The same menu offers "Inspect & compare / Migration consult / Pin / Delete", depending on what that row is.
|
|
@@ -541,7 +576,7 @@ dsh-config-manager backup --out D:/rescue/config.zip # explicit destination
|
|
|
541
576
|
dsh-config-manager backup --sections skills,self # narrow the scope
|
|
542
577
|
```
|
|
543
578
|
|
|
544
|
-
`--sections` accepts `skills,agentPresets,agentInstructions,self,pluginFiles
|
|
579
|
+
`--sections` accepts `skills,agentPresets,agentInstructions,self,pluginFiles` (third-party plugin data — `pluginData` / `pluginAssets` — is **not** available to the offline CLI backup; naming it explicitly points you to the GUI export/import). `pluginFiles` is **opt-in** (as in the GUI): it copies third-party plugin files verbatim, and `dsh-ssh.json` holds plaintext host passwords — select it only when you have looked at what is in there.
|
|
545
580
|
|
|
546
581
|
**A typical rescue flow** when DSH won't start: ① `dsh-config-manager reinstall` to bring the launcher back (plus any clean-up), ② if DSH reports a session-log error (`corrupt session log` / `duplicate JSONL session id`), run `dsh-config-manager sessions repair --fix` — it repairs the log layout offline, ③ `dsh web` to start DSH again, ④ re-add the plugin from the registry, and ⑤ pull a snapshot from the remote repo (or run `dsh-config-manager restore`) to bring your config back. The CLI works at every step regardless of DSH's health.
|
|
547
582
|
|
|
@@ -693,6 +728,7 @@ Yes. The import wizard asks for the export-time encryption password and verifies
|
|
|
693
728
|
6. **Snapshot restore is offline and honest**: entries the offline engine can't restore (settings namespaces / patch lines when the snapshot has no whole-file backup, workspace records stored in DSH storages) are reported as skipped with a pointer to online rollback; credential **values** are never auto-written (manual re-entry hint only); old snapshots without a plugin baseline only get a hint to remove added plugins manually
|
|
694
729
|
7. **Foreign-agent import has two known blanks**: **Copilot has no chat-history import** (its session format could not be verified on this machine, so only its configuration is imported), and Cursor's per-project history is located through Cursor's own folder-slug scheme, which may not resolve for every project. Conversations already imported with an older build should be deleted and re-imported first (a second import of the same id is reported as a **skipped** `session-id-conflict`, not merged)
|
|
695
730
|
8. **Local source plugins (`link:` / `file:`) are packed into the backup**: the export runs `npm pack` on plugins you are developing locally and stores the tarball in the backup; the import unpacks it under `$DSH_HOME/dsh-config-manager/local-plugins/` and installs it as `file:`. Three consequences: ① backup size grows with those plugins (a single plugin over 100 MB is skipped with a warning — publish it to a registry / git first); ② plugin **source code** enters the backup (this does not conflict with "no secrets in backups" — secrets stay excluded, what travels is code); ③ packing needs a working `npm` on this machine; without it the plugin falls back to the previous behaviour (the original spec is kept, and a new machine still needs a manual install)
|
|
731
|
+
9. **A rewind only reaches as far as its rollback point**: it only handles the files recorded at capture time plus files created after that point; the default per-point limit is **20,000 files / 512 MiB** (an over-limit point is marked incomplete and reported as such — never faked). While a conversation is **running**, only the conversation is rewound by default, not the files; crash leftovers that already moved into the quarantine no longer take part in the next startup's classification and need an explicit restore or a manual move-back
|
|
696
732
|
|
|
697
733
|
## 💬 Feedback
|
|
698
734
|
|
package/README.zh-CN.md
CHANGED
|
@@ -74,6 +74,10 @@ DSH 是你的 AI 助手工作台,里面存着你的各种设置:模型配置
|
|
|
74
74
|
|
|
75
75
|
你在这台机器上已经用过别的 AI 编程 Agent,配置和历史对话都在?选一个来源,本插件会把它**翻译成标准 bundle**(MCP、技能、全局指令、历史会话及其所属工作区),再交给同一条「先审后写」的导入流程:预览 → 逐项冲突决策 → 自动快照 → 应用 → 可回滚。共识别 **30 个来源**,其中 **29 个**可一并迁移**历史会话**。密钥**值**绝不读取,只记下名字供导入计划提示补录。
|
|
76
76
|
|
|
77
|
+
### 回到对话里的某一刻(对话回退)
|
|
78
|
+
|
|
79
|
+
聊着聊着发现「这条消息把方向带偏了,我想从那一刻重来」?每条用户消息旁都有 **↺ 回退到这一刻**:它开出一条**新的分支对话**从那一刻继续,**原对话不改写、归档保留**;需要的话可以把**工作区文件**也一起退回去——动手前先给你逐文件差异与增删行数。留点按回合自动做,不需要你手动拍点。
|
|
80
|
+
|
|
77
81
|
---
|
|
78
82
|
|
|
79
83
|
## 🆚 与其它备份 / 同步插件的区别
|
|
@@ -103,6 +107,7 @@ DSH 生态里这个方向有几个插件,它们解决的问题并不相同—
|
|
|
103
107
|
| 🔒 | **密钥安全** | API Key 默认不导出;非加密导入后提醒重新填写,加密备份用密码解锁恢复 |
|
|
104
108
|
| ↩️ | **自动回滚** | 导入失败自动恢复原样,不会弄坏现有配置 |
|
|
105
109
|
| 📸 | **快照恢复** | 在产物库对某份导入前快照点「恢复」:先看逐行对照的恢复计划,确认后整文件还原 + 卸载新增插件(CLI 与 GUI 均支持) |
|
|
110
|
+
| ↺ | **对话回退** | 聊天里每条用户消息旁都能「回退到这一刻」:开出新分支对话、原对话归档保留;可勾选把工作区文件一起退回,动手前先看逐文件差异(与「快照恢复」不是同一件事) |
|
|
106
111
|
| 🔄 | **远程同步** | 通过 **Git 私有仓库 / WebDAV / S3 兼容对象存储 / GitHub Gist** 推送 / 拉取可移植配置,四条通道各配各的(密钥默认不参与同步;加密快照可选携带密文凭据) |
|
|
107
112
|
| ⏰ | **定时全量备份** | 按固定周期(6h / 12h / 24h / 7d 或自定义每周固定时刻)自动全量备份,一劳永逸,密钥永不包含 |
|
|
108
113
|
| 🛒 | **配置市场** | 浏览并一键安装社区分享的配置——供应链警示 + 逐项内容选择(可就地看到改动与高风险分区);入口在产物库底栏「逛市场 / 发布到市场」或 ⌘K |
|
|
@@ -124,9 +129,9 @@ DSH 生态里这个方向有几个插件,它们解决的问题并不相同—
|
|
|
124
129
|
|:---:|:---:|
|
|
125
130
|
|  |  |
|
|
126
131
|
|
|
127
|
-
| 同步(四条通道卡) | 环境 ·
|
|
132
|
+
| 同步(四条通道卡) | 环境 · 维护与诊断 |
|
|
128
133
|
|:---:|:---:|
|
|
129
|
-
|  |  |  |
|
|
130
135
|
|
|
131
136
|
---
|
|
132
137
|
|
|
@@ -151,6 +156,16 @@ DSH 生态里这个方向有几个插件,它们解决的问题并不相同—
|
|
|
151
156
|
└─ 中途失败?→ 自动恢复原样(回滚,可在确认页关闭)
|
|
152
157
|
```
|
|
153
158
|
|
|
159
|
+
### 回退(回到对话里的某一刻)
|
|
160
|
+
|
|
161
|
+
每个回合自动留一个回滚点(工作区态 = 那一轮**开始之前**),随时可以退回那一刻:
|
|
162
|
+
|
|
163
|
+
```
|
|
164
|
+
在聊天里点某条用户消息旁的「↺ 回退」 → 预览这次要改哪些文件、增删多少行
|
|
165
|
+
→ 确认 → 新分支对话接替那一刻继续,原对话归档保留
|
|
166
|
+
→ 退错了?回退前自动留的「救援点」还能再退回来
|
|
167
|
+
```
|
|
168
|
+
|
|
154
169
|
---
|
|
155
170
|
|
|
156
171
|
## 📥 安装
|
|
@@ -392,6 +407,26 @@ allowBuilds:
|
|
|
392
407
|
> 本插件启动的实例记在 `<dataDir>/launches.json`(pid/端口/日志),所以**关得掉**;
|
|
393
408
|
> 第三方插件需要在该档案里单独安装(`dsh plugin --profile <name> add <pkg>`)。
|
|
394
409
|
|
|
410
|
+
### ↺ 对话回退(回到某一刻)
|
|
411
|
+
|
|
412
|
+
聊天里**每条用户消息**旁都有一个「↺ 回退到这一刻」——不用回设置页,也不用先导出什么:
|
|
413
|
+
|
|
414
|
+
| 动作 | 说明 |
|
|
415
|
+
|---|---|
|
|
416
|
+
| 回到那一刻 | 从那条消息之前继续:插件**开一条新的分支对话**接替它,**原对话归档保留**(内容不改写;会话列表里只留退回后的那条) |
|
|
417
|
+
| 只退对话 / 连文件一起退 | 默认只回溯对话;勾上「连工作区文件一起退回」才会动盘,动手前给出**逐文件差异**与增删行数 |
|
|
418
|
+
| 回退预览 | 先说清这次要改几个文件、增删多少行、删掉几个;**点文件名就能看这个文件的「现在 → 回退后」差异**(设置页的回滚面板与聊天气泡里是同一屏) |
|
|
419
|
+
| 自动救援点 | 每次回退前自动留一个保护点,退错了还能再退回来;每会话 20 个 / 全局 50 个,超出淘汰最旧的 |
|
|
420
|
+
| 回滚点台账 | 环境 → 维护与诊断 → 「查看回滚点」打开**回滚点台账**弹窗:按对话分组列出所有可回溯的时刻(显示对话真实标题),可单条删除 |
|
|
421
|
+
|
|
422
|
+
**留点是自动的**:每个回合自动记一个点(工作区 + 对话;工作区态 = 那一轮**开始之前**),不需要你手动拍点。点存放在 `$DSH_HOME/dsh-config-manager/rollback-points/`;升级前留下的旧三态检查点数据仍在磁盘上,本插件不再读取、也不删它。
|
|
423
|
+
|
|
424
|
+
**跳过你指定的目录**:在项目里放一个 `.dsh-rollback-ignore`(写法是 `.gitignore` 的常用子集)就能让留点与回退跳过匹配的目录与文件(`node_modules` 等依赖目录不可取消)。整个目录被跳过时,「该点之后新建的文件」的删除会**整体拒绝**(宁可不动)。
|
|
425
|
+
|
|
426
|
+
**两条诚实边界**:① 对话**正在运行**时默认只回溯消息、不动文件(避免和正在跑的工具抢盘),要连文件一起退请等这一轮结束后重新打开弹窗;② 一个点的捕获不完整时**绝不猜着删**——只有能证明「留点时不存在」的新建文件才会被删,证明不了就一个都不删,并如实告知。
|
|
427
|
+
|
|
428
|
+
**在聊天里直接用**:输入 `/rollback` 列出本对话可回溯的时刻;带上某个时刻的编号再输一次,就退到那一刻,不必离开聊天。
|
|
429
|
+
|
|
395
430
|
### 📸 快照恢复(撤销一次导入)
|
|
396
431
|
|
|
397
432
|
每次导入都会先创建**安全快照**。导入后如果觉得哪里不对劲,可以把目标环境恢复到导入前的状态:
|
|
@@ -400,7 +435,7 @@ allowBuilds:
|
|
|
400
435
|
|---|---|
|
|
401
436
|
| 整文件还原 | settings.yaml / settings.json / cordis.patch.yml 的 blob 写回 `$DSH_HOME`;快照时不存在、导入后新增的文件会被移除 |
|
|
402
437
|
| 插件卸载 | 导入期间新增的插件经官方 `dsh plugin remove` 卸载(与基线对比;旧快照无基线时只给提示) |
|
|
403
|
-
| 文件补偿 | skills / agentPresets / agentInstructions / pluginFiles / sessions 的 blob 写回原路径 |
|
|
438
|
+
| 文件补偿 | skills / agentPresets / agentInstructions / pluginFiles / pluginData / pluginAssets / sessions 的 blob 写回原路径 |
|
|
404
439
|
| 凭据 | DSH 不回读凭据值——只提示人工重新填写 |
|
|
405
440
|
|
|
406
441
|
**GUI**:产物库 → 源筛选切到**「本机快照」**→ 行内 ⋯ → **「恢复」**→ 先看恢复计划(dry-run,零写入;git 风格的逐行对照)→ 确认执行。行内还有「查看与对比 / 迁移前咨询 / 置顶 / 删除」等能力,按该行是什么产物给。
|
|
@@ -539,7 +574,7 @@ dsh-config-manager backup --out D:/rescue/config.zip # 指定输出(绝
|
|
|
539
574
|
dsh-config-manager backup --sections skills,self # 收窄范围
|
|
540
575
|
```
|
|
541
576
|
|
|
542
|
-
`--sections` 可选值为 `skills,agentPresets,agentInstructions,self,pluginFiles
|
|
577
|
+
`--sections` 可选值为 `skills,agentPresets,agentInstructions,self,pluginFiles`(第三方插件数据 `pluginData` / `pluginAssets` **不在**离线命令行备份范围内,显式点名会提示改用图形界面的导出/导入)。其中 `pluginFiles` 与 GUI 侧一致属**默认关闭**:它原样复制第三方插件自有文件,而 `dsh-ssh.json` 里是明文主机密码——确认过内容之后再选它。
|
|
543
578
|
|
|
544
579
|
|
|
545
580
|
**典型救急流程**(DSH 起不来时):① `dsh-config-manager reinstall` 先把启动器重装回来(必要时顺带清理),② 若 DSH 报会话日志错误(`corrupt session log` / `duplicate JSONL session id`),先跑 `dsh-config-manager sessions repair --fix` 把日志目录离线归位,③ `dsh web` 重新启动 DSH,④ 从仓库装回插件,⑤ 从远程仓库拉取快照(或执行 `dsh-config-manager restore`)把配置恢复回来。整个流程中 CLI 全程可用,与 DSH 是否健康无关。
|
|
@@ -698,6 +733,7 @@ $env:HTTPS_PROXY='http://127.0.0.1:7897'; dsh web
|
|
|
698
733
|
6. **快照恢复是离线的、诚实的**:离线引擎无法恢复的条目(快照无整文件备份时的 settings namespace / patch 行、存在 DSH storages 里的 workspace 记录)会如实列为跳过并指向在线回滚;凭据**值**绝不自动改写(只提示人工补录);无插件基线的旧快照只提示人工核对新增插件
|
|
699
734
|
7. **从其它 Agent 导入有两处已知空白**:**Copilot 不支持导入历史会话**(其会话格式在本机无法取证,只导入它的配置);Cursor 的项目历史要靠它自己的目录 slug 反解,个别项目可能对不上。用**旧版本**导入过的会话请先删掉再重导(同一 id 二次导入会如实报**跳过** `session-id-conflict`,不会合并)
|
|
700
735
|
8. **本地源插件(`link:` / `file:`)随备份打包**:导出时执行 `npm pack` 把本地开发中的插件打成 tarball 一并备份,导入时解包到 `$DSH_HOME/dsh-config-manager/local-plugins/` 后按 `file:` 安装。因此:① 备份体积会随本地插件的体积增大(单插件超过 100 MB 会被跳过并告警,建议先发布到 registry / git 再备份);② 插件**源码**会进入备份(与「密钥永不进备份」不冲突——密钥仍被排除,这里进的是代码);③ 打包需要本机有可用的 `npm`,无 npm 时该插件退化为原行为(保留原 spec,换机后仍需手工安装)
|
|
736
|
+
9. **对话回退只覆盖「留点之后」**:它只处理「留点当时记录在案的文件」与「该点之后新建的文件」;单个回滚点默认上限 **20000 个文件 / 512 MiB**(超限会把该点标记为不完整并如实告知,绝不假装完整)。对话**正在运行**时默认只回溯消息、不动文件;已进隔离区(`quarantine/`)的崩溃残留不再参与下次启动的分类,需要显式恢复或手工移回
|
|
701
737
|
|
|
702
738
|
## 💬 反馈与建议
|
|
703
739
|
|
package/lib/adapters/index.d.ts
CHANGED
|
@@ -54,5 +54,6 @@ export { CredentialsAdapter, defaultCredentialRefs } from './credentials.ts';
|
|
|
54
54
|
export { PluginFilesAdapter, DEFAULT_PLUGIN_FILE_WHITELIST } from './plugin-files.ts';
|
|
55
55
|
export { SessionsAdapter } from './sessions.ts';
|
|
56
56
|
export { SelfAdapter, SELF_CONFIG_FILES } from './self.ts';
|
|
57
|
+
export { PluginDataAdapter, type PluginDataAdapterOptions, type PluginDataEntry } from './plugin-data.ts';
|
|
57
58
|
export { FileCollectionAdapter } from './file-collection.ts';
|
|
58
59
|
export { validateJsonSection, type SectionIssues } from './json-section.ts';
|
package/lib/adapters/index.js
CHANGED
|
@@ -12,6 +12,7 @@ import { CredentialsAdapter } from './credentials.js';
|
|
|
12
12
|
import { PluginFilesAdapter } from './plugin-files.js';
|
|
13
13
|
import { SessionsAdapter } from './sessions.js';
|
|
14
14
|
import { SelfAdapter } from './self.js';
|
|
15
|
+
import { PluginDataAdapter } from './plugin-data.js';
|
|
15
16
|
/** 组装默认 adapter 列表(pluginFiles/sessions 恒挂载但 defaultIncluded=false,用户勾选才导出) */
|
|
16
17
|
export function createAdapters(options = {}) {
|
|
17
18
|
const namespaces = options.namespaces ?? [];
|
|
@@ -28,6 +29,9 @@ export function createAdapters(options = {}) {
|
|
|
28
29
|
new WorkspacesAdapter(),
|
|
29
30
|
new CredentialsAdapter({ namespaces, refs: options.credentialsRefs }),
|
|
30
31
|
new PluginFilesAdapter(options.pluginFiles, options.pluginFilesDir),
|
|
32
|
+
// 第三方插件数据(两个分区:配置类 / 大资产类;均 defaultIncluded=false,用户显式勾选)
|
|
33
|
+
new PluginDataAdapter('pluginData'),
|
|
34
|
+
new PluginDataAdapter('pluginAssets'),
|
|
31
35
|
];
|
|
32
36
|
// self 分区:插件自身配置(同步/市场/偏好),portable 默认包含;'' = 不挂载
|
|
33
37
|
if (options.selfDir !== '') {
|
|
@@ -51,6 +55,7 @@ export { CredentialsAdapter, defaultCredentialRefs } from './credentials.js';
|
|
|
51
55
|
export { PluginFilesAdapter, DEFAULT_PLUGIN_FILE_WHITELIST } from './plugin-files.js';
|
|
52
56
|
export { SessionsAdapter } from './sessions.js';
|
|
53
57
|
export { SelfAdapter, SELF_CONFIG_FILES } from './self.js';
|
|
58
|
+
export { PluginDataAdapter } from './plugin-data.js';
|
|
54
59
|
export { FileCollectionAdapter } from './file-collection.js';
|
|
55
60
|
/* JSON 分区共享校验骨架(本导出是其接入点)。收敛轨迹:
|
|
56
61
|
* · t31 抽出骨架,settings / ui / providers / plugins / mcp / prompts / credentials /
|
|
@@ -12,6 +12,9 @@ export declare const zh: {
|
|
|
12
12
|
readonly 'adapter.uiLocalStorageTaskBoard': '任务看板数据存于浏览器 localStorage,Host 侧无访问通道,需在目标机器重新配置';
|
|
13
13
|
readonly 'adapter.uiLocalStoragePanel': '面板宽度/折叠状态存于浏览器 localStorage,Host 侧无访问通道,需在目标机器重新配置';
|
|
14
14
|
readonly 'adapter.uiLocalStorageGeneric': '浏览器端 UI 状态无法经 Host API 迁移';
|
|
15
|
+
readonly 'adapter.pluginDataEnvOverridden': '{path} 的位置被环境变量 {env} 覆盖,本次已跳过(不猜它指向哪里)';
|
|
16
|
+
readonly 'adapter.pluginDataPathNotAllowed': '路径不在第三方插件数据的允许范围内,已拒绝写入: {path}';
|
|
17
|
+
readonly 'adapter.pluginDataNoOsHome': '第三方插件的数据在系统主目录下,但当前宿主没有提供该目录的文件通道(旧版本宿主):本次不读取也不导入 / third-party plugin data lives under the system home directory, but this host offers no file channel for it (older host): nothing is read or imported this time';
|
|
15
18
|
};
|
|
16
19
|
export declare const en: Record<keyof typeof zh, string>;
|
|
17
20
|
/** 本域活翻译器(与 core 的 engineMsg 同一实现;导入本模块即完成目录注册)。 */
|
package/lib/adapters/messages.js
CHANGED
|
@@ -29,6 +29,10 @@ export const zh = {
|
|
|
29
29
|
'adapter.uiLocalStorageTaskBoard': '任务看板数据存于浏览器 localStorage,Host 侧无访问通道,需在目标机器重新配置',
|
|
30
30
|
'adapter.uiLocalStoragePanel': '面板宽度/折叠状态存于浏览器 localStorage,Host 侧无访问通道,需在目标机器重新配置',
|
|
31
31
|
'adapter.uiLocalStorageGeneric': '浏览器端 UI 状态无法经 Host API 迁移',
|
|
32
|
+
// ---------- pluginData / pluginAssets(第三方插件数据迁移) ----------
|
|
33
|
+
'adapter.pluginDataEnvOverridden': '{path} 的位置被环境变量 {env} 覆盖,本次已跳过(不猜它指向哪里)',
|
|
34
|
+
'adapter.pluginDataPathNotAllowed': '路径不在第三方插件数据的允许范围内,已拒绝写入: {path}',
|
|
35
|
+
'adapter.pluginDataNoOsHome': '第三方插件的数据在系统主目录下,但当前宿主没有提供该目录的文件通道(旧版本宿主):本次不读取也不导入 / third-party plugin data lives under the system home directory, but this host offers no file channel for it (older host): nothing is read or imported this time',
|
|
32
36
|
};
|
|
33
37
|
export const en = {
|
|
34
38
|
'adapter.linkDirEntry': '{path} ({files})',
|
|
@@ -43,6 +47,9 @@ export const en = {
|
|
|
43
47
|
'adapter.uiLocalStorageTaskBoard': 'Task-board data lives in the browser localStorage and the host has no access path; reconfigure it on the target machine',
|
|
44
48
|
'adapter.uiLocalStoragePanel': 'Panel width/collapsed state lives in the browser localStorage and the host has no access path; reconfigure it on the target machine',
|
|
45
49
|
'adapter.uiLocalStorageGeneric': 'Browser-side UI state cannot be migrated through the host API',
|
|
50
|
+
'adapter.pluginDataEnvOverridden': "the location of {path} is overridden by the {env} environment variable; skipped this time (the new location is never guessed)",
|
|
51
|
+
'adapter.pluginDataPathNotAllowed': 'the path is outside the range allowed for third-party plugin data and was refused: {path}',
|
|
52
|
+
'adapter.pluginDataNoOsHome': 'third-party plugin data lives under the system home directory, but this host offers no file channel for it (older host): nothing is read or imported this time',
|
|
46
53
|
};
|
|
47
54
|
registerMessageCatalog({ id: 'adapters-domain', zh, en });
|
|
48
55
|
/** 本域活翻译器(与 core 的 engineMsg 同一实现;导入本模块即完成目录注册)。 */
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import type { RecursiveListing } from '../utils/recursive-walk.ts';
|
|
2
|
+
import type { PluginDataArea, PluginDataSource, PluginDataSection } from '../core/plugin-data-registry.ts';
|
|
3
|
+
import { FileCollectionAdapter } from './file-collection.ts';
|
|
4
|
+
import type { FilesSection } from '../schema/types.ts';
|
|
5
|
+
import type { ApplyResult, ExportOptions, ExportSection, HostContext, ImportContext, PlanItem, SectionPreview } from '../core/types.ts';
|
|
6
|
+
/** 区域条目(来源 + 区域),编译期与运行期唯一的区域表示 */
|
|
7
|
+
export type PluginDataEntry = {
|
|
8
|
+
source: PluginDataSource;
|
|
9
|
+
area: PluginDataArea;
|
|
10
|
+
};
|
|
11
|
+
export interface PluginDataAdapterOptions {
|
|
12
|
+
/** 内置来源(缺省 = BUILTIN_PLUGIN_DATA_SOURCES;测试注入假来源) */
|
|
13
|
+
readonly sources?: readonly PluginDataSource[];
|
|
14
|
+
/** 自述声明缓存 TTL;测试可传 0 关闭缓存 */
|
|
15
|
+
readonly declaredTtlMs?: number;
|
|
16
|
+
/**
|
|
17
|
+
* 环境变量快照(缺省 process.env)。
|
|
18
|
+
*
|
|
19
|
+
* 为什么走构造参数而不是 HostContext:HostContext 上没有 env(它是宿主门面的集合),
|
|
20
|
+
* 而「区域被环境变量搬走」这件事必须在**同一次调用**里可判定、可注入测试值 ——
|
|
21
|
+
* 直接读 process.env 会让单测无法造「被覆盖」的场景(本机若恰好设了该变量,测试还会 flaky)。
|
|
22
|
+
*/
|
|
23
|
+
readonly env?: Readonly<Record<string, string | undefined>>;
|
|
24
|
+
}
|
|
25
|
+
export declare class PluginDataAdapter extends FileCollectionAdapter {
|
|
26
|
+
readonly id: PluginDataSection;
|
|
27
|
+
readonly displayName: string;
|
|
28
|
+
readonly defaultIncluded = false;
|
|
29
|
+
readonly portability: 'deviceSpecific';
|
|
30
|
+
/** home 根(相对路径 = home 相对路径,与 agentInstructions 同形) */
|
|
31
|
+
readonly baseDir = "";
|
|
32
|
+
private readonly builtin;
|
|
33
|
+
private readonly declaredTtlMs;
|
|
34
|
+
private readonly env;
|
|
35
|
+
/**
|
|
36
|
+
* 自述声明缓存。**必须带 ctx 键**(homeDir + profile):这台机器装了某插件,不代表另一台装了 ——
|
|
37
|
+
* 缓存若按实例共享,导入到「没装该插件」的目标机时会拿源机的声明去放行写入,
|
|
38
|
+
* 白名单就变成了「谁先导入谁说了算」。
|
|
39
|
+
*/
|
|
40
|
+
private declaredMemo;
|
|
41
|
+
/** 本轮清单(listRelPaths 写入;unitIdOf 是同步纯函数,只能从这里拿区域表) */
|
|
42
|
+
private active;
|
|
43
|
+
/** 本轮告警(环境变量覆盖跳过等;export/preview 收尾合并进返回的 warnings) */
|
|
44
|
+
private pendingWarnings;
|
|
45
|
+
constructor(section: PluginDataSection, options?: PluginDataAdapterOptions);
|
|
46
|
+
/** 本分区可收集区域(内置 + 自述声明) */
|
|
47
|
+
private allowedEntries;
|
|
48
|
+
private allSources;
|
|
49
|
+
/**
|
|
50
|
+
* 已安装插件的自述声明(dsh.dataDirs)。
|
|
51
|
+
*
|
|
52
|
+
* 为什么读 profile 的 node_modules 而不是全局目录:DSH 的插件装在 \`profiles/<profile>/node_modules\`
|
|
53
|
+
* 下(pnpm 隔离布局),且同一个包在不同 profile 可以是不同版本 —— 声明必须取**本 profile 实际装的**那份。
|
|
54
|
+
*
|
|
55
|
+
* 读不到(未安装 / 无声明 / 形状非法)一律跳过:本机制是**附加**信息,绝不因为一个包读不出来
|
|
56
|
+
* 就影响内置来源或整次导出。
|
|
57
|
+
*/
|
|
58
|
+
private declaredSources;
|
|
59
|
+
/**
|
|
60
|
+
* 区域是否被环境变量覆盖(覆盖 → 返回变量名;未覆盖 / 未声明 → null)。
|
|
61
|
+
*
|
|
62
|
+
* 判定口径:变量**存在且解析结果与默认位置不同**才算覆盖 —— 仅仅 \`DSH_WE_DATA_DIR=~/.dsh-wallpaper-engine\`
|
|
63
|
+
* 这种「设成同一个值」的写法不该让区域凭空消失(那会让用户以为「没有数据」)。
|
|
64
|
+
* 解析只做 \`~\` 展开与分隔符归一(不做 realpath:不读盘、不猜)。
|
|
65
|
+
*/
|
|
66
|
+
private envOverrideOf;
|
|
67
|
+
/**
|
|
68
|
+
* 区域是否是**普通文件**(而不是目录 / 不存在)。
|
|
69
|
+
*
|
|
70
|
+
* 判据顺序为什么是这样:`statSize` 对「非普通文件」与「不存在」都返回 null,无法单独区分
|
|
71
|
+
* 目录与缺失;所以先判「是文件」,其余一律按目录走遍历 —— 目录不存在时遍历返回空,
|
|
72
|
+
* 结果同样是「没有内容」,不会把缺失误报成文件,也不会把目录误读成空内容而静默丢数据。
|
|
73
|
+
* 门面未实现 statSize(旧宿主)时退回读一次内容:读得出来就是文件。
|
|
74
|
+
*/
|
|
75
|
+
private isRegularFile;
|
|
76
|
+
private static emptyListing;
|
|
77
|
+
/**
|
|
78
|
+
* 清单枚举 = 来源注册表的区域(覆写基类的目录递归;baseDir='' 时基类实现会递归**整个 home**)。
|
|
79
|
+
*
|
|
80
|
+
* **必须覆写**:home 根上递归会遍历 profiles/ sessions/ attachments/ 等全部内容(本机实测
|
|
81
|
+
* 4000+ 文件 / 数百 MB),既慢又会把无关目录当成该分区的「可勾选单元」。
|
|
82
|
+
*/
|
|
83
|
+
protected listRelPaths(ctx: HostContext): Promise<RecursiveListing>;
|
|
84
|
+
/**
|
|
85
|
+
* 单元 = 区域(最长前缀归属)。
|
|
86
|
+
*
|
|
87
|
+
* 用「本轮清单写入的区域表」而不是重新解析:listRelPaths 与本函数在同一次 collect 里成对调用,
|
|
88
|
+
* 重算会让两次解析结果不一致时出现「单元 id 不在清单里」(选择器勾不上)。测试/异常路径下
|
|
89
|
+
* \`active\` 可能是 undefined(例如直接调 listUnits),此时退回**内置来源**的最长前缀 —— 与清单口径同源。
|
|
90
|
+
*/
|
|
91
|
+
protected unitIdOf(relativePath: string): string;
|
|
92
|
+
/**
|
|
93
|
+
* 读一个成员文件:**换根到系统 home 门面**(基类的其余逻辑 —— 体积、哈希、字节闸门 —— 一字不动)。
|
|
94
|
+
*
|
|
95
|
+
* 能走到这里说明 listRelPaths 产出过条目(缺门面时它不产出任何路径),因此缺门面即**抛错**,
|
|
96
|
+
* 不再造一个「空文件」出来(那会把缺通道伪装成空内容)。
|
|
97
|
+
*/
|
|
98
|
+
protected readMember(ctx: HostContext, rel: string, mode: 'content' | 'size', statTimes?: Map<string, number>): Promise<{
|
|
99
|
+
data: Uint8Array;
|
|
100
|
+
sizeBytes: number;
|
|
101
|
+
contentHash: string;
|
|
102
|
+
}>;
|
|
103
|
+
export(ctx: HostContext, options: ExportOptions): Promise<ExportSection<FilesSection>>;
|
|
104
|
+
preview(ctx: HostContext, options: ExportOptions): Promise<SectionPreview<FilesSection>>;
|
|
105
|
+
/**
|
|
106
|
+
* 导入分析 + **写入路径白名单**(本分区唯一的越界防线)。
|
|
107
|
+
*
|
|
108
|
+
* 判据:路径必须是本机可解析的某个区域(内置来源,或已安装插件的自述声明)之下,且未被该区域
|
|
109
|
+
* 的 excludeRel 排除。不满足 → Error(计划里可见、执行期不写),绝不「尽力而为」地写出去。
|
|
110
|
+
*/
|
|
111
|
+
analyzeImport(data: FilesSection, ctx: ImportContext): Promise<PlanItem[]>;
|
|
112
|
+
/** 应用前再查一遍白名单(纵深防御:plan 可能来自消费者手工构造/缓存反序列化) */
|
|
113
|
+
applyItem(item: PlanItem, ctx: ImportContext): Promise<ApplyResult>;
|
|
114
|
+
}
|