133 lines
4.7 KiB
JavaScript
133 lines
4.7 KiB
JavaScript
|
|
'use strict';
|
||
|
|
|
||
|
|
const { spawn } = require('child_process');
|
||
|
|
const path = require('path');
|
||
|
|
|
||
|
|
module.exports = function (RED) {
|
||
|
|
function AgentExecNode(config) {
|
||
|
|
RED.nodes.createNode(this, config);
|
||
|
|
const node = this;
|
||
|
|
this.configNode = RED.nodes.getNode(config.agentConfig);
|
||
|
|
this.command = config.command || 'run';
|
||
|
|
this.args = config.args || '';
|
||
|
|
this.workingDir = config.workingDir || '';
|
||
|
|
this.timeout = parseInt(config.timeout) || 120000;
|
||
|
|
this.outputFormat = config.outputFormat || 'text';
|
||
|
|
|
||
|
|
if (!this.configNode) {
|
||
|
|
node.error('No agent config node selected');
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
node.on('input', async function (msg) {
|
||
|
|
const cfg = node.configNode;
|
||
|
|
const startTime = Date.now();
|
||
|
|
|
||
|
|
const cliCmd = cfg.cliPath || 'agent';
|
||
|
|
const workspaceDir = msg.workingDir || node.workingDir || cfg.workspaceDir || '/data/agent-workspace';
|
||
|
|
const command = msg.command || node.command;
|
||
|
|
const args = msg.args ? (Array.isArray(msg.args) ? msg.args : msg.args.split(' ').filter(Boolean)) : (node.args ? node.args.split(' ').filter(Boolean) : []);
|
||
|
|
const timeout = msg.timeout || node.timeout;
|
||
|
|
|
||
|
|
const fullArgs = [command, ...args];
|
||
|
|
if (command === 'run' && msg.prompt) {
|
||
|
|
fullArgs.push('--prompt', msg.prompt);
|
||
|
|
}
|
||
|
|
if (cfg.defaultArgs) {
|
||
|
|
fullArgs.unshift(...cfg.defaultArgs.split(' ').filter(Boolean));
|
||
|
|
}
|
||
|
|
|
||
|
|
node.status({ fill: 'blue', shape: 'dot', text: `Running: ${command}` });
|
||
|
|
|
||
|
|
try {
|
||
|
|
const result = await runAgent(cliCmd, fullArgs, {
|
||
|
|
cwd: workspaceDir,
|
||
|
|
timeout,
|
||
|
|
env: {
|
||
|
|
...process.env,
|
||
|
|
AGENT_WORKSPACE: workspaceDir,
|
||
|
|
AGENT_API_ENDPOINT: cfg.apiEndpoint,
|
||
|
|
AGENT_API_KEY: cfg.apiKey || ''
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
const duration = Date.now() - startTime;
|
||
|
|
node.status({ fill: 'green', shape: 'dot', text: `Done (${duration}ms)` });
|
||
|
|
|
||
|
|
msg.payload = node.outputFormat === 'json' ? parseOutput(result.stdout) : result.stdout;
|
||
|
|
msg.stderr = result.stderr;
|
||
|
|
msg.exitCode = result.exitCode;
|
||
|
|
msg.duration = duration;
|
||
|
|
msg.command = `${cliCmd} ${fullArgs.join(' ')}`;
|
||
|
|
msg.workingDir = workspaceDir;
|
||
|
|
|
||
|
|
node.send(msg);
|
||
|
|
} catch (err) {
|
||
|
|
const duration = Date.now() - startTime;
|
||
|
|
node.status({ fill: 'red', shape: 'ring', text: `Error (${duration}ms)` });
|
||
|
|
msg.error = err.message;
|
||
|
|
msg.stderr = err.stderr || '';
|
||
|
|
msg.exitCode = err.exitCode || -1;
|
||
|
|
msg.duration = duration;
|
||
|
|
node.error(`agent ${command} failed: ${err.message}`, msg);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
function runAgent(cmd, args, options) {
|
||
|
|
return new Promise((resolve, reject) => {
|
||
|
|
let stdout = '';
|
||
|
|
let stderr = '';
|
||
|
|
let exitCode = 0;
|
||
|
|
|
||
|
|
const child = spawn(cmd, args, {
|
||
|
|
cwd: options.cwd,
|
||
|
|
env: options.env,
|
||
|
|
timeout: options.timeout,
|
||
|
|
windowsHide: true
|
||
|
|
});
|
||
|
|
|
||
|
|
child.stdout.on('data', (data) => { stdout += data.toString(); });
|
||
|
|
child.stderr.on('data', (data) => { stderr += data.toString(); });
|
||
|
|
|
||
|
|
child.on('close', (code) => {
|
||
|
|
exitCode = code || 0;
|
||
|
|
if (exitCode === 0) {
|
||
|
|
resolve({ stdout, stderr, exitCode });
|
||
|
|
} else {
|
||
|
|
const err = new Error(`Process exited with code ${exitCode}`);
|
||
|
|
err.exitCode = exitCode;
|
||
|
|
err.stderr = stderr;
|
||
|
|
reject(err);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
child.on('error', (err) => {
|
||
|
|
err.exitCode = -1;
|
||
|
|
reject(err);
|
||
|
|
});
|
||
|
|
|
||
|
|
child.on('timeout', () => {
|
||
|
|
child.kill('SIGTERM');
|
||
|
|
const err = new Error(`Process timed out after ${options.timeout}ms`);
|
||
|
|
err.exitCode = -2;
|
||
|
|
err.stderr = stderr;
|
||
|
|
reject(err);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
function parseOutput(text) {
|
||
|
|
try {
|
||
|
|
const trimmed = text.trim();
|
||
|
|
if (trimmed.startsWith('{') || trimmed.startsWith('[')) {
|
||
|
|
return JSON.parse(trimmed);
|
||
|
|
}
|
||
|
|
return text;
|
||
|
|
} catch {
|
||
|
|
return text;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
RED.nodes.registerType('agent-exec', AgentExecNode);
|
||
|
|
};
|