@lakitu/sdk 0.1.65 → 0.1.67

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.
@@ -1,4 +1,35 @@
1
1
  /**
2
+ * @deprecated LEGACY - Use sandboxConvex.ts instead
3
+ *
4
+ * This file is DEPRECATED as of 2026-01-21.
5
+ * The replacement is: sandboxConvex.ts (self-hosted Convex backend)
6
+ *
7
+ * WHY DEPRECATED:
8
+ * - Uses E2B gRPC which times out after 50-60s in Convex actions
9
+ * - Requires OpenCode HTTP server + event forwarder workarounds
10
+ * - Complex polling logic to work around gRPC timeout issues
11
+ *
12
+ * WHY NOT DELETED:
13
+ * - Gateway falls back to lifecycleSandbox.getSession for legacy sessions
14
+ * - Frontend subscribes to both session types for backward compatibility
15
+ * - Existing sessions may still reference this module
16
+ *
17
+ * MIGRATION:
18
+ * - New code should use sandboxConvex.ts exclusively
19
+ * - This file will be removed once all legacy sessions have completed
20
+ *
21
+ * KEY DIFFERENCES (sandboxConvex.ts):
22
+ * - Uses self-hosted Convex backend instead of OpenCode
23
+ * - Native Convex streaming (no SSE/event forwarder)
24
+ * - Direct Convex client communication
25
+ * - Simpler architecture without gRPC timeout workarounds
26
+ * - Checkpoint-based chaining for long tasks
27
+ *
28
+ * @see sandboxConvex.ts for the modern implementation
29
+ * ============================================================================
30
+ *
31
+ * ORIGINAL DOCUMENTATION (preserved for reference):
32
+ *
2
33
  * Sandbox Lifecycle - E2B sandbox spawn and session management
3
34
  *
4
35
  * NON-BLOCKING flow (avoids E2B gRPC stream timeout):
@@ -81,23 +112,23 @@ function createMetrics(): TimingMetrics {
81
112
  function recordStep(metrics: TimingMetrics, name: string, startMs: number): void {
82
113
  const durationMs = Date.now() - startMs;
83
114
  metrics.steps.push({ name, startMs: startMs - metrics.startTime, durationMs });
84
- console.log(`⏱️ [${name}] ${durationMs}ms`);
115
+ console.log(`[DEPRECATED:lifecycleSandbox] [${name}] ${durationMs}ms`);
85
116
  }
86
117
 
87
118
  function formatMetrics(metrics: TimingMetrics): string {
88
119
  const totalDuration = Date.now() - metrics.startTime;
89
120
  const lines = [
90
- `\n📊 TIMING REPORT (total: ${(totalDuration / 1000).toFixed(1)}s)`,
91
- "".repeat(50),
121
+ `\n[DEPRECATED:lifecycleSandbox] TIMING REPORT (total: ${(totalDuration / 1000).toFixed(1)}s)`,
122
+ "-".repeat(50),
92
123
  ];
93
124
 
94
125
  for (const step of metrics.steps) {
95
126
  const pct = totalDuration > 0 ? ((step.durationMs / totalDuration) * 100).toFixed(1) : "0";
96
- const bar = "".repeat(Math.min(20, Math.round(step.durationMs / (totalDuration / 20))));
127
+ const bar = "=".repeat(Math.min(20, Math.round(step.durationMs / (totalDuration / 20))));
97
128
  lines.push(`${step.name.padEnd(25)} ${(step.durationMs / 1000).toFixed(2)}s ${bar} ${pct}%`);
98
129
  }
99
130
 
100
- lines.push("".repeat(50));
131
+ lines.push("-".repeat(50));
101
132
  return lines.join("\n");
102
133
  }
103
134
 
@@ -312,7 +343,7 @@ export const storeSessionMetrics = internalMutation({
312
343
  // Also log as a timing entry for visibility
313
344
  await ctx.db.insert("agentSessionLogs", {
314
345
  sessionId: args.sessionId,
315
- message: `⏱️ SETUP: ${(args.metrics.setupMs / 1000).toFixed(1)}s (sandbox: ${(args.metrics.sandboxCreateMs / 1000).toFixed(1)}s, server: ${(args.metrics.serverStartupMs / 1000).toFixed(1)}s)`,
346
+ message: `[DEPRECATED] SETUP: ${(args.metrics.setupMs / 1000).toFixed(1)}s (sandbox: ${(args.metrics.sandboxCreateMs / 1000).toFixed(1)}s, server: ${(args.metrics.serverStartupMs / 1000).toFixed(1)}s)`,
316
347
  createdAt: Date.now(),
317
348
  });
318
349
  },
@@ -378,13 +409,13 @@ export const completeFromForwarder = mutation({
378
409
  );
379
410
 
380
411
  if (!session) {
381
- console.log(`[completeFromForwarder] Session not found: ${args.sessionId}`);
412
+ console.log(`[DEPRECATED:completeFromForwarder] Session not found: ${args.sessionId}`);
382
413
  return { success: false, error: "Session not found" };
383
414
  }
384
415
 
385
416
  // Check if already completed
386
417
  if (session.status === "completed" || session.status === "failed") {
387
- console.log(`[completeFromForwarder] Session already ${session.status}`);
418
+ console.log(`[DEPRECATED:completeFromForwarder] Session already ${session.status}`);
388
419
  return { success: true, alreadyComplete: true };
389
420
  }
390
421
 
@@ -403,11 +434,11 @@ export const completeFromForwarder = mutation({
403
434
  // Log completion
404
435
  await ctx.db.insert("agentSessionLogs", {
405
436
  sessionId: session._id,
406
- message: `✅ Completed via event forwarder (${args.messagesCount} messages, ${args.toolCalls.length} tools)`,
437
+ message: `[DEPRECATED] Completed via event forwarder (${args.messagesCount} messages, ${args.toolCalls.length} tools)`,
407
438
  createdAt: Date.now(),
408
439
  });
409
440
 
410
- console.log(`[completeFromForwarder] Session ${session._id} completed`);
441
+ console.log(`[DEPRECATED:completeFromForwarder] Session ${session._id} completed`);
411
442
 
412
443
  // Kill the sandbox (schedule async to not block)
413
444
  // The polling action will notice the session is complete and skip processing
@@ -442,6 +473,7 @@ export const markSessionRunning = internalMutation({
442
473
  export const startSession = action({
443
474
  args: { projectId: v.string(), prompt: v.string(), config: v.optional(v.any()) },
444
475
  handler: async (ctx, args) => {
476
+ console.warn("[DEPRECATED] lifecycleSandbox.startSession is deprecated. Use sandboxConvex.startSession instead.");
445
477
  if (args.config?.cardId) {
446
478
  const existing = await ctx.runQuery(api.workflows.lifecycleSandbox.getActiveSessionForCard, {
447
479
  cardId: args.config.cardId.toString(),
@@ -492,6 +524,7 @@ export const spawnSandbox = action({
492
524
  maxTokens: v.optional(v.number()),
493
525
  },
494
526
  handler: async (ctx, args) => {
527
+ console.warn("[DEPRECATED] lifecycleSandbox.spawnSandbox is deprecated. Use sandboxConvex instead.");
495
528
  let fullPrompt = args.prompt;
496
529
  if (args.systemPrompt) {
497
530
  fullPrompt = `${args.systemPrompt}\n\n${args.prompt}`;
@@ -513,7 +546,7 @@ export const spawnSandbox = action({
513
546
  // Check if we're in polling mode (non-blocking)
514
547
  // If so, DON'T return output so runAgentStep uses the async DB polling path
515
548
  if ((result as any).status === "polling") {
516
- console.log(`[spawnSandbox] Sandbox started in polling mode for session ${args.sessionId}`);
549
+ console.log(`[DEPRECATED:spawnSandbox] Sandbox started in polling mode for session ${args.sessionId}`);
517
550
  return {
518
551
  sessionId: args.sessionId,
519
552
  polling: true, // Indicator for caller
@@ -557,6 +590,7 @@ export const runSandbox = internalAction({
557
590
  })),
558
591
  },
559
592
  handler: async (ctx, args) => {
593
+ console.warn("[DEPRECATED] lifecycleSandbox.runSandbox is deprecated. Use sandboxConvex.runConvexSandbox instead.");
560
594
  // Get agent config from args (unified settings) or use defaults
561
595
  const agentConfig = getAgentConfig(args.modelConfig);
562
596
  // Helper to restore previous stage state (VFS + Beads)
@@ -578,7 +612,7 @@ export const runSandbox = internalAction({
578
612
 
579
613
  // 2. Write artifacts to sandbox VFS
580
614
  if (artifacts && artifacts.length > 0) {
581
- console.log(`[Sandbox] 📂 Restoring ${artifacts.length} files from previous stages...`);
615
+ console.log(`[DEPRECATED:Sandbox] Restoring ${artifacts.length} files from previous stages...`);
582
616
 
583
617
  for (const artifact of artifacts) {
584
618
  const targetPath = artifact.path?.startsWith('/home/user/workspace')
@@ -607,7 +641,7 @@ ARTIFACT_EOF`);
607
641
 
608
642
  filesRestored++;
609
643
  }
610
- console.log(`[Sandbox] Restored ${filesRestored} files to VFS`);
644
+ console.log(`[DEPRECATED:Sandbox] Restored ${filesRestored} files to VFS`);
611
645
  }
612
646
 
613
647
  // 3. Get and restore Beads state
@@ -616,7 +650,7 @@ ARTIFACT_EOF`);
616
650
  });
617
651
 
618
652
  if (beadsState && beadsState.beadsState) {
619
- console.log(`[Sandbox] 🧠 Restoring Beads state from previous stage...`);
653
+ console.log(`[DEPRECATED:Sandbox] Restoring Beads state from previous stage...`);
620
654
  const beadsJson = typeof beadsState.beadsState === 'string'
621
655
  ? beadsState.beadsState
622
656
  : JSON.stringify(beadsState.beadsState);
@@ -626,10 +660,10 @@ ARTIFACT_EOF`);
626
660
  ${beadsJson}
627
661
  BEADS_EOF`);
628
662
  beadsRestored = true;
629
- console.log(`[Sandbox] Beads state restored`);
663
+ console.log(`[DEPRECATED:Sandbox] Beads state restored`);
630
664
  }
631
665
  } catch (e) {
632
- console.warn(`[Sandbox] ⚠️ State restoration error (non-fatal): ${e}`);
666
+ console.warn(`[DEPRECATED:Sandbox] State restoration error (non-fatal): ${e}`);
633
667
  }
634
668
 
635
669
  recordStepFn(metricsFn, "state_restore", stepStart);
@@ -646,7 +680,7 @@ BEADS_EOF`);
646
680
 
647
681
  try {
648
682
  stepStart = Date.now();
649
- // Generate JWT for sandbox Convex callbacks
683
+ // Generate JWT for sandbox -> Convex callbacks
650
684
  const secret = process.env.SANDBOX_JWT_SECRET;
651
685
  if (!secret) throw new Error("SANDBOX_JWT_SECRET not configured");
652
686
 
@@ -690,7 +724,7 @@ BEADS_EOF`);
690
724
  if (args.cardId) {
691
725
  const restoreResult = await restorePreviousState(sandbox, args.cardId, recordStep, metrics);
692
726
  if (restoreResult.filesRestored > 0 || restoreResult.beadsRestored) {
693
- console.log(`[Sandbox] 🔄 State restored: ${restoreResult.filesRestored} files, beads=${restoreResult.beadsRestored}`);
727
+ console.log(`[DEPRECATED:Sandbox] State restored: ${restoreResult.filesRestored} files, beads=${restoreResult.beadsRestored}`);
694
728
  }
695
729
  }
696
730
 
@@ -728,7 +762,7 @@ BEADS_EOF`);
728
762
  if (!serverReady) throw new Error("OpenCode server failed to start");
729
763
  recordStep(metrics, "server_startup", stepStart);
730
764
  metrics.totals.serverStartup = Date.now() - stepStart;
731
- console.log(`⏱️ [server_startup] Ready after ${serverReadyLoops} polls (${((Date.now() - stepStart) / 1000).toFixed(2)}s)`);
765
+ console.log(`[DEPRECATED] [server_startup] Ready after ${serverReadyLoops} polls (${((Date.now() - stepStart) / 1000).toFixed(2)}s)`);
732
766
 
733
767
  // 4. Configure auth + create session IN PARALLEL
734
768
  stepStart = Date.now();
@@ -750,8 +784,8 @@ BEADS_EOF`);
750
784
  throw new Error(`Auth config failed: ${authRes.status} - ${authErr}`);
751
785
  }
752
786
  const authBody = await authRes.text();
753
- console.log(`[Sandbox] Auth response: ${authBody.slice(0, 200)}`);
754
- console.log(`[Sandbox] OpenRouter key present: ${!!process.env.OPENROUTER_API_KEY}, length: ${process.env.OPENROUTER_API_KEY?.length || 0}`);
787
+ console.log(`[DEPRECATED:Sandbox] Auth response: ${authBody.slice(0, 200)}`);
788
+ console.log(`[DEPRECATED:Sandbox] OpenRouter key present: ${!!process.env.OPENROUTER_API_KEY}, length: ${process.env.OPENROUTER_API_KEY?.length || 0}`);
755
789
  recordStep(metrics, "auth_config", stepStart);
756
790
  metrics.totals.authConfig = Date.now() - stepStart;
757
791
 
@@ -770,13 +804,13 @@ BEADS_EOF`);
770
804
  // Log prompt size for debugging latency issues
771
805
  const promptChars = fullPrompt.length;
772
806
  const estimatedTokens = Math.round(promptChars / 4); // ~4 chars per token estimate
773
- console.log(`📝 [Prompt] ${promptChars} chars, ~${estimatedTokens} tokens estimated`);
807
+ console.log(`[DEPRECATED] [Prompt] ${promptChars} chars, ~${estimatedTokens} tokens estimated`);
774
808
 
775
809
  // 7. Update convex with session status
776
810
  stepStart = Date.now();
777
811
  const forwarderPath = "/home/user/scripts/event-forwarder.ts";
778
- console.log(`[Sandbox] JWT length: ${jwt.length}, first 20 chars: ${jwt.slice(0, 20)}...`);
779
- console.log(`[Sandbox] CONVEX_URL in sandbox: ${convexUrl}`);
812
+ console.log(`[DEPRECATED:Sandbox] JWT length: ${jwt.length}, first 20 chars: ${jwt.slice(0, 20)}...`);
813
+ console.log(`[DEPRECATED:Sandbox] CONVEX_URL in sandbox: ${convexUrl}`);
780
814
 
781
815
  await ctx.runMutation(internal.workflows.lifecycleSandbox.markSessionRunning, {
782
816
  sessionId: args.sessionId,
@@ -815,8 +849,8 @@ BEADS_EOF`);
815
849
  throw new Error(`Async message failed: ${asyncRes.status} ${errBody.slice(0, 200)}`);
816
850
  }
817
851
  const promptBody = await asyncRes.text();
818
- console.log(`[Sandbox] Prompt response: ${promptBody.slice(0, 300)}`);
819
- console.log(`[Sandbox] Model: ${agentConfig.primaryModel}, Provider: ${agentConfig.provider}`);
852
+ console.log(`[DEPRECATED:Sandbox] Prompt response: ${promptBody.slice(0, 300)}`);
853
+ console.log(`[DEPRECATED:Sandbox] Model: ${agentConfig.primaryModel}, Provider: ${agentConfig.provider}`);
820
854
  recordStep(metrics, "prompt_send", stepStart);
821
855
  metrics.totals.promptSend = Date.now() - stepStart;
822
856
 
@@ -828,14 +862,14 @@ BEADS_EOF`);
828
862
  { background: true }
829
863
  );
830
864
  recordStep(metrics, "event_forwarder", stepStart);
831
- console.log(`[Sandbox] Started event forwarder for session ${openCodeSessionId}`);
865
+ console.log(`[DEPRECATED:Sandbox] Started event forwarder for session ${openCodeSessionId}`);
832
866
 
833
867
  // Calculate total setup time
834
868
  metrics.totals.totalSetup = Date.now() - metrics.startTime;
835
869
 
836
870
  // Log timing report
837
871
  console.log(formatMetrics(metrics));
838
- console.log(`[Sandbox] Async prompt sent, scheduling polling action`);
872
+ console.log(`[DEPRECATED:Sandbox] Async prompt sent, scheduling polling action`);
839
873
 
840
874
  // Store metrics in session for later analysis
841
875
  await ctx.runMutation(internal.workflows.lifecycleSandbox.storeSessionMetrics, {
@@ -864,7 +898,7 @@ BEADS_EOF`);
864
898
  return { success: true, output: "", toolCalls: [], todos: [], status: "polling" };
865
899
  } catch (error) {
866
900
  const message = error instanceof Error ? error.message : String(error);
867
- console.error(`[Sandbox] Error during setup: ${message}`);
901
+ console.error(`[DEPRECATED:Sandbox] Error during setup: ${message}`);
868
902
  await ctx.runMutation(api.workflows.lifecycleSandbox.updateSessionStatus, {
869
903
  sessionId: args.sessionId,
870
904
  status: "failed",
@@ -905,11 +939,11 @@ export const timeoutWatchdog = internalAction({
905
939
  });
906
940
 
907
941
  if (session?.status === "completed" || session?.status === "failed" || session?.status === "cancelled") {
908
- console.log(`[Watchdog] Session ${args.sessionId} already ${session.status}, cleaning up sandbox`);
942
+ console.log(`[DEPRECATED:Watchdog] Session ${args.sessionId} already ${session.status}, cleaning up sandbox`);
909
943
  } else {
910
944
  // Session still running after 10 minutes - mark as timed out
911
945
  const elapsed = Math.round((Date.now() - args.startTime) / 1000);
912
- console.log(`[Watchdog] Session ${args.sessionId} timed out after ${elapsed}s`);
946
+ console.log(`[DEPRECATED:Watchdog] Session ${args.sessionId} timed out after ${elapsed}s`);
913
947
 
914
948
  await ctx.runMutation(api.workflows.lifecycleSandbox.updateSessionStatus, {
915
949
  sessionId: args.sessionId,
@@ -923,9 +957,9 @@ export const timeoutWatchdog = internalAction({
923
957
  const { Sandbox } = await import("@e2b/code-interpreter");
924
958
  const sandbox = await Sandbox.connect(args.sandboxId);
925
959
  await sandbox.kill();
926
- console.log(`[Watchdog] Killed sandbox ${args.sandboxId}`);
960
+ console.log(`[DEPRECATED:Watchdog] Killed sandbox ${args.sandboxId}`);
927
961
  } catch (e) {
928
- console.log(`[Watchdog] Sandbox cleanup: ${e}`);
962
+ console.log(`[DEPRECATED:Watchdog] Sandbox cleanup: ${e}`);
929
963
  }
930
964
  },
931
965
  });
@@ -963,16 +997,16 @@ export const pollSandboxCompletion = internalAction({
963
997
  });
964
998
 
965
999
  if (session?.status === "completed" || session?.status === "failed") {
966
- console.log(`[Poll ${args.pollCount}] Session already ${session.status} (via forwarder), cleaning up`);
1000
+ console.log(`[DEPRECATED:Poll ${args.pollCount}] Session already ${session.status} (via forwarder), cleaning up`);
967
1001
 
968
1002
  // Kill the sandbox
969
1003
  try {
970
1004
  const { Sandbox } = await import("@e2b/code-interpreter");
971
1005
  const sandbox = await Sandbox.connect(args.sandboxId);
972
1006
  await sandbox.kill();
973
- console.log(`[Poll] Killed sandbox ${args.sandboxId}`);
1007
+ console.log(`[DEPRECATED:Poll] Killed sandbox ${args.sandboxId}`);
974
1008
  } catch (e) {
975
- console.log(`[Poll] Sandbox cleanup: ${e}`);
1009
+ console.log(`[DEPRECATED:Poll] Sandbox cleanup: ${e}`);
976
1010
  }
977
1011
  return;
978
1012
  }
@@ -1007,7 +1041,7 @@ export const pollSandboxCompletion = internalAction({
1007
1041
 
1008
1042
  // Debug: Log progress
1009
1043
  if (args.pollCount % 3 === 0 || totalParts > lastPartsCount) {
1010
- console.log(`[Poll ${args.pollCount}] 📬 Messages: ${messages.length}, Parts: ${totalParts} (last seen: ${lastPartsCount})`);
1044
+ console.log(`[DEPRECATED:Poll ${args.pollCount}] Messages: ${messages.length}, Parts: ${totalParts} (last seen: ${lastPartsCount})`);
1011
1045
  }
1012
1046
 
1013
1047
  // Stream new parts to Convex (parts we haven't seen before)
@@ -1027,7 +1061,7 @@ export const pollSandboxCompletion = internalAction({
1027
1061
  name = name.replace(/([A-Z])/g, " $1").trim();
1028
1062
  return name.charAt(0).toUpperCase() + name.slice(1);
1029
1063
  };
1030
- console.log(`[Poll ${args.pollCount}] 📝 Processing ${newParts.length} new parts`);
1064
+ console.log(`[DEPRECATED:Poll ${args.pollCount}] Processing ${newParts.length} new parts`);
1031
1065
 
1032
1066
  for (const { role, part } of newParts) {
1033
1067
  // Only log meaningful events:
@@ -1047,14 +1081,14 @@ export const pollSandboxCompletion = internalAction({
1047
1081
 
1048
1082
  // Special handling for specific tools
1049
1083
  if (rawName.includes("saveArtifact")) {
1050
- logsToAdd.push(`📄 Saving artifact...`);
1084
+ logsToAdd.push(`[DEPRECATED] Saving artifact...`);
1051
1085
  } else if (rawName.includes("completeStage")) {
1052
- logsToAdd.push(`🏁 Completing stage...`);
1086
+ logsToAdd.push(`[DEPRECATED] Completing stage...`);
1053
1087
  } else if (rawName.includes("beads.create") || rawName.includes("beads.close") || rawName.includes("beads.update")) {
1054
1088
  const action = rawName.includes("create") ? "Creating" : rawName.includes("close") ? "Completing" : "Updating";
1055
- logsToAdd.push(`📋 ${action} task...`);
1089
+ logsToAdd.push(`[DEPRECATED] ${action} task...`);
1056
1090
  } else if (state === "calling" || state === "pending") {
1057
- logsToAdd.push(`🔧 ${toolName}...`);
1091
+ logsToAdd.push(`[DEPRECATED] ${toolName}...`);
1058
1092
  }
1059
1093
  } else if (part.type === "tool-result" || part.toolResult) {
1060
1094
  // Tool completed
@@ -1064,30 +1098,30 @@ export const pollSandboxCompletion = internalAction({
1064
1098
  // Check for errors in result
1065
1099
  const hasError = part.toolResult?.error || part.state?.error;
1066
1100
  if (hasError) {
1067
- logsToAdd.push(`❌ ${toolName} failed`);
1101
+ logsToAdd.push(`[DEPRECATED] ${toolName} failed`);
1068
1102
  } else if (rawName.includes("saveArtifact")) {
1069
1103
  // Get artifact name from result if available
1070
1104
  const result = part.toolResult?.result || part.result;
1071
1105
  const name = result?.saved || result?.name || "artifact";
1072
- logsToAdd.push(`✅ Saved: ${name}`);
1106
+ logsToAdd.push(`[DEPRECATED] Saved: ${name}`);
1073
1107
  } else if (rawName.includes("completeStage")) {
1074
- logsToAdd.push(`✅ Stage completed`);
1108
+ logsToAdd.push(`[DEPRECATED] Stage completed`);
1075
1109
  } else if (rawName.includes("beads.create")) {
1076
1110
  const result = part.toolResult?.result || part.result;
1077
1111
  const title = result?.title || "task";
1078
- logsToAdd.push(`✅ Created: ${title}`);
1112
+ logsToAdd.push(`[DEPRECATED] Created: ${title}`);
1079
1113
  } else if (rawName.includes("beads.close")) {
1080
- logsToAdd.push(`✅ Task completed`);
1114
+ logsToAdd.push(`[DEPRECATED] Task completed`);
1081
1115
  } else {
1082
1116
  // Generic tool completion
1083
- logsToAdd.push(`✅ ${toolName}`);
1117
+ logsToAdd.push(`[DEPRECATED] ${toolName}`);
1084
1118
  }
1085
1119
  }
1086
1120
  // Skip: text, reasoning, step-start, step-finish, patch, unknown
1087
1121
  }
1088
1122
 
1089
1123
  if (logsToAdd.length > 0) {
1090
- console.log(`[Poll ${args.pollCount}] 📨 Streaming ${logsToAdd.length} new log entries`);
1124
+ console.log(`[DEPRECATED:Poll ${args.pollCount}] Streaming ${logsToAdd.length} new log entries`);
1091
1125
  await ctx.runMutation(api.workflows.lifecycleSandbox.appendSessionLogs, {
1092
1126
  sessionId: args.sessionId,
1093
1127
  logs: logsToAdd,
@@ -1097,7 +1131,7 @@ export const pollSandboxCompletion = internalAction({
1097
1131
  }
1098
1132
  } catch (e) {
1099
1133
  // Don't fail the poll if message streaming fails
1100
- console.log(`[Poll ${args.pollCount}] ⚠️ Message fetch error: ${e}`);
1134
+ console.log(`[DEPRECATED:Poll ${args.pollCount}] Message fetch error: ${e}`);
1101
1135
  }
1102
1136
 
1103
1137
  // ═══════════════════════════════════════════════════════════════════════
@@ -1111,7 +1145,7 @@ export const pollSandboxCompletion = internalAction({
1111
1145
  if (!statusRes.ok) {
1112
1146
  // Check if sandbox is dead (E2B returns 502 with specific messages)
1113
1147
  const errorBody = await statusRes.text().catch(() => "");
1114
- console.log(`[Poll ${args.pollCount}] Status fetch failed: ${statusRes.status}, body: ${errorBody.slice(0, 200)}`);
1148
+ console.log(`[DEPRECATED:Poll ${args.pollCount}] Status fetch failed: ${statusRes.status}, body: ${errorBody.slice(0, 200)}`);
1115
1149
 
1116
1150
  // If sandbox is dead or port not open, fail fast
1117
1151
  if (errorBody.includes("not found") || errorBody.includes("not open") || statusRes.status === 502) {
@@ -1143,7 +1177,7 @@ export const pollSandboxCompletion = internalAction({
1143
1177
  if (sessionStatus === undefined) {
1144
1178
  // Session not in status list - check if it's because it completed
1145
1179
  // by verifying we have messages (meaning the session existed and ran)
1146
- console.log(`[Poll ${args.pollCount}] ${elapsed}s elapsed - session not in status list, checking for completion...`);
1180
+ console.log(`[DEPRECATED:Poll ${args.pollCount}] ${elapsed}s elapsed - session not in status list, checking for completion...`);
1147
1181
 
1148
1182
  // Quick check: try to fetch messages to see if session existed
1149
1183
  const msgCheckRes = await fetch(`${baseUrl}/session/${args.openCodeSessionId}/message`, {
@@ -1157,11 +1191,11 @@ export const pollSandboxCompletion = internalAction({
1157
1191
 
1158
1192
  if (messages.length > 0) {
1159
1193
  // We have messages, so the session ran and completed (removed from status)
1160
- console.log(`[Poll ${args.pollCount}] Session completed (removed from status list, has ${messages.length} messages)`);
1194
+ console.log(`[DEPRECATED:Poll ${args.pollCount}] Session completed (removed from status list, has ${messages.length} messages)`);
1161
1195
  // Fall through to result collection below
1162
1196
  } else if (args.pollCount < 5) {
1163
1197
  // No messages yet and early in polling - maybe still initializing
1164
- console.log(`[Poll ${args.pollCount}] No messages yet, scheduling retry...`);
1198
+ console.log(`[DEPRECATED:Poll ${args.pollCount}] No messages yet, scheduling retry...`);
1165
1199
  await ctx.scheduler.runAfter(pollInterval, internal.workflows.lifecycleSandbox.pollSandboxCompletion, {
1166
1200
  ...args,
1167
1201
  pollCount: args.pollCount + 1,
@@ -1179,7 +1213,7 @@ export const pollSandboxCompletion = internalAction({
1179
1213
  } else {
1180
1214
  // OpenCode returns {type: "busy"} or {type: "idle"}, not {status: ...}
1181
1215
  const status = sessionStatus?.type || sessionStatus?.status || "unknown";
1182
- console.log(`[Poll ${args.pollCount}] ${elapsed}s elapsed, type=${status}, raw=${JSON.stringify(sessionStatus).slice(0, 100)}`);
1216
+ console.log(`[DEPRECATED:Poll ${args.pollCount}] ${elapsed}s elapsed, type=${status}, raw=${JSON.stringify(sessionStatus).slice(0, 100)}`);
1183
1217
 
1184
1218
  // Check if still processing
1185
1219
  if (status === "busy" || status === "processing") {
@@ -1203,7 +1237,7 @@ export const pollSandboxCompletion = internalAction({
1203
1237
  }
1204
1238
 
1205
1239
  // type === "idle" || type === "ready" - session is done
1206
- console.log(`[Poll ${args.pollCount}] OpenCode completed in ${elapsed}s (status: ${status})`);
1240
+ console.log(`[DEPRECATED:Poll ${args.pollCount}] OpenCode completed in ${elapsed}s (status: ${status})`);
1207
1241
  }
1208
1242
 
1209
1243
  // Collect results via direct HTTP
@@ -1219,7 +1253,7 @@ export const pollSandboxCompletion = internalAction({
1219
1253
  try {
1220
1254
  return JSON.parse(text);
1221
1255
  } catch (e: any) {
1222
- console.error(`[Sandbox] Failed to parse ${name}: ${e.message}`);
1256
+ console.error(`[DEPRECATED:Sandbox] Failed to parse ${name}: ${e.message}`);
1223
1257
  return [];
1224
1258
  }
1225
1259
  };
@@ -1228,7 +1262,7 @@ export const pollSandboxCompletion = internalAction({
1228
1262
  const todos = safeJsonParse(await todosRes.text(), "todos");
1229
1263
  const diffs = safeJsonParse(await diffsRes.text(), "diffs");
1230
1264
 
1231
- console.log(`[Sandbox] Collected: ${messages.length} messages, ${todos.length} todos, ${diffs.length} diffs`);
1265
+ console.log(`[DEPRECATED:Sandbox] Collected: ${messages.length} messages, ${todos.length} todos, ${diffs.length} diffs`);
1232
1266
 
1233
1267
  // Extract assistant responses with structured output:
1234
1268
  // - thinking: All reasoning and intermediate activity (for progress UI)
@@ -1253,7 +1287,7 @@ export const pollSandboxCompletion = internalAction({
1253
1287
  if (part.type === "text" && part.text) {
1254
1288
  // Skip if it looks like the system prompt being echoed
1255
1289
  if (part.text.startsWith("# ") && part.text.includes("## Context") && part.text.includes("## Rules")) {
1256
- console.log(`[Sandbox] Skipping system prompt echo (${part.text.length} chars)`);
1290
+ console.log(`[DEPRECATED:Sandbox] Skipping system prompt echo (${part.text.length} chars)`);
1257
1291
  continue;
1258
1292
  }
1259
1293
 
@@ -1266,20 +1300,20 @@ export const pollSandboxCompletion = internalAction({
1266
1300
  }
1267
1301
  } else if (part.type === "reasoning" && part.text) {
1268
1302
  // Chain of thought reasoning
1269
- thinkingParts.push(`💭 ${part.text}`);
1303
+ thinkingParts.push(`[DEPRECATED] ${part.text}`);
1270
1304
  } else if (part.type === "tool-invocation" || part.type === "tool") {
1271
1305
  const toolName = part.toolInvocation?.toolName || part.toolName || part.callID?.match(/tool_([^_]+_[^_]+)/)?.[1]?.replace("_", ".") || "unknown";
1272
1306
  const status = part.toolInvocation?.state?.status || part.state || "calling";
1273
1307
  const args = part.toolInvocation?.args || part.args;
1274
1308
  toolCalls.push({ name: toolName, status, args });
1275
- thinkingParts.push(`🔧 ${toolName}(${JSON.stringify(args || {}).slice(0, 100)})`);
1309
+ thinkingParts.push(`[DEPRECATED] ${toolName}(${JSON.stringify(args || {}).slice(0, 100)})`);
1276
1310
  } else if (part.type === "tool-result") {
1277
1311
  const toolName = part.toolName || part.toolResult?.toolName || "unknown";
1278
1312
  const result = typeof part.result === "string" ? part.result : JSON.stringify(part.result || {});
1279
1313
  // Update the last matching tool call with result
1280
1314
  const lastCall = [...toolCalls].reverse().find(t => t.name === toolName);
1281
1315
  if (lastCall) lastCall.result = result.slice(0, 500);
1282
- thinkingParts.push(`✅ ${toolName} ${result.slice(0, 100)}`);
1316
+ thinkingParts.push(`[DEPRECATED] ${toolName} -> ${result.slice(0, 100)}`);
1283
1317
  }
1284
1318
  }
1285
1319
  }
@@ -1290,7 +1324,7 @@ export const pollSandboxCompletion = internalAction({
1290
1324
  finalResponse = thinkingParts.pop() || "";
1291
1325
  }
1292
1326
 
1293
- console.log(`[Sandbox] Extracted: thinking=${thinkingParts.length} parts, response=${finalResponse.length} chars, tools=${toolCalls.length}`);
1327
+ console.log(`[DEPRECATED:Sandbox] Extracted: thinking=${thinkingParts.length} parts, response=${finalResponse.length} chars, tools=${toolCalls.length}`);
1294
1328
 
1295
1329
  // Calculate final timing metrics
1296
1330
  const totalExecutionMs = Date.now() - args.startTime;
@@ -1303,13 +1337,13 @@ export const pollSandboxCompletion = internalAction({
1303
1337
  };
1304
1338
 
1305
1339
  // Log final timing report
1306
- console.log(`\n📊 EXECUTION COMPLETE`);
1307
- console.log(`─`.repeat(50));
1340
+ console.log(`\n[DEPRECATED] EXECUTION COMPLETE`);
1341
+ console.log(`-`.repeat(50));
1308
1342
  console.log(`Total execution: ${(totalExecutionMs / 1000).toFixed(1)}s`);
1309
1343
  console.log(`Poll count: ${args.pollCount}`);
1310
1344
  console.log(`Messages: ${messages.length}`);
1311
1345
  console.log(`Tool calls: ${toolCalls.length}`);
1312
- console.log(`─`.repeat(50));
1346
+ console.log(`-`.repeat(50));
1313
1347
 
1314
1348
  // Update session with results and metrics
1315
1349
  // Output structure:
@@ -1339,7 +1373,7 @@ export const pollSandboxCompletion = internalAction({
1339
1373
  // Add final timing log entry
1340
1374
  await ctx.runMutation(api.workflows.lifecycleSandbox.appendSessionLogs, {
1341
1375
  sessionId: args.sessionId,
1342
- logs: [`⏱️ EXECUTION: ${(totalExecutionMs / 1000).toFixed(1)}s (${args.pollCount} polls, ${toolCalls.length} tools)`],
1376
+ logs: [`[DEPRECATED] EXECUTION: ${(totalExecutionMs / 1000).toFixed(1)}s (${args.pollCount} polls, ${toolCalls.length} tools)`],
1343
1377
  });
1344
1378
 
1345
1379
  // Capture workspace files before killing sandbox
@@ -1347,19 +1381,19 @@ export const pollSandboxCompletion = internalAction({
1347
1381
  try {
1348
1382
  const { Sandbox } = await import("@e2b/code-interpreter");
1349
1383
  const sandbox = await Sandbox.connect(args.sandboxId);
1350
-
1384
+
1351
1385
  const filesToSync: Array<{ path: string; name: string; content: string; type: string }> = [];
1352
-
1386
+
1353
1387
  for (const diff of diffs) {
1354
1388
  const filePath = diff.path || diff;
1355
1389
  if (!filePath || typeof filePath !== "string") continue;
1356
-
1390
+
1357
1391
  try {
1358
1392
  // Read file content from sandbox
1359
1393
  const result = await sandbox.commands.run(`cat "${filePath}" 2>/dev/null || echo ""`);
1360
1394
  const content = result.stdout || "";
1361
1395
  if (!content) continue;
1362
-
1396
+
1363
1397
  // Determine file type from extension
1364
1398
  const ext = filePath.split(".").pop()?.toLowerCase() || "";
1365
1399
  const typeMap: Record<string, string> = {
@@ -1370,7 +1404,7 @@ export const pollSandboxCompletion = internalAction({
1370
1404
  html: "html", css: "code", scss: "code",
1371
1405
  txt: "text", csv: "csv",
1372
1406
  };
1373
-
1407
+
1374
1408
  filesToSync.push({
1375
1409
  path: filePath,
1376
1410
  name: filePath.split("/").pop() || "file",
@@ -1381,21 +1415,21 @@ export const pollSandboxCompletion = internalAction({
1381
1415
  // Skip files that can't be read
1382
1416
  }
1383
1417
  }
1384
-
1418
+
1385
1419
  if (filesToSync.length > 0) {
1386
- console.log(`[Sandbox] 📦 Capturing ${filesToSync.length} workspace files...`);
1420
+ console.log(`[DEPRECATED:Sandbox] Capturing ${filesToSync.length} workspace files...`);
1387
1421
  await ctx.runMutation(api.features.kanban.file_sync.batchFileSync, {
1388
1422
  cardId: args.cardId as any,
1389
1423
  files: filesToSync,
1390
1424
  });
1391
- console.log(`[Sandbox] Workspace captured`);
1425
+ console.log(`[DEPRECATED:Sandbox] Workspace captured`);
1392
1426
  }
1393
-
1427
+
1394
1428
  // Kill the sandbox after capture
1395
1429
  await sandbox.kill();
1396
- console.log(`[Sandbox] Killed sandbox ${args.sandboxId}`);
1430
+ console.log(`[DEPRECATED:Sandbox] Killed sandbox ${args.sandboxId}`);
1397
1431
  } catch (e) {
1398
- console.log(`[Sandbox] Workspace capture/kill error: ${e}`);
1432
+ console.log(`[DEPRECATED:Sandbox] Workspace capture/kill error: ${e}`);
1399
1433
  }
1400
1434
  } else {
1401
1435
  // No cardId or no diffs - just kill the sandbox
@@ -1403,14 +1437,14 @@ export const pollSandboxCompletion = internalAction({
1403
1437
  const { Sandbox } = await import("@e2b/code-interpreter");
1404
1438
  const sandbox = await Sandbox.connect(args.sandboxId);
1405
1439
  await sandbox.kill();
1406
- console.log(`[Sandbox] Killed sandbox ${args.sandboxId}`);
1440
+ console.log(`[DEPRECATED:Sandbox] Killed sandbox ${args.sandboxId}`);
1407
1441
  } catch (e) {
1408
- console.log(`[Sandbox] Failed to kill sandbox (may already be dead): ${e}`);
1442
+ console.log(`[DEPRECATED:Sandbox] Failed to kill sandbox (may already be dead): ${e}`);
1409
1443
  }
1410
1444
  }
1411
1445
  } catch (error) {
1412
1446
  const message = error instanceof Error ? error.message : String(error);
1413
- console.error(`[Poll ${args.pollCount}] Error: ${message}`);
1447
+ console.error(`[DEPRECATED:Poll ${args.pollCount}] Error: ${message}`);
1414
1448
 
1415
1449
  await ctx.runMutation(api.workflows.lifecycleSandbox.updateSessionStatus, {
1416
1450
  sessionId: args.sessionId,
@@ -1434,6 +1468,7 @@ export const pollSandboxCompletion = internalAction({
1434
1468
  export const cancelSession = action({
1435
1469
  args: { sessionId: v.id("agentSessions") },
1436
1470
  handler: async (ctx, args) => {
1471
+ console.warn("[DEPRECATED] lifecycleSandbox.cancelSession is deprecated. Use sandboxConvex.cancelSession instead.");
1437
1472
  const session = await ctx.runQuery(api.workflows.lifecycleSandbox.getSession, { sessionId: args.sessionId });
1438
1473
  if (!session) throw new Error("Session not found");
1439
1474
 
@@ -11,41 +11,30 @@ import { httpAction } from "./_generated/server";
11
11
  const http = httpRouter();
12
12
 
13
13
  /**
14
- * Metrics endpoint - returns sandbox health and resource usage.
14
+ * Sandbox metrics endpoint - returns sandbox health and resource usage.
15
15
  * Used by pool health checks and Claude Code observability.
16
16
  *
17
- * GET /metrics
17
+ * Note: /metrics is reserved by Convex backend for Prometheus metrics.
18
+ * Use /sandbox-metrics to avoid collision.
19
+ *
20
+ * GET /sandbox-metrics
18
21
  *
19
22
  * Response:
20
23
  * {
21
- * uptime: number, // Process uptime in seconds
22
- * cpu: { usage: number, cores: number },
23
- * memory: { total, free, used, heapUsed, heapTotal },
24
+ * status: string,
24
25
  * timestamp: number
25
26
  * }
26
27
  */
27
28
  http.route({
28
- path: "/metrics",
29
+ path: "/sandbox-metrics",
29
30
  method: "GET",
30
31
  handler: httpAction(async () => {
31
- // Dynamic imports for Node.js APIs (required for Convex bundling)
32
- const os = await import("os");
33
-
32
+ // Convex V8 runtime doesn't have access to Node.js APIs like 'os'
33
+ // Return basic metrics that are available
34
34
  const metrics = {
35
- uptime: process.uptime(),
36
- cpu: {
37
- usage: os.loadavg()[0], // 1-minute load average
38
- cores: os.cpus().length,
39
- },
40
- memory: {
41
- total: os.totalmem(),
42
- free: os.freemem(),
43
- used: os.totalmem() - os.freemem(),
44
- heapUsed: process.memoryUsage().heapUsed,
45
- heapTotal: process.memoryUsage().heapTotal,
46
- },
35
+ status: "running",
47
36
  services: {
48
- convex: "running", // We're responding, so Convex is up
37
+ convex: "running",
49
38
  },
50
39
  timestamp: Date.now(),
51
40
  };
@@ -64,10 +53,10 @@ http.route({
64
53
  * Health check endpoint - simple ping for pool management.
65
54
  * Used by E2B pool to verify sandbox is responsive.
66
55
  *
67
- * GET /health
56
+ * GET /sandbox-health
68
57
  */
69
58
  http.route({
70
- path: "/health",
59
+ path: "/sandbox-health",
71
60
  method: "GET",
72
61
  handler: httpAction(async () => {
73
62
  return new Response(JSON.stringify({ status: "ok", timestamp: Date.now() }), {
@@ -81,21 +70,18 @@ http.route({
81
70
  * Version endpoint - returns SDK version for debugging.
82
71
  * Used by pool health checks.
83
72
  *
84
- * GET /version
73
+ * GET /sandbox-version
74
+ *
75
+ * Note: Version is hardcoded since Convex V8 runtime doesn't have
76
+ * access to Node.js fs APIs. Update this when publishing new versions.
85
77
  */
86
78
  http.route({
87
- path: "/version",
79
+ path: "/sandbox-version",
88
80
  method: "GET",
89
81
  handler: httpAction(async () => {
90
- // Read version from package.json at runtime
91
- const fs = await import("fs/promises");
92
- let version = "unknown";
93
- try {
94
- const pkg = JSON.parse(await fs.readFile("/home/user/lakitu/package.json", "utf-8"));
95
- version = pkg.version || "unknown";
96
- } catch {
97
- // Fallback if file not found
98
- }
82
+ // Convex V8 runtime can't read files directly
83
+ // Version is set at build time
84
+ const version = "0.1.66"; // UPDATE THIS ON RELEASE
99
85
 
100
86
  return new Response(JSON.stringify({ version, timestamp: Date.now() }), {
101
87
  status: 200,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lakitu/sdk",
3
- "version": "0.1.65",
3
+ "version": "0.1.67",
4
4
  "description": "Self-hosted AI agent framework for Convex + E2B with code execution",
5
5
  "type": "module",
6
6
  "main": "./dist/sdk/index.js",
package/template/build.ts CHANGED
@@ -76,7 +76,7 @@ function generatePipInstall(packages: string[]): string {
76
76
  */
77
77
  function generateNpmInstall(packages: string[]): string {
78
78
  if (packages.length === 0) return "";
79
- return `npm install -g ${packages.join(" ")}`;
79
+ return `sudo npm install -g ${packages.join(" ")}`;
80
80
  }
81
81
 
82
82
  async function getApiKey(): Promise<string> {