dsh-config-manager 0.1.71 → 0.1.72

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (548) hide show
  1. package/README.md +111 -96
  2. package/README.zh-CN.md +207 -86
  3. package/lib/adapters/agent-instructions.js +1 -1
  4. package/lib/adapters/file-collection.js +1 -1
  5. package/lib/adapters/index.d.ts +1 -1
  6. package/lib/adapters/index.js +1 -1
  7. package/lib/adapters/link-report.d.ts +1 -0
  8. package/lib/adapters/link-report.js +27 -3
  9. package/lib/adapters/messages.d.ts +18 -0
  10. package/lib/adapters/messages.js +50 -0
  11. package/lib/adapters/plugin-files.js +3 -2
  12. package/lib/adapters/plugins.js +4 -3
  13. package/lib/adapters/prompts.d.ts +1 -0
  14. package/lib/adapters/prompts.js +7 -1
  15. package/lib/adapters/self.d.ts +7 -1
  16. package/lib/adapters/self.js +1 -1
  17. package/lib/adapters/sessions.d.ts +1 -0
  18. package/lib/adapters/sessions.js +4 -2
  19. package/lib/adapters/ui.d.ts +2 -2
  20. package/lib/adapters/ui.js +25 -25
  21. package/lib/adapters/workspaces.d.ts +1 -0
  22. package/lib/adapters/workspaces.js +7 -3
  23. package/lib/cli/actions.d.ts +9 -1
  24. package/lib/cli/actions.js +5 -2
  25. package/lib/cli/help.js +23 -10
  26. package/lib/cli/index.d.ts +23 -0
  27. package/lib/cli/index.js +90 -32
  28. package/lib/cli/sessions-inspect.js +3 -1
  29. package/lib/cli/sessions-repair.js +25 -6
  30. package/lib/cli/web/page.d.ts +12 -0
  31. package/lib/cli/web/page.js +21 -12
  32. package/lib/cli/web/routes.js +2 -2
  33. package/lib/cli/web/server.js +4 -4
  34. package/lib/client.d.ts +1349 -490
  35. package/lib/client.js +43047 -36738
  36. package/lib/core/backup-plan.js +30 -17
  37. package/lib/core/backup-verify.js +24 -23
  38. package/lib/core/backup.d.ts +5 -0
  39. package/lib/core/backup.js +51 -25
  40. package/lib/core/boot-rescue.js +4 -3
  41. package/lib/core/boot-safety.js +2 -1
  42. package/lib/core/command-rollback.d.ts +178 -0
  43. package/lib/core/command-rollback.js +289 -0
  44. package/lib/core/consult-source.js +7 -6
  45. package/lib/core/disk-usage.d.ts +74 -10
  46. package/lib/core/disk-usage.js +67 -4
  47. package/lib/core/engine-locale.d.ts +87 -0
  48. package/lib/core/engine-locale.js +125 -0
  49. package/lib/core/exporter.js +3 -3
  50. package/lib/core/fork-reconcile.d.ts +128 -0
  51. package/lib/core/fork-reconcile.js +150 -0
  52. package/lib/core/journal.js +6 -5
  53. package/lib/core/local-plugin-host.js +3 -2
  54. package/lib/core/local-plugin-pack.js +11 -6
  55. package/lib/core/messages-core.d.ts +292 -0
  56. package/lib/core/messages-core.js +615 -0
  57. package/lib/core/messages.d.ts +60 -2
  58. package/lib/core/messages.js +116 -13
  59. package/lib/core/migration-consult.js +21 -20
  60. package/lib/core/migration-history.js +19 -5
  61. package/lib/core/model-tools.d.ts +0 -22
  62. package/lib/core/model-tools.js +50 -27
  63. package/lib/core/phase3-host.js +30 -5
  64. package/lib/core/plugin-cli.js +18 -19
  65. package/lib/core/reconcile.d.ts +2 -14
  66. package/lib/core/reconcile.js +87 -17
  67. package/lib/core/recovery-orchestrator.js +19 -18
  68. package/lib/core/reinstall.d.ts +4 -0
  69. package/lib/core/reinstall.js +26 -21
  70. package/lib/core/restore.js +20 -17
  71. package/lib/core/rollback-ignore-rules.d.ts +31 -0
  72. package/lib/core/rollback-ignore-rules.js +140 -0
  73. package/lib/core/rollback-point.d.ts +1208 -0
  74. package/lib/core/rollback-point.js +2993 -0
  75. package/lib/core/run-registry.js +6 -5
  76. package/lib/core/session-health.d.ts +15 -0
  77. package/lib/core/session-meta.d.ts +42 -2
  78. package/lib/core/session-meta.js +140 -1
  79. package/lib/core/session-title.d.ts +28 -0
  80. package/lib/core/session-title.js +185 -0
  81. package/lib/core/startup-barrier.d.ts +24 -0
  82. package/lib/core/startup-barrier.js +22 -0
  83. package/lib/core/transaction-coordinator.js +4 -3
  84. package/lib/core/types.d.ts +18 -1
  85. package/lib/core/validator.js +5 -4
  86. package/lib/core/verify-recovery.js +31 -30
  87. package/lib/foreign/bundle.js +3 -2
  88. package/lib/foreign/continue.js +2 -1
  89. package/lib/foreign/crush.js +2 -1
  90. package/lib/foreign/gemini.js +2 -1
  91. package/lib/foreign/goose.js +2 -1
  92. package/lib/foreign/kilocode.js +2 -1
  93. package/lib/foreign/kimi.js +2 -1
  94. package/lib/foreign/messages.d.ts +15 -0
  95. package/lib/foreign/messages.js +40 -0
  96. package/lib/foreign/mimocode.js +2 -1
  97. package/lib/foreign/openclaw.js +2 -1
  98. package/lib/foreign/opencode.js +2 -1
  99. package/lib/foreign/pi.js +2 -1
  100. package/lib/foreign/qoder.js +2 -1
  101. package/lib/foreign/qwen.js +2 -1
  102. package/lib/foreign/registry.js +7 -6
  103. package/lib/foreign/teleagent.js +2 -1
  104. package/lib/foreign/workbuddy.js +2 -1
  105. package/lib/foreign/zcode.js +2 -1
  106. package/lib/foreign/zed.js +2 -1
  107. package/lib/index.d.ts +223 -7
  108. package/lib/index.js +729 -54
  109. package/lib/market/git-file-writer.js +20 -19
  110. package/lib/market/github-repos.js +35 -19
  111. package/lib/market/index-parser.js +31 -30
  112. package/lib/market/messages-market.d.ts +138 -0
  113. package/lib/market/messages-market.js +305 -0
  114. package/lib/market/my-repo.d.ts +2 -2
  115. package/lib/market/my-repo.js +32 -29
  116. package/lib/market/prepare.js +18 -15
  117. package/lib/market/security.js +20 -19
  118. package/lib/market/types.js +2 -1
  119. package/lib/market/url.js +2 -1
  120. package/lib/migrations/index.js +6 -5
  121. package/lib/migrations/messages-migrations.d.ts +32 -0
  122. package/lib/migrations/messages-migrations.js +58 -0
  123. package/lib/profiles/dsh-profile-launcher.js +2 -1
  124. package/lib/profiles/dsh-profile-runtime.js +2 -1
  125. package/lib/profiles/messages.d.ts +7 -0
  126. package/lib/profiles/messages.js +27 -0
  127. package/lib/routes/backup.js +18 -2
  128. package/lib/routes/import.js +3 -3
  129. package/lib/routes/index.d.ts +1 -1
  130. package/lib/routes/kit.d.ts +3 -2
  131. package/lib/routes/kit.js +6 -1
  132. package/lib/routes/market.js +1 -1
  133. package/lib/routes/messages.d.ts +42 -0
  134. package/lib/routes/messages.js +100 -0
  135. package/lib/routes/profiles.js +12 -6
  136. package/lib/routes/recovery.js +378 -114
  137. package/lib/routes/snapshots.js +51 -4
  138. package/lib/routes/sync.js +68 -16
  139. package/lib/schema/config.d.ts +11 -1
  140. package/lib/schema/config.js +52 -20
  141. package/lib/schema/index.d.ts +8 -6
  142. package/lib/schema/index.js +8 -6
  143. package/lib/schema/manifest.js +19 -18
  144. package/lib/schema/messages.d.ts +47 -0
  145. package/lib/schema/messages.js +110 -0
  146. package/lib/schema/section-registry.d.ts +41 -14
  147. package/lib/schema/section-registry.js +35 -29
  148. package/lib/schema/versions.js +6 -5
  149. package/lib/security/encryption.js +16 -15
  150. package/lib/security/integrity.js +8 -7
  151. package/lib/security/messages.d.ts +30 -0
  152. package/lib/security/messages.js +77 -0
  153. package/lib/security/redaction.js +12 -0
  154. package/lib/security/secret-scanner.js +16 -13
  155. package/lib/security/vault.js +2 -1
  156. package/lib/security/zip-security.js +4 -3
  157. package/lib/sync/ancestor.js +2 -1
  158. package/lib/sync/autosync-config.d.ts +11 -0
  159. package/lib/sync/autosync-config.js +12 -0
  160. package/lib/sync/autosync-scheduler.d.ts +11 -1
  161. package/lib/sync/autosync-scheduler.js +115 -76
  162. package/lib/sync/backup-files.js +3 -2
  163. package/lib/sync/backup-scheduler.js +6 -5
  164. package/lib/sync/blob-store.js +2 -1
  165. package/lib/sync/git/git-transport.d.ts +55 -0
  166. package/lib/sync/git/git-transport.js +152 -2
  167. package/lib/sync/github-auth.js +10 -9
  168. package/lib/sync/layout.js +13 -12
  169. package/lib/sync/messages.d.ts +63 -0
  170. package/lib/sync/messages.js +149 -0
  171. package/lib/sync/review-queue.js +6 -5
  172. package/lib/sync/s3/messages.d.ts +7 -0
  173. package/lib/sync/s3/messages.js +16 -0
  174. package/lib/sync/s3/s3-providers.d.ts +4 -1
  175. package/lib/sync/s3/s3-providers.js +5 -5
  176. package/lib/sync/s3/s3-transport.js +1 -1
  177. package/lib/sync/s3/sigv4.js +2 -1
  178. package/lib/sync/snapshot-crypto.js +6 -5
  179. package/lib/sync/snapshot-json.js +3 -2
  180. package/lib/sync/sync-engine.d.ts +37 -15
  181. package/lib/sync/sync-engine.js +67 -37
  182. package/lib/sync/sync-history.js +7 -6
  183. package/lib/sync/sync-selection.d.ts +15 -4
  184. package/lib/sync/sync-selection.js +17 -5
  185. package/lib/sync/webdav/webdav-transport.d.ts +1 -0
  186. package/lib/sync/webdav/webdav-transport.js +4 -1
  187. package/lib/ui/artifact-view.js +4 -3
  188. package/lib/ui/commands.d.ts +1 -1
  189. package/lib/ui/commands.js +10 -0
  190. package/lib/ui/disk-usage-view.d.ts +33 -4
  191. package/lib/ui/disk-usage-view.js +29 -1
  192. package/lib/ui/errors.d.ts +1 -1
  193. package/lib/ui/errors.js +4 -3
  194. package/lib/ui/export-flow.js +24 -20
  195. package/lib/ui/foreign-view.d.ts +5 -1
  196. package/lib/ui/foreign-view.js +26 -2
  197. package/lib/ui/i18n.d.ts +65 -0
  198. package/lib/ui/i18n.js +114 -0
  199. package/lib/ui/import-wizard.d.ts +7 -0
  200. package/lib/ui/import-wizard.js +13 -10
  201. package/lib/ui/progress.d.ts +2 -2
  202. package/lib/ui/progress.js +1 -2
  203. package/lib/ui/report.d.ts +5 -5
  204. package/lib/ui/report.js +6 -6
  205. package/lib/ui/rollback-view.d.ts +756 -0
  206. package/lib/ui/rollback-view.js +724 -0
  207. package/lib/ui/selection-model.d.ts +41 -0
  208. package/lib/ui/selection-model.js +30 -0
  209. package/lib/ui/session-inventory-view.d.ts +97 -3
  210. package/lib/ui/session-inventory-view.js +76 -2
  211. package/lib/ui/session-issue-labels.d.ts +43 -0
  212. package/lib/ui/session-issue-labels.js +48 -0
  213. package/lib/ui/sync-settings-view.d.ts +33 -0
  214. package/lib/ui/sync-settings-view.js +40 -0
  215. package/lib/ui/types.d.ts +8 -3
  216. package/lib/ui/types.js +1 -1
  217. package/lib/utils/atomic-write.js +6 -5
  218. package/lib/utils/bundle-scan.d.ts +11 -1
  219. package/lib/utils/bundle-scan.js +12 -1
  220. package/lib/utils/env-lock.d.ts +10 -0
  221. package/lib/utils/env-lock.js +73 -46
  222. package/lib/utils/json.js +6 -5
  223. package/lib/utils/messages-utils.d.ts +96 -0
  224. package/lib/utils/messages-utils.js +216 -0
  225. package/lib/utils/proxy.js +18 -5
  226. package/lib/utils/recursive-walk.d.ts +22 -0
  227. package/lib/utils/recursive-walk.js +75 -1
  228. package/lib/utils/session-log-repair.js +10 -9
  229. package/lib/utils/zip.js +27 -26
  230. package/package.json +12 -2
  231. package/src/adapters/agent-instructions.ts +1 -1
  232. package/src/adapters/file-collection.test.ts +1 -0
  233. package/src/adapters/file-collection.ts +1 -1
  234. package/src/adapters/files.test.ts +27 -0
  235. package/src/adapters/index.ts +1 -1
  236. package/src/adapters/link-report.test.ts +22 -2
  237. package/src/adapters/link-report.ts +29 -4
  238. package/src/adapters/messages.ts +54 -0
  239. package/src/adapters/plugin-files.ts +3 -2
  240. package/src/adapters/plugins.ts +4 -3
  241. package/src/adapters/prompts.ts +7 -1
  242. package/src/adapters/self.test.ts +26 -0
  243. package/src/adapters/self.ts +8 -2
  244. package/src/adapters/sessions.ts +4 -2
  245. package/src/adapters/settings.test.ts +38 -1
  246. package/src/adapters/ui.test.ts +2 -2
  247. package/src/adapters/ui.ts +27 -26
  248. package/src/adapters/workspaces.ts +7 -3
  249. package/src/cli/actions.test.ts +28 -0
  250. package/src/cli/actions.ts +11 -2
  251. package/src/cli/help.ts +23 -10
  252. package/src/cli/index-guards.test.ts +193 -0
  253. package/src/cli/index.ts +106 -31
  254. package/src/cli/sessions-inspect.test.ts +15 -1
  255. package/src/cli/sessions-inspect.ts +3 -1
  256. package/src/cli/sessions-repair.test.ts +84 -0
  257. package/src/cli/sessions-repair.ts +33 -7
  258. package/src/cli/web/console-route-parity.test.ts +115 -0
  259. package/src/cli/web/page.ts +23 -12
  260. package/src/cli/web/routes.ts +2 -1
  261. package/src/cli/web/server.ts +4 -4
  262. package/src/cli/web/web.test.ts +2 -2
  263. package/src/client/ConfigManagerSection.tsx +30 -2
  264. package/src/client/about/AboutPanel.tsx +2 -2
  265. package/src/client/about/PluginUpdateDialog.tsx +1 -1
  266. package/src/client/about/ReleaseNotesDialog.tsx +2 -2
  267. package/src/client/about/about-view.test.ts +13 -3
  268. package/src/client/about/about-view.ts +10 -6
  269. package/src/client/about/release-notes-view.test.ts +14 -4
  270. package/src/client/about/release-notes-view.ts +4 -2
  271. package/src/client/api.ts +18 -6
  272. package/src/client/chat/ChatEntryView.tsx +391 -0
  273. package/src/client/chat/RollbackDiffTab.tsx +61 -0
  274. package/src/client/chat/RollbackFileIcon.tsx +96 -0
  275. package/src/client/chat/RollbackReviewDialog.tsx +591 -0
  276. package/src/client/chat/chat-entry-view.test.ts +1174 -0
  277. package/src/client/chat/chat-entry-view.ts +758 -0
  278. package/src/client/chat/chat-locales.ts +152 -0
  279. package/src/client/chat/chat-node-registration.ts +64 -0
  280. package/src/client/chat/chat-point-window.ts +173 -0
  281. package/src/client/chat/chat-slots.ts +107 -0
  282. package/src/client/chat/rollback-diff-tab.test.ts +105 -0
  283. package/src/client/chat/rollback-diff-tab.ts +164 -0
  284. package/src/client/chat/rollback-entry-view.test.ts +1469 -0
  285. package/src/client/chat/rollback-entry-view.ts +941 -0
  286. package/src/client/chat/sidebar-slots.ts +37 -0
  287. package/src/client/common/ConfirmDialog.tsx +7 -3
  288. package/src/client/common/ContentPicker.tsx +48 -10
  289. package/src/client/common/DiffTable.tsx +97 -0
  290. package/src/client/common/ErrorBanner.tsx +4 -4
  291. package/src/client/common/Icon.tsx +2 -0
  292. package/src/client/common/Modal.tsx +8 -1
  293. package/src/client/common/ProgressBar.tsx +8 -2
  294. package/src/client/common/ReportView.tsx +4 -4
  295. package/src/client/common/RunsCenter.tsx +1 -0
  296. package/src/client/common/client-route-parity.test.ts +78 -0
  297. package/src/client/common/info-hint-guard.test.ts +46 -13
  298. package/src/client/common/picker-lazy-units.test.ts +42 -0
  299. package/src/client/common/plan-text-redaction.test.ts +17 -6
  300. package/src/client/common/progress-view-edge.test.ts +8 -7
  301. package/src/client/common/progress-view.test.ts +9 -9
  302. package/src/client/common/progress-view.ts +2 -2
  303. package/src/client/common/routes.ts +28 -0
  304. package/src/client/common/ui.tsx +73 -10
  305. package/src/client/config-manager.module.css +996 -21
  306. package/src/client/css-scale-guard.test.ts +30 -0
  307. package/src/client/environment/EnvironmentPanel.tsx +11 -4
  308. package/src/client/environment/maintenance/DiskUsageCard.tsx +23 -2
  309. package/src/client/export/ExportView.tsx +13 -8
  310. package/src/client/history/HistoryPanel.tsx +4 -3
  311. package/src/client/history/history-api.ts +2 -2
  312. package/src/client/history/history-locales.ts +5 -1
  313. package/src/client/home/HomePanel.tsx +29 -8
  314. package/src/client/import/ForeignImportView.tsx +4 -5
  315. package/src/client/import/ImportWizardView.tsx +5 -3
  316. package/src/client/import/import-wizard-split.test.ts +3 -1
  317. package/src/client/import/import-wizard-steps.tsx +9 -9
  318. package/src/client/index.ts +186 -3
  319. package/src/client/library/LibraryActions.tsx +32 -7
  320. package/src/client/library/LibraryPanel.tsx +23 -3
  321. package/src/client/library/RestorePlanView.tsx +9 -66
  322. package/src/client/locales.ts +54 -5
  323. package/src/client/lucide-icons.d.ts +1 -0
  324. package/src/client/market/MarketPanel.tsx +13 -2
  325. package/src/client/market/MyConfigsView.tsx +5 -4
  326. package/src/client/market/MyConfigsWizard.tsx +10 -8
  327. package/src/client/market/market-api.test.ts +9 -8
  328. package/src/client/market/market-api.ts +2 -2
  329. package/src/client/market/market-locales.ts +3 -0
  330. package/src/client/market/market-view.test.ts +6 -6
  331. package/src/client/market/market-view.ts +3 -3
  332. package/src/client/market/my-configs-api.ts +2 -2
  333. package/src/client/market/my-configs-view.test.ts +23 -22
  334. package/src/client/market/my-configs-view.ts +8 -8
  335. package/src/client/recovery/IncompleteCopiesSection.tsx +2 -1
  336. package/src/client/recovery/RecoveryPanel.tsx +442 -283
  337. package/src/client/recovery/RollbackCard.tsx +665 -0
  338. package/src/client/recovery/RollbackDiffPane.tsx +58 -0
  339. package/src/client/recovery/RollbackFileChanges.tsx +163 -0
  340. package/src/client/recovery/incident-api.ts +2 -2
  341. package/src/client/recovery/recovery-api.ts +116 -6
  342. package/src/client/recovery/recovery-locales.test.ts +299 -0
  343. package/src/client/recovery/recovery-locales.ts +488 -149
  344. package/src/client/recovery/recovery-panel-layout.test.ts +132 -2
  345. package/src/client/recovery/rollback-file-changes.test.ts +73 -0
  346. package/src/client/recovery/session-dialog-mount.test.ts +54 -0
  347. package/src/client/run-store.ts +84 -21
  348. package/src/client/sync/AutosyncCard.tsx +25 -1
  349. package/src/client/sync/ChannelClearConfirmDialog.tsx +6 -3
  350. package/src/client/sync/ChannelConfigDialog.tsx +10 -10
  351. package/src/client/sync/DecryptPasswordCard.tsx +2 -2
  352. package/src/client/sync/SecurityOptionsCard.tsx +27 -17
  353. package/src/client/sync/SyncChannelEntryCard.tsx +14 -2
  354. package/src/client/sync/SyncConfirmView.tsx +46 -8
  355. package/src/client/sync/SyncLogList.tsx +1 -1
  356. package/src/client/sync/SyncPanel.tsx +31 -23
  357. package/src/client/sync/SyncRepositoryPicker.tsx +3 -5
  358. package/src/client/sync/SyncSectionPickerDialog.tsx +11 -2
  359. package/src/client/sync/SyncSettingsView.test.ts +20 -1
  360. package/src/client/sync/UnreadableSnapshotsBanner.tsx +1 -1
  361. package/src/client/sync/WorkDirCard.tsx +112 -0
  362. package/src/client/sync/history-model.test.ts +42 -9
  363. package/src/client/sync/history-model.ts +30 -15
  364. package/src/client/sync/sync-api.test.ts +57 -33
  365. package/src/client/sync/sync-api.ts +80 -6
  366. package/src/client/sync/sync-locales.ts +104 -3
  367. package/src/client/sync/sync-view-v2.test.ts +65 -13
  368. package/src/client/sync/sync-view.test.ts +107 -52
  369. package/src/client/sync/sync-view.ts +124 -41
  370. package/src/core/backup-plan.test.ts +28 -0
  371. package/src/core/backup-plan.ts +29 -17
  372. package/src/core/backup-verify.ts +24 -23
  373. package/src/core/backup.test.ts +76 -0
  374. package/src/core/backup.ts +55 -25
  375. package/src/core/boot-rescue.ts +4 -3
  376. package/src/core/boot-safety.ts +2 -1
  377. package/src/core/command-rollback.test.ts +536 -0
  378. package/src/core/command-rollback.ts +402 -0
  379. package/src/core/consult-source.ts +7 -6
  380. package/src/core/disk-usage.test.ts +257 -1
  381. package/src/core/disk-usage.ts +134 -14
  382. package/src/core/engine-locale.test.ts +593 -0
  383. package/src/core/engine-locale.ts +175 -0
  384. package/src/core/exporter.ts +3 -3
  385. package/src/core/fork-reconcile.test.ts +279 -0
  386. package/src/core/fork-reconcile.ts +234 -0
  387. package/src/core/journal.ts +6 -5
  388. package/src/core/local-plugin-host.ts +3 -2
  389. package/src/core/local-plugin-pack.ts +11 -6
  390. package/src/core/messages-core.ts +657 -0
  391. package/src/core/messages.ts +123 -17
  392. package/src/core/migration-consult.ts +21 -20
  393. package/src/core/migration-history.ts +19 -5
  394. package/src/core/model-tools.ts +28 -27
  395. package/src/core/phase3-host.ts +21 -5
  396. package/src/core/phase3-p2b-fingerprint.test.ts +22 -0
  397. package/src/core/plugin-cli.ts +18 -19
  398. package/src/core/reconcile.test.ts +117 -0
  399. package/src/core/reconcile.ts +85 -17
  400. package/src/core/recovery-orchestrator.ts +19 -18
  401. package/src/core/reinstall.ts +26 -21
  402. package/src/core/restore.ts +20 -17
  403. package/src/core/rollback-file-diff.test.ts +159 -0
  404. package/src/core/rollback-ignore-rules.ts +126 -0
  405. package/src/core/rollback-point.test.ts +2603 -0
  406. package/src/core/rollback-point.ts +3755 -0
  407. package/src/core/run-registry.ts +6 -5
  408. package/src/core/session-health.ts +15 -0
  409. package/src/core/session-meta.test.ts +117 -1
  410. package/src/core/session-meta.ts +149 -2
  411. package/src/core/session-title.test.ts +127 -0
  412. package/src/core/session-title.ts +189 -0
  413. package/src/core/startup-barrier.test.ts +69 -0
  414. package/src/core/startup-barrier.ts +27 -0
  415. package/src/core/transaction-coordinator.ts +4 -3
  416. package/src/core/types.ts +18 -1
  417. package/src/core/validator.ts +5 -4
  418. package/src/core/verify-recovery.ts +31 -30
  419. package/src/foreign/bundle.ts +3 -2
  420. package/src/foreign/continue.ts +2 -1
  421. package/src/foreign/crush.ts +2 -1
  422. package/src/foreign/file-budget.test.ts +2 -0
  423. package/src/foreign/gemini.ts +2 -1
  424. package/src/foreign/goose.ts +2 -1
  425. package/src/foreign/kilocode.ts +2 -1
  426. package/src/foreign/kimi.ts +2 -1
  427. package/src/foreign/messages.ts +44 -0
  428. package/src/foreign/mimocode.ts +2 -1
  429. package/src/foreign/openclaw.ts +2 -1
  430. package/src/foreign/opencode.ts +2 -1
  431. package/src/foreign/pi.ts +2 -1
  432. package/src/foreign/qoder.ts +2 -1
  433. package/src/foreign/qwen.ts +2 -1
  434. package/src/foreign/registry.ts +7 -6
  435. package/src/foreign/teleagent.ts +2 -1
  436. package/src/foreign/workbuddy.ts +2 -1
  437. package/src/foreign/zcode.ts +2 -1
  438. package/src/foreign/zed.ts +2 -1
  439. package/src/index.facade.test.ts +340 -1
  440. package/src/index.ts +813 -57
  441. package/src/market/git-file-writer.ts +20 -19
  442. package/src/market/github-repos.test.ts +23 -0
  443. package/src/market/github-repos.ts +37 -19
  444. package/src/market/index-parser.ts +31 -30
  445. package/src/market/messages-market.ts +325 -0
  446. package/src/market/my-repo.ts +32 -29
  447. package/src/market/prepare.ts +18 -15
  448. package/src/market/security.ts +20 -19
  449. package/src/market/types.ts +2 -1
  450. package/src/market/url.ts +2 -1
  451. package/src/migrations/index.ts +6 -5
  452. package/src/migrations/messages-migrations.ts +59 -0
  453. package/src/profiles/dsh-profile-launcher.ts +2 -1
  454. package/src/profiles/dsh-profile-runtime.ts +2 -1
  455. package/src/profiles/messages.ts +31 -0
  456. package/src/routes/backup.ts +18 -2
  457. package/src/routes/import.ts +3 -3
  458. package/src/routes/index.ts +1 -1
  459. package/src/routes/kit.ts +9 -3
  460. package/src/routes/market.ts +1 -1
  461. package/src/routes/messages.ts +104 -0
  462. package/src/routes/profiles.ts +12 -6
  463. package/src/routes/recovery.test.ts +405 -1
  464. package/src/routes/recovery.ts +377 -109
  465. package/src/routes/snapshots.ts +51 -4
  466. package/src/routes/sync.ts +67 -16
  467. package/src/schema/config.ts +53 -20
  468. package/src/schema/index.ts +7 -5
  469. package/src/schema/manifest.ts +19 -18
  470. package/src/schema/messages.ts +120 -0
  471. package/src/schema/registry.test.ts +19 -5
  472. package/src/schema/section-registry.ts +64 -39
  473. package/src/schema/versions.ts +6 -5
  474. package/src/security/encryption.ts +16 -15
  475. package/src/security/integrity.ts +8 -7
  476. package/src/security/messages.ts +87 -0
  477. package/src/security/redaction.test.ts +17 -1
  478. package/src/security/redaction.ts +13 -0
  479. package/src/security/secret-scanner.ts +19 -12
  480. package/src/security/security.test.ts +47 -0
  481. package/src/security/vault.ts +2 -1
  482. package/src/security/zip-security.ts +4 -3
  483. package/src/sync/ancestor.ts +2 -1
  484. package/src/sync/autosync-config.ts +21 -0
  485. package/src/sync/autosync-scheduler.test.ts +63 -7
  486. package/src/sync/autosync-scheduler.ts +118 -78
  487. package/src/sync/backup-files.ts +3 -2
  488. package/src/sync/backup-scheduler.ts +6 -5
  489. package/src/sync/blob-store.ts +2 -1
  490. package/src/sync/git/git-transport.test.ts +170 -8
  491. package/src/sync/git/git-transport.ts +176 -2
  492. package/src/sync/github-auth.ts +10 -9
  493. package/src/sync/layout.ts +13 -12
  494. package/src/sync/messages.ts +153 -0
  495. package/src/sync/review-queue.ts +6 -5
  496. package/src/sync/s3/messages.ts +16 -0
  497. package/src/sync/s3/s3-providers.ts +9 -6
  498. package/src/sync/s3/s3-transport.ts +1 -1
  499. package/src/sync/s3/sigv4.ts +3 -1
  500. package/src/sync/snapshot-crypto.ts +6 -5
  501. package/src/sync/snapshot-json.ts +3 -2
  502. package/src/sync/sync-engine-download-tombstone.test.ts +1 -1
  503. package/src/sync/sync-engine.test.ts +226 -8
  504. package/src/sync/sync-engine.ts +103 -41
  505. package/src/sync/sync-history.ts +7 -6
  506. package/src/sync/sync-selection.test.ts +12 -2
  507. package/src/sync/sync-selection.ts +18 -5
  508. package/src/sync/webdav/webdav-transport.ts +4 -1
  509. package/src/ui/artifact-view.ts +4 -3
  510. package/src/ui/commands.test.ts +1 -0
  511. package/src/ui/commands.ts +11 -1
  512. package/src/ui/disk-usage-view.test.ts +113 -6
  513. package/src/ui/disk-usage-view.ts +63 -7
  514. package/src/ui/errors.ts +4 -3
  515. package/src/ui/export-flow.ts +24 -20
  516. package/src/ui/foreign-view.test.ts +43 -0
  517. package/src/ui/foreign-view.ts +30 -3
  518. package/src/ui/i18n.ts +125 -0
  519. package/src/ui/import-wizard.ts +18 -10
  520. package/src/ui/progress.ts +2 -2
  521. package/src/ui/report.ts +7 -6
  522. package/src/ui/rollback-view.test.ts +819 -0
  523. package/src/ui/rollback-view.ts +1339 -0
  524. package/src/ui/selection-model.test.ts +33 -1
  525. package/src/ui/selection-model.ts +53 -0
  526. package/src/ui/session-inventory-view.test.ts +109 -0
  527. package/src/ui/session-inventory-view.ts +138 -3
  528. package/src/ui/session-issue-labels.test.ts +51 -0
  529. package/src/ui/session-issue-labels.ts +70 -0
  530. package/src/ui/sync-settings-view.test.ts +36 -0
  531. package/src/ui/sync-settings-view.ts +49 -0
  532. package/src/ui/types.ts +10 -4
  533. package/src/utils/atomic-write.ts +6 -5
  534. package/src/utils/bundle-scan.test.ts +2 -2
  535. package/src/utils/bundle-scan.ts +12 -1
  536. package/src/utils/env-lock.ts +83 -49
  537. package/src/utils/json.ts +7 -5
  538. package/src/utils/messages-utils.ts +232 -0
  539. package/src/utils/proxy.test.ts +25 -0
  540. package/src/utils/proxy.ts +19 -5
  541. package/src/utils/recursive-walk.test.ts +43 -0
  542. package/src/utils/recursive-walk.ts +75 -1
  543. package/src/utils/session-log-repair.ts +10 -9
  544. package/src/utils/zip.ts +27 -26
  545. package/lib/core/checkpoint.d.ts +0 -362
  546. package/lib/core/checkpoint.js +0 -1309
  547. package/src/core/checkpoint.test.ts +0 -665
  548. package/src/core/checkpoint.ts +0 -1614
@@ -0,0 +1,189 @@
1
+ /**
2
+ * 「会话标题」的**日志兜底**(2026-10-12 用户要求:缓存里没有这条会话,就自动读这条会话自己的名字)。
3
+ *
4
+ * 两条来源,优先级固定:
5
+ * ① `storages/session_projcache.json` 的 `rows.title.val`(`core/session-meta.ts` 的索引,快、覆盖常态);
6
+ * ② **会话日志里的 `session/title` 事件**(本模块)—— DSH 自己写的标题事件,
7
+ * `data = { title, messageSeqs, source }`;`source = 'user'` 表示用户显式改过名(它压过自动生成的那条)。
8
+ * 缓存里没有这条会话(旧会话 / 别的档案写的 / 子代理会话)时,这是**唯一不猜**的名字来源。
9
+ *
10
+ * 只读 + 有界 + 诚实:
11
+ * · 只读日志**前** {@link SESSION_TITLE_SCAN_BYTES}(1 MiB,覆盖「标题在开头几回合内生成」的常态);
12
+ * 超出这个窗口的标题事件读不到 → 返回 null,界面回落显示会话 id(**绝不从首条消息编造标题**);
13
+ * · 同一个会话出现多条标题事件时取**最后一条**(用户改名压过自动标题);
14
+ * · 同一条日志按 `mtime + size` 缓存在进程内:日志增长(新标题落盘)后自动重读,不吃陈旧结论;
15
+ * · 一次请求最多补 {@link SESSION_TITLE_MAX_READS} 个会话(台账里几十条缺标题时不把盘读穿)。
16
+ */
17
+ import { open, readdir, stat } from 'node:fs/promises';
18
+ import { join } from 'node:path';
19
+
20
+ import { isSessionLogName } from '../utils/session-log.ts';
21
+ import { decodeZstdFrame, scanZstdFrames, zstdAvailable } from '../utils/zstd-frame.ts';
22
+
23
+ /** 只扫日志前这么多字节(标题通常在开头几回合内写入;更大的日志只扫前缀,读不到就回落 id)。 */
24
+ export const SESSION_TITLE_SCAN_BYTES = 1024 * 1024;
25
+
26
+ /** 一次请求最多为几个会话做日志兜底(按 unitId 去重之后)。 */
27
+ export const SESSION_TITLE_MAX_READS = 24;
28
+
29
+ interface TitleCacheEntry {
30
+ mtimeMs: number;
31
+ size: number;
32
+ title: string | null;
33
+ }
34
+
35
+ /** 进程内缓存:绝对路径 → (mtime + size + 结论)。日志一增长就失效(不吃陈旧结论)。 */
36
+ const titleCache = new Map<string, TitleCacheEntry>();
37
+
38
+ /** 清空缓存(单测用;宿主进程内长期有效是刻意的)。 */
39
+ export function clearSessionTitleCache(): void {
40
+ titleCache.clear();
41
+ }
42
+
43
+ /** `session/title` 事件行 → 标题(形状不对 / 空标题一律 null,绝不猜)。 */
44
+ function titleOfRow(row: unknown): string | null {
45
+ if (row === null || typeof row !== 'object' || Array.isArray(row)) return null;
46
+ const rec = row as Record<string, unknown>;
47
+ if (rec['type'] !== 'session/title') return null;
48
+ const data = rec['data'];
49
+ if (data === null || typeof data !== 'object' || Array.isArray(data)) return null;
50
+ const title = (data as Record<string, unknown>)['title'];
51
+ return typeof title === 'string' && title.trim() !== '' ? title.trim() : null;
52
+ }
53
+
54
+ /** 只读日志**前缀**(最多 `max` 字节;读不出回 null,绝不抛)。 */
55
+ async function readPrefix(absPath: string, max: number): Promise<Buffer | null> {
56
+ let handle: Awaited<ReturnType<typeof open>>;
57
+ try {
58
+ handle = await open(absPath, 'r');
59
+ } catch {
60
+ return null;
61
+ }
62
+ try {
63
+ const buffer = Buffer.allocUnsafe(max);
64
+ const { bytesRead } = await handle.read(buffer, 0, max, 0);
65
+ return buffer.subarray(0, bytesRead);
66
+ } catch {
67
+ return null;
68
+ } finally {
69
+ await handle.close().catch(() => undefined);
70
+ }
71
+ }
72
+
73
+ /**
74
+ * 从会话日志字节里取标题:解出每个**完整**帧,逐行先做 `session/title` 子串预筛(省掉整篇 JSON.parse),
75
+ * 命中才解析该行;多条命中取最后一条。容器损坏 / 无 zstd 能力 → null。
76
+ */
77
+ function titleFromBytes(bytes: Uint8Array): string | null {
78
+ let frames: { start: number; end: number }[];
79
+ try {
80
+ frames = scanZstdFrames(bytes).frames;
81
+ } catch {
82
+ return null;
83
+ }
84
+ let found: string | null = null;
85
+ for (const frame of frames) {
86
+ let text: string;
87
+ try {
88
+ text = decodeZstdFrame(bytes.subarray(frame.start, frame.end)).toString('utf8');
89
+ } catch {
90
+ return found;
91
+ }
92
+ for (const line of text.split('\n')) {
93
+ if (line === '' || !line.includes('session/title')) continue;
94
+ let parsed: unknown;
95
+ try {
96
+ parsed = JSON.parse(line);
97
+ } catch {
98
+ continue;
99
+ }
100
+ const title = titleOfRow(parsed);
101
+ if (title !== null) found = title;
102
+ }
103
+ }
104
+ return found;
105
+ }
106
+
107
+ /** 读单个日志文件的标题(带 mtime+size 缓存)。 */
108
+ async function readTitleFromLogFile(absPath: string): Promise<string | null> {
109
+ if (!zstdAvailable()) return null;
110
+ const info = await stat(absPath).catch(() => null);
111
+ if (info === null) return null;
112
+ const cached = titleCache.get(absPath);
113
+ if (cached !== undefined && cached.mtimeMs === info.mtimeMs && cached.size === info.size) return cached.title;
114
+ const prefix = await readPrefix(absPath, SESSION_TITLE_SCAN_BYTES);
115
+ const title = prefix === null ? null : titleFromBytes(prefix);
116
+ titleCache.set(absPath, { mtimeMs: info.mtimeMs, size: info.size, title });
117
+ return title;
118
+ }
119
+
120
+ /** `unitId`(`<projectKey>/<sessionId>`)→ 目录段;越界 / 形状不符一律 null(绝不拼可疑路径)。 */
121
+ function unitSegments(unitId: string): string[] | null {
122
+ const parts = unitId.split('/').filter((part) => part !== '');
123
+ if (parts.length < 2) return null;
124
+ for (const part of parts) {
125
+ if (part === '.' || part === '..' || part.includes('\\') || part.includes(':')) return null;
126
+ }
127
+ return parts;
128
+ }
129
+
130
+ /**
131
+ * 从会话日志里读这条会话自己的名字(`session/title` 事件)。
132
+ *
133
+ * @param sessionsDir 会话根目录(`<homeDir>/sessions`)。
134
+ * @param unitId 台账里的会话单元 id(`<projectKey>/<sessionId>`;缺了就返回 null)。
135
+ * @returns 标题;读不到 / 没有标题事件 / 形状不符 → null(界面回落显示会话 id)。
136
+ */
137
+ export async function readSessionTitleFromLog(sessionsDir: string, unitId: string): Promise<string | null> {
138
+ const parts = unitSegments(unitId);
139
+ if (parts === null) return null;
140
+ const dir = join(sessionsDir, ...parts);
141
+ const names = await readdir(dir).catch(() => [] as string[]);
142
+ const logs = names.filter((name) => isSessionLogName(name)).sort();
143
+ if (logs.length === 0) return null;
144
+ // 与体检同口径:同一会话有多份 generation 时以**最新的一份**(字典序最后)为准
145
+ const picked = logs[logs.length - 1];
146
+ if (picked === undefined) return null;
147
+ return readTitleFromLogFile(join(dir, picked));
148
+ }
149
+
150
+ /**
151
+ * 给一批台账行补标题:**已有标题的不动**,缺标题的按 `unitId` 去重后走日志兜底。
152
+ *
153
+ * 只读、有界(见 {@link SESSION_TITLE_MAX_READS})、失败一律原样返回(界面回落显示会话 id)。
154
+ */
155
+ export async function applySessionLogTitles<
156
+ T extends { sessionId?: string | null; unitId?: string | null; title?: string },
157
+ >(
158
+ rows: readonly T[],
159
+ sessionsDir: string,
160
+ options: { maxReads?: number } = {},
161
+ ): Promise<(T & { title?: string })[]> {
162
+ const maxReads = Math.max(0, options.maxReads ?? SESSION_TITLE_MAX_READS);
163
+ const resolved = new Map<string, string | null>();
164
+ const out: (T & { title?: string })[] = [];
165
+ let reads = 0;
166
+ for (const row of rows) {
167
+ if (typeof row.title === 'string' && row.title.trim() !== '') {
168
+ out.push(row);
169
+ continue;
170
+ }
171
+ const unitId = typeof row.unitId === 'string' && row.unitId !== '' ? row.unitId : null;
172
+ if (unitId === null) {
173
+ out.push(row);
174
+ continue;
175
+ }
176
+ let title = resolved.get(unitId);
177
+ if (title === undefined) {
178
+ if (reads >= maxReads) {
179
+ out.push(row);
180
+ continue;
181
+ }
182
+ reads += 1;
183
+ title = await readSessionTitleFromLog(sessionsDir, unitId);
184
+ resolved.set(unitId, title);
185
+ }
186
+ out.push(title === null || title === '' ? row : { ...row, title });
187
+ }
188
+ return out;
189
+ }
@@ -0,0 +1,69 @@
1
+ /**
2
+ * issue #87:启动分类 → SAFE MODE 处置的单一判据(纯函数矩阵)。
3
+ *
4
+ * 背景:`probeSafeModeSync()` 读的是上一轮留下的 durable 标记,分类结论才是本轮权威。旧实现只做
5
+ * `safeModeActive = safeModeActive || kind ∈ {RECOVERY_REQUIRED, NEEDS_ATTENTION, UNKNOWN_STATE}` ——
6
+ * 只会把标志**置真**,于是「崩溃前什么都没写」的残留会让本会话的写操作一直被 423 挡着(界面却说
7
+ * 「无待处理事项」),且下次启动又从头再来一遍。
8
+ */
9
+ import test from 'node:test';
10
+ import assert from 'node:assert/strict';
11
+ import { resolveStartupSafeMode, classifyStartup, FAIL_CLOSED_STARTUP } from './startup-barrier.ts';
12
+
13
+ test('issue #87:NORMAL + 无活锁者 + 陈旧 blocked 标记 → 解除(内存标志与 durable 标记一起复位)', () => {
14
+ assert.deepEqual(
15
+ resolveStartupSafeMode({ kind: 'NORMAL', lockState: 'FREE', currentlyBlocked: true }),
16
+ { blocked: false, clearStaleMarker: true },
17
+ );
18
+ });
19
+
20
+ test('issue #87:NORMAL 但没有陈旧标记 → 不动标记(不做无意义写入)', () => {
21
+ assert.deepEqual(
22
+ resolveStartupSafeMode({ kind: 'NORMAL', lockState: 'FREE', currentlyBlocked: false }),
23
+ { blocked: false, clearStaleMarker: false },
24
+ );
25
+ });
26
+
27
+ test('issue #87:NORMAL 但锁被别人持有(LOCKED)→ 绝不清标记、保持阻断(不替活锁者解除保护)', () => {
28
+ assert.deepEqual(
29
+ resolveStartupSafeMode({ kind: 'NORMAL', lockState: 'LOCKED', currentlyBlocked: true }),
30
+ { blocked: true, clearStaleMarker: false },
31
+ );
32
+ });
33
+
34
+ test('issue #87:残留锁(STALE_LOCK_DETECTED)未显式回收 → 不清标记(仍需用户回收残留锁)', () => {
35
+ assert.deepEqual(
36
+ resolveStartupSafeMode({ kind: 'NORMAL', lockState: 'STALE_LOCK_DETECTED', currentlyBlocked: true }),
37
+ { blocked: true, clearStaleMarker: false },
38
+ );
39
+ });
40
+
41
+ test('issue #87:RECOVERY_REQUIRED / NEEDS_ATTENTION / UNKNOWN_STATE → 一律阻断且不清标记', () => {
42
+ for (const kind of ['RECOVERY_REQUIRED', 'NEEDS_ATTENTION', 'UNKNOWN_STATE'] as const) {
43
+ assert.deepEqual(
44
+ resolveStartupSafeMode({ kind, lockState: 'FREE', currentlyBlocked: false }),
45
+ { blocked: true, clearStaleMarker: false },
46
+ kind + ' 必须阻断',
47
+ );
48
+ }
49
+ });
50
+
51
+ test('issue #87:fail-closed 结论(FAIL_CLOSED_STARTUP)经同一判据仍然是阻断', () => {
52
+ const verdict = resolveStartupSafeMode({
53
+ kind: FAIL_CLOSED_STARTUP.state.kind,
54
+ lockState: 'FREE',
55
+ currentlyBlocked: false,
56
+ });
57
+ assert.equal(verdict.blocked, true, 'inspect 抛错时绝不因 NORMAL 之外的分支被解除');
58
+ assert.equal(verdict.clearStaleMarker, false);
59
+ });
60
+
61
+ test('issue #87:classifyStartup 仍是分类唯一来源(形状自检,防止第二份分类漂移)', () => {
62
+ const classifier = classifyStartup({
63
+ store: {} as never,
64
+ hooks: {} as never,
65
+ env: { environmentFingerprint: 'fp', isLiveOwner: async () => false },
66
+ lockState: 'FREE',
67
+ });
68
+ assert.equal(typeof classifier.classify, 'function');
69
+ });
@@ -58,6 +58,33 @@ export function classifyStartup(opts: {
58
58
  };
59
59
  }
60
60
 
61
+ /**
62
+ * 启动分类结束后如何处置 SAFE MODE(issue #87,单一判据,便于单测;宿主 apply() 只消费结论)。
63
+ *
64
+ * 为什么需要它:`probeSafeModeSync()` 读的是**上一轮**留下的 durable 标记,而分类结论才是本轮权威。
65
+ * 分类判 `NORMAL` ⇒ 没有任何未决 journal 能解释那个标记 ⇒ 它是陈旧标记,必须**同时**复位内存标志与
66
+ * durable 标记 —— 只复位内存会让本会话的写操作继续被 423 挡着(界面却说「无待处理事项」),只复位标记
67
+ * 又会在下一次 probe 时重新置起(标记跨重启生效)。
68
+ *
69
+ * 为什么要求 `lockState === 'FREE'`:`LOCKED` 且无未决 journal 时分类同样返回 NORMAL,但那只说明
70
+ * **另一个存活实例**正在持有环境锁 —— 此刻清标记等于替别人解除保护。**只有确认没有活锁者才清。**
71
+ *
72
+ * 为什么要求 `currentlyBlocked`(即 probe 读到 durable 标记确实是 'blocked'):标记为 `'unknown'`
73
+ * (存在但读不出/布局不可信)时不在这里清 —— 那属于无法判定,既不假装已解除,也不假装没看见;
74
+ * 它本会话不阻断的语义由 `probeSafeModeSync` 的既有约定承担。
75
+ */
76
+ export function resolveStartupSafeMode(input: {
77
+ kind: StartupRecoveryState['kind']
78
+ lockState: 'LOCKED' | 'STALE_LOCK_DETECTED' | 'UNKNOWN_STATE' | 'FREE'
79
+ /** 本轮 probe 之前的内存标志(probeSafeModeSync 的结论) */
80
+ currentlyBlocked: boolean
81
+ }): { blocked: boolean; clearStaleMarker: boolean } {
82
+ const staleMarker = input.kind === 'NORMAL' && input.lockState === 'FREE' && input.currentlyBlocked
83
+ if (staleMarker) return { blocked: false, clearStaleMarker: true }
84
+ const forceBlocked = input.kind === 'RECOVERY_REQUIRED' || input.kind === 'NEEDS_ATTENTION' || input.kind === 'UNKNOWN_STATE'
85
+ return { blocked: input.currentlyBlocked || forceBlocked, clearStaleMarker: false }
86
+ }
87
+
61
88
  export interface StartupSchedulers {
62
89
  start(): void;
63
90
  }
@@ -19,6 +19,7 @@ import {
19
19
  isTerminalState, generateOperationId, redactJournalText, type OperationJournal,
20
20
  } from './journal.ts';
21
21
  import type { MutationLockContext, MutationLockPort } from '../utils/env-lock.ts';
22
+ import { engineMsg } from './messages-core.ts';
22
23
 
23
24
  // ---------- 类型 ----------
24
25
 
@@ -109,9 +110,9 @@ export class MutationTransactionCoordinator {
109
110
  : 'needs-attention';
110
111
  if (recovered === 'recovered' || recovered === 'rolled-back') {
111
112
  // 残留已收敛 → 可继续;但本函数语义:先返回,调用方重试 run
112
- return { outcome: 'RECOVERY_REQUIRED', operationId: activeCheck.residue, reason: `残留 active transaction ${activeCheck.residue} 待处理,请重试` };
113
+ return { outcome: 'RECOVERY_REQUIRED', operationId: activeCheck.residue, reason: engineMsg('core.txn.residuePending', { residue: activeCheck.residue }) };
113
114
  }
114
- return { outcome: 'RECOVERY_REQUIRED', operationId: activeCheck.residue, reason: `存在 unresolved active transaction ${activeCheck.residue}` };
115
+ return { outcome: 'RECOVERY_REQUIRED', operationId: activeCheck.residue, reason: engineMsg('core.txn.unresolvedActive', { residue: activeCheck.residue }) };
115
116
  }
116
117
 
117
118
  // 3. 创建 journal
@@ -227,7 +228,7 @@ export class MutationTransactionCoordinator {
227
228
  if (current !== null && isTerminalState(current.state)) {
228
229
  // 已 terminal(如 COMMITTED 后 move 抛错 → 保持 terminal,不回滚)
229
230
  const opId = operationId;
230
- return { outcome: 'NEEDS_ATTENTION', operationId: opId, reason: `tail/commit 后异常:${msg}`, snapshotId };
231
+ return { outcome: 'NEEDS_ATTENTION', operationId: opId, reason: engineMsg('core.txn.tailCommitFailed', { message: msg }), snapshotId };
231
232
  }
232
233
  return await this.finalizeAttention(operationId, msg, snapshotId, context);
233
234
  }
package/src/core/types.ts CHANGED
@@ -140,6 +140,13 @@ export interface ExportUnit {
140
140
  * 包里 41 条)。非 sessions 分区 / 非子代理会话一律不设该字段(不猜)。
141
141
  */
142
142
  parentSessionId?: string;
143
+ /**
144
+ * 空白会话(**从未提问过**;只有 sessions 分区的单元可能有值)。
145
+ *
146
+ * 用途:DSH 自己给这类会话的标题就是本地化的「新会话」(`session.new`),没有标题可显示。
147
+ * 只下发**事实**,不下发文案 —— 界面按当前语言显示(与 DSH 同口径),绝不用 uuid 冒充标题。
148
+ */
149
+ blank?: boolean;
143
150
  }
144
151
 
145
152
  export interface ValidationResult {
@@ -660,6 +667,8 @@ export interface PlanItem {
660
667
  label?: string;
661
668
  /** 单元的**二级分组名**(可选,同 `/export-preview` 的 `ExportUnit.group` 语义;纯展示)。 */
662
669
  group?: string;
670
+ /** 空白会话(从未提问;同 `ExportUnit.blank`,只下发事实、文案由界面本地化)。 */
671
+ blank?: boolean;
663
672
  /** 本项对应的**工作区记录拥有的会话 id**(工作区计划项专用;issue #45 勾选联动)。 */
664
673
  sessionIds?: string[];
665
674
  /** 本工作区计划项 **path 的 cwd 目录键**(`projectKeyOf(path)`;issue #45 勾选联动第二判据)。 */
@@ -941,11 +950,19 @@ export interface SnapshotManifest {
941
950
 
942
951
  /** 宿主整文件备份登记(M1):导入前对 $DSH_HOME 关键文件的整文件快照。
943
952
  * relPath 相对 $DSH_HOME(如 settings.yaml / cordis.patch.yml / profiles/<p>/cordis.patch.yml),
944
- * blobPath 为快照内 blob 路径(existed=false 时为空串,表示该文件当时不存在)。 */
953
+ * blobPath 为快照内 blob 路径(existed=false 时为空串,表示该文件当时确实不存在)。 */
945
954
  export interface HostFileBackup {
946
955
  relPath: string;
947
956
  blobPath: string;
948
957
  existed: boolean;
958
+ /**
959
+ * 探测到**存在**、但这一次读不出来(EACCES / EBUSY / IO 错):existed 仍为 true、blobPath 为空串。
960
+ *
961
+ * 为什么必须与 existed:false 分开:恢复侧「快照里没有、现在有 → 删掉」这条动作是按 existed 判定的,
962
+ * 把「没读到」写成「本来不存在」会让恢复**删掉用户真实的文件**(Windows 上 settings.yaml 被占用即触发)。
963
+ * 存在但读不到的条目一律只跳过并如实告警,绝不参与删除判定(与「读不到 ≠ 没有」同一条口径)。
964
+ */
965
+ unreadable?: boolean;
949
966
  }
950
967
 
951
968
  export interface Snapshot {
@@ -4,6 +4,7 @@
4
4
  import { validateSectionData } from '../schema/config.ts';
5
5
  import { canImport, describeVersion } from '../schema/versions.ts';
6
6
  import type { SectionId } from '../schema/types.ts';
7
+ import { engineMsg } from './messages-core.ts';
7
8
  import type { CompatibilityInput, CompatibilityReason, CompatibilityScore, ValidationResult } from './types.ts';
8
9
 
9
10
  export { validateManifest } from '../schema/manifest.ts';
@@ -102,10 +103,10 @@ export function computeCompatibility(input: CompatibilityInput): CompatibilitySc
102
103
  /** 兼容性得分的可读描述(UI 报告用) */
103
104
  export function describeCompatibility(score: CompatibilityScore): string {
104
105
  switch (score) {
105
- case 'excellent': return 'Excellent — 完美兼容(同平台、无缺失、schema 支持)';
106
- case 'good': return 'Good — 兼容(旧版备份导入新版 DSH)';
107
- case 'partial': return 'Partial — 部分兼容(跨平台/分区缺失/版本超前,需人工确认)';
108
- case 'unsupported': return 'Unsupported — 不受支持(schema 超出范围)';
106
+ case 'excellent': return engineMsg('core.compat.excellent');
107
+ case 'good': return engineMsg('core.compat.good');
108
+ case 'partial': return engineMsg('core.compat.partial');
109
+ case 'unsupported': return engineMsg('core.compat.unsupported');
109
110
  }
110
111
  }
111
112
 
@@ -24,6 +24,7 @@
24
24
  *
25
25
  * 本模块为纯函数(fs 经 node:fs + HostContext 门面注入),可独立测试。
26
26
  */
27
+ import { engineMsg } from './messages-core.ts';
27
28
  import fs from 'node:fs/promises';
28
29
  import path from 'node:path';
29
30
  import { isDeepStrictEqual } from 'node:util';
@@ -58,7 +59,7 @@ function toRelPath(homeDir: string, abs: string): string {
58
59
  /** 快照目录内 blob 绝对路径(防 snapshot.json 里伪造 ../ 越界读) */
59
60
  function blobAbs(snapshotDir: string, blobPath: string): string {
60
61
  const abs = path.resolve(snapshotDir, blobPath);
61
- if (!isWithinHome(snapshotDir, abs)) throw new Error(`blob 路径越界: ${blobPath}`);
62
+ if (!isWithinHome(snapshotDir, abs)) throw new Error(engineMsg('core.verifyRecovery.blobPathEscaped', { path: blobPath }));
62
63
  return abs;
63
64
  }
64
65
 
@@ -99,12 +100,12 @@ export async function verifyRecovery(
99
100
  if (val.verdict !== 'TRUSTED_OPERATION_SNAPSHOT') {
100
101
  return {
101
102
  verdict: 'VERIFICATION_ERROR',
102
- details: [`快照重验失败: ${val.verdict}${val.reason ? ` — ${val.reason}` : ''}`],
103
+ details: [engineMsg('core.verifyRecovery.revalidateFailed', { verdict: val.verdict }) + (val.reason ? ` — ${val.reason}` : '')],
103
104
  manualHints: [],
104
105
  at,
105
106
  };
106
107
  }
107
- details.push('快照重验通过(TRUSTED_OPERATION_SNAPSHOT)');
108
+ details.push(engineMsg('core.verifyRecovery.revalidatePassed'));
108
109
 
109
110
  // 2) 从磁盘重读 snapshot.json 作为权威(不信任传入的 snapshot 对象,防 execute→verify 间替换)。
110
111
  let diskSnapshot: Snapshot;
@@ -112,17 +113,17 @@ export async function verifyRecovery(
112
113
  const raw = await fs.readFile(path.join(snapshotDir, 'snapshot.json'), 'utf8');
113
114
  diskSnapshot = JSON.parse(raw) as Snapshot;
114
115
  } catch (err) {
115
- return { verdict: 'VERIFICATION_ERROR', details: [...details, `snapshot.json 重读失败: ${err instanceof Error ? err.message : String(err)}`], manualHints: [], at };
116
+ return { verdict: 'VERIFICATION_ERROR', details: [...details, engineMsg('core.verifyRecovery.snapshotJsonRereadFailed', { reason: err instanceof Error ? err.message : String(err) })], manualHints: [], at };
116
117
  }
117
118
 
118
119
  // 3) environment binding(validateSnapshotForRestore 已查,此处双保险)
119
120
  if (diskSnapshot.environmentFingerprint && diskSnapshot.environmentFingerprint !== opts.environmentFingerprint) {
120
- return { verdict: 'VERIFICATION_ERROR', details: [...details, 'environmentFingerprint 不匹配(WRONG_ENVIRONMENT)'], manualHints: [], at };
121
+ return { verdict: 'VERIFICATION_ERROR', details: [...details, engineMsg('core.verifyRecovery.envMismatch')], manualHints: [], at };
121
122
  }
122
123
 
123
124
  // 3b) operation binding(journal.operationId ↔ snapshot.operationId 双向一致;WRONG_OPERATION 拒绝)
124
125
  if (opts.expectedOperationId !== undefined && diskSnapshot.operationId !== opts.expectedOperationId) {
125
- return { verdict: 'VERIFICATION_ERROR', details: [...details, `operationId 不匹配(WRONG_OPERATION): snapshot=${diskSnapshot.operationId ?? '(无)'} expected=${opts.expectedOperationId}`], manualHints: [], at };
126
+ return { verdict: 'VERIFICATION_ERROR', details: [...details, engineMsg('core.verifyRecovery.operationMismatch', { snapshot: diskSnapshot.operationId ?? engineMsg('core.verifyRecovery.none'), expected: opts.expectedOperationId ?? '' })], manualHints: [], at };
126
127
  }
127
128
 
128
129
  let mismatch = false;
@@ -135,12 +136,12 @@ export async function verifyRecovery(
135
136
  if (backup.existed) {
136
137
  if (backup.blobPath === '') {
137
138
  unverifiable = true;
138
- details.push(`host file 无 blob 信息: ${rel}`);
139
+ details.push(engineMsg('core.verifyRecovery.hostFileNoBlob', { rel }));
139
140
  continue;
140
141
  }
141
142
  if (!(await ctx.fs.exists(rel))) {
142
143
  mismatch = true;
143
- details.push(`host file 缺失: ${rel}`);
144
+ details.push(engineMsg('core.verifyRecovery.hostFileMissing', { rel }));
144
145
  continue;
145
146
  }
146
147
  let blob: Uint8Array;
@@ -148,23 +149,23 @@ export async function verifyRecovery(
148
149
  blob = await fs.readFile(blobAbs(snapshotDir, backup.blobPath));
149
150
  } catch {
150
151
  unverifiable = true;
151
- details.push(`host file blob 读取失败: ${rel}`);
152
+ details.push(engineMsg('core.verifyRecovery.hostFileBlobReadFailed', { rel }));
152
153
  continue;
153
154
  }
154
155
  const current = await ctx.fs.readFile(rel);
155
156
  if (!bufEq(current, blob)) {
156
157
  mismatch = true;
157
- details.push(`host file 内容不匹配: ${rel}`);
158
+ details.push(engineMsg('core.verifyRecovery.hostFileContentMismatch', { rel }));
158
159
  } else {
159
- details.push(`host file 匹配: ${rel}`);
160
+ details.push(engineMsg('core.verifyRecovery.hostFileMatch', { rel }));
160
161
  }
161
162
  } else {
162
163
  // snapshot expects absent → target 必须不存在(删除动作失败 → 残留被检测)
163
164
  if (await ctx.fs.exists(rel)) {
164
165
  mismatch = true;
165
- details.push(`应删除目标仍存在: ${rel}`);
166
+ details.push(engineMsg('core.verifyRecovery.hostFileStillPresent', { rel }));
166
167
  } else {
167
- details.push(`删除目标已不存在: ${rel}`);
168
+ details.push(engineMsg('core.verifyRecovery.hostFileAlreadyGone', { rel }));
168
169
  }
169
170
  }
170
171
  }
@@ -175,13 +176,13 @@ export async function verifyRecovery(
175
176
  const rel = toRelPath(ctx.homeDir, resolveFileAbs(ctx.homeDir, entry.adapter, entry.ref));
176
177
  if (rel === '') {
177
178
  unverifiable = true;
178
- details.push(`file 路径越界: ${entry.adapter}:${entry.ref}`);
179
+ details.push(engineMsg('core.verifyRecovery.filePathEscaped', { ref: `${entry.adapter}:${entry.ref}` }));
179
180
  continue;
180
181
  }
181
182
  if (entry.existed && entry.copiedTo) {
182
183
  if (!(await ctx.fs.exists(rel))) {
183
184
  mismatch = true;
184
- details.push(`file 缺失: ${entry.adapter}:${entry.ref}`);
185
+ details.push(engineMsg('core.verifyRecovery.fileMissing', { ref: `${entry.adapter}:${entry.ref}` }));
185
186
  continue;
186
187
  }
187
188
  let blob: Uint8Array;
@@ -189,22 +190,22 @@ export async function verifyRecovery(
189
190
  blob = await fs.readFile(blobAbs(snapshotDir, entry.copiedTo));
190
191
  } catch {
191
192
  unverifiable = true;
192
- details.push(`file blob 读取失败: ${entry.adapter}:${entry.ref}`);
193
+ details.push(engineMsg('core.verifyRecovery.fileBlobReadFailed', { ref: `${entry.adapter}:${entry.ref}` }));
193
194
  continue;
194
195
  }
195
196
  const current = await ctx.fs.readFile(rel);
196
197
  if (!bufEq(current, blob)) {
197
198
  mismatch = true;
198
- details.push(`file 内容不匹配: ${entry.adapter}:${entry.ref}`);
199
+ details.push(engineMsg('core.verifyRecovery.fileContentMismatch', { ref: `${entry.adapter}:${entry.ref}` }));
199
200
  } else {
200
- details.push(`file 匹配: ${entry.adapter}:${entry.ref}`);
201
+ details.push(engineMsg('core.verifyRecovery.fileMatch', { ref: `${entry.adapter}:${entry.ref}` }));
201
202
  }
202
203
  } else if (!entry.existed) {
203
204
  if (await ctx.fs.exists(rel)) {
204
205
  mismatch = true;
205
- details.push(`应删除 file 目标仍存在: ${entry.adapter}:${entry.ref}`);
206
+ details.push(engineMsg('core.verifyRecovery.fileStillPresent', { ref: `${entry.adapter}:${entry.ref}` }));
206
207
  } else {
207
- details.push(`删除 file 目标已不存在: ${entry.adapter}:${entry.ref}`);
208
+ details.push(engineMsg('core.verifyRecovery.fileAlreadyGone', { ref: `${entry.adapter}:${entry.ref}` }));
208
209
  }
209
210
  }
210
211
  }
@@ -214,8 +215,8 @@ export async function verifyRecovery(
214
215
  const beforePlugins = diskSnapshot.beforePlugins;
215
216
  if (beforePlugins === undefined) {
216
217
  unverifiable = true;
217
- details.push('快照无 beforePlugins 基线,插件状态不可验证');
218
- manualHints.push('插件状态无法自动验证(快照无基线),请人工确认已装插件');
218
+ details.push(engineMsg('core.verifyRecovery.pluginsNoBaseline'));
219
+ manualHints.push(engineMsg('core.verifyRecovery.pluginsNoBaselineHint'));
219
220
  } else {
220
221
  try {
221
222
  const current = await ctx.plugins.listInstalled();
@@ -223,14 +224,14 @@ export async function verifyRecovery(
223
224
  const added = current.filter((p) => !beforeNames.has(p.name));
224
225
  if (added.length > 0) {
225
226
  mismatch = true;
226
- details.push(`导入期间新增插件未移除: ${added.map((p) => p.name).join(', ')}`);
227
+ details.push(engineMsg('core.verifyRecovery.pluginsAdded', { names: added.map((p) => p.name).join(', ') }));
227
228
  } else {
228
- details.push('插件状态匹配(无新增插件)');
229
+ details.push(engineMsg('core.verifyRecovery.pluginsMatch'));
229
230
  }
230
231
  } catch {
231
232
  unverifiable = true;
232
- details.push('插件状态无法验证(listInstalled 失败)');
233
- manualHints.push('插件状态无法自动验证(listInstalled 失败),请人工确认已装插件');
233
+ details.push(engineMsg('core.verifyRecovery.pluginsVerifyFailed'));
234
+ manualHints.push(engineMsg('core.verifyRecovery.pluginsVerifyFailedHint'));
234
235
  }
235
236
  }
236
237
 
@@ -241,14 +242,14 @@ export async function verifyRecovery(
241
242
  const current = await ctx.settings.describe(entry.ref);
242
243
  if (!isDeepStrictEqual(current.value, entry.before)) {
243
244
  mismatch = true;
244
- details.push(`settings 不匹配: ${entry.ref}`);
245
+ details.push(engineMsg('core.verifyRecovery.settingsMismatch', { ref: entry.ref }));
245
246
  } else {
246
- details.push(`settings 匹配: ${entry.ref}`);
247
+ details.push(engineMsg('core.verifyRecovery.settingsMatch', { ref: entry.ref }));
247
248
  }
248
249
  } catch {
249
250
  unverifiable = true;
250
- details.push(`settings 无法验证: ${entry.ref}`);
251
- manualHints.push(`settings 无法自动验证(${entry.ref}),请人工确认`);
251
+ details.push(engineMsg('core.verifyRecovery.settingsVerifyFailed', { ref: entry.ref }));
252
+ manualHints.push(engineMsg('core.verifyRecovery.settingsVerifyFailedHint', { ref: entry.ref }));
252
253
  }
253
254
  }
254
255
 
@@ -19,6 +19,7 @@ import { writeZip } from '../utils/zip.ts';
19
19
  import type { ZipWriteEntry } from '../utils/zip.ts';
20
20
  import type { Platform, SectionId } from '../schema/types.ts';
21
21
  import type { ForeignImportResult, ForeignSkip } from './types.ts';
22
+ import { engineMsg } from './messages.ts';
22
23
 
23
24
  export interface ForeignBundleOptions {
24
25
  result: ForeignImportResult;
@@ -57,7 +58,7 @@ export async function writeForeignBundle(opts: ForeignBundleOptions): Promise<Fo
57
58
  if (isFileSection(s.sectionId)) {
58
59
  const prefix = SECTION_FILE_PREFIXES[s.sectionId];
59
60
  if (prefix === undefined) {
60
- throw new Error('外部导入:文件类分区缺少 ZIP 前缀 ' + s.sectionId);
61
+ throw new Error(engineMsg('foreign.bundleFileSectionPrefixMissing', { section: s.sectionId }));
61
62
  }
62
63
  const files = s.files ?? [];
63
64
  if (files.length === 0) continue;
@@ -67,7 +68,7 @@ export async function writeForeignBundle(opts: ForeignBundleOptions): Promise<Fo
67
68
  } else {
68
69
  const jsonPath = SECTION_JSON_PATHS[s.sectionId];
69
70
  if (jsonPath === undefined) {
70
- throw new Error('外部导入:JSON 类分区缺少 ZIP 路径 ' + s.sectionId);
71
+ throw new Error(engineMsg('foreign.bundleJsonSectionPathMissing', { section: s.sectionId }));
71
72
  }
72
73
  if (s.data === undefined) continue;
73
74
  flags[s.sectionId] = true;
@@ -19,6 +19,7 @@
19
19
  import { envValue, normalizePlatform } from './platform-paths.ts';
20
20
  import { draftFromTranscript, sessionSourceOf } from './session-source.ts';
21
21
  import type { RootProbeOptions } from './session-source.ts';
22
+ import { engineMsg } from './messages.ts';
22
23
  import { FOREIGN_TRUTH_TABLES } from './truth-table.ts';
23
24
  import type { ForeignTruthTableEntry } from './truth-table.ts';
24
25
  import type { ForeignSource } from './registry.ts';
@@ -31,7 +32,7 @@ export const CONTINUE_PROVIDER = 'continue';
31
32
 
32
33
  function truthEntryOf(id: ForeignSourceId): ForeignTruthTableEntry {
33
34
  const found = FOREIGN_TRUTH_TABLES.find((entry) => entry.id === id);
34
- if (found === undefined) throw new Error('真值表缺少来源定义: ' + id);
35
+ if (found === undefined) throw new Error(engineMsg('foreign.truthTableMissingSource', { id }));
35
36
  return found;
36
37
  }
37
38
 
@@ -18,6 +18,7 @@ import { draftFromTranscript } from './session-source.ts';
18
18
  import type { RootProbeOptions, SessionDraft } from './session-source.ts';
19
19
  import { sessionSourceOf } from './session-source.ts';
20
20
  import type { ForeignSource } from './registry.ts';
21
+ import { engineMsg } from './messages.ts';
21
22
  import { FOREIGN_TRUTH_TABLES } from './truth-table.ts';
22
23
  import type { ForeignTruthTableEntry } from './truth-table.ts';
23
24
  import type { ForeignEvidenceKind, ForeignSourceId } from './types.ts';
@@ -27,7 +28,7 @@ import { crushProjectDbPath, crushRegistryPath, readCrush } from './read-crush.t
27
28
  /** 真值表条目(找不到即装配期炸:宁可炸,也不让来源悄悄少一份取证声明 —— 与 registry.ts 同口径) */
28
29
  function truthEntryOf(id: ForeignSourceId): ForeignTruthTableEntry {
29
30
  const found = FOREIGN_TRUTH_TABLES.find((entry) => entry.id === id);
30
- if (found === undefined) throw new Error('真值表缺少来源定义: ' + id);
31
+ if (found === undefined) throw new Error(engineMsg('foreign.truthTableMissingSource', { id }));
31
32
  return found;
32
33
  }
33
34
 
@@ -244,6 +244,8 @@ test('C 来源形状:共享模块清单是封闭集合(新模块必须显式
244
244
  const shared = modules.filter((m) => !isSourceShaped(m));
245
245
  const expected = [
246
246
  'bundle.ts', 'claude-sessions.ts', 'kernel.ts',
247
+ /* t6 新增:本域追加消息目录(零 source 形状;中文只在这里,装配期抛错的文案经 engineMsg 取)。 */
248
+ 'messages.ts',
247
249
  /* 档 B 新增的 5 个共享地基(零 source 形状,但也不是来源模块): */
248
250
  'platform-paths.ts', 'registry.ts', 'session-ir.ts', 'session-read.ts', 'session-source.ts',
249
251
  'source-modules.ts', 'sqlite.ts', 'truth-table.ts', 'types.ts',