dsh-config-manager 0.1.63 → 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.
- package/README.md +35 -8
- package/README.zh-CN.md +12 -7
- package/lib/adapters/agent-instructions.d.ts +3 -3
- package/lib/adapters/agent-instructions.js +5 -3
- package/lib/adapters/agent-presets.d.ts +3 -3
- package/lib/adapters/agent-presets.js +5 -3
- package/lib/adapters/credentials.d.ts +3 -3
- package/lib/adapters/credentials.js +20 -23
- package/lib/adapters/file-collection.d.ts +43 -0
- package/lib/adapters/file-collection.js +130 -18
- package/lib/adapters/index.d.ts +1 -0
- package/lib/adapters/index.js +11 -0
- package/lib/adapters/json-section.d.ts +32 -0
- package/lib/adapters/json-section.js +61 -0
- package/lib/adapters/mcp.d.ts +3 -3
- package/lib/adapters/mcp.js +16 -19
- package/lib/adapters/plugin-files.d.ts +3 -3
- package/lib/adapters/plugin-files.js +11 -14
- package/lib/adapters/plugins.d.ts +3 -3
- package/lib/adapters/plugins.js +39 -42
- package/lib/adapters/prompts.d.ts +3 -3
- package/lib/adapters/prompts.js +16 -20
- package/lib/adapters/providers.d.ts +3 -3
- package/lib/adapters/providers.js +23 -26
- package/lib/adapters/sessions.d.ts +97 -1
- package/lib/adapters/sessions.js +455 -15
- package/lib/adapters/settings.d.ts +3 -3
- package/lib/adapters/settings.js +23 -26
- package/lib/adapters/skills.d.ts +3 -3
- package/lib/adapters/skills.js +5 -3
- package/lib/adapters/test-helpers.d.ts +8 -0
- package/lib/adapters/test-helpers.js +8 -0
- package/lib/adapters/ui.d.ts +3 -3
- package/lib/adapters/ui.js +14 -17
- package/lib/adapters/workspaces.d.ts +22 -0
- package/lib/adapters/workspaces.js +237 -16
- package/lib/cli/index.d.ts +35 -4
- package/lib/cli/index.js +147 -25
- package/lib/cli/sessions-repair.d.ts +28 -0
- package/lib/cli/sessions-repair.js +206 -0
- package/lib/client.d.ts +260 -19
- package/lib/client.js +9257 -5984
- package/lib/core/analyzer.d.ts +67 -5
- package/lib/core/analyzer.js +402 -91
- package/lib/core/backup-plan.d.ts +7 -1
- package/lib/core/backup-plan.js +15 -8
- package/lib/core/backup.d.ts +53 -2
- package/lib/core/backup.js +120 -24
- package/lib/core/boot-safety.d.ts +50 -0
- package/lib/core/boot-safety.js +211 -0
- package/lib/core/config-lifecycle.d.ts +0 -1
- package/lib/core/config-lifecycle.js +0 -2
- package/lib/core/config-snapshot.d.ts +3 -1
- package/lib/core/config-snapshot.js +25 -6
- package/lib/core/credential-status.d.ts +15 -0
- package/lib/core/credential-status.js +13 -0
- package/lib/core/exporter.d.ts +18 -0
- package/lib/core/exporter.js +115 -3
- package/lib/core/importer.d.ts +15 -2
- package/lib/core/importer.js +10 -3
- package/lib/core/index.d.ts +2 -0
- package/lib/core/index.js +3 -0
- package/lib/core/journal.d.ts +74 -5
- package/lib/core/journal.js +127 -12
- package/lib/core/messages.d.ts +54 -0
- package/lib/core/messages.js +110 -0
- package/lib/core/migration-consult.js +0 -4
- package/lib/core/model-tools.js +6 -9
- package/lib/core/path-mapping.d.ts +11 -0
- package/lib/core/path-mapping.js +28 -0
- package/lib/core/phase3-host.d.ts +79 -6
- package/lib/core/phase3-host.js +223 -73
- package/lib/core/phase4-crash-child.js +0 -1
- package/lib/core/plugin-cli.js +1 -1
- package/lib/core/reconcile.d.ts +8 -1
- package/lib/core/reconcile.js +12 -4
- package/lib/core/recovery-orchestrator.js +1 -1
- package/lib/core/restore.js +10 -4
- package/lib/core/rollback.js +1 -1
- package/lib/core/run-registry.d.ts +63 -3
- package/lib/core/run-registry.js +74 -4
- package/lib/core/session-meta.d.ts +27 -8
- package/lib/core/session-meta.js +59 -32
- package/lib/core/session-repair.d.ts +89 -0
- package/lib/core/session-repair.js +118 -0
- package/lib/core/session-select.d.ts +50 -0
- package/lib/core/session-select.js +137 -0
- package/lib/core/startup-barrier.d.ts +15 -0
- package/lib/core/startup-barrier.js +24 -2
- package/lib/core/transaction-coordinator.js +3 -3
- package/lib/core/types.d.ts +241 -0
- package/lib/core/validator.js +0 -1
- package/lib/index.d.ts +367 -6
- package/lib/index.js +909 -3278
- package/lib/market/index-parser.d.ts +2 -1
- package/lib/market/index-parser.js +27 -6
- package/lib/market/my-repo.d.ts +0 -1
- package/lib/market/my-repo.js +0 -6
- package/lib/market/prepare.d.ts +9 -0
- package/lib/market/prepare.js +5 -31
- package/lib/market/reader.js +0 -1
- package/lib/market/security.js +3 -9
- package/lib/routes/backup.d.ts +9 -0
- package/lib/routes/backup.js +99 -0
- package/lib/routes/consult.d.ts +9 -0
- package/lib/routes/consult.js +134 -0
- package/lib/routes/context.d.ts +9 -0
- package/lib/routes/context.js +2 -0
- package/lib/routes/history.d.ts +9 -0
- package/lib/routes/history.js +53 -0
- package/lib/routes/import.d.ts +9 -0
- package/lib/routes/import.js +390 -0
- package/lib/routes/index.d.ts +10 -0
- package/lib/routes/index.js +27 -0
- package/lib/routes/kit.d.ts +98 -0
- package/lib/routes/kit.js +161 -0
- package/lib/routes/market.d.ts +9 -0
- package/lib/routes/market.js +256 -0
- package/lib/routes/me.d.ts +9 -0
- package/lib/routes/me.js +170 -0
- package/lib/routes/prefs.d.ts +9 -0
- package/lib/routes/prefs.js +103 -0
- package/lib/routes/profiles.d.ts +9 -0
- package/lib/routes/profiles.js +130 -0
- package/lib/routes/recovery.d.ts +9 -0
- package/lib/routes/recovery.js +137 -0
- package/lib/routes/snapshots.d.ts +9 -0
- package/lib/routes/snapshots.js +214 -0
- package/lib/routes/sync.d.ts +9 -0
- package/lib/routes/sync.js +783 -0
- package/lib/schema/config.d.ts +10 -9
- package/lib/schema/config.js +78 -96
- package/lib/schema/index.d.ts +8 -1
- package/lib/schema/index.js +13 -1
- package/lib/schema/manifest.d.ts +2 -0
- package/lib/schema/manifest.js +1 -0
- package/lib/schema/section-registry.d.ts +189 -0
- package/lib/schema/section-registry.js +258 -0
- package/lib/schema/types.d.ts +13 -3
- package/lib/schema/versions.d.ts +11 -6
- package/lib/schema/versions.js +27 -1
- package/lib/security/secret-scanner.js +1 -1
- package/lib/security/zip-security.d.ts +8 -5
- package/lib/security/zip-security.js +20 -90
- package/lib/sync/ancestor.d.ts +11 -0
- package/lib/sync/ancestor.js +20 -0
- package/lib/sync/autosync-config.js +9 -18
- package/lib/sync/autosync-scheduler.d.ts +7 -2
- package/lib/sync/autosync-scheduler.js +35 -17
- package/lib/sync/backup-schedule-config.js +0 -1
- package/lib/sync/blob-store.d.ts +85 -0
- package/lib/sync/blob-store.js +85 -0
- package/lib/sync/git/git-transport.d.ts +28 -5
- package/lib/sync/git/git-transport.js +170 -24
- package/lib/sync/github-auth.d.ts +0 -1
- package/lib/sync/github-auth.js +0 -2
- package/lib/sync/layout.d.ts +16 -2
- package/lib/sync/layout.js +48 -7
- package/lib/sync/risk.d.ts +18 -12
- package/lib/sync/risk.js +17 -28
- package/lib/sync/session-tombstones.d.ts +39 -0
- package/lib/sync/session-tombstones.js +125 -0
- package/lib/sync/sync-config.d.ts +26 -3
- package/lib/sync/sync-config.js +40 -3
- package/lib/sync/sync-engine.d.ts +140 -12
- package/lib/sync/sync-engine.js +266 -49
- package/lib/sync/sync-history.js +0 -1
- package/lib/sync/sync-selection.d.ts +18 -0
- package/lib/sync/sync-selection.js +50 -24
- package/lib/sync/sync-state.d.ts +31 -4
- package/lib/sync/sync-state.js +38 -6
- package/lib/sync/transport.d.ts +103 -0
- package/lib/sync/transport.js +121 -0
- package/lib/sync/ui-prefs.d.ts +4 -2
- package/lib/sync/ui-prefs.js +6 -4
- package/lib/sync/webdav/webdav-transport.d.ts +36 -4
- package/lib/sync/webdav/webdav-transport.js +242 -43
- package/lib/ui/export-flow.d.ts +2 -2
- package/lib/ui/export-flow.js +63 -17
- package/lib/ui/i18n.d.ts +12 -1
- package/lib/ui/i18n.js +25 -2
- package/lib/ui/import-log.d.ts +72 -0
- package/lib/ui/import-log.js +110 -0
- package/lib/ui/import-stepper.d.ts +14 -0
- package/lib/ui/import-stepper.js +18 -0
- package/lib/ui/import-wizard.d.ts +76 -0
- package/lib/ui/import-wizard.js +96 -2
- package/lib/ui/mid-ellipsis.d.ts +31 -0
- package/lib/ui/mid-ellipsis.js +39 -0
- package/lib/ui/my-configs-view.d.ts +136 -0
- package/lib/ui/my-configs-view.js +127 -0
- package/lib/ui/progress.js +6 -0
- package/lib/ui/report.d.ts +26 -0
- package/lib/ui/report.js +37 -0
- package/lib/ui/runs-view.d.ts +128 -0
- package/lib/ui/runs-view.js +123 -0
- package/lib/ui/selection-model.d.ts +90 -1
- package/lib/ui/selection-model.js +243 -1
- package/lib/ui/snapshots-view.d.ts +116 -0
- package/lib/ui/snapshots-view.js +141 -0
- package/lib/ui/sync-settings-view.d.ts +295 -0
- package/lib/ui/sync-settings-view.js +280 -0
- package/lib/ui/test-helpers.d.ts +7 -1
- package/lib/ui/test-helpers.js +4 -1
- package/lib/ui/types.d.ts +8 -1
- package/lib/utils/env-lock.d.ts +38 -5
- package/lib/utils/env-lock.js +55 -37
- package/lib/utils/logger.d.ts +17 -13
- package/lib/utils/logger.js +79 -41
- package/lib/utils/paths.d.ts +1 -1
- package/lib/utils/paths.js +2 -2
- package/lib/utils/session-log.d.ts +70 -0
- package/lib/utils/session-log.js +347 -0
- package/lib/utils/zip.d.ts +49 -5
- package/lib/utils/zip.js +240 -51
- package/lib/utils/zstd-frame.d.ts +44 -0
- package/lib/utils/zstd-frame.js +115 -0
- package/package.json +2 -2
- package/src/adapters/agent-instructions.ts +5 -3
- package/src/adapters/agent-presets.ts +5 -3
- package/src/adapters/credentials.ts +19 -22
- package/src/adapters/file-collection.test.ts +256 -0
- package/src/adapters/file-collection.ts +129 -17
- package/src/adapters/index.ts +12 -1
- package/src/adapters/json-section.ts +73 -0
- package/src/adapters/mcp.ts +16 -19
- package/src/adapters/plugin-files.ts +11 -14
- package/src/adapters/plugins.test.ts +1 -1
- package/src/adapters/plugins.ts +39 -42
- package/src/adapters/prompts.ts +16 -20
- package/src/adapters/providers.ts +22 -25
- package/src/adapters/roundtrip.test.ts +44 -0
- package/src/adapters/sessions.test.ts +400 -1
- package/src/adapters/sessions.ts +435 -16
- package/src/adapters/settings.ts +22 -25
- package/src/adapters/skills.ts +5 -3
- package/src/adapters/test-helpers.ts +8 -0
- package/src/adapters/ui.ts +14 -17
- package/src/adapters/workspaces.test.ts +226 -2
- package/src/adapters/workspaces.ts +247 -16
- package/src/cli/index.ts +154 -28
- package/src/cli/sessions-repair.ts +229 -0
- package/src/client/CLIENT_DEPENDENCIES.md +3 -1
- package/src/client/ConfigManagerSection.tsx +38 -7
- package/src/client/api.ts +160 -237
- package/src/client/common/ContentPicker.tsx +33 -8
- package/src/client/common/ProgressBar.tsx +18 -9
- package/src/client/common/ReportView.tsx +127 -53
- package/src/client/common/RunsCenter.tsx +377 -0
- package/src/client/common/client-route-parity.test.ts +171 -0
- package/src/client/common/http-usage-guard.test.ts +163 -0
- package/src/client/common/http.test.ts +225 -0
- package/src/client/common/http.ts +238 -0
- package/src/client/common/plan-text-redaction.test.ts +29 -7
- package/src/client/common/progress-view.test.ts +33 -1
- package/src/client/common/progress-view.ts +17 -0
- package/src/client/common/routes.ts +132 -0
- package/src/client/common/runs-center-guard.test.ts +44 -0
- package/src/client/config-manager.module.css +332 -1
- package/src/client/export/ExportView.tsx +27 -3
- package/src/client/history/HistoryPanel.tsx +0 -1
- package/src/client/history/history-api.ts +34 -61
- package/src/client/import/ImportLogPanel.tsx +136 -0
- package/src/client/import/ImportWizardView.tsx +442 -836
- package/src/client/import/PathMappingForm.tsx +12 -1
- package/src/client/import/import-wizard-split.test.ts +101 -0
- package/src/client/import/import-wizard-steps.tsx +352 -0
- package/src/client/import/use-import-wizard-controller.ts +459 -0
- package/src/client/index.ts +0 -2
- package/src/client/lifecycle/lifecycle-api.ts +17 -88
- package/src/client/locales.ts +140 -0
- package/src/client/market/MyConfigsInstall.tsx +92 -0
- package/src/client/market/MyConfigsList.tsx +120 -0
- package/src/client/market/MyConfigsLoginCard.tsx +99 -0
- package/src/client/market/MyConfigsView.tsx +148 -476
- package/src/client/market/MyConfigsWizard.tsx +252 -0
- package/src/client/market/market-api.test.ts +36 -0
- package/src/client/market/market-api.ts +13 -70
- package/src/client/market/market-view.ts +1 -1
- package/src/client/market/my-configs-api.ts +17 -71
- package/src/client/market/my-configs-split.test.ts +58 -0
- package/src/client/overview/OverviewPanel.tsx +1 -9
- package/src/client/recovery/RecoveryPanel.tsx +1 -2
- package/src/client/recovery/recovery-api.ts +15 -65
- package/src/client/run-store.test.ts +421 -3
- package/src/client/run-store.ts +284 -53
- package/src/client/snapshots/BackupScheduleCard.tsx +437 -0
- package/src/client/snapshots/SnapshotsEmptyState.tsx +31 -0
- package/src/client/snapshots/SnapshotsListTable.tsx +97 -0
- package/src/client/snapshots/SnapshotsPanel.tsx +42 -626
- package/src/client/sync/AutosyncCard.tsx +88 -0
- package/src/client/sync/ChannelConfigDialog.tsx +268 -0
- package/src/client/sync/DecryptPasswordCard.tsx +48 -0
- package/src/client/sync/SecurityOptionsCard.tsx +83 -0
- package/src/client/sync/SyncChannelEntryCard.tsx +78 -0
- package/src/client/sync/SyncConfirmView.tsx +10 -8
- package/src/client/sync/SyncSectionPickerDialog.tsx +182 -0
- package/src/client/sync/SyncSettingsView.test.ts +85 -0
- package/src/client/sync/SyncSettingsView.tsx +230 -649
- package/src/client/sync/history-model.ts +4 -10
- package/src/client/sync/sync-api.ts +59 -100
- package/src/client/sync/sync-locales.ts +16 -2
- package/src/client/sync/sync-view-v2.test.ts +30 -4
- package/src/client/sync/sync-view.ts +67 -17
- package/src/core/analyzer-mapping.test.ts +55 -0
- package/src/core/analyzer.ts +451 -91
- package/src/core/apply-order.test.ts +182 -0
- package/src/core/backup-plan.test.ts +141 -0
- package/src/core/backup-plan.ts +16 -8
- package/src/core/backup.test.ts +310 -0
- package/src/core/backup.ts +174 -23
- package/src/core/base-path-rebase.test.ts +97 -0
- package/src/core/boot-rescue.test.ts +26 -0
- package/src/core/boot-safety.test.ts +138 -0
- package/src/core/boot-safety.ts +248 -0
- package/src/core/config-lifecycle.test.ts +255 -2
- package/src/core/config-lifecycle.ts +0 -2
- package/src/core/config-snapshot.test.ts +193 -3
- package/src/core/config-snapshot.ts +24 -8
- package/src/core/credential-status.test.ts +35 -0
- package/src/core/credential-status.ts +25 -0
- package/src/core/export-coupling.test.ts +255 -0
- package/src/core/exporter.ts +110 -3
- package/src/core/finalize-order.test.ts +82 -0
- package/src/core/import-sessions-visibility.test.ts +93 -0
- package/src/core/importer.ts +21 -3
- package/src/core/index.ts +10 -0
- package/src/core/journal.test.ts +59 -2
- package/src/core/journal.ts +152 -10
- package/src/core/messages.ts +110 -0
- package/src/core/migration-consult.ts +0 -5
- package/src/core/migration-history.test.ts +3 -5
- package/src/core/model-tools.test.ts +0 -14
- package/src/core/model-tools.ts +6 -8
- package/src/core/path-mapping.ts +33 -0
- package/src/core/phase1-wiring.test.ts +12 -3
- package/src/core/phase3-consistency.test.ts +1 -1
- package/src/core/phase3-host.ts +248 -70
- package/src/core/phase3-p2b-fingerprint.test.ts +0 -1
- package/src/core/phase3-production-integration.test.ts +1 -1
- package/src/core/phase4-crash-child.ts +0 -1
- package/src/core/phase4-crash-injection.test.ts +1 -3
- package/src/core/plugin-cli.fs.test.ts +2 -2
- package/src/core/plugin-cli.ts +1 -1
- package/src/core/reconcile.test.ts +62 -0
- package/src/core/reconcile.ts +20 -5
- package/src/core/recovery-orchestrator.test.ts +13 -1
- package/src/core/recovery-orchestrator.ts +1 -1
- package/src/core/restore.ts +10 -4
- package/src/core/rollback.ts +1 -1
- package/src/core/run-registry-runs.test.ts +71 -0
- package/src/core/run-registry.test.ts +58 -7
- package/src/core/run-registry.ts +96 -5
- package/src/core/session-meta.test.ts +59 -1
- package/src/core/session-meta.ts +62 -29
- package/src/core/session-repair.test.ts +91 -0
- package/src/core/session-repair.ts +203 -0
- package/src/core/session-select.test.ts +73 -0
- package/src/core/session-select.ts +131 -0
- package/src/core/smoke.test.ts +1 -2
- package/src/core/startup-barrier.ts +29 -2
- package/src/core/transaction-coordinator.ts +3 -3
- package/src/core/types.ts +244 -0
- package/src/core/validator.ts +0 -1
- package/src/index.sync.test.ts +8 -0
- package/src/index.ts +530 -2757
- package/src/market/git-file-writer.test.ts +1 -1
- package/src/market/index-parser.ts +29 -6
- package/src/market/market.test.ts +146 -1
- package/src/market/my-repo.test.ts +57 -4
- package/src/market/my-repo.ts +0 -7
- package/src/market/prepare.ts +12 -32
- package/src/market/reader.ts +0 -1
- package/src/market/security.ts +3 -11
- package/src/routes/backup.ts +103 -0
- package/src/routes/consult.ts +139 -0
- package/src/routes/context.ts +10 -0
- package/src/routes/history.ts +56 -0
- package/src/routes/import.ts +399 -0
- package/src/routes/index.ts +36 -0
- package/src/routes/kit.ts +222 -0
- package/src/routes/market.ts +275 -0
- package/src/routes/me.ts +168 -0
- package/src/routes/prefs.ts +104 -0
- package/src/routes/profiles.ts +131 -0
- package/src/routes/recovery.ts +113 -0
- package/src/routes/snapshots.ts +219 -0
- package/src/routes/sync.ts +810 -0
- package/src/schema/config.ts +96 -91
- package/src/schema/index.ts +33 -1
- package/src/schema/manifest.ts +3 -0
- package/src/schema/registry.test.ts +257 -0
- package/src/schema/section-registry.ts +423 -0
- package/src/schema/types.ts +13 -3
- package/src/schema/versions.ts +31 -1
- package/src/security/secret-scanner.ts +1 -1
- package/src/security/security.test.ts +145 -2
- package/src/security/zip-security.ts +20 -92
- package/src/sync/ancestor.test.ts +0 -1
- package/src/sync/ancestor.ts +23 -0
- package/src/sync/autosync-config.ts +9 -18
- package/src/sync/autosync-scheduler.test.ts +60 -3
- package/src/sync/autosync-scheduler.ts +45 -19
- package/src/sync/backup-schedule-config.ts +0 -1
- package/src/sync/backup-scheduler.test.ts +15 -2
- package/src/sync/blob-store.test.ts +191 -0
- package/src/sync/blob-store.ts +139 -0
- package/src/sync/git/git-transport.test.ts +203 -1
- package/src/sync/git/git-transport.ts +173 -24
- package/src/sync/github-auth.ts +0 -2
- package/src/sync/layout.ts +62 -8
- package/src/sync/risk.test.ts +32 -1
- package/src/sync/risk.ts +26 -31
- package/src/sync/session-tombstones.test.ts +87 -0
- package/src/sync/session-tombstones.ts +123 -0
- package/src/sync/sync-config.test.ts +132 -0
- package/src/sync/sync-config.ts +47 -6
- package/src/sync/sync-credentials.test.ts +77 -0
- package/src/sync/sync-engine.test.ts +799 -8
- package/src/sync/sync-engine.ts +322 -51
- package/src/sync/sync-history.ts +0 -1
- package/src/sync/sync-selection.test.ts +43 -7
- package/src/sync/sync-selection.ts +56 -24
- package/src/sync/sync-state.test.ts +83 -0
- package/src/sync/sync-state.ts +58 -7
- package/src/sync/transport.ts +191 -0
- package/src/sync/ui-prefs.ts +10 -6
- package/src/sync/webdav/webdav-transport.test.ts +122 -2
- package/src/sync/webdav/webdav-transport.ts +256 -37
- package/src/ui/export-flow.ts +65 -18
- package/src/ui/i18n.test.ts +149 -0
- package/src/ui/i18n.ts +25 -2
- package/src/ui/import-log.test.ts +106 -0
- package/src/ui/import-log.ts +158 -0
- package/src/ui/import-stepper.test.ts +25 -1
- package/src/ui/import-stepper.ts +18 -0
- package/src/ui/import-wizard.test.ts +130 -2
- package/src/ui/import-wizard.ts +135 -2
- package/src/ui/market-import.test.ts +0 -1
- package/src/ui/mid-ellipsis.test.ts +160 -0
- package/src/ui/mid-ellipsis.ts +37 -0
- package/src/ui/my-configs-view.test.ts +222 -0
- package/src/ui/my-configs-view.ts +206 -0
- package/src/ui/progress.ts +6 -0
- package/src/ui/report.test.ts +32 -2
- package/src/ui/report.ts +52 -0
- package/src/ui/runs-view.test.ts +152 -0
- package/src/ui/runs-view.ts +207 -0
- package/src/ui/selection-model.test.ts +268 -7
- package/src/ui/selection-model.ts +277 -1
- package/src/ui/snapshots-view.test.ts +228 -0
- package/src/ui/snapshots-view.ts +233 -0
- package/src/ui/sync-settings-view.test.ts +348 -0
- package/src/ui/sync-settings-view.ts +516 -0
- package/src/ui/test-helpers.ts +8 -1
- package/src/ui/types.ts +10 -1
- package/src/utils/env-lock.test.ts +170 -107
- package/src/utils/env-lock.ts +71 -35
- package/src/utils/logger.test.ts +178 -1
- package/src/utils/logger.ts +81 -44
- package/src/utils/paths.ts +2 -2
- package/src/utils/session-log.test.ts +136 -0
- package/src/utils/session-log.ts +344 -0
- package/src/utils/zip.test.ts +284 -0
- package/src/utils/zip.ts +226 -52
- package/src/utils/zstd-frame.test.ts +80 -0
- 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
|
+
|  |  |
|
|
93
|
+
|
|
90
94
|
| Export | Import Preview |
|
|
91
95
|
|:---:|:---:|
|
|
92
|
-
|  |  |  |
|
|
93
97
|
|
|
94
|
-
|
|
|
98
|
+
| Remote Sync | Configuration Market |
|
|
95
99
|
|:---:|:---:|
|
|
96
|
-
|  |  |
|
|
97
101
|
|
|
98
|
-
|
|
|
102
|
+
| Profiles (DSH profiles) |
|
|
99
103
|
|:---:|
|
|
100
|
-
|  |
|
|
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)
|
|
@@ -521,8 +546,10 @@ Yes. The import wizard asks for the export-time encryption password and verifies
|
|
|
521
546
|
issue #43 "Back up now" false-success bug. Two details from that patch were more robust than the mainline implementation and
|
|
522
547
|
have been adopted into `main`: (1) when `failed` comes back with an empty / whitespace-only error text, fall back to the
|
|
523
548
|
generic message instead of rendering a dangling "Backup failed:"; (2) an unknown `skipReason` is mapped to a localized
|
|
524
|
-
message rather than echoing the raw machine token.
|
|
525
|
-
|
|
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.
|
|
526
553
|
- **Bug reports**: `zhyx1996` (#38 sync "Export secrets" did nothing), `IPF-Sinon` (#39 the `.credentials.yaml` `refs:` block
|
|
527
554
|
was not recognized — they also implemented and machine-tested a fix in their own fork), `zerginlaw` (#43 "Back up now"
|
|
528
555
|
silently no-oped while toasting success). Each report drove one fix.
|
package/README.zh-CN.md
CHANGED
|
@@ -90,17 +90,21 @@ DSH 是你的 AI 助手工作台,里面存着你的各种设置:模型配置
|
|
|
90
90
|
|
|
91
91
|
## 📸 功能截图
|
|
92
92
|
|
|
93
|
+
| 总览 | 备份与快照 |
|
|
94
|
+
|:---:|:---:|
|
|
95
|
+
|  |  |
|
|
96
|
+
|
|
93
97
|
| 导出备份 | 导入预览 |
|
|
94
98
|
|:---:|:---:|
|
|
95
|
-
|  |  |  |
|
|
96
100
|
|
|
97
|
-
|
|
|
101
|
+
| 远程同步 | 配置市场 |
|
|
98
102
|
|:---:|:---:|
|
|
99
|
-
|  |  |
|
|
100
104
|
|
|
101
|
-
|
|
|
105
|
+
| 档案 Profiles |
|
|
102
106
|
|:---:|
|
|
103
|
-
|  |
|
|
104
108
|
|
|
105
109
|
---
|
|
106
110
|
|
|
@@ -459,8 +463,9 @@ dsh-config-manager backup --sections skills,self # 收窄范围
|
|
|
459
463
|
- **lux-liang (Jialiang Liang)** —— [PR #44](https://github.com/xiajiajun516/dsh-config-manager/pull/44):独立修复了 issue #43
|
|
460
464
|
「立即备份」假报成功的问题。其中两点比主线实现更稳,已采纳进 `main`:① `failed` 且宿主错误文本为空 / 全空白时
|
|
461
465
|
回退通用文案(否则会渲染出「备份失败:」这种半截提示);② 未知 `skipReason` 归一为本地化说明,不把机器 token
|
|
462
|
-
|
|
463
|
-
|
|
466
|
+
摆到用户面前。此外,为了让 GitHub 的 Contributors 列表如实计入这次贡献,其提交 `1248200` 已通过一次
|
|
467
|
+
**「保留其提交、树取主线」**的合并(`122317b`)成为 `main` 的祖先 —— 该合并**不取其代码**(合并后的树与主线
|
|
468
|
+
逐字节一致),只用于记录署名;因此按 GitHub 的口径这次贡献已被计入(而非 closed 后无痕)。
|
|
464
469
|
- **问题报告**:`zhyx1996`(#38 同步「导出密钥」未生效)、`IPF-Sinon`(#39 凭据 `refs:` 块未被识别,并在自己的 fork
|
|
465
470
|
上完成实现与真机验证)、`zerginlaw`(#43「立即备份」空转却提示成功)—— 这三条报告各自驱动了一个修复。
|
|
466
471
|
|
|
@@ -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
|
|
7
|
-
readonly defaultIncluded
|
|
8
|
-
readonly portability:
|
|
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
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
|
10
|
-
readonly defaultIncluded
|
|
11
|
-
readonly portability:
|
|
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
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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
|
|
29
|
-
readonly defaultIncluded
|
|
30
|
-
readonly portability:
|
|
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
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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
|
-
|
|
77
|
-
|
|
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
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
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
|
package/lib/adapters/index.d.ts
CHANGED
|
@@ -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';
|
package/lib/adapters/index.js
CHANGED
|
@@ -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;
|