@akon-labs/nexus-cli 0.1.9 → 0.1.11-rc.2
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 +21 -0
- package/LICENSE +1 -0
- package/README.md +3 -2
- package/dist/app-bridge.bundle.js +67 -0
- package/dist/cli/require-login.d.ts.map +1 -1
- package/dist/cli/require-login.js +2 -2
- package/dist/cli/require-login.js.map +1 -1
- package/dist/cli.js +20866 -1882
- package/dist/core/agent-session.d.ts +3 -1
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +5 -1
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/auto-model.d.ts +1 -1
- package/dist/core/auto-model.d.ts.map +1 -1
- package/dist/core/auto-model.js +1 -1
- package/dist/core/auto-model.js.map +1 -1
- package/dist/core/extensions/index.d.ts +1 -1
- package/dist/core/extensions/index.d.ts.map +1 -1
- package/dist/core/extensions/index.js.map +1 -1
- package/dist/core/extensions/loader.d.ts.map +1 -1
- package/dist/core/extensions/loader.js +6 -0
- package/dist/core/extensions/loader.js.map +1 -1
- package/dist/core/extensions/runner.d.ts +7 -1
- package/dist/core/extensions/runner.d.ts.map +1 -1
- package/dist/core/extensions/runner.js +14 -0
- package/dist/core/extensions/runner.js.map +1 -1
- package/dist/core/extensions/types.d.ts +18 -0
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/model-resolver.d.ts +2 -2
- package/dist/core/model-resolver.d.ts.map +1 -1
- package/dist/core/model-resolver.js +3 -3
- package/dist/core/model-resolver.js.map +1 -1
- package/dist/core/package-manager.d.ts +15 -0
- package/dist/core/package-manager.d.ts.map +1 -1
- package/dist/core/package-manager.js +50 -0
- package/dist/core/package-manager.js.map +1 -1
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +1 -1
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/slash-commands.d.ts.map +1 -1
- package/dist/core/slash-commands.js +3 -3
- package/dist/core/slash-commands.js.map +1 -1
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +1 -1
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/core/tools/find.d.ts.map +1 -1
- package/dist/core/tools/find.js +1 -1
- package/dist/core/tools/find.js.map +1 -1
- package/dist/core/tools/grep.d.ts.map +1 -1
- package/dist/core/tools/grep.js +1 -1
- package/dist/core/tools/grep.js.map +1 -1
- package/dist/core/tools/index.d.ts +1 -1
- package/dist/core/tools/index.d.ts.map +1 -1
- package/dist/core/tools/index.js +5 -5
- package/dist/core/tools/index.js.map +1 -1
- package/dist/core/tools/websearch.d.ts.map +1 -1
- package/dist/core/tools/websearch.js +3 -3
- package/dist/core/tools/websearch.js.map +1 -1
- package/dist/extensions/gitnexus/config.d.ts +15 -1
- package/dist/extensions/gitnexus/config.d.ts.map +1 -1
- package/dist/extensions/gitnexus/config.js +86 -2
- package/dist/extensions/gitnexus/config.js.map +1 -1
- package/dist/extensions/gitnexus/gateway.d.ts +6 -6
- package/dist/extensions/gitnexus/gateway.d.ts.map +1 -1
- package/dist/extensions/gitnexus/gateway.js +4 -4
- package/dist/extensions/gitnexus/gateway.js.map +1 -1
- package/dist/extensions/gitnexus/index.d.ts.map +1 -1
- package/dist/extensions/gitnexus/index.js +6 -4
- package/dist/extensions/gitnexus/index.js.map +1 -1
- package/dist/extensions/gitnexus/observatory.d.ts.map +1 -1
- package/dist/extensions/gitnexus/observatory.js +2 -2
- package/dist/extensions/gitnexus/observatory.js.map +1 -1
- package/dist/extensions/index.d.ts.map +1 -1
- package/dist/extensions/index.js +3 -0
- package/dist/extensions/index.js.map +1 -1
- package/dist/extensions/mcp/abort.d.ts +3 -0
- package/dist/extensions/mcp/abort.d.ts.map +1 -0
- package/dist/extensions/mcp/abort.js +41 -0
- package/dist/extensions/mcp/abort.js.map +1 -0
- package/dist/extensions/mcp/agent-dir.d.ts +6 -0
- package/dist/extensions/mcp/agent-dir.d.ts.map +1 -0
- package/dist/extensions/mcp/agent-dir.js +18 -0
- package/dist/extensions/mcp/agent-dir.js.map +1 -0
- package/dist/extensions/mcp/agent-plugin-loader.d.ts +9 -0
- package/dist/extensions/mcp/agent-plugin-loader.d.ts.map +1 -0
- package/dist/extensions/mcp/agent-plugin-loader.js +384 -0
- package/dist/extensions/mcp/agent-plugin-loader.js.map +1 -0
- package/dist/extensions/mcp/app-bridge.bundle.js +67 -0
- package/dist/extensions/mcp/commands.d.ts +47 -0
- package/dist/extensions/mcp/commands.d.ts.map +1 -0
- package/dist/extensions/mcp/commands.js +821 -0
- package/dist/extensions/mcp/commands.js.map +1 -0
- package/dist/extensions/mcp/config.d.ts +123 -38
- package/dist/extensions/mcp/config.d.ts.map +1 -1
- package/dist/extensions/mcp/config.js +1035 -70
- package/dist/extensions/mcp/config.js.map +1 -1
- package/dist/extensions/mcp/consent-manager.d.ts +14 -0
- package/dist/extensions/mcp/consent-manager.d.ts.map +1 -0
- package/dist/extensions/mcp/consent-manager.js +60 -0
- package/dist/extensions/mcp/consent-manager.js.map +1 -0
- package/dist/extensions/mcp/direct-tools.d.ts +11 -0
- package/dist/extensions/mcp/direct-tools.d.ts.map +1 -0
- package/dist/extensions/mcp/direct-tools.js +562 -0
- package/dist/extensions/mcp/direct-tools.js.map +1 -0
- package/dist/extensions/mcp/elicitation-handler.d.ts +18 -0
- package/dist/extensions/mcp/elicitation-handler.d.ts.map +1 -0
- package/dist/extensions/mcp/elicitation-handler.js +320 -0
- package/dist/extensions/mcp/elicitation-handler.js.map +1 -0
- package/dist/extensions/mcp/error-signal.d.ts +16 -0
- package/dist/extensions/mcp/error-signal.d.ts.map +1 -0
- package/dist/extensions/mcp/error-signal.js +22 -0
- package/dist/extensions/mcp/error-signal.js.map +1 -0
- package/dist/extensions/mcp/errors.d.ts +104 -0
- package/dist/extensions/mcp/errors.d.ts.map +1 -0
- package/dist/extensions/mcp/errors.js +171 -0
- package/dist/extensions/mcp/errors.js.map +1 -0
- package/dist/extensions/mcp/glimpse-ui.d.ts +10 -0
- package/dist/extensions/mcp/glimpse-ui.d.ts.map +1 -0
- package/dist/extensions/mcp/glimpse-ui.js +67 -0
- package/dist/extensions/mcp/glimpse-ui.js.map +1 -0
- package/dist/extensions/mcp/host-html-template.d.ts +17 -0
- package/dist/extensions/mcp/host-html-template.d.ts.map +1 -0
- package/dist/extensions/mcp/host-html-template.js +442 -0
- package/dist/extensions/mcp/host-html-template.js.map +1 -0
- package/dist/extensions/mcp/index.d.ts +20 -15
- package/dist/extensions/mcp/index.d.ts.map +1 -1
- package/dist/extensions/mcp/index.js +1113 -82
- package/dist/extensions/mcp/index.js.map +1 -1
- package/dist/extensions/mcp/init.d.ts +28 -0
- package/dist/extensions/mcp/init.d.ts.map +1 -0
- package/dist/extensions/mcp/init.js +631 -0
- package/dist/extensions/mcp/init.js.map +1 -0
- package/dist/extensions/mcp/json-schema-validator.d.ts +3 -0
- package/dist/extensions/mcp/json-schema-validator.d.ts.map +1 -0
- package/dist/extensions/mcp/json-schema-validator.js +53 -0
- package/dist/extensions/mcp/json-schema-validator.js.map +1 -0
- package/dist/extensions/mcp/lifecycle.d.ts +56 -0
- package/dist/extensions/mcp/lifecycle.d.ts.map +1 -0
- package/dist/extensions/mcp/lifecycle.js +395 -0
- package/dist/extensions/mcp/lifecycle.js.map +1 -0
- package/dist/extensions/mcp/logger.d.ts +52 -0
- package/dist/extensions/mcp/logger.d.ts.map +1 -0
- package/dist/extensions/mcp/logger.js +134 -0
- package/dist/extensions/mcp/logger.js.map +1 -0
- package/dist/extensions/mcp/mcp-auth-flow.d.ts +117 -0
- package/dist/extensions/mcp/mcp-auth-flow.d.ts.map +1 -0
- package/dist/extensions/mcp/mcp-auth-flow.js +869 -0
- package/dist/extensions/mcp/mcp-auth-flow.js.map +1 -0
- package/dist/extensions/mcp/mcp-auth.d.ts +162 -0
- package/dist/extensions/mcp/mcp-auth.d.ts.map +1 -0
- package/dist/extensions/mcp/mcp-auth.js +897 -0
- package/dist/extensions/mcp/mcp-auth.js.map +1 -0
- package/dist/extensions/mcp/mcp-bearer-store.d.ts +25 -0
- package/dist/extensions/mcp/mcp-bearer-store.d.ts.map +1 -0
- package/dist/extensions/mcp/mcp-bearer-store.js +350 -0
- package/dist/extensions/mcp/mcp-bearer-store.js.map +1 -0
- package/dist/extensions/mcp/mcp-callback-server.d.ts +54 -0
- package/dist/extensions/mcp/mcp-callback-server.d.ts.map +1 -0
- package/dist/extensions/mcp/mcp-callback-server.js +454 -0
- package/dist/extensions/mcp/mcp-callback-server.js.map +1 -0
- package/dist/extensions/mcp/mcp-code.d.ts +48 -0
- package/dist/extensions/mcp/mcp-code.d.ts.map +1 -0
- package/dist/extensions/mcp/mcp-code.js +358 -0
- package/dist/extensions/mcp/mcp-code.js.map +1 -0
- package/dist/extensions/mcp/mcp-keyring-helper.cjs +89 -0
- package/dist/extensions/mcp/mcp-oauth-provider.d.ts +135 -0
- package/dist/extensions/mcp/mcp-oauth-provider.d.ts.map +1 -0
- package/dist/extensions/mcp/mcp-oauth-provider.js +491 -0
- package/dist/extensions/mcp/mcp-oauth-provider.js.map +1 -0
- package/dist/extensions/mcp/mcp-output-guard.d.ts +59 -0
- package/dist/extensions/mcp/mcp-output-guard.d.ts.map +1 -0
- package/dist/extensions/mcp/mcp-output-guard.js +343 -0
- package/dist/extensions/mcp/mcp-output-guard.js.map +1 -0
- package/dist/extensions/mcp/mcp-panel.d.ts +71 -0
- package/dist/extensions/mcp/mcp-panel.d.ts.map +1 -0
- package/dist/extensions/mcp/mcp-panel.js +973 -0
- package/dist/extensions/mcp/mcp-panel.js.map +1 -0
- package/dist/extensions/mcp/mcp-probe.d.ts +7 -0
- package/dist/extensions/mcp/mcp-probe.d.ts.map +1 -0
- package/dist/extensions/mcp/mcp-probe.js +155 -0
- package/dist/extensions/mcp/mcp-probe.js.map +1 -0
- package/dist/extensions/mcp/mcp-script-worker.mjs +130 -0
- package/dist/extensions/mcp/mcp-setup-panel.d.ts +146 -0
- package/dist/extensions/mcp/mcp-setup-panel.d.ts.map +1 -0
- package/dist/extensions/mcp/mcp-setup-panel.js +1104 -0
- package/dist/extensions/mcp/mcp-setup-panel.js.map +1 -0
- package/dist/extensions/mcp/mcp-status.d.ts +12 -0
- package/dist/extensions/mcp/mcp-status.d.ts.map +1 -0
- package/dist/extensions/mcp/mcp-status.js +98 -0
- package/dist/extensions/mcp/mcp-status.js.map +1 -0
- package/dist/extensions/mcp/mcp-trace.d.ts +95 -0
- package/dist/extensions/mcp/mcp-trace.d.ts.map +1 -0
- package/dist/extensions/mcp/mcp-trace.js +252 -0
- package/dist/extensions/mcp/mcp-trace.js.map +1 -0
- package/dist/extensions/mcp/metadata-cache.d.ts +20 -0
- package/dist/extensions/mcp/metadata-cache.d.ts.map +1 -0
- package/dist/extensions/mcp/metadata-cache.js +318 -0
- package/dist/extensions/mcp/metadata-cache.js.map +1 -0
- package/dist/extensions/mcp/npx-resolver.d.ts +7 -0
- package/dist/extensions/mcp/npx-resolver.d.ts.map +1 -0
- package/dist/extensions/mcp/npx-resolver.js +505 -0
- package/dist/extensions/mcp/npx-resolver.js.map +1 -0
- package/dist/extensions/mcp/oauth.d.ts +21 -0
- package/dist/extensions/mcp/oauth.d.ts.map +1 -0
- package/dist/extensions/mcp/oauth.js +15 -0
- package/dist/extensions/mcp/oauth.js.map +1 -0
- package/dist/extensions/mcp/onboarding-state.d.ts +13 -0
- package/dist/extensions/mcp/onboarding-state.d.ts.map +1 -0
- package/dist/extensions/mcp/onboarding-state.js +65 -0
- package/dist/extensions/mcp/onboarding-state.js.map +1 -0
- package/dist/extensions/mcp/package-mcp-loader.d.ts +3 -0
- package/dist/extensions/mcp/package-mcp-loader.d.ts.map +1 -0
- package/dist/extensions/mcp/package-mcp-loader.js +170 -0
- package/dist/extensions/mcp/package-mcp-loader.js.map +1 -0
- package/dist/extensions/mcp/panel-keys.d.ts +21 -0
- package/dist/extensions/mcp/panel-keys.d.ts.map +1 -0
- package/dist/extensions/mcp/panel-keys.js +29 -0
- package/dist/extensions/mcp/panel-keys.js.map +1 -0
- package/dist/extensions/mcp/prompts.d.ts +63 -0
- package/dist/extensions/mcp/prompts.d.ts.map +1 -0
- package/dist/extensions/mcp/prompts.js +297 -0
- package/dist/extensions/mcp/prompts.js.map +1 -0
- package/dist/extensions/mcp/proxy-modes.d.ts +15 -0
- package/dist/extensions/mcp/proxy-modes.d.ts.map +1 -0
- package/dist/extensions/mcp/proxy-modes.js +1421 -0
- package/dist/extensions/mcp/proxy-modes.js.map +1 -0
- package/dist/extensions/mcp/request-headers-command.d.ts +11 -0
- package/dist/extensions/mcp/request-headers-command.d.ts.map +1 -0
- package/dist/extensions/mcp/request-headers-command.js +334 -0
- package/dist/extensions/mcp/request-headers-command.js.map +1 -0
- package/dist/extensions/mcp/resource-tools.d.ts +2 -0
- package/dist/extensions/mcp/resource-tools.d.ts.map +1 -0
- package/dist/extensions/mcp/resource-tools.js +15 -0
- package/dist/extensions/mcp/resource-tools.js.map +1 -0
- package/dist/extensions/mcp/runtime-owner.d.ts +14 -0
- package/dist/extensions/mcp/runtime-owner.d.ts.map +1 -0
- package/dist/extensions/mcp/runtime-owner.js +84 -0
- package/dist/extensions/mcp/runtime-owner.js.map +1 -0
- package/dist/extensions/mcp/sampling-handler.d.ts +18 -0
- package/dist/extensions/mcp/sampling-handler.d.ts.map +1 -0
- package/dist/extensions/mcp/sampling-handler.js +229 -0
- package/dist/extensions/mcp/sampling-handler.js.map +1 -0
- package/dist/extensions/mcp/search-ranking.d.ts +25 -0
- package/dist/extensions/mcp/search-ranking.d.ts.map +1 -0
- package/dist/extensions/mcp/search-ranking.js +234 -0
- package/dist/extensions/mcp/search-ranking.js.map +1 -0
- package/dist/extensions/mcp/server-manager.d.ts +116 -0
- package/dist/extensions/mcp/server-manager.d.ts.map +1 -0
- package/dist/extensions/mcp/server-manager.js +1052 -0
- package/dist/extensions/mcp/server-manager.js.map +1 -0
- package/dist/extensions/mcp/session-recovery.d.ts +27 -0
- package/dist/extensions/mcp/session-recovery.d.ts.map +1 -0
- package/dist/extensions/mcp/session-recovery.js +140 -0
- package/dist/extensions/mcp/session-recovery.js.map +1 -0
- package/dist/extensions/mcp/skills/mcp-scripting/SKILL.md +38 -0
- package/dist/extensions/mcp/state.d.ts +62 -0
- package/dist/extensions/mcp/state.d.ts.map +1 -0
- package/dist/extensions/mcp/state.js +2 -0
- package/dist/extensions/mcp/state.js.map +1 -0
- package/dist/extensions/mcp/tool-approval.d.ts +11 -0
- package/dist/extensions/mcp/tool-approval.d.ts.map +1 -0
- package/dist/extensions/mcp/tool-approval.js +156 -0
- package/dist/extensions/mcp/tool-approval.js.map +1 -0
- package/dist/extensions/mcp/tool-metadata.d.ts +11 -0
- package/dist/extensions/mcp/tool-metadata.d.ts.map +1 -0
- package/dist/extensions/mcp/tool-metadata.js +264 -0
- package/dist/extensions/mcp/tool-metadata.js.map +1 -0
- package/dist/extensions/mcp/tool-registrar.d.ts +21 -0
- package/dist/extensions/mcp/tool-registrar.d.ts.map +1 -0
- package/dist/extensions/mcp/tool-registrar.js +229 -0
- package/dist/extensions/mcp/tool-registrar.js.map +1 -0
- package/dist/extensions/mcp/tool-result-renderer.d.ts +83 -0
- package/dist/extensions/mcp/tool-result-renderer.d.ts.map +1 -0
- package/dist/extensions/mcp/tool-result-renderer.js +345 -0
- package/dist/extensions/mcp/tool-result-renderer.js.map +1 -0
- package/dist/extensions/mcp/ts-shape.d.ts +3 -0
- package/dist/extensions/mcp/ts-shape.d.ts.map +1 -0
- package/dist/extensions/mcp/ts-shape.js +160 -0
- package/dist/extensions/mcp/ts-shape.js.map +1 -0
- package/dist/extensions/mcp/types.d.ts +521 -0
- package/dist/extensions/mcp/types.d.ts.map +1 -0
- package/dist/extensions/mcp/types.js +294 -0
- package/dist/extensions/mcp/types.js.map +1 -0
- package/dist/extensions/mcp/ui-app-bridge-helpers.d.ts +7 -0
- package/dist/extensions/mcp/ui-app-bridge-helpers.d.ts.map +1 -0
- package/dist/extensions/mcp/ui-app-bridge-helpers.js +37 -0
- package/dist/extensions/mcp/ui-app-bridge-helpers.js.map +1 -0
- package/dist/extensions/mcp/ui-resource-handler.d.ts +18 -0
- package/dist/extensions/mcp/ui-resource-handler.d.ts.map +1 -0
- package/dist/extensions/mcp/ui-resource-handler.js +210 -0
- package/dist/extensions/mcp/ui-resource-handler.js.map +1 -0
- package/dist/extensions/mcp/ui-server.d.ts +52 -0
- package/dist/extensions/mcp/ui-server.d.ts.map +1 -0
- package/dist/extensions/mcp/ui-server.js +740 -0
- package/dist/extensions/mcp/ui-server.js.map +1 -0
- package/dist/extensions/mcp/ui-session.d.ts +40 -0
- package/dist/extensions/mcp/ui-session.d.ts.map +1 -0
- package/dist/extensions/mcp/ui-session.js +477 -0
- package/dist/extensions/mcp/ui-session.js.map +1 -0
- package/dist/extensions/mcp/ui-stream-types.d.ts +105 -0
- package/dist/extensions/mcp/ui-stream-types.d.ts.map +1 -0
- package/dist/extensions/mcp/ui-stream-types.js +61 -0
- package/dist/extensions/mcp/ui-stream-types.js.map +1 -0
- package/dist/extensions/mcp/ui-tool-visibility.d.ts +5 -0
- package/dist/extensions/mcp/ui-tool-visibility.d.ts.map +1 -0
- package/dist/extensions/mcp/ui-tool-visibility.js +27 -0
- package/dist/extensions/mcp/ui-tool-visibility.js.map +1 -0
- package/dist/extensions/mcp/unix-socket-transport.d.ts +15 -0
- package/dist/extensions/mcp/unix-socket-transport.d.ts.map +1 -0
- package/dist/extensions/mcp/unix-socket-transport.js +85 -0
- package/dist/extensions/mcp/unix-socket-transport.js.map +1 -0
- package/dist/extensions/mcp/utils.d.ts +32 -0
- package/dist/extensions/mcp/utils.d.ts.map +1 -0
- package/dist/extensions/mcp/utils.js +336 -0
- package/dist/extensions/mcp/utils.js.map +1 -0
- package/dist/extensions/subagent/index.d.ts +62 -1
- package/dist/extensions/subagent/index.d.ts.map +1 -1
- package/dist/extensions/subagent/index.js +195 -9
- package/dist/extensions/subagent/index.js.map +1 -1
- package/dist/extensions/subagent-render.d.ts +3 -0
- package/dist/extensions/subagent-render.d.ts.map +1 -0
- package/dist/extensions/subagent-render.js +39 -0
- package/dist/extensions/subagent-render.js.map +1 -0
- package/dist/extensions/web-access.d.ts.map +1 -1
- package/dist/extensions/web-access.js +1 -1
- package/dist/extensions/web-access.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +20992 -2003
- package/dist/index.js.map +1 -1
- package/dist/mcp-keyring-helper.cjs +89 -0
- package/dist/mcp-script-worker.mjs +130 -0
- package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/model-selector.js +1 -1
- package/dist/modes/interactive/components/model-selector.js.map +1 -1
- package/dist/modes/interactive/components/response-separator.d.ts +1 -1
- package/dist/modes/interactive/components/response-separator.d.ts.map +1 -1
- package/dist/modes/interactive/components/response-separator.js +1 -1
- package/dist/modes/interactive/components/response-separator.js.map +1 -1
- package/dist/modes/interactive/components/tool-execution.d.ts +3 -2
- package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/tool-execution.js +12 -2
- package/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +2 -0
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +14 -8
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/rpc-entry.js +20866 -1882
- package/dist/skills/mcp-scripting/SKILL.md +38 -0
- package/dist/utils/hidden-spawn.d.ts +29 -0
- package/dist/utils/hidden-spawn.d.ts.map +1 -0
- package/dist/utils/hidden-spawn.js +101 -0
- package/dist/utils/hidden-spawn.js.map +1 -0
- package/dist/vendor/ai.js +110 -35
- package/docs/mcp.md +178 -0
- package/docs/pi-subagents.md +75 -0
- package/docs/quickstart.md +1 -1
- package/docs/usage.md +2 -1
- package/examples/extensions/pi-subagents-worker-policy.ts +86 -0
- package/native/win32/build.mjs +95 -0
- package/native/win32/prebuilds/win32-x64/hidden-spawn.exe +0 -0
- package/native/win32/src/hidden-spawn.c +157 -0
- package/npm-shrinkwrap.json +728 -6
- package/package.json +21 -6
- package/dist/extensions/mcp/client.d.ts +0 -86
- package/dist/extensions/mcp/client.d.ts.map +0 -1
- package/dist/extensions/mcp/client.js +0 -275
- package/dist/extensions/mcp/client.js.map +0 -1
|
@@ -4,11 +4,11 @@ import { join } from "node:path";
|
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
import { graphRailStore } from "../../core/graph-rail-store.js";
|
|
6
6
|
import { openBrowser } from "../../utils/open-browser.js";
|
|
7
|
-
import { isDelegatedWorkerProcess, registerDelegateTaskTool } from "../subagent/index.js";
|
|
7
|
+
import { applyNexusSubagentEnvAliases, isDelegatedWorkerProcess, isExternalDelegationMode, registerDelegateTaskTool, } from "../subagent/index.js";
|
|
8
8
|
import { registerAugmentedGrep } from "./augment.js";
|
|
9
9
|
import { registerChangeTracking } from "./changes.js";
|
|
10
10
|
import { GitNexusClient, GitNexusClientError } from "./client.js";
|
|
11
|
-
import {
|
|
11
|
+
import { resolveGitNexusConfig, resolveHubUrl, saveGitNexusConfig } from "./config.js";
|
|
12
12
|
import { defaultDeviceName, deviceFingerprint, pollDeviceAuth, startDeviceAuth } from "./device-auth.js";
|
|
13
13
|
import { formatCreditBalance, readGatewayCache, refreshGatewayPolicy, registerAutoProvider } from "./gateway.js";
|
|
14
14
|
import { spawnObservatoryFetch } from "./observatory.js";
|
|
@@ -42,15 +42,17 @@ export default function gitnexusExtension(pi) {
|
|
|
42
42
|
let startupNotice;
|
|
43
43
|
let loginRequired = false;
|
|
44
44
|
const cwd = process.cwd();
|
|
45
|
-
//
|
|
45
|
+
// Nexus Agent must also register at factory time: the initial model is
|
|
46
46
|
// resolved before session_start fires, and a provider that appears after
|
|
47
47
|
// that is invisible to it. Reads the policy cache the startup gate wrote.
|
|
48
|
+
applyNexusSubagentEnvAliases();
|
|
48
49
|
let gatewayPolicy = readGatewayCache();
|
|
49
50
|
const initialConfig = resolveGitNexusConfig();
|
|
50
51
|
registerAutoProvider(pi, initialConfig, gatewayPolicy);
|
|
51
52
|
function registerDelegationIfAvailable() {
|
|
52
53
|
if (delegationRegistered ||
|
|
53
54
|
isDelegatedWorkerProcess() ||
|
|
55
|
+
isExternalDelegationMode() ||
|
|
54
56
|
!resolveGitNexusConfig() ||
|
|
55
57
|
!gatewayPolicy?.delegation) {
|
|
56
58
|
return;
|
|
@@ -162,7 +164,7 @@ export default function gitnexusExtension(pi) {
|
|
|
162
164
|
pi.registerCommand("gitnexus-login", {
|
|
163
165
|
description: "Sign in to GitNexus (implementation of /login → GitNexus account; accepts a pasted token)",
|
|
164
166
|
handler: async (args, ctx) => {
|
|
165
|
-
const hubUrl =
|
|
167
|
+
const hubUrl = resolveHubUrl();
|
|
166
168
|
const pasted = args.trim();
|
|
167
169
|
// Pasting a token stays supported for CI and headless installs.
|
|
168
170
|
if (pasted) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/extensions/gitnexus/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,OAAO,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AAChE,OAAO,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAC;AAC1D,OAAO,EAAE,wBAAwB,EAAE,wBAAwB,EAAE,MAAM,sBAAsB,CAAC;AAC1F,OAAO,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAC;AACtD,OAAO,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAClE,OAAO,EAAE,eAAe,EAAE,qBAAqB,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACzF,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AACzG,OAAO,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AACjH,OAAO,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AACzD,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,EACN,aAAa,EAEb,gBAAgB,EAChB,kBAAkB,GAElB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAEjD,2EAA2E;AAC3E,MAAM,UAAU,wBAAwB,CACvC,SAAS,GAAW,OAAO,IAAI,CAAC,GAAG,EACnC,UAAU,GAA8B,UAAU,EAC7B;IACrB,MAAM,UAAU,GAAG;QAClB,aAAa,CAAC,IAAI,GAAG,CAAC,WAAW,EAAE,SAAS,CAAC,CAAC;QAC9C,aAAa,CAAC,IAAI,GAAG,CAAC,+BAA+B,EAAE,SAAS,CAAC,CAAC;KAClE,CAAC;IACF,OAAO,UAAU,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;AAAA,CACnC;AAED;;;;;;;;GAQG;AACH,MAAM,CAAC,OAAO,UAAU,iBAAiB,CAAC,EAAgB,EAAQ;IACjE,IAAI,SAAoC,CAAC;IACzC,IAAI,OAAgC,CAAC;IACrC,IAAI,YAAwC,CAAC;IAC7C,IAAI,eAAe,GAAG,KAAK,CAAC;IAC5B,IAAI,oBAAoB,GAAG,KAAK,CAAC;IACjC,IAAI,aAAiC,CAAC;IACtC,IAAI,aAAa,GAAG,KAAK,CAAC;IAE1B,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;IAE1B,qEAAqE;IACrE,yEAAyE;IACzE,0EAA0E;IAC1E,IAAI,aAAa,GAAG,gBAAgB,EAAE,CAAC;IACvC,MAAM,aAAa,GAAG,qBAAqB,EAAE,CAAC;IAC9C,oBAAoB,CAAC,EAAE,EAAE,aAAa,EAAE,aAAa,CAAC,CAAC;IAEvD,SAAS,6BAA6B,GAAS;QAC9C,IACC,oBAAoB;YACpB,wBAAwB,EAAE;YAC1B,CAAC,qBAAqB,EAAE;YACxB,CAAC,aAAa,EAAE,UAAU,EACzB,CAAC;YACF,OAAO;QACR,CAAC;QACD,wBAAwB,CAAC,EAAE,EAAE,aAAa,CAAC,UAAU,CAAC,CAAC;QACvD,oBAAoB,GAAG,IAAI,CAAC;IAAA,CAC5B;IAED,6BAA6B,EAAE,CAAC;IAEhC,SAAS,kBAAkB,CAAC,GAAuE,EAAQ;QAC1G,GAAG,CAAC,EAAE,CAAC,SAAS,CAAC,eAAe,EAAE,mBAAmB,CAAC,aAAa,CAAC,CAAC,CAAC;IAAA,CACtE;IAED,gFAAgF;IAChF,oFAAkF;IAClF,0EAA0E;IAC1E,qBAAqB,CACpB,EAAE,EACF,GAAG,EACH,GAAG,EAAE,CAAC,YAAY,EAClB,GAAG,EAAE,CAAC,OAAO,EAAE,IAAI,CACnB,CAAC;IACF,sBAAsB,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC;IAEhC,KAAK,UAAU,OAAO,GAAkB;QACvC,SAAS,GAAG,SAAS,CAAC;QACtB,OAAO,GAAG,SAAS,CAAC;QACpB,YAAY,GAAG,SAAS,CAAC;QACzB,aAAa,GAAG,SAAS,CAAC;QAC1B,aAAa,GAAG,KAAK,CAAC;QACtB,MAAM,MAAM,GAAG,qBAAqB,EAAE,CAAC;QACvC,IAAI,CAAC,MAAM;YAAE,OAAO;QACpB,MAAM,MAAM,GAAG,IAAI,cAAc,CAAC,MAAM,CAAC,CAAC;QAC1C,IAAI,CAAC;YACJ,MAAM,UAAU,GAAG,MAAM,MAAM,CAAC,QAAQ,CAAC,YAAY,EAAE,EAAE,CAAC,CAAC;YAC3D,SAAS,GAAG,kBAAkB,CAAC,cAAc,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC;QACjE,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,MAAM,MAAM,GAAG,KAAK,YAAY,mBAAmB,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YACpF,IAAI,KAAK,YAAY,mBAAmB,IAAI,KAAK,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;gBACnE,aAAa,GAAG,IAAI,CAAC;gBACrB,OAAO;YACR,CAAC;YACD,aAAa,GAAG,yBAAyB,MAAM,EAAE,CAAC;YAClD,OAAO;QACR,CAAC;QAED,OAAO,GAAG,gBAAgB,CAAC,SAAS,EAAE,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC;QAC1D,YAAY,GAAG,MAAM,CAAC;QACtB,cAAc,CAAC,WAAW,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC;QACxG,sEAAsE;QACtE,yEAAuE;QACvE,qEAAqE;QACrE,sCAAsC;QACtC,IAAI,OAAO;YAAE,qBAAqB,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;QACzD,cAAc,CAAC,OAAO,CACrB,OAAO;YACN,CAAC,CAAC;gBACA,IAAI,EAAE,OAAO,CAAC,IAAI;gBAClB,OAAO,EAAE,OAAO,CAAC,OAAO;gBACxB,aAAa,EAAE,OAAO,CAAC,aAAa;gBACpC,SAAS,EAAE,OAAO,CAAC,SAAS;gBAC5B,SAAS,EAAE,SAAS,CAAC,KAAK,CAAC,MAAM;aACjC;YACF,CAAC,CAAC;gBACA,IAAI,EAAE,EAAE;gBACR,OAAO,EAAE,GAAG;gBACZ,aAAa,EAAE,GAAG;gBAClB,SAAS,EAAE,EAAE;gBACb,SAAS,EAAE,SAAS,CAAC,KAAK,CAAC,MAAM;aACjC,EACH,OAAO,KAAK,SAAS,CACrB,CAAC;QACF,IAAI,CAAC,eAAe,EAAE,CAAC;YACtB,MAAM,GAAG,GAAG,EAAE,MAAM,EAAE,WAAW,EAAE,GAAG,EAAE,CAAC,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC;YAC9D,KAAK,MAAM,IAAI,IAAI,mBAAmB,CAAC,GAAG,CAAC;gBAAE,EAAE,CAAC,YAAY,CAAC,oBAAoB,CAAC,IAAI,CAAC,CAAC,CAAC;YACzF,eAAe,GAAG,IAAI,CAAC;QACxB,CAAC;QAED,aAAa,GAAG,OAAO;YACtB,CAAC,CAAC,2BAAyB,OAAO,CAAC,IAAI,KAAK,OAAO,CAAC,OAAO,qBAAqB,OAAO,CAAC,SAAS,MAAM,SAAS,CAAC,KAAK,CAAC,MAAM,mBAAmB;YAChJ,CAAC,CAAC,6DAA2D,SAAS,CAAC,KAAK,CAAC,MAAM,wGAAwG,CAAC;IAAA,CAC7L;IAED,EAAE,CAAC,EAAE,CAAC,eAAe,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,EAAE,CAAC;QAC7C,MAAM,OAAO,EAAE,CAAC;QAChB,kBAAkB,CAAC,GAAG,CAAC,CAAC;QACxB,IAAI,aAAa;YAAE,GAAG,CAAC,EAAE,CAAC,MAAM,CAAC,aAAa,EAAE,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;IAAA,CAChF,CAAC,CAAC;IAEH,EAAE,CAAC,EAAE,CAAC,eAAe,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,EAAE,CAAC;QAC7C,IAAI,CAAC,aAAa,IAAI,GAAG,CAAC,KAAK,EAAE,QAAQ,KAAK,UAAU;YAAE,OAAO;QACjE,MAAM,MAAM,GAAG,qBAAqB,EAAE,CAAC;QACvC,IAAI,MAAM;YAAE,aAAa,GAAG,MAAM,oBAAoB,CAAC,MAAM,CAAC,CAAC;QAC/D,6BAA6B,EAAE,CAAC;QAChC,kBAAkB,CAAC,GAAG,CAAC,CAAC;IAAA,CACxB,CAAC,CAAC;IAEH,2EAA2E;IAC3E,6EAA2E;IAC3E,gFAAgF;IAChF,yEAAyE;IACzE,oEAAoE;IACpE,EAAE,CAAC,EAAE,CAAC,oBAAoB,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC;QACtC,MAAM,oBAAoB,GACzB,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,SAAS,EAAE,QAAQ,EAAE,UAAU,CAAC,CAAC;YAC5D,UAAU,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,QAAQ,EAAE,oBAAoB,CAAC,CAAC,CAAC;QACxE,IAAI,oBAAoB;YAAE,OAAO;QACjC,MAAM,SAAS,GAAG,wBAAwB,EAAE,CAAC;QAC7C,IAAI,CAAC,SAAS;YAAE,OAAO;QACvB,OAAO,EAAE,UAAU,EAAE,CAAC,SAAS,CAAC,EAAE,CAAC;IAAA,CACnC,CAAC,CAAC;IAEH,EAAE,CAAC,EAAE,CAAC,oBAAoB,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC;QACtC,IAAI,CAAC,SAAS;YAAE,OAAO;QACvB,OAAO,EAAE,YAAY,EAAE,GAAG,KAAK,CAAC,YAAY,OAAO,mBAAmB,CAAC,SAAS,EAAE,OAAO,CAAC,EAAE,EAAE,CAAC;IAAA,CAC/F,CAAC,CAAC;IAEH,EAAE,CAAC,eAAe,CAAC,gBAAgB,EAAE;QACpC,WAAW,EAAE,6FAA2F;QACxG,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,EAAE,CAAC;YAC7B,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,gBAAgB,IAAI,eAAe,CAAC;YAC/D,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;YAE3B,gEAAgE;YAChE,IAAI,MAAM,EAAE,CAAC;gBACZ,IAAI,CAAC;oBACJ,MAAM,IAAI,cAAc,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC,QAAQ,CAAC,YAAY,EAAE,EAAE,CAAC,CAAC;oBAC/E,MAAM,IAAI,GAAG,kBAAkB,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC;oBAC3D,MAAM,OAAO,EAAE,CAAC;oBAChB,GAAG,CAAC,EAAE,CAAC,MAAM,CAAC,2BAA2B,IAAI,KAAK,aAAa,IAAI,EAAE,EAAE,EAAE,MAAM,CAAC,CAAC;gBAClF,CAAC;gBAAC,OAAO,KAAK,EAAE,CAAC;oBAChB,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;oBACvE,GAAG,CAAC,EAAE,CAAC,MAAM,CAAC,0BAA0B,OAAO,EAAE,EAAE,OAAO,CAAC,CAAC;gBAC7D,CAAC;gBACD,OAAO;YACR,CAAC;YAED,IAAI,CAAC;gBACJ,MAAM,UAAU,GAAG,iBAAiB,EAAE,CAAC;gBACvC,MAAM,WAAW,GAAG,iBAAiB,EAAE,CAAC;gBACxC,MAAM,KAAK,GAAG,MAAM,eAAe,CAAC,MAAM,EAAE,EAAE,UAAU,EAAE,iBAAiB,EAAE,WAAW,EAAE,CAAC,CAAC;gBAC5F,MAAM,IAAI,GAAG,KAAK,CAAC,uBAAuB,IAAI,KAAK,CAAC,eAAe,CAAC;gBACpE,mEAAmE;gBACnE,iEAAiE;gBACjE,GAAG,CAAC,EAAE,CAAC,MAAM,CAAC,QAAQ,KAAK,CAAC,QAAQ,gBAAc,IAAI,EAAE,EAAE,MAAM,CAAC,CAAC;gBAClE,WAAW,CAAC,IAAI,CAAC,CAAC;gBAElB,MAAM,MAAM,GAAG,MAAM,cAAc,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;gBACnD,MAAM,MAAM,GAAG;oBACd,MAAM,EAAE,MAAM,CAAC,MAAM;oBACrB,KAAK,EAAE,MAAM,CAAC,KAAK;oBACnB,iBAAiB,EAAE,WAAW;oBAC9B,UAAU;iBACV,CAAC;gBACF,MAAM,IAAI,cAAc,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,YAAY,EAAE,EAAE,CAAC,CAAC;gBAC5D,MAAM,IAAI,GAAG,kBAAkB,CAAC,MAAM,CAAC,CAAC;gBACxC,MAAM,OAAO,EAAE,CAAC;gBAChB,GAAG,CAAC,EAAE,CAAC,MAAM,CAAC,6BAA6B,IAAI,KAAK,aAAa,IAAI,EAAE,EAAE,EAAE,MAAM,CAAC,CAAC;YACpF,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBAChB,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;gBACvE,GAAG,CAAC,EAAE,CAAC,MAAM,CAAC,0BAA0B,OAAO,EAAE,EAAE,OAAO,CAAC,CAAC;YAC7D,CAAC;QAAA,CACD;KACD,CAAC,CAAC;IAEH,EAAE,CAAC,eAAe,CAAC,iBAAiB,EAAE;QACrC,WAAW,EAAE,iCAAiC;QAC9C,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE,CAAC;YAC9B,MAAM,MAAM,GAAG,qBAAqB,EAAE,CAAC;YACvC,IAAI,MAAM;gBAAE,aAAa,GAAG,MAAM,oBAAoB,CAAC,MAAM,CAAC,CAAC;YAC/D,kBAAkB,CAAC,GAAG,CAAC,CAAC;YACxB,IAAI,CAAC,SAAS,EAAE,CAAC;gBAChB,MAAM,OAAO,GAAG,aAAa;oBAC5B,CAAC,CAAC,wDAAwD;oBAC1D,CAAC,CAAC,CAAC,aAAa,IAAI,sCAAsC,CAAC,CAAC;gBAC7D,GAAG,CAAC,EAAE,CAAC,MAAM,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;gBAClC,OAAO;YACR,CAAC;YACD,MAAM,QAAQ,GAAG,OAAO,CAAC,CAAC,CAAC,iBAAiB,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,2BAA2B,CAAC;YACzF,MAAM,UAAU,GAAG,mBAAmB,CAAC,aAAa,CAAC,CAAC;YACtD,GAAG,CAAC,EAAE,CAAC,MAAM,CACZ,2BAAyB,SAAS,CAAC,KAAK,CAAC,MAAM,WAAW,QAAQ,GAAG,UAAU,CAAC,CAAC,CAAC,KAAK,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,EAC1G,MAAM,CACN,CAAC;QAAA,CACF;KACD,CAAC,CAAC;AAAA,CACH","sourcesContent":["import { existsSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport type { ExtensionAPI } from \"../../core/extensions/types.ts\";\nimport { graphRailStore } from \"../../core/graph-rail-store.ts\";\nimport { openBrowser } from \"../../utils/open-browser.ts\";\nimport { isDelegatedWorkerProcess, registerDelegateTaskTool } from \"../subagent/index.ts\";\nimport { registerAugmentedGrep } from \"./augment.ts\";\nimport { registerChangeTracking } from \"./changes.ts\";\nimport { GitNexusClient, GitNexusClientError } from \"./client.ts\";\nimport { DEFAULT_HUB_URL, resolveGitNexusConfig, saveGitNexusConfig } from \"./config.ts\";\nimport { defaultDeviceName, deviceFingerprint, pollDeviceAuth, startDeviceAuth } from \"./device-auth.ts\";\nimport { formatCreditBalance, readGatewayCache, refreshGatewayPolicy, registerAutoProvider } from \"./gateway.ts\";\nimport { spawnObservatoryFetch } from \"./observatory.ts\";\nimport { buildGitNexusPrompt } from \"./prompt.ts\";\nimport { withGitNexusRenderer } from \"./render.ts\";\nimport {\n\tgitRemoteSlug,\n\ttype IndexedRepo,\n\tmatchCurrentRepo,\n\tparseRepoInventory,\n\ttype RepoInventory,\n} from \"./resolve.ts\";\nimport { createGitNexusTools } from \"./tools.ts\";\n\n/** Resolve workflow skills in source mode and bundled dist/cli.js mode. */\nexport function resolveGitNexusSkillsDir(\n\tmoduleUrl: string = import.meta.url,\n\tpathExists: (path: string) => boolean = existsSync,\n): string | undefined {\n\tconst candidates = [\n\t\tfileURLToPath(new URL(\"./skills/\", moduleUrl)),\n\t\tfileURLToPath(new URL(\"./extensions/gitnexus/skills/\", moduleUrl)),\n\t];\n\treturn candidates.find(pathExists);\n}\n\n/**\n * GitNexus built-in extension: connects nexus-cli to the GitNexus hub.\n *\n * - session_start: resolve config + repo inventory, register the six graph\n * tools and the augmented grep. No token / hub down / repo unindexed all\n * degrade to a plain coding agent (graph is additive, never load-bearing).\n * - before_agent_start: append the GitNexus guidance block (ported from the\n * CLAUDE.md generator) to the system prompt.\n */\nexport default function gitnexusExtension(pi: ExtensionAPI): void {\n\tlet inventory: RepoInventory | undefined;\n\tlet current: IndexedRepo | undefined;\n\tlet activeClient: GitNexusClient | undefined;\n\tlet toolsRegistered = false;\n\tlet delegationRegistered = false;\n\tlet startupNotice: string | undefined;\n\tlet loginRequired = false;\n\n\tconst cwd = process.cwd();\n\n\t// Auto mode must also register at factory time: the initial model is\n\t// resolved before session_start fires, and a provider that appears after\n\t// that is invisible to it. Reads the policy cache the startup gate wrote.\n\tlet gatewayPolicy = readGatewayCache();\n\tconst initialConfig = resolveGitNexusConfig();\n\tregisterAutoProvider(pi, initialConfig, gatewayPolicy);\n\n\tfunction registerDelegationIfAvailable(): void {\n\t\tif (\n\t\t\tdelegationRegistered ||\n\t\t\tisDelegatedWorkerProcess() ||\n\t\t\t!resolveGitNexusConfig() ||\n\t\t\t!gatewayPolicy?.delegation\n\t\t) {\n\t\t\treturn;\n\t\t}\n\t\tregisterDelegateTaskTool(pi, gatewayPolicy.delegation);\n\t\tdelegationRegistered = true;\n\t}\n\n\tregisterDelegationIfAvailable();\n\n\tfunction updateCreditStatus(ctx: { ui: { setStatus(key: string, text: string | undefined): void } }): void {\n\t\tctx.ui.setStatus(\"nexus-credits\", formatCreditBalance(gatewayPolicy));\n\t}\n\n\t// Built-in tool overrides must happen at factory time (like the bash-spawn-hook\n\t// example) — session_start registrations only add NEW names reliably. The wrapper\n\t// behaves as plain grep until connect() provides a client + current repo.\n\tregisterAugmentedGrep(\n\t\tpi,\n\t\tcwd,\n\t\t() => activeClient,\n\t\t() => current?.name,\n\t);\n\tregisterChangeTracking(pi, cwd);\n\n\tasync function connect(): Promise<void> {\n\t\tinventory = undefined;\n\t\tcurrent = undefined;\n\t\tactiveClient = undefined;\n\t\tstartupNotice = undefined;\n\t\tloginRequired = false;\n\t\tconst config = resolveGitNexusConfig();\n\t\tif (!config) return;\n\t\tconst client = new GitNexusClient(config);\n\t\ttry {\n\t\t\tconst listResult = await client.callTool(\"list_repos\", {});\n\t\t\tinventory = parseRepoInventory(GitNexusClient.text(listResult));\n\t\t} catch (error) {\n\t\t\tconst reason = error instanceof GitNexusClientError ? error.message : String(error);\n\t\t\tif (error instanceof GitNexusClientError && error.kind === \"auth\") {\n\t\t\t\tloginRequired = true;\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tstartupNotice = `GitNexus unavailable: ${reason}`;\n\t\t\treturn;\n\t\t}\n\n\t\tcurrent = matchCurrentRepo(inventory, gitRemoteSlug(cwd));\n\t\tactiveClient = client;\n\t\tgraphRailStore.setRepoList(inventory.repos.map((repo) => ({ name: repo.name, symbols: repo.symbols })));\n\t\t// Observatory (community map) rides a detached subprocess + temp-file\n\t\t// handoff — in-process hub calls made after startup can starve in some\n\t\t// environments, a fresh process never does. The UI picks the file up\n\t\t// lazily via tryLoadObservatoryCache.\n\t\tif (current) spawnObservatoryFetch(config, current.name);\n\t\tgraphRailStore.setRepo(\n\t\t\tcurrent\n\t\t\t\t? {\n\t\t\t\t\t\tname: current.name,\n\t\t\t\t\t\tsymbols: current.symbols,\n\t\t\t\t\t\trelationships: current.relationships,\n\t\t\t\t\t\tindexedAt: current.indexedAt,\n\t\t\t\t\t\trepoCount: inventory.repos.length,\n\t\t\t\t\t}\n\t\t\t\t: {\n\t\t\t\t\t\tname: \"\",\n\t\t\t\t\t\tsymbols: \"0\",\n\t\t\t\t\t\trelationships: \"0\",\n\t\t\t\t\t\tindexedAt: \"\",\n\t\t\t\t\t\trepoCount: inventory.repos.length,\n\t\t\t\t\t},\n\t\t\tcurrent !== undefined,\n\t\t);\n\t\tif (!toolsRegistered) {\n\t\t\tconst ctx = { client, currentRepo: () => current?.name, cwd };\n\t\t\tfor (const tool of createGitNexusTools(ctx)) pi.registerTool(withGitNexusRenderer(tool));\n\t\t\ttoolsRegistered = true;\n\t\t}\n\n\t\tstartupNotice = current\n\t\t\t? `GitNexus: connected — ${current.name} (${current.symbols} symbols, indexed ${current.indexedAt}), ${inventory.repos.length} repos available.`\n\t\t\t: `GitNexus: connected — current directory is not indexed; ${inventory.repos.length} indexed repos remain queryable. Index this repo from the Hub dashboard to unlock local graph context.`;\n\t}\n\n\tpi.on(\"session_start\", async (_event, ctx) => {\n\t\tawait connect();\n\t\tupdateCreditStatus(ctx);\n\t\tif (startupNotice) ctx.ui.notify(startupNotice, inventory ? \"info\" : \"warning\");\n\t});\n\n\tpi.on(\"agent_settled\", async (_event, ctx) => {\n\t\tif (!gatewayPolicy || ctx.model?.provider !== \"gitnexus\") return;\n\t\tconst config = resolveGitNexusConfig();\n\t\tif (config) gatewayPolicy = await refreshGatewayPolicy(config);\n\t\tregisterDelegationIfAvailable();\n\t\tupdateCreditStatus(ctx);\n\t});\n\n\t// Bundled GitNexus workflow skills (debugging, exploring, impact-analysis,\n\t// refactoring, pr-review, guide) — same SKILL.md content the Claude plugin\n\t// ships, adapted for hub-based indexing. Loaded on-demand by pi's skill system.\n\t// Skipped entirely when the user already has gitnexus skills (project or\n\t// global) so the startup screen doesn't report shadowed duplicates.\n\tpi.on(\"resources_discover\", (event) => {\n\t\tconst hasOwnGitnexusSkills =\n\t\t\texistsSync(join(event.cwd, \".agents\", \"skills\", \"gitnexus\")) ||\n\t\t\texistsSync(join(homedir(), \".agents\", \"skills\", \"gitnexus-debugging\"));\n\t\tif (hasOwnGitnexusSkills) return;\n\t\tconst skillsDir = resolveGitNexusSkillsDir();\n\t\tif (!skillsDir) return;\n\t\treturn { skillPaths: [skillsDir] };\n\t});\n\n\tpi.on(\"before_agent_start\", (event) => {\n\t\tif (!inventory) return;\n\t\treturn { systemPrompt: `${event.systemPrompt}\\n\\n${buildGitNexusPrompt(inventory, current)}` };\n\t});\n\n\tpi.registerCommand(\"gitnexus-login\", {\n\t\tdescription: \"Sign in to GitNexus (implementation of /login → GitNexus account; accepts a pasted token)\",\n\t\thandler: async (args, ctx) => {\n\t\t\tconst hubUrl = process.env.GITNEXUS_HUB_URL ?? DEFAULT_HUB_URL;\n\t\t\tconst pasted = args.trim();\n\n\t\t\t// Pasting a token stays supported for CI and headless installs.\n\t\t\tif (pasted) {\n\t\t\t\ttry {\n\t\t\t\t\tawait new GitNexusClient({ hubUrl, token: pasted }).callTool(\"list_repos\", {});\n\t\t\t\t\tconst path = saveGitNexusConfig({ hubUrl, token: pasted });\n\t\t\t\t\tawait connect();\n\t\t\t\t\tctx.ui.notify(`GitNexus token saved to ${path}. ${startupNotice ?? \"\"}`, \"info\");\n\t\t\t\t} catch (error) {\n\t\t\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\t\t\tctx.ui.notify(`GitNexus login failed: ${message}`, \"error\");\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\tconst deviceName = defaultDeviceName();\n\t\t\t\tconst fingerprint = deviceFingerprint();\n\t\t\t\tconst start = await startDeviceAuth(hubUrl, { deviceName, deviceFingerprint: fingerprint });\n\t\t\t\tconst link = start.verificationUrlComplete ?? start.verificationUrl;\n\t\t\t\t// The link carries the code, so an opened browser needs no typing.\n\t\t\t\t// Print it too: the launcher is best effort and can be headless.\n\t\t\t\tctx.ui.notify(`Code ${start.userCode} — opening ${link}`, \"info\");\n\t\t\t\topenBrowser(link);\n\n\t\t\t\tconst result = await pollDeviceAuth(hubUrl, start);\n\t\t\t\tconst config = {\n\t\t\t\t\thubUrl: result.hubUrl,\n\t\t\t\t\ttoken: result.token,\n\t\t\t\t\tdeviceFingerprint: fingerprint,\n\t\t\t\t\tdeviceName,\n\t\t\t\t};\n\t\t\t\tawait new GitNexusClient(config).callTool(\"list_repos\", {});\n\t\t\t\tconst path = saveGitNexusConfig(config);\n\t\t\t\tawait connect();\n\t\t\t\tctx.ui.notify(`Signed in. Token saved to ${path}. ${startupNotice ?? \"\"}`, \"info\");\n\t\t\t} catch (error) {\n\t\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\t\tctx.ui.notify(`GitNexus login failed: ${message}`, \"error\");\n\t\t\t}\n\t\t},\n\t});\n\n\tpi.registerCommand(\"gitnexus-status\", {\n\t\tdescription: \"Show GitNexus connection status\",\n\t\thandler: async (_args, ctx) => {\n\t\t\tconst config = resolveGitNexusConfig();\n\t\t\tif (config) gatewayPolicy = await refreshGatewayPolicy(config);\n\t\t\tupdateCreditStatus(ctx);\n\t\t\tif (!inventory) {\n\t\t\t\tconst message = loginRequired\n\t\t\t\t\t? \"GitNexus session expired. Run /login to sign in again.\"\n\t\t\t\t\t: (startupNotice ?? \"GitNexus: not signed in. Run /login.\");\n\t\t\t\tctx.ui.notify(message, \"warning\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst repoLine = current ? `current repo: ${current.name}` : \"current repo: not indexed\";\n\t\t\tconst creditLine = formatCreditBalance(gatewayPolicy);\n\t\t\tctx.ui.notify(\n\t\t\t\t`GitNexus: connected — ${inventory.repos.length} repos, ${repoLine}${creditLine ? `, ${creditLine}` : \"\"}`,\n\t\t\t\t\"info\",\n\t\t\t);\n\t\t},\n\t});\n}\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/extensions/gitnexus/index.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport type { ExtensionAPI } from \"../../core/extensions/types.ts\";\nimport { graphRailStore } from \"../../core/graph-rail-store.ts\";\nimport { openBrowser } from \"../../utils/open-browser.ts\";\nimport {\n\tapplyNexusSubagentEnvAliases,\n\tisDelegatedWorkerProcess,\n\tisExternalDelegationMode,\n\tregisterDelegateTaskTool,\n} from \"../subagent/index.ts\";\nimport { registerAugmentedGrep } from \"./augment.ts\";\nimport { registerChangeTracking } from \"./changes.ts\";\nimport { GitNexusClient, GitNexusClientError } from \"./client.ts\";\nimport { resolveGitNexusConfig, resolveHubUrl, saveGitNexusConfig } from \"./config.ts\";\nimport { defaultDeviceName, deviceFingerprint, pollDeviceAuth, startDeviceAuth } from \"./device-auth.ts\";\nimport { formatCreditBalance, readGatewayCache, refreshGatewayPolicy, registerAutoProvider } from \"./gateway.ts\";\nimport { spawnObservatoryFetch } from \"./observatory.ts\";\nimport { buildGitNexusPrompt } from \"./prompt.ts\";\nimport { withGitNexusRenderer } from \"./render.ts\";\nimport {\n\tgitRemoteSlug,\n\ttype IndexedRepo,\n\tmatchCurrentRepo,\n\tparseRepoInventory,\n\ttype RepoInventory,\n} from \"./resolve.ts\";\nimport { createGitNexusTools } from \"./tools.ts\";\n\n/** Resolve workflow skills in source mode and bundled dist/cli.js mode. */\nexport function resolveGitNexusSkillsDir(\n\tmoduleUrl: string = import.meta.url,\n\tpathExists: (path: string) => boolean = existsSync,\n): string | undefined {\n\tconst candidates = [\n\t\tfileURLToPath(new URL(\"./skills/\", moduleUrl)),\n\t\tfileURLToPath(new URL(\"./extensions/gitnexus/skills/\", moduleUrl)),\n\t];\n\treturn candidates.find(pathExists);\n}\n\n/**\n * GitNexus built-in extension: connects nexus-cli to the GitNexus hub.\n *\n * - session_start: resolve config + repo inventory, register the six graph\n * tools and the augmented grep. No token / hub down / repo unindexed all\n * degrade to a plain coding agent (graph is additive, never load-bearing).\n * - before_agent_start: append the GitNexus guidance block (ported from the\n * CLAUDE.md generator) to the system prompt.\n */\nexport default function gitnexusExtension(pi: ExtensionAPI): void {\n\tlet inventory: RepoInventory | undefined;\n\tlet current: IndexedRepo | undefined;\n\tlet activeClient: GitNexusClient | undefined;\n\tlet toolsRegistered = false;\n\tlet delegationRegistered = false;\n\tlet startupNotice: string | undefined;\n\tlet loginRequired = false;\n\n\tconst cwd = process.cwd();\n\n\t// Nexus Agent must also register at factory time: the initial model is\n\t// resolved before session_start fires, and a provider that appears after\n\t// that is invisible to it. Reads the policy cache the startup gate wrote.\n\tapplyNexusSubagentEnvAliases();\n\tlet gatewayPolicy = readGatewayCache();\n\tconst initialConfig = resolveGitNexusConfig();\n\tregisterAutoProvider(pi, initialConfig, gatewayPolicy);\n\n\tfunction registerDelegationIfAvailable(): void {\n\t\tif (\n\t\t\tdelegationRegistered ||\n\t\t\tisDelegatedWorkerProcess() ||\n\t\t\tisExternalDelegationMode() ||\n\t\t\t!resolveGitNexusConfig() ||\n\t\t\t!gatewayPolicy?.delegation\n\t\t) {\n\t\t\treturn;\n\t\t}\n\t\tregisterDelegateTaskTool(pi, gatewayPolicy.delegation);\n\t\tdelegationRegistered = true;\n\t}\n\n\tregisterDelegationIfAvailable();\n\n\tfunction updateCreditStatus(ctx: { ui: { setStatus(key: string, text: string | undefined): void } }): void {\n\t\tctx.ui.setStatus(\"nexus-credits\", formatCreditBalance(gatewayPolicy));\n\t}\n\n\t// Built-in tool overrides must happen at factory time (like the bash-spawn-hook\n\t// example) — session_start registrations only add NEW names reliably. The wrapper\n\t// behaves as plain grep until connect() provides a client + current repo.\n\tregisterAugmentedGrep(\n\t\tpi,\n\t\tcwd,\n\t\t() => activeClient,\n\t\t() => current?.name,\n\t);\n\tregisterChangeTracking(pi, cwd);\n\n\tasync function connect(): Promise<void> {\n\t\tinventory = undefined;\n\t\tcurrent = undefined;\n\t\tactiveClient = undefined;\n\t\tstartupNotice = undefined;\n\t\tloginRequired = false;\n\t\tconst config = resolveGitNexusConfig();\n\t\tif (!config) return;\n\t\tconst client = new GitNexusClient(config);\n\t\ttry {\n\t\t\tconst listResult = await client.callTool(\"list_repos\", {});\n\t\t\tinventory = parseRepoInventory(GitNexusClient.text(listResult));\n\t\t} catch (error) {\n\t\t\tconst reason = error instanceof GitNexusClientError ? error.message : String(error);\n\t\t\tif (error instanceof GitNexusClientError && error.kind === \"auth\") {\n\t\t\t\tloginRequired = true;\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tstartupNotice = `GitNexus unavailable: ${reason}`;\n\t\t\treturn;\n\t\t}\n\n\t\tcurrent = matchCurrentRepo(inventory, gitRemoteSlug(cwd));\n\t\tactiveClient = client;\n\t\tgraphRailStore.setRepoList(inventory.repos.map((repo) => ({ name: repo.name, symbols: repo.symbols })));\n\t\t// Observatory (community map) rides a detached subprocess + temp-file\n\t\t// handoff — in-process hub calls made after startup can starve in some\n\t\t// environments, a fresh process never does. The UI picks the file up\n\t\t// lazily via tryLoadObservatoryCache.\n\t\tif (current) spawnObservatoryFetch(config, current.name);\n\t\tgraphRailStore.setRepo(\n\t\t\tcurrent\n\t\t\t\t? {\n\t\t\t\t\t\tname: current.name,\n\t\t\t\t\t\tsymbols: current.symbols,\n\t\t\t\t\t\trelationships: current.relationships,\n\t\t\t\t\t\tindexedAt: current.indexedAt,\n\t\t\t\t\t\trepoCount: inventory.repos.length,\n\t\t\t\t\t}\n\t\t\t\t: {\n\t\t\t\t\t\tname: \"\",\n\t\t\t\t\t\tsymbols: \"0\",\n\t\t\t\t\t\trelationships: \"0\",\n\t\t\t\t\t\tindexedAt: \"\",\n\t\t\t\t\t\trepoCount: inventory.repos.length,\n\t\t\t\t\t},\n\t\t\tcurrent !== undefined,\n\t\t);\n\t\tif (!toolsRegistered) {\n\t\t\tconst ctx = { client, currentRepo: () => current?.name, cwd };\n\t\t\tfor (const tool of createGitNexusTools(ctx)) pi.registerTool(withGitNexusRenderer(tool));\n\t\t\ttoolsRegistered = true;\n\t\t}\n\n\t\tstartupNotice = current\n\t\t\t? `GitNexus: connected — ${current.name} (${current.symbols} symbols, indexed ${current.indexedAt}), ${inventory.repos.length} repos available.`\n\t\t\t: `GitNexus: connected — current directory is not indexed; ${inventory.repos.length} indexed repos remain queryable. Index this repo from the Hub dashboard to unlock local graph context.`;\n\t}\n\n\tpi.on(\"session_start\", async (_event, ctx) => {\n\t\tawait connect();\n\t\tupdateCreditStatus(ctx);\n\t\tif (startupNotice) ctx.ui.notify(startupNotice, inventory ? \"info\" : \"warning\");\n\t});\n\n\tpi.on(\"agent_settled\", async (_event, ctx) => {\n\t\tif (!gatewayPolicy || ctx.model?.provider !== \"gitnexus\") return;\n\t\tconst config = resolveGitNexusConfig();\n\t\tif (config) gatewayPolicy = await refreshGatewayPolicy(config);\n\t\tregisterDelegationIfAvailable();\n\t\tupdateCreditStatus(ctx);\n\t});\n\n\t// Bundled GitNexus workflow skills (debugging, exploring, impact-analysis,\n\t// refactoring, pr-review, guide) — same SKILL.md content the Claude plugin\n\t// ships, adapted for hub-based indexing. Loaded on-demand by pi's skill system.\n\t// Skipped entirely when the user already has gitnexus skills (project or\n\t// global) so the startup screen doesn't report shadowed duplicates.\n\tpi.on(\"resources_discover\", (event) => {\n\t\tconst hasOwnGitnexusSkills =\n\t\t\texistsSync(join(event.cwd, \".agents\", \"skills\", \"gitnexus\")) ||\n\t\t\texistsSync(join(homedir(), \".agents\", \"skills\", \"gitnexus-debugging\"));\n\t\tif (hasOwnGitnexusSkills) return;\n\t\tconst skillsDir = resolveGitNexusSkillsDir();\n\t\tif (!skillsDir) return;\n\t\treturn { skillPaths: [skillsDir] };\n\t});\n\n\tpi.on(\"before_agent_start\", (event) => {\n\t\tif (!inventory) return;\n\t\treturn { systemPrompt: `${event.systemPrompt}\\n\\n${buildGitNexusPrompt(inventory, current)}` };\n\t});\n\n\tpi.registerCommand(\"gitnexus-login\", {\n\t\tdescription: \"Sign in to GitNexus (implementation of /login → GitNexus account; accepts a pasted token)\",\n\t\thandler: async (args, ctx) => {\n\t\t\tconst hubUrl = resolveHubUrl();\n\t\t\tconst pasted = args.trim();\n\n\t\t\t// Pasting a token stays supported for CI and headless installs.\n\t\t\tif (pasted) {\n\t\t\t\ttry {\n\t\t\t\t\tawait new GitNexusClient({ hubUrl, token: pasted }).callTool(\"list_repos\", {});\n\t\t\t\t\tconst path = saveGitNexusConfig({ hubUrl, token: pasted });\n\t\t\t\t\tawait connect();\n\t\t\t\t\tctx.ui.notify(`GitNexus token saved to ${path}. ${startupNotice ?? \"\"}`, \"info\");\n\t\t\t\t} catch (error) {\n\t\t\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\t\t\tctx.ui.notify(`GitNexus login failed: ${message}`, \"error\");\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\tconst deviceName = defaultDeviceName();\n\t\t\t\tconst fingerprint = deviceFingerprint();\n\t\t\t\tconst start = await startDeviceAuth(hubUrl, { deviceName, deviceFingerprint: fingerprint });\n\t\t\t\tconst link = start.verificationUrlComplete ?? start.verificationUrl;\n\t\t\t\t// The link carries the code, so an opened browser needs no typing.\n\t\t\t\t// Print it too: the launcher is best effort and can be headless.\n\t\t\t\tctx.ui.notify(`Code ${start.userCode} — opening ${link}`, \"info\");\n\t\t\t\topenBrowser(link);\n\n\t\t\t\tconst result = await pollDeviceAuth(hubUrl, start);\n\t\t\t\tconst config = {\n\t\t\t\t\thubUrl: result.hubUrl,\n\t\t\t\t\ttoken: result.token,\n\t\t\t\t\tdeviceFingerprint: fingerprint,\n\t\t\t\t\tdeviceName,\n\t\t\t\t};\n\t\t\t\tawait new GitNexusClient(config).callTool(\"list_repos\", {});\n\t\t\t\tconst path = saveGitNexusConfig(config);\n\t\t\t\tawait connect();\n\t\t\t\tctx.ui.notify(`Signed in. Token saved to ${path}. ${startupNotice ?? \"\"}`, \"info\");\n\t\t\t} catch (error) {\n\t\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\t\tctx.ui.notify(`GitNexus login failed: ${message}`, \"error\");\n\t\t\t}\n\t\t},\n\t});\n\n\tpi.registerCommand(\"gitnexus-status\", {\n\t\tdescription: \"Show GitNexus connection status\",\n\t\thandler: async (_args, ctx) => {\n\t\t\tconst config = resolveGitNexusConfig();\n\t\t\tif (config) gatewayPolicy = await refreshGatewayPolicy(config);\n\t\t\tupdateCreditStatus(ctx);\n\t\t\tif (!inventory) {\n\t\t\t\tconst message = loginRequired\n\t\t\t\t\t? \"GitNexus session expired. Run /login to sign in again.\"\n\t\t\t\t\t: (startupNotice ?? \"GitNexus: not signed in. Run /login.\");\n\t\t\t\tctx.ui.notify(message, \"warning\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst repoLine = current ? `current repo: ${current.name}` : \"current repo: not indexed\";\n\t\t\tconst creditLine = formatCreditBalance(gatewayPolicy);\n\t\t\tctx.ui.notify(\n\t\t\t\t`GitNexus: connected — ${inventory.repos.length} repos, ${repoLine}${creditLine ? `, ${creditLine}` : \"\"}`,\n\t\t\t\t\"info\",\n\t\t\t);\n\t\t},\n\t});\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"observatory.d.ts","sourceRoot":"","sources":["../../../src/extensions/gitnexus/observatory.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAkClD,4FAA4F;AAC5F,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,EAAE,CAiBxD;AAeD,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA2B1F;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAmBhF","sourcesContent":["import {
|
|
1
|
+
{"version":3,"file":"observatory.d.ts","sourceRoot":"","sources":["../../../src/extensions/gitnexus/observatory.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAkClD,4FAA4F;AAC5F,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,EAAE,CAiBxD;AAeD,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA2B1F;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAmBhF","sourcesContent":["import { appendFileSync } from \"node:fs\";\nimport { graphRailStore } from \"../../core/graph-rail-store.ts\";\nimport { observatoryCachePath } from \"../../core/observatory-cache.ts\";\nimport { spawnWithoutConsole } from \"../../utils/hidden-spawn.ts\";\nimport { GitNexusClient } from \"./client.ts\";\nimport type { GitNexusConfig } from \"./config.ts\";\n\nfunction trace(message: string): void {\n\tif (!process.env.NEXUS_TRACE) return;\n\ttry {\n\t\tappendFileSync(\"/tmp/nexus-observatory-trace.log\", `${new Date().toISOString()} ${message}\\n`);\n\t} catch {\n\t\t// tracing is best-effort\n\t}\n}\n\n/**\n * Startup graph observatory: fetches the community map of the current repo\n * (community sizes, cross-community call edges, flow count) and feeds the\n * rail store. Fire-and-forget — failures leave the observatory empty and\n * never surface to the user.\n */\n\nconst COMMUNITY_QUERY =\n\t\"MATCH (f)-[:CodeRelation {type:'MEMBER_OF'}]->(c:Community) \" +\n\t\"RETURN c.heuristicLabel AS name, count(f) AS members ORDER BY members DESC LIMIT 8\";\n\nconst EDGE_QUERY =\n\t\"MATCH (a)-[:CodeRelation {type:'MEMBER_OF'}]->(ca:Community), \" +\n\t\"(b)-[:CodeRelation {type:'MEMBER_OF'}]->(cb:Community), \" +\n\t\"(a)-[r:CodeRelation {type:'CALLS'}]->(b) \" +\n\t\"WHERE ca.heuristicLabel <> cb.heuristicLabel \" +\n\t\"RETURN ca.heuristicLabel AS source, cb.heuristicLabel AS target, count(r) AS weight \" +\n\t\"ORDER BY weight DESC LIMIT 16\";\n\nconst FLOW_QUERY = \"MATCH (p:Process) RETURN count(p) AS flows\";\n\nconst TIMEOUT_MS = 20_000;\n\n/** Parse the hub cypher result: `{\"markdown\": \"| a | b |\\n| --- |...\", \"row_count\": n}`. */\nexport function parseCypherRows(text: string): string[][] {\n\tlet markdown: string;\n\ttry {\n\t\tconst parsed = JSON.parse(text.split(\"\\n\\n---\")[0]?.trim() ?? \"\") as { markdown?: string };\n\t\tif (typeof parsed.markdown !== \"string\") return [];\n\t\tmarkdown = parsed.markdown;\n\t} catch {\n\t\treturn [];\n\t}\n\tconst lines = markdown.split(\"\\n\").filter((line) => line.trim().startsWith(\"|\"));\n\t// Drop header + separator rows.\n\treturn lines.slice(2).map((line) =>\n\t\tline\n\t\t\t.split(\"|\")\n\t\t\t.slice(1, -1)\n\t\t\t.map((cell) => cell.trim()),\n\t);\n}\n\nasync function runQuery(client: GitNexusClient, repo: string, query: string, label: string): Promise<string[][]> {\n\tconst start = Date.now();\n\ttrace(`${label}: start`);\n\ttry {\n\t\tconst result = await client.callTool(\"cypher\", { query, repo }, AbortSignal.timeout(TIMEOUT_MS));\n\t\ttrace(`${label}: ok in ${Date.now() - start}ms`);\n\t\treturn parseCypherRows(GitNexusClient.text(result));\n\t} catch (error) {\n\t\ttrace(`${label}: failed in ${Date.now() - start}ms: ${String(error)}`);\n\t\tthrow error;\n\t}\n}\n\nexport async function fetchObservatory(client: GitNexusClient, repo: string): Promise<void> {\n\ttry {\n\t\ttrace(`fetching for ${repo}`);\n\t\t// Sequential on purpose: parallel POSTs against one hub MCP session stall\n\t\t// behind per-session serialization; three sequential cyphers finish in ~2s.\n\t\tconst communityRows = await runQuery(client, repo, COMMUNITY_QUERY, \"communities\");\n\t\tconst edgeRows = await runQuery(client, repo, EDGE_QUERY, \"edges\");\n\t\tconst flowRows = await runQuery(client, repo, FLOW_QUERY, \"flows\");\n\t\ttrace(`rows: communities=${communityRows.length} edges=${edgeRows.length} flows=${flowRows.length}`);\n\t\tconst communities = communityRows\n\t\t\t.filter((row) => row[0])\n\t\t\t.map((row) => ({ name: row[0], members: Number(row[1]) || 0 }));\n\t\tif (communities.length === 0) return;\n\t\tconst known = new Set(communities.map((c) => c.name));\n\t\tconst edges = edgeRows\n\t\t\t.filter((row) => row[0] && row[1] && known.has(row[0]) && known.has(row[1]))\n\t\t\t.map((row) => ({ source: row[0], target: row[1], weight: Number(row[2]) || 0 }));\n\t\tconst flowCount = Number(flowRows[0]?.[0]);\n\t\tgraphRailStore.setObservatory({\n\t\t\tcommunities,\n\t\t\tedges,\n\t\t\t...(Number.isNaN(flowCount) ? {} : { flowCount }),\n\t\t});\n\t} catch (error) {\n\t\t// observatory is cosmetic — startup must never depend on it\n\t\ttrace(`fetch failed: ${String(error)}`);\n\t}\n}\n\n/**\n * Fetch the observatory in a detached child process and hand the result over\n * via a temp file (picked up lazily by the landing panel / rail). In-process\n * hub calls issued after startup can starve behind the session's own HTTP\n * machinery in some environments; a fresh process never does.\n */\nexport function spawnObservatoryFetch(config: GitNexusConfig, repo: string): void {\n\ttry {\n\t\tconst script = buildFetchScript();\n\t\tconst child = spawnWithoutConsole(process.execPath, [\"-e\", script], {\n\t\t\tdetached: true,\n\t\t\tstdio: \"ignore\",\n\t\t\tenv: {\n\t\t\t\t...process.env,\n\t\t\t\tNEXUS_OBS_HUB_URL: config.hubUrl,\n\t\t\t\tNEXUS_OBS_TOKEN: config.token,\n\t\t\t\tNEXUS_OBS_REPO: repo,\n\t\t\t\tNEXUS_OBS_OUT: observatoryCachePath(repo),\n\t\t\t},\n\t\t});\n\t\tchild.unref();\n\t\ttrace(`spawned observatory fetcher pid=${child.pid ?? \"?\"} for ${repo}`);\n\t} catch (error) {\n\t\ttrace(`spawn failed: ${String(error)}`);\n\t}\n}\n\n/** Self-contained fetch script (plain node, no imports beyond fs). */\nfunction buildFetchScript(): string {\n\treturn `\nconst { writeFileSync } = require(\"node:fs\");\nconst hubUrl = process.env.NEXUS_OBS_HUB_URL;\nconst token = process.env.NEXUS_OBS_TOKEN;\nconst repo = process.env.NEXUS_OBS_REPO;\nconst out = process.env.NEXUS_OBS_OUT;\nlet sessionId;\nasync function post(body) {\n\tconst res = await fetch(hubUrl, {\n\t\tmethod: \"POST\",\n\t\theaders: {\n\t\t\t\"content-type\": \"application/json\",\n\t\t\taccept: \"application/json, text/event-stream\",\n\t\t\tauthorization: \"Bearer \" + token,\n\t\t\t...(sessionId ? { \"mcp-session-id\": sessionId } : {}),\n\t\t},\n\t\tbody: JSON.stringify(body),\n\t\tsignal: AbortSignal.timeout(20000),\n\t});\n\tconst sid = res.headers.get(\"mcp-session-id\");\n\tif (sid) sessionId = sid;\n\tconst text = await res.text();\n\tif (res.status === 202) return undefined;\n\tif (!res.ok) throw new Error(\"hub HTTP \" + res.status);\n\tif ((res.headers.get(\"content-type\") || \"\").includes(\"text/event-stream\")) {\n\t\tlet last;\n\t\tfor (const ev of text.split(\"\\\\n\\\\n\")) {\n\t\t\tconst data = ev.split(\"\\\\n\").filter((l) => l.startsWith(\"data:\")).map((l) => l.slice(5).trim()).join(\"\\\\n\");\n\t\t\tif (!data) continue;\n\t\t\ttry { const p = JSON.parse(data); if (p.id !== undefined && p.id !== null) last = p; } catch {}\n\t\t}\n\t\treturn last;\n\t}\n\treturn text ? JSON.parse(text) : undefined;\n}\nlet nextId = 1;\nasync function call(name, args) {\n\tconst r = await post({ jsonrpc: \"2.0\", id: nextId++, method: \"tools/call\", params: { name, arguments: args } });\n\tif (!r || r.error) throw new Error(r && r.error ? r.error.message : \"empty response\");\n\tconst parts = (r.result && r.result.content) || [];\n\treturn parts.filter((p) => p.type === \"text\").map((p) => p.text).join(\"\\\\n\");\n}\nfunction rows(text) {\n\tlet md;\n\ttry { md = JSON.parse((text.split(\"\\\\n\\\\n---\")[0] || \"\").trim()).markdown; } catch { return []; }\n\tif (typeof md !== \"string\") return [];\n\tconst lines = md.split(\"\\\\n\").filter((l) => l.trim().startsWith(\"|\"));\n\treturn lines.slice(2).map((l) => l.split(\"|\").slice(1, -1).map((c) => c.trim()));\n}\n(async () => {\n\tawait post({ jsonrpc: \"2.0\", id: nextId++, method: \"initialize\", params: { protocolVersion: \"2025-06-18\", capabilities: {}, clientInfo: { name: \"nexus-observatory\", version: \"0.1.0\" } } });\n\tawait post({ jsonrpc: \"2.0\", method: \"notifications/initialized\" });\n\tconst communityRows = rows(await call(\"cypher\", { repo, query: \"MATCH (f)-[:CodeRelation {type:'MEMBER_OF'}]->(c:Community) RETURN c.heuristicLabel AS name, count(f) AS members ORDER BY members DESC LIMIT 8\" }));\n\tconst edgeRows = rows(await call(\"cypher\", { repo, query: \"MATCH (a)-[:CodeRelation {type:'MEMBER_OF'}]->(ca:Community), (b)-[:CodeRelation {type:'MEMBER_OF'}]->(cb:Community), (a)-[r:CodeRelation {type:'CALLS'}]->(b) WHERE ca.heuristicLabel <> cb.heuristicLabel RETURN ca.heuristicLabel AS source, cb.heuristicLabel AS target, count(r) AS weight ORDER BY weight DESC LIMIT 16\" }));\n\tconst flowRows = rows(await call(\"cypher\", { repo, query: \"MATCH (p:Process) RETURN count(p) AS flows\" }));\n\tconst communities = communityRows.filter((r) => r[0]).map((r) => ({ name: r[0], members: Number(r[1]) || 0 }));\n\tif (!communities.length) process.exit(0);\n\tconst known = new Set(communities.map((c) => c.name));\n\tconst edges = edgeRows.filter((r) => r[0] && r[1] && known.has(r[0]) && known.has(r[1])).map((r) => ({ source: r[0], target: r[1], weight: Number(r[2]) || 0 }));\n\tconst flowCount = Number(flowRows[0] && flowRows[0][0]);\n\tconst payload = { communities, edges, ...(Number.isNaN(flowCount) ? {} : { flowCount }), fetchedAt: Date.now() };\n\twriteFileSync(out, JSON.stringify(payload));\n\tprocess.exit(0);\n})().catch(() => process.exit(1));\n`;\n}\n"]}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { spawn } from "node:child_process";
|
|
2
1
|
import { appendFileSync } from "node:fs";
|
|
3
2
|
import { graphRailStore } from "../../core/graph-rail-store.js";
|
|
4
3
|
import { observatoryCachePath } from "../../core/observatory-cache.js";
|
|
4
|
+
import { spawnWithoutConsole } from "../../utils/hidden-spawn.js";
|
|
5
5
|
import { GitNexusClient } from "./client.js";
|
|
6
6
|
function trace(message) {
|
|
7
7
|
if (!process.env.NEXUS_TRACE)
|
|
@@ -100,7 +100,7 @@ export async function fetchObservatory(client, repo) {
|
|
|
100
100
|
export function spawnObservatoryFetch(config, repo) {
|
|
101
101
|
try {
|
|
102
102
|
const script = buildFetchScript();
|
|
103
|
-
const child =
|
|
103
|
+
const child = spawnWithoutConsole(process.execPath, ["-e", script], {
|
|
104
104
|
detached: true,
|
|
105
105
|
stdio: "ignore",
|
|
106
106
|
env: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"observatory.js","sourceRoot":"","sources":["../../../src/extensions/gitnexus/observatory.ts"],"names":[],"mappings":"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","sourcesContent":["import { spawn } from \"node:child_process\";\nimport { appendFileSync } from \"node:fs\";\nimport { graphRailStore } from \"../../core/graph-rail-store.ts\";\nimport { observatoryCachePath } from \"../../core/observatory-cache.ts\";\nimport { GitNexusClient } from \"./client.ts\";\nimport type { GitNexusConfig } from \"./config.ts\";\n\nfunction trace(message: string): void {\n\tif (!process.env.NEXUS_TRACE) return;\n\ttry {\n\t\tappendFileSync(\"/tmp/nexus-observatory-trace.log\", `${new Date().toISOString()} ${message}\\n`);\n\t} catch {\n\t\t// tracing is best-effort\n\t}\n}\n\n/**\n * Startup graph observatory: fetches the community map of the current repo\n * (community sizes, cross-community call edges, flow count) and feeds the\n * rail store. Fire-and-forget — failures leave the observatory empty and\n * never surface to the user.\n */\n\nconst COMMUNITY_QUERY =\n\t\"MATCH (f)-[:CodeRelation {type:'MEMBER_OF'}]->(c:Community) \" +\n\t\"RETURN c.heuristicLabel AS name, count(f) AS members ORDER BY members DESC LIMIT 8\";\n\nconst EDGE_QUERY =\n\t\"MATCH (a)-[:CodeRelation {type:'MEMBER_OF'}]->(ca:Community), \" +\n\t\"(b)-[:CodeRelation {type:'MEMBER_OF'}]->(cb:Community), \" +\n\t\"(a)-[r:CodeRelation {type:'CALLS'}]->(b) \" +\n\t\"WHERE ca.heuristicLabel <> cb.heuristicLabel \" +\n\t\"RETURN ca.heuristicLabel AS source, cb.heuristicLabel AS target, count(r) AS weight \" +\n\t\"ORDER BY weight DESC LIMIT 16\";\n\nconst FLOW_QUERY = \"MATCH (p:Process) RETURN count(p) AS flows\";\n\nconst TIMEOUT_MS = 20_000;\n\n/** Parse the hub cypher result: `{\"markdown\": \"| a | b |\\n| --- |...\", \"row_count\": n}`. */\nexport function parseCypherRows(text: string): string[][] {\n\tlet markdown: string;\n\ttry {\n\t\tconst parsed = JSON.parse(text.split(\"\\n\\n---\")[0]?.trim() ?? \"\") as { markdown?: string };\n\t\tif (typeof parsed.markdown !== \"string\") return [];\n\t\tmarkdown = parsed.markdown;\n\t} catch {\n\t\treturn [];\n\t}\n\tconst lines = markdown.split(\"\\n\").filter((line) => line.trim().startsWith(\"|\"));\n\t// Drop header + separator rows.\n\treturn lines.slice(2).map((line) =>\n\t\tline\n\t\t\t.split(\"|\")\n\t\t\t.slice(1, -1)\n\t\t\t.map((cell) => cell.trim()),\n\t);\n}\n\nasync function runQuery(client: GitNexusClient, repo: string, query: string, label: string): Promise<string[][]> {\n\tconst start = Date.now();\n\ttrace(`${label}: start`);\n\ttry {\n\t\tconst result = await client.callTool(\"cypher\", { query, repo }, AbortSignal.timeout(TIMEOUT_MS));\n\t\ttrace(`${label}: ok in ${Date.now() - start}ms`);\n\t\treturn parseCypherRows(GitNexusClient.text(result));\n\t} catch (error) {\n\t\ttrace(`${label}: failed in ${Date.now() - start}ms: ${String(error)}`);\n\t\tthrow error;\n\t}\n}\n\nexport async function fetchObservatory(client: GitNexusClient, repo: string): Promise<void> {\n\ttry {\n\t\ttrace(`fetching for ${repo}`);\n\t\t// Sequential on purpose: parallel POSTs against one hub MCP session stall\n\t\t// behind per-session serialization; three sequential cyphers finish in ~2s.\n\t\tconst communityRows = await runQuery(client, repo, COMMUNITY_QUERY, \"communities\");\n\t\tconst edgeRows = await runQuery(client, repo, EDGE_QUERY, \"edges\");\n\t\tconst flowRows = await runQuery(client, repo, FLOW_QUERY, \"flows\");\n\t\ttrace(`rows: communities=${communityRows.length} edges=${edgeRows.length} flows=${flowRows.length}`);\n\t\tconst communities = communityRows\n\t\t\t.filter((row) => row[0])\n\t\t\t.map((row) => ({ name: row[0], members: Number(row[1]) || 0 }));\n\t\tif (communities.length === 0) return;\n\t\tconst known = new Set(communities.map((c) => c.name));\n\t\tconst edges = edgeRows\n\t\t\t.filter((row) => row[0] && row[1] && known.has(row[0]) && known.has(row[1]))\n\t\t\t.map((row) => ({ source: row[0], target: row[1], weight: Number(row[2]) || 0 }));\n\t\tconst flowCount = Number(flowRows[0]?.[0]);\n\t\tgraphRailStore.setObservatory({\n\t\t\tcommunities,\n\t\t\tedges,\n\t\t\t...(Number.isNaN(flowCount) ? {} : { flowCount }),\n\t\t});\n\t} catch (error) {\n\t\t// observatory is cosmetic — startup must never depend on it\n\t\ttrace(`fetch failed: ${String(error)}`);\n\t}\n}\n\n/**\n * Fetch the observatory in a detached child process and hand the result over\n * via a temp file (picked up lazily by the landing panel / rail). In-process\n * hub calls issued after startup can starve behind the session's own HTTP\n * machinery in some environments; a fresh process never does.\n */\nexport function spawnObservatoryFetch(config: GitNexusConfig, repo: string): void {\n\ttry {\n\t\tconst script = buildFetchScript();\n\t\tconst child = spawn(process.execPath, [\"-e\", script], {\n\t\t\tdetached: true,\n\t\t\tstdio: \"ignore\",\n\t\t\tenv: {\n\t\t\t\t...process.env,\n\t\t\t\tNEXUS_OBS_HUB_URL: config.hubUrl,\n\t\t\t\tNEXUS_OBS_TOKEN: config.token,\n\t\t\t\tNEXUS_OBS_REPO: repo,\n\t\t\t\tNEXUS_OBS_OUT: observatoryCachePath(repo),\n\t\t\t},\n\t\t});\n\t\tchild.unref();\n\t\ttrace(`spawned observatory fetcher pid=${child.pid ?? \"?\"} for ${repo}`);\n\t} catch (error) {\n\t\ttrace(`spawn failed: ${String(error)}`);\n\t}\n}\n\n/** Self-contained fetch script (plain node, no imports beyond fs). */\nfunction buildFetchScript(): string {\n\treturn `\nconst { writeFileSync } = require(\"node:fs\");\nconst hubUrl = process.env.NEXUS_OBS_HUB_URL;\nconst token = process.env.NEXUS_OBS_TOKEN;\nconst repo = process.env.NEXUS_OBS_REPO;\nconst out = process.env.NEXUS_OBS_OUT;\nlet sessionId;\nasync function post(body) {\n\tconst res = await fetch(hubUrl, {\n\t\tmethod: \"POST\",\n\t\theaders: {\n\t\t\t\"content-type\": \"application/json\",\n\t\t\taccept: \"application/json, text/event-stream\",\n\t\t\tauthorization: \"Bearer \" + token,\n\t\t\t...(sessionId ? { \"mcp-session-id\": sessionId } : {}),\n\t\t},\n\t\tbody: JSON.stringify(body),\n\t\tsignal: AbortSignal.timeout(20000),\n\t});\n\tconst sid = res.headers.get(\"mcp-session-id\");\n\tif (sid) sessionId = sid;\n\tconst text = await res.text();\n\tif (res.status === 202) return undefined;\n\tif (!res.ok) throw new Error(\"hub HTTP \" + res.status);\n\tif ((res.headers.get(\"content-type\") || \"\").includes(\"text/event-stream\")) {\n\t\tlet last;\n\t\tfor (const ev of text.split(\"\\\\n\\\\n\")) {\n\t\t\tconst data = ev.split(\"\\\\n\").filter((l) => l.startsWith(\"data:\")).map((l) => l.slice(5).trim()).join(\"\\\\n\");\n\t\t\tif (!data) continue;\n\t\t\ttry { const p = JSON.parse(data); if (p.id !== undefined && p.id !== null) last = p; } catch {}\n\t\t}\n\t\treturn last;\n\t}\n\treturn text ? JSON.parse(text) : undefined;\n}\nlet nextId = 1;\nasync function call(name, args) {\n\tconst r = await post({ jsonrpc: \"2.0\", id: nextId++, method: \"tools/call\", params: { name, arguments: args } });\n\tif (!r || r.error) throw new Error(r && r.error ? r.error.message : \"empty response\");\n\tconst parts = (r.result && r.result.content) || [];\n\treturn parts.filter((p) => p.type === \"text\").map((p) => p.text).join(\"\\\\n\");\n}\nfunction rows(text) {\n\tlet md;\n\ttry { md = JSON.parse((text.split(\"\\\\n\\\\n---\")[0] || \"\").trim()).markdown; } catch { return []; }\n\tif (typeof md !== \"string\") return [];\n\tconst lines = md.split(\"\\\\n\").filter((l) => l.trim().startsWith(\"|\"));\n\treturn lines.slice(2).map((l) => l.split(\"|\").slice(1, -1).map((c) => c.trim()));\n}\n(async () => {\n\tawait post({ jsonrpc: \"2.0\", id: nextId++, method: \"initialize\", params: { protocolVersion: \"2025-06-18\", capabilities: {}, clientInfo: { name: \"nexus-observatory\", version: \"0.1.0\" } } });\n\tawait post({ jsonrpc: \"2.0\", method: \"notifications/initialized\" });\n\tconst communityRows = rows(await call(\"cypher\", { repo, query: \"MATCH (f)-[:CodeRelation {type:'MEMBER_OF'}]->(c:Community) RETURN c.heuristicLabel AS name, count(f) AS members ORDER BY members DESC LIMIT 8\" }));\n\tconst edgeRows = rows(await call(\"cypher\", { repo, query: \"MATCH (a)-[:CodeRelation {type:'MEMBER_OF'}]->(ca:Community), (b)-[:CodeRelation {type:'MEMBER_OF'}]->(cb:Community), (a)-[r:CodeRelation {type:'CALLS'}]->(b) WHERE ca.heuristicLabel <> cb.heuristicLabel RETURN ca.heuristicLabel AS source, cb.heuristicLabel AS target, count(r) AS weight ORDER BY weight DESC LIMIT 16\" }));\n\tconst flowRows = rows(await call(\"cypher\", { repo, query: \"MATCH (p:Process) RETURN count(p) AS flows\" }));\n\tconst communities = communityRows.filter((r) => r[0]).map((r) => ({ name: r[0], members: Number(r[1]) || 0 }));\n\tif (!communities.length) process.exit(0);\n\tconst known = new Set(communities.map((c) => c.name));\n\tconst edges = edgeRows.filter((r) => r[0] && r[1] && known.has(r[0]) && known.has(r[1])).map((r) => ({ source: r[0], target: r[1], weight: Number(r[2]) || 0 }));\n\tconst flowCount = Number(flowRows[0] && flowRows[0][0]);\n\tconst payload = { communities, edges, ...(Number.isNaN(flowCount) ? {} : { flowCount }), fetchedAt: Date.now() };\n\twriteFileSync(out, JSON.stringify(payload));\n\tprocess.exit(0);\n})().catch(() => process.exit(1));\n`;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"observatory.js","sourceRoot":"","sources":["../../../src/extensions/gitnexus/observatory.ts"],"names":[],"mappings":"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","sourcesContent":["import { appendFileSync } from \"node:fs\";\nimport { graphRailStore } from \"../../core/graph-rail-store.ts\";\nimport { observatoryCachePath } from \"../../core/observatory-cache.ts\";\nimport { spawnWithoutConsole } from \"../../utils/hidden-spawn.ts\";\nimport { GitNexusClient } from \"./client.ts\";\nimport type { GitNexusConfig } from \"./config.ts\";\n\nfunction trace(message: string): void {\n\tif (!process.env.NEXUS_TRACE) return;\n\ttry {\n\t\tappendFileSync(\"/tmp/nexus-observatory-trace.log\", `${new Date().toISOString()} ${message}\\n`);\n\t} catch {\n\t\t// tracing is best-effort\n\t}\n}\n\n/**\n * Startup graph observatory: fetches the community map of the current repo\n * (community sizes, cross-community call edges, flow count) and feeds the\n * rail store. Fire-and-forget — failures leave the observatory empty and\n * never surface to the user.\n */\n\nconst COMMUNITY_QUERY =\n\t\"MATCH (f)-[:CodeRelation {type:'MEMBER_OF'}]->(c:Community) \" +\n\t\"RETURN c.heuristicLabel AS name, count(f) AS members ORDER BY members DESC LIMIT 8\";\n\nconst EDGE_QUERY =\n\t\"MATCH (a)-[:CodeRelation {type:'MEMBER_OF'}]->(ca:Community), \" +\n\t\"(b)-[:CodeRelation {type:'MEMBER_OF'}]->(cb:Community), \" +\n\t\"(a)-[r:CodeRelation {type:'CALLS'}]->(b) \" +\n\t\"WHERE ca.heuristicLabel <> cb.heuristicLabel \" +\n\t\"RETURN ca.heuristicLabel AS source, cb.heuristicLabel AS target, count(r) AS weight \" +\n\t\"ORDER BY weight DESC LIMIT 16\";\n\nconst FLOW_QUERY = \"MATCH (p:Process) RETURN count(p) AS flows\";\n\nconst TIMEOUT_MS = 20_000;\n\n/** Parse the hub cypher result: `{\"markdown\": \"| a | b |\\n| --- |...\", \"row_count\": n}`. */\nexport function parseCypherRows(text: string): string[][] {\n\tlet markdown: string;\n\ttry {\n\t\tconst parsed = JSON.parse(text.split(\"\\n\\n---\")[0]?.trim() ?? \"\") as { markdown?: string };\n\t\tif (typeof parsed.markdown !== \"string\") return [];\n\t\tmarkdown = parsed.markdown;\n\t} catch {\n\t\treturn [];\n\t}\n\tconst lines = markdown.split(\"\\n\").filter((line) => line.trim().startsWith(\"|\"));\n\t// Drop header + separator rows.\n\treturn lines.slice(2).map((line) =>\n\t\tline\n\t\t\t.split(\"|\")\n\t\t\t.slice(1, -1)\n\t\t\t.map((cell) => cell.trim()),\n\t);\n}\n\nasync function runQuery(client: GitNexusClient, repo: string, query: string, label: string): Promise<string[][]> {\n\tconst start = Date.now();\n\ttrace(`${label}: start`);\n\ttry {\n\t\tconst result = await client.callTool(\"cypher\", { query, repo }, AbortSignal.timeout(TIMEOUT_MS));\n\t\ttrace(`${label}: ok in ${Date.now() - start}ms`);\n\t\treturn parseCypherRows(GitNexusClient.text(result));\n\t} catch (error) {\n\t\ttrace(`${label}: failed in ${Date.now() - start}ms: ${String(error)}`);\n\t\tthrow error;\n\t}\n}\n\nexport async function fetchObservatory(client: GitNexusClient, repo: string): Promise<void> {\n\ttry {\n\t\ttrace(`fetching for ${repo}`);\n\t\t// Sequential on purpose: parallel POSTs against one hub MCP session stall\n\t\t// behind per-session serialization; three sequential cyphers finish in ~2s.\n\t\tconst communityRows = await runQuery(client, repo, COMMUNITY_QUERY, \"communities\");\n\t\tconst edgeRows = await runQuery(client, repo, EDGE_QUERY, \"edges\");\n\t\tconst flowRows = await runQuery(client, repo, FLOW_QUERY, \"flows\");\n\t\ttrace(`rows: communities=${communityRows.length} edges=${edgeRows.length} flows=${flowRows.length}`);\n\t\tconst communities = communityRows\n\t\t\t.filter((row) => row[0])\n\t\t\t.map((row) => ({ name: row[0], members: Number(row[1]) || 0 }));\n\t\tif (communities.length === 0) return;\n\t\tconst known = new Set(communities.map((c) => c.name));\n\t\tconst edges = edgeRows\n\t\t\t.filter((row) => row[0] && row[1] && known.has(row[0]) && known.has(row[1]))\n\t\t\t.map((row) => ({ source: row[0], target: row[1], weight: Number(row[2]) || 0 }));\n\t\tconst flowCount = Number(flowRows[0]?.[0]);\n\t\tgraphRailStore.setObservatory({\n\t\t\tcommunities,\n\t\t\tedges,\n\t\t\t...(Number.isNaN(flowCount) ? {} : { flowCount }),\n\t\t});\n\t} catch (error) {\n\t\t// observatory is cosmetic — startup must never depend on it\n\t\ttrace(`fetch failed: ${String(error)}`);\n\t}\n}\n\n/**\n * Fetch the observatory in a detached child process and hand the result over\n * via a temp file (picked up lazily by the landing panel / rail). In-process\n * hub calls issued after startup can starve behind the session's own HTTP\n * machinery in some environments; a fresh process never does.\n */\nexport function spawnObservatoryFetch(config: GitNexusConfig, repo: string): void {\n\ttry {\n\t\tconst script = buildFetchScript();\n\t\tconst child = spawnWithoutConsole(process.execPath, [\"-e\", script], {\n\t\t\tdetached: true,\n\t\t\tstdio: \"ignore\",\n\t\t\tenv: {\n\t\t\t\t...process.env,\n\t\t\t\tNEXUS_OBS_HUB_URL: config.hubUrl,\n\t\t\t\tNEXUS_OBS_TOKEN: config.token,\n\t\t\t\tNEXUS_OBS_REPO: repo,\n\t\t\t\tNEXUS_OBS_OUT: observatoryCachePath(repo),\n\t\t\t},\n\t\t});\n\t\tchild.unref();\n\t\ttrace(`spawned observatory fetcher pid=${child.pid ?? \"?\"} for ${repo}`);\n\t} catch (error) {\n\t\ttrace(`spawn failed: ${String(error)}`);\n\t}\n}\n\n/** Self-contained fetch script (plain node, no imports beyond fs). */\nfunction buildFetchScript(): string {\n\treturn `\nconst { writeFileSync } = require(\"node:fs\");\nconst hubUrl = process.env.NEXUS_OBS_HUB_URL;\nconst token = process.env.NEXUS_OBS_TOKEN;\nconst repo = process.env.NEXUS_OBS_REPO;\nconst out = process.env.NEXUS_OBS_OUT;\nlet sessionId;\nasync function post(body) {\n\tconst res = await fetch(hubUrl, {\n\t\tmethod: \"POST\",\n\t\theaders: {\n\t\t\t\"content-type\": \"application/json\",\n\t\t\taccept: \"application/json, text/event-stream\",\n\t\t\tauthorization: \"Bearer \" + token,\n\t\t\t...(sessionId ? { \"mcp-session-id\": sessionId } : {}),\n\t\t},\n\t\tbody: JSON.stringify(body),\n\t\tsignal: AbortSignal.timeout(20000),\n\t});\n\tconst sid = res.headers.get(\"mcp-session-id\");\n\tif (sid) sessionId = sid;\n\tconst text = await res.text();\n\tif (res.status === 202) return undefined;\n\tif (!res.ok) throw new Error(\"hub HTTP \" + res.status);\n\tif ((res.headers.get(\"content-type\") || \"\").includes(\"text/event-stream\")) {\n\t\tlet last;\n\t\tfor (const ev of text.split(\"\\\\n\\\\n\")) {\n\t\t\tconst data = ev.split(\"\\\\n\").filter((l) => l.startsWith(\"data:\")).map((l) => l.slice(5).trim()).join(\"\\\\n\");\n\t\t\tif (!data) continue;\n\t\t\ttry { const p = JSON.parse(data); if (p.id !== undefined && p.id !== null) last = p; } catch {}\n\t\t}\n\t\treturn last;\n\t}\n\treturn text ? JSON.parse(text) : undefined;\n}\nlet nextId = 1;\nasync function call(name, args) {\n\tconst r = await post({ jsonrpc: \"2.0\", id: nextId++, method: \"tools/call\", params: { name, arguments: args } });\n\tif (!r || r.error) throw new Error(r && r.error ? r.error.message : \"empty response\");\n\tconst parts = (r.result && r.result.content) || [];\n\treturn parts.filter((p) => p.type === \"text\").map((p) => p.text).join(\"\\\\n\");\n}\nfunction rows(text) {\n\tlet md;\n\ttry { md = JSON.parse((text.split(\"\\\\n\\\\n---\")[0] || \"\").trim()).markdown; } catch { return []; }\n\tif (typeof md !== \"string\") return [];\n\tconst lines = md.split(\"\\\\n\").filter((l) => l.trim().startsWith(\"|\"));\n\treturn lines.slice(2).map((l) => l.split(\"|\").slice(1, -1).map((c) => c.trim()));\n}\n(async () => {\n\tawait post({ jsonrpc: \"2.0\", id: nextId++, method: \"initialize\", params: { protocolVersion: \"2025-06-18\", capabilities: {}, clientInfo: { name: \"nexus-observatory\", version: \"0.1.0\" } } });\n\tawait post({ jsonrpc: \"2.0\", method: \"notifications/initialized\" });\n\tconst communityRows = rows(await call(\"cypher\", { repo, query: \"MATCH (f)-[:CodeRelation {type:'MEMBER_OF'}]->(c:Community) RETURN c.heuristicLabel AS name, count(f) AS members ORDER BY members DESC LIMIT 8\" }));\n\tconst edgeRows = rows(await call(\"cypher\", { repo, query: \"MATCH (a)-[:CodeRelation {type:'MEMBER_OF'}]->(ca:Community), (b)-[:CodeRelation {type:'MEMBER_OF'}]->(cb:Community), (a)-[r:CodeRelation {type:'CALLS'}]->(b) WHERE ca.heuristicLabel <> cb.heuristicLabel RETURN ca.heuristicLabel AS source, cb.heuristicLabel AS target, count(r) AS weight ORDER BY weight DESC LIMIT 16\" }));\n\tconst flowRows = rows(await call(\"cypher\", { repo, query: \"MATCH (p:Process) RETURN count(p) AS flows\" }));\n\tconst communities = communityRows.filter((r) => r[0]).map((r) => ({ name: r[0], members: Number(r[1]) || 0 }));\n\tif (!communities.length) process.exit(0);\n\tconst known = new Set(communities.map((c) => c.name));\n\tconst edges = edgeRows.filter((r) => r[0] && r[1] && known.has(r[0]) && known.has(r[1])).map((r) => ({ source: r[0], target: r[1], weight: Number(r[2]) || 0 }));\n\tconst flowCount = Number(flowRows[0] && flowRows[0][0]);\n\tconst payload = { communities, edges, ...(Number.isNaN(flowCount) ? {} : { flowCount }), fetchedAt: Date.now() };\n\twriteFileSync(out, JSON.stringify(payload));\n\tprocess.exit(0);\n})().catch(() => process.exit(1));\n`;\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/extensions/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/extensions/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AASnE,eAAO,MAAM,iBAAiB,EAAE,eAAe,EAQ9C,CAAC","sourcesContent":["import type { InlineExtension } from \"../core/extensions/types.ts\";\nimport gitnexusExtension from \"./gitnexus/index.ts\";\nimport mcpExtension from \"./mcp/index.ts\";\nimport subagentExtension from \"./subagent/index.ts\";\nimport subagentRenderExtension from \"./subagent-render.ts\";\nimport webSearchConcurrencyExtension from \"./web-search-concurrency.ts\";\n\n// llama.cpp support lives in ./llama and is kept in tree but not registered.\n// Re-add `{ name: \"llama.cpp\", factory: llamaExtension, hidden: true }` to turn it back on.\nexport const builtInExtensions: InlineExtension[] = [\n\t{ name: \"gitnexus\", factory: gitnexusExtension },\n\t{ name: \"delegated-worker-runtime\", factory: subagentExtension, hidden: true },\n\t// No-op until the user writes an mcp.json; connecting nothing costs nothing.\n\t{ name: \"mcp\", factory: mcpExtension },\n\t// Display-only override for bundled pi-subagents tools; harmless no-op if that package isn't loaded.\n\t{ name: \"subagent-render\", factory: subagentRenderExtension, hidden: true },\n\t{ name: \"web-search-concurrency\", factory: webSearchConcurrencyExtension },\n];\n"]}
|
package/dist/extensions/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import gitnexusExtension from "./gitnexus/index.js";
|
|
2
2
|
import mcpExtension from "./mcp/index.js";
|
|
3
3
|
import subagentExtension from "./subagent/index.js";
|
|
4
|
+
import subagentRenderExtension from "./subagent-render.js";
|
|
4
5
|
import webSearchConcurrencyExtension from "./web-search-concurrency.js";
|
|
5
6
|
// llama.cpp support lives in ./llama and is kept in tree but not registered.
|
|
6
7
|
// Re-add `{ name: "llama.cpp", factory: llamaExtension, hidden: true }` to turn it back on.
|
|
@@ -9,6 +10,8 @@ export const builtInExtensions = [
|
|
|
9
10
|
{ name: "delegated-worker-runtime", factory: subagentExtension, hidden: true },
|
|
10
11
|
// No-op until the user writes an mcp.json; connecting nothing costs nothing.
|
|
11
12
|
{ name: "mcp", factory: mcpExtension },
|
|
13
|
+
// Display-only override for bundled pi-subagents tools; harmless no-op if that package isn't loaded.
|
|
14
|
+
{ name: "subagent-render", factory: subagentRenderExtension, hidden: true },
|
|
12
15
|
{ name: "web-search-concurrency", factory: webSearchConcurrencyExtension },
|
|
13
16
|
];
|
|
14
17
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/extensions/index.ts"],"names":[],"mappings":"AACA,OAAO,iBAAiB,MAAM,qBAAqB,CAAC;AACpD,OAAO,YAAY,MAAM,gBAAgB,CAAC;AAC1C,OAAO,iBAAiB,MAAM,qBAAqB,CAAC;AACpD,OAAO,6BAA6B,MAAM,6BAA6B,CAAC;AAExE,6EAA6E;AAC7E,4FAA4F;AAC5F,MAAM,CAAC,MAAM,iBAAiB,GAAsB;IACnD,EAAE,IAAI,EAAE,UAAU,EAAE,OAAO,EAAE,iBAAiB,EAAE;IAChD,EAAE,IAAI,EAAE,0BAA0B,EAAE,OAAO,EAAE,iBAAiB,EAAE,MAAM,EAAE,IAAI,EAAE;IAC9E,6EAA6E;IAC7E,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE;IACtC,EAAE,IAAI,EAAE,wBAAwB,EAAE,OAAO,EAAE,6BAA6B,EAAE;CAC1E,CAAC","sourcesContent":["import type { InlineExtension } from \"../core/extensions/types.ts\";\nimport gitnexusExtension from \"./gitnexus/index.ts\";\nimport mcpExtension from \"./mcp/index.ts\";\nimport subagentExtension from \"./subagent/index.ts\";\nimport webSearchConcurrencyExtension from \"./web-search-concurrency.ts\";\n\n// llama.cpp support lives in ./llama and is kept in tree but not registered.\n// Re-add `{ name: \"llama.cpp\", factory: llamaExtension, hidden: true }` to turn it back on.\nexport const builtInExtensions: InlineExtension[] = [\n\t{ name: \"gitnexus\", factory: gitnexusExtension },\n\t{ name: \"delegated-worker-runtime\", factory: subagentExtension, hidden: true },\n\t// No-op until the user writes an mcp.json; connecting nothing costs nothing.\n\t{ name: \"mcp\", factory: mcpExtension },\n\t{ name: \"web-search-concurrency\", factory: webSearchConcurrencyExtension },\n];\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/extensions/index.ts"],"names":[],"mappings":"AACA,OAAO,iBAAiB,MAAM,qBAAqB,CAAC;AACpD,OAAO,YAAY,MAAM,gBAAgB,CAAC;AAC1C,OAAO,iBAAiB,MAAM,qBAAqB,CAAC;AACpD,OAAO,uBAAuB,MAAM,sBAAsB,CAAC;AAC3D,OAAO,6BAA6B,MAAM,6BAA6B,CAAC;AAExE,6EAA6E;AAC7E,4FAA4F;AAC5F,MAAM,CAAC,MAAM,iBAAiB,GAAsB;IACnD,EAAE,IAAI,EAAE,UAAU,EAAE,OAAO,EAAE,iBAAiB,EAAE;IAChD,EAAE,IAAI,EAAE,0BAA0B,EAAE,OAAO,EAAE,iBAAiB,EAAE,MAAM,EAAE,IAAI,EAAE;IAC9E,6EAA6E;IAC7E,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE;IACtC,qGAAqG;IACrG,EAAE,IAAI,EAAE,iBAAiB,EAAE,OAAO,EAAE,uBAAuB,EAAE,MAAM,EAAE,IAAI,EAAE;IAC3E,EAAE,IAAI,EAAE,wBAAwB,EAAE,OAAO,EAAE,6BAA6B,EAAE;CAC1E,CAAC","sourcesContent":["import type { InlineExtension } from \"../core/extensions/types.ts\";\nimport gitnexusExtension from \"./gitnexus/index.ts\";\nimport mcpExtension from \"./mcp/index.ts\";\nimport subagentExtension from \"./subagent/index.ts\";\nimport subagentRenderExtension from \"./subagent-render.ts\";\nimport webSearchConcurrencyExtension from \"./web-search-concurrency.ts\";\n\n// llama.cpp support lives in ./llama and is kept in tree but not registered.\n// Re-add `{ name: \"llama.cpp\", factory: llamaExtension, hidden: true }` to turn it back on.\nexport const builtInExtensions: InlineExtension[] = [\n\t{ name: \"gitnexus\", factory: gitnexusExtension },\n\t{ name: \"delegated-worker-runtime\", factory: subagentExtension, hidden: true },\n\t// No-op until the user writes an mcp.json; connecting nothing costs nothing.\n\t{ name: \"mcp\", factory: mcpExtension },\n\t// Display-only override for bundled pi-subagents tools; harmless no-op if that package isn't loaded.\n\t{ name: \"subagent-render\", factory: subagentRenderExtension, hidden: true },\n\t{ name: \"web-search-concurrency\", factory: webSearchConcurrencyExtension },\n];\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"abort.d.ts","sourceRoot":"","sources":["../../../src/extensions/mcp/abort.ts"],"names":[],"mappings":"AAAA,wBAAgB,cAAc,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,IAAI,CAGzD;AAED,wBAAsB,SAAS,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,CAAC,CAAC,CAmCxF","sourcesContent":["export function throwIfAborted(signal?: AbortSignal): void {\n\tif (!signal?.aborted) return;\n\tthrow signal.reason instanceof Error ? signal.reason : new Error(String(signal.reason ?? \"MCP request aborted\"));\n}\n\nexport async function abortable<T>(promise: Promise<T>, signal?: AbortSignal): Promise<T> {\n\tif (!signal) return promise;\n\tif (signal.aborted) {\n\t\t// The operation may have been created before the caller observed the\n\t\t// aborted signal. Consume a later rejection before returning the abort.\n\t\tvoid promise.catch(() => {});\n\t}\n\tthrowIfAborted(signal);\n\treturn await new Promise<T>((resolve, reject) => {\n\t\tlet settled = false;\n\t\tconst cleanup = () => signal.removeEventListener(\"abort\", onAbort);\n\t\tconst onAbort = () => {\n\t\t\tif (settled) return;\n\t\t\tsettled = true;\n\t\t\tcleanup();\n\t\t\treject(\n\t\t\t\tsignal.reason instanceof Error ? signal.reason : new Error(String(signal.reason ?? \"MCP request aborted\")),\n\t\t\t);\n\t\t};\n\t\tsignal.addEventListener(\"abort\", onAbort, { once: true });\n\t\tpromise.then(\n\t\t\t(value) => {\n\t\t\t\tif (settled) return;\n\t\t\t\tsettled = true;\n\t\t\t\tcleanup();\n\t\t\t\tresolve(value);\n\t\t\t},\n\t\t\t(error) => {\n\t\t\t\tif (settled) return;\n\t\t\t\tsettled = true;\n\t\t\t\tcleanup();\n\t\t\t\treject(error);\n\t\t\t},\n\t\t);\n\t});\n}\n"]}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
export function throwIfAborted(signal) {
|
|
2
|
+
if (!signal?.aborted)
|
|
3
|
+
return;
|
|
4
|
+
throw signal.reason instanceof Error ? signal.reason : new Error(String(signal.reason ?? "MCP request aborted"));
|
|
5
|
+
}
|
|
6
|
+
export async function abortable(promise, signal) {
|
|
7
|
+
if (!signal)
|
|
8
|
+
return promise;
|
|
9
|
+
if (signal.aborted) {
|
|
10
|
+
// The operation may have been created before the caller observed the
|
|
11
|
+
// aborted signal. Consume a later rejection before returning the abort.
|
|
12
|
+
void promise.catch(() => { });
|
|
13
|
+
}
|
|
14
|
+
throwIfAborted(signal);
|
|
15
|
+
return await new Promise((resolve, reject) => {
|
|
16
|
+
let settled = false;
|
|
17
|
+
const cleanup = () => signal.removeEventListener("abort", onAbort);
|
|
18
|
+
const onAbort = () => {
|
|
19
|
+
if (settled)
|
|
20
|
+
return;
|
|
21
|
+
settled = true;
|
|
22
|
+
cleanup();
|
|
23
|
+
reject(signal.reason instanceof Error ? signal.reason : new Error(String(signal.reason ?? "MCP request aborted")));
|
|
24
|
+
};
|
|
25
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
26
|
+
promise.then((value) => {
|
|
27
|
+
if (settled)
|
|
28
|
+
return;
|
|
29
|
+
settled = true;
|
|
30
|
+
cleanup();
|
|
31
|
+
resolve(value);
|
|
32
|
+
}, (error) => {
|
|
33
|
+
if (settled)
|
|
34
|
+
return;
|
|
35
|
+
settled = true;
|
|
36
|
+
cleanup();
|
|
37
|
+
reject(error);
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=abort.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"abort.js","sourceRoot":"","sources":["../../../src/extensions/mcp/abort.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,cAAc,CAAC,MAAoB,EAAQ;IAC1D,IAAI,CAAC,MAAM,EAAE,OAAO;QAAE,OAAO;IAC7B,MAAM,MAAM,CAAC,MAAM,YAAY,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,IAAI,qBAAqB,CAAC,CAAC,CAAC;AAAA,CACjH;AAED,MAAM,CAAC,KAAK,UAAU,SAAS,CAAI,OAAmB,EAAE,MAAoB,EAAc;IACzF,IAAI,CAAC,MAAM;QAAE,OAAO,OAAO,CAAC;IAC5B,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;QACpB,qEAAqE;QACrE,wEAAwE;QACxE,KAAK,OAAO,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAC,CAAC,CAAC,CAAC;IAC9B,CAAC;IACD,cAAc,CAAC,MAAM,CAAC,CAAC;IACvB,OAAO,MAAM,IAAI,OAAO,CAAI,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,CAAC;QAChD,IAAI,OAAO,GAAG,KAAK,CAAC;QACpB,MAAM,OAAO,GAAG,GAAG,EAAE,CAAC,MAAM,CAAC,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QACnE,MAAM,OAAO,GAAG,GAAG,EAAE,CAAC;YACrB,IAAI,OAAO;gBAAE,OAAO;YACpB,OAAO,GAAG,IAAI,CAAC;YACf,OAAO,EAAE,CAAC;YACV,MAAM,CACL,MAAM,CAAC,MAAM,YAAY,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,IAAI,qBAAqB,CAAC,CAAC,CAC1G,CAAC;QAAA,CACF,CAAC;QACF,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;QAC1D,OAAO,CAAC,IAAI,CACX,CAAC,KAAK,EAAE,EAAE,CAAC;YACV,IAAI,OAAO;gBAAE,OAAO;YACpB,OAAO,GAAG,IAAI,CAAC;YACf,OAAO,EAAE,CAAC;YACV,OAAO,CAAC,KAAK,CAAC,CAAC;QAAA,CACf,EACD,CAAC,KAAK,EAAE,EAAE,CAAC;YACV,IAAI,OAAO;gBAAE,OAAO;YACpB,OAAO,GAAG,IAAI,CAAC;YACf,OAAO,EAAE,CAAC;YACV,MAAM,CAAC,KAAK,CAAC,CAAC;QAAA,CACd,CACD,CAAC;IAAA,CACF,CAAC,CAAC;AAAA,CACH","sourcesContent":["export function throwIfAborted(signal?: AbortSignal): void {\n\tif (!signal?.aborted) return;\n\tthrow signal.reason instanceof Error ? signal.reason : new Error(String(signal.reason ?? \"MCP request aborted\"));\n}\n\nexport async function abortable<T>(promise: Promise<T>, signal?: AbortSignal): Promise<T> {\n\tif (!signal) return promise;\n\tif (signal.aborted) {\n\t\t// The operation may have been created before the caller observed the\n\t\t// aborted signal. Consume a later rejection before returning the abort.\n\t\tvoid promise.catch(() => {});\n\t}\n\tthrowIfAborted(signal);\n\treturn await new Promise<T>((resolve, reject) => {\n\t\tlet settled = false;\n\t\tconst cleanup = () => signal.removeEventListener(\"abort\", onAbort);\n\t\tconst onAbort = () => {\n\t\t\tif (settled) return;\n\t\t\tsettled = true;\n\t\t\tcleanup();\n\t\t\treject(\n\t\t\t\tsignal.reason instanceof Error ? signal.reason : new Error(String(signal.reason ?? \"MCP request aborted\")),\n\t\t\t);\n\t\t};\n\t\tsignal.addEventListener(\"abort\", onAbort, { once: true });\n\t\tpromise.then(\n\t\t\t(value) => {\n\t\t\t\tif (settled) return;\n\t\t\t\tsettled = true;\n\t\t\t\tcleanup();\n\t\t\t\tresolve(value);\n\t\t\t},\n\t\t\t(error) => {\n\t\t\t\tif (settled) return;\n\t\t\t\tsettled = true;\n\t\t\t\tcleanup();\n\t\t\t\treject(error);\n\t\t\t},\n\t\t);\n\t});\n}\n"]}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export declare function getConfigDirName(): string;
|
|
2
|
+
export declare function getAgentDir(): string;
|
|
3
|
+
export declare function getAgentPath(...segments: string[]): string;
|
|
4
|
+
export declare function getAppName(): string;
|
|
5
|
+
export declare function getAppClientUri(): string;
|
|
6
|
+
//# sourceMappingURL=agent-dir.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent-dir.d.ts","sourceRoot":"","sources":["../../../src/extensions/mcp/agent-dir.ts"],"names":[],"mappings":"AAGA,wBAAgB,gBAAgB,IAAI,MAAM,CAEzC;AAED,wBAAgB,WAAW,IAAI,MAAM,CAEpC;AAED,wBAAgB,YAAY,CAAC,GAAG,QAAQ,EAAE,MAAM,EAAE,GAAG,MAAM,CAE1D;AAED,wBAAgB,UAAU,IAAI,MAAM,CAEnC;AAED,wBAAgB,eAAe,IAAI,MAAM,CAExC","sourcesContent":["import { join } from \"node:path\";\nimport { CONFIG_DIR_NAME, getAgentDir as getNexusAgentDir } from \"../../config.ts\";\n\nexport function getConfigDirName(): string {\n\treturn CONFIG_DIR_NAME;\n}\n\nexport function getAgentDir(): string {\n\treturn getNexusAgentDir();\n}\n\nexport function getAgentPath(...segments: string[]): string {\n\treturn join(getAgentDir(), ...segments);\n}\n\nexport function getAppName(): string {\n\treturn \"Nexus\";\n}\n\nexport function getAppClientUri(): string {\n\treturn \"https://github.com/Akon-Labs/nexus-cli\";\n}\n"]}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { CONFIG_DIR_NAME, getAgentDir as getNexusAgentDir } from "../../config.js";
|
|
3
|
+
export function getConfigDirName() {
|
|
4
|
+
return CONFIG_DIR_NAME;
|
|
5
|
+
}
|
|
6
|
+
export function getAgentDir() {
|
|
7
|
+
return getNexusAgentDir();
|
|
8
|
+
}
|
|
9
|
+
export function getAgentPath(...segments) {
|
|
10
|
+
return join(getAgentDir(), ...segments);
|
|
11
|
+
}
|
|
12
|
+
export function getAppName() {
|
|
13
|
+
return "Nexus";
|
|
14
|
+
}
|
|
15
|
+
export function getAppClientUri() {
|
|
16
|
+
return "https://github.com/Akon-Labs/nexus-cli";
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=agent-dir.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent-dir.js","sourceRoot":"","sources":["../../../src/extensions/mcp/agent-dir.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,eAAe,EAAE,WAAW,IAAI,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAEnF,MAAM,UAAU,gBAAgB,GAAW;IAC1C,OAAO,eAAe,CAAC;AAAA,CACvB;AAED,MAAM,UAAU,WAAW,GAAW;IACrC,OAAO,gBAAgB,EAAE,CAAC;AAAA,CAC1B;AAED,MAAM,UAAU,YAAY,CAAC,GAAG,QAAkB,EAAU;IAC3D,OAAO,IAAI,CAAC,WAAW,EAAE,EAAE,GAAG,QAAQ,CAAC,CAAC;AAAA,CACxC;AAED,MAAM,UAAU,UAAU,GAAW;IACpC,OAAO,OAAO,CAAC;AAAA,CACf;AAED,MAAM,UAAU,eAAe,GAAW;IACzC,OAAO,wCAAwC,CAAC;AAAA,CAChD","sourcesContent":["import { join } from \"node:path\";\nimport { CONFIG_DIR_NAME, getAgentDir as getNexusAgentDir } from \"../../config.ts\";\n\nexport function getConfigDirName(): string {\n\treturn CONFIG_DIR_NAME;\n}\n\nexport function getAgentDir(): string {\n\treturn getNexusAgentDir();\n}\n\nexport function getAgentPath(...segments: string[]): string {\n\treturn join(getAgentDir(), ...segments);\n}\n\nexport function getAppName(): string {\n\treturn \"Nexus\";\n}\n\nexport function getAppClientUri(): string {\n\treturn \"https://github.com/Akon-Labs/nexus-cli\";\n}\n"]}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { McpConfig } from "./types.ts";
|
|
2
|
+
export interface AgentPluginSummary {
|
|
3
|
+
path: string;
|
|
4
|
+
name?: string;
|
|
5
|
+
serverCount: number;
|
|
6
|
+
}
|
|
7
|
+
export declare function loadAgentPluginConfigs(paths: unknown, cwd?: string): McpConfig;
|
|
8
|
+
export declare function getAgentPluginSummaries(paths: unknown, cwd?: string): AgentPluginSummary[];
|
|
9
|
+
//# sourceMappingURL=agent-plugin-loader.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent-plugin-loader.d.ts","sourceRoot":"","sources":["../../../src/extensions/mcp/agent-plugin-loader.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAe,MAAM,YAAY,CAAC;AA2BzD,MAAM,WAAW,kBAAkB;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;CACpB;AAED,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,SAAgB,GAAG,SAAS,CAgBrF;AAED,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,SAAgB,GAAG,kBAAkB,EAAE,CAWjG","sourcesContent":["import { existsSync, readFileSync, realpathSync, statSync } from \"node:fs\";\nimport { isAbsolute, relative, resolve, sep } from \"node:path\";\nimport { getAgentPath } from \"./agent-dir.ts\";\nimport type { McpConfig, ServerEntry } from \"./types.ts\";\n\nconst PLUGIN_SCHEMA = \"https://agent-plugins.org/schemas/1.0.0/plugin.schema.json\";\nconst MCP_SCHEMA = \"https://agent-plugins.org/schemas/1.0.0/mcp.schema.json\";\nconst PLUGIN_NAME_PATTERN = /^(?!.*(?:--|\\.\\.))[a-z0-9](?:[a-z0-9.-]*[a-z0-9])?$/;\nconst PLUGIN_MANIFEST_FIELDS = new Set([\n\t\"$schema\",\n\t\"name\",\n\t\"version\",\n\t\"description\",\n\t\"author\",\n\t\"homepage\",\n\t\"repository\",\n\t\"license\",\n\t\"keywords\",\n\t\"extensions\",\n]);\nconst MCP_CONFIG_FIELDS = new Set([\"$schema\", \"mcpServers\"]);\nconst STDIO_FIELDS = new Set([\"type\", \"command\", \"args\", \"env\", \"cwd\"]);\nconst HTTP_FIELDS = new Set([\"type\", \"url\", \"headers\"]);\nconst PLUGIN_ROOT_PLACEHOLDER = `\\${PLUGIN_ROOT}`;\nconst PLUGIN_DATA_PLACEHOLDER = `\\${PLUGIN_DATA}`;\n\ninterface AgentPluginManifest {\n\tname: string;\n}\n\nexport interface AgentPluginSummary {\n\tpath: string;\n\tname?: string;\n\tserverCount: number;\n}\n\nexport function loadAgentPluginConfigs(paths: unknown, cwd = process.cwd()): McpConfig {\n\tconst mcpServers: Record<string, ServerEntry> = {};\n\tfor (const pluginPath of getPluginPaths(paths)) {\n\t\tconst loaded = loadAgentPluginMcpConfig(pluginPath, cwd);\n\t\tif (!loaded) continue;\n\t\tfor (const [serverName, server] of Object.entries(loaded.mcpServers)) {\n\t\t\tif (mcpServers[serverName]) {\n\t\t\t\tconsole.warn(\n\t\t\t\t\t`Agent Plugin at ${resolvePluginPath(pluginPath, cwd)} skips duplicate normalized MCP server ${serverName}`,\n\t\t\t\t);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tmcpServers[serverName] = server;\n\t\t}\n\t}\n\treturn { mcpServers };\n}\n\nexport function getAgentPluginSummaries(paths: unknown, cwd = process.cwd()): AgentPluginSummary[] {\n\treturn getPluginPaths(paths).map((path) => {\n\t\tconst pluginRoot = resolvePluginPath(path, cwd);\n\t\tconst loaded = loadAgentPluginMcpConfig(path, cwd);\n\t\tconst manifest = loaded ? readPluginManifest(pluginRoot, false) : null;\n\t\treturn {\n\t\t\tpath: pluginRoot,\n\t\t\t...(manifest?.name ? { name: manifest.name } : {}),\n\t\t\tserverCount: loaded ? Object.keys(loaded.mcpServers).length : 0,\n\t\t};\n\t});\n}\n\nfunction getPluginPaths(paths: unknown): string[] {\n\treturn Array.isArray(paths) ? paths.filter((path): path is string => typeof path === \"string\") : [];\n}\n\nfunction loadAgentPluginMcpConfig(path: string, cwd: string): McpConfig | null {\n\tconst pluginRoot = resolvePluginPath(path, cwd);\n\tconst manifest = readPluginManifest(pluginRoot, true);\n\tif (!manifest) return null;\n\n\tconst mcpPath = resolve(pluginRoot, \"mcp.json\");\n\tif (!existsSync(mcpPath)) return { mcpServers: {} };\n\tif (!statSync(mcpPath).isFile()) {\n\t\tconsole.warn(`Agent Plugin ${manifest.name} has invalid MCP config: mcp.json is not a regular file`);\n\t\treturn { mcpServers: {} };\n\t}\n\n\tlet raw: unknown;\n\ttry {\n\t\traw = JSON.parse(readFileSync(mcpPath, \"utf8\"));\n\t} catch (error) {\n\t\tconsole.warn(`Agent Plugin ${manifest.name} has invalid MCP config: failed to parse mcp.json`, error);\n\t\treturn { mcpServers: {} };\n\t}\n\n\treturn translateAgentPluginMcpConfig(raw, manifest, pluginRoot);\n}\n\nfunction readPluginManifest(pluginRoot: string, report: boolean): AgentPluginManifest | null {\n\tconst manifestPath = resolve(pluginRoot, \"plugin.json\");\n\tif (!existsSync(manifestPath)) {\n\t\tif (report) console.warn(`Agent Plugin at ${pluginRoot} is invalid: missing plugin.json`);\n\t\treturn null;\n\t}\n\tif (!statSync(manifestPath).isFile()) {\n\t\tif (report) console.warn(`Agent Plugin at ${pluginRoot} is invalid: plugin.json is not a regular file`);\n\t\treturn null;\n\t}\n\n\tlet raw: unknown;\n\ttry {\n\t\traw = JSON.parse(readFileSync(manifestPath, \"utf8\"));\n\t} catch (error) {\n\t\tif (report) console.warn(`Agent Plugin at ${pluginRoot} is invalid: failed to parse plugin.json`, error);\n\t\treturn null;\n\t}\n\tif (!raw || typeof raw !== \"object\" || Array.isArray(raw)) {\n\t\tif (report) console.warn(`Agent Plugin at ${pluginRoot} is invalid: plugin.json must be an object`);\n\t\treturn null;\n\t}\n\n\tconst manifest = raw as Record<string, unknown>;\n\tfor (const key of Object.keys(manifest)) {\n\t\tif (!PLUGIN_MANIFEST_FIELDS.has(key) && report) {\n\t\t\tconsole.warn(`Agent Plugin at ${pluginRoot} ignores unknown plugin.json field: ${key}`);\n\t\t}\n\t}\n\tif (manifest.$schema !== PLUGIN_SCHEMA) {\n\t\tif (report) console.warn(`Agent Plugin at ${pluginRoot} is invalid: unsupported plugin.json $schema`);\n\t\treturn null;\n\t}\n\tif (\n\t\ttypeof manifest.name !== \"string\" ||\n\t\tmanifest.name.length < 1 ||\n\t\tmanifest.name.length > 64 ||\n\t\t!PLUGIN_NAME_PATTERN.test(manifest.name)\n\t) {\n\t\tif (report) console.warn(`Agent Plugin at ${pluginRoot} is invalid: plugin.json name is invalid`);\n\t\treturn null;\n\t}\n\tif (\n\t\tmanifest.extensions !== undefined &&\n\t\t(!manifest.extensions || typeof manifest.extensions !== \"object\" || Array.isArray(manifest.extensions))\n\t) {\n\t\tif (report) console.warn(`Agent Plugin ${manifest.name} ignores non-object plugin.json extensions`);\n\t}\n\n\treturn { name: manifest.name };\n}\n\nfunction translateAgentPluginMcpConfig(raw: unknown, manifest: AgentPluginManifest, pluginRoot: string): McpConfig {\n\tif (!raw || typeof raw !== \"object\" || Array.isArray(raw)) {\n\t\tconsole.warn(`Agent Plugin ${manifest.name} has invalid MCP config: mcp.json must be an object`);\n\t\treturn { mcpServers: {} };\n\t}\n\n\tconst mcpConfig = raw as Record<string, unknown>;\n\tfor (const key of Object.keys(mcpConfig)) {\n\t\tif (!MCP_CONFIG_FIELDS.has(key)) {\n\t\t\tconsole.warn(`Agent Plugin ${manifest.name} has invalid MCP config: unknown top-level field ${key}`);\n\t\t\treturn { mcpServers: {} };\n\t\t}\n\t}\n\tif (mcpConfig.$schema !== MCP_SCHEMA) {\n\t\tconsole.warn(`Agent Plugin ${manifest.name} has invalid MCP config: unsupported mcp.json $schema`);\n\t\treturn { mcpServers: {} };\n\t}\n\tif (!mcpConfig.mcpServers || typeof mcpConfig.mcpServers !== \"object\" || Array.isArray(mcpConfig.mcpServers)) {\n\t\tconsole.warn(`Agent Plugin ${manifest.name} has invalid MCP config: mcpServers must be an object`);\n\t\treturn { mcpServers: {} };\n\t}\n\n\tconst mcpServers: Record<string, ServerEntry> = {};\n\tfor (const [serverName, entry] of Object.entries(mcpConfig.mcpServers)) {\n\t\tconst translated = translateAgentPluginServer(manifest, pluginRoot, serverName, entry);\n\t\tif (!translated) continue;\n\n\t\tconst normalizedName = formatAgentPluginServerName(manifest.name, serverName);\n\t\tif (mcpServers[normalizedName]) {\n\t\t\tconsole.warn(\n\t\t\t\t`Agent Plugin ${manifest.name} skips invalid MCP server ${serverName}: normalized server name ${normalizedName} already exists`,\n\t\t\t);\n\t\t\tcontinue;\n\t\t}\n\t\tmcpServers[normalizedName] = translated;\n\t}\n\treturn { mcpServers };\n}\n\nfunction translateAgentPluginServer(\n\tmanifest: AgentPluginManifest,\n\tpluginRoot: string,\n\tserverName: string,\n\tentry: unknown,\n): ServerEntry | null {\n\tif (!entry || typeof entry !== \"object\" || Array.isArray(entry)) {\n\t\tconsole.warn(`Agent Plugin ${manifest.name} skips invalid MCP server ${serverName}: entry must be an object`);\n\t\treturn null;\n\t}\n\n\tconst raw = entry as Record<string, unknown>;\n\tif (raw.type === \"stdio\") return translateStdioServer(manifest, pluginRoot, serverName, raw);\n\tif (raw.type === \"streamable-http\" || raw.type === \"sse\")\n\t\treturn translateHttpServer(manifest, serverName, raw, raw.type);\n\n\tconsole.warn(`Agent Plugin ${manifest.name} skips invalid MCP server ${serverName}: unsupported type`);\n\treturn null;\n}\n\nfunction translateStdioServer(\n\tmanifest: AgentPluginManifest,\n\tpluginRoot: string,\n\tserverName: string,\n\traw: Record<string, unknown>,\n): ServerEntry | null {\n\tfor (const key of Object.keys(raw)) {\n\t\tif (!STDIO_FIELDS.has(key)) return skipServer(manifest, serverName, `unknown field ${key}`);\n\t}\n\tif (typeof raw.command !== \"string\" || raw.command.length === 0)\n\t\treturn skipServer(manifest, serverName, \"command must be a non-empty string\");\n\tif (!isBareCommand(raw.command) && !raw.command.startsWith(\"./\"))\n\t\treturn skipServer(manifest, serverName, \"command must be bare or plugin-relative\");\n\n\tconst args = translateStringArray(raw.args, manifest, serverName, \"args\");\n\tif (args === null) return null;\n\tconst env = translateEnv(raw.env, manifest, serverName);\n\tif (env === null) return null;\n\n\tconst command = raw.command.startsWith(\"./\")\n\t\t? resolveExistingContainedPath(pluginRoot, raw.command, pluginRoot)\n\t\t: raw.command;\n\tif (command === null) return skipServer(manifest, serverName, \"command must stay inside the plugin directory\");\n\n\tconst pluginDataDir = getAgentPath(\"agent-plugin-data\", manifest.name);\n\tconst cwd = resolvePluginCwd(raw.cwd, pluginRoot, pluginDataDir);\n\tif (cwd === null)\n\t\treturn skipServer(manifest, serverName, \"cwd must be plugin-relative, PLUGIN_ROOT-rooted, or PLUGIN_DATA-rooted\");\n\n\treturn {\n\t\tcommand,\n\t\targs: args.map((value) => expandPluginPlaceholders(value, pluginRoot, pluginDataDir)),\n\t\tenv: {\n\t\t\t...Object.fromEntries(\n\t\t\t\tObject.entries(env).map(([key, value]) => [\n\t\t\t\t\tkey,\n\t\t\t\t\texpandPluginPlaceholders(value, pluginRoot, pluginDataDir),\n\t\t\t\t]),\n\t\t\t),\n\t\t\tPLUGIN_ROOT: pluginRoot,\n\t\t\tPLUGIN_DATA: pluginDataDir,\n\t\t},\n\t\tcwd,\n\t\tpluginDataDir,\n\t\tliteralEnv: true,\n\t};\n}\n\nfunction translateHttpServer(\n\tmanifest: AgentPluginManifest,\n\tserverName: string,\n\traw: Record<string, unknown>,\n\ttype: \"streamable-http\" | \"sse\",\n): ServerEntry | null {\n\tfor (const key of Object.keys(raw)) {\n\t\tif (!HTTP_FIELDS.has(key)) return skipServer(manifest, serverName, `unknown field ${key}`);\n\t}\n\tif (typeof raw.url !== \"string\" || raw.url.length === 0)\n\t\treturn skipServer(manifest, serverName, \"url must be a non-empty string\");\n\tif (!isValidAgentPluginUrl(raw.url))\n\t\treturn skipServer(manifest, serverName, \"url must be an allowed absolute HTTP(S) URL\");\n\tconst headers = translateHeaders(raw.headers, manifest, serverName);\n\tif (headers === null) return null;\n\n\treturn {\n\t\turl: raw.url,\n\t\thttpTransport: type,\n\t\t...(headers ? { headers } : {}),\n\t};\n}\n\nfunction formatAgentPluginServerName(pluginName: string, serverName: string): string {\n\tconst pluginPart = pluginName.replace(/[^A-Za-z0-9_-]+/g, \"_\").replace(/^[_-]+|[_-]+$/g, \"\") || \"plugin\";\n\tconst serverPart = serverName.replace(/[^A-Za-z0-9_-]+/g, \"_\").replace(/^[_-]+|[_-]+$/g, \"\") || \"server\";\n\treturn `${pluginPart}__${serverPart}`;\n}\n\nfunction skipServer(manifest: AgentPluginManifest, serverName: string, reason: string): null {\n\tconsole.warn(`Agent Plugin ${manifest.name} skips invalid MCP server ${serverName}: ${reason}`);\n\treturn null;\n}\n\nfunction translateStringArray(\n\tvalue: unknown,\n\tmanifest: AgentPluginManifest,\n\tserverName: string,\n\tfield: string,\n): string[] | null {\n\tif (value === undefined) return [];\n\tif (!Array.isArray(value) || value.some((item) => typeof item !== \"string\")) {\n\t\tconsole.warn(\n\t\t\t`Agent Plugin ${manifest.name} skips invalid MCP server ${serverName}: ${field} must be an array of strings`,\n\t\t);\n\t\treturn null;\n\t}\n\treturn value;\n}\n\nfunction translateEnv(\n\tvalue: unknown,\n\tmanifest: AgentPluginManifest,\n\tserverName: string,\n): Record<string, string> | null {\n\tif (value === undefined) return {};\n\tif (!value || typeof value !== \"object\" || Array.isArray(value)) {\n\t\tconsole.warn(\n\t\t\t`Agent Plugin ${manifest.name} skips invalid MCP server ${serverName}: env must be an object of strings`,\n\t\t);\n\t\treturn null;\n\t}\n\tconst env: Record<string, string> = {};\n\tfor (const [key, entry] of Object.entries(value)) {\n\t\tif (key === \"PLUGIN_ROOT\" || key === \"PLUGIN_DATA\") {\n\t\t\tconsole.warn(\n\t\t\t\t`Agent Plugin ${manifest.name} skips invalid MCP server ${serverName}: env must not define ${key}`,\n\t\t\t);\n\t\t\treturn null;\n\t\t}\n\t\tif (typeof entry !== \"string\") {\n\t\t\tconsole.warn(\n\t\t\t\t`Agent Plugin ${manifest.name} skips invalid MCP server ${serverName}: env values must be strings`,\n\t\t\t);\n\t\t\treturn null;\n\t\t}\n\t\tenv[key] = entry;\n\t}\n\treturn env;\n}\n\nfunction translateHeaders(\n\tvalue: unknown,\n\tmanifest: AgentPluginManifest,\n\tserverName: string,\n): Record<string, string> | undefined | null {\n\tif (value === undefined) return undefined;\n\tif (!value || typeof value !== \"object\" || Array.isArray(value)) {\n\t\tconsole.warn(\n\t\t\t`Agent Plugin ${manifest.name} skips invalid MCP server ${serverName}: headers must be an object of strings`,\n\t\t);\n\t\treturn null;\n\t}\n\tconst headers: Record<string, string> = {};\n\tconst seen = new Set<string>();\n\tfor (const [key, entry] of Object.entries(value)) {\n\t\tif (typeof entry !== \"string\") {\n\t\t\tconsole.warn(\n\t\t\t\t`Agent Plugin ${manifest.name} skips invalid MCP server ${serverName}: header values must be strings`,\n\t\t\t);\n\t\t\treturn null;\n\t\t}\n\t\tconst normalized = key.toLowerCase();\n\t\tif (seen.has(normalized)) {\n\t\t\tconsole.warn(`Agent Plugin ${manifest.name} skips invalid MCP server ${serverName}: duplicate header ${key}`);\n\t\t\treturn null;\n\t\t}\n\t\tseen.add(normalized);\n\t\theaders[key] = entry;\n\t}\n\ttry {\n\t\tnew Headers(headers);\n\t} catch {\n\t\tconsole.warn(\n\t\t\t`Agent Plugin ${manifest.name} skips invalid MCP server ${serverName}: headers are not valid HTTP fields`,\n\t\t);\n\t\treturn null;\n\t}\n\treturn Object.keys(headers).length > 0 ? headers : undefined;\n}\n\nfunction resolvePluginPath(path: string, cwd: string): string {\n\tif (path === \"~\") return resolve(process.env.HOME ?? \"\", \".\");\n\tif (path.startsWith(\"~/\")) return resolve(process.env.HOME ?? \"\", path.slice(2));\n\treturn isAbsolute(path) ? resolve(path) : resolve(cwd, path);\n}\n\nfunction isBareCommand(command: string): boolean {\n\treturn (\n\t\t!command.includes(\"/\") &&\n\t\t!command.includes(\"\\\\\") &&\n\t\t!command.includes(PLUGIN_ROOT_PLACEHOLDER) &&\n\t\t!command.includes(PLUGIN_DATA_PLACEHOLDER)\n\t);\n}\n\nfunction resolvePluginCwd(value: unknown, pluginRoot: string, pluginDataDir: string): string | null {\n\tif (value === undefined) return pluginRoot;\n\tif (typeof value !== \"string\") return null;\n\tif (value.startsWith(\"./\")) return resolveExistingContainedPath(pluginRoot, value, pluginRoot);\n\tif (value === PLUGIN_ROOT_PLACEHOLDER || value.startsWith(`${PLUGIN_ROOT_PLACEHOLDER}/`)) {\n\t\treturn resolveExistingContainedPath(pluginRoot, value.replace(PLUGIN_ROOT_PLACEHOLDER, \".\"), pluginRoot);\n\t}\n\tif (value === PLUGIN_DATA_PLACEHOLDER || value.startsWith(`${PLUGIN_DATA_PLACEHOLDER}/`)) {\n\t\tif (value === PLUGIN_DATA_PLACEHOLDER) return pluginDataDir;\n\t\treturn resolveExistingContainedPath(pluginDataDir, value.replace(PLUGIN_DATA_PLACEHOLDER, \".\"), pluginDataDir);\n\t}\n\treturn null;\n}\n\nfunction resolveContainedPath(root: string, value: string, containmentRoot: string): string | null {\n\tconst resolved = resolve(root, value);\n\tconst rel = relative(containmentRoot, resolved);\n\tif (rel === \"\" || (!rel.startsWith(\"..\") && !rel.startsWith(sep) && !isAbsolute(rel))) return resolved;\n\treturn null;\n}\n\nfunction resolveExistingContainedPath(root: string, value: string, containmentRoot: string): string | null {\n\tconst resolved = resolveContainedPath(root, value, containmentRoot);\n\tif (!resolved) return null;\n\ttry {\n\t\tconst realRoot = realpathSync(containmentRoot);\n\t\tconst realResolved = realpathSync(resolved);\n\t\tconst rel = relative(realRoot, realResolved);\n\t\treturn rel === \"\" || (!rel.startsWith(\"..\") && !rel.startsWith(sep) && !isAbsolute(rel)) ? resolved : null;\n\t} catch {\n\t\treturn null;\n\t}\n}\n\nfunction expandPluginPlaceholders(value: string, pluginRoot: string, pluginDataDir: string): string {\n\treturn value.replaceAll(PLUGIN_ROOT_PLACEHOLDER, pluginRoot).replaceAll(PLUGIN_DATA_PLACEHOLDER, pluginDataDir);\n}\n\nfunction isValidAgentPluginUrl(value: string): boolean {\n\tif (value.includes(\"${\") || value.includes(\"$env:\") || value.includes(\"{env:\")) return false;\n\n\tlet url: URL;\n\ttry {\n\t\turl = new URL(value);\n\t} catch {\n\t\treturn false;\n\t}\n\tif (url.protocol !== \"http:\" && url.protocol !== \"https:\") return false;\n\tif (url.username || url.password || url.hash) return false;\n\tif (url.protocol === \"https:\") return true;\n\treturn isLoopbackHost(url.hostname);\n}\n\nfunction isLoopbackHost(hostname: string): boolean {\n\tconst host = hostname.toLowerCase();\n\tif (host === \"localhost\" || host === \"127.0.0.1\" || host === \"::1\" || host === \"[::1]\") return true;\n\tif (/^127(?:\\.\\d{1,3}){3}$/.test(host)) return true;\n\treturn false;\n}\n"]}
|