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/src/core/model-tools.ts
CHANGED
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
* - config_sync_pull 零写入(只 analyze+plan 出差异);落地需另走确认导入管道;
|
|
21
21
|
* - 分区/路径走既有白名单(SECTION_IDS 过滤;同步引擎 portable + FORBIDDEN_SECTIONS 双保险)。
|
|
22
22
|
*/
|
|
23
|
+
import { engineMsg } from './messages-core.ts';
|
|
23
24
|
import type { Context } from '@deepseek-ai/cordis'
|
|
24
25
|
import { defineTool } from '@deepseek-ai/dsh-tools'
|
|
25
26
|
import { randomBytes } from 'node:crypto'
|
|
@@ -68,7 +69,7 @@ export interface ModelToolsDeps {
|
|
|
68
69
|
/** 快照 id 校验:拒绝路径分隔符与 `.`/`..`(防 join(snapshotsDir, id) 越界)。 */
|
|
69
70
|
function assertValidSnapshotId(snapshotId: string): void {
|
|
70
71
|
if (snapshotId === '' || snapshotId.includes('/') || snapshotId.includes('\\')) {
|
|
71
|
-
throw new Error(
|
|
72
|
+
throw new Error(engineMsg('core.modelTools.illegalSnapshotId', { id: JSON.stringify(snapshotId) }))
|
|
72
73
|
}
|
|
73
74
|
}
|
|
74
75
|
|
|
@@ -99,7 +100,7 @@ async function resolveEngine(
|
|
|
99
100
|
const ch: SyncTransportType = channel ?? parseSyncChannel(full?.transport) ?? 'git'
|
|
100
101
|
const cfg = await readSyncConfigFor(deps.syncDir, ch)
|
|
101
102
|
if (cfg === null) {
|
|
102
|
-
throw new Error(
|
|
103
|
+
throw new Error(engineMsg('core.modelTools.syncChannelNotConfigured', { channel: ch }))
|
|
103
104
|
}
|
|
104
105
|
return { engine: deps.makeSyncEngine(cfg), channel: ch }
|
|
105
106
|
}
|
|
@@ -171,7 +172,7 @@ export function createModelTools(deps: ModelToolsDeps) {
|
|
|
171
172
|
const { snapshotId, confirm } = input
|
|
172
173
|
assertValidSnapshotId(snapshotId)
|
|
173
174
|
if (!(await snapshotDirExists(deps, snapshotId))) {
|
|
174
|
-
throw new Error(
|
|
175
|
+
throw new Error(engineMsg('core.modelTools.snapshotNotFound', { id: snapshotId }))
|
|
175
176
|
}
|
|
176
177
|
const snapshotDir = join(deps.snapshotsDir, snapshotId)
|
|
177
178
|
// profile:host.profile 由宿主 resolveProfileName 保证非空(缺省 'web',见 ConfigManagerHostContext),
|
|
@@ -315,7 +316,7 @@ type ModelTools = ReturnType<typeof createModelTools>
|
|
|
315
316
|
export function registerModelTools(ctx: Context, deps: ModelToolsDeps): void {
|
|
316
317
|
const toolsSvc = ctx.get('tools')
|
|
317
318
|
if (toolsSvc === null || toolsSvc === undefined || typeof toolsSvc !== 'object') {
|
|
318
|
-
deps.host.log.warn?.('
|
|
319
|
+
deps.host.log.warn?.(engineMsg('core.modelTools.toolsServiceUnavailable'))
|
|
319
320
|
return
|
|
320
321
|
}
|
|
321
322
|
const tools: ModelTools = createModelTools(deps)
|
|
@@ -329,22 +330,22 @@ export function registerModelTools(ctx: Context, deps: ModelToolsDeps): void {
|
|
|
329
330
|
|
|
330
331
|
register(defineTool({
|
|
331
332
|
name: 'config_backup',
|
|
332
|
-
description: '
|
|
333
|
+
description: engineMsg('core.modelTools.backupToolDesc'),
|
|
333
334
|
parameters: {
|
|
334
335
|
only: {
|
|
335
336
|
type: 'array',
|
|
336
337
|
items: { type: 'string', enum: [...SECTION_IDS] },
|
|
337
|
-
description: '
|
|
338
|
+
description: engineMsg('core.modelTools.backupOnlyParam'),
|
|
338
339
|
},
|
|
339
340
|
password: {
|
|
340
341
|
type: 'string',
|
|
341
|
-
description: '
|
|
342
|
+
description: engineMsg('core.modelTools.backupPasswordParam'),
|
|
342
343
|
},
|
|
343
344
|
},
|
|
344
345
|
output: {
|
|
345
346
|
schema: {
|
|
346
347
|
type: 'json',
|
|
347
|
-
description: '
|
|
348
|
+
description: engineMsg('core.modelTools.backupOutputDesc'),
|
|
348
349
|
},
|
|
349
350
|
render: (_args, value) => [{ type: 'text', text: JSON.stringify(value, null, 2) }],
|
|
350
351
|
},
|
|
@@ -355,12 +356,12 @@ export function registerModelTools(ctx: Context, deps: ModelToolsDeps): void {
|
|
|
355
356
|
|
|
356
357
|
register(defineTool({
|
|
357
358
|
name: 'config_list_snapshots',
|
|
358
|
-
description: '
|
|
359
|
+
description: engineMsg('core.modelTools.listSnapshotsToolDesc'),
|
|
359
360
|
parameters: {},
|
|
360
361
|
output: {
|
|
361
362
|
schema: {
|
|
362
363
|
type: 'json',
|
|
363
|
-
description: '
|
|
364
|
+
description: engineMsg('core.modelTools.listSnapshotsOutputDesc'),
|
|
364
365
|
},
|
|
365
366
|
render: (_args, value) => [{ type: 'text', text: JSON.stringify(value, null, 2) }],
|
|
366
367
|
},
|
|
@@ -372,20 +373,20 @@ export function registerModelTools(ctx: Context, deps: ModelToolsDeps): void {
|
|
|
372
373
|
register(defineTool({
|
|
373
374
|
name: 'config_restore',
|
|
374
375
|
description:
|
|
375
|
-
'
|
|
376
|
+
engineMsg('core.modelTools.restoreToolDesc'),
|
|
376
377
|
parameters: {
|
|
377
378
|
snapshotId: {
|
|
378
379
|
type: 'string',
|
|
379
380
|
required: true,
|
|
380
|
-
description: '
|
|
381
|
+
description: engineMsg('core.modelTools.restoreSnapshotIdParam'),
|
|
381
382
|
},
|
|
382
383
|
confirm: {
|
|
383
384
|
type: 'boolean',
|
|
384
|
-
description: '
|
|
385
|
+
description: engineMsg('core.modelTools.restoreConfirmParam'),
|
|
385
386
|
},
|
|
386
387
|
},
|
|
387
388
|
output: {
|
|
388
|
-
schema: { type: 'json', description: '
|
|
389
|
+
schema: { type: 'json', description: engineMsg('core.modelTools.restoreOutputDesc') },
|
|
389
390
|
render: (_args, value) => [{ type: 'text', text: JSON.stringify(value, null, 2) }],
|
|
390
391
|
},
|
|
391
392
|
async execute(args) {
|
|
@@ -396,33 +397,33 @@ export function registerModelTools(ctx: Context, deps: ModelToolsDeps): void {
|
|
|
396
397
|
register(defineTool({
|
|
397
398
|
name: 'config_sync_push',
|
|
398
399
|
description:
|
|
399
|
-
'
|
|
400
|
+
engineMsg('core.modelTools.syncPushToolDesc'),
|
|
400
401
|
parameters: {
|
|
401
402
|
channel: {
|
|
402
403
|
type: 'string',
|
|
403
404
|
enum: [...SYNC_CHANNELS],
|
|
404
|
-
description: '
|
|
405
|
+
description: engineMsg('core.modelTools.channelParam'),
|
|
405
406
|
},
|
|
406
407
|
sections: {
|
|
407
408
|
type: 'array',
|
|
408
409
|
items: { type: 'string', enum: [...SECTION_IDS] },
|
|
409
|
-
description: '
|
|
410
|
+
description: engineMsg('core.modelTools.syncPushSectionsParam'),
|
|
410
411
|
},
|
|
411
412
|
encrypt: {
|
|
412
413
|
type: 'boolean',
|
|
413
|
-
description: '
|
|
414
|
+
description: engineMsg('core.modelTools.syncPushEncryptParam'),
|
|
414
415
|
},
|
|
415
416
|
includeSecrets: {
|
|
416
417
|
type: 'boolean',
|
|
417
|
-
description: '
|
|
418
|
+
description: engineMsg('core.modelTools.syncPushIncludeSecretsParam'),
|
|
418
419
|
},
|
|
419
420
|
password: {
|
|
420
421
|
type: 'string',
|
|
421
|
-
description: '
|
|
422
|
+
description: engineMsg('core.modelTools.syncPushPasswordParam'),
|
|
422
423
|
},
|
|
423
424
|
},
|
|
424
425
|
output: {
|
|
425
|
-
schema: { type: 'json', description: '
|
|
426
|
+
schema: { type: 'json', description: engineMsg('core.modelTools.syncPushOutputDesc') },
|
|
426
427
|
render: (_args, value) => [{ type: 'text', text: JSON.stringify(value, null, 2) }],
|
|
427
428
|
},
|
|
428
429
|
async execute(args) {
|
|
@@ -433,29 +434,29 @@ export function registerModelTools(ctx: Context, deps: ModelToolsDeps): void {
|
|
|
433
434
|
register(defineTool({
|
|
434
435
|
name: 'config_sync_pull',
|
|
435
436
|
description:
|
|
436
|
-
'
|
|
437
|
+
engineMsg('core.modelTools.syncPullToolDesc'),
|
|
437
438
|
parameters: {
|
|
438
439
|
channel: {
|
|
439
440
|
type: 'string',
|
|
440
441
|
enum: [...SYNC_CHANNELS],
|
|
441
|
-
description: '
|
|
442
|
+
description: engineMsg('core.modelTools.channelParam'),
|
|
442
443
|
},
|
|
443
444
|
snapshotId: {
|
|
444
445
|
type: 'string',
|
|
445
|
-
description: '
|
|
446
|
+
description: engineMsg('core.modelTools.syncPullSnapshotIdParam'),
|
|
446
447
|
},
|
|
447
448
|
strategy: {
|
|
448
449
|
type: 'string',
|
|
449
450
|
enum: ['merge', 'replace', 'skipExisting'],
|
|
450
|
-
description: '
|
|
451
|
+
description: engineMsg('core.modelTools.syncPullStrategyParam'),
|
|
451
452
|
},
|
|
452
453
|
password: {
|
|
453
454
|
type: 'string',
|
|
454
|
-
description: '
|
|
455
|
+
description: engineMsg('core.modelTools.syncPullPasswordParam'),
|
|
455
456
|
},
|
|
456
457
|
},
|
|
457
458
|
output: {
|
|
458
|
-
schema: { type: 'json', description: '
|
|
459
|
+
schema: { type: 'json', description: engineMsg('core.modelTools.syncPullOutputDesc') },
|
|
459
460
|
render: (_args, value) => [{ type: 'text', text: JSON.stringify(value, null, 2) }],
|
|
460
461
|
},
|
|
461
462
|
async execute(args) {
|
package/src/core/phase3-host.ts
CHANGED
|
@@ -22,11 +22,12 @@ import { inspectStartup, type ReconcileProbeHooks, type ReconcileEnv } from './r
|
|
|
22
22
|
import { OWNERSHIP_FILE, type LockState, type MutationLockContext } from '../utils/env-lock.ts';
|
|
23
23
|
import { sha256Hex } from '../utils/hashing.ts';
|
|
24
24
|
import type { JournalStepRecord } from './types.ts';
|
|
25
|
+
import { engineMsg } from './messages-core.ts';
|
|
25
26
|
|
|
26
27
|
/** 存在未收敛的 active transaction → 拒绝创建第二个(active≤1)。 */
|
|
27
28
|
export class TransactionRecoveryRequiredError extends Error {
|
|
28
29
|
constructor(opId: string) {
|
|
29
|
-
super(
|
|
30
|
+
super(engineMsg('core.phase3.transactionNotConverged', { opId }))
|
|
30
31
|
this.name = 'TransactionRecoveryRequiredError'
|
|
31
32
|
}
|
|
32
33
|
}
|
|
@@ -75,6 +76,9 @@ export interface JournalRunContext {
|
|
|
75
76
|
export function mapLockStateForStartup(state: LockState): 'LOCKED' | 'STALE_LOCK_DETECTED' | 'UNKNOWN_STATE' | 'FREE' {
|
|
76
77
|
if (state === 'STALE_LOCK_DETECTED') return 'STALE_LOCK_DETECTED';
|
|
77
78
|
if (state === 'UNKNOWN_STATE') return 'UNKNOWN_STATE';
|
|
79
|
+
// issue #79:FREE(ownership 确实不存在)如实透传。此前它落到下面的兜底被映射成 LOCKED ——
|
|
80
|
+
// 与 inspectLockState 的 missing→LOCKED 一起,把「没有锁」的系统性误报成「有活跃锁」。
|
|
81
|
+
if (state === 'FREE') return 'FREE';
|
|
78
82
|
return 'LOCKED'; // ACQUIRED / LOCKED / LOCK_IO_ERROR / PERMISSION_ERROR → 保守 LOCKED
|
|
79
83
|
}
|
|
80
84
|
|
|
@@ -209,7 +213,19 @@ export class Phase3Recovery {
|
|
|
209
213
|
if (step.beforeFp !== null && fp === step.beforeFp) return 'before-match';
|
|
210
214
|
return 'none';
|
|
211
215
|
},
|
|
212
|
-
|
|
216
|
+
// issue #87:external step 的**真实只读探测**(仅在 journal 里确实记录了指纹时才可能得出结论)。
|
|
217
|
+
// 语义仍是「证不出来就说 unknown」:ref 为空 / 无指纹 / 读失败 / 与两个指纹都不符 → unknown。
|
|
218
|
+
// 注意:runExternalIntent 目前写入的 external step 恒无指纹(beforeFp/afterFp 皆 null),
|
|
219
|
+
// 对它们本探测必然返回 unknown —— 那是刻意的,放行判断在 reconcile 的「可自证残留」门(独立判据)。
|
|
220
|
+
probeExternal: async (step) => {
|
|
221
|
+
if (step.ref === '' || step.beforeFp === null || step.afterFp === null) return 'unknown'
|
|
222
|
+
let data: Buffer
|
|
223
|
+
try { data = await fs.readFile(step.ref) } catch { return 'unknown' }
|
|
224
|
+
const fp = sha256Hex(data)
|
|
225
|
+
if (fp === step.afterFp) return 'installed'
|
|
226
|
+
if (fp === step.beforeFp) return 'not-installed'
|
|
227
|
+
return 'unknown'
|
|
228
|
+
},
|
|
213
229
|
snapshotExists: async (snapshotId, binding) => {
|
|
214
230
|
if (snapshotExists === undefined) return false; // 未注入 → 保守 false
|
|
215
231
|
return snapshotExists(snapshotId, binding);
|
|
@@ -330,7 +346,7 @@ export class Phase3Recovery {
|
|
|
330
346
|
const snapId = await snapshotProvider();
|
|
331
347
|
// P1-2 修复:snapshotProvider 返回 null 表示快照创建失败 → 显式 abort(不得在无快照下继续 mutation)
|
|
332
348
|
if (snapId === null) {
|
|
333
|
-
throw new Error(
|
|
349
|
+
throw new Error(engineMsg('core.phase3.snapshotProviderNull', { operationType }));
|
|
334
350
|
}
|
|
335
351
|
await journalCtx.bindSnapshot(snapId);
|
|
336
352
|
} else if (deferredSnapshot !== true) {
|
|
@@ -446,7 +462,7 @@ export class Phase3Recovery {
|
|
|
446
462
|
environmentFingerprint: this.environmentFingerprint || 'unknown',
|
|
447
463
|
ownerInstanceId,
|
|
448
464
|
bindSnapshot: async (snapshotId: string) => {
|
|
449
|
-
if (snapshotId === null || snapshotId === '') throw new Error('
|
|
465
|
+
if (snapshotId === null || snapshotId === '') throw new Error(engineMsg('core.phase3.bindSnapshotEmptyId'));
|
|
450
466
|
// Reviewer A P2①:持久化失败必须传播(fail-closed)——若 journal 无法 durable 绑定 SNAPSHOT_CREATED,
|
|
451
467
|
// 引擎的 await bindSnapshot 抛错 → fn 抛错 → runJournaled catch(NEEDS_ATTENTION),mutation 绝不在无绑定下继续。
|
|
452
468
|
await this.store.update(opId, (j) => {
|
|
@@ -454,7 +470,7 @@ export class Phase3Recovery {
|
|
|
454
470
|
if (next.state === 'CREATED') next = transitionJournalState(next, 'SNAPSHOT_CREATED');
|
|
455
471
|
else if (next.state === 'SNAPSHOT_CREATED') { /* 幂等:已绑定 */ }
|
|
456
472
|
else if (next.state === 'APPLYING') { /* 允许:绑定已晚但幂等记录 */ }
|
|
457
|
-
else throw new Error(
|
|
473
|
+
else throw new Error(engineMsg('core.phase3.bindSnapshotIllegalState', { state: next.state }));
|
|
458
474
|
return next;
|
|
459
475
|
});
|
|
460
476
|
},
|
|
@@ -68,6 +68,28 @@ test('verifyStepFingerprint:本地文件 step 真实磁盘指纹判定', async
|
|
|
68
68
|
assert.equal(await hooks.verifyStepFingerprint({ adapter: 'plugins', ref: 'plugin:@x', kind: 'Install', external: true, beforeFp: null, afterFp: null, status: 'planned', appliedAt: null }), 'unable');
|
|
69
69
|
});
|
|
70
70
|
|
|
71
|
+
test('issue #87 probeExternal:有指纹时做真实只读探测;证不出来一律 unknown(默认保守语义不变)', async (t) => {
|
|
72
|
+
const dir = tmp(t);
|
|
73
|
+
const recovery = await makeRecovery(dir);
|
|
74
|
+
const target = path.join(dir, 'artifact.bin');
|
|
75
|
+
const fpA = sha256Hex(Buffer.from('A'));
|
|
76
|
+
const fpB = sha256Hex(Buffer.from('B'));
|
|
77
|
+
const step = (beforeFp: string | null, afterFp: string | null, ref = target): import('./journal.ts').JournalStep => ({
|
|
78
|
+
adapter: 'plugins', ref, kind: 'Install', external: true, beforeFp, afterFp, status: 'planned', appliedAt: null,
|
|
79
|
+
});
|
|
80
|
+
const hooks = recovery.recoveryHooks;
|
|
81
|
+
|
|
82
|
+
fssync.writeFileSync(target, 'A');
|
|
83
|
+
assert.equal(await hooks.probeExternal(step(fpA, fpB)), 'not-installed', '磁盘仍等于 beforeFp → 可自证未装');
|
|
84
|
+
fssync.writeFileSync(target, 'B');
|
|
85
|
+
assert.equal(await hooks.probeExternal(step(fpA, fpB)), 'installed', '磁盘等于 afterFp → 已装');
|
|
86
|
+
fssync.writeFileSync(target, 'C');
|
|
87
|
+
assert.equal(await hooks.probeExternal(step(fpA, fpB)), 'unknown', '与两个指纹都不符 → unknown(绝不猜)');
|
|
88
|
+
assert.equal(await hooks.probeExternal(step(null, null)), 'unknown', '无指纹(runExternalIntent 的真实形态)→ unknown');
|
|
89
|
+
assert.equal(await hooks.probeExternal(step(fpA, fpB, '')), 'unknown', 'ref 为空 → unknown');
|
|
90
|
+
assert.equal(await hooks.probeExternal(step(fpA, fpB, path.join(dir, 'missing.bin'))), 'unknown', '读失败 → unknown');
|
|
91
|
+
});
|
|
92
|
+
|
|
71
93
|
// ---------- 2. recordStep 持久化 ----------
|
|
72
94
|
|
|
73
95
|
test('runJournaled journalCtx.recordStep:文件/外部 step 落 journal', async (t) => {
|
package/src/core/plugin-cli.ts
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
* 只依赖 node 内置模块,零 DSH 运行时依赖(M5 的 TDD 可直接单测本模块)。
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
+
import { engineMsg } from './messages-core.ts';
|
|
18
19
|
import { spawn } from 'node:child_process'
|
|
19
20
|
import type { ChildProcess, SpawnOptions } from 'node:child_process'
|
|
20
21
|
import { readFileSync, rmSync } from 'node:fs'
|
|
@@ -609,35 +610,35 @@ export function classifyDshPluginFailure(output: string): DshPluginFailure | nul
|
|
|
609
610
|
return {
|
|
610
611
|
code: 'hoist-pattern-diff',
|
|
611
612
|
recoverable: true,
|
|
612
|
-
message: '
|
|
613
|
+
message: engineMsg('core.pluginCli.hoistPatternDiff'),
|
|
613
614
|
}
|
|
614
615
|
}
|
|
615
616
|
if (output.includes('ERR_PNPM_ADDING_TO_ROOT')) {
|
|
616
617
|
return {
|
|
617
618
|
code: 'adding-to-root',
|
|
618
619
|
recoverable: true,
|
|
619
|
-
message: '
|
|
620
|
+
message: engineMsg('core.pluginCli.addingToRoot'),
|
|
620
621
|
}
|
|
621
622
|
}
|
|
622
623
|
if (/--workspace-root may only be used inside a workspace/i.test(output)) {
|
|
623
624
|
return {
|
|
624
625
|
code: 'not-a-workspace',
|
|
625
626
|
recoverable: false,
|
|
626
|
-
message: '
|
|
627
|
+
message: engineMsg('core.pluginCli.notAWorkspace'),
|
|
627
628
|
}
|
|
628
629
|
}
|
|
629
630
|
if (output.includes('ERR_PNPM_MINIMUM_RELEASE_AGE_VIOLATION') || output.includes('ERR_PNPM_NO_MATURE_MATCHING_VERSION')) {
|
|
630
631
|
return {
|
|
631
632
|
code: 'release-age-violation',
|
|
632
633
|
recoverable: true,
|
|
633
|
-
message: '
|
|
634
|
+
message: engineMsg('core.pluginCli.releaseAgeViolation'),
|
|
634
635
|
}
|
|
635
636
|
}
|
|
636
637
|
if (output.includes('ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED') || /git-hosted package "[^"]+" needs to execute build scripts/i.test(output)) {
|
|
637
638
|
return {
|
|
638
639
|
code: 'git-build-blocked',
|
|
639
640
|
recoverable: false,
|
|
640
|
-
message: '
|
|
641
|
+
message: engineMsg('core.pluginCli.gitBuildBlocked'),
|
|
641
642
|
}
|
|
642
643
|
}
|
|
643
644
|
// issue #35:pnpm-workspace.yaml 的 patchedDependencies 指向不存在的 patch 文件时,
|
|
@@ -647,29 +648,27 @@ export function classifyDshPluginFailure(output: string): DshPluginFailure | nul
|
|
|
647
648
|
// 路径可能含盘符冒号(C:\...),故按整行取、再切掉尾随的 ':' 或 ': <错误文本>'
|
|
648
649
|
const raw = /Failed to read patch file\s+([^\r\n]+)/i.exec(output)?.[1]?.trim()
|
|
649
650
|
const file = raw === undefined ? undefined : raw.split(/:\s/)[0]?.replace(/:\s*$/, '').trim()
|
|
650
|
-
const
|
|
651
|
-
const en = file === undefined ? '' : ` (${file})`
|
|
651
|
+
const patchSuffix = file === undefined ? '' : engineMsg('core.pluginCli.parenValue', { value: file })
|
|
652
652
|
return {
|
|
653
653
|
code: 'patch-file-missing',
|
|
654
654
|
recoverable: false,
|
|
655
|
-
message:
|
|
655
|
+
message: engineMsg('core.pluginCli.patchFileMissing', { patch: patchSuffix }),
|
|
656
656
|
}
|
|
657
657
|
}
|
|
658
658
|
if (output.includes('ERR_PNPM_FETCH_404')) {
|
|
659
659
|
const pkg = /GET\s+\S*\/([^/\s]+):/.exec(output)?.[1]?.replace(/%2[Ff]/g, '/')
|
|
660
|
-
const
|
|
661
|
-
const en = pkg === undefined ? '' : ` (${pkg})`
|
|
660
|
+
const pkgSuffix = pkg === undefined ? '' : engineMsg('core.pluginCli.parenValue', { value: pkg })
|
|
662
661
|
return {
|
|
663
662
|
code: 'fetch-404',
|
|
664
663
|
recoverable: false,
|
|
665
|
-
message:
|
|
664
|
+
message: engineMsg('core.pluginCli.fetch404', { pkg: pkgSuffix }),
|
|
666
665
|
}
|
|
667
666
|
}
|
|
668
667
|
if (isTransientDshPluginFailure(output)) {
|
|
669
668
|
return {
|
|
670
669
|
code: 'transient-network',
|
|
671
670
|
recoverable: true,
|
|
672
|
-
message: '
|
|
671
|
+
message: engineMsg('core.pluginCli.transientNetwork'),
|
|
673
672
|
}
|
|
674
673
|
}
|
|
675
674
|
// 桌面端保留档案:普通 dsh CLI(< 0.2 的旧版尤其)对 `--profile desktop` 直接报
|
|
@@ -680,14 +679,14 @@ export function classifyDshPluginFailure(output: string): DshPluginFailure | nul
|
|
|
680
679
|
return {
|
|
681
680
|
code: 'desktop-profile-reserved',
|
|
682
681
|
recoverable: false,
|
|
683
|
-
message: '
|
|
682
|
+
message: engineMsg('core.pluginCli.desktopProfileReserved'),
|
|
684
683
|
}
|
|
685
684
|
}
|
|
686
685
|
if (output.includes('pnpm not found on PATH')) {
|
|
687
686
|
return {
|
|
688
687
|
code: 'pnpm-missing',
|
|
689
688
|
recoverable: false,
|
|
690
|
-
message: '
|
|
689
|
+
message: engineMsg('core.pluginCli.pnpmMissing'),
|
|
691
690
|
}
|
|
692
691
|
}
|
|
693
692
|
return null
|
|
@@ -696,18 +695,18 @@ export function classifyDshPluginFailure(output: string): DshPluginFailure | nul
|
|
|
696
695
|
/** 安装失败 → 分类后的双语可读 Error;未识别则保留 stderr 尾部摘要。 */
|
|
697
696
|
export function installErrorFor(pkg: string, result: DshPluginResult): Error {
|
|
698
697
|
if (result.spawnError !== undefined && /ENOENT|EINVAL/.test(result.spawnError)) {
|
|
699
|
-
return new Error(
|
|
698
|
+
return new Error(engineMsg('core.pluginCli.dshCliStartFailed', { spawnError: result.spawnError, pkg }))
|
|
700
699
|
}
|
|
701
700
|
if (result.timedOut) {
|
|
702
|
-
return new Error(
|
|
701
|
+
return new Error(engineMsg('core.pluginCli.installTimeout', { pkg }))
|
|
703
702
|
}
|
|
704
703
|
const output = `${result.stderr}\n${result.stdout}`
|
|
705
704
|
const failure = classifyDshPluginFailure(output)
|
|
706
705
|
if (failure !== null) {
|
|
707
|
-
return new Error(
|
|
706
|
+
return new Error(engineMsg('core.pluginCli.installFailed', { pkg, code: failure.code, message: failure.message }))
|
|
708
707
|
}
|
|
709
708
|
const tail = (result.stderr.trim().split('\n').slice(-8).join('\n')
|
|
710
709
|
|| result.stdout.trim().split('\n').slice(-8).join('\n')
|
|
711
|
-
|| '
|
|
712
|
-
return new Error(
|
|
710
|
+
|| engineMsg('core.pluginCli.noOutput'))
|
|
711
|
+
return new Error(engineMsg('core.pluginCli.installFailedWithTail', { pkg, exitCode: String(result.exitCode), tail }))
|
|
713
712
|
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* pluginData / pluginAssets 来源注册表的纯函数单测(零 I/O)。
|
|
3
|
+
*
|
|
4
|
+
* 这里钉的是「收录口径」的四条硬性质:
|
|
5
|
+
* ① 区域归属(角色 → 分区)与内置来源的取证标签;
|
|
6
|
+
* ② 最长前缀归属 —— 大资产不能被短前缀的配置区域吞掉;
|
|
7
|
+
* ③ 排除与安全(cache 不进包;非法 home 相对路径一律拒绝);
|
|
8
|
+
* ④ 自述声明(dsh.dataDirs)的形状解析:不认识就整条丢弃,绝不猜。
|
|
9
|
+
*/
|
|
10
|
+
import test from 'node:test';
|
|
11
|
+
import assert from 'node:assert/strict';
|
|
12
|
+
|
|
13
|
+
import {
|
|
14
|
+
BUILTIN_PLUGIN_DATA_SOURCES, areaOfPath, areasForSection, isExcludedRel, normalizeHomeRel,
|
|
15
|
+
parseDeclaredDataDirs, relInArea, sectionOfRole,
|
|
16
|
+
} from './plugin-data-registry.ts';
|
|
17
|
+
import type { PluginDataArea, PluginDataSource } from './plugin-data-registry.ts';
|
|
18
|
+
|
|
19
|
+
/** 区域条目(与 adapter 的 PluginDataEntry 同形;core 测试不反向依赖 adapters 层) */
|
|
20
|
+
type AreaEntry = { source: PluginDataSource; area: PluginDataArea };
|
|
21
|
+
|
|
22
|
+
const ALL: AreaEntry[] = [];
|
|
23
|
+
for (const source of BUILTIN_PLUGIN_DATA_SOURCES) {
|
|
24
|
+
for (const area of source.areas) ALL.push({ source, area });
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
test('全局唯一来源:id 不重复,且每个来源都如实标注取证强度', () => {
|
|
28
|
+
const ids = BUILTIN_PLUGIN_DATA_SOURCES.map((s) => s.id);
|
|
29
|
+
assert.equal(new Set(ids).size, ids.length);
|
|
30
|
+
for (const s of BUILTIN_PLUGIN_DATA_SOURCES) {
|
|
31
|
+
assert.ok(['measured', 'fixture', 'documented'].includes(s.evidence), s.id);
|
|
32
|
+
assert.ok(s.areas.length > 0, s.id);
|
|
33
|
+
}
|
|
34
|
+
// 本机实测的两个来源必须真有 measured 证据(其余是文档取证,不得美化)
|
|
35
|
+
assert.equal(BUILTIN_PLUGIN_DATA_SOURCES.find((s) => s.id === 'mnemon')?.evidence, 'measured');
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test('角色 → 分区:config 进 pluginData,assets 进 pluginAssets,cache 两个都不进', () => {
|
|
39
|
+
assert.equal(sectionOfRole('config'), 'pluginData');
|
|
40
|
+
assert.equal(sectionOfRole('assets'), 'pluginAssets');
|
|
41
|
+
assert.equal(sectionOfRole('cache'), null);
|
|
42
|
+
|
|
43
|
+
const config = areasForSection(BUILTIN_PLUGIN_DATA_SOURCES, 'pluginData').map((e) => e.area.relPath);
|
|
44
|
+
const assets = areasForSection(BUILTIN_PLUGIN_DATA_SOURCES, 'pluginAssets').map((e) => e.area.relPath);
|
|
45
|
+
assert.ok(config.includes('.dsh-wallpaper-engine'));
|
|
46
|
+
assert.ok(config.includes('.mnemon/runtime'));
|
|
47
|
+
assert.ok(!config.includes('.dsh-wallpaper-engine/uploads'), '大资产不得出现在配置分区');
|
|
48
|
+
assert.ok(assets.includes('.dsh-wallpaper-engine/uploads'));
|
|
49
|
+
assert.ok(assets.includes('.mnemon/data'));
|
|
50
|
+
assert.ok(![...config, ...assets].includes('.dsh-wallpaper-engine/cache'), '缓存永不进任何分区');
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test('最长前缀归属:uploads 下的文件归 uploads 区域,不是它祖先的配置区域', () => {
|
|
54
|
+
const hit = areaOfPath(ALL, '.dsh-wallpaper-engine/uploads/a.mp4');
|
|
55
|
+
assert.equal(hit?.area.relPath, '.dsh-wallpaper-engine/uploads');
|
|
56
|
+
assert.equal(hit?.area.role, 'assets');
|
|
57
|
+
|
|
58
|
+
// 配置分区视角:upload 路径被祖先区域的 excludeRel 挡住 → 判 null(导入侧据此拒绝写)
|
|
59
|
+
const configOnly = areasForSection(BUILTIN_PLUGIN_DATA_SOURCES, 'pluginData');
|
|
60
|
+
assert.equal(areaOfPath(configOnly, '.dsh-wallpaper-engine/uploads/a.mp4'), null);
|
|
61
|
+
assert.equal(areaOfPath(configOnly, '.dsh-wallpaper-engine/config.json')?.area.relPath, '.dsh-wallpaper-engine');
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test('relInArea / isExcludedRel:文件区域精确匹配,目录区域前缀匹配', () => {
|
|
65
|
+
const fileArea = { relPath: '.x/state.json', role: 'config' as const };
|
|
66
|
+
assert.equal(relInArea(fileArea, '.x/state.json'), true);
|
|
67
|
+
assert.equal(relInArea(fileArea, '.x/state.json.bak'), false);
|
|
68
|
+
|
|
69
|
+
const dirArea = { relPath: '.x', role: 'config' as const, excludeRel: ['cache'] };
|
|
70
|
+
assert.equal(relInArea(dirArea, '.x/a/b'), true);
|
|
71
|
+
assert.equal(isExcludedRel(dirArea, '.x/cache'), true);
|
|
72
|
+
assert.equal(isExcludedRel(dirArea, '.x/cache/deep/b'), true);
|
|
73
|
+
assert.equal(isExcludedRel(dirArea, '.x/cacheX'), false, '前缀必须是完整路径段');
|
|
74
|
+
assert.equal(isExcludedRel(dirArea, '.x/a'), false);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test('normalizeHomeRel:拒绝一切可能越界的写法,反斜杠归一为 POSIX', () => {
|
|
78
|
+
assert.equal(normalizeHomeRel('.a/b'), '.a/b');
|
|
79
|
+
assert.equal(normalizeHomeRel('.a\\b'), '.a/b');
|
|
80
|
+
assert.equal(normalizeHomeRel(' .a/b '), '.a/b');
|
|
81
|
+
|
|
82
|
+
for (const bad of ['', ' ', '/abs/x', 'C:/x', 'a/../b', '..', './a', 'a/', 'a//b', 'a/\u0000b', 42, null, undefined]) {
|
|
83
|
+
assert.equal(normalizeHomeRel(bad), null, JSON.stringify(bad));
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test('parseDeclaredDataDirs:合法声明解析成来源;形状不认识整条丢弃(不猜)', () => {
|
|
88
|
+
const source = parseDeclaredDataDirs('dsh-fake', {
|
|
89
|
+
dsh: { dataDirs: [
|
|
90
|
+
{ path: '.fake-plugin', role: 'config', env: ['FAKE_DATA_DIR'] },
|
|
91
|
+
{ path: '.fake-plugin/uploads', role: 'assets', exclude: ['tmp'] },
|
|
92
|
+
] },
|
|
93
|
+
});
|
|
94
|
+
assert.ok(source);
|
|
95
|
+
assert.equal(source?.id, 'declared:dsh-fake');
|
|
96
|
+
assert.deepEqual(source?.packages, ['dsh-fake']);
|
|
97
|
+
assert.equal(source?.areas.length, 2);
|
|
98
|
+
assert.deepEqual(source?.areas[0]?.envOverrides, ['FAKE_DATA_DIR']);
|
|
99
|
+
assert.deepEqual(source?.areas[1]?.excludeRel, ['tmp']);
|
|
100
|
+
assert.equal(source?.areas[1]?.role, 'assets');
|
|
101
|
+
|
|
102
|
+
// 缺省角色 = config
|
|
103
|
+
assert.equal(parseDeclaredDataDirs('p', { dsh: { dataDirs: [{ path: '.p' }] } })?.areas[0]?.role, 'config');
|
|
104
|
+
|
|
105
|
+
// 整条丢弃的形态
|
|
106
|
+
assert.equal(parseDeclaredDataDirs('p', null), null);
|
|
107
|
+
assert.equal(parseDeclaredDataDirs('p', {}), null);
|
|
108
|
+
assert.equal(parseDeclaredDataDirs('p', { dsh: {} }), null);
|
|
109
|
+
assert.equal(parseDeclaredDataDirs('p', { dsh: { dataDirs: [] } }), null);
|
|
110
|
+
assert.equal(parseDeclaredDataDirs('p', { dsh: { dataDirs: 'nope' } }), null);
|
|
111
|
+
assert.equal(parseDeclaredDataDirs('p', { dsh: { dataDirs: [{ path: '/abs' }] } }), null, '绝对路径 → 整条丢弃');
|
|
112
|
+
assert.equal(parseDeclaredDataDirs('p', { dsh: { dataDirs: [{ path: '.ok' }, { path: '../escape' }] } }), null, '任一非法 → 整条丢弃');
|
|
113
|
+
});
|