dsh-config-manager 0.1.71 → 0.1.72
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +111 -96
- package/README.zh-CN.md +207 -86
- package/lib/adapters/agent-instructions.js +1 -1
- package/lib/adapters/file-collection.js +1 -1
- package/lib/adapters/index.d.ts +1 -1
- package/lib/adapters/index.js +1 -1
- package/lib/adapters/link-report.d.ts +1 -0
- package/lib/adapters/link-report.js +27 -3
- package/lib/adapters/messages.d.ts +18 -0
- package/lib/adapters/messages.js +50 -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 +7 -1
- package/lib/adapters/self.js +1 -1
- package/lib/adapters/sessions.d.ts +1 -0
- package/lib/adapters/sessions.js +4 -2
- 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 +1349 -490
- package/lib/client.js +43047 -36738
- package/lib/core/backup-plan.js +30 -17
- package/lib/core/backup-verify.js +24 -23
- package/lib/core/backup.d.ts +5 -0
- package/lib/core/backup.js +51 -25
- package/lib/core/boot-rescue.js +4 -3
- package/lib/core/boot-safety.js +2 -1
- package/lib/core/command-rollback.d.ts +178 -0
- package/lib/core/command-rollback.js +289 -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 +292 -0
- package/lib/core/messages-core.js +615 -0
- package/lib/core/messages.d.ts +60 -2
- package/lib/core/messages.js +116 -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/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-ignore-rules.d.ts +31 -0
- package/lib/core/rollback-ignore-rules.js +140 -0
- package/lib/core/rollback-point.d.ts +1208 -0
- package/lib/core/rollback-point.js +2993 -0
- 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 +18 -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.js +7 -6
- 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 +223 -7
- package/lib/index.js +729 -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 +42 -0
- package/lib/routes/messages.js +100 -0
- package/lib/routes/profiles.js +12 -6
- package/lib/routes/recovery.js +378 -114
- package/lib/routes/snapshots.js +51 -4
- package/lib/routes/sync.js +68 -16
- 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 +41 -14
- package/lib/schema/section-registry.js +35 -29
- 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 +37 -15
- package/lib/sync/sync-engine.js +67 -37
- 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/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/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 +24 -20
- package/lib/ui/foreign-view.d.ts +5 -1
- package/lib/ui/foreign-view.js +26 -2
- package/lib/ui/i18n.d.ts +65 -0
- package/lib/ui/i18n.js +114 -0
- 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 +756 -0
- package/lib/ui/rollback-view.js +724 -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/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 +1 -1
- package/src/adapters/link-report.test.ts +22 -2
- package/src/adapters/link-report.ts +29 -4
- package/src/adapters/messages.ts +54 -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 +8 -2
- package/src/adapters/sessions.ts +4 -2
- package/src/adapters/settings.test.ts +38 -1
- 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 +391 -0
- package/src/client/chat/RollbackDiffTab.tsx +61 -0
- package/src/client/chat/RollbackFileIcon.tsx +96 -0
- package/src/client/chat/RollbackReviewDialog.tsx +591 -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 +173 -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 +164 -0
- package/src/client/chat/rollback-entry-view.test.ts +1469 -0
- package/src/client/chat/rollback-entry-view.ts +941 -0
- package/src/client/chat/sidebar-slots.ts +37 -0
- package/src/client/common/ConfirmDialog.tsx +7 -3
- package/src/client/common/ContentPicker.tsx +48 -10
- package/src/client/common/DiffTable.tsx +97 -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 +78 -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 +28 -0
- package/src/client/common/ui.tsx +73 -10
- package/src/client/config-manager.module.css +996 -21
- 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 +186 -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 +54 -5
- 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 +665 -0
- package/src/client/recovery/RollbackDiffPane.tsx +58 -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 +116 -6
- package/src/client/recovery/recovery-locales.test.ts +299 -0
- package/src/client/recovery/recovery-locales.ts +488 -149
- package/src/client/recovery/recovery-panel-layout.test.ts +132 -2
- package/src/client/recovery/rollback-file-changes.test.ts +73 -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/backup-plan.test.ts +28 -0
- package/src/core/backup-plan.ts +29 -17
- package/src/core/backup-verify.ts +24 -23
- package/src/core/backup.test.ts +76 -0
- package/src/core/backup.ts +55 -25
- 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 +402 -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 +657 -0
- package/src/core/messages.ts +123 -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/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-ignore-rules.ts +126 -0
- package/src/core/rollback-point.test.ts +2603 -0
- package/src/core/rollback-point.ts +3755 -0
- 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 +18 -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.ts +7 -6
- 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 +813 -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 +104 -0
- package/src/routes/profiles.ts +12 -6
- package/src/routes/recovery.test.ts +405 -1
- package/src/routes/recovery.ts +377 -109
- package/src/routes/snapshots.ts +51 -4
- package/src/routes/sync.ts +67 -16
- package/src/schema/config.ts +53 -20
- package/src/schema/index.ts +7 -5
- package/src/schema/manifest.ts +19 -18
- package/src/schema/messages.ts +120 -0
- package/src/schema/registry.test.ts +19 -5
- package/src/schema/section-registry.ts +64 -39
- 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-engine-download-tombstone.test.ts +1 -1
- package/src/sync/sync-engine.test.ts +226 -8
- package/src/sync/sync-engine.ts +103 -41
- 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/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/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 +24 -20
- package/src/ui/foreign-view.test.ts +43 -0
- package/src/ui/foreign-view.ts +30 -3
- package/src/ui/i18n.ts +125 -0
- 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 +819 -0
- package/src/ui/rollback-view.ts +1339 -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/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
|
@@ -0,0 +1,536 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 宿主斜杠命令 /rollback 单测(t3)。
|
|
3
|
+
*
|
|
4
|
+
* 覆盖验收四件:
|
|
5
|
+
* 1. 未组合命令服务 → 不注册、不抛错(注册失败只 warn);
|
|
6
|
+
* 2. 无参数 → 只读列表(不碰 mutation lock / 不调 rewind);
|
|
7
|
+
* 3. 带参数 → 走与 HTTP 路由同源的门:runWithMutationLock(含 SAFE MODE 阻断 → 返回错误结果);
|
|
8
|
+
* 4. 引擎回 confirmation-required / protected-point / record-incomplete 一律翻成可读错误结果、绝不绕过。
|
|
9
|
+
*
|
|
10
|
+
* 说明:本文件用**假 host/假引擎**(不碰真实磁盘与 DSH 会话存储)验证命令层的编排与文案分派,
|
|
11
|
+
* 数据形状与 RollbackPointSummary / RollbackRewindResult 对齐(type-only import,运行时零依赖)。
|
|
12
|
+
*/
|
|
13
|
+
import test from 'node:test'
|
|
14
|
+
import assert from 'node:assert/strict'
|
|
15
|
+
import { readFileSync } from 'node:fs'
|
|
16
|
+
import { join } from 'node:path'
|
|
17
|
+
|
|
18
|
+
import {
|
|
19
|
+
ROLLBACK_COMMAND_ALIAS,
|
|
20
|
+
ROLLBACK_COMMAND_DEFINITION_ID,
|
|
21
|
+
ROLLBACK_COMMAND_NAME,
|
|
22
|
+
agentSessionFactsOf,
|
|
23
|
+
isAbsolutePath,
|
|
24
|
+
makeRollbackCommandHandler,
|
|
25
|
+
parseRollbackCommand,
|
|
26
|
+
registerRollbackCommand,
|
|
27
|
+
renderRollbackList,
|
|
28
|
+
renderRewindResult,
|
|
29
|
+
type CommandDefinitionLike,
|
|
30
|
+
type CommandInvocationLike,
|
|
31
|
+
type CommandResult,
|
|
32
|
+
type RollbackCommandHost,
|
|
33
|
+
type RollbackCommandListResult,
|
|
34
|
+
} from './command-rollback.ts'
|
|
35
|
+
import { engineMsg, setEngineLocale } from './engine-locale.ts'
|
|
36
|
+
// 只读 DSH 命令契约的类型(运行时零副作用;core 源文件本身不得 import @deepseek-ai/* —— 见架构边界测试)
|
|
37
|
+
import type { CommandResult as DshCommandResult } from '@deepseek-ai/dsh-commands/types'
|
|
38
|
+
// 副作用 import:注册 zh/en 基础目录(插件装配期由 src/index.ts 的同名 import 完成)。
|
|
39
|
+
// 本文件单独跑时也必须有真实文案 —— 否则 engineMsg 只会回退键名。
|
|
40
|
+
import './messages.ts'
|
|
41
|
+
import { EnvironmentLockUnavailableError, runWithMutationLock } from '../utils/env-lock.ts'
|
|
42
|
+
import type { MutationLockPort } from '../utils/env-lock.ts'
|
|
43
|
+
import type { RollbackPointSummary, RollbackRewindResult, RollbackSegmentResult, RollbackSegmentSummary } from './rollback-point.ts'
|
|
44
|
+
|
|
45
|
+
/* ------------------------------------------------------------------ 夹具 */
|
|
46
|
+
|
|
47
|
+
const SESSION_ID = 'aaaa1111-2222-3333-4444-555566667777'
|
|
48
|
+
const OTHER_SESSION_ID = 'bbbb1111-2222-3333-4444-555566667777'
|
|
49
|
+
|
|
50
|
+
function segment(state: 'workspace' | 'session', restorable: boolean): RollbackSegmentSummary {
|
|
51
|
+
return {
|
|
52
|
+
state,
|
|
53
|
+
ok: true,
|
|
54
|
+
degraded: false,
|
|
55
|
+
capturedAt: 1_700_000_000_000,
|
|
56
|
+
fingerprint: null,
|
|
57
|
+
reasonCode: null,
|
|
58
|
+
entries: state === 'workspace' ? 3 : 1,
|
|
59
|
+
bytes: 42,
|
|
60
|
+
cursorBytes: null,
|
|
61
|
+
restorable,
|
|
62
|
+
skippedCount: 0,
|
|
63
|
+
truncated: false,
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function point(overrides: Partial<RollbackPointSummary> = {}): RollbackPointSummary {
|
|
68
|
+
return {
|
|
69
|
+
id: 'point-1',
|
|
70
|
+
createdAt: 1_700_000_000_000,
|
|
71
|
+
sessionId: SESSION_ID,
|
|
72
|
+
unitId: 'proj/' + SESSION_ID,
|
|
73
|
+
turn: 3,
|
|
74
|
+
boundarySeq: 12,
|
|
75
|
+
kind: 'auto',
|
|
76
|
+
protected: false,
|
|
77
|
+
incomplete: false,
|
|
78
|
+
note: null,
|
|
79
|
+
spreadMs: 5,
|
|
80
|
+
segments: [segment('workspace', true), segment('session', true)],
|
|
81
|
+
...overrides,
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function listResult(points: readonly RollbackPointSummary[], overrides: Partial<RollbackCommandListResult> = {}): RollbackCommandListResult {
|
|
86
|
+
return { ok: true, storage: { available: true, root: '/data/rollback-points' }, points, ...overrides }
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function rewindResult(overrides: Partial<RollbackRewindResult> = {}): RollbackRewindResult {
|
|
90
|
+
return {
|
|
91
|
+
ok: false,
|
|
92
|
+
outcome: 'failed',
|
|
93
|
+
code: 'restore-failed',
|
|
94
|
+
id: 'point-1',
|
|
95
|
+
segments: [],
|
|
96
|
+
skippedSegments: [],
|
|
97
|
+
storage: { available: true, root: '/data/rollback-points', guidance: [], requirements: [] },
|
|
98
|
+
guidance: [],
|
|
99
|
+
warnings: [],
|
|
100
|
+
...overrides,
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** 假的命令服务:记录 register 的每一条定义,disposer 可断言被调用。 */
|
|
105
|
+
function fakeCommands() {
|
|
106
|
+
const registered: CommandDefinitionLike[] = []
|
|
107
|
+
const disposed: string[] = []
|
|
108
|
+
const service = {
|
|
109
|
+
register(definition: CommandDefinitionLike) {
|
|
110
|
+
registered.push(definition)
|
|
111
|
+
return () => { disposed.push(definition.name) }
|
|
112
|
+
},
|
|
113
|
+
}
|
|
114
|
+
return { service, registered, disposed }
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function fakeLogger() {
|
|
118
|
+
const warns: string[] = []
|
|
119
|
+
return { warns, log: { warn: (message: string) => { warns.push(message) } } }
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function invocation(rawInput: string, agent: unknown = { id: SESSION_ID }): CommandInvocationLike {
|
|
123
|
+
return { agent, rawInput }
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/* ------------------------------------------------------------------ 解析/窄化 */
|
|
127
|
+
|
|
128
|
+
test('parseRollbackCommand:空 = 列表;单 token = rewind;多 token = invalid', () => {
|
|
129
|
+
assert.deepEqual(parseRollbackCommand(''), { kind: 'list' })
|
|
130
|
+
assert.deepEqual(parseRollbackCommand(' '), { kind: 'list' })
|
|
131
|
+
assert.deepEqual(parseRollbackCommand(' point-9'), { kind: 'rewind', id: 'point-9' })
|
|
132
|
+
assert.deepEqual(parseRollbackCommand('point-9\t'), { kind: 'rewind', id: 'point-9' })
|
|
133
|
+
const invalid = parseRollbackCommand('point-9 extra')
|
|
134
|
+
assert.equal(invalid.kind, 'invalid')
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
test('isAbsolutePath:认 win32 盘符 / UNC / POSIX,不认相对路径', () => {
|
|
138
|
+
assert.equal(isAbsolutePath('C:\\work\\proj'), true)
|
|
139
|
+
assert.equal(isAbsolutePath('C:/work/proj'), true)
|
|
140
|
+
assert.equal(isAbsolutePath('\\\\server\\share'), true)
|
|
141
|
+
assert.equal(isAbsolutePath('/home/u/proj'), true)
|
|
142
|
+
assert.equal(isAbsolutePath('proj'), false)
|
|
143
|
+
assert.equal(isAbsolutePath('./proj'), false)
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
test('agentSessionFactsOf:优先 session.id,退回 agent.id;cwd 非绝对不给分块;都没有则如实 ok:false', () => {
|
|
147
|
+
const full = agentSessionFactsOf({ id: 'agent-1', session: { id: SESSION_ID, header: { cwd: 'C:\\work\\proj' } } })
|
|
148
|
+
assert.deepEqual(full, { ok: true, sessionId: SESSION_ID, chunks: ['C:\\work\\proj'] })
|
|
149
|
+
const relative = agentSessionFactsOf({ id: SESSION_ID, session: { header: { cwd: 'proj' } } })
|
|
150
|
+
assert.deepEqual(relative, { ok: true, sessionId: SESSION_ID, chunks: [] })
|
|
151
|
+
const noSession = agentSessionFactsOf({ id: SESSION_ID })
|
|
152
|
+
assert.deepEqual(noSession, { ok: true, sessionId: SESSION_ID, chunks: [] })
|
|
153
|
+
const none = agentSessionFactsOf({})
|
|
154
|
+
assert.equal(none.ok, false)
|
|
155
|
+
assert.equal(agentSessionFactsOf(null).ok, false)
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
/* ------------------------------------------------------------------ 注册 */
|
|
159
|
+
|
|
160
|
+
test('未组合命令服务:不注册、不抛错,只 warn(宿主照常启动)', () => {
|
|
161
|
+
const logger = fakeLogger()
|
|
162
|
+
let reads = 0
|
|
163
|
+
const dispose = registerRollbackCommand({
|
|
164
|
+
readCommands: () => { reads += 1; return undefined },
|
|
165
|
+
deps: { host: throwingHost() },
|
|
166
|
+
log: logger.log,
|
|
167
|
+
})
|
|
168
|
+
assert.equal(reads, 1, '必须经惰取回调读服务(ctx.get)')
|
|
169
|
+
assert.equal(logger.warns.length, 1)
|
|
170
|
+
assert.match(logger.warns[0] ?? '', /commands-service-missing/)
|
|
171
|
+
assert.doesNotThrow(() => dispose())
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
test('命令服务存在:注册 /rollback 与 /rw(同一 definitionId),disposer 反注册', () => {
|
|
175
|
+
const commands = fakeCommands()
|
|
176
|
+
const dispose = registerRollbackCommand({ readCommands: () => commands.service, deps: { host: throwingHost() } })
|
|
177
|
+
assert.deepEqual(commands.registered.map((d) => d.name), [ROLLBACK_COMMAND_NAME, ROLLBACK_COMMAND_ALIAS])
|
|
178
|
+
for (const definition of commands.registered) {
|
|
179
|
+
assert.equal(definition.definitionId, ROLLBACK_COMMAND_DEFINITION_ID)
|
|
180
|
+
assert.equal(typeof definition.handler, 'function')
|
|
181
|
+
assert.ok((definition.description ?? '').length > 0)
|
|
182
|
+
}
|
|
183
|
+
dispose()
|
|
184
|
+
assert.deepEqual(commands.disposed, [ROLLBACK_COMMAND_NAME, ROLLBACK_COMMAND_ALIAS])
|
|
185
|
+
dispose()
|
|
186
|
+
assert.equal(commands.disposed.length, 2, '重复 dispose 必须幂等')
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
test('注册抛错:只 warn,不影响后续定义与其它装配', () => {
|
|
190
|
+
const logger = fakeLogger()
|
|
191
|
+
const attempted: string[] = []
|
|
192
|
+
const service = {
|
|
193
|
+
register(definition: CommandDefinitionLike) {
|
|
194
|
+
attempted.push(definition.name)
|
|
195
|
+
if (definition.name === ROLLBACK_COMMAND_NAME) throw new Error('boom')
|
|
196
|
+
return undefined
|
|
197
|
+
},
|
|
198
|
+
}
|
|
199
|
+
const dispose = registerRollbackCommand({ readCommands: () => service, deps: { host: throwingHost() }, log: logger.log })
|
|
200
|
+
assert.deepEqual(attempted, [ROLLBACK_COMMAND_NAME, ROLLBACK_COMMAND_ALIAS])
|
|
201
|
+
assert.equal(logger.warns.length, 1)
|
|
202
|
+
assert.match(logger.warns[0] ?? '', /boom/)
|
|
203
|
+
assert.doesNotThrow(() => dispose())
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
test('惰取回调本身抛错:warn 后安静返回(绝不冒泡到装配)', () => {
|
|
207
|
+
const logger = fakeLogger()
|
|
208
|
+
const dispose = registerRollbackCommand({
|
|
209
|
+
readCommands: () => { throw new Error('ctx exploded') },
|
|
210
|
+
deps: { host: throwingHost() },
|
|
211
|
+
log: logger.log,
|
|
212
|
+
})
|
|
213
|
+
assert.equal(logger.warns.length, 1)
|
|
214
|
+
assert.match(logger.warns[0] ?? '', /ctx exploded/)
|
|
215
|
+
assert.doesNotThrow(() => dispose())
|
|
216
|
+
})
|
|
217
|
+
|
|
218
|
+
/** host 哨兵:列表路径之外被调用即失败(证明「只读列表」没碰执行路径)。 */
|
|
219
|
+
function throwingHost(): RollbackCommandHost {
|
|
220
|
+
return {
|
|
221
|
+
agentSession: agentSessionFactsOf,
|
|
222
|
+
list: async () => { throw new Error('list should not be called') },
|
|
223
|
+
rewind: async () => { throw new Error('rewind should not be called') },
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/* ------------------------------------------------------------------ 只读列表 */
|
|
228
|
+
|
|
229
|
+
test('无参数:只读列表 —— 只调 host.list,绝不碰 rewind / mutation lock', async () => {
|
|
230
|
+
const host: RollbackCommandHost = {
|
|
231
|
+
agentSession: agentSessionFactsOf,
|
|
232
|
+
list: async (sessionId: string) => {
|
|
233
|
+
assert.equal(sessionId, SESSION_ID)
|
|
234
|
+
return listResult([point(), point({ id: 'point-2', sessionId: OTHER_SESSION_ID })])
|
|
235
|
+
},
|
|
236
|
+
rewind: async () => { throw new Error('rewind must not run for a read-only list') },
|
|
237
|
+
}
|
|
238
|
+
const handler = makeRollbackCommandHandler({ host })
|
|
239
|
+
const result = await handler(invocation(''))
|
|
240
|
+
assert.equal(result.kind, 'success')
|
|
241
|
+
const text = result.kind === 'success' ? result.text ?? '' : ''
|
|
242
|
+
assert.match(text, /point-1/)
|
|
243
|
+
assert.equal(text.includes('point-2'), false, '别的会话的点不得混进来')
|
|
244
|
+
})
|
|
245
|
+
|
|
246
|
+
test('列表行含 回合号 / 时间 / 类型 / 状态 / 可回退性 五项事实', () => {
|
|
247
|
+
const rendered = renderRollbackList(listResult([point({ turn: 7 })]), SESSION_ID)
|
|
248
|
+
assert.equal(rendered.kind, 'success')
|
|
249
|
+
const text = rendered.kind === 'success' ? rendered.text ?? '' : ''
|
|
250
|
+
assert.match(text, /turn 7|回合 7/)
|
|
251
|
+
assert.match(text, new RegExp(new Date(1_700_000_000_000).toISOString()))
|
|
252
|
+
assert.match(text, new RegExp(engineMsg('command.rollback.kindAuto')))
|
|
253
|
+
assert.match(text, new RegExp(engineMsg('command.rollback.stateNormal')))
|
|
254
|
+
assert.match(text, new RegExp(engineMsg('command.rollback.restorableYes')))
|
|
255
|
+
})
|
|
256
|
+
|
|
257
|
+
test('列表:受保护 / 不完整 / 救援点 三种状态分开说,且不谎报「可回退」', () => {
|
|
258
|
+
const protectedPoint = point({ id: 'p-protected', protected: true })
|
|
259
|
+
const incomplete = point({ id: 'p-incomplete', incomplete: true, segments: [segment('workspace', false), segment('session', true)] })
|
|
260
|
+
const guard = point({ id: 'p-guard', kind: 'guard' })
|
|
261
|
+
const rendered = renderRollbackList(listResult([protectedPoint, incomplete, guard]), SESSION_ID)
|
|
262
|
+
const text = rendered.kind === 'success' ? rendered.text ?? '' : ''
|
|
263
|
+
assert.match(text, new RegExp(engineMsg('command.rollback.stateProtected')))
|
|
264
|
+
assert.match(text, new RegExp(engineMsg('command.rollback.stateIncomplete')))
|
|
265
|
+
assert.match(text, new RegExp(engineMsg('command.rollback.kindGuard')))
|
|
266
|
+
assert.match(text, new RegExp(engineMsg('command.rollback.restorableNo')))
|
|
267
|
+
})
|
|
268
|
+
|
|
269
|
+
test('本会话没有点:明确说「本会话」,不冒充「全库为空」', () => {
|
|
270
|
+
const rendered = renderRollbackList(listResult([point({ sessionId: OTHER_SESSION_ID })]), SESSION_ID)
|
|
271
|
+
assert.equal(rendered.kind, 'error')
|
|
272
|
+
const text = rendered.kind === 'error' ? rendered.text : ''
|
|
273
|
+
assert.equal(text, engineMsg('command.rollback.empty'))
|
|
274
|
+
})
|
|
275
|
+
|
|
276
|
+
test('存储栈不可用:如实报 storage 细节(绝不假装「没有点」)', () => {
|
|
277
|
+
const rendered = renderRollbackList(
|
|
278
|
+
{ ok: false, storage: { available: false, root: '/data/rollback-points', detail: 'ENOENT' }, points: [] },
|
|
279
|
+
SESSION_ID,
|
|
280
|
+
)
|
|
281
|
+
assert.equal(rendered.kind, 'error')
|
|
282
|
+
const text = rendered.kind === 'error' ? rendered.text : ''
|
|
283
|
+
assert.match(text, /ENOENT/)
|
|
284
|
+
})
|
|
285
|
+
|
|
286
|
+
test('拿不到会话:如实报错,绝不列全网的点', async () => {
|
|
287
|
+
const handler = makeRollbackCommandHandler({ host: throwingHost() })
|
|
288
|
+
const result = await handler(invocation('', {}))
|
|
289
|
+
assert.equal(result.kind, 'error')
|
|
290
|
+
})
|
|
291
|
+
|
|
292
|
+
test('多参数:usage 错误,不执行任何回退', async () => {
|
|
293
|
+
const handler = makeRollbackCommandHandler({ host: throwingHost() })
|
|
294
|
+
const result = await handler(invocation('point-1 point-2'))
|
|
295
|
+
assert.equal(result.kind, 'error')
|
|
296
|
+
assert.match(result.kind === 'error' ? result.text : '', /too-many-arguments/)
|
|
297
|
+
})
|
|
298
|
+
|
|
299
|
+
/* ------------------------------------------------------------------ 带参数:写入门 */
|
|
300
|
+
|
|
301
|
+
test('带参数 → 先过 mutation lock 再执行(同源写入门),并按引擎结果分派文案', async () => {
|
|
302
|
+
const host: RollbackCommandHost = {
|
|
303
|
+
agentSession: agentSessionFactsOf,
|
|
304
|
+
list: async () => { throw new Error('list must not run when rewinding') },
|
|
305
|
+
rewind: async ({ id, signal }) => {
|
|
306
|
+
assert.equal(id, 'point-1')
|
|
307
|
+
assert.equal(signal, undefined, '未给 signal 时不得凭空造一个')
|
|
308
|
+
return rewindResult({ ok: true, outcome: 'restored', code: 'ok', segments: [] })
|
|
309
|
+
},
|
|
310
|
+
}
|
|
311
|
+
const handler = makeRollbackCommandHandler({ host })
|
|
312
|
+
const result = await handler(invocation(' point-1', { session: { id: SESSION_ID, header: { cwd: 'C:\\work\\proj' } } }))
|
|
313
|
+
assert.equal(result.kind, 'success', 'rewind 应成功,实际: ' + JSON.stringify(result))
|
|
314
|
+
})
|
|
315
|
+
|
|
316
|
+
test('调用方的取消信号原样透传给回退执行(不吞信号)', async () => {
|
|
317
|
+
const controller = new AbortController()
|
|
318
|
+
let seen: AbortSignal | undefined
|
|
319
|
+
const host: RollbackCommandHost = {
|
|
320
|
+
agentSession: agentSessionFactsOf,
|
|
321
|
+
list: async () => listResult([]),
|
|
322
|
+
rewind: async ({ signal }) => {
|
|
323
|
+
seen = signal
|
|
324
|
+
return rewindResult({ ok: true, outcome: 'restored', code: 'ok' })
|
|
325
|
+
},
|
|
326
|
+
}
|
|
327
|
+
const handler = makeRollbackCommandHandler({ host })
|
|
328
|
+
const result = await handler({
|
|
329
|
+
agent: { id: SESSION_ID },
|
|
330
|
+
rawInput: ' point-1',
|
|
331
|
+
signal: controller.signal,
|
|
332
|
+
})
|
|
333
|
+
assert.equal(result.kind, 'success')
|
|
334
|
+
assert.equal(seen, controller.signal, '必须原样透传同一个 AbortSignal')
|
|
335
|
+
})
|
|
336
|
+
|
|
337
|
+
test('SAFE MODE / 环境锁阻断:EnvironmentLockUnavailableError → 可读错误结果,绝不绕过', async () => {
|
|
338
|
+
const host: RollbackCommandHost = {
|
|
339
|
+
agentSession: agentSessionFactsOf,
|
|
340
|
+
list: async () => listResult([]),
|
|
341
|
+
rewind: async () => { throw new EnvironmentLockUnavailableError('rollback-rewind', 'blocked') },
|
|
342
|
+
}
|
|
343
|
+
const handler = makeRollbackCommandHandler({ host })
|
|
344
|
+
const result = await handler(invocation('point-1'))
|
|
345
|
+
assert.equal(result.kind, 'error')
|
|
346
|
+
const text = result.kind === 'error' ? result.text : ''
|
|
347
|
+
assert.match(text, /blocked/)
|
|
348
|
+
assert.match(text, /point-1/)
|
|
349
|
+
})
|
|
350
|
+
|
|
351
|
+
test('引擎回 confirmation-required / protected-point / record-incomplete 一律是错误结果(不绕过)', () => {
|
|
352
|
+
const cases: Array<{ code: RollbackRewindResult['code']; outcome: RollbackRewindResult['outcome'] }> = [
|
|
353
|
+
{ code: 'confirmation-required', outcome: 'denied' },
|
|
354
|
+
{ code: 'protected-point', outcome: 'denied' },
|
|
355
|
+
{ code: 'record-incomplete', outcome: 'denied' },
|
|
356
|
+
]
|
|
357
|
+
for (const item of cases) {
|
|
358
|
+
const rendered = renderRewindResult(rewindResult({ ok: false, outcome: item.outcome, code: item.code }), 'point-1')
|
|
359
|
+
assert.equal(rendered.kind, 'error', item.code + ' 必须是错误结果')
|
|
360
|
+
const text = rendered.kind === 'error' ? rendered.text : ''
|
|
361
|
+
assert.match(text, new RegExp(item.code))
|
|
362
|
+
}
|
|
363
|
+
})
|
|
364
|
+
|
|
365
|
+
test('partial 回退:错误结果里带逐段事实(含被跳过的段)', () => {
|
|
366
|
+
const segments: RollbackSegmentResult[] = [
|
|
367
|
+
{ state: 'workspace', status: 'failed', reasonCode: 'restore-failed', detail: ['x'], restored: 0, failed: 1, leftovers: [], leftoversTruncated: false },
|
|
368
|
+
{ state: 'session', status: 'restored', detail: [], restored: 1, failed: 0, leftovers: [], leftoversTruncated: false },
|
|
369
|
+
]
|
|
370
|
+
const rendered = renderRewindResult(rewindResult({ ok: false, outcome: 'partial', code: 'restore-failed', segments }), 'point-1')
|
|
371
|
+
assert.equal(rendered.kind, 'error')
|
|
372
|
+
const text = rendered.kind === 'error' ? rendered.text : ''
|
|
373
|
+
assert.match(text, /workspace\/failed\(restore-failed\)/)
|
|
374
|
+
assert.match(text, /session\/restored/)
|
|
375
|
+
})
|
|
376
|
+
|
|
377
|
+
test('语言在取文案那一刻解析:切到 en 后同一渲染函数输出英文(不冻结在装配期)', async () => {
|
|
378
|
+
const host: RollbackCommandHost = {
|
|
379
|
+
agentSession: agentSessionFactsOf,
|
|
380
|
+
list: async () => listResult([point()]),
|
|
381
|
+
rewind: async () => { throw new Error('unused') },
|
|
382
|
+
}
|
|
383
|
+
const handler = makeRollbackCommandHandler({ host })
|
|
384
|
+
setEngineLocale('en')
|
|
385
|
+
try {
|
|
386
|
+
const result = await handler(invocation(''))
|
|
387
|
+
const text = result.kind === 'success' ? result.text ?? '' : ''
|
|
388
|
+
assert.ok(text.includes('Rollback points of this session:'), 'en 列表标题必须是英文: ' + text)
|
|
389
|
+
} finally {
|
|
390
|
+
setEngineLocale('zh')
|
|
391
|
+
}
|
|
392
|
+
})
|
|
393
|
+
|
|
394
|
+
/* ------------------------------------------------------------------ 与 HTTP 同源:真跑 runWithMutationLock */
|
|
395
|
+
|
|
396
|
+
/** 记录调用的假 lock 端口:acquire 返回 ACQUIRED(可放行);token 形状与 MutationLockToken 一致。 */
|
|
397
|
+
function fakeLockPort() {
|
|
398
|
+
const ops: string[] = []
|
|
399
|
+
const released: string[] = []
|
|
400
|
+
const token = { tokenId: 'tok-1', managerId: 'test-manager', instanceId: 'test-instance', acquiredAt: 1 }
|
|
401
|
+
const port: MutationLockPort = {
|
|
402
|
+
async acquire(opts) {
|
|
403
|
+
ops.push(opts.op)
|
|
404
|
+
return { state: 'ACQUIRED', token }
|
|
405
|
+
},
|
|
406
|
+
async release(value) {
|
|
407
|
+
released.push(value.tokenId)
|
|
408
|
+
},
|
|
409
|
+
validate(value): value is typeof token {
|
|
410
|
+
return value === token || (typeof value === 'object' && value !== null && 'tokenId' in value)
|
|
411
|
+
},
|
|
412
|
+
}
|
|
413
|
+
return { port, ops, released }
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
test('同源写入门(真 runWithMutationLock):SAFE MODE 阻断 → 引擎一次都不执行,命令回错误结果', async () => {
|
|
417
|
+
const { port, ops } = fakeLockPort()
|
|
418
|
+
let rewindCalls = 0
|
|
419
|
+
const host: RollbackCommandHost = {
|
|
420
|
+
agentSession: agentSessionFactsOf,
|
|
421
|
+
list: async () => listResult([]),
|
|
422
|
+
rewind: async ({ id }) => {
|
|
423
|
+
rewindCalls += 1
|
|
424
|
+
return runRewindThroughGate({ port, isBlocked: () => true }, { id }, async () =>
|
|
425
|
+
rewindResult({ ok: true, outcome: 'restored', code: 'ok' }),
|
|
426
|
+
)
|
|
427
|
+
},
|
|
428
|
+
}
|
|
429
|
+
try {
|
|
430
|
+
const handler = makeRollbackCommandHandler({ host, mutationLock: port as unknown as MutationLockPort, isBlocked: () => true })
|
|
431
|
+
const result = await handler(invocation('point-1'))
|
|
432
|
+
assert.equal(result.kind, 'error')
|
|
433
|
+
const text = result.kind === 'error' ? result.text : ''
|
|
434
|
+
assert.match(text, /blocked/)
|
|
435
|
+
} finally {
|
|
436
|
+
// 断言:SAFE MODE 阻断时**没有**取锁、也没有执行引擎
|
|
437
|
+
assert.deepEqual(ops, [], 'SAFE MODE 阻断必须先于 acquire')
|
|
438
|
+
assert.equal(rewindCalls, 1, '宿主被调用一次,但内部什么都没做')
|
|
439
|
+
}
|
|
440
|
+
})
|
|
441
|
+
|
|
442
|
+
test('同源写入门(真 runWithMutationLock):未阻断 → acquire 后执行并 release', async () => {
|
|
443
|
+
const { port, ops, released } = fakeLockPort()
|
|
444
|
+
const outcome = await runRewindThroughGate({ port, isBlocked: () => false }, { id: 'p' }, async () =>
|
|
445
|
+
rewindResult({ ok: true, outcome: 'restored', code: 'ok' }),
|
|
446
|
+
)
|
|
447
|
+
assert.equal(outcome.ok, true)
|
|
448
|
+
assert.deepEqual(ops, ['rollback-rewind'], '必须经 mutation lock(op=rollback-rewind)')
|
|
449
|
+
assert.deepEqual(released, ['tok-1'], '执行完必须释放锁(不残留)')
|
|
450
|
+
})
|
|
451
|
+
|
|
452
|
+
|
|
453
|
+
/* ------------------------------------------------------------------ 源码级断言(验收项) */
|
|
454
|
+
|
|
455
|
+
/**
|
|
456
|
+
* 读源码:仅源码级断言用(rel = 仓库根相对路径)。
|
|
457
|
+
*
|
|
458
|
+
* 用 process.cwd()(`npm test` 的工作目录就是仓库根)而**不用** path.resolve(base, '..'):
|
|
459
|
+
* 本机 Node 的 win32 path.resolve 在「≥1 个 .. 段 + 绝对 base」下会把项目目录整段吃掉
|
|
460
|
+
* (实测 D:\\Projects\\personal\\dcm\\src\\core + '../../..' → D:\\Projects\\personal),
|
|
461
|
+
* 于是守卫会去读一个不存在的路径 —— 那是**假红**而不是真问题。path.join 不受影响。
|
|
462
|
+
*/
|
|
463
|
+
function sourceOf(rel: string): string {
|
|
464
|
+
return readFileSync(join(process.cwd(), rel), 'utf8')
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
test('源码级:命令服务只经 ctx.get(\'commands\') 读取,且 src/index.ts 的 inject 不含 commands', () => {
|
|
468
|
+
const indexSrc = sourceOf('src/index.ts')
|
|
469
|
+
assert.ok(
|
|
470
|
+
indexSrc.includes("readService<CommandServiceLike>(ctx, 'commands')"),
|
|
471
|
+
'注册点必须走 ctx.get(\'commands\') 的惰取读取(readService 是它的唯一封装)',
|
|
472
|
+
)
|
|
473
|
+
const injectLine = indexSrc.split('\n').find((line) => line.includes('export const inject'))
|
|
474
|
+
assert.ok(injectLine !== undefined, '找不到 inject 声明')
|
|
475
|
+
assert.equal(/commands/.test(injectLine ?? ''), false, 'commands 绝不能进 inject(未组合该服务的部署必须照常启动): ' + injectLine)
|
|
476
|
+
})
|
|
477
|
+
|
|
478
|
+
test('源码级:命令执行路径调用 runWithMutationLock(与 HTTP 路由同一道写入门)', () => {
|
|
479
|
+
const indexSrc = sourceOf('src/index.ts')
|
|
480
|
+
assert.ok(indexSrc.includes('runWithMutationLock('), '执行路径必须真的调用 runWithMutationLock')
|
|
481
|
+
assert.ok(indexSrc.includes("op: 'rollback-rewind'"), '必须与 HTTP 路由同一 op 名(审计/锁归属可对照)')
|
|
482
|
+
assert.ok(indexSrc.includes('userConfirmed: true'), '/rollback <id> 必须带 fail-closed 确认位')
|
|
483
|
+
const coreSrc = sourceOf('src/core/command-rollback.ts')
|
|
484
|
+
assert.equal(
|
|
485
|
+
/\brouter\b|\.endpoint\(|registerRoutes\(/.test(coreSrc),
|
|
486
|
+
false,
|
|
487
|
+
'命令层不得新增任何 HTTP 路由(路由数不变量守卫必须保持绿)',
|
|
488
|
+
)
|
|
489
|
+
})
|
|
490
|
+
|
|
491
|
+
test('宿主 commands 的类型与 handler 的 CommandResult 结构一致(无运行时依赖的类型协同)', async () => {
|
|
492
|
+
const commands = fakeCommands()
|
|
493
|
+
const dispose = registerRollbackCommand({ readCommands: () => commands.service, deps: { host: throwingHost() } })
|
|
494
|
+
const definition = commands.registered[0]
|
|
495
|
+
assert.ok(definition !== undefined)
|
|
496
|
+
const result = await definition.handler({ agent: { id: SESSION_ID }, rawInput: '' })
|
|
497
|
+
assert.ok(result.kind === 'success' || result.kind === 'error')
|
|
498
|
+
assert.equal(typeof (result.kind === 'success' ? result.text ?? '' : result.text), 'string')
|
|
499
|
+
dispose()
|
|
500
|
+
})
|
|
501
|
+
/* ------------------------------------------------------------------ 同源写入门参考实现(测试用) */
|
|
502
|
+
|
|
503
|
+
/**
|
|
504
|
+
* 与 HTTP 路由逐条同序的那道门(测试里真跑 runWithMutationLock):
|
|
505
|
+
* runWithMutationLock(内含 SAFE MODE isBlocked 判定 + 环境锁 acquire/release)→ 真执行。
|
|
506
|
+
* 被挡(SAFE MODE / 锁不可得)时抛 EnvironmentLockUnavailableError。
|
|
507
|
+
*/
|
|
508
|
+
async function runRewindThroughGate(
|
|
509
|
+
gate: { port: MutationLockPort; isBlocked: () => boolean },
|
|
510
|
+
target: { id: string },
|
|
511
|
+
run: () => Promise<RollbackRewindResult>,
|
|
512
|
+
): Promise<RollbackRewindResult> {
|
|
513
|
+
return await runWithMutationLock(
|
|
514
|
+
gate.port,
|
|
515
|
+
{ op: 'rollback-rewind', target: target.id, isBlocked: gate.isBlocked },
|
|
516
|
+
async () => await run(),
|
|
517
|
+
)
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
/* ------------------------------------------------------------------ 契约形状:与 DSH 的命令结果逐字同形 */
|
|
521
|
+
|
|
522
|
+
/**
|
|
523
|
+
* 编译期断言(双向可赋值):core 自己声明的 CommandResult 与 DSH 的 CommandResult 一旦漂移,
|
|
524
|
+
* 本文件在 tsc 下即红 —— 这是「core 不 import @deepseek-ai/*,但类型仍必须对得上」的护栏。
|
|
525
|
+
*/
|
|
526
|
+
function assertCommandResultShape(value: DshCommandResult): CommandResult {
|
|
527
|
+
return value
|
|
528
|
+
}
|
|
529
|
+
function assertCommandResultBack(value: CommandResult): DshCommandResult {
|
|
530
|
+
return value
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
test('契约形状:本地 CommandResult 与 @deepseek-ai/dsh-commands 的 CommandResult 双向可赋值', () => {
|
|
534
|
+
assert.equal(typeof assertCommandResultShape, 'function')
|
|
535
|
+
assert.equal(typeof assertCommandResultBack, 'function')
|
|
536
|
+
})
|