@aigne/afs-sandbox 1.11.0-beta.6
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/LICENSE.md +26 -0
- package/README.md +489 -0
- package/dist/index.d.mts +432 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +1385 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +57 -0
package/LICENSE.md
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Proprietary License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024-2025 ArcBlock, Inc. All Rights Reserved.
|
|
4
|
+
|
|
5
|
+
This software and associated documentation files (the "Software") are proprietary
|
|
6
|
+
and confidential. Unauthorized copying, modification, distribution, or use of
|
|
7
|
+
this Software, via any medium, is strictly prohibited.
|
|
8
|
+
|
|
9
|
+
The Software is provided for internal use only within ArcBlock, Inc. and its
|
|
10
|
+
authorized affiliates.
|
|
11
|
+
|
|
12
|
+
## No License Granted
|
|
13
|
+
|
|
14
|
+
No license, express or implied, is granted to any party for any purpose.
|
|
15
|
+
All rights are reserved by ArcBlock, Inc.
|
|
16
|
+
|
|
17
|
+
## Public Artifact Distribution
|
|
18
|
+
|
|
19
|
+
Portions of this Software may be released publicly under separate open-source
|
|
20
|
+
licenses (such as MIT License) through designated public repositories. Such
|
|
21
|
+
public releases are governed by their respective licenses and do not affect
|
|
22
|
+
the proprietary nature of this repository.
|
|
23
|
+
|
|
24
|
+
## Contact
|
|
25
|
+
|
|
26
|
+
For licensing inquiries, contact: legal@arcblock.io
|
package/README.md
ADDED
|
@@ -0,0 +1,489 @@
|
|
|
1
|
+
# @aigne/afs-sandbox
|
|
2
|
+
|
|
3
|
+
AFS Sandbox Provider - A secure JavaScript execution environment for LLM-generated code.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
The AFS Sandbox Provider transforms AFS from a storage abstraction layer into a **semantic-level virtual machine for LLMs**. It enables a powerful development loop:
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
LLM generates JS → Sandbox executes → Results/errors flow back → LLM self-debugs → Stabilize as tool
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
This provider uses [QuickJS](https://bellard.org/quickjs/) (via WebAssembly) to execute JavaScript in a completely isolated environment with controlled capabilities.
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
pnpm add @aigne/afs-sandbox
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Quick Start
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
import { AFS } from "@aigne/afs";
|
|
25
|
+
import { AFSSandbox } from "@aigne/afs-sandbox";
|
|
26
|
+
|
|
27
|
+
// Create and mount the sandbox
|
|
28
|
+
const afs = new AFS();
|
|
29
|
+
const sandbox = new AFSSandbox({ name: "sandbox" });
|
|
30
|
+
afs.mount(sandbox);
|
|
31
|
+
|
|
32
|
+
// Write a script
|
|
33
|
+
await afs.write("/modules/sandbox/scripts/greet.js", {
|
|
34
|
+
content: `
|
|
35
|
+
afs.log("info", "Hello from sandbox!");
|
|
36
|
+
return "Hello, " + args.name;
|
|
37
|
+
`
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// Execute the script
|
|
41
|
+
const result = await afs.exec("/modules/sandbox/@exec/greet", { name: "World" });
|
|
42
|
+
// result.data = { success: true, result: "Hello, World", logs: [...] }
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Features
|
|
46
|
+
|
|
47
|
+
### Secure Isolation
|
|
48
|
+
|
|
49
|
+
- **No Node.js globals**: `process`, `require`, `Buffer`, `__dirname` are not available
|
|
50
|
+
- **No network access**: `fetch`, `XMLHttpRequest`, `WebSocket` are blocked
|
|
51
|
+
- **Memory limits**: Configurable memory ceiling (default 128MB)
|
|
52
|
+
- **Timeout protection**: Configurable execution timeout (default 5000ms)
|
|
53
|
+
- **Path traversal protection**: Encoded paths and `..` sequences are blocked
|
|
54
|
+
|
|
55
|
+
### Capability-Based API
|
|
56
|
+
|
|
57
|
+
Scripts have access to a controlled `afs` object with whitelisted capabilities:
|
|
58
|
+
|
|
59
|
+
```javascript
|
|
60
|
+
// Logging
|
|
61
|
+
afs.log("info", "Processing data...");
|
|
62
|
+
afs.log("error", "Something went wrong");
|
|
63
|
+
|
|
64
|
+
// Read from AFS (requires mount with AFS root)
|
|
65
|
+
const content = afs.read("/modules/fs/config.json");
|
|
66
|
+
|
|
67
|
+
// Write to AFS (requires write capability enabled)
|
|
68
|
+
afs.write("/modules/data/output.json", JSON.stringify(result));
|
|
69
|
+
|
|
70
|
+
// List directory contents
|
|
71
|
+
const entries = afs.list("/modules/fs/src");
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Script Storage & Versioning
|
|
75
|
+
|
|
76
|
+
Scripts are stored at `/scripts/{name}.js` with automatic metadata and version tracking:
|
|
77
|
+
|
|
78
|
+
**Metadata:**
|
|
79
|
+
- `createdAt` - Creation timestamp
|
|
80
|
+
- `updatedAt` - Last modification timestamp
|
|
81
|
+
- `lastRun` - Last execution timestamp
|
|
82
|
+
- `runCount` - Total execution count
|
|
83
|
+
|
|
84
|
+
**Version History:**
|
|
85
|
+
- Last 5 versions automatically preserved
|
|
86
|
+
- Access via `/@history/{name}` path
|
|
87
|
+
- Rollback support via `/@actions/rollback`
|
|
88
|
+
|
|
89
|
+
### Execution Context & History
|
|
90
|
+
|
|
91
|
+
Track executions with context information:
|
|
92
|
+
|
|
93
|
+
```typescript
|
|
94
|
+
const result = await afs.exec("/modules/sandbox/@exec/script", args, {
|
|
95
|
+
context: {
|
|
96
|
+
userId: "user-123",
|
|
97
|
+
sessionId: "session-456"
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Execution history is stored per-script with:
|
|
103
|
+
- Execution ID, timestamp, duration
|
|
104
|
+
- Success/failure status
|
|
105
|
+
- Sanitized arguments (sensitive fields redacted)
|
|
106
|
+
|
|
107
|
+
### Script Templates
|
|
108
|
+
|
|
109
|
+
Create scripts from built-in templates:
|
|
110
|
+
|
|
111
|
+
```typescript
|
|
112
|
+
// List available templates
|
|
113
|
+
const templates = await afs.list("/modules/sandbox/@templates");
|
|
114
|
+
|
|
115
|
+
// Create script from template
|
|
116
|
+
await afs.exec("/modules/sandbox/@actions/create-from-template", {
|
|
117
|
+
template: "data-transform",
|
|
118
|
+
name: "my-transformer",
|
|
119
|
+
variables: {
|
|
120
|
+
inputField: "rawData",
|
|
121
|
+
outputField: "processedData"
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
**Built-in Templates:**
|
|
127
|
+
- `basic` - Simple script template
|
|
128
|
+
- `data-transform` - Transform input data to output format
|
|
129
|
+
- `validation` - Input validation with error collection
|
|
130
|
+
- `aggregation` - Aggregate multiple values
|
|
131
|
+
|
|
132
|
+
### Script Promotion (Script → Action)
|
|
133
|
+
|
|
134
|
+
Promote tested scripts to formal actions:
|
|
135
|
+
|
|
136
|
+
```typescript
|
|
137
|
+
// Promote a script to an action
|
|
138
|
+
await afs.exec("/modules/sandbox/@actions/promote", {
|
|
139
|
+
script: "calculator",
|
|
140
|
+
name: "calculate",
|
|
141
|
+
description: "Perform arithmetic calculations",
|
|
142
|
+
inputSchema: {
|
|
143
|
+
type: "object",
|
|
144
|
+
properties: {
|
|
145
|
+
a: { type: "number" },
|
|
146
|
+
b: { type: "number" },
|
|
147
|
+
op: { type: "string", enum: ["add", "sub", "mul", "div"] }
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
// Execute the promoted action
|
|
153
|
+
const result = await afs.exec("/modules/sandbox/@actions/calculate", {
|
|
154
|
+
a: 10, b: 5, op: "mul"
|
|
155
|
+
});
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### Audit Logging
|
|
159
|
+
|
|
160
|
+
Track all sandbox operations for security and debugging:
|
|
161
|
+
|
|
162
|
+
```typescript
|
|
163
|
+
const sandbox = new AFSSandbox({
|
|
164
|
+
audit: {
|
|
165
|
+
enabled: true,
|
|
166
|
+
onEvent: (event) => console.log(event),
|
|
167
|
+
maxEntries: 1000
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
// Read audit log
|
|
172
|
+
const auditLog = await afs.read("/modules/sandbox/@audit");
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
**Event Types:**
|
|
176
|
+
- `execution` - Script executions
|
|
177
|
+
- `capability` - Capability usage (read/write/list)
|
|
178
|
+
- `security` - Security-related events (denied access)
|
|
179
|
+
- `script_write` - Script creation/modification
|
|
180
|
+
- `script_delete` - Script deletion
|
|
181
|
+
- `action_promote` - Script promotion to action
|
|
182
|
+
|
|
183
|
+
### Rate Limiting
|
|
184
|
+
|
|
185
|
+
Protect against abuse with configurable rate limits:
|
|
186
|
+
|
|
187
|
+
```typescript
|
|
188
|
+
const sandbox = new AFSSandbox({
|
|
189
|
+
rateLimit: {
|
|
190
|
+
maxExecutionsPerMinute: 100, // Global limit
|
|
191
|
+
maxExecutionsPerMinutePerUser: 20, // Per-user limit
|
|
192
|
+
maxExecutionsPerMinutePerScript: 30 // Per-script limit
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### Execution Metrics
|
|
198
|
+
|
|
199
|
+
Monitor sandbox performance:
|
|
200
|
+
|
|
201
|
+
```typescript
|
|
202
|
+
// Global metrics
|
|
203
|
+
const metrics = await afs.read("/modules/sandbox/@metrics");
|
|
204
|
+
// { totalExecutions, successCount, failureCount, timeoutCount, averageDuration }
|
|
205
|
+
|
|
206
|
+
// Per-script metrics
|
|
207
|
+
const scriptMetrics = await afs.read("/modules/sandbox/@metrics/my-script");
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
## Configuration
|
|
211
|
+
|
|
212
|
+
```typescript
|
|
213
|
+
interface AFSSandboxOptions {
|
|
214
|
+
name?: string; // Module name (default: "sandbox")
|
|
215
|
+
description?: string; // Module description
|
|
216
|
+
accessMode?: "readonly" | "readwrite"; // Access mode (default: "readwrite")
|
|
217
|
+
|
|
218
|
+
runtime?: {
|
|
219
|
+
timeout?: number; // Execution timeout in ms (default: 5000)
|
|
220
|
+
memoryLimit?: number; // Memory limit in MB (default: 128)
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
capabilities?: {
|
|
224
|
+
afs?: {
|
|
225
|
+
read?: boolean; // Enable afs.read() (default: true)
|
|
226
|
+
write?: boolean; // Enable afs.write() (default: false)
|
|
227
|
+
allowPaths?: string[]; // Allowed path patterns
|
|
228
|
+
denyPaths?: string[]; // Denied path patterns
|
|
229
|
+
};
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
audit?: {
|
|
233
|
+
enabled: boolean; // Enable audit logging
|
|
234
|
+
onEvent?: (event) => void; // Callback for audit events
|
|
235
|
+
maxEntries?: number; // Max entries to keep (default: 1000)
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
rateLimit?: {
|
|
239
|
+
maxExecutionsPerMinute?: number;
|
|
240
|
+
maxExecutionsPerMinutePerUser?: number;
|
|
241
|
+
maxExecutionsPerMinutePerScript?: number;
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
## Path Structure
|
|
247
|
+
|
|
248
|
+
```
|
|
249
|
+
/modules/sandbox/
|
|
250
|
+
├── /scripts/ # Script storage
|
|
251
|
+
│ └── {name}.js # Script content
|
|
252
|
+
├── /@exec/{name} # Execute stored script
|
|
253
|
+
├── /@history/{name} # Script version history
|
|
254
|
+
├── /@actions/
|
|
255
|
+
│ ├── run # Run inline code
|
|
256
|
+
│ ├── validate # Syntax validation
|
|
257
|
+
│ ├── rollback # Rollback to previous version
|
|
258
|
+
│ ├── promote # Promote script to action
|
|
259
|
+
│ ├── create-from-template # Create script from template
|
|
260
|
+
│ └── {promoted-action} # User-promoted actions
|
|
261
|
+
├── /@templates # List available templates
|
|
262
|
+
│ └── {name} # Template details
|
|
263
|
+
├── /@metrics # Global execution metrics
|
|
264
|
+
│ └── {script} # Per-script metrics
|
|
265
|
+
└── /@audit # Audit log
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
## Execution Result
|
|
269
|
+
|
|
270
|
+
All executions return a structured result:
|
|
271
|
+
|
|
272
|
+
```typescript
|
|
273
|
+
interface ExecutionResult {
|
|
274
|
+
success: boolean;
|
|
275
|
+
result?: unknown; // Return value (if success)
|
|
276
|
+
error?: string; // Error message (if failed)
|
|
277
|
+
stack?: string; // Stack trace (if failed)
|
|
278
|
+
logs: LogEntry[]; // All log entries
|
|
279
|
+
duration: number; // Execution time in ms
|
|
280
|
+
timedOut?: boolean; // True if execution timed out
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
interface LogEntry {
|
|
284
|
+
level: "debug" | "info" | "warn" | "error";
|
|
285
|
+
message: string;
|
|
286
|
+
timestamp: number;
|
|
287
|
+
}
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
## Implementation Status
|
|
291
|
+
|
|
292
|
+
### Phase 1: POC (Complete) ✅
|
|
293
|
+
|
|
294
|
+
Core execution loop validated with 47 tests.
|
|
295
|
+
|
|
296
|
+
| KPI | Status |
|
|
297
|
+
|-----|--------|
|
|
298
|
+
| Basic execution | ✅ `return 2 + 2` → `{ success: true, result: 4 }` |
|
|
299
|
+
| AFS read capability | ✅ `afs.read('/path')` works |
|
|
300
|
+
| Error propagation | ✅ Full error messages with line numbers |
|
|
301
|
+
| Timeout handling | ✅ Infinite loops timeout correctly |
|
|
302
|
+
| Security isolation | ✅ No access to Node.js globals |
|
|
303
|
+
|
|
304
|
+
### Phase 2: MVP (Complete) ✅
|
|
305
|
+
|
|
306
|
+
Internal team usable with 54 additional tests (101 total).
|
|
307
|
+
|
|
308
|
+
| Feature | Status |
|
|
309
|
+
|---------|--------|
|
|
310
|
+
| afs.write() capability | ✅ Write to allowed paths |
|
|
311
|
+
| Script version history | ✅ Last 5 versions preserved |
|
|
312
|
+
| Execution context | ✅ userId, sessionId tracking |
|
|
313
|
+
| Execution history | ✅ Per-script history with redaction |
|
|
314
|
+
| Rollback support | ✅ Restore previous versions |
|
|
315
|
+
|
|
316
|
+
### Phase 3: Beta (Complete) ✅
|
|
317
|
+
|
|
318
|
+
Production-ready with 55 additional tests (156 total).
|
|
319
|
+
|
|
320
|
+
| Feature | Status |
|
|
321
|
+
|---------|--------|
|
|
322
|
+
| Script Promote Workflow | ✅ Promote scripts to formal actions |
|
|
323
|
+
| Script Templates | ✅ 4 built-in templates |
|
|
324
|
+
| Audit Logging | ✅ 6 event types tracked |
|
|
325
|
+
| Rate Limiting | ✅ Global, per-user, per-script |
|
|
326
|
+
| Execution Metrics | ✅ Global and per-script tracking |
|
|
327
|
+
|
|
328
|
+
### Phase 4+: Future Vision
|
|
329
|
+
|
|
330
|
+
- Agent Tool Marketplace
|
|
331
|
+
- Multi-agent orchestration
|
|
332
|
+
- Alternative runtimes (Deno)
|
|
333
|
+
- Full async/await support (see Limitations below)
|
|
334
|
+
|
|
335
|
+
## Known Limitations
|
|
336
|
+
|
|
337
|
+
### Async/Await Support (Deferred)
|
|
338
|
+
|
|
339
|
+
**Current State:** Capability calls (`afs.read()`, `afs.write()`, `afs.list()`) work synchronously within scripts. The sandbox does not support full async/await patterns.
|
|
340
|
+
|
|
341
|
+
| Works | Does Not Work |
|
|
342
|
+
|-------|---------------|
|
|
343
|
+
| `const data = afs.read(path);` | `const data = await afs.read(path);` (across multiple statements) |
|
|
344
|
+
| Single-statement operations | Complex Promise chains |
|
|
345
|
+
| Synchronous data processing | `setTimeout`/`setInterval` |
|
|
346
|
+
|
|
347
|
+
**Technical Reason:** QuickJS (WebAssembly) executes JavaScript synchronously. While we set up Promise infrastructure for capability calls, the runtime cannot pause execution to wait for external async operations. Implementing full async/await would require:
|
|
348
|
+
|
|
349
|
+
1. Custom Promise polling mechanism
|
|
350
|
+
2. Careful handle management to avoid memory leaks
|
|
351
|
+
3. Significant changes to the execution model
|
|
352
|
+
|
|
353
|
+
**Attempted Solution:** We tried implementing async support but encountered QuickJS handle leaks (`list_empty(&rt->gc_obj_list)` assertion failure) when Promises weren't properly resolved before context disposal.
|
|
354
|
+
|
|
355
|
+
**Future Path:** Phase 4 may introduce Deno runtime as an alternative backend with native async support.
|
|
356
|
+
|
|
357
|
+
### Other Limitations
|
|
358
|
+
|
|
359
|
+
- **No ES modules**: `import`/`export` syntax not supported
|
|
360
|
+
- **No top-level await**: Use synchronous patterns
|
|
361
|
+
- **Limited standard library**: Only core JavaScript, no Node.js APIs
|
|
362
|
+
- **Memory ceiling**: Hard limit of configurable MB (default 128MB)
|
|
363
|
+
|
|
364
|
+
## Design Documents
|
|
365
|
+
|
|
366
|
+
- **Design Philosophy**: [`/intent/designs/afs-as-llm-runtime.md`](../../intent/designs/afs-as-llm-runtime.md)
|
|
367
|
+
- **Implementation Plan**: [`/intent/plans/sandbox-implementation.md`](../../intent/plans/sandbox-implementation.md)
|
|
368
|
+
|
|
369
|
+
## Technical Details
|
|
370
|
+
|
|
371
|
+
### Why QuickJS?
|
|
372
|
+
|
|
373
|
+
| Option | Pros | Cons |
|
|
374
|
+
|--------|------|------|
|
|
375
|
+
| `isolated-vm` | V8 compatible, fast | Native bindings, doesn't work with Bun |
|
|
376
|
+
| `quickjs-emscripten` | WebAssembly, works everywhere | Slightly slower, sync-only execution |
|
|
377
|
+
| `vm2` | Easy to use | Security vulnerabilities, deprecated |
|
|
378
|
+
|
|
379
|
+
We chose `quickjs-emscripten` for its universal compatibility (Node.js, Bun, browsers) and strong isolation guarantees.
|
|
380
|
+
|
|
381
|
+
## Examples
|
|
382
|
+
|
|
383
|
+
### Simple Calculator
|
|
384
|
+
|
|
385
|
+
```typescript
|
|
386
|
+
await afs.write("/modules/sandbox/scripts/calc.js", {
|
|
387
|
+
content: `
|
|
388
|
+
const { a, b, op } = args;
|
|
389
|
+
switch(op) {
|
|
390
|
+
case 'add': return a + b;
|
|
391
|
+
case 'sub': return a - b;
|
|
392
|
+
case 'mul': return a * b;
|
|
393
|
+
case 'div': return b !== 0 ? a / b : 'Division by zero';
|
|
394
|
+
default: return 'Unknown operation';
|
|
395
|
+
}
|
|
396
|
+
`
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
const result = await afs.exec("/modules/sandbox/@exec/calc", {
|
|
400
|
+
a: 10, b: 5, op: "mul"
|
|
401
|
+
});
|
|
402
|
+
// result.data.result = 50
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
### Data Processing with Logging
|
|
406
|
+
|
|
407
|
+
```typescript
|
|
408
|
+
await afs.write("/modules/sandbox/scripts/process.js", {
|
|
409
|
+
content: `
|
|
410
|
+
afs.log("info", "Starting processing...");
|
|
411
|
+
|
|
412
|
+
const items = args.items || [];
|
|
413
|
+
const processed = items.map(item => {
|
|
414
|
+
afs.log("debug", "Processing: " + JSON.stringify(item));
|
|
415
|
+
return { ...item, processed: true };
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
afs.log("info", "Completed: " + processed.length + " items");
|
|
419
|
+
return processed;
|
|
420
|
+
`
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
const result = await afs.exec("/modules/sandbox/@exec/process", {
|
|
424
|
+
items: [{ id: 1 }, { id: 2 }]
|
|
425
|
+
});
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
### Promote Script to Action
|
|
429
|
+
|
|
430
|
+
```typescript
|
|
431
|
+
// 1. Develop and test a script
|
|
432
|
+
await afs.write("/modules/sandbox/scripts/greet.js", {
|
|
433
|
+
content: `return "Hello, " + (args.name || "World") + "!";`
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
// Test it
|
|
437
|
+
const test = await afs.exec("/modules/sandbox/@exec/greet", { name: "Alice" });
|
|
438
|
+
// test.data.result = "Hello, Alice!"
|
|
439
|
+
|
|
440
|
+
// 2. Promote to a formal action
|
|
441
|
+
await afs.exec("/modules/sandbox/@actions/promote", {
|
|
442
|
+
script: "greet",
|
|
443
|
+
name: "greet-user",
|
|
444
|
+
description: "Greet a user by name"
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
// 3. Use the promoted action
|
|
448
|
+
const result = await afs.exec("/modules/sandbox/@actions/greet-user", { name: "Bob" });
|
|
449
|
+
// result.data.result = "Hello, Bob!"
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
### Error Handling for LLM Debug Loop
|
|
453
|
+
|
|
454
|
+
```typescript
|
|
455
|
+
// LLM writes code with a bug
|
|
456
|
+
await afs.write("/modules/sandbox/scripts/buggy.js", {
|
|
457
|
+
content: `
|
|
458
|
+
const data = JSON.parse(args.json);
|
|
459
|
+
return data.value.nested.property; // Bug: doesn't handle missing properties
|
|
460
|
+
`
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
// Execute and get error
|
|
464
|
+
const result = await afs.exec("/modules/sandbox/@exec/buggy", {
|
|
465
|
+
json: '{"value": {}}'
|
|
466
|
+
});
|
|
467
|
+
// result.data = {
|
|
468
|
+
// success: false,
|
|
469
|
+
// error: "Cannot read properties of undefined (reading 'property')",
|
|
470
|
+
// }
|
|
471
|
+
|
|
472
|
+
// LLM sees error, fixes the code
|
|
473
|
+
await afs.write("/modules/sandbox/scripts/buggy.js", {
|
|
474
|
+
content: `
|
|
475
|
+
const data = JSON.parse(args.json);
|
|
476
|
+
return data?.value?.nested?.property ?? 'default';
|
|
477
|
+
`
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
// Now it works
|
|
481
|
+
const fixed = await afs.exec("/modules/sandbox/@exec/buggy", {
|
|
482
|
+
json: '{"value": {}}'
|
|
483
|
+
});
|
|
484
|
+
// fixed.data.result = 'default'
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
## License
|
|
488
|
+
|
|
489
|
+
UNLICENSED - Internal use only
|