dsh-config-manager 0.1.71 → 0.1.73
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 +149 -98
- package/README.zh-CN.md +245 -88
- package/lib/adapters/agent-instructions.js +1 -1
- package/lib/adapters/file-collection.js +1 -1
- package/lib/adapters/index.d.ts +2 -1
- package/lib/adapters/index.js +6 -1
- package/lib/adapters/link-report.d.ts +1 -0
- package/lib/adapters/link-report.js +27 -3
- package/lib/adapters/messages.d.ts +21 -0
- package/lib/adapters/messages.js +57 -0
- package/lib/adapters/plugin-data.d.ts +114 -0
- package/lib/adapters/plugin-data.js +309 -0
- package/lib/adapters/plugin-files.js +3 -2
- package/lib/adapters/plugins.js +4 -3
- package/lib/adapters/prompts.d.ts +1 -0
- package/lib/adapters/prompts.js +7 -1
- package/lib/adapters/self.d.ts +8 -2
- package/lib/adapters/self.js +3 -2
- package/lib/adapters/sessions.d.ts +1 -0
- package/lib/adapters/sessions.js +4 -2
- package/lib/adapters/test-helpers.d.ts +9 -0
- package/lib/adapters/test-helpers.js +14 -0
- package/lib/adapters/ui.d.ts +2 -2
- package/lib/adapters/ui.js +25 -25
- package/lib/adapters/workspaces.d.ts +1 -0
- package/lib/adapters/workspaces.js +7 -3
- package/lib/cli/actions.d.ts +9 -1
- package/lib/cli/actions.js +5 -2
- package/lib/cli/help.js +23 -10
- package/lib/cli/index.d.ts +23 -0
- package/lib/cli/index.js +90 -32
- package/lib/cli/sessions-inspect.js +3 -1
- package/lib/cli/sessions-repair.js +25 -6
- package/lib/cli/web/page.d.ts +12 -0
- package/lib/cli/web/page.js +21 -12
- package/lib/cli/web/routes.js +2 -2
- package/lib/cli/web/server.js +4 -4
- package/lib/client.d.ts +1442 -494
- package/lib/client.js +15326 -8520
- package/lib/core/analyzer.d.ts +11 -2
- package/lib/core/analyzer.js +31 -8
- package/lib/core/backup-plan.d.ts +10 -0
- package/lib/core/backup-plan.js +51 -20
- package/lib/core/backup-verify.js +24 -23
- package/lib/core/backup.d.ts +25 -1
- package/lib/core/backup.js +99 -28
- package/lib/core/boot-rescue.js +4 -3
- package/lib/core/boot-safety.js +2 -1
- package/lib/core/command-rollback.d.ts +182 -0
- package/lib/core/command-rollback.js +293 -0
- package/lib/core/consult-source.js +7 -6
- package/lib/core/disk-usage.d.ts +74 -10
- package/lib/core/disk-usage.js +67 -4
- package/lib/core/engine-locale.d.ts +87 -0
- package/lib/core/engine-locale.js +125 -0
- package/lib/core/exporter.js +3 -3
- package/lib/core/fork-reconcile.d.ts +128 -0
- package/lib/core/fork-reconcile.js +150 -0
- package/lib/core/journal.js +6 -5
- package/lib/core/local-plugin-host.js +3 -2
- package/lib/core/local-plugin-pack.js +11 -6
- package/lib/core/messages-core.d.ts +294 -0
- package/lib/core/messages-core.js +619 -0
- package/lib/core/messages.d.ts +61 -2
- package/lib/core/messages.js +118 -13
- package/lib/core/migration-consult.js +21 -20
- package/lib/core/migration-history.js +19 -5
- package/lib/core/model-tools.d.ts +0 -22
- package/lib/core/model-tools.js +50 -27
- package/lib/core/phase3-host.js +30 -5
- package/lib/core/plugin-cli.js +18 -19
- package/lib/core/plugin-data-registry.d.ts +124 -0
- package/lib/core/plugin-data-registry.js +198 -0
- package/lib/core/reconcile.d.ts +2 -14
- package/lib/core/reconcile.js +87 -17
- package/lib/core/recovery-orchestrator.js +19 -18
- package/lib/core/reinstall.d.ts +4 -0
- package/lib/core/reinstall.js +26 -21
- package/lib/core/restore.js +20 -17
- package/lib/core/rollback-gate.d.ts +33 -0
- package/lib/core/rollback-gate.js +57 -0
- package/lib/core/rollback-ignore-rules.d.ts +31 -0
- package/lib/core/rollback-ignore-rules.js +140 -0
- package/lib/core/rollback-ledger.d.ts +248 -0
- package/lib/core/rollback-ledger.js +1282 -0
- package/lib/core/rollback-point.d.ts +1391 -0
- package/lib/core/rollback-point.js +3503 -0
- package/lib/core/rollback.js +6 -4
- package/lib/core/run-registry.js +6 -5
- package/lib/core/session-health.d.ts +15 -0
- package/lib/core/session-meta.d.ts +42 -2
- package/lib/core/session-meta.js +140 -1
- package/lib/core/session-title.d.ts +28 -0
- package/lib/core/session-title.js +185 -0
- package/lib/core/startup-barrier.d.ts +24 -0
- package/lib/core/startup-barrier.js +22 -0
- package/lib/core/transaction-coordinator.js +4 -3
- package/lib/core/types.d.ts +37 -1
- package/lib/core/validator.js +5 -4
- package/lib/core/verify-recovery.js +31 -30
- package/lib/foreign/bundle.js +3 -2
- package/lib/foreign/continue.js +2 -1
- package/lib/foreign/crush.js +2 -1
- package/lib/foreign/gemini.js +2 -1
- package/lib/foreign/goose.js +2 -1
- package/lib/foreign/kilocode.js +2 -1
- package/lib/foreign/kimi.js +2 -1
- package/lib/foreign/messages.d.ts +15 -0
- package/lib/foreign/messages.js +40 -0
- package/lib/foreign/mimocode.js +2 -1
- package/lib/foreign/openclaw.js +2 -1
- package/lib/foreign/opencode.js +2 -1
- package/lib/foreign/pi.js +2 -1
- package/lib/foreign/qoder.js +2 -1
- package/lib/foreign/qwen.js +2 -1
- package/lib/foreign/registry.d.ts +1 -1
- package/lib/foreign/registry.js +8 -7
- package/lib/foreign/teleagent.js +2 -1
- package/lib/foreign/workbuddy.js +2 -1
- package/lib/foreign/zcode.js +2 -1
- package/lib/foreign/zed.js +2 -1
- package/lib/index.d.ts +227 -7
- package/lib/index.js +799 -54
- package/lib/market/git-file-writer.js +20 -19
- package/lib/market/github-repos.js +35 -19
- package/lib/market/index-parser.js +31 -30
- package/lib/market/messages-market.d.ts +138 -0
- package/lib/market/messages-market.js +305 -0
- package/lib/market/my-repo.d.ts +2 -2
- package/lib/market/my-repo.js +32 -29
- package/lib/market/prepare.js +18 -15
- package/lib/market/security.js +20 -19
- package/lib/market/types.js +2 -1
- package/lib/market/url.js +2 -1
- package/lib/migrations/index.js +6 -5
- package/lib/migrations/messages-migrations.d.ts +32 -0
- package/lib/migrations/messages-migrations.js +58 -0
- package/lib/profiles/dsh-profile-launcher.js +2 -1
- package/lib/profiles/dsh-profile-runtime.js +2 -1
- package/lib/profiles/messages.d.ts +7 -0
- package/lib/profiles/messages.js +27 -0
- package/lib/routes/backup.js +18 -2
- package/lib/routes/import.js +3 -3
- package/lib/routes/index.d.ts +1 -1
- package/lib/routes/kit.d.ts +3 -2
- package/lib/routes/kit.js +6 -1
- package/lib/routes/market.js +1 -1
- package/lib/routes/messages.d.ts +43 -0
- package/lib/routes/messages.js +102 -0
- package/lib/routes/prefs.js +26 -3
- package/lib/routes/profiles.js +12 -6
- package/lib/routes/recovery.d.ts +21 -0
- package/lib/routes/recovery.js +478 -94
- package/lib/routes/snapshots.js +51 -4
- package/lib/routes/sync.js +79 -17
- package/lib/schema/config.d.ts +11 -1
- package/lib/schema/config.js +52 -20
- package/lib/schema/index.d.ts +8 -6
- package/lib/schema/index.js +8 -6
- package/lib/schema/manifest.js +19 -18
- package/lib/schema/messages.d.ts +47 -0
- package/lib/schema/messages.js +110 -0
- package/lib/schema/section-registry.d.ts +42 -15
- package/lib/schema/section-registry.js +67 -32
- package/lib/schema/types.d.ts +1 -1
- package/lib/schema/versions.js +6 -5
- package/lib/security/encryption.js +16 -15
- package/lib/security/integrity.js +8 -7
- package/lib/security/messages.d.ts +30 -0
- package/lib/security/messages.js +77 -0
- package/lib/security/redaction.js +12 -0
- package/lib/security/secret-scanner.js +16 -13
- package/lib/security/vault.js +2 -1
- package/lib/security/zip-security.js +4 -3
- package/lib/sync/ancestor.js +2 -1
- package/lib/sync/autosync-config.d.ts +11 -0
- package/lib/sync/autosync-config.js +12 -0
- package/lib/sync/autosync-scheduler.d.ts +11 -1
- package/lib/sync/autosync-scheduler.js +115 -76
- package/lib/sync/backup-files.js +3 -2
- package/lib/sync/backup-scheduler.js +6 -5
- package/lib/sync/blob-store.js +2 -1
- package/lib/sync/git/git-transport.d.ts +55 -0
- package/lib/sync/git/git-transport.js +152 -2
- package/lib/sync/github-auth.js +10 -9
- package/lib/sync/layout.js +13 -12
- package/lib/sync/messages.d.ts +63 -0
- package/lib/sync/messages.js +149 -0
- package/lib/sync/review-queue.js +6 -5
- package/lib/sync/s3/messages.d.ts +7 -0
- package/lib/sync/s3/messages.js +16 -0
- package/lib/sync/s3/s3-providers.d.ts +4 -1
- package/lib/sync/s3/s3-providers.js +5 -5
- package/lib/sync/s3/s3-transport.js +1 -1
- package/lib/sync/s3/sigv4.js +2 -1
- package/lib/sync/snapshot-crypto.js +6 -5
- package/lib/sync/snapshot-json.js +3 -2
- package/lib/sync/sync-engine.d.ts +58 -22
- package/lib/sync/sync-engine.js +119 -48
- package/lib/sync/sync-history.js +7 -6
- package/lib/sync/sync-selection.d.ts +15 -4
- package/lib/sync/sync-selection.js +17 -5
- package/lib/sync/ui-prefs.d.ts +22 -0
- package/lib/sync/ui-prefs.js +28 -0
- package/lib/sync/webdav/webdav-transport.d.ts +1 -0
- package/lib/sync/webdav/webdav-transport.js +4 -1
- package/lib/ui/artifact-view.js +4 -3
- package/lib/ui/commands.d.ts +1 -1
- package/lib/ui/commands.js +10 -0
- package/lib/ui/diff-view.d.ts +10 -0
- package/lib/ui/diff-view.js +17 -0
- package/lib/ui/disk-usage-view.d.ts +33 -4
- package/lib/ui/disk-usage-view.js +29 -1
- package/lib/ui/errors.d.ts +1 -1
- package/lib/ui/errors.js +4 -3
- package/lib/ui/export-flow.js +26 -20
- package/lib/ui/foreign-view.d.ts +5 -1
- package/lib/ui/foreign-view.js +26 -2
- package/lib/ui/i18n.d.ts +68 -1
- package/lib/ui/i18n.js +120 -2
- package/lib/ui/import-wizard.d.ts +7 -0
- package/lib/ui/import-wizard.js +13 -10
- package/lib/ui/progress.d.ts +2 -2
- package/lib/ui/progress.js +1 -2
- package/lib/ui/report.d.ts +5 -5
- package/lib/ui/report.js +6 -6
- package/lib/ui/rollback-view.d.ts +815 -0
- package/lib/ui/rollback-view.js +738 -0
- package/lib/ui/selection-model.d.ts +41 -0
- package/lib/ui/selection-model.js +30 -0
- package/lib/ui/session-inventory-view.d.ts +97 -3
- package/lib/ui/session-inventory-view.js +76 -2
- package/lib/ui/session-issue-labels.d.ts +43 -0
- package/lib/ui/session-issue-labels.js +48 -0
- package/lib/ui/sync-settings-view.d.ts +33 -0
- package/lib/ui/sync-settings-view.js +40 -0
- package/lib/ui/test-helpers.js +2 -1
- package/lib/ui/types.d.ts +8 -3
- package/lib/ui/types.js +1 -1
- package/lib/utils/atomic-write.js +6 -5
- package/lib/utils/bundle-scan.d.ts +11 -1
- package/lib/utils/bundle-scan.js +12 -1
- package/lib/utils/env-lock.d.ts +10 -0
- package/lib/utils/env-lock.js +73 -46
- package/lib/utils/json.js +6 -5
- package/lib/utils/messages-utils.d.ts +96 -0
- package/lib/utils/messages-utils.js +216 -0
- package/lib/utils/proxy.js +18 -5
- package/lib/utils/recursive-walk.d.ts +22 -0
- package/lib/utils/recursive-walk.js +75 -1
- package/lib/utils/session-log-repair.js +10 -9
- package/lib/utils/zip.js +27 -26
- package/package.json +12 -2
- package/src/adapters/agent-instructions.ts +1 -1
- package/src/adapters/file-collection.test.ts +1 -0
- package/src/adapters/file-collection.ts +1 -1
- package/src/adapters/files.test.ts +27 -0
- package/src/adapters/index.ts +6 -1
- package/src/adapters/link-report.test.ts +22 -2
- package/src/adapters/link-report.ts +29 -4
- package/src/adapters/messages.ts +61 -0
- package/src/adapters/plugin-data.test.ts +225 -0
- package/src/adapters/plugin-data.ts +351 -0
- package/src/adapters/plugin-files.ts +3 -2
- package/src/adapters/plugins.ts +4 -3
- package/src/adapters/prompts.ts +7 -1
- package/src/adapters/self.test.ts +26 -0
- package/src/adapters/self.ts +10 -3
- package/src/adapters/sessions.ts +4 -2
- package/src/adapters/settings.test.ts +38 -1
- package/src/adapters/test-helpers.ts +15 -0
- package/src/adapters/ui.test.ts +2 -2
- package/src/adapters/ui.ts +27 -26
- package/src/adapters/workspaces.ts +7 -3
- package/src/cli/actions.test.ts +28 -0
- package/src/cli/actions.ts +11 -2
- package/src/cli/help.ts +23 -10
- package/src/cli/index-guards.test.ts +193 -0
- package/src/cli/index.ts +106 -31
- package/src/cli/sessions-inspect.test.ts +15 -1
- package/src/cli/sessions-inspect.ts +3 -1
- package/src/cli/sessions-repair.test.ts +84 -0
- package/src/cli/sessions-repair.ts +33 -7
- package/src/cli/web/console-route-parity.test.ts +115 -0
- package/src/cli/web/page.ts +23 -12
- package/src/cli/web/routes.ts +2 -1
- package/src/cli/web/server.ts +4 -4
- package/src/cli/web/web.test.ts +2 -2
- package/src/client/ConfigManagerSection.tsx +30 -2
- package/src/client/about/AboutPanel.tsx +2 -2
- package/src/client/about/PluginUpdateDialog.tsx +1 -1
- package/src/client/about/ReleaseNotesDialog.tsx +2 -2
- package/src/client/about/about-view.test.ts +13 -3
- package/src/client/about/about-view.ts +10 -6
- package/src/client/about/release-notes-view.test.ts +14 -4
- package/src/client/about/release-notes-view.ts +4 -2
- package/src/client/api.ts +18 -6
- package/src/client/chat/ChatEntryView.tsx +411 -0
- package/src/client/chat/RollbackDiffTab.tsx +108 -0
- package/src/client/chat/RollbackFileIcon.tsx +96 -0
- package/src/client/chat/RollbackReviewDialog.tsx +607 -0
- package/src/client/chat/chat-entry-view.test.ts +1174 -0
- package/src/client/chat/chat-entry-view.ts +758 -0
- package/src/client/chat/chat-locales.ts +152 -0
- package/src/client/chat/chat-node-registration.ts +64 -0
- package/src/client/chat/chat-point-window.ts +183 -0
- package/src/client/chat/chat-slots.ts +107 -0
- package/src/client/chat/rollback-diff-tab.test.ts +105 -0
- package/src/client/chat/rollback-diff-tab.ts +174 -0
- package/src/client/chat/rollback-entry-view.test.ts +1469 -0
- package/src/client/chat/rollback-entry-view.ts +946 -0
- package/src/client/chat/sidebar-slots.ts +59 -0
- package/src/client/common/ConfirmDialog.tsx +7 -3
- package/src/client/common/ContentPicker.tsx +48 -10
- package/src/client/common/DiffTable.tsx +133 -0
- package/src/client/common/ErrorBanner.tsx +4 -4
- package/src/client/common/Icon.tsx +2 -0
- package/src/client/common/Modal.tsx +8 -1
- package/src/client/common/ProgressBar.tsx +8 -2
- package/src/client/common/ReportView.tsx +4 -4
- package/src/client/common/RunsCenter.tsx +1 -0
- package/src/client/common/client-route-parity.test.ts +84 -0
- package/src/client/common/info-hint-guard.test.ts +46 -13
- package/src/client/common/picker-lazy-units.test.ts +42 -0
- package/src/client/common/plan-text-redaction.test.ts +17 -6
- package/src/client/common/progress-view-edge.test.ts +8 -7
- package/src/client/common/progress-view.test.ts +9 -9
- package/src/client/common/progress-view.ts +2 -2
- package/src/client/common/routes.ts +38 -0
- package/src/client/common/section-labels.test.ts +1 -0
- package/src/client/common/section-labels.ts +2 -0
- package/src/client/common/ui.tsx +73 -10
- package/src/client/config-manager.module.css +1109 -31
- package/src/client/css-scale-guard.test.ts +30 -0
- package/src/client/environment/EnvironmentPanel.tsx +11 -4
- package/src/client/environment/maintenance/DiskUsageCard.tsx +23 -2
- package/src/client/export/ExportView.tsx +13 -8
- package/src/client/history/HistoryPanel.tsx +4 -3
- package/src/client/history/history-api.ts +2 -2
- package/src/client/history/history-locales.ts +5 -1
- package/src/client/home/HomePanel.tsx +29 -8
- package/src/client/import/ForeignImportView.tsx +4 -5
- package/src/client/import/ImportWizardView.tsx +5 -3
- package/src/client/import/import-wizard-split.test.ts +3 -1
- package/src/client/import/import-wizard-steps.tsx +9 -9
- package/src/client/index.ts +232 -3
- package/src/client/library/LibraryActions.tsx +32 -7
- package/src/client/library/LibraryPanel.tsx +23 -3
- package/src/client/library/RestorePlanView.tsx +9 -66
- package/src/client/locales.ts +62 -9
- package/src/client/lucide-icons.d.ts +1 -0
- package/src/client/market/MarketPanel.tsx +13 -2
- package/src/client/market/MyConfigsView.tsx +5 -4
- package/src/client/market/MyConfigsWizard.tsx +10 -8
- package/src/client/market/market-api.test.ts +9 -8
- package/src/client/market/market-api.ts +2 -2
- package/src/client/market/market-locales.ts +3 -0
- package/src/client/market/market-view.test.ts +6 -6
- package/src/client/market/market-view.ts +3 -3
- package/src/client/market/my-configs-api.ts +2 -2
- package/src/client/market/my-configs-view.test.ts +23 -22
- package/src/client/market/my-configs-view.ts +8 -8
- package/src/client/recovery/IncompleteCopiesSection.tsx +2 -1
- package/src/client/recovery/RecoveryPanel.tsx +442 -283
- package/src/client/recovery/RollbackCard.tsx +859 -0
- package/src/client/recovery/RollbackDiffPane.tsx +69 -0
- package/src/client/recovery/RollbackFileChanges.tsx +163 -0
- package/src/client/recovery/incident-api.ts +2 -2
- package/src/client/recovery/recovery-api.ts +144 -6
- package/src/client/recovery/recovery-locales.test.ts +299 -0
- package/src/client/recovery/recovery-locales.ts +520 -149
- package/src/client/recovery/recovery-panel-layout.test.ts +132 -2
- package/src/client/recovery/rollback-enabled.ts +41 -0
- package/src/client/recovery/rollback-file-changes.test.ts +114 -0
- package/src/client/recovery/session-dialog-mount.test.ts +54 -0
- package/src/client/run-store.ts +84 -21
- package/src/client/sync/AutosyncCard.tsx +25 -1
- package/src/client/sync/ChannelClearConfirmDialog.tsx +6 -3
- package/src/client/sync/ChannelConfigDialog.tsx +10 -10
- package/src/client/sync/DecryptPasswordCard.tsx +2 -2
- package/src/client/sync/SecurityOptionsCard.tsx +27 -17
- package/src/client/sync/SyncChannelEntryCard.tsx +14 -2
- package/src/client/sync/SyncConfirmView.tsx +46 -8
- package/src/client/sync/SyncLogList.tsx +1 -1
- package/src/client/sync/SyncPanel.tsx +31 -23
- package/src/client/sync/SyncRepositoryPicker.tsx +3 -5
- package/src/client/sync/SyncSectionPickerDialog.tsx +11 -2
- package/src/client/sync/SyncSettingsView.test.ts +20 -1
- package/src/client/sync/UnreadableSnapshotsBanner.tsx +1 -1
- package/src/client/sync/WorkDirCard.tsx +112 -0
- package/src/client/sync/history-model.test.ts +42 -9
- package/src/client/sync/history-model.ts +30 -15
- package/src/client/sync/sync-api.test.ts +57 -33
- package/src/client/sync/sync-api.ts +80 -6
- package/src/client/sync/sync-locales.ts +104 -3
- package/src/client/sync/sync-view-v2.test.ts +65 -13
- package/src/client/sync/sync-view.test.ts +107 -52
- package/src/client/sync/sync-view.ts +124 -41
- package/src/core/analyzer.ts +31 -9
- package/src/core/backup-plan.test.ts +48 -5
- package/src/core/backup-plan.ts +51 -20
- package/src/core/backup-verify.ts +24 -23
- package/src/core/backup.test.ts +76 -0
- package/src/core/backup.ts +105 -29
- package/src/core/boot-rescue.ts +4 -3
- package/src/core/boot-safety.ts +2 -1
- package/src/core/command-rollback.test.ts +536 -0
- package/src/core/command-rollback.ts +406 -0
- package/src/core/consult-source.ts +7 -6
- package/src/core/disk-usage.test.ts +257 -1
- package/src/core/disk-usage.ts +134 -14
- package/src/core/engine-locale.test.ts +593 -0
- package/src/core/engine-locale.ts +175 -0
- package/src/core/exporter.ts +3 -3
- package/src/core/fork-reconcile.test.ts +279 -0
- package/src/core/fork-reconcile.ts +234 -0
- package/src/core/journal.ts +6 -5
- package/src/core/local-plugin-host.ts +3 -2
- package/src/core/local-plugin-pack.ts +11 -6
- package/src/core/messages-core.ts +661 -0
- package/src/core/messages.ts +125 -17
- package/src/core/migration-consult.ts +21 -20
- package/src/core/migration-history.ts +19 -5
- package/src/core/model-tools.ts +28 -27
- package/src/core/phase3-host.ts +21 -5
- package/src/core/phase3-p2b-fingerprint.test.ts +22 -0
- package/src/core/plugin-cli.ts +18 -19
- package/src/core/plugin-data-registry.test.ts +113 -0
- package/src/core/plugin-data-registry.ts +261 -0
- package/src/core/reconcile.test.ts +117 -0
- package/src/core/reconcile.ts +85 -17
- package/src/core/recovery-orchestrator.ts +19 -18
- package/src/core/reinstall.ts +26 -21
- package/src/core/restore.ts +20 -17
- package/src/core/rollback-file-diff.test.ts +159 -0
- package/src/core/rollback-gate.test.ts +66 -0
- package/src/core/rollback-gate.ts +61 -0
- package/src/core/rollback-ignore-rules.ts +126 -0
- package/src/core/rollback-ledger.test.ts +655 -0
- package/src/core/rollback-ledger.ts +1314 -0
- package/src/core/rollback-point.test.ts +2853 -0
- package/src/core/rollback-point.ts +4340 -0
- package/src/core/rollback.ts +5 -3
- package/src/core/run-registry.ts +6 -5
- package/src/core/session-health.ts +15 -0
- package/src/core/session-meta.test.ts +117 -1
- package/src/core/session-meta.ts +149 -2
- package/src/core/session-title.test.ts +127 -0
- package/src/core/session-title.ts +189 -0
- package/src/core/startup-barrier.test.ts +69 -0
- package/src/core/startup-barrier.ts +27 -0
- package/src/core/transaction-coordinator.ts +4 -3
- package/src/core/types.ts +37 -1
- package/src/core/validator.ts +5 -4
- package/src/core/verify-recovery.ts +31 -30
- package/src/foreign/bundle.ts +3 -2
- package/src/foreign/continue.ts +2 -1
- package/src/foreign/crush.ts +2 -1
- package/src/foreign/file-budget.test.ts +2 -0
- package/src/foreign/gemini.ts +2 -1
- package/src/foreign/goose.ts +2 -1
- package/src/foreign/kilocode.ts +2 -1
- package/src/foreign/kimi.ts +2 -1
- package/src/foreign/messages.ts +44 -0
- package/src/foreign/mimocode.ts +2 -1
- package/src/foreign/openclaw.ts +2 -1
- package/src/foreign/opencode.ts +2 -1
- package/src/foreign/pi.ts +2 -1
- package/src/foreign/qoder.ts +2 -1
- package/src/foreign/qwen.ts +2 -1
- package/src/foreign/registry.test.ts +1 -1
- package/src/foreign/registry.ts +8 -7
- package/src/foreign/teleagent.ts +2 -1
- package/src/foreign/workbuddy.ts +2 -1
- package/src/foreign/zcode.ts +2 -1
- package/src/foreign/zed.ts +2 -1
- package/src/index.facade.test.ts +340 -1
- package/src/index.ts +876 -57
- package/src/market/git-file-writer.ts +20 -19
- package/src/market/github-repos.test.ts +23 -0
- package/src/market/github-repos.ts +37 -19
- package/src/market/index-parser.ts +31 -30
- package/src/market/messages-market.ts +325 -0
- package/src/market/my-repo.ts +32 -29
- package/src/market/prepare.ts +18 -15
- package/src/market/security.ts +20 -19
- package/src/market/types.ts +2 -1
- package/src/market/url.ts +2 -1
- package/src/migrations/index.ts +6 -5
- package/src/migrations/messages-migrations.ts +59 -0
- package/src/profiles/dsh-profile-launcher.ts +2 -1
- package/src/profiles/dsh-profile-runtime.ts +2 -1
- package/src/profiles/messages.ts +31 -0
- package/src/routes/backup.ts +18 -2
- package/src/routes/import.ts +3 -3
- package/src/routes/index.ts +1 -1
- package/src/routes/kit.ts +9 -3
- package/src/routes/market.ts +1 -1
- package/src/routes/messages.ts +106 -0
- package/src/routes/prefs.ts +31 -2
- package/src/routes/profiles.ts +12 -6
- package/src/routes/recovery.test.ts +405 -1
- package/src/routes/recovery.ts +477 -94
- package/src/routes/snapshots.ts +51 -4
- package/src/routes/sync.ts +82 -17
- package/src/schema/config.ts +53 -20
- package/src/schema/index.ts +7 -5
- package/src/schema/manifest-schema.test.ts +4 -4
- package/src/schema/manifest.ts +19 -18
- package/src/schema/messages.ts +120 -0
- package/src/schema/registry.test.ts +28 -7
- package/src/schema/section-registry.ts +96 -42
- package/src/schema/types.ts +2 -1
- package/src/schema/versions.ts +6 -5
- package/src/security/encryption.ts +16 -15
- package/src/security/integrity.ts +8 -7
- package/src/security/messages.ts +87 -0
- package/src/security/redaction.test.ts +17 -1
- package/src/security/redaction.ts +13 -0
- package/src/security/secret-scanner.ts +19 -12
- package/src/security/security.test.ts +47 -0
- package/src/security/vault.ts +2 -1
- package/src/security/zip-security.ts +4 -3
- package/src/sync/ancestor.ts +2 -1
- package/src/sync/autosync-config.ts +21 -0
- package/src/sync/autosync-scheduler.test.ts +63 -7
- package/src/sync/autosync-scheduler.ts +118 -78
- package/src/sync/backup-files.ts +3 -2
- package/src/sync/backup-scheduler.ts +6 -5
- package/src/sync/blob-store.ts +2 -1
- package/src/sync/git/git-transport.test.ts +170 -8
- package/src/sync/git/git-transport.ts +176 -2
- package/src/sync/github-auth.ts +10 -9
- package/src/sync/layout.ts +13 -12
- package/src/sync/messages.ts +153 -0
- package/src/sync/review-queue.ts +6 -5
- package/src/sync/s3/messages.ts +16 -0
- package/src/sync/s3/s3-providers.ts +9 -6
- package/src/sync/s3/s3-transport.ts +1 -1
- package/src/sync/s3/sigv4.ts +3 -1
- package/src/sync/snapshot-crypto.ts +6 -5
- package/src/sync/snapshot-json.ts +3 -2
- package/src/sync/sync-credentials.test.ts +119 -0
- package/src/sync/sync-engine-download-tombstone.test.ts +1 -1
- package/src/sync/sync-engine.test.ts +226 -8
- package/src/sync/sync-engine.ts +154 -52
- package/src/sync/sync-history.ts +7 -6
- package/src/sync/sync-selection.test.ts +12 -2
- package/src/sync/sync-selection.ts +18 -5
- package/src/sync/transport.test.ts +1 -0
- package/src/sync/ui-prefs.test.ts +44 -1
- package/src/sync/ui-prefs.ts +39 -0
- package/src/sync/webdav/webdav-transport.ts +4 -1
- package/src/ui/artifact-view.ts +4 -3
- package/src/ui/commands.test.ts +1 -0
- package/src/ui/commands.ts +11 -1
- package/src/ui/diff-view.test.ts +9 -1
- package/src/ui/diff-view.ts +22 -0
- package/src/ui/disk-usage-view.test.ts +113 -6
- package/src/ui/disk-usage-view.ts +63 -7
- package/src/ui/errors.ts +4 -3
- package/src/ui/export-flow.ts +26 -20
- package/src/ui/foreign-view.test.ts +43 -0
- package/src/ui/foreign-view.ts +30 -3
- package/src/ui/i18n.ts +131 -2
- package/src/ui/import-wizard.ts +18 -10
- package/src/ui/progress.ts +2 -2
- package/src/ui/report.ts +7 -6
- package/src/ui/rollback-view.test.ts +836 -0
- package/src/ui/rollback-view.ts +1405 -0
- package/src/ui/selection-model.test.ts +33 -1
- package/src/ui/selection-model.ts +53 -0
- package/src/ui/session-inventory-view.test.ts +109 -0
- package/src/ui/session-inventory-view.ts +138 -3
- package/src/ui/session-issue-labels.test.ts +51 -0
- package/src/ui/session-issue-labels.ts +70 -0
- package/src/ui/sync-settings-view.test.ts +36 -0
- package/src/ui/sync-settings-view.ts +49 -0
- package/src/ui/test-helpers.ts +2 -1
- package/src/ui/types.ts +10 -4
- package/src/utils/atomic-write.ts +6 -5
- package/src/utils/bundle-scan.test.ts +2 -2
- package/src/utils/bundle-scan.ts +12 -1
- package/src/utils/env-lock.ts +83 -49
- package/src/utils/json.ts +7 -5
- package/src/utils/messages-utils.ts +232 -0
- package/src/utils/proxy.test.ts +25 -0
- package/src/utils/proxy.ts +19 -5
- package/src/utils/recursive-walk.test.ts +43 -0
- package/src/utils/recursive-walk.ts +75 -1
- package/src/utils/session-log-repair.ts +10 -9
- package/src/utils/zip.ts +27 -26
- package/lib/core/checkpoint.d.ts +0 -362
- package/lib/core/checkpoint.js +0 -1309
- package/src/core/checkpoint.test.ts +0 -665
- package/src/core/checkpoint.ts +0 -1614
package/lib/core/backup.js
CHANGED
|
@@ -5,7 +5,9 @@
|
|
|
5
5
|
* 快照不保存 credential 值(DSH 永不回读值)——credential 条目只记 existed 标志,
|
|
6
6
|
* 回滚时 existed=true 的条目如实标记「值需人工补录」。
|
|
7
7
|
*/
|
|
8
|
+
import { engineMsg } from './messages-core.js';
|
|
8
9
|
import fs from 'node:fs/promises';
|
|
10
|
+
import { homedir } from 'node:os';
|
|
9
11
|
import path from 'node:path';
|
|
10
12
|
import crypto from 'node:crypto';
|
|
11
13
|
import { parseJsonSafe } from '../utils/json.js';
|
|
@@ -60,8 +62,11 @@ async function backupHostFiles(ctx, blobs) {
|
|
|
60
62
|
existed = await ctx.fs.exists(relPath);
|
|
61
63
|
}
|
|
62
64
|
catch (err) {
|
|
65
|
+
// 探测自身失败 = 存在性判不出来:按「存在但不可读」登记。**绝不写 existed:false** ——
|
|
66
|
+
// 那会被恢复侧读成「当时确实不存在」,进而删掉用户真实的文件。
|
|
63
67
|
ctx.log.warn(`快照探测宿主文件失败 ${relPath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
64
|
-
existed
|
|
68
|
+
backups.push({ relPath, blobPath: '', existed: true, unreadable: true });
|
|
69
|
+
continue;
|
|
65
70
|
}
|
|
66
71
|
if (!existed) {
|
|
67
72
|
backups.push({ relPath, blobPath: '', existed: false });
|
|
@@ -73,8 +78,10 @@ async function backupHostFiles(ctx, blobs) {
|
|
|
73
78
|
backups.push({ relPath, blobPath, existed: true });
|
|
74
79
|
}
|
|
75
80
|
catch (err) {
|
|
81
|
+
// 文件确实存在、只是这次读不出来(占用/权限/IO):登记 existed=true + unreadable,
|
|
82
|
+
// 恢复侧据此只跳过并告警,绝不进入「删除」分支(见 HostFileBackup.unreadable)。
|
|
76
83
|
ctx.log.warn(`快照读取宿主文件失败 ${relPath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
77
|
-
backups.push({ relPath, blobPath: '', existed:
|
|
84
|
+
backups.push({ relPath, blobPath: '', existed: true, unreadable: true });
|
|
78
85
|
}
|
|
79
86
|
}
|
|
80
87
|
return backups;
|
|
@@ -90,6 +97,10 @@ const FILE_BASES = {
|
|
|
90
97
|
agentPresets: '.agent-presets',
|
|
91
98
|
agentInstructions: '', // homeDir 根(~/.dsh/AGENTS.md)
|
|
92
99
|
pluginFiles: '',
|
|
100
|
+
// 第三方插件数据:baseDir '' = home 根,ref 是**完整的 home 相对路径**
|
|
101
|
+
// (.dsh-wallpaper-engine/config.json / .mnemon/data/x.db)
|
|
102
|
+
pluginData: '',
|
|
103
|
+
pluginAssets: '',
|
|
93
104
|
sessions: 'sessions',
|
|
94
105
|
self: 'dsh-config-manager',
|
|
95
106
|
};
|
|
@@ -98,7 +109,7 @@ const FILE_BASES = {
|
|
|
98
109
|
// 其它分区语义处理」同类缺陷)。注册表新增文件类分区时若忘了在这里补,进程启动即失败。
|
|
99
110
|
for (const id of SECTION_IDS) {
|
|
100
111
|
if (isFileSection(id) && FILE_BASES[id] === undefined) {
|
|
101
|
-
throw new Error(
|
|
112
|
+
throw new Error(engineMsg('core.backup.fileSectionBaseMissing', { section: id }));
|
|
102
113
|
}
|
|
103
114
|
}
|
|
104
115
|
/**
|
|
@@ -112,8 +123,41 @@ function profileDirOf(ctx) {
|
|
|
112
123
|
const profile = ctx.profile !== undefined && ctx.profile !== '' ? ctx.profile : 'web';
|
|
113
124
|
return path.join(ctx.homeDir, 'profiles', profile);
|
|
114
125
|
}
|
|
126
|
+
/**
|
|
127
|
+
* 以**系统 home 为根**的文件类分区(ref 是系统 home 相对路径)。
|
|
128
|
+
*
|
|
129
|
+
* 为什么单独一组:`FileSystemFacade` 被限制在 $DSH_HOME 内,而第三方插件的数据常在 $DSH_HOME 之外
|
|
130
|
+
* (`~/.dsh-wallpaper-engine/`、`~/.mnemon/` …)。这两个分区的路径解析与 I/O 门面都换根,
|
|
131
|
+
* 其余分区一字不动 —— 单根不变量只在**这两个分区**上被显式放宽,且仍然只允许白名单区域。
|
|
132
|
+
*/
|
|
133
|
+
export const OS_HOME_FILE_SECTIONS = new Set(['pluginData', 'pluginAssets']);
|
|
134
|
+
/** 该分区文件目标的根(`dsh` = $DSH_HOME;`osHome` = 系统 home) */
|
|
135
|
+
export function sectionFileRoot(adapter) {
|
|
136
|
+
return OS_HOME_FILE_SECTIONS.has(adapter) ? 'osHome' : 'dsh';
|
|
137
|
+
}
|
|
138
|
+
/** 系统 home:宿主显式给就用它,否则进程 `homedir()`(`~` 的定义,不是猜) */
|
|
139
|
+
export function osHomeDirOf(ctx) {
|
|
140
|
+
return ctx.osHomeDir !== undefined && ctx.osHomeDir !== '' ? ctx.osHomeDir : homedir();
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* 该分区文件 I/O 应使用的门面。
|
|
144
|
+
*
|
|
145
|
+
* `osHome` 分区必须用 `homeFs`:缺了它**显式抛错**,绝不退回 `ctx.fs` —— 那会把
|
|
146
|
+
* `~/.mnemon/x.db` 写成 `$DSH_HOME/.mnemon/x.db`(看起来成功、实际写错位置)。
|
|
147
|
+
*/
|
|
148
|
+
export function fsForSection(ctx, adapter) {
|
|
149
|
+
if (sectionFileRoot(adapter) === 'dsh')
|
|
150
|
+
return ctx.fs;
|
|
151
|
+
if (ctx.homeFs === undefined)
|
|
152
|
+
throw new Error(engineMsg('core.backup.osHomeUnavailable', { section: adapter }));
|
|
153
|
+
return ctx.homeFs;
|
|
154
|
+
}
|
|
115
155
|
/** 解析文件类目标的绝对路径(引擎通用快照与回滚共用) */
|
|
116
156
|
export function resolveFileTarget(ctx, adapter, ref) {
|
|
157
|
+
// 系统 home 根的分区:整体换根(plugins 的两个 profile 特例与它们互不相干)
|
|
158
|
+
if (sectionFileRoot(adapter) === 'osHome') {
|
|
159
|
+
return path.join(osHomeDirOf(ctx), FILE_BASES[adapter] ?? '', ref);
|
|
160
|
+
}
|
|
117
161
|
// plugins 分区的 pnpm-workspace.yaml:位于 profiles/<profile>/ 下(非 FILE_BASES 静态基准)
|
|
118
162
|
if (adapter === 'plugins' && ref === 'pnpm-workspace.yaml') {
|
|
119
163
|
return path.join(profileDirOf(ctx), 'pnpm-workspace.yaml');
|
|
@@ -137,6 +181,9 @@ export function resolveFileTarget(ctx, adapter, ref) {
|
|
|
137
181
|
* 指纹恒为 null,crash 后 reconcile 无法证明该项是否已应用(保守但可避免的精度损失)。
|
|
138
182
|
*/
|
|
139
183
|
export function resolveFileTargetRel(adapter, ref, profile) {
|
|
184
|
+
// 系统 home 根的分区:ref 本身就是「根相对路径」,原样归一即可
|
|
185
|
+
if (sectionFileRoot(adapter) === 'osHome')
|
|
186
|
+
return normalizePath(ref);
|
|
140
187
|
if (adapter === 'plugins') {
|
|
141
188
|
const profileDir = `profiles/${profile !== undefined && profile !== '' ? profile : 'web'}`;
|
|
142
189
|
if (ref === 'pnpm-workspace.yaml')
|
|
@@ -237,13 +284,17 @@ async function engineSnapshotEntry(ctx, target) {
|
|
|
237
284
|
case 'agentPresets':
|
|
238
285
|
case 'agentInstructions':
|
|
239
286
|
case 'pluginFiles':
|
|
287
|
+
case 'pluginData':
|
|
288
|
+
case 'pluginAssets':
|
|
240
289
|
case 'sessions':
|
|
241
290
|
case 'self': {
|
|
291
|
+
// 门面按分区选:pluginData / pluginAssets 在系统 home 根(见 fsForSection 的说明)
|
|
292
|
+
const fsFacade = fsForSection(ctx, target.adapter);
|
|
242
293
|
const abs = resolveFileTarget(ctx, target.adapter, target.ref);
|
|
243
|
-
if (!(await
|
|
294
|
+
if (!(await fsFacade.exists(abs))) {
|
|
244
295
|
return { kind: 'file', adapter: target.adapter, ref: target.ref, before: null, existed: false };
|
|
245
296
|
}
|
|
246
|
-
const data = await
|
|
297
|
+
const data = await fsFacade.readFile(abs);
|
|
247
298
|
// 文件字节不放进 SnapshotEntry(契约纯净),由 createSnapshot 收集进 blobs Map 统一落盘
|
|
248
299
|
return {
|
|
249
300
|
kind: 'file',
|
|
@@ -257,7 +308,7 @@ async function engineSnapshotEntry(ctx, target) {
|
|
|
257
308
|
case 'secrets':
|
|
258
309
|
// secrets 已注册但**没有 adapter、也没有 ZIP 内分区 JSON**(凭据值走独立加密容器 secrets.enc)。
|
|
259
310
|
// 任何计划项都不应以它为快照目标 —— 显式报错,而不是落到 default 分支。
|
|
260
|
-
throw new Error('
|
|
311
|
+
throw new Error(engineMsg('core.backup.secretsSectionNotSnapshotTarget'));
|
|
261
312
|
default: {
|
|
262
313
|
// 编译期穷举断言:SectionId 的每一项都已被上面的 case 处理 → 此处 target.adapter 收窄为 never。
|
|
263
314
|
// 新增分区若忘了在这里实现快照语义,tsc 会直接报错(不再依赖人肉核对清单)。
|
|
@@ -265,8 +316,7 @@ async function engineSnapshotEntry(ctx, target) {
|
|
|
265
316
|
// 运行期(只可能来自运行期强转:被篡改的 plan / 旧 journal 反序列化):**显式报错**。
|
|
266
317
|
// 旧实现在这里静默返回 settingsNamespace 条目(连 existed 都没有)→ 回滚把该目标当「原本不存在」
|
|
267
318
|
// 而不做任何补偿:该分区的写入永远撤不掉,报告却仍说「已快照、可回滚」(审计 core-flow#F-10)。
|
|
268
|
-
throw new Error(
|
|
269
|
-
+ '拒绝按其它分区语义静默记录(那会让该分区的写入无法回滚)');
|
|
319
|
+
throw new Error(engineMsg('core.backup.sectionUnsupported', { section: String(unhandled) }));
|
|
270
320
|
}
|
|
271
321
|
}
|
|
272
322
|
}
|
|
@@ -330,7 +380,7 @@ export async function createSnapshot(opts) {
|
|
|
330
380
|
if (entry.kind === 'file' && entry.copiedTo && entry.existed) {
|
|
331
381
|
const abs = resolveFileTarget(ctx, adapterId, target.ref);
|
|
332
382
|
// 与改动前**完全相同的读调用**,只是延后到被消费时执行(路径语义与异常传播不变)
|
|
333
|
-
lazyBlobSources.push({ blobPath: entry.copiedTo, read: () => ctx.
|
|
383
|
+
lazyBlobSources.push({ blobPath: entry.copiedTo, read: () => fsForSection(ctx, adapterId).readFile(abs) });
|
|
334
384
|
}
|
|
335
385
|
entries.push(entry);
|
|
336
386
|
}
|
|
@@ -405,7 +455,7 @@ export class FileSnapshotStore {
|
|
|
405
455
|
async writeBlob(dir, blobPath, data, hashes) {
|
|
406
456
|
const target = path.join(dir, blobPath);
|
|
407
457
|
if (!target.startsWith(dir))
|
|
408
|
-
throw new Error(
|
|
458
|
+
throw new Error(engineMsg('core.backup.blobPathEscaped', { path: blobPath }));
|
|
409
459
|
await fs.mkdir(path.dirname(target), { recursive: true });
|
|
410
460
|
await atomicWriteFile(target, data);
|
|
411
461
|
hashes[blobPath] = sha256Hex(data);
|
|
@@ -429,7 +479,7 @@ export class FileSnapshotStore {
|
|
|
429
479
|
// 4) 验证(从磁盘重读,不信任内存对象)
|
|
430
480
|
const verified = await verifySnapshot(this.options.dir, snapshot.id);
|
|
431
481
|
if (!verified.ok) {
|
|
432
|
-
throw new Error(
|
|
482
|
+
throw new Error(engineMsg('core.backup.snapshotVerifyFailed', { id: snapshot.id, reason: verified.reason ?? '' }));
|
|
433
483
|
}
|
|
434
484
|
// 5) 原子发布 READY(重写 snapshot.json readiness='READY';metadataHash 稳定不受影响)
|
|
435
485
|
const ready = { ...snapshot, readiness: 'READY' };
|
|
@@ -439,7 +489,9 @@ export class FileSnapshotStore {
|
|
|
439
489
|
await this.prune();
|
|
440
490
|
}
|
|
441
491
|
catch (err) {
|
|
442
|
-
// 快照已 READY + verified;prune 失败(EBUSY/EPERM)不应把刚成功的快照判为 unusable
|
|
492
|
+
// 快照已 READY + verified;prune 失败(EBUSY/EPERM)不应把刚成功的快照判为 unusable ——
|
|
493
|
+
// 但必须留痕:保留清理长期失败会让快照无限堆积,用户与日志都看不到线索。
|
|
494
|
+
this.options.onWarn?.(engineMsg('core.backup.pruneFailed', { reason: err instanceof Error ? err.message : String(err) }));
|
|
443
495
|
}
|
|
444
496
|
return snapshot.id;
|
|
445
497
|
}
|
|
@@ -480,7 +532,19 @@ export class FileSnapshotStore {
|
|
|
480
532
|
async prune() {
|
|
481
533
|
const dir = this.options.dir;
|
|
482
534
|
const entries = await fs.readdir(dir, { withFileTypes: true }).catch(() => []);
|
|
483
|
-
|
|
535
|
+
// F3:被未收敛 journal 引用的快照**绝不自动清理**。判据是「读不到 ≠ 没有引用」——
|
|
536
|
+
// 引用提供者抛错时**放弃本轮清理**,绝不按空集合继续删:一次瞬时 IO 错就可能删掉
|
|
537
|
+
// 事故恢复唯一的回滚点(那是 SAFE MODE 的唯一证据)。此前这里是 `.catch(() => new Set())`。
|
|
538
|
+
let referenced = new Set();
|
|
539
|
+
if (this.options.referencedSnapshotIds !== undefined) {
|
|
540
|
+
try {
|
|
541
|
+
referenced = await this.options.referencedSnapshotIds();
|
|
542
|
+
}
|
|
543
|
+
catch (err) {
|
|
544
|
+
this.options.onWarn?.(engineMsg('core.backup.pruneAbandoned', { reason: err instanceof Error ? err.message : String(err) }));
|
|
545
|
+
return;
|
|
546
|
+
}
|
|
547
|
+
}
|
|
484
548
|
// m-retention:策略读取失败 → 回退缺省(绝不因策略提供者异常而误删/崩溃)
|
|
485
549
|
const policy = await Promise.resolve()
|
|
486
550
|
.then(() => this.options.retentionPolicy?.() ?? defaultRetentionPolicyLike())
|
|
@@ -494,11 +558,18 @@ export class FileSnapshotStore {
|
|
|
494
558
|
const parsed = parseJsonSafe(await fs.readFile(path.join(dir, entry.name, 'snapshot.json'), 'utf8'));
|
|
495
559
|
if (typeof parsed.id !== 'string' || parsed.id === '' || typeof parsed.createdAt !== 'string')
|
|
496
560
|
continue;
|
|
561
|
+
// 删除目标只能是**目录名**(下面 path.join(dir, id) 就是按它删的)。目录名与内部 id 不一致时
|
|
562
|
+
// **绝不猜**:pinned/被引用的判断是对着这份 snapshot.json 做的,删除却会打在另一个目录上 ——
|
|
563
|
+
// 跳过并留痕(手工改名/半截拷贝残留会出现这种形态)。
|
|
564
|
+
if (parsed.id !== entry.name) {
|
|
565
|
+
this.options.onWarn?.(engineMsg('core.backup.dirNameIdMismatch', { dir: entry.name, id: parsed.id }));
|
|
566
|
+
continue;
|
|
567
|
+
}
|
|
497
568
|
if (parsed.pinned === true)
|
|
498
569
|
continue; // 置顶快照豁免自动清理
|
|
499
|
-
if (referenced.has(
|
|
570
|
+
if (referenced.has(entry.name))
|
|
500
571
|
continue; // Phase 4 F3:recovery 引用豁免
|
|
501
|
-
metas.push({ id:
|
|
572
|
+
metas.push({ id: entry.name, createdAt: parsed.createdAt });
|
|
502
573
|
}
|
|
503
574
|
catch {
|
|
504
575
|
// 损坏 / 非快照目录:跳过(与 listSnapshots 语义一致)
|
|
@@ -528,7 +599,7 @@ export class FileSnapshotStore {
|
|
|
528
599
|
const target = path.join(this.snapshotDir(id), blobPath);
|
|
529
600
|
const dir = this.options.dir;
|
|
530
601
|
if (!target.startsWith(dir))
|
|
531
|
-
throw new Error(
|
|
602
|
+
throw new Error(engineMsg('core.backup.blobPathEscaped', { path: blobPath }));
|
|
532
603
|
return fs.readFile(target);
|
|
533
604
|
}
|
|
534
605
|
/** 标记快照生命周期状态:重写 <dir>/<id>/snapshot.json(保留其余字段)。 */
|
|
@@ -559,55 +630,55 @@ export function computeMetadataHash(snapshot) {
|
|
|
559
630
|
export async function verifySnapshot(snapshotsDir, id) {
|
|
560
631
|
const dir = path.join(snapshotsDir, id);
|
|
561
632
|
if (!dir.startsWith(path.resolve(snapshotsDir) + path.sep)) {
|
|
562
|
-
return { ok: false, reason: '
|
|
633
|
+
return { ok: false, reason: engineMsg('core.backup.snapshotPathEscaped') };
|
|
563
634
|
}
|
|
564
635
|
let raw;
|
|
565
636
|
try {
|
|
566
637
|
raw = await fs.readFile(path.join(dir, 'snapshot.json'), 'utf8');
|
|
567
638
|
}
|
|
568
639
|
catch {
|
|
569
|
-
return { ok: false, reason: '
|
|
640
|
+
return { ok: false, reason: engineMsg('core.backup.snapshotJsonMissing') };
|
|
570
641
|
}
|
|
571
642
|
const snapshot = parseJsonSafe(raw);
|
|
572
643
|
if (snapshot === null || typeof snapshot !== 'object' || typeof snapshot.id !== 'string' || snapshot.id === '') {
|
|
573
|
-
return { ok: false, reason: '
|
|
644
|
+
return { ok: false, reason: engineMsg('core.backup.snapshotJsonInvalid') };
|
|
574
645
|
}
|
|
575
646
|
if (snapshot.id !== id) {
|
|
576
|
-
return { ok: false, reason: '
|
|
647
|
+
return { ok: false, reason: engineMsg('core.backup.snapshotIdDirMismatch') };
|
|
577
648
|
}
|
|
578
649
|
let manifestRaw;
|
|
579
650
|
try {
|
|
580
651
|
manifestRaw = await fs.readFile(path.join(dir, 'manifest.json'), 'utf8');
|
|
581
652
|
}
|
|
582
653
|
catch {
|
|
583
|
-
return { ok: false, reason: '
|
|
654
|
+
return { ok: false, reason: engineMsg('core.backup.manifestMissing') };
|
|
584
655
|
}
|
|
585
656
|
const manifest = parseJsonSafe(manifestRaw);
|
|
586
657
|
if (manifest === null || typeof manifest !== 'object' || manifest.snapshotId !== id) {
|
|
587
|
-
return { ok: false, reason: '
|
|
658
|
+
return { ok: false, reason: engineMsg('core.backup.manifestInvalid') };
|
|
588
659
|
}
|
|
589
660
|
// entryCount 匹配(manifest 声明条目数与实际一致,防计数篡改)
|
|
590
661
|
if (typeof manifest.entryCount !== 'number' || manifest.entryCount !== snapshot.entries.length) {
|
|
591
|
-
return { ok: false, reason: '
|
|
662
|
+
return { ok: false, reason: engineMsg('core.backup.entryCountMismatch') };
|
|
592
663
|
}
|
|
593
664
|
// metadataHash 匹配(破坏性内容)
|
|
594
665
|
if (computeMetadataHash(snapshot) !== manifest.metadataHash) {
|
|
595
|
-
return { ok: false, reason: '
|
|
666
|
+
return { ok: false, reason: engineMsg('core.backup.metadataHashMismatch') };
|
|
596
667
|
}
|
|
597
668
|
// blob hashes 匹配 + 存在
|
|
598
669
|
for (const [blobPath, expectedHash] of Object.entries(manifest.blobHashes)) {
|
|
599
670
|
const target = path.join(dir, blobPath);
|
|
600
671
|
if (!target.startsWith(dir))
|
|
601
|
-
return { ok: false, reason:
|
|
672
|
+
return { ok: false, reason: engineMsg('core.backup.blobPathEscaped', { path: blobPath }) };
|
|
602
673
|
let data;
|
|
603
674
|
try {
|
|
604
675
|
data = await fs.readFile(target);
|
|
605
676
|
}
|
|
606
677
|
catch {
|
|
607
|
-
return { ok: false, reason:
|
|
678
|
+
return { ok: false, reason: engineMsg('core.backup.blobMissing', { path: blobPath }) };
|
|
608
679
|
}
|
|
609
680
|
if (sha256Hex(data) !== expectedHash) {
|
|
610
|
-
return { ok: false, reason:
|
|
681
|
+
return { ok: false, reason: engineMsg('core.backup.blobHashMismatch', { path: blobPath }) };
|
|
611
682
|
}
|
|
612
683
|
}
|
|
613
684
|
// Review C P2:snapshot.json 引用的每个 blob(entries.copiedTo / hostFileBackups.blobPath)都必须被 manifest 覆盖,
|
|
@@ -623,7 +694,7 @@ export async function verifySnapshot(snapshotsDir, id) {
|
|
|
623
694
|
}
|
|
624
695
|
for (const relBlob of referencedBlobs) {
|
|
625
696
|
if (!(relBlob in manifest.blobHashes)) {
|
|
626
|
-
return { ok: false, reason:
|
|
697
|
+
return { ok: false, reason: engineMsg('core.backup.blobNotInManifest', { blob: relBlob }) };
|
|
627
698
|
}
|
|
628
699
|
}
|
|
629
700
|
return { ok: true };
|
package/lib/core/boot-rescue.js
CHANGED
|
@@ -34,6 +34,7 @@ import fs from 'node:fs/promises';
|
|
|
34
34
|
import path from 'node:path';
|
|
35
35
|
import { atomicWriteFile } from '../utils/atomic-write.js';
|
|
36
36
|
import { sha256Hex } from '../utils/hashing.js';
|
|
37
|
+
import { engineMsg } from './messages-core.js';
|
|
37
38
|
/* ---------------------------------------------------------------- 常量 */
|
|
38
39
|
/** 默认备份目录(相对 homeDir 的 posix 路径;backupDir 缺省时使用) */
|
|
39
40
|
const DEFAULT_BACKUP_DIR_REL = 'dsh-config-manager/transactions/rescue-backups';
|
|
@@ -340,13 +341,13 @@ async function readRescueState(backupDir) {
|
|
|
340
341
|
/** 救援模式 profile patch 的头注释(说明本文件被谁接管、bundle 层的实际状态)。 */
|
|
341
342
|
const RESCUE_PATCH_HEADER_LINES = [
|
|
342
343
|
'# dsh-config-manager RESCUE MODE',
|
|
343
|
-
'
|
|
344
|
+
engineMsg('core.bootRescue.patchTakenOver'),
|
|
344
345
|
];
|
|
345
346
|
/** 救援模式 profile patch 的头注释(按 bundle 处理方式给出准确说明,不得夸大能力)。 */
|
|
346
347
|
function rescuePatchHeader(bundlesDisabled) {
|
|
347
348
|
const note = bundlesDisabled
|
|
348
|
-
? '
|
|
349
|
-
: '
|
|
349
|
+
? engineMsg('core.bootRescue.bundlesNarrowed')
|
|
350
|
+
: engineMsg('core.bootRescue.bundlesNote');
|
|
350
351
|
return [...RESCUE_PATCH_HEADER_LINES, note, ''].join('\n');
|
|
351
352
|
}
|
|
352
353
|
/** 单引号 YAML 标量(按 YAML 规则把 `'` 转义为 `''`)。 */
|
package/lib/core/boot-safety.js
CHANGED
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
import { RESCUE_KEEP_BUNDLE_PREFIXES, computeSafeBundles } from './boot-rescue.js';
|
|
22
22
|
import { BOOT_CRITICAL_RELS, profileCriticalRels } from './boot-paths.js';
|
|
23
23
|
import { zhMsg } from './messages.js';
|
|
24
|
+
import { engineMsg } from './messages-core.js';
|
|
24
25
|
/** 可解析性检查的目标(.json → JSON.parse;.yaml/.yml → 注入的 parseYaml)。 */
|
|
25
26
|
function parseKind(rel) {
|
|
26
27
|
if (rel.endsWith('.json'))
|
|
@@ -118,7 +119,7 @@ export async function auditBootSafety(deps) {
|
|
|
118
119
|
const drop = new Set(prunable.map((p) => p.name));
|
|
119
120
|
const next = (Array.isArray(bundles) ? bundles : []).filter((b) => !(typeof b === 'string' && drop.has(b)));
|
|
120
121
|
if (!writeBundles(pkg, next))
|
|
121
|
-
throw new Error('
|
|
122
|
+
throw new Error(engineMsg('core.bootSafety.bundlesNotArray'));
|
|
122
123
|
await deps.writeText(pkgRel, JSON.stringify(pkg, null, 2) + '\n');
|
|
123
124
|
written = true;
|
|
124
125
|
}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 宿主斜杠命令 /rollback(别名 /rw)—— 不打开设置面板也能回退(对齐 dsh-rewind 的 /rewind、/undo)。
|
|
3
|
+
*
|
|
4
|
+
* 语义(用户 2026-10-12 确认方案 A):
|
|
5
|
+
* /rollback → 当前**会话**的可回退点列表(回合号 / 时间 / 类型 / 状态 / 可回退性)
|
|
6
|
+
* /rollback <id> → 回退到该点,走**与 HTTP 路由同源**的那道写入门:
|
|
7
|
+
* runWithMutationLock(= SAFE MODE 门 + 跨进程/跨 kind 环境锁)
|
|
8
|
+
* → 引擎 preview(零写入)→ 引擎 rewind(userConfirmed=true)
|
|
9
|
+
* 引擎回的 confirmation-required / protected-point / record-incomplete 一律翻成可读错误结果,
|
|
10
|
+
* **绝不绕过**(命令入口没有「确认按钮」,用户敲下 id 就是那次确认;其余门照旧全量生效)。
|
|
11
|
+
*
|
|
12
|
+
* 纪律(与 src/core/model-tools.ts 的 registerModelTools 同口径):
|
|
13
|
+
* · 命令服务**惰取**:一律 ctx.get('commands'),绝不属性访问 ctx.commands、绝不放进 inject ——
|
|
14
|
+
* 未组合该服务的部署必须照常启动(命令是可选能力,不是硬依赖);
|
|
15
|
+
* **读的时机是装配期一次**(见 registerRollbackCommand):Cordis 的 ctx.get 在 apply 期只能拿到
|
|
16
|
+
* **已 apply** 的服务,所以 `commands` 行晚于本插件时 `/rollback` 不会注册(只 warn 一行、无重试入口)。
|
|
17
|
+
* DSH 内置插件与本插件的实际 apply 顺序**未坐实**(asar 不可直接读、本机 profile 无相关行);
|
|
18
|
+
* 要彻底消除这个窗口需要 `ctx.inject(['commands'])` 式的「服务就绪再注册」回调 —— 未做,如实记在这里。
|
|
19
|
+
* · 注册失败只 warn:命令挂不上不得影响其它装配(宿主启动 = blocker);
|
|
20
|
+
* · 用户可见文案一律走宿主字典 engineMsg(key: command.rollback.*),**取文案那一刻**解析语言;
|
|
21
|
+
* · 本模块不 import @deepseek-ai/dsh-commands 的运行时值(只做结构化窄化),core 保持与 DSH 解耦。
|
|
22
|
+
*/
|
|
23
|
+
import type { MutationLockPort } from '../utils/env-lock.ts';
|
|
24
|
+
import type { RollbackPointSummary, RollbackRewindResult } from './rollback-point.ts';
|
|
25
|
+
/**
|
|
26
|
+
* 命令结果(与 @deepseek-ai/dsh-commands 的 `CommandResult` **逐字同形**)。
|
|
27
|
+
*
|
|
28
|
+
* 为什么在这里自己声明而不是 import 那门包的类型:`src/core/**` 的架构边界(tests/architecture-boundaries.test.ts
|
|
29
|
+
* 的 core allow 表)只允许 node 内置 / core 内部 / schema / utils / security / migrations / js-yaml ——
|
|
30
|
+
* `@deepseek-ai/*` 仅允许出现在 `src/index.ts` 与 `src/client/`。core 必须保持与 DSH 解耦,
|
|
31
|
+
* 所以这里做**结构化窄化**;两边一旦漂移,`command-rollback.test.ts` 的编译期断言会红。
|
|
32
|
+
*/
|
|
33
|
+
export type CommandResult = {
|
|
34
|
+
readonly kind: 'success';
|
|
35
|
+
readonly text?: string;
|
|
36
|
+
readonly sourceEventSeq?: number;
|
|
37
|
+
} | {
|
|
38
|
+
readonly kind: 'error';
|
|
39
|
+
readonly text: string;
|
|
40
|
+
};
|
|
41
|
+
/** 命令名(无前导斜杠)与别名。 */
|
|
42
|
+
export declare const ROLLBACK_COMMAND_NAME = "rollback";
|
|
43
|
+
export declare const ROLLBACK_COMMAND_ALIAS = "rw";
|
|
44
|
+
/**
|
|
45
|
+
* 命令定义 id:/rw 别名是**第二条定义** —— 同一个 definitionId(客户端按 definitionId 做身份化行为,
|
|
46
|
+
* 别名与主名必须被视作同一条命令)。
|
|
47
|
+
*/
|
|
48
|
+
export declare const ROLLBACK_COMMAND_DEFINITION_ID = "dsh-config-manager/rollback";
|
|
49
|
+
/** 命令服务惰取回调(调用期读 ctx.get('commands');未组合返回 undefined)。 */
|
|
50
|
+
export type CommandServiceReader = () => CommandServiceLike | undefined | null;
|
|
51
|
+
/** 一条已注册命令的最小形状(避免把 DSH 类型变成本插件的硬依赖)。 */
|
|
52
|
+
export interface CommandDefinitionLike {
|
|
53
|
+
readonly name: string;
|
|
54
|
+
readonly description: string;
|
|
55
|
+
readonly definitionId?: string;
|
|
56
|
+
readonly input?: {
|
|
57
|
+
readonly hint: string;
|
|
58
|
+
readonly attachments?: boolean;
|
|
59
|
+
};
|
|
60
|
+
readonly handler: (invocation: CommandInvocationLike) => CommandResult | Promise<CommandResult>;
|
|
61
|
+
}
|
|
62
|
+
/** 一次命令调用的最小形状(只读用到的字段)。 */
|
|
63
|
+
export interface CommandInvocationLike {
|
|
64
|
+
readonly agent: unknown;
|
|
65
|
+
/** 命令名之后的原文(含分隔空白)。 */
|
|
66
|
+
readonly rawInput: string;
|
|
67
|
+
/** 派发 UI 持有的取消信号(透传给回退执行)。 */
|
|
68
|
+
readonly signal?: AbortSignal;
|
|
69
|
+
}
|
|
70
|
+
/** 命令服务的最小形状(ctx.get('commands') 的结构化窄化)。 */
|
|
71
|
+
export interface CommandServiceLike {
|
|
72
|
+
register(definition: CommandDefinitionLike): unknown;
|
|
73
|
+
}
|
|
74
|
+
/** 宿主提供的回退能力(由 src/index.ts 装配;测试用假的实现即可)。 */
|
|
75
|
+
export interface RollbackCommandHost {
|
|
76
|
+
/** 当前 agent 所属会话(拿不准则 ok:false —— 绝不猜)。 */
|
|
77
|
+
agentSession(agent: unknown): AgentSessionFacts;
|
|
78
|
+
/** 列出该会话的回退点(含存储栈状态;只读)。 */
|
|
79
|
+
list(sessionId: string): Promise<RollbackCommandListResult>;
|
|
80
|
+
/**
|
|
81
|
+
* 执行回退:runWithMutationLock → SAFE MODE → 引擎 preview → 引擎 rewind。
|
|
82
|
+
*
|
|
83
|
+
* 被 SAFE MODE / 环境锁挡住时**抛 EnvironmentLockUnavailableError**(与 HTTP 路由同口径),
|
|
84
|
+
* 由本模块翻成可读错误结果。
|
|
85
|
+
*/
|
|
86
|
+
rewind(input: RollbackRewindExecution): Promise<RollbackRewindResult>;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* 回退执行入参。
|
|
90
|
+
*
|
|
91
|
+
* 只带 id + 取消信号:引擎 `rewind({ id, userConfirmed: true })` 的**写入面**已经由该点的记录
|
|
92
|
+
* 决定(不是调用方临时指定路径),多传会话/分块只会制造「两处口径」;会话事实只供只读列表过滤用。
|
|
93
|
+
*/
|
|
94
|
+
export interface RollbackRewindExecution {
|
|
95
|
+
readonly id: string;
|
|
96
|
+
readonly signal?: AbortSignal;
|
|
97
|
+
}
|
|
98
|
+
/** 列表结果(引擎 RollbackListResult 的可测子集)。 */
|
|
99
|
+
export interface RollbackCommandListResult {
|
|
100
|
+
ok: boolean;
|
|
101
|
+
storage: {
|
|
102
|
+
available: boolean;
|
|
103
|
+
root: string;
|
|
104
|
+
detail?: string;
|
|
105
|
+
};
|
|
106
|
+
points: readonly RollbackPointSummary[];
|
|
107
|
+
error?: string;
|
|
108
|
+
}
|
|
109
|
+
/** 当前 agent 的会话事实(宽进严出:形状不符即 ok:false)。 */
|
|
110
|
+
export type AgentSessionFacts = {
|
|
111
|
+
ok: true;
|
|
112
|
+
sessionId: string;
|
|
113
|
+
chunks: readonly string[];
|
|
114
|
+
} | {
|
|
115
|
+
ok: false;
|
|
116
|
+
detail: string;
|
|
117
|
+
};
|
|
118
|
+
/** 绝对路径判定(win32 盘符 + POSIX 前缀 + UNC;纯函数,不引 node:path)。 */
|
|
119
|
+
export declare function isAbsolutePath(value: string): boolean;
|
|
120
|
+
/**
|
|
121
|
+
* 从 agent 窄化会话事实。
|
|
122
|
+
*
|
|
123
|
+
* **不猜**:agent.session.id 读不出来就退回 agent.id(DSH 的 Agent.id 就是会话 id);
|
|
124
|
+
* 两者都读不出 → ok:false(命令如实报「拿不到当前会话」,而不是列全网的点)。
|
|
125
|
+
* 工作区分块只认**绝对路径**的 cwd(与 rollbackSessionBindingOf 同一口径)。
|
|
126
|
+
*/
|
|
127
|
+
export declare function agentSessionFactsOf(agent: unknown): AgentSessionFacts;
|
|
128
|
+
/** 解析结果:空输入 = 只读列表;单个 token = 目标点 id;其余 = invalid。 */
|
|
129
|
+
export type RollbackCommandInput = {
|
|
130
|
+
kind: 'list';
|
|
131
|
+
} | {
|
|
132
|
+
kind: 'rewind';
|
|
133
|
+
id: string;
|
|
134
|
+
} | {
|
|
135
|
+
kind: 'invalid';
|
|
136
|
+
detail: string;
|
|
137
|
+
};
|
|
138
|
+
/**
|
|
139
|
+
* 解析 rawInput(命令名之后的原文,含分隔空白)。
|
|
140
|
+
* 语法只有两种:空 = 列表;单个非空 token = 目标 id。多 token 一律 invalid。
|
|
141
|
+
*/
|
|
142
|
+
export declare function parseRollbackCommand(rawInput: string): RollbackCommandInput;
|
|
143
|
+
/** 渲染只读列表(本会话没有点时给「本会话没有」,绝不冒充「全库为空」)。 */
|
|
144
|
+
export declare function renderRollbackList(result: RollbackCommandListResult, sessionId: string): CommandResult;
|
|
145
|
+
/** 把引擎结果翻成命令结果(失败一律带机器码 + 技术细节;processed 的段事实不隐藏)。 */
|
|
146
|
+
export declare function renderRewindResult(result: RollbackRewindResult, id: string): CommandResult;
|
|
147
|
+
/** 命令依赖(index.ts 装配;测试直接构造)。 */
|
|
148
|
+
export interface RollbackCommandDeps {
|
|
149
|
+
readonly host: RollbackCommandHost;
|
|
150
|
+
/** mutation lock 端口(index.ts 传 host.mutationLock;测试可传 undefined 或假端口)。 */
|
|
151
|
+
readonly mutationLock?: MutationLockPort;
|
|
152
|
+
/** SAFE MODE 同步谓词(与 HTTP 路由同一份 host.safeModeIsBlocked)。 */
|
|
153
|
+
readonly isBlocked?: () => boolean;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* 构造命令处理器(**纯编排**:不注册、不读 ctx)。
|
|
157
|
+
*
|
|
158
|
+
* 带参数时:parser → agentSession(只用于「这次是不是列表」的会话判定)→ host.rewind。
|
|
159
|
+
* host.rewind 内部才是那道门:runWithMutationLock → SAFE MODE → 引擎 preview → 引擎 rewind
|
|
160
|
+
* EnvironmentLockUnavailableError(SAFE MODE / 环境锁被挡)在此翻成可读错误结果。
|
|
161
|
+
* 任何未预期异常也翻成错误结果 —— 命令处理器抛异常会让整条命令生命周期报泛化失败,
|
|
162
|
+
* UI 只看到一句「失败」;这里如实把原因说清楚。
|
|
163
|
+
*/
|
|
164
|
+
export declare function makeRollbackCommandHandler(deps: RollbackCommandDeps): (invocation: CommandInvocationLike) => Promise<CommandResult>;
|
|
165
|
+
/** 注册所需的依赖面(命令服务惰取回调 + 宿主日志 + 命令依赖)。 */
|
|
166
|
+
export interface RollbackCommandRegistrationOptions {
|
|
167
|
+
/** 惰取命令服务:实现必须是「调用期」的 ctx.get('commands')(绝不属性访问、绝不进 inject)。 */
|
|
168
|
+
readonly readCommands: CommandServiceReader;
|
|
169
|
+
readonly deps: RollbackCommandDeps;
|
|
170
|
+
/** 只 warn:注册失败/服务缺失不得影响其它装配。 */
|
|
171
|
+
readonly log?: {
|
|
172
|
+
warn(message: string, meta?: unknown): void;
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* 注册 /rollback 与 /rw(两条定义 = 同一 definitionId)。
|
|
177
|
+
*
|
|
178
|
+
* 服务缺失 → warn 后**安静返回**(未组合 commands 的部署照常启动、不抛错);
|
|
179
|
+
* 服务存在但 register 抛错 → warn(其余装配不受影响)。
|
|
180
|
+
* @returns 反注册函数(重复调用安全)。
|
|
181
|
+
*/
|
|
182
|
+
export declare function registerRollbackCommand(options: RollbackCommandRegistrationOptions): () => void;
|