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,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 「会话标题」的**日志兜底**(2026-10-12 用户要求:缓存里没有这条会话,就自动读这条会话自己的名字)。
|
|
3
|
+
*
|
|
4
|
+
* 两条来源,优先级固定:
|
|
5
|
+
* ① `storages/session_projcache.json` 的 `rows.title.val`(`core/session-meta.ts` 的索引,快、覆盖常态);
|
|
6
|
+
* ② **会话日志里的 `session/title` 事件**(本模块)—— DSH 自己写的标题事件,
|
|
7
|
+
* `data = { title, messageSeqs, source }`;`source = 'user'` 表示用户显式改过名(它压过自动生成的那条)。
|
|
8
|
+
* 缓存里没有这条会话(旧会话 / 别的档案写的 / 子代理会话)时,这是**唯一不猜**的名字来源。
|
|
9
|
+
*
|
|
10
|
+
* 只读 + 有界 + 诚实:
|
|
11
|
+
* · 只读日志**前** {@link SESSION_TITLE_SCAN_BYTES}(1 MiB,覆盖「标题在开头几回合内生成」的常态);
|
|
12
|
+
* 超出这个窗口的标题事件读不到 → 返回 null,界面回落显示会话 id(**绝不从首条消息编造标题**);
|
|
13
|
+
* · 同一个会话出现多条标题事件时取**最后一条**(用户改名压过自动标题);
|
|
14
|
+
* · 同一条日志按 `mtime + size` 缓存在进程内:日志增长(新标题落盘)后自动重读,不吃陈旧结论;
|
|
15
|
+
* · 一次请求最多补 {@link SESSION_TITLE_MAX_READS} 个会话(台账里几十条缺标题时不把盘读穿)。
|
|
16
|
+
*/
|
|
17
|
+
import { open, readdir, stat } from 'node:fs/promises';
|
|
18
|
+
import { join } from 'node:path';
|
|
19
|
+
|
|
20
|
+
import { isSessionLogName } from '../utils/session-log.ts';
|
|
21
|
+
import { decodeZstdFrame, scanZstdFrames, zstdAvailable } from '../utils/zstd-frame.ts';
|
|
22
|
+
|
|
23
|
+
/** 只扫日志前这么多字节(标题通常在开头几回合内写入;更大的日志只扫前缀,读不到就回落 id)。 */
|
|
24
|
+
export const SESSION_TITLE_SCAN_BYTES = 1024 * 1024;
|
|
25
|
+
|
|
26
|
+
/** 一次请求最多为几个会话做日志兜底(按 unitId 去重之后)。 */
|
|
27
|
+
export const SESSION_TITLE_MAX_READS = 24;
|
|
28
|
+
|
|
29
|
+
interface TitleCacheEntry {
|
|
30
|
+
mtimeMs: number;
|
|
31
|
+
size: number;
|
|
32
|
+
title: string | null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** 进程内缓存:绝对路径 → (mtime + size + 结论)。日志一增长就失效(不吃陈旧结论)。 */
|
|
36
|
+
const titleCache = new Map<string, TitleCacheEntry>();
|
|
37
|
+
|
|
38
|
+
/** 清空缓存(单测用;宿主进程内长期有效是刻意的)。 */
|
|
39
|
+
export function clearSessionTitleCache(): void {
|
|
40
|
+
titleCache.clear();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** `session/title` 事件行 → 标题(形状不对 / 空标题一律 null,绝不猜)。 */
|
|
44
|
+
function titleOfRow(row: unknown): string | null {
|
|
45
|
+
if (row === null || typeof row !== 'object' || Array.isArray(row)) return null;
|
|
46
|
+
const rec = row as Record<string, unknown>;
|
|
47
|
+
if (rec['type'] !== 'session/title') return null;
|
|
48
|
+
const data = rec['data'];
|
|
49
|
+
if (data === null || typeof data !== 'object' || Array.isArray(data)) return null;
|
|
50
|
+
const title = (data as Record<string, unknown>)['title'];
|
|
51
|
+
return typeof title === 'string' && title.trim() !== '' ? title.trim() : null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** 只读日志**前缀**(最多 `max` 字节;读不出回 null,绝不抛)。 */
|
|
55
|
+
async function readPrefix(absPath: string, max: number): Promise<Buffer | null> {
|
|
56
|
+
let handle: Awaited<ReturnType<typeof open>>;
|
|
57
|
+
try {
|
|
58
|
+
handle = await open(absPath, 'r');
|
|
59
|
+
} catch {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
try {
|
|
63
|
+
const buffer = Buffer.allocUnsafe(max);
|
|
64
|
+
const { bytesRead } = await handle.read(buffer, 0, max, 0);
|
|
65
|
+
return buffer.subarray(0, bytesRead);
|
|
66
|
+
} catch {
|
|
67
|
+
return null;
|
|
68
|
+
} finally {
|
|
69
|
+
await handle.close().catch(() => undefined);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* 从会话日志字节里取标题:解出每个**完整**帧,逐行先做 `session/title` 子串预筛(省掉整篇 JSON.parse),
|
|
75
|
+
* 命中才解析该行;多条命中取最后一条。容器损坏 / 无 zstd 能力 → null。
|
|
76
|
+
*/
|
|
77
|
+
function titleFromBytes(bytes: Uint8Array): string | null {
|
|
78
|
+
let frames: { start: number; end: number }[];
|
|
79
|
+
try {
|
|
80
|
+
frames = scanZstdFrames(bytes).frames;
|
|
81
|
+
} catch {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
let found: string | null = null;
|
|
85
|
+
for (const frame of frames) {
|
|
86
|
+
let text: string;
|
|
87
|
+
try {
|
|
88
|
+
text = decodeZstdFrame(bytes.subarray(frame.start, frame.end)).toString('utf8');
|
|
89
|
+
} catch {
|
|
90
|
+
return found;
|
|
91
|
+
}
|
|
92
|
+
for (const line of text.split('\n')) {
|
|
93
|
+
if (line === '' || !line.includes('session/title')) continue;
|
|
94
|
+
let parsed: unknown;
|
|
95
|
+
try {
|
|
96
|
+
parsed = JSON.parse(line);
|
|
97
|
+
} catch {
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
const title = titleOfRow(parsed);
|
|
101
|
+
if (title !== null) found = title;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return found;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** 读单个日志文件的标题(带 mtime+size 缓存)。 */
|
|
108
|
+
async function readTitleFromLogFile(absPath: string): Promise<string | null> {
|
|
109
|
+
if (!zstdAvailable()) return null;
|
|
110
|
+
const info = await stat(absPath).catch(() => null);
|
|
111
|
+
if (info === null) return null;
|
|
112
|
+
const cached = titleCache.get(absPath);
|
|
113
|
+
if (cached !== undefined && cached.mtimeMs === info.mtimeMs && cached.size === info.size) return cached.title;
|
|
114
|
+
const prefix = await readPrefix(absPath, SESSION_TITLE_SCAN_BYTES);
|
|
115
|
+
const title = prefix === null ? null : titleFromBytes(prefix);
|
|
116
|
+
titleCache.set(absPath, { mtimeMs: info.mtimeMs, size: info.size, title });
|
|
117
|
+
return title;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** `unitId`(`<projectKey>/<sessionId>`)→ 目录段;越界 / 形状不符一律 null(绝不拼可疑路径)。 */
|
|
121
|
+
function unitSegments(unitId: string): string[] | null {
|
|
122
|
+
const parts = unitId.split('/').filter((part) => part !== '');
|
|
123
|
+
if (parts.length < 2) return null;
|
|
124
|
+
for (const part of parts) {
|
|
125
|
+
if (part === '.' || part === '..' || part.includes('\\') || part.includes(':')) return null;
|
|
126
|
+
}
|
|
127
|
+
return parts;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* 从会话日志里读这条会话自己的名字(`session/title` 事件)。
|
|
132
|
+
*
|
|
133
|
+
* @param sessionsDir 会话根目录(`<homeDir>/sessions`)。
|
|
134
|
+
* @param unitId 台账里的会话单元 id(`<projectKey>/<sessionId>`;缺了就返回 null)。
|
|
135
|
+
* @returns 标题;读不到 / 没有标题事件 / 形状不符 → null(界面回落显示会话 id)。
|
|
136
|
+
*/
|
|
137
|
+
export async function readSessionTitleFromLog(sessionsDir: string, unitId: string): Promise<string | null> {
|
|
138
|
+
const parts = unitSegments(unitId);
|
|
139
|
+
if (parts === null) return null;
|
|
140
|
+
const dir = join(sessionsDir, ...parts);
|
|
141
|
+
const names = await readdir(dir).catch(() => [] as string[]);
|
|
142
|
+
const logs = names.filter((name) => isSessionLogName(name)).sort();
|
|
143
|
+
if (logs.length === 0) return null;
|
|
144
|
+
// 与体检同口径:同一会话有多份 generation 时以**最新的一份**(字典序最后)为准
|
|
145
|
+
const picked = logs[logs.length - 1];
|
|
146
|
+
if (picked === undefined) return null;
|
|
147
|
+
return readTitleFromLogFile(join(dir, picked));
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* 给一批台账行补标题:**已有标题的不动**,缺标题的按 `unitId` 去重后走日志兜底。
|
|
152
|
+
*
|
|
153
|
+
* 只读、有界(见 {@link SESSION_TITLE_MAX_READS})、失败一律原样返回(界面回落显示会话 id)。
|
|
154
|
+
*/
|
|
155
|
+
export async function applySessionLogTitles<
|
|
156
|
+
T extends { sessionId?: string | null; unitId?: string | null; title?: string },
|
|
157
|
+
>(
|
|
158
|
+
rows: readonly T[],
|
|
159
|
+
sessionsDir: string,
|
|
160
|
+
options: { maxReads?: number } = {},
|
|
161
|
+
): Promise<(T & { title?: string })[]> {
|
|
162
|
+
const maxReads = Math.max(0, options.maxReads ?? SESSION_TITLE_MAX_READS);
|
|
163
|
+
const resolved = new Map<string, string | null>();
|
|
164
|
+
const out: (T & { title?: string })[] = [];
|
|
165
|
+
let reads = 0;
|
|
166
|
+
for (const row of rows) {
|
|
167
|
+
if (typeof row.title === 'string' && row.title.trim() !== '') {
|
|
168
|
+
out.push(row);
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
const unitId = typeof row.unitId === 'string' && row.unitId !== '' ? row.unitId : null;
|
|
172
|
+
if (unitId === null) {
|
|
173
|
+
out.push(row);
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
let title = resolved.get(unitId);
|
|
177
|
+
if (title === undefined) {
|
|
178
|
+
if (reads >= maxReads) {
|
|
179
|
+
out.push(row);
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
reads += 1;
|
|
183
|
+
title = await readSessionTitleFromLog(sessionsDir, unitId);
|
|
184
|
+
resolved.set(unitId, title);
|
|
185
|
+
}
|
|
186
|
+
out.push(title === null || title === '' ? row : { ...row, title });
|
|
187
|
+
}
|
|
188
|
+
return out;
|
|
189
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* issue #87:启动分类 → SAFE MODE 处置的单一判据(纯函数矩阵)。
|
|
3
|
+
*
|
|
4
|
+
* 背景:`probeSafeModeSync()` 读的是上一轮留下的 durable 标记,分类结论才是本轮权威。旧实现只做
|
|
5
|
+
* `safeModeActive = safeModeActive || kind ∈ {RECOVERY_REQUIRED, NEEDS_ATTENTION, UNKNOWN_STATE}` ——
|
|
6
|
+
* 只会把标志**置真**,于是「崩溃前什么都没写」的残留会让本会话的写操作一直被 423 挡着(界面却说
|
|
7
|
+
* 「无待处理事项」),且下次启动又从头再来一遍。
|
|
8
|
+
*/
|
|
9
|
+
import test from 'node:test';
|
|
10
|
+
import assert from 'node:assert/strict';
|
|
11
|
+
import { resolveStartupSafeMode, classifyStartup, FAIL_CLOSED_STARTUP } from './startup-barrier.ts';
|
|
12
|
+
|
|
13
|
+
test('issue #87:NORMAL + 无活锁者 + 陈旧 blocked 标记 → 解除(内存标志与 durable 标记一起复位)', () => {
|
|
14
|
+
assert.deepEqual(
|
|
15
|
+
resolveStartupSafeMode({ kind: 'NORMAL', lockState: 'FREE', currentlyBlocked: true }),
|
|
16
|
+
{ blocked: false, clearStaleMarker: true },
|
|
17
|
+
);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test('issue #87:NORMAL 但没有陈旧标记 → 不动标记(不做无意义写入)', () => {
|
|
21
|
+
assert.deepEqual(
|
|
22
|
+
resolveStartupSafeMode({ kind: 'NORMAL', lockState: 'FREE', currentlyBlocked: false }),
|
|
23
|
+
{ blocked: false, clearStaleMarker: false },
|
|
24
|
+
);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test('issue #87:NORMAL 但锁被别人持有(LOCKED)→ 绝不清标记、保持阻断(不替活锁者解除保护)', () => {
|
|
28
|
+
assert.deepEqual(
|
|
29
|
+
resolveStartupSafeMode({ kind: 'NORMAL', lockState: 'LOCKED', currentlyBlocked: true }),
|
|
30
|
+
{ blocked: true, clearStaleMarker: false },
|
|
31
|
+
);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test('issue #87:残留锁(STALE_LOCK_DETECTED)未显式回收 → 不清标记(仍需用户回收残留锁)', () => {
|
|
35
|
+
assert.deepEqual(
|
|
36
|
+
resolveStartupSafeMode({ kind: 'NORMAL', lockState: 'STALE_LOCK_DETECTED', currentlyBlocked: true }),
|
|
37
|
+
{ blocked: true, clearStaleMarker: false },
|
|
38
|
+
);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test('issue #87:RECOVERY_REQUIRED / NEEDS_ATTENTION / UNKNOWN_STATE → 一律阻断且不清标记', () => {
|
|
42
|
+
for (const kind of ['RECOVERY_REQUIRED', 'NEEDS_ATTENTION', 'UNKNOWN_STATE'] as const) {
|
|
43
|
+
assert.deepEqual(
|
|
44
|
+
resolveStartupSafeMode({ kind, lockState: 'FREE', currentlyBlocked: false }),
|
|
45
|
+
{ blocked: true, clearStaleMarker: false },
|
|
46
|
+
kind + ' 必须阻断',
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test('issue #87:fail-closed 结论(FAIL_CLOSED_STARTUP)经同一判据仍然是阻断', () => {
|
|
52
|
+
const verdict = resolveStartupSafeMode({
|
|
53
|
+
kind: FAIL_CLOSED_STARTUP.state.kind,
|
|
54
|
+
lockState: 'FREE',
|
|
55
|
+
currentlyBlocked: false,
|
|
56
|
+
});
|
|
57
|
+
assert.equal(verdict.blocked, true, 'inspect 抛错时绝不因 NORMAL 之外的分支被解除');
|
|
58
|
+
assert.equal(verdict.clearStaleMarker, false);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test('issue #87:classifyStartup 仍是分类唯一来源(形状自检,防止第二份分类漂移)', () => {
|
|
62
|
+
const classifier = classifyStartup({
|
|
63
|
+
store: {} as never,
|
|
64
|
+
hooks: {} as never,
|
|
65
|
+
env: { environmentFingerprint: 'fp', isLiveOwner: async () => false },
|
|
66
|
+
lockState: 'FREE',
|
|
67
|
+
});
|
|
68
|
+
assert.equal(typeof classifier.classify, 'function');
|
|
69
|
+
});
|
|
@@ -58,6 +58,33 @@ export function classifyStartup(opts: {
|
|
|
58
58
|
};
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
+
/**
|
|
62
|
+
* 启动分类结束后如何处置 SAFE MODE(issue #87,单一判据,便于单测;宿主 apply() 只消费结论)。
|
|
63
|
+
*
|
|
64
|
+
* 为什么需要它:`probeSafeModeSync()` 读的是**上一轮**留下的 durable 标记,而分类结论才是本轮权威。
|
|
65
|
+
* 分类判 `NORMAL` ⇒ 没有任何未决 journal 能解释那个标记 ⇒ 它是陈旧标记,必须**同时**复位内存标志与
|
|
66
|
+
* durable 标记 —— 只复位内存会让本会话的写操作继续被 423 挡着(界面却说「无待处理事项」),只复位标记
|
|
67
|
+
* 又会在下一次 probe 时重新置起(标记跨重启生效)。
|
|
68
|
+
*
|
|
69
|
+
* 为什么要求 `lockState === 'FREE'`:`LOCKED` 且无未决 journal 时分类同样返回 NORMAL,但那只说明
|
|
70
|
+
* **另一个存活实例**正在持有环境锁 —— 此刻清标记等于替别人解除保护。**只有确认没有活锁者才清。**
|
|
71
|
+
*
|
|
72
|
+
* 为什么要求 `currentlyBlocked`(即 probe 读到 durable 标记确实是 'blocked'):标记为 `'unknown'`
|
|
73
|
+
* (存在但读不出/布局不可信)时不在这里清 —— 那属于无法判定,既不假装已解除,也不假装没看见;
|
|
74
|
+
* 它本会话不阻断的语义由 `probeSafeModeSync` 的既有约定承担。
|
|
75
|
+
*/
|
|
76
|
+
export function resolveStartupSafeMode(input: {
|
|
77
|
+
kind: StartupRecoveryState['kind']
|
|
78
|
+
lockState: 'LOCKED' | 'STALE_LOCK_DETECTED' | 'UNKNOWN_STATE' | 'FREE'
|
|
79
|
+
/** 本轮 probe 之前的内存标志(probeSafeModeSync 的结论) */
|
|
80
|
+
currentlyBlocked: boolean
|
|
81
|
+
}): { blocked: boolean; clearStaleMarker: boolean } {
|
|
82
|
+
const staleMarker = input.kind === 'NORMAL' && input.lockState === 'FREE' && input.currentlyBlocked
|
|
83
|
+
if (staleMarker) return { blocked: false, clearStaleMarker: true }
|
|
84
|
+
const forceBlocked = input.kind === 'RECOVERY_REQUIRED' || input.kind === 'NEEDS_ATTENTION' || input.kind === 'UNKNOWN_STATE'
|
|
85
|
+
return { blocked: input.currentlyBlocked || forceBlocked, clearStaleMarker: false }
|
|
86
|
+
}
|
|
87
|
+
|
|
61
88
|
export interface StartupSchedulers {
|
|
62
89
|
start(): void;
|
|
63
90
|
}
|
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
isTerminalState, generateOperationId, redactJournalText, type OperationJournal,
|
|
20
20
|
} from './journal.ts';
|
|
21
21
|
import type { MutationLockContext, MutationLockPort } from '../utils/env-lock.ts';
|
|
22
|
+
import { engineMsg } from './messages-core.ts';
|
|
22
23
|
|
|
23
24
|
// ---------- 类型 ----------
|
|
24
25
|
|
|
@@ -109,9 +110,9 @@ export class MutationTransactionCoordinator {
|
|
|
109
110
|
: 'needs-attention';
|
|
110
111
|
if (recovered === 'recovered' || recovered === 'rolled-back') {
|
|
111
112
|
// 残留已收敛 → 可继续;但本函数语义:先返回,调用方重试 run
|
|
112
|
-
return { outcome: 'RECOVERY_REQUIRED', operationId: activeCheck.residue, reason:
|
|
113
|
+
return { outcome: 'RECOVERY_REQUIRED', operationId: activeCheck.residue, reason: engineMsg('core.txn.residuePending', { residue: activeCheck.residue }) };
|
|
113
114
|
}
|
|
114
|
-
return { outcome: 'RECOVERY_REQUIRED', operationId: activeCheck.residue, reason:
|
|
115
|
+
return { outcome: 'RECOVERY_REQUIRED', operationId: activeCheck.residue, reason: engineMsg('core.txn.unresolvedActive', { residue: activeCheck.residue }) };
|
|
115
116
|
}
|
|
116
117
|
|
|
117
118
|
// 3. 创建 journal
|
|
@@ -227,7 +228,7 @@ export class MutationTransactionCoordinator {
|
|
|
227
228
|
if (current !== null && isTerminalState(current.state)) {
|
|
228
229
|
// 已 terminal(如 COMMITTED 后 move 抛错 → 保持 terminal,不回滚)
|
|
229
230
|
const opId = operationId;
|
|
230
|
-
return { outcome: 'NEEDS_ATTENTION', operationId: opId, reason:
|
|
231
|
+
return { outcome: 'NEEDS_ATTENTION', operationId: opId, reason: engineMsg('core.txn.tailCommitFailed', { message: msg }), snapshotId };
|
|
231
232
|
}
|
|
232
233
|
return await this.finalizeAttention(operationId, msg, snapshotId, context);
|
|
233
234
|
}
|
package/src/core/types.ts
CHANGED
|
@@ -140,6 +140,13 @@ export interface ExportUnit {
|
|
|
140
140
|
* 包里 41 条)。非 sessions 分区 / 非子代理会话一律不设该字段(不猜)。
|
|
141
141
|
*/
|
|
142
142
|
parentSessionId?: string;
|
|
143
|
+
/**
|
|
144
|
+
* 空白会话(**从未提问过**;只有 sessions 分区的单元可能有值)。
|
|
145
|
+
*
|
|
146
|
+
* 用途:DSH 自己给这类会话的标题就是本地化的「新会话」(`session.new`),没有标题可显示。
|
|
147
|
+
* 只下发**事实**,不下发文案 —— 界面按当前语言显示(与 DSH 同口径),绝不用 uuid 冒充标题。
|
|
148
|
+
*/
|
|
149
|
+
blank?: boolean;
|
|
143
150
|
}
|
|
144
151
|
|
|
145
152
|
export interface ValidationResult {
|
|
@@ -660,6 +667,8 @@ export interface PlanItem {
|
|
|
660
667
|
label?: string;
|
|
661
668
|
/** 单元的**二级分组名**(可选,同 `/export-preview` 的 `ExportUnit.group` 语义;纯展示)。 */
|
|
662
669
|
group?: string;
|
|
670
|
+
/** 空白会话(从未提问;同 `ExportUnit.blank`,只下发事实、文案由界面本地化)。 */
|
|
671
|
+
blank?: boolean;
|
|
663
672
|
/** 本项对应的**工作区记录拥有的会话 id**(工作区计划项专用;issue #45 勾选联动)。 */
|
|
664
673
|
sessionIds?: string[];
|
|
665
674
|
/** 本工作区计划项 **path 的 cwd 目录键**(`projectKeyOf(path)`;issue #45 勾选联动第二判据)。 */
|
|
@@ -941,11 +950,19 @@ export interface SnapshotManifest {
|
|
|
941
950
|
|
|
942
951
|
/** 宿主整文件备份登记(M1):导入前对 $DSH_HOME 关键文件的整文件快照。
|
|
943
952
|
* relPath 相对 $DSH_HOME(如 settings.yaml / cordis.patch.yml / profiles/<p>/cordis.patch.yml),
|
|
944
|
-
* blobPath 为快照内 blob 路径(existed=false
|
|
953
|
+
* blobPath 为快照内 blob 路径(existed=false 时为空串,表示该文件当时确实不存在)。 */
|
|
945
954
|
export interface HostFileBackup {
|
|
946
955
|
relPath: string;
|
|
947
956
|
blobPath: string;
|
|
948
957
|
existed: boolean;
|
|
958
|
+
/**
|
|
959
|
+
* 探测到**存在**、但这一次读不出来(EACCES / EBUSY / IO 错):existed 仍为 true、blobPath 为空串。
|
|
960
|
+
*
|
|
961
|
+
* 为什么必须与 existed:false 分开:恢复侧「快照里没有、现在有 → 删掉」这条动作是按 existed 判定的,
|
|
962
|
+
* 把「没读到」写成「本来不存在」会让恢复**删掉用户真实的文件**(Windows 上 settings.yaml 被占用即触发)。
|
|
963
|
+
* 存在但读不到的条目一律只跳过并如实告警,绝不参与删除判定(与「读不到 ≠ 没有」同一条口径)。
|
|
964
|
+
*/
|
|
965
|
+
unreadable?: boolean;
|
|
949
966
|
}
|
|
950
967
|
|
|
951
968
|
export interface Snapshot {
|
package/src/core/validator.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import { validateSectionData } from '../schema/config.ts';
|
|
5
5
|
import { canImport, describeVersion } from '../schema/versions.ts';
|
|
6
6
|
import type { SectionId } from '../schema/types.ts';
|
|
7
|
+
import { engineMsg } from './messages-core.ts';
|
|
7
8
|
import type { CompatibilityInput, CompatibilityReason, CompatibilityScore, ValidationResult } from './types.ts';
|
|
8
9
|
|
|
9
10
|
export { validateManifest } from '../schema/manifest.ts';
|
|
@@ -102,10 +103,10 @@ export function computeCompatibility(input: CompatibilityInput): CompatibilitySc
|
|
|
102
103
|
/** 兼容性得分的可读描述(UI 报告用) */
|
|
103
104
|
export function describeCompatibility(score: CompatibilityScore): string {
|
|
104
105
|
switch (score) {
|
|
105
|
-
case 'excellent': return '
|
|
106
|
-
case 'good': return '
|
|
107
|
-
case 'partial': return '
|
|
108
|
-
case 'unsupported': return '
|
|
106
|
+
case 'excellent': return engineMsg('core.compat.excellent');
|
|
107
|
+
case 'good': return engineMsg('core.compat.good');
|
|
108
|
+
case 'partial': return engineMsg('core.compat.partial');
|
|
109
|
+
case 'unsupported': return engineMsg('core.compat.unsupported');
|
|
109
110
|
}
|
|
110
111
|
}
|
|
111
112
|
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
*
|
|
25
25
|
* 本模块为纯函数(fs 经 node:fs + HostContext 门面注入),可独立测试。
|
|
26
26
|
*/
|
|
27
|
+
import { engineMsg } from './messages-core.ts';
|
|
27
28
|
import fs from 'node:fs/promises';
|
|
28
29
|
import path from 'node:path';
|
|
29
30
|
import { isDeepStrictEqual } from 'node:util';
|
|
@@ -58,7 +59,7 @@ function toRelPath(homeDir: string, abs: string): string {
|
|
|
58
59
|
/** 快照目录内 blob 绝对路径(防 snapshot.json 里伪造 ../ 越界读) */
|
|
59
60
|
function blobAbs(snapshotDir: string, blobPath: string): string {
|
|
60
61
|
const abs = path.resolve(snapshotDir, blobPath);
|
|
61
|
-
if (!isWithinHome(snapshotDir, abs)) throw new Error(
|
|
62
|
+
if (!isWithinHome(snapshotDir, abs)) throw new Error(engineMsg('core.verifyRecovery.blobPathEscaped', { path: blobPath }));
|
|
62
63
|
return abs;
|
|
63
64
|
}
|
|
64
65
|
|
|
@@ -99,12 +100,12 @@ export async function verifyRecovery(
|
|
|
99
100
|
if (val.verdict !== 'TRUSTED_OPERATION_SNAPSHOT') {
|
|
100
101
|
return {
|
|
101
102
|
verdict: 'VERIFICATION_ERROR',
|
|
102
|
-
details: [
|
|
103
|
+
details: [engineMsg('core.verifyRecovery.revalidateFailed', { verdict: val.verdict }) + (val.reason ? ` — ${val.reason}` : '')],
|
|
103
104
|
manualHints: [],
|
|
104
105
|
at,
|
|
105
106
|
};
|
|
106
107
|
}
|
|
107
|
-
details.push('
|
|
108
|
+
details.push(engineMsg('core.verifyRecovery.revalidatePassed'));
|
|
108
109
|
|
|
109
110
|
// 2) 从磁盘重读 snapshot.json 作为权威(不信任传入的 snapshot 对象,防 execute→verify 间替换)。
|
|
110
111
|
let diskSnapshot: Snapshot;
|
|
@@ -112,17 +113,17 @@ export async function verifyRecovery(
|
|
|
112
113
|
const raw = await fs.readFile(path.join(snapshotDir, 'snapshot.json'), 'utf8');
|
|
113
114
|
diskSnapshot = JSON.parse(raw) as Snapshot;
|
|
114
115
|
} catch (err) {
|
|
115
|
-
return { verdict: 'VERIFICATION_ERROR', details: [...details,
|
|
116
|
+
return { verdict: 'VERIFICATION_ERROR', details: [...details, engineMsg('core.verifyRecovery.snapshotJsonRereadFailed', { reason: err instanceof Error ? err.message : String(err) })], manualHints: [], at };
|
|
116
117
|
}
|
|
117
118
|
|
|
118
119
|
// 3) environment binding(validateSnapshotForRestore 已查,此处双保险)
|
|
119
120
|
if (diskSnapshot.environmentFingerprint && diskSnapshot.environmentFingerprint !== opts.environmentFingerprint) {
|
|
120
|
-
return { verdict: 'VERIFICATION_ERROR', details: [...details, '
|
|
121
|
+
return { verdict: 'VERIFICATION_ERROR', details: [...details, engineMsg('core.verifyRecovery.envMismatch')], manualHints: [], at };
|
|
121
122
|
}
|
|
122
123
|
|
|
123
124
|
// 3b) operation binding(journal.operationId ↔ snapshot.operationId 双向一致;WRONG_OPERATION 拒绝)
|
|
124
125
|
if (opts.expectedOperationId !== undefined && diskSnapshot.operationId !== opts.expectedOperationId) {
|
|
125
|
-
return { verdict: 'VERIFICATION_ERROR', details: [...details,
|
|
126
|
+
return { verdict: 'VERIFICATION_ERROR', details: [...details, engineMsg('core.verifyRecovery.operationMismatch', { snapshot: diskSnapshot.operationId ?? engineMsg('core.verifyRecovery.none'), expected: opts.expectedOperationId ?? '' })], manualHints: [], at };
|
|
126
127
|
}
|
|
127
128
|
|
|
128
129
|
let mismatch = false;
|
|
@@ -135,12 +136,12 @@ export async function verifyRecovery(
|
|
|
135
136
|
if (backup.existed) {
|
|
136
137
|
if (backup.blobPath === '') {
|
|
137
138
|
unverifiable = true;
|
|
138
|
-
details.push(
|
|
139
|
+
details.push(engineMsg('core.verifyRecovery.hostFileNoBlob', { rel }));
|
|
139
140
|
continue;
|
|
140
141
|
}
|
|
141
142
|
if (!(await ctx.fs.exists(rel))) {
|
|
142
143
|
mismatch = true;
|
|
143
|
-
details.push(
|
|
144
|
+
details.push(engineMsg('core.verifyRecovery.hostFileMissing', { rel }));
|
|
144
145
|
continue;
|
|
145
146
|
}
|
|
146
147
|
let blob: Uint8Array;
|
|
@@ -148,23 +149,23 @@ export async function verifyRecovery(
|
|
|
148
149
|
blob = await fs.readFile(blobAbs(snapshotDir, backup.blobPath));
|
|
149
150
|
} catch {
|
|
150
151
|
unverifiable = true;
|
|
151
|
-
details.push(
|
|
152
|
+
details.push(engineMsg('core.verifyRecovery.hostFileBlobReadFailed', { rel }));
|
|
152
153
|
continue;
|
|
153
154
|
}
|
|
154
155
|
const current = await ctx.fs.readFile(rel);
|
|
155
156
|
if (!bufEq(current, blob)) {
|
|
156
157
|
mismatch = true;
|
|
157
|
-
details.push(
|
|
158
|
+
details.push(engineMsg('core.verifyRecovery.hostFileContentMismatch', { rel }));
|
|
158
159
|
} else {
|
|
159
|
-
details.push(
|
|
160
|
+
details.push(engineMsg('core.verifyRecovery.hostFileMatch', { rel }));
|
|
160
161
|
}
|
|
161
162
|
} else {
|
|
162
163
|
// snapshot expects absent → target 必须不存在(删除动作失败 → 残留被检测)
|
|
163
164
|
if (await ctx.fs.exists(rel)) {
|
|
164
165
|
mismatch = true;
|
|
165
|
-
details.push(
|
|
166
|
+
details.push(engineMsg('core.verifyRecovery.hostFileStillPresent', { rel }));
|
|
166
167
|
} else {
|
|
167
|
-
details.push(
|
|
168
|
+
details.push(engineMsg('core.verifyRecovery.hostFileAlreadyGone', { rel }));
|
|
168
169
|
}
|
|
169
170
|
}
|
|
170
171
|
}
|
|
@@ -175,13 +176,13 @@ export async function verifyRecovery(
|
|
|
175
176
|
const rel = toRelPath(ctx.homeDir, resolveFileAbs(ctx.homeDir, entry.adapter, entry.ref));
|
|
176
177
|
if (rel === '') {
|
|
177
178
|
unverifiable = true;
|
|
178
|
-
details.push(
|
|
179
|
+
details.push(engineMsg('core.verifyRecovery.filePathEscaped', { ref: `${entry.adapter}:${entry.ref}` }));
|
|
179
180
|
continue;
|
|
180
181
|
}
|
|
181
182
|
if (entry.existed && entry.copiedTo) {
|
|
182
183
|
if (!(await ctx.fs.exists(rel))) {
|
|
183
184
|
mismatch = true;
|
|
184
|
-
details.push(
|
|
185
|
+
details.push(engineMsg('core.verifyRecovery.fileMissing', { ref: `${entry.adapter}:${entry.ref}` }));
|
|
185
186
|
continue;
|
|
186
187
|
}
|
|
187
188
|
let blob: Uint8Array;
|
|
@@ -189,22 +190,22 @@ export async function verifyRecovery(
|
|
|
189
190
|
blob = await fs.readFile(blobAbs(snapshotDir, entry.copiedTo));
|
|
190
191
|
} catch {
|
|
191
192
|
unverifiable = true;
|
|
192
|
-
details.push(
|
|
193
|
+
details.push(engineMsg('core.verifyRecovery.fileBlobReadFailed', { ref: `${entry.adapter}:${entry.ref}` }));
|
|
193
194
|
continue;
|
|
194
195
|
}
|
|
195
196
|
const current = await ctx.fs.readFile(rel);
|
|
196
197
|
if (!bufEq(current, blob)) {
|
|
197
198
|
mismatch = true;
|
|
198
|
-
details.push(
|
|
199
|
+
details.push(engineMsg('core.verifyRecovery.fileContentMismatch', { ref: `${entry.adapter}:${entry.ref}` }));
|
|
199
200
|
} else {
|
|
200
|
-
details.push(
|
|
201
|
+
details.push(engineMsg('core.verifyRecovery.fileMatch', { ref: `${entry.adapter}:${entry.ref}` }));
|
|
201
202
|
}
|
|
202
203
|
} else if (!entry.existed) {
|
|
203
204
|
if (await ctx.fs.exists(rel)) {
|
|
204
205
|
mismatch = true;
|
|
205
|
-
details.push(
|
|
206
|
+
details.push(engineMsg('core.verifyRecovery.fileStillPresent', { ref: `${entry.adapter}:${entry.ref}` }));
|
|
206
207
|
} else {
|
|
207
|
-
details.push(
|
|
208
|
+
details.push(engineMsg('core.verifyRecovery.fileAlreadyGone', { ref: `${entry.adapter}:${entry.ref}` }));
|
|
208
209
|
}
|
|
209
210
|
}
|
|
210
211
|
}
|
|
@@ -214,8 +215,8 @@ export async function verifyRecovery(
|
|
|
214
215
|
const beforePlugins = diskSnapshot.beforePlugins;
|
|
215
216
|
if (beforePlugins === undefined) {
|
|
216
217
|
unverifiable = true;
|
|
217
|
-
details.push('
|
|
218
|
-
manualHints.push('
|
|
218
|
+
details.push(engineMsg('core.verifyRecovery.pluginsNoBaseline'));
|
|
219
|
+
manualHints.push(engineMsg('core.verifyRecovery.pluginsNoBaselineHint'));
|
|
219
220
|
} else {
|
|
220
221
|
try {
|
|
221
222
|
const current = await ctx.plugins.listInstalled();
|
|
@@ -223,14 +224,14 @@ export async function verifyRecovery(
|
|
|
223
224
|
const added = current.filter((p) => !beforeNames.has(p.name));
|
|
224
225
|
if (added.length > 0) {
|
|
225
226
|
mismatch = true;
|
|
226
|
-
details.push(
|
|
227
|
+
details.push(engineMsg('core.verifyRecovery.pluginsAdded', { names: added.map((p) => p.name).join(', ') }));
|
|
227
228
|
} else {
|
|
228
|
-
details.push('
|
|
229
|
+
details.push(engineMsg('core.verifyRecovery.pluginsMatch'));
|
|
229
230
|
}
|
|
230
231
|
} catch {
|
|
231
232
|
unverifiable = true;
|
|
232
|
-
details.push('
|
|
233
|
-
manualHints.push('
|
|
233
|
+
details.push(engineMsg('core.verifyRecovery.pluginsVerifyFailed'));
|
|
234
|
+
manualHints.push(engineMsg('core.verifyRecovery.pluginsVerifyFailedHint'));
|
|
234
235
|
}
|
|
235
236
|
}
|
|
236
237
|
|
|
@@ -241,14 +242,14 @@ export async function verifyRecovery(
|
|
|
241
242
|
const current = await ctx.settings.describe(entry.ref);
|
|
242
243
|
if (!isDeepStrictEqual(current.value, entry.before)) {
|
|
243
244
|
mismatch = true;
|
|
244
|
-
details.push(
|
|
245
|
+
details.push(engineMsg('core.verifyRecovery.settingsMismatch', { ref: entry.ref }));
|
|
245
246
|
} else {
|
|
246
|
-
details.push(
|
|
247
|
+
details.push(engineMsg('core.verifyRecovery.settingsMatch', { ref: entry.ref }));
|
|
247
248
|
}
|
|
248
249
|
} catch {
|
|
249
250
|
unverifiable = true;
|
|
250
|
-
details.push(
|
|
251
|
-
manualHints.push(
|
|
251
|
+
details.push(engineMsg('core.verifyRecovery.settingsVerifyFailed', { ref: entry.ref }));
|
|
252
|
+
manualHints.push(engineMsg('core.verifyRecovery.settingsVerifyFailedHint', { ref: entry.ref }));
|
|
252
253
|
}
|
|
253
254
|
}
|
|
254
255
|
|
package/src/foreign/bundle.ts
CHANGED
|
@@ -19,6 +19,7 @@ import { writeZip } from '../utils/zip.ts';
|
|
|
19
19
|
import type { ZipWriteEntry } from '../utils/zip.ts';
|
|
20
20
|
import type { Platform, SectionId } from '../schema/types.ts';
|
|
21
21
|
import type { ForeignImportResult, ForeignSkip } from './types.ts';
|
|
22
|
+
import { engineMsg } from './messages.ts';
|
|
22
23
|
|
|
23
24
|
export interface ForeignBundleOptions {
|
|
24
25
|
result: ForeignImportResult;
|
|
@@ -57,7 +58,7 @@ export async function writeForeignBundle(opts: ForeignBundleOptions): Promise<Fo
|
|
|
57
58
|
if (isFileSection(s.sectionId)) {
|
|
58
59
|
const prefix = SECTION_FILE_PREFIXES[s.sectionId];
|
|
59
60
|
if (prefix === undefined) {
|
|
60
|
-
throw new Error('
|
|
61
|
+
throw new Error(engineMsg('foreign.bundleFileSectionPrefixMissing', { section: s.sectionId }));
|
|
61
62
|
}
|
|
62
63
|
const files = s.files ?? [];
|
|
63
64
|
if (files.length === 0) continue;
|
|
@@ -67,7 +68,7 @@ export async function writeForeignBundle(opts: ForeignBundleOptions): Promise<Fo
|
|
|
67
68
|
} else {
|
|
68
69
|
const jsonPath = SECTION_JSON_PATHS[s.sectionId];
|
|
69
70
|
if (jsonPath === undefined) {
|
|
70
|
-
throw new Error('
|
|
71
|
+
throw new Error(engineMsg('foreign.bundleJsonSectionPathMissing', { section: s.sectionId }));
|
|
71
72
|
}
|
|
72
73
|
if (s.data === undefined) continue;
|
|
73
74
|
flags[s.sectionId] = true;
|
package/src/foreign/continue.ts
CHANGED
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
import { envValue, normalizePlatform } from './platform-paths.ts';
|
|
20
20
|
import { draftFromTranscript, sessionSourceOf } from './session-source.ts';
|
|
21
21
|
import type { RootProbeOptions } from './session-source.ts';
|
|
22
|
+
import { engineMsg } from './messages.ts';
|
|
22
23
|
import { FOREIGN_TRUTH_TABLES } from './truth-table.ts';
|
|
23
24
|
import type { ForeignTruthTableEntry } from './truth-table.ts';
|
|
24
25
|
import type { ForeignSource } from './registry.ts';
|
|
@@ -31,7 +32,7 @@ export const CONTINUE_PROVIDER = 'continue';
|
|
|
31
32
|
|
|
32
33
|
function truthEntryOf(id: ForeignSourceId): ForeignTruthTableEntry {
|
|
33
34
|
const found = FOREIGN_TRUTH_TABLES.find((entry) => entry.id === id);
|
|
34
|
-
if (found === undefined) throw new Error('
|
|
35
|
+
if (found === undefined) throw new Error(engineMsg('foreign.truthTableMissingSource', { id }));
|
|
35
36
|
return found;
|
|
36
37
|
}
|
|
37
38
|
|
package/src/foreign/crush.ts
CHANGED
|
@@ -18,6 +18,7 @@ import { draftFromTranscript } from './session-source.ts';
|
|
|
18
18
|
import type { RootProbeOptions, SessionDraft } from './session-source.ts';
|
|
19
19
|
import { sessionSourceOf } from './session-source.ts';
|
|
20
20
|
import type { ForeignSource } from './registry.ts';
|
|
21
|
+
import { engineMsg } from './messages.ts';
|
|
21
22
|
import { FOREIGN_TRUTH_TABLES } from './truth-table.ts';
|
|
22
23
|
import type { ForeignTruthTableEntry } from './truth-table.ts';
|
|
23
24
|
import type { ForeignEvidenceKind, ForeignSourceId } from './types.ts';
|
|
@@ -27,7 +28,7 @@ import { crushProjectDbPath, crushRegistryPath, readCrush } from './read-crush.t
|
|
|
27
28
|
/** 真值表条目(找不到即装配期炸:宁可炸,也不让来源悄悄少一份取证声明 —— 与 registry.ts 同口径) */
|
|
28
29
|
function truthEntryOf(id: ForeignSourceId): ForeignTruthTableEntry {
|
|
29
30
|
const found = FOREIGN_TRUTH_TABLES.find((entry) => entry.id === id);
|
|
30
|
-
if (found === undefined) throw new Error('
|
|
31
|
+
if (found === undefined) throw new Error(engineMsg('foreign.truthTableMissingSource', { id }));
|
|
31
32
|
return found;
|
|
32
33
|
}
|
|
33
34
|
|
|
@@ -244,6 +244,8 @@ test('C 来源形状:共享模块清单是封闭集合(新模块必须显式
|
|
|
244
244
|
const shared = modules.filter((m) => !isSourceShaped(m));
|
|
245
245
|
const expected = [
|
|
246
246
|
'bundle.ts', 'claude-sessions.ts', 'kernel.ts',
|
|
247
|
+
/* t6 新增:本域追加消息目录(零 source 形状;中文只在这里,装配期抛错的文案经 engineMsg 取)。 */
|
|
248
|
+
'messages.ts',
|
|
247
249
|
/* 档 B 新增的 5 个共享地基(零 source 形状,但也不是来源模块): */
|
|
248
250
|
'platform-paths.ts', 'registry.ts', 'session-ir.ts', 'session-read.ts', 'session-source.ts',
|
|
249
251
|
'source-modules.ts', 'sqlite.ts', 'truth-table.ts', 'types.ts',
|