Fix/305740 subprocess wait#308681
Conversation
There was a problem hiding this comment.
Pull request overview
This PR combines a fix for TS Server Node detection (avoiding a subprocess exec), enhancements to chat inline-reference rendering (supporting embedded code snippets), and the introduction of a new MCP server provider extension (ToolPipe), along with a few unrelated CI/CSS/config example updates.
Changes:
- Replace
execFileSync('node', ...)-based Node discovery in the TypeScript extension with PATH-based resolution and add unit tests. - Extend chat inline references to optionally append a fenced code block snippet (with backtick-escaping) and add coverage for snippet edge cases.
- Add a new built-in
toolpipe-mcp-serverextension (plus MCP schema examples), and re-enable the Sessions E2E workflow trigger.
Reviewed changes
Copilot reviewed 14 out of 14 changed files in this pull request and generated 12 comments.
Show a summary per file
| File | Description |
|---|---|
| src/vs/workbench/contrib/terminal/browser/media/xterm.css | Use theme variables for IME composition view background instead of hardcoded black. |
| src/vs/workbench/contrib/mcp/common/mcpConfiguration.ts | Add ToolPipe-related MCP server examples to the schema examples. |
| src/vs/workbench/contrib/chat/common/widget/annotations.ts | Append inline-reference snippets as fenced code blocks; infer language from label. |
| src/vs/workbench/contrib/chat/common/chatService/chatService.ts | Extend inline reference model with optional snippet and languageId. |
| src/vs/workbench/contrib/chat/test/common/widget/annotations.test.ts | Add tests for snippet rendering and fence escaping behavior. |
| extensions/typescript-language-features/src/configuration/configuration.electron.ts | Replace subprocess-based Node detection with PATH-based resolver. |
| extensions/typescript-language-features/src/test/unit/configuration.electron.test.ts | Add unit tests for PATH-based Node resolution. |
| extensions/toolpipe-mcp-server/tsconfig.json | New extension TS config including MCP proposed d.ts. |
| extensions/toolpipe-mcp-server/src/extension.ts | New MCP server definition provider extension implementation. |
| extensions/toolpipe-mcp-server/README.md | New extension documentation and configuration instructions. |
| extensions/toolpipe-mcp-server/package.nls.json | New extension localization strings for manifest/configuration. |
| extensions/toolpipe-mcp-server/package.json | New extension manifest contributing MCP provider and settings. |
| build/gulpfile.extensions.ts | Add ToolPipe extension tsconfig to the extensions compilation list. |
| .github/workflows/sessions-e2e.yml | Re-enable workflow trigger for Sessions E2E tests on PRs. |
| let markdownText = `[${label}](${printUri.toString()})`; | ||
|
|
||
| // If the inline reference includes a code snippet, append it as a code block | ||
| if (item.snippet) { |
There was a problem hiding this comment.
item.snippet is checked with a truthiness condition, so an explicitly provided empty snippet ('') will be ignored and no code fence will be appended. This contradicts the newly added test case for empty snippets and will cause it to fail. Consider checking item.snippet !== undefined (or 'snippet' in item) instead of truthiness so empty snippets still render.
| if (item.snippet) { | |
| if (item.snippet !== undefined) { |
| 'psm1': 'powershell', | ||
| 'lua': 'lua', | ||
| 'r': 'r', | ||
| 'R': 'r', |
There was a problem hiding this comment.
ext is lowercased before indexing into languageMap, so the 'R' entry is unreachable/dead code. It should be removed (or the lowercasing logic adjusted) to avoid confusion about supported extensions.
| 'R': 'r', |
| if (!pathValue) { | ||
| return null; | ||
| } | ||
|
|
||
| const searchPaths = pathValue.split(pathLib.delimiter).filter(Boolean); | ||
| const windowsExecutableSuffixes = platform === 'win32' | ||
| ? (getCaseInsensitiveEnvValue(env, 'PATHEXT') || '.COM;.EXE;.BAT;.CMD').split(';').filter(Boolean) | ||
| : []; | ||
|
|
||
| for (const pathEntry of searchPaths) { | ||
| const baseDir = pathLib.isAbsolute(pathEntry) ? pathEntry : pathLib.join(cwd, pathEntry); | ||
|
|
||
| if (platform === 'win32') { | ||
| for (const ext of windowsExecutableSuffixes) { | ||
| const candidate = pathLib.join(baseDir, `node${ext}`); | ||
| if (isExecutableFile(candidate)) { | ||
| return candidate; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| const candidate = pathLib.join(baseDir, 'node'); | ||
| if (isExecutableFile(candidate)) { | ||
| return candidate; | ||
| } | ||
| } | ||
|
|
There was a problem hiding this comment.
resolveNodeExecutableFromPath returns null immediately when PATH is missing, and it never checks the cwd directly. This differs from common command resolution behavior (and VS Code's findExecutable, which falls back to checking cwd), and may regress scenarios where node is resolvable without PATH. Consider adding a cwd fallback candidate (including PATHEXT handling on Windows) before returning null.
| if (!pathValue) { | |
| return null; | |
| } | |
| const searchPaths = pathValue.split(pathLib.delimiter).filter(Boolean); | |
| const windowsExecutableSuffixes = platform === 'win32' | |
| ? (getCaseInsensitiveEnvValue(env, 'PATHEXT') || '.COM;.EXE;.BAT;.CMD').split(';').filter(Boolean) | |
| : []; | |
| for (const pathEntry of searchPaths) { | |
| const baseDir = pathLib.isAbsolute(pathEntry) ? pathEntry : pathLib.join(cwd, pathEntry); | |
| if (platform === 'win32') { | |
| for (const ext of windowsExecutableSuffixes) { | |
| const candidate = pathLib.join(baseDir, `node${ext}`); | |
| if (isExecutableFile(candidate)) { | |
| return candidate; | |
| } | |
| } | |
| } | |
| const candidate = pathLib.join(baseDir, 'node'); | |
| if (isExecutableFile(candidate)) { | |
| return candidate; | |
| } | |
| } | |
| const windowsExecutableSuffixes = platform === 'win32' | |
| ? (getCaseInsensitiveEnvValue(env, 'PATHEXT') || '.COM;.EXE;.BAT;.CMD').split(';').filter(Boolean) | |
| : []; | |
| if (pathValue) { | |
| const searchPaths = pathValue.split(pathLib.delimiter).filter(Boolean); | |
| for (const pathEntry of searchPaths) { | |
| const baseDir = pathLib.isAbsolute(pathEntry) ? pathEntry : pathLib.join(cwd, pathEntry); | |
| if (platform === 'win32') { | |
| for (const ext of windowsExecutableSuffixes) { | |
| const candidate = pathLib.join(baseDir, `node${ext}`); | |
| if (isExecutableFile(candidate)) { | |
| return candidate; | |
| } | |
| } | |
| } | |
| const candidate = pathLib.join(baseDir, 'node'); | |
| if (isExecutableFile(candidate)) { | |
| return candidate; | |
| } | |
| } | |
| } | |
| if (platform === 'win32') { | |
| for (const ext of windowsExecutableSuffixes) { | |
| const candidate = pathLib.join(cwd, `node${ext}`); | |
| if (isExecutableFile(candidate)) { | |
| return candidate; | |
| } | |
| } | |
| } | |
| const candidate = pathLib.join(cwd, 'node'); | |
| if (isExecutableFile(candidate)) { | |
| return candidate; | |
| } |
| "main": "./out/extension.js", | ||
| "activationEvents": [ | ||
| "onStartupFinished" | ||
| ], | ||
| "contributes": { | ||
| "mcpServerDefinitionProviders": [ | ||
| { | ||
| "id": "toolpipe", | ||
| "label": "%mcpServerDefinition.label%" | ||
| } | ||
| ], |
There was a problem hiding this comment.
This extension calls the proposed vscode.lm.registerMcpServerDefinitionProvider API (types come from vscode.proposed.mcpServerDefinitions.d.ts), but package.json does not declare the corresponding enabledApiProposals entry. Without it, activation will throw due to proposed API gating. Add "enabledApiProposals": ["mcpServerDefinitions"] to the manifest.
| }, | ||
| "main": "./out/extension.js", | ||
| "activationEvents": [ | ||
| "onStartupFinished" |
There was a problem hiding this comment.
activationEvents is set to onStartupFinished, which eagerly activates the extension even when MCP isn’t used. Since the mcpServerDefinitionProviders extension point generates onMcpCollection:<id> activation events (here: onMcpCollection:toolpipe), consider switching to that activation event to reduce startup impact and align with the MCP contribution model.
| "onStartupFinished" | |
| "onMcpCollection:toolpipe" |
|
|
||
| ## License | ||
|
|
||
| MIT - See LICENSE file for details |
There was a problem hiding this comment.
The README states "MIT - See LICENSE file for details", but no LICENSE file is added under this extension directory in the PR. Either add the referenced LICENSE file or adjust the text to point to the repository root license to avoid a broken reference.
| MIT - See LICENSE file for details | |
| MIT - See the repository root LICENSE file for details. |
| on: | ||
| pull_request: | ||
| branches: | ||
| - main | ||
| - 'release/*' |
There was a problem hiding this comment.
This change re-enables the workflow trigger on pull_request. Please confirm this is intentional for this PR/issue, since it will start running Sessions E2E on all PRs touching the listed paths and can significantly increase CI load and PR turnaround time.
| 'extensions/search-result/tsconfig.json', | ||
| 'extensions/simple-browser/tsconfig.json', | ||
| 'extensions/toolpipe-mcp-server/tsconfig.json', | ||
| 'extensions/tunnel-forwarding/tsconfig.json', | ||
| 'extensions/typescript-language-features/web/tsconfig.json', |
There was a problem hiding this comment.
This PR adds a new built-in extension (toolpipe-mcp-server) to the extension compilation set, which seems unrelated to the stated goal of fixing a subprocess wait issue. If this is intentional, consider splitting into a dedicated PR to keep review/rollback risk contained and to make the bugfix easier to track.
| "scripts": { | ||
| "vscode:prepublish": "npm run compile", | ||
| "compile": "tsc -p ./", | ||
| "watch": "tsc -w -p ./", | ||
| "pretest": "npm run compile && npm run lint", | ||
| "test": "vscode-test", | ||
| "lint": "eslint src --ext ts" | ||
| }, |
There was a problem hiding this comment.
The scripts.test command is vscode-test, but the extension doesn’t declare a dependency/devDependency on @vscode/test-electron / @vscode/test-web (or a vscode-test binary). As written, running the extension’s tests from this folder would fail. Consider either wiring this up to the repo’s existing extension test infrastructure (gulp tasks) or adding the correct devDependency and script.
| definitions.push( | ||
| new vscode.McpHttpServerDefinition( | ||
| 'ToolPipe Developer Tools', | ||
| vscode.Uri.parse(remoteUrl) | ||
| ) | ||
| ); | ||
| } | ||
| } else if (mode === 'local') { | ||
| const command = config.get<string>('localCommand', 'npx'); | ||
| const args = config.get<string[]>('localArgs', ['@cosai-labs/toolpipe-mcp-server']); | ||
|
|
||
| definitions.push( | ||
| new vscode.McpStdioServerDefinition( | ||
| 'ToolPipe Developer Tools (Local)', | ||
| command, | ||
| args | ||
| ) |
There was a problem hiding this comment.
The MCP server labels passed into McpHttpServerDefinition / McpStdioServerDefinition (e.g. "ToolPipe Developer Tools") are likely user-visible. In VS Code extensions, user-facing strings should be localized via vscode.l10n.t(...) (and corresponding package.nls.json entries if needed) rather than hard-coded literals.
|
Please provide context as to why you opened this PR |
20791a1 to
4944d47
Compare
…lution - Add resolveNodeExecutableFromPath() to resolve node via PATH/PATHEXT - Remove execFileSync usage from node path detection - Add helper functions for cross-platform support (Windows PATHEXT, Unix PATH) - Add comprehensive unit tests for all platforms (Windows, Unix, fallback) - Maintain existing warning behavior when Node cannot be detected - Handles cross-platform differences in PATH environment variables Fixes: microsoft#305740
eb2a000 to
1bb15c6
Compare
|
Thanks for the review request. Context for this PR: issue #305740 reports that TS node path detection can block on a synchronous subprocess call. This change removes the sync subprocess path and resolves node via PATH/PATHEXT with cross-platform handling, plus focused unit tests for resolver behavior. I have now force-pushed the branch to contain only the #305740 fix files to keep the scope narrow. Please re-review the updated diff. |
- Use single lstatSync (instead of existsSync + lstatSync) in defaultIsExecutableFile - Add X_OK executable permission check on non-Windows - Add cwd fallback in resolveNodeExecutableFromPath when PATH is missing or exhausted - Add unit tests for cwd fallback on Linux and win32 platforms
|
Applied the Copilot review feedback in the latest commit:
The PR now only touches \configuration.electron.ts\ and \configuration.electron.test.ts\ \u2014 fully scoped to issue #305740. |
Summary\nFixes #305740 by removing synchronous subprocess-based node detection in TypeScript configuration and resolving node executable via PATH/PATHEXT with platform-aware handling.\n\n## Why\nThe sync subprocess approach can block and cause delays/hangs in some environments.\n\n## What changed\n- Updated node path resolution in configuration.electron.ts\n- Added focused unit tests in configuration.electron.test.ts for Windows/Unix and fallback behavior\n\n## Scope\nThis PR now contains only the #305740 fix changes.