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