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
|
@@ -59,6 +59,17 @@ const DEFAULT_AUTHOR = { name: 'DSH Config Sync', email: 'sync@dsh.local' };
|
|
|
59
59
|
const DEFAULT_CREDENTIAL_USERNAME = 'oauth2';
|
|
60
60
|
/** 快照 id 安全字符集:字母数字开头,仅 . _ -;防路径穿越与 commit message 注入 */
|
|
61
61
|
const SAFE_ID_RE = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
|
|
62
|
+
/**
|
|
63
|
+
* SSH 主机密钥校验失败(issue #79):git 只回一句 `Host key verification failed`,
|
|
64
|
+
* 用户拿不到任何可操作信息(要不要信任?怎么写 known_hosts?)。识别后换成可操作文案。
|
|
65
|
+
*/
|
|
66
|
+
const SSH_HOST_KEY_RE = /host key verification failed|remote host identification has changed|no matching host key/i;
|
|
67
|
+
/**
|
|
68
|
+
* 工作副本与远端不是同一条历史(issue #79):改成一个「另一个仓库」的地址后,
|
|
69
|
+
* `pull --ff-only` 会以 refusing to merge unrelated histories / Not possible to fast-forward 失败。
|
|
70
|
+
* 这类失败与网络无关,必须给「改回地址或重建工作副本」的出口,而不是只丢一句原始 stderr。
|
|
71
|
+
*/
|
|
72
|
+
const DIVERGED_HISTORY_RE = /refusing to merge unrelated histories|unrelated histories|not possible to fast-forward/i;
|
|
62
73
|
const defaultExec = async (cmd, args, opts) => {
|
|
63
74
|
try {
|
|
64
75
|
const { stdout, stderr } = await execFileAsync(cmd, args, {
|
|
@@ -326,6 +337,12 @@ export class GitTransport {
|
|
|
326
337
|
const fsx = createSnapshotFs();
|
|
327
338
|
await fsx.mkdir(this.o.workDir); // 不存在则创建
|
|
328
339
|
if (await fsx.exists(path.join(this.o.workDir, '.git'))) {
|
|
340
|
+
// issue #79:已有工作副本时**也必须**让 origin 跟随 repoUrl。
|
|
341
|
+
// 此前这里直接 `repoReady = true; return`,repoUrl 只在首次 clone 时被使用 ——
|
|
342
|
+
// 于是「改仓库地址」对已存在的工作副本完全无效:pull/push 全走旧 origin,
|
|
343
|
+
// 而失败面表现为旧地址的网络超时(真机:局域网地址 → Tailscale 地址后同步长期失败),
|
|
344
|
+
// 用户会误判成网络/凭据问题。
|
|
345
|
+
await this.reconcileOrigin();
|
|
329
346
|
this.repoReady = true;
|
|
330
347
|
return;
|
|
331
348
|
}
|
|
@@ -346,6 +363,116 @@ export class GitTransport {
|
|
|
346
363
|
await this.runGit(['config', 'user.email', this.o.author.email]);
|
|
347
364
|
this.repoReady = true;
|
|
348
365
|
}
|
|
366
|
+
/**
|
|
367
|
+
* 让工作副本的 origin 与配置的 repoUrl 保持一致(issue #79)。
|
|
368
|
+
*
|
|
369
|
+
* 幂等:地址已一致时**一条写命令都不发**(只读 get-url)。三条边界:
|
|
370
|
+
* - 读不出 origin(无该 remote)→ `remote add origin`,否则 `push -u origin` 永远失败;
|
|
371
|
+
* - 读得出但为空串 → 视为「读不到」,**不改写**(不拿不确定的读取结果去覆盖用户的仓库配置);
|
|
372
|
+
* - push URL 可被单独配置(`git remote set-url --push`)→ 不一致时一并跟随,否则推送仍走旧地址。
|
|
373
|
+
*/
|
|
374
|
+
async reconcileOrigin() {
|
|
375
|
+
const current = await this.runGit(['remote', 'get-url', 'origin'], { allowNonZero: true });
|
|
376
|
+
const origin = current.code === 0 ? current.stdout.trim() : '';
|
|
377
|
+
if (current.code !== 0) {
|
|
378
|
+
await this.runGit(['remote', 'add', 'origin', this.o.repoUrl]);
|
|
379
|
+
}
|
|
380
|
+
else if (origin !== '' && origin !== this.o.repoUrl) {
|
|
381
|
+
await this.runGit(['remote', 'set-url', 'origin', this.o.repoUrl]);
|
|
382
|
+
}
|
|
383
|
+
const push = await this.runGit(['remote', 'get-url', '--push', 'origin'], { allowNonZero: true });
|
|
384
|
+
const pushUrl = push.code === 0 ? push.stdout.trim() : '';
|
|
385
|
+
if (pushUrl !== '' && pushUrl !== this.o.repoUrl) {
|
|
386
|
+
await this.runGit(['remote', 'set-url', '--push', 'origin', this.o.repoUrl]);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
/**
|
|
390
|
+
* ssh 远端的主机提示(issue #79 主机密钥错误文案用);非 ssh 或解析不出 → 空串。
|
|
391
|
+
* 只回传 host[:port](不含用户名与仓库路径),地址由调用方经 mask() 处理。
|
|
392
|
+
*/
|
|
393
|
+
sshHostHint() {
|
|
394
|
+
const url = this.o.repoUrl;
|
|
395
|
+
if (/^ssh:\/\//i.test(url)) {
|
|
396
|
+
try {
|
|
397
|
+
const u = new URL(url);
|
|
398
|
+
return u.port === '' ? u.hostname : `${u.hostname}:${u.port}`;
|
|
399
|
+
}
|
|
400
|
+
catch {
|
|
401
|
+
return '';
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
// scp 形态:git@host:path(可带端口需用 ssh:// 形态,这里只取主机)
|
|
405
|
+
const scp = /^[^@/]+@([^:/]+)(?::(\d+))?:/.exec(url);
|
|
406
|
+
if (scp === null)
|
|
407
|
+
return '';
|
|
408
|
+
const host = scp[1] ?? '';
|
|
409
|
+
const port = scp[2];
|
|
410
|
+
return port === undefined ? host : `${host}:${port}`;
|
|
411
|
+
}
|
|
412
|
+
/**
|
|
413
|
+
* 工作副本自检(issue #79,只读):origin / push URL 是否已跟随 repoUrl。
|
|
414
|
+
*
|
|
415
|
+
* **默认只做 2 条本地只读命令**(get-url ×2,毫秒级)——它挂在 `GET /sync/status` 上,
|
|
416
|
+
* 每次打开面板都会跑;`rev-list`/`status` 在大工作副本(会话 blob 上千文件)上会明显变慢,
|
|
417
|
+
* 因此放进 `detail: true`(供「重建工作副本」前评估数据量等场景显式索取)。
|
|
418
|
+
*/
|
|
419
|
+
async inspectWorkDir(opts = {}) {
|
|
420
|
+
const fsx = createSnapshotFs();
|
|
421
|
+
if (!(await fsx.exists(path.join(this.o.workDir, '.git')))) {
|
|
422
|
+
return { path: this.o.workDir, exists: false, origin: null, pushUrl: null, match: false, ahead: null, dirty: null };
|
|
423
|
+
}
|
|
424
|
+
const originRes = await this.runGit(['remote', 'get-url', 'origin'], { allowNonZero: true });
|
|
425
|
+
const origin = originRes.code === 0 ? originRes.stdout.trim() : '';
|
|
426
|
+
const pushRes = await this.runGit(['remote', 'get-url', '--push', 'origin'], { allowNonZero: true });
|
|
427
|
+
const pushRaw = pushRes.code === 0 ? pushRes.stdout.trim() : '';
|
|
428
|
+
let ahead = null;
|
|
429
|
+
let dirty = null;
|
|
430
|
+
if (opts.detail === true) {
|
|
431
|
+
// 未上行的本地提交:HEAD 可达、但任何远端跟踪引用都不可达的提交数(无 upstream 也能算)
|
|
432
|
+
const aheadRes = await this.runGit(['rev-list', '--count', 'HEAD', '--not', '--remotes'], { allowNonZero: true });
|
|
433
|
+
const parsed = aheadRes.code === 0 ? Number.parseInt(aheadRes.stdout.trim(), 10) : Number.NaN;
|
|
434
|
+
ahead = Number.isFinite(parsed) ? parsed : null;
|
|
435
|
+
const statusRes = await this.runGit(['status', '--porcelain'], { allowNonZero: true });
|
|
436
|
+
dirty = statusRes.code === 0 ? statusRes.stdout.trim() !== '' : null;
|
|
437
|
+
}
|
|
438
|
+
return {
|
|
439
|
+
path: this.o.workDir,
|
|
440
|
+
exists: true,
|
|
441
|
+
origin: origin === '' ? null : origin,
|
|
442
|
+
pushUrl: pushRaw === '' ? null : pushRaw,
|
|
443
|
+
// 读不出 origin(null)→ 不算 match(状态接口据此提示「工作副本地址未知」)
|
|
444
|
+
match: origin === this.o.repoUrl && (pushRaw === '' || pushRaw === this.o.repoUrl),
|
|
445
|
+
ahead,
|
|
446
|
+
dirty,
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
/**
|
|
450
|
+
* 重建工作副本(issue #79 的自救入口):
|
|
451
|
+
* - `relink`:只把 origin / push URL 重新指向 repoUrl(**非破坏性**:不碰工作树与本地提交);
|
|
452
|
+
* - `reclone`:删除工作副本目录后重新 clone(**丢弃未上行的本地提交**;调用方必须先让用户确认)。
|
|
453
|
+
*
|
|
454
|
+
* 安全边界:`reclone` 只在目录**确实是 git 工作副本**(有 .git)时才删;目录非空且无 .git
|
|
455
|
+
* 一律拒绝(与 ensureRepo 同一句 workDirNotRepo)—— 绝不删来路不明的目录。
|
|
456
|
+
*/
|
|
457
|
+
async rebuildWorkDir(mode) {
|
|
458
|
+
this.repoReady = false;
|
|
459
|
+
if (mode === 'relink') {
|
|
460
|
+
await this.ensureRepo(); // .git 存在 → reconcileOrigin
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
463
|
+
const fsx = createSnapshotFs();
|
|
464
|
+
await fsx.mkdir(this.o.workDir);
|
|
465
|
+
if (!(await fsx.exists(path.join(this.o.workDir, '.git')))) {
|
|
466
|
+
const entries = await fsx.readdir(this.o.workDir);
|
|
467
|
+
if (entries.length > 0) {
|
|
468
|
+
throw new GitTransportError(this.msg('sync.git.workDirNotRepo', { dir: this.o.workDir }), { kind: 'protocol', retryable: false });
|
|
469
|
+
}
|
|
470
|
+
await this.ensureRepo(); // 空目录 → 直接 clone
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
await fs.rm(this.o.workDir, { recursive: true, force: true });
|
|
474
|
+
await this.ensureRepo();
|
|
475
|
+
}
|
|
349
476
|
/**
|
|
350
477
|
* pull --ff-only 同步远端;无本地提交(全新仓库)或无 upstream 时静默跳过。
|
|
351
478
|
* opts.retryable=true(仅 list / download 等**幂等读**路径传):瞬时网络故障走有限指数退避重试。
|
|
@@ -361,7 +488,15 @@ export class GitTransport {
|
|
|
361
488
|
return;
|
|
362
489
|
if (/no tracking information/i.test(res.stderr))
|
|
363
490
|
return; // 无 upstream(初始状态)→ 跳过
|
|
364
|
-
|
|
491
|
+
const combined = res.stderr + ' ' + res.stdout;
|
|
492
|
+
// issue #79:这两类失败与网络无关,必须给「改回地址 / 重建工作副本」的出口
|
|
493
|
+
if (SSH_HOST_KEY_RE.test(combined)) {
|
|
494
|
+
throw new GitTransportError(this.hostKeyMessage(), { kind: 'auth', retryable: false });
|
|
495
|
+
}
|
|
496
|
+
if (DIVERGED_HISTORY_RE.test(combined)) {
|
|
497
|
+
throw new GitTransportError(this.msg('sync.git.historyDiverged', { dir: this.o.workDir }), { kind: 'conflict', retryable: false });
|
|
498
|
+
}
|
|
499
|
+
throw new GitTransportError(this.msg('sync.git.pullFailed', { err: this.mask(res.stderr, await this.readTokenOnce()) }), classifyNetworkErrorText(combined));
|
|
365
500
|
};
|
|
366
501
|
if (opts.retryable === true)
|
|
367
502
|
await withSyncRetry(attempt, this.o.retry);
|
|
@@ -396,8 +531,13 @@ export class GitTransport {
|
|
|
396
531
|
try {
|
|
397
532
|
const result = await this.o.exec(this.o.gitBin, [...extra, ...args], { cwd, timeoutMs: this.o.timeoutMs });
|
|
398
533
|
if (result.code !== 0 && !opts.allowNonZero) {
|
|
534
|
+
const combined = result.stderr + ' ' + result.stdout;
|
|
399
535
|
// 统一分类(超时 / 网络 / 鉴权 / 5xx…):上层可据 kind / retryable 分流,不必解析 message
|
|
400
|
-
const classified = classifyNetworkErrorText(
|
|
536
|
+
const classified = classifyNetworkErrorText(combined);
|
|
537
|
+
// issue #79:SSH 主机密钥校验失败 → 换成「怎么信任该主机」的可操作说明(绝不自动信任)
|
|
538
|
+
if (SSH_HOST_KEY_RE.test(combined)) {
|
|
539
|
+
throw new GitTransportError(this.hostKeyMessage(), { kind: 'auth', retryable: false });
|
|
540
|
+
}
|
|
401
541
|
// 推送 + 鉴权失败:git 原文只说「认证失败」——换成能照做的说明(令牌失效 / 无写权限 / 远端禁用了密码推送)
|
|
402
542
|
const message = opts.requirePushToken === true && classified.kind === 'auth'
|
|
403
543
|
? this.msg('sync.git.pushAuthFailed', { err: this.mask(result.stderr, token) })
|
|
@@ -473,6 +613,16 @@ export class GitTransport {
|
|
|
473
613
|
}
|
|
474
614
|
return out;
|
|
475
615
|
}
|
|
616
|
+
/**
|
|
617
|
+
* SSH 主机密钥失败的**可操作**文案(issue #79)。有 host 提示时带上 host[:port];
|
|
618
|
+
* 解析不出主机(异常地址)走无主机变体,绝不把解析失败的地址硬塞进文案。
|
|
619
|
+
*/
|
|
620
|
+
hostKeyMessage() {
|
|
621
|
+
const host = this.sshHostHint();
|
|
622
|
+
return host === ''
|
|
623
|
+
? this.msg('sync.git.hostKeyUnverifiedNoHost')
|
|
624
|
+
: this.msg('sync.git.hostKeyUnverified', { host });
|
|
625
|
+
}
|
|
476
626
|
blobsDir() {
|
|
477
627
|
return path.join(this.o.workDir, BLOBS_REL);
|
|
478
628
|
}
|
package/lib/sync/github-auth.js
CHANGED
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
*/
|
|
25
25
|
import { randomUUID } from 'node:crypto';
|
|
26
26
|
import { defaultFetcher } from '../utils/proxy.js';
|
|
27
|
+
import { engineMsg } from './messages.js';
|
|
27
28
|
/** GitHub device flow 第一步:请求设备码 */
|
|
28
29
|
export const GITHUB_DEVICE_CODE_URL = 'https://github.com/login/device/code';
|
|
29
30
|
/** GitHub device flow 第三步:轮询换取 access token */
|
|
@@ -51,7 +52,7 @@ export class GitHubAuthError extends Error {
|
|
|
51
52
|
async function parseGitHubJson(response, context) {
|
|
52
53
|
const text = await response.text();
|
|
53
54
|
if (text.trim() === '') {
|
|
54
|
-
throw new GitHubAuthError(
|
|
55
|
+
throw new GitHubAuthError(engineMsg('sync.githubEmptyResponse', { context, status: response.status }), 'empty_response', response.status);
|
|
55
56
|
}
|
|
56
57
|
try {
|
|
57
58
|
const parsed = JSON.parse(text);
|
|
@@ -61,7 +62,7 @@ async function parseGitHubJson(response, context) {
|
|
|
61
62
|
return parsed;
|
|
62
63
|
}
|
|
63
64
|
catch {
|
|
64
|
-
throw new GitHubAuthError(
|
|
65
|
+
throw new GitHubAuthError(engineMsg('sync.githubInvalidJson', { context, status: response.status }), 'invalid_json', response.status);
|
|
65
66
|
}
|
|
66
67
|
}
|
|
67
68
|
/**
|
|
@@ -79,7 +80,7 @@ export class GitHubAuthClient {
|
|
|
79
80
|
*/
|
|
80
81
|
async startDeviceFlow(clientId, scope = GITHUB_DEVICE_FLOW_SCOPE) {
|
|
81
82
|
if (typeof clientId !== 'string' || clientId === '') {
|
|
82
|
-
throw new GitHubAuthError('
|
|
83
|
+
throw new GitHubAuthError(engineMsg('sync.githubClientIdRequired'), 'invalid_client_id');
|
|
83
84
|
}
|
|
84
85
|
const body = new URLSearchParams();
|
|
85
86
|
body.set('client_id', clientId);
|
|
@@ -93,13 +94,13 @@ export class GitHubAuthClient {
|
|
|
93
94
|
});
|
|
94
95
|
}
|
|
95
96
|
catch (err) {
|
|
96
|
-
throw new GitHubAuthError(
|
|
97
|
+
throw new GitHubAuthError(engineMsg('sync.githubDeviceCodeRequestFailed', { err: err instanceof Error ? err.message : String(err) }), 'network_error');
|
|
97
98
|
}
|
|
98
|
-
const parsed = await parseGitHubJson(response, '
|
|
99
|
+
const parsed = await parseGitHubJson(response, engineMsg('sync.githubContextDeviceCode'));
|
|
99
100
|
const ghError = parsed['error'];
|
|
100
101
|
if (typeof ghError === 'string' && ghError !== '') {
|
|
101
102
|
const desc = parsed['error_description'];
|
|
102
|
-
throw new GitHubAuthError(
|
|
103
|
+
throw new GitHubAuthError(engineMsg('sync.githubDeviceCodeDenied', { detail: typeof desc === 'string' && desc !== '' ? desc : ghError }), ghError, response.status);
|
|
103
104
|
}
|
|
104
105
|
const deviceCode = parsed['device_code'];
|
|
105
106
|
const userCode = parsed['user_code'];
|
|
@@ -107,7 +108,7 @@ export class GitHubAuthClient {
|
|
|
107
108
|
if (typeof deviceCode !== 'string' || deviceCode === ''
|
|
108
109
|
|| typeof userCode !== 'string' || userCode === ''
|
|
109
110
|
|| typeof verificationUri !== 'string' || verificationUri === '') {
|
|
110
|
-
throw new GitHubAuthError('
|
|
111
|
+
throw new GitHubAuthError(engineMsg('sync.githubDeviceCodeFieldsMissing'), 'invalid_response', response.status);
|
|
111
112
|
}
|
|
112
113
|
const expiresIn = typeof parsed['expires_in'] === 'number' && Number.isFinite(parsed['expires_in'])
|
|
113
114
|
? Math.min(Math.max(Math.floor(parsed['expires_in']), 1), DEVICE_CODE_MAX_EXPIRES_IN)
|
|
@@ -139,9 +140,9 @@ export class GitHubAuthClient {
|
|
|
139
140
|
});
|
|
140
141
|
}
|
|
141
142
|
catch (err) {
|
|
142
|
-
throw new GitHubAuthError(
|
|
143
|
+
throw new GitHubAuthError(engineMsg('sync.githubTokenExchangeFailed', { err: err instanceof Error ? err.message : String(err) }), 'network_error');
|
|
143
144
|
}
|
|
144
|
-
const parsed = await parseGitHubJson(response, '
|
|
145
|
+
const parsed = await parseGitHubJson(response, engineMsg('sync.githubContextTokenExchange'));
|
|
145
146
|
const accessToken = parsed['access_token'];
|
|
146
147
|
if (typeof accessToken === 'string' && accessToken !== '') {
|
|
147
148
|
return { ok: true, accessToken };
|
package/lib/sync/layout.js
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* 核心逻辑经 SnapshotFs 注入(默认 node:fs/promises)。
|
|
8
8
|
*/
|
|
9
9
|
import path from 'node:path';
|
|
10
|
+
import { engineMsg } from './messages.js';
|
|
10
11
|
import { parseJsonSafe, stringifyJsonSafe } from '../utils/json.js';
|
|
11
12
|
import { sha256Hex } from '../utils/hashing.js';
|
|
12
13
|
import { filePrefixOf, jsonPathOf, SECTION_FILE_PREFIXES, SECTION_IDS, SECTION_JSON_PATHS, } from '../schema/section-registry.js';
|
|
@@ -101,18 +102,18 @@ export async function listSnapshotFiles(fsx, dir) {
|
|
|
101
102
|
export async function writeSnapshotToDir(snapshot, dir, fsx = createSnapshotFs(), opts = {}) {
|
|
102
103
|
// 加密快照不写散文件目录(本地不落盘:密文/明文都不落;远端已存密文)
|
|
103
104
|
if (isEncryptedSections(snapshot.sections)) {
|
|
104
|
-
throw new Error('
|
|
105
|
+
throw new Error(engineMsg('sync.layoutEncryptedDirUnsupported'));
|
|
105
106
|
}
|
|
106
107
|
// issue #38:凭据载荷只随加密快照的密文单文件布局传输。散文件布局无法承载它,
|
|
107
108
|
// 静默丢弃 = 「勾了导出密钥却什么也没导」,因此这里显式拒绝(调用方本不该走到这里)。
|
|
108
109
|
if (snapshot.credentials !== undefined) {
|
|
109
|
-
throw new Error('
|
|
110
|
+
throw new Error(engineMsg('sync.layoutCredentialsNeedEncryptedLayout'));
|
|
110
111
|
}
|
|
111
112
|
const sectionHashes = {};
|
|
112
113
|
for (const [id, data] of Object.entries(snapshot.sections)) {
|
|
113
114
|
const sid = id;
|
|
114
115
|
if (!LAYOUT_SECTION_IDS.includes(sid)) {
|
|
115
|
-
throw new Error(
|
|
116
|
+
throw new Error(engineMsg('sync.layoutSectionUnsupported', { section: sid }));
|
|
116
117
|
}
|
|
117
118
|
sectionHashes[sid] = hashSection(data);
|
|
118
119
|
}
|
|
@@ -122,7 +123,7 @@ export async function writeSnapshotToDir(snapshot, dir, fsx = createSnapshotFs()
|
|
|
122
123
|
continue;
|
|
123
124
|
for (const file of data.files) {
|
|
124
125
|
if (!isSafeRelPath(file.relativePath)) {
|
|
125
|
-
throw new Error(
|
|
126
|
+
throw new Error(engineMsg('sync.layoutFileRelPathInvalid', { path: file.relativePath, section: sid }));
|
|
126
127
|
}
|
|
127
128
|
}
|
|
128
129
|
}
|
|
@@ -177,24 +178,24 @@ export async function writeSnapshotToDir(snapshot, dir, fsx = createSnapshotFs()
|
|
|
177
178
|
export async function readSnapshotFromDir(dir, fsx = createSnapshotFs(), opts = {}) {
|
|
178
179
|
const manifestAbs = joinFs(dir, SNAPSHOT_MANIFEST_FILE);
|
|
179
180
|
if (!(await fsx.exists(manifestAbs))) {
|
|
180
|
-
throw new Error(
|
|
181
|
+
throw new Error(engineMsg('sync.layoutManifestMissing', { file: SNAPSHOT_MANIFEST_FILE, dir }));
|
|
181
182
|
}
|
|
182
183
|
const parsed = parseJsonSafe(Buffer.from(await fsx.readFile(manifestAbs)).toString('utf8'));
|
|
183
184
|
if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
184
|
-
throw new Error('
|
|
185
|
+
throw new Error(engineMsg('sync.layoutManifestNotObject'));
|
|
185
186
|
}
|
|
186
187
|
const m = parsed;
|
|
187
188
|
if (typeof m['id'] !== 'string' || typeof m['createdAt'] !== 'string'
|
|
188
189
|
|| m['manifest'] === null || typeof m['manifest'] !== 'object'
|
|
189
190
|
|| m['sectionHashes'] === null || typeof m['sectionHashes'] !== 'object') {
|
|
190
|
-
throw new Error('
|
|
191
|
+
throw new Error(engineMsg('sync.layoutManifestMissingFields'));
|
|
191
192
|
}
|
|
192
193
|
const manifest = parsed;
|
|
193
194
|
const sections = {};
|
|
194
195
|
const declared = Object.keys(manifest.sectionHashes);
|
|
195
196
|
for (const sid of declared) {
|
|
196
197
|
if (!LAYOUT_SECTION_IDS.includes(sid)) {
|
|
197
|
-
throw new Error(
|
|
198
|
+
throw new Error(engineMsg('sync.layoutManifestSectionUnsupported', { section: sid }));
|
|
198
199
|
}
|
|
199
200
|
}
|
|
200
201
|
// JSON 分区
|
|
@@ -203,7 +204,7 @@ export async function readSnapshotFromDir(dir, fsx = createSnapshotFs(), opts =
|
|
|
203
204
|
continue;
|
|
204
205
|
const abs = joinFs(dir, rel);
|
|
205
206
|
if (!(await fsx.exists(abs))) {
|
|
206
|
-
throw new Error(
|
|
207
|
+
throw new Error(engineMsg('sync.layoutJsonSectionMissing', { path: rel, section: sid }));
|
|
207
208
|
}
|
|
208
209
|
sections[sid] = parseJsonSafe(Buffer.from(await fsx.readFile(abs)).toString('utf8'));
|
|
209
210
|
}
|
|
@@ -216,10 +217,10 @@ export async function readSnapshotFromDir(dir, fsx = createSnapshotFs(), opts =
|
|
|
216
217
|
if (await fsx.exists(refsAbs)) {
|
|
217
218
|
const parsedRefs = parseJsonSafe(Buffer.from(await fsx.readFile(refsAbs)).toString('utf8'));
|
|
218
219
|
if (!isBlobRefsSection(parsedRefs)) {
|
|
219
|
-
throw new Error(
|
|
220
|
+
throw new Error(engineMsg('sync.layoutBlobRefsInvalid', { file: blobRefsFile(sid) }));
|
|
220
221
|
}
|
|
221
222
|
if (opts.blobs === undefined) {
|
|
222
|
-
throw new Error(
|
|
223
|
+
throw new Error(engineMsg('sync.layoutBlobRefsNoSink', { section: sid }));
|
|
223
224
|
}
|
|
224
225
|
sections[sid] = await refsToSection(parsedRefs, opts.blobs);
|
|
225
226
|
continue;
|
|
@@ -231,7 +232,7 @@ export async function readSnapshotFromDir(dir, fsx = createSnapshotFs(), opts =
|
|
|
231
232
|
sections[sid] = { version: 1, files: [] };
|
|
232
233
|
continue;
|
|
233
234
|
}
|
|
234
|
-
throw new Error(
|
|
235
|
+
throw new Error(engineMsg('sync.layoutFileSectionDirMissing', { prefix, section: sid }));
|
|
235
236
|
}
|
|
236
237
|
const rels = await listSnapshotFiles(fsx, baseAbs);
|
|
237
238
|
const files = [];
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { MsgFunc } from '../core/msg-types.ts';
|
|
2
|
+
export declare const zh: {
|
|
3
|
+
readonly 'sync.ancestorSnapshotIdRequired': 'loadAncestor: snapshotId 不能为空';
|
|
4
|
+
readonly 'sync.snapshotSectionsNotObject': '快照 sections 必须是对象';
|
|
5
|
+
readonly 'sync.snapshotJsonInvalid': '快照 JSON 形状非法(缺少 id/createdAt/manifest/sections)';
|
|
6
|
+
readonly 'sync.snapshotStillEncrypted': '快照仍为加密载荷,无法转 ZIP(需先解密)';
|
|
7
|
+
readonly 'sync.encryptPasswordRequired': '加密密码不能为空';
|
|
8
|
+
readonly 'sync.decryptPasswordRequired': '解密密码不能为空';
|
|
9
|
+
readonly 'sync.encryptedContentInvalid': '加密快照内容损坏:解密后不是有效的分区对象';
|
|
10
|
+
readonly 'sync.layoutEncryptedDirUnsupported': '加密快照不写散文件目录(本地不落盘;请直接使用密文传输)';
|
|
11
|
+
readonly 'sync.layoutCredentialsNeedEncryptedLayout': '凭据载荷只能随加密快照的密文单文件布局传输(散文件目录不落任何凭据)';
|
|
12
|
+
readonly 'sync.layoutSectionUnsupported': '分区 {section} 不在散文件布局支持范围(SECTION_JSON_PATHS / SECTION_FILE_PREFIXES)';
|
|
13
|
+
readonly 'sync.layoutFileRelPathInvalid': '非法文件相对路径: {path}(分区 {section})';
|
|
14
|
+
readonly 'sync.layoutManifestMissing': '快照目录缺少 {file}: {dir}';
|
|
15
|
+
readonly 'sync.layoutManifestNotObject': 'manifest.json 损坏:必须是对象';
|
|
16
|
+
readonly 'sync.layoutManifestMissingFields': 'manifest.json 损坏:缺少 id/createdAt/manifest/sectionHashes';
|
|
17
|
+
readonly 'sync.layoutManifestSectionUnsupported': 'manifest 声明了布局不支持的分区 {section}';
|
|
18
|
+
readonly 'sync.layoutJsonSectionMissing': '快照缺少 JSON 分区文件 {path}({section})';
|
|
19
|
+
readonly 'sync.layoutBlobRefsInvalid': '外置引用文件形状非法: {file}';
|
|
20
|
+
readonly 'sync.layoutBlobRefsNoSink': '快照的 {section} 分区是内容寻址外置形态,但当前通道未提供 blob 仓';
|
|
21
|
+
readonly 'sync.layoutFileSectionDirMissing': '快照缺少文件分区目录 {prefix}({section})';
|
|
22
|
+
readonly 'sync.blobMissing': '内容的 blob 缺失({hash},{path}):远端仓不完整,拒绝降级为空分区';
|
|
23
|
+
readonly 'sync.webdav.blobHashInvalid': '内容哈希形状非法,拒绝写入 blob 仓: {hash}';
|
|
24
|
+
readonly 'sync.reviewQueueNotObject': 'sync-review-queue.json 损坏:必须是对象';
|
|
25
|
+
readonly 'sync.reviewQueueItemsNotArray': 'sync-review-queue.json 损坏:items 必须是数组';
|
|
26
|
+
readonly 'sync.reviewQueueItemNotObject': 'sync-review-queue.json 损坏:每个 item 必须是对象';
|
|
27
|
+
readonly 'sync.reviewQueueItemFieldsMissing': 'sync-review-queue.json 损坏:item 必须含字符串 id/sectionId/kind/description';
|
|
28
|
+
readonly 'sync.reviewQueueItemNotFound': 'resolveItem: 未找到 itemId {id}';
|
|
29
|
+
readonly 'sync.historyJsonParseFailed': '{file} 损坏:JSON 解析失败';
|
|
30
|
+
readonly 'sync.historyNotObject': '{file} 损坏:必须是对象';
|
|
31
|
+
readonly 'sync.historySchemaVersionMismatch': '{file} 损坏:schemaVersion 必须是 {version}';
|
|
32
|
+
readonly 'sync.historyEntriesNotArray': '{file} 损坏:autosyncEntries 必须是数组';
|
|
33
|
+
readonly 'sync.historyEntryNotObject': '{file} 损坏:每个 entry 必须是对象';
|
|
34
|
+
readonly 'sync.historyEntryFieldsMissing': '{file} 损坏:entry 必须含字符串 direction/status/createdAt';
|
|
35
|
+
readonly 'sync.githubEmptyResponse': '{context}: GitHub 返回空响应(HTTP {status})';
|
|
36
|
+
readonly 'sync.githubInvalidJson': '{context}: GitHub 返回了无法解析的响应(HTTP {status}),请稍后重试';
|
|
37
|
+
readonly 'sync.githubClientIdRequired': 'client_id 必须是非空字符串';
|
|
38
|
+
readonly 'sync.githubDeviceCodeRequestFailed': '请求 GitHub 设备码失败:{err}';
|
|
39
|
+
readonly 'sync.githubDeviceCodeDenied': 'GitHub 拒绝了设备码请求:{detail}';
|
|
40
|
+
readonly 'sync.githubDeviceCodeFieldsMissing': 'GitHub 设备码响应缺少必要字段(device_code/user_code/verification_uri)';
|
|
41
|
+
readonly 'sync.githubTokenExchangeFailed': '交换 GitHub token 失败:{err}';
|
|
42
|
+
readonly 'sync.githubContextDeviceCode': '请求设备码';
|
|
43
|
+
readonly 'sync.githubContextTokenExchange': '交换 token';
|
|
44
|
+
readonly 'sync.rollbackProofFailed': '回滚失败(未能证明已恢复):{err}';
|
|
45
|
+
readonly 'sync.rollbackManualHintParen': '({hint})';
|
|
46
|
+
readonly 'sync.rollbackIncomplete': '回滚未完成:{item} —— {reason}{hint}';
|
|
47
|
+
readonly 'sync.applyMergePlanNoImporter': 'applyMergePlan: SyncEngine 缺少 importer(需在 options 中注入)';
|
|
48
|
+
readonly 'sync.applyItemsNoImporter': 'applyItems: SyncEngine 缺少 importer(需在 options 中注入)';
|
|
49
|
+
readonly 'sync.preApplySnapshotFailed': '应用前快照失败:{err}';
|
|
50
|
+
readonly 'sync.autosyncApplyMergeFailed': 'applyMergePlan 执行失败';
|
|
51
|
+
readonly 'sync.autosyncPushFailed': 'push 失败';
|
|
52
|
+
readonly 'sync.autosyncConsecutiveFailureNotified': '连续失败 {count} 次,已通知';
|
|
53
|
+
readonly 'sync.backupExportsDirUnreadable': '导出目录读不出来({code}):{dir}';
|
|
54
|
+
readonly 'sync.backupFileNameInvalid': '非法备份文件名: {name}';
|
|
55
|
+
readonly 'sync.backupTriggerManual': '手动备份';
|
|
56
|
+
readonly 'sync.backupTriggerScheduled': '定时备份';
|
|
57
|
+
readonly 'sync.backupHistoryDone': '{trigger}完成:{zip}';
|
|
58
|
+
readonly 'sync.backupHistorySkipped': '{trigger}跳过:{reason}';
|
|
59
|
+
readonly 'sync.backupHistoryFailed': '{trigger}失败';
|
|
60
|
+
};
|
|
61
|
+
export declare const en: Record<keyof typeof zh, string>;
|
|
62
|
+
/** 本域活翻译器(与 core 的 engineMsg 同一实现;导入本模块即完成目录注册)。 */
|
|
63
|
+
export declare const engineMsg: MsgFunc;
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 同步域(src/sync/**)的追加消息目录(zh 源 / en 镜像),归 t6 作用域。
|
|
3
|
+
*
|
|
4
|
+
* 为什么不写进 \`src/core/messages.ts\`:该文件不在本作用域的 inScope 内,且既有 372 键有冻结指纹
|
|
5
|
+
* (改既有键即红灯)。本模块经 \`registerMessageCatalog\` 注册一本**追加目录**:engineMsg / makeMsg 的查找
|
|
6
|
+
* 顺序是「既有目录 → en 下回退既有 zh → 注册目录 → 键名」,因此本域新键**只在既有目录未命中时生效**,
|
|
7
|
+
* 不可能遮蔽 core 的既有键(含 sync.git.* / sync.webdav.* 的既有键)。
|
|
8
|
+
*
|
|
9
|
+
* 纪律:
|
|
10
|
+
* - zh 值 = 改动前的既有中文文案**逐字原样**(只把字面量搬进目录,拼接顺序/空格/全角标点不变);
|
|
11
|
+
* - en 为 \`Record<keyof typeof zh, string>\` —— 缺键/多键即编译错误;
|
|
12
|
+
* - 本域消费面是注入的 \`MsgFunc\`(ctx.msg / sync-engine 的 this.msg)与 \`engineMsg\`,两者都查注册目录;
|
|
13
|
+
* - **导入本模块即完成注册**:凡引用本域键的模块都要 import 本模块(或 import 它导出的 engineMsg)。
|
|
14
|
+
* - 例外(不得改动):\`src/sync/s3/messages.ts\` 与 \`src/sync/gist/messages.ts\` 是通道**本地**目录,
|
|
15
|
+
* 它们的键经 \`composeMsg(host, local)\` 解析(t79/t90 的既有护栏断言 core 直查必然回退键名 ⇒
|
|
16
|
+
* 那两本目录**不得**注册进这里,否则既有回归护栏的前提被打破)。s3/gist 的新键一律落进各自本地目录。
|
|
17
|
+
*/
|
|
18
|
+
import { engineMsg as engineMsgOf, registerMessageCatalog } from '../core/engine-locale.js';
|
|
19
|
+
export const zh = {
|
|
20
|
+
// ---------- 快照 / 祖先 ----------
|
|
21
|
+
'sync.ancestorSnapshotIdRequired': 'loadAncestor: snapshotId 不能为空',
|
|
22
|
+
'sync.snapshotSectionsNotObject': '快照 sections 必须是对象',
|
|
23
|
+
'sync.snapshotJsonInvalid': '快照 JSON 形状非法(缺少 id/createdAt/manifest/sections)',
|
|
24
|
+
'sync.snapshotStillEncrypted': '快照仍为加密载荷,无法转 ZIP(需先解密)',
|
|
25
|
+
'sync.encryptPasswordRequired': '加密密码不能为空',
|
|
26
|
+
'sync.decryptPasswordRequired': '解密密码不能为空',
|
|
27
|
+
'sync.encryptedContentInvalid': '加密快照内容损坏:解密后不是有效的分区对象',
|
|
28
|
+
// ---------- 散文件布局 ----------
|
|
29
|
+
'sync.layoutEncryptedDirUnsupported': '加密快照不写散文件目录(本地不落盘;请直接使用密文传输)',
|
|
30
|
+
'sync.layoutCredentialsNeedEncryptedLayout': '凭据载荷只能随加密快照的密文单文件布局传输(散文件目录不落任何凭据)',
|
|
31
|
+
'sync.layoutSectionUnsupported': '分区 {section} 不在散文件布局支持范围(SECTION_JSON_PATHS / SECTION_FILE_PREFIXES)',
|
|
32
|
+
'sync.layoutFileRelPathInvalid': '非法文件相对路径: {path}(分区 {section})',
|
|
33
|
+
'sync.layoutManifestMissing': '快照目录缺少 {file}: {dir}',
|
|
34
|
+
'sync.layoutManifestNotObject': 'manifest.json 损坏:必须是对象',
|
|
35
|
+
'sync.layoutManifestMissingFields': 'manifest.json 损坏:缺少 id/createdAt/manifest/sectionHashes',
|
|
36
|
+
'sync.layoutManifestSectionUnsupported': 'manifest 声明了布局不支持的分区 {section}',
|
|
37
|
+
'sync.layoutJsonSectionMissing': '快照缺少 JSON 分区文件 {path}({section})',
|
|
38
|
+
'sync.layoutBlobRefsInvalid': '外置引用文件形状非法: {file}',
|
|
39
|
+
'sync.layoutBlobRefsNoSink': '快照的 {section} 分区是内容寻址外置形态,但当前通道未提供 blob 仓',
|
|
40
|
+
'sync.layoutFileSectionDirMissing': '快照缺少文件分区目录 {prefix}({section})',
|
|
41
|
+
// ---------- 内容寻址 blob 仓 ----------
|
|
42
|
+
'sync.blobMissing': '内容的 blob 缺失({hash},{path}):远端仓不完整,拒绝降级为空分区',
|
|
43
|
+
'sync.webdav.blobHashInvalid': '内容哈希形状非法,拒绝写入 blob 仓: {hash}',
|
|
44
|
+
// ---------- 审查队列 ----------
|
|
45
|
+
'sync.reviewQueueNotObject': 'sync-review-queue.json 损坏:必须是对象',
|
|
46
|
+
'sync.reviewQueueItemsNotArray': 'sync-review-queue.json 损坏:items 必须是数组',
|
|
47
|
+
'sync.reviewQueueItemNotObject': 'sync-review-queue.json 损坏:每个 item 必须是对象',
|
|
48
|
+
'sync.reviewQueueItemFieldsMissing': 'sync-review-queue.json 损坏:item 必须含字符串 id/sectionId/kind/description',
|
|
49
|
+
'sync.reviewQueueItemNotFound': 'resolveItem: 未找到 itemId {id}',
|
|
50
|
+
// ---------- 同步历史 ----------
|
|
51
|
+
'sync.historyJsonParseFailed': '{file} 损坏:JSON 解析失败',
|
|
52
|
+
'sync.historyNotObject': '{file} 损坏:必须是对象',
|
|
53
|
+
'sync.historySchemaVersionMismatch': '{file} 损坏:schemaVersion 必须是 {version}',
|
|
54
|
+
'sync.historyEntriesNotArray': '{file} 损坏:autosyncEntries 必须是数组',
|
|
55
|
+
'sync.historyEntryNotObject': '{file} 损坏:每个 entry 必须是对象',
|
|
56
|
+
'sync.historyEntryFieldsMissing': '{file} 损坏:entry 必须含字符串 direction/status/createdAt',
|
|
57
|
+
// ---------- GitHub 设备码登录 ----------
|
|
58
|
+
'sync.githubEmptyResponse': '{context}: GitHub 返回空响应(HTTP {status})',
|
|
59
|
+
'sync.githubInvalidJson': '{context}: GitHub 返回了无法解析的响应(HTTP {status}),请稍后重试',
|
|
60
|
+
'sync.githubClientIdRequired': 'client_id 必须是非空字符串',
|
|
61
|
+
'sync.githubDeviceCodeRequestFailed': '请求 GitHub 设备码失败:{err}',
|
|
62
|
+
'sync.githubDeviceCodeDenied': 'GitHub 拒绝了设备码请求:{detail}',
|
|
63
|
+
'sync.githubDeviceCodeFieldsMissing': 'GitHub 设备码响应缺少必要字段(device_code/user_code/verification_uri)',
|
|
64
|
+
'sync.githubTokenExchangeFailed': '交换 GitHub token 失败:{err}',
|
|
65
|
+
'sync.githubContextDeviceCode': '请求设备码',
|
|
66
|
+
'sync.githubContextTokenExchange': '交换 token',
|
|
67
|
+
// ---------- 回滚 / 应用 ----------
|
|
68
|
+
'sync.rollbackProofFailed': '回滚失败(未能证明已恢复):{err}',
|
|
69
|
+
'sync.rollbackManualHintParen': '({hint})',
|
|
70
|
+
'sync.rollbackIncomplete': '回滚未完成:{item} —— {reason}{hint}',
|
|
71
|
+
'sync.applyMergePlanNoImporter': 'applyMergePlan: SyncEngine 缺少 importer(需在 options 中注入)',
|
|
72
|
+
'sync.applyItemsNoImporter': 'applyItems: SyncEngine 缺少 importer(需在 options 中注入)',
|
|
73
|
+
'sync.preApplySnapshotFailed': '应用前快照失败:{err}',
|
|
74
|
+
// ---------- 自动同步 ----------
|
|
75
|
+
'sync.autosyncApplyMergeFailed': 'applyMergePlan 执行失败',
|
|
76
|
+
'sync.autosyncPushFailed': 'push 失败',
|
|
77
|
+
'sync.autosyncConsecutiveFailureNotified': '连续失败 {count} 次,已通知',
|
|
78
|
+
// ---------- 备份产物 / 调度 ----------
|
|
79
|
+
'sync.backupExportsDirUnreadable': '导出目录读不出来({code}):{dir}',
|
|
80
|
+
'sync.backupFileNameInvalid': '非法备份文件名: {name}',
|
|
81
|
+
'sync.backupTriggerManual': '手动备份',
|
|
82
|
+
'sync.backupTriggerScheduled': '定时备份',
|
|
83
|
+
'sync.backupHistoryDone': '{trigger}完成:{zip}',
|
|
84
|
+
'sync.backupHistorySkipped': '{trigger}跳过:{reason}',
|
|
85
|
+
'sync.backupHistoryFailed': '{trigger}失败',
|
|
86
|
+
};
|
|
87
|
+
export const en = {
|
|
88
|
+
'sync.ancestorSnapshotIdRequired': 'loadAncestor: snapshotId must not be empty',
|
|
89
|
+
'sync.snapshotSectionsNotObject': 'Snapshot sections must be an object',
|
|
90
|
+
'sync.snapshotJsonInvalid': 'Malformed snapshot JSON (missing id/createdAt/manifest/sections)',
|
|
91
|
+
'sync.snapshotStillEncrypted': 'The snapshot is still an encrypted payload and cannot be converted to ZIP (decrypt it first)',
|
|
92
|
+
'sync.encryptPasswordRequired': 'The encryption password must not be empty',
|
|
93
|
+
'sync.decryptPasswordRequired': 'The decryption password must not be empty',
|
|
94
|
+
'sync.encryptedContentInvalid': 'The encrypted snapshot is corrupt: after decryption it is not a valid sections object',
|
|
95
|
+
'sync.layoutEncryptedDirUnsupported': 'Encrypted snapshots are not written as a loose-file directory (nothing is stored locally; transfer the ciphertext instead)',
|
|
96
|
+
'sync.layoutCredentialsNeedEncryptedLayout': 'Credential payloads can only travel inside the single ciphertext file of an encrypted snapshot (the loose-file layout stores no credentials)',
|
|
97
|
+
'sync.layoutSectionUnsupported': 'Section {section} is outside the loose-file layout support range (SECTION_JSON_PATHS / SECTION_FILE_PREFIXES)',
|
|
98
|
+
'sync.layoutFileRelPathInvalid': 'Invalid relative file path: {path} (section {section})',
|
|
99
|
+
'sync.layoutManifestMissing': 'Snapshot directory is missing {file}: {dir}',
|
|
100
|
+
'sync.layoutManifestNotObject': 'manifest.json is corrupt: must be an object',
|
|
101
|
+
'sync.layoutManifestMissingFields': 'manifest.json is corrupt: missing id/createdAt/manifest/sectionHashes',
|
|
102
|
+
'sync.layoutManifestSectionUnsupported': 'The manifest declares section {section}, which the layout does not support',
|
|
103
|
+
'sync.layoutJsonSectionMissing': 'Snapshot is missing the JSON section file {path} ({section})',
|
|
104
|
+
'sync.layoutBlobRefsInvalid': 'Malformed external reference file: {file}',
|
|
105
|
+
'sync.layoutBlobRefsNoSink': 'Section {section} of the snapshot uses content-addressed external form, but the current channel provides no blob store',
|
|
106
|
+
'sync.layoutFileSectionDirMissing': 'Snapshot is missing the file section directory {prefix} ({section})',
|
|
107
|
+
'sync.blobMissing': 'The blob for {path} is missing ({hash}): the remote store is incomplete, refusing to degrade to an empty section',
|
|
108
|
+
'sync.webdav.blobHashInvalid': 'Malformed content hash; refusing to write to the blob store: {hash}',
|
|
109
|
+
'sync.reviewQueueNotObject': 'sync-review-queue.json is corrupt: must be an object',
|
|
110
|
+
'sync.reviewQueueItemsNotArray': 'sync-review-queue.json is corrupt: items must be an array',
|
|
111
|
+
'sync.reviewQueueItemNotObject': 'sync-review-queue.json is corrupt: every item must be an object',
|
|
112
|
+
'sync.reviewQueueItemFieldsMissing': 'sync-review-queue.json is corrupt: an item must contain the strings id/sectionId/kind/description',
|
|
113
|
+
'sync.reviewQueueItemNotFound': 'resolveItem: no item found for itemId {id}',
|
|
114
|
+
'sync.historyJsonParseFailed': '{file} is corrupt: JSON parsing failed',
|
|
115
|
+
'sync.historyNotObject': '{file} is corrupt: must be an object',
|
|
116
|
+
'sync.historySchemaVersionMismatch': '{file} is corrupt: schemaVersion must be {version}',
|
|
117
|
+
'sync.historyEntriesNotArray': '{file} is corrupt: autosyncEntries must be an array',
|
|
118
|
+
'sync.historyEntryNotObject': '{file} is corrupt: every entry must be an object',
|
|
119
|
+
'sync.historyEntryFieldsMissing': '{file} is corrupt: an entry must contain the strings direction/status/createdAt',
|
|
120
|
+
'sync.githubEmptyResponse': '{context}: GitHub returned an empty response (HTTP {status})',
|
|
121
|
+
'sync.githubInvalidJson': '{context}: GitHub returned an unparsable response (HTTP {status}), please retry later',
|
|
122
|
+
'sync.githubClientIdRequired': 'client_id must be a non-empty string',
|
|
123
|
+
'sync.githubDeviceCodeRequestFailed': 'Failed to request the GitHub device code: {err}',
|
|
124
|
+
'sync.githubDeviceCodeDenied': 'GitHub rejected the device code request: {detail}',
|
|
125
|
+
'sync.githubDeviceCodeFieldsMissing': 'The GitHub device code response is missing required fields (device_code/user_code/verification_uri)',
|
|
126
|
+
'sync.githubTokenExchangeFailed': 'Failed to exchange the GitHub token: {err}',
|
|
127
|
+
'sync.githubContextDeviceCode': 'device code request',
|
|
128
|
+
'sync.githubContextTokenExchange': 'token exchange',
|
|
129
|
+
'sync.rollbackProofFailed': 'Rollback failed (could not prove anything was restored): {err}',
|
|
130
|
+
'sync.rollbackManualHintParen': ' ({hint})',
|
|
131
|
+
'sync.rollbackIncomplete': 'Rollback incomplete: {item} — {reason}{hint}',
|
|
132
|
+
'sync.applyMergePlanNoImporter': 'applyMergePlan: SyncEngine is missing the importer (must be injected via options)',
|
|
133
|
+
'sync.applyItemsNoImporter': 'applyItems: SyncEngine is missing the importer (must be injected via options)',
|
|
134
|
+
'sync.preApplySnapshotFailed': 'Pre-apply snapshot failed: {err}',
|
|
135
|
+
'sync.autosyncApplyMergeFailed': 'applyMergePlan failed',
|
|
136
|
+
'sync.autosyncPushFailed': 'push failed',
|
|
137
|
+
'sync.autosyncConsecutiveFailureNotified': 'Failed {count} times in a row; notification sent',
|
|
138
|
+
'sync.backupExportsDirUnreadable': 'Cannot read the export directory ({code}): {dir}',
|
|
139
|
+
'sync.backupFileNameInvalid': 'Invalid backup file name: {name}',
|
|
140
|
+
'sync.backupTriggerManual': 'Manual backup',
|
|
141
|
+
'sync.backupTriggerScheduled': 'Scheduled backup',
|
|
142
|
+
'sync.backupHistoryDone': '{trigger} finished: {zip}',
|
|
143
|
+
'sync.backupHistorySkipped': '{trigger} skipped: {reason}',
|
|
144
|
+
'sync.backupHistoryFailed': '{trigger} failed',
|
|
145
|
+
};
|
|
146
|
+
registerMessageCatalog({ id: 'sync-domain', zh, en });
|
|
147
|
+
/** 本域活翻译器(与 core 的 engineMsg 同一实现;导入本模块即完成目录注册)。 */
|
|
148
|
+
export const engineMsg = engineMsgOf;
|
|
149
|
+
//# sourceMappingURL=messages.js.map
|
package/lib/sync/review-queue.js
CHANGED
|
@@ -16,6 +16,7 @@ import path from 'node:path';
|
|
|
16
16
|
import crypto from 'node:crypto';
|
|
17
17
|
import { parseJsonSafe, stringifyJsonSafe } from '../utils/json.js';
|
|
18
18
|
import { atomicWriteFile } from '../utils/atomic-write.js';
|
|
19
|
+
import { engineMsg } from './messages.js';
|
|
19
20
|
export const REVIEW_QUEUE_FILE = 'sync-review-queue.json';
|
|
20
21
|
export const EMPTY_REVIEW_QUEUE = { items: [], updatedAt: '' };
|
|
21
22
|
/** 读取待审队列;文件不存在 → 返回空队列;损坏 JSON 抛错。 */
|
|
@@ -25,21 +26,21 @@ export async function readReviewQueue(stateDir, fsx = fs) {
|
|
|
25
26
|
const raw = await fsx.readFile(file, 'utf8');
|
|
26
27
|
const parsed = parseJsonSafe(raw);
|
|
27
28
|
if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
28
|
-
throw new Error('sync
|
|
29
|
+
throw new Error(engineMsg('sync.reviewQueueNotObject'));
|
|
29
30
|
}
|
|
30
31
|
const obj = parsed;
|
|
31
32
|
if (!Array.isArray(obj['items'])) {
|
|
32
|
-
throw new Error('sync
|
|
33
|
+
throw new Error(engineMsg('sync.reviewQueueItemsNotArray'));
|
|
33
34
|
}
|
|
34
35
|
const items = [];
|
|
35
36
|
for (const it of obj['items']) {
|
|
36
37
|
if (it === null || typeof it !== 'object' || Array.isArray(it)) {
|
|
37
|
-
throw new Error('sync
|
|
38
|
+
throw new Error(engineMsg('sync.reviewQueueItemNotObject'));
|
|
38
39
|
}
|
|
39
40
|
const i = it;
|
|
40
41
|
if (typeof i['id'] !== 'string' || typeof i['sectionId'] !== 'string'
|
|
41
42
|
|| typeof i['kind'] !== 'string' || typeof i['description'] !== 'string') {
|
|
42
|
-
throw new Error('sync
|
|
43
|
+
throw new Error(engineMsg('sync.reviewQueueItemFieldsMissing'));
|
|
43
44
|
}
|
|
44
45
|
items.push(i);
|
|
45
46
|
}
|
|
@@ -94,7 +95,7 @@ export async function resolveItem(stateDir, itemId, decision, now = () => new Da
|
|
|
94
95
|
const current = await readReviewQueue(stateDir, fsx);
|
|
95
96
|
const idx = current.items.findIndex((i) => i.id === itemId);
|
|
96
97
|
if (idx === -1)
|
|
97
|
-
throw new Error(
|
|
98
|
+
throw new Error(engineMsg('sync.reviewQueueItemNotFound', { id: itemId }));
|
|
98
99
|
current.items[idx] = { ...current.items[idx], decision, decidedAt: now().toISOString() };
|
|
99
100
|
current.updatedAt = now().toISOString();
|
|
100
101
|
await writeReviewQueue(stateDir, current, fsx);
|