dsh-config-manager 0.1.69 → 0.1.71
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 +59 -18
- package/README.zh-CN.md +29 -9
- package/lib/adapters/credentials.js +6 -3
- package/lib/adapters/file-collection.d.ts +21 -1
- package/lib/adapters/file-collection.js +32 -8
- package/lib/adapters/mcp.d.ts +12 -2
- package/lib/adapters/mcp.js +30 -13
- package/lib/adapters/plugin-files.d.ts +8 -0
- package/lib/adapters/plugin-files.js +36 -4
- package/lib/adapters/plugins.d.ts +28 -2
- package/lib/adapters/plugins.js +204 -55
- package/lib/adapters/prompts.d.ts +12 -2
- package/lib/adapters/prompts.js +34 -19
- package/lib/adapters/providers.js +5 -4
- package/lib/adapters/self.d.ts +20 -0
- package/lib/adapters/self.js +73 -0
- package/lib/adapters/sessions.d.ts +8 -0
- package/lib/adapters/sessions.js +47 -1
- package/lib/adapters/settings.d.ts +21 -3
- package/lib/adapters/settings.js +44 -7
- package/lib/adapters/skills.d.ts +36 -4
- package/lib/adapters/skills.js +222 -3
- package/lib/adapters/test-helpers.d.ts +31 -2
- package/lib/adapters/test-helpers.js +42 -5
- package/lib/adapters/workspaces.js +97 -5
- package/lib/cli/actions.d.ts +50 -3
- package/lib/cli/actions.js +103 -11
- package/lib/cli/help.d.ts +65 -0
- package/lib/cli/help.js +517 -0
- package/lib/cli/import-source.d.ts +32 -0
- package/lib/cli/import-source.js +178 -0
- package/lib/cli/index.d.ts +18 -2
- package/lib/cli/index.js +118 -90
- package/lib/cli/web/client-script.d.ts +8 -0
- package/lib/cli/web/client-script.js +11 -0
- package/lib/cli/web/http.d.ts +5 -0
- package/lib/cli/web/http.js +2 -1
- package/lib/cli/web/page.d.ts +16 -6
- package/lib/cli/web/page.js +102 -25
- package/lib/cli/web/routes.js +24 -5
- package/lib/cli/web/server.js +23 -2
- package/lib/client.d.ts +1019 -36
- package/lib/client.js +5522 -1564
- package/lib/core/analyzer.d.ts +15 -1
- package/lib/core/analyzer.js +114 -13
- package/lib/core/backup-plan.js +17 -1
- package/lib/core/backup.d.ts +8 -0
- package/lib/core/backup.js +28 -6
- package/lib/core/cache-cleaner.d.ts +10 -0
- package/lib/core/cache-cleaner.js +63 -22
- package/lib/core/checkpoint.d.ts +362 -0
- package/lib/core/checkpoint.js +1309 -0
- package/lib/core/journal.d.ts +37 -0
- package/lib/core/journal.js +69 -0
- package/lib/core/messages.d.ts +13 -0
- package/lib/core/messages.js +26 -0
- package/lib/core/model-tools.js +3 -0
- package/lib/core/patch-layers.d.ts +83 -0
- package/lib/core/patch-layers.js +110 -0
- package/lib/core/restore.d.ts +21 -0
- package/lib/core/restore.js +37 -3
- package/lib/core/rollback.js +6 -3
- package/lib/core/self-update.d.ts +52 -0
- package/lib/core/self-update.js +70 -0
- package/lib/core/session-health.d.ts +29 -5
- package/lib/core/session-health.js +28 -3
- package/lib/core/types.d.ts +126 -0
- package/lib/foreign/antigravity.d.ts +28 -0
- package/lib/foreign/antigravity.js +157 -0
- package/lib/foreign/bundle.d.ts +25 -0
- package/lib/foreign/bundle.js +79 -0
- package/lib/foreign/chatgpt.d.ts +6 -0
- package/lib/foreign/chatgpt.js +25 -0
- package/lib/foreign/claude-code.d.ts +3 -0
- package/lib/foreign/claude-code.js +85 -0
- package/lib/foreign/claude-sessions.d.ts +108 -0
- package/lib/foreign/claude-sessions.js +299 -0
- package/lib/foreign/cline.d.ts +6 -0
- package/lib/foreign/cline.js +27 -0
- package/lib/foreign/codex.d.ts +83 -0
- package/lib/foreign/codex.js +348 -0
- package/lib/foreign/continue.d.ts +9 -0
- package/lib/foreign/continue.js +65 -0
- package/lib/foreign/copilot.d.ts +39 -0
- package/lib/foreign/copilot.js +95 -0
- package/lib/foreign/crush.d.ts +11 -0
- package/lib/foreign/crush.js +57 -0
- package/lib/foreign/cursor.d.ts +87 -0
- package/lib/foreign/cursor.js +171 -0
- package/lib/foreign/dsh.d.ts +30 -0
- package/lib/foreign/dsh.js +101 -0
- package/lib/foreign/dsh4.d.ts +3 -0
- package/lib/foreign/dsh4.js +13 -0
- package/lib/foreign/gemini.d.ts +7 -0
- package/lib/foreign/gemini.js +44 -0
- package/lib/foreign/goose.d.ts +11 -0
- package/lib/foreign/goose.js +53 -0
- package/lib/foreign/grokbuild.d.ts +8 -0
- package/lib/foreign/grokbuild.js +25 -0
- package/lib/foreign/hermes.d.ts +2 -0
- package/lib/foreign/hermes.js +78 -0
- package/lib/foreign/kernel.d.ts +132 -0
- package/lib/foreign/kernel.js +569 -0
- package/lib/foreign/kilocode.d.ts +11 -0
- package/lib/foreign/kilocode.js +51 -0
- package/lib/foreign/kimi.d.ts +7 -0
- package/lib/foreign/kimi.js +47 -0
- package/lib/foreign/mimocode.d.ts +11 -0
- package/lib/foreign/mimocode.js +51 -0
- package/lib/foreign/openclaw.d.ts +7 -0
- package/lib/foreign/openclaw.js +46 -0
- package/lib/foreign/opencode.d.ts +11 -0
- package/lib/foreign/opencode.js +52 -0
- package/lib/foreign/pi.d.ts +7 -0
- package/lib/foreign/pi.js +48 -0
- package/lib/foreign/platform-paths.d.ts +66 -0
- package/lib/foreign/platform-paths.js +141 -0
- package/lib/foreign/qoder.d.ts +10 -0
- package/lib/foreign/qoder.js +49 -0
- package/lib/foreign/qwen.d.ts +7 -0
- package/lib/foreign/qwen.js +45 -0
- package/lib/foreign/read-antigravity.d.ts +57 -0
- package/lib/foreign/read-antigravity.js +504 -0
- package/lib/foreign/read-chatgpt.d.ts +20 -0
- package/lib/foreign/read-chatgpt.js +509 -0
- package/lib/foreign/read-claude-code.d.ts +24 -0
- package/lib/foreign/read-claude-code.js +234 -0
- package/lib/foreign/read-cline.d.ts +53 -0
- package/lib/foreign/read-cline.js +479 -0
- package/lib/foreign/read-codex.d.ts +75 -0
- package/lib/foreign/read-codex.js +842 -0
- package/lib/foreign/read-continue.d.ts +87 -0
- package/lib/foreign/read-continue.js +489 -0
- package/lib/foreign/read-copilot.d.ts +42 -0
- package/lib/foreign/read-copilot.js +267 -0
- package/lib/foreign/read-crush.d.ts +54 -0
- package/lib/foreign/read-crush.js +478 -0
- package/lib/foreign/read-cursor.d.ts +141 -0
- package/lib/foreign/read-cursor.js +705 -0
- package/lib/foreign/read-dsh.d.ts +43 -0
- package/lib/foreign/read-dsh.js +205 -0
- package/lib/foreign/read-dsh4.d.ts +14 -0
- package/lib/foreign/read-dsh4.js +27 -0
- package/lib/foreign/read-gemini.d.ts +55 -0
- package/lib/foreign/read-gemini.js +273 -0
- package/lib/foreign/read-goose.d.ts +25 -0
- package/lib/foreign/read-goose.js +322 -0
- package/lib/foreign/read-grokbuild.d.ts +34 -0
- package/lib/foreign/read-grokbuild.js +455 -0
- package/lib/foreign/read-hermes.d.ts +74 -0
- package/lib/foreign/read-hermes.js +690 -0
- package/lib/foreign/read-kilocode.d.ts +17 -0
- package/lib/foreign/read-kilocode.js +46 -0
- package/lib/foreign/read-kimi.d.ts +63 -0
- package/lib/foreign/read-kimi.js +719 -0
- package/lib/foreign/read-mimocode.d.ts +12 -0
- package/lib/foreign/read-mimocode.js +56 -0
- package/lib/foreign/read-openclaw.d.ts +51 -0
- package/lib/foreign/read-openclaw.js +445 -0
- package/lib/foreign/read-opencode.d.ts +150 -0
- package/lib/foreign/read-opencode.js +896 -0
- package/lib/foreign/read-pi.d.ts +56 -0
- package/lib/foreign/read-pi.js +411 -0
- package/lib/foreign/read-qoder.d.ts +54 -0
- package/lib/foreign/read-qoder.js +317 -0
- package/lib/foreign/read-qwen.d.ts +40 -0
- package/lib/foreign/read-qwen.js +326 -0
- package/lib/foreign/read-reasonix.d.ts +66 -0
- package/lib/foreign/read-reasonix.js +537 -0
- package/lib/foreign/read-teleagent.d.ts +15 -0
- package/lib/foreign/read-teleagent.js +68 -0
- package/lib/foreign/read-trae.d.ts +67 -0
- package/lib/foreign/read-trae.js +492 -0
- package/lib/foreign/read-vibe.d.ts +28 -0
- package/lib/foreign/read-vibe.js +364 -0
- package/lib/foreign/read-workbuddy.d.ts +49 -0
- package/lib/foreign/read-workbuddy.js +346 -0
- package/lib/foreign/read-zcode.d.ts +12 -0
- package/lib/foreign/read-zcode.js +38 -0
- package/lib/foreign/read-zed.d.ts +30 -0
- package/lib/foreign/read-zed.js +473 -0
- package/lib/foreign/reasonix.d.ts +6 -0
- package/lib/foreign/reasonix.js +20 -0
- package/lib/foreign/registry.d.ts +196 -0
- package/lib/foreign/registry.js +573 -0
- package/lib/foreign/session-ir.d.ts +186 -0
- package/lib/foreign/session-ir.js +427 -0
- package/lib/foreign/session-read.d.ts +102 -0
- package/lib/foreign/session-read.js +254 -0
- package/lib/foreign/session-source.d.ts +216 -0
- package/lib/foreign/session-source.js +453 -0
- package/lib/foreign/source-modules.d.ts +45 -0
- package/lib/foreign/source-modules.js +52 -0
- package/lib/foreign/sqlite.d.ts +61 -0
- package/lib/foreign/sqlite.js +404 -0
- package/lib/foreign/teleagent.d.ts +11 -0
- package/lib/foreign/teleagent.js +53 -0
- package/lib/foreign/trae.d.ts +6 -0
- package/lib/foreign/trae.js +20 -0
- package/lib/foreign/truth-table.d.ts +62 -0
- package/lib/foreign/truth-table.js +333 -0
- package/lib/foreign/types.d.ts +272 -0
- package/lib/foreign/types.js +2 -0
- package/lib/foreign/vibe.d.ts +6 -0
- package/lib/foreign/vibe.js +23 -0
- package/lib/foreign/workbuddy.d.ts +7 -0
- package/lib/foreign/workbuddy.js +45 -0
- package/lib/foreign/zcode.d.ts +11 -0
- package/lib/foreign/zcode.js +51 -0
- package/lib/foreign/zed.d.ts +11 -0
- package/lib/foreign/zed.js +53 -0
- package/lib/index.d.ts +154 -13
- package/lib/index.js +661 -84
- package/lib/market/github-repos.d.ts +28 -0
- package/lib/market/github-repos.js +43 -1
- package/lib/market/types.d.ts +1 -3
- package/lib/profiles/dsh-profile-manager.d.ts +54 -2
- package/lib/profiles/dsh-profile-manager.js +182 -11
- package/lib/profiles/dsh-profile-shared.d.ts +47 -0
- package/lib/profiles/dsh-profile-shared.js +15 -1
- package/lib/profiles/index.d.ts +1 -1
- package/lib/profiles/index.js +1 -1
- package/lib/routes/consult.js +51 -5
- package/lib/routes/foreign-context.d.ts +43 -0
- package/lib/routes/foreign-context.js +65 -0
- package/lib/routes/foreign.d.ts +11 -0
- package/lib/routes/foreign.js +208 -0
- package/lib/routes/import.d.ts +8 -0
- package/lib/routes/import.js +22 -1
- package/lib/routes/index.d.ts +2 -2
- package/lib/routes/index.js +4 -0
- package/lib/routes/kit.d.ts +4 -3
- package/lib/routes/market.js +0 -1
- package/lib/routes/prefs.d.ts +2 -1
- package/lib/routes/prefs.js +14 -2
- package/lib/routes/recovery.js +363 -3
- package/lib/routes/session-export.d.ts +117 -0
- package/lib/routes/session-export.js +270 -0
- package/lib/routes/snapshots.js +12 -2
- package/lib/routes/sync.js +82 -10
- package/lib/security/redaction.d.ts +14 -0
- package/lib/security/redaction.js +24 -9
- package/lib/security/secret-scanner.d.ts +27 -0
- package/lib/security/secret-scanner.js +54 -1
- package/lib/sync/autosync-scheduler.d.ts +13 -1
- package/lib/sync/autosync-scheduler.js +49 -26
- package/lib/sync/backup-files.js +6 -0
- package/lib/sync/gist/gist-transport.d.ts +91 -0
- package/lib/sync/gist/gist-transport.js +406 -0
- package/lib/sync/gist/messages.d.ts +22 -0
- package/lib/sync/gist/messages.js +46 -0
- package/lib/sync/s3/messages.d.ts +53 -0
- package/lib/sync/s3/messages.js +83 -0
- package/lib/sync/s3/s3-providers.d.ts +78 -0
- package/lib/sync/s3/s3-providers.js +156 -0
- package/lib/sync/s3/s3-transport.d.ts +126 -0
- package/lib/sync/s3/s3-transport.js +551 -0
- package/lib/sync/s3/sigv4.d.ts +107 -0
- package/lib/sync/s3/sigv4.js +196 -0
- package/lib/sync/sync-config.d.ts +250 -4
- package/lib/sync/sync-config.js +658 -57
- package/lib/sync/sync-engine.d.ts +1 -0
- package/lib/sync/sync-engine.js +4 -2
- package/lib/sync/sync-history.d.ts +1 -1
- package/lib/sync/webdav/webdav-transport.d.ts +31 -3
- package/lib/sync/webdav/webdav-transport.js +102 -18
- package/lib/ui/commands.d.ts +4 -3
- package/lib/ui/commands.js +41 -9
- package/lib/ui/disk-usage-view.d.ts +44 -0
- package/lib/ui/disk-usage-view.js +55 -1
- package/lib/ui/dsh-profiles-view.d.ts +53 -0
- package/lib/ui/dsh-profiles-view.js +56 -0
- package/lib/ui/foreign-view.d.ts +162 -0
- package/lib/ui/foreign-view.js +225 -0
- package/lib/ui/i18n.d.ts +77 -0
- package/lib/ui/i18n.js +164 -0
- package/lib/ui/session-inventory-view.d.ts +106 -0
- package/lib/ui/session-inventory-view.js +58 -0
- package/lib/ui/session-layout-view.d.ts +265 -0
- package/lib/ui/session-layout-view.js +271 -0
- package/lib/ui/sync-repository-picker.d.ts +98 -0
- package/lib/ui/sync-repository-picker.js +145 -0
- package/lib/ui/sync-settings-view.d.ts +143 -4
- package/lib/ui/sync-settings-view.js +231 -7
- package/lib/ui/types.d.ts +47 -0
- package/lib/utils/asar-read.d.ts +113 -0
- package/lib/utils/asar-read.js +523 -0
- package/lib/utils/env-lock.js +7 -2
- package/lib/utils/patch-yaml.d.ts +25 -0
- package/lib/utils/patch-yaml.js +62 -0
- package/lib/utils/paths.d.ts +10 -1
- package/lib/utils/paths.js +24 -3
- package/lib/utils/recursive-walk.js +12 -13
- package/lib/utils/session-format.d.ts +35 -2
- package/lib/utils/session-format.js +131 -5
- package/lib/utils/session-health-scan.d.ts +7 -1
- package/lib/utils/session-health-scan.js +369 -12
- package/lib/utils/session-layout-repair-service.d.ts +98 -0
- package/lib/utils/session-layout-repair-service.js +457 -0
- package/lib/utils/session-log-repair.d.ts +27 -0
- package/lib/utils/session-log-repair.js +233 -62
- package/lib/utils/session-repair-service.d.ts +32 -3
- package/lib/utils/session-repair-service.js +148 -9
- package/lib/utils/session-row-facts.d.ts +73 -0
- package/lib/utils/session-row-facts.js +182 -0
- package/lib/utils/session-verify.d.ts +118 -0
- package/lib/utils/session-verify.js +705 -0
- package/lib/utils/shared-constants.d.ts +59 -0
- package/lib/utils/shared-constants.js +71 -0
- package/package.json +1 -1
- package/src/adapters/credentials.test.ts +27 -0
- package/src/adapters/credentials.ts +6 -3
- package/src/adapters/file-collection.ts +33 -8
- package/src/adapters/files.test.ts +222 -0
- package/src/adapters/mcp.test.ts +43 -0
- package/src/adapters/mcp.ts +37 -14
- package/src/adapters/plugin-files.ts +34 -4
- package/src/adapters/plugins.test.ts +241 -0
- package/src/adapters/plugins.ts +201 -53
- package/src/adapters/prompts.test.ts +49 -0
- package/src/adapters/prompts.ts +41 -20
- package/src/adapters/providers.test.ts +16 -0
- package/src/adapters/providers.ts +4 -3
- package/src/adapters/self.test.ts +77 -0
- package/src/adapters/self.ts +75 -0
- package/src/adapters/sessions.test.ts +77 -0
- package/src/adapters/sessions.ts +47 -1
- package/src/adapters/settings.test.ts +78 -0
- package/src/adapters/settings.ts +42 -6
- package/src/adapters/skills.ts +221 -3
- package/src/adapters/test-helpers.ts +43 -5
- package/src/adapters/workspaces-dup-id.test.ts +144 -0
- package/src/adapters/workspaces.test.ts +10 -0
- package/src/adapters/workspaces.ts +97 -5
- package/src/cli/actions.test.ts +117 -2
- package/src/cli/actions.ts +132 -12
- package/src/cli/help.ts +570 -0
- package/src/cli/import-source.test.ts +193 -0
- package/src/cli/import-source.ts +229 -0
- package/src/cli/index.ts +129 -94
- package/src/cli/web/client-script.ts +12 -0
- package/src/cli/web/console-fixes.test.ts +36 -0
- package/src/cli/web/http.ts +7 -1
- package/src/cli/web/page.ts +108 -25
- package/src/cli/web/routes.ts +23 -4
- package/src/cli/web/script-anchor.test.ts +33 -0
- package/src/cli/web/server.ts +28 -2
- package/src/cli/web/web.test.ts +447 -4
- package/src/client/ConfigManagerSection.tsx +118 -63
- package/src/client/about/AboutPanel.tsx +42 -3
- package/src/client/about/PluginUpdateDialog.tsx +185 -0
- package/src/client/about/ReleaseBody.tsx +144 -0
- package/src/client/about/ReleaseNotesDialog.tsx +3 -130
- package/src/client/about/about-view.ts +7 -5
- package/src/client/about/plugin-update-view.test.ts +25 -0
- package/src/client/about/plugin-update-view.ts +26 -0
- package/src/client/about/release-notes-view.test.ts +33 -0
- package/src/client/about/release-notes-view.ts +34 -0
- package/src/client/api.ts +145 -1
- package/src/client/common/Icon.tsx +3 -0
- package/src/client/common/RunsCenter.tsx +8 -2
- package/src/client/common/Select.tsx +9 -2
- package/src/client/common/info-hint-guard.test.ts +63 -18
- package/src/client/common/plan-text-redaction.test.ts +193 -8
- package/src/client/common/routes.ts +15 -0
- package/src/client/config-manager.module.css +506 -354
- package/src/client/css-scale-guard.test.ts +122 -0
- package/src/client/css-token-guard.test.ts +82 -0
- package/src/client/environment/EnvironmentPanel.tsx +45 -16
- package/src/client/{snapshots → environment/maintenance}/DiskUsageCard.tsx +21 -17
- package/src/client/environment/profiles-incomplete-ui.test.ts +103 -0
- package/src/client/export/ExportView.tsx +9 -2
- package/src/client/{snapshots → home}/BackupScheduleCard.tsx +13 -4
- package/src/client/home/HomePanel.tsx +69 -157
- package/src/client/icon-layer-guard.test.ts +1 -1
- package/src/client/import/ForeignImportView.tsx +318 -0
- package/src/client/import/ImportWizardView.tsx +3 -1
- package/src/client/import/foreign-import-view-guard.test.ts +195 -0
- package/src/client/import/import-file-select.test.ts +6 -6
- package/src/client/import/import-wizard-steps.tsx +17 -1
- package/src/client/import/use-import-wizard-controller.ts +3 -3
- package/src/client/index.ts +1 -1
- package/src/client/library/LibraryActions.tsx +31 -34
- package/src/client/library/LibraryPanel.tsx +4 -1
- package/src/client/{snapshots → library}/RestorePlanView.tsx +1 -1
- package/src/client/{snapshots → library}/files-layout.test.ts +2 -2
- package/src/client/{snapshots → library}/report-dismiss.test.ts +26 -3
- package/src/client/library/task-panel-visibility.test.ts +25 -17
- package/src/client/locales.ts +278 -36
- package/src/client/lucide-icons.d.ts +1 -0
- package/src/client/market/MarketPanel.tsx +11 -2
- package/src/client/recovery/IncompleteCopiesSection.tsx +132 -0
- package/src/client/recovery/RecoveryPanel.tsx +486 -59
- package/src/client/recovery/recovery-api.ts +30 -0
- package/src/client/recovery/recovery-incomplete-copies.test.ts +151 -0
- package/src/client/recovery/recovery-locales.test.ts +88 -0
- package/src/client/recovery/recovery-locales.ts +321 -6
- package/src/client/recovery/recovery-panel-layout.test.ts +52 -0
- package/src/client/recovery/recovery-view.ts +92 -2
- package/src/client/run-store.test.ts +201 -89
- package/src/client/run-store.ts +114 -143
- package/src/client/session-export/session-export-view.test.ts +103 -0
- package/src/client/session-export/session-export-view.ts +118 -0
- package/src/client/sync/ChannelConfigDialog.tsx +219 -14
- package/src/client/sync/SyncChannelEntryCard.tsx +52 -4
- package/src/client/sync/SyncLogList.tsx +1 -1
- package/src/client/sync/SyncPanel.tsx +166 -102
- package/src/client/sync/SyncRepositoryPicker.tsx +216 -0
- package/src/client/sync/SyncSettingsView.test.ts +6 -1
- package/src/client/sync/history-model.test.ts +94 -57
- package/src/client/sync/history-model.ts +44 -25
- package/src/client/sync/sync-api.test.ts +56 -0
- package/src/client/sync/sync-api.ts +92 -0
- package/src/client/sync/sync-locales.ts +162 -2
- package/src/client/sync/sync-view.test.ts +3 -8
- package/src/client/sync/sync-view.ts +111 -21
- package/src/core/analyzer-cache.test.ts +47 -0
- package/src/core/analyzer.ts +115 -14
- package/src/core/backup-plan.test.ts +35 -0
- package/src/core/backup-plan.ts +19 -1
- package/src/core/backup.test.ts +37 -1
- package/src/core/backup.ts +27 -6
- package/src/core/cache-cleaner.test.ts +120 -1
- package/src/core/cache-cleaner.ts +59 -22
- package/src/core/checkpoint.test.ts +665 -0
- package/src/core/checkpoint.ts +1614 -0
- package/src/core/credential-plan-consistency.test.ts +93 -0
- package/src/core/incident-wiring.test.ts +6 -2
- package/src/core/journal.test.ts +70 -0
- package/src/core/journal.ts +68 -0
- package/src/core/messages.ts +26 -0
- package/src/core/model-tools.ts +3 -0
- package/src/core/patch-layers.test.ts +94 -0
- package/src/core/patch-layers.ts +150 -0
- package/src/core/restore.ts +59 -3
- package/src/core/rollback.ts +6 -3
- package/src/core/section-content-consistency.test.ts +185 -0
- package/src/core/self-update.test.ts +110 -0
- package/src/core/self-update.ts +118 -0
- package/src/core/session-health.test.ts +91 -1
- package/src/core/session-health.ts +56 -9
- package/src/core/types.ts +127 -1
- package/src/foreign/antigravity.test.ts +271 -0
- package/src/foreign/antigravity.ts +188 -0
- package/src/foreign/bundle.ts +108 -0
- package/src/foreign/chatgpt.test.ts +279 -0
- package/src/foreign/chatgpt.ts +30 -0
- package/src/foreign/claude-code.test.ts +234 -0
- package/src/foreign/claude-code.ts +106 -0
- package/src/foreign/claude-sessions-bytes.test.ts +227 -0
- package/src/foreign/claude-sessions.test.ts +180 -0
- package/src/foreign/claude-sessions.ts +461 -0
- package/src/foreign/cline.test.ts +274 -0
- package/src/foreign/cline.ts +32 -0
- package/src/foreign/codex.test.ts +585 -0
- package/src/foreign/codex.ts +409 -0
- package/src/foreign/continue.test.ts +365 -0
- package/src/foreign/continue.ts +73 -0
- package/src/foreign/copilot.test.ts +273 -0
- package/src/foreign/copilot.ts +133 -0
- package/src/foreign/crush.test.ts +234 -0
- package/src/foreign/crush.ts +66 -0
- package/src/foreign/cursor.test.ts +509 -0
- package/src/foreign/cursor.ts +259 -0
- package/src/foreign/dsh.test.ts +198 -0
- package/src/foreign/dsh.ts +117 -0
- package/src/foreign/dsh4.test.ts +127 -0
- package/src/foreign/dsh4.ts +15 -0
- package/src/foreign/file-budget.test.ts +256 -0
- package/src/foreign/fixtures/antigravity/README.md +18 -0
- package/src/foreign/fixtures/antigravity/basic/gemini/antigravity/mcp_config.json +13 -0
- package/src/foreign/fixtures/antigravity/basic/gemini/config/mcp_config.json +24 -0
- package/src/foreign/fixtures/antigravity/empty/gemini/antigravity/mcp_config.json +0 -0
- package/src/foreign/fixtures/antigravity/empty/gemini/antigravity/mcp_oauth_tokens.json +0 -0
- package/src/foreign/fixtures/antigravity/empty/gemini/config/mcp_config.json +0 -0
- package/src/foreign/fixtures/codex/README.md +31 -0
- package/src/foreign/fixtures/codex/basic/.agents/skills/hello/SKILL.md +6 -0
- package/src/foreign/fixtures/codex/basic/.agents/skills/wrapper/nested/SKILL.md +6 -0
- package/src/foreign/fixtures/codex/basic/.codex/AGENTS.md +4 -0
- package/src/foreign/fixtures/codex/basic/.codex/config.toml +28 -0
- package/src/foreign/fixtures/codex/malformed/.codex/config.toml +3 -0
- package/src/foreign/fixtures/codex/override/.codex/AGENTS.md +1 -0
- package/src/foreign/fixtures/codex/override/.codex/AGENTS.override.md +3 -0
- package/src/foreign/fixtures/codex/sessions/.codex/archived_sessions/rollout-2026-10-05T09-00-00-22222222-2222-4222-8222-222222222222.jsonl +2 -0
- package/src/foreign/fixtures/codex/sessions/.codex/sessions/2026/10/06/rollout-2026-10-06T10-00-00-11111111-1111-4111-8111-111111111111.jsonl +15 -0
- package/src/foreign/fixtures/codex/sessions/.codex/sessions/2026/10/06/rollout-2026-10-06T11-00-00-33333333-3333-4333-8333-333333333333.jsonl +2 -0
- package/src/foreign/fixtures/codex/sessions/.codex/sessions/2026/10/06/rollout-2026-10-06T11-30-00-55555555-5555-4555-8555-555555555555.jsonl +2 -0
- package/src/foreign/fixtures/codex/sessions/.codex/sessions/2026/10/06/rollout-2026-10-06T12-00-00-44444444-4444-4444-8444-444444444444.jsonl +2 -0
- package/src/foreign/fixtures/codex/sessions/.codex/sessions/notes.jsonl +1 -0
- package/src/foreign/fixtures/copilot/README.md +22 -0
- package/src/foreign/fixtures/copilot/basic/copilot-instructions.md +3 -0
- package/src/foreign/fixtures/copilot/basic/instructions/empty.instructions.md +1 -0
- package/src/foreign/fixtures/copilot/basic/instructions/repo.instructions.md +5 -0
- package/src/foreign/fixtures/copilot/basic/mcp-config.json +24 -0
- package/src/foreign/fixtures/copilot/basic/skills/hello/SKILL.md +6 -0
- package/src/foreign/fixtures/copilot/overridden/mcp-config.json +10 -0
- package/src/foreign/fixtures/cursor/README.md +17 -0
- package/src/foreign/fixtures/cursor/alt/.cursor/mcp.json +5 -0
- package/src/foreign/fixtures/cursor/basic/.cursor/mcp.json +18 -0
- package/src/foreign/fixtures/cursor/basic/.cursor/rules/agent.mdc +5 -0
- package/src/foreign/fixtures/cursor/basic/.cursor/rules/always.mdc +6 -0
- package/src/foreign/fixtures/cursor/basic/.cursor/rules/auto.mdc +7 -0
- package/src/foreign/fixtures/cursor/basic/.cursor/rules/manual.mdc +3 -0
- package/src/foreign/fixtures/cursor/basic/.cursor/skills/.hidden/SKILL.md +6 -0
- package/src/foreign/fixtures/cursor/basic/.cursor/skills/hello/SKILL.md +6 -0
- package/src/foreign/fixtures/cursor/basic/.cursor/skills/wrapper/nested/SKILL.md +6 -0
- package/src/foreign/fixtures/cursor/basic/.cursor/skills/wrapper/nested/notes.md +1 -0
- package/src/foreign/fixtures/cursor/basic/.cursorrules +1 -0
- package/src/foreign/fixtures/cursor/basic/project/.cursor/mcp.json +9 -0
- package/src/foreign/fixtures/cursor/basic/project/.cursor/rules/project-always.mdc +5 -0
- package/src/foreign/fixtures/cursor/basic/project/.cursor/skills/hello/SKILL.md +6 -0
- package/src/foreign/fixtures/cursor/basic/project/.cursorrules +1 -0
- package/src/foreign/fixtures/cursor/empty/.cursor/mcp.json +0 -0
- package/src/foreign/fixtures/cursor/malformed/.cursor/mcp.json +3 -0
- package/src/foreign/fixtures/cursor/malformed/.cursor/rules/broken.mdc +6 -0
- package/src/foreign/fixtures/cursor/shape/.cursor/mcp.json +3 -0
- package/src/foreign/fixtures/hermes/README.md +14 -0
- package/src/foreign/fixtures/hermes/basic/SOUL.md +3 -0
- package/src/foreign/fixtures/hermes/basic/config.yaml +48 -0
- package/src/foreign/fixtures/hermes/basic/dotenv.fixture +1 -0
- package/src/foreign/fixtures/hermes/basic/memories/MEMORY.md +1 -0
- package/src/foreign/fixtures/hermes/basic/memories/USER.md +1 -0
- package/src/foreign/fixtures/hermes/basic/skills/se-team-design/SKILL.md +6 -0
- package/src/foreign/fixtures/hermes/basic/skills/se-team-design/examples/usage.md +1 -0
- package/src/foreign/fixtures/hermes/basic/skills/software-development/dogfood/SKILL.md +6 -0
- package/src/foreign/fixtures/hermes/basic/skills/software-development/dogfood/notes.md +1 -0
- package/src/foreign/fixtures/hermes/basic/state.db +1 -0
- package/src/foreign/fixtures/hermes/empty/config.yaml +0 -0
- package/src/foreign/gemini.test.ts +291 -0
- package/src/foreign/gemini.ts +52 -0
- package/src/foreign/goose.test.ts +196 -0
- package/src/foreign/goose.ts +62 -0
- package/src/foreign/grokbuild.test.ts +246 -0
- package/src/foreign/grokbuild.ts +30 -0
- package/src/foreign/hermes.test.ts +545 -0
- package/src/foreign/hermes.ts +86 -0
- package/src/foreign/kernel-credentials.test.ts +88 -0
- package/src/foreign/kernel-workspaces.test.ts +88 -0
- package/src/foreign/kernel.ts +584 -0
- package/src/foreign/kilocode.test.ts +186 -0
- package/src/foreign/kilocode.ts +60 -0
- package/src/foreign/kimi.test.ts +259 -0
- package/src/foreign/kimi.ts +55 -0
- package/src/foreign/mcp-value-shape.test.ts +286 -0
- package/src/foreign/mimocode.test.ts +178 -0
- package/src/foreign/mimocode.ts +60 -0
- package/src/foreign/openclaw.test.ts +248 -0
- package/src/foreign/openclaw.ts +54 -0
- package/src/foreign/opencode.test.ts +376 -0
- package/src/foreign/opencode.ts +61 -0
- package/src/foreign/pi.test.ts +275 -0
- package/src/foreign/pi.ts +56 -0
- package/src/foreign/platform-paths.ts +156 -0
- package/src/foreign/qoder.test.ts +266 -0
- package/src/foreign/qoder.ts +57 -0
- package/src/foreign/qwen.test.ts +203 -0
- package/src/foreign/qwen.ts +53 -0
- package/src/foreign/read-antigravity.ts +569 -0
- package/src/foreign/read-chatgpt.ts +506 -0
- package/src/foreign/read-claude-code.ts +264 -0
- package/src/foreign/read-cline.ts +517 -0
- package/src/foreign/read-codex.ts +823 -0
- package/src/foreign/read-continue.ts +557 -0
- package/src/foreign/read-copilot.ts +304 -0
- package/src/foreign/read-crush.ts +503 -0
- package/src/foreign/read-cursor.ts +778 -0
- package/src/foreign/read-dsh.ts +215 -0
- package/src/foreign/read-dsh4.ts +35 -0
- package/src/foreign/read-gemini.ts +306 -0
- package/src/foreign/read-goose.ts +333 -0
- package/src/foreign/read-grokbuild.ts +475 -0
- package/src/foreign/read-hermes.ts +724 -0
- package/src/foreign/read-kilocode.ts +55 -0
- package/src/foreign/read-kimi.ts +768 -0
- package/src/foreign/read-mimocode.ts +66 -0
- package/src/foreign/read-openclaw.ts +505 -0
- package/src/foreign/read-opencode.ts +994 -0
- package/src/foreign/read-pi.ts +459 -0
- package/src/foreign/read-qoder.ts +351 -0
- package/src/foreign/read-qwen.ts +342 -0
- package/src/foreign/read-reasonix.ts +590 -0
- package/src/foreign/read-teleagent.ts +82 -0
- package/src/foreign/read-trae.ts +488 -0
- package/src/foreign/read-vibe.ts +353 -0
- package/src/foreign/read-workbuddy.ts +374 -0
- package/src/foreign/read-zcode.ts +48 -0
- package/src/foreign/read-zed.ts +479 -0
- package/src/foreign/reasonix.test.ts +336 -0
- package/src/foreign/reasonix.ts +25 -0
- package/src/foreign/registry.test.ts +377 -0
- package/src/foreign/registry.ts +750 -0
- package/src/foreign/session-ir.test.ts +189 -0
- package/src/foreign/session-ir.ts +614 -0
- package/src/foreign/session-read.ts +295 -0
- package/src/foreign/session-source.ts +625 -0
- package/src/foreign/skill-file-caps.test.ts +171 -0
- package/src/foreign/source-modules.ts +86 -0
- package/src/foreign/source-registry.test.ts +190 -0
- package/src/foreign/sqlite.test.ts +313 -0
- package/src/foreign/sqlite.ts +446 -0
- package/src/foreign/teleagent.test.ts +147 -0
- package/src/foreign/teleagent.ts +62 -0
- package/src/foreign/trae.test.ts +316 -0
- package/src/foreign/trae.ts +25 -0
- package/src/foreign/truncation-report.test.ts +238 -0
- package/src/foreign/truth-table.ts +453 -0
- package/src/foreign/types.ts +312 -0
- package/src/foreign/vibe.test.ts +209 -0
- package/src/foreign/vibe.ts +28 -0
- package/src/foreign/workbuddy.test.ts +207 -0
- package/src/foreign/workbuddy.ts +53 -0
- package/src/foreign/zcode.test.ts +224 -0
- package/src/foreign/zcode.ts +60 -0
- package/src/foreign/zed.test.ts +240 -0
- package/src/foreign/zed.ts +62 -0
- package/src/index.facade.test.ts +254 -16
- package/src/index.ts +704 -82
- package/src/market/github-repos.test.ts +98 -1
- package/src/market/github-repos.ts +63 -1
- package/src/market/types.ts +3 -3
- package/src/profiles/dsh-profile-incomplete-copy.test.ts +177 -0
- package/src/profiles/dsh-profile-manager.test.ts +37 -0
- package/src/profiles/dsh-profile-manager.ts +188 -13
- package/src/profiles/dsh-profile-shared.ts +55 -1
- package/src/profiles/index.ts +2 -1
- package/src/routes/consult.ts +53 -4
- package/src/routes/foreign-context.ts +89 -0
- package/src/routes/foreign.test.ts +443 -0
- package/src/routes/foreign.ts +226 -0
- package/src/routes/import.ts +23 -1
- package/src/routes/index.ts +6 -2
- package/src/routes/kit.ts +4 -3
- package/src/routes/market.ts +0 -1
- package/src/routes/prefs.ts +15 -1
- package/src/routes/recovery.test.ts +167 -0
- package/src/routes/recovery.ts +331 -1
- package/src/routes/session-export.ts +308 -0
- package/src/routes/snapshots.ts +12 -2
- package/src/routes/sync.ts +91 -7
- package/src/security/redaction.test.ts +149 -0
- package/src/security/redaction.ts +25 -9
- package/src/security/secret-scanner.test.ts +186 -0
- package/src/security/secret-scanner.ts +57 -1
- package/src/sync/autosync-risk-tier.test.ts +61 -0
- package/src/sync/autosync-scheduler.test.ts +48 -1
- package/src/sync/autosync-scheduler.ts +50 -26
- package/src/sync/backup-files.ts +5 -0
- package/src/sync/gist/gist-msgkeys-catalog.test.ts +33 -0
- package/src/sync/gist/gist-msgkeys.test.ts +71 -0
- package/src/sync/gist/gist-transport.test.ts +308 -0
- package/src/sync/gist/gist-transport.ts +550 -0
- package/src/sync/gist/messages.ts +49 -0
- package/src/sync/retention-prune-all-off.test.ts +50 -0
- package/src/sync/s3/cloud-config.test.ts +283 -0
- package/src/sync/s3/messages.ts +102 -0
- package/src/sync/s3/s3-delete-index-404.test.ts +170 -0
- package/src/sync/s3/s3-delete-index-unreadable.test.ts +141 -0
- package/src/sync/s3/s3-msgkeys.test.ts +101 -0
- package/src/sync/s3/s3-providers.ts +213 -0
- package/src/sync/s3/s3-transport.test.ts +394 -0
- package/src/sync/s3/s3-transport.ts +651 -0
- package/src/sync/s3/s3-upload-index-404.test.ts +171 -0
- package/src/sync/s3/sigv4.test.ts +219 -0
- package/src/sync/s3/sigv4.ts +283 -0
- package/src/sync/sync-config.test.ts +237 -5
- package/src/sync/sync-config.ts +841 -52
- package/src/sync/sync-engine-download-tombstone.test.ts +73 -0
- package/src/sync/sync-engine.ts +5 -3
- package/src/sync/sync-history.ts +1 -1
- package/src/sync/webdav/webdav-delete-index-404.test.ts +178 -0
- package/src/sync/webdav/webdav-delete-index-unreadable.test.ts +143 -0
- package/src/sync/webdav/webdav-msgkeys.test.ts +53 -0
- package/src/sync/webdav/webdav-transport.test.ts +9 -5
- package/src/sync/webdav/webdav-transport.ts +99 -17
- package/src/sync/webdav/webdav-upload-index-404.test.ts +177 -0
- package/src/ui/commands.test.ts +4 -1
- package/src/ui/commands.ts +46 -12
- package/src/ui/disk-usage-view.test.ts +63 -1
- package/src/ui/disk-usage-view.ts +93 -1
- package/src/ui/dsh-profiles-incomplete-view.test.ts +102 -0
- package/src/ui/dsh-profiles-view.ts +94 -0
- package/src/ui/foreign-view.test.ts +241 -0
- package/src/ui/foreign-view.ts +362 -0
- package/src/ui/i18n.ts +164 -0
- package/src/ui/mid-ellipsis.test.ts +5 -10
- package/src/ui/session-inventory-view.test.ts +142 -2
- package/src/ui/session-inventory-view.ts +127 -0
- package/src/ui/session-layout-view.test.ts +361 -0
- package/src/ui/session-layout-view.ts +444 -0
- package/src/ui/sync-repository-picker.test.ts +130 -0
- package/src/ui/sync-repository-picker.ts +180 -0
- package/src/ui/sync-settings-view.test.ts +166 -0
- package/src/ui/sync-settings-view.ts +319 -9
- package/src/ui/types.ts +42 -0
- package/src/utils/asar-read.test.ts +330 -0
- package/src/utils/asar-read.ts +606 -0
- package/src/utils/env-lock.test.ts +55 -0
- package/src/utils/env-lock.ts +6 -1
- package/src/utils/patch-yaml.test.ts +112 -0
- package/src/utils/patch-yaml.ts +72 -0
- package/src/utils/paths.test.ts +63 -0
- package/src/utils/paths.ts +25 -3
- package/src/utils/recursive-walk.test.ts +20 -0
- package/src/utils/recursive-walk.ts +12 -13
- package/src/utils/session-format.test.ts +113 -2
- package/src/utils/session-format.ts +127 -5
- package/src/utils/session-health-scan.test.ts +364 -1
- package/src/utils/session-health-scan.ts +434 -15
- package/src/utils/session-layout-repair-service.test.ts +362 -0
- package/src/utils/session-layout-repair-service.ts +568 -0
- package/src/utils/session-log-repair.test.ts +260 -1
- package/src/utils/session-log-repair.ts +294 -55
- package/src/utils/session-repair-service.test.ts +148 -1
- package/src/utils/session-repair-service.ts +165 -10
- package/src/utils/session-row-facts.test.ts +159 -0
- package/src/utils/session-row-facts.ts +187 -0
- package/src/utils/session-verify.test.ts +762 -0
- package/src/utils/session-verify.ts +784 -0
- package/src/utils/shared-constants.ts +82 -0
package/src/adapters/plugins.ts
CHANGED
|
@@ -10,25 +10,33 @@
|
|
|
10
10
|
* 文件条目随分区进入 ZIP(落在 `plugin-files/local-plugins/` 前缀下,复用既有文件类分区通道,
|
|
11
11
|
* **不新增分区 id**);导入时把 spec 重写为 `file:<解包后的绝对路径>` 再交给官方安装通道。
|
|
12
12
|
*/
|
|
13
|
+
import { join } from 'node:path';
|
|
13
14
|
import { isDeepStrictEqual } from 'node:util';
|
|
14
15
|
import { sha256Hex } from '../utils/hashing.ts';
|
|
15
16
|
import { installSpecFor, resolveProcessProfileName } from '../core/plugin-cli.ts';
|
|
16
|
-
import { LOCAL_PLUGIN_DIR } from '../core/local-plugin-pack.ts';
|
|
17
|
+
import { classifyPluginSpec, LOCAL_PLUGIN_DIR, resolveLocalPluginPath } from '../core/local-plugin-pack.ts';
|
|
17
18
|
import type { PackLocalPluginsResult } from '../core/local-plugin-pack.ts';
|
|
18
19
|
import { msgOf, zhMsg } from '../core/messages.ts';
|
|
19
20
|
import type { MsgFunc } from '../core/messages.ts';
|
|
21
|
+
import { stringifyJsonSafe } from '../utils/json.ts';
|
|
20
22
|
import { isPathSafe, normalizePath } from '../utils/paths.ts';
|
|
23
|
+
import { isENOENT } from '../utils/guards.ts';
|
|
21
24
|
import { PLUGIN_PATCH_REF_PREFIX } from '../core/backup.ts';
|
|
25
|
+
import { readEffectivePatchLines, resolveWriteLayer, USER_PATCH_FILE } from '../core/patch-layers.ts';
|
|
22
26
|
import { parsePnpmPatchedDependencies, sanitizePnpmWorkspacePatches } from './pnpm-workspace.ts';
|
|
23
27
|
import type { LocalPluginTarball, PatchLine, PluginEntry, PluginsSection, PnpmPatchFile } from '../schema/types.ts';
|
|
24
28
|
import type {
|
|
25
29
|
ApplyResult, ConfigAdapter, ExportOptions, ExportSection, ExportUnit, HostContext,
|
|
26
|
-
ImportContext, PlanItem, ValidationResult,
|
|
30
|
+
ImportContext, PlanItem, SectionPreview, ValidationResult,
|
|
27
31
|
} from '../core/types.ts';
|
|
28
32
|
import { sectionMeta } from '../schema/section-registry.ts';
|
|
29
33
|
import { validateJsonSection } from './json-section.ts';
|
|
30
34
|
|
|
31
|
-
|
|
35
|
+
/**
|
|
36
|
+
* 用户(home)patch 层路径 —— 定义已移到 `core/patch-layers.ts`(issue #71:patch 有 home /
|
|
37
|
+
* profile 两层,层身份属于引擎知识,不属于某个 adapter)。此处 re-export 保持既有 import 面。
|
|
38
|
+
*/
|
|
39
|
+
export { USER_PATCH_FILE };
|
|
32
40
|
|
|
33
41
|
/**
|
|
34
42
|
* 本地源插件打包钩子(由宿主注入,见 src/index.ts createAdapters)。
|
|
@@ -137,6 +145,109 @@ function entriesOfRaw(raw: unknown): { config?: unknown }[] {
|
|
|
137
145
|
return [];
|
|
138
146
|
}
|
|
139
147
|
|
|
148
|
+
/**
|
|
149
|
+
* 预览路径测到的本地源插件体积(`preview()` 写入,`listUnits()` 读取)。
|
|
150
|
+
*
|
|
151
|
+
* 为什么用 WeakMap 而不是给 `PluginsSection` 加字段:`localTarballs` 是备份格式的一部分
|
|
152
|
+
* (schema 校验、市场安全防线都在看它),而预览产物里本就没有 tarball 字节;把「只读预览测到的
|
|
153
|
+
* 体积」混进载荷只会让「预览说的」与「导出做的」更难分辨。挂在载荷对象上则随对象一起回收,
|
|
154
|
+
* 导出产物(真实 base64)走另一条分支,行为逐字不变。
|
|
155
|
+
*/
|
|
156
|
+
const previewTarballBytes = new WeakMap<object, Map<string, number>>();
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* 零 spawn 推导本地源插件的体积(预览专用)。
|
|
160
|
+
*
|
|
161
|
+
* 两条路径,都是**只读度量**、都不 spawn 打包进程:
|
|
162
|
+
* - `file:` 源 → 一次 stat:源 tarball 本身就是 gzip 容器,字节数与 `npm pack` 产物同量级;
|
|
163
|
+
* - `link:` 源 → 目录体积上界(`fs.dirSizeBytes`):这是 `npm pack` 的输入目录,体积比
|
|
164
|
+
* pack 产物大(真机约 3.4 倍,pack 产物是 gzip),用来展示「量级」足够。
|
|
165
|
+
* 绝不在这里手写递归(见 local-plugin-pack.ts 安全约束:node_modules 由 npm pack 自己排除,
|
|
166
|
+
* 手写递归会把它整个算进来)——度量交给宿主门面,由它负责跳过 node_modules 与链接目录。
|
|
167
|
+
*
|
|
168
|
+
* 登记本身就是语义:Map 里有该插件 = 「该单元随备份携带一个文件」,与导出产物里
|
|
169
|
+
* `npm pack` 打出的 tarball(`fileCount: 1`)对齐;宿主未实现相应度量、越界路径
|
|
170
|
+
* (fs 门面对 home 外绝对路径抛 `host.fsPathEscape`)、目录读不到一律按 0 ——
|
|
171
|
+
* 预览绝不能因为一个体积数字而失败。
|
|
172
|
+
*/
|
|
173
|
+
async function measureLocalSources(plugins: PluginEntry[], ctx: HostContext): Promise<Map<string, number>> {
|
|
174
|
+
const sizes = new Map<string, number>();
|
|
175
|
+
const statSize = ctx.fs.statSize;
|
|
176
|
+
const dirSize = ctx.fs.dirSizeBytes;
|
|
177
|
+
// profile 目录与打包钩子同源(local-plugin-host.ts:优先 profileDir,回退按 profile 名拼接)
|
|
178
|
+
const profileDir = ctx.profileDir !== undefined && ctx.profileDir !== ''
|
|
179
|
+
? ctx.profileDir
|
|
180
|
+
: join(ctx.homeDir, PNPM_PROFILE_DIR(ctx.profile));
|
|
181
|
+
for (const pl of plugins) {
|
|
182
|
+
const kind = classifyPluginSpec(pl.spec);
|
|
183
|
+
if (kind !== 'file' && kind !== 'link') continue;
|
|
184
|
+
try {
|
|
185
|
+
const abs = resolveLocalPluginPath(pl.spec ?? '', { homeDir: ctx.homeDir, profileDir });
|
|
186
|
+
// 宿主未实现对应度量 → null → 按 0(旧行为,只少一个数字)
|
|
187
|
+
const size = kind === 'link'
|
|
188
|
+
? (dirSize !== undefined ? await dirSize.call(ctx.fs, abs) : null)
|
|
189
|
+
: (statSize !== undefined ? await statSize.call(ctx.fs, abs) : null);
|
|
190
|
+
sizes.set(pl.name, size !== null && size > 0 ? size : 0);
|
|
191
|
+
} catch {
|
|
192
|
+
// 越界 / 读不到:按 0 处理(预览不因此失败),但仍算「随包携带一个文件」
|
|
193
|
+
sizes.set(pl.name, 0);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
return sizes;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* 由分区载荷派生单元清单(纯函数、零 I/O)。
|
|
201
|
+
*
|
|
202
|
+
* 单元 = 插件包 / cordis 补丁行 / pnpm-workspace.yaml / 单个 patch 文件。
|
|
203
|
+
* 后两者构成**原子组**:patchedDependencies 的声明与 patches/** 文件必须同进同出
|
|
204
|
+
* (issue #35 —— 只搬声明会让目标机 pnpm 拒绝一切 add),故互相写进 lockedWith,
|
|
205
|
+
* 由选择模型保证勾选/取消同步。
|
|
206
|
+
*
|
|
207
|
+
* `tarballBytes` = 预览路径登记到的本地源体积(见 measureLocalSources,0 = 体积未知但仍随包携带);
|
|
208
|
+
* 缺省时体积与文件数完全按导出产物里的 base64 计算 —— 与改造前逐字一致。
|
|
209
|
+
*/
|
|
210
|
+
function unitsOf(data: PluginsSection, tarballBytes?: Map<string, number>): ExportUnit[] {
|
|
211
|
+
const wsId = 'plugins:pnpm-workspace';
|
|
212
|
+
const patchFileIds = (data.patchFiles ?? []).map((pf) => `plugins:patch:${normalizePath(pf.relativePath)}`);
|
|
213
|
+
const group = [wsId, ...patchFileIds];
|
|
214
|
+
const units: ExportUnit[] = [];
|
|
215
|
+
for (const pl of data.plugins) {
|
|
216
|
+
const tarball = tarballOf(data, pl.name);
|
|
217
|
+
const measured = tarballBytes?.get(pl.name);
|
|
218
|
+
units.push({
|
|
219
|
+
id: `plugin:${pl.name}`,
|
|
220
|
+
label: pl.name,
|
|
221
|
+
detail: pl.version,
|
|
222
|
+
// base64 长度 → 原始字节数(仅用于展示体积,不求精确);预览路径没有字节,用 stat 量到的源体积
|
|
223
|
+
sizeBytes: tarball !== undefined ? Math.floor((tarball.base64.length * 3) / 4) : measured ?? 0,
|
|
224
|
+
...(tarball !== undefined || measured !== undefined ? { fileCount: 1 } : {}),
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
for (const line of data.patch) {
|
|
228
|
+
// 由 mcp / prompts adapter 管理的行不属本分区(applyItem 同样跳过),列出来只会误导用户
|
|
229
|
+
if (isManagedElsewhere(line.raw)) continue;
|
|
230
|
+
units.push({ id: `patch:${line.lineId}`, label: line.lineId, sizeBytes: 0 });
|
|
231
|
+
}
|
|
232
|
+
if (data.pnpmWorkspace !== undefined && data.pnpmWorkspace !== null && data.pnpmWorkspace !== '') {
|
|
233
|
+
units.push({
|
|
234
|
+
id: wsId,
|
|
235
|
+
label: 'pnpm-workspace.yaml',
|
|
236
|
+
sizeBytes: data.pnpmWorkspace.length,
|
|
237
|
+
...(group.length > 1 ? { lockedWith: group } : {}),
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
for (const pf of data.patchFiles ?? []) {
|
|
241
|
+
units.push({
|
|
242
|
+
id: `plugins:patch:${normalizePath(pf.relativePath)}`,
|
|
243
|
+
label: pf.relativePath,
|
|
244
|
+
sizeBytes: Math.floor((pf.base64.length * 3) / 4),
|
|
245
|
+
...(group.length > 1 ? { lockedWith: group } : {}),
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
return units;
|
|
249
|
+
}
|
|
250
|
+
|
|
140
251
|
export class PluginsAdapter implements ConfigAdapter<PluginsSection> {
|
|
141
252
|
readonly id = 'plugins' as const;
|
|
142
253
|
// 元数据唯一来源 = 注册表(t31):不再与 ui/export-flow.ts 的导出目录各写一份
|
|
@@ -154,6 +265,42 @@ export class PluginsAdapter implements ConfigAdapter<PluginsSection> {
|
|
|
154
265
|
}
|
|
155
266
|
|
|
156
267
|
async export(ctx: HostContext, options: ExportOptions): Promise<ExportSection<PluginsSection>> {
|
|
268
|
+
return this.collect(ctx, options, true);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* 只读预览(`/export-preview` 用):与 `export()` **同口径**,但不做任何本地源打包。
|
|
273
|
+
*
|
|
274
|
+
* 为什么必须覆写:基类缺省会让调用方退回 `export()`,而 `export()` 会为每个 `link:`/`file:`
|
|
275
|
+
* 插件 spawn 一次 `npm pack`(真机 12 个本地源插件 ≈12 s)。「总览」页只想知道条目数与体积,
|
|
276
|
+
* 却因此每次都要打包一遍全部本地插件 —— 预览是纯只读路径,不该有任何进程副作用。
|
|
277
|
+
*
|
|
278
|
+
* 与 `export()` 的**唯一**差异:
|
|
279
|
+
* - 不注入 `localTarballs`(没有字节),故清单 spec 保持原样(不做 `file:<tarball>` 改写);
|
|
280
|
+
* - 插件单元体积改为 `file:` 源的一次 stat(`measureLocalSources`),`link:` 目录按 0;
|
|
281
|
+
* - 体积用一次 `stringify` 算 UTF-8 字节数(与 /export-preview 对 JSON 分区的口径一致)。
|
|
282
|
+
* 其余(清单过滤、patch 行、pnpm-workspace、patchFiles、条目级白名单、告警文案、单元分组)逐字相同。
|
|
283
|
+
*/
|
|
284
|
+
async preview(ctx: HostContext, options: ExportOptions): Promise<SectionPreview<PluginsSection>> {
|
|
285
|
+
const collected = await this.collect(ctx, options, false);
|
|
286
|
+
const data = collected.data;
|
|
287
|
+
const tarballBytes = await measureLocalSources(data.plugins, ctx);
|
|
288
|
+
if (tarballBytes.size > 0) previewTarballBytes.set(data, tarballBytes);
|
|
289
|
+
return {
|
|
290
|
+
section: collected,
|
|
291
|
+
// 与 src/index.ts 对 JSON 分区现算体积的口径一致(utf8 字节数)
|
|
292
|
+
sizeBytes: new TextEncoder().encode(stringifyJsonSafe(data)).length,
|
|
293
|
+
items: unitsOf(data, tarballBytes),
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* 导出与预览的共同内核。
|
|
299
|
+
*
|
|
300
|
+
* `pack=false` 时跳过本地源打包(预览路径)—— 除该段外两条路径逐字共用,
|
|
301
|
+
* 避免「预览说会带什么」与「导出真的带什么」分叉。
|
|
302
|
+
*/
|
|
303
|
+
private async collect(ctx: HostContext, options: ExportOptions, pack: boolean): Promise<ExportSection<PluginsSection>> {
|
|
157
304
|
const plugins: PluginEntry[] = [];
|
|
158
305
|
const warnings: string[] = [];
|
|
159
306
|
try {
|
|
@@ -175,11 +322,12 @@ export class PluginsAdapter implements ConfigAdapter<PluginsSection> {
|
|
|
175
322
|
warnings.push(msgOf(ctx)('adapter.pluginListReadFailed', { reason: err instanceof Error ? err.message : String(err) }));
|
|
176
323
|
}
|
|
177
324
|
const patch: PatchLine[] = [];
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
325
|
+
// issue #71:patch 有两层(home 层 + profile 层),只读 home 层会漏掉 DSH 工具与 marketplace
|
|
326
|
+
// 写入 profile 层的激活行 —— 导出少了它们,导入到新机就少一批插件加载项。
|
|
327
|
+
const patchRead = await readEffectivePatchLines(ctx.patchFile, ctx.profile);
|
|
328
|
+
for (const l of patchRead.lines) patch.push({ file: l.file, lineId: l.lineId, raw: l.raw });
|
|
329
|
+
for (const f of patchRead.failures) {
|
|
330
|
+
warnings.push(msgOf(ctx)('adapter.patchReadFailed', { reason: `${f.file}: ${f.reason}` }));
|
|
183
331
|
}
|
|
184
332
|
// pnpm-workspace.yaml(allowBuilds / minimumReleaseAgeExclude 等):随插件分区迁移,
|
|
185
333
|
// 否则目标 profile 的 pnpm 可能因构建白名单/冷静期拒绝安装插件(§34.17 同款语义)。
|
|
@@ -236,7 +384,7 @@ export class PluginsAdapter implements ConfigAdapter<PluginsSection> {
|
|
|
236
384
|
// (曾导致插件被静默丢失)。钩子由宿主注入;未注入 / 无本地源 / 单个失败一律不中断导出。
|
|
237
385
|
let localTarballs: LocalPluginTarball[] | undefined;
|
|
238
386
|
let effectivePlugins = plugins;
|
|
239
|
-
if (this.localPack !== undefined) {
|
|
387
|
+
if (pack && this.localPack !== undefined) {
|
|
240
388
|
try {
|
|
241
389
|
const packed = await this.localPack(plugins, ctx);
|
|
242
390
|
warnings.push(...packed.warnings);
|
|
@@ -312,44 +460,8 @@ export class PluginsAdapter implements ConfigAdapter<PluginsSection> {
|
|
|
312
460
|
* 由选择模型保证勾选/取消同步。
|
|
313
461
|
*/
|
|
314
462
|
listUnits(section: ExportSection<PluginsSection>): ExportUnit[] {
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
const patchFileIds = (data.patchFiles ?? []).map((pf) => `plugins:patch:${normalizePath(pf.relativePath)}`);
|
|
318
|
-
const group = [wsId, ...patchFileIds];
|
|
319
|
-
const units: ExportUnit[] = [];
|
|
320
|
-
for (const pl of data.plugins) {
|
|
321
|
-
const tarball = (data.localTarballs ?? []).find((t) => t.packageName === pl.name);
|
|
322
|
-
units.push({
|
|
323
|
-
id: `plugin:${pl.name}`,
|
|
324
|
-
label: pl.name,
|
|
325
|
-
detail: pl.version,
|
|
326
|
-
// base64 长度 → 原始字节数(仅用于展示体积,不求精确)
|
|
327
|
-
sizeBytes: tarball !== undefined ? Math.floor((tarball.base64.length * 3) / 4) : 0,
|
|
328
|
-
...(tarball !== undefined ? { fileCount: 1 } : {}),
|
|
329
|
-
});
|
|
330
|
-
}
|
|
331
|
-
for (const line of data.patch) {
|
|
332
|
-
// 由 mcp / prompts adapter 管理的行不属本分区(applyItem 同样跳过),列出来只会误导用户
|
|
333
|
-
if (isManagedElsewhere(line.raw)) continue;
|
|
334
|
-
units.push({ id: `patch:${line.lineId}`, label: line.lineId, sizeBytes: 0 });
|
|
335
|
-
}
|
|
336
|
-
if (data.pnpmWorkspace !== undefined && data.pnpmWorkspace !== null && data.pnpmWorkspace !== '') {
|
|
337
|
-
units.push({
|
|
338
|
-
id: wsId,
|
|
339
|
-
label: 'pnpm-workspace.yaml',
|
|
340
|
-
sizeBytes: data.pnpmWorkspace.length,
|
|
341
|
-
...(group.length > 1 ? { lockedWith: group } : {}),
|
|
342
|
-
});
|
|
343
|
-
}
|
|
344
|
-
for (const pf of data.patchFiles ?? []) {
|
|
345
|
-
units.push({
|
|
346
|
-
id: `plugins:patch:${normalizePath(pf.relativePath)}`,
|
|
347
|
-
label: pf.relativePath,
|
|
348
|
-
sizeBytes: Math.floor((pf.base64.length * 3) / 4),
|
|
349
|
-
...(group.length > 1 ? { lockedWith: group } : {}),
|
|
350
|
-
});
|
|
351
|
-
}
|
|
352
|
-
return units;
|
|
463
|
+
// 预览产物没有 tarball 字节,体积来自 measureLocalSources 挂上的统计(导出产物走 base64 分支)
|
|
464
|
+
return unitsOf(section.data, previewTarballBytes.get(section.data));
|
|
353
465
|
}
|
|
354
466
|
|
|
355
467
|
/**
|
|
@@ -369,7 +481,12 @@ export class PluginsAdapter implements ConfigAdapter<PluginsSection> {
|
|
|
369
481
|
if (rel === '' || !isPathSafe(rel) || set.has(rel)) continue;
|
|
370
482
|
try {
|
|
371
483
|
if (await ctx.target.fs.exists(`${profileDir}/${rel}`)) set.add(rel);
|
|
372
|
-
} catch {
|
|
484
|
+
} catch (err) {
|
|
485
|
+
// t81 / t23 口径:**读不到 ≠ 没有**。只有 ENOENT 才是「本来就没有」;EACCES/EBUSY/IO 一律上抛,
|
|
486
|
+
// 否则会把「声明在、文件也在」的 patch 当作不存在 → 从将要写入的 pnpm-workspace.yaml 里剔除
|
|
487
|
+
// (issue #35 的症状:目标机 pnpm 从此拒绝一切 add)。存在性探测自身失败不得降级成「不存在」。
|
|
488
|
+
if (!isENOENT(err)) throw err;
|
|
489
|
+
}
|
|
373
490
|
}
|
|
374
491
|
return set;
|
|
375
492
|
}
|
|
@@ -491,16 +608,20 @@ export class PluginsAdapter implements ConfigAdapter<PluginsSection> {
|
|
|
491
608
|
|
|
492
609
|
// 用户 patch 行:lineId 唯一键;存在且同 → Skip;存在不同 → Conflict;不存在 → Create。
|
|
493
610
|
// mcp-client 行与 systemPrompt/planMode 行由 mcp/prompts adapter 管理,此处跳过(避免重复写入覆盖)。
|
|
494
|
-
|
|
611
|
+
// issue #71:目标行同样跨两层(否则 profile 层已有的行会被误判成 Create → 导入后出现重复行)
|
|
612
|
+
const targetLines = (await readEffectivePatchLines(ctx.target.patchFile, ctx.target.profile)).lines;
|
|
495
613
|
for (const pl of data.patch) {
|
|
496
614
|
if (isManagedElsewhere(pl.raw)) continue;
|
|
497
615
|
const id = `patch:${pl.lineId}`;
|
|
498
616
|
const tl = targetLines.find((l) => l.lineId === pl.lineId);
|
|
617
|
+
// 层身份随计划项走:applyItem 按 pl.file 写回,导入前快照按 target.file 记原行,
|
|
618
|
+
// 两者必须是同一层,否则回滚会把另一层改坏(issue #71)。
|
|
619
|
+
const layer = resolveWriteLayer(pl.file, ctx.target.profile);
|
|
499
620
|
if (!tl) {
|
|
500
621
|
items.push({
|
|
501
622
|
id, kind: 'Create', adapter: 'plugins',
|
|
502
623
|
description: msg('adapter.patchLineCreate', { lineId: pl.lineId }), severity: 'info',
|
|
503
|
-
target: { adapter: 'plugins', ref: pl.lineId },
|
|
624
|
+
target: { adapter: 'plugins', ref: pl.lineId, file: layer },
|
|
504
625
|
});
|
|
505
626
|
} else if (isDeepStrictEqual(tl.raw, pl.raw)) {
|
|
506
627
|
items.push({ id, kind: 'Skip', adapter: 'plugins', description: msg('adapter.patchLineSame', { lineId: pl.lineId }), severity: 'info' });
|
|
@@ -508,7 +629,7 @@ export class PluginsAdapter implements ConfigAdapter<PluginsSection> {
|
|
|
508
629
|
items.push({
|
|
509
630
|
id, kind: 'Conflict', adapter: 'plugins',
|
|
510
631
|
description: msg('adapter.patchLineDiff', { lineId: pl.lineId }), severity: 'warning',
|
|
511
|
-
target: { adapter: 'plugins', ref: pl.lineId },
|
|
632
|
+
target: { adapter: 'plugins', ref: pl.lineId, file: layer },
|
|
512
633
|
});
|
|
513
634
|
}
|
|
514
635
|
}
|
|
@@ -561,7 +682,10 @@ export class PluginsAdapter implements ConfigAdapter<PluginsSection> {
|
|
|
561
682
|
if (rel === '' || !isPathSafe(rel) || available.has(rel)) continue;
|
|
562
683
|
try {
|
|
563
684
|
if (await ctx.target.fs.exists(`${profileDir}/${rel}`)) available.add(rel);
|
|
564
|
-
} catch {
|
|
685
|
+
} catch (err) {
|
|
686
|
+
// 同上(t81 / t23):探测失败必须上抛,绝不把它降级成「声明不可满足」而写瘦 pnpm-workspace.yaml。
|
|
687
|
+
if (!isENOENT(err)) throw err;
|
|
688
|
+
}
|
|
565
689
|
}
|
|
566
690
|
const clean = sanitizePnpmWorkspacePatches(text, (rel) => available.has(rel));
|
|
567
691
|
try {
|
|
@@ -646,7 +770,9 @@ export class PluginsAdapter implements ConfigAdapter<PluginsSection> {
|
|
|
646
770
|
const data = ctx.sections.get('plugins') as PluginsSection | undefined;
|
|
647
771
|
const pl = data?.patch.find((p) => p.lineId === ref);
|
|
648
772
|
if (!pl) return { ok: false, message: msg('adapter.patchMissing', { ref }) };
|
|
649
|
-
|
|
773
|
+
// 层身份按**语义**映射到目标机:来源是别的 profile 层时不能照抄名字(目标机未必有那个
|
|
774
|
+
// profile,宿主门面会拒绝),一律落到目标机当前 profile 层(与计划项 target.file 同口径)。
|
|
775
|
+
await ctx.target.patchFile.applyPatchChanges(resolveWriteLayer(pl.file, ctx.target.profile), [
|
|
650
776
|
{ lineId: ref, raw: pl.raw, action: item.kind === 'Create' ? 'insert' : 'update' },
|
|
651
777
|
]);
|
|
652
778
|
return { ok: true, needsRestart: true, message: msg('adapter.patchWritten', { ref }) };
|
|
@@ -656,9 +782,31 @@ export class PluginsAdapter implements ConfigAdapter<PluginsSection> {
|
|
|
656
782
|
return validateJsonSection<PluginsSection>('plugins', data, msg, (section, issues) => {
|
|
657
783
|
if (!Array.isArray(section.plugins)) {
|
|
658
784
|
issues.push({ path: 'plugins', message: msg('adapter.validate.array', { subject: 'plugins' }), severity: 'error' });
|
|
785
|
+
} else {
|
|
786
|
+
// ui-F3:条目级形状必须与 analyzeImport 的假设对齐(它直接读 p.name / p.version)。
|
|
787
|
+
// 只校验「是数组」会放过 plugins:[null],analyzeImport 抛内部 TypeError → 整包被拦下。
|
|
788
|
+
for (const [i, raw] of section.plugins.entries()) {
|
|
789
|
+
const p = raw as unknown;
|
|
790
|
+
if (p === null || typeof p !== 'object') {
|
|
791
|
+
issues.push({ path: `plugins[${i}]`, message: msg('adapter.validate.recordObject', { subject: 'plugin' }), severity: 'error' });
|
|
792
|
+
continue;
|
|
793
|
+
}
|
|
794
|
+
const name = (p as { name?: unknown }).name;
|
|
795
|
+
if (typeof name !== 'string' || name === '') {
|
|
796
|
+
issues.push({ path: `plugins[${i}].name`, message: msg('adapter.validate.requiredField', { subject: 'plugins[].name' }), severity: 'error' });
|
|
797
|
+
}
|
|
798
|
+
}
|
|
659
799
|
}
|
|
660
800
|
if (section.patch !== undefined && !Array.isArray(section.patch)) {
|
|
661
801
|
issues.push({ path: 'patch', message: msg('adapter.validate.array', { subject: 'patch' }), severity: 'error' });
|
|
802
|
+
} else if (Array.isArray(section.patch)) {
|
|
803
|
+
// ui-F3:patch 条目在 analyzeImport 里被读 pl.raw / pl.lineId(isManagedElsewhere(pl.raw))。
|
|
804
|
+
for (const [i, raw] of section.patch.entries()) {
|
|
805
|
+
const pl = raw as unknown;
|
|
806
|
+
if (pl === null || typeof pl !== 'object') {
|
|
807
|
+
issues.push({ path: `patch[${i}]`, message: msg('adapter.validate.recordObject', { subject: 'patch line' }), severity: 'error' });
|
|
808
|
+
}
|
|
809
|
+
}
|
|
662
810
|
}
|
|
663
811
|
if (section.pnpmWorkspace !== undefined && section.pnpmWorkspace !== null && typeof section.pnpmWorkspace !== 'string') {
|
|
664
812
|
issues.push({ path: 'pnpmWorkspace', message: msg('adapter.validate.string', { subject: 'pnpmWorkspace' }), severity: 'error' });
|
|
@@ -101,6 +101,55 @@ test('prompts: 同行同内容 Skip / 不同 Conflict(update) / 目标无 → Wa
|
|
|
101
101
|
assert.equal(w.ok, true, 'Warning 项 applyItem 无副作用');
|
|
102
102
|
});
|
|
103
103
|
|
|
104
|
+
test('issue #71: profile 层 prompt 行也要导出(含 sourceFile),Create 落目标机当前 profile 层', async () => {
|
|
105
|
+
const PROFILE_REL = 'profiles/tauri/cordis.patch.yml';
|
|
106
|
+
const src = makeContext('win32', 'C:\\Users\\alice', 'tauri');
|
|
107
|
+
src.patchFile.bucket(PROFILE_REL).set('persona-line', LINES[0]!);
|
|
108
|
+
const adapter = new PromptsAdapter();
|
|
109
|
+
const exported = await adapter.export(src, { includeSecrets: false });
|
|
110
|
+
assert.equal(exported.data.prompts.length, 1, 'profile 层的 persona 必须在备份里(此前只读 home 层 → 恒为空)');
|
|
111
|
+
assert.equal(exported.data.prompts[0]?.sourceFile, PROFILE_REL, '必须记住来源层');
|
|
112
|
+
const sections = new Map([['prompts', exported.data]]);
|
|
113
|
+
|
|
114
|
+
// 目标机 profile 名不同(tauri → web):Create 必须落**目标机自己的** profile 层
|
|
115
|
+
const dst = makeContext('linux', '/home/bob', 'web');
|
|
116
|
+
const items = await adapter.analyzeImport(exported.data, makeImportContext(dst, sections));
|
|
117
|
+
assert.equal(items[0]?.kind, 'Create');
|
|
118
|
+
const r = await adapter.applyItem(items[0]!, makeImportContext(dst, sections));
|
|
119
|
+
assert.equal(r.ok, true);
|
|
120
|
+
assert.equal(dst.patchFile.bucket('profiles/web/cordis.patch.yml').has('persona-line'), true, '落目标机当前 profile 层');
|
|
121
|
+
assert.equal(dst.patchFile.lines.size, 0, '不得搬到 home 层');
|
|
122
|
+
|
|
123
|
+
// Update:目标机该行在 profile 层 → 合并回 profile 层(不搬家、不新建)
|
|
124
|
+
const dst2 = makeContext('linux', '/home/bob', 'web');
|
|
125
|
+
dst2.patchFile.bucket('profiles/web/cordis.patch.yml').set('persona-line', {
|
|
126
|
+
lineId: 'persona-line',
|
|
127
|
+
raw: { id: 'persona-line', name: '@deepseek-ai/dsh-web', config: { systemPrompt: { persona: 'Old persona.' } } },
|
|
128
|
+
});
|
|
129
|
+
const items2 = await adapter.analyzeImport(exported.data, makeImportContext(dst2, sections));
|
|
130
|
+
assert.equal(items2[0]?.kind, 'Conflict');
|
|
131
|
+
const r2 = await adapter.applyItem({ ...items2[0]!, kind: 'Update' }, makeImportContext(dst2, sections));
|
|
132
|
+
assert.equal(r2.ok, true);
|
|
133
|
+
const merged = dst2.patchFile.bucket('profiles/web/cordis.patch.yml').get('persona-line')?.raw as Record<string, unknown>;
|
|
134
|
+
const sp = (merged['config'] as Record<string, unknown>)['systemPrompt'] as Record<string, unknown>;
|
|
135
|
+
assert.equal(sp['persona'], 'You are a helpful coding assistant.');
|
|
136
|
+
assert.equal(dst2.patchFile.lines.size, 0, 'Update 同样不得在 home 层留下副本');
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test('issue #71: 同名行两层都有 → 只导出优先级更高的一层(home 覆盖 profile)', async () => {
|
|
140
|
+
const src = makeContext('win32', 'C:\\Users\\alice', 'tauri');
|
|
141
|
+
const profileRel = 'profiles/tauri/cordis.patch.yml';
|
|
142
|
+
src.patchFile.lines.set('plan-line', LINES[1]!);
|
|
143
|
+
src.patchFile.bucket(profileRel).set('plan-line', {
|
|
144
|
+
lineId: 'plan-line',
|
|
145
|
+
raw: { id: 'plan-line', name: 'plan-mode', config: { planMode: { sections: [{ name: 'plan', order: 1, text: 'Profile version.' }] } } },
|
|
146
|
+
});
|
|
147
|
+
const exported = await new PromptsAdapter().export(src, { includeSecrets: false });
|
|
148
|
+
assert.equal(exported.data.prompts.length, 1);
|
|
149
|
+
assert.equal(exported.data.prompts[0]?.text, 'Plan first, then code.', '生效值是 home 层');
|
|
150
|
+
assert.equal(exported.data.prompts[0]?.sourceFile, 'cordis.patch.yml');
|
|
151
|
+
});
|
|
152
|
+
|
|
104
153
|
test('prompts: validate', async () => {
|
|
105
154
|
const adapter = new PromptsAdapter();
|
|
106
155
|
const ok = await adapter.validate({ version: 1, prompts: [{ id: 'a', name: 'n', kind: 'systemPrompt', text: 't' }] });
|
package/src/adapters/prompts.ts
CHANGED
|
@@ -8,6 +8,9 @@
|
|
|
8
8
|
* - 目标无该行但存在同名 prompt → Conflict(更新其所在行);
|
|
9
9
|
* - 均不存在 → Warning(不编造行名自动创建,提示手动配置)。
|
|
10
10
|
* rules/commands 无独立存储(研究报告 §2.2),不实现。
|
|
11
|
+
*
|
|
12
|
+
* issue #71:数据源与 mcp 分区同因 —— patch 有 home 层与 profile 层两层,此前只读 home 层,
|
|
13
|
+
* 于是写进 profile 层的 systemPrompt / planMode 在备份里全部丢失。现在两层都读、写回原层。
|
|
11
14
|
*/
|
|
12
15
|
import { msgOf, zhMsg } from '../core/messages.ts';
|
|
13
16
|
import type { MsgFunc } from '../core/messages.ts';
|
|
@@ -16,19 +19,28 @@ import type {
|
|
|
16
19
|
ApplyResult, ConfigAdapter, ExportOptions, ExportSection, HostContext,
|
|
17
20
|
ImportContext, PlanItem, ValidationResult,
|
|
18
21
|
} from '../core/types.ts';
|
|
19
|
-
import {
|
|
22
|
+
import { locatePatchLineLayer, readEffectivePatchLines, resolveWriteLayer } from '../core/patch-layers.ts';
|
|
20
23
|
import { sectionMeta } from '../schema/section-registry.ts';
|
|
21
24
|
import { validateJsonSection } from './json-section.ts';
|
|
22
25
|
|
|
23
|
-
/** 导出记录:PromptEntry
|
|
26
|
+
/** 导出记录:PromptEntry 之外记录来源行名与所在层(导入重建 / 写回定位用) */
|
|
24
27
|
export interface PromptExportEntry extends PromptEntry {
|
|
25
28
|
sourceLineName?: string;
|
|
29
|
+
/**
|
|
30
|
+
* 来源 patch 层(相对 homeDir 的 POSIX 路径;见 core/patch-layers.ts)。
|
|
31
|
+
* 缺省 = 用户层(旧备份包没有该字段 → 按旧行为写回 home 层)。
|
|
32
|
+
*/
|
|
33
|
+
sourceFile?: string;
|
|
26
34
|
}
|
|
27
35
|
|
|
28
36
|
export interface PromptsExportSection { version: 1; prompts: PromptExportEntry[]; }
|
|
29
37
|
|
|
30
|
-
/**
|
|
31
|
-
|
|
38
|
+
/**
|
|
39
|
+
* 从 patch 行 config 中提取 prompt 条目(systemPrompt.persona / planMode.sections[].text)。
|
|
40
|
+
*
|
|
41
|
+
* 行对象可带 `file`(层身份):带上时逐条目记录 `sourceFile`,导入写回落回原层。
|
|
42
|
+
*/
|
|
43
|
+
export function extractPrompts(lines: { lineId: string; raw: unknown; file?: string }[]): PromptExportEntry[] {
|
|
32
44
|
const prompts: PromptExportEntry[] = [];
|
|
33
45
|
for (const line of lines) {
|
|
34
46
|
for (const entry of entriesOf(line.raw)) {
|
|
@@ -39,11 +51,11 @@ export function extractPrompts(lines: { lineId: string; raw: unknown }[]): Promp
|
|
|
39
51
|
|
|
40
52
|
const sp = c['systemPrompt'];
|
|
41
53
|
if (typeof sp === 'string' && sp.trim() !== '') {
|
|
42
|
-
prompts.push({ id: `prompt:${line.lineId}:persona`, name: `${line.lineId}:persona`, kind: 'systemPrompt', text: sp, sourceLineId: line.lineId, sourceLineName });
|
|
54
|
+
prompts.push({ id: `prompt:${line.lineId}:persona`, name: `${line.lineId}:persona`, kind: 'systemPrompt', text: sp, sourceLineId: line.lineId, sourceLineName, ...(line.file !== undefined ? { sourceFile: line.file } : {}) });
|
|
43
55
|
} else if (sp !== null && typeof sp === 'object' && typeof (sp as Record<string, unknown>)['persona'] === 'string') {
|
|
44
56
|
const persona = (sp as Record<string, unknown>)['persona'] as string;
|
|
45
57
|
if (persona.trim() !== '') {
|
|
46
|
-
prompts.push({ id: `prompt:${line.lineId}:persona`, name: `${line.lineId}:persona`, kind: 'systemPrompt', text: persona, sourceLineId: line.lineId, sourceLineName });
|
|
58
|
+
prompts.push({ id: `prompt:${line.lineId}:persona`, name: `${line.lineId}:persona`, kind: 'systemPrompt', text: persona, sourceLineId: line.lineId, sourceLineName, ...(line.file !== undefined ? { sourceFile: line.file } : {}) });
|
|
47
59
|
}
|
|
48
60
|
}
|
|
49
61
|
|
|
@@ -53,7 +65,7 @@ export function extractPrompts(lines: { lineId: string; raw: unknown }[]): Promp
|
|
|
53
65
|
for (const [i, s] of sections.entries()) {
|
|
54
66
|
if (s === null || typeof s !== 'object' || typeof s['text'] !== 'string' || (s['text'] as string).trim() === '') continue;
|
|
55
67
|
const name = typeof s['name'] === 'string' && s['name'] !== '' ? s['name'] : `${line.lineId}:section${i}`;
|
|
56
|
-
prompts.push({ id: `prompt:${line.lineId}:${name}`, name, kind: 'planMode', text: s['text'] as string, sourceLineId: line.lineId, sourceLineName });
|
|
68
|
+
prompts.push({ id: `prompt:${line.lineId}:${name}`, name, kind: 'planMode', text: s['text'] as string, sourceLineId: line.lineId, sourceLineName, ...(line.file !== undefined ? { sourceFile: line.file } : {}) });
|
|
57
69
|
}
|
|
58
70
|
}
|
|
59
71
|
}
|
|
@@ -116,13 +128,12 @@ export class PromptsAdapter implements ConfigAdapter<PromptsExportSection> {
|
|
|
116
128
|
|
|
117
129
|
async export(ctx: HostContext, _options: ExportOptions): Promise<ExportSection<PromptsExportSection>> {
|
|
118
130
|
const warnings: string[] = [];
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
warnings.push(msgOf(ctx)('adapter.patchReadFailedPrompts', { reason: err instanceof Error ? err.message : String(err) }));
|
|
131
|
+
// issue #71:两层都读(home 层优先),否则 profile 层里的 persona / planMode 全部丢失
|
|
132
|
+
const read = await readEffectivePatchLines(ctx.patchFile, ctx.profile);
|
|
133
|
+
for (const f of read.failures) {
|
|
134
|
+
warnings.push(msgOf(ctx)('adapter.patchReadFailedPrompts', { reason: `${f.file}: ${f.reason}` }));
|
|
124
135
|
}
|
|
125
|
-
const prompts = extractPrompts(lines);
|
|
136
|
+
const prompts = extractPrompts(read.lines);
|
|
126
137
|
return {
|
|
127
138
|
sectionId: 'prompts',
|
|
128
139
|
data: { version: 1, prompts },
|
|
@@ -134,7 +145,8 @@ export class PromptsAdapter implements ConfigAdapter<PromptsExportSection> {
|
|
|
134
145
|
async analyzeImport(data: PromptsExportSection, ctx: ImportContext): Promise<PlanItem[]> {
|
|
135
146
|
const msg = ctx.msg;
|
|
136
147
|
const items: PlanItem[] = [];
|
|
137
|
-
|
|
148
|
+
// issue #71:目标行跨两层;同一 lineId 只保留优先级最高的一层(与 DSH 合并序一致)
|
|
149
|
+
const targetLines = (await readEffectivePatchLines(ctx.target.patchFile, ctx.target.profile)).lines;
|
|
138
150
|
const targetPrompts = extractPrompts(targetLines);
|
|
139
151
|
for (const p of data.prompts) {
|
|
140
152
|
const id = p.id;
|
|
@@ -149,7 +161,8 @@ export class PromptsAdapter implements ConfigAdapter<PromptsExportSection> {
|
|
|
149
161
|
description: msg('adapter.promptDiff', { name: p.name }),
|
|
150
162
|
detail: `行 ${p.sourceLineId} current=${JSON.stringify(sameName.text).slice(0, 80)} imported=${JSON.stringify(p.text).slice(0, 80)}`,
|
|
151
163
|
severity: 'warning',
|
|
152
|
-
|
|
164
|
+
// 层身份随计划项走:快照按这一层记原行、回滚才能写回原文件(issue #71)
|
|
165
|
+
target: { adapter: 'prompts', ref: p.sourceLineId ?? '', file: targetLine.file },
|
|
153
166
|
});
|
|
154
167
|
}
|
|
155
168
|
} else if (sameName) {
|
|
@@ -161,7 +174,10 @@ export class PromptsAdapter implements ConfigAdapter<PromptsExportSection> {
|
|
|
161
174
|
id, kind: 'Conflict', adapter: 'prompts',
|
|
162
175
|
description: msg('adapter.promptDiffWithLine', { name: p.name, line: sameName.sourceLineId ?? '' }),
|
|
163
176
|
severity: 'warning',
|
|
164
|
-
target: {
|
|
177
|
+
target: {
|
|
178
|
+
adapter: 'prompts', ref: sameName.sourceLineId ?? '',
|
|
179
|
+
file: targetLines.find((l) => l.lineId === sameName.sourceLineId)?.file,
|
|
180
|
+
},
|
|
165
181
|
});
|
|
166
182
|
}
|
|
167
183
|
} else if (p.sourceLineName) {
|
|
@@ -170,7 +186,7 @@ export class PromptsAdapter implements ConfigAdapter<PromptsExportSection> {
|
|
|
170
186
|
items.push({
|
|
171
187
|
id, kind: 'Create', adapter: 'prompts',
|
|
172
188
|
description: msg('adapter.promptCreate', { name: p.name, line: lineId }), severity: 'info',
|
|
173
|
-
target: { adapter: 'prompts', ref: lineId },
|
|
189
|
+
target: { adapter: 'prompts', ref: lineId, file: resolveWriteLayer(p.sourceFile, ctx.target.profile) },
|
|
174
190
|
});
|
|
175
191
|
} else {
|
|
176
192
|
items.push({
|
|
@@ -194,17 +210,22 @@ export class PromptsAdapter implements ConfigAdapter<PromptsExportSection> {
|
|
|
194
210
|
// Create:目标无来源行 → 用记录的行名重建 patch 行(insert)
|
|
195
211
|
if (item.kind === 'Create') {
|
|
196
212
|
const raw = buildPromptLine(ref, prompt);
|
|
197
|
-
|
|
213
|
+
// issue #71:备份里记了来源层就落「同语义层」(来源 profile 层 → 目标机当前 profile 层,
|
|
214
|
+
// 跨机器照抄 profile 名会被宿主门面拒绝);旧备份包无该字段 → 落 home 层(与改造前一致)
|
|
215
|
+
await ctx.target.patchFile.applyPatchChanges(resolveWriteLayer(prompt.sourceFile, ctx.target.profile), [
|
|
198
216
|
{ lineId: ref, raw, action: 'insert' },
|
|
199
217
|
]);
|
|
200
218
|
return { ok: true, needsRestart: true, message: msg('adapter.promptCreated', { name: prompt.name, ref }) };
|
|
201
219
|
}
|
|
202
220
|
// Update / Conflict(useImported):合并进目标行 config
|
|
203
|
-
|
|
221
|
+
// issue #71:先定位原行所在层(两层都没有 → home 层),再读该层的原行合并 —— 绝不把
|
|
222
|
+
// profile 层的行搬到 home 层(同一 lineId 两层各一份会让 DSH 的合并语义变得不可预期)
|
|
223
|
+
const file = await locatePatchLineLayer(ctx.target.patchFile, ctx.target.profile, ref);
|
|
224
|
+
const lines = await ctx.target.patchFile.readPatchLines(file);
|
|
204
225
|
const line = lines.find((l) => l.lineId === ref);
|
|
205
226
|
if (!line) return { ok: false, message: msg('adapter.patchLineMissing', { ref }) };
|
|
206
227
|
const newRaw = mergePromptIntoLine(line.raw, prompt);
|
|
207
|
-
await ctx.target.patchFile.applyPatchChanges(
|
|
228
|
+
await ctx.target.patchFile.applyPatchChanges(file, [
|
|
208
229
|
{ lineId: ref, raw: newRaw, action: 'update' },
|
|
209
230
|
]);
|
|
210
231
|
return { ok: true, needsRestart: true, message: msg('adapter.promptWritten', { name: prompt.name, ref }) };
|
|
@@ -124,3 +124,19 @@ test('providers: 多 route 共享 secrets 引用不触发强化 scanner 循环
|
|
|
124
124
|
assert.equal(sanitizedProviders['openai']?.['apiKeyEnv'], 'OPENAI_API_KEY', '引用字段 apiKeyEnv 保留');
|
|
125
125
|
assert.equal(sanitizedProviders['openai']?.['baseURL'], 'https://api.openai.com', '非敏感字段保留');
|
|
126
126
|
});
|
|
127
|
+
|
|
128
|
+
/* ---------------- issue #73:与 settings 同型的空值判定回归 ---------------- */
|
|
129
|
+
|
|
130
|
+
test('providers: 两端 namespace 值皆空({})→ Skip(此前恒判 Create,写入是空写入)', async () => {
|
|
131
|
+
const adapter = new ProvidersAdapter();
|
|
132
|
+
const src = makeContext('win32', 'C:\\Users\\alice');
|
|
133
|
+
src.settings.ns.set('llm-pi-ai', { value: {}, revision: 0, secrets: [] });
|
|
134
|
+
const exported = await adapter.export(src, { includeSecrets: false });
|
|
135
|
+
const sections = new Map([['providers', exported.data]]);
|
|
136
|
+
|
|
137
|
+
const dst = makeContext('linux', '/home/bob');
|
|
138
|
+
dst.settings.ns.set('llm-pi-ai', { value: {}, revision: 0, secrets: [] });
|
|
139
|
+
const items = await adapter.analyzeImport(exported.data, makeImportContext(dst, sections));
|
|
140
|
+
assert.equal(items.length, 1);
|
|
141
|
+
assert.equal(items[0]?.kind, 'Skip', '两端皆 {} → Skip');
|
|
142
|
+
});
|
|
@@ -110,15 +110,16 @@ export class ProvidersAdapter implements ConfigAdapter<ProviderExportSection> {
|
|
|
110
110
|
description: msg('adapter.providerUnregistered', { route, ns: entry.namespace }),
|
|
111
111
|
severity: 'warning', target: { adapter: 'providers', ref: entry.namespace },
|
|
112
112
|
});
|
|
113
|
+
} else if (current !== null && isDeepStrictEqual(current.value, entry.raw ?? stripEntry(entry))) {
|
|
114
|
+
// 值相等优先于「目标为空」——与 settings.planNamespaceItems 同一口径与顺序(issue #73)
|
|
115
|
+
items.push({ id, kind: 'Skip', adapter: 'providers', description: msg('adapter.providerSame', { route }), severity: 'info' });
|
|
113
116
|
} else if (current === null || isEmptyValue(current.value)) {
|
|
114
|
-
//
|
|
117
|
+
// 目标已注册但为空**且导入值非空** → 初始化(Create)
|
|
115
118
|
items.push({
|
|
116
119
|
id, kind: 'Create', adapter: 'providers',
|
|
117
120
|
description: msg('adapter.providerCreate', { route, ns: entry.namespace }), severity: 'info',
|
|
118
121
|
target: { adapter: 'providers', ref: entry.namespace },
|
|
119
122
|
});
|
|
120
|
-
} else if (isDeepStrictEqual(current.value, entry.raw ?? stripEntry(entry))) {
|
|
121
|
-
items.push({ id, kind: 'Skip', adapter: 'providers', description: msg('adapter.providerSame', { route }), severity: 'info' });
|
|
122
123
|
} else {
|
|
123
124
|
items.push({
|
|
124
125
|
id, kind: 'Conflict', adapter: 'providers',
|