@measey/mycoder-agent
Version:
Agent module for mycoder - an AI-powered software development assistant
197 lines • 8.67 kB
JavaScript
import { z } from 'zod';
import { zodToJsonSchema } from 'zod-to-json-schema';
import { sleep } from '../../utils/sleep.js';
import { ShellStatus } from './ShellTracker.js';
// Define NodeJS signals as an enum
export var NodeSignals;
(function (NodeSignals) {
NodeSignals["SIGABRT"] = "SIGABRT";
NodeSignals["SIGALRM"] = "SIGALRM";
NodeSignals["SIGBUS"] = "SIGBUS";
NodeSignals["SIGCHLD"] = "SIGCHLD";
NodeSignals["SIGCONT"] = "SIGCONT";
NodeSignals["SIGFPE"] = "SIGFPE";
NodeSignals["SIGHUP"] = "SIGHUP";
NodeSignals["SIGILL"] = "SIGILL";
NodeSignals["SIGINT"] = "SIGINT";
NodeSignals["SIGIO"] = "SIGIO";
NodeSignals["SIGIOT"] = "SIGIOT";
NodeSignals["SIGKILL"] = "SIGKILL";
NodeSignals["SIGPIPE"] = "SIGPIPE";
NodeSignals["SIGPOLL"] = "SIGPOLL";
NodeSignals["SIGPROF"] = "SIGPROF";
NodeSignals["SIGPWR"] = "SIGPWR";
NodeSignals["SIGQUIT"] = "SIGQUIT";
NodeSignals["SIGSEGV"] = "SIGSEGV";
NodeSignals["SIGSTKFLT"] = "SIGSTKFLT";
NodeSignals["SIGSTOP"] = "SIGSTOP";
NodeSignals["SIGSYS"] = "SIGSYS";
NodeSignals["SIGTERM"] = "SIGTERM";
NodeSignals["SIGTRAP"] = "SIGTRAP";
NodeSignals["SIGTSTP"] = "SIGTSTP";
NodeSignals["SIGTTIN"] = "SIGTTIN";
NodeSignals["SIGTTOU"] = "SIGTTOU";
NodeSignals["SIGUNUSED"] = "SIGUNUSED";
NodeSignals["SIGURG"] = "SIGURG";
NodeSignals["SIGUSR1"] = "SIGUSR1";
NodeSignals["SIGUSR2"] = "SIGUSR2";
NodeSignals["SIGVTALRM"] = "SIGVTALRM";
NodeSignals["SIGWINCH"] = "SIGWINCH";
NodeSignals["SIGXCPU"] = "SIGXCPU";
NodeSignals["SIGXFSZ"] = "SIGXFSZ";
})(NodeSignals || (NodeSignals = {}));
const parameterSchema = z.object({
shellId: z.string().describe('The ID returned by shellStart'),
stdin: z.string().optional().describe('Input to send to process'),
signal: z
.nativeEnum(NodeSignals)
.optional()
.describe('Signal to send to the process (e.g., SIGTERM, SIGINT)'),
description: z
.string()
.describe('The reason for this shell interaction (max 80 chars)'),
showStdIn: z
.boolean()
.optional()
.describe('Whether to show the input to the user, or keep the output clean (default: false or value from shellStart)'),
showStdout: z
.boolean()
.optional()
.describe('Whether to show output to the user, or keep the output clean (default: false or value from shellStart)'),
});
const returnSchema = z
.object({
stdout: z.string(),
stderr: z.string(),
completed: z.boolean(),
error: z.string().optional(),
signaled: z.boolean().optional(),
})
.describe('Process interaction results including stdout, stderr, and completion status');
export const shellMessageTool = {
name: 'shellMessage',
description: 'Interacts with a running shell process, sending input and receiving output',
logPrefix: '💻',
parameters: parameterSchema,
parametersJsonSchema: zodToJsonSchema(parameterSchema),
returns: returnSchema,
returnsJsonSchema: zodToJsonSchema(returnSchema),
execute: async ({ shellId, stdin, signal, showStdIn, showStdout }, { logger, shellTracker }) => {
logger.debug(`Interacting with shell process ${shellId}${stdin ? ' with input' : ''}${signal ? ` with signal ${signal}` : ''}`);
try {
const processState = shellTracker.processStates.get(shellId);
if (!processState) {
throw new Error(`No process found with ID ${shellId}`);
}
// Send signal if provided
if (signal) {
try {
processState.process.kill(signal);
// Mark as signaled regardless of process status
processState.state.signaled = true;
}
catch (error) {
// If the process is already terminated, we'll just mark it as signaled anyway
processState.state.signaled = true;
// Update shell tracker if signal failed
shellTracker.updateShellStatus(shellId, ShellStatus.ERROR, {
error: `Failed to send signal ${signal}: ${String(error)}`,
signalAttempted: signal,
});
logger.debug(`Failed to send signal ${signal}: ${String(error)}, but marking as signaled anyway`);
}
// Update shell tracker with signal information
if (signal === 'SIGTERM' ||
signal === 'SIGKILL' ||
signal === 'SIGINT') {
shellTracker.updateShellStatus(shellId, ShellStatus.TERMINATED, {
signal,
terminatedByUser: true,
});
}
else {
shellTracker.updateShellStatus(shellId, ShellStatus.RUNNING, {
signal,
signaled: true,
});
}
}
// Send input if provided
if (stdin) {
if (!processState.process.stdin?.writable) {
throw new Error('Process stdin is not available');
}
// Determine whether to show stdin (prefer explicit parameter, fall back to process state)
const shouldShowStdIn = showStdIn !== undefined ? showStdIn : processState.showStdIn;
if (shouldShowStdIn) {
logger.log(`[${shellId}] stdin: ${stdin}`);
}
// No special handling for 'cat' command - let the actual process handle the echo
processState.process.stdin.write(`${stdin}\n`);
// For interactive processes like 'cat', we need to give them time to process
// and echo back the input before clearing the buffer
await sleep(300);
}
// Wait a brief moment for output to be processed
await sleep(100);
// Get accumulated output
const stdout = processState.stdout.join('');
const stderr = processState.stderr.join('');
// Clear the buffers
processState.stdout = [];
processState.stderr = [];
logger.debug('Interaction completed successfully');
// Determine whether to show stdout (prefer explicit parameter, fall back to process state)
const shouldShowStdout = showStdout !== undefined ? showStdout : processState.showStdout;
if (stdout) {
logger.debug(`stdout: ${stdout.trim()}`);
if (shouldShowStdout) {
logger.log(`[${shellId}] stdout: ${stdout.trim()}`);
}
}
if (stderr) {
logger.debug(`stderr: ${stderr.trim()}`);
if (shouldShowStdout) {
logger.log(`[${shellId}] stderr: ${stderr.trim()}`);
}
}
return {
stdout: stdout.trim(),
stderr: stderr.trim(),
completed: processState.state.completed,
signaled: processState.state.signaled,
};
}
catch (error) {
if (error instanceof Error) {
logger.debug(`Process interaction failed: ${error.message}`);
return {
stdout: '',
stderr: '',
completed: false,
error: error.message,
};
}
const errorMessage = String(error);
logger.error(`Unknown error during process interaction: ${errorMessage}`);
return {
stdout: '',
stderr: '',
completed: false,
error: `Unknown error occurred: ${errorMessage}`,
};
}
},
logParameters: (input, { logger, shellTracker }) => {
const processState = shellTracker.processStates.get(input.shellId);
const showStdIn = input.showStdIn !== undefined
? input.showStdIn
: processState?.showStdIn || false;
const showStdout = input.showStdout !== undefined
? input.showStdout
: processState?.showStdout || false;
logger.log(`Interacting with shell command "${processState ? processState.command : '<unknown shellId>'}", ${input.description} (showStdIn: ${showStdIn}, showStdout: ${showStdout})`);
},
logReturns: () => { },
};
//# sourceMappingURL=shellMessage.js.map