dsh-config-manager 0.1.71 → 0.1.72
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +111 -96
- package/README.zh-CN.md +207 -86
- package/lib/adapters/agent-instructions.js +1 -1
- package/lib/adapters/file-collection.js +1 -1
- package/lib/adapters/index.d.ts +1 -1
- package/lib/adapters/index.js +1 -1
- package/lib/adapters/link-report.d.ts +1 -0
- package/lib/adapters/link-report.js +27 -3
- package/lib/adapters/messages.d.ts +18 -0
- package/lib/adapters/messages.js +50 -0
- package/lib/adapters/plugin-files.js +3 -2
- package/lib/adapters/plugins.js +4 -3
- package/lib/adapters/prompts.d.ts +1 -0
- package/lib/adapters/prompts.js +7 -1
- package/lib/adapters/self.d.ts +7 -1
- package/lib/adapters/self.js +1 -1
- package/lib/adapters/sessions.d.ts +1 -0
- package/lib/adapters/sessions.js +4 -2
- package/lib/adapters/ui.d.ts +2 -2
- package/lib/adapters/ui.js +25 -25
- package/lib/adapters/workspaces.d.ts +1 -0
- package/lib/adapters/workspaces.js +7 -3
- package/lib/cli/actions.d.ts +9 -1
- package/lib/cli/actions.js +5 -2
- package/lib/cli/help.js +23 -10
- package/lib/cli/index.d.ts +23 -0
- package/lib/cli/index.js +90 -32
- package/lib/cli/sessions-inspect.js +3 -1
- package/lib/cli/sessions-repair.js +25 -6
- package/lib/cli/web/page.d.ts +12 -0
- package/lib/cli/web/page.js +21 -12
- package/lib/cli/web/routes.js +2 -2
- package/lib/cli/web/server.js +4 -4
- package/lib/client.d.ts +1349 -490
- package/lib/client.js +43047 -36738
- package/lib/core/backup-plan.js +30 -17
- package/lib/core/backup-verify.js +24 -23
- package/lib/core/backup.d.ts +5 -0
- package/lib/core/backup.js +51 -25
- package/lib/core/boot-rescue.js +4 -3
- package/lib/core/boot-safety.js +2 -1
- package/lib/core/command-rollback.d.ts +178 -0
- package/lib/core/command-rollback.js +289 -0
- package/lib/core/consult-source.js +7 -6
- package/lib/core/disk-usage.d.ts +74 -10
- package/lib/core/disk-usage.js +67 -4
- package/lib/core/engine-locale.d.ts +87 -0
- package/lib/core/engine-locale.js +125 -0
- package/lib/core/exporter.js +3 -3
- package/lib/core/fork-reconcile.d.ts +128 -0
- package/lib/core/fork-reconcile.js +150 -0
- package/lib/core/journal.js +6 -5
- package/lib/core/local-plugin-host.js +3 -2
- package/lib/core/local-plugin-pack.js +11 -6
- package/lib/core/messages-core.d.ts +292 -0
- package/lib/core/messages-core.js +615 -0
- package/lib/core/messages.d.ts +60 -2
- package/lib/core/messages.js +116 -13
- package/lib/core/migration-consult.js +21 -20
- package/lib/core/migration-history.js +19 -5
- package/lib/core/model-tools.d.ts +0 -22
- package/lib/core/model-tools.js +50 -27
- package/lib/core/phase3-host.js +30 -5
- package/lib/core/plugin-cli.js +18 -19
- package/lib/core/reconcile.d.ts +2 -14
- package/lib/core/reconcile.js +87 -17
- package/lib/core/recovery-orchestrator.js +19 -18
- package/lib/core/reinstall.d.ts +4 -0
- package/lib/core/reinstall.js +26 -21
- package/lib/core/restore.js +20 -17
- package/lib/core/rollback-ignore-rules.d.ts +31 -0
- package/lib/core/rollback-ignore-rules.js +140 -0
- package/lib/core/rollback-point.d.ts +1208 -0
- package/lib/core/rollback-point.js +2993 -0
- package/lib/core/run-registry.js +6 -5
- package/lib/core/session-health.d.ts +15 -0
- package/lib/core/session-meta.d.ts +42 -2
- package/lib/core/session-meta.js +140 -1
- package/lib/core/session-title.d.ts +28 -0
- package/lib/core/session-title.js +185 -0
- package/lib/core/startup-barrier.d.ts +24 -0
- package/lib/core/startup-barrier.js +22 -0
- package/lib/core/transaction-coordinator.js +4 -3
- package/lib/core/types.d.ts +18 -1
- package/lib/core/validator.js +5 -4
- package/lib/core/verify-recovery.js +31 -30
- package/lib/foreign/bundle.js +3 -2
- package/lib/foreign/continue.js +2 -1
- package/lib/foreign/crush.js +2 -1
- package/lib/foreign/gemini.js +2 -1
- package/lib/foreign/goose.js +2 -1
- package/lib/foreign/kilocode.js +2 -1
- package/lib/foreign/kimi.js +2 -1
- package/lib/foreign/messages.d.ts +15 -0
- package/lib/foreign/messages.js +40 -0
- package/lib/foreign/mimocode.js +2 -1
- package/lib/foreign/openclaw.js +2 -1
- package/lib/foreign/opencode.js +2 -1
- package/lib/foreign/pi.js +2 -1
- package/lib/foreign/qoder.js +2 -1
- package/lib/foreign/qwen.js +2 -1
- package/lib/foreign/registry.js +7 -6
- package/lib/foreign/teleagent.js +2 -1
- package/lib/foreign/workbuddy.js +2 -1
- package/lib/foreign/zcode.js +2 -1
- package/lib/foreign/zed.js +2 -1
- package/lib/index.d.ts +223 -7
- package/lib/index.js +729 -54
- package/lib/market/git-file-writer.js +20 -19
- package/lib/market/github-repos.js +35 -19
- package/lib/market/index-parser.js +31 -30
- package/lib/market/messages-market.d.ts +138 -0
- package/lib/market/messages-market.js +305 -0
- package/lib/market/my-repo.d.ts +2 -2
- package/lib/market/my-repo.js +32 -29
- package/lib/market/prepare.js +18 -15
- package/lib/market/security.js +20 -19
- package/lib/market/types.js +2 -1
- package/lib/market/url.js +2 -1
- package/lib/migrations/index.js +6 -5
- package/lib/migrations/messages-migrations.d.ts +32 -0
- package/lib/migrations/messages-migrations.js +58 -0
- package/lib/profiles/dsh-profile-launcher.js +2 -1
- package/lib/profiles/dsh-profile-runtime.js +2 -1
- package/lib/profiles/messages.d.ts +7 -0
- package/lib/profiles/messages.js +27 -0
- package/lib/routes/backup.js +18 -2
- package/lib/routes/import.js +3 -3
- package/lib/routes/index.d.ts +1 -1
- package/lib/routes/kit.d.ts +3 -2
- package/lib/routes/kit.js +6 -1
- package/lib/routes/market.js +1 -1
- package/lib/routes/messages.d.ts +42 -0
- package/lib/routes/messages.js +100 -0
- package/lib/routes/profiles.js +12 -6
- package/lib/routes/recovery.js +378 -114
- package/lib/routes/snapshots.js +51 -4
- package/lib/routes/sync.js +68 -16
- package/lib/schema/config.d.ts +11 -1
- package/lib/schema/config.js +52 -20
- package/lib/schema/index.d.ts +8 -6
- package/lib/schema/index.js +8 -6
- package/lib/schema/manifest.js +19 -18
- package/lib/schema/messages.d.ts +47 -0
- package/lib/schema/messages.js +110 -0
- package/lib/schema/section-registry.d.ts +41 -14
- package/lib/schema/section-registry.js +35 -29
- package/lib/schema/versions.js +6 -5
- package/lib/security/encryption.js +16 -15
- package/lib/security/integrity.js +8 -7
- package/lib/security/messages.d.ts +30 -0
- package/lib/security/messages.js +77 -0
- package/lib/security/redaction.js +12 -0
- package/lib/security/secret-scanner.js +16 -13
- package/lib/security/vault.js +2 -1
- package/lib/security/zip-security.js +4 -3
- package/lib/sync/ancestor.js +2 -1
- package/lib/sync/autosync-config.d.ts +11 -0
- package/lib/sync/autosync-config.js +12 -0
- package/lib/sync/autosync-scheduler.d.ts +11 -1
- package/lib/sync/autosync-scheduler.js +115 -76
- package/lib/sync/backup-files.js +3 -2
- package/lib/sync/backup-scheduler.js +6 -5
- package/lib/sync/blob-store.js +2 -1
- package/lib/sync/git/git-transport.d.ts +55 -0
- package/lib/sync/git/git-transport.js +152 -2
- package/lib/sync/github-auth.js +10 -9
- package/lib/sync/layout.js +13 -12
- package/lib/sync/messages.d.ts +63 -0
- package/lib/sync/messages.js +149 -0
- package/lib/sync/review-queue.js +6 -5
- package/lib/sync/s3/messages.d.ts +7 -0
- package/lib/sync/s3/messages.js +16 -0
- package/lib/sync/s3/s3-providers.d.ts +4 -1
- package/lib/sync/s3/s3-providers.js +5 -5
- package/lib/sync/s3/s3-transport.js +1 -1
- package/lib/sync/s3/sigv4.js +2 -1
- package/lib/sync/snapshot-crypto.js +6 -5
- package/lib/sync/snapshot-json.js +3 -2
- package/lib/sync/sync-engine.d.ts +37 -15
- package/lib/sync/sync-engine.js +67 -37
- package/lib/sync/sync-history.js +7 -6
- package/lib/sync/sync-selection.d.ts +15 -4
- package/lib/sync/sync-selection.js +17 -5
- package/lib/sync/webdav/webdav-transport.d.ts +1 -0
- package/lib/sync/webdav/webdav-transport.js +4 -1
- package/lib/ui/artifact-view.js +4 -3
- package/lib/ui/commands.d.ts +1 -1
- package/lib/ui/commands.js +10 -0
- package/lib/ui/disk-usage-view.d.ts +33 -4
- package/lib/ui/disk-usage-view.js +29 -1
- package/lib/ui/errors.d.ts +1 -1
- package/lib/ui/errors.js +4 -3
- package/lib/ui/export-flow.js +24 -20
- package/lib/ui/foreign-view.d.ts +5 -1
- package/lib/ui/foreign-view.js +26 -2
- package/lib/ui/i18n.d.ts +65 -0
- package/lib/ui/i18n.js +114 -0
- package/lib/ui/import-wizard.d.ts +7 -0
- package/lib/ui/import-wizard.js +13 -10
- package/lib/ui/progress.d.ts +2 -2
- package/lib/ui/progress.js +1 -2
- package/lib/ui/report.d.ts +5 -5
- package/lib/ui/report.js +6 -6
- package/lib/ui/rollback-view.d.ts +756 -0
- package/lib/ui/rollback-view.js +724 -0
- package/lib/ui/selection-model.d.ts +41 -0
- package/lib/ui/selection-model.js +30 -0
- package/lib/ui/session-inventory-view.d.ts +97 -3
- package/lib/ui/session-inventory-view.js +76 -2
- package/lib/ui/session-issue-labels.d.ts +43 -0
- package/lib/ui/session-issue-labels.js +48 -0
- package/lib/ui/sync-settings-view.d.ts +33 -0
- package/lib/ui/sync-settings-view.js +40 -0
- package/lib/ui/types.d.ts +8 -3
- package/lib/ui/types.js +1 -1
- package/lib/utils/atomic-write.js +6 -5
- package/lib/utils/bundle-scan.d.ts +11 -1
- package/lib/utils/bundle-scan.js +12 -1
- package/lib/utils/env-lock.d.ts +10 -0
- package/lib/utils/env-lock.js +73 -46
- package/lib/utils/json.js +6 -5
- package/lib/utils/messages-utils.d.ts +96 -0
- package/lib/utils/messages-utils.js +216 -0
- package/lib/utils/proxy.js +18 -5
- package/lib/utils/recursive-walk.d.ts +22 -0
- package/lib/utils/recursive-walk.js +75 -1
- package/lib/utils/session-log-repair.js +10 -9
- package/lib/utils/zip.js +27 -26
- package/package.json +12 -2
- package/src/adapters/agent-instructions.ts +1 -1
- package/src/adapters/file-collection.test.ts +1 -0
- package/src/adapters/file-collection.ts +1 -1
- package/src/adapters/files.test.ts +27 -0
- package/src/adapters/index.ts +1 -1
- package/src/adapters/link-report.test.ts +22 -2
- package/src/adapters/link-report.ts +29 -4
- package/src/adapters/messages.ts +54 -0
- package/src/adapters/plugin-files.ts +3 -2
- package/src/adapters/plugins.ts +4 -3
- package/src/adapters/prompts.ts +7 -1
- package/src/adapters/self.test.ts +26 -0
- package/src/adapters/self.ts +8 -2
- package/src/adapters/sessions.ts +4 -2
- package/src/adapters/settings.test.ts +38 -1
- package/src/adapters/ui.test.ts +2 -2
- package/src/adapters/ui.ts +27 -26
- package/src/adapters/workspaces.ts +7 -3
- package/src/cli/actions.test.ts +28 -0
- package/src/cli/actions.ts +11 -2
- package/src/cli/help.ts +23 -10
- package/src/cli/index-guards.test.ts +193 -0
- package/src/cli/index.ts +106 -31
- package/src/cli/sessions-inspect.test.ts +15 -1
- package/src/cli/sessions-inspect.ts +3 -1
- package/src/cli/sessions-repair.test.ts +84 -0
- package/src/cli/sessions-repair.ts +33 -7
- package/src/cli/web/console-route-parity.test.ts +115 -0
- package/src/cli/web/page.ts +23 -12
- package/src/cli/web/routes.ts +2 -1
- package/src/cli/web/server.ts +4 -4
- package/src/cli/web/web.test.ts +2 -2
- package/src/client/ConfigManagerSection.tsx +30 -2
- package/src/client/about/AboutPanel.tsx +2 -2
- package/src/client/about/PluginUpdateDialog.tsx +1 -1
- package/src/client/about/ReleaseNotesDialog.tsx +2 -2
- package/src/client/about/about-view.test.ts +13 -3
- package/src/client/about/about-view.ts +10 -6
- package/src/client/about/release-notes-view.test.ts +14 -4
- package/src/client/about/release-notes-view.ts +4 -2
- package/src/client/api.ts +18 -6
- package/src/client/chat/ChatEntryView.tsx +391 -0
- package/src/client/chat/RollbackDiffTab.tsx +61 -0
- package/src/client/chat/RollbackFileIcon.tsx +96 -0
- package/src/client/chat/RollbackReviewDialog.tsx +591 -0
- package/src/client/chat/chat-entry-view.test.ts +1174 -0
- package/src/client/chat/chat-entry-view.ts +758 -0
- package/src/client/chat/chat-locales.ts +152 -0
- package/src/client/chat/chat-node-registration.ts +64 -0
- package/src/client/chat/chat-point-window.ts +173 -0
- package/src/client/chat/chat-slots.ts +107 -0
- package/src/client/chat/rollback-diff-tab.test.ts +105 -0
- package/src/client/chat/rollback-diff-tab.ts +164 -0
- package/src/client/chat/rollback-entry-view.test.ts +1469 -0
- package/src/client/chat/rollback-entry-view.ts +941 -0
- package/src/client/chat/sidebar-slots.ts +37 -0
- package/src/client/common/ConfirmDialog.tsx +7 -3
- package/src/client/common/ContentPicker.tsx +48 -10
- package/src/client/common/DiffTable.tsx +97 -0
- package/src/client/common/ErrorBanner.tsx +4 -4
- package/src/client/common/Icon.tsx +2 -0
- package/src/client/common/Modal.tsx +8 -1
- package/src/client/common/ProgressBar.tsx +8 -2
- package/src/client/common/ReportView.tsx +4 -4
- package/src/client/common/RunsCenter.tsx +1 -0
- package/src/client/common/client-route-parity.test.ts +78 -0
- package/src/client/common/info-hint-guard.test.ts +46 -13
- package/src/client/common/picker-lazy-units.test.ts +42 -0
- package/src/client/common/plan-text-redaction.test.ts +17 -6
- package/src/client/common/progress-view-edge.test.ts +8 -7
- package/src/client/common/progress-view.test.ts +9 -9
- package/src/client/common/progress-view.ts +2 -2
- package/src/client/common/routes.ts +28 -0
- package/src/client/common/ui.tsx +73 -10
- package/src/client/config-manager.module.css +996 -21
- package/src/client/css-scale-guard.test.ts +30 -0
- package/src/client/environment/EnvironmentPanel.tsx +11 -4
- package/src/client/environment/maintenance/DiskUsageCard.tsx +23 -2
- package/src/client/export/ExportView.tsx +13 -8
- package/src/client/history/HistoryPanel.tsx +4 -3
- package/src/client/history/history-api.ts +2 -2
- package/src/client/history/history-locales.ts +5 -1
- package/src/client/home/HomePanel.tsx +29 -8
- package/src/client/import/ForeignImportView.tsx +4 -5
- package/src/client/import/ImportWizardView.tsx +5 -3
- package/src/client/import/import-wizard-split.test.ts +3 -1
- package/src/client/import/import-wizard-steps.tsx +9 -9
- package/src/client/index.ts +186 -3
- package/src/client/library/LibraryActions.tsx +32 -7
- package/src/client/library/LibraryPanel.tsx +23 -3
- package/src/client/library/RestorePlanView.tsx +9 -66
- package/src/client/locales.ts +54 -5
- package/src/client/lucide-icons.d.ts +1 -0
- package/src/client/market/MarketPanel.tsx +13 -2
- package/src/client/market/MyConfigsView.tsx +5 -4
- package/src/client/market/MyConfigsWizard.tsx +10 -8
- package/src/client/market/market-api.test.ts +9 -8
- package/src/client/market/market-api.ts +2 -2
- package/src/client/market/market-locales.ts +3 -0
- package/src/client/market/market-view.test.ts +6 -6
- package/src/client/market/market-view.ts +3 -3
- package/src/client/market/my-configs-api.ts +2 -2
- package/src/client/market/my-configs-view.test.ts +23 -22
- package/src/client/market/my-configs-view.ts +8 -8
- package/src/client/recovery/IncompleteCopiesSection.tsx +2 -1
- package/src/client/recovery/RecoveryPanel.tsx +442 -283
- package/src/client/recovery/RollbackCard.tsx +665 -0
- package/src/client/recovery/RollbackDiffPane.tsx +58 -0
- package/src/client/recovery/RollbackFileChanges.tsx +163 -0
- package/src/client/recovery/incident-api.ts +2 -2
- package/src/client/recovery/recovery-api.ts +116 -6
- package/src/client/recovery/recovery-locales.test.ts +299 -0
- package/src/client/recovery/recovery-locales.ts +488 -149
- package/src/client/recovery/recovery-panel-layout.test.ts +132 -2
- package/src/client/recovery/rollback-file-changes.test.ts +73 -0
- package/src/client/recovery/session-dialog-mount.test.ts +54 -0
- package/src/client/run-store.ts +84 -21
- package/src/client/sync/AutosyncCard.tsx +25 -1
- package/src/client/sync/ChannelClearConfirmDialog.tsx +6 -3
- package/src/client/sync/ChannelConfigDialog.tsx +10 -10
- package/src/client/sync/DecryptPasswordCard.tsx +2 -2
- package/src/client/sync/SecurityOptionsCard.tsx +27 -17
- package/src/client/sync/SyncChannelEntryCard.tsx +14 -2
- package/src/client/sync/SyncConfirmView.tsx +46 -8
- package/src/client/sync/SyncLogList.tsx +1 -1
- package/src/client/sync/SyncPanel.tsx +31 -23
- package/src/client/sync/SyncRepositoryPicker.tsx +3 -5
- package/src/client/sync/SyncSectionPickerDialog.tsx +11 -2
- package/src/client/sync/SyncSettingsView.test.ts +20 -1
- package/src/client/sync/UnreadableSnapshotsBanner.tsx +1 -1
- package/src/client/sync/WorkDirCard.tsx +112 -0
- package/src/client/sync/history-model.test.ts +42 -9
- package/src/client/sync/history-model.ts +30 -15
- package/src/client/sync/sync-api.test.ts +57 -33
- package/src/client/sync/sync-api.ts +80 -6
- package/src/client/sync/sync-locales.ts +104 -3
- package/src/client/sync/sync-view-v2.test.ts +65 -13
- package/src/client/sync/sync-view.test.ts +107 -52
- package/src/client/sync/sync-view.ts +124 -41
- package/src/core/backup-plan.test.ts +28 -0
- package/src/core/backup-plan.ts +29 -17
- package/src/core/backup-verify.ts +24 -23
- package/src/core/backup.test.ts +76 -0
- package/src/core/backup.ts +55 -25
- package/src/core/boot-rescue.ts +4 -3
- package/src/core/boot-safety.ts +2 -1
- package/src/core/command-rollback.test.ts +536 -0
- package/src/core/command-rollback.ts +402 -0
- package/src/core/consult-source.ts +7 -6
- package/src/core/disk-usage.test.ts +257 -1
- package/src/core/disk-usage.ts +134 -14
- package/src/core/engine-locale.test.ts +593 -0
- package/src/core/engine-locale.ts +175 -0
- package/src/core/exporter.ts +3 -3
- package/src/core/fork-reconcile.test.ts +279 -0
- package/src/core/fork-reconcile.ts +234 -0
- package/src/core/journal.ts +6 -5
- package/src/core/local-plugin-host.ts +3 -2
- package/src/core/local-plugin-pack.ts +11 -6
- package/src/core/messages-core.ts +657 -0
- package/src/core/messages.ts +123 -17
- package/src/core/migration-consult.ts +21 -20
- package/src/core/migration-history.ts +19 -5
- package/src/core/model-tools.ts +28 -27
- package/src/core/phase3-host.ts +21 -5
- package/src/core/phase3-p2b-fingerprint.test.ts +22 -0
- package/src/core/plugin-cli.ts +18 -19
- package/src/core/reconcile.test.ts +117 -0
- package/src/core/reconcile.ts +85 -17
- package/src/core/recovery-orchestrator.ts +19 -18
- package/src/core/reinstall.ts +26 -21
- package/src/core/restore.ts +20 -17
- package/src/core/rollback-file-diff.test.ts +159 -0
- package/src/core/rollback-ignore-rules.ts +126 -0
- package/src/core/rollback-point.test.ts +2603 -0
- package/src/core/rollback-point.ts +3755 -0
- package/src/core/run-registry.ts +6 -5
- package/src/core/session-health.ts +15 -0
- package/src/core/session-meta.test.ts +117 -1
- package/src/core/session-meta.ts +149 -2
- package/src/core/session-title.test.ts +127 -0
- package/src/core/session-title.ts +189 -0
- package/src/core/startup-barrier.test.ts +69 -0
- package/src/core/startup-barrier.ts +27 -0
- package/src/core/transaction-coordinator.ts +4 -3
- package/src/core/types.ts +18 -1
- package/src/core/validator.ts +5 -4
- package/src/core/verify-recovery.ts +31 -30
- package/src/foreign/bundle.ts +3 -2
- package/src/foreign/continue.ts +2 -1
- package/src/foreign/crush.ts +2 -1
- package/src/foreign/file-budget.test.ts +2 -0
- package/src/foreign/gemini.ts +2 -1
- package/src/foreign/goose.ts +2 -1
- package/src/foreign/kilocode.ts +2 -1
- package/src/foreign/kimi.ts +2 -1
- package/src/foreign/messages.ts +44 -0
- package/src/foreign/mimocode.ts +2 -1
- package/src/foreign/openclaw.ts +2 -1
- package/src/foreign/opencode.ts +2 -1
- package/src/foreign/pi.ts +2 -1
- package/src/foreign/qoder.ts +2 -1
- package/src/foreign/qwen.ts +2 -1
- package/src/foreign/registry.ts +7 -6
- package/src/foreign/teleagent.ts +2 -1
- package/src/foreign/workbuddy.ts +2 -1
- package/src/foreign/zcode.ts +2 -1
- package/src/foreign/zed.ts +2 -1
- package/src/index.facade.test.ts +340 -1
- package/src/index.ts +813 -57
- package/src/market/git-file-writer.ts +20 -19
- package/src/market/github-repos.test.ts +23 -0
- package/src/market/github-repos.ts +37 -19
- package/src/market/index-parser.ts +31 -30
- package/src/market/messages-market.ts +325 -0
- package/src/market/my-repo.ts +32 -29
- package/src/market/prepare.ts +18 -15
- package/src/market/security.ts +20 -19
- package/src/market/types.ts +2 -1
- package/src/market/url.ts +2 -1
- package/src/migrations/index.ts +6 -5
- package/src/migrations/messages-migrations.ts +59 -0
- package/src/profiles/dsh-profile-launcher.ts +2 -1
- package/src/profiles/dsh-profile-runtime.ts +2 -1
- package/src/profiles/messages.ts +31 -0
- package/src/routes/backup.ts +18 -2
- package/src/routes/import.ts +3 -3
- package/src/routes/index.ts +1 -1
- package/src/routes/kit.ts +9 -3
- package/src/routes/market.ts +1 -1
- package/src/routes/messages.ts +104 -0
- package/src/routes/profiles.ts +12 -6
- package/src/routes/recovery.test.ts +405 -1
- package/src/routes/recovery.ts +377 -109
- package/src/routes/snapshots.ts +51 -4
- package/src/routes/sync.ts +67 -16
- package/src/schema/config.ts +53 -20
- package/src/schema/index.ts +7 -5
- package/src/schema/manifest.ts +19 -18
- package/src/schema/messages.ts +120 -0
- package/src/schema/registry.test.ts +19 -5
- package/src/schema/section-registry.ts +64 -39
- package/src/schema/versions.ts +6 -5
- package/src/security/encryption.ts +16 -15
- package/src/security/integrity.ts +8 -7
- package/src/security/messages.ts +87 -0
- package/src/security/redaction.test.ts +17 -1
- package/src/security/redaction.ts +13 -0
- package/src/security/secret-scanner.ts +19 -12
- package/src/security/security.test.ts +47 -0
- package/src/security/vault.ts +2 -1
- package/src/security/zip-security.ts +4 -3
- package/src/sync/ancestor.ts +2 -1
- package/src/sync/autosync-config.ts +21 -0
- package/src/sync/autosync-scheduler.test.ts +63 -7
- package/src/sync/autosync-scheduler.ts +118 -78
- package/src/sync/backup-files.ts +3 -2
- package/src/sync/backup-scheduler.ts +6 -5
- package/src/sync/blob-store.ts +2 -1
- package/src/sync/git/git-transport.test.ts +170 -8
- package/src/sync/git/git-transport.ts +176 -2
- package/src/sync/github-auth.ts +10 -9
- package/src/sync/layout.ts +13 -12
- package/src/sync/messages.ts +153 -0
- package/src/sync/review-queue.ts +6 -5
- package/src/sync/s3/messages.ts +16 -0
- package/src/sync/s3/s3-providers.ts +9 -6
- package/src/sync/s3/s3-transport.ts +1 -1
- package/src/sync/s3/sigv4.ts +3 -1
- package/src/sync/snapshot-crypto.ts +6 -5
- package/src/sync/snapshot-json.ts +3 -2
- package/src/sync/sync-engine-download-tombstone.test.ts +1 -1
- package/src/sync/sync-engine.test.ts +226 -8
- package/src/sync/sync-engine.ts +103 -41
- package/src/sync/sync-history.ts +7 -6
- package/src/sync/sync-selection.test.ts +12 -2
- package/src/sync/sync-selection.ts +18 -5
- package/src/sync/webdav/webdav-transport.ts +4 -1
- package/src/ui/artifact-view.ts +4 -3
- package/src/ui/commands.test.ts +1 -0
- package/src/ui/commands.ts +11 -1
- package/src/ui/disk-usage-view.test.ts +113 -6
- package/src/ui/disk-usage-view.ts +63 -7
- package/src/ui/errors.ts +4 -3
- package/src/ui/export-flow.ts +24 -20
- package/src/ui/foreign-view.test.ts +43 -0
- package/src/ui/foreign-view.ts +30 -3
- package/src/ui/i18n.ts +125 -0
- package/src/ui/import-wizard.ts +18 -10
- package/src/ui/progress.ts +2 -2
- package/src/ui/report.ts +7 -6
- package/src/ui/rollback-view.test.ts +819 -0
- package/src/ui/rollback-view.ts +1339 -0
- package/src/ui/selection-model.test.ts +33 -1
- package/src/ui/selection-model.ts +53 -0
- package/src/ui/session-inventory-view.test.ts +109 -0
- package/src/ui/session-inventory-view.ts +138 -3
- package/src/ui/session-issue-labels.test.ts +51 -0
- package/src/ui/session-issue-labels.ts +70 -0
- package/src/ui/sync-settings-view.test.ts +36 -0
- package/src/ui/sync-settings-view.ts +49 -0
- package/src/ui/types.ts +10 -4
- package/src/utils/atomic-write.ts +6 -5
- package/src/utils/bundle-scan.test.ts +2 -2
- package/src/utils/bundle-scan.ts +12 -1
- package/src/utils/env-lock.ts +83 -49
- package/src/utils/json.ts +7 -5
- package/src/utils/messages-utils.ts +232 -0
- package/src/utils/proxy.test.ts +25 -0
- package/src/utils/proxy.ts +19 -5
- package/src/utils/recursive-walk.test.ts +43 -0
- package/src/utils/recursive-walk.ts +75 -1
- package/src/utils/session-log-repair.ts +10 -9
- package/src/utils/zip.ts +27 -26
- package/lib/core/checkpoint.d.ts +0 -362
- package/lib/core/checkpoint.js +0 -1309
- package/src/core/checkpoint.test.ts +0 -665
- package/src/core/checkpoint.ts +0 -1614
|
@@ -0,0 +1,591 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 回退预览对话框 —— **简洁版**(用户 2026-10-10 第三条要求逐字落地)。
|
|
3
|
+
*
|
|
4
|
+
* 页面只做三件事:
|
|
5
|
+
* ① 标题固定「回退预览」(`rollback.preview.title`);
|
|
6
|
+
* ② 正文只有**会被回退的文件**(路径 + 新增行数 + 删除行数,取自 E1 的
|
|
7
|
+
* `preview.workspace.files`;内容已一致的文件一项都不列;空列表 ⇒ 一行
|
|
8
|
+
* 「工作区与该回退点一致,无需恢复」);行数可能是 `null`(E1:超限 / 非文本 / 读不出),
|
|
9
|
+
* 那时该文件那格显示「行数未统计」而**不是 0**;
|
|
10
|
+
* ③ 底部只有主按钮「回退」与「取消」。
|
|
11
|
+
* 唯一允许的额外一行:**不可回退的原因**(宿主拒绝了这次回退时,主按钮是禁用的,一行字说清
|
|
12
|
+
* 「为什么点不了」;引擎恒会给出原因键,回落「原因未知」只是防御)。
|
|
13
|
+
*
|
|
14
|
+
* 有意**删除**的东西(不在这里 = 不再渲染,键值仍留在字典里供设置页回滚卡使用):
|
|
15
|
+
* 三选一 mode 单选、两态不全放行勾选、二次确认勾选、工作区/会话计数面板、救援点说明段、
|
|
16
|
+
* 逐段结果详情面板、留点期 notice 面板。**结果改为一行摘要 + 失败原因文案**(失败时仍如实
|
|
17
|
+
* 显示 code 与 detail,绝不裸渲染机器码)。
|
|
18
|
+
* **描述性文案清零(2026-10-12 用户第 8 条)**:说明行「这次回退会做以下改动:」与
|
|
19
|
+
* 「这些文件的行数没有统计出来…」整段一并删除 —— 弹窗只留能改变决定的信息(DESIGN §7);
|
|
20
|
+
* 两个键仍留在字典里(删渲染不删键,与上面那批同一个先例)。
|
|
21
|
+
*
|
|
22
|
+
* 确认语义(对上一版的有意替换):**点「回退」即视为 `userConfirmed=true`**。
|
|
23
|
+
* 对话框本身就是「预览 + 确认」两步,所以不再需要勾选框;未点之前 `confirmed` 恒 false,
|
|
24
|
+
* 连请求体都不成形(`buildRewindOptions` 的 fail-closed 一个字没动)。
|
|
25
|
+
*
|
|
26
|
+
* 为什么不用 common/Modal.tsx 或 ConfirmDialog:
|
|
27
|
+
* · `Modal` 的 Radix Portal 容器被**硬钉**在 `#dsh-config-manager-root`(插件设置页根节点,
|
|
28
|
+
* 见 common/Modal.tsx 的长注释)—— 聊天里那个节点不存在,Modal 会**什么都不渲染**
|
|
29
|
+
* (container === null 时直接 return null,这是它刻意的行为,不是 bug)。
|
|
30
|
+
* · 因此这里直接用同一个 Radix Dialog 依赖 + **同一套 CSS 类**(dialogMask/dialogCard/...),
|
|
31
|
+
* 视觉与设置页一致、零新增样式档位;Portal 走 Radix 缺省(document.body)—— 聊天区没有设置
|
|
32
|
+
* 面板那层 fixed overlay,不存在 Modal 注释里那个层级冲突。
|
|
33
|
+
*/
|
|
34
|
+
import { useEffect, useRef, useState } from 'react'
|
|
35
|
+
import * as Dialog from '@radix-ui/react-dialog'
|
|
36
|
+
import type { TranslateNS } from '../client-types.ts'
|
|
37
|
+
import { rollbackPreviewScope } from '../../ui/rollback-view.ts'
|
|
38
|
+
import type {
|
|
39
|
+
RollbackPointPort,
|
|
40
|
+
RollbackPreviewResult,
|
|
41
|
+
RollbackRewindResult,
|
|
42
|
+
} from '../../ui/rollback-view.ts'
|
|
43
|
+
import { Badge, Banner, Button, Checkbox, Empty, Spinner } from '../common/ui.tsx'
|
|
44
|
+
import type { RollbackFileIconRenderer } from './RollbackFileIcon.tsx'
|
|
45
|
+
import { RollbackDiffPane, type RollbackDiffState } from '../recovery/RollbackDiffPane.tsx'
|
|
46
|
+
import { RollbackFileChanges } from '../recovery/RollbackFileChanges.tsx'
|
|
47
|
+
import type { RollbackDiffOpenResult } from './rollback-diff-tab.ts'
|
|
48
|
+
import { CloseIcon } from '../common/Icon.tsx'
|
|
49
|
+
import { redact } from '../../security/redaction.ts'
|
|
50
|
+
import css from '../config-manager.module.css'
|
|
51
|
+
import {
|
|
52
|
+
CHAT_SESSION_ACTIVE_NOTE_KEY,
|
|
53
|
+
canRevertFiles,
|
|
54
|
+
canSubmitRewind,
|
|
55
|
+
chatRewindFailed,
|
|
56
|
+
chatRewindFailure,
|
|
57
|
+
chatRewindNotices,
|
|
58
|
+
chatRewindSummary,
|
|
59
|
+
codeKeyOfError,
|
|
60
|
+
confirmOnSubmit,
|
|
61
|
+
archiveHintKey,
|
|
62
|
+
createInFlightLock,
|
|
63
|
+
defaultDraft,
|
|
64
|
+
errorDetail,
|
|
65
|
+
findPointForTurn,
|
|
66
|
+
isSessionActive,
|
|
67
|
+
readRollbackPreviewFiles,
|
|
68
|
+
readRollbackPreviewRemovals,
|
|
69
|
+
requestRewind,
|
|
70
|
+
rollbackChildSession,
|
|
71
|
+
rollbackOutcome,
|
|
72
|
+
rollbackPreviewNote,
|
|
73
|
+
rollbackPreviewVerdict,
|
|
74
|
+
rollbackRewindCounts,
|
|
75
|
+
revertFilesOn,
|
|
76
|
+
setRevertFiles,
|
|
77
|
+
submitButtonEnabled,
|
|
78
|
+
type ArchiveOutcome,
|
|
79
|
+
type ChildOpenState,
|
|
80
|
+
type RewindDraft,
|
|
81
|
+
} from './rollback-entry-view.ts'
|
|
82
|
+
|
|
83
|
+
/** recovery 字典键的动态取键助手(键值来自判定层常量,形状已冻结)。 */
|
|
84
|
+
type RKey = Parameters<TranslateNS<'config-manager-recovery'>>[0]
|
|
85
|
+
const rk = (key: string): RKey => key as RKey
|
|
86
|
+
|
|
87
|
+
export interface RollbackReviewDialogProps {
|
|
88
|
+
/** 触发入口所属会话(session scope 标准 props)。 */
|
|
89
|
+
sessionId: string
|
|
90
|
+
/** 触发入口所在回合(回合尾按钮就在这个回合的尾部)。 */
|
|
91
|
+
turn: number
|
|
92
|
+
/** 回退端口(RecoveryApi 实现了 RollbackPointPort)。 */
|
|
93
|
+
port: RollbackPointPort
|
|
94
|
+
/** recovery 字典翻译器(回退词汇单一所有)。 */
|
|
95
|
+
rt: TranslateNS<'config-manager-recovery'>
|
|
96
|
+
/** chat 字典翻译器(聊天入口独有文案)。 */
|
|
97
|
+
t: TranslateNS<'config-manager-chat'>
|
|
98
|
+
/** 打开 fork 出来的子会话(降级态由本组件如实呈现)。 */
|
|
99
|
+
openChildSession: (childId: string) => Promise<ChildOpenState>
|
|
100
|
+
/** A(无感替换):回退后把**原会话**收进「已归档」;失败一律结构化返回,由本组件如实呈现。 */
|
|
101
|
+
archiveParentSession: (sessionId: string) => Promise<ArchiveOutcome>
|
|
102
|
+
/** 第一条消息之前没有可 fork 的边界:开一条全新会话(失败结构化返回,绝不抛)。 */
|
|
103
|
+
startFreshSession: () => Promise<{ ok: true } | { ok: false; code: string }>
|
|
104
|
+
/**
|
|
105
|
+
* 「查看」:把该文件的差异开进 DSH 右侧栏(由装配点注入;侧栏不可用时返回 no-sidebar,
|
|
106
|
+
* 本组件据此**如实降级**为弹窗内展示,绝不假装打开过)。
|
|
107
|
+
*/
|
|
108
|
+
openDiff: (pointId: string, filePath: string) => RollbackDiffOpenResult
|
|
109
|
+
/**
|
|
110
|
+
* 文件类型图标的外部来源(可选,装配点注入):装了 dsh-better-sidebar 时用它发布的
|
|
111
|
+
* `betterSidebar.fileIcon(path, 14)`,否则组件回落到 DSH 自带的 `FileTypeIcon`(见 RollbackFileIcon.tsx)。
|
|
112
|
+
*/
|
|
113
|
+
renderFileIcon?: RollbackFileIconRenderer
|
|
114
|
+
onClose: () => void
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* 回退后是否把原会话自动收进「已归档」。
|
|
119
|
+
*
|
|
120
|
+
* **默认 false**:归档当前会话会让 DSH 客户端换绑新会话,而它复用的 prepared id(`session-1` 这类
|
|
121
|
+
* 由 `SessionStore.prepare()` 铸的计数 id)在宿主里已不存在 ⇒ 真机报
|
|
122
|
+
* 「新建会话失败: gateway/internal: failed to create session "session-1": SessionPersistenceNotFoundError」。
|
|
123
|
+
* 这是「列表里少一条」的观感收益 vs 「回退后整页会话打不开」的风险,选择后者不换。
|
|
124
|
+
*/
|
|
125
|
+
const ARCHIVE_REWOUND_PARENT = false
|
|
126
|
+
|
|
127
|
+
type LoadState = 'loading' | 'ready' | 'nopoint' | 'error'
|
|
128
|
+
|
|
129
|
+
/* 一行文件统计(`+N` / `−M` / 「删除」/「行数未统计」)与整个文件清单都搬进了共享组件
|
|
130
|
+
* `src/client/recovery/RollbackFileChanges.tsx` —— 设置页回滚卡的预览弹窗要用**同一份渲染**
|
|
131
|
+
* (2026-10-12 用户要求「环境页的回退预览也要一眼看清被改动的文件」)。对话框这边只负责:
|
|
132
|
+
* 读行数据(readRollbackPreviewFiles / readRollbackPreviewRemovals)、算汇总口径
|
|
133
|
+
* (rollbackPreviewScope)与给出逐行「查看差异」通道。 */
|
|
134
|
+
|
|
135
|
+
/** 预览对话框主体(挂载即打开;关闭由父级卸载)。 */
|
|
136
|
+
export function RollbackReviewDialog({ sessionId, turn, port, rt, t, openChildSession, archiveParentSession, startFreshSession, openDiff, renderFileIcon, onClose }: RollbackReviewDialogProps) {
|
|
137
|
+
const [state, setState] = useState<LoadState>('loading')
|
|
138
|
+
const [loadKey, setLoadKey] = useState<string | null>(null)
|
|
139
|
+
const [loadDetail, setLoadDetail] = useState<string | null>(null)
|
|
140
|
+
/** 命中回滚点的 id(唯一写路径要它;其余字段界面不再渲染) */
|
|
141
|
+
const [pointId, setPointId] = useState<string | null>(null)
|
|
142
|
+
const [preview, setPreview] = useState<RollbackPreviewResult | null>(null)
|
|
143
|
+
const [draft, setDraft] = useState<RewindDraft>(() => defaultDraft(null))
|
|
144
|
+
const [busy, setBusy] = useState(false)
|
|
145
|
+
const [writeKey, setWriteKey] = useState<string | null>(null)
|
|
146
|
+
const [writeDetail, setWriteDetail] = useState<string | null>(null)
|
|
147
|
+
const [result, setResult] = useState<RollbackRewindResult | null>(null)
|
|
148
|
+
const [childId, setChildId] = useState<string | null>(null)
|
|
149
|
+
const [childState, setChildState] = useState<ChildOpenState | 'opening' | null>(null)
|
|
150
|
+
/** A:原会话归档结果(null = 还没走到这一步)。 */
|
|
151
|
+
const [archive, setArchive] = useState<ArchiveOutcome | null>(null)
|
|
152
|
+
/** 第一条消息之前:新建空会话的结果(null = 这次没有走这条路径)。 */
|
|
153
|
+
const [freshSession, setFreshSession] = useState<{ ok: true } | { ok: false; code: string } | null>(null)
|
|
154
|
+
/**
|
|
155
|
+
* 「查看」的**降级通道**(null = 这次把它开进了右侧栏):
|
|
156
|
+
* 只有本客户端没有侧栏能力 / 打开动作抛错时才启动,并在弹窗内如实说清为什么在这里看。
|
|
157
|
+
*/
|
|
158
|
+
const [inlineDiff, setInlineDiff] = useState<{ path: string; state: RollbackDiffState } | null>(null)
|
|
159
|
+
const [inlineNotice, setInlineNotice] = useState<string | null>(null)
|
|
160
|
+
const alive = useRef(true)
|
|
161
|
+
/**
|
|
162
|
+
* 同一 tick 的**同步幂等锁**(t3 F4):`busy` 是异步 setState,挡不住同一 tick 里的第二次派发
|
|
163
|
+
* (双击 / 按键重复 / 程序化 dispatchEvent)—— 那会发出两次写请求。这把锁在第一个 `await` 之前
|
|
164
|
+
* **同步**置位,保证回退写请求恰好一次;`finally` 释放,失败后仍可重试。
|
|
165
|
+
*/
|
|
166
|
+
const submitLock = useRef(createInFlightLock()).current
|
|
167
|
+
|
|
168
|
+
useEffect(() => {
|
|
169
|
+
alive.current = true
|
|
170
|
+
return () => { alive.current = false }
|
|
171
|
+
}, [])
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* 打开即读:台账列表 → 匹配(会话 + 回合号)→ 只读预览(**零写入**)。
|
|
175
|
+
* 找不到该回合的点就停在 `nopoint`(如实说「还没有」,绝不自动挑一个别的点)。
|
|
176
|
+
*/
|
|
177
|
+
const load = async (): Promise<void> => {
|
|
178
|
+
setState('loading')
|
|
179
|
+
setLoadKey(null)
|
|
180
|
+
setLoadDetail(null)
|
|
181
|
+
setResult(null)
|
|
182
|
+
setChildId(null)
|
|
183
|
+
setChildState(null)
|
|
184
|
+
setArchive(null)
|
|
185
|
+
setFreshSession(null)
|
|
186
|
+
setWriteKey(null)
|
|
187
|
+
setWriteDetail(null)
|
|
188
|
+
setPreview(null)
|
|
189
|
+
setPointId(null)
|
|
190
|
+
try {
|
|
191
|
+
const list = await port.rollbackPoints()
|
|
192
|
+
const match = findPointForTurn(list.points, sessionId, turn)
|
|
193
|
+
if (match === null) {
|
|
194
|
+
setState('nopoint')
|
|
195
|
+
return
|
|
196
|
+
}
|
|
197
|
+
const pre = await port.rollbackPointPreview(match.id)
|
|
198
|
+
if (!alive.current) return
|
|
199
|
+
setPointId(match.id)
|
|
200
|
+
setPreview(pre)
|
|
201
|
+
setDraft(defaultDraft(pre))
|
|
202
|
+
setState('ready')
|
|
203
|
+
} catch (error) {
|
|
204
|
+
if (!alive.current) return
|
|
205
|
+
setLoadKey(codeKeyOfError(error))
|
|
206
|
+
setLoadDetail(errorDetail(error))
|
|
207
|
+
setState('error')
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
useEffect(() => { void load() /* 打开一次读一次;重开由父级卸载重挂保证 */ }, [sessionId, turn])
|
|
212
|
+
|
|
213
|
+
const tryOpenChild = async (id: string): Promise<void> => {
|
|
214
|
+
setChildState('opening')
|
|
215
|
+
const opened = await openChildSession(id)
|
|
216
|
+
if (alive.current) setChildState(opened)
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* 提交(唯一写路径):**点主按钮 = 用户确认**(`confirmOnSubmit` 置 confirmed),
|
|
221
|
+
* 再交给 `canSubmitRewind` / `requestRewind` 的 fail-closed 判定;整段由同步幂等锁包住(F4)。
|
|
222
|
+
* 返回 200 且 `code='nothing-to-restore'` 时**不是失败**:下面按 noop 摘要如实呈现
|
|
223
|
+
*(E1:工作区与该回退点一致,HTTP 200)。
|
|
224
|
+
*/
|
|
225
|
+
const submit = (): Promise<boolean> => submitLock(async () => {
|
|
226
|
+
if (pointId === null || busy) return
|
|
227
|
+
const confirmedDraft = confirmOnSubmit(draft)
|
|
228
|
+
if (!canSubmitRewind(confirmedDraft, preview)) return
|
|
229
|
+
setDraft(confirmedDraft as RewindDraft)
|
|
230
|
+
setBusy(true)
|
|
231
|
+
setWriteKey(null)
|
|
232
|
+
setWriteDetail(null)
|
|
233
|
+
const attempt = await requestRewind(port, pointId, confirmedDraft)
|
|
234
|
+
if (!alive.current) return
|
|
235
|
+
setBusy(false)
|
|
236
|
+
if (attempt.kind === 'blocked') { setWriteKey(attempt.key); return }
|
|
237
|
+
if (attempt.kind === 'error') { setWriteKey(attempt.key); setWriteDetail(attempt.detail); return }
|
|
238
|
+
setResult(attempt.result)
|
|
239
|
+
const child = rollbackChildSession(attempt.result)
|
|
240
|
+
if (child !== null) {
|
|
241
|
+
setChildId(child.id)
|
|
242
|
+
await tryOpenChild(child.id)
|
|
243
|
+
// A(无感替换)里的「把原会话收进已归档」默认**关掉**(2026-10-11 真机定位):
|
|
244
|
+
// 归档**当前**会话会让 DSH 客户端去换绑一个新会话,而它复用的 prepared id 是宿主
|
|
245
|
+
// `SessionStore.prepare()` 铸的 `session-<counter>`(第一个就是 `session-1`,见 dsh-session 的 prepare);
|
|
246
|
+
// 那个 id 在宿主侧已不存在 ⇒ 用户看到「新建会话失败: … session \"session-1\" not found」,
|
|
247
|
+
// 回退后连不在回退里的那条对话都打不开了。归档只是列表观感的美化,不值得拿「回退后无法继续」去换。
|
|
248
|
+
// 保留实现与单测备查:真要开启,把 `ARCHIVE_REWOUND_PARENT` 置 true 并重跑本文件所在目录的测试。
|
|
249
|
+
if (ARCHIVE_REWOUND_PARENT) {
|
|
250
|
+
const archived = await archiveParentSession(sessionId)
|
|
251
|
+
if (alive.current) setArchive(archived)
|
|
252
|
+
}
|
|
253
|
+
} else if ((attempt.result.segments ?? []).some((s) => s.state === 'session' && s.reasonCode === 'no-boundary')) {
|
|
254
|
+
// **第一条用户消息之前没有上一个回合** ⇒ 没有可 fork 的边界(会话段结构化 no-boundary)。
|
|
255
|
+
// 参照实现(Anionex/dsh-turn-rewind)在这种情形建一条同目录**空会话**,而不是硬造空子会话;
|
|
256
|
+
// 我们走客户端同一条入口(uiWorkspace.startSession =「新会话」按钮那条路,id 由 DSH 自己铸)。
|
|
257
|
+
const fresh = await startFreshSession()
|
|
258
|
+
if (alive.current) setFreshSession(fresh)
|
|
259
|
+
}
|
|
260
|
+
})
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* 「查看」:把该文件的差异开到 DSH 右侧栏(**只读**,不做任何写操作)。
|
|
264
|
+
*
|
|
265
|
+
* 侧栏不可用 / 打开动作抛错时**绝不假装成功**:改用弹窗内展示同一份差异,
|
|
266
|
+
* 并把「为什么在这里看」如实说一句(`no-sidebar` 与「调用失败」两句不同的话)。
|
|
267
|
+
*/
|
|
268
|
+
const view = (filePath: string): void => {
|
|
269
|
+
if (pointId === null) return
|
|
270
|
+
const opened = openDiff(pointId, filePath)
|
|
271
|
+
if (opened.ok) {
|
|
272
|
+
// 第 5 条(用户 2026-10-12):开进右侧栏之后**顺手关掉弹窗** —— 否则这块模态盖在新开的侧栏上,
|
|
273
|
+
// 用户还得再点一次「取消」才能看差异。**只有真的开成功才关**:降级通道(下面那两支)必须留着,
|
|
274
|
+
// 它正是「没侧栏时把同一份差异画在弹窗里」的地方。
|
|
275
|
+
setInlineNotice(null)
|
|
276
|
+
setInlineDiff(null)
|
|
277
|
+
onClose()
|
|
278
|
+
return
|
|
279
|
+
}
|
|
280
|
+
setInlineNotice(
|
|
281
|
+
opened.reason === 'no-sidebar'
|
|
282
|
+
? t('chat.preview.viewInline')
|
|
283
|
+
: t('chat.preview.viewFailed', { detail: redact(opened.detail ?? '') }),
|
|
284
|
+
)
|
|
285
|
+
setInlineDiff({ path: filePath, state: { status: 'loading' } })
|
|
286
|
+
const read = port.rollbackPointFileDiff
|
|
287
|
+
if (typeof read !== 'function') {
|
|
288
|
+
setInlineDiff({ path: filePath, state: { status: 'error', message: null } })
|
|
289
|
+
return
|
|
290
|
+
}
|
|
291
|
+
read.call(port, pointId, filePath).then(
|
|
292
|
+
(diff) => { if (alive.current) setInlineDiff({ path: filePath, state: { status: 'ready', diff } }) },
|
|
293
|
+
(error: unknown) => { if (alive.current) setInlineDiff({ path: filePath, state: { status: 'error', message: errorDetail(error) } }) },
|
|
294
|
+
)
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const verdict = rollbackPreviewVerdict(preview)
|
|
298
|
+
/**
|
|
299
|
+
* 预览态那句「工作区怎样怎样」= **一处实现**(src/ui/rollback-view.ts 的 rollbackPreviewNote):
|
|
300
|
+
* 判据只来自 preview 字段 + 宿主 canRestore,四种情形说四句不同的话(③a 不含「无需恢复」——
|
|
301
|
+
* 点下去真会 fork 子会话,说「无需恢复」是假尾句)。
|
|
302
|
+
*/
|
|
303
|
+
const note = rollbackPreviewNote({
|
|
304
|
+
workspace: preview?.workspace,
|
|
305
|
+
session: preview?.session,
|
|
306
|
+
segments: preview?.point?.segments,
|
|
307
|
+
canRestore: verdict.canRestore,
|
|
308
|
+
})
|
|
309
|
+
const noteText = note.key === null
|
|
310
|
+
? null
|
|
311
|
+
: note.reasonKey === null
|
|
312
|
+
? rt(rk(note.key))
|
|
313
|
+
: rt(rk(note.key), { reason: rt(rk(note.reasonKey)) })
|
|
314
|
+
const files = readRollbackPreviewFiles(preview?.workspace)
|
|
315
|
+
/**
|
|
316
|
+
* 会被**删掉**的文件(第 6 条):`preview.workspace.removals` —— 引擎 `planCreatedRemovals` 的产物,
|
|
317
|
+
* 与执行阶段**同一个函数**(所以逐条 = 实际删的)。宿主 rewind 的 `removeCreated` 缺省为真,
|
|
318
|
+
* 点「回退」真的会删它们(读法与诚实边界见 `readRollbackPreviewRemovals` 的注释)。
|
|
319
|
+
*/
|
|
320
|
+
const removals = readRollbackPreviewRemovals(preview?.workspace)
|
|
321
|
+
/** 选择框(第 1 条)当前是否「连文件一起退」以及它能不能被点。 */
|
|
322
|
+
const revertFiles = revertFilesOn(draft)
|
|
323
|
+
const revertFilesEnabled = canRevertFiles(preview)
|
|
324
|
+
/**
|
|
325
|
+
* t8:这条对话此刻正在运行(引擎 preview 的 `session.active`)。运行中时:
|
|
326
|
+
* · 默认档 = 只回溯消息(`defaultDraft`);· 文件开关禁用(`canRevertFiles`);
|
|
327
|
+
* · 带工作区段的提交不可行(`canSubmitRewind`)—— 路由层对同一信号还有一道硬拒(409)。
|
|
328
|
+
*/
|
|
329
|
+
const sessionActive = isSessionActive(preview)
|
|
330
|
+
const summary = chatRewindSummary(result)
|
|
331
|
+
/** F2:只有**真失败**才渲染下面的「失败原因」盒(成功 / 无需恢复的结果不得弹失败信息)。 */
|
|
332
|
+
const failed = chatRewindFailed(result)
|
|
333
|
+
/** 摘要文本:① 需要把「该段 reasonCode 的字典键文本」插进 {reason}(未知码回落 unknown)。 */
|
|
334
|
+
const summaryText = summary.kind === 'plain'
|
|
335
|
+
? null
|
|
336
|
+
: summary.kind === 'no-record'
|
|
337
|
+
? rt(rk(summary.key), { reason: rt(rk(summary.reasonKey)) })
|
|
338
|
+
: rt(rk(summary.key))
|
|
339
|
+
const failure = result === null ? { code: null, detail: null, keys: [] } : chatRewindFailure(result)
|
|
340
|
+
/** 结果里「与预期不同」的指引(没能挂回工作区等);成功回退时为**空数组**,不占版面。 */
|
|
341
|
+
const notices = chatRewindNotices(result)
|
|
342
|
+
const counts = rollbackRewindCounts(result)
|
|
343
|
+
const outcome = result === null ? null : rollbackOutcome(result.outcome)
|
|
344
|
+
// R1:enablement 用「这次提交可行」判据(不含 confirmed);确认只由 submit() 里
|
|
345
|
+
// confirmOnSubmit 置位,绝不能反过来当前置 —— 否则按钮因未确认禁用、确认又只能靠点按钮(死锁)。
|
|
346
|
+
// R2/F3:判据维度必须与 submit() 的执行体同宽(含「已有可写目标 pointId」与忙态)。
|
|
347
|
+
const submitEnabled = submitButtonEnabled({ draft, preview, pointId, busy })
|
|
348
|
+
const warnings = preview?.warnings ?? []
|
|
349
|
+
/**
|
|
350
|
+
* 顶部**汇总**(2026-10-12 第二轮):一次回退动多少东西 —— 用户先判规模、再逐行核文件。
|
|
351
|
+
* 数字口径在 `rollbackPreviewScope`(| 文件数 · +A / −D · 将删除 K |,行数只累加可信的那部分)。
|
|
352
|
+
*/
|
|
353
|
+
const scope = rollbackPreviewScope(files, removals)
|
|
354
|
+
|
|
355
|
+
return (
|
|
356
|
+
<Dialog.Root open onOpenChange={(next) => { if (!next && !busy) onClose() }}>
|
|
357
|
+
<Dialog.Portal>
|
|
358
|
+
<Dialog.Overlay className={css.dialogMask} />
|
|
359
|
+
<Dialog.Content
|
|
360
|
+
className={`${css.dialogContentCenter} ${css.dialogCard} ${css.dialogWide}`}
|
|
361
|
+
aria-label={rt('rollback.preview.title')}
|
|
362
|
+
data-cm-dialog=""
|
|
363
|
+
onEscapeKeyDown={(e) => { if (busy) e.preventDefault() }}
|
|
364
|
+
onPointerDownOutside={(e) => { if (busy) e.preventDefault() }}
|
|
365
|
+
>
|
|
366
|
+
<div className={css.dialogHeaderRow}>
|
|
367
|
+
<span className={css.dialogHeader}>{rt('rollback.preview.title')}</span>
|
|
368
|
+
<Dialog.Close asChild>
|
|
369
|
+
<button type="button" className={`${css.iconBtn} ${css.dialogClose}`} aria-label={t('chat.close')} disabled={busy}>
|
|
370
|
+
<CloseIcon size={14} />
|
|
371
|
+
</button>
|
|
372
|
+
</Dialog.Close>
|
|
373
|
+
</div>
|
|
374
|
+
|
|
375
|
+
<div className={`${css.dialogBody} ${css.dialogBodyScroll}`}>
|
|
376
|
+
{state === 'loading' && <Spinner label={t('chat.entry.loading')} />}
|
|
377
|
+
|
|
378
|
+
{state === 'nopoint' && <Empty>{t('chat.entry.noPoint')}</Empty>}
|
|
379
|
+
|
|
380
|
+
{state === 'error' && (
|
|
381
|
+
<Banner kind="error">
|
|
382
|
+
{rt(rk(loadKey ?? 'rollback.code.unknown'))}{loadDetail !== null ? ' — ' + loadDetail : ''}
|
|
383
|
+
</Banner>
|
|
384
|
+
)}
|
|
385
|
+
|
|
386
|
+
{state === 'ready' && result === null && (
|
|
387
|
+
<>
|
|
388
|
+
{/* 2026-10-12 用户第 8 条「移除回退弹窗中的所有描述」:说明行「这次回退会做以下改动:」
|
|
389
|
+
整体删除 —— 它不改任何决定(DESIGN §7)。该键仍保留在字典里(删渲染不删键)。 */}
|
|
390
|
+
{/* 选择框(第 1 条,用户 2026-10-12):要不要**连文件一起**退回去。
|
|
391
|
+
勾 = files+session(默认,= 「回到这条消息之前」的文件也退);
|
|
392
|
+
取消 = session(只退对话,工作区一个字节都不动)。
|
|
393
|
+
工作区态没有可用记录时**禁用**(那时没有文件可退,开关点了也没有区别)。 */}
|
|
394
|
+
<div className={css.previewChoiceRow}>
|
|
395
|
+
<Checkbox
|
|
396
|
+
checked={revertFiles}
|
|
397
|
+
disabled={busy || !revertFilesEnabled}
|
|
398
|
+
onChange={(next) => { setDraft((previous) => setRevertFiles(previous, next) ?? previous) }}
|
|
399
|
+
label={t('chat.preview.revertFiles')}
|
|
400
|
+
/>
|
|
401
|
+
{/* 汇总:**紧挨着那颗选择框**(同一行、右对齐)—— 它管的正是这份清单的规模。
|
|
402
|
+
取消勾选时不显示数字,改写一句「文件不改动」:那时数字与用户无关,
|
|
403
|
+
而清单仍在下面(保持满不透明度 —— 旧做法整块 opacity 会读成「弹窗坏了」)。 */}
|
|
404
|
+
{files.length + removals.length > 0 && (
|
|
405
|
+
<span className={css.previewScope} data-off={revertFiles ? undefined : 'true'}>
|
|
406
|
+
{revertFiles ? (
|
|
407
|
+
<>
|
|
408
|
+
<span>{t('chat.preview.scope.files', { n: String(scope.files) })}</span>
|
|
409
|
+
<span className={css.previewScopeStats}>
|
|
410
|
+
<span className={css.rollbackAdded}>{rt(rk('rollback.preview.file.added'), { n: String(scope.added) })}</span>
|
|
411
|
+
<span className={css.rollbackRemoved}>{rt(rk('rollback.preview.file.removed'), { n: String(scope.removed) })}</span>
|
|
412
|
+
{scope.removals > 0 && (
|
|
413
|
+
<span className={css.rollbackDeleted}>{rt(rk('rollback.preview.file.deleted'))} {String(scope.removals)}</span>
|
|
414
|
+
)}
|
|
415
|
+
</span>
|
|
416
|
+
</>
|
|
417
|
+
) : (
|
|
418
|
+
<span className={css.previewScopeOff}>{t('chat.preview.scope.off')}</span>
|
|
419
|
+
)}
|
|
420
|
+
</span>
|
|
421
|
+
)}
|
|
422
|
+
</div>
|
|
423
|
+
|
|
424
|
+
{/* t8:对话正在运行 ⇒ 一句明确说明(默认档已改成「只回溯消息」、文件开关被禁用,
|
|
425
|
+
这句解释的正是「为什么这次不能连文件一起退」)。只留能改变决定的信息(DESIGN §7)。 */}
|
|
426
|
+
{sessionActive && (
|
|
427
|
+
<div className={css.hint} data-cm-session-active="">{rt(rk(CHAT_SESSION_ACTIVE_NOTE_KEY))}</div>
|
|
428
|
+
)}
|
|
429
|
+
|
|
430
|
+
{/* ① 会被改动的文件 + 会被**删除**的文件(第 6 条:删除写「删除」,不写 +0 −N)。
|
|
431
|
+
两段都空 ⇒ 只剩四态句。**取消勾选时清单不再降透明度**(2026-10-12 第二轮:
|
|
432
|
+
半透明的整块清单会被读成「弹窗坏了」)—— 改成汇总行改写「文件不改动」+ 统计转弱化色,
|
|
433
|
+
仍然不把「不会发生的事」画得像会发生的事;行序、图标、路径一律保持满可读。 */}
|
|
434
|
+
{files.length === 0 && removals.length === 0 ? (
|
|
435
|
+
// 预览态四态句:由 rollbackPreviewNote 决定(key=null 时本情形没有该类句子可说,
|
|
436
|
+
// 宁可不说话,也不上一句不实的「一致」)
|
|
437
|
+
noteText !== null ? <div className={css.hint}>{noteText}</div> : null
|
|
438
|
+
) : (
|
|
439
|
+
<>
|
|
440
|
+
<RollbackFileChanges
|
|
441
|
+
files={files}
|
|
442
|
+
removals={removals}
|
|
443
|
+
rt={rt}
|
|
444
|
+
renderFileIcon={renderFileIcon}
|
|
445
|
+
view={{
|
|
446
|
+
// 「查看差异:{path}」由字典给(chat 侧文案);行内名字/目录就是那颗按钮。
|
|
447
|
+
hint: (path: string) => t('chat.preview.viewHint', { path }),
|
|
448
|
+
disabled: busy || pointId === null,
|
|
449
|
+
onView: view,
|
|
450
|
+
}}
|
|
451
|
+
/>
|
|
452
|
+
{/* 2026-10-12 用户第 8 条:整段「行数没有统计出来…」说明删除 —— 逐文件那格的
|
|
453
|
+
「行数未统计」已经说明一切。该键仍保留在字典里(删渲染不删键)。 */}
|
|
454
|
+
{/* 「查看」的降级通道:没有右侧栏(或打开失败)时在弹窗内展示同一份差异 */}
|
|
455
|
+
{inlineDiff !== null && (
|
|
456
|
+
<div className={css.inlineDiffBox}>
|
|
457
|
+
{inlineNotice !== null && <div className={css.hint}>{inlineNotice}</div>}
|
|
458
|
+
<div className={`${css.hint} ${css.mono}`}>{inlineDiff.path}</div>
|
|
459
|
+
<RollbackDiffPane state={inlineDiff.state} rt={rt} />
|
|
460
|
+
</div>
|
|
461
|
+
)}
|
|
462
|
+
</>
|
|
463
|
+
)}
|
|
464
|
+
|
|
465
|
+
{/* ② 不可回退的原因:主按钮因此禁用 —— 一行字说清「为什么点不了」。
|
|
466
|
+
原因键由 src/ui/rollback-view.ts 分派(protected / guard / incomplete / 段原因 /
|
|
467
|
+
hostRefused);**只有它一个都没给时**才回落「原因未知」,绝不回落成
|
|
468
|
+
「没有可回退的内容」(那是另一件事,T9-F3)。 */}
|
|
469
|
+
{!verdict.canRestore && (
|
|
470
|
+
<div className={css.warnText}>
|
|
471
|
+
{verdict.reasonKey !== null ? rt(rk(verdict.reasonKey)) : t('chat.preview.reasonUnknown')}
|
|
472
|
+
</div>
|
|
473
|
+
)}
|
|
474
|
+
|
|
475
|
+
{/* ③ 点级告警:宿主在**真的出事**时才给(正常结果恒为空 ⇒ 不占版面) */}
|
|
476
|
+
{warnings.length > 0 && (
|
|
477
|
+
<Banner kind="warn">
|
|
478
|
+
{warnings.map((warning) => (
|
|
479
|
+
<div key={warning.code + (warning.detail ?? '')} className={css.warnText}>
|
|
480
|
+
{rt(rk(warning.code))}{warning.detail !== undefined && warning.detail !== '' ? ' — ' + warning.detail : ''}
|
|
481
|
+
</div>
|
|
482
|
+
))}
|
|
483
|
+
</Banner>
|
|
484
|
+
)}
|
|
485
|
+
</>
|
|
486
|
+
)}
|
|
487
|
+
|
|
488
|
+
{state === 'ready' && result !== null && (
|
|
489
|
+
<>
|
|
490
|
+
{/* ④ 结果:**一行摘要** —— 「成功、0 变更」下三种情形说三句不同的话(T11):
|
|
491
|
+
· 与该点一致 / 该点没有记录任何文件 / 该态没有可用记录(原因:{该段 code 键})。
|
|
492
|
+
原因键走 rollbackCodeKey(未知码回落 unknown),**不裸渲染机器码**;
|
|
493
|
+
都不是失败 ⇒ 成功徽章(outcome=failed/partial 时不走这套,由下面 ⑤ 如实呈现)。 */}
|
|
494
|
+
<div className={css.statRow}>
|
|
495
|
+
{summaryText !== null
|
|
496
|
+
? <Badge kind="ok">{summaryText}</Badge>
|
|
497
|
+
: outcome !== null && <Badge kind={outcome.badgeKind}>{rt(rk(outcome.key))}</Badge>}
|
|
498
|
+
{summaryText === null && (
|
|
499
|
+
<span className={css.hint}>
|
|
500
|
+
{rt('rollback.rewind.result.counts', { restored: counts.restored, removed: counts.removed, failed: counts.failed, leftovers: counts.leftovers })}
|
|
501
|
+
</span>
|
|
502
|
+
)}
|
|
503
|
+
</div>
|
|
504
|
+
|
|
505
|
+
{/* ⑤ 失败原因:**只有真失败才渲染**(F2)—— 成功 / 无需恢复的结果一个字都不弹。
|
|
506
|
+
真失败时给:可读文案 + 真实 code + detail(不裸渲染机器码,但也不藏)。 */}
|
|
507
|
+
{failed && (failure.keys.length > 0 || failure.detail !== null) && (
|
|
508
|
+
<div className={css.rollbackBox}>
|
|
509
|
+
{failure.keys.map((key) => (
|
|
510
|
+
<div key={key} className={css.warnText}>{rt(rk(key))}</div>
|
|
511
|
+
))}
|
|
512
|
+
{failure.code !== null && (
|
|
513
|
+
<div className={`${css.hint} ${css.mono}`}>{rt('rollback.preview.result.code', { code: failure.code })}</div>
|
|
514
|
+
)}
|
|
515
|
+
{failure.detail !== null && <div className={css.hint}>{failure.detail}</div>}
|
|
516
|
+
</div>
|
|
517
|
+
)}
|
|
518
|
+
|
|
519
|
+
{/* ⑥ fork 出来的子会话(有才显示;自动打开失败如实降级) */}
|
|
520
|
+
{childId !== null && (
|
|
521
|
+
<div className={css.statRow}>
|
|
522
|
+
<span className={css.hint}>{rt('rollback.rewind.result.child', { id: childId })}</span>
|
|
523
|
+
<Button
|
|
524
|
+
variant="ghost"
|
|
525
|
+
size="sm"
|
|
526
|
+
disabled={childState === 'opening'}
|
|
527
|
+
onClick={() => { void tryOpenChild(childId) }}
|
|
528
|
+
>
|
|
529
|
+
{childState === 'opening' ? t('chat.open.opening') : t('chat.open.child')}
|
|
530
|
+
</Button>
|
|
531
|
+
</div>
|
|
532
|
+
)}
|
|
533
|
+
{childState === 'unavailable' && (
|
|
534
|
+
<div className={css.warnText}>{t('chat.open.unavailable', { id: childId ?? '' })}</div>
|
|
535
|
+
)}
|
|
536
|
+
{childState === 'degraded' && (
|
|
537
|
+
<div className={css.warnText}>{t('chat.open.degraded', { id: childId ?? '' })}</div>
|
|
538
|
+
)}
|
|
539
|
+
{childState === 'failed' && (
|
|
540
|
+
<div className={css.warnText}>{t('chat.open.failed', { id: childId ?? '' })}</div>
|
|
541
|
+
)}
|
|
542
|
+
{/* ⑥b 结果级 / 段级指引里「与预期不同」的事实(**没能挂回原工作区**等)。
|
|
543
|
+
成功回退时为空 ⇒ 一个字都不多说;有则如实说 —— 绝不静默让用户以为
|
|
544
|
+
「对话被回退弄丢了」(真机 2026-10-11/12 反馈:子会话落到侧栏「未分组」)。 */}
|
|
545
|
+
{notices.length > 0 && (
|
|
546
|
+
<div className={css.rollbackBox}>
|
|
547
|
+
{notices.map((notice) => (
|
|
548
|
+
<div key={notice.key + (notice.detail ?? '')} className={css.warnText}>
|
|
549
|
+
{rt(rk(notice.key))}{notice.detail !== null ? ' — ' + notice.detail : ''}
|
|
550
|
+
</div>
|
|
551
|
+
))}
|
|
552
|
+
</div>
|
|
553
|
+
)}
|
|
554
|
+
{/* A:原会话的归档结果 —— 成功一句话(列表里只剩这一条),失败如实说并在哪能看到它 */}
|
|
555
|
+
{archive !== null && archive.ok && (
|
|
556
|
+
<div className={css.hint} data-dcm-archived="">{rt('rollback.rewind.archive.done')}</div>
|
|
557
|
+
)}
|
|
558
|
+
{archive !== null && !archive.ok && (
|
|
559
|
+
<div className={css.warnText}>{rt(archiveHintKey(archive) ?? 'rollback.rewind.archive.failed')}</div>
|
|
560
|
+
)}
|
|
561
|
+
{/* 第一条消息之前:会话段没有边界 ⇒ 已按参照实现开一条全新会话(失败如实说) */}
|
|
562
|
+
{freshSession !== null && freshSession.ok && (
|
|
563
|
+
<div className={css.hint}>{rt('rollback.rewind.freshSession')}</div>
|
|
564
|
+
)}
|
|
565
|
+
{freshSession !== null && !freshSession.ok && (
|
|
566
|
+
<div className={css.warnText}>{rt('rollback.rewind.freshSessionFailed')}</div>
|
|
567
|
+
)}
|
|
568
|
+
</>
|
|
569
|
+
)}
|
|
570
|
+
|
|
571
|
+
{/* 写请求本身失败(端口抛错 / 客户端拦下):文案 + code + detail */}
|
|
572
|
+
{writeKey !== null && (
|
|
573
|
+
<Banner kind="error">
|
|
574
|
+
{rt(rk(writeKey))}{writeDetail !== null ? ' — ' + writeDetail : ''}
|
|
575
|
+
</Banner>
|
|
576
|
+
)}
|
|
577
|
+
</div>
|
|
578
|
+
|
|
579
|
+
<div className={`${css.actionRow} ${css.dialogFooter}`}>
|
|
580
|
+
{result === null && state === 'ready' && (
|
|
581
|
+
<Button variant="primary" disabled={!submitEnabled} loading={busy} onClick={() => { void submit() }}>
|
|
582
|
+
{busy ? t('chat.rewind.running') : t('chat.rewind.submit')}
|
|
583
|
+
</Button>
|
|
584
|
+
)}
|
|
585
|
+
<Button variant="ghost" disabled={busy} onClick={onClose}>{result === null ? rt('common.cancel') : t('chat.close')}</Button>
|
|
586
|
+
</div>
|
|
587
|
+
</Dialog.Content>
|
|
588
|
+
</Dialog.Portal>
|
|
589
|
+
</Dialog.Root>
|
|
590
|
+
)
|
|
591
|
+
}
|