zeitlich 0.2.13 → 0.2.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +49 -38
- package/dist/adapters/sandbox/daytona/index.cjs +205 -0
- package/dist/adapters/sandbox/daytona/index.cjs.map +1 -0
- package/dist/adapters/sandbox/daytona/index.d.cts +86 -0
- package/dist/adapters/sandbox/daytona/index.d.ts +86 -0
- package/dist/adapters/sandbox/daytona/index.js +202 -0
- package/dist/adapters/sandbox/daytona/index.js.map +1 -0
- package/dist/adapters/sandbox/inmemory/index.cjs +174 -0
- package/dist/adapters/sandbox/inmemory/index.cjs.map +1 -0
- package/dist/adapters/sandbox/inmemory/index.d.cts +28 -0
- package/dist/adapters/sandbox/inmemory/index.d.ts +28 -0
- package/dist/adapters/sandbox/inmemory/index.js +172 -0
- package/dist/adapters/sandbox/inmemory/index.js.map +1 -0
- package/dist/adapters/sandbox/virtual/index.cjs +405 -0
- package/dist/adapters/sandbox/virtual/index.cjs.map +1 -0
- package/dist/adapters/sandbox/virtual/index.d.cts +85 -0
- package/dist/adapters/sandbox/virtual/index.d.ts +85 -0
- package/dist/adapters/sandbox/virtual/index.js +400 -0
- package/dist/adapters/sandbox/virtual/index.js.map +1 -0
- package/dist/adapters/thread/google-genai/index.cjs +284 -0
- package/dist/adapters/thread/google-genai/index.cjs.map +1 -0
- package/dist/adapters/thread/google-genai/index.d.cts +145 -0
- package/dist/adapters/thread/google-genai/index.d.ts +145 -0
- package/dist/adapters/thread/google-genai/index.js +278 -0
- package/dist/adapters/thread/google-genai/index.js.map +1 -0
- package/dist/adapters/{langchain → thread/langchain}/index.cjs +7 -9
- package/dist/adapters/thread/langchain/index.cjs.map +1 -0
- package/dist/adapters/{langchain → thread/langchain}/index.d.cts +17 -21
- package/dist/adapters/{langchain → thread/langchain}/index.d.ts +17 -21
- package/dist/adapters/{langchain → thread/langchain}/index.js +7 -9
- package/dist/adapters/thread/langchain/index.js.map +1 -0
- package/dist/index.cjs +816 -545
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +235 -74
- package/dist/index.d.ts +235 -74
- package/dist/index.js +804 -540
- package/dist/index.js.map +1 -1
- package/dist/types-B4C9txdq.d.ts +389 -0
- package/dist/{thread-manager-qc0g5Rvd.d.cts → types-B9ljZewB.d.cts} +1 -6
- package/dist/{thread-manager-qc0g5Rvd.d.ts → types-B9ljZewB.d.ts} +1 -6
- package/dist/types-BMXzv7TN.d.cts +476 -0
- package/dist/types-BMXzv7TN.d.ts +476 -0
- package/dist/types-BVP87m_W.d.cts +121 -0
- package/dist/types-CDubRtad.d.cts +115 -0
- package/dist/types-CDubRtad.d.ts +115 -0
- package/dist/types-CwwgQ_9H.d.ts +121 -0
- package/dist/types-GpMU4b0w.d.cts +389 -0
- package/dist/workflow.cjs +444 -318
- package/dist/workflow.cjs.map +1 -1
- package/dist/workflow.d.cts +271 -222
- package/dist/workflow.d.ts +271 -222
- package/dist/workflow.js +440 -316
- package/dist/workflow.js.map +1 -1
- package/package.json +59 -6
- package/src/adapters/sandbox/daytona/filesystem.ts +136 -0
- package/src/adapters/sandbox/daytona/index.ts +149 -0
- package/src/adapters/sandbox/daytona/types.ts +34 -0
- package/src/adapters/sandbox/inmemory/index.ts +213 -0
- package/src/adapters/sandbox/virtual/filesystem.ts +345 -0
- package/src/adapters/sandbox/virtual/index.ts +88 -0
- package/src/adapters/sandbox/virtual/mutations.ts +38 -0
- package/src/adapters/sandbox/virtual/provider.ts +101 -0
- package/src/adapters/sandbox/virtual/tree.ts +82 -0
- package/src/adapters/sandbox/virtual/types.ts +127 -0
- package/src/adapters/sandbox/virtual/virtual-sandbox.test.ts +523 -0
- package/src/adapters/sandbox/virtual/with-virtual-sandbox.ts +91 -0
- package/src/adapters/thread/google-genai/activities.ts +121 -0
- package/src/adapters/thread/google-genai/index.ts +41 -0
- package/src/adapters/thread/google-genai/model-invoker.ts +154 -0
- package/src/adapters/thread/google-genai/thread-manager.ts +169 -0
- package/src/adapters/{langchain → thread/langchain}/activities.ts +11 -15
- package/src/adapters/{langchain → thread/langchain}/index.ts +1 -1
- package/src/adapters/{langchain → thread/langchain}/model-invoker.ts +15 -18
- package/src/adapters/{langchain → thread/langchain}/thread-manager.ts +1 -1
- package/src/index.ts +32 -24
- package/src/lib/activity.ts +87 -0
- package/src/lib/hooks/index.ts +11 -0
- package/src/lib/hooks/types.ts +98 -0
- package/src/lib/model/helpers.ts +6 -0
- package/src/lib/model/index.ts +13 -0
- package/src/lib/{model-invoker.ts → model/types.ts} +18 -1
- package/src/lib/sandbox/index.ts +19 -0
- package/src/lib/sandbox/manager.ts +76 -0
- package/src/lib/sandbox/sandbox.test.ts +158 -0
- package/src/lib/{fs.ts → sandbox/tree.ts} +6 -6
- package/src/lib/sandbox/types.ts +164 -0
- package/src/lib/session/index.ts +11 -0
- package/src/lib/{session.ts → session/session.ts} +76 -48
- package/src/lib/session/types.ts +93 -0
- package/src/lib/skills/fs-provider.ts +16 -15
- package/src/lib/skills/handler.ts +31 -0
- package/src/lib/skills/index.ts +5 -1
- package/src/lib/skills/register.ts +20 -0
- package/src/lib/skills/tool.ts +47 -0
- package/src/lib/state/index.ts +9 -0
- package/src/lib/{state-manager.ts → state/manager.ts} +10 -147
- package/src/lib/state/types.ts +134 -0
- package/src/lib/subagent/define.ts +71 -0
- package/src/lib/subagent/handler.ts +99 -0
- package/src/lib/subagent/index.ts +13 -0
- package/src/lib/subagent/register.ts +53 -0
- package/src/lib/subagent/tool.ts +80 -0
- package/src/lib/subagent/types.ts +92 -0
- package/src/lib/thread/index.ts +7 -0
- package/src/lib/{thread-manager.ts → thread/manager.ts} +1 -33
- package/src/lib/thread/types.ts +33 -0
- package/src/lib/tool-router/auto-append.ts +55 -0
- package/src/lib/tool-router/index.ts +41 -0
- package/src/lib/tool-router/router.ts +462 -0
- package/src/lib/tool-router/types.ts +478 -0
- package/src/lib/tool-router/with-sandbox.ts +70 -0
- package/src/lib/types.ts +5 -382
- package/src/tools/bash/bash.test.ts +53 -55
- package/src/tools/bash/handler.ts +23 -51
- package/src/tools/edit/handler.ts +67 -81
- package/src/tools/glob/handler.ts +60 -17
- package/src/tools/read-file/handler.ts +67 -0
- package/src/tools/read-skill/handler.ts +1 -31
- package/src/tools/read-skill/tool.ts +5 -47
- package/src/tools/subagent/handler.ts +1 -100
- package/src/tools/subagent/tool.ts +5 -93
- package/src/tools/task-create/handler.ts +1 -1
- package/src/tools/task-get/handler.ts +1 -1
- package/src/tools/task-list/handler.ts +1 -1
- package/src/tools/task-update/handler.ts +1 -1
- package/src/tools/write-file/handler.ts +47 -0
- package/src/workflow.ts +88 -47
- package/tsup.config.ts +8 -1
- package/dist/adapters/langchain/index.cjs.map +0 -1
- package/dist/adapters/langchain/index.js.map +0 -1
- package/dist/model-invoker-y_zlyMqu.d.cts +0 -892
- package/dist/model-invoker-y_zlyMqu.d.ts +0 -892
- package/src/lib/tool-router.ts +0 -977
- package/src/lib/workflow-helpers.ts +0 -50
- /package/src/lib/{thread-id.ts → thread/id.ts} +0 -0
package/dist/index.cjs
CHANGED
|
@@ -2,157 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
var workflow = require('@temporalio/workflow');
|
|
4
4
|
var z14 = require('zod');
|
|
5
|
-
var
|
|
6
|
-
var justBash = require('just-bash');
|
|
7
|
-
var promises = require('fs/promises');
|
|
5
|
+
var common = require('@temporalio/common');
|
|
8
6
|
var path = require('path');
|
|
7
|
+
var activity = require('@temporalio/activity');
|
|
9
8
|
|
|
10
9
|
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
11
10
|
|
|
12
11
|
var z14__default = /*#__PURE__*/_interopDefault(z14);
|
|
13
12
|
|
|
14
|
-
// src/lib/session.ts
|
|
15
|
-
var SUBAGENT_TOOL_NAME = "Subagent";
|
|
16
|
-
function buildSubagentDescription(subagents) {
|
|
17
|
-
const subagentList = subagents.map((s) => {
|
|
18
|
-
const continuation = s.allowThreadContinuation ? "\n*(Supports thread continuation \u2014 pass a threadId to resume a previous conversation)*" : "";
|
|
19
|
-
return `## ${s.agentName}
|
|
20
|
-
${s.description}${continuation}`;
|
|
21
|
-
}).join("\n\n");
|
|
22
|
-
return `The ${SUBAGENT_TOOL_NAME} tool launches specialized agents (subagents) that autonomously handle complex work. Each agent type has specific capabilities and tools available to it.
|
|
23
|
-
|
|
24
|
-
# Available subagents:
|
|
25
|
-
${subagentList}
|
|
26
|
-
`;
|
|
27
|
-
}
|
|
28
|
-
function createSubagentTool(subagents) {
|
|
29
|
-
if (subagents.length === 0) {
|
|
30
|
-
throw new Error("createTaskTool requires at least one subagent");
|
|
31
|
-
}
|
|
32
|
-
const names = subagents.map((s) => s.agentName);
|
|
33
|
-
const hasThreadContinuation = subagents.some(
|
|
34
|
-
(s) => s.allowThreadContinuation
|
|
35
|
-
);
|
|
36
|
-
const baseFields = {
|
|
37
|
-
subagent: z14__default.default.enum(names).describe("The type of subagent to launch"),
|
|
38
|
-
description: z14__default.default.string().describe("A short (3-5 word) description of the task"),
|
|
39
|
-
prompt: z14__default.default.string().describe("The task for the agent to perform")
|
|
40
|
-
};
|
|
41
|
-
const schema = hasThreadContinuation ? z14__default.default.object({
|
|
42
|
-
...baseFields,
|
|
43
|
-
threadId: z14__default.default.string().nullable().describe(
|
|
44
|
-
"Thread ID to continue an existing conversation, or null to start a new one"
|
|
45
|
-
)
|
|
46
|
-
}) : z14__default.default.object(baseFields);
|
|
47
|
-
return {
|
|
48
|
-
name: SUBAGENT_TOOL_NAME,
|
|
49
|
-
description: buildSubagentDescription(subagents),
|
|
50
|
-
schema
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
var BASE62 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
|
54
|
-
function getShortId(length = 12) {
|
|
55
|
-
const hex = workflow.uuid4().replace(/-/g, "");
|
|
56
|
-
let result = "";
|
|
57
|
-
for (let i = 0; i < length; i++) {
|
|
58
|
-
const byte = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
|
|
59
|
-
result += BASE62[byte % BASE62.length];
|
|
60
|
-
}
|
|
61
|
-
return result;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// src/tools/subagent/handler.ts
|
|
65
|
-
function createSubagentHandler(subagents) {
|
|
66
|
-
const { taskQueue: parentTaskQueue } = workflow.workflowInfo();
|
|
67
|
-
return async (args) => {
|
|
68
|
-
const config = subagents.find((s) => s.agentName === args.subagent);
|
|
69
|
-
if (!config) {
|
|
70
|
-
throw new Error(
|
|
71
|
-
`Unknown subagent: ${args.subagent}. Available: ${subagents.map((s) => s.agentName).join(", ")}`
|
|
72
|
-
);
|
|
73
|
-
}
|
|
74
|
-
const childWorkflowId = `${args.subagent}-${getShortId()}`;
|
|
75
|
-
const input = {
|
|
76
|
-
prompt: args.prompt,
|
|
77
|
-
...config.context && { context: config.context },
|
|
78
|
-
...args.threadId && config.allowThreadContinuation && { threadId: args.threadId }
|
|
79
|
-
};
|
|
80
|
-
const childOpts = {
|
|
81
|
-
workflowId: childWorkflowId,
|
|
82
|
-
args: [input],
|
|
83
|
-
taskQueue: config.taskQueue ?? parentTaskQueue
|
|
84
|
-
};
|
|
85
|
-
const { toolResponse, data, usage, threadId: childThreadId } = typeof config.workflow === "string" ? await workflow.executeChild(config.workflow, childOpts) : await workflow.executeChild(config.workflow, childOpts);
|
|
86
|
-
if (!toolResponse) {
|
|
87
|
-
return {
|
|
88
|
-
toolResponse: "Subagent workflow returned no response",
|
|
89
|
-
data: null,
|
|
90
|
-
...usage && { usage }
|
|
91
|
-
};
|
|
92
|
-
}
|
|
93
|
-
const validated = config.resultSchema ? config.resultSchema.safeParse(data) : null;
|
|
94
|
-
if (validated && !validated.success) {
|
|
95
|
-
return {
|
|
96
|
-
toolResponse: `Subagent workflow returned invalid data: ${validated.error.message}`,
|
|
97
|
-
data: null,
|
|
98
|
-
...usage && { usage }
|
|
99
|
-
};
|
|
100
|
-
}
|
|
101
|
-
let finalToolResponse = toolResponse;
|
|
102
|
-
if (config.allowThreadContinuation && childThreadId) {
|
|
103
|
-
finalToolResponse = typeof toolResponse === "string" ? `${toolResponse}
|
|
104
|
-
|
|
105
|
-
[Thread ID: ${childThreadId}]` : toolResponse;
|
|
106
|
-
}
|
|
107
|
-
return {
|
|
108
|
-
toolResponse: finalToolResponse,
|
|
109
|
-
data: validated ? validated.data : data,
|
|
110
|
-
...usage && { usage }
|
|
111
|
-
};
|
|
112
|
-
};
|
|
113
|
-
}
|
|
114
|
-
var READ_SKILL_TOOL_NAME = "ReadSkill";
|
|
115
|
-
function buildReadSkillDescription(skills) {
|
|
116
|
-
const skillList = skills.map((s) => `- **${s.name}**: ${s.description}`).join("\n");
|
|
117
|
-
return `Load the full instructions for a skill. Read the skill before following its instructions.
|
|
118
|
-
|
|
119
|
-
# Available skills:
|
|
120
|
-
${skillList}
|
|
121
|
-
`;
|
|
122
|
-
}
|
|
123
|
-
function createReadSkillTool(skills) {
|
|
124
|
-
if (skills.length === 0) {
|
|
125
|
-
throw new Error("createReadSkillTool requires at least one skill");
|
|
126
|
-
}
|
|
127
|
-
const names = skills.map((s) => s.name);
|
|
128
|
-
return {
|
|
129
|
-
name: READ_SKILL_TOOL_NAME,
|
|
130
|
-
description: buildReadSkillDescription(skills),
|
|
131
|
-
schema: z14__default.default.object({
|
|
132
|
-
skill_name: z14__default.default.enum(names).describe("The name of the skill to load")
|
|
133
|
-
})
|
|
134
|
-
};
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
// src/tools/read-skill/handler.ts
|
|
138
|
-
function createReadSkillHandler(skills) {
|
|
139
|
-
const skillMap = new Map(skills.map((s) => [s.name, s]));
|
|
140
|
-
return (args) => {
|
|
141
|
-
const skill = skillMap.get(args.skill_name);
|
|
142
|
-
if (!skill) {
|
|
143
|
-
return {
|
|
144
|
-
toolResponse: JSON.stringify({
|
|
145
|
-
error: `Skill "${args.skill_name}" not found`
|
|
146
|
-
}),
|
|
147
|
-
data: null
|
|
148
|
-
};
|
|
149
|
-
}
|
|
150
|
-
return {
|
|
151
|
-
toolResponse: skill.instructions,
|
|
152
|
-
data: null
|
|
153
|
-
};
|
|
154
|
-
};
|
|
155
|
-
}
|
|
13
|
+
// src/lib/session/session.ts
|
|
156
14
|
function createToolRouter(options) {
|
|
157
15
|
const { appendToolResult } = options;
|
|
158
16
|
const toolMap = /* @__PURE__ */ new Map();
|
|
@@ -160,45 +18,12 @@ function createToolRouter(options) {
|
|
|
160
18
|
toolMap.set(tool.name, tool);
|
|
161
19
|
}
|
|
162
20
|
const isEnabled = (tool) => tool.enabled ?? true;
|
|
163
|
-
if (options.
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
for (const s of options.subagents) {
|
|
167
|
-
if (s.hooks) subagentHooksMap.set(s.agentName, s.hooks);
|
|
168
|
-
}
|
|
169
|
-
const resolveSubagentName = (args) => args.subagent;
|
|
170
|
-
toolMap.set(SUBAGENT_TOOL_NAME, {
|
|
171
|
-
...createSubagentTool(options.subagents),
|
|
172
|
-
handler: createSubagentHandler(options.subagents),
|
|
173
|
-
...subagentHooksMap.size > 0 && {
|
|
174
|
-
hooks: {
|
|
175
|
-
onPreToolUse: async (ctx) => {
|
|
176
|
-
const hooks = subagentHooksMap.get(resolveSubagentName(ctx.args));
|
|
177
|
-
return hooks?.onPreExecution?.(ctx) ?? {};
|
|
178
|
-
},
|
|
179
|
-
onPostToolUse: async (ctx) => {
|
|
180
|
-
const hooks = subagentHooksMap.get(resolveSubagentName(ctx.args));
|
|
181
|
-
await hooks?.onPostExecution?.(ctx);
|
|
182
|
-
},
|
|
183
|
-
onPostToolUseFailure: async (ctx) => {
|
|
184
|
-
const hooks = subagentHooksMap.get(resolveSubagentName(ctx.args));
|
|
185
|
-
return hooks?.onExecutionFailure?.(ctx) ?? {};
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
});
|
|
21
|
+
if (options.plugins) {
|
|
22
|
+
for (const plugin of options.plugins) {
|
|
23
|
+
toolMap.set(plugin.name, plugin);
|
|
190
24
|
}
|
|
191
25
|
}
|
|
192
|
-
|
|
193
|
-
toolMap.set(READ_SKILL_TOOL_NAME, {
|
|
194
|
-
...createReadSkillTool(options.skills),
|
|
195
|
-
handler: createReadSkillHandler(options.skills)
|
|
196
|
-
});
|
|
197
|
-
}
|
|
198
|
-
async function processToolCall(toolCall, turn, handlerContext) {
|
|
199
|
-
const startTime = Date.now();
|
|
200
|
-
const tool = toolMap.get(toolCall.name);
|
|
201
|
-
const toolHooks = tool?.hooks;
|
|
26
|
+
async function runPreHooks(toolCall, tool, turn) {
|
|
202
27
|
let effectiveArgs = toolCall.args;
|
|
203
28
|
if (options.hooks?.onPreToolUse) {
|
|
204
29
|
const preResult = await options.hooks.onPreToolUse({
|
|
@@ -206,58 +31,105 @@ function createToolRouter(options) {
|
|
|
206
31
|
threadId: options.threadId,
|
|
207
32
|
turn
|
|
208
33
|
});
|
|
209
|
-
if (preResult?.skip) {
|
|
210
|
-
|
|
211
|
-
threadId: options.threadId,
|
|
212
|
-
toolCallId: toolCall.id,
|
|
213
|
-
toolName: toolCall.name,
|
|
214
|
-
content: JSON.stringify({
|
|
215
|
-
skipped: true,
|
|
216
|
-
reason: "Skipped by PreToolUse hook"
|
|
217
|
-
})
|
|
218
|
-
});
|
|
219
|
-
return null;
|
|
220
|
-
}
|
|
221
|
-
if (preResult?.modifiedArgs !== void 0) {
|
|
34
|
+
if (preResult?.skip) return { skip: true };
|
|
35
|
+
if (preResult?.modifiedArgs !== void 0)
|
|
222
36
|
effectiveArgs = preResult.modifiedArgs;
|
|
223
|
-
}
|
|
224
37
|
}
|
|
225
|
-
if (
|
|
226
|
-
const preResult = await
|
|
38
|
+
if (tool?.hooks?.onPreToolUse) {
|
|
39
|
+
const preResult = await tool.hooks.onPreToolUse({
|
|
227
40
|
args: effectiveArgs,
|
|
228
41
|
threadId: options.threadId,
|
|
229
42
|
turn
|
|
230
43
|
});
|
|
231
|
-
if (preResult?.skip) {
|
|
232
|
-
|
|
233
|
-
threadId: options.threadId,
|
|
234
|
-
toolCallId: toolCall.id,
|
|
235
|
-
toolName: toolCall.name,
|
|
236
|
-
content: JSON.stringify({
|
|
237
|
-
skipped: true,
|
|
238
|
-
reason: "Skipped by tool PreToolUse hook"
|
|
239
|
-
})
|
|
240
|
-
});
|
|
241
|
-
return null;
|
|
242
|
-
}
|
|
243
|
-
if (preResult?.modifiedArgs !== void 0) {
|
|
44
|
+
if (preResult?.skip) return { skip: true };
|
|
45
|
+
if (preResult?.modifiedArgs !== void 0)
|
|
244
46
|
effectiveArgs = preResult.modifiedArgs;
|
|
245
|
-
}
|
|
246
47
|
}
|
|
48
|
+
return { skip: false, args: effectiveArgs };
|
|
49
|
+
}
|
|
50
|
+
async function runFailureHooks(toolCall, tool, error, effectiveArgs, turn) {
|
|
51
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
52
|
+
const errorStr = String(error);
|
|
53
|
+
if (tool?.hooks?.onPostToolUseFailure) {
|
|
54
|
+
const r = await tool.hooks.onPostToolUseFailure({
|
|
55
|
+
args: effectiveArgs,
|
|
56
|
+
error: err,
|
|
57
|
+
threadId: options.threadId,
|
|
58
|
+
turn
|
|
59
|
+
});
|
|
60
|
+
if (r?.fallbackContent !== void 0)
|
|
61
|
+
return { content: r.fallbackContent, result: { error: errorStr, recovered: true } };
|
|
62
|
+
if (r?.suppress)
|
|
63
|
+
return {
|
|
64
|
+
content: JSON.stringify({ error: errorStr, suppressed: true }),
|
|
65
|
+
result: { error: errorStr, suppressed: true }
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
if (options.hooks?.onPostToolUseFailure) {
|
|
69
|
+
const r = await options.hooks.onPostToolUseFailure({
|
|
70
|
+
toolCall,
|
|
71
|
+
error: err,
|
|
72
|
+
threadId: options.threadId,
|
|
73
|
+
turn
|
|
74
|
+
});
|
|
75
|
+
if (r?.fallbackContent !== void 0)
|
|
76
|
+
return { content: r.fallbackContent, result: { error: errorStr, recovered: true } };
|
|
77
|
+
if (r?.suppress)
|
|
78
|
+
return {
|
|
79
|
+
content: JSON.stringify({ error: errorStr, suppressed: true }),
|
|
80
|
+
result: { error: errorStr, suppressed: true }
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
throw workflow.ApplicationFailure.fromError(error, { nonRetryable: true });
|
|
84
|
+
}
|
|
85
|
+
async function runPostHooks(toolCall, tool, toolResult, effectiveArgs, turn, durationMs) {
|
|
86
|
+
if (tool?.hooks?.onPostToolUse) {
|
|
87
|
+
await tool.hooks.onPostToolUse({
|
|
88
|
+
args: effectiveArgs,
|
|
89
|
+
result: toolResult.data,
|
|
90
|
+
threadId: options.threadId,
|
|
91
|
+
turn,
|
|
92
|
+
durationMs
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
if (options.hooks?.onPostToolUse) {
|
|
96
|
+
await options.hooks.onPostToolUse({
|
|
97
|
+
toolCall,
|
|
98
|
+
result: toolResult,
|
|
99
|
+
threadId: options.threadId,
|
|
100
|
+
turn,
|
|
101
|
+
durationMs
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
async function processToolCall(toolCall, turn, sandboxId) {
|
|
106
|
+
const startTime = Date.now();
|
|
107
|
+
const tool = toolMap.get(toolCall.name);
|
|
108
|
+
const preResult = await runPreHooks(toolCall, tool, turn);
|
|
109
|
+
if (preResult.skip) {
|
|
110
|
+
await appendToolResult({
|
|
111
|
+
threadId: options.threadId,
|
|
112
|
+
toolCallId: toolCall.id,
|
|
113
|
+
toolName: toolCall.name,
|
|
114
|
+
content: JSON.stringify({ skipped: true, reason: "Skipped by PreToolUse hook" })
|
|
115
|
+
});
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
const effectiveArgs = preResult.args;
|
|
247
119
|
let result;
|
|
248
120
|
let content;
|
|
249
121
|
let resultAppended = false;
|
|
250
122
|
try {
|
|
251
123
|
if (tool) {
|
|
252
|
-
const
|
|
253
|
-
...handlerContext ?? {},
|
|
124
|
+
const routerContext = {
|
|
254
125
|
threadId: options.threadId,
|
|
255
126
|
toolCallId: toolCall.id,
|
|
256
|
-
toolName: toolCall.name
|
|
127
|
+
toolName: toolCall.name,
|
|
128
|
+
...sandboxId !== void 0 && { sandboxId }
|
|
257
129
|
};
|
|
258
130
|
const response = await tool.handler(
|
|
259
131
|
effectiveArgs,
|
|
260
|
-
|
|
132
|
+
routerContext
|
|
261
133
|
);
|
|
262
134
|
result = response.data;
|
|
263
135
|
content = response.toolResponse;
|
|
@@ -267,47 +139,9 @@ function createToolRouter(options) {
|
|
|
267
139
|
content = JSON.stringify(result, null, 2);
|
|
268
140
|
}
|
|
269
141
|
} catch (error) {
|
|
270
|
-
const
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
const failureResult = await toolHooks.onPostToolUseFailure({
|
|
274
|
-
args: effectiveArgs,
|
|
275
|
-
error: err,
|
|
276
|
-
threadId: options.threadId,
|
|
277
|
-
turn
|
|
278
|
-
});
|
|
279
|
-
if (failureResult?.fallbackContent !== void 0) {
|
|
280
|
-
content = failureResult.fallbackContent;
|
|
281
|
-
result = { error: String(error), recovered: true };
|
|
282
|
-
recovered = true;
|
|
283
|
-
} else if (failureResult?.suppress) {
|
|
284
|
-
content = JSON.stringify({ error: String(error), suppressed: true });
|
|
285
|
-
result = { error: String(error), suppressed: true };
|
|
286
|
-
recovered = true;
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
if (!recovered && options.hooks?.onPostToolUseFailure) {
|
|
290
|
-
const failureResult = await options.hooks.onPostToolUseFailure({
|
|
291
|
-
toolCall,
|
|
292
|
-
error: err,
|
|
293
|
-
threadId: options.threadId,
|
|
294
|
-
turn
|
|
295
|
-
});
|
|
296
|
-
if (failureResult?.fallbackContent !== void 0) {
|
|
297
|
-
content = failureResult.fallbackContent;
|
|
298
|
-
result = { error: String(error), recovered: true };
|
|
299
|
-
recovered = true;
|
|
300
|
-
} else if (failureResult?.suppress) {
|
|
301
|
-
content = JSON.stringify({ error: String(error), suppressed: true });
|
|
302
|
-
result = { error: String(error), suppressed: true };
|
|
303
|
-
recovered = true;
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
if (!recovered) {
|
|
307
|
-
throw workflow.ApplicationFailure.fromError(error, {
|
|
308
|
-
nonRetryable: true
|
|
309
|
-
});
|
|
310
|
-
}
|
|
142
|
+
const recovery = await runFailureHooks(toolCall, tool, error, effectiveArgs, turn);
|
|
143
|
+
result = recovery.result;
|
|
144
|
+
content = recovery.content;
|
|
311
145
|
}
|
|
312
146
|
if (!resultAppended) {
|
|
313
147
|
await appendToolResult({
|
|
@@ -322,29 +156,10 @@ function createToolRouter(options) {
|
|
|
322
156
|
name: toolCall.name,
|
|
323
157
|
data: result
|
|
324
158
|
};
|
|
325
|
-
|
|
326
|
-
if (toolHooks?.onPostToolUse) {
|
|
327
|
-
await toolHooks.onPostToolUse({
|
|
328
|
-
args: effectiveArgs,
|
|
329
|
-
result,
|
|
330
|
-
threadId: options.threadId,
|
|
331
|
-
turn,
|
|
332
|
-
durationMs
|
|
333
|
-
});
|
|
334
|
-
}
|
|
335
|
-
if (options.hooks?.onPostToolUse) {
|
|
336
|
-
await options.hooks.onPostToolUse({
|
|
337
|
-
toolCall,
|
|
338
|
-
result: toolResult,
|
|
339
|
-
threadId: options.threadId,
|
|
340
|
-
turn,
|
|
341
|
-
durationMs
|
|
342
|
-
});
|
|
343
|
-
}
|
|
159
|
+
await runPostHooks(toolCall, tool, toolResult, effectiveArgs, turn, Date.now() - startTime);
|
|
344
160
|
return toolResult;
|
|
345
161
|
}
|
|
346
162
|
return {
|
|
347
|
-
// --- Methods from registry ---
|
|
348
163
|
hasTools() {
|
|
349
164
|
return Array.from(toolMap.values()).some(isEnabled);
|
|
350
165
|
},
|
|
@@ -368,32 +183,25 @@ function createToolRouter(options) {
|
|
|
368
183
|
return Array.from(toolMap.entries()).filter(([, tool]) => isEnabled(tool)).map(([name]) => name);
|
|
369
184
|
},
|
|
370
185
|
getToolDefinitions() {
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
description: tool.description,
|
|
379
|
-
schema: tool.schema,
|
|
380
|
-
strict: tool.strict,
|
|
381
|
-
max_uses: tool.max_uses
|
|
382
|
-
})),
|
|
383
|
-
...activeSubagents.length > 0 ? [createSubagentTool(activeSubagents)] : [],
|
|
384
|
-
...activeSkills.length > 0 ? [createReadSkillTool(activeSkills)] : []
|
|
385
|
-
];
|
|
186
|
+
return Array.from(toolMap).filter(([, tool]) => isEnabled(tool)).map(([name, tool]) => ({
|
|
187
|
+
name,
|
|
188
|
+
description: tool.description,
|
|
189
|
+
schema: tool.schema,
|
|
190
|
+
strict: tool.strict,
|
|
191
|
+
max_uses: tool.max_uses
|
|
192
|
+
}));
|
|
386
193
|
},
|
|
387
|
-
// --- Methods for processing tool calls ---
|
|
388
194
|
async processToolCalls(toolCalls, context) {
|
|
389
195
|
if (toolCalls.length === 0) {
|
|
390
196
|
return [];
|
|
391
197
|
}
|
|
392
198
|
const turn = context?.turn ?? 0;
|
|
393
|
-
const
|
|
199
|
+
const sandboxId = context?.sandboxId;
|
|
394
200
|
if (options.parallel) {
|
|
395
201
|
const results2 = await Promise.all(
|
|
396
|
-
toolCalls.map(
|
|
202
|
+
toolCalls.map(
|
|
203
|
+
(tc) => processToolCall(tc, turn, sandboxId)
|
|
204
|
+
)
|
|
397
205
|
);
|
|
398
206
|
return results2.filter(
|
|
399
207
|
(r) => r !== null
|
|
@@ -401,7 +209,11 @@ function createToolRouter(options) {
|
|
|
401
209
|
}
|
|
402
210
|
const results = [];
|
|
403
211
|
for (const toolCall of toolCalls) {
|
|
404
|
-
const result = await processToolCall(
|
|
212
|
+
const result = await processToolCall(
|
|
213
|
+
toolCall,
|
|
214
|
+
turn,
|
|
215
|
+
sandboxId
|
|
216
|
+
);
|
|
405
217
|
if (result !== null) {
|
|
406
218
|
results.push(result);
|
|
407
219
|
}
|
|
@@ -413,17 +225,16 @@ function createToolRouter(options) {
|
|
|
413
225
|
if (matchingCalls.length === 0) {
|
|
414
226
|
return [];
|
|
415
227
|
}
|
|
416
|
-
const handlerContext = context?.handlerContext ?? {};
|
|
417
228
|
const processOne = async (toolCall) => {
|
|
418
|
-
const
|
|
419
|
-
...handlerContext ?? {},
|
|
229
|
+
const routerContext = {
|
|
420
230
|
threadId: options.threadId,
|
|
421
231
|
toolCallId: toolCall.id,
|
|
422
|
-
toolName: toolCall.name
|
|
232
|
+
toolName: toolCall.name,
|
|
233
|
+
...context?.sandboxId !== void 0 && { sandboxId: context.sandboxId }
|
|
423
234
|
};
|
|
424
235
|
const response = await handler(
|
|
425
236
|
toolCall.args,
|
|
426
|
-
|
|
237
|
+
routerContext
|
|
427
238
|
);
|
|
428
239
|
if (!response.resultAppended) {
|
|
429
240
|
await appendToolResult({
|
|
@@ -448,7 +259,6 @@ function createToolRouter(options) {
|
|
|
448
259
|
}
|
|
449
260
|
return results;
|
|
450
261
|
},
|
|
451
|
-
// --- Utility methods ---
|
|
452
262
|
filterByName(toolCalls, name) {
|
|
453
263
|
return toolCalls.filter(
|
|
454
264
|
(tc) => tc.name === name
|
|
@@ -462,36 +272,201 @@ function createToolRouter(options) {
|
|
|
462
272
|
}
|
|
463
273
|
};
|
|
464
274
|
}
|
|
465
|
-
function
|
|
275
|
+
function defineTool(tool) {
|
|
276
|
+
return tool;
|
|
277
|
+
}
|
|
278
|
+
function hasNoOtherToolCalls(toolCalls, excludeName) {
|
|
279
|
+
return toolCalls.filter((tc) => tc.name !== excludeName).length === 0;
|
|
280
|
+
}
|
|
281
|
+
var BASE62 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
|
282
|
+
function getShortId(length = 12) {
|
|
283
|
+
const hex = workflow.uuid4().replace(/-/g, "");
|
|
284
|
+
let result = "";
|
|
285
|
+
for (let i = 0; i < length; i++) {
|
|
286
|
+
const byte = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
|
|
287
|
+
result += BASE62[byte % BASE62.length];
|
|
288
|
+
}
|
|
289
|
+
return result;
|
|
290
|
+
}
|
|
291
|
+
var SUBAGENT_TOOL_NAME = "Subagent";
|
|
292
|
+
function buildSubagentDescription(subagents) {
|
|
293
|
+
const subagentList = subagents.map((s) => {
|
|
294
|
+
const continuation = s.allowThreadContinuation ? "\n*(Supports thread continuation \u2014 pass a threadId to resume a previous conversation)*" : "";
|
|
295
|
+
return `## ${s.agentName}
|
|
296
|
+
${s.description}${continuation}`;
|
|
297
|
+
}).join("\n\n");
|
|
298
|
+
return `The ${SUBAGENT_TOOL_NAME} tool launches specialized agents (subagents) that autonomously handle complex work. Each agent type has specific capabilities and tools available to it.
|
|
299
|
+
|
|
300
|
+
# Available subagents:
|
|
301
|
+
${subagentList}
|
|
302
|
+
`;
|
|
303
|
+
}
|
|
304
|
+
function createSubagentTool(subagents) {
|
|
305
|
+
if (subagents.length === 0) {
|
|
306
|
+
throw new Error("createSubagentTool requires at least one subagent");
|
|
307
|
+
}
|
|
308
|
+
const names = subagents.map((s) => s.agentName);
|
|
309
|
+
const hasThreadContinuation = subagents.some(
|
|
310
|
+
(s) => s.allowThreadContinuation
|
|
311
|
+
);
|
|
312
|
+
const baseFields = {
|
|
313
|
+
subagent: z14__default.default.enum(names).describe("The type of subagent to launch"),
|
|
314
|
+
description: z14__default.default.string().describe("A short (3-5 word) description of the task"),
|
|
315
|
+
prompt: z14__default.default.string().describe("The task for the agent to perform")
|
|
316
|
+
};
|
|
317
|
+
const schema = hasThreadContinuation ? z14__default.default.object({
|
|
318
|
+
...baseFields,
|
|
319
|
+
threadId: z14__default.default.string().nullable().describe(
|
|
320
|
+
"Thread ID to continue an existing conversation, or null to start a new one"
|
|
321
|
+
)
|
|
322
|
+
}) : z14__default.default.object(baseFields);
|
|
323
|
+
return {
|
|
324
|
+
name: SUBAGENT_TOOL_NAME,
|
|
325
|
+
description: buildSubagentDescription(subagents),
|
|
326
|
+
schema
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
function createSubagentHandler(subagents) {
|
|
330
|
+
const { taskQueue: parentTaskQueue } = workflow.workflowInfo();
|
|
466
331
|
return async (args, context) => {
|
|
467
|
-
const
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
332
|
+
const config = subagents.find((s) => s.agentName === args.subagent);
|
|
333
|
+
if (!config) {
|
|
334
|
+
throw new Error(
|
|
335
|
+
`Unknown subagent: ${args.subagent}. Available: ${subagents.map((s) => s.agentName).join(", ")}`
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
const childWorkflowId = `${args.subagent}-${getShortId()}`;
|
|
339
|
+
const { sandboxId: parentSandboxId } = context;
|
|
340
|
+
const inheritSandbox = config.sandbox !== "own" && !!parentSandboxId;
|
|
341
|
+
const input = {
|
|
342
|
+
prompt: args.prompt,
|
|
343
|
+
...config.context && { context: config.context },
|
|
344
|
+
...args.threadId && args.threadId !== null && config.allowThreadContinuation && { previousThreadId: args.threadId },
|
|
345
|
+
...inheritSandbox && { sandboxId: parentSandboxId }
|
|
346
|
+
};
|
|
347
|
+
const childOpts = {
|
|
348
|
+
workflowId: childWorkflowId,
|
|
349
|
+
args: [input],
|
|
350
|
+
taskQueue: config.taskQueue ?? parentTaskQueue
|
|
351
|
+
};
|
|
352
|
+
const {
|
|
353
|
+
toolResponse,
|
|
354
|
+
data,
|
|
355
|
+
usage,
|
|
356
|
+
threadId: childThreadId
|
|
357
|
+
} = typeof config.workflow === "string" ? await workflow.executeChild(config.workflow, childOpts) : await workflow.executeChild(config.workflow, childOpts);
|
|
358
|
+
if (!toolResponse) {
|
|
359
|
+
return {
|
|
360
|
+
toolResponse: "Subagent workflow returned no response",
|
|
361
|
+
data: null,
|
|
362
|
+
...usage && { usage }
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
const validated = config.resultSchema ? config.resultSchema.safeParse(data) : null;
|
|
366
|
+
if (validated && !validated.success) {
|
|
367
|
+
return {
|
|
368
|
+
toolResponse: `Subagent workflow returned invalid data: ${validated.error.message}`,
|
|
369
|
+
data: null,
|
|
370
|
+
...usage && { usage }
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
let finalToolResponse = toolResponse;
|
|
374
|
+
if (config.allowThreadContinuation && childThreadId) {
|
|
375
|
+
finalToolResponse = typeof toolResponse === "string" ? `${toolResponse}
|
|
376
|
+
|
|
377
|
+
[Thread ID: ${childThreadId}]` : toolResponse;
|
|
378
|
+
}
|
|
477
379
|
return {
|
|
478
|
-
toolResponse:
|
|
479
|
-
data:
|
|
480
|
-
|
|
380
|
+
toolResponse: finalToolResponse,
|
|
381
|
+
data: validated ? validated.data : data,
|
|
382
|
+
...usage && { usage }
|
|
481
383
|
};
|
|
482
384
|
};
|
|
483
385
|
}
|
|
484
|
-
|
|
485
|
-
|
|
386
|
+
|
|
387
|
+
// src/lib/subagent/register.ts
|
|
388
|
+
function buildSubagentRegistration(subagents) {
|
|
389
|
+
const enabled = subagents.filter((s) => s.enabled ?? true);
|
|
390
|
+
if (enabled.length === 0) return null;
|
|
391
|
+
const subagentHooksMap = /* @__PURE__ */ new Map();
|
|
392
|
+
for (const s of enabled) {
|
|
393
|
+
if (s.hooks) subagentHooksMap.set(s.agentName, s.hooks);
|
|
394
|
+
}
|
|
395
|
+
const resolveSubagentName = (args) => args.subagent;
|
|
396
|
+
return {
|
|
397
|
+
...createSubagentTool(enabled),
|
|
398
|
+
handler: createSubagentHandler(enabled),
|
|
399
|
+
...subagentHooksMap.size > 0 && {
|
|
400
|
+
hooks: {
|
|
401
|
+
onPreToolUse: async (ctx) => {
|
|
402
|
+
const hooks = subagentHooksMap.get(resolveSubagentName(ctx.args));
|
|
403
|
+
return hooks?.onPreExecution?.(ctx) ?? {};
|
|
404
|
+
},
|
|
405
|
+
onPostToolUse: async (ctx) => {
|
|
406
|
+
const hooks = subagentHooksMap.get(resolveSubagentName(ctx.args));
|
|
407
|
+
await hooks?.onPostExecution?.(ctx);
|
|
408
|
+
},
|
|
409
|
+
onPostToolUseFailure: async (ctx) => {
|
|
410
|
+
const hooks = subagentHooksMap.get(resolveSubagentName(ctx.args));
|
|
411
|
+
return hooks?.onExecutionFailure?.(ctx) ?? {};
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
};
|
|
486
416
|
}
|
|
487
|
-
|
|
488
|
-
|
|
417
|
+
var READ_SKILL_TOOL_NAME = "ReadSkill";
|
|
418
|
+
function buildReadSkillDescription(skills) {
|
|
419
|
+
const skillList = skills.map((s) => `- **${s.name}**: ${s.description}`).join("\n");
|
|
420
|
+
return `Load the full instructions for a skill. Read the skill before following its instructions.
|
|
421
|
+
|
|
422
|
+
# Available skills:
|
|
423
|
+
${skillList}
|
|
424
|
+
`;
|
|
489
425
|
}
|
|
490
|
-
function
|
|
491
|
-
|
|
426
|
+
function createReadSkillTool(skills) {
|
|
427
|
+
if (skills.length === 0) {
|
|
428
|
+
throw new Error("createReadSkillTool requires at least one skill");
|
|
429
|
+
}
|
|
430
|
+
const names = skills.map((s) => s.name);
|
|
431
|
+
return {
|
|
432
|
+
name: READ_SKILL_TOOL_NAME,
|
|
433
|
+
description: buildReadSkillDescription(skills),
|
|
434
|
+
schema: z14__default.default.object({
|
|
435
|
+
skill_name: z14__default.default.enum(names).describe("The name of the skill to load")
|
|
436
|
+
})
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// src/lib/skills/handler.ts
|
|
441
|
+
function createReadSkillHandler(skills) {
|
|
442
|
+
const skillMap = new Map(skills.map((s) => [s.name, s]));
|
|
443
|
+
return (args) => {
|
|
444
|
+
const skill = skillMap.get(args.skill_name);
|
|
445
|
+
if (!skill) {
|
|
446
|
+
return {
|
|
447
|
+
toolResponse: JSON.stringify({
|
|
448
|
+
error: `Skill "${args.skill_name}" not found`
|
|
449
|
+
}),
|
|
450
|
+
data: null
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
return {
|
|
454
|
+
toolResponse: skill.instructions,
|
|
455
|
+
data: null
|
|
456
|
+
};
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// src/lib/skills/register.ts
|
|
461
|
+
function buildSkillRegistration(skills) {
|
|
462
|
+
if (skills.length === 0) return null;
|
|
463
|
+
return {
|
|
464
|
+
...createReadSkillTool(skills),
|
|
465
|
+
handler: createReadSkillHandler(skills)
|
|
466
|
+
};
|
|
492
467
|
}
|
|
493
468
|
|
|
494
|
-
// src/lib/session.ts
|
|
469
|
+
// src/lib/session/session.ts
|
|
495
470
|
var createSession = async ({
|
|
496
471
|
threadId: providedThreadId,
|
|
497
472
|
agentName,
|
|
@@ -507,7 +482,9 @@ var createSession = async ({
|
|
|
507
482
|
hooks = {},
|
|
508
483
|
appendSystemPrompt = true,
|
|
509
484
|
continueThread = false,
|
|
510
|
-
waitForInputTimeout = "48h"
|
|
485
|
+
waitForInputTimeout = "48h",
|
|
486
|
+
sandbox: sandboxOps,
|
|
487
|
+
sandboxId: inheritedSandboxId
|
|
511
488
|
}) => {
|
|
512
489
|
const threadId = providedThreadId ?? getShortId();
|
|
513
490
|
const {
|
|
@@ -516,13 +493,21 @@ var createSession = async ({
|
|
|
516
493
|
initializeThread,
|
|
517
494
|
appendSystemMessage
|
|
518
495
|
} = threadOps ?? proxyDefaultThreadOps();
|
|
496
|
+
const plugins = [];
|
|
497
|
+
if (subagents) {
|
|
498
|
+
const reg = buildSubagentRegistration(subagents);
|
|
499
|
+
if (reg) plugins.push(reg);
|
|
500
|
+
}
|
|
501
|
+
if (skills) {
|
|
502
|
+
const reg = buildSkillRegistration(skills);
|
|
503
|
+
if (reg) plugins.push(reg);
|
|
504
|
+
}
|
|
519
505
|
const toolRouter = createToolRouter({
|
|
520
506
|
tools,
|
|
521
507
|
appendToolResult,
|
|
522
508
|
threadId,
|
|
523
509
|
hooks,
|
|
524
|
-
|
|
525
|
-
skills,
|
|
510
|
+
plugins,
|
|
526
511
|
parallel: processToolsInParallel
|
|
527
512
|
});
|
|
528
513
|
const callSessionEnd = async (exitReason, turns) => {
|
|
@@ -559,6 +544,17 @@ var createSession = async ({
|
|
|
559
544
|
stateManager.run();
|
|
560
545
|
}
|
|
561
546
|
);
|
|
547
|
+
let sandboxId = inheritedSandboxId;
|
|
548
|
+
const ownsSandbox = !sandboxId && !!sandboxOps;
|
|
549
|
+
if (ownsSandbox) {
|
|
550
|
+
const result = await sandboxOps.createSandbox({ id: threadId });
|
|
551
|
+
sandboxId = result.sandboxId;
|
|
552
|
+
if (result.stateUpdate) {
|
|
553
|
+
stateManager.mergeUpdate(
|
|
554
|
+
result.stateUpdate
|
|
555
|
+
);
|
|
556
|
+
}
|
|
557
|
+
}
|
|
562
558
|
if (hooks.onSessionStart) {
|
|
563
559
|
await hooks.onSessionStart({
|
|
564
560
|
threadId,
|
|
@@ -599,6 +595,7 @@ var createSession = async ({
|
|
|
599
595
|
stateManager.complete();
|
|
600
596
|
exitReason = "completed";
|
|
601
597
|
return {
|
|
598
|
+
threadId,
|
|
602
599
|
finalMessage: message,
|
|
603
600
|
exitReason,
|
|
604
601
|
usage: stateManager.getTotalUsage()
|
|
@@ -622,7 +619,8 @@ var createSession = async ({
|
|
|
622
619
|
const toolCallResults = await toolRouter.processToolCalls(
|
|
623
620
|
parsedToolCalls,
|
|
624
621
|
{
|
|
625
|
-
turn: currentTurn
|
|
622
|
+
turn: currentTurn,
|
|
623
|
+
...sandboxId !== void 0 && { sandboxId }
|
|
626
624
|
}
|
|
627
625
|
);
|
|
628
626
|
for (const result of toolCallResults) {
|
|
@@ -650,8 +648,12 @@ var createSession = async ({
|
|
|
650
648
|
throw workflow.ApplicationFailure.fromError(error);
|
|
651
649
|
} finally {
|
|
652
650
|
await callSessionEnd(exitReason, stateManager.getTurns());
|
|
651
|
+
if (ownsSandbox && sandboxId && sandboxOps) {
|
|
652
|
+
await sandboxOps.destroySandbox(sandboxId);
|
|
653
|
+
}
|
|
653
654
|
}
|
|
654
655
|
return {
|
|
656
|
+
threadId,
|
|
655
657
|
finalMessage: null,
|
|
656
658
|
exitReason,
|
|
657
659
|
usage: stateManager.getTotalUsage()
|
|
@@ -672,16 +674,94 @@ function proxyDefaultThreadOps(options) {
|
|
|
672
674
|
}
|
|
673
675
|
);
|
|
674
676
|
}
|
|
677
|
+
function proxySandboxOps(options) {
|
|
678
|
+
return workflow.proxyActivities(
|
|
679
|
+
options ?? {
|
|
680
|
+
startToCloseTimeout: "30s",
|
|
681
|
+
retry: {
|
|
682
|
+
maximumAttempts: 3,
|
|
683
|
+
initialInterval: "2s",
|
|
684
|
+
maximumInterval: "30s",
|
|
685
|
+
backoffCoefficient: 2
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
);
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
// src/lib/thread/manager.ts
|
|
692
|
+
var THREAD_TTL_SECONDS = 60 * 60 * 24 * 90;
|
|
693
|
+
var APPEND_IDEMPOTENT_SCRIPT = `
|
|
694
|
+
if redis.call('EXISTS', KEYS[1]) == 1 then
|
|
695
|
+
return 0
|
|
696
|
+
end
|
|
697
|
+
for i = 2, #ARGV do
|
|
698
|
+
redis.call('RPUSH', KEYS[2], ARGV[i])
|
|
699
|
+
end
|
|
700
|
+
redis.call('EXPIRE', KEYS[2], tonumber(ARGV[1]))
|
|
701
|
+
redis.call('SET', KEYS[1], '1', 'EX', tonumber(ARGV[1]))
|
|
702
|
+
return 1
|
|
703
|
+
`;
|
|
704
|
+
function getThreadKey(threadId, key) {
|
|
705
|
+
return `thread:${threadId}:${key}`;
|
|
706
|
+
}
|
|
707
|
+
function createThreadManager(config) {
|
|
708
|
+
const {
|
|
709
|
+
redis,
|
|
710
|
+
threadId,
|
|
711
|
+
key = "messages",
|
|
712
|
+
serialize = (m) => JSON.stringify(m),
|
|
713
|
+
deserialize = (raw) => JSON.parse(raw),
|
|
714
|
+
idOf
|
|
715
|
+
} = config;
|
|
716
|
+
const redisKey = getThreadKey(threadId, key);
|
|
717
|
+
const metaKey = getThreadKey(threadId, `${key}:meta`);
|
|
718
|
+
async function assertThreadExists() {
|
|
719
|
+
const exists = await redis.exists(metaKey);
|
|
720
|
+
if (!exists) {
|
|
721
|
+
throw new Error(`Thread "${threadId}" (key: ${key}) does not exist`);
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
return {
|
|
725
|
+
async initialize() {
|
|
726
|
+
await redis.del(redisKey);
|
|
727
|
+
await redis.set(metaKey, "1", "EX", THREAD_TTL_SECONDS);
|
|
728
|
+
},
|
|
729
|
+
async load() {
|
|
730
|
+
await assertThreadExists();
|
|
731
|
+
const data = await redis.lrange(redisKey, 0, -1);
|
|
732
|
+
return data.map(deserialize);
|
|
733
|
+
},
|
|
734
|
+
async append(messages) {
|
|
735
|
+
if (messages.length === 0) return;
|
|
736
|
+
await assertThreadExists();
|
|
737
|
+
if (idOf) {
|
|
738
|
+
const dedupId = messages.map(idOf).join(":");
|
|
739
|
+
const dedupKey = getThreadKey(threadId, `dedup:${dedupId}`);
|
|
740
|
+
await redis.eval(
|
|
741
|
+
APPEND_IDEMPOTENT_SCRIPT,
|
|
742
|
+
2,
|
|
743
|
+
dedupKey,
|
|
744
|
+
redisKey,
|
|
745
|
+
String(THREAD_TTL_SECONDS),
|
|
746
|
+
...messages.map(serialize)
|
|
747
|
+
);
|
|
748
|
+
} else {
|
|
749
|
+
await redis.rpush(redisKey, ...messages.map(serialize));
|
|
750
|
+
await redis.expire(redisKey, THREAD_TTL_SECONDS);
|
|
751
|
+
}
|
|
752
|
+
},
|
|
753
|
+
async delete() {
|
|
754
|
+
await redis.del(redisKey, metaKey);
|
|
755
|
+
}
|
|
756
|
+
};
|
|
757
|
+
}
|
|
675
758
|
|
|
676
759
|
// src/lib/types.ts
|
|
677
|
-
var agentQueryName = (agentName) => `get${agentName}State`;
|
|
678
|
-
var agentStateChangeUpdateName = (agentName) => `waitFor${agentName}StateChange`;
|
|
679
760
|
function isTerminalStatus(status) {
|
|
680
761
|
return status === "COMPLETED" || status === "FAILED" || status === "CANCELLED";
|
|
681
762
|
}
|
|
682
763
|
function createAgentStateManager({
|
|
683
|
-
initialState
|
|
684
|
-
agentName
|
|
764
|
+
initialState
|
|
685
765
|
}) {
|
|
686
766
|
let status = initialState?.status ?? "RUNNING";
|
|
687
767
|
let version = initialState?.version ?? 0;
|
|
@@ -712,11 +792,9 @@ function createAgentStateManager({
|
|
|
712
792
|
...customState
|
|
713
793
|
};
|
|
714
794
|
}
|
|
715
|
-
const stateQuery = workflow.defineQuery(
|
|
716
|
-
agentQueryName(agentName)
|
|
717
|
-
);
|
|
795
|
+
const stateQuery = workflow.defineQuery("getAgentState");
|
|
718
796
|
const stateChangeUpdate = workflow.defineUpdate(
|
|
719
|
-
|
|
797
|
+
"waitForAgentStateChange"
|
|
720
798
|
);
|
|
721
799
|
workflow.setHandler(stateQuery, () => buildState());
|
|
722
800
|
workflow.setHandler(stateChangeUpdate, async (lastKnownVersion) => {
|
|
@@ -780,6 +858,10 @@ function createAgentStateManager({
|
|
|
780
858
|
customState[key] = value;
|
|
781
859
|
version++;
|
|
782
860
|
},
|
|
861
|
+
mergeUpdate(update) {
|
|
862
|
+
Object.assign(customState, update);
|
|
863
|
+
version++;
|
|
864
|
+
},
|
|
783
865
|
getCurrentState() {
|
|
784
866
|
return buildState();
|
|
785
867
|
},
|
|
@@ -835,6 +917,127 @@ function createAgentStateManager({
|
|
|
835
917
|
};
|
|
836
918
|
}
|
|
837
919
|
|
|
920
|
+
// src/lib/tool-router/auto-append.ts
|
|
921
|
+
function withAutoAppend(threadHandler, handler) {
|
|
922
|
+
return async (args, context) => {
|
|
923
|
+
const response = await handler(args, context);
|
|
924
|
+
await threadHandler({
|
|
925
|
+
threadId: context.threadId,
|
|
926
|
+
toolCallId: context.toolCallId,
|
|
927
|
+
toolName: context.toolName,
|
|
928
|
+
content: response.toolResponse
|
|
929
|
+
});
|
|
930
|
+
return {
|
|
931
|
+
toolResponse: "Response appended via withAutoAppend",
|
|
932
|
+
data: response.data,
|
|
933
|
+
resultAppended: true
|
|
934
|
+
};
|
|
935
|
+
};
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
// src/lib/tool-router/with-sandbox.ts
|
|
939
|
+
function withSandbox(manager, handler) {
|
|
940
|
+
return async (args, context) => {
|
|
941
|
+
if (!context.sandboxId) {
|
|
942
|
+
return {
|
|
943
|
+
toolResponse: `Error: No sandbox configured for this agent. The ${context.toolName} tool requires a sandbox.`,
|
|
944
|
+
data: null
|
|
945
|
+
};
|
|
946
|
+
}
|
|
947
|
+
const sandbox = await manager.getSandbox(context.sandboxId);
|
|
948
|
+
return handler(args, { ...context, sandbox, sandboxId: context.sandboxId });
|
|
949
|
+
};
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
// src/lib/subagent/define.ts
|
|
953
|
+
function defineSubagent(config) {
|
|
954
|
+
return config;
|
|
955
|
+
}
|
|
956
|
+
var SandboxNotSupportedError = class extends common.ApplicationFailure {
|
|
957
|
+
constructor(operation) {
|
|
958
|
+
super(
|
|
959
|
+
`Sandbox does not support: ${operation}`,
|
|
960
|
+
"SandboxNotSupportedError",
|
|
961
|
+
true
|
|
962
|
+
);
|
|
963
|
+
}
|
|
964
|
+
};
|
|
965
|
+
var SandboxNotFoundError = class extends common.ApplicationFailure {
|
|
966
|
+
constructor(sandboxId) {
|
|
967
|
+
super(`Sandbox not found: ${sandboxId}`, "SandboxNotFoundError", true);
|
|
968
|
+
}
|
|
969
|
+
};
|
|
970
|
+
|
|
971
|
+
// src/adapters/sandbox/virtual/mutations.ts
|
|
972
|
+
function applyVirtualTreeMutations(stateManager, mutations) {
|
|
973
|
+
let tree = [...stateManager.get("fileTree")];
|
|
974
|
+
for (const m of mutations) {
|
|
975
|
+
switch (m.type) {
|
|
976
|
+
case "add":
|
|
977
|
+
tree.push(m.entry);
|
|
978
|
+
break;
|
|
979
|
+
case "remove":
|
|
980
|
+
tree = tree.filter((e) => e.path !== m.path);
|
|
981
|
+
break;
|
|
982
|
+
case "update":
|
|
983
|
+
tree = tree.map(
|
|
984
|
+
(e) => e.path === m.path ? { ...e, ...m.entry } : e
|
|
985
|
+
);
|
|
986
|
+
break;
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
stateManager.set("fileTree", tree);
|
|
990
|
+
return tree;
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
// src/adapters/sandbox/virtual/tree.ts
|
|
994
|
+
var buildTree = (entries) => {
|
|
995
|
+
const root = { name: "/", children: /* @__PURE__ */ new Map(), isFile: false };
|
|
996
|
+
for (const entry of entries) {
|
|
997
|
+
const parts = entry.path.split("/").filter(Boolean);
|
|
998
|
+
let current = root;
|
|
999
|
+
for (const part of parts) {
|
|
1000
|
+
let child = current.children.get(part);
|
|
1001
|
+
if (!child) {
|
|
1002
|
+
child = { name: part, children: /* @__PURE__ */ new Map(), isFile: false };
|
|
1003
|
+
current.children.set(part, child);
|
|
1004
|
+
}
|
|
1005
|
+
current = child;
|
|
1006
|
+
}
|
|
1007
|
+
current.isFile = current.children.size === 0;
|
|
1008
|
+
}
|
|
1009
|
+
return root;
|
|
1010
|
+
};
|
|
1011
|
+
var printNode = (node, tab, sort) => {
|
|
1012
|
+
const entries = [...node.children.values()];
|
|
1013
|
+
if (sort) {
|
|
1014
|
+
entries.sort((a, b) => {
|
|
1015
|
+
if (!a.isFile && !b.isFile) return a.name.localeCompare(b.name);
|
|
1016
|
+
if (!a.isFile) return -1;
|
|
1017
|
+
if (!b.isFile) return 1;
|
|
1018
|
+
return a.name.localeCompare(b.name);
|
|
1019
|
+
});
|
|
1020
|
+
}
|
|
1021
|
+
let str = "";
|
|
1022
|
+
for (const [i, entry] of entries.entries()) {
|
|
1023
|
+
const isLast = i === entries.length - 1;
|
|
1024
|
+
const branch = isLast ? "\u2514\u2500" : "\u251C\u2500";
|
|
1025
|
+
const childTab = tab + (isLast ? " " : "\u2502 ");
|
|
1026
|
+
if (entry.isFile) {
|
|
1027
|
+
str += "\n" + tab + branch + " " + entry.name;
|
|
1028
|
+
} else {
|
|
1029
|
+
const subtree = printNode(entry, childTab, sort);
|
|
1030
|
+
str += "\n" + tab + branch + " " + entry.name + "/" + subtree;
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
return str;
|
|
1034
|
+
};
|
|
1035
|
+
function formatVirtualFileTree(entries, opts = {}) {
|
|
1036
|
+
const sort = opts.sort ?? true;
|
|
1037
|
+
const root = buildTree(entries);
|
|
1038
|
+
return "/" + printNode(root, "", sort);
|
|
1039
|
+
}
|
|
1040
|
+
|
|
838
1041
|
// src/lib/skills/parse.ts
|
|
839
1042
|
function parseSkillFile(raw) {
|
|
840
1043
|
const trimmed = raw.replace(/^\uFEFF/, "");
|
|
@@ -1256,197 +1459,313 @@ var createAskUserQuestionHandler = () => async (args) => {
|
|
|
1256
1459
|
data: { questions: args.questions }
|
|
1257
1460
|
};
|
|
1258
1461
|
};
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
return 1
|
|
1272
|
-
`;
|
|
1273
|
-
function getThreadKey(threadId, key) {
|
|
1274
|
-
return `thread:${threadId}:${key}`;
|
|
1275
|
-
}
|
|
1276
|
-
function createThreadManager(config) {
|
|
1277
|
-
const {
|
|
1278
|
-
redis,
|
|
1279
|
-
threadId,
|
|
1280
|
-
key = "messages",
|
|
1281
|
-
serialize = (m) => JSON.stringify(m),
|
|
1282
|
-
deserialize = (raw) => JSON.parse(raw),
|
|
1283
|
-
idOf
|
|
1284
|
-
} = config;
|
|
1285
|
-
const redisKey = getThreadKey(threadId, key);
|
|
1286
|
-
const metaKey = getThreadKey(threadId, `${key}:meta`);
|
|
1287
|
-
async function assertThreadExists() {
|
|
1288
|
-
const exists = await redis.exists(metaKey);
|
|
1289
|
-
if (!exists) {
|
|
1290
|
-
throw new Error(`Thread "${threadId}" (key: ${key}) does not exist`);
|
|
1462
|
+
var FileSystemSkillProvider = class {
|
|
1463
|
+
constructor(fs, baseDir) {
|
|
1464
|
+
this.fs = fs;
|
|
1465
|
+
this.baseDir = baseDir;
|
|
1466
|
+
}
|
|
1467
|
+
async listSkills() {
|
|
1468
|
+
const dirs = await this.discoverSkillDirs();
|
|
1469
|
+
const skills = [];
|
|
1470
|
+
for (const dir of dirs) {
|
|
1471
|
+
const raw = await this.fs.readFile(path.join(this.baseDir, dir, "SKILL.md"));
|
|
1472
|
+
const { frontmatter } = parseSkillFile(raw);
|
|
1473
|
+
skills.push(frontmatter);
|
|
1291
1474
|
}
|
|
1475
|
+
return skills;
|
|
1292
1476
|
}
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
}
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
}
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1477
|
+
async getSkill(name) {
|
|
1478
|
+
const raw = await this.fs.readFile(
|
|
1479
|
+
path.join(this.baseDir, name, "SKILL.md")
|
|
1480
|
+
);
|
|
1481
|
+
const { frontmatter, body } = parseSkillFile(raw);
|
|
1482
|
+
if (frontmatter.name !== name) {
|
|
1483
|
+
throw new Error(
|
|
1484
|
+
`Skill directory "${name}" contains SKILL.md with mismatched name "${frontmatter.name}"`
|
|
1485
|
+
);
|
|
1486
|
+
}
|
|
1487
|
+
return { ...frontmatter, instructions: body };
|
|
1488
|
+
}
|
|
1489
|
+
/**
|
|
1490
|
+
* Convenience method to load all skills with full instructions.
|
|
1491
|
+
* Returns `Skill[]` ready to pass into a workflow.
|
|
1492
|
+
*/
|
|
1493
|
+
async loadAll() {
|
|
1494
|
+
const dirs = await this.discoverSkillDirs();
|
|
1495
|
+
const skills = [];
|
|
1496
|
+
for (const dir of dirs) {
|
|
1497
|
+
const raw = await this.fs.readFile(path.join(this.baseDir, dir, "SKILL.md"));
|
|
1498
|
+
const { frontmatter, body } = parseSkillFile(raw);
|
|
1499
|
+
skills.push({ ...frontmatter, instructions: body });
|
|
1500
|
+
}
|
|
1501
|
+
return skills;
|
|
1502
|
+
}
|
|
1503
|
+
async discoverSkillDirs() {
|
|
1504
|
+
const entries = await this.fs.readdirWithFileTypes(this.baseDir);
|
|
1505
|
+
const dirs = [];
|
|
1506
|
+
for (const entry of entries) {
|
|
1507
|
+
if (!entry.isDirectory) continue;
|
|
1508
|
+
const skillPath = path.join(this.baseDir, entry.name, "SKILL.md");
|
|
1509
|
+
if (await this.fs.exists(skillPath)) {
|
|
1510
|
+
dirs.push(entry.name);
|
|
1320
1511
|
}
|
|
1321
|
-
},
|
|
1322
|
-
async delete() {
|
|
1323
|
-
await redis.del(redisKey, metaKey);
|
|
1324
1512
|
}
|
|
1325
|
-
|
|
1326
|
-
}
|
|
1327
|
-
|
|
1513
|
+
return dirs;
|
|
1514
|
+
}
|
|
1515
|
+
};
|
|
1516
|
+
async function queryParentWorkflowState(client) {
|
|
1328
1517
|
const { workflowExecution } = activity.Context.current().info;
|
|
1329
1518
|
const handle = client.getHandle(
|
|
1330
1519
|
workflowExecution.workflowId,
|
|
1331
1520
|
workflowExecution.runId
|
|
1332
1521
|
);
|
|
1333
|
-
return handle.query(
|
|
1522
|
+
return handle.query("getAgentState");
|
|
1334
1523
|
}
|
|
1335
|
-
function createRunAgentActivity(client,
|
|
1524
|
+
function createRunAgentActivity(client, handler) {
|
|
1336
1525
|
return async (config) => {
|
|
1337
|
-
const state = await queryParentWorkflowState(
|
|
1338
|
-
|
|
1339
|
-
agentQueryName(config.agentName)
|
|
1340
|
-
);
|
|
1341
|
-
return invoker({ ...config, state });
|
|
1526
|
+
const state = await queryParentWorkflowState(client);
|
|
1527
|
+
return handler({ ...config, state });
|
|
1342
1528
|
};
|
|
1343
1529
|
}
|
|
1344
|
-
function
|
|
1345
|
-
return async (
|
|
1346
|
-
|
|
1347
|
-
return {
|
|
1348
|
-
toolResponse: "Hello, world!",
|
|
1349
|
-
data: { files: [] }
|
|
1350
|
-
};
|
|
1530
|
+
function withParentWorkflowState(client, handler) {
|
|
1531
|
+
return async (args, context) => {
|
|
1532
|
+
const state = await queryParentWorkflowState(client);
|
|
1533
|
+
return handler(args, { ...context, state });
|
|
1351
1534
|
};
|
|
1352
1535
|
}
|
|
1353
1536
|
|
|
1537
|
+
// src/lib/sandbox/manager.ts
|
|
1538
|
+
var SandboxManager = class {
|
|
1539
|
+
constructor(provider) {
|
|
1540
|
+
this.provider = provider;
|
|
1541
|
+
}
|
|
1542
|
+
async create(options) {
|
|
1543
|
+
const { sandbox, stateUpdate } = await this.provider.create(options);
|
|
1544
|
+
return { sandboxId: sandbox.id, ...stateUpdate && { stateUpdate } };
|
|
1545
|
+
}
|
|
1546
|
+
async getSandbox(id) {
|
|
1547
|
+
return this.provider.get(id);
|
|
1548
|
+
}
|
|
1549
|
+
async destroy(id) {
|
|
1550
|
+
await this.provider.destroy(id);
|
|
1551
|
+
}
|
|
1552
|
+
async snapshot(id) {
|
|
1553
|
+
return this.provider.snapshot(id);
|
|
1554
|
+
}
|
|
1555
|
+
async restore(snapshot) {
|
|
1556
|
+
const sandbox = await this.provider.restore(snapshot);
|
|
1557
|
+
return sandbox.id;
|
|
1558
|
+
}
|
|
1559
|
+
/**
|
|
1560
|
+
* Returns Temporal activity functions matching {@link SandboxOps}.
|
|
1561
|
+
* Spread these into your worker's activity map.
|
|
1562
|
+
*/
|
|
1563
|
+
createActivities() {
|
|
1564
|
+
return {
|
|
1565
|
+
createSandbox: async (options) => {
|
|
1566
|
+
return this.create(options);
|
|
1567
|
+
},
|
|
1568
|
+
destroySandbox: async (sandboxId) => {
|
|
1569
|
+
await this.destroy(sandboxId);
|
|
1570
|
+
},
|
|
1571
|
+
snapshotSandbox: async (sandboxId) => {
|
|
1572
|
+
return this.snapshot(sandboxId);
|
|
1573
|
+
}
|
|
1574
|
+
};
|
|
1575
|
+
}
|
|
1576
|
+
};
|
|
1577
|
+
|
|
1578
|
+
// src/tools/bash/handler.ts
|
|
1579
|
+
var bashHandler = async (args, { sandbox }) => {
|
|
1580
|
+
try {
|
|
1581
|
+
const result = await sandbox.exec(args.command);
|
|
1582
|
+
return {
|
|
1583
|
+
toolResponse: `Exit code: ${result.exitCode}
|
|
1584
|
+
|
|
1585
|
+
stdout:
|
|
1586
|
+
${result.stdout}
|
|
1587
|
+
|
|
1588
|
+
stderr:
|
|
1589
|
+
${result.stderr}`,
|
|
1590
|
+
data: result
|
|
1591
|
+
};
|
|
1592
|
+
} catch (error) {
|
|
1593
|
+
const err = error instanceof Error ? error : new Error("Unknown error");
|
|
1594
|
+
return {
|
|
1595
|
+
toolResponse: `Error executing bash command: ${err.message}`,
|
|
1596
|
+
data: null
|
|
1597
|
+
};
|
|
1598
|
+
}
|
|
1599
|
+
};
|
|
1600
|
+
|
|
1354
1601
|
// src/tools/edit/handler.ts
|
|
1355
1602
|
function escapeRegExp(str) {
|
|
1356
1603
|
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1357
1604
|
}
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1605
|
+
var editHandler = async (args, { sandbox }) => {
|
|
1606
|
+
const { fs } = sandbox;
|
|
1607
|
+
const { file_path, old_string, new_string, replace_all = false } = args;
|
|
1608
|
+
if (old_string === new_string) {
|
|
1609
|
+
return {
|
|
1610
|
+
toolResponse: `Error: old_string and new_string must be different.`,
|
|
1611
|
+
data: { path: file_path, success: false, replacements: 0 }
|
|
1612
|
+
};
|
|
1613
|
+
}
|
|
1614
|
+
try {
|
|
1615
|
+
const exists = await fs.exists(file_path);
|
|
1616
|
+
if (!exists) {
|
|
1362
1617
|
return {
|
|
1363
|
-
toolResponse: `Error:
|
|
1618
|
+
toolResponse: `Error: File "${file_path}" does not exist.`,
|
|
1364
1619
|
data: { path: file_path, success: false, replacements: 0 }
|
|
1365
1620
|
};
|
|
1366
1621
|
}
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
if (!exists) {
|
|
1370
|
-
return {
|
|
1371
|
-
toolResponse: `Error: File "${file_path}" does not exist.`,
|
|
1372
|
-
data: { path: file_path, success: false, replacements: 0 }
|
|
1373
|
-
};
|
|
1374
|
-
}
|
|
1375
|
-
const content = await fs.readFile(file_path);
|
|
1376
|
-
if (!content.includes(old_string)) {
|
|
1377
|
-
return {
|
|
1378
|
-
toolResponse: `Error: Could not find the specified text in "${file_path}". Make sure old_string matches exactly (whitespace-sensitive).`,
|
|
1379
|
-
data: { path: file_path, success: false, replacements: 0 }
|
|
1380
|
-
};
|
|
1381
|
-
}
|
|
1382
|
-
const escapedOldString = escapeRegExp(old_string);
|
|
1383
|
-
const globalRegex = new RegExp(escapedOldString, "g");
|
|
1384
|
-
const occurrences = (content.match(globalRegex) || []).length;
|
|
1385
|
-
if (!replace_all && occurrences > 1) {
|
|
1386
|
-
return {
|
|
1387
|
-
toolResponse: `Error: old_string appears ${occurrences} times in "${file_path}". Either provide more context to make it unique, or use replace_all: true.`,
|
|
1388
|
-
data: { path: file_path, success: false, replacements: 0 }
|
|
1389
|
-
};
|
|
1390
|
-
}
|
|
1391
|
-
let newContent;
|
|
1392
|
-
let replacements;
|
|
1393
|
-
if (replace_all) {
|
|
1394
|
-
newContent = content.split(old_string).join(new_string);
|
|
1395
|
-
replacements = occurrences;
|
|
1396
|
-
} else {
|
|
1397
|
-
const index = content.indexOf(old_string);
|
|
1398
|
-
newContent = content.slice(0, index) + new_string + content.slice(index + old_string.length);
|
|
1399
|
-
replacements = 1;
|
|
1400
|
-
}
|
|
1401
|
-
await fs.writeFile(file_path, newContent);
|
|
1402
|
-
const summary = replace_all ? `Replaced ${replacements} occurrence(s)` : `Replaced 1 occurrence`;
|
|
1622
|
+
const content = await fs.readFile(file_path);
|
|
1623
|
+
if (!content.includes(old_string)) {
|
|
1403
1624
|
return {
|
|
1404
|
-
toolResponse:
|
|
1405
|
-
data: { path: file_path, success:
|
|
1625
|
+
toolResponse: `Error: Could not find the specified text in "${file_path}". Make sure old_string matches exactly (whitespace-sensitive).`,
|
|
1626
|
+
data: { path: file_path, success: false, replacements: 0 }
|
|
1406
1627
|
};
|
|
1407
|
-
}
|
|
1408
|
-
|
|
1628
|
+
}
|
|
1629
|
+
const escapedOldString = escapeRegExp(old_string);
|
|
1630
|
+
const globalRegex = new RegExp(escapedOldString, "g");
|
|
1631
|
+
const occurrences = (content.match(globalRegex) || []).length;
|
|
1632
|
+
if (!replace_all && occurrences > 1) {
|
|
1409
1633
|
return {
|
|
1410
|
-
toolResponse: `Error
|
|
1634
|
+
toolResponse: `Error: old_string appears ${occurrences} times in "${file_path}". Either provide more context to make it unique, or use replace_all: true.`,
|
|
1411
1635
|
data: { path: file_path, success: false, replacements: 0 }
|
|
1412
1636
|
};
|
|
1413
1637
|
}
|
|
1414
|
-
|
|
1638
|
+
let newContent;
|
|
1639
|
+
let replacements;
|
|
1640
|
+
if (replace_all) {
|
|
1641
|
+
newContent = content.split(old_string).join(new_string);
|
|
1642
|
+
replacements = occurrences;
|
|
1643
|
+
} else {
|
|
1644
|
+
const index = content.indexOf(old_string);
|
|
1645
|
+
newContent = content.slice(0, index) + new_string + content.slice(index + old_string.length);
|
|
1646
|
+
replacements = 1;
|
|
1647
|
+
}
|
|
1648
|
+
await fs.writeFile(file_path, newContent);
|
|
1649
|
+
const summary = replace_all ? `Replaced ${replacements} occurrence(s)` : `Replaced 1 occurrence`;
|
|
1650
|
+
return {
|
|
1651
|
+
toolResponse: `${summary} in ${file_path}`,
|
|
1652
|
+
data: { path: file_path, success: true, replacements }
|
|
1653
|
+
};
|
|
1654
|
+
} catch (error) {
|
|
1655
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
1656
|
+
return {
|
|
1657
|
+
toolResponse: `Error editing file "${file_path}": ${message}`,
|
|
1658
|
+
data: { path: file_path, success: false, replacements: 0 }
|
|
1659
|
+
};
|
|
1660
|
+
}
|
|
1661
|
+
};
|
|
1662
|
+
|
|
1663
|
+
// src/tools/glob/handler.ts
|
|
1664
|
+
function matchGlob(pattern, path) {
|
|
1665
|
+
const regex = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*\*/g, "{{GLOBSTAR}}").replace(/\*/g, "[^/]*").replace(/\{\{GLOBSTAR\}\}/g, ".*");
|
|
1666
|
+
return new RegExp(`^${regex}$`).test(path);
|
|
1415
1667
|
}
|
|
1416
|
-
|
|
1417
|
-
const
|
|
1418
|
-
const
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1668
|
+
async function walk(fs, dir) {
|
|
1669
|
+
const results = [];
|
|
1670
|
+
const entries = await fs.readdirWithFileTypes(dir);
|
|
1671
|
+
for (const entry of entries) {
|
|
1672
|
+
const full = dir === "/" ? `/${entry.name}` : `${dir}/${entry.name}`;
|
|
1673
|
+
if (entry.isDirectory) {
|
|
1674
|
+
results.push(...await walk(fs, full));
|
|
1675
|
+
} else {
|
|
1676
|
+
results.push(full);
|
|
1424
1677
|
}
|
|
1425
|
-
}
|
|
1426
|
-
|
|
1678
|
+
}
|
|
1679
|
+
return results;
|
|
1680
|
+
}
|
|
1681
|
+
var globHandler = async (args, { sandbox }) => {
|
|
1682
|
+
const { fs } = sandbox;
|
|
1683
|
+
const { pattern, root = "/" } = args;
|
|
1427
1684
|
try {
|
|
1428
|
-
const
|
|
1429
|
-
const
|
|
1685
|
+
const allFiles = await walk(fs, root);
|
|
1686
|
+
const relativeTo = root.endsWith("/") ? root : `${root}/`;
|
|
1687
|
+
const matched = allFiles.map((f) => f.startsWith(relativeTo) ? f.slice(relativeTo.length) : f).filter((f) => matchGlob(pattern, f));
|
|
1430
1688
|
return {
|
|
1431
|
-
toolResponse: `
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1689
|
+
toolResponse: matched.length > 0 ? `Found ${matched.length} file(s):
|
|
1690
|
+
${matched.join("\n")}` : `No files matched pattern "${pattern}"`,
|
|
1691
|
+
data: { files: matched }
|
|
1692
|
+
};
|
|
1693
|
+
} catch (error) {
|
|
1694
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
1695
|
+
return {
|
|
1696
|
+
toolResponse: `Error running glob: ${message}`,
|
|
1697
|
+
data: { files: [] }
|
|
1698
|
+
};
|
|
1699
|
+
}
|
|
1700
|
+
};
|
|
1435
1701
|
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1702
|
+
// src/tools/read-file/handler.ts
|
|
1703
|
+
var readFileHandler = async (args, { sandbox }) => {
|
|
1704
|
+
const { fs } = sandbox;
|
|
1705
|
+
const { path, offset, limit } = args;
|
|
1706
|
+
try {
|
|
1707
|
+
const exists = await fs.exists(path);
|
|
1708
|
+
if (!exists) {
|
|
1709
|
+
return {
|
|
1710
|
+
toolResponse: `Error: File "${path}" does not exist.`,
|
|
1711
|
+
data: null
|
|
1712
|
+
};
|
|
1713
|
+
}
|
|
1714
|
+
const raw = await fs.readFile(path);
|
|
1715
|
+
const lines = raw.split("\n");
|
|
1716
|
+
const totalLines = lines.length;
|
|
1717
|
+
if (offset !== void 0 || limit !== void 0) {
|
|
1718
|
+
const start = Math.max(0, (offset ?? 1) - 1);
|
|
1719
|
+
const end = limit !== void 0 ? start + limit : lines.length;
|
|
1720
|
+
const slice = lines.slice(start, end);
|
|
1721
|
+
const numbered2 = slice.map((line, i) => `${String(start + i + 1).padStart(6)}|${line}`).join("\n");
|
|
1722
|
+
return {
|
|
1723
|
+
toolResponse: numbered2,
|
|
1724
|
+
data: { path, content: numbered2, totalLines }
|
|
1725
|
+
};
|
|
1726
|
+
}
|
|
1727
|
+
const numbered = lines.map((line, i) => `${String(i + 1).padStart(6)}|${line}`).join("\n");
|
|
1728
|
+
return {
|
|
1729
|
+
toolResponse: numbered,
|
|
1730
|
+
data: { path, content: numbered, totalLines }
|
|
1439
1731
|
};
|
|
1440
1732
|
} catch (error) {
|
|
1441
|
-
const
|
|
1733
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
1442
1734
|
return {
|
|
1443
|
-
toolResponse: `Error
|
|
1735
|
+
toolResponse: `Error reading file "${path}": ${message}`,
|
|
1444
1736
|
data: null
|
|
1445
1737
|
};
|
|
1446
1738
|
}
|
|
1447
1739
|
};
|
|
1448
1740
|
|
|
1449
|
-
// src/
|
|
1741
|
+
// src/tools/write-file/handler.ts
|
|
1742
|
+
var writeFileHandler = async (args, { sandbox }) => {
|
|
1743
|
+
const { fs } = sandbox;
|
|
1744
|
+
const { file_path, content } = args;
|
|
1745
|
+
try {
|
|
1746
|
+
const lastSlash = file_path.lastIndexOf("/");
|
|
1747
|
+
if (lastSlash > 0) {
|
|
1748
|
+
const dir = file_path.slice(0, lastSlash);
|
|
1749
|
+
const dirExists = await fs.exists(dir);
|
|
1750
|
+
if (!dirExists) {
|
|
1751
|
+
await fs.mkdir(dir, { recursive: true });
|
|
1752
|
+
}
|
|
1753
|
+
}
|
|
1754
|
+
await fs.writeFile(file_path, content);
|
|
1755
|
+
return {
|
|
1756
|
+
toolResponse: `Successfully wrote to ${file_path}`,
|
|
1757
|
+
data: { path: file_path, success: true }
|
|
1758
|
+
};
|
|
1759
|
+
} catch (error) {
|
|
1760
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
1761
|
+
return {
|
|
1762
|
+
toolResponse: `Error writing file "${file_path}": ${message}`,
|
|
1763
|
+
data: { path: file_path, success: false }
|
|
1764
|
+
};
|
|
1765
|
+
}
|
|
1766
|
+
};
|
|
1767
|
+
|
|
1768
|
+
// src/lib/sandbox/tree.ts
|
|
1450
1769
|
var basename = (path, separator) => {
|
|
1451
1770
|
if (path[path.length - 1] === separator) path = path.slice(0, -1);
|
|
1452
1771
|
const lastSlashIndex = path.lastIndexOf(separator);
|
|
@@ -1475,7 +1794,7 @@ var toTree = async (fs, opts = {}) => {
|
|
|
1475
1794
|
const sort = opts.sort ?? true;
|
|
1476
1795
|
let subtree = " (...)";
|
|
1477
1796
|
if (depth > 0) {
|
|
1478
|
-
const list = await fs.readdirWithFileTypes
|
|
1797
|
+
const list = await fs.readdirWithFileTypes(dir);
|
|
1479
1798
|
if (sort) {
|
|
1480
1799
|
list.sort((a, b) => {
|
|
1481
1800
|
if (a.isDirectory && b.isDirectory) {
|
|
@@ -1509,78 +1828,22 @@ var toTree = async (fs, opts = {}) => {
|
|
|
1509
1828
|
const base = basename(dir, separator) + separator;
|
|
1510
1829
|
return base + subtree;
|
|
1511
1830
|
};
|
|
1512
|
-
var FileSystemSkillProvider = class {
|
|
1513
|
-
constructor(baseDir) {
|
|
1514
|
-
this.baseDir = baseDir;
|
|
1515
|
-
}
|
|
1516
|
-
async listSkills() {
|
|
1517
|
-
const dirs = await this.discoverSkillDirs();
|
|
1518
|
-
const skills = [];
|
|
1519
|
-
for (const dir of dirs) {
|
|
1520
|
-
const raw = await promises.readFile(path.join(this.baseDir, dir, "SKILL.md"), "utf-8");
|
|
1521
|
-
const { frontmatter } = parseSkillFile(raw);
|
|
1522
|
-
skills.push(frontmatter);
|
|
1523
|
-
}
|
|
1524
|
-
return skills;
|
|
1525
|
-
}
|
|
1526
|
-
async getSkill(name) {
|
|
1527
|
-
const raw = await promises.readFile(
|
|
1528
|
-
path.join(this.baseDir, name, "SKILL.md"),
|
|
1529
|
-
"utf-8"
|
|
1530
|
-
);
|
|
1531
|
-
const { frontmatter, body } = parseSkillFile(raw);
|
|
1532
|
-
if (frontmatter.name !== name) {
|
|
1533
|
-
throw new Error(
|
|
1534
|
-
`Skill directory "${name}" contains SKILL.md with mismatched name "${frontmatter.name}"`
|
|
1535
|
-
);
|
|
1536
|
-
}
|
|
1537
|
-
return { ...frontmatter, instructions: body };
|
|
1538
|
-
}
|
|
1539
|
-
/**
|
|
1540
|
-
* Convenience method to load all skills with full instructions.
|
|
1541
|
-
* Returns `Skill[]` ready to pass into a workflow.
|
|
1542
|
-
*/
|
|
1543
|
-
async loadAll() {
|
|
1544
|
-
const dirs = await this.discoverSkillDirs();
|
|
1545
|
-
const skills = [];
|
|
1546
|
-
for (const dir of dirs) {
|
|
1547
|
-
const raw = await promises.readFile(path.join(this.baseDir, dir, "SKILL.md"), "utf-8");
|
|
1548
|
-
const { frontmatter, body } = parseSkillFile(raw);
|
|
1549
|
-
skills.push({ ...frontmatter, instructions: body });
|
|
1550
|
-
}
|
|
1551
|
-
return skills;
|
|
1552
|
-
}
|
|
1553
|
-
async discoverSkillDirs() {
|
|
1554
|
-
const entries = await promises.readdir(this.baseDir, { withFileTypes: true });
|
|
1555
|
-
const dirs = [];
|
|
1556
|
-
for (const entry of entries) {
|
|
1557
|
-
if (!entry.isDirectory()) continue;
|
|
1558
|
-
try {
|
|
1559
|
-
await promises.readFile(path.join(this.baseDir, entry.name, "SKILL.md"), "utf-8");
|
|
1560
|
-
dirs.push(entry.name);
|
|
1561
|
-
} catch {
|
|
1562
|
-
}
|
|
1563
|
-
}
|
|
1564
|
-
return dirs;
|
|
1565
|
-
}
|
|
1566
|
-
};
|
|
1567
1831
|
|
|
1568
1832
|
exports.FileSystemSkillProvider = FileSystemSkillProvider;
|
|
1569
|
-
exports.
|
|
1570
|
-
exports.
|
|
1833
|
+
exports.SandboxManager = SandboxManager;
|
|
1834
|
+
exports.SandboxNotFoundError = SandboxNotFoundError;
|
|
1835
|
+
exports.SandboxNotSupportedError = SandboxNotSupportedError;
|
|
1836
|
+
exports.applyVirtualTreeMutations = applyVirtualTreeMutations;
|
|
1571
1837
|
exports.askUserQuestionTool = askUserQuestionTool;
|
|
1838
|
+
exports.bashHandler = bashHandler;
|
|
1572
1839
|
exports.bashTool = bashTool;
|
|
1573
1840
|
exports.createAgentStateManager = createAgentStateManager;
|
|
1574
1841
|
exports.createAskUserQuestionHandler = createAskUserQuestionHandler;
|
|
1575
|
-
exports.createBashHandler = createBashHandler;
|
|
1576
1842
|
exports.createBashToolDescription = createBashToolDescription;
|
|
1577
|
-
exports.createEditHandler = createEditHandler;
|
|
1578
|
-
exports.createGlobHandler = createGlobHandler;
|
|
1579
1843
|
exports.createReadSkillHandler = createReadSkillHandler;
|
|
1580
1844
|
exports.createReadSkillTool = createReadSkillTool;
|
|
1581
1845
|
exports.createRunAgentActivity = createRunAgentActivity;
|
|
1582
1846
|
exports.createSession = createSession;
|
|
1583
|
-
exports.createSubagentTool = createSubagentTool;
|
|
1584
1847
|
exports.createTaskCreateHandler = createTaskCreateHandler;
|
|
1585
1848
|
exports.createTaskGetHandler = createTaskGetHandler;
|
|
1586
1849
|
exports.createTaskListHandler = createTaskListHandler;
|
|
@@ -1589,15 +1852,20 @@ exports.createThreadManager = createThreadManager;
|
|
|
1589
1852
|
exports.createToolRouter = createToolRouter;
|
|
1590
1853
|
exports.defineSubagent = defineSubagent;
|
|
1591
1854
|
exports.defineTool = defineTool;
|
|
1855
|
+
exports.editHandler = editHandler;
|
|
1592
1856
|
exports.editTool = editTool;
|
|
1857
|
+
exports.formatVirtualFileTree = formatVirtualFileTree;
|
|
1593
1858
|
exports.getShortId = getShortId;
|
|
1859
|
+
exports.globHandler = globHandler;
|
|
1594
1860
|
exports.globTool = globTool;
|
|
1595
1861
|
exports.grepTool = grepTool;
|
|
1596
1862
|
exports.hasNoOtherToolCalls = hasNoOtherToolCalls;
|
|
1597
1863
|
exports.isTerminalStatus = isTerminalStatus;
|
|
1598
1864
|
exports.parseSkillFile = parseSkillFile;
|
|
1599
1865
|
exports.proxyDefaultThreadOps = proxyDefaultThreadOps;
|
|
1866
|
+
exports.proxySandboxOps = proxySandboxOps;
|
|
1600
1867
|
exports.queryParentWorkflowState = queryParentWorkflowState;
|
|
1868
|
+
exports.readFileHandler = readFileHandler;
|
|
1601
1869
|
exports.readFileTool = readFileTool;
|
|
1602
1870
|
exports.taskCreateTool = taskCreateTool;
|
|
1603
1871
|
exports.taskGetTool = taskGetTool;
|
|
@@ -1605,6 +1873,9 @@ exports.taskListTool = taskListTool;
|
|
|
1605
1873
|
exports.taskUpdateTool = taskUpdateTool;
|
|
1606
1874
|
exports.toTree = toTree;
|
|
1607
1875
|
exports.withAutoAppend = withAutoAppend;
|
|
1876
|
+
exports.withParentWorkflowState = withParentWorkflowState;
|
|
1877
|
+
exports.withSandbox = withSandbox;
|
|
1878
|
+
exports.writeFileHandler = writeFileHandler;
|
|
1608
1879
|
exports.writeFileTool = writeFileTool;
|
|
1609
1880
|
//# sourceMappingURL=index.cjs.map
|
|
1610
1881
|
//# sourceMappingURL=index.cjs.map
|