mirror of
https://github.com/jlengrand/Maestro.git
synced 2026-03-10 08:31:19 +00:00
feat: prefer PowerShell and auto-detect shell scripts on Windows
Improvements to Windows shell execution for OpenCode and other shell-based tools: 1. **Binary detection priority**: Changed Windows detection to prefer extensionless shell scripts (like opencode) over .cmd wrappers. The .cmd files execute through cmd.exe which has command line length limits and less robust script handling. 2. **Auto-detect shell scripts**: Added shebang detection in ChildProcessSpawner to automatically enable shell execution for POSIX scripts, preventing ENOENT errors when trying to execute shell scripts without explicit shell. 3. **Use PowerShell by default**: Changed Windows agent execution to prefer PowerShell over cmd.exe when available via PSHOME environment variable. PowerShell provides: - Better handling of POSIX shell scripts (with shebangs) - Avoids cmd.exe command line length limitations - Better compatibility with modern tooling Falls back to cmd.exe if PowerShell unavailable, respects user customizations. 4. **Improved path detection**: Updated Windows path probing to correctly prefer .exe > extensionless scripts > .cmd in the selection order.
This commit is contained in:
@@ -474,13 +474,16 @@ export async function checkBinaryExists(binaryName: string): Promise<BinaryDetec
|
||||
.filter((p) => p);
|
||||
|
||||
if (process.platform === 'win32' && matches.length > 0) {
|
||||
// On Windows, prefer .exe over .cmd over extensionless
|
||||
// This helps with proper execution handling
|
||||
// On Windows, prefer .exe > extensionless (shell scripts) > .cmd
|
||||
// This helps avoid cmd.exe limitations and supports PowerShell/bash scripts
|
||||
const exeMatch = matches.find((p) => p.toLowerCase().endsWith('.exe'));
|
||||
const cmdMatch = matches.find((p) => p.toLowerCase().endsWith('.cmd'));
|
||||
const extensionlessMatch = matches.find(
|
||||
(p) => !p.toLowerCase().endsWith('.exe') && !p.toLowerCase().endsWith('.cmd')
|
||||
);
|
||||
|
||||
// Return the best match: .exe > .cmd > first result
|
||||
let bestMatch = exeMatch || cmdMatch || matches[0];
|
||||
// Return the best match: .exe > extensionless shell scripts > .cmd > first result
|
||||
let bestMatch = exeMatch || extensionlessMatch || cmdMatch || matches[0];
|
||||
|
||||
// If the first match doesn't have an extension, check if .cmd or .exe version exists
|
||||
// This handles cases where 'where' returns a path without extension
|
||||
|
||||
@@ -283,7 +283,8 @@ export function registerProcessHandlers(deps: ProcessHandlerDependencies): void
|
||||
) as Record<string, string>;
|
||||
|
||||
// Determine an explicit shell to use when forcing shell execution on Windows.
|
||||
// Prefer a user-configured custom shell path, otherwise fall back to COMSPEC/cmd.exe.
|
||||
// Prefer a user-configured custom shell path, then PowerShell, then ComSpec/cmd.exe.
|
||||
// PowerShell is preferred over cmd.exe for better script handling and to avoid cmd.exe limits.
|
||||
const customShellPath = settingsStore.get('customShellPath', '') as string;
|
||||
if (customShellPath && customShellPath.trim()) {
|
||||
shellToUse = customShellPath.trim();
|
||||
@@ -291,8 +292,20 @@ export function registerProcessHandlers(deps: ProcessHandlerDependencies): void
|
||||
customShellPath: shellToUse,
|
||||
});
|
||||
} else if (!shellToUse) {
|
||||
// Use COMSPEC if available, otherwise default to cmd.exe
|
||||
shellToUse = process.env.ComSpec || 'cmd.exe';
|
||||
// Try PowerShell if available (common on modern Windows)
|
||||
// If not, fall back to ComSpec/cmd.exe
|
||||
// PowerShell handles shell scripts better and avoids cmd.exe command line length limits
|
||||
const powerShellPath = process.env.PSHOME
|
||||
? `${process.env.PSHOME}\\powershell.exe`
|
||||
: 'powershell';
|
||||
shellToUse = powerShellPath;
|
||||
logger.debug(
|
||||
'Using PowerShell for agent execution on Windows (shell script support)',
|
||||
LOG_CONTEXT,
|
||||
{
|
||||
shellPath: shellToUse,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
logger.info(`Forcing shell execution for agent on Windows for PATH access`, LOG_CONTEXT, {
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import { spawn } from 'child_process';
|
||||
import { EventEmitter } from 'events';
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
import { logger } from '../../utils/logger';
|
||||
import { getOutputParser } from '../../parsers';
|
||||
import { getAgentCapabilities } from '../../agents';
|
||||
@@ -184,6 +185,24 @@ export class ChildProcessSpawner {
|
||||
);
|
||||
}
|
||||
|
||||
// Auto-enable shell for Windows when command is a shell script (extensionless with shebang)
|
||||
// This handles tools like OpenCode installed via npm with shell scripts
|
||||
if (isWindows && !useShell && !commandExt && commandHasPath) {
|
||||
try {
|
||||
const fileContent = fs.readFileSync(spawnCommand, 'utf8');
|
||||
if (fileContent.startsWith('#!')) {
|
||||
useShell = true;
|
||||
logger.info(
|
||||
'[ProcessManager] Auto-enabling shell for Windows to execute shell script',
|
||||
'ProcessManager',
|
||||
{ command: spawnCommand, shebang: fileContent.split('\n')[0] }
|
||||
);
|
||||
}
|
||||
} catch {
|
||||
// If we can't read the file, just continue without special handling
|
||||
}
|
||||
}
|
||||
|
||||
if (isWindows && useShell) {
|
||||
logger.debug(
|
||||
'[ProcessManager] Forcing shell=true for agent spawn on Windows (runInShell or auto)',
|
||||
|
||||
Reference in New Issue
Block a user