dsh-config-manager 0.1.71 → 0.1.73
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +149 -98
- package/README.zh-CN.md +245 -88
- package/lib/adapters/agent-instructions.js +1 -1
- package/lib/adapters/file-collection.js +1 -1
- package/lib/adapters/index.d.ts +2 -1
- package/lib/adapters/index.js +6 -1
- package/lib/adapters/link-report.d.ts +1 -0
- package/lib/adapters/link-report.js +27 -3
- package/lib/adapters/messages.d.ts +21 -0
- package/lib/adapters/messages.js +57 -0
- package/lib/adapters/plugin-data.d.ts +114 -0
- package/lib/adapters/plugin-data.js +309 -0
- package/lib/adapters/plugin-files.js +3 -2
- package/lib/adapters/plugins.js +4 -3
- package/lib/adapters/prompts.d.ts +1 -0
- package/lib/adapters/prompts.js +7 -1
- package/lib/adapters/self.d.ts +8 -2
- package/lib/adapters/self.js +3 -2
- package/lib/adapters/sessions.d.ts +1 -0
- package/lib/adapters/sessions.js +4 -2
- package/lib/adapters/test-helpers.d.ts +9 -0
- package/lib/adapters/test-helpers.js +14 -0
- package/lib/adapters/ui.d.ts +2 -2
- package/lib/adapters/ui.js +25 -25
- package/lib/adapters/workspaces.d.ts +1 -0
- package/lib/adapters/workspaces.js +7 -3
- package/lib/cli/actions.d.ts +9 -1
- package/lib/cli/actions.js +5 -2
- package/lib/cli/help.js +23 -10
- package/lib/cli/index.d.ts +23 -0
- package/lib/cli/index.js +90 -32
- package/lib/cli/sessions-inspect.js +3 -1
- package/lib/cli/sessions-repair.js +25 -6
- package/lib/cli/web/page.d.ts +12 -0
- package/lib/cli/web/page.js +21 -12
- package/lib/cli/web/routes.js +2 -2
- package/lib/cli/web/server.js +4 -4
- package/lib/client.d.ts +1442 -494
- package/lib/client.js +15326 -8520
- package/lib/core/analyzer.d.ts +11 -2
- package/lib/core/analyzer.js +31 -8
- package/lib/core/backup-plan.d.ts +10 -0
- package/lib/core/backup-plan.js +51 -20
- package/lib/core/backup-verify.js +24 -23
- package/lib/core/backup.d.ts +25 -1
- package/lib/core/backup.js +99 -28
- package/lib/core/boot-rescue.js +4 -3
- package/lib/core/boot-safety.js +2 -1
- package/lib/core/command-rollback.d.ts +182 -0
- package/lib/core/command-rollback.js +293 -0
- package/lib/core/consult-source.js +7 -6
- package/lib/core/disk-usage.d.ts +74 -10
- package/lib/core/disk-usage.js +67 -4
- package/lib/core/engine-locale.d.ts +87 -0
- package/lib/core/engine-locale.js +125 -0
- package/lib/core/exporter.js +3 -3
- package/lib/core/fork-reconcile.d.ts +128 -0
- package/lib/core/fork-reconcile.js +150 -0
- package/lib/core/journal.js +6 -5
- package/lib/core/local-plugin-host.js +3 -2
- package/lib/core/local-plugin-pack.js +11 -6
- package/lib/core/messages-core.d.ts +294 -0
- package/lib/core/messages-core.js +619 -0
- package/lib/core/messages.d.ts +61 -2
- package/lib/core/messages.js +118 -13
- package/lib/core/migration-consult.js +21 -20
- package/lib/core/migration-history.js +19 -5
- package/lib/core/model-tools.d.ts +0 -22
- package/lib/core/model-tools.js +50 -27
- package/lib/core/phase3-host.js +30 -5
- package/lib/core/plugin-cli.js +18 -19
- package/lib/core/plugin-data-registry.d.ts +124 -0
- package/lib/core/plugin-data-registry.js +198 -0
- package/lib/core/reconcile.d.ts +2 -14
- package/lib/core/reconcile.js +87 -17
- package/lib/core/recovery-orchestrator.js +19 -18
- package/lib/core/reinstall.d.ts +4 -0
- package/lib/core/reinstall.js +26 -21
- package/lib/core/restore.js +20 -17
- package/lib/core/rollback-gate.d.ts +33 -0
- package/lib/core/rollback-gate.js +57 -0
- package/lib/core/rollback-ignore-rules.d.ts +31 -0
- package/lib/core/rollback-ignore-rules.js +140 -0
- package/lib/core/rollback-ledger.d.ts +248 -0
- package/lib/core/rollback-ledger.js +1282 -0
- package/lib/core/rollback-point.d.ts +1391 -0
- package/lib/core/rollback-point.js +3503 -0
- package/lib/core/rollback.js +6 -4
- package/lib/core/run-registry.js +6 -5
- package/lib/core/session-health.d.ts +15 -0
- package/lib/core/session-meta.d.ts +42 -2
- package/lib/core/session-meta.js +140 -1
- package/lib/core/session-title.d.ts +28 -0
- package/lib/core/session-title.js +185 -0
- package/lib/core/startup-barrier.d.ts +24 -0
- package/lib/core/startup-barrier.js +22 -0
- package/lib/core/transaction-coordinator.js +4 -3
- package/lib/core/types.d.ts +37 -1
- package/lib/core/validator.js +5 -4
- package/lib/core/verify-recovery.js +31 -30
- package/lib/foreign/bundle.js +3 -2
- package/lib/foreign/continue.js +2 -1
- package/lib/foreign/crush.js +2 -1
- package/lib/foreign/gemini.js +2 -1
- package/lib/foreign/goose.js +2 -1
- package/lib/foreign/kilocode.js +2 -1
- package/lib/foreign/kimi.js +2 -1
- package/lib/foreign/messages.d.ts +15 -0
- package/lib/foreign/messages.js +40 -0
- package/lib/foreign/mimocode.js +2 -1
- package/lib/foreign/openclaw.js +2 -1
- package/lib/foreign/opencode.js +2 -1
- package/lib/foreign/pi.js +2 -1
- package/lib/foreign/qoder.js +2 -1
- package/lib/foreign/qwen.js +2 -1
- package/lib/foreign/registry.d.ts +1 -1
- package/lib/foreign/registry.js +8 -7
- package/lib/foreign/teleagent.js +2 -1
- package/lib/foreign/workbuddy.js +2 -1
- package/lib/foreign/zcode.js +2 -1
- package/lib/foreign/zed.js +2 -1
- package/lib/index.d.ts +227 -7
- package/lib/index.js +799 -54
- package/lib/market/git-file-writer.js +20 -19
- package/lib/market/github-repos.js +35 -19
- package/lib/market/index-parser.js +31 -30
- package/lib/market/messages-market.d.ts +138 -0
- package/lib/market/messages-market.js +305 -0
- package/lib/market/my-repo.d.ts +2 -2
- package/lib/market/my-repo.js +32 -29
- package/lib/market/prepare.js +18 -15
- package/lib/market/security.js +20 -19
- package/lib/market/types.js +2 -1
- package/lib/market/url.js +2 -1
- package/lib/migrations/index.js +6 -5
- package/lib/migrations/messages-migrations.d.ts +32 -0
- package/lib/migrations/messages-migrations.js +58 -0
- package/lib/profiles/dsh-profile-launcher.js +2 -1
- package/lib/profiles/dsh-profile-runtime.js +2 -1
- package/lib/profiles/messages.d.ts +7 -0
- package/lib/profiles/messages.js +27 -0
- package/lib/routes/backup.js +18 -2
- package/lib/routes/import.js +3 -3
- package/lib/routes/index.d.ts +1 -1
- package/lib/routes/kit.d.ts +3 -2
- package/lib/routes/kit.js +6 -1
- package/lib/routes/market.js +1 -1
- package/lib/routes/messages.d.ts +43 -0
- package/lib/routes/messages.js +102 -0
- package/lib/routes/prefs.js +26 -3
- package/lib/routes/profiles.js +12 -6
- package/lib/routes/recovery.d.ts +21 -0
- package/lib/routes/recovery.js +478 -94
- package/lib/routes/snapshots.js +51 -4
- package/lib/routes/sync.js +79 -17
- package/lib/schema/config.d.ts +11 -1
- package/lib/schema/config.js +52 -20
- package/lib/schema/index.d.ts +8 -6
- package/lib/schema/index.js +8 -6
- package/lib/schema/manifest.js +19 -18
- package/lib/schema/messages.d.ts +47 -0
- package/lib/schema/messages.js +110 -0
- package/lib/schema/section-registry.d.ts +42 -15
- package/lib/schema/section-registry.js +67 -32
- package/lib/schema/types.d.ts +1 -1
- package/lib/schema/versions.js +6 -5
- package/lib/security/encryption.js +16 -15
- package/lib/security/integrity.js +8 -7
- package/lib/security/messages.d.ts +30 -0
- package/lib/security/messages.js +77 -0
- package/lib/security/redaction.js +12 -0
- package/lib/security/secret-scanner.js +16 -13
- package/lib/security/vault.js +2 -1
- package/lib/security/zip-security.js +4 -3
- package/lib/sync/ancestor.js +2 -1
- package/lib/sync/autosync-config.d.ts +11 -0
- package/lib/sync/autosync-config.js +12 -0
- package/lib/sync/autosync-scheduler.d.ts +11 -1
- package/lib/sync/autosync-scheduler.js +115 -76
- package/lib/sync/backup-files.js +3 -2
- package/lib/sync/backup-scheduler.js +6 -5
- package/lib/sync/blob-store.js +2 -1
- package/lib/sync/git/git-transport.d.ts +55 -0
- package/lib/sync/git/git-transport.js +152 -2
- package/lib/sync/github-auth.js +10 -9
- package/lib/sync/layout.js +13 -12
- package/lib/sync/messages.d.ts +63 -0
- package/lib/sync/messages.js +149 -0
- package/lib/sync/review-queue.js +6 -5
- package/lib/sync/s3/messages.d.ts +7 -0
- package/lib/sync/s3/messages.js +16 -0
- package/lib/sync/s3/s3-providers.d.ts +4 -1
- package/lib/sync/s3/s3-providers.js +5 -5
- package/lib/sync/s3/s3-transport.js +1 -1
- package/lib/sync/s3/sigv4.js +2 -1
- package/lib/sync/snapshot-crypto.js +6 -5
- package/lib/sync/snapshot-json.js +3 -2
- package/lib/sync/sync-engine.d.ts +58 -22
- package/lib/sync/sync-engine.js +119 -48
- package/lib/sync/sync-history.js +7 -6
- package/lib/sync/sync-selection.d.ts +15 -4
- package/lib/sync/sync-selection.js +17 -5
- package/lib/sync/ui-prefs.d.ts +22 -0
- package/lib/sync/ui-prefs.js +28 -0
- package/lib/sync/webdav/webdav-transport.d.ts +1 -0
- package/lib/sync/webdav/webdav-transport.js +4 -1
- package/lib/ui/artifact-view.js +4 -3
- package/lib/ui/commands.d.ts +1 -1
- package/lib/ui/commands.js +10 -0
- package/lib/ui/diff-view.d.ts +10 -0
- package/lib/ui/diff-view.js +17 -0
- package/lib/ui/disk-usage-view.d.ts +33 -4
- package/lib/ui/disk-usage-view.js +29 -1
- package/lib/ui/errors.d.ts +1 -1
- package/lib/ui/errors.js +4 -3
- package/lib/ui/export-flow.js +26 -20
- package/lib/ui/foreign-view.d.ts +5 -1
- package/lib/ui/foreign-view.js +26 -2
- package/lib/ui/i18n.d.ts +68 -1
- package/lib/ui/i18n.js +120 -2
- package/lib/ui/import-wizard.d.ts +7 -0
- package/lib/ui/import-wizard.js +13 -10
- package/lib/ui/progress.d.ts +2 -2
- package/lib/ui/progress.js +1 -2
- package/lib/ui/report.d.ts +5 -5
- package/lib/ui/report.js +6 -6
- package/lib/ui/rollback-view.d.ts +815 -0
- package/lib/ui/rollback-view.js +738 -0
- package/lib/ui/selection-model.d.ts +41 -0
- package/lib/ui/selection-model.js +30 -0
- package/lib/ui/session-inventory-view.d.ts +97 -3
- package/lib/ui/session-inventory-view.js +76 -2
- package/lib/ui/session-issue-labels.d.ts +43 -0
- package/lib/ui/session-issue-labels.js +48 -0
- package/lib/ui/sync-settings-view.d.ts +33 -0
- package/lib/ui/sync-settings-view.js +40 -0
- package/lib/ui/test-helpers.js +2 -1
- package/lib/ui/types.d.ts +8 -3
- package/lib/ui/types.js +1 -1
- package/lib/utils/atomic-write.js +6 -5
- package/lib/utils/bundle-scan.d.ts +11 -1
- package/lib/utils/bundle-scan.js +12 -1
- package/lib/utils/env-lock.d.ts +10 -0
- package/lib/utils/env-lock.js +73 -46
- package/lib/utils/json.js +6 -5
- package/lib/utils/messages-utils.d.ts +96 -0
- package/lib/utils/messages-utils.js +216 -0
- package/lib/utils/proxy.js +18 -5
- package/lib/utils/recursive-walk.d.ts +22 -0
- package/lib/utils/recursive-walk.js +75 -1
- package/lib/utils/session-log-repair.js +10 -9
- package/lib/utils/zip.js +27 -26
- package/package.json +12 -2
- package/src/adapters/agent-instructions.ts +1 -1
- package/src/adapters/file-collection.test.ts +1 -0
- package/src/adapters/file-collection.ts +1 -1
- package/src/adapters/files.test.ts +27 -0
- package/src/adapters/index.ts +6 -1
- package/src/adapters/link-report.test.ts +22 -2
- package/src/adapters/link-report.ts +29 -4
- package/src/adapters/messages.ts +61 -0
- package/src/adapters/plugin-data.test.ts +225 -0
- package/src/adapters/plugin-data.ts +351 -0
- package/src/adapters/plugin-files.ts +3 -2
- package/src/adapters/plugins.ts +4 -3
- package/src/adapters/prompts.ts +7 -1
- package/src/adapters/self.test.ts +26 -0
- package/src/adapters/self.ts +10 -3
- package/src/adapters/sessions.ts +4 -2
- package/src/adapters/settings.test.ts +38 -1
- package/src/adapters/test-helpers.ts +15 -0
- package/src/adapters/ui.test.ts +2 -2
- package/src/adapters/ui.ts +27 -26
- package/src/adapters/workspaces.ts +7 -3
- package/src/cli/actions.test.ts +28 -0
- package/src/cli/actions.ts +11 -2
- package/src/cli/help.ts +23 -10
- package/src/cli/index-guards.test.ts +193 -0
- package/src/cli/index.ts +106 -31
- package/src/cli/sessions-inspect.test.ts +15 -1
- package/src/cli/sessions-inspect.ts +3 -1
- package/src/cli/sessions-repair.test.ts +84 -0
- package/src/cli/sessions-repair.ts +33 -7
- package/src/cli/web/console-route-parity.test.ts +115 -0
- package/src/cli/web/page.ts +23 -12
- package/src/cli/web/routes.ts +2 -1
- package/src/cli/web/server.ts +4 -4
- package/src/cli/web/web.test.ts +2 -2
- package/src/client/ConfigManagerSection.tsx +30 -2
- package/src/client/about/AboutPanel.tsx +2 -2
- package/src/client/about/PluginUpdateDialog.tsx +1 -1
- package/src/client/about/ReleaseNotesDialog.tsx +2 -2
- package/src/client/about/about-view.test.ts +13 -3
- package/src/client/about/about-view.ts +10 -6
- package/src/client/about/release-notes-view.test.ts +14 -4
- package/src/client/about/release-notes-view.ts +4 -2
- package/src/client/api.ts +18 -6
- package/src/client/chat/ChatEntryView.tsx +411 -0
- package/src/client/chat/RollbackDiffTab.tsx +108 -0
- package/src/client/chat/RollbackFileIcon.tsx +96 -0
- package/src/client/chat/RollbackReviewDialog.tsx +607 -0
- package/src/client/chat/chat-entry-view.test.ts +1174 -0
- package/src/client/chat/chat-entry-view.ts +758 -0
- package/src/client/chat/chat-locales.ts +152 -0
- package/src/client/chat/chat-node-registration.ts +64 -0
- package/src/client/chat/chat-point-window.ts +183 -0
- package/src/client/chat/chat-slots.ts +107 -0
- package/src/client/chat/rollback-diff-tab.test.ts +105 -0
- package/src/client/chat/rollback-diff-tab.ts +174 -0
- package/src/client/chat/rollback-entry-view.test.ts +1469 -0
- package/src/client/chat/rollback-entry-view.ts +946 -0
- package/src/client/chat/sidebar-slots.ts +59 -0
- package/src/client/common/ConfirmDialog.tsx +7 -3
- package/src/client/common/ContentPicker.tsx +48 -10
- package/src/client/common/DiffTable.tsx +133 -0
- package/src/client/common/ErrorBanner.tsx +4 -4
- package/src/client/common/Icon.tsx +2 -0
- package/src/client/common/Modal.tsx +8 -1
- package/src/client/common/ProgressBar.tsx +8 -2
- package/src/client/common/ReportView.tsx +4 -4
- package/src/client/common/RunsCenter.tsx +1 -0
- package/src/client/common/client-route-parity.test.ts +84 -0
- package/src/client/common/info-hint-guard.test.ts +46 -13
- package/src/client/common/picker-lazy-units.test.ts +42 -0
- package/src/client/common/plan-text-redaction.test.ts +17 -6
- package/src/client/common/progress-view-edge.test.ts +8 -7
- package/src/client/common/progress-view.test.ts +9 -9
- package/src/client/common/progress-view.ts +2 -2
- package/src/client/common/routes.ts +38 -0
- package/src/client/common/section-labels.test.ts +1 -0
- package/src/client/common/section-labels.ts +2 -0
- package/src/client/common/ui.tsx +73 -10
- package/src/client/config-manager.module.css +1109 -31
- package/src/client/css-scale-guard.test.ts +30 -0
- package/src/client/environment/EnvironmentPanel.tsx +11 -4
- package/src/client/environment/maintenance/DiskUsageCard.tsx +23 -2
- package/src/client/export/ExportView.tsx +13 -8
- package/src/client/history/HistoryPanel.tsx +4 -3
- package/src/client/history/history-api.ts +2 -2
- package/src/client/history/history-locales.ts +5 -1
- package/src/client/home/HomePanel.tsx +29 -8
- package/src/client/import/ForeignImportView.tsx +4 -5
- package/src/client/import/ImportWizardView.tsx +5 -3
- package/src/client/import/import-wizard-split.test.ts +3 -1
- package/src/client/import/import-wizard-steps.tsx +9 -9
- package/src/client/index.ts +232 -3
- package/src/client/library/LibraryActions.tsx +32 -7
- package/src/client/library/LibraryPanel.tsx +23 -3
- package/src/client/library/RestorePlanView.tsx +9 -66
- package/src/client/locales.ts +62 -9
- package/src/client/lucide-icons.d.ts +1 -0
- package/src/client/market/MarketPanel.tsx +13 -2
- package/src/client/market/MyConfigsView.tsx +5 -4
- package/src/client/market/MyConfigsWizard.tsx +10 -8
- package/src/client/market/market-api.test.ts +9 -8
- package/src/client/market/market-api.ts +2 -2
- package/src/client/market/market-locales.ts +3 -0
- package/src/client/market/market-view.test.ts +6 -6
- package/src/client/market/market-view.ts +3 -3
- package/src/client/market/my-configs-api.ts +2 -2
- package/src/client/market/my-configs-view.test.ts +23 -22
- package/src/client/market/my-configs-view.ts +8 -8
- package/src/client/recovery/IncompleteCopiesSection.tsx +2 -1
- package/src/client/recovery/RecoveryPanel.tsx +442 -283
- package/src/client/recovery/RollbackCard.tsx +859 -0
- package/src/client/recovery/RollbackDiffPane.tsx +69 -0
- package/src/client/recovery/RollbackFileChanges.tsx +163 -0
- package/src/client/recovery/incident-api.ts +2 -2
- package/src/client/recovery/recovery-api.ts +144 -6
- package/src/client/recovery/recovery-locales.test.ts +299 -0
- package/src/client/recovery/recovery-locales.ts +520 -149
- package/src/client/recovery/recovery-panel-layout.test.ts +132 -2
- package/src/client/recovery/rollback-enabled.ts +41 -0
- package/src/client/recovery/rollback-file-changes.test.ts +114 -0
- package/src/client/recovery/session-dialog-mount.test.ts +54 -0
- package/src/client/run-store.ts +84 -21
- package/src/client/sync/AutosyncCard.tsx +25 -1
- package/src/client/sync/ChannelClearConfirmDialog.tsx +6 -3
- package/src/client/sync/ChannelConfigDialog.tsx +10 -10
- package/src/client/sync/DecryptPasswordCard.tsx +2 -2
- package/src/client/sync/SecurityOptionsCard.tsx +27 -17
- package/src/client/sync/SyncChannelEntryCard.tsx +14 -2
- package/src/client/sync/SyncConfirmView.tsx +46 -8
- package/src/client/sync/SyncLogList.tsx +1 -1
- package/src/client/sync/SyncPanel.tsx +31 -23
- package/src/client/sync/SyncRepositoryPicker.tsx +3 -5
- package/src/client/sync/SyncSectionPickerDialog.tsx +11 -2
- package/src/client/sync/SyncSettingsView.test.ts +20 -1
- package/src/client/sync/UnreadableSnapshotsBanner.tsx +1 -1
- package/src/client/sync/WorkDirCard.tsx +112 -0
- package/src/client/sync/history-model.test.ts +42 -9
- package/src/client/sync/history-model.ts +30 -15
- package/src/client/sync/sync-api.test.ts +57 -33
- package/src/client/sync/sync-api.ts +80 -6
- package/src/client/sync/sync-locales.ts +104 -3
- package/src/client/sync/sync-view-v2.test.ts +65 -13
- package/src/client/sync/sync-view.test.ts +107 -52
- package/src/client/sync/sync-view.ts +124 -41
- package/src/core/analyzer.ts +31 -9
- package/src/core/backup-plan.test.ts +48 -5
- package/src/core/backup-plan.ts +51 -20
- package/src/core/backup-verify.ts +24 -23
- package/src/core/backup.test.ts +76 -0
- package/src/core/backup.ts +105 -29
- package/src/core/boot-rescue.ts +4 -3
- package/src/core/boot-safety.ts +2 -1
- package/src/core/command-rollback.test.ts +536 -0
- package/src/core/command-rollback.ts +406 -0
- package/src/core/consult-source.ts +7 -6
- package/src/core/disk-usage.test.ts +257 -1
- package/src/core/disk-usage.ts +134 -14
- package/src/core/engine-locale.test.ts +593 -0
- package/src/core/engine-locale.ts +175 -0
- package/src/core/exporter.ts +3 -3
- package/src/core/fork-reconcile.test.ts +279 -0
- package/src/core/fork-reconcile.ts +234 -0
- package/src/core/journal.ts +6 -5
- package/src/core/local-plugin-host.ts +3 -2
- package/src/core/local-plugin-pack.ts +11 -6
- package/src/core/messages-core.ts +661 -0
- package/src/core/messages.ts +125 -17
- package/src/core/migration-consult.ts +21 -20
- package/src/core/migration-history.ts +19 -5
- package/src/core/model-tools.ts +28 -27
- package/src/core/phase3-host.ts +21 -5
- package/src/core/phase3-p2b-fingerprint.test.ts +22 -0
- package/src/core/plugin-cli.ts +18 -19
- package/src/core/plugin-data-registry.test.ts +113 -0
- package/src/core/plugin-data-registry.ts +261 -0
- package/src/core/reconcile.test.ts +117 -0
- package/src/core/reconcile.ts +85 -17
- package/src/core/recovery-orchestrator.ts +19 -18
- package/src/core/reinstall.ts +26 -21
- package/src/core/restore.ts +20 -17
- package/src/core/rollback-file-diff.test.ts +159 -0
- package/src/core/rollback-gate.test.ts +66 -0
- package/src/core/rollback-gate.ts +61 -0
- package/src/core/rollback-ignore-rules.ts +126 -0
- package/src/core/rollback-ledger.test.ts +655 -0
- package/src/core/rollback-ledger.ts +1314 -0
- package/src/core/rollback-point.test.ts +2853 -0
- package/src/core/rollback-point.ts +4340 -0
- package/src/core/rollback.ts +5 -3
- package/src/core/run-registry.ts +6 -5
- package/src/core/session-health.ts +15 -0
- package/src/core/session-meta.test.ts +117 -1
- package/src/core/session-meta.ts +149 -2
- package/src/core/session-title.test.ts +127 -0
- package/src/core/session-title.ts +189 -0
- package/src/core/startup-barrier.test.ts +69 -0
- package/src/core/startup-barrier.ts +27 -0
- package/src/core/transaction-coordinator.ts +4 -3
- package/src/core/types.ts +37 -1
- package/src/core/validator.ts +5 -4
- package/src/core/verify-recovery.ts +31 -30
- package/src/foreign/bundle.ts +3 -2
- package/src/foreign/continue.ts +2 -1
- package/src/foreign/crush.ts +2 -1
- package/src/foreign/file-budget.test.ts +2 -0
- package/src/foreign/gemini.ts +2 -1
- package/src/foreign/goose.ts +2 -1
- package/src/foreign/kilocode.ts +2 -1
- package/src/foreign/kimi.ts +2 -1
- package/src/foreign/messages.ts +44 -0
- package/src/foreign/mimocode.ts +2 -1
- package/src/foreign/openclaw.ts +2 -1
- package/src/foreign/opencode.ts +2 -1
- package/src/foreign/pi.ts +2 -1
- package/src/foreign/qoder.ts +2 -1
- package/src/foreign/qwen.ts +2 -1
- package/src/foreign/registry.test.ts +1 -1
- package/src/foreign/registry.ts +8 -7
- package/src/foreign/teleagent.ts +2 -1
- package/src/foreign/workbuddy.ts +2 -1
- package/src/foreign/zcode.ts +2 -1
- package/src/foreign/zed.ts +2 -1
- package/src/index.facade.test.ts +340 -1
- package/src/index.ts +876 -57
- package/src/market/git-file-writer.ts +20 -19
- package/src/market/github-repos.test.ts +23 -0
- package/src/market/github-repos.ts +37 -19
- package/src/market/index-parser.ts +31 -30
- package/src/market/messages-market.ts +325 -0
- package/src/market/my-repo.ts +32 -29
- package/src/market/prepare.ts +18 -15
- package/src/market/security.ts +20 -19
- package/src/market/types.ts +2 -1
- package/src/market/url.ts +2 -1
- package/src/migrations/index.ts +6 -5
- package/src/migrations/messages-migrations.ts +59 -0
- package/src/profiles/dsh-profile-launcher.ts +2 -1
- package/src/profiles/dsh-profile-runtime.ts +2 -1
- package/src/profiles/messages.ts +31 -0
- package/src/routes/backup.ts +18 -2
- package/src/routes/import.ts +3 -3
- package/src/routes/index.ts +1 -1
- package/src/routes/kit.ts +9 -3
- package/src/routes/market.ts +1 -1
- package/src/routes/messages.ts +106 -0
- package/src/routes/prefs.ts +31 -2
- package/src/routes/profiles.ts +12 -6
- package/src/routes/recovery.test.ts +405 -1
- package/src/routes/recovery.ts +477 -94
- package/src/routes/snapshots.ts +51 -4
- package/src/routes/sync.ts +82 -17
- package/src/schema/config.ts +53 -20
- package/src/schema/index.ts +7 -5
- package/src/schema/manifest-schema.test.ts +4 -4
- package/src/schema/manifest.ts +19 -18
- package/src/schema/messages.ts +120 -0
- package/src/schema/registry.test.ts +28 -7
- package/src/schema/section-registry.ts +96 -42
- package/src/schema/types.ts +2 -1
- package/src/schema/versions.ts +6 -5
- package/src/security/encryption.ts +16 -15
- package/src/security/integrity.ts +8 -7
- package/src/security/messages.ts +87 -0
- package/src/security/redaction.test.ts +17 -1
- package/src/security/redaction.ts +13 -0
- package/src/security/secret-scanner.ts +19 -12
- package/src/security/security.test.ts +47 -0
- package/src/security/vault.ts +2 -1
- package/src/security/zip-security.ts +4 -3
- package/src/sync/ancestor.ts +2 -1
- package/src/sync/autosync-config.ts +21 -0
- package/src/sync/autosync-scheduler.test.ts +63 -7
- package/src/sync/autosync-scheduler.ts +118 -78
- package/src/sync/backup-files.ts +3 -2
- package/src/sync/backup-scheduler.ts +6 -5
- package/src/sync/blob-store.ts +2 -1
- package/src/sync/git/git-transport.test.ts +170 -8
- package/src/sync/git/git-transport.ts +176 -2
- package/src/sync/github-auth.ts +10 -9
- package/src/sync/layout.ts +13 -12
- package/src/sync/messages.ts +153 -0
- package/src/sync/review-queue.ts +6 -5
- package/src/sync/s3/messages.ts +16 -0
- package/src/sync/s3/s3-providers.ts +9 -6
- package/src/sync/s3/s3-transport.ts +1 -1
- package/src/sync/s3/sigv4.ts +3 -1
- package/src/sync/snapshot-crypto.ts +6 -5
- package/src/sync/snapshot-json.ts +3 -2
- package/src/sync/sync-credentials.test.ts +119 -0
- package/src/sync/sync-engine-download-tombstone.test.ts +1 -1
- package/src/sync/sync-engine.test.ts +226 -8
- package/src/sync/sync-engine.ts +154 -52
- package/src/sync/sync-history.ts +7 -6
- package/src/sync/sync-selection.test.ts +12 -2
- package/src/sync/sync-selection.ts +18 -5
- package/src/sync/transport.test.ts +1 -0
- package/src/sync/ui-prefs.test.ts +44 -1
- package/src/sync/ui-prefs.ts +39 -0
- package/src/sync/webdav/webdav-transport.ts +4 -1
- package/src/ui/artifact-view.ts +4 -3
- package/src/ui/commands.test.ts +1 -0
- package/src/ui/commands.ts +11 -1
- package/src/ui/diff-view.test.ts +9 -1
- package/src/ui/diff-view.ts +22 -0
- package/src/ui/disk-usage-view.test.ts +113 -6
- package/src/ui/disk-usage-view.ts +63 -7
- package/src/ui/errors.ts +4 -3
- package/src/ui/export-flow.ts +26 -20
- package/src/ui/foreign-view.test.ts +43 -0
- package/src/ui/foreign-view.ts +30 -3
- package/src/ui/i18n.ts +131 -2
- package/src/ui/import-wizard.ts +18 -10
- package/src/ui/progress.ts +2 -2
- package/src/ui/report.ts +7 -6
- package/src/ui/rollback-view.test.ts +836 -0
- package/src/ui/rollback-view.ts +1405 -0
- package/src/ui/selection-model.test.ts +33 -1
- package/src/ui/selection-model.ts +53 -0
- package/src/ui/session-inventory-view.test.ts +109 -0
- package/src/ui/session-inventory-view.ts +138 -3
- package/src/ui/session-issue-labels.test.ts +51 -0
- package/src/ui/session-issue-labels.ts +70 -0
- package/src/ui/sync-settings-view.test.ts +36 -0
- package/src/ui/sync-settings-view.ts +49 -0
- package/src/ui/test-helpers.ts +2 -1
- package/src/ui/types.ts +10 -4
- package/src/utils/atomic-write.ts +6 -5
- package/src/utils/bundle-scan.test.ts +2 -2
- package/src/utils/bundle-scan.ts +12 -1
- package/src/utils/env-lock.ts +83 -49
- package/src/utils/json.ts +7 -5
- package/src/utils/messages-utils.ts +232 -0
- package/src/utils/proxy.test.ts +25 -0
- package/src/utils/proxy.ts +19 -5
- package/src/utils/recursive-walk.test.ts +43 -0
- package/src/utils/recursive-walk.ts +75 -1
- package/src/utils/session-log-repair.ts +10 -9
- package/src/utils/zip.ts +27 -26
- package/lib/core/checkpoint.d.ts +0 -362
- package/lib/core/checkpoint.js +0 -1309
- package/src/core/checkpoint.test.ts +0 -665
- package/src/core/checkpoint.ts +0 -1614
package/lib/core/rollback.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* 如实标记 failed + manualHint(人工补录);
|
|
8
8
|
* - settings 回滚仍走 expectedRevision 乐观锁,避免覆盖导入后用户的新修改。
|
|
9
9
|
*/
|
|
10
|
-
import { resolveFileTarget } from './backup.js';
|
|
10
|
+
import { fsForSection, resolveFileTarget } from './backup.js';
|
|
11
11
|
import { isPatchLayerRel, USER_PATCH_FILE } from './patch-layers.js';
|
|
12
12
|
import { msgOf } from './messages.js';
|
|
13
13
|
/** 逆序补偿单条快照条目;返回 null=成功,否则为失败原因 */
|
|
@@ -75,15 +75,17 @@ async function compensateOne(entry, ctx, store) {
|
|
|
75
75
|
}
|
|
76
76
|
case 'file': {
|
|
77
77
|
try {
|
|
78
|
+
// 门面按分区选(pluginData / pluginAssets 在系统 home 根;见 backup.ts 的 fsForSection)
|
|
79
|
+
const fsFacade = fsForSection(ctx, entry.adapter);
|
|
78
80
|
const abs = resolveFileTarget(ctx, entry.adapter, entry.ref);
|
|
79
81
|
if (entry.existed && entry.copiedTo && store) {
|
|
80
82
|
const data = await store.readBlob(entry.snapshotId ?? '', entry.copiedTo);
|
|
81
|
-
await
|
|
83
|
+
await fsFacade.writeFile(abs, data);
|
|
82
84
|
}
|
|
83
85
|
else if (!entry.existed) {
|
|
84
86
|
// 原文件不存在 → 删除导入写入的文件
|
|
85
|
-
if (await
|
|
86
|
-
await
|
|
87
|
+
if (await fsFacade.exists(abs))
|
|
88
|
+
await fsFacade.remove(abs);
|
|
87
89
|
}
|
|
88
90
|
return null;
|
|
89
91
|
}
|
package/lib/core/run-registry.js
CHANGED
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
*/
|
|
21
21
|
import { randomBytes } from 'node:crypto';
|
|
22
22
|
import { zhMsg } from './messages.js';
|
|
23
|
+
import { engineMsg } from './messages-core.js';
|
|
23
24
|
/** 单个 run 执行日志的行数上限(防无限增长;截断时保留最新行)。 */
|
|
24
25
|
export const MAX_RUN_LOG_LINES = 500;
|
|
25
26
|
/** 进行中同 kind 已有 run 时注册被拒(宿主路由 → 409)。 */
|
|
@@ -30,15 +31,15 @@ export class RunConflictError extends Error {
|
|
|
30
31
|
super(kind === 'export'
|
|
31
32
|
? msg('run.conflict.export', { runId })
|
|
32
33
|
: kind === 'autosync'
|
|
33
|
-
? '
|
|
34
|
+
? engineMsg('core.run.autosyncBusy')
|
|
34
35
|
: kind === 'sync-apply'
|
|
35
|
-
? '
|
|
36
|
+
? engineMsg('core.run.syncApplyBusy')
|
|
36
37
|
: kind === 'backup-schedule'
|
|
37
|
-
? '
|
|
38
|
+
? engineMsg('core.run.backupScheduleBusy')
|
|
38
39
|
: kind === 'restore'
|
|
39
|
-
? '
|
|
40
|
+
? engineMsg('core.run.restoreBusy')
|
|
40
41
|
: kind === 'recovery'
|
|
41
|
-
? '
|
|
42
|
+
? engineMsg('core.run.recoveryBusy')
|
|
42
43
|
: msg('run.conflict.import', { runId }));
|
|
43
44
|
this.name = 'RunConflictError';
|
|
44
45
|
this.runId = runId;
|
|
@@ -141,12 +141,27 @@ export interface SessionHealthContext {
|
|
|
141
141
|
export interface SessionHealthRow {
|
|
142
142
|
unitId: string;
|
|
143
143
|
sessionId: string;
|
|
144
|
+
/**
|
|
145
|
+
* 界面标题(**宿主侧补**,不是分类判定的一部分)。
|
|
146
|
+
*
|
|
147
|
+
* 来源 = DSH 会话缓存 `storages/session_projcache.json` 的 `rows.title.val`,由宿主在扫描后用
|
|
148
|
+
* `core/session-meta.ts` 的 `applySessionTitles` 统一补上(本模块零 IO,自己不读盘)。
|
|
149
|
+
* 读不到 = 缺省 → 界面回落显示会话目录名(绝不编造标题)。
|
|
150
|
+
*/
|
|
151
|
+
title?: string;
|
|
144
152
|
projectKey: string;
|
|
145
153
|
severity: SessionHealthSeverity;
|
|
146
154
|
issues: SessionHealthIssue[];
|
|
147
155
|
version?: number;
|
|
148
156
|
cwd?: string;
|
|
149
157
|
origin?: string;
|
|
158
|
+
/**
|
|
159
|
+
* 空白会话(从未提问过;宿主侧从会话投影补的**事实**)。
|
|
160
|
+
*
|
|
161
|
+
* 用途:这类会话在 DSH 自己那里也没有标题(显示本地化的「新会话」)—— 界面据此显示同样的
|
|
162
|
+
* 占位名,而不是把 `session-<uuid>` 当标题。只下发事实,文案由界面按当前语言解析。
|
|
163
|
+
*/
|
|
164
|
+
blank?: boolean;
|
|
150
165
|
sizeBytes?: number;
|
|
151
166
|
mtimeMs?: number;
|
|
152
167
|
}
|
|
@@ -37,7 +37,7 @@ export interface SessionMetaIndex {
|
|
|
37
37
|
}
|
|
38
38
|
/** 空索引(读不到任何缓存时的安全返回值)。 */
|
|
39
39
|
export declare function emptySessionMeta(): SessionMetaIndex;
|
|
40
|
-
/**
|
|
40
|
+
/** 清空索引与逐会话投影缓存(测试用;宿主在收到「DSH 存储已重载」信号时也可调用)。 */
|
|
41
41
|
export declare function clearSessionMetaCache(): void;
|
|
42
42
|
/**
|
|
43
43
|
* 读取会话标题与工作区注册表(带 mtime 失效的进程内缓存,见 `metaCache`)。
|
|
@@ -46,6 +46,27 @@ export declare function clearSessionMetaCache(): void;
|
|
|
46
46
|
* 返回的索引视为**只读**(调用方 `applySessionMeta*` 不改它)——缓存会把同一份 Map 再发出去。
|
|
47
47
|
*/
|
|
48
48
|
export declare function readSessionMeta(ctx: HostContext): Promise<SessionMetaIndex>;
|
|
49
|
+
/**
|
|
50
|
+
* 会话单元 id(`sessions:<项目键>/<目录名>`)或裸目录名 → **归一化裸键**(`sessionIdKey`)。
|
|
51
|
+
*
|
|
52
|
+
* 为什么要有:调用方手上是单元 id / 计划项 id(末段才是目录名),而索引与投影文件都按**裸键**存。
|
|
53
|
+
*/
|
|
54
|
+
export declare function sessionBareKeyOf(ref: string): string;
|
|
55
|
+
/**
|
|
56
|
+
* 「界面标题」的**全量**解析:聚合缓存优先,缺的按需读**逐会话投影文件**。
|
|
57
|
+
*
|
|
58
|
+
* 用户要求(2026-10-12):「历史对话的文本**始终**用对话标题,而不是只取缓存里的标题」。
|
|
59
|
+
* 只读聚合缓存(`session_projcache.json`)时,本机 1347 条会话里有 902 条拿不到标题 → 界面退化成
|
|
60
|
+
* `session-<uuid>`,用户在同屏十几条里认不出哪条是自己的对话。
|
|
61
|
+
*
|
|
62
|
+
* 成本:只读「聚合缓存里没有标题」的那些(本机约 900 条小文件;实测全量顺序读 1349 份 = 361 ms),
|
|
63
|
+
* 32 路并发 + mtime 缓存 —— 同一批会话重复查询(打开选择器 / 勾选 / 联动补跑)不再重复读盘。
|
|
64
|
+
*
|
|
65
|
+
* 语义:**只增不改** —— 已缓存的标题与 blank 一律保留;实在读不到的会话原样返回(界面回落目录名)。
|
|
66
|
+
*
|
|
67
|
+
* @param refs 会话单元 id(`sessions:<项目键>/<目录名>`)或裸目录名,混着传也行
|
|
68
|
+
*/
|
|
69
|
+
export declare function readSessionMetaWithTitles(ctx: HostContext, refs: readonly string[]): Promise<SessionMetaIndex>;
|
|
49
70
|
/**
|
|
50
71
|
* 给 **导入计划项**(sessions 分区)补上「界面标题 + 工作区分组」。
|
|
51
72
|
*
|
|
@@ -62,7 +83,9 @@ export declare function applySessionMetaToPlanItems<T extends {
|
|
|
62
83
|
adapter: string;
|
|
63
84
|
label?: string;
|
|
64
85
|
group?: string;
|
|
65
|
-
}>(items: readonly T[], index: SessionMetaIndex, sectionId?: string): T
|
|
86
|
+
}>(items: readonly T[], index: SessionMetaIndex, sectionId?: string): (T & {
|
|
87
|
+
blank?: boolean;
|
|
88
|
+
})[];
|
|
66
89
|
/**
|
|
67
90
|
* 「子代理会话 → 父对话」映射(**裸键**):DSH 会话存储的父子关系 → `Map<子会话裸键, 父对话裸键>`。
|
|
68
91
|
*
|
|
@@ -80,6 +103,23 @@ export declare function applySessionParentLinks<T extends {
|
|
|
80
103
|
id: string;
|
|
81
104
|
parentSessionId?: string;
|
|
82
105
|
}>(units: readonly T[], parentOf: ReadonlyMap<string, string> | undefined, sectionId?: string): T[];
|
|
106
|
+
/**
|
|
107
|
+
* 给**会话体检行**补上「界面标题」(行上的 `sessionId` = 会话目录名,索引键是裸键 → 查表前必须归一化)。
|
|
108
|
+
*
|
|
109
|
+
* 为什么需要:体检列表此前只显示目录名(`session-20d2722c-…`),用户在同一屏十几条里认不出哪条是自己的
|
|
110
|
+
* 对话(2026-10-12 用户反馈截图实证)。标题的唯一可靠来源就是 DSH 自己的会话缓存
|
|
111
|
+
* (`storages/session_projcache.json` 的 `rows.title.val`)—— 与导出选择器**同一份索引、同一套
|
|
112
|
+
* `sessionIdKey` 归一化口径**,绝不另写一套查表规则。
|
|
113
|
+
*
|
|
114
|
+
* 只读、纯函数、**绝不编造**:索引里没有这条会话 / 没有标题 → 原样返回(界面回落显示目录名)。
|
|
115
|
+
* 索引本身由 `readSessionMetaWithTitles()` 产出(聚合缓存 + **逐会话投影文件**兜底,见那里)。
|
|
116
|
+
*/
|
|
117
|
+
export declare function applySessionTitles<T extends {
|
|
118
|
+
sessionId?: string | null;
|
|
119
|
+
}>(rows: readonly T[], index: SessionMetaIndex): (T & {
|
|
120
|
+
title?: string;
|
|
121
|
+
blank?: boolean;
|
|
122
|
+
})[];
|
|
83
123
|
/**
|
|
84
124
|
* 给 sessions 分区的单元补上「界面标题 + 工作区分组」,并按「工作区 → 最近在前」排序。
|
|
85
125
|
*
|
package/lib/core/session-meta.js
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
const SESSION_PROJCACHE = 'storages/session_projcache.json';
|
|
2
2
|
const WORKSPACE_STORE = 'storages/workspace.json';
|
|
3
|
+
/**
|
|
4
|
+
* **逐会话**投影存储目录(`storages/session_projcache/sessions/<会话目录名>.json`)。
|
|
5
|
+
*
|
|
6
|
+
* 与 `session_projcache.json` 的关系:那个是**会淘汰的近期聚合**(本机实测只覆盖 445/1347 条会话),
|
|
7
|
+
* 这个目录才是 DSH 为**每条会话**留下的投影(本机 1349 份,其中 1150 份带标题)。只读聚合缓存会让
|
|
8
|
+
* 其余会话在选择器 / 体检列表里退化成 uuid 目录名 —— 用户认不出哪条是哪条。
|
|
9
|
+
*/
|
|
10
|
+
const SESSION_PROJECTION_DIR = 'storages/session_projcache/sessions';
|
|
3
11
|
// projectKeyOf 已下移到**零依赖**模块 `session-select.ts`(客户端勾选联动要按 cwd 目录键认领工作区,
|
|
4
12
|
// 而本模块会用 Buffer 读 DSH 存储缓存,不能进浏览器 bundle)。这里 re-export,既有导入路径逐字不变。
|
|
5
13
|
import { projectKeyOf, sessionIdKey } from './session-select.js';
|
|
@@ -35,9 +43,18 @@ function asRecord(value) {
|
|
|
35
43
|
* 键含 `homeDir`(同进程可能服务多份 home)。
|
|
36
44
|
*/
|
|
37
45
|
const metaCache = new Map();
|
|
38
|
-
/**
|
|
46
|
+
/**
|
|
47
|
+
* 逐会话投影文件的**进程内缓存**(键含 homeDir + 相对路径,失效判据 = 该文件 mtime)。
|
|
48
|
+
*
|
|
49
|
+
* 为什么需要:导出选择器一次要补上千条会话的标题,而同一批会话在一次交互里会被反复查询
|
|
50
|
+
* (打开选择器 → 勾选 → 会话↔工作区联动补跑)。每次重读 1300 个小文件会把「打开弹窗」拖成秒级。
|
|
51
|
+
* 与 `metaCache` 同一条纪律:**读不到 mtime 就不缓存**(宁可贵一点,也绝不拿陈旧标题骗用户)。
|
|
52
|
+
*/
|
|
53
|
+
const projectionCache = new Map();
|
|
54
|
+
/** 清空索引与逐会话投影缓存(测试用;宿主在收到「DSH 存储已重载」信号时也可调用)。 */
|
|
39
55
|
export function clearSessionMetaCache() {
|
|
40
56
|
metaCache.clear();
|
|
57
|
+
projectionCache.clear();
|
|
41
58
|
}
|
|
42
59
|
/** 文件 mtime(毫秒;门面未实现 / 读不到 → null = 无法判定新鲜度,调用方不得缓存)。 */
|
|
43
60
|
async function mtimeOf(ctx, rel) {
|
|
@@ -115,6 +132,97 @@ async function loadSessionMeta(ctx) {
|
|
|
115
132
|
}
|
|
116
133
|
return index;
|
|
117
134
|
}
|
|
135
|
+
/**
|
|
136
|
+
* 会话单元 id(`sessions:<项目键>/<目录名>`)或裸目录名 → **归一化裸键**(`sessionIdKey`)。
|
|
137
|
+
*
|
|
138
|
+
* 为什么要有:调用方手上是单元 id / 计划项 id(末段才是目录名),而索引与投影文件都按**裸键**存。
|
|
139
|
+
*/
|
|
140
|
+
export function sessionBareKeyOf(ref) {
|
|
141
|
+
const slash = ref.lastIndexOf('/');
|
|
142
|
+
return sessionIdKey(slash >= 0 ? ref.slice(slash + 1) : ref);
|
|
143
|
+
}
|
|
144
|
+
/** 逐会话投影文件的候选名(DSH 实测两种目录名形态并存:`session-<uuid>.json` 与 `<uuid>.json`)。 */
|
|
145
|
+
function projectionFileNames(bare) {
|
|
146
|
+
return ['session-' + bare + '.json', bare + '.json'];
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* 读一条会话的**逐会话投影文件**(`storages/session_projcache/sessions/<会话目录名>.json`)。
|
|
150
|
+
*
|
|
151
|
+
* 形状与聚合缓存略有差异:文件里是 `{ version, record: { identity, rows } }`,聚合里是 `{ identity, rows }`
|
|
152
|
+
* —— 两种都认(`record` 缺省即顶层)。**只读、尽力而为、绝不抛错**:读不到 / 形状不认 → undefined。
|
|
153
|
+
*/
|
|
154
|
+
async function readProjectedSession(ctx, bare) {
|
|
155
|
+
for (const name of projectionFileNames(bare)) {
|
|
156
|
+
const rel = SESSION_PROJECTION_DIR + '/' + name;
|
|
157
|
+
const cacheId = ctx.homeDir + '\u0000' + rel;
|
|
158
|
+
const mtime = await mtimeOf(ctx, rel);
|
|
159
|
+
if (mtime !== null) {
|
|
160
|
+
const hit = projectionCache.get(cacheId);
|
|
161
|
+
if (hit !== undefined && hit.key === String(mtime))
|
|
162
|
+
return hit.facts;
|
|
163
|
+
}
|
|
164
|
+
let facts;
|
|
165
|
+
const raw = asRecord(await readJsonSafe(ctx, rel));
|
|
166
|
+
if (raw !== null) {
|
|
167
|
+
const rec = asRecord(raw['record']) ?? raw;
|
|
168
|
+
const rows = asRecord(rec['rows']);
|
|
169
|
+
const title = asRecord(rows?.['title'])?.['val'];
|
|
170
|
+
const listMeta = asRecord(asRecord(rows?.['sessionListMetadata'])?.['val']);
|
|
171
|
+
const out = {};
|
|
172
|
+
if (typeof title === 'string' && title.trim() !== '')
|
|
173
|
+
out.title = title.trim();
|
|
174
|
+
if (listMeta?.['blank'] === true)
|
|
175
|
+
out.blank = true;
|
|
176
|
+
facts = out;
|
|
177
|
+
}
|
|
178
|
+
if (mtime === null)
|
|
179
|
+
projectionCache.delete(cacheId);
|
|
180
|
+
else
|
|
181
|
+
projectionCache.set(cacheId, { key: String(mtime), facts });
|
|
182
|
+
if (facts !== undefined)
|
|
183
|
+
return facts;
|
|
184
|
+
}
|
|
185
|
+
return undefined;
|
|
186
|
+
}
|
|
187
|
+
/** 一次并发读多少份投影文件(小文件;32 路实测把 1300 条压到百毫秒级)。 */
|
|
188
|
+
const PROJECTION_CONCURRENCY = 32;
|
|
189
|
+
/**
|
|
190
|
+
* 「界面标题」的**全量**解析:聚合缓存优先,缺的按需读**逐会话投影文件**。
|
|
191
|
+
*
|
|
192
|
+
* 用户要求(2026-10-12):「历史对话的文本**始终**用对话标题,而不是只取缓存里的标题」。
|
|
193
|
+
* 只读聚合缓存(`session_projcache.json`)时,本机 1347 条会话里有 902 条拿不到标题 → 界面退化成
|
|
194
|
+
* `session-<uuid>`,用户在同屏十几条里认不出哪条是自己的对话。
|
|
195
|
+
*
|
|
196
|
+
* 成本:只读「聚合缓存里没有标题」的那些(本机约 900 条小文件;实测全量顺序读 1349 份 = 361 ms),
|
|
197
|
+
* 32 路并发 + mtime 缓存 —— 同一批会话重复查询(打开选择器 / 勾选 / 联动补跑)不再重复读盘。
|
|
198
|
+
*
|
|
199
|
+
* 语义:**只增不改** —— 已缓存的标题与 blank 一律保留;实在读不到的会话原样返回(界面回落目录名)。
|
|
200
|
+
*
|
|
201
|
+
* @param refs 会话单元 id(`sessions:<项目键>/<目录名>`)或裸目录名,混着传也行
|
|
202
|
+
*/
|
|
203
|
+
export async function readSessionMetaWithTitles(ctx, refs) {
|
|
204
|
+
const index = await readSessionMeta(ctx);
|
|
205
|
+
const need = [...new Set(refs.map((ref) => sessionBareKeyOf(ref)).filter((bare) => bare !== ''))]
|
|
206
|
+
.filter((bare) => index.bySessionId.get(bare)?.title === undefined);
|
|
207
|
+
if (need.length === 0)
|
|
208
|
+
return index;
|
|
209
|
+
const bySessionId = new Map(index.bySessionId);
|
|
210
|
+
for (let i = 0; i < need.length; i += PROJECTION_CONCURRENCY) {
|
|
211
|
+
const chunk = need.slice(i, i + PROJECTION_CONCURRENCY);
|
|
212
|
+
const found = await Promise.all(chunk.map(async (bare) => [bare, await readProjectedSession(ctx, bare)]));
|
|
213
|
+
for (const [bare, facts] of found) {
|
|
214
|
+
if (facts === undefined)
|
|
215
|
+
continue;
|
|
216
|
+
const existing = bySessionId.get(bare) ?? {};
|
|
217
|
+
bySessionId.set(bare, {
|
|
218
|
+
...existing,
|
|
219
|
+
...(facts.title !== undefined ? { title: facts.title } : {}),
|
|
220
|
+
...(facts.blank !== undefined ? { blank: facts.blank } : {}),
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
return { ...index, bySessionId };
|
|
225
|
+
}
|
|
118
226
|
/** 单元 id(`sessions:<项目键>/<会话目录名>`)→ 会话目录名;形状不符 → null。 */
|
|
119
227
|
function sessionIdOfUnit(sectionId, unitId) {
|
|
120
228
|
const prefix = sectionId + ':';
|
|
@@ -150,6 +258,8 @@ export function applySessionMetaToPlanItems(items, index, sectionId = 'sessions'
|
|
|
150
258
|
return {
|
|
151
259
|
...item,
|
|
152
260
|
...(meta?.title !== undefined ? { label: meta.title } : {}),
|
|
261
|
+
// 空白会话(从未提问):DSH 自己显示「新会话」而不是 uuid;标记随计划项下发,由界面本地化
|
|
262
|
+
...(meta?.blank === true ? { blank: true } : {}),
|
|
153
263
|
group,
|
|
154
264
|
};
|
|
155
265
|
});
|
|
@@ -192,6 +302,33 @@ export function applySessionParentLinks(units, parentOf, sectionId = 'sessions')
|
|
|
192
302
|
return parent === undefined ? unit : { ...unit, parentSessionId: parent };
|
|
193
303
|
});
|
|
194
304
|
}
|
|
305
|
+
/**
|
|
306
|
+
* 给**会话体检行**补上「界面标题」(行上的 `sessionId` = 会话目录名,索引键是裸键 → 查表前必须归一化)。
|
|
307
|
+
*
|
|
308
|
+
* 为什么需要:体检列表此前只显示目录名(`session-20d2722c-…`),用户在同一屏十几条里认不出哪条是自己的
|
|
309
|
+
* 对话(2026-10-12 用户反馈截图实证)。标题的唯一可靠来源就是 DSH 自己的会话缓存
|
|
310
|
+
* (`storages/session_projcache.json` 的 `rows.title.val`)—— 与导出选择器**同一份索引、同一套
|
|
311
|
+
* `sessionIdKey` 归一化口径**,绝不另写一套查表规则。
|
|
312
|
+
*
|
|
313
|
+
* 只读、纯函数、**绝不编造**:索引里没有这条会话 / 没有标题 → 原样返回(界面回落显示目录名)。
|
|
314
|
+
* 索引本身由 `readSessionMetaWithTitles()` 产出(聚合缓存 + **逐会话投影文件**兜底,见那里)。
|
|
315
|
+
*/
|
|
316
|
+
export function applySessionTitles(rows, index) {
|
|
317
|
+
return rows.map((row) => {
|
|
318
|
+
// 会话 id 缺失 / 空串的记录**一个字段都不加**(回滚台账里有这类历史记录:绝不猜它属于哪条对话)
|
|
319
|
+
if (typeof row.sessionId !== 'string' || row.sessionId === '')
|
|
320
|
+
return row;
|
|
321
|
+
const meta = index.bySessionId.get(sessionIdKey(row.sessionId));
|
|
322
|
+
if (meta === undefined)
|
|
323
|
+
return row;
|
|
324
|
+
return {
|
|
325
|
+
...row,
|
|
326
|
+
...(meta.title !== undefined ? { title: meta.title } : {}),
|
|
327
|
+
// 空白会话(从未提问):没有标题,界面据此显示本地化的「新会话」(与 DSH 的 session.new 同口径)
|
|
328
|
+
...(meta.blank === true ? { blank: true } : {}),
|
|
329
|
+
};
|
|
330
|
+
});
|
|
331
|
+
}
|
|
195
332
|
/**
|
|
196
333
|
* 给 sessions 分区的单元补上「界面标题 + 工作区分组」,并按「工作区 → 最近在前」排序。
|
|
197
334
|
*
|
|
@@ -221,6 +358,8 @@ export function applySessionMeta(units, index, sectionId = 'sessions', activityA
|
|
|
221
358
|
label: meta?.title ?? unit.label,
|
|
222
359
|
...(detail !== undefined ? { detail } : {}),
|
|
223
360
|
group: workspaceTitle ?? parsed.projectKey,
|
|
361
|
+
// 空白会话(从未提问):没有标题可显示,标记出来让界面显示本地化的「新会话」(与 DSH 同口径)
|
|
362
|
+
...(meta?.blank === true ? { blank: true } : {}),
|
|
224
363
|
};
|
|
225
364
|
});
|
|
226
365
|
// 排序:先按工作区(保持首次出现顺序),组内最近活跃的在前;无时间的排在组内末尾。
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/** 只扫日志前这么多字节(标题通常在开头几回合内写入;更大的日志只扫前缀,读不到就回落 id)。 */
|
|
2
|
+
export declare const SESSION_TITLE_SCAN_BYTES: number;
|
|
3
|
+
/** 一次请求最多为几个会话做日志兜底(按 unitId 去重之后)。 */
|
|
4
|
+
export declare const SESSION_TITLE_MAX_READS = 24;
|
|
5
|
+
/** 清空缓存(单测用;宿主进程内长期有效是刻意的)。 */
|
|
6
|
+
export declare function clearSessionTitleCache(): void;
|
|
7
|
+
/**
|
|
8
|
+
* 从会话日志里读这条会话自己的名字(`session/title` 事件)。
|
|
9
|
+
*
|
|
10
|
+
* @param sessionsDir 会话根目录(`<homeDir>/sessions`)。
|
|
11
|
+
* @param unitId 台账里的会话单元 id(`<projectKey>/<sessionId>`;缺了就返回 null)。
|
|
12
|
+
* @returns 标题;读不到 / 没有标题事件 / 形状不符 → null(界面回落显示会话 id)。
|
|
13
|
+
*/
|
|
14
|
+
export declare function readSessionTitleFromLog(sessionsDir: string, unitId: string): Promise<string | null>;
|
|
15
|
+
/**
|
|
16
|
+
* 给一批台账行补标题:**已有标题的不动**,缺标题的按 `unitId` 去重后走日志兜底。
|
|
17
|
+
*
|
|
18
|
+
* 只读、有界(见 {@link SESSION_TITLE_MAX_READS})、失败一律原样返回(界面回落显示会话 id)。
|
|
19
|
+
*/
|
|
20
|
+
export declare function applySessionLogTitles<T extends {
|
|
21
|
+
sessionId?: string | null;
|
|
22
|
+
unitId?: string | null;
|
|
23
|
+
title?: string;
|
|
24
|
+
}>(rows: readonly T[], sessionsDir: string, options?: {
|
|
25
|
+
maxReads?: number;
|
|
26
|
+
}): Promise<(T & {
|
|
27
|
+
title?: string;
|
|
28
|
+
})[]>;
|
|
@@ -0,0 +1,185 @@
|
|
|
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
|
+
import { isSessionLogName } from '../utils/session-log.js';
|
|
20
|
+
import { decodeZstdFrame, scanZstdFrames, zstdAvailable } from '../utils/zstd-frame.js';
|
|
21
|
+
/** 只扫日志前这么多字节(标题通常在开头几回合内写入;更大的日志只扫前缀,读不到就回落 id)。 */
|
|
22
|
+
export const SESSION_TITLE_SCAN_BYTES = 1024 * 1024;
|
|
23
|
+
/** 一次请求最多为几个会话做日志兜底(按 unitId 去重之后)。 */
|
|
24
|
+
export const SESSION_TITLE_MAX_READS = 24;
|
|
25
|
+
/** 进程内缓存:绝对路径 → (mtime + size + 结论)。日志一增长就失效(不吃陈旧结论)。 */
|
|
26
|
+
const titleCache = new Map();
|
|
27
|
+
/** 清空缓存(单测用;宿主进程内长期有效是刻意的)。 */
|
|
28
|
+
export function clearSessionTitleCache() {
|
|
29
|
+
titleCache.clear();
|
|
30
|
+
}
|
|
31
|
+
/** `session/title` 事件行 → 标题(形状不对 / 空标题一律 null,绝不猜)。 */
|
|
32
|
+
function titleOfRow(row) {
|
|
33
|
+
if (row === null || typeof row !== 'object' || Array.isArray(row))
|
|
34
|
+
return null;
|
|
35
|
+
const rec = row;
|
|
36
|
+
if (rec['type'] !== 'session/title')
|
|
37
|
+
return null;
|
|
38
|
+
const data = rec['data'];
|
|
39
|
+
if (data === null || typeof data !== 'object' || Array.isArray(data))
|
|
40
|
+
return null;
|
|
41
|
+
const title = data['title'];
|
|
42
|
+
return typeof title === 'string' && title.trim() !== '' ? title.trim() : null;
|
|
43
|
+
}
|
|
44
|
+
/** 只读日志**前缀**(最多 `max` 字节;读不出回 null,绝不抛)。 */
|
|
45
|
+
async function readPrefix(absPath, max) {
|
|
46
|
+
let handle;
|
|
47
|
+
try {
|
|
48
|
+
handle = await open(absPath, 'r');
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
try {
|
|
54
|
+
const buffer = Buffer.allocUnsafe(max);
|
|
55
|
+
const { bytesRead } = await handle.read(buffer, 0, max, 0);
|
|
56
|
+
return buffer.subarray(0, bytesRead);
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
finally {
|
|
62
|
+
await handle.close().catch(() => undefined);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* 从会话日志字节里取标题:解出每个**完整**帧,逐行先做 `session/title` 子串预筛(省掉整篇 JSON.parse),
|
|
67
|
+
* 命中才解析该行;多条命中取最后一条。容器损坏 / 无 zstd 能力 → null。
|
|
68
|
+
*/
|
|
69
|
+
function titleFromBytes(bytes) {
|
|
70
|
+
let frames;
|
|
71
|
+
try {
|
|
72
|
+
frames = scanZstdFrames(bytes).frames;
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
let found = null;
|
|
78
|
+
for (const frame of frames) {
|
|
79
|
+
let text;
|
|
80
|
+
try {
|
|
81
|
+
text = decodeZstdFrame(bytes.subarray(frame.start, frame.end)).toString('utf8');
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
return found;
|
|
85
|
+
}
|
|
86
|
+
for (const line of text.split('\n')) {
|
|
87
|
+
if (line === '' || !line.includes('session/title'))
|
|
88
|
+
continue;
|
|
89
|
+
let parsed;
|
|
90
|
+
try {
|
|
91
|
+
parsed = JSON.parse(line);
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
const title = titleOfRow(parsed);
|
|
97
|
+
if (title !== null)
|
|
98
|
+
found = title;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return found;
|
|
102
|
+
}
|
|
103
|
+
/** 读单个日志文件的标题(带 mtime+size 缓存)。 */
|
|
104
|
+
async function readTitleFromLogFile(absPath) {
|
|
105
|
+
if (!zstdAvailable())
|
|
106
|
+
return null;
|
|
107
|
+
const info = await stat(absPath).catch(() => null);
|
|
108
|
+
if (info === null)
|
|
109
|
+
return null;
|
|
110
|
+
const cached = titleCache.get(absPath);
|
|
111
|
+
if (cached !== undefined && cached.mtimeMs === info.mtimeMs && cached.size === info.size)
|
|
112
|
+
return cached.title;
|
|
113
|
+
const prefix = await readPrefix(absPath, SESSION_TITLE_SCAN_BYTES);
|
|
114
|
+
const title = prefix === null ? null : titleFromBytes(prefix);
|
|
115
|
+
titleCache.set(absPath, { mtimeMs: info.mtimeMs, size: info.size, title });
|
|
116
|
+
return title;
|
|
117
|
+
}
|
|
118
|
+
/** `unitId`(`<projectKey>/<sessionId>`)→ 目录段;越界 / 形状不符一律 null(绝不拼可疑路径)。 */
|
|
119
|
+
function unitSegments(unitId) {
|
|
120
|
+
const parts = unitId.split('/').filter((part) => part !== '');
|
|
121
|
+
if (parts.length < 2)
|
|
122
|
+
return null;
|
|
123
|
+
for (const part of parts) {
|
|
124
|
+
if (part === '.' || part === '..' || part.includes('\\') || part.includes(':'))
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
return parts;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* 从会话日志里读这条会话自己的名字(`session/title` 事件)。
|
|
131
|
+
*
|
|
132
|
+
* @param sessionsDir 会话根目录(`<homeDir>/sessions`)。
|
|
133
|
+
* @param unitId 台账里的会话单元 id(`<projectKey>/<sessionId>`;缺了就返回 null)。
|
|
134
|
+
* @returns 标题;读不到 / 没有标题事件 / 形状不符 → null(界面回落显示会话 id)。
|
|
135
|
+
*/
|
|
136
|
+
export async function readSessionTitleFromLog(sessionsDir, unitId) {
|
|
137
|
+
const parts = unitSegments(unitId);
|
|
138
|
+
if (parts === null)
|
|
139
|
+
return null;
|
|
140
|
+
const dir = join(sessionsDir, ...parts);
|
|
141
|
+
const names = await readdir(dir).catch(() => []);
|
|
142
|
+
const logs = names.filter((name) => isSessionLogName(name)).sort();
|
|
143
|
+
if (logs.length === 0)
|
|
144
|
+
return null;
|
|
145
|
+
// 与体检同口径:同一会话有多份 generation 时以**最新的一份**(字典序最后)为准
|
|
146
|
+
const picked = logs[logs.length - 1];
|
|
147
|
+
if (picked === undefined)
|
|
148
|
+
return null;
|
|
149
|
+
return readTitleFromLogFile(join(dir, picked));
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* 给一批台账行补标题:**已有标题的不动**,缺标题的按 `unitId` 去重后走日志兜底。
|
|
153
|
+
*
|
|
154
|
+
* 只读、有界(见 {@link SESSION_TITLE_MAX_READS})、失败一律原样返回(界面回落显示会话 id)。
|
|
155
|
+
*/
|
|
156
|
+
export async function applySessionLogTitles(rows, sessionsDir, options = {}) {
|
|
157
|
+
const maxReads = Math.max(0, options.maxReads ?? SESSION_TITLE_MAX_READS);
|
|
158
|
+
const resolved = new Map();
|
|
159
|
+
const out = [];
|
|
160
|
+
let reads = 0;
|
|
161
|
+
for (const row of rows) {
|
|
162
|
+
if (typeof row.title === 'string' && row.title.trim() !== '') {
|
|
163
|
+
out.push(row);
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
const unitId = typeof row.unitId === 'string' && row.unitId !== '' ? row.unitId : null;
|
|
167
|
+
if (unitId === null) {
|
|
168
|
+
out.push(row);
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
let title = resolved.get(unitId);
|
|
172
|
+
if (title === undefined) {
|
|
173
|
+
if (reads >= maxReads) {
|
|
174
|
+
out.push(row);
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
reads += 1;
|
|
178
|
+
title = await readSessionTitleFromLog(sessionsDir, unitId);
|
|
179
|
+
resolved.set(unitId, title);
|
|
180
|
+
}
|
|
181
|
+
out.push(title === null || title === '' ? row : { ...row, title });
|
|
182
|
+
}
|
|
183
|
+
return out;
|
|
184
|
+
}
|
|
185
|
+
//# sourceMappingURL=session-title.js.map
|
|
@@ -30,6 +30,30 @@ export declare function classifyStartup(opts: {
|
|
|
30
30
|
options?: ReconcileOptions;
|
|
31
31
|
lockState: 'LOCKED' | 'STALE_LOCK_DETECTED' | 'UNKNOWN_STATE' | 'FREE';
|
|
32
32
|
}): StartupClassifier;
|
|
33
|
+
/**
|
|
34
|
+
* 启动分类结束后如何处置 SAFE MODE(issue #87,单一判据,便于单测;宿主 apply() 只消费结论)。
|
|
35
|
+
*
|
|
36
|
+
* 为什么需要它:`probeSafeModeSync()` 读的是**上一轮**留下的 durable 标记,而分类结论才是本轮权威。
|
|
37
|
+
* 分类判 `NORMAL` ⇒ 没有任何未决 journal 能解释那个标记 ⇒ 它是陈旧标记,必须**同时**复位内存标志与
|
|
38
|
+
* durable 标记 —— 只复位内存会让本会话的写操作继续被 423 挡着(界面却说「无待处理事项」),只复位标记
|
|
39
|
+
* 又会在下一次 probe 时重新置起(标记跨重启生效)。
|
|
40
|
+
*
|
|
41
|
+
* 为什么要求 `lockState === 'FREE'`:`LOCKED` 且无未决 journal 时分类同样返回 NORMAL,但那只说明
|
|
42
|
+
* **另一个存活实例**正在持有环境锁 —— 此刻清标记等于替别人解除保护。**只有确认没有活锁者才清。**
|
|
43
|
+
*
|
|
44
|
+
* 为什么要求 `currentlyBlocked`(即 probe 读到 durable 标记确实是 'blocked'):标记为 `'unknown'`
|
|
45
|
+
* (存在但读不出/布局不可信)时不在这里清 —— 那属于无法判定,既不假装已解除,也不假装没看见;
|
|
46
|
+
* 它本会话不阻断的语义由 `probeSafeModeSync` 的既有约定承担。
|
|
47
|
+
*/
|
|
48
|
+
export declare function resolveStartupSafeMode(input: {
|
|
49
|
+
kind: StartupRecoveryState['kind'];
|
|
50
|
+
lockState: 'LOCKED' | 'STALE_LOCK_DETECTED' | 'UNKNOWN_STATE' | 'FREE';
|
|
51
|
+
/** 本轮 probe 之前的内存标志(probeSafeModeSync 的结论) */
|
|
52
|
+
currentlyBlocked: boolean;
|
|
53
|
+
}): {
|
|
54
|
+
blocked: boolean;
|
|
55
|
+
clearStaleMarker: boolean;
|
|
56
|
+
};
|
|
33
57
|
export interface StartupSchedulers {
|
|
34
58
|
start(): void;
|
|
35
59
|
}
|
|
@@ -40,6 +40,28 @@ export function classifyStartup(opts) {
|
|
|
40
40
|
},
|
|
41
41
|
};
|
|
42
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* 启动分类结束后如何处置 SAFE MODE(issue #87,单一判据,便于单测;宿主 apply() 只消费结论)。
|
|
45
|
+
*
|
|
46
|
+
* 为什么需要它:`probeSafeModeSync()` 读的是**上一轮**留下的 durable 标记,而分类结论才是本轮权威。
|
|
47
|
+
* 分类判 `NORMAL` ⇒ 没有任何未决 journal 能解释那个标记 ⇒ 它是陈旧标记,必须**同时**复位内存标志与
|
|
48
|
+
* durable 标记 —— 只复位内存会让本会话的写操作继续被 423 挡着(界面却说「无待处理事项」),只复位标记
|
|
49
|
+
* 又会在下一次 probe 时重新置起(标记跨重启生效)。
|
|
50
|
+
*
|
|
51
|
+
* 为什么要求 `lockState === 'FREE'`:`LOCKED` 且无未决 journal 时分类同样返回 NORMAL,但那只说明
|
|
52
|
+
* **另一个存活实例**正在持有环境锁 —— 此刻清标记等于替别人解除保护。**只有确认没有活锁者才清。**
|
|
53
|
+
*
|
|
54
|
+
* 为什么要求 `currentlyBlocked`(即 probe 读到 durable 标记确实是 'blocked'):标记为 `'unknown'`
|
|
55
|
+
* (存在但读不出/布局不可信)时不在这里清 —— 那属于无法判定,既不假装已解除,也不假装没看见;
|
|
56
|
+
* 它本会话不阻断的语义由 `probeSafeModeSync` 的既有约定承担。
|
|
57
|
+
*/
|
|
58
|
+
export function resolveStartupSafeMode(input) {
|
|
59
|
+
const staleMarker = input.kind === 'NORMAL' && input.lockState === 'FREE' && input.currentlyBlocked;
|
|
60
|
+
if (staleMarker)
|
|
61
|
+
return { blocked: false, clearStaleMarker: true };
|
|
62
|
+
const forceBlocked = input.kind === 'RECOVERY_REQUIRED' || input.kind === 'NEEDS_ATTENTION' || input.kind === 'UNKNOWN_STATE';
|
|
63
|
+
return { blocked: input.currentlyBlocked || forceBlocked, clearStaleMarker: false };
|
|
64
|
+
}
|
|
43
65
|
/**
|
|
44
66
|
* 启动分类/探测**抛错**时的 fail-closed 结论(单一来源,勿再各写一份):
|
|
45
67
|
* - `state`:绝不默认 NORMAL,归为 RECOVERY_REQUIRED;
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
* SAFE MODE 门禁经注入谓词(isBlocked)由 Coordinator 传入 lock helper(见 env-lock 改造)。
|
|
16
16
|
*/
|
|
17
17
|
import { JournalStore, createJournalEntry, transitionJournalState, isTerminalState, generateOperationId, redactJournalText, } from './journal.js';
|
|
18
|
+
import { engineMsg } from './messages-core.js';
|
|
18
19
|
// ---------- Coordinator ----------
|
|
19
20
|
export class MutationTransactionCoordinator {
|
|
20
21
|
deps;
|
|
@@ -36,9 +37,9 @@ export class MutationTransactionCoordinator {
|
|
|
36
37
|
: 'needs-attention';
|
|
37
38
|
if (recovered === 'recovered' || recovered === 'rolled-back') {
|
|
38
39
|
// 残留已收敛 → 可继续;但本函数语义:先返回,调用方重试 run
|
|
39
|
-
return { outcome: 'RECOVERY_REQUIRED', operationId: activeCheck.residue, reason:
|
|
40
|
+
return { outcome: 'RECOVERY_REQUIRED', operationId: activeCheck.residue, reason: engineMsg('core.txn.residuePending', { residue: activeCheck.residue }) };
|
|
40
41
|
}
|
|
41
|
-
return { outcome: 'RECOVERY_REQUIRED', operationId: activeCheck.residue, reason:
|
|
42
|
+
return { outcome: 'RECOVERY_REQUIRED', operationId: activeCheck.residue, reason: engineMsg('core.txn.unresolvedActive', { residue: activeCheck.residue }) };
|
|
42
43
|
}
|
|
43
44
|
// 3. 创建 journal
|
|
44
45
|
const operationId = generateOperationId();
|
|
@@ -149,7 +150,7 @@ export class MutationTransactionCoordinator {
|
|
|
149
150
|
if (current !== null && isTerminalState(current.state)) {
|
|
150
151
|
// 已 terminal(如 COMMITTED 后 move 抛错 → 保持 terminal,不回滚)
|
|
151
152
|
const opId = operationId;
|
|
152
|
-
return { outcome: 'NEEDS_ATTENTION', operationId: opId, reason:
|
|
153
|
+
return { outcome: 'NEEDS_ATTENTION', operationId: opId, reason: engineMsg('core.txn.tailCommitFailed', { message: msg }), snapshotId };
|
|
153
154
|
}
|
|
154
155
|
return await this.finalizeAttention(operationId, msg, snapshotId, context);
|
|
155
156
|
}
|