mirror of
https://github.com/jlengrand/Maestro.git
synced 2026-03-10 08:31:19 +00:00
fix: Windows PATH issues and SSH remote execution improvements
- Fix Windows agent execution by using buildExpandedEnv for proper PATH expansion - Add PowerShell support for SSH commands to handle long command lines (32K+ chars) - Implement here document support for large OpenCode prompts over SSH - Add raw stdin prompt sending for agents without stream-json support - Restrict inline wizard to Claude, Claude Code, and Codex (OpenCode incompatible) - Improve argument escaping for both cmd.exe and PowerShell shells - Update tsconfig.main.json to include shared files for proper compilation - Enhance agent path resolution for packaged Electron applications - Add read-only mode for OpenCode in wizard conversations - Update tests and UI components for better SSH remote configuration
This commit is contained in:
@@ -392,11 +392,10 @@ describe('agent-detector', () => {
|
|||||||
expect.stringContaining('Agent detection starting'),
|
expect.stringContaining('Agent detection starting'),
|
||||||
'AgentDetector'
|
'AgentDetector'
|
||||||
);
|
);
|
||||||
expect(logger.info).toHaveBeenCalledWith(
|
const calls = logger.info.mock.calls;
|
||||||
expect.stringContaining('Agent detection complete'),
|
const completeCall = calls.find((call) => call[0].includes('Agent detection complete'));
|
||||||
'AgentDetector',
|
expect(completeCall).toBeDefined();
|
||||||
expect.any(Object)
|
expect(completeCall[1]).toBe('AgentDetector');
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should log when agents are found', async () => {
|
it('should log when agents are found', async () => {
|
||||||
|
|||||||
@@ -376,7 +376,7 @@ describe('Auto Run Folder Validation', () => {
|
|||||||
expect(validatePathWithinFolder(joined, folderPath)).toBe(true); // It's still within the folder
|
expect(validatePathWithinFolder(joined, folderPath)).toBe(true); // It's still within the folder
|
||||||
} else {
|
} else {
|
||||||
// On Unix, path.join with absolute second arg gives the absolute path
|
// On Unix, path.join with absolute second arg gives the absolute path
|
||||||
expect(joined).toBe('/etc/passwd');
|
expect(joined).toBe('/test/autorun/etc/passwd');
|
||||||
expect(validatePathWithinFolder(joined, folderPath)).toBe(false);
|
expect(validatePathWithinFolder(joined, folderPath)).toBe(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -240,6 +240,9 @@ describe('process IPC handlers', () => {
|
|||||||
name: 'Claude Code',
|
name: 'Claude Code',
|
||||||
requiresPty: true,
|
requiresPty: true,
|
||||||
path: '/usr/local/bin/claude',
|
path: '/usr/local/bin/claude',
|
||||||
|
capabilities: {
|
||||||
|
supportsStreamJsonInput: true,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
mockAgentDetector.getAgent.mockResolvedValue(mockAgent);
|
mockAgentDetector.getAgent.mockResolvedValue(mockAgent);
|
||||||
@@ -866,6 +869,9 @@ describe('process IPC handlers', () => {
|
|||||||
const mockAgent = {
|
const mockAgent = {
|
||||||
id: 'claude-code',
|
id: 'claude-code',
|
||||||
requiresPty: true, // Note: should be disabled when using SSH
|
requiresPty: true, // Note: should be disabled when using SSH
|
||||||
|
capabilities: {
|
||||||
|
supportsStreamJsonInput: true,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
mockAgentDetector.getAgent.mockResolvedValue(mockAgent);
|
mockAgentDetector.getAgent.mockResolvedValue(mockAgent);
|
||||||
@@ -946,6 +952,9 @@ describe('process IPC handlers', () => {
|
|||||||
const mockAgent = {
|
const mockAgent = {
|
||||||
id: 'claude-code',
|
id: 'claude-code',
|
||||||
requiresPty: false,
|
requiresPty: false,
|
||||||
|
capabilities: {
|
||||||
|
supportsStreamJsonInput: true,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// Mock applyAgentConfigOverrides to return custom env vars
|
// Mock applyAgentConfigOverrides to return custom env vars
|
||||||
@@ -1073,6 +1082,9 @@ describe('process IPC handlers', () => {
|
|||||||
const mockAgent = {
|
const mockAgent = {
|
||||||
id: 'claude-code',
|
id: 'claude-code',
|
||||||
requiresPty: false,
|
requiresPty: false,
|
||||||
|
capabilities: {
|
||||||
|
supportsStreamJsonInput: true,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
mockAgentDetector.getAgent.mockResolvedValue(mockAgent);
|
mockAgentDetector.getAgent.mockResolvedValue(mockAgent);
|
||||||
@@ -1120,6 +1132,9 @@ describe('process IPC handlers', () => {
|
|||||||
binaryName: 'codex', // Just the binary name, without path
|
binaryName: 'codex', // Just the binary name, without path
|
||||||
path: '/opt/homebrew/bin/codex', // Local macOS path
|
path: '/opt/homebrew/bin/codex', // Local macOS path
|
||||||
requiresPty: false,
|
requiresPty: false,
|
||||||
|
capabilities: {
|
||||||
|
supportsStreamJsonInput: false,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
mockAgentDetector.getAgent.mockResolvedValue(mockAgent);
|
mockAgentDetector.getAgent.mockResolvedValue(mockAgent);
|
||||||
@@ -1161,6 +1176,9 @@ describe('process IPC handlers', () => {
|
|||||||
binaryName: 'codex',
|
binaryName: 'codex',
|
||||||
path: '/opt/homebrew/bin/codex', // Local path
|
path: '/opt/homebrew/bin/codex', // Local path
|
||||||
requiresPty: false,
|
requiresPty: false,
|
||||||
|
capabilities: {
|
||||||
|
supportsStreamJsonInput: false,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
mockAgentDetector.getAgent.mockResolvedValue(mockAgent);
|
mockAgentDetector.getAgent.mockResolvedValue(mockAgent);
|
||||||
|
|||||||
@@ -547,10 +547,10 @@ describe('useInputProcessing', () => {
|
|||||||
isBuiltIn: true,
|
isBuiltIn: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'speckit-specify',
|
id: 'test-command',
|
||||||
command: '/commit',
|
command: '/testcommand',
|
||||||
description: 'Create a feature spec',
|
description: 'Test command',
|
||||||
prompt: 'Create a spec for: $ARGUMENTS',
|
prompt: 'Test: $ARGUMENTS',
|
||||||
isBuiltIn: true,
|
isBuiltIn: true,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
@@ -559,7 +559,7 @@ describe('useInputProcessing', () => {
|
|||||||
vi.useFakeTimers();
|
vi.useFakeTimers();
|
||||||
|
|
||||||
const deps = createDeps({
|
const deps = createDeps({
|
||||||
inputValue: '/speckit.constitution Blah blah blah',
|
inputValue: '/testcommand Blah blah blah',
|
||||||
customAICommands: speckitCommandsWithArgs,
|
customAICommands: speckitCommandsWithArgs,
|
||||||
});
|
});
|
||||||
const { result } = renderHook(() => useInputProcessing(deps));
|
const { result } = renderHook(() => useInputProcessing(deps));
|
||||||
@@ -583,7 +583,7 @@ describe('useInputProcessing', () => {
|
|||||||
const queuedItem = callArgs[1] as QueuedItem;
|
const queuedItem = callArgs[1] as QueuedItem;
|
||||||
|
|
||||||
expect(queuedItem.type).toBe('command');
|
expect(queuedItem.type).toBe('command');
|
||||||
expect(queuedItem.command).toBe('/speckit.constitution');
|
expect(queuedItem.command).toBe('/testcommand');
|
||||||
expect(queuedItem.commandArgs).toBe('Blah blah blah');
|
expect(queuedItem.commandArgs).toBe('Blah blah blah');
|
||||||
|
|
||||||
vi.useRealTimers();
|
vi.useRealTimers();
|
||||||
@@ -619,7 +619,7 @@ describe('useInputProcessing', () => {
|
|||||||
vi.useFakeTimers();
|
vi.useFakeTimers();
|
||||||
|
|
||||||
const deps = createDeps({
|
const deps = createDeps({
|
||||||
inputValue: '/commit Add user authentication with OAuth 2.0 support',
|
inputValue: '/testcommand Add user authentication with OAuth 2.0 support',
|
||||||
customAICommands: speckitCommandsWithArgs,
|
customAICommands: speckitCommandsWithArgs,
|
||||||
});
|
});
|
||||||
const { result } = renderHook(() => useInputProcessing(deps));
|
const { result } = renderHook(() => useInputProcessing(deps));
|
||||||
@@ -633,7 +633,7 @@ describe('useInputProcessing', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const queuedItem = mockProcessQueuedItemRef.current.mock.calls[0][1] as QueuedItem;
|
const queuedItem = mockProcessQueuedItemRef.current.mock.calls[0][1] as QueuedItem;
|
||||||
expect(queuedItem.command).toBe('/commit');
|
expect(queuedItem.command).toBe('/testcommand');
|
||||||
expect(queuedItem.commandArgs).toBe('Add user authentication with OAuth 2.0 support');
|
expect(queuedItem.commandArgs).toBe('Add user authentication with OAuth 2.0 support');
|
||||||
|
|
||||||
vi.useRealTimers();
|
vi.useRealTimers();
|
||||||
|
|||||||
@@ -269,7 +269,7 @@ export const AGENT_CAPABILITIES: Record<string, AgentCapabilities> = {
|
|||||||
supportsStreaming: true, // Streams JSONL events - Verified
|
supportsStreaming: true, // Streams JSONL events - Verified
|
||||||
supportsResultMessages: true, // step_finish with part.reason:"stop" - Verified
|
supportsResultMessages: true, // step_finish with part.reason:"stop" - Verified
|
||||||
supportsModelSelection: true, // --model provider/model (e.g., 'ollama/qwen3:8b') - Verified
|
supportsModelSelection: true, // --model provider/model (e.g., 'ollama/qwen3:8b') - Verified
|
||||||
supportsStreamJsonInput: false, // Uses -f, --file flag instead
|
supportsStreamJsonInput: false, // Uses positional arguments for prompt
|
||||||
supportsThinkingDisplay: true, // Emits streaming text chunks
|
supportsThinkingDisplay: true, // Emits streaming text chunks
|
||||||
supportsContextMerge: true, // Can receive merged context via prompts
|
supportsContextMerge: true, // Can receive merged context via prompts
|
||||||
supportsContextExport: true, // Session storage supports context export
|
supportsContextExport: true, // Session storage supports context export
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import {
|
|||||||
} from '../../utils/ipcHandler';
|
} from '../../utils/ipcHandler';
|
||||||
import { getSshRemoteConfig, createSshRemoteStoreAdapter } from '../../utils/ssh-remote-resolver';
|
import { getSshRemoteConfig, createSshRemoteStoreAdapter } from '../../utils/ssh-remote-resolver';
|
||||||
import { buildSshCommand } from '../../utils/ssh-command-builder';
|
import { buildSshCommand } from '../../utils/ssh-command-builder';
|
||||||
|
import { buildExpandedEnv } from '../../../shared/pathUtils';
|
||||||
import type { SshRemoteConfig } from '../../../shared/types';
|
import type { SshRemoteConfig } from '../../../shared/types';
|
||||||
import { powerManager } from '../../power-manager';
|
import { powerManager } from '../../power-manager';
|
||||||
import { MaestroSettings } from './persistence';
|
import { MaestroSettings } from './persistence';
|
||||||
@@ -262,6 +263,7 @@ export function registerProcessHandlers(deps: ProcessHandlerDependencies): void
|
|||||||
let useShell = false;
|
let useShell = false;
|
||||||
let sshRemoteUsed: SshRemoteConfig | null = null;
|
let sshRemoteUsed: SshRemoteConfig | null = null;
|
||||||
let customEnvVarsToPass: Record<string, string> | undefined = effectiveCustomEnvVars;
|
let customEnvVarsToPass: Record<string, string> | undefined = effectiveCustomEnvVars;
|
||||||
|
let useHereDocForOpenCode = false;
|
||||||
|
|
||||||
if (config.sessionCustomPath) {
|
if (config.sessionCustomPath) {
|
||||||
logger.debug(`Using session-level custom path for ${config.toolType}`, LOG_CONTEXT, {
|
logger.debug(`Using session-level custom path for ${config.toolType}`, LOG_CONTEXT, {
|
||||||
@@ -273,13 +275,11 @@ export function registerProcessHandlers(deps: ProcessHandlerDependencies): void
|
|||||||
// On Windows (except SSH), always use shell execution for agents
|
// On Windows (except SSH), always use shell execution for agents
|
||||||
if (isWindows && !config.sessionSshRemoteConfig?.enabled) {
|
if (isWindows && !config.sessionSshRemoteConfig?.enabled) {
|
||||||
useShell = true;
|
useShell = true;
|
||||||
// Merge process.env with custom env vars, to ensure PATH is present
|
// Use expanded environment with custom env vars to ensure PATH includes all binary locations
|
||||||
// Only keep string values (filter out undefined)
|
const expandedEnv = buildExpandedEnv(customEnvVarsToPass);
|
||||||
|
// Filter out undefined values to match Record<string, string> type
|
||||||
customEnvVarsToPass = Object.fromEntries(
|
customEnvVarsToPass = Object.fromEntries(
|
||||||
Object.entries({
|
Object.entries(expandedEnv).filter(([_, value]) => value !== undefined)
|
||||||
...process.env,
|
|
||||||
...(customEnvVarsToPass || {}),
|
|
||||||
}).filter(([_, v]) => typeof v === 'string')
|
|
||||||
) as Record<string, string>;
|
) as Record<string, string>;
|
||||||
|
|
||||||
// Determine an explicit shell to use when forcing shell execution on Windows.
|
// Determine an explicit shell to use when forcing shell execution on Windows.
|
||||||
@@ -320,6 +320,7 @@ export function registerProcessHandlers(deps: ProcessHandlerDependencies): void
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
let shouldSendPromptViaStdin = false;
|
let shouldSendPromptViaStdin = false;
|
||||||
|
let shouldSendPromptViaStdinRaw = false;
|
||||||
if (config.toolType !== 'terminal' && config.sessionSshRemoteConfig?.enabled) {
|
if (config.toolType !== 'terminal' && config.sessionSshRemoteConfig?.enabled) {
|
||||||
// Session-level SSH config provided - resolve and use it
|
// Session-level SSH config provided - resolve and use it
|
||||||
logger.info(`Using session-level SSH config`, LOG_CONTEXT, {
|
logger.info(`Using session-level SSH config`, LOG_CONTEXT, {
|
||||||
@@ -351,6 +352,7 @@ export function registerProcessHandlers(deps: ProcessHandlerDependencies): void
|
|||||||
const isLargePrompt = config.prompt && config.prompt.length > 4000;
|
const isLargePrompt = config.prompt && config.prompt.length > 4000;
|
||||||
const hasStreamJsonInput =
|
const hasStreamJsonInput =
|
||||||
finalArgs.includes('--input-format') && finalArgs.includes('stream-json');
|
finalArgs.includes('--input-format') && finalArgs.includes('stream-json');
|
||||||
|
const agentSupportsStreamJson = agent?.capabilities.supportsStreamJsonInput ?? false;
|
||||||
let sshArgs = finalArgs;
|
let sshArgs = finalArgs;
|
||||||
if (config.prompt && !isLargePrompt && !hasStreamJsonInput) {
|
if (config.prompt && !isLargePrompt && !hasStreamJsonInput) {
|
||||||
// Small prompt - embed in command line as usual (only if not using stream-json input)
|
// Small prompt - embed in command line as usual (only if not using stream-json input)
|
||||||
@@ -361,8 +363,12 @@ export function registerProcessHandlers(deps: ProcessHandlerDependencies): void
|
|||||||
} else {
|
} else {
|
||||||
sshArgs = [...finalArgs, '--', config.prompt];
|
sshArgs = [...finalArgs, '--', config.prompt];
|
||||||
}
|
}
|
||||||
} else if (config.prompt && (isLargePrompt || hasStreamJsonInput)) {
|
} else if (
|
||||||
// Large prompt or stream-json input - ensure --input-format stream-json is present
|
config.prompt &&
|
||||||
|
(isLargePrompt || hasStreamJsonInput) &&
|
||||||
|
agentSupportsStreamJson
|
||||||
|
) {
|
||||||
|
// Large prompt or stream-json input, and agent supports it - use stdin
|
||||||
if (!hasStreamJsonInput) {
|
if (!hasStreamJsonInput) {
|
||||||
sshArgs = [...finalArgs, '--input-format', 'stream-json'];
|
sshArgs = [...finalArgs, '--input-format', 'stream-json'];
|
||||||
}
|
}
|
||||||
@@ -375,16 +381,43 @@ export function registerProcessHandlers(deps: ProcessHandlerDependencies): void
|
|||||||
: 'stream-json-input-mode',
|
: 'stream-json-input-mode',
|
||||||
hasStreamJsonInput,
|
hasStreamJsonInput,
|
||||||
});
|
});
|
||||||
|
} else if (config.prompt && isLargePrompt && !agentSupportsStreamJson) {
|
||||||
|
// Large prompt but agent doesn't support stream-json
|
||||||
|
if (config.toolType === 'opencode') {
|
||||||
|
// OpenCode: mark for here document processing (will be handled after remoteCommand is set)
|
||||||
|
useHereDocForOpenCode = true;
|
||||||
|
} else {
|
||||||
|
// Other agents: send via stdin as raw text
|
||||||
|
shouldSendPromptViaStdinRaw = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build the SSH command that wraps the agent execution
|
|
||||||
//
|
//
|
||||||
// Determine the command to run on the remote host:
|
// Determine the command to run on the remote host:
|
||||||
// 1. If user set a session-specific custom path, use that (they configured it for the remote)
|
// 1. If user set a session-specific custom path, use that (they configured it for the remote)
|
||||||
// 2. Otherwise, use the agent's binaryName (e.g., 'codex', 'claude') and let
|
// 2. Otherwise, use the agent's binaryName (e.g., 'codex', 'claude') and let
|
||||||
// the remote shell's PATH resolve it. This avoids using local paths like
|
// the remote shell's PATH resolve it. This avoids using local paths like
|
||||||
// '/opt/homebrew/bin/codex' which don't exist on the remote host.
|
// '/opt/homebrew/bin/codex' which don't exist on the remote host.
|
||||||
const remoteCommand = config.sessionCustomPath || agent?.binaryName || config.command;
|
let remoteCommand = config.sessionCustomPath || agent?.binaryName || config.command;
|
||||||
|
|
||||||
|
// Handle OpenCode here document for large prompts
|
||||||
|
if (useHereDocForOpenCode && config.prompt) {
|
||||||
|
// OpenCode: use here document to avoid command line limits
|
||||||
|
// Escape single quotes in the prompt for bash here document
|
||||||
|
const escapedPrompt = config.prompt.replace(/'/g, "'\\''");
|
||||||
|
// Construct: cat << 'EOF' | opencode run --format json\nlong prompt here\nEOF
|
||||||
|
const hereDocCommand = `cat << 'EOF' | ${remoteCommand} ${sshArgs.join(' ')}\n${escapedPrompt}\nEOF`;
|
||||||
|
sshArgs = []; // Clear args since they're now in the here doc command
|
||||||
|
remoteCommand = hereDocCommand; // Update to use here document
|
||||||
|
logger.info(
|
||||||
|
`Using here document for large OpenCode prompt to avoid command line limits`,
|
||||||
|
LOG_CONTEXT,
|
||||||
|
{
|
||||||
|
sessionId: config.sessionId,
|
||||||
|
promptLength: config.prompt?.length,
|
||||||
|
commandLength: hereDocCommand.length,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
// Decide whether we'll send input via stdin to the remote command
|
// Decide whether we'll send input via stdin to the remote command
|
||||||
const useStdin = sshArgs.includes('--input-format') && sshArgs.includes('stream-json');
|
const useStdin = sshArgs.includes('--input-format') && sshArgs.includes('stream-json');
|
||||||
|
|
||||||
@@ -406,6 +439,21 @@ export function registerProcessHandlers(deps: ProcessHandlerDependencies): void
|
|||||||
// For SSH, env vars are passed in the remote command string, not locally
|
// For SSH, env vars are passed in the remote command string, not locally
|
||||||
customEnvVarsToPass = undefined;
|
customEnvVarsToPass = undefined;
|
||||||
|
|
||||||
|
// On Windows, use PowerShell for SSH commands to avoid cmd.exe's 8191 character limit
|
||||||
|
// PowerShell supports up to 32,767 characters, which is needed for large prompts
|
||||||
|
if (isWindows) {
|
||||||
|
useShell = true;
|
||||||
|
shellToUse = 'powershell.exe';
|
||||||
|
logger.info(
|
||||||
|
`Using PowerShell for SSH command on Windows to support long command lines`,
|
||||||
|
LOG_CONTEXT,
|
||||||
|
{
|
||||||
|
sessionId: config.sessionId,
|
||||||
|
commandLength: sshCommand.args.join(' ').length,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Detailed debug logging to diagnose SSH command execution issues
|
// Detailed debug logging to diagnose SSH command execution issues
|
||||||
logger.debug(`SSH command details for debugging`, LOG_CONTEXT, {
|
logger.debug(`SSH command details for debugging`, LOG_CONTEXT, {
|
||||||
sessionId: config.sessionId,
|
sessionId: config.sessionId,
|
||||||
@@ -427,6 +475,15 @@ export function registerProcessHandlers(deps: ProcessHandlerDependencies): void
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Debug logging for shell configuration
|
||||||
|
logger.info(`Shell configuration before spawn`, LOG_CONTEXT, {
|
||||||
|
sessionId: config.sessionId,
|
||||||
|
useShell,
|
||||||
|
shellToUse,
|
||||||
|
isWindows,
|
||||||
|
isSshCommand: !!sshRemoteUsed,
|
||||||
|
});
|
||||||
|
|
||||||
const result = processManager.spawn({
|
const result = processManager.spawn({
|
||||||
...config,
|
...config,
|
||||||
command: commandToSpawn,
|
command: commandToSpawn,
|
||||||
@@ -458,6 +515,7 @@ export function registerProcessHandlers(deps: ProcessHandlerDependencies): void
|
|||||||
noPromptSeparator: agent?.noPromptSeparator, // Some agents don't support '--' before prompt
|
noPromptSeparator: agent?.noPromptSeparator, // Some agents don't support '--' before prompt
|
||||||
// For SSH with stream-json input, send prompt via stdin instead of command line
|
// For SSH with stream-json input, send prompt via stdin instead of command line
|
||||||
sendPromptViaStdin: shouldSendPromptViaStdin ? true : undefined,
|
sendPromptViaStdin: shouldSendPromptViaStdin ? true : undefined,
|
||||||
|
sendPromptViaStdinRaw: shouldSendPromptViaStdinRaw ? true : undefined,
|
||||||
// Stats tracking: use cwd as projectPath if not explicitly provided
|
// Stats tracking: use cwd as projectPath if not explicitly provided
|
||||||
projectPath: config.cwd,
|
projectPath: config.cwd,
|
||||||
// SSH remote context (for SSH-specific error messages)
|
// SSH remote context (for SSH-specific error messages)
|
||||||
|
|||||||
@@ -177,6 +177,30 @@ export class ChildProcessSpawner {
|
|||||||
'ProcessManager',
|
'ProcessManager',
|
||||||
{ command: spawnCommand }
|
{ command: spawnCommand }
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Check if we're using PowerShell (for SSH commands to avoid cmd.exe 8191 char limit)
|
||||||
|
const isPowerShell =
|
||||||
|
typeof config.shell === 'string' && config.shell.toLowerCase().includes('powershell');
|
||||||
|
|
||||||
|
if (isPowerShell) {
|
||||||
|
// Escape arguments for PowerShell (supports longer command lines than cmd.exe)
|
||||||
|
spawnArgs = finalArgs.map((arg) => {
|
||||||
|
const needsQuoting = /[ &|<>^%!()"\n\r#?*`$]/.test(arg) || arg.length > 100;
|
||||||
|
if (needsQuoting) {
|
||||||
|
// PowerShell escaping: wrap in single quotes, escape single quotes by doubling
|
||||||
|
const escaped = arg.replace(/'/g, "''");
|
||||||
|
return `'${escaped}'`;
|
||||||
|
}
|
||||||
|
return arg;
|
||||||
|
});
|
||||||
|
logger.info('[ProcessManager] Escaped args for PowerShell', 'ProcessManager', {
|
||||||
|
originalArgsCount: finalArgs.length,
|
||||||
|
escapedArgsCount: spawnArgs.length,
|
||||||
|
escapedPromptArgLength: spawnArgs[spawnArgs.length - 1]?.length,
|
||||||
|
escapedPromptArgPreview: spawnArgs[spawnArgs.length - 1]?.substring(0, 200),
|
||||||
|
argsModified: finalArgs.some((arg, i) => arg !== spawnArgs[i]),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
// Escape arguments for cmd.exe when using shell
|
// Escape arguments for cmd.exe when using shell
|
||||||
spawnArgs = finalArgs.map((arg) => {
|
spawnArgs = finalArgs.map((arg) => {
|
||||||
const needsQuoting = /[ &|<>^%!()"\n\r#?*]/.test(arg) || arg.length > 100;
|
const needsQuoting = /[ &|<>^%!()"\n\r#?*]/.test(arg) || arg.length > 100;
|
||||||
@@ -194,6 +218,7 @@ export class ChildProcessSpawner {
|
|||||||
argsModified: finalArgs.some((arg, i) => arg !== spawnArgs[i]),
|
argsModified: finalArgs.some((arg, i) => arg !== spawnArgs[i]),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Determine shell option to pass to child_process.spawn.
|
// Determine shell option to pass to child_process.spawn.
|
||||||
// If the caller provided a specific shell path, prefer that (string).
|
// If the caller provided a specific shell path, prefer that (string).
|
||||||
@@ -241,7 +266,8 @@ export class ChildProcessSpawner {
|
|||||||
argsContain('--json') ||
|
argsContain('--json') ||
|
||||||
(argsContain('--format') && argsContain('json')) ||
|
(argsContain('--format') && argsContain('json')) ||
|
||||||
(hasImages && !!prompt) ||
|
(hasImages && !!prompt) ||
|
||||||
!!config.sendPromptViaStdin;
|
!!config.sendPromptViaStdin ||
|
||||||
|
!!config.sendPromptViaStdinRaw;
|
||||||
|
|
||||||
// Get the output parser for this agent type
|
// Get the output parser for this agent type
|
||||||
const outputParser = getOutputParser(toolType) || undefined;
|
const outputParser = getOutputParser(toolType) || undefined;
|
||||||
@@ -253,8 +279,10 @@ export class ChildProcessSpawner {
|
|||||||
parserId: outputParser?.agentId,
|
parserId: outputParser?.agentId,
|
||||||
isStreamJsonMode,
|
isStreamJsonMode,
|
||||||
isBatchMode,
|
isBatchMode,
|
||||||
|
command: config.command,
|
||||||
|
argsCount: finalArgs.length,
|
||||||
argsPreview:
|
argsPreview:
|
||||||
finalArgs.length > 0 ? finalArgs[finalArgs.length - 1]?.substring(0, 200) : undefined,
|
finalArgs.length > 0 ? finalArgs[finalArgs.length - 1]?.substring(0, 500) : undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
const managedProcess: ManagedProcess = {
|
const managedProcess: ManagedProcess = {
|
||||||
@@ -386,6 +414,15 @@ export class ChildProcessSpawner {
|
|||||||
|
|
||||||
// Handle stdin for batch mode and stream-json
|
// Handle stdin for batch mode and stream-json
|
||||||
if (isStreamJsonMode && prompt) {
|
if (isStreamJsonMode && prompt) {
|
||||||
|
if (config.sendPromptViaStdinRaw) {
|
||||||
|
// Send raw prompt via stdin
|
||||||
|
logger.debug('[ProcessManager] Sending raw prompt via stdin', 'ProcessManager', {
|
||||||
|
sessionId,
|
||||||
|
promptLength: prompt.length,
|
||||||
|
});
|
||||||
|
childProcess.stdin?.write(prompt);
|
||||||
|
childProcess.stdin?.end();
|
||||||
|
} else {
|
||||||
// Stream-json mode: send the message via stdin
|
// Stream-json mode: send the message via stdin
|
||||||
const streamJsonMessage = buildStreamJsonMessage(prompt, images || []);
|
const streamJsonMessage = buildStreamJsonMessage(prompt, images || []);
|
||||||
logger.debug('[ProcessManager] Sending stream-json message via stdin', 'ProcessManager', {
|
logger.debug('[ProcessManager] Sending stream-json message via stdin', 'ProcessManager', {
|
||||||
@@ -396,6 +433,7 @@ export class ChildProcessSpawner {
|
|||||||
});
|
});
|
||||||
childProcess.stdin?.write(streamJsonMessage + '\n');
|
childProcess.stdin?.write(streamJsonMessage + '\n');
|
||||||
childProcess.stdin?.end();
|
childProcess.stdin?.end();
|
||||||
|
}
|
||||||
} else if (isBatchMode) {
|
} else if (isBatchMode) {
|
||||||
// Regular batch mode: close stdin immediately
|
// Regular batch mode: close stdin immediately
|
||||||
logger.debug('[ProcessManager] Closing stdin for batch mode', 'ProcessManager', {
|
logger.debug('[ProcessManager] Closing stdin for batch mode', 'ProcessManager', {
|
||||||
|
|||||||
@@ -32,6 +32,8 @@ export interface ProcessConfig {
|
|||||||
runInShell?: boolean;
|
runInShell?: boolean;
|
||||||
/** If true, send the prompt via stdin as JSON instead of command line */
|
/** If true, send the prompt via stdin as JSON instead of command line */
|
||||||
sendPromptViaStdin?: boolean;
|
sendPromptViaStdin?: boolean;
|
||||||
|
/** If true, send the prompt via stdin as raw text instead of command line */
|
||||||
|
sendPromptViaStdinRaw?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -101,13 +101,21 @@ export function buildRemoteCommand(options: RemoteCommandOptions): string {
|
|||||||
// Build the command with arguments
|
// Build the command with arguments
|
||||||
const commandWithArgs = buildShellCommand(command, args);
|
const commandWithArgs = buildShellCommand(command, args);
|
||||||
|
|
||||||
// If command expects JSON via stdin (stream-json), use exec to replace the
|
// Handle stdin input modes
|
||||||
// shell process so stdin is delivered directly to the agent binary and no
|
let finalCommandWithArgs: string;
|
||||||
// intermediate shell produces control sequences that could corrupt the stream.
|
if (options.useStdin) {
|
||||||
const hasStreamJsonInput = options.useStdin
|
const hasStreamJsonInput =
|
||||||
? true
|
Array.isArray(args) && args.includes('--input-format') && args.includes('stream-json');
|
||||||
: Array.isArray(args) && args.includes('--input-format') && args.includes('stream-json');
|
if (hasStreamJsonInput) {
|
||||||
const finalCommandWithArgs = hasStreamJsonInput ? `exec ${commandWithArgs}` : commandWithArgs;
|
// Stream-JSON mode: use exec to avoid shell control sequences
|
||||||
|
finalCommandWithArgs = `exec ${commandWithArgs}`;
|
||||||
|
} else {
|
||||||
|
// Raw prompt mode: pipe stdin directly to the command
|
||||||
|
finalCommandWithArgs = commandWithArgs;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
finalCommandWithArgs = commandWithArgs;
|
||||||
|
}
|
||||||
|
|
||||||
// Combine env exports with command
|
// Combine env exports with command
|
||||||
let fullCommand: string;
|
let fullCommand: string;
|
||||||
|
|||||||
@@ -318,7 +318,8 @@ export function NewInstanceModal({
|
|||||||
// (hidden agents like 'terminal' should never be auto-selected)
|
// (hidden agents like 'terminal' should never be auto-selected)
|
||||||
if (source) {
|
if (source) {
|
||||||
setSelectedAgent(source.toolType);
|
setSelectedAgent(source.toolType);
|
||||||
} else {
|
} else if (!sshRemoteId) {
|
||||||
|
// Only auto-select on initial load, not on SSH remote re-detection
|
||||||
const firstAvailable = detectedAgents.find((a: AgentConfig) => a.available && !a.hidden);
|
const firstAvailable = detectedAgents.find((a: AgentConfig) => a.available && !a.hidden);
|
||||||
if (firstAvailable) {
|
if (firstAvailable) {
|
||||||
setSelectedAgent(firstAvailable.id);
|
setSelectedAgent(firstAvailable.id);
|
||||||
@@ -599,8 +600,12 @@ export function NewInstanceModal({
|
|||||||
// Transfer pending SSH config to selected agent automatically
|
// Transfer pending SSH config to selected agent automatically
|
||||||
// This ensures SSH config is preserved when agent is auto-selected or manually clicked
|
// This ensures SSH config is preserved when agent is auto-selected or manually clicked
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (selectedAgent && agentSshRemoteConfigs['_pending_'] && !agentSshRemoteConfigs[selectedAgent]) {
|
if (
|
||||||
setAgentSshRemoteConfigs(prev => ({
|
selectedAgent &&
|
||||||
|
agentSshRemoteConfigs['_pending_'] &&
|
||||||
|
!agentSshRemoteConfigs[selectedAgent]
|
||||||
|
) {
|
||||||
|
setAgentSshRemoteConfigs((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
[selectedAgent]: prev['_pending_'],
|
[selectedAgent]: prev['_pending_'],
|
||||||
}));
|
}));
|
||||||
@@ -644,7 +649,6 @@ export function NewInstanceModal({
|
|||||||
|
|
||||||
// Re-run agent detection with the new SSH remote ID
|
// Re-run agent detection with the new SSH remote ID
|
||||||
loadAgents(undefined, currentSshRemoteId ?? undefined);
|
loadAgents(undefined, currentSshRemoteId ?? undefined);
|
||||||
|
|
||||||
}, [isOpen, currentSshRemoteId]);
|
}, [isOpen, currentSshRemoteId]);
|
||||||
|
|
||||||
if (!isOpen) return null;
|
if (!isOpen) return null;
|
||||||
@@ -1131,11 +1135,13 @@ export function NewInstanceModal({
|
|||||||
agentSshRemoteConfigs[selectedAgent] || agentSshRemoteConfigs['_pending_']
|
agentSshRemoteConfigs[selectedAgent] || agentSshRemoteConfigs['_pending_']
|
||||||
}
|
}
|
||||||
onSshRemoteConfigChange={(config) => {
|
onSshRemoteConfigChange={(config) => {
|
||||||
const key = selectedAgent || '_pending_';
|
setAgentSshRemoteConfigs((prev) => {
|
||||||
setAgentSshRemoteConfigs((prev) => ({
|
const newConfigs = { ...prev, _pending_: config };
|
||||||
...prev,
|
if (selectedAgent) {
|
||||||
[key]: config,
|
newConfigs[selectedAgent] = config;
|
||||||
}));
|
}
|
||||||
|
return newConfigs;
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -469,7 +469,6 @@ export function AgentSelectionScreen({ theme }: AgentSelectionScreenProps): JSX.
|
|||||||
// Using JSON.stringify with 'null' fallback to ensure the effect runs when switching
|
// Using JSON.stringify with 'null' fallback to ensure the effect runs when switching
|
||||||
// between remote and local (JSON.stringify(undefined) returns undefined, not 'null',
|
// between remote and local (JSON.stringify(undefined) returns undefined, not 'null',
|
||||||
// so we need the fallback to ensure React sees it as a real string change)
|
// so we need the fallback to ensure React sees it as a real string change)
|
||||||
|
|
||||||
}, [setAvailableAgents, setSelectedAgent, JSON.stringify(sshRemoteConfig) ?? 'null']);
|
}, [setAvailableAgents, setSelectedAgent, JSON.stringify(sshRemoteConfig) ?? 'null']);
|
||||||
|
|
||||||
// Load SSH remote configurations on mount
|
// Load SSH remote configurations on mount
|
||||||
@@ -858,9 +857,54 @@ export function AgentSelectionScreen({ theme }: AgentSelectionScreenProps): JSX.
|
|||||||
<ArrowLeft className="w-4 h-4" />
|
<ArrowLeft className="w-4 h-4" />
|
||||||
Back
|
Back
|
||||||
</button>
|
</button>
|
||||||
|
<div className="flex flex-col items-center gap-2">
|
||||||
<h3 className="text-xl font-semibold" style={{ color: theme.colors.textMain }}>
|
<h3 className="text-xl font-semibold" style={{ color: theme.colors.textMain }}>
|
||||||
Configure {configuringTile.name}
|
Configure {configuringTile.name}
|
||||||
</h3>
|
</h3>
|
||||||
|
{/* SSH Remote Location Dropdown - only shown if remotes are configured */}
|
||||||
|
{sshRemotes.length > 0 && (
|
||||||
|
<div className="flex items-center gap-2 text-sm">
|
||||||
|
<span style={{ color: theme.colors.textDim }}>on</span>
|
||||||
|
<select
|
||||||
|
value={sshRemoteConfig?.enabled ? sshRemoteConfig.remoteId || '' : ''}
|
||||||
|
onChange={(e) => {
|
||||||
|
const remoteId = e.target.value;
|
||||||
|
if (remoteId === '') {
|
||||||
|
// Local machine selected
|
||||||
|
setSshRemoteConfig(undefined);
|
||||||
|
// Also update wizard context immediately
|
||||||
|
setWizardSessionSshRemoteConfig({ enabled: false, remoteId: null });
|
||||||
|
} else {
|
||||||
|
// Remote selected
|
||||||
|
setSshRemoteConfig({
|
||||||
|
enabled: true,
|
||||||
|
remoteId,
|
||||||
|
});
|
||||||
|
// Also update wizard context immediately
|
||||||
|
setWizardSessionSshRemoteConfig({
|
||||||
|
enabled: true,
|
||||||
|
remoteId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
className="px-3 py-1 rounded border outline-none transition-all cursor-pointer text-xs"
|
||||||
|
style={{
|
||||||
|
backgroundColor: theme.colors.bgMain,
|
||||||
|
borderColor: theme.colors.border,
|
||||||
|
color: theme.colors.textMain,
|
||||||
|
}}
|
||||||
|
aria-label="Agent location"
|
||||||
|
>
|
||||||
|
<option value="">Local Machine</option>
|
||||||
|
{sshRemotes.map((remote) => (
|
||||||
|
<option key={remote.id} value={remote.id}>
|
||||||
|
{remote.name || remote.host}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
<div className="w-20" /> {/* Spacer for centering */}
|
<div className="w-20" /> {/* Spacer for centering */}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -651,12 +651,7 @@ class ConversationManager {
|
|||||||
// Must include these explicitly since wizard pre-builds args before IPC handler
|
// Must include these explicitly since wizard pre-builds args before IPC handler
|
||||||
const args = [];
|
const args = [];
|
||||||
|
|
||||||
// Add batch mode prefix: 'exec'
|
// Add base args (if any) - batchModePrefix will be added by buildAgentArgs
|
||||||
if (agent.batchModePrefix) {
|
|
||||||
args.push(...agent.batchModePrefix);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add base args (if any)
|
|
||||||
args.push(...(agent.args || []));
|
args.push(...(agent.args || []));
|
||||||
|
|
||||||
// Add batch mode args: '--dangerously-bypass-approvals-and-sandbox', '--skip-git-repo-check'
|
// Add batch mode args: '--dangerously-bypass-approvals-and-sandbox', '--skip-git-repo-check'
|
||||||
@@ -676,12 +671,7 @@ class ConversationManager {
|
|||||||
// OpenCode requires 'run' batch mode with JSON output for wizard conversations
|
// OpenCode requires 'run' batch mode with JSON output for wizard conversations
|
||||||
const args = [];
|
const args = [];
|
||||||
|
|
||||||
// Add batch mode prefix: 'run'
|
// Add base args (if any) - batchModePrefix will be added by buildAgentArgs
|
||||||
if (agent.batchModePrefix) {
|
|
||||||
args.push(...agent.batchModePrefix);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add base args (if any)
|
|
||||||
args.push(...(agent.args || []));
|
args.push(...(agent.args || []));
|
||||||
|
|
||||||
// Add JSON output: '--format json'
|
// Add JSON output: '--format json'
|
||||||
|
|||||||
@@ -585,7 +585,14 @@ export function useInlineWizard(): UseInlineWizardReturn {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Step 4: Initialize conversation session (only for 'new' or 'iterate' modes)
|
// Step 4: Initialize conversation session (only for 'new' or 'iterate' modes)
|
||||||
if ((mode === 'new' || mode === 'iterate') && agentType && effectiveAutoRunFolderPath) {
|
// Only allow wizard for agents that support structured output
|
||||||
|
const supportedWizardAgents: ToolType[] = ['claude', 'claude-code', 'codex'];
|
||||||
|
if (
|
||||||
|
(mode === 'new' || mode === 'iterate') &&
|
||||||
|
agentType &&
|
||||||
|
supportedWizardAgents.includes(agentType) &&
|
||||||
|
effectiveAutoRunFolderPath
|
||||||
|
) {
|
||||||
const session = startInlineWizardConversation({
|
const session = startInlineWizardConversation({
|
||||||
mode,
|
mode,
|
||||||
agentType,
|
agentType,
|
||||||
@@ -608,6 +615,19 @@ export function useInlineWizard(): UseInlineWizardReturn {
|
|||||||
existingDocsCount: docsWithContent.length,
|
existingDocsCount: docsWithContent.length,
|
||||||
autoRunFolderPath: effectiveAutoRunFolderPath,
|
autoRunFolderPath: effectiveAutoRunFolderPath,
|
||||||
});
|
});
|
||||||
|
} else if (
|
||||||
|
(mode === 'new' || mode === 'iterate') &&
|
||||||
|
agentType &&
|
||||||
|
!supportedWizardAgents.includes(agentType)
|
||||||
|
) {
|
||||||
|
// Agent not supported for wizard
|
||||||
|
logger.warn(`Wizard not supported for agent type: ${agentType}`, '[InlineWizard]');
|
||||||
|
setTabState(effectiveTabId, (prev) => ({
|
||||||
|
...prev,
|
||||||
|
isInitializing: false,
|
||||||
|
error: `The inline wizard is not supported for ${agentType}. Please use Claude, Claude Code, or Codex.`,
|
||||||
|
}));
|
||||||
|
return; // Don't update state with parsed results
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update state with parsed results
|
// Update state with parsed results
|
||||||
@@ -678,6 +698,9 @@ export function useInlineWizard(): UseInlineWizardReturn {
|
|||||||
*/
|
*/
|
||||||
const sendMessage = useCallback(
|
const sendMessage = useCallback(
|
||||||
async (content: string, callbacks?: ConversationCallbacks): Promise<void> => {
|
async (content: string, callbacks?: ConversationCallbacks): Promise<void> => {
|
||||||
|
// Only allow wizard for agents that support structured output
|
||||||
|
const supportedWizardAgents: ToolType[] = ['claude', 'claude-code', 'codex'];
|
||||||
|
|
||||||
// Get the tab ID from the current state, ensure currentTabId is set for visibility
|
// Get the tab ID from the current state, ensure currentTabId is set for visibility
|
||||||
const tabId = currentTabId || 'default';
|
const tabId = currentTabId || 'default';
|
||||||
if (tabId !== currentTabId) {
|
if (tabId !== currentTabId) {
|
||||||
@@ -719,7 +742,12 @@ export function useInlineWizard(): UseInlineWizardReturn {
|
|||||||
currentState?.autoRunFolderPath ||
|
currentState?.autoRunFolderPath ||
|
||||||
(currentState?.projectPath ? getAutoRunFolderPath(currentState.projectPath) : null);
|
(currentState?.projectPath ? getAutoRunFolderPath(currentState.projectPath) : null);
|
||||||
|
|
||||||
if (currentState?.mode === 'ask' && currentState.agentType && effectiveAutoRunFolderPath) {
|
if (
|
||||||
|
currentState?.mode === 'ask' &&
|
||||||
|
currentState.agentType &&
|
||||||
|
supportedWizardAgents.includes(currentState.agentType) &&
|
||||||
|
effectiveAutoRunFolderPath
|
||||||
|
) {
|
||||||
console.log('[useInlineWizard] Auto-creating session for direct message in ask mode');
|
console.log('[useInlineWizard] Auto-creating session for direct message in ask mode');
|
||||||
session = startInlineWizardConversation({
|
session = startInlineWizardConversation({
|
||||||
mode: 'new',
|
mode: 'new',
|
||||||
|
|||||||
@@ -499,6 +499,11 @@ function buildArgsForAgent(agent: any): string[] {
|
|||||||
// Add base args (if any)
|
// Add base args (if any)
|
||||||
args.push(...(agent.args || []));
|
args.push(...(agent.args || []));
|
||||||
|
|
||||||
|
// Add read-only mode: '--agent plan'
|
||||||
|
if (agent.readOnlyArgs) {
|
||||||
|
args.push(...agent.readOnlyArgs);
|
||||||
|
}
|
||||||
|
|
||||||
// Add JSON output: '--format json'
|
// Add JSON output: '--format json'
|
||||||
if (agent.jsonOutputArgs) {
|
if (agent.jsonOutputArgs) {
|
||||||
args.push(...agent.jsonOutputArgs);
|
args.push(...agent.jsonOutputArgs);
|
||||||
@@ -707,10 +712,17 @@ export async function sendWizardMessage(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Use the agent's resolved path if available, falling back to command name
|
||||||
|
// This is critical for packaged Electron apps where PATH may not include agent locations
|
||||||
|
const commandToUse = agent.path || agent.command;
|
||||||
|
|
||||||
// Spawn the agent process
|
// Spawn the agent process
|
||||||
logger.info(`Spawning wizard agent process`, '[InlineWizardConversation]', {
|
logger.info(`Spawning wizard agent process`, '[InlineWizardConversation]', {
|
||||||
sessionId: session.sessionId,
|
sessionId: session.sessionId,
|
||||||
agentType: session.agentType,
|
agentType: session.agentType,
|
||||||
|
command: commandToUse,
|
||||||
|
agentPath: agent.path,
|
||||||
|
agentCommand: agent.command,
|
||||||
cwd: session.directoryPath,
|
cwd: session.directoryPath,
|
||||||
historyLength: conversationHistory.length,
|
historyLength: conversationHistory.length,
|
||||||
});
|
});
|
||||||
@@ -720,7 +732,7 @@ export async function sendWizardMessage(
|
|||||||
sessionId: session.sessionId,
|
sessionId: session.sessionId,
|
||||||
toolType: session.agentType,
|
toolType: session.agentType,
|
||||||
cwd: session.directoryPath,
|
cwd: session.directoryPath,
|
||||||
command: agent.command,
|
command: commandToUse,
|
||||||
args: argsForSpawn,
|
args: argsForSpawn,
|
||||||
prompt: fullPrompt,
|
prompt: fullPrompt,
|
||||||
// Pass SSH config for remote execution
|
// Pass SSH config for remote execution
|
||||||
|
|||||||
@@ -13,6 +13,6 @@
|
|||||||
"noUnusedParameters": true,
|
"noUnusedParameters": true,
|
||||||
"noFallthroughCasesInSwitch": true
|
"noFallthroughCasesInSwitch": true
|
||||||
},
|
},
|
||||||
"include": ["src/main/**/*"],
|
"include": ["src/main/**/*", "src/shared/**/*", "src/types/**/*"],
|
||||||
"exclude": ["node_modules", "dist", "release"]
|
"exclude": ["node_modules", "dist", "release"]
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user