@lakitu/sdk 0.1.42 → 0.1.44
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/convex/sandbox/agent/subagents.ts +139 -33
- package/package.json +1 -1
- package/template/e2b/start.sh +42 -37
|
@@ -266,38 +266,145 @@ export const spawn = internalAction({
|
|
|
266
266
|
});
|
|
267
267
|
|
|
268
268
|
/**
|
|
269
|
-
*
|
|
270
|
-
*
|
|
269
|
+
* Run subagent code execution loop
|
|
270
|
+
* Uses the same pattern as the main agent - LLM generates code, we execute it.
|
|
271
271
|
*/
|
|
272
|
-
async function
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
): Promise<{ text: string;
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
272
|
+
async function runSubagentCodeExecLoop(
|
|
273
|
+
ctx: any,
|
|
274
|
+
systemPrompt: string,
|
|
275
|
+
task: string,
|
|
276
|
+
model: string,
|
|
277
|
+
maxSteps: number = 5,
|
|
278
|
+
onProgress?: (step: ChainOfThoughtStep) => Promise<void>
|
|
279
|
+
): Promise<{ text: string; codeExecutions: Array<{ code: string; output: string; success: boolean }> }> {
|
|
280
|
+
const messages: Array<{ role: "system" | "user" | "assistant"; content: string }> = [
|
|
281
|
+
{ role: "system", content: systemPrompt },
|
|
282
|
+
{ role: "user", content: `Task: ${task}\n\nYou have access to KSAs (Knowledge, Skills, Abilities) via imports from './ksa/'. Write TypeScript code to accomplish this task.\n\nRespond with JSON containing:\n- "thinking": Your reasoning\n- "code": TypeScript code to execute (or empty if done)\n- "response": Final result when task is complete` },
|
|
283
|
+
];
|
|
284
|
+
|
|
285
|
+
const allExecutions: Array<{ code: string; output: string; success: boolean }> = [];
|
|
286
|
+
let finalText = "";
|
|
287
|
+
|
|
288
|
+
for (let step = 0; step < maxSteps; step++) {
|
|
289
|
+
// Call LLM via local sandbox
|
|
290
|
+
let llmResponse: string;
|
|
291
|
+
try {
|
|
292
|
+
const res = await fetch("http://localhost:3210/api/action", {
|
|
293
|
+
method: "POST",
|
|
294
|
+
headers: { "Content-Type": "application/json" },
|
|
295
|
+
body: JSON.stringify({
|
|
296
|
+
path: "agent/index:callLLM",
|
|
297
|
+
args: { messages, model },
|
|
298
|
+
format: "json",
|
|
299
|
+
}),
|
|
300
|
+
});
|
|
301
|
+
if (!res.ok) {
|
|
302
|
+
throw new Error(`LLM call failed: ${await res.text()}`);
|
|
303
|
+
}
|
|
304
|
+
const result = await res.json();
|
|
305
|
+
llmResponse = result.value?.text || result.value || "";
|
|
306
|
+
} catch (e: any) {
|
|
307
|
+
console.error("[subagent] LLM call failed:", e.message);
|
|
308
|
+
finalText = `Error: ${e.message}`;
|
|
309
|
+
break;
|
|
310
|
+
}
|
|
284
311
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
312
|
+
// Parse JSON response
|
|
313
|
+
let action: { thinking?: string; code?: string; response?: string } = {};
|
|
314
|
+
try {
|
|
315
|
+
const jsonMatch = llmResponse.match(/\{[\s\S]*\}/);
|
|
316
|
+
if (jsonMatch) {
|
|
317
|
+
action = JSON.parse(jsonMatch[0]);
|
|
318
|
+
}
|
|
319
|
+
} catch {
|
|
320
|
+
// Try to extract code block directly
|
|
321
|
+
const codeMatch = llmResponse.match(/```(?:typescript|ts)?\s*([\s\S]*?)```/);
|
|
322
|
+
if (codeMatch) {
|
|
323
|
+
action = { code: codeMatch[1].trim() };
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// Emit thinking progress
|
|
328
|
+
if (action.thinking && onProgress) {
|
|
329
|
+
await onProgress({
|
|
330
|
+
id: `step_${step}_thinking`,
|
|
331
|
+
type: "thinking",
|
|
332
|
+
label: action.thinking.slice(0, 150),
|
|
333
|
+
status: "complete",
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// Check if done
|
|
338
|
+
if (action.response && !action.code) {
|
|
339
|
+
finalText = action.response;
|
|
340
|
+
break;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// Execute code
|
|
344
|
+
if (action.code && action.code.trim()) {
|
|
345
|
+
if (onProgress) {
|
|
346
|
+
await onProgress({
|
|
347
|
+
id: `step_${step}_code`,
|
|
348
|
+
type: "tool",
|
|
349
|
+
label: "Executing code...",
|
|
350
|
+
status: "active",
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
try {
|
|
355
|
+
const execResult = await ctx.runAction(internal.nodeActions.codeExec.execute, {
|
|
356
|
+
code: action.code,
|
|
357
|
+
timeoutMs: 60_000,
|
|
358
|
+
env: {
|
|
359
|
+
LOCAL_CONVEX_URL: "http://localhost:3210",
|
|
360
|
+
},
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
allExecutions.push({
|
|
364
|
+
code: action.code,
|
|
365
|
+
output: execResult.output,
|
|
366
|
+
success: execResult.success,
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
// Add to conversation
|
|
370
|
+
messages.push({
|
|
371
|
+
role: "assistant",
|
|
372
|
+
content: `Thinking: ${action.thinking || "..."}\n\n\`\`\`typescript\n${action.code}\n\`\`\``,
|
|
373
|
+
});
|
|
374
|
+
messages.push({
|
|
375
|
+
role: "user",
|
|
376
|
+
content: `Execution ${execResult.success ? "succeeded" : "failed"}:\n${execResult.output}\n\nContinue with JSON response.`,
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
if (onProgress) {
|
|
380
|
+
await onProgress({
|
|
381
|
+
id: `step_${step}_result`,
|
|
382
|
+
type: execResult.success ? "complete" : "error",
|
|
383
|
+
label: execResult.output.slice(0, 150),
|
|
384
|
+
status: execResult.success ? "complete" : "error",
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
} catch (e: any) {
|
|
388
|
+
allExecutions.push({
|
|
389
|
+
code: action.code,
|
|
390
|
+
output: e.message,
|
|
391
|
+
success: false,
|
|
392
|
+
});
|
|
393
|
+
messages.push({
|
|
394
|
+
role: "user",
|
|
395
|
+
content: `Execution error: ${e.message}\n\nTry a different approach.`,
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
} else if (!action.response) {
|
|
399
|
+
// No code and no response - try to continue
|
|
400
|
+
messages.push({
|
|
401
|
+
role: "user",
|
|
402
|
+
content: "Please provide either code to execute or a final response.",
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
return { text: finalText || "Subagent completed.", codeExecutions: allExecutions };
|
|
301
408
|
}
|
|
302
409
|
|
|
303
410
|
/**
|
|
@@ -369,11 +476,10 @@ Guidelines:
|
|
|
369
476
|
);
|
|
370
477
|
};
|
|
371
478
|
|
|
372
|
-
const result = await
|
|
479
|
+
const result = await runSubagentCodeExecLoop(
|
|
373
480
|
ctx,
|
|
374
481
|
systemPrompt,
|
|
375
482
|
args.task,
|
|
376
|
-
args.tools,
|
|
377
483
|
args.model || DEFAULT_SUBAGENT_MODEL,
|
|
378
484
|
5,
|
|
379
485
|
onProgress
|
|
@@ -385,7 +491,7 @@ Guidelines:
|
|
|
385
491
|
status: "completed",
|
|
386
492
|
result: {
|
|
387
493
|
text: result.text,
|
|
388
|
-
|
|
494
|
+
codeExecutions: result.codeExecutions,
|
|
389
495
|
},
|
|
390
496
|
});
|
|
391
497
|
|
package/package.json
CHANGED
package/template/e2b/start.sh
CHANGED
|
@@ -1,55 +1,60 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
|
-
# Start convex-backend
|
|
2
|
+
# Start convex-backend with pre-deployed functions
|
|
3
3
|
|
|
4
4
|
STORAGE_DIR=/home/user/.convex/convex-backend-state/lakitu
|
|
5
5
|
SQLITE_DB=$STORAGE_DIR/convex_local_backend.sqlite3
|
|
6
6
|
MODULES_DIR=$STORAGE_DIR/modules
|
|
7
7
|
|
|
8
|
-
#
|
|
9
|
-
convex-backend \
|
|
10
|
-
"$SQLITE_DB" \
|
|
11
|
-
--port 3210 \
|
|
12
|
-
--site-proxy-port 3211 \
|
|
13
|
-
--local-storage "$STORAGE_DIR" \
|
|
14
|
-
--disable-beacon &
|
|
15
|
-
|
|
16
|
-
BACKEND_PID=$!
|
|
17
|
-
|
|
18
|
-
# Wait for backend to be ready
|
|
19
|
-
for i in {1..30}; do
|
|
20
|
-
if curl -s http://127.0.0.1:3210/version > /dev/null 2>&1; then
|
|
21
|
-
echo "Backend ready"
|
|
22
|
-
break
|
|
23
|
-
fi
|
|
24
|
-
sleep 1
|
|
25
|
-
done
|
|
26
|
-
|
|
27
|
-
# Deploy functions if not already deployed
|
|
8
|
+
# Deploy functions FIRST if not already deployed
|
|
28
9
|
if [ ! -d "$MODULES_DIR" ] || [ -z "$(ls -A $MODULES_DIR 2>/dev/null)" ]; then
|
|
29
|
-
echo "
|
|
10
|
+
echo "Pre-deploying Convex functions..."
|
|
11
|
+
|
|
12
|
+
# Start temp backend on different port for deployment
|
|
13
|
+
convex-backend \
|
|
14
|
+
"$SQLITE_DB" \
|
|
15
|
+
--port 3209 \
|
|
16
|
+
--site-proxy-port 3208 \
|
|
17
|
+
--local-storage "$STORAGE_DIR" \
|
|
18
|
+
--disable-beacon &
|
|
19
|
+
TEMP_PID=$!
|
|
20
|
+
|
|
21
|
+
# Wait for temp backend
|
|
22
|
+
for i in {1..30}; do
|
|
23
|
+
if curl -s http://127.0.0.1:3209/version > /dev/null 2>&1; then
|
|
24
|
+
break
|
|
25
|
+
fi
|
|
26
|
+
sleep 1
|
|
27
|
+
done
|
|
28
|
+
|
|
29
|
+
# Deploy functions
|
|
30
30
|
cd /home/user/lakitu
|
|
31
|
-
export CONVEX_SELF_HOSTED_URL=http://127.0.0.1:
|
|
31
|
+
export CONVEX_SELF_HOSTED_URL=http://127.0.0.1:3209
|
|
32
32
|
export CONVEX_SELF_HOSTED_ADMIN_KEY=0135d8598650f8f5cb0f30c34ec2e2bb62793bc28717c8eb6fb577996d50be5f4281b59181095065c5d0f86a2c31ddbe9b597ec62b47ded69782cd
|
|
33
33
|
|
|
34
|
-
# Run deployment and wait for completion
|
|
35
34
|
if npx convex dev --once --typecheck disable > /tmp/convex-deploy.log 2>&1; then
|
|
36
35
|
echo "Functions deployed successfully"
|
|
37
36
|
else
|
|
38
|
-
echo "
|
|
37
|
+
echo "Deploy failed:"
|
|
39
38
|
cat /tmp/convex-deploy.log
|
|
40
39
|
fi
|
|
41
40
|
|
|
42
|
-
#
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
41
|
+
# Stop temp backend
|
|
42
|
+
kill $TEMP_PID 2>/dev/null
|
|
43
|
+
sleep 1
|
|
44
|
+
|
|
45
|
+
# Verify deployment
|
|
46
|
+
if [ -d "$MODULES_DIR" ] && [ -n "$(ls -A $MODULES_DIR 2>/dev/null)" ]; then
|
|
47
|
+
MODULE_COUNT=$(ls -1 $MODULES_DIR | wc -l)
|
|
48
|
+
echo "Deployed $MODULE_COUNT modules"
|
|
49
|
+
else
|
|
50
|
+
echo "WARNING: No modules deployed"
|
|
51
|
+
fi
|
|
52
52
|
fi
|
|
53
53
|
|
|
54
|
-
#
|
|
55
|
-
|
|
54
|
+
# Now start the main backend on correct port
|
|
55
|
+
exec convex-backend \
|
|
56
|
+
"$SQLITE_DB" \
|
|
57
|
+
--port 3210 \
|
|
58
|
+
--site-proxy-port 3211 \
|
|
59
|
+
--local-storage "$STORAGE_DIR" \
|
|
60
|
+
--disable-beacon
|