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
@@ -10,6 +10,7 @@
10
10
  * 唯一来源是 `section-registry.ts` 各分区条目的 `dataVersion`,
11
11
  * 历史上有 11 处 `data.version !== 1` 字面量,现已收敛到此处两个函数。
12
12
  */
13
+ import { schemaMsg } from './messages.js';
13
14
  import { SECTION_REGISTRY } from './section-registry.js';
14
15
  /** 当前 Export Schema 版本(v1 起点;未来随迁移链演进) */
15
16
  export const CURRENT_SCHEMA_VERSION = 1;
@@ -47,12 +48,12 @@ export function canImport(v) {
47
48
  /** 版本的可读描述(报告/日志用) */
48
49
  export function describeVersion(v) {
49
50
  if (isTooNew(v))
50
- return `schema v${v}(高于当前 ${CURRENT_SCHEMA_VERSION},需升级插件)`;
51
+ return schemaMsg('schema.versionTooNew', { version: v, current: CURRENT_SCHEMA_VERSION });
51
52
  if (needsMigration(v))
52
- return `schema v${v}(旧版,将迁移到 v${CURRENT_SCHEMA_VERSION})`;
53
+ return schemaMsg('schema.versionNeedsMigration', { version: v, current: CURRENT_SCHEMA_VERSION });
53
54
  if (isCurrent(v))
54
- return `schema v${v}(当前版本)`;
55
- return `schema v${v}(低于最低支持 ${MIN_SUPPORTED_SCHEMA_VERSION},不受支持)`;
55
+ return schemaMsg('schema.versionCurrent', { version: v });
56
+ return schemaMsg('schema.versionBelowMinimum', { version: v, minimum: MIN_SUPPORTED_SCHEMA_VERSION });
56
57
  }
57
58
  /* ---------------------------------------------------------------- 分区载荷版本(第二条版本轴) */
58
59
  /** 某分区的载荷版本(唯一来源:注册表 `dataVersion`) */
@@ -71,6 +72,6 @@ export function isSupportedSectionDataVersion(sectionId, version) {
71
72
  export function sectionDataVersionIssue(sectionId, version) {
72
73
  if (isSupportedSectionDataVersion(sectionId, version))
73
74
  return null;
74
- return `分区 ${sectionId} 的 version 必须为 ${SECTION_REGISTRY[sectionId].dataVersion}(收到 ${String(version)})`;
75
+ return schemaMsg('schema.sectionDataVersionMismatch', { section: sectionId, expected: SECTION_REGISTRY[sectionId].dataVersion, actual: String(version) });
75
76
  }
76
77
  //# sourceMappingURL=versions.js.map
@@ -19,6 +19,7 @@
19
19
  */
20
20
  import crypto from 'node:crypto';
21
21
  import { promisify } from 'node:util';
22
+ import { securityMsg } from './messages.js';
22
23
  const scryptAsync = promisify(crypto.scrypt);
23
24
  export const SCHEMA_MAGIC = 'DSC1';
24
25
  export const SCHEMA_VERSION = 1;
@@ -63,7 +64,7 @@ export async function deriveKey(password, salt, params = SCRYPT_PARAMS) {
63
64
  /** 加密 .credentials.yaml 原文 → { blob, info }(info 直接进 manifest.security.encryption) */
64
65
  export async function encryptCredentials(plaintext, password) {
65
66
  if (password === '')
66
- throw new SecurityError('BAD_PASSWORD', '加密密码不能为空');
67
+ throw new SecurityError('BAD_PASSWORD', securityMsg('security.encryptionPasswordEmpty'));
67
68
  // 产品决策(2026-09-13):加密**不做任何密码强度校验**——用户提供的任何密码都必须可用。
68
69
  // 请勿在此重新加入强度闸门;用户自定的密码策略不由本插件约束。
69
70
  const salt = crypto.randomBytes(SALT_LENGTH);
@@ -92,18 +93,18 @@ export async function encryptCredentials(plaintext, password) {
92
93
  /** 解密 secrets.enc(authTag 校验失败抛 BAD_PASSWORD;元数据不一致抛 TAMPERED) */
93
94
  export async function decryptCredentials(blob, info, password) {
94
95
  if (blob.length < HEADER_LENGTH) {
95
- throw new SecurityError('TAMPERED', 'secrets.enc 体积过小(截断或损坏)');
96
+ throw new SecurityError('TAMPERED', securityMsg('security.secretsBlobTooSmall'));
96
97
  }
97
98
  const magic = Buffer.from(blob.subarray(0, 4)).toString('ascii');
98
99
  if (magic !== SCHEMA_MAGIC) {
99
- throw new SecurityError('UNSUPPORTED_FORMAT', `未知文件格式 magic "${magic}",不是本插件产物`);
100
+ throw new SecurityError('UNSUPPORTED_FORMAT', securityMsg('security.unknownFileMagic', { magic }));
100
101
  }
101
102
  const version = blob[4];
102
103
  if (version !== SCHEMA_VERSION) {
103
- throw new SecurityError('UNSUPPORTED_FORMAT', `不支持的 secrets.enc 版本 ${version}`);
104
+ throw new SecurityError('UNSUPPORTED_FORMAT', securityMsg('security.unsupportedSecretsVersion', { version }));
104
105
  }
105
106
  if (info.algorithm !== 'aes-256-gcm' || info.kdf !== 'scrypt' || !validateKdfParams(info.kdfParams)) {
106
- throw new SecurityError('UNSUPPORTED_FORMAT', 'manifest 加密参数非法或不受支持');
107
+ throw new SecurityError('UNSUPPORTED_FORMAT', securityMsg('security.invalidEncryptionParams'));
107
108
  }
108
109
  const blobSalt = blob.subarray(5, 5 + SALT_LENGTH);
109
110
  const blobIv = blob.subarray(5 + SALT_LENGTH, 5 + SALT_LENGTH + IV_LENGTH);
@@ -111,14 +112,14 @@ export async function decryptCredentials(blob, info, password) {
111
112
  if (info.salt !== Buffer.from(blobSalt).toString('base64') ||
112
113
  info.iv !== Buffer.from(blobIv).toString('base64') ||
113
114
  info.authTag !== Buffer.from(blobTag).toString('base64')) {
114
- throw new SecurityError('TAMPERED', 'secrets.enc 与 manifest 加密参数不一致(可能被篡改)');
115
+ throw new SecurityError('TAMPERED', securityMsg('security.encryptionParamsMismatch'));
115
116
  }
116
117
  let key;
117
118
  try {
118
119
  key = await deriveKey(password, blobSalt, info.kdfParams);
119
120
  }
120
121
  catch (err) {
121
- throw new SecurityError('BAD_PASSWORD', `密钥派生失败: ${err instanceof Error ? err.message : String(err)}`);
122
+ throw new SecurityError('BAD_PASSWORD', securityMsg('security.keyDerivationFailed', { reason: err instanceof Error ? err.message : String(err) }));
122
123
  }
123
124
  const decipher = crypto.createDecipheriv('aes-256-gcm', key, blobIv);
124
125
  decipher.setAuthTag(blobTag);
@@ -130,7 +131,7 @@ export async function decryptCredentials(blob, info, password) {
130
131
  return decrypted.toString('utf8');
131
132
  }
132
133
  catch {
133
- throw new SecurityError('BAD_PASSWORD', '解密认证失败:密码错误或密文被篡改');
134
+ throw new SecurityError('BAD_PASSWORD', securityMsg('security.decryptAuthFailed'));
134
135
  }
135
136
  }
136
137
  /**
@@ -160,7 +161,7 @@ export function createEncryptionProvider(password) {
160
161
  */
161
162
  export async function encryptArchive(plainZip, password) {
162
163
  if (password === '')
163
- throw new SecurityError('BAD_PASSWORD', '加密密码不能为空');
164
+ throw new SecurityError('BAD_PASSWORD', securityMsg('security.encryptionPasswordEmpty'));
164
165
  // 产品决策(2026-09-13):加密**不做任何密码强度校验**——用户提供的任何密码都必须可用。
165
166
  // 请勿在此重新加入强度闸门;用户自定的密码策略不由本插件约束。
166
167
  const salt = crypto.randomBytes(SALT_LENGTH);
@@ -189,15 +190,15 @@ export async function encryptArchive(plainZip, password) {
189
190
  /** 容器版解密:用「先验真实参数」校验密码并解密(—— 调用方必须先用 verifyEncryptedBlob 拿到 info/kdf) */
190
191
  export async function decryptArchive(blob, info, kdfParam, password) {
191
192
  if (blob.length < HEADER_LENGTH) {
192
- throw new SecurityError('TAMPERED', '加密备份容器体积过小(截断或损坏)');
193
+ throw new SecurityError('TAMPERED', securityMsg('security.archiveBlobTooSmall'));
193
194
  }
194
195
  const magic = Buffer.from(blob.subarray(0, 4)).toString('ascii');
195
196
  if (magic !== ARCHIVE_MAGIC) {
196
- throw new SecurityError('UNSUPPORTED_FORMAT', `未知文件格式 magic "${magic}",不是本插件产物`);
197
+ throw new SecurityError('UNSUPPORTED_FORMAT', securityMsg('security.unknownFileMagic', { magic }));
197
198
  }
198
199
  const version = blob[4];
199
200
  if (version !== ARCHIVE_VERSION) {
200
- throw new SecurityError('UNSUPPORTED_FORMAT', `不支持的加密备份版本 ${version}`);
201
+ throw new SecurityError('UNSUPPORTED_FORMAT', securityMsg('security.unsupportedArchiveVersion', { version }));
201
202
  }
202
203
  const blobSalt = Buffer.from(blob.subarray(5, 5 + SALT_LENGTH));
203
204
  const blobIv = Buffer.from(blob.subarray(5 + SALT_LENGTH, 5 + SALT_LENGTH + IV_LENGTH));
@@ -207,14 +208,14 @@ export async function decryptArchive(blob, info, kdfParam, password) {
207
208
  // 与此时真实的随机参数对齐(参数来自 verify 阶段读取,防调用方换参数)
208
209
  if (!blobSalt.equals(paramSalt) ||
209
210
  !blobIv.equals(paramIv)) {
210
- throw new SecurityError('TAMPERED', '加密备份参数不一致(可能被篡改)');
211
+ throw new SecurityError('TAMPERED', securityMsg('security.archiveParamsMismatch'));
211
212
  }
212
213
  let key;
213
214
  try {
214
215
  key = await deriveKey(password, blobSalt, info.kdfParams);
215
216
  }
216
217
  catch (err) {
217
- throw new SecurityError('BAD_PASSWORD', `密钥派生失败: ${err instanceof Error ? err.message : String(err)}`);
218
+ throw new SecurityError('BAD_PASSWORD', securityMsg('security.keyDerivationFailed', { reason: err instanceof Error ? err.message : String(err) }));
218
219
  }
219
220
  const decipher = crypto.createDecipheriv('aes-256-gcm', key, blobIv);
220
221
  decipher.setAuthTag(blobTag);
@@ -225,7 +226,7 @@ export async function decryptArchive(blob, info, kdfParam, password) {
225
226
  ]);
226
227
  }
227
228
  catch {
228
- throw new SecurityError('BAD_PASSWORD', '解密认证失败:密码错误或密文被篡改');
229
+ throw new SecurityError('BAD_PASSWORD', securityMsg('security.decryptAuthFailed'));
229
230
  }
230
231
  }
231
232
  /**
@@ -13,6 +13,7 @@
13
13
  */
14
14
  import { parseJsonSafe } from '../utils/json.js';
15
15
  import { buildChecksums as coreBuildChecksums, verifyChecksums as coreVerifyChecksums, } from '../utils/hashing.js';
16
+ import { securityMsg } from './messages.js';
16
17
  export const SHA256_HEX_RE = /^[0-9a-f]{64}$/i;
17
18
  /** 与 core ZIP 条目数上限一致的默认表大小上限 */
18
19
  export const DEFAULT_MAX_CHECKSUM_ENTRIES = 10_000;
@@ -33,22 +34,22 @@ export function parseChecksumsTable(raw, opts = {}) {
33
34
  const maxEntries = opts.maxEntries ?? DEFAULT_MAX_CHECKSUM_ENTRIES;
34
35
  const parsed = parseJsonSafe(raw);
35
36
  if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {
36
- throw new Error('checksums.json 必须是 JSON 对象');
37
+ throw new Error(securityMsg('security.checksumsMustBeJsonObject'));
37
38
  }
38
39
  const entries = Object.entries(parsed);
39
40
  if (entries.length > maxEntries) {
40
- throw new Error(`checksums.json 条目数 ${entries.length} 超过上限 ${maxEntries}`);
41
+ throw new Error(securityMsg('security.checksumsTooManyEntries', { count: entries.length, max: maxEntries }));
41
42
  }
42
43
  const table = {};
43
44
  for (const [relPath, hash] of entries) {
44
45
  if (DANGEROUS_KEYS.has(relPath)) {
45
- throw new Error(`checksums.json 含危险键 "${relPath}",已拒绝`);
46
+ throw new Error(securityMsg('security.checksumsDangerousKey', { path: relPath }));
46
47
  }
47
48
  if (relPath === '' || relPath.includes('\\') || relPath.startsWith('/')) {
48
- throw new Error(`checksums.json 含非法相对路径 "${relPath}"`);
49
+ throw new Error(securityMsg('security.checksumsIllegalPath', { path: relPath }));
49
50
  }
50
51
  if (typeof hash !== 'string' || !SHA256_HEX_RE.test(hash)) {
51
- throw new Error(`checksums.json["${relPath}"] 不是合法的 SHA-256 hex`);
52
+ throw new Error(securityMsg('security.checksumsNotSha256', { path: relPath }));
52
53
  }
53
54
  table[relPath] = hash;
54
55
  }
@@ -69,8 +70,8 @@ export function verifyChecksumsJson(entries, raw, opts = {}) {
69
70
  /** 人类可读的不匹配摘要(给「Backup integrity check failed」错误消息用) */
70
71
  export function describeMismatches(result) {
71
72
  const parts = [
72
- ...result.mismatches.map((m) => `"${m}" (hash 不符)`),
73
- ...result.missing.map((m) => `"${m}" (缺失)`),
73
+ ...result.mismatches.map((m) => securityMsg('security.checksumMismatch', { path: m })),
74
+ ...result.missing.map((m) => securityMsg('security.checksumMissing', { path: m })),
74
75
  ];
75
76
  return parts.join(', ');
76
77
  }
@@ -0,0 +1,30 @@
1
+ import type { MsgParams } from '../core/msg-types.ts';
2
+ export declare const zh: {
3
+ readonly 'security.encryptionPasswordEmpty': '加密密码不能为空';
4
+ readonly 'security.secretsBlobTooSmall': 'secrets.enc 体积过小(截断或损坏)';
5
+ readonly 'security.unknownFileMagic': '未知文件格式 magic "{magic}",不是本插件产物';
6
+ readonly 'security.unsupportedSecretsVersion': '不支持的 secrets.enc 版本 {version}';
7
+ readonly 'security.invalidEncryptionParams': 'manifest 加密参数非法或不受支持';
8
+ readonly 'security.encryptionParamsMismatch': 'secrets.enc 与 manifest 加密参数不一致(可能被篡改)';
9
+ readonly 'security.keyDerivationFailed': '密钥派生失败: {reason}';
10
+ readonly 'security.decryptAuthFailed': '解密认证失败:密码错误或密文被篡改';
11
+ readonly 'security.archiveBlobTooSmall': '加密备份容器体积过小(截断或损坏)';
12
+ readonly 'security.unsupportedArchiveVersion': '不支持的加密备份版本 {version}';
13
+ readonly 'security.archiveParamsMismatch': '加密备份参数不一致(可能被篡改)';
14
+ readonly 'security.checksumsMustBeJsonObject': 'checksums.json 必须是 JSON 对象';
15
+ readonly 'security.checksumsTooManyEntries': 'checksums.json 条目数 {count} 超过上限 {max}';
16
+ readonly 'security.checksumsDangerousKey': 'checksums.json 含危险键 "{path}",已拒绝';
17
+ readonly 'security.checksumsIllegalPath': 'checksums.json 含非法相对路径 "{path}"';
18
+ readonly 'security.checksumsNotSha256': 'checksums.json["{path}"] 不是合法的 SHA-256 hex';
19
+ readonly 'security.checksumMismatch': '"{path}" (hash 不符)';
20
+ readonly 'security.checksumMissing': '"{path}" (缺失)';
21
+ readonly 'security.scanDepthExceeded': '扫描深度 {depth} 超过上限 {max}';
22
+ readonly 'security.circularReference': '检测到循环引用,无法扫描(路径 {path})';
23
+ readonly 'security.vaultUnsafePath': 'vault: 非法敏感文件路径 "{path}"';
24
+ readonly 'security.zipTargetOutOfBounds': '解压目标越界: {name}';
25
+ readonly 'security.zipExecutableEntry': '条目 "{name}" 是潜在可执行文件,本插件不会执行它';
26
+ readonly 'security.zipEntryNotRegularFile': '解压产物不是普通文件: {rel}';
27
+ };
28
+ export declare const en: Record<keyof typeof zh, string>;
29
+ /** 本域翻译器:key 受目录约束(拼错键 = 编译错误),语言按 engineMsg 的活语言解析。 */
30
+ export declare function securityMsg(key: keyof typeof zh, params?: MsgParams): string;
@@ -0,0 +1,77 @@
1
+ /**
2
+ * security 层的领域消息目录(zh 源 / en 镜像)—— 经 engineMsg 取「调用期」语言文案。
3
+ *
4
+ * 为什么是本域独立目录:src/security/** 的写作用域不含 src/core/messages.ts(既有键有冻结指纹),
5
+ * 所以本域的键用 registerMessageCatalog 在**模块加载时**注册进引擎目录,查找顺序见
6
+ * src/core/engine-locale.ts(基础目录 → 注册目录 → 键名)。
7
+ *
8
+ * 纪律(与 src/core/messages.ts 同口径):
9
+ * - zh 值 = 迁移前的既有中文文案**逐字不变**(既有测试断言依赖);
10
+ * - en 类型为 Record<keyof typeof zh, string> —— 缺键/多键即编译错误;
11
+ * - {param} 插值与缺参保留由 engineMsg 负责;
12
+ * - **凭据 / 密码值永不进消息**:这里只换文案,脱敏口径与拼接顺序逐字不变(转写前长什么样,
13
+ * 转写后还是什么样),绝不新增任何值参数。
14
+ */
15
+ import { engineMsg, registerMessageCatalog } from '../core/engine-locale.js';
16
+ export const zh = {
17
+ // ---------- 加密容器(encryption.ts) ----------
18
+ 'security.encryptionPasswordEmpty': '加密密码不能为空',
19
+ 'security.secretsBlobTooSmall': 'secrets.enc 体积过小(截断或损坏)',
20
+ 'security.unknownFileMagic': '未知文件格式 magic "{magic}",不是本插件产物',
21
+ 'security.unsupportedSecretsVersion': '不支持的 secrets.enc 版本 {version}',
22
+ 'security.invalidEncryptionParams': 'manifest 加密参数非法或不受支持',
23
+ 'security.encryptionParamsMismatch': 'secrets.enc 与 manifest 加密参数不一致(可能被篡改)',
24
+ 'security.keyDerivationFailed': '密钥派生失败: {reason}',
25
+ 'security.decryptAuthFailed': '解密认证失败:密码错误或密文被篡改',
26
+ 'security.archiveBlobTooSmall': '加密备份容器体积过小(截断或损坏)',
27
+ 'security.unsupportedArchiveVersion': '不支持的加密备份版本 {version}',
28
+ 'security.archiveParamsMismatch': '加密备份参数不一致(可能被篡改)',
29
+ // ---------- 完整性校验表(integrity.ts) ----------
30
+ 'security.checksumsMustBeJsonObject': 'checksums.json 必须是 JSON 对象',
31
+ 'security.checksumsTooManyEntries': 'checksums.json 条目数 {count} 超过上限 {max}',
32
+ 'security.checksumsDangerousKey': 'checksums.json 含危险键 "{path}",已拒绝',
33
+ 'security.checksumsIllegalPath': 'checksums.json 含非法相对路径 "{path}"',
34
+ 'security.checksumsNotSha256': 'checksums.json["{path}"] 不是合法的 SHA-256 hex',
35
+ 'security.checksumMismatch': '"{path}" (hash 不符)',
36
+ 'security.checksumMissing': '"{path}" (缺失)',
37
+ // ---------- 秘密扫描(secret-scanner.ts) ----------
38
+ 'security.scanDepthExceeded': '扫描深度 {depth} 超过上限 {max}',
39
+ 'security.circularReference': '检测到循环引用,无法扫描(路径 {path})',
40
+ // ---------- vault / ZIP 安全(vault.ts / zip-security.ts) ----------
41
+ 'security.vaultUnsafePath': 'vault: 非法敏感文件路径 "{path}"',
42
+ 'security.zipTargetOutOfBounds': '解压目标越界: {name}',
43
+ 'security.zipExecutableEntry': '条目 "{name}" 是潜在可执行文件,本插件不会执行它',
44
+ 'security.zipEntryNotRegularFile': '解压产物不是普通文件: {rel}',
45
+ };
46
+ export const en = {
47
+ 'security.encryptionPasswordEmpty': 'Encryption password must not be empty',
48
+ 'security.secretsBlobTooSmall': 'secrets.enc is too small (truncated or corrupted)',
49
+ 'security.unknownFileMagic': 'Unknown file format magic "{magic}"; not a product of this plugin',
50
+ 'security.unsupportedSecretsVersion': 'Unsupported secrets.enc version {version}',
51
+ 'security.invalidEncryptionParams': 'Invalid or unsupported manifest encryption parameters',
52
+ 'security.encryptionParamsMismatch': 'secrets.enc encryption parameters do not match the manifest (possibly tampered with)',
53
+ 'security.keyDerivationFailed': 'Key derivation failed: {reason}',
54
+ 'security.decryptAuthFailed': 'Decryption authentication failed: wrong password or tampered ciphertext',
55
+ 'security.archiveBlobTooSmall': 'Encrypted backup container is too small (truncated or corrupted)',
56
+ 'security.unsupportedArchiveVersion': 'Unsupported encrypted backup version {version}',
57
+ 'security.archiveParamsMismatch': 'Encrypted backup parameters do not match (possibly tampered with)',
58
+ 'security.checksumsMustBeJsonObject': 'checksums.json must be a JSON object',
59
+ 'security.checksumsTooManyEntries': 'checksums.json has {count} entries, exceeding the limit of {max}',
60
+ 'security.checksumsDangerousKey': 'checksums.json contains a dangerous key "{path}", rejected',
61
+ 'security.checksumsIllegalPath': 'checksums.json contains an illegal relative path "{path}"',
62
+ 'security.checksumsNotSha256': 'checksums.json["{path}"] is not a valid SHA-256 hex string',
63
+ 'security.checksumMismatch': '"{path}" (hash mismatch)',
64
+ 'security.checksumMissing': '"{path}" (missing)',
65
+ 'security.scanDepthExceeded': 'Scan depth {depth} exceeds the limit of {max}',
66
+ 'security.circularReference': 'Circular reference detected, cannot scan (path {path})',
67
+ 'security.vaultUnsafePath': 'vault: illegal sensitive file path "{path}"',
68
+ 'security.zipTargetOutOfBounds': 'Extraction target escapes the destination: {name}',
69
+ 'security.zipExecutableEntry': 'Entry "{name}" is a potential executable; this plugin will not execute it',
70
+ 'security.zipEntryNotRegularFile': 'Extracted entry is not a regular file: {rel}',
71
+ };
72
+ registerMessageCatalog({ id: 'security', zh, en });
73
+ /** 本域翻译器:key 受目录约束(拼错键 = 编译错误),语言按 engineMsg 的活语言解析。 */
74
+ export function securityMsg(key, params) {
75
+ return engineMsg(key, params);
76
+ }
77
+ //# sourceMappingURL=messages.js.map
@@ -46,6 +46,16 @@ export const REDACTION_VALUE_PATTERNS = [
46
46
  const VALUE_PATTERNS = REDACTION_VALUE_PATTERNS;
47
47
  /** URL query 中的敏感参数值(保留参数名,只替换值) */
48
48
  const URL_QUERY_RE = /([?&](?:token|api[_-]?key|key|secret|access[_-]?token|password|auth)=)([^&\s"']+)/gi;
49
+ /**
50
+ * URL **内嵌凭据**(\`scheme://user:password@host\`,GitLab/Gitea 常见的 \`https://oauth2:<token>@…\`)。
51
+ *
52
+ * 为什么必须补这一条:URL_QUERY_RE 只认 query 参数,而 userinfo 形态的凭据此前在**三条通道**
53
+ * 都不被掩码 —— 日志、界面渲染、以及 sessionStorage 落盘(run-store 的 \`redactPersistedValue\`)。
54
+ * 宿主侧 sync-config 的 validateRepoUrl/validateWebDavUrl 本来就拒绝带 userinfo 的地址,
55
+ * 所以这里只影响「掩码」:不改任何合法输入的行为,只让非法规格里的凭据不再明文可见。
56
+ * 只替换密码段(用户名保留可读),与 URL_QUERY_RE 的取舍一致。
57
+ */
58
+ const URL_USERINFO_RE = /(\/\/[^\/@\s:]+:)([^\/@\s]+)(@)/g;
49
59
  /* ---------------- 高熵长 token(journal / 迁移历史强脱敏共用) ---------------- */
50
60
  /** 高熵值形状(长 hex/base64/随机 id):结构化 redact() 覆盖不了任意 secret,journal / 迁移
51
61
  * 历史级强脱敏补挡。
@@ -110,6 +120,7 @@ export function redact(text, blacklist = []) {
110
120
  out = out.replace(re, REDACTED);
111
121
  }
112
122
  out = out.replace(URL_QUERY_RE, `$1${REDACTED}`);
123
+ out = out.replace(URL_USERINFO_RE, `$1${REDACTED}$3`);
113
124
  return out;
114
125
  }
115
126
  /** 对象级掩码:字段名命中敏感名单 → 整值替换(返回新对象,不改原对象;结构与类型保留) */
@@ -138,6 +149,7 @@ export function createRedactor(blacklist = []) {
138
149
  out = out.replace(re, REDACTED);
139
150
  }
140
151
  out = out.replace(URL_QUERY_RE, `$1${REDACTED}`);
152
+ out = out.replace(URL_USERINFO_RE, `$1${REDACTED}$3`);
141
153
  return out;
142
154
  };
143
155
  }
@@ -22,9 +22,12 @@
22
22
  * - 高熵 base64 默认关(误伤率高)。
23
23
  *
24
24
  * 实现不变量:返回 sanitized 为**全新对象**(不改原数据);迭代式显式栈(防爆栈)+ 深度上限;
25
+ * 判环只看**当前路径的祖先链**(`ancestors`)——同一对象被两处引用(DAG)不是环,用全局 visited
26
+ * 会把共享引用误判成循环引用并让整次导出失败;
25
27
  * 命中记录只含路径与字段名,**值永不外泄**。
26
28
  */
27
29
  import { JsonDepthError } from '../utils/json.js';
30
+ import { securityMsg } from './messages.js';
28
31
  /** 剥离占位(与 core defaultSecretScanner 语义一致:空串 = 需补录提示) */
29
32
  export const REDACTED_PLACEHOLDER = '';
30
33
  /**
@@ -322,14 +325,12 @@ export function scanAndRedact(data, opts = {}) {
322
325
  if (data instanceof Uint8Array)
323
326
  return { sanitized: data, hits };
324
327
  const rootOut = Array.isArray(data) ? [] : {};
325
- const stack = [{ src: data, dst: rootOut, path: '', depth: 1 }];
326
- const visited = new WeakSet();
327
- visited.add(data);
328
+ const stack = [{ src: data, dst: rootOut, path: '', depth: 1, ancestors: new Set([data]) }];
328
329
  while (stack.length > 0) {
329
330
  const frame = stack.pop();
330
- const { src, dst, path, depth } = frame;
331
+ const { src, dst, path, depth, ancestors } = frame;
331
332
  if (depth > maxDepth)
332
- throw new JsonDepthError(`扫描深度 ${depth} 超过上限 ${maxDepth}`);
333
+ throw new JsonDepthError(securityMsg('security.scanDepthExceeded', { depth, max: maxDepth }));
333
334
  if (src instanceof Uint8Array)
334
335
  continue;
335
336
  if (Array.isArray(src)) {
@@ -338,12 +339,13 @@ export function scanAndRedact(data, opts = {}) {
338
339
  const v = src[i];
339
340
  const childPath = `${path}[${i}]`;
340
341
  if (v !== null && typeof v === 'object' && !(v instanceof Uint8Array)) {
341
- if (visited.has(v))
342
- throw new Error(`检测到循环引用,无法扫描(路径 ${childPath})`);
343
- visited.add(v);
342
+ if (ancestors.has(v))
343
+ throw new Error(securityMsg('security.circularReference', { path: childPath }));
344
+ const childAncestors = new Set(ancestors);
345
+ childAncestors.add(v);
344
346
  const childOut = Array.isArray(v) ? [] : {};
345
347
  outArr.push(childOut);
346
- stack.push({ src: v, dst: childOut, path: childPath, depth: depth + 1 });
348
+ stack.push({ src: v, dst: childOut, path: childPath, depth: depth + 1, ancestors: childAncestors });
347
349
  }
348
350
  else {
349
351
  outArr.push(v);
@@ -365,12 +367,13 @@ export function scanAndRedact(data, opts = {}) {
365
367
  continue;
366
368
  }
367
369
  if (v !== null && typeof v === 'object' && !(v instanceof Uint8Array)) {
368
- if (visited.has(v))
369
- throw new Error(`检测到循环引用,无法扫描(路径 ${childPath})`);
370
- visited.add(v);
370
+ if (ancestors.has(v))
371
+ throw new Error(securityMsg('security.circularReference', { path: childPath }));
372
+ const childAncestors = new Set(ancestors);
373
+ childAncestors.add(v);
371
374
  const childOut = Array.isArray(v) ? [] : {};
372
375
  outObj[k] = childOut;
373
- stack.push({ src: v, dst: childOut, path: childPath, depth: depth + 1 });
376
+ stack.push({ src: v, dst: childOut, path: childPath, depth: depth + 1, ancestors: childAncestors });
374
377
  }
375
378
  else {
376
379
  outObj[k] = v;
@@ -22,6 +22,7 @@
22
22
  */
23
23
  import path from 'node:path';
24
24
  import { isPathSafe, normalizePath } from '../utils/paths.js';
25
+ import { securityMsg } from './messages.js';
25
26
  /**
26
27
  * 默认敏感文件清单(相对 $DSH_HOME):当前 DSH 唯一「整文件即秘密」的文件
27
28
  * (.credentials.yaml = 凭据明文 token/password/密钥)。
@@ -37,7 +38,7 @@ export function vaultRootOf(dataDir) {
37
38
  /** rel 必须为安全相对路径(拒绝绝对路径 / .. / 盘符 / NUL),防路径穿越 */
38
39
  function assertSafeRel(rel) {
39
40
  if (!isPathSafe(rel))
40
- throw new Error(`vault: 非法敏感文件路径 "${rel}"`);
41
+ throw new Error(securityMsg('security.vaultUnsafePath', { path: rel }));
41
42
  }
42
43
  /** 校验 + 归一化 rel 清单(统一 / 分隔,与 FileSystemFacade.listRecursive 产出对齐) */
43
44
  function normalizeRels(rels) {
@@ -16,6 +16,7 @@ import fs from 'node:fs/promises';
16
16
  import path from 'node:path';
17
17
  import { isSameOrChild } from '../utils/paths.js';
18
18
  import { parseZip, ZipArchive, ZipSafetyError, } from '../utils/zip.js';
19
+ import { securityMsg } from './messages.js';
19
20
  export { isPathSafe } from '../utils/paths.js';
20
21
  /** 可执行文件扩展名黑名单(§19.6:只警告,本插件不执行任何脚本) */
21
22
  export const EXECUTABLE_EXTENSIONS = new Set([
@@ -57,13 +58,13 @@ export async function safeExtractHardened(zipPath, destDir, limits = {}) {
57
58
  continue;
58
59
  const target = path.join(destDir, ...meta.name.split('/'));
59
60
  if (!isSameOrChild(target, destDir)) {
60
- throw new ZipSafetyError(`解压目标越界: ${meta.name}`);
61
+ throw new ZipSafetyError(securityMsg('security.zipTargetOutOfBounds', { name: meta.name }));
61
62
  }
62
63
  await fs.mkdir(path.dirname(target), { recursive: true });
63
64
  await fs.writeFile(target, archive.readEntry(meta.name));
64
65
  extracted.push(meta.name);
65
66
  if (isExecutableName(meta.name)) {
66
- warnings.push(`条目 "${meta.name}" 是潜在可执行文件,本插件不会执行它`);
67
+ warnings.push(securityMsg('security.zipExecutableEntry', { name: meta.name }));
67
68
  }
68
69
  }
69
70
  // 解压后复查:全部产物必须是普通文件(防 symlink 逃逸 / 非常规写入)
@@ -71,7 +72,7 @@ export async function safeExtractHardened(zipPath, destDir, limits = {}) {
71
72
  const p = path.join(destDir, ...rel.split('/'));
72
73
  const st = await fs.lstat(p);
73
74
  if (!st.isFile()) {
74
- throw new ZipSafetyError(`解压产物不是普通文件: ${rel}`);
75
+ throw new ZipSafetyError(securityMsg('security.zipEntryNotRegularFile', { rel }));
75
76
  }
76
77
  }
77
78
  return { files: extracted, warnings };
@@ -4,6 +4,7 @@
4
4
  * 纯逻辑 + 可注入 fs;不触碰真实 ~/.dsh。
5
5
  */
6
6
  import { createSnapshotFs, joinFs } from './fs.js';
7
+ import { engineMsg } from './messages.js';
7
8
  import { readSnapshotFromDir, writeSnapshotToDir } from './layout.js';
8
9
  import { SNAPSHOT_MANIFEST_FILE } from './layout.js';
9
10
  /** 默认保留的祖先快照数量(最旧超出则被裁剪)。 */
@@ -11,7 +12,7 @@ export const DEFAULT_ANCESTOR_KEEP = 10;
11
12
  /** 按 snapshotId 读取祖先快照目录;不存在抛错,不静默降级。 */
12
13
  export async function loadAncestor(localSnapshotsDir, snapshotId, fsx = createSnapshotFs()) {
13
14
  if (typeof snapshotId !== 'string' || snapshotId === '') {
14
- throw new Error('loadAncestor: snapshotId 不能为空');
15
+ throw new Error(engineMsg('sync.ancestorSnapshotIdRequired'));
15
16
  }
16
17
  const dir = joinFs(localSnapshotsDir, snapshotId);
17
18
  return await readSnapshotFromDir(dir, fsx);
@@ -24,6 +24,17 @@ export interface AutosyncConfig {
24
24
  /** 最近一次自动同步执行状态 */
25
25
  lastRunStatus?: AutosyncRunStatus;
26
26
  lastRunMessage?: string;
27
+ /**
28
+ * 最近一次**跳过**的结构化原因码(issue #79)。
29
+ *
30
+ * 为什么是码而不是文案:语言在「取文案那一刻」解析(AGENTS.md i18n 铁律),状态行由界面
31
+ * 按码取自己的字典;存一句当时语言的中文,用户切到英文界面后会看到中英混排。
32
+ * 已知值见 `svc.autosyncSkip.*` 的字典键('conflict'/'mutation-locked'/'upToDate'…),
33
+ * **未知码一律渲染兜底文案,绝不渲染裸枚举**。
34
+ */
35
+ lastRunSkipReason?: string;
36
+ /** 最近一次跳过涉及的分区数(当前仅 conflict:远端另有 N 个分区待确认) */
37
+ lastRunSkipCount?: number;
27
38
  /** 最近一次自动同步触发的同步历史条目 id */
28
39
  lastRunHistoryId?: string;
29
40
  }
@@ -54,6 +54,13 @@ function parseV1Channel(obj) {
54
54
  }
55
55
  if (typeof obj['lastRunMessage'] === 'string')
56
56
  cfg.lastRunMessage = obj['lastRunMessage'];
57
+ // issue #79:状态行的结构化跳过原因(旧文件没有这两个字段 → undefined,行为不变)
58
+ if (typeof obj['lastRunSkipReason'] === 'string' && obj['lastRunSkipReason'] !== '') {
59
+ cfg.lastRunSkipReason = obj['lastRunSkipReason'];
60
+ }
61
+ if (typeof obj['lastRunSkipCount'] === 'number' && Number.isFinite(obj['lastRunSkipCount']) && obj['lastRunSkipCount'] >= 0) {
62
+ cfg.lastRunSkipCount = obj['lastRunSkipCount'];
63
+ }
57
64
  if (typeof obj['lastRunHistoryId'] === 'string')
58
65
  cfg.lastRunHistoryId = obj['lastRunHistoryId'];
59
66
  return cfg;
@@ -132,6 +139,11 @@ function toChannelPayload(cfg) {
132
139
  payload['lastRunStatus'] = cfg.lastRunStatus;
133
140
  if (cfg.lastRunMessage !== undefined && cfg.lastRunMessage !== '')
134
141
  payload['lastRunMessage'] = cfg.lastRunMessage;
142
+ // 空串/undefined 一律不写 → 本轮成功时,上一轮的跳过码/旧消息在文件里被真正清掉(issue #79)
143
+ if (cfg.lastRunSkipReason !== undefined && cfg.lastRunSkipReason !== '')
144
+ payload['lastRunSkipReason'] = cfg.lastRunSkipReason;
145
+ if (cfg.lastRunSkipCount !== undefined)
146
+ payload['lastRunSkipCount'] = cfg.lastRunSkipCount;
135
147
  if (cfg.lastRunHistoryId !== undefined && cfg.lastRunHistoryId !== '')
136
148
  payload['lastRunHistoryId'] = cfg.lastRunHistoryId;
137
149
  return payload;
@@ -134,7 +134,17 @@ export declare class AutoSyncScheduler {
134
134
  runOnce(channel: SyncTransportType, opts?: {
135
135
  startup?: boolean;
136
136
  }): Promise<AutosyncRunResult>;
137
- /** 收尾:写该通道 autosync-config(lastRunAt, lastRunStatus, consecutiveFailures, lastRunHistoryId)。 */
137
+ /**
138
+ * 收尾:写该通道 autosync-config(lastRunAt / lastRunStatus / consecutiveFailures /
139
+ * lastRunHistoryId / 本轮消息与跳过原因)。
140
+ *
141
+ * issue #79:**每一轮都必须覆盖上一轮的 lastRunMessage 与 lastRunSkipReason**。
142
+ * 此前只在 `result.error !== undefined` 时写 lastRunMessage → 真机现场连续 16 次 skipped 期间,
143
+ * 面板一直挂着几天前「旧地址连接超时」,用户据此去查网络;跳过原因则只进了 info 级日志
144
+ * (默认日志级别 warn),所以「为什么没同步」在界面与日志里都看不见。
145
+ * 现在:失败写错误原文;跳过写**结构化原因码**(界面按码取字典,语言在渲染那刻解析);
146
+ * 成功清空两者(空串/undefined 不进文件,见 autosync-config.toChannelPayload)。
147
+ */
138
148
  private writeFinalConfig;
139
149
  /** 连续失败 ≥ 3 → 通知(host.log.warn)。 */
140
150
  private maybeNotify;