dsh-config-manager 0.1.71 → 0.1.72

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 (548) hide show
  1. package/README.md +111 -96
  2. package/README.zh-CN.md +207 -86
  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 +1 -1
  6. package/lib/adapters/index.js +1 -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 +18 -0
  10. package/lib/adapters/messages.js +50 -0
  11. package/lib/adapters/plugin-files.js +3 -2
  12. package/lib/adapters/plugins.js +4 -3
  13. package/lib/adapters/prompts.d.ts +1 -0
  14. package/lib/adapters/prompts.js +7 -1
  15. package/lib/adapters/self.d.ts +7 -1
  16. package/lib/adapters/self.js +1 -1
  17. package/lib/adapters/sessions.d.ts +1 -0
  18. package/lib/adapters/sessions.js +4 -2
  19. package/lib/adapters/ui.d.ts +2 -2
  20. package/lib/adapters/ui.js +25 -25
  21. package/lib/adapters/workspaces.d.ts +1 -0
  22. package/lib/adapters/workspaces.js +7 -3
  23. package/lib/cli/actions.d.ts +9 -1
  24. package/lib/cli/actions.js +5 -2
  25. package/lib/cli/help.js +23 -10
  26. package/lib/cli/index.d.ts +23 -0
  27. package/lib/cli/index.js +90 -32
  28. package/lib/cli/sessions-inspect.js +3 -1
  29. package/lib/cli/sessions-repair.js +25 -6
  30. package/lib/cli/web/page.d.ts +12 -0
  31. package/lib/cli/web/page.js +21 -12
  32. package/lib/cli/web/routes.js +2 -2
  33. package/lib/cli/web/server.js +4 -4
  34. package/lib/client.d.ts +1349 -490
  35. package/lib/client.js +43047 -36738
  36. package/lib/core/backup-plan.js +30 -17
  37. package/lib/core/backup-verify.js +24 -23
  38. package/lib/core/backup.d.ts +5 -0
  39. package/lib/core/backup.js +51 -25
  40. package/lib/core/boot-rescue.js +4 -3
  41. package/lib/core/boot-safety.js +2 -1
  42. package/lib/core/command-rollback.d.ts +178 -0
  43. package/lib/core/command-rollback.js +289 -0
  44. package/lib/core/consult-source.js +7 -6
  45. package/lib/core/disk-usage.d.ts +74 -10
  46. package/lib/core/disk-usage.js +67 -4
  47. package/lib/core/engine-locale.d.ts +87 -0
  48. package/lib/core/engine-locale.js +125 -0
  49. package/lib/core/exporter.js +3 -3
  50. package/lib/core/fork-reconcile.d.ts +128 -0
  51. package/lib/core/fork-reconcile.js +150 -0
  52. package/lib/core/journal.js +6 -5
  53. package/lib/core/local-plugin-host.js +3 -2
  54. package/lib/core/local-plugin-pack.js +11 -6
  55. package/lib/core/messages-core.d.ts +292 -0
  56. package/lib/core/messages-core.js +615 -0
  57. package/lib/core/messages.d.ts +60 -2
  58. package/lib/core/messages.js +116 -13
  59. package/lib/core/migration-consult.js +21 -20
  60. package/lib/core/migration-history.js +19 -5
  61. package/lib/core/model-tools.d.ts +0 -22
  62. package/lib/core/model-tools.js +50 -27
  63. package/lib/core/phase3-host.js +30 -5
  64. package/lib/core/plugin-cli.js +18 -19
  65. package/lib/core/reconcile.d.ts +2 -14
  66. package/lib/core/reconcile.js +87 -17
  67. package/lib/core/recovery-orchestrator.js +19 -18
  68. package/lib/core/reinstall.d.ts +4 -0
  69. package/lib/core/reinstall.js +26 -21
  70. package/lib/core/restore.js +20 -17
  71. package/lib/core/rollback-ignore-rules.d.ts +31 -0
  72. package/lib/core/rollback-ignore-rules.js +140 -0
  73. package/lib/core/rollback-point.d.ts +1208 -0
  74. package/lib/core/rollback-point.js +2993 -0
  75. package/lib/core/run-registry.js +6 -5
  76. package/lib/core/session-health.d.ts +15 -0
  77. package/lib/core/session-meta.d.ts +42 -2
  78. package/lib/core/session-meta.js +140 -1
  79. package/lib/core/session-title.d.ts +28 -0
  80. package/lib/core/session-title.js +185 -0
  81. package/lib/core/startup-barrier.d.ts +24 -0
  82. package/lib/core/startup-barrier.js +22 -0
  83. package/lib/core/transaction-coordinator.js +4 -3
  84. package/lib/core/types.d.ts +18 -1
  85. package/lib/core/validator.js +5 -4
  86. package/lib/core/verify-recovery.js +31 -30
  87. package/lib/foreign/bundle.js +3 -2
  88. package/lib/foreign/continue.js +2 -1
  89. package/lib/foreign/crush.js +2 -1
  90. package/lib/foreign/gemini.js +2 -1
  91. package/lib/foreign/goose.js +2 -1
  92. package/lib/foreign/kilocode.js +2 -1
  93. package/lib/foreign/kimi.js +2 -1
  94. package/lib/foreign/messages.d.ts +15 -0
  95. package/lib/foreign/messages.js +40 -0
  96. package/lib/foreign/mimocode.js +2 -1
  97. package/lib/foreign/openclaw.js +2 -1
  98. package/lib/foreign/opencode.js +2 -1
  99. package/lib/foreign/pi.js +2 -1
  100. package/lib/foreign/qoder.js +2 -1
  101. package/lib/foreign/qwen.js +2 -1
  102. package/lib/foreign/registry.js +7 -6
  103. package/lib/foreign/teleagent.js +2 -1
  104. package/lib/foreign/workbuddy.js +2 -1
  105. package/lib/foreign/zcode.js +2 -1
  106. package/lib/foreign/zed.js +2 -1
  107. package/lib/index.d.ts +223 -7
  108. package/lib/index.js +729 -54
  109. package/lib/market/git-file-writer.js +20 -19
  110. package/lib/market/github-repos.js +35 -19
  111. package/lib/market/index-parser.js +31 -30
  112. package/lib/market/messages-market.d.ts +138 -0
  113. package/lib/market/messages-market.js +305 -0
  114. package/lib/market/my-repo.d.ts +2 -2
  115. package/lib/market/my-repo.js +32 -29
  116. package/lib/market/prepare.js +18 -15
  117. package/lib/market/security.js +20 -19
  118. package/lib/market/types.js +2 -1
  119. package/lib/market/url.js +2 -1
  120. package/lib/migrations/index.js +6 -5
  121. package/lib/migrations/messages-migrations.d.ts +32 -0
  122. package/lib/migrations/messages-migrations.js +58 -0
  123. package/lib/profiles/dsh-profile-launcher.js +2 -1
  124. package/lib/profiles/dsh-profile-runtime.js +2 -1
  125. package/lib/profiles/messages.d.ts +7 -0
  126. package/lib/profiles/messages.js +27 -0
  127. package/lib/routes/backup.js +18 -2
  128. package/lib/routes/import.js +3 -3
  129. package/lib/routes/index.d.ts +1 -1
  130. package/lib/routes/kit.d.ts +3 -2
  131. package/lib/routes/kit.js +6 -1
  132. package/lib/routes/market.js +1 -1
  133. package/lib/routes/messages.d.ts +42 -0
  134. package/lib/routes/messages.js +100 -0
  135. package/lib/routes/profiles.js +12 -6
  136. package/lib/routes/recovery.js +378 -114
  137. package/lib/routes/snapshots.js +51 -4
  138. package/lib/routes/sync.js +68 -16
  139. package/lib/schema/config.d.ts +11 -1
  140. package/lib/schema/config.js +52 -20
  141. package/lib/schema/index.d.ts +8 -6
  142. package/lib/schema/index.js +8 -6
  143. package/lib/schema/manifest.js +19 -18
  144. package/lib/schema/messages.d.ts +47 -0
  145. package/lib/schema/messages.js +110 -0
  146. package/lib/schema/section-registry.d.ts +41 -14
  147. package/lib/schema/section-registry.js +35 -29
  148. package/lib/schema/versions.js +6 -5
  149. package/lib/security/encryption.js +16 -15
  150. package/lib/security/integrity.js +8 -7
  151. package/lib/security/messages.d.ts +30 -0
  152. package/lib/security/messages.js +77 -0
  153. package/lib/security/redaction.js +12 -0
  154. package/lib/security/secret-scanner.js +16 -13
  155. package/lib/security/vault.js +2 -1
  156. package/lib/security/zip-security.js +4 -3
  157. package/lib/sync/ancestor.js +2 -1
  158. package/lib/sync/autosync-config.d.ts +11 -0
  159. package/lib/sync/autosync-config.js +12 -0
  160. package/lib/sync/autosync-scheduler.d.ts +11 -1
  161. package/lib/sync/autosync-scheduler.js +115 -76
  162. package/lib/sync/backup-files.js +3 -2
  163. package/lib/sync/backup-scheduler.js +6 -5
  164. package/lib/sync/blob-store.js +2 -1
  165. package/lib/sync/git/git-transport.d.ts +55 -0
  166. package/lib/sync/git/git-transport.js +152 -2
  167. package/lib/sync/github-auth.js +10 -9
  168. package/lib/sync/layout.js +13 -12
  169. package/lib/sync/messages.d.ts +63 -0
  170. package/lib/sync/messages.js +149 -0
  171. package/lib/sync/review-queue.js +6 -5
  172. package/lib/sync/s3/messages.d.ts +7 -0
  173. package/lib/sync/s3/messages.js +16 -0
  174. package/lib/sync/s3/s3-providers.d.ts +4 -1
  175. package/lib/sync/s3/s3-providers.js +5 -5
  176. package/lib/sync/s3/s3-transport.js +1 -1
  177. package/lib/sync/s3/sigv4.js +2 -1
  178. package/lib/sync/snapshot-crypto.js +6 -5
  179. package/lib/sync/snapshot-json.js +3 -2
  180. package/lib/sync/sync-engine.d.ts +37 -15
  181. package/lib/sync/sync-engine.js +67 -37
  182. package/lib/sync/sync-history.js +7 -6
  183. package/lib/sync/sync-selection.d.ts +15 -4
  184. package/lib/sync/sync-selection.js +17 -5
  185. package/lib/sync/webdav/webdav-transport.d.ts +1 -0
  186. package/lib/sync/webdav/webdav-transport.js +4 -1
  187. package/lib/ui/artifact-view.js +4 -3
  188. package/lib/ui/commands.d.ts +1 -1
  189. package/lib/ui/commands.js +10 -0
  190. package/lib/ui/disk-usage-view.d.ts +33 -4
  191. package/lib/ui/disk-usage-view.js +29 -1
  192. package/lib/ui/errors.d.ts +1 -1
  193. package/lib/ui/errors.js +4 -3
  194. package/lib/ui/export-flow.js +24 -20
  195. package/lib/ui/foreign-view.d.ts +5 -1
  196. package/lib/ui/foreign-view.js +26 -2
  197. package/lib/ui/i18n.d.ts +65 -0
  198. package/lib/ui/i18n.js +114 -0
  199. package/lib/ui/import-wizard.d.ts +7 -0
  200. package/lib/ui/import-wizard.js +13 -10
  201. package/lib/ui/progress.d.ts +2 -2
  202. package/lib/ui/progress.js +1 -2
  203. package/lib/ui/report.d.ts +5 -5
  204. package/lib/ui/report.js +6 -6
  205. package/lib/ui/rollback-view.d.ts +756 -0
  206. package/lib/ui/rollback-view.js +724 -0
  207. package/lib/ui/selection-model.d.ts +41 -0
  208. package/lib/ui/selection-model.js +30 -0
  209. package/lib/ui/session-inventory-view.d.ts +97 -3
  210. package/lib/ui/session-inventory-view.js +76 -2
  211. package/lib/ui/session-issue-labels.d.ts +43 -0
  212. package/lib/ui/session-issue-labels.js +48 -0
  213. package/lib/ui/sync-settings-view.d.ts +33 -0
  214. package/lib/ui/sync-settings-view.js +40 -0
  215. package/lib/ui/types.d.ts +8 -3
  216. package/lib/ui/types.js +1 -1
  217. package/lib/utils/atomic-write.js +6 -5
  218. package/lib/utils/bundle-scan.d.ts +11 -1
  219. package/lib/utils/bundle-scan.js +12 -1
  220. package/lib/utils/env-lock.d.ts +10 -0
  221. package/lib/utils/env-lock.js +73 -46
  222. package/lib/utils/json.js +6 -5
  223. package/lib/utils/messages-utils.d.ts +96 -0
  224. package/lib/utils/messages-utils.js +216 -0
  225. package/lib/utils/proxy.js +18 -5
  226. package/lib/utils/recursive-walk.d.ts +22 -0
  227. package/lib/utils/recursive-walk.js +75 -1
  228. package/lib/utils/session-log-repair.js +10 -9
  229. package/lib/utils/zip.js +27 -26
  230. package/package.json +12 -2
  231. package/src/adapters/agent-instructions.ts +1 -1
  232. package/src/adapters/file-collection.test.ts +1 -0
  233. package/src/adapters/file-collection.ts +1 -1
  234. package/src/adapters/files.test.ts +27 -0
  235. package/src/adapters/index.ts +1 -1
  236. package/src/adapters/link-report.test.ts +22 -2
  237. package/src/adapters/link-report.ts +29 -4
  238. package/src/adapters/messages.ts +54 -0
  239. package/src/adapters/plugin-files.ts +3 -2
  240. package/src/adapters/plugins.ts +4 -3
  241. package/src/adapters/prompts.ts +7 -1
  242. package/src/adapters/self.test.ts +26 -0
  243. package/src/adapters/self.ts +8 -2
  244. package/src/adapters/sessions.ts +4 -2
  245. package/src/adapters/settings.test.ts +38 -1
  246. package/src/adapters/ui.test.ts +2 -2
  247. package/src/adapters/ui.ts +27 -26
  248. package/src/adapters/workspaces.ts +7 -3
  249. package/src/cli/actions.test.ts +28 -0
  250. package/src/cli/actions.ts +11 -2
  251. package/src/cli/help.ts +23 -10
  252. package/src/cli/index-guards.test.ts +193 -0
  253. package/src/cli/index.ts +106 -31
  254. package/src/cli/sessions-inspect.test.ts +15 -1
  255. package/src/cli/sessions-inspect.ts +3 -1
  256. package/src/cli/sessions-repair.test.ts +84 -0
  257. package/src/cli/sessions-repair.ts +33 -7
  258. package/src/cli/web/console-route-parity.test.ts +115 -0
  259. package/src/cli/web/page.ts +23 -12
  260. package/src/cli/web/routes.ts +2 -1
  261. package/src/cli/web/server.ts +4 -4
  262. package/src/cli/web/web.test.ts +2 -2
  263. package/src/client/ConfigManagerSection.tsx +30 -2
  264. package/src/client/about/AboutPanel.tsx +2 -2
  265. package/src/client/about/PluginUpdateDialog.tsx +1 -1
  266. package/src/client/about/ReleaseNotesDialog.tsx +2 -2
  267. package/src/client/about/about-view.test.ts +13 -3
  268. package/src/client/about/about-view.ts +10 -6
  269. package/src/client/about/release-notes-view.test.ts +14 -4
  270. package/src/client/about/release-notes-view.ts +4 -2
  271. package/src/client/api.ts +18 -6
  272. package/src/client/chat/ChatEntryView.tsx +391 -0
  273. package/src/client/chat/RollbackDiffTab.tsx +61 -0
  274. package/src/client/chat/RollbackFileIcon.tsx +96 -0
  275. package/src/client/chat/RollbackReviewDialog.tsx +591 -0
  276. package/src/client/chat/chat-entry-view.test.ts +1174 -0
  277. package/src/client/chat/chat-entry-view.ts +758 -0
  278. package/src/client/chat/chat-locales.ts +152 -0
  279. package/src/client/chat/chat-node-registration.ts +64 -0
  280. package/src/client/chat/chat-point-window.ts +173 -0
  281. package/src/client/chat/chat-slots.ts +107 -0
  282. package/src/client/chat/rollback-diff-tab.test.ts +105 -0
  283. package/src/client/chat/rollback-diff-tab.ts +164 -0
  284. package/src/client/chat/rollback-entry-view.test.ts +1469 -0
  285. package/src/client/chat/rollback-entry-view.ts +941 -0
  286. package/src/client/chat/sidebar-slots.ts +37 -0
  287. package/src/client/common/ConfirmDialog.tsx +7 -3
  288. package/src/client/common/ContentPicker.tsx +48 -10
  289. package/src/client/common/DiffTable.tsx +97 -0
  290. package/src/client/common/ErrorBanner.tsx +4 -4
  291. package/src/client/common/Icon.tsx +2 -0
  292. package/src/client/common/Modal.tsx +8 -1
  293. package/src/client/common/ProgressBar.tsx +8 -2
  294. package/src/client/common/ReportView.tsx +4 -4
  295. package/src/client/common/RunsCenter.tsx +1 -0
  296. package/src/client/common/client-route-parity.test.ts +78 -0
  297. package/src/client/common/info-hint-guard.test.ts +46 -13
  298. package/src/client/common/picker-lazy-units.test.ts +42 -0
  299. package/src/client/common/plan-text-redaction.test.ts +17 -6
  300. package/src/client/common/progress-view-edge.test.ts +8 -7
  301. package/src/client/common/progress-view.test.ts +9 -9
  302. package/src/client/common/progress-view.ts +2 -2
  303. package/src/client/common/routes.ts +28 -0
  304. package/src/client/common/ui.tsx +73 -10
  305. package/src/client/config-manager.module.css +996 -21
  306. package/src/client/css-scale-guard.test.ts +30 -0
  307. package/src/client/environment/EnvironmentPanel.tsx +11 -4
  308. package/src/client/environment/maintenance/DiskUsageCard.tsx +23 -2
  309. package/src/client/export/ExportView.tsx +13 -8
  310. package/src/client/history/HistoryPanel.tsx +4 -3
  311. package/src/client/history/history-api.ts +2 -2
  312. package/src/client/history/history-locales.ts +5 -1
  313. package/src/client/home/HomePanel.tsx +29 -8
  314. package/src/client/import/ForeignImportView.tsx +4 -5
  315. package/src/client/import/ImportWizardView.tsx +5 -3
  316. package/src/client/import/import-wizard-split.test.ts +3 -1
  317. package/src/client/import/import-wizard-steps.tsx +9 -9
  318. package/src/client/index.ts +186 -3
  319. package/src/client/library/LibraryActions.tsx +32 -7
  320. package/src/client/library/LibraryPanel.tsx +23 -3
  321. package/src/client/library/RestorePlanView.tsx +9 -66
  322. package/src/client/locales.ts +54 -5
  323. package/src/client/lucide-icons.d.ts +1 -0
  324. package/src/client/market/MarketPanel.tsx +13 -2
  325. package/src/client/market/MyConfigsView.tsx +5 -4
  326. package/src/client/market/MyConfigsWizard.tsx +10 -8
  327. package/src/client/market/market-api.test.ts +9 -8
  328. package/src/client/market/market-api.ts +2 -2
  329. package/src/client/market/market-locales.ts +3 -0
  330. package/src/client/market/market-view.test.ts +6 -6
  331. package/src/client/market/market-view.ts +3 -3
  332. package/src/client/market/my-configs-api.ts +2 -2
  333. package/src/client/market/my-configs-view.test.ts +23 -22
  334. package/src/client/market/my-configs-view.ts +8 -8
  335. package/src/client/recovery/IncompleteCopiesSection.tsx +2 -1
  336. package/src/client/recovery/RecoveryPanel.tsx +442 -283
  337. package/src/client/recovery/RollbackCard.tsx +665 -0
  338. package/src/client/recovery/RollbackDiffPane.tsx +58 -0
  339. package/src/client/recovery/RollbackFileChanges.tsx +163 -0
  340. package/src/client/recovery/incident-api.ts +2 -2
  341. package/src/client/recovery/recovery-api.ts +116 -6
  342. package/src/client/recovery/recovery-locales.test.ts +299 -0
  343. package/src/client/recovery/recovery-locales.ts +488 -149
  344. package/src/client/recovery/recovery-panel-layout.test.ts +132 -2
  345. package/src/client/recovery/rollback-file-changes.test.ts +73 -0
  346. package/src/client/recovery/session-dialog-mount.test.ts +54 -0
  347. package/src/client/run-store.ts +84 -21
  348. package/src/client/sync/AutosyncCard.tsx +25 -1
  349. package/src/client/sync/ChannelClearConfirmDialog.tsx +6 -3
  350. package/src/client/sync/ChannelConfigDialog.tsx +10 -10
  351. package/src/client/sync/DecryptPasswordCard.tsx +2 -2
  352. package/src/client/sync/SecurityOptionsCard.tsx +27 -17
  353. package/src/client/sync/SyncChannelEntryCard.tsx +14 -2
  354. package/src/client/sync/SyncConfirmView.tsx +46 -8
  355. package/src/client/sync/SyncLogList.tsx +1 -1
  356. package/src/client/sync/SyncPanel.tsx +31 -23
  357. package/src/client/sync/SyncRepositoryPicker.tsx +3 -5
  358. package/src/client/sync/SyncSectionPickerDialog.tsx +11 -2
  359. package/src/client/sync/SyncSettingsView.test.ts +20 -1
  360. package/src/client/sync/UnreadableSnapshotsBanner.tsx +1 -1
  361. package/src/client/sync/WorkDirCard.tsx +112 -0
  362. package/src/client/sync/history-model.test.ts +42 -9
  363. package/src/client/sync/history-model.ts +30 -15
  364. package/src/client/sync/sync-api.test.ts +57 -33
  365. package/src/client/sync/sync-api.ts +80 -6
  366. package/src/client/sync/sync-locales.ts +104 -3
  367. package/src/client/sync/sync-view-v2.test.ts +65 -13
  368. package/src/client/sync/sync-view.test.ts +107 -52
  369. package/src/client/sync/sync-view.ts +124 -41
  370. package/src/core/backup-plan.test.ts +28 -0
  371. package/src/core/backup-plan.ts +29 -17
  372. package/src/core/backup-verify.ts +24 -23
  373. package/src/core/backup.test.ts +76 -0
  374. package/src/core/backup.ts +55 -25
  375. package/src/core/boot-rescue.ts +4 -3
  376. package/src/core/boot-safety.ts +2 -1
  377. package/src/core/command-rollback.test.ts +536 -0
  378. package/src/core/command-rollback.ts +402 -0
  379. package/src/core/consult-source.ts +7 -6
  380. package/src/core/disk-usage.test.ts +257 -1
  381. package/src/core/disk-usage.ts +134 -14
  382. package/src/core/engine-locale.test.ts +593 -0
  383. package/src/core/engine-locale.ts +175 -0
  384. package/src/core/exporter.ts +3 -3
  385. package/src/core/fork-reconcile.test.ts +279 -0
  386. package/src/core/fork-reconcile.ts +234 -0
  387. package/src/core/journal.ts +6 -5
  388. package/src/core/local-plugin-host.ts +3 -2
  389. package/src/core/local-plugin-pack.ts +11 -6
  390. package/src/core/messages-core.ts +657 -0
  391. package/src/core/messages.ts +123 -17
  392. package/src/core/migration-consult.ts +21 -20
  393. package/src/core/migration-history.ts +19 -5
  394. package/src/core/model-tools.ts +28 -27
  395. package/src/core/phase3-host.ts +21 -5
  396. package/src/core/phase3-p2b-fingerprint.test.ts +22 -0
  397. package/src/core/plugin-cli.ts +18 -19
  398. package/src/core/reconcile.test.ts +117 -0
  399. package/src/core/reconcile.ts +85 -17
  400. package/src/core/recovery-orchestrator.ts +19 -18
  401. package/src/core/reinstall.ts +26 -21
  402. package/src/core/restore.ts +20 -17
  403. package/src/core/rollback-file-diff.test.ts +159 -0
  404. package/src/core/rollback-ignore-rules.ts +126 -0
  405. package/src/core/rollback-point.test.ts +2603 -0
  406. package/src/core/rollback-point.ts +3755 -0
  407. package/src/core/run-registry.ts +6 -5
  408. package/src/core/session-health.ts +15 -0
  409. package/src/core/session-meta.test.ts +117 -1
  410. package/src/core/session-meta.ts +149 -2
  411. package/src/core/session-title.test.ts +127 -0
  412. package/src/core/session-title.ts +189 -0
  413. package/src/core/startup-barrier.test.ts +69 -0
  414. package/src/core/startup-barrier.ts +27 -0
  415. package/src/core/transaction-coordinator.ts +4 -3
  416. package/src/core/types.ts +18 -1
  417. package/src/core/validator.ts +5 -4
  418. package/src/core/verify-recovery.ts +31 -30
  419. package/src/foreign/bundle.ts +3 -2
  420. package/src/foreign/continue.ts +2 -1
  421. package/src/foreign/crush.ts +2 -1
  422. package/src/foreign/file-budget.test.ts +2 -0
  423. package/src/foreign/gemini.ts +2 -1
  424. package/src/foreign/goose.ts +2 -1
  425. package/src/foreign/kilocode.ts +2 -1
  426. package/src/foreign/kimi.ts +2 -1
  427. package/src/foreign/messages.ts +44 -0
  428. package/src/foreign/mimocode.ts +2 -1
  429. package/src/foreign/openclaw.ts +2 -1
  430. package/src/foreign/opencode.ts +2 -1
  431. package/src/foreign/pi.ts +2 -1
  432. package/src/foreign/qoder.ts +2 -1
  433. package/src/foreign/qwen.ts +2 -1
  434. package/src/foreign/registry.ts +7 -6
  435. package/src/foreign/teleagent.ts +2 -1
  436. package/src/foreign/workbuddy.ts +2 -1
  437. package/src/foreign/zcode.ts +2 -1
  438. package/src/foreign/zed.ts +2 -1
  439. package/src/index.facade.test.ts +340 -1
  440. package/src/index.ts +813 -57
  441. package/src/market/git-file-writer.ts +20 -19
  442. package/src/market/github-repos.test.ts +23 -0
  443. package/src/market/github-repos.ts +37 -19
  444. package/src/market/index-parser.ts +31 -30
  445. package/src/market/messages-market.ts +325 -0
  446. package/src/market/my-repo.ts +32 -29
  447. package/src/market/prepare.ts +18 -15
  448. package/src/market/security.ts +20 -19
  449. package/src/market/types.ts +2 -1
  450. package/src/market/url.ts +2 -1
  451. package/src/migrations/index.ts +6 -5
  452. package/src/migrations/messages-migrations.ts +59 -0
  453. package/src/profiles/dsh-profile-launcher.ts +2 -1
  454. package/src/profiles/dsh-profile-runtime.ts +2 -1
  455. package/src/profiles/messages.ts +31 -0
  456. package/src/routes/backup.ts +18 -2
  457. package/src/routes/import.ts +3 -3
  458. package/src/routes/index.ts +1 -1
  459. package/src/routes/kit.ts +9 -3
  460. package/src/routes/market.ts +1 -1
  461. package/src/routes/messages.ts +104 -0
  462. package/src/routes/profiles.ts +12 -6
  463. package/src/routes/recovery.test.ts +405 -1
  464. package/src/routes/recovery.ts +377 -109
  465. package/src/routes/snapshots.ts +51 -4
  466. package/src/routes/sync.ts +67 -16
  467. package/src/schema/config.ts +53 -20
  468. package/src/schema/index.ts +7 -5
  469. package/src/schema/manifest.ts +19 -18
  470. package/src/schema/messages.ts +120 -0
  471. package/src/schema/registry.test.ts +19 -5
  472. package/src/schema/section-registry.ts +64 -39
  473. package/src/schema/versions.ts +6 -5
  474. package/src/security/encryption.ts +16 -15
  475. package/src/security/integrity.ts +8 -7
  476. package/src/security/messages.ts +87 -0
  477. package/src/security/redaction.test.ts +17 -1
  478. package/src/security/redaction.ts +13 -0
  479. package/src/security/secret-scanner.ts +19 -12
  480. package/src/security/security.test.ts +47 -0
  481. package/src/security/vault.ts +2 -1
  482. package/src/security/zip-security.ts +4 -3
  483. package/src/sync/ancestor.ts +2 -1
  484. package/src/sync/autosync-config.ts +21 -0
  485. package/src/sync/autosync-scheduler.test.ts +63 -7
  486. package/src/sync/autosync-scheduler.ts +118 -78
  487. package/src/sync/backup-files.ts +3 -2
  488. package/src/sync/backup-scheduler.ts +6 -5
  489. package/src/sync/blob-store.ts +2 -1
  490. package/src/sync/git/git-transport.test.ts +170 -8
  491. package/src/sync/git/git-transport.ts +176 -2
  492. package/src/sync/github-auth.ts +10 -9
  493. package/src/sync/layout.ts +13 -12
  494. package/src/sync/messages.ts +153 -0
  495. package/src/sync/review-queue.ts +6 -5
  496. package/src/sync/s3/messages.ts +16 -0
  497. package/src/sync/s3/s3-providers.ts +9 -6
  498. package/src/sync/s3/s3-transport.ts +1 -1
  499. package/src/sync/s3/sigv4.ts +3 -1
  500. package/src/sync/snapshot-crypto.ts +6 -5
  501. package/src/sync/snapshot-json.ts +3 -2
  502. package/src/sync/sync-engine-download-tombstone.test.ts +1 -1
  503. package/src/sync/sync-engine.test.ts +226 -8
  504. package/src/sync/sync-engine.ts +103 -41
  505. package/src/sync/sync-history.ts +7 -6
  506. package/src/sync/sync-selection.test.ts +12 -2
  507. package/src/sync/sync-selection.ts +18 -5
  508. package/src/sync/webdav/webdav-transport.ts +4 -1
  509. package/src/ui/artifact-view.ts +4 -3
  510. package/src/ui/commands.test.ts +1 -0
  511. package/src/ui/commands.ts +11 -1
  512. package/src/ui/disk-usage-view.test.ts +113 -6
  513. package/src/ui/disk-usage-view.ts +63 -7
  514. package/src/ui/errors.ts +4 -3
  515. package/src/ui/export-flow.ts +24 -20
  516. package/src/ui/foreign-view.test.ts +43 -0
  517. package/src/ui/foreign-view.ts +30 -3
  518. package/src/ui/i18n.ts +125 -0
  519. package/src/ui/import-wizard.ts +18 -10
  520. package/src/ui/progress.ts +2 -2
  521. package/src/ui/report.ts +7 -6
  522. package/src/ui/rollback-view.test.ts +819 -0
  523. package/src/ui/rollback-view.ts +1339 -0
  524. package/src/ui/selection-model.test.ts +33 -1
  525. package/src/ui/selection-model.ts +53 -0
  526. package/src/ui/session-inventory-view.test.ts +109 -0
  527. package/src/ui/session-inventory-view.ts +138 -3
  528. package/src/ui/session-issue-labels.test.ts +51 -0
  529. package/src/ui/session-issue-labels.ts +70 -0
  530. package/src/ui/sync-settings-view.test.ts +36 -0
  531. package/src/ui/sync-settings-view.ts +49 -0
  532. package/src/ui/types.ts +10 -4
  533. package/src/utils/atomic-write.ts +6 -5
  534. package/src/utils/bundle-scan.test.ts +2 -2
  535. package/src/utils/bundle-scan.ts +12 -1
  536. package/src/utils/env-lock.ts +83 -49
  537. package/src/utils/json.ts +7 -5
  538. package/src/utils/messages-utils.ts +232 -0
  539. package/src/utils/proxy.test.ts +25 -0
  540. package/src/utils/proxy.ts +19 -5
  541. package/src/utils/recursive-walk.test.ts +43 -0
  542. package/src/utils/recursive-walk.ts +75 -1
  543. package/src/utils/session-log-repair.ts +10 -9
  544. package/src/utils/zip.ts +27 -26
  545. package/lib/core/checkpoint.d.ts +0 -362
  546. package/lib/core/checkpoint.js +0 -1309
  547. package/src/core/checkpoint.test.ts +0 -665
  548. package/src/core/checkpoint.ts +0 -1614
@@ -32,6 +32,8 @@
32
32
  * 目标越出 homeDir 或断链的链接跳过但**写进 warnings**——绝不静默少收内容;
33
33
  * 普通目录**超过层级上限**同样留痕(cli-F2),且告警措辞按**真实来源**分派(t37:链接类 vs 目录超深);
34
34
  * - 命中 `isReservedInternalRel` 的内部命名空间(locks/snapshots/transactions…)跳过;
35
+ * - 运行时依赖目录 node_modules(任意深度)**整棵跳过**(issue #76:与 plugins 分区的 npm pack 同口径,
36
+ * 依赖树可由包内 package.json / lock 重建),跳过了哪些目录、各多少文件一律写进 warnings;
35
37
  * - `pluginFiles`(`dsh-ssh.json` 等第三方插件自有文件)**默认不收**:该分区与 GUI
36
38
  * 侧同为 deviceSpecific「任意文件直通、无内容过滤」,`dsh-ssh.json` 里是明文主机
37
39
  * 密码——必须由用户显式 `--sections pluginFiles` 选中,并附带显式告警
@@ -39,6 +41,7 @@
39
41
  *
40
42
  * 零 DSH 运行时依赖:仅 `node:fs/promises` + `node:path` + 本仓库零依赖模块。
41
43
  */
44
+ import { engineMsg } from './messages-core.js';
42
45
  import fs from 'node:fs/promises';
43
46
  import path from 'node:path';
44
47
  import { listRecursiveFollowingLinks } from '../utils/recursive-walk.js';
@@ -110,7 +113,7 @@ const SECTION_SPECS = {
110
113
  files: ['dsh-ssh.json', 'pet.json'],
111
114
  dirs: ['plugin-config'],
112
115
  label: 'Plugin Files',
113
- risk: '第三方插件自有文件不受控(dsh-ssh.json 内含明文主机密码等),仅在你确认内容后才应纳入备份',
116
+ get risk() { return engineMsg('core.backupPlan.pluginFilesRisk'); },
114
117
  },
115
118
  // adapters/self.ts(baseDir='dsh-config-manager',SELF_CONFIG_FILES 白名单)
116
119
  self: {
@@ -134,13 +137,16 @@ const SECTION_SPECS = {
134
137
  * 跳过原因;而 `too-deep` 自 cli-F2 起对**普通目录**同样会记录(此前只有链接超深才记)—— 整块内容被
135
138
  * 深度上限裁掉时若仍写成「链接未进备份」,用户看到的原因就与真实来源(目录层级超出上限)不符。
136
139
  * 措辞里**不编具体层数**:上限常量住在 utils/recursive-walk.ts,本模块不 import 它的私有常量。
140
+ *
141
+ * 路径**不叠加分区前缀**:内核返回的 `s.path` 已是 homeDir 相对路径(含分区目录),再加一层
142
+ * 会打印成 `skills/skills/...`(真机规模探针实测),与相邻的「目录读取失败」行口径也不一致。
137
143
  */
138
- function skippedLinkWarning(label, s) {
139
- const at = `${label}/${s.path}(${s.reason})`;
144
+ function skippedLinkWarning(s) {
145
+ const at = engineMsg('core.backupPlan.pathWithReason', { path: s.path, reason: s.reason });
140
146
  if (s.reason === 'too-deep') {
141
- return `层级超过上限未进备份(目录或链接目标超出深度上限) / nesting limit exceeded, content NOT in this backup: ${at}`;
147
+ return engineMsg('core.backupPlan.tooDeep', { at });
142
148
  }
143
- return `链接未进备份 / link NOT in this backup: ${at}`;
149
+ return engineMsg('core.backupPlan.linkSkipped', { at });
144
150
  }
145
151
  /**
146
152
  * 递归列出目录下全部普通文件(**相对 homeDir** 的 posix 路径,已排序);目录不存在 → 空。
@@ -149,16 +155,23 @@ function skippedLinkWarning(label, s) {
149
155
  * 并把「跟随了多少链接」「哪些链接/目录没进来(及原因)」写进 warnings。
150
156
  * 旧实现遇到链接目录直接跳过且零告警,用户拿到的是「成功但缺斤少两」的备份。
151
157
  */
152
- async function listFilesRecursive(dirAbs, homeDir, warnings, label) {
158
+ async function listFilesRecursive(dirAbs, homeDir, warnings) {
153
159
  const listing = await listRecursiveFollowingLinks(dirAbs, homeDir);
154
160
  for (const s of listing.skippedLinks) {
155
- warnings.push(skippedLinkWarning(label, s));
161
+ warnings.push(skippedLinkWarning(s));
156
162
  }
157
163
  for (const dir of listing.unreadableDirs) {
158
- warnings.push(`目录读取失败,其内容未进备份 / directory unreadable, content NOT in this backup: ${dir}`);
164
+ warnings.push(engineMsg('core.backupPlan.dirUnreadable', { dir }));
165
+ }
166
+ // issue #76:运行时依赖目录整棵跳过(内容未进备份)——必须可见,绝不静默。
167
+ for (const dep of listing.skippedDepDirs) {
168
+ const files = dep.files === null
169
+ ? engineMsg('core.backupPlan.countNotMeasured')
170
+ : engineMsg('core.backupPlan.fileCount', { count: dep.files });
171
+ warnings.push(engineMsg('core.backupPlan.depDirSkipped', { path: dep.path, files }));
159
172
  }
160
173
  if (listing.followedLinks > 0) {
161
- warnings.push(`已跟随 ${listing.followedLinks} 个链接目录收集内容 / followed ${listing.followedLinks} linked director(ies)`);
174
+ warnings.push(engineMsg('core.backupPlan.followedLinks', { count: listing.followedLinks }));
162
175
  }
163
176
  return listing.paths;
164
177
  }
@@ -171,15 +184,15 @@ function toRelativePath(baseDir, homeRel) {
171
184
  }
172
185
  /** `--sections` 取值校验:返回选中分区或可读错误(未知 id 一律拒绝并列出合法值) */
173
186
  export function parseSectionsArg(raw) {
174
- const parts = raw.split(/[,,\s]+/).filter((p) => p !== '');
187
+ const parts = raw.split(/[,\uFF0C\s]+/).filter((p) => p !== '');
175
188
  if (parts.length === 0) {
176
- return { ok: false, error: '`--sections` 缺值 / missing value for `--sections`' };
189
+ return { ok: false, error: engineMsg('core.backupPlan.sectionsMissingValue') };
177
190
  }
178
191
  const unknown = parts.filter((p) => !OFFLINE_BACKUP_SECTIONS.includes(p));
179
192
  if (unknown.length > 0) {
180
193
  return {
181
194
  ok: false,
182
- error: `未知分区 / unknown section(s): ${unknown.join(', ')}(可用 / available: ${OFFLINE_BACKUP_SECTIONS.join(', ')})`,
195
+ error: engineMsg('core.backupPlan.unknownSections', { unknown: unknown.join(', '), available: OFFLINE_BACKUP_SECTIONS.join(', ') }),
183
196
  };
184
197
  }
185
198
  // 去重并保持声明顺序,保证产物可复现
@@ -207,7 +220,7 @@ export async function collectBackupEntries(homeDir, only = []) {
207
220
  let excluded = 0;
208
221
  const push = (zipName, data) => {
209
222
  if (!isPathSafe(zipName)) {
210
- warnings.push(`跳过不安全条目名 / unsafe entry name skipped: ${zipName}`);
223
+ warnings.push(engineMsg('core.backupPlan.unsafeEntryName', { name: zipName }));
211
224
  excluded += 1;
212
225
  return;
213
226
  }
@@ -229,7 +242,7 @@ export async function collectBackupEntries(homeDir, only = []) {
229
242
  catch (err) {
230
243
  // 不存在 → 静默跳过(按需创建的文件,如 dsh-ssh.json)
231
244
  if (err.code !== 'ENOENT') {
232
- warnings.push(`探测失败 / stat failed: ${norm}(${err instanceof Error ? err.message : String(err)})`);
245
+ warnings.push(engineMsg('core.backupPlan.statFailed', { path: norm, reason: err instanceof Error ? err.message : String(err) }));
233
246
  }
234
247
  return;
235
248
  }
@@ -242,7 +255,7 @@ export async function collectBackupEntries(homeDir, only = []) {
242
255
  push(`${prefix}${toRelativePath(spec.baseDir, norm)}`, new Uint8Array(data));
243
256
  }
244
257
  catch (err) {
245
- warnings.push(`读取失败 / read failed: ${norm}(${err instanceof Error ? err.message : String(err)})`);
258
+ warnings.push(engineMsg('core.backupPlan.readFailed', { path: norm, reason: err instanceof Error ? err.message : String(err) }));
246
259
  }
247
260
  };
248
261
  // 1) 固定文件(存在才收)
@@ -251,7 +264,7 @@ export async function collectBackupEntries(homeDir, only = []) {
251
264
  // 2) 递归目录(跟随 junction/符号链接;未跟随的链接与不可读目录写进 warnings)
252
265
  for (const dirRel of spec.dirs ?? []) {
253
266
  const dirAbs = path.join(homeDir, ...normalizePath(dirRel).split('/'));
254
- const rels = await listFilesRecursive(dirAbs, homeDir, warnings, normalizePath(dirRel));
267
+ const rels = await listFilesRecursive(dirAbs, homeDir, warnings);
255
268
  for (const homeRel of rels)
256
269
  await consider(homeRel);
257
270
  }
@@ -264,7 +277,7 @@ export async function collectBackupEntries(homeDir, only = []) {
264
277
  }
265
278
  else {
266
279
  empty.push(sectionId);
267
- warnings.push(`分区无内容,未写入 / section empty, skipped: ${sectionId}`);
280
+ warnings.push(engineMsg('core.backupPlan.sectionEmpty', { section: sectionId }));
268
281
  }
269
282
  }
270
283
  return { entries, sections, warnings, included, empty };
@@ -28,6 +28,7 @@
28
28
  * (实测 `verifyChecksums` 只调用 `map.get()`,不会整体物化 Map)。
29
29
  * 对 MB 级的 DSH 配置备份完全可接受。
30
30
  */
31
+ import { engineMsg } from './messages-core.js';
31
32
  import fs from 'node:fs/promises';
32
33
  import { verifyChecksums } from '../utils/hashing.js';
33
34
  import { isENOENT } from '../utils/guards.js';
@@ -114,7 +115,7 @@ export async function verifyBackupZip(zipPath) {
114
115
  try {
115
116
  const stat = await fs.stat(zipPath);
116
117
  if (!stat.isFile()) {
117
- errors.push(`不是普通文件(目录或特殊文件)/ not a regular file: ${zipPath}`);
118
+ errors.push(engineMsg('core.backupVerify.notRegularFile', { zip: zipPath }));
118
119
  return makeResult(zipPath, 'MISSING', errors, warnings);
119
120
  }
120
121
  sizeBytes = stat.size;
@@ -122,11 +123,11 @@ export async function verifyBackupZip(zipPath) {
122
123
  }
123
124
  catch (err) {
124
125
  if (isENOENT(err)) {
125
- errors.push(`备份文件不存在 / backup file not found: ${zipPath}`);
126
+ errors.push(engineMsg('core.backupVerify.fileNotFound', { zip: zipPath }));
126
127
  return makeResult(zipPath, 'MISSING', errors, warnings);
127
128
  }
128
129
  // 文件在但读不动(EACCES / EBUSY / IO 错):这是自检自身的失败,不是「备份坏了」
129
- errors.push(`备份文件不可读 / backup file unreadable: ${msgOfError(err)}`);
130
+ errors.push(engineMsg('core.backupVerify.fileUnreadable', { reason: msgOfError(err) }));
130
131
  return makeResult(zipPath, 'VERIFY_ERROR', errors, warnings);
131
132
  }
132
133
  /** 已知字节数后的骨架(后续阶段失败时保留体积信息) */
@@ -137,7 +138,7 @@ export async function verifyBackupZip(zipPath) {
137
138
  /* ---------------- 2. 打开为 ZIP ---------------- */
138
139
  // 整体加密容器不是 ZIP(先识别,给出比「ZIP 损坏」准确得多的结论)
139
140
  if (isArchiveBlob(buf)) {
140
- errors.push('整体加密备份容器(DCA1)需先用密码解密为 ZIP 才能自检 / encrypted container must be decrypted first');
141
+ errors.push(engineMsg('core.backupVerify.encryptedContainer'));
141
142
  return withSize('UNSUPPORTED');
142
143
  }
143
144
  let archive;
@@ -145,7 +146,7 @@ export async function verifyBackupZip(zipPath) {
145
146
  archive = createHardenedZipParser()(buf);
146
147
  }
147
148
  catch (err) {
148
- errors.push(`ZIP 无法打开 / cannot open as ZIP: ${msgOfError(err)}`);
149
+ errors.push(engineMsg('core.backupVerify.cannotOpenZip', { reason: msgOfError(err) }));
149
150
  return withSize('CORRUPT');
150
151
  }
151
152
  const metas = archive.entries();
@@ -154,12 +155,12 @@ export async function verifyBackupZip(zipPath) {
154
155
  file: zipPath, verdict, sizeBytes, entryCount, errors, warnings,
155
156
  });
156
157
  if (entryCount === 0) {
157
- errors.push('空 ZIP(0 条目)/ empty archive');
158
+ errors.push(engineMsg('core.backupVerify.emptyArchive'));
158
159
  return withEntries('CORRUPT');
159
160
  }
160
161
  /* ---------------- 3. manifest.json ---------------- */
161
162
  if (!archive.has(MANIFEST_FILE)) {
162
- errors.push(`缺少 ${MANIFEST_FILE}(不是本插件导出的备份)/ ${MANIFEST_FILE} missing`);
163
+ errors.push(engineMsg('core.backupVerify.missingManifest', { file: MANIFEST_FILE }));
163
164
  return withEntries('CORRUPT');
164
165
  }
165
166
  let manifestRaw;
@@ -168,7 +169,7 @@ export async function verifyBackupZip(zipPath) {
168
169
  }
169
170
  catch (err) {
170
171
  // 条目本身读不出来(CRC 失败/解压失败)→ 归档损坏,而非 manifest 语义非法
171
- errors.push(`${MANIFEST_FILE} 无法读取(归档损坏)/ unreadable entry: ${msgOfError(err)}`);
172
+ errors.push(engineMsg('core.backupVerify.manifestUnreadable', { file: MANIFEST_FILE, reason: msgOfError(err) }));
172
173
  return withEntries('CORRUPT');
173
174
  }
174
175
  let manifest;
@@ -176,7 +177,7 @@ export async function verifyBackupZip(zipPath) {
176
177
  manifest = parseManifest(manifestRaw);
177
178
  }
178
179
  catch (err) {
179
- errors.push(`${MANIFEST_FILE} 非法 / invalid manifest: ${msgOfError(err)}`);
180
+ errors.push(engineMsg('core.backupVerify.invalidManifest', { file: MANIFEST_FILE, reason: msgOfError(err) }));
180
181
  return withEntries('CORRUPT');
181
182
  }
182
183
  const sections = Object.entries(manifest.sections ?? {})
@@ -187,12 +188,12 @@ export async function verifyBackupZip(zipPath) {
187
188
  });
188
189
  if (!isSupported(manifest.schemaVersion)) {
189
190
  // 版本超范围是「本插件读不了」,不是「备份坏了」——明确区分,避免误导用户重导
190
- errors.push(`schema 版本不受支持 / unsupported schema version: ${describeVersion(manifest.schemaVersion)}`);
191
+ errors.push(engineMsg('core.backupVerify.unsupportedSchema', { version: describeVersion(manifest.schemaVersion) }));
191
192
  return withManifest('UNSUPPORTED');
192
193
  }
193
194
  /* ---------------- 4. integrity/checksums.json ---------------- */
194
195
  if (!archive.has(CHECKSUMS_FILE)) {
195
- errors.push(`缺少 ${CHECKSUMS_FILE}(无法做完整性校验)/ ${CHECKSUMS_FILE} missing`);
196
+ errors.push(engineMsg('core.backupVerify.missingChecksums', { file: CHECKSUMS_FILE }));
196
197
  return withManifest('CORRUPT');
197
198
  }
198
199
  let table;
@@ -201,7 +202,7 @@ export async function verifyBackupZip(zipPath) {
201
202
  }
202
203
  catch (err) {
203
204
  // readEntryText 的 CRC/解压失败与「表结构非法」都归 CORRUPT(两者都说明备份不可信)
204
- errors.push(`${CHECKSUMS_FILE} 非法或不可读 / invalid or unreadable checksums table: ${msgOfError(err)}`);
205
+ errors.push(engineMsg('core.backupVerify.invalidChecksums', { file: CHECKSUMS_FILE, reason: msgOfError(err) }));
205
206
  return withManifest('CORRUPT');
206
207
  }
207
208
  // 4a. 完整性比对:**复用官方 verifyChecksums**(与导出侧 buildChecksums 同源同口径),
@@ -209,7 +210,7 @@ export async function verifyBackupZip(zipPath) {
209
210
  const unreadableNames = new Set();
210
211
  const unreadable = [];
211
212
  const lazyEntries = new LazyEntryMap(archive, unreadableNames, (name, reason) => {
212
- unreadable.push(`${name}(${reason})`);
213
+ unreadable.push(engineMsg('core.backupVerify.unreadableEntry', { name, reason }));
213
214
  });
214
215
  const tableKeys = Object.keys(table);
215
216
  const check = verifyChecksums(lazyEntries, table);
@@ -219,10 +220,10 @@ export async function verifyBackupZip(zipPath) {
219
220
  // 4b. 结构提示:未纳入校验表的条目(导出器只把 manifest 与校验表自身排除在外)
220
221
  if (tableKeys.length === 0) {
221
222
  if (entryCount > NON_DATA_ENTRIES.size) {
222
- errors.push('校验表为空但归档含数据条目 / empty checksums table with data entries');
223
+ errors.push(engineMsg('core.backupVerify.emptyTableWithData'));
223
224
  return withManifest('CORRUPT');
224
225
  }
225
- warnings.push('校验表为空(归档仅含 manifest 与校验表自身)/ empty checksums table');
226
+ warnings.push(engineMsg('core.backupVerify.emptyTable'));
226
227
  }
227
228
  const covered = new Set(tableKeys);
228
229
  const uncovered = metas
@@ -230,7 +231,7 @@ export async function verifyBackupZip(zipPath) {
230
231
  .map((m) => m.name);
231
232
  if (uncovered.length > 0) {
232
233
  const shown = uncovered.slice(0, 5).join(', ');
233
- warnings.push(`含未纳入校验表的条目 ${uncovered.length} 个 / entries not covered by checksums: ${shown}${uncovered.length > 5 ? ', …' : ''}`);
234
+ warnings.push(engineMsg('core.backupVerify.uncoveredEntries', { count: uncovered.length, shown, suffix: uncovered.length > 5 ? ', …' : '' }));
234
235
  }
235
236
  // 4c. 结构提示:manifest 声明包含的分区在归档内应有对应条目
236
237
  for (const id of sections) {
@@ -238,31 +239,31 @@ export async function verifyBackupZip(zipPath) {
238
239
  const jsonPath = SECTION_JSON_PATHS[id];
239
240
  if (prefix !== undefined) {
240
241
  if (!metas.some((m) => m.name.startsWith(prefix) && m.name !== prefix)) {
241
- warnings.push(`manifest 声明分区 ${id} 但归档内无对应文件 / declared section has no entries: ${id}`);
242
+ warnings.push(engineMsg('core.backupVerify.declaredSectionNoEntries', { id }));
242
243
  }
243
244
  }
244
245
  else if (jsonPath !== undefined && !archive.has(jsonPath)) {
245
- warnings.push(`manifest 声明分区 ${id} 但缺少 ${jsonPath} / declared section payload missing: ${id}`);
246
+ warnings.push(engineMsg('core.backupVerify.declaredSectionPayloadMissing', { id, path: jsonPath }));
246
247
  }
247
248
  }
248
249
  if (manifest.security?.encrypted === true && !metas.some((m) => SECRETS_ENC_RE.test(m.name))) {
249
- warnings.push('manifest 标记加密但归档内无凭据密文条目 / encrypted flag without secrets entry');
250
+ warnings.push(engineMsg('core.backupVerify.encryptedFlagNoSecrets'));
250
251
  }
251
252
  /* ---------------- 裁决 ---------------- */
252
253
  if (missing.length > 0) {
253
- errors.push(`校验表内条目缺失(${missing.length})/ missing entries: ${missing.join(', ')}`);
254
+ errors.push(engineMsg('core.backupVerify.missingEntries', { count: missing.length, list: missing.join(', ') }));
254
255
  }
255
256
  if (mismatched.length > 0) {
256
- errors.push(`条目 SHA-256 不符(${mismatched.length})/ hash mismatch: ${mismatched.join(', ')}`);
257
+ errors.push(engineMsg('core.backupVerify.hashMismatch', { count: mismatched.length, list: mismatched.join(', ') }));
257
258
  }
258
259
  if (unreadable.length > 0) {
259
- errors.push(`条目无法读取(${unreadable.length})/ unreadable entries: ${unreadable.join(', ')}`);
260
+ errors.push(engineMsg('core.backupVerify.unreadableEntries', { count: unreadable.length, list: unreadable.join(', ') }));
260
261
  }
261
262
  return withManifest(missing.length > 0 || mismatched.length > 0 || unreadable.length > 0 ? 'CORRUPT' : 'OK');
262
263
  }
263
264
  catch (err) {
264
265
  // 5. 自检自身异常:如实报 VERIFY_ERROR,绝不降级成 OK
265
- errors.push(`自检过程异常 / verification error: ${msgOfError(err)}`);
266
+ errors.push(engineMsg('core.backupVerify.verificationError', { reason: msgOfError(err) }));
266
267
  return withSize('VERIFY_ERROR');
267
268
  }
268
269
  }
@@ -144,6 +144,11 @@ export interface FileSnapshotStoreOptions {
144
144
  * 未注入 → `fallbackPruneSelector`(只按 keepLast,等价既有 FIFO;分层字段被忽略 = 保守少删)。
145
145
  */
146
146
  pruneSelector?: PruneSelector;
147
+ /**
148
+ * 诊断回调(可选):保留清理**被放弃**或失败时的可见线索。
149
+ * 缺省 = 不记录(不改变行为);宿主接 `host.log.warn` —— 「这轮为什么没清理」必须能查。
150
+ */
151
+ onWarn?: (message: string) => void;
147
152
  }
148
153
  /** 文件快照存储:<dir>/<id>/snapshot.json + <dir>/<id>/blobs/* */
149
154
  export declare class FileSnapshotStore implements SnapshotStore {
@@ -5,6 +5,7 @@
5
5
  * 快照不保存 credential 值(DSH 永不回读值)——credential 条目只记 existed 标志,
6
6
  * 回滚时 existed=true 的条目如实标记「值需人工补录」。
7
7
  */
8
+ import { engineMsg } from './messages-core.js';
8
9
  import fs from 'node:fs/promises';
9
10
  import path from 'node:path';
10
11
  import crypto from 'node:crypto';
@@ -60,8 +61,11 @@ async function backupHostFiles(ctx, blobs) {
60
61
  existed = await ctx.fs.exists(relPath);
61
62
  }
62
63
  catch (err) {
64
+ // 探测自身失败 = 存在性判不出来:按「存在但不可读」登记。**绝不写 existed:false** ——
65
+ // 那会被恢复侧读成「当时确实不存在」,进而删掉用户真实的文件。
63
66
  ctx.log.warn(`快照探测宿主文件失败 ${relPath}: ${err instanceof Error ? err.message : String(err)}`);
64
- existed = false;
67
+ backups.push({ relPath, blobPath: '', existed: true, unreadable: true });
68
+ continue;
65
69
  }
66
70
  if (!existed) {
67
71
  backups.push({ relPath, blobPath: '', existed: false });
@@ -73,8 +77,10 @@ async function backupHostFiles(ctx, blobs) {
73
77
  backups.push({ relPath, blobPath, existed: true });
74
78
  }
75
79
  catch (err) {
80
+ // 文件确实存在、只是这次读不出来(占用/权限/IO):登记 existed=true + unreadable,
81
+ // 恢复侧据此只跳过并告警,绝不进入「删除」分支(见 HostFileBackup.unreadable)。
76
82
  ctx.log.warn(`快照读取宿主文件失败 ${relPath}: ${err instanceof Error ? err.message : String(err)}`);
77
- backups.push({ relPath, blobPath: '', existed: false });
83
+ backups.push({ relPath, blobPath: '', existed: true, unreadable: true });
78
84
  }
79
85
  }
80
86
  return backups;
@@ -98,7 +104,7 @@ const FILE_BASES = {
98
104
  // 其它分区语义处理」同类缺陷)。注册表新增文件类分区时若忘了在这里补,进程启动即失败。
99
105
  for (const id of SECTION_IDS) {
100
106
  if (isFileSection(id) && FILE_BASES[id] === undefined) {
101
- throw new Error(`分区 ${id} 是文件类分区,但 core/backup.ts 的 FILE_BASES 未给基准目录(拒绝静默退回 homeDir 根)`);
107
+ throw new Error(engineMsg('core.backup.fileSectionBaseMissing', { section: id }));
102
108
  }
103
109
  }
104
110
  /**
@@ -257,7 +263,7 @@ async function engineSnapshotEntry(ctx, target) {
257
263
  case 'secrets':
258
264
  // secrets 已注册但**没有 adapter、也没有 ZIP 内分区 JSON**(凭据值走独立加密容器 secrets.enc)。
259
265
  // 任何计划项都不应以它为快照目标 —— 显式报错,而不是落到 default 分支。
260
- throw new Error('secrets 分区没有 adapter / 无分区载荷,不应作为快照目标(凭据值走独立加密容器,绝不进快照)');
266
+ throw new Error(engineMsg('core.backup.secretsSectionNotSnapshotTarget'));
261
267
  default: {
262
268
  // 编译期穷举断言:SectionId 的每一项都已被上面的 case 处理 → 此处 target.adapter 收窄为 never。
263
269
  // 新增分区若忘了在这里实现快照语义,tsc 会直接报错(不再依赖人肉核对清单)。
@@ -265,8 +271,7 @@ async function engineSnapshotEntry(ctx, target) {
265
271
  // 运行期(只可能来自运行期强转:被篡改的 plan / 旧 journal 反序列化):**显式报错**。
266
272
  // 旧实现在这里静默返回 settingsNamespace 条目(连 existed 都没有)→ 回滚把该目标当「原本不存在」
267
273
  // 而不做任何补偿:该分区的写入永远撤不掉,报告却仍说「已快照、可回滚」(审计 core-flow#F-10)。
268
- throw new Error(`快照不支持分区「${String(unhandled)}」:它未在 schema/section-registry.ts 注册(或缺少快照实现);`
269
- + '拒绝按其它分区语义静默记录(那会让该分区的写入无法回滚)');
274
+ throw new Error(engineMsg('core.backup.sectionUnsupported', { section: String(unhandled) }));
270
275
  }
271
276
  }
272
277
  }
@@ -405,7 +410,7 @@ export class FileSnapshotStore {
405
410
  async writeBlob(dir, blobPath, data, hashes) {
406
411
  const target = path.join(dir, blobPath);
407
412
  if (!target.startsWith(dir))
408
- throw new Error(`快照 blob 路径越界: ${blobPath}`);
413
+ throw new Error(engineMsg('core.backup.blobPathEscaped', { path: blobPath }));
409
414
  await fs.mkdir(path.dirname(target), { recursive: true });
410
415
  await atomicWriteFile(target, data);
411
416
  hashes[blobPath] = sha256Hex(data);
@@ -429,7 +434,7 @@ export class FileSnapshotStore {
429
434
  // 4) 验证(从磁盘重读,不信任内存对象)
430
435
  const verified = await verifySnapshot(this.options.dir, snapshot.id);
431
436
  if (!verified.ok) {
432
- throw new Error(`快照 ${snapshot.id} 验证失败: ${verified.reason}`);
437
+ throw new Error(engineMsg('core.backup.snapshotVerifyFailed', { id: snapshot.id, reason: verified.reason ?? '' }));
433
438
  }
434
439
  // 5) 原子发布 READY(重写 snapshot.json readiness='READY';metadataHash 稳定不受影响)
435
440
  const ready = { ...snapshot, readiness: 'READY' };
@@ -439,7 +444,9 @@ export class FileSnapshotStore {
439
444
  await this.prune();
440
445
  }
441
446
  catch (err) {
442
- // 快照已 READY + verified;prune 失败(EBUSY/EPERM)不应把刚成功的快照判为 unusable
447
+ // 快照已 READY + verified;prune 失败(EBUSY/EPERM)不应把刚成功的快照判为 unusable ——
448
+ // 但必须留痕:保留清理长期失败会让快照无限堆积,用户与日志都看不到线索。
449
+ this.options.onWarn?.(engineMsg('core.backup.pruneFailed', { reason: err instanceof Error ? err.message : String(err) }));
443
450
  }
444
451
  return snapshot.id;
445
452
  }
@@ -480,7 +487,19 @@ export class FileSnapshotStore {
480
487
  async prune() {
481
488
  const dir = this.options.dir;
482
489
  const entries = await fs.readdir(dir, { withFileTypes: true }).catch(() => []);
483
- const referenced = await this.options.referencedSnapshotIds?.().catch(() => new Set()) ?? new Set();
490
+ // F3:被未收敛 journal 引用的快照**绝不自动清理**。判据是「读不到 ≠ 没有引用」——
491
+ // 引用提供者抛错时**放弃本轮清理**,绝不按空集合继续删:一次瞬时 IO 错就可能删掉
492
+ // 事故恢复唯一的回滚点(那是 SAFE MODE 的唯一证据)。此前这里是 `.catch(() => new Set())`。
493
+ let referenced = new Set();
494
+ if (this.options.referencedSnapshotIds !== undefined) {
495
+ try {
496
+ referenced = await this.options.referencedSnapshotIds();
497
+ }
498
+ catch (err) {
499
+ this.options.onWarn?.(engineMsg('core.backup.pruneAbandoned', { reason: err instanceof Error ? err.message : String(err) }));
500
+ return;
501
+ }
502
+ }
484
503
  // m-retention:策略读取失败 → 回退缺省(绝不因策略提供者异常而误删/崩溃)
485
504
  const policy = await Promise.resolve()
486
505
  .then(() => this.options.retentionPolicy?.() ?? defaultRetentionPolicyLike())
@@ -494,11 +513,18 @@ export class FileSnapshotStore {
494
513
  const parsed = parseJsonSafe(await fs.readFile(path.join(dir, entry.name, 'snapshot.json'), 'utf8'));
495
514
  if (typeof parsed.id !== 'string' || parsed.id === '' || typeof parsed.createdAt !== 'string')
496
515
  continue;
516
+ // 删除目标只能是**目录名**(下面 path.join(dir, id) 就是按它删的)。目录名与内部 id 不一致时
517
+ // **绝不猜**:pinned/被引用的判断是对着这份 snapshot.json 做的,删除却会打在另一个目录上 ——
518
+ // 跳过并留痕(手工改名/半截拷贝残留会出现这种形态)。
519
+ if (parsed.id !== entry.name) {
520
+ this.options.onWarn?.(engineMsg('core.backup.dirNameIdMismatch', { dir: entry.name, id: parsed.id }));
521
+ continue;
522
+ }
497
523
  if (parsed.pinned === true)
498
524
  continue; // 置顶快照豁免自动清理
499
- if (referenced.has(parsed.id))
525
+ if (referenced.has(entry.name))
500
526
  continue; // Phase 4 F3:recovery 引用豁免
501
- metas.push({ id: parsed.id, createdAt: parsed.createdAt });
527
+ metas.push({ id: entry.name, createdAt: parsed.createdAt });
502
528
  }
503
529
  catch {
504
530
  // 损坏 / 非快照目录:跳过(与 listSnapshots 语义一致)
@@ -528,7 +554,7 @@ export class FileSnapshotStore {
528
554
  const target = path.join(this.snapshotDir(id), blobPath);
529
555
  const dir = this.options.dir;
530
556
  if (!target.startsWith(dir))
531
- throw new Error(`快照 blob 路径越界: ${blobPath}`);
557
+ throw new Error(engineMsg('core.backup.blobPathEscaped', { path: blobPath }));
532
558
  return fs.readFile(target);
533
559
  }
534
560
  /** 标记快照生命周期状态:重写 <dir>/<id>/snapshot.json(保留其余字段)。 */
@@ -559,55 +585,55 @@ export function computeMetadataHash(snapshot) {
559
585
  export async function verifySnapshot(snapshotsDir, id) {
560
586
  const dir = path.join(snapshotsDir, id);
561
587
  if (!dir.startsWith(path.resolve(snapshotsDir) + path.sep)) {
562
- return { ok: false, reason: '快照路径越界' };
588
+ return { ok: false, reason: engineMsg('core.backup.snapshotPathEscaped') };
563
589
  }
564
590
  let raw;
565
591
  try {
566
592
  raw = await fs.readFile(path.join(dir, 'snapshot.json'), 'utf8');
567
593
  }
568
594
  catch {
569
- return { ok: false, reason: 'snapshot.json 缺失' };
595
+ return { ok: false, reason: engineMsg('core.backup.snapshotJsonMissing') };
570
596
  }
571
597
  const snapshot = parseJsonSafe(raw);
572
598
  if (snapshot === null || typeof snapshot !== 'object' || typeof snapshot.id !== 'string' || snapshot.id === '') {
573
- return { ok: false, reason: 'snapshot.json 非法' };
599
+ return { ok: false, reason: engineMsg('core.backup.snapshotJsonInvalid') };
574
600
  }
575
601
  if (snapshot.id !== id) {
576
- return { ok: false, reason: 'snapshot.id 与目录不匹配' };
602
+ return { ok: false, reason: engineMsg('core.backup.snapshotIdDirMismatch') };
577
603
  }
578
604
  let manifestRaw;
579
605
  try {
580
606
  manifestRaw = await fs.readFile(path.join(dir, 'manifest.json'), 'utf8');
581
607
  }
582
608
  catch {
583
- return { ok: false, reason: 'manifest.json 缺失' };
609
+ return { ok: false, reason: engineMsg('core.backup.manifestMissing') };
584
610
  }
585
611
  const manifest = parseJsonSafe(manifestRaw);
586
612
  if (manifest === null || typeof manifest !== 'object' || manifest.snapshotId !== id) {
587
- return { ok: false, reason: 'manifest 非法或 snapshotId 不匹配' };
613
+ return { ok: false, reason: engineMsg('core.backup.manifestInvalid') };
588
614
  }
589
615
  // entryCount 匹配(manifest 声明条目数与实际一致,防计数篡改)
590
616
  if (typeof manifest.entryCount !== 'number' || manifest.entryCount !== snapshot.entries.length) {
591
- return { ok: false, reason: 'manifest.entryCount 与 snapshot.entries 不一致(篡改)' };
617
+ return { ok: false, reason: engineMsg('core.backup.entryCountMismatch') };
592
618
  }
593
619
  // metadataHash 匹配(破坏性内容)
594
620
  if (computeMetadataHash(snapshot) !== manifest.metadataHash) {
595
- return { ok: false, reason: 'metadataHash 不匹配(破坏性内容被篡改)' };
621
+ return { ok: false, reason: engineMsg('core.backup.metadataHashMismatch') };
596
622
  }
597
623
  // blob hashes 匹配 + 存在
598
624
  for (const [blobPath, expectedHash] of Object.entries(manifest.blobHashes)) {
599
625
  const target = path.join(dir, blobPath);
600
626
  if (!target.startsWith(dir))
601
- return { ok: false, reason: `blob 路径越界: ${blobPath}` };
627
+ return { ok: false, reason: engineMsg('core.backup.blobPathEscaped', { path: blobPath }) };
602
628
  let data;
603
629
  try {
604
630
  data = await fs.readFile(target);
605
631
  }
606
632
  catch {
607
- return { ok: false, reason: `blob 缺失: ${blobPath}` };
633
+ return { ok: false, reason: engineMsg('core.backup.blobMissing', { path: blobPath }) };
608
634
  }
609
635
  if (sha256Hex(data) !== expectedHash) {
610
- return { ok: false, reason: `blob hash 不匹配: ${blobPath}` };
636
+ return { ok: false, reason: engineMsg('core.backup.blobHashMismatch', { path: blobPath }) };
611
637
  }
612
638
  }
613
639
  // Review C P2:snapshot.json 引用的每个 blob(entries.copiedTo / hostFileBackups.blobPath)都必须被 manifest 覆盖,
@@ -623,7 +649,7 @@ export async function verifySnapshot(snapshotsDir, id) {
623
649
  }
624
650
  for (const relBlob of referencedBlobs) {
625
651
  if (!(relBlob in manifest.blobHashes)) {
626
- return { ok: false, reason: `snapshot 引用的 blob 未在 manifest 中: ${relBlob}` };
652
+ return { ok: false, reason: engineMsg('core.backup.blobNotInManifest', { blob: relBlob }) };
627
653
  }
628
654
  }
629
655
  return { ok: true };
@@ -34,6 +34,7 @@ import fs from 'node:fs/promises';
34
34
  import path from 'node:path';
35
35
  import { atomicWriteFile } from '../utils/atomic-write.js';
36
36
  import { sha256Hex } from '../utils/hashing.js';
37
+ import { engineMsg } from './messages-core.js';
37
38
  /* ---------------------------------------------------------------- 常量 */
38
39
  /** 默认备份目录(相对 homeDir 的 posix 路径;backupDir 缺省时使用) */
39
40
  const DEFAULT_BACKUP_DIR_REL = 'dsh-config-manager/transactions/rescue-backups';
@@ -340,13 +341,13 @@ async function readRescueState(backupDir) {
340
341
  /** 救援模式 profile patch 的头注释(说明本文件被谁接管、bundle 层的实际状态)。 */
341
342
  const RESCUE_PATCH_HEADER_LINES = [
342
343
  '# dsh-config-manager RESCUE MODE',
343
- '# 本文件(profile patch 层)已被救援模式接管;home patch 同时被置空。',
344
+ engineMsg('core.bootRescue.patchTakenOver'),
344
345
  ];
345
346
  /** 救援模式 profile patch 的头注释(按 bundle 处理方式给出准确说明,不得夸大能力)。 */
346
347
  function rescuePatchHeader(bundlesDisabled) {
347
348
  const note = bundlesDisabled
348
- ? '# dsh.profile.bundles 已被收窄为 DSH 核心与救援插件自身,被禁用的包同时已从 dependencies 临时摘除:其余用户插件本次启动不挂载。'
349
- : '# 注意:dsh.profile.bundles 里的插件仍由 bundle 层挂载(本次只裁剪了不可解析的 bundle)。';
349
+ ? engineMsg('core.bootRescue.bundlesNarrowed')
350
+ : engineMsg('core.bootRescue.bundlesNote');
350
351
  return [...RESCUE_PATCH_HEADER_LINES, note, ''].join('\n');
351
352
  }
352
353
  /** 单引号 YAML 标量(按 YAML 规则把 `'` 转义为 `''`)。 */
@@ -21,6 +21,7 @@
21
21
  import { RESCUE_KEEP_BUNDLE_PREFIXES, computeSafeBundles } from './boot-rescue.js';
22
22
  import { BOOT_CRITICAL_RELS, profileCriticalRels } from './boot-paths.js';
23
23
  import { zhMsg } from './messages.js';
24
+ import { engineMsg } from './messages-core.js';
24
25
  /** 可解析性检查的目标(.json → JSON.parse;.yaml/.yml → 注入的 parseYaml)。 */
25
26
  function parseKind(rel) {
26
27
  if (rel.endsWith('.json'))
@@ -118,7 +119,7 @@ export async function auditBootSafety(deps) {
118
119
  const drop = new Set(prunable.map((p) => p.name));
119
120
  const next = (Array.isArray(bundles) ? bundles : []).filter((b) => !(typeof b === 'string' && drop.has(b)));
120
121
  if (!writeBundles(pkg, next))
121
- throw new Error('dsh.profile.bundles 不是数组或结构异常');
122
+ throw new Error(engineMsg('core.bootSafety.bundlesNotArray'));
122
123
  await deps.writeText(pkgRel, JSON.stringify(pkg, null, 2) + '\n');
123
124
  written = true;
124
125
  }