mirror of
https://github.com/n8n-io/n8n
synced 2026-04-19 13:05:54 +02:00
feat: Instance AI and local gateway modules (no-changelog) (#27206)
Signed-off-by: Oleg Ivaniv <me@olegivaniv.com> Co-authored-by: Albert Alises <albert.alises@gmail.com> Co-authored-by: Jaakko Husso <jaakko@n8n.io> Co-authored-by: Dimitri Lavrenük <20122620+dlavrenuek@users.noreply.github.com> Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com> Co-authored-by: Tuukka Kantola <Tuukkaa@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Mutasem Aldmour <4711238+mutdmour@users.noreply.github.com> Co-authored-by: Raúl Gómez Morales <raul00gm@gmail.com> Co-authored-by: Elias Meire <elias@meire.dev> Co-authored-by: Dimitri Lavrenük <dimitri.lavrenuek@n8n.io> Co-authored-by: Tomi Turtiainen <10324676+tomi@users.noreply.github.com> Co-authored-by: Mutasem Aldmour <mutasem@n8n.io>
This commit is contained in:
105
packages/@n8n/fs-proxy/src/tools/shell/shell-execute.ts
Normal file
105
packages/@n8n/fs-proxy/src/tools/shell/shell-execute.ts
Normal file
@@ -0,0 +1,105 @@
|
||||
import { SandboxManager, type SandboxRuntimeConfig } from '@anthropic-ai/sandbox-runtime';
|
||||
import { rgPath } from '@vscode/ripgrep';
|
||||
import { spawn } from 'child_process';
|
||||
import { z } from 'zod';
|
||||
|
||||
import type { CallToolResult, ToolDefinition } from '../types';
|
||||
import { formatCallToolResult, formatErrorResult } from '../utils';
|
||||
import { buildShellResource } from './build-shell-resource';
|
||||
|
||||
async function initializeSandbox({ dir }: { dir: string }) {
|
||||
const config: SandboxRuntimeConfig = {
|
||||
ripgrep: {
|
||||
command: rgPath,
|
||||
},
|
||||
network: {
|
||||
allowedDomains: [],
|
||||
deniedDomains: [],
|
||||
},
|
||||
filesystem: {
|
||||
denyRead: ['~/.ssh'],
|
||||
allowRead: [],
|
||||
allowWrite: [dir],
|
||||
denyWrite: [],
|
||||
},
|
||||
};
|
||||
await SandboxManager.initialize(config);
|
||||
}
|
||||
|
||||
const inputSchema = z.object({
|
||||
command: z.string().describe('Shell command to execute'),
|
||||
timeout: z.number().int().optional().describe('Timeout in milliseconds (default: 30000)'),
|
||||
cwd: z.string().optional().describe('Working directory for the command'),
|
||||
});
|
||||
|
||||
export const shellExecuteTool: ToolDefinition<typeof inputSchema> = {
|
||||
name: 'shell_execute',
|
||||
description: 'Execute a shell command and return stdout, stderr, and exit code',
|
||||
inputSchema,
|
||||
annotations: { destructiveHint: true },
|
||||
getAffectedResources({ command }) {
|
||||
return [
|
||||
{
|
||||
toolGroup: 'shell' as const,
|
||||
resource: buildShellResource(command),
|
||||
description: `Execute shell command: ${command}`,
|
||||
},
|
||||
];
|
||||
},
|
||||
async execute({ command, timeout = 30_000, cwd }, { dir }) {
|
||||
return await runCommand(command, { timeout, dir, cwd: cwd ?? dir });
|
||||
},
|
||||
};
|
||||
|
||||
async function spawnCommand(command: string, { dir, cwd }: { dir: string; cwd?: string }) {
|
||||
const isWindows = process.platform === 'win32';
|
||||
const isMac = process.platform === 'darwin';
|
||||
|
||||
if (isWindows) {
|
||||
return spawn('cmd.exe', ['/C', command], { cwd });
|
||||
}
|
||||
|
||||
if (isMac) {
|
||||
await initializeSandbox({ dir });
|
||||
const sandboxedCommand = await SandboxManager.wrapWithSandbox(command);
|
||||
return spawn(sandboxedCommand, { shell: true, cwd });
|
||||
}
|
||||
|
||||
return spawn('sh', ['-c', command], { cwd });
|
||||
}
|
||||
|
||||
async function runCommand(
|
||||
command: string,
|
||||
{ timeout, cwd, dir }: { timeout: number; dir: string; cwd?: string },
|
||||
): Promise<CallToolResult> {
|
||||
return await new Promise<CallToolResult>((resolve, reject) => {
|
||||
spawnCommand(command, { dir, cwd })
|
||||
.then((child) => {
|
||||
let stdout = '';
|
||||
let stderr = '';
|
||||
|
||||
child.stdout?.on('data', (chunk: Buffer) => {
|
||||
stdout += String(chunk);
|
||||
});
|
||||
child.stderr?.on('data', (chunk: Buffer) => {
|
||||
stderr += String(chunk);
|
||||
});
|
||||
|
||||
const timer = setTimeout(() => {
|
||||
child.kill();
|
||||
resolve(formatCallToolResult({ stdout, stderr, exitCode: null, timedOut: true }));
|
||||
}, timeout);
|
||||
|
||||
child.on('close', (code) => {
|
||||
clearTimeout(timer);
|
||||
resolve(formatCallToolResult({ stdout, stderr, exitCode: code }));
|
||||
});
|
||||
|
||||
child.on('error', (error) => {
|
||||
clearTimeout(timer);
|
||||
resolve(formatErrorResult(`Failed to start process: ${error.message}`));
|
||||
});
|
||||
})
|
||||
.catch(reject);
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user