zeitlich 0.2.13 → 0.2.15
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 +61 -50
- 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 +306 -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 +300 -0
- package/dist/adapters/thread/google-genai/index.js.map +1 -0
- package/dist/adapters/{langchain → thread/langchain}/index.cjs +29 -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 +29 -9
- package/dist/adapters/thread/langchain/index.js.map +1 -0
- package/dist/index.cjs +866 -567
- 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 +854 -562
- package/dist/index.js.map +1 -1
- package/dist/{thread-manager-qc0g5Rvd.d.cts → types-35POpVfa.d.cts} +7 -6
- package/dist/{thread-manager-qc0g5Rvd.d.ts → types-35POpVfa.d.ts} +7 -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-BWvIYK28.d.ts +391 -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-Dje1TdH6.d.cts +391 -0
- package/dist/workflow.cjs +460 -321
- 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 +456 -319
- package/dist/workflow.js.map +1 -1
- package/package.json +65 -8
- 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 +132 -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 +22 -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} +83 -50
- package/src/lib/session/types.ts +95 -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 +68 -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} +20 -33
- package/src/lib/thread/types.ts +39 -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({
|
|
@@ -433,59 +244,232 @@ function createToolRouter(options) {
|
|
|
433
244
|
data: response.data
|
|
434
245
|
};
|
|
435
246
|
};
|
|
436
|
-
if (options.parallel) {
|
|
437
|
-
return Promise.all(matchingCalls.map(processOne));
|
|
438
|
-
}
|
|
439
|
-
const results = [];
|
|
440
|
-
for (const toolCall of matchingCalls) {
|
|
441
|
-
results.push(await processOne(toolCall));
|
|
442
|
-
}
|
|
443
|
-
return results;
|
|
247
|
+
if (options.parallel) {
|
|
248
|
+
return Promise.all(matchingCalls.map(processOne));
|
|
249
|
+
}
|
|
250
|
+
const results = [];
|
|
251
|
+
for (const toolCall of matchingCalls) {
|
|
252
|
+
results.push(await processOne(toolCall));
|
|
253
|
+
}
|
|
254
|
+
return results;
|
|
255
|
+
},
|
|
256
|
+
filterByName(toolCalls, name) {
|
|
257
|
+
return toolCalls.filter(
|
|
258
|
+
(tc) => tc.name === name
|
|
259
|
+
);
|
|
260
|
+
},
|
|
261
|
+
hasToolCall(toolCalls, name) {
|
|
262
|
+
return toolCalls.some((tc) => tc.name === name);
|
|
263
|
+
},
|
|
264
|
+
getResultsByName(results, name) {
|
|
265
|
+
return results.filter((r) => r.name === name);
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
}
|
|
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();
|
|
325
|
+
return async (args, context) => {
|
|
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
|
+
}
|
|
373
|
+
return {
|
|
374
|
+
toolResponse: finalToolResponse,
|
|
375
|
+
data: validated ? validated.data : data,
|
|
376
|
+
...usage && { usage }
|
|
377
|
+
};
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// src/lib/subagent/register.ts
|
|
382
|
+
function buildSubagentRegistration(subagents) {
|
|
383
|
+
if (subagents.length === 0) return null;
|
|
384
|
+
const getEnabled = () => subagents.filter((s) => s.enabled ?? true);
|
|
385
|
+
const subagentHooksMap = /* @__PURE__ */ new Map();
|
|
386
|
+
for (const s of subagents) {
|
|
387
|
+
if (s.hooks) subagentHooksMap.set(s.agentName, s.hooks);
|
|
388
|
+
}
|
|
389
|
+
const resolveSubagentName = (args) => args.subagent;
|
|
390
|
+
return {
|
|
391
|
+
name: SUBAGENT_TOOL_NAME,
|
|
392
|
+
get enabled() {
|
|
393
|
+
return getEnabled().length > 0;
|
|
444
394
|
},
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
return toolCalls.filter(
|
|
448
|
-
(tc) => tc.name === name
|
|
449
|
-
);
|
|
395
|
+
get description() {
|
|
396
|
+
return createSubagentTool(getEnabled()).description;
|
|
450
397
|
},
|
|
451
|
-
|
|
452
|
-
return
|
|
398
|
+
get schema() {
|
|
399
|
+
return createSubagentTool(getEnabled()).schema;
|
|
453
400
|
},
|
|
454
|
-
|
|
455
|
-
|
|
401
|
+
handler: createSubagentHandler(subagents),
|
|
402
|
+
...subagentHooksMap.size > 0 && {
|
|
403
|
+
hooks: {
|
|
404
|
+
onPreToolUse: async (ctx) => {
|
|
405
|
+
const hooks = subagentHooksMap.get(resolveSubagentName(ctx.args));
|
|
406
|
+
return hooks?.onPreExecution?.(ctx) ?? {};
|
|
407
|
+
},
|
|
408
|
+
onPostToolUse: async (ctx) => {
|
|
409
|
+
const hooks = subagentHooksMap.get(resolveSubagentName(ctx.args));
|
|
410
|
+
await hooks?.onPostExecution?.(ctx);
|
|
411
|
+
},
|
|
412
|
+
onPostToolUseFailure: async (ctx) => {
|
|
413
|
+
const hooks = subagentHooksMap.get(resolveSubagentName(ctx.args));
|
|
414
|
+
return hooks?.onExecutionFailure?.(ctx) ?? {};
|
|
415
|
+
}
|
|
416
|
+
}
|
|
456
417
|
}
|
|
457
418
|
};
|
|
458
419
|
}
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
420
|
+
var READ_SKILL_TOOL_NAME = "ReadSkill";
|
|
421
|
+
function buildReadSkillDescription(skills) {
|
|
422
|
+
const skillList = skills.map((s) => `- **${s.name}**: ${s.description}`).join("\n");
|
|
423
|
+
return `Load the full instructions for a skill. Read the skill before following its instructions.
|
|
424
|
+
|
|
425
|
+
# Available skills:
|
|
426
|
+
${skillList}
|
|
427
|
+
`;
|
|
428
|
+
}
|
|
429
|
+
function createReadSkillTool(skills) {
|
|
430
|
+
if (skills.length === 0) {
|
|
431
|
+
throw new Error("createReadSkillTool requires at least one skill");
|
|
432
|
+
}
|
|
433
|
+
const names = skills.map((s) => s.name);
|
|
434
|
+
return {
|
|
435
|
+
name: READ_SKILL_TOOL_NAME,
|
|
436
|
+
description: buildReadSkillDescription(skills),
|
|
437
|
+
schema: z14.object({
|
|
438
|
+
skill_name: z14.enum(names).describe("The name of the skill to load")
|
|
439
|
+
})
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// src/lib/skills/handler.ts
|
|
444
|
+
function createReadSkillHandler(skills) {
|
|
445
|
+
const skillMap = new Map(skills.map((s) => [s.name, s]));
|
|
446
|
+
return (args) => {
|
|
447
|
+
const skill = skillMap.get(args.skill_name);
|
|
448
|
+
if (!skill) {
|
|
449
|
+
return {
|
|
450
|
+
toolResponse: JSON.stringify({
|
|
451
|
+
error: `Skill "${args.skill_name}" not found`
|
|
452
|
+
}),
|
|
453
|
+
data: null
|
|
454
|
+
};
|
|
455
|
+
}
|
|
471
456
|
return {
|
|
472
|
-
toolResponse:
|
|
473
|
-
data:
|
|
474
|
-
resultAppended: true
|
|
457
|
+
toolResponse: skill.instructions,
|
|
458
|
+
data: null
|
|
475
459
|
};
|
|
476
460
|
};
|
|
477
461
|
}
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
return
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
462
|
+
|
|
463
|
+
// src/lib/skills/register.ts
|
|
464
|
+
function buildSkillRegistration(skills) {
|
|
465
|
+
if (skills.length === 0) return null;
|
|
466
|
+
return {
|
|
467
|
+
...createReadSkillTool(skills),
|
|
468
|
+
handler: createReadSkillHandler(skills)
|
|
469
|
+
};
|
|
486
470
|
}
|
|
487
471
|
|
|
488
|
-
// src/lib/session.ts
|
|
472
|
+
// src/lib/session/session.ts
|
|
489
473
|
var createSession = async ({
|
|
490
474
|
threadId: providedThreadId,
|
|
491
475
|
agentName,
|
|
@@ -501,22 +485,34 @@ var createSession = async ({
|
|
|
501
485
|
hooks = {},
|
|
502
486
|
appendSystemPrompt = true,
|
|
503
487
|
continueThread = false,
|
|
504
|
-
waitForInputTimeout = "48h"
|
|
488
|
+
waitForInputTimeout = "48h",
|
|
489
|
+
sandbox: sandboxOps,
|
|
490
|
+
sandboxId: inheritedSandboxId
|
|
505
491
|
}) => {
|
|
506
|
-
const
|
|
492
|
+
const sourceThreadId = continueThread ? providedThreadId : void 0;
|
|
493
|
+
const threadId = continueThread && providedThreadId ? getShortId() : providedThreadId ?? getShortId();
|
|
507
494
|
const {
|
|
508
495
|
appendToolResult,
|
|
509
496
|
appendHumanMessage,
|
|
510
497
|
initializeThread,
|
|
511
|
-
appendSystemMessage
|
|
498
|
+
appendSystemMessage,
|
|
499
|
+
forkThread
|
|
512
500
|
} = threadOps ?? proxyDefaultThreadOps();
|
|
501
|
+
const plugins = [];
|
|
502
|
+
if (subagents) {
|
|
503
|
+
const reg = buildSubagentRegistration(subagents);
|
|
504
|
+
if (reg) plugins.push(reg);
|
|
505
|
+
}
|
|
506
|
+
if (skills) {
|
|
507
|
+
const reg = buildSkillRegistration(skills);
|
|
508
|
+
if (reg) plugins.push(reg);
|
|
509
|
+
}
|
|
513
510
|
const toolRouter = createToolRouter({
|
|
514
511
|
tools,
|
|
515
512
|
appendToolResult,
|
|
516
513
|
threadId,
|
|
517
514
|
hooks,
|
|
518
|
-
|
|
519
|
-
skills,
|
|
515
|
+
plugins,
|
|
520
516
|
parallel: processToolsInParallel
|
|
521
517
|
});
|
|
522
518
|
const callSessionEnd = async (exitReason, turns) => {
|
|
@@ -553,6 +549,17 @@ var createSession = async ({
|
|
|
553
549
|
stateManager.run();
|
|
554
550
|
}
|
|
555
551
|
);
|
|
552
|
+
let sandboxId = inheritedSandboxId;
|
|
553
|
+
const ownsSandbox = !sandboxId && !!sandboxOps;
|
|
554
|
+
if (ownsSandbox) {
|
|
555
|
+
const result = await sandboxOps.createSandbox({ id: threadId });
|
|
556
|
+
sandboxId = result.sandboxId;
|
|
557
|
+
if (result.stateUpdate) {
|
|
558
|
+
stateManager.mergeUpdate(
|
|
559
|
+
result.stateUpdate
|
|
560
|
+
);
|
|
561
|
+
}
|
|
562
|
+
}
|
|
556
563
|
if (hooks.onSessionStart) {
|
|
557
564
|
await hooks.onSessionStart({
|
|
558
565
|
threadId,
|
|
@@ -561,7 +568,9 @@ var createSession = async ({
|
|
|
561
568
|
});
|
|
562
569
|
}
|
|
563
570
|
const systemPrompt = stateManager.getSystemPrompt();
|
|
564
|
-
if (
|
|
571
|
+
if (continueThread && sourceThreadId) {
|
|
572
|
+
await forkThread(sourceThreadId, threadId);
|
|
573
|
+
} else {
|
|
565
574
|
if (appendSystemPrompt) {
|
|
566
575
|
if (!systemPrompt || systemPrompt.trim() === "") {
|
|
567
576
|
throw ApplicationFailure.create({
|
|
@@ -593,6 +602,7 @@ var createSession = async ({
|
|
|
593
602
|
stateManager.complete();
|
|
594
603
|
exitReason = "completed";
|
|
595
604
|
return {
|
|
605
|
+
threadId,
|
|
596
606
|
finalMessage: message,
|
|
597
607
|
exitReason,
|
|
598
608
|
usage: stateManager.getTotalUsage()
|
|
@@ -616,7 +626,8 @@ var createSession = async ({
|
|
|
616
626
|
const toolCallResults = await toolRouter.processToolCalls(
|
|
617
627
|
parsedToolCalls,
|
|
618
628
|
{
|
|
619
|
-
turn: currentTurn
|
|
629
|
+
turn: currentTurn,
|
|
630
|
+
...sandboxId !== void 0 && { sandboxId }
|
|
620
631
|
}
|
|
621
632
|
);
|
|
622
633
|
for (const result of toolCallResults) {
|
|
@@ -644,8 +655,12 @@ var createSession = async ({
|
|
|
644
655
|
throw ApplicationFailure.fromError(error);
|
|
645
656
|
} finally {
|
|
646
657
|
await callSessionEnd(exitReason, stateManager.getTurns());
|
|
658
|
+
if (ownsSandbox && sandboxId && sandboxOps) {
|
|
659
|
+
await sandboxOps.destroySandbox(sandboxId);
|
|
660
|
+
}
|
|
647
661
|
}
|
|
648
662
|
return {
|
|
663
|
+
threadId,
|
|
649
664
|
finalMessage: null,
|
|
650
665
|
exitReason,
|
|
651
666
|
usage: stateManager.getTotalUsage()
|
|
@@ -666,16 +681,109 @@ function proxyDefaultThreadOps(options) {
|
|
|
666
681
|
}
|
|
667
682
|
);
|
|
668
683
|
}
|
|
684
|
+
function proxySandboxOps(options) {
|
|
685
|
+
return proxyActivities(
|
|
686
|
+
options ?? {
|
|
687
|
+
startToCloseTimeout: "30s",
|
|
688
|
+
retry: {
|
|
689
|
+
maximumAttempts: 3,
|
|
690
|
+
initialInterval: "2s",
|
|
691
|
+
maximumInterval: "30s",
|
|
692
|
+
backoffCoefficient: 2
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
);
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
// src/lib/thread/manager.ts
|
|
699
|
+
var THREAD_TTL_SECONDS = 60 * 60 * 24 * 90;
|
|
700
|
+
var APPEND_IDEMPOTENT_SCRIPT = `
|
|
701
|
+
if redis.call('EXISTS', KEYS[1]) == 1 then
|
|
702
|
+
return 0
|
|
703
|
+
end
|
|
704
|
+
for i = 2, #ARGV do
|
|
705
|
+
redis.call('RPUSH', KEYS[2], ARGV[i])
|
|
706
|
+
end
|
|
707
|
+
redis.call('EXPIRE', KEYS[2], tonumber(ARGV[1]))
|
|
708
|
+
redis.call('SET', KEYS[1], '1', 'EX', tonumber(ARGV[1]))
|
|
709
|
+
return 1
|
|
710
|
+
`;
|
|
711
|
+
function getThreadKey(threadId, key) {
|
|
712
|
+
return `thread:${threadId}:${key}`;
|
|
713
|
+
}
|
|
714
|
+
function createThreadManager(config) {
|
|
715
|
+
const {
|
|
716
|
+
redis,
|
|
717
|
+
threadId,
|
|
718
|
+
key = "messages",
|
|
719
|
+
serialize = (m) => JSON.stringify(m),
|
|
720
|
+
deserialize = (raw) => JSON.parse(raw),
|
|
721
|
+
idOf
|
|
722
|
+
} = config;
|
|
723
|
+
const redisKey = getThreadKey(threadId, key);
|
|
724
|
+
const metaKey = getThreadKey(threadId, `${key}:meta`);
|
|
725
|
+
async function assertThreadExists() {
|
|
726
|
+
const exists = await redis.exists(metaKey);
|
|
727
|
+
if (!exists) {
|
|
728
|
+
throw new Error(`Thread "${threadId}" (key: ${key}) does not exist`);
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
return {
|
|
732
|
+
async initialize() {
|
|
733
|
+
await redis.del(redisKey);
|
|
734
|
+
await redis.set(metaKey, "1", "EX", THREAD_TTL_SECONDS);
|
|
735
|
+
},
|
|
736
|
+
async load() {
|
|
737
|
+
await assertThreadExists();
|
|
738
|
+
const data = await redis.lrange(redisKey, 0, -1);
|
|
739
|
+
return data.map(deserialize);
|
|
740
|
+
},
|
|
741
|
+
async append(messages) {
|
|
742
|
+
if (messages.length === 0) return;
|
|
743
|
+
await assertThreadExists();
|
|
744
|
+
if (idOf) {
|
|
745
|
+
const dedupId = messages.map(idOf).join(":");
|
|
746
|
+
const dedupKey = getThreadKey(threadId, `dedup:${dedupId}`);
|
|
747
|
+
await redis.eval(
|
|
748
|
+
APPEND_IDEMPOTENT_SCRIPT,
|
|
749
|
+
2,
|
|
750
|
+
dedupKey,
|
|
751
|
+
redisKey,
|
|
752
|
+
String(THREAD_TTL_SECONDS),
|
|
753
|
+
...messages.map(serialize)
|
|
754
|
+
);
|
|
755
|
+
} else {
|
|
756
|
+
await redis.rpush(redisKey, ...messages.map(serialize));
|
|
757
|
+
await redis.expire(redisKey, THREAD_TTL_SECONDS);
|
|
758
|
+
}
|
|
759
|
+
},
|
|
760
|
+
async fork(newThreadId) {
|
|
761
|
+
await assertThreadExists();
|
|
762
|
+
const data = await redis.lrange(redisKey, 0, -1);
|
|
763
|
+
const forked = createThreadManager({
|
|
764
|
+
...config,
|
|
765
|
+
threadId: newThreadId
|
|
766
|
+
});
|
|
767
|
+
await forked.initialize();
|
|
768
|
+
if (data.length > 0) {
|
|
769
|
+
const newKey = getThreadKey(newThreadId, key);
|
|
770
|
+
await redis.rpush(newKey, ...data);
|
|
771
|
+
await redis.expire(newKey, THREAD_TTL_SECONDS);
|
|
772
|
+
}
|
|
773
|
+
return forked;
|
|
774
|
+
},
|
|
775
|
+
async delete() {
|
|
776
|
+
await redis.del(redisKey, metaKey);
|
|
777
|
+
}
|
|
778
|
+
};
|
|
779
|
+
}
|
|
669
780
|
|
|
670
781
|
// src/lib/types.ts
|
|
671
|
-
var agentQueryName = (agentName) => `get${agentName}State`;
|
|
672
|
-
var agentStateChangeUpdateName = (agentName) => `waitFor${agentName}StateChange`;
|
|
673
782
|
function isTerminalStatus(status) {
|
|
674
783
|
return status === "COMPLETED" || status === "FAILED" || status === "CANCELLED";
|
|
675
784
|
}
|
|
676
785
|
function createAgentStateManager({
|
|
677
|
-
initialState
|
|
678
|
-
agentName
|
|
786
|
+
initialState
|
|
679
787
|
}) {
|
|
680
788
|
let status = initialState?.status ?? "RUNNING";
|
|
681
789
|
let version = initialState?.version ?? 0;
|
|
@@ -706,11 +814,9 @@ function createAgentStateManager({
|
|
|
706
814
|
...customState
|
|
707
815
|
};
|
|
708
816
|
}
|
|
709
|
-
const stateQuery = defineQuery(
|
|
710
|
-
agentQueryName(agentName)
|
|
711
|
-
);
|
|
817
|
+
const stateQuery = defineQuery("getAgentState");
|
|
712
818
|
const stateChangeUpdate = defineUpdate(
|
|
713
|
-
|
|
819
|
+
"waitForAgentStateChange"
|
|
714
820
|
);
|
|
715
821
|
setHandler(stateQuery, () => buildState());
|
|
716
822
|
setHandler(stateChangeUpdate, async (lastKnownVersion) => {
|
|
@@ -774,6 +880,10 @@ function createAgentStateManager({
|
|
|
774
880
|
customState[key] = value;
|
|
775
881
|
version++;
|
|
776
882
|
},
|
|
883
|
+
mergeUpdate(update) {
|
|
884
|
+
Object.assign(customState, update);
|
|
885
|
+
version++;
|
|
886
|
+
},
|
|
777
887
|
getCurrentState() {
|
|
778
888
|
return buildState();
|
|
779
889
|
},
|
|
@@ -829,6 +939,127 @@ function createAgentStateManager({
|
|
|
829
939
|
};
|
|
830
940
|
}
|
|
831
941
|
|
|
942
|
+
// src/lib/tool-router/auto-append.ts
|
|
943
|
+
function withAutoAppend(threadHandler, handler) {
|
|
944
|
+
return async (args, context) => {
|
|
945
|
+
const response = await handler(args, context);
|
|
946
|
+
await threadHandler({
|
|
947
|
+
threadId: context.threadId,
|
|
948
|
+
toolCallId: context.toolCallId,
|
|
949
|
+
toolName: context.toolName,
|
|
950
|
+
content: response.toolResponse
|
|
951
|
+
});
|
|
952
|
+
return {
|
|
953
|
+
toolResponse: "Response appended via withAutoAppend",
|
|
954
|
+
data: response.data,
|
|
955
|
+
resultAppended: true
|
|
956
|
+
};
|
|
957
|
+
};
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
// src/lib/tool-router/with-sandbox.ts
|
|
961
|
+
function withSandbox(manager, handler) {
|
|
962
|
+
return async (args, context) => {
|
|
963
|
+
if (!context.sandboxId) {
|
|
964
|
+
return {
|
|
965
|
+
toolResponse: `Error: No sandbox configured for this agent. The ${context.toolName} tool requires a sandbox.`,
|
|
966
|
+
data: null
|
|
967
|
+
};
|
|
968
|
+
}
|
|
969
|
+
const sandbox = await manager.getSandbox(context.sandboxId);
|
|
970
|
+
return handler(args, { ...context, sandbox, sandboxId: context.sandboxId });
|
|
971
|
+
};
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
// src/lib/subagent/define.ts
|
|
975
|
+
function defineSubagent(config) {
|
|
976
|
+
return config;
|
|
977
|
+
}
|
|
978
|
+
var SandboxNotSupportedError = class extends ApplicationFailure$1 {
|
|
979
|
+
constructor(operation) {
|
|
980
|
+
super(
|
|
981
|
+
`Sandbox does not support: ${operation}`,
|
|
982
|
+
"SandboxNotSupportedError",
|
|
983
|
+
true
|
|
984
|
+
);
|
|
985
|
+
}
|
|
986
|
+
};
|
|
987
|
+
var SandboxNotFoundError = class extends ApplicationFailure$1 {
|
|
988
|
+
constructor(sandboxId) {
|
|
989
|
+
super(`Sandbox not found: ${sandboxId}`, "SandboxNotFoundError", true);
|
|
990
|
+
}
|
|
991
|
+
};
|
|
992
|
+
|
|
993
|
+
// src/adapters/sandbox/virtual/mutations.ts
|
|
994
|
+
function applyVirtualTreeMutations(stateManager, mutations) {
|
|
995
|
+
let tree = [...stateManager.get("fileTree")];
|
|
996
|
+
for (const m of mutations) {
|
|
997
|
+
switch (m.type) {
|
|
998
|
+
case "add":
|
|
999
|
+
tree.push(m.entry);
|
|
1000
|
+
break;
|
|
1001
|
+
case "remove":
|
|
1002
|
+
tree = tree.filter((e) => e.path !== m.path);
|
|
1003
|
+
break;
|
|
1004
|
+
case "update":
|
|
1005
|
+
tree = tree.map(
|
|
1006
|
+
(e) => e.path === m.path ? { ...e, ...m.entry } : e
|
|
1007
|
+
);
|
|
1008
|
+
break;
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
stateManager.set("fileTree", tree);
|
|
1012
|
+
return tree;
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
// src/adapters/sandbox/virtual/tree.ts
|
|
1016
|
+
var buildTree = (entries) => {
|
|
1017
|
+
const root = { name: "/", children: /* @__PURE__ */ new Map(), isFile: false };
|
|
1018
|
+
for (const entry of entries) {
|
|
1019
|
+
const parts = entry.path.split("/").filter(Boolean);
|
|
1020
|
+
let current = root;
|
|
1021
|
+
for (const part of parts) {
|
|
1022
|
+
let child = current.children.get(part);
|
|
1023
|
+
if (!child) {
|
|
1024
|
+
child = { name: part, children: /* @__PURE__ */ new Map(), isFile: false };
|
|
1025
|
+
current.children.set(part, child);
|
|
1026
|
+
}
|
|
1027
|
+
current = child;
|
|
1028
|
+
}
|
|
1029
|
+
current.isFile = current.children.size === 0;
|
|
1030
|
+
}
|
|
1031
|
+
return root;
|
|
1032
|
+
};
|
|
1033
|
+
var printNode = (node, tab, sort) => {
|
|
1034
|
+
const entries = [...node.children.values()];
|
|
1035
|
+
if (sort) {
|
|
1036
|
+
entries.sort((a, b) => {
|
|
1037
|
+
if (!a.isFile && !b.isFile) return a.name.localeCompare(b.name);
|
|
1038
|
+
if (!a.isFile) return -1;
|
|
1039
|
+
if (!b.isFile) return 1;
|
|
1040
|
+
return a.name.localeCompare(b.name);
|
|
1041
|
+
});
|
|
1042
|
+
}
|
|
1043
|
+
let str = "";
|
|
1044
|
+
for (const [i, entry] of entries.entries()) {
|
|
1045
|
+
const isLast = i === entries.length - 1;
|
|
1046
|
+
const branch = isLast ? "\u2514\u2500" : "\u251C\u2500";
|
|
1047
|
+
const childTab = tab + (isLast ? " " : "\u2502 ");
|
|
1048
|
+
if (entry.isFile) {
|
|
1049
|
+
str += "\n" + tab + branch + " " + entry.name;
|
|
1050
|
+
} else {
|
|
1051
|
+
const subtree = printNode(entry, childTab, sort);
|
|
1052
|
+
str += "\n" + tab + branch + " " + entry.name + "/" + subtree;
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
return str;
|
|
1056
|
+
};
|
|
1057
|
+
function formatVirtualFileTree(entries, opts = {}) {
|
|
1058
|
+
const sort = opts.sort ?? true;
|
|
1059
|
+
const root = buildTree(entries);
|
|
1060
|
+
return "/" + printNode(root, "", sort);
|
|
1061
|
+
}
|
|
1062
|
+
|
|
832
1063
|
// src/lib/skills/parse.ts
|
|
833
1064
|
function parseSkillFile(raw) {
|
|
834
1065
|
const trimmed = raw.replace(/^\uFEFF/, "");
|
|
@@ -1250,197 +1481,313 @@ var createAskUserQuestionHandler = () => async (args) => {
|
|
|
1250
1481
|
data: { questions: args.questions }
|
|
1251
1482
|
};
|
|
1252
1483
|
};
|
|
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`);
|
|
1484
|
+
var FileSystemSkillProvider = class {
|
|
1485
|
+
constructor(fs, baseDir) {
|
|
1486
|
+
this.fs = fs;
|
|
1487
|
+
this.baseDir = baseDir;
|
|
1488
|
+
}
|
|
1489
|
+
async listSkills() {
|
|
1490
|
+
const dirs = await this.discoverSkillDirs();
|
|
1491
|
+
const skills = [];
|
|
1492
|
+
for (const dir of dirs) {
|
|
1493
|
+
const raw = await this.fs.readFile(join(this.baseDir, dir, "SKILL.md"));
|
|
1494
|
+
const { frontmatter } = parseSkillFile(raw);
|
|
1495
|
+
skills.push(frontmatter);
|
|
1285
1496
|
}
|
|
1497
|
+
return skills;
|
|
1286
1498
|
}
|
|
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
|
-
|
|
1499
|
+
async getSkill(name) {
|
|
1500
|
+
const raw = await this.fs.readFile(
|
|
1501
|
+
join(this.baseDir, name, "SKILL.md")
|
|
1502
|
+
);
|
|
1503
|
+
const { frontmatter, body } = parseSkillFile(raw);
|
|
1504
|
+
if (frontmatter.name !== name) {
|
|
1505
|
+
throw new Error(
|
|
1506
|
+
`Skill directory "${name}" contains SKILL.md with mismatched name "${frontmatter.name}"`
|
|
1507
|
+
);
|
|
1508
|
+
}
|
|
1509
|
+
return { ...frontmatter, instructions: body };
|
|
1510
|
+
}
|
|
1511
|
+
/**
|
|
1512
|
+
* Convenience method to load all skills with full instructions.
|
|
1513
|
+
* Returns `Skill[]` ready to pass into a workflow.
|
|
1514
|
+
*/
|
|
1515
|
+
async loadAll() {
|
|
1516
|
+
const dirs = await this.discoverSkillDirs();
|
|
1517
|
+
const skills = [];
|
|
1518
|
+
for (const dir of dirs) {
|
|
1519
|
+
const raw = await this.fs.readFile(join(this.baseDir, dir, "SKILL.md"));
|
|
1520
|
+
const { frontmatter, body } = parseSkillFile(raw);
|
|
1521
|
+
skills.push({ ...frontmatter, instructions: body });
|
|
1522
|
+
}
|
|
1523
|
+
return skills;
|
|
1524
|
+
}
|
|
1525
|
+
async discoverSkillDirs() {
|
|
1526
|
+
const entries = await this.fs.readdirWithFileTypes(this.baseDir);
|
|
1527
|
+
const dirs = [];
|
|
1528
|
+
for (const entry of entries) {
|
|
1529
|
+
if (!entry.isDirectory) continue;
|
|
1530
|
+
const skillPath = join(this.baseDir, entry.name, "SKILL.md");
|
|
1531
|
+
if (await this.fs.exists(skillPath)) {
|
|
1532
|
+
dirs.push(entry.name);
|
|
1314
1533
|
}
|
|
1315
|
-
},
|
|
1316
|
-
async delete() {
|
|
1317
|
-
await redis.del(redisKey, metaKey);
|
|
1318
1534
|
}
|
|
1319
|
-
|
|
1320
|
-
}
|
|
1321
|
-
|
|
1535
|
+
return dirs;
|
|
1536
|
+
}
|
|
1537
|
+
};
|
|
1538
|
+
async function queryParentWorkflowState(client) {
|
|
1322
1539
|
const { workflowExecution } = Context.current().info;
|
|
1323
1540
|
const handle = client.getHandle(
|
|
1324
1541
|
workflowExecution.workflowId,
|
|
1325
1542
|
workflowExecution.runId
|
|
1326
1543
|
);
|
|
1327
|
-
return handle.query(
|
|
1544
|
+
return handle.query("getAgentState");
|
|
1328
1545
|
}
|
|
1329
|
-
function createRunAgentActivity(client,
|
|
1546
|
+
function createRunAgentActivity(client, handler) {
|
|
1330
1547
|
return async (config) => {
|
|
1331
|
-
const state = await queryParentWorkflowState(
|
|
1332
|
-
|
|
1333
|
-
agentQueryName(config.agentName)
|
|
1334
|
-
);
|
|
1335
|
-
return invoker({ ...config, state });
|
|
1548
|
+
const state = await queryParentWorkflowState(client);
|
|
1549
|
+
return handler({ ...config, state });
|
|
1336
1550
|
};
|
|
1337
1551
|
}
|
|
1338
|
-
function
|
|
1339
|
-
return async (
|
|
1340
|
-
|
|
1341
|
-
return {
|
|
1342
|
-
toolResponse: "Hello, world!",
|
|
1343
|
-
data: { files: [] }
|
|
1344
|
-
};
|
|
1552
|
+
function withParentWorkflowState(client, handler) {
|
|
1553
|
+
return async (args, context) => {
|
|
1554
|
+
const state = await queryParentWorkflowState(client);
|
|
1555
|
+
return handler(args, { ...context, state });
|
|
1345
1556
|
};
|
|
1346
1557
|
}
|
|
1347
1558
|
|
|
1559
|
+
// src/lib/sandbox/manager.ts
|
|
1560
|
+
var SandboxManager = class {
|
|
1561
|
+
constructor(provider) {
|
|
1562
|
+
this.provider = provider;
|
|
1563
|
+
}
|
|
1564
|
+
async create(options) {
|
|
1565
|
+
const { sandbox, stateUpdate } = await this.provider.create(options);
|
|
1566
|
+
return { sandboxId: sandbox.id, ...stateUpdate && { stateUpdate } };
|
|
1567
|
+
}
|
|
1568
|
+
async getSandbox(id) {
|
|
1569
|
+
return this.provider.get(id);
|
|
1570
|
+
}
|
|
1571
|
+
async destroy(id) {
|
|
1572
|
+
await this.provider.destroy(id);
|
|
1573
|
+
}
|
|
1574
|
+
async snapshot(id) {
|
|
1575
|
+
return this.provider.snapshot(id);
|
|
1576
|
+
}
|
|
1577
|
+
async restore(snapshot) {
|
|
1578
|
+
const sandbox = await this.provider.restore(snapshot);
|
|
1579
|
+
return sandbox.id;
|
|
1580
|
+
}
|
|
1581
|
+
/**
|
|
1582
|
+
* Returns Temporal activity functions matching {@link SandboxOps}.
|
|
1583
|
+
* Spread these into your worker's activity map.
|
|
1584
|
+
*/
|
|
1585
|
+
createActivities() {
|
|
1586
|
+
return {
|
|
1587
|
+
createSandbox: async (options) => {
|
|
1588
|
+
return this.create(options);
|
|
1589
|
+
},
|
|
1590
|
+
destroySandbox: async (sandboxId) => {
|
|
1591
|
+
await this.destroy(sandboxId);
|
|
1592
|
+
},
|
|
1593
|
+
snapshotSandbox: async (sandboxId) => {
|
|
1594
|
+
return this.snapshot(sandboxId);
|
|
1595
|
+
}
|
|
1596
|
+
};
|
|
1597
|
+
}
|
|
1598
|
+
};
|
|
1599
|
+
|
|
1600
|
+
// src/tools/bash/handler.ts
|
|
1601
|
+
var bashHandler = async (args, { sandbox }) => {
|
|
1602
|
+
try {
|
|
1603
|
+
const result = await sandbox.exec(args.command);
|
|
1604
|
+
return {
|
|
1605
|
+
toolResponse: `Exit code: ${result.exitCode}
|
|
1606
|
+
|
|
1607
|
+
stdout:
|
|
1608
|
+
${result.stdout}
|
|
1609
|
+
|
|
1610
|
+
stderr:
|
|
1611
|
+
${result.stderr}`,
|
|
1612
|
+
data: result
|
|
1613
|
+
};
|
|
1614
|
+
} catch (error) {
|
|
1615
|
+
const err = error instanceof Error ? error : new Error("Unknown error");
|
|
1616
|
+
return {
|
|
1617
|
+
toolResponse: `Error executing bash command: ${err.message}`,
|
|
1618
|
+
data: null
|
|
1619
|
+
};
|
|
1620
|
+
}
|
|
1621
|
+
};
|
|
1622
|
+
|
|
1348
1623
|
// src/tools/edit/handler.ts
|
|
1349
1624
|
function escapeRegExp(str) {
|
|
1350
1625
|
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1351
1626
|
}
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1627
|
+
var editHandler = async (args, { sandbox }) => {
|
|
1628
|
+
const { fs } = sandbox;
|
|
1629
|
+
const { file_path, old_string, new_string, replace_all = false } = args;
|
|
1630
|
+
if (old_string === new_string) {
|
|
1631
|
+
return {
|
|
1632
|
+
toolResponse: `Error: old_string and new_string must be different.`,
|
|
1633
|
+
data: { path: file_path, success: false, replacements: 0 }
|
|
1634
|
+
};
|
|
1635
|
+
}
|
|
1636
|
+
try {
|
|
1637
|
+
const exists = await fs.exists(file_path);
|
|
1638
|
+
if (!exists) {
|
|
1356
1639
|
return {
|
|
1357
|
-
toolResponse: `Error:
|
|
1640
|
+
toolResponse: `Error: File "${file_path}" does not exist.`,
|
|
1358
1641
|
data: { path: file_path, success: false, replacements: 0 }
|
|
1359
1642
|
};
|
|
1360
1643
|
}
|
|
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`;
|
|
1644
|
+
const content = await fs.readFile(file_path);
|
|
1645
|
+
if (!content.includes(old_string)) {
|
|
1397
1646
|
return {
|
|
1398
|
-
toolResponse:
|
|
1399
|
-
data: { path: file_path, success:
|
|
1647
|
+
toolResponse: `Error: Could not find the specified text in "${file_path}". Make sure old_string matches exactly (whitespace-sensitive).`,
|
|
1648
|
+
data: { path: file_path, success: false, replacements: 0 }
|
|
1400
1649
|
};
|
|
1401
|
-
}
|
|
1402
|
-
|
|
1650
|
+
}
|
|
1651
|
+
const escapedOldString = escapeRegExp(old_string);
|
|
1652
|
+
const globalRegex = new RegExp(escapedOldString, "g");
|
|
1653
|
+
const occurrences = (content.match(globalRegex) || []).length;
|
|
1654
|
+
if (!replace_all && occurrences > 1) {
|
|
1403
1655
|
return {
|
|
1404
|
-
toolResponse: `Error
|
|
1656
|
+
toolResponse: `Error: old_string appears ${occurrences} times in "${file_path}". Either provide more context to make it unique, or use replace_all: true.`,
|
|
1405
1657
|
data: { path: file_path, success: false, replacements: 0 }
|
|
1406
1658
|
};
|
|
1407
1659
|
}
|
|
1408
|
-
|
|
1660
|
+
let newContent;
|
|
1661
|
+
let replacements;
|
|
1662
|
+
if (replace_all) {
|
|
1663
|
+
newContent = content.split(old_string).join(new_string);
|
|
1664
|
+
replacements = occurrences;
|
|
1665
|
+
} else {
|
|
1666
|
+
const index = content.indexOf(old_string);
|
|
1667
|
+
newContent = content.slice(0, index) + new_string + content.slice(index + old_string.length);
|
|
1668
|
+
replacements = 1;
|
|
1669
|
+
}
|
|
1670
|
+
await fs.writeFile(file_path, newContent);
|
|
1671
|
+
const summary = replace_all ? `Replaced ${replacements} occurrence(s)` : `Replaced 1 occurrence`;
|
|
1672
|
+
return {
|
|
1673
|
+
toolResponse: `${summary} in ${file_path}`,
|
|
1674
|
+
data: { path: file_path, success: true, replacements }
|
|
1675
|
+
};
|
|
1676
|
+
} catch (error) {
|
|
1677
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
1678
|
+
return {
|
|
1679
|
+
toolResponse: `Error editing file "${file_path}": ${message}`,
|
|
1680
|
+
data: { path: file_path, success: false, replacements: 0 }
|
|
1681
|
+
};
|
|
1682
|
+
}
|
|
1683
|
+
};
|
|
1684
|
+
|
|
1685
|
+
// src/tools/glob/handler.ts
|
|
1686
|
+
function matchGlob(pattern, path) {
|
|
1687
|
+
const regex = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*\*/g, "{{GLOBSTAR}}").replace(/\*/g, "[^/]*").replace(/\{\{GLOBSTAR\}\}/g, ".*");
|
|
1688
|
+
return new RegExp(`^${regex}$`).test(path);
|
|
1409
1689
|
}
|
|
1410
|
-
|
|
1411
|
-
const
|
|
1412
|
-
const
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1690
|
+
async function walk(fs, dir) {
|
|
1691
|
+
const results = [];
|
|
1692
|
+
const entries = await fs.readdirWithFileTypes(dir);
|
|
1693
|
+
for (const entry of entries) {
|
|
1694
|
+
const full = dir === "/" ? `/${entry.name}` : `${dir}/${entry.name}`;
|
|
1695
|
+
if (entry.isDirectory) {
|
|
1696
|
+
results.push(...await walk(fs, full));
|
|
1697
|
+
} else {
|
|
1698
|
+
results.push(full);
|
|
1418
1699
|
}
|
|
1419
|
-
}
|
|
1420
|
-
|
|
1700
|
+
}
|
|
1701
|
+
return results;
|
|
1702
|
+
}
|
|
1703
|
+
var globHandler = async (args, { sandbox }) => {
|
|
1704
|
+
const { fs } = sandbox;
|
|
1705
|
+
const { pattern, root = "/" } = args;
|
|
1421
1706
|
try {
|
|
1422
|
-
const
|
|
1423
|
-
const
|
|
1707
|
+
const allFiles = await walk(fs, root);
|
|
1708
|
+
const relativeTo = root.endsWith("/") ? root : `${root}/`;
|
|
1709
|
+
const matched = allFiles.map((f) => f.startsWith(relativeTo) ? f.slice(relativeTo.length) : f).filter((f) => matchGlob(pattern, f));
|
|
1424
1710
|
return {
|
|
1425
|
-
toolResponse: `
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1711
|
+
toolResponse: matched.length > 0 ? `Found ${matched.length} file(s):
|
|
1712
|
+
${matched.join("\n")}` : `No files matched pattern "${pattern}"`,
|
|
1713
|
+
data: { files: matched }
|
|
1714
|
+
};
|
|
1715
|
+
} catch (error) {
|
|
1716
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
1717
|
+
return {
|
|
1718
|
+
toolResponse: `Error running glob: ${message}`,
|
|
1719
|
+
data: { files: [] }
|
|
1720
|
+
};
|
|
1721
|
+
}
|
|
1722
|
+
};
|
|
1429
1723
|
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1724
|
+
// src/tools/read-file/handler.ts
|
|
1725
|
+
var readFileHandler = async (args, { sandbox }) => {
|
|
1726
|
+
const { fs } = sandbox;
|
|
1727
|
+
const { path, offset, limit } = args;
|
|
1728
|
+
try {
|
|
1729
|
+
const exists = await fs.exists(path);
|
|
1730
|
+
if (!exists) {
|
|
1731
|
+
return {
|
|
1732
|
+
toolResponse: `Error: File "${path}" does not exist.`,
|
|
1733
|
+
data: null
|
|
1734
|
+
};
|
|
1735
|
+
}
|
|
1736
|
+
const raw = await fs.readFile(path);
|
|
1737
|
+
const lines = raw.split("\n");
|
|
1738
|
+
const totalLines = lines.length;
|
|
1739
|
+
if (offset !== void 0 || limit !== void 0) {
|
|
1740
|
+
const start = Math.max(0, (offset ?? 1) - 1);
|
|
1741
|
+
const end = limit !== void 0 ? start + limit : lines.length;
|
|
1742
|
+
const slice = lines.slice(start, end);
|
|
1743
|
+
const numbered2 = slice.map((line, i) => `${String(start + i + 1).padStart(6)}|${line}`).join("\n");
|
|
1744
|
+
return {
|
|
1745
|
+
toolResponse: numbered2,
|
|
1746
|
+
data: { path, content: numbered2, totalLines }
|
|
1747
|
+
};
|
|
1748
|
+
}
|
|
1749
|
+
const numbered = lines.map((line, i) => `${String(i + 1).padStart(6)}|${line}`).join("\n");
|
|
1750
|
+
return {
|
|
1751
|
+
toolResponse: numbered,
|
|
1752
|
+
data: { path, content: numbered, totalLines }
|
|
1433
1753
|
};
|
|
1434
1754
|
} catch (error) {
|
|
1435
|
-
const
|
|
1755
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
1436
1756
|
return {
|
|
1437
|
-
toolResponse: `Error
|
|
1757
|
+
toolResponse: `Error reading file "${path}": ${message}`,
|
|
1438
1758
|
data: null
|
|
1439
1759
|
};
|
|
1440
1760
|
}
|
|
1441
1761
|
};
|
|
1442
1762
|
|
|
1443
|
-
// src/
|
|
1763
|
+
// src/tools/write-file/handler.ts
|
|
1764
|
+
var writeFileHandler = async (args, { sandbox }) => {
|
|
1765
|
+
const { fs } = sandbox;
|
|
1766
|
+
const { file_path, content } = args;
|
|
1767
|
+
try {
|
|
1768
|
+
const lastSlash = file_path.lastIndexOf("/");
|
|
1769
|
+
if (lastSlash > 0) {
|
|
1770
|
+
const dir = file_path.slice(0, lastSlash);
|
|
1771
|
+
const dirExists = await fs.exists(dir);
|
|
1772
|
+
if (!dirExists) {
|
|
1773
|
+
await fs.mkdir(dir, { recursive: true });
|
|
1774
|
+
}
|
|
1775
|
+
}
|
|
1776
|
+
await fs.writeFile(file_path, content);
|
|
1777
|
+
return {
|
|
1778
|
+
toolResponse: `Successfully wrote to ${file_path}`,
|
|
1779
|
+
data: { path: file_path, success: true }
|
|
1780
|
+
};
|
|
1781
|
+
} catch (error) {
|
|
1782
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
1783
|
+
return {
|
|
1784
|
+
toolResponse: `Error writing file "${file_path}": ${message}`,
|
|
1785
|
+
data: { path: file_path, success: false }
|
|
1786
|
+
};
|
|
1787
|
+
}
|
|
1788
|
+
};
|
|
1789
|
+
|
|
1790
|
+
// src/lib/sandbox/tree.ts
|
|
1444
1791
|
var basename = (path, separator) => {
|
|
1445
1792
|
if (path[path.length - 1] === separator) path = path.slice(0, -1);
|
|
1446
1793
|
const lastSlashIndex = path.lastIndexOf(separator);
|
|
@@ -1469,7 +1816,7 @@ var toTree = async (fs, opts = {}) => {
|
|
|
1469
1816
|
const sort = opts.sort ?? true;
|
|
1470
1817
|
let subtree = " (...)";
|
|
1471
1818
|
if (depth > 0) {
|
|
1472
|
-
const list = await fs.readdirWithFileTypes
|
|
1819
|
+
const list = await fs.readdirWithFileTypes(dir);
|
|
1473
1820
|
if (sort) {
|
|
1474
1821
|
list.sort((a, b) => {
|
|
1475
1822
|
if (a.isDirectory && b.isDirectory) {
|
|
@@ -1503,62 +1850,7 @@ var toTree = async (fs, opts = {}) => {
|
|
|
1503
1850
|
const base = basename(dir, separator) + separator;
|
|
1504
1851
|
return base + subtree;
|
|
1505
1852
|
};
|
|
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
1853
|
|
|
1562
|
-
export { FileSystemSkillProvider,
|
|
1854
|
+
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
1855
|
//# sourceMappingURL=index.js.map
|
|
1564
1856
|
//# sourceMappingURL=index.js.map
|