@lleverage-ai/agent-sdk 0.0.1 → 0.0.2-alpha.3
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/README.md +51 -2160
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +32 -25
- package/dist/agent.js.map +1 -1
- package/dist/backend.d.ts +90 -68
- package/dist/backend.d.ts.map +1 -1
- package/dist/backend.js +22 -12
- package/dist/backend.js.map +1 -1
- package/dist/backends/filesystem.d.ts +153 -5
- package/dist/backends/filesystem.d.ts.map +1 -1
- package/dist/backends/filesystem.js +274 -1
- package/dist/backends/filesystem.js.map +1 -1
- package/dist/backends/index.d.ts +1 -2
- package/dist/backends/index.d.ts.map +1 -1
- package/dist/backends/index.js +1 -2
- package/dist/backends/index.js.map +1 -1
- package/dist/index.d.ts +6 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -6
- package/dist/index.js.map +1 -1
- package/dist/security/index.d.ts +20 -20
- package/dist/security/index.d.ts.map +1 -1
- package/dist/security/index.js +26 -24
- package/dist/security/index.js.map +1 -1
- package/dist/tools/execute.d.ts +15 -9
- package/dist/tools/execute.d.ts.map +1 -1
- package/dist/tools/execute.js +19 -9
- package/dist/tools/execute.js.map +1 -1
- package/dist/tools/factory.d.ts +48 -32
- package/dist/tools/factory.d.ts.map +1 -1
- package/dist/tools/factory.js +60 -43
- package/dist/tools/factory.js.map +1 -1
- package/dist/tools/index.d.ts +3 -5
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +1 -3
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/task.d.ts +49 -1
- package/dist/tools/task.d.ts.map +1 -1
- package/dist/tools/task.js +130 -3
- package/dist/tools/task.js.map +1 -1
- package/dist/types.d.ts +4 -4
- package/dist/types.d.ts.map +1 -1
- package/package.json +4 -8
- package/dist/backends/sandbox.d.ts +0 -315
- package/dist/backends/sandbox.d.ts.map +0 -1
- package/dist/backends/sandbox.js +0 -490
- package/dist/backends/sandbox.js.map +0 -1
- package/dist/tools/user-interaction.d.ts +0 -116
- package/dist/tools/user-interaction.d.ts.map +0 -1
- package/dist/tools/user-interaction.js +0 -147
- package/dist/tools/user-interaction.js.map +0 -1
package/dist/backend.js
CHANGED
|
@@ -41,31 +41,41 @@ export function isBackend(value) {
|
|
|
41
41
|
typeof obj.edit === "function");
|
|
42
42
|
}
|
|
43
43
|
/**
|
|
44
|
-
* Check if a
|
|
44
|
+
* Check if a backend supports command execution.
|
|
45
45
|
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
46
|
+
* Use this to safely narrow a BackendProtocol to one that supports the `execute()` method.
|
|
47
|
+
* This is the recommended way to check for execute capability.
|
|
48
|
+
*
|
|
49
|
+
* @param backend - Backend to check
|
|
50
|
+
* @returns True if the backend supports execute()
|
|
48
51
|
*
|
|
49
52
|
* @example
|
|
50
53
|
* ```typescript
|
|
51
|
-
* function
|
|
52
|
-
* if (
|
|
54
|
+
* function maybeRunCommand(backend: BackendProtocol, command: string) {
|
|
55
|
+
* if (hasExecuteCapability(backend)) {
|
|
53
56
|
* return backend.execute(command);
|
|
54
57
|
* }
|
|
55
58
|
* throw new Error("Backend does not support command execution");
|
|
56
59
|
* }
|
|
60
|
+
*
|
|
61
|
+
* // With FilesystemBackend that has bash enabled
|
|
62
|
+
* const backend = new FilesystemBackend({
|
|
63
|
+
* rootDir: process.cwd(),
|
|
64
|
+
* enableBash: true,
|
|
65
|
+
* });
|
|
66
|
+
*
|
|
67
|
+
* if (hasExecuteCapability(backend)) {
|
|
68
|
+
* const result = await backend.execute("npm test");
|
|
69
|
+
* }
|
|
57
70
|
* ```
|
|
58
71
|
*
|
|
59
72
|
* @category Backend
|
|
60
73
|
*/
|
|
61
|
-
export function
|
|
62
|
-
if (!
|
|
74
|
+
export function hasExecuteCapability(backend) {
|
|
75
|
+
if (!backend || typeof backend !== "object") {
|
|
63
76
|
return false;
|
|
64
77
|
}
|
|
65
|
-
const obj =
|
|
66
|
-
return
|
|
67
|
-
typeof obj.execute === "function" &&
|
|
68
|
-
typeof obj.uploadFiles === "function" &&
|
|
69
|
-
typeof obj.downloadFiles === "function");
|
|
78
|
+
const obj = backend;
|
|
79
|
+
return typeof obj.id === "string" && typeof obj.execute === "function";
|
|
70
80
|
}
|
|
71
81
|
//# sourceMappingURL=backend.js.map
|
package/dist/backend.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"backend.js","sourceRoot":"","sources":["../src/backend.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;
|
|
1
|
+
{"version":3,"file":"backend.js","sourceRoot":"","sources":["../src/backend.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAwYH,gFAAgF;AAChF,cAAc;AACd,gFAAgF;AAEhF;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,SAAS,CAAC,KAAc;IACtC,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAChD,OAAO,KAAK,CAAC;IACf,CAAC;IAED,MAAM,GAAG,GAAG,KAAgC,CAAC;IAC7C,OAAO,CACL,OAAO,GAAG,CAAC,MAAM,KAAK,UAAU;QAChC,OAAO,GAAG,CAAC,IAAI,KAAK,UAAU;QAC9B,OAAO,GAAG,CAAC,OAAO,KAAK,UAAU;QACjC,OAAO,GAAG,CAAC,OAAO,KAAK,UAAU;QACjC,OAAO,GAAG,CAAC,QAAQ,KAAK,UAAU;QAClC,OAAO,GAAG,CAAC,KAAK,KAAK,UAAU;QAC/B,OAAO,GAAG,CAAC,IAAI,KAAK,UAAU,CAC/B,CAAC;AACJ,CAAC;AAkBD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,MAAM,UAAU,oBAAoB,CAClC,OAA2C;IAE3C,IAAI,CAAC,OAAO,IAAI,OAAO,OAAO,KAAK,QAAQ,EAAE,CAAC;QAC5C,OAAO,KAAK,CAAC;IACf,CAAC;IACD,MAAM,GAAG,GAAG,OAA6C,CAAC;IAC1D,OAAO,OAAO,GAAG,CAAC,EAAE,KAAK,QAAQ,IAAI,OAAO,GAAG,CAAC,OAAO,KAAK,UAAU,CAAC;AACzE,CAAC"}
|
|
@@ -26,17 +26,25 @@
|
|
|
26
26
|
*
|
|
27
27
|
* @packageDocumentation
|
|
28
28
|
*/
|
|
29
|
-
import type { BackendProtocol, EditResult, FileData, FileInfo, GrepMatch, WriteResult } from "../backend.js";
|
|
29
|
+
import type { BackendProtocol, EditResult, ExecuteResponse, FileData, FileInfo, FileUploadResponse, GrepMatch, WriteResult } from "../backend.js";
|
|
30
30
|
/**
|
|
31
31
|
* Configuration options for FilesystemBackend.
|
|
32
32
|
*
|
|
33
33
|
* @example
|
|
34
34
|
* ```typescript
|
|
35
|
-
*
|
|
35
|
+
* // Basic filesystem backend (no bash)
|
|
36
|
+
* const backend = new FilesystemBackend({
|
|
36
37
|
* rootDir: "/home/user/project",
|
|
37
38
|
* maxFileSizeMb: 5,
|
|
38
|
-
*
|
|
39
|
-
*
|
|
39
|
+
* });
|
|
40
|
+
*
|
|
41
|
+
* // Filesystem backend with bash enabled
|
|
42
|
+
* const backendWithBash = new FilesystemBackend({
|
|
43
|
+
* rootDir: "/home/user/project",
|
|
44
|
+
* enableBash: true,
|
|
45
|
+
* timeout: 30000,
|
|
46
|
+
* blockedCommands: ["rm -rf /"],
|
|
47
|
+
* });
|
|
40
48
|
* ```
|
|
41
49
|
*
|
|
42
50
|
* @category Backend
|
|
@@ -65,6 +73,54 @@ export interface FilesystemBackendOptions {
|
|
|
65
73
|
* Useful for accessing system paths like /tmp.
|
|
66
74
|
*/
|
|
67
75
|
allowedPaths?: string[];
|
|
76
|
+
/**
|
|
77
|
+
* Enable shell command execution via the `execute()` method.
|
|
78
|
+
* When false (default), the backend only supports file operations.
|
|
79
|
+
* @defaultValue false
|
|
80
|
+
*/
|
|
81
|
+
enableBash?: boolean;
|
|
82
|
+
/**
|
|
83
|
+
* Default timeout in milliseconds for command execution.
|
|
84
|
+
* Only used when enableBash is true.
|
|
85
|
+
* @defaultValue 120000 (2 minutes)
|
|
86
|
+
*/
|
|
87
|
+
timeout?: number;
|
|
88
|
+
/**
|
|
89
|
+
* Maximum output size in bytes before truncation.
|
|
90
|
+
* Only used when enableBash is true.
|
|
91
|
+
* @defaultValue 1048576 (1MB)
|
|
92
|
+
*/
|
|
93
|
+
maxOutputSize?: number;
|
|
94
|
+
/**
|
|
95
|
+
* Shell to use for command execution.
|
|
96
|
+
* Only used when enableBash is true.
|
|
97
|
+
* @defaultValue "/bin/sh" on Unix, "cmd.exe" on Windows
|
|
98
|
+
*/
|
|
99
|
+
shell?: string;
|
|
100
|
+
/**
|
|
101
|
+
* Environment variables to set for all commands.
|
|
102
|
+
* Only used when enableBash is true.
|
|
103
|
+
*/
|
|
104
|
+
env?: Record<string, string>;
|
|
105
|
+
/**
|
|
106
|
+
* Commands or patterns that are blocked from execution.
|
|
107
|
+
* Supports simple string matching and regex patterns.
|
|
108
|
+
* Only used when enableBash is true.
|
|
109
|
+
*/
|
|
110
|
+
blockedCommands?: Array<string | RegExp>;
|
|
111
|
+
/**
|
|
112
|
+
* Only allow these commands to be executed.
|
|
113
|
+
* If set, only commands matching these patterns are allowed.
|
|
114
|
+
* Only used when enableBash is true.
|
|
115
|
+
*/
|
|
116
|
+
allowedCommands?: Array<string | RegExp>;
|
|
117
|
+
/**
|
|
118
|
+
* Whether to allow potentially dangerous commands.
|
|
119
|
+
* When false (default), certain dangerous patterns are blocked.
|
|
120
|
+
* Only used when enableBash is true.
|
|
121
|
+
* @defaultValue false
|
|
122
|
+
*/
|
|
123
|
+
allowDangerous?: boolean;
|
|
68
124
|
}
|
|
69
125
|
/**
|
|
70
126
|
* Error thrown when a path traversal attack is detected.
|
|
@@ -96,6 +152,33 @@ export declare class FileSizeLimitError extends Error {
|
|
|
96
152
|
readonly limitMb: number;
|
|
97
153
|
constructor(path: string, sizeMb: number, limitMb: number);
|
|
98
154
|
}
|
|
155
|
+
/**
|
|
156
|
+
* Default patterns that are considered dangerous.
|
|
157
|
+
* These are blocked unless allowDangerous is true.
|
|
158
|
+
*
|
|
159
|
+
* @category Backend
|
|
160
|
+
*/
|
|
161
|
+
export declare const DANGEROUS_COMMAND_PATTERNS: RegExp[];
|
|
162
|
+
/**
|
|
163
|
+
* Error thrown when a command is blocked by security filters.
|
|
164
|
+
*
|
|
165
|
+
* @category Backend
|
|
166
|
+
*/
|
|
167
|
+
export declare class CommandBlockedError extends Error {
|
|
168
|
+
readonly command: string;
|
|
169
|
+
readonly reason: string;
|
|
170
|
+
constructor(command: string, reason: string);
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Error thrown when a command execution times out.
|
|
174
|
+
*
|
|
175
|
+
* @category Backend
|
|
176
|
+
*/
|
|
177
|
+
export declare class CommandTimeoutError extends Error {
|
|
178
|
+
readonly command: string;
|
|
179
|
+
readonly timeoutMs: number;
|
|
180
|
+
constructor(command: string, timeoutMs: number);
|
|
181
|
+
}
|
|
99
182
|
/**
|
|
100
183
|
* Filesystem backend implementation for real disk operations.
|
|
101
184
|
*
|
|
@@ -104,12 +187,21 @@ export declare class FileSizeLimitError extends Error {
|
|
|
104
187
|
* - Symlink attacks
|
|
105
188
|
* - Excessive file sizes
|
|
106
189
|
*
|
|
190
|
+
* Optionally supports shell command execution when `enableBash` is true.
|
|
191
|
+
*
|
|
107
192
|
* @example
|
|
108
193
|
* ```typescript
|
|
194
|
+
* // Basic usage - file operations only
|
|
109
195
|
* const backend = new FilesystemBackend({
|
|
110
196
|
* rootDir: "/home/user/project",
|
|
111
197
|
* maxFileSizeMb: 10,
|
|
112
|
-
*
|
|
198
|
+
* });
|
|
199
|
+
*
|
|
200
|
+
* // With bash enabled
|
|
201
|
+
* const backend = new FilesystemBackend({
|
|
202
|
+
* rootDir: "/home/user/project",
|
|
203
|
+
* enableBash: true,
|
|
204
|
+
* timeout: 30000,
|
|
113
205
|
* });
|
|
114
206
|
*
|
|
115
207
|
* // List files
|
|
@@ -120,15 +212,30 @@ export declare class FileSizeLimitError extends Error {
|
|
|
120
212
|
*
|
|
121
213
|
* // Search for patterns
|
|
122
214
|
* const matches = await backend.grepRaw("TODO", "./src", "*.ts");
|
|
215
|
+
*
|
|
216
|
+
* // Execute commands (if enableBash is true)
|
|
217
|
+
* if (hasExecuteCapability(backend)) {
|
|
218
|
+
* const result = await backend.execute("npm test");
|
|
219
|
+
* }
|
|
123
220
|
* ```
|
|
124
221
|
*
|
|
125
222
|
* @category Backend
|
|
126
223
|
*/
|
|
127
224
|
export declare class FilesystemBackend implements BackendProtocol {
|
|
225
|
+
/** Unique identifier for this backend instance */
|
|
226
|
+
readonly id: string;
|
|
128
227
|
private readonly rootDir;
|
|
129
228
|
private readonly maxFileSizeMb;
|
|
130
229
|
private readonly followSymlinks;
|
|
131
230
|
private readonly allowedPaths;
|
|
231
|
+
private readonly enableBash;
|
|
232
|
+
private readonly timeout;
|
|
233
|
+
private readonly maxOutputSize;
|
|
234
|
+
private readonly shell;
|
|
235
|
+
private readonly env;
|
|
236
|
+
private readonly blockedCommands;
|
|
237
|
+
private readonly allowedCommands?;
|
|
238
|
+
private readonly allowDangerous;
|
|
132
239
|
/**
|
|
133
240
|
* Create a new FilesystemBackend.
|
|
134
241
|
*
|
|
@@ -193,6 +300,47 @@ export declare class FilesystemBackend implements BackendProtocol {
|
|
|
193
300
|
* @returns Result indicating success or failure
|
|
194
301
|
*/
|
|
195
302
|
edit(filePath: string, oldString: string, newString: string, replaceAll?: boolean): Promise<EditResult>;
|
|
303
|
+
/**
|
|
304
|
+
* Execute a shell command.
|
|
305
|
+
*
|
|
306
|
+
* Only available when `enableBash` is true in the constructor options.
|
|
307
|
+
*
|
|
308
|
+
* @param command - Shell command to execute
|
|
309
|
+
* @returns Execution result with output and exit code
|
|
310
|
+
* @throws {Error} If bash is not enabled
|
|
311
|
+
* @throws {CommandBlockedError} If the command is blocked
|
|
312
|
+
*/
|
|
313
|
+
execute(command: string): Promise<ExecuteResponse>;
|
|
314
|
+
/**
|
|
315
|
+
* Upload files to the backend.
|
|
316
|
+
*
|
|
317
|
+
* @param files - Array of [path, content] tuples
|
|
318
|
+
* @returns Array of upload results
|
|
319
|
+
*/
|
|
320
|
+
uploadFiles(files: Array<[string, Uint8Array]>): Promise<FileUploadResponse[]>;
|
|
321
|
+
/**
|
|
322
|
+
* Download files from the backend.
|
|
323
|
+
*
|
|
324
|
+
* @param paths - Paths to download
|
|
325
|
+
* @returns Array of { path, content } objects
|
|
326
|
+
*/
|
|
327
|
+
downloadFiles(paths: string[]): Promise<Array<{
|
|
328
|
+
path: string;
|
|
329
|
+
content: Uint8Array;
|
|
330
|
+
}>>;
|
|
331
|
+
/**
|
|
332
|
+
* Validate a command before execution.
|
|
333
|
+
*
|
|
334
|
+
* @param command - Command to validate
|
|
335
|
+
* @throws {CommandBlockedError} If the command is blocked
|
|
336
|
+
* @internal
|
|
337
|
+
*/
|
|
338
|
+
private validateCommand;
|
|
339
|
+
/**
|
|
340
|
+
* Check if a command matches a pattern.
|
|
341
|
+
* @internal
|
|
342
|
+
*/
|
|
343
|
+
private matchesCommandPattern;
|
|
196
344
|
/**
|
|
197
345
|
* Resolve a path and validate it's within the allowed boundaries.
|
|
198
346
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"filesystem.d.ts","sourceRoot":"","sources":["../../src/backends/filesystem.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;
|
|
1
|
+
{"version":3,"file":"filesystem.d.ts","sourceRoot":"","sources":["../../src/backends/filesystem.ts"],"names":[],"mappings":"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"}
|
|
@@ -26,6 +26,8 @@
|
|
|
26
26
|
*
|
|
27
27
|
* @packageDocumentation
|
|
28
28
|
*/
|
|
29
|
+
import { spawn } from "node:child_process";
|
|
30
|
+
import { randomUUID } from "node:crypto";
|
|
29
31
|
import * as fsSync from "node:fs";
|
|
30
32
|
import * as fs from "node:fs/promises";
|
|
31
33
|
import * as path from "node:path";
|
|
@@ -78,6 +80,70 @@ export class FileSizeLimitError extends Error {
|
|
|
78
80
|
}
|
|
79
81
|
}
|
|
80
82
|
// =============================================================================
|
|
83
|
+
// Dangerous Command Patterns
|
|
84
|
+
// =============================================================================
|
|
85
|
+
/**
|
|
86
|
+
* Default patterns that are considered dangerous.
|
|
87
|
+
* These are blocked unless allowDangerous is true.
|
|
88
|
+
*
|
|
89
|
+
* @category Backend
|
|
90
|
+
*/
|
|
91
|
+
export const DANGEROUS_COMMAND_PATTERNS = [
|
|
92
|
+
// Recursive force delete from root or home
|
|
93
|
+
/rm\s+(-[a-z]*r[a-z]*\s+)?(-[a-z]*f[a-z]*\s+)?[/~]/i,
|
|
94
|
+
/rm\s+(-[a-z]*f[a-z]*\s+)?(-[a-z]*r[a-z]*\s+)?[/~]/i,
|
|
95
|
+
// Format/wipe commands
|
|
96
|
+
/mkfs\./i,
|
|
97
|
+
/dd\s+.*of=\/dev\//i,
|
|
98
|
+
// Shutdown/reboot
|
|
99
|
+
/shutdown/i,
|
|
100
|
+
/reboot/i,
|
|
101
|
+
/halt/i,
|
|
102
|
+
/poweroff/i,
|
|
103
|
+
// Fork bombs
|
|
104
|
+
/:\(\)\s*\{\s*:\s*\|\s*:\s*&\s*\}\s*;?\s*:/,
|
|
105
|
+
// Overwrite important files
|
|
106
|
+
/>\s*\/etc\/(passwd|shadow|sudoers)/i,
|
|
107
|
+
// Downloading and executing
|
|
108
|
+
/curl.*\|\s*(ba)?sh/i,
|
|
109
|
+
/wget.*\|\s*(ba)?sh/i,
|
|
110
|
+
// Chmod dangerous patterns
|
|
111
|
+
/chmod\s+777\s+\//i,
|
|
112
|
+
/chmod\s+-R\s+777/i,
|
|
113
|
+
// Environment manipulation that could be dangerous
|
|
114
|
+
/export\s+PATH\s*=\s*$/i,
|
|
115
|
+
];
|
|
116
|
+
/**
|
|
117
|
+
* Error thrown when a command is blocked by security filters.
|
|
118
|
+
*
|
|
119
|
+
* @category Backend
|
|
120
|
+
*/
|
|
121
|
+
export class CommandBlockedError extends Error {
|
|
122
|
+
command;
|
|
123
|
+
reason;
|
|
124
|
+
constructor(command, reason) {
|
|
125
|
+
super(`Command blocked: ${reason}`);
|
|
126
|
+
this.command = command;
|
|
127
|
+
this.reason = reason;
|
|
128
|
+
this.name = "CommandBlockedError";
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Error thrown when a command execution times out.
|
|
133
|
+
*
|
|
134
|
+
* @category Backend
|
|
135
|
+
*/
|
|
136
|
+
export class CommandTimeoutError extends Error {
|
|
137
|
+
command;
|
|
138
|
+
timeoutMs;
|
|
139
|
+
constructor(command, timeoutMs) {
|
|
140
|
+
super(`Command timed out after ${timeoutMs}ms: "${command.slice(0, 100)}${command.length > 100 ? "..." : ""}"`);
|
|
141
|
+
this.command = command;
|
|
142
|
+
this.timeoutMs = timeoutMs;
|
|
143
|
+
this.name = "CommandTimeoutError";
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
// =============================================================================
|
|
81
147
|
// Filesystem Backend Implementation
|
|
82
148
|
// =============================================================================
|
|
83
149
|
/**
|
|
@@ -88,12 +154,21 @@ export class FileSizeLimitError extends Error {
|
|
|
88
154
|
* - Symlink attacks
|
|
89
155
|
* - Excessive file sizes
|
|
90
156
|
*
|
|
157
|
+
* Optionally supports shell command execution when `enableBash` is true.
|
|
158
|
+
*
|
|
91
159
|
* @example
|
|
92
160
|
* ```typescript
|
|
161
|
+
* // Basic usage - file operations only
|
|
93
162
|
* const backend = new FilesystemBackend({
|
|
94
163
|
* rootDir: "/home/user/project",
|
|
95
164
|
* maxFileSizeMb: 10,
|
|
96
|
-
*
|
|
165
|
+
* });
|
|
166
|
+
*
|
|
167
|
+
* // With bash enabled
|
|
168
|
+
* const backend = new FilesystemBackend({
|
|
169
|
+
* rootDir: "/home/user/project",
|
|
170
|
+
* enableBash: true,
|
|
171
|
+
* timeout: 30000,
|
|
97
172
|
* });
|
|
98
173
|
*
|
|
99
174
|
* // List files
|
|
@@ -104,21 +179,39 @@ export class FileSizeLimitError extends Error {
|
|
|
104
179
|
*
|
|
105
180
|
* // Search for patterns
|
|
106
181
|
* const matches = await backend.grepRaw("TODO", "./src", "*.ts");
|
|
182
|
+
*
|
|
183
|
+
* // Execute commands (if enableBash is true)
|
|
184
|
+
* if (hasExecuteCapability(backend)) {
|
|
185
|
+
* const result = await backend.execute("npm test");
|
|
186
|
+
* }
|
|
107
187
|
* ```
|
|
108
188
|
*
|
|
109
189
|
* @category Backend
|
|
110
190
|
*/
|
|
111
191
|
export class FilesystemBackend {
|
|
192
|
+
/** Unique identifier for this backend instance */
|
|
193
|
+
id;
|
|
112
194
|
rootDir;
|
|
113
195
|
maxFileSizeMb;
|
|
114
196
|
followSymlinks;
|
|
115
197
|
allowedPaths;
|
|
198
|
+
// Bash execution options
|
|
199
|
+
enableBash;
|
|
200
|
+
timeout;
|
|
201
|
+
maxOutputSize;
|
|
202
|
+
shell;
|
|
203
|
+
env;
|
|
204
|
+
blockedCommands;
|
|
205
|
+
allowedCommands;
|
|
206
|
+
allowDangerous;
|
|
116
207
|
/**
|
|
117
208
|
* Create a new FilesystemBackend.
|
|
118
209
|
*
|
|
119
210
|
* @param options - Configuration options
|
|
120
211
|
*/
|
|
121
212
|
constructor(options = {}) {
|
|
213
|
+
// Generate unique ID
|
|
214
|
+
this.id = `fs-${randomUUID()}`;
|
|
122
215
|
// Resolve symlinks in rootDir to handle cases like /tmp -> /private/tmp on macOS
|
|
123
216
|
// This ensures symlink checks within rootDir work correctly
|
|
124
217
|
const resolvedRoot = path.resolve(options.rootDir ?? process.cwd());
|
|
@@ -141,6 +234,15 @@ export class FilesystemBackend {
|
|
|
141
234
|
return resolved;
|
|
142
235
|
}
|
|
143
236
|
}));
|
|
237
|
+
// Initialize bash execution options
|
|
238
|
+
this.enableBash = options.enableBash ?? false;
|
|
239
|
+
this.timeout = options.timeout ?? 120000; // 2 minutes default
|
|
240
|
+
this.maxOutputSize = options.maxOutputSize ?? 1048576; // 1MB default
|
|
241
|
+
this.shell = options.shell ?? (process.platform === "win32" ? "cmd.exe" : "/bin/sh");
|
|
242
|
+
this.env = { ...process.env, ...options.env };
|
|
243
|
+
this.blockedCommands = options.blockedCommands ?? [];
|
|
244
|
+
this.allowedCommands = options.allowedCommands;
|
|
245
|
+
this.allowDangerous = options.allowDangerous ?? false;
|
|
144
246
|
}
|
|
145
247
|
/**
|
|
146
248
|
* List files and directories at the given path.
|
|
@@ -382,6 +484,177 @@ export class FilesystemBackend {
|
|
|
382
484
|
}
|
|
383
485
|
}
|
|
384
486
|
// ===========================================================================
|
|
487
|
+
// Command Execution Methods
|
|
488
|
+
// ===========================================================================
|
|
489
|
+
/**
|
|
490
|
+
* Execute a shell command.
|
|
491
|
+
*
|
|
492
|
+
* Only available when `enableBash` is true in the constructor options.
|
|
493
|
+
*
|
|
494
|
+
* @param command - Shell command to execute
|
|
495
|
+
* @returns Execution result with output and exit code
|
|
496
|
+
* @throws {Error} If bash is not enabled
|
|
497
|
+
* @throws {CommandBlockedError} If the command is blocked
|
|
498
|
+
*/
|
|
499
|
+
async execute(command) {
|
|
500
|
+
if (!this.enableBash) {
|
|
501
|
+
throw new Error("Bash execution is not enabled. Create FilesystemBackend with enableBash: true");
|
|
502
|
+
}
|
|
503
|
+
// Validate command
|
|
504
|
+
this.validateCommand(command);
|
|
505
|
+
return new Promise((resolve) => {
|
|
506
|
+
let output = "";
|
|
507
|
+
let truncated = false;
|
|
508
|
+
let resolved = false;
|
|
509
|
+
const shellArgs = process.platform === "win32" ? ["/c", command] : ["-c", command];
|
|
510
|
+
const child = spawn(this.shell, shellArgs, {
|
|
511
|
+
cwd: this.rootDir,
|
|
512
|
+
env: this.env,
|
|
513
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
514
|
+
});
|
|
515
|
+
const handleOutput = (data) => {
|
|
516
|
+
if (truncated)
|
|
517
|
+
return;
|
|
518
|
+
const chunk = data.toString();
|
|
519
|
+
if (output.length + chunk.length > this.maxOutputSize) {
|
|
520
|
+
output += chunk.slice(0, this.maxOutputSize - output.length);
|
|
521
|
+
truncated = true;
|
|
522
|
+
}
|
|
523
|
+
else {
|
|
524
|
+
output += chunk;
|
|
525
|
+
}
|
|
526
|
+
};
|
|
527
|
+
child.stdout?.on("data", handleOutput);
|
|
528
|
+
child.stderr?.on("data", handleOutput);
|
|
529
|
+
// Set up timeout
|
|
530
|
+
const timeoutId = setTimeout(() => {
|
|
531
|
+
if (!resolved) {
|
|
532
|
+
resolved = true;
|
|
533
|
+
child.kill("SIGTERM");
|
|
534
|
+
// Give it a moment to terminate gracefully
|
|
535
|
+
setTimeout(() => {
|
|
536
|
+
child.kill("SIGKILL");
|
|
537
|
+
}, 1000);
|
|
538
|
+
resolve({
|
|
539
|
+
output: `${output}\n[Command timed out after ${this.timeout}ms]`,
|
|
540
|
+
exitCode: null,
|
|
541
|
+
truncated,
|
|
542
|
+
});
|
|
543
|
+
}
|
|
544
|
+
}, this.timeout);
|
|
545
|
+
child.on("close", (code) => {
|
|
546
|
+
if (!resolved) {
|
|
547
|
+
resolved = true;
|
|
548
|
+
clearTimeout(timeoutId);
|
|
549
|
+
resolve({
|
|
550
|
+
output,
|
|
551
|
+
exitCode: code,
|
|
552
|
+
truncated,
|
|
553
|
+
});
|
|
554
|
+
}
|
|
555
|
+
});
|
|
556
|
+
child.on("error", (error) => {
|
|
557
|
+
if (!resolved) {
|
|
558
|
+
resolved = true;
|
|
559
|
+
clearTimeout(timeoutId);
|
|
560
|
+
resolve({
|
|
561
|
+
output: `Error: ${error.message}`,
|
|
562
|
+
exitCode: 1,
|
|
563
|
+
truncated: false,
|
|
564
|
+
});
|
|
565
|
+
}
|
|
566
|
+
});
|
|
567
|
+
});
|
|
568
|
+
}
|
|
569
|
+
/**
|
|
570
|
+
* Upload files to the backend.
|
|
571
|
+
*
|
|
572
|
+
* @param files - Array of [path, content] tuples
|
|
573
|
+
* @returns Array of upload results
|
|
574
|
+
*/
|
|
575
|
+
async uploadFiles(files) {
|
|
576
|
+
const results = [];
|
|
577
|
+
for (const [filePath, content] of files) {
|
|
578
|
+
try {
|
|
579
|
+
const textContent = new TextDecoder().decode(content);
|
|
580
|
+
const result = await this.write(filePath, textContent);
|
|
581
|
+
results.push({
|
|
582
|
+
path: filePath,
|
|
583
|
+
success: result.success,
|
|
584
|
+
error: result.error,
|
|
585
|
+
});
|
|
586
|
+
}
|
|
587
|
+
catch (error) {
|
|
588
|
+
results.push({
|
|
589
|
+
path: filePath,
|
|
590
|
+
success: false,
|
|
591
|
+
error: error instanceof Error ? error.message : String(error),
|
|
592
|
+
});
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
return results;
|
|
596
|
+
}
|
|
597
|
+
/**
|
|
598
|
+
* Download files from the backend.
|
|
599
|
+
*
|
|
600
|
+
* @param paths - Paths to download
|
|
601
|
+
* @returns Array of { path, content } objects
|
|
602
|
+
*/
|
|
603
|
+
async downloadFiles(paths) {
|
|
604
|
+
const results = [];
|
|
605
|
+
for (const filePath of paths) {
|
|
606
|
+
try {
|
|
607
|
+
const fileData = await this.readRaw(filePath);
|
|
608
|
+
const content = new TextEncoder().encode(fileData.content.join("\n"));
|
|
609
|
+
results.push({ path: filePath, content });
|
|
610
|
+
}
|
|
611
|
+
catch {
|
|
612
|
+
// Skip files we can't read
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
return results;
|
|
616
|
+
}
|
|
617
|
+
/**
|
|
618
|
+
* Validate a command before execution.
|
|
619
|
+
*
|
|
620
|
+
* @param command - Command to validate
|
|
621
|
+
* @throws {CommandBlockedError} If the command is blocked
|
|
622
|
+
* @internal
|
|
623
|
+
*/
|
|
624
|
+
validateCommand(command) {
|
|
625
|
+
// Check allowlist first (if configured)
|
|
626
|
+
if (this.allowedCommands && this.allowedCommands.length > 0) {
|
|
627
|
+
const isAllowed = this.allowedCommands.some((pattern) => this.matchesCommandPattern(command, pattern));
|
|
628
|
+
if (!isAllowed) {
|
|
629
|
+
throw new CommandBlockedError(command, "Command not in allowlist");
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
// Check blocklist
|
|
633
|
+
for (const pattern of this.blockedCommands) {
|
|
634
|
+
if (this.matchesCommandPattern(command, pattern)) {
|
|
635
|
+
throw new CommandBlockedError(command, "Command matches blocked pattern");
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
// Check dangerous patterns (unless explicitly allowed)
|
|
639
|
+
if (!this.allowDangerous) {
|
|
640
|
+
for (const pattern of DANGEROUS_COMMAND_PATTERNS) {
|
|
641
|
+
if (pattern.test(command)) {
|
|
642
|
+
throw new CommandBlockedError(command, `Command matches dangerous pattern: ${pattern.source.slice(0, 50)}`);
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
/**
|
|
648
|
+
* Check if a command matches a pattern.
|
|
649
|
+
* @internal
|
|
650
|
+
*/
|
|
651
|
+
matchesCommandPattern(command, pattern) {
|
|
652
|
+
if (typeof pattern === "string") {
|
|
653
|
+
return command.includes(pattern);
|
|
654
|
+
}
|
|
655
|
+
return pattern.test(command);
|
|
656
|
+
}
|
|
657
|
+
// ===========================================================================
|
|
385
658
|
// Security Methods
|
|
386
659
|
// ===========================================================================
|
|
387
660
|
/**
|