@pandablessing/dsh-user-sync 1.0.11 → 1.0.14

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
@@ -40,6 +40,16 @@ pnpm test # 144 tests
40
40
 
41
41
  Docs: [design](docs/design.md) · [test cases](docs/test-cases.md) · [deploy](docs/deploy.md) · [changelog](CHANGELOG.md).
42
42
 
43
+ ## Sponsor
44
+
45
+ The plugin is free and open source (MulanPSL-2.0). If it saved you time migrating machines, a small tip is welcome —
46
+ it covers the storage quota of the hosted service (Nutstore space for remote packages) and ongoing compatibility work.
47
+
48
+ ![WeChat sponsor QR](docs/assets/sponsor-wechat.jpg)
49
+
50
+ Scan with WeChat Pay. Tips are voluntary and do not unlock any extra feature.
51
+
52
+
43
53
  ## License
44
54
 
45
55
  [MulanPSL-2.0](LICENSE)
package/README.md CHANGED
@@ -1,132 +1,120 @@
1
- # dsh-user-sync
2
-
3
- DeepSeek Harness(DSH)的**用户级配置同步插件**:把技能、插件清单、配置、记忆四类资产在设备间对齐。
4
-
5
- > 当前状态:**v1.0.4**(已发布 npm:`@pandablessing/dsh-user-sync`)。本机打包/应用、跨机上传/恢复、缺插件装回、自动检查均已实现并实测;服务端已部署(见 [部署文档](docs/deploy.md))。
6
- > 设计依据:[docs/design.md](docs/design.md);测试用例:[docs/test-cases.md](docs/test-cases.md)。
7
-
8
- ## 功能
9
-
10
- - **四类用户级资产**:技能(`~/.agents/skills`)、插件清单(profile 声明)、配置(`cordis.patch.yml`)、用户记忆(`AGENTS.md` + `memories/` 根);项目记忆默认不选,会话不参与同步。
11
- - **新覆盖旧**:包级 `createdAt` 比较,不做合并;覆盖前必须确认,先快照再写盘,失败可整批还原。
12
- - **上传前查远端**:远端已被别的设备更新时先拦下(`E_REMOTE_NEWER`),用户点「用本机覆盖远端」才覆盖。
13
- - **敏感数据硬拒绝**:文件名黑名单 + 内容模式,命中即拒绝打包并列出文件与行号。
14
- - **官方账号自动配对**:用 `deepseekAccount` 取账号 id 派生 `accountKey`,自动向服务端换 token,**没有运营方账号登录入口**。
15
- - **缺插件自动装回**:覆盖确认里列出缺失 bundle,一键 `installBundle`;`link:` 本地包列入手工清单。
16
- - **自动检查**:启动检查 + 每 6 小时(可配)只读远端元数据,发现更新只提醒、不自动应用。
17
-
18
- ## 安装
19
-
20
- ### 从 npm(发布后)
21
-
22
- ```powershell
23
- cd ~/.dsh/profiles/desktop
24
- pnpm add @pandablessing/dsh-user-sync
25
- ```
26
-
27
- macOS / Linux 用同一组命令(`cd ~/.dsh/profiles/desktop && pnpm add @pandablessing/dsh-user-sync`);profile 的 `package.json` 与 `cordis.patch.yml` 里必须写 npm 包名 `@pandablessing/dsh-user-sync`(见 [v1.0.2 修复](CHANGELOG.md))。
28
-
29
- > **升级/安装前先清旧条目**:如果 profile 里还留着旧名 `dsh-user-sync` 的 junction、`dependencies` 或 bundles 条目,先删掉再装。旧名与新名会各自注册一次 client bundle,同一面板 id 冲突——1.0.4 遇到这种情况会直接把 `web boot` 打挂(`duplicate factory registration`),1.0.5 起只是多一条 warn,但画面上会出现两个「设置同步」。
30
- >
31
- > 建议顺序:① 备份 `~/.dsh/profiles/desktop/{package.json,cordis.patch.yml}`;② 移除旧名条目;③ `pnpm add @pandablessing/dsh-user-sync@1.0.5`;④ 在 profile 的 `dsh.profile.bundles` 里确认只出现一次 `@pandablessing/dsh-user-sync`;⑤ 重启 DSH。
32
-
33
- 安装后在 DSH 设置页启用「设置同步」。
34
-
35
- ### 开发期(junction 到本仓库)
36
-
37
- ```powershell
38
- # 以 junction 把仓库装进 desktop profile,改 package.json 前自动备份,失败自动回滚
39
- powershell -NoProfile -ExecutionPolicy Bypass -File scripts\install-dev.ps1
40
-
41
- # 回滚
42
- scripts\uninstall-dev.cmd # 双击即可
43
- ```
44
-
45
- 安装后**重启 DSH**,设置页出现「设置同步」。
46
-
47
- ## 使用
48
-
49
- 1. 设置 → 设置同步 → 首次点「同意并启用上传」(说明上传范围与存储位置);
50
- 2. 勾选同步范围(默认四类用户级);
51
- 3. 「上传用户配置」→ 打包并上传(远端更新时先确认再用本机覆盖);
52
- 4. 「从远端同步」→ 检查远端,覆盖前确认。
53
-
54
- 服务地址默认内置(`https://dsh-user-sync-test.wenzhi.info`),传输固定走运营方 API;面板只读显示当前地址(`(内置)` / `(自定义)`),不提供输入框。需要改用自建服务时,在 `state.json` 写 `baseUrl` 即可覆盖。
55
-
56
- ## 服务端
57
-
58
- 纯 `node:http`、无第三方依赖,支持坚果云 WebDAV(凭据缺失回退本地磁盘)。部署步骤见 [docs/deploy.md](docs/deploy.md)。
59
-
60
- ## 架构
61
-
62
- ```text
63
- 插件(HTTP 客户端) ──HTTPS──▶ 运营方 API ──WebDAV──▶ 坚果云
64
- │ │
65
- └─ 本地 state.json └─ 鉴权 / 配额 / 隔离 / 审计
66
- ```
67
-
68
- - 插件侧只实现 `api` 传输;`folder` 仅用于本机调试。
69
- - 存储凭据只在服务端,插件包内不含任何存储口令。
70
- - 身份:官方账号 id → `accountKey`(sha256 前 16 位)→ 服务端分区。
71
-
72
- ## 构建与测试
73
-
74
- 源码是 TypeScript,构建产物 `lib/` 与 `server/dist/` 已提交(junction 安装与部署直接可用)。
75
-
76
- ```powershell
77
- pnpm install # 仅开发:安装 typescript 与 @types/node
78
- pnpm run build # 插件:src/*.ts → lib/*.js
79
- pnpm run build:server # 服务端:server/src/*.ts → server/dist/*.js
80
-
81
- node --test test/*.test.js # 154 条:单元 / 组件 / 时序(直接跑源码,macOS 全绿)
82
- node scripts/audit-cases.mjs # 用例清单与结果记录一致性审计(149/149)
83
- node scripts/e2e-clone.mjs # 替代换机演练(默认本地模式,临时起服务端,不写真实 WebDAV)
84
- # node scripts/e2e-clone.mjs --remote https://dsh-user-sync-test.wenzhi.info # 真实服务端(会留测试目录)
85
- powershell -File scripts/smoke.ps1 # 真实 DSH 路由冒烟
86
- ```
87
-
88
- ## 发布
89
-
90
- > **发布时机由维护者决定**:`scripts/publish.mjs` 只在手动执行时发布,agent 不自动发布(改版本、构建、dry-run 可以提前做,发布等确认)。
91
-
92
- token 只放本地文件,绝不进仓库(`.env.local` 已在 `.gitignore`):
93
-
94
- ```bash
95
- cp .env.local.example .env.local && chmod 600 .env.local # 填 NPM_TOKEN=(发布 token)
96
- node scripts/publish.mjs --dry-run # 先看打包内容(24 个文件)
97
- node scripts/publish.mjs --otp=123456 # 账号开 2FA 时带当次验证码
98
- ```
99
-
100
- - 脚本把 token 写进 `0600` 的临时 npmrc,发布结束立即删除;token 不出现在命令行参数、日志与 git 历史里。
101
- - CI 用 secret 注入 `NPM_TOKEN` 环境变量即可,优先级高于 `.env.local`。
102
- - 发布前记得先 `pnpm run build && pnpm run build:server`,产物与源码不一致时 tarball 里会是旧代码。
103
-
1
+ # dsh-user-sync
2
+
3
+ DeepSeek Harness(DSH)的**用户级配置同步插件**:把技能、插件清单、配置、记忆在设备间对齐。
4
+
5
+ > 已发布 npm:`@pandablessing/dsh-user-sync`(版本以 npm `latest` 与 [CHANGELOG](CHANGELOG.md) 为准)。
6
+ > 设计与取舍见 [docs/design.md](docs/design.md),用例见 [docs/test-cases.md](docs/test-cases.md),服务端部署见 [docs/deploy.md](docs/deploy.md)。
7
+
8
+ ## 功能
9
+
10
+ - **四类用户级资产**:技能(`~/.agents/skills`)、插件清单(profile 声明)、配置(`cordis.patch.yml`)、用户记忆(`AGENTS.md` + `memories/`);项目记忆默认不选,会话不参与同步。
11
+ - **新覆盖旧**:包级 `createdAt` 比较,不做合并;覆盖前弹窗确认,先快照再写盘。
12
+ - **上传前查远端**:远端已被别的设备更新时先拦下(`E_REMOTE_NEWER`),确认后才用本机覆盖。
13
+ - **敏感数据硬拒绝**:文件名黑名单 + 内容模式,命中即拒绝打包并列出文件与行号。
14
+ - **官方账号自动配对**:用 `deepseekAccount` 派生 `accountKey` 自动换 token,无需运营方登录。
15
+ - **缺插件自动装回**:应用远端包后按 `manifest.dependencies` 自动安装,结果列出已装 / 失败 / 需手动放回(`link:` 依赖)。
16
+ - **自动检查**:启动 + 每 6 小时(可配)只读远端元数据,发现更新只提示、不自动应用。
17
+ - **更新提示**:发现 npm 上有新版本时面板顶部给出「更新插件」按钮(走 DSH 官方安装器)。
18
+
19
+ ## 安装
20
+
21
+ ### 从 npm
22
+
23
+ ```bash
24
+ cd ~/.dsh/profiles/desktop
25
+ pnpm add @pandablessing/dsh-user-sync
26
+ ```
27
+
28
+ profile 的 `package.json` 与 `cordis.patch.yml` 里必须使用 npm 包名 `@pandablessing/dsh-user-sync`;若此前用旧名 `dsh-user-sync` 装过,先删掉旧条目(junction / `dependencies` / bundles)再装,否则会重复注册面板。
29
+
30
+ 安装后重启 DSH,在设置页启用「设置同步」。
31
+
32
+ ### 开发期(junction 到本仓库)
33
+
34
+ ```powershell
35
+ powershell -NoProfile -ExecutionPolicy Bypass -File scripts\install-dev.ps1 # 装进 desktop profile
36
+ scripts\uninstall-dev.cmd # 回滚
37
+ ```
38
+
39
+ ## 使用
40
+
41
+ 1. 设置 → 设置同步 → 首次点「同意并启用上传」;
42
+ 2. 勾选同步范围(默认四类用户级);
43
+ 3. 「上传用户配置」→ 打包并上传(远端更新时先确认再用本机覆盖);
44
+ 4. 「从远端同步」→ 检查远端,覆盖前弹窗确认。
45
+
46
+ 服务地址内置(`https://dsh-user-sync-test.wenzhi.info`),传输固定走运营方 API;需要自建服务时在 `state.json` 写 `baseUrl` 覆盖。
47
+
48
+ ## 架构
49
+
50
+ ```text
51
+ 插件(HTTP 客户端) ──HTTPS──▶ 运营方 API ──WebDAV──▶ 坚果云
52
+ │ │
53
+ └─ 本地 state.json └─ 鉴权 / 配额 / 隔离 / 审计
54
+ ```
55
+
56
+ - 插件侧只实现 `api` 传输;`folder` 仅用于本机调试。
57
+ - 存储凭据只在服务端,插件包内不含任何存储口令。
58
+ - 身份:官方账号 id → `accountKey`(sha256 前 16 位)→ 服务端分区。
59
+ - 远端只保留 1 份包,上传即覆盖;每次应用在本地生成 `pre-restore` 快照。
60
+
61
+ ## 构建与测试
62
+
63
+ 源码是 TypeScript,构建产物 `lib/` 与 `server/dist/` 已提交(junction 安装与部署直接用)。
64
+
65
+ ```powershell
66
+ pnpm install
67
+ pnpm run build # 插件:src/*.ts → lib/*.js
68
+ pnpm run build:server # 服务端:server/src/*.ts → server/dist/*.js
69
+ pnpm test # 单元 / 组件 / 时序(直接跑源码)
70
+ node scripts/audit-cases.mjs # 用例清单与结果记录一致性审计
71
+ node scripts/e2e-clone.mjs # 替代换机演练(本地模式,不写真实 WebDAV)
72
+ node scripts/verify-npm-install.mjs # npm 安装冒烟(临时 profile,用完即删)
73
+ powershell -File scripts/smoke.ps1 # 真实 DSH 路由冒烟
74
+ ```
75
+
104
76
  ## 开发与真实环境
105
77
 
106
- - **日常开发(junction)**:`scripts/install-dev.ps1` 把仓库 link 进 profile,改源码 → `pnpm run build` → 热重载即生效;调试最快。
107
- - **真实安装(npm 包)**:`node scripts/verify-npm-install.mjs` 在临时 profile 里 `pnpm add` 当前发布版本,校验 patch name、宿主/client 两个入口、locale 与 patch 资源,用完自动删目录,不影响开发 profile。
108
- - 为什么分开:npm 安装的是 `lib/*.js` 编译产物副本,改仓库源码不会生效;所以开发用 link,验证真实安装路径用临时 profile。
109
-
110
- ## 仓库结构
111
-
112
- ```text
113
- docs/design.md 完整设计(数据面 / 同步 / 安全 / UI / 契约 / 多用户)
114
- docs/test-cases.md 测试用例清单(结果单独放 docs/test-results/)
115
- docs/deploy.md 服务端部署与验证
116
- lib/ 插件(宿主 + client + 传输 + 同步编排)
117
- server/ 运营方 API(纯 node:http)
118
- scripts/ 开发安装 / 卸载 / 冒烟 / 替代换机 / 发布
119
- test/ 单元、组件、时序测试与 fixtures
120
- legacy/ 需求反馈稿与原型脚本
121
- ```
122
-
123
- ## 已知限制
124
-
125
- - macOS 上单测已全绿(Node 22.23.3),但 DSH 桌面端整机闭环只在 Windows 实测过;Linux 未验证。
126
- - 服务端单节点,未做限流与多副本;坚果云单账号有流量配额限制。
127
- - `link:` 本地依赖需要手工放回目标机。
128
- - 远端只保留 1 份包,上传即覆盖(回滚靠本地 `pre-restore` 快照;每次应用生成独立快照目录)。
129
-
130
- ## License
131
-
132
- [Mulan PSL v2](LICENSE)
78
+ - **日常开发(junction)**:`install-dev.ps1` 把仓库 link 进 profile,改源码 → `pnpm run build` → 热重载生效。
79
+ - **验证真实安装**:`verify-npm-install.mjs` 在临时 profile 里 `pnpm add` 已发布版本,校验 patch 名、宿主/client 入口与资源文件。
80
+ - 两者互不干扰:npm 安装的是编译产物副本,改源码不会生效,所以开发用 link、验证用临时 profile。
81
+
82
+ ## 发布
83
+
84
+ 发布时机由维护者决定(`scripts/publish.mjs` 只手动执行,agent 不自动发布)。
85
+
86
+ ```bash
87
+ cp .env.local.example .env.local && chmod 600 .env.local # 填 NPM_TOKEN
88
+ node scripts/publish.mjs --dry-run # 先看打包内容
89
+ node scripts/publish.mjs --otp=123456 # 2FA 时带当次验证码
90
+ ```
91
+
92
+ token 只写进 `0600` 临时 npmrc、发布后删除,不进命令行与 git;CI 可用 `NPM_TOKEN` 环境变量。发布前先 `pnpm run build && pnpm run build:server`。
93
+
94
+ ## 已知限制
95
+
96
+ - 桌面端整机验证以 Windows / macOS 为主,Linux 未验证。
97
+ - 服务端单节点,未做限流与多副本;坚果云单账号有流量配额。
98
+ - `link:` 本地依赖需要手工放回目标机。
99
+ - 远端只保留 1 份包,回滚依赖本地 `pre-restore` 快照。
100
+
101
+ ## 赞赏
102
+
103
+ 插件免费开源(MulanPSL-2.0)。如果它帮你省过换机时间,欢迎小额赞赏,用于服务端坚果云空间与持续维护。赞赏自愿,不影响功能与权益。
104
+
105
+ ![微信赞赏码](docs/assets/sponsor-wechat.jpg)
106
+
107
+ ## 仓库结构
108
+
109
+ ```text
110
+ docs/ 设计 / 用例 / 部署 / 测试结果
111
+ lib/ 插件(宿主 + client + 传输 + 同步编排)
112
+ server/ 运营方 API(纯 node:http)
113
+ scripts/ 安装 / 卸载 / 冒烟 / 换机演练 / 发布 / npm 安装冒烟
114
+ test/ 单元、组件、时序测试与 fixtures
115
+ legacy/ 需求稿与原型脚本
116
+ ```
117
+
118
+ ## License
119
+
120
+ [Mulan PSL v2](LICENSE)
package/lib/client.js CHANGED
@@ -147,6 +147,7 @@ if (typeof window !== 'undefined' && window.__ModuleLoader__) {
147
147
  'remote-missing': '远端无包',
148
148
  };
149
149
  /** 最近一次同步的摘要:上传 / 应用 / 检查。 */
150
+ /** 最近一次同步的摘要:上传 / 应用 / 检查。 */
150
151
  function lastSyncSummary(status) {
151
152
  if (!status)
152
153
  return '尚未同步';
@@ -158,7 +159,12 @@ if (typeof window !== 'undefined' && window.__ModuleLoader__) {
158
159
  if (status.scheduler?.lastCheckAt) {
159
160
  const decision = status.scheduler.lastDecision;
160
161
  const label = decision?.action && DECISION_LABELS[decision.action] ? DECISION_LABELS[decision.action] : '';
161
- parts.push(`上次检查 ${fmtTime(status.scheduler.lastCheckAt)}${label ? `(${label})` : ''}`);
162
+ // 检查结论只在「没有比它更新的上传/应用」时才算数:否则显示「发现更新」会与上传时间矛盾
163
+ const checkAt = Date.parse(status.scheduler.lastCheckAt);
164
+ const newerAt = Math.max(status.lastUploaded?.createdAt ? Date.parse(status.lastUploaded.createdAt) : Number.NEGATIVE_INFINITY, status.lastApplied?.createdAt ? Date.parse(status.lastApplied.createdAt) : Number.NEGATIVE_INFINITY);
165
+ const stale = Number.isFinite(checkAt) && newerAt > checkAt;
166
+ const suffix = label && !stale ? `(${label})` : '';
167
+ parts.push(`上次检查 ${fmtTime(status.scheduler.lastCheckAt)}${suffix}`);
162
168
  }
163
169
  return parts.length > 0 ? parts.join(' · ') : '尚未同步';
164
170
  }
@@ -215,6 +221,33 @@ if (typeof window !== 'undefined' && window.__ModuleLoader__) {
215
221
  const [overwriteDialog, setOverwriteDialog] = R.useState(null);
216
222
  // 版本检查结果:有新版本时在面板顶部提示(不自动安装)
217
223
  const [updateInfo, setUpdateInfo] = R.useState(null);
224
+ // 一键更新:调宿主 /update(走 DSH 官方安装器),成功后提示重启
225
+ const updatePlugin = async () => {
226
+ const target = updateInfo?.latest;
227
+ if (!target)
228
+ return;
229
+ setBusy(true);
230
+ try {
231
+ const result = await fetchJson(`${API}/update`, {
232
+ method: 'POST',
233
+ headers: { 'content-type': 'application/json' },
234
+ body: JSON.stringify({ version: target }),
235
+ });
236
+ if (result.ok) {
237
+ setUpdateInfo(null);
238
+ setNotice({ kind: 'result', data: { detail: `已更新到 v${result.version ?? target},请重启 DSH 生效` } });
239
+ }
240
+ else {
241
+ setNotice({ kind: 'error', data: { detail: `更新失败:${result.error ?? '未知错误'}` } });
242
+ }
243
+ }
244
+ catch (error) {
245
+ setNotice({ kind: 'error', data: { detail: String(error) } });
246
+ }
247
+ finally {
248
+ setBusy(false);
249
+ }
250
+ };
218
251
  // 当前展开文件清单的模块(null 表示都收起)
219
252
  const [expandedModule, setExpandedModule] = R.useState(null);
220
253
  // 设置改动后自动保存(600ms 防抖),不再提供「保存设置」按钮
@@ -394,7 +427,19 @@ if (typeof window !== 'undefined' && window.__ModuleLoader__) {
394
427
  });
395
428
  }
396
429
  else {
397
- setNotice({ kind: 'error', data: result });
430
+ // 常见失败给出可操作的提示(服务端错误码透传到 result.code)
431
+ const friendly = {
432
+ 'quota-exceeded': '远端配额已满:请联系维护者清理旧包,或稍后再试。',
433
+ 'package-too-large': '包超过 10 MiB 上限:请减少同步范围(取消项目记忆/大文件)后重试。',
434
+ 'E_LOCKED': '另一台设备正在同步,请稍后再试。',
435
+ };
436
+ const hint = typeof result.code === 'string' ? friendly[result.code] : undefined;
437
+ setNotice({
438
+ kind: 'error',
439
+ data: hint
440
+ ? { ...result, detail: `${hint}(${result.detail ?? result.code})` }
441
+ : { ...result, detail: result.detail ?? '上传失败:远端存储可能空间不足或不可用,请联系维护者。' },
442
+ });
398
443
  }
399
444
  refresh();
400
445
  }
@@ -460,7 +505,7 @@ if (typeof window !== 'undefined' && window.__ModuleLoader__) {
460
505
  // 这样 /scan 请求失败时面板仍能显示真实数量。
461
506
  const counts = status?.probe?.counts ?? scan?.counts ?? {};
462
507
  return R.createElement('div', { style: styles.section }, R.createElement('h2', { style: styles.heading }, '设置同步'), updateInfo?.available
463
- ? R.createElement('p', { style: styles.warn }, `插件有新版本 v${updateInfo.latest}(当前 v${updateInfo.current}):pnpm add @pandablessing/dsh-user-sync@${updateInfo.latest}`)
508
+ ? R.createElement('div', { style: styles.card }, R.createElement('strong', { style: styles.warn }, `插件有新版本 v${updateInfo.latest}(当前 v${updateInfo.current})`), R.createElement('div', { style: styles.actions }, R.createElement('button', { style: styles.secondary, disabled: busy, onClick: updatePlugin }, busy ? '更新中…' : '更新插件')))
464
509
  : null, !consentGiven
465
510
  ? R.createElement('div', { style: styles.card }, R.createElement('strong', null, '启用前请确认'), R.createElement('p', { style: styles.muted }, '上传内容:技能、插件清单、配置、用户记忆。数据经运营方服务中转并存放在运营方账号(非 DSH 官方服务器);远端只保留 1 份,上传即覆盖。'), R.createElement('div', { style: styles.actions },
466
511
  // 用普通按钮:主色按钮看起来像「已选中」,同意是主动作但不应暗示状态
package/lib/index.js CHANGED
@@ -409,6 +409,46 @@ export function apply(ctx) {
409
409
  }
410
410
  },
411
411
  },
412
+ {
413
+ // 一键更新:走 DSH 官方安装器(与插件页安装同一路径),只允许本插件自身
414
+ path: '/api/dsh-user-sync/update',
415
+ methods: ['POST'],
416
+ requestBody: 'buffered',
417
+ fetch: async (request) => {
418
+ const pm = pluginManager;
419
+ if (!pm)
420
+ return json({ ok: false, error: 'E_NO_PLUGIN_MANAGER' }, 503);
421
+ let version = '';
422
+ try {
423
+ const body = (await request.json());
424
+ version = typeof body?.version === 'string' ? body.version.trim() : '';
425
+ }
426
+ catch {
427
+ // 无 body 时按未指定版本处理
428
+ }
429
+ if (!/^\d+\.\d+\.\d+$/.test(version))
430
+ return json({ ok: false, error: 'E_BAD_VERSION' }, 400);
431
+ const spec = `@pandablessing/dsh-user-sync@${version}`;
432
+ try {
433
+ await pm.installBundle(spec);
434
+ }
435
+ catch (error) {
436
+ recordHandlerError('update', error);
437
+ return json({ ok: false, error: error?.message ?? String(error) }, 500);
438
+ }
439
+ await forceReinstallIfMissing(['@pandablessing/dsh-user-sync']);
440
+ const installedVersion = (() => {
441
+ try {
442
+ const pkg = JSON.parse(readFileSync(join(paths.profileDir, 'node_modules', '@pandablessing', 'dsh-user-sync', 'package.json'), 'utf8'));
443
+ return pkg.version ?? null;
444
+ }
445
+ catch {
446
+ return null;
447
+ }
448
+ })();
449
+ return json({ ok: true, version: installedVersion, restartRequired: true });
450
+ },
451
+ },
412
452
  {
413
453
  // 最小同步路由:用于区分「同步 handler 本身不可用」与「status/scan 具体逻辑抛错」
414
454
  path: '/api/dsh-user-sync/ping',
package/lib/sync.js CHANGED
@@ -216,7 +216,8 @@ export function createSync({ dshHome, userHome, profileName = 'desktop', transpo
216
216
  }
217
217
  catch (error) {
218
218
  appendHistory(paths.history, { at: new Date().toISOString(), action: 'backup', ok: false, ms: Date.now() - started });
219
- return { ok: false, code: 'E_UPLOAD_FAILED', detail: error.message };
219
+ const code = error?.code ?? 'E_UPLOAD_FAILED';
220
+ return { ok: false, code, detail: error.message };
220
221
  }
221
222
  finally {
222
223
  releaseLock({ lockPath: paths.lock });
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@pandablessing/dsh-user-sync",
3
- "version": "1.0.11",
4
- "description": "DeepSeek Harness 用户级配置同步插件",
3
+ "version": "1.0.14",
4
+ "description": "DSH用户级配置同步插件,更新需重启DSH",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
7
7
  "exports": {