mirror of
https://github.com/jlengrand/Maestro.git
synced 2026-03-10 08:31:19 +00:00
all test cases passing
This commit is contained in:
@@ -139,7 +139,12 @@ describe('BatchRunnerModal', () => {
|
||||
};
|
||||
|
||||
(window.maestro.git as Record<string, unknown>).branches = vi.fn().mockResolvedValue({ branches: ['main', 'develop'] });
|
||||
(window.maestro.git as Record<string, unknown>).checkGhCli = vi.fn().mockResolvedValue({ installed: true, authenticated: true });
|
||||
(window.maestro.git as Record<string, unknown>).checkGhCli = vi.fn(() => ({
|
||||
then: (cb: (value: { installed: boolean; authenticated: boolean }) => void) => {
|
||||
cb({ installed: true, authenticated: true });
|
||||
return Promise.resolve({ installed: true, authenticated: true });
|
||||
},
|
||||
}));
|
||||
(window.maestro.git as Record<string, unknown>).worktreeInfo = vi.fn().mockResolvedValue({
|
||||
success: true,
|
||||
exists: false,
|
||||
|
||||
@@ -300,9 +300,11 @@ describe('fileExplorer utils', () => {
|
||||
const error = new Error('Permission denied');
|
||||
vi.mocked(window.maestro.fs.readDir).mockRejectedValue(error);
|
||||
|
||||
const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
||||
await expect(loadFileTree('/restricted')).rejects.toThrow(
|
||||
'Permission denied'
|
||||
);
|
||||
consoleSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('respects default maxDepth of 10', async () => {
|
||||
|
||||
@@ -459,6 +459,7 @@ Binary files /dev/null and b/image.png differ`;
|
||||
|
||||
describe('diffText preservation', () => {
|
||||
it('preserves the original diff text in the result', () => {
|
||||
const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
||||
const diffText = `diff --git a/file.ts b/file.ts
|
||||
--- a/file.ts
|
||||
+++ b/file.ts
|
||||
@@ -473,6 +474,7 @@ Binary files /dev/null and b/image.png differ`;
|
||||
const result = parseGitDiff(diffText);
|
||||
|
||||
expect(result[0].diffText).toBe(diffText);
|
||||
consoleSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('preserves individual diff sections for multiple files', () => {
|
||||
|
||||
@@ -36,6 +36,16 @@ vi.mock('../../../web/hooks/useDeviceColorScheme', () => ({
|
||||
|
||||
const mockedCssCustomProperties = vi.mocked(cssCustomProperties);
|
||||
const mockedUseDeviceColorScheme = vi.mocked(useDeviceColorSchemeModule.useDeviceColorScheme);
|
||||
let consoleErrorSpy: ReturnType<typeof vi.spyOn> | undefined;
|
||||
|
||||
beforeEach(() => {
|
||||
consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
consoleErrorSpy?.mockRestore();
|
||||
consoleErrorSpy = undefined;
|
||||
});
|
||||
|
||||
// Test themes
|
||||
const customDarkTheme: Theme = {
|
||||
|
||||
@@ -43,6 +43,7 @@ export interface AgentConfig {
|
||||
yoloModeArgs?: string[]; // Args for YOLO/full-access mode (e.g., ['--dangerously-bypass-approvals-and-sandbox'])
|
||||
workingDirArgs?: (dir: string) => string[]; // Function to build working directory args (e.g., ['-C', dir])
|
||||
imageArgs?: (imagePath: string) => string[]; // Function to build image attachment args (e.g., ['-i', imagePath] for Codex)
|
||||
noPromptSeparator?: boolean; // If true, don't add '--' before the prompt in batch mode (OpenCode doesn't support it)
|
||||
}
|
||||
|
||||
const AGENT_DEFINITIONS: Omit<AgentConfig, 'available' | 'path' | 'capabilities'>[] = [
|
||||
@@ -126,6 +127,7 @@ const AGENT_DEFINITIONS: Omit<AgentConfig, 'available' | 'path' | 'capabilities'
|
||||
modelArgs: (modelId: string) => ['--model', modelId], // Model selection (e.g., 'ollama/qwen3:8b')
|
||||
yoloModeArgs: ['run'], // 'run' subcommand auto-approves all permissions (YOLO mode is implicit)
|
||||
imageArgs: (imagePath: string) => ['-f', imagePath], // Image/file attachment: opencode run -f /path/to/image.png
|
||||
noPromptSeparator: true, // OpenCode doesn't support '--' before prompt (breaks yargs parsing)
|
||||
// Agent-specific configuration options shown in UI
|
||||
configOptions: [
|
||||
{
|
||||
|
||||
@@ -200,9 +200,11 @@ export async function addParticipant(
|
||||
prompt,
|
||||
contextWindow: getContextWindowValue(agentConfig, agentConfigValues || {}),
|
||||
customEnvVars: configResolution.effectiveCustomEnvVars ?? effectiveEnvVars,
|
||||
noPromptSeparator: agentConfig?.noPromptSeparator,
|
||||
});
|
||||
|
||||
console.log(`[GroupChat:Debug] Spawn result: ${JSON.stringify(result)}`);
|
||||
console.log(`[GroupChat:Debug] noPromptSeparator: ${agentConfig?.noPromptSeparator ?? false}`);
|
||||
|
||||
if (!result.success) {
|
||||
console.log(`[GroupChat:Debug] ERROR: Spawn failed!`);
|
||||
|
||||
@@ -30,6 +30,7 @@ export interface IProcessManager {
|
||||
prompt?: string;
|
||||
customEnvVars?: Record<string, string>;
|
||||
contextWindow?: number;
|
||||
noPromptSeparator?: boolean;
|
||||
}): { pid: number; success: boolean };
|
||||
|
||||
write(sessionId: string, data: string): boolean;
|
||||
|
||||
@@ -384,6 +384,7 @@ ${message}`;
|
||||
// Get the base args from the agent configuration
|
||||
const args = [...agent.args];
|
||||
const agentConfigValues = getAgentConfigCallback?.(chat.moderatorAgentId) || {};
|
||||
console.log(`[GroupChat:Debug] agentConfigValues for ${chat.moderatorAgentId}: ${JSON.stringify(agentConfigValues)}`);
|
||||
const baseArgs = buildAgentArgs(agent, {
|
||||
baseArgs: args,
|
||||
prompt: fullPrompt,
|
||||
@@ -422,10 +423,12 @@ ${message}`;
|
||||
prompt: fullPrompt,
|
||||
contextWindow: getContextWindowValue(agent, agentConfigValues),
|
||||
customEnvVars: configResolution.effectiveCustomEnvVars ?? getCustomEnvVarsCallback?.(chat.moderatorAgentId),
|
||||
noPromptSeparator: agent.noPromptSeparator,
|
||||
});
|
||||
|
||||
console.log(`[GroupChat:Debug] Spawn result: ${JSON.stringify(spawnResult)}`);
|
||||
console.log(`[GroupChat:Debug] Moderator process spawned successfully`);
|
||||
console.log(`[GroupChat:Debug] noPromptSeparator: ${agent.noPromptSeparator ?? false}`);
|
||||
console.log(`[GroupChat:Debug] =================================================`);
|
||||
} catch (error) {
|
||||
console.error(`[GroupChat:Debug] SPAWN ERROR:`, error);
|
||||
@@ -679,9 +682,11 @@ Please respond to this request.${readOnly ? ' Remember: READ-ONLY mode is active
|
||||
prompt: participantPrompt,
|
||||
contextWindow: getContextWindowValue(agent, agentConfigValues),
|
||||
customEnvVars: configResolution.effectiveCustomEnvVars ?? getCustomEnvVarsCallback?.(participant.agentId),
|
||||
noPromptSeparator: agent.noPromptSeparator,
|
||||
});
|
||||
|
||||
console.log(`[GroupChat:Debug] Spawn result for ${participantName}: ${JSON.stringify(spawnResult)}`);
|
||||
console.log(`[GroupChat:Debug] noPromptSeparator: ${agent.noPromptSeparator ?? false}`);
|
||||
|
||||
// Track this participant as pending response
|
||||
participantsToRespond.add(participantName);
|
||||
@@ -933,10 +938,12 @@ Review the agent responses above. Either:
|
||||
prompt: synthesisPrompt,
|
||||
contextWindow: getContextWindowValue(agent, agentConfigValues),
|
||||
customEnvVars: configResolution.effectiveCustomEnvVars ?? getCustomEnvVarsCallback?.(chat.moderatorAgentId),
|
||||
noPromptSeparator: agent.noPromptSeparator,
|
||||
});
|
||||
|
||||
console.log(`[GroupChat:Debug] Synthesis spawn result: ${JSON.stringify(spawnResult)}`);
|
||||
console.log(`[GroupChat:Debug] Synthesis moderator process spawned successfully`);
|
||||
console.log(`[GroupChat:Debug] noPromptSeparator: ${agent.noPromptSeparator ?? false}`);
|
||||
console.log(`[GroupChat:Debug] ================================================`);
|
||||
} catch (error) {
|
||||
console.error(`[GroupChat:Debug] SYNTHESIS SPAWN ERROR:`, error);
|
||||
|
||||
@@ -203,6 +203,7 @@ export function registerProcessHandlers(deps: ProcessHandlerDependencies): void
|
||||
contextWindow, // Pass configured context window to process manager
|
||||
customEnvVars: effectiveCustomEnvVars, // Pass custom env vars (session-level or agent-level)
|
||||
imageArgs: agent?.imageArgs, // Function to build image CLI args (for Codex, OpenCode)
|
||||
noPromptSeparator: agent?.noPromptSeparator, // OpenCode doesn't support '--' before prompt
|
||||
});
|
||||
|
||||
logger.info(`Process spawned successfully`, LOG_CONTEXT, {
|
||||
|
||||
@@ -57,6 +57,7 @@ interface ProcessConfig {
|
||||
imageArgs?: (imagePath: string) => string[]; // Function to build image CLI args (e.g., ['-i', path] for Codex)
|
||||
contextWindow?: number; // Configured context window size (0 or undefined = not configured, hide UI)
|
||||
customEnvVars?: Record<string, string>; // Custom environment variables from user configuration
|
||||
noPromptSeparator?: boolean; // If true, don't add '--' before the prompt (e.g., OpenCode doesn't support it)
|
||||
}
|
||||
|
||||
interface ManagedProcess {
|
||||
@@ -194,10 +195,10 @@ export class ProcessManager extends EventEmitter {
|
||||
* Spawn a new process for a session
|
||||
*/
|
||||
spawn(config: ProcessConfig): { pid: number; success: boolean } {
|
||||
const { sessionId, toolType, cwd, command, args, requiresPty, prompt, shell, shellArgs, shellEnvVars, images, imageArgs, contextWindow, customEnvVars } = config;
|
||||
const { sessionId, toolType, cwd, command, args, requiresPty, prompt, shell, shellArgs, shellEnvVars, images, imageArgs, contextWindow, customEnvVars, noPromptSeparator } = config;
|
||||
|
||||
// For batch mode with images, use stream-json mode and send message via stdin
|
||||
// For batch mode without images, append prompt to args with -- separator
|
||||
// For batch mode without images, append prompt to args with -- separator (unless noPromptSeparator is true)
|
||||
const hasImages = images && images.length > 0;
|
||||
const capabilities = getAgentCapabilities(toolType);
|
||||
let finalArgs: string[];
|
||||
@@ -219,8 +220,12 @@ export class ProcessManager extends EventEmitter {
|
||||
finalArgs = [...finalArgs, ...imageArgs(tempPath)];
|
||||
}
|
||||
}
|
||||
// Add the prompt at the end
|
||||
finalArgs = [...finalArgs, '--', prompt];
|
||||
// Add the prompt at the end (with or without -- separator)
|
||||
if (noPromptSeparator) {
|
||||
finalArgs = [...finalArgs, prompt];
|
||||
} else {
|
||||
finalArgs = [...finalArgs, '--', prompt];
|
||||
}
|
||||
logger.debug('[ProcessManager] Using file-based image args', 'ProcessManager', {
|
||||
sessionId,
|
||||
imageCount: images.length,
|
||||
@@ -229,7 +234,12 @@ export class ProcessManager extends EventEmitter {
|
||||
} else if (prompt) {
|
||||
// Regular batch mode - prompt as CLI arg
|
||||
// The -- ensures prompt is treated as positional arg, not a flag (even if it starts with --)
|
||||
finalArgs = [...args, '--', prompt];
|
||||
// Some agents (e.g., OpenCode) don't support the -- separator
|
||||
if (noPromptSeparator) {
|
||||
finalArgs = [...args, prompt];
|
||||
} else {
|
||||
finalArgs = [...args, '--', prompt];
|
||||
}
|
||||
} else {
|
||||
finalArgs = args;
|
||||
}
|
||||
@@ -494,6 +504,13 @@ export class ProcessManager extends EventEmitter {
|
||||
childProcess.stdout.on('data', (data: Buffer | string) => {
|
||||
const output = data.toString();
|
||||
|
||||
// Debug: Log all stdout data for group chat sessions
|
||||
if (sessionId.includes('group-chat-')) {
|
||||
console.log(`[GroupChat:Debug:ProcessManager] STDOUT received for session ${sessionId}`);
|
||||
console.log(`[GroupChat:Debug:ProcessManager] Raw output length: ${output.length}`);
|
||||
console.log(`[GroupChat:Debug:ProcessManager] Raw output preview: "${output.substring(0, 500)}${output.length > 500 ? '...' : ''}"`);
|
||||
}
|
||||
|
||||
if (isStreamJsonMode) {
|
||||
// In stream-json mode, each line is a JSONL message
|
||||
// Accumulate and process complete lines
|
||||
@@ -668,6 +685,13 @@ export class ProcessManager extends EventEmitter {
|
||||
const stderrData = data.toString();
|
||||
logger.debug('[ProcessManager] stderr event fired', 'ProcessManager', { sessionId, dataPreview: stderrData.substring(0, 100) });
|
||||
|
||||
// Debug: Log all stderr data for group chat sessions
|
||||
if (sessionId.includes('group-chat-')) {
|
||||
console.log(`[GroupChat:Debug:ProcessManager] STDERR received for session ${sessionId}`);
|
||||
console.log(`[GroupChat:Debug:ProcessManager] Stderr length: ${stderrData.length}`);
|
||||
console.log(`[GroupChat:Debug:ProcessManager] Stderr preview: "${stderrData.substring(0, 500)}${stderrData.length > 500 ? '...' : ''}"`);
|
||||
}
|
||||
|
||||
// Accumulate stderr for error detection at exit (with size limit to prevent memory exhaustion)
|
||||
managedProcess.stderrBuffer = appendToBuffer(managedProcess.stderrBuffer || '', stderrData);
|
||||
|
||||
@@ -705,6 +729,19 @@ export class ProcessManager extends EventEmitter {
|
||||
jsonBufferLength: managedProcess.jsonBuffer?.length || 0,
|
||||
jsonBufferPreview: managedProcess.jsonBuffer?.substring(0, 200)
|
||||
});
|
||||
|
||||
// Debug: Log exit details for group chat sessions
|
||||
if (sessionId.includes('group-chat-')) {
|
||||
console.log(`[GroupChat:Debug:ProcessManager] EXIT for session ${sessionId}`);
|
||||
console.log(`[GroupChat:Debug:ProcessManager] Exit code: ${code}`);
|
||||
console.log(`[GroupChat:Debug:ProcessManager] isStreamJsonMode: ${isStreamJsonMode}`);
|
||||
console.log(`[GroupChat:Debug:ProcessManager] isBatchMode: ${isBatchMode}`);
|
||||
console.log(`[GroupChat:Debug:ProcessManager] resultEmitted: ${managedProcess.resultEmitted}`);
|
||||
console.log(`[GroupChat:Debug:ProcessManager] streamedText length: ${managedProcess.streamedText?.length || 0}`);
|
||||
console.log(`[GroupChat:Debug:ProcessManager] jsonBuffer length: ${managedProcess.jsonBuffer?.length || 0}`);
|
||||
console.log(`[GroupChat:Debug:ProcessManager] stderrBuffer length: ${managedProcess.stderrBuffer?.length || 0}`);
|
||||
console.log(`[GroupChat:Debug:ProcessManager] stderrBuffer preview: "${(managedProcess.stderrBuffer || '').substring(0, 500)}"`);
|
||||
}
|
||||
if (isBatchMode && !isStreamJsonMode && managedProcess.jsonBuffer) {
|
||||
// Parse JSON response from regular batch mode (not stream-json)
|
||||
try {
|
||||
|
||||
@@ -51,3 +51,7 @@ This restriction ensures:
|
||||
If a user requests an operation that would write outside your assigned directory, explain the restriction and ask them to either:
|
||||
1. Change to the appropriate session/agent for that directory
|
||||
2. Explicitly confirm they want to override this safety measure
|
||||
|
||||
### Recommended Operations
|
||||
|
||||
Format you responses in Markdown. When referencing file paths, use backticks (ex: `path/to/file`).
|
||||
Reference in New Issue
Block a user