@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.
Files changed (2) hide show
  1. package/index.js +205 -172
  2. 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 { execSync as execSync2, spawn } from "child_process";
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
- console.log(`Creating TAP device ${tapDevice} for VM ${vmId}...`);
715
- await setupBridge();
717
+ log(`[VM ${vmId}] Stale iptables cleared`);
716
718
  if (await tapDeviceExists(tapDevice)) {
717
- console.log(`TAP device ${tapDevice} already exists, deleting it first...`);
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
- console.log(
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
- console.log(`[VM ${this.config.vmId}] Creating sparse overlay file...`);
1025
- const overlaySize = 2 * 1024 * 1024 * 1024;
1026
- const fd = fs3.openSync(this.vmOverlayPath, "w");
1027
- fs3.ftruncateSync(fd, overlaySize);
1028
- fs3.closeSync(fd);
1029
- execSync2(`mkfs.ext4 -F -q "${this.vmOverlayPath}"`, { stdio: "ignore" });
1030
- console.log(`[VM ${this.config.vmId}] Setting up network...`);
1031
- this.networkConfig = await createTapDevice(this.config.vmId);
1032
- console.log(`[VM ${this.config.vmId}] Starting Firecracker...`);
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
- console.error(`[VM ${this.config.vmId}] Firecracker error:`, err);
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
- console.log(
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
- console.log(`[VM ${this.config.vmId}] ${line}`);
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
- console.error(`[VM ${this.config.vmId}] stderr: ${line}`);
1082
+ this.log(`[VM ${this.config.vmId}] stderr: ${line}`);
1071
1083
  }
1072
1084
  });
1073
1085
  }
1074
1086
  this.client = new FirecrackerClient(this.socketPath);
1075
- console.log(`[VM ${this.config.vmId}] Waiting for API...`);
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
- console.log(`[VM ${this.config.vmId}] Booting...`);
1091
+ this.log(`[VM ${this.config.vmId}] Booting...`);
1080
1092
  await this.client.start();
1081
1093
  this.state = "running";
1082
- console.log(
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
- console.log(
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/overlay-init ${networkBootArgs}`;
1107
- console.log(`[VM ${this.config.vmId}] Boot args: ${bootArgs}`);
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
- console.log(
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
- console.log(`[VM ${this.config.vmId}] Overlay: ${this.vmOverlayPath}`);
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
- console.log(
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
- console.log(`[VM ${this.config.vmId}] Vsock: ${this.vsockPath}`);
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
- console.log(`[VM ${this.config.vmId}] Not running, state: ${this.state}`);
1158
+ this.log(`[VM ${this.config.vmId}] Not running, state: ${this.state}`);
1149
1159
  return;
1150
1160
  }
1151
1161
  this.state = "stopping";
1152
- console.log(`[VM ${this.config.vmId}] Stopping...`);
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
- console.log(
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
- console.log(`[VM ${this.config.vmId}] Force killing...`);
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
- console.log(`[VM ${this.config.vmId}] Stopped`);
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
- function encode(msg) {
1233
- const json = Buffer.from(JSON.stringify(msg), "utf-8");
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(json.length, 0);
1236
- return Buffer.concat([header, json]);
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 len = this.buf.readUInt32BE(0);
1245
- if (len > MAX_MESSAGE_SIZE) throw new Error(`Message too large: ${len}`);
1246
- const total = HEADER_SIZE + len;
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 json = this.buf.subarray(HEADER_SIZE, total);
1249
- messages.push(JSON.parse(json.toString("utf-8")));
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.id);
1357
+ const pending = this.pendingRequests.get(msg.seq);
1268
1358
  if (pending) {
1269
1359
  clearTimeout(pending.timeout);
1270
- this.pendingRequests.delete(msg.id);
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 id = crypto.randomUUID();
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(id);
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(id, {
1289
- resolve,
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
- "exec",
1304
- { command, timeoutMs: actualTimeout },
1389
+ MSG_EXEC,
1390
+ payload,
1305
1391
  actualTimeout + 5e3
1306
1392
  // Add buffer for network latency
1307
1393
  );
1308
- if (response.type === "error") {
1309
- const errorPayload = response.payload;
1394
+ if (response.type === MSG_ERROR) {
1310
1395
  return {
1311
1396
  exitCode: 1,
1312
1397
  stdout: "",
1313
- stderr: errorPayload.message
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 writeFile(remotePath, content) {
1345
- const encoded = Buffer.from(content).toString("base64");
1346
- const maxChunkSize = 65e3;
1347
- if (encoded.length <= maxChunkSize) {
1348
- await this.execOrThrow(`echo '${encoded}' | base64 -d > '${remotePath}'`);
1349
- } else {
1350
- await this.execOrThrow(`rm -f '${remotePath}'`);
1351
- for (let i = 0; i < encoded.length; i += maxChunkSize) {
1352
- const chunk = encoded.slice(i, i + maxChunkSize);
1353
- const operator = i === 0 ? ">" : ">>";
1354
- await this.execOrThrow(
1355
- `echo '${chunk}' | base64 -d ${operator} '${remotePath}'`
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
- const encoded = Buffer.from(content).toString("base64");
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 pingId = null;
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 === "ready") {
1541
+ if (state === 0 /* WaitingForReady */ && msg.type === MSG_READY) {
1465
1542
  state = 1 /* WaitingForPong */;
1466
- pingId = crypto.randomUUID();
1467
- socket.write(encode({ type: "ping", id: pingId, payload: {} }));
1468
- } else if (state === 1 /* WaitingForPong */ && msg.type === "pong" && msg.id === pingId) {
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 crypto2 from "crypto";
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.s]
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(1);
10241
- return `[${String(minutes).padStart(2, "0")}:${seconds.padStart(4, "0")}]`;
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: crypto2.randomUUID(),
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.5" : "0.1.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);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vm0/runner",
3
- "version": "3.0.5",
3
+ "version": "3.1.0",
4
4
  "description": "Self-hosted runner for VM0 agents",
5
5
  "repository": {
6
6
  "type": "git",