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
package/lib/cli/index.js CHANGED
@@ -53,8 +53,23 @@ const VERIFY_ONLY_FLAGS = new Set(['--json']);
53
53
  const BACKUP_ONLY_FLAGS = new Set(['--sections', '--out']);
54
54
  /** backup 明确不支持的 restore 系参数(--dry-run 对 backup 有效,故不在此表) */
55
55
  const BACKUP_REJECTED_FLAGS = new Set(['--id', '--profile', '--settings']);
56
- /** 仅 restore 子命令允许的参数 */
57
- const RESTORE_ONLY_FLAGS = new Set(['--id', '--dry-run', '--profile', '--settings']);
56
+ /**
57
+ * 每个参数**真正被哪些子命令使用**(白名单,而不是「想起来才拒绝某一条」)。
58
+ *
59
+ * 为什么要有这张表:此前只有 `snapshots` 会拒绝 restore 系参数,于是
60
+ * `verify --dry-run / --profile / --settings` 被**悄悄接受并忽略** —— 用户会以为自检跑成了
61
+ * 「只读预演」。纪律是「本命令用不到的参数一律拒绝」,而不是「反正不影响结果就算了」。
62
+ */
63
+ const FLAG_ALLOWED_COMMANDS = new Map([
64
+ // --id:restore 的目标快照;verify 把它当位置参数的等价写法(见 help 的 verify 段)。
65
+ ['--id', new Set(['restore', 'verify'])],
66
+ // --dry-run:restore(只打印计划)/ reinstall(只打印重装步骤)/ backup(只打印打包清单)。
67
+ ['--dry-run', new Set(['restore', 'reinstall', 'backup'])],
68
+ // --profile:restore 要恢复的档案;web 用它决定救急台操作哪个档案(两条都真的在用,故放行)。
69
+ ['--profile', new Set(['restore', 'web'])],
70
+ // --settings:只有 restore 的「覆盖 settings 文件路径」用得到。
71
+ ['--settings', new Set(['restore'])],
72
+ ]);
58
73
  /** 仅 reinstall 子命令允许的参数 */
59
74
  const REINSTALL_ONLY_FLAGS = new Set(['--yes', '--list', '--wipe-config', '--version']);
60
75
  /** 仅 import 允许的参数(--out/--dry-run 与 backup/restore 共用,故不在此表) */
@@ -281,8 +296,10 @@ export function parseCli(argv) {
281
296
  options.json = true;
282
297
  continue;
283
298
  }
284
- if (command === 'snapshots' && RESTORE_ONLY_FLAGS.has(flag)) {
285
- return { ok: false, error: `snapshots 子命令不支持参数 / flag not allowed here: ${flag}` };
299
+ // 本命令用不到的参数一律拒绝(绝不「悄悄接受并忽略」):白名单见 FLAG_ALLOWED_COMMANDS。
300
+ const allowedCommands = FLAG_ALLOWED_COMMANDS.get(flag);
301
+ if (allowedCommands !== undefined && !allowedCommands.has(command)) {
302
+ return { ok: false, error: `${command} 子命令不支持参数 / flag not allowed here: ${flag}` };
286
303
  }
287
304
  if (command !== 'reinstall' && REINSTALL_ONLY_FLAGS.has(flag)) {
288
305
  return { ok: false, error: `${command} 子命令不支持参数 / flag not allowed here: ${flag}` };
@@ -886,14 +903,29 @@ export async function runCli(argv, io = defaultIo, env = process.env, deps = {})
886
903
  }
887
904
  const dataDir = lockDataDir;
888
905
  if (options.command === 'snapshots') {
889
- const metas = await listSnapshots(dataDir);
906
+ // 「读不到」≠「没有快照」(core/restore.ts 的 listSnapshots 只有 ENOENT 才算没有):
907
+ // 目录存在却读不出来时必须把事实打到 stderr 并以非 0 退出,绝不显示成「无快照」。
908
+ const probe = {};
909
+ const metas = await listSnapshots(dataDir, probe);
910
+ if (probe.unreadable !== undefined) {
911
+ io.error(probe.unreadable);
912
+ io.error('读不到 ≠ 没有快照:请检查该目录的权限/形态,或用 --data-dir 指定正确位置。/ unreadable is not the same as no snapshots');
913
+ return 1;
914
+ }
890
915
  printSnapshots(metas, io);
891
916
  return 0;
892
917
  }
893
918
  // restore
894
919
  let id = options.id;
895
920
  if (id === undefined) {
896
- const picked = pickDefaultSnapshotId(await listSnapshots(dataDir));
921
+ // 与 snapshots 同一口径:读不出来时如实打印(restore 的退出语义不变 —— 拿不到候选快照即拒绝执行)。
922
+ const probe = {};
923
+ const picked = pickDefaultSnapshotId(await listSnapshots(dataDir, probe));
924
+ if (probe.unreadable !== undefined) {
925
+ io.error(probe.unreadable);
926
+ io.error('读不到 ≠ 没有快照:恢复拒绝在「不知道有哪些快照」的状态下继续。/ unreadable is not the same as no snapshots; refusing to restore');
927
+ return 1;
928
+ }
897
929
  if (picked === null) {
898
930
  io.error('没有可用快照(无快照或全部已回滚)/ no usable snapshot (none or all rolled-back)');
899
931
  return 1;
@@ -922,7 +954,8 @@ export async function runCli(argv, io = defaultIo, env = process.env, deps = {})
922
954
  }
923
955
  // Phase 3 SAFE MODE:存在未恢复 transaction → 拒绝 destructive(CLI 不旁路 SAFE MODE)。
924
956
  // 候选根与真实 restore 的锁目录/journal 根同源(均为 <root> = dirname(快照目录) 的那一族)。
925
- const safeMsg2 = checkSafeModeBlocked(resolveControlRoots({ dataRoot: options.dataRoot, dataDir: options.dataDir }, env));
957
+ const controlRoots = resolveControlRoots({ dataRoot: options.dataRoot, dataDir: options.dataDir }, env);
958
+ const safeMsg2 = checkSafeModeBlocked(controlRoots);
926
959
  if (safeMsg2 !== null) {
927
960
  io.error(`拒绝执行:${safeMsg2}`);
928
961
  return 1;
@@ -939,12 +972,26 @@ export async function runCli(argv, io = defaultIo, env = process.env, deps = {})
939
972
  // Phase 3 P0-A:CLI restore 也创建 durable journal(关闭 bypass)。
940
973
  const recovery = new Phase3Recovery({ dataDir: path.dirname(dataDir), packageVersion: '0.1.0', fingerprintDataDir: path.dirname(dataDir) });
941
974
  await recovery.initFingerprint().catch(() => undefined);
942
- const report = await runWithMutationLock(lock, { op: 'cli-restore', target: id }, async (lockCtx) => {
943
- if (lockCtx !== null) {
944
- return (await recovery.runJournaled({ operationType: 'cli-restore', lockCtx, fn: () => restore(restoreOptions) })).result;
945
- }
946
- return restore(restoreOptions);
975
+ const outcome = await runWithMutationLock(lock, { op: 'cli-restore', target: id }, async (lockCtx) => {
976
+ // 第三道门(与救急台 actions.ts 的 checkWriteGates 同源、同序:SAFE MODE → 残留锁 → DSH 未运行):
977
+ // 恢复会覆盖/删除 $DSH_HOME 下**正在被实例使用**的文件。放在持有环境锁之后,于是「残留锁」仍先于它报出。
978
+ const running = await readRunningInstances(controlRoots);
979
+ if (running.length > 0)
980
+ return { kind: 'dsh-running', running };
981
+ return {
982
+ kind: 'restore',
983
+ report: lockCtx !== null
984
+ ? (await recovery.runJournaled({ operationType: 'cli-restore', lockCtx, fn: () => restore(restoreOptions) })).result
985
+ : await restore(restoreOptions),
986
+ };
947
987
  });
988
+ if (outcome.kind === 'dsh-running') {
989
+ io.error('检测到 DSH 正在运行(' + outcome.running.map((i) => i.name + ' pid=' + String(i.pid)).join('、')
990
+ + '):请先关闭 DSH 再执行恢复 —— 恢复会覆盖/删除 $DSH_HOME 下正在使用的文件。');
991
+ io.error('如 DSH 已起不来(无法正常关闭),改用本机救急台:dcm web(「恢复」页与 CLI 同一实现,可先停实例/回收残留锁)。');
992
+ return 1;
993
+ }
994
+ const report = outcome.report;
948
995
  printReport(report, io);
949
996
  return report.failed.length > 0 ? 1 : 0;
950
997
  }
@@ -956,36 +1003,44 @@ export async function runCli(argv, io = defaultIo, env = process.env, deps = {})
956
1003
  throw err;
957
1004
  }
958
1005
  }
959
- /* ------------------------------------------------------------ web:离线只读救急台 */
1006
+ /**
1007
+ * 派生救急台要用的四个目录 —— **root 只算一次**,再同时派生 snapshots / exports / locks / dataDir。
1008
+ *
1009
+ * 为什么必须统一:`--data-dir` 的历史语义是「快照目录 / 导出目录」,尾段命中 `snapshots`/`exports`
1010
+ * 时要**上溯一层**才是数据根。此前只对「那一条」取原值、另一条仍 `path.join(dataDir, ...)`,
1011
+ * 于是 `--data-dir <root>/snapshots` 会派生出 `<root>/snapshots/exports`(半上溯)——
1012
+ * 命令行与网页看到的目录事实立刻分叉。`--data-root` 恒为准。
1013
+ */
1014
+ export function resolveWebPaths(opts = {}, env = process.env) {
1015
+ const explicitRoot = opts.dataRoot !== undefined && opts.dataRoot !== '' ? path.resolve(opts.dataRoot) : undefined;
1016
+ const explicitDataDir = opts.dataDir !== undefined && opts.dataDir !== '' ? path.resolve(opts.dataDir) : undefined;
1017
+ const root = explicitRoot
1018
+ ?? (explicitDataDir === undefined
1019
+ ? path.join(resolveDshHome(env), 'dsh-config-manager')
1020
+ : ['snapshots', 'exports'].includes(path.basename(explicitDataDir))
1021
+ ? path.dirname(explicitDataDir)
1022
+ : explicitDataDir);
1023
+ return {
1024
+ root,
1025
+ dataDir: root,
1026
+ snapshotsDir: path.join(root, 'snapshots'),
1027
+ exportsDir: path.join(root, 'exports'),
1028
+ locksDir: path.join(root, 'locks'),
1029
+ controlRoots: resolveControlRoots({ dataRoot: explicitRoot, dataDir: explicitDataDir }, env),
1030
+ };
1031
+ }
960
1032
  /**
961
1033
  * 启动离线救急台(**只读**)。
962
1034
  *
963
1035
  * 为什么目录要在这一侧解析:`--data-dir / --data-root` 的既有语义只有本文件的 resolve* 系列说了算——
964
1036
  * 网页与各子命令必须看到同一份目录事实,否则迟早出现「命令行说没有、网页说有一个」的分叉。
965
- * 这里统一按**控制面根**派生(`<root>/snapshots` 与 `<root>/exports`)。`--data-dir` 的历史语义是
966
- * 「快照目录 / 导出目录」,所以尾段命中 `snapshots` / `exports` 时会**上溯一层**再派生
967
- * (否则 <root>/snapshots 会变成 <root>/snapshots/snapshots,快照列表恒空 —— 验收 F6);
968
- * `--data-root` 仍恒为准。
1037
+ * 目录派生统一走 `resolveWebPaths()`(`--data-dir` 尾段命中 snapshots/exports 时上溯一层;`--data-root` 恒为准)。
969
1038
  *
970
1039
  * 服务端只绑 127.0.0.1,端口缺省 0(内核分配);启动后打印带一次性 token 的 URL 并打开浏览器。
971
1040
  */
972
1041
  async function runWeb(options, io, env) {
973
1042
  const homeDir = options.home ?? resolveDshHome(env);
974
- const controlRoots = resolveControlRoots({ dataRoot: options.dataRoot, dataDir: options.dataDir }, env);
975
- const dataDir = controlRoots[0] ?? path.join(homeDir, 'dsh-config-manager');
976
- // --data-dir 的历史语义是「快照目录 / 导出目录」:用户按该语义传 <root>/snapshots 时,
977
- // 不能把它本身当作数据根(否则会派生出 <root>/snapshots/snapshots,真实快照必然看不见 —— 验收 F6)。
978
- // 尾段命中 snapshots / exports 就上溯一层;显式 --data-root 仍以它为准。
979
- const resolvedSnapshotsDir = options.dataRoot === undefined
980
- && options.dataDir !== undefined
981
- && ['snapshots', 'exports'].includes(path.basename(path.resolve(options.dataDir)))
982
- ? path.resolve(options.dataDir)
983
- : path.join(dataDir, 'snapshots');
984
- const snapshotsDir = resolvedSnapshotsDir;
985
- const exportsDir = options.dataDir !== undefined && path.basename(path.resolve(options.dataDir)) === 'exports'
986
- ? path.resolve(options.dataDir)
987
- : path.join(dataDir, 'exports');
988
- const locksDir = resolveRecoverLocksDir({ dataRoot: options.dataRoot, dataDir: options.dataDir }, env);
1043
+ const { dataDir, snapshotsDir, exportsDir, locksDir, controlRoots } = resolveWebPaths({ dataRoot: options.dataRoot, dataDir: options.dataDir }, env);
989
1044
  // 懒加载:http 服务端代码不进其他子命令(尤其 reinstall/restore 的救急路径)的启动链路
990
1045
  const { startConsoleServer } = await import('./web/server.js');
991
1046
  const server = await startConsoleServer({
@@ -997,6 +1052,9 @@ async function runWeb(options, io, env) {
997
1052
  locksDir,
998
1053
  controlRoots,
999
1054
  profile: options.profile,
1055
+ // 离线导出的 manifest.exporter.version 用**真实 CLI 版本**(与 backup 同一口径),
1056
+ // 不再写死 'rescue-console' —— 那会让产物声称一个不存在的导出器版本。
1057
+ exporterVersion: CLI_EXPORTER_VERSION,
1000
1058
  },
1001
1059
  version: CLI_EXPORTER_VERSION,
1002
1060
  port: options.port ?? 0,
@@ -53,7 +53,9 @@ export async function runSessionsInspect(options, io) {
53
53
  const blocking = result.summary.bySeverity.blocksStartup + result.summary.bySeverity.unloadable;
54
54
  if (options.json) {
55
55
  io.log(JSON.stringify({
56
- ok: true,
56
+ // ok 与退出码**同源**(0 ⟺ 无阻断级问题):此前恒 true,存在不可加载会话时
57
+ // CI 会看到 ok:true 而退出码 1 —— 两个字段互相矛盾,机器消费者只会信错一个。
58
+ ok: blocking === 0,
57
59
  readOnly: true,
58
60
  sessionsDir: result.sessionsDir,
59
61
  summary: { ...result.summary, untested: result.untested, unreadableEntries: result.unreadableEntries },
@@ -41,7 +41,12 @@ export function parseRepairMaps(maps) {
41
41
  }
42
42
  return { mappings, errors };
43
43
  }
44
- /** 扫描 <home>/sessions:projectKey 段 → 会话目录 → 首帧 cwd。任何读不出的都如实记为「无法判定」。 */
44
+ /**
45
+ * 扫描 <home>/sessions:projectKey 段 → 会话目录 → 首帧 cwd。任何读不出的都如实记为「无法判定」。
46
+ *
47
+ * 返回值里必须带 rootReadable:会话根存在但读不出来时,空列表只能说明「扫描失败」,
48
+ * 调用方据此拒绝给出「无需修复」+ exit 0(读不到 ≠ 无会话 —— 验收 F2 同口径)。
49
+ */
45
50
  async function scanSessions(sessionsRoot, io) {
46
51
  const out = [];
47
52
  let projectDirs;
@@ -50,11 +55,11 @@ async function scanSessions(sessionsRoot, io) {
50
55
  }
51
56
  catch (error) {
52
57
  // 会话根不是目录 / 读不出来(ENOTDIR、EACCES…):绝不能把裸异常抛给调用方
53
- // (救急台会因此吐一段 Node 原始 JSON 500 —— 验收 F3)。如实报错并返回空扫描结果。
58
+ // (救急台会因此吐一段 Node 原始 JSON 500 —— 验收 F3)。如实报错并标记「根不可读」。
54
59
  const code = error.code;
55
60
  io.error('会话根读不出来 / sessions root not readable(' + String(code ?? 'unknown') + '): ' + sessionsRoot);
56
61
  io.error(error instanceof Error ? error.message : String(error));
57
- return out;
62
+ return { sessions: out, rootReadable: false };
58
63
  }
59
64
  for (const entry of projectDirs) {
60
65
  if (!entry.isDirectory())
@@ -91,7 +96,7 @@ async function scanSessions(sessionsRoot, io) {
91
96
  });
92
97
  }
93
98
  }
94
- return out;
99
+ return { sessions: out, rootReadable: true };
95
100
  }
96
101
  /** 隔离目录:同一轮修复一个时间戳目录;**只搬不删**(用户可自行处理)。 */
97
102
  async function quarantineDirFor(sessionsRoot, stamp, action) {
@@ -115,8 +120,22 @@ export async function runSessionsRepair(options, io) {
115
120
  io.error(error);
116
121
  if (parsed.errors.length > 0)
117
122
  return 1;
118
- const scanned = await scanSessions(sessionsRoot, io);
119
- const plan = planSessionRepair(scanned, {
123
+ const scan = await scanSessions(sessionsRoot, io);
124
+ if (!scan.rootReadable) {
125
+ // 「读不到」≠「没有会话」:根存在但读不出来时,空扫描结果只说明**扫描失败** ——
126
+ // 绝不能给出「无需修复」+ exit 0(那等于把「读不到」说成「没问题」),--json 也不得回 ok:true。
127
+ const message = '会话根存在但读不出来:读不到 ≠ 没有会话 / sessions root exists but is unreadable; '
128
+ + 'unreadable is not the same as no sessions: ' + sessionsRoot;
129
+ io.error(message);
130
+ if (options.json) {
131
+ io.log(JSON.stringify({
132
+ ok: false, rootReadable: false, dryRun: options.fix !== true,
133
+ home: options.home, root: sessionsRoot, error: message,
134
+ }, null, 2));
135
+ }
136
+ return 1;
137
+ }
138
+ const plan = planSessionRepair(scan.sessions, {
120
139
  mappings: parsed.mappings,
121
140
  ...(options.keep !== undefined ? { keep: options.keep } : {}),
122
141
  });
@@ -25,6 +25,18 @@ export declare function formatBytes(bytes: number): string;
25
25
  type Cell = string | {
26
26
  html: string;
27
27
  };
28
+ /**
29
+ * 救急台的**页面导航表(唯一事实源)**:头部导航与 404 / 405 / 错误页的「可用页面」清单
30
+ * 全部由它派生 —— 此前那三处各写一份、且与现实不一致(把 JSON 自述的 `/healthz` 说成页面,
31
+ * 又漏掉 `/restore` / `/export`)。`/healthz` 是 JSON 自述、不是页面,故不在表内。
32
+ */
33
+ export declare const CONSOLE_PAGE_NAV: ReadonlyArray<{
34
+ key: PageChrome['active'];
35
+ href: string;
36
+ label: string;
37
+ }>;
38
+ /** 「可用页面」提示行(404 / 405 / 错误页共用这一份派生结果,不再各写一份)。 */
39
+ export declare function consolePagesHint(): string;
28
40
  export declare function renderLayout(chrome: PageChrome, body: string, subtitle?: string): string;
29
41
  /** 独立极简页(403/404 用;不依赖任何目录事实,避免在错误路径上再读盘)。 */
30
42
  export declare function renderMessagePage(title: string, message: string, hint?: string): string;
@@ -225,19 +225,28 @@ function section(title, body) {
225
225
  * 页面名在这里只定义一次,导航与跨页指引共用,免得两处说法漂移。
226
226
  */
227
227
  const RESTORE_PAGE_LABEL = '恢复';
228
+ /**
229
+ * 救急台的**页面导航表(唯一事实源)**:头部导航与 404 / 405 / 错误页的「可用页面」清单
230
+ * 全部由它派生 —— 此前那三处各写一份、且与现实不一致(把 JSON 自述的 `/healthz` 说成页面,
231
+ * 又漏掉 `/restore` / `/export`)。`/healthz` 是 JSON 自述、不是页面,故不在表内。
232
+ */
233
+ export const CONSOLE_PAGE_NAV = [
234
+ { key: 'home', href: '/', label: '首页' },
235
+ { key: 'disk', href: '/disk', label: '磁盘占用' },
236
+ { key: 'sessions', href: '/sessions', label: '会话体检' },
237
+ { key: 'profiles', href: '/profiles', label: '档案与实例' },
238
+ { key: 'restore', href: '/restore', label: RESTORE_PAGE_LABEL },
239
+ { key: 'export', href: '/export', label: '导出' },
240
+ { key: 'unlock', href: '/unlock', label: '解锁' },
241
+ { key: 'reinstall', href: '/reinstall', label: '重装' },
242
+ ];
243
+ /** 「可用页面」提示行(404 / 405 / 错误页共用这一份派生结果,不再各写一份)。 */
244
+ export function consolePagesHint() {
245
+ return '可用页面:' + CONSOLE_PAGE_NAV.map((item) => item.href + '(' + item.label + ')').join('、');
246
+ }
228
247
  function renderHeader(chrome, subtitle) {
229
- const items = [
230
- ['home', '/', '首页'],
231
- ['disk', '/disk', '磁盘占用'],
232
- ['sessions', '/sessions', '会话体检'],
233
- ['profiles', '/profiles', '档案与实例'],
234
- ['restore', '/restore', RESTORE_PAGE_LABEL],
235
- ['export', '/export', '导出'],
236
- ['unlock', '/unlock', '解锁'],
237
- ['reinstall', '/reinstall', '重装'],
238
- ];
239
- const links = items
240
- .map(([key, href, label]) => '<a href="' + href + '"' + (chrome.active === key ? ' class="active"' : '') + '>' + esc(label) + '</a>')
248
+ const links = CONSOLE_PAGE_NAV
249
+ .map((item) => '<a href="' + item.href + '"' + (chrome.active === item.key ? ' class="active"' : '') + '>' + esc(item.label) + '</a>')
241
250
  .join('');
242
251
  const themeSwitch = '<div class="themeSwitch" role="group" aria-label="配色"><span>配色</span>'
243
252
  + [['auto', '自动'], ['light', '浅色'], ['dark', '深色']]
@@ -17,13 +17,13 @@ import { checkWriteGates, cleanupDisk, collectVerifyResults, exportOfflineBackup
17
17
  import { REINSTALL_ITEMS } from '../../core/reinstall.js';
18
18
  import { createExec } from './exec.js';
19
19
  import { writeHtml } from './http.js';
20
- import { escCapability, renderDiskPage, renderExportPage, renderHomePage, renderLockPage, renderMessagePage, renderProfilesPage, renderReinstallPage, renderRestorePage, renderResultPage, renderSessionsPage, renderUnlockPage, renderVerifyPage, verifyIdOf, } from './page.js';
20
+ import { consolePagesHint, escCapability, renderDiskPage, renderExportPage, renderHomePage, renderLockPage, renderMessagePage, renderProfilesPage, renderReinstallPage, renderRestorePage, renderResultPage, renderSessionsPage, renderUnlockPage, renderVerifyPage, verifyIdOf, } from './page.js';
21
21
  /**
22
22
  * 救急台的错误渲染器:把 RouteError 渲染成 **HTML 错误页**(而不是插件 API 形状的裸 JSON)。
23
23
  * 页面是给人看的,用户在浏览器里收到 `{"error":"…"}` 只会以为坏了(验收 F3)。
24
24
  */
25
25
  function renderConsoleError(res, status, message) {
26
- writeHtml(res, status, renderMessagePage(status === 404 ? '目标不存在' : status === 400 ? '请求无法处理' : '操作未完成', message, status === 404 ? '可用页面:/(首页)、/disk、/sessions、/lock、/profiles、/healthz' : ''));
26
+ writeHtml(res, status, renderMessagePage(status === 404 ? '目标不存在' : status === 400 ? '请求无法处理' : '操作未完成', message, status === 404 ? consolePagesHint() : ''));
27
27
  }
28
28
  /**
29
29
  * 把页面上的不透明 id 解析回**真实文件名**(文件名可能夹带密钥,绝不写回 URL —— 验收 F7)。
@@ -15,7 +15,7 @@ import http from 'node:http';
15
15
  import { isLoopbackRequest, registerRoutes, routeSpecOf, RouteError } from '../../routes/kit.js';
16
16
  import { openInBrowser } from './browser.js';
17
17
  import { writeHtml } from './http.js';
18
- import { renderMessagePage } from './page.js';
18
+ import { consolePagesHint, renderMessagePage } from './page.js';
19
19
  import { buildConsoleRoutes } from './routes.js';
20
20
  /** 会话 cookie 名(HttpOnly + SameSite=Strict,仅本机回环使用)。 */
21
21
  export const CONSOLE_SESSION_COOKIE = 'dcm_rescue_session';
@@ -129,7 +129,7 @@ export async function startConsoleServer(options) {
129
129
  }
130
130
  const route = table.get(url.pathname);
131
131
  if (route === undefined) {
132
- writeHtml(res, 404, renderMessagePage('页面不存在', '没有这个路径:' + url.pathname, '可用页面:/(首页)、/disk、/sessions、/healthz'));
132
+ writeHtml(res, 404, renderMessagePage('页面不存在', '没有这个路径:' + url.pathname, consolePagesHint()));
133
133
  return;
134
134
  }
135
135
  // 方法白名单(cli-F4):kit 的 405 出口写的是 **JSON**(给插件 API 客户端用),而救急台是
@@ -137,7 +137,7 @@ export async function startConsoleServer(options) {
137
137
  // kit handler 内那道判定仍在(纵深防御,不依赖本处)。
138
138
  const spec = routeSpecOf(route);
139
139
  if (spec !== undefined && !spec.methods.includes(req.method)) {
140
- writeHtml(res, 405, renderMessagePage('方法不被允许', '这个地址只接受 ' + spec.methods.join(' / ') + ' 请求(收到 ' + String(req.method ?? '') + ')。', '可用页面:/(首页)、/disk、/sessions、/lock、/profiles、/healthz'));
140
+ writeHtml(res, 405, renderMessagePage('方法不被允许', '这个地址只接受 ' + spec.methods.join(' / ') + ' 请求(收到 ' + String(req.method ?? '') + ')。', consolePagesHint()));
141
141
  return;
142
142
  }
143
143
  // kit 的 handler 自带方法白名单 + 顶层错误映射。
@@ -153,7 +153,7 @@ export async function startConsoleServer(options) {
153
153
  }
154
154
  const status = error instanceof RouteError ? error.status : 500;
155
155
  const message = error instanceof Error ? error.message : String(error);
156
- writeHtml(res, status, renderMessagePage(status === 404 ? '页面 / 目标不存在' : '操作未完成', message, status === 404 ? '可用页面:/(首页)、/disk、/sessions、/lock、/profiles、/healthz' : ''));
156
+ writeHtml(res, status, renderMessagePage(status === 404 ? '页面 / 目标不存在' : '操作未完成', message, status === 404 ? consolePagesHint() : ''));
157
157
  }
158
158
  };
159
159
  const server = http.createServer((req, res) => {