dsh-config-manager 0.1.69 → 0.1.71

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 (719) hide show
  1. package/README.md +59 -18
  2. package/README.zh-CN.md +29 -9
  3. package/lib/adapters/credentials.js +6 -3
  4. package/lib/adapters/file-collection.d.ts +21 -1
  5. package/lib/adapters/file-collection.js +32 -8
  6. package/lib/adapters/mcp.d.ts +12 -2
  7. package/lib/adapters/mcp.js +30 -13
  8. package/lib/adapters/plugin-files.d.ts +8 -0
  9. package/lib/adapters/plugin-files.js +36 -4
  10. package/lib/adapters/plugins.d.ts +28 -2
  11. package/lib/adapters/plugins.js +204 -55
  12. package/lib/adapters/prompts.d.ts +12 -2
  13. package/lib/adapters/prompts.js +34 -19
  14. package/lib/adapters/providers.js +5 -4
  15. package/lib/adapters/self.d.ts +20 -0
  16. package/lib/adapters/self.js +73 -0
  17. package/lib/adapters/sessions.d.ts +8 -0
  18. package/lib/adapters/sessions.js +47 -1
  19. package/lib/adapters/settings.d.ts +21 -3
  20. package/lib/adapters/settings.js +44 -7
  21. package/lib/adapters/skills.d.ts +36 -4
  22. package/lib/adapters/skills.js +222 -3
  23. package/lib/adapters/test-helpers.d.ts +31 -2
  24. package/lib/adapters/test-helpers.js +42 -5
  25. package/lib/adapters/workspaces.js +97 -5
  26. package/lib/cli/actions.d.ts +50 -3
  27. package/lib/cli/actions.js +103 -11
  28. package/lib/cli/help.d.ts +65 -0
  29. package/lib/cli/help.js +517 -0
  30. package/lib/cli/import-source.d.ts +32 -0
  31. package/lib/cli/import-source.js +178 -0
  32. package/lib/cli/index.d.ts +18 -2
  33. package/lib/cli/index.js +118 -90
  34. package/lib/cli/web/client-script.d.ts +8 -0
  35. package/lib/cli/web/client-script.js +11 -0
  36. package/lib/cli/web/http.d.ts +5 -0
  37. package/lib/cli/web/http.js +2 -1
  38. package/lib/cli/web/page.d.ts +16 -6
  39. package/lib/cli/web/page.js +102 -25
  40. package/lib/cli/web/routes.js +24 -5
  41. package/lib/cli/web/server.js +23 -2
  42. package/lib/client.d.ts +1019 -36
  43. package/lib/client.js +5522 -1564
  44. package/lib/core/analyzer.d.ts +15 -1
  45. package/lib/core/analyzer.js +114 -13
  46. package/lib/core/backup-plan.js +17 -1
  47. package/lib/core/backup.d.ts +8 -0
  48. package/lib/core/backup.js +28 -6
  49. package/lib/core/cache-cleaner.d.ts +10 -0
  50. package/lib/core/cache-cleaner.js +63 -22
  51. package/lib/core/checkpoint.d.ts +362 -0
  52. package/lib/core/checkpoint.js +1309 -0
  53. package/lib/core/journal.d.ts +37 -0
  54. package/lib/core/journal.js +69 -0
  55. package/lib/core/messages.d.ts +13 -0
  56. package/lib/core/messages.js +26 -0
  57. package/lib/core/model-tools.js +3 -0
  58. package/lib/core/patch-layers.d.ts +83 -0
  59. package/lib/core/patch-layers.js +110 -0
  60. package/lib/core/restore.d.ts +21 -0
  61. package/lib/core/restore.js +37 -3
  62. package/lib/core/rollback.js +6 -3
  63. package/lib/core/self-update.d.ts +52 -0
  64. package/lib/core/self-update.js +70 -0
  65. package/lib/core/session-health.d.ts +29 -5
  66. package/lib/core/session-health.js +28 -3
  67. package/lib/core/types.d.ts +126 -0
  68. package/lib/foreign/antigravity.d.ts +28 -0
  69. package/lib/foreign/antigravity.js +157 -0
  70. package/lib/foreign/bundle.d.ts +25 -0
  71. package/lib/foreign/bundle.js +79 -0
  72. package/lib/foreign/chatgpt.d.ts +6 -0
  73. package/lib/foreign/chatgpt.js +25 -0
  74. package/lib/foreign/claude-code.d.ts +3 -0
  75. package/lib/foreign/claude-code.js +85 -0
  76. package/lib/foreign/claude-sessions.d.ts +108 -0
  77. package/lib/foreign/claude-sessions.js +299 -0
  78. package/lib/foreign/cline.d.ts +6 -0
  79. package/lib/foreign/cline.js +27 -0
  80. package/lib/foreign/codex.d.ts +83 -0
  81. package/lib/foreign/codex.js +348 -0
  82. package/lib/foreign/continue.d.ts +9 -0
  83. package/lib/foreign/continue.js +65 -0
  84. package/lib/foreign/copilot.d.ts +39 -0
  85. package/lib/foreign/copilot.js +95 -0
  86. package/lib/foreign/crush.d.ts +11 -0
  87. package/lib/foreign/crush.js +57 -0
  88. package/lib/foreign/cursor.d.ts +87 -0
  89. package/lib/foreign/cursor.js +171 -0
  90. package/lib/foreign/dsh.d.ts +30 -0
  91. package/lib/foreign/dsh.js +101 -0
  92. package/lib/foreign/dsh4.d.ts +3 -0
  93. package/lib/foreign/dsh4.js +13 -0
  94. package/lib/foreign/gemini.d.ts +7 -0
  95. package/lib/foreign/gemini.js +44 -0
  96. package/lib/foreign/goose.d.ts +11 -0
  97. package/lib/foreign/goose.js +53 -0
  98. package/lib/foreign/grokbuild.d.ts +8 -0
  99. package/lib/foreign/grokbuild.js +25 -0
  100. package/lib/foreign/hermes.d.ts +2 -0
  101. package/lib/foreign/hermes.js +78 -0
  102. package/lib/foreign/kernel.d.ts +132 -0
  103. package/lib/foreign/kernel.js +569 -0
  104. package/lib/foreign/kilocode.d.ts +11 -0
  105. package/lib/foreign/kilocode.js +51 -0
  106. package/lib/foreign/kimi.d.ts +7 -0
  107. package/lib/foreign/kimi.js +47 -0
  108. package/lib/foreign/mimocode.d.ts +11 -0
  109. package/lib/foreign/mimocode.js +51 -0
  110. package/lib/foreign/openclaw.d.ts +7 -0
  111. package/lib/foreign/openclaw.js +46 -0
  112. package/lib/foreign/opencode.d.ts +11 -0
  113. package/lib/foreign/opencode.js +52 -0
  114. package/lib/foreign/pi.d.ts +7 -0
  115. package/lib/foreign/pi.js +48 -0
  116. package/lib/foreign/platform-paths.d.ts +66 -0
  117. package/lib/foreign/platform-paths.js +141 -0
  118. package/lib/foreign/qoder.d.ts +10 -0
  119. package/lib/foreign/qoder.js +49 -0
  120. package/lib/foreign/qwen.d.ts +7 -0
  121. package/lib/foreign/qwen.js +45 -0
  122. package/lib/foreign/read-antigravity.d.ts +57 -0
  123. package/lib/foreign/read-antigravity.js +504 -0
  124. package/lib/foreign/read-chatgpt.d.ts +20 -0
  125. package/lib/foreign/read-chatgpt.js +509 -0
  126. package/lib/foreign/read-claude-code.d.ts +24 -0
  127. package/lib/foreign/read-claude-code.js +234 -0
  128. package/lib/foreign/read-cline.d.ts +53 -0
  129. package/lib/foreign/read-cline.js +479 -0
  130. package/lib/foreign/read-codex.d.ts +75 -0
  131. package/lib/foreign/read-codex.js +842 -0
  132. package/lib/foreign/read-continue.d.ts +87 -0
  133. package/lib/foreign/read-continue.js +489 -0
  134. package/lib/foreign/read-copilot.d.ts +42 -0
  135. package/lib/foreign/read-copilot.js +267 -0
  136. package/lib/foreign/read-crush.d.ts +54 -0
  137. package/lib/foreign/read-crush.js +478 -0
  138. package/lib/foreign/read-cursor.d.ts +141 -0
  139. package/lib/foreign/read-cursor.js +705 -0
  140. package/lib/foreign/read-dsh.d.ts +43 -0
  141. package/lib/foreign/read-dsh.js +205 -0
  142. package/lib/foreign/read-dsh4.d.ts +14 -0
  143. package/lib/foreign/read-dsh4.js +27 -0
  144. package/lib/foreign/read-gemini.d.ts +55 -0
  145. package/lib/foreign/read-gemini.js +273 -0
  146. package/lib/foreign/read-goose.d.ts +25 -0
  147. package/lib/foreign/read-goose.js +322 -0
  148. package/lib/foreign/read-grokbuild.d.ts +34 -0
  149. package/lib/foreign/read-grokbuild.js +455 -0
  150. package/lib/foreign/read-hermes.d.ts +74 -0
  151. package/lib/foreign/read-hermes.js +690 -0
  152. package/lib/foreign/read-kilocode.d.ts +17 -0
  153. package/lib/foreign/read-kilocode.js +46 -0
  154. package/lib/foreign/read-kimi.d.ts +63 -0
  155. package/lib/foreign/read-kimi.js +719 -0
  156. package/lib/foreign/read-mimocode.d.ts +12 -0
  157. package/lib/foreign/read-mimocode.js +56 -0
  158. package/lib/foreign/read-openclaw.d.ts +51 -0
  159. package/lib/foreign/read-openclaw.js +445 -0
  160. package/lib/foreign/read-opencode.d.ts +150 -0
  161. package/lib/foreign/read-opencode.js +896 -0
  162. package/lib/foreign/read-pi.d.ts +56 -0
  163. package/lib/foreign/read-pi.js +411 -0
  164. package/lib/foreign/read-qoder.d.ts +54 -0
  165. package/lib/foreign/read-qoder.js +317 -0
  166. package/lib/foreign/read-qwen.d.ts +40 -0
  167. package/lib/foreign/read-qwen.js +326 -0
  168. package/lib/foreign/read-reasonix.d.ts +66 -0
  169. package/lib/foreign/read-reasonix.js +537 -0
  170. package/lib/foreign/read-teleagent.d.ts +15 -0
  171. package/lib/foreign/read-teleagent.js +68 -0
  172. package/lib/foreign/read-trae.d.ts +67 -0
  173. package/lib/foreign/read-trae.js +492 -0
  174. package/lib/foreign/read-vibe.d.ts +28 -0
  175. package/lib/foreign/read-vibe.js +364 -0
  176. package/lib/foreign/read-workbuddy.d.ts +49 -0
  177. package/lib/foreign/read-workbuddy.js +346 -0
  178. package/lib/foreign/read-zcode.d.ts +12 -0
  179. package/lib/foreign/read-zcode.js +38 -0
  180. package/lib/foreign/read-zed.d.ts +30 -0
  181. package/lib/foreign/read-zed.js +473 -0
  182. package/lib/foreign/reasonix.d.ts +6 -0
  183. package/lib/foreign/reasonix.js +20 -0
  184. package/lib/foreign/registry.d.ts +196 -0
  185. package/lib/foreign/registry.js +573 -0
  186. package/lib/foreign/session-ir.d.ts +186 -0
  187. package/lib/foreign/session-ir.js +427 -0
  188. package/lib/foreign/session-read.d.ts +102 -0
  189. package/lib/foreign/session-read.js +254 -0
  190. package/lib/foreign/session-source.d.ts +216 -0
  191. package/lib/foreign/session-source.js +453 -0
  192. package/lib/foreign/source-modules.d.ts +45 -0
  193. package/lib/foreign/source-modules.js +52 -0
  194. package/lib/foreign/sqlite.d.ts +61 -0
  195. package/lib/foreign/sqlite.js +404 -0
  196. package/lib/foreign/teleagent.d.ts +11 -0
  197. package/lib/foreign/teleagent.js +53 -0
  198. package/lib/foreign/trae.d.ts +6 -0
  199. package/lib/foreign/trae.js +20 -0
  200. package/lib/foreign/truth-table.d.ts +62 -0
  201. package/lib/foreign/truth-table.js +333 -0
  202. package/lib/foreign/types.d.ts +272 -0
  203. package/lib/foreign/types.js +2 -0
  204. package/lib/foreign/vibe.d.ts +6 -0
  205. package/lib/foreign/vibe.js +23 -0
  206. package/lib/foreign/workbuddy.d.ts +7 -0
  207. package/lib/foreign/workbuddy.js +45 -0
  208. package/lib/foreign/zcode.d.ts +11 -0
  209. package/lib/foreign/zcode.js +51 -0
  210. package/lib/foreign/zed.d.ts +11 -0
  211. package/lib/foreign/zed.js +53 -0
  212. package/lib/index.d.ts +154 -13
  213. package/lib/index.js +661 -84
  214. package/lib/market/github-repos.d.ts +28 -0
  215. package/lib/market/github-repos.js +43 -1
  216. package/lib/market/types.d.ts +1 -3
  217. package/lib/profiles/dsh-profile-manager.d.ts +54 -2
  218. package/lib/profiles/dsh-profile-manager.js +182 -11
  219. package/lib/profiles/dsh-profile-shared.d.ts +47 -0
  220. package/lib/profiles/dsh-profile-shared.js +15 -1
  221. package/lib/profiles/index.d.ts +1 -1
  222. package/lib/profiles/index.js +1 -1
  223. package/lib/routes/consult.js +51 -5
  224. package/lib/routes/foreign-context.d.ts +43 -0
  225. package/lib/routes/foreign-context.js +65 -0
  226. package/lib/routes/foreign.d.ts +11 -0
  227. package/lib/routes/foreign.js +208 -0
  228. package/lib/routes/import.d.ts +8 -0
  229. package/lib/routes/import.js +22 -1
  230. package/lib/routes/index.d.ts +2 -2
  231. package/lib/routes/index.js +4 -0
  232. package/lib/routes/kit.d.ts +4 -3
  233. package/lib/routes/market.js +0 -1
  234. package/lib/routes/prefs.d.ts +2 -1
  235. package/lib/routes/prefs.js +14 -2
  236. package/lib/routes/recovery.js +363 -3
  237. package/lib/routes/session-export.d.ts +117 -0
  238. package/lib/routes/session-export.js +270 -0
  239. package/lib/routes/snapshots.js +12 -2
  240. package/lib/routes/sync.js +82 -10
  241. package/lib/security/redaction.d.ts +14 -0
  242. package/lib/security/redaction.js +24 -9
  243. package/lib/security/secret-scanner.d.ts +27 -0
  244. package/lib/security/secret-scanner.js +54 -1
  245. package/lib/sync/autosync-scheduler.d.ts +13 -1
  246. package/lib/sync/autosync-scheduler.js +49 -26
  247. package/lib/sync/backup-files.js +6 -0
  248. package/lib/sync/gist/gist-transport.d.ts +91 -0
  249. package/lib/sync/gist/gist-transport.js +406 -0
  250. package/lib/sync/gist/messages.d.ts +22 -0
  251. package/lib/sync/gist/messages.js +46 -0
  252. package/lib/sync/s3/messages.d.ts +53 -0
  253. package/lib/sync/s3/messages.js +83 -0
  254. package/lib/sync/s3/s3-providers.d.ts +78 -0
  255. package/lib/sync/s3/s3-providers.js +156 -0
  256. package/lib/sync/s3/s3-transport.d.ts +126 -0
  257. package/lib/sync/s3/s3-transport.js +551 -0
  258. package/lib/sync/s3/sigv4.d.ts +107 -0
  259. package/lib/sync/s3/sigv4.js +196 -0
  260. package/lib/sync/sync-config.d.ts +250 -4
  261. package/lib/sync/sync-config.js +658 -57
  262. package/lib/sync/sync-engine.d.ts +1 -0
  263. package/lib/sync/sync-engine.js +4 -2
  264. package/lib/sync/sync-history.d.ts +1 -1
  265. package/lib/sync/webdav/webdav-transport.d.ts +31 -3
  266. package/lib/sync/webdav/webdav-transport.js +102 -18
  267. package/lib/ui/commands.d.ts +4 -3
  268. package/lib/ui/commands.js +41 -9
  269. package/lib/ui/disk-usage-view.d.ts +44 -0
  270. package/lib/ui/disk-usage-view.js +55 -1
  271. package/lib/ui/dsh-profiles-view.d.ts +53 -0
  272. package/lib/ui/dsh-profiles-view.js +56 -0
  273. package/lib/ui/foreign-view.d.ts +162 -0
  274. package/lib/ui/foreign-view.js +225 -0
  275. package/lib/ui/i18n.d.ts +77 -0
  276. package/lib/ui/i18n.js +164 -0
  277. package/lib/ui/session-inventory-view.d.ts +106 -0
  278. package/lib/ui/session-inventory-view.js +58 -0
  279. package/lib/ui/session-layout-view.d.ts +265 -0
  280. package/lib/ui/session-layout-view.js +271 -0
  281. package/lib/ui/sync-repository-picker.d.ts +98 -0
  282. package/lib/ui/sync-repository-picker.js +145 -0
  283. package/lib/ui/sync-settings-view.d.ts +143 -4
  284. package/lib/ui/sync-settings-view.js +231 -7
  285. package/lib/ui/types.d.ts +47 -0
  286. package/lib/utils/asar-read.d.ts +113 -0
  287. package/lib/utils/asar-read.js +523 -0
  288. package/lib/utils/env-lock.js +7 -2
  289. package/lib/utils/patch-yaml.d.ts +25 -0
  290. package/lib/utils/patch-yaml.js +62 -0
  291. package/lib/utils/paths.d.ts +10 -1
  292. package/lib/utils/paths.js +24 -3
  293. package/lib/utils/recursive-walk.js +12 -13
  294. package/lib/utils/session-format.d.ts +35 -2
  295. package/lib/utils/session-format.js +131 -5
  296. package/lib/utils/session-health-scan.d.ts +7 -1
  297. package/lib/utils/session-health-scan.js +369 -12
  298. package/lib/utils/session-layout-repair-service.d.ts +98 -0
  299. package/lib/utils/session-layout-repair-service.js +457 -0
  300. package/lib/utils/session-log-repair.d.ts +27 -0
  301. package/lib/utils/session-log-repair.js +233 -62
  302. package/lib/utils/session-repair-service.d.ts +32 -3
  303. package/lib/utils/session-repair-service.js +148 -9
  304. package/lib/utils/session-row-facts.d.ts +73 -0
  305. package/lib/utils/session-row-facts.js +182 -0
  306. package/lib/utils/session-verify.d.ts +118 -0
  307. package/lib/utils/session-verify.js +705 -0
  308. package/lib/utils/shared-constants.d.ts +59 -0
  309. package/lib/utils/shared-constants.js +71 -0
  310. package/package.json +1 -1
  311. package/src/adapters/credentials.test.ts +27 -0
  312. package/src/adapters/credentials.ts +6 -3
  313. package/src/adapters/file-collection.ts +33 -8
  314. package/src/adapters/files.test.ts +222 -0
  315. package/src/adapters/mcp.test.ts +43 -0
  316. package/src/adapters/mcp.ts +37 -14
  317. package/src/adapters/plugin-files.ts +34 -4
  318. package/src/adapters/plugins.test.ts +241 -0
  319. package/src/adapters/plugins.ts +201 -53
  320. package/src/adapters/prompts.test.ts +49 -0
  321. package/src/adapters/prompts.ts +41 -20
  322. package/src/adapters/providers.test.ts +16 -0
  323. package/src/adapters/providers.ts +4 -3
  324. package/src/adapters/self.test.ts +77 -0
  325. package/src/adapters/self.ts +75 -0
  326. package/src/adapters/sessions.test.ts +77 -0
  327. package/src/adapters/sessions.ts +47 -1
  328. package/src/adapters/settings.test.ts +78 -0
  329. package/src/adapters/settings.ts +42 -6
  330. package/src/adapters/skills.ts +221 -3
  331. package/src/adapters/test-helpers.ts +43 -5
  332. package/src/adapters/workspaces-dup-id.test.ts +144 -0
  333. package/src/adapters/workspaces.test.ts +10 -0
  334. package/src/adapters/workspaces.ts +97 -5
  335. package/src/cli/actions.test.ts +117 -2
  336. package/src/cli/actions.ts +132 -12
  337. package/src/cli/help.ts +570 -0
  338. package/src/cli/import-source.test.ts +193 -0
  339. package/src/cli/import-source.ts +229 -0
  340. package/src/cli/index.ts +129 -94
  341. package/src/cli/web/client-script.ts +12 -0
  342. package/src/cli/web/console-fixes.test.ts +36 -0
  343. package/src/cli/web/http.ts +7 -1
  344. package/src/cli/web/page.ts +108 -25
  345. package/src/cli/web/routes.ts +23 -4
  346. package/src/cli/web/script-anchor.test.ts +33 -0
  347. package/src/cli/web/server.ts +28 -2
  348. package/src/cli/web/web.test.ts +447 -4
  349. package/src/client/ConfigManagerSection.tsx +118 -63
  350. package/src/client/about/AboutPanel.tsx +42 -3
  351. package/src/client/about/PluginUpdateDialog.tsx +185 -0
  352. package/src/client/about/ReleaseBody.tsx +144 -0
  353. package/src/client/about/ReleaseNotesDialog.tsx +3 -130
  354. package/src/client/about/about-view.ts +7 -5
  355. package/src/client/about/plugin-update-view.test.ts +25 -0
  356. package/src/client/about/plugin-update-view.ts +26 -0
  357. package/src/client/about/release-notes-view.test.ts +33 -0
  358. package/src/client/about/release-notes-view.ts +34 -0
  359. package/src/client/api.ts +145 -1
  360. package/src/client/common/Icon.tsx +3 -0
  361. package/src/client/common/RunsCenter.tsx +8 -2
  362. package/src/client/common/Select.tsx +9 -2
  363. package/src/client/common/info-hint-guard.test.ts +63 -18
  364. package/src/client/common/plan-text-redaction.test.ts +193 -8
  365. package/src/client/common/routes.ts +15 -0
  366. package/src/client/config-manager.module.css +506 -354
  367. package/src/client/css-scale-guard.test.ts +122 -0
  368. package/src/client/css-token-guard.test.ts +82 -0
  369. package/src/client/environment/EnvironmentPanel.tsx +45 -16
  370. package/src/client/{snapshots → environment/maintenance}/DiskUsageCard.tsx +21 -17
  371. package/src/client/environment/profiles-incomplete-ui.test.ts +103 -0
  372. package/src/client/export/ExportView.tsx +9 -2
  373. package/src/client/{snapshots → home}/BackupScheduleCard.tsx +13 -4
  374. package/src/client/home/HomePanel.tsx +69 -157
  375. package/src/client/icon-layer-guard.test.ts +1 -1
  376. package/src/client/import/ForeignImportView.tsx +318 -0
  377. package/src/client/import/ImportWizardView.tsx +3 -1
  378. package/src/client/import/foreign-import-view-guard.test.ts +195 -0
  379. package/src/client/import/import-file-select.test.ts +6 -6
  380. package/src/client/import/import-wizard-steps.tsx +17 -1
  381. package/src/client/import/use-import-wizard-controller.ts +3 -3
  382. package/src/client/index.ts +1 -1
  383. package/src/client/library/LibraryActions.tsx +31 -34
  384. package/src/client/library/LibraryPanel.tsx +4 -1
  385. package/src/client/{snapshots → library}/RestorePlanView.tsx +1 -1
  386. package/src/client/{snapshots → library}/files-layout.test.ts +2 -2
  387. package/src/client/{snapshots → library}/report-dismiss.test.ts +26 -3
  388. package/src/client/library/task-panel-visibility.test.ts +25 -17
  389. package/src/client/locales.ts +278 -36
  390. package/src/client/lucide-icons.d.ts +1 -0
  391. package/src/client/market/MarketPanel.tsx +11 -2
  392. package/src/client/recovery/IncompleteCopiesSection.tsx +132 -0
  393. package/src/client/recovery/RecoveryPanel.tsx +486 -59
  394. package/src/client/recovery/recovery-api.ts +30 -0
  395. package/src/client/recovery/recovery-incomplete-copies.test.ts +151 -0
  396. package/src/client/recovery/recovery-locales.test.ts +88 -0
  397. package/src/client/recovery/recovery-locales.ts +321 -6
  398. package/src/client/recovery/recovery-panel-layout.test.ts +52 -0
  399. package/src/client/recovery/recovery-view.ts +92 -2
  400. package/src/client/run-store.test.ts +201 -89
  401. package/src/client/run-store.ts +114 -143
  402. package/src/client/session-export/session-export-view.test.ts +103 -0
  403. package/src/client/session-export/session-export-view.ts +118 -0
  404. package/src/client/sync/ChannelConfigDialog.tsx +219 -14
  405. package/src/client/sync/SyncChannelEntryCard.tsx +52 -4
  406. package/src/client/sync/SyncLogList.tsx +1 -1
  407. package/src/client/sync/SyncPanel.tsx +166 -102
  408. package/src/client/sync/SyncRepositoryPicker.tsx +216 -0
  409. package/src/client/sync/SyncSettingsView.test.ts +6 -1
  410. package/src/client/sync/history-model.test.ts +94 -57
  411. package/src/client/sync/history-model.ts +44 -25
  412. package/src/client/sync/sync-api.test.ts +56 -0
  413. package/src/client/sync/sync-api.ts +92 -0
  414. package/src/client/sync/sync-locales.ts +162 -2
  415. package/src/client/sync/sync-view.test.ts +3 -8
  416. package/src/client/sync/sync-view.ts +111 -21
  417. package/src/core/analyzer-cache.test.ts +47 -0
  418. package/src/core/analyzer.ts +115 -14
  419. package/src/core/backup-plan.test.ts +35 -0
  420. package/src/core/backup-plan.ts +19 -1
  421. package/src/core/backup.test.ts +37 -1
  422. package/src/core/backup.ts +27 -6
  423. package/src/core/cache-cleaner.test.ts +120 -1
  424. package/src/core/cache-cleaner.ts +59 -22
  425. package/src/core/checkpoint.test.ts +665 -0
  426. package/src/core/checkpoint.ts +1614 -0
  427. package/src/core/credential-plan-consistency.test.ts +93 -0
  428. package/src/core/incident-wiring.test.ts +6 -2
  429. package/src/core/journal.test.ts +70 -0
  430. package/src/core/journal.ts +68 -0
  431. package/src/core/messages.ts +26 -0
  432. package/src/core/model-tools.ts +3 -0
  433. package/src/core/patch-layers.test.ts +94 -0
  434. package/src/core/patch-layers.ts +150 -0
  435. package/src/core/restore.ts +59 -3
  436. package/src/core/rollback.ts +6 -3
  437. package/src/core/section-content-consistency.test.ts +185 -0
  438. package/src/core/self-update.test.ts +110 -0
  439. package/src/core/self-update.ts +118 -0
  440. package/src/core/session-health.test.ts +91 -1
  441. package/src/core/session-health.ts +56 -9
  442. package/src/core/types.ts +127 -1
  443. package/src/foreign/antigravity.test.ts +271 -0
  444. package/src/foreign/antigravity.ts +188 -0
  445. package/src/foreign/bundle.ts +108 -0
  446. package/src/foreign/chatgpt.test.ts +279 -0
  447. package/src/foreign/chatgpt.ts +30 -0
  448. package/src/foreign/claude-code.test.ts +234 -0
  449. package/src/foreign/claude-code.ts +106 -0
  450. package/src/foreign/claude-sessions-bytes.test.ts +227 -0
  451. package/src/foreign/claude-sessions.test.ts +180 -0
  452. package/src/foreign/claude-sessions.ts +461 -0
  453. package/src/foreign/cline.test.ts +274 -0
  454. package/src/foreign/cline.ts +32 -0
  455. package/src/foreign/codex.test.ts +585 -0
  456. package/src/foreign/codex.ts +409 -0
  457. package/src/foreign/continue.test.ts +365 -0
  458. package/src/foreign/continue.ts +73 -0
  459. package/src/foreign/copilot.test.ts +273 -0
  460. package/src/foreign/copilot.ts +133 -0
  461. package/src/foreign/crush.test.ts +234 -0
  462. package/src/foreign/crush.ts +66 -0
  463. package/src/foreign/cursor.test.ts +509 -0
  464. package/src/foreign/cursor.ts +259 -0
  465. package/src/foreign/dsh.test.ts +198 -0
  466. package/src/foreign/dsh.ts +117 -0
  467. package/src/foreign/dsh4.test.ts +127 -0
  468. package/src/foreign/dsh4.ts +15 -0
  469. package/src/foreign/file-budget.test.ts +256 -0
  470. package/src/foreign/fixtures/antigravity/README.md +18 -0
  471. package/src/foreign/fixtures/antigravity/basic/gemini/antigravity/mcp_config.json +13 -0
  472. package/src/foreign/fixtures/antigravity/basic/gemini/config/mcp_config.json +24 -0
  473. package/src/foreign/fixtures/antigravity/empty/gemini/antigravity/mcp_config.json +0 -0
  474. package/src/foreign/fixtures/antigravity/empty/gemini/antigravity/mcp_oauth_tokens.json +0 -0
  475. package/src/foreign/fixtures/antigravity/empty/gemini/config/mcp_config.json +0 -0
  476. package/src/foreign/fixtures/codex/README.md +31 -0
  477. package/src/foreign/fixtures/codex/basic/.agents/skills/hello/SKILL.md +6 -0
  478. package/src/foreign/fixtures/codex/basic/.agents/skills/wrapper/nested/SKILL.md +6 -0
  479. package/src/foreign/fixtures/codex/basic/.codex/AGENTS.md +4 -0
  480. package/src/foreign/fixtures/codex/basic/.codex/config.toml +28 -0
  481. package/src/foreign/fixtures/codex/malformed/.codex/config.toml +3 -0
  482. package/src/foreign/fixtures/codex/override/.codex/AGENTS.md +1 -0
  483. package/src/foreign/fixtures/codex/override/.codex/AGENTS.override.md +3 -0
  484. package/src/foreign/fixtures/codex/sessions/.codex/archived_sessions/rollout-2026-10-05T09-00-00-22222222-2222-4222-8222-222222222222.jsonl +2 -0
  485. package/src/foreign/fixtures/codex/sessions/.codex/sessions/2026/10/06/rollout-2026-10-06T10-00-00-11111111-1111-4111-8111-111111111111.jsonl +15 -0
  486. package/src/foreign/fixtures/codex/sessions/.codex/sessions/2026/10/06/rollout-2026-10-06T11-00-00-33333333-3333-4333-8333-333333333333.jsonl +2 -0
  487. package/src/foreign/fixtures/codex/sessions/.codex/sessions/2026/10/06/rollout-2026-10-06T11-30-00-55555555-5555-4555-8555-555555555555.jsonl +2 -0
  488. package/src/foreign/fixtures/codex/sessions/.codex/sessions/2026/10/06/rollout-2026-10-06T12-00-00-44444444-4444-4444-8444-444444444444.jsonl +2 -0
  489. package/src/foreign/fixtures/codex/sessions/.codex/sessions/notes.jsonl +1 -0
  490. package/src/foreign/fixtures/copilot/README.md +22 -0
  491. package/src/foreign/fixtures/copilot/basic/copilot-instructions.md +3 -0
  492. package/src/foreign/fixtures/copilot/basic/instructions/empty.instructions.md +1 -0
  493. package/src/foreign/fixtures/copilot/basic/instructions/repo.instructions.md +5 -0
  494. package/src/foreign/fixtures/copilot/basic/mcp-config.json +24 -0
  495. package/src/foreign/fixtures/copilot/basic/skills/hello/SKILL.md +6 -0
  496. package/src/foreign/fixtures/copilot/overridden/mcp-config.json +10 -0
  497. package/src/foreign/fixtures/cursor/README.md +17 -0
  498. package/src/foreign/fixtures/cursor/alt/.cursor/mcp.json +5 -0
  499. package/src/foreign/fixtures/cursor/basic/.cursor/mcp.json +18 -0
  500. package/src/foreign/fixtures/cursor/basic/.cursor/rules/agent.mdc +5 -0
  501. package/src/foreign/fixtures/cursor/basic/.cursor/rules/always.mdc +6 -0
  502. package/src/foreign/fixtures/cursor/basic/.cursor/rules/auto.mdc +7 -0
  503. package/src/foreign/fixtures/cursor/basic/.cursor/rules/manual.mdc +3 -0
  504. package/src/foreign/fixtures/cursor/basic/.cursor/skills/.hidden/SKILL.md +6 -0
  505. package/src/foreign/fixtures/cursor/basic/.cursor/skills/hello/SKILL.md +6 -0
  506. package/src/foreign/fixtures/cursor/basic/.cursor/skills/wrapper/nested/SKILL.md +6 -0
  507. package/src/foreign/fixtures/cursor/basic/.cursor/skills/wrapper/nested/notes.md +1 -0
  508. package/src/foreign/fixtures/cursor/basic/.cursorrules +1 -0
  509. package/src/foreign/fixtures/cursor/basic/project/.cursor/mcp.json +9 -0
  510. package/src/foreign/fixtures/cursor/basic/project/.cursor/rules/project-always.mdc +5 -0
  511. package/src/foreign/fixtures/cursor/basic/project/.cursor/skills/hello/SKILL.md +6 -0
  512. package/src/foreign/fixtures/cursor/basic/project/.cursorrules +1 -0
  513. package/src/foreign/fixtures/cursor/empty/.cursor/mcp.json +0 -0
  514. package/src/foreign/fixtures/cursor/malformed/.cursor/mcp.json +3 -0
  515. package/src/foreign/fixtures/cursor/malformed/.cursor/rules/broken.mdc +6 -0
  516. package/src/foreign/fixtures/cursor/shape/.cursor/mcp.json +3 -0
  517. package/src/foreign/fixtures/hermes/README.md +14 -0
  518. package/src/foreign/fixtures/hermes/basic/SOUL.md +3 -0
  519. package/src/foreign/fixtures/hermes/basic/config.yaml +48 -0
  520. package/src/foreign/fixtures/hermes/basic/dotenv.fixture +1 -0
  521. package/src/foreign/fixtures/hermes/basic/memories/MEMORY.md +1 -0
  522. package/src/foreign/fixtures/hermes/basic/memories/USER.md +1 -0
  523. package/src/foreign/fixtures/hermes/basic/skills/se-team-design/SKILL.md +6 -0
  524. package/src/foreign/fixtures/hermes/basic/skills/se-team-design/examples/usage.md +1 -0
  525. package/src/foreign/fixtures/hermes/basic/skills/software-development/dogfood/SKILL.md +6 -0
  526. package/src/foreign/fixtures/hermes/basic/skills/software-development/dogfood/notes.md +1 -0
  527. package/src/foreign/fixtures/hermes/basic/state.db +1 -0
  528. package/src/foreign/fixtures/hermes/empty/config.yaml +0 -0
  529. package/src/foreign/gemini.test.ts +291 -0
  530. package/src/foreign/gemini.ts +52 -0
  531. package/src/foreign/goose.test.ts +196 -0
  532. package/src/foreign/goose.ts +62 -0
  533. package/src/foreign/grokbuild.test.ts +246 -0
  534. package/src/foreign/grokbuild.ts +30 -0
  535. package/src/foreign/hermes.test.ts +545 -0
  536. package/src/foreign/hermes.ts +86 -0
  537. package/src/foreign/kernel-credentials.test.ts +88 -0
  538. package/src/foreign/kernel-workspaces.test.ts +88 -0
  539. package/src/foreign/kernel.ts +584 -0
  540. package/src/foreign/kilocode.test.ts +186 -0
  541. package/src/foreign/kilocode.ts +60 -0
  542. package/src/foreign/kimi.test.ts +259 -0
  543. package/src/foreign/kimi.ts +55 -0
  544. package/src/foreign/mcp-value-shape.test.ts +286 -0
  545. package/src/foreign/mimocode.test.ts +178 -0
  546. package/src/foreign/mimocode.ts +60 -0
  547. package/src/foreign/openclaw.test.ts +248 -0
  548. package/src/foreign/openclaw.ts +54 -0
  549. package/src/foreign/opencode.test.ts +376 -0
  550. package/src/foreign/opencode.ts +61 -0
  551. package/src/foreign/pi.test.ts +275 -0
  552. package/src/foreign/pi.ts +56 -0
  553. package/src/foreign/platform-paths.ts +156 -0
  554. package/src/foreign/qoder.test.ts +266 -0
  555. package/src/foreign/qoder.ts +57 -0
  556. package/src/foreign/qwen.test.ts +203 -0
  557. package/src/foreign/qwen.ts +53 -0
  558. package/src/foreign/read-antigravity.ts +569 -0
  559. package/src/foreign/read-chatgpt.ts +506 -0
  560. package/src/foreign/read-claude-code.ts +264 -0
  561. package/src/foreign/read-cline.ts +517 -0
  562. package/src/foreign/read-codex.ts +823 -0
  563. package/src/foreign/read-continue.ts +557 -0
  564. package/src/foreign/read-copilot.ts +304 -0
  565. package/src/foreign/read-crush.ts +503 -0
  566. package/src/foreign/read-cursor.ts +778 -0
  567. package/src/foreign/read-dsh.ts +215 -0
  568. package/src/foreign/read-dsh4.ts +35 -0
  569. package/src/foreign/read-gemini.ts +306 -0
  570. package/src/foreign/read-goose.ts +333 -0
  571. package/src/foreign/read-grokbuild.ts +475 -0
  572. package/src/foreign/read-hermes.ts +724 -0
  573. package/src/foreign/read-kilocode.ts +55 -0
  574. package/src/foreign/read-kimi.ts +768 -0
  575. package/src/foreign/read-mimocode.ts +66 -0
  576. package/src/foreign/read-openclaw.ts +505 -0
  577. package/src/foreign/read-opencode.ts +994 -0
  578. package/src/foreign/read-pi.ts +459 -0
  579. package/src/foreign/read-qoder.ts +351 -0
  580. package/src/foreign/read-qwen.ts +342 -0
  581. package/src/foreign/read-reasonix.ts +590 -0
  582. package/src/foreign/read-teleagent.ts +82 -0
  583. package/src/foreign/read-trae.ts +488 -0
  584. package/src/foreign/read-vibe.ts +353 -0
  585. package/src/foreign/read-workbuddy.ts +374 -0
  586. package/src/foreign/read-zcode.ts +48 -0
  587. package/src/foreign/read-zed.ts +479 -0
  588. package/src/foreign/reasonix.test.ts +336 -0
  589. package/src/foreign/reasonix.ts +25 -0
  590. package/src/foreign/registry.test.ts +377 -0
  591. package/src/foreign/registry.ts +750 -0
  592. package/src/foreign/session-ir.test.ts +189 -0
  593. package/src/foreign/session-ir.ts +614 -0
  594. package/src/foreign/session-read.ts +295 -0
  595. package/src/foreign/session-source.ts +625 -0
  596. package/src/foreign/skill-file-caps.test.ts +171 -0
  597. package/src/foreign/source-modules.ts +86 -0
  598. package/src/foreign/source-registry.test.ts +190 -0
  599. package/src/foreign/sqlite.test.ts +313 -0
  600. package/src/foreign/sqlite.ts +446 -0
  601. package/src/foreign/teleagent.test.ts +147 -0
  602. package/src/foreign/teleagent.ts +62 -0
  603. package/src/foreign/trae.test.ts +316 -0
  604. package/src/foreign/trae.ts +25 -0
  605. package/src/foreign/truncation-report.test.ts +238 -0
  606. package/src/foreign/truth-table.ts +453 -0
  607. package/src/foreign/types.ts +312 -0
  608. package/src/foreign/vibe.test.ts +209 -0
  609. package/src/foreign/vibe.ts +28 -0
  610. package/src/foreign/workbuddy.test.ts +207 -0
  611. package/src/foreign/workbuddy.ts +53 -0
  612. package/src/foreign/zcode.test.ts +224 -0
  613. package/src/foreign/zcode.ts +60 -0
  614. package/src/foreign/zed.test.ts +240 -0
  615. package/src/foreign/zed.ts +62 -0
  616. package/src/index.facade.test.ts +254 -16
  617. package/src/index.ts +704 -82
  618. package/src/market/github-repos.test.ts +98 -1
  619. package/src/market/github-repos.ts +63 -1
  620. package/src/market/types.ts +3 -3
  621. package/src/profiles/dsh-profile-incomplete-copy.test.ts +177 -0
  622. package/src/profiles/dsh-profile-manager.test.ts +37 -0
  623. package/src/profiles/dsh-profile-manager.ts +188 -13
  624. package/src/profiles/dsh-profile-shared.ts +55 -1
  625. package/src/profiles/index.ts +2 -1
  626. package/src/routes/consult.ts +53 -4
  627. package/src/routes/foreign-context.ts +89 -0
  628. package/src/routes/foreign.test.ts +443 -0
  629. package/src/routes/foreign.ts +226 -0
  630. package/src/routes/import.ts +23 -1
  631. package/src/routes/index.ts +6 -2
  632. package/src/routes/kit.ts +4 -3
  633. package/src/routes/market.ts +0 -1
  634. package/src/routes/prefs.ts +15 -1
  635. package/src/routes/recovery.test.ts +167 -0
  636. package/src/routes/recovery.ts +331 -1
  637. package/src/routes/session-export.ts +308 -0
  638. package/src/routes/snapshots.ts +12 -2
  639. package/src/routes/sync.ts +91 -7
  640. package/src/security/redaction.test.ts +149 -0
  641. package/src/security/redaction.ts +25 -9
  642. package/src/security/secret-scanner.test.ts +186 -0
  643. package/src/security/secret-scanner.ts +57 -1
  644. package/src/sync/autosync-risk-tier.test.ts +61 -0
  645. package/src/sync/autosync-scheduler.test.ts +48 -1
  646. package/src/sync/autosync-scheduler.ts +50 -26
  647. package/src/sync/backup-files.ts +5 -0
  648. package/src/sync/gist/gist-msgkeys-catalog.test.ts +33 -0
  649. package/src/sync/gist/gist-msgkeys.test.ts +71 -0
  650. package/src/sync/gist/gist-transport.test.ts +308 -0
  651. package/src/sync/gist/gist-transport.ts +550 -0
  652. package/src/sync/gist/messages.ts +49 -0
  653. package/src/sync/retention-prune-all-off.test.ts +50 -0
  654. package/src/sync/s3/cloud-config.test.ts +283 -0
  655. package/src/sync/s3/messages.ts +102 -0
  656. package/src/sync/s3/s3-delete-index-404.test.ts +170 -0
  657. package/src/sync/s3/s3-delete-index-unreadable.test.ts +141 -0
  658. package/src/sync/s3/s3-msgkeys.test.ts +101 -0
  659. package/src/sync/s3/s3-providers.ts +213 -0
  660. package/src/sync/s3/s3-transport.test.ts +394 -0
  661. package/src/sync/s3/s3-transport.ts +651 -0
  662. package/src/sync/s3/s3-upload-index-404.test.ts +171 -0
  663. package/src/sync/s3/sigv4.test.ts +219 -0
  664. package/src/sync/s3/sigv4.ts +283 -0
  665. package/src/sync/sync-config.test.ts +237 -5
  666. package/src/sync/sync-config.ts +841 -52
  667. package/src/sync/sync-engine-download-tombstone.test.ts +73 -0
  668. package/src/sync/sync-engine.ts +5 -3
  669. package/src/sync/sync-history.ts +1 -1
  670. package/src/sync/webdav/webdav-delete-index-404.test.ts +178 -0
  671. package/src/sync/webdav/webdav-delete-index-unreadable.test.ts +143 -0
  672. package/src/sync/webdav/webdav-msgkeys.test.ts +53 -0
  673. package/src/sync/webdav/webdav-transport.test.ts +9 -5
  674. package/src/sync/webdav/webdav-transport.ts +99 -17
  675. package/src/sync/webdav/webdav-upload-index-404.test.ts +177 -0
  676. package/src/ui/commands.test.ts +4 -1
  677. package/src/ui/commands.ts +46 -12
  678. package/src/ui/disk-usage-view.test.ts +63 -1
  679. package/src/ui/disk-usage-view.ts +93 -1
  680. package/src/ui/dsh-profiles-incomplete-view.test.ts +102 -0
  681. package/src/ui/dsh-profiles-view.ts +94 -0
  682. package/src/ui/foreign-view.test.ts +241 -0
  683. package/src/ui/foreign-view.ts +362 -0
  684. package/src/ui/i18n.ts +164 -0
  685. package/src/ui/mid-ellipsis.test.ts +5 -10
  686. package/src/ui/session-inventory-view.test.ts +142 -2
  687. package/src/ui/session-inventory-view.ts +127 -0
  688. package/src/ui/session-layout-view.test.ts +361 -0
  689. package/src/ui/session-layout-view.ts +444 -0
  690. package/src/ui/sync-repository-picker.test.ts +130 -0
  691. package/src/ui/sync-repository-picker.ts +180 -0
  692. package/src/ui/sync-settings-view.test.ts +166 -0
  693. package/src/ui/sync-settings-view.ts +319 -9
  694. package/src/ui/types.ts +42 -0
  695. package/src/utils/asar-read.test.ts +330 -0
  696. package/src/utils/asar-read.ts +606 -0
  697. package/src/utils/env-lock.test.ts +55 -0
  698. package/src/utils/env-lock.ts +6 -1
  699. package/src/utils/patch-yaml.test.ts +112 -0
  700. package/src/utils/patch-yaml.ts +72 -0
  701. package/src/utils/paths.test.ts +63 -0
  702. package/src/utils/paths.ts +25 -3
  703. package/src/utils/recursive-walk.test.ts +20 -0
  704. package/src/utils/recursive-walk.ts +12 -13
  705. package/src/utils/session-format.test.ts +113 -2
  706. package/src/utils/session-format.ts +127 -5
  707. package/src/utils/session-health-scan.test.ts +364 -1
  708. package/src/utils/session-health-scan.ts +434 -15
  709. package/src/utils/session-layout-repair-service.test.ts +362 -0
  710. package/src/utils/session-layout-repair-service.ts +568 -0
  711. package/src/utils/session-log-repair.test.ts +260 -1
  712. package/src/utils/session-log-repair.ts +294 -55
  713. package/src/utils/session-repair-service.test.ts +148 -1
  714. package/src/utils/session-repair-service.ts +165 -10
  715. package/src/utils/session-row-facts.test.ts +159 -0
  716. package/src/utils/session-row-facts.ts +187 -0
  717. package/src/utils/session-verify.test.ts +762 -0
  718. package/src/utils/session-verify.ts +784 -0
  719. package/src/utils/shared-constants.ts +82 -0
@@ -10,6 +10,8 @@ export declare class MemFs implements FileSystemFacade {
10
10
  ensureDir?: FileSystemFacade['ensureDir'];
11
11
  /** 可选(issue #45):绝对路径 realpath。测试按需注入;缺省维持「宿主不支持」的旧行为。 */
12
12
  realpathDir?: FileSystemFacade['realpathDir'];
13
+ /** 可选(只读预览):目录体积上界。测试按需注入;缺省维持「宿主不支持」的旧行为(`link:` 体积按 0)。 */
14
+ dirSizeBytes?: FileSystemFacade['dirSizeBytes'];
13
15
  private readonly homeDir;
14
16
  constructor(homeDir: string);
15
17
  private key;
@@ -63,6 +65,11 @@ declare class AutoRegisterMap extends Map<string, {
63
65
  export declare class MemSettings implements SettingsFacade {
64
66
  /** 已注册命名空间(对齐真实 dsh-settings:插件激活时注册;未注册的 describe/replace 抛错) */
65
67
  registered: Set<string>;
68
+ /**
69
+ * 可选(宿主能力探测):**默认不提供** ⇒ 既有测试继续覆盖「逐名 describe」回退路径;
70
+ * 需要验证快路径的测试自行挂上(真实宿主 DshSettingsFacade 始终提供)。
71
+ */
72
+ describeAll?: SettingsFacade['describeAll'];
66
73
  /** 有值的命名空间自动视为已注册 */
67
74
  ns: AutoRegisterMap;
68
75
  describe(namespace: string, _opts?: {
@@ -100,16 +107,36 @@ export declare class MemWorkspace implements WorkspaceFacade {
100
107
  writeRecord(r: WorkspaceRecord): Promise<void>;
101
108
  removeRecord(id: string): Promise<void>;
102
109
  }
110
+ /**
111
+ * 内存 patch 门面,**按层分桶**(issue #71)。
112
+ *
113
+ * `lines` 仍是**用户(home)层**的桶 —— 绝大多数既有测试只关心「一行进、一行出」,
114
+ * 直接操作 `lines` 即可,语义与改造前完全一致。其它层(profile 层)走 `byFile`:
115
+ * 缺省空桶,因此「读两层」的调用方在旧测试里读到的 profile 层就是空的,不会污染断言。
116
+ *
117
+ * 分层用 `byFile(rel)` 显式取桶,避免测试里手写路径字符串。
118
+ */
103
119
  export declare class MemPatch implements PatchFileFacade {
120
+ /** 用户(home)层桶:`cordis.patch.yml`。 */
104
121
  lines: Map<string, {
105
122
  lineId: string;
106
123
  raw: unknown;
107
124
  }>;
108
- readPatchLines(_file: string): Promise<{
125
+ /** 其它层桶:层路径 → 行表(profile 层等)。 */
126
+ byFile: Map<string, Map<string, {
127
+ lineId: string;
128
+ raw: unknown;
129
+ }>>;
130
+ /** 取(必要时建)某一层的桶;用户层恒为 `lines`(与旧测试兼容)。 */
131
+ bucket(file: string): Map<string, {
132
+ lineId: string;
133
+ raw: unknown;
134
+ }>;
135
+ readPatchLines(file: string): Promise<{
109
136
  lineId: string;
110
137
  raw: unknown;
111
138
  }[]>;
112
- applyPatchChanges(_file: string, changes: {
139
+ applyPatchChanges(file: string, changes: {
113
140
  lineId: string;
114
141
  raw: unknown;
115
142
  action: 'insert' | 'update' | 'remove';
@@ -138,6 +165,8 @@ export declare class MockHostContext implements HostContext {
138
165
  fs: MemFs;
139
166
  /** 可选(issue #45):会话存储端口;测试按需注入(缺省 = 宿主未提供该能力)。 */
140
167
  sessions?: HostContext['sessions'];
168
+ /** 可选(issue #71):技能服务端口;测试按需注入(缺省 = 退回纯目录扫描)。 */
169
+ skills?: HostContext['skills'];
141
170
  constructor(platform: string, homeDir: string, profile?: string);
142
171
  }
143
172
  export declare function makeContext(platform: string, homeDir: string, profile?: string): MockHostContext;
@@ -6,6 +6,7 @@ import { normalizePath } from '../utils/paths.js';
6
6
  import { createLogger } from '../utils/logger.js';
7
7
  import { sha256Hex } from '../utils/hashing.js';
8
8
  import { zhMsg } from '../core/messages.js';
9
+ import { USER_PATCH_FILE } from '../core/patch-layers.js';
9
10
  export class MemFs {
10
11
  files = new Map();
11
12
  /** 可选(issue #37):详细的链接遍历。测试按需注入;缺省走 listRecursive 回退路径。 */
@@ -14,6 +15,8 @@ export class MemFs {
14
15
  ensureDir;
15
16
  /** 可选(issue #45):绝对路径 realpath。测试按需注入;缺省维持「宿主不支持」的旧行为。 */
16
17
  realpathDir;
18
+ /** 可选(只读预览):目录体积上界。测试按需注入;缺省维持「宿主不支持」的旧行为(`link:` 体积按 0)。 */
19
+ dirSizeBytes;
17
20
  homeDir;
18
21
  constructor(homeDir) {
19
22
  this.homeDir = homeDir;
@@ -95,6 +98,11 @@ class AutoRegisterMap extends Map {
95
98
  export class MemSettings {
96
99
  /** 已注册命名空间(对齐真实 dsh-settings:插件激活时注册;未注册的 describe/replace 抛错) */
97
100
  registered = new Set();
101
+ /**
102
+ * 可选(宿主能力探测):**默认不提供** ⇒ 既有测试继续覆盖「逐名 describe」回退路径;
103
+ * 需要验证快路径的测试自行挂上(真实宿主 DshSettingsFacade 始终提供)。
104
+ */
105
+ describeAll;
98
106
  /** 有值的命名空间自动视为已注册 */
99
107
  ns = new AutoRegisterMap(this.registered);
100
108
  async describe(namespace, _opts) {
@@ -167,17 +175,44 @@ export class MemWorkspace {
167
175
  async writeRecord(r) { this.records.set(r.id, r); }
168
176
  async removeRecord(id) { this.records.delete(id); }
169
177
  }
178
+ /**
179
+ * 内存 patch 门面,**按层分桶**(issue #71)。
180
+ *
181
+ * `lines` 仍是**用户(home)层**的桶 —— 绝大多数既有测试只关心「一行进、一行出」,
182
+ * 直接操作 `lines` 即可,语义与改造前完全一致。其它层(profile 层)走 `byFile`:
183
+ * 缺省空桶,因此「读两层」的调用方在旧测试里读到的 profile 层就是空的,不会污染断言。
184
+ *
185
+ * 分层用 `byFile(rel)` 显式取桶,避免测试里手写路径字符串。
186
+ */
170
187
  export class MemPatch {
188
+ /** 用户(home)层桶:`cordis.patch.yml`。 */
171
189
  lines = new Map();
172
- async readPatchLines(_file) {
173
- return [...this.lines.values()];
190
+ /** 其它层桶:层路径 → 行表(profile 层等)。 */
191
+ byFile = new Map();
192
+ /** 取(必要时建)某一层的桶;用户层恒为 `lines`(与旧测试兼容)。 */
193
+ bucket(file) {
194
+ if (file === USER_PATCH_FILE)
195
+ return this.lines;
196
+ let bucket = this.byFile.get(file);
197
+ if (bucket === undefined) {
198
+ bucket = new Map();
199
+ this.byFile.set(file, bucket);
200
+ }
201
+ return bucket;
202
+ }
203
+ async readPatchLines(file) {
204
+ if (file === USER_PATCH_FILE)
205
+ return [...this.lines.values()];
206
+ const bucket = this.byFile.get(file);
207
+ return bucket === undefined ? [] : [...bucket.values()];
174
208
  }
175
- async applyPatchChanges(_file, changes) {
209
+ async applyPatchChanges(file, changes) {
210
+ const bucket = this.bucket(file);
176
211
  for (const c of changes) {
177
212
  if (c.action === 'remove')
178
- this.lines.delete(c.lineId);
213
+ bucket.delete(c.lineId);
179
214
  else
180
- this.lines.set(c.lineId, { lineId: c.lineId, raw: c.raw });
215
+ bucket.set(c.lineId, { lineId: c.lineId, raw: c.raw });
181
216
  }
182
217
  }
183
218
  }
@@ -224,6 +259,8 @@ export class MockHostContext {
224
259
  fs;
225
260
  /** 可选(issue #45):会话存储端口;测试按需注入(缺省 = 宿主未提供该能力)。 */
226
261
  sessions;
262
+ /** 可选(issue #71):技能服务端口;测试按需注入(缺省 = 退回纯目录扫描)。 */
263
+ skills;
227
264
  constructor(platform, homeDir, profile) {
228
265
  this.platform = platform;
229
266
  this.homeDir = homeDir;
@@ -29,6 +29,50 @@ export function isCreatableWorkspacePath(path, platform = process.platform) {
29
29
  function reasonOf(error) {
30
30
  return error instanceof Error ? error.message : String(error);
31
31
  }
32
+ /**
33
+ * 按记录 id 归并工作区记录(audit-foreign foreign-F2;与上游内核 FIX-foreign 同一口径)。
34
+ *
35
+ * 为什么必须归并:记录 id 由 `projectKeyOf(path)` 派生(多对一 —— 分隔符 run 会被折叠),
36
+ * 同一目录的两种写法(Windows 反斜杠/正斜杠、posix `/a/b` 与 `/a//b`)会产出**同一个 id 的两条记录**。
37
+ * 不归并的话:计划里出现同 id 的两条 Create(一次决策命中两条)、applyItem 按 id 只能取到第一条
38
+ * → 第二条记录的 path/title/sessionIds 静默消失(analysis.valid 仍为 true,零告警)。
39
+ *
40
+ * 归并规则:`sessionIds` 取**并集**(按 `sessionIdKey` 裸键去重,保持先到顺序;两种命名形态
41
+ * 指向同一条会话时只留一条),`path`/`title` 取**先到者**(与内核侧一致)。
42
+ * 返回顺序 = 首次出现顺序;`duplicateCount` 供调用方在计划里显式标注(绝不静默)。
43
+ */
44
+ function mergeWorkspaceRecordsById(records) {
45
+ const order = [];
46
+ const byId = new Map();
47
+ for (const rec of records) {
48
+ // 纵深防御(t73 的 F2):非对象记录直接跳过。第三方包可能带 null;validate 已拒(workspaces[]),
49
+ // 生产不可达 —— 但归并是**新增**调用点,不让它成为又一条内部 TypeError 的来源。
50
+ const raw = rec;
51
+ if (raw === null || typeof raw !== 'object')
52
+ continue;
53
+ // 只接受字符串元素(t73 的 F1):`sessionIdKey` 的入参契约是字符串,数字/null 元素会让它抛
54
+ // TypeError(base 上这些元素根本不经过 sessionIdKey —— 归并去重是新增调用点)。非法元素丢弃。
55
+ const ids = Array.isArray(rec.sessionIds)
56
+ ? rec.sessionIds.filter((sid) => typeof sid === 'string')
57
+ : [];
58
+ const hit = byId.get(rec.id);
59
+ if (hit === undefined) {
60
+ byId.set(rec.id, { rec: { ...rec, sessionIds: [...ids] }, duplicateCount: 1 });
61
+ order.push(rec.id);
62
+ continue;
63
+ }
64
+ hit.duplicateCount += 1;
65
+ const seen = new Set(hit.rec.sessionIds.map((s) => sessionIdKey(s)));
66
+ for (const sid of ids) {
67
+ const key = sessionIdKey(sid);
68
+ if (seen.has(key))
69
+ continue;
70
+ seen.add(key);
71
+ hit.rec.sessionIds.push(sid);
72
+ }
73
+ }
74
+ return order.map((id) => byId.get(id));
75
+ }
32
76
  export class WorkspacesAdapter {
33
77
  id = 'workspaces';
34
78
  displayName = 'Workspaces';
@@ -62,7 +106,11 @@ export class WorkspacesAdapter {
62
106
  async analyzeImport(data, ctx) {
63
107
  const msg = ctx.msg;
64
108
  const items = [];
65
- for (const rec of data.workspaces) {
109
+ // audit-foreign foreign-F2(适配器侧加固):同一 id 的多条记录(同一目录的多种写法 → 同一个
110
+ // 派生 id)必须先**归并**再规划。否则会产出同 id 的两条 Create(用户一次决策命中两条,
111
+ // 逐条决策语义错乱),且 applyItem 按 id 只能取到第一条 → 第二条记录的 path/title/sessionIds
112
+ // 静默消失。归并口径与上游内核一致:sessionIds 取并集(按裸键去重),path/title 取先到者。
113
+ for (const { rec, duplicateCount } of mergeWorkspaceRecordsById(data.workspaces)) {
66
114
  const id = `workspace:${rec.id}`;
67
115
  /** issue #45:本工作区拥有的会话 id 随计划项带上(导入选择器据此联动勾选会话) */
68
116
  const ownedSessions = {
@@ -70,24 +118,35 @@ export class WorkspacesAdapter {
70
118
  // 第二判据:sessionIds 覆盖不到的会话按「cwd 目录键相同」认领(与导出侧同一口径)
71
119
  projectKey: projectKeyOf(rec.path),
72
120
  };
121
+ // 归并发生时的可见标注(raw key=value,与既有 detail 的 current=/imported= 同风格;
122
+ // 绝不静默:计划里看得见「这条是被归并过的」)
123
+ const mergeDetail = duplicateCount > 1
124
+ ? `duplicateIdRecords=${duplicateCount} mergedSessionIds=${rec.sessionIds.length}`
125
+ : '';
73
126
  const existing = (await ctx.target.workspace.listRecords()).find((r) => r.id === rec.id);
74
127
  if (!existing) {
75
128
  items.push({
76
129
  id, kind: 'Create', adapter: 'workspaces',
77
130
  ...ownedSessions,
131
+ ...(mergeDetail !== '' ? { detail: mergeDetail } : {}),
78
132
  description: msg('adapter.workspaceCreate', { title: rec.title ?? rec.id, path: rec.path }), severity: 'info',
79
133
  target: { adapter: 'workspaces', ref: rec.id },
80
134
  });
81
135
  }
82
136
  else if (isDeepStrictEqual(existing, rec)) {
83
- items.push({ id, kind: 'Skip', adapter: 'workspaces', ...ownedSessions, description: msg('adapter.workspaceSame', { id: rec.id }), severity: 'info' });
137
+ items.push({
138
+ id, kind: 'Skip', adapter: 'workspaces', ...ownedSessions,
139
+ ...(mergeDetail !== '' ? { detail: mergeDetail } : {}),
140
+ description: msg('adapter.workspaceSame', { id: rec.id }), severity: 'info',
141
+ });
84
142
  }
85
143
  else {
86
144
  items.push({
87
145
  id, kind: 'Conflict', adapter: 'workspaces',
88
146
  ...ownedSessions,
89
147
  description: msg('adapter.workspaceDiff', { id: rec.id }),
90
- detail: `current=${JSON.stringify(existing)} imported=${JSON.stringify(rec)}`.slice(0, 200),
148
+ detail: (`current=${JSON.stringify(existing)} imported=${JSON.stringify(rec)}`
149
+ + (mergeDetail !== '' ? ' ' + mergeDetail : '')).slice(0, 200),
91
150
  severity: 'warning', target: { adapter: 'workspaces', ref: rec.id },
92
151
  });
93
152
  }
@@ -111,8 +170,19 @@ export class WorkspacesAdapter {
111
170
  if (!ref)
112
171
  return { ok: false, message: ctx.msg('adapter.missingTargetRef') };
113
172
  const data = ctx.sections.get('workspaces');
114
- const rec = data?.workspaces.find((r) => r.id === ref);
115
- if (!rec)
173
+ // audit-foreign foreign-F2(适配器侧加固):同一 id 可能有多条记录(同一目录的多种写法派生同一 id)。
174
+ // 此前 find() 恒取第一条 → 第二条记录的 path/title/sessionIds 静默丢失(analysis.valid 仍为 true、
175
+ // 零告警)。这里取**全部**同 id 记录并按同一口径归并后写入 —— 绝不静默丢弃。同 id 的两条计划项
176
+ // 即便都执行,也只是重复写同一条归并结果(幂等)。
177
+ // t73 的 F2(纵深防御,两入口行为一致):非数组 / 含 null 记录的畸形载荷一律不当成记录集 ——
178
+ // `r.id` 直接取会在 null 上抛(validate 已拒 ⇒ 生产不可达,但这里也不让它成为新的内部 TypeError 来源)。
179
+ const allRecords = data === undefined || !Array.isArray(data.workspaces) ? [] : data.workspaces;
180
+ const sameIdRecords = allRecords.filter((r) => {
181
+ const raw = r;
182
+ return raw !== null && typeof raw === 'object' && r.id === ref;
183
+ });
184
+ const rec = sameIdRecords.length === 0 ? undefined : mergeWorkspaceRecordsById(sameIdRecords)[0]?.rec;
185
+ if (rec === undefined)
116
186
  return { ok: false, message: ctx.msg('adapter.workspaceMissing', { ref }) };
117
187
  const notes = [];
118
188
  // issue #45 已知缺口①:备份里的工作区路径在本机不存在时,DSH registry.create() 的
@@ -195,6 +265,28 @@ export class WorkspacesAdapter {
195
265
  for (const w of section.workspaces) {
196
266
  if (w === null || typeof w !== 'object' || typeof w.id !== 'string' || typeof w.path !== 'string') {
197
267
  issues.push({ path: 'workspaces[]', message: msg('adapter.validate.workspaceIdentity'), severity: 'error' });
268
+ continue;
269
+ }
270
+ // ui-F3:sessionIds 是会话可见性的必需字段(docs/spec/bundle-format-v1.md §6),
271
+ // 而 analyzeImport / finalizeImport 直接读 rec.sessionIds.length —— 缺它有两条后果:
272
+ // ① validate 放过后 analyzeImport 抛内部 TypeError → 整包被拦下且文案不可行动;
273
+ // ② 即便绕过,工作区记录也失去会话归属。这里按「必需数组」显式报出字段名。
274
+ if (!Array.isArray(w.sessionIds)) {
275
+ issues.push({ path: `workspaces.${String(w.id)}.sessionIds`, message: msg('adapter.validate.array', { subject: 'sessionIds' }), severity: 'error' });
276
+ }
277
+ else {
278
+ // t73 的 F1:**元素级**校验 —— 归并去重会对每个元素调 sessionIdKey(字符串契约),
279
+ // 非字符串元素会让 analyzeImport / applyItem 抛内部 TypeError(validate 放行 ⇒ 整包被拦、文案不可行动)。
280
+ // 这里显式报出下标,让这种包在 validate 阶段就被拦下。
281
+ w.sessionIds.forEach((sid, i) => {
282
+ if (typeof sid !== 'string') {
283
+ issues.push({
284
+ path: `workspaces.${String(w.id)}.sessionIds[${i}]`,
285
+ message: msg('adapter.validate.string', { subject: `sessionIds[${i}]` }),
286
+ severity: 'error',
287
+ });
288
+ }
289
+ });
198
290
  }
199
291
  }
200
292
  }
@@ -4,6 +4,7 @@ import { readSafeModeMarkerSync } from '../core/phase3-host.ts';
4
4
  import { type SnapshotMeta } from '../core/restore.ts';
5
5
  import { type BackupFileMeta } from '../sync/backup-files.ts';
6
6
  import { type SessionHealthScanResult } from '../utils/session-health-scan.ts';
7
+ import type { SessionVerifyResult } from '../utils/session-verify.ts';
7
8
  /** 进程是否存活(不发送信号,只探测)。EPERM = 存在但无权限发信号 → 仍算存活。 */
8
9
  export declare function isProcessAlive(pid: number): boolean;
9
10
  /** 一个在跑的 DSH 实例(来自 `<root>/running/<profile>.json` 心跳)。 */
@@ -229,25 +230,71 @@ export interface SessionLogRepairRow {
229
230
  /** 备份文件名(与日志同目录,回滚只认台账,不认用户给的路径) */
230
231
  backupName?: string;
231
232
  repairId?: string;
233
+ /**
234
+ * 写后真 codec 复验结论(**逐字段透传** service 的 SessionVerifyResult,不改造/不丢字段)。
235
+ * JSON 消费者必须据它区分三态:verified+equivalentToReadPath(现役读盘可读)/
236
+ * verified+!equivalentToReadPath(只有迁移链能还原)/ !verified(未验证,原因见 reason)。
237
+ * 预览路径不跑复验门 ⇒ 这一项缺省。
238
+ */
239
+ verify?: SessionVerifyResult;
240
+ /** 确定性失败(reason='verify-failed')时:本次备份的自动回滚是否成功(false = 目标仍是修复后字节) */
241
+ rolledBack?: boolean;
232
242
  message: string;
233
243
  }
234
244
  export interface InlineRepairOutcome {
235
245
  ok: boolean;
236
246
  dryRun: boolean;
237
247
  rows: SessionLogRepairRow[];
238
- /** 页面用的等价命令 */
248
+ /**
249
+ * 离线出口命令(可复制去终端)。
250
+ *
251
+ * **不是本入口的等价实现** —— 见 `repairSessionLogInline` 的能力边界:`sessions repair` 只做
252
+ * 布局归位与重复 id 隔离,不改写会话字节;字节级离线通道只有救急台 `dcm web`。
253
+ */
239
254
  commands: string[];
240
255
  error?: string;
241
256
  }
257
+ /**
258
+ * 真 codec 复验结论 → 一句人话(**三态**;服务层 utils/session-verify.ts 的定义为准)。
259
+ *
260
+ * · verified && equivalentToReadPath → 「现役读盘可读」= header.version === catalog.currentVersion === 已装版本,
261
+ * DSH 的现役读盘路径此刻就能直接读它;
262
+ * · verified && !equivalentToReadPath → 只有**迁移链**能还原(pre-v4 日志:官方 resolveCurrentLog 对本代之前的
263
+ * 代际返回 undefined,即「没有现役日志」);
264
+ * · !verified → 「**未验证**」= 本机跑不了真 codec 门(如 available catalog 缺席 / 代际对不上 /
265
+ * 缺子会话事实)——**这是最常见的一态,必须说出来**,绝不能让它看起来像成功;
266
+ * · verify 缺失(改造前的旧结果 / 预览路径)→ 空串:调用方保持原文案,绝不臆造「已验证」。
267
+ */
268
+ export declare function sessionVerifyNoteOf(verify: SessionVerifyResult | undefined): string;
269
+ /** 会话修复行的文案入参(只吃机器可读事实 —— 便于单测与两边共用)。 */
270
+ export interface SessionRepairRowFacts {
271
+ ok: boolean;
272
+ reason?: string;
273
+ droppedRows?: number;
274
+ backupName?: string;
275
+ rolledBack?: boolean;
276
+ verify?: SessionVerifyResult;
277
+ }
278
+ /**
279
+ * 会话修复行 → message(CLI 与离线救急台**同一份**文案)。
280
+ *
281
+ * 成功路径按 verify 分三态;**!verified 时不出现无限定的成功表述**(先声明未验证,再如实说写入已生效);
282
+ * verify 缺失时**逐字保持改造前的文案**(向后兼容,不臆造);verify-failed 时说明已自动回滚 + 官方错误 detail。
283
+ */
284
+ export declare function sessionRepairRowMessage(facts: SessionRepairRowFacts): string;
242
285
  /**
243
286
  * **就地修复「重放重复行」**(离线救急台 /sessions 页的写入口)。
244
287
  *
245
288
  * 能力边界(**不得放宽**):
246
289
  * - 只做 `session-repair-service` 已有的那一类 —— 字节相同 + seq 相同的重放重复行(零损失);
247
290
  * 其余类别(合成 closer / seq 空洞 / 不可解析行 / 容器非法 / header 不可读 / 子代理缺父)
248
- * **只报告 + 给离线命令**,绝不在这里扩张(设计稿 §10.3/§10.4 的 G-23 界限)。
291
+ * **只报告**,绝不在这里扩张(设计稿 §10.3/§10.4 的 G-23 界限)。
292
+ * - **离线出口只有一个**:救急台 `dsh-config-manager web`(别名 `dcm web`)—— 它调用的就是本函数。
293
+ * CLI 的 `sessions repair`(加 `--fix`/`--apply` 才落盘)**不是**字节级修复的等价命令:
294
+ * 它只做**布局归位**(按首帧 cwd 把目录搬回 `projectKeyOf(cwd)`)与**重复 id 隔离**,不碰会话字节。
295
+ * 此前把 `sessions repair --apply` 写成「等价命令」是不实表述(已订正,见下面的 `commands`)。
249
296
  * - 三道写入门由服务层自持:单元必须解析到会话根内 / 无 `session.lock` / 文件不在 30s 静止期内;
250
- * 「DSH 已停」这一条由 `checkWriteGates` 在这里补上(= 与 CLI `sessions repair --apply` 同一道门,
297
+ * 「DSH 已停」这一条由 `checkWriteGates` 在这里补上(与 CLI 写动作共用同一道写入门,
251
298
  * 比应用内 T8 通道更严:T8 跑在 DSH 内部,靠静止期而非停机)。
252
299
  * - 与 CLI 同源:预览-应用指纹一致(TOCTOU)/ 时间戳备份 / 原子换入 / 写后复验,全在服务层。
253
300
  *
@@ -19,6 +19,7 @@ import { EXPORTS_RETENTION_DEFAULT_MS, MARKET_RETENTION_DEFAULT_MS, TMP_RETENTIO
19
19
  import { scanDiskUsage } from '../core/disk-usage.js';
20
20
  import { readSafeModeMarkerSync, safeModeMarkerPath } from '../core/phase3-host.js';
21
21
  import { listSnapshots } from '../core/restore.js';
22
+ import { readInstalledVersion, resolveProfileDir } from '../core/plugin-cli.js';
22
23
  import { DshProfileError, DshProfileManager } from '../profiles/dsh-profile-manager.js';
23
24
  import { DshProfileLauncher, LAUNCHES_FILENAME, parseLaunches } from '../profiles/dsh-profile-launcher.js';
24
25
  import { DshProfileRuntimeRegistry, parseRuntimeRecord, RUNTIME_STALE_MS } from '../profiles/dsh-profile-runtime.js';
@@ -345,15 +346,74 @@ export async function checkWriteGates(controlRoots, options = { needsDshStopped:
345
346
  }
346
347
  return { ok: true };
347
348
  }
349
+ /* ----------------------------------------------- 会话修复行的文案(纯函数,可单测) */
350
+ /**
351
+ * 真 codec 复验结论 → 一句人话(**三态**;服务层 utils/session-verify.ts 的定义为准)。
352
+ *
353
+ * · verified && equivalentToReadPath → 「现役读盘可读」= header.version === catalog.currentVersion === 已装版本,
354
+ * DSH 的现役读盘路径此刻就能直接读它;
355
+ * · verified && !equivalentToReadPath → 只有**迁移链**能还原(pre-v4 日志:官方 resolveCurrentLog 对本代之前的
356
+ * 代际返回 undefined,即「没有现役日志」);
357
+ * · !verified → 「**未验证**」= 本机跑不了真 codec 门(如 available catalog 缺席 / 代际对不上 /
358
+ * 缺子会话事实)——**这是最常见的一态,必须说出来**,绝不能让它看起来像成功;
359
+ * · verify 缺失(改造前的旧结果 / 预览路径)→ 空串:调用方保持原文案,绝不臆造「已验证」。
360
+ */
361
+ export function sessionVerifyNoteOf(verify) {
362
+ if (verify === undefined)
363
+ return '';
364
+ if (verify.verified) {
365
+ return verify.equivalentToReadPath
366
+ ? '真 codec 复验通过(现役读盘可读)。'
367
+ : '真 codec 复验通过(迁移链可还原,非现役读盘)。';
368
+ }
369
+ return '未验证:本机无法运行真 codec 复验(' + verify.reason + ')。';
370
+ }
371
+ /**
372
+ * 会话修复行 → message(CLI 与离线救急台**同一份**文案)。
373
+ *
374
+ * 成功路径按 verify 分三态;**!verified 时不出现无限定的成功表述**(先声明未验证,再如实说写入已生效);
375
+ * verify 缺失时**逐字保持改造前的文案**(向后兼容,不臆造);verify-failed 时说明已自动回滚 + 官方错误 detail。
376
+ */
377
+ export function sessionRepairRowMessage(facts) {
378
+ const dropped = String(facts.droppedRows ?? 0);
379
+ const backup = String(facts.backupName ?? '(未落盘)');
380
+ if (!facts.ok) {
381
+ if (facts.reason === 'verify-failed') {
382
+ const rolled = facts.rolledBack === true
383
+ ? '已自动回滚到修复前字节'
384
+ : facts.rolledBack === false
385
+ ? '已自动回滚未成功(目标仍是修复后字节,需人工处理)'
386
+ : '已自动回滚(服务层未回传结果,请以目标字节为准)';
387
+ const detail = facts.verify !== undefined && !facts.verify.verified && facts.verify.detail !== undefined && facts.verify.detail !== ''
388
+ ? ';官方错误:' + facts.verify.detail
389
+ : '';
390
+ return '未修复(verify-failed):真 codec 复验未通过,' + rolled + detail + '。';
391
+ }
392
+ return '未修复(' + (facts.reason ?? 'unknown') + '):原文件未改动。';
393
+ }
394
+ if (facts.verify === undefined) {
395
+ // 向后兼容:没有复验结论就不添油加醋(改造前逐字如此)
396
+ return '已丢弃 ' + dropped + ' 行重放重复事件;备份 ' + backup;
397
+ }
398
+ if (facts.verify.verified) {
399
+ return '已丢弃 ' + dropped + ' 行重放重复事件;备份 ' + backup + '。' + sessionVerifyNoteOf(facts.verify);
400
+ }
401
+ return sessionVerifyNoteOf(facts.verify)
402
+ + '写入已生效(已丢弃 ' + dropped + ' 行重放重复事件;备份 ' + backup + ')——本机 DSH 能否现役读它**没有被证明**,可用 repairId 回滚。';
403
+ }
348
404
  /**
349
405
  * **就地修复「重放重复行」**(离线救急台 /sessions 页的写入口)。
350
406
  *
351
407
  * 能力边界(**不得放宽**):
352
408
  * - 只做 `session-repair-service` 已有的那一类 —— 字节相同 + seq 相同的重放重复行(零损失);
353
409
  * 其余类别(合成 closer / seq 空洞 / 不可解析行 / 容器非法 / header 不可读 / 子代理缺父)
354
- * **只报告 + 给离线命令**,绝不在这里扩张(设计稿 §10.3/§10.4 的 G-23 界限)。
410
+ * **只报告**,绝不在这里扩张(设计稿 §10.3/§10.4 的 G-23 界限)。
411
+ * - **离线出口只有一个**:救急台 `dsh-config-manager web`(别名 `dcm web`)—— 它调用的就是本函数。
412
+ * CLI 的 `sessions repair`(加 `--fix`/`--apply` 才落盘)**不是**字节级修复的等价命令:
413
+ * 它只做**布局归位**(按首帧 cwd 把目录搬回 `projectKeyOf(cwd)`)与**重复 id 隔离**,不碰会话字节。
414
+ * 此前把 `sessions repair --apply` 写成「等价命令」是不实表述(已订正,见下面的 `commands`)。
355
415
  * - 三道写入门由服务层自持:单元必须解析到会话根内 / 无 `session.lock` / 文件不在 30s 静止期内;
356
- * 「DSH 已停」这一条由 `checkWriteGates` 在这里补上(= 与 CLI `sessions repair --apply` 同一道门,
416
+ * 「DSH 已停」这一条由 `checkWriteGates` 在这里补上(与 CLI 写动作共用同一道写入门,
357
417
  * 比应用内 T8 通道更严:T8 跑在 DSH 内部,靠静止期而非停机)。
358
418
  * - 与 CLI 同源:预览-应用指纹一致(TOCTOU)/ 时间戳备份 / 原子换入 / 写后复验,全在服务层。
359
419
  *
@@ -363,12 +423,16 @@ export async function checkWriteGates(controlRoots, options = { needsDshStopped:
363
423
  export async function repairSessionLogInline(options, controlRoots) {
364
424
  const base = {
365
425
  ok: true, dryRun: !options.apply, rows: [],
366
- commands: ['dsh-config-manager sessions repair --apply # 等价命令(离线时)'],
426
+ // 不得再把 `sessions repair --apply` 说成等价命令:它只做布局归位与重复 id 隔离,不改写会话字节。
427
+ commands: [
428
+ 'dsh-config-manager web # 离线救急台:字节级就地修复的唯一离线通道(别名 dcm web)',
429
+ 'dsh-config-manager sessions repair # 只做布局归位与重复 id 隔离,不改写会话字节(--fix/--apply 才落盘)',
430
+ ],
367
431
  };
368
432
  if (options.unitIds.length === 0)
369
433
  return { ...base, ok: false, error: '没有选中任何会话单元。' };
370
434
  if (options.apply) {
371
- // 与 sessions repair --apply 同一道门:改写会话字节必须先停 DSH(+ SAFE MODE + 环境锁)
435
+ // 与 CLI 写动作共用同一道写入门:改写会话字节必须先停 DSH(+ SAFE MODE + 环境锁)
372
436
  const gate = await checkWriteGates(controlRoots, { needsDshStopped: true });
373
437
  if (!gate.ok)
374
438
  return { ...base, ok: false, error: gate.reason };
@@ -388,7 +452,8 @@ export async function repairSessionLogInline(options, controlRoots) {
388
452
  ...(preview.expect !== undefined ? { expect: preview.expect } : {}),
389
453
  message: preview.ok
390
454
  ? '可零损失修复:将丢弃 ' + String(preview.droppedRows ?? 0) + ' 行重放重复事件。'
391
- : '不可修复(' + (preview.reason ?? 'unknown') + '):这一类只报告,请用离线命令或保留原样。',
455
+ : '不可修复(' + (preview.reason ?? 'unknown') + '):这一类只报告,本入口绝不改写它的字节;'
456
+ + '字节级离线出口是救急台 dsh-config-manager web(sessions repair 只做布局归位与重复 id 隔离)。',
392
457
  });
393
458
  continue;
394
459
  }
@@ -397,18 +462,22 @@ export async function repairSessionLogInline(options, controlRoots) {
397
462
  homeDir: options.home, dataDir: options.dataDir, unitId,
398
463
  ...(expect !== undefined ? { expect } : {}),
399
464
  });
400
- rows.push({
401
- unitId,
465
+ // 文案与结构化字段同源:verify 逐字段透传(JSON 消费者据它区分三态),message 由纯函数分派
466
+ const facts = {
402
467
  ok: applied.ok,
403
468
  ...(applied.reason !== undefined ? { reason: applied.reason } : {}),
404
469
  ...(applied.droppedRows !== undefined ? { droppedRows: applied.droppedRows } : {}),
470
+ ...(applied.backupName !== undefined ? { backupName: applied.backupName } : {}),
471
+ ...(applied.rolledBack !== undefined ? { rolledBack: applied.rolledBack } : {}),
472
+ ...(applied.verify !== undefined ? { verify: applied.verify } : {}),
473
+ };
474
+ rows.push({
475
+ unitId,
476
+ ...facts,
405
477
  ...(applied.bytesBefore !== undefined ? { bytesBefore: applied.bytesBefore } : {}),
406
478
  ...(applied.bytesAfter !== undefined ? { bytesAfter: applied.bytesAfter } : {}),
407
- ...(applied.backupName !== undefined ? { backupName: applied.backupName } : {}),
408
479
  ...(applied.repairId !== undefined ? { repairId: applied.repairId } : {}),
409
- message: applied.ok
410
- ? '已丢弃 ' + String(applied.droppedRows ?? 0) + ' 行重放重复事件;备份 ' + String(applied.backupName ?? '(未落盘)')
411
- : '未修复(' + (applied.reason ?? 'unknown') + '):原文件未改动。',
480
+ message: sessionRepairRowMessage(facts),
412
481
  });
413
482
  }
414
483
  return { ...base, ok: rows.every((r) => r.ok || !options.apply), rows };
@@ -782,6 +851,27 @@ export async function unlockEncryptedBackup(exportsDir, fileName, password) {
782
851
  function isDangerousRestoreKind(kind) {
783
852
  return kind === 'hostFileRestore' || kind === 'hostFileRemove' || kind === 'fileRestore' || kind === 'fileRemove' || kind === 'pluginRemove';
784
853
  }
854
+ /** 本机 DSH 包名(T8-F3;与 core/restore.ts 的 `DSH_PACKAGE_NAME` 同值 —— 两处读的是同一个文件)。 */
855
+ const DSH_PACKAGE_NAME = '@deepseek-ai/dsh';
856
+ /**
857
+ * 目标档案当前的 DSH 版本(T8-F3;离线 CLI 的 best-effort 来源)。
858
+ *
859
+ * 为什么是这个来源:CLI 恰恰在 **DSH 已经起不来** 的场景下运行,没有「运行时权威版本」可问
860
+ * (那是宿主侧的东西:`HostContext.dshVersion` / `profileContext.installAnchor`)。这里与
861
+ * `core/restore.ts` 的兜底、档案页的 `DshProfileMeta.dshVersion` **读的是同一个文件**
862
+ * (`profiles/<p>/node_modules/@deepseek-ai/dsh/package.json`),所以显式传出不改变数值,
863
+ * 但把「版本判定来源」固定在一处(将来 CLI 若拿到更好的来源只改这里),且不再让 core 猜一次。
864
+ * 读不到 → undefined:core 按「版本未知」处理(不猜、不产出「版本不同」告警)。
865
+ */
866
+ function currentProfileDshVersion(paths) {
867
+ try {
868
+ return readInstalledVersion(resolveProfileDir(paths.homeDir, paths.profile), DSH_PACKAGE_NAME) ?? undefined;
869
+ }
870
+ catch {
871
+ // 档案名非法 / 目录不可读 → 与「没装」同样处理:不猜版本(绝不因此让恢复计划失败)
872
+ return undefined;
873
+ }
874
+ }
785
875
  /**
786
876
  * 取某个快照的**恢复计划**(只读;与 CLI \`restore --dry-run\` 同一实现)。
787
877
  *
@@ -796,6 +886,7 @@ export async function planSnapshotRestore(paths, snapshotId) {
796
886
  homeDir: paths.homeDir,
797
887
  profile: paths.profile,
798
888
  snapshotsRoot: paths.snapshotsDir,
889
+ currentDshVersion: currentProfileDshVersion(paths),
799
890
  });
800
891
  return {
801
892
  ok: true,
@@ -837,6 +928,7 @@ export async function runSnapshotRestore(paths, snapshotId) {
837
928
  homeDir: paths.homeDir,
838
929
  profile: paths.profile,
839
930
  snapshotsRoot: paths.snapshotsDir,
931
+ currentDshVersion: currentProfileDshVersion(paths),
840
932
  });
841
933
  return {
842
934
  ok: report.failed.length === 0,
@@ -0,0 +1,65 @@
1
+ /**
2
+ * 离线 CLI 帮助文案的单一事实源(2026-10 整理)。
3
+ *
4
+ * 帮助分两层,都由本文件渲染,**绝不在 index.ts 里再散写一遍文案**:
5
+ * - 速查页(`dcm help`):按风险分组的命令表 + 通用选项,目标是「30 行内扫一眼就知道用哪个」;
6
+ * - 详情页(`dcm <命令> --help` / `dcm help <命令>`):说明 / 选项 / 退出码 / 示例。
7
+ *
8
+ * 两条渲染约定(改文案前先读):
9
+ * 1. 速查页的「用法」列**只用 ASCII**(占位符写 `<file|path>` 而不是 `<文件|路径>`):
10
+ * CJK 是双宽字符,混进 padEnd 对齐的一列会让整页参差。
11
+ * 2. 退出码必须与实现一致 —— 判定逻辑在各 run* 函数里,这里只是结论;改了行为要同步改这里。
12
+ */
13
+ export declare const IMPORT_SOURCE_IDS: readonly string[];
14
+ /** 详情页里的一个「左列 + 右列」条目(选项 / 退出码 / 子命令都用它) */
15
+ export interface HelpEntry {
16
+ /** 左列,只用 ASCII */
17
+ readonly flag: string;
18
+ /** 右列说明(中文) */
19
+ readonly desc: string;
20
+ }
21
+ export interface HelpSection {
22
+ readonly title: string;
23
+ readonly entries: readonly HelpEntry[];
24
+ }
25
+ export interface CommandHelp {
26
+ /** 命令名(`dcm <name> --help` 与 `dcm help <name>` 的键) */
27
+ readonly name: string;
28
+ /** 详情页首行:`<name> — <title>` */
29
+ readonly title: string;
30
+ /** 速查页里那一行的用法片段(ASCII only、尽量短) */
31
+ readonly usage: string;
32
+ /** 速查页里那一行的摘要(中文、一行) */
33
+ readonly summary: string;
34
+ /** 详情页「用法」段(不含 `dcm` 前缀,每项一行) */
35
+ readonly usageLines: readonly string[];
36
+ /** 详情页「说明」段(每项一行) */
37
+ readonly description: readonly string[];
38
+ /** 详情页的键值段,按声明顺序渲染 */
39
+ readonly sections: readonly HelpSection[];
40
+ /** 详情页「示例」 */
41
+ readonly examples: readonly string[];
42
+ }
43
+ export interface HelpRow {
44
+ /** 用法片段(ASCII only) */
45
+ readonly usage: string;
46
+ /** 一行摘要 */
47
+ readonly summary: string;
48
+ /** 该行指向的详情页(CommandHelp.name;`sessions` 有三行指向同一页) */
49
+ readonly help: string;
50
+ }
51
+ export interface HelpGroup {
52
+ readonly title: string;
53
+ readonly rows: readonly HelpRow[];
54
+ }
55
+ /** 通用选项(所有命令共有)——命令专属选项只出现在各自的详情页里 */
56
+ export declare const GENERAL_OPTIONS: readonly HelpEntry[];
57
+ /** 分组顺序 = 推荐阅读顺序:先诊断、再备份、再修复、最后才是危险的重装 */
58
+ export declare const HELP_GROUPS: readonly HelpGroup[];
59
+ export declare const COMMAND_HELP: readonly CommandHelp[];
60
+ /** 速查页「用法」列的宽度(全表统一,保证摘要起始列一致) */
61
+ export declare function usageColumnWidth(): number;
62
+ /** 速查页(`dcm help`):分组命令表 + 通用选项 + 上手顺序 */
63
+ export declare function renderOverview(version: string): string[];
64
+ /** 单命令详情页;不认识的名字返回 null(调用方决定怎么报错) */
65
+ export declare function renderCommandHelp(name: string, version: string): string[] | null;