@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.
Files changed (111) hide show
  1. package/README.md +166 -0
  2. package/convex/_generated/api.d.ts +45 -0
  3. package/convex/_generated/api.js +23 -0
  4. package/convex/_generated/dataModel.d.ts +58 -0
  5. package/convex/_generated/server.d.ts +143 -0
  6. package/convex/_generated/server.js +93 -0
  7. package/convex/cloud/CLAUDE.md +238 -0
  8. package/convex/cloud/_generated/api.ts +84 -0
  9. package/convex/cloud/_generated/component.ts +861 -0
  10. package/convex/cloud/_generated/dataModel.ts +60 -0
  11. package/convex/cloud/_generated/server.ts +156 -0
  12. package/convex/cloud/convex.config.ts +16 -0
  13. package/convex/cloud/index.ts +29 -0
  14. package/convex/cloud/intentSchema/generate.ts +447 -0
  15. package/convex/cloud/intentSchema/index.ts +16 -0
  16. package/convex/cloud/intentSchema/types.ts +418 -0
  17. package/convex/cloud/ksaPolicy.ts +554 -0
  18. package/convex/cloud/mail.ts +92 -0
  19. package/convex/cloud/schema.ts +322 -0
  20. package/convex/cloud/utils/kanbanContext.ts +229 -0
  21. package/convex/cloud/workflows/agentBoard.ts +451 -0
  22. package/convex/cloud/workflows/agentPrompt.ts +272 -0
  23. package/convex/cloud/workflows/agentThread.ts +374 -0
  24. package/convex/cloud/workflows/compileSandbox.ts +146 -0
  25. package/convex/cloud/workflows/crudBoard.ts +217 -0
  26. package/convex/cloud/workflows/crudKSAs.ts +262 -0
  27. package/convex/cloud/workflows/crudLorobeads.ts +371 -0
  28. package/convex/cloud/workflows/crudSkills.ts +205 -0
  29. package/convex/cloud/workflows/crudThreads.ts +708 -0
  30. package/convex/cloud/workflows/lifecycleSandbox.ts +1396 -0
  31. package/convex/cloud/workflows/sandboxConvex.ts +1046 -0
  32. package/convex/sandbox/README.md +90 -0
  33. package/convex/sandbox/_generated/api.d.ts +2934 -0
  34. package/convex/sandbox/_generated/api.js +23 -0
  35. package/convex/sandbox/_generated/dataModel.d.ts +60 -0
  36. package/convex/sandbox/_generated/server.d.ts +143 -0
  37. package/convex/sandbox/_generated/server.js +93 -0
  38. package/convex/sandbox/actions/bash.ts +130 -0
  39. package/convex/sandbox/actions/browser.ts +282 -0
  40. package/convex/sandbox/actions/file.ts +336 -0
  41. package/convex/sandbox/actions/lsp.ts +325 -0
  42. package/convex/sandbox/actions/pdf.ts +119 -0
  43. package/convex/sandbox/agent/codeExecLoop.ts +535 -0
  44. package/convex/sandbox/agent/decisions.ts +284 -0
  45. package/convex/sandbox/agent/index.ts +515 -0
  46. package/convex/sandbox/agent/subagents.ts +651 -0
  47. package/convex/sandbox/brandResearch/index.ts +417 -0
  48. package/convex/sandbox/context/index.ts +7 -0
  49. package/convex/sandbox/context/session.ts +402 -0
  50. package/convex/sandbox/convex.config.ts +17 -0
  51. package/convex/sandbox/index.ts +51 -0
  52. package/convex/sandbox/nodeActions/codeExec.ts +130 -0
  53. package/convex/sandbox/planning/beads.ts +187 -0
  54. package/convex/sandbox/planning/index.ts +8 -0
  55. package/convex/sandbox/planning/sync.ts +194 -0
  56. package/convex/sandbox/prompts/codeExec.ts +852 -0
  57. package/convex/sandbox/prompts/modes.ts +231 -0
  58. package/convex/sandbox/prompts/system.ts +142 -0
  59. package/convex/sandbox/schema.ts +510 -0
  60. package/convex/sandbox/state/artifacts.ts +99 -0
  61. package/convex/sandbox/state/checkpoints.ts +341 -0
  62. package/convex/sandbox/state/files.ts +383 -0
  63. package/convex/sandbox/state/index.ts +10 -0
  64. package/convex/sandbox/state/verification.actions.ts +268 -0
  65. package/convex/sandbox/state/verification.ts +101 -0
  66. package/convex/sandbox/tsconfig.json +25 -0
  67. package/convex/sandbox/utils/codeExecHelpers.ts +52 -0
  68. package/dist/cli/commands/build.d.ts +19 -0
  69. package/dist/cli/commands/build.d.ts.map +1 -0
  70. package/dist/cli/commands/build.js +223 -0
  71. package/dist/cli/commands/init.d.ts +16 -0
  72. package/dist/cli/commands/init.d.ts.map +1 -0
  73. package/dist/cli/commands/init.js +148 -0
  74. package/dist/cli/commands/publish.d.ts +12 -0
  75. package/dist/cli/commands/publish.d.ts.map +1 -0
  76. package/dist/cli/commands/publish.js +33 -0
  77. package/dist/cli/index.d.ts +14 -0
  78. package/dist/cli/index.d.ts.map +1 -0
  79. package/dist/cli/index.js +40 -0
  80. package/dist/sdk/builders.d.ts +104 -0
  81. package/dist/sdk/builders.d.ts.map +1 -0
  82. package/dist/sdk/builders.js +214 -0
  83. package/dist/sdk/index.d.ts +29 -0
  84. package/dist/sdk/index.d.ts.map +1 -0
  85. package/dist/sdk/index.js +38 -0
  86. package/dist/sdk/types.d.ts +107 -0
  87. package/dist/sdk/types.d.ts.map +1 -0
  88. package/dist/sdk/types.js +6 -0
  89. package/ksa/README.md +263 -0
  90. package/ksa/_generated/REFERENCE.md +2954 -0
  91. package/ksa/_generated/registry.ts +257 -0
  92. package/ksa/_shared/configReader.ts +302 -0
  93. package/ksa/_shared/configSchemas.ts +649 -0
  94. package/ksa/_shared/gateway.ts +175 -0
  95. package/ksa/_shared/ksaBehaviors.ts +411 -0
  96. package/ksa/_shared/ksaProxy.ts +248 -0
  97. package/ksa/_shared/localDb.ts +302 -0
  98. package/ksa/index.ts +134 -0
  99. package/package.json +93 -0
  100. package/runtime/browser/agent-browser.ts +330 -0
  101. package/runtime/entrypoint.ts +194 -0
  102. package/runtime/lsp/manager.ts +366 -0
  103. package/runtime/pdf/pdf-generator.ts +50 -0
  104. package/runtime/pdf/renderer.ts +357 -0
  105. package/runtime/pdf/schema.ts +97 -0
  106. package/runtime/services/file-watcher.ts +191 -0
  107. package/template/build.ts +307 -0
  108. package/template/e2b/Dockerfile +69 -0
  109. package/template/e2b/e2b.toml +13 -0
  110. package/template/e2b/prebuild.sh +68 -0
  111. 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
+ });