coding-tool-x 3.8.4 → 3.8.6
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-Db9wAyaZ.js +1 -0
- package/dist/web/assets/AgentsDrawer-Pw2k-ZAa.css +1 -0
- package/dist/web/assets/Analytics-C4gMzv7g.js +1 -0
- package/dist/web/assets/Analytics-CyHhR126.css +1 -0
- package/dist/web/assets/AssetPathField-Bd_nMFu3.js +1 -0
- package/dist/web/assets/ChatHistoryDrawer-B2qBQs9L.css +1 -0
- package/dist/web/assets/ChatHistoryDrawer-CwzJHyNP.js +1 -0
- package/dist/web/assets/CollapseItem-CTCfSiue.js +1 -0
- package/dist/web/assets/CommandsDrawer-BZJxlPvV.css +1 -0
- package/dist/web/assets/CommandsDrawer-vDghB1U9.js +1 -0
- package/dist/web/assets/ConfigExportDrawer-C-XDvlHc.js +1 -0
- package/dist/web/assets/ConfigExportDrawer-dwruRKWr.css +1 -0
- package/dist/web/assets/ConfigTemplates-CYY8XRED.js +1 -0
- package/dist/web/assets/ConfigTemplatesDrawer-BfDi8yO3.css +1 -0
- package/dist/web/assets/ConfigTemplatesDrawer-CbsJp76X.js +1 -0
- package/dist/web/assets/ConfigTemplatesPanel-CWyPtk67.css +1 -0
- package/dist/web/assets/ConfigTemplatesPanel-liMSTh7s.js +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-D6QSiYaL.js +1 -0
- package/dist/web/assets/GatewayConvertDrawer-CuNlf7AC.css +1 -0
- package/dist/web/assets/GatewayConvertDrawer-RvET6iNG.js +1 -0
- package/dist/web/assets/Home-CSuFuJ9Y.css +1 -0
- package/dist/web/assets/Home-P8eseZcf.js +1 -0
- package/dist/web/assets/MarkdownEditor-CYmLGYju.js +1 -0
- package/dist/web/assets/MarkdownEditor-qfVgSre_.css +1 -0
- package/dist/web/assets/McpDrawer-C0dnsZhQ.css +1 -0
- package/dist/web/assets/McpDrawer-Ch3bkKIq.js +1 -0
- package/dist/web/assets/McpFormDrawer-BRbDcawn.css +1 -0
- package/dist/web/assets/McpFormDrawer-BcTlgSpN.js +1 -0
- package/dist/web/assets/PluginManager-Dh2o0xkZ.js +1 -0
- package/dist/web/assets/PluginsDrawer-DM3QjprE.css +1 -0
- package/dist/web/assets/PluginsDrawer-deKQ5u3I.js +1 -0
- package/dist/web/assets/PluginsPanel-BofsgQgj.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-DhSWJHTA.js +1 -0
- package/dist/web/assets/PromptsDrawer-BIgiYCEQ.css +1 -0
- package/dist/web/assets/PromptsDrawer-Cfb2FeeU.js +1 -0
- package/dist/web/assets/RecentSessionsDrawer-3eXamJhr.js +1 -0
- package/dist/web/assets/RecentSessionsDrawer-CKFsOYDM.css +1 -0
- package/dist/web/assets/SessionCard-CW0eHURI.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-DQfyDGWU.js +1 -0
- package/dist/web/assets/SettingsDrawer-Dw5NUgHC.css +1 -0
- package/dist/web/assets/SettingsDrawer-u_vwUuPS.js +1 -0
- package/dist/web/assets/SkillManager-C0GCT2Kb.css +1 -0
- package/dist/web/assets/SkillManager-DHMprJLA.js +1 -0
- package/dist/web/assets/SkillsDrawer-BCNme-aJ.css +1 -0
- package/dist/web/assets/SkillsDrawer-CkF3BpyC.js +1 -0
- package/dist/web/assets/SkillsPanel-B3Nm5j32.css +1 -0
- package/dist/web/assets/SkillsPanel-DWE4pFGx.js +1 -0
- package/dist/web/assets/SpeedTestDrawer-BgfG1Ked.js +1 -0
- package/dist/web/assets/SpeedTestDrawer-Dc0WToJ_.css +1 -0
- package/dist/web/assets/Upload-CNwaGOws.js +1 -0
- package/dist/web/assets/WorkspaceDrawer-Br5CeCzL.js +1 -0
- package/dist/web/assets/WorkspaceDrawer-CGnMu9p1.css +1 -0
- package/dist/web/assets/WorkspaceManager-B2O8-qIw.js +1 -0
- package/dist/web/assets/WorkspacePanel-5oDKit3c.js +1 -0
- package/dist/web/assets/WorkspacePanel-D0-zHTHP.css +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-BMAUPq5K.js +2 -0
- package/dist/web/assets/index-Bi1MQHYX.css +1 -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-jbmqZuJs.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 +104 -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 +165 -0
- package/src/platforms/channel-auth-service.js +107 -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 +98 -418
- package/src/{server/services → platforms}/notification-hooks.js +190 -1023
- package/src/platforms/oauth-credentials-service.js +635 -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 +133 -24
- 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 +12 -2
- 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 +876 -214
- 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
|
@@ -1,47 +1,59 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const os = require('os');
|
|
4
|
-
const http = require('http');
|
|
5
|
-
const https = require('https');
|
|
6
|
-
const crypto = require('crypto');
|
|
7
|
-
const { HttpsProxyAgent } = require('https-proxy-agent');
|
|
8
4
|
const { execSync, execFileSync } = require('child_process');
|
|
9
|
-
const { PATHS, NATIVE_PATHS } = require('
|
|
10
|
-
const { loadUIConfig, saveUIConfig } = require('
|
|
11
|
-
const
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
5
|
+
const { PATHS, NATIVE_PATHS } = require('../config/paths');
|
|
6
|
+
const { loadUIConfig, saveUIConfig } = require('../server/services/ui-config');
|
|
7
|
+
const remoteProviderRegistry = require('./remote-notification-providers');
|
|
8
|
+
const {
|
|
9
|
+
getRemoteProviderTypes,
|
|
10
|
+
normalizeRemoteProvider,
|
|
11
|
+
normalizeRemoteNotificationsConfig,
|
|
12
|
+
validateRemoteProviderConfig,
|
|
13
|
+
validateRemoteNotifications,
|
|
14
|
+
sendRemoteProviderTest,
|
|
15
|
+
validateFeishuWebhookUrl
|
|
16
|
+
} = remoteProviderRegistry;
|
|
17
|
+
const { getPlatformCatalog } = require('../server/services/platform-catalog');
|
|
18
|
+
const notificationHooksDriver = require('./drivers/notification-hooks');
|
|
19
|
+
const {
|
|
20
|
+
buildClaudeCommand,
|
|
21
|
+
buildCodexNotifyCommand,
|
|
22
|
+
buildGeminiCommand,
|
|
23
|
+
buildOpenCodePluginContent,
|
|
24
|
+
buildOmpExtensionContent,
|
|
25
|
+
getOpenCodeManagedPluginPath,
|
|
26
|
+
getOmpManagedExtensionPath,
|
|
27
|
+
getClaudeHookStatus,
|
|
28
|
+
getCodexHookStatus,
|
|
29
|
+
getGeminiHookStatus,
|
|
30
|
+
getOpenCodeHookStatus,
|
|
31
|
+
getOmpHookStatus,
|
|
32
|
+
parseCodexNotificationStatus,
|
|
33
|
+
parseGeminiNotificationStatus,
|
|
34
|
+
parseOpenCodeNotificationStatus,
|
|
35
|
+
parseOmpNotificationStatus
|
|
36
|
+
} = notificationHooksDriver;
|
|
37
|
+
|
|
38
|
+
const MANAGED_HOOK_NAME = notificationHooksDriver.MANAGED_HOOK_NAME;
|
|
39
|
+
|
|
40
|
+
function getConfiguredNativePath(platform, name, fallback) {
|
|
41
|
+
try {
|
|
42
|
+
const { getPlatformContext } = require('../server/platform-context');
|
|
43
|
+
const pathContext = getPlatformContext().registry.resolvePathContext(platform);
|
|
44
|
+
if (pathContext?.customized && pathContext.native?.[name]) {
|
|
45
|
+
return pathContext.native[name];
|
|
46
|
+
}
|
|
47
|
+
} catch (_) {
|
|
48
|
+
// Keep legacy paths when notification helpers are used in isolation.
|
|
49
|
+
}
|
|
50
|
+
return fallback;
|
|
51
|
+
}
|
|
34
52
|
|
|
35
53
|
function normalizeType(type) {
|
|
36
54
|
return type === 'dialog' || type === 'browser' ? type : 'notification';
|
|
37
55
|
}
|
|
38
56
|
|
|
39
|
-
function createClientId(prefix = 'coding-tool') {
|
|
40
|
-
const randomPart = typeof crypto.randomUUID === 'function'
|
|
41
|
-
? crypto.randomUUID().replace(/-/g, '').slice(0, 16)
|
|
42
|
-
: crypto.randomBytes(8).toString('hex');
|
|
43
|
-
return `${prefix}-${randomPart}`;
|
|
44
|
-
}
|
|
45
57
|
|
|
46
58
|
function ensureParentDir(filePath) {
|
|
47
59
|
const dir = path.dirname(filePath);
|
|
@@ -69,96 +81,13 @@ function writeJsonFile(filePath, value) {
|
|
|
69
81
|
}
|
|
70
82
|
|
|
71
83
|
function readClaudeSettings() {
|
|
72
|
-
return readJsonFile(NATIVE_PATHS.claude.settings);
|
|
84
|
+
return readJsonFile(getConfiguredNativePath('claude', 'settings', NATIVE_PATHS.claude.settings));
|
|
73
85
|
}
|
|
74
86
|
|
|
75
87
|
function writeClaudeSettings(settings) {
|
|
76
|
-
writeJsonFile(NATIVE_PATHS.claude.settings, settings);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
function trimString(value) {
|
|
80
|
-
return String(value || '').trim();
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
function normalizeRemoteProviderType(type) {
|
|
84
|
-
const value = trimString(type);
|
|
85
|
-
return REMOTE_PROVIDER_TYPES.includes(value) ? value : '';
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
function createRemoteProviderId(type, index = 0) {
|
|
89
|
-
return `${type || 'provider'}-${Date.now().toString(36)}-${index}`;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
function normalizeProviderConfig(type, config = {}) {
|
|
93
|
-
const source = config && typeof config === 'object' ? config : {};
|
|
94
|
-
switch (type) {
|
|
95
|
-
case 'wechatBot':
|
|
96
|
-
return {
|
|
97
|
-
tokenFile: trimString(source.tokenFile),
|
|
98
|
-
botToken: trimString(source.botToken),
|
|
99
|
-
targetUserId: trimString(source.targetUserId),
|
|
100
|
-
contextToken: trimString(source.contextToken)
|
|
101
|
-
};
|
|
102
|
-
case 'qqBot':
|
|
103
|
-
return {
|
|
104
|
-
endpoint: trimString(source.endpoint),
|
|
105
|
-
accessToken: trimString(source.accessToken),
|
|
106
|
-
targetType: source.targetType === 'group' ? 'group' : 'private',
|
|
107
|
-
targetId: trimString(source.targetId)
|
|
108
|
-
};
|
|
109
|
-
case 'feishuBot':
|
|
110
|
-
return {
|
|
111
|
-
webhookUrl: trimString(source.webhookUrl)
|
|
112
|
-
};
|
|
113
|
-
case 'wecomBot':
|
|
114
|
-
return {
|
|
115
|
-
webhookUrl: trimString(source.webhookUrl)
|
|
116
|
-
};
|
|
117
|
-
case 'dingtalkBot':
|
|
118
|
-
return {
|
|
119
|
-
mode: source.mode === 'app' ? 'app' : 'webhook',
|
|
120
|
-
webhookUrl: trimString(source.webhookUrl),
|
|
121
|
-
clientId: trimString(source.clientId),
|
|
122
|
-
clientSecret: trimString(source.clientSecret),
|
|
123
|
-
targetType: source.targetType === 'user' ? 'user' : 'group',
|
|
124
|
-
targetId: trimString(source.targetId)
|
|
125
|
-
};
|
|
126
|
-
case 'telegramBot':
|
|
127
|
-
return {
|
|
128
|
-
botToken: trimString(source.botToken),
|
|
129
|
-
chatId: trimString(source.chatId),
|
|
130
|
-
proxy: trimString(source.proxy)
|
|
131
|
-
};
|
|
132
|
-
default:
|
|
133
|
-
return {};
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
function normalizeRemoteProvider(provider = {}, index = 0) {
|
|
138
|
-
const type = normalizeRemoteProviderType(provider.type);
|
|
139
|
-
if (!type) {
|
|
140
|
-
return null;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
return {
|
|
144
|
-
id: trimString(provider.id) || createRemoteProviderId(type, index),
|
|
145
|
-
type,
|
|
146
|
-
name: trimString(provider.name) || REMOTE_PROVIDER_LABELS[type],
|
|
147
|
-
enabled: provider.enabled === true,
|
|
148
|
-
config: normalizeProviderConfig(type, provider.config)
|
|
149
|
-
};
|
|
88
|
+
writeJsonFile(getConfiguredNativePath('claude', 'settings', NATIVE_PATHS.claude.settings), settings);
|
|
150
89
|
}
|
|
151
90
|
|
|
152
|
-
function normalizeRemoteNotificationsConfig(remoteNotifications = {}) {
|
|
153
|
-
const source = remoteNotifications && typeof remoteNotifications === 'object' ? remoteNotifications : {};
|
|
154
|
-
const providers = Array.isArray(source.providers)
|
|
155
|
-
? source.providers.map((provider, index) => normalizeRemoteProvider(provider, index)).filter(Boolean)
|
|
156
|
-
: [];
|
|
157
|
-
|
|
158
|
-
return {
|
|
159
|
-
providers
|
|
160
|
-
};
|
|
161
|
-
}
|
|
162
91
|
|
|
163
92
|
function getRemoteNotificationsConfig(uiConfig = loadUIConfig()) {
|
|
164
93
|
return normalizeRemoteNotificationsConfig(uiConfig.remoteNotifications);
|
|
@@ -190,115 +119,7 @@ function createValidationError(message) {
|
|
|
190
119
|
return error;
|
|
191
120
|
}
|
|
192
121
|
|
|
193
|
-
function validateFeishuWebhookUrl(webhookUrl) {
|
|
194
|
-
const value = String(webhookUrl || '').trim();
|
|
195
|
-
if (!value) {
|
|
196
|
-
return null;
|
|
197
|
-
}
|
|
198
122
|
|
|
199
|
-
let urlObj;
|
|
200
|
-
try {
|
|
201
|
-
urlObj = new URL(value);
|
|
202
|
-
} catch (error) {
|
|
203
|
-
throw createValidationError('飞书 Webhook URL 格式不正确');
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
if (urlObj.protocol !== 'https:') {
|
|
207
|
-
throw createValidationError('飞书 Webhook 必须使用 HTTPS');
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
if (urlObj.hostname !== 'open.feishu.cn') {
|
|
211
|
-
throw createValidationError('仅支持 open.feishu.cn 的飞书 Webhook');
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
return urlObj;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
function validateHttpsUrl(value, label, options = {}) {
|
|
218
|
-
const raw = trimString(value);
|
|
219
|
-
if (!raw) {
|
|
220
|
-
return null;
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
let urlObj;
|
|
224
|
-
try {
|
|
225
|
-
urlObj = new URL(raw);
|
|
226
|
-
} catch (error) {
|
|
227
|
-
throw createValidationError(`${label} 格式不正确`);
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
if (options.requireHttps !== false && urlObj.protocol !== 'https:') {
|
|
231
|
-
throw createValidationError(`${label} 必须使用 HTTPS`);
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
if (options.hostname && urlObj.hostname !== options.hostname) {
|
|
235
|
-
throw createValidationError(`${label} 仅支持 ${options.hostname}`);
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
return urlObj;
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
function requireConfigValue(config, key, label) {
|
|
242
|
-
if (!trimString(config?.[key])) {
|
|
243
|
-
throw createValidationError(`请填写${label}`);
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
function validateRemoteProviderConfig(provider = {}) {
|
|
248
|
-
const type = normalizeRemoteProviderType(provider.type);
|
|
249
|
-
const config = provider.config || {};
|
|
250
|
-
if (!type) {
|
|
251
|
-
throw createValidationError('远程通知渠道类型不正确');
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
switch (type) {
|
|
255
|
-
case 'wechatBot':
|
|
256
|
-
if (!trimString(config.botToken) && !trimString(config.tokenFile)) {
|
|
257
|
-
throw createValidationError('请填写微信 Bot Token 或 token.json 路径');
|
|
258
|
-
}
|
|
259
|
-
requireConfigValue(config, 'targetUserId', '微信接收用户 ID');
|
|
260
|
-
break;
|
|
261
|
-
case 'qqBot':
|
|
262
|
-
requireConfigValue(config, 'endpoint', 'QQ Bot HTTP 地址');
|
|
263
|
-
validateHttpsUrl(config.endpoint, 'QQ Bot HTTP 地址', { requireHttps: false });
|
|
264
|
-
requireConfigValue(config, 'targetId', 'QQ 接收对象 ID');
|
|
265
|
-
break;
|
|
266
|
-
case 'feishuBot':
|
|
267
|
-
validateFeishuWebhookUrl(config.webhookUrl);
|
|
268
|
-
requireConfigValue(config, 'webhookUrl', '飞书 Webhook URL');
|
|
269
|
-
break;
|
|
270
|
-
case 'wecomBot':
|
|
271
|
-
validateHttpsUrl(config.webhookUrl, '企业微信 Webhook URL');
|
|
272
|
-
requireConfigValue(config, 'webhookUrl', '企业微信 Webhook URL');
|
|
273
|
-
break;
|
|
274
|
-
case 'dingtalkBot':
|
|
275
|
-
if (config.mode === 'app') {
|
|
276
|
-
requireConfigValue(config, 'clientId', '钉钉 App Key');
|
|
277
|
-
requireConfigValue(config, 'clientSecret', '钉钉 App Secret');
|
|
278
|
-
requireConfigValue(config, 'targetId', '钉钉接收对象 ID');
|
|
279
|
-
} else {
|
|
280
|
-
validateHttpsUrl(config.webhookUrl, '钉钉 Webhook URL');
|
|
281
|
-
requireConfigValue(config, 'webhookUrl', '钉钉 Webhook URL');
|
|
282
|
-
}
|
|
283
|
-
break;
|
|
284
|
-
case 'telegramBot':
|
|
285
|
-
requireConfigValue(config, 'botToken', 'Telegram Bot Token');
|
|
286
|
-
requireConfigValue(config, 'chatId', 'Telegram Chat ID');
|
|
287
|
-
break;
|
|
288
|
-
default:
|
|
289
|
-
break;
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
function validateRemoteNotifications(remoteNotifications = {}) {
|
|
294
|
-
const remote = normalizeRemoteNotificationsConfig(remoteNotifications);
|
|
295
|
-
remote.providers.forEach((provider) => {
|
|
296
|
-
if (provider.enabled === true) {
|
|
297
|
-
validateRemoteProviderConfig(provider);
|
|
298
|
-
}
|
|
299
|
-
});
|
|
300
|
-
return remote;
|
|
301
|
-
}
|
|
302
123
|
|
|
303
124
|
function removeNotifyScript() {
|
|
304
125
|
if (fs.existsSync(PATHS.notifyHook)) {
|
|
@@ -333,148 +154,6 @@ function isManagedNotifyPath(input) {
|
|
|
333
154
|
return normalizedInput.includes('notify-hook.js') || (normalizedPath && normalizedInput.includes(normalizedPath));
|
|
334
155
|
}
|
|
335
156
|
|
|
336
|
-
function buildManagedArgs(source, type, extra = []) {
|
|
337
|
-
const mode = normalizeType(type);
|
|
338
|
-
return [
|
|
339
|
-
`--source=${source}`,
|
|
340
|
-
`--mode=${mode}`,
|
|
341
|
-
`--cc-notify-type=${mode}`,
|
|
342
|
-
...extra
|
|
343
|
-
];
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
function quoteShellArg(value) {
|
|
347
|
-
const stringValue = String(value || '');
|
|
348
|
-
if (/^[A-Za-z0-9_./:=+-]+$/.test(stringValue)) {
|
|
349
|
-
return stringValue;
|
|
350
|
-
}
|
|
351
|
-
return `"${stringValue.replace(/"/g, '\\"')}"`;
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
function buildClaudeCommand(type) {
|
|
355
|
-
const mode = normalizeType(type);
|
|
356
|
-
const args = ['node', PATHS.notifyHook, `--source=claude`, `--mode=${mode}`, `--cc-notify-type=${mode}`];
|
|
357
|
-
return `${quoteShellArg(args[0])} ${quoteShellArg(args[1])} ${args.slice(2).map(quoteShellArg).join(' ')}`;
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
function buildCodexNotifyCommand(type) {
|
|
361
|
-
const mode = normalizeType(type);
|
|
362
|
-
return ['node', PATHS.notifyHook, `--source=codex`, `--mode=${mode}`, `--cc-notify-type=${mode}`];
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
function buildGeminiCommand(type) {
|
|
366
|
-
const mode = normalizeType(type);
|
|
367
|
-
const args = ['node', PATHS.notifyHook, `--source=gemini`, `--mode=${mode}`, `--cc-notify-type=${mode}`];
|
|
368
|
-
return `${quoteShellArg(args[0])} ${quoteShellArg(args[1])} ${args.slice(2).map(quoteShellArg).join(' ')}`;
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
function getOpenCodeManagedPluginPath() {
|
|
372
|
-
return path.join(NATIVE_PATHS.opencode.config, 'plugins', MANAGED_OPENCODE_PLUGIN_FILE);
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
function getOmpManagedExtensionPath() {
|
|
376
|
-
const ompPaths = NATIVE_PATHS.omp || {};
|
|
377
|
-
const extensionsDir = ompPaths.extensions ||
|
|
378
|
-
path.join(ompPaths.dir || path.dirname(ompPaths.settings || PATHS.notifyHook), 'extensions');
|
|
379
|
-
return path.join(extensionsDir, MANAGED_OMP_EXTENSION_FILE);
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
function buildOpenCodePluginContent(type) {
|
|
383
|
-
const mode = normalizeType(type);
|
|
384
|
-
return `// Managed by Coding Tool. Do not edit manually.
|
|
385
|
-
// mode:${mode}
|
|
386
|
-
import { spawn } from 'node:child_process'
|
|
387
|
-
|
|
388
|
-
const SCRIPT_PATH = ${JSON.stringify(PATHS.notifyHook)}
|
|
389
|
-
const MODE = ${JSON.stringify(mode)}
|
|
390
|
-
|
|
391
|
-
function fire(eventType) {
|
|
392
|
-
try {
|
|
393
|
-
const child = spawn('node', [
|
|
394
|
-
SCRIPT_PATH,
|
|
395
|
-
'--source=opencode',
|
|
396
|
-
\`--mode=\${MODE}\`,
|
|
397
|
-
\`--cc-notify-type=\${MODE}\`,
|
|
398
|
-
\`--event-type=\${eventType}\`
|
|
399
|
-
], {
|
|
400
|
-
detached: true,
|
|
401
|
-
stdio: 'ignore',
|
|
402
|
-
windowsHide: true
|
|
403
|
-
})
|
|
404
|
-
child.unref()
|
|
405
|
-
} catch (error) {
|
|
406
|
-
// Ignore notification failures.
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
export const CodingToolNotifyPlugin = async () => ({
|
|
411
|
-
event: async ({ event }) => {
|
|
412
|
-
const eventType = event?.type
|
|
413
|
-
if (eventType === 'session.idle' || eventType === 'session.error') {
|
|
414
|
-
fire(eventType)
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
})
|
|
418
|
-
`;
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
function buildOmpExtensionContent(type) {
|
|
422
|
-
const mode = normalizeType(type);
|
|
423
|
-
return `// Managed by Coding Tool. Do not edit manually.
|
|
424
|
-
// mode:${mode}
|
|
425
|
-
import { spawn } from 'node:child_process'
|
|
426
|
-
|
|
427
|
-
const SCRIPT_PATH = ${JSON.stringify(PATHS.notifyHook)}
|
|
428
|
-
const MODE = ${JSON.stringify(mode)}
|
|
429
|
-
let lastFireAt = 0
|
|
430
|
-
|
|
431
|
-
function shouldFire() {
|
|
432
|
-
const now = Date.now()
|
|
433
|
-
if (now - lastFireAt < 1000) {
|
|
434
|
-
return false
|
|
435
|
-
}
|
|
436
|
-
lastFireAt = now
|
|
437
|
-
return true
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
function fire(eventType) {
|
|
441
|
-
if (!shouldFire()) {
|
|
442
|
-
return
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
try {
|
|
446
|
-
const child = spawn('node', [
|
|
447
|
-
SCRIPT_PATH,
|
|
448
|
-
'--source=omp',
|
|
449
|
-
\`--mode=\${MODE}\`,
|
|
450
|
-
\`--cc-notify-type=\${MODE}\`,
|
|
451
|
-
\`--event-type=\${eventType}\`
|
|
452
|
-
], {
|
|
453
|
-
detached: true,
|
|
454
|
-
stdio: 'ignore',
|
|
455
|
-
windowsHide: true
|
|
456
|
-
})
|
|
457
|
-
child.unref()
|
|
458
|
-
} catch (error) {
|
|
459
|
-
// Ignore notification failures.
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
export default async function CodingToolNotifyExtension(omp) {
|
|
464
|
-
const register = (eventName) => {
|
|
465
|
-
if (typeof omp?.on !== 'function') {
|
|
466
|
-
return
|
|
467
|
-
}
|
|
468
|
-
omp.on(eventName, async () => {
|
|
469
|
-
fire(eventName)
|
|
470
|
-
})
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
register('agent_settled')
|
|
474
|
-
register('turn_end')
|
|
475
|
-
}
|
|
476
|
-
`;
|
|
477
|
-
}
|
|
478
157
|
|
|
479
158
|
function escapeForAppleScript(value) {
|
|
480
159
|
return String(value || '').replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
|
@@ -714,6 +393,20 @@ function createHttpsProxyAgent(proxyUrl) {
|
|
|
714
393
|
const REMOTE_PROVIDERS = ${JSON.stringify(enabledProviders)}
|
|
715
394
|
const CONFIG_FILE = ${JSON.stringify(PATHS.configFile)}
|
|
716
395
|
|
|
396
|
+
function isCliPlatformEnabled(source) {
|
|
397
|
+
try {
|
|
398
|
+
const config = JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'))
|
|
399
|
+
const configuredPlatforms = config?.enabledCliPlatforms
|
|
400
|
+
if (!Array.isArray(configuredPlatforms)) return true
|
|
401
|
+
const normalizedSource = String(source || '').trim().toLowerCase()
|
|
402
|
+
return configuredPlatforms.some((platform) => (
|
|
403
|
+
String(platform || '').trim().toLowerCase() === normalizedSource
|
|
404
|
+
))
|
|
405
|
+
} catch (error) {
|
|
406
|
+
return true
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
717
410
|
function createClientId(prefix = 'coding-tool') {
|
|
718
411
|
const randomPart = typeof crypto.randomUUID === 'function'
|
|
719
412
|
? crypto.randomUUID().replace(/-/g, '').slice(0, 16)
|
|
@@ -1147,6 +840,7 @@ async function sendRemoteNotifications(message, source, eventType) {
|
|
|
1147
840
|
|
|
1148
841
|
(async () => {
|
|
1149
842
|
const source = readArg('--source') || 'claude'
|
|
843
|
+
if (!isCliPlatformEnabled(source)) return
|
|
1150
844
|
const mode = readArg('--mode') || readArg('--cc-notify-type') || 'notification'
|
|
1151
845
|
const eventType = readArg('--event-type') || ''
|
|
1152
846
|
const payload = readOptionalPayload()
|
|
@@ -1382,365 +1076,91 @@ function writeNotifyScript(remoteNotifications = {}) {
|
|
|
1382
1076
|
fs.writeFileSync(PATHS.notifyHook, generateNotifyScript(remoteNotifications), { mode: 0o755 });
|
|
1383
1077
|
}
|
|
1384
1078
|
|
|
1385
|
-
function getClaudeHookStatus() {
|
|
1386
|
-
const settings = readClaudeSettings();
|
|
1387
|
-
const stopHooks = Array.isArray(settings?.hooks?.Stop) ? settings.hooks.Stop : [];
|
|
1388
|
-
let enabled = false;
|
|
1389
|
-
let external = false;
|
|
1390
|
-
let type = 'notification';
|
|
1391
1079
|
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
return {
|
|
1409
|
-
enabled,
|
|
1410
|
-
external,
|
|
1411
|
-
type,
|
|
1412
|
-
method: 'Stop Hook'
|
|
1413
|
-
};
|
|
1414
|
-
}
|
|
1415
|
-
|
|
1416
|
-
function saveClaudeHook(enabled, type) {
|
|
1417
|
-
const settings = readClaudeSettings();
|
|
1418
|
-
const hooks = settings.hooks && typeof settings.hooks === 'object' ? { ...settings.hooks } : {};
|
|
1419
|
-
const currentGroups = Array.isArray(hooks.Stop) ? hooks.Stop : [];
|
|
1420
|
-
|
|
1421
|
-
const filteredGroups = currentGroups.map((group) => {
|
|
1422
|
-
const groupHooks = Array.isArray(group?.hooks) ? group.hooks : [];
|
|
1423
|
-
const nextHooks = groupHooks.filter((hook) => !isManagedNotifyPath(hook?.command));
|
|
1424
|
-
if (nextHooks.length === 0) {
|
|
1425
|
-
return null;
|
|
1426
|
-
}
|
|
1427
|
-
return { ...group, hooks: nextHooks };
|
|
1428
|
-
}).filter(Boolean);
|
|
1429
|
-
|
|
1430
|
-
if (enabled) {
|
|
1431
|
-
filteredGroups.push({
|
|
1432
|
-
hooks: [
|
|
1433
|
-
{
|
|
1434
|
-
name: MANAGED_HOOK_NAME,
|
|
1435
|
-
type: 'command',
|
|
1436
|
-
command: buildClaudeCommand(type)
|
|
1437
|
-
}
|
|
1438
|
-
]
|
|
1439
|
-
});
|
|
1440
|
-
}
|
|
1441
|
-
|
|
1442
|
-
if (filteredGroups.length > 0) {
|
|
1443
|
-
hooks.Stop = filteredGroups;
|
|
1444
|
-
} else {
|
|
1445
|
-
delete hooks.Stop;
|
|
1446
|
-
}
|
|
1447
|
-
|
|
1448
|
-
if (Object.keys(hooks).length > 0) {
|
|
1449
|
-
settings.hooks = hooks;
|
|
1450
|
-
} else {
|
|
1451
|
-
delete settings.hooks;
|
|
1452
|
-
}
|
|
1453
|
-
|
|
1454
|
-
writeClaudeSettings(settings);
|
|
1455
|
-
}
|
|
1456
|
-
|
|
1457
|
-
function safeReadCodexConfig() {
|
|
1458
|
-
try {
|
|
1459
|
-
if (typeof codexSettingsManager.configExists === 'function' && !codexSettingsManager.configExists()) {
|
|
1460
|
-
return {};
|
|
1461
|
-
}
|
|
1462
|
-
return codexSettingsManager.readConfig();
|
|
1463
|
-
} catch (error) {
|
|
1464
|
-
return {};
|
|
1465
|
-
}
|
|
1466
|
-
}
|
|
1467
|
-
|
|
1468
|
-
function isManagedCodexNotify(notify) {
|
|
1469
|
-
return Array.isArray(notify) && notify.some((part) => isManagedNotifyPath(part));
|
|
1470
|
-
}
|
|
1471
|
-
|
|
1472
|
-
function parseCodexNotificationStatus(config = {}) {
|
|
1473
|
-
const notify = Array.isArray(config?.notify) ? config.notify : [];
|
|
1474
|
-
|
|
1475
|
-
if (notify.length === 0) {
|
|
1476
|
-
return {
|
|
1477
|
-
enabled: false,
|
|
1478
|
-
external: false,
|
|
1479
|
-
type: 'notification',
|
|
1480
|
-
method: 'notify'
|
|
1481
|
-
};
|
|
1482
|
-
}
|
|
1483
|
-
|
|
1484
|
-
const joined = notify.join(' ');
|
|
1485
|
-
const managed = isManagedCodexNotify(notify);
|
|
1486
|
-
|
|
1487
|
-
return {
|
|
1488
|
-
enabled: managed,
|
|
1489
|
-
external: !managed,
|
|
1490
|
-
type: parseManagedType(joined) || 'notification',
|
|
1491
|
-
method: 'notify'
|
|
1492
|
-
};
|
|
1493
|
-
}
|
|
1494
|
-
|
|
1495
|
-
function getCodexHookStatus() {
|
|
1496
|
-
return parseCodexNotificationStatus(safeReadCodexConfig());
|
|
1497
|
-
}
|
|
1498
|
-
|
|
1499
|
-
function saveCodexHook(enabled, type) {
|
|
1500
|
-
const config = safeReadCodexConfig();
|
|
1501
|
-
const nextConfig = (config && typeof config === 'object') ? { ...config } : {};
|
|
1502
|
-
const managed = isManagedCodexNotify(nextConfig.notify);
|
|
1503
|
-
|
|
1504
|
-
if (enabled) {
|
|
1505
|
-
nextConfig.notify = buildCodexNotifyCommand(type);
|
|
1506
|
-
} else if (managed) {
|
|
1507
|
-
delete nextConfig.notify;
|
|
1508
|
-
}
|
|
1509
|
-
|
|
1510
|
-
ensureParentDir(NATIVE_PATHS.codex.config);
|
|
1511
|
-
codexSettingsManager.writeConfig(nextConfig);
|
|
1512
|
-
}
|
|
1513
|
-
|
|
1514
|
-
function safeReadGeminiSettings() {
|
|
1515
|
-
try {
|
|
1516
|
-
if (typeof geminiSettingsManager.settingsExists === 'function' && !geminiSettingsManager.settingsExists()) {
|
|
1517
|
-
return {};
|
|
1080
|
+
function getHookPlatformEntries(catalog = getPlatformCatalog()) {
|
|
1081
|
+
const manifests = typeof catalog?.list === 'function'
|
|
1082
|
+
? catalog.list({ capability: 'hooks' })
|
|
1083
|
+
: [];
|
|
1084
|
+
const entries = [];
|
|
1085
|
+
for (const manifest of manifests) {
|
|
1086
|
+
const driver = typeof catalog.driver === 'function'
|
|
1087
|
+
? catalog.driver(manifest.key, 'hooks')
|
|
1088
|
+
: null;
|
|
1089
|
+
if (!driver
|
|
1090
|
+
|| typeof driver.getHooks !== 'function'
|
|
1091
|
+
|| typeof driver.saveHooks !== 'function'
|
|
1092
|
+
|| typeof driver.testHooks !== 'function'
|
|
1093
|
+
|| typeof driver.getDefinition !== 'function') {
|
|
1094
|
+
continue;
|
|
1518
1095
|
}
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1096
|
+
const definition = driver.getDefinition();
|
|
1097
|
+
if (!definition || typeof definition !== 'object') continue;
|
|
1098
|
+
entries.push({ manifest, driver, definition });
|
|
1522
1099
|
}
|
|
1100
|
+
return entries;
|
|
1523
1101
|
}
|
|
1524
1102
|
|
|
1525
|
-
function
|
|
1526
|
-
const
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
const afterAgentGroups = Array.isArray(settings?.hooks?.AfterAgent) ? settings.hooks.AfterAgent : [];
|
|
1533
|
-
let enabled = false;
|
|
1534
|
-
let external = false;
|
|
1535
|
-
let type = 'notification';
|
|
1536
|
-
|
|
1537
|
-
afterAgentGroups.forEach((group) => {
|
|
1538
|
-
const hooks = Array.isArray(group?.hooks) ? group.hooks : [];
|
|
1539
|
-
hooks.forEach((hook) => {
|
|
1540
|
-
if (isManagedGeminiHook(hook)) {
|
|
1541
|
-
enabled = true;
|
|
1542
|
-
type = parseManagedType(hook.command) || type;
|
|
1543
|
-
} else {
|
|
1544
|
-
external = true;
|
|
1545
|
-
}
|
|
1546
|
-
});
|
|
1547
|
-
});
|
|
1548
|
-
|
|
1549
|
-
return {
|
|
1550
|
-
enabled,
|
|
1551
|
-
external,
|
|
1552
|
-
type,
|
|
1553
|
-
method: 'AfterAgent Hook'
|
|
1554
|
-
};
|
|
1555
|
-
}
|
|
1556
|
-
|
|
1557
|
-
function getGeminiHookStatus() {
|
|
1558
|
-
return parseGeminiNotificationStatus(safeReadGeminiSettings());
|
|
1559
|
-
}
|
|
1560
|
-
|
|
1561
|
-
function saveGeminiHook(enabled, type) {
|
|
1562
|
-
const settings = safeReadGeminiSettings();
|
|
1563
|
-
const nextSettings = (settings && typeof settings === 'object') ? { ...settings } : {};
|
|
1564
|
-
const hooks = nextSettings.hooks && typeof nextSettings.hooks === 'object' ? { ...nextSettings.hooks } : {};
|
|
1565
|
-
const currentGroups = Array.isArray(hooks.AfterAgent) ? hooks.AfterAgent : [];
|
|
1566
|
-
|
|
1567
|
-
const filteredGroups = currentGroups.map((group) => {
|
|
1568
|
-
const groupHooks = Array.isArray(group?.hooks) ? group.hooks : [];
|
|
1569
|
-
const nextHooks = groupHooks.filter((hook) => !isManagedGeminiHook(hook));
|
|
1570
|
-
if (nextHooks.length === 0) {
|
|
1571
|
-
return null;
|
|
1572
|
-
}
|
|
1573
|
-
return { ...group, hooks: nextHooks };
|
|
1574
|
-
}).filter(Boolean);
|
|
1575
|
-
|
|
1576
|
-
if (enabled) {
|
|
1577
|
-
filteredGroups.push({
|
|
1578
|
-
matcher: '*',
|
|
1579
|
-
hooks: [
|
|
1580
|
-
{
|
|
1581
|
-
name: MANAGED_HOOK_NAME,
|
|
1582
|
-
type: 'command',
|
|
1583
|
-
command: buildGeminiCommand(type)
|
|
1584
|
-
}
|
|
1585
|
-
]
|
|
1586
|
-
});
|
|
1587
|
-
}
|
|
1588
|
-
|
|
1589
|
-
if (filteredGroups.length > 0) {
|
|
1590
|
-
hooks.AfterAgent = filteredGroups;
|
|
1591
|
-
} else {
|
|
1592
|
-
delete hooks.AfterAgent;
|
|
1593
|
-
}
|
|
1594
|
-
|
|
1595
|
-
if (Object.keys(hooks).length > 0) {
|
|
1596
|
-
nextSettings.hooks = hooks;
|
|
1597
|
-
} else {
|
|
1598
|
-
delete nextSettings.hooks;
|
|
1599
|
-
}
|
|
1600
|
-
|
|
1601
|
-
ensureParentDir(geminiSettingsManager.getSettingsPath());
|
|
1602
|
-
geminiSettingsManager.writeSettings(nextSettings);
|
|
1103
|
+
function getChannelPlatformKeys(catalog = getPlatformCatalog()) {
|
|
1104
|
+
const manifests = typeof catalog?.list === 'function'
|
|
1105
|
+
? catalog.list({ capability: 'channels' })
|
|
1106
|
+
: [];
|
|
1107
|
+
return new Set(manifests
|
|
1108
|
+
.map(manifest => String(manifest?.key || '').trim().toLowerCase())
|
|
1109
|
+
.filter(Boolean));
|
|
1603
1110
|
}
|
|
1604
1111
|
|
|
1605
|
-
function
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
external: false,
|
|
1610
|
-
type: 'notification',
|
|
1611
|
-
method: 'Plugin Events'
|
|
1612
|
-
};
|
|
1613
|
-
}
|
|
1112
|
+
function isConfiguredCliPlatformEnabled(key, uiConfig, catalog) {
|
|
1113
|
+
const channelPlatformKeys = getChannelPlatformKeys(catalog);
|
|
1114
|
+
const normalizedKey = String(key || '').trim().toLowerCase();
|
|
1115
|
+
if (!channelPlatformKeys.has(normalizedKey)) return true;
|
|
1614
1116
|
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
};
|
|
1117
|
+
const configuredPlatforms = uiConfig?.enabledCliPlatforms;
|
|
1118
|
+
if (!Array.isArray(configuredPlatforms)) return true;
|
|
1119
|
+
return configuredPlatforms.some(platform => (
|
|
1120
|
+
String(platform || '').trim().toLowerCase() === normalizedKey
|
|
1121
|
+
));
|
|
1621
1122
|
}
|
|
1622
1123
|
|
|
1623
|
-
function
|
|
1624
|
-
if (!content) {
|
|
1625
|
-
return {
|
|
1626
|
-
enabled: false,
|
|
1627
|
-
external: false,
|
|
1628
|
-
type: 'notification',
|
|
1629
|
-
method: 'Extension Events'
|
|
1630
|
-
};
|
|
1631
|
-
}
|
|
1632
|
-
|
|
1124
|
+
function toPublicHookDefinition({ manifest, definition }) {
|
|
1633
1125
|
return {
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1126
|
+
key: manifest.key,
|
|
1127
|
+
label: manifest.label || definition.label || manifest.key,
|
|
1128
|
+
description: typeof definition.description === 'string' ? definition.description : '',
|
|
1129
|
+
implementation: typeof definition.implementation === 'string' ? definition.implementation : '',
|
|
1130
|
+
externalMessage: typeof definition.externalMessage === 'string' ? definition.externalMessage : '',
|
|
1131
|
+
hints: Array.isArray(definition.hints) ? [...definition.hints] : []
|
|
1638
1132
|
};
|
|
1639
1133
|
}
|
|
1640
1134
|
|
|
1641
|
-
function
|
|
1642
|
-
const
|
|
1643
|
-
|
|
1644
|
-
return parseOpenCodeNotificationStatus('');
|
|
1645
|
-
}
|
|
1646
|
-
|
|
1647
|
-
return parseOpenCodeNotificationStatus(fs.readFileSync(pluginPath, 'utf8'));
|
|
1648
|
-
}
|
|
1649
|
-
|
|
1650
|
-
function getOmpHookStatus() {
|
|
1651
|
-
const extensionPath = getOmpManagedExtensionPath();
|
|
1652
|
-
if (!fs.existsSync(extensionPath)) {
|
|
1653
|
-
return parseOmpNotificationStatus('');
|
|
1654
|
-
}
|
|
1655
|
-
|
|
1656
|
-
return parseOmpNotificationStatus(fs.readFileSync(extensionPath, 'utf8'));
|
|
1657
|
-
}
|
|
1658
|
-
|
|
1659
|
-
function saveOpenCodeHook(enabled, type) {
|
|
1660
|
-
const pluginPath = getOpenCodeManagedPluginPath();
|
|
1661
|
-
const opencodeSettingsManager = require('./opencode-settings-manager');
|
|
1662
|
-
const configPath = opencodeSettingsManager.selectConfigPath();
|
|
1663
|
-
|
|
1664
|
-
if (!enabled) {
|
|
1665
|
-
// Remove plugin file
|
|
1666
|
-
if (fs.existsSync(pluginPath)) {
|
|
1667
|
-
fs.unlinkSync(pluginPath);
|
|
1668
|
-
}
|
|
1669
|
-
|
|
1670
|
-
// Remove from opencode.json plugins array
|
|
1671
|
-
if (fs.existsSync(configPath)) {
|
|
1672
|
-
try {
|
|
1673
|
-
const config = opencodeSettingsManager.readConfig(configPath);
|
|
1674
|
-
if (Array.isArray(config.plugins)) {
|
|
1675
|
-
config.plugins = config.plugins.filter(p => p !== './plugins/coding-tool-notify.js');
|
|
1676
|
-
if (config.plugins.length === 0) {
|
|
1677
|
-
delete config.plugins;
|
|
1678
|
-
}
|
|
1679
|
-
opencodeSettingsManager.writeConfig(configPath, config);
|
|
1680
|
-
}
|
|
1681
|
-
} catch (error) {
|
|
1682
|
-
console.error('Failed to update opencode.json:', error);
|
|
1683
|
-
}
|
|
1684
|
-
}
|
|
1685
|
-
return;
|
|
1686
|
-
}
|
|
1687
|
-
|
|
1688
|
-
// Create plugin file
|
|
1689
|
-
ensureParentDir(pluginPath);
|
|
1690
|
-
fs.writeFileSync(pluginPath, buildOpenCodePluginContent(type), 'utf8');
|
|
1691
|
-
|
|
1692
|
-
// Add to opencode.json plugins array
|
|
1693
|
-
try {
|
|
1694
|
-
const config = fs.existsSync(configPath)
|
|
1695
|
-
? opencodeSettingsManager.readConfig(configPath)
|
|
1696
|
-
: {};
|
|
1697
|
-
|
|
1698
|
-
if (!Array.isArray(config.plugins)) {
|
|
1699
|
-
config.plugins = [];
|
|
1700
|
-
}
|
|
1701
|
-
|
|
1702
|
-
const pluginRef = './plugins/coding-tool-notify.js';
|
|
1703
|
-
if (!config.plugins.includes(pluginRef)) {
|
|
1704
|
-
config.plugins.push(pluginRef);
|
|
1705
|
-
}
|
|
1706
|
-
|
|
1707
|
-
opencodeSettingsManager.writeConfig(configPath, config);
|
|
1708
|
-
} catch (error) {
|
|
1709
|
-
console.error('Failed to update opencode.json:', error);
|
|
1710
|
-
}
|
|
1711
|
-
}
|
|
1712
|
-
|
|
1713
|
-
function saveOmpHook(enabled, type) {
|
|
1714
|
-
const extensionPath = getOmpManagedExtensionPath();
|
|
1715
|
-
|
|
1716
|
-
if (!enabled) {
|
|
1717
|
-
if (fs.existsSync(extensionPath)) {
|
|
1718
|
-
fs.unlinkSync(extensionPath);
|
|
1719
|
-
}
|
|
1720
|
-
return;
|
|
1721
|
-
}
|
|
1722
|
-
|
|
1723
|
-
ensureParentDir(extensionPath);
|
|
1724
|
-
fs.writeFileSync(extensionPath, buildOmpExtensionContent(type), 'utf8');
|
|
1725
|
-
}
|
|
1726
|
-
|
|
1727
|
-
function getNotificationSettings() {
|
|
1135
|
+
function getNotificationSettings({ catalog } = {}) {
|
|
1136
|
+
const resolvedCatalog = catalog || getPlatformCatalog();
|
|
1137
|
+
const entries = getHookPlatformEntries(resolvedCatalog);
|
|
1728
1138
|
const uiConfig = loadUIConfig();
|
|
1729
1139
|
const remoteNotifications = getRemoteNotificationsConfig(uiConfig);
|
|
1140
|
+
const platforms = {};
|
|
1141
|
+
const platformDefinitions = [];
|
|
1142
|
+
for (const entry of entries) {
|
|
1143
|
+
const hooks = entry.driver.getHooks();
|
|
1144
|
+
platforms[entry.manifest.key] = isConfiguredCliPlatformEnabled(
|
|
1145
|
+
entry.manifest.key,
|
|
1146
|
+
uiConfig,
|
|
1147
|
+
resolvedCatalog
|
|
1148
|
+
)
|
|
1149
|
+
? hooks
|
|
1150
|
+
: { ...hooks, enabled: false };
|
|
1151
|
+
platformDefinitions.push(toPublicHookDefinition(entry));
|
|
1152
|
+
}
|
|
1153
|
+
const claudeStatus = platforms.claude || { enabled: false, type: 'notification' };
|
|
1730
1154
|
return {
|
|
1731
1155
|
success: true,
|
|
1732
1156
|
platform: os.platform(),
|
|
1733
1157
|
remoteNotifications,
|
|
1734
|
-
remoteProviderTypes:
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
codex: getCodexHookStatus(),
|
|
1741
|
-
gemini: getGeminiHookStatus(),
|
|
1742
|
-
opencode: getOpenCodeHookStatus(),
|
|
1743
|
-
omp: getOmpHookStatus()
|
|
1158
|
+
remoteProviderTypes: getRemoteProviderTypes(),
|
|
1159
|
+
platformDefinitions,
|
|
1160
|
+
platforms,
|
|
1161
|
+
stopHook: {
|
|
1162
|
+
enabled: claudeStatus.enabled === true,
|
|
1163
|
+
type: normalizeType(claudeStatus.type)
|
|
1744
1164
|
}
|
|
1745
1165
|
};
|
|
1746
1166
|
}
|
|
@@ -1800,43 +1220,49 @@ function emitBrowserNotification(input = {}) {
|
|
|
1800
1220
|
timestamp: Date.now()
|
|
1801
1221
|
};
|
|
1802
1222
|
|
|
1803
|
-
const { broadcastBrowserNotification } = require('../websocket-server');
|
|
1223
|
+
const { broadcastBrowserNotification } = require('../server/websocket-server');
|
|
1804
1224
|
broadcastBrowserNotification(payload);
|
|
1805
1225
|
return payload;
|
|
1806
1226
|
}
|
|
1807
|
-
|
|
1808
|
-
function saveNotificationSettings(input = {}) {
|
|
1227
|
+
function saveNotificationSettings(input = {}, { catalog } = {}) {
|
|
1809
1228
|
const remoteNotifications = validateRemoteNotifications(
|
|
1810
1229
|
input.remoteNotifications !== undefined
|
|
1811
1230
|
? input.remoteNotifications
|
|
1812
1231
|
: {}
|
|
1813
1232
|
);
|
|
1814
|
-
const
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1233
|
+
const resolvedCatalog = catalog || getPlatformCatalog();
|
|
1234
|
+
const entries = getHookPlatformEntries(resolvedCatalog);
|
|
1235
|
+
const uiConfig = loadUIConfig();
|
|
1236
|
+
const inputPlatforms = input.platforms && typeof input.platforms === 'object' && !Array.isArray(input.platforms)
|
|
1237
|
+
? input.platforms
|
|
1238
|
+
: {};
|
|
1239
|
+
const platforms = Object.fromEntries(entries.map(entry => {
|
|
1240
|
+
const key = entry.manifest.key;
|
|
1241
|
+
const platformInput = Object.prototype.hasOwnProperty.call(inputPlatforms, key)
|
|
1242
|
+
? inputPlatforms[key]
|
|
1243
|
+
: Object.prototype.hasOwnProperty.call(input, key)
|
|
1244
|
+
? input[key]
|
|
1245
|
+
: key === 'claude'
|
|
1246
|
+
? input.stopHook
|
|
1247
|
+
: undefined;
|
|
1248
|
+
const normalized = normalizePlatformInput(platformInput);
|
|
1249
|
+
return [key, isConfiguredCliPlatformEnabled(key, uiConfig, resolvedCatalog)
|
|
1250
|
+
? normalized
|
|
1251
|
+
: { ...normalized, enabled: false }];
|
|
1252
|
+
}));
|
|
1253
|
+
|
|
1254
|
+
const claudeEntry = entries.find(entry => entry.manifest.key === 'claude');
|
|
1255
|
+
saveNotificationUiConfig(remoteNotifications, claudeEntry ? platforms.claude.enabled : undefined);
|
|
1256
|
+
|
|
1257
|
+
const hasManagedPlatform = Object.values(platforms).some(platform => platform.enabled);
|
|
1258
|
+
for (const entry of entries) {
|
|
1259
|
+
entry.driver.saveHooks(platforms[entry.manifest.key]);
|
|
1260
|
+
}
|
|
1261
|
+
|
|
1262
|
+
if (hasManagedPlatform) writeNotifyScript(remoteNotifications);
|
|
1263
|
+
else removeNotifyScript();
|
|
1264
|
+
|
|
1265
|
+
return getNotificationSettings({ catalog: resolvedCatalog });
|
|
1840
1266
|
}
|
|
1841
1267
|
|
|
1842
1268
|
function parseNotifyTypeMarker(command) {
|
|
@@ -1950,20 +1376,19 @@ function normalizeSavedPlatformStatus(platform = {}) {
|
|
|
1950
1376
|
}
|
|
1951
1377
|
|
|
1952
1378
|
function buildLegacyClaudeSaveInput(input = {}, currentSettings = getNotificationSettings()) {
|
|
1379
|
+
const platforms = {
|
|
1380
|
+
...(currentSettings?.platforms && typeof currentSettings.platforms === 'object'
|
|
1381
|
+
? currentSettings.platforms
|
|
1382
|
+
: {})
|
|
1383
|
+
};
|
|
1384
|
+
platforms.claude = input.stopHook !== undefined
|
|
1385
|
+
? normalizePlatformInput(input.stopHook)
|
|
1386
|
+
: { enabled: false, type: 'notification' };
|
|
1953
1387
|
return {
|
|
1954
|
-
platforms
|
|
1955
|
-
claude: input.stopHook !== undefined
|
|
1956
|
-
? normalizePlatformInput(input.stopHook)
|
|
1957
|
-
: { enabled: false, type: 'notification' },
|
|
1958
|
-
codex: normalizeSavedPlatformStatus(currentSettings?.platforms?.codex),
|
|
1959
|
-
gemini: normalizeSavedPlatformStatus(currentSettings?.platforms?.gemini),
|
|
1960
|
-
opencode: normalizeSavedPlatformStatus(currentSettings?.platforms?.opencode),
|
|
1961
|
-
omp: normalizeSavedPlatformStatus(currentSettings?.platforms?.omp)
|
|
1962
|
-
},
|
|
1388
|
+
platforms,
|
|
1963
1389
|
remoteNotifications: currentSettings?.remoteNotifications || { providers: [] }
|
|
1964
1390
|
};
|
|
1965
1391
|
}
|
|
1966
|
-
|
|
1967
1392
|
function getLegacyClaudeHookSettings() {
|
|
1968
1393
|
return {
|
|
1969
1394
|
success: true,
|
|
@@ -1986,21 +1411,21 @@ function initDefaultHooks() {
|
|
|
1986
1411
|
return;
|
|
1987
1412
|
}
|
|
1988
1413
|
|
|
1414
|
+
const entries = getHookPlatformEntries();
|
|
1415
|
+
const claudeEntry = entries.find(entry => entry.manifest.key === 'claude');
|
|
1416
|
+
if (!claudeEntry) return;
|
|
1417
|
+
|
|
1989
1418
|
const currentClaudeSettings = readClaudeSettings();
|
|
1990
1419
|
const currentStatus = parseStopHookStatus(currentClaudeSettings);
|
|
1420
|
+
const currentSettings = getNotificationSettings();
|
|
1421
|
+
const platforms = { ...currentSettings.platforms };
|
|
1991
1422
|
|
|
1992
1423
|
if (currentStatus.enabled) {
|
|
1993
1424
|
if (shouldRepairStopHook(currentClaudeSettings)) {
|
|
1994
|
-
|
|
1425
|
+
platforms.claude = { enabled: true, type: currentStatus.type || 'notification' };
|
|
1995
1426
|
saveNotificationSettings({
|
|
1996
|
-
platforms
|
|
1997
|
-
|
|
1998
|
-
codex: normalizeSavedPlatformStatus(currentSettings?.platforms?.codex),
|
|
1999
|
-
gemini: normalizeSavedPlatformStatus(currentSettings?.platforms?.gemini),
|
|
2000
|
-
opencode: normalizeSavedPlatformStatus(currentSettings?.platforms?.opencode),
|
|
2001
|
-
omp: normalizeSavedPlatformStatus(currentSettings?.platforms?.omp)
|
|
2002
|
-
},
|
|
2003
|
-
remoteNotifications: currentSettings?.remoteNotifications || { providers: [] }
|
|
1427
|
+
platforms,
|
|
1428
|
+
remoteNotifications: currentSettings.remoteNotifications || { providers: [] }
|
|
2004
1429
|
});
|
|
2005
1430
|
console.log('[Claude Hooks] 检测到旧版 Stop hook 路径,已自动修复');
|
|
2006
1431
|
} else {
|
|
@@ -2009,16 +1434,10 @@ function initDefaultHooks() {
|
|
|
2009
1434
|
return;
|
|
2010
1435
|
}
|
|
2011
1436
|
|
|
2012
|
-
|
|
1437
|
+
platforms.claude = { enabled: true, type: 'notification' };
|
|
2013
1438
|
saveNotificationSettings({
|
|
2014
|
-
platforms
|
|
2015
|
-
|
|
2016
|
-
codex: normalizeSavedPlatformStatus(currentSettings?.platforms?.codex),
|
|
2017
|
-
gemini: normalizeSavedPlatformStatus(currentSettings?.platforms?.gemini),
|
|
2018
|
-
opencode: normalizeSavedPlatformStatus(currentSettings?.platforms?.opencode),
|
|
2019
|
-
omp: normalizeSavedPlatformStatus(currentSettings?.platforms?.omp)
|
|
2020
|
-
},
|
|
2021
|
-
remoteNotifications: currentSettings?.remoteNotifications || { providers: [] }
|
|
1439
|
+
platforms,
|
|
1440
|
+
remoteNotifications: currentSettings.remoteNotifications || { providers: [] }
|
|
2022
1441
|
});
|
|
2023
1442
|
console.log('[Claude Hooks] 已自动开启任务完成通知(右上角卡片)');
|
|
2024
1443
|
} catch (error) {
|
|
@@ -2026,261 +1445,6 @@ function initDefaultHooks() {
|
|
|
2026
1445
|
}
|
|
2027
1446
|
}
|
|
2028
1447
|
|
|
2029
|
-
function sendFeishuTest(webhookUrl) {
|
|
2030
|
-
return new Promise((resolve, reject) => {
|
|
2031
|
-
try {
|
|
2032
|
-
const urlObj = validateFeishuWebhookUrl(webhookUrl);
|
|
2033
|
-
const data = JSON.stringify({
|
|
2034
|
-
msg_type: 'interactive',
|
|
2035
|
-
card: {
|
|
2036
|
-
header: {
|
|
2037
|
-
title: { tag: 'plain_text', content: 'Coding Tool - 测试通知' },
|
|
2038
|
-
template: 'blue'
|
|
2039
|
-
},
|
|
2040
|
-
elements: [
|
|
2041
|
-
{
|
|
2042
|
-
tag: 'div',
|
|
2043
|
-
text: { tag: 'lark_md', content: '**状态**: 这是一条测试通知' }
|
|
2044
|
-
},
|
|
2045
|
-
{
|
|
2046
|
-
tag: 'div',
|
|
2047
|
-
text: { tag: 'lark_md', content: '**时间**: ' + new Date().toLocaleString('zh-CN') }
|
|
2048
|
-
}
|
|
2049
|
-
]
|
|
2050
|
-
}
|
|
2051
|
-
});
|
|
2052
|
-
|
|
2053
|
-
const options = {
|
|
2054
|
-
hostname: urlObj.hostname,
|
|
2055
|
-
port: urlObj.port || (urlObj.protocol === 'https:' ? 443 : 80),
|
|
2056
|
-
path: urlObj.pathname + urlObj.search,
|
|
2057
|
-
method: 'POST',
|
|
2058
|
-
headers: {
|
|
2059
|
-
'Content-Type': 'application/json',
|
|
2060
|
-
'Content-Length': Buffer.byteLength(data)
|
|
2061
|
-
},
|
|
2062
|
-
timeout: 10000
|
|
2063
|
-
};
|
|
2064
|
-
|
|
2065
|
-
const requestModule = urlObj.protocol === 'https:' ? https : http;
|
|
2066
|
-
const request = requestModule.request(options, () => resolve());
|
|
2067
|
-
request.on('error', reject);
|
|
2068
|
-
request.on('timeout', () => {
|
|
2069
|
-
request.destroy(new Error('飞书测试通知超时'));
|
|
2070
|
-
});
|
|
2071
|
-
request.write(data);
|
|
2072
|
-
request.end();
|
|
2073
|
-
} catch (error) {
|
|
2074
|
-
reject(error);
|
|
2075
|
-
}
|
|
2076
|
-
});
|
|
2077
|
-
}
|
|
2078
|
-
|
|
2079
|
-
function requestJson(url, data, headers = {}, extraOptions = {}) {
|
|
2080
|
-
return new Promise((resolve, reject) => {
|
|
2081
|
-
try {
|
|
2082
|
-
const urlObj = new URL(url);
|
|
2083
|
-
const body = JSON.stringify(data);
|
|
2084
|
-
const requestModule = urlObj.protocol === 'https:' ? https : http;
|
|
2085
|
-
const request = requestModule.request({
|
|
2086
|
-
hostname: urlObj.hostname,
|
|
2087
|
-
port: urlObj.port || (urlObj.protocol === 'https:' ? 443 : 80),
|
|
2088
|
-
path: urlObj.pathname + urlObj.search,
|
|
2089
|
-
method: 'POST',
|
|
2090
|
-
timeout: 10000,
|
|
2091
|
-
headers: {
|
|
2092
|
-
'Content-Type': 'application/json',
|
|
2093
|
-
'Content-Length': Buffer.byteLength(body),
|
|
2094
|
-
...headers
|
|
2095
|
-
},
|
|
2096
|
-
...extraOptions
|
|
2097
|
-
}, (response) => {
|
|
2098
|
-
response.resume();
|
|
2099
|
-
if (response.statusCode >= 200 && response.statusCode < 300) {
|
|
2100
|
-
resolve();
|
|
2101
|
-
} else {
|
|
2102
|
-
reject(new Error(`远程通知返回 HTTP ${response.statusCode}`));
|
|
2103
|
-
}
|
|
2104
|
-
});
|
|
2105
|
-
request.on('error', reject);
|
|
2106
|
-
request.on('timeout', () => {
|
|
2107
|
-
request.destroy(new Error('远程通知测试超时'));
|
|
2108
|
-
});
|
|
2109
|
-
request.write(body);
|
|
2110
|
-
request.end();
|
|
2111
|
-
} catch (error) {
|
|
2112
|
-
reject(error);
|
|
2113
|
-
}
|
|
2114
|
-
});
|
|
2115
|
-
}
|
|
2116
|
-
|
|
2117
|
-
function requestDingTalkAccessToken(config = {}) {
|
|
2118
|
-
if (!config.clientId || !config.clientSecret) {
|
|
2119
|
-
return Promise.resolve('');
|
|
2120
|
-
}
|
|
2121
|
-
return new Promise((resolve, reject) => {
|
|
2122
|
-
try {
|
|
2123
|
-
const body = JSON.stringify({ appKey: config.clientId, appSecret: config.clientSecret });
|
|
2124
|
-
const request = https.request({
|
|
2125
|
-
hostname: 'api.dingtalk.com',
|
|
2126
|
-
path: '/v1.0/oauth2/accessToken',
|
|
2127
|
-
method: 'POST',
|
|
2128
|
-
timeout: 10000,
|
|
2129
|
-
headers: {
|
|
2130
|
-
'Content-Type': 'application/json',
|
|
2131
|
-
'Content-Length': Buffer.byteLength(body)
|
|
2132
|
-
}
|
|
2133
|
-
}, (response) => {
|
|
2134
|
-
let responseBody = '';
|
|
2135
|
-
response.setEncoding('utf8');
|
|
2136
|
-
response.on('data', (chunk) => { responseBody += chunk; });
|
|
2137
|
-
response.on('end', () => {
|
|
2138
|
-
try {
|
|
2139
|
-
const parsed = JSON.parse(responseBody);
|
|
2140
|
-
if (response.statusCode >= 200 && response.statusCode < 300 && parsed.accessToken) {
|
|
2141
|
-
resolve(parsed.accessToken);
|
|
2142
|
-
return;
|
|
2143
|
-
}
|
|
2144
|
-
reject(new Error(`钉钉 Access Token 获取失败: ${responseBody.slice(0, 300)}`));
|
|
2145
|
-
} catch (error) {
|
|
2146
|
-
reject(error);
|
|
2147
|
-
}
|
|
2148
|
-
});
|
|
2149
|
-
});
|
|
2150
|
-
request.on('error', reject);
|
|
2151
|
-
request.on('timeout', () => {
|
|
2152
|
-
request.destroy(new Error('钉钉 Access Token 获取超时'));
|
|
2153
|
-
});
|
|
2154
|
-
request.write(body);
|
|
2155
|
-
request.end();
|
|
2156
|
-
} catch (error) {
|
|
2157
|
-
reject(error);
|
|
2158
|
-
}
|
|
2159
|
-
});
|
|
2160
|
-
}
|
|
2161
|
-
|
|
2162
|
-
function buildTestNotificationContext() {
|
|
2163
|
-
return {
|
|
2164
|
-
title: 'Coding Tool - 测试通知',
|
|
2165
|
-
message: '这是一条测试通知',
|
|
2166
|
-
source: 'test',
|
|
2167
|
-
eventType: 'test',
|
|
2168
|
-
timestamp: new Date().toLocaleString('zh-CN'),
|
|
2169
|
-
hostname: os.hostname()
|
|
2170
|
-
};
|
|
2171
|
-
}
|
|
2172
|
-
|
|
2173
|
-
function formatProviderText(ctx) {
|
|
2174
|
-
return [
|
|
2175
|
-
ctx.title,
|
|
2176
|
-
`来源: ${ctx.source}`,
|
|
2177
|
-
`状态: ${ctx.message}`,
|
|
2178
|
-
`时间: ${ctx.timestamp}`,
|
|
2179
|
-
`设备: ${ctx.hostname}`
|
|
2180
|
-
].join('\n');
|
|
2181
|
-
}
|
|
2182
|
-
|
|
2183
|
-
async function sendRemoteProviderTest(providerInput = {}) {
|
|
2184
|
-
const provider = normalizeRemoteProvider(providerInput);
|
|
2185
|
-
if (!provider) {
|
|
2186
|
-
throw createValidationError('远程通知渠道类型不正确');
|
|
2187
|
-
}
|
|
2188
|
-
validateRemoteProviderConfig({ ...provider, enabled: true });
|
|
2189
|
-
|
|
2190
|
-
const config = provider.config || {};
|
|
2191
|
-
const ctx = buildTestNotificationContext();
|
|
2192
|
-
switch (provider.type) {
|
|
2193
|
-
case 'feishuBot':
|
|
2194
|
-
return sendFeishuTest(config.webhookUrl);
|
|
2195
|
-
case 'qqBot': {
|
|
2196
|
-
const pathSuffix = config.targetType === 'group' ? '/send_group_msg' : '/send_private_msg';
|
|
2197
|
-
return requestJson(String(config.endpoint).replace(/\/$/, '') + pathSuffix, {
|
|
2198
|
-
[config.targetType === 'group' ? 'group_id' : 'user_id']: config.targetId,
|
|
2199
|
-
message: formatProviderText(ctx)
|
|
2200
|
-
}, config.accessToken ? { Authorization: `Bearer ${config.accessToken}` } : {});
|
|
2201
|
-
}
|
|
2202
|
-
case 'wecomBot':
|
|
2203
|
-
return requestJson(config.webhookUrl, {
|
|
2204
|
-
msgtype: 'markdown',
|
|
2205
|
-
markdown: { content: `**${ctx.title}**\n> 状态: ${ctx.message}\n> 时间: ${ctx.timestamp}` }
|
|
2206
|
-
});
|
|
2207
|
-
case 'dingtalkBot':
|
|
2208
|
-
if (config.mode === 'app') {
|
|
2209
|
-
return sendDingTalkAppProviderTest(config, ctx);
|
|
2210
|
-
}
|
|
2211
|
-
return requestJson(config.webhookUrl, {
|
|
2212
|
-
msgtype: 'markdown',
|
|
2213
|
-
markdown: { title: ctx.title, text: `### ${ctx.title}\n\n- 状态: ${ctx.message}\n- 时间: ${ctx.timestamp}` }
|
|
2214
|
-
});
|
|
2215
|
-
case 'telegramBot':
|
|
2216
|
-
return requestTelegramMessage(config, ctx);
|
|
2217
|
-
case 'wechatBot':
|
|
2218
|
-
if (!config.botToken && config.tokenFile) {
|
|
2219
|
-
try {
|
|
2220
|
-
const raw = fs.readFileSync(config.tokenFile, 'utf8');
|
|
2221
|
-
const parsed = JSON.parse(raw);
|
|
2222
|
-
config.botToken = parsed.bot_token || parsed.token || '';
|
|
2223
|
-
} catch (error) {
|
|
2224
|
-
throw createValidationError('无法读取微信 token.json');
|
|
2225
|
-
}
|
|
2226
|
-
}
|
|
2227
|
-
requireConfigValue(config, 'botToken', '微信 Bot Token');
|
|
2228
|
-
return requestJson('https://ilinkai.weixin.qq.com/ilink/bot/sendmessage', {
|
|
2229
|
-
msg: {
|
|
2230
|
-
from_user_id: '',
|
|
2231
|
-
to_user_id: config.targetUserId,
|
|
2232
|
-
client_id: createClientId('coding-tool'),
|
|
2233
|
-
message_type: 2,
|
|
2234
|
-
message_state: 2,
|
|
2235
|
-
item_list: [{ type: 1, text_item: { text: formatProviderText(ctx) } }],
|
|
2236
|
-
...(config.contextToken ? { context_token: config.contextToken } : {})
|
|
2237
|
-
},
|
|
2238
|
-
base_info: { channel_version: '2.1.8' }
|
|
2239
|
-
}, {
|
|
2240
|
-
AuthorizationType: 'ilink_bot_token',
|
|
2241
|
-
Authorization: `Bearer ${config.botToken}`,
|
|
2242
|
-
'X-WECHAT-UIN': Buffer.from(String(Math.floor(Math.random() * 0xffffffff))).toString('base64')
|
|
2243
|
-
});
|
|
2244
|
-
default:
|
|
2245
|
-
throw createValidationError('不支持的远程通知渠道');
|
|
2246
|
-
}
|
|
2247
|
-
}
|
|
2248
|
-
|
|
2249
|
-
function requestTelegramMessage(config = {}, ctx) {
|
|
2250
|
-
const url = `https://api.telegram.org/bot${config.botToken}/sendMessage`;
|
|
2251
|
-
const payload = {
|
|
2252
|
-
chat_id: config.chatId,
|
|
2253
|
-
text: formatProviderText(ctx)
|
|
2254
|
-
};
|
|
2255
|
-
const agent = config.proxy ? new HttpsProxyAgent(config.proxy) : null;
|
|
2256
|
-
return requestJson(url, payload, {}, agent ? { agent } : {});
|
|
2257
|
-
}
|
|
2258
|
-
|
|
2259
|
-
async function sendDingTalkAppProviderTest(config = {}, ctx) {
|
|
2260
|
-
const token = await requestDingTalkAccessToken(config);
|
|
2261
|
-
if (!token) {
|
|
2262
|
-
throw createValidationError('钉钉 App Access Token 获取失败');
|
|
2263
|
-
}
|
|
2264
|
-
const isGroup = config.targetType !== 'user';
|
|
2265
|
-
const payload = {
|
|
2266
|
-
robotCode: config.clientId,
|
|
2267
|
-
msgKey: 'sampleMarkdown',
|
|
2268
|
-
msgParam: JSON.stringify({
|
|
2269
|
-
title: ctx.title,
|
|
2270
|
-
text: `### ${ctx.title}\n\n- 状态: ${ctx.message}\n- 时间: ${ctx.timestamp}\n- 设备: ${ctx.hostname}`
|
|
2271
|
-
})
|
|
2272
|
-
};
|
|
2273
|
-
if (isGroup) {
|
|
2274
|
-
payload.openConversationId = config.targetId;
|
|
2275
|
-
} else {
|
|
2276
|
-
payload.userIds = [config.targetId];
|
|
2277
|
-
}
|
|
2278
|
-
return requestJson(
|
|
2279
|
-
'https://api.dingtalk.com/v1.0/robot/' + (isGroup ? 'groupMessages/send' : 'oToMessages/batchSend'),
|
|
2280
|
-
payload,
|
|
2281
|
-
{ 'x-acs-dingtalk-access-token': token }
|
|
2282
|
-
);
|
|
2283
|
-
}
|
|
2284
1448
|
|
|
2285
1449
|
function generateSystemNotificationCommand(type, message, platformOverride = os.platform()) {
|
|
2286
1450
|
const normalizedType = normalizeType(type);
|
|
@@ -2375,14 +1539,14 @@ function syncManagedNotificationAssets() {
|
|
|
2375
1539
|
return settings;
|
|
2376
1540
|
}
|
|
2377
1541
|
|
|
2378
|
-
function testNotification({ type, provider } = {}) {
|
|
1542
|
+
function testNotification({ type, provider, source = 'claude' } = {}) {
|
|
2379
1543
|
if (provider) {
|
|
2380
1544
|
return sendRemoteProviderTest(provider);
|
|
2381
1545
|
}
|
|
2382
1546
|
|
|
2383
1547
|
if (normalizeType(type) === 'browser') {
|
|
2384
1548
|
emitBrowserNotification({
|
|
2385
|
-
source
|
|
1549
|
+
source,
|
|
2386
1550
|
title: 'coding-tool-x',
|
|
2387
1551
|
message: '这是一条浏览器测试通知'
|
|
2388
1552
|
});
|
|
@@ -2412,6 +1576,9 @@ module.exports = {
|
|
|
2412
1576
|
normalizeRemoteNotificationsConfig,
|
|
2413
1577
|
validateRemoteProviderConfig,
|
|
2414
1578
|
_test: {
|
|
1579
|
+
getHookPlatformEntries,
|
|
1580
|
+
isConfiguredCliPlatformEnabled,
|
|
1581
|
+
toPublicHookDefinition,
|
|
2415
1582
|
applyClaudeDisablePreference,
|
|
2416
1583
|
getManagedCommandType,
|
|
2417
1584
|
parseManagedType,
|