@lakitu/sdk 0.1.0
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 +166 -0
- package/convex/_generated/api.d.ts +45 -0
- package/convex/_generated/api.js +23 -0
- package/convex/_generated/dataModel.d.ts +58 -0
- package/convex/_generated/server.d.ts +143 -0
- package/convex/_generated/server.js +93 -0
- package/convex/cloud/CLAUDE.md +238 -0
- package/convex/cloud/_generated/api.ts +84 -0
- package/convex/cloud/_generated/component.ts +861 -0
- package/convex/cloud/_generated/dataModel.ts +60 -0
- package/convex/cloud/_generated/server.ts +156 -0
- package/convex/cloud/convex.config.ts +16 -0
- package/convex/cloud/index.ts +29 -0
- package/convex/cloud/intentSchema/generate.ts +447 -0
- package/convex/cloud/intentSchema/index.ts +16 -0
- package/convex/cloud/intentSchema/types.ts +418 -0
- package/convex/cloud/ksaPolicy.ts +554 -0
- package/convex/cloud/mail.ts +92 -0
- package/convex/cloud/schema.ts +322 -0
- package/convex/cloud/utils/kanbanContext.ts +229 -0
- package/convex/cloud/workflows/agentBoard.ts +451 -0
- package/convex/cloud/workflows/agentPrompt.ts +272 -0
- package/convex/cloud/workflows/agentThread.ts +374 -0
- package/convex/cloud/workflows/compileSandbox.ts +146 -0
- package/convex/cloud/workflows/crudBoard.ts +217 -0
- package/convex/cloud/workflows/crudKSAs.ts +262 -0
- package/convex/cloud/workflows/crudLorobeads.ts +371 -0
- package/convex/cloud/workflows/crudSkills.ts +205 -0
- package/convex/cloud/workflows/crudThreads.ts +708 -0
- package/convex/cloud/workflows/lifecycleSandbox.ts +1396 -0
- package/convex/cloud/workflows/sandboxConvex.ts +1046 -0
- package/convex/sandbox/README.md +90 -0
- package/convex/sandbox/_generated/api.d.ts +2934 -0
- package/convex/sandbox/_generated/api.js +23 -0
- package/convex/sandbox/_generated/dataModel.d.ts +60 -0
- package/convex/sandbox/_generated/server.d.ts +143 -0
- package/convex/sandbox/_generated/server.js +93 -0
- package/convex/sandbox/actions/bash.ts +130 -0
- package/convex/sandbox/actions/browser.ts +282 -0
- package/convex/sandbox/actions/file.ts +336 -0
- package/convex/sandbox/actions/lsp.ts +325 -0
- package/convex/sandbox/actions/pdf.ts +119 -0
- package/convex/sandbox/agent/codeExecLoop.ts +535 -0
- package/convex/sandbox/agent/decisions.ts +284 -0
- package/convex/sandbox/agent/index.ts +515 -0
- package/convex/sandbox/agent/subagents.ts +651 -0
- package/convex/sandbox/brandResearch/index.ts +417 -0
- package/convex/sandbox/context/index.ts +7 -0
- package/convex/sandbox/context/session.ts +402 -0
- package/convex/sandbox/convex.config.ts +17 -0
- package/convex/sandbox/index.ts +51 -0
- package/convex/sandbox/nodeActions/codeExec.ts +130 -0
- package/convex/sandbox/planning/beads.ts +187 -0
- package/convex/sandbox/planning/index.ts +8 -0
- package/convex/sandbox/planning/sync.ts +194 -0
- package/convex/sandbox/prompts/codeExec.ts +852 -0
- package/convex/sandbox/prompts/modes.ts +231 -0
- package/convex/sandbox/prompts/system.ts +142 -0
- package/convex/sandbox/schema.ts +510 -0
- package/convex/sandbox/state/artifacts.ts +99 -0
- package/convex/sandbox/state/checkpoints.ts +341 -0
- package/convex/sandbox/state/files.ts +383 -0
- package/convex/sandbox/state/index.ts +10 -0
- package/convex/sandbox/state/verification.actions.ts +268 -0
- package/convex/sandbox/state/verification.ts +101 -0
- package/convex/sandbox/tsconfig.json +25 -0
- package/convex/sandbox/utils/codeExecHelpers.ts +52 -0
- package/dist/cli/commands/build.d.ts +19 -0
- package/dist/cli/commands/build.d.ts.map +1 -0
- package/dist/cli/commands/build.js +223 -0
- package/dist/cli/commands/init.d.ts +16 -0
- package/dist/cli/commands/init.d.ts.map +1 -0
- package/dist/cli/commands/init.js +148 -0
- package/dist/cli/commands/publish.d.ts +12 -0
- package/dist/cli/commands/publish.d.ts.map +1 -0
- package/dist/cli/commands/publish.js +33 -0
- package/dist/cli/index.d.ts +14 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +40 -0
- package/dist/sdk/builders.d.ts +104 -0
- package/dist/sdk/builders.d.ts.map +1 -0
- package/dist/sdk/builders.js +214 -0
- package/dist/sdk/index.d.ts +29 -0
- package/dist/sdk/index.d.ts.map +1 -0
- package/dist/sdk/index.js +38 -0
- package/dist/sdk/types.d.ts +107 -0
- package/dist/sdk/types.d.ts.map +1 -0
- package/dist/sdk/types.js +6 -0
- package/ksa/README.md +263 -0
- package/ksa/_generated/REFERENCE.md +2954 -0
- package/ksa/_generated/registry.ts +257 -0
- package/ksa/_shared/configReader.ts +302 -0
- package/ksa/_shared/configSchemas.ts +649 -0
- package/ksa/_shared/gateway.ts +175 -0
- package/ksa/_shared/ksaBehaviors.ts +411 -0
- package/ksa/_shared/ksaProxy.ts +248 -0
- package/ksa/_shared/localDb.ts +302 -0
- package/ksa/index.ts +134 -0
- package/package.json +93 -0
- package/runtime/browser/agent-browser.ts +330 -0
- package/runtime/entrypoint.ts +194 -0
- package/runtime/lsp/manager.ts +366 -0
- package/runtime/pdf/pdf-generator.ts +50 -0
- package/runtime/pdf/renderer.ts +357 -0
- package/runtime/pdf/schema.ts +97 -0
- package/runtime/services/file-watcher.ts +191 -0
- package/template/build.ts +307 -0
- package/template/e2b/Dockerfile +69 -0
- package/template/e2b/e2b.toml +13 -0
- package/template/e2b/prebuild.sh +68 -0
- package/template/e2b/start.sh +14 -0
|
@@ -0,0 +1,651 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Subagents - Spawn and manage child agents
|
|
3
|
+
*
|
|
4
|
+
* Enables parallel work and task delegation through subagent spawning.
|
|
5
|
+
* Uses cloud gateway for LLM calls (same as main agent).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
mutation,
|
|
10
|
+
query,
|
|
11
|
+
internalMutation,
|
|
12
|
+
internalAction,
|
|
13
|
+
} from "../_generated/server";
|
|
14
|
+
import { internal } from "../_generated/api";
|
|
15
|
+
import { v } from "convex/values";
|
|
16
|
+
|
|
17
|
+
// Legacy tool system removed - subagents now use code execution mode
|
|
18
|
+
// createSubagentToolset is no longer available
|
|
19
|
+
|
|
20
|
+
// Default model for subagents - fast and cheap
|
|
21
|
+
// This should match the "subagent" context defaults in convex/features/settings/models.ts
|
|
22
|
+
// For customization, pass model via options when spawning subagents
|
|
23
|
+
const DEFAULT_SUBAGENT_MODEL = "google/gemini-2.0-flash";
|
|
24
|
+
|
|
25
|
+
// ============================================
|
|
26
|
+
// Progress Emission (to cloud thread)
|
|
27
|
+
// ============================================
|
|
28
|
+
|
|
29
|
+
interface ChainOfThoughtStep {
|
|
30
|
+
id: string;
|
|
31
|
+
type: "thinking" | "tool" | "search" | "file" | "complete" | "error";
|
|
32
|
+
label: string;
|
|
33
|
+
status: "active" | "complete" | "error";
|
|
34
|
+
details?: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Emit subagent progress to the cloud thread for UI display.
|
|
39
|
+
* This calls the cloud Convex gateway to update the thread messages.
|
|
40
|
+
*/
|
|
41
|
+
async function emitProgress(
|
|
42
|
+
parentThreadId: string | undefined,
|
|
43
|
+
subagentId: string,
|
|
44
|
+
name: string,
|
|
45
|
+
task: string,
|
|
46
|
+
status: "spawning" | "running" | "completed" | "failed",
|
|
47
|
+
options?: {
|
|
48
|
+
progress?: number;
|
|
49
|
+
result?: string;
|
|
50
|
+
error?: string;
|
|
51
|
+
children?: ChainOfThoughtStep[];
|
|
52
|
+
}
|
|
53
|
+
): Promise<void> {
|
|
54
|
+
if (!parentThreadId) return; // No parent thread to emit to
|
|
55
|
+
|
|
56
|
+
const convexUrl = process.env.CONVEX_URL;
|
|
57
|
+
const jwt = process.env.SANDBOX_JWT;
|
|
58
|
+
|
|
59
|
+
if (!convexUrl || !jwt) return; // Can't emit without credentials
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
await fetch(`${convexUrl}/agent/call`, {
|
|
63
|
+
method: "POST",
|
|
64
|
+
headers: {
|
|
65
|
+
"Content-Type": "application/json",
|
|
66
|
+
Authorization: `Bearer ${jwt}`,
|
|
67
|
+
},
|
|
68
|
+
body: JSON.stringify({
|
|
69
|
+
path: "agent.workflows.crudThreads.emitSubagentProgress",
|
|
70
|
+
args: {
|
|
71
|
+
threadId: parentThreadId,
|
|
72
|
+
subagentId,
|
|
73
|
+
name,
|
|
74
|
+
task,
|
|
75
|
+
status,
|
|
76
|
+
progress: options?.progress,
|
|
77
|
+
result: options?.result,
|
|
78
|
+
error: options?.error,
|
|
79
|
+
children: options?.children,
|
|
80
|
+
},
|
|
81
|
+
}),
|
|
82
|
+
});
|
|
83
|
+
} catch (e) {
|
|
84
|
+
// Don't fail the subagent if progress emission fails
|
|
85
|
+
console.error("[subagent] Failed to emit progress:", e);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// ============================================
|
|
90
|
+
// Cloud LLM Gateway (shared with main agent)
|
|
91
|
+
// ============================================
|
|
92
|
+
|
|
93
|
+
interface LLMMessage {
|
|
94
|
+
role: "system" | "user" | "assistant";
|
|
95
|
+
content: string;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
interface ToolCall {
|
|
99
|
+
toolName: string;
|
|
100
|
+
args: Record<string, unknown>;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
interface LLMResponse {
|
|
104
|
+
text: string;
|
|
105
|
+
toolCalls?: ToolCall[];
|
|
106
|
+
finishReason?: string;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
interface OpenAITool {
|
|
110
|
+
type: "function";
|
|
111
|
+
function: {
|
|
112
|
+
name: string;
|
|
113
|
+
description?: string;
|
|
114
|
+
parameters?: Record<string, any>;
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Call the cloud Convex gateway for LLM completions.
|
|
120
|
+
*/
|
|
121
|
+
async function callCloudLLM(
|
|
122
|
+
messages: LLMMessage[],
|
|
123
|
+
options: {
|
|
124
|
+
model?: string;
|
|
125
|
+
tools?: Array<{ name: string; description: string; parameters: any }>;
|
|
126
|
+
maxTokens?: number;
|
|
127
|
+
temperature?: number;
|
|
128
|
+
} = {}
|
|
129
|
+
): Promise<LLMResponse> {
|
|
130
|
+
const convexUrl = process.env.CONVEX_URL;
|
|
131
|
+
const jwt = process.env.SANDBOX_JWT;
|
|
132
|
+
|
|
133
|
+
if (!convexUrl || !jwt) {
|
|
134
|
+
throw new Error("CONVEX_URL or SANDBOX_JWT not set");
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const openAITools: OpenAITool[] | undefined = options.tools?.map((tool) => ({
|
|
138
|
+
type: "function" as const,
|
|
139
|
+
function: {
|
|
140
|
+
name: tool.name,
|
|
141
|
+
description: tool.description,
|
|
142
|
+
parameters: tool.parameters || { type: "object", properties: {} },
|
|
143
|
+
},
|
|
144
|
+
}));
|
|
145
|
+
|
|
146
|
+
const response = await fetch(`${convexUrl}/agent/call`, {
|
|
147
|
+
method: "POST",
|
|
148
|
+
headers: {
|
|
149
|
+
"Content-Type": "application/json",
|
|
150
|
+
Authorization: `Bearer ${jwt}`,
|
|
151
|
+
},
|
|
152
|
+
body: JSON.stringify({
|
|
153
|
+
path: "services.OpenRouter.internal.chatCompletion",
|
|
154
|
+
args: {
|
|
155
|
+
model: options.model || DEFAULT_SUBAGENT_MODEL,
|
|
156
|
+
messages,
|
|
157
|
+
tools: openAITools,
|
|
158
|
+
maxTokens: options.maxTokens || 4096,
|
|
159
|
+
temperature: options.temperature,
|
|
160
|
+
speedy: false,
|
|
161
|
+
},
|
|
162
|
+
}),
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
if (!response.ok) {
|
|
166
|
+
const error = await response.text();
|
|
167
|
+
throw new Error(`Cloud LLM call failed (${response.status}): ${error}`);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const result = await response.json();
|
|
171
|
+
if (!result.ok) {
|
|
172
|
+
throw new Error(`Cloud LLM error: ${result.error || JSON.stringify(result)}`);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const data = result.data;
|
|
176
|
+
const choice = data.choices?.[0];
|
|
177
|
+
|
|
178
|
+
const toolCalls = choice?.message?.tool_calls?.map((tc: any) => ({
|
|
179
|
+
toolName: tc.function?.name || tc.name,
|
|
180
|
+
args: typeof tc.function?.arguments === "string"
|
|
181
|
+
? JSON.parse(tc.function.arguments)
|
|
182
|
+
: tc.function?.arguments || tc.arguments || {},
|
|
183
|
+
}));
|
|
184
|
+
|
|
185
|
+
return {
|
|
186
|
+
text: choice?.message?.content || "",
|
|
187
|
+
toolCalls: toolCalls?.length > 0 ? toolCalls : undefined,
|
|
188
|
+
finishReason: choice?.finish_reason,
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// ============================================
|
|
193
|
+
// Types
|
|
194
|
+
// ============================================
|
|
195
|
+
|
|
196
|
+
interface SpawnResult {
|
|
197
|
+
subagentId: string;
|
|
198
|
+
status: "spawned";
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
interface ExecuteResult {
|
|
202
|
+
success: boolean;
|
|
203
|
+
error?: string;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// ============================================
|
|
207
|
+
// Helpers
|
|
208
|
+
// ============================================
|
|
209
|
+
|
|
210
|
+
function createSubagentId(): string {
|
|
211
|
+
return `subagent_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// ============================================
|
|
215
|
+
// Actions
|
|
216
|
+
// ============================================
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Spawn a new subagent
|
|
220
|
+
*/
|
|
221
|
+
export const spawn = internalAction({
|
|
222
|
+
args: {
|
|
223
|
+
parentThreadId: v.optional(v.string()),
|
|
224
|
+
cloudThreadId: v.optional(v.string()), // Cloud thread ID for progress emission
|
|
225
|
+
name: v.string(),
|
|
226
|
+
task: v.string(),
|
|
227
|
+
tools: v.array(v.string()),
|
|
228
|
+
model: v.string(),
|
|
229
|
+
},
|
|
230
|
+
handler: async (ctx, args): Promise<SpawnResult> => {
|
|
231
|
+
const subagentId = createSubagentId();
|
|
232
|
+
|
|
233
|
+
// Emit spawning progress to cloud thread
|
|
234
|
+
await emitProgress(
|
|
235
|
+
args.cloudThreadId,
|
|
236
|
+
subagentId,
|
|
237
|
+
args.name,
|
|
238
|
+
args.task,
|
|
239
|
+
"spawning"
|
|
240
|
+
);
|
|
241
|
+
|
|
242
|
+
// Record subagent in database
|
|
243
|
+
await ctx.runMutation(internal.agent.subagents.internalRecordSubagent, {
|
|
244
|
+
threadId: subagentId,
|
|
245
|
+
parentThreadId: args.parentThreadId || "",
|
|
246
|
+
name: args.name,
|
|
247
|
+
task: args.task,
|
|
248
|
+
tools: args.tools,
|
|
249
|
+
model: args.model,
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
// Execute asynchronously using scheduler
|
|
253
|
+
await ctx.scheduler.runAfter(0, internal.agent.subagents.execute, {
|
|
254
|
+
threadId: subagentId,
|
|
255
|
+
cloudThreadId: args.cloudThreadId,
|
|
256
|
+
task: args.task,
|
|
257
|
+
tools: args.tools,
|
|
258
|
+
model: args.model,
|
|
259
|
+
name: args.name,
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
return { subagentId, status: "spawned" };
|
|
263
|
+
},
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Execute subagent task with tool loop
|
|
268
|
+
* @deprecated Legacy tool system removed - use code execution mode instead
|
|
269
|
+
*/
|
|
270
|
+
async function runSubagentLoop(
|
|
271
|
+
_ctx: any,
|
|
272
|
+
_systemPrompt: string,
|
|
273
|
+
_task: string,
|
|
274
|
+
_toolNames: string[],
|
|
275
|
+
_model: string,
|
|
276
|
+
_maxSteps: number = 5
|
|
277
|
+
): Promise<{ text: string; toolCalls: ToolCall[] }> {
|
|
278
|
+
throw new Error(
|
|
279
|
+
"Legacy subagent tool system is deprecated. Use code execution mode with KSAs instead."
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Execute subagent task with tool loop and progress emission
|
|
285
|
+
* @deprecated Legacy tool system removed - use code execution mode instead
|
|
286
|
+
*/
|
|
287
|
+
async function runSubagentLoopWithProgress(
|
|
288
|
+
_ctx: any,
|
|
289
|
+
_systemPrompt: string,
|
|
290
|
+
_task: string,
|
|
291
|
+
_toolNames: string[],
|
|
292
|
+
_model: string,
|
|
293
|
+
_maxSteps: number = 5,
|
|
294
|
+
_onProgress?: (step: ChainOfThoughtStep) => Promise<void>
|
|
295
|
+
): Promise<{ text: string; toolCalls: ToolCall[] }> {
|
|
296
|
+
throw new Error(
|
|
297
|
+
"Legacy subagent tool system is deprecated. Use code execution mode with KSAs instead."
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Map tool names to step types for progress display
|
|
303
|
+
*/
|
|
304
|
+
function getToolType(toolName: string): ChainOfThoughtStep["type"] {
|
|
305
|
+
const lowered = toolName.toLowerCase();
|
|
306
|
+
if (lowered.includes("search") || lowered.includes("web")) return "search";
|
|
307
|
+
if (lowered.includes("file") || lowered.includes("read") || lowered.includes("write")) return "file";
|
|
308
|
+
return "tool";
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Execute subagent task
|
|
313
|
+
*/
|
|
314
|
+
export const execute = internalAction({
|
|
315
|
+
args: {
|
|
316
|
+
threadId: v.string(),
|
|
317
|
+
cloudThreadId: v.optional(v.string()), // Cloud thread ID for progress emission
|
|
318
|
+
task: v.string(),
|
|
319
|
+
tools: v.array(v.string()),
|
|
320
|
+
model: v.string(),
|
|
321
|
+
name: v.string(),
|
|
322
|
+
},
|
|
323
|
+
handler: async (ctx, args): Promise<ExecuteResult> => {
|
|
324
|
+
const children: ChainOfThoughtStep[] = [];
|
|
325
|
+
|
|
326
|
+
try {
|
|
327
|
+
// Update status to running
|
|
328
|
+
await ctx.runMutation(internal.agent.subagents.updateStatus, {
|
|
329
|
+
threadId: args.threadId,
|
|
330
|
+
status: "running",
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
// Emit running progress to cloud thread
|
|
334
|
+
await emitProgress(
|
|
335
|
+
args.cloudThreadId,
|
|
336
|
+
args.threadId,
|
|
337
|
+
args.name,
|
|
338
|
+
args.task,
|
|
339
|
+
"running",
|
|
340
|
+
{ children }
|
|
341
|
+
);
|
|
342
|
+
|
|
343
|
+
// Build system prompt for subagent
|
|
344
|
+
const systemPrompt = `You are ${args.name}, a specialized subagent.
|
|
345
|
+
|
|
346
|
+
Your task: ${args.task}
|
|
347
|
+
|
|
348
|
+
Guidelines:
|
|
349
|
+
- Focus only on the assigned task
|
|
350
|
+
- Be concise and efficient
|
|
351
|
+
- Report results clearly
|
|
352
|
+
- If blocked, explain why`;
|
|
353
|
+
|
|
354
|
+
// Execute using cloud gateway with progress callback
|
|
355
|
+
const onProgress = async (step: ChainOfThoughtStep) => {
|
|
356
|
+
children.push(step);
|
|
357
|
+
await emitProgress(
|
|
358
|
+
args.cloudThreadId,
|
|
359
|
+
args.threadId,
|
|
360
|
+
args.name,
|
|
361
|
+
args.task,
|
|
362
|
+
"running",
|
|
363
|
+
{
|
|
364
|
+
progress: Math.min(95, children.length * 15), // Approximate progress
|
|
365
|
+
children
|
|
366
|
+
}
|
|
367
|
+
);
|
|
368
|
+
};
|
|
369
|
+
|
|
370
|
+
const result = await runSubagentLoopWithProgress(
|
|
371
|
+
ctx,
|
|
372
|
+
systemPrompt,
|
|
373
|
+
args.task,
|
|
374
|
+
args.tools,
|
|
375
|
+
args.model || DEFAULT_SUBAGENT_MODEL,
|
|
376
|
+
5,
|
|
377
|
+
onProgress
|
|
378
|
+
);
|
|
379
|
+
|
|
380
|
+
// Update with result
|
|
381
|
+
await ctx.runMutation(internal.agent.subagents.updateStatus, {
|
|
382
|
+
threadId: args.threadId,
|
|
383
|
+
status: "completed",
|
|
384
|
+
result: {
|
|
385
|
+
text: result.text,
|
|
386
|
+
toolCalls: result.toolCalls,
|
|
387
|
+
},
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
// Mark all children as complete and emit final progress
|
|
391
|
+
const completedChildren = children.map((c) => ({
|
|
392
|
+
...c,
|
|
393
|
+
status: "complete" as const,
|
|
394
|
+
}));
|
|
395
|
+
completedChildren.push({
|
|
396
|
+
id: `${args.threadId}_complete`,
|
|
397
|
+
type: "complete",
|
|
398
|
+
label: "Task completed",
|
|
399
|
+
status: "complete",
|
|
400
|
+
details: result.text.slice(0, 100),
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
await emitProgress(
|
|
404
|
+
args.cloudThreadId,
|
|
405
|
+
args.threadId,
|
|
406
|
+
args.name,
|
|
407
|
+
args.task,
|
|
408
|
+
"completed",
|
|
409
|
+
{ progress: 100, result: result.text, children: completedChildren }
|
|
410
|
+
);
|
|
411
|
+
|
|
412
|
+
return { success: true };
|
|
413
|
+
} catch (error: unknown) {
|
|
414
|
+
const errorMessage =
|
|
415
|
+
error instanceof Error ? error.message : String(error);
|
|
416
|
+
|
|
417
|
+
// Update with error
|
|
418
|
+
await ctx.runMutation(internal.agent.subagents.updateStatus, {
|
|
419
|
+
threadId: args.threadId,
|
|
420
|
+
status: "failed",
|
|
421
|
+
error: errorMessage,
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
// Emit failed progress
|
|
425
|
+
children.push({
|
|
426
|
+
id: `${args.threadId}_error`,
|
|
427
|
+
type: "error",
|
|
428
|
+
label: "Task failed",
|
|
429
|
+
status: "error",
|
|
430
|
+
details: errorMessage,
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
await emitProgress(
|
|
434
|
+
args.cloudThreadId,
|
|
435
|
+
args.threadId,
|
|
436
|
+
args.name,
|
|
437
|
+
args.task,
|
|
438
|
+
"failed",
|
|
439
|
+
{ error: errorMessage, children }
|
|
440
|
+
);
|
|
441
|
+
|
|
442
|
+
return { success: false, error: errorMessage };
|
|
443
|
+
}
|
|
444
|
+
},
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
// ============================================
|
|
448
|
+
// Mutations
|
|
449
|
+
// ============================================
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* Internal: Record subagent in database
|
|
453
|
+
*/
|
|
454
|
+
export const internalRecordSubagent = internalMutation({
|
|
455
|
+
args: {
|
|
456
|
+
threadId: v.string(),
|
|
457
|
+
parentThreadId: v.string(),
|
|
458
|
+
name: v.string(),
|
|
459
|
+
task: v.string(),
|
|
460
|
+
tools: v.array(v.string()),
|
|
461
|
+
model: v.string(),
|
|
462
|
+
},
|
|
463
|
+
handler: async (ctx, args) => {
|
|
464
|
+
return await ctx.db.insert("subagents", {
|
|
465
|
+
threadId: args.threadId,
|
|
466
|
+
parentThreadId: args.parentThreadId,
|
|
467
|
+
name: args.name,
|
|
468
|
+
task: args.task,
|
|
469
|
+
tools: args.tools,
|
|
470
|
+
model: args.model,
|
|
471
|
+
status: "pending",
|
|
472
|
+
createdAt: Date.now(),
|
|
473
|
+
});
|
|
474
|
+
},
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
/**
|
|
478
|
+
* Internal: Update subagent status
|
|
479
|
+
*/
|
|
480
|
+
export const updateStatus = internalMutation({
|
|
481
|
+
args: {
|
|
482
|
+
threadId: v.string(),
|
|
483
|
+
status: v.string(),
|
|
484
|
+
result: v.optional(v.any()),
|
|
485
|
+
error: v.optional(v.string()),
|
|
486
|
+
},
|
|
487
|
+
handler: async (ctx, args) => {
|
|
488
|
+
const subagent = await ctx.db
|
|
489
|
+
.query("subagents")
|
|
490
|
+
.filter((q) => q.eq(q.field("threadId"), args.threadId))
|
|
491
|
+
.first();
|
|
492
|
+
|
|
493
|
+
if (!subagent) return;
|
|
494
|
+
|
|
495
|
+
const updates: Record<string, unknown> = {
|
|
496
|
+
status: args.status,
|
|
497
|
+
};
|
|
498
|
+
|
|
499
|
+
if (args.result) {
|
|
500
|
+
updates.result = args.result;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
if (args.error) {
|
|
504
|
+
updates.error = args.error;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
if (args.status === "completed" || args.status === "failed") {
|
|
508
|
+
updates.completedAt = Date.now();
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
await ctx.db.patch(subagent._id, updates);
|
|
512
|
+
},
|
|
513
|
+
});
|
|
514
|
+
|
|
515
|
+
/**
|
|
516
|
+
* Cancel a subagent
|
|
517
|
+
*/
|
|
518
|
+
export const cancel = internalMutation({
|
|
519
|
+
args: {
|
|
520
|
+
subagentId: v.string(),
|
|
521
|
+
},
|
|
522
|
+
handler: async (ctx, args) => {
|
|
523
|
+
const subagent = await ctx.db
|
|
524
|
+
.query("subagents")
|
|
525
|
+
.filter((q) => q.eq(q.field("threadId"), args.subagentId))
|
|
526
|
+
.first();
|
|
527
|
+
|
|
528
|
+
if (!subagent) {
|
|
529
|
+
return { success: false, error: "Subagent not found" };
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
if (subagent.status === "completed" || subagent.status === "failed") {
|
|
533
|
+
return { success: false, error: "Subagent already finished" };
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
await ctx.db.patch(subagent._id, {
|
|
537
|
+
status: "failed",
|
|
538
|
+
error: "Cancelled by parent",
|
|
539
|
+
completedAt: Date.now(),
|
|
540
|
+
});
|
|
541
|
+
|
|
542
|
+
return { success: true };
|
|
543
|
+
},
|
|
544
|
+
});
|
|
545
|
+
|
|
546
|
+
// ============================================
|
|
547
|
+
// Queries
|
|
548
|
+
// ============================================
|
|
549
|
+
|
|
550
|
+
/**
|
|
551
|
+
* Get subagent status
|
|
552
|
+
*/
|
|
553
|
+
export const getStatus = query({
|
|
554
|
+
args: {
|
|
555
|
+
subagentId: v.string(),
|
|
556
|
+
},
|
|
557
|
+
handler: async (ctx, args) => {
|
|
558
|
+
const subagent = await ctx.db
|
|
559
|
+
.query("subagents")
|
|
560
|
+
.filter((q) => q.eq(q.field("threadId"), args.subagentId))
|
|
561
|
+
.first();
|
|
562
|
+
|
|
563
|
+
if (!subagent) {
|
|
564
|
+
return { found: false, status: null };
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
return {
|
|
568
|
+
found: true,
|
|
569
|
+
status: subagent.status,
|
|
570
|
+
name: subagent.name,
|
|
571
|
+
task: subagent.task,
|
|
572
|
+
hasError: !!subagent.error,
|
|
573
|
+
};
|
|
574
|
+
},
|
|
575
|
+
});
|
|
576
|
+
|
|
577
|
+
/**
|
|
578
|
+
* Get subagent result
|
|
579
|
+
*/
|
|
580
|
+
export const getResult = query({
|
|
581
|
+
args: {
|
|
582
|
+
subagentId: v.string(),
|
|
583
|
+
},
|
|
584
|
+
handler: async (ctx, args) => {
|
|
585
|
+
const subagent = await ctx.db
|
|
586
|
+
.query("subagents")
|
|
587
|
+
.filter((q) => q.eq(q.field("threadId"), args.subagentId))
|
|
588
|
+
.first();
|
|
589
|
+
|
|
590
|
+
if (!subagent) {
|
|
591
|
+
return { found: false };
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
if (subagent.status !== "completed" && subagent.status !== "failed") {
|
|
595
|
+
return {
|
|
596
|
+
found: true,
|
|
597
|
+
ready: false,
|
|
598
|
+
status: subagent.status,
|
|
599
|
+
};
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
return {
|
|
603
|
+
found: true,
|
|
604
|
+
ready: true,
|
|
605
|
+
status: subagent.status,
|
|
606
|
+
result: subagent.result,
|
|
607
|
+
error: subagent.error,
|
|
608
|
+
};
|
|
609
|
+
},
|
|
610
|
+
});
|
|
611
|
+
|
|
612
|
+
/**
|
|
613
|
+
* List subagents
|
|
614
|
+
*/
|
|
615
|
+
export const list = query({
|
|
616
|
+
args: {
|
|
617
|
+
status: v.optional(
|
|
618
|
+
v.union(
|
|
619
|
+
v.literal("pending"),
|
|
620
|
+
v.literal("running"),
|
|
621
|
+
v.literal("completed"),
|
|
622
|
+
v.literal("failed")
|
|
623
|
+
)
|
|
624
|
+
),
|
|
625
|
+
parentThreadId: v.optional(v.string()),
|
|
626
|
+
},
|
|
627
|
+
handler: async (ctx, args) => {
|
|
628
|
+
let q = ctx.db.query("subagents");
|
|
629
|
+
|
|
630
|
+
if (args.status) {
|
|
631
|
+
q = q.filter((q) => q.eq(q.field("status"), args.status));
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
if (args.parentThreadId) {
|
|
635
|
+
q = q.filter((q) =>
|
|
636
|
+
q.eq(q.field("parentThreadId"), args.parentThreadId)
|
|
637
|
+
);
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
const subagents = await q.order("desc").take(50);
|
|
641
|
+
|
|
642
|
+
return subagents.map((s) => ({
|
|
643
|
+
id: s.threadId,
|
|
644
|
+
name: s.name,
|
|
645
|
+
task: s.task,
|
|
646
|
+
status: s.status,
|
|
647
|
+
createdAt: s.createdAt,
|
|
648
|
+
completedAt: s.completedAt,
|
|
649
|
+
}));
|
|
650
|
+
},
|
|
651
|
+
});
|