@vm0/runner 3.0.6 → 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 +66 -55
  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({
@@ -1104,28 +1116,26 @@ var FirecrackerVM = class {
1104
1116
  });
1105
1117
  const networkBootArgs = generateNetworkBootArgs(this.networkConfig);
1106
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}`;
1107
- console.log(`[VM ${this.config.vmId}] Boot args: ${bootArgs}`);
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
@@ -5626,11 +5635,6 @@ var appStringSchema = z6.string().superRefine((val, ctx) => {
5626
5635
  });
5627
5636
  var agentDefinitionSchema = z6.object({
5628
5637
  description: z6.string().optional(),
5629
- /**
5630
- * @deprecated Use `apps` field instead for pre-installed tools.
5631
- * This field will be removed in a future version.
5632
- */
5633
- image: z6.string().optional(),
5634
5638
  framework: z6.string().min(1, "Framework is required"),
5635
5639
  /**
5636
5640
  * Array of pre-installed apps/tools for the agent environment.
@@ -5640,8 +5644,6 @@ var agentDefinitionSchema = z6.object({
5640
5644
  */
5641
5645
  apps: z6.array(appStringSchema).optional(),
5642
5646
  volumes: z6.array(z6.string()).optional(),
5643
- working_dir: z6.string().optional(),
5644
- // Optional when provider supports auto-config
5645
5647
  environment: z6.record(z6.string(), z6.string()).optional(),
5646
5648
  /**
5647
5649
  * Path to instructions file (e.g., AGENTS.md).
@@ -5668,7 +5670,17 @@ var agentDefinitionSchema = z6.object({
5668
5670
  * Requires experimental_runner to be configured.
5669
5671
  * When enabled, filters outbound traffic by domain/IP rules.
5670
5672
  */
5671
- 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()
5672
5684
  });
5673
5685
  var agentComposeContentSchema = z6.object({
5674
5686
  version: z6.string().min(1, "Version is required"),
@@ -6800,13 +6812,11 @@ var scopeResponseSchema = z14.object({
6800
6812
  id: z14.string().uuid(),
6801
6813
  slug: z14.string(),
6802
6814
  type: scopeTypeSchema,
6803
- displayName: z14.string().nullable(),
6804
6815
  createdAt: z14.string(),
6805
6816
  updatedAt: z14.string()
6806
6817
  });
6807
6818
  var createScopeRequestSchema = z14.object({
6808
- slug: scopeSlugSchema,
6809
- displayName: z14.string().max(128).optional()
6819
+ slug: scopeSlugSchema
6810
6820
  });
6811
6821
  var updateScopeRequestSchema = z14.object({
6812
6822
  slug: scopeSlugSchema,
@@ -9299,7 +9309,8 @@ async function executeJob(context, config, options = {}) {
9299
9309
  kernelPath: config.firecracker.kernel,
9300
9310
  rootfsPath: config.firecracker.rootfs,
9301
9311
  firecrackerBinary: config.firecracker.binary,
9302
- workDir: path4.join(workspacesDir, `vm0-${vmId}`)
9312
+ workDir: path4.join(workspacesDir, `vm0-${vmId}`),
9313
+ logger: log
9303
9314
  };
9304
9315
  log(`[Executor] Creating VM ${vmId}...`);
9305
9316
  vm = new FirecrackerVM(vmConfig);
@@ -10253,14 +10264,14 @@ var Timer = class {
10253
10264
  this.startTime = Date.now();
10254
10265
  }
10255
10266
  /**
10256
- * Get elapsed time formatted as [MM:SS.s]
10267
+ * Get elapsed time formatted as [MM:SS.ss]
10257
10268
  */
10258
10269
  elapsed() {
10259
10270
  const ms = Date.now() - this.startTime;
10260
10271
  const totalSeconds = ms / 1e3;
10261
10272
  const minutes = Math.floor(totalSeconds / 60);
10262
- const seconds = (totalSeconds % 60).toFixed(1);
10263
- 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")}]`;
10264
10275
  }
10265
10276
  /**
10266
10277
  * Log message with timestamp
@@ -10334,7 +10345,7 @@ var benchmarkCommand = new Command4("benchmark").description(
10334
10345
  });
10335
10346
 
10336
10347
  // src/index.ts
10337
- var version = true ? "3.0.6" : "0.1.0";
10348
+ var version = true ? "3.1.0" : "0.1.0";
10338
10349
  program.name("vm0-runner").version(version).description("Self-hosted runner for VM0 agents");
10339
10350
  program.addCommand(startCommand);
10340
10351
  program.addCommand(doctorCommand);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vm0/runner",
3
- "version": "3.0.6",
3
+ "version": "3.1.0",
4
4
  "description": "Self-hosted runner for VM0 agents",
5
5
  "repository": {
6
6
  "type": "git",