@vm0/runner 3.0.5 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.js +205 -172
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -192,10 +192,11 @@ async function completeJob(apiUrl, context, exitCode, error) {
|
|
|
192
192
|
import path4 from "path";
|
|
193
193
|
|
|
194
194
|
// src/lib/firecracker/vm.ts
|
|
195
|
-
import {
|
|
195
|
+
import { exec as exec3, spawn } from "child_process";
|
|
196
196
|
import fs3 from "fs";
|
|
197
197
|
import path2 from "path";
|
|
198
198
|
import readline from "readline";
|
|
199
|
+
import { promisify as promisify3 } from "util";
|
|
199
200
|
|
|
200
201
|
// src/lib/firecracker/client.ts
|
|
201
202
|
import http from "http";
|
|
@@ -706,23 +707,24 @@ async function clearStaleIptablesRulesForIP(ip) {
|
|
|
706
707
|
} catch {
|
|
707
708
|
}
|
|
708
709
|
}
|
|
709
|
-
async function createTapDevice(vmId) {
|
|
710
|
+
async function createTapDevice(vmId, logger) {
|
|
711
|
+
const log = logger ?? console.log;
|
|
710
712
|
const tapDevice = `tap${vmId.substring(0, 8)}`;
|
|
711
713
|
const guestMac = generateMacAddress(vmId);
|
|
712
714
|
const guestIp = await allocateIP(vmId);
|
|
715
|
+
log(`[VM ${vmId}] IP allocated: ${guestIp}`);
|
|
713
716
|
await clearStaleIptablesRulesForIP(guestIp);
|
|
714
|
-
|
|
715
|
-
await setupBridge();
|
|
717
|
+
log(`[VM ${vmId}] Stale iptables cleared`);
|
|
716
718
|
if (await tapDeviceExists(tapDevice)) {
|
|
717
|
-
|
|
719
|
+
log(`[VM ${vmId}] TAP device ${tapDevice} already exists, deleting...`);
|
|
718
720
|
await deleteTapDevice(tapDevice);
|
|
719
721
|
}
|
|
720
722
|
await execCommand(`ip tuntap add ${tapDevice} mode tap`);
|
|
723
|
+
log(`[VM ${vmId}] TAP device created`);
|
|
721
724
|
await execCommand(`ip link set ${tapDevice} master ${BRIDGE_NAME2}`);
|
|
725
|
+
log(`[VM ${vmId}] TAP added to bridge`);
|
|
722
726
|
await execCommand(`ip link set ${tapDevice} up`);
|
|
723
|
-
|
|
724
|
-
`TAP ${tapDevice} created: guest MAC ${guestMac}, guest IP ${guestIp}`
|
|
725
|
-
);
|
|
727
|
+
log(`[VM ${vmId}] TAP created: ${tapDevice}, MAC ${guestMac}, IP ${guestIp}`);
|
|
726
728
|
return {
|
|
727
729
|
tapDevice,
|
|
728
730
|
guestMac,
|
|
@@ -959,6 +961,7 @@ async function cleanupOrphanedProxyRules(runnerName) {
|
|
|
959
961
|
}
|
|
960
962
|
|
|
961
963
|
// src/lib/firecracker/vm.ts
|
|
964
|
+
var execAsync3 = promisify3(exec3);
|
|
962
965
|
var FirecrackerVM = class {
|
|
963
966
|
config;
|
|
964
967
|
process = null;
|
|
@@ -978,6 +981,9 @@ var FirecrackerVM = class {
|
|
|
978
981
|
this.vmOverlayPath = path2.join(this.workDir, "overlay.ext4");
|
|
979
982
|
this.vsockPath = path2.join(this.workDir, "vsock.sock");
|
|
980
983
|
}
|
|
984
|
+
log(msg) {
|
|
985
|
+
(this.config.logger ?? console.log)(msg);
|
|
986
|
+
}
|
|
981
987
|
/**
|
|
982
988
|
* Get current VM state
|
|
983
989
|
*/
|
|
@@ -1021,15 +1027,21 @@ var FirecrackerVM = class {
|
|
|
1021
1027
|
if (fs3.existsSync(this.socketPath)) {
|
|
1022
1028
|
fs3.unlinkSync(this.socketPath);
|
|
1023
1029
|
}
|
|
1024
|
-
|
|
1025
|
-
const
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1030
|
+
this.log(`[VM ${this.config.vmId}] Setting up overlay and network...`);
|
|
1031
|
+
const createOverlay = async () => {
|
|
1032
|
+
const overlaySize = 2 * 1024 * 1024 * 1024;
|
|
1033
|
+
const fd = fs3.openSync(this.vmOverlayPath, "w");
|
|
1034
|
+
fs3.ftruncateSync(fd, overlaySize);
|
|
1035
|
+
fs3.closeSync(fd);
|
|
1036
|
+
await execAsync3(`mkfs.ext4 -F -q "${this.vmOverlayPath}"`);
|
|
1037
|
+
this.log(`[VM ${this.config.vmId}] Overlay created`);
|
|
1038
|
+
};
|
|
1039
|
+
const [, networkConfig] = await Promise.all([
|
|
1040
|
+
createOverlay(),
|
|
1041
|
+
createTapDevice(this.config.vmId, this.log.bind(this))
|
|
1042
|
+
]);
|
|
1043
|
+
this.networkConfig = networkConfig;
|
|
1044
|
+
this.log(`[VM ${this.config.vmId}] Starting Firecracker...`);
|
|
1033
1045
|
this.process = spawn(
|
|
1034
1046
|
this.config.firecrackerBinary,
|
|
1035
1047
|
["--api-sock", this.socketPath],
|
|
@@ -1040,11 +1052,11 @@ var FirecrackerVM = class {
|
|
|
1040
1052
|
}
|
|
1041
1053
|
);
|
|
1042
1054
|
this.process.on("error", (err) => {
|
|
1043
|
-
|
|
1055
|
+
this.log(`[VM ${this.config.vmId}] Firecracker error: ${err}`);
|
|
1044
1056
|
this.state = "error";
|
|
1045
1057
|
});
|
|
1046
1058
|
this.process.on("exit", (code, signal) => {
|
|
1047
|
-
|
|
1059
|
+
this.log(
|
|
1048
1060
|
`[VM ${this.config.vmId}] Firecracker exited: code=${code}, signal=${signal}`
|
|
1049
1061
|
);
|
|
1050
1062
|
if (this.state !== "stopped") {
|
|
@@ -1057,7 +1069,7 @@ var FirecrackerVM = class {
|
|
|
1057
1069
|
});
|
|
1058
1070
|
stdoutRL.on("line", (line) => {
|
|
1059
1071
|
if (line.trim()) {
|
|
1060
|
-
|
|
1072
|
+
this.log(`[VM ${this.config.vmId}] ${line}`);
|
|
1061
1073
|
}
|
|
1062
1074
|
});
|
|
1063
1075
|
}
|
|
@@ -1067,19 +1079,19 @@ var FirecrackerVM = class {
|
|
|
1067
1079
|
});
|
|
1068
1080
|
stderrRL.on("line", (line) => {
|
|
1069
1081
|
if (line.trim()) {
|
|
1070
|
-
|
|
1082
|
+
this.log(`[VM ${this.config.vmId}] stderr: ${line}`);
|
|
1071
1083
|
}
|
|
1072
1084
|
});
|
|
1073
1085
|
}
|
|
1074
1086
|
this.client = new FirecrackerClient(this.socketPath);
|
|
1075
|
-
|
|
1087
|
+
this.log(`[VM ${this.config.vmId}] Waiting for API...`);
|
|
1076
1088
|
await this.client.waitUntilReady(1e4, 100);
|
|
1077
1089
|
this.state = "configuring";
|
|
1078
1090
|
await this.configure();
|
|
1079
|
-
|
|
1091
|
+
this.log(`[VM ${this.config.vmId}] Booting...`);
|
|
1080
1092
|
await this.client.start();
|
|
1081
1093
|
this.state = "running";
|
|
1082
|
-
|
|
1094
|
+
this.log(
|
|
1083
1095
|
`[VM ${this.config.vmId}] Running at ${this.networkConfig.guestIp}`
|
|
1084
1096
|
);
|
|
1085
1097
|
} catch (error) {
|
|
@@ -1095,7 +1107,7 @@ var FirecrackerVM = class {
|
|
|
1095
1107
|
if (!this.client || !this.networkConfig) {
|
|
1096
1108
|
throw new Error("VM not properly initialized");
|
|
1097
1109
|
}
|
|
1098
|
-
|
|
1110
|
+
this.log(
|
|
1099
1111
|
`[VM ${this.config.vmId}] Configuring: ${this.config.vcpus} vCPUs, ${this.config.memoryMb}MB RAM`
|
|
1100
1112
|
);
|
|
1101
1113
|
await this.client.setMachineConfig({
|
|
@@ -1103,29 +1115,27 @@ var FirecrackerVM = class {
|
|
|
1103
1115
|
mem_size_mib: this.config.memoryMb
|
|
1104
1116
|
});
|
|
1105
1117
|
const networkBootArgs = generateNetworkBootArgs(this.networkConfig);
|
|
1106
|
-
const bootArgs = `console=ttyS0 reboot=k panic=1 pci=off nomodules random.trust_cpu=on quiet loglevel=0 nokaslr audit=0 numa=off mitigations=off noresume init=/sbin/
|
|
1107
|
-
|
|
1118
|
+
const bootArgs = `console=ttyS0 reboot=k panic=1 pci=off nomodules random.trust_cpu=on quiet loglevel=0 nokaslr audit=0 numa=off mitigations=off noresume init=/sbin/vm-init ${networkBootArgs}`;
|
|
1119
|
+
this.log(`[VM ${this.config.vmId}] Boot args: ${bootArgs}`);
|
|
1108
1120
|
await this.client.setBootSource({
|
|
1109
1121
|
kernel_image_path: this.config.kernelPath,
|
|
1110
1122
|
boot_args: bootArgs
|
|
1111
1123
|
});
|
|
1112
|
-
|
|
1113
|
-
`[VM ${this.config.vmId}] Base rootfs: ${this.config.rootfsPath}`
|
|
1114
|
-
);
|
|
1124
|
+
this.log(`[VM ${this.config.vmId}] Base rootfs: ${this.config.rootfsPath}`);
|
|
1115
1125
|
await this.client.setDrive({
|
|
1116
1126
|
drive_id: "rootfs",
|
|
1117
1127
|
path_on_host: this.config.rootfsPath,
|
|
1118
1128
|
is_root_device: true,
|
|
1119
1129
|
is_read_only: true
|
|
1120
1130
|
});
|
|
1121
|
-
|
|
1131
|
+
this.log(`[VM ${this.config.vmId}] Overlay: ${this.vmOverlayPath}`);
|
|
1122
1132
|
await this.client.setDrive({
|
|
1123
1133
|
drive_id: "overlay",
|
|
1124
1134
|
path_on_host: this.vmOverlayPath,
|
|
1125
1135
|
is_root_device: false,
|
|
1126
1136
|
is_read_only: false
|
|
1127
1137
|
});
|
|
1128
|
-
|
|
1138
|
+
this.log(
|
|
1129
1139
|
`[VM ${this.config.vmId}] Network: ${this.networkConfig.tapDevice}`
|
|
1130
1140
|
);
|
|
1131
1141
|
await this.client.setNetworkInterface({
|
|
@@ -1133,7 +1143,7 @@ var FirecrackerVM = class {
|
|
|
1133
1143
|
guest_mac: this.networkConfig.guestMac,
|
|
1134
1144
|
host_dev_name: this.networkConfig.tapDevice
|
|
1135
1145
|
});
|
|
1136
|
-
|
|
1146
|
+
this.log(`[VM ${this.config.vmId}] Vsock: ${this.vsockPath}`);
|
|
1137
1147
|
await this.client.setVsock({
|
|
1138
1148
|
vsock_id: "vsock0",
|
|
1139
1149
|
guest_cid: 3,
|
|
@@ -1145,17 +1155,16 @@ var FirecrackerVM = class {
|
|
|
1145
1155
|
*/
|
|
1146
1156
|
async stop() {
|
|
1147
1157
|
if (this.state !== "running") {
|
|
1148
|
-
|
|
1158
|
+
this.log(`[VM ${this.config.vmId}] Not running, state: ${this.state}`);
|
|
1149
1159
|
return;
|
|
1150
1160
|
}
|
|
1151
1161
|
this.state = "stopping";
|
|
1152
|
-
|
|
1162
|
+
this.log(`[VM ${this.config.vmId}] Stopping...`);
|
|
1153
1163
|
try {
|
|
1154
1164
|
if (this.client) {
|
|
1155
1165
|
await this.client.sendCtrlAltDel().catch((error) => {
|
|
1156
|
-
|
|
1157
|
-
`[VM ${this.config.vmId}] Graceful shutdown signal failed (VM may already be stopping)
|
|
1158
|
-
error instanceof Error ? error.message : error
|
|
1166
|
+
this.log(
|
|
1167
|
+
`[VM ${this.config.vmId}] Graceful shutdown signal failed (VM may already be stopping): ${error instanceof Error ? error.message : error}`
|
|
1159
1168
|
);
|
|
1160
1169
|
});
|
|
1161
1170
|
}
|
|
@@ -1167,7 +1176,7 @@ var FirecrackerVM = class {
|
|
|
1167
1176
|
* Force kill the VM
|
|
1168
1177
|
*/
|
|
1169
1178
|
async kill() {
|
|
1170
|
-
|
|
1179
|
+
this.log(`[VM ${this.config.vmId}] Force killing...`);
|
|
1171
1180
|
await this.cleanup();
|
|
1172
1181
|
}
|
|
1173
1182
|
/**
|
|
@@ -1192,7 +1201,7 @@ var FirecrackerVM = class {
|
|
|
1192
1201
|
}
|
|
1193
1202
|
this.client = null;
|
|
1194
1203
|
this.state = "stopped";
|
|
1195
|
-
|
|
1204
|
+
this.log(`[VM ${this.config.vmId}] Stopped`);
|
|
1196
1205
|
}
|
|
1197
1206
|
/**
|
|
1198
1207
|
* Wait for the VM process to exit
|
|
@@ -1224,16 +1233,79 @@ var FirecrackerVM = class {
|
|
|
1224
1233
|
// src/lib/firecracker/vsock.ts
|
|
1225
1234
|
import * as net from "net";
|
|
1226
1235
|
import * as fs4 from "fs";
|
|
1227
|
-
import * as crypto from "crypto";
|
|
1228
1236
|
var VSOCK_PORT = 1e3;
|
|
1229
1237
|
var HEADER_SIZE = 4;
|
|
1230
|
-
var MAX_MESSAGE_SIZE = 1024 * 1024;
|
|
1238
|
+
var MAX_MESSAGE_SIZE = 16 * 1024 * 1024;
|
|
1231
1239
|
var DEFAULT_EXEC_TIMEOUT_MS = 3e5;
|
|
1232
|
-
|
|
1233
|
-
|
|
1240
|
+
var MSG_READY = 0;
|
|
1241
|
+
var MSG_PING = 1;
|
|
1242
|
+
var MSG_PONG = 2;
|
|
1243
|
+
var MSG_EXEC = 3;
|
|
1244
|
+
var MSG_WRITE_FILE = 5;
|
|
1245
|
+
var MSG_ERROR = 255;
|
|
1246
|
+
var FLAG_SUDO = 1;
|
|
1247
|
+
function encode(type, seq, payload = Buffer.alloc(0)) {
|
|
1248
|
+
const body = Buffer.alloc(5 + payload.length);
|
|
1249
|
+
body.writeUInt8(type, 0);
|
|
1250
|
+
body.writeUInt32BE(seq, 1);
|
|
1251
|
+
payload.copy(body, 5);
|
|
1234
1252
|
const header = Buffer.alloc(HEADER_SIZE);
|
|
1235
|
-
header.writeUInt32BE(
|
|
1236
|
-
return Buffer.concat([header,
|
|
1253
|
+
header.writeUInt32BE(body.length, 0);
|
|
1254
|
+
return Buffer.concat([header, body]);
|
|
1255
|
+
}
|
|
1256
|
+
function encodeExecPayload(command, timeoutMs) {
|
|
1257
|
+
const cmdBuf = Buffer.from(command, "utf-8");
|
|
1258
|
+
const payload = Buffer.alloc(8 + cmdBuf.length);
|
|
1259
|
+
payload.writeUInt32BE(timeoutMs, 0);
|
|
1260
|
+
payload.writeUInt32BE(cmdBuf.length, 4);
|
|
1261
|
+
cmdBuf.copy(payload, 8);
|
|
1262
|
+
return payload;
|
|
1263
|
+
}
|
|
1264
|
+
function encodeWriteFilePayload(path6, content, sudo) {
|
|
1265
|
+
const pathBuf = Buffer.from(path6, "utf-8");
|
|
1266
|
+
if (pathBuf.length > 65535) {
|
|
1267
|
+
throw new Error(`Path too long: ${pathBuf.length} bytes (max 65535)`);
|
|
1268
|
+
}
|
|
1269
|
+
const payload = Buffer.alloc(2 + pathBuf.length + 1 + 4 + content.length);
|
|
1270
|
+
let offset = 0;
|
|
1271
|
+
payload.writeUInt16BE(pathBuf.length, offset);
|
|
1272
|
+
offset += 2;
|
|
1273
|
+
pathBuf.copy(payload, offset);
|
|
1274
|
+
offset += pathBuf.length;
|
|
1275
|
+
payload.writeUInt8(sudo ? FLAG_SUDO : 0, offset);
|
|
1276
|
+
offset += 1;
|
|
1277
|
+
payload.writeUInt32BE(content.length, offset);
|
|
1278
|
+
offset += 4;
|
|
1279
|
+
content.copy(payload, offset);
|
|
1280
|
+
return payload;
|
|
1281
|
+
}
|
|
1282
|
+
function decodeExecResult(payload) {
|
|
1283
|
+
if (payload.length < 8) {
|
|
1284
|
+
return { exitCode: 1, stdout: "", stderr: "Invalid exec_result payload" };
|
|
1285
|
+
}
|
|
1286
|
+
const exitCode = payload.readInt32BE(0);
|
|
1287
|
+
const stdoutLen = payload.readUInt32BE(4);
|
|
1288
|
+
const stdout = payload.subarray(8, 8 + stdoutLen).toString("utf-8");
|
|
1289
|
+
const stderrLenOffset = 8 + stdoutLen;
|
|
1290
|
+
const stderrLen = payload.readUInt32BE(stderrLenOffset);
|
|
1291
|
+
const stderr = payload.subarray(stderrLenOffset + 4, stderrLenOffset + 4 + stderrLen).toString("utf-8");
|
|
1292
|
+
return { exitCode, stdout, stderr };
|
|
1293
|
+
}
|
|
1294
|
+
function decodeWriteFileResult(payload) {
|
|
1295
|
+
if (payload.length < 3) {
|
|
1296
|
+
return { success: false, error: "Invalid write_file_result payload" };
|
|
1297
|
+
}
|
|
1298
|
+
const success = payload.readUInt8(0) === 1;
|
|
1299
|
+
const errorLen = payload.readUInt16BE(1);
|
|
1300
|
+
const error = payload.subarray(3, 3 + errorLen).toString("utf-8");
|
|
1301
|
+
return { success, error };
|
|
1302
|
+
}
|
|
1303
|
+
function decodeError(payload) {
|
|
1304
|
+
if (payload.length < 2) {
|
|
1305
|
+
return "Invalid error payload";
|
|
1306
|
+
}
|
|
1307
|
+
const errorLen = payload.readUInt16BE(0);
|
|
1308
|
+
return payload.subarray(2, 2 + errorLen).toString("utf-8");
|
|
1237
1309
|
}
|
|
1238
1310
|
var Decoder = class {
|
|
1239
1311
|
buf = Buffer.alloc(0);
|
|
@@ -1241,12 +1313,20 @@ var Decoder = class {
|
|
|
1241
1313
|
this.buf = Buffer.concat([this.buf, data]);
|
|
1242
1314
|
const messages = [];
|
|
1243
1315
|
while (this.buf.length >= HEADER_SIZE) {
|
|
1244
|
-
const
|
|
1245
|
-
if (
|
|
1246
|
-
|
|
1316
|
+
const length = this.buf.readUInt32BE(0);
|
|
1317
|
+
if (length > MAX_MESSAGE_SIZE) {
|
|
1318
|
+
throw new Error(`Message too large: ${length}`);
|
|
1319
|
+
}
|
|
1320
|
+
if (length < 5) {
|
|
1321
|
+
throw new Error(`Message too small: ${length}`);
|
|
1322
|
+
}
|
|
1323
|
+
const total = HEADER_SIZE + length;
|
|
1247
1324
|
if (this.buf.length < total) break;
|
|
1248
|
-
const
|
|
1249
|
-
|
|
1325
|
+
const body = this.buf.subarray(HEADER_SIZE, total);
|
|
1326
|
+
const type = body.readUInt8(0);
|
|
1327
|
+
const seq = body.readUInt32BE(1);
|
|
1328
|
+
const payload = body.subarray(5);
|
|
1329
|
+
messages.push({ type, seq, payload });
|
|
1250
1330
|
this.buf = this.buf.subarray(total);
|
|
1251
1331
|
}
|
|
1252
1332
|
return messages;
|
|
@@ -1256,18 +1336,28 @@ var VsockClient = class {
|
|
|
1256
1336
|
vsockPath;
|
|
1257
1337
|
socket = null;
|
|
1258
1338
|
connected = false;
|
|
1339
|
+
nextSeq = 1;
|
|
1259
1340
|
pendingRequests = /* @__PURE__ */ new Map();
|
|
1260
1341
|
constructor(vsockPath) {
|
|
1261
1342
|
this.vsockPath = vsockPath;
|
|
1262
1343
|
}
|
|
1344
|
+
/**
|
|
1345
|
+
* Get next sequence number
|
|
1346
|
+
*/
|
|
1347
|
+
getNextSeq() {
|
|
1348
|
+
const seq = this.nextSeq;
|
|
1349
|
+
this.nextSeq = this.nextSeq + 1 & 4294967295;
|
|
1350
|
+
if (this.nextSeq === 0) this.nextSeq = 1;
|
|
1351
|
+
return seq;
|
|
1352
|
+
}
|
|
1263
1353
|
/**
|
|
1264
1354
|
* Handle incoming message and route to pending request
|
|
1265
1355
|
*/
|
|
1266
1356
|
handleMessage(msg) {
|
|
1267
|
-
const pending = this.pendingRequests.get(msg.
|
|
1357
|
+
const pending = this.pendingRequests.get(msg.seq);
|
|
1268
1358
|
if (pending) {
|
|
1269
1359
|
clearTimeout(pending.timeout);
|
|
1270
|
-
this.pendingRequests.delete(msg.
|
|
1360
|
+
this.pendingRequests.delete(msg.seq);
|
|
1271
1361
|
pending.resolve(msg);
|
|
1272
1362
|
}
|
|
1273
1363
|
}
|
|
@@ -1278,19 +1368,14 @@ var VsockClient = class {
|
|
|
1278
1368
|
if (!this.connected || !this.socket) {
|
|
1279
1369
|
throw new Error("Not connected - call waitForGuestConnection() first");
|
|
1280
1370
|
}
|
|
1281
|
-
const
|
|
1282
|
-
const msg = { type, id, payload };
|
|
1371
|
+
const seq = this.getNextSeq();
|
|
1283
1372
|
return new Promise((resolve, reject) => {
|
|
1284
1373
|
const timeout = setTimeout(() => {
|
|
1285
|
-
this.pendingRequests.delete(
|
|
1286
|
-
reject(new Error(`Request timeout: ${type}`));
|
|
1374
|
+
this.pendingRequests.delete(seq);
|
|
1375
|
+
reject(new Error(`Request timeout: type=0x${type.toString(16)}`));
|
|
1287
1376
|
}, timeoutMs);
|
|
1288
|
-
this.pendingRequests.set(
|
|
1289
|
-
|
|
1290
|
-
reject,
|
|
1291
|
-
timeout
|
|
1292
|
-
});
|
|
1293
|
-
this.socket.write(encode(msg));
|
|
1377
|
+
this.pendingRequests.set(seq, { resolve, reject, timeout });
|
|
1378
|
+
this.socket.write(encode(type, seq, payload));
|
|
1294
1379
|
});
|
|
1295
1380
|
}
|
|
1296
1381
|
/**
|
|
@@ -1299,25 +1384,21 @@ var VsockClient = class {
|
|
|
1299
1384
|
async exec(command, timeoutMs) {
|
|
1300
1385
|
const actualTimeout = timeoutMs ?? DEFAULT_EXEC_TIMEOUT_MS;
|
|
1301
1386
|
try {
|
|
1387
|
+
const payload = encodeExecPayload(command, actualTimeout);
|
|
1302
1388
|
const response = await this.request(
|
|
1303
|
-
|
|
1304
|
-
|
|
1389
|
+
MSG_EXEC,
|
|
1390
|
+
payload,
|
|
1305
1391
|
actualTimeout + 5e3
|
|
1306
1392
|
// Add buffer for network latency
|
|
1307
1393
|
);
|
|
1308
|
-
if (response.type ===
|
|
1309
|
-
const errorPayload = response.payload;
|
|
1394
|
+
if (response.type === MSG_ERROR) {
|
|
1310
1395
|
return {
|
|
1311
1396
|
exitCode: 1,
|
|
1312
1397
|
stdout: "",
|
|
1313
|
-
stderr:
|
|
1398
|
+
stderr: decodeError(response.payload)
|
|
1314
1399
|
};
|
|
1315
1400
|
}
|
|
1316
|
-
return
|
|
1317
|
-
exitCode: response.payload.exitCode,
|
|
1318
|
-
stdout: response.payload.stdout,
|
|
1319
|
-
stderr: response.payload.stderr
|
|
1320
|
-
};
|
|
1401
|
+
return decodeExecResult(response.payload);
|
|
1321
1402
|
} catch (e) {
|
|
1322
1403
|
return {
|
|
1323
1404
|
exitCode: 1,
|
|
@@ -1341,42 +1422,38 @@ var VsockClient = class {
|
|
|
1341
1422
|
/**
|
|
1342
1423
|
* Write content to a file on the remote VM
|
|
1343
1424
|
*/
|
|
1344
|
-
async
|
|
1345
|
-
const
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1425
|
+
async writeFileInternal(remotePath, content, sudo) {
|
|
1426
|
+
const contentBuf = Buffer.from(content, "utf-8");
|
|
1427
|
+
if (contentBuf.length > MAX_MESSAGE_SIZE - 1024) {
|
|
1428
|
+
throw new Error(
|
|
1429
|
+
`Content too large: ${contentBuf.length} bytes (max ${MAX_MESSAGE_SIZE - 1024})`
|
|
1430
|
+
);
|
|
1431
|
+
}
|
|
1432
|
+
const payload = encodeWriteFilePayload(remotePath, contentBuf, sudo);
|
|
1433
|
+
const response = await this.request(
|
|
1434
|
+
MSG_WRITE_FILE,
|
|
1435
|
+
payload,
|
|
1436
|
+
DEFAULT_EXEC_TIMEOUT_MS
|
|
1437
|
+
);
|
|
1438
|
+
if (response.type === MSG_ERROR) {
|
|
1439
|
+
throw new Error(`Write file failed: ${decodeError(response.payload)}`);
|
|
1440
|
+
}
|
|
1441
|
+
const result = decodeWriteFileResult(response.payload);
|
|
1442
|
+
if (!result.success) {
|
|
1443
|
+
throw new Error(`Write file failed: ${result.error}`);
|
|
1358
1444
|
}
|
|
1359
1445
|
}
|
|
1446
|
+
/**
|
|
1447
|
+
* Write content to a file on the remote VM
|
|
1448
|
+
*/
|
|
1449
|
+
async writeFile(remotePath, content) {
|
|
1450
|
+
return this.writeFileInternal(remotePath, content, false);
|
|
1451
|
+
}
|
|
1360
1452
|
/**
|
|
1361
1453
|
* Write content to a file on the remote VM using sudo
|
|
1362
1454
|
*/
|
|
1363
1455
|
async writeFileWithSudo(remotePath, content) {
|
|
1364
|
-
|
|
1365
|
-
const maxChunkSize = 65e3;
|
|
1366
|
-
if (encoded.length <= maxChunkSize) {
|
|
1367
|
-
await this.execOrThrow(
|
|
1368
|
-
`echo '${encoded}' | base64 -d | sudo tee '${remotePath}' > /dev/null`
|
|
1369
|
-
);
|
|
1370
|
-
} else {
|
|
1371
|
-
await this.execOrThrow(`sudo rm -f '${remotePath}'`);
|
|
1372
|
-
for (let i = 0; i < encoded.length; i += maxChunkSize) {
|
|
1373
|
-
const chunk = encoded.slice(i, i + maxChunkSize);
|
|
1374
|
-
const teeFlag = i === 0 ? "" : "-a";
|
|
1375
|
-
await this.execOrThrow(
|
|
1376
|
-
`echo '${chunk}' | base64 -d | sudo tee ${teeFlag} '${remotePath}' > /dev/null`
|
|
1377
|
-
);
|
|
1378
|
-
}
|
|
1379
|
-
}
|
|
1456
|
+
return this.writeFileInternal(remotePath, content, true);
|
|
1380
1457
|
}
|
|
1381
1458
|
/**
|
|
1382
1459
|
* Read a file from the remote VM
|
|
@@ -1457,15 +1534,15 @@ var VsockClient = class {
|
|
|
1457
1534
|
State2[State2["Connected"] = 2] = "Connected";
|
|
1458
1535
|
})(State || (State = {}));
|
|
1459
1536
|
let state = 0 /* WaitingForReady */;
|
|
1460
|
-
let
|
|
1537
|
+
let pingSeq = 0;
|
|
1461
1538
|
socket.on("data", (data) => {
|
|
1462
1539
|
try {
|
|
1463
1540
|
for (const msg of decoder.decode(data)) {
|
|
1464
|
-
if (state === 0 /* WaitingForReady */ && msg.type ===
|
|
1541
|
+
if (state === 0 /* WaitingForReady */ && msg.type === MSG_READY) {
|
|
1465
1542
|
state = 1 /* WaitingForPong */;
|
|
1466
|
-
|
|
1467
|
-
socket.write(encode(
|
|
1468
|
-
} else if (state === 1 /* WaitingForPong */ && msg.type ===
|
|
1543
|
+
pingSeq = this.getNextSeq();
|
|
1544
|
+
socket.write(encode(MSG_PING, pingSeq));
|
|
1545
|
+
} else if (state === 1 /* WaitingForPong */ && msg.type === MSG_PONG && msg.seq === pingSeq) {
|
|
1469
1546
|
if (settled) {
|
|
1470
1547
|
socket.destroy();
|
|
1471
1548
|
return;
|
|
@@ -5558,11 +5635,6 @@ var appStringSchema = z6.string().superRefine((val, ctx) => {
|
|
|
5558
5635
|
});
|
|
5559
5636
|
var agentDefinitionSchema = z6.object({
|
|
5560
5637
|
description: z6.string().optional(),
|
|
5561
|
-
/**
|
|
5562
|
-
* @deprecated Use `apps` field instead for pre-installed tools.
|
|
5563
|
-
* This field will be removed in a future version.
|
|
5564
|
-
*/
|
|
5565
|
-
image: z6.string().optional(),
|
|
5566
5638
|
framework: z6.string().min(1, "Framework is required"),
|
|
5567
5639
|
/**
|
|
5568
5640
|
* Array of pre-installed apps/tools for the agent environment.
|
|
@@ -5572,8 +5644,6 @@ var agentDefinitionSchema = z6.object({
|
|
|
5572
5644
|
*/
|
|
5573
5645
|
apps: z6.array(appStringSchema).optional(),
|
|
5574
5646
|
volumes: z6.array(z6.string()).optional(),
|
|
5575
|
-
working_dir: z6.string().optional(),
|
|
5576
|
-
// Optional when provider supports auto-config
|
|
5577
5647
|
environment: z6.record(z6.string(), z6.string()).optional(),
|
|
5578
5648
|
/**
|
|
5579
5649
|
* Path to instructions file (e.g., AGENTS.md).
|
|
@@ -5600,7 +5670,17 @@ var agentDefinitionSchema = z6.object({
|
|
|
5600
5670
|
* Requires experimental_runner to be configured.
|
|
5601
5671
|
* When enabled, filters outbound traffic by domain/IP rules.
|
|
5602
5672
|
*/
|
|
5603
|
-
experimental_firewall: experimentalFirewallSchema.optional()
|
|
5673
|
+
experimental_firewall: experimentalFirewallSchema.optional(),
|
|
5674
|
+
/**
|
|
5675
|
+
* @deprecated Server-resolved field. User input is ignored.
|
|
5676
|
+
* @internal
|
|
5677
|
+
*/
|
|
5678
|
+
image: z6.string().optional(),
|
|
5679
|
+
/**
|
|
5680
|
+
* @deprecated Server-resolved field. User input is ignored.
|
|
5681
|
+
* @internal
|
|
5682
|
+
*/
|
|
5683
|
+
working_dir: z6.string().optional()
|
|
5604
5684
|
});
|
|
5605
5685
|
var agentComposeContentSchema = z6.object({
|
|
5606
5686
|
version: z6.string().min(1, "Version is required"),
|
|
@@ -6732,13 +6812,11 @@ var scopeResponseSchema = z14.object({
|
|
|
6732
6812
|
id: z14.string().uuid(),
|
|
6733
6813
|
slug: z14.string(),
|
|
6734
6814
|
type: scopeTypeSchema,
|
|
6735
|
-
displayName: z14.string().nullable(),
|
|
6736
6815
|
createdAt: z14.string(),
|
|
6737
6816
|
updatedAt: z14.string()
|
|
6738
6817
|
});
|
|
6739
6818
|
var createScopeRequestSchema = z14.object({
|
|
6740
|
-
slug: scopeSlugSchema
|
|
6741
|
-
displayName: z14.string().max(128).optional()
|
|
6819
|
+
slug: scopeSlugSchema
|
|
6742
6820
|
});
|
|
6743
6821
|
var updateScopeRequestSchema = z14.object({
|
|
6744
6822
|
slug: scopeSlugSchema,
|
|
@@ -7954,12 +8032,6 @@ var publicVolumeDownloadContract = c18.router({
|
|
|
7954
8032
|
}
|
|
7955
8033
|
});
|
|
7956
8034
|
|
|
7957
|
-
// ../../packages/core/src/sandbox/scripts/dist/bundled.ts
|
|
7958
|
-
var RUN_AGENT_SCRIPT = '#!/usr/bin/env node\n\n// src/sandbox/scripts/src/run-agent.ts\nimport * as fs7 from "fs";\nimport { spawn, execSync as execSync4 } from "child_process";\nimport * as readline from "readline";\n\n// src/sandbox/scripts/src/lib/common.ts\nimport * as fs from "fs";\nvar RUN_ID = process.env.VM0_RUN_ID ?? "";\nvar API_URL = process.env.VM0_API_URL ?? "";\nvar API_TOKEN = process.env.VM0_API_TOKEN ?? "";\nvar PROMPT = process.env.VM0_PROMPT ?? "";\nvar VERCEL_BYPASS = process.env.VERCEL_PROTECTION_BYPASS ?? "";\nvar RESUME_SESSION_ID = process.env.VM0_RESUME_SESSION_ID ?? "";\nvar CLI_AGENT_TYPE = process.env.CLI_AGENT_TYPE ?? "claude-code";\nvar OPENAI_MODEL = process.env.OPENAI_MODEL ?? "";\nvar WORKING_DIR = process.env.VM0_WORKING_DIR ?? "";\nvar ARTIFACT_DRIVER = process.env.VM0_ARTIFACT_DRIVER ?? "";\nvar ARTIFACT_MOUNT_PATH = process.env.VM0_ARTIFACT_MOUNT_PATH ?? "";\nvar ARTIFACT_VOLUME_NAME = process.env.VM0_ARTIFACT_VOLUME_NAME ?? "";\nvar ARTIFACT_VERSION_ID = process.env.VM0_ARTIFACT_VERSION_ID ?? "";\nvar WEBHOOK_URL = `${API_URL}/api/webhooks/agent/events`;\nvar CHECKPOINT_URL = `${API_URL}/api/webhooks/agent/checkpoints`;\nvar COMPLETE_URL = `${API_URL}/api/webhooks/agent/complete`;\nvar HEARTBEAT_URL = `${API_URL}/api/webhooks/agent/heartbeat`;\nvar TELEMETRY_URL = `${API_URL}/api/webhooks/agent/telemetry`;\nvar PROXY_URL = `${API_URL}/api/webhooks/agent/proxy`;\nvar STORAGE_PREPARE_URL = `${API_URL}/api/webhooks/agent/storages/prepare`;\nvar STORAGE_COMMIT_URL = `${API_URL}/api/webhooks/agent/storages/commit`;\nvar HEARTBEAT_INTERVAL = 60;\nvar TELEMETRY_INTERVAL = 30;\nvar HTTP_CONNECT_TIMEOUT = 10;\nvar HTTP_MAX_TIME = 30;\nvar HTTP_MAX_TIME_UPLOAD = 60;\nvar HTTP_MAX_RETRIES = 3;\nvar SESSION_ID_FILE = `/tmp/vm0-session-${RUN_ID}.txt`;\nvar SESSION_HISTORY_PATH_FILE = `/tmp/vm0-session-history-${RUN_ID}.txt`;\nvar EVENT_ERROR_FLAG = `/tmp/vm0-event-error-${RUN_ID}`;\nvar SYSTEM_LOG_FILE = `/tmp/vm0-main-${RUN_ID}.log`;\nvar AGENT_LOG_FILE = `/tmp/vm0-agent-${RUN_ID}.log`;\nvar METRICS_LOG_FILE = `/tmp/vm0-metrics-${RUN_ID}.jsonl`;\nvar NETWORK_LOG_FILE = `/tmp/vm0-network-${RUN_ID}.jsonl`;\nvar TELEMETRY_LOG_POS_FILE = `/tmp/vm0-telemetry-log-pos-${RUN_ID}.txt`;\nvar TELEMETRY_METRICS_POS_FILE = `/tmp/vm0-telemetry-metrics-pos-${RUN_ID}.txt`;\nvar TELEMETRY_NETWORK_POS_FILE = `/tmp/vm0-telemetry-network-pos-${RUN_ID}.txt`;\nvar TELEMETRY_SANDBOX_OPS_POS_FILE = `/tmp/vm0-telemetry-sandbox-ops-pos-${RUN_ID}.txt`;\nvar SANDBOX_OPS_LOG_FILE = `/tmp/vm0-sandbox-ops-${RUN_ID}.jsonl`;\nvar METRICS_INTERVAL = 5;\nfunction validateConfig() {\n if (!WORKING_DIR) {\n throw new Error("VM0_WORKING_DIR is required but not set");\n }\n return true;\n}\nfunction recordSandboxOp(actionType, durationMs, success, error) {\n const entry = {\n ts: (/* @__PURE__ */ new Date()).toISOString(),\n action_type: actionType,\n duration_ms: durationMs,\n success\n };\n if (error) {\n entry.error = error;\n }\n fs.appendFileSync(SANDBOX_OPS_LOG_FILE, JSON.stringify(entry) + "\\n");\n}\n\n// src/sandbox/scripts/src/lib/log.ts\nvar SCRIPT_NAME = process.env.LOG_SCRIPT_NAME ?? "run-agent";\nvar DEBUG_MODE = process.env.VM0_DEBUG === "1";\nfunction timestamp() {\n return (/* @__PURE__ */ new Date()).toISOString().replace(/\\.\\d{3}Z$/, "Z");\n}\nfunction logInfo(msg) {\n console.error(`[${timestamp()}] [INFO] [sandbox:${SCRIPT_NAME}] ${msg}`);\n}\nfunction logWarn(msg) {\n console.error(`[${timestamp()}] [WARN] [sandbox:${SCRIPT_NAME}] ${msg}`);\n}\nfunction logError(msg) {\n console.error(`[${timestamp()}] [ERROR] [sandbox:${SCRIPT_NAME}] ${msg}`);\n}\nfunction logDebug(msg) {\n if (DEBUG_MODE) {\n console.error(`[${timestamp()}] [DEBUG] [sandbox:${SCRIPT_NAME}] ${msg}`);\n }\n}\n\n// src/sandbox/scripts/src/lib/events.ts\nimport * as fs2 from "fs";\n\n// src/sandbox/scripts/src/lib/http-client.ts\nimport { execSync } from "child_process";\nfunction sleep(ms) {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\nasync function httpPostJson(url, data, maxRetries = HTTP_MAX_RETRIES) {\n const headers = {\n "Content-Type": "application/json",\n Authorization: `Bearer ${API_TOKEN}`\n };\n if (VERCEL_BYPASS) {\n headers["x-vercel-protection-bypass"] = VERCEL_BYPASS;\n }\n for (let attempt = 1; attempt <= maxRetries; attempt++) {\n logDebug(`HTTP POST attempt ${attempt}/${maxRetries} to ${url}`);\n try {\n const controller = new AbortController();\n const timeoutId = setTimeout(\n () => controller.abort(),\n HTTP_MAX_TIME * 1e3\n );\n const response = await fetch(url, {\n method: "POST",\n headers,\n body: JSON.stringify(data),\n signal: controller.signal\n });\n clearTimeout(timeoutId);\n if (response.ok) {\n const text = await response.text();\n if (text) {\n return JSON.parse(text);\n }\n return {};\n }\n logWarn(\n `HTTP POST failed (attempt ${attempt}/${maxRetries}): HTTP ${response.status}`\n );\n if (attempt < maxRetries) {\n await sleep(1e3);\n }\n } catch (error) {\n const errorMsg = error instanceof Error ? error.message : String(error);\n if (errorMsg.includes("abort")) {\n logWarn(`HTTP POST failed (attempt ${attempt}/${maxRetries}): Timeout`);\n } else {\n logWarn(\n `HTTP POST failed (attempt ${attempt}/${maxRetries}): ${errorMsg}`\n );\n }\n if (attempt < maxRetries) {\n await sleep(1e3);\n }\n }\n }\n logError(`HTTP POST failed after ${maxRetries} attempts to ${url}`);\n return null;\n}\nasync function httpPutPresigned(presignedUrl, filePath, contentType = "application/octet-stream", maxRetries = HTTP_MAX_RETRIES) {\n for (let attempt = 1; attempt <= maxRetries; attempt++) {\n logDebug(`HTTP PUT presigned attempt ${attempt}/${maxRetries}`);\n try {\n const curlCmd = [\n "curl",\n "-f",\n "-X",\n "PUT",\n "-H",\n `Content-Type: ${contentType}`,\n "--data-binary",\n `@${filePath}`,\n "--connect-timeout",\n String(HTTP_CONNECT_TIMEOUT),\n "--max-time",\n String(HTTP_MAX_TIME_UPLOAD),\n "--silent",\n `"${presignedUrl}"`\n ].join(" ");\n execSync(curlCmd, {\n timeout: HTTP_MAX_TIME_UPLOAD * 1e3,\n stdio: ["pipe", "pipe", "pipe"]\n });\n return true;\n } catch (error) {\n const errorMsg = error instanceof Error ? error.message : String(error);\n if (errorMsg.includes("ETIMEDOUT") || errorMsg.includes("timeout")) {\n logWarn(\n `HTTP PUT presigned failed (attempt ${attempt}/${maxRetries}): Timeout`\n );\n } else {\n logWarn(\n `HTTP PUT presigned failed (attempt ${attempt}/${maxRetries}): ${errorMsg}`\n );\n }\n if (attempt < maxRetries) {\n await sleep(1e3);\n }\n }\n }\n logError(`HTTP PUT presigned failed after ${maxRetries} attempts`);\n return false;\n}\n\n// src/sandbox/scripts/src/lib/secret-masker.ts\nvar MASK_PLACEHOLDER = "***";\nvar MIN_SECRET_LENGTH = 5;\nvar _masker = null;\nvar SecretMasker = class {\n patterns;\n /**\n * Initialize masker with secret values.\n *\n * @param secretValues - List of secret values to mask\n */\n constructor(secretValues) {\n this.patterns = /* @__PURE__ */ new Set();\n for (const secret of secretValues) {\n if (!secret || secret.length < MIN_SECRET_LENGTH) {\n continue;\n }\n this.patterns.add(secret);\n try {\n const b64 = Buffer.from(secret).toString("base64");\n if (b64.length >= MIN_SECRET_LENGTH) {\n this.patterns.add(b64);\n }\n } catch {\n }\n try {\n const urlEnc = encodeURIComponent(secret);\n if (urlEnc !== secret && urlEnc.length >= MIN_SECRET_LENGTH) {\n this.patterns.add(urlEnc);\n }\n } catch {\n }\n }\n }\n /**\n * Recursively mask all occurrences of secrets in the data.\n *\n * @param data - Data to mask (string, list, dict, or primitive)\n * @returns Masked data with the same structure\n */\n mask(data) {\n return this.deepMask(data);\n }\n deepMask(data) {\n if (typeof data === "string") {\n let result = data;\n for (const pattern of this.patterns) {\n result = result.split(pattern).join(MASK_PLACEHOLDER);\n }\n return result;\n }\n if (Array.isArray(data)) {\n return data.map((item) => this.deepMask(item));\n }\n if (data !== null && typeof data === "object") {\n const result = {};\n for (const [key, value] of Object.entries(\n data\n )) {\n result[key] = this.deepMask(value);\n }\n return result;\n }\n return data;\n }\n};\nfunction createMasker() {\n const secretValuesStr = process.env.VM0_SECRET_VALUES ?? "";\n if (!secretValuesStr) {\n return new SecretMasker([]);\n }\n const secretValues = [];\n for (const encodedValue of secretValuesStr.split(",")) {\n const trimmed = encodedValue.trim();\n if (trimmed) {\n try {\n const decoded = Buffer.from(trimmed, "base64").toString("utf-8");\n if (decoded) {\n secretValues.push(decoded);\n }\n } catch {\n }\n }\n }\n return new SecretMasker(secretValues);\n}\nfunction getMasker() {\n if (_masker === null) {\n _masker = createMasker();\n }\n return _masker;\n}\nfunction maskData(data) {\n return getMasker().mask(data);\n}\n\n// src/sandbox/scripts/src/lib/events.ts\nasync function sendEvent(event, sequenceNumber) {\n const eventType = event.type ?? "";\n const eventSubtype = event.subtype ?? "";\n let sessionId = null;\n if (CLI_AGENT_TYPE === "codex") {\n if (eventType === "thread.started") {\n sessionId = event.thread_id ?? "";\n }\n } else {\n if (eventType === "system" && eventSubtype === "init") {\n sessionId = event.session_id ?? "";\n }\n }\n if (sessionId && !fs2.existsSync(SESSION_ID_FILE)) {\n logInfo(`Captured session ID: ${sessionId}`);\n fs2.writeFileSync(SESSION_ID_FILE, sessionId);\n const homeDir = process.env.HOME ?? "/home/user";\n let sessionHistoryPath;\n if (CLI_AGENT_TYPE === "codex") {\n const codexHome = process.env.CODEX_HOME ?? `${homeDir}/.codex`;\n sessionHistoryPath = `CODEX_SEARCH:${codexHome}/sessions:${sessionId}`;\n } else {\n const projectName = WORKING_DIR.replace(/^\\//, "").replace(/\\//g, "-");\n sessionHistoryPath = `${homeDir}/.claude/projects/-${projectName}/${sessionId}.jsonl`;\n }\n fs2.writeFileSync(SESSION_HISTORY_PATH_FILE, sessionHistoryPath);\n logInfo(`Session history will be at: ${sessionHistoryPath}`);\n }\n const eventWithSequence = {\n ...event,\n sequenceNumber\n };\n const maskedEvent = maskData(eventWithSequence);\n const payload = {\n runId: RUN_ID,\n events: [maskedEvent]\n };\n const result = await httpPostJson(WEBHOOK_URL, payload);\n if (result === null) {\n logError("Failed to send event after retries");\n fs2.writeFileSync(EVENT_ERROR_FLAG, "1");\n return false;\n }\n return true;\n}\n\n// src/sandbox/scripts/src/lib/checkpoint.ts\nimport * as fs4 from "fs";\nimport * as path2 from "path";\n\n// src/sandbox/scripts/src/lib/direct-upload.ts\nimport * as fs3 from "fs";\nimport * as path from "path";\nimport * as crypto from "crypto";\nimport { execSync as execSync2 } from "child_process";\nfunction computeFileHash(filePath) {\n const hash = crypto.createHash("sha256");\n const buffer = fs3.readFileSync(filePath);\n hash.update(buffer);\n return hash.digest("hex");\n}\nfunction collectFileMetadata(dirPath) {\n const files = [];\n function walkDir(currentPath, relativePath) {\n const items = fs3.readdirSync(currentPath);\n for (const item of items) {\n if (item === ".git" || item === ".vm0") {\n continue;\n }\n const fullPath = path.join(currentPath, item);\n const relPath = relativePath ? path.join(relativePath, item) : item;\n const stat = fs3.statSync(fullPath);\n if (stat.isDirectory()) {\n walkDir(fullPath, relPath);\n } else if (stat.isFile()) {\n try {\n const fileHash = computeFileHash(fullPath);\n files.push({\n path: relPath,\n hash: fileHash,\n size: stat.size\n });\n } catch (error) {\n logWarn(`Could not process file ${relPath}: ${error}`);\n }\n }\n }\n }\n walkDir(dirPath, "");\n return files;\n}\nfunction createArchive(dirPath, tarPath) {\n try {\n execSync2(\n `tar -czf "${tarPath}" --exclude=\'.git\' --exclude=\'.vm0\' -C "${dirPath}" .`,\n { stdio: ["pipe", "pipe", "pipe"] }\n );\n return true;\n } catch (error) {\n logError(`Failed to create archive: ${error}`);\n return false;\n }\n}\nfunction createManifest(files, manifestPath) {\n try {\n const manifest = {\n version: 1,\n files,\n createdAt: (/* @__PURE__ */ new Date()).toISOString()\n };\n fs3.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));\n return true;\n } catch (error) {\n logError(`Failed to create manifest: ${error}`);\n return false;\n }\n}\nasync function createDirectUploadSnapshot(mountPath, storageName, storageType = "artifact", runId, message) {\n logInfo(\n `Creating direct upload snapshot for \'${storageName}\' (type: ${storageType})`\n );\n logInfo("Computing file hashes...");\n const hashStart = Date.now();\n const files = collectFileMetadata(mountPath);\n recordSandboxOp("artifact_hash_compute", Date.now() - hashStart, true);\n logInfo(`Found ${files.length} files`);\n if (files.length === 0) {\n logInfo("No files to upload, creating empty version");\n }\n logInfo("Calling prepare endpoint...");\n const prepareStart = Date.now();\n const preparePayload = {\n storageName,\n storageType,\n files\n };\n if (runId) {\n preparePayload.runId = runId;\n }\n const prepareResponse = await httpPostJson(\n STORAGE_PREPARE_URL,\n preparePayload\n );\n if (!prepareResponse) {\n logError("Failed to call prepare endpoint");\n recordSandboxOp("artifact_prepare_api", Date.now() - prepareStart, false);\n return null;\n }\n const versionId = prepareResponse.versionId;\n if (!versionId) {\n logError(`Invalid prepare response: ${JSON.stringify(prepareResponse)}`);\n recordSandboxOp("artifact_prepare_api", Date.now() - prepareStart, false);\n return null;\n }\n recordSandboxOp("artifact_prepare_api", Date.now() - prepareStart, true);\n if (prepareResponse.existing) {\n logInfo(`Version already exists (deduplicated): ${versionId.slice(0, 8)}`);\n logInfo("Updating HEAD pointer...");\n const commitPayload = {\n storageName,\n storageType,\n versionId,\n files\n };\n if (runId) {\n commitPayload.runId = runId;\n }\n const commitResponse = await httpPostJson(\n STORAGE_COMMIT_URL,\n commitPayload\n );\n if (!commitResponse || !commitResponse.success) {\n logError(`Failed to update HEAD: ${JSON.stringify(commitResponse)}`);\n return null;\n }\n return { versionId, deduplicated: true };\n }\n const uploads = prepareResponse.uploads;\n if (!uploads) {\n logError("No upload URLs in prepare response");\n return null;\n }\n const archiveInfo = uploads.archive;\n const manifestInfo = uploads.manifest;\n if (!archiveInfo || !manifestInfo) {\n logError("Missing archive or manifest upload info");\n return null;\n }\n const tempDir = fs3.mkdtempSync(`/tmp/direct-upload-${storageName}-`);\n try {\n logInfo("Creating archive...");\n const archiveStart = Date.now();\n const archivePath = path.join(tempDir, "archive.tar.gz");\n if (!createArchive(mountPath, archivePath)) {\n logError("Failed to create archive");\n recordSandboxOp(\n "artifact_archive_create",\n Date.now() - archiveStart,\n false\n );\n return null;\n }\n recordSandboxOp("artifact_archive_create", Date.now() - archiveStart, true);\n logInfo("Creating manifest...");\n const manifestPath = path.join(tempDir, "manifest.json");\n if (!createManifest(files, manifestPath)) {\n logError("Failed to create manifest");\n return null;\n }\n logInfo("Uploading archive to S3...");\n const s3UploadStart = Date.now();\n if (!await httpPutPresigned(\n archiveInfo.presignedUrl,\n archivePath,\n "application/gzip"\n )) {\n logError("Failed to upload archive to S3");\n recordSandboxOp("artifact_s3_upload", Date.now() - s3UploadStart, false);\n return null;\n }\n logInfo("Uploading manifest to S3...");\n if (!await httpPutPresigned(\n manifestInfo.presignedUrl,\n manifestPath,\n "application/json"\n )) {\n logError("Failed to upload manifest to S3");\n recordSandboxOp("artifact_s3_upload", Date.now() - s3UploadStart, false);\n return null;\n }\n recordSandboxOp("artifact_s3_upload", Date.now() - s3UploadStart, true);\n logInfo("Calling commit endpoint...");\n const commitStart = Date.now();\n const commitPayload = {\n storageName,\n storageType,\n versionId,\n files\n };\n if (runId) {\n commitPayload.runId = runId;\n }\n if (message) {\n commitPayload.message = message;\n }\n const commitResponse = await httpPostJson(\n STORAGE_COMMIT_URL,\n commitPayload\n );\n if (!commitResponse) {\n logError("Failed to call commit endpoint");\n recordSandboxOp("artifact_commit_api", Date.now() - commitStart, false);\n return null;\n }\n if (!commitResponse.success) {\n logError(`Commit failed: ${JSON.stringify(commitResponse)}`);\n recordSandboxOp("artifact_commit_api", Date.now() - commitStart, false);\n return null;\n }\n recordSandboxOp("artifact_commit_api", Date.now() - commitStart, true);\n logInfo(`Direct upload snapshot created: ${versionId.slice(0, 8)}`);\n return { versionId };\n } finally {\n try {\n fs3.rmSync(tempDir, { recursive: true, force: true });\n } catch {\n }\n }\n}\n\n// src/sandbox/scripts/src/lib/checkpoint.ts\nfunction findJsonlFiles(dir) {\n const files = [];\n function walk(currentDir) {\n try {\n const items = fs4.readdirSync(currentDir);\n for (const item of items) {\n const fullPath = path2.join(currentDir, item);\n const stat = fs4.statSync(fullPath);\n if (stat.isDirectory()) {\n walk(fullPath);\n } else if (item.endsWith(".jsonl")) {\n files.push(fullPath);\n }\n }\n } catch {\n }\n }\n walk(dir);\n return files;\n}\nfunction findCodexSessionFile(sessionsDir, sessionId) {\n const files = findJsonlFiles(sessionsDir);\n logInfo(`Searching for Codex session ${sessionId} in ${files.length} files`);\n for (const filepath of files) {\n const filename = path2.basename(filepath);\n if (filename.includes(sessionId) || filename.replace(/-/g, "").includes(sessionId.replace(/-/g, ""))) {\n logInfo(`Found Codex session file: ${filepath}`);\n return filepath;\n }\n }\n if (files.length > 0) {\n files.sort((a, b) => {\n const statA = fs4.statSync(a);\n const statB = fs4.statSync(b);\n return statB.mtimeMs - statA.mtimeMs;\n });\n const mostRecent = files[0] ?? null;\n if (mostRecent) {\n logInfo(\n `Session ID not found in filenames, using most recent: ${mostRecent}`\n );\n }\n return mostRecent;\n }\n return null;\n}\nasync function createCheckpoint() {\n const checkpointStart = Date.now();\n logInfo("Creating checkpoint...");\n const sessionIdStart = Date.now();\n if (!fs4.existsSync(SESSION_ID_FILE)) {\n logError("No session ID found, checkpoint creation failed");\n recordSandboxOp(\n "session_id_read",\n Date.now() - sessionIdStart,\n false,\n "Session ID file not found"\n );\n recordSandboxOp("checkpoint_total", Date.now() - checkpointStart, false);\n return false;\n }\n const cliAgentSessionId = fs4.readFileSync(SESSION_ID_FILE, "utf-8").trim();\n recordSandboxOp("session_id_read", Date.now() - sessionIdStart, true);\n const sessionHistoryStart = Date.now();\n if (!fs4.existsSync(SESSION_HISTORY_PATH_FILE)) {\n logError("No session history path found, checkpoint creation failed");\n recordSandboxOp(\n "session_history_read",\n Date.now() - sessionHistoryStart,\n false,\n "Session history path file not found"\n );\n recordSandboxOp("checkpoint_total", Date.now() - checkpointStart, false);\n return false;\n }\n const sessionHistoryPathRaw = fs4.readFileSync(SESSION_HISTORY_PATH_FILE, "utf-8").trim();\n let sessionHistoryPath;\n if (sessionHistoryPathRaw.startsWith("CODEX_SEARCH:")) {\n const parts = sessionHistoryPathRaw.split(":");\n if (parts.length !== 3) {\n logError(`Invalid Codex search marker format: ${sessionHistoryPathRaw}`);\n recordSandboxOp(\n "session_history_read",\n Date.now() - sessionHistoryStart,\n false,\n "Invalid Codex search marker"\n );\n recordSandboxOp("checkpoint_total", Date.now() - checkpointStart, false);\n return false;\n }\n const sessionsDir = parts[1] ?? "";\n const codexSessionId = parts[2] ?? "";\n logInfo(`Searching for Codex session in ${sessionsDir}`);\n const foundPath = findCodexSessionFile(sessionsDir, codexSessionId);\n if (!foundPath) {\n logError(\n `Could not find Codex session file for ${codexSessionId} in ${sessionsDir}`\n );\n recordSandboxOp(\n "session_history_read",\n Date.now() - sessionHistoryStart,\n false,\n "Codex session file not found"\n );\n recordSandboxOp("checkpoint_total", Date.now() - checkpointStart, false);\n return false;\n }\n sessionHistoryPath = foundPath;\n } else {\n sessionHistoryPath = sessionHistoryPathRaw;\n }\n if (!fs4.existsSync(sessionHistoryPath)) {\n logError(\n `Session history file not found at ${sessionHistoryPath}, checkpoint creation failed`\n );\n recordSandboxOp(\n "session_history_read",\n Date.now() - sessionHistoryStart,\n false,\n "Session history file not found"\n );\n recordSandboxOp("checkpoint_total", Date.now() - checkpointStart, false);\n return false;\n }\n let cliAgentSessionHistory;\n try {\n cliAgentSessionHistory = fs4.readFileSync(sessionHistoryPath, "utf-8");\n } catch (error) {\n logError(`Failed to read session history: ${error}`);\n recordSandboxOp(\n "session_history_read",\n Date.now() - sessionHistoryStart,\n false,\n String(error)\n );\n recordSandboxOp("checkpoint_total", Date.now() - checkpointStart, false);\n return false;\n }\n if (!cliAgentSessionHistory.trim()) {\n logError("Session history is empty, checkpoint creation failed");\n recordSandboxOp(\n "session_history_read",\n Date.now() - sessionHistoryStart,\n false,\n "Session history empty"\n );\n recordSandboxOp("checkpoint_total", Date.now() - checkpointStart, false);\n return false;\n }\n const lineCount = cliAgentSessionHistory.trim().split("\\n").length;\n logInfo(`Session history loaded (${lineCount} lines)`);\n recordSandboxOp(\n "session_history_read",\n Date.now() - sessionHistoryStart,\n true\n );\n let artifactSnapshot = null;\n if (ARTIFACT_DRIVER && ARTIFACT_VOLUME_NAME) {\n logInfo(`Processing artifact with driver: ${ARTIFACT_DRIVER}`);\n if (ARTIFACT_DRIVER !== "vas") {\n logError(\n `Unknown artifact driver: ${ARTIFACT_DRIVER} (only \'vas\' is supported)`\n );\n recordSandboxOp("checkpoint_total", Date.now() - checkpointStart, false);\n return false;\n }\n logInfo(\n `Creating VAS snapshot for artifact \'${ARTIFACT_VOLUME_NAME}\' at ${ARTIFACT_MOUNT_PATH}`\n );\n logInfo("Using direct S3 upload...");\n const snapshot = await createDirectUploadSnapshot(\n ARTIFACT_MOUNT_PATH,\n ARTIFACT_VOLUME_NAME,\n "artifact",\n RUN_ID,\n `Checkpoint from run ${RUN_ID}`\n );\n if (!snapshot) {\n logError("Failed to create VAS snapshot for artifact");\n recordSandboxOp("checkpoint_total", Date.now() - checkpointStart, false);\n return false;\n }\n const artifactVersion = snapshot.versionId;\n if (!artifactVersion) {\n logError("Failed to extract versionId from snapshot");\n recordSandboxOp("checkpoint_total", Date.now() - checkpointStart, false);\n return false;\n }\n artifactSnapshot = {\n artifactName: ARTIFACT_VOLUME_NAME,\n artifactVersion\n };\n logInfo(\n `VAS artifact snapshot created: ${ARTIFACT_VOLUME_NAME}@${artifactVersion}`\n );\n } else {\n logInfo(\n "No artifact configured, creating checkpoint without artifact snapshot"\n );\n }\n logInfo("Calling checkpoint API...");\n const checkpointPayload = {\n runId: RUN_ID,\n cliAgentType: CLI_AGENT_TYPE,\n cliAgentSessionId,\n cliAgentSessionHistory\n };\n if (artifactSnapshot) {\n checkpointPayload.artifactSnapshot = artifactSnapshot;\n }\n const apiCallStart = Date.now();\n const result = await httpPostJson(\n CHECKPOINT_URL,\n checkpointPayload\n );\n if (result && result.checkpointId) {\n const checkpointId = result.checkpointId;\n logInfo(`Checkpoint created successfully: ${checkpointId}`);\n recordSandboxOp("checkpoint_api_call", Date.now() - apiCallStart, true);\n recordSandboxOp("checkpoint_total", Date.now() - checkpointStart, true);\n return true;\n } else {\n logError(\n `Checkpoint API returned invalid response: ${JSON.stringify(result)}`\n );\n recordSandboxOp(\n "checkpoint_api_call",\n Date.now() - apiCallStart,\n false,\n "Invalid API response"\n );\n recordSandboxOp("checkpoint_total", Date.now() - checkpointStart, false);\n return false;\n }\n}\n\n// src/sandbox/scripts/src/lib/metrics.ts\nimport * as fs5 from "fs";\nimport { execSync as execSync3 } from "child_process";\nvar shutdownRequested = false;\nfunction getCpuPercent() {\n try {\n const content = fs5.readFileSync("/proc/stat", "utf-8");\n const line = content.split("\\n")[0];\n if (!line) {\n return 0;\n }\n const parts = line.split(/\\s+/);\n if (parts[0] !== "cpu") {\n return 0;\n }\n const values = parts.slice(1).map((x) => parseInt(x, 10));\n const idleVal = values[3];\n const iowaitVal = values[4];\n if (idleVal === void 0 || iowaitVal === void 0) {\n return 0;\n }\n const idle = idleVal + iowaitVal;\n const total = values.reduce((a, b) => a + b, 0);\n if (total === 0) {\n return 0;\n }\n const cpuPercent = 100 * (1 - idle / total);\n return Math.round(cpuPercent * 100) / 100;\n } catch (error) {\n logDebug(`Failed to get CPU percent: ${error}`);\n return 0;\n }\n}\nfunction getMemoryInfo() {\n try {\n const result = execSync3("free -b", {\n encoding: "utf-8",\n timeout: 5e3,\n stdio: ["pipe", "pipe", "pipe"]\n });\n const lines = result.trim().split("\\n");\n for (const line of lines) {\n if (line.startsWith("Mem:")) {\n const parts = line.split(/\\s+/);\n const totalStr = parts[1];\n const usedStr = parts[2];\n if (!totalStr || !usedStr) {\n return [0, 0];\n }\n const total = parseInt(totalStr, 10);\n const used = parseInt(usedStr, 10);\n return [used, total];\n }\n }\n return [0, 0];\n } catch (error) {\n logDebug(`Failed to get memory info: ${error}`);\n return [0, 0];\n }\n}\nfunction getDiskInfo() {\n try {\n const result = execSync3("df -B1 /", {\n encoding: "utf-8",\n timeout: 5e3,\n stdio: ["pipe", "pipe", "pipe"]\n });\n const lines = result.trim().split("\\n");\n if (lines.length < 2) {\n return [0, 0];\n }\n const dataLine = lines[1];\n if (!dataLine) {\n return [0, 0];\n }\n const parts = dataLine.split(/\\s+/);\n const totalStr = parts[1];\n const usedStr = parts[2];\n if (!totalStr || !usedStr) {\n return [0, 0];\n }\n const total = parseInt(totalStr, 10);\n const used = parseInt(usedStr, 10);\n return [used, total];\n } catch (error) {\n logDebug(`Failed to get disk info: ${error}`);\n return [0, 0];\n }\n}\nfunction collectMetrics() {\n const cpu = getCpuPercent();\n const [memUsed, memTotal] = getMemoryInfo();\n const [diskUsed, diskTotal] = getDiskInfo();\n return {\n ts: (/* @__PURE__ */ new Date()).toISOString(),\n cpu,\n mem_used: memUsed,\n mem_total: memTotal,\n disk_used: diskUsed,\n disk_total: diskTotal\n };\n}\nfunction metricsCollectorLoop() {\n logInfo(`Metrics collector started, writing to ${METRICS_LOG_FILE}`);\n const writeMetrics = () => {\n if (shutdownRequested) {\n logInfo("Metrics collector stopped");\n return;\n }\n try {\n const metrics = collectMetrics();\n fs5.appendFileSync(METRICS_LOG_FILE, JSON.stringify(metrics) + "\\n");\n logDebug(\n `Metrics collected: cpu=${metrics.cpu}%, mem=${metrics.mem_used}/${metrics.mem_total}`\n );\n } catch (error) {\n logError(`Failed to collect/write metrics: ${error}`);\n }\n setTimeout(writeMetrics, METRICS_INTERVAL * 1e3);\n };\n writeMetrics();\n}\nfunction startMetricsCollector() {\n shutdownRequested = false;\n setTimeout(metricsCollectorLoop, 0);\n}\nfunction stopMetricsCollector() {\n shutdownRequested = true;\n}\n\n// src/sandbox/scripts/src/lib/upload-telemetry.ts\nimport * as fs6 from "fs";\nvar shutdownRequested2 = false;\nfunction readFileFromPosition(filePath, posFile) {\n let lastPos = 0;\n if (fs6.existsSync(posFile)) {\n try {\n const content = fs6.readFileSync(posFile, "utf-8").trim();\n lastPos = parseInt(content, 10) || 0;\n } catch {\n lastPos = 0;\n }\n }\n let newContent = "";\n let newPos = lastPos;\n if (fs6.existsSync(filePath)) {\n try {\n const fd = fs6.openSync(filePath, "r");\n const stats = fs6.fstatSync(fd);\n const bufferSize = stats.size - lastPos;\n if (bufferSize > 0) {\n const buffer = Buffer.alloc(bufferSize);\n fs6.readSync(fd, buffer, 0, bufferSize, lastPos);\n newContent = buffer.toString("utf-8");\n newPos = stats.size;\n }\n fs6.closeSync(fd);\n } catch (error) {\n logDebug(`Failed to read ${filePath}: ${error}`);\n }\n }\n return [newContent, newPos];\n}\nfunction savePosition(posFile, position) {\n try {\n fs6.writeFileSync(posFile, String(position));\n } catch (error) {\n logDebug(`Failed to save position to ${posFile}: ${error}`);\n }\n}\nfunction readJsonlFromPosition(filePath, posFile) {\n const [content, newPos] = readFileFromPosition(filePath, posFile);\n const entries = [];\n if (content) {\n for (const line of content.trim().split("\\n")) {\n if (line) {\n try {\n entries.push(JSON.parse(line));\n } catch {\n }\n }\n }\n }\n return [entries, newPos];\n}\nfunction readMetricsFromPosition(posFile) {\n return readJsonlFromPosition(METRICS_LOG_FILE, posFile);\n}\nfunction readNetworkLogsFromPosition(posFile) {\n return readJsonlFromPosition(NETWORK_LOG_FILE, posFile);\n}\nfunction readSandboxOpsFromPosition(posFile) {\n return readJsonlFromPosition(SANDBOX_OPS_LOG_FILE, posFile);\n}\nasync function uploadTelemetry() {\n const [systemLog, logPos] = readFileFromPosition(\n SYSTEM_LOG_FILE,\n TELEMETRY_LOG_POS_FILE\n );\n const [metrics, metricsPos] = readMetricsFromPosition(\n TELEMETRY_METRICS_POS_FILE\n );\n const [networkLogs, networkPos] = readNetworkLogsFromPosition(\n TELEMETRY_NETWORK_POS_FILE\n );\n const [sandboxOps, sandboxOpsPos] = readSandboxOpsFromPosition(\n TELEMETRY_SANDBOX_OPS_POS_FILE\n );\n if (!systemLog && metrics.length === 0 && networkLogs.length === 0 && sandboxOps.length === 0) {\n logDebug("No new telemetry data to upload");\n return true;\n }\n const maskedSystemLog = systemLog ? maskData(systemLog) : "";\n const maskedNetworkLogs = networkLogs.length > 0 ? maskData(networkLogs) : [];\n const payload = {\n runId: RUN_ID,\n systemLog: maskedSystemLog,\n metrics,\n // Metrics don\'t contain secrets (just numbers)\n networkLogs: maskedNetworkLogs,\n sandboxOperations: sandboxOps\n // Sandbox ops don\'t contain secrets (just timing data)\n };\n logDebug(\n `Uploading telemetry: ${systemLog.length} bytes log, ${metrics.length} metrics, ${networkLogs.length} network logs, ${sandboxOps.length} sandbox ops`\n );\n const result = await httpPostJson(TELEMETRY_URL, payload, 1);\n if (result) {\n savePosition(TELEMETRY_LOG_POS_FILE, logPos);\n savePosition(TELEMETRY_METRICS_POS_FILE, metricsPos);\n savePosition(TELEMETRY_NETWORK_POS_FILE, networkPos);\n savePosition(TELEMETRY_SANDBOX_OPS_POS_FILE, sandboxOpsPos);\n logDebug(\n `Telemetry uploaded successfully: ${result.id ?? "unknown"}`\n );\n return true;\n } else {\n logWarn("Failed to upload telemetry (will retry next interval)");\n return false;\n }\n}\nasync function telemetryUploadLoop() {\n logInfo(`Telemetry upload started (interval: ${TELEMETRY_INTERVAL}s)`);\n const runUpload = async () => {\n if (shutdownRequested2) {\n logInfo("Telemetry upload stopped");\n return;\n }\n try {\n await uploadTelemetry();\n } catch (error) {\n logError(`Telemetry upload error: ${error}`);\n }\n setTimeout(() => void runUpload(), TELEMETRY_INTERVAL * 1e3);\n };\n await runUpload();\n}\nfunction startTelemetryUpload() {\n shutdownRequested2 = false;\n setTimeout(() => void telemetryUploadLoop(), 0);\n}\nfunction stopTelemetryUpload() {\n shutdownRequested2 = true;\n}\nasync function finalTelemetryUpload() {\n logInfo("Performing final telemetry upload...");\n return uploadTelemetry();\n}\n\n// src/sandbox/scripts/src/run-agent.ts\nvar shutdownRequested3 = false;\nfunction heartbeatLoop() {\n const sendHeartbeat = async () => {\n if (shutdownRequested3) {\n return;\n }\n try {\n if (await httpPostJson(HEARTBEAT_URL, { runId: RUN_ID })) {\n logInfo("Heartbeat sent");\n } else {\n logWarn("Heartbeat failed");\n }\n } catch (error) {\n logWarn(`Heartbeat error: ${error}`);\n }\n setTimeout(() => {\n sendHeartbeat().catch(() => {\n });\n }, HEARTBEAT_INTERVAL * 1e3);\n };\n sendHeartbeat().catch(() => {\n });\n}\nasync function cleanup(exitCode, errorMessage) {\n logInfo("\\u25B7 Cleanup");\n const telemetryStart = Date.now();\n let telemetrySuccess = true;\n try {\n await finalTelemetryUpload();\n } catch (error) {\n telemetrySuccess = false;\n logError(`Final telemetry upload failed: ${error}`);\n }\n recordSandboxOp(\n "final_telemetry_upload",\n Date.now() - telemetryStart,\n telemetrySuccess\n );\n logInfo(`Calling complete API with exitCode=${exitCode}`);\n const completePayload = {\n runId: RUN_ID,\n exitCode\n };\n if (errorMessage) {\n completePayload.error = errorMessage;\n }\n const completeStart = Date.now();\n let completeSuccess = false;\n try {\n if (await httpPostJson(COMPLETE_URL, completePayload)) {\n logInfo("Complete API called successfully");\n completeSuccess = true;\n } else {\n logError("Failed to call complete API (sandbox may not be cleaned up)");\n }\n } catch (error) {\n logError(`Complete API call failed: ${error}`);\n }\n recordSandboxOp(\n "complete_api_call",\n Date.now() - completeStart,\n completeSuccess\n );\n shutdownRequested3 = true;\n stopMetricsCollector();\n stopTelemetryUpload();\n logInfo("Background processes stopped");\n if (exitCode === 0) {\n logInfo("\\u2713 Sandbox finished successfully");\n } else {\n logInfo(`\\u2717 Sandbox failed (exit code ${exitCode})`);\n }\n}\nasync function run() {\n validateConfig();\n logInfo(`\\u25B6 VM0 Sandbox ${RUN_ID}`);\n logInfo("\\u25B7 Initialization");\n const initStartTime = Date.now();\n logInfo(`Working directory: ${WORKING_DIR}`);\n const heartbeatStart = Date.now();\n heartbeatLoop();\n logInfo("Heartbeat started");\n recordSandboxOp("heartbeat_start", Date.now() - heartbeatStart, true);\n const metricsStart = Date.now();\n startMetricsCollector();\n logInfo("Metrics collector started");\n recordSandboxOp("metrics_collector_start", Date.now() - metricsStart, true);\n const telemetryStart = Date.now();\n startTelemetryUpload();\n logInfo("Telemetry upload started");\n recordSandboxOp("telemetry_upload_start", Date.now() - telemetryStart, true);\n const workingDirStart = Date.now();\n try {\n fs7.mkdirSync(WORKING_DIR, { recursive: true });\n process.chdir(WORKING_DIR);\n } catch (error) {\n recordSandboxOp(\n "working_dir_setup",\n Date.now() - workingDirStart,\n false,\n String(error)\n );\n throw new Error(\n `Failed to create/change to working directory: ${WORKING_DIR} - ${error}`\n );\n }\n recordSandboxOp("working_dir_setup", Date.now() - workingDirStart, true);\n if (CLI_AGENT_TYPE === "codex") {\n const homeDir = process.env.HOME ?? "/home/user";\n const codexHome = `${homeDir}/.codex`;\n fs7.mkdirSync(codexHome, { recursive: true });\n process.env.CODEX_HOME = codexHome;\n logInfo(`Codex home directory: ${codexHome}`);\n const codexLoginStart = Date.now();\n let codexLoginSuccess = false;\n const apiKey = process.env.OPENAI_API_KEY ?? "";\n if (apiKey) {\n try {\n execSync4("codex login --with-api-key", {\n input: apiKey,\n encoding: "utf-8",\n stdio: ["pipe", "pipe", "pipe"]\n });\n logInfo("Codex authenticated with API key");\n codexLoginSuccess = true;\n } catch (error) {\n logError(`Codex login failed: ${error}`);\n }\n } else {\n logError("OPENAI_API_KEY not set");\n }\n recordSandboxOp(\n "codex_login",\n Date.now() - codexLoginStart,\n codexLoginSuccess\n );\n }\n const initDurationMs = Date.now() - initStartTime;\n recordSandboxOp("init_total", initDurationMs, true);\n logInfo(`\\u2713 Initialization complete (${Math.floor(initDurationMs / 1e3)}s)`);\n logInfo("\\u25B7 Execution");\n const execStartTime = Date.now();\n logInfo(`Starting ${CLI_AGENT_TYPE} execution...`);\n logInfo(`Prompt: ${PROMPT}`);\n const useMock = process.env.USE_MOCK_CLAUDE === "true";\n let cmd;\n if (CLI_AGENT_TYPE === "codex") {\n if (useMock) {\n throw new Error("Mock mode not supported for Codex");\n }\n const codexArgs = [\n "exec",\n "--json",\n "--dangerously-bypass-approvals-and-sandbox",\n "--skip-git-repo-check",\n "-C",\n WORKING_DIR\n ];\n if (OPENAI_MODEL) {\n codexArgs.push("-m", OPENAI_MODEL);\n }\n if (RESUME_SESSION_ID) {\n logInfo(`Resuming session: ${RESUME_SESSION_ID}`);\n codexArgs.push("resume", RESUME_SESSION_ID, PROMPT);\n } else {\n logInfo("Starting new session");\n codexArgs.push(PROMPT);\n }\n cmd = ["codex", ...codexArgs];\n } else {\n const claudeArgs = [\n "--print",\n "--verbose",\n "--output-format",\n "stream-json",\n "--dangerously-skip-permissions"\n ];\n if (RESUME_SESSION_ID) {\n logInfo(`Resuming session: ${RESUME_SESSION_ID}`);\n claudeArgs.push("--resume", RESUME_SESSION_ID);\n } else {\n logInfo("Starting new session");\n }\n const claudeBin = useMock ? "/usr/local/bin/vm0-agent/mock-claude.mjs" : "claude";\n if (useMock) {\n logInfo("Using mock-claude for testing");\n }\n cmd = [claudeBin, ...claudeArgs, PROMPT];\n }\n let agentExitCode = 0;\n const stderrLines = [];\n let logFile = null;\n try {\n logFile = fs7.createWriteStream(AGENT_LOG_FILE);\n const cmdExe = cmd[0];\n if (!cmdExe) {\n throw new Error("Empty command");\n }\n const proc = spawn(cmdExe, cmd.slice(1), {\n stdio: ["ignore", "pipe", "pipe"]\n });\n const exitPromise = new Promise((resolve) => {\n let resolved = false;\n proc.on("error", (err) => {\n if (!resolved) {\n resolved = true;\n logError(`Failed to spawn ${CLI_AGENT_TYPE}: ${err.message}`);\n stderrLines.push(`Spawn error: ${err.message}`);\n resolve(1);\n }\n });\n proc.on("close", (code) => {\n if (!resolved) {\n resolved = true;\n resolve(code ?? 1);\n }\n });\n });\n if (proc.stderr) {\n const stderrRl = readline.createInterface({ input: proc.stderr });\n stderrRl.on("line", (line) => {\n stderrLines.push(line);\n if (logFile && !logFile.destroyed) {\n logFile.write(`[STDERR] ${line}\n`);\n }\n });\n }\n if (proc.stdout) {\n const stdoutRl = readline.createInterface({ input: proc.stdout });\n let eventSequence = 0;\n for await (const line of stdoutRl) {\n if (logFile && !logFile.destroyed) {\n logFile.write(line + "\\n");\n }\n const stripped = line.trim();\n if (!stripped) {\n continue;\n }\n try {\n const event = JSON.parse(stripped);\n await sendEvent(event, eventSequence);\n eventSequence++;\n if (event.type === "result") {\n const resultContent = event.result;\n if (resultContent) {\n console.log(resultContent);\n }\n }\n } catch {\n logDebug(`Non-JSON line from agent: ${stripped.slice(0, 100)}`);\n }\n }\n }\n agentExitCode = await exitPromise;\n } catch (error) {\n logError(`Failed to execute ${CLI_AGENT_TYPE}: ${error}`);\n agentExitCode = 1;\n } finally {\n if (logFile && !logFile.destroyed) {\n logFile.end();\n }\n }\n console.log();\n let finalExitCode = agentExitCode;\n let errorMessage = "";\n if (fs7.existsSync(EVENT_ERROR_FLAG)) {\n logError("Some events failed to send, marking run as failed");\n finalExitCode = 1;\n errorMessage = "Some events failed to send";\n }\n const execDurationMs = Date.now() - execStartTime;\n recordSandboxOp("cli_execution", execDurationMs, agentExitCode === 0);\n if (agentExitCode === 0 && finalExitCode === 0) {\n logInfo(`\\u2713 Execution complete (${Math.floor(execDurationMs / 1e3)}s)`);\n } else {\n logInfo(`\\u2717 Execution failed (${Math.floor(execDurationMs / 1e3)}s)`);\n }\n if (agentExitCode === 0 && finalExitCode === 0) {\n logInfo(`${CLI_AGENT_TYPE} completed successfully`);\n logInfo("\\u25B7 Checkpoint");\n const checkpointStartTime = Date.now();\n const checkpointSuccess = await createCheckpoint();\n const checkpointDuration = Math.floor(\n (Date.now() - checkpointStartTime) / 1e3\n );\n if (checkpointSuccess) {\n logInfo(`\\u2713 Checkpoint complete (${checkpointDuration}s)`);\n } else {\n logInfo(`\\u2717 Checkpoint failed (${checkpointDuration}s)`);\n }\n if (!checkpointSuccess) {\n logError("Checkpoint creation failed, marking run as failed");\n finalExitCode = 1;\n errorMessage = "Checkpoint creation failed";\n }\n } else {\n if (agentExitCode !== 0) {\n logInfo(`${CLI_AGENT_TYPE} failed with exit code ${agentExitCode}`);\n if (stderrLines.length > 0) {\n errorMessage = stderrLines.map((line) => line.trim()).join(" ");\n logInfo(`Captured stderr: ${errorMessage}`);\n } else {\n errorMessage = `Agent exited with code ${agentExitCode}`;\n }\n }\n }\n return [finalExitCode, errorMessage];\n}\nasync function main() {\n let exitCode = 1;\n let errorMessage = "Unexpected termination";\n try {\n [exitCode, errorMessage] = await run();\n } catch (error) {\n if (error instanceof Error) {\n exitCode = 1;\n errorMessage = error.message;\n logError(`Error: ${errorMessage}`);\n } else {\n exitCode = 1;\n errorMessage = `Unexpected error: ${error}`;\n logError(errorMessage);\n }\n } finally {\n await cleanup(exitCode, errorMessage);\n }\n return exitCode;\n}\nmain().then((code) => process.exit(code)).catch((error) => {\n console.error("Fatal error:", error);\n process.exit(1);\n});\n';
|
|
7959
|
-
var DOWNLOAD_SCRIPT = '#!/usr/bin/env node\n\n// src/sandbox/scripts/src/download.ts\nimport * as fs2 from "fs";\nimport * as path from "path";\nimport * as os from "os";\nimport { execSync as execSync2 } from "child_process";\n\n// src/sandbox/scripts/src/lib/common.ts\nimport * as fs from "fs";\nvar RUN_ID = process.env.VM0_RUN_ID ?? "";\nvar API_URL = process.env.VM0_API_URL ?? "";\nvar API_TOKEN = process.env.VM0_API_TOKEN ?? "";\nvar PROMPT = process.env.VM0_PROMPT ?? "";\nvar VERCEL_BYPASS = process.env.VERCEL_PROTECTION_BYPASS ?? "";\nvar RESUME_SESSION_ID = process.env.VM0_RESUME_SESSION_ID ?? "";\nvar CLI_AGENT_TYPE = process.env.CLI_AGENT_TYPE ?? "claude-code";\nvar OPENAI_MODEL = process.env.OPENAI_MODEL ?? "";\nvar WORKING_DIR = process.env.VM0_WORKING_DIR ?? "";\nvar ARTIFACT_DRIVER = process.env.VM0_ARTIFACT_DRIVER ?? "";\nvar ARTIFACT_MOUNT_PATH = process.env.VM0_ARTIFACT_MOUNT_PATH ?? "";\nvar ARTIFACT_VOLUME_NAME = process.env.VM0_ARTIFACT_VOLUME_NAME ?? "";\nvar ARTIFACT_VERSION_ID = process.env.VM0_ARTIFACT_VERSION_ID ?? "";\nvar WEBHOOK_URL = `${API_URL}/api/webhooks/agent/events`;\nvar CHECKPOINT_URL = `${API_URL}/api/webhooks/agent/checkpoints`;\nvar COMPLETE_URL = `${API_URL}/api/webhooks/agent/complete`;\nvar HEARTBEAT_URL = `${API_URL}/api/webhooks/agent/heartbeat`;\nvar TELEMETRY_URL = `${API_URL}/api/webhooks/agent/telemetry`;\nvar PROXY_URL = `${API_URL}/api/webhooks/agent/proxy`;\nvar STORAGE_PREPARE_URL = `${API_URL}/api/webhooks/agent/storages/prepare`;\nvar STORAGE_COMMIT_URL = `${API_URL}/api/webhooks/agent/storages/commit`;\nvar HTTP_MAX_TIME_UPLOAD = 60;\nvar HTTP_MAX_RETRIES = 3;\nvar SESSION_ID_FILE = `/tmp/vm0-session-${RUN_ID}.txt`;\nvar SESSION_HISTORY_PATH_FILE = `/tmp/vm0-session-history-${RUN_ID}.txt`;\nvar EVENT_ERROR_FLAG = `/tmp/vm0-event-error-${RUN_ID}`;\nvar SYSTEM_LOG_FILE = `/tmp/vm0-main-${RUN_ID}.log`;\nvar AGENT_LOG_FILE = `/tmp/vm0-agent-${RUN_ID}.log`;\nvar METRICS_LOG_FILE = `/tmp/vm0-metrics-${RUN_ID}.jsonl`;\nvar NETWORK_LOG_FILE = `/tmp/vm0-network-${RUN_ID}.jsonl`;\nvar TELEMETRY_LOG_POS_FILE = `/tmp/vm0-telemetry-log-pos-${RUN_ID}.txt`;\nvar TELEMETRY_METRICS_POS_FILE = `/tmp/vm0-telemetry-metrics-pos-${RUN_ID}.txt`;\nvar TELEMETRY_NETWORK_POS_FILE = `/tmp/vm0-telemetry-network-pos-${RUN_ID}.txt`;\nvar TELEMETRY_SANDBOX_OPS_POS_FILE = `/tmp/vm0-telemetry-sandbox-ops-pos-${RUN_ID}.txt`;\nvar SANDBOX_OPS_LOG_FILE = `/tmp/vm0-sandbox-ops-${RUN_ID}.jsonl`;\nfunction recordSandboxOp(actionType, durationMs, success, error) {\n const entry = {\n ts: (/* @__PURE__ */ new Date()).toISOString(),\n action_type: actionType,\n duration_ms: durationMs,\n success\n };\n if (error) {\n entry.error = error;\n }\n fs.appendFileSync(SANDBOX_OPS_LOG_FILE, JSON.stringify(entry) + "\\n");\n}\n\n// src/sandbox/scripts/src/lib/log.ts\nvar SCRIPT_NAME = process.env.LOG_SCRIPT_NAME ?? "run-agent";\nvar DEBUG_MODE = process.env.VM0_DEBUG === "1";\nfunction timestamp() {\n return (/* @__PURE__ */ new Date()).toISOString().replace(/\\.\\d{3}Z$/, "Z");\n}\nfunction logInfo(msg) {\n console.error(`[${timestamp()}] [INFO] [sandbox:${SCRIPT_NAME}] ${msg}`);\n}\nfunction logWarn(msg) {\n console.error(`[${timestamp()}] [WARN] [sandbox:${SCRIPT_NAME}] ${msg}`);\n}\nfunction logError(msg) {\n console.error(`[${timestamp()}] [ERROR] [sandbox:${SCRIPT_NAME}] ${msg}`);\n}\nfunction logDebug(msg) {\n if (DEBUG_MODE) {\n console.error(`[${timestamp()}] [DEBUG] [sandbox:${SCRIPT_NAME}] ${msg}`);\n }\n}\n\n// src/sandbox/scripts/src/lib/http-client.ts\nimport { execSync } from "child_process";\nfunction sleep(ms) {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\nasync function httpDownload(url, destPath, maxRetries = HTTP_MAX_RETRIES) {\n for (let attempt = 1; attempt <= maxRetries; attempt++) {\n logDebug(`HTTP download attempt ${attempt}/${maxRetries} from ${url}`);\n try {\n const curlCmd = ["curl", "-fsSL", "-o", destPath, `"${url}"`].join(" ");\n execSync(curlCmd, {\n timeout: HTTP_MAX_TIME_UPLOAD * 1e3,\n stdio: ["pipe", "pipe", "pipe"]\n });\n return true;\n } catch (error) {\n const errorMsg = error instanceof Error ? error.message : String(error);\n if (errorMsg.includes("ETIMEDOUT") || errorMsg.includes("timeout")) {\n logWarn(\n `HTTP download failed (attempt ${attempt}/${maxRetries}): Timeout`\n );\n } else {\n logWarn(\n `HTTP download failed (attempt ${attempt}/${maxRetries}): ${errorMsg}`\n );\n }\n if (attempt < maxRetries) {\n await sleep(1e3);\n }\n }\n }\n logError(`HTTP download failed after ${maxRetries} attempts from ${url}`);\n return false;\n}\n\n// src/sandbox/scripts/src/download.ts\nasync function downloadStorage(mountPath, archiveUrl) {\n logInfo(`Downloading storage to ${mountPath}`);\n const tempTar = path.join(\n os.tmpdir(),\n `storage-${Date.now()}-${Math.random().toString(36).slice(2)}.tar.gz`\n );\n try {\n if (!await httpDownload(archiveUrl, tempTar)) {\n logError(`Failed to download archive for ${mountPath}`);\n return false;\n }\n fs2.mkdirSync(mountPath, { recursive: true });\n try {\n execSync2(`tar -xzf "${tempTar}" -C "${mountPath}"`, {\n stdio: ["pipe", "pipe", "pipe"]\n });\n } catch {\n logInfo(`Archive appears empty for ${mountPath}`);\n }\n logInfo(`Successfully extracted to ${mountPath}`);\n return true;\n } finally {\n try {\n fs2.unlinkSync(tempTar);\n } catch {\n }\n }\n}\nasync function main() {\n const args = process.argv.slice(2);\n if (args.length < 1) {\n logError("Usage: node download.mjs <manifest_path>");\n process.exit(1);\n }\n const manifestPath = args[0] ?? "";\n if (!manifestPath || !fs2.existsSync(manifestPath)) {\n logError(`Manifest file not found: ${manifestPath}`);\n process.exit(1);\n }\n logInfo(`Starting storage download from manifest: ${manifestPath}`);\n let manifest;\n try {\n const content = fs2.readFileSync(manifestPath, "utf-8");\n manifest = JSON.parse(content);\n } catch (error) {\n logError(`Failed to load manifest: ${error}`);\n process.exit(1);\n }\n const storages = manifest.storages ?? [];\n const artifact = manifest.artifact;\n const storageCount = storages.length;\n const hasArtifact = artifact !== void 0;\n logInfo(`Found ${storageCount} storages, artifact: ${hasArtifact}`);\n const downloadTotalStart = Date.now();\n let downloadSuccess = true;\n for (const storage of storages) {\n const mountPath = storage.mountPath;\n const archiveUrl = storage.archiveUrl;\n if (archiveUrl && archiveUrl !== "null") {\n const storageStart = Date.now();\n const success = await downloadStorage(mountPath, archiveUrl);\n recordSandboxOp("storage_download", Date.now() - storageStart, success);\n if (!success) {\n downloadSuccess = false;\n }\n }\n }\n if (artifact) {\n const artifactMount = artifact.mountPath;\n const artifactUrl = artifact.archiveUrl;\n if (artifactUrl && artifactUrl !== "null") {\n const artifactStart = Date.now();\n const success = await downloadStorage(artifactMount, artifactUrl);\n recordSandboxOp("artifact_download", Date.now() - artifactStart, success);\n if (!success) {\n downloadSuccess = false;\n }\n }\n }\n recordSandboxOp(\n "download_total",\n Date.now() - downloadTotalStart,\n downloadSuccess\n );\n logInfo("All storages downloaded successfully");\n}\nmain().catch((error) => {\n logError(`Fatal error: ${error}`);\n process.exit(1);\n});\n';
|
|
7960
|
-
var MOCK_CLAUDE_SCRIPT = '#!/usr/bin/env node\n\n// src/sandbox/scripts/src/mock-claude.ts\nimport * as fs from "fs";\nimport * as path from "path";\nimport { execSync } from "child_process";\nfunction parseArgs(args) {\n const result = {\n outputFormat: "text",\n print: false,\n verbose: false,\n dangerouslySkipPermissions: false,\n resume: null,\n prompt: ""\n };\n const remaining = [];\n let i = 0;\n while (i < args.length) {\n const arg = args[i];\n if (arg === "--output-format" && i + 1 < args.length) {\n result.outputFormat = args[i + 1] ?? "text";\n i += 2;\n } else if (arg === "--print") {\n result.print = true;\n i++;\n } else if (arg === "--verbose") {\n result.verbose = true;\n i++;\n } else if (arg === "--dangerously-skip-permissions") {\n result.dangerouslySkipPermissions = true;\n i++;\n } else if (arg === "--resume" && i + 1 < args.length) {\n result.resume = args[i + 1] ?? null;\n i += 2;\n } else if (arg) {\n remaining.push(arg);\n i++;\n } else {\n i++;\n }\n }\n if (remaining.length > 0) {\n result.prompt = remaining[0] ?? "";\n }\n return result;\n}\nfunction createSessionHistory(sessionId, cwd) {\n const projectName = cwd.replace(/^\\//, "").replace(/\\//g, "-");\n const homeDir = process.env.HOME ?? "/home/user";\n const sessionDir = `${homeDir}/.claude/projects/-${projectName}`;\n fs.mkdirSync(sessionDir, { recursive: true });\n return path.join(sessionDir, `${sessionId}.jsonl`);\n}\nfunction main() {\n const sessionId = `mock-${Date.now() * 1e3 + Math.floor(Math.random() * 1e3)}`;\n const args = parseArgs(process.argv.slice(2));\n const prompt = args.prompt;\n const outputFormat = args.outputFormat;\n if (prompt.startsWith("@fail:")) {\n const errorMsg = prompt.slice(6);\n console.error(errorMsg);\n process.exit(1);\n }\n const cwd = process.cwd();\n if (outputFormat === "stream-json") {\n const sessionHistoryFile = createSessionHistory(sessionId, cwd);\n const events = [];\n const initEvent = {\n type: "system",\n subtype: "init",\n cwd,\n session_id: sessionId,\n tools: ["Bash"],\n model: "mock-claude"\n };\n console.log(JSON.stringify(initEvent));\n events.push(initEvent);\n const textEvent = {\n type: "assistant",\n message: {\n role: "assistant",\n content: [{ type: "text", text: "Executing command..." }]\n },\n session_id: sessionId\n };\n console.log(JSON.stringify(textEvent));\n events.push(textEvent);\n const toolUseEvent = {\n type: "assistant",\n message: {\n role: "assistant",\n content: [\n {\n type: "tool_use",\n id: "toolu_mock_001",\n name: "Bash",\n input: { command: prompt }\n }\n ]\n },\n session_id: sessionId\n };\n console.log(JSON.stringify(toolUseEvent));\n events.push(toolUseEvent);\n let output;\n let exitCode;\n try {\n output = execSync(`bash -c ${JSON.stringify(prompt)}`, {\n encoding: "utf-8",\n stdio: ["pipe", "pipe", "pipe"]\n });\n exitCode = 0;\n } catch (error) {\n const execError = error;\n output = (execError.stdout ?? "") + (execError.stderr ?? "");\n exitCode = execError.status ?? 1;\n }\n const isError = exitCode !== 0;\n const toolResultEvent = {\n type: "user",\n message: {\n role: "user",\n content: [\n {\n type: "tool_result",\n tool_use_id: "toolu_mock_001",\n content: output,\n is_error: isError\n }\n ]\n },\n session_id: sessionId\n };\n console.log(JSON.stringify(toolResultEvent));\n events.push(toolResultEvent);\n const resultEvent = {\n type: "result",\n subtype: exitCode === 0 ? "success" : "error",\n is_error: exitCode !== 0,\n duration_ms: 100,\n num_turns: 1,\n result: output,\n session_id: sessionId,\n total_cost_usd: 0,\n usage: { input_tokens: 0, output_tokens: 0 }\n };\n console.log(JSON.stringify(resultEvent));\n events.push(resultEvent);\n const historyContent = events.map((e) => JSON.stringify(e)).join("\\n") + "\\n";\n fs.writeFileSync(sessionHistoryFile, historyContent);\n process.exit(exitCode);\n } else {\n try {\n execSync(`bash -c ${JSON.stringify(prompt)}`, {\n stdio: "inherit"\n });\n process.exit(0);\n } catch (error) {\n const execError = error;\n process.exit(execError.status ?? 1);\n }\n }\n}\nvar isMainModule = process.argv[1]?.endsWith("mock-claude.mjs") || process.argv[1]?.endsWith("mock-claude.ts");\nif (isMainModule) {\n main();\n}\nexport {\n createSessionHistory,\n parseArgs\n};\n';
|
|
7961
|
-
var ENV_LOADER_SCRIPT = '#!/usr/bin/env node\n\n// src/sandbox/scripts/src/env-loader.ts\nimport * as fs from "fs";\nimport { spawn } from "child_process";\nvar ENV_JSON_PATH = "/tmp/vm0-env.json";\nconsole.log("[env-loader] Starting...");\nif (fs.existsSync(ENV_JSON_PATH)) {\n console.log(`[env-loader] Loading environment from ${ENV_JSON_PATH}`);\n try {\n const content = fs.readFileSync(ENV_JSON_PATH, "utf-8");\n const envData = JSON.parse(content);\n for (const [key, value] of Object.entries(envData)) {\n process.env[key] = value;\n }\n console.log(\n `[env-loader] Loaded ${Object.keys(envData).length} environment variables`\n );\n } catch (error) {\n console.error(`[env-loader] ERROR loading JSON: ${error}`);\n process.exit(1);\n }\n} else {\n console.error(\n `[env-loader] ERROR: Environment file not found: ${ENV_JSON_PATH}`\n );\n process.exit(1);\n}\nvar criticalVars = [\n "VM0_RUN_ID",\n "VM0_API_URL",\n "VM0_WORKING_DIR",\n "VM0_PROMPT"\n];\nfor (const varName of criticalVars) {\n const val = process.env[varName] ?? "";\n if (val) {\n const display = val.length > 50 ? val.substring(0, 50) + "..." : val;\n console.log(`[env-loader] ${varName}=${display}`);\n } else {\n console.log(`[env-loader] WARNING: ${varName} is empty`);\n }\n}\nvar runAgentPath = "/usr/local/bin/vm0-agent/run-agent.mjs";\nconsole.log(`[env-loader] Executing ${runAgentPath}`);\nvar child = spawn("node", [runAgentPath], {\n stdio: "inherit",\n env: process.env\n});\nchild.on("close", (code) => {\n process.exit(code ?? 1);\n});\n';
|
|
7962
|
-
|
|
7963
8035
|
// ../../packages/core/src/sandbox/scripts/index.ts
|
|
7964
8036
|
var SCRIPT_PATHS = {
|
|
7965
8037
|
/** Base directory for agent scripts */
|
|
@@ -7984,10 +8056,6 @@ var FEATURE_SWITCHES = {
|
|
|
7984
8056
|
maintainer: "ethan@vm0.ai",
|
|
7985
8057
|
enabled: true
|
|
7986
8058
|
},
|
|
7987
|
-
["platformOnboarding" /* PlatformOnboarding */]: {
|
|
7988
|
-
maintainer: "ethan@vm0.ai",
|
|
7989
|
-
enabled: false
|
|
7990
|
-
},
|
|
7991
8059
|
["platformAgents" /* PlatformAgents */]: {
|
|
7992
8060
|
maintainer: "ethan@vm0.ai",
|
|
7993
8061
|
enabled: false
|
|
@@ -9117,29 +9185,6 @@ async function uploadNetworkLogs(apiUrl, sandboxToken, runId) {
|
|
|
9117
9185
|
|
|
9118
9186
|
// src/lib/vm-setup/vm-setup.ts
|
|
9119
9187
|
import fs8 from "fs";
|
|
9120
|
-
|
|
9121
|
-
// src/lib/scripts/utils.ts
|
|
9122
|
-
function getAllScripts() {
|
|
9123
|
-
return [
|
|
9124
|
-
{ content: RUN_AGENT_SCRIPT, path: SCRIPT_PATHS.runAgent },
|
|
9125
|
-
{ content: DOWNLOAD_SCRIPT, path: SCRIPT_PATHS.download },
|
|
9126
|
-
{ content: MOCK_CLAUDE_SCRIPT, path: SCRIPT_PATHS.mockClaude },
|
|
9127
|
-
// Env loader is runner-specific (loads env from JSON before executing run-agent.mjs)
|
|
9128
|
-
{ content: ENV_LOADER_SCRIPT, path: ENV_LOADER_PATH }
|
|
9129
|
-
];
|
|
9130
|
-
}
|
|
9131
|
-
|
|
9132
|
-
// src/lib/vm-setup/vm-setup.ts
|
|
9133
|
-
async function uploadScripts(guest) {
|
|
9134
|
-
const scripts = getAllScripts();
|
|
9135
|
-
await guest.execOrThrow(`sudo mkdir -p ${SCRIPT_PATHS.baseDir}`);
|
|
9136
|
-
for (const script of scripts) {
|
|
9137
|
-
await guest.writeFileWithSudo(script.path, script.content);
|
|
9138
|
-
}
|
|
9139
|
-
await guest.execOrThrow(
|
|
9140
|
-
`sudo chmod +x ${SCRIPT_PATHS.baseDir}/*.mjs 2>/dev/null || true`
|
|
9141
|
-
);
|
|
9142
|
-
}
|
|
9143
9188
|
async function downloadStorages(guest, manifest) {
|
|
9144
9189
|
const totalArchives = manifest.storages.filter((s) => s.archiveUrl).length + (manifest.artifact?.archiveUrl ? 1 : 0);
|
|
9145
9190
|
if (totalArchives === 0) {
|
|
@@ -9194,14 +9239,6 @@ async function installProxyCA(guest, caCertPath) {
|
|
|
9194
9239
|
await guest.execOrThrow("sudo update-ca-certificates");
|
|
9195
9240
|
console.log(`[Executor] Proxy CA certificate installed successfully`);
|
|
9196
9241
|
}
|
|
9197
|
-
async function configureDNS(guest) {
|
|
9198
|
-
const dnsConfig = `nameserver 8.8.8.8
|
|
9199
|
-
nameserver 8.8.4.4
|
|
9200
|
-
nameserver 1.1.1.1`;
|
|
9201
|
-
await guest.execOrThrow(
|
|
9202
|
-
`sudo sh -c 'rm -f /etc/resolv.conf && echo "${dnsConfig}" > /etc/resolv.conf'`
|
|
9203
|
-
);
|
|
9204
|
-
}
|
|
9205
9242
|
|
|
9206
9243
|
// src/lib/executor.ts
|
|
9207
9244
|
function getVmIdFromRunId(runId) {
|
|
@@ -9272,7 +9309,8 @@ async function executeJob(context, config, options = {}) {
|
|
|
9272
9309
|
kernelPath: config.firecracker.kernel,
|
|
9273
9310
|
rootfsPath: config.firecracker.rootfs,
|
|
9274
9311
|
firecrackerBinary: config.firecracker.binary,
|
|
9275
|
-
workDir: path4.join(workspacesDir, `vm0-${vmId}`)
|
|
9312
|
+
workDir: path4.join(workspacesDir, `vm0-${vmId}`),
|
|
9313
|
+
logger: log
|
|
9276
9314
|
};
|
|
9277
9315
|
log(`[Executor] Creating VM ${vmId}...`);
|
|
9278
9316
|
vm = new FirecrackerVM(vmConfig);
|
|
@@ -9312,11 +9350,6 @@ async function executeJob(context, config, options = {}) {
|
|
|
9312
9350
|
await installProxyCA(guest, caCertPath);
|
|
9313
9351
|
}
|
|
9314
9352
|
}
|
|
9315
|
-
log(`[Executor] Configuring DNS...`);
|
|
9316
|
-
await configureDNS(guest);
|
|
9317
|
-
log(`[Executor] Uploading scripts...`);
|
|
9318
|
-
await withSandboxTiming("script_upload", () => uploadScripts(guest));
|
|
9319
|
-
log(`[Executor] Scripts uploaded to ${SCRIPT_PATHS.baseDir}`);
|
|
9320
9353
|
if (context.storageManifest) {
|
|
9321
9354
|
await withSandboxTiming(
|
|
9322
9355
|
"storage_download",
|
|
@@ -10222,7 +10255,7 @@ async function confirm(message) {
|
|
|
10222
10255
|
|
|
10223
10256
|
// src/commands/benchmark.ts
|
|
10224
10257
|
import { Command as Command4 } from "commander";
|
|
10225
|
-
import
|
|
10258
|
+
import crypto from "crypto";
|
|
10226
10259
|
|
|
10227
10260
|
// src/lib/timing.ts
|
|
10228
10261
|
var Timer = class {
|
|
@@ -10231,14 +10264,14 @@ var Timer = class {
|
|
|
10231
10264
|
this.startTime = Date.now();
|
|
10232
10265
|
}
|
|
10233
10266
|
/**
|
|
10234
|
-
* Get elapsed time formatted as [MM:SS.
|
|
10267
|
+
* Get elapsed time formatted as [MM:SS.ss]
|
|
10235
10268
|
*/
|
|
10236
10269
|
elapsed() {
|
|
10237
10270
|
const ms = Date.now() - this.startTime;
|
|
10238
10271
|
const totalSeconds = ms / 1e3;
|
|
10239
10272
|
const minutes = Math.floor(totalSeconds / 60);
|
|
10240
|
-
const seconds = (totalSeconds % 60).toFixed(
|
|
10241
|
-
return `[${String(minutes).padStart(2, "0")}:${seconds.padStart(
|
|
10273
|
+
const seconds = (totalSeconds % 60).toFixed(2);
|
|
10274
|
+
return `[${String(minutes).padStart(2, "0")}:${seconds.padStart(5, "0")}]`;
|
|
10242
10275
|
}
|
|
10243
10276
|
/**
|
|
10244
10277
|
* Log message with timestamp
|
|
@@ -10257,7 +10290,7 @@ var Timer = class {
|
|
|
10257
10290
|
// src/commands/benchmark.ts
|
|
10258
10291
|
function createBenchmarkContext(prompt, options) {
|
|
10259
10292
|
return {
|
|
10260
|
-
runId:
|
|
10293
|
+
runId: crypto.randomUUID(),
|
|
10261
10294
|
prompt,
|
|
10262
10295
|
agentComposeVersionId: "benchmark-local",
|
|
10263
10296
|
vars: null,
|
|
@@ -10312,7 +10345,7 @@ var benchmarkCommand = new Command4("benchmark").description(
|
|
|
10312
10345
|
});
|
|
10313
10346
|
|
|
10314
10347
|
// src/index.ts
|
|
10315
|
-
var version = true ? "3.0
|
|
10348
|
+
var version = true ? "3.1.0" : "0.1.0";
|
|
10316
10349
|
program.name("vm0-runner").version(version).description("Self-hosted runner for VM0 agents");
|
|
10317
10350
|
program.addCommand(startCommand);
|
|
10318
10351
|
program.addCommand(doctorCommand);
|