coding-tool-x 3.8.4 → 3.8.5
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/CHANGELOG.md +15 -0
- package/README.md +86 -15
- package/dist/web/assets/AgentsDrawer-BG6P3DX-.js +1 -0
- package/dist/web/assets/AgentsDrawer-Pw2k-ZAa.css +1 -0
- package/dist/web/assets/Analytics--z_FmL5P.js +1 -0
- package/dist/web/assets/Analytics-CyHhR126.css +1 -0
- package/dist/web/assets/AssetPathField-CrJtRKHy.js +1 -0
- package/dist/web/assets/ChatHistoryDrawer-B2qBQs9L.css +1 -0
- package/dist/web/assets/ChatHistoryDrawer-BiuEryJj.js +1 -0
- package/dist/web/assets/CollapseItem-BAYEsL_Q.js +1 -0
- package/dist/web/assets/CommandsDrawer-BZJxlPvV.css +1 -0
- package/dist/web/assets/CommandsDrawer-YJ-U7BXN.js +1 -0
- package/dist/web/assets/ConfigExportDrawer-Ce_hAknW.js +1 -0
- package/dist/web/assets/ConfigExportDrawer-dwruRKWr.css +1 -0
- package/dist/web/assets/ConfigTemplates-Bvr1NN6K.js +1 -0
- package/dist/web/assets/ConfigTemplatesDrawer-BfDi8yO3.css +1 -0
- package/dist/web/assets/ConfigTemplatesDrawer-BjIeHfF-.js +1 -0
- package/dist/web/assets/ConfigTemplatesPanel-AV00tm_D.js +1 -0
- package/dist/web/assets/ConfigTemplatesPanel-CWyPtk67.css +1 -0
- package/dist/web/assets/Divider-D28g1gwj.js +1 -0
- package/dist/web/assets/DrawerContent-ChXgZ1Xm.js +1 -0
- package/dist/web/assets/FavoritesDrawer-CG9eI9Ay.css +1 -0
- package/dist/web/assets/FavoritesDrawer-Cz95o4CD.js +1 -0
- package/dist/web/assets/GatewayConvertDrawer-CuNlf7AC.css +1 -0
- package/dist/web/assets/GatewayConvertDrawer-DLlbMz27.js +1 -0
- package/dist/web/assets/Home-CSuFuJ9Y.css +1 -0
- package/dist/web/assets/Home-D7CCOFQC.js +1 -0
- package/dist/web/assets/MarkdownEditor-qfVgSre_.css +1 -0
- package/dist/web/assets/MarkdownEditor-rEZ-c72L.js +1 -0
- package/dist/web/assets/McpDrawer-C0dnsZhQ.css +1 -0
- package/dist/web/assets/McpDrawer-DfoCyxU2.js +1 -0
- package/dist/web/assets/McpFormDrawer-BRbDcawn.css +1 -0
- package/dist/web/assets/McpFormDrawer-SKu8HNUR.js +1 -0
- package/dist/web/assets/PluginManager-BbeK-3rV.js +1 -0
- package/dist/web/assets/PluginsDrawer-CWVJ7yjS.js +1 -0
- package/dist/web/assets/PluginsDrawer-DM3QjprE.css +1 -0
- package/dist/web/assets/PluginsPanel-Cm2N3StG.js +1 -0
- package/dist/web/assets/PluginsPanel-D6ZqITcR.css +1 -0
- package/dist/web/assets/ProjectList-677RLZ8O.css +1 -0
- package/dist/web/assets/ProjectList-CsjnsNPv.js +1 -0
- package/dist/web/assets/PromptsDrawer-BIgiYCEQ.css +1 -0
- package/dist/web/assets/PromptsDrawer-EGd3djFa.js +1 -0
- package/dist/web/assets/RecentSessionsDrawer-CKFsOYDM.css +1 -0
- package/dist/web/assets/RecentSessionsDrawer-D1vtoLAs.js +1 -0
- package/dist/web/assets/SessionCard-BgLg49U3.js +1 -0
- package/dist/web/assets/SessionCard-aM2NHu6i.css +1 -0
- package/dist/web/assets/SessionList-BpYRz1cX.css +1 -0
- package/dist/web/assets/SessionList-TRiTklWk.js +1 -0
- package/dist/web/assets/SettingsDrawer-d5liXjHq.css +1 -0
- package/dist/web/assets/SettingsDrawer-rz2WejGY.js +1 -0
- package/dist/web/assets/SkillManager-BTjTUbHq.js +1 -0
- package/dist/web/assets/SkillManager-C0GCT2Kb.css +1 -0
- package/dist/web/assets/SkillsDrawer-BCNme-aJ.css +1 -0
- package/dist/web/assets/SkillsDrawer-pN0hbkK3.js +1 -0
- package/dist/web/assets/SkillsPanel-B3Nm5j32.css +1 -0
- package/dist/web/assets/SkillsPanel-gmBWEoiF.js +1 -0
- package/dist/web/assets/SpeedTestDrawer-Dc0WToJ_.css +1 -0
- package/dist/web/assets/SpeedTestDrawer-DcNzuVJk.js +1 -0
- package/dist/web/assets/Upload-v5rV0qTT.js +1 -0
- package/dist/web/assets/WorkspaceDrawer-CGnMu9p1.css +1 -0
- package/dist/web/assets/WorkspaceDrawer-DxmicM4U.js +1 -0
- package/dist/web/assets/WorkspaceManager-CJHCjARN.js +1 -0
- package/dist/web/assets/WorkspacePanel-D0-zHTHP.css +1 -0
- package/dist/web/assets/WorkspacePanel-D8_Zq-du.js +1 -0
- package/dist/web/assets/_plugin-vue_export-helper-BeLZJZij.js +1 -0
- package/dist/web/assets/client-UDwXBP9i.js +1 -0
- package/dist/web/assets/clipboard-B46yyoz_.js +1 -0
- package/dist/web/assets/headers-cf-QapUq.js +1 -0
- package/dist/web/assets/icons-B2Vg-IOC.js +1 -0
- package/dist/web/assets/index-C0EHRym0.css +1 -0
- package/dist/web/assets/index-DnEocV0M.js +2 -0
- package/dist/web/assets/light-Cp0t7v32.js +1 -0
- package/dist/web/assets/markdown-u082bbtj.js +1 -0
- package/dist/web/assets/platforms-CBkB45JP.js +1 -0
- package/dist/web/assets/platforms-FTpjxAT6.js +1 -0
- package/dist/web/assets/project-config-B2baPzYL.js +1 -0
- package/dist/web/assets/sessions-Cmy31slD.js +1 -0
- package/dist/web/assets/sessions-yC_oVVh8.js +1 -0
- package/dist/web/assets/skill-repo-input-B_fkGlUK.js +1 -0
- package/dist/web/assets/use-lock-html-scroll-DXIqmUW4.js +1 -0
- package/dist/web/assets/use-message-DoLM8C1C.js +1 -0
- package/dist/web/assets/useResponsiveDrawer-B3uojyvV.js +1 -0
- package/dist/web/assets/vendors-D0c_0KLP.js +1 -0
- package/dist/web/assets/vue-vendor-BqZbbXHA.js +1 -0
- package/dist/web/assets/workspaces-mjyPVGS8.js +1 -0
- package/dist/web/assets/xml-CgY3RMlX.js +1 -0
- package/dist/web/index.html +12 -8
- package/docs/native-log-memory-2026-09-16.md +43 -0
- package/docs/superpowers/plans/2026-08-26-configuration-driven-cli-platform.md +1578 -0
- package/docs/superpowers/plans/2026-08-30-project-scoped-config-control.md +1260 -0
- package/docs/superpowers/plans/2026-09-01-omp-skill-refresh-boundary.md +213 -0
- package/docs/superpowers/plans/2026-09-01-realtime-logs-panel.md +125 -0
- package/docs/superpowers/plans/2026-09-02-config-driven-platform-boundary.md +86 -0
- package/docs/superpowers/plans/2026-09-02-platform-implementation-drivers.md +508 -0
- package/docs/superpowers/plans/2026-09-03-cli-platform-correctness-boundary.md +760 -0
- package/docs/superpowers/plans/2026-09-03-dependency-security-remediation.md +133 -0
- package/docs/superpowers/plans/2026-09-04-model-catalog-pricing-sync.md +1078 -0
- package/docs/superpowers/plans/2026-09-05-codex-project-scan-500.md +128 -0
- package/docs/superpowers/plans/2026-09-06-project-skill-path-fix.md +97 -0
- package/docs/superpowers/specs/2026-08-26-configuration-driven-cli-platform-design.md +408 -0
- package/docs/superpowers/specs/2026-08-30-project-scoped-config-control-design.md +467 -0
- package/docs/superpowers/specs/2026-09-01-claude-session-content-repair-design.md +111 -0
- package/docs/superpowers/specs/2026-09-01-omp-skill-refresh-boundary-design.md +83 -0
- package/docs/superpowers/specs/2026-09-01-realtime-logs-panel-design.md +27 -0
- package/docs/superpowers/specs/2026-09-02-config-driven-platform-boundary-design.md +64 -0
- package/docs/superpowers/specs/2026-09-02-platform-implementation-driver-boundary-design.md +123 -0
- package/docs/superpowers/specs/2026-09-03-cli-platform-correctness-boundary-design.md +249 -0
- package/docs/superpowers/specs/2026-09-03-dependency-security-remediation-design.md +87 -0
- package/docs/superpowers/specs/2026-09-04-model-catalog-pricing-sync-design.md +301 -0
- package/docs/superpowers/specs/2026-09-05-codex-project-scan-500-design.md +24 -0
- package/package.json +8 -4
- package/src/commands/channels.js +88 -191
- package/src/commands/cli-type.js +19 -11
- package/src/commands/daemon.js +94 -32
- package/src/commands/doctor.js +16 -9
- package/src/commands/export-config.js +23 -5
- package/src/commands/list.js +130 -52
- package/src/commands/logs.js +44 -27
- package/src/commands/platform-command-registry.js +78 -0
- package/src/commands/plugin.js +11 -5
- package/src/commands/port-config.js +34 -52
- package/src/commands/proxy-control.js +207 -145
- package/src/commands/proxy.js +2 -2
- package/src/commands/resume.js +87 -91
- package/src/commands/search.js +137 -67
- package/src/commands/stats.js +30 -22
- package/src/commands/switch.js +1 -1
- package/src/commands/toggle-proxy.js +94 -111
- package/src/commands/ui.js +67 -6
- package/src/commands/workspace.js +90 -8
- package/src/config/default.js +6 -53
- package/src/config/loader.js +61 -24
- package/src/config/model-metadata.js +49 -59
- package/src/config/model-metadata.json +4095 -99
- package/src/config/paths.js +181 -8
- package/src/index.js +134 -71
- package/src/platforms/access.js +196 -0
- package/src/{server/services → platforms}/agents-service.js +354 -197
- package/src/platforms/channel-auth-registry.js +162 -0
- package/src/platforms/channel-auth-service.js +82 -0
- package/src/{server/services → platforms}/commands-service.js +195 -115
- package/src/platforms/driver-registry.js +59 -0
- package/src/platforms/drivers/claude/api-operations.js +62 -0
- package/src/{server/services/channels.js → platforms/drivers/claude/channels-implementation.js} +131 -33
- package/src/platforms/drivers/claude/channels.js +39 -0
- package/src/{server/services/config-sync-service.js → platforms/drivers/claude/config-sync.js} +29 -5
- package/src/{server/services → platforms/drivers/claude}/health-check.js +4 -4
- package/src/platforms/drivers/claude/health.js +22 -0
- package/src/platforms/drivers/claude/mcp.js +9 -0
- package/src/{server/services/settings-manager.js → platforms/drivers/claude/native-config-implementation.js} +13 -4
- package/src/platforms/drivers/claude/native-config.js +46 -0
- package/src/platforms/drivers/claude/native-logs.js +72 -0
- package/src/{server/services/claude-openai-gateway.js → platforms/drivers/claude/openai-gateway.js} +8 -65
- package/src/platforms/drivers/claude/project-config.js +21 -0
- package/src/platforms/drivers/claude/projects.js +62 -0
- package/src/platforms/drivers/claude/prompts.js +9 -0
- package/src/{server/proxy-server.js → platforms/drivers/claude/proxy-implementation.js} +41 -278
- package/src/platforms/drivers/claude/proxy.js +16 -0
- package/src/platforms/drivers/claude/resource-sync.js +9 -0
- package/src/platforms/drivers/claude/session-history-adapter.js +313 -0
- package/src/{server/services/sessions.js → platforms/drivers/claude/sessions-implementation.js} +145 -35
- package/src/platforms/drivers/claude/sessions.js +76 -0
- package/src/{server/services/claude-statistics-service.js → platforms/drivers/claude/statistics-implementation.js} +2 -2
- package/src/platforms/drivers/claude/statistics.js +16 -0
- package/src/{server/services/claude-wire.js → platforms/drivers/claude/wire.js} +1 -1
- package/src/platforms/drivers/codex/api-operations.js +9 -0
- package/src/{server/services/codex-channels.js → platforms/drivers/codex/channels-implementation.js} +149 -14
- package/src/platforms/drivers/codex/channels.js +45 -0
- package/src/{server/services/codex-config.js → platforms/drivers/codex/config.js} +12 -2
- package/src/{server/services/codex-env-manager.js → platforms/drivers/codex/env-manager.js} +1 -1
- package/src/platforms/drivers/codex/mcp.js +9 -0
- package/src/{server/services/codex-settings-manager.js → platforms/drivers/codex/native-config-implementation.js} +15 -6
- package/src/platforms/drivers/codex/native-config.js +47 -0
- package/src/platforms/drivers/codex/native-logs.js +166 -0
- package/src/platforms/drivers/codex/project-config.js +21 -0
- package/src/platforms/drivers/codex/projects.js +26 -0
- package/src/platforms/drivers/codex/prompts.js +9 -0
- package/src/{server/codex-proxy-server.js → platforms/drivers/codex/proxy-implementation.js} +30 -233
- package/src/platforms/drivers/codex/proxy.js +16 -0
- package/src/platforms/drivers/codex/resource-sync.js +9 -0
- package/src/{server/services/session-history-adapters/codex.js → platforms/drivers/codex/session-history-adapter.js} +37 -23
- package/src/{server/services/codex-sessions.js → platforms/drivers/codex/sessions-implementation.js} +81 -75
- package/src/platforms/drivers/codex/sessions.js +36 -0
- package/src/{server/services/codex-statistics-service.js → platforms/drivers/codex/statistics-implementation.js} +2 -2
- package/src/platforms/drivers/codex/statistics.js +16 -0
- package/src/platforms/drivers/gemini/api-operations.js +9 -0
- package/src/{server/services/gemini-channels.js → platforms/drivers/gemini/channels-implementation.js} +202 -37
- package/src/platforms/drivers/gemini/channels.js +39 -0
- package/src/{server/services/gemini-config.js → platforms/drivers/gemini/config.js} +12 -2
- package/src/platforms/drivers/gemini/mcp.js +9 -0
- package/src/{server/services/gemini-settings-manager.js → platforms/drivers/gemini/native-config-implementation.js} +14 -5
- package/src/platforms/drivers/gemini/native-config.js +39 -0
- package/src/platforms/drivers/gemini/native-logs.js +180 -0
- package/src/platforms/drivers/gemini/project-config.js +21 -0
- package/src/platforms/drivers/gemini/projects.js +26 -0
- package/src/platforms/drivers/gemini/prompts.js +9 -0
- package/src/{server/gemini-proxy-server.js → platforms/drivers/gemini/proxy-implementation.js} +36 -267
- package/src/platforms/drivers/gemini/proxy.js +16 -0
- package/src/platforms/drivers/gemini/resource-sync.js +9 -0
- package/src/{server/services/session-history-adapters/gemini.js → platforms/drivers/gemini/session-history-adapter.js} +70 -18
- package/src/{server/services/gemini-sessions.js → platforms/drivers/gemini/sessions-implementation.js} +53 -37
- package/src/platforms/drivers/gemini/sessions.js +47 -0
- package/src/{server/services/gemini-statistics-service.js → platforms/drivers/gemini/statistics-implementation.js} +2 -2
- package/src/platforms/drivers/gemini/statistics.js +16 -0
- package/src/{server/services/gemini-wire.js → platforms/drivers/gemini/wire.js} +1 -1
- package/src/platforms/drivers/generic-filesystem.js +166 -0
- package/src/platforms/drivers/generic-jsonl.js +141 -0
- package/src/platforms/drivers/generic-mcp.js +44 -0
- package/src/platforms/drivers/generic-openai-compatible.js +45 -0
- package/src/platforms/drivers/generic-prompt.js +41 -0
- package/src/platforms/drivers/legacy.js +677 -0
- package/src/platforms/drivers/native-config-snapshot.js +125 -0
- package/src/platforms/drivers/native-log-utils.js +1292 -0
- package/src/platforms/drivers/notification-hooks.js +578 -0
- package/src/{server/api/omp-config.js → platforms/drivers/omp/api-operations.js} +52 -64
- package/src/{server/services/omp-auth-provider-worker.js → platforms/drivers/omp/auth-provider-worker.js} +1 -1
- package/src/{server/services/omp-auth-providers.js → platforms/drivers/omp/auth-providers.js} +1 -1
- package/src/{server/services/omp-channels.js → platforms/drivers/omp/channels-implementation.js} +166 -39
- package/src/platforms/drivers/omp/channels.js +58 -0
- package/src/{server/services/omp-config.js → platforms/drivers/omp/config.js} +53 -13
- package/src/{server/services/omp-gateway-routing.js → platforms/drivers/omp/gateway-routing.js} +14 -7
- package/src/{server/services/omp-gateway.js → platforms/drivers/omp/gateway.js} +4 -262
- package/src/platforms/drivers/omp/mcp.js +9 -0
- package/src/{server/services/omp-settings-manager.js → platforms/drivers/omp/native-config-implementation.js} +75 -10
- package/src/platforms/drivers/omp/native-config.js +46 -0
- package/src/platforms/drivers/omp/native-logs.js +52 -0
- package/src/{server/services/omp-native-plugin-adapter.js → platforms/drivers/omp/native-plugin-adapter.js} +91 -4
- package/src/platforms/drivers/omp/project-config.js +29 -0
- package/src/platforms/drivers/omp/projects.js +20 -0
- package/src/platforms/drivers/omp/prompts.js +9 -0
- package/src/{server/omp-proxy-server.js → platforms/drivers/omp/proxy-implementation.js} +6 -15
- package/src/platforms/drivers/omp/proxy.js +16 -0
- package/src/platforms/drivers/omp/resource-sync.js +9 -0
- package/src/{server/services/session-history-adapters/omp.js → platforms/drivers/omp/session-history-adapter.js} +42 -18
- package/src/{server/services/omp-sessions.js → platforms/drivers/omp/sessions-implementation.js} +130 -94
- package/src/platforms/drivers/omp/sessions.js +36 -0
- package/src/{server/services/omp-skill-discovery.js → platforms/drivers/omp/skill-discovery.js} +159 -32
- package/src/{server/services/omp-skill-settings-service.js → platforms/drivers/omp/skill-settings.js} +1 -1
- package/src/{server/services/omp-statistics-service.js → platforms/drivers/omp/statistics-implementation.js} +3 -3
- package/src/platforms/drivers/omp/statistics.js +16 -0
- package/src/platforms/drivers/opencode/api-operations.js +9 -0
- package/src/{server/services/opencode-channels.js → platforms/drivers/opencode/channels-implementation.js} +28 -9
- package/src/platforms/drivers/opencode/channels.js +40 -0
- package/src/platforms/drivers/opencode/conversion.js +41 -0
- package/src/{server/services/opencode-gateway-converter.js → platforms/drivers/opencode/gateway-converter.js} +1 -1
- package/src/platforms/drivers/opencode/mcp.js +9 -0
- package/src/{server/services/opencode-settings-manager.js → platforms/drivers/opencode/native-config-implementation.js} +17 -6
- package/src/platforms/drivers/opencode/native-config.js +26 -0
- package/src/platforms/drivers/opencode/native-logs.js +133 -0
- package/src/platforms/drivers/opencode/project-config.js +98 -0
- package/src/platforms/drivers/opencode/projects.js +20 -0
- package/src/platforms/drivers/opencode/prompts.js +9 -0
- package/src/{server/opencode-proxy-server.js → platforms/drivers/opencode/proxy-implementation.js} +27 -362
- package/src/platforms/drivers/opencode/proxy.js +16 -0
- package/src/platforms/drivers/opencode/resource-sync.js +9 -0
- package/src/{server/services/opencode-sessions.js → platforms/drivers/opencode/sessions-implementation.js} +211 -64
- package/src/platforms/drivers/opencode/sessions.js +48 -0
- package/src/{server/services/opencode-statistics-service.js → platforms/drivers/opencode/statistics-implementation.js} +2 -2
- package/src/platforms/drivers/opencode/statistics.js +16 -0
- package/src/platforms/drivers/secure-file-driver.js +272 -0
- package/src/platforms/drivers/unsupported.js +7 -0
- package/src/platforms/manifest-schema.js +398 -0
- package/src/platforms/manifests/claude.json +489 -0
- package/src/platforms/manifests/codex.json +448 -0
- package/src/platforms/manifests/gemini.json +447 -0
- package/src/platforms/manifests/omp.json +496 -0
- package/src/platforms/manifests/opencode.json +422 -0
- package/src/{server/services → platforms}/native-oauth-adapters.js +87 -417
- package/src/{server/services → platforms}/notification-hooks.js +140 -1023
- package/src/platforms/oauth-credentials-service.js +506 -0
- package/src/platforms/path-resolver.js +179 -0
- package/src/platforms/platform-path-context.js +305 -0
- package/src/platforms/registry.js +248 -0
- package/src/platforms/remote-notification-providers.js +678 -0
- package/src/platforms/runtime.js +259 -0
- package/src/{server/services/session-history-adapters/index.js → platforms/session-history-adapters.js} +8 -1
- package/src/plugins/plugin-loader.js +2 -2
- package/src/plugins/plugin-manager.js +2 -2
- package/src/plugins/registry.js +27 -10
- package/src/reset-config.js +21 -5
- package/src/server/api/agents.js +46 -72
- package/src/server/api/commands.js +16 -6
- package/src/server/api/config-registry.js +206 -59
- package/src/server/api/config-sync.js +3 -2
- package/src/server/api/config-templates.js +20 -4
- package/src/server/api/config.js +111 -102
- package/src/server/api/convert.js +54 -178
- package/src/server/api/dashboard.js +168 -102
- package/src/server/api/hooks.js +1 -1
- package/src/server/api/mcp.js +224 -84
- package/src/server/api/platform-api-config.js +102 -0
- package/src/server/api/platform-route-factory.js +215 -0
- package/src/server/api/platform-routes.js +26 -0
- package/src/server/api/platforms.js +110 -0
- package/src/server/api/plugins.js +57 -57
- package/src/server/api/project-config.js +168 -0
- package/src/server/api/prompts.js +58 -45
- package/src/server/api/settings.js +55 -8
- package/src/server/api/skills.js +262 -133
- package/src/server/api/ui-config.js +2 -2
- package/src/server/api/validation-errors.js +18 -4
- package/src/server/api/workspaces.js +47 -12
- package/src/server/index.js +164 -213
- package/src/server/platform-context.js +81 -0
- package/src/server/services/channel-balance.js +11 -20
- package/src/server/services/channel-health.js +21 -6
- package/src/server/services/channel-scheduler.js +29 -39
- package/src/server/services/channel-sync-utils.js +23 -13
- package/src/server/services/config-export-service.js +350 -319
- package/src/server/services/config-registry-service.js +280 -54
- package/src/server/services/config-sync-manager.js +172 -944
- package/src/server/services/config-templates-service.js +419 -353
- package/src/server/services/control-manifest-store.js +206 -0
- package/src/server/services/dashboard-snapshot-worker.js +656 -100
- package/src/server/services/effective-control-service.js +557 -0
- package/src/server/services/local-resource-index.js +314 -0
- package/src/server/services/mcp-service.js +607 -977
- package/src/server/services/model-detector.js +15 -10
- package/src/server/services/native-log-channel-resolver.js +214 -0
- package/src/server/services/native-log-observer.js +291 -0
- package/src/server/services/network-access.js +1 -1
- package/src/server/services/platform-catalog.js +82 -0
- package/src/server/services/platform-resolution.js +32 -29
- package/src/server/services/plugins-service.js +376 -122
- package/src/server/services/project-config-service.js +550 -0
- package/src/server/services/project-path-validation.js +31 -0
- package/src/server/services/prompts-service.js +267 -233
- package/src/server/services/proxy-runtime.js +7 -4
- package/src/server/services/remote-credential-cache.js +43 -0
- package/src/server/services/repo-scanner-base.js +182 -75
- package/src/server/services/request-logger.js +7 -4
- package/src/server/services/session-history-index.js +780 -213
- package/src/server/services/session-history-worker.js +228 -33
- package/src/server/services/session-snapshot-worker.js +124 -34
- package/src/server/services/session-snapshots.js +4 -2
- package/src/server/services/skill-artifact-store.js +355 -0
- package/src/server/services/skill-format-adapters.js +65 -0
- package/src/server/services/skill-projection-service.js +323 -0
- package/src/server/services/skill-refresh-task-service.js +275 -0
- package/src/server/services/skill-service.js +2020 -677
- package/src/server/services/snapshot-cache.js +30 -8
- package/src/server/services/speed-test.js +8 -8
- package/src/server/services/sqlite-connection.js +19 -3
- package/src/server/services/statistics-service.js +240 -57
- package/src/server/services/ui-config.js +107 -76
- package/src/server/services/usage-log-utils.js +98 -0
- package/src/server/services/web-build.js +1 -1
- package/src/server/services/workspace-service.js +59 -29
- package/src/server/utils/pricing.js +10 -19
- package/src/server/websocket-server.js +153 -28
- package/src/{server/services/base → shared}/base-channel-service.js +156 -28
- package/src/shared/capability-driver.js +104 -0
- package/src/shared/channel-auth-policy.js +27 -0
- package/src/shared/channel-driver.js +161 -0
- package/src/shared/driver-factories/api.js +180 -0
- package/src/shared/driver-factories/mcp.js +244 -0
- package/src/shared/driver-factories/projects.js +121 -0
- package/src/shared/driver-factories/prompts.js +58 -0
- package/src/shared/driver-factories/resource-sync.js +375 -0
- package/src/shared/driver-result.js +78 -0
- package/src/shared/mcp-format.js +189 -0
- package/src/shared/platforms.js +104 -167
- package/src/shared/project-config.js +512 -0
- package/src/shared/proxy-driver.js +57 -0
- package/src/{server/services/base → shared}/response-usage-parser.js +8 -4
- package/src/ui/menu.js +91 -67
- package/src/utils/port-helper.js +35 -19
- package/src/utils/session.js +1 -1
- package/dist/web/assets/Analytics-COEUXmd4.js +0 -25
- package/dist/web/assets/Analytics-RNn1BUbG.css +0 -1
- package/dist/web/assets/ConfigTemplates-DLLkS53H.js +0 -1
- package/dist/web/assets/Home-DCEyLzwX.js +0 -1
- package/dist/web/assets/Home-DYVy4cGZ.css +0 -1
- package/dist/web/assets/PluginManager-Oe1ZHoMO.js +0 -1
- package/dist/web/assets/ProjectList-CWjuAFof.js +0 -1
- package/dist/web/assets/ProjectList-VeMNXfgO.css +0 -1
- package/dist/web/assets/SessionList-C4chCPxK.css +0 -1
- package/dist/web/assets/SessionList-v3ehGg9U.js +0 -1
- package/dist/web/assets/SkillManager-BsX8Qpj0.css +0 -1
- package/dist/web/assets/SkillManager-DUF_qP5S.js +0 -1
- package/dist/web/assets/WorkspaceManager-CjAWBosO.js +0 -1
- package/dist/web/assets/icons-nGdSKQn2.js +0 -1
- package/dist/web/assets/index-D5hyHPj7.js +0 -2
- package/dist/web/assets/index-bJRG3RXH.css +0 -1
- package/dist/web/assets/markdown-BNJfEgCA.js +0 -1
- package/dist/web/assets/markdown-BfC0goYb.css +0 -10
- package/dist/web/assets/naive-ui-BALVpMgd.js +0 -1
- package/dist/web/assets/vendors-DQA4Hw9v.js +0 -7
- package/dist/web/assets/vue-vendor-B5RqjEjQ.js +0 -45
- package/src/config/model-pricing.js +0 -35
- package/src/server/api/channels.js +0 -439
- package/src/server/api/claude-hooks.js +0 -56
- package/src/server/api/claude-statistics.js +0 -57
- package/src/server/api/codex-channels.js +0 -491
- package/src/server/api/codex-projects.js +0 -117
- package/src/server/api/codex-proxy.js +0 -293
- package/src/server/api/codex-sessions.js +0 -652
- package/src/server/api/codex-statistics.js +0 -57
- package/src/server/api/gemini-channels.js +0 -392
- package/src/server/api/gemini-projects.js +0 -103
- package/src/server/api/gemini-proxy.js +0 -231
- package/src/server/api/gemini-sessions.js +0 -550
- package/src/server/api/gemini-statistics.js +0 -57
- package/src/server/api/health-check.js +0 -31
- package/src/server/api/oauth-credentials.js +0 -198
- package/src/server/api/omp-channels.js +0 -575
- package/src/server/api/omp-projects.js +0 -88
- package/src/server/api/omp-proxy.js +0 -125
- package/src/server/api/omp-sessions.js +0 -386
- package/src/server/api/omp-statistics.js +0 -40
- package/src/server/api/opencode-channels.js +0 -471
- package/src/server/api/opencode-projects.js +0 -110
- package/src/server/api/opencode-proxy.js +0 -354
- package/src/server/api/opencode-sessions.js +0 -466
- package/src/server/api/opencode-statistics.js +0 -57
- package/src/server/api/projects.js +0 -120
- package/src/server/api/proxy.js +0 -330
- package/src/server/api/sessions.js +0 -810
- package/src/server/services/oauth-credentials-service.js +0 -945
- package/src/server/services/omp-session-log-observer.js +0 -138
- package/src/server/services/opencode-gateway-adapters.js +0 -43
- package/src/server/services/project-snapshots.js +0 -59
- package/src/server/services/proxy-log-helper.js +0 -298
- package/src/server/services/session-cache.js +0 -127
- package/src/server/services/session-converter.js +0 -582
- package/src/server/services/session-history-adapters/claude.js +0 -235
- /package/src/{server/services/claude-token-recovery.js → platforms/drivers/claude/token-recovery.js} +0 -0
- /package/src/{server/services/codex-parser.js → platforms/drivers/codex/parser.js} +0 -0
- /package/src/{server/services/codex-speed-test-template.json → platforms/drivers/codex/speed-test-template.json} +0 -0
- /package/src/{server/services/codex-wire.js → platforms/drivers/codex/wire.js} +0 -0
- /package/src/{server/services/omp-auth-gateway-client.js → platforms/drivers/omp/auth-gateway-client.js} +0 -0
- /package/src/{server/services/opencode-normalization.js → platforms/drivers/opencode/normalization.js} +0 -0
- /package/src/{server/services → shared}/config-artifact-paths.js +0 -0
- /package/src/{server/services/base → shared}/proxy-utils.js +0 -0
|
@@ -0,0 +1,1578 @@
|
|
|
1
|
+
# Configuration-Driven CLI Platform Implementation Plan
|
|
2
|
+
|
|
3
|
+
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
|
4
|
+
|
|
5
|
+
**Goal:** 将平台扩展改造成 Manifest + Capability Driver 架构,让符合通用协议的 CLI 只需增加安全的 Manifest 配置,同时保持现有五个平台行为和测试 mock path。
|
|
6
|
+
|
|
7
|
+
**Architecture:** 内置和用户平台由 `Platform Registry` 统一加载、校验和暴露。项目、会话、渠道、代理、统计和资源同步分别使用能力 driver;Claude、Codex、Gemini、OpenCode、OMP 的现有模块保留为稳定 facade 和专用 driver。CLI、HTTP、Dashboard 和 Web UI 只依赖 Registry 与 application use case,不直接维护平台 `switch`。
|
|
8
|
+
|
|
9
|
+
**Tech Stack:** Node.js >= 22.13.0、CommonJS、Express、AJV、Vitest、Vue 3、Vite、Pinia、现有 `require.resolve()` + `require.cache` 测试 seam。
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Constraints and Working-Tree Safety
|
|
14
|
+
|
|
15
|
+
- 不重置、覆盖或格式化当前已有的 skill 相关未提交修改:`package-lock.json`、`scripts/test-skill-providers.js`、`src/server/api/plugins.js`、`src/server/api/skills.js`、`src/server/services/skill-service.js`、`tests/unit/services/skill-service.test.js` 和两个 SQLite WAL 文件。
|
|
16
|
+
- 每个提交只 `git add` 本任务明确列出的文件。
|
|
17
|
+
- 保留现有模块路径和导出名称,尤其是 `src/config/paths.js`、五个平台 channel service、五个平台 proxy server 和对应 API facade。
|
|
18
|
+
- 不在迁移中改变公开 CLI 命令、现有 API 路径、OMP managed mode 或原生配置恢复语义。
|
|
19
|
+
- 每个任务先写行为测试,再写实现;任务内只运行对应的 focused test,整套测试放在最终验证任务运行。
|
|
20
|
+
|
|
21
|
+
## File Map
|
|
22
|
+
|
|
23
|
+
### New core files
|
|
24
|
+
|
|
25
|
+
- `src/platforms/manifest-schema.js`: Manifest 的 AJV schema、校验错误格式和 driver ID allowlist 校验。
|
|
26
|
+
- `src/platforms/manifests/*.json`: Claude、Codex、Gemini、OpenCode、OMP 的内置声明。
|
|
27
|
+
- `src/platforms/path-resolver.js`: 受控环境变量、`{home}` 模板和内置特殊路径解析。
|
|
28
|
+
- `src/platforms/driver-registry.js`: capability driver 注册、allowlist 和 lazy factory。
|
|
29
|
+
- `src/platforms/registry.js`: 内置 Manifest、`~/.cc-tool/config/platforms.json`、旧 UI 配置兼容数据的合并与公开字段投影。
|
|
30
|
+
- `src/platforms/runtime.js`: Registry、PathResolver、DriverRegistry 和基础依赖的组合入口。
|
|
31
|
+
- `src/platforms/drivers/unsupported.js`: 明确返回 `unsupported` 的 driver。
|
|
32
|
+
- `src/platforms/drivers/generic-jsonl.js`: 声明式 JSONL 会话 inventory/parse driver。
|
|
33
|
+
- `src/platforms/drivers/generic-filesystem.js`: 声明式目录/文件资源同步 driver。
|
|
34
|
+
- `src/platforms/drivers/generic-openai-compatible.js`: 标准 OpenAI-compatible 渠道传输 driver。
|
|
35
|
+
- `src/platforms/drivers/legacy.js`: 通过原有模块路径懒加载现有平台实现的 driver factory。
|
|
36
|
+
- `src/server/api/platforms.js`: 平台目录和通用平台能力 API。
|
|
37
|
+
- `src/server/api/platform-route-factory.js`: 新平台使用的通用 route factory。
|
|
38
|
+
- `src/web/src/api/platforms.js`: 平台目录 API client。
|
|
39
|
+
- `src/web/src/stores/platforms.js`: 前端平台目录的加载、缓存和状态。
|
|
40
|
+
- `src/web/src/config/iconTokens.js`: `iconToken` 到 Vue 图标组件的唯一映射。
|
|
41
|
+
- `src/web/src/components/channel/commonChannelSchema.js`: 通用渠道表单 schema。
|
|
42
|
+
|
|
43
|
+
### Existing files to modify
|
|
44
|
+
|
|
45
|
+
- `package.json`: 将 `src/platforms/` 纳入发布文件。
|
|
46
|
+
- `src/config/paths.js`: 增加中心平台配置文件路径,同时保留既有 `PATHS`、`NATIVE_PATHS` 结构。
|
|
47
|
+
- `src/shared/platforms.js`: 保留规范化函数,改为消费 Registry 投影或调用共享归一化逻辑。
|
|
48
|
+
- `src/server/services/session-history-adapters/index.js`: 保留路径和导出,接入 Registry 的 session driver 兼容层。
|
|
49
|
+
- `src/server/services/session-history-index.js`: 默认 adapter 从 runtime 获取,显式 `adapterRegistry` 仍优先。
|
|
50
|
+
- `src/server/services/dashboard-snapshot-worker.js`: 用 capability driver 替换平台聚合 `switch`。
|
|
51
|
+
- `src/server/index.js`: 挂载平台目录 API,逐步切换自动恢复和 route registration。
|
|
52
|
+
- `src/server/services/session-history-worker.js`: 子进程 inventory boot、结构化 IPC 错误和协议清理。
|
|
53
|
+
- `src/server/services/config-sync-manager.js`: 增加 `syncToPlatform`,让批量同步遍历 Registry。
|
|
54
|
+
- `src/server/services/config-registry-service.js`: 平台集合和支持矩阵从 Registry 派生。
|
|
55
|
+
- `src/server/services/config-export-service.js`: 平台 snapshot 和 native config 通过 driver 获取。
|
|
56
|
+
- `src/server/services/config-templates-service.js`: AI config 文件名和支持平台从 Manifest 派生。
|
|
57
|
+
- `src/server/services/ui-config.js`: 读取旧 `customCliPlatforms` 并迁移到 `platforms.json`。
|
|
58
|
+
- `src/commands/proxy-control.js`: 标签、端点和可用平台从 Registry 获取。
|
|
59
|
+
- `src/commands/toggle-proxy.js`: 通过 ProxyDriver 解析平台。
|
|
60
|
+
- `src/commands/daemon.js`: 端口和状态输出遍历 Registry。
|
|
61
|
+
- `src/commands/cli-type.js`: 类型选项从 Registry 派生。
|
|
62
|
+
- `src/index.js`: 保留兼容命令,平台路由使用 command registry。
|
|
63
|
+
- `src/web/src/config/platforms.js`: 删除完整内置平台副本,只保留 API 归一化、图标和排序 fallback。
|
|
64
|
+
- `src/web/src/main.js`、`src/web/src/stores/platforms.js`: 启动时加载公共平台目录并保留最小离线 fallback。
|
|
65
|
+
- `src/web/src/components/channel/BaseChannelPanel.vue`、`src/web/src/components/channel/commonChannelSchema.js`: 泛型渠道面板和 CRUD API 接入。
|
|
66
|
+
- `src/web/src/components/dashboard/ChannelColumn.vue`、`src/web/src/views/SkillManager.vue`、资源面板及命令抽屉:消费 reactive platform store。
|
|
67
|
+
- `src/web/src/api/channels.js`: 增加基于 platform key 的通用请求函数,保留旧导出。
|
|
68
|
+
- `src/web/src/components/channel/channelPanelFactories.js`: 提取 common schema,保留平台专用字段和 driver。
|
|
69
|
+
- `src/web/src/composables/useUIConfig.js`、`src/web/src/views/Home.vue`、`SettingsDrawer.vue` 及平台面板:消费 reactive platform catalog。
|
|
70
|
+
|
|
71
|
+
- `src/web/vitest.config.js`: 扩展前端测试发现范围。
|
|
72
|
+
### New test files
|
|
73
|
+
|
|
74
|
+
- `tests/unit/platforms/manifest-schema.test.js`
|
|
75
|
+
- `tests/unit/platforms/path-resolver.test.js`
|
|
76
|
+
- `tests/unit/platforms/registry.test.js`
|
|
77
|
+
- `tests/unit/platforms/generic-drivers.test.js`
|
|
78
|
+
- `tests/unit/platforms/legacy-drivers.test.js`
|
|
79
|
+
- `tests/unit/services/dashboard-snapshot-worker.test.js`
|
|
80
|
+
- `tests/unit/api/platforms-api.test.js`
|
|
81
|
+
- `tests/unit/commands/proxy-control.test.js`
|
|
82
|
+
- `tests/unit/commands/platform-command-registry.test.js`
|
|
83
|
+
- `tests/unit/commands/cli-type.test.js`
|
|
84
|
+
- `src/web/src/config/__tests__/platforms.test.js`
|
|
85
|
+
- `src/web/src/stores/__tests__/platforms.test.js`
|
|
86
|
+
- `tests/unit/platforms/config-only-contract.test.js`
|
|
87
|
+
- `tests/unit/services/session-history-worker.test.js`
|
|
88
|
+
- `src/web/src/components/channel/__tests__/commonChannelSchema.test.js`
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
### Task 1: Add Manifest Schema and Registry Foundation
|
|
93
|
+
|
|
94
|
+
**Files:**
|
|
95
|
+
- Create: `src/platforms/manifest-schema.js`
|
|
96
|
+
- Create: `src/platforms/manifests/claude.json`
|
|
97
|
+
- Create: `src/platforms/manifests/codex.json`
|
|
98
|
+
- Create: `src/platforms/manifests/gemini.json`
|
|
99
|
+
- Create: `src/platforms/manifests/opencode.json`
|
|
100
|
+
- Create: `src/platforms/manifests/omp.json`
|
|
101
|
+
- Create: `src/platforms/path-resolver.js`
|
|
102
|
+
- Create: `src/platforms/registry.js`
|
|
103
|
+
- Create: `src/platforms/runtime.js`
|
|
104
|
+
- Modify: `src/config/paths.js`
|
|
105
|
+
- Modify: `package.json`
|
|
106
|
+
- Test: `tests/unit/platforms/manifest-schema.test.js`
|
|
107
|
+
- Test: `tests/unit/platforms/path-resolver.test.js`
|
|
108
|
+
- Test: `tests/unit/platforms/registry.test.js`
|
|
109
|
+
|
|
110
|
+
- [ ] **Step 1: Write failing Manifest validation tests**
|
|
111
|
+
|
|
112
|
+
```js
|
|
113
|
+
const { validateManifest, normalizeManifestError } = require('../../../src/platforms/manifest-schema');
|
|
114
|
+
|
|
115
|
+
test('accepts a valid generic platform manifest', () => {
|
|
116
|
+
const result = validateManifest({
|
|
117
|
+
key: 'demo-cli',
|
|
118
|
+
label: 'Demo CLI',
|
|
119
|
+
command: 'demo',
|
|
120
|
+
iconToken: 'terminal',
|
|
121
|
+
paths: { home: '~/.demo', sessions: '{home}/sessions' },
|
|
122
|
+
capabilities: { sessions: 'generic-jsonl' }
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
expect(result.valid).toBe(true);
|
|
126
|
+
expect(result.errors).toEqual([]);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
test('rejects executable module paths and unknown drivers', () => {
|
|
130
|
+
const result = validateManifest({
|
|
131
|
+
key: 'bad-cli',
|
|
132
|
+
label: 'Bad',
|
|
133
|
+
command: 'bad',
|
|
134
|
+
driverModule: '/tmp/driver.js',
|
|
135
|
+
capabilities: { sessions: 'user-code' }
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
expect(result.valid).toBe(false);
|
|
139
|
+
expect(normalizeManifestError(result.errors)).toContain('driver');
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
test('rejects duplicate or malformed platform keys', () => {
|
|
143
|
+
const result = validateManifest({
|
|
144
|
+
key: 'Bad Key',
|
|
145
|
+
label: 'Bad',
|
|
146
|
+
command: 'bad',
|
|
147
|
+
capabilities: {}
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
expect(result.valid).toBe(false);
|
|
151
|
+
expect(normalizeManifestError(result.errors)).toContain('key');
|
|
152
|
+
});
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
- [ ] **Step 2: Run focused tests and verify the foundation is absent**
|
|
156
|
+
|
|
157
|
+
Run: `npx vitest run tests/unit/platforms/manifest-schema.test.js`
|
|
158
|
+
Expected: FAIL because `src/platforms/manifest-schema.js` does not exist.
|
|
159
|
+
|
|
160
|
+
- [ ] **Step 3: Implement the schema and safe validation result**
|
|
161
|
+
|
|
162
|
+
Use the existing `ajv` dependency and export exactly:
|
|
163
|
+
|
|
164
|
+
```js
|
|
165
|
+
const Ajv = require('ajv');
|
|
166
|
+
|
|
167
|
+
const DRIVER_IDS = new Set([
|
|
168
|
+
'unsupported',
|
|
169
|
+
'generic-jsonl',
|
|
170
|
+
'generic-filesystem',
|
|
171
|
+
'generic-openai-compatible',
|
|
172
|
+
'legacy:claude',
|
|
173
|
+
'legacy:codex',
|
|
174
|
+
'legacy:gemini',
|
|
175
|
+
'legacy:opencode',
|
|
176
|
+
'legacy:omp'
|
|
177
|
+
]);
|
|
178
|
+
|
|
179
|
+
const PATH_RESOLVER_IDS = new Set([
|
|
180
|
+
'declarative',
|
|
181
|
+
'claude',
|
|
182
|
+
'codex',
|
|
183
|
+
'gemini',
|
|
184
|
+
'opencode',
|
|
185
|
+
'omp'
|
|
186
|
+
]);
|
|
187
|
+
|
|
188
|
+
const schema = {
|
|
189
|
+
type: 'object',
|
|
190
|
+
required: ['key', 'label', 'command', 'capabilities'],
|
|
191
|
+
additionalProperties: false,
|
|
192
|
+
properties: {
|
|
193
|
+
key: { type: 'string', pattern: '^[a-z0-9][a-z0-9_-]*$' },
|
|
194
|
+
label: { type: 'string', minLength: 1 },
|
|
195
|
+
title: { type: 'string', minLength: 1 },
|
|
196
|
+
command: { type: 'string', minLength: 1 },
|
|
197
|
+
iconToken: { type: 'string', minLength: 1 },
|
|
198
|
+
color: { type: 'string' },
|
|
199
|
+
defaultVisible: { type: 'boolean' },
|
|
200
|
+
custom: { type: 'boolean' },
|
|
201
|
+
apiBasePath: { type: 'string', minLength: 1 },
|
|
202
|
+
logFile: { type: 'string', minLength: 1 },
|
|
203
|
+
logAliases: { type: 'array', items: { type: 'string', minLength: 1 } },
|
|
204
|
+
portKey: { type: 'string', minLength: 1 },
|
|
205
|
+
defaultPort: { type: 'integer', minimum: 1 },
|
|
206
|
+
statisticsPath: { type: 'string', minLength: 1 },
|
|
207
|
+
promptFile: { type: ['string', 'null'] },
|
|
208
|
+
paths: { type: 'object', additionalProperties: { type: 'string' } },
|
|
209
|
+
pathResolverId: { enum: [...PATH_RESOLVER_IDS] },
|
|
210
|
+
sessionMapping: { type: 'object', additionalProperties: { type: 'string' } },
|
|
211
|
+
resourceMappings: { type: 'object', additionalProperties: { type: 'string' } },
|
|
212
|
+
capabilities: {
|
|
213
|
+
type: 'object',
|
|
214
|
+
additionalProperties: { enum: [...DRIVER_IDS] }
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
const validate = new Ajv({ allErrors: true, strict: false }).compile(schema);
|
|
220
|
+
|
|
221
|
+
function validateManifest(manifest) {
|
|
222
|
+
const valid = validate(manifest);
|
|
223
|
+
return { valid, errors: valid ? [] : (validate.errors || []) };
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function normalizeManifestError(errors = []) {
|
|
227
|
+
return errors.map(error => `${error.instancePath || '/'} ${error.message}`).join('; ');
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
module.exports = { DRIVER_IDS, PATH_RESOLVER_IDS, schema, validateManifest, normalizeManifestError };
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
The implementation must additionally reject `capabilities` whose value is not in `DRIVER_IDS`; do not accept arbitrary `modulePath` fields.
|
|
234
|
+
|
|
235
|
+
- [ ] **Step 4: Add path resolution tests with injected environment and command runner**
|
|
236
|
+
|
|
237
|
+
```js
|
|
238
|
+
const { resolveManifestPaths } = require('../../../src/platforms/path-resolver');
|
|
239
|
+
|
|
240
|
+
test('expands home and environment values without touching the real home directory', () => {
|
|
241
|
+
const paths = resolveManifestPaths({
|
|
242
|
+
key: 'demo-cli',
|
|
243
|
+
paths: { home: '$DEMO_HOME', sessions: '{home}/sessions' }
|
|
244
|
+
}, { env: { DEMO_HOME: '/tmp/demo-home' }, homeDir: '/tmp/test-home' });
|
|
245
|
+
|
|
246
|
+
expect(paths).toEqual({
|
|
247
|
+
home: '/tmp/demo-home',
|
|
248
|
+
sessions: '/tmp/demo-home/sessions'
|
|
249
|
+
});
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
test('uses the injected OMP command runner for special native paths', () => {
|
|
253
|
+
const paths = resolveManifestPaths({ key: 'omp', pathResolverId: 'omp' }, {
|
|
254
|
+
env: { OMP_COMMAND: 'omp' },
|
|
255
|
+
commandRunner: () => '/tmp/omp-agent\n'
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
expect(paths.home).toBe('/tmp/omp-agent');
|
|
259
|
+
});
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
- [ ] **Step 5: Implement `PathResolver` and add the center config path**
|
|
263
|
+
|
|
264
|
+
`resolveManifestPaths(manifest, { env, homeDir, commandRunner })` must:
|
|
265
|
+
|
|
266
|
+
```js
|
|
267
|
+
function resolveTemplate(value, resolved) {
|
|
268
|
+
return String(value)
|
|
269
|
+
.replace(/\$([A-Z][A-Z0-9_]*)/g, (_, name) => resolved.env[name] || '')
|
|
270
|
+
.replace(/\{home\}/g, resolved.home);
|
|
271
|
+
}
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
It must support `~`, `$ENV_NAME`, `{home}`, path normalization, and the existing OMP command/profile path behavior. It must reject an empty required root and never resolve a path outside the configured home unless the Manifest explicitly uses an absolute path.
|
|
275
|
+
|
|
276
|
+
Add `PATHS.platforms = path.join(CONFIG_DIR, 'platforms.json')` in `src/config/paths.js`. Existing tests that provide partial `PATHS` objects must continue to work through a fallback:
|
|
277
|
+
|
|
278
|
+
```js
|
|
279
|
+
const platformsFile = PATHS.platforms || path.join(PATHS.config || process.cwd(), 'platforms.json');
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
- [ ] **Step 6: Add the five built-in Manifests**
|
|
283
|
+
|
|
284
|
+
Each JSON file must contain the current display metadata, path resolver ID when native paths are special, and capability IDs. The OMP Manifest must keep `proxy: 'legacy:omp'`; it must not be assigned the generic proxy driver.
|
|
285
|
+
|
|
286
|
+
- [ ] **Step 7: Implement Registry and Runtime with deterministic merge rules**
|
|
287
|
+
|
|
288
|
+
Export:
|
|
289
|
+
|
|
290
|
+
```js
|
|
291
|
+
function createPlatformRegistry({ builtIns, userFile, legacyUiConfig, fsImpl, logger } = {}) {
|
|
292
|
+
return {
|
|
293
|
+
resolve(key),
|
|
294
|
+
list({ enabledOnly = false } = {}),
|
|
295
|
+
getCapability(key, capability),
|
|
296
|
+
resolvePaths(key, options),
|
|
297
|
+
getPublicDefinition(key),
|
|
298
|
+
diagnostics()
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function createPlatformRuntime({ registry, driverRegistry, dependencies = {} } = {}) {
|
|
303
|
+
return {
|
|
304
|
+
registry,
|
|
305
|
+
getDriver(platform, capability, context = {}),
|
|
306
|
+
invoke(platform, capability, operation, args = [])
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
`runtime.js` also exports lazy `getPlatformRegistry()` and `getPlatformRuntime()` singletons for production callers. Tests use `createPlatformRegistry()` and `createPlatformRuntime()` directly to avoid global state.
|
|
312
|
+
|
|
313
|
+
Merge order is built-in definitions, then valid user definitions, with built-in keys winning. Legacy `ui-config.customCliPlatforms` is metadata-only until Task 9 migration. An invalid user entry is excluded and added to `diagnostics()`; an invalid built-in entry throws.
|
|
314
|
+
|
|
315
|
+
- [ ] **Step 8: Add Registry tests and package inclusion**
|
|
316
|
+
|
|
317
|
+
```js
|
|
318
|
+
const { createPlatformRegistry } = require('../../../src/platforms/registry');
|
|
319
|
+
test('resolves built-ins and rejects a user override', () => {
|
|
320
|
+
const registry = createPlatformRegistry({
|
|
321
|
+
builtIns: [{ key: 'claude', label: 'Claude', command: 'claude', capabilities: {} }],
|
|
322
|
+
userFile: {
|
|
323
|
+
platforms: [
|
|
324
|
+
{ key: 'claude', label: 'Fake Claude', command: 'fake', capabilities: {} },
|
|
325
|
+
{ key: 'demo-cli', label: 'Demo', command: 'demo', capabilities: { sessions: 'generic-jsonl' } }
|
|
326
|
+
]
|
|
327
|
+
}
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
expect(registry.resolve('claude').label).toBe('Claude');
|
|
331
|
+
expect(registry.resolve('demo-cli').label).toBe('Demo');
|
|
332
|
+
expect(registry.diagnostics()).toEqual([]);
|
|
333
|
+
});
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
Add `src/platforms/` to the `files` array in `package.json`. Run: `npx vitest run tests/unit/platforms/manifest-schema.test.js tests/unit/platforms/path-resolver.test.js tests/unit/platforms/registry.test.js`. Expected: PASS.
|
|
337
|
+
|
|
338
|
+
- [ ] **Step 9: Commit the foundation**
|
|
339
|
+
|
|
340
|
+
```bash
|
|
341
|
+
git add package.json src/config/paths.js src/platforms tests/unit/platforms
|
|
342
|
+
git commit -m "feat: add platform manifest registry"
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
---
|
|
346
|
+
|
|
347
|
+
### Task 2: Add Driver Registry and Generic Drivers
|
|
348
|
+
|
|
349
|
+
**Files:**
|
|
350
|
+
- Create: `src/platforms/driver-registry.js`
|
|
351
|
+
- Create: `src/platforms/drivers/unsupported.js`
|
|
352
|
+
- Create: `src/platforms/drivers/generic-jsonl.js`
|
|
353
|
+
- Create: `src/platforms/drivers/generic-filesystem.js`
|
|
354
|
+
- Create: `src/platforms/drivers/generic-openai-compatible.js`
|
|
355
|
+
- Create: `tests/unit/platforms/generic-drivers.test.js`
|
|
356
|
+
|
|
357
|
+
- [ ] **Step 1: Write driver contract tests first**
|
|
358
|
+
|
|
359
|
+
```js
|
|
360
|
+
const { createDriverRegistry } = require('../../../src/platforms/driver-registry');
|
|
361
|
+
const { createGenericJsonlDriver } = require('../../../src/platforms/drivers/generic-jsonl');
|
|
362
|
+
const { createGenericFilesystemDriver } = require('../../../src/platforms/drivers/generic-filesystem');
|
|
363
|
+
const { createGenericOpenAICompatibleDriver } = require('../../../src/platforms/drivers/generic-openai-compatible');
|
|
364
|
+
|
|
365
|
+
function makeRegistry() {
|
|
366
|
+
return createDriverRegistry({
|
|
367
|
+
drivers: {
|
|
368
|
+
'generic-jsonl': createGenericJsonlDriver,
|
|
369
|
+
'generic-filesystem': createGenericFilesystemDriver,
|
|
370
|
+
'generic-openai-compatible': createGenericOpenAICompatibleDriver,
|
|
371
|
+
unsupported: ({ platform, capability }) => ({
|
|
372
|
+
status: 'unsupported', platform, capability
|
|
373
|
+
})
|
|
374
|
+
}
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
test('generic JSONL driver inventories and normalizes sessions', async () => {
|
|
379
|
+
const fsImpl = {
|
|
380
|
+
readdir: async () => ['session-1.jsonl'],
|
|
381
|
+
stat: async () => ({ size: 20, mtimeMs: 10 }),
|
|
382
|
+
readFile: async () => '{"id":"m1","role":"user","content":"hello"}\n'
|
|
383
|
+
};
|
|
384
|
+
const driver = makeRegistry().create('generic-jsonl', {
|
|
385
|
+
platform: 'demo-cli',
|
|
386
|
+
manifest: {
|
|
387
|
+
paths: { sessions: '/tmp/demo/sessions' },
|
|
388
|
+
sessionMapping: { sessionId: 'id', messages: 'messages' }
|
|
389
|
+
},
|
|
390
|
+
fsImpl
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
const descriptors = await driver.inventory();
|
|
394
|
+
expect(descriptors[0]).toEqual(expect.objectContaining({ sessionId: 'session-1' }));
|
|
395
|
+
const parsed = await driver.parse(descriptors[0]);
|
|
396
|
+
expect(parsed.messages[0]).toEqual(expect.objectContaining({ role: 'user', content: 'hello' }));
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
test('unsupported driver never returns a successful empty value', () => {
|
|
400
|
+
const result = makeRegistry().create('unsupported', {
|
|
401
|
+
platform: 'demo-cli', capability: 'proxy'
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
expect(result.status).toBe('unsupported');
|
|
405
|
+
expect(result.status).not.toBe('ok');
|
|
406
|
+
});
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
- [ ] **Step 2: Run the focused contract test and verify it fails**
|
|
410
|
+
|
|
411
|
+
Run: `npx vitest run tests/unit/platforms/generic-drivers.test.js`
|
|
412
|
+
Expected: FAIL because the driver registry and generic driver modules do not exist.
|
|
413
|
+
|
|
414
|
+
- [ ] **Step 3: Implement lazy driver registration**
|
|
415
|
+
|
|
416
|
+
```js
|
|
417
|
+
function createDriverRegistry({ drivers = {} } = {}) {
|
|
418
|
+
const factories = new Map(Object.entries(drivers));
|
|
419
|
+
|
|
420
|
+
return {
|
|
421
|
+
register(id, factory) {
|
|
422
|
+
if (!/^[a-z0-9:_-]+$/.test(id) || typeof factory !== 'function') {
|
|
423
|
+
throw new Error(`Invalid capability driver: ${id}`);
|
|
424
|
+
}
|
|
425
|
+
factories.set(id, factory);
|
|
426
|
+
},
|
|
427
|
+
has(id) {
|
|
428
|
+
return factories.has(id);
|
|
429
|
+
},
|
|
430
|
+
create(id, context = {}) {
|
|
431
|
+
const factory = factories.get(id);
|
|
432
|
+
if (!factory) throw new Error(`Unknown capability driver: ${id}`);
|
|
433
|
+
return factory(context);
|
|
434
|
+
},
|
|
435
|
+
ids() {
|
|
436
|
+
return [...factories.keys()];
|
|
437
|
+
}
|
|
438
|
+
};
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
module.exports = { createDriverRegistry };
|
|
442
|
+
```
|
|
443
|
+
`unsupported.js` exports a concrete factory:
|
|
444
|
+
|
|
445
|
+
```js
|
|
446
|
+
function createUnsupportedDriver({ platform, capability }) {
|
|
447
|
+
return { status: 'unsupported', platform, capability };
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
module.exports = { createUnsupportedDriver };
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
The Registry must call `driverRegistry.create()` only after resolving a capability. It must not load all platform services at module import time.
|
|
454
|
+
|
|
455
|
+
- [ ] **Step 4: Implement `generic-jsonl` with explicit field mappings**
|
|
456
|
+
|
|
457
|
+
The driver must accept `manifest.paths.sessions`, an optional `sessionGlob`, and mappings for `sessionId`, `projectName`, `messages`, `role`, `content`, `timestamp`, and `model`. It returns descriptors matching the existing index contract:
|
|
458
|
+
|
|
459
|
+
```js
|
|
460
|
+
{
|
|
461
|
+
filePath,
|
|
462
|
+
size,
|
|
463
|
+
mtimeMs,
|
|
464
|
+
sessionId,
|
|
465
|
+
projectHint
|
|
466
|
+
}
|
|
467
|
+
```
|
|
468
|
+
|
|
469
|
+
Parsing malformed lines must produce a `failed` error with `platform`, `capability: 'sessions'`, `operation: 'parse'`, and the file path. It must not silently convert malformed input into a successful empty session.
|
|
470
|
+
|
|
471
|
+
- [ ] **Step 5: Implement `generic-filesystem` with safe roots**
|
|
472
|
+
|
|
473
|
+
The driver must expose:
|
|
474
|
+
|
|
475
|
+
```js
|
|
476
|
+
{
|
|
477
|
+
list(type),
|
|
478
|
+
sync(type, name, sourceRoot),
|
|
479
|
+
remove(type, name)
|
|
480
|
+
}
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
It must normalize relative names, reject `..`, enforce the Manifest target root, preserve directory/file semantics, and return `{ status: 'ok', target }` or a typed failure. Do not add recursive deletion outside the resolved target root.
|
|
484
|
+
|
|
485
|
+
- [ ] **Step 6: Implement the generic OpenAI-compatible transport driver**
|
|
486
|
+
|
|
487
|
+
`generic-openai-compatible.js` handles endpoint normalization and request authentication only; it is not a local proxy lifecycle driver. A Manifest must use `proxy: 'unsupported'` unless a separate allowlisted proxy driver is implemented.
|
|
488
|
+
|
|
489
|
+
```js
|
|
490
|
+
function createGenericOpenAICompatibleDriver({ platform, manifest, fetchImpl = globalThis.fetch } = {}) {
|
|
491
|
+
const baseUrl = String(manifest.paths?.baseUrl || manifest.baseUrl || '').replace(/\/+$/, '');
|
|
492
|
+
if (!baseUrl) throw new Error(`Missing base URL for ${platform}`);
|
|
493
|
+
return {
|
|
494
|
+
platform,
|
|
495
|
+
capability: 'channels',
|
|
496
|
+
normalizeEndpoint(pathname = '') {
|
|
497
|
+
return `${baseUrl}/${String(pathname).replace(/^\/+/, '')}`;
|
|
498
|
+
},
|
|
499
|
+
buildHeaders(channel = {}) {
|
|
500
|
+
const headers = { 'Content-Type': 'application/json' };
|
|
501
|
+
if (channel.apiKey) headers.Authorization = `Bearer ${channel.apiKey}`;
|
|
502
|
+
return headers;
|
|
503
|
+
},
|
|
504
|
+
async request(pathname, channel, init = {}) {
|
|
505
|
+
const response = await fetchImpl(this.normalizeEndpoint(pathname), {
|
|
506
|
+
...init,
|
|
507
|
+
headers: { ...this.buildHeaders(channel), ...(init.headers || {}) }
|
|
508
|
+
});
|
|
509
|
+
if (!response.ok) {
|
|
510
|
+
throw new Error(`OpenAI-compatible request failed: ${response.status}`);
|
|
511
|
+
}
|
|
512
|
+
return response.json();
|
|
513
|
+
}
|
|
514
|
+
};
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
module.exports = { createGenericOpenAICompatibleDriver };
|
|
518
|
+
```
|
|
519
|
+
|
|
520
|
+
The driver must strip duplicate trailing slashes and never log API keys.
|
|
521
|
+
|
|
522
|
+
- [ ] **Step 7: Add generic driver tests for boundary failures**
|
|
523
|
+
|
|
524
|
+
Cover missing session directories, malformed JSONL, path traversal, duplicate session IDs, unsupported proxy capabilities, and OpenAI-compatible endpoint normalization. Run: `npx vitest run tests/unit/platforms/generic-drivers.test.js`. Expected: PASS.
|
|
525
|
+
|
|
526
|
+
- [ ] **Step 8: Commit generic drivers**
|
|
527
|
+
|
|
528
|
+
```bash
|
|
529
|
+
git add src/platforms/driver-registry.js src/platforms/drivers tests/unit/platforms/generic-drivers.test.js
|
|
530
|
+
git commit -m "feat: add generic platform capability drivers"
|
|
531
|
+
```
|
|
532
|
+
|
|
533
|
+
---
|
|
534
|
+
### Task 3: Wrap Existing Implementations Without Breaking Mock Paths
|
|
535
|
+
|
|
536
|
+
**Files:**
|
|
537
|
+
- Create: `src/platforms/drivers/legacy.js`
|
|
538
|
+
- Test: `tests/unit/platforms/legacy-drivers.test.js`
|
|
539
|
+
- Reference unchanged: `src/config/paths.js`, `src/server/services/channels.js`, `src/server/services/codex-channels.js`, `src/server/services/gemini-channels.js`, `src/server/services/opencode-channels.js`, `src/server/services/omp-channels.js`, `src/server/omp-proxy-server.js`
|
|
540
|
+
|
|
541
|
+
- [ ] **Step 1: Write the cache-interception regression test**
|
|
542
|
+
|
|
543
|
+
```js
|
|
544
|
+
const CHANNEL_SERVICE_PATH = require.resolve('../../../src/server/services/codex-channels');
|
|
545
|
+
const PATHS_PATH = require.resolve('../../../src/config/paths');
|
|
546
|
+
|
|
547
|
+
test('legacy driver loads the exact facade path after require.cache stubbing', () => {
|
|
548
|
+
const listChannels = vi.fn(() => ({ channels: [{ id: 'mock-channel' }] }));
|
|
549
|
+
require.cache[CHANNEL_SERVICE_PATH] = {
|
|
550
|
+
id: CHANNEL_SERVICE_PATH,
|
|
551
|
+
filename: CHANNEL_SERVICE_PATH,
|
|
552
|
+
loaded: true,
|
|
553
|
+
exports: { getChannels: listChannels }
|
|
554
|
+
};
|
|
555
|
+
require.cache[PATHS_PATH] = {
|
|
556
|
+
id: PATHS_PATH,
|
|
557
|
+
filename: PATHS_PATH,
|
|
558
|
+
loaded: true,
|
|
559
|
+
exports: { PATHS: { activeChannel: { codex: '/tmp/mock-active.json' } } }
|
|
560
|
+
};
|
|
561
|
+
|
|
562
|
+
const { createLegacyDriver } = require('../../../src/platforms/drivers/legacy');
|
|
563
|
+
const driver = createLegacyDriver({ platform: 'codex', capability: 'channels' });
|
|
564
|
+
expect(driver.list().channels).toEqual([{ id: 'mock-channel' }]);
|
|
565
|
+
expect(listChannels).toHaveBeenCalledTimes(1);
|
|
566
|
+
|
|
567
|
+
delete require.cache[CHANNEL_SERVICE_PATH];
|
|
568
|
+
delete require.cache[PATHS_PATH];
|
|
569
|
+
});
|
|
570
|
+
```
|
|
571
|
+
|
|
572
|
+
- [ ] **Step 2: Run the focused test and verify the new factory is absent**
|
|
573
|
+
|
|
574
|
+
Run: `npx vitest run tests/unit/platforms/legacy-drivers.test.js`
|
|
575
|
+
Expected: FAIL because `src/platforms/drivers/legacy.js` does not exist.
|
|
576
|
+
|
|
577
|
+
- [ ] **Step 3: Implement lazy legacy module resolution**
|
|
578
|
+
|
|
579
|
+
Use an explicit allowlisted table and lazy `require` calls:
|
|
580
|
+
|
|
581
|
+
```js
|
|
582
|
+
const MODULE_PATHS = {
|
|
583
|
+
claude: {
|
|
584
|
+
sessions: '../../server/services/sessions',
|
|
585
|
+
channels: '../../server/services/channels',
|
|
586
|
+
proxy: '../../server/proxy-server',
|
|
587
|
+
statistics: '../../server/services/claude-statistics-service'
|
|
588
|
+
},
|
|
589
|
+
codex: {
|
|
590
|
+
sessions: '../../server/services/codex-sessions',
|
|
591
|
+
channels: '../../server/services/codex-channels',
|
|
592
|
+
proxy: '../../server/codex-proxy-server',
|
|
593
|
+
statistics: '../../server/services/codex-statistics-service'
|
|
594
|
+
},
|
|
595
|
+
gemini: {
|
|
596
|
+
sessions: '../../server/services/gemini-sessions',
|
|
597
|
+
channels: '../../server/services/gemini-channels',
|
|
598
|
+
proxy: '../../server/gemini-proxy-server',
|
|
599
|
+
statistics: '../../server/services/gemini-statistics-service'
|
|
600
|
+
},
|
|
601
|
+
opencode: {
|
|
602
|
+
sessions: '../../server/services/opencode-sessions',
|
|
603
|
+
channels: '../../server/services/opencode-channels',
|
|
604
|
+
proxy: '../../server/opencode-proxy-server',
|
|
605
|
+
statistics: '../../server/services/opencode-statistics-service'
|
|
606
|
+
},
|
|
607
|
+
omp: {
|
|
608
|
+
sessions: '../../server/services/omp-sessions',
|
|
609
|
+
channels: '../../server/services/omp-channels',
|
|
610
|
+
proxy: '../../server/omp-proxy-server',
|
|
611
|
+
statistics: '../../server/services/omp-statistics-service'
|
|
612
|
+
}
|
|
613
|
+
};
|
|
614
|
+
|
|
615
|
+
function createLegacyDriver({ platform, capability, requireImpl = require } = {}) {
|
|
616
|
+
const modulePath = MODULE_PATHS[platform]?.[capability];
|
|
617
|
+
if (!modulePath) {
|
|
618
|
+
return { status: 'unsupported', platform, capability };
|
|
619
|
+
}
|
|
620
|
+
const moduleExports = requireImpl(modulePath);
|
|
621
|
+
return adaptLegacyModule(platform, capability, moduleExports);
|
|
622
|
+
}
|
|
623
|
+
```
|
|
624
|
+
|
|
625
|
+
`adaptLegacyModule` normalizes `getAllChannels()` versus `getChannels().channels`, `getProjects()` signatures, proxy method names, and statistics method names. It must not move or rename the legacy modules.
|
|
626
|
+
|
|
627
|
+
- [ ] **Step 4: Add adapters for current method differences**
|
|
628
|
+
|
|
629
|
+
The adapter table must explicitly map:
|
|
630
|
+
|
|
631
|
+
```text
|
|
632
|
+
claude channels: getAllChannels / createChannel / updateChannel / deleteChannel
|
|
633
|
+
codex channels: getChannels().channels / createChannel / updateChannel / deleteChannel
|
|
634
|
+
Gemini channels: getChannels().channels / createChannel / updateChannel / deleteChannel
|
|
635
|
+
OpenCode channels: getChannels().channels / createChannel / updateChannel / deleteChannel
|
|
636
|
+
OMP channels: getChannels().channels / createChannel / updateChannel / deleteChannel
|
|
637
|
+
```
|
|
638
|
+
|
|
639
|
+
For proxy drivers preserve OMP `preserveManagedMode`, `forceAfterMs`, drain and restoration options. Do not normalize away platform-specific options.
|
|
640
|
+
|
|
641
|
+
- [ ] **Step 5: Extend tests for every existing mock seam**
|
|
642
|
+
|
|
643
|
+
Add tests that stub `src/config/paths.js`, `src/server/services/omp-channels.js`, `src/server/omp-proxy-server.js`, and one non-OMP proxy module by `require.resolve()`. Assert the legacy driver invokes the stub rather than the real implementation. Run: `npx vitest run tests/unit/platforms/legacy-drivers.test.js tests/unit/index.test.js tests/unit/services/omp-proxy-server.test.js`. Expected: PASS.
|
|
644
|
+
|
|
645
|
+
- [ ] **Step 6: Commit the compatibility layer**
|
|
646
|
+
|
|
647
|
+
```bash
|
|
648
|
+
git add src/platforms/drivers/legacy.js tests/unit/platforms/legacy-drivers.test.js
|
|
649
|
+
git commit -m "feat: adapt legacy platform modules to capability drivers"
|
|
650
|
+
```
|
|
651
|
+
|
|
652
|
+
---
|
|
653
|
+
|
|
654
|
+
### Task 4: Migrate Session Indexing and Dashboard Read Aggregation
|
|
655
|
+
|
|
656
|
+
**Files:**
|
|
657
|
+
- Modify: `src/server/services/session-history-adapters/index.js`
|
|
658
|
+
- Modify: `src/server/services/session-history-index.js`
|
|
659
|
+
- Modify: `src/server/services/dashboard-snapshot-worker.js`
|
|
660
|
+
- Test: `tests/unit/services/session-history-index.test.js`
|
|
661
|
+
- Create: `tests/unit/services/dashboard-snapshot-worker.test.js`
|
|
662
|
+
|
|
663
|
+
- [ ] **Step 1: Add a Dashboard runtime-injection test**
|
|
664
|
+
|
|
665
|
+
```js
|
|
666
|
+
const fs = require('fs');
|
|
667
|
+
const os = require('os');
|
|
668
|
+
const path = require('path');
|
|
669
|
+
const { buildPayload } = require('../../../src/server/services/dashboard-snapshot-worker');
|
|
670
|
+
const { createSessionHistoryIndex } = require('../../../src/server/services/session-history-index');
|
|
671
|
+
|
|
672
|
+
const tempDbPath = path.join(
|
|
673
|
+
fs.mkdtempSync(path.join(os.tmpdir(), 'platform-index-test-')),
|
|
674
|
+
'history.sqlite'
|
|
675
|
+
);
|
|
676
|
+
|
|
677
|
+
test('builds a project payload through the injected platform driver', async () => {
|
|
678
|
+
const listProjects = vi.fn(async () => [{ name: 'demo-project', lastUsed: 10 }]);
|
|
679
|
+
const runtime = {
|
|
680
|
+
getDriver: vi.fn((platform, capability) => {
|
|
681
|
+
expect(platform).toBe('demo-cli');
|
|
682
|
+
expect(capability).toBe('projects');
|
|
683
|
+
return { listProjects };
|
|
684
|
+
})
|
|
685
|
+
};
|
|
686
|
+
|
|
687
|
+
const result = await buildPayload({
|
|
688
|
+
kind: 'projects',
|
|
689
|
+
source: 'demo-cli',
|
|
690
|
+
config: {},
|
|
691
|
+
options: {},
|
|
692
|
+
runtime
|
|
693
|
+
});
|
|
694
|
+
|
|
695
|
+
expect(result.projects).toEqual([{ name: 'demo-project', lastUsed: 10 }]);
|
|
696
|
+
expect(listProjects).toHaveBeenCalledWith(expect.objectContaining({ force: false, config: {} }));
|
|
697
|
+
});
|
|
698
|
+
|
|
699
|
+
test('explicit adapterRegistry remains authoritative for tests and workers', async () => {
|
|
700
|
+
const inventory = vi.fn(async () => []);
|
|
701
|
+
const parse = vi.fn();
|
|
702
|
+
const index = createSessionHistoryIndex({
|
|
703
|
+
dbPath: tempDbPath,
|
|
704
|
+
adapterRegistry: { demo: { inventory, parse } },
|
|
705
|
+
workerRunner: async () => {}
|
|
706
|
+
});
|
|
707
|
+
|
|
708
|
+
await index.ensureSourceIndexed('demo', { consistency: 'complete' });
|
|
709
|
+
expect(inventory).toHaveBeenCalledTimes(1);
|
|
710
|
+
expect(parse).not.toHaveBeenCalled();
|
|
711
|
+
});
|
|
712
|
+
```
|
|
713
|
+
|
|
714
|
+
- [ ] **Step 2: Refactor `session-history-index.js` default adapter selection**
|
|
715
|
+
|
|
716
|
+
Keep this precedence:
|
|
717
|
+
|
|
718
|
+
```js
|
|
719
|
+
const adapters = opts.adapterRegistry || getDefaultSessionAdapters();
|
|
720
|
+
```
|
|
721
|
+
|
|
722
|
+
`getDefaultSessionAdapters()` obtains the session drivers from `runtime`; explicit test adapters never load the Registry or native paths. Keep the existing `inventory`, `parse`, stale handling, retry and SQLite schema unchanged.
|
|
723
|
+
|
|
724
|
+
- [ ] **Step 3: Replace Dashboard `switch` with capability dispatch**
|
|
725
|
+
|
|
726
|
+
Implement one dispatcher per capability:
|
|
727
|
+
|
|
728
|
+
```js
|
|
729
|
+
async function buildProjectsPayload(source, config = {}, options = {}, runtime = getPlatformRuntime()) {
|
|
730
|
+
const driver = runtime.getDriver(source, 'projects');
|
|
731
|
+
const projects = await driver.listProjects({ force: options.force === true, config });
|
|
732
|
+
return {
|
|
733
|
+
projects,
|
|
734
|
+
currentProject: projects[0] ? projects[0].name : null
|
|
735
|
+
};
|
|
736
|
+
}
|
|
737
|
+
```
|
|
738
|
+
|
|
739
|
+
Apply the same pattern to counts, today statistics and channels. Existing legacy adapters provide the current five-platform behavior; generic platforms use their configured drivers. Production child-process IPC must continue to send only serializable `kind`, `source`, `config` and `options`; test-only runtime injection is used only when `NODE_ENV === 'test'`.
|
|
740
|
+
|
|
741
|
+
- [ ] **Step 4: Keep `session-history-adapters/index.js` as a stable facade**
|
|
742
|
+
|
|
743
|
+
It must continue exporting `claude`, `codex`, `gemini`, and `omp` objects with `inventory` and `parse`. OpenCode remains excluded from this index until its native database adapter is represented by a driver. Existing tests that stub this module path must still intercept default loading.
|
|
744
|
+
|
|
745
|
+
- [ ] **Step 5: Run focused read-side tests**
|
|
746
|
+
|
|
747
|
+
Run: `npx vitest run tests/unit/services/session-history-index.test.js tests/unit/services/dashboard-snapshot-worker.test.js tests/unit/services/{sessions,codex-sessions,gemini-sessions,opencode-sessions,omp-sessions}.test.js`
|
|
748
|
+
Expected: PASS with the current session normalization and snapshot behavior.
|
|
749
|
+
|
|
750
|
+
- [ ] **Step 6: Commit the read-side migration**
|
|
751
|
+
|
|
752
|
+
```bash
|
|
753
|
+
git add src/server/services/session-history-adapters/index.js src/server/services/session-history-index.js src/server/services/dashboard-snapshot-worker.js tests/unit/services/session-history-index.test.js tests/unit/services/dashboard-snapshot-worker.test.js
|
|
754
|
+
git commit -m "refactor: route read aggregation through platform drivers"
|
|
755
|
+
```
|
|
756
|
+
|
|
757
|
+
---
|
|
758
|
+
|
|
759
|
+
### Task 5: Add the Public Platform Catalog and Generic Route Factory
|
|
760
|
+
|
|
761
|
+
**Files:**
|
|
762
|
+
- Create: `src/server/api/platforms.js`
|
|
763
|
+
- Create: `src/server/api/platform-route-factory.js`
|
|
764
|
+
- Modify: `src/server/index.js`
|
|
765
|
+
- Create: `tests/unit/api/platforms-api.test.js`
|
|
766
|
+
|
|
767
|
+
- [ ] **Step 1: Write catalog and unsupported-capability API tests**
|
|
768
|
+
|
|
769
|
+
```js
|
|
770
|
+
const express = require('express');
|
|
771
|
+
const { once } = require('events');
|
|
772
|
+
const createPlatformRouter = require('../../../src/server/api/platforms');
|
|
773
|
+
|
|
774
|
+
async function requestJson(app, route) {
|
|
775
|
+
const server = app.listen(0);
|
|
776
|
+
await once(server, 'listening');
|
|
777
|
+
const { port } = server.address();
|
|
778
|
+
const response = await fetch(`http://127.0.0.1:${port}${route}`);
|
|
779
|
+
const body = await response.json();
|
|
780
|
+
await new Promise(resolve => server.close(resolve));
|
|
781
|
+
return { status: response.status, body };
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
test('catalog exposes only safe public platform fields', async () => {
|
|
785
|
+
const app = express();
|
|
786
|
+
app.use('/api/platforms', createPlatformRouter({
|
|
787
|
+
registry: {
|
|
788
|
+
list: () => [{
|
|
789
|
+
key: 'demo-cli', label: 'Demo', command: 'demo', iconToken: 'terminal',
|
|
790
|
+
color: '#000', capabilities: { sessions: 'generic-jsonl' },
|
|
791
|
+
paths: { home: '/private/home' }, driverIds: { sessions: 'generic-jsonl' }
|
|
792
|
+
}]
|
|
793
|
+
}
|
|
794
|
+
}));
|
|
795
|
+
|
|
796
|
+
const response = await requestJson(app, '/api/platforms');
|
|
797
|
+
expect(response.status).toBe(200);
|
|
798
|
+
expect(response.body.platforms[0]).toEqual(expect.objectContaining({ key: 'demo-cli', label: 'Demo' }));
|
|
799
|
+
expect(response.body.platforms[0]).not.toHaveProperty('paths');
|
|
800
|
+
expect(response.body.platforms[0]).not.toHaveProperty('driverIds');
|
|
801
|
+
});
|
|
802
|
+
|
|
803
|
+
test('unsupported capability is explicit', async () => {
|
|
804
|
+
const app = express();
|
|
805
|
+
app.use('/api/platforms', createPlatformRouter({
|
|
806
|
+
registry: { list: () => [], resolve: () => ({ key: 'demo-cli' }) },
|
|
807
|
+
runtime: { getDriver: () => ({ status: 'unsupported', platform: 'demo-cli', capability: 'proxy' }) }
|
|
808
|
+
}));
|
|
809
|
+
|
|
810
|
+
const response = await requestJson(app, '/api/platforms/demo-cli/proxy/status');
|
|
811
|
+
expect(response.status).toBe(404);
|
|
812
|
+
expect(response.body.error.code).toBe('unsupported');
|
|
813
|
+
});
|
|
814
|
+
```
|
|
815
|
+
|
|
816
|
+
- [ ] **Step 2: Run the API test and verify the route factory is absent**
|
|
817
|
+
|
|
818
|
+
Run: `npx vitest run tests/unit/api/platforms-api.test.js`
|
|
819
|
+
Expected: FAIL because the catalog router does not exist.
|
|
820
|
+
|
|
821
|
+
- [ ] **Step 3: Implement public projection and generic routes**
|
|
822
|
+
|
|
823
|
+
`src/server/api/platforms.js` must export the factory both as the CommonJS default and as a named property so old-style route loading and focused tests use the same implementation:
|
|
824
|
+
|
|
825
|
+
```js
|
|
826
|
+
module.exports = createPlatformRouter;
|
|
827
|
+
module.exports.createPlatformRouter = createPlatformRouter;
|
|
828
|
+
```
|
|
829
|
+
|
|
830
|
+
`src/server/api/platform-route-factory.js` must contain the shared handler construction; `platforms.js` only supplies the catalog route and binds the factory to the Registry/runtime.
|
|
831
|
+
|
|
832
|
+
The router must expose:
|
|
833
|
+
|
|
834
|
+
```text
|
|
835
|
+
GET /api/platforms
|
|
836
|
+
GET /api/platforms/:platform/projects
|
|
837
|
+
GET /api/platforms/:platform/sessions/:projectName
|
|
838
|
+
GET /api/platforms/:platform/channels
|
|
839
|
+
GET /api/platforms/:platform/proxy/status
|
|
840
|
+
```
|
|
841
|
+
|
|
842
|
+
Route handlers resolve the platform, call the capability driver, and map driver states as follows:
|
|
843
|
+
|
|
844
|
+
```text
|
|
845
|
+
ok -> 200
|
|
846
|
+
unsupported -> 404 { error: { code: 'unsupported', ... } }
|
|
847
|
+
unavailable -> 503
|
|
848
|
+
invalid -> 400
|
|
849
|
+
failed -> 500
|
|
850
|
+
```
|
|
851
|
+
|
|
852
|
+
Use `createPlatformRouter({ registry, runtime })` so tests can inject fakes. Do not import all legacy services at module scope.
|
|
853
|
+
|
|
854
|
+
- [ ] **Step 4: Mount the router without changing old endpoints**
|
|
855
|
+
|
|
856
|
+
In `src/server/index.js`, add the catalog route before static files:
|
|
857
|
+
|
|
858
|
+
```js
|
|
859
|
+
const { getPlatformRegistry, getPlatformRuntime } = require('../platforms/runtime');
|
|
860
|
+
|
|
861
|
+
app.use('/api/platforms', require('./api/platforms')({
|
|
862
|
+
registry: getPlatformRegistry(),
|
|
863
|
+
runtime: getPlatformRuntime()
|
|
864
|
+
}));
|
|
865
|
+
```
|
|
866
|
+
|
|
867
|
+
Keep existing `/api/channels`, `/api/codex/channels`, `/api/gemini/channels`, `/api/opencode/channels` and `/api/omp/channels` mounts unchanged in this task. The new route is the extension path for Manifest-driven platforms.
|
|
868
|
+
|
|
869
|
+
- [ ] **Step 5: Run focused API tests**
|
|
870
|
+
|
|
871
|
+
Run: `npx vitest run tests/unit/api/platforms-api.test.js tests/unit/api/{channels,codex-channels-api,gemini-channels-api,opencode-channels-api,omp-channels-api}.test.js`
|
|
872
|
+
Expected: PASS; old APIs remain available and the new catalog only exposes safe fields.
|
|
873
|
+
|
|
874
|
+
- [ ] **Step 6: Commit the catalog and route factory**
|
|
875
|
+
|
|
876
|
+
```bash
|
|
877
|
+
git add src/server/api/platforms.js src/server/api/platform-route-factory.js src/server/index.js tests/unit/api/platforms-api.test.js
|
|
878
|
+
git commit -m "feat: expose registry-backed platform routes"
|
|
879
|
+
```
|
|
880
|
+
|
|
881
|
+
---
|
|
882
|
+
|
|
883
|
+
|
|
884
|
+
### Task 6: Move Channel, Proxy, and Daemon Aggregation to the Registry
|
|
885
|
+
|
|
886
|
+
**Files:**
|
|
887
|
+
- Modify: `src/commands/proxy-control.js`
|
|
888
|
+
- Modify: `src/commands/toggle-proxy.js`
|
|
889
|
+
- Modify: `src/commands/daemon.js`
|
|
890
|
+
- Modify: `src/server/index.js`
|
|
891
|
+
- Modify: `src/server/services/channel-scheduler.js`
|
|
892
|
+
- Create: `tests/unit/commands/proxy-control.test.js`
|
|
893
|
+
- Test: `tests/unit/commands/daemon.test.js`
|
|
894
|
+
- Test: `tests/unit/services/channel-scheduler.test.js`
|
|
895
|
+
|
|
896
|
+
- [ ] **Step 1: Add a Registry-derived proxy control test**
|
|
897
|
+
|
|
898
|
+
```js
|
|
899
|
+
const { createProxyControl } = require('../../../src/commands/proxy-control');
|
|
900
|
+
|
|
901
|
+
test('proxy labels and endpoint are derived from the resolved Manifest', async () => {
|
|
902
|
+
const httpRequest = vi.fn(async () => ({ data: { success: true, port: 23100 }, status: 200 }));
|
|
903
|
+
const registry = {
|
|
904
|
+
resolve: () => ({
|
|
905
|
+
key: 'demo-cli', label: 'Demo CLI', apiBasePath: '/api/platforms/demo-cli',
|
|
906
|
+
capabilities: { proxy: 'legacy:codex' }
|
|
907
|
+
}),
|
|
908
|
+
list: () => [{ key: 'demo-cli', label: 'Demo CLI' }]
|
|
909
|
+
};
|
|
910
|
+
|
|
911
|
+
const control = createProxyControl({ registry, httpRequest, loadConfig: () => ({ ports: { webUI: 19999 } }) });
|
|
912
|
+
await control.start('demo-cli');
|
|
913
|
+
expect(httpRequest).toHaveBeenCalledWith('POST', '/api/platforms/demo-cli/proxy/start');
|
|
914
|
+
});
|
|
915
|
+
```
|
|
916
|
+
|
|
917
|
+
`proxy-control.js` must export `createProxyControl` in addition to the four existing `handleProxy*` functions. The factory receives `{ registry, httpRequest, loadConfig }` and contains no direct terminal output in its pure methods; the existing handlers remain the CLI output adapter.
|
|
918
|
+
|
|
919
|
+
- [ ] **Step 2: Refactor `proxy-control.js` to use Registry metadata**
|
|
920
|
+
|
|
921
|
+
Remove the fixed five-entry `CHANNEL_CONFIG` as the source of truth. Keep OMP-specific labels in its Manifest or legacy driver metadata:
|
|
922
|
+
|
|
923
|
+
```js
|
|
924
|
+
const platform = registry.resolve(normalizePlatformKey(channel));
|
|
925
|
+
if (!platform) return failUnknownPlatform(channel);
|
|
926
|
+
const basePath = platform.apiBasePath || `/api/platforms/${platform.key}`;
|
|
927
|
+
```
|
|
928
|
+
|
|
929
|
+
Preserve HTTP timeout, error text, status formatting, and `process.exit` behavior for existing commands. Existing tests may inject `require.cache` stubs for the underlying API modules; do not replace those seams with direct network calls.
|
|
930
|
+
|
|
931
|
+
- [ ] **Step 3: Refactor toggle-proxy service lookup**
|
|
932
|
+
|
|
933
|
+
Replace the hard-coded `SETTINGS_MANAGERS` lookup with a driver lookup for `nativeConfig` or `proxy`. Keep these existing service modules as allowlisted legacy drivers:
|
|
934
|
+
|
|
935
|
+
```text
|
|
936
|
+
src/server/services/settings-manager.js
|
|
937
|
+
src/server/services/codex-settings-manager.js
|
|
938
|
+
src/server/services/gemini-settings-manager.js
|
|
939
|
+
src/server/services/opencode-settings-manager.js
|
|
940
|
+
```
|
|
941
|
+
|
|
942
|
+
OMP remains handled by `omp-proxy-server.js` and `omp-settings-manager.js` with its managed-mode semantics.
|
|
943
|
+
|
|
944
|
+
- [ ] **Step 4: Refactor daemon status and automatic recovery**
|
|
945
|
+
|
|
946
|
+
Replace five repeated status blocks in `src/commands/daemon.js` with:
|
|
947
|
+
|
|
948
|
+
```js
|
|
949
|
+
for (const platform of registry.list({ enabledOnly: true })) {
|
|
950
|
+
const proxy = runtime.getDriver(platform.key, 'proxy');
|
|
951
|
+
const status = await Promise.resolve(proxy.status());
|
|
952
|
+
printProxyStatus(platform, status, config);
|
|
953
|
+
}
|
|
954
|
+
```
|
|
955
|
+
|
|
956
|
+
Replace `autoRestoreProxies()` with the same driver iteration. A driver may expose `restoreOnBoot()` for Codex config sync, OpenCode model sync, or OMP managed recovery. Generic drivers without that method are skipped with no error. Failures remain visible with platform and capability context.
|
|
957
|
+
|
|
958
|
+
- [ ] **Step 5: Preserve scheduler source keys**
|
|
959
|
+
|
|
960
|
+
`channel-scheduler.js` must accept any Registry key but retain existing persisted source names. Add a test for `demo-cli` and keep current `claude`, `codex`, `gemini`, `opencode`, and `omp` state behavior. Do not rename storage files in this task.
|
|
961
|
+
|
|
962
|
+
- [ ] **Step 6: Run focused command and scheduler tests**
|
|
963
|
+
|
|
964
|
+
Run: `npx vitest run tests/unit/commands/proxy-control.test.js tests/unit/commands/daemon.test.js tests/unit/commands/toggle-proxy.test.js tests/unit/services/channel-scheduler.test.js tests/unit/index.test.js`
|
|
965
|
+
Expected: PASS, including OMP shutdown preservation and existing CLI proxy output.
|
|
966
|
+
|
|
967
|
+
- [ ] **Step 7: Commit aggregation migration**
|
|
968
|
+
|
|
969
|
+
```bash
|
|
970
|
+
git add src/commands/proxy-control.js src/commands/toggle-proxy.js src/commands/daemon.js src/server/index.js src/server/services/channel-scheduler.js tests/unit/commands/proxy-control.test.js tests/unit/commands/daemon.test.js tests/unit/commands/toggle-proxy.test.js tests/unit/services/channel-scheduler.test.js
|
|
971
|
+
git commit -m "refactor: derive proxy aggregation from platform registry"
|
|
972
|
+
```
|
|
973
|
+
|
|
974
|
+
---
|
|
975
|
+
|
|
976
|
+
### Task 7: Make Config Sync, Registry, Export, and Templates Data-Driven
|
|
977
|
+
|
|
978
|
+
**Files:**
|
|
979
|
+
- Modify: `src/server/services/config-sync-manager.js`
|
|
980
|
+
- Modify: `src/server/services/config-registry-service.js`
|
|
981
|
+
- Modify: `src/server/services/config-export-service.js`
|
|
982
|
+
- Modify: `src/server/services/config-templates-service.js`
|
|
983
|
+
- Test: `tests/unit/services/config-sync-manager.test.js`
|
|
984
|
+
- Test: `tests/unit/services/config-registry-service.test.js`
|
|
985
|
+
- Test: `tests/unit/services/config-export-service.test.js`
|
|
986
|
+
- Test: `tests/unit/services/config-templates-service.test.js`
|
|
987
|
+
|
|
988
|
+
- [ ] **Step 1: Write a generic platform sync test**
|
|
989
|
+
|
|
990
|
+
```js
|
|
991
|
+
const { ConfigSyncManager } = require('../../../src/server/services/config-sync-manager');
|
|
992
|
+
|
|
993
|
+
test('syncAll discovers a generic platform from the Registry', () => {
|
|
994
|
+
const sync = vi.fn(() => ({ status: 'ok', target: '/tmp/demo/skills/review' }));
|
|
995
|
+
const remove = vi.fn(() => ({ status: 'ok' }));
|
|
996
|
+
const manager = new ConfigSyncManager({
|
|
997
|
+
registry: {
|
|
998
|
+
list: () => [{ key: 'demo-cli', capabilities: { resourceSync: 'generic-filesystem' } }]
|
|
999
|
+
},
|
|
1000
|
+
runtime: {
|
|
1001
|
+
getDriver: () => ({ sync, remove })
|
|
1002
|
+
}
|
|
1003
|
+
});
|
|
1004
|
+
|
|
1005
|
+
const result = manager.syncAll('skills', {
|
|
1006
|
+
review: { enabled: true, platforms: { 'demo-cli': true } }
|
|
1007
|
+
});
|
|
1008
|
+
|
|
1009
|
+
expect(sync).toHaveBeenCalledWith('skills', 'review');
|
|
1010
|
+
expect(result.synced).toContainEqual({ type: 'skills', name: 'review', platform: 'demo-cli' });
|
|
1011
|
+
});
|
|
1012
|
+
```
|
|
1013
|
+
|
|
1014
|
+
- [ ] **Step 2: Add dynamic platform assertions to registry and export tests**
|
|
1015
|
+
|
|
1016
|
+
Use a fake Registry containing `demo-cli` and the new pure export helper:
|
|
1017
|
+
|
|
1018
|
+
```js
|
|
1019
|
+
const { getSupportedPlatforms } = require('../../../src/server/services/config-registry-service');
|
|
1020
|
+
const { exportPlatformSnapshots } = require('../../../src/server/services/config-export-service');
|
|
1021
|
+
|
|
1022
|
+
const registry = {
|
|
1023
|
+
list: () => [{ key: 'demo-cli', capabilities: { resourceSync: 'generic-filesystem' } }]
|
|
1024
|
+
};
|
|
1025
|
+
|
|
1026
|
+
expect(getSupportedPlatforms(registry)).toContain('demo-cli');
|
|
1027
|
+
expect(exportPlatformSnapshots({
|
|
1028
|
+
registry,
|
|
1029
|
+
exportByPlatform: () => ({})
|
|
1030
|
+
})).toHaveProperty('demo-cli');
|
|
1031
|
+
```
|
|
1032
|
+
|
|
1033
|
+
Also assert that a platform with `agents: 'unsupported'` is reported as skipped rather than exported as an empty successful config.
|
|
1034
|
+
|
|
1035
|
+
|
|
1036
|
+
- [ ] **Step 3: Add `syncToPlatform` and retain old method wrappers**
|
|
1037
|
+
|
|
1038
|
+
Implement these methods on `ConfigSyncManager`; its constructor accepts `{ registry, runtime }` for tests and defaults both from `src/platforms/runtime.js`:
|
|
1039
|
+
|
|
1040
|
+
```js
|
|
1041
|
+
constructor({ registry = getPlatformRegistry(), runtime = getPlatformRuntime() } = {}) {
|
|
1042
|
+
this.registry = registry;
|
|
1043
|
+
this.runtime = runtime;
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
syncToPlatform(platform, type, name) {
|
|
1047
|
+
const driver = this.runtime.getDriver(platform, 'resourceSync');
|
|
1048
|
+
return driver.sync(type, name);
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
removeFromPlatform(platform, type, name) {
|
|
1052
|
+
const driver = this.runtime.getDriver(platform, 'resourceSync');
|
|
1053
|
+
return driver.remove(type, name);
|
|
1054
|
+
}
|
|
1055
|
+
```
|
|
1056
|
+
|
|
1057
|
+
Keep `syncToClaude`, `syncToCodex`, `syncToGemini`, `syncToOpenCode`, `syncToOmp` and matching remove methods as wrappers that call the same runtime. This preserves current service exports and tests while replacing `syncAll` platform enumeration with `registry.list()`.
|
|
1058
|
+
|
|
1059
|
+
- [ ] **Step 4: Derive config registry support from Manifest capabilities**
|
|
1060
|
+
|
|
1061
|
+
Import `getPlatformRegistry` from `src/platforms/runtime.js` and add:
|
|
1062
|
+
|
|
1063
|
+
```js
|
|
1064
|
+
function getSupportedPlatforms(registry = getPlatformRegistry()) {
|
|
1065
|
+
return registry.list({ enabledOnly: true }).map(platform => platform.key);
|
|
1066
|
+
}
|
|
1067
|
+
```
|
|
1068
|
+
|
|
1069
|
+
Replace the static `SUPPORTED_PLATFORMS` source with `getSupportedPlatforms(registry)` while retaining the exported `SUPPORTED_PLATFORMS` name for existing tests. Build `PLATFORM_SUPPORT` from `resourceSync` capability and resource type declarations. Existing OMP false support for agents must remain false, and export `getSupportedPlatforms`.
|
|
1070
|
+
|
|
1071
|
+
- [ ] **Step 5: Refactor export and template maps**
|
|
1072
|
+
|
|
1073
|
+
Add and export this pure helper in `config-export-service.js` so the platform map can be tested without reading the real home directory:
|
|
1074
|
+
|
|
1075
|
+
```js
|
|
1076
|
+
function exportPlatformSnapshots({ registry, exportByPlatform }) {
|
|
1077
|
+
return Object.fromEntries(
|
|
1078
|
+
registry.list({ enabledOnly: true }).map(platform => [
|
|
1079
|
+
platform.key,
|
|
1080
|
+
exportByPlatform(platform.key)
|
|
1081
|
+
])
|
|
1082
|
+
);
|
|
1083
|
+
}
|
|
1084
|
+
```
|
|
1085
|
+
|
|
1086
|
+
`config-export-service.js` must use `nativeConfig` drivers for native snapshots and `resourceSync` drivers for managed resources. Keep existing special handling for Claude hooks, Codex TOML, OpenCode package configuration and OMP package/extension records.
|
|
1087
|
+
|
|
1088
|
+
`config-templates-service.js` must derive AI config file names from Manifest fields such as `promptFile` and use an explicit `unsupported` result when no file mapping exists. Preserve current fallback from Codex to OpenCode and OMP command-template semantics.
|
|
1089
|
+
|
|
1090
|
+
- [ ] **Step 6: Run focused service tests**
|
|
1091
|
+
|
|
1092
|
+
Run: `npx vitest run tests/unit/services/config-sync-manager.test.js tests/unit/services/config-registry-service.test.js tests/unit/services/config-export-service.test.js tests/unit/services/config-templates-service.test.js`
|
|
1093
|
+
Expected: PASS, including old platform snapshots, OMP resource skips, and new `demo-cli` discovery.
|
|
1094
|
+
|
|
1095
|
+
- [ ] **Step 7: Commit data-driven config services**
|
|
1096
|
+
|
|
1097
|
+
```bash
|
|
1098
|
+
git add src/server/services/config-sync-manager.js src/server/services/config-registry-service.js src/server/services/config-export-service.js src/server/services/config-templates-service.js tests/unit/services/config-sync-manager.test.js tests/unit/services/config-registry-service.test.js tests/unit/services/config-export-service.test.js tests/unit/services/config-templates-service.test.js
|
|
1099
|
+
git commit -m "refactor: drive config services from platform capabilities"
|
|
1100
|
+
```
|
|
1101
|
+
|
|
1102
|
+
---
|
|
1103
|
+
|
|
1104
|
+
### Task 8: Refactor CLI Routing and Platform Type Selection
|
|
1105
|
+
|
|
1106
|
+
**Files:**
|
|
1107
|
+
- Create: `src/commands/platform-command-registry.js`
|
|
1108
|
+
- Modify: `src/index.js`
|
|
1109
|
+
- Modify: `src/commands/cli-type.js`
|
|
1110
|
+
- Modify: `src/commands/logs.js`
|
|
1111
|
+
- Modify: `src/commands/stats.js`
|
|
1112
|
+
- Modify: `src/commands/port-config.js`
|
|
1113
|
+
- Create: `tests/unit/commands/platform-command-registry.test.js`
|
|
1114
|
+
- Create: `tests/unit/commands/cli-type.test.js`
|
|
1115
|
+
- Test: `tests/unit/index.test.js`
|
|
1116
|
+
- Test: `tests/unit/commands/logs.test.js`
|
|
1117
|
+
- Test: `tests/unit/commands/stats.test.js`
|
|
1118
|
+
- Test: `tests/unit/commands/port-config.test.js`
|
|
1119
|
+
|
|
1120
|
+
- [ ] **Step 1: Write command registry tests**
|
|
1121
|
+
|
|
1122
|
+
```js
|
|
1123
|
+
const { createPlatformCommandRegistry } = require('../../../src/commands/platform-command-registry');
|
|
1124
|
+
|
|
1125
|
+
test('adds a configured platform to proxy, logs, stats, and port command options', () => {
|
|
1126
|
+
const registry = createPlatformCommandRegistry({
|
|
1127
|
+
platforms: [{
|
|
1128
|
+
key: 'demo-cli', label: 'Demo CLI', command: 'demo',
|
|
1129
|
+
logFile: 'demo-proxy.log', portKey: 'demoProxy', capabilities: {
|
|
1130
|
+
proxy: 'unsupported', statistics: 'unsupported'
|
|
1131
|
+
}
|
|
1132
|
+
}]
|
|
1133
|
+
});
|
|
1134
|
+
|
|
1135
|
+
expect(registry.resolve('demo-cli').label).toBe('Demo CLI');
|
|
1136
|
+
expect(registry.logTypes()).toContain('demo-cli');
|
|
1137
|
+
expect(registry.portKeys()).toContain('demoProxy');
|
|
1138
|
+
});
|
|
1139
|
+
```
|
|
1140
|
+
|
|
1141
|
+
- [ ] **Step 2: Implement a pure command registry**
|
|
1142
|
+
|
|
1143
|
+
The registry exposes `resolve`, `platformKeys`, `logTypes`, `portKeys`, and `helpEntries`. It receives the Registry as a dependency and returns no terminal output. Existing aliases such as `omp` remain exact keys.
|
|
1144
|
+
|
|
1145
|
+
- [ ] **Step 3: Replace platform arrays in CLI commands**
|
|
1146
|
+
|
|
1147
|
+
Use `registry.list()` in `logs.js`, `stats.js`, `port-config.js`, and `cli-type.js`. Preserve:
|
|
1148
|
+
|
|
1149
|
+
- `omp` log alias to UI/server logs;
|
|
1150
|
+
- `omp` dynamic gateway labels;
|
|
1151
|
+
- existing default ports;
|
|
1152
|
+
- existing stats endpoint paths for five built-ins;
|
|
1153
|
+
- existing CLI type colors and prompts.
|
|
1154
|
+
|
|
1155
|
+
An unsupported capability is excluded from the relevant option list; an unknown explicit key still produces the current error path.
|
|
1156
|
+
|
|
1157
|
+
- [ ] **Step 4: Extract the platform branch from `src/index.js`**
|
|
1158
|
+
|
|
1159
|
+
Add a command table for non-platform commands and a Registry-derived platform route:
|
|
1160
|
+
|
|
1161
|
+
```js
|
|
1162
|
+
const commandHandlers = {
|
|
1163
|
+
start: () => handleStart(),
|
|
1164
|
+
stop: () => handleStop(),
|
|
1165
|
+
restart: () => handleRestart(),
|
|
1166
|
+
status: () => handleStatus()
|
|
1167
|
+
};
|
|
1168
|
+
|
|
1169
|
+
const platform = registry.resolve(normalizePlatformKey(args[0]));
|
|
1170
|
+
if (platform) {
|
|
1171
|
+
return dispatchPlatformCommand(platform, args.slice(1), runtime);
|
|
1172
|
+
}
|
|
1173
|
+
```
|
|
1174
|
+
|
|
1175
|
+
Do not remove compatibility branches for `daemon`, `proxy`, `security`, `plugin`, `ui`, or `reset`. Preserve `finishCli`, OMP exit cleanup, uncaught exception handling, and `_test.stopOwnedOmpGatewayBeforeExit`.
|
|
1176
|
+
|
|
1177
|
+
- [ ] **Step 5: Make help output Registry-derived**
|
|
1178
|
+
|
|
1179
|
+
Keep static descriptions for global commands, but render platform commands from `helpEntries()`. The output for existing five platforms must remain semantically identical; adding `demo-cli` must not require editing `showHelp()`.
|
|
1180
|
+
|
|
1181
|
+
- [ ] **Step 6: Run focused CLI tests and smoke help**
|
|
1182
|
+
|
|
1183
|
+
Run: `npx vitest run tests/unit/index.test.js tests/unit/commands/cli-type.test.js tests/unit/commands/logs.test.js tests/unit/commands/stats.test.js tests/unit/commands/port-config.test.js`
|
|
1184
|
+
Expected: PASS.
|
|
1185
|
+
|
|
1186
|
+
Run: `node bin/ctx.js --help`
|
|
1187
|
+
Expected: existing global command sections and all enabled Registry platform entries are printed, followed by exit code 0.
|
|
1188
|
+
|
|
1189
|
+
- [ ] **Step 7: Commit CLI routing**
|
|
1190
|
+
|
|
1191
|
+
```bash
|
|
1192
|
+
git add src/index.js src/commands/platform-command-registry.js src/commands/cli-type.js src/commands/logs.js src/commands/stats.js src/commands/port-config.js tests/unit/index.test.js tests/unit/commands/platform-command-registry.test.js tests/unit/commands/cli-type.test.js tests/unit/commands/logs.test.js tests/unit/commands/stats.test.js tests/unit/commands/port-config.test.js
|
|
1193
|
+
git commit -m "refactor: derive CLI platform commands from registry"
|
|
1194
|
+
```
|
|
1195
|
+
|
|
1196
|
+
---
|
|
1197
|
+
|
|
1198
|
+
### Task 9: Migrate User Platform Config and Preserve Legacy UI Config
|
|
1199
|
+
|
|
1200
|
+
**Files:**
|
|
1201
|
+
- Modify: `src/server/services/ui-config.js`
|
|
1202
|
+
- Modify: `src/shared/platforms.js`
|
|
1203
|
+
- Modify: `src/platforms/registry.js`
|
|
1204
|
+
- Test: `tests/unit/services/ui-config.test.js`
|
|
1205
|
+
- Test: `tests/unit/shared/platforms.test.js`
|
|
1206
|
+
- Test: `tests/unit/platforms/registry.test.js`
|
|
1207
|
+
|
|
1208
|
+
- [ ] **Step 1: Write migration compatibility tests**
|
|
1209
|
+
|
|
1210
|
+
```js
|
|
1211
|
+
const { createPlatformRegistry } = require('../../../src/platforms/registry');
|
|
1212
|
+
test('legacy customCliPlatforms remain readable and become Registry metadata', () => {
|
|
1213
|
+
const registry = createPlatformRegistry({
|
|
1214
|
+
builtIns: [{ key: 'claude', label: 'Claude', command: 'claude', capabilities: {} }],
|
|
1215
|
+
legacyUiConfig: {
|
|
1216
|
+
customCliPlatforms: [{ key: 'demo-cli', name: 'Demo', command: 'demo', enabled: true }]
|
|
1217
|
+
},
|
|
1218
|
+
userFile: { platforms: [] }
|
|
1219
|
+
});
|
|
1220
|
+
|
|
1221
|
+
expect(registry.resolve('demo-cli')).toEqual(expect.objectContaining({
|
|
1222
|
+
key: 'demo-cli', label: 'Demo', custom: true
|
|
1223
|
+
}));
|
|
1224
|
+
});
|
|
1225
|
+
|
|
1226
|
+
test('user definitions cannot override built-in behavior or driver IDs', () => {
|
|
1227
|
+
const registry = createPlatformRegistry({
|
|
1228
|
+
builtIns: [{ key: 'claude', label: 'Claude', command: 'claude', capabilities: {} }],
|
|
1229
|
+
userFile: {
|
|
1230
|
+
platforms: [{ key: 'claude', label: 'Fake', command: 'fake', capabilities: { proxy: 'user-code' } }]
|
|
1231
|
+
}
|
|
1232
|
+
});
|
|
1233
|
+
|
|
1234
|
+
expect(registry.resolve('claude').label).toBe('Claude');
|
|
1235
|
+
expect(registry.diagnostics()[0].reason).toMatch(/built-in|driver/i);
|
|
1236
|
+
});
|
|
1237
|
+
```
|
|
1238
|
+
|
|
1239
|
+
- [ ] **Step 2: Add `platforms.json` loading without changing current UI file shape**
|
|
1240
|
+
|
|
1241
|
+
`ui-config.js` continues returning `customCliPlatforms` for old frontend callers during migration. The Registry reads `PATHS.platforms` first, then imports valid legacy custom metadata. Saving UI config must not erase legacy fields before the frontend migration completes.
|
|
1242
|
+
|
|
1243
|
+
- [ ] **Step 3: Normalize custom metadata once**
|
|
1244
|
+
|
|
1245
|
+
Move key normalization, duplicate rejection, built-in collision rejection, and enabled filtering to one shared normalizer. Keep `normalizePlatformKey`, `normalizeCustomCliPlatform`, `normalizeCustomCliPlatforms`, and `normalizeHomeCliColumns` exports from `src/shared/platforms.js`; change their data source, not their observable behavior.
|
|
1246
|
+
|
|
1247
|
+
- [ ] **Step 4: Add safe user Manifest fields**
|
|
1248
|
+
|
|
1249
|
+
Allow user config to set only:
|
|
1250
|
+
|
|
1251
|
+
```text
|
|
1252
|
+
key, label, title, command, iconToken, color, defaultVisible,
|
|
1253
|
+
paths, pathResolverId from allowlist, capabilities from allowlist,
|
|
1254
|
+
resource mappings and session field mappings
|
|
1255
|
+
```
|
|
1256
|
+
|
|
1257
|
+
Reject `modulePath`, `require`, function-valued fields, shell commands, and driver IDs not in `DRIVER_IDS`. Validate all filesystem roots before Registry insertion.
|
|
1258
|
+
|
|
1259
|
+
- [ ] **Step 5: Run focused migration tests**
|
|
1260
|
+
|
|
1261
|
+
Run: `npx vitest run tests/unit/services/ui-config.test.js tests/unit/shared/platforms.test.js tests/unit/platforms/registry.test.js`
|
|
1262
|
+
Expected: PASS, including existing OMP key normalization and custom home column behavior.
|
|
1263
|
+
|
|
1264
|
+
- [ ] **Step 6: Commit configuration migration**
|
|
1265
|
+
|
|
1266
|
+
```bash
|
|
1267
|
+
git add src/server/services/ui-config.js src/shared/platforms.js src/platforms/registry.js tests/unit/services/ui-config.test.js tests/unit/shared/platforms.test.js tests/unit/platforms/registry.test.js
|
|
1268
|
+
git commit -m "feat: load user CLI platform manifests safely"
|
|
1269
|
+
```
|
|
1270
|
+
|
|
1271
|
+
---
|
|
1272
|
+
|
|
1273
|
+
### Task 10: Remove Frontend Platform Duplication and Add Generic Channel UI
|
|
1274
|
+
|
|
1275
|
+
**Files:**
|
|
1276
|
+
- Create: `src/web/src/api/platforms.js`
|
|
1277
|
+
- Create: `src/web/src/stores/platforms.js`
|
|
1278
|
+
- Create: `src/web/src/config/iconTokens.js`
|
|
1279
|
+
- Create: `src/web/src/components/channel/commonChannelSchema.js`
|
|
1280
|
+
- Modify: `src/web/vitest.config.js`
|
|
1281
|
+
- Modify: `src/web/src/config/platforms.js`
|
|
1282
|
+
- Modify: `src/web/src/api/channels.js`
|
|
1283
|
+
- Modify: `src/web/src/composables/useUIConfig.js`
|
|
1284
|
+
- Modify: `src/web/src/views/Home.vue`
|
|
1285
|
+
- Modify: `src/web/src/components/SettingsDrawer.vue`
|
|
1286
|
+
- Modify: `src/web/src/components/channel/channelPanelFactories.js`
|
|
1287
|
+
- Modify: `src/web/src/components/AgentsPanel.vue`
|
|
1288
|
+
- Modify: `src/web/src/components/CommandsPanel.vue`
|
|
1289
|
+
- Modify: `src/web/src/components/PluginsPanel.vue`
|
|
1290
|
+
- Modify: `src/web/src/components/SkillsPanel.vue`
|
|
1291
|
+
- Create: `src/web/src/config/__tests__/platforms.test.js`
|
|
1292
|
+
- Create: `src/web/src/components/channel/__tests__/commonChannelSchema.test.js`
|
|
1293
|
+
|
|
1294
|
+
- [ ] **Step 1: Add frontend API and store tests**
|
|
1295
|
+
|
|
1296
|
+
```js
|
|
1297
|
+
import { beforeEach, expect, test } from 'vitest'
|
|
1298
|
+
import { setActivePinia, createPinia } from 'pinia'
|
|
1299
|
+
import { usePlatformStore } from '../../stores/platforms'
|
|
1300
|
+
|
|
1301
|
+
beforeEach(() => setActivePinia(createPinia()))
|
|
1302
|
+
|
|
1303
|
+
test('loads public platform metadata from the server', async () => {
|
|
1304
|
+
const store = usePlatformStore()
|
|
1305
|
+
store.fetchPlatforms = async () => {
|
|
1306
|
+
store.platforms = [{ key: 'demo-cli', label: 'Demo CLI', iconToken: 'terminal', capabilities: {} }]
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
await store.load()
|
|
1310
|
+
expect(store.get('demo-cli').label).toBe('Demo CLI')
|
|
1311
|
+
})
|
|
1312
|
+
```
|
|
1313
|
+
|
|
1314
|
+
- [ ] **Step 2: Implement `api/platforms.js` and `stores/platforms.js`**
|
|
1315
|
+
|
|
1316
|
+
The client calls `GET /platforms`, stores only public fields, and exposes `get(key)`, `all`, `enabled`, and `hasCapability(key, capability)`. Loading must be idempotent and retain the last successful catalog on a transient failure.
|
|
1317
|
+
|
|
1318
|
+
- [ ] **Step 3: Reduce `config/platforms.js` to normalization and icon mapping**
|
|
1319
|
+
|
|
1320
|
+
Delete the duplicated complete built-in platform array. Retain:
|
|
1321
|
+
|
|
1322
|
+
```js
|
|
1323
|
+
export function normalizePublicPlatform(platform) {
|
|
1324
|
+
return {
|
|
1325
|
+
key: String(platform?.key || '').trim().toLowerCase(),
|
|
1326
|
+
label: String(platform?.label || platform?.title || platform?.key || '').trim(),
|
|
1327
|
+
title: String(platform?.title || platform?.label || platform?.key || '').trim(),
|
|
1328
|
+
command: String(platform?.command || platform?.key || '').trim(),
|
|
1329
|
+
iconToken: String(platform?.iconToken || 'terminal').trim(),
|
|
1330
|
+
color: String(platform?.color || '#64748b').trim(),
|
|
1331
|
+
defaultVisible: platform?.defaultVisible !== false,
|
|
1332
|
+
capabilities: { ...(platform?.capabilities || {}) }
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1336
|
+
export function normalizePublicPlatforms(platforms = []) {
|
|
1337
|
+
const seen = new Set()
|
|
1338
|
+
return platforms.map(normalizePublicPlatform).filter(platform => {
|
|
1339
|
+
if (!platform.key || seen.has(platform.key)) return false
|
|
1340
|
+
seen.add(platform.key)
|
|
1341
|
+
return true
|
|
1342
|
+
})
|
|
1343
|
+
}
|
|
1344
|
+
|
|
1345
|
+
export function getPlatformConfig(key, platforms = []) {
|
|
1346
|
+
return normalizePublicPlatforms(platforms).find(platform => platform.key === key) || null
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1349
|
+
export function normalizeHomeCliColumns(input = [], platforms = []) {
|
|
1350
|
+
const allowed = new Set(normalizePublicPlatforms(platforms).map(platform => platform.key))
|
|
1351
|
+
const result = []
|
|
1352
|
+
for (const value of input) {
|
|
1353
|
+
const key = String(value || '').trim().toLowerCase()
|
|
1354
|
+
if (key && allowed.has(key) && !result.includes(key)) result.push(key)
|
|
1355
|
+
}
|
|
1356
|
+
return result.slice(0, 4)
|
|
1357
|
+
}
|
|
1358
|
+
|
|
1359
|
+
export function buildCliPlatformOptions(platforms = []) {
|
|
1360
|
+
return normalizePublicPlatforms(platforms).map(platform => ({
|
|
1361
|
+
label: platform.label || platform.title || platform.key,
|
|
1362
|
+
value: platform.key
|
|
1363
|
+
}))
|
|
1364
|
+
}
|
|
1365
|
+
```
|
|
1366
|
+
|
|
1367
|
+
`iconTokens.js` contains the only static map from strings to imported Ionicons. Unknown tokens use `TerminalOutline`.
|
|
1368
|
+
|
|
1369
|
+
- [ ] **Step 4: Extract common channel schema**
|
|
1370
|
+
|
|
1371
|
+
Move schedule, enable/disable, health, provider URL and generic auth fields out of `channelPanelFactories.js`:
|
|
1372
|
+
|
|
1373
|
+
```js
|
|
1374
|
+
export const commonChannelSchema = {
|
|
1375
|
+
endpoint: [
|
|
1376
|
+
{ key: 'baseUrl', label: 'Base URL', type: 'text', required: true },
|
|
1377
|
+
{ key: 'websiteUrl', label: '官网链接', type: 'text', required: false }
|
|
1378
|
+
],
|
|
1379
|
+
auth: [
|
|
1380
|
+
{ key: 'authMode', label: '认证方式', type: 'select', options: ['api_key', 'none'] },
|
|
1381
|
+
{ key: 'apiKey', label: 'API Key', type: 'password', required: false }
|
|
1382
|
+
],
|
|
1383
|
+
schedule: [
|
|
1384
|
+
{ key: 'maxConcurrency', label: '最大并发', type: 'number', required: false },
|
|
1385
|
+
{ key: 'weight', label: '调度权重', type: 'number', min: 1, max: 100 },
|
|
1386
|
+
{ key: 'enabled', label: '渠道状态', type: 'switch', default: true }
|
|
1387
|
+
]
|
|
1388
|
+
}
|
|
1389
|
+
|
|
1390
|
+
export function createGenericChannelPanel(manifest, api) {
|
|
1391
|
+
return {
|
|
1392
|
+
type: manifest.key,
|
|
1393
|
+
displayName: manifest.label,
|
|
1394
|
+
formSections: [
|
|
1395
|
+
{ title: '基本信息', fields: commonChannelSchema.endpoint },
|
|
1396
|
+
{ title: '认证', fields: commonChannelSchema.auth },
|
|
1397
|
+
{ title: '调度配置', fields: commonChannelSchema.schedule }
|
|
1398
|
+
],
|
|
1399
|
+
api
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1402
|
+
```
|
|
1403
|
+
|
|
1404
|
+
Keep Claude/Codex/Gemini/OpenCode/OMP-specific field definitions and serializers in their existing factories. Generic platforms use the generic factory only when their channel capability is a generic driver.
|
|
1405
|
+
|
|
1406
|
+
- [ ] **Step 5: Add generic channel API functions while retaining old exports**
|
|
1407
|
+
|
|
1408
|
+
Add:
|
|
1409
|
+
|
|
1410
|
+
```js
|
|
1411
|
+
export function getPlatformChannels(platform) {
|
|
1412
|
+
return client.get(`/platforms/${platform}/channels`).then(response => response.data)
|
|
1413
|
+
}
|
|
1414
|
+
|
|
1415
|
+
export function createPlatformChannel(platform, payload) {
|
|
1416
|
+
return client.post(`/platforms/${platform}/channels`, payload).then(response => response.data)
|
|
1417
|
+
}
|
|
1418
|
+
```
|
|
1419
|
+
|
|
1420
|
+
Keep `getCodexChannels`, `createCodexChannel`, and all existing named functions as wrappers. Existing specialized panels must not be forced through generic serialization.
|
|
1421
|
+
|
|
1422
|
+
- [ ] **Step 6: Migrate Home, Settings, and resource panels to the store**
|
|
1423
|
+
|
|
1424
|
+
Replace module-scope `BUILT_IN_CLI_PLATFORMS` filtering with `platformStore.enabled` and `hasCapability`. Preserve current `customCliPlatforms` loading behavior during the backend migration, then use the Registry catalog as the source for display and ordering.
|
|
1425
|
+
|
|
1426
|
+
- [ ] **Step 7: Add frontend tests and build**
|
|
1427
|
+
|
|
1428
|
+
Test generic option generation, unknown icon fallback, capability filtering, duplicate home columns, and common schema validation. Update `src/web/vitest.config.js` to include `src/**/__tests__/**/*.test.js`, then run:
|
|
1429
|
+
|
|
1430
|
+
```bash
|
|
1431
|
+
cd src/web && npm run test:unit -- src/config/__tests__/platforms.test.js src/components/channel/__tests__/commonChannelSchema.test.js
|
|
1432
|
+
npm run build
|
|
1433
|
+
```
|
|
1434
|
+
|
|
1435
|
+
Expected: PASS and a successful Vite production build.
|
|
1436
|
+
|
|
1437
|
+
- [ ] **Step 8: Commit frontend migration**
|
|
1438
|
+
|
|
1439
|
+
```bash
|
|
1440
|
+
git add src/web/vitest.config.js src/web/src/api/platforms.js src/web/src/stores/platforms.js src/web/src/config src/web/src/api/channels.js src/web/src/composables/useUIConfig.js src/web/src/views/Home.vue src/web/src/components/SettingsDrawer.vue src/web/src/components/channel src/web/src/components/AgentsPanel.vue src/web/src/components/CommandsPanel.vue src/web/src/components/PluginsPanel.vue src/web/src/components/SkillsPanel.vue
|
|
1441
|
+
git commit -m "refactor: consume registry-backed platform metadata in web UI"
|
|
1442
|
+
```
|
|
1443
|
+
|
|
1444
|
+
---
|
|
1445
|
+
|
|
1446
|
+
### Task 11: Complete Contract Tests, Documentation, and Regression Verification
|
|
1447
|
+
|
|
1448
|
+
**Files:**
|
|
1449
|
+
- Modify: `README.md`
|
|
1450
|
+
- Modify: `CHANGELOG.md`
|
|
1451
|
+
- Test: all affected unit/API tests
|
|
1452
|
+
- Smoke: `node bin/ctx.js --help`, service status, and Web UI build
|
|
1453
|
+
|
|
1454
|
+
- [ ] **Step 1: Add the config-only `demo-cli` contract test**
|
|
1455
|
+
|
|
1456
|
+
Register this platform only through a test Manifest and existing generic drivers:
|
|
1457
|
+
|
|
1458
|
+
```js
|
|
1459
|
+
const fs = require('fs');
|
|
1460
|
+
const os = require('os');
|
|
1461
|
+
const path = require('path');
|
|
1462
|
+
const tempHome = fs.mkdtempSync(path.join(os.tmpdir(), 'platform-contract-home-'));
|
|
1463
|
+
const demoManifest = {
|
|
1464
|
+
key: 'demo-cli',
|
|
1465
|
+
label: 'Demo CLI',
|
|
1466
|
+
command: 'demo',
|
|
1467
|
+
paths: { home: tempHome, sessions: '{home}/sessions' },
|
|
1468
|
+
capabilities: {
|
|
1469
|
+
sessions: 'generic-jsonl',
|
|
1470
|
+
resourceSync: 'generic-filesystem',
|
|
1471
|
+
proxy: 'unsupported',
|
|
1472
|
+
statistics: 'unsupported'
|
|
1473
|
+
}
|
|
1474
|
+
};
|
|
1475
|
+
```
|
|
1476
|
+
|
|
1477
|
+
Assert that Registry listing, path resolution, generic route discovery, session indexing and resource sync work without editing Dashboard, CLI router, or platform-specific source files.
|
|
1478
|
+
|
|
1479
|
+
- [ ] **Step 2: Add mock path compatibility assertions to existing suites**
|
|
1480
|
+
|
|
1481
|
+
Run and retain tests covering:
|
|
1482
|
+
|
|
1483
|
+
```text
|
|
1484
|
+
tests/unit/index.test.js
|
|
1485
|
+
tests/unit/api/channels.test.js
|
|
1486
|
+
tests/unit/api/codex-proxy-api.test.js
|
|
1487
|
+
tests/unit/api/gemini-proxy-api.test.js
|
|
1488
|
+
tests/unit/api/opencode-proxy-api.test.js
|
|
1489
|
+
tests/unit/api/proxy-api.test.js
|
|
1490
|
+
tests/unit/services/session-history-index.test.js
|
|
1491
|
+
tests/unit/services/omp-proxy-server.test.js
|
|
1492
|
+
```
|
|
1493
|
+
|
|
1494
|
+
At least one test for every preserved path must install a `require.cache` stub before loading the module under test. Do not replace these with source-text assertions.
|
|
1495
|
+
|
|
1496
|
+
- [ ] **Step 3: Add explicit error-state tests**
|
|
1497
|
+
|
|
1498
|
+
Cover:
|
|
1499
|
+
|
|
1500
|
+
```text
|
|
1501
|
+
unknown explicit platform -> invalid / 400 or existing CLI error
|
|
1502
|
+
unsupported capability -> unsupported / 404
|
|
1503
|
+
missing executable/path -> unavailable / 503 or diagnostic output
|
|
1504
|
+
malformed Manifest -> invalid with field path
|
|
1505
|
+
runtime driver exception -> failed with platform/capability/operation
|
|
1506
|
+
```
|
|
1507
|
+
|
|
1508
|
+
Assert that none of these cases returns `{ success: true }` with an empty value.
|
|
1509
|
+
|
|
1510
|
+
- [ ] **Step 4: Update README and CHANGELOG**
|
|
1511
|
+
|
|
1512
|
+
Document:
|
|
1513
|
+
|
|
1514
|
+
- generic CLI support requires a valid Manifest and an allowlisted generic driver;
|
|
1515
|
+
- special CLI protocols require a capability driver;
|
|
1516
|
+
- user Manifest location `~/.cc-tool/config/platforms.json`;
|
|
1517
|
+
- arbitrary Node module loading is prohibited;
|
|
1518
|
+
- existing CLI/API compatibility and mock path behavior are intentional.
|
|
1519
|
+
|
|
1520
|
+
Do not claim that every arbitrary CLI gains full parity from configuration alone.
|
|
1521
|
+
|
|
1522
|
+
- [ ] **Step 5: Run the focused suites**
|
|
1523
|
+
|
|
1524
|
+
Run:
|
|
1525
|
+
|
|
1526
|
+
```bash
|
|
1527
|
+
npx vitest run tests/unit/platforms tests/unit/services/session-history-index.test.js tests/unit/services/dashboard-snapshot-worker.test.js tests/unit/api/platforms-api.test.js
|
|
1528
|
+
npm run test:unit
|
|
1529
|
+
npm run test:basic
|
|
1530
|
+
npm run test:api
|
|
1531
|
+
npm run test:codex-agents
|
|
1532
|
+
npm run test:skills
|
|
1533
|
+
npm run test:plugins-market
|
|
1534
|
+
npm run test:windows
|
|
1535
|
+
```
|
|
1536
|
+
|
|
1537
|
+
Expected: all commands exit 0. If a pre-existing user-modified skill test fails, record the exact failure and do not revert those files.
|
|
1538
|
+
|
|
1539
|
+
- [ ] **Step 6: Run runtime smoke checks**
|
|
1540
|
+
|
|
1541
|
+
Run:
|
|
1542
|
+
|
|
1543
|
+
```bash
|
|
1544
|
+
node bin/ctx.js --version
|
|
1545
|
+
node bin/ctx.js --help
|
|
1546
|
+
npm run build:web
|
|
1547
|
+
```
|
|
1548
|
+
|
|
1549
|
+
Expected: version output, Registry-derived help output, and a successful Web UI build. If a local service is available, start it through the existing CLI and verify `GET /api/platforms` returns the five built-ins plus any configured user platform; stop it using the existing command.
|
|
1550
|
+
|
|
1551
|
+
- [ ] **Step 7: Inspect final diff and commit only owned files**
|
|
1552
|
+
|
|
1553
|
+
Run: `git diff --check`
|
|
1554
|
+
|
|
1555
|
+
Then inspect `git status --short` and verify unrelated skill changes remain untouched. Commit only the documentation and contract tests:
|
|
1556
|
+
|
|
1557
|
+
```bash
|
|
1558
|
+
git add README.md CHANGELOG.md tests/unit/platforms tests/unit/services/dashboard-snapshot-worker.test.js tests/unit/api/platforms-api.test.js
|
|
1559
|
+
git commit -m "docs: document configurable CLI platform extensions"
|
|
1560
|
+
```
|
|
1561
|
+
|
|
1562
|
+
- [ ] **Step 8: Final acceptance review**
|
|
1563
|
+
|
|
1564
|
+
Confirm each approved-spec requirement has evidence:
|
|
1565
|
+
|
|
1566
|
+
```text
|
|
1567
|
+
Manifest + Registry present
|
|
1568
|
+
capability drivers isolated
|
|
1569
|
+
config-only generic demo works
|
|
1570
|
+
legacy platform facades remain loadable
|
|
1571
|
+
require.resolve + require.cache mocks still intercept
|
|
1572
|
+
old CLI/API paths unchanged
|
|
1573
|
+
OMP managed mode and exit recovery unchanged
|
|
1574
|
+
unsupported and failed states are distinguishable
|
|
1575
|
+
frontend has no second complete built-in platform catalog
|
|
1576
|
+
```
|
|
1577
|
+
|
|
1578
|
+
Only after these checks pass is the refactor complete.
|