@vm0/runner 3.9.3 → 3.10.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 -35
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -1352,12 +1352,15 @@ var TapPool = class {
1352
1352
  }
1353
1353
  const guestMac = generateMacAddress(vmId);
1354
1354
  try {
1355
- await this.config.setMac(resource.tapDevice, guestMac);
1355
+ await Promise.all([
1356
+ this.config.setMac(resource.tapDevice, guestMac),
1357
+ clearArpEntry(resource.guestIp)
1358
+ ]);
1356
1359
  } catch (err) {
1357
1360
  if (fromPool) {
1358
1361
  this.queue.push(resource);
1359
1362
  logger4.log(
1360
- `Returned pair to pool after MAC set failure: ${resource.tapDevice}`
1363
+ `Returned pair to pool after MAC/ARP failure: ${resource.tapDevice}`
1361
1364
  );
1362
1365
  } else {
1363
1366
  await releaseIP(resource.guestIp).catch(() => {
@@ -1367,14 +1370,11 @@ var TapPool = class {
1367
1370
  }
1368
1371
  throw err;
1369
1372
  }
1370
- await clearArpEntry(resource.guestIp);
1371
- try {
1372
- await assignVmIdToIP(resource.guestIp, vmId);
1373
- } catch (err) {
1373
+ assignVmIdToIP(resource.guestIp, vmId).catch((err) => {
1374
1374
  logger4.error(
1375
1375
  `Failed to assign vmId to IP registry: ${err instanceof Error ? err.message : "Unknown"}`
1376
1376
  );
1377
- }
1377
+ });
1378
1378
  logger4.log(
1379
1379
  `Acquired: TAP ${resource.tapDevice}, MAC ${guestMac}, IP ${resource.guestIp}`
1380
1380
  );
@@ -1695,12 +1695,9 @@ var FirecrackerVM = class {
1695
1695
  /**
1696
1696
  * Stop the VM
1697
1697
  *
1698
- * Note: With --no-api mode, we can only force kill the process.
1699
- * The VM doesn't have an API endpoint for graceful shutdown.
1700
- *
1701
- * TODO(#2118): Implement graceful shutdown via vsock command to guest agent.
1702
- * This would allow the guest to clean up before termination without
1703
- * adding the startup latency of API mode.
1698
+ * Note: With --no-api mode, we force kill the Firecracker process.
1699
+ * Graceful shutdown (filesystem sync) should be done via vsock
1700
+ * before calling this method.
1704
1701
  */
1705
1702
  async stop() {
1706
1703
  if (this.state !== "running") {
@@ -1794,6 +1791,8 @@ var MSG_WRITE_FILE = 5;
1794
1791
  var MSG_SPAWN_WATCH = 7;
1795
1792
  var MSG_SPAWN_WATCH_RESULT = 8;
1796
1793
  var MSG_PROCESS_EXIT = 9;
1794
+ var MSG_SHUTDOWN = 10;
1795
+ var MSG_SHUTDOWN_ACK = 11;
1797
1796
  var MSG_ERROR = 255;
1798
1797
  var FLAG_SUDO = 1;
1799
1798
  function encode(type, seq, payload = Buffer.alloc(0)) {
@@ -2295,6 +2294,30 @@ var VsockClient = class {
2295
2294
  getVsockPath() {
2296
2295
  return this.vsockPath;
2297
2296
  }
2297
+ /**
2298
+ * Request graceful shutdown from guest
2299
+ *
2300
+ * Sends shutdown command to guest agent, which syncs filesystems
2301
+ * and returns acknowledgment. Falls back gracefully on timeout.
2302
+ *
2303
+ * @param timeoutMs Maximum time to wait for acknowledgment (default: 2000ms)
2304
+ * @returns true if guest acknowledged, false on timeout/error
2305
+ */
2306
+ async shutdown(timeoutMs = 2e3) {
2307
+ if (!this.connected || !this.socket) {
2308
+ return false;
2309
+ }
2310
+ try {
2311
+ const response = await this.request(
2312
+ MSG_SHUTDOWN,
2313
+ Buffer.alloc(0),
2314
+ timeoutMs
2315
+ );
2316
+ return response.type === MSG_SHUTDOWN_ACK;
2317
+ } catch {
2318
+ return false;
2319
+ }
2320
+ }
2298
2321
  /**
2299
2322
  * Close the connection
2300
2323
  */
@@ -7743,7 +7766,8 @@ import { z as z17 } from "zod";
7743
7766
  var c11 = initContract();
7744
7767
  var modelProviderTypeSchema = z17.enum([
7745
7768
  "claude-code-oauth-token",
7746
- "anthropic-api-key"
7769
+ "anthropic-api-key",
7770
+ "moonshot-api-key"
7747
7771
  ]);
7748
7772
  var modelProviderFrameworkSchema = z17.enum(["claude-code", "codex"]);
7749
7773
  var modelProviderResponseSchema = z17.object({
@@ -7752,6 +7776,7 @@ var modelProviderResponseSchema = z17.object({
7752
7776
  framework: modelProviderFrameworkSchema,
7753
7777
  credentialName: z17.string(),
7754
7778
  isDefault: z17.boolean(),
7779
+ selectedModel: z17.string().nullable(),
7755
7780
  createdAt: z17.string(),
7756
7781
  updatedAt: z17.string()
7757
7782
  });
@@ -7761,7 +7786,8 @@ var modelProviderListResponseSchema = z17.object({
7761
7786
  var upsertModelProviderRequestSchema = z17.object({
7762
7787
  type: modelProviderTypeSchema,
7763
7788
  credential: z17.string().min(1, "Credential is required"),
7764
- convert: z17.boolean().optional()
7789
+ convert: z17.boolean().optional(),
7790
+ selectedModel: z17.string().optional()
7765
7791
  });
7766
7792
  var upsertModelProviderResponseSchema = z17.object({
7767
7793
  provider: modelProviderResponseSchema,
@@ -10030,6 +10056,7 @@ async function executeJob(context, config, options = {}) {
10030
10056
  const vmId = getVmIdFromRunId(context.runId);
10031
10057
  let vm = null;
10032
10058
  let guestIp = null;
10059
+ let vsockClient = null;
10033
10060
  logger10.log(`Starting job ${context.runId} in VM ${vmId}`);
10034
10061
  try {
10035
10062
  const vmConfig = {
@@ -10050,14 +10077,12 @@ async function executeJob(context, config, options = {}) {
10050
10077
  }
10051
10078
  logger10.log(`VM ${vmId} started, guest IP: ${guestIp}`);
10052
10079
  const vsockPath = vm.getVsockPath();
10053
- const guest = new VsockClient(vsockPath);
10080
+ vsockClient = new VsockClient(vsockPath);
10081
+ const guest = vsockClient;
10054
10082
  logger10.log(`Using vsock for guest communication: ${vsockPath}`);
10055
- logger10.log(`Waiting for guest connection...`);
10056
- await withSandboxTiming(
10057
- "guest_wait",
10058
- () => guest.waitForGuestConnection(3e4)
10083
+ const envJson = JSON.stringify(
10084
+ buildEnvironmentVariables(context, config.server.url)
10059
10085
  );
10060
- logger10.log(`Guest client ready`);
10061
10086
  const firewallConfig = context.experimentalFirewall;
10062
10087
  if (firewallConfig?.enabled) {
10063
10088
  const mitmEnabled = firewallConfig.experimental_mitm ?? false;
@@ -10065,19 +10090,18 @@ async function executeJob(context, config, options = {}) {
10065
10090
  logger10.log(
10066
10091
  `Setting up network security for VM ${guestIp} (mitm=${mitmEnabled}, sealSecrets=${sealSecretsEnabled})`
10067
10092
  );
10068
- await withSandboxTiming("network_setup", async () => {
10069
- getVMRegistry().register(
10070
- guestIp,
10071
- context.runId,
10072
- context.sandboxToken,
10073
- {
10074
- firewallRules: firewallConfig?.rules,
10075
- mitmEnabled,
10076
- sealSecretsEnabled
10077
- }
10078
- );
10093
+ getVMRegistry().register(guestIp, context.runId, context.sandboxToken, {
10094
+ firewallRules: firewallConfig?.rules,
10095
+ mitmEnabled,
10096
+ sealSecretsEnabled
10079
10097
  });
10080
10098
  }
10099
+ logger10.log(`Waiting for guest connection...`);
10100
+ await withSandboxTiming(
10101
+ "guest_wait",
10102
+ () => guest.waitForGuestConnection(3e4)
10103
+ );
10104
+ logger10.log(`Guest client ready`);
10081
10105
  if (context.storageManifest) {
10082
10106
  await withSandboxTiming(
10083
10107
  "storage_download",
@@ -10095,8 +10119,6 @@ async function executeJob(context, config, options = {}) {
10095
10119
  )
10096
10120
  );
10097
10121
  }
10098
- const envVars = buildEnvironmentVariables(context, config.server.url);
10099
- const envJson = JSON.stringify(envVars);
10100
10122
  logger10.log(
10101
10123
  `Writing env JSON (${envJson.length} bytes) to ${ENV_JSON_PATH}`
10102
10124
  );
@@ -10193,6 +10215,15 @@ async function executeJob(context, config, options = {}) {
10193
10215
  }
10194
10216
  }
10195
10217
  if (vm) {
10218
+ if (vsockClient) {
10219
+ const acked = await vsockClient.shutdown(2e3);
10220
+ if (acked) {
10221
+ logger10.log(`Guest acknowledged shutdown`);
10222
+ } else {
10223
+ logger10.log(`Guest shutdown timeout, proceeding with SIGKILL`);
10224
+ }
10225
+ vsockClient.close();
10226
+ }
10196
10227
  logger10.log(`Cleaning up VM ${vmId}...`);
10197
10228
  await withSandboxTiming("cleanup", () => vm.kill());
10198
10229
  }
@@ -11229,7 +11260,7 @@ var benchmarkCommand = new Command4("benchmark").description(
11229
11260
  });
11230
11261
 
11231
11262
  // src/index.ts
11232
- var version = true ? "3.9.3" : "0.1.0";
11263
+ var version = true ? "3.10.0" : "0.1.0";
11233
11264
  program.name("vm0-runner").version(version).description("Self-hosted runner for VM0 agents");
11234
11265
  program.addCommand(startCommand);
11235
11266
  program.addCommand(doctorCommand);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vm0/runner",
3
- "version": "3.9.3",
3
+ "version": "3.10.0",
4
4
  "description": "Self-hosted runner for VM0 agents",
5
5
  "repository": {
6
6
  "type": "git",