@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,402 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Context - Context Window Orchestration
|
|
3
|
+
*
|
|
4
|
+
* OpenCode-inspired context management:
|
|
5
|
+
* - Session persistence across commands
|
|
6
|
+
* - Dependency graph for surgical context injection
|
|
7
|
+
* - Lazy tool loading (only inject tools needed)
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { mutation, query } from "../_generated/server";
|
|
11
|
+
import { v } from "convex/values";
|
|
12
|
+
|
|
13
|
+
// ============================================
|
|
14
|
+
// Mutations
|
|
15
|
+
// ============================================
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Store session memory entry
|
|
19
|
+
*/
|
|
20
|
+
export const setMemory = mutation({
|
|
21
|
+
args: {
|
|
22
|
+
sessionId: v.string(),
|
|
23
|
+
key: v.string(),
|
|
24
|
+
value: v.any(),
|
|
25
|
+
ttlMs: v.optional(v.number()), // Time to live
|
|
26
|
+
},
|
|
27
|
+
handler: async (ctx, args) => {
|
|
28
|
+
// Check for existing entry
|
|
29
|
+
const existing = await ctx.db
|
|
30
|
+
.query("sessionMemory")
|
|
31
|
+
.withIndex("by_session_key", (q) =>
|
|
32
|
+
q.eq("sessionId", args.sessionId).eq("key", args.key)
|
|
33
|
+
)
|
|
34
|
+
.first();
|
|
35
|
+
|
|
36
|
+
const expiresAt = args.ttlMs ? Date.now() + args.ttlMs : undefined;
|
|
37
|
+
|
|
38
|
+
if (existing) {
|
|
39
|
+
await ctx.db.patch(existing._id, {
|
|
40
|
+
value: args.value,
|
|
41
|
+
updatedAt: Date.now(),
|
|
42
|
+
expiresAt,
|
|
43
|
+
});
|
|
44
|
+
return existing._id;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return await ctx.db.insert("sessionMemory", {
|
|
48
|
+
sessionId: args.sessionId,
|
|
49
|
+
key: args.key,
|
|
50
|
+
value: args.value,
|
|
51
|
+
createdAt: Date.now(),
|
|
52
|
+
updatedAt: Date.now(),
|
|
53
|
+
expiresAt,
|
|
54
|
+
});
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Delete session memory entry
|
|
60
|
+
*/
|
|
61
|
+
export const deleteMemory = mutation({
|
|
62
|
+
args: {
|
|
63
|
+
sessionId: v.string(),
|
|
64
|
+
key: v.string(),
|
|
65
|
+
},
|
|
66
|
+
handler: async (ctx, args) => {
|
|
67
|
+
const existing = await ctx.db
|
|
68
|
+
.query("sessionMemory")
|
|
69
|
+
.withIndex("by_session_key", (q) =>
|
|
70
|
+
q.eq("sessionId", args.sessionId).eq("key", args.key)
|
|
71
|
+
)
|
|
72
|
+
.first();
|
|
73
|
+
|
|
74
|
+
if (existing) {
|
|
75
|
+
await ctx.db.delete(existing._id);
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
return false;
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Cache context for reuse
|
|
84
|
+
*/
|
|
85
|
+
export const cacheContext = mutation({
|
|
86
|
+
args: {
|
|
87
|
+
sessionId: v.string(),
|
|
88
|
+
taskHash: v.string(),
|
|
89
|
+
context: v.object({
|
|
90
|
+
relevantFiles: v.array(
|
|
91
|
+
v.object({
|
|
92
|
+
path: v.string(),
|
|
93
|
+
snippet: v.optional(v.string()),
|
|
94
|
+
importance: v.number(),
|
|
95
|
+
})
|
|
96
|
+
),
|
|
97
|
+
toolsNeeded: v.array(v.string()),
|
|
98
|
+
tokenBudget: v.number(),
|
|
99
|
+
}),
|
|
100
|
+
ttlMs: v.optional(v.number()),
|
|
101
|
+
},
|
|
102
|
+
handler: async (ctx, args) => {
|
|
103
|
+
// Check for existing cache
|
|
104
|
+
const existing = await ctx.db
|
|
105
|
+
.query("contextCache")
|
|
106
|
+
.withIndex("by_session_task", (q) =>
|
|
107
|
+
q.eq("sessionId", args.sessionId).eq("taskHash", args.taskHash)
|
|
108
|
+
)
|
|
109
|
+
.first();
|
|
110
|
+
|
|
111
|
+
const expiresAt = args.ttlMs
|
|
112
|
+
? Date.now() + args.ttlMs
|
|
113
|
+
: Date.now() + 3600000; // 1 hour default
|
|
114
|
+
|
|
115
|
+
if (existing) {
|
|
116
|
+
await ctx.db.patch(existing._id, {
|
|
117
|
+
context: args.context,
|
|
118
|
+
updatedAt: Date.now(),
|
|
119
|
+
expiresAt,
|
|
120
|
+
hitCount: existing.hitCount + 1,
|
|
121
|
+
});
|
|
122
|
+
return existing._id;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return await ctx.db.insert("contextCache", {
|
|
126
|
+
sessionId: args.sessionId,
|
|
127
|
+
taskHash: args.taskHash,
|
|
128
|
+
context: args.context,
|
|
129
|
+
createdAt: Date.now(),
|
|
130
|
+
updatedAt: Date.now(),
|
|
131
|
+
expiresAt,
|
|
132
|
+
hitCount: 0,
|
|
133
|
+
});
|
|
134
|
+
},
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Track file dependency
|
|
139
|
+
*/
|
|
140
|
+
export const trackDependency = mutation({
|
|
141
|
+
args: {
|
|
142
|
+
sessionId: v.string(),
|
|
143
|
+
fromPath: v.string(),
|
|
144
|
+
toPath: v.string(),
|
|
145
|
+
type: v.union(
|
|
146
|
+
v.literal("import"),
|
|
147
|
+
v.literal("reference"),
|
|
148
|
+
v.literal("test"),
|
|
149
|
+
v.literal("config")
|
|
150
|
+
),
|
|
151
|
+
},
|
|
152
|
+
handler: async (ctx, args) => {
|
|
153
|
+
// Check for existing dependency
|
|
154
|
+
const existing = await ctx.db
|
|
155
|
+
.query("dependencyGraph")
|
|
156
|
+
.filter((q) =>
|
|
157
|
+
q.and(
|
|
158
|
+
q.eq(q.field("sessionId"), args.sessionId),
|
|
159
|
+
q.eq(q.field("fromPath"), args.fromPath),
|
|
160
|
+
q.eq(q.field("toPath"), args.toPath)
|
|
161
|
+
)
|
|
162
|
+
)
|
|
163
|
+
.first();
|
|
164
|
+
|
|
165
|
+
if (existing) {
|
|
166
|
+
await ctx.db.patch(existing._id, {
|
|
167
|
+
type: args.type,
|
|
168
|
+
lastSeen: Date.now(),
|
|
169
|
+
});
|
|
170
|
+
return existing._id;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return await ctx.db.insert("dependencyGraph", {
|
|
174
|
+
sessionId: args.sessionId,
|
|
175
|
+
fromPath: args.fromPath,
|
|
176
|
+
toPath: args.toPath,
|
|
177
|
+
type: args.type,
|
|
178
|
+
createdAt: Date.now(),
|
|
179
|
+
lastSeen: Date.now(),
|
|
180
|
+
});
|
|
181
|
+
},
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Clear expired cache entries
|
|
186
|
+
*/
|
|
187
|
+
export const clearExpired = mutation({
|
|
188
|
+
args: { sessionId: v.optional(v.string()) },
|
|
189
|
+
handler: async (ctx, args) => {
|
|
190
|
+
const now = Date.now();
|
|
191
|
+
let deleted = 0;
|
|
192
|
+
const { sessionId } = args;
|
|
193
|
+
|
|
194
|
+
// Clear expired session memory
|
|
195
|
+
const memory = sessionId
|
|
196
|
+
? await ctx.db
|
|
197
|
+
.query("sessionMemory")
|
|
198
|
+
.withIndex("by_session", (q) => q.eq("sessionId", sessionId))
|
|
199
|
+
.collect()
|
|
200
|
+
: await ctx.db.query("sessionMemory").collect();
|
|
201
|
+
|
|
202
|
+
for (const entry of memory) {
|
|
203
|
+
if (entry.expiresAt && entry.expiresAt < now) {
|
|
204
|
+
await ctx.db.delete(entry._id);
|
|
205
|
+
deleted++;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Clear expired context cache
|
|
210
|
+
const cache = sessionId
|
|
211
|
+
? await ctx.db
|
|
212
|
+
.query("contextCache")
|
|
213
|
+
.withIndex("by_session", (q) => q.eq("sessionId", sessionId))
|
|
214
|
+
.collect()
|
|
215
|
+
: await ctx.db.query("contextCache").collect();
|
|
216
|
+
|
|
217
|
+
for (const entry of cache) {
|
|
218
|
+
if (entry.expiresAt && entry.expiresAt < now) {
|
|
219
|
+
await ctx.db.delete(entry._id);
|
|
220
|
+
deleted++;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return { deleted };
|
|
225
|
+
},
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
// ============================================
|
|
229
|
+
// Queries
|
|
230
|
+
// ============================================
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Get session memory value
|
|
234
|
+
*/
|
|
235
|
+
export const getMemory = query({
|
|
236
|
+
args: {
|
|
237
|
+
sessionId: v.string(),
|
|
238
|
+
key: v.string(),
|
|
239
|
+
},
|
|
240
|
+
handler: async (ctx, args) => {
|
|
241
|
+
const entry = await ctx.db
|
|
242
|
+
.query("sessionMemory")
|
|
243
|
+
.withIndex("by_session_key", (q) =>
|
|
244
|
+
q.eq("sessionId", args.sessionId).eq("key", args.key)
|
|
245
|
+
)
|
|
246
|
+
.first();
|
|
247
|
+
|
|
248
|
+
if (!entry) return null;
|
|
249
|
+
|
|
250
|
+
// Check expiration
|
|
251
|
+
if (entry.expiresAt && entry.expiresAt < Date.now()) {
|
|
252
|
+
return null;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
return entry.value;
|
|
256
|
+
},
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Get all session memory
|
|
261
|
+
*/
|
|
262
|
+
export const getAllMemory = query({
|
|
263
|
+
args: { sessionId: v.string() },
|
|
264
|
+
handler: async (ctx, args) => {
|
|
265
|
+
const entries = await ctx.db
|
|
266
|
+
.query("sessionMemory")
|
|
267
|
+
.withIndex("by_session", (q) => q.eq("sessionId", args.sessionId))
|
|
268
|
+
.collect();
|
|
269
|
+
|
|
270
|
+
const now = Date.now();
|
|
271
|
+
const result: Record<string, any> = {};
|
|
272
|
+
|
|
273
|
+
for (const entry of entries) {
|
|
274
|
+
if (!entry.expiresAt || entry.expiresAt >= now) {
|
|
275
|
+
result[entry.key] = entry.value;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
return result;
|
|
280
|
+
},
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Get cached context
|
|
285
|
+
*/
|
|
286
|
+
export const getCachedContext = query({
|
|
287
|
+
args: {
|
|
288
|
+
sessionId: v.string(),
|
|
289
|
+
taskHash: v.string(),
|
|
290
|
+
},
|
|
291
|
+
handler: async (ctx, args) => {
|
|
292
|
+
const cached = await ctx.db
|
|
293
|
+
.query("contextCache")
|
|
294
|
+
.withIndex("by_session_task", (q) =>
|
|
295
|
+
q.eq("sessionId", args.sessionId).eq("taskHash", args.taskHash)
|
|
296
|
+
)
|
|
297
|
+
.first();
|
|
298
|
+
|
|
299
|
+
if (!cached) return null;
|
|
300
|
+
|
|
301
|
+
// Check expiration
|
|
302
|
+
if (cached.expiresAt && cached.expiresAt < Date.now()) {
|
|
303
|
+
return null;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
return cached.context;
|
|
307
|
+
},
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Get dependencies for a file
|
|
312
|
+
*/
|
|
313
|
+
export const getDependencies = query({
|
|
314
|
+
args: {
|
|
315
|
+
sessionId: v.string(),
|
|
316
|
+
path: v.string(),
|
|
317
|
+
},
|
|
318
|
+
handler: async (ctx, args) => {
|
|
319
|
+
// Files this file depends on
|
|
320
|
+
const dependsOn = await ctx.db
|
|
321
|
+
.query("dependencyGraph")
|
|
322
|
+
.filter((q) =>
|
|
323
|
+
q.and(
|
|
324
|
+
q.eq(q.field("sessionId"), args.sessionId),
|
|
325
|
+
q.eq(q.field("fromPath"), args.path)
|
|
326
|
+
)
|
|
327
|
+
)
|
|
328
|
+
.collect();
|
|
329
|
+
|
|
330
|
+
// Files that depend on this file
|
|
331
|
+
const dependedBy = await ctx.db
|
|
332
|
+
.query("dependencyGraph")
|
|
333
|
+
.filter((q) =>
|
|
334
|
+
q.and(
|
|
335
|
+
q.eq(q.field("sessionId"), args.sessionId),
|
|
336
|
+
q.eq(q.field("toPath"), args.path)
|
|
337
|
+
)
|
|
338
|
+
)
|
|
339
|
+
.collect();
|
|
340
|
+
|
|
341
|
+
return {
|
|
342
|
+
dependsOn: dependsOn.map((d) => ({ path: d.toPath, type: d.type })),
|
|
343
|
+
dependedBy: dependedBy.map((d) => ({ path: d.fromPath, type: d.type })),
|
|
344
|
+
};
|
|
345
|
+
},
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Query dependency graph for relevant files
|
|
350
|
+
*/
|
|
351
|
+
export const queryRelevantFiles = query({
|
|
352
|
+
args: {
|
|
353
|
+
sessionId: v.string(),
|
|
354
|
+
paths: v.array(v.string()),
|
|
355
|
+
depth: v.optional(v.number()),
|
|
356
|
+
},
|
|
357
|
+
handler: async (ctx, args) => {
|
|
358
|
+
const maxDepth = args.depth ?? 2;
|
|
359
|
+
const visited = new Set<string>(args.paths);
|
|
360
|
+
const result: Array<{ path: string; depth: number; type: string }> = [];
|
|
361
|
+
|
|
362
|
+
// BFS to find related files
|
|
363
|
+
let currentLevel = args.paths;
|
|
364
|
+
for (let depth = 0; depth < maxDepth && currentLevel.length > 0; depth++) {
|
|
365
|
+
const nextLevel: string[] = [];
|
|
366
|
+
|
|
367
|
+
for (const path of currentLevel) {
|
|
368
|
+
// Get all dependencies
|
|
369
|
+
const deps = await ctx.db
|
|
370
|
+
.query("dependencyGraph")
|
|
371
|
+
.filter((q) =>
|
|
372
|
+
q.and(
|
|
373
|
+
q.eq(q.field("sessionId"), args.sessionId),
|
|
374
|
+
q.or(
|
|
375
|
+
q.eq(q.field("fromPath"), path),
|
|
376
|
+
q.eq(q.field("toPath"), path)
|
|
377
|
+
)
|
|
378
|
+
)
|
|
379
|
+
)
|
|
380
|
+
.collect();
|
|
381
|
+
|
|
382
|
+
for (const dep of deps) {
|
|
383
|
+
const relatedPath =
|
|
384
|
+
dep.fromPath === path ? dep.toPath : dep.fromPath;
|
|
385
|
+
if (!visited.has(relatedPath)) {
|
|
386
|
+
visited.add(relatedPath);
|
|
387
|
+
nextLevel.push(relatedPath);
|
|
388
|
+
result.push({
|
|
389
|
+
path: relatedPath,
|
|
390
|
+
depth: depth + 1,
|
|
391
|
+
type: dep.type,
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
currentLevel = nextLevel;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
return result;
|
|
401
|
+
},
|
|
402
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Convex Config for Sandbox Agent
|
|
3
|
+
*
|
|
4
|
+
* This is the configuration for the self-hosted Convex backend
|
|
5
|
+
* that runs inside the E2B sandbox. It uses the Convex Agent SDK
|
|
6
|
+
* for native LLM orchestration and streaming.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { defineApp } from "convex/server";
|
|
10
|
+
import agent from "@convex-dev/agent/convex.config";
|
|
11
|
+
|
|
12
|
+
const app = defineApp();
|
|
13
|
+
|
|
14
|
+
// Convex Agent SDK component for LLM orchestration
|
|
15
|
+
app.use(agent);
|
|
16
|
+
|
|
17
|
+
export default app;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sandbox Agent - Main Entry Point
|
|
3
|
+
*
|
|
4
|
+
* Re-exports all public APIs for the sandbox agent.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// ============================================
|
|
8
|
+
// Agent
|
|
9
|
+
// ============================================
|
|
10
|
+
export {
|
|
11
|
+
startThread,
|
|
12
|
+
continueThread,
|
|
13
|
+
runWithTimeout,
|
|
14
|
+
getThreadMessages,
|
|
15
|
+
getStreamDeltas,
|
|
16
|
+
} from "./agent";
|
|
17
|
+
export * as decisions from "./agent/decisions";
|
|
18
|
+
|
|
19
|
+
// ============================================
|
|
20
|
+
// State Management
|
|
21
|
+
// ============================================
|
|
22
|
+
export * as state from "./state";
|
|
23
|
+
export * as files from "./state/files";
|
|
24
|
+
export * as checkpoints from "./state/checkpoints";
|
|
25
|
+
export * as verification from "./state/verification";
|
|
26
|
+
export * as artifacts from "./state/artifacts";
|
|
27
|
+
|
|
28
|
+
// ============================================
|
|
29
|
+
// Planning
|
|
30
|
+
// ============================================
|
|
31
|
+
export * as planning from "./planning";
|
|
32
|
+
export * as beads from "./planning/beads";
|
|
33
|
+
export * as sync from "./planning/sync";
|
|
34
|
+
|
|
35
|
+
// ============================================
|
|
36
|
+
// Context
|
|
37
|
+
// ============================================
|
|
38
|
+
export * as context from "./context";
|
|
39
|
+
export * as session from "./context/session";
|
|
40
|
+
|
|
41
|
+
// ============================================
|
|
42
|
+
// Tools (DEPRECATED - Use KSAs instead)
|
|
43
|
+
// ============================================
|
|
44
|
+
// Legacy tool system has been removed. Use the KSA (Knowledge, Skills, Abilities)
|
|
45
|
+
// architecture with code execution mode instead. See packages/lakitu/ksa/
|
|
46
|
+
|
|
47
|
+
// ============================================
|
|
48
|
+
// Prompts
|
|
49
|
+
// ============================================
|
|
50
|
+
export * as prompts from "./prompts/system";
|
|
51
|
+
export * as modes from "./prompts/modes";
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
"use node";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Code Execution Action
|
|
5
|
+
*
|
|
6
|
+
* Executes TypeScript code generated by the LLM.
|
|
7
|
+
* This is the core of the code execution model - instead of JSON tool calls,
|
|
8
|
+
* the agent writes code that imports from ksa/ and we execute it.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { internalAction } from "../_generated/server";
|
|
12
|
+
import { v } from "convex/values";
|
|
13
|
+
|
|
14
|
+
// Execution limits
|
|
15
|
+
const MAX_TIMEOUT_MS = 120_000; // 2 minutes
|
|
16
|
+
const MAX_OUTPUT_LENGTH = 50_000; // 50KB
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Execute TypeScript code in the sandbox.
|
|
20
|
+
*
|
|
21
|
+
* The code can import from /home/user/ksa/ to use available capabilities.
|
|
22
|
+
* Output is captured from stdout/stderr.
|
|
23
|
+
*/
|
|
24
|
+
export const execute = internalAction({
|
|
25
|
+
args: {
|
|
26
|
+
code: v.string(),
|
|
27
|
+
timeoutMs: v.optional(v.number()),
|
|
28
|
+
env: v.optional(v.object({
|
|
29
|
+
CONVEX_URL: v.optional(v.string()),
|
|
30
|
+
GATEWAY_URL: v.optional(v.string()),
|
|
31
|
+
SANDBOX_JWT: v.optional(v.string()),
|
|
32
|
+
CARD_ID: v.optional(v.string()),
|
|
33
|
+
THREAD_ID: v.optional(v.string()),
|
|
34
|
+
})),
|
|
35
|
+
},
|
|
36
|
+
handler: async (_ctx, args): Promise<{
|
|
37
|
+
success: boolean;
|
|
38
|
+
output: string;
|
|
39
|
+
error?: string;
|
|
40
|
+
exitCode: number;
|
|
41
|
+
}> => {
|
|
42
|
+
// Dynamic imports for Node.js modules (required for Convex bundling)
|
|
43
|
+
const { exec } = await import("child_process");
|
|
44
|
+
const { promisify } = await import("util");
|
|
45
|
+
const fs = await import("fs/promises");
|
|
46
|
+
const crypto = await import("crypto");
|
|
47
|
+
|
|
48
|
+
const execAsync = promisify(exec);
|
|
49
|
+
const timeout = Math.min(args.timeoutMs || 60_000, MAX_TIMEOUT_MS);
|
|
50
|
+
|
|
51
|
+
// Generate unique filename for this execution
|
|
52
|
+
// IMPORTANT: Write to /home/user/ so relative imports like './ksa/beads' resolve correctly
|
|
53
|
+
const hash = crypto.createHash("md5").update(args.code).digest("hex").slice(0, 8);
|
|
54
|
+
const filename = `/home/user/agent_exec_${Date.now()}_${hash}.ts`;
|
|
55
|
+
|
|
56
|
+
try {
|
|
57
|
+
// Write code to temp file
|
|
58
|
+
await fs.writeFile(filename, args.code, "utf-8");
|
|
59
|
+
|
|
60
|
+
// Execute with bun (use full path since PATH may not be set in Convex process)
|
|
61
|
+
const bunPath = "/home/user/.bun/bin/bun";
|
|
62
|
+
const { stdout, stderr } = await execAsync(`${bunPath} run ${filename}`, {
|
|
63
|
+
timeout,
|
|
64
|
+
cwd: "/home/user/workspace",
|
|
65
|
+
env: {
|
|
66
|
+
...process.env,
|
|
67
|
+
// Ensure PATH includes bun
|
|
68
|
+
PATH: "/home/user/.bun/bin:/usr/local/bin:/usr/bin:/bin",
|
|
69
|
+
HOME: "/home/user",
|
|
70
|
+
// Make KSAs available via import path
|
|
71
|
+
NODE_PATH: "/home/user",
|
|
72
|
+
// Gateway config for KSAs to call cloud services
|
|
73
|
+
...(args.env?.CONVEX_URL && { CONVEX_URL: args.env.CONVEX_URL }),
|
|
74
|
+
...(args.env?.GATEWAY_URL && { GATEWAY_URL: args.env.GATEWAY_URL }),
|
|
75
|
+
...(args.env?.SANDBOX_JWT && { SANDBOX_JWT: args.env.SANDBOX_JWT }),
|
|
76
|
+
...(args.env?.CARD_ID && { CARD_ID: args.env.CARD_ID }),
|
|
77
|
+
...(args.env?.THREAD_ID && { THREAD_ID: args.env.THREAD_ID }),
|
|
78
|
+
},
|
|
79
|
+
maxBuffer: MAX_OUTPUT_LENGTH * 2,
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// Combine and truncate output
|
|
83
|
+
let output = stdout || "";
|
|
84
|
+
if (stderr) {
|
|
85
|
+
output += stderr ? `\n[stderr]\n${stderr}` : "";
|
|
86
|
+
}
|
|
87
|
+
if (output.length > MAX_OUTPUT_LENGTH) {
|
|
88
|
+
output = output.slice(0, MAX_OUTPUT_LENGTH) + "\n... (output truncated)";
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
success: true,
|
|
93
|
+
output: output.trim(),
|
|
94
|
+
exitCode: 0,
|
|
95
|
+
};
|
|
96
|
+
} catch (error: unknown) {
|
|
97
|
+
const err = error as { message?: string; stdout?: string; stderr?: string; code?: number };
|
|
98
|
+
const message = err.message || String(error);
|
|
99
|
+
let output = "";
|
|
100
|
+
|
|
101
|
+
// Capture any partial output
|
|
102
|
+
if (err.stdout) output += err.stdout;
|
|
103
|
+
if (err.stderr) output += `\n[stderr]\n${err.stderr}`;
|
|
104
|
+
|
|
105
|
+
// Check for timeout
|
|
106
|
+
if (message.includes("TIMEOUT") || message.includes("timed out")) {
|
|
107
|
+
return {
|
|
108
|
+
success: false,
|
|
109
|
+
output: output.trim(),
|
|
110
|
+
error: `Execution timed out after ${timeout}ms`,
|
|
111
|
+
exitCode: 124,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return {
|
|
116
|
+
success: false,
|
|
117
|
+
output: output.trim(),
|
|
118
|
+
error: message,
|
|
119
|
+
exitCode: err.code || 1,
|
|
120
|
+
};
|
|
121
|
+
} finally {
|
|
122
|
+
// Clean up temp file
|
|
123
|
+
try {
|
|
124
|
+
await fs.unlink(filename);
|
|
125
|
+
} catch {
|
|
126
|
+
// Ignore cleanup errors
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
});
|