gsd-pi 2.70.0 → 2.70.1-dev.3e19108
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/dist/loader.js +4 -0
- package/dist/resources/extensions/claude-code-cli/stream-adapter.js +152 -2
- package/dist/resources/extensions/gsd/auto-model-selection.js +33 -19
- package/dist/resources/extensions/gsd/auto-prompts.js +7 -3
- package/dist/resources/extensions/gsd/auto-start.js +28 -12
- package/dist/resources/extensions/gsd/auto.js +12 -8
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +4 -0
- package/dist/resources/extensions/gsd/commands-handlers.js +22 -8
- package/dist/resources/extensions/gsd/doctor-engine-checks.js +12 -0
- package/dist/resources/extensions/gsd/doctor-format.js +2 -0
- package/dist/resources/extensions/gsd/guided-flow.js +33 -20
- package/dist/resources/extensions/gsd/init-wizard.js +3 -11
- package/dist/resources/extensions/gsd/pre-execution-checks.js +5 -3
- package/dist/resources/extensions/gsd/prompts/discuss.md +31 -13
- package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +34 -0
- package/dist/resources/extensions/gsd/validate-directory.js +30 -12
- package/dist/resources/extensions/gsd/workflow-mcp-auto-prep.js +56 -0
- package/dist/resources/extensions/gsd/workflow-mcp.js +12 -1
- package/dist/resources/extensions/slash-commands/audit.js +2 -1
- package/dist/resources/extensions/subagent/isolation.js +4 -2
- package/dist/update-check.d.ts +1 -0
- package/dist/update-check.js +30 -27
- package/dist/update-cmd.js +3 -11
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +15 -15
- package/dist/web/standalone/.next/build-manifest.json +4 -4
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/react-loadable-manifest.json +1 -1
- package/dist/web/standalone/.next/required-server-files.json +3 -3
- package/dist/web/standalone/.next/server/app/_global-error/page.js +3 -3
- package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found/page.js +2 -2
- package/dist/web/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +3 -3
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
- package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +3 -3
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/api/boot/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/boot/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/browse-directories/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/browse-directories/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/captures/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/captures/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/cleanup/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/cleanup/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/dev-mode/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/dev-mode/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/doctor/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/doctor/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/experimental/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/experimental/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/export-data/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/export-data/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/files/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/files/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/forensics/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/forensics/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/git/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/history/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/history/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/hooks/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/hooks/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/inspect/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/inspect/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/knowledge/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/knowledge/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/live-state/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/live-state/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/notifications/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/notifications/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/onboarding/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/onboarding/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/preferences/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/preferences/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/projects/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/projects/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/recovery/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/recovery/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/remote-questions/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/remote-questions/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/browser/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/browser/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/command/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/command/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/events/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/session/events/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/manage/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/manage/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/settings-data/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/settings-data/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/shutdown/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/shutdown/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/skill-health/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/skill-health/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/steer/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/steer/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/switch-root/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/switch-root/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/input/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/input/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/resize/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/terminal/resize/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/sessions/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/terminal/stream/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/upload/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/upload/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/undo/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/undo/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/update/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/visualizer/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/visualizer/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +4 -4
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +4 -4
- package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +3 -3
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/page.js +2 -2
- package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +15 -15
- package/dist/web/standalone/.next/server/chunks/63.js +3 -3
- package/dist/web/standalone/.next/server/chunks/6897.js +1 -1
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/middleware-react-loadable-manifest.js +1 -1
- package/dist/web/standalone/.next/server/middleware.js +2 -2
- package/dist/web/standalone/.next/server/next-font-manifest.js +1 -1
- package/dist/web/standalone/.next/server/next-font-manifest.json +1 -1
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +1 -1
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/dist/web/standalone/.next/static/chunks/2826.dd3dc8bbd3025fa5.js +9 -0
- package/dist/web/standalone/.next/static/chunks/app/_not-found/{page-2f24283c162b6ab3.js → page-f2a7482d42a5614b.js} +1 -1
- package/dist/web/standalone/.next/static/chunks/app/{layout-9ecfd95f343793f0.js → layout-a16c7a7ecdf0c2cf.js} +1 -1
- package/dist/web/standalone/.next/static/chunks/app/page-f1e30ab6bb269149.js +1 -0
- package/dist/web/standalone/.next/static/chunks/main-app-fdab67f7802d7832.js +1 -0
- package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-459824ffb8c323dd.js +1 -0
- package/dist/web/standalone/.next/static/chunks/{webpack-6e4d7e9a4f57bed4.js → webpack-b868033a5834586d.js} +1 -1
- package/dist/web/standalone/node_modules/node-pty/build/Makefile +2 -2
- package/dist/web/standalone/node_modules/node-pty/build/Release/pty.node +0 -0
- package/dist/web/standalone/node_modules/node-pty/build/pty.target.mk +14 -14
- package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api.target.mk +14 -14
- package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_except.target.mk +14 -14
- package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_maybe.target.mk +14 -14
- package/dist/web/standalone/server.js +1 -1
- package/dist/web-mode.js +4 -0
- package/package.json +11 -11
- package/packages/mcp-server/dist/env-writer.d.ts +39 -0
- package/packages/mcp-server/dist/env-writer.d.ts.map +1 -0
- package/packages/mcp-server/dist/env-writer.js +158 -0
- package/packages/mcp-server/dist/env-writer.js.map +1 -0
- package/packages/mcp-server/dist/server.d.ts +11 -2
- package/packages/mcp-server/dist/server.d.ts.map +1 -1
- package/packages/mcp-server/dist/server.js +102 -2
- package/packages/mcp-server/dist/server.js.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.d.ts +2 -0
- package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.js +35 -3
- package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
- package/packages/mcp-server/src/env-writer.test.ts +280 -0
- package/packages/mcp-server/src/env-writer.ts +183 -0
- package/packages/mcp-server/src/import-candidates.test.ts +48 -0
- package/packages/mcp-server/src/secure-env-collect.test.ts +265 -0
- package/packages/mcp-server/src/server.ts +137 -3
- package/packages/mcp-server/src/workflow-tools.ts +34 -1
- package/packages/pi-agent-core/dist/agent.d.ts +8 -0
- package/packages/pi-agent-core/dist/agent.d.ts.map +1 -1
- package/packages/pi-agent-core/dist/agent.js +3 -0
- package/packages/pi-agent-core/dist/agent.js.map +1 -1
- package/packages/pi-agent-core/src/agent.test.ts +82 -0
- package/packages/pi-agent-core/src/agent.ts +12 -0
- package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.js +133 -0
- package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/lsp/config.d.ts +1 -0
- package/packages/pi-coding-agent/dist/core/lsp/config.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/config.js +38 -15
- package/packages/pi-coding-agent/dist/core/lsp/config.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/sdk.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/sdk.js +10 -0
- package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +58 -21
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.js +3 -1
- package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.js.map +1 -1
- package/packages/pi-coding-agent/package.json +1 -1
- package/packages/pi-coding-agent/src/core/chat-controller-ordering.test.ts +152 -0
- package/packages/pi-coding-agent/src/core/lsp/config.ts +43 -17
- package/packages/pi-coding-agent/src/core/sdk.ts +8 -0
- package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +83 -27
- package/packages/pi-coding-agent/src/modes/interactive/slash-command-handlers.ts +7 -5
- package/pkg/package.json +1 -1
- package/src/resources/extensions/claude-code-cli/stream-adapter.ts +229 -2
- package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +205 -0
- package/src/resources/extensions/gsd/auto-model-selection.ts +39 -25
- package/src/resources/extensions/gsd/auto-prompts.ts +7 -3
- package/src/resources/extensions/gsd/auto-start.ts +37 -14
- package/src/resources/extensions/gsd/auto.ts +12 -8
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +4 -0
- package/src/resources/extensions/gsd/commands-handlers.ts +22 -7
- package/src/resources/extensions/gsd/doctor-engine-checks.ts +14 -0
- package/src/resources/extensions/gsd/doctor-format.ts +1 -0
- package/src/resources/extensions/gsd/doctor-types.ts +1 -0
- package/src/resources/extensions/gsd/guided-flow.ts +36 -17
- package/src/resources/extensions/gsd/init-wizard.ts +3 -13
- package/src/resources/extensions/gsd/pre-execution-checks.ts +6 -3
- package/src/resources/extensions/gsd/prompts/discuss.md +31 -13
- package/src/resources/extensions/gsd/tests/discuss-incremental-persistence.test.ts +9 -0
- package/src/resources/extensions/gsd/tests/doctor-scope-db-unavailable.test.ts +43 -0
- package/src/resources/extensions/gsd/tests/interactive-routing-bypass.test.ts +207 -0
- package/src/resources/extensions/gsd/tests/pre-exec-backtick-strip.test.ts +48 -1
- package/src/resources/extensions/gsd/tests/resource-loader-import-path.test.ts +8 -7
- package/src/resources/extensions/gsd/tests/validate-directory.test.ts +33 -1
- package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +87 -1
- package/src/resources/extensions/gsd/tests/workflow-mcp-auto-prep.test.ts +76 -0
- package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +180 -1
- package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +22 -0
- package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +60 -25
- package/src/resources/extensions/gsd/validate-directory.ts +33 -11
- package/src/resources/extensions/gsd/workflow-mcp-auto-prep.ts +76 -0
- package/src/resources/extensions/gsd/workflow-mcp.ts +16 -1
- package/src/resources/extensions/slash-commands/audit.ts +2 -1
- package/src/resources/extensions/subagent/isolation.ts +4 -3
- package/dist/web/standalone/.next/static/chunks/2826.821e01b07d92e948.js +0 -9
- package/dist/web/standalone/.next/static/chunks/app/page-7115e62689b5fd84.js +0 -1
- package/dist/web/standalone/.next/static/chunks/main-app-d3d4c336195465f9.js +0 -1
- package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-ab5a8926e07ec673.js +0 -1
- /package/dist/web/standalone/.next/static/{Nl6lg7zP5dNgNBV1107v1 → cHCEWiRJM5bXJa9HkP1QU}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{Nl6lg7zP5dNgNBV1107v1 → cHCEWiRJM5bXJa9HkP1QU}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
// @gsd-build/mcp-server — Tests for secure_env_collect MCP tool
|
|
2
|
+
// Copyright (c) 2026 Jeremy McSpadden <jeremy@fluxlabs.net>
|
|
3
|
+
//
|
|
4
|
+
// Tests the secure_env_collect tool registered in createMcpServer.
|
|
5
|
+
// Uses a mock MCP server to intercept tool registration and elicitInput calls.
|
|
6
|
+
|
|
7
|
+
import { describe, it, beforeEach } from 'node:test';
|
|
8
|
+
import assert from 'node:assert/strict';
|
|
9
|
+
import { mkdtempSync, mkdirSync, rmSync, writeFileSync, readFileSync } from 'node:fs';
|
|
10
|
+
import { tmpdir } from 'node:os';
|
|
11
|
+
import { join } from 'node:path';
|
|
12
|
+
|
|
13
|
+
import { createMcpServer } from './server.js';
|
|
14
|
+
import { SessionManager } from './session-manager.js';
|
|
15
|
+
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
// Mock infrastructure
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* We intercept McpServer construction by monkey-patching the dynamic import.
|
|
22
|
+
* Instead, we'll test the tool handler indirectly through the exported
|
|
23
|
+
* createMcpServer function — capturing the registered tool handlers.
|
|
24
|
+
*
|
|
25
|
+
* Since createMcpServer dynamically imports McpServer, we need to test at
|
|
26
|
+
* a level that exercises the tool handler logic. We do this by extracting
|
|
27
|
+
* the tool handler through the server.tool() calls.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
interface RegisteredTool {
|
|
31
|
+
name: string;
|
|
32
|
+
description: string;
|
|
33
|
+
params: Record<string, unknown>;
|
|
34
|
+
handler: (args: Record<string, unknown>) => Promise<unknown>;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
interface ToolResult {
|
|
38
|
+
content?: Array<{ type: string; text: string }>;
|
|
39
|
+
isError?: boolean;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Mock McpServer that captures tool registrations and provides
|
|
44
|
+
* a controllable elicitInput response.
|
|
45
|
+
*/
|
|
46
|
+
class MockMcpServer {
|
|
47
|
+
registeredTools: RegisteredTool[] = [];
|
|
48
|
+
elicitResponse: { action: string; content?: Record<string, unknown> } = { action: 'accept', content: {} };
|
|
49
|
+
|
|
50
|
+
server = {
|
|
51
|
+
elicitInput: async (_params: unknown) => {
|
|
52
|
+
return this.elicitResponse;
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
tool(name: string, description: string, params: Record<string, unknown>, handler: (args: Record<string, unknown>) => Promise<unknown>) {
|
|
57
|
+
this.registeredTools.push({ name, description, params, handler });
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async connect(_transport: unknown) { /* no-op */ }
|
|
61
|
+
async close() { /* no-op */ }
|
|
62
|
+
|
|
63
|
+
getToolHandler(name: string): ((args: Record<string, unknown>) => Promise<unknown>) | undefined {
|
|
64
|
+
return this.registeredTools.find((t) => t.name === name)?.handler;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// ---------------------------------------------------------------------------
|
|
69
|
+
// Helper to create a mock MCP server with secure_env_collect registered
|
|
70
|
+
// ---------------------------------------------------------------------------
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Since createMcpServer uses dynamic import for McpServer, we can't easily
|
|
74
|
+
* mock it. Instead, we test the env-writer utilities directly (in env-writer.test.ts)
|
|
75
|
+
* and test the tool integration by verifying:
|
|
76
|
+
* 1. The tool exists in the registered tools list
|
|
77
|
+
* 2. The handler produces correct results with mock data
|
|
78
|
+
*
|
|
79
|
+
* For handler-level testing, we create a standalone test that replicates
|
|
80
|
+
* the tool handler logic with a controllable mock.
|
|
81
|
+
*/
|
|
82
|
+
|
|
83
|
+
function makeTempDir(prefix: string): string {
|
|
84
|
+
return mkdtempSync(join(tmpdir(), `${prefix}-`));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// ---------------------------------------------------------------------------
|
|
88
|
+
// Integration test — verify tool is registered
|
|
89
|
+
// ---------------------------------------------------------------------------
|
|
90
|
+
|
|
91
|
+
describe('secure_env_collect tool registration', () => {
|
|
92
|
+
it('createMcpServer registers secure_env_collect tool', async () => {
|
|
93
|
+
// This test verifies the tool exists — createMcpServer internally calls
|
|
94
|
+
// server.tool('secure_env_collect', ...) which we can't intercept without
|
|
95
|
+
// module mocking, but we can verify the server creates successfully
|
|
96
|
+
const sm = new SessionManager();
|
|
97
|
+
try {
|
|
98
|
+
const { server } = await createMcpServer(sm);
|
|
99
|
+
assert.ok(server, 'server should be created');
|
|
100
|
+
// The McpServer internally tracks registered tools — we verify no error
|
|
101
|
+
} finally {
|
|
102
|
+
await sm.cleanup();
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
// ---------------------------------------------------------------------------
|
|
108
|
+
// Handler logic tests — using env-writer directly to test the flow
|
|
109
|
+
// ---------------------------------------------------------------------------
|
|
110
|
+
|
|
111
|
+
describe('secure_env_collect handler logic', () => {
|
|
112
|
+
it('skips keys that already exist in .env', async () => {
|
|
113
|
+
const tmp = makeTempDir('sec-collect');
|
|
114
|
+
try {
|
|
115
|
+
const envPath = join(tmp, '.env');
|
|
116
|
+
writeFileSync(envPath, 'ALREADY_SET=existing-value\n');
|
|
117
|
+
|
|
118
|
+
// Import the utility directly to test the pre-check logic
|
|
119
|
+
const { checkExistingEnvKeys } = await import('./env-writer.js');
|
|
120
|
+
const existing = await checkExistingEnvKeys(['ALREADY_SET', 'NEW_KEY'], envPath);
|
|
121
|
+
assert.deepStrictEqual(existing, ['ALREADY_SET']);
|
|
122
|
+
} finally {
|
|
123
|
+
rmSync(tmp, { recursive: true, force: true });
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('writes collected values to .env without returning secret values', async () => {
|
|
128
|
+
const tmp = makeTempDir('sec-collect');
|
|
129
|
+
try {
|
|
130
|
+
const envPath = join(tmp, '.env');
|
|
131
|
+
const savedKey = process.env.SEC_COLLECT_TEST_KEY;
|
|
132
|
+
|
|
133
|
+
const { applySecrets } = await import('./env-writer.js');
|
|
134
|
+
const { applied, errors } = await applySecrets(
|
|
135
|
+
[{ key: 'SEC_COLLECT_TEST_KEY', value: 'super-secret-value' }],
|
|
136
|
+
'dotenv',
|
|
137
|
+
{ envFilePath: envPath },
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
assert.deepStrictEqual(applied, ['SEC_COLLECT_TEST_KEY']);
|
|
141
|
+
assert.deepStrictEqual(errors, []);
|
|
142
|
+
|
|
143
|
+
// Verify the value was written
|
|
144
|
+
const content = readFileSync(envPath, 'utf8');
|
|
145
|
+
assert.ok(content.includes('SEC_COLLECT_TEST_KEY=super-secret-value'));
|
|
146
|
+
|
|
147
|
+
// Verify process.env was hydrated
|
|
148
|
+
assert.equal(process.env.SEC_COLLECT_TEST_KEY, 'super-secret-value');
|
|
149
|
+
|
|
150
|
+
// Cleanup
|
|
151
|
+
if (savedKey === undefined) delete process.env.SEC_COLLECT_TEST_KEY;
|
|
152
|
+
else process.env.SEC_COLLECT_TEST_KEY = savedKey;
|
|
153
|
+
} finally {
|
|
154
|
+
rmSync(tmp, { recursive: true, force: true });
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it('auto-detects vercel destination from vercel.json', async () => {
|
|
159
|
+
const tmp = makeTempDir('sec-collect');
|
|
160
|
+
try {
|
|
161
|
+
writeFileSync(join(tmp, 'vercel.json'), '{}');
|
|
162
|
+
const { detectDestination } = await import('./env-writer.js');
|
|
163
|
+
assert.equal(detectDestination(tmp), 'vercel');
|
|
164
|
+
} finally {
|
|
165
|
+
rmSync(tmp, { recursive: true, force: true });
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it('handles empty form values as skipped', async () => {
|
|
170
|
+
// Simulate what happens when user leaves a field empty in the form
|
|
171
|
+
const formContent: Record<string, string> = {
|
|
172
|
+
'API_KEY': 'provided-value',
|
|
173
|
+
'OPTIONAL_KEY': '', // empty = skip
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
const provided: Array<{ key: string; value: string }> = [];
|
|
177
|
+
const skipped: string[] = [];
|
|
178
|
+
|
|
179
|
+
for (const [key, raw] of Object.entries(formContent)) {
|
|
180
|
+
const value = typeof raw === 'string' ? raw.trim() : '';
|
|
181
|
+
if (value.length > 0) {
|
|
182
|
+
provided.push({ key, value });
|
|
183
|
+
} else {
|
|
184
|
+
skipped.push(key);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
assert.deepStrictEqual(provided, [{ key: 'API_KEY', value: 'provided-value' }]);
|
|
189
|
+
assert.deepStrictEqual(skipped, ['OPTIONAL_KEY']);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it('result text never contains secret values', async () => {
|
|
193
|
+
const tmp = makeTempDir('sec-collect');
|
|
194
|
+
try {
|
|
195
|
+
const envPath = join(tmp, '.env');
|
|
196
|
+
const savedKey = process.env.RESULT_TEXT_TEST;
|
|
197
|
+
|
|
198
|
+
const { applySecrets } = await import('./env-writer.js');
|
|
199
|
+
const { applied } = await applySecrets(
|
|
200
|
+
[{ key: 'RESULT_TEXT_TEST', value: 'sk-super-secret-abc123' }],
|
|
201
|
+
'dotenv',
|
|
202
|
+
{ envFilePath: envPath },
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
// Simulate building result text (same logic as the tool handler)
|
|
206
|
+
const lines: string[] = [
|
|
207
|
+
'destination: dotenv (auto-detected)',
|
|
208
|
+
...applied.map((k) => `✓ ${k}: applied`),
|
|
209
|
+
];
|
|
210
|
+
const resultText = lines.join('\n');
|
|
211
|
+
|
|
212
|
+
// The result MUST NOT contain the secret value
|
|
213
|
+
assert.ok(!resultText.includes('sk-super-secret-abc123'), 'result text must not contain secret value');
|
|
214
|
+
assert.ok(resultText.includes('RESULT_TEXT_TEST'), 'result text should contain key name');
|
|
215
|
+
|
|
216
|
+
// Cleanup
|
|
217
|
+
if (savedKey === undefined) delete process.env.RESULT_TEXT_TEST;
|
|
218
|
+
else process.env.RESULT_TEXT_TEST = savedKey;
|
|
219
|
+
} finally {
|
|
220
|
+
rmSync(tmp, { recursive: true, force: true });
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it('handles multiple keys with mixed existing/new/skipped', async () => {
|
|
225
|
+
const tmp = makeTempDir('sec-collect');
|
|
226
|
+
try {
|
|
227
|
+
const envPath = join(tmp, '.env');
|
|
228
|
+
writeFileSync(envPath, 'EXISTING_A=already-here\n');
|
|
229
|
+
const savedB = process.env.NEW_B;
|
|
230
|
+
const savedC = process.env.SKIP_C;
|
|
231
|
+
|
|
232
|
+
const { checkExistingEnvKeys, applySecrets } = await import('./env-writer.js');
|
|
233
|
+
|
|
234
|
+
const allKeys = ['EXISTING_A', 'NEW_B', 'SKIP_C'];
|
|
235
|
+
const existing = await checkExistingEnvKeys(allKeys, envPath);
|
|
236
|
+
assert.deepStrictEqual(existing, ['EXISTING_A']);
|
|
237
|
+
|
|
238
|
+
// Simulate form response: NEW_B has value, SKIP_C is empty
|
|
239
|
+
const formContent = { NEW_B: 'new-value', SKIP_C: '' };
|
|
240
|
+
const provided: Array<{ key: string; value: string }> = [];
|
|
241
|
+
const skipped: string[] = [];
|
|
242
|
+
|
|
243
|
+
for (const key of allKeys.filter((k) => !existing.includes(k))) {
|
|
244
|
+
const raw = formContent[key as keyof typeof formContent] ?? '';
|
|
245
|
+
if (raw.trim().length > 0) provided.push({ key, value: raw.trim() });
|
|
246
|
+
else skipped.push(key);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const { applied, errors } = await applySecrets(provided, 'dotenv', { envFilePath: envPath });
|
|
250
|
+
|
|
251
|
+
assert.deepStrictEqual(applied, ['NEW_B']);
|
|
252
|
+
assert.deepStrictEqual(skipped, ['SKIP_C']);
|
|
253
|
+
assert.deepStrictEqual(errors, []);
|
|
254
|
+
assert.deepStrictEqual(existing, ['EXISTING_A']);
|
|
255
|
+
|
|
256
|
+
// Cleanup
|
|
257
|
+
if (savedB === undefined) delete process.env.NEW_B;
|
|
258
|
+
else process.env.NEW_B = savedB;
|
|
259
|
+
if (savedC === undefined) delete process.env.SKIP_C;
|
|
260
|
+
else process.env.SKIP_C = savedC;
|
|
261
|
+
} finally {
|
|
262
|
+
rmSync(tmp, { recursive: true, force: true });
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
});
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* MCP Server — registers GSD orchestration, project-state, and workflow tools.
|
|
3
3
|
*
|
|
4
4
|
* Session tools (6): gsd_execute, gsd_status, gsd_result, gsd_cancel, gsd_query, gsd_resolve_blocker
|
|
5
|
-
* Interactive tools (
|
|
5
|
+
* Interactive tools (2): ask_user_questions, secure_env_collect via MCP form elicitation
|
|
6
6
|
* Read-only tools (6): gsd_progress, gsd_roadmap, gsd_history, gsd_doctor, gsd_captures, gsd_knowledge
|
|
7
7
|
* Workflow tools (29): headless-safe planning, metadata persistence, replanning, completion, validation, reassessment, gate result, status, and journal tools
|
|
8
8
|
*
|
|
@@ -22,6 +22,7 @@ import { readCaptures } from './readers/captures.js';
|
|
|
22
22
|
import { readKnowledge } from './readers/knowledge.js';
|
|
23
23
|
import { runDoctorLite } from './readers/doctor-lite.js';
|
|
24
24
|
import { registerWorkflowTools } from './workflow-tools.js';
|
|
25
|
+
import { applySecrets, checkExistingEnvKeys, detectDestination } from './env-writer.js';
|
|
25
26
|
|
|
26
27
|
// ---------------------------------------------------------------------------
|
|
27
28
|
// Constants
|
|
@@ -112,11 +113,26 @@ async function fileExists(path: string): Promise<boolean> {
|
|
|
112
113
|
// MCP Server type — minimal interface for the dynamically-imported McpServer
|
|
113
114
|
// ---------------------------------------------------------------------------
|
|
114
115
|
|
|
116
|
+
interface ElicitResult {
|
|
117
|
+
action: 'accept' | 'decline' | 'cancel';
|
|
118
|
+
content?: Record<string, string | number | boolean | string[]>;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
interface ElicitRequestFormParams {
|
|
122
|
+
mode?: 'form';
|
|
123
|
+
message: string;
|
|
124
|
+
requestedSchema: {
|
|
125
|
+
type: 'object';
|
|
126
|
+
properties: Record<string, Record<string, unknown>>;
|
|
127
|
+
required?: string[];
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
115
131
|
interface McpServerInstance {
|
|
116
132
|
tool(name: string, description: string, params: Record<string, unknown>, handler: (args: Record<string, unknown>) => Promise<unknown>): unknown;
|
|
117
133
|
server: {
|
|
118
134
|
elicitInput(
|
|
119
|
-
params: AskUserQuestionsElicitRequest,
|
|
135
|
+
params: AskUserQuestionsElicitRequest | ElicitRequestFormParams,
|
|
120
136
|
options?: unknown,
|
|
121
137
|
): Promise<AskUserQuestionsElicitResult>;
|
|
122
138
|
};
|
|
@@ -282,7 +298,7 @@ export async function createMcpServer(sessionManager: SessionManager): Promise<{
|
|
|
282
298
|
|
|
283
299
|
const server: McpServerInstance = new McpServer(
|
|
284
300
|
{ name: SERVER_NAME, version: SERVER_VERSION },
|
|
285
|
-
{ capabilities: { tools: {} } },
|
|
301
|
+
{ capabilities: { tools: {}, elicitation: {} } },
|
|
286
302
|
);
|
|
287
303
|
|
|
288
304
|
// -----------------------------------------------------------------------
|
|
@@ -472,6 +488,124 @@ export async function createMcpServer(sessionManager: SessionManager): Promise<{
|
|
|
472
488
|
},
|
|
473
489
|
);
|
|
474
490
|
|
|
491
|
+
// -----------------------------------------------------------------------
|
|
492
|
+
// secure_env_collect — collect secrets via MCP form elicitation
|
|
493
|
+
// -----------------------------------------------------------------------
|
|
494
|
+
server.tool(
|
|
495
|
+
'secure_env_collect',
|
|
496
|
+
'Collect environment variables securely via form input. Values are written directly to .env (or Vercel/Convex) and NEVER appear in tool output — only key names and applied/skipped status are returned. Use this instead of asking users to manually edit .env files or paste secrets into chat.',
|
|
497
|
+
{
|
|
498
|
+
projectDir: z.string().describe('Absolute path to the project directory'),
|
|
499
|
+
keys: z.array(z.object({
|
|
500
|
+
key: z.string().describe('Env var name, e.g. OPENAI_API_KEY'),
|
|
501
|
+
hint: z.string().optional().describe('Format hint shown to user, e.g. "starts with sk-"'),
|
|
502
|
+
guidance: z.array(z.string()).optional().describe('Step-by-step instructions for obtaining this key'),
|
|
503
|
+
})).min(1).describe('Environment variables to collect'),
|
|
504
|
+
destination: z.enum(['dotenv', 'vercel', 'convex']).optional().describe('Where to write secrets. Auto-detected from project files if omitted.'),
|
|
505
|
+
envFilePath: z.string().optional().describe('Path to .env file (dotenv only). Defaults to .env in projectDir.'),
|
|
506
|
+
environment: z.enum(['development', 'preview', 'production']).optional().describe('Target environment (vercel/convex only)'),
|
|
507
|
+
},
|
|
508
|
+
async (args: Record<string, unknown>) => {
|
|
509
|
+
const { projectDir, keys, destination, envFilePath, environment } = args as {
|
|
510
|
+
projectDir: string;
|
|
511
|
+
keys: Array<{ key: string; hint?: string; guidance?: string[] }>;
|
|
512
|
+
destination?: 'dotenv' | 'vercel' | 'convex';
|
|
513
|
+
envFilePath?: string;
|
|
514
|
+
environment?: 'development' | 'preview' | 'production';
|
|
515
|
+
};
|
|
516
|
+
|
|
517
|
+
try {
|
|
518
|
+
const resolvedProjectDir = resolve(projectDir);
|
|
519
|
+
const resolvedEnvPath = resolve(resolvedProjectDir, envFilePath ?? '.env');
|
|
520
|
+
|
|
521
|
+
// (1) Check which keys already exist
|
|
522
|
+
const allKeyNames = keys.map((k) => k.key);
|
|
523
|
+
const existingKeys = await checkExistingEnvKeys(allKeyNames, resolvedEnvPath);
|
|
524
|
+
const existingSet = new Set(existingKeys);
|
|
525
|
+
const pendingKeys = keys.filter((k) => !existingSet.has(k.key));
|
|
526
|
+
|
|
527
|
+
// If all keys already exist, return immediately
|
|
528
|
+
if (pendingKeys.length === 0) {
|
|
529
|
+
const lines = existingKeys.map((k) => `• ${k}: already set`);
|
|
530
|
+
return textContent(`All ${existingKeys.length} key(s) already set.\n${lines.join('\n')}`);
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
// (2) Build elicitation form — one string field per pending key
|
|
534
|
+
const properties: Record<string, Record<string, unknown>> = {};
|
|
535
|
+
const required: string[] = [];
|
|
536
|
+
|
|
537
|
+
for (const item of pendingKeys) {
|
|
538
|
+
const descParts: string[] = [];
|
|
539
|
+
if (item.hint) descParts.push(`Format: ${item.hint}`);
|
|
540
|
+
if (item.guidance && item.guidance.length > 0) {
|
|
541
|
+
descParts.push('How to get this:');
|
|
542
|
+
item.guidance.forEach((step, i) => descParts.push(`${i + 1}. ${step}`));
|
|
543
|
+
}
|
|
544
|
+
descParts.push('Leave empty to skip.');
|
|
545
|
+
|
|
546
|
+
properties[item.key] = {
|
|
547
|
+
type: 'string',
|
|
548
|
+
title: item.key,
|
|
549
|
+
description: descParts.join('\n'),
|
|
550
|
+
};
|
|
551
|
+
// Don't mark as required — empty string = skip
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
// (3) Elicit input from the MCP client
|
|
555
|
+
const elicitation = await server.server.elicitInput({
|
|
556
|
+
message: `Enter values for ${pendingKeys.length} environment variable(s). Values are written directly to the project and never shown to the AI.`,
|
|
557
|
+
requestedSchema: {
|
|
558
|
+
type: 'object',
|
|
559
|
+
properties,
|
|
560
|
+
required,
|
|
561
|
+
},
|
|
562
|
+
});
|
|
563
|
+
|
|
564
|
+
if (elicitation.action !== 'accept' || !elicitation.content) {
|
|
565
|
+
return textContent('secure_env_collect was cancelled by user.');
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
// (4) Separate provided vs skipped from form response
|
|
569
|
+
const provided: Array<{ key: string; value: string }> = [];
|
|
570
|
+
const skipped: string[] = [];
|
|
571
|
+
|
|
572
|
+
for (const item of pendingKeys) {
|
|
573
|
+
const raw = elicitation.content[item.key];
|
|
574
|
+
const value = typeof raw === 'string' ? raw.trim() : '';
|
|
575
|
+
if (value.length > 0) {
|
|
576
|
+
provided.push({ key: item.key, value });
|
|
577
|
+
} else {
|
|
578
|
+
skipped.push(item.key);
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
// (5) Auto-detect destination if not specified
|
|
583
|
+
const resolvedDestination = destination ?? detectDestination(resolvedProjectDir);
|
|
584
|
+
|
|
585
|
+
// (6) Write secrets to destination
|
|
586
|
+
const { applied, errors } = await applySecrets(provided, resolvedDestination, {
|
|
587
|
+
envFilePath: resolvedEnvPath,
|
|
588
|
+
environment,
|
|
589
|
+
});
|
|
590
|
+
|
|
591
|
+
// (7) Build result — NEVER include secret values
|
|
592
|
+
const lines: string[] = [
|
|
593
|
+
`destination: ${resolvedDestination}${!destination ? ' (auto-detected)' : ''}${environment ? ` (${environment})` : ''}`,
|
|
594
|
+
];
|
|
595
|
+
for (const k of applied) lines.push(`✓ ${k}: applied`);
|
|
596
|
+
for (const k of skipped) lines.push(`• ${k}: skipped`);
|
|
597
|
+
for (const k of existingKeys) lines.push(`• ${k}: already set`);
|
|
598
|
+
for (const e of errors) lines.push(`✗ ${e}`);
|
|
599
|
+
|
|
600
|
+
return errors.length > 0 && applied.length === 0
|
|
601
|
+
? errorContent(lines.join('\n'))
|
|
602
|
+
: textContent(lines.join('\n'));
|
|
603
|
+
} catch (err) {
|
|
604
|
+
return errorContent(err instanceof Error ? err.message : String(err));
|
|
605
|
+
}
|
|
606
|
+
},
|
|
607
|
+
);
|
|
608
|
+
|
|
475
609
|
// =======================================================================
|
|
476
610
|
// READ-ONLY TOOLS — no session required, pure filesystem reads
|
|
477
611
|
// =======================================================================
|
|
@@ -326,6 +326,7 @@ function getWriteGateModuleCandidates(): string[] {
|
|
|
326
326
|
|
|
327
327
|
candidates.push(
|
|
328
328
|
new URL("../../../src/resources/extensions/gsd/bootstrap/write-gate.js", import.meta.url).href,
|
|
329
|
+
new URL("../../../dist/resources/extensions/gsd/bootstrap/write-gate.js", import.meta.url).href,
|
|
329
330
|
new URL("../../../src/resources/extensions/gsd/bootstrap/write-gate.ts", import.meta.url).href,
|
|
330
331
|
);
|
|
331
332
|
|
|
@@ -336,8 +337,39 @@ function toFileUrl(modulePath: string): string {
|
|
|
336
337
|
return pathToFileURL(resolve(modulePath)).href;
|
|
337
338
|
}
|
|
338
339
|
|
|
340
|
+
/** @internal — exported for testing only */
|
|
341
|
+
export function _buildImportCandidates(relativePath: string): string[] {
|
|
342
|
+
// Build candidate paths: try the given path first, then swap src/<->dist/
|
|
343
|
+
// and try .ts extension. This handles both dev (tsx from src/) and prod
|
|
344
|
+
// (compiled from dist/) execution contexts.
|
|
345
|
+
const candidates: string[] = [relativePath];
|
|
346
|
+
const swapped = relativePath.includes("/src/")
|
|
347
|
+
? relativePath.replace("/src/", "/dist/")
|
|
348
|
+
: relativePath.includes("/dist/")
|
|
349
|
+
? relativePath.replace("/dist/", "/src/")
|
|
350
|
+
: null;
|
|
351
|
+
if (swapped) candidates.push(swapped);
|
|
352
|
+
// Also try .ts variants for dev-mode tsx execution
|
|
353
|
+
if (relativePath.endsWith(".js")) {
|
|
354
|
+
candidates.push(relativePath.replace(/\.js$/, ".ts"));
|
|
355
|
+
if (swapped) candidates.push(swapped.replace(/\.js$/, ".ts"));
|
|
356
|
+
}
|
|
357
|
+
return candidates;
|
|
358
|
+
}
|
|
359
|
+
|
|
339
360
|
async function importLocalModule<T>(relativePath: string): Promise<T> {
|
|
340
|
-
|
|
361
|
+
const candidates = _buildImportCandidates(relativePath)
|
|
362
|
+
.map((p) => new URL(p, import.meta.url).href);
|
|
363
|
+
|
|
364
|
+
let lastErr: unknown;
|
|
365
|
+
for (const candidate of candidates) {
|
|
366
|
+
try {
|
|
367
|
+
return await import(candidate) as T;
|
|
368
|
+
} catch (err) {
|
|
369
|
+
lastErr = err;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
throw lastErr;
|
|
341
373
|
}
|
|
342
374
|
|
|
343
375
|
function getWorkflowExecutorModuleCandidates(env: NodeJS.ProcessEnv = process.env): string[] {
|
|
@@ -352,6 +384,7 @@ function getWorkflowExecutorModuleCandidates(env: NodeJS.ProcessEnv = process.en
|
|
|
352
384
|
|
|
353
385
|
candidates.push(
|
|
354
386
|
new URL("../../../src/resources/extensions/gsd/tools/workflow-tool-executors.js", import.meta.url).href,
|
|
387
|
+
new URL("../../../dist/resources/extensions/gsd/tools/workflow-tool-executors.js", import.meta.url).href,
|
|
355
388
|
new URL("../../../src/resources/extensions/gsd/tools/workflow-tool-executors.ts", import.meta.url).href,
|
|
356
389
|
);
|
|
357
390
|
|
|
@@ -63,6 +63,13 @@ export interface AgentOptions {
|
|
|
63
63
|
* switches mid-session are handled correctly.
|
|
64
64
|
*/
|
|
65
65
|
externalToolExecution?: (model: Model<any>) => boolean;
|
|
66
|
+
/**
|
|
67
|
+
* Optional provider-specific options to merge into the next stream call.
|
|
68
|
+
*
|
|
69
|
+
* Use this for runtime-only callbacks or handles that should not live in
|
|
70
|
+
* shared agent state, such as UI bridges for external CLI providers.
|
|
71
|
+
*/
|
|
72
|
+
getProviderOptions?: (model: Model<any>) => Record<string, unknown> | undefined | Promise<Record<string, unknown> | undefined>;
|
|
66
73
|
}
|
|
67
74
|
export declare class Agent {
|
|
68
75
|
private _state;
|
|
@@ -86,6 +93,7 @@ export declare class Agent {
|
|
|
86
93
|
private _beforeToolCall?;
|
|
87
94
|
private _afterToolCall?;
|
|
88
95
|
private _externalToolExecution?;
|
|
96
|
+
private _getProviderOptions?;
|
|
89
97
|
constructor(opts?: AgentOptions);
|
|
90
98
|
/**
|
|
91
99
|
* Get the current session ID used for provider caching.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../src/agent.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAEN,KAAK,YAAY,EACjB,KAAK,OAAO,EACZ,KAAK,KAAK,EACV,KAAK,mBAAmB,EAGxB,KAAK,eAAe,EACpB,KAAK,SAAS,EACd,MAAM,YAAY,CAAC;AAEpB,OAAO,KAAK,EAEX,UAAU,EACV,eAAe,EACf,YAAY,EACZ,UAAU,EACV,SAAS,EAKT,QAAQ,EACR,aAAa,EACb,MAAM,YAAY,CAAC;AASpB,MAAM,WAAW,YAAY;IAC5B,YAAY,CAAC,EAAE,OAAO,CAAC,UAAU,CAAC,CAAC;IAEnC;;;OAGG;IACH,YAAY,CAAC,EAAE,CAAC,QAAQ,EAAE,YAAY,EAAE,KAAK,OAAO,EAAE,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC;IAE5E;;;OAGG;IACH,gBAAgB,CAAC,EAAE,CAAC,QAAQ,EAAE,YAAY,EAAE,EAAE,MAAM,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,YAAY,EAAE,CAAC,CAAC;IAE/F;;OAEG;IACH,YAAY,CAAC,EAAE,KAAK,GAAG,eAAe,CAAC;IAEvC;;OAEG;IACH,YAAY,CAAC,EAAE,KAAK,GAAG,eAAe,CAAC;IAEvC;;OAEG;IACH,QAAQ,CAAC,EAAE,QAAQ,CAAC;IAEpB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;;OAGG;IACH,SAAS,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,GAAG,MAAM,GAAG,SAAS,CAAC;IAEnF;;OAEG;IACH,SAAS,CAAC,EAAE,mBAAmB,CAAC,WAAW,CAAC,CAAC;IAE7C;;OAEG;IACH,eAAe,CAAC,EAAE,eAAe,CAAC;IAElC;;OAEG;IACH,SAAS,CAAC,EAAE,SAAS,CAAC;IAEtB;;;;;OAKG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IAEzB;;;;OAIG;IACH,qBAAqB,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,KAAK,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../src/agent.ts"],"names":[],"mappings":"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"}
|
|
@@ -39,6 +39,7 @@ export class Agent {
|
|
|
39
39
|
this._transport = opts.transport ?? "sse";
|
|
40
40
|
this._maxRetryDelayMs = opts.maxRetryDelayMs;
|
|
41
41
|
this._externalToolExecution = opts.externalToolExecution;
|
|
42
|
+
this._getProviderOptions = opts.getProviderOptions;
|
|
42
43
|
}
|
|
43
44
|
/**
|
|
44
45
|
* Get the current session ID used for provider caching.
|
|
@@ -303,7 +304,9 @@ export class Agent {
|
|
|
303
304
|
tools: this._state.tools,
|
|
304
305
|
};
|
|
305
306
|
let skipInitialSteeringPoll = options?.skipInitialSteeringPoll === true;
|
|
307
|
+
const providerOptions = await this._getProviderOptions?.(model);
|
|
306
308
|
const config = {
|
|
309
|
+
...(providerOptions ?? {}),
|
|
307
310
|
model,
|
|
308
311
|
reasoning,
|
|
309
312
|
sessionId: this._sessionId,
|