@vm0/runner 2.15.0 → 3.0.1
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 +83 -287
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -16,8 +16,7 @@ var SANDBOX_DEFAULTS = {
|
|
|
16
16
|
max_concurrent: 1,
|
|
17
17
|
vcpu: 2,
|
|
18
18
|
memory_mb: 2048,
|
|
19
|
-
poll_interval_ms: 5e3
|
|
20
|
-
guest_protocol: "vsock"
|
|
19
|
+
poll_interval_ms: 5e3
|
|
21
20
|
};
|
|
22
21
|
var PROXY_DEFAULTS = {
|
|
23
22
|
port: 8080
|
|
@@ -36,8 +35,7 @@ var runnerConfigSchema = z.object({
|
|
|
36
35
|
max_concurrent: z.number().int().min(1).default(SANDBOX_DEFAULTS.max_concurrent),
|
|
37
36
|
vcpu: z.number().int().min(1).default(SANDBOX_DEFAULTS.vcpu),
|
|
38
37
|
memory_mb: z.number().int().min(128).default(SANDBOX_DEFAULTS.memory_mb),
|
|
39
|
-
poll_interval_ms: z.number().int().min(1e3).default(SANDBOX_DEFAULTS.poll_interval_ms)
|
|
40
|
-
guest_protocol: z.enum(["vsock", "ssh"]).default(SANDBOX_DEFAULTS.guest_protocol)
|
|
38
|
+
poll_interval_ms: z.number().int().min(1e3).default(SANDBOX_DEFAULTS.poll_interval_ms)
|
|
41
39
|
}).default(SANDBOX_DEFAULTS),
|
|
42
40
|
firecracker: z.object({
|
|
43
41
|
binary: z.string().min(1, "Firecracker binary path is required"),
|
|
@@ -64,8 +62,7 @@ var debugConfigSchema = z.object({
|
|
|
64
62
|
max_concurrent: z.number().int().min(1).default(SANDBOX_DEFAULTS.max_concurrent),
|
|
65
63
|
vcpu: z.number().int().min(1).default(SANDBOX_DEFAULTS.vcpu),
|
|
66
64
|
memory_mb: z.number().int().min(128).default(SANDBOX_DEFAULTS.memory_mb),
|
|
67
|
-
poll_interval_ms: z.number().int().min(1e3).default(SANDBOX_DEFAULTS.poll_interval_ms)
|
|
68
|
-
guest_protocol: z.enum(["vsock", "ssh"]).default(SANDBOX_DEFAULTS.guest_protocol)
|
|
65
|
+
poll_interval_ms: z.number().int().min(1e3).default(SANDBOX_DEFAULTS.poll_interval_ms)
|
|
69
66
|
}).default(SANDBOX_DEFAULTS),
|
|
70
67
|
firecracker: z.object({
|
|
71
68
|
binary: z.string().min(1, "Firecracker binary path is required"),
|
|
@@ -192,7 +189,7 @@ async function completeJob(apiUrl, context, exitCode, error) {
|
|
|
192
189
|
}
|
|
193
190
|
|
|
194
191
|
// src/lib/executor.ts
|
|
195
|
-
import
|
|
192
|
+
import path4 from "path";
|
|
196
193
|
|
|
197
194
|
// src/lib/firecracker/vm.ts
|
|
198
195
|
import { execSync as execSync2, spawn } from "child_process";
|
|
@@ -210,7 +207,7 @@ var FirecrackerClient = class {
|
|
|
210
207
|
/**
|
|
211
208
|
* Make HTTP request to Firecracker API
|
|
212
209
|
*/
|
|
213
|
-
async request(method,
|
|
210
|
+
async request(method, path6, body) {
|
|
214
211
|
return new Promise((resolve, reject) => {
|
|
215
212
|
const bodyStr = body !== void 0 ? JSON.stringify(body) : void 0;
|
|
216
213
|
const headers = {
|
|
@@ -223,11 +220,11 @@ var FirecrackerClient = class {
|
|
|
223
220
|
headers["Content-Length"] = Buffer.byteLength(bodyStr);
|
|
224
221
|
}
|
|
225
222
|
console.log(
|
|
226
|
-
`[FC API] ${method} ${
|
|
223
|
+
`[FC API] ${method} ${path6}${bodyStr ? ` (${Buffer.byteLength(bodyStr)} bytes)` : ""}`
|
|
227
224
|
);
|
|
228
225
|
const options = {
|
|
229
226
|
socketPath: this.socketPath,
|
|
230
|
-
path:
|
|
227
|
+
path: path6,
|
|
231
228
|
method,
|
|
232
229
|
headers,
|
|
233
230
|
// Disable agent to ensure fresh connection for each request
|
|
@@ -1224,216 +1221,9 @@ var FirecrackerVM = class {
|
|
|
1224
1221
|
}
|
|
1225
1222
|
};
|
|
1226
1223
|
|
|
1227
|
-
// src/lib/firecracker/guest.ts
|
|
1228
|
-
import { exec as exec3, execSync as execSync3 } from "child_process";
|
|
1229
|
-
import { promisify as promisify3 } from "util";
|
|
1230
|
-
import fs4 from "fs";
|
|
1231
|
-
import path3 from "path";
|
|
1232
|
-
import os from "os";
|
|
1233
|
-
var execAsync3 = promisify3(exec3);
|
|
1234
|
-
var DEFAULT_SSH_OPTIONS = [
|
|
1235
|
-
"-o",
|
|
1236
|
-
"StrictHostKeyChecking=no",
|
|
1237
|
-
"-o",
|
|
1238
|
-
"UserKnownHostsFile=/dev/null",
|
|
1239
|
-
"-o",
|
|
1240
|
-
"LogLevel=ERROR",
|
|
1241
|
-
"-o",
|
|
1242
|
-
"ConnectTimeout=10",
|
|
1243
|
-
"-o",
|
|
1244
|
-
"ServerAliveInterval=5",
|
|
1245
|
-
"-o",
|
|
1246
|
-
"ServerAliveCountMax=3"
|
|
1247
|
-
];
|
|
1248
|
-
var SSHClient = class {
|
|
1249
|
-
config;
|
|
1250
|
-
sshOptions;
|
|
1251
|
-
constructor(config) {
|
|
1252
|
-
this.config = config;
|
|
1253
|
-
this.sshOptions = [...DEFAULT_SSH_OPTIONS];
|
|
1254
|
-
if (config.privateKeyPath) {
|
|
1255
|
-
this.sshOptions.push("-i", config.privateKeyPath);
|
|
1256
|
-
}
|
|
1257
|
-
if (config.connectTimeout) {
|
|
1258
|
-
const idx = this.sshOptions.indexOf("ConnectTimeout=10");
|
|
1259
|
-
if (idx !== -1) {
|
|
1260
|
-
this.sshOptions[idx] = `ConnectTimeout=${config.connectTimeout}`;
|
|
1261
|
-
}
|
|
1262
|
-
}
|
|
1263
|
-
}
|
|
1264
|
-
/**
|
|
1265
|
-
* Build SSH command prefix
|
|
1266
|
-
*/
|
|
1267
|
-
buildSSHCommand() {
|
|
1268
|
-
return [
|
|
1269
|
-
"ssh",
|
|
1270
|
-
...this.sshOptions,
|
|
1271
|
-
`${this.config.user}@${this.config.host}`
|
|
1272
|
-
];
|
|
1273
|
-
}
|
|
1274
|
-
/**
|
|
1275
|
-
* Execute a command on the remote VM
|
|
1276
|
-
* @param command - The command to execute
|
|
1277
|
-
* @param timeoutMs - Optional timeout in milliseconds (default: 300000ms = 5 minutes)
|
|
1278
|
-
*/
|
|
1279
|
-
async exec(command, timeoutMs) {
|
|
1280
|
-
const sshCmd = this.buildSSHCommand();
|
|
1281
|
-
const escapedCommand = command.replace(/'/g, "'\\''");
|
|
1282
|
-
const fullCmd = [...sshCmd, `'${escapedCommand}'`].join(" ");
|
|
1283
|
-
try {
|
|
1284
|
-
const { stdout, stderr } = await execAsync3(fullCmd, {
|
|
1285
|
-
maxBuffer: 50 * 1024 * 1024,
|
|
1286
|
-
// 50MB buffer
|
|
1287
|
-
timeout: timeoutMs ?? 3e5
|
|
1288
|
-
// Default 5 minutes, customizable per call
|
|
1289
|
-
});
|
|
1290
|
-
return {
|
|
1291
|
-
exitCode: 0,
|
|
1292
|
-
stdout: stdout || "",
|
|
1293
|
-
stderr: stderr || ""
|
|
1294
|
-
};
|
|
1295
|
-
} catch (error) {
|
|
1296
|
-
const execError = error;
|
|
1297
|
-
return {
|
|
1298
|
-
exitCode: execError.code ?? 1,
|
|
1299
|
-
stdout: execError.stdout || "",
|
|
1300
|
-
stderr: execError.stderr || execError.message || "Unknown error"
|
|
1301
|
-
};
|
|
1302
|
-
}
|
|
1303
|
-
}
|
|
1304
|
-
/**
|
|
1305
|
-
* Execute a command and throw on non-zero exit
|
|
1306
|
-
*/
|
|
1307
|
-
async execOrThrow(command) {
|
|
1308
|
-
const result = await this.exec(command);
|
|
1309
|
-
if (result.exitCode !== 0) {
|
|
1310
|
-
throw new Error(
|
|
1311
|
-
`Command failed (exit ${result.exitCode}): ${result.stderr || result.stdout}`
|
|
1312
|
-
);
|
|
1313
|
-
}
|
|
1314
|
-
return result.stdout;
|
|
1315
|
-
}
|
|
1316
|
-
/**
|
|
1317
|
-
* Write content to a file on the remote VM
|
|
1318
|
-
* Uses base64 encoding to safely transfer any content
|
|
1319
|
-
*/
|
|
1320
|
-
async writeFile(remotePath, content) {
|
|
1321
|
-
const encoded = Buffer.from(content).toString("base64");
|
|
1322
|
-
const maxChunkSize = 65e3;
|
|
1323
|
-
if (encoded.length <= maxChunkSize) {
|
|
1324
|
-
await this.execOrThrow(`echo '${encoded}' | base64 -d > '${remotePath}'`);
|
|
1325
|
-
} else {
|
|
1326
|
-
await this.execOrThrow(`rm -f '${remotePath}'`);
|
|
1327
|
-
for (let i = 0; i < encoded.length; i += maxChunkSize) {
|
|
1328
|
-
const chunk = encoded.slice(i, i + maxChunkSize);
|
|
1329
|
-
const operator = i === 0 ? ">" : ">>";
|
|
1330
|
-
await this.execOrThrow(
|
|
1331
|
-
`echo '${chunk}' | base64 -d ${operator} '${remotePath}'`
|
|
1332
|
-
);
|
|
1333
|
-
}
|
|
1334
|
-
}
|
|
1335
|
-
}
|
|
1336
|
-
/**
|
|
1337
|
-
* Write content to a file on the remote VM using sudo
|
|
1338
|
-
* Used for writing to privileged locations like /usr/local/bin
|
|
1339
|
-
*/
|
|
1340
|
-
async writeFileWithSudo(remotePath, content) {
|
|
1341
|
-
const encoded = Buffer.from(content).toString("base64");
|
|
1342
|
-
const maxChunkSize = 65e3;
|
|
1343
|
-
if (encoded.length <= maxChunkSize) {
|
|
1344
|
-
await this.execOrThrow(
|
|
1345
|
-
`echo '${encoded}' | base64 -d | sudo tee '${remotePath}' > /dev/null`
|
|
1346
|
-
);
|
|
1347
|
-
} else {
|
|
1348
|
-
await this.execOrThrow(`sudo rm -f '${remotePath}'`);
|
|
1349
|
-
for (let i = 0; i < encoded.length; i += maxChunkSize) {
|
|
1350
|
-
const chunk = encoded.slice(i, i + maxChunkSize);
|
|
1351
|
-
const teeFlag = i === 0 ? "" : "-a";
|
|
1352
|
-
await this.execOrThrow(
|
|
1353
|
-
`echo '${chunk}' | base64 -d | sudo tee ${teeFlag} '${remotePath}' > /dev/null`
|
|
1354
|
-
);
|
|
1355
|
-
}
|
|
1356
|
-
}
|
|
1357
|
-
}
|
|
1358
|
-
/**
|
|
1359
|
-
* Read a file from the remote VM
|
|
1360
|
-
*/
|
|
1361
|
-
async readFile(remotePath) {
|
|
1362
|
-
const result = await this.exec(`cat '${remotePath}'`);
|
|
1363
|
-
if (result.exitCode !== 0) {
|
|
1364
|
-
throw new Error(`Failed to read file: ${result.stderr}`);
|
|
1365
|
-
}
|
|
1366
|
-
return result.stdout;
|
|
1367
|
-
}
|
|
1368
|
-
/**
|
|
1369
|
-
* Check if SSH connection is available
|
|
1370
|
-
* Uses a short timeout (15s) to ensure waitUntilReachable() respects its outer timeout
|
|
1371
|
-
*/
|
|
1372
|
-
async isReachable() {
|
|
1373
|
-
try {
|
|
1374
|
-
const result = await this.exec("echo ok", 15e3);
|
|
1375
|
-
return result.exitCode === 0 && result.stdout.trim() === "ok";
|
|
1376
|
-
} catch {
|
|
1377
|
-
return false;
|
|
1378
|
-
}
|
|
1379
|
-
}
|
|
1380
|
-
/**
|
|
1381
|
-
* Wait for SSH to become available
|
|
1382
|
-
*/
|
|
1383
|
-
async waitUntilReachable(timeoutMs = 12e4, intervalMs = 2e3) {
|
|
1384
|
-
const start = Date.now();
|
|
1385
|
-
while (Date.now() - start < timeoutMs) {
|
|
1386
|
-
if (await this.isReachable()) {
|
|
1387
|
-
return;
|
|
1388
|
-
}
|
|
1389
|
-
await new Promise((resolve) => {
|
|
1390
|
-
const remaining = timeoutMs - (Date.now() - start);
|
|
1391
|
-
if (remaining > 0) {
|
|
1392
|
-
setTimeout(resolve, Math.min(intervalMs, remaining));
|
|
1393
|
-
} else {
|
|
1394
|
-
resolve();
|
|
1395
|
-
}
|
|
1396
|
-
});
|
|
1397
|
-
}
|
|
1398
|
-
throw new Error(
|
|
1399
|
-
`SSH not reachable after ${timeoutMs}ms at ${this.config.host}`
|
|
1400
|
-
);
|
|
1401
|
-
}
|
|
1402
|
-
/**
|
|
1403
|
-
* Create a directory on the remote VM (mkdir -p)
|
|
1404
|
-
*/
|
|
1405
|
-
async mkdir(remotePath) {
|
|
1406
|
-
await this.execOrThrow(`mkdir -p '${remotePath}'`);
|
|
1407
|
-
}
|
|
1408
|
-
/**
|
|
1409
|
-
* Check if a file/directory exists on the remote VM
|
|
1410
|
-
*/
|
|
1411
|
-
async exists(remotePath) {
|
|
1412
|
-
const result = await this.exec(`test -e '${remotePath}'`);
|
|
1413
|
-
return result.exitCode === 0;
|
|
1414
|
-
}
|
|
1415
|
-
/**
|
|
1416
|
-
* Get the host IP
|
|
1417
|
-
*/
|
|
1418
|
-
getHost() {
|
|
1419
|
-
return this.config.host;
|
|
1420
|
-
}
|
|
1421
|
-
};
|
|
1422
|
-
function getRunnerSSHKeyPath() {
|
|
1423
|
-
const runnerKeyPath = "/opt/vm0-runner/ssh/id_rsa";
|
|
1424
|
-
if (fs4.existsSync(runnerKeyPath)) {
|
|
1425
|
-
return runnerKeyPath;
|
|
1426
|
-
}
|
|
1427
|
-
const userKeyPath = path3.join(os.homedir(), ".ssh", "id_rsa");
|
|
1428
|
-
if (fs4.existsSync(userKeyPath)) {
|
|
1429
|
-
return userKeyPath;
|
|
1430
|
-
}
|
|
1431
|
-
return "";
|
|
1432
|
-
}
|
|
1433
|
-
|
|
1434
1224
|
// src/lib/firecracker/vsock.ts
|
|
1435
1225
|
import * as net from "net";
|
|
1436
|
-
import * as
|
|
1226
|
+
import * as fs4 from "fs";
|
|
1437
1227
|
import * as crypto from "crypto";
|
|
1438
1228
|
var VSOCK_PORT = 1e3;
|
|
1439
1229
|
var CONNECT_TIMEOUT_MS = 5e3;
|
|
@@ -1479,7 +1269,7 @@ var VsockClient = class {
|
|
|
1479
1269
|
return;
|
|
1480
1270
|
}
|
|
1481
1271
|
return new Promise((resolve, reject) => {
|
|
1482
|
-
if (!
|
|
1272
|
+
if (!fs4.existsSync(this.vsockPath)) {
|
|
1483
1273
|
reject(new Error(`Vsock socket not found: ${this.vsockPath}`));
|
|
1484
1274
|
return;
|
|
1485
1275
|
}
|
|
@@ -2099,8 +1889,8 @@ function getErrorMap() {
|
|
|
2099
1889
|
return overrideErrorMap;
|
|
2100
1890
|
}
|
|
2101
1891
|
var makeIssue = (params) => {
|
|
2102
|
-
const { data, path:
|
|
2103
|
-
const fullPath = [...
|
|
1892
|
+
const { data, path: path6, errorMaps, issueData } = params;
|
|
1893
|
+
const fullPath = [...path6, ...issueData.path || []];
|
|
2104
1894
|
const fullIssue = {
|
|
2105
1895
|
...issueData,
|
|
2106
1896
|
path: fullPath
|
|
@@ -2199,11 +1989,11 @@ var errorUtil;
|
|
|
2199
1989
|
errorUtil2.toString = (message) => typeof message === "string" ? message : message === null || message === void 0 ? void 0 : message.message;
|
|
2200
1990
|
})(errorUtil || (errorUtil = {}));
|
|
2201
1991
|
var ParseInputLazyPath = class {
|
|
2202
|
-
constructor(parent, value,
|
|
1992
|
+
constructor(parent, value, path6, key) {
|
|
2203
1993
|
this._cachedPath = [];
|
|
2204
1994
|
this.parent = parent;
|
|
2205
1995
|
this.data = value;
|
|
2206
|
-
this._path =
|
|
1996
|
+
this._path = path6;
|
|
2207
1997
|
this._key = key;
|
|
2208
1998
|
}
|
|
2209
1999
|
get path() {
|
|
@@ -5969,7 +5759,7 @@ var unifiedRunRequestSchema = z6.object({
|
|
|
5969
5759
|
volumeVersions: z6.record(z6.string(), z6.string()).optional(),
|
|
5970
5760
|
// Debug flag to force real Claude in mock environments (internal use only)
|
|
5971
5761
|
debugNoMockClaude: z6.boolean().optional(),
|
|
5972
|
-
// Model provider for automatic
|
|
5762
|
+
// Model provider for automatic credential injection
|
|
5973
5763
|
modelProvider: z6.string().optional(),
|
|
5974
5764
|
// Required
|
|
5975
5765
|
prompt: z6.string().min(1, "Missing prompt")
|
|
@@ -8113,20 +7903,46 @@ var SCRIPT_PATHS = {
|
|
|
8113
7903
|
};
|
|
8114
7904
|
|
|
8115
7905
|
// ../../packages/core/src/feature-switch.ts
|
|
8116
|
-
var PricingSwitch = {
|
|
8117
|
-
key: "pricing" /* Pricing */,
|
|
8118
|
-
maintainer: "ethan@vm0.ai",
|
|
8119
|
-
enabled: false
|
|
8120
|
-
};
|
|
8121
7906
|
var FEATURE_SWITCHES = {
|
|
8122
|
-
["pricing" /* Pricing */]:
|
|
7907
|
+
["pricing" /* Pricing */]: {
|
|
7908
|
+
maintainer: "ethan@vm0.ai",
|
|
7909
|
+
enabled: false
|
|
7910
|
+
},
|
|
7911
|
+
["dummy" /* Dummy */]: {
|
|
7912
|
+
maintainer: "ethan@vm0.ai",
|
|
7913
|
+
enabled: true
|
|
7914
|
+
},
|
|
7915
|
+
["platformOnboarding" /* PlatformOnboarding */]: {
|
|
7916
|
+
maintainer: "ethan@vm0.ai",
|
|
7917
|
+
enabled: false
|
|
7918
|
+
},
|
|
7919
|
+
["platformAgents" /* PlatformAgents */]: {
|
|
7920
|
+
maintainer: "ethan@vm0.ai",
|
|
7921
|
+
enabled: false
|
|
7922
|
+
},
|
|
7923
|
+
["platformSecrets" /* PlatformSecrets */]: {
|
|
7924
|
+
maintainer: "ethan@vm0.ai",
|
|
7925
|
+
enabled: false
|
|
7926
|
+
},
|
|
7927
|
+
["platformArtifacts" /* PlatformArtifacts */]: {
|
|
7928
|
+
maintainer: "ethan@vm0.ai",
|
|
7929
|
+
enabled: false
|
|
7930
|
+
},
|
|
7931
|
+
["platformApiKeys" /* PlatformApiKeys */]: {
|
|
7932
|
+
maintainer: "ethan@vm0.ai",
|
|
7933
|
+
enabled: false
|
|
7934
|
+
},
|
|
7935
|
+
["platformLogs" /* PlatformLogs */]: {
|
|
7936
|
+
maintainer: "ethan@vm0.ai",
|
|
7937
|
+
enabled: false
|
|
7938
|
+
}
|
|
8123
7939
|
};
|
|
8124
7940
|
|
|
8125
7941
|
// src/lib/scripts/index.ts
|
|
8126
7942
|
var ENV_LOADER_PATH = "/usr/local/bin/vm0-agent/env-loader.mjs";
|
|
8127
7943
|
|
|
8128
7944
|
// src/lib/proxy/vm-registry.ts
|
|
8129
|
-
import
|
|
7945
|
+
import fs5 from "fs";
|
|
8130
7946
|
var DEFAULT_REGISTRY_PATH = "/tmp/vm0-vm-registry.json";
|
|
8131
7947
|
var VMRegistry = class {
|
|
8132
7948
|
registryPath;
|
|
@@ -8140,8 +7956,8 @@ var VMRegistry = class {
|
|
|
8140
7956
|
*/
|
|
8141
7957
|
load() {
|
|
8142
7958
|
try {
|
|
8143
|
-
if (
|
|
8144
|
-
const content =
|
|
7959
|
+
if (fs5.existsSync(this.registryPath)) {
|
|
7960
|
+
const content = fs5.readFileSync(this.registryPath, "utf-8");
|
|
8145
7961
|
return JSON.parse(content);
|
|
8146
7962
|
}
|
|
8147
7963
|
} catch {
|
|
@@ -8155,8 +7971,8 @@ var VMRegistry = class {
|
|
|
8155
7971
|
this.data.updatedAt = Date.now();
|
|
8156
7972
|
const content = JSON.stringify(this.data, null, 2);
|
|
8157
7973
|
const tempPath = `${this.registryPath}.tmp`;
|
|
8158
|
-
|
|
8159
|
-
|
|
7974
|
+
fs5.writeFileSync(tempPath, content, { mode: 420 });
|
|
7975
|
+
fs5.renameSync(tempPath, this.registryPath);
|
|
8160
7976
|
}
|
|
8161
7977
|
/**
|
|
8162
7978
|
* Register a VM with its IP address
|
|
@@ -8231,8 +8047,8 @@ function initVMRegistry(registryPath) {
|
|
|
8231
8047
|
|
|
8232
8048
|
// src/lib/proxy/proxy-manager.ts
|
|
8233
8049
|
import { spawn as spawn2 } from "child_process";
|
|
8234
|
-
import
|
|
8235
|
-
import
|
|
8050
|
+
import fs6 from "fs";
|
|
8051
|
+
import path3 from "path";
|
|
8236
8052
|
|
|
8237
8053
|
// src/lib/proxy/mitm-addon-script.ts
|
|
8238
8054
|
var RUNNER_MITM_ADDON_SCRIPT = `#!/usr/bin/env python3
|
|
@@ -8725,7 +8541,7 @@ var ProxyManager = class {
|
|
|
8725
8541
|
process = null;
|
|
8726
8542
|
isRunning = false;
|
|
8727
8543
|
constructor(config) {
|
|
8728
|
-
const addonPath =
|
|
8544
|
+
const addonPath = path3.join(config.caDir, "mitm_addon.py");
|
|
8729
8545
|
this.config = {
|
|
8730
8546
|
...DEFAULT_PROXY_OPTIONS,
|
|
8731
8547
|
...config,
|
|
@@ -8752,11 +8568,11 @@ var ProxyManager = class {
|
|
|
8752
8568
|
* Ensure the addon script exists at the configured path
|
|
8753
8569
|
*/
|
|
8754
8570
|
ensureAddonScript() {
|
|
8755
|
-
const addonDir =
|
|
8756
|
-
if (!
|
|
8757
|
-
|
|
8571
|
+
const addonDir = path3.dirname(this.config.addonPath);
|
|
8572
|
+
if (!fs6.existsSync(addonDir)) {
|
|
8573
|
+
fs6.mkdirSync(addonDir, { recursive: true });
|
|
8758
8574
|
}
|
|
8759
|
-
|
|
8575
|
+
fs6.writeFileSync(this.config.addonPath, RUNNER_MITM_ADDON_SCRIPT, {
|
|
8760
8576
|
mode: 493
|
|
8761
8577
|
});
|
|
8762
8578
|
console.log(
|
|
@@ -8767,11 +8583,11 @@ var ProxyManager = class {
|
|
|
8767
8583
|
* Validate proxy configuration
|
|
8768
8584
|
*/
|
|
8769
8585
|
validateConfig() {
|
|
8770
|
-
if (!
|
|
8586
|
+
if (!fs6.existsSync(this.config.caDir)) {
|
|
8771
8587
|
throw new Error(`Proxy CA directory not found: ${this.config.caDir}`);
|
|
8772
8588
|
}
|
|
8773
|
-
const caCertPath =
|
|
8774
|
-
if (!
|
|
8589
|
+
const caCertPath = path3.join(this.config.caDir, "mitmproxy-ca.pem");
|
|
8590
|
+
if (!fs6.existsSync(caCertPath)) {
|
|
8775
8591
|
throw new Error(`Proxy CA certificate not found: ${caCertPath}`);
|
|
8776
8592
|
}
|
|
8777
8593
|
this.ensureAddonScript();
|
|
@@ -9161,17 +8977,17 @@ function buildEnvironmentVariables(context, apiUrl) {
|
|
|
9161
8977
|
}
|
|
9162
8978
|
|
|
9163
8979
|
// src/lib/network-logs/network-logs.ts
|
|
9164
|
-
import
|
|
8980
|
+
import fs7 from "fs";
|
|
9165
8981
|
function getNetworkLogPath(runId) {
|
|
9166
8982
|
return `/tmp/vm0-network-${runId}.jsonl`;
|
|
9167
8983
|
}
|
|
9168
8984
|
function readNetworkLogs(runId) {
|
|
9169
8985
|
const logPath = getNetworkLogPath(runId);
|
|
9170
|
-
if (!
|
|
8986
|
+
if (!fs7.existsSync(logPath)) {
|
|
9171
8987
|
return [];
|
|
9172
8988
|
}
|
|
9173
8989
|
try {
|
|
9174
|
-
const content =
|
|
8990
|
+
const content = fs7.readFileSync(logPath, "utf-8");
|
|
9175
8991
|
const lines = content.split("\n").filter((line) => line.trim());
|
|
9176
8992
|
return lines.map((line) => JSON.parse(line));
|
|
9177
8993
|
} catch (err) {
|
|
@@ -9184,8 +9000,8 @@ function readNetworkLogs(runId) {
|
|
|
9184
9000
|
function cleanupNetworkLogs(runId) {
|
|
9185
9001
|
const logPath = getNetworkLogPath(runId);
|
|
9186
9002
|
try {
|
|
9187
|
-
if (
|
|
9188
|
-
|
|
9003
|
+
if (fs7.existsSync(logPath)) {
|
|
9004
|
+
fs7.unlinkSync(logPath);
|
|
9189
9005
|
}
|
|
9190
9006
|
} catch (err) {
|
|
9191
9007
|
console.error(
|
|
@@ -9228,7 +9044,7 @@ async function uploadNetworkLogs(apiUrl, sandboxToken, runId) {
|
|
|
9228
9044
|
}
|
|
9229
9045
|
|
|
9230
9046
|
// src/lib/vm-setup/vm-setup.ts
|
|
9231
|
-
import
|
|
9047
|
+
import fs8 from "fs";
|
|
9232
9048
|
|
|
9233
9049
|
// src/lib/scripts/utils.ts
|
|
9234
9050
|
function getAllScripts() {
|
|
@@ -9290,12 +9106,12 @@ async function restoreSessionHistory(guest, resumeSession, workingDir, cliAgentT
|
|
|
9290
9106
|
);
|
|
9291
9107
|
}
|
|
9292
9108
|
async function installProxyCA(guest, caCertPath) {
|
|
9293
|
-
if (!
|
|
9109
|
+
if (!fs8.existsSync(caCertPath)) {
|
|
9294
9110
|
throw new Error(
|
|
9295
9111
|
`Proxy CA certificate not found at ${caCertPath}. Run generate-proxy-ca.sh first.`
|
|
9296
9112
|
);
|
|
9297
9113
|
}
|
|
9298
|
-
const caCert =
|
|
9114
|
+
const caCert = fs8.readFileSync(caCertPath, "utf-8");
|
|
9299
9115
|
console.log(
|
|
9300
9116
|
`[Executor] Installing proxy CA certificate (${caCert.length} bytes)`
|
|
9301
9117
|
);
|
|
@@ -9376,7 +9192,7 @@ async function executeJob(context, config, options = {}) {
|
|
|
9376
9192
|
const log = options.logger ?? ((msg) => console.log(msg));
|
|
9377
9193
|
log(`[Executor] Starting job ${context.runId} in VM ${vmId}`);
|
|
9378
9194
|
try {
|
|
9379
|
-
const workspacesDir =
|
|
9195
|
+
const workspacesDir = path4.join(process.cwd(), "workspaces");
|
|
9380
9196
|
const vmConfig = {
|
|
9381
9197
|
vmId,
|
|
9382
9198
|
vcpus: config.sandbox.vcpu,
|
|
@@ -9384,7 +9200,7 @@ async function executeJob(context, config, options = {}) {
|
|
|
9384
9200
|
kernelPath: config.firecracker.kernel,
|
|
9385
9201
|
rootfsPath: config.firecracker.rootfs,
|
|
9386
9202
|
firecrackerBinary: config.firecracker.binary,
|
|
9387
|
-
workDir:
|
|
9203
|
+
workDir: path4.join(workspacesDir, `vm0-${vmId}`)
|
|
9388
9204
|
};
|
|
9389
9205
|
log(`[Executor] Creating VM ${vmId}...`);
|
|
9390
9206
|
vm = new FirecrackerVM(vmConfig);
|
|
@@ -9394,27 +9210,15 @@ async function executeJob(context, config, options = {}) {
|
|
|
9394
9210
|
throw new Error("VM started but no IP address available");
|
|
9395
9211
|
}
|
|
9396
9212
|
log(`[Executor] VM ${vmId} started, guest IP: ${guestIp}`);
|
|
9397
|
-
const
|
|
9398
|
-
|
|
9399
|
-
|
|
9400
|
-
|
|
9401
|
-
guest = new SSHClient({
|
|
9402
|
-
host: guestIp,
|
|
9403
|
-
user: "user",
|
|
9404
|
-
privateKeyPath: sshKeyPath || void 0
|
|
9405
|
-
});
|
|
9406
|
-
log(`[Executor] Using SSH for guest communication: ${guestIp}`);
|
|
9407
|
-
} else {
|
|
9408
|
-
const vsockPath = vm.getVsockPath();
|
|
9409
|
-
guest = new VsockClient(vsockPath);
|
|
9410
|
-
log(`[Executor] Using vsock for guest communication: ${vsockPath}`);
|
|
9411
|
-
}
|
|
9412
|
-
log(`[Executor] Verifying ${guestProtocol} connectivity...`);
|
|
9213
|
+
const vsockPath = vm.getVsockPath();
|
|
9214
|
+
const guest = new VsockClient(vsockPath);
|
|
9215
|
+
log(`[Executor] Using vsock for guest communication: ${vsockPath}`);
|
|
9216
|
+
log(`[Executor] Verifying vsock connectivity...`);
|
|
9413
9217
|
await withSandboxTiming(
|
|
9414
9218
|
"guest_wait",
|
|
9415
9219
|
() => guest.waitUntilReachable(3e4, 1e3)
|
|
9416
9220
|
);
|
|
9417
|
-
log(`[Executor] Guest client ready
|
|
9221
|
+
log(`[Executor] Guest client ready`);
|
|
9418
9222
|
const firewallConfig = context.experimentalFirewall;
|
|
9419
9223
|
if (firewallConfig?.enabled) {
|
|
9420
9224
|
const mitmEnabled = firewallConfig.experimental_mitm ?? false;
|
|
@@ -9429,7 +9233,7 @@ async function executeJob(context, config, options = {}) {
|
|
|
9429
9233
|
sealSecretsEnabled
|
|
9430
9234
|
});
|
|
9431
9235
|
if (mitmEnabled) {
|
|
9432
|
-
const caCertPath =
|
|
9236
|
+
const caCertPath = path4.join(
|
|
9433
9237
|
config.proxy.ca_dir,
|
|
9434
9238
|
"mitmproxy-ca-cert.pem"
|
|
9435
9239
|
);
|
|
@@ -9859,7 +9663,7 @@ import { dirname as dirname2, join as join3 } from "path";
|
|
|
9859
9663
|
|
|
9860
9664
|
// src/lib/firecracker/process.ts
|
|
9861
9665
|
import { readdirSync, readFileSync as readFileSync2, existsSync as existsSync3 } from "fs";
|
|
9862
|
-
import
|
|
9666
|
+
import path5 from "path";
|
|
9863
9667
|
function parseFirecrackerCmdline(cmdline) {
|
|
9864
9668
|
const args = cmdline.split("\0");
|
|
9865
9669
|
if (!args[0]?.includes("firecracker")) return null;
|
|
@@ -9892,7 +9696,7 @@ function findFirecrackerProcesses() {
|
|
|
9892
9696
|
for (const entry of entries) {
|
|
9893
9697
|
if (!/^\d+$/.test(entry)) continue;
|
|
9894
9698
|
const pid = parseInt(entry, 10);
|
|
9895
|
-
const cmdlinePath =
|
|
9699
|
+
const cmdlinePath = path5.join(procDir, entry, "cmdline");
|
|
9896
9700
|
if (!existsSync3(cmdlinePath)) continue;
|
|
9897
9701
|
try {
|
|
9898
9702
|
const cmdline = readFileSync2(cmdlinePath, "utf-8");
|
|
@@ -9949,7 +9753,7 @@ function findMitmproxyProcess() {
|
|
|
9949
9753
|
for (const entry of entries) {
|
|
9950
9754
|
if (!/^\d+$/.test(entry)) continue;
|
|
9951
9755
|
const pid = parseInt(entry, 10);
|
|
9952
|
-
const cmdlinePath =
|
|
9756
|
+
const cmdlinePath = path5.join(procDir, entry, "cmdline");
|
|
9953
9757
|
if (!existsSync3(cmdlinePath)) continue;
|
|
9954
9758
|
try {
|
|
9955
9759
|
const cmdline = readFileSync2(cmdlinePath, "utf-8");
|
|
@@ -10345,7 +10149,7 @@ async function confirm(message) {
|
|
|
10345
10149
|
}
|
|
10346
10150
|
|
|
10347
10151
|
// src/commands/benchmark.ts
|
|
10348
|
-
import { Command as Command4
|
|
10152
|
+
import { Command as Command4 } from "commander";
|
|
10349
10153
|
import crypto2 from "crypto";
|
|
10350
10154
|
|
|
10351
10155
|
// src/lib/timing.ts
|
|
@@ -10398,19 +10202,11 @@ function createBenchmarkContext(prompt, options) {
|
|
|
10398
10202
|
}
|
|
10399
10203
|
var benchmarkCommand = new Command4("benchmark").description(
|
|
10400
10204
|
"Run a VM performance benchmark (executes bash command directly)"
|
|
10401
|
-
).argument("<prompt>", "The bash command to execute in the VM").option("--config <path>", "Config file path", "./runner.yaml").option("--working-dir <path>", "Working directory in VM", "/home/user").option("--agent-type <type>", "Agent type", "claude-code").
|
|
10402
|
-
new Option(
|
|
10403
|
-
"--guest-protocol <protocol>",
|
|
10404
|
-
"Guest communication protocol"
|
|
10405
|
-
).choices(["vsock", "ssh"])
|
|
10406
|
-
).action(async (prompt, options) => {
|
|
10205
|
+
).argument("<prompt>", "The bash command to execute in the VM").option("--config <path>", "Config file path", "./runner.yaml").option("--working-dir <path>", "Working directory in VM", "/home/user").option("--agent-type <type>", "Agent type", "claude-code").action(async (prompt, options) => {
|
|
10407
10206
|
const timer = new Timer();
|
|
10408
10207
|
try {
|
|
10409
10208
|
timer.log("Loading configuration...");
|
|
10410
10209
|
const config = loadDebugConfig(options.config);
|
|
10411
|
-
if (options.guestProtocol) {
|
|
10412
|
-
config.sandbox.guest_protocol = options.guestProtocol;
|
|
10413
|
-
}
|
|
10414
10210
|
validateFirecrackerPaths(config.firecracker);
|
|
10415
10211
|
timer.log("Checking network prerequisites...");
|
|
10416
10212
|
const networkCheck = checkNetworkPrerequisites();
|
|
@@ -10444,7 +10240,7 @@ var benchmarkCommand = new Command4("benchmark").description(
|
|
|
10444
10240
|
});
|
|
10445
10241
|
|
|
10446
10242
|
// src/index.ts
|
|
10447
|
-
var version = true ? "
|
|
10243
|
+
var version = true ? "3.0.1" : "0.1.0";
|
|
10448
10244
|
program.name("vm0-runner").version(version).description("Self-hosted runner for VM0 agents");
|
|
10449
10245
|
program.addCommand(startCommand);
|
|
10450
10246
|
program.addCommand(doctorCommand);
|