@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.
- package/convex/cloud/workflows/lifecycleSandbox.ts +116 -81
- package/convex/sandbox/http.ts +21 -35
- package/package.json +1 -1
- package/template/build.ts +1 -1
|
@@ -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(
|
|
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
|
|
91
|
-
"
|
|
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 = "
|
|
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("
|
|
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:
|
|
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:
|
|
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]
|
|
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]
|
|
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]
|
|
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]
|
|
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]
|
|
663
|
+
console.log(`[DEPRECATED:Sandbox] Beads state restored`);
|
|
630
664
|
}
|
|
631
665
|
} catch (e) {
|
|
632
|
-
console.warn(`[Sandbox]
|
|
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
|
|
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]
|
|
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(
|
|
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(
|
|
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]
|
|
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]
|
|
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}]
|
|
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}]
|
|
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(
|
|
1084
|
+
logsToAdd.push(`[DEPRECATED] Saving artifact...`);
|
|
1051
1085
|
} else if (rawName.includes("completeStage")) {
|
|
1052
|
-
logsToAdd.push(
|
|
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(
|
|
1089
|
+
logsToAdd.push(`[DEPRECATED] ${action} task...`);
|
|
1056
1090
|
} else if (state === "calling" || state === "pending") {
|
|
1057
|
-
logsToAdd.push(
|
|
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(
|
|
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(
|
|
1106
|
+
logsToAdd.push(`[DEPRECATED] Saved: ${name}`);
|
|
1073
1107
|
} else if (rawName.includes("completeStage")) {
|
|
1074
|
-
logsToAdd.push(
|
|
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(
|
|
1112
|
+
logsToAdd.push(`[DEPRECATED] Created: ${title}`);
|
|
1079
1113
|
} else if (rawName.includes("beads.close")) {
|
|
1080
|
-
logsToAdd.push(
|
|
1114
|
+
logsToAdd.push(`[DEPRECATED] Task completed`);
|
|
1081
1115
|
} else {
|
|
1082
1116
|
// Generic tool completion
|
|
1083
|
-
logsToAdd.push(
|
|
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}]
|
|
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}]
|
|
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}]
|
|
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}]
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
1307
|
-
console.log(
|
|
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(
|
|
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: [
|
|
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]
|
|
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]
|
|
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}]
|
|
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
|
|
package/convex/sandbox/http.ts
CHANGED
|
@@ -11,41 +11,30 @@ import { httpAction } from "./_generated/server";
|
|
|
11
11
|
const http = httpRouter();
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
|
-
*
|
|
14
|
+
* Sandbox metrics endpoint - returns sandbox health and resource usage.
|
|
15
15
|
* Used by pool health checks and Claude Code observability.
|
|
16
16
|
*
|
|
17
|
-
*
|
|
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
|
-
*
|
|
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
|
-
//
|
|
32
|
-
|
|
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
|
-
|
|
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",
|
|
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
|
-
//
|
|
91
|
-
|
|
92
|
-
|
|
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
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> {
|