@vm0/runner 3.11.1 → 3.11.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.
Files changed (2) hide show
  1. package/index.js +187 -125
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -33,6 +33,8 @@ var runtimePaths = {
33
33
  };
34
34
  var VM_WORKSPACE_PREFIX = "vm0-";
35
35
  var runnerPaths = {
36
+ /** Base directory used for snapshot generation (baseDir + /snapshot-gen) */
37
+ snapshotBaseDir: (baseDir) => path.join(baseDir, "snapshot-gen"),
36
38
  /** Overlay pool directory for pre-warmed VM overlays */
37
39
  overlayPool: (baseDir) => path.join(baseDir, "overlay-pool"),
38
40
  /** Workspaces directory for VM work directories */
@@ -41,16 +43,24 @@ var runnerPaths = {
41
43
  vmWorkDir: (baseDir, vmId) => path.join(baseDir, "workspaces", `${VM_WORKSPACE_PREFIX}${vmId}`),
42
44
  /** Runner status file */
43
45
  statusFile: (baseDir) => path.join(baseDir, "status.json"),
46
+ /** Snapshot generation work directory */
47
+ snapshotWorkDir: (baseDir) => path.join(baseDir, "workspaces", ".snapshot-work"),
44
48
  /** Check if a directory name is a VM workspace */
45
49
  isVmWorkspace: (dirname) => dirname.startsWith(VM_WORKSPACE_PREFIX),
46
50
  /** Extract vmId from workspace directory name */
47
51
  extractVmId: (dirname) => createVmId(dirname.replace(VM_WORKSPACE_PREFIX, ""))
48
52
  };
49
53
  var vmPaths = {
50
- /** Firecracker config file (used with --config-file --no-api) */
54
+ /** Firecracker config file (used with --config-file) */
51
55
  config: (workDir) => path.join(workDir, "config.json"),
52
- /** Vsock UDS for host-guest communication */
53
- vsock: (workDir) => path.join(workDir, "vsock.sock")
56
+ /** Vsock directory for host-guest communication */
57
+ vsockDir: (workDir) => path.join(workDir, "vsock"),
58
+ /** Vsock UDS path for host-guest communication */
59
+ vsock: (workDir) => path.join(workDir, "vsock", "vsock.sock"),
60
+ /** Firecracker API socket (used with --api-sock) */
61
+ apiSock: (workDir) => path.join(workDir, "api.sock"),
62
+ /** Overlay filesystem for VM writes */
63
+ overlay: (workDir) => path.join(workDir, "overlay.ext4")
54
64
  };
55
65
  var tempPaths = {
56
66
  /** Default proxy CA directory */
@@ -325,15 +335,16 @@ async function subscribeToJobs(server, group, onJob, onConnectionChange) {
325
335
  };
326
336
  }
327
337
 
338
+ // src/lib/executor.ts
339
+ import fs9 from "fs";
340
+
328
341
  // src/lib/firecracker/vm.ts
329
342
  import { spawn } from "child_process";
330
343
  import fs4 from "fs";
331
344
  import readline from "readline";
332
345
 
333
346
  // src/lib/firecracker/netns-pool.ts
334
- import { exec } from "child_process";
335
347
  import path2 from "path";
336
- import { promisify } from "util";
337
348
  import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
338
349
  import { z as z3 } from "zod";
339
350
 
@@ -377,9 +388,25 @@ async function withFileLock(path7, fn, options) {
377
388
  }
378
389
  }
379
390
 
380
- // src/lib/firecracker/netns-pool.ts
391
+ // src/lib/utils/exec.ts
392
+ import { exec } from "child_process";
393
+ import { promisify } from "util";
381
394
  var execAsync = promisify(exec);
382
- var logger = createLogger("NetnsPool");
395
+ async function execCommand(cmd, sudo = true) {
396
+ const fullCmd = sudo ? `sudo ${cmd}` : cmd;
397
+ try {
398
+ const { stdout } = await execAsync(fullCmd);
399
+ return stdout.trim();
400
+ } catch (error) {
401
+ const execError = error;
402
+ throw new Error(
403
+ `Command failed: ${fullCmd}
404
+ ${execError.stderr || execError.message}`
405
+ );
406
+ }
407
+ }
408
+
409
+ // src/lib/firecracker/netns.ts
383
410
  var SNAPSHOT_NETWORK = {
384
411
  /** TAP device name inside namespace (must match Firecracker config) */
385
412
  tapName: "vm0-tap",
@@ -394,14 +421,28 @@ var SNAPSHOT_NETWORK = {
394
421
  /** CIDR prefix length (for ip commands) */
395
422
  prefixLen: 29
396
423
  };
397
- var VETH_NS = "veth0";
398
- var NS_PREFIX = "vm0-ns-";
399
- var VETH_PREFIX = "vm0-ve-";
400
- var VETH_IP_PREFIX = "10.200";
401
424
  function generateSnapshotNetworkBootArgs() {
402
425
  const { guestIp, gatewayIp, netmask } = SNAPSHOT_NETWORK;
403
426
  return `ip=${guestIp}::${gatewayIp}:${netmask}:vm0-guest:eth0:off`;
404
427
  }
428
+ async function createNetnsWithTap(nsName, tap) {
429
+ await execCommand(`ip netns add ${nsName}`);
430
+ await execCommand(
431
+ `ip netns exec ${nsName} ip tuntap add ${tap.tapName} mode tap`
432
+ );
433
+ await execCommand(
434
+ `ip netns exec ${nsName} ip addr add ${tap.gatewayIpWithPrefix} dev ${tap.tapName}`
435
+ );
436
+ await execCommand(`ip netns exec ${nsName} ip link set ${tap.tapName} up`);
437
+ await execCommand(`ip netns exec ${nsName} ip link set lo up`);
438
+ }
439
+
440
+ // src/lib/firecracker/netns-pool.ts
441
+ var logger = createLogger("NetnsPool");
442
+ var VETH_NS = "veth0";
443
+ var NS_PREFIX = "vm0-ns-";
444
+ var VETH_PREFIX = "vm0-ve-";
445
+ var VETH_IP_PREFIX = "10.200";
405
446
  var MAX_RUNNERS = 64;
406
447
  var MAX_NAMESPACES_PER_RUNNER = 256;
407
448
  var NamespaceEntrySchema = z3.object({
@@ -459,19 +500,6 @@ var RegistryFile = class {
459
500
  });
460
501
  }
461
502
  };
462
- async function execCommand(cmd, sudo = true) {
463
- const fullCmd = sudo ? `sudo ${cmd}` : cmd;
464
- try {
465
- const { stdout } = await execAsync(fullCmd);
466
- return stdout.trim();
467
- } catch (error) {
468
- const execError = error;
469
- throw new Error(
470
- `Command failed: ${fullCmd}
471
- ${execError.stderr || execError.message}`
472
- );
473
- }
474
- }
475
503
  async function getDefaultInterface() {
476
504
  const result = await execCommand("ip route get 8.8.8.8", false);
477
505
  const match = result.match(/dev\s+(\S+)/);
@@ -694,16 +722,10 @@ var NetnsPool = class _NetnsPool {
694
722
  });
695
723
  logger.log(`Creating namespace ${name}...`);
696
724
  try {
697
- await execCommand(`ip netns add ${name}`);
698
- await execCommand(
699
- `ip netns exec ${name} ip tuntap add ${SNAPSHOT_NETWORK.tapName} mode tap`
700
- );
701
- await execCommand(
702
- `ip netns exec ${name} ip addr add ${SNAPSHOT_NETWORK.gatewayIp}/${SNAPSHOT_NETWORK.prefixLen} dev ${SNAPSHOT_NETWORK.tapName}`
703
- );
704
- await execCommand(
705
- `ip netns exec ${name} ip link set ${SNAPSHOT_NETWORK.tapName} up`
706
- );
725
+ await createNetnsWithTap(name, {
726
+ tapName: SNAPSHOT_NETWORK.tapName,
727
+ gatewayIpWithPrefix: `${SNAPSHOT_NETWORK.gatewayIp}/${SNAPSHOT_NETWORK.prefixLen}`
728
+ });
707
729
  await execCommand(
708
730
  `ip link add ${vethHost} type veth peer name ${VETH_NS} netns ${name}`
709
731
  );
@@ -711,7 +733,6 @@ var NetnsPool = class _NetnsPool {
711
733
  `ip netns exec ${name} ip addr add ${vethNsIp}/30 dev ${VETH_NS}`
712
734
  );
713
735
  await execCommand(`ip netns exec ${name} ip link set ${VETH_NS} up`);
714
- await execCommand(`ip netns exec ${name} ip link set lo up`);
715
736
  await execCommand(`ip addr add ${vethHostIp}/30 dev ${vethHost}`);
716
737
  await execCommand(`ip link set ${vethHost} up`);
717
738
  await execCommand(
@@ -1068,6 +1089,36 @@ var FirecrackerClient = class {
1068
1089
  constructor(socketPath) {
1069
1090
  this.socketPath = socketPath;
1070
1091
  }
1092
+ /**
1093
+ * Configure machine settings (vCPUs, memory)
1094
+ */
1095
+ async configureMachine(config) {
1096
+ await this.put("/machine-config", config);
1097
+ }
1098
+ /**
1099
+ * Configure boot source (kernel, boot args)
1100
+ */
1101
+ async configureBootSource(config) {
1102
+ await this.put("/boot-source", config);
1103
+ }
1104
+ /**
1105
+ * Configure network interface
1106
+ */
1107
+ async configureNetworkInterface(config) {
1108
+ await this.put(`/network-interfaces/${config.iface_id}`, config);
1109
+ }
1110
+ /**
1111
+ * Configure vsock device
1112
+ */
1113
+ async configureVsock(config) {
1114
+ await this.put("/vsock", config);
1115
+ }
1116
+ /**
1117
+ * Start the VM instance
1118
+ */
1119
+ async startInstance() {
1120
+ await this.put("/actions", { action_type: "InstanceStart" });
1121
+ }
1071
1122
  /**
1072
1123
  * Pause the VM
1073
1124
  *
@@ -1164,17 +1215,27 @@ var FirecrackerClient = class {
1164
1215
  */
1165
1216
  request(method, path7, body, timeoutMs = 3e4) {
1166
1217
  return new Promise((resolve, reject) => {
1218
+ const bodyStr = body !== void 0 ? JSON.stringify(body) : void 0;
1219
+ const headers = {
1220
+ Accept: "application/json",
1221
+ // Disable keep-alive to prevent pipelining issues
1222
+ Connection: "close"
1223
+ };
1224
+ if (bodyStr !== void 0) {
1225
+ headers["Content-Type"] = "application/json";
1226
+ headers["Content-Length"] = Buffer.byteLength(bodyStr);
1227
+ }
1167
1228
  const options = {
1168
1229
  socketPath: this.socketPath,
1169
- path: `http://localhost${path7}`,
1230
+ path: path7,
1170
1231
  method,
1171
- headers: {
1172
- "Content-Type": "application/json",
1173
- Accept: "application/json"
1174
- },
1175
- timeout: timeoutMs
1232
+ headers,
1233
+ timeout: timeoutMs,
1234
+ // Disable agent to ensure fresh connection for each request
1235
+ // Firecracker's single-threaded API can have issues with pipelined requests
1236
+ agent: false
1176
1237
  };
1177
- logger3.log(`${method} ${path7}${body ? " " + JSON.stringify(body) : ""}`);
1238
+ logger3.log(`${method} ${path7}${bodyStr ? " " + bodyStr : ""}`);
1178
1239
  const req = http.request(options, (res) => {
1179
1240
  let data = "";
1180
1241
  res.on("data", (chunk) => {
@@ -1204,8 +1265,8 @@ var FirecrackerClient = class {
1204
1265
  req.on("error", (err) => {
1205
1266
  reject(err);
1206
1267
  });
1207
- if (body) {
1208
- req.write(JSON.stringify(body));
1268
+ if (bodyStr !== void 0) {
1269
+ req.write(bodyStr);
1209
1270
  }
1210
1271
  req.end();
1211
1272
  });
@@ -1218,6 +1279,58 @@ var FirecrackerClient = class {
1218
1279
  }
1219
1280
  };
1220
1281
 
1282
+ // src/lib/firecracker/config.ts
1283
+ function buildBootArgs() {
1284
+ const networkBootArgs = generateSnapshotNetworkBootArgs();
1285
+ return `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}`;
1286
+ }
1287
+ function buildFirecrackerConfig(params) {
1288
+ const bootArgs = buildBootArgs();
1289
+ return {
1290
+ "boot-source": {
1291
+ kernel_image_path: params.kernelPath,
1292
+ boot_args: bootArgs
1293
+ },
1294
+ drives: [
1295
+ // Base drive (squashfs, read-only, shared across VMs)
1296
+ // Mounted as /dev/vda inside the VM
1297
+ {
1298
+ drive_id: "rootfs",
1299
+ path_on_host: params.rootfsPath,
1300
+ is_root_device: true,
1301
+ is_read_only: true
1302
+ },
1303
+ // Overlay drive (ext4, read-write, per-VM)
1304
+ // Mounted as /dev/vdb inside the VM
1305
+ // The vm-init script combines these using overlayfs
1306
+ {
1307
+ drive_id: "overlay",
1308
+ path_on_host: params.overlayPath,
1309
+ is_root_device: false,
1310
+ is_read_only: false
1311
+ }
1312
+ ],
1313
+ "machine-config": {
1314
+ vcpu_count: params.vcpus,
1315
+ mem_size_mib: params.memoryMb
1316
+ },
1317
+ "network-interfaces": [
1318
+ {
1319
+ // Network interface uses fixed config from SNAPSHOT_NETWORK
1320
+ // TAP device is inside the namespace, created by netns-pool
1321
+ iface_id: "eth0",
1322
+ guest_mac: SNAPSHOT_NETWORK.guestMac,
1323
+ host_dev_name: SNAPSHOT_NETWORK.tapName
1324
+ }
1325
+ ],
1326
+ // Guest CID 3 is the standard guest identifier (CID 0=hypervisor, 1=local, 2=host)
1327
+ vsock: {
1328
+ guest_cid: 3,
1329
+ uds_path: params.vsockPath
1330
+ }
1331
+ };
1332
+ }
1333
+
1221
1334
  // src/lib/firecracker/vm.ts
1222
1335
  var logger4 = createLogger("VM");
1223
1336
  var FirecrackerVM = class {
@@ -1282,7 +1395,6 @@ var FirecrackerVM = class {
1282
1395
  throw new Error(`Cannot start VM in state: ${this.state}`);
1283
1396
  }
1284
1397
  try {
1285
- fs4.mkdirSync(this.workDir, { recursive: true });
1286
1398
  logger4.log(`[VM ${this.config.vmId}] Acquiring resources...`);
1287
1399
  const results = await Promise.allSettled([
1288
1400
  acquireOverlay(),
@@ -1458,74 +1570,23 @@ var FirecrackerVM = class {
1458
1570
  }
1459
1571
  /**
1460
1572
  * Build Firecracker configuration object
1461
- *
1462
- * Creates the JSON configuration for Firecracker's --config-file option.
1463
- * Boot args:
1464
- * - console=ttyS0: serial console output
1465
- * - reboot=k: use keyboard controller for reboot
1466
- * - panic=1: reboot after 1 second on kernel panic
1467
- * - pci=off: disable PCI bus (not needed in microVM)
1468
- * - nomodules: skip module loading (not needed in microVM)
1469
- * - random.trust_cpu=on: trust CPU RNG, skip entropy wait
1470
- * - quiet loglevel=0: minimize kernel log output
1471
- * - nokaslr: disable kernel address space randomization
1472
- * - audit=0: disable kernel auditing
1473
- * - numa=off: disable NUMA (single node)
1474
- * - mitigations=off: disable CPU vulnerability mitigations
1475
- * - noresume: skip hibernation resume check
1476
- * - init=/sbin/vm-init: use vm-init (Rust binary) for filesystem setup and vsock-agent
1477
- * - ip=...: network configuration (fixed IPs from SNAPSHOT_NETWORK)
1478
1573
  */
1479
1574
  buildConfig() {
1480
1575
  if (!this.netns || !this.vmOverlayPath) {
1481
1576
  throw new Error("VM not properly initialized");
1482
1577
  }
1483
- const networkBootArgs = generateSnapshotNetworkBootArgs();
1484
- 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}`;
1485
- logger4.log(`[VM ${this.config.vmId}] Boot args: ${bootArgs}`);
1486
- return {
1487
- "boot-source": {
1488
- kernel_image_path: this.config.kernelPath,
1489
- boot_args: bootArgs
1490
- },
1491
- drives: [
1492
- // Base drive (squashfs, read-only, shared across VMs)
1493
- // Mounted as /dev/vda inside the VM
1494
- {
1495
- drive_id: "rootfs",
1496
- path_on_host: this.config.rootfsPath,
1497
- is_root_device: true,
1498
- is_read_only: true
1499
- },
1500
- // Overlay drive (ext4, read-write, per-VM)
1501
- // Mounted as /dev/vdb inside the VM
1502
- // The vm-init script combines these using overlayfs
1503
- {
1504
- drive_id: "overlay",
1505
- path_on_host: this.vmOverlayPath,
1506
- is_root_device: false,
1507
- is_read_only: false
1508
- }
1509
- ],
1510
- "machine-config": {
1511
- vcpu_count: this.config.vcpus,
1512
- mem_size_mib: this.config.memoryMb
1513
- },
1514
- "network-interfaces": [
1515
- {
1516
- // Network interface uses fixed config from SNAPSHOT_NETWORK
1517
- // TAP device is inside the namespace, created by netns-pool
1518
- iface_id: "eth0",
1519
- guest_mac: SNAPSHOT_NETWORK.guestMac,
1520
- host_dev_name: SNAPSHOT_NETWORK.tapName
1521
- }
1522
- ],
1523
- // Guest CID 3 is the standard guest identifier (CID 0=hypervisor, 1=local, 2=host)
1524
- vsock: {
1525
- guest_cid: 3,
1526
- uds_path: this.vsockPath
1527
- }
1528
- };
1578
+ const config = buildFirecrackerConfig({
1579
+ kernelPath: this.config.kernelPath,
1580
+ rootfsPath: this.config.rootfsPath,
1581
+ overlayPath: this.vmOverlayPath,
1582
+ vsockPath: this.vsockPath,
1583
+ vcpus: this.config.vcpus,
1584
+ memoryMb: this.config.memoryMb
1585
+ });
1586
+ logger4.log(
1587
+ `[VM ${this.config.vmId}] Boot args: ${config["boot-source"].boot_args}`
1588
+ );
1589
+ return config;
1529
1590
  }
1530
1591
  /**
1531
1592
  * Stop the VM
@@ -7653,6 +7714,7 @@ var modelProviderTypeSchema = z18.enum([
7653
7714
  "openrouter-api-key",
7654
7715
  "moonshot-api-key",
7655
7716
  "minimax-api-key",
7717
+ "deepseek-api-key",
7656
7718
  "aws-bedrock"
7657
7719
  ]);
7658
7720
  var modelProviderFrameworkSchema = z18.enum(["claude-code", "codex"]);
@@ -10026,6 +10088,13 @@ async function executeJob(context, config, options = {}) {
10026
10088
  firecrackerBinary: config.firecracker.binary,
10027
10089
  workDir: runnerPaths.vmWorkDir(config.base_dir, vmId)
10028
10090
  };
10091
+ fs9.mkdirSync(vmConfig.workDir, { recursive: true });
10092
+ fs9.mkdirSync(vmPaths.vsockDir(vmConfig.workDir), { recursive: true });
10093
+ const vsockPath = vmPaths.vsock(vmConfig.workDir);
10094
+ vsockClient = new VsockClient(vsockPath);
10095
+ const guest = vsockClient;
10096
+ logger9.log(`Starting vsock listener: ${vsockPath}`);
10097
+ const guestConnectionPromise = guest.waitForGuestConnection(3e4);
10029
10098
  logger9.log(`Creating VM ${vmId}...`);
10030
10099
  vm = new FirecrackerVM(vmConfig);
10031
10100
  await withSandboxTiming("vm_create", () => vm.start());
@@ -10037,10 +10106,6 @@ async function executeJob(context, config, options = {}) {
10037
10106
  logger9.log(
10038
10107
  `VM ${vmId} started, guest IP: ${guestIp}, veth NS IP: ${vethNsIp}`
10039
10108
  );
10040
- const vsockPath = vm.getVsockPath();
10041
- vsockClient = new VsockClient(vsockPath);
10042
- const guest = vsockClient;
10043
- logger9.log(`Using vsock for guest communication: ${vsockPath}`);
10044
10109
  const envJson = JSON.stringify(
10045
10110
  buildEnvironmentVariables(context, config.server.url)
10046
10111
  );
@@ -10058,10 +10123,7 @@ async function executeJob(context, config, options = {}) {
10058
10123
  });
10059
10124
  }
10060
10125
  logger9.log(`Waiting for guest connection...`);
10061
- await withSandboxTiming(
10062
- "guest_wait",
10063
- () => guest.waitForGuestConnection(3e4)
10064
- );
10126
+ await withSandboxTiming("guest_wait", () => guestConnectionPromise);
10065
10127
  logger9.log(`Guest client ready`);
10066
10128
  if (context.storageManifest) {
10067
10129
  await withSandboxTiming(
@@ -10264,7 +10326,7 @@ async function isPortInUse(port) {
10264
10326
  }
10265
10327
 
10266
10328
  // src/lib/runner/runner-lock.ts
10267
- import fs9 from "fs";
10329
+ import fs10 from "fs";
10268
10330
  import path5 from "path";
10269
10331
  var logger11 = createLogger("RunnerLock");
10270
10332
  var DEFAULT_PID_FILE = runtimePaths.runnerPid;
@@ -10283,9 +10345,9 @@ function isProcessRunning(pid) {
10283
10345
  function acquireRunnerLock(options = {}) {
10284
10346
  const pidFile = options.pidFile ?? DEFAULT_PID_FILE;
10285
10347
  const runDir = path5.dirname(pidFile);
10286
- fs9.mkdirSync(runDir, { recursive: true });
10287
- if (fs9.existsSync(pidFile)) {
10288
- const pidStr = fs9.readFileSync(pidFile, "utf-8").trim();
10348
+ fs10.mkdirSync(runDir, { recursive: true });
10349
+ if (fs10.existsSync(pidFile)) {
10350
+ const pidStr = fs10.readFileSync(pidFile, "utf-8").trim();
10289
10351
  const pid = parseInt(pidStr, 10);
10290
10352
  if (!isNaN(pid) && isProcessRunning(pid)) {
10291
10353
  logger11.error(`Error: Another runner is already running (PID ${pid})`);
@@ -10297,16 +10359,16 @@ function acquireRunnerLock(options = {}) {
10297
10359
  } else {
10298
10360
  logger11.log(`Cleaning up stale PID file (PID ${pid} not running)`);
10299
10361
  }
10300
- fs9.unlinkSync(pidFile);
10362
+ fs10.unlinkSync(pidFile);
10301
10363
  }
10302
- fs9.writeFileSync(pidFile, process.pid.toString());
10364
+ fs10.writeFileSync(pidFile, process.pid.toString());
10303
10365
  currentPidFile = pidFile;
10304
10366
  logger11.log(`Runner lock acquired (PID ${process.pid})`);
10305
10367
  }
10306
10368
  function releaseRunnerLock() {
10307
10369
  const pidFile = currentPidFile ?? DEFAULT_PID_FILE;
10308
- if (fs9.existsSync(pidFile)) {
10309
- fs9.unlinkSync(pidFile);
10370
+ if (fs10.existsSync(pidFile)) {
10371
+ fs10.unlinkSync(pidFile);
10310
10372
  logger11.log("Runner lock released");
10311
10373
  }
10312
10374
  currentPidFile = null;
@@ -11178,7 +11240,7 @@ var benchmarkCommand = new Command4("benchmark").description(
11178
11240
  });
11179
11241
 
11180
11242
  // src/index.ts
11181
- var version = true ? "3.11.1" : "0.1.0";
11243
+ var version = true ? "3.11.2" : "0.1.0";
11182
11244
  program.name("vm0-runner").version(version).description("Self-hosted runner for VM0 agents");
11183
11245
  program.addCommand(startCommand);
11184
11246
  program.addCommand(doctorCommand);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vm0/runner",
3
- "version": "3.11.1",
3
+ "version": "3.11.2",
4
4
  "description": "Self-hosted runner for VM0 agents",
5
5
  "repository": {
6
6
  "type": "git",