@tyvm/knowhow 0.0.21 → 0.0.23
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/package.json +3 -1
- package/src/agents/base/base.ts +16 -7
- package/src/agents/configurable/ConfigAgent.ts +5 -3
- package/src/agents/developer/developer.ts +3 -4
- package/src/agents/index.ts +26 -2
- package/src/agents/patcher/patcher.ts +3 -5
- package/src/agents/researcher/researcher.ts +3 -4
- package/src/agents/tools/agentCall.ts +5 -2
- package/src/agents/tools/executeScript/README.md +78 -0
- package/src/agents/tools/executeScript/definition.ts +73 -0
- package/src/agents/tools/executeScript/examples/dependency-injection-validation.ts +272 -0
- package/src/agents/tools/executeScript/examples/quick-test.ts +74 -0
- package/src/agents/tools/executeScript/examples/serialization-test.ts +321 -0
- package/src/agents/tools/executeScript/examples/test-runner.ts +197 -0
- package/src/agents/tools/executeScript/index.ts +93 -0
- package/src/agents/tools/index.ts +1 -0
- package/src/agents/tools/list.ts +2 -1
- package/src/agents/vim/vim.ts +3 -4
- package/src/ai.ts +2 -1
- package/src/chat.ts +4 -2
- package/src/cli.ts +7 -15
- package/src/clients/index.ts +23 -9
- package/src/dataset/diffs/test.ts +2 -1
- package/src/index.ts +3 -3
- package/src/services/AgentService.ts +9 -10
- package/src/services/EventService.ts +0 -2
- package/src/services/GitHub.ts +0 -1
- package/src/services/KnowhowClient.ts +0 -3
- package/src/services/Mcp.ts +0 -2
- package/src/services/S3.ts +0 -1
- package/src/services/Tools.ts +63 -8
- package/src/services/flags.ts +0 -1
- package/src/services/index.ts +56 -0
- package/src/services/modules/index.ts +53 -0
- package/src/{modules → services/modules}/types.ts +16 -5
- package/src/services/script-execution/SandboxContext.ts +278 -0
- package/src/services/script-execution/ScriptExecutor.ts +339 -0
- package/src/services/script-execution/ScriptPolicy.ts +236 -0
- package/src/services/script-execution/ScriptTracer.ts +249 -0
- package/src/services/script-execution/types.ts +134 -0
- package/src/worker.ts +3 -3
- package/tests/integration/fileblocks/readwrite.test.ts +2 -1
- package/tests/integration/patching.test.ts +5 -5
- package/ts_build/src/agents/base/base.d.ts +9 -4
- package/ts_build/src/agents/base/base.js +7 -10
- package/ts_build/src/agents/base/base.js.map +1 -1
- package/ts_build/src/agents/configurable/ConfigAgent.d.ts +2 -2
- package/ts_build/src/agents/configurable/ConfigAgent.js +2 -2
- package/ts_build/src/agents/configurable/ConfigAgent.js.map +1 -1
- package/ts_build/src/agents/developer/developer.d.ts +2 -3
- package/ts_build/src/agents/developer/developer.js +3 -4
- package/ts_build/src/agents/developer/developer.js.map +1 -1
- package/ts_build/src/agents/index.d.ts +11 -2
- package/ts_build/src/agents/index.js +19 -3
- package/ts_build/src/agents/index.js.map +1 -1
- package/ts_build/src/agents/patcher/patcher.d.ts +2 -3
- package/ts_build/src/agents/patcher/patcher.js +3 -4
- package/ts_build/src/agents/patcher/patcher.js.map +1 -1
- package/ts_build/src/agents/researcher/researcher.d.ts +2 -3
- package/ts_build/src/agents/researcher/researcher.js +3 -4
- package/ts_build/src/agents/researcher/researcher.js.map +1 -1
- package/ts_build/src/agents/tools/agentCall.js +4 -4
- package/ts_build/src/agents/tools/agentCall.js.map +1 -1
- package/ts_build/src/agents/tools/executeScript/definition.d.ts +2 -0
- package/ts_build/src/agents/tools/executeScript/definition.js +70 -0
- package/ts_build/src/agents/tools/executeScript/definition.js.map +1 -0
- package/ts_build/src/agents/tools/executeScript/examples/dependency-injection-validation.d.ts +18 -0
- package/ts_build/src/agents/tools/executeScript/examples/dependency-injection-validation.js +192 -0
- package/ts_build/src/agents/tools/executeScript/examples/dependency-injection-validation.js.map +1 -0
- package/ts_build/src/agents/tools/executeScript/examples/quick-test.d.ts +3 -0
- package/ts_build/src/agents/tools/executeScript/examples/quick-test.js +65 -0
- package/ts_build/src/agents/tools/executeScript/examples/quick-test.js.map +1 -0
- package/ts_build/src/agents/tools/executeScript/examples/serialization-test.d.ts +15 -0
- package/ts_build/src/agents/tools/executeScript/examples/serialization-test.js +266 -0
- package/ts_build/src/agents/tools/executeScript/examples/serialization-test.js.map +1 -0
- package/ts_build/src/agents/tools/executeScript/examples/simple-example.d.ts +20 -0
- package/ts_build/src/agents/tools/executeScript/examples/simple-example.js +35 -0
- package/ts_build/src/agents/tools/executeScript/examples/simple-example.js.map +1 -0
- package/ts_build/src/agents/tools/executeScript/examples/test-runner.d.ts +4 -0
- package/ts_build/src/agents/tools/executeScript/examples/test-runner.js +198 -0
- package/ts_build/src/agents/tools/executeScript/examples/test-runner.js.map +1 -0
- package/ts_build/src/agents/tools/executeScript/handler.d.ts +27 -0
- package/ts_build/src/agents/tools/executeScript/handler.js +64 -0
- package/ts_build/src/agents/tools/executeScript/handler.js.map +1 -0
- package/ts_build/src/agents/tools/executeScript/index.d.ts +27 -0
- package/ts_build/src/agents/tools/executeScript/index.js +70 -0
- package/ts_build/src/agents/tools/executeScript/index.js.map +1 -0
- package/ts_build/src/agents/tools/executeScript.d.ts +29 -0
- package/ts_build/src/agents/tools/executeScript.js +124 -0
- package/ts_build/src/agents/tools/executeScript.js.map +1 -0
- package/ts_build/src/agents/tools/index.d.ts +1 -0
- package/ts_build/src/agents/tools/index.js +1 -0
- package/ts_build/src/agents/tools/index.js.map +1 -1
- package/ts_build/src/agents/tools/list.js +2 -0
- package/ts_build/src/agents/tools/list.js.map +1 -1
- package/ts_build/src/agents/vim/vim.d.ts +2 -3
- package/ts_build/src/agents/vim/vim.js +3 -4
- package/ts_build/src/agents/vim/vim.js.map +1 -1
- package/ts_build/src/ai.js +2 -1
- package/ts_build/src/ai.js.map +1 -1
- package/ts_build/src/chat.js +10 -9
- package/ts_build/src/chat.js.map +1 -1
- package/ts_build/src/cli.js +12 -19
- package/ts_build/src/cli.js.map +1 -1
- package/ts_build/src/clients/index.d.ts +9 -2
- package/ts_build/src/clients/index.js +17 -4
- package/ts_build/src/clients/index.js.map +1 -1
- package/ts_build/src/dataset/diffs/test.js +2 -1
- package/ts_build/src/dataset/diffs/test.js.map +1 -1
- package/ts_build/src/index.js +10 -10
- package/ts_build/src/index.js.map +1 -1
- package/ts_build/src/services/AgentService.d.ts +7 -3
- package/ts_build/src/services/AgentService.js +11 -10
- package/ts_build/src/services/AgentService.js.map +1 -1
- package/ts_build/src/services/EventService.d.ts +0 -1
- package/ts_build/src/services/EventService.js +1 -2
- package/ts_build/src/services/EventService.js.map +1 -1
- package/ts_build/src/services/GitHub.d.ts +0 -1
- package/ts_build/src/services/GitHub.js +1 -2
- package/ts_build/src/services/GitHub.js.map +1 -1
- package/ts_build/src/services/KnowhowClient.d.ts +0 -1
- package/ts_build/src/services/KnowhowClient.js +1 -2
- package/ts_build/src/services/KnowhowClient.js.map +1 -1
- package/ts_build/src/services/Mcp.d.ts +0 -1
- package/ts_build/src/services/Mcp.js +1 -2
- package/ts_build/src/services/Mcp.js.map +1 -1
- package/ts_build/src/services/S3.d.ts +0 -1
- package/ts_build/src/services/S3.js +1 -2
- package/ts_build/src/services/S3.js.map +1 -1
- package/ts_build/src/services/Tools.d.ts +22 -1
- package/ts_build/src/services/Tools.js +32 -6
- package/ts_build/src/services/Tools.js.map +1 -1
- package/ts_build/src/services/flags.d.ts +0 -1
- package/ts_build/src/services/flags.js +1 -2
- package/ts_build/src/services/flags.js.map +1 -1
- package/ts_build/src/services/index.d.ts +25 -0
- package/ts_build/src/services/index.js +42 -1
- package/ts_build/src/services/index.js.map +1 -1
- package/ts_build/src/services/modules/example-usage.d.ts +11 -0
- package/ts_build/src/services/modules/example-usage.js +43 -0
- package/ts_build/src/services/modules/example-usage.js.map +1 -0
- package/ts_build/src/services/modules/index.d.ts +4 -0
- package/ts_build/src/services/modules/index.js +44 -0
- package/ts_build/src/services/modules/index.js.map +1 -0
- package/ts_build/src/services/modules/types.d.ts +47 -0
- package/ts_build/src/services/modules/types.js +3 -0
- package/ts_build/src/services/modules/types.js.map +1 -0
- package/ts_build/src/services/script-execution/SandboxContext.d.ts +34 -0
- package/ts_build/src/services/script-execution/SandboxContext.js +186 -0
- package/ts_build/src/services/script-execution/SandboxContext.js.map +1 -0
- package/ts_build/src/services/script-execution/ScriptExecutor.d.ts +17 -0
- package/ts_build/src/services/script-execution/ScriptExecutor.js +211 -0
- package/ts_build/src/services/script-execution/ScriptExecutor.js.map +1 -0
- package/ts_build/src/services/script-execution/ScriptPolicy.d.ts +27 -0
- package/ts_build/src/services/script-execution/ScriptPolicy.js +150 -0
- package/ts_build/src/services/script-execution/ScriptPolicy.js.map +1 -0
- package/ts_build/src/services/script-execution/ScriptTracer.d.ts +19 -0
- package/ts_build/src/services/script-execution/ScriptTracer.js +186 -0
- package/ts_build/src/services/script-execution/ScriptTracer.js.map +1 -0
- package/ts_build/src/services/script-execution/types.d.ts +108 -0
- package/ts_build/src/services/script-execution/types.js +3 -0
- package/ts_build/src/services/script-execution/types.js.map +1 -0
- package/ts_build/src/services/singletons.d.ts +17 -0
- package/ts_build/src/services/singletons.js +28 -0
- package/ts_build/src/services/singletons.js.map +1 -0
- package/ts_build/src/worker.js +4 -3
- package/ts_build/src/worker.js.map +1 -1
- package/ts_build/tests/integration/fileblocks/readwrite.test.js +10 -9
- package/ts_build/tests/integration/fileblocks/readwrite.test.js.map +1 -1
- package/ts_build/tests/integration/patching.test.js +9 -10
- package/ts_build/tests/integration/patching.test.js.map +1 -1
- package/src/modules/index.ts +0 -37
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
import { AIClient } from "../../clients";
|
|
2
|
+
import { ScriptTracer } from "./ScriptTracer";
|
|
3
|
+
import { ScriptPolicyEnforcer } from "./ScriptPolicy";
|
|
4
|
+
import { Artifact, QuotaUsage } from "./types";
|
|
5
|
+
import { Message } from "../../clients/types";
|
|
6
|
+
import { ToolsService } from "../Tools";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Provides the execution context for scripts with controlled access to tools and AI
|
|
10
|
+
*/
|
|
11
|
+
export class SandboxContext {
|
|
12
|
+
private artifacts: Artifact[] = [];
|
|
13
|
+
private consoleOutput: string[] = [];
|
|
14
|
+
|
|
15
|
+
constructor(
|
|
16
|
+
private toolsService: ToolsService,
|
|
17
|
+
private clients: AIClient,
|
|
18
|
+
private tracer: ScriptTracer,
|
|
19
|
+
private policyEnforcer: ScriptPolicyEnforcer
|
|
20
|
+
) {}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Console implementation that captures output
|
|
24
|
+
*/
|
|
25
|
+
console = {
|
|
26
|
+
log: (...args: any[]) => {
|
|
27
|
+
const message = args
|
|
28
|
+
.map((arg) =>
|
|
29
|
+
typeof arg === "object" ? JSON.stringify(arg) : String(arg)
|
|
30
|
+
)
|
|
31
|
+
.join(" ");
|
|
32
|
+
this.consoleOutput.push(`[LOG] ${message}`);
|
|
33
|
+
this.tracer.emitEvent("console_log", { message, args });
|
|
34
|
+
},
|
|
35
|
+
|
|
36
|
+
error: (...args: any[]) => {
|
|
37
|
+
const message = args
|
|
38
|
+
.map((arg) =>
|
|
39
|
+
typeof arg === "object" ? JSON.stringify(arg) : String(arg)
|
|
40
|
+
)
|
|
41
|
+
.join(" ");
|
|
42
|
+
this.consoleOutput.push(`[ERROR] ${message}`);
|
|
43
|
+
this.tracer.emitEvent("console_error", { message, args });
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
warn: (...args: any[]) => {
|
|
47
|
+
const message = args
|
|
48
|
+
.map((arg) =>
|
|
49
|
+
typeof arg === "object" ? JSON.stringify(arg) : String(arg)
|
|
50
|
+
)
|
|
51
|
+
.join(" ");
|
|
52
|
+
this.consoleOutput.push(`[WARN] ${message}`);
|
|
53
|
+
this.tracer.emitEvent("console_warn", { message, args });
|
|
54
|
+
},
|
|
55
|
+
|
|
56
|
+
info: (...args: any[]) => {
|
|
57
|
+
const message = args
|
|
58
|
+
.map((arg) =>
|
|
59
|
+
typeof arg === "object" ? JSON.stringify(arg) : String(arg)
|
|
60
|
+
)
|
|
61
|
+
.join(" ");
|
|
62
|
+
this.consoleOutput.push(`[INFO] ${message}`);
|
|
63
|
+
this.tracer.emitEvent("console_info", { message, args });
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Call a tool through the tools service
|
|
69
|
+
*/
|
|
70
|
+
async callTool(toolName: string, parameters: any): Promise<any> {
|
|
71
|
+
// Check policy first
|
|
72
|
+
if (!this.policyEnforcer.checkToolCall(toolName)) {
|
|
73
|
+
throw new Error(`Tool call '${toolName}' blocked by policy`);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
this.tracer.emitEvent("tool_call_start", {
|
|
77
|
+
toolName,
|
|
78
|
+
parameters: this.sanitizeForLogging(parameters),
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
try {
|
|
82
|
+
// Record the tool call
|
|
83
|
+
this.policyEnforcer.recordToolCall();
|
|
84
|
+
|
|
85
|
+
// Create a proper ToolCall object
|
|
86
|
+
const toolCall = {
|
|
87
|
+
id: `script-tool-${Date.now()}-${Math.random()
|
|
88
|
+
.toString(36)
|
|
89
|
+
.substr(2, 9)}`,
|
|
90
|
+
type: "function" as const,
|
|
91
|
+
function: {
|
|
92
|
+
name: toolName,
|
|
93
|
+
arguments: JSON.stringify(parameters),
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
// Call the actual tool through the Tools service
|
|
98
|
+
const result = await this.toolsService.callTool(toolCall);
|
|
99
|
+
|
|
100
|
+
this.tracer.emitEvent("tool_call_success", {
|
|
101
|
+
toolName,
|
|
102
|
+
result: this.sanitizeForLogging(result),
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
return result;
|
|
106
|
+
} catch (error) {
|
|
107
|
+
this.tracer.emitEvent("tool_call_error", {
|
|
108
|
+
toolName,
|
|
109
|
+
error: error instanceof Error ? error.message : String(error),
|
|
110
|
+
});
|
|
111
|
+
throw error;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Call LLM through the clients service
|
|
117
|
+
*/
|
|
118
|
+
async llm(
|
|
119
|
+
messages: Message[],
|
|
120
|
+
options: {
|
|
121
|
+
model?: string;
|
|
122
|
+
maxTokens?: number;
|
|
123
|
+
temperature?: number;
|
|
124
|
+
} = {}
|
|
125
|
+
) {
|
|
126
|
+
const estimatedTokens = this.estimateTokens(messages);
|
|
127
|
+
|
|
128
|
+
// Check token quota
|
|
129
|
+
if (!this.policyEnforcer.checkTokenUsage(estimatedTokens)) {
|
|
130
|
+
throw new Error("Token quota would be exceeded");
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
this.tracer.emitEvent("llm_call_start", {
|
|
134
|
+
messageCount: messages.length,
|
|
135
|
+
estimatedTokens,
|
|
136
|
+
model: options.model,
|
|
137
|
+
options: this.sanitizeForLogging(options),
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
try {
|
|
141
|
+
// Record token usage
|
|
142
|
+
this.policyEnforcer.recordTokenUsage(estimatedTokens);
|
|
143
|
+
|
|
144
|
+
// Use the actual Clients service to make LLM calls
|
|
145
|
+
const completionOptions = {
|
|
146
|
+
model: options.model,
|
|
147
|
+
messages,
|
|
148
|
+
max_tokens: options.maxTokens,
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
// Detect provider from model or use default
|
|
152
|
+
const response = await this.clients.createCompletion(
|
|
153
|
+
"",
|
|
154
|
+
completionOptions
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
this.tracer.emitEvent("llm_call_success", {
|
|
158
|
+
model: response.model,
|
|
159
|
+
usage: response.usage,
|
|
160
|
+
usdCost: response.usd_cost,
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
return response;
|
|
164
|
+
} catch (error) {
|
|
165
|
+
this.tracer.emitEvent("llm_call_error", {
|
|
166
|
+
error: error instanceof Error ? error.message : String(error),
|
|
167
|
+
});
|
|
168
|
+
throw error;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Get current quota usage
|
|
174
|
+
*/
|
|
175
|
+
getQuotaUsage(): QuotaUsage {
|
|
176
|
+
return this.policyEnforcer.getUsage();
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Create an artifact
|
|
181
|
+
*/
|
|
182
|
+
async createArtifact(
|
|
183
|
+
name: string,
|
|
184
|
+
content: string,
|
|
185
|
+
type: "text" | "json" | "csv" | "html" | "markdown" = "text"
|
|
186
|
+
): Promise<Artifact> {
|
|
187
|
+
const artifact: Artifact = {
|
|
188
|
+
id: `artifact-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
|
|
189
|
+
name,
|
|
190
|
+
type,
|
|
191
|
+
content,
|
|
192
|
+
createdAt: new Date().toISOString(),
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
this.artifacts.push(artifact);
|
|
196
|
+
|
|
197
|
+
this.tracer.emitEvent("artifact_created", {
|
|
198
|
+
artifactId: artifact.id,
|
|
199
|
+
name,
|
|
200
|
+
type,
|
|
201
|
+
contentLength: content.length,
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
return artifact;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
async sleep(ms: number): Promise<void> {
|
|
208
|
+
if (typeof ms !== "number" || ms < 0 || ms > 2000) {
|
|
209
|
+
throw new Error("Invalid sleep duration");
|
|
210
|
+
}
|
|
211
|
+
await new Promise((res) => setTimeout(res, ms));
|
|
212
|
+
this.tracer.emitEvent("sleep", { durationMs: ms });
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Get all created artifacts
|
|
217
|
+
*/
|
|
218
|
+
getArtifacts(): Artifact[] {
|
|
219
|
+
return [...this.artifacts];
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Get console output
|
|
224
|
+
*/
|
|
225
|
+
getConsoleOutput(): string[] {
|
|
226
|
+
return [...this.consoleOutput];
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Estimate tokens for text (rough approximation)
|
|
231
|
+
*/
|
|
232
|
+
private estimateTokens(messages: any[]): number {
|
|
233
|
+
let totalText = "";
|
|
234
|
+
for (const message of messages) {
|
|
235
|
+
if (typeof message === "string") {
|
|
236
|
+
totalText += message;
|
|
237
|
+
} else if (message && typeof message.content === "string") {
|
|
238
|
+
totalText += message.content;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
// Rough estimation: ~4 characters per token
|
|
242
|
+
return Math.ceil(totalText.length / 4);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Sanitize data for logging (remove sensitive information)
|
|
247
|
+
*/
|
|
248
|
+
private sanitizeForLogging(data: any): any {
|
|
249
|
+
if (data === null || data === undefined) {
|
|
250
|
+
return data;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (typeof data === "string") {
|
|
254
|
+
// Truncate very long strings
|
|
255
|
+
return data.length > 500 ? data.substring(0, 500) + "..." : data;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if (typeof data === "object") {
|
|
259
|
+
const sanitized: any = {};
|
|
260
|
+
for (const [key, value] of Object.entries(data)) {
|
|
261
|
+
// Skip potentially sensitive keys
|
|
262
|
+
if (
|
|
263
|
+
key.toLowerCase().includes("password") ||
|
|
264
|
+
key.toLowerCase().includes("token") ||
|
|
265
|
+
key.toLowerCase().includes("secret") ||
|
|
266
|
+
key.toLowerCase().includes("key")
|
|
267
|
+
) {
|
|
268
|
+
sanitized[key] = "[REDACTED]";
|
|
269
|
+
} else {
|
|
270
|
+
sanitized[key] = this.sanitizeForLogging(value);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
return sanitized;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
return data;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
import ivm from "isolated-vm";
|
|
2
|
+
import { services, ToolsService } from "../../services";
|
|
3
|
+
import { AIClient, Clients } from "../../clients";
|
|
4
|
+
import { SandboxContext } from "./SandboxContext";
|
|
5
|
+
import { ScriptTracer } from "./ScriptTracer";
|
|
6
|
+
import { ScriptPolicyEnforcer } from "./ScriptPolicy";
|
|
7
|
+
import {
|
|
8
|
+
ExecutionRequest,
|
|
9
|
+
ExecutionResult,
|
|
10
|
+
ResourceQuotas,
|
|
11
|
+
SecurityPolicy,
|
|
12
|
+
ExecutionTrace,
|
|
13
|
+
} from "./types";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Executes TypeScript scripts in a secure sandbox environment
|
|
17
|
+
*/
|
|
18
|
+
export class ScriptExecutor {
|
|
19
|
+
private defaultQuotas: ResourceQuotas = {
|
|
20
|
+
maxToolCalls: 50,
|
|
21
|
+
maxTokens: 10000,
|
|
22
|
+
maxExecutionTimeMs: 30000, // 30 seconds
|
|
23
|
+
maxCostUsd: 1.0,
|
|
24
|
+
maxMemoryMb: 100,
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
private defaultPolicy: SecurityPolicy = {
|
|
28
|
+
allowlistedTools: [], // Empty means all tools allowed
|
|
29
|
+
denylistedTools: [
|
|
30
|
+
"execCommand", // Dangerous system commands
|
|
31
|
+
"writeFileChunk", // File system write access
|
|
32
|
+
"patchFile", // File system modification
|
|
33
|
+
],
|
|
34
|
+
maxScriptLength: 50000, // 50KB
|
|
35
|
+
allowNetworkAccess: false,
|
|
36
|
+
allowFileSystemAccess: false,
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
constructor(private toolsService: ToolsService, private clients: AIClient) {}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Execute a TypeScript script in sandbox
|
|
43
|
+
*/
|
|
44
|
+
async execute(request: ExecutionRequest): Promise<ExecutionResult> {
|
|
45
|
+
const tracer = new ScriptTracer();
|
|
46
|
+
const quotas = { ...this.defaultQuotas, ...request.quotas };
|
|
47
|
+
const policy = { ...this.defaultPolicy, ...request.policy };
|
|
48
|
+
const policyEnforcer = new ScriptPolicyEnforcer(quotas, policy);
|
|
49
|
+
|
|
50
|
+
tracer.emitEvent("execution_start", {
|
|
51
|
+
scriptLength: request.script.length,
|
|
52
|
+
quotas,
|
|
53
|
+
policy: {
|
|
54
|
+
...policy,
|
|
55
|
+
// Don't log the full tool lists
|
|
56
|
+
allowlistedTools: `${policy.allowlistedTools.length} tools`,
|
|
57
|
+
denylistedTools: `${policy.denylistedTools.length} tools`,
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
// Validate script
|
|
63
|
+
const validation = policyEnforcer.validateScript(request.script);
|
|
64
|
+
if (!validation.valid) {
|
|
65
|
+
tracer.emitEvent("script_validation_failed", {
|
|
66
|
+
issues: validation.issues,
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
success: false,
|
|
71
|
+
error: `Script validation failed: ${validation.issues.join(", ")}`,
|
|
72
|
+
result: null,
|
|
73
|
+
trace: tracer.getTrace(),
|
|
74
|
+
artifacts: [],
|
|
75
|
+
consoleOutput: [],
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
tracer.emitEvent("script_validation_passed", {});
|
|
80
|
+
|
|
81
|
+
// Create sandbox context
|
|
82
|
+
const context = new SandboxContext(
|
|
83
|
+
this.toolsService,
|
|
84
|
+
this.clients,
|
|
85
|
+
tracer,
|
|
86
|
+
policyEnforcer
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
// Execute script with timeout
|
|
90
|
+
const startTime = Date.now();
|
|
91
|
+
const timeoutMs = quotas.maxExecutionTimeMs;
|
|
92
|
+
|
|
93
|
+
const result = await this.executeWithTimeout(
|
|
94
|
+
request.script,
|
|
95
|
+
context,
|
|
96
|
+
timeoutMs,
|
|
97
|
+
tracer,
|
|
98
|
+
policyEnforcer
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
const executionTime = Date.now() - startTime;
|
|
102
|
+
tracer.emitEvent("execution_complete", {
|
|
103
|
+
executionTimeMs: executionTime,
|
|
104
|
+
finalUsage: policyEnforcer.getUsage(),
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
return {
|
|
108
|
+
success: true,
|
|
109
|
+
error: null,
|
|
110
|
+
result,
|
|
111
|
+
trace: tracer.getTrace(),
|
|
112
|
+
artifacts: context.getArtifacts(),
|
|
113
|
+
consoleOutput: context.getConsoleOutput(),
|
|
114
|
+
};
|
|
115
|
+
} catch (error) {
|
|
116
|
+
const errorMessage =
|
|
117
|
+
error instanceof Error ? error.message : String(error);
|
|
118
|
+
|
|
119
|
+
tracer.emitEvent("execution_error", {
|
|
120
|
+
error: errorMessage,
|
|
121
|
+
finalUsage: policyEnforcer.getUsage(),
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
return {
|
|
125
|
+
success: false,
|
|
126
|
+
error: errorMessage,
|
|
127
|
+
result: null,
|
|
128
|
+
trace: tracer.getTrace(),
|
|
129
|
+
artifacts: [],
|
|
130
|
+
consoleOutput: [],
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Execute script with timeout protection
|
|
137
|
+
*/
|
|
138
|
+
private async executeWithTimeout(
|
|
139
|
+
script: string,
|
|
140
|
+
context: SandboxContext,
|
|
141
|
+
timeoutMs: number,
|
|
142
|
+
tracer: ScriptTracer,
|
|
143
|
+
policyEnforcer: ScriptPolicyEnforcer
|
|
144
|
+
): Promise<any> {
|
|
145
|
+
return new Promise((resolve, reject) => {
|
|
146
|
+
const timeoutId = setTimeout(() => {
|
|
147
|
+
tracer.emitEvent("execution_timeout", { timeoutMs });
|
|
148
|
+
reject(new Error(`Script execution timed out after ${timeoutMs}ms`));
|
|
149
|
+
}, timeoutMs);
|
|
150
|
+
|
|
151
|
+
// Use isolated-vm for secure execution
|
|
152
|
+
this.executeScriptSecure(script, context, tracer, policyEnforcer)
|
|
153
|
+
.then((result) => {
|
|
154
|
+
clearTimeout(timeoutId);
|
|
155
|
+
resolve(result);
|
|
156
|
+
})
|
|
157
|
+
.catch((error) => {
|
|
158
|
+
clearTimeout(timeoutId);
|
|
159
|
+
reject(error);
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Secure script execution using isolated-vm
|
|
166
|
+
*/
|
|
167
|
+
private async executeScriptSecure(
|
|
168
|
+
script: string,
|
|
169
|
+
context: SandboxContext,
|
|
170
|
+
tracer: ScriptTracer,
|
|
171
|
+
policyEnforcer: ScriptPolicyEnforcer
|
|
172
|
+
) {
|
|
173
|
+
tracer.emitEvent("secure_execution_start", {
|
|
174
|
+
note: "Using isolated-vm for secure execution",
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
// Create isolated VM instance with memory limit
|
|
178
|
+
const isolate = new ivm.Isolate({
|
|
179
|
+
memoryLimit: policyEnforcer.getQuotas().maxMemoryMb,
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
try {
|
|
183
|
+
// Create new context within the isolate
|
|
184
|
+
const vmContext = await isolate.createContext();
|
|
185
|
+
|
|
186
|
+
tracer.emitEvent("vm_context_created", {});
|
|
187
|
+
|
|
188
|
+
// Set up the global environment in the isolated context
|
|
189
|
+
await this.setupIsolatedContext(vmContext, context, tracer);
|
|
190
|
+
|
|
191
|
+
tracer.emitEvent("script_compilation_start", {});
|
|
192
|
+
|
|
193
|
+
// Compile the script
|
|
194
|
+
const wrappedScript = `
|
|
195
|
+
(async function() {
|
|
196
|
+
"use strict";
|
|
197
|
+
${script}
|
|
198
|
+
})()
|
|
199
|
+
`;
|
|
200
|
+
|
|
201
|
+
const compiledScript = await isolate.compileScript(wrappedScript);
|
|
202
|
+
|
|
203
|
+
tracer.emitEvent("script_compilation_complete", {});
|
|
204
|
+
tracer.emitEvent("script_execution_start", {});
|
|
205
|
+
|
|
206
|
+
// Execute the script and get the result
|
|
207
|
+
const result = await compiledScript.run(vmContext, {
|
|
208
|
+
timeout: policyEnforcer.getQuotas().maxExecutionTimeMs,
|
|
209
|
+
promise: true,
|
|
210
|
+
copy: true,
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
tracer.emitEvent("script_execution_complete", {
|
|
214
|
+
resultType: typeof result,
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
return result;
|
|
218
|
+
} finally {
|
|
219
|
+
// Clean up the isolate
|
|
220
|
+
isolate.dispose();
|
|
221
|
+
tracer.emitEvent("vm_cleanup_complete", {});
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Set up the isolated context with safe globals and sandbox functions
|
|
227
|
+
*/
|
|
228
|
+
private async setupIsolatedContext(
|
|
229
|
+
vmContext: ivm.Context,
|
|
230
|
+
sandboxContext: SandboxContext,
|
|
231
|
+
tracer: ScriptTracer
|
|
232
|
+
): Promise<void> {
|
|
233
|
+
tracer.emitEvent("context_setup_start", {});
|
|
234
|
+
|
|
235
|
+
const globalRef = vmContext.global;
|
|
236
|
+
await globalRef.set("globalThis", globalRef.derefInto());
|
|
237
|
+
|
|
238
|
+
// Helper function to expose async host functions
|
|
239
|
+
const exposeAsync = async (
|
|
240
|
+
name: string,
|
|
241
|
+
fn: (...a: any[]) => Promise<any>
|
|
242
|
+
) => {
|
|
243
|
+
await globalRef.set(
|
|
244
|
+
`__host_${name}`,
|
|
245
|
+
new ivm.Reference(async (...args: any[]) => {
|
|
246
|
+
const result = await fn(...args);
|
|
247
|
+
return new ivm.ExternalCopy(result).copyInto();
|
|
248
|
+
})
|
|
249
|
+
);
|
|
250
|
+
await vmContext.eval(`
|
|
251
|
+
globalThis.${name} = (...a) =>
|
|
252
|
+
__host_${name}.apply(undefined, a,
|
|
253
|
+
{ arguments: { copy: true }, result: { promise: true, copy: true } });
|
|
254
|
+
`);
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
// Helper function to expose sync host functions
|
|
258
|
+
const exposeSync = async (name: string, fn: (...a: any[]) => any) => {
|
|
259
|
+
await globalRef.set(
|
|
260
|
+
`__host_${name}`,
|
|
261
|
+
new ivm.Reference((...args: any[]) => {
|
|
262
|
+
const result = fn(...args);
|
|
263
|
+
return new ivm.ExternalCopy(result).copyInto();
|
|
264
|
+
})
|
|
265
|
+
);
|
|
266
|
+
await vmContext.eval(`
|
|
267
|
+
globalThis.${name} = (...a) =>
|
|
268
|
+
__host_${name}.apply(undefined, a,
|
|
269
|
+
{ arguments: { copy: true }, result: { copy: true } });
|
|
270
|
+
`);
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
// Expose async sandbox functions
|
|
274
|
+
await exposeAsync("callTool", async (tool, params) => {
|
|
275
|
+
const { functionResp } = await sandboxContext.callTool(
|
|
276
|
+
tool as string,
|
|
277
|
+
params
|
|
278
|
+
);
|
|
279
|
+
return functionResp;
|
|
280
|
+
});
|
|
281
|
+
await exposeAsync("llm", (messages, options) =>
|
|
282
|
+
sandboxContext.llm(messages, options || {})
|
|
283
|
+
);
|
|
284
|
+
await exposeAsync("sleep", (ms) => sandboxContext.sleep(ms));
|
|
285
|
+
|
|
286
|
+
// Expose sync sandbox functions
|
|
287
|
+
await exposeSync("createArtifact", (name, content, type) =>
|
|
288
|
+
sandboxContext.createArtifact(name as string, content, type)
|
|
289
|
+
);
|
|
290
|
+
await exposeSync("getQuotaUsage", () => sandboxContext.getQuotaUsage());
|
|
291
|
+
|
|
292
|
+
// Set up console bridging with individual function references
|
|
293
|
+
for (const level of ["log", "info", "warn", "error"] as const) {
|
|
294
|
+
await globalRef.set(
|
|
295
|
+
`__console_${level}`,
|
|
296
|
+
new ivm.Reference((...args: any[]) =>
|
|
297
|
+
sandboxContext.console[level](...args)
|
|
298
|
+
)
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
await vmContext.eval(`
|
|
302
|
+
globalThis.console = {};
|
|
303
|
+
for (const lvl of ["log", "info", "warn", "error"]) {
|
|
304
|
+
globalThis.console[lvl] = (...a) =>
|
|
305
|
+
globalThis["__console_" + lvl].apply(undefined, a,
|
|
306
|
+
{ arguments: { copy: true } });
|
|
307
|
+
}
|
|
308
|
+
`);
|
|
309
|
+
|
|
310
|
+
tracer.emitEvent("context_setup_complete", {});
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Legacy fallback execution method
|
|
315
|
+
*/
|
|
316
|
+
private async executeScriptFallback(
|
|
317
|
+
script: string,
|
|
318
|
+
context: SandboxContext,
|
|
319
|
+
tracer: ScriptTracer,
|
|
320
|
+
policyEnforcer: ScriptPolicyEnforcer
|
|
321
|
+
): Promise<any> {
|
|
322
|
+
// This is a fallback method that could use vm2 or other sandboxing
|
|
323
|
+
throw new Error("Isolated-vm execution failed, no fallback available");
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Get default quotas
|
|
328
|
+
*/
|
|
329
|
+
getDefaultQuotas(): ResourceQuotas {
|
|
330
|
+
return { ...this.defaultQuotas };
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Get default policy
|
|
335
|
+
*/
|
|
336
|
+
getDefaultPolicy(): SecurityPolicy {
|
|
337
|
+
return { ...this.defaultPolicy };
|
|
338
|
+
}
|
|
339
|
+
}
|