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
@@ -16,10 +16,14 @@ import type {
16
16
  } from './sync-api.ts';
17
17
  // 通道枚举的唯一声明处(**零依赖**,可被浏览器半运行时 import):加一条通道只改这里一处。
18
18
  import { SYNC_CHANNELS } from '../../utils/shared-constants.ts';
19
- import type { SyncKey } from './sync-locales.ts';
19
+ // zh 源目录按**值**引入:本文件的渲染模型(UiT 字典不含 syncflow.* 键)需要为「回滚未完成」
20
+ // 这类 sync 命名空间文案取词;组件可经 syncText 参数注入自己的翻译器(缺省 = zh 源)。
21
+ import { en as syncEnCatalog, zh as syncZhCatalog, type SyncKey } from './sync-locales.ts';
22
+ // 跳过原因码 → 字典键的唯一映射(与同步历史共用;绝不各自维护一份 switch)
23
+ import { skipReasonKey } from './history-model.ts';
20
24
  import type { CloudConfigIssueCode } from '../../ui/sync-settings-view.ts';
21
25
  import type { S3CompatProvider } from '../../sync/sync-config.ts';
22
- import { zhUiT, type UiT } from '../../ui/i18n.ts';
26
+ import { currentUiLanguage, type UiT } from '../../ui/i18n.ts';
23
27
 
24
28
  /* ---------------------------------------------------------------- 私有仓库提示 */
25
29
 
@@ -37,7 +41,7 @@ export function needsGitToken(repoUrl: string): boolean {
37
41
  return /^https?:\/\//i.test(repoUrl.trim());
38
42
  }
39
43
 
40
- export function privateRepoHint(t: UiT = zhUiT): string {
44
+ export function privateRepoHint(t: UiT): string {
41
45
  return t('sync.privateRepoHint');
42
46
  }
43
47
 
@@ -62,14 +66,15 @@ export interface SyncSectionOption {
62
66
  /** host 目录(SyncSectionInfo[])→ UI 勾选项(保留 id 顺序)。
63
67
  * 同步分区必为导出目录(DEFAULT_CATEGORIES)的可移植子集:分组/描述从导出目录
64
68
  * 补充(单一事实源,与「导出备份·自定义模式」的目录保持一致),未命中 id 兜底。 */
65
- export function syncSectionOptions(info: readonly SyncSectionInfo[]): SyncSectionOption[] {
69
+ export function syncSectionOptions(info: readonly SyncSectionInfo[], t: UiT): SyncSectionOption[] {
66
70
  const meta = new Map(DEFAULT_CATEGORIES.map((c) => [c.id, c]));
67
71
  return info.map((s) => {
68
72
  const cat = meta.get(s.id);
69
73
  return {
70
74
  id: s.id,
71
75
  label: s.displayName,
72
- description: cat?.description ?? '',
76
+ // 描述是**字典键**(英文界面零中文):在这里按当前语言解析,渲染层直接显示
77
+ description: cat !== undefined ? t(cat.descriptionKey) : '',
73
78
  group: cat?.group ?? 'general',
74
79
  portability: s.portability,
75
80
  defaultIncluded: s.defaultIncluded,
@@ -79,7 +84,7 @@ export function syncSectionOptions(info: readonly SyncSectionInfo[]): SyncSectio
79
84
 
80
85
  /** 高级模式勾选目录 → 按导出分组(EXPORT_GROUPS)投影:与「导出备份·自定义模式」同构,
81
86
  * 空分组省略;UI 直接渲染 groupCard。 */
82
- export function syncSectionGroups(options: readonly SyncSectionOption[]): {
87
+ export function syncSectionGroups(options: readonly SyncSectionOption[], t: UiT): {
83
88
  group: ExportGroup;
84
89
  label: string;
85
90
  note?: string;
@@ -89,7 +94,7 @@ export function syncSectionGroups(options: readonly SyncSectionOption[]): {
89
94
  .map((g) => ({
90
95
  group: g.id,
91
96
  label: g.label,
92
- ...(g.note !== undefined ? { note: g.note } : {}),
97
+ ...(g.noteKey !== undefined ? { note: t(g.noteKey) } : {}),
93
98
  items: options.filter((o) => o.group === g.id),
94
99
  }))
95
100
  .filter((g) => g.items.length > 0);
@@ -167,7 +172,7 @@ export function summarizePullChanges(changes: readonly PullChange[]): PullChange
167
172
  }
168
173
 
169
174
  /** PlanItemKind → 短标签(列表徽章) */
170
- export function kindLabel(kind: PlanItemKind, t: UiT = zhUiT): string {
175
+ export function kindLabel(kind: PlanItemKind, t: UiT): string {
171
176
  switch (kind) {
172
177
  case 'Create': return t('sync.kind.create');
173
178
  case 'Update': return t('sync.kind.update');
@@ -184,7 +189,7 @@ export function kindLabel(kind: PlanItemKind, t: UiT = zhUiT): string {
184
189
  }
185
190
 
186
191
  /** severity → 短标签 */
187
- export function severityLabel(severity: PlanItem['severity'], t: UiT = zhUiT): string {
192
+ export function severityLabel(severity: PlanItem['severity'], t: UiT): string {
188
193
  switch (severity) {
189
194
  case 'error': return t('sync.severity.error');
190
195
  case 'warning': return t('sync.severity.warning');
@@ -286,6 +291,26 @@ export function cloudIssueKey(code: string): SyncKey | null {
286
291
  : null;
287
292
  }
288
293
 
294
+ /* ------------------------------------------------ G-34:gist 通道不适合大分区 */
295
+
296
+ /**
297
+ * G-34(docs/spec/known-gaps.md):gist 通道**刻意不做内容寻址外置** —— 一次 GET 把全部
298
+ * 文件内容取回、单文件内容超限会被截断、文件数还有上限,因此会话这类大分区不适合走 gist。
299
+ *
300
+ * 本常量 = 「gist 表单」常驻提示的字典键(组件按它取 sync 字典文案渲染 hint)。
301
+ */
302
+ export const GIST_LARGE_SECTION_HINT_KEY: SyncKey = 'cloud.gistLargeSectionHint'
303
+
304
+ /**
305
+ * 「通道 = gist 且勾选 sessions」时的风险提示键;不满足 → null。
306
+ *
307
+ * 只做判定、不渲染、**不改选择器逻辑**(不阻止勾选、不动勾选集合)—— 组件拿到非 null 时
308
+ * 用 sync 字典渲染一条 warn Banner(复用既有 hint / Banner 机制,不新增设置项与样式)。
309
+ */
310
+ export function gistSessionsRiskKey(channel: SyncChannel, sections: readonly SectionId[]): SyncKey | null {
311
+ return channel === 'gist' && sections.includes('sessions') ? 'mode.gistSessionsWarn' : null
312
+ }
313
+
289
314
  /**
290
315
  * 按通道构造 `Record<SyncChannel, T>`(缺省值由 `make` 给出)。
291
316
  *
@@ -424,10 +449,11 @@ function browserStorage(): Pick<Storage, 'getItem' | 'setItem'> | null {
424
449
 
425
450
  /* ---------------------------------------------------------------- WebDAV 预设 */
426
451
 
427
- /** 常见 WebDAV 服务器预设:label 展示名 + url 模板(含 <占位> 待用户替换)。 */
452
+ /** 常见 WebDAV 服务器预设:labelKey 展示名键 + url 模板(含 <占位> 待用户替换)。 */
428
453
  export interface WebDavPreset {
429
454
  id: string;
430
- label: string;
455
+ /** 展示名的**字典键**(sync 命名空间;与 SYNC_CHANNEL_LABEL_KEY / S3_PROVIDER_LABEL_KEY 同口径) */
456
+ labelKey: SyncKey;
431
457
  /** url 模板;可能含 <server>/<user> 占位符,用户需替换为真实地址 */
432
458
  url: string;
433
459
  /** 是否需要用户替换占位符 */
@@ -436,13 +462,13 @@ export interface WebDavPreset {
436
462
 
437
463
  /** 内置常见 WebDAV 服务器(预设下拉数据源;第一项为自定义)。 */
438
464
  export const WEBDAV_PRESETS: readonly WebDavPreset[] = [
439
- { id: 'custom', label: 'Custom URL', url: '', hasPlaceholder: false },
440
- { id: 'jianguoyun', label: '坚果云 (Jianguoyun)', url: 'https://dav.jianguoyun.com/dav/', hasPlaceholder: false },
441
- { id: 'nextcloud', label: 'Nextcloud', url: 'https://<server>/remote.php/dav/files/<user>/', hasPlaceholder: true },
442
- { id: 'owncloud', label: 'ownCloud', url: 'https://<server>/remote.php/dav/files/<user>/', hasPlaceholder: true },
443
- { id: 'seafile', label: 'Seafile', url: 'https://<server>/seafdav/', hasPlaceholder: true },
444
- { id: 'synology', label: 'Synology NAS (WebDAV)', url: 'https://<nas-ip>:5006/', hasPlaceholder: true },
445
- { id: 'box', label: 'Box', url: 'https://dav.box.com/dav/', hasPlaceholder: false },
465
+ { id: 'custom', labelKey: 'webdav.preset.custom', url: '', hasPlaceholder: false },
466
+ { id: 'jianguoyun', labelKey: 'webdav.preset.jianguoyun', url: 'https://dav.jianguoyun.com/dav/', hasPlaceholder: false },
467
+ { id: 'nextcloud', labelKey: 'webdav.preset.nextcloud', url: 'https://<server>/remote.php/dav/files/<user>/', hasPlaceholder: true },
468
+ { id: 'owncloud', labelKey: 'webdav.preset.owncloud', url: 'https://<server>/remote.php/dav/files/<user>/', hasPlaceholder: true },
469
+ { id: 'seafile', labelKey: 'webdav.preset.seafile', url: 'https://<server>/seafdav/', hasPlaceholder: true },
470
+ { id: 'synology', labelKey: 'webdav.preset.synology', url: 'https://<nas-ip>:5006/', hasPlaceholder: true },
471
+ { id: 'box', labelKey: 'webdav.preset.box', url: 'https://dav.box.com/dav/', hasPlaceholder: false },
446
472
  ];
447
473
 
448
474
  /** 默认预设(自定义)对应的 id。 */
@@ -484,7 +510,7 @@ export interface SyncButtons {
484
510
  * - 活动通道远端地址未就绪(remoteReady=false)→ 禁用(无从同步);
485
511
  * - busy 时按钮文案切换为「正在推送/拉取…」(配 Spinner)。
486
512
  */
487
- export function computeSyncButtons(busy: 'sync' | 'push' | 'pull' | 'apply' | 'rollback' | null, remoteReady: boolean, t: UiT = zhUiT): SyncButtons {
513
+ export function computeSyncButtons(busy: 'sync' | 'push' | 'pull' | 'apply' | 'rollback' | null, remoteReady: boolean, t: UiT): SyncButtons {
488
514
  const idle = busy === null;
489
515
  const enabled = idle && remoteReady;
490
516
  return {
@@ -509,7 +535,7 @@ export function computeSyncStatus(
509
535
  statusInfo: SyncStatusResponse | null,
510
536
  loading: boolean,
511
537
  error: string | null,
512
- t: UiT = zhUiT,
538
+ t: UiT,
513
539
  ): SyncStatusSummary {
514
540
  if (loading) return { kind: 'loading', text: t('sync.statusLoading') };
515
541
  if (error !== null) return { kind: 'error', text: error };
@@ -543,7 +569,7 @@ export function formatDateTime(iso: string): string {
543
569
  }
544
570
 
545
571
  /** 上次同步时间的展示文本('' / undefined = 从未同步) */
546
- export function formatLastSync(iso: string | undefined, t: UiT = zhUiT): string {
572
+ export function formatLastSync(iso: string | undefined, t: UiT): string {
547
573
  if (iso === undefined || iso === '') return t('sync.neverSyncedShort');
548
574
  return formatDateTime(iso);
549
575
  }
@@ -558,7 +584,7 @@ export interface PushReportView {
558
584
  }
559
585
 
560
586
  /** push 报告 → 渲染模型(ok 头部带快照 id;失败显示引擎 message;分区与告警透传) */
561
- export function pushReportView(report: SyncPushReport | null, t: UiT = zhUiT): PushReportView | null {
587
+ export function pushReportView(report: SyncPushReport | null, t: UiT): PushReportView | null {
562
588
  if (report === null) return null;
563
589
  if (!report.ok) {
564
590
  return { kind: 'error', headline: report.message ?? t('sync.pushFailed'), sections: report.sections, warnings: report.warnings };
@@ -576,7 +602,8 @@ export function pushReportView(report: SyncPushReport | null, t: UiT = zhUiT): P
576
602
  export interface PushPreviewView {
577
603
  ok: boolean;
578
604
  /** 将推送的分区行(含计数 + 是否相对基线有变化) */
579
- rows: { section: string; count: number; changed: boolean }[];
605
+ /** 分区 id 来自宿主 SyncPushPreviewSection.section(SectionId)——写 string 会让渲染侧被迫转型 */
606
+ rows: { section: SectionId; count: number; changed: boolean }[];
580
607
  /** 变更分区数(要展示「新增/更新 N 个分区」) */
581
608
  changedCount: number;
582
609
  /** 远端现有快照数(0 = 首次推送创建首个基线) */
@@ -592,7 +619,7 @@ export interface PushPreviewView {
592
619
  }
593
620
 
594
621
  /** push 预览 → 渲染模型(P0-②):列分区 + 变更计数 + 远端基线提示。 */
595
- export function pushPreviewView(preview: SyncPushPreview | null, t: UiT = zhUiT): PushPreviewView | null {
622
+ export function pushPreviewView(preview: SyncPushPreview | null, t: UiT): PushPreviewView | null {
596
623
  if (preview === null) return null;
597
624
  if (!preview.ok) {
598
625
  return {
@@ -627,7 +654,7 @@ export interface PullReportView {
627
654
  }
628
655
 
629
656
  /** pull 报告 → 渲染模型(差异预览;empty = 无变更;error = 拉取失败) */
630
- export function pullReportView(report: SyncPullReport | null, t: UiT = zhUiT): PullReportView | null {
657
+ export function pullReportView(report: SyncPullReport | null, t: UiT): PullReportView | null {
631
658
  if (report === null) return null;
632
659
  if (!report.ok) {
633
660
  return { kind: 'error', headline: report.message ?? t('sync.pullFailed'), summary: null, previewHint: '' };
@@ -677,7 +704,7 @@ export function computeGithubLoginView(
677
704
  userCode: string,
678
705
  verificationUri: string,
679
706
  error: string | null,
680
- t: UiT = zhUiT,
707
+ t: UiT,
681
708
  ): GithubLoginView {
682
709
  const inFlight = phase === 'starting' || phase === 'waiting' || phase === 'polling';
683
710
  let statusText: string;
@@ -716,7 +743,7 @@ export function computeGithubLoginView(
716
743
  }
717
744
 
718
745
  /** 轮询终止态 → 用户可读消息(pending 不是终止态,返回空串;成功/拒绝/过期/错误给出明确文案) */
719
- export function githubPollMessage(poll: GithubPollResponse, t: UiT = zhUiT): string {
746
+ export function githubPollMessage(poll: GithubPollResponse, t: UiT): string {
720
747
  switch (poll.status) {
721
748
  case 'success':
722
749
  return t('sync.github.pollSuccess');
@@ -895,6 +922,7 @@ export function buildAdoptions(
895
922
  items: readonly SyncConfirmItem[],
896
923
  adopted: ReadonlyMap<string, boolean>,
897
924
  resolutions: ReadonlyMap<string, SyncConflictResolution>,
925
+ t: UiT,
898
926
  ): SyncItemAdoption[] {
899
927
  const out: SyncItemAdoption[] = [];
900
928
  for (const it of items) {
@@ -903,7 +931,7 @@ export function buildAdoptions(
903
931
  if (it.kind === 'Conflict') {
904
932
  const resolution = resolutions.get(it.itemId);
905
933
  if (resolution === undefined) {
906
- throw new Error(`冲突项 ${it.itemId} 必须先选择解决方式(保留当前 / 使用导入)`);
934
+ throw new Error(t('sync.conflictUnresolved', { id: it.itemId }));
907
935
  }
908
936
  adoption.resolution = resolution;
909
937
  }
@@ -919,7 +947,21 @@ export function buildAdoptions(
919
947
  * `ok` 因此仍为 true;用户看到「已导入 N 个分区」就以为全部成功,
920
948
  * 而排在插件之后的步骤(凭据写入)根本没执行 —— 真机表现就是「模型列表同步过来了、密钥没进来」。
921
949
  */
922
- export type ApplyItemsViewKind = 'ok' | 'partial' | 'failed' | 'rolledBack';
950
+ export type ApplyItemsViewKind = 'ok' | 'partial' | 'failed' | 'rolledBack' | 'rollbackIncomplete';
951
+
952
+ /** sync 命名空间的取词器(缺省 = 该字典的 zh 源;组件可注入自己的 t 以本地化)。 */
953
+ export type SyncTextFn = (key: SyncKey, params?: Record<string, unknown>) => string
954
+ // **活**取词器(不是 zh 快照):sync 命名空间的 zh/en 目录都在手边,按当前语言取词 ——
955
+ // 否则英文界面上「回滚未完成」这类只住在 sync 字典的文案会原样透出中文。
956
+ const defaultSyncText: SyncTextFn = (key, params) => {
957
+ const raw = (currentUiLanguage() === 'zh' ? syncZhCatalog : syncEnCatalog)[key]
958
+ if (params === undefined) return raw
959
+ // 与字典的 {param} 口径一致(缺参原样保留,便于排查)
960
+ return raw.replace(/\{(\w+)\}/g, (m, name: string) => {
961
+ const value = params[name]
962
+ return value === undefined ? m : String(value)
963
+ })
964
+ }
923
965
 
924
966
  export interface ApplyItemsView {
925
967
  kind: ApplyItemsViewKind;
@@ -930,30 +972,45 @@ export interface ApplyItemsView {
930
972
  ineffective: { itemId: string; adapter: string; message?: string }[];
931
973
  restoreId: string;
932
974
  needsRestart: boolean;
975
+ /**
976
+ * 整体回滚是否**完整**(诚实性,2026-10):`rolledBack=true` 只说明触发了整体回滚,
977
+ * 不代表每一项都补偿成功(凭据原本存在 → DSH 不可回读,无法自动恢复)。
978
+ * false 时 kind='rollbackIncomplete' —— **绝不**渲染成「已整体回滚」。
979
+ * 旧宿主不返回 → 字段缺席(按旧的「已整体回滚」处理,不误报)。
980
+ */
981
+ rollbackFull?: boolean;
933
982
  }
934
983
 
935
- /** apply-items 执行结果 → 渲染模型(ok / failed / 整体回滚)。 */
984
+ /** apply-items 执行结果 → 渲染模型(ok / partial / failed / 整体回滚 / 回滚未完成)。 */
936
985
  export function applyItemsReportView(
937
986
  report: ApplyItemsResponse | null,
938
- t: UiT = zhUiT,
987
+ t: UiT,
988
+ syncText: SyncTextFn = defaultSyncText,
939
989
  ): ApplyItemsView | null {
940
990
  if (report === null) return null;
941
991
  const failedOnly = report.failed.length > 0 && !report.ok;
942
992
  // 旧宿主不返回 ineffective → 空数组(按「无未生效项」处理,绝不据此误报部分成功)
943
993
  const ineffective = report.ineffective ?? [];
944
- const kind: ApplyItemsViewKind = !report.ok && report.rolledBack
945
- ? 'rolledBack'
946
- : failedOnly
947
- ? 'failed'
948
- : ineffective.length > 0
949
- ? 'partial'
950
- : 'ok';
994
+ const kind: ApplyItemsViewKind = !report.ok && report.rollbackFull === false
995
+ // 回滚**未完成**优先于「已整体回滚」:半回滚态的配置可能停在中间状态,必须单独成态
996
+ ? 'rollbackIncomplete'
997
+ : !report.ok && report.rolledBack
998
+ ? 'rolledBack'
999
+ : failedOnly
1000
+ ? 'failed'
1001
+ : ineffective.length > 0
1002
+ ? 'partial'
1003
+ : 'ok';
951
1004
  const headline = kind === 'ok'
952
1005
  ? t('sync.importDone', { n: String(report.applied.length) })
953
1006
  : kind === 'partial'
954
1007
  // 不用「失败」措辞:这次同步**大部分成功**,只是有 N 项没生效 —— 措辞必须与事实同强度
955
1008
  ? t('sync.importPartial', { n: String(ineffective.length) })
956
- : t('sync.importFailed');
1009
+ // 回滚未完成:UiT 字典没有这个键('sync.importFailed' 的文案断言「已整体回滚」),
1010
+ // 取 sync 字典的专用文案 —— 措辞与事实同强度,绝不把半回滚说成已回滚
1011
+ : kind === 'rollbackIncomplete'
1012
+ ? syncText('syncflow.importRollbackIncomplete')
1013
+ : t('sync.importFailed');
957
1014
  return {
958
1015
  kind,
959
1016
  headline,
@@ -962,6 +1019,7 @@ export function applyItemsReportView(
962
1019
  ineffective,
963
1020
  restoreId: report.restoreId,
964
1021
  needsRestart: report.needsRestart,
1022
+ ...(report.rollbackFull !== undefined ? { rollbackFull: report.rollbackFull } : {}),
965
1023
  };
966
1024
  }
967
1025
 
@@ -990,7 +1048,7 @@ export function computeAutosyncCountdown(elapsedMs: number, intervalMs: number):
990
1048
  * 剩余时长 → 可读文案(向上取整,避免出现「0 分钟」;≤0 视为 1 分钟兜底)。
991
1049
  * 例:4 分钟 →「4 分钟」;90 分钟 →「2 小时」;30 小时 →「2 天」。
992
1050
  */
993
- export function formatIntervalDuration(ms: number, t: UiT = zhUiT): string {
1051
+ export function formatIntervalDuration(ms: number, t: UiT): string {
994
1052
  const totalMinutes = Math.max(1, Math.ceil(ms / 60000));
995
1053
  if (totalMinutes < 60) return t('sync.duration.min', { n: totalMinutes });
996
1054
  const totalHours = Math.ceil(totalMinutes / 60);
@@ -999,8 +1057,33 @@ export function formatIntervalDuration(ms: number, t: UiT = zhUiT): string {
999
1057
  return t('sync.duration.day', { n: totalDays });
1000
1058
  }
1001
1059
 
1060
+ /**
1061
+ * 自动同步状态行的「本轮跳过原因」明细(issue #79)。
1062
+ *
1063
+ * 为什么单独成行而不是塞进状态徽章:跳过原因可能带数量(远端另有 N 个分区待确认),
1064
+ * 徽章会挤成两行;且用户最需要的信息正是「为什么没同步」——改造前它只进 info 级日志
1065
+ * (默认日志级别 warn),界面完全看不到。
1066
+ *
1067
+ * 未知码回退宿主原串(与同步历史同口径,不吞信息,也绝不渲染裸回调码当文案)。
1068
+ */
1069
+ export function autosyncSkipDetail(
1070
+ status: AutosyncStatusResponse,
1071
+ syncText: SyncTextFn = defaultSyncText,
1072
+ ): string | null {
1073
+ const reason = status.lastRunSkipReason
1074
+ if (reason === undefined || reason === '') return null
1075
+ const key = skipReasonKey(reason)
1076
+ // 未知码:给可读兜底,**绝不**把裸码渲染成文案(历史行保留原串以便排查,状态行只要可读)
1077
+ const label = key === null ? syncText('history.autosyncReasonUnknown') : syncText(key)
1078
+ // 冲突:补一句「远端另有 N 个分区待确认」(数量来自宿主的结构化字段,不是从文案里抠)
1079
+ if (reason === 'conflict' && status.lastRunSkipCount !== undefined && status.lastRunSkipCount > 0) {
1080
+ return syncText('autosync.skipReasonConflictCount', { reason: label, n: String(status.lastRunSkipCount) })
1081
+ }
1082
+ return label
1083
+ }
1084
+
1002
1085
  /** 自动同步状态行的可读文案(未运行 / 上次状态 / 连续失败计数)。 */
1003
- export function autosyncStatusText(status: AutosyncStatusResponse, t: UiT = zhUiT): string {
1086
+ export function autosyncStatusText(status: AutosyncStatusResponse, t: UiT): string {
1004
1087
  if (status.lastRunAt === undefined || status.lastRunAt === '' || status.lastRunStatus === undefined) {
1005
1088
  return t('sync.autosyncNever');
1006
1089
  }
@@ -26,7 +26,7 @@ import type { FilesSection, Manifest, SectionId } from '../schema/types.ts';
26
26
  import { loadTombstones, isTombstoned } from '../schema/tombstones.ts';
27
27
  import type { Tombstone, TombstoneKind } from '../schema/tombstones.ts';
28
28
  import { DEFAULT_SENSITIVE_RELS, restoreVaultFiles } from '../security/vault.ts';
29
- import { createSnapshot, planItemWritesTarget, resolveFileTarget, resolveFileTargetRel } from './backup.ts';
29
+ import { createSnapshot, fsForSection, planItemWritesTarget, resolveFileTarget, resolveFileTargetRel } from './backup.ts';
30
30
  import { isCredentialConfigured } from './credential-status.ts';
31
31
  import { rollback } from './rollback.ts';
32
32
  import { compatibilityReasons, computeCompatibility } from './validator.ts';
@@ -37,7 +37,7 @@ import {
37
37
  ImportNotConfirmedError, ImportUserSkippedError, type ApplyResult, type ConfigAdapter,
38
38
  type ExecutedItem, type HostContext, type ImportAnalysis, type ImportContext,
39
39
  type ImportDecisions, type ImportPlan, type ImportResult, type PathIssue,
40
- type PathMapping, type PlanItem, type SessionFormatProbeResult, type SkippedTombstone,
40
+ type FileSystemFacade, type PathMapping, type PlanItem, type SessionFormatProbeResult, type SkippedTombstone,
41
41
  type Snapshot, type SnapshotStore, type TransactionSnapshotContext,
42
42
  } from './types.ts';
43
43
 
@@ -953,7 +953,7 @@ export class Analyzer {
953
953
  let beforeFp: string | null = null;
954
954
  if (Analyzer.fileRelFor(item, importCtx.target.profile) !== null) {
955
955
  rel = Analyzer.fileRelFor(item, importCtx.target.profile) as string;
956
- beforeFp = await this.fileFp(importCtx, rel);
956
+ beforeFp = await this.fileFp(importCtx, item);
957
957
  }
958
958
  beforeFpByItem.set(item.id, beforeFp);
959
959
  await sb.recordStep({
@@ -1055,7 +1055,7 @@ export class Analyzer {
1055
1055
  const rel = Analyzer.fileRelFor(item, importCtx.target.profile);
1056
1056
  let afterFp: string | null = null;
1057
1057
  if (rel !== null && outcome.executed.status === 'ok') {
1058
- afterFp = await this.fileFp(importCtx, rel);
1058
+ afterFp = await this.fileFp(importCtx, item);
1059
1059
  }
1060
1060
  await sb.recordStep({
1061
1061
  id: item.id,
@@ -1382,7 +1382,7 @@ export class Analyzer {
1382
1382
  return planItemWritesTarget(item);
1383
1383
  }
1384
1384
 
1385
- /** 文件类且可指纹 → 返回 home-relative 目标路径(posix);否则 null(不可指纹外部项)。
1385
+ /** 文件类且可指纹 → 返回「根相对路径」(journal 的 ref 身份);否则 null(不可指纹外部项)。
1386
1386
  * profile 必须传入:plugins 分区的 pnpm-workspace.yaml / patch 文件位于
1387
1387
  * `profiles/<profile>/` 下,缺了它算出的相对路径不存在 → 指纹恒 null(issue #35)。 */
1388
1388
  private static fileRelFor(item: PlanItem, profile?: string): string | null {
@@ -1392,11 +1392,33 @@ export class Analyzer {
1392
1392
  return resolveFileTargetRel(item.adapter, ref, profile);
1393
1393
  }
1394
1394
 
1395
- /** 读目标文件算 sha256(home-relative;经 HostContext.fs 使测试 mock 可注入)。失败返回 null。 */
1396
- private async fileFp(ctx: ImportContext, rel: string): Promise<string | null> {
1395
+ /**
1396
+ * 文件类且可指纹 → 返回「该分区自己的门面 + 绝对路径」。
1397
+ *
1398
+ * 为什么不能只拿 home-relative 路径去读:`pluginData` / `pluginAssets` 的根是**系统 home**
1399
+ * (见 `core/backup.ts` 的 `fsForSection`),用 `ctx.fs` 读会落到 $DSH_HOME 下的错误位置
1400
+ * 或被越界守卫拦下 —— 指纹会静默变成「读不到」,reconcile 因此永远判不出是否已应用。
1401
+ * 宿主未提供该门面 / 路径不可解析 → null(保守:不可证明)。
1402
+ */
1403
+ private static fileTargetFor(
1404
+ ctx: ImportContext, item: PlanItem,
1405
+ ): { fs: FileSystemFacade; abs: string } | null {
1406
+ if (!isFileSection(item.adapter)) return null;
1407
+ const ref = item.target?.ref;
1408
+ if (ref === undefined || ref === '') return null;
1409
+ try {
1410
+ return { fs: fsForSection(ctx.target, item.adapter), abs: resolveFileTarget(ctx.target, item.adapter, ref) };
1411
+ } catch {
1412
+ return null;
1413
+ }
1414
+ }
1415
+
1416
+ /** 读目标文件算 sha256(经该分区自己的门面,测试 mock 可注入)。失败返回 null。 */
1417
+ private async fileFp(ctx: ImportContext, item: PlanItem): Promise<string | null> {
1418
+ const target = Analyzer.fileTargetFor(ctx, item);
1419
+ if (target === null) return null;
1397
1420
  try {
1398
- const data = await ctx.target.fs.readFile(rel);
1399
- return sha256Hex(data);
1421
+ return sha256Hex(await target.fs.readFile(target.abs));
1400
1422
  } catch {
1401
1423
  return null;
1402
1424
  }
@@ -16,7 +16,8 @@ import fs from 'node:fs/promises';
16
16
  import path from 'node:path';
17
17
 
18
18
  import {
19
- DEFAULT_BACKUP_SECTIONS, OFFLINE_BACKUP_SECTIONS, OFFLINE_UNAVAILABLE_SECTIONS, OPT_IN_BACKUP_SECTIONS,
19
+ DEFAULT_BACKUP_SECTIONS, OFFLINE_BACKUP_SECTIONS, OFFLINE_EXCLUDED_SECTIONS, OFFLINE_UNAVAILABLE_SECTIONS,
20
+ OPT_IN_BACKUP_SECTIONS,
20
21
  buildSectionFlags,
21
22
  } from './backup-plan.ts';
22
23
  import { APPLY_ORDER } from './analyzer.ts';
@@ -105,20 +106,34 @@ test('离线分区清单:注册表所有分区都必须显式归类(可收
105
106
  // 「明确不参与离线」:sessions 体量巨大且属用户数据(GUI 侧单独 opt-in)、secrets 无分区载荷
106
107
  // (凭据值走独立加密容器)。二者都不在 CLI 离线备份的目标集合内 —— 但必须**显式**列出,
107
108
  // 否则新增分区会被静默漏收/漏报。
108
- const NOT_OFFLINE_APPLICABLE: readonly SectionId[] = ['sessions', 'secrets'];
109
+ // pluginData / pluginAssets 也在此列(第三方插件数据的收集依赖 adapter 的来源注册表;
110
+ // 见 backup-plan.ts 的 OFFLINE_EXCLUDED_SECTIONS —— 直接引用产品常量,避免测试与实现各抄一份)
111
+ const NOT_OFFLINE_APPLICABLE: readonly SectionId[] = [
112
+ 'sessions', 'secrets', ...OFFLINE_EXCLUDED_SECTIONS,
113
+ ];
109
114
  const classified = new Set<string>([...OFFLINE_BACKUP_SECTIONS, ...OFFLINE_UNAVAILABLE_SECTIONS, ...NOT_OFFLINE_APPLICABLE]);
110
115
  assert.deepEqual([...classified].sort(), [...SECTION_IDS].sort(), '新增分区必须在 backup-plan 里显式归类');
111
116
  });
112
117
 
113
118
  /* ------------------------------------------------------------ FILE_BASES */
114
119
 
115
- test('resolveFileTarget:文件类分区都有明确基准目录(除两个 home 根分区外不得落 homeDir 根)', () => {
116
- // 只有这两个分区的文件**本来就在** $DSH_HOME 根(AGENTS.md / 插件自有文件白名单)
120
+ test('resolveFileTarget:文件类分区都有明确根($DSH_HOME 内,或系统 home 根的那两个分区)', () => {
121
+ // 文件本来就在 $DSH_HOME 根的分区:AGENTS.md / 插件自有文件白名单
117
122
  const HOME_ROOT_SECTIONS: readonly SectionId[] = ['agentInstructions', 'pluginFiles'];
118
- const ctx = { homeDir: path.join('C:', 'home', '.dsh'), profile: 'web' } as unknown as HostContext;
123
+ // **系统 home 为根**的分区(第三方插件数据):$DSH_HOME 之外的第二个根,见 backup.ts 的 fsForSection
124
+ const OS_HOME_SECTIONS: readonly SectionId[] = ['pluginData', 'pluginAssets'];
125
+ const ctx = {
126
+ homeDir: path.join('C:', 'home', '.dsh'),
127
+ osHomeDir: path.join('C:', 'home'),
128
+ profile: 'web',
129
+ } as unknown as HostContext;
119
130
  const atHomeRoot = path.join(ctx.homeDir, 'probe.txt');
120
131
  for (const id of SECTION_IDS.filter((s) => isFileSection(s))) {
121
132
  const target = resolveFileTarget(ctx, id, 'probe.txt');
133
+ if (OS_HOME_SECTIONS.includes(id)) {
134
+ assert.equal(target, path.join(ctx.osHomeDir as string, 'probe.txt'), `${id} 的根必须是系统 home`);
135
+ continue;
136
+ }
122
137
  assert.ok(target.startsWith(ctx.homeDir), `${id} 的目标必须在 homeDir 下: ${target}`);
123
138
  if (HOME_ROOT_SECTIONS.includes(id)) {
124
139
  assert.equal(target, atHomeRoot, `${id} 是 home 根分区`);
@@ -169,8 +184,36 @@ test('t37 skippedLinks 措辞:普通目录超深不得被描述成「链接」
169
184
  assert.match(depthLine, /层级超过上限/, '普通目录超深的原因必须指向层级上限,实际:' + depthLine);
170
185
  assert.doesNotMatch(depthLine, /链接未进备份/, '普通目录超深不得被描述成链接问题,实际:' + depthLine);
171
186
  assert.match(linkLine, /链接未进备份/, '链接类跳过仍必须是链接措辞,实际:' + linkLine);
187
+ // issue #76 附带修正:内核返回的已是 homeDir 相对路径,**不得再叠一层分区前缀**(曾打印成 skills/skills/outlink)
188
+ assert.match(linkLine, /skills\/outlink(/, '路径必须只出现一次且为 homeDir 相对形态,实际:' + linkLine);
189
+ assert.doesNotMatch(linkLine, /skills\/skills\//, '不得叠加分区前缀(skills/skills/…),实际:' + linkLine);
172
190
  } finally {
173
191
  await fs.rm(home, { recursive: true, force: true });
174
192
  await fs.rm(outside, { recursive: true, force: true });
175
193
  }
176
194
  });
195
+
196
+ test('issue #76:离线备份跳过 skills 下的 node_modules,并在 warnings 里说明(真机形态)', async () => {
197
+ const os = await import('node:os');
198
+ const { collectBackupEntries } = await import('./backup-plan.ts');
199
+ const home = await fs.mkdtemp(path.join(os.tmpdir(), 'dcm-bp-deps-'));
200
+ try {
201
+ const nm = path.join(home, 'skills', 'md2pdf', 'node_modules', 'marked');
202
+ await fs.mkdir(nm, { recursive: true });
203
+ await fs.writeFile(path.join(home, 'skills', 'md2pdf', 'SKILL.md'), 'S');
204
+ await fs.writeFile(path.join(home, 'skills', 'md2pdf', 'package.json'), '{}');
205
+ await fs.writeFile(path.join(nm, 'index.js'), 'M');
206
+ await fs.writeFile(path.join(nm, 'extra.js'), 'M');
207
+
208
+ const plan = await collectBackupEntries(home, ['skills']);
209
+ const names = plan.entries.map((e) => e.name);
210
+ assert.ok(names.some((n) => n.endsWith('md2pdf/SKILL.md')), '技能本体必须进归档: ' + names.join(','));
211
+ assert.ok(names.some((n) => n.endsWith('md2pdf/package.json')), 'package.json 必须进归档(目标机据此重建依赖)');
212
+ assert.deepEqual(names.filter((n) => n.includes('node_modules/')), [], '依赖树不得进归档: ' + names.join(','));
213
+ const line = plan.warnings.find((w) => w.includes('运行时依赖目录')) ?? '';
214
+ assert.ok(line.length > 0, '缺依赖目录告警: ' + plan.warnings.join(' | '));
215
+ assert.match(line, /2 个文件/, '必须报出被跳过的文件数量级');
216
+ } finally {
217
+ await fs.rm(home, { recursive: true, force: true });
218
+ }
219
+ });