@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.
Files changed (51) hide show
  1. package/README.md +51 -2160
  2. package/dist/agent.d.ts.map +1 -1
  3. package/dist/agent.js +32 -25
  4. package/dist/agent.js.map +1 -1
  5. package/dist/backend.d.ts +90 -68
  6. package/dist/backend.d.ts.map +1 -1
  7. package/dist/backend.js +22 -12
  8. package/dist/backend.js.map +1 -1
  9. package/dist/backends/filesystem.d.ts +153 -5
  10. package/dist/backends/filesystem.d.ts.map +1 -1
  11. package/dist/backends/filesystem.js +274 -1
  12. package/dist/backends/filesystem.js.map +1 -1
  13. package/dist/backends/index.d.ts +1 -2
  14. package/dist/backends/index.d.ts.map +1 -1
  15. package/dist/backends/index.js +1 -2
  16. package/dist/backends/index.js.map +1 -1
  17. package/dist/index.d.ts +6 -6
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +4 -6
  20. package/dist/index.js.map +1 -1
  21. package/dist/security/index.d.ts +20 -20
  22. package/dist/security/index.d.ts.map +1 -1
  23. package/dist/security/index.js +26 -24
  24. package/dist/security/index.js.map +1 -1
  25. package/dist/tools/execute.d.ts +15 -9
  26. package/dist/tools/execute.d.ts.map +1 -1
  27. package/dist/tools/execute.js +19 -9
  28. package/dist/tools/execute.js.map +1 -1
  29. package/dist/tools/factory.d.ts +48 -32
  30. package/dist/tools/factory.d.ts.map +1 -1
  31. package/dist/tools/factory.js +60 -43
  32. package/dist/tools/factory.js.map +1 -1
  33. package/dist/tools/index.d.ts +3 -5
  34. package/dist/tools/index.d.ts.map +1 -1
  35. package/dist/tools/index.js +1 -3
  36. package/dist/tools/index.js.map +1 -1
  37. package/dist/tools/task.d.ts +49 -1
  38. package/dist/tools/task.d.ts.map +1 -1
  39. package/dist/tools/task.js +130 -3
  40. package/dist/tools/task.js.map +1 -1
  41. package/dist/types.d.ts +4 -4
  42. package/dist/types.d.ts.map +1 -1
  43. package/package.json +4 -8
  44. package/dist/backends/sandbox.d.ts +0 -315
  45. package/dist/backends/sandbox.d.ts.map +0 -1
  46. package/dist/backends/sandbox.js +0 -490
  47. package/dist/backends/sandbox.js.map +0 -1
  48. package/dist/tools/user-interaction.d.ts +0 -116
  49. package/dist/tools/user-interaction.d.ts.map +0 -1
  50. package/dist/tools/user-interaction.js +0 -147
  51. 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 value implements the SandboxBackendProtocol interface.
44
+ * Check if a backend supports command execution.
45
45
  *
46
- * @param value - Value to check
47
- * @returns True if value is a SandboxBackendProtocol
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 maybeExecute(backend: BackendProtocol, command: string) {
52
- * if (isSandboxBackend(backend)) {
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 isSandboxBackend(value) {
62
- if (!isBackend(value)) {
74
+ export function hasExecuteCapability(backend) {
75
+ if (!backend || typeof backend !== "object") {
63
76
  return false;
64
77
  }
65
- const obj = value;
66
- return (typeof obj.id === "string" &&
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
@@ -1 +1 @@
1
- {"version":3,"file":"backend.js","sourceRoot":"","sources":["../src/backend.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAyYH,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;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,gBAAgB,CAAC,KAAc;IAC7C,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE,CAAC;QACtB,OAAO,KAAK,CAAC;IACf,CAAC;IAED,MAAM,GAAG,GAAG,KAA2C,CAAC;IACxD,OAAO,CACL,OAAO,GAAG,CAAC,EAAE,KAAK,QAAQ;QAC1B,OAAO,GAAG,CAAC,OAAO,KAAK,UAAU;QACjC,OAAO,GAAG,CAAC,WAAW,KAAK,UAAU;QACrC,OAAO,GAAG,CAAC,aAAa,KAAK,UAAU,CACxC,CAAC;AACJ,CAAC"}
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
- * const options: FilesystemBackendOptions = {
35
+ * // Basic filesystem backend (no bash)
36
+ * const backend = new FilesystemBackend({
36
37
  * rootDir: "/home/user/project",
37
38
  * maxFileSizeMb: 5,
38
- * followSymlinks: false,
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
- * followSymlinks: false,
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":"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"}
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
- * followSymlinks: false,
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
  /**