dsh-config-manager 0.1.62 → 0.1.64

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 (471) hide show
  1. package/README.md +45 -6
  2. package/README.zh-CN.md +20 -5
  3. package/lib/adapters/agent-instructions.d.ts +3 -3
  4. package/lib/adapters/agent-instructions.js +5 -3
  5. package/lib/adapters/agent-presets.d.ts +3 -3
  6. package/lib/adapters/agent-presets.js +5 -3
  7. package/lib/adapters/credentials.d.ts +3 -3
  8. package/lib/adapters/credentials.js +20 -23
  9. package/lib/adapters/file-collection.d.ts +43 -0
  10. package/lib/adapters/file-collection.js +130 -18
  11. package/lib/adapters/index.d.ts +1 -0
  12. package/lib/adapters/index.js +11 -0
  13. package/lib/adapters/json-section.d.ts +32 -0
  14. package/lib/adapters/json-section.js +61 -0
  15. package/lib/adapters/mcp.d.ts +3 -3
  16. package/lib/adapters/mcp.js +16 -19
  17. package/lib/adapters/plugin-files.d.ts +3 -3
  18. package/lib/adapters/plugin-files.js +11 -14
  19. package/lib/adapters/plugins.d.ts +3 -3
  20. package/lib/adapters/plugins.js +39 -42
  21. package/lib/adapters/prompts.d.ts +3 -3
  22. package/lib/adapters/prompts.js +16 -20
  23. package/lib/adapters/providers.d.ts +3 -3
  24. package/lib/adapters/providers.js +23 -26
  25. package/lib/adapters/sessions.d.ts +97 -1
  26. package/lib/adapters/sessions.js +455 -15
  27. package/lib/adapters/settings.d.ts +3 -3
  28. package/lib/adapters/settings.js +23 -26
  29. package/lib/adapters/skills.d.ts +3 -3
  30. package/lib/adapters/skills.js +5 -3
  31. package/lib/adapters/test-helpers.d.ts +8 -0
  32. package/lib/adapters/test-helpers.js +8 -0
  33. package/lib/adapters/ui.d.ts +3 -3
  34. package/lib/adapters/ui.js +14 -17
  35. package/lib/adapters/workspaces.d.ts +22 -0
  36. package/lib/adapters/workspaces.js +237 -16
  37. package/lib/cli/index.d.ts +35 -4
  38. package/lib/cli/index.js +147 -25
  39. package/lib/cli/sessions-repair.d.ts +28 -0
  40. package/lib/cli/sessions-repair.js +206 -0
  41. package/lib/client.d.ts +261 -20
  42. package/lib/client.js +9265 -5993
  43. package/lib/core/analyzer.d.ts +67 -5
  44. package/lib/core/analyzer.js +402 -91
  45. package/lib/core/backup-plan.d.ts +7 -1
  46. package/lib/core/backup-plan.js +15 -8
  47. package/lib/core/backup.d.ts +53 -2
  48. package/lib/core/backup.js +120 -24
  49. package/lib/core/boot-safety.d.ts +50 -0
  50. package/lib/core/boot-safety.js +211 -0
  51. package/lib/core/config-lifecycle.d.ts +0 -1
  52. package/lib/core/config-lifecycle.js +0 -2
  53. package/lib/core/config-snapshot.d.ts +3 -1
  54. package/lib/core/config-snapshot.js +25 -6
  55. package/lib/core/credential-status.d.ts +15 -0
  56. package/lib/core/credential-status.js +13 -0
  57. package/lib/core/exporter.d.ts +18 -0
  58. package/lib/core/exporter.js +115 -3
  59. package/lib/core/importer.d.ts +15 -2
  60. package/lib/core/importer.js +10 -3
  61. package/lib/core/index.d.ts +2 -0
  62. package/lib/core/index.js +3 -0
  63. package/lib/core/journal.d.ts +74 -5
  64. package/lib/core/journal.js +127 -12
  65. package/lib/core/messages.d.ts +54 -0
  66. package/lib/core/messages.js +110 -0
  67. package/lib/core/migration-consult.js +0 -4
  68. package/lib/core/model-tools.js +6 -9
  69. package/lib/core/path-mapping.d.ts +11 -0
  70. package/lib/core/path-mapping.js +28 -0
  71. package/lib/core/phase3-host.d.ts +79 -6
  72. package/lib/core/phase3-host.js +223 -73
  73. package/lib/core/phase4-crash-child.js +0 -1
  74. package/lib/core/plugin-cli.js +1 -1
  75. package/lib/core/reconcile.d.ts +8 -1
  76. package/lib/core/reconcile.js +12 -4
  77. package/lib/core/recovery-orchestrator.js +1 -1
  78. package/lib/core/restore.js +10 -4
  79. package/lib/core/rollback.js +1 -1
  80. package/lib/core/run-registry.d.ts +63 -3
  81. package/lib/core/run-registry.js +74 -4
  82. package/lib/core/session-meta.d.ts +27 -8
  83. package/lib/core/session-meta.js +59 -32
  84. package/lib/core/session-repair.d.ts +89 -0
  85. package/lib/core/session-repair.js +118 -0
  86. package/lib/core/session-select.d.ts +50 -0
  87. package/lib/core/session-select.js +137 -0
  88. package/lib/core/startup-barrier.d.ts +15 -0
  89. package/lib/core/startup-barrier.js +24 -2
  90. package/lib/core/transaction-coordinator.js +3 -3
  91. package/lib/core/types.d.ts +241 -0
  92. package/lib/core/validator.js +0 -1
  93. package/lib/index.d.ts +367 -6
  94. package/lib/index.js +909 -3278
  95. package/lib/market/index-parser.d.ts +2 -1
  96. package/lib/market/index-parser.js +27 -6
  97. package/lib/market/my-repo.d.ts +0 -1
  98. package/lib/market/my-repo.js +0 -6
  99. package/lib/market/prepare.d.ts +9 -0
  100. package/lib/market/prepare.js +5 -31
  101. package/lib/market/reader.js +0 -1
  102. package/lib/market/security.js +3 -9
  103. package/lib/routes/backup.d.ts +9 -0
  104. package/lib/routes/backup.js +99 -0
  105. package/lib/routes/consult.d.ts +9 -0
  106. package/lib/routes/consult.js +134 -0
  107. package/lib/routes/context.d.ts +9 -0
  108. package/lib/routes/context.js +2 -0
  109. package/lib/routes/history.d.ts +9 -0
  110. package/lib/routes/history.js +53 -0
  111. package/lib/routes/import.d.ts +9 -0
  112. package/lib/routes/import.js +390 -0
  113. package/lib/routes/index.d.ts +10 -0
  114. package/lib/routes/index.js +27 -0
  115. package/lib/routes/kit.d.ts +98 -0
  116. package/lib/routes/kit.js +161 -0
  117. package/lib/routes/market.d.ts +9 -0
  118. package/lib/routes/market.js +256 -0
  119. package/lib/routes/me.d.ts +9 -0
  120. package/lib/routes/me.js +170 -0
  121. package/lib/routes/prefs.d.ts +9 -0
  122. package/lib/routes/prefs.js +103 -0
  123. package/lib/routes/profiles.d.ts +9 -0
  124. package/lib/routes/profiles.js +130 -0
  125. package/lib/routes/recovery.d.ts +9 -0
  126. package/lib/routes/recovery.js +137 -0
  127. package/lib/routes/snapshots.d.ts +9 -0
  128. package/lib/routes/snapshots.js +214 -0
  129. package/lib/routes/sync.d.ts +9 -0
  130. package/lib/routes/sync.js +783 -0
  131. package/lib/schema/config.d.ts +10 -9
  132. package/lib/schema/config.js +78 -96
  133. package/lib/schema/index.d.ts +8 -1
  134. package/lib/schema/index.js +13 -1
  135. package/lib/schema/manifest.d.ts +2 -0
  136. package/lib/schema/manifest.js +1 -0
  137. package/lib/schema/section-registry.d.ts +189 -0
  138. package/lib/schema/section-registry.js +258 -0
  139. package/lib/schema/types.d.ts +13 -3
  140. package/lib/schema/versions.d.ts +11 -6
  141. package/lib/schema/versions.js +27 -1
  142. package/lib/security/secret-scanner.js +1 -1
  143. package/lib/security/zip-security.d.ts +8 -5
  144. package/lib/security/zip-security.js +20 -90
  145. package/lib/sync/ancestor.d.ts +11 -0
  146. package/lib/sync/ancestor.js +20 -0
  147. package/lib/sync/autosync-config.js +9 -18
  148. package/lib/sync/autosync-scheduler.d.ts +7 -2
  149. package/lib/sync/autosync-scheduler.js +35 -17
  150. package/lib/sync/backup-schedule-config.js +0 -1
  151. package/lib/sync/blob-store.d.ts +85 -0
  152. package/lib/sync/blob-store.js +85 -0
  153. package/lib/sync/git/git-transport.d.ts +28 -5
  154. package/lib/sync/git/git-transport.js +170 -24
  155. package/lib/sync/github-auth.d.ts +0 -1
  156. package/lib/sync/github-auth.js +0 -2
  157. package/lib/sync/layout.d.ts +16 -2
  158. package/lib/sync/layout.js +48 -7
  159. package/lib/sync/risk.d.ts +18 -12
  160. package/lib/sync/risk.js +17 -28
  161. package/lib/sync/session-tombstones.d.ts +39 -0
  162. package/lib/sync/session-tombstones.js +125 -0
  163. package/lib/sync/sync-config.d.ts +26 -3
  164. package/lib/sync/sync-config.js +40 -3
  165. package/lib/sync/sync-engine.d.ts +140 -12
  166. package/lib/sync/sync-engine.js +266 -49
  167. package/lib/sync/sync-history.js +0 -1
  168. package/lib/sync/sync-selection.d.ts +18 -0
  169. package/lib/sync/sync-selection.js +50 -24
  170. package/lib/sync/sync-state.d.ts +31 -4
  171. package/lib/sync/sync-state.js +38 -6
  172. package/lib/sync/transport.d.ts +103 -0
  173. package/lib/sync/transport.js +121 -0
  174. package/lib/sync/ui-prefs.d.ts +4 -2
  175. package/lib/sync/ui-prefs.js +6 -4
  176. package/lib/sync/webdav/webdav-transport.d.ts +36 -4
  177. package/lib/sync/webdav/webdav-transport.js +242 -43
  178. package/lib/ui/backup-schedule.d.ts +4 -2
  179. package/lib/ui/backup-schedule.js +9 -6
  180. package/lib/ui/export-flow.d.ts +2 -2
  181. package/lib/ui/export-flow.js +63 -17
  182. package/lib/ui/i18n.d.ts +12 -1
  183. package/lib/ui/i18n.js +25 -2
  184. package/lib/ui/import-log.d.ts +72 -0
  185. package/lib/ui/import-log.js +110 -0
  186. package/lib/ui/import-stepper.d.ts +14 -0
  187. package/lib/ui/import-stepper.js +18 -0
  188. package/lib/ui/import-wizard.d.ts +76 -0
  189. package/lib/ui/import-wizard.js +96 -2
  190. package/lib/ui/mid-ellipsis.d.ts +31 -0
  191. package/lib/ui/mid-ellipsis.js +39 -0
  192. package/lib/ui/my-configs-view.d.ts +136 -0
  193. package/lib/ui/my-configs-view.js +127 -0
  194. package/lib/ui/progress.js +6 -0
  195. package/lib/ui/report.d.ts +26 -0
  196. package/lib/ui/report.js +37 -0
  197. package/lib/ui/runs-view.d.ts +128 -0
  198. package/lib/ui/runs-view.js +123 -0
  199. package/lib/ui/selection-model.d.ts +90 -1
  200. package/lib/ui/selection-model.js +243 -1
  201. package/lib/ui/snapshots-view.d.ts +116 -0
  202. package/lib/ui/snapshots-view.js +141 -0
  203. package/lib/ui/sync-settings-view.d.ts +295 -0
  204. package/lib/ui/sync-settings-view.js +280 -0
  205. package/lib/ui/test-helpers.d.ts +7 -1
  206. package/lib/ui/test-helpers.js +4 -1
  207. package/lib/ui/types.d.ts +8 -1
  208. package/lib/utils/env-lock.d.ts +38 -5
  209. package/lib/utils/env-lock.js +55 -37
  210. package/lib/utils/logger.d.ts +17 -13
  211. package/lib/utils/logger.js +79 -41
  212. package/lib/utils/paths.d.ts +1 -1
  213. package/lib/utils/paths.js +2 -2
  214. package/lib/utils/session-log.d.ts +70 -0
  215. package/lib/utils/session-log.js +347 -0
  216. package/lib/utils/zip.d.ts +49 -5
  217. package/lib/utils/zip.js +240 -51
  218. package/lib/utils/zstd-frame.d.ts +44 -0
  219. package/lib/utils/zstd-frame.js +115 -0
  220. package/package.json +2 -2
  221. package/src/adapters/agent-instructions.ts +5 -3
  222. package/src/adapters/agent-presets.ts +5 -3
  223. package/src/adapters/credentials.ts +19 -22
  224. package/src/adapters/file-collection.test.ts +256 -0
  225. package/src/adapters/file-collection.ts +129 -17
  226. package/src/adapters/index.ts +12 -1
  227. package/src/adapters/json-section.ts +73 -0
  228. package/src/adapters/mcp.ts +16 -19
  229. package/src/adapters/plugin-files.ts +11 -14
  230. package/src/adapters/plugins.test.ts +1 -1
  231. package/src/adapters/plugins.ts +39 -42
  232. package/src/adapters/prompts.ts +16 -20
  233. package/src/adapters/providers.ts +22 -25
  234. package/src/adapters/roundtrip.test.ts +44 -0
  235. package/src/adapters/sessions.test.ts +400 -1
  236. package/src/adapters/sessions.ts +435 -16
  237. package/src/adapters/settings.ts +22 -25
  238. package/src/adapters/skills.ts +5 -3
  239. package/src/adapters/test-helpers.ts +8 -0
  240. package/src/adapters/ui.ts +14 -17
  241. package/src/adapters/workspaces.test.ts +226 -2
  242. package/src/adapters/workspaces.ts +247 -16
  243. package/src/cli/index.ts +154 -28
  244. package/src/cli/sessions-repair.ts +229 -0
  245. package/src/client/CLIENT_DEPENDENCIES.md +3 -1
  246. package/src/client/ConfigManagerSection.tsx +38 -7
  247. package/src/client/api.ts +160 -237
  248. package/src/client/common/ContentPicker.tsx +33 -8
  249. package/src/client/common/ProgressBar.tsx +18 -9
  250. package/src/client/common/ReportView.tsx +127 -53
  251. package/src/client/common/RunsCenter.tsx +377 -0
  252. package/src/client/common/client-route-parity.test.ts +171 -0
  253. package/src/client/common/http-usage-guard.test.ts +163 -0
  254. package/src/client/common/http.test.ts +225 -0
  255. package/src/client/common/http.ts +238 -0
  256. package/src/client/common/plan-text-redaction.test.ts +29 -7
  257. package/src/client/common/progress-view.test.ts +33 -1
  258. package/src/client/common/progress-view.ts +17 -0
  259. package/src/client/common/routes.ts +132 -0
  260. package/src/client/common/runs-center-guard.test.ts +44 -0
  261. package/src/client/config-manager.module.css +332 -1
  262. package/src/client/export/ExportView.tsx +27 -3
  263. package/src/client/history/HistoryPanel.tsx +0 -1
  264. package/src/client/history/history-api.ts +34 -61
  265. package/src/client/import/ImportLogPanel.tsx +136 -0
  266. package/src/client/import/ImportWizardView.tsx +442 -836
  267. package/src/client/import/PathMappingForm.tsx +12 -1
  268. package/src/client/import/import-wizard-split.test.ts +101 -0
  269. package/src/client/import/import-wizard-steps.tsx +352 -0
  270. package/src/client/import/use-import-wizard-controller.ts +459 -0
  271. package/src/client/index.ts +0 -2
  272. package/src/client/lifecycle/lifecycle-api.ts +17 -88
  273. package/src/client/locales.ts +142 -2
  274. package/src/client/market/MyConfigsInstall.tsx +92 -0
  275. package/src/client/market/MyConfigsList.tsx +120 -0
  276. package/src/client/market/MyConfigsLoginCard.tsx +99 -0
  277. package/src/client/market/MyConfigsView.tsx +148 -476
  278. package/src/client/market/MyConfigsWizard.tsx +252 -0
  279. package/src/client/market/market-api.test.ts +36 -0
  280. package/src/client/market/market-api.ts +13 -70
  281. package/src/client/market/market-view.ts +1 -1
  282. package/src/client/market/my-configs-api.ts +17 -71
  283. package/src/client/market/my-configs-split.test.ts +58 -0
  284. package/src/client/overview/OverviewPanel.tsx +2 -10
  285. package/src/client/recovery/RecoveryPanel.tsx +1 -2
  286. package/src/client/recovery/recovery-api.ts +15 -65
  287. package/src/client/run-store.test.ts +421 -3
  288. package/src/client/run-store.ts +284 -53
  289. package/src/client/snapshots/BackupScheduleCard.tsx +437 -0
  290. package/src/client/snapshots/SnapshotsEmptyState.tsx +31 -0
  291. package/src/client/snapshots/SnapshotsListTable.tsx +97 -0
  292. package/src/client/snapshots/SnapshotsPanel.tsx +42 -626
  293. package/src/client/sync/AutosyncCard.tsx +88 -0
  294. package/src/client/sync/ChannelConfigDialog.tsx +268 -0
  295. package/src/client/sync/DecryptPasswordCard.tsx +48 -0
  296. package/src/client/sync/SecurityOptionsCard.tsx +83 -0
  297. package/src/client/sync/SyncChannelEntryCard.tsx +78 -0
  298. package/src/client/sync/SyncConfirmView.tsx +10 -8
  299. package/src/client/sync/SyncSectionPickerDialog.tsx +182 -0
  300. package/src/client/sync/SyncSettingsView.test.ts +85 -0
  301. package/src/client/sync/SyncSettingsView.tsx +230 -649
  302. package/src/client/sync/history-model.ts +4 -10
  303. package/src/client/sync/sync-api.ts +59 -100
  304. package/src/client/sync/sync-locales.ts +16 -2
  305. package/src/client/sync/sync-view-v2.test.ts +30 -4
  306. package/src/client/sync/sync-view.ts +67 -17
  307. package/src/core/analyzer-mapping.test.ts +55 -0
  308. package/src/core/analyzer.ts +451 -91
  309. package/src/core/apply-order.test.ts +182 -0
  310. package/src/core/backup-plan.test.ts +141 -0
  311. package/src/core/backup-plan.ts +16 -8
  312. package/src/core/backup.test.ts +310 -0
  313. package/src/core/backup.ts +174 -23
  314. package/src/core/base-path-rebase.test.ts +97 -0
  315. package/src/core/boot-rescue.test.ts +26 -0
  316. package/src/core/boot-safety.test.ts +138 -0
  317. package/src/core/boot-safety.ts +248 -0
  318. package/src/core/config-lifecycle.test.ts +255 -2
  319. package/src/core/config-lifecycle.ts +0 -2
  320. package/src/core/config-snapshot.test.ts +193 -3
  321. package/src/core/config-snapshot.ts +24 -8
  322. package/src/core/credential-status.test.ts +35 -0
  323. package/src/core/credential-status.ts +25 -0
  324. package/src/core/export-coupling.test.ts +255 -0
  325. package/src/core/exporter.ts +110 -3
  326. package/src/core/finalize-order.test.ts +82 -0
  327. package/src/core/import-sessions-visibility.test.ts +93 -0
  328. package/src/core/importer.ts +21 -3
  329. package/src/core/index.ts +10 -0
  330. package/src/core/journal.test.ts +59 -2
  331. package/src/core/journal.ts +152 -10
  332. package/src/core/messages.ts +110 -0
  333. package/src/core/migration-consult.ts +0 -5
  334. package/src/core/migration-history.test.ts +3 -5
  335. package/src/core/model-tools.test.ts +0 -14
  336. package/src/core/model-tools.ts +6 -8
  337. package/src/core/path-mapping.ts +33 -0
  338. package/src/core/phase1-wiring.test.ts +12 -3
  339. package/src/core/phase3-consistency.test.ts +1 -1
  340. package/src/core/phase3-host.ts +248 -70
  341. package/src/core/phase3-p2b-fingerprint.test.ts +0 -1
  342. package/src/core/phase3-production-integration.test.ts +1 -1
  343. package/src/core/phase4-crash-child.ts +0 -1
  344. package/src/core/phase4-crash-injection.test.ts +1 -3
  345. package/src/core/plugin-cli.fs.test.ts +2 -2
  346. package/src/core/plugin-cli.ts +1 -1
  347. package/src/core/reconcile.test.ts +62 -0
  348. package/src/core/reconcile.ts +20 -5
  349. package/src/core/recovery-orchestrator.test.ts +13 -1
  350. package/src/core/recovery-orchestrator.ts +1 -1
  351. package/src/core/restore.ts +10 -4
  352. package/src/core/rollback.ts +1 -1
  353. package/src/core/run-registry-runs.test.ts +71 -0
  354. package/src/core/run-registry.test.ts +58 -7
  355. package/src/core/run-registry.ts +96 -5
  356. package/src/core/session-meta.test.ts +59 -1
  357. package/src/core/session-meta.ts +62 -29
  358. package/src/core/session-repair.test.ts +91 -0
  359. package/src/core/session-repair.ts +203 -0
  360. package/src/core/session-select.test.ts +73 -0
  361. package/src/core/session-select.ts +131 -0
  362. package/src/core/smoke.test.ts +1 -2
  363. package/src/core/startup-barrier.ts +29 -2
  364. package/src/core/transaction-coordinator.ts +3 -3
  365. package/src/core/types.ts +244 -0
  366. package/src/core/validator.ts +0 -1
  367. package/src/index.sync.test.ts +8 -0
  368. package/src/index.ts +530 -2757
  369. package/src/market/git-file-writer.test.ts +1 -1
  370. package/src/market/index-parser.ts +29 -6
  371. package/src/market/market.test.ts +146 -1
  372. package/src/market/my-repo.test.ts +57 -4
  373. package/src/market/my-repo.ts +0 -7
  374. package/src/market/prepare.ts +12 -32
  375. package/src/market/reader.ts +0 -1
  376. package/src/market/security.ts +3 -11
  377. package/src/routes/backup.ts +103 -0
  378. package/src/routes/consult.ts +139 -0
  379. package/src/routes/context.ts +10 -0
  380. package/src/routes/history.ts +56 -0
  381. package/src/routes/import.ts +399 -0
  382. package/src/routes/index.ts +36 -0
  383. package/src/routes/kit.ts +222 -0
  384. package/src/routes/market.ts +275 -0
  385. package/src/routes/me.ts +168 -0
  386. package/src/routes/prefs.ts +104 -0
  387. package/src/routes/profiles.ts +131 -0
  388. package/src/routes/recovery.ts +113 -0
  389. package/src/routes/snapshots.ts +219 -0
  390. package/src/routes/sync.ts +810 -0
  391. package/src/schema/config.ts +96 -91
  392. package/src/schema/index.ts +33 -1
  393. package/src/schema/manifest.ts +3 -0
  394. package/src/schema/registry.test.ts +257 -0
  395. package/src/schema/section-registry.ts +423 -0
  396. package/src/schema/types.ts +13 -3
  397. package/src/schema/versions.ts +31 -1
  398. package/src/security/secret-scanner.ts +1 -1
  399. package/src/security/security.test.ts +145 -2
  400. package/src/security/zip-security.ts +20 -92
  401. package/src/sync/ancestor.test.ts +0 -1
  402. package/src/sync/ancestor.ts +23 -0
  403. package/src/sync/autosync-config.ts +9 -18
  404. package/src/sync/autosync-scheduler.test.ts +60 -3
  405. package/src/sync/autosync-scheduler.ts +45 -19
  406. package/src/sync/backup-schedule-config.ts +0 -1
  407. package/src/sync/backup-scheduler.test.ts +15 -2
  408. package/src/sync/blob-store.test.ts +191 -0
  409. package/src/sync/blob-store.ts +139 -0
  410. package/src/sync/git/git-transport.test.ts +203 -1
  411. package/src/sync/git/git-transport.ts +173 -24
  412. package/src/sync/github-auth.ts +0 -2
  413. package/src/sync/layout.ts +62 -8
  414. package/src/sync/risk.test.ts +32 -1
  415. package/src/sync/risk.ts +26 -31
  416. package/src/sync/session-tombstones.test.ts +87 -0
  417. package/src/sync/session-tombstones.ts +123 -0
  418. package/src/sync/sync-config.test.ts +132 -0
  419. package/src/sync/sync-config.ts +47 -6
  420. package/src/sync/sync-credentials.test.ts +77 -0
  421. package/src/sync/sync-engine.test.ts +799 -8
  422. package/src/sync/sync-engine.ts +322 -51
  423. package/src/sync/sync-history.ts +0 -1
  424. package/src/sync/sync-selection.test.ts +43 -7
  425. package/src/sync/sync-selection.ts +56 -24
  426. package/src/sync/sync-state.test.ts +83 -0
  427. package/src/sync/sync-state.ts +58 -7
  428. package/src/sync/transport.ts +191 -0
  429. package/src/sync/ui-prefs.ts +10 -6
  430. package/src/sync/webdav/webdav-transport.test.ts +122 -2
  431. package/src/sync/webdav/webdav-transport.ts +256 -37
  432. package/src/ui/backup-schedule.test.ts +12 -2
  433. package/src/ui/backup-schedule.ts +10 -7
  434. package/src/ui/export-flow.ts +65 -18
  435. package/src/ui/i18n.test.ts +149 -0
  436. package/src/ui/i18n.ts +25 -2
  437. package/src/ui/import-log.test.ts +106 -0
  438. package/src/ui/import-log.ts +158 -0
  439. package/src/ui/import-stepper.test.ts +25 -1
  440. package/src/ui/import-stepper.ts +18 -0
  441. package/src/ui/import-wizard.test.ts +130 -2
  442. package/src/ui/import-wizard.ts +135 -2
  443. package/src/ui/market-import.test.ts +0 -1
  444. package/src/ui/mid-ellipsis.test.ts +160 -0
  445. package/src/ui/mid-ellipsis.ts +37 -0
  446. package/src/ui/my-configs-view.test.ts +222 -0
  447. package/src/ui/my-configs-view.ts +206 -0
  448. package/src/ui/progress.ts +6 -0
  449. package/src/ui/report.test.ts +32 -2
  450. package/src/ui/report.ts +52 -0
  451. package/src/ui/runs-view.test.ts +152 -0
  452. package/src/ui/runs-view.ts +207 -0
  453. package/src/ui/selection-model.test.ts +268 -7
  454. package/src/ui/selection-model.ts +277 -1
  455. package/src/ui/snapshots-view.test.ts +228 -0
  456. package/src/ui/snapshots-view.ts +233 -0
  457. package/src/ui/sync-settings-view.test.ts +348 -0
  458. package/src/ui/sync-settings-view.ts +516 -0
  459. package/src/ui/test-helpers.ts +8 -1
  460. package/src/ui/types.ts +10 -1
  461. package/src/utils/env-lock.test.ts +204 -110
  462. package/src/utils/env-lock.ts +71 -35
  463. package/src/utils/logger.test.ts +178 -1
  464. package/src/utils/logger.ts +81 -44
  465. package/src/utils/paths.ts +2 -2
  466. package/src/utils/session-log.test.ts +136 -0
  467. package/src/utils/session-log.ts +344 -0
  468. package/src/utils/zip.test.ts +284 -0
  469. package/src/utils/zip.ts +226 -52
  470. package/src/utils/zstd-frame.test.ts +80 -0
  471. package/src/utils/zstd-frame.ts +131 -0
package/README.md CHANGED
@@ -87,17 +87,21 @@ Browse the built-in official market for ready-made configurations (model provide
87
87
 
88
88
  ## 📸 Screenshots
89
89
 
90
+ | Overview | Backups & Snapshots |
91
+ |:---:|:---:|
92
+ | ![Overview](assets/screenshot-overview-en.png) | ![Backups and snapshots](assets/screenshot-backups-en.png) |
93
+
90
94
  | Export | Import Preview |
91
95
  |:---:|:---:|
92
- | ![One-click Export](assets/screenshot-export.png) | ![Import Preview](assets/screenshot-import-preview.png) |
96
+ | ![One-click Export](assets/screenshot-export-en.png) | ![Import Preview](assets/screenshot-import-en.png) |
93
97
 
94
- | Snapshot Restore | Remote Sync |
98
+ | Remote Sync | Configuration Market |
95
99
  |:---:|:---:|
96
- | ![Snapshot Restore](assets/screenshot-snapshots.png) | ![Remote Sync](assets/screenshot-sync.png) |
100
+ | ![Remote Sync](assets/screenshot-sync-en.png) | ![Configuration Market](assets/screenshot-market-en.png) |
97
101
 
98
- | Configuration Market |
102
+ | Profiles (DSH profiles) |
99
103
  |:---:|
100
- | ![Configuration Market](assets/screenshot-market.png) |
104
+ | ![Profiles](assets/screenshot-profiles-en.png) |
101
105
 
102
106
  ---
103
107
 
@@ -331,6 +335,8 @@ dsh-config-manager verify [--id <file|path>] [--json] # read-only check
331
335
  [--data-dir <dir>]
332
336
  dsh-config-manager backup [--sections <a,b,c>] [--out <path>] # offline file-level backup
333
337
  [--dry-run] [--data-dir <dir>]
338
+ dsh-config-manager sessions repair [--home <dir>] [--fix] # offline session layout repair
339
+ [--keep <dir>] [--map old=new]...
334
340
  ```
335
341
 
336
342
  **`reinstall` — rescue when DSH is broken.** It reinstalls the `@deepseek-ai/dsh` launcher across platforms (uses the right command per OS: PowerShell on Windows, bash on Unix). By default it reinstalls the launcher + clears global caches; interactively it asks which **dangerous** clean-up items to include (settings / plugins / session data & credentials) — those are **not** selected by default, and any destructive choice requires a second confirmation by typing `YES` before anything runs. Before wiping any `~/.dsh` data it makes an emergency backup at `.reinstall-backup` (the `snapshots/` folder is deliberately never touched).
@@ -396,7 +402,7 @@ dsh-config-manager backup --sections skills,self # narrow the scope
396
402
 
397
403
  `--sections` accepts `skills,agentPresets,agentInstructions,self,pluginFiles`. `pluginFiles` is **opt-in** (as in the GUI): it copies third-party plugin files verbatim, and `dsh-ssh.json` holds plaintext host passwords — select it only when you have looked at what is in there.
398
404
 
399
- **A typical rescue flow** when DSH won't start: ① `dsh-config-manager reinstall` to bring the launcher back (plus any clean-up), ② `dsh web` to start DSH again, ③ re-add the plugin from the registry, and ④ pull a snapshot from the remote repo (or run `dsh-config-manager restore`) to bring your config back. The CLI works at every step regardless of DSH's health.
405
+ **A typical rescue flow** when DSH won't start: ① `dsh-config-manager reinstall` to bring the launcher back (plus any clean-up), ② if DSH reports a session-log error (`corrupt session log` / `duplicate JSONL session id`), run `dsh-config-manager sessions repair --fix` first — it repairs the log layout offline, ③ `dsh web` to start DSH again, ③ re-add the plugin from the registry, and ④ pull a snapshot from the remote repo (or run `dsh-config-manager restore`) to bring your config back. The CLI works at every step regardless of DSH's health.
400
406
 
401
407
  **`recover-stale-lock` — when every operation suddenly fails.** Before touching your config, the plugin claims a small environment lock (it records who is operating plus a heartbeat) so that two operations can never write your config at the same time. If a `dsh web` process is **force-killed** (Task Manager, `kill -9`), the lock file survives with a dead owner: the next operation is refused, and it stays refused **no matter how often you retry or restart DSH** — because the plugin deliberately never removes a lock on its own (a wrong guess could evict a live operation).
402
408
 
@@ -413,6 +419,25 @@ Symptoms and the fix:
413
419
  dsh-config-manager recover-stale-lock
414
420
  ```
415
421
 
422
+ **`sessions repair` — when DSH refuses to start over a session log.** DSH validates that each session log sits exactly where its own header says it belongs: `corrupt session log … header id and cwd identify …`, or `duplicate JSONL session id … in multiple project directories`. The plugin cannot help at that point (it only loads *inside* DSH), so this is the one repair path that works while DSH is down. It reads every session's first-frame cwd and moves the session directory under `projectKeyOf(cwd)` — the location DSH expects, derived from the log itself, never guessed.
423
+
424
+ - **Dry run by default** (zero writes); `--fix` performs the moves. Exit code: dry runs always 0, `--fix` returns 1 if anything failed, conflicted or rolled back.
425
+ - **`--map old=new`** (repeatable) is for cross-machine restores: a matching prefix rewrites the first-frame cwd before relocating the directory (every other frame is copied byte for byte). Targets that already exist are never overwritten.
426
+ - **`--keep <dir>`** resolves duplicate ids: the copy you name is kept, the others are moved into `sessions/.cm-repair-quarantine-<timestamp>/` — moved, never deleted. Without `--keep` duplicates are reported only.
427
+
428
+ ```bash
429
+ # see what would move (no writes at all)
430
+ dsh-config-manager sessions repair
431
+ # apply, mapping a source-machine prefix onto this machine
432
+ dsh-config-manager sessions repair --fix --map 'C:/Users/alice=D:/Work'
433
+ ```
434
+
435
+ If the two machines use **different DSH base paths** (e.g. `/opt/dsh/.dsh` vs a Windows drive path), the backup records the source base path and the import **rebases automatically** every path that lives under it (session cwd, workspace path, …) onto your local one — no mapping to type. User mappings still apply afterwards, so you can override anything.
436
+
437
+ In the content picker, selecting sessions also selects the workspaces that own them (and unchecking a workspace unchecks its sessions).
438
+
439
+ Cross-machine restores need no extra step in the GUI: **exporting sessions now carries the workspaces that own them**, and the path mapping you fill in the import wizard rewrites both the workspace paths and the sessions' first-frame cwd (relocating the directories accordingly) before the sessions are attached to those workspaces.
440
+
416
441
  **Plugins installed but the backup doesn't see them?** Check **Settings → DSH Config Manager → About**: it now shows which directory / profile the plugin list was read from, and how many plugins were detected. The list comes from `$DSH_HOME/profiles/<profile>/package.json` → `dependencies` (plus anything declared in `dsh.profile.bundles` that is not a dependency), where `<profile>` is resolved as `config.profile` → `--profile` → `web`. If the shown path is not the profile you installed into (Desktop builds may use a different profile or a different `DSH_HOME`), that is the cause — align `--profile` / `DSH_HOME` with it.
417
442
 
418
443
  ### 🌐 Behind a proxy? (GitHub login / sync)
@@ -515,6 +540,20 @@ Yes. The import wizard asks for the export-time encryption password and verifies
515
540
  5. **Encrypted backups**: a lost password means the `secrets.enc` can't be decrypted (by design — keep your password safe)
516
541
  6. **Snapshot restore is offline and honest**: entries the offline engine can't restore (settings namespaces / patch lines when the snapshot has no whole-file backup, workspace records stored in DSH storages) are reported as skipped with a pointer to online rollback; credential **values** are never auto-written (manual re-entry hint only); old snapshots without a plugin baseline only get a hint to remove added plugins manually
517
542
 
543
+ ## 🙏 Contributors
544
+
545
+ - **lux-liang (Jialiang Liang)** — [PR #44](https://github.com/xiajiajun516/dsh-config-manager/pull/44): independently fixed the
546
+ issue #43 "Back up now" false-success bug. Two details from that patch were more robust than the mainline implementation and
547
+ have been adopted into `main`: (1) when `failed` comes back with an empty / whitespace-only error text, fall back to the
548
+ generic message instead of rendering a dangling "Backup failed:"; (2) an unknown `skipReason` is mapped to a localized
549
+ message rather than echoing the raw machine token. In addition, so that GitHub's contributor list records the
550
+ contribution, commit `1248200` was landed on `main` through a **"keep the commit, take the mainline tree"** merge
551
+ (`122317b`) — that merge takes **none** of its code (the resulting tree is byte-identical to the mainline) and exists purely
552
+ to record authorship, so GitHub now counts this contribution instead of leaving it invisible behind a closed PR.
553
+ - **Bug reports**: `zhyx1996` (#38 sync "Export secrets" did nothing), `IPF-Sinon` (#39 the `.credentials.yaml` `refs:` block
554
+ was not recognized — they also implemented and machine-tested a fix in their own fork), `zerginlaw` (#43 "Back up now"
555
+ silently no-oped while toasting success). Each report drove one fix.
556
+
518
557
  > Maintainers & developers: see [DEVELOPERS.md](DEVELOPERS.md) for build, testing, auto-publishing and full technical notes.
519
558
 
520
559
  ---
package/README.zh-CN.md CHANGED
@@ -90,17 +90,21 @@ DSH 是你的 AI 助手工作台,里面存着你的各种设置:模型配置
90
90
 
91
91
  ## 📸 功能截图
92
92
 
93
+ | 总览 | 备份与快照 |
94
+ |:---:|:---:|
95
+ | ![总览](assets/screenshot-overview-zh.png) | ![备份与快照](assets/screenshot-backups-zh.png) |
96
+
93
97
  | 导出备份 | 导入预览 |
94
98
  |:---:|:---:|
95
- | ![一键导出](assets/screenshot-export.png) | ![导入预览](assets/screenshot-import-preview.png) |
99
+ | ![一键导出](assets/screenshot-export-zh.png) | ![导入预览](assets/screenshot-import-zh.png) |
96
100
 
97
- | 快照恢复 | 远程同步 |
101
+ | 远程同步 | 配置市场 |
98
102
  |:---:|:---:|
99
- | ![快照恢复](assets/screenshot-snapshots.png) | ![远程同步](assets/screenshot-sync.png) |
103
+ | ![远程同步](assets/screenshot-sync-zh.png) | ![配置市场](assets/screenshot-market-zh.png) |
100
104
 
101
- | 配置市场 |
105
+ | 档案 Profiles |
102
106
  |:---:|
103
- | ![配置市场](assets/screenshot-market.png) |
107
+ | ![档案 Profiles](assets/screenshot-profiles-zh.png) |
104
108
 
105
109
  ---
106
110
 
@@ -454,6 +458,17 @@ dsh-config-manager backup --sections skills,self # 收窄范围
454
458
  6. **快照恢复是离线的、诚实的**:离线引擎无法恢复的条目(快照无整文件备份时的 settings namespace / patch 行、存在 DSH storages 里的 workspace 记录)会如实列为跳过并指向在线回滚;凭据**值**绝不自动改写(只提示人工补录);无插件基线的旧快照只提示人工核对新增插件
455
459
  7. **本地源插件(`link:` / `file:`)随备份打包**:导出时执行 `npm pack` 把本地开发中的插件打成 tarball 一并备份,导入时解包到 `$DSH_HOME/dsh-config-manager/local-plugins/` 后按 `file:` 安装。因此:① 备份体积会随本地插件的体积增大(单插件超过 100 MB 会被跳过并告警,建议先发布到 registry / git 再备份);② 插件**源码**会进入备份(与「密钥永不进备份」不冲突——密钥仍被排除,这里进的是代码);③ 打包需要本机有可用的 `npm`,无 npm 时该插件退化为原行为(保留原 spec,换机后仍需手工安装)
456
460
 
461
+ ## 🙏 贡献者
462
+
463
+ - **lux-liang (Jialiang Liang)** —— [PR #44](https://github.com/xiajiajun516/dsh-config-manager/pull/44):独立修复了 issue #43
464
+ 「立即备份」假报成功的问题。其中两点比主线实现更稳,已采纳进 `main`:① `failed` 且宿主错误文本为空 / 全空白时
465
+ 回退通用文案(否则会渲染出「备份失败:」这种半截提示);② 未知 `skipReason` 归一为本地化说明,不把机器 token
466
+ 摆到用户面前。此外,为了让 GitHub 的 Contributors 列表如实计入这次贡献,其提交 `1248200` 已通过一次
467
+ **「保留其提交、树取主线」**的合并(`122317b`)成为 `main` 的祖先 —— 该合并**不取其代码**(合并后的树与主线
468
+ 逐字节一致),只用于记录署名;因此按 GitHub 的口径这次贡献已被计入(而非 closed 后无痕)。
469
+ - **问题报告**:`zhyx1996`(#38 同步「导出密钥」未生效)、`IPF-Sinon`(#39 凭据 `refs:` 块未被识别,并在自己的 fork
470
+ 上完成实现与真机验证)、`zerginlaw`(#43「立即备份」空转却提示成功)—— 这三条报告各自驱动了一个修复。
471
+
457
472
  > 维护者与开发者:构建、测试、自动发布与完整技术说明见 [DEVELOPERS.md](DEVELOPERS.md)。
458
473
 
459
474
  ---
@@ -3,9 +3,9 @@ import type { FilesSection } from '../schema/types.ts';
3
3
  import type { ExportOptions, ExportSection, HostContext } from '../core/types.ts';
4
4
  export declare class AgentInstructionsAdapter extends FileCollectionAdapter {
5
5
  readonly id: 'agentInstructions';
6
- readonly displayName = "Agent Instructions";
7
- readonly defaultIncluded = true;
8
- readonly portability: 'portable';
6
+ readonly displayName: string;
7
+ readonly defaultIncluded: boolean;
8
+ readonly portability: import("../schema/section-registry.ts").SectionPortability;
9
9
  readonly baseDir = "";
10
10
  /** 用户全局指令文件(相对 homeDir;同时是 schema 端该分区唯一的文件) */
11
11
  static readonly FILE = "AGENTS.md";
@@ -11,11 +11,13 @@
11
11
  import { sha256Hex } from '../utils/hashing.js';
12
12
  import { msgOf } from '../core/messages.js';
13
13
  import { FileCollectionAdapter } from './file-collection.js';
14
+ import { sectionMeta } from '../schema/section-registry.js';
14
15
  export class AgentInstructionsAdapter extends FileCollectionAdapter {
15
16
  id = 'agentInstructions';
16
- displayName = 'Agent Instructions';
17
- defaultIncluded = true;
18
- portability = 'portable';
17
+ // 元数据唯一来源 = 注册表(t31):不再与 ui/export-flow.ts 的导出目录各写一份
18
+ displayName = sectionMeta('agentInstructions').displayName;
19
+ defaultIncluded = sectionMeta('agentInstructions').defaultIncluded;
20
+ portability = sectionMeta('agentInstructions').portability;
19
21
  baseDir = '';
20
22
  /** 用户全局指令文件(相对 homeDir;同时是 schema 端该分区唯一的文件) */
21
23
  static FILE = 'AGENTS.md';
@@ -6,8 +6,8 @@
6
6
  import { FileCollectionAdapter } from './file-collection.ts';
7
7
  export declare class AgentPresetsAdapter extends FileCollectionAdapter {
8
8
  readonly id: 'agentPresets';
9
- readonly displayName = "Agent Presets";
10
- readonly defaultIncluded = true;
11
- readonly portability: 'portable';
9
+ readonly displayName: string;
10
+ readonly defaultIncluded: boolean;
11
+ readonly portability: import("../schema/section-registry.ts").SectionPortability;
12
12
  readonly baseDir = ".agent-presets";
13
13
  }
@@ -4,11 +4,13 @@
4
4
  * system 预设(安装目录)只记引用不复制(本 adapter 只读用户目录)。
5
5
  */
6
6
  import { FileCollectionAdapter } from './file-collection.js';
7
+ import { sectionMeta } from '../schema/section-registry.js';
7
8
  export class AgentPresetsAdapter extends FileCollectionAdapter {
8
9
  id = 'agentPresets';
9
- displayName = 'Agent Presets';
10
- defaultIncluded = true;
11
- portability = 'portable';
10
+ // 元数据唯一来源 = 注册表(t31):不再与 ui/export-flow.ts 的导出目录各写一份
11
+ displayName = sectionMeta('agentPresets').displayName;
12
+ defaultIncluded = sectionMeta('agentPresets').defaultIncluded;
13
+ portability = sectionMeta('agentPresets').portability;
12
14
  baseDir = '.agent-presets';
13
15
  }
14
16
  //# sourceMappingURL=agent-presets.js.map
@@ -25,9 +25,9 @@ export interface CredentialsAdapterOptions {
25
25
  }
26
26
  export declare class CredentialsAdapter implements ConfigAdapter<CredentialsSection> {
27
27
  readonly id: 'credentialsStatus';
28
- readonly displayName = "Credentials";
29
- readonly defaultIncluded = true;
30
- readonly portability: 'deviceSpecific';
28
+ readonly displayName: string;
29
+ readonly defaultIncluded: boolean;
30
+ readonly portability: import("../schema/section-registry.ts").SectionPortability;
31
31
  private readonly refs;
32
32
  constructor(options?: CredentialsAdapterOptions);
33
33
  export(ctx: HostContext, _options: ExportOptions): Promise<ExportSection<CredentialsSection>>;
@@ -1,5 +1,7 @@
1
1
  import { msgOf, zhMsg } from '../core/messages.js';
2
2
  import { resolveNamespaces } from './settings.js';
3
+ import { sectionMeta } from '../schema/section-registry.js';
4
+ import { validateJsonSection } from './json-section.js';
3
5
  /** 缺省 ref 收集:遍历 settings namespace,收集 llm apiKeyEnv / providers[].apiKeyEnv,
4
6
  * 以及 secrets 标记中「引用类字段」(apiKeyEnv/tokenEnv…)的字段值。
5
7
  * 注意 secrets[].path[0] 是 settings 文档内的字段路径(如 ['apiKey']),本身不是凭据 ref(设计 §4.2),
@@ -47,9 +49,10 @@ export function defaultCredentialRefs(namespaces) {
47
49
  }
48
50
  export class CredentialsAdapter {
49
51
  id = 'credentialsStatus';
50
- displayName = 'Credentials';
51
- defaultIncluded = true;
52
- portability = 'deviceSpecific';
52
+ // 元数据唯一来源 = 注册表(t31):不再与 ui/export-flow.ts 的导出目录各写一份
53
+ displayName = sectionMeta('credentialsStatus').displayName;
54
+ defaultIncluded = sectionMeta('credentialsStatus').defaultIncluded;
55
+ portability = sectionMeta('credentialsStatus').portability;
53
56
  refs;
54
57
  constructor(options = {}) {
55
58
  this.refs = options.refs ?? defaultCredentialRefs(options.namespaces ?? []);
@@ -95,28 +98,22 @@ export class CredentialsAdapter {
95
98
  return { ok: true };
96
99
  }
97
100
  async validate(data, msg = zhMsg) {
98
- const issues = [];
99
- if (data === null || typeof data !== 'object') {
100
- return { valid: false, issues: [{ path: '$', message: msg('adapter.validate.object', { subject: 'credentials' }), severity: 'error' }] };
101
- }
102
- if (data.version !== 1) {
103
- issues.push({ path: 'version', message: msg('adapter.validate.version', { value: String(data.version) }), severity: 'error' });
104
- }
105
- if (!Array.isArray(data.credentials)) {
106
- issues.push({ path: 'credentials', message: msg('adapter.validate.array', { subject: 'credentials' }), severity: 'error' });
107
- }
108
- else {
109
- for (const c of data.credentials) {
110
- if (c === null || typeof c !== 'object' || typeof c.ref !== 'string' || c.ref === '') {
111
- issues.push({ path: 'credentials[]', message: msg('adapter.validate.credentialRef'), severity: 'error' });
112
- }
113
- if (c.hasValue === true) {
114
- // 安全不变量:普通导出恒不携带值;若备份声称有值,视为结构异常
115
- issues.push({ path: `credentials.${c.ref}.hasValue`, message: msg('adapter.validate.hasValueFalse'), severity: 'error' });
101
+ return validateJsonSection('credentialsStatus', data, msg, (section, issues) => {
102
+ if (!Array.isArray(section.credentials)) {
103
+ issues.push({ path: 'credentials', message: msg('adapter.validate.array', { subject: 'credentials' }), severity: 'error' });
104
+ }
105
+ else {
106
+ for (const c of section.credentials) {
107
+ if (c === null || typeof c !== 'object' || typeof c.ref !== 'string' || c.ref === '') {
108
+ issues.push({ path: 'credentials[]', message: msg('adapter.validate.credentialRef'), severity: 'error' });
109
+ }
110
+ if (c.hasValue === true) {
111
+ // 安全不变量:普通导出恒不携带值;若备份声称有值,视为结构异常
112
+ issues.push({ path: `credentials.${c.ref}.hasValue`, message: msg('adapter.validate.hasValueFalse'), severity: 'error' });
113
+ }
116
114
  }
117
115
  }
118
- }
119
- return { valid: issues.filter((i) => i.severity === 'error').length === 0, issues };
116
+ }, 'credentials');
120
117
  }
121
118
  }
122
119
  //# sourceMappingURL=credentials.js.map
@@ -1,7 +1,29 @@
1
1
  import type { MsgFunc } from '../core/messages.ts';
2
2
  import type { FilesSection, SectionId } from '../schema/types.ts';
3
3
  import type { ApplyResult, ConfigAdapter, ExportOptions, ExportSection, ExportUnit, HostContext, ImportContext, PlanItem, Portability, ValidationResult } from '../core/types.ts';
4
+ /**
5
+ * 文件类分区导出的**单分区累计字节闸门**:分区内文件内容合计放不下的单元整块剔除(见 export())。
6
+ *
7
+ * 取值依据(不是随手拍的数):
8
+ * 1) **对称性** —— 导入侧对上传的备份包有硬上限 256 MiB(MAX_UPLOAD_BYTES,src/index.ts 与
9
+ * src/routes/kit.ts 的上传路径);导出侧此前**没有任何体积约束**,于是会出现「导出成功、
10
+ * 传不回来」(审计 core-flow F-12 / sync#F-12)。取同一个 256 MiB 作**单分区**上限:
11
+ * 任何单个分区都落在导入侧上传路径能收的范围内。
12
+ * 2) **内存峰值** —— 导出时文件内容以完整 Uint8Array 驻留内存(ZIP 组装前全量在内存里),同步通道
13
+ * 还要再写一份。闸门把「单分区常驻」钳在 256 MiB 以内(峰值 ≈ 上限 + 正在读的那个单元),
14
+ * 不再是「会话附件多大就多吃多少」。
15
+ * 3) **与既有同类上限同量级** —— MAX_MARKET_ZIP_BYTES = 64 MiB、DEFAULT_MAX_JSON_BYTES = 64 MiB、
16
+ * MAX_LOCAL_TARBALL_BYTES = 100 MiB、MAX_PATCH_FILE_BYTES = 2 MiB。文件类分区装的是**用户真实
17
+ * 文件**,所以取「导入侧能收」的 256 MiB,而不是为省内存随意调小(那会平白少带用户数据)。
18
+ * 4) **为什么按分区而不是按整包** —— 整包体积由用户勾选跨分区决定;闸门要能给出「哪些分区、哪些
19
+ * 单元没进去」的可执行信息,落在分区/单元层才可解释。
20
+ *
21
+ * 超限时**绝不静默**:被整块剔除的单元清单进 ExportSection.warnings(导出报告可见)。
22
+ */
23
+ export declare const MAX_FILE_SECTION_BYTES: number;
4
24
  export declare abstract class FileCollectionAdapter implements ConfigAdapter<FilesSection> {
25
+ /** 文件集合:`relativePath` 是身份而非配置 → 永不被导入时的前缀映射改写(见 ConfigAdapter.fileCollection) */
26
+ readonly fileCollection = true;
5
27
  abstract readonly id: SectionId;
6
28
  abstract readonly displayName: string;
7
29
  abstract readonly defaultIncluded: boolean;
@@ -18,6 +40,14 @@ export declare abstract class FileCollectionAdapter implements ConfigAdapter<Fil
18
40
  protected unitIdOf(relativePath: string): string;
19
41
  /** 清单里的路径 → 相对 baseDir 的路径(baseDir 为空表示整目录即根)。 */
20
42
  protected relPathOf(rel: string): string;
43
+ /**
44
+ * 单分区累计字节上限(缺省 MAX_FILE_SECTION_BYTES = 256 MiB,见该常量注释)。
45
+ *
46
+ * 生产代码不覆写;留成受保护方法只为让测试能构造「超限」场景(真实阈值 256 MiB 无法在单测里
47
+ * 造出来而不吃掉 256 MiB 内存)。若要给用户配置入口,应走 ExportOptions(src/core/types.ts),
48
+ * 而不是在子类里飘一个隐式默认值。
49
+ */
50
+ protected sectionByteLimit(): number;
21
51
  /**
22
52
  * 条目级选择(Phase 1):只导出白名单命中的单元。
23
53
  *
@@ -40,5 +70,18 @@ export declare abstract class FileCollectionAdapter implements ConfigAdapter<Fil
40
70
  listUnits(section: ExportSection<FilesSection>): ExportUnit[];
41
71
  analyzeImport(data: FilesSection, ctx: ImportContext): Promise<PlanItem[]>;
42
72
  applyItem(item: PlanItem, ctx: ImportContext): Promise<ApplyResult>;
73
+ /**
74
+ * 结构校验(5 个文件类分区共用:skills / agentPresets / agentInstructions / sessions / self)。
75
+ *
76
+ * **整段样板都接入共享骨架 `validateJsonSection`** —— t7 接入 version 守卫与形状检查收尾,t11 接入
77
+ * object 守卫;dataVersion 由注册表按 `this.id` 派生,等价于此前的字面量 `!== 1`
78
+ * (SECTION_DATA_VERSION = 1)。本类里已无手写样板。
79
+ *
80
+ * object 守卫的**文案**为什么还要参数:文件类分区历史上用的是**专用消息键**
81
+ * `adapter.validate.fileSection`('文件分区数据必须是对象',不含 `{subject}` 占位符),与骨架缺省的
82
+ * `adapter.validate.object` + `{subject}` 是**键**级差异 —— 第 5 参 subject 补不了它。t11 给骨架加了
83
+ * 可选第 6 参 `objectMessageKey`,这里传专用键:5 个分区的用户可见报错文案与改造前**逐字相同**,
84
+ * 同时不放任何手写样板。等价由 src/adapters/file-collection.test.ts 与 outputs/t7-equiv/ 的差分脚本钉住。
85
+ */
43
86
  validate(data: FilesSection, msg?: MsgFunc): Promise<ValidationResult>;
44
87
  }
@@ -6,6 +6,10 @@
6
6
  *
7
7
  * 文件内容以真实文件进入 ZIP(custom/skills/… 等前缀由 exporter 按 SECTION_FILE_PREFIXES 处理)。
8
8
  * 幂等:相对路径 + 内容 SHA-256 hash 比对(重复导入 → Skip)。
9
+ *
10
+ * 导出的**单分区字节闸门**(审计 core-flow F-12 / sync#F-12):累计到 MAX_FILE_SECTION_BYTES 后,
11
+ * 放不下的单元**整块**剔除并写进告警(见 export())。覆盖 skills / agentPresets / sessions;
12
+ * agentInstructions(单文件 AGENTS.md)与 self(白名单配置小文件)各自覆写 export,不在本闸门射程内。
9
13
  */
10
14
  import path from 'node:path';
11
15
  import { sha256Hex } from '../utils/hashing.js';
@@ -13,7 +17,40 @@ import { isReservedInternalRel, normalizePath } from '../utils/paths.js';
13
17
  import { msgOf, zhMsg } from '../core/messages.js';
14
18
  import { linkWarnings, listFilesDetailed } from './link-report.js';
15
19
  import { defaultUnitId, unitAllowed, unitsFromFiles } from './units.js';
20
+ import { validateJsonSection } from './json-section.js';
21
+ /**
22
+ * 文件类分区导出的**单分区累计字节闸门**:分区内文件内容合计放不下的单元整块剔除(见 export())。
23
+ *
24
+ * 取值依据(不是随手拍的数):
25
+ * 1) **对称性** —— 导入侧对上传的备份包有硬上限 256 MiB(MAX_UPLOAD_BYTES,src/index.ts 与
26
+ * src/routes/kit.ts 的上传路径);导出侧此前**没有任何体积约束**,于是会出现「导出成功、
27
+ * 传不回来」(审计 core-flow F-12 / sync#F-12)。取同一个 256 MiB 作**单分区**上限:
28
+ * 任何单个分区都落在导入侧上传路径能收的范围内。
29
+ * 2) **内存峰值** —— 导出时文件内容以完整 Uint8Array 驻留内存(ZIP 组装前全量在内存里),同步通道
30
+ * 还要再写一份。闸门把「单分区常驻」钳在 256 MiB 以内(峰值 ≈ 上限 + 正在读的那个单元),
31
+ * 不再是「会话附件多大就多吃多少」。
32
+ * 3) **与既有同类上限同量级** —— MAX_MARKET_ZIP_BYTES = 64 MiB、DEFAULT_MAX_JSON_BYTES = 64 MiB、
33
+ * MAX_LOCAL_TARBALL_BYTES = 100 MiB、MAX_PATCH_FILE_BYTES = 2 MiB。文件类分区装的是**用户真实
34
+ * 文件**,所以取「导入侧能收」的 256 MiB,而不是为省内存随意调小(那会平白少带用户数据)。
35
+ * 4) **为什么按分区而不是按整包** —— 整包体积由用户勾选跨分区决定;闸门要能给出「哪些分区、哪些
36
+ * 单元没进去」的可执行信息,落在分区/单元层才可解释。
37
+ *
38
+ * 超限时**绝不静默**:被整块剔除的单元清单进 ExportSection.warnings(导出报告可见)。
39
+ */
40
+ export const MAX_FILE_SECTION_BYTES = 256 * 1024 * 1024;
41
+ /** 字节闸门告警里最多列出的单元数(其余折叠成 "(+ N)",避免大分区把报告撑爆;与 link-report 同口径) */
42
+ const MAX_UNITS_PER_GATE_WARNING = 5;
43
+ /** 告警文案里的体积(人类可读;纯数值格式化,不含用户可见散文) */
44
+ function formatBytes(bytes) {
45
+ if (bytes < 1024)
46
+ return `${bytes} B`;
47
+ if (bytes < 1024 * 1024)
48
+ return `${(bytes / 1024).toFixed(1)} KiB`;
49
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MiB`;
50
+ }
16
51
  export class FileCollectionAdapter {
52
+ /** 文件集合:`relativePath` 是身份而非配置 → 永不被导入时的前缀映射改写(见 ConfigAdapter.fileCollection) */
53
+ fileCollection = true;
17
54
  /**
18
55
  * 最小可拆单元的 id(相对 baseDir 的路径)。
19
56
  *
@@ -31,6 +68,16 @@ export class FileCollectionAdapter {
31
68
  const prefix = this.baseDir.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
32
69
  return rel.replace(new RegExp('^' + prefix + '[\\\\/]'), '');
33
70
  }
71
+ /**
72
+ * 单分区累计字节上限(缺省 MAX_FILE_SECTION_BYTES = 256 MiB,见该常量注释)。
73
+ *
74
+ * 生产代码不覆写;留成受保护方法只为让测试能构造「超限」场景(真实阈值 256 MiB 无法在单测里
75
+ * 造出来而不吃掉 256 MiB 内存)。若要给用户配置入口,应走 ExportOptions(src/core/types.ts),
76
+ * 而不是在子类里飘一个隐式默认值。
77
+ */
78
+ sectionByteLimit() {
79
+ return MAX_FILE_SECTION_BYTES;
80
+ }
34
81
  /**
35
82
  * 条目级选择(Phase 1):只导出白名单命中的单元。
36
83
  *
@@ -66,6 +113,12 @@ export class FileCollectionAdapter {
66
113
  if (restricted !== null)
67
114
  warnings.push(...restricted.warnings);
68
115
  const keep = restricted?.keep ?? null;
116
+ // ① 先按「单元」分组(零 I/O:只看路径)。单元的成员文件**不保证**在遍历结果里连续,
117
+ // 必须整组一起判定,否则同一单元会被拦腰劈开(半个技能目录 / 半个会话目录)。
118
+ // `considered` 保序记录,收尾按它还原改造前的文件顺序(未触发闸门时逐项一致)。
119
+ const considered = [];
120
+ const unitOrder = [];
121
+ const unitEntries = new Map();
69
122
  for (const rel of rels) {
70
123
  const relPath = this.relPathOf(rel);
71
124
  const unitId = this.unitIdOf(relPath);
@@ -73,8 +126,59 @@ export class FileCollectionAdapter {
73
126
  continue;
74
127
  if (!unitAllowed(allow, `${this.id}:${unitId}`))
75
128
  continue;
76
- const data = await ctx.fs.readFile(rel);
77
- files.push({ relativePath: relPath, data, contentHash: sha256Hex(data) });
129
+ let members = unitEntries.get(unitId);
130
+ if (members === undefined) {
131
+ members = [];
132
+ unitEntries.set(unitId, members);
133
+ unitOrder.push(unitId);
134
+ }
135
+ members.push({ rel, relPath });
136
+ considered.push(rel);
137
+ }
138
+ // ② 逐单元读取 + 累计字节闸门(审计 core-flow F-12 / sync#F-12)。超出 sectionByteLimit() 的
139
+ // 单元**整块**不进备份(仓库语义:单元不可拆分 —— 宁可少带,不可半带);一旦判定该单元放不下,
140
+ // 它的剩余文件不再读盘(省 I/O,也不为「统计精确」多吃内存)。放不下的单元跳过后**继续尝试
141
+ // 后面的单元**:一个巨型会话不该把它之后的所有内容一起带走(后面的小单元仍可能入选)。
142
+ const limit = this.sectionByteLimit();
143
+ let usedBytes = 0;
144
+ const droppedUnits = [];
145
+ const keptByRel = new Map();
146
+ for (const unitId of unitOrder) {
147
+ const room = limit - usedBytes;
148
+ const pending = [];
149
+ let unitBytes = 0;
150
+ for (const member of unitEntries.get(unitId) ?? []) {
151
+ const data = await ctx.fs.readFile(member.rel);
152
+ unitBytes += data.byteLength;
153
+ if (unitBytes > room)
154
+ break;
155
+ pending.push({ rel: member.rel, file: { relativePath: member.relPath, data, contentHash: sha256Hex(data) } });
156
+ }
157
+ if (unitBytes > room) {
158
+ droppedUnits.push(unitId);
159
+ continue;
160
+ }
161
+ usedBytes += unitBytes;
162
+ for (const p of pending)
163
+ keptByRel.set(p.rel, p.file);
164
+ }
165
+ // ③ 按 ① 的顺序落盘(未触发闸门时与改造前的文件顺序逐项一致)
166
+ for (const rel of considered) {
167
+ const file = keptByRel.get(rel);
168
+ if (file !== undefined)
169
+ files.push(file);
170
+ }
171
+ // 绝不静默:被整块剔除的单元写进告警(导出报告可见),并给出上限与实际保留量
172
+ if (droppedUnits.length > 0) {
173
+ const shown = droppedUnits.slice(0, MAX_UNITS_PER_GATE_WARNING);
174
+ const rest = droppedUnits.length - shown.length;
175
+ warnings.push(msgOf(ctx)('adapter.fileSectionByteLimit', {
176
+ type: this.displayName,
177
+ limit: formatBytes(limit),
178
+ kept: formatBytes(usedBytes),
179
+ count: String(droppedUnits.length),
180
+ detail: (rest > 0 ? [...shown, `(+ ${rest})`] : shown).join(', '),
181
+ }));
78
182
  }
79
183
  if (rels.length === 0)
80
184
  warnings.push(msgOf(ctx)('adapter.dirEmpty', { type: this.displayName }));
@@ -152,25 +256,33 @@ export class FileCollectionAdapter {
152
256
  await ctx.target.fs.writeFile(path.join(this.baseDir, ref), file.data);
153
257
  return { ok: true };
154
258
  }
259
+ /**
260
+ * 结构校验(5 个文件类分区共用:skills / agentPresets / agentInstructions / sessions / self)。
261
+ *
262
+ * **整段样板都接入共享骨架 `validateJsonSection`** —— t7 接入 version 守卫与形状检查收尾,t11 接入
263
+ * object 守卫;dataVersion 由注册表按 `this.id` 派生,等价于此前的字面量 `!== 1`
264
+ * (SECTION_DATA_VERSION = 1)。本类里已无手写样板。
265
+ *
266
+ * object 守卫的**文案**为什么还要参数:文件类分区历史上用的是**专用消息键**
267
+ * `adapter.validate.fileSection`('文件分区数据必须是对象',不含 `{subject}` 占位符),与骨架缺省的
268
+ * `adapter.validate.object` + `{subject}` 是**键**级差异 —— 第 5 参 subject 补不了它。t11 给骨架加了
269
+ * 可选第 6 参 `objectMessageKey`,这里传专用键:5 个分区的用户可见报错文案与改造前**逐字相同**,
270
+ * 同时不放任何手写样板。等价由 src/adapters/file-collection.test.ts 与 outputs/t7-equiv/ 的差分脚本钉住。
271
+ */
155
272
  async validate(data, msg = zhMsg) {
156
- const issues = [];
157
- if (data === null || typeof data !== 'object') {
158
- return { valid: false, issues: [{ path: '$', message: msg('adapter.validate.fileSection'), severity: 'error' }] };
159
- }
160
- if (data.version !== 1) {
161
- issues.push({ path: 'version', message: msg('adapter.validate.version', { value: String(data.version) }), severity: 'error' });
162
- }
163
- if (!Array.isArray(data.files)) {
164
- issues.push({ path: 'files', message: msg('adapter.validate.array', { subject: 'files' }), severity: 'error' });
165
- }
166
- else {
167
- for (const f of data.files) {
168
- if (f === null || typeof f !== 'object' || typeof f.relativePath !== 'string' || f.relativePath === '') {
169
- issues.push({ path: 'files[]', message: msg('adapter.validate.fileRelativePath'), severity: 'error' });
273
+ // 第 5 参 subject 传 this.id 即骨架缺省语义(专用键不含 {subject},取值不影响输出)
274
+ return validateJsonSection(this.id, data, msg, (section, issues) => {
275
+ if (!Array.isArray(section.files)) {
276
+ issues.push({ path: 'files', message: msg('adapter.validate.array', { subject: 'files' }), severity: 'error' });
277
+ }
278
+ else {
279
+ for (const f of section.files) {
280
+ if (f === null || typeof f !== 'object' || typeof f.relativePath !== 'string' || f.relativePath === '') {
281
+ issues.push({ path: 'files[]', message: msg('adapter.validate.fileRelativePath'), severity: 'error' });
282
+ }
170
283
  }
171
284
  }
172
- }
173
- return { valid: issues.filter((i) => i.severity === 'error').length === 0, issues };
285
+ }, this.id, 'adapter.validate.fileSection');
174
286
  }
175
287
  }
176
288
  //# sourceMappingURL=file-collection.js.map
@@ -55,3 +55,4 @@ export { PluginFilesAdapter, DEFAULT_PLUGIN_FILE_WHITELIST } from './plugin-file
55
55
  export { SessionsAdapter } from './sessions.ts';
56
56
  export { SelfAdapter, SELF_CONFIG_FILES } from './self.ts';
57
57
  export { FileCollectionAdapter } from './file-collection.ts';
58
+ export { validateJsonSection, type SectionIssues } from './json-section.ts';
@@ -52,4 +52,15 @@ export { PluginFilesAdapter, DEFAULT_PLUGIN_FILE_WHITELIST } from './plugin-file
52
52
  export { SessionsAdapter } from './sessions.js';
53
53
  export { SelfAdapter, SELF_CONFIG_FILES } from './self.js';
54
54
  export { FileCollectionAdapter } from './file-collection.js';
55
+ /* JSON 分区共享校验骨架(本导出是其接入点)。收敛轨迹:
56
+ * · t31 抽出骨架,settings / ui / providers / plugins / mcp / prompts / credentials /
57
+ * pluginFiles 八个分区的样板并入;
58
+ * · t7 接上 workspaces.ts 与 file-collection.ts 基类的 version 守卫 + 形状检查收尾;
59
+ * · t11 给骨架加了可选第 6 参 `objectMessageKey`(object 错误的文案键,缺省
60
+ * `adapter.validate.object`),file-collection 的 object 守卫也并入并沿用文件类分区原专用键
61
+ * `adapter.validate.fileSection`,故 5 个文件类分区的用户可见报错文案逐字不变。
62
+ * 现状:**9 个分区全部走共享骨架**(settings / ui / providers / plugins / mcp / prompts /
63
+ * credentialsStatus / pluginFiles / workspaces),文件类基类(skills / agentPresets /
64
+ * agentInstructions / sessions / self)同样走它 —— src/adapters 下不再有任何手写 validate 样板。 */
65
+ export { validateJsonSection } from './json-section.js';
55
66
  //# sourceMappingURL=index.js.map
@@ -0,0 +1,32 @@
1
+ import type { SectionId } from '../schema/types.ts';
2
+ import type { MsgFunc, zh } from '../core/messages.ts';
3
+ import type { ValidationResult } from '../core/types.ts';
4
+ /** 校验问题列表(各 adapter 的形状检查直接 push 到这里,与历史 `issues` 数组同一形状)。 */
5
+ export type SectionIssues = ValidationResult['issues'];
6
+ /**
7
+ * JSON 分区通用结构校验骨架。
8
+ *
9
+ * 执行顺序(与改造前逐字一致,保证行为等价):
10
+ * 1. object 守卫:非对象 → **直接返回**单条 `objectMessageKey` 错误(缺省 `adapter.validate.object`;后续检查不再执行);
11
+ * 2. version 守卫:`version !== sectionMeta(sectionId).dataVersion` → push 一条 `version` 错误,
12
+ * **不提前返回**(继续做形状检查,与历史实现一致);
13
+ * 3. `shape` 回调:分区特有的载荷形状检查(可选)。
14
+ *
15
+ * @param sectionId 分区 id —— 同时是 object 错误的默认 subject 与 dataVersion 的查表键
16
+ * @param data 反序列化后的分区载荷(来自不可信备份 → 必须按 unknown 处理)
17
+ * @param msg 文案翻译器(adapter 的 `validate(data, msg = zhMsg)` 原样传入)
18
+ * @param shape 分区特有的形状检查(在 version 守卫之后调用)
19
+ * @param subject object 错误里的显示名;缺省 = sectionId。历史上 credentials 用的是
20
+ * `'credentials'`(而非分区 id `credentialsStatus`),此参数为保留该原文案而存在 ——
21
+ * 改文案属独立的行为变更,不在本任务射程内。
22
+ * @param objectMessageKey object 错误的**消息键**(t11 新增,可选);缺省 `adapter.validate.object`
23
+ * (`'{subject} 数据必须是对象'`)。为什么需要它:文件类分区历史上用的是专用键
24
+ * `adapter.validate.fileSection`(`'文件分区数据必须是对象'`,**不含** `{subject}` 占位符),
25
+ * 那是**键**级差异 —— `subject` 参数补不了它(把 subject 拼成"文件分区"后中文模板仍多一个空格),
26
+ * 而硬换键会让 skills / agentPresets / agentInstructions / sessions / self 五个分区的用户可见
27
+ * 报错文案一起变。有了这个可选键,那处也能并入骨架而文案逐字不变。
28
+ * 键必须存在于 `src/core/messages.ts` 目录(`MsgFunc` 形参是 string,编译器只能校验到键名集合)。
29
+ */
30
+ export declare function validateJsonSection<S extends {
31
+ version?: unknown;
32
+ }>(sectionId: SectionId, data: unknown, msg: MsgFunc, shape?: (section: S, issues: SectionIssues) => void, subject?: string, objectMessageKey?: keyof typeof zh): ValidationResult;