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
|
@@ -0,0 +1,738 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* rollback-point(**工作区 + 会话回滚**)的**展示模型**(框架无关纯函数,node 可测)。
|
|
3
|
+
*
|
|
4
|
+
* 数据来自宿主 /recovery/rollback-points 族的 6 条子路径(list / storage / <id> / <id>/preview /
|
|
5
|
+
* <id>/rewind / <id>/delete;第 3 条 <id> 单条详情同样进端口 —— 面板要能深链到某个点)。
|
|
6
|
+
* 宿主把它挂在既有 recovery **prefix 路由**内部按 path 分发,因此不新增注册路由条目。
|
|
7
|
+
*
|
|
8
|
+
* 三条纪律(与旧检查点展示模型同源,改名不改口径):
|
|
9
|
+
* ① **不 import src/core/rollback-point.ts(或任何 core 模块)** —— 那条链经 node:fs 依赖宿主运行时,
|
|
10
|
+
* 一旦进 client bundle,DSH loader 会报 missed the module table(AGENTS.md 的 client bundle 铁律)。
|
|
11
|
+
* 因此这里按「线协议结构子集」重新声明类型(字段只取展示所需,缺字段一律回落安全值)。
|
|
12
|
+
* ② **本模块不产出任何用户可见文案**:只回传字典键(rollback.*),zh/en 在 recovery-locales.ts。
|
|
13
|
+
* ③ **未知码 / 未知态 / 未知来源一律回落 unknown 键,绝不裸渲染机器码**;用户自由文本(note)与
|
|
14
|
+
* 宿主技术细节(detail / guidance.detail / 路径清单)一律先过 redact()(AGENTS.md UI 硬性规则 7)。
|
|
15
|
+
*
|
|
16
|
+
* 两态纪律(S2):只有 `workspace` 与 `session`。第三态 `config` 与「同点判定」已随检查点一起删除:
|
|
17
|
+
* 任何非两态的 state 字符串都回落 rollback.state.unknown,本模块**不出现** config 态。
|
|
18
|
+
*
|
|
19
|
+
* 线协议形状(与 T1 宿主侧 src/core/rollback-point.ts 逐字段对齐,2026-10-10 双人确认):
|
|
20
|
+
* - points[] = { id, createdAt, sessionId, turn, kind, protected, incomplete, note, spreadMs, segments:[…] },
|
|
21
|
+
* segments **恒为 [workspace, session] 两项**;
|
|
22
|
+
* - preview.workspace = { overwrite, different, missing, createdAfter(数), leftovers(路径清单,≤20), leftoversTruncated },
|
|
23
|
+
* preview.session = { from, to, turns, cursorNow, cursorTarget, restorable, active, reasonCode }
|
|
24
|
+
* (from = 目标回合开始前最后一个事件的 seq,即 fork 的 inclusive 边界;turns = 回合号);
|
|
25
|
+
* - rewind.segments[] = { state, status, reasonCode?, detail[], restored, failed, leftovers[], leftoversTruncated, childSessionId?, guidance? }。
|
|
26
|
+
*/
|
|
27
|
+
import { redact } from '../security/redaction.js';
|
|
28
|
+
/* ------------------------------------------------------------ 两态 / 三选一 mode */
|
|
29
|
+
/** 只保留两态(S2):工作区 + 会话。 */
|
|
30
|
+
export const ROLLBACK_STATES = ['workspace', 'session'];
|
|
31
|
+
/** 段名 → 是否为受支持的段(出现别的段一律按未知处理,绝不假设它是第三态)。 */
|
|
32
|
+
export function isRollbackState(value) {
|
|
33
|
+
return value === 'workspace' || value === 'session';
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* 回退范围三选一(冻结 wire contract 的 `mode`):
|
|
37
|
+
* - `files+session`:工作区文件 + 会话(新子会话)一起回退;
|
|
38
|
+
* - `files`:只回退工作区文件;
|
|
39
|
+
* - `session`:只回退会话(fork 子会话)。
|
|
40
|
+
*/
|
|
41
|
+
export const ROLLBACK_MODES = ['files+session', 'files', 'session'];
|
|
42
|
+
export function isRollbackMode(value) {
|
|
43
|
+
return value === 'files+session' || value === 'files' || value === 'session';
|
|
44
|
+
}
|
|
45
|
+
/** mode → 段集合(workspace 段 = 工作区文件;session 段 = 会话)。 */
|
|
46
|
+
export function rollbackModeSegments(mode) {
|
|
47
|
+
if (mode === 'files')
|
|
48
|
+
return ['workspace'];
|
|
49
|
+
if (mode === 'session')
|
|
50
|
+
return ['session'];
|
|
51
|
+
if (mode === 'files+session')
|
|
52
|
+
return ['workspace', 'session'];
|
|
53
|
+
return [];
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* 该 mode 在当前可回退段集合下**是否可选**(界面据此禁用注定失败的选项)。
|
|
57
|
+
*
|
|
58
|
+
* 例:某点没有稳定边界(session 段 restorable=false)时,`session` 与 `files+session` 都不可选 ——
|
|
59
|
+
* 绝不发一个宿主一定会拒绝的请求。
|
|
60
|
+
*/
|
|
61
|
+
export function rollbackModeAvailable(mode, availableSegments) {
|
|
62
|
+
const needed = rollbackModeSegments(mode);
|
|
63
|
+
return needed.length > 0 && needed.every((state) => availableSegments.includes(state));
|
|
64
|
+
}
|
|
65
|
+
/** 段集合 → mode(三选一;空集合回落 `files+session`,因为那正是自动点缺省该回退的两态)。 */
|
|
66
|
+
export function rollbackModeFromSegments(segments) {
|
|
67
|
+
const hasWorkspace = segments.includes('workspace');
|
|
68
|
+
const hasSession = segments.includes('session');
|
|
69
|
+
if (hasWorkspace && !hasSession)
|
|
70
|
+
return 'files';
|
|
71
|
+
if (hasSession && !hasWorkspace)
|
|
72
|
+
return 'session';
|
|
73
|
+
return 'files+session';
|
|
74
|
+
}
|
|
75
|
+
/* ------------------------------------------------------------ 字典键映射(绝不裸渲染) */
|
|
76
|
+
export const ROLLBACK_STATE_UNKNOWN_KEY = 'rollback.state.unknown';
|
|
77
|
+
export const ROLLBACK_MODE_UNKNOWN_KEY = 'rollback.mode.unknown';
|
|
78
|
+
export const ROLLBACK_CODE_UNKNOWN_KEY = 'rollback.code.unknown';
|
|
79
|
+
export const ROLLBACK_GUIDANCE_UNKNOWN_KEY = 'rollback.guidance.unknown';
|
|
80
|
+
export const ROLLBACK_KIND_UNKNOWN_KEY = 'rollback.kind.unknown';
|
|
81
|
+
const STATE_KEYS = {
|
|
82
|
+
workspace: 'rollback.state.workspace',
|
|
83
|
+
session: 'rollback.state.session',
|
|
84
|
+
};
|
|
85
|
+
const MODE_KEYS = {
|
|
86
|
+
'files+session': 'rollback.mode.filesAndSession',
|
|
87
|
+
files: 'rollback.mode.filesOnly',
|
|
88
|
+
session: 'rollback.mode.sessionOnly',
|
|
89
|
+
};
|
|
90
|
+
/**
|
|
91
|
+
* code 白名单:**只有这些码进字典**,其余一律回落 unknown ——
|
|
92
|
+
* 宿主若新增码而字典没跟上,界面显示「未识别的错误码」而不是把机器码拼上屏。
|
|
93
|
+
*
|
|
94
|
+
* 与宿主引擎 `src/core/rollback-point.ts` 的 `RollbackCode` 联合**逐条对应**(core 不能进
|
|
95
|
+
* client bundle,所以只能在这里镜像一份;t3 已按 T1 的最终码表补齐 31 条,t10 补
|
|
96
|
+
* `no-recorded-files` ⇒ 32 条)。顶层 code 与 segments[].reasonCode / warnings 共用同一枚举,
|
|
97
|
+
* 所以一份白名单同时服务三处渲染。
|
|
98
|
+
*
|
|
99
|
+
* ⚠️ **这份镜像与引擎码表的等价性有守卫**:`rollback-view.test.ts` 的「码表镜像 != 引擎码表即红」
|
|
100
|
+
* 用例从 `src/core/rollback-point.ts` 源码级读出 `RollbackCode` union,双向断言集合相等
|
|
101
|
+
* (引擎新增码忘了补这里、或这里多写了引擎没有的码,都会立刻变红 —— 本次 t10 的
|
|
102
|
+
* `no-recorded-files` 就是因为缺这条对照而静默漂移过一轮)。
|
|
103
|
+
*/
|
|
104
|
+
export const ROLLBACK_CODES = [
|
|
105
|
+
'ok',
|
|
106
|
+
'deleted',
|
|
107
|
+
// 冻结契约的 9 条
|
|
108
|
+
'confirmation-required',
|
|
109
|
+
'protected-point',
|
|
110
|
+
'record-not-found',
|
|
111
|
+
'record-incomplete',
|
|
112
|
+
'invalid-input',
|
|
113
|
+
// 待删的「该点未记录」路径超上限 ⇒ 整套拒绝、零写入(绝不删一半;顶层 code,不是泛化的 invalid-input)
|
|
114
|
+
'created-removals-limit',
|
|
115
|
+
'no-boundary',
|
|
116
|
+
'storage-unavailable',
|
|
117
|
+
'mutation-locked',
|
|
118
|
+
'method-not-allowed',
|
|
119
|
+
// 台账 / 存储
|
|
120
|
+
'ledger-unreadable',
|
|
121
|
+
'ledger-write-failed',
|
|
122
|
+
// 工作区写回(记录校验 / 对象仓 / 写后复验 / 无事可做)
|
|
123
|
+
'invalid-path',
|
|
124
|
+
'object-missing',
|
|
125
|
+
'object-hash-mismatch',
|
|
126
|
+
'restore-failed',
|
|
127
|
+
'verify-failed',
|
|
128
|
+
'nothing-to-restore',
|
|
129
|
+
// T10:该态**没有记录任何文件**(工作区段 files=0)—— 与「记录了但全部已一致」严格分开,
|
|
130
|
+
// 只出现在 segments[].reasonCode(不是顶层 code);界面据此说两句不同的话(t11)。
|
|
131
|
+
'no-recorded-files',
|
|
132
|
+
// 救援点(S5 guard)
|
|
133
|
+
'guard-failed',
|
|
134
|
+
// 会话(fork 子会话;原会话日志只读)
|
|
135
|
+
'session-missing',
|
|
136
|
+
'session-out-of-root',
|
|
137
|
+
'session-too-large',
|
|
138
|
+
'session-active',
|
|
139
|
+
// 子代理会话(origin=subagent):**不自动留点** —— 出现即代表该会话被有意跳过(宿主日志留痕)
|
|
140
|
+
'subagent-session',
|
|
141
|
+
'session-fork-unavailable',
|
|
142
|
+
'fork-failed',
|
|
143
|
+
// 自动留点(工作区快照)
|
|
144
|
+
'capture-failed',
|
|
145
|
+
'capture-truncated',
|
|
146
|
+
'capture-degraded',
|
|
147
|
+
'no-chunks',
|
|
148
|
+
'chunk-not-absolute',
|
|
149
|
+
'chunk-not-found',
|
|
150
|
+
// 单文件差异(「查看」的数据源):该路径**不在**这个回滚点的记录里
|
|
151
|
+
'file-not-recorded',
|
|
152
|
+
];
|
|
153
|
+
/**
|
|
154
|
+
* 已知指引码后缀(T1 引擎实际产出;白名单外回落 unknown)。
|
|
155
|
+
*
|
|
156
|
+
* 引擎回传的是**完整字典键**(rollback.guidance.workspaceRolledBack),旧检查点引擎回传的是
|
|
157
|
+
* **裸后缀**(workspacePartial)—— 映射两种形态都认,避免把已知指引误判成 unknown
|
|
158
|
+
* (那会让「工作区已回滚 / 已 fork 子会话」这类关键事实消失)。
|
|
159
|
+
*/
|
|
160
|
+
export const ROLLBACK_GUIDANCE_CODES = [
|
|
161
|
+
'mountStorage', 'fixDataDir', 'readOnlyFallback', 'probeDetail',
|
|
162
|
+
'workspaceRolledBack', 'forkUnavailable', 'sessionForked', 'sessionNotAttached',
|
|
163
|
+
];
|
|
164
|
+
/** 已知告警码后缀(warnings 走独立一族 rollback.warn.*;白名单外回落 rollback.warn.unknown)。 */
|
|
165
|
+
export const ROLLBACK_WARN_CODES = [
|
|
166
|
+
'ledgerUnreadable', 'trackMissing', 'trackDegraded', 'protected', 'guardFailed',
|
|
167
|
+
// t2:删除集被整体拒绝的**成因**(忽略目录 / 路径索引缺失 / 目录读不出)—— 必须能说清为什么没删
|
|
168
|
+
'createdRefused',
|
|
169
|
+
];
|
|
170
|
+
export const ROLLBACK_WARN_UNKNOWN_KEY = 'rollback.warn.unknown';
|
|
171
|
+
/**
|
|
172
|
+
* 指引 / 告警码 → 字典键。三种形态都认:
|
|
173
|
+
* - 完整键 `rollback.guidance.X` / `rollback.warn.X`(T1 引擎);
|
|
174
|
+
* - 裸后缀 `X`(旧检查点引擎)。
|
|
175
|
+
* 未知一律回落 unknown 键,**绝不裸渲染机器码**。
|
|
176
|
+
*/
|
|
177
|
+
function rollbackNoticeKey(code) {
|
|
178
|
+
if (typeof code !== 'string' || code === '')
|
|
179
|
+
return ROLLBACK_GUIDANCE_UNKNOWN_KEY;
|
|
180
|
+
if (code.startsWith('rollback.warn.')) {
|
|
181
|
+
const bare = code.slice('rollback.warn.'.length);
|
|
182
|
+
return ROLLBACK_WARN_CODES.includes(bare) ? 'rollback.warn.' + bare : ROLLBACK_WARN_UNKNOWN_KEY;
|
|
183
|
+
}
|
|
184
|
+
const bare = code.startsWith('rollback.guidance.') ? code.slice('rollback.guidance.'.length) : code;
|
|
185
|
+
return ROLLBACK_GUIDANCE_CODES.includes(bare) ? 'rollback.guidance.' + bare : ROLLBACK_GUIDANCE_UNKNOWN_KEY;
|
|
186
|
+
}
|
|
187
|
+
/** 段名 → 字典键(非两态回落 unknown,绝不裸渲染)。 */
|
|
188
|
+
export function rollbackStateKey(state) {
|
|
189
|
+
return isRollbackState(state) ? STATE_KEYS[state] : ROLLBACK_STATE_UNKNOWN_KEY;
|
|
190
|
+
}
|
|
191
|
+
/** mode → 字典键(三选一;其它回落 unknown)。 */
|
|
192
|
+
export function rollbackModeKey(mode) {
|
|
193
|
+
return isRollbackMode(mode) ? MODE_KEYS[mode] : ROLLBACK_MODE_UNKNOWN_KEY;
|
|
194
|
+
}
|
|
195
|
+
const GUARD_KEYS = {
|
|
196
|
+
require: 'rollback.guard.require',
|
|
197
|
+
warn: 'rollback.guard.warn',
|
|
198
|
+
off: 'rollback.guard.off',
|
|
199
|
+
};
|
|
200
|
+
/** 救援点策略(S5 guard)→ 字典键(未知回落 unknown)。 */
|
|
201
|
+
export function rollbackGuardPolicyKey(policy) {
|
|
202
|
+
return policy === 'require' || policy === 'warn' || policy === 'off' ? GUARD_KEYS[policy] : 'rollback.guard.unknown';
|
|
203
|
+
}
|
|
204
|
+
/** 机器码 → 字典键(白名单外回落 unknown)。 */
|
|
205
|
+
export function rollbackCodeKey(code) {
|
|
206
|
+
if (typeof code !== 'string' || code === '')
|
|
207
|
+
return ROLLBACK_CODE_UNKNOWN_KEY;
|
|
208
|
+
return ROLLBACK_CODES.includes(code) ? 'rollback.code.' + code : ROLLBACK_CODE_UNKNOWN_KEY;
|
|
209
|
+
}
|
|
210
|
+
/** 指引 / 告警:code → rollback.guidance.* / rollback.warn.*(三种形态都认),detail 过 redact。 */
|
|
211
|
+
export function rollbackGuidanceRows(items) {
|
|
212
|
+
return (items ?? []).map((item) => ({
|
|
213
|
+
key: rollbackNoticeKey(item.code),
|
|
214
|
+
detail: item.detail !== undefined && item.detail !== '' ? redact(item.detail) : null,
|
|
215
|
+
}));
|
|
216
|
+
}
|
|
217
|
+
/* ------------------------------------------------------------ 留存口径(S6) */
|
|
218
|
+
/**
|
|
219
|
+
* 每会话最多保留的**自动点**(超出只淘汰最旧的自动点 —— 引擎 selectPrune 的自动点桶)。
|
|
220
|
+
* 救援点(guard)不占这个额度、也不被它淘汰,但有自己的**两道**上限
|
|
221
|
+
* (每会话引擎 ROLLBACK_MAX_GUARDS_PER_SESSION / 全局 ROLLBACK_MAX_GUARDS)。
|
|
222
|
+
*/
|
|
223
|
+
export const ROLLBACK_MAX_POINTS_PER_SESSION = 20;
|
|
224
|
+
/** 全局最多保留的**自动点**(同上,只淘汰最旧的自动点)。 */
|
|
225
|
+
export const ROLLBACK_MAX_POINTS_TOTAL = 50;
|
|
226
|
+
/** 每会话最多保留的**救援点**(guard):超出只淘汰最旧的救援点;数值 = 引擎 ROLLBACK_MAX_GUARDS_PER_SESSION。 */
|
|
227
|
+
export const ROLLBACK_MAX_POINTS_GUARDS_PER_SESSION = 20;
|
|
228
|
+
/**
|
|
229
|
+
* **全局**最多保留的**救援点**(guard):与 ROLLBACK_MAX_POINTS_TOTAL **互相独立**
|
|
230
|
+
* (两类各计预算,谁也不吃谁额度;数值相同不是耦合),超出只淘汰最旧的救援点。
|
|
231
|
+
* 数值 = 引擎 ROLLBACK_MAX_GUARDS(两侧必须同步,T17 用例逐句钉住)。
|
|
232
|
+
*/
|
|
233
|
+
export const ROLLBACK_MAX_POINTS_GUARDS_TOTAL = 50;
|
|
234
|
+
/**
|
|
235
|
+
* 预览不可回退时的原因键(按宿主真实形状分派;**绝不回落 rollback.code.unknown**)。
|
|
236
|
+
*
|
|
237
|
+
* 注意(t22 冻结口径):救援点**可以**作为回退目标 —— 引擎 canRestore = !protected && !incomplete &&
|
|
238
|
+
* 至少一段可恢复(**不含** kind!=='guard',见 src/core/rollback-point.ts:1531)。所以这个键**只在
|
|
239
|
+
* 宿主真的拒绝了一个看起来可恢复的救援点**(canRestore=false 且不是 protected/incomplete/无可用段)时使用,
|
|
240
|
+
* 不是「救援点一律不可回退」。
|
|
241
|
+
*/
|
|
242
|
+
export const ROLLBACK_PREVIEW_REASON_GUARD_KEY = 'rollback.preview.reason.guard';
|
|
243
|
+
export const ROLLBACK_PREVIEW_REASON_INCOMPLETE_KEY = 'rollback.preview.reason.incomplete';
|
|
244
|
+
export const ROLLBACK_PREVIEW_REASON_HOST_REFUSED_KEY = 'rollback.preview.reason.hostRefused';
|
|
245
|
+
/**
|
|
246
|
+
* 聊天内「↺ 回退」入口是否可见(issue #88:关闭后**收起**入口,不是置灰)。
|
|
247
|
+
*
|
|
248
|
+
* 判据只有一条:**宿主回传的开关**。老宿主(storage 响应里没有 `rollbackEnabled` 字段)按
|
|
249
|
+
* 「未提供 = 功能默认开」处理 —— 那一版的语义就是默认启用,此时把入口藏起来会让用户
|
|
250
|
+
* 「有点但没有入口」。形状不符(不是布尔)同样按「未提供」处理,绝不因为一个坏字段藏掉入口。
|
|
251
|
+
*/
|
|
252
|
+
export function rollbackEntryVisible(storage) {
|
|
253
|
+
if (storage === undefined || storage === null)
|
|
254
|
+
return false;
|
|
255
|
+
return storage.rollbackEnabled !== false;
|
|
256
|
+
}
|
|
257
|
+
/* ------------------------------------------------------------ 内部小工具 */
|
|
258
|
+
function countOf(value) {
|
|
259
|
+
if (typeof value === 'number' && Number.isFinite(value))
|
|
260
|
+
return value;
|
|
261
|
+
if (Array.isArray(value))
|
|
262
|
+
return value.length;
|
|
263
|
+
return 0;
|
|
264
|
+
}
|
|
265
|
+
function numberOrNull(value) {
|
|
266
|
+
return typeof value === 'number' && Number.isFinite(value) ? value : null;
|
|
267
|
+
}
|
|
268
|
+
/** 路径/事实清单:逐条脱敏(宿主技术细节不得明文上屏)。 */
|
|
269
|
+
function redactLines(values) {
|
|
270
|
+
return (values ?? []).map((line) => redact(line));
|
|
271
|
+
}
|
|
272
|
+
/** 段可用性:显式 available/restorable 为 false 才算不可用(缺字段 = 宿主没给否定信号)。 */
|
|
273
|
+
function segmentAvailable(segment) {
|
|
274
|
+
return segment.available !== false && segment.restorable !== false;
|
|
275
|
+
}
|
|
276
|
+
/** 段级 / 点级「跳过 · 截断」告警键(S4:宿主已如实回传,界面必须说出来)。 */
|
|
277
|
+
export const ROLLBACK_NOTICE_SKIPPED_KEY = 'rollback.row.notice.skipped';
|
|
278
|
+
export const ROLLBACK_NOTICE_TRUNCATED_KEY = 'rollback.row.notice.truncated';
|
|
279
|
+
export const ROLLBACK_PREVIEW_NOTICE_SKIPPED_KEY = 'rollback.preview.notice.skipped';
|
|
280
|
+
export const ROLLBACK_PREVIEW_NOTICE_TRUNCATED_KEY = 'rollback.preview.notice.truncated';
|
|
281
|
+
/** 跳过 / 截断事实 → 告警键(顺序固定:先「跳过多少」,再「被截断」)。 */
|
|
282
|
+
function noticeKeysOf(skippedCount, truncated, skippedKey, truncatedKey) {
|
|
283
|
+
const keys = [];
|
|
284
|
+
if (skippedCount > 0)
|
|
285
|
+
keys.push(skippedKey);
|
|
286
|
+
if (truncated)
|
|
287
|
+
keys.push(truncatedKey);
|
|
288
|
+
return keys;
|
|
289
|
+
}
|
|
290
|
+
function segmentView(segment) {
|
|
291
|
+
const ok = segment.ok !== false;
|
|
292
|
+
const degraded = segment.degraded === true;
|
|
293
|
+
const badgeKind = ok ? (degraded ? 'warn' : 'ok') : 'error';
|
|
294
|
+
const skippedCount = numberOrNull(segment.skippedCount) ?? 0;
|
|
295
|
+
const truncated = segment.truncated === true;
|
|
296
|
+
return {
|
|
297
|
+
state: segment.state,
|
|
298
|
+
labelKey: rollbackStateKey(segment.state),
|
|
299
|
+
restorable: segment.restorable === true,
|
|
300
|
+
badgeKind,
|
|
301
|
+
entries: numberOrNull(segment.entries) ?? 0,
|
|
302
|
+
cursorBytes: numberOrNull(segment.cursorBytes),
|
|
303
|
+
reasonKey: ok ? null : rollbackCodeKey(segment.reasonCode),
|
|
304
|
+
skippedCount,
|
|
305
|
+
truncated,
|
|
306
|
+
noticeKeys: noticeKeysOf(skippedCount, truncated, ROLLBACK_NOTICE_SKIPPED_KEY, ROLLBACK_NOTICE_TRUNCATED_KEY),
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
/** 列表 → 展示行(台账已按时间倒序;本函数只做映射与判定,不重排)。 */
|
|
310
|
+
export function rollbackPointRows(list) {
|
|
311
|
+
if (list === null)
|
|
312
|
+
return [];
|
|
313
|
+
const points = Array.isArray(list.points) ? list.points : [];
|
|
314
|
+
return points.map((point) => {
|
|
315
|
+
const segments = (Array.isArray(point.segments) ? point.segments : []).map(segmentView);
|
|
316
|
+
const kind = point.kind === 'guard' ? 'guard' : point.kind === 'auto' ? 'auto' : 'unknown';
|
|
317
|
+
const kindKey = kind === 'guard' ? 'rollback.kind.guard' : kind === 'auto' ? 'rollback.kind.auto' : ROLLBACK_KIND_UNKNOWN_KEY;
|
|
318
|
+
return {
|
|
319
|
+
id: point.id,
|
|
320
|
+
kind,
|
|
321
|
+
kindKey,
|
|
322
|
+
createdAtMs: point.createdAt,
|
|
323
|
+
sessionId: typeof point.sessionId === 'string' && point.sessionId !== '' ? point.sessionId : null,
|
|
324
|
+
unitId: typeof point.unitId === 'string' && point.unitId !== '' ? point.unitId : null,
|
|
325
|
+
title: typeof point.title === 'string' && point.title.trim() !== '' ? redact(point.title) : null,
|
|
326
|
+
turn: numberOrNull(point.turn),
|
|
327
|
+
protected: point.protected === true,
|
|
328
|
+
incomplete: point.incomplete === true,
|
|
329
|
+
badgeKind: point.incomplete === true ? 'warn' : 'ok',
|
|
330
|
+
spreadMs: numberOrNull(point.spreadMs) ?? 0,
|
|
331
|
+
segments,
|
|
332
|
+
rewindable: point.protected !== true && point.incomplete !== true && segments.some((segment) => segment.restorable),
|
|
333
|
+
// 宿主真值(引擎 preview/remove 同一判据):protected 与 **guard** 一律不可删 ——
|
|
334
|
+
// 只看 protected 会把救援点的「删除」按钮点亮,点了必得 protected-point(按钮与文案自相矛盾)。
|
|
335
|
+
removable: point.protected !== true && kind !== 'guard',
|
|
336
|
+
note: point.note !== null && point.note !== undefined && point.note !== '' ? redact(point.note) : null,
|
|
337
|
+
};
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
/**
|
|
341
|
+
* 台账 → **按会话分组**的时间线(组内保持台账顺序 = 时间倒序;组间按各组最新时间倒序)。
|
|
342
|
+
*
|
|
343
|
+
* 分组键 = sessionId;缺 sessionId 的记录归入「未知会话」组并排在最后 —— 绝不猜它属于哪个会话。
|
|
344
|
+
*/
|
|
345
|
+
export function rollbackPointGroups(list) {
|
|
346
|
+
const groups = [];
|
|
347
|
+
const index = new Map();
|
|
348
|
+
for (const row of rollbackPointRows(list)) {
|
|
349
|
+
const key = row.sessionId ?? '\u0000unknown';
|
|
350
|
+
let at = index.get(key);
|
|
351
|
+
if (at === undefined) {
|
|
352
|
+
at = groups.length;
|
|
353
|
+
index.set(key, at);
|
|
354
|
+
groups.push({ sessionId: row.sessionId, unitId: row.unitId, title: row.title, latestAtMs: null, rows: [] });
|
|
355
|
+
}
|
|
356
|
+
const group = groups[at];
|
|
357
|
+
group.rows.push(row);
|
|
358
|
+
if (group.unitId === null && row.unitId !== null)
|
|
359
|
+
group.unitId = row.unitId;
|
|
360
|
+
if (group.title === null && row.title !== null)
|
|
361
|
+
group.title = row.title;
|
|
362
|
+
if (group.latestAtMs === null || row.createdAtMs > group.latestAtMs)
|
|
363
|
+
group.latestAtMs = row.createdAtMs;
|
|
364
|
+
}
|
|
365
|
+
return groups.sort((a, b) => {
|
|
366
|
+
if (a.sessionId === null && b.sessionId !== null)
|
|
367
|
+
return 1;
|
|
368
|
+
if (b.sessionId === null && a.sessionId !== null)
|
|
369
|
+
return -1;
|
|
370
|
+
return (b.latestAtMs ?? 0) - (a.latestAtMs ?? 0);
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
/** 该回滚点记下了多少个工作区文件(时间线上的「文件数」)。 */
|
|
374
|
+
export function rollbackRowFileCount(row) {
|
|
375
|
+
let total = 0;
|
|
376
|
+
for (const segment of row.segments)
|
|
377
|
+
if (segment.state === 'workspace')
|
|
378
|
+
total += segment.entries;
|
|
379
|
+
return total;
|
|
380
|
+
}
|
|
381
|
+
/** 该回滚点是否有会话游标(时间线上的「是否有会话游标」—— 没有就只回退工作区)。 */
|
|
382
|
+
export function rollbackRowHasSessionCursor(row) {
|
|
383
|
+
return row.segments.some((segment) => segment.state === 'session' && segment.cursorBytes !== null);
|
|
384
|
+
}
|
|
385
|
+
/**
|
|
386
|
+
* 预览 → 展示行(<id>/preview 的只读结果)。
|
|
387
|
+
*
|
|
388
|
+
* 计数口径逐条对应「界面能看到什么」:覆盖多少项、其中多少项内容不同、多少项当前已不存在
|
|
389
|
+
* (会被重新创建)、多少项是该点**未记录**的(引擎只统计「不在记录里」的文件,留点时被跳过的
|
|
390
|
+
* 既有文件同样落在这里,所以不得声称「之后新建」)。
|
|
391
|
+
*
|
|
392
|
+
* 「未记录」与「会被删除」是**两件事**:会被删的只有过了安全判据的那一份
|
|
393
|
+
* (`workspace.removals` / `removalsCount`),未记录清单里混着永不删除的 `.git` 等拒删项。
|
|
394
|
+
*/
|
|
395
|
+
/**
|
|
396
|
+
* 文件行的**主标签** = 文件名(Cursor「Confirm Undo」式:一眼看是哪几个文件,而不是一屏绝对路径)。
|
|
397
|
+
* 完整路径由组件放进 `title`(悬停可见),**信息一个都不丢**。
|
|
398
|
+
*/
|
|
399
|
+
export function rollbackPreviewFileLabel(filePath) {
|
|
400
|
+
const trimmed = filePath.replace(/[\\/]+$/, '');
|
|
401
|
+
const at = Math.max(trimmed.lastIndexOf('/'), trimmed.lastIndexOf('\\'));
|
|
402
|
+
return at < 0 ? trimmed : trimmed.slice(at + 1);
|
|
403
|
+
}
|
|
404
|
+
export function splitRollbackPath(filePath) {
|
|
405
|
+
const trimmed = filePath.replace(/[\\/]+$/, '');
|
|
406
|
+
const at = Math.max(trimmed.lastIndexOf('/'), trimmed.lastIndexOf('\\'));
|
|
407
|
+
if (at < 0)
|
|
408
|
+
return { dir: '', base: trimmed };
|
|
409
|
+
return { dir: trimmed.slice(0, at + 1), base: trimmed.slice(at + 1) };
|
|
410
|
+
}
|
|
411
|
+
export function rollbackPreviewScope(files, removals) {
|
|
412
|
+
let added = 0;
|
|
413
|
+
let removed = 0;
|
|
414
|
+
for (const file of files) {
|
|
415
|
+
if (file.added === null || file.removed === null)
|
|
416
|
+
continue;
|
|
417
|
+
added += file.added;
|
|
418
|
+
removed += file.removed;
|
|
419
|
+
}
|
|
420
|
+
return { files: files.length, added, removed, removals: removals.length };
|
|
421
|
+
}
|
|
422
|
+
/**
|
|
423
|
+
* 差异无法逐行展示时的字典键(本层**只回键**,文案在字典里)。
|
|
424
|
+
* 未知 / 缺失原因回落 null —— 调用方按「未知原因」出话,绝不回落成「没有差异」(那是另一件事)。
|
|
425
|
+
*/
|
|
426
|
+
export function rollbackFileDiffReasonKey(reason) {
|
|
427
|
+
switch (reason) {
|
|
428
|
+
case 'missing-blob': return 'rollback.diff.reason.missingBlob';
|
|
429
|
+
case 'binary': return 'rollback.diff.reason.binary';
|
|
430
|
+
case 'too-large': return 'rollback.diff.reason.tooLarge';
|
|
431
|
+
case 'unreadable': return 'rollback.diff.reason.unreadable';
|
|
432
|
+
default: return null;
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
/** 可渲染的 hunks(缺字段 / 非数组一律当空 —— 渲染层绝不因宿主形态差异崩掉)。 */
|
|
436
|
+
export function rollbackFileDiffHunks(result) {
|
|
437
|
+
const hunks = result?.hunks;
|
|
438
|
+
return Array.isArray(hunks) ? hunks : [];
|
|
439
|
+
}
|
|
440
|
+
/** 两侧是否都真的读到了文本(否则说不出「无差异」)。 */
|
|
441
|
+
export function rollbackFileDiffComparable(result) {
|
|
442
|
+
if (result === null || result.ok !== true)
|
|
443
|
+
return false;
|
|
444
|
+
if (result.reason !== undefined)
|
|
445
|
+
return false;
|
|
446
|
+
return result.before.binary !== true && result.after.binary !== true && result.before.oversized !== true && result.after.oversized !== true;
|
|
447
|
+
}
|
|
448
|
+
export function rollbackPreviewRows(preview) {
|
|
449
|
+
if (preview === null)
|
|
450
|
+
return [];
|
|
451
|
+
const rows = [];
|
|
452
|
+
const pointWarnings = rollbackGuidanceRows(preview.warnings);
|
|
453
|
+
const workspace = preview.workspace;
|
|
454
|
+
if (workspace !== undefined && workspace !== null) {
|
|
455
|
+
const skippedCount = countOf(workspace.skippedCount);
|
|
456
|
+
const truncated = workspace.truncated === true;
|
|
457
|
+
rows.push({
|
|
458
|
+
state: 'workspace',
|
|
459
|
+
labelKey: rollbackStateKey('workspace'),
|
|
460
|
+
available: segmentAvailable(workspace),
|
|
461
|
+
reasonKey: workspace.reasonCode !== undefined && workspace.reasonCode !== null && workspace.reasonCode !== ''
|
|
462
|
+
? rollbackCodeKey(workspace.reasonCode)
|
|
463
|
+
: null,
|
|
464
|
+
counts: [
|
|
465
|
+
{ key: 'rollback.preview.overwrite', n: countOf(workspace.overwrite) },
|
|
466
|
+
{ key: 'rollback.preview.different', n: countOf(workspace.different) },
|
|
467
|
+
{ key: 'rollback.preview.missing', n: countOf(workspace.missing) },
|
|
468
|
+
{ key: 'rollback.preview.createdAfter', n: Math.max(countOf(workspace.createdAfter), countOf(workspace.leftovers)) },
|
|
469
|
+
],
|
|
470
|
+
leftovers: redactLines(workspace.leftovers),
|
|
471
|
+
leftoversTruncated: workspace.leftoversTruncated === true,
|
|
472
|
+
removals: redactLines(workspace.removals),
|
|
473
|
+
removalsCount: Math.max(countOf(workspace.removalsCount), Array.isArray(workspace.removals) ? workspace.removals.length : 0),
|
|
474
|
+
removalsTruncated: workspace.removalsTruncated === true,
|
|
475
|
+
createdSkipped: redactLines(workspace.createdSkipped),
|
|
476
|
+
createdSkippedCount: Math.max(countOf(workspace.createdSkippedCount), Array.isArray(workspace.createdSkipped) ? workspace.createdSkipped.length : 0),
|
|
477
|
+
createdSkippedTruncated: workspace.createdSkippedTruncated === true,
|
|
478
|
+
createdRefusedReason: typeof workspace.createdRefusedReason === 'string' && workspace.createdRefusedReason !== '' ? workspace.createdRefusedReason : null,
|
|
479
|
+
skippedCount,
|
|
480
|
+
truncated,
|
|
481
|
+
noticeKeys: noticeKeysOf(skippedCount, truncated, ROLLBACK_PREVIEW_NOTICE_SKIPPED_KEY, ROLLBACK_PREVIEW_NOTICE_TRUNCATED_KEY),
|
|
482
|
+
warnings: pointWarnings,
|
|
483
|
+
cursor: null,
|
|
484
|
+
cursorBytes: null,
|
|
485
|
+
turns: null,
|
|
486
|
+
active: false,
|
|
487
|
+
guidance: rollbackGuidanceRows(workspace.guidance),
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
const session = preview.session;
|
|
491
|
+
if (session !== undefined && session !== null) {
|
|
492
|
+
rows.push({
|
|
493
|
+
state: 'session',
|
|
494
|
+
labelKey: rollbackStateKey('session'),
|
|
495
|
+
available: segmentAvailable(session),
|
|
496
|
+
reasonKey: session.reasonCode !== undefined && session.reasonCode !== null && session.reasonCode !== ''
|
|
497
|
+
? rollbackCodeKey(session.reasonCode)
|
|
498
|
+
: null,
|
|
499
|
+
counts: [],
|
|
500
|
+
leftovers: [],
|
|
501
|
+
leftoversTruncated: false,
|
|
502
|
+
removals: [],
|
|
503
|
+
removalsCount: 0,
|
|
504
|
+
removalsTruncated: false,
|
|
505
|
+
createdSkipped: [],
|
|
506
|
+
createdSkippedCount: 0,
|
|
507
|
+
createdSkippedTruncated: false,
|
|
508
|
+
createdRefusedReason: null,
|
|
509
|
+
skippedCount: 0,
|
|
510
|
+
truncated: false,
|
|
511
|
+
noticeKeys: [],
|
|
512
|
+
warnings: pointWarnings,
|
|
513
|
+
cursor: { from: numberOrNull(session.from), to: numberOrNull(session.to) },
|
|
514
|
+
cursorBytes: { now: numberOrNull(session.cursorNowBytes), target: numberOrNull(session.cursorTargetBytes) },
|
|
515
|
+
turns: numberOrNull(session.turns),
|
|
516
|
+
active: session.active === true,
|
|
517
|
+
guidance: rollbackGuidanceRows(session.guidance),
|
|
518
|
+
});
|
|
519
|
+
}
|
|
520
|
+
return rows;
|
|
521
|
+
}
|
|
522
|
+
/** 点级告警(preview.warnings)→ 展示行;界面在面板级渲染一次(点级告警不归属任何段,绝不猜)。 */
|
|
523
|
+
export function rollbackPreviewWarnings(preview) {
|
|
524
|
+
return rollbackGuidanceRows(preview?.warnings);
|
|
525
|
+
}
|
|
526
|
+
/** 点级指引(preview.guidance)→ 展示行(同上,面板级渲染一次)。 */
|
|
527
|
+
export function rollbackPreviewGuidance(preview) {
|
|
528
|
+
return rollbackGuidanceRows(preview?.guidance);
|
|
529
|
+
}
|
|
530
|
+
/** ① 的原因键来源:workspace.reasonCode 优先 → 段摘要里的 workspace 段 → 未知(绝不编原因)。 */
|
|
531
|
+
function previewNoteReasonKey(workspace, segments) {
|
|
532
|
+
const direct = workspace?.reasonCode;
|
|
533
|
+
if (typeof direct === 'string' && direct !== '')
|
|
534
|
+
return rollbackCodeKey(direct);
|
|
535
|
+
const segment = segments?.find((item) => item.state === 'workspace');
|
|
536
|
+
const code = segment?.reasonCode;
|
|
537
|
+
if (typeof code === 'string' && code !== '')
|
|
538
|
+
return rollbackCodeKey(code);
|
|
539
|
+
return ROLLBACK_CODE_UNKNOWN_KEY;
|
|
540
|
+
}
|
|
541
|
+
/**
|
|
542
|
+
* 预览态文案判定(**一处实现**:聊天对话框调用它;将来设置页若要复用同一口径也走这里)。
|
|
543
|
+
*
|
|
544
|
+
* @param input.workspace preview.workspace(缺失按「不可用」处理:没有记录信息 ⇒ 不能说「一致」)
|
|
545
|
+
* @param input.session preview.session(只有 `restorable === true` 才算「会话仍会回退」)
|
|
546
|
+
* @param input.segments 段摘要(`preview.point.segments`;只用于 ① 的原因键回落,读 reasonCode 字段)
|
|
547
|
+
* @param input.canRestore 宿主/判定层的回退门(缺省按 true;传 false 时不得声称「会话仍会回退」)
|
|
548
|
+
*/
|
|
549
|
+
export function rollbackPreviewNote(input) {
|
|
550
|
+
const workspace = input.workspace ?? null;
|
|
551
|
+
const session = input.session ?? null;
|
|
552
|
+
const overwrite = countOf(workspace?.overwrite);
|
|
553
|
+
const different = countOf(workspace?.different);
|
|
554
|
+
const missing = countOf(workspace?.missing);
|
|
555
|
+
/**
|
|
556
|
+
* **会被删除**的条数(= 该点未记录 **且**过了安全判据的那些)。
|
|
557
|
+
*
|
|
558
|
+
* 为什么它必须参与「这句话说得说不说得」:宿主 rewind 的 `removeCreated` **缺省为真**
|
|
559
|
+
* (src/routes/recovery.ts:459),所以「工作区与该点一致,无需恢复」在会删文件的点上同样是**假尾句**
|
|
560
|
+
* —— 点下去真的会删文件(用户 2026-10-12 第 6 条)。
|
|
561
|
+
*
|
|
562
|
+
* 判据**只能**取 `removalsCount` / `removals`(引擎 `planCreatedRemovals` 的同一份规划):
|
|
563
|
+
* 拿「未记录总数」(`createdAfter` / `leftovers`)当删除数会**双向出错** —— 它把永不删除的
|
|
564
|
+
* `.git` 路径也算进去,又把证明名单不完整时「一条都不删」的情形说成「工作区会变」。
|
|
565
|
+
*/
|
|
566
|
+
const removals = Math.max(countOf(workspace?.removalsCount), Array.isArray(workspace?.removals) ? workspace.removals.length : 0);
|
|
567
|
+
// 「会话仍会回退」两个条件都要:该段可回退 + 该点整体可回退(受保护点/两态不全时按钮本来就点不了,
|
|
568
|
+
// 这时说「会话仍会回退到该点之前」同样是假话)
|
|
569
|
+
const sessionForks = input.canRestore !== false && session?.restorable === true;
|
|
570
|
+
// ① 工作区态没有可用记录
|
|
571
|
+
if (workspace?.available !== true) {
|
|
572
|
+
const reasonKey = previewNoteReasonKey(workspace, input.segments ?? undefined);
|
|
573
|
+
return {
|
|
574
|
+
kind: 'workspace-unavailable',
|
|
575
|
+
key: sessionForks ? 'rollback.preview.files.unavailableSession' : 'rollback.preview.files.unavailable',
|
|
576
|
+
reasonKey,
|
|
577
|
+
changesSomething: sessionForks,
|
|
578
|
+
sessionForks,
|
|
579
|
+
};
|
|
580
|
+
}
|
|
581
|
+
// ⓪ 工作区态可用,但该点**未记录**的新建内容会被删掉 ⇒ 工作区真会变,
|
|
582
|
+
// 「一致 / 无需恢复」在这里也是假尾句(宁可不说话:删掉的文件由列表逐条说出来)。
|
|
583
|
+
if (removals > 0) {
|
|
584
|
+
return { kind: 'workspace-changes', key: null, reasonKey: null, changesSomething: true, sessionForks };
|
|
585
|
+
}
|
|
586
|
+
// ② 该点没记录任何文件
|
|
587
|
+
if (overwrite === 0) {
|
|
588
|
+
return {
|
|
589
|
+
kind: 'no-recorded-files',
|
|
590
|
+
key: sessionForks ? 'rollback.preview.files.noFilesSession' : 'rollback.preview.result.noFiles',
|
|
591
|
+
reasonKey: null,
|
|
592
|
+
changesSomething: sessionForks,
|
|
593
|
+
sessionForks,
|
|
594
|
+
};
|
|
595
|
+
}
|
|
596
|
+
// 工作区真会写文件 ⇒ 没有「一致」句(由文件列表说话);这也是假尾句禁令的前置保护
|
|
597
|
+
if (different > 0 || missing > 0) {
|
|
598
|
+
return { kind: 'workspace-changes', key: null, reasonKey: null, changesSomething: true, sessionForks };
|
|
599
|
+
}
|
|
600
|
+
// ③a 工作区一致,但会话仍会回退 ⇒ 只说工作区(**禁止**「无需恢复」:点下去真会 fork)
|
|
601
|
+
if (sessionForks) {
|
|
602
|
+
return { kind: 'workspace-consistent-session', key: 'rollback.preview.files.consistentSession', reasonKey: null, changesSomething: true, sessionForks: true };
|
|
603
|
+
}
|
|
604
|
+
// ③b 工作区一致且无事可做
|
|
605
|
+
return { kind: 'workspace-consistent-noop', key: 'rollback.preview.files.empty', reasonKey: null, changesSomething: false, sessionForks: false };
|
|
606
|
+
}
|
|
607
|
+
/**
|
|
608
|
+
* 预览里的「可回退 / 可删除」判定。
|
|
609
|
+
*
|
|
610
|
+
* **原因键按宿主真实形状分派**(引擎 preview 恒回 `code:'ok'`,靠 canRestore/canRemove 表达拒绝 ——
|
|
611
|
+
* 见 src/core/rollback-point.ts:1530-1531:`canRemove = !protected && kind!=='guard'`(删除门)与
|
|
612
|
+
* `canRestore = !protected && !incomplete && (workspaceRestorable || sessionRestorable)`(回退门)**互相独立**):
|
|
613
|
+
* ① 受保护点 → rollback.code.protected-point(引擎 remove/rewind 都拒)
|
|
614
|
+
* ② 两态不全 → rollback.preview.reason.incomplete(引擎要 allowPartial 才放行)
|
|
615
|
+
* ③ 一段都不可回退 → 段自己的 reasonCode;没有就 rollback.preview.none
|
|
616
|
+
* ④ 宿主给了非 ok 的 code → 该 code 的键
|
|
617
|
+
* ⑤ 兜底:救援点 → rollback.preview.reason.guard(宿主拒绝了本可回退的救援点);其余 → rollback.preview.reason.hostRefused
|
|
618
|
+
* —— **绝不回落 rollback.code.unknown**
|
|
619
|
+
* 救援点(guard)在 ①②③ 之外**是可回退的**(t22):它只在宿主真的拒绝时落到 ⑤。
|
|
620
|
+
*/
|
|
621
|
+
export function rollbackPreviewVerdict(preview) {
|
|
622
|
+
if (preview === null) {
|
|
623
|
+
return { canRestore: false, canRemove: false, reasonKey: ROLLBACK_CODE_UNKNOWN_KEY, mode: 'files+session', segments: [], incomplete: false };
|
|
624
|
+
}
|
|
625
|
+
const segments = [];
|
|
626
|
+
if (preview.workspace !== undefined && preview.workspace !== null && segmentAvailable(preview.workspace))
|
|
627
|
+
segments.push('workspace');
|
|
628
|
+
if (preview.session !== undefined && preview.session !== null && segmentAvailable(preview.session))
|
|
629
|
+
segments.push('session');
|
|
630
|
+
const point = preview.point ?? preview.summary ?? null;
|
|
631
|
+
const protectedPoint = point?.protected === true;
|
|
632
|
+
const guardPoint = point?.kind === 'guard';
|
|
633
|
+
const incomplete = point?.incomplete === true;
|
|
634
|
+
const canRestore = preview.canRestore === true && segments.length > 0;
|
|
635
|
+
let reasonKey = null;
|
|
636
|
+
if (!canRestore) {
|
|
637
|
+
const segmentReason = preview.session?.reasonCode ?? preview.workspace?.reasonCode;
|
|
638
|
+
if (protectedPoint)
|
|
639
|
+
reasonKey = 'rollback.code.protected-point';
|
|
640
|
+
else if (incomplete)
|
|
641
|
+
reasonKey = ROLLBACK_PREVIEW_REASON_INCOMPLETE_KEY;
|
|
642
|
+
else if (segments.length === 0) {
|
|
643
|
+
reasonKey = typeof segmentReason === 'string' && segmentReason !== '' ? rollbackCodeKey(segmentReason) : 'rollback.preview.none';
|
|
644
|
+
}
|
|
645
|
+
else if (preview.code !== undefined && preview.code !== '' && preview.code !== 'ok')
|
|
646
|
+
reasonKey = rollbackCodeKey(preview.code);
|
|
647
|
+
// 兜底:宿主拒绝了一个「看起来可恢复」的点。救援点在这里单独说清(它本来是可回退目标,t22)——
|
|
648
|
+
// 绝不把它写成「受保护」或「一律不可回退」。
|
|
649
|
+
else
|
|
650
|
+
reasonKey = guardPoint ? ROLLBACK_PREVIEW_REASON_GUARD_KEY : ROLLBACK_PREVIEW_REASON_HOST_REFUSED_KEY;
|
|
651
|
+
}
|
|
652
|
+
return {
|
|
653
|
+
canRestore,
|
|
654
|
+
canRemove: preview.canRemove === true,
|
|
655
|
+
reasonKey,
|
|
656
|
+
mode: rollbackModeFromSegments(segments),
|
|
657
|
+
segments,
|
|
658
|
+
incomplete,
|
|
659
|
+
};
|
|
660
|
+
}
|
|
661
|
+
export function rollbackRewindRows(result) {
|
|
662
|
+
if (result === null)
|
|
663
|
+
return [];
|
|
664
|
+
const segments = Array.isArray(result.segments) ? result.segments : [];
|
|
665
|
+
return segments.map((segment) => {
|
|
666
|
+
const status = segment.status === 'restored' ? 'restored' : segment.status === 'skipped' ? 'skipped' : segment.status === 'failed' ? 'failed' : 'unknown';
|
|
667
|
+
const statusKey = status === 'unknown' ? 'rollback.segment.unknown' : 'rollback.segment.' + status;
|
|
668
|
+
const badgeKind = status === 'restored' ? 'ok' : status === 'skipped' ? 'warn' : 'error';
|
|
669
|
+
const leftovers = redactLines(segment.leftovers);
|
|
670
|
+
const childSessionId = typeof segment.childSessionId === 'string' && segment.childSessionId !== '' ? segment.childSessionId : null;
|
|
671
|
+
return {
|
|
672
|
+
state: segment.state,
|
|
673
|
+
labelKey: rollbackStateKey(segment.state),
|
|
674
|
+
statusKey,
|
|
675
|
+
badgeKind,
|
|
676
|
+
restored: numberOrNull(segment.restored) ?? 0,
|
|
677
|
+
failed: numberOrNull(segment.failed) ?? 0,
|
|
678
|
+
leftoversCount: Math.max(leftovers.length, countOf(segment.createdAfter)),
|
|
679
|
+
removedCount: countOf(segment.removed),
|
|
680
|
+
leftovers,
|
|
681
|
+
leftoversTruncated: segment.leftoversTruncated === true || segment.createdAfterTruncated === true,
|
|
682
|
+
childSessionId,
|
|
683
|
+
detail: redactLines(segment.detail),
|
|
684
|
+
guidance: rollbackGuidanceRows(segment.guidance),
|
|
685
|
+
};
|
|
686
|
+
});
|
|
687
|
+
}
|
|
688
|
+
/** 回退总体结论 → 字典键 + 徽章语义(未知回落 unknown,绝不裸渲染 outcome)。 */
|
|
689
|
+
export function rollbackOutcome(outcome) {
|
|
690
|
+
if (outcome === 'restored')
|
|
691
|
+
return { key: 'rollback.outcome.restored', badgeKind: 'ok' };
|
|
692
|
+
if (outcome === 'partial')
|
|
693
|
+
return { key: 'rollback.outcome.partial', badgeKind: 'warn' };
|
|
694
|
+
if (outcome === 'failed' || outcome === 'denied')
|
|
695
|
+
return { key: 'rollback.outcome.' + outcome, badgeKind: 'error' };
|
|
696
|
+
return { key: 'rollback.outcome.unknown', badgeKind: 'error' };
|
|
697
|
+
}
|
|
698
|
+
/** 回退结果汇总计数(界面一句话说清「恢复多少 / 失败多少 / 没删多少」)。 */
|
|
699
|
+
export function rollbackRewindCounts(result) {
|
|
700
|
+
const rows = rollbackRewindRows(result);
|
|
701
|
+
let restored = 0;
|
|
702
|
+
let failed = 0;
|
|
703
|
+
let leftovers = 0;
|
|
704
|
+
let removed = 0;
|
|
705
|
+
for (const row of rows) {
|
|
706
|
+
restored += row.restored;
|
|
707
|
+
failed += row.failed;
|
|
708
|
+
leftovers += row.leftoversCount;
|
|
709
|
+
removed += row.removedCount;
|
|
710
|
+
}
|
|
711
|
+
return { restored, failed, leftovers, removed };
|
|
712
|
+
}
|
|
713
|
+
/**
|
|
714
|
+
* 会话回退的 fork 结果(S3):原会话日志**只读**,回退产出的是一个新子会话。
|
|
715
|
+
* 返回 null = 本次没有会话回退(界面不渲染这一行,绝不编造子会话 id)。
|
|
716
|
+
*/
|
|
717
|
+
export function rollbackChildSession(result) {
|
|
718
|
+
const direct = result?.childSessionId;
|
|
719
|
+
if (typeof direct === 'string' && direct !== '')
|
|
720
|
+
return { key: 'rollback.rewind.result.child', id: direct };
|
|
721
|
+
for (const segment of result?.segments ?? []) {
|
|
722
|
+
const id = segment.childSessionId;
|
|
723
|
+
if (typeof id === 'string' && id !== '')
|
|
724
|
+
return { key: 'rollback.rewind.result.child', id };
|
|
725
|
+
}
|
|
726
|
+
return null;
|
|
727
|
+
}
|
|
728
|
+
/** 本次因前段失败 / 未请求而未执行的段(只可能是两态;未知段回落 unknown 键)。 */
|
|
729
|
+
export function rollbackSkippedSegments(result) {
|
|
730
|
+
const segments = Array.isArray(result?.skippedSegments) ? result.skippedSegments : [];
|
|
731
|
+
return segments.map((state) => ({ state, key: rollbackStateKey(state) }));
|
|
732
|
+
}
|
|
733
|
+
/* ------------------------------------------------------------ 文案键常量(面板复用) */
|
|
734
|
+
/** 删除成功文案键(delete 的 `code:'deleted'`)。 */
|
|
735
|
+
export const ROLLBACK_DELETED_KEY = 'rollback.code.deleted';
|
|
736
|
+
/** 「没有可回退的内容」文案键(预览判定为否时的兜底原因)。 */
|
|
737
|
+
export const ROLLBACK_PREVIEW_NONE_KEY = 'rollback.preview.none';
|
|
738
|
+
//# sourceMappingURL=rollback-view.js.map
|