mcp-gm 3.4.95
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.gitattributes +10 -0
- package/LICENSE +21 -0
- package/README.md +66 -0
- package/package.json +42 -0
- package/server.json +20 -0
- package/src/background-tasks.js +98 -0
- package/src/index.js +120 -0
- package/src/recovery-state.js +45 -0
- package/src/rpc-client.js +56 -0
- package/src/runner-supervisor.js +124 -0
- package/src/task-runner.js +132 -0
- package/src/tools/execute-code-isolated.js +20 -0
- package/src/tools/executor-tool-isolated.js +139 -0
- package/src/tools-registry.js +114 -0
- package/src/workers/isolation-worker.js +497 -0
- package/src/workers/worker-pool.js +277 -0
- package/tmp/playwriter-screenshot-1770021779355-69ck.jpg +0 -0
- package/tmp/playwriter-screenshot-1770021786127-r4n7.jpg +0 -0
- package/tmp/playwriter-screenshot-1770021792088-n22l.jpg +0 -0
- package/tmp/playwriter-screenshot-1770021798215-2c62.jpg +0 -0
- package/tmp/playwriter-screenshot-1770021809450-3w8m.jpg +0 -0
- package/tmp/playwriter-screenshot-1770021814006-9ydf.jpg +0 -0
- package/tmp/playwriter-screenshot-1770021819728-3ba8.jpg +0 -0
- package/tmp/playwriter-screenshot-1770021837236-jy89.jpg +0 -0
- package/tmp/playwriter-screenshot-1770021842582-ugtx.jpg +0 -0
- package/tmp/playwriter-screenshot-1770021867754-thcl.jpg +0 -0
- package/tmp/playwriter-screenshot-1770021873864-fpqk.jpg +0 -0
- package/tmp/playwriter-screenshot-1770021879378-3tqw.jpg +0 -0
- package/tmp/playwriter-screenshot-1770021897685-xwo4.jpg +0 -0
- package/tmp/playwriter-screenshot-1770021914159-gp06.jpg +0 -0
- package/tmp/playwriter-screenshot-1770021921332-new5.jpg +0 -0
- package/tmp/playwriter-screenshot-1770021931151-2i9a.jpg +0 -0
- package/tmp/playwriter-screenshot-1770021955223-fohh.jpg +0 -0
- package/tmp/playwriter-screenshot-1770021962887-trmf.jpg +0 -0
- package/tmp/playwriter-screenshot-1770021972452-sl2q.jpg +0 -0
- package/tmp/playwriter-screenshot-1770022222379-ympr.jpg +0 -0
- package/tmp/playwriter-screenshot-1770022254512-m8la.jpg +0 -0
- package/tmp/playwriter-screenshot-1770022263852-eenb.jpg +0 -0
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import http from 'http';
|
|
2
|
+
import { writeFileSync } from 'fs';
|
|
3
|
+
import { WorkerPool } from './workers/worker-pool.js';
|
|
4
|
+
import { BackgroundTaskStore } from './background-tasks.js';
|
|
5
|
+
|
|
6
|
+
const pool = new WorkerPool(4);
|
|
7
|
+
const backgroundStore = new BackgroundTaskStore();
|
|
8
|
+
|
|
9
|
+
function randomPort() {
|
|
10
|
+
return Math.floor(Math.random() * 10000) + 30000;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async function tryListen(server, port) {
|
|
14
|
+
return new Promise((resolve, reject) => {
|
|
15
|
+
server.once('error', reject);
|
|
16
|
+
server.listen(port, '127.0.0.1', () => resolve());
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async function startServer() {
|
|
21
|
+
const server = http.createServer(handleRequest);
|
|
22
|
+
for (let i = 0; i < 10; i++) {
|
|
23
|
+
const port = randomPort();
|
|
24
|
+
try {
|
|
25
|
+
await tryListen(server, port);
|
|
26
|
+
writeFileSync('/tmp/glootie-runner.port', String(port));
|
|
27
|
+
return server;
|
|
28
|
+
} catch (e) {
|
|
29
|
+
if (e.code !== 'EADDRINUSE') throw e;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
throw new Error('Could not bind port after 10 attempts');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function sendJSON(res, status, body) {
|
|
36
|
+
const data = JSON.stringify(body);
|
|
37
|
+
res.writeHead(status, { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(data) });
|
|
38
|
+
res.end(data);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async function readBody(req) {
|
|
42
|
+
return new Promise((resolve, reject) => {
|
|
43
|
+
const chunks = [];
|
|
44
|
+
req.on('data', c => chunks.push(c));
|
|
45
|
+
req.on('end', () => {
|
|
46
|
+
try { resolve(JSON.parse(Buffer.concat(chunks).toString())); }
|
|
47
|
+
catch (e) { reject(e); }
|
|
48
|
+
});
|
|
49
|
+
req.on('error', reject);
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async function handleRPC(body) {
|
|
54
|
+
const { method, params = {} } = body;
|
|
55
|
+
switch (method) {
|
|
56
|
+
case 'execute': {
|
|
57
|
+
const { code, runtime, workingDirectory, timeout, backgroundTaskId } = params;
|
|
58
|
+
const result = await pool.execute(code, runtime, workingDirectory, timeout, backgroundTaskId);
|
|
59
|
+
return { result };
|
|
60
|
+
}
|
|
61
|
+
case 'createTask': {
|
|
62
|
+
const { code, runtime, workingDirectory } = params;
|
|
63
|
+
const taskId = backgroundStore.createTask(code, runtime, workingDirectory);
|
|
64
|
+
return { taskId };
|
|
65
|
+
}
|
|
66
|
+
case 'startTask':
|
|
67
|
+
backgroundStore.startTask(params.taskId);
|
|
68
|
+
return {};
|
|
69
|
+
case 'completeTask':
|
|
70
|
+
backgroundStore.completeTask(params.taskId, params.result);
|
|
71
|
+
return {};
|
|
72
|
+
case 'failTask':
|
|
73
|
+
backgroundStore.failTask(params.taskId, new Error(params.error));
|
|
74
|
+
return {};
|
|
75
|
+
case 'getTask': {
|
|
76
|
+
const task = backgroundStore.getTask(params.taskId);
|
|
77
|
+
return { task };
|
|
78
|
+
}
|
|
79
|
+
case 'deleteTask':
|
|
80
|
+
if (backgroundStore.deleteTask) backgroundStore.deleteTask(params.taskId);
|
|
81
|
+
return {};
|
|
82
|
+
case 'appendOutput':
|
|
83
|
+
backgroundStore.appendOutput(params.taskId, params.type, params.data);
|
|
84
|
+
return {};
|
|
85
|
+
case 'getAndClearOutput': {
|
|
86
|
+
const output = backgroundStore.getAndClearOutput(params.taskId);
|
|
87
|
+
return { output };
|
|
88
|
+
}
|
|
89
|
+
case 'shutdown':
|
|
90
|
+
setImmediate(gracefulShutdown);
|
|
91
|
+
return { ok: true };
|
|
92
|
+
default:
|
|
93
|
+
throw Object.assign(new Error(`Unknown method: ${method}`), { code: -32601 });
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async function handleRequest(req, res) {
|
|
98
|
+
try {
|
|
99
|
+
if (req.method === 'GET' && req.url === '/health') {
|
|
100
|
+
return sendJSON(res, 200, { ok: true });
|
|
101
|
+
}
|
|
102
|
+
if (req.method === 'POST' && req.url === '/rpc') {
|
|
103
|
+
const body = await readBody(req);
|
|
104
|
+
try {
|
|
105
|
+
const result = await handleRPC(body);
|
|
106
|
+
return sendJSON(res, 200, { id: body.id, result });
|
|
107
|
+
} catch (e) {
|
|
108
|
+
return sendJSON(res, 200, {
|
|
109
|
+
id: body.id,
|
|
110
|
+
error: { code: e.code || -32603, message: e.message }
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
sendJSON(res, 404, { error: 'Not found' });
|
|
115
|
+
} catch (e) {
|
|
116
|
+
try { sendJSON(res, 400, { error: e.message }); } catch (_) {}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
let server;
|
|
121
|
+
|
|
122
|
+
async function gracefulShutdown() {
|
|
123
|
+
await pool.shutdown();
|
|
124
|
+
backgroundStore.shutdown();
|
|
125
|
+
if (server) server.close(() => process.exit(0));
|
|
126
|
+
else process.exit(0);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
process.on('SIGTERM', gracefulShutdown);
|
|
130
|
+
process.on('SIGINT', gracefulShutdown);
|
|
131
|
+
|
|
132
|
+
server = await startServer();
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { executeCode as rpcExecuteCode } from '../rpc-client.js';
|
|
2
|
+
|
|
3
|
+
const validate = {
|
|
4
|
+
execute({ code, workingDirectory }) {
|
|
5
|
+
if (!code || typeof code !== 'string') return 'Error: code must be a non-empty string';
|
|
6
|
+
if (!workingDirectory || typeof workingDirectory !== 'string') return 'Error: workingDirectory must be a non-empty string';
|
|
7
|
+
return null;
|
|
8
|
+
},
|
|
9
|
+
bash({ commands, workingDirectory }) {
|
|
10
|
+
if (!commands) return 'Error: commands must be provided';
|
|
11
|
+
if (!workingDirectory || typeof workingDirectory !== 'string') return 'Error: workingDirectory must be a non-empty string';
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export async function executeCode(code, runtime, workingDirectory, timeout = 30000, backgroundTaskId = null) {
|
|
17
|
+
return rpcExecuteCode(code, runtime, workingDirectory, timeout, backgroundTaskId);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export { validate };
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { executeCode, validate } from './execute-code-isolated.js';
|
|
2
|
+
import { backgroundStore } from '../rpc-client.js';
|
|
3
|
+
import { readFileSync, unlinkSync } from 'fs';
|
|
4
|
+
|
|
5
|
+
const HARD_CEILING_MS = 15000;
|
|
6
|
+
|
|
7
|
+
const formatters = {
|
|
8
|
+
output(result) {
|
|
9
|
+
const parts = result.stdout ? [`[STDOUT]\n${result.stdout}`] : [];
|
|
10
|
+
if (result.stderr) parts.push(`[STDERR]\n${result.stderr}`);
|
|
11
|
+
return parts.length ? parts.join('\n\n') : '(no output)';
|
|
12
|
+
},
|
|
13
|
+
context(result) {
|
|
14
|
+
const ctx = [`Exit code: ${result.exitCode ?? result.code}`, `Time: ${result.executionTimeMs}ms`];
|
|
15
|
+
if (result.stdout) ctx.push(`Stdout size: ${result.stdout.length} bytes`);
|
|
16
|
+
if (result.stderr) ctx.push(`Stderr size: ${result.stderr.length} bytes`);
|
|
17
|
+
return ctx.join(' | ');
|
|
18
|
+
},
|
|
19
|
+
logContent(logFile) {
|
|
20
|
+
if (!logFile) return '';
|
|
21
|
+
try {
|
|
22
|
+
const content = readFileSync(logFile, 'utf8');
|
|
23
|
+
try { unlinkSync(logFile); } catch (e) {}
|
|
24
|
+
return content;
|
|
25
|
+
} catch (e) {
|
|
26
|
+
return '';
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const response = {
|
|
32
|
+
success(text) { return { content: [{ type: 'text', text }], isError: false }; },
|
|
33
|
+
error(text) { return { content: [{ type: 'text', text }], isError: true }; }
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const createExecutionHandler = (validateFn, isBash = false) => async (args) => {
|
|
37
|
+
const { code, commands, workingDirectory, language = isBash ? 'bash' : 'auto', run_in_background } = args;
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
const params = isBash ? { commands, workingDirectory } : { code, workingDirectory };
|
|
41
|
+
const err = validate[isBash ? 'bash' : 'execute'](params);
|
|
42
|
+
if (err) return response.error(err);
|
|
43
|
+
|
|
44
|
+
const cmd = isBash ? (Array.isArray(commands) ? commands.join(' && ') : String(commands)) : code;
|
|
45
|
+
let runtime = language || 'nodejs';
|
|
46
|
+
if (!isBash && (runtime === 'typescript' || runtime === 'auto')) runtime = 'nodejs';
|
|
47
|
+
|
|
48
|
+
const backgroundTaskId = await backgroundStore.createTask(cmd, runtime, workingDirectory);
|
|
49
|
+
|
|
50
|
+
const timeout = HARD_CEILING_MS;
|
|
51
|
+
const deadline = Date.now() + timeout;
|
|
52
|
+
|
|
53
|
+
const safetyTimeout = new Promise(resolve => {
|
|
54
|
+
const remaining = Math.max(100, deadline - Date.now());
|
|
55
|
+
setTimeout(() => {
|
|
56
|
+
backgroundStore.startTask(backgroundTaskId);
|
|
57
|
+
resolve({ backgroundTaskId, persisted: true });
|
|
58
|
+
}, remaining);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
const result = await Promise.race([
|
|
62
|
+
executeCode(cmd, runtime, workingDirectory, timeout, backgroundTaskId),
|
|
63
|
+
safetyTimeout
|
|
64
|
+
]);
|
|
65
|
+
|
|
66
|
+
if (result.persisted) {
|
|
67
|
+
return response.success(
|
|
68
|
+
`Process backgrounded (ID: task_${result.backgroundTaskId})`
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (result.backgroundTaskId && result.completed) {
|
|
73
|
+
return response.success(
|
|
74
|
+
`Process completed in background (ID: task_${result.backgroundTaskId})`
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
backgroundStore.deleteTask(backgroundTaskId);
|
|
79
|
+
|
|
80
|
+
const logContent = formatters.logContent(result.logFile);
|
|
81
|
+
const logSection = logContent ? `\n\n[LOG]\n${logContent}` : '';
|
|
82
|
+
|
|
83
|
+
if (!result.success && !result.error) {
|
|
84
|
+
return response.error(`Command failed\n${formatters.context(result)}\n\n${formatters.output(result)}${logSection}`);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (result.error) return response.error(`Error: ${result.error}${logSection}`);
|
|
88
|
+
|
|
89
|
+
return response.success(`${formatters.context(result)}\n\n${formatters.output(result)}${logSection}`);
|
|
90
|
+
} catch (error) {
|
|
91
|
+
return response.error(`Error: ${error?.message || String(error)}`);
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
export const executionTools = process.platform === 'win32'
|
|
96
|
+
? [{
|
|
97
|
+
name: 'execute',
|
|
98
|
+
description: 'Execute code (JS/TS, Deno, Go, Rust, Python, C, C++, Java)',
|
|
99
|
+
inputSchema: {
|
|
100
|
+
type: 'object',
|
|
101
|
+
properties: {
|
|
102
|
+
workingDirectory: { type: 'string', description: 'Working directory' },
|
|
103
|
+
code: { type: 'string', description: 'Code to execute' },
|
|
104
|
+
language: { type: 'string', enum: ['nodejs', 'typescript', 'deno', 'go', 'rust', 'python', 'c', 'cpp', 'java', 'auto'], description: 'Language (default: auto)' },
|
|
105
|
+
run_in_background: { type: 'boolean', description: 'Return immediately with task reference. Commands have a hard 15s ceiling then auto-background.' }
|
|
106
|
+
},
|
|
107
|
+
required: ['workingDirectory', 'code']
|
|
108
|
+
},
|
|
109
|
+
handler: createExecutionHandler(validate.execute)
|
|
110
|
+
}]
|
|
111
|
+
: [{
|
|
112
|
+
name: 'execute',
|
|
113
|
+
description: 'Execute code (JS/TS, Deno, Go, Rust, Python, C, C++, Java)',
|
|
114
|
+
inputSchema: {
|
|
115
|
+
type: 'object',
|
|
116
|
+
properties: {
|
|
117
|
+
workingDirectory: { type: 'string', description: 'Working directory' },
|
|
118
|
+
code: { type: 'string', description: 'Code to execute' },
|
|
119
|
+
language: { type: 'string', enum: ['nodejs', 'typescript', 'deno', 'go', 'rust', 'python', 'c', 'cpp', 'java', 'auto'], description: 'Language (default: auto)' },
|
|
120
|
+
run_in_background: { type: 'boolean', description: 'Return immediately with task reference. Commands have a hard 15s ceiling then auto-background.' }
|
|
121
|
+
},
|
|
122
|
+
required: ['workingDirectory', 'code']
|
|
123
|
+
},
|
|
124
|
+
handler: createExecutionHandler(validate.execute)
|
|
125
|
+
}, {
|
|
126
|
+
name: 'bash',
|
|
127
|
+
description: 'Execute bash shell commands',
|
|
128
|
+
inputSchema: {
|
|
129
|
+
type: 'object',
|
|
130
|
+
properties: {
|
|
131
|
+
workingDirectory: { type: 'string', description: 'Working directory' },
|
|
132
|
+
commands: { type: ['string', 'array'], description: 'Commands to execute' },
|
|
133
|
+
language: { type: 'string', enum: ['bash', 'sh', 'zsh'], description: 'Language (default: bash)' },
|
|
134
|
+
run_in_background: { type: 'boolean', description: 'Return immediately with task reference. Commands have a hard 15s ceiling then auto-background.' }
|
|
135
|
+
},
|
|
136
|
+
required: ['workingDirectory', 'commands']
|
|
137
|
+
},
|
|
138
|
+
handler: createExecutionHandler(validate.bash, true)
|
|
139
|
+
}];
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { executionTools } from './tools/executor-tool-isolated.js';
|
|
2
|
+
import { backgroundStore } from './background-tasks.js';
|
|
3
|
+
|
|
4
|
+
const response = {
|
|
5
|
+
success(text) {
|
|
6
|
+
return {
|
|
7
|
+
content: [{ type: 'text', text }],
|
|
8
|
+
isError: false
|
|
9
|
+
};
|
|
10
|
+
},
|
|
11
|
+
error(text) {
|
|
12
|
+
return {
|
|
13
|
+
content: [{ type: 'text', text }],
|
|
14
|
+
isError: true
|
|
15
|
+
};
|
|
16
|
+
},
|
|
17
|
+
json(data, isErr = false) {
|
|
18
|
+
return this[isErr ? 'error' : 'success'](JSON.stringify(data, null, 2));
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const createSimpleTool = (name, description, inputSchema, handler) => ({
|
|
23
|
+
name,
|
|
24
|
+
description,
|
|
25
|
+
inputSchema,
|
|
26
|
+
handler
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
export const sleepTool = createSimpleTool(
|
|
30
|
+
'sleep',
|
|
31
|
+
'Sleep for a specified number of milliseconds',
|
|
32
|
+
{
|
|
33
|
+
type: 'object',
|
|
34
|
+
properties: {
|
|
35
|
+
milliseconds: { type: 'number', description: 'Number of milliseconds to sleep' }
|
|
36
|
+
},
|
|
37
|
+
required: ['milliseconds']
|
|
38
|
+
},
|
|
39
|
+
async ({ milliseconds }) => {
|
|
40
|
+
try {
|
|
41
|
+
if (typeof milliseconds !== 'number' || milliseconds < 0) {
|
|
42
|
+
return response.error('Invalid milliseconds: must be a non-negative number');
|
|
43
|
+
}
|
|
44
|
+
const cap = milliseconds < 295000 ? milliseconds : 295000;
|
|
45
|
+
await new Promise(resolve => setTimeout(resolve, cap));
|
|
46
|
+
return response.success(`Slept for ${milliseconds}ms`);
|
|
47
|
+
} catch (e) {
|
|
48
|
+
return response.error(`Sleep failed: ${e.message}`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
export const processStatusTool = createSimpleTool(
|
|
54
|
+
'process_status',
|
|
55
|
+
'Check status of a persisted background process',
|
|
56
|
+
{
|
|
57
|
+
type: 'object',
|
|
58
|
+
properties: {
|
|
59
|
+
task_id: { type: 'number', description: 'The task ID returned from execute' }
|
|
60
|
+
},
|
|
61
|
+
required: ['task_id']
|
|
62
|
+
},
|
|
63
|
+
async ({ task_id }) => {
|
|
64
|
+
try {
|
|
65
|
+
if (typeof task_id !== 'number' || task_id < 1) {
|
|
66
|
+
return response.error('Invalid task_id: must be a positive number');
|
|
67
|
+
}
|
|
68
|
+
const task = backgroundStore.getTask(task_id);
|
|
69
|
+
if (!task) {
|
|
70
|
+
return response.error(`Task ${task_id} not found`);
|
|
71
|
+
}
|
|
72
|
+
return response.json({
|
|
73
|
+
id: task.id,
|
|
74
|
+
status: task.status,
|
|
75
|
+
createdAt: new Date(task.createdAt).toISOString(),
|
|
76
|
+
startedAt: task.startedAt ? new Date(task.startedAt).toISOString() : null,
|
|
77
|
+
completedAt: task.completedAt ? new Date(task.completedAt).toISOString() : null,
|
|
78
|
+
runtime: task.runtime,
|
|
79
|
+
result: task.result
|
|
80
|
+
});
|
|
81
|
+
} catch (e) {
|
|
82
|
+
return response.error(`Status check failed: ${e.message}`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
export const processCloseTool = createSimpleTool(
|
|
88
|
+
'process_close',
|
|
89
|
+
'Clean up a completed background process',
|
|
90
|
+
{
|
|
91
|
+
type: 'object',
|
|
92
|
+
properties: {
|
|
93
|
+
task_id: { type: 'number', description: 'The task ID to close' }
|
|
94
|
+
},
|
|
95
|
+
required: ['task_id']
|
|
96
|
+
},
|
|
97
|
+
async ({ task_id }) => {
|
|
98
|
+
try {
|
|
99
|
+
if (typeof task_id !== 'number' || task_id < 1) {
|
|
100
|
+
return response.error('Invalid task_id: must be a positive number');
|
|
101
|
+
}
|
|
102
|
+
const task = backgroundStore.getTask(task_id);
|
|
103
|
+
if (!task) {
|
|
104
|
+
return response.error(`Task ${task_id} not found`);
|
|
105
|
+
}
|
|
106
|
+
backgroundStore.deleteTask(task_id);
|
|
107
|
+
return response.success(`Task ${task_id} closed`);
|
|
108
|
+
} catch (e) {
|
|
109
|
+
return response.error(`Close failed: ${e.message}`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
export const allTools = [...(executionTools || []), sleepTool, processStatusTool, processCloseTool];
|