dsh-config-manager 0.1.71 → 0.1.73
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +149 -98
- package/README.zh-CN.md +245 -88
- package/lib/adapters/agent-instructions.js +1 -1
- package/lib/adapters/file-collection.js +1 -1
- package/lib/adapters/index.d.ts +2 -1
- package/lib/adapters/index.js +6 -1
- package/lib/adapters/link-report.d.ts +1 -0
- package/lib/adapters/link-report.js +27 -3
- package/lib/adapters/messages.d.ts +21 -0
- package/lib/adapters/messages.js +57 -0
- package/lib/adapters/plugin-data.d.ts +114 -0
- package/lib/adapters/plugin-data.js +309 -0
- package/lib/adapters/plugin-files.js +3 -2
- package/lib/adapters/plugins.js +4 -3
- package/lib/adapters/prompts.d.ts +1 -0
- package/lib/adapters/prompts.js +7 -1
- package/lib/adapters/self.d.ts +8 -2
- package/lib/adapters/self.js +3 -2
- package/lib/adapters/sessions.d.ts +1 -0
- package/lib/adapters/sessions.js +4 -2
- package/lib/adapters/test-helpers.d.ts +9 -0
- package/lib/adapters/test-helpers.js +14 -0
- package/lib/adapters/ui.d.ts +2 -2
- package/lib/adapters/ui.js +25 -25
- package/lib/adapters/workspaces.d.ts +1 -0
- package/lib/adapters/workspaces.js +7 -3
- package/lib/cli/actions.d.ts +9 -1
- package/lib/cli/actions.js +5 -2
- package/lib/cli/help.js +23 -10
- package/lib/cli/index.d.ts +23 -0
- package/lib/cli/index.js +90 -32
- package/lib/cli/sessions-inspect.js +3 -1
- package/lib/cli/sessions-repair.js +25 -6
- package/lib/cli/web/page.d.ts +12 -0
- package/lib/cli/web/page.js +21 -12
- package/lib/cli/web/routes.js +2 -2
- package/lib/cli/web/server.js +4 -4
- package/lib/client.d.ts +1442 -494
- package/lib/client.js +15326 -8520
- package/lib/core/analyzer.d.ts +11 -2
- package/lib/core/analyzer.js +31 -8
- package/lib/core/backup-plan.d.ts +10 -0
- package/lib/core/backup-plan.js +51 -20
- package/lib/core/backup-verify.js +24 -23
- package/lib/core/backup.d.ts +25 -1
- package/lib/core/backup.js +99 -28
- package/lib/core/boot-rescue.js +4 -3
- package/lib/core/boot-safety.js +2 -1
- package/lib/core/command-rollback.d.ts +182 -0
- package/lib/core/command-rollback.js +293 -0
- package/lib/core/consult-source.js +7 -6
- package/lib/core/disk-usage.d.ts +74 -10
- package/lib/core/disk-usage.js +67 -4
- package/lib/core/engine-locale.d.ts +87 -0
- package/lib/core/engine-locale.js +125 -0
- package/lib/core/exporter.js +3 -3
- package/lib/core/fork-reconcile.d.ts +128 -0
- package/lib/core/fork-reconcile.js +150 -0
- package/lib/core/journal.js +6 -5
- package/lib/core/local-plugin-host.js +3 -2
- package/lib/core/local-plugin-pack.js +11 -6
- package/lib/core/messages-core.d.ts +294 -0
- package/lib/core/messages-core.js +619 -0
- package/lib/core/messages.d.ts +61 -2
- package/lib/core/messages.js +118 -13
- package/lib/core/migration-consult.js +21 -20
- package/lib/core/migration-history.js +19 -5
- package/lib/core/model-tools.d.ts +0 -22
- package/lib/core/model-tools.js +50 -27
- package/lib/core/phase3-host.js +30 -5
- package/lib/core/plugin-cli.js +18 -19
- package/lib/core/plugin-data-registry.d.ts +124 -0
- package/lib/core/plugin-data-registry.js +198 -0
- package/lib/core/reconcile.d.ts +2 -14
- package/lib/core/reconcile.js +87 -17
- package/lib/core/recovery-orchestrator.js +19 -18
- package/lib/core/reinstall.d.ts +4 -0
- package/lib/core/reinstall.js +26 -21
- package/lib/core/restore.js +20 -17
- package/lib/core/rollback-gate.d.ts +33 -0
- package/lib/core/rollback-gate.js +57 -0
- package/lib/core/rollback-ignore-rules.d.ts +31 -0
- package/lib/core/rollback-ignore-rules.js +140 -0
- package/lib/core/rollback-ledger.d.ts +248 -0
- package/lib/core/rollback-ledger.js +1282 -0
- package/lib/core/rollback-point.d.ts +1391 -0
- package/lib/core/rollback-point.js +3503 -0
- package/lib/core/rollback.js +6 -4
- package/lib/core/run-registry.js +6 -5
- package/lib/core/session-health.d.ts +15 -0
- package/lib/core/session-meta.d.ts +42 -2
- package/lib/core/session-meta.js +140 -1
- package/lib/core/session-title.d.ts +28 -0
- package/lib/core/session-title.js +185 -0
- package/lib/core/startup-barrier.d.ts +24 -0
- package/lib/core/startup-barrier.js +22 -0
- package/lib/core/transaction-coordinator.js +4 -3
- package/lib/core/types.d.ts +37 -1
- package/lib/core/validator.js +5 -4
- package/lib/core/verify-recovery.js +31 -30
- package/lib/foreign/bundle.js +3 -2
- package/lib/foreign/continue.js +2 -1
- package/lib/foreign/crush.js +2 -1
- package/lib/foreign/gemini.js +2 -1
- package/lib/foreign/goose.js +2 -1
- package/lib/foreign/kilocode.js +2 -1
- package/lib/foreign/kimi.js +2 -1
- package/lib/foreign/messages.d.ts +15 -0
- package/lib/foreign/messages.js +40 -0
- package/lib/foreign/mimocode.js +2 -1
- package/lib/foreign/openclaw.js +2 -1
- package/lib/foreign/opencode.js +2 -1
- package/lib/foreign/pi.js +2 -1
- package/lib/foreign/qoder.js +2 -1
- package/lib/foreign/qwen.js +2 -1
- package/lib/foreign/registry.d.ts +1 -1
- package/lib/foreign/registry.js +8 -7
- package/lib/foreign/teleagent.js +2 -1
- package/lib/foreign/workbuddy.js +2 -1
- package/lib/foreign/zcode.js +2 -1
- package/lib/foreign/zed.js +2 -1
- package/lib/index.d.ts +227 -7
- package/lib/index.js +799 -54
- package/lib/market/git-file-writer.js +20 -19
- package/lib/market/github-repos.js +35 -19
- package/lib/market/index-parser.js +31 -30
- package/lib/market/messages-market.d.ts +138 -0
- package/lib/market/messages-market.js +305 -0
- package/lib/market/my-repo.d.ts +2 -2
- package/lib/market/my-repo.js +32 -29
- package/lib/market/prepare.js +18 -15
- package/lib/market/security.js +20 -19
- package/lib/market/types.js +2 -1
- package/lib/market/url.js +2 -1
- package/lib/migrations/index.js +6 -5
- package/lib/migrations/messages-migrations.d.ts +32 -0
- package/lib/migrations/messages-migrations.js +58 -0
- package/lib/profiles/dsh-profile-launcher.js +2 -1
- package/lib/profiles/dsh-profile-runtime.js +2 -1
- package/lib/profiles/messages.d.ts +7 -0
- package/lib/profiles/messages.js +27 -0
- package/lib/routes/backup.js +18 -2
- package/lib/routes/import.js +3 -3
- package/lib/routes/index.d.ts +1 -1
- package/lib/routes/kit.d.ts +3 -2
- package/lib/routes/kit.js +6 -1
- package/lib/routes/market.js +1 -1
- package/lib/routes/messages.d.ts +43 -0
- package/lib/routes/messages.js +102 -0
- package/lib/routes/prefs.js +26 -3
- package/lib/routes/profiles.js +12 -6
- package/lib/routes/recovery.d.ts +21 -0
- package/lib/routes/recovery.js +478 -94
- package/lib/routes/snapshots.js +51 -4
- package/lib/routes/sync.js +79 -17
- package/lib/schema/config.d.ts +11 -1
- package/lib/schema/config.js +52 -20
- package/lib/schema/index.d.ts +8 -6
- package/lib/schema/index.js +8 -6
- package/lib/schema/manifest.js +19 -18
- package/lib/schema/messages.d.ts +47 -0
- package/lib/schema/messages.js +110 -0
- package/lib/schema/section-registry.d.ts +42 -15
- package/lib/schema/section-registry.js +67 -32
- package/lib/schema/types.d.ts +1 -1
- package/lib/schema/versions.js +6 -5
- package/lib/security/encryption.js +16 -15
- package/lib/security/integrity.js +8 -7
- package/lib/security/messages.d.ts +30 -0
- package/lib/security/messages.js +77 -0
- package/lib/security/redaction.js +12 -0
- package/lib/security/secret-scanner.js +16 -13
- package/lib/security/vault.js +2 -1
- package/lib/security/zip-security.js +4 -3
- package/lib/sync/ancestor.js +2 -1
- package/lib/sync/autosync-config.d.ts +11 -0
- package/lib/sync/autosync-config.js +12 -0
- package/lib/sync/autosync-scheduler.d.ts +11 -1
- package/lib/sync/autosync-scheduler.js +115 -76
- package/lib/sync/backup-files.js +3 -2
- package/lib/sync/backup-scheduler.js +6 -5
- package/lib/sync/blob-store.js +2 -1
- package/lib/sync/git/git-transport.d.ts +55 -0
- package/lib/sync/git/git-transport.js +152 -2
- package/lib/sync/github-auth.js +10 -9
- package/lib/sync/layout.js +13 -12
- package/lib/sync/messages.d.ts +63 -0
- package/lib/sync/messages.js +149 -0
- package/lib/sync/review-queue.js +6 -5
- package/lib/sync/s3/messages.d.ts +7 -0
- package/lib/sync/s3/messages.js +16 -0
- package/lib/sync/s3/s3-providers.d.ts +4 -1
- package/lib/sync/s3/s3-providers.js +5 -5
- package/lib/sync/s3/s3-transport.js +1 -1
- package/lib/sync/s3/sigv4.js +2 -1
- package/lib/sync/snapshot-crypto.js +6 -5
- package/lib/sync/snapshot-json.js +3 -2
- package/lib/sync/sync-engine.d.ts +58 -22
- package/lib/sync/sync-engine.js +119 -48
- package/lib/sync/sync-history.js +7 -6
- package/lib/sync/sync-selection.d.ts +15 -4
- package/lib/sync/sync-selection.js +17 -5
- package/lib/sync/ui-prefs.d.ts +22 -0
- package/lib/sync/ui-prefs.js +28 -0
- package/lib/sync/webdav/webdav-transport.d.ts +1 -0
- package/lib/sync/webdav/webdav-transport.js +4 -1
- package/lib/ui/artifact-view.js +4 -3
- package/lib/ui/commands.d.ts +1 -1
- package/lib/ui/commands.js +10 -0
- package/lib/ui/diff-view.d.ts +10 -0
- package/lib/ui/diff-view.js +17 -0
- package/lib/ui/disk-usage-view.d.ts +33 -4
- package/lib/ui/disk-usage-view.js +29 -1
- package/lib/ui/errors.d.ts +1 -1
- package/lib/ui/errors.js +4 -3
- package/lib/ui/export-flow.js +26 -20
- package/lib/ui/foreign-view.d.ts +5 -1
- package/lib/ui/foreign-view.js +26 -2
- package/lib/ui/i18n.d.ts +68 -1
- package/lib/ui/i18n.js +120 -2
- package/lib/ui/import-wizard.d.ts +7 -0
- package/lib/ui/import-wizard.js +13 -10
- package/lib/ui/progress.d.ts +2 -2
- package/lib/ui/progress.js +1 -2
- package/lib/ui/report.d.ts +5 -5
- package/lib/ui/report.js +6 -6
- package/lib/ui/rollback-view.d.ts +815 -0
- package/lib/ui/rollback-view.js +738 -0
- package/lib/ui/selection-model.d.ts +41 -0
- package/lib/ui/selection-model.js +30 -0
- package/lib/ui/session-inventory-view.d.ts +97 -3
- package/lib/ui/session-inventory-view.js +76 -2
- package/lib/ui/session-issue-labels.d.ts +43 -0
- package/lib/ui/session-issue-labels.js +48 -0
- package/lib/ui/sync-settings-view.d.ts +33 -0
- package/lib/ui/sync-settings-view.js +40 -0
- package/lib/ui/test-helpers.js +2 -1
- package/lib/ui/types.d.ts +8 -3
- package/lib/ui/types.js +1 -1
- package/lib/utils/atomic-write.js +6 -5
- package/lib/utils/bundle-scan.d.ts +11 -1
- package/lib/utils/bundle-scan.js +12 -1
- package/lib/utils/env-lock.d.ts +10 -0
- package/lib/utils/env-lock.js +73 -46
- package/lib/utils/json.js +6 -5
- package/lib/utils/messages-utils.d.ts +96 -0
- package/lib/utils/messages-utils.js +216 -0
- package/lib/utils/proxy.js +18 -5
- package/lib/utils/recursive-walk.d.ts +22 -0
- package/lib/utils/recursive-walk.js +75 -1
- package/lib/utils/session-log-repair.js +10 -9
- package/lib/utils/zip.js +27 -26
- package/package.json +12 -2
- package/src/adapters/agent-instructions.ts +1 -1
- package/src/adapters/file-collection.test.ts +1 -0
- package/src/adapters/file-collection.ts +1 -1
- package/src/adapters/files.test.ts +27 -0
- package/src/adapters/index.ts +6 -1
- package/src/adapters/link-report.test.ts +22 -2
- package/src/adapters/link-report.ts +29 -4
- package/src/adapters/messages.ts +61 -0
- package/src/adapters/plugin-data.test.ts +225 -0
- package/src/adapters/plugin-data.ts +351 -0
- package/src/adapters/plugin-files.ts +3 -2
- package/src/adapters/plugins.ts +4 -3
- package/src/adapters/prompts.ts +7 -1
- package/src/adapters/self.test.ts +26 -0
- package/src/adapters/self.ts +10 -3
- package/src/adapters/sessions.ts +4 -2
- package/src/adapters/settings.test.ts +38 -1
- package/src/adapters/test-helpers.ts +15 -0
- package/src/adapters/ui.test.ts +2 -2
- package/src/adapters/ui.ts +27 -26
- package/src/adapters/workspaces.ts +7 -3
- package/src/cli/actions.test.ts +28 -0
- package/src/cli/actions.ts +11 -2
- package/src/cli/help.ts +23 -10
- package/src/cli/index-guards.test.ts +193 -0
- package/src/cli/index.ts +106 -31
- package/src/cli/sessions-inspect.test.ts +15 -1
- package/src/cli/sessions-inspect.ts +3 -1
- package/src/cli/sessions-repair.test.ts +84 -0
- package/src/cli/sessions-repair.ts +33 -7
- package/src/cli/web/console-route-parity.test.ts +115 -0
- package/src/cli/web/page.ts +23 -12
- package/src/cli/web/routes.ts +2 -1
- package/src/cli/web/server.ts +4 -4
- package/src/cli/web/web.test.ts +2 -2
- package/src/client/ConfigManagerSection.tsx +30 -2
- package/src/client/about/AboutPanel.tsx +2 -2
- package/src/client/about/PluginUpdateDialog.tsx +1 -1
- package/src/client/about/ReleaseNotesDialog.tsx +2 -2
- package/src/client/about/about-view.test.ts +13 -3
- package/src/client/about/about-view.ts +10 -6
- package/src/client/about/release-notes-view.test.ts +14 -4
- package/src/client/about/release-notes-view.ts +4 -2
- package/src/client/api.ts +18 -6
- package/src/client/chat/ChatEntryView.tsx +411 -0
- package/src/client/chat/RollbackDiffTab.tsx +108 -0
- package/src/client/chat/RollbackFileIcon.tsx +96 -0
- package/src/client/chat/RollbackReviewDialog.tsx +607 -0
- package/src/client/chat/chat-entry-view.test.ts +1174 -0
- package/src/client/chat/chat-entry-view.ts +758 -0
- package/src/client/chat/chat-locales.ts +152 -0
- package/src/client/chat/chat-node-registration.ts +64 -0
- package/src/client/chat/chat-point-window.ts +183 -0
- package/src/client/chat/chat-slots.ts +107 -0
- package/src/client/chat/rollback-diff-tab.test.ts +105 -0
- package/src/client/chat/rollback-diff-tab.ts +174 -0
- package/src/client/chat/rollback-entry-view.test.ts +1469 -0
- package/src/client/chat/rollback-entry-view.ts +946 -0
- package/src/client/chat/sidebar-slots.ts +59 -0
- package/src/client/common/ConfirmDialog.tsx +7 -3
- package/src/client/common/ContentPicker.tsx +48 -10
- package/src/client/common/DiffTable.tsx +133 -0
- package/src/client/common/ErrorBanner.tsx +4 -4
- package/src/client/common/Icon.tsx +2 -0
- package/src/client/common/Modal.tsx +8 -1
- package/src/client/common/ProgressBar.tsx +8 -2
- package/src/client/common/ReportView.tsx +4 -4
- package/src/client/common/RunsCenter.tsx +1 -0
- package/src/client/common/client-route-parity.test.ts +84 -0
- package/src/client/common/info-hint-guard.test.ts +46 -13
- package/src/client/common/picker-lazy-units.test.ts +42 -0
- package/src/client/common/plan-text-redaction.test.ts +17 -6
- package/src/client/common/progress-view-edge.test.ts +8 -7
- package/src/client/common/progress-view.test.ts +9 -9
- package/src/client/common/progress-view.ts +2 -2
- package/src/client/common/routes.ts +38 -0
- package/src/client/common/section-labels.test.ts +1 -0
- package/src/client/common/section-labels.ts +2 -0
- package/src/client/common/ui.tsx +73 -10
- package/src/client/config-manager.module.css +1109 -31
- package/src/client/css-scale-guard.test.ts +30 -0
- package/src/client/environment/EnvironmentPanel.tsx +11 -4
- package/src/client/environment/maintenance/DiskUsageCard.tsx +23 -2
- package/src/client/export/ExportView.tsx +13 -8
- package/src/client/history/HistoryPanel.tsx +4 -3
- package/src/client/history/history-api.ts +2 -2
- package/src/client/history/history-locales.ts +5 -1
- package/src/client/home/HomePanel.tsx +29 -8
- package/src/client/import/ForeignImportView.tsx +4 -5
- package/src/client/import/ImportWizardView.tsx +5 -3
- package/src/client/import/import-wizard-split.test.ts +3 -1
- package/src/client/import/import-wizard-steps.tsx +9 -9
- package/src/client/index.ts +232 -3
- package/src/client/library/LibraryActions.tsx +32 -7
- package/src/client/library/LibraryPanel.tsx +23 -3
- package/src/client/library/RestorePlanView.tsx +9 -66
- package/src/client/locales.ts +62 -9
- package/src/client/lucide-icons.d.ts +1 -0
- package/src/client/market/MarketPanel.tsx +13 -2
- package/src/client/market/MyConfigsView.tsx +5 -4
- package/src/client/market/MyConfigsWizard.tsx +10 -8
- package/src/client/market/market-api.test.ts +9 -8
- package/src/client/market/market-api.ts +2 -2
- package/src/client/market/market-locales.ts +3 -0
- package/src/client/market/market-view.test.ts +6 -6
- package/src/client/market/market-view.ts +3 -3
- package/src/client/market/my-configs-api.ts +2 -2
- package/src/client/market/my-configs-view.test.ts +23 -22
- package/src/client/market/my-configs-view.ts +8 -8
- package/src/client/recovery/IncompleteCopiesSection.tsx +2 -1
- package/src/client/recovery/RecoveryPanel.tsx +442 -283
- package/src/client/recovery/RollbackCard.tsx +859 -0
- package/src/client/recovery/RollbackDiffPane.tsx +69 -0
- package/src/client/recovery/RollbackFileChanges.tsx +163 -0
- package/src/client/recovery/incident-api.ts +2 -2
- package/src/client/recovery/recovery-api.ts +144 -6
- package/src/client/recovery/recovery-locales.test.ts +299 -0
- package/src/client/recovery/recovery-locales.ts +520 -149
- package/src/client/recovery/recovery-panel-layout.test.ts +132 -2
- package/src/client/recovery/rollback-enabled.ts +41 -0
- package/src/client/recovery/rollback-file-changes.test.ts +114 -0
- package/src/client/recovery/session-dialog-mount.test.ts +54 -0
- package/src/client/run-store.ts +84 -21
- package/src/client/sync/AutosyncCard.tsx +25 -1
- package/src/client/sync/ChannelClearConfirmDialog.tsx +6 -3
- package/src/client/sync/ChannelConfigDialog.tsx +10 -10
- package/src/client/sync/DecryptPasswordCard.tsx +2 -2
- package/src/client/sync/SecurityOptionsCard.tsx +27 -17
- package/src/client/sync/SyncChannelEntryCard.tsx +14 -2
- package/src/client/sync/SyncConfirmView.tsx +46 -8
- package/src/client/sync/SyncLogList.tsx +1 -1
- package/src/client/sync/SyncPanel.tsx +31 -23
- package/src/client/sync/SyncRepositoryPicker.tsx +3 -5
- package/src/client/sync/SyncSectionPickerDialog.tsx +11 -2
- package/src/client/sync/SyncSettingsView.test.ts +20 -1
- package/src/client/sync/UnreadableSnapshotsBanner.tsx +1 -1
- package/src/client/sync/WorkDirCard.tsx +112 -0
- package/src/client/sync/history-model.test.ts +42 -9
- package/src/client/sync/history-model.ts +30 -15
- package/src/client/sync/sync-api.test.ts +57 -33
- package/src/client/sync/sync-api.ts +80 -6
- package/src/client/sync/sync-locales.ts +104 -3
- package/src/client/sync/sync-view-v2.test.ts +65 -13
- package/src/client/sync/sync-view.test.ts +107 -52
- package/src/client/sync/sync-view.ts +124 -41
- package/src/core/analyzer.ts +31 -9
- package/src/core/backup-plan.test.ts +48 -5
- package/src/core/backup-plan.ts +51 -20
- package/src/core/backup-verify.ts +24 -23
- package/src/core/backup.test.ts +76 -0
- package/src/core/backup.ts +105 -29
- package/src/core/boot-rescue.ts +4 -3
- package/src/core/boot-safety.ts +2 -1
- package/src/core/command-rollback.test.ts +536 -0
- package/src/core/command-rollback.ts +406 -0
- package/src/core/consult-source.ts +7 -6
- package/src/core/disk-usage.test.ts +257 -1
- package/src/core/disk-usage.ts +134 -14
- package/src/core/engine-locale.test.ts +593 -0
- package/src/core/engine-locale.ts +175 -0
- package/src/core/exporter.ts +3 -3
- package/src/core/fork-reconcile.test.ts +279 -0
- package/src/core/fork-reconcile.ts +234 -0
- package/src/core/journal.ts +6 -5
- package/src/core/local-plugin-host.ts +3 -2
- package/src/core/local-plugin-pack.ts +11 -6
- package/src/core/messages-core.ts +661 -0
- package/src/core/messages.ts +125 -17
- package/src/core/migration-consult.ts +21 -20
- package/src/core/migration-history.ts +19 -5
- package/src/core/model-tools.ts +28 -27
- package/src/core/phase3-host.ts +21 -5
- package/src/core/phase3-p2b-fingerprint.test.ts +22 -0
- package/src/core/plugin-cli.ts +18 -19
- package/src/core/plugin-data-registry.test.ts +113 -0
- package/src/core/plugin-data-registry.ts +261 -0
- package/src/core/reconcile.test.ts +117 -0
- package/src/core/reconcile.ts +85 -17
- package/src/core/recovery-orchestrator.ts +19 -18
- package/src/core/reinstall.ts +26 -21
- package/src/core/restore.ts +20 -17
- package/src/core/rollback-file-diff.test.ts +159 -0
- package/src/core/rollback-gate.test.ts +66 -0
- package/src/core/rollback-gate.ts +61 -0
- package/src/core/rollback-ignore-rules.ts +126 -0
- package/src/core/rollback-ledger.test.ts +655 -0
- package/src/core/rollback-ledger.ts +1314 -0
- package/src/core/rollback-point.test.ts +2853 -0
- package/src/core/rollback-point.ts +4340 -0
- package/src/core/rollback.ts +5 -3
- package/src/core/run-registry.ts +6 -5
- package/src/core/session-health.ts +15 -0
- package/src/core/session-meta.test.ts +117 -1
- package/src/core/session-meta.ts +149 -2
- package/src/core/session-title.test.ts +127 -0
- package/src/core/session-title.ts +189 -0
- package/src/core/startup-barrier.test.ts +69 -0
- package/src/core/startup-barrier.ts +27 -0
- package/src/core/transaction-coordinator.ts +4 -3
- package/src/core/types.ts +37 -1
- package/src/core/validator.ts +5 -4
- package/src/core/verify-recovery.ts +31 -30
- package/src/foreign/bundle.ts +3 -2
- package/src/foreign/continue.ts +2 -1
- package/src/foreign/crush.ts +2 -1
- package/src/foreign/file-budget.test.ts +2 -0
- package/src/foreign/gemini.ts +2 -1
- package/src/foreign/goose.ts +2 -1
- package/src/foreign/kilocode.ts +2 -1
- package/src/foreign/kimi.ts +2 -1
- package/src/foreign/messages.ts +44 -0
- package/src/foreign/mimocode.ts +2 -1
- package/src/foreign/openclaw.ts +2 -1
- package/src/foreign/opencode.ts +2 -1
- package/src/foreign/pi.ts +2 -1
- package/src/foreign/qoder.ts +2 -1
- package/src/foreign/qwen.ts +2 -1
- package/src/foreign/registry.test.ts +1 -1
- package/src/foreign/registry.ts +8 -7
- package/src/foreign/teleagent.ts +2 -1
- package/src/foreign/workbuddy.ts +2 -1
- package/src/foreign/zcode.ts +2 -1
- package/src/foreign/zed.ts +2 -1
- package/src/index.facade.test.ts +340 -1
- package/src/index.ts +876 -57
- package/src/market/git-file-writer.ts +20 -19
- package/src/market/github-repos.test.ts +23 -0
- package/src/market/github-repos.ts +37 -19
- package/src/market/index-parser.ts +31 -30
- package/src/market/messages-market.ts +325 -0
- package/src/market/my-repo.ts +32 -29
- package/src/market/prepare.ts +18 -15
- package/src/market/security.ts +20 -19
- package/src/market/types.ts +2 -1
- package/src/market/url.ts +2 -1
- package/src/migrations/index.ts +6 -5
- package/src/migrations/messages-migrations.ts +59 -0
- package/src/profiles/dsh-profile-launcher.ts +2 -1
- package/src/profiles/dsh-profile-runtime.ts +2 -1
- package/src/profiles/messages.ts +31 -0
- package/src/routes/backup.ts +18 -2
- package/src/routes/import.ts +3 -3
- package/src/routes/index.ts +1 -1
- package/src/routes/kit.ts +9 -3
- package/src/routes/market.ts +1 -1
- package/src/routes/messages.ts +106 -0
- package/src/routes/prefs.ts +31 -2
- package/src/routes/profiles.ts +12 -6
- package/src/routes/recovery.test.ts +405 -1
- package/src/routes/recovery.ts +477 -94
- package/src/routes/snapshots.ts +51 -4
- package/src/routes/sync.ts +82 -17
- package/src/schema/config.ts +53 -20
- package/src/schema/index.ts +7 -5
- package/src/schema/manifest-schema.test.ts +4 -4
- package/src/schema/manifest.ts +19 -18
- package/src/schema/messages.ts +120 -0
- package/src/schema/registry.test.ts +28 -7
- package/src/schema/section-registry.ts +96 -42
- package/src/schema/types.ts +2 -1
- package/src/schema/versions.ts +6 -5
- package/src/security/encryption.ts +16 -15
- package/src/security/integrity.ts +8 -7
- package/src/security/messages.ts +87 -0
- package/src/security/redaction.test.ts +17 -1
- package/src/security/redaction.ts +13 -0
- package/src/security/secret-scanner.ts +19 -12
- package/src/security/security.test.ts +47 -0
- package/src/security/vault.ts +2 -1
- package/src/security/zip-security.ts +4 -3
- package/src/sync/ancestor.ts +2 -1
- package/src/sync/autosync-config.ts +21 -0
- package/src/sync/autosync-scheduler.test.ts +63 -7
- package/src/sync/autosync-scheduler.ts +118 -78
- package/src/sync/backup-files.ts +3 -2
- package/src/sync/backup-scheduler.ts +6 -5
- package/src/sync/blob-store.ts +2 -1
- package/src/sync/git/git-transport.test.ts +170 -8
- package/src/sync/git/git-transport.ts +176 -2
- package/src/sync/github-auth.ts +10 -9
- package/src/sync/layout.ts +13 -12
- package/src/sync/messages.ts +153 -0
- package/src/sync/review-queue.ts +6 -5
- package/src/sync/s3/messages.ts +16 -0
- package/src/sync/s3/s3-providers.ts +9 -6
- package/src/sync/s3/s3-transport.ts +1 -1
- package/src/sync/s3/sigv4.ts +3 -1
- package/src/sync/snapshot-crypto.ts +6 -5
- package/src/sync/snapshot-json.ts +3 -2
- package/src/sync/sync-credentials.test.ts +119 -0
- package/src/sync/sync-engine-download-tombstone.test.ts +1 -1
- package/src/sync/sync-engine.test.ts +226 -8
- package/src/sync/sync-engine.ts +154 -52
- package/src/sync/sync-history.ts +7 -6
- package/src/sync/sync-selection.test.ts +12 -2
- package/src/sync/sync-selection.ts +18 -5
- package/src/sync/transport.test.ts +1 -0
- package/src/sync/ui-prefs.test.ts +44 -1
- package/src/sync/ui-prefs.ts +39 -0
- package/src/sync/webdav/webdav-transport.ts +4 -1
- package/src/ui/artifact-view.ts +4 -3
- package/src/ui/commands.test.ts +1 -0
- package/src/ui/commands.ts +11 -1
- package/src/ui/diff-view.test.ts +9 -1
- package/src/ui/diff-view.ts +22 -0
- package/src/ui/disk-usage-view.test.ts +113 -6
- package/src/ui/disk-usage-view.ts +63 -7
- package/src/ui/errors.ts +4 -3
- package/src/ui/export-flow.ts +26 -20
- package/src/ui/foreign-view.test.ts +43 -0
- package/src/ui/foreign-view.ts +30 -3
- package/src/ui/i18n.ts +131 -2
- package/src/ui/import-wizard.ts +18 -10
- package/src/ui/progress.ts +2 -2
- package/src/ui/report.ts +7 -6
- package/src/ui/rollback-view.test.ts +836 -0
- package/src/ui/rollback-view.ts +1405 -0
- package/src/ui/selection-model.test.ts +33 -1
- package/src/ui/selection-model.ts +53 -0
- package/src/ui/session-inventory-view.test.ts +109 -0
- package/src/ui/session-inventory-view.ts +138 -3
- package/src/ui/session-issue-labels.test.ts +51 -0
- package/src/ui/session-issue-labels.ts +70 -0
- package/src/ui/sync-settings-view.test.ts +36 -0
- package/src/ui/sync-settings-view.ts +49 -0
- package/src/ui/test-helpers.ts +2 -1
- package/src/ui/types.ts +10 -4
- package/src/utils/atomic-write.ts +6 -5
- package/src/utils/bundle-scan.test.ts +2 -2
- package/src/utils/bundle-scan.ts +12 -1
- package/src/utils/env-lock.ts +83 -49
- package/src/utils/json.ts +7 -5
- package/src/utils/messages-utils.ts +232 -0
- package/src/utils/proxy.test.ts +25 -0
- package/src/utils/proxy.ts +19 -5
- package/src/utils/recursive-walk.test.ts +43 -0
- package/src/utils/recursive-walk.ts +75 -1
- package/src/utils/session-log-repair.ts +10 -9
- package/src/utils/zip.ts +27 -26
- package/lib/core/checkpoint.d.ts +0 -362
- package/lib/core/checkpoint.js +0 -1309
- package/src/core/checkpoint.test.ts +0 -665
- package/src/core/checkpoint.ts +0 -1614
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 引擎语言运行时(宿主 / 引擎半)——「英文界面零中文」的引擎侧根因修复。
|
|
3
|
+
*
|
|
4
|
+
* 语义(唯一要记住的一句话):**语言必须在「取文案的那一刻」解析,而不是在「装配的那一刻」**。
|
|
5
|
+
* 改造前的写法是 this.msg = makeMsg(resolveAppLanguage(ctx)):apply() 只在插件加载时执行一次,
|
|
6
|
+
* 于是宿主产出的报告 / 计划 / 错误文本被冻结成加载时那门语言(用户切到 English 后仍是中文)。
|
|
7
|
+
*
|
|
8
|
+
* 本模块提供三件事:
|
|
9
|
+
* 1. 语言运行时:setEngineLocale / setEngineLanguageSource / getEngineLocale /
|
|
10
|
+
* invalidateEngineLocale(函数源按 LANGUAGE_TTL_MS 兜底缓存;取文案时才解析);
|
|
11
|
+
* 2. 消息目录注册:registerMessageCatalog(领域模块追加自己的目录,**仅在既有目录未命中时生效**);
|
|
12
|
+
* 基础目录(src/core/messages.ts 的 zh/en)由该模块经 registerBaseMessageCatalog 反向注册;
|
|
13
|
+
* 3. engineMsg:宿主 / 引擎半的活翻译器(MsgFunc 形状),未知键回退 zh → 键名,绝不抛错。
|
|
14
|
+
*
|
|
15
|
+
* 纪律:
|
|
16
|
+
* - 未设定语言时行为等于 zh(与改造前的缺省逐字一致);
|
|
17
|
+
* - 本模块**不 import src/core/messages.ts**(否则形成循环依赖):基础目录由 messages.ts 反向注册;
|
|
18
|
+
* - 目录查找每次调用现查(目录是常量,成本可忽略);语言只按 LANGUAGE_TTL_MS 缓存。
|
|
19
|
+
*/
|
|
20
|
+
import type { MsgParams } from './msg-types.ts';
|
|
21
|
+
|
|
22
|
+
/** 应用语言(与 DSH settings 的 locale preference 同口径)。 */
|
|
23
|
+
export type LanguageId = 'zh' | 'en';
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* 语言源允许的缓存上界(毫秒)。
|
|
27
|
+
*
|
|
28
|
+
* 只给**非请求路径**(调度器、后台任务)兜底:请求入口应调用 invalidateEngineLocale()
|
|
29
|
+
* 让每个请求都拿到最新语言(见 routes/kit.ts 的注册点)。
|
|
30
|
+
*/
|
|
31
|
+
export const LANGUAGE_TTL_MS = 1000;
|
|
32
|
+
|
|
33
|
+
/** 目录(zh 源 / en 镜像;两本都可选,缺的那本不参与查找)。 */
|
|
34
|
+
export interface MessageCatalog {
|
|
35
|
+
/** 目录标识(诊断用)。 */
|
|
36
|
+
readonly id?: string;
|
|
37
|
+
readonly zh?: Readonly<Record<string, string>>;
|
|
38
|
+
readonly en?: Readonly<Record<string, string>>;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** 语言源:固定值,或「调用期解析」的活源。 */
|
|
42
|
+
export type LanguageSource = LanguageId | (() => LanguageId);
|
|
43
|
+
|
|
44
|
+
const DEFAULT_LANGUAGE: LanguageId = 'zh';
|
|
45
|
+
|
|
46
|
+
let source: LanguageSource = DEFAULT_LANGUAGE;
|
|
47
|
+
let cachedLanguage: LanguageId = DEFAULT_LANGUAGE;
|
|
48
|
+
let cachedAt = 0;
|
|
49
|
+
let cachedValid = false;
|
|
50
|
+
|
|
51
|
+
function normalize(value: unknown): LanguageId {
|
|
52
|
+
return value === 'en' ? 'en' : 'zh';
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** 设定引擎语言(固定值):构造宿主时按 resolveAppLanguage(ctx) 初始化。 */
|
|
56
|
+
export function setEngineLocale(locale: LanguageSource): void {
|
|
57
|
+
source = locale;
|
|
58
|
+
invalidateEngineLocale();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* 注册「调用期解析」的语言活源(宿主用 () => resolveAppLanguage(ctx))。
|
|
63
|
+
*
|
|
64
|
+
* 与 setEngineLocale 同源:后者给构造期的初值,本函数让**之后**每次取文案都能跟随用户切语言。
|
|
65
|
+
*/
|
|
66
|
+
export function setEngineLanguageSource(resolve: () => LanguageId): void {
|
|
67
|
+
setEngineLocale(resolve);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** 强制失效:下一次 getEngineLocale() 必须重新解析(路由入口 / 测试用)。 */
|
|
71
|
+
export function invalidateEngineLocale(): void {
|
|
72
|
+
cachedValid = false;
|
|
73
|
+
cachedAt = 0;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* 当前引擎语言(调用期解析)。
|
|
78
|
+
*
|
|
79
|
+
* 函数源的结果按 LANGUAGE_TTL_MS 缓存;固定值源缓存无副作用。解析失败一律回落 zh(绝不抛错)。
|
|
80
|
+
* now 可注入(单测用假时钟验证 TTL 上界)。
|
|
81
|
+
*/
|
|
82
|
+
export function getEngineLocale(now: number = Date.now()): LanguageId {
|
|
83
|
+
if (cachedValid && now - cachedAt < LANGUAGE_TTL_MS) return cachedLanguage;
|
|
84
|
+
let next = DEFAULT_LANGUAGE;
|
|
85
|
+
try {
|
|
86
|
+
next = normalize(typeof source === 'function' ? source() : source);
|
|
87
|
+
} catch {
|
|
88
|
+
next = cachedValid ? cachedLanguage : DEFAULT_LANGUAGE;
|
|
89
|
+
}
|
|
90
|
+
cachedLanguage = next;
|
|
91
|
+
cachedAt = now;
|
|
92
|
+
cachedValid = true;
|
|
93
|
+
return next;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/* ------------------------------------------------------------- 目录注册 */
|
|
97
|
+
|
|
98
|
+
const registry: MessageCatalog[] = [];
|
|
99
|
+
|
|
100
|
+
let baseZh: Readonly<Record<string, string>> | undefined;
|
|
101
|
+
let baseEn: Readonly<Record<string, string>> | undefined;
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* 注册基础目录(zh 源 / en 镜像)。
|
|
105
|
+
*
|
|
106
|
+
* 由 src/core/messages.ts 在模块初始化时调用一次 —— 这样本模块不必 import 它(避免循环依赖)。
|
|
107
|
+
* 直接 import engine-locale 而不 import messages.ts 的模块,engineMsg 只能拿到注册目录 + 键名。
|
|
108
|
+
*/
|
|
109
|
+
export function registerBaseMessageCatalog(catalogs: {
|
|
110
|
+
readonly zh: Readonly<Record<string, string>>;
|
|
111
|
+
readonly en: Readonly<Record<string, string>>;
|
|
112
|
+
}): void {
|
|
113
|
+
baseZh = catalogs.zh;
|
|
114
|
+
baseEn = catalogs.en;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* 注册一本追加目录(领域模块的键并入既有目录)。
|
|
119
|
+
*
|
|
120
|
+
* **只在既有目录未命中时生效**(makeMsg / engineMsg 的查找顺序:既有目录 → 注册目录 → 键名);
|
|
121
|
+
* 同一键先注册者优先。en 语言下注册目录未命中时再回退注册目录的 zh(与既有 en → zh 同口径)。
|
|
122
|
+
*/
|
|
123
|
+
export function registerMessageCatalog(catalog: MessageCatalog): void {
|
|
124
|
+
registry.push(catalog);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** 已注册的追加目录(只读快照,诊断用)。 */
|
|
128
|
+
export function registeredMessageCatalogs(): readonly MessageCatalog[] {
|
|
129
|
+
return registry;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** 清空追加目录(测试 / 装配隔离用)。 */
|
|
133
|
+
export function resetMessageCatalogs(): void {
|
|
134
|
+
registry.length = 0;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** 注册目录里查一条(未命中 → undefined)。 */
|
|
138
|
+
export function registeredMessage(lang: LanguageId, key: string): string | undefined {
|
|
139
|
+
for (const catalog of registry) {
|
|
140
|
+
const hit = catalog[lang]?.[key];
|
|
141
|
+
if (hit !== undefined) return hit;
|
|
142
|
+
}
|
|
143
|
+
if (lang === 'en') {
|
|
144
|
+
for (const catalog of registry) {
|
|
145
|
+
const hit = catalog.zh?.[key];
|
|
146
|
+
if (hit !== undefined) return hit;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return undefined;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/* --------------------------------------------------------------- 翻译器 */
|
|
153
|
+
|
|
154
|
+
/** 插值:{param} → 形参值;缺参原样保留(便于排查)。与改造前的 messages.ts 逐字同口径。 */
|
|
155
|
+
export function interpolate(template: string, params?: MsgParams): string {
|
|
156
|
+
if (params === undefined) return template;
|
|
157
|
+
return template.replace(/\{(\w+)\}/g, (match, key: string) => {
|
|
158
|
+
const value = params[key];
|
|
159
|
+
return value === undefined ? match : String(value);
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* 活翻译器(宿主 / 引擎半的 MsgFunc):每次调用按 getEngineLocale() 选目录。
|
|
165
|
+
*
|
|
166
|
+
* 查找顺序:既有目录(当前语言)→ en 语言下回退既有 zh → 注册目录(同语言 → zh)→ 键名。
|
|
167
|
+
* 未设定语言时 = zh 目录,行为与改造前逐字一致;未知键回退键名,绝不抛错。
|
|
168
|
+
*/
|
|
169
|
+
export function engineMsg(key: string, params?: MsgParams): string {
|
|
170
|
+
const lang = getEngineLocale();
|
|
171
|
+
const dict = lang === 'en' ? baseEn : baseZh;
|
|
172
|
+
const fallback = lang === 'en' ? baseZh : undefined;
|
|
173
|
+
const template = dict?.[key] ?? fallback?.[key] ?? registeredMessage(lang, key) ?? key;
|
|
174
|
+
return interpolate(template, params);
|
|
175
|
+
}
|
package/src/core/exporter.ts
CHANGED
|
@@ -18,6 +18,7 @@ import { SECTION_JSON_PATHS, SECTION_FILE_PREFIXES, isFileSection } from '../sch
|
|
|
18
18
|
import { DEFAULT_SENSITIVE_RELS, refreshVault } from '../security/vault.ts';
|
|
19
19
|
import { writeZip } from '../utils/zip.ts';
|
|
20
20
|
import { msgOf } from './messages.ts';
|
|
21
|
+
import { engineMsg } from './messages-core.ts';
|
|
21
22
|
import { declareBundledSessionsInWorkspaces, normalizeSessionLimit, sessionIdKey } from './session-select.ts';
|
|
22
23
|
// issue #45 连带匹配的第二判据:工作区 path 的 cwd 目录键(DSH 的 sessionIds 覆盖率很低,见 coupleSessionWorkspaces)
|
|
23
24
|
import { projectKeyOf } from './session-meta.ts';
|
|
@@ -370,11 +371,10 @@ export class Exporter {
|
|
|
370
371
|
warnings.push(this.msg('export.fileSectionSecrets', { section: adapter.id, path: hitPath }));
|
|
371
372
|
}
|
|
372
373
|
// 被截断的文件数必须**显式**汇总告警,否则截断本身又变成静默丢失。
|
|
373
|
-
//
|
|
374
|
-
// (src/core/messages.ts 属他人改动范围);故复用 export.fileSectionSecrets,仅 path 传汇总文案。
|
|
374
|
+
// 文案在 t4 域目录(src/core/messages-core.ts):不再借用 export.fileSectionSecrets 传中文 path 值。
|
|
375
375
|
const truncatedCount = hitPaths.length - warnedPaths.length;
|
|
376
376
|
if (truncatedCount > 0) {
|
|
377
|
-
warnings.push(
|
|
377
|
+
warnings.push(engineMsg('core.exporter.fileSectionSecretsTruncated', { section: adapter.id, count: truncatedCount }));
|
|
378
378
|
}
|
|
379
379
|
}
|
|
380
380
|
}
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* t17 历史 fork 归位:只读盘点 + 逐个归位 + 逐个复核,逐条回执、失败带具体理由。
|
|
3
|
+
*
|
|
4
|
+
* 测试策略:seam 用**带真实状态的小注册表**(成员集合 + 写计数)而不是「返回固定值的桩」——
|
|
5
|
+
* 这样「已归位 ⇒ 幂等不重复写」与「复核不通过 ⇒ 不报成功」才是被真的判据证明的,
|
|
6
|
+
* 而不是把桩的返回值抄一遍。
|
|
7
|
+
*/
|
|
8
|
+
import test from 'node:test'
|
|
9
|
+
import assert from 'node:assert/strict'
|
|
10
|
+
import { readFileSync } from 'node:fs'
|
|
11
|
+
import { fileURLToPath } from 'node:url'
|
|
12
|
+
|
|
13
|
+
import {
|
|
14
|
+
FORK_RECONCILE_RECEIPT_LIMIT,
|
|
15
|
+
reconcileForkSessions,
|
|
16
|
+
type ForkReconcileChild,
|
|
17
|
+
type ForkReconcileOutcome,
|
|
18
|
+
} from './fork-reconcile.ts'
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* 影子工作区注册表:`path` 是真实目录(realpath 判定用不到真实盘 —— 本文件只喂 seam),
|
|
22
|
+
* `members` 是生效成员,`writes` 记录真的写了几次(幂等判据看它)。
|
|
23
|
+
*/
|
|
24
|
+
function fakeWorkspace(input: { id: string; path: string; resolvable: boolean }) {
|
|
25
|
+
return {
|
|
26
|
+
id: input.id,
|
|
27
|
+
path: input.path,
|
|
28
|
+
resolvable: input.resolvable,
|
|
29
|
+
members: new Set<string>(),
|
|
30
|
+
writes: 0,
|
|
31
|
+
attach(childId: string): void {
|
|
32
|
+
this.writes += 1
|
|
33
|
+
this.members.add(childId)
|
|
34
|
+
},
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* seam 工厂:复刻 t10 的判据(成员/血缘/cwd 同目录三档 + 写后复核),但用影子注册表 ——
|
|
40
|
+
* **写后复核就是重新读一遍 members**(真机就是重新读 `list()`)。
|
|
41
|
+
*/
|
|
42
|
+
function makeSeam(input: {
|
|
43
|
+
workspaces: ReturnType<typeof fakeWorkspace>[]
|
|
44
|
+
/** 每条子会话的 cwd(缺省 = 解析不到任何工作区) */
|
|
45
|
+
cwdOf?: Record<string, string>
|
|
46
|
+
/** 强制某条子会话 attach 抛错(模拟「写调用被拒」) */
|
|
47
|
+
rejectOf?: Record<string, string>
|
|
48
|
+
/** 强制某条子会话「写了但复核不含它」(模拟 DSH 没真的落盘) */
|
|
49
|
+
noopOf?: Set<string>
|
|
50
|
+
calls?: ForkReconcileChild[]
|
|
51
|
+
}): (child: ForkReconcileChild) => Promise<ForkReconcileOutcome> {
|
|
52
|
+
return async (child) => {
|
|
53
|
+
input.calls?.push(child)
|
|
54
|
+
// ① 已在账内(幂等:**不重复写**)
|
|
55
|
+
const member = input.workspaces.find((ws) => ws.members.has(child.sessionId))
|
|
56
|
+
if (member !== undefined) return { ok: true, status: 'already', workspaceId: member.id }
|
|
57
|
+
// ② cwd → 已注册工作区(解析不到 ⇒ 具体理由)
|
|
58
|
+
const cwd = input.cwdOf?.[child.sessionId]
|
|
59
|
+
if (cwd === undefined) return { ok: false, reason: 'cwd-unavailable' }
|
|
60
|
+
const target = input.workspaces.find((ws) => ws.path === cwd && ws.resolvable)
|
|
61
|
+
if (target === undefined) return { ok: false, reason: 'no-workspace-for-cwd', detail: cwd }
|
|
62
|
+
// ③ 写(可能被拒 / 可能没落盘)
|
|
63
|
+
const reject = input.rejectOf?.[child.sessionId]
|
|
64
|
+
if (reject !== undefined) return { ok: false, reason: 'attach-rejected', detail: reject }
|
|
65
|
+
if (input.noopOf?.has(child.sessionId) === true) return { ok: false, reason: 'verify-failed', detail: 'child-not-in-any-workspace-sessionIds' }
|
|
66
|
+
target.attach(child.sessionId)
|
|
67
|
+
// ④ 复核:重新读 members(attach 返回成功 ≠ 归位成功)
|
|
68
|
+
const verified = input.workspaces.find((ws) => ws.members.has(child.sessionId))
|
|
69
|
+
return verified === undefined
|
|
70
|
+
? { ok: false, reason: 'verify-failed', detail: 'child-not-in-any-workspace-sessionIds' }
|
|
71
|
+
: { ok: true, status: 'attached', workspaceId: verified.id }
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const HERE = fileURLToPath(new URL('.', import.meta.url))
|
|
76
|
+
|
|
77
|
+
/** 候选清单简写:`kids(['child-1', 'parent-1'])`(subagent 只用于「有意跳过」那条用例)。 */
|
|
78
|
+
const kids = (...pairs: [string, string][]): ForkReconcileChild[] =>
|
|
79
|
+
pairs.map(([sessionId, parentSessionId]) => ({ sessionId, parentSessionId }))
|
|
80
|
+
|
|
81
|
+
test('t17 能解析 ⇒ 归位且复核通过(attached)', async () => {
|
|
82
|
+
const ws = fakeWorkspace({ id: 'ws-1', path: 'D:/proj/a', resolvable: true })
|
|
83
|
+
const result = await reconcileForkSessions({
|
|
84
|
+
childSessions: async () => kids(['child-1', 'parent-1']),
|
|
85
|
+
reconcileOne: makeSeam({ workspaces: [ws], cwdOf: { 'child-1': 'D:/proj/a' } }),
|
|
86
|
+
})
|
|
87
|
+
assert.equal(result.ok, true)
|
|
88
|
+
assert.equal(result.candidates, 1)
|
|
89
|
+
assert.equal(result.attached, 1)
|
|
90
|
+
assert.equal(result.already, 0)
|
|
91
|
+
assert.equal(result.refused, 0)
|
|
92
|
+
assert.deepEqual(result.receipts, [{
|
|
93
|
+
sessionId: 'child-1',
|
|
94
|
+
parentSessionId: 'parent-1',
|
|
95
|
+
status: 'attached',
|
|
96
|
+
reason: 'attached',
|
|
97
|
+
workspaceId: 'ws-1',
|
|
98
|
+
}])
|
|
99
|
+
assert.equal(ws.writes, 1, '真的写了一次')
|
|
100
|
+
assert.deepEqual([...ws.members], ['child-1'], '复核读的就是它(写后重读)')
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
test('t17 解析不到 ⇒ refused 且带具体理由(绝不猜工作区)', async () => {
|
|
104
|
+
const ws = fakeWorkspace({ id: 'ws-1', path: 'D:/proj/a', resolvable: true })
|
|
105
|
+
const result = await reconcileForkSessions({
|
|
106
|
+
childSessions: async () => kids(['child-elsewhere', 'parent-1'], ['child-nocwd', 'parent-1']),
|
|
107
|
+
reconcileOne: makeSeam({ workspaces: [ws], cwdOf: { 'child-elsewhere': 'D:/other/place' } }),
|
|
108
|
+
})
|
|
109
|
+
assert.equal(result.ok, true)
|
|
110
|
+
assert.equal(result.candidates, 2)
|
|
111
|
+
assert.equal(result.attached, 0)
|
|
112
|
+
assert.equal(result.refused, 2)
|
|
113
|
+
assert.deepEqual(result.reasonCounts, { 'no-workspace-for-cwd': 1, 'cwd-unavailable': 1 })
|
|
114
|
+
assert.equal(ws.writes, 0, '一条都不许猜着写')
|
|
115
|
+
assert.deepEqual(result.receipts.map((r) => [r.sessionId, r.status, r.reason]), [
|
|
116
|
+
['child-elsewhere', 'refused', 'no-workspace-for-cwd'],
|
|
117
|
+
['child-nocwd', 'refused', 'cwd-unavailable'],
|
|
118
|
+
])
|
|
119
|
+
assert.equal(result.receipts[0]?.detail, 'D:/other/place', '理由要能定位(带路径)')
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
test('t17 已归位 ⇒ already,且**幂等不重复写**(连跑两次第二次零写)', async () => {
|
|
123
|
+
const ws = fakeWorkspace({ id: 'ws-1', path: 'D:/proj/a', resolvable: true })
|
|
124
|
+
const calls: ForkReconcileChild[] = []
|
|
125
|
+
const deps = {
|
|
126
|
+
childSessions: async () => kids(['child-1', 'parent-1']),
|
|
127
|
+
reconcileOne: makeSeam({ workspaces: [ws], cwdOf: { 'child-1': 'D:/proj/a' }, calls }),
|
|
128
|
+
}
|
|
129
|
+
const first = await reconcileForkSessions(deps)
|
|
130
|
+
assert.equal(first.attached, 1)
|
|
131
|
+
assert.equal(ws.writes, 1)
|
|
132
|
+
const second = await reconcileForkSessions(deps)
|
|
133
|
+
assert.equal(second.already, 1, '第二次必须报 already(不是又写一次)')
|
|
134
|
+
assert.equal(second.attached, 0)
|
|
135
|
+
assert.equal(ws.writes, 1, '幂等:attach 只写了一次')
|
|
136
|
+
assert.deepEqual(second.receipts[0]?.reason, 'already-attached')
|
|
137
|
+
assert.deepEqual(calls.map((c) => c.sessionId), ['child-1', 'child-1'], '同一条会话每次盘点各问一次,绝不重复写')
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
test('t17 复核不通过 ⇒ 报失败(refused/verify-failed),绝不报成功', async () => {
|
|
141
|
+
const ws = fakeWorkspace({ id: 'ws-1', path: 'D:/proj/a', resolvable: true })
|
|
142
|
+
const result = await reconcileForkSessions({
|
|
143
|
+
childSessions: async () => kids(['child-ghost', 'parent-1']),
|
|
144
|
+
reconcileOne: makeSeam({ workspaces: [ws], cwdOf: { 'child-ghost': 'D:/proj/a' }, noopOf: new Set(['child-ghost']) }),
|
|
145
|
+
})
|
|
146
|
+
assert.equal(result.attached, 0, '写调用没抛错但复核不含它 ⇒ 不许报成功')
|
|
147
|
+
assert.equal(result.refused, 1)
|
|
148
|
+
assert.deepEqual(result.reasonCounts, { 'verify-failed': 1 })
|
|
149
|
+
assert.equal(result.receipts[0]?.status, 'refused')
|
|
150
|
+
assert.equal(ws.writes, 0, 'noop 档连写都没发生(判据在 seam 内,本用例只证明 core 不把它算成功)')
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
test('t17 写被拒 ⇒ refused/attach-rejected 带 detail(失败不静默)', async () => {
|
|
154
|
+
const ws = fakeWorkspace({ id: 'ws-1', path: 'D:/proj/a', resolvable: true })
|
|
155
|
+
const logs: string[] = []
|
|
156
|
+
const result = await reconcileForkSessions({
|
|
157
|
+
childSessions: async () => kids(['child-1', 'parent-1']),
|
|
158
|
+
reconcileOne: makeSeam({ workspaces: [ws], cwdOf: { 'child-1': 'D:/proj/a' }, rejectOf: { 'child-1': 'workspace write failed' } }),
|
|
159
|
+
log: (line) => logs.push(line),
|
|
160
|
+
})
|
|
161
|
+
assert.equal(result.refused, 1)
|
|
162
|
+
assert.deepEqual(result.reasonCounts, { 'attach-rejected': 1 })
|
|
163
|
+
assert.equal(result.receipts[0]?.detail, 'workspace write failed')
|
|
164
|
+
assert.equal(logs.some((line) => line.includes('refused: attach-rejected')), true, '逐条失败必须留痕')
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
test('t17 候选只认「有父会话的子会话」:无父/空 id/自环的噪声项只计入 scanned', async () => {
|
|
168
|
+
const ws = fakeWorkspace({ id: 'ws-1', path: 'D:/proj/a', resolvable: true })
|
|
169
|
+
const calls: ForkReconcileChild[] = []
|
|
170
|
+
const result = await reconcileForkSessions({
|
|
171
|
+
childSessions: async () => kids(
|
|
172
|
+
['child-1', 'parent-1'],
|
|
173
|
+
['child-1', 'parent-1'], // 重复项只处理一次
|
|
174
|
+
['root-1', ''], // 无父
|
|
175
|
+
['', 'parent-1'], // 空 id
|
|
176
|
+
['self-1', 'self-1'], // 自环
|
|
177
|
+
),
|
|
178
|
+
reconcileOne: makeSeam({ workspaces: [ws], cwdOf: { 'child-1': 'D:/proj/a' }, calls }),
|
|
179
|
+
})
|
|
180
|
+
assert.equal(result.scanned, 5, 'scanned = 原始清单条数(含噪声项)')
|
|
181
|
+
assert.equal(result.candidates, 1, '只有 child-1 是候选')
|
|
182
|
+
assert.deepEqual(calls.map((c) => c.sessionId), ['child-1'])
|
|
183
|
+
assert.equal(result.attached, 1)
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
test('t17 一条抛错不影响其余(收成 refused/unexpected-error 且逐条互不影响)', async () => {
|
|
187
|
+
const ws = fakeWorkspace({ id: 'ws-1', path: 'D:/proj/a', resolvable: true })
|
|
188
|
+
const base = makeSeam({ workspaces: [ws], cwdOf: { 'child-2': 'D:/proj/a' } })
|
|
189
|
+
const result = await reconcileForkSessions({
|
|
190
|
+
childSessions: async () => kids(['child-boom', 'parent-1'], ['child-2', 'parent-1']),
|
|
191
|
+
reconcileOne: async (child) => {
|
|
192
|
+
if (child.sessionId === 'child-boom') throw new Error('registry exploded')
|
|
193
|
+
return await base(child)
|
|
194
|
+
},
|
|
195
|
+
})
|
|
196
|
+
assert.equal(result.ok, true)
|
|
197
|
+
assert.equal(result.attached, 1, '后面那条照常归位')
|
|
198
|
+
assert.equal(result.refused, 1)
|
|
199
|
+
assert.deepEqual(result.reasonCounts, { 'unexpected-error': 1 })
|
|
200
|
+
assert.equal(result.receipts[0]?.detail, 'registry exploded')
|
|
201
|
+
})
|
|
202
|
+
|
|
203
|
+
test('t17 盘点读不到父子关系 ⇒ ok:false + error(**不是**「本机没有子会话」)', async () => {
|
|
204
|
+
const result = await reconcileForkSessions({
|
|
205
|
+
childSessions: async () => { throw new Error('session store not ready') },
|
|
206
|
+
reconcileOne: async () => ({ ok: true, status: 'attached' }),
|
|
207
|
+
})
|
|
208
|
+
assert.equal(result.ok, false)
|
|
209
|
+
assert.equal(result.candidates, 0)
|
|
210
|
+
assert.match(String(result.error), /child-sessions-unavailable: session store not ready/)
|
|
211
|
+
assert.deepEqual(result.receipts, [])
|
|
212
|
+
})
|
|
213
|
+
|
|
214
|
+
test('t17 默认口径:**全部**有父会话的子会话都是候选(origin=subagent 也照做,真机 50/58 条 fork 带这个标记)', async () => {
|
|
215
|
+
const ws = fakeWorkspace({ id: 'ws-1', path: 'D:/proj/a', resolvable: true })
|
|
216
|
+
const calls: ForkReconcileChild[] = []
|
|
217
|
+
const result = await reconcileForkSessions({
|
|
218
|
+
childSessions: async () => [
|
|
219
|
+
{ sessionId: 'fork-1', parentSessionId: 'parent-1' },
|
|
220
|
+
{ sessionId: 'fork-2', parentSessionId: 'parent-1', subagent: true },
|
|
221
|
+
],
|
|
222
|
+
reconcileOne: makeSeam({ workspaces: [ws], cwdOf: { 'fork-1': 'D:/proj/a', 'fork-2': 'D:/proj/a' }, calls }),
|
|
223
|
+
})
|
|
224
|
+
assert.equal(result.candidates, 2, 'subagent 标记不得让回退 fork 掉队(真机 50/58 条就带它)')
|
|
225
|
+
assert.deepEqual(result.skipped, { subagent: 0 })
|
|
226
|
+
assert.deepEqual(calls.map((c) => c.sessionId), ['fork-1', 'fork-2'])
|
|
227
|
+
assert.equal(result.attached, 2)
|
|
228
|
+
})
|
|
229
|
+
|
|
230
|
+
test('t17 显式窄化(skipSubagentSessions: true):跳过的条数如实计数、不静默', async () => {
|
|
231
|
+
const ws = fakeWorkspace({ id: 'ws-1', path: 'D:/proj/a', resolvable: true })
|
|
232
|
+
const calls: ForkReconcileChild[] = []
|
|
233
|
+
const logs: string[] = []
|
|
234
|
+
const result = await reconcileForkSessions({
|
|
235
|
+
childSessions: async () => [
|
|
236
|
+
{ sessionId: 'fork-1', parentSessionId: 'parent-1' },
|
|
237
|
+
{ sessionId: 'sub-1', parentSessionId: 'parent-1', subagent: true },
|
|
238
|
+
{ sessionId: 'sub-2', parentSessionId: 'parent-1', subagent: true },
|
|
239
|
+
],
|
|
240
|
+
reconcileOne: makeSeam({ workspaces: [ws], cwdOf: { 'fork-1': 'D:/proj/a' }, calls }),
|
|
241
|
+
log: (line) => logs.push(line),
|
|
242
|
+
}, { skipSubagentSessions: true })
|
|
243
|
+
assert.equal(result.scanned, 3)
|
|
244
|
+
assert.equal(result.candidates, 1)
|
|
245
|
+
assert.deepEqual(result.skipped, { subagent: 2 }, '跳过必须如实计数(不是静默丢弃)')
|
|
246
|
+
assert.deepEqual(calls.map((c) => c.sessionId), ['fork-1'], '一条子代理会话都没被碰')
|
|
247
|
+
assert.equal(result.attached, 1)
|
|
248
|
+
assert.equal(result.refused, 0)
|
|
249
|
+
assert.equal(logs.some((line) => line.includes('skipped 2 subagent sessions')), true, '跳过要有留痕')
|
|
250
|
+
})
|
|
251
|
+
|
|
252
|
+
test('t17 回执有界但计数精确(截断如实标记)', async () => {
|
|
253
|
+
const ws = fakeWorkspace({ id: 'ws-1', path: 'D:/proj/a', resolvable: true })
|
|
254
|
+
const relations: ForkReconcileChild[] = []
|
|
255
|
+
const cwdOf: Record<string, string> = {}
|
|
256
|
+
const total = FORK_RECONCILE_RECEIPT_LIMIT + 25
|
|
257
|
+
for (let i = 0; i < total; i += 1) {
|
|
258
|
+
const id = 'child-' + String(i)
|
|
259
|
+
relations.push({ sessionId: id, parentSessionId: 'parent-1' })
|
|
260
|
+
if (i % 5 !== 0) cwdOf[id] = 'D:/proj/a' // 1/5 解析不到
|
|
261
|
+
}
|
|
262
|
+
const result = await reconcileForkSessions({
|
|
263
|
+
childSessions: async () => relations,
|
|
264
|
+
reconcileOne: makeSeam({ workspaces: [ws], cwdOf }),
|
|
265
|
+
})
|
|
266
|
+
assert.equal(result.candidates, total)
|
|
267
|
+
assert.equal(result.attached + result.already + result.refused, total, '计数精确')
|
|
268
|
+
assert.equal(result.receipts.length, FORK_RECONCILE_RECEIPT_LIMIT, '明细有界')
|
|
269
|
+
assert.equal(result.receiptsTruncated, true)
|
|
270
|
+
})
|
|
271
|
+
|
|
272
|
+
test('t17 只读纪律:core 不碰文件系统(源码级),候选里也没有「删除/清理」这种动作', () => {
|
|
273
|
+
const source = readFileSync(new URL('./fork-reconcile.ts', import.meta.url), 'utf8')
|
|
274
|
+
assert.ok(!/from 'node:/.test(source), 'core 只编排,IO 全在 seam 里(绝不自己读盘/写盘)')
|
|
275
|
+
assert.ok(!/rmSync|rmdirSync|unlinkSync|fs\.rm\(|fs\.unlink\(|writeFileSync|appendFileSync/.test(source), '绝不删除、绝不改写任何东西')
|
|
276
|
+
assert.ok(!/session-log|readLogHeader|zstd/.test(source), '绝不碰会话日志字节(连读都不读)')
|
|
277
|
+
// 本文件(测试)也不许碰真实盘:只读源码文本 + 内存夹具
|
|
278
|
+
assert.equal(HERE.length > 0, true)
|
|
279
|
+
})
|