@logictan/dsh-config-manager 0.1.60
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/LICENSE +21 -0
- package/README.md +105 -0
- package/cordis.patch.yml +21 -0
- package/lib/adapters/agent-instructions.d.ts +13 -0
- package/lib/adapters/agent-instructions.js +46 -0
- package/lib/adapters/agent-presets.d.ts +13 -0
- package/lib/adapters/agent-presets.js +14 -0
- package/lib/adapters/credentials.d.ts +37 -0
- package/lib/adapters/credentials.js +122 -0
- package/lib/adapters/file-collection.d.ts +15 -0
- package/lib/adapters/file-collection.js +127 -0
- package/lib/adapters/index.d.ts +57 -0
- package/lib/adapters/index.js +55 -0
- package/lib/adapters/link-report.d.ts +16 -0
- package/lib/adapters/link-report.js +47 -0
- package/lib/adapters/mcp.d.ts +28 -0
- package/lib/adapters/mcp.js +178 -0
- package/lib/adapters/plugin-files.d.ts +18 -0
- package/lib/adapters/plugin-files.js +142 -0
- package/lib/adapters/plugins.d.ts +41 -0
- package/lib/adapters/plugins.js +603 -0
- package/lib/adapters/pnpm-workspace.d.ts +26 -0
- package/lib/adapters/pnpm-workspace.js +147 -0
- package/lib/adapters/prompts.d.ts +30 -0
- package/lib/adapters/prompts.js +231 -0
- package/lib/adapters/providers.d.ts +34 -0
- package/lib/adapters/providers.js +155 -0
- package/lib/adapters/self.d.ts +17 -0
- package/lib/adapters/self.js +71 -0
- package/lib/adapters/sessions.d.ts +14 -0
- package/lib/adapters/sessions.js +15 -0
- package/lib/adapters/settings.d.ts +28 -0
- package/lib/adapters/settings.js +165 -0
- package/lib/adapters/skills.d.ts +13 -0
- package/lib/adapters/skills.js +14 -0
- package/lib/adapters/test-helpers.d.ts +125 -0
- package/lib/adapters/test-helpers.js +236 -0
- package/lib/adapters/ui.d.ts +27 -0
- package/lib/adapters/ui.js +89 -0
- package/lib/adapters/workspaces.d.ts +13 -0
- package/lib/adapters/workspaces.js +106 -0
- package/lib/client.d.ts +1026 -0
- package/lib/client.js +10047 -0
- package/lib/core/analyzer.d.ts +99 -0
- package/lib/core/analyzer.js +934 -0
- package/lib/core/backup.d.ts +138 -0
- package/lib/core/backup.js +513 -0
- package/lib/core/boot-rescue.d.ts +146 -0
- package/lib/core/boot-rescue.js +618 -0
- package/lib/core/cache-cleaner.d.ts +31 -0
- package/lib/core/cache-cleaner.js +95 -0
- package/lib/core/config-lifecycle.d.ts +180 -0
- package/lib/core/config-lifecycle.js +481 -0
- package/lib/core/config-snapshot.d.ts +135 -0
- package/lib/core/config-snapshot.js +424 -0
- package/lib/core/config-state.d.ts +70 -0
- package/lib/core/config-state.js +177 -0
- package/lib/core/consult-source.d.ts +58 -0
- package/lib/core/consult-source.js +260 -0
- package/lib/core/crash-report.d.ts +84 -0
- package/lib/core/crash-report.js +317 -0
- package/lib/core/exporter.d.ts +68 -0
- package/lib/core/exporter.js +375 -0
- package/lib/core/ghost-sweep.d.ts +19 -0
- package/lib/core/ghost-sweep.js +52 -0
- package/lib/core/importer.d.ts +60 -0
- package/lib/core/importer.js +29 -0
- package/lib/core/index.d.ts +14 -0
- package/lib/core/index.js +14 -0
- package/lib/core/journal.d.ts +197 -0
- package/lib/core/journal.js +418 -0
- package/lib/core/local-plugin-host.d.ts +35 -0
- package/lib/core/local-plugin-host.js +120 -0
- package/lib/core/local-plugin-pack.d.ts +135 -0
- package/lib/core/local-plugin-pack.js +278 -0
- package/lib/core/messages.d.ts +310 -0
- package/lib/core/messages.js +621 -0
- package/lib/core/migration-consult.d.ts +117 -0
- package/lib/core/migration-consult.js +363 -0
- package/lib/core/migration-history.d.ts +153 -0
- package/lib/core/migration-history.js +411 -0
- package/lib/core/model-tools.d.ts +50 -0
- package/lib/core/model-tools.js +148 -0
- package/lib/core/msg-types.d.ts +8 -0
- package/lib/core/msg-types.js +6 -0
- package/lib/core/phase3-child-crash.d.ts +1 -0
- package/lib/core/phase3-child-crash.js +87 -0
- package/lib/core/phase3-host.d.ts +138 -0
- package/lib/core/phase3-host.js +359 -0
- package/lib/core/phase3-prod-child.d.ts +1 -0
- package/lib/core/phase3-prod-child.js +35 -0
- package/lib/core/phase4-crash-child.d.ts +1 -0
- package/lib/core/phase4-crash-child.js +103 -0
- package/lib/core/plugin-cli.d.ts +150 -0
- package/lib/core/plugin-cli.js +497 -0
- package/lib/core/reconcile.d.ts +120 -0
- package/lib/core/reconcile.js +389 -0
- package/lib/core/recovery-orchestrator.d.ts +94 -0
- package/lib/core/recovery-orchestrator.js +327 -0
- package/lib/core/restore.d.ts +162 -0
- package/lib/core/restore.js +703 -0
- package/lib/core/rollback.d.ts +12 -0
- package/lib/core/rollback.js +145 -0
- package/lib/core/run-registry.d.ts +85 -0
- package/lib/core/run-registry.js +157 -0
- package/lib/core/startup-barrier.d.ts +53 -0
- package/lib/core/startup-barrier.js +77 -0
- package/lib/core/types.d.ts +515 -0
- package/lib/core/types.js +27 -0
- package/lib/core/undo.d.ts +73 -0
- package/lib/core/undo.js +87 -0
- package/lib/core/validator.d.ts +18 -0
- package/lib/core/validator.js +77 -0
- package/lib/core/verify-recovery.d.ts +21 -0
- package/lib/core/verify-recovery.js +256 -0
- package/lib/core/watcher.d.ts +101 -0
- package/lib/core/watcher.js +219 -0
- package/lib/index.d.ts +238 -0
- package/lib/index.js +2764 -0
- package/lib/migrations/index.d.ts +29 -0
- package/lib/migrations/index.js +56 -0
- package/lib/migrations/v1-to-v2.d.ts +9 -0
- package/lib/migrations/v1-to-v2.js +10 -0
- package/lib/profiles/index.d.ts +4 -0
- package/lib/profiles/index.js +5 -0
- package/lib/profiles/profile-manager.d.ts +106 -0
- package/lib/profiles/profile-manager.js +543 -0
- package/lib/schema/config.d.ts +28 -0
- package/lib/schema/config.js +165 -0
- package/lib/schema/index.d.ts +24 -0
- package/lib/schema/index.js +26 -0
- package/lib/schema/manifest.d.ts +28 -0
- package/lib/schema/manifest.js +109 -0
- package/lib/schema/tombstones.d.ts +46 -0
- package/lib/schema/tombstones.js +64 -0
- package/lib/schema/types.d.ts +232 -0
- package/lib/schema/types.js +8 -0
- package/lib/schema/versions.d.ts +27 -0
- package/lib/schema/versions.js +50 -0
- package/lib/security/encryption.d.ts +85 -0
- package/lib/security/encryption.js +279 -0
- package/lib/security/index.d.ts +16 -0
- package/lib/security/index.js +17 -0
- package/lib/security/integrity.d.ts +30 -0
- package/lib/security/integrity.js +78 -0
- package/lib/security/redaction.d.ts +26 -0
- package/lib/security/redaction.js +95 -0
- package/lib/security/secret-scanner.d.ts +86 -0
- package/lib/security/secret-scanner.js +404 -0
- package/lib/security/vault.d.ts +59 -0
- package/lib/security/vault.js +151 -0
- package/lib/security/zip-security.d.ts +27 -0
- package/lib/security/zip-security.js +154 -0
- package/lib/sync/autosync-config.d.ts +39 -0
- package/lib/sync/autosync-config.js +151 -0
- package/lib/sync/autosync-scheduler.d.ts +139 -0
- package/lib/sync/autosync-scheduler.js +564 -0
- package/lib/sync/fs.d.ts +18 -0
- package/lib/sync/fs.js +49 -0
- package/lib/sync/git/git-transport.d.ts +100 -0
- package/lib/sync/git/git-transport.js +437 -0
- package/lib/sync/github-auth.d.ts +131 -0
- package/lib/sync/github-auth.js +264 -0
- package/lib/sync/layout.d.ts +43 -0
- package/lib/sync/layout.js +203 -0
- package/lib/sync/snapshot-json.d.ts +9 -0
- package/lib/sync/snapshot-json.js +107 -0
- package/lib/sync/sync-config.d.ts +81 -0
- package/lib/sync/sync-config.js +281 -0
- package/lib/sync/sync-engine.d.ts +269 -0
- package/lib/sync/sync-engine.js +591 -0
- package/lib/sync/sync-history.d.ts +42 -0
- package/lib/sync/sync-history.js +78 -0
- package/lib/sync/sync-selection.d.ts +29 -0
- package/lib/sync/sync-selection.js +127 -0
- package/lib/sync/sync-session.d.ts +40 -0
- package/lib/sync/sync-session.js +54 -0
- package/lib/sync/sync-state.d.ts +37 -0
- package/lib/sync/sync-state.js +108 -0
- package/lib/sync/transport.d.ts +75 -0
- package/lib/sync/transport.js +55 -0
- package/lib/sync/ui-prefs.d.ts +34 -0
- package/lib/sync/ui-prefs.js +109 -0
- package/lib/sync/webdav/webdav-transport.d.ts +77 -0
- package/lib/sync/webdav/webdav-transport.js +451 -0
- package/lib/ui/errors.d.ts +19 -0
- package/lib/ui/errors.js +81 -0
- package/lib/ui/export-flow.d.ts +82 -0
- package/lib/ui/export-flow.js +98 -0
- package/lib/ui/i18n.d.ts +289 -0
- package/lib/ui/i18n.js +581 -0
- package/lib/ui/migration-consult-view.d.ts +91 -0
- package/lib/ui/migration-consult-view.js +115 -0
- package/lib/ui/progress.d.ts +38 -0
- package/lib/ui/progress.js +87 -0
- package/lib/ui/report.d.ts +23 -0
- package/lib/ui/report.js +153 -0
- package/lib/ui/test-helpers.d.ts +69 -0
- package/lib/ui/test-helpers.js +154 -0
- package/lib/ui/types.d.ts +257 -0
- package/lib/ui/types.js +21 -0
- package/lib/utils/atomic-write.d.ts +88 -0
- package/lib/utils/atomic-write.js +399 -0
- package/lib/utils/env-lock.d.ts +393 -0
- package/lib/utils/env-lock.js +930 -0
- package/lib/utils/hashing.d.ts +17 -0
- package/lib/utils/hashing.js +41 -0
- package/lib/utils/json.d.ts +24 -0
- package/lib/utils/json.js +76 -0
- package/lib/utils/logger.d.ts +39 -0
- package/lib/utils/logger.js +82 -0
- package/lib/utils/paths.d.ts +42 -0
- package/lib/utils/paths.js +189 -0
- package/lib/utils/proxy.d.ts +69 -0
- package/lib/utils/proxy.js +455 -0
- package/lib/utils/recursive-walk.d.ts +24 -0
- package/lib/utils/recursive-walk.js +134 -0
- package/lib/utils/zip.d.ts +54 -0
- package/lib/utils/zip.js +285 -0
- package/package.json +188 -0
- package/src/adapters/agent-instructions.ts +48 -0
- package/src/adapters/agent-presets.ts +14 -0
- package/src/adapters/credentials.test.ts +90 -0
- package/src/adapters/credentials.ts +143 -0
- package/src/adapters/file-collection.ts +137 -0
- package/src/adapters/files.test.ts +353 -0
- package/src/adapters/index.ts +94 -0
- package/src/adapters/link-report.test.ts +65 -0
- package/src/adapters/link-report.ts +61 -0
- package/src/adapters/mcp.test.ts +96 -0
- package/src/adapters/mcp.ts +185 -0
- package/src/adapters/plugin-files.ts +146 -0
- package/src/adapters/plugins.test.ts +326 -0
- package/src/adapters/plugins.ts +615 -0
- package/src/adapters/pnpm-workspace.test.ts +87 -0
- package/src/adapters/pnpm-workspace.ts +150 -0
- package/src/adapters/prompts.test.ts +110 -0
- package/src/adapters/prompts.ts +230 -0
- package/src/adapters/providers.test.ts +126 -0
- package/src/adapters/providers.ts +173 -0
- package/src/adapters/roundtrip.test.ts +190 -0
- package/src/adapters/self.test.ts +92 -0
- package/src/adapters/self.ts +75 -0
- package/src/adapters/sessions.ts +15 -0
- package/src/adapters/settings.test.ts +100 -0
- package/src/adapters/settings.ts +187 -0
- package/src/adapters/skills.ts +14 -0
- package/src/adapters/test-helpers.ts +248 -0
- package/src/adapters/ui.test.ts +57 -0
- package/src/adapters/ui.ts +108 -0
- package/src/adapters/workspaces.test.ts +106 -0
- package/src/adapters/workspaces.ts +109 -0
- package/src/client/ConfigManagerSection.tsx +90 -0
- package/src/client/api.ts +66 -0
- package/src/client/client-types.ts +18 -0
- package/src/client/common/ErrorBanner.tsx +73 -0
- package/src/client/common/Icon.tsx +129 -0
- package/src/client/common/Modal.tsx +173 -0
- package/src/client/common/ToastViewport.tsx +73 -0
- package/src/client/common/toast-store.test.ts +192 -0
- package/src/client/common/toast-store.ts +181 -0
- package/src/client/common/toast-types.ts +7 -0
- package/src/client/common/ui.tsx +342 -0
- package/src/client/config-manager.module.css +2971 -0
- package/src/client/css-modules.d.ts +8 -0
- package/src/client/index.ts +65 -0
- package/src/client/locales.ts +56 -0
- package/src/client/lucide-icons.d.ts +38 -0
- package/src/client/run-store.test.ts +223 -0
- package/src/client/run-store.ts +372 -0
- package/src/client/sync/SyncConfirmView.tsx +379 -0
- package/src/client/sync/SyncHistoryView.test.ts +40 -0
- package/src/client/sync/SyncHistoryView.tsx +220 -0
- package/src/client/sync/SyncSettingsView.tsx +1535 -0
- package/src/client/sync/history-model.test.ts +194 -0
- package/src/client/sync/history-model.ts +195 -0
- package/src/client/sync/sync-api.test.ts +585 -0
- package/src/client/sync/sync-api.ts +543 -0
- package/src/client/sync/sync-locales.ts +416 -0
- package/src/client/sync/sync-push-preview.test.ts +52 -0
- package/src/client/sync/sync-view-v2.test.ts +213 -0
- package/src/client/sync/sync-view.test.ts +487 -0
- package/src/client/sync/sync-view.ts +759 -0
- package/src/core/analyzer.ts +1068 -0
- package/src/core/backup.ts +605 -0
- package/src/core/boot-rescue.test.ts +847 -0
- package/src/core/boot-rescue.ts +737 -0
- package/src/core/cache-cleaner.test.ts +169 -0
- package/src/core/cache-cleaner.ts +121 -0
- package/src/core/config-lifecycle.test.ts +727 -0
- package/src/core/config-lifecycle.ts +573 -0
- package/src/core/config-snapshot.test.ts +369 -0
- package/src/core/config-snapshot.ts +522 -0
- package/src/core/config-state.test.ts +381 -0
- package/src/core/config-state.ts +207 -0
- package/src/core/consult-source.test.ts +199 -0
- package/src/core/consult-source.ts +293 -0
- package/src/core/crash-report.test.ts +380 -0
- package/src/core/crash-report.ts +369 -0
- package/src/core/exporter.ts +417 -0
- package/src/core/ghost-sweep.test.ts +94 -0
- package/src/core/ghost-sweep.ts +67 -0
- package/src/core/importer.ts +83 -0
- package/src/core/index.ts +56 -0
- package/src/core/journal.test.ts +257 -0
- package/src/core/journal.ts +518 -0
- package/src/core/local-plugin-host.ts +152 -0
- package/src/core/local-plugin-pack.test.ts +359 -0
- package/src/core/local-plugin-pack.ts +363 -0
- package/src/core/messages.test.ts +54 -0
- package/src/core/messages.ts +664 -0
- package/src/core/migration-consult.test.ts +315 -0
- package/src/core/migration-consult.ts +494 -0
- package/src/core/migration-history.test.ts +319 -0
- package/src/core/migration-history.ts +484 -0
- package/src/core/model-tools.test.ts +190 -0
- package/src/core/model-tools.ts +208 -0
- package/src/core/msg-types.ts +10 -0
- package/src/core/phase3-child-crash.ts +95 -0
- package/src/core/phase3-host.ts +411 -0
- package/src/core/phase3-p1.test.ts +212 -0
- package/src/core/phase3-p2b-fingerprint.test.ts +189 -0
- package/src/core/phase3-prod-child.ts +36 -0
- package/src/core/phase3-production-integration.test.ts +286 -0
- package/src/core/phase4-crash-child.ts +101 -0
- package/src/core/phase4-crash-injection.test.ts +166 -0
- package/src/core/plugin-cli.fs.test.ts +184 -0
- package/src/core/plugin-cli.test.ts +137 -0
- package/src/core/plugin-cli.ts +563 -0
- package/src/core/reconcile.test.ts +392 -0
- package/src/core/reconcile.ts +498 -0
- package/src/core/recovery-orchestrator.test.ts +145 -0
- package/src/core/recovery-orchestrator.ts +366 -0
- package/src/core/restore-manage.test.ts +94 -0
- package/src/core/restore.ts +849 -0
- package/src/core/rollback.ts +151 -0
- package/src/core/run-progress.test.ts +237 -0
- package/src/core/run-registry.test.ts +232 -0
- package/src/core/run-registry.ts +209 -0
- package/src/core/smoke.test.ts +842 -0
- package/src/core/startup-barrier.ts +99 -0
- package/src/core/types.ts +542 -0
- package/src/core/undo.ts +116 -0
- package/src/core/validator.ts +84 -0
- package/src/core/verify-recovery.ts +265 -0
- package/src/core/watcher.test.ts +326 -0
- package/src/core/watcher.ts +263 -0
- package/src/index.facade.test.ts +265 -0
- package/src/index.sync.test.ts +217 -0
- package/src/index.ts +2975 -0
- package/src/migrations/index.ts +71 -0
- package/src/migrations/v1-to-v2.ts +17 -0
- package/src/profiles/index.ts +8 -0
- package/src/profiles/profile-manager.test.ts +282 -0
- package/src/profiles/profile-manager.ts +630 -0
- package/src/schema/config.ts +165 -0
- package/src/schema/index.ts +71 -0
- package/src/schema/manifest-schema.test.ts +519 -0
- package/src/schema/manifest.ts +124 -0
- package/src/schema/tombstones.test.ts +131 -0
- package/src/schema/tombstones.ts +89 -0
- package/src/schema/types.ts +245 -0
- package/src/schema/versions.ts +55 -0
- package/src/security/encryption.ts +335 -0
- package/src/security/index.ts +16 -0
- package/src/security/integrity.ts +102 -0
- package/src/security/redaction.ts +101 -0
- package/src/security/secret-scanner.ts +445 -0
- package/src/security/security.test.ts +1215 -0
- package/src/security/vault.test.ts +164 -0
- package/src/security/vault.ts +198 -0
- package/src/security/zip-security.ts +180 -0
- package/src/sync/autosync-config.test.ts +147 -0
- package/src/sync/autosync-config.ts +179 -0
- package/src/sync/autosync-scheduler.test.ts +490 -0
- package/src/sync/autosync-scheduler.ts +646 -0
- package/src/sync/fs.ts +48 -0
- package/src/sync/git/git-transport.test.ts +673 -0
- package/src/sync/git/git-transport.ts +491 -0
- package/src/sync/github-auth.test.ts +280 -0
- package/src/sync/github-auth.ts +356 -0
- package/src/sync/layout.test.ts +270 -0
- package/src/sync/layout.ts +222 -0
- package/src/sync/snapshot-json.test.ts +119 -0
- package/src/sync/snapshot-json.ts +128 -0
- package/src/sync/sync-config.test.ts +310 -0
- package/src/sync/sync-config.ts +299 -0
- package/src/sync/sync-engine.test.ts +1074 -0
- package/src/sync/sync-engine.ts +754 -0
- package/src/sync/sync-history.test.ts +116 -0
- package/src/sync/sync-history.ts +120 -0
- package/src/sync/sync-selection.test.ts +186 -0
- package/src/sync/sync-selection.ts +143 -0
- package/src/sync/sync-session.test.ts +143 -0
- package/src/sync/sync-session.ts +77 -0
- package/src/sync/sync-state.test.ts +164 -0
- package/src/sync/sync-state.ts +131 -0
- package/src/sync/transport.test.ts +111 -0
- package/src/sync/transport.ts +118 -0
- package/src/sync/ui-prefs.test.ts +152 -0
- package/src/sync/ui-prefs.ts +135 -0
- package/src/sync/webdav/webdav-transport.test.ts +871 -0
- package/src/sync/webdav/webdav-transport.ts +535 -0
- package/src/ui/errors.test.ts +47 -0
- package/src/ui/errors.ts +100 -0
- package/src/ui/export-flow.test.ts +87 -0
- package/src/ui/export-flow.ts +156 -0
- package/src/ui/i18n.ts +590 -0
- package/src/ui/migration-consult-view.test.ts +151 -0
- package/src/ui/migration-consult-view.ts +178 -0
- package/src/ui/progress.test.ts +47 -0
- package/src/ui/progress.ts +102 -0
- package/src/ui/report.test.ts +101 -0
- package/src/ui/report.ts +146 -0
- package/src/ui/test-helpers.ts +184 -0
- package/src/ui/types.ts +325 -0
- package/src/utils/atomic-write.test.ts +312 -0
- package/src/utils/atomic-write.ts +422 -0
- package/src/utils/env-lock.test.ts +999 -0
- package/src/utils/env-lock.ts +1086 -0
- package/src/utils/hashing.ts +53 -0
- package/src/utils/json.ts +77 -0
- package/src/utils/logger.ts +114 -0
- package/src/utils/paths.ts +176 -0
- package/src/utils/proxy.test.ts +465 -0
- package/src/utils/proxy.ts +493 -0
- package/src/utils/recursive-walk.test.ts +148 -0
- package/src/utils/recursive-walk.ts +145 -0
- package/src/utils/zip.ts +332 -0
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ui 分区 adapter(设计 §3.3/§4.1/§4.3):
|
|
3
|
+
* 数据源 = settings.yaml 中「UI 类」namespace(pet / dsh-better-sidebar / remote-web-ui / ui-onboarding / skin 等)。
|
|
4
|
+
* localStorage 等 Host 不可迁移的 UI 状态以 uiMigrationNotes 纯说明形式导出(不含任何值)。
|
|
5
|
+
*/
|
|
6
|
+
import type { NamespaceRecord, UiSection } from '../schema/types.ts';
|
|
7
|
+
import { zhMsg } from '../core/messages.ts';
|
|
8
|
+
import type { MsgFunc } from '../core/messages.ts';
|
|
9
|
+
import type {
|
|
10
|
+
ApplyResult, ConfigAdapter, ExportOptions, ExportSection, HostContext,
|
|
11
|
+
ImportContext, PlanItem, ValidationResult,
|
|
12
|
+
} from '../core/types.ts';
|
|
13
|
+
import { collectNamespaceRecords, planNamespaceItems, applyNamespaceItem, resolveNamespaces, type NamespaceProvider } from './settings.ts';
|
|
14
|
+
|
|
15
|
+
/** UI 类 namespace 前缀名单(设计 §4.1;随新插件扩展,只影响「某一 namespace 归哪一边」,不影响数据完整性) */
|
|
16
|
+
export const KNOWN_UI_NAMESPACE_PREFIXES: readonly string[] = [
|
|
17
|
+
'pet',
|
|
18
|
+
'dsh-better-sidebar',
|
|
19
|
+
'remote-web-ui',
|
|
20
|
+
'ui-onboarding',
|
|
21
|
+
'dsh-client-ui',
|
|
22
|
+
'skin',
|
|
23
|
+
'theme',
|
|
24
|
+
'layout',
|
|
25
|
+
'sidebar',
|
|
26
|
+
'appearance',
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
/** namespace 名是否属于 UI 类(大小写不敏感前缀匹配) */
|
|
30
|
+
export function isUiNamespace(name: string): boolean {
|
|
31
|
+
const lower = name.toLowerCase();
|
|
32
|
+
return KNOWN_UI_NAMESPACE_PREFIXES.some((p) => lower.startsWith(p.toLowerCase()));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** localStorage 等 Host 无通道的 UI 状态说明(纯说明,不含值;研究报告 §2.2/§4.4) */
|
|
36
|
+
export const UI_MIGRATION_NOTES: UiSection['uiMigrationNotes'] = [
|
|
37
|
+
{
|
|
38
|
+
plugin: 'dsh-task-board',
|
|
39
|
+
storage: 'localStorage',
|
|
40
|
+
key: 'dsh.taskBoard.v1',
|
|
41
|
+
migratable: false,
|
|
42
|
+
reason: '任务看板数据存于浏览器 localStorage,Host 侧无访问通道,需在目标机器重新配置',
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
plugin: 'dsh-aionui-panel',
|
|
46
|
+
storage: 'localStorage',
|
|
47
|
+
key: 'aionui-panel.*',
|
|
48
|
+
migratable: false,
|
|
49
|
+
reason: '面板宽度/折叠状态存于浏览器 localStorage,Host 侧无访问通道,需在目标机器重新配置',
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
plugin: 'dsh-client-ui-*',
|
|
53
|
+
storage: 'localStorage',
|
|
54
|
+
key: '*',
|
|
55
|
+
migratable: false,
|
|
56
|
+
reason: '浏览器端 UI 状态无法经 Host API 迁移',
|
|
57
|
+
},
|
|
58
|
+
];
|
|
59
|
+
|
|
60
|
+
export class UiAdapter implements ConfigAdapter<UiSection> {
|
|
61
|
+
readonly id = 'ui' as const;
|
|
62
|
+
readonly displayName = 'UI Preferences';
|
|
63
|
+
readonly defaultIncluded = true;
|
|
64
|
+
readonly portability = 'portable' as const;
|
|
65
|
+
private readonly namespaces: string[] | NamespaceProvider;
|
|
66
|
+
|
|
67
|
+
constructor(namespaces: string[] | NamespaceProvider = []) {
|
|
68
|
+
this.namespaces = namespaces;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async export(ctx: HostContext, _options: ExportOptions): Promise<ExportSection<UiSection>> {
|
|
72
|
+
const warnings: string[] = [];
|
|
73
|
+
const all = await resolveNamespaces(this.namespaces, ctx);
|
|
74
|
+
const uiNames = all.filter((n) => isUiNamespace(n));
|
|
75
|
+
const namespaces: Record<string, NamespaceRecord> = await collectNamespaceRecords(ctx, uiNames, warnings);
|
|
76
|
+
return {
|
|
77
|
+
sectionId: 'ui',
|
|
78
|
+
data: { version: 1, namespaces, uiMigrationNotes: UI_MIGRATION_NOTES },
|
|
79
|
+
counts: { namespaces: Object.keys(namespaces).length, notes: UI_MIGRATION_NOTES.length },
|
|
80
|
+
warnings,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async analyzeImport(data: UiSection, ctx: ImportContext): Promise<PlanItem[]> {
|
|
85
|
+
return planNamespaceItems(data.namespaces, 'ui', ctx);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async applyItem(item: PlanItem, ctx: ImportContext): Promise<ApplyResult> {
|
|
89
|
+
return applyNamespaceItem(item, 'ui', ctx);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async validate(data: UiSection, msg: MsgFunc = zhMsg): Promise<ValidationResult> {
|
|
93
|
+
const issues: ValidationResult['issues'] = [];
|
|
94
|
+
if (data === null || typeof data !== 'object') {
|
|
95
|
+
return { valid: false, issues: [{ path: '$', message: msg('adapter.validate.object', { subject: 'ui' }), severity: 'error' }] };
|
|
96
|
+
}
|
|
97
|
+
if (data.version !== 1) {
|
|
98
|
+
issues.push({ path: 'version', message: msg('adapter.validate.version', { value: String(data.version) }), severity: 'error' });
|
|
99
|
+
}
|
|
100
|
+
if (data.namespaces === null || typeof data.namespaces !== 'object') {
|
|
101
|
+
issues.push({ path: 'namespaces', message: msg('adapter.validate.missingObject', { subject: 'namespaces' }), severity: 'error' });
|
|
102
|
+
}
|
|
103
|
+
if (data.uiMigrationNotes !== undefined && !Array.isArray(data.uiMigrationNotes)) {
|
|
104
|
+
issues.push({ path: 'uiMigrationNotes', message: msg('adapter.validate.array', { subject: 'uiMigrationNotes' }), severity: 'error' });
|
|
105
|
+
}
|
|
106
|
+
return { valid: issues.filter((i) => i.severity === 'error').length === 0, issues };
|
|
107
|
+
}
|
|
108
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* workspaces adapter 测试:绝对路径导出、Create/Skip/Conflict + PathMapping 项、
|
|
3
|
+
* applyItem 写 record(PathMapper 先行后的映射数据)。
|
|
4
|
+
*/
|
|
5
|
+
import test from 'node:test';
|
|
6
|
+
import assert from 'node:assert/strict';
|
|
7
|
+
import { WorkspacesAdapter } from './workspaces.ts';
|
|
8
|
+
import { makeContext, makeImportContext } from './test-helpers.ts';
|
|
9
|
+
import { applyPrefixMappings } from '../utils/paths.ts';
|
|
10
|
+
import type { WorkspacesSection } from '../schema/types.ts';
|
|
11
|
+
import type { PlanItem } from '../core/types.ts';
|
|
12
|
+
|
|
13
|
+
test('workspaces: 导出 records(含绝对路径)', async () => {
|
|
14
|
+
const ctx = makeContext('win32', 'C:\\Users\\alice');
|
|
15
|
+
ctx.workspace.records.set('ws-ops', {
|
|
16
|
+
id: 'ws-ops', path: 'C:\\Users\\alice\\projects\\ops', title: 'OpsFlow', sessionIds: [],
|
|
17
|
+
});
|
|
18
|
+
const adapter = new WorkspacesAdapter();
|
|
19
|
+
const out = await adapter.export(ctx, { includeSecrets: false });
|
|
20
|
+
assert.equal(out.data.workspaces.length, 1);
|
|
21
|
+
assert.equal(out.data.workspaces[0]?.path, 'C:\\Users\\alice\\projects\\ops');
|
|
22
|
+
assert.equal(out.counts.workspaces, 1);
|
|
23
|
+
const v = await adapter.validate(out.data);
|
|
24
|
+
assert.equal(v.valid, true);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test('workspaces: analyzeImport 生成 Create + PathMapping(绝对路径)', async () => {
|
|
28
|
+
const src = makeContext('win32', 'C:\\Users\\alice');
|
|
29
|
+
src.workspace.records.set('ws-ops', {
|
|
30
|
+
id: 'ws-ops', path: 'C:\\Users\\alice\\projects\\ops', title: 'OpsFlow', sessionIds: [],
|
|
31
|
+
});
|
|
32
|
+
const adapter = new WorkspacesAdapter();
|
|
33
|
+
const exported = await adapter.export(src, { includeSecrets: false });
|
|
34
|
+
const sections = new Map([['workspaces', exported.data]]);
|
|
35
|
+
|
|
36
|
+
const dst = makeContext('linux', '/home/bob');
|
|
37
|
+
const items = await adapter.analyzeImport(exported.data, makeImportContext(dst, sections));
|
|
38
|
+
assert.equal(items.length, 2);
|
|
39
|
+
const create = items.find((i) => i.id === 'workspace:ws-ops');
|
|
40
|
+
const pathItem = items.find((i) => i.id === 'workspace:ws-ops:path');
|
|
41
|
+
assert.equal(create?.kind, 'Create');
|
|
42
|
+
assert.equal(pathItem?.kind, 'PathMapping');
|
|
43
|
+
assert.deepEqual(pathItem?.pathMapping?.appliesTo, ['workspaces']);
|
|
44
|
+
assert.equal(pathItem?.pathMapping?.oldPrefix, 'C:\\Users\\alice\\projects\\ops');
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test('workspaces: applyItem 写入映射后数据(PathMapper 先行)', async () => {
|
|
48
|
+
const src = makeContext('win32', 'C:\\Users\\alice');
|
|
49
|
+
src.workspace.records.set('ws-ops', {
|
|
50
|
+
id: 'ws-ops', path: 'C:\\Users\\alice\\projects\\ops', title: 'OpsFlow', sessionIds: [],
|
|
51
|
+
});
|
|
52
|
+
const adapter = new WorkspacesAdapter();
|
|
53
|
+
const exported = await adapter.export(src, { includeSecrets: false });
|
|
54
|
+
|
|
55
|
+
// 模拟 PathMapper:把映射应用到 sections
|
|
56
|
+
const mappedData = applyPrefixMappings(exported.data, [
|
|
57
|
+
{ oldPrefix: 'C:\\Users\\alice', newPrefix: '/home/bob', appliesTo: ['workspaces'] },
|
|
58
|
+
]) as WorkspacesSection;
|
|
59
|
+
assert.equal(mappedData.workspaces[0]?.path, '/home/bob/projects/ops');
|
|
60
|
+
|
|
61
|
+
const sections = new Map([['workspaces', mappedData]]);
|
|
62
|
+
const dst = makeContext('linux', '/home/bob');
|
|
63
|
+
const items = await adapter.analyzeImport(mappedData, makeImportContext(dst, sections));
|
|
64
|
+
const create = items.find((i) => i.id === 'workspace:ws-ops') as PlanItem;
|
|
65
|
+
const r = await adapter.applyItem(create, makeImportContext(dst, sections));
|
|
66
|
+
assert.equal(r.ok, true);
|
|
67
|
+
const rec = dst.workspace.records.get('ws-ops');
|
|
68
|
+
assert.ok(rec);
|
|
69
|
+
assert.equal(rec.path, '/home/bob/projects/ops', '写入的是映射后路径');
|
|
70
|
+
|
|
71
|
+
// PathMapping 项 applyItem 无副作用
|
|
72
|
+
const pathItem = items.find((i) => i.id === 'workspace:ws-ops:path') as PlanItem;
|
|
73
|
+
const r2 = await adapter.applyItem(pathItem, makeImportContext(dst, sections));
|
|
74
|
+
assert.equal(r2.ok, true);
|
|
75
|
+
|
|
76
|
+
// 幂等
|
|
77
|
+
const items2 = await adapter.analyzeImport(mappedData, makeImportContext(dst, sections));
|
|
78
|
+
assert.equal(items2.find((i) => i.id === 'workspace:ws-ops')?.kind, 'Skip');
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test('workspaces: applyItem 写入失败(路径 realpath ENOENT)→ warning 非致命,不抛错', async () => {
|
|
82
|
+
const src = makeContext('win32', 'C:\\Users\\alice');
|
|
83
|
+
src.workspace.records.set('ws-ops', {
|
|
84
|
+
id: 'ws-ops', path: 'C:\\Users\\alice\\projects\\ops', title: 'OpsFlow', sessionIds: [],
|
|
85
|
+
});
|
|
86
|
+
const adapter = new WorkspacesAdapter();
|
|
87
|
+
const exported = await adapter.export(src, { includeSecrets: false });
|
|
88
|
+
const sections = new Map([['workspaces', exported.data]]);
|
|
89
|
+
const dst = makeContext('linux', '/home/bob');
|
|
90
|
+
// 模拟目标端工作区服务对不存在路径 realpath 失败(dsh-workspace 真实行为)
|
|
91
|
+
const original = dst.workspace.writeRecord.bind(dst.workspace);
|
|
92
|
+
dst.workspace.writeRecord = async () => {
|
|
93
|
+
throw new Error("ENOENT: no such file or directory, realpath '/home/bob/projects/ops'");
|
|
94
|
+
};
|
|
95
|
+
try {
|
|
96
|
+
const items = await adapter.analyzeImport(exported.data, makeImportContext(dst, sections));
|
|
97
|
+
const create = items.find((i) => i.id === 'workspace:ws-ops') as PlanItem;
|
|
98
|
+
const r = await adapter.applyItem(create, makeImportContext(dst, sections));
|
|
99
|
+
assert.equal(r.ok, false, '写入失败');
|
|
100
|
+
assert.equal(r.warning, true, '应为非致命警告(§34.17:不拖垮整体导入)');
|
|
101
|
+
assert.match(r.message ?? '', /未能写入|realpath/);
|
|
102
|
+
} finally {
|
|
103
|
+
dst.workspace.writeRecord = original;
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* workspaces 分区 adapter(设计 §3.3/§11):
|
|
3
|
+
* 数据源 = ~/.dsh/storages/workspace.json 的 tables.workspaces(经 ctx.workspace 门面)。
|
|
4
|
+
* workspace.path 为绝对路径 → 跨设备必须 PathMapping(analyzeImport 生成 PathMapping 项,
|
|
5
|
+
* 映射由引擎 PathMapper 先行应用到 sections,applyItem 拿到的已是映射后数据)。
|
|
6
|
+
*/
|
|
7
|
+
import { isDeepStrictEqual } from 'node:util';
|
|
8
|
+
import { zhMsg } from '../core/messages.ts';
|
|
9
|
+
import type { MsgFunc } from '../core/messages.ts';
|
|
10
|
+
import type { WorkspaceRecord, WorkspacesSection } from '../schema/types.ts';
|
|
11
|
+
import type {
|
|
12
|
+
ApplyResult, ConfigAdapter, ExportOptions, ExportSection, HostContext,
|
|
13
|
+
ImportContext, PlanItem, ValidationResult,
|
|
14
|
+
} from '../core/types.ts';
|
|
15
|
+
|
|
16
|
+
export class WorkspacesAdapter implements ConfigAdapter<WorkspacesSection> {
|
|
17
|
+
readonly id = 'workspaces' as const;
|
|
18
|
+
readonly displayName = 'Workspaces';
|
|
19
|
+
readonly defaultIncluded = true;
|
|
20
|
+
readonly portability = 'platformSpecific' as const;
|
|
21
|
+
|
|
22
|
+
async export(ctx: HostContext, _options: ExportOptions): Promise<ExportSection<WorkspacesSection>> {
|
|
23
|
+
const records = await ctx.workspace.listRecords();
|
|
24
|
+
return {
|
|
25
|
+
sectionId: 'workspaces',
|
|
26
|
+
data: { version: 1, workspaces: records },
|
|
27
|
+
counts: { workspaces: records.length },
|
|
28
|
+
warnings: [],
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async analyzeImport(data: WorkspacesSection, ctx: ImportContext): Promise<PlanItem[]> {
|
|
33
|
+
const msg = ctx.msg;
|
|
34
|
+
const items: PlanItem[] = [];
|
|
35
|
+
for (const rec of data.workspaces) {
|
|
36
|
+
const id = `workspace:${rec.id}`;
|
|
37
|
+
const existing = (await ctx.target.workspace.listRecords()).find((r) => r.id === rec.id);
|
|
38
|
+
if (!existing) {
|
|
39
|
+
items.push({
|
|
40
|
+
id, kind: 'Create', adapter: 'workspaces',
|
|
41
|
+
description: msg('adapter.workspaceCreate', { title: rec.title ?? rec.id, path: rec.path }), severity: 'info',
|
|
42
|
+
target: { adapter: 'workspaces', ref: rec.id },
|
|
43
|
+
});
|
|
44
|
+
} else if (isDeepStrictEqual(existing, rec)) {
|
|
45
|
+
items.push({ id, kind: 'Skip', adapter: 'workspaces', description: msg('adapter.workspaceSame', { id: rec.id }), severity: 'info' });
|
|
46
|
+
} else {
|
|
47
|
+
items.push({
|
|
48
|
+
id, kind: 'Conflict', adapter: 'workspaces',
|
|
49
|
+
description: msg('adapter.workspaceDiff', { id: rec.id }),
|
|
50
|
+
detail: `current=${JSON.stringify(existing)} imported=${JSON.stringify(rec)}`.slice(0, 200),
|
|
51
|
+
severity: 'warning', target: { adapter: 'workspaces', ref: rec.id },
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
// 绝对路径 → 路径映射提示(oldPrefix 先用整条路径占位,newPrefix 由 UI 确认)
|
|
55
|
+
if (rec.path.startsWith('/') || /^[a-zA-Z]:[\\/]/.test(rec.path)) {
|
|
56
|
+
items.push({
|
|
57
|
+
id: `${id}:path`, kind: 'PathMapping', adapter: 'workspaces',
|
|
58
|
+
description: msg('adapter.workspacePathMapping', { path: rec.path }), severity: 'warning',
|
|
59
|
+
pathMapping: { oldPrefix: rec.path, newPrefix: '', appliesTo: ['workspaces'] },
|
|
60
|
+
target: { adapter: 'workspaces', ref: rec.id },
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return items;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async applyItem(item: PlanItem, ctx: ImportContext): Promise<ApplyResult> {
|
|
68
|
+
// PathMapping 项:数据已由 PathMapper 应用到 sections,无需额外写入
|
|
69
|
+
if (item.kind === 'PathMapping') return { ok: true };
|
|
70
|
+
const ref = item.target?.ref;
|
|
71
|
+
if (!ref) return { ok: false, message: ctx.msg('adapter.missingTargetRef') };
|
|
72
|
+
const data = ctx.sections.get('workspaces') as WorkspacesSection | undefined;
|
|
73
|
+
const rec = data?.workspaces.find((r) => r.id === ref);
|
|
74
|
+
if (!rec) return { ok: false, message: ctx.msg('adapter.workspaceMissing', { ref }) };
|
|
75
|
+
try {
|
|
76
|
+
await ctx.target.workspace.writeRecord(rec);
|
|
77
|
+
return { ok: true };
|
|
78
|
+
} catch (err) {
|
|
79
|
+
// 目标端无法写入(如路径 realpath 失败/目录不存在)→ 非致命警告(§34.17),
|
|
80
|
+
// 不触发整体回滚——否则一个失效路径会拖垮已成功导入的其余配置。
|
|
81
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
82
|
+
return {
|
|
83
|
+
ok: false,
|
|
84
|
+
warning: true,
|
|
85
|
+
message: ctx.msg('adapter.workspaceWriteFailed', { ref, msg: reason }),
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async validate(data: WorkspacesSection, msg: MsgFunc = zhMsg): Promise<ValidationResult> {
|
|
91
|
+
const issues: ValidationResult['issues'] = [];
|
|
92
|
+
if (data === null || typeof data !== 'object') {
|
|
93
|
+
return { valid: false, issues: [{ path: '$', message: msg('adapter.validate.object', { subject: 'workspaces' }), severity: 'error' }] };
|
|
94
|
+
}
|
|
95
|
+
if (data.version !== 1) {
|
|
96
|
+
issues.push({ path: 'version', message: msg('adapter.validate.version', { value: String(data.version) }), severity: 'error' });
|
|
97
|
+
}
|
|
98
|
+
if (!Array.isArray(data.workspaces)) {
|
|
99
|
+
issues.push({ path: 'workspaces', message: msg('adapter.validate.array', { subject: 'workspaces' }), severity: 'error' });
|
|
100
|
+
} else {
|
|
101
|
+
for (const w of data.workspaces as WorkspaceRecord[]) {
|
|
102
|
+
if (w === null || typeof w !== 'object' || typeof w.id !== 'string' || typeof w.path !== 'string') {
|
|
103
|
+
issues.push({ path: 'workspaces[]', message: msg('adapter.validate.workspaceIdentity'), severity: 'error' });
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return { valid: issues.filter((i) => i.severity === 'error').length === 0, issues };
|
|
108
|
+
}
|
|
109
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Config Manager 设置页(settings.section 入口)—— Workbench Shell。
|
|
3
|
+
* 宽读法改造:仅保留「远程同步」单一标签页。
|
|
4
|
+
*/
|
|
5
|
+
import { useEffect, useState, useSyncExternalStore } from 'react'
|
|
6
|
+
import type { PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots'
|
|
7
|
+
import type { ConfigManagerSectionInjected, TranslateNS } from './client-types.ts'
|
|
8
|
+
import type { ServiceStatus } from './api.ts'
|
|
9
|
+
import { runStore } from './run-store.ts'
|
|
10
|
+
import { SyncSettingsView } from './sync/SyncSettingsView.tsx'
|
|
11
|
+
import { MODAL_ROOT_ID } from './common/Modal.tsx'
|
|
12
|
+
import { StatusDot } from './common/ui.tsx'
|
|
13
|
+
import { ToastViewport } from './common/ToastViewport.tsx'
|
|
14
|
+
import css from './config-manager.module.css'
|
|
15
|
+
|
|
16
|
+
export type ConfigManagerSectionProps =
|
|
17
|
+
& PropsRuntime<'settings.section'>
|
|
18
|
+
& ConfigManagerSectionInjected
|
|
19
|
+
& { t: TranslateNS<'config-manager'> }
|
|
20
|
+
|
|
21
|
+
/** 一级导航(宽读法:仅保留同步页)。 */
|
|
22
|
+
const NAV_ITEMS = [
|
|
23
|
+
{ id: 'sync' as const, label: 'nav.sync' },
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
export function ConfigManagerSection({ api, syncApi, syncT, t }: ConfigManagerSectionProps) {
|
|
27
|
+
const state = useSyncExternalStore(runStore.subscribe, runStore.getSnapshot)
|
|
28
|
+
|
|
29
|
+
/* ---------------- 状态栏版本(挂载时取一次;失败隐藏) ---------------- */
|
|
30
|
+
const [version, setVersion] = useState<ServiceStatus | null>(null)
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
let cancelled = false
|
|
33
|
+
api.status().then(
|
|
34
|
+
(s) => { if (!cancelled) setVersion(s) },
|
|
35
|
+
() => { /* 版本信息失败不影响功能 */ },
|
|
36
|
+
)
|
|
37
|
+
return () => { cancelled = true }
|
|
38
|
+
}, [api])
|
|
39
|
+
|
|
40
|
+
/* ---------------- 状态栏数据 ---------------- */
|
|
41
|
+
const runningCount = state.sync.busy !== null ? 1 : 0
|
|
42
|
+
const statusKind: 'ok' | 'info' | 'error' = runningCount > 0 ? 'info' : 'ok'
|
|
43
|
+
const statusText = runningCount > 0
|
|
44
|
+
? t('shell.status.running', { count: String(runningCount) })
|
|
45
|
+
: t('shell.status.idle')
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<div className={css.section} id={MODAL_ROOT_ID}>
|
|
49
|
+
{/* 顶部导航条:页签 */}
|
|
50
|
+
<nav className={css.shellNav} aria-label={t('section.label')}>
|
|
51
|
+
<div className={css.navStrip} role="tablist">
|
|
52
|
+
{NAV_ITEMS.map((item) => (
|
|
53
|
+
<button
|
|
54
|
+
key={item.id}
|
|
55
|
+
type="button"
|
|
56
|
+
role="tab"
|
|
57
|
+
aria-selected={true}
|
|
58
|
+
data-active=""
|
|
59
|
+
className={css.navTab}
|
|
60
|
+
>
|
|
61
|
+
{t(item.label as Parameters<TranslateNS<'config-manager'>>[0])}
|
|
62
|
+
</button>
|
|
63
|
+
))}
|
|
64
|
+
</div>
|
|
65
|
+
</nav>
|
|
66
|
+
|
|
67
|
+
{/* 页面主体(独立滚动) */}
|
|
68
|
+
<main className={css.shellMain}>
|
|
69
|
+
<div className={css.pagePad}>
|
|
70
|
+
<SyncSettingsView api={syncApi} t={syncT} />
|
|
71
|
+
</div>
|
|
72
|
+
</main>
|
|
73
|
+
|
|
74
|
+
{/* 底部状态栏:运行状态 + 版本 */}
|
|
75
|
+
<footer className={css.statusBar}>
|
|
76
|
+
<StatusDot kind={statusKind} pulse={runningCount > 0} />
|
|
77
|
+
<span className={css.statusText}>{statusText}</span>
|
|
78
|
+
<span className={css.statusSpacer} />
|
|
79
|
+
{version !== null && (
|
|
80
|
+
<span className={css.statusMeta}>
|
|
81
|
+
{t('shell.version', { plugin: version.pluginVersion, dsh: version.dshVersion })}
|
|
82
|
+
</span>
|
|
83
|
+
)}
|
|
84
|
+
</footer>
|
|
85
|
+
|
|
86
|
+
{/* 全局通知视口(右下角堆叠) */}
|
|
87
|
+
<ToastViewport t={t} />
|
|
88
|
+
</div>
|
|
89
|
+
)
|
|
90
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Config Manager 浏览器半 API 客户端 —— 精简版(仅保留 status 与通用 API 错误类)。
|
|
3
|
+
* 远程同步端点由 ./sync/sync-api.ts 的 SyncApi 承载。
|
|
4
|
+
*/
|
|
5
|
+
import { zhUiT, type UiT } from '../ui/i18n.ts'
|
|
6
|
+
|
|
7
|
+
/** Host 半健康检查响应(plugin 版本 / DSH 版本 / 平台,用于主页横幅与兼容性说明) */
|
|
8
|
+
export interface ServiceStatus {
|
|
9
|
+
ready: boolean
|
|
10
|
+
pluginVersion: string
|
|
11
|
+
dshVersion: string
|
|
12
|
+
platform: string
|
|
13
|
+
arch: string
|
|
14
|
+
homeDir?: string
|
|
15
|
+
profile?: string
|
|
16
|
+
profileManifestReadable?: boolean
|
|
17
|
+
installedPluginCount?: number
|
|
18
|
+
installedPluginNames?: string[]
|
|
19
|
+
bundles?: string[]
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const CONFIG_MANAGER_API = {
|
|
23
|
+
status: '/api/dsh-config-manager/status',
|
|
24
|
+
} as const
|
|
25
|
+
|
|
26
|
+
/** 携带路由 JSON error 消息的错误类型 */
|
|
27
|
+
export class ConfigManagerApiError extends Error {
|
|
28
|
+
constructor(message: string) {
|
|
29
|
+
super(message)
|
|
30
|
+
this.name = 'ConfigManagerApiError'
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async function readJson<T>(response: Response, t: UiT): Promise<T> {
|
|
35
|
+
const notMountedMessage = t('error.notMounted')
|
|
36
|
+
let body: unknown
|
|
37
|
+
try {
|
|
38
|
+
body = await response.json()
|
|
39
|
+
} catch {
|
|
40
|
+
if (response.status === 404) throw new ConfigManagerApiError(notMountedMessage)
|
|
41
|
+
throw new ConfigManagerApiError(t('error.httpInvalidJson', { status: String(response.status) }))
|
|
42
|
+
}
|
|
43
|
+
if (!response.ok) {
|
|
44
|
+
const message =
|
|
45
|
+
typeof body === 'object' && body !== null && typeof (body as { error?: unknown }).error === 'string'
|
|
46
|
+
? (body as { error: string }).error
|
|
47
|
+
: response.status === 404
|
|
48
|
+
? notMountedMessage
|
|
49
|
+
: `HTTP ${response.status}`
|
|
50
|
+
throw new ConfigManagerApiError(message)
|
|
51
|
+
}
|
|
52
|
+
return body as T
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export class ConfigManagerApi {
|
|
56
|
+
public readonly t: UiT
|
|
57
|
+
|
|
58
|
+
constructor(t: UiT = zhUiT) {
|
|
59
|
+
this.t = t
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async status(): Promise<ServiceStatus> {
|
|
63
|
+
const response = await fetch(CONFIG_MANAGER_API.status)
|
|
64
|
+
return readJson<ServiceStatus>(response, this.t)
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client 半的类型集中出口:把对 @deepseek-ai 运行时包的类型依赖收敛到本文件,
|
|
3
|
+
* 其余组件只从 `../client-types.ts` 引用,避免类型散落与误用值导入。
|
|
4
|
+
*/
|
|
5
|
+
import type { ClientContext } from '@deepseek-ai/dsh-client-runtime/client'
|
|
6
|
+
import type { TranslateNS } from '@deepseek-ai/dsh-client-ui-slots'
|
|
7
|
+
|
|
8
|
+
export type { ClientContext }
|
|
9
|
+
export type { TranslateNS }
|
|
10
|
+
|
|
11
|
+
/** 本插件 Client 半的注入业务面:每个 settings.section 注册项都拿到同一个 api 实例 */
|
|
12
|
+
export interface ConfigManagerSectionInjected {
|
|
13
|
+
api: import('./api.ts').ConfigManagerApi
|
|
14
|
+
/** 远程同步 API */
|
|
15
|
+
syncApi: import('./sync/sync-api.ts').SyncApi
|
|
16
|
+
/** 远程同步 locale(config-manager-sync 命名空间) */
|
|
17
|
+
syncT: import('@deepseek-ai/dsh-client-ui-slots').TranslateNS<'config-manager-sync'>
|
|
18
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 可操作错误展示(规范 §23,绑 src/ui/errors.ts)。
|
|
3
|
+
*
|
|
4
|
+
* 安全约束:**强制 redact 不泄 Secret** —— 展示前对错误消息、关联项文本
|
|
5
|
+
* 统一过 `redact()`(字段名黑名单 + sk-/JWT/PEM/Bearer 等值形状模式),
|
|
6
|
+
* 渲染结果只含 Reason / Suggested action / Item,绝不出现密钥原文。
|
|
7
|
+
*/
|
|
8
|
+
import { useState } from 'react'
|
|
9
|
+
import { formatActionableError, toActionableError } from '../../ui/errors.ts'
|
|
10
|
+
import { redact } from '../../security/redaction.ts'
|
|
11
|
+
import { zhUiT, type UiT } from '../../ui/i18n.ts'
|
|
12
|
+
import { Button, Spinner } from './ui.tsx'
|
|
13
|
+
import { ArrowRightIcon } from './Icon.tsx'
|
|
14
|
+
import css from '../config-manager.module.css'
|
|
15
|
+
|
|
16
|
+
export interface ErrorBannerProps {
|
|
17
|
+
/** 任意未知错误(Error / 字符串 / API 错误) */
|
|
18
|
+
error: unknown
|
|
19
|
+
/** 可重试时显示的重试按钮(缺省不显示) */
|
|
20
|
+
onRetry?: () => void
|
|
21
|
+
/** 重试中的 loading 态 */
|
|
22
|
+
retrying?: boolean
|
|
23
|
+
/** 展示层翻译器(缺省 zh;ErrorBanner 为通用组件,不绑定 settings 命名空间字典) */
|
|
24
|
+
t?: UiT
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* 错误横幅:toActionableError 解析为标题 + 原因 + 建议动作,
|
|
29
|
+
* 文本在渲染前再经 redact() 兜底(双保险),Reason 以等宽块展示。
|
|
30
|
+
* 重试按钮:进行中(retrying)时显示 Spinner 并禁用(防重复点击)。
|
|
31
|
+
*/
|
|
32
|
+
export function ErrorBanner({ error, onRetry, retrying, t = zhUiT }: ErrorBannerProps) {
|
|
33
|
+
const [actionable] = useState(() => toActionableError(error))
|
|
34
|
+
const reason = redact(actionable.reason)
|
|
35
|
+
const item = actionable.item !== undefined ? redact(actionable.item) : undefined
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<div className={css.errorBanner} role="alert">
|
|
39
|
+
<div className={css.errorTitle}>{redact(actionable.title)}</div>
|
|
40
|
+
<pre className={css.errorReason}>{reason}</pre>
|
|
41
|
+
{actionable.suggestedAction !== undefined && (
|
|
42
|
+
<div className={css.errorAction}>
|
|
43
|
+
<span className={css.errorActionLabel}><ArrowRightIcon size={13} /></span> {redact(actionable.suggestedAction)}
|
|
44
|
+
</div>
|
|
45
|
+
)}
|
|
46
|
+
{item !== undefined && <div className={css.errorItem}>{item}</div>}
|
|
47
|
+
{actionable.retryable && onRetry !== undefined && (
|
|
48
|
+
<div className={css.errorFooter}>
|
|
49
|
+
<Button variant="primary" onClick={onRetry} disabled={retrying === true} loading={retrying === true}>
|
|
50
|
+
{retrying === true ? <Spinner /> : t('commonRetry')}
|
|
51
|
+
</Button>
|
|
52
|
+
</div>
|
|
53
|
+
)}
|
|
54
|
+
</div>
|
|
55
|
+
)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** 多行错误文本展示(Wizard.errors 数组用;同样强制 redact) */
|
|
59
|
+
export function ErrorList({ errors }: { errors: readonly string[] }) {
|
|
60
|
+
if (errors.length === 0) return null
|
|
61
|
+
return (
|
|
62
|
+
<div className={css.errorList}>
|
|
63
|
+
{errors.map((line, i) => (
|
|
64
|
+
<pre key={i} className={css.errorLine}>{redact(line)}</pre>
|
|
65
|
+
))}
|
|
66
|
+
</div>
|
|
67
|
+
)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** formatActionableError 的文本化出口(供非 React 渲染场景复用) */
|
|
71
|
+
export function formatErrorText(error: unknown): string {
|
|
72
|
+
return formatActionableError(toActionableError(error))
|
|
73
|
+
}
|