@polos/sdk 0.1.1 → 0.1.2
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/dist/index.cjs +1087 -33
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +558 -3
- package/dist/index.d.ts +558 -3
- package/dist/index.js +1053 -34
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -8,10 +8,34 @@ var Fastify = require('fastify');
|
|
|
8
8
|
var api = require('@opentelemetry/api');
|
|
9
9
|
var sdkTraceBase = require('@opentelemetry/sdk-trace-base');
|
|
10
10
|
var contextAsyncHooks = require('@opentelemetry/context-async-hooks');
|
|
11
|
+
var zod = require('zod');
|
|
12
|
+
var child_process = require('child_process');
|
|
13
|
+
var fs2 = require('fs/promises');
|
|
14
|
+
var path = require('path');
|
|
11
15
|
|
|
12
16
|
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
13
17
|
|
|
18
|
+
function _interopNamespace(e) {
|
|
19
|
+
if (e && e.__esModule) return e;
|
|
20
|
+
var n = Object.create(null);
|
|
21
|
+
if (e) {
|
|
22
|
+
Object.keys(e).forEach(function (k) {
|
|
23
|
+
if (k !== 'default') {
|
|
24
|
+
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
25
|
+
Object.defineProperty(n, k, d.get ? d : {
|
|
26
|
+
enumerable: true,
|
|
27
|
+
get: function () { return e[k]; }
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
n.default = e;
|
|
33
|
+
return Object.freeze(n);
|
|
34
|
+
}
|
|
35
|
+
|
|
14
36
|
var Fastify__default = /*#__PURE__*/_interopDefault(Fastify);
|
|
37
|
+
var fs2__namespace = /*#__PURE__*/_interopNamespace(fs2);
|
|
38
|
+
var path__namespace = /*#__PURE__*/_interopNamespace(path);
|
|
15
39
|
|
|
16
40
|
// src/core/registry.ts
|
|
17
41
|
var WorkflowNotFoundError = class extends Error {
|
|
@@ -212,6 +236,12 @@ var OrchestratorClient = class {
|
|
|
212
236
|
this.timeout = config.timeout ?? 3e4;
|
|
213
237
|
this.maxRetries = config.maxRetries ?? 3;
|
|
214
238
|
}
|
|
239
|
+
/**
|
|
240
|
+
* Get the API URL.
|
|
241
|
+
*/
|
|
242
|
+
getApiUrl() {
|
|
243
|
+
return this.apiUrl;
|
|
244
|
+
}
|
|
215
245
|
/**
|
|
216
246
|
* Get the project ID.
|
|
217
247
|
*/
|
|
@@ -235,8 +265,8 @@ var OrchestratorClient = class {
|
|
|
235
265
|
/**
|
|
236
266
|
* Make an HTTP request with retry logic.
|
|
237
267
|
*/
|
|
238
|
-
async request(method,
|
|
239
|
-
const url = `${this.apiUrl}${
|
|
268
|
+
async request(method, path3, options) {
|
|
269
|
+
const url = `${this.apiUrl}${path3}`;
|
|
240
270
|
const retries = options?.retries ?? this.maxRetries;
|
|
241
271
|
let lastError;
|
|
242
272
|
for (let attempt = 0; attempt <= retries; attempt++) {
|
|
@@ -288,7 +318,7 @@ var OrchestratorClient = class {
|
|
|
288
318
|
}
|
|
289
319
|
if (attempt < retries) {
|
|
290
320
|
const delay = Math.min(1e3 * Math.pow(2, attempt), 16e3);
|
|
291
|
-
await new Promise((
|
|
321
|
+
await new Promise((resolve8) => setTimeout(resolve8, delay));
|
|
292
322
|
}
|
|
293
323
|
}
|
|
294
324
|
}
|
|
@@ -727,7 +757,7 @@ var OrchestratorClient = class {
|
|
|
727
757
|
if (execution.status === "completed" || execution.status === "failed" || execution.status === "cancelled") {
|
|
728
758
|
return execution;
|
|
729
759
|
}
|
|
730
|
-
await new Promise((
|
|
760
|
+
await new Promise((resolve8) => setTimeout(resolve8, pollInterval));
|
|
731
761
|
}
|
|
732
762
|
throw new Error(`Execution ${executionId} timed out after ${String(timeout)}ms`);
|
|
733
763
|
}
|
|
@@ -1193,6 +1223,46 @@ function isToolWorkflow(workflow) {
|
|
|
1193
1223
|
}
|
|
1194
1224
|
function defineTool(config, handler) {
|
|
1195
1225
|
const toolParameters = config.inputSchema ? zodToJsonSchema.zodToJsonSchema(config.inputSchema, { target: "openApi3" }) : { type: "object", properties: {} };
|
|
1226
|
+
const effectiveHandler = config.approval === "always" ? async (ctx, input) => {
|
|
1227
|
+
const approvalId = await ctx.step.uuid("_approval_id");
|
|
1228
|
+
const response = await ctx.step.suspend(
|
|
1229
|
+
`approve_${config.id}_${approvalId}`,
|
|
1230
|
+
{
|
|
1231
|
+
data: {
|
|
1232
|
+
_form: {
|
|
1233
|
+
title: `Approve tool: ${config.id}`,
|
|
1234
|
+
description: `The agent wants to use the "${config.id}" tool.`,
|
|
1235
|
+
fields: [
|
|
1236
|
+
{
|
|
1237
|
+
key: "approved",
|
|
1238
|
+
type: "boolean",
|
|
1239
|
+
label: "Approve this tool call?",
|
|
1240
|
+
required: true,
|
|
1241
|
+
default: false
|
|
1242
|
+
},
|
|
1243
|
+
{
|
|
1244
|
+
key: "feedback",
|
|
1245
|
+
type: "textarea",
|
|
1246
|
+
label: "Feedback for the agent (optional)",
|
|
1247
|
+
description: "If rejecting, tell the agent what to do instead.",
|
|
1248
|
+
required: false
|
|
1249
|
+
}
|
|
1250
|
+
],
|
|
1251
|
+
context: { tool: config.id, input }
|
|
1252
|
+
},
|
|
1253
|
+
_source: "tool_approval",
|
|
1254
|
+
_tool: config.id
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1257
|
+
);
|
|
1258
|
+
if (response.data?.approved !== true) {
|
|
1259
|
+
const feedback = response.data?.feedback;
|
|
1260
|
+
throw new Error(
|
|
1261
|
+
`Tool "${config.id}" was rejected by the user.${feedback ? ` Feedback: ${feedback}` : ""}`
|
|
1262
|
+
);
|
|
1263
|
+
}
|
|
1264
|
+
return handler(ctx, input);
|
|
1265
|
+
} : handler;
|
|
1196
1266
|
const workflow = defineWorkflow(
|
|
1197
1267
|
{
|
|
1198
1268
|
id: config.id,
|
|
@@ -1205,7 +1275,8 @@ function defineTool(config, handler) {
|
|
|
1205
1275
|
onStart: config.onStart,
|
|
1206
1276
|
onEnd: config.onEnd
|
|
1207
1277
|
},
|
|
1208
|
-
|
|
1278
|
+
effectiveHandler,
|
|
1279
|
+
config.autoRegister === void 0 ? void 0 : { autoRegister: config.autoRegister }
|
|
1209
1280
|
);
|
|
1210
1281
|
const toolWorkflow = Object.assign(workflow, {
|
|
1211
1282
|
toolDescription: config.description,
|
|
@@ -1249,7 +1320,7 @@ function calculateDelay(attempt, options) {
|
|
|
1249
1320
|
return Math.round(delay);
|
|
1250
1321
|
}
|
|
1251
1322
|
function sleep(ms) {
|
|
1252
|
-
return new Promise((
|
|
1323
|
+
return new Promise((resolve8) => setTimeout(resolve8, ms));
|
|
1253
1324
|
}
|
|
1254
1325
|
async function retry(fn, options = {}) {
|
|
1255
1326
|
const opts = { ...DEFAULT_OPTIONS, ...options };
|
|
@@ -1438,7 +1509,7 @@ function createStepHelper(options) {
|
|
|
1438
1509
|
if (options2.days) totalSeconds += options2.days * 86400;
|
|
1439
1510
|
if (options2.weeks) totalSeconds += options2.weeks * 604800;
|
|
1440
1511
|
const totalMs = totalSeconds * 1e3;
|
|
1441
|
-
await new Promise((
|
|
1512
|
+
await new Promise((resolve8) => setTimeout(resolve8, totalMs));
|
|
1442
1513
|
store.set(key, { wait_until: new Date(Date.now() + totalMs).toISOString() });
|
|
1443
1514
|
},
|
|
1444
1515
|
async waitUntil(key, date) {
|
|
@@ -1446,7 +1517,7 @@ function createStepHelper(options) {
|
|
|
1446
1517
|
if (cached) return;
|
|
1447
1518
|
const now = Date.now();
|
|
1448
1519
|
const waitMs = Math.max(0, date.getTime() - now);
|
|
1449
|
-
await new Promise((
|
|
1520
|
+
await new Promise((resolve8) => setTimeout(resolve8, waitMs));
|
|
1450
1521
|
store.set(key, { wait_until: date.toISOString() });
|
|
1451
1522
|
},
|
|
1452
1523
|
// eslint-disable-next-line @typescript-eslint/require-await -- stub implementation
|
|
@@ -2518,7 +2589,6 @@ async function agentStreamFunction(ctx, payload, agentDef) {
|
|
|
2518
2589
|
const allToolResults = [];
|
|
2519
2590
|
const steps = [];
|
|
2520
2591
|
let endSteps = false;
|
|
2521
|
-
let toolResults;
|
|
2522
2592
|
let checkedStructuredOutput = false;
|
|
2523
2593
|
let cachedHistoryMessages = null;
|
|
2524
2594
|
if (agentDef.conversationHistory > 0 && conversationId) {
|
|
@@ -2559,7 +2629,7 @@ async function agentStreamFunction(ctx, payload, agentDef) {
|
|
|
2559
2629
|
break;
|
|
2560
2630
|
}
|
|
2561
2631
|
}
|
|
2562
|
-
const safetyMaxSteps = hasMaxStepsCondition ? null : parseInt(process.env["POLOS_AGENT_MAX_STEPS"] ?? "
|
|
2632
|
+
const safetyMaxSteps = hasMaxStepsCondition ? null : parseInt(process.env["POLOS_AGENT_MAX_STEPS"] ?? "20", 10);
|
|
2563
2633
|
const execCtxForPublish = getExecutionContext();
|
|
2564
2634
|
const publishEvent = async (topic, eventData) => {
|
|
2565
2635
|
if (execCtxForPublish?.orchestratorClient) {
|
|
@@ -2608,7 +2678,6 @@ async function agentStreamFunction(ctx, payload, agentDef) {
|
|
|
2608
2678
|
agent_step: agentStep,
|
|
2609
2679
|
guardrails,
|
|
2610
2680
|
guardrail_max_retries: guardrailMaxRetries,
|
|
2611
|
-
tool_results: toolResults,
|
|
2612
2681
|
outputSchema: outputSchemaForLlm
|
|
2613
2682
|
});
|
|
2614
2683
|
if (guardrails.length > 0 && streaming && llmResult.content) {
|
|
@@ -2637,13 +2706,11 @@ async function agentStreamFunction(ctx, payload, agentDef) {
|
|
|
2637
2706
|
temperature: agentConfig.temperature,
|
|
2638
2707
|
maxTokens: agentConfig.maxOutputTokens,
|
|
2639
2708
|
agent_step: agentStep,
|
|
2640
|
-
tool_results: toolResults,
|
|
2641
2709
|
outputSchema: outputSchemaForLlm
|
|
2642
2710
|
},
|
|
2643
2711
|
publishEvent
|
|
2644
2712
|
);
|
|
2645
2713
|
}
|
|
2646
|
-
toolResults = void 0;
|
|
2647
2714
|
if (llmResult.usage) {
|
|
2648
2715
|
finalInputTokens += llmResult.usage.input_tokens;
|
|
2649
2716
|
finalOutputTokens += llmResult.usage.output_tokens;
|
|
@@ -2656,6 +2723,7 @@ async function agentStreamFunction(ctx, payload, agentDef) {
|
|
|
2656
2723
|
`LLM failed to generate output: agent_id=${agentDef.id}, agent_step=${String(agentStep)}`
|
|
2657
2724
|
);
|
|
2658
2725
|
}
|
|
2726
|
+
conversationMessages.push(...llmResult.raw_output);
|
|
2659
2727
|
const batchWorkflows = [];
|
|
2660
2728
|
const toolCallList = [];
|
|
2661
2729
|
let toolResultsRecordedList = [];
|
|
@@ -2716,7 +2784,7 @@ async function agentStreamFunction(ctx, payload, agentDef) {
|
|
|
2716
2784
|
const toolSpec = batchWorkflows[i];
|
|
2717
2785
|
const toolCallInfo = toolCallList[i];
|
|
2718
2786
|
const toolName = toolCallInfo.tool_name;
|
|
2719
|
-
const toolResult = batchToolResult
|
|
2787
|
+
const toolResult = batchToolResult.success ? batchToolResult.result : `Error: ${batchToolResult.error ?? "unknown error"}`;
|
|
2720
2788
|
const toolCallId = toolCallInfo.tool_call_id;
|
|
2721
2789
|
const toolCallCallId = toolCallInfo.tool_call_call_id;
|
|
2722
2790
|
if (agentDef.agentHooks.onToolEnd.length > 0) {
|
|
@@ -2747,7 +2815,8 @@ async function agentStreamFunction(ctx, payload, agentDef) {
|
|
|
2747
2815
|
});
|
|
2748
2816
|
}
|
|
2749
2817
|
allToolResults.push(...toolResultsRecordedList);
|
|
2750
|
-
|
|
2818
|
+
const toolResultMessages = convertToolResultsToMessages(currentIterationToolResults);
|
|
2819
|
+
conversationMessages.push(...toolResultMessages);
|
|
2751
2820
|
}
|
|
2752
2821
|
steps.push({
|
|
2753
2822
|
step: agentStep,
|
|
@@ -2755,7 +2824,7 @@ async function agentStreamFunction(ctx, payload, agentDef) {
|
|
|
2755
2824
|
tool_calls: toolCalls,
|
|
2756
2825
|
tool_results: toolResultsRecordedList,
|
|
2757
2826
|
usage: llmResult.usage,
|
|
2758
|
-
raw_output:
|
|
2827
|
+
raw_output: conversationMessages
|
|
2759
2828
|
});
|
|
2760
2829
|
if (agentDef.agentHooks.onAgentStepEnd.length > 0) {
|
|
2761
2830
|
const hookResult = await executeHookChain(agentDef.agentHooks.onAgentStepEnd, {
|
|
@@ -2772,7 +2841,7 @@ async function agentStreamFunction(ctx, payload, agentDef) {
|
|
|
2772
2841
|
throw new Error(hookResult.error ?? "on_agent_step_end hook failed");
|
|
2773
2842
|
}
|
|
2774
2843
|
}
|
|
2775
|
-
if (
|
|
2844
|
+
if (currentIterationToolResults.length === 0) {
|
|
2776
2845
|
endSteps = true;
|
|
2777
2846
|
}
|
|
2778
2847
|
if (stopConditions.length > 0 && !endSteps) {
|
|
@@ -2805,7 +2874,6 @@ async function agentStreamFunction(ctx, payload, agentDef) {
|
|
|
2805
2874
|
checkedStructuredOutput = true;
|
|
2806
2875
|
if (!parseResult.success) {
|
|
2807
2876
|
endSteps = false;
|
|
2808
|
-
conversationMessages = llmResult.raw_output;
|
|
2809
2877
|
const schemaJson = JSON.stringify(agentDef.outputSchema, null, 2);
|
|
2810
2878
|
const fixPrompt = `The previous response was not valid JSON matching the required schema. Please reformat your response to be valid JSON that strictly conforms to this schema:
|
|
2811
2879
|
|
|
@@ -2818,9 +2886,6 @@ Please provide ONLY valid JSON that matches the schema, with no additional text
|
|
|
2818
2886
|
}
|
|
2819
2887
|
}
|
|
2820
2888
|
if (!endSteps) {
|
|
2821
|
-
if (!checkedStructuredOutput) {
|
|
2822
|
-
conversationMessages = llmResult.raw_output;
|
|
2823
|
-
}
|
|
2824
2889
|
agentStep++;
|
|
2825
2890
|
}
|
|
2826
2891
|
}
|
|
@@ -3034,7 +3099,7 @@ function defineAgent(config) {
|
|
|
3034
3099
|
maxOutputTokens: config.maxOutputTokens
|
|
3035
3100
|
},
|
|
3036
3101
|
input,
|
|
3037
|
-
streaming: streamingFlag ??
|
|
3102
|
+
streaming: streamingFlag ?? false,
|
|
3038
3103
|
conversation_id: conversationIdValue
|
|
3039
3104
|
};
|
|
3040
3105
|
return agentStreamFunction(ctx, streamPayload, {
|
|
@@ -4091,16 +4156,27 @@ function createOrchestratorStepHelper(orchestratorClient, cachedSteps, execCtx,
|
|
|
4091
4156
|
if (items.length === 0) return [];
|
|
4092
4157
|
const existing = checkExistingStep(key);
|
|
4093
4158
|
if (existing) {
|
|
4094
|
-
|
|
4159
|
+
let raw;
|
|
4160
|
+
try {
|
|
4161
|
+
raw = handleExistingStep(existing);
|
|
4162
|
+
} catch {
|
|
4163
|
+
raw = existing.outputs;
|
|
4164
|
+
}
|
|
4095
4165
|
if (Array.isArray(raw)) {
|
|
4096
4166
|
return raw.map((item) => {
|
|
4097
|
-
if (item && typeof item === "object" && "
|
|
4098
|
-
|
|
4167
|
+
if (item && typeof item === "object" && "success" in item) {
|
|
4168
|
+
const rec = item;
|
|
4169
|
+
return {
|
|
4170
|
+
workflowId: typeof rec["workflow_id"] === "string" ? rec["workflow_id"] : "",
|
|
4171
|
+
success: rec["success"] === true,
|
|
4172
|
+
result: rec["result"] ?? null,
|
|
4173
|
+
error: typeof rec["error"] === "string" ? rec["error"] : null
|
|
4174
|
+
};
|
|
4099
4175
|
}
|
|
4100
|
-
return item;
|
|
4176
|
+
return { workflowId: "", success: true, result: item, error: null };
|
|
4101
4177
|
});
|
|
4102
4178
|
}
|
|
4103
|
-
return raw;
|
|
4179
|
+
return raw.map((r) => ({ workflowId: "", success: true, result: r, error: null }));
|
|
4104
4180
|
}
|
|
4105
4181
|
const workflows = items.map((item) => {
|
|
4106
4182
|
const workflowId = typeof item.workflow === "string" ? item.workflow : item.workflow.id;
|
|
@@ -4163,7 +4239,7 @@ function createOrchestratorStepHelper(orchestratorClient, cachedSteps, execCtx,
|
|
|
4163
4239
|
});
|
|
4164
4240
|
const waitThreshold = Number(process.env["POLOS_WAIT_THRESHOLD_SECONDS"] ?? "10");
|
|
4165
4241
|
if (totalSeconds <= waitThreshold) {
|
|
4166
|
-
await new Promise((
|
|
4242
|
+
await new Promise((resolve8) => setTimeout(resolve8, totalSeconds * 1e3));
|
|
4167
4243
|
await saveStepOutput(key, { wait_until: waitUntil.toISOString() });
|
|
4168
4244
|
return;
|
|
4169
4245
|
}
|
|
@@ -4199,7 +4275,7 @@ function createOrchestratorStepHelper(orchestratorClient, cachedSteps, execCtx,
|
|
|
4199
4275
|
});
|
|
4200
4276
|
const waitThreshold = Number(process.env["POLOS_WAIT_THRESHOLD_SECONDS"] ?? "10");
|
|
4201
4277
|
if (waitSeconds <= waitThreshold) {
|
|
4202
|
-
await new Promise((
|
|
4278
|
+
await new Promise((resolve8) => setTimeout(resolve8, waitSeconds * 1e3));
|
|
4203
4279
|
await saveStepOutput(key, { wait_until: date.toISOString() });
|
|
4204
4280
|
return;
|
|
4205
4281
|
}
|
|
@@ -4273,12 +4349,14 @@ function createOrchestratorStepHelper(orchestratorClient, cachedSteps, execCtx,
|
|
|
4273
4349
|
return handleExistingStep(existing);
|
|
4274
4350
|
}
|
|
4275
4351
|
const topic = `workflow/${execCtx.rootWorkflowId}/${execCtx.rootExecutionId}`;
|
|
4352
|
+
const approvalUrl = `${orchestratorClient.getApiUrl()}/approve/${execCtx.rootExecutionId}/${key}`;
|
|
4353
|
+
const eventData = options?.data != null && typeof options.data === "object" && !Array.isArray(options.data) ? { ...options.data, _approval_url: approvalUrl } : { _original: options?.data, _approval_url: approvalUrl };
|
|
4276
4354
|
await orchestratorClient.publishEvent({
|
|
4277
4355
|
topic,
|
|
4278
4356
|
events: [
|
|
4279
4357
|
{
|
|
4280
4358
|
eventType: `suspend_${key}`,
|
|
4281
|
-
data:
|
|
4359
|
+
data: eventData
|
|
4282
4360
|
}
|
|
4283
4361
|
],
|
|
4284
4362
|
executionId: execCtx.executionId,
|
|
@@ -4941,10 +5019,10 @@ var Worker = class {
|
|
|
4941
5019
|
process.on("SIGINT", signalHandler);
|
|
4942
5020
|
process.on("SIGTERM", signalHandler);
|
|
4943
5021
|
this.signalHandler = signalHandler;
|
|
4944
|
-
await new Promise((
|
|
5022
|
+
await new Promise((resolve8) => {
|
|
4945
5023
|
const checkState = () => {
|
|
4946
5024
|
if (this.state === "stopping" || this.state === "stopped") {
|
|
4947
|
-
|
|
5025
|
+
resolve8();
|
|
4948
5026
|
} else {
|
|
4949
5027
|
setTimeout(checkState, 100);
|
|
4950
5028
|
}
|
|
@@ -4983,7 +5061,7 @@ var Worker = class {
|
|
|
4983
5061
|
const waitTimeout = 3e4;
|
|
4984
5062
|
const waitStart = Date.now();
|
|
4985
5063
|
while (this.activeExecutions.size > 0 && Date.now() - waitStart < waitTimeout) {
|
|
4986
|
-
await new Promise((
|
|
5064
|
+
await new Promise((resolve8) => setTimeout(resolve8, 100));
|
|
4987
5065
|
}
|
|
4988
5066
|
if (this.activeExecutions.size > 0) {
|
|
4989
5067
|
logger6.warn(`${String(this.activeExecutions.size)} executions did not complete in time`);
|
|
@@ -5539,8 +5617,969 @@ var GuardrailResult2 = {
|
|
|
5539
5617
|
error
|
|
5540
5618
|
})
|
|
5541
5619
|
};
|
|
5620
|
+
var askUserInputSchema = zod.z.object({
|
|
5621
|
+
question: zod.z.string().describe("The question to ask the user"),
|
|
5622
|
+
title: zod.z.string().optional().describe("Short title for the question (shown as heading)"),
|
|
5623
|
+
fields: zod.z.array(
|
|
5624
|
+
zod.z.object({
|
|
5625
|
+
key: zod.z.string().describe("Unique key for this field"),
|
|
5626
|
+
type: zod.z.enum(["text", "textarea", "number", "boolean", "select"]).describe("Field type"),
|
|
5627
|
+
label: zod.z.string().describe("Label shown to user"),
|
|
5628
|
+
description: zod.z.string().optional().describe("Help text for the field"),
|
|
5629
|
+
required: zod.z.boolean().optional().describe("Whether this field is required"),
|
|
5630
|
+
options: zod.z.array(
|
|
5631
|
+
zod.z.object({
|
|
5632
|
+
label: zod.z.string(),
|
|
5633
|
+
value: zod.z.string()
|
|
5634
|
+
})
|
|
5635
|
+
).optional().describe("Options for select fields")
|
|
5636
|
+
})
|
|
5637
|
+
).optional().describe(
|
|
5638
|
+
"Structured form fields for the response. If omitted, shows a single text response field."
|
|
5639
|
+
)
|
|
5640
|
+
});
|
|
5641
|
+
function createAskUserTool() {
|
|
5642
|
+
return defineTool(
|
|
5643
|
+
{
|
|
5644
|
+
id: "ask_user",
|
|
5645
|
+
description: "Ask the user a question and wait for their response. Use this when you need clarification, a decision, or any input from the user. You can define structured fields (text, select, boolean, etc.) for specific response formats, or omit fields for a free-text response.",
|
|
5646
|
+
inputSchema: askUserInputSchema
|
|
5647
|
+
},
|
|
5648
|
+
async (ctx, input) => {
|
|
5649
|
+
const fields = input.fields ?? [
|
|
5650
|
+
{
|
|
5651
|
+
key: "response",
|
|
5652
|
+
type: "textarea",
|
|
5653
|
+
label: input.question,
|
|
5654
|
+
required: true
|
|
5655
|
+
}
|
|
5656
|
+
];
|
|
5657
|
+
const askId = await ctx.step.uuid("_ask_user_id");
|
|
5658
|
+
const response = await ctx.step.suspend(`ask_user_${askId}`, {
|
|
5659
|
+
data: {
|
|
5660
|
+
_form: {
|
|
5661
|
+
title: input.title ?? "Agent Question",
|
|
5662
|
+
description: input.question,
|
|
5663
|
+
fields
|
|
5664
|
+
},
|
|
5665
|
+
_source: "ask_user",
|
|
5666
|
+
_tool: "ask_user"
|
|
5667
|
+
}
|
|
5668
|
+
});
|
|
5669
|
+
return response?.["data"] ?? {};
|
|
5670
|
+
}
|
|
5671
|
+
);
|
|
5672
|
+
}
|
|
5673
|
+
var webSearchInputSchema = zod.z.object({
|
|
5674
|
+
query: zod.z.string().describe("The search query"),
|
|
5675
|
+
maxResults: zod.z.number().optional().describe("Maximum number of results to return"),
|
|
5676
|
+
topic: zod.z.enum(["general", "news"]).optional().describe("Topic filter: general web search or news")
|
|
5677
|
+
});
|
|
5678
|
+
function createTavilySearchFn(config) {
|
|
5679
|
+
return async (query, options) => {
|
|
5680
|
+
const apiKey = config.apiKey ?? process.env["TAVILY_API_KEY"];
|
|
5681
|
+
if (!apiKey) {
|
|
5682
|
+
throw new Error(
|
|
5683
|
+
"Tavily API key is required. Provide it via the apiKey option or set the TAVILY_API_KEY environment variable."
|
|
5684
|
+
);
|
|
5685
|
+
}
|
|
5686
|
+
const baseUrl = (config.baseUrl ?? "https://api.tavily.com").replace(/\/+$/, "");
|
|
5687
|
+
const body = {
|
|
5688
|
+
query,
|
|
5689
|
+
max_results: options.maxResults,
|
|
5690
|
+
search_depth: config.searchDepth ?? "basic",
|
|
5691
|
+
include_answer: config.includeAnswer ?? true,
|
|
5692
|
+
include_raw_content: config.includeRawContent ?? false,
|
|
5693
|
+
topic: options.topic
|
|
5694
|
+
};
|
|
5695
|
+
const response = await fetch(`${baseUrl}/search`, {
|
|
5696
|
+
method: "POST",
|
|
5697
|
+
headers: {
|
|
5698
|
+
"Content-Type": "application/json",
|
|
5699
|
+
Authorization: `Bearer ${apiKey}`
|
|
5700
|
+
},
|
|
5701
|
+
body: JSON.stringify(body)
|
|
5702
|
+
});
|
|
5703
|
+
if (!response.ok) {
|
|
5704
|
+
let errorMessage;
|
|
5705
|
+
try {
|
|
5706
|
+
const errorBody = await response.json();
|
|
5707
|
+
errorMessage = typeof errorBody["detail"] === "string" ? errorBody["detail"] : JSON.stringify(errorBody);
|
|
5708
|
+
} catch {
|
|
5709
|
+
errorMessage = await response.text();
|
|
5710
|
+
}
|
|
5711
|
+
throw new Error(`Tavily API error (${String(response.status)}): ${errorMessage}`);
|
|
5712
|
+
}
|
|
5713
|
+
const data = await response.json();
|
|
5714
|
+
return {
|
|
5715
|
+
query: data.query,
|
|
5716
|
+
answer: data.answer,
|
|
5717
|
+
results: data.results.map((r) => ({
|
|
5718
|
+
title: r.title,
|
|
5719
|
+
url: r.url,
|
|
5720
|
+
content: r.content,
|
|
5721
|
+
score: r.score,
|
|
5722
|
+
publishedDate: r.published_date
|
|
5723
|
+
}))
|
|
5724
|
+
};
|
|
5725
|
+
};
|
|
5726
|
+
}
|
|
5727
|
+
function createWebSearchTool(config) {
|
|
5728
|
+
const toolId = config?.toolId ?? "web_search";
|
|
5729
|
+
const defaultMaxResults = config?.maxResults ?? 5;
|
|
5730
|
+
const defaultTopic = config?.topic ?? "general";
|
|
5731
|
+
const searchFn = config?.search ?? createTavilySearchFn(config ?? {});
|
|
5732
|
+
return defineTool(
|
|
5733
|
+
{
|
|
5734
|
+
id: toolId,
|
|
5735
|
+
description: "Search the web for current information. Returns a list of relevant results with titles, URLs, and content snippets.",
|
|
5736
|
+
inputSchema: webSearchInputSchema,
|
|
5737
|
+
approval: config?.approval
|
|
5738
|
+
},
|
|
5739
|
+
async (ctx, input) => {
|
|
5740
|
+
const options = {
|
|
5741
|
+
maxResults: input.maxResults ?? defaultMaxResults,
|
|
5742
|
+
topic: input.topic ?? defaultTopic
|
|
5743
|
+
};
|
|
5744
|
+
const result = await ctx.step.run("web_search", () => searchFn(input.query, options), {
|
|
5745
|
+
input: { query: input.query, options }
|
|
5746
|
+
});
|
|
5747
|
+
return result;
|
|
5748
|
+
}
|
|
5749
|
+
);
|
|
5750
|
+
}
|
|
5751
|
+
|
|
5752
|
+
// src/execution/output.ts
|
|
5753
|
+
var DEFAULT_MAX_CHARS = 1e5;
|
|
5754
|
+
var HEAD_RATIO = 0.2;
|
|
5755
|
+
function truncateOutput(output, maxChars) {
|
|
5756
|
+
const max = maxChars ?? DEFAULT_MAX_CHARS;
|
|
5757
|
+
if (output.length <= max) {
|
|
5758
|
+
return { text: output, truncated: false };
|
|
5759
|
+
}
|
|
5760
|
+
const headSize = Math.floor(max * HEAD_RATIO);
|
|
5761
|
+
const tailSize = max - headSize;
|
|
5762
|
+
const omitted = output.length - headSize - tailSize;
|
|
5763
|
+
const head = output.slice(0, headSize);
|
|
5764
|
+
const tail = output.slice(-tailSize);
|
|
5765
|
+
const text = `${head}
|
|
5766
|
+
|
|
5767
|
+
--- truncated ${String(omitted)} characters ---
|
|
5768
|
+
|
|
5769
|
+
${tail}`;
|
|
5770
|
+
return { text, truncated: true };
|
|
5771
|
+
}
|
|
5772
|
+
function isBinary(buffer) {
|
|
5773
|
+
const checkLength = Math.min(buffer.length, 8192);
|
|
5774
|
+
for (let i = 0; i < checkLength; i++) {
|
|
5775
|
+
if (buffer[i] === 0) {
|
|
5776
|
+
return true;
|
|
5777
|
+
}
|
|
5778
|
+
}
|
|
5779
|
+
return false;
|
|
5780
|
+
}
|
|
5781
|
+
function parseGrepOutput(output) {
|
|
5782
|
+
if (!output.trim()) {
|
|
5783
|
+
return [];
|
|
5784
|
+
}
|
|
5785
|
+
const matches = [];
|
|
5786
|
+
const lines = output.split("\n");
|
|
5787
|
+
for (const line of lines) {
|
|
5788
|
+
if (!line) continue;
|
|
5789
|
+
const match = /^(.+?):(\d+):(.*)$/.exec(line);
|
|
5790
|
+
if (match?.[1] && match[2] && match[3] !== void 0) {
|
|
5791
|
+
matches.push({ path: match[1], line: parseInt(match[2], 10), text: match[3] });
|
|
5792
|
+
}
|
|
5793
|
+
}
|
|
5794
|
+
return matches;
|
|
5795
|
+
}
|
|
5796
|
+
function stripAnsi(text) {
|
|
5797
|
+
return text.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, "");
|
|
5798
|
+
}
|
|
5799
|
+
|
|
5800
|
+
// src/execution/docker.ts
|
|
5801
|
+
var DEFAULT_CONTAINER_WORKDIR = "/workspace";
|
|
5802
|
+
var DEFAULT_TIMEOUT_SECONDS = 300;
|
|
5803
|
+
var DEFAULT_MAX_OUTPUT_CHARS = 1e5;
|
|
5804
|
+
function spawnCommand(command, args, options) {
|
|
5805
|
+
return new Promise((resolve8, reject) => {
|
|
5806
|
+
const proc = child_process.spawn(command, args, { stdio: ["pipe", "pipe", "pipe"] });
|
|
5807
|
+
let stdout = "";
|
|
5808
|
+
let stderr = "";
|
|
5809
|
+
let killed = false;
|
|
5810
|
+
proc.stdout.on("data", (data) => {
|
|
5811
|
+
stdout += data.toString();
|
|
5812
|
+
});
|
|
5813
|
+
proc.stderr.on("data", (data) => {
|
|
5814
|
+
stderr += data.toString();
|
|
5815
|
+
});
|
|
5816
|
+
const timeoutMs = (options?.timeout ?? DEFAULT_TIMEOUT_SECONDS) * 1e3;
|
|
5817
|
+
const timer = setTimeout(() => {
|
|
5818
|
+
killed = true;
|
|
5819
|
+
proc.kill("SIGKILL");
|
|
5820
|
+
}, timeoutMs);
|
|
5821
|
+
proc.on("close", (code) => {
|
|
5822
|
+
clearTimeout(timer);
|
|
5823
|
+
if (killed) {
|
|
5824
|
+
resolve8({
|
|
5825
|
+
exitCode: 137,
|
|
5826
|
+
stdout,
|
|
5827
|
+
stderr: stderr + "\n[Process killed: timeout exceeded]"
|
|
5828
|
+
});
|
|
5829
|
+
} else {
|
|
5830
|
+
resolve8({ exitCode: code ?? 1, stdout, stderr });
|
|
5831
|
+
}
|
|
5832
|
+
});
|
|
5833
|
+
proc.on("error", (err) => {
|
|
5834
|
+
clearTimeout(timer);
|
|
5835
|
+
reject(err);
|
|
5836
|
+
});
|
|
5837
|
+
if (options?.stdin) {
|
|
5838
|
+
proc.stdin.write(options.stdin);
|
|
5839
|
+
}
|
|
5840
|
+
proc.stdin.end();
|
|
5841
|
+
});
|
|
5842
|
+
}
|
|
5843
|
+
var DockerEnvironment = class {
|
|
5844
|
+
type = "docker";
|
|
5845
|
+
containerId = null;
|
|
5846
|
+
containerName;
|
|
5847
|
+
config;
|
|
5848
|
+
containerWorkdir;
|
|
5849
|
+
maxOutputChars;
|
|
5850
|
+
constructor(config, maxOutputChars) {
|
|
5851
|
+
this.config = config;
|
|
5852
|
+
this.containerWorkdir = config.containerWorkdir ?? DEFAULT_CONTAINER_WORKDIR;
|
|
5853
|
+
this.containerName = `polos-sandbox-${crypto.randomUUID().slice(0, 8)}`;
|
|
5854
|
+
this.maxOutputChars = maxOutputChars ?? DEFAULT_MAX_OUTPUT_CHARS;
|
|
5855
|
+
}
|
|
5856
|
+
async initialize() {
|
|
5857
|
+
const args = [
|
|
5858
|
+
"run",
|
|
5859
|
+
"-d",
|
|
5860
|
+
"--name",
|
|
5861
|
+
this.containerName,
|
|
5862
|
+
"-v",
|
|
5863
|
+
`${this.config.workspaceDir}:${this.containerWorkdir}:rw`,
|
|
5864
|
+
"-w",
|
|
5865
|
+
this.containerWorkdir
|
|
5866
|
+
];
|
|
5867
|
+
if (this.config.memory) {
|
|
5868
|
+
args.push("--memory", this.config.memory);
|
|
5869
|
+
}
|
|
5870
|
+
if (this.config.cpus) {
|
|
5871
|
+
args.push("--cpus", this.config.cpus);
|
|
5872
|
+
}
|
|
5873
|
+
args.push("--network", this.config.network ?? "none");
|
|
5874
|
+
if (this.config.env) {
|
|
5875
|
+
for (const [key, value] of Object.entries(this.config.env)) {
|
|
5876
|
+
args.push("-e", `${key}=${value}`);
|
|
5877
|
+
}
|
|
5878
|
+
}
|
|
5879
|
+
args.push(this.config.image, "sleep", "infinity");
|
|
5880
|
+
const result = await spawnCommand("docker", args, { timeout: 60 });
|
|
5881
|
+
if (result.exitCode !== 0) {
|
|
5882
|
+
throw new Error(`Failed to create Docker container: ${result.stderr.trim()}`);
|
|
5883
|
+
}
|
|
5884
|
+
this.containerId = result.stdout.trim().slice(0, 12);
|
|
5885
|
+
if (this.config.setupCommand) {
|
|
5886
|
+
const setupResult = await this.exec(this.config.setupCommand);
|
|
5887
|
+
if (setupResult.exitCode !== 0) {
|
|
5888
|
+
throw new Error(
|
|
5889
|
+
`Setup command failed (exit ${String(setupResult.exitCode)}): ${setupResult.stderr.trim()}`
|
|
5890
|
+
);
|
|
5891
|
+
}
|
|
5892
|
+
}
|
|
5893
|
+
}
|
|
5894
|
+
async exec(command, opts) {
|
|
5895
|
+
this.assertInitialized();
|
|
5896
|
+
const args = ["exec", "-i"];
|
|
5897
|
+
const cwd = opts?.cwd ?? this.containerWorkdir;
|
|
5898
|
+
args.push("-w", cwd);
|
|
5899
|
+
if (opts?.env) {
|
|
5900
|
+
for (const [key, value] of Object.entries(opts.env)) {
|
|
5901
|
+
args.push("-e", `${key}=${value}`);
|
|
5902
|
+
}
|
|
5903
|
+
}
|
|
5904
|
+
args.push(this.containerName, "sh", "-c", command);
|
|
5905
|
+
const timeout = opts?.timeout ?? DEFAULT_TIMEOUT_SECONDS;
|
|
5906
|
+
const start = Date.now();
|
|
5907
|
+
const result = await spawnCommand("docker", args, {
|
|
5908
|
+
timeout,
|
|
5909
|
+
stdin: opts?.stdin
|
|
5910
|
+
});
|
|
5911
|
+
const durationMs = Date.now() - start;
|
|
5912
|
+
const { text: stdout, truncated: stdoutTruncated } = truncateOutput(
|
|
5913
|
+
stripAnsi(result.stdout),
|
|
5914
|
+
this.maxOutputChars
|
|
5915
|
+
);
|
|
5916
|
+
const { text: stderr } = truncateOutput(stripAnsi(result.stderr), this.maxOutputChars);
|
|
5917
|
+
return {
|
|
5918
|
+
exitCode: result.exitCode,
|
|
5919
|
+
stdout,
|
|
5920
|
+
stderr,
|
|
5921
|
+
durationMs,
|
|
5922
|
+
truncated: stdoutTruncated
|
|
5923
|
+
};
|
|
5924
|
+
}
|
|
5925
|
+
async readFile(filePath) {
|
|
5926
|
+
const hostPath = this.toHostPath(filePath);
|
|
5927
|
+
const buffer = await fs2__namespace.readFile(hostPath);
|
|
5928
|
+
if (isBinary(buffer)) {
|
|
5929
|
+
throw new Error(`Cannot read binary file: ${filePath}`);
|
|
5930
|
+
}
|
|
5931
|
+
return buffer.toString("utf-8");
|
|
5932
|
+
}
|
|
5933
|
+
async writeFile(filePath, content) {
|
|
5934
|
+
const hostPath = this.toHostPath(filePath);
|
|
5935
|
+
await fs2__namespace.mkdir(path__namespace.dirname(hostPath), { recursive: true });
|
|
5936
|
+
await fs2__namespace.writeFile(hostPath, content, "utf-8");
|
|
5937
|
+
}
|
|
5938
|
+
async fileExists(filePath) {
|
|
5939
|
+
const hostPath = this.toHostPath(filePath);
|
|
5940
|
+
try {
|
|
5941
|
+
await fs2__namespace.access(hostPath);
|
|
5942
|
+
return true;
|
|
5943
|
+
} catch {
|
|
5944
|
+
return false;
|
|
5945
|
+
}
|
|
5946
|
+
}
|
|
5947
|
+
async glob(pattern, opts) {
|
|
5948
|
+
const cwd = opts?.cwd ?? this.containerWorkdir;
|
|
5949
|
+
let command = `find ${cwd} -type f -name '${pattern}'`;
|
|
5950
|
+
if (opts?.ignore) {
|
|
5951
|
+
for (const ignore of opts.ignore) {
|
|
5952
|
+
command += ` ! -path '${ignore}'`;
|
|
5953
|
+
}
|
|
5954
|
+
}
|
|
5955
|
+
command += " 2>/dev/null | sort | head -1000";
|
|
5956
|
+
const result = await this.exec(command);
|
|
5957
|
+
if (!result.stdout.trim()) return [];
|
|
5958
|
+
return result.stdout.trim().split("\n").filter(Boolean);
|
|
5959
|
+
}
|
|
5960
|
+
async grep(pattern, opts) {
|
|
5961
|
+
const cwd = opts?.cwd ?? this.containerWorkdir;
|
|
5962
|
+
const maxResults = opts?.maxResults ?? 100;
|
|
5963
|
+
let command = "grep -rn";
|
|
5964
|
+
if (opts?.contextLines !== void 0) {
|
|
5965
|
+
command += ` -C ${String(opts.contextLines)}`;
|
|
5966
|
+
}
|
|
5967
|
+
if (opts?.include) {
|
|
5968
|
+
for (const inc of opts.include) {
|
|
5969
|
+
command += ` --include='${inc}'`;
|
|
5970
|
+
}
|
|
5971
|
+
}
|
|
5972
|
+
const escapedPattern = pattern.replace(/'/g, "'\\''");
|
|
5973
|
+
command += ` -- '${escapedPattern}' ${cwd}`;
|
|
5974
|
+
command += ` 2>/dev/null | head -${String(maxResults)}`;
|
|
5975
|
+
const result = await this.exec(command);
|
|
5976
|
+
return parseGrepOutput(result.stdout);
|
|
5977
|
+
}
|
|
5978
|
+
async destroy() {
|
|
5979
|
+
if (!this.containerId) return;
|
|
5980
|
+
try {
|
|
5981
|
+
await spawnCommand("docker", ["rm", "-f", this.containerName], { timeout: 30 });
|
|
5982
|
+
} finally {
|
|
5983
|
+
this.containerId = null;
|
|
5984
|
+
}
|
|
5985
|
+
}
|
|
5986
|
+
getCwd() {
|
|
5987
|
+
return this.containerWorkdir;
|
|
5988
|
+
}
|
|
5989
|
+
getInfo() {
|
|
5990
|
+
return {
|
|
5991
|
+
type: "docker",
|
|
5992
|
+
cwd: this.containerWorkdir,
|
|
5993
|
+
sandboxId: this.containerId ?? void 0
|
|
5994
|
+
};
|
|
5995
|
+
}
|
|
5996
|
+
/**
|
|
5997
|
+
* Translate a container path to the corresponding host filesystem path.
|
|
5998
|
+
* Validates the path stays within the workspace to prevent traversal.
|
|
5999
|
+
*/
|
|
6000
|
+
toHostPath(containerPath) {
|
|
6001
|
+
const resolved = path__namespace.posix.resolve(this.containerWorkdir, containerPath);
|
|
6002
|
+
if (!resolved.startsWith(this.containerWorkdir)) {
|
|
6003
|
+
throw new Error(`Path traversal detected: "${containerPath}" resolves outside workspace`);
|
|
6004
|
+
}
|
|
6005
|
+
const relative2 = path__namespace.posix.relative(this.containerWorkdir, resolved);
|
|
6006
|
+
return path__namespace.join(this.config.workspaceDir, relative2);
|
|
6007
|
+
}
|
|
6008
|
+
/**
|
|
6009
|
+
* Translate a host filesystem path to the corresponding container path.
|
|
6010
|
+
*/
|
|
6011
|
+
toContainerPath(hostPath) {
|
|
6012
|
+
const resolved = path__namespace.resolve(hostPath);
|
|
6013
|
+
if (!resolved.startsWith(this.config.workspaceDir)) {
|
|
6014
|
+
throw new Error(
|
|
6015
|
+
`Path outside workspace: "${hostPath}" is not within "${this.config.workspaceDir}"`
|
|
6016
|
+
);
|
|
6017
|
+
}
|
|
6018
|
+
const relative2 = path__namespace.relative(this.config.workspaceDir, resolved);
|
|
6019
|
+
return path__namespace.posix.join(this.containerWorkdir, relative2);
|
|
6020
|
+
}
|
|
6021
|
+
assertInitialized() {
|
|
6022
|
+
if (!this.containerId) {
|
|
6023
|
+
throw new Error("Docker environment not initialized. Call initialize() first.");
|
|
6024
|
+
}
|
|
6025
|
+
}
|
|
6026
|
+
};
|
|
6027
|
+
var DEFAULT_TIMEOUT_SECONDS2 = 300;
|
|
6028
|
+
var DEFAULT_MAX_OUTPUT_CHARS2 = 1e5;
|
|
6029
|
+
function spawnLocal(command, options) {
|
|
6030
|
+
return new Promise((resolve8, reject) => {
|
|
6031
|
+
const proc = child_process.spawn("sh", ["-c", command], {
|
|
6032
|
+
cwd: options.cwd,
|
|
6033
|
+
env: options.env ? { ...process.env, ...options.env } : void 0,
|
|
6034
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
6035
|
+
});
|
|
6036
|
+
let stdout = "";
|
|
6037
|
+
let stderr = "";
|
|
6038
|
+
let killed = false;
|
|
6039
|
+
proc.stdout.on("data", (data) => {
|
|
6040
|
+
stdout += data.toString();
|
|
6041
|
+
});
|
|
6042
|
+
proc.stderr.on("data", (data) => {
|
|
6043
|
+
stderr += data.toString();
|
|
6044
|
+
});
|
|
6045
|
+
const timeoutMs = (options.timeout ?? DEFAULT_TIMEOUT_SECONDS2) * 1e3;
|
|
6046
|
+
const timer = setTimeout(() => {
|
|
6047
|
+
killed = true;
|
|
6048
|
+
proc.kill("SIGKILL");
|
|
6049
|
+
}, timeoutMs);
|
|
6050
|
+
proc.on("close", (code) => {
|
|
6051
|
+
clearTimeout(timer);
|
|
6052
|
+
if (killed) {
|
|
6053
|
+
resolve8({
|
|
6054
|
+
exitCode: 137,
|
|
6055
|
+
stdout,
|
|
6056
|
+
stderr: stderr + "\n[Process killed: timeout exceeded]"
|
|
6057
|
+
});
|
|
6058
|
+
} else {
|
|
6059
|
+
resolve8({ exitCode: code ?? 1, stdout, stderr });
|
|
6060
|
+
}
|
|
6061
|
+
});
|
|
6062
|
+
proc.on("error", (err) => {
|
|
6063
|
+
clearTimeout(timer);
|
|
6064
|
+
reject(err);
|
|
6065
|
+
});
|
|
6066
|
+
if (options.stdin) {
|
|
6067
|
+
proc.stdin.write(options.stdin);
|
|
6068
|
+
}
|
|
6069
|
+
proc.stdin.end();
|
|
6070
|
+
});
|
|
6071
|
+
}
|
|
6072
|
+
var LocalEnvironment = class {
|
|
6073
|
+
type = "local";
|
|
6074
|
+
config;
|
|
6075
|
+
cwd;
|
|
6076
|
+
maxOutputChars;
|
|
6077
|
+
constructor(config, maxOutputChars) {
|
|
6078
|
+
this.config = config ?? {};
|
|
6079
|
+
this.cwd = path__namespace.resolve(config?.cwd ?? process.cwd());
|
|
6080
|
+
this.maxOutputChars = maxOutputChars ?? DEFAULT_MAX_OUTPUT_CHARS2;
|
|
6081
|
+
}
|
|
6082
|
+
async initialize() {
|
|
6083
|
+
try {
|
|
6084
|
+
const stat2 = await fs2__namespace.stat(this.cwd);
|
|
6085
|
+
if (!stat2.isDirectory()) {
|
|
6086
|
+
throw new Error(`Working directory is not a directory: ${this.cwd}`);
|
|
6087
|
+
}
|
|
6088
|
+
} catch (err) {
|
|
6089
|
+
if (err.code === "ENOENT") {
|
|
6090
|
+
throw new Error(`Working directory does not exist: ${this.cwd}`);
|
|
6091
|
+
}
|
|
6092
|
+
throw err;
|
|
6093
|
+
}
|
|
6094
|
+
}
|
|
6095
|
+
async exec(command, opts) {
|
|
6096
|
+
const cwd = opts?.cwd ? this.resolvePath(opts.cwd) : this.cwd;
|
|
6097
|
+
const timeout = opts?.timeout ?? DEFAULT_TIMEOUT_SECONDS2;
|
|
6098
|
+
const start = Date.now();
|
|
6099
|
+
const result = await spawnLocal(command, {
|
|
6100
|
+
cwd,
|
|
6101
|
+
env: opts?.env,
|
|
6102
|
+
timeout,
|
|
6103
|
+
stdin: opts?.stdin
|
|
6104
|
+
});
|
|
6105
|
+
const durationMs = Date.now() - start;
|
|
6106
|
+
const { text: stdout, truncated: stdoutTruncated } = truncateOutput(
|
|
6107
|
+
stripAnsi(result.stdout),
|
|
6108
|
+
this.maxOutputChars
|
|
6109
|
+
);
|
|
6110
|
+
const { text: stderr } = truncateOutput(stripAnsi(result.stderr), this.maxOutputChars);
|
|
6111
|
+
return {
|
|
6112
|
+
exitCode: result.exitCode,
|
|
6113
|
+
stdout,
|
|
6114
|
+
stderr,
|
|
6115
|
+
durationMs,
|
|
6116
|
+
truncated: stdoutTruncated
|
|
6117
|
+
};
|
|
6118
|
+
}
|
|
6119
|
+
async readFile(filePath) {
|
|
6120
|
+
const resolved = this.resolvePath(filePath);
|
|
6121
|
+
await this.assertNotSymlink(resolved);
|
|
6122
|
+
const buffer = await fs2__namespace.readFile(resolved);
|
|
6123
|
+
if (isBinary(buffer)) {
|
|
6124
|
+
throw new Error(`Cannot read binary file: ${filePath}`);
|
|
6125
|
+
}
|
|
6126
|
+
return buffer.toString("utf-8");
|
|
6127
|
+
}
|
|
6128
|
+
async writeFile(filePath, content) {
|
|
6129
|
+
const resolved = this.resolvePath(filePath);
|
|
6130
|
+
this.assertPathSafe(resolved);
|
|
6131
|
+
const parentDir = path__namespace.dirname(resolved);
|
|
6132
|
+
await fs2__namespace.mkdir(parentDir, { recursive: true });
|
|
6133
|
+
await fs2__namespace.writeFile(resolved, content, "utf-8");
|
|
6134
|
+
}
|
|
6135
|
+
async fileExists(filePath) {
|
|
6136
|
+
const resolved = this.resolvePath(filePath);
|
|
6137
|
+
try {
|
|
6138
|
+
await fs2__namespace.access(resolved);
|
|
6139
|
+
return true;
|
|
6140
|
+
} catch {
|
|
6141
|
+
return false;
|
|
6142
|
+
}
|
|
6143
|
+
}
|
|
6144
|
+
async glob(pattern, opts) {
|
|
6145
|
+
const cwd = opts?.cwd ? this.resolvePath(opts.cwd) : this.cwd;
|
|
6146
|
+
let command = `find ${cwd} -type f -name '${pattern}'`;
|
|
6147
|
+
if (opts?.ignore) {
|
|
6148
|
+
for (const ignore of opts.ignore) {
|
|
6149
|
+
command += ` ! -path '${ignore}'`;
|
|
6150
|
+
}
|
|
6151
|
+
}
|
|
6152
|
+
command += " 2>/dev/null | sort | head -1000";
|
|
6153
|
+
const result = await this.exec(command);
|
|
6154
|
+
if (!result.stdout.trim()) return [];
|
|
6155
|
+
return result.stdout.trim().split("\n").filter(Boolean);
|
|
6156
|
+
}
|
|
6157
|
+
async grep(pattern, opts) {
|
|
6158
|
+
const cwd = opts?.cwd ? this.resolvePath(opts.cwd) : this.cwd;
|
|
6159
|
+
const maxResults = opts?.maxResults ?? 100;
|
|
6160
|
+
let command = "grep -rn";
|
|
6161
|
+
if (opts?.contextLines !== void 0) {
|
|
6162
|
+
command += ` -C ${String(opts.contextLines)}`;
|
|
6163
|
+
}
|
|
6164
|
+
if (opts?.include) {
|
|
6165
|
+
for (const inc of opts.include) {
|
|
6166
|
+
command += ` --include='${inc}'`;
|
|
6167
|
+
}
|
|
6168
|
+
}
|
|
6169
|
+
const escapedPattern = pattern.replace(/'/g, "'\\''");
|
|
6170
|
+
command += ` -- '${escapedPattern}' ${cwd}`;
|
|
6171
|
+
command += ` 2>/dev/null | head -${String(maxResults)}`;
|
|
6172
|
+
const result = await this.exec(command);
|
|
6173
|
+
return parseGrepOutput(result.stdout);
|
|
6174
|
+
}
|
|
6175
|
+
async destroy() {
|
|
6176
|
+
}
|
|
6177
|
+
getCwd() {
|
|
6178
|
+
return this.cwd;
|
|
6179
|
+
}
|
|
6180
|
+
getInfo() {
|
|
6181
|
+
return {
|
|
6182
|
+
type: "local",
|
|
6183
|
+
cwd: this.cwd
|
|
6184
|
+
};
|
|
6185
|
+
}
|
|
6186
|
+
/**
|
|
6187
|
+
* Resolve a path relative to the working directory.
|
|
6188
|
+
*/
|
|
6189
|
+
resolvePath(p) {
|
|
6190
|
+
return path__namespace.resolve(this.cwd, p);
|
|
6191
|
+
}
|
|
6192
|
+
/**
|
|
6193
|
+
* Assert that a resolved path stays within the path restriction.
|
|
6194
|
+
* No-op when path restriction is not configured.
|
|
6195
|
+
*/
|
|
6196
|
+
assertPathSafe(resolvedPath) {
|
|
6197
|
+
if (!this.config.pathRestriction) return;
|
|
6198
|
+
const restriction = path__namespace.resolve(this.config.pathRestriction);
|
|
6199
|
+
if (resolvedPath !== restriction && !resolvedPath.startsWith(restriction + "/")) {
|
|
6200
|
+
throw new Error(`Path traversal detected: "${resolvedPath}" is outside of "${restriction}"`);
|
|
6201
|
+
}
|
|
6202
|
+
}
|
|
6203
|
+
/**
|
|
6204
|
+
* Assert that a path is not a symbolic link.
|
|
6205
|
+
* Only enforced when path restriction is configured.
|
|
6206
|
+
*/
|
|
6207
|
+
async assertNotSymlink(resolvedPath) {
|
|
6208
|
+
if (!this.config.pathRestriction) return;
|
|
6209
|
+
try {
|
|
6210
|
+
const stat2 = await fs2__namespace.lstat(resolvedPath);
|
|
6211
|
+
if (stat2.isSymbolicLink()) {
|
|
6212
|
+
throw new Error(
|
|
6213
|
+
`Symbolic link detected: "${resolvedPath}". Symlinks are blocked when pathRestriction is set.`
|
|
6214
|
+
);
|
|
6215
|
+
}
|
|
6216
|
+
} catch (err) {
|
|
6217
|
+
if (err.code === "ENOENT") return;
|
|
6218
|
+
throw err;
|
|
6219
|
+
}
|
|
6220
|
+
}
|
|
6221
|
+
};
|
|
6222
|
+
function matchGlob(text, pattern) {
|
|
6223
|
+
const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
|
|
6224
|
+
const regexStr = `^${escaped.replace(/\*/g, ".*")}$`;
|
|
6225
|
+
return new RegExp(regexStr).test(text);
|
|
6226
|
+
}
|
|
6227
|
+
function evaluateAllowlist(command, patterns) {
|
|
6228
|
+
const trimmed = command.trim();
|
|
6229
|
+
return patterns.some((pattern) => matchGlob(trimmed, pattern));
|
|
6230
|
+
}
|
|
6231
|
+
function isWithinRestriction(resolvedPath, restriction) {
|
|
6232
|
+
const base = path.resolve(restriction);
|
|
6233
|
+
return resolvedPath === base || resolvedPath.startsWith(base + "/");
|
|
6234
|
+
}
|
|
6235
|
+
function assertSafePath(filePath, restriction) {
|
|
6236
|
+
const base = path.resolve(restriction);
|
|
6237
|
+
const resolved = path.resolve(base, filePath);
|
|
6238
|
+
if (!isWithinRestriction(resolved, base)) {
|
|
6239
|
+
throw new Error(`Path traversal detected: "${filePath}" resolves outside of "${restriction}"`);
|
|
6240
|
+
}
|
|
6241
|
+
}
|
|
6242
|
+
|
|
6243
|
+
// src/execution/tools/exec.ts
|
|
6244
|
+
async function requestApproval(ctx, command, env) {
|
|
6245
|
+
const envInfo = env.getInfo();
|
|
6246
|
+
const approvalId = await ctx.step.uuid("_approval_id");
|
|
6247
|
+
const response = await ctx.step.suspend(
|
|
6248
|
+
`approve_exec_${approvalId}`,
|
|
6249
|
+
{
|
|
6250
|
+
data: {
|
|
6251
|
+
_form: {
|
|
6252
|
+
title: "Approve command execution",
|
|
6253
|
+
description: `The agent wants to run a shell command in the ${envInfo.type} environment.`,
|
|
6254
|
+
fields: [
|
|
6255
|
+
{
|
|
6256
|
+
key: "approved",
|
|
6257
|
+
type: "boolean",
|
|
6258
|
+
label: "Approve this command?",
|
|
6259
|
+
required: true,
|
|
6260
|
+
default: false
|
|
6261
|
+
},
|
|
6262
|
+
{
|
|
6263
|
+
key: "allow_always",
|
|
6264
|
+
type: "boolean",
|
|
6265
|
+
label: "Always allow this command in the future?",
|
|
6266
|
+
required: false,
|
|
6267
|
+
default: false
|
|
6268
|
+
},
|
|
6269
|
+
{
|
|
6270
|
+
key: "feedback",
|
|
6271
|
+
type: "textarea",
|
|
6272
|
+
label: "Feedback for the agent (optional)",
|
|
6273
|
+
description: "If rejecting, tell the agent what to do instead.",
|
|
6274
|
+
required: false
|
|
6275
|
+
}
|
|
6276
|
+
],
|
|
6277
|
+
context: {
|
|
6278
|
+
command,
|
|
6279
|
+
cwd: env.getCwd(),
|
|
6280
|
+
environment: envInfo.type
|
|
6281
|
+
}
|
|
6282
|
+
},
|
|
6283
|
+
_source: "exec_security",
|
|
6284
|
+
_tool: "exec"
|
|
6285
|
+
}
|
|
6286
|
+
}
|
|
6287
|
+
);
|
|
6288
|
+
const feedback = response.data?.feedback;
|
|
6289
|
+
return {
|
|
6290
|
+
approved: response.data?.approved === true,
|
|
6291
|
+
...feedback ? { feedback } : {}
|
|
6292
|
+
};
|
|
6293
|
+
}
|
|
6294
|
+
function rejectedResult(command, feedback) {
|
|
6295
|
+
let stderr = `Command rejected by user: ${command}`;
|
|
6296
|
+
if (feedback) {
|
|
6297
|
+
stderr += `
|
|
6298
|
+
User feedback: ${feedback}`;
|
|
6299
|
+
}
|
|
6300
|
+
return {
|
|
6301
|
+
exitCode: -1,
|
|
6302
|
+
stdout: "",
|
|
6303
|
+
stderr,
|
|
6304
|
+
durationMs: 0,
|
|
6305
|
+
truncated: false
|
|
6306
|
+
};
|
|
6307
|
+
}
|
|
6308
|
+
function createExecTool(getEnv, config) {
|
|
6309
|
+
return defineTool(
|
|
6310
|
+
{
|
|
6311
|
+
id: "exec",
|
|
6312
|
+
description: "Execute a shell command in the sandbox environment. Returns stdout, stderr, and exit code. Use this for running builds, tests, installing packages, or any shell operation.",
|
|
6313
|
+
inputSchema: zod.z.object({
|
|
6314
|
+
command: zod.z.string().describe("The shell command to execute"),
|
|
6315
|
+
cwd: zod.z.string().optional().describe("Working directory for the command"),
|
|
6316
|
+
env: zod.z.record(zod.z.string()).optional().describe("Environment variables to set"),
|
|
6317
|
+
timeout: zod.z.number().optional().describe("Timeout in seconds (default: 300)")
|
|
6318
|
+
})
|
|
6319
|
+
},
|
|
6320
|
+
async (ctx, input) => {
|
|
6321
|
+
const env = await getEnv();
|
|
6322
|
+
if (config?.security === "approval-always") {
|
|
6323
|
+
const result = await requestApproval(ctx, input.command, env);
|
|
6324
|
+
if (!result.approved) return rejectedResult(input.command, result.feedback);
|
|
6325
|
+
} else if (config?.security === "allowlist") {
|
|
6326
|
+
if (!evaluateAllowlist(input.command, config.allowlist ?? [])) {
|
|
6327
|
+
const result = await requestApproval(ctx, input.command, env);
|
|
6328
|
+
if (!result.approved) return rejectedResult(input.command, result.feedback);
|
|
6329
|
+
}
|
|
6330
|
+
}
|
|
6331
|
+
return env.exec(input.command, {
|
|
6332
|
+
cwd: input.cwd,
|
|
6333
|
+
env: input.env,
|
|
6334
|
+
timeout: input.timeout ?? config?.timeout
|
|
6335
|
+
});
|
|
6336
|
+
}
|
|
6337
|
+
);
|
|
6338
|
+
}
|
|
6339
|
+
function isPathAllowed(resolvedPath, restriction) {
|
|
6340
|
+
return isWithinRestriction(resolvedPath, path.resolve(restriction));
|
|
6341
|
+
}
|
|
6342
|
+
async function requirePathApproval(ctx, toolName, targetPath, restriction) {
|
|
6343
|
+
const approvalId = await ctx.step.uuid("_approval_id");
|
|
6344
|
+
const response = await ctx.step.suspend(
|
|
6345
|
+
`approve_${toolName}_${approvalId}`,
|
|
6346
|
+
{
|
|
6347
|
+
data: {
|
|
6348
|
+
_form: {
|
|
6349
|
+
title: `${toolName}: access outside workspace`,
|
|
6350
|
+
description: `The agent wants to ${toolName} a path outside the workspace.`,
|
|
6351
|
+
fields: [
|
|
6352
|
+
{
|
|
6353
|
+
key: "approved",
|
|
6354
|
+
type: "boolean",
|
|
6355
|
+
label: "Allow this operation?",
|
|
6356
|
+
required: true,
|
|
6357
|
+
default: false
|
|
6358
|
+
},
|
|
6359
|
+
{
|
|
6360
|
+
key: "feedback",
|
|
6361
|
+
type: "textarea",
|
|
6362
|
+
label: "Feedback for the agent (optional)",
|
|
6363
|
+
description: "If rejecting, tell the agent what to do instead.",
|
|
6364
|
+
required: false
|
|
6365
|
+
}
|
|
6366
|
+
],
|
|
6367
|
+
context: {
|
|
6368
|
+
tool: toolName,
|
|
6369
|
+
path: targetPath,
|
|
6370
|
+
restriction
|
|
6371
|
+
}
|
|
6372
|
+
},
|
|
6373
|
+
_source: "path_approval",
|
|
6374
|
+
_tool: toolName
|
|
6375
|
+
}
|
|
6376
|
+
}
|
|
6377
|
+
);
|
|
6378
|
+
if (response.data?.approved !== true) {
|
|
6379
|
+
const feedback = response.data?.feedback;
|
|
6380
|
+
throw new Error(
|
|
6381
|
+
`Access to "${targetPath}" was rejected by the user.${feedback ? ` Feedback: ${feedback}` : ""}`
|
|
6382
|
+
);
|
|
6383
|
+
}
|
|
6384
|
+
}
|
|
6385
|
+
|
|
6386
|
+
// src/execution/tools/read.ts
|
|
6387
|
+
function createReadTool(getEnv, pathConfig) {
|
|
6388
|
+
return defineTool(
|
|
6389
|
+
{
|
|
6390
|
+
id: "read",
|
|
6391
|
+
description: "Read the contents of a file. Returns the file content as text. Optionally specify offset (line number to start from, 0-based) and limit (number of lines).",
|
|
6392
|
+
inputSchema: zod.z.object({
|
|
6393
|
+
path: zod.z.string().describe("Path to the file to read"),
|
|
6394
|
+
offset: zod.z.number().optional().describe("Line offset to start reading from (0-based)"),
|
|
6395
|
+
limit: zod.z.number().optional().describe("Maximum number of lines to return")
|
|
6396
|
+
})
|
|
6397
|
+
},
|
|
6398
|
+
async (ctx, input) => {
|
|
6399
|
+
const env = await getEnv();
|
|
6400
|
+
if (pathConfig?.pathRestriction) {
|
|
6401
|
+
const resolved = path.resolve(env.getCwd(), input.path);
|
|
6402
|
+
if (!isPathAllowed(resolved, pathConfig.pathRestriction)) {
|
|
6403
|
+
await requirePathApproval(ctx, "read", resolved, pathConfig.pathRestriction);
|
|
6404
|
+
}
|
|
6405
|
+
}
|
|
6406
|
+
let content = await env.readFile(input.path);
|
|
6407
|
+
if (input.offset !== void 0 || input.limit !== void 0) {
|
|
6408
|
+
const lines = content.split("\n");
|
|
6409
|
+
const start = input.offset ?? 0;
|
|
6410
|
+
const end = input.limit !== void 0 ? start + input.limit : lines.length;
|
|
6411
|
+
content = lines.slice(start, end).join("\n");
|
|
6412
|
+
}
|
|
6413
|
+
return { content, path: input.path };
|
|
6414
|
+
}
|
|
6415
|
+
);
|
|
6416
|
+
}
|
|
6417
|
+
function createWriteTool(getEnv, approval) {
|
|
6418
|
+
return defineTool(
|
|
6419
|
+
{
|
|
6420
|
+
id: "write",
|
|
6421
|
+
description: "Write content to a file. Creates the file if it does not exist, or overwrites it if it does. Parent directories are created automatically.",
|
|
6422
|
+
inputSchema: zod.z.object({
|
|
6423
|
+
path: zod.z.string().describe("Path to the file to write"),
|
|
6424
|
+
content: zod.z.string().describe("Content to write to the file")
|
|
6425
|
+
}),
|
|
6426
|
+
approval
|
|
6427
|
+
},
|
|
6428
|
+
async (_ctx, input) => {
|
|
6429
|
+
const env = await getEnv();
|
|
6430
|
+
await env.writeFile(input.path, input.content);
|
|
6431
|
+
return { success: true, path: input.path };
|
|
6432
|
+
}
|
|
6433
|
+
);
|
|
6434
|
+
}
|
|
6435
|
+
function createEditTool(getEnv, approval) {
|
|
6436
|
+
return defineTool(
|
|
6437
|
+
{
|
|
6438
|
+
id: "edit",
|
|
6439
|
+
description: "Edit a file by replacing an exact string match. The old_text must match exactly (including whitespace and indentation). Use this for precise code modifications.",
|
|
6440
|
+
inputSchema: zod.z.object({
|
|
6441
|
+
path: zod.z.string().describe("Path to the file to edit"),
|
|
6442
|
+
old_text: zod.z.string().describe("Exact text to find and replace"),
|
|
6443
|
+
new_text: zod.z.string().describe("Text to replace the old_text with")
|
|
6444
|
+
}),
|
|
6445
|
+
approval
|
|
6446
|
+
},
|
|
6447
|
+
async (_ctx, input) => {
|
|
6448
|
+
const env = await getEnv();
|
|
6449
|
+
const content = await env.readFile(input.path);
|
|
6450
|
+
if (!content.includes(input.old_text)) {
|
|
6451
|
+
throw new Error(
|
|
6452
|
+
`old_text not found in ${input.path}. Make sure the text matches exactly, including whitespace and indentation.`
|
|
6453
|
+
);
|
|
6454
|
+
}
|
|
6455
|
+
const newContent = content.replace(input.old_text, input.new_text);
|
|
6456
|
+
await env.writeFile(input.path, newContent);
|
|
6457
|
+
return { success: true, path: input.path };
|
|
6458
|
+
}
|
|
6459
|
+
);
|
|
6460
|
+
}
|
|
6461
|
+
function createGlobTool(getEnv, pathConfig) {
|
|
6462
|
+
return defineTool(
|
|
6463
|
+
{
|
|
6464
|
+
id: "glob",
|
|
6465
|
+
description: "Find files matching a glob pattern. Returns a list of file paths. Use this to discover files in the project structure.",
|
|
6466
|
+
inputSchema: zod.z.object({
|
|
6467
|
+
pattern: zod.z.string().describe('Glob pattern to match (e.g., "*.ts", "src/**/*.js")'),
|
|
6468
|
+
cwd: zod.z.string().optional().describe("Directory to search in"),
|
|
6469
|
+
ignore: zod.z.array(zod.z.string()).optional().describe("Patterns to exclude from results")
|
|
6470
|
+
})
|
|
6471
|
+
},
|
|
6472
|
+
async (ctx, input) => {
|
|
6473
|
+
const env = await getEnv();
|
|
6474
|
+
if (pathConfig?.pathRestriction && input.cwd) {
|
|
6475
|
+
const resolved = path.resolve(env.getCwd(), input.cwd);
|
|
6476
|
+
if (!isPathAllowed(resolved, pathConfig.pathRestriction)) {
|
|
6477
|
+
await requirePathApproval(ctx, "glob", resolved, pathConfig.pathRestriction);
|
|
6478
|
+
}
|
|
6479
|
+
}
|
|
6480
|
+
const files = await env.glob(input.pattern, {
|
|
6481
|
+
cwd: input.cwd,
|
|
6482
|
+
ignore: input.ignore
|
|
6483
|
+
});
|
|
6484
|
+
return { files };
|
|
6485
|
+
}
|
|
6486
|
+
);
|
|
6487
|
+
}
|
|
6488
|
+
function createGrepTool(getEnv, pathConfig) {
|
|
6489
|
+
return defineTool(
|
|
6490
|
+
{
|
|
6491
|
+
id: "grep",
|
|
6492
|
+
description: "Search file contents for a pattern using grep. Returns matching lines with file paths and line numbers. Use this to find code patterns, references, or specific text.",
|
|
6493
|
+
inputSchema: zod.z.object({
|
|
6494
|
+
pattern: zod.z.string().describe("Search pattern (regex supported)"),
|
|
6495
|
+
cwd: zod.z.string().optional().describe("Directory to search in"),
|
|
6496
|
+
include: zod.z.array(zod.z.string()).optional().describe('File patterns to include (e.g., ["*.ts", "*.js"])'),
|
|
6497
|
+
maxResults: zod.z.number().optional().describe("Maximum number of matches to return (default: 100)"),
|
|
6498
|
+
contextLines: zod.z.number().optional().describe("Number of context lines around each match")
|
|
6499
|
+
})
|
|
6500
|
+
},
|
|
6501
|
+
async (ctx, input) => {
|
|
6502
|
+
const env = await getEnv();
|
|
6503
|
+
if (pathConfig?.pathRestriction && input.cwd) {
|
|
6504
|
+
const resolved = path.resolve(env.getCwd(), input.cwd);
|
|
6505
|
+
if (!isPathAllowed(resolved, pathConfig.pathRestriction)) {
|
|
6506
|
+
await requirePathApproval(ctx, "grep", resolved, pathConfig.pathRestriction);
|
|
6507
|
+
}
|
|
6508
|
+
}
|
|
6509
|
+
const matches = await env.grep(input.pattern, {
|
|
6510
|
+
cwd: input.cwd,
|
|
6511
|
+
include: input.include,
|
|
6512
|
+
maxResults: input.maxResults,
|
|
6513
|
+
contextLines: input.contextLines
|
|
6514
|
+
});
|
|
6515
|
+
return { matches };
|
|
6516
|
+
}
|
|
6517
|
+
);
|
|
6518
|
+
}
|
|
6519
|
+
|
|
6520
|
+
// src/execution/sandbox-tools.ts
|
|
6521
|
+
function createEnvironment(config) {
|
|
6522
|
+
const envType = config?.env ?? "docker";
|
|
6523
|
+
switch (envType) {
|
|
6524
|
+
case "docker": {
|
|
6525
|
+
const dockerConfig = config?.docker ?? {
|
|
6526
|
+
image: "node:20-slim",
|
|
6527
|
+
workspaceDir: process.cwd()
|
|
6528
|
+
};
|
|
6529
|
+
return new DockerEnvironment(dockerConfig, config?.exec?.maxOutputChars);
|
|
6530
|
+
}
|
|
6531
|
+
case "e2b":
|
|
6532
|
+
throw new Error("E2B environment is not yet implemented.");
|
|
6533
|
+
case "local":
|
|
6534
|
+
return new LocalEnvironment(config?.local, config?.exec?.maxOutputChars);
|
|
6535
|
+
default:
|
|
6536
|
+
throw new Error(`Unknown environment type: ${String(envType)}`);
|
|
6537
|
+
}
|
|
6538
|
+
}
|
|
6539
|
+
function sandboxTools(config) {
|
|
6540
|
+
let env = null;
|
|
6541
|
+
let envPromise = null;
|
|
6542
|
+
async function getEnv() {
|
|
6543
|
+
if (env) return env;
|
|
6544
|
+
if (envPromise) return envPromise;
|
|
6545
|
+
envPromise = (async () => {
|
|
6546
|
+
const created = createEnvironment(config);
|
|
6547
|
+
await created.initialize();
|
|
6548
|
+
env = created;
|
|
6549
|
+
return env;
|
|
6550
|
+
})();
|
|
6551
|
+
return envPromise;
|
|
6552
|
+
}
|
|
6553
|
+
const envType = config?.env ?? "docker";
|
|
6554
|
+
if (envType === "e2b") {
|
|
6555
|
+
throw new Error("E2B environment is not yet implemented.");
|
|
6556
|
+
}
|
|
6557
|
+
const effectiveExecConfig = envType === "local" && !config?.exec?.security ? { ...config?.exec, security: "approval-always" } : config?.exec;
|
|
6558
|
+
const fileApproval = config?.fileApproval ?? (envType === "local" ? "always" : void 0);
|
|
6559
|
+
const pathConfig = config?.local?.pathRestriction ? { pathRestriction: config.local.pathRestriction } : void 0;
|
|
6560
|
+
const include = new Set(
|
|
6561
|
+
config?.tools ?? ["exec", "read", "write", "edit", "glob", "grep"]
|
|
6562
|
+
);
|
|
6563
|
+
const tools = [];
|
|
6564
|
+
if (include.has("exec")) tools.push(createExecTool(getEnv, effectiveExecConfig));
|
|
6565
|
+
if (include.has("read")) tools.push(createReadTool(getEnv, pathConfig));
|
|
6566
|
+
if (include.has("write")) tools.push(createWriteTool(getEnv, fileApproval));
|
|
6567
|
+
if (include.has("edit")) tools.push(createEditTool(getEnv, fileApproval));
|
|
6568
|
+
if (include.has("glob")) tools.push(createGlobTool(getEnv, pathConfig));
|
|
6569
|
+
if (include.has("grep")) tools.push(createGrepTool(getEnv, pathConfig));
|
|
6570
|
+
const result = tools;
|
|
6571
|
+
result.cleanup = async () => {
|
|
6572
|
+
if (env) {
|
|
6573
|
+
await env.destroy();
|
|
6574
|
+
env = null;
|
|
6575
|
+
envPromise = null;
|
|
6576
|
+
}
|
|
6577
|
+
};
|
|
6578
|
+
return result;
|
|
6579
|
+
}
|
|
5542
6580
|
|
|
5543
6581
|
exports.AgentRunConfig = AgentRunConfig;
|
|
6582
|
+
exports.DockerEnvironment = DockerEnvironment;
|
|
5544
6583
|
exports.DuplicateWorkflowError = DuplicateWorkflowError;
|
|
5545
6584
|
exports.ExecutionHandle = ExecutionHandle;
|
|
5546
6585
|
exports.GuardrailError = GuardrailError;
|
|
@@ -5562,6 +6601,7 @@ exports.Worker = Worker;
|
|
|
5562
6601
|
exports.WorkerServer = WorkerServer;
|
|
5563
6602
|
exports.WorkflowNotFoundError = WorkflowNotFoundError;
|
|
5564
6603
|
exports.agentStreamFunction = agentStreamFunction;
|
|
6604
|
+
exports.assertSafePath = assertSafePath;
|
|
5565
6605
|
exports.batchAgentInvoke = batchAgentInvoke;
|
|
5566
6606
|
exports.batchInvoke = batchInvoke;
|
|
5567
6607
|
exports.composeGuardrails = composeGuardrails;
|
|
@@ -5574,15 +6614,24 @@ exports.convertToolResultsToMessages = convertToolResultsToMessages;
|
|
|
5574
6614
|
exports.convertToolsToVercel = convertToolsToVercel;
|
|
5575
6615
|
exports.convertVercelToolCallToPython = convertVercelToolCallToPython;
|
|
5576
6616
|
exports.convertVercelUsageToPython = convertVercelUsageToPython;
|
|
6617
|
+
exports.createAskUserTool = createAskUserTool;
|
|
6618
|
+
exports.createEditTool = createEditTool;
|
|
6619
|
+
exports.createExecTool = createExecTool;
|
|
6620
|
+
exports.createGlobTool = createGlobTool;
|
|
6621
|
+
exports.createGrepTool = createGrepTool;
|
|
5577
6622
|
exports.createLogger = createLogger;
|
|
6623
|
+
exports.createReadTool = createReadTool;
|
|
5578
6624
|
exports.createStepHelper = createStepHelper;
|
|
5579
6625
|
exports.createStepStore = createStepStore;
|
|
6626
|
+
exports.createWebSearchTool = createWebSearchTool;
|
|
5580
6627
|
exports.createWorkflowRegistry = createWorkflowRegistry;
|
|
6628
|
+
exports.createWriteTool = createWriteTool;
|
|
5581
6629
|
exports.defineAgent = defineAgent;
|
|
5582
6630
|
exports.defineGuardrail = defineGuardrail;
|
|
5583
6631
|
exports.defineHook = defineHook;
|
|
5584
6632
|
exports.defineTool = defineTool;
|
|
5585
6633
|
exports.defineWorkflow = defineWorkflow;
|
|
6634
|
+
exports.evaluateAllowlist = evaluateAllowlist;
|
|
5586
6635
|
exports.executeGuardrailChain = executeGuardrailChain;
|
|
5587
6636
|
exports.executeGuardrailsOrThrow = executeGuardrailsOrThrow;
|
|
5588
6637
|
exports.executeHookChain = executeHookChain;
|
|
@@ -5597,6 +6646,7 @@ exports.hasText = hasText;
|
|
|
5597
6646
|
exports.initializeOtel = initializeOtel;
|
|
5598
6647
|
exports.initializeState = initializeState;
|
|
5599
6648
|
exports.isAgentWorkflow = isAgentWorkflow;
|
|
6649
|
+
exports.isBinary = isBinary;
|
|
5600
6650
|
exports.isGuardrail = isGuardrail;
|
|
5601
6651
|
exports.isHook = isHook;
|
|
5602
6652
|
exports.isOtelAvailable = isOtelAvailable;
|
|
@@ -5610,10 +6660,14 @@ exports.normalizeGuardrail = normalizeGuardrail;
|
|
|
5610
6660
|
exports.normalizeGuardrails = normalizeGuardrails;
|
|
5611
6661
|
exports.normalizeHook = normalizeHook;
|
|
5612
6662
|
exports.normalizeHooks = normalizeHooks;
|
|
6663
|
+
exports.parseGrepOutput = parseGrepOutput;
|
|
5613
6664
|
exports.retry = retry;
|
|
6665
|
+
exports.sandboxTools = sandboxTools;
|
|
5614
6666
|
exports.serializeFinalState = serializeFinalState;
|
|
5615
6667
|
exports.sleep = sleep;
|
|
5616
6668
|
exports.stopCondition = stopCondition;
|
|
6669
|
+
exports.stripAnsi = stripAnsi;
|
|
6670
|
+
exports.truncateOutput = truncateOutput;
|
|
5617
6671
|
exports.validateState = validateState;
|
|
5618
6672
|
//# sourceMappingURL=index.cjs.map
|
|
5619
6673
|
//# sourceMappingURL=index.cjs.map
|