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
@@ -20,6 +20,7 @@
20
20
  * - config_sync_pull 零写入(只 analyze+plan 出差异);落地需另走确认导入管道;
21
21
  * - 分区/路径走既有白名单(SECTION_IDS 过滤;同步引擎 portable + FORBIDDEN_SECTIONS 双保险)。
22
22
  */
23
+ import { engineMsg } from './messages-core.ts';
23
24
  import type { Context } from '@deepseek-ai/cordis'
24
25
  import { defineTool } from '@deepseek-ai/dsh-tools'
25
26
  import { randomBytes } from 'node:crypto'
@@ -68,7 +69,7 @@ export interface ModelToolsDeps {
68
69
  /** 快照 id 校验:拒绝路径分隔符与 `.`/`..`(防 join(snapshotsDir, id) 越界)。 */
69
70
  function assertValidSnapshotId(snapshotId: string): void {
70
71
  if (snapshotId === '' || snapshotId.includes('/') || snapshotId.includes('\\')) {
71
- throw new Error(`非法快照 id:${JSON.stringify(snapshotId)}`)
72
+ throw new Error(engineMsg('core.modelTools.illegalSnapshotId', { id: JSON.stringify(snapshotId) }))
72
73
  }
73
74
  }
74
75
 
@@ -99,7 +100,7 @@ async function resolveEngine(
99
100
  const ch: SyncTransportType = channel ?? parseSyncChannel(full?.transport) ?? 'git'
100
101
  const cfg = await readSyncConfigFor(deps.syncDir, ch)
101
102
  if (cfg === null) {
102
- throw new Error(`同步通道 ${ch} 尚未配置(sync-config.json 缺失或损坏)`)
103
+ throw new Error(engineMsg('core.modelTools.syncChannelNotConfigured', { channel: ch }))
103
104
  }
104
105
  return { engine: deps.makeSyncEngine(cfg), channel: ch }
105
106
  }
@@ -171,7 +172,7 @@ export function createModelTools(deps: ModelToolsDeps) {
171
172
  const { snapshotId, confirm } = input
172
173
  assertValidSnapshotId(snapshotId)
173
174
  if (!(await snapshotDirExists(deps, snapshotId))) {
174
- throw new Error(`快照不存在:${snapshotId}(用 config_list_snapshots 查看可用快照)`)
175
+ throw new Error(engineMsg('core.modelTools.snapshotNotFound', { id: snapshotId }))
175
176
  }
176
177
  const snapshotDir = join(deps.snapshotsDir, snapshotId)
177
178
  // profile:host.profile 由宿主 resolveProfileName 保证非空(缺省 'web',见 ConfigManagerHostContext),
@@ -315,7 +316,7 @@ type ModelTools = ReturnType<typeof createModelTools>
315
316
  export function registerModelTools(ctx: Context, deps: ModelToolsDeps): void {
316
317
  const toolsSvc = ctx.get('tools')
317
318
  if (toolsSvc === null || toolsSvc === undefined || typeof toolsSvc !== 'object') {
318
- deps.host.log.warn?.('tools 服务不可用:跳过模型工具注册(引擎能力仍可用)')
319
+ deps.host.log.warn?.(engineMsg('core.modelTools.toolsServiceUnavailable'))
319
320
  return
320
321
  }
321
322
  const tools: ModelTools = createModelTools(deps)
@@ -329,22 +330,22 @@ export function registerModelTools(ctx: Context, deps: ModelToolsDeps): void {
329
330
 
330
331
  register(defineTool({
331
332
  name: 'config_backup',
332
- description: '全量备份 DSH 配置到本地 exports 目录(默认不含 secret;可传 password 加密导出)。返回 ZIP 文件名/大小/分区清单/加密状态。',
333
+ description: engineMsg('core.modelTools.backupToolDesc'),
333
334
  parameters: {
334
335
  only: {
335
336
  type: 'array',
336
337
  items: { type: 'string', enum: [...SECTION_IDS] },
337
- description: '仅导出的分区 id 白名单;缺省 = 全部推荐分区',
338
+ description: engineMsg('core.modelTools.backupOnlyParam'),
338
339
  },
339
340
  password: {
340
341
  type: 'string',
341
- description: '可选:加密备份密码(仅内存,不落盘/不回显;提供即加密导出)',
342
+ description: engineMsg('core.modelTools.backupPasswordParam'),
342
343
  },
343
344
  },
344
345
  output: {
345
346
  schema: {
346
347
  type: 'json',
347
- description: '备份结果(zip 文件名 / sizeBytes / 分区清单 / excluded / encrypted / containsSecrets / redactedHits / warnings)',
348
+ description: engineMsg('core.modelTools.backupOutputDesc'),
348
349
  },
349
350
  render: (_args, value) => [{ type: 'text', text: JSON.stringify(value, null, 2) }],
350
351
  },
@@ -355,12 +356,12 @@ export function registerModelTools(ctx: Context, deps: ModelToolsDeps): void {
355
356
 
356
357
  register(defineTool({
357
358
  name: 'config_list_snapshots',
358
- description: '列出 DSH 配置的本地回滚快照(id / 创建时间 / 来源 / 状态 / 条目数),供后续 config_restore 使用。',
359
+ description: engineMsg('core.modelTools.listSnapshotsToolDesc'),
359
360
  parameters: {},
360
361
  output: {
361
362
  schema: {
362
363
  type: 'json',
363
- description: '快照数组(id / createdAt / sourceZip / status / entryCount)',
364
+ description: engineMsg('core.modelTools.listSnapshotsOutputDesc'),
364
365
  },
365
366
  render: (_args, value) => [{ type: 'text', text: JSON.stringify(value, null, 2) }],
366
367
  },
@@ -372,20 +373,20 @@ export function registerModelTools(ctx: Context, deps: ModelToolsDeps): void {
372
373
  register(defineTool({
373
374
  name: 'config_restore',
374
375
  description:
375
- '恢复到指定快照。默认只返回动作计划(零写入);确认要真实执行时传 confirm:true(会覆盖/删除 $DSH_HOME 文件并卸载导入期间新增插件,属破坏性操作,务必先预览)。',
376
+ engineMsg('core.modelTools.restoreToolDesc'),
376
377
  parameters: {
377
378
  snapshotId: {
378
379
  type: 'string',
379
380
  required: true,
380
- description: '要恢复的快照 id(用 config_list_snapshots 获取)',
381
+ description: engineMsg('core.modelTools.restoreSnapshotIdParam'),
381
382
  },
382
383
  confirm: {
383
384
  type: 'boolean',
384
- description: 'true 才真实执行恢复;缺省 false 只预览计划(零写入)',
385
+ description: engineMsg('core.modelTools.restoreConfirmParam'),
385
386
  },
386
387
  },
387
388
  output: {
388
- schema: { type: 'json', description: 'dryRun=true 时为恢复计划;dryRun=false 时为执行报告' },
389
+ schema: { type: 'json', description: engineMsg('core.modelTools.restoreOutputDesc') },
389
390
  render: (_args, value) => [{ type: 'text', text: JSON.stringify(value, null, 2) }],
390
391
  },
391
392
  async execute(args) {
@@ -396,33 +397,33 @@ export function registerModelTools(ctx: Context, deps: ModelToolsDeps): void {
396
397
  register(defineTool({
397
398
  name: 'config_sync_push',
398
399
  description:
399
- '手动推送 DSH 配置同步到远端(Git/WebDAV),复用已持久化的通道配置。写远端属主动操作;加密/含凭据须传 password 且引擎强制 encrypt。',
400
+ engineMsg('core.modelTools.syncPushToolDesc'),
400
401
  parameters: {
401
402
  channel: {
402
403
  type: 'string',
403
404
  enum: [...SYNC_CHANNELS],
404
- description: '同步通道;缺省 = 已配置的活动通道',
405
+ description: engineMsg('core.modelTools.channelParam'),
405
406
  },
406
407
  sections: {
407
408
  type: 'array',
408
409
  items: { type: 'string', enum: [...SECTION_IDS] },
409
- description: '仅推送的分区;缺省 = 全部推荐 portable 分区(非 portable 自动跳过)',
410
+ description: engineMsg('core.modelTools.syncPushSectionsParam'),
410
411
  },
411
412
  encrypt: {
412
413
  type: 'boolean',
413
- description: '加密快照;开启必须提供 password',
414
+ description: engineMsg('core.modelTools.syncPushEncryptParam'),
414
415
  },
415
416
  includeSecrets: {
416
417
  type: 'boolean',
417
- description: '推送真实凭据值(必须同时 encrypt=true,否则引擎拒绝)',
418
+ description: engineMsg('core.modelTools.syncPushIncludeSecretsParam'),
418
419
  },
419
420
  password: {
420
421
  type: 'string',
421
- description: '加密密码(仅内存,不落盘/不回显)',
422
+ description: engineMsg('core.modelTools.syncPushPasswordParam'),
422
423
  },
423
424
  },
424
425
  output: {
425
- schema: { type: 'json', description: '推送报告(snapshotId / 实际同步分区 / 告警)' },
426
+ schema: { type: 'json', description: engineMsg('core.modelTools.syncPushOutputDesc') },
426
427
  render: (_args, value) => [{ type: 'text', text: JSON.stringify(value, null, 2) }],
427
428
  },
428
429
  async execute(args) {
@@ -433,29 +434,29 @@ export function registerModelTools(ctx: Context, deps: ModelToolsDeps): void {
433
434
  register(defineTool({
434
435
  name: 'config_sync_pull',
435
436
  description:
436
- '拉取远端同步差异预览(Git/WebDAV,零写入:只下载 + 分析出差异报告,绝不直接写配置)。若要落地差异需另走确认导入管道。',
437
+ engineMsg('core.modelTools.syncPullToolDesc'),
437
438
  parameters: {
438
439
  channel: {
439
440
  type: 'string',
440
441
  enum: [...SYNC_CHANNELS],
441
- description: '同步通道;缺省 = 已配置的活动通道',
442
+ description: engineMsg('core.modelTools.channelParam'),
442
443
  },
443
444
  snapshotId: {
444
445
  type: 'string',
445
- description: '远端快照 id;缺省 = 最新',
446
+ description: engineMsg('core.modelTools.syncPullSnapshotIdParam'),
446
447
  },
447
448
  strategy: {
448
449
  type: 'string',
449
450
  enum: ['merge', 'replace', 'skipExisting'],
450
- description: '冲突全局策略;缺省 merge(冲突保留待决策)',
451
+ description: engineMsg('core.modelTools.syncPullStrategyParam'),
451
452
  },
452
453
  password: {
453
454
  type: 'string',
454
- description: '解密密码(加密快照需要;仅内存)',
455
+ description: engineMsg('core.modelTools.syncPullPasswordParam'),
455
456
  },
456
457
  },
457
458
  output: {
458
- schema: { type: 'json', description: '差异报告(changes 列表 / needsReview)' },
459
+ schema: { type: 'json', description: engineMsg('core.modelTools.syncPullOutputDesc') },
459
460
  render: (_args, value) => [{ type: 'text', text: JSON.stringify(value, null, 2) }],
460
461
  },
461
462
  async execute(args) {
@@ -22,11 +22,12 @@ import { inspectStartup, type ReconcileProbeHooks, type ReconcileEnv } from './r
22
22
  import { OWNERSHIP_FILE, type LockState, type MutationLockContext } from '../utils/env-lock.ts';
23
23
  import { sha256Hex } from '../utils/hashing.ts';
24
24
  import type { JournalStepRecord } from './types.ts';
25
+ import { engineMsg } from './messages-core.ts';
25
26
 
26
27
  /** 存在未收敛的 active transaction → 拒绝创建第二个(active≤1)。 */
27
28
  export class TransactionRecoveryRequiredError extends Error {
28
29
  constructor(opId: string) {
29
- super(`active transaction ${opId} 未收敛(待 recover / reconcile),拒绝创建新 transaction`)
30
+ super(engineMsg('core.phase3.transactionNotConverged', { opId }))
30
31
  this.name = 'TransactionRecoveryRequiredError'
31
32
  }
32
33
  }
@@ -75,6 +76,9 @@ export interface JournalRunContext {
75
76
  export function mapLockStateForStartup(state: LockState): 'LOCKED' | 'STALE_LOCK_DETECTED' | 'UNKNOWN_STATE' | 'FREE' {
76
77
  if (state === 'STALE_LOCK_DETECTED') return 'STALE_LOCK_DETECTED';
77
78
  if (state === 'UNKNOWN_STATE') return 'UNKNOWN_STATE';
79
+ // issue #79:FREE(ownership 确实不存在)如实透传。此前它落到下面的兜底被映射成 LOCKED ——
80
+ // 与 inspectLockState 的 missing→LOCKED 一起,把「没有锁」的系统性误报成「有活跃锁」。
81
+ if (state === 'FREE') return 'FREE';
78
82
  return 'LOCKED'; // ACQUIRED / LOCKED / LOCK_IO_ERROR / PERMISSION_ERROR → 保守 LOCKED
79
83
  }
80
84
 
@@ -209,7 +213,19 @@ export class Phase3Recovery {
209
213
  if (step.beforeFp !== null && fp === step.beforeFp) return 'before-match';
210
214
  return 'none';
211
215
  },
212
- probeExternal: async () => 'unknown',
216
+ // issue #87:external step 的**真实只读探测**(仅在 journal 里确实记录了指纹时才可能得出结论)。
217
+ // 语义仍是「证不出来就说 unknown」:ref 为空 / 无指纹 / 读失败 / 与两个指纹都不符 → unknown。
218
+ // 注意:runExternalIntent 目前写入的 external step 恒无指纹(beforeFp/afterFp 皆 null),
219
+ // 对它们本探测必然返回 unknown —— 那是刻意的,放行判断在 reconcile 的「可自证残留」门(独立判据)。
220
+ probeExternal: async (step) => {
221
+ if (step.ref === '' || step.beforeFp === null || step.afterFp === null) return 'unknown'
222
+ let data: Buffer
223
+ try { data = await fs.readFile(step.ref) } catch { return 'unknown' }
224
+ const fp = sha256Hex(data)
225
+ if (fp === step.afterFp) return 'installed'
226
+ if (fp === step.beforeFp) return 'not-installed'
227
+ return 'unknown'
228
+ },
213
229
  snapshotExists: async (snapshotId, binding) => {
214
230
  if (snapshotExists === undefined) return false; // 未注入 → 保守 false
215
231
  return snapshotExists(snapshotId, binding);
@@ -330,7 +346,7 @@ export class Phase3Recovery {
330
346
  const snapId = await snapshotProvider();
331
347
  // P1-2 修复:snapshotProvider 返回 null 表示快照创建失败 → 显式 abort(不得在无快照下继续 mutation)
332
348
  if (snapId === null) {
333
- throw new Error(`snapshotProvider 返回 null(快照创建失败),abort operation ${operationType}`);
349
+ throw new Error(engineMsg('core.phase3.snapshotProviderNull', { operationType }));
334
350
  }
335
351
  await journalCtx.bindSnapshot(snapId);
336
352
  } else if (deferredSnapshot !== true) {
@@ -446,7 +462,7 @@ export class Phase3Recovery {
446
462
  environmentFingerprint: this.environmentFingerprint || 'unknown',
447
463
  ownerInstanceId,
448
464
  bindSnapshot: async (snapshotId: string) => {
449
- if (snapshotId === null || snapshotId === '') throw new Error('bindSnapshot: snapshotId 为空');
465
+ if (snapshotId === null || snapshotId === '') throw new Error(engineMsg('core.phase3.bindSnapshotEmptyId'));
450
466
  // Reviewer A P2①:持久化失败必须传播(fail-closed)——若 journal 无法 durable 绑定 SNAPSHOT_CREATED,
451
467
  // 引擎的 await bindSnapshot 抛错 → fn 抛错 → runJournaled catch(NEEDS_ATTENTION),mutation 绝不在无绑定下继续。
452
468
  await this.store.update(opId, (j) => {
@@ -454,7 +470,7 @@ export class Phase3Recovery {
454
470
  if (next.state === 'CREATED') next = transitionJournalState(next, 'SNAPSHOT_CREATED');
455
471
  else if (next.state === 'SNAPSHOT_CREATED') { /* 幂等:已绑定 */ }
456
472
  else if (next.state === 'APPLYING') { /* 允许:绑定已晚但幂等记录 */ }
457
- else throw new Error(`bindSnapshot 非法 state: ${next.state}`);
473
+ else throw new Error(engineMsg('core.phase3.bindSnapshotIllegalState', { state: next.state }));
458
474
  return next;
459
475
  });
460
476
  },
@@ -68,6 +68,28 @@ test('verifyStepFingerprint:本地文件 step 真实磁盘指纹判定', async
68
68
  assert.equal(await hooks.verifyStepFingerprint({ adapter: 'plugins', ref: 'plugin:@x', kind: 'Install', external: true, beforeFp: null, afterFp: null, status: 'planned', appliedAt: null }), 'unable');
69
69
  });
70
70
 
71
+ test('issue #87 probeExternal:有指纹时做真实只读探测;证不出来一律 unknown(默认保守语义不变)', async (t) => {
72
+ const dir = tmp(t);
73
+ const recovery = await makeRecovery(dir);
74
+ const target = path.join(dir, 'artifact.bin');
75
+ const fpA = sha256Hex(Buffer.from('A'));
76
+ const fpB = sha256Hex(Buffer.from('B'));
77
+ const step = (beforeFp: string | null, afterFp: string | null, ref = target): import('./journal.ts').JournalStep => ({
78
+ adapter: 'plugins', ref, kind: 'Install', external: true, beforeFp, afterFp, status: 'planned', appliedAt: null,
79
+ });
80
+ const hooks = recovery.recoveryHooks;
81
+
82
+ fssync.writeFileSync(target, 'A');
83
+ assert.equal(await hooks.probeExternal(step(fpA, fpB)), 'not-installed', '磁盘仍等于 beforeFp → 可自证未装');
84
+ fssync.writeFileSync(target, 'B');
85
+ assert.equal(await hooks.probeExternal(step(fpA, fpB)), 'installed', '磁盘等于 afterFp → 已装');
86
+ fssync.writeFileSync(target, 'C');
87
+ assert.equal(await hooks.probeExternal(step(fpA, fpB)), 'unknown', '与两个指纹都不符 → unknown(绝不猜)');
88
+ assert.equal(await hooks.probeExternal(step(null, null)), 'unknown', '无指纹(runExternalIntent 的真实形态)→ unknown');
89
+ assert.equal(await hooks.probeExternal(step(fpA, fpB, '')), 'unknown', 'ref 为空 → unknown');
90
+ assert.equal(await hooks.probeExternal(step(fpA, fpB, path.join(dir, 'missing.bin'))), 'unknown', '读失败 → unknown');
91
+ });
92
+
71
93
  // ---------- 2. recordStep 持久化 ----------
72
94
 
73
95
  test('runJournaled journalCtx.recordStep:文件/外部 step 落 journal', async (t) => {
@@ -15,6 +15,7 @@
15
15
  * 只依赖 node 内置模块,零 DSH 运行时依赖(M5 的 TDD 可直接单测本模块)。
16
16
  */
17
17
 
18
+ import { engineMsg } from './messages-core.ts';
18
19
  import { spawn } from 'node:child_process'
19
20
  import type { ChildProcess, SpawnOptions } from 'node:child_process'
20
21
  import { readFileSync, rmSync } from 'node:fs'
@@ -609,35 +610,35 @@ export function classifyDshPluginFailure(output: string): DshPluginFailure | nul
609
610
  return {
610
611
  code: 'hoist-pattern-diff',
611
612
  recoverable: true,
612
- message: 'profile 的 node_modules 由旧版 pnpm 创建,与当前 pnpm 的默认配置不兼容;在 profile 目录执行一次 pnpm install 重建后重试 / this profile\'s node_modules was created by a different pnpm major; rebuild it (pnpm install) before retrying',
613
+ message: engineMsg('core.pluginCli.hoistPatternDiff'),
613
614
  }
614
615
  }
615
616
  if (output.includes('ERR_PNPM_ADDING_TO_ROOT')) {
616
617
  return {
617
618
  code: 'adding-to-root',
618
619
  recoverable: true,
619
- message: 'profile 是 pnpm workspace,当前 dsh CLI 转发未带 -w 被 pnpm 拒绝;可手动执行 dsh plugin --profile <name> add -w <pkg> / pnpm refused to add at a workspace root (missing -w); run dsh plugin --profile <name> add -w <pkg> by hand',
620
+ message: engineMsg('core.pluginCli.addingToRoot'),
620
621
  }
621
622
  }
622
623
  if (/--workspace-root may only be used inside a workspace/i.test(output)) {
623
624
  return {
624
625
  code: 'not-a-workspace',
625
626
  recoverable: false,
626
- message: 'profile 目录不是 pnpm workspace,却传入了 -w,因此被 pnpm 拒绝 / -w was passed but the profile is not a pnpm workspace, so pnpm refused the operation',
627
+ message: engineMsg('core.pluginCli.notAWorkspace'),
627
628
  }
628
629
  }
629
630
  if (output.includes('ERR_PNPM_MINIMUM_RELEASE_AGE_VIOLATION') || output.includes('ERR_PNPM_NO_MATURE_MATCHING_VERSION')) {
630
631
  return {
631
632
  code: 'release-age-violation',
632
633
  recoverable: true,
633
- message: 'profile 里有刚发布不久的插件版本,pnpm 的安全等待期检查拒绝了本次改动(即使改的是别的插件);等待发布期满,或临时以 --config.minimumReleaseAge=0 重试 / a recently-published plugin version in this profile trips pnpm\'s fresh-release safety check, blocking any change (even to other plugins); wait for the release to mature, or retry once with --config.minimumReleaseAge=0',
634
+ message: engineMsg('core.pluginCli.releaseAgeViolation'),
634
635
  }
635
636
  }
636
637
  if (output.includes('ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED') || /git-hosted package "[^"]+" needs to execute build scripts/i.test(output)) {
637
638
  return {
638
639
  code: 'git-build-blocked',
639
640
  recoverable: false,
640
- message: '该 git 插件需要在安装时执行构建脚本,被 pnpm 的 allowBuilds 白名单拦截;把 pnpm 提示的包名加入 profile 的 pnpm-workspace.yaml 的 allowBuilds 后重试 / this git-hosted plugin needs to run its build script at install time, which pnpm blocks until allowed — add the exact package pnpm printed to allowBuilds in the profile\'s pnpm-workspace.yaml, then re-run',
641
+ message: engineMsg('core.pluginCli.gitBuildBlocked'),
641
642
  }
642
643
  }
643
644
  // issue #35:pnpm-workspace.yaml 的 patchedDependencies 指向不存在的 patch 文件时,
@@ -647,29 +648,27 @@ export function classifyDshPluginFailure(output: string): DshPluginFailure | nul
647
648
  // 路径可能含盘符冒号(C:\...),故按整行取、再切掉尾随的 ':' 或 ': <错误文本>'
648
649
  const raw = /Failed to read patch file\s+([^\r\n]+)/i.exec(output)?.[1]?.trim()
649
650
  const file = raw === undefined ? undefined : raw.split(/:\s/)[0]?.replace(/:\s*$/, '').trim()
650
- const zh = file === undefined ? '' : `(${file})`
651
- const en = file === undefined ? '' : ` (${file})`
651
+ const patchSuffix = file === undefined ? '' : engineMsg('core.pluginCli.parenValue', { value: file })
652
652
  return {
653
653
  code: 'patch-file-missing',
654
654
  recoverable: false,
655
- message: `profile 的 pnpm-workspace.yaml 声明了 patchedDependencies,但对应的 patch 文件不存在${zh};pnpm 因此拒绝任何安装(含与补丁无关的插件)。把缺失的 patch 文件放回 profile 的 patches/ 目录,或从 pnpm-workspace.yaml 删除该条目后重试 / the profile's pnpm-workspace.yaml declares patchedDependencies but the patch file is missing${en}; pnpm then refuses every install (even unrelated plugins). Put the patch file back under the profile's patches/ directory, or remove that entry from pnpm-workspace.yaml and retry`,
655
+ message: engineMsg('core.pluginCli.patchFileMissing', { patch: patchSuffix }),
656
656
  }
657
657
  }
658
658
  if (output.includes('ERR_PNPM_FETCH_404')) {
659
659
  const pkg = /GET\s+\S*\/([^/\s]+):/.exec(output)?.[1]?.replace(/%2[Ff]/g, '/')
660
- const zh = pkg === undefined ? '' : `(${pkg})`
661
- const en = pkg === undefined ? '' : ` (${pkg})`
660
+ const pkgSuffix = pkg === undefined ? '' : engineMsg('core.pluginCli.parenValue', { value: pkg })
662
661
  return {
663
662
  code: 'fetch-404',
664
663
  recoverable: false,
665
- message: `有一个依赖在 registry 上不存在${zh},pnpm 拒绝任何安装操作;它可能是之前失败操作残留在 profile package.json 里的幽灵依赖(手动删除该行),也可能是需要登录的私有包 / a dependency cannot be resolved from the registry${en}; pnpm refuses every install while it is present — it may be a ghost entry left in the profile's package.json by an earlier failed operation (remove that line by hand), or a private package needing registry credentials`,
664
+ message: engineMsg('core.pluginCli.fetch404', { pkg: pkgSuffix }),
666
665
  }
667
666
  }
668
667
  if (isTransientDshPluginFailure(output)) {
669
668
  return {
670
669
  code: 'transient-network',
671
670
  recoverable: true,
672
- message: '拉取依赖时网络临时失败(安装会重放整个依赖树,任何一个既有依赖抖动都会中断本次操作);稍后直接重试通常即可 / a transient network failure while fetching dependencies (installs replay the whole dependency tree, so any existing dependency can hiccup); a plain retry usually succeeds',
671
+ message: engineMsg('core.pluginCli.transientNetwork'),
673
672
  }
674
673
  }
675
674
  // 桌面端保留档案:普通 dsh CLI(< 0.2 的旧版尤其)对 `--profile desktop` 直接报
@@ -680,14 +679,14 @@ export function classifyDshPluginFailure(output: string): DshPluginFailure | nul
680
679
  return {
681
680
  code: 'desktop-profile-reserved',
682
681
  recoverable: false,
683
- message: 'desktop 是 DeepSeek Harness Desktop 独占管理的保留档案,普通 dsh CLI 拒绝操作它;请在桌面端应用内重试(新版本插件会自动改用桌面端自带的 CLI 载体),或改用其它档案 / the "desktop" profile is managed exclusively by the DeepSeek Harness Desktop application and the plain dsh CLI refuses to touch it; retry from inside the Desktop app (a current version of this plugin switches to the Desktop-bundled CLI carrier) or target another profile',
682
+ message: engineMsg('core.pluginCli.desktopProfileReserved'),
684
683
  }
685
684
  }
686
685
  if (output.includes('pnpm not found on PATH')) {
687
686
  return {
688
687
  code: 'pnpm-missing',
689
688
  recoverable: false,
690
- message: '找不到 pnpm(dsh 转发 pnpm 失败):请先安装 pnpm(npm install -g pnpm 或 corepack enable pnpm),然后重试 / pnpm is not on PATH (dsh forwards to pnpm): install pnpm (npm install -g pnpm or corepack enable pnpm) and retry',
689
+ message: engineMsg('core.pluginCli.pnpmMissing'),
691
690
  }
692
691
  }
693
692
  return null
@@ -696,18 +695,18 @@ export function classifyDshPluginFailure(output: string): DshPluginFailure | nul
696
695
  /** 安装失败 → 分类后的双语可读 Error;未识别则保留 stderr 尾部摘要。 */
697
696
  export function installErrorFor(pkg: string, result: DshPluginResult): Error {
698
697
  if (result.spawnError !== undefined && /ENOENT|EINVAL/.test(result.spawnError)) {
699
- return new Error(`无法启动 dsh CLI(${result.spawnError}):请确认 dsh 已安装且在 PATH 上,然后重试安装 ${pkg} / cannot start dsh CLI (${result.spawnError}): make sure dsh is installed and on PATH, then retry installing ${pkg}`)
698
+ return new Error(engineMsg('core.pluginCli.dshCliStartFailed', { spawnError: result.spawnError, pkg }))
700
699
  }
701
700
  if (result.timedOut) {
702
- return new Error(`安装 ${pkg} 超时(进程已终止,默认 15 分钟):请检查网络或稍后重试 / install ${pkg} timed out (process killed, default 15 min): check your network and retry later`)
701
+ return new Error(engineMsg('core.pluginCli.installTimeout', { pkg }))
703
702
  }
704
703
  const output = `${result.stderr}\n${result.stdout}`
705
704
  const failure = classifyDshPluginFailure(output)
706
705
  if (failure !== null) {
707
- return new Error(`安装 ${pkg} 失败(${failure.code}):${failure.message}`)
706
+ return new Error(engineMsg('core.pluginCli.installFailed', { pkg, code: failure.code, message: failure.message }))
708
707
  }
709
708
  const tail = (result.stderr.trim().split('\n').slice(-8).join('\n')
710
709
  || result.stdout.trim().split('\n').slice(-8).join('\n')
711
- || '无输出')
712
- return new Error(`dsh plugin 安装 ${pkg} 失败(exit ${String(result.exitCode)}):${tail}`)
710
+ || engineMsg('core.pluginCli.noOutput'))
711
+ return new Error(engineMsg('core.pluginCli.installFailedWithTail', { pkg, exitCode: String(result.exitCode), tail }))
713
712
  }
@@ -0,0 +1,113 @@
1
+ /**
2
+ * pluginData / pluginAssets 来源注册表的纯函数单测(零 I/O)。
3
+ *
4
+ * 这里钉的是「收录口径」的四条硬性质:
5
+ * ① 区域归属(角色 → 分区)与内置来源的取证标签;
6
+ * ② 最长前缀归属 —— 大资产不能被短前缀的配置区域吞掉;
7
+ * ③ 排除与安全(cache 不进包;非法 home 相对路径一律拒绝);
8
+ * ④ 自述声明(dsh.dataDirs)的形状解析:不认识就整条丢弃,绝不猜。
9
+ */
10
+ import test from 'node:test';
11
+ import assert from 'node:assert/strict';
12
+
13
+ import {
14
+ BUILTIN_PLUGIN_DATA_SOURCES, areaOfPath, areasForSection, isExcludedRel, normalizeHomeRel,
15
+ parseDeclaredDataDirs, relInArea, sectionOfRole,
16
+ } from './plugin-data-registry.ts';
17
+ import type { PluginDataArea, PluginDataSource } from './plugin-data-registry.ts';
18
+
19
+ /** 区域条目(与 adapter 的 PluginDataEntry 同形;core 测试不反向依赖 adapters 层) */
20
+ type AreaEntry = { source: PluginDataSource; area: PluginDataArea };
21
+
22
+ const ALL: AreaEntry[] = [];
23
+ for (const source of BUILTIN_PLUGIN_DATA_SOURCES) {
24
+ for (const area of source.areas) ALL.push({ source, area });
25
+ }
26
+
27
+ test('全局唯一来源:id 不重复,且每个来源都如实标注取证强度', () => {
28
+ const ids = BUILTIN_PLUGIN_DATA_SOURCES.map((s) => s.id);
29
+ assert.equal(new Set(ids).size, ids.length);
30
+ for (const s of BUILTIN_PLUGIN_DATA_SOURCES) {
31
+ assert.ok(['measured', 'fixture', 'documented'].includes(s.evidence), s.id);
32
+ assert.ok(s.areas.length > 0, s.id);
33
+ }
34
+ // 本机实测的两个来源必须真有 measured 证据(其余是文档取证,不得美化)
35
+ assert.equal(BUILTIN_PLUGIN_DATA_SOURCES.find((s) => s.id === 'mnemon')?.evidence, 'measured');
36
+ });
37
+
38
+ test('角色 → 分区:config 进 pluginData,assets 进 pluginAssets,cache 两个都不进', () => {
39
+ assert.equal(sectionOfRole('config'), 'pluginData');
40
+ assert.equal(sectionOfRole('assets'), 'pluginAssets');
41
+ assert.equal(sectionOfRole('cache'), null);
42
+
43
+ const config = areasForSection(BUILTIN_PLUGIN_DATA_SOURCES, 'pluginData').map((e) => e.area.relPath);
44
+ const assets = areasForSection(BUILTIN_PLUGIN_DATA_SOURCES, 'pluginAssets').map((e) => e.area.relPath);
45
+ assert.ok(config.includes('.dsh-wallpaper-engine'));
46
+ assert.ok(config.includes('.mnemon/runtime'));
47
+ assert.ok(!config.includes('.dsh-wallpaper-engine/uploads'), '大资产不得出现在配置分区');
48
+ assert.ok(assets.includes('.dsh-wallpaper-engine/uploads'));
49
+ assert.ok(assets.includes('.mnemon/data'));
50
+ assert.ok(![...config, ...assets].includes('.dsh-wallpaper-engine/cache'), '缓存永不进任何分区');
51
+ });
52
+
53
+ test('最长前缀归属:uploads 下的文件归 uploads 区域,不是它祖先的配置区域', () => {
54
+ const hit = areaOfPath(ALL, '.dsh-wallpaper-engine/uploads/a.mp4');
55
+ assert.equal(hit?.area.relPath, '.dsh-wallpaper-engine/uploads');
56
+ assert.equal(hit?.area.role, 'assets');
57
+
58
+ // 配置分区视角:upload 路径被祖先区域的 excludeRel 挡住 → 判 null(导入侧据此拒绝写)
59
+ const configOnly = areasForSection(BUILTIN_PLUGIN_DATA_SOURCES, 'pluginData');
60
+ assert.equal(areaOfPath(configOnly, '.dsh-wallpaper-engine/uploads/a.mp4'), null);
61
+ assert.equal(areaOfPath(configOnly, '.dsh-wallpaper-engine/config.json')?.area.relPath, '.dsh-wallpaper-engine');
62
+ });
63
+
64
+ test('relInArea / isExcludedRel:文件区域精确匹配,目录区域前缀匹配', () => {
65
+ const fileArea = { relPath: '.x/state.json', role: 'config' as const };
66
+ assert.equal(relInArea(fileArea, '.x/state.json'), true);
67
+ assert.equal(relInArea(fileArea, '.x/state.json.bak'), false);
68
+
69
+ const dirArea = { relPath: '.x', role: 'config' as const, excludeRel: ['cache'] };
70
+ assert.equal(relInArea(dirArea, '.x/a/b'), true);
71
+ assert.equal(isExcludedRel(dirArea, '.x/cache'), true);
72
+ assert.equal(isExcludedRel(dirArea, '.x/cache/deep/b'), true);
73
+ assert.equal(isExcludedRel(dirArea, '.x/cacheX'), false, '前缀必须是完整路径段');
74
+ assert.equal(isExcludedRel(dirArea, '.x/a'), false);
75
+ });
76
+
77
+ test('normalizeHomeRel:拒绝一切可能越界的写法,反斜杠归一为 POSIX', () => {
78
+ assert.equal(normalizeHomeRel('.a/b'), '.a/b');
79
+ assert.equal(normalizeHomeRel('.a\\b'), '.a/b');
80
+ assert.equal(normalizeHomeRel(' .a/b '), '.a/b');
81
+
82
+ for (const bad of ['', ' ', '/abs/x', 'C:/x', 'a/../b', '..', './a', 'a/', 'a//b', 'a/\u0000b', 42, null, undefined]) {
83
+ assert.equal(normalizeHomeRel(bad), null, JSON.stringify(bad));
84
+ }
85
+ });
86
+
87
+ test('parseDeclaredDataDirs:合法声明解析成来源;形状不认识整条丢弃(不猜)', () => {
88
+ const source = parseDeclaredDataDirs('dsh-fake', {
89
+ dsh: { dataDirs: [
90
+ { path: '.fake-plugin', role: 'config', env: ['FAKE_DATA_DIR'] },
91
+ { path: '.fake-plugin/uploads', role: 'assets', exclude: ['tmp'] },
92
+ ] },
93
+ });
94
+ assert.ok(source);
95
+ assert.equal(source?.id, 'declared:dsh-fake');
96
+ assert.deepEqual(source?.packages, ['dsh-fake']);
97
+ assert.equal(source?.areas.length, 2);
98
+ assert.deepEqual(source?.areas[0]?.envOverrides, ['FAKE_DATA_DIR']);
99
+ assert.deepEqual(source?.areas[1]?.excludeRel, ['tmp']);
100
+ assert.equal(source?.areas[1]?.role, 'assets');
101
+
102
+ // 缺省角色 = config
103
+ assert.equal(parseDeclaredDataDirs('p', { dsh: { dataDirs: [{ path: '.p' }] } })?.areas[0]?.role, 'config');
104
+
105
+ // 整条丢弃的形态
106
+ assert.equal(parseDeclaredDataDirs('p', null), null);
107
+ assert.equal(parseDeclaredDataDirs('p', {}), null);
108
+ assert.equal(parseDeclaredDataDirs('p', { dsh: {} }), null);
109
+ assert.equal(parseDeclaredDataDirs('p', { dsh: { dataDirs: [] } }), null);
110
+ assert.equal(parseDeclaredDataDirs('p', { dsh: { dataDirs: 'nope' } }), null);
111
+ assert.equal(parseDeclaredDataDirs('p', { dsh: { dataDirs: [{ path: '/abs' }] } }), null, '绝对路径 → 整条丢弃');
112
+ assert.equal(parseDeclaredDataDirs('p', { dsh: { dataDirs: [{ path: '.ok' }, { path: '../escape' }] } }), null, '任一非法 → 整条丢弃');
113
+ });