dsh-config-manager 0.1.71 → 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.
Files changed (596) hide show
  1. package/README.md +149 -98
  2. package/README.zh-CN.md +245 -88
  3. package/lib/adapters/agent-instructions.js +1 -1
  4. package/lib/adapters/file-collection.js +1 -1
  5. package/lib/adapters/index.d.ts +2 -1
  6. package/lib/adapters/index.js +6 -1
  7. package/lib/adapters/link-report.d.ts +1 -0
  8. package/lib/adapters/link-report.js +27 -3
  9. package/lib/adapters/messages.d.ts +21 -0
  10. package/lib/adapters/messages.js +57 -0
  11. package/lib/adapters/plugin-data.d.ts +114 -0
  12. package/lib/adapters/plugin-data.js +309 -0
  13. package/lib/adapters/plugin-files.js +3 -2
  14. package/lib/adapters/plugins.js +4 -3
  15. package/lib/adapters/prompts.d.ts +1 -0
  16. package/lib/adapters/prompts.js +7 -1
  17. package/lib/adapters/self.d.ts +8 -2
  18. package/lib/adapters/self.js +3 -2
  19. package/lib/adapters/sessions.d.ts +1 -0
  20. package/lib/adapters/sessions.js +4 -2
  21. package/lib/adapters/test-helpers.d.ts +9 -0
  22. package/lib/adapters/test-helpers.js +14 -0
  23. package/lib/adapters/ui.d.ts +2 -2
  24. package/lib/adapters/ui.js +25 -25
  25. package/lib/adapters/workspaces.d.ts +1 -0
  26. package/lib/adapters/workspaces.js +7 -3
  27. package/lib/cli/actions.d.ts +9 -1
  28. package/lib/cli/actions.js +5 -2
  29. package/lib/cli/help.js +23 -10
  30. package/lib/cli/index.d.ts +23 -0
  31. package/lib/cli/index.js +90 -32
  32. package/lib/cli/sessions-inspect.js +3 -1
  33. package/lib/cli/sessions-repair.js +25 -6
  34. package/lib/cli/web/page.d.ts +12 -0
  35. package/lib/cli/web/page.js +21 -12
  36. package/lib/cli/web/routes.js +2 -2
  37. package/lib/cli/web/server.js +4 -4
  38. package/lib/client.d.ts +1442 -494
  39. package/lib/client.js +15326 -8520
  40. package/lib/core/analyzer.d.ts +11 -2
  41. package/lib/core/analyzer.js +31 -8
  42. package/lib/core/backup-plan.d.ts +10 -0
  43. package/lib/core/backup-plan.js +51 -20
  44. package/lib/core/backup-verify.js +24 -23
  45. package/lib/core/backup.d.ts +25 -1
  46. package/lib/core/backup.js +99 -28
  47. package/lib/core/boot-rescue.js +4 -3
  48. package/lib/core/boot-safety.js +2 -1
  49. package/lib/core/command-rollback.d.ts +182 -0
  50. package/lib/core/command-rollback.js +293 -0
  51. package/lib/core/consult-source.js +7 -6
  52. package/lib/core/disk-usage.d.ts +74 -10
  53. package/lib/core/disk-usage.js +67 -4
  54. package/lib/core/engine-locale.d.ts +87 -0
  55. package/lib/core/engine-locale.js +125 -0
  56. package/lib/core/exporter.js +3 -3
  57. package/lib/core/fork-reconcile.d.ts +128 -0
  58. package/lib/core/fork-reconcile.js +150 -0
  59. package/lib/core/journal.js +6 -5
  60. package/lib/core/local-plugin-host.js +3 -2
  61. package/lib/core/local-plugin-pack.js +11 -6
  62. package/lib/core/messages-core.d.ts +294 -0
  63. package/lib/core/messages-core.js +619 -0
  64. package/lib/core/messages.d.ts +61 -2
  65. package/lib/core/messages.js +118 -13
  66. package/lib/core/migration-consult.js +21 -20
  67. package/lib/core/migration-history.js +19 -5
  68. package/lib/core/model-tools.d.ts +0 -22
  69. package/lib/core/model-tools.js +50 -27
  70. package/lib/core/phase3-host.js +30 -5
  71. package/lib/core/plugin-cli.js +18 -19
  72. package/lib/core/plugin-data-registry.d.ts +124 -0
  73. package/lib/core/plugin-data-registry.js +198 -0
  74. package/lib/core/reconcile.d.ts +2 -14
  75. package/lib/core/reconcile.js +87 -17
  76. package/lib/core/recovery-orchestrator.js +19 -18
  77. package/lib/core/reinstall.d.ts +4 -0
  78. package/lib/core/reinstall.js +26 -21
  79. package/lib/core/restore.js +20 -17
  80. package/lib/core/rollback-gate.d.ts +33 -0
  81. package/lib/core/rollback-gate.js +57 -0
  82. package/lib/core/rollback-ignore-rules.d.ts +31 -0
  83. package/lib/core/rollback-ignore-rules.js +140 -0
  84. package/lib/core/rollback-ledger.d.ts +248 -0
  85. package/lib/core/rollback-ledger.js +1282 -0
  86. package/lib/core/rollback-point.d.ts +1391 -0
  87. package/lib/core/rollback-point.js +3503 -0
  88. package/lib/core/rollback.js +6 -4
  89. package/lib/core/run-registry.js +6 -5
  90. package/lib/core/session-health.d.ts +15 -0
  91. package/lib/core/session-meta.d.ts +42 -2
  92. package/lib/core/session-meta.js +140 -1
  93. package/lib/core/session-title.d.ts +28 -0
  94. package/lib/core/session-title.js +185 -0
  95. package/lib/core/startup-barrier.d.ts +24 -0
  96. package/lib/core/startup-barrier.js +22 -0
  97. package/lib/core/transaction-coordinator.js +4 -3
  98. package/lib/core/types.d.ts +37 -1
  99. package/lib/core/validator.js +5 -4
  100. package/lib/core/verify-recovery.js +31 -30
  101. package/lib/foreign/bundle.js +3 -2
  102. package/lib/foreign/continue.js +2 -1
  103. package/lib/foreign/crush.js +2 -1
  104. package/lib/foreign/gemini.js +2 -1
  105. package/lib/foreign/goose.js +2 -1
  106. package/lib/foreign/kilocode.js +2 -1
  107. package/lib/foreign/kimi.js +2 -1
  108. package/lib/foreign/messages.d.ts +15 -0
  109. package/lib/foreign/messages.js +40 -0
  110. package/lib/foreign/mimocode.js +2 -1
  111. package/lib/foreign/openclaw.js +2 -1
  112. package/lib/foreign/opencode.js +2 -1
  113. package/lib/foreign/pi.js +2 -1
  114. package/lib/foreign/qoder.js +2 -1
  115. package/lib/foreign/qwen.js +2 -1
  116. package/lib/foreign/registry.d.ts +1 -1
  117. package/lib/foreign/registry.js +8 -7
  118. package/lib/foreign/teleagent.js +2 -1
  119. package/lib/foreign/workbuddy.js +2 -1
  120. package/lib/foreign/zcode.js +2 -1
  121. package/lib/foreign/zed.js +2 -1
  122. package/lib/index.d.ts +227 -7
  123. package/lib/index.js +799 -54
  124. package/lib/market/git-file-writer.js +20 -19
  125. package/lib/market/github-repos.js +35 -19
  126. package/lib/market/index-parser.js +31 -30
  127. package/lib/market/messages-market.d.ts +138 -0
  128. package/lib/market/messages-market.js +305 -0
  129. package/lib/market/my-repo.d.ts +2 -2
  130. package/lib/market/my-repo.js +32 -29
  131. package/lib/market/prepare.js +18 -15
  132. package/lib/market/security.js +20 -19
  133. package/lib/market/types.js +2 -1
  134. package/lib/market/url.js +2 -1
  135. package/lib/migrations/index.js +6 -5
  136. package/lib/migrations/messages-migrations.d.ts +32 -0
  137. package/lib/migrations/messages-migrations.js +58 -0
  138. package/lib/profiles/dsh-profile-launcher.js +2 -1
  139. package/lib/profiles/dsh-profile-runtime.js +2 -1
  140. package/lib/profiles/messages.d.ts +7 -0
  141. package/lib/profiles/messages.js +27 -0
  142. package/lib/routes/backup.js +18 -2
  143. package/lib/routes/import.js +3 -3
  144. package/lib/routes/index.d.ts +1 -1
  145. package/lib/routes/kit.d.ts +3 -2
  146. package/lib/routes/kit.js +6 -1
  147. package/lib/routes/market.js +1 -1
  148. package/lib/routes/messages.d.ts +43 -0
  149. package/lib/routes/messages.js +102 -0
  150. package/lib/routes/prefs.js +26 -3
  151. package/lib/routes/profiles.js +12 -6
  152. package/lib/routes/recovery.d.ts +21 -0
  153. package/lib/routes/recovery.js +478 -94
  154. package/lib/routes/snapshots.js +51 -4
  155. package/lib/routes/sync.js +79 -17
  156. package/lib/schema/config.d.ts +11 -1
  157. package/lib/schema/config.js +52 -20
  158. package/lib/schema/index.d.ts +8 -6
  159. package/lib/schema/index.js +8 -6
  160. package/lib/schema/manifest.js +19 -18
  161. package/lib/schema/messages.d.ts +47 -0
  162. package/lib/schema/messages.js +110 -0
  163. package/lib/schema/section-registry.d.ts +42 -15
  164. package/lib/schema/section-registry.js +67 -32
  165. package/lib/schema/types.d.ts +1 -1
  166. package/lib/schema/versions.js +6 -5
  167. package/lib/security/encryption.js +16 -15
  168. package/lib/security/integrity.js +8 -7
  169. package/lib/security/messages.d.ts +30 -0
  170. package/lib/security/messages.js +77 -0
  171. package/lib/security/redaction.js +12 -0
  172. package/lib/security/secret-scanner.js +16 -13
  173. package/lib/security/vault.js +2 -1
  174. package/lib/security/zip-security.js +4 -3
  175. package/lib/sync/ancestor.js +2 -1
  176. package/lib/sync/autosync-config.d.ts +11 -0
  177. package/lib/sync/autosync-config.js +12 -0
  178. package/lib/sync/autosync-scheduler.d.ts +11 -1
  179. package/lib/sync/autosync-scheduler.js +115 -76
  180. package/lib/sync/backup-files.js +3 -2
  181. package/lib/sync/backup-scheduler.js +6 -5
  182. package/lib/sync/blob-store.js +2 -1
  183. package/lib/sync/git/git-transport.d.ts +55 -0
  184. package/lib/sync/git/git-transport.js +152 -2
  185. package/lib/sync/github-auth.js +10 -9
  186. package/lib/sync/layout.js +13 -12
  187. package/lib/sync/messages.d.ts +63 -0
  188. package/lib/sync/messages.js +149 -0
  189. package/lib/sync/review-queue.js +6 -5
  190. package/lib/sync/s3/messages.d.ts +7 -0
  191. package/lib/sync/s3/messages.js +16 -0
  192. package/lib/sync/s3/s3-providers.d.ts +4 -1
  193. package/lib/sync/s3/s3-providers.js +5 -5
  194. package/lib/sync/s3/s3-transport.js +1 -1
  195. package/lib/sync/s3/sigv4.js +2 -1
  196. package/lib/sync/snapshot-crypto.js +6 -5
  197. package/lib/sync/snapshot-json.js +3 -2
  198. package/lib/sync/sync-engine.d.ts +58 -22
  199. package/lib/sync/sync-engine.js +119 -48
  200. package/lib/sync/sync-history.js +7 -6
  201. package/lib/sync/sync-selection.d.ts +15 -4
  202. package/lib/sync/sync-selection.js +17 -5
  203. package/lib/sync/ui-prefs.d.ts +22 -0
  204. package/lib/sync/ui-prefs.js +28 -0
  205. package/lib/sync/webdav/webdav-transport.d.ts +1 -0
  206. package/lib/sync/webdav/webdav-transport.js +4 -1
  207. package/lib/ui/artifact-view.js +4 -3
  208. package/lib/ui/commands.d.ts +1 -1
  209. package/lib/ui/commands.js +10 -0
  210. package/lib/ui/diff-view.d.ts +10 -0
  211. package/lib/ui/diff-view.js +17 -0
  212. package/lib/ui/disk-usage-view.d.ts +33 -4
  213. package/lib/ui/disk-usage-view.js +29 -1
  214. package/lib/ui/errors.d.ts +1 -1
  215. package/lib/ui/errors.js +4 -3
  216. package/lib/ui/export-flow.js +26 -20
  217. package/lib/ui/foreign-view.d.ts +5 -1
  218. package/lib/ui/foreign-view.js +26 -2
  219. package/lib/ui/i18n.d.ts +68 -1
  220. package/lib/ui/i18n.js +120 -2
  221. package/lib/ui/import-wizard.d.ts +7 -0
  222. package/lib/ui/import-wizard.js +13 -10
  223. package/lib/ui/progress.d.ts +2 -2
  224. package/lib/ui/progress.js +1 -2
  225. package/lib/ui/report.d.ts +5 -5
  226. package/lib/ui/report.js +6 -6
  227. package/lib/ui/rollback-view.d.ts +815 -0
  228. package/lib/ui/rollback-view.js +738 -0
  229. package/lib/ui/selection-model.d.ts +41 -0
  230. package/lib/ui/selection-model.js +30 -0
  231. package/lib/ui/session-inventory-view.d.ts +97 -3
  232. package/lib/ui/session-inventory-view.js +76 -2
  233. package/lib/ui/session-issue-labels.d.ts +43 -0
  234. package/lib/ui/session-issue-labels.js +48 -0
  235. package/lib/ui/sync-settings-view.d.ts +33 -0
  236. package/lib/ui/sync-settings-view.js +40 -0
  237. package/lib/ui/test-helpers.js +2 -1
  238. package/lib/ui/types.d.ts +8 -3
  239. package/lib/ui/types.js +1 -1
  240. package/lib/utils/atomic-write.js +6 -5
  241. package/lib/utils/bundle-scan.d.ts +11 -1
  242. package/lib/utils/bundle-scan.js +12 -1
  243. package/lib/utils/env-lock.d.ts +10 -0
  244. package/lib/utils/env-lock.js +73 -46
  245. package/lib/utils/json.js +6 -5
  246. package/lib/utils/messages-utils.d.ts +96 -0
  247. package/lib/utils/messages-utils.js +216 -0
  248. package/lib/utils/proxy.js +18 -5
  249. package/lib/utils/recursive-walk.d.ts +22 -0
  250. package/lib/utils/recursive-walk.js +75 -1
  251. package/lib/utils/session-log-repair.js +10 -9
  252. package/lib/utils/zip.js +27 -26
  253. package/package.json +12 -2
  254. package/src/adapters/agent-instructions.ts +1 -1
  255. package/src/adapters/file-collection.test.ts +1 -0
  256. package/src/adapters/file-collection.ts +1 -1
  257. package/src/adapters/files.test.ts +27 -0
  258. package/src/adapters/index.ts +6 -1
  259. package/src/adapters/link-report.test.ts +22 -2
  260. package/src/adapters/link-report.ts +29 -4
  261. package/src/adapters/messages.ts +61 -0
  262. package/src/adapters/plugin-data.test.ts +225 -0
  263. package/src/adapters/plugin-data.ts +351 -0
  264. package/src/adapters/plugin-files.ts +3 -2
  265. package/src/adapters/plugins.ts +4 -3
  266. package/src/adapters/prompts.ts +7 -1
  267. package/src/adapters/self.test.ts +26 -0
  268. package/src/adapters/self.ts +10 -3
  269. package/src/adapters/sessions.ts +4 -2
  270. package/src/adapters/settings.test.ts +38 -1
  271. package/src/adapters/test-helpers.ts +15 -0
  272. package/src/adapters/ui.test.ts +2 -2
  273. package/src/adapters/ui.ts +27 -26
  274. package/src/adapters/workspaces.ts +7 -3
  275. package/src/cli/actions.test.ts +28 -0
  276. package/src/cli/actions.ts +11 -2
  277. package/src/cli/help.ts +23 -10
  278. package/src/cli/index-guards.test.ts +193 -0
  279. package/src/cli/index.ts +106 -31
  280. package/src/cli/sessions-inspect.test.ts +15 -1
  281. package/src/cli/sessions-inspect.ts +3 -1
  282. package/src/cli/sessions-repair.test.ts +84 -0
  283. package/src/cli/sessions-repair.ts +33 -7
  284. package/src/cli/web/console-route-parity.test.ts +115 -0
  285. package/src/cli/web/page.ts +23 -12
  286. package/src/cli/web/routes.ts +2 -1
  287. package/src/cli/web/server.ts +4 -4
  288. package/src/cli/web/web.test.ts +2 -2
  289. package/src/client/ConfigManagerSection.tsx +30 -2
  290. package/src/client/about/AboutPanel.tsx +2 -2
  291. package/src/client/about/PluginUpdateDialog.tsx +1 -1
  292. package/src/client/about/ReleaseNotesDialog.tsx +2 -2
  293. package/src/client/about/about-view.test.ts +13 -3
  294. package/src/client/about/about-view.ts +10 -6
  295. package/src/client/about/release-notes-view.test.ts +14 -4
  296. package/src/client/about/release-notes-view.ts +4 -2
  297. package/src/client/api.ts +18 -6
  298. package/src/client/chat/ChatEntryView.tsx +411 -0
  299. package/src/client/chat/RollbackDiffTab.tsx +108 -0
  300. package/src/client/chat/RollbackFileIcon.tsx +96 -0
  301. package/src/client/chat/RollbackReviewDialog.tsx +607 -0
  302. package/src/client/chat/chat-entry-view.test.ts +1174 -0
  303. package/src/client/chat/chat-entry-view.ts +758 -0
  304. package/src/client/chat/chat-locales.ts +152 -0
  305. package/src/client/chat/chat-node-registration.ts +64 -0
  306. package/src/client/chat/chat-point-window.ts +183 -0
  307. package/src/client/chat/chat-slots.ts +107 -0
  308. package/src/client/chat/rollback-diff-tab.test.ts +105 -0
  309. package/src/client/chat/rollback-diff-tab.ts +174 -0
  310. package/src/client/chat/rollback-entry-view.test.ts +1469 -0
  311. package/src/client/chat/rollback-entry-view.ts +946 -0
  312. package/src/client/chat/sidebar-slots.ts +59 -0
  313. package/src/client/common/ConfirmDialog.tsx +7 -3
  314. package/src/client/common/ContentPicker.tsx +48 -10
  315. package/src/client/common/DiffTable.tsx +133 -0
  316. package/src/client/common/ErrorBanner.tsx +4 -4
  317. package/src/client/common/Icon.tsx +2 -0
  318. package/src/client/common/Modal.tsx +8 -1
  319. package/src/client/common/ProgressBar.tsx +8 -2
  320. package/src/client/common/ReportView.tsx +4 -4
  321. package/src/client/common/RunsCenter.tsx +1 -0
  322. package/src/client/common/client-route-parity.test.ts +84 -0
  323. package/src/client/common/info-hint-guard.test.ts +46 -13
  324. package/src/client/common/picker-lazy-units.test.ts +42 -0
  325. package/src/client/common/plan-text-redaction.test.ts +17 -6
  326. package/src/client/common/progress-view-edge.test.ts +8 -7
  327. package/src/client/common/progress-view.test.ts +9 -9
  328. package/src/client/common/progress-view.ts +2 -2
  329. package/src/client/common/routes.ts +38 -0
  330. package/src/client/common/section-labels.test.ts +1 -0
  331. package/src/client/common/section-labels.ts +2 -0
  332. package/src/client/common/ui.tsx +73 -10
  333. package/src/client/config-manager.module.css +1109 -31
  334. package/src/client/css-scale-guard.test.ts +30 -0
  335. package/src/client/environment/EnvironmentPanel.tsx +11 -4
  336. package/src/client/environment/maintenance/DiskUsageCard.tsx +23 -2
  337. package/src/client/export/ExportView.tsx +13 -8
  338. package/src/client/history/HistoryPanel.tsx +4 -3
  339. package/src/client/history/history-api.ts +2 -2
  340. package/src/client/history/history-locales.ts +5 -1
  341. package/src/client/home/HomePanel.tsx +29 -8
  342. package/src/client/import/ForeignImportView.tsx +4 -5
  343. package/src/client/import/ImportWizardView.tsx +5 -3
  344. package/src/client/import/import-wizard-split.test.ts +3 -1
  345. package/src/client/import/import-wizard-steps.tsx +9 -9
  346. package/src/client/index.ts +232 -3
  347. package/src/client/library/LibraryActions.tsx +32 -7
  348. package/src/client/library/LibraryPanel.tsx +23 -3
  349. package/src/client/library/RestorePlanView.tsx +9 -66
  350. package/src/client/locales.ts +62 -9
  351. package/src/client/lucide-icons.d.ts +1 -0
  352. package/src/client/market/MarketPanel.tsx +13 -2
  353. package/src/client/market/MyConfigsView.tsx +5 -4
  354. package/src/client/market/MyConfigsWizard.tsx +10 -8
  355. package/src/client/market/market-api.test.ts +9 -8
  356. package/src/client/market/market-api.ts +2 -2
  357. package/src/client/market/market-locales.ts +3 -0
  358. package/src/client/market/market-view.test.ts +6 -6
  359. package/src/client/market/market-view.ts +3 -3
  360. package/src/client/market/my-configs-api.ts +2 -2
  361. package/src/client/market/my-configs-view.test.ts +23 -22
  362. package/src/client/market/my-configs-view.ts +8 -8
  363. package/src/client/recovery/IncompleteCopiesSection.tsx +2 -1
  364. package/src/client/recovery/RecoveryPanel.tsx +442 -283
  365. package/src/client/recovery/RollbackCard.tsx +859 -0
  366. package/src/client/recovery/RollbackDiffPane.tsx +69 -0
  367. package/src/client/recovery/RollbackFileChanges.tsx +163 -0
  368. package/src/client/recovery/incident-api.ts +2 -2
  369. package/src/client/recovery/recovery-api.ts +144 -6
  370. package/src/client/recovery/recovery-locales.test.ts +299 -0
  371. package/src/client/recovery/recovery-locales.ts +520 -149
  372. package/src/client/recovery/recovery-panel-layout.test.ts +132 -2
  373. package/src/client/recovery/rollback-enabled.ts +41 -0
  374. package/src/client/recovery/rollback-file-changes.test.ts +114 -0
  375. package/src/client/recovery/session-dialog-mount.test.ts +54 -0
  376. package/src/client/run-store.ts +84 -21
  377. package/src/client/sync/AutosyncCard.tsx +25 -1
  378. package/src/client/sync/ChannelClearConfirmDialog.tsx +6 -3
  379. package/src/client/sync/ChannelConfigDialog.tsx +10 -10
  380. package/src/client/sync/DecryptPasswordCard.tsx +2 -2
  381. package/src/client/sync/SecurityOptionsCard.tsx +27 -17
  382. package/src/client/sync/SyncChannelEntryCard.tsx +14 -2
  383. package/src/client/sync/SyncConfirmView.tsx +46 -8
  384. package/src/client/sync/SyncLogList.tsx +1 -1
  385. package/src/client/sync/SyncPanel.tsx +31 -23
  386. package/src/client/sync/SyncRepositoryPicker.tsx +3 -5
  387. package/src/client/sync/SyncSectionPickerDialog.tsx +11 -2
  388. package/src/client/sync/SyncSettingsView.test.ts +20 -1
  389. package/src/client/sync/UnreadableSnapshotsBanner.tsx +1 -1
  390. package/src/client/sync/WorkDirCard.tsx +112 -0
  391. package/src/client/sync/history-model.test.ts +42 -9
  392. package/src/client/sync/history-model.ts +30 -15
  393. package/src/client/sync/sync-api.test.ts +57 -33
  394. package/src/client/sync/sync-api.ts +80 -6
  395. package/src/client/sync/sync-locales.ts +104 -3
  396. package/src/client/sync/sync-view-v2.test.ts +65 -13
  397. package/src/client/sync/sync-view.test.ts +107 -52
  398. package/src/client/sync/sync-view.ts +124 -41
  399. package/src/core/analyzer.ts +31 -9
  400. package/src/core/backup-plan.test.ts +48 -5
  401. package/src/core/backup-plan.ts +51 -20
  402. package/src/core/backup-verify.ts +24 -23
  403. package/src/core/backup.test.ts +76 -0
  404. package/src/core/backup.ts +105 -29
  405. package/src/core/boot-rescue.ts +4 -3
  406. package/src/core/boot-safety.ts +2 -1
  407. package/src/core/command-rollback.test.ts +536 -0
  408. package/src/core/command-rollback.ts +406 -0
  409. package/src/core/consult-source.ts +7 -6
  410. package/src/core/disk-usage.test.ts +257 -1
  411. package/src/core/disk-usage.ts +134 -14
  412. package/src/core/engine-locale.test.ts +593 -0
  413. package/src/core/engine-locale.ts +175 -0
  414. package/src/core/exporter.ts +3 -3
  415. package/src/core/fork-reconcile.test.ts +279 -0
  416. package/src/core/fork-reconcile.ts +234 -0
  417. package/src/core/journal.ts +6 -5
  418. package/src/core/local-plugin-host.ts +3 -2
  419. package/src/core/local-plugin-pack.ts +11 -6
  420. package/src/core/messages-core.ts +661 -0
  421. package/src/core/messages.ts +125 -17
  422. package/src/core/migration-consult.ts +21 -20
  423. package/src/core/migration-history.ts +19 -5
  424. package/src/core/model-tools.ts +28 -27
  425. package/src/core/phase3-host.ts +21 -5
  426. package/src/core/phase3-p2b-fingerprint.test.ts +22 -0
  427. package/src/core/plugin-cli.ts +18 -19
  428. package/src/core/plugin-data-registry.test.ts +113 -0
  429. package/src/core/plugin-data-registry.ts +261 -0
  430. package/src/core/reconcile.test.ts +117 -0
  431. package/src/core/reconcile.ts +85 -17
  432. package/src/core/recovery-orchestrator.ts +19 -18
  433. package/src/core/reinstall.ts +26 -21
  434. package/src/core/restore.ts +20 -17
  435. package/src/core/rollback-file-diff.test.ts +159 -0
  436. package/src/core/rollback-gate.test.ts +66 -0
  437. package/src/core/rollback-gate.ts +61 -0
  438. package/src/core/rollback-ignore-rules.ts +126 -0
  439. package/src/core/rollback-ledger.test.ts +655 -0
  440. package/src/core/rollback-ledger.ts +1314 -0
  441. package/src/core/rollback-point.test.ts +2853 -0
  442. package/src/core/rollback-point.ts +4340 -0
  443. package/src/core/rollback.ts +5 -3
  444. package/src/core/run-registry.ts +6 -5
  445. package/src/core/session-health.ts +15 -0
  446. package/src/core/session-meta.test.ts +117 -1
  447. package/src/core/session-meta.ts +149 -2
  448. package/src/core/session-title.test.ts +127 -0
  449. package/src/core/session-title.ts +189 -0
  450. package/src/core/startup-barrier.test.ts +69 -0
  451. package/src/core/startup-barrier.ts +27 -0
  452. package/src/core/transaction-coordinator.ts +4 -3
  453. package/src/core/types.ts +37 -1
  454. package/src/core/validator.ts +5 -4
  455. package/src/core/verify-recovery.ts +31 -30
  456. package/src/foreign/bundle.ts +3 -2
  457. package/src/foreign/continue.ts +2 -1
  458. package/src/foreign/crush.ts +2 -1
  459. package/src/foreign/file-budget.test.ts +2 -0
  460. package/src/foreign/gemini.ts +2 -1
  461. package/src/foreign/goose.ts +2 -1
  462. package/src/foreign/kilocode.ts +2 -1
  463. package/src/foreign/kimi.ts +2 -1
  464. package/src/foreign/messages.ts +44 -0
  465. package/src/foreign/mimocode.ts +2 -1
  466. package/src/foreign/openclaw.ts +2 -1
  467. package/src/foreign/opencode.ts +2 -1
  468. package/src/foreign/pi.ts +2 -1
  469. package/src/foreign/qoder.ts +2 -1
  470. package/src/foreign/qwen.ts +2 -1
  471. package/src/foreign/registry.test.ts +1 -1
  472. package/src/foreign/registry.ts +8 -7
  473. package/src/foreign/teleagent.ts +2 -1
  474. package/src/foreign/workbuddy.ts +2 -1
  475. package/src/foreign/zcode.ts +2 -1
  476. package/src/foreign/zed.ts +2 -1
  477. package/src/index.facade.test.ts +340 -1
  478. package/src/index.ts +876 -57
  479. package/src/market/git-file-writer.ts +20 -19
  480. package/src/market/github-repos.test.ts +23 -0
  481. package/src/market/github-repos.ts +37 -19
  482. package/src/market/index-parser.ts +31 -30
  483. package/src/market/messages-market.ts +325 -0
  484. package/src/market/my-repo.ts +32 -29
  485. package/src/market/prepare.ts +18 -15
  486. package/src/market/security.ts +20 -19
  487. package/src/market/types.ts +2 -1
  488. package/src/market/url.ts +2 -1
  489. package/src/migrations/index.ts +6 -5
  490. package/src/migrations/messages-migrations.ts +59 -0
  491. package/src/profiles/dsh-profile-launcher.ts +2 -1
  492. package/src/profiles/dsh-profile-runtime.ts +2 -1
  493. package/src/profiles/messages.ts +31 -0
  494. package/src/routes/backup.ts +18 -2
  495. package/src/routes/import.ts +3 -3
  496. package/src/routes/index.ts +1 -1
  497. package/src/routes/kit.ts +9 -3
  498. package/src/routes/market.ts +1 -1
  499. package/src/routes/messages.ts +106 -0
  500. package/src/routes/prefs.ts +31 -2
  501. package/src/routes/profiles.ts +12 -6
  502. package/src/routes/recovery.test.ts +405 -1
  503. package/src/routes/recovery.ts +477 -94
  504. package/src/routes/snapshots.ts +51 -4
  505. package/src/routes/sync.ts +82 -17
  506. package/src/schema/config.ts +53 -20
  507. package/src/schema/index.ts +7 -5
  508. package/src/schema/manifest-schema.test.ts +4 -4
  509. package/src/schema/manifest.ts +19 -18
  510. package/src/schema/messages.ts +120 -0
  511. package/src/schema/registry.test.ts +28 -7
  512. package/src/schema/section-registry.ts +96 -42
  513. package/src/schema/types.ts +2 -1
  514. package/src/schema/versions.ts +6 -5
  515. package/src/security/encryption.ts +16 -15
  516. package/src/security/integrity.ts +8 -7
  517. package/src/security/messages.ts +87 -0
  518. package/src/security/redaction.test.ts +17 -1
  519. package/src/security/redaction.ts +13 -0
  520. package/src/security/secret-scanner.ts +19 -12
  521. package/src/security/security.test.ts +47 -0
  522. package/src/security/vault.ts +2 -1
  523. package/src/security/zip-security.ts +4 -3
  524. package/src/sync/ancestor.ts +2 -1
  525. package/src/sync/autosync-config.ts +21 -0
  526. package/src/sync/autosync-scheduler.test.ts +63 -7
  527. package/src/sync/autosync-scheduler.ts +118 -78
  528. package/src/sync/backup-files.ts +3 -2
  529. package/src/sync/backup-scheduler.ts +6 -5
  530. package/src/sync/blob-store.ts +2 -1
  531. package/src/sync/git/git-transport.test.ts +170 -8
  532. package/src/sync/git/git-transport.ts +176 -2
  533. package/src/sync/github-auth.ts +10 -9
  534. package/src/sync/layout.ts +13 -12
  535. package/src/sync/messages.ts +153 -0
  536. package/src/sync/review-queue.ts +6 -5
  537. package/src/sync/s3/messages.ts +16 -0
  538. package/src/sync/s3/s3-providers.ts +9 -6
  539. package/src/sync/s3/s3-transport.ts +1 -1
  540. package/src/sync/s3/sigv4.ts +3 -1
  541. package/src/sync/snapshot-crypto.ts +6 -5
  542. package/src/sync/snapshot-json.ts +3 -2
  543. package/src/sync/sync-credentials.test.ts +119 -0
  544. package/src/sync/sync-engine-download-tombstone.test.ts +1 -1
  545. package/src/sync/sync-engine.test.ts +226 -8
  546. package/src/sync/sync-engine.ts +154 -52
  547. package/src/sync/sync-history.ts +7 -6
  548. package/src/sync/sync-selection.test.ts +12 -2
  549. package/src/sync/sync-selection.ts +18 -5
  550. package/src/sync/transport.test.ts +1 -0
  551. package/src/sync/ui-prefs.test.ts +44 -1
  552. package/src/sync/ui-prefs.ts +39 -0
  553. package/src/sync/webdav/webdav-transport.ts +4 -1
  554. package/src/ui/artifact-view.ts +4 -3
  555. package/src/ui/commands.test.ts +1 -0
  556. package/src/ui/commands.ts +11 -1
  557. package/src/ui/diff-view.test.ts +9 -1
  558. package/src/ui/diff-view.ts +22 -0
  559. package/src/ui/disk-usage-view.test.ts +113 -6
  560. package/src/ui/disk-usage-view.ts +63 -7
  561. package/src/ui/errors.ts +4 -3
  562. package/src/ui/export-flow.ts +26 -20
  563. package/src/ui/foreign-view.test.ts +43 -0
  564. package/src/ui/foreign-view.ts +30 -3
  565. package/src/ui/i18n.ts +131 -2
  566. package/src/ui/import-wizard.ts +18 -10
  567. package/src/ui/progress.ts +2 -2
  568. package/src/ui/report.ts +7 -6
  569. package/src/ui/rollback-view.test.ts +836 -0
  570. package/src/ui/rollback-view.ts +1405 -0
  571. package/src/ui/selection-model.test.ts +33 -1
  572. package/src/ui/selection-model.ts +53 -0
  573. package/src/ui/session-inventory-view.test.ts +109 -0
  574. package/src/ui/session-inventory-view.ts +138 -3
  575. package/src/ui/session-issue-labels.test.ts +51 -0
  576. package/src/ui/session-issue-labels.ts +70 -0
  577. package/src/ui/sync-settings-view.test.ts +36 -0
  578. package/src/ui/sync-settings-view.ts +49 -0
  579. package/src/ui/test-helpers.ts +2 -1
  580. package/src/ui/types.ts +10 -4
  581. package/src/utils/atomic-write.ts +6 -5
  582. package/src/utils/bundle-scan.test.ts +2 -2
  583. package/src/utils/bundle-scan.ts +12 -1
  584. package/src/utils/env-lock.ts +83 -49
  585. package/src/utils/json.ts +7 -5
  586. package/src/utils/messages-utils.ts +232 -0
  587. package/src/utils/proxy.test.ts +25 -0
  588. package/src/utils/proxy.ts +19 -5
  589. package/src/utils/recursive-walk.test.ts +43 -0
  590. package/src/utils/recursive-walk.ts +75 -1
  591. package/src/utils/session-log-repair.ts +10 -9
  592. package/src/utils/zip.ts +27 -26
  593. package/lib/core/checkpoint.d.ts +0 -362
  594. package/lib/core/checkpoint.js +0 -1309
  595. package/src/core/checkpoint.test.ts +0 -665
  596. package/src/core/checkpoint.ts +0 -1614
@@ -29,6 +29,7 @@ import {
29
29
  import { RunRegistry, type RunState } from './run-registry.ts';
30
30
  import type { HostContext, Snapshot } from './types.ts';
31
31
  import type { MsgFunc } from './messages.ts';
32
+ import { engineMsg } from './messages-core.ts';
32
33
 
33
34
  /** 路由注入的恢复执行器(restore / rollback 引擎;测试注入 mock)。 */
34
35
  export interface RecoveryExecutorFns {
@@ -195,8 +196,8 @@ export function createRecoveryOrchestrator(deps: RecoveryOrchestratorDeps): Reco
195
196
  performRestore: executors.performRestore, performRollback: executors.performRollback,
196
197
  }, true);
197
198
  if (result === 'failed') {
198
- runs.fail(runId, 'recovery 执行失败');
199
- return { status: 400, body: { error: 'recovery 执行失败', runId } };
199
+ runs.fail(runId, engineMsg('core.recovery.executeFailed'));
200
+ return { status: 400, body: { error: engineMsg('core.recovery.executeFailed'), runId } };
200
201
  }
201
202
  runs.finish(runId, { decision: decisionKind, state: 'RECOVERING' });
202
203
  return { status: 200, body: { ok: true, operationId, decision: decisionKind, state: 'RECOVERING', runId } };
@@ -290,10 +291,10 @@ export function createRecoveryOrchestrator(deps: RecoveryOrchestratorDeps): Reco
290
291
  if (userConfirmed !== true) return { status: 400, body: { error: 'userConfirmed required' } };
291
292
  const j = await store.loadActive(operationId);
292
293
  if (j === null) return { status: 404, body: { error: 'operation not found' } };
293
- if (j.state !== 'NEEDS_ATTENTION') return { status: 409, body: { error: `journal state ${j.state} 不允许 confirm` } };
294
- if (j.snapshotId === null || j.snapshotId === '') return { status: 400, body: { error: 'journal 无 trusted snapshot,无法恢复' } };
294
+ if (j.state !== 'NEEDS_ATTENTION') return { status: 409, body: { error: engineMsg('core.recovery.stateNotAllowed', { state: j.state, action: 'confirm' }) } };
295
+ if (j.snapshotId === null || j.snapshotId === '') return { status: 400, body: { error: engineMsg('core.recovery.noTrustedSnapshot') } };
295
296
  const verdict = await validateSnapshotVerdict(j.snapshotId);
296
- if (verdict !== 'TRUSTED_OPERATION_SNAPSHOT') return { status: 400, body: { error: `snapshot 不可信: ${verdict}` } };
297
+ if (verdict !== 'TRUSTED_OPERATION_SNAPSHOT') return { status: 400, body: { error: engineMsg('core.recovery.snapshotUntrusted', { verdict }) } };
297
298
  // journal 保持 NEEDS_ATTENTION(confirm 不转 RECOVERING)
298
299
  return { status: 200, body: { ok: true, operationId, snapshotId: j.snapshotId, verdict } };
299
300
  },
@@ -303,12 +304,12 @@ export function createRecoveryOrchestrator(deps: RecoveryOrchestratorDeps): Reco
303
304
  if (userConfirmed !== true) return { status: 400, body: { error: 'userConfirmed required' } };
304
305
  const j = await store.loadActive(operationId);
305
306
  if (j === null) return { status: 404, body: { error: 'operation not found' } };
306
- if (j.state !== 'NEEDS_ATTENTION' && j.state !== 'RECOVERING') return { status: 409, body: { error: `journal state ${j.state} 不允许 execute` } };
307
- if (j.snapshotId === null || j.snapshotId === '') return { status: 400, body: { error: 'journal 无 trusted snapshot,无法恢复' } };
307
+ if (j.state !== 'NEEDS_ATTENTION' && j.state !== 'RECOVERING') return { status: 409, body: { error: engineMsg('core.recovery.stateNotAllowed', { state: j.state, action: 'execute' }) } };
308
+ if (j.snapshotId === null || j.snapshotId === '') return { status: 400, body: { error: engineMsg('core.recovery.noTrustedSnapshot') } };
308
309
  const d = await decision(j);
309
- if (d === 'needs-attention') return { status: 400, body: { error: 'recovery decision 为 needs-attention,无法自动执行' } };
310
+ if (d === 'needs-attention') return { status: 400, body: { error: engineMsg('core.recovery.needsAttentionNoAuto') } };
310
311
  const verdict = await validateSnapshotVerdict(j.snapshotId);
311
- if (verdict !== 'TRUSTED_OPERATION_SNAPSHOT') return { status: 400, body: { error: `snapshot 不可信: ${verdict}` } };
312
+ if (verdict !== 'TRUSTED_OPERATION_SNAPSHOT') return { status: 400, body: { error: engineMsg('core.recovery.snapshotUntrusted', { verdict }) } };
312
313
  return runExecution(operationId, j, d, makeExecutors);
313
314
  },
314
315
 
@@ -316,13 +317,13 @@ export function createRecoveryOrchestrator(deps: RecoveryOrchestratorDeps): Reco
316
317
  if (!isValidOperationId(operationId)) return { status: 400, body: { error: 'invalid operationId' } };
317
318
  const j = await store.loadActive(operationId);
318
319
  if (j === null) return { status: 404, body: { error: 'operation not found' } };
319
- if (j.state !== 'RECOVERING') return { status: 409, body: { error: `journal state ${j.state} 不允许 verify` } };
320
- if (j.snapshotId === null || j.snapshotId === '') return { status: 400, body: { error: 'journal 无 trusted snapshot' } };
320
+ if (j.state !== 'RECOVERING') return { status: 409, body: { error: engineMsg('core.recovery.stateNotAllowed', { state: j.state, action: 'verify' }) } };
321
+ if (j.snapshotId === null || j.snapshotId === '') return { status: 400, body: { error: engineMsg('core.recovery.noTrustedSnapshotShort') } };
321
322
  let snap: Snapshot;
322
323
  try {
323
324
  snap = await new FileSnapshotStore({ dir: snapshotsDir }).load(j.snapshotId);
324
325
  } catch {
325
- return { status: 400, body: { error: 'snapshot 无法加载' } };
326
+ return { status: 400, body: { error: engineMsg('core.recovery.snapshotLoadFailed') } };
326
327
  }
327
328
  const verification = await verifyRecovery(snap, host, { snapshotsRoot: snapshotsDir, environmentFingerprint: getEnvironmentFingerprint(), expectedOperationId: j.operationId });
328
329
  const terminal = recoveryTerminalState(verification.verdict);
@@ -351,12 +352,12 @@ export function createRecoveryOrchestrator(deps: RecoveryOrchestratorDeps): Reco
351
352
  if (userConfirmed !== true) return { status: 400, body: { error: 'userConfirmed required' } };
352
353
  const j = await store.loadActive(operationId);
353
354
  if (j === null) return { status: 404, body: { error: 'operation not found' } };
354
- if (j.state !== 'NEEDS_ATTENTION') return { status: 409, body: { error: `journal state ${j.state} 不允许 retry` } };
355
- if (j.snapshotId === null || j.snapshotId === '') return { status: 400, body: { error: 'journal 无 trusted snapshot' } };
355
+ if (j.state !== 'NEEDS_ATTENTION') return { status: 409, body: { error: engineMsg('core.recovery.stateNotAllowed', { state: j.state, action: 'retry' }) } };
356
+ if (j.snapshotId === null || j.snapshotId === '') return { status: 400, body: { error: engineMsg('core.recovery.noTrustedSnapshotShort') } };
356
357
  const d = await decision(j);
357
- if (d === 'needs-attention') return { status: 400, body: { error: 'recovery decision 为 needs-attention,无法 retry' } };
358
+ if (d === 'needs-attention') return { status: 400, body: { error: engineMsg('core.recovery.needsAttentionNoRetry') } };
358
359
  const verdict = await validateSnapshotVerdict(j.snapshotId);
359
- if (verdict !== 'TRUSTED_OPERATION_SNAPSHOT') return { status: 400, body: { error: `snapshot 不可信: ${verdict}` } };
360
+ if (verdict !== 'TRUSTED_OPERATION_SNAPSHOT') return { status: 400, body: { error: engineMsg('core.recovery.snapshotUntrusted', { verdict }) } };
360
361
  return runExecution(operationId, j, d, makeExecutors);
361
362
  },
362
363
 
@@ -367,7 +368,7 @@ export function createRecoveryOrchestrator(deps: RecoveryOrchestratorDeps): Reco
367
368
  if (j === null) return { status: 404, body: { error: 'operation not found' } };
368
369
  // dismiss → quarantine(用户放弃;不删除 snapshot/journal evidence,不强行 RECOVERED)
369
370
  const result = await executeRecovery(store, { operationId, action: 'dismiss', snapshotId: j.snapshotId }, true);
370
- if (result === 'failed') return { status: 400, body: { error: 'dismiss 失败' } };
371
+ if (result === 'failed') return { status: 400, body: { error: engineMsg('core.recovery.dismissFailed') } };
371
372
  // issue #56:放弃恢复 = 该 incident 结案 —— 若没有别的未解决 incident,必须同时解除保护。
372
373
  // 此前这里只 quarantine 不清 SAFE MODE,而唯一的清除点挂在 verify(ROLLED_BACK) 上,
373
374
  // 事务已被移出 active/ ⇒ 那条分支永远走不到:界面显示「暂无待处理」,写操作却持续 423,
@@ -429,7 +430,7 @@ export function createRecoveryOrchestrator(deps: RecoveryOrchestratorDeps): Reco
429
430
  }
430
431
  if (!res.ok) {
431
432
  // 拒绝是正常结果(活锁/无法证明 stale/二次验证失败),不是 500
432
- return { status: 200, body: { ok: false, removed: false, state: res.state, reason: res.detail ?? '未判定为 stale,已拒绝回收' } };
433
+ return { status: 200, body: { ok: false, removed: false, state: res.state, reason: res.detail ?? engineMsg('core.recovery.lockNotStale') } };
433
434
  }
434
435
  // 回收成功后 stale 分类已消失;锁态交由客户端重新拉取 status 刷新
435
436
  return { status: 200, body: { ok: true, removed: res.removed, state: res.state } };
@@ -21,6 +21,7 @@
21
21
  * 显式亮出独立开关删除。
22
22
  */
23
23
 
24
+ import { engineMsg } from './messages-core.ts';
24
25
  import os from 'node:os'
25
26
  import path from 'node:path'
26
27
 
@@ -40,39 +41,43 @@ export interface ReinstallItemDef {
40
41
  defaultOn: boolean
41
42
  }
42
43
 
44
+ /**
45
+ * 类别清单。label/desc 写成 **getter**(访问时才解析语言):本常量在模块加载期求值,
46
+ * 若直接调用 engineMsg 会把文案冻结成加载时的语言 —— 正是本次 i18n 改造要修的根因。
47
+ */
43
48
  export const REINSTALL_ITEMS: readonly ReinstallItemDef[] = [
44
49
  {
45
50
  id: 'program',
46
- label: 'DSH 程序本体',
47
- desc: '卸载并重装 @deepseek-ai/dsh 全局启动器(核心动作)',
51
+ get label() { return engineMsg('core.reinstall.programLabel') },
52
+ get desc() { return engineMsg('core.reinstall.programDesc') },
48
53
  destructive: false,
49
54
  defaultOn: true,
50
55
  },
51
56
  {
52
57
  id: 'cache',
53
- label: '全局残留缓存',
54
- desc: '清 pnpm store 与 npm cache(可选)',
58
+ get label() { return engineMsg('core.reinstall.cacheLabel') },
59
+ get desc() { return engineMsg('core.reinstall.cacheDesc') },
55
60
  destructive: false,
56
61
  defaultOn: true,
57
62
  },
58
63
  {
59
64
  id: 'settings',
60
- label: '~/.dsh 设置',
61
- desc: '清空 settings.yaml 等配置(不可逆)',
65
+ get label() { return engineMsg('core.reinstall.settingsLabel') },
66
+ get desc() { return engineMsg('core.reinstall.settingsDesc') },
62
67
  destructive: true,
63
68
  defaultOn: false,
64
69
  },
65
70
  {
66
71
  id: 'plugins',
67
- label: '~/.dsh 已装插件',
68
- desc: '清空 profiles 下已装插件依赖(不可逆)',
72
+ get label() { return engineMsg('core.reinstall.pluginsLabel') },
73
+ get desc() { return engineMsg('core.reinstall.pluginsDesc') },
69
74
  destructive: true,
70
75
  defaultOn: false,
71
76
  },
72
77
  {
73
78
  id: 'data',
74
- label: '会话记录与凭据',
75
- desc: '清空 sessions 会话与 .credentials.yaml 凭据(最高风险,强制二次确认)',
79
+ get label() { return engineMsg('core.reinstall.dataLabel') },
80
+ get desc() { return engineMsg('core.reinstall.dataDesc') },
76
81
  destructive: true,
77
82
  defaultOn: false,
78
83
  },
@@ -216,22 +221,22 @@ export async function buildReinstallPlan(
216
221
  const globalRoot = await resolveNpmGlobalRoot(exec)
217
222
  const dshGlobalDir = path.join(globalRoot, '@deepseek-ai', 'dsh')
218
223
  steps.push({
219
- label: '卸载全局 @deepseek-ai/dsh',
224
+ label: engineMsg('core.reinstall.stepUninstallGlobal'),
220
225
  command: 'npm uninstall -g @deepseek-ai/dsh',
221
226
  dangerous: false,
222
227
  })
223
228
  steps.push({
224
- label: '清理全局残留 @deepseek-ai/dsh',
229
+ label: engineMsg('core.reinstall.stepCleanGlobal'),
225
230
  command: rmCommand(dshGlobalDir, platform),
226
231
  dangerous: false,
227
232
  })
228
233
  steps.push({
229
- label: `安装全局 @deepseek-ai/dsh@${versionSpec}`,
234
+ label: engineMsg('core.reinstall.stepInstallGlobal', { version: versionSpec }),
230
235
  command: `npm install -g @deepseek-ai/dsh@${versionSpec}`,
231
236
  dangerous: false,
232
237
  })
233
238
  steps.push({
234
- label: '验证版本(dsh --version)',
239
+ label: engineMsg('core.reinstall.stepVerifyVersion'),
235
240
  command: 'dsh --version',
236
241
  dangerous: false,
237
242
  })
@@ -241,13 +246,13 @@ export async function buildReinstallPlan(
241
246
  const pnpmStore = resolvePnpmStoreDir(env, platform)
242
247
  if (pnpmStore !== null) {
243
248
  steps.push({
244
- label: '清理 pnpm store',
249
+ label: engineMsg('core.reinstall.stepCleanPnpmStore'),
245
250
  command: rmCommand(pnpmStore, platform),
246
251
  dangerous: false,
247
252
  })
248
253
  }
249
254
  steps.push({
250
- label: '清理 npm cache',
255
+ label: engineMsg('core.reinstall.stepCleanNpmCache'),
251
256
  command: 'npm cache clean --force',
252
257
  dangerous: false,
253
258
  })
@@ -259,33 +264,33 @@ export async function buildReinstallPlan(
259
264
  // 但历史会话/凭据在此备份,作为抢救兜底)。
260
265
  if (wipeConfig) {
261
266
  steps.push({
262
- label: '备份 ~/.dsh 到同级 .reinstall-backup(抢救兜底)',
267
+ label: engineMsg('core.reinstall.stepBackupHome'),
263
268
  command: copyDirCommand(dshHome, `${dshHome}.reinstall-backup`, platform),
264
269
  dangerous: true,
265
270
  })
266
271
  }
267
272
  if (selection.has('settings')) {
268
273
  steps.push({
269
- label: '清空 ~/.dsh 设置(settings.yaml 等)',
274
+ label: engineMsg('core.reinstall.stepWipeSettings'),
270
275
  command: rmCommand(path.join(dshHome, 'settings.yaml'), platform),
271
276
  dangerous: true,
272
277
  })
273
278
  }
274
279
  if (selection.has('plugins')) {
275
280
  steps.push({
276
- label: '清空 ~/.dsh 已装插件(profiles/*/node_modules)',
281
+ label: engineMsg('core.reinstall.stepWipePlugins'),
277
282
  command: rmCommand(path.join(dshHome, 'profiles'), platform),
278
283
  dangerous: true,
279
284
  })
280
285
  }
281
286
  if (selection.has('data')) {
282
287
  steps.push({
283
- label: '清空会话记录(sessions/)',
288
+ label: engineMsg('core.reinstall.stepWipeSessions'),
284
289
  command: rmCommand(path.join(dshHome, 'sessions'), platform),
285
290
  dangerous: true,
286
291
  })
287
292
  steps.push({
288
- label: '清空凭据(.credentials.yaml,仅删除文件)',
293
+ label: engineMsg('core.reinstall.stepWipeCredentials'),
289
294
  command: rmCommand(path.join(dshHome, '.credentials.yaml'), platform),
290
295
  dangerous: true,
291
296
  })
@@ -20,6 +20,7 @@
20
20
  * cordis.patch.yml);workspaceRecord 离线无法经 DSH storages 恢复 → 如实提示。
21
21
  * 旧快照(无 beforePlugins / hostFileBackups)兼容:缺基线的部分只提示不动作。
22
22
  */
23
+ import { engineMsg } from './messages-core.ts';
23
24
  import fs from 'node:fs/promises';
24
25
  import path from 'node:path';
25
26
 
@@ -281,10 +282,10 @@ export interface RestoreSnapshotValidation {
281
282
  /** 递归检查快照目录内关键文件非 symlink(F25:拒绝 symlink 化 metadata/blob 写穿)。 */
282
283
  async function assertNoSnapshotSymlink(dir: string, rel: string): Promise<string | null> {
283
284
  const p = path.join(dir, rel);
284
- if (!isWithinHome(dir, p)) return `路径越界: ${rel}`;
285
+ if (!isWithinHome(dir, p)) return engineMsg('core.restore.pathEscaped', { rel });
285
286
  try {
286
287
  const st = await fs.lstat(p);
287
- if (st.isSymbolicLink()) return `symlink 化关键文件: ${rel}`;
288
+ if (st.isSymbolicLink()) return engineMsg('core.restore.symlinkCriticalFile', { rel });
288
289
  return null;
289
290
  } catch {
290
291
  // 缺失由 verify/logic 处理(此处不判缺失)
@@ -310,12 +311,12 @@ export async function validateSnapshotForRestore(
310
311
  env?: { environmentFingerprint?: string },
311
312
  ): Promise<RestoreSnapshotValidation> {
312
313
  const id = path.basename(snapshotDir);
313
- if (!isValidSnapshotId(id)) return { verdict: 'INVALID', reason: 'snapshotId 非法' };
314
+ if (!isValidSnapshotId(id)) return { verdict: 'INVALID', reason: engineMsg('core.restore.invalidSnapshotId') };
314
315
 
315
316
  // 目录边界:snapshotDir 必须紧邻 snapshotsRoot 下的一层
316
317
  const rel = path.relative(snapshotsRoot, snapshotDir).split(path.sep).join('/');
317
318
  if (rel === '' || rel.includes('/') || !isWithinHome(snapshotsRoot, snapshotDir)) {
318
- return { verdict: 'INVALID', reason: '快照目录越界或非直接子目录' };
319
+ return { verdict: 'INVALID', reason: engineMsg('core.restore.snapshotDirOutOfBounds') };
319
320
  }
320
321
 
321
322
  // 关键文件 symlink 检查(F25)
@@ -339,7 +340,7 @@ export async function validateSnapshotForRestore(
339
340
  const blobEntries = await fs.readdir(blobsDir, { withFileTypes: true }).catch(() => [] as Array<{ name: string; isSymbolicLink(): boolean }>);
340
341
  for (const be of blobEntries) {
341
342
  if (be.isSymbolicLink()) {
342
- return { verdict: 'UNSAFE_PATH', reason: `blob 是 symlink: ${be.name}` };
343
+ return { verdict: 'UNSAFE_PATH', reason: engineMsg('core.restore.blobSymlink', { name: be.name }) };
343
344
  }
344
345
  }
345
346
 
@@ -352,9 +353,9 @@ export async function validateSnapshotForRestore(
352
353
  } catch {
353
354
  /* verifySnapshot 已报缺失 */
354
355
  }
355
- if (snapshot === null || typeof snapshot !== 'object') return { verdict: 'CORRUPT', reason: 'snapshot.json 无法解析' };
356
+ if (snapshot === null || typeof snapshot !== 'object') return { verdict: 'CORRUPT', reason: engineMsg('core.restore.snapshotJsonUnparsable') };
356
357
  if (!manifestExists && snapshot.readiness === undefined) {
357
- return { verdict: 'LEGACY_REQUIRES_CONFIRMATION', reason: '旧快照(无 manifest/READY),需显式确认后恢复' };
358
+ return { verdict: 'LEGACY_REQUIRES_CONFIRMATION', reason: engineMsg('core.restore.legacyNeedsConfirm') };
358
359
  }
359
360
 
360
361
  // 结构 + 完整性(磁盘重读,不信任内存)
@@ -364,18 +365,18 @@ export async function validateSnapshotForRestore(
364
365
  // 环境绑定(WRONG_ENVIRONMENT)
365
366
  if (env?.environmentFingerprint && snapshot.readiness === 'READY' && snapshot.environmentFingerprint
366
367
  && snapshot.environmentFingerprint !== env.environmentFingerprint) {
367
- return { verdict: 'WRONG_ENVIRONMENT', reason: 'environmentFingerprint 不匹配(可能来自其他机器/安装)' };
368
+ return { verdict: 'WRONG_ENVIRONMENT', reason: engineMsg('core.restore.envFingerprintMismatch') };
368
369
  }
369
370
 
370
371
  // provenance 分类
371
372
  const hasBinding = snapshot.operationId !== undefined || snapshot.environmentFingerprint !== undefined || snapshot.ownerInstanceId !== undefined;
372
373
  if (snapshot.readiness === undefined) {
373
- return { verdict: 'LEGACY_REQUIRES_CONFIRMATION', reason: '旧快照(无 readiness),需显式确认后恢复' };
374
+ return { verdict: 'LEGACY_REQUIRES_CONFIRMATION', reason: engineMsg('core.restore.legacyNoReadiness') };
374
375
  }
375
376
  if (!hasBinding) {
376
- return { verdict: 'TRUSTED_MANUAL_LOCAL', reason: '完整性通过,但非 operation-bound(手动/本地快照)' };
377
+ return { verdict: 'TRUSTED_MANUAL_LOCAL', reason: engineMsg('core.restore.trustedManualLocal') };
377
378
  }
378
- return { verdict: 'TRUSTED_OPERATION_SNAPSHOT', reason: 'READY + manifest 完整 + op/env/owner binding 匹配' };
379
+ return { verdict: 'TRUSTED_OPERATION_SNAPSHOT', reason: engineMsg('core.restore.trustedOperationSnapshot') };
379
380
  }
380
381
 
381
382
 
@@ -434,9 +435,11 @@ export async function planRestore(opts: RestoreOptions): Promise<RestorePlan> {
434
435
  const currentExists = await fileExists(abs);
435
436
  if (backup.existed) {
436
437
  if (backup.blobPath === '') {
438
+ // unreadable = 拍快照时文件存在但读不出来:本次**只跳过并如实说明**,绝不因为
439
+ // 「快照里没有字节」就把它当成「当时不存在」(那会在恢复时删掉用户真实的文件)。
437
440
  actions.push({
438
441
  kind: 'skip',
439
- description: msg('restore.hostNoBlobInfo', { path: backup.relPath }),
442
+ description: msg(backup.unreadable === true ? 'restore.hostUnreadable' : 'restore.hostNoBlobInfo', { path: backup.relPath }),
440
443
  target: backup.relPath,
441
444
  });
442
445
  continue;
@@ -850,7 +853,7 @@ export async function listSnapshots(
850
853
  // 其余(EACCES / EPERM / ENOTDIR…)经 out.unreadable 回传,由调用方如实展示。
851
854
  const code = (error as { code?: string }).code;
852
855
  if (code !== 'ENOENT' && out !== undefined) {
853
- out.unreadable = '快照目录读不出来(' + String(code ?? 'unknown') + '):' + dir;
856
+ out.unreadable = engineMsg('core.restore.snapshotDirUnreadable', { code: String(code ?? 'unknown'), dir });
854
857
  }
855
858
  return [];
856
859
  }
@@ -891,11 +894,11 @@ export function isValidSnapshotId(id: unknown): id is string {
891
894
  * 不存在视为成功(幂等)。返回是否实际删除。 */
892
895
  export async function deleteSnapshot(snapshotsDir: string, id: string): Promise<boolean> {
893
896
  if (!isValidSnapshotId(id)) {
894
- throw new Error(`非法快照 id: ${JSON.stringify(id)}`);
897
+ throw new Error(engineMsg('core.restore.illegalSnapshotId', { id: JSON.stringify(id) }));
895
898
  }
896
899
  const target = path.join(snapshotsDir, id);
897
900
  if (!target.startsWith(path.resolve(snapshotsDir) + path.sep)) {
898
- throw new Error(`快照路径越界: ${id}`);
901
+ throw new Error(engineMsg('core.restore.snapshotPathEscaped', { id }));
899
902
  }
900
903
  try {
901
904
  await fs.stat(target);
@@ -910,12 +913,12 @@ export async function deleteSnapshot(snapshotsDir: string, id: string): Promise<
910
913
  * 只接受合法 id;快照不存在抛错。 */
911
914
  export async function setSnapshotPinned(snapshotsDir: string, id: string, pinned: boolean): Promise<boolean> {
912
915
  if (!isValidSnapshotId(id)) {
913
- throw new Error(`非法快照 id: ${JSON.stringify(id)}`);
916
+ throw new Error(engineMsg('core.restore.illegalSnapshotId', { id: JSON.stringify(id) }));
914
917
  }
915
918
  const file = path.join(snapshotsDir, id, 'snapshot.json');
916
919
  const resolvedRoot = path.resolve(snapshotsDir) + path.sep;
917
920
  if (!file.startsWith(resolvedRoot)) {
918
- throw new Error(`快照路径越界: ${id}`);
921
+ throw new Error(engineMsg('core.restore.snapshotPathEscaped', { id }));
919
922
  }
920
923
  const snapshot = parseJsonSafe(await fs.readFile(file, 'utf8')) as Snapshot;
921
924
  snapshot.pinned = pinned;
@@ -0,0 +1,159 @@
1
+ /**
2
+ * 回滚点「单文件前后对照」(`RollbackPointEngine.fileDiff`)单测。
3
+ *
4
+ * 纪律与本仓既有 core 测试同款:真实临时目录 + 真实读写,不 mock 文件系统。
5
+ *
6
+ * 覆盖的是这个端点的**收窄与诚实性**(它是新的只读读取面,用户 2026-10-12 的「查看」数据源):
7
+ * · 方向必须是 before = **当前磁盘** / after = **回退点快照**(读反了整屏差异都是反的);
8
+ * · 只认**记录在案**的路径(未记录 ⇒ file-not-recorded,绝不变成任意路径读取器);
9
+ * · 二进制 / 超限 / 读不出一律结构化 reason + 空 hunks(「不知道」不得显示成「没有差异」);
10
+ * · 全程**零写入**(工作区字节不变、对象仓不新增)。
11
+ */
12
+ import test from 'node:test'
13
+ import assert from 'node:assert/strict'
14
+ import fs from 'node:fs/promises'
15
+ import os from 'node:os'
16
+ import path from 'node:path'
17
+
18
+ import { RollbackPointEngine } from './rollback-point.ts'
19
+
20
+ interface Fixture {
21
+ root: string
22
+ homeDir: string
23
+ dataDir: string
24
+ workspaceDir: string
25
+ cleanup: () => Promise<void>
26
+ }
27
+
28
+ async function makeFixture(): Promise<Fixture> {
29
+ const root = await fs.mkdtemp(path.join(os.tmpdir(), 'dsh-cm-rollback-file-diff-'))
30
+ const homeDir = path.join(root, 'home')
31
+ const dataDir = path.join(homeDir, 'dsh-config-manager')
32
+ const workspaceDir = path.join(root, 'ws')
33
+ await fs.mkdir(workspaceDir, { recursive: true })
34
+ await fs.writeFile(path.join(workspaceDir, 'a.txt'), 'A1\nA2\n')
35
+ return { root, homeDir, dataDir, workspaceDir, cleanup: async () => { await fs.rm(root, { recursive: true, force: true }) } }
36
+ }
37
+
38
+ function makeEngine(fx: Fixture): RollbackPointEngine {
39
+ return new RollbackPointEngine({ dataDir: fx.dataDir, homeDir: fx.homeDir, sessionQuiescentMs: 0 })
40
+ }
41
+
42
+ /** 留一个点(只带工作区态),返回点 id。 */
43
+ async function capture(engine: RollbackPointEngine, fx: Fixture): Promise<string> {
44
+ const captured = await engine.capturePoint({ chunks: [fx.workspaceDir], sessionId: 'session-1111', turnSeq: 1 })
45
+ const id = captured.point?.id
46
+ assert.ok(typeof id === 'string' && id !== '', 'fixture 必须先真的留下一个点:' + JSON.stringify(captured))
47
+ return id as string
48
+ }
49
+
50
+ test('fileDiff:before = 当前磁盘、after = 回退点快照(方向不许读反)', async () => {
51
+ const fx = await makeFixture()
52
+ try {
53
+ const engine = makeEngine(fx)
54
+ const id = await capture(engine, fx)
55
+ const file = path.join(fx.workspaceDir, 'a.txt')
56
+ // 留点**之后**改文件:回退会把 A2 删掉、把 A3 加回来
57
+ await fs.writeFile(file, 'A1\nA3\n')
58
+ const diff = await engine.fileDiff(id, file)
59
+ assert.equal(diff.ok, true, JSON.stringify(diff))
60
+ assert.equal(diff.status, 'overwrite')
61
+ assert.equal(diff.before.exists, true)
62
+ assert.equal(diff.after.exists, true)
63
+ assert.equal(diff.identical, false)
64
+ // 方向证据:删掉的是当前独有的 A3,新增的是快照里的 A2
65
+ assert.equal(diff.removed, 1)
66
+ assert.equal(diff.added, 1)
67
+ const texts = diff.hunks.flatMap((hunk) => hunk.lines.map((line) => line.kind + ':' + line.text))
68
+ assert.ok(texts.includes('del:A3'), '当前独有的行必须是删除:' + JSON.stringify(texts))
69
+ assert.ok(texts.includes('add:A2'), '快照独有的行必须是新增:' + JSON.stringify(texts))
70
+ } finally {
71
+ await fx.cleanup()
72
+ }
73
+ })
74
+
75
+ test('fileDiff:当前文件不存在(ENOENT)→ status=create,新增 = 快照全部行', async () => {
76
+ const fx = await makeFixture()
77
+ try {
78
+ const engine = makeEngine(fx)
79
+ const id = await capture(engine, fx)
80
+ const file = path.join(fx.workspaceDir, 'a.txt')
81
+ await fs.rm(file)
82
+ const diff = await engine.fileDiff(id, file)
83
+ assert.equal(diff.ok, true)
84
+ assert.equal(diff.status, 'create')
85
+ assert.equal(diff.before.exists, false)
86
+ assert.equal(diff.after.exists, true)
87
+ assert.equal(diff.removed, 0)
88
+ assert.equal(diff.added, diff.after.lines)
89
+ } finally {
90
+ await fx.cleanup()
91
+ }
92
+ })
93
+
94
+ test('fileDiff:未记录在案的路径 → file-not-recorded(这条端点不是任意路径读取器)', async () => {
95
+ const fx = await makeFixture()
96
+ try {
97
+ const engine = makeEngine(fx)
98
+ const id = await capture(engine, fx)
99
+ const outside = path.join(fx.root, 'outside.txt')
100
+ await fs.writeFile(outside, 'secret\n')
101
+ const diff = await engine.fileDiff(id, outside)
102
+ assert.equal(diff.ok, false)
103
+ assert.equal(diff.code, 'file-not-recorded')
104
+ assert.deepEqual(diff.hunks, [], '未记录的路径不得给出任何差异内容')
105
+ } finally {
106
+ await fx.cleanup()
107
+ }
108
+ })
109
+
110
+ test('fileDiff:二进制 → reason=binary 且 hunks 空(不把「不知道」说成「没有差异」)', async () => {
111
+ const fx = await makeFixture()
112
+ try {
113
+ const engine = makeEngine(fx)
114
+ const binary = path.join(fx.workspaceDir, 'blob.bin')
115
+ await fs.writeFile(binary, Buffer.from([0, 1, 2, 3, 0, 9]))
116
+ const id = await capture(engine, fx)
117
+ await fs.writeFile(binary, Buffer.from([0, 5, 6, 7, 0, 9]))
118
+ const diff = await engine.fileDiff(id, binary)
119
+ assert.equal(diff.ok, true)
120
+ assert.equal(diff.reason, 'binary')
121
+ assert.deepEqual(diff.hunks, [])
122
+ assert.equal(diff.identical, false, '无法逐行对比不得被说成「一致」')
123
+ } finally {
124
+ await fx.cleanup()
125
+ }
126
+ })
127
+
128
+ test('fileDiff:未知 id → record-not-found(结构化码,不抛)', async () => {
129
+ const fx = await makeFixture()
130
+ try {
131
+ const engine = makeEngine(fx)
132
+ await capture(engine, fx)
133
+ const diff = await engine.fileDiff('nope', path.join(fx.workspaceDir, 'a.txt'))
134
+ assert.equal(diff.ok, false)
135
+ assert.equal(diff.code, 'record-not-found')
136
+ } finally {
137
+ await fx.cleanup()
138
+ }
139
+ })
140
+
141
+ test('fileDiff:零写入 —— 对照前后工作区字节与对象仓条目都不变', async () => {
142
+ const fx = await makeFixture()
143
+ try {
144
+ const engine = makeEngine(fx)
145
+ const id = await capture(engine, fx)
146
+ const file = path.join(fx.workspaceDir, 'a.txt')
147
+ await fs.writeFile(file, 'A1\nCHANGED\n')
148
+ const before = await fs.readFile(file)
149
+ const objectsDir = path.join(engine.storeDir, 'objects')
150
+ const objectsBefore = await fs.readdir(objectsDir)
151
+ const ledgerBefore = await fs.readFile(path.join(engine.storeDir, 'index.json'), 'utf8')
152
+ await engine.fileDiff(id, file)
153
+ assert.deepEqual(await fs.readFile(file), before, '只读对照不得改动工作区文件')
154
+ assert.deepEqual(await fs.readdir(objectsDir), objectsBefore, '只读对照不得写对象仓')
155
+ assert.equal(await fs.readFile(path.join(engine.storeDir, 'index.json'), 'utf8'), ledgerBefore, '只读对照不得写台账')
156
+ } finally {
157
+ await fx.cleanup()
158
+ }
159
+ })
@@ -0,0 +1,66 @@
1
+ /**
2
+ * 回合自动留点开关的**进程内**语义(issue #88 / #89,src/core/rollback-gate.ts)。
3
+ *
4
+ * 钉住四条:① 缺省关闭;② 变化才通知(同值不通知,避免写偏好时抖动 attach/detach);
5
+ * ③ 监听器抛错不影响开关本身与其它监听器;④ 解绑幂等。
6
+ *
7
+ * 为什么值得单测:宿主那条 `session/event` 订阅链的 attach/detach 完全由这里驱动 ——
8
+ * 一旦「关了还通知」或「通知丢了」,就是「界面说关了、宿主还在每回合写台账」这类静默故障。
9
+ */
10
+ import test from 'node:test'
11
+ import assert from 'node:assert/strict'
12
+
13
+ import {
14
+ isRollbackEnabled,
15
+ onRollbackEnabledChange,
16
+ resetRollbackGateForTest,
17
+ setRollbackEnabled,
18
+ } from './rollback-gate.ts'
19
+
20
+ test('G-01 缺省关闭 + 置真置假都生效', () => {
21
+ resetRollbackGateForTest()
22
+ assert.equal(isRollbackEnabled(), false, '缺省必须是关闭(fail-closed)')
23
+ setRollbackEnabled(true)
24
+ assert.equal(isRollbackEnabled(), true)
25
+ setRollbackEnabled(false)
26
+ assert.equal(isRollbackEnabled(), false)
27
+ resetRollbackGateForTest()
28
+ })
29
+
30
+ test('G-02 同值不通知;变化按顺序通知一次', () => {
31
+ resetRollbackGateForTest()
32
+ const seen: boolean[] = []
33
+ const off = onRollbackEnabledChange((value) => { seen.push(value) })
34
+ setRollbackEnabled(false) // 与缺省同值 → 不通知
35
+ assert.deepEqual(seen, [], '同值不得通知')
36
+ setRollbackEnabled(true)
37
+ setRollbackEnabled(true) // 重复同值
38
+ setRollbackEnabled(false)
39
+ assert.deepEqual(seen, [true, false], '只在真实变化时通知一次')
40
+ off()
41
+ resetRollbackGateForTest()
42
+ })
43
+
44
+ test('G-03 监听器抛错不影响开关与其它监听器', () => {
45
+ resetRollbackGateForTest()
46
+ const seen: boolean[] = []
47
+ const offBad = onRollbackEnabledChange(() => { throw new Error('boom') })
48
+ const offGood = onRollbackEnabledChange((value) => { seen.push(value) })
49
+ setRollbackEnabled(true)
50
+ assert.equal(isRollbackEnabled(), true, '坏监听器不得拦住状态切换')
51
+ assert.deepEqual(seen, [true], '坏监听器不得吃掉后面监听器的通知')
52
+ offBad()
53
+ offGood()
54
+ resetRollbackGateForTest()
55
+ })
56
+
57
+ test('G-04 解绑幂等:解绑后再改状态不通知已解绑者', () => {
58
+ resetRollbackGateForTest()
59
+ const seen: boolean[] = []
60
+ const off = onRollbackEnabledChange((value) => { seen.push(value) })
61
+ off()
62
+ off() // 幂等
63
+ setRollbackEnabled(true)
64
+ assert.deepEqual(seen, [])
65
+ resetRollbackGateForTest()
66
+ })