dsh-config-manager 0.1.67 → 0.1.69

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 (433) hide show
  1. package/README.md +53 -0
  2. package/README.zh-CN.md +51 -0
  3. package/icon.svg +16 -0
  4. package/lib/adapters/agent-instructions.d.ts +28 -3
  5. package/lib/adapters/agent-instructions.js +26 -22
  6. package/lib/adapters/file-collection.d.ts +40 -1
  7. package/lib/adapters/file-collection.js +106 -15
  8. package/lib/adapters/plugins.d.ts +3 -1
  9. package/lib/adapters/self.d.ts +51 -5
  10. package/lib/adapters/self.js +34 -49
  11. package/lib/adapters/sessions.d.ts +7 -2
  12. package/lib/adapters/sessions.js +41 -3
  13. package/lib/adapters/test-helpers.d.ts +7 -0
  14. package/lib/adapters/test-helpers.js +10 -0
  15. package/lib/adapters/units.d.ts +12 -2
  16. package/lib/adapters/units.js +13 -6
  17. package/lib/cli/actions.d.ts +513 -0
  18. package/lib/cli/actions.js +1074 -0
  19. package/lib/cli/index.d.ts +7 -1
  20. package/lib/cli/index.js +156 -38
  21. package/lib/cli/sessions-inspect.d.ts +17 -0
  22. package/lib/cli/sessions-inspect.js +104 -0
  23. package/lib/cli/sessions-repair.js +12 -1
  24. package/lib/cli/web/browser.d.ts +1 -0
  25. package/lib/cli/web/browser.js +21 -0
  26. package/lib/cli/web/client-script.d.ts +4 -0
  27. package/lib/cli/web/client-script.js +245 -0
  28. package/lib/cli/web/exec.d.ts +1 -0
  29. package/lib/cli/web/exec.js +22 -0
  30. package/lib/cli/web/http.d.ts +19 -0
  31. package/lib/cli/web/http.js +13 -0
  32. package/lib/cli/web/page.d.ts +137 -0
  33. package/lib/cli/web/page.js +943 -0
  34. package/lib/cli/web/routes.d.ts +15 -0
  35. package/lib/cli/web/routes.js +407 -0
  36. package/lib/cli/web/server.d.ts +37 -0
  37. package/lib/cli/web/server.js +189 -0
  38. package/lib/client.d.ts +1006 -120
  39. package/lib/client.js +25726 -20712
  40. package/lib/core/analyzer.d.ts +33 -1
  41. package/lib/core/analyzer.js +229 -8
  42. package/lib/core/backup-verify.js +1 -3
  43. package/lib/core/cache-cleaner.d.ts +28 -1
  44. package/lib/core/cache-cleaner.js +153 -88
  45. package/lib/core/crash-report.js +1 -4
  46. package/lib/core/disk-usage.d.ts +113 -0
  47. package/lib/core/disk-usage.js +246 -0
  48. package/lib/core/importer.d.ts +6 -1
  49. package/lib/core/journal.js +1 -10
  50. package/lib/core/local-plugin-host.d.ts +1 -1
  51. package/lib/core/local-plugin-host.js +17 -2
  52. package/lib/core/local-plugin-pack.d.ts +28 -0
  53. package/lib/core/local-plugin-pack.js +56 -1
  54. package/lib/core/messages.d.ts +7 -0
  55. package/lib/core/messages.js +15 -0
  56. package/lib/core/migration-history.js +3 -10
  57. package/lib/core/recovery-orchestrator.d.ts +19 -1
  58. package/lib/core/recovery-orchestrator.js +68 -7
  59. package/lib/core/restore.d.ts +7 -1
  60. package/lib/core/restore.js +12 -2
  61. package/lib/core/session-health.d.ts +164 -0
  62. package/lib/core/session-health.js +164 -0
  63. package/lib/core/session-meta.d.ts +6 -1
  64. package/lib/core/session-meta.js +49 -1
  65. package/lib/core/types.d.ts +221 -1
  66. package/lib/core/update-check.d.ts +81 -0
  67. package/lib/core/update-check.js +145 -0
  68. package/lib/core/validator.d.ts +22 -2
  69. package/lib/core/validator.js +52 -19
  70. package/lib/index.d.ts +70 -5
  71. package/lib/index.js +250 -26
  72. package/lib/market/git-file-writer.js +1 -4
  73. package/lib/market/types.d.ts +0 -5
  74. package/lib/profiles/dsh-profile-io.d.ts +8 -0
  75. package/lib/profiles/dsh-profile-io.js +31 -0
  76. package/lib/profiles/dsh-profile-launcher.js +6 -19
  77. package/lib/profiles/dsh-profile-manager.d.ts +2 -0
  78. package/lib/profiles/dsh-profile-manager.js +22 -13
  79. package/lib/profiles/dsh-profile-runtime.js +4 -14
  80. package/lib/profiles/dsh-profile-shared.d.ts +19 -0
  81. package/lib/routes/backup.js +155 -1
  82. package/lib/routes/consult.js +10 -2
  83. package/lib/routes/import.js +21 -10
  84. package/lib/routes/kit.d.ts +8 -0
  85. package/lib/routes/kit.js +8 -0
  86. package/lib/routes/prefs.d.ts +2 -1
  87. package/lib/routes/prefs.js +32 -1
  88. package/lib/routes/profiles.js +2 -0
  89. package/lib/routes/recovery.js +167 -1
  90. package/lib/routes/session-format.d.ts +25 -0
  91. package/lib/routes/session-format.js +54 -0
  92. package/lib/routes/sync.js +138 -5
  93. package/lib/schema/config.d.ts +1 -11
  94. package/lib/schema/config.js +0 -10
  95. package/lib/security/container-kind.d.ts +15 -0
  96. package/lib/security/container-kind.js +48 -0
  97. package/lib/security/index.d.ts +2 -1
  98. package/lib/security/index.js +2 -1
  99. package/lib/security/integrity.d.ts +0 -1
  100. package/lib/security/integrity.js +0 -1
  101. package/lib/security/redaction.d.ts +2 -0
  102. package/lib/security/redaction.js +37 -1
  103. package/lib/sync/backup-files.d.ts +28 -2
  104. package/lib/sync/backup-files.js +19 -5
  105. package/lib/sync/git/git-transport.d.ts +27 -2
  106. package/lib/sync/git/git-transport.js +50 -11
  107. package/lib/sync/snapshot-json.d.ts +8 -1
  108. package/lib/sync/snapshot-json.js +10 -3
  109. package/lib/sync/sync-config.d.ts +25 -0
  110. package/lib/sync/sync-config.js +46 -0
  111. package/lib/sync/sync-engine.d.ts +54 -0
  112. package/lib/sync/sync-engine.js +75 -7
  113. package/lib/sync/transport.js +4 -1
  114. package/lib/sync/ui-prefs.d.ts +10 -0
  115. package/lib/sync/ui-prefs.js +6 -0
  116. package/lib/sync/webdav/webdav-transport.d.ts +12 -0
  117. package/lib/sync/webdav/webdav-transport.js +22 -2
  118. package/lib/ui/artifact-view.d.ts +150 -0
  119. package/lib/ui/artifact-view.js +264 -0
  120. package/lib/ui/commands.d.ts +72 -0
  121. package/lib/ui/commands.js +153 -0
  122. package/lib/ui/disk-usage-view.d.ts +77 -0
  123. package/lib/ui/disk-usage-view.js +104 -0
  124. package/lib/ui/dsh-profiles-view.d.ts +17 -0
  125. package/lib/ui/dsh-profiles-view.js +17 -0
  126. package/lib/ui/export-flow.d.ts +0 -7
  127. package/lib/ui/history-model.d.ts +1 -1
  128. package/lib/ui/history-model.js +1 -1
  129. package/lib/ui/i18n.d.ts +116 -0
  130. package/lib/ui/i18n.js +234 -0
  131. package/lib/ui/import-wizard.d.ts +25 -1
  132. package/lib/ui/import-wizard.js +69 -1
  133. package/lib/ui/menu-placement.d.ts +56 -0
  134. package/lib/ui/menu-placement.js +63 -0
  135. package/lib/ui/mid-ellipsis.d.ts +3 -2
  136. package/lib/ui/mid-ellipsis.js +3 -2
  137. package/lib/ui/nav-model.d.ts +34 -0
  138. package/lib/ui/nav-model.js +70 -0
  139. package/lib/ui/next-steps.d.ts +23 -1
  140. package/lib/ui/next-steps.js +19 -2
  141. package/lib/ui/overview-view.d.ts +1 -1
  142. package/lib/ui/overview-view.js +1 -1
  143. package/lib/ui/runs-view.d.ts +1 -6
  144. package/lib/ui/runs-view.js +1 -8
  145. package/lib/ui/session-format-disposition.d.ts +108 -0
  146. package/lib/ui/session-format-disposition.js +122 -0
  147. package/lib/ui/session-inventory-view.d.ts +255 -0
  148. package/lib/ui/session-inventory-view.js +181 -0
  149. package/lib/ui/test-helpers.d.ts +6 -2
  150. package/lib/ui/test-helpers.js +1 -1
  151. package/lib/ui/types.d.ts +114 -14
  152. package/lib/ui/types.js +0 -4
  153. package/lib/utils/atomic-write.js +1 -3
  154. package/lib/utils/env-lock.d.ts +0 -4
  155. package/lib/utils/env-lock.js +11 -16
  156. package/lib/utils/git-quote.d.ts +8 -0
  157. package/lib/utils/git-quote.js +11 -0
  158. package/lib/utils/guards.d.ts +12 -0
  159. package/lib/utils/guards.js +17 -0
  160. package/lib/utils/hashing.d.ts +0 -1
  161. package/lib/utils/hashing.js +0 -5
  162. package/lib/utils/json.d.ts +13 -0
  163. package/lib/utils/json.js +13 -0
  164. package/lib/utils/logger.d.ts +0 -3
  165. package/lib/utils/logger.js +1 -6
  166. package/lib/utils/paths.d.ts +0 -4
  167. package/lib/utils/paths.js +0 -10
  168. package/lib/utils/session-format.d.ts +32 -0
  169. package/lib/utils/session-format.js +107 -0
  170. package/lib/utils/session-health-scan.d.ts +42 -0
  171. package/lib/utils/session-health-scan.js +319 -0
  172. package/lib/utils/session-log-repair.d.ts +127 -0
  173. package/lib/utils/session-log-repair.js +484 -0
  174. package/lib/utils/session-log.d.ts +28 -0
  175. package/lib/utils/session-log.js +65 -8
  176. package/lib/utils/session-repair-service.d.ts +146 -0
  177. package/lib/utils/session-repair-service.js +353 -0
  178. package/lib/utils/session-row-facts.d.ts +53 -0
  179. package/lib/utils/session-row-facts.js +108 -0
  180. package/lib/utils/shared-constants.d.ts +23 -0
  181. package/lib/utils/shared-constants.js +24 -0
  182. package/lib/utils/ttl-cache.d.ts +40 -0
  183. package/lib/utils/ttl-cache.js +66 -0
  184. package/lib/utils/zip.d.ts +15 -0
  185. package/lib/utils/zip.js +69 -14
  186. package/locale/en.json +6 -0
  187. package/locale/zh.json +6 -0
  188. package/package.json +13 -6
  189. package/src/adapters/agent-instructions.test.ts +113 -0
  190. package/src/adapters/agent-instructions.ts +28 -23
  191. package/src/adapters/file-collection.test.ts +168 -1
  192. package/src/adapters/file-collection.ts +128 -17
  193. package/src/adapters/plugins.test.ts +3 -1
  194. package/src/adapters/plugins.ts +2 -1
  195. package/src/adapters/self.test.ts +48 -0
  196. package/src/adapters/self.ts +45 -29
  197. package/src/adapters/sessions.test.ts +112 -0
  198. package/src/adapters/sessions.ts +49 -4
  199. package/src/adapters/test-helpers.ts +10 -0
  200. package/src/adapters/units.ts +22 -7
  201. package/src/cli/actions.test.ts +433 -0
  202. package/src/cli/actions.ts +1440 -0
  203. package/src/cli/index.ts +174 -39
  204. package/src/cli/sessions-inspect.test.ts +90 -0
  205. package/src/cli/sessions-inspect.ts +114 -0
  206. package/src/cli/sessions-repair.ts +11 -1
  207. package/src/cli/web/browser.ts +20 -0
  208. package/src/cli/web/client-script.ts +247 -0
  209. package/src/cli/web/exec.ts +23 -0
  210. package/src/cli/web/http.ts +30 -0
  211. package/src/cli/web/page.ts +1120 -0
  212. package/src/cli/web/routes.ts +502 -0
  213. package/src/cli/web/server.ts +234 -0
  214. package/src/cli/web/web.test.ts +1003 -0
  215. package/src/client/CLIENT_DEPENDENCIES.md +10 -1
  216. package/src/client/ConfigManagerSection.tsx +370 -159
  217. package/src/client/about/AboutPanel.tsx +83 -8
  218. package/src/client/about/about-view.test.ts +42 -2
  219. package/src/client/about/about-view.ts +57 -1
  220. package/src/client/api.ts +53 -4
  221. package/src/client/client-types.ts +5 -0
  222. package/src/client/common/CommandPalette.tsx +173 -0
  223. package/src/client/common/ContentPicker.tsx +4 -1
  224. package/src/client/common/CopyButton.tsx +9 -12
  225. package/src/client/common/ErrorBanner.tsx +1 -5
  226. package/src/client/common/Icon.tsx +4 -4
  227. package/src/client/common/InfoHint.tsx +280 -0
  228. package/src/client/common/Modal.tsx +14 -2
  229. package/src/client/common/MoreMenu.tsx +195 -0
  230. package/src/client/common/RunsCenter.tsx +12 -10
  231. package/src/client/common/Select.tsx +183 -40
  232. package/src/client/common/ToastViewport.tsx +1 -1
  233. package/src/client/common/client-route-parity.test.ts +10 -0
  234. package/src/client/common/clipboard.test.ts +42 -0
  235. package/src/client/common/clipboard.ts +19 -0
  236. package/src/client/common/compat-label.test.ts +43 -0
  237. package/src/client/common/compat-label.ts +28 -0
  238. package/src/client/common/floating-bounds.ts +48 -0
  239. package/src/client/common/floating-units.test.ts +71 -0
  240. package/src/client/common/http.test.ts +24 -0
  241. package/src/client/common/http.ts +19 -3
  242. package/src/client/common/info-hint-guard.test.ts +836 -0
  243. package/src/client/common/menu-align.ts +29 -0
  244. package/src/client/common/menu-scroll-guard.test.ts +61 -0
  245. package/src/client/common/morph-icons.ts +0 -2
  246. package/src/client/common/plan-text-redaction.test.ts +7 -6
  247. package/src/client/common/routes.ts +18 -0
  248. package/src/client/common/ui.tsx +1 -1
  249. package/src/client/config-manager.module.css +793 -446
  250. package/src/client/{profiles/ProfilesPanel.tsx → environment/EnvironmentPanel.tsx} +91 -21
  251. package/src/client/export/ExportView.tsx +18 -10
  252. package/src/client/history/HistoryPanel.tsx +2 -2
  253. package/src/client/history/history-locales.ts +0 -20
  254. package/src/client/{overview/OverviewPanel.tsx → home/HomePanel.tsx} +105 -32
  255. package/src/client/import/ImportWizardView.tsx +110 -16
  256. package/src/client/import/import-file-select.test.ts +53 -0
  257. package/src/client/import/import-wizard-steps.tsx +101 -1
  258. package/src/client/import/use-import-wizard-controller.ts +113 -5
  259. package/src/client/index.ts +2 -2
  260. package/src/client/library/ArtifactDetail.tsx +34 -0
  261. package/src/client/library/ArtifactRow.tsx +128 -0
  262. package/src/client/library/BackupInspectView.tsx +96 -0
  263. package/src/client/library/LibraryActions.tsx +638 -0
  264. package/src/client/library/LibraryPanel.tsx +250 -0
  265. package/src/client/library/task-panel-visibility.test.ts +42 -0
  266. package/src/client/locales.ts +205 -0
  267. package/src/client/lucide-icons.d.ts +1 -0
  268. package/src/client/market/MarketImportReview.tsx +8 -2
  269. package/src/client/market/MarketPanel.tsx +52 -4
  270. package/src/client/market/MyConfigsLoginCard.tsx +2 -1
  271. package/src/client/market/MyConfigsWizard.tsx +4 -2
  272. package/src/client/market/market-locales.ts +5 -0
  273. package/src/client/recovery/RecoveryPanel.tsx +612 -4
  274. package/src/client/recovery/recovery-api.ts +51 -1
  275. package/src/client/recovery/recovery-locales.ts +186 -0
  276. package/src/client/recovery/recovery-view.test.ts +38 -0
  277. package/src/client/recovery/recovery-view.ts +14 -10
  278. package/src/client/recovery/session-dialog-mount.test.ts +58 -0
  279. package/src/client/run-store.test.ts +90 -56
  280. package/src/client/run-store.ts +162 -62
  281. package/src/client/snapshots/BackupScheduleCard.tsx +84 -115
  282. package/src/client/snapshots/DiskUsageCard.tsx +253 -0
  283. package/src/client/snapshots/files-layout.test.ts +90 -0
  284. package/src/client/snapshots/report-dismiss.test.ts +19 -15
  285. package/src/client/sync/AutosyncCard.tsx +5 -6
  286. package/src/client/sync/ChannelClearConfirmDialog.tsx +58 -0
  287. package/src/client/sync/ChannelConfigDialog.tsx +31 -45
  288. package/src/client/sync/DecryptPasswordCard.tsx +2 -2
  289. package/src/client/sync/SecurityOptionsCard.tsx +2 -2
  290. package/src/client/sync/SyncChannelEntryCard.tsx +22 -6
  291. package/src/client/sync/SyncConfirmView.tsx +85 -26
  292. package/src/client/sync/{SyncHistoryView.tsx → SyncLogList.tsx} +84 -103
  293. package/src/client/sync/{SyncSettingsView.tsx → SyncPanel.tsx} +313 -174
  294. package/src/client/sync/SyncSectionPickerDialog.tsx +20 -9
  295. package/src/client/sync/SyncSettingsView.test.ts +40 -5
  296. package/src/client/sync/UnreadableSnapshotsBanner.tsx +35 -0
  297. package/src/client/sync/history-model.ts +2 -2
  298. package/src/client/sync/sync-api.ts +66 -1
  299. package/src/client/sync/sync-locales.ts +52 -0
  300. package/src/client/sync/sync-view-v2.test.ts +54 -2
  301. package/src/client/sync/sync-view.test.ts +1 -14
  302. package/src/client/sync/sync-view.ts +67 -23
  303. package/src/client/task/TaskShell.tsx +83 -0
  304. package/src/core/analyzer-cache.test.ts +154 -0
  305. package/src/core/analyzer.ts +248 -10
  306. package/src/core/backup-verify.ts +1 -4
  307. package/src/core/cache-cleaner.test.ts +66 -0
  308. package/src/core/cache-cleaner.ts +98 -15
  309. package/src/core/compatibility-reasons.test.ts +100 -0
  310. package/src/core/crash-report.ts +1 -5
  311. package/src/core/disk-usage.test.ts +179 -0
  312. package/src/core/disk-usage.ts +360 -0
  313. package/src/core/import-sessions-visibility.test.ts +183 -7
  314. package/src/core/importer.ts +3 -1
  315. package/src/core/incident-wiring.test.ts +6 -1
  316. package/src/core/journal.ts +1 -13
  317. package/src/core/local-plugin-host.ts +17 -3
  318. package/src/core/local-plugin-pack.test.ts +101 -0
  319. package/src/core/local-plugin-pack.ts +70 -1
  320. package/src/core/messages.ts +15 -0
  321. package/src/core/migration-history.ts +3 -13
  322. package/src/core/recovery-orchestrator.ts +84 -9
  323. package/src/core/restore.ts +15 -2
  324. package/src/core/session-health.test.ts +224 -0
  325. package/src/core/session-health.ts +305 -0
  326. package/src/core/session-meta.test.ts +88 -2
  327. package/src/core/session-meta.ts +48 -1
  328. package/src/core/smoke.test.ts +14 -0
  329. package/src/core/types.ts +201 -1
  330. package/src/core/update-check.test.ts +149 -0
  331. package/src/core/update-check.ts +170 -0
  332. package/src/core/validator.ts +53 -21
  333. package/src/index.ts +256 -25
  334. package/src/market/git-file-writer.ts +1 -5
  335. package/src/market/types.ts +0 -6
  336. package/src/profiles/dsh-profile-io.ts +31 -0
  337. package/src/profiles/dsh-profile-launcher.ts +6 -23
  338. package/src/profiles/dsh-profile-manager.test.ts +26 -0
  339. package/src/profiles/dsh-profile-manager.ts +21 -14
  340. package/src/profiles/dsh-profile-runtime.ts +4 -14
  341. package/src/profiles/dsh-profile-shared.ts +19 -0
  342. package/src/routes/backup.ts +167 -1
  343. package/src/routes/consult.ts +10 -2
  344. package/src/routes/import.ts +21 -8
  345. package/src/routes/kit.ts +13 -0
  346. package/src/routes/prefs.ts +31 -1
  347. package/src/routes/profiles.ts +2 -0
  348. package/src/routes/recovery.ts +151 -0
  349. package/src/routes/session-format.test.ts +59 -0
  350. package/src/routes/session-format.ts +74 -0
  351. package/src/routes/sync.ts +140 -4
  352. package/src/schema/config.ts +1 -15
  353. package/src/security/container-kind.test.ts +92 -0
  354. package/src/security/container-kind.ts +51 -0
  355. package/src/security/index.ts +2 -1
  356. package/src/security/integrity.ts +0 -1
  357. package/src/security/redaction.ts +39 -1
  358. package/src/security/security.test.ts +9 -0
  359. package/src/sync/backup-files.test.ts +34 -0
  360. package/src/sync/backup-files.ts +39 -5
  361. package/src/sync/git/git-transport.test.ts +56 -0
  362. package/src/sync/git/git-transport.ts +50 -16
  363. package/src/sync/snapshot-json.test.ts +35 -0
  364. package/src/sync/snapshot-json.ts +10 -3
  365. package/src/sync/sync-config.test.ts +68 -1
  366. package/src/sync/sync-config.ts +56 -0
  367. package/src/sync/sync-engine.test.ts +31 -0
  368. package/src/sync/sync-engine.ts +84 -7
  369. package/src/sync/transport.ts +5 -1
  370. package/src/sync/ui-prefs.test.ts +24 -0
  371. package/src/sync/ui-prefs.ts +16 -0
  372. package/src/sync/webdav/webdav-transport.ts +24 -2
  373. package/src/ui/artifact-view.test.ts +203 -0
  374. package/src/ui/artifact-view.ts +410 -0
  375. package/src/ui/commands.test.ts +159 -0
  376. package/src/ui/commands.ts +208 -0
  377. package/src/ui/disk-usage-view.test.ts +119 -0
  378. package/src/ui/disk-usage-view.ts +179 -0
  379. package/src/ui/dsh-profiles-view.test.ts +21 -2
  380. package/src/ui/dsh-profiles-view.ts +38 -0
  381. package/src/ui/export-flow.ts +0 -8
  382. package/src/ui/history-model.ts +1 -1
  383. package/src/ui/i18n.ts +234 -0
  384. package/src/ui/import-wizard.test.ts +124 -5
  385. package/src/ui/import-wizard.ts +93 -3
  386. package/src/ui/menu-placement.test.ts +84 -0
  387. package/src/ui/menu-placement.ts +89 -0
  388. package/src/ui/mid-ellipsis.test.ts +8 -6
  389. package/src/ui/mid-ellipsis.ts +3 -2
  390. package/src/ui/nav-model.test.ts +86 -0
  391. package/src/ui/nav-model.ts +82 -0
  392. package/src/ui/next-steps.test.ts +39 -1
  393. package/src/ui/next-steps.ts +33 -2
  394. package/src/ui/overview-view.ts +1 -1
  395. package/src/ui/runs-view.ts +1 -8
  396. package/src/ui/session-format-disposition.test.ts +114 -0
  397. package/src/ui/session-format-disposition.ts +174 -0
  398. package/src/ui/session-inventory-view.test.ts +209 -0
  399. package/src/ui/session-inventory-view.ts +366 -0
  400. package/src/ui/test-helpers.ts +12 -6
  401. package/src/ui/types.ts +128 -21
  402. package/src/utils/atomic-write.ts +1 -4
  403. package/src/utils/env-lock.test.ts +32 -4
  404. package/src/utils/env-lock.ts +11 -18
  405. package/src/utils/git-quote.ts +10 -0
  406. package/src/utils/guards.ts +17 -0
  407. package/src/utils/hashing.ts +0 -6
  408. package/src/utils/json.ts +14 -0
  409. package/src/utils/logger.ts +1 -7
  410. package/src/utils/paths.ts +0 -12
  411. package/src/utils/session-format.test.ts +107 -0
  412. package/src/utils/session-format.ts +115 -0
  413. package/src/utils/session-health-scan.test.ts +217 -0
  414. package/src/utils/session-health-scan.ts +365 -0
  415. package/src/utils/session-log-repair.test.ts +310 -0
  416. package/src/utils/session-log-repair.ts +544 -0
  417. package/src/utils/session-log.test.ts +13 -0
  418. package/src/utils/session-log.ts +78 -0
  419. package/src/utils/session-repair-service.test.ts +178 -0
  420. package/src/utils/session-repair-service.ts +457 -0
  421. package/src/utils/session-row-facts.ts +118 -0
  422. package/src/utils/shared-constants.ts +24 -0
  423. package/src/utils/ttl-cache.test.ts +96 -0
  424. package/src/utils/ttl-cache.ts +90 -0
  425. package/src/utils/zip.ts +65 -15
  426. package/lib/ui/nav-overflow.d.ts +0 -35
  427. package/lib/ui/nav-overflow.js +0 -41
  428. package/src/client/overview/overview-kind-labels.test.ts +0 -43
  429. package/src/client/snapshots/SnapshotsEmptyState.tsx +0 -31
  430. package/src/client/snapshots/SnapshotsListTable.tsx +0 -97
  431. package/src/client/snapshots/SnapshotsPanel.tsx +0 -811
  432. package/src/ui/nav-overflow.test.ts +0 -57
  433. package/src/ui/nav-overflow.ts +0 -58
package/README.md CHANGED
@@ -103,6 +103,9 @@ Several DSH plugins live in this space and they solve different problems — pic
103
103
  | 🗂️ | **Profiles (DSH profiles)** | Manage `$DSH_HOME/profiles/<name>` directly: list / create from a shipped template / rename / hard delete / **launch this profile (independent instance)** / **stop the instance** (the row button flips between Launch and Stop with the running state) |
104
104
  | 🌐 | **Bilingual UI** | Interface, reports and error details follow the DSH app language (中文 / English) |
105
105
  | 🤖 | **Agent tools** | Backup / snapshot / restore / sync right from an agent session |
106
+ | 💾 | **Disk usage report** | The backups page shows how much space the plugin's own artifacts take (backups / snapshots / sync copies / caches / staging) with a three-tier cleanup policy; **one-click cleanup only touches regenerable caches and expired backups** — snapshots and sync data are never removed there |
107
+ | ⬆️ | **Update check** | The About page reads the latest version from npm and, when there is a newer one, hands you a copyable upgrade command (never auto-installs); offline failures are reported honestly and affect nothing else |
108
+ | 🧭 | **Compatibility explained** | Before importing you see "source DSH version / platform → local" plus **structured reasons** for the score (cross-platform / missing sections / newer source …) instead of a bare "partial" |
106
109
 
107
110
  ---
108
111
 
@@ -211,6 +214,27 @@ dsh plugin --profile web add dsh-config-manager@latest
211
214
  >
212
215
  > Then retry the install. As long as that global copy exists and is incompatible with your DSH, every profile (`web` / `desktop` / custom) is rejected the same way.
213
216
 
217
+ ### Installing from the GitHub source (`git+https://...`)
218
+
219
+ When installed from a git source, pnpm first runs this package's `prepare` script (= `npm run build`) inside that clone to produce `lib/` — **npm ships a prebuilt `lib/`, a git install builds it on the spot**. pnpm 11 blocks that script by default, so the install fails with `dsh: plugin command failed` and this in the log:
220
+
221
+ ```
222
+ ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED Failed to prepare git-hosted package ...
223
+ The git-hosted package "dsh-config-manager@x.y.z" needs to execute build scripts
224
+ but is not in the "allowBuilds" allowlist.
225
+ ```
226
+
227
+ **Fix**: copy the exact line pnpm prints (it includes the full git URL and commit sha) into the profile's `pnpm-workspace.yaml`:
228
+
229
+ ```yaml
230
+ # ~/.dsh/profiles/web/pnpm-workspace.yaml
231
+ allowBuilds:
232
+ dsh-config-manager@git+https://github.com/xiajiajun516/dsh-config-manager.git#<sha printed by pnpm>: true
233
+ ```
234
+
235
+ > The key must be copied **verbatim** — the bare package name `dsh-config-manager` does not work (the allowlist matches on source + version).
236
+ > The simpler path is the prebuilt npm release above (`dsh-config-manager@latest`), which needs no allowlist at all.
237
+
214
238
  ---
215
239
 
216
240
  ## 🚀 Quick start (3-minute tour)
@@ -369,6 +393,19 @@ Every import creates a **safety snapshot** first. If something feels off afterwa
369
393
 
370
394
  ---
371
395
 
396
+
397
+ **Backup files & disk usage**: the **"Backup Files"** sub-tab (Backups → snapshot tab) manages everything in
398
+ `exports/` (manual exports + scheduled backups): download, import straight back, inspect, delete, search by
399
+ name or note. The **"Disk Usage"** card on the same page lists what this plugin itself occupies (exports,
400
+ pre-import snapshots, sync config and Git working copies, marketplace cache, temp staging, logs, transaction
401
+ log, …) and labels each item's cleanup policy: **regenerated on demand** (caches/staging), **retained**
402
+ (exports for 7 days, scheduled backups keep the latest N), or **your data / safety net** (snapshots and sync —
403
+ never auto-cleaned). **"Clean now"** clears only regenerable caches by default; reclaiming expired backups is
404
+ an explicit opt-in with a confirmation. Snapshots and sync data are never touched, and unreadable directories
405
+ are reported as "not measured" instead of a misleading 0 bytes.
406
+
407
+ ---
408
+
372
409
  ### 🚨 CLI — the first line of defense when DSH is broken
373
410
 
374
411
  The GUI lives *inside* DSH — it can't help you if DSH won't start. The `dsh-config-manager` **CLI is completely independent of the DSH runtime** (pure Node + the core engine, **zero `@deepseek-ai/*` imports** — it runs even when the DSH peer packages are broken or missing). That makes it your **first rescue tool** when the config is corrupted, the GUI won't boot, or you changed machines and need to bring an environment back.
@@ -530,6 +567,17 @@ How it behaves:
530
567
 
531
568
  Alternative (host-wide): set `NODE_USE_ENV_PROXY=1` **before starting DSH** (Node 24+); note this also routes the host's own outbound traffic, not just this plugin's.
532
569
 
570
+ **`web` — the offline rescue console (for when you would rather click than type).** It starts a small web page bound to **127.0.0.1 only** and lays out the same diagnostics and rescue actions in a browser:
571
+
572
+ ```bash
573
+ dsh-config-manager web # starts it and opens your browser (the terminal prints a one-time token URL)
574
+ ```
575
+
576
+ - **Read-only:** running instances / SAFE MODE / leftover lock / snapshots / exported backups (with one-click verification) / disk usage / session health / profiles and their instances.
577
+ - **Write actions (each needs an explicit confirmation in the page, and every one calls the same implementation as the CLI):** ① relocate misplaced sessions ② clear rebuildable caches and expired exports ③ reclaim a stale environment lock ④ start/stop a profile's standalone instance ⑤ **unlock an encrypted backup** (decrypted in memory, lists entries only — never written to disk) ⑥ **restore a snapshot** (see the per-item plan first, zero writes; restoring copies current files to `<snapshot>/pre-restore/` first) ⑦ **offline export** (file-based sections, self-checked right after writing) ⑧ **reinstall DSH** (uninstall + reinstall the global CLI; **requires a 6-character code printed only in the terminal**).
578
+ - **Safety:** loopback-only binding; a one-time token printed to your terminal is exchanged for an HttpOnly + SameSite=Strict session cookie; the page has no scripts and no external resources. `Ctrl+C` or the idle timeout (30 minutes by default) shuts it down.
579
+ - **Preconditions:** session relocation and restore need DSH stopped; with an unresolved SAFE MODE transaction or a leftover environment lock every write action is refused (the page says why). **Import** (writing a bundle back into this machine) stays on the GUI/CLI — structural sections need live DSH services.
580
+
533
581
  ### 🤖 Agent tools (for AI assistants)
534
582
 
535
583
  The plugin also registers **5 model tools** that an AI agent (a DSH assistant session) can call directly — the same backup / snapshot / sync engines, no GUI needed:
@@ -629,6 +677,11 @@ Found a bug, a misaligned panel, a button that does nothing — or just have an
629
677
  contribution, commit `1248200` was landed on `main` through a **"keep the commit, take the mainline tree"** merge
630
678
  (`122317b`) — that merge takes **none** of its code (the resulting tree is byte-identical to the mainline) and exists purely
631
679
  to record authorship, so GitHub now counts this contribution instead of leaving it invisible behind a closed PR.
680
+ - **OMSociety** — [PR #66](https://github.com/xiajiajun516/dsh-config-manager/pull/66), merged as `4170af4`: proposed and
681
+ implemented the 36×36 frame-less `icon.svg` (issue #61, aligning the plugin with the official bundle convention).
682
+ The report came with upstream evidence (host artwork frames of 48/36 px and 40/30 px, the official published bundle
683
+ icons, the fixture SVG) and the PR shipped a 16/30/36/48 × dark/light acceptance sheet plus a written argument for
684
+ keeping the proposal's palette over the plugin's brand blue.
632
685
  - **Bug reports**: `zhyx1996` (#38 sync "Export secrets" did nothing), `IPF-Sinon` (#39 the `.credentials.yaml` `refs:` block
633
686
  was not recognized — they also implemented and machine-tested a fix in their own fork), `zerginlaw` (#43 "Back up now"
634
687
  silently no-oped while toasting success). Each report drove one fix.
package/README.zh-CN.md CHANGED
@@ -106,6 +106,9 @@ DSH 生态里这个方向有几个插件,它们解决的问题并不相同—
106
106
  | 🧩 | **本地插件随备份迁移** | `link:` / `file:` 安装的本地开发插件会被打包进备份,换机不再丢失 |
107
107
  | 🗄️ | **保留策略可配(GFS 分层)** | 「最近 N 份 + 每月留 1 份 + 每年留 1 份」,默认值等价旧行为 |
108
108
  | 🤖 | **Agent 工具** | Agent 会话内直接备份 / 快照 / 恢复 / 同步 |
109
+ | 💾 | **磁盘占用体检** | 备份页逐项列出本插件自身占了多少盘(备份 / 快照 / 同步副本 / 缓存 / 暂存),三档回收策略一目了然;**一键清理只碰可重建缓存与过期备份**,快照与同步数据永不在此删除 |
110
+ | ⬆️ | **版本更新检查** | 「关于」页只读探测 npm 上的最新版本,有新版本时给一条可复制的升级命令(绝不自动安装);离线失败如实显示,不影响其它功能 |
111
+ | 🧭 | **兼容性讲清楚** | 导入前显示「来源 DSH 版本 / 平台 → 本机」,并用**结构化原因**解释评分(跨平台 / 分区缺失 / 版本超前…),而不是只丢一句「部分兼容」 |
109
112
 
110
113
  ---
111
114
 
@@ -188,6 +191,27 @@ dsh plugin --profile web add dsh-config-manager@latest
188
191
  > ```
189
192
  >
190
193
  > 重启 DSH 后,可在 **设置 → 备份与迁移 → 关于** 看到实际运行的版本(版本变化时也会自动弹出更新内容)。
194
+
195
+ ### 从 GitHub 源码安装(`git+https://...`)
196
+
197
+ 用 git 源安装时,pnpm 会先在该 clone 里跑本包的 `prepare` 脚本(= `npm run build`)把 `lib/` 构建出来 —— **产物在 npm 上已预构建,从 git 安装则是现构建**。pnpm 11 默认**拦截**这个脚本,直接报 `dsh: plugin command failed`,日志里是:
198
+
199
+ ```
200
+ ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED Failed to prepare git-hosted package ...
201
+ The git-hosted package "dsh-config-manager@x.y.z" needs to execute build scripts
202
+ but is not in the "allowBuilds" allowlist.
203
+ ```
204
+
205
+ **解决**:把它提示的那一行(**含完整 git URL 与 commit sha**,逐字复制)加进 profile 的 `pnpm-workspace.yaml`:
206
+
207
+ ```yaml
208
+ # ~/.dsh/profiles/web/pnpm-workspace.yaml
209
+ allowBuilds:
210
+ dsh-config-manager@git+https://github.com/xiajiajun516/dsh-config-manager.git#<pnpm 打印的 sha>: true
211
+ ```
212
+
213
+ > 键必须**逐字**照抄 pnpm 打印的那一行 —— 只写包名 `dsh-config-manager` 不生效(该白名单按「来源 + 版本」匹配)。
214
+ > 更省事的路径是直接装 npm 预构建版(上面的 `dsh-config-manager@latest`),无需任何白名单。
191
215
  >
192
216
  > - 或一行命令彻底关闭年龄门槛(在 profile 的 `pnpm-workspace.yaml` 顶部加 `minimumReleaseAge: 0`):
193
217
  > ```powershell
@@ -351,6 +375,14 @@ dsh plugin --profile web add dsh-config-manager@latest
351
375
 
352
376
  **GUI**:设置 → 「备份与迁移」→「快照恢复」tab → 选择快照 → 预览恢复计划(dry-run,零写入)→ 确认执行。
353
377
 
378
+ **备份文件与磁盘占用**:「备份与迁移」→「快照恢复」tab 下的**「备份文件」**子页统一管理 `exports/` 里的导出产物
379
+ (手动导出 + 定时备份)——可下载、直接导入恢复、查看内容、删除,并按文件名/备注搜索。
380
+ 同页的**「磁盘占用」卡**会逐项列出本插件自己占用的空间(导出备份 / 导入前快照 / 同步配置与 Git 工作副本 /
381
+ 市场缓存 / 临时暂存 / 日志 / 事务日志 …),标注每项的回收策略:**可随时重建**(缓存与暂存)、
382
+ **有保留期**(导出产物 7 天、定时备份保留最近 N 个)、**用户数据 / 安全网**(快照与同步,永不自动清理)。
383
+ 需要腾空间时点**「立即清理」**:默认只清可重建的缓存与暂存;要回收过期备份文件需显式勾选(并二次确认)。
384
+ **快照与同步数据永远不会被这个按钮删掉**;目录读不到时如实显示「未统计」,不会伪报 0 字节。
385
+
354
386
  ---
355
387
 
356
388
  ### 🚨 CLI —— DSH 挂了时的第一救急手段
@@ -447,6 +479,21 @@ dsh-config-manager backup --sections skills,self # 收窄范围
447
479
 
448
480
  **典型救急流程**(DSH 起不来时):① `dsh-config-manager reinstall` 先把启动器重装回来(必要时顺带清理),② `dsh web` 重新启动 DSH,③ 从仓库装回插件,④ 从远程仓库拉取快照(或执行 `dsh-config-manager restore`)把配置恢复回来。整个流程中 CLI 全程可用,与 DSH 是否健康无关。
449
481
 
482
+ **`web` —— 离线救急台(不想在终端里敲命令时用这个)。** 它在本机起一个**只绑 127.0.0.1** 的小网页,
483
+ 把上面的诊断信息与救急动作都摊在浏览器里:
484
+
485
+ ```bash
486
+ dsh-config-manager web # 启动并自动打开浏览器(终端里会打印带一次性 token 的链接)
487
+ ```
488
+
489
+ - **只读部分**:实例心跳 / SAFE MODE / 残留锁 / 快照 / 导出产物(可一键自检)/ 磁盘占用 / 会话体检 / 档案与实例状态。
490
+ - **写动作(都要在网页里显式确认)**:会话布局归位、清理可重建缓存与过期导出产物、回收残留环境锁、
491
+ 启动/停止某个档案的独立实例。它们调用的是与命令行**同一套实现**,结果页逐条如实回执。
492
+ - **安全**:只绑回环地址;启动时生成一次性 token(只打印在终端),换成一个 HttpOnly + SameSite=Strict 的
493
+ 会话 cookie,用过即废;页面零脚本、零外链。`Ctrl+C` 或空闲超时(缺省 30 分钟)即退出。
494
+ - **门槛**:会话归位需要 DSH 已停止;有 SAFE MODE 未结案时写动作一律被拒(页面会写明原因)。
495
+ 恢复快照(`restore`)与重装(`reinstall`)**不在网页里** —— 它们需要更强的确认,留在命令行/GUI。
496
+
450
497
  ### 🤖 Agent 可调用的模型工具
451
498
 
452
499
  插件还向宿主注册了 **5 个模型工具**,让 AI agent(DSH 助手会话)能在对话中直接驱动配置运维——与 GUI 共用同一套备份 / 快照 / 同步引擎,无需打开界面:
@@ -545,6 +592,10 @@ dsh-config-manager backup --sections skills,self # 收窄范围
545
592
  摆到用户面前。此外,为了让 GitHub 的 Contributors 列表如实计入这次贡献,其提交 `1248200` 已通过一次
546
593
  **「保留其提交、树取主线」**的合并(`122317b`)成为 `main` 的祖先 —— 该合并**不取其代码**(合并后的树与主线
547
594
  逐字节一致),只用于记录署名;因此按 GitHub 的口径这次贡献已被计入(而非 closed 后无痕)。
595
+ - **OMSociety** —— [PR #66](https://github.com/xiajiajun516/dsh-config-manager/pull/66)(合并提交 `4170af4`):提出并实现了
596
+ 36×36 无底框的 `icon.svg`(issue #61,对齐官方插件图标约定)。反馈自带上游取证(宿主图位 48/36 与 40/30、
597
+ 官方随包发布的图标形态与官方 fixture),PR 里附 16/30/36/48 × 深浅双主题实况图,并对「不换成品牌蓝」写了
598
+ 可复核的理由(后片若同色,深色模式下两层会融成一块、16px 读不出前后关系)。
548
599
  - **问题报告**:`zhyx1996`(#38 同步「导出密钥」未生效)、`IPF-Sinon`(#39 凭据 `refs:` 块未被识别,并在自己的 fork
549
600
  上完成实现与真机验证)、`zerginlaw`(#43「立即备份」空转却提示成功)—— 这三条报告各自驱动了一个修复。
550
601
 
package/icon.svg ADDED
@@ -0,0 +1,16 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="36" height="36" viewBox="0 0 36 36" fill="none" role="img" aria-label="DSH Config Manager">
2
+ <defs>
3
+ <linearGradient id="cm-snapshot-back" x1="4" y1="4" x2="26.1" y2="26.1" gradientUnits="userSpaceOnUse">
4
+ <stop stop-color="#45E7A4"/>
5
+ <stop offset="1" stop-color="#145AF3"/>
6
+ </linearGradient>
7
+ <linearGradient id="cm-snapshot-front" x1="9.9" y1="9.9" x2="32" y2="32" gradientUnits="userSpaceOnUse">
8
+ <stop stop-color="#67C7FE"/>
9
+ <stop offset="1" stop-color="#324DE2"/>
10
+ </linearGradient>
11
+ </defs>
12
+ <rect x="4" y="4" width="22.1" height="22.1" rx="5.2" fill="url(#cm-snapshot-back)" fill-opacity="0.9"/>
13
+ <rect x="9.9" y="9.9" width="22.1" height="22.1" rx="5.2" fill="url(#cm-snapshot-front)"/>
14
+ <path d="M20.95 15.8V26.1" stroke="#FFFFFF" stroke-width="2.9" stroke-linecap="round"/>
15
+ <path d="M17.27 22.4L20.95 26.1L24.63 22.4" stroke="#FFFFFF" stroke-width="2.9" stroke-linecap="round" stroke-linejoin="round"/>
16
+ </svg>
@@ -1,6 +1,22 @@
1
+ /**
2
+ * agentInstructions 分区 adapter(设计 §3.3 / dsh-agent-instructions 插件):
3
+ * 数据源 = ~/.dsh/AGENTS.md(用户全局指令文件,dsh-agent-instructions 固定读取 $DSH_HOME/AGENTS.md,
4
+ * 注入每个会话的基线指令)。
5
+ *
6
+ * 与 skills/agentPresets 的差异:AGENTS.md 位于 $DSH_HOME 根目录,**不能对 homeDir 整目录递归枚举**,
7
+ * 因此覆写 `listRelPaths()` 返回单文件白名单(而不是目录扫描)。
8
+ * 仅迁移全局文件;项目级 AGENTS.md / CLAUDE.md 属于各项目仓库,默认不迁(研究报告 §2.2 同策略)。
9
+ * baseDir = ''(homeDir 根),与 core/backup.ts FILE_BASES 的 '' 基准一致,保证通用快照/回滚路径正确。
10
+ *
11
+ * 2026-09 修复(真机报告「总览/导出页变慢」):本类此前只覆写了 `export()`(单文件),
12
+ * 却让 v0.1.68 新增的 `preview()` 走了基类的 `collect()` → 对 `baseDir=''` 递归**整个 $DSH_HOME**
13
+ * (实测 4.8 s / 4016 个文件 / 242 MB,且选择器冒出 21 个假单元:profiles、sessions、attachments…)。
14
+ * 现在 `export()` 与 `preview()` 都经 `collect()`,但清单来自同一个 `listRelPaths()` 白名单
15
+ * —— 既快,又保证「预览说的」与「导出做的」逐项一致(这正是基类 preview() 的存在意义)。
16
+ */
1
17
  import { FileCollectionAdapter } from './file-collection.ts';
2
- import type { FilesSection } from '../schema/types.ts';
3
- import type { ExportOptions, ExportSection, HostContext } from '../core/types.ts';
18
+ import type { HostContext } from '../core/types.ts';
19
+ import type { RecursiveListing } from '../utils/recursive-walk.ts';
4
20
  export declare class AgentInstructionsAdapter extends FileCollectionAdapter {
5
21
  readonly id: 'agentInstructions';
6
22
  readonly displayName: string;
@@ -9,5 +25,14 @@ export declare class AgentInstructionsAdapter extends FileCollectionAdapter {
9
25
  readonly baseDir = "";
10
26
  /** 用户全局指令文件(相对 homeDir;同时是 schema 端该分区唯一的文件) */
11
27
  static readonly FILE = "AGENTS.md";
12
- export(ctx: HostContext, _options: ExportOptions): Promise<ExportSection<FilesSection>>;
28
+ /**
29
+ * 清单枚举 = **单文件白名单**(覆写基类的目录递归)。
30
+ *
31
+ * 为什么必须覆写:`baseDir` 是 ''(AGENTS.md 在 `$DSH_HOME` 根),基类会对整个 home 递归
32
+ * —— 4.8 s、4016 个文件,并把 `profiles/`/`sessions/` 等无关目录当成该分区的单元。
33
+ *
34
+ * 存在性判定优先走 `statSize`(只 stat);门面未实现(旧宿主 / 测试 mock)时退回读一次文件
35
+ * (单文件代价可忽略,且 `collect` 之后还要读它)。读不到 → 空清单(与「文件不存在 = 空分区」同语义)。
36
+ */
37
+ protected listRelPaths(ctx: HostContext): Promise<RecursiveListing>;
13
38
  }
@@ -3,13 +3,17 @@
3
3
  * 数据源 = ~/.dsh/AGENTS.md(用户全局指令文件,dsh-agent-instructions 固定读取 $DSH_HOME/AGENTS.md,
4
4
  * 注入每个会话的基线指令)。
5
5
  *
6
- * 与 skills/agentPresets 的差异:AGENTS.md 位于 $DSH_HOME 根目录,不能对 homeDir 整目录递归枚举,
7
- * 因此不复用 FileCollectionAdapter 的目录扫描,而是白名单固定收集单文件 AGENTS.md。
6
+ * 与 skills/agentPresets 的差异:AGENTS.md 位于 $DSH_HOME 根目录,**不能对 homeDir 整目录递归枚举**,
7
+ * 因此覆写 `listRelPaths()` 返回单文件白名单(而不是目录扫描)。
8
8
  * 仅迁移全局文件;项目级 AGENTS.md / CLAUDE.md 属于各项目仓库,默认不迁(研究报告 §2.2 同策略)。
9
9
  * baseDir = ''(homeDir 根),与 core/backup.ts FILE_BASES 的 '' 基准一致,保证通用快照/回滚路径正确。
10
+ *
11
+ * 2026-09 修复(真机报告「总览/导出页变慢」):本类此前只覆写了 `export()`(单文件),
12
+ * 却让 v0.1.68 新增的 `preview()` 走了基类的 `collect()` → 对 `baseDir=''` 递归**整个 $DSH_HOME**
13
+ * (实测 4.8 s / 4016 个文件 / 242 MB,且选择器冒出 21 个假单元:profiles、sessions、attachments…)。
14
+ * 现在 `export()` 与 `preview()` 都经 `collect()`,但清单来自同一个 `listRelPaths()` 白名单
15
+ * —— 既快,又保证「预览说的」与「导出做的」逐项一致(这正是基类 preview() 的存在意义)。
10
16
  */
11
- import { sha256Hex } from '../utils/hashing.js';
12
- import { msgOf } from '../core/messages.js';
13
17
  import { FileCollectionAdapter } from './file-collection.js';
14
18
  import { sectionMeta } from '../schema/section-registry.js';
15
19
  export class AgentInstructionsAdapter extends FileCollectionAdapter {
@@ -21,28 +25,28 @@ export class AgentInstructionsAdapter extends FileCollectionAdapter {
21
25
  baseDir = '';
22
26
  /** 用户全局指令文件(相对 homeDir;同时是 schema 端该分区唯一的文件) */
23
27
  static FILE = 'AGENTS.md';
24
- async export(ctx, _options) {
25
- const files = [];
28
+ /**
29
+ * 清单枚举 = **单文件白名单**(覆写基类的目录递归)。
30
+ *
31
+ * 为什么必须覆写:`baseDir` 是 ''(AGENTS.md 在 `$DSH_HOME` 根),基类会对整个 home 递归
32
+ * —— 4.8 s、4016 个文件,并把 `profiles/`/`sessions/` 等无关目录当成该分区的单元。
33
+ *
34
+ * 存在性判定优先走 `statSize`(只 stat);门面未实现(旧宿主 / 测试 mock)时退回读一次文件
35
+ * (单文件代价可忽略,且 `collect` 之后还要读它)。读不到 → 空清单(与「文件不存在 = 空分区」同语义)。
36
+ */
37
+ async listRelPaths(ctx) {
38
+ const empty = { paths: [], skippedLinks: [], followedLinks: 0, unreadableDirs: [] };
39
+ const rel = AgentInstructionsAdapter.FILE;
26
40
  try {
27
- const data = await ctx.fs.readFile(AgentInstructionsAdapter.FILE);
28
- files.push({
29
- relativePath: AgentInstructionsAdapter.FILE,
30
- data,
31
- contentHash: sha256Hex(data),
32
- });
41
+ if (ctx.fs.statSize !== undefined) {
42
+ return (await ctx.fs.statSize(rel)) === null ? empty : { ...empty, paths: [rel] };
43
+ }
44
+ await ctx.fs.readFile(rel);
45
+ return { ...empty, paths: [rel] };
33
46
  }
34
47
  catch {
35
- // 文件不存在 → 空分区(与 skills 目录不存在视为空一致),下方归档 dirEmpty 提示
48
+ return empty;
36
49
  }
37
- const warnings = [];
38
- if (files.length === 0)
39
- warnings.push(msgOf(ctx)('adapter.dirEmpty', { type: this.displayName }));
40
- return {
41
- sectionId: this.id,
42
- data: { version: 1, files },
43
- counts: { files: files.length },
44
- warnings,
45
- };
46
50
  }
47
51
  }
48
52
  //# sourceMappingURL=agent-instructions.js.map
@@ -1,6 +1,7 @@
1
1
  import type { MsgFunc } from '../core/messages.ts';
2
+ import type { RecursiveListing } from '../utils/recursive-walk.ts';
2
3
  import type { FilesSection, SectionId } from '../schema/types.ts';
3
- import type { ApplyResult, ConfigAdapter, ExportOptions, ExportSection, ExportUnit, HostContext, ImportContext, PlanItem, Portability, ValidationResult } from '../core/types.ts';
4
+ import type { ApplyResult, ConfigAdapter, ExportOptions, ExportSection, ExportUnit, HostContext, ImportContext, PlanItem, Portability, SectionPreview, ValidationResult } from '../core/types.ts';
4
5
  /**
5
6
  * 文件类分区导出的**单分区累计字节闸门**:分区内文件内容合计放不下的单元整块剔除(见 export())。
6
7
  *
@@ -66,6 +67,44 @@ export declare abstract class FileCollectionAdapter implements ConfigAdapter<Fil
66
67
  warnings: string[];
67
68
  } | null>;
68
69
  export(ctx: HostContext, options: ExportOptions): Promise<ExportSection<FilesSection>>;
70
+ /**
71
+ * 只读预览(`/export-preview`):与 `export()` **共用同一个内核**(`collect`),
72
+ * 只把「读文件内容 + SHA-256」换成 `ctx.fs.statSize`。
73
+ *
74
+ * 为什么必须共用:预览说的「会带哪些单元、各多大」就是用户勾选的依据,一旦与真实导出
75
+ * 分叉,用户会按预览勾选却拿到不同的包(`file-collection.test.ts` 逐项断言两者相等)。
76
+ */
77
+ preview(ctx: HostContext, options: ExportOptions): Promise<SectionPreview<FilesSection>>;
78
+ /**
79
+ * 分区收集内核(`export` / `preview` 共用)。
80
+ *
81
+ * - `mode === 'content'`:读文件内容 + 算 SHA-256(真实导出);
82
+ * - `mode === 'size'`:只取字节数(`ctx.fs.statSize`),不解码、不哈希(预览)。
83
+ *
84
+ * 两种模式共用**同一套**单元分组、条目级白名单、单分区字节闸门与告警生成 ——
85
+ * 因此「预览的单元 / 体积 / 告警」与「导出的单元 / 体积 / 告警」逐项相等。
86
+ *
87
+ * 返回值里的 `entries` **只含** `relativePath`/`data`/`contentHash` 三个字段:
88
+ * `FilesSection` 是备份格式的一部分,多带内部字段会被序列化出去;体积走旁路 `sizes`。
89
+ */
90
+ private collect;
91
+ /**
92
+ * 分区清单枚举(`export` / `preview` 共用;两者必须走同一份清单,否则预览与导出分叉)。
93
+ *
94
+ * **单文件分区必须覆写它**:`AgentInstructionsAdapter` 的 `baseDir` 是 ''(AGENTS.md 就放在
95
+ * `$DSH_HOME` 根),走缺省实现会递归**整个 home** —— 实测 4016 个文件 / 242 MB / 4.8 s,而且
96
+ * 选择器会把 `profiles/`、`sessions/`、`attachments/` 这些无关目录当成该分区的「单元」。
97
+ */
98
+ protected listRelPaths(ctx: HostContext): Promise<RecursiveListing>;
99
+ /**
100
+ * 读一个成员文件。
101
+ *
102
+ * - `content`:读内容并按需算 SHA-256(真实导出);
103
+ * - `size`:优先 `ctx.fs.statSize`(只 stat,不读内容);门面未实现或返回 null(文件消失 /
104
+ * 读不到)时**退回 readFile** —— 与改造前把「读不到」暴露成同一个错误,绝不把未知体积
105
+ * 当成 0(0 会让字节闸门少算,把本该剔除的单元放进预览,预览与导出就分叉了)。
106
+ */
107
+ private readMember;
69
108
  /** 单元清单(零 I/O:直接由 export 产物归并)。 */
70
109
  listUnits(section: ExportSection<FilesSection>): ExportUnit[];
71
110
  analyzeImport(data: FilesSection, ctx: ImportContext): Promise<PlanItem[]>;
@@ -16,7 +16,7 @@ import { sha256Hex } from '../utils/hashing.js';
16
16
  import { isReservedInternalRel, normalizePath } from '../utils/paths.js';
17
17
  import { msgOf, zhMsg } from '../core/messages.js';
18
18
  import { linkWarnings, listFilesDetailed } from './link-report.js';
19
- import { defaultUnitId, unitAllowed, unitsFromFiles } from './units.js';
19
+ import { defaultUnitId, unitAllowed, unitsFromEntries, unitsFromFiles } from './units.js';
20
20
  import { validateJsonSection } from './json-section.js';
21
21
  /**
22
22
  * 文件类分区导出的**单分区累计字节闸门**:分区内文件内容合计放不下的单元整块剔除(见 export())。
@@ -40,6 +40,14 @@ import { validateJsonSection } from './json-section.js';
40
40
  export const MAX_FILE_SECTION_BYTES = 256 * 1024 * 1024;
41
41
  /** 字节闸门告警里最多列出的单元数(其余折叠成 "(+ N)",避免大分区把报告撑爆;与 link-report 同口径) */
42
42
  const MAX_UNITS_PER_GATE_WARNING = 5;
43
+ /**
44
+ * 只读预览下文件条目的占位内容(长度 0)。
45
+ *
46
+ * 为什么可用空内容:预览产出的 `FilesSection` 只在内存里流转(选择器只需要 `relativePath`
47
+ * 与体积),**绝不**进 ZIP / 快照 / 导入路径 —— 空内容一旦被误当导出产物,写出去的会是空文件。
48
+ * 因此 `preview()` 的返回值在类型注释里明确标注「不是格式的一部分」(见 types.ts 的 SectionPreview)。
49
+ */
50
+ const EMPTY_DATA = new Uint8Array(0);
43
51
  /** 告警文案里的体积(人类可读;纯数值格式化,不含用户可见散文) */
44
52
  function formatBytes(bytes) {
45
53
  if (bytes < 1024)
@@ -95,14 +103,60 @@ export class FileCollectionAdapter {
95
103
  return null;
96
104
  }
97
105
  async export(ctx, options) {
106
+ const collected = await this.collect(ctx, options, 'content');
107
+ return {
108
+ sectionId: this.id,
109
+ data: { version: 1, files: collected.entries },
110
+ counts: { files: collected.entries.length },
111
+ warnings: collected.warnings,
112
+ };
113
+ }
114
+ /**
115
+ * 只读预览(`/export-preview`):与 `export()` **共用同一个内核**(`collect`),
116
+ * 只把「读文件内容 + SHA-256」换成 `ctx.fs.statSize`。
117
+ *
118
+ * 为什么必须共用:预览说的「会带哪些单元、各多大」就是用户勾选的依据,一旦与真实导出
119
+ * 分叉,用户会按预览勾选却拿到不同的包(`file-collection.test.ts` 逐项断言两者相等)。
120
+ */
121
+ async preview(ctx, options) {
122
+ const collected = await this.collect(ctx, options, 'size');
123
+ return {
124
+ section: {
125
+ sectionId: this.id,
126
+ data: { version: 1, files: collected.entries },
127
+ counts: { files: collected.entries.length },
128
+ warnings: collected.warnings,
129
+ },
130
+ sizeBytes: collected.totalBytes,
131
+ items: unitsFromEntries(this.id, collected.sizes, (rel) => this.unitIdOf(rel)),
132
+ // 本趟 stat 顺带取到的时间(宿主有 statInfo 时才有):调用方据此省掉第二趟逐文件 stat。
133
+ ...(collected.statTimes.size > 0 ? { statTimes: collected.statTimes } : {}),
134
+ };
135
+ }
136
+ /**
137
+ * 分区收集内核(`export` / `preview` 共用)。
138
+ *
139
+ * - `mode === 'content'`:读文件内容 + 算 SHA-256(真实导出);
140
+ * - `mode === 'size'`:只取字节数(`ctx.fs.statSize`),不解码、不哈希(预览)。
141
+ *
142
+ * 两种模式共用**同一套**单元分组、条目级白名单、单分区字节闸门与告警生成 ——
143
+ * 因此「预览的单元 / 体积 / 告警」与「导出的单元 / 体积 / 告警」逐项相等。
144
+ *
145
+ * 返回值里的 `entries` **只含** `relativePath`/`data`/`contentHash` 三个字段:
146
+ * `FilesSection` 是备份格式的一部分,多带内部字段会被序列化出去;体积走旁路 `sizes`。
147
+ */
148
+ async collect(ctx, options, mode) {
98
149
  const files = [];
150
+ const statTimes = new Map();
151
+ const sizes = [];
99
152
  const warnings = [];
100
153
  const allow = options.includeItems?.[this.id];
101
154
  // issue #37:用「跟随 junction/符号链接」的遍历,并把跟随/跳过的链接写进告警——
102
155
  // 此前链接目录及其全部内容被静默排除,备份仍报成功。
156
+ // 枚举走 listRelPaths 钩子(单文件分区覆写它,避免对 home 根整目录递归)。
103
157
  let listing = { paths: [], skippedLinks: [], followedLinks: 0, unreadableDirs: [] };
104
158
  try {
105
- listing = await listFilesDetailed(ctx.fs, this.baseDir);
159
+ listing = await this.listRelPaths(ctx);
106
160
  }
107
161
  catch {
108
162
  // 目录不存在视为空
@@ -148,11 +202,15 @@ export class FileCollectionAdapter {
148
202
  const pending = [];
149
203
  let unitBytes = 0;
150
204
  for (const member of unitEntries.get(unitId) ?? []) {
151
- const data = await ctx.fs.readFile(member.rel);
152
- unitBytes += data.byteLength;
205
+ const read = await this.readMember(ctx, member.rel, mode, statTimes);
206
+ unitBytes += read.sizeBytes;
153
207
  if (unitBytes > room)
154
208
  break;
155
- pending.push({ rel: member.rel, file: { relativePath: member.relPath, data, contentHash: sha256Hex(data) } });
209
+ pending.push({
210
+ rel: member.rel,
211
+ sizeBytes: read.sizeBytes,
212
+ entry: { relativePath: member.relPath, data: read.data, contentHash: read.contentHash },
213
+ });
156
214
  }
157
215
  if (unitBytes > room) {
158
216
  droppedUnits.push(unitId);
@@ -160,13 +218,15 @@ export class FileCollectionAdapter {
160
218
  }
161
219
  usedBytes += unitBytes;
162
220
  for (const p of pending)
163
- keptByRel.set(p.rel, p.file);
221
+ keptByRel.set(p.rel, { entry: p.entry, sizeBytes: p.sizeBytes });
164
222
  }
165
223
  // ③ 按 ① 的顺序落盘(未触发闸门时与改造前的文件顺序逐项一致)
166
224
  for (const rel of considered) {
167
- const file = keptByRel.get(rel);
168
- if (file !== undefined)
169
- files.push(file);
225
+ const kept = keptByRel.get(rel);
226
+ if (kept === undefined)
227
+ continue;
228
+ files.push(kept.entry);
229
+ sizes.push({ relativePath: kept.entry.relativePath, sizeBytes: kept.sizeBytes });
170
230
  }
171
231
  // 绝不静默:被整块剔除的单元写进告警(导出报告可见),并给出上限与实际保留量
172
232
  if (droppedUnits.length > 0) {
@@ -183,12 +243,43 @@ export class FileCollectionAdapter {
183
243
  if (rels.length === 0)
184
244
  warnings.push(msgOf(ctx)('adapter.dirEmpty', { type: this.displayName }));
185
245
  warnings.push(...linkWarnings(msgOf(ctx), this.displayName, listing));
186
- return {
187
- sectionId: this.id,
188
- data: { version: 1, files },
189
- counts: { files: files.length },
190
- warnings,
191
- };
246
+ return { entries: files, sizes, totalBytes: usedBytes, warnings, statTimes };
247
+ }
248
+ /**
249
+ * 分区清单枚举(`export` / `preview` 共用;两者必须走同一份清单,否则预览与导出分叉)。
250
+ *
251
+ * **单文件分区必须覆写它**:`AgentInstructionsAdapter` 的 `baseDir` 是 ''(AGENTS.md 就放在
252
+ * `$DSH_HOME` 根),走缺省实现会递归**整个 home** —— 实测 4016 个文件 / 242 MB / 4.8 s,而且
253
+ * 选择器会把 `profiles/`、`sessions/`、`attachments/` 这些无关目录当成该分区的「单元」。
254
+ */
255
+ async listRelPaths(ctx) {
256
+ return listFilesDetailed(ctx.fs, this.baseDir);
257
+ }
258
+ /**
259
+ * 读一个成员文件。
260
+ *
261
+ * - `content`:读内容并按需算 SHA-256(真实导出);
262
+ * - `size`:优先 `ctx.fs.statSize`(只 stat,不读内容);门面未实现或返回 null(文件消失 /
263
+ * 读不到)时**退回 readFile** —— 与改造前把「读不到」暴露成同一个错误,绝不把未知体积
264
+ * 当成 0(0 会让字节闸门少算,把本该剔除的单元放进预览,预览与导出就分叉了)。
265
+ */
266
+ async readMember(ctx, rel, mode, statTimes) {
267
+ if (mode === 'size') {
268
+ // 优先 statInfo:一次 stat 同时拿体积与 mtime(sessions 的排序时间顺带就有了,
269
+ // 省掉 unitActivityTimes 的第二趟逐文件 stat —— 实测会话树 983 个文件)。
270
+ if (ctx.fs.statInfo !== undefined) {
271
+ const info = await ctx.fs.statInfo(rel);
272
+ if (info !== null) {
273
+ statTimes?.set(rel, info.mtimeMs);
274
+ return { data: EMPTY_DATA, sizeBytes: info.size, contentHash: '' };
275
+ }
276
+ }
277
+ const size = (await ctx.fs.statSize?.(rel)) ?? null;
278
+ if (size !== null)
279
+ return { data: EMPTY_DATA, sizeBytes: size, contentHash: '' };
280
+ }
281
+ const data = await ctx.fs.readFile(rel);
282
+ return { data, sizeBytes: data.byteLength, contentHash: mode === 'content' ? sha256Hex(data) : '' };
192
283
  }
193
284
  /** 单元清单(零 I/O:直接由 export 产物归并)。 */
194
285
  listUnits(section) {
@@ -7,7 +7,9 @@ export declare const USER_PATCH_FILE = "cordis.patch.yml";
7
7
  * 本地源插件打包钩子(由宿主注入,见 src/index.ts createAdapters)。
8
8
  * 返回打包结果(含字节与重写后的 spec);不注入 = 不做本地源打包(保持旧行为)。
9
9
  */
10
- export type LocalPluginPackHook = (plugins: PluginEntry[], ctx: HostContext) => Promise<PackLocalPluginsResult>;
10
+ export type LocalPluginPackHook = (plugins: PluginEntry[],
11
+ /** 结构化类型:HostContext 满足它(core 侧刻意不 import adapters 的 HostContext 类型) */
12
+ ctx: Pick<HostContext, 'profile' | 'profileDir'>) => Promise<PackLocalPluginsResult>;
11
13
  /** 本地插件 tarball 解包到 homeDir 内的固定缓存目录(相对 $DSH_HOME)。 */
12
14
  export declare const LOCAL_TARBALL_CACHE_REL = "dsh-config-manager/local-plugins";
13
15
  /** 归档内相对路径 → 缓存目录下的安全文件名(拒绝任何路径穿越)。 */
@@ -1,5 +1,37 @@
1
- import type { ExportOptions, ExportSection, HostContext } from '../core/types.ts';
2
- import type { FilesSection } from '../schema/types.ts';
1
+ /**
2
+ * self 分区 adapter(插件自身配置,设计「self 分区」):
3
+ * 数据源 = $DSH_HOME/dsh-config-manager/ 下的插件自身配置文件(白名单收集,非递归):
4
+ * - sync/sync-config.json 同步通道配置(git repoUrl / webdav url+username;凭据值走 credentials 槽位,不含 secret)
5
+ * - sync/sync-autosync.json 自动同步调度配置
6
+ * - sync/sync-selection.json 同步分区选择(默认/高级模式 + 勾选分区)
7
+ * - sync/ui-prefs.json 插件 UI 偏好(如上次选择的同步通道;从 localStorage 迁入)
8
+ * - market/market-config.json 配置市场配置
9
+ *
10
+ * 排除项:sync/sync-history.json(执行记录,属数据非配置)、market/cache/(缓存)、
11
+ * snapshots/(快照)、tmp/ exports/(临时/导出产物)——只备份「配置」,不备份数据。
12
+ *
13
+ * 实现:继承 FileCollectionAdapter 复用 analyzeImport/applyItem/validate(幂等 hash 比对、
14
+ * 快照/回滚路径一致);**清单来自覆写的 listRelPaths()(白名单),export() 与 preview() 都经
15
+ * 基类 collect()**(self 目录内存在大量非配置子目录,不能像 skills/sessions 那样整体递归)。
16
+ *
17
+ * 为什么必须靠 listRelPaths() 而不是覆写 export() 来收窄(2026-10 真机报告):
18
+ * `preview()` / `listRelPaths()` 是后来才加进基类的。本类此前只覆写 export(),于是只读预览
19
+ * 落到基类的目录递归 —— 把整个 $DSH_HOME/dsh-config-manager(快照、config-snapshots、
20
+ * sync/work 的 Git 工作副本与远端快照、transactions、exports、遗留 profiles)都当成该分区的
21
+ * 可勾选单元:实测 2460 个文件 / 11.44 MB,而真实导出只有 6 个文件 / 1936 B;界面上
22
+ * 「插件自身配置 已选 2460/2460 11.5 MB」即由此而来。同款缺陷已在 agent-instructions.ts 踩过
23
+ * 并修复(那边 baseDir='' 时更夸张:把整个 home 当该分区)。覆写 listRelPaths() 让两条路径
24
+ * 共用同一份清单 —— 这正是基类 preview() 存在的意义。
25
+ *
26
+ * relativePath 一律是「相对 baseDir」的路径(如 sync/sync-config.json),与
27
+ * FileCollectionAdapter 的基准目录语义一致:导入时按 path.join(baseDir, rel) 写回
28
+ * $DSH_HOME/dsh-config-manager/<rel>;ZIP 内位于 self/<rel>。
29
+ *
30
+ * 安全不变量:配置文件本身不含凭据值(同步凭据走 DSH credentials 槽位引用),
31
+ * 且文件类分区不进 SecretScanner(与 pluginFiles/skills 同语义)。
32
+ */
33
+ import type { HostContext } from '../core/types.ts';
34
+ import type { RecursiveListing } from '../utils/recursive-walk.ts';
3
35
  import { FileCollectionAdapter } from './file-collection.ts';
4
36
  /** self 分区白名单文件(相对 baseDir,即 $DSH_HOME/dsh-config-manager/)。 */
5
37
  export declare const SELF_CONFIG_FILES: readonly string[];
@@ -11,10 +43,24 @@ export declare class SelfAdapter extends FileCollectionAdapter {
11
43
  /** 插件自身配置目录(相对 homeDir;宿主按 dataDir 解析注入,缺省 dsh-config-manager) */
12
44
  readonly baseDir: string;
13
45
  constructor(baseDir?: string);
14
- /** 白名单收集:只导出配置类文件(存在才收),不递归(排除快照/历史/缓存/临时产物)。
15
- * relativePath 产出相对 baseDir 的路径(与 analyzeImport/applyItem 的 path.join(baseDir, ref) 匹配)。 */
16
46
  /** self 的白名单文件彼此独立(同步配置 / 市场配置 / UI 偏好…),不构成 bundle
17
47
  * → 覆写为「逐文件单元」,用户可以只带走其中几项。 */
18
48
  protected unitIdOf(relativePath: string): string;
19
- export(ctx: HostContext, options: ExportOptions): Promise<ExportSection<FilesSection>>;
49
+ /**
50
+ * 清单枚举 = **白名单文件**(覆写基类的目录递归)。
51
+ *
52
+ * 为什么必须覆写:self 目录下并存着插件自身的数据(snapshots/、config-snapshots/、
53
+ * sync/work/ 的 Git 工作副本与远端快照、transactions/、exports/、遗留 profiles/),
54
+ * 整目录递归会把它们全部当成「可勾选单元」(实测本机 2460 个文件 / 11.44 MB)。
55
+ * 覆写后 export()(基类 collect,content 模式)与 preview()(size 模式)拿到**同一份清单**,
56
+ * 选择器显示的条目/体积与真实导出逐项一致。
57
+ *
58
+ * 存在性判定优先 statSize(只 stat;白名单最多 7 个文件);旧宿主未实现时退回读一次文件
59
+ * (单文件代价可忽略,且 collect 之后还要读它)。读不到 / 未创建 → 跳过,
60
+ * 与「文件不存在 = 非白名单命中」同语义(如从未配置市场/同步)。
61
+ *
62
+ * 路径一律用 POSIX 分隔符拼:relativePath 是**备份格式的一部分**(ZIP 内为 self/<rel>),
63
+ * 同时是单元 id(self:<rel>)与 includeItems 的比对键 —— 不能随平台漂移成反斜杠。
64
+ */
65
+ protected listRelPaths(ctx: HostContext): Promise<RecursiveListing>;
20
66
  }