@vm0/runner 3.0.0 → 3.0.2
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 +114 -123
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -1226,7 +1226,6 @@ import * as net from "net";
|
|
|
1226
1226
|
import * as fs4 from "fs";
|
|
1227
1227
|
import * as crypto from "crypto";
|
|
1228
1228
|
var VSOCK_PORT = 1e3;
|
|
1229
|
-
var CONNECT_TIMEOUT_MS = 5e3;
|
|
1230
1229
|
var HEADER_SIZE = 4;
|
|
1231
1230
|
var MAX_MESSAGE_SIZE = 1024 * 1024;
|
|
1232
1231
|
var DEFAULT_EXEC_TIMEOUT_MS = 3e5;
|
|
@@ -1261,90 +1260,6 @@ var VsockClient = class {
|
|
|
1261
1260
|
constructor(vsockPath) {
|
|
1262
1261
|
this.vsockPath = vsockPath;
|
|
1263
1262
|
}
|
|
1264
|
-
/**
|
|
1265
|
-
* Connect to the guest agent via vsock
|
|
1266
|
-
*/
|
|
1267
|
-
async connect() {
|
|
1268
|
-
if (this.connected && this.socket) {
|
|
1269
|
-
return;
|
|
1270
|
-
}
|
|
1271
|
-
return new Promise((resolve, reject) => {
|
|
1272
|
-
if (!fs4.existsSync(this.vsockPath)) {
|
|
1273
|
-
reject(new Error(`Vsock socket not found: ${this.vsockPath}`));
|
|
1274
|
-
return;
|
|
1275
|
-
}
|
|
1276
|
-
const socket = net.createConnection(this.vsockPath);
|
|
1277
|
-
const decoder = new Decoder();
|
|
1278
|
-
let fcConnected = false;
|
|
1279
|
-
let gotReady = false;
|
|
1280
|
-
let pingId = null;
|
|
1281
|
-
let connectionEstablished = false;
|
|
1282
|
-
const timeout = setTimeout(() => {
|
|
1283
|
-
socket.destroy();
|
|
1284
|
-
reject(new Error("Vsock connection timeout"));
|
|
1285
|
-
}, CONNECT_TIMEOUT_MS);
|
|
1286
|
-
socket.on("connect", () => {
|
|
1287
|
-
socket.write(`CONNECT ${VSOCK_PORT}
|
|
1288
|
-
`);
|
|
1289
|
-
});
|
|
1290
|
-
socket.on("data", (data) => {
|
|
1291
|
-
if (!fcConnected) {
|
|
1292
|
-
const str = data.toString();
|
|
1293
|
-
if (str.startsWith("OK ")) {
|
|
1294
|
-
fcConnected = true;
|
|
1295
|
-
} else {
|
|
1296
|
-
clearTimeout(timeout);
|
|
1297
|
-
socket.destroy();
|
|
1298
|
-
reject(new Error(`Firecracker connect failed: ${str.trim()}`));
|
|
1299
|
-
}
|
|
1300
|
-
return;
|
|
1301
|
-
}
|
|
1302
|
-
try {
|
|
1303
|
-
for (const msg of decoder.decode(data)) {
|
|
1304
|
-
if (!connectionEstablished) {
|
|
1305
|
-
if (!gotReady && msg.type === "ready") {
|
|
1306
|
-
gotReady = true;
|
|
1307
|
-
pingId = crypto.randomUUID();
|
|
1308
|
-
const ping = { type: "ping", id: pingId, payload: {} };
|
|
1309
|
-
socket.write(encode(ping));
|
|
1310
|
-
} else if (msg.type === "pong" && msg.id === pingId) {
|
|
1311
|
-
clearTimeout(timeout);
|
|
1312
|
-
this.socket = socket;
|
|
1313
|
-
this.connected = true;
|
|
1314
|
-
connectionEstablished = true;
|
|
1315
|
-
resolve();
|
|
1316
|
-
}
|
|
1317
|
-
} else {
|
|
1318
|
-
this.handleMessage(msg);
|
|
1319
|
-
}
|
|
1320
|
-
}
|
|
1321
|
-
} catch (e) {
|
|
1322
|
-
clearTimeout(timeout);
|
|
1323
|
-
socket.destroy();
|
|
1324
|
-
reject(new Error(`Failed to parse message: ${e}`));
|
|
1325
|
-
}
|
|
1326
|
-
});
|
|
1327
|
-
socket.on("error", (err) => {
|
|
1328
|
-
clearTimeout(timeout);
|
|
1329
|
-
this.connected = false;
|
|
1330
|
-
this.socket = null;
|
|
1331
|
-
reject(new Error(`Vsock error: ${err.message}`));
|
|
1332
|
-
});
|
|
1333
|
-
socket.on("close", () => {
|
|
1334
|
-
clearTimeout(timeout);
|
|
1335
|
-
this.connected = false;
|
|
1336
|
-
this.socket = null;
|
|
1337
|
-
if (!gotReady) {
|
|
1338
|
-
reject(new Error("Vsock closed before ready"));
|
|
1339
|
-
}
|
|
1340
|
-
for (const [id, req] of this.pendingRequests) {
|
|
1341
|
-
clearTimeout(req.timeout);
|
|
1342
|
-
req.reject(new Error("Connection closed"));
|
|
1343
|
-
this.pendingRequests.delete(id);
|
|
1344
|
-
}
|
|
1345
|
-
});
|
|
1346
|
-
});
|
|
1347
|
-
}
|
|
1348
1263
|
/**
|
|
1349
1264
|
* Handle incoming message and route to pending request
|
|
1350
1265
|
*/
|
|
@@ -1360,9 +1275,8 @@ var VsockClient = class {
|
|
|
1360
1275
|
* Send a request and wait for response
|
|
1361
1276
|
*/
|
|
1362
1277
|
async request(type, payload, timeoutMs) {
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
throw new Error("Not connected");
|
|
1278
|
+
if (!this.connected || !this.socket) {
|
|
1279
|
+
throw new Error("Not connected - call waitForGuestConnection() first");
|
|
1366
1280
|
}
|
|
1367
1281
|
const id = crypto.randomUUID();
|
|
1368
1282
|
const msg = { type, id, payload };
|
|
@@ -1486,24 +1400,113 @@ var VsockClient = class {
|
|
|
1486
1400
|
}
|
|
1487
1401
|
}
|
|
1488
1402
|
/**
|
|
1489
|
-
* Wait for
|
|
1403
|
+
* Wait for guest to connect (Guest-initiated mode)
|
|
1404
|
+
*
|
|
1405
|
+
* Instead of polling, this listens on "{vsockPath}_{port}" and waits
|
|
1406
|
+
* for the guest to actively connect. This provides zero-latency
|
|
1407
|
+
* notification when the guest is ready.
|
|
1408
|
+
*
|
|
1409
|
+
* Flow:
|
|
1410
|
+
* 1. Host creates UDS server at "{vsockPath}_{port}"
|
|
1411
|
+
* 2. Guest boots and vsock-agent connects to CID=2, port
|
|
1412
|
+
* 3. Firecracker forwards connection to Host's UDS
|
|
1413
|
+
* 4. Host accepts, receives "ready", sends ping/pong
|
|
1490
1414
|
*/
|
|
1491
|
-
async
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1415
|
+
async waitForGuestConnection(timeoutMs = 3e4) {
|
|
1416
|
+
if (this.connected && this.socket) {
|
|
1417
|
+
return;
|
|
1418
|
+
}
|
|
1419
|
+
const listenerPath = `${this.vsockPath}_${VSOCK_PORT}`;
|
|
1420
|
+
if (fs4.existsSync(listenerPath)) {
|
|
1421
|
+
fs4.unlinkSync(listenerPath);
|
|
1422
|
+
}
|
|
1423
|
+
return new Promise((resolve, reject) => {
|
|
1424
|
+
const server = net.createServer();
|
|
1425
|
+
const decoder = new Decoder();
|
|
1426
|
+
let settled = false;
|
|
1427
|
+
const timeout = setTimeout(() => {
|
|
1428
|
+
if (!settled) {
|
|
1429
|
+
settled = true;
|
|
1430
|
+
server.close();
|
|
1431
|
+
if (fs4.existsSync(listenerPath)) {
|
|
1432
|
+
fs4.unlinkSync(listenerPath);
|
|
1433
|
+
}
|
|
1434
|
+
reject(new Error(`Guest connection timeout after ${timeoutMs}ms`));
|
|
1503
1435
|
}
|
|
1436
|
+
}, timeoutMs);
|
|
1437
|
+
const cleanup = (err) => {
|
|
1438
|
+
if (!settled) {
|
|
1439
|
+
settled = true;
|
|
1440
|
+
clearTimeout(timeout);
|
|
1441
|
+
server.close();
|
|
1442
|
+
if (fs4.existsSync(listenerPath)) {
|
|
1443
|
+
fs4.unlinkSync(listenerPath);
|
|
1444
|
+
}
|
|
1445
|
+
reject(err);
|
|
1446
|
+
}
|
|
1447
|
+
};
|
|
1448
|
+
server.on("error", (err) => {
|
|
1449
|
+
cleanup(new Error(`Server error: ${err.message}`));
|
|
1504
1450
|
});
|
|
1505
|
-
|
|
1506
|
-
|
|
1451
|
+
server.on("connection", (socket) => {
|
|
1452
|
+
server.close();
|
|
1453
|
+
let State;
|
|
1454
|
+
((State2) => {
|
|
1455
|
+
State2[State2["WaitingForReady"] = 0] = "WaitingForReady";
|
|
1456
|
+
State2[State2["WaitingForPong"] = 1] = "WaitingForPong";
|
|
1457
|
+
State2[State2["Connected"] = 2] = "Connected";
|
|
1458
|
+
})(State || (State = {}));
|
|
1459
|
+
let state = 0 /* WaitingForReady */;
|
|
1460
|
+
let pingId = null;
|
|
1461
|
+
socket.on("data", (data) => {
|
|
1462
|
+
try {
|
|
1463
|
+
for (const msg of decoder.decode(data)) {
|
|
1464
|
+
if (state === 0 /* WaitingForReady */ && msg.type === "ready") {
|
|
1465
|
+
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) {
|
|
1469
|
+
if (settled) {
|
|
1470
|
+
socket.destroy();
|
|
1471
|
+
return;
|
|
1472
|
+
}
|
|
1473
|
+
settled = true;
|
|
1474
|
+
clearTimeout(timeout);
|
|
1475
|
+
if (fs4.existsSync(listenerPath)) {
|
|
1476
|
+
fs4.unlinkSync(listenerPath);
|
|
1477
|
+
}
|
|
1478
|
+
state = 2 /* Connected */;
|
|
1479
|
+
this.socket = socket;
|
|
1480
|
+
this.connected = true;
|
|
1481
|
+
resolve();
|
|
1482
|
+
} else if (state === 2 /* Connected */) {
|
|
1483
|
+
this.handleMessage(msg);
|
|
1484
|
+
}
|
|
1485
|
+
}
|
|
1486
|
+
} catch (e) {
|
|
1487
|
+
cleanup(new Error(`Failed to parse message: ${e}`));
|
|
1488
|
+
}
|
|
1489
|
+
});
|
|
1490
|
+
socket.on("error", (err) => {
|
|
1491
|
+
cleanup(new Error(`Socket error: ${err.message}`));
|
|
1492
|
+
});
|
|
1493
|
+
socket.on("close", () => {
|
|
1494
|
+
if (!settled) {
|
|
1495
|
+
cleanup(new Error("Guest disconnected before ready"));
|
|
1496
|
+
}
|
|
1497
|
+
this.connected = false;
|
|
1498
|
+
this.socket = null;
|
|
1499
|
+
const pending = Array.from(this.pendingRequests.values());
|
|
1500
|
+
this.pendingRequests.clear();
|
|
1501
|
+
for (const req of pending) {
|
|
1502
|
+
clearTimeout(req.timeout);
|
|
1503
|
+
req.reject(new Error("Connection closed"));
|
|
1504
|
+
}
|
|
1505
|
+
});
|
|
1506
|
+
});
|
|
1507
|
+
server.listen(listenerPath, () => {
|
|
1508
|
+
});
|
|
1509
|
+
});
|
|
1507
1510
|
}
|
|
1508
1511
|
/**
|
|
1509
1512
|
* Create a directory on the remote VM
|
|
@@ -1533,10 +1536,11 @@ var VsockClient = class {
|
|
|
1533
1536
|
this.socket = null;
|
|
1534
1537
|
}
|
|
1535
1538
|
this.connected = false;
|
|
1536
|
-
|
|
1539
|
+
const pending = Array.from(this.pendingRequests.values());
|
|
1540
|
+
this.pendingRequests.clear();
|
|
1541
|
+
for (const req of pending) {
|
|
1537
1542
|
clearTimeout(req.timeout);
|
|
1538
1543
|
req.reject(new Error("Connection closed"));
|
|
1539
|
-
this.pendingRequests.delete(id);
|
|
1540
1544
|
}
|
|
1541
1545
|
}
|
|
1542
1546
|
};
|
|
@@ -5542,9 +5546,6 @@ var appStringSchema = z5.string().superRefine((val, ctx) => {
|
|
|
5542
5546
|
});
|
|
5543
5547
|
}
|
|
5544
5548
|
});
|
|
5545
|
-
var nonEmptyStringArraySchema = z5.array(
|
|
5546
|
-
z5.string().min(1, "Array entries cannot be empty strings")
|
|
5547
|
-
);
|
|
5548
5549
|
var agentDefinitionSchema = z5.object({
|
|
5549
5550
|
description: z5.string().optional(),
|
|
5550
5551
|
/**
|
|
@@ -5589,17 +5590,7 @@ var agentDefinitionSchema = z5.object({
|
|
|
5589
5590
|
* Requires experimental_runner to be configured.
|
|
5590
5591
|
* When enabled, filters outbound traffic by domain/IP rules.
|
|
5591
5592
|
*/
|
|
5592
|
-
experimental_firewall: experimentalFirewallSchema.optional()
|
|
5593
|
-
/**
|
|
5594
|
-
* Array of secret names to inject from the scope's secret store.
|
|
5595
|
-
* Each entry must be a non-empty string.
|
|
5596
|
-
*/
|
|
5597
|
-
experimental_secrets: nonEmptyStringArraySchema.optional(),
|
|
5598
|
-
/**
|
|
5599
|
-
* Array of variable names to inject from the scope's variable store.
|
|
5600
|
-
* Each entry must be a non-empty string.
|
|
5601
|
-
*/
|
|
5602
|
-
experimental_vars: nonEmptyStringArraySchema.optional()
|
|
5593
|
+
experimental_firewall: experimentalFirewallSchema.optional()
|
|
5603
5594
|
});
|
|
5604
5595
|
var agentComposeContentSchema = z5.object({
|
|
5605
5596
|
version: z5.string().min(1, "Version is required"),
|
|
@@ -5759,7 +5750,7 @@ var unifiedRunRequestSchema = z6.object({
|
|
|
5759
5750
|
volumeVersions: z6.record(z6.string(), z6.string()).optional(),
|
|
5760
5751
|
// Debug flag to force real Claude in mock environments (internal use only)
|
|
5761
5752
|
debugNoMockClaude: z6.boolean().optional(),
|
|
5762
|
-
// Model provider for automatic
|
|
5753
|
+
// Model provider for automatic credential injection
|
|
5763
5754
|
modelProvider: z6.string().optional(),
|
|
5764
5755
|
// Required
|
|
5765
5756
|
prompt: z6.string().min(1, "Missing prompt")
|
|
@@ -9213,10 +9204,10 @@ async function executeJob(context, config, options = {}) {
|
|
|
9213
9204
|
const vsockPath = vm.getVsockPath();
|
|
9214
9205
|
const guest = new VsockClient(vsockPath);
|
|
9215
9206
|
log(`[Executor] Using vsock for guest communication: ${vsockPath}`);
|
|
9216
|
-
log(`[Executor]
|
|
9207
|
+
log(`[Executor] Waiting for guest connection...`);
|
|
9217
9208
|
await withSandboxTiming(
|
|
9218
9209
|
"guest_wait",
|
|
9219
|
-
() => guest.
|
|
9210
|
+
() => guest.waitForGuestConnection(3e4)
|
|
9220
9211
|
);
|
|
9221
9212
|
log(`[Executor] Guest client ready`);
|
|
9222
9213
|
const firewallConfig = context.experimentalFirewall;
|
|
@@ -10240,7 +10231,7 @@ var benchmarkCommand = new Command4("benchmark").description(
|
|
|
10240
10231
|
});
|
|
10241
10232
|
|
|
10242
10233
|
// src/index.ts
|
|
10243
|
-
var version = true ? "3.0.
|
|
10234
|
+
var version = true ? "3.0.2" : "0.1.0";
|
|
10244
10235
|
program.name("vm0-runner").version(version).description("Self-hosted runner for VM0 agents");
|
|
10245
10236
|
program.addCommand(startCommand);
|
|
10246
10237
|
program.addCommand(doctorCommand);
|