@vm0/runner 3.4.0 → 3.6.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 +209 -229
  2. package/package.json +1 -6
package/index.js CHANGED
@@ -857,53 +857,51 @@ function checkNetworkPrerequisites() {
857
857
  errors
858
858
  };
859
859
  }
860
- async function setupVMProxyRules(vmIp, proxyPort, runnerName) {
861
- const comment = `vm0:runner:${runnerName}`;
860
+ async function setupCIDRProxyRules(proxyPort) {
861
+ const comment = "vm0:cidr-proxy";
862
862
  console.log(
863
- `Setting up proxy rules for VM ${vmIp} -> localhost:${proxyPort} (comment: ${comment})`
863
+ `Setting up CIDR proxy rules for ${BRIDGE_CIDR} -> port ${proxyPort}`
864
864
  );
865
865
  try {
866
866
  await execCommand(
867
- `iptables -t nat -C PREROUTING -s ${vmIp} -p tcp --dport 80 -j REDIRECT --to-port ${proxyPort} -m comment --comment "${comment}"`
867
+ `iptables -t nat -C PREROUTING -s ${BRIDGE_CIDR} -p tcp --dport 80 -j REDIRECT --to-port ${proxyPort} -m comment --comment "${comment}"`
868
868
  );
869
- console.log(`Proxy rule for ${vmIp}:80 already exists`);
869
+ console.log("CIDR proxy rule for port 80 already exists");
870
870
  } catch {
871
871
  await execCommand(
872
- `iptables -t nat -A PREROUTING -s ${vmIp} -p tcp --dport 80 -j REDIRECT --to-port ${proxyPort} -m comment --comment "${comment}"`
872
+ `iptables -t nat -A PREROUTING -s ${BRIDGE_CIDR} -p tcp --dport 80 -j REDIRECT --to-port ${proxyPort} -m comment --comment "${comment}"`
873
873
  );
874
- console.log(`Proxy rule for ${vmIp}:80 added`);
874
+ console.log("CIDR proxy rule for port 80 added");
875
875
  }
876
876
  try {
877
877
  await execCommand(
878
- `iptables -t nat -C PREROUTING -s ${vmIp} -p tcp --dport 443 -j REDIRECT --to-port ${proxyPort} -m comment --comment "${comment}"`
878
+ `iptables -t nat -C PREROUTING -s ${BRIDGE_CIDR} -p tcp --dport 443 -j REDIRECT --to-port ${proxyPort} -m comment --comment "${comment}"`
879
879
  );
880
- console.log(`Proxy rule for ${vmIp}:443 already exists`);
880
+ console.log("CIDR proxy rule for port 443 already exists");
881
881
  } catch {
882
882
  await execCommand(
883
- `iptables -t nat -A PREROUTING -s ${vmIp} -p tcp --dport 443 -j REDIRECT --to-port ${proxyPort} -m comment --comment "${comment}"`
883
+ `iptables -t nat -A PREROUTING -s ${BRIDGE_CIDR} -p tcp --dport 443 -j REDIRECT --to-port ${proxyPort} -m comment --comment "${comment}"`
884
884
  );
885
- console.log(`Proxy rule for ${vmIp}:443 added`);
885
+ console.log("CIDR proxy rule for port 443 added");
886
886
  }
887
- console.log(`Proxy rules configured for VM ${vmIp}`);
888
887
  }
889
- async function removeVMProxyRules(vmIp, proxyPort, runnerName) {
890
- const comment = `vm0:runner:${runnerName}`;
891
- console.log(`Removing proxy rules for VM ${vmIp}...`);
888
+ async function cleanupCIDRProxyRules(proxyPort) {
889
+ const comment = "vm0:cidr-proxy";
890
+ console.log("Cleaning up CIDR proxy rules...");
892
891
  try {
893
892
  await execCommand(
894
- `iptables -t nat -D PREROUTING -s ${vmIp} -p tcp --dport 80 -j REDIRECT --to-port ${proxyPort} -m comment --comment "${comment}"`
893
+ `iptables -t nat -D PREROUTING -s ${BRIDGE_CIDR} -p tcp --dport 80 -j REDIRECT --to-port ${proxyPort} -m comment --comment "${comment}"`
895
894
  );
896
- console.log(`Proxy rule for ${vmIp}:80 removed`);
895
+ console.log("CIDR proxy rule for port 80 removed");
897
896
  } catch {
898
897
  }
899
898
  try {
900
899
  await execCommand(
901
- `iptables -t nat -D PREROUTING -s ${vmIp} -p tcp --dport 443 -j REDIRECT --to-port ${proxyPort} -m comment --comment "${comment}"`
900
+ `iptables -t nat -D PREROUTING -s ${BRIDGE_CIDR} -p tcp --dport 443 -j REDIRECT --to-port ${proxyPort} -m comment --comment "${comment}"`
902
901
  );
903
- console.log(`Proxy rule for ${vmIp}:443 removed`);
902
+ console.log("CIDR proxy rule for port 443 removed");
904
903
  } catch {
905
904
  }
906
- console.log(`Proxy rules cleanup complete for VM ${vmIp}`);
907
905
  }
908
906
  async function listTapDevices() {
909
907
  try {
@@ -5627,7 +5625,9 @@ var storedExecutionContextSchema = z6.object({
5627
5625
  cliAgentType: z6.string(),
5628
5626
  experimentalFirewall: experimentalFirewallSchema.optional(),
5629
5627
  // Debug flag to force real Claude in mock environments (internal use only)
5630
- debugNoMockClaude: z6.boolean().optional()
5628
+ debugNoMockClaude: z6.boolean().optional(),
5629
+ // Dispatch timestamp for E2E timing metrics
5630
+ apiStartTime: z6.number().optional()
5631
5631
  });
5632
5632
  var executionContextSchema = z6.object({
5633
5633
  runId: z6.string().uuid(),
@@ -5647,7 +5647,9 @@ var executionContextSchema = z6.object({
5647
5647
  // Experimental firewall configuration
5648
5648
  experimentalFirewall: experimentalFirewallSchema.optional(),
5649
5649
  // Debug flag to force real Claude in mock environments (internal use only)
5650
- debugNoMockClaude: z6.boolean().optional()
5650
+ debugNoMockClaude: z6.boolean().optional(),
5651
+ // Dispatch timestamp for E2E timing metrics
5652
+ apiStartTime: z6.number().optional()
5651
5653
  });
5652
5654
  var runnersJobClaimContract = c.router({
5653
5655
  claim: {
@@ -5984,7 +5986,36 @@ var eventsResponseSchema = z8.object({
5984
5986
  run: runStateSchema,
5985
5987
  framework: z8.string()
5986
5988
  });
5989
+ var runListItemSchema = z8.object({
5990
+ id: z8.string(),
5991
+ agentName: z8.string(),
5992
+ status: runStatusSchema,
5993
+ prompt: z8.string(),
5994
+ createdAt: z8.string(),
5995
+ startedAt: z8.string().nullable()
5996
+ });
5997
+ var runsListResponseSchema = z8.object({
5998
+ runs: z8.array(runListItemSchema)
5999
+ });
5987
6000
  var runsMainContract = c3.router({
6001
+ /**
6002
+ * GET /api/agent/runs
6003
+ * List agent runs (pending and running by default)
6004
+ */
6005
+ list: {
6006
+ method: "GET",
6007
+ path: "/api/agent/runs",
6008
+ headers: authHeadersSchema,
6009
+ query: z8.object({
6010
+ status: runStatusSchema.optional(),
6011
+ limit: z8.coerce.number().min(1).max(100).default(50)
6012
+ }),
6013
+ responses: {
6014
+ 200: runsListResponseSchema,
6015
+ 401: apiErrorSchema
6016
+ },
6017
+ summary: "List agent runs"
6018
+ },
5988
6019
  /**
5989
6020
  * POST /api/agent/runs
5990
6021
  * Create and execute a new agent run
@@ -6025,6 +6056,33 @@ var runsByIdContract = c3.router({
6025
6056
  summary: "Get agent run by ID"
6026
6057
  }
6027
6058
  });
6059
+ var cancelRunResponseSchema = z8.object({
6060
+ id: z8.string(),
6061
+ status: z8.literal("cancelled"),
6062
+ message: z8.string()
6063
+ });
6064
+ var runsCancelContract = c3.router({
6065
+ /**
6066
+ * POST /api/agent/runs/:id/cancel
6067
+ * Cancel a pending or running run
6068
+ */
6069
+ cancel: {
6070
+ method: "POST",
6071
+ path: "/api/agent/runs/:id/cancel",
6072
+ headers: authHeadersSchema,
6073
+ pathParams: z8.object({
6074
+ id: z8.string().min(1, "Run ID is required")
6075
+ }),
6076
+ body: z8.undefined(),
6077
+ responses: {
6078
+ 200: cancelRunResponseSchema,
6079
+ 400: apiErrorSchema,
6080
+ 401: apiErrorSchema,
6081
+ 404: apiErrorSchema
6082
+ },
6083
+ summary: "Cancel a pending or running run"
6084
+ }
6085
+ });
6028
6086
  var runEventsContract = c3.router({
6029
6087
  /**
6030
6088
  * GET /api/agent/runs/:id/events
@@ -8594,7 +8652,10 @@ def tls_clienthello(data: tls.ClientHelloData) -> None:
8594
8652
 
8595
8653
  vm_info = get_vm_info(client_ip)
8596
8654
  if not vm_info:
8597
- return # Not a registered VM, let it through
8655
+ # Not a registered VM - pass through without MITM interception
8656
+ # This is critical for CIDR-based rules where all VM traffic is redirected
8657
+ data.ignore_connection = True
8658
+ return
8598
8659
 
8599
8660
  # If MITM is enabled, let the normal flow handle it
8600
8661
  if vm_info.get("mitmEnabled", False):
@@ -9065,171 +9126,80 @@ function initProxyManager(config) {
9065
9126
  return globalProxyManager;
9066
9127
  }
9067
9128
 
9068
- // src/lib/metrics/provider.ts
9069
- import {
9070
- MeterProvider,
9071
- PeriodicExportingMetricReader
9072
- } from "@opentelemetry/sdk-metrics";
9073
- import { OTLPMetricExporter } from "@opentelemetry/exporter-metrics-otlp-proto";
9074
- import { Resource } from "@opentelemetry/resources";
9075
- import { ATTR_SERVICE_NAME } from "@opentelemetry/semantic-conventions";
9076
- import { metrics } from "@opentelemetry/api";
9077
- var meterProvider = null;
9078
- var initialized = false;
9079
- var enabled = false;
9080
- var _runnerLabel = "";
9081
- function initMetrics(config) {
9082
- if (initialized) return;
9083
- initialized = true;
9084
- _runnerLabel = config.runnerLabel;
9085
- if (!config.axiomToken) {
9086
- console.log("[metrics] AXIOM_TOKEN not configured, metrics disabled");
9087
- return;
9088
- }
9089
- const env = config.environment ?? "dev";
9090
- const exporter = new OTLPMetricExporter({
9091
- url: "https://api.axiom.co/v1/metrics",
9092
- headers: {
9093
- Authorization: `Bearer ${config.axiomToken}`,
9094
- "X-Axiom-Dataset": `vm0-sandbox-op-log-${env}`
9095
- }
9096
- });
9097
- meterProvider = new MeterProvider({
9098
- resource: new Resource({
9099
- [ATTR_SERVICE_NAME]: config.serviceName,
9100
- "deployment.environment": env,
9101
- "runner.label": config.runnerLabel
9102
- }),
9103
- readers: [
9104
- new PeriodicExportingMetricReader({
9105
- exporter,
9106
- exportIntervalMillis: config.exportIntervalMs ?? 3e4
9107
- })
9108
- ]
9109
- });
9110
- metrics.setGlobalMeterProvider(meterProvider);
9111
- enabled = true;
9112
- console.log(
9113
- `[metrics] initialized for ${config.serviceName} (${env}), runner: ${config.runnerLabel}`
9114
- );
9115
- }
9116
- function isMetricsEnabled() {
9117
- return enabled;
9118
- }
9119
- function getRunnerLabel() {
9120
- return _runnerLabel;
9121
- }
9122
- function getMeter(name) {
9123
- return metrics.getMeter(name);
9129
+ // src/lib/metrics/instruments.ts
9130
+ var FLUSH_THRESHOLD_MS = 3e4;
9131
+ var sandboxContext = null;
9132
+ var pendingOps = [];
9133
+ var oldestPendingTime = null;
9134
+ function setSandboxContext(ctx) {
9135
+ sandboxContext = ctx;
9136
+ pendingOps = [];
9137
+ oldestPendingTime = null;
9124
9138
  }
9125
- async function flushMetrics() {
9126
- if (meterProvider) {
9127
- await meterProvider.forceFlush();
9139
+ async function clearSandboxContext() {
9140
+ const ctx = sandboxContext;
9141
+ const ops = pendingOps;
9142
+ sandboxContext = null;
9143
+ pendingOps = [];
9144
+ oldestPendingTime = null;
9145
+ if (ctx && ops.length > 0) {
9146
+ await flushOpsWithContext(ctx, ops);
9128
9147
  }
9129
9148
  }
9130
- async function shutdownMetrics() {
9131
- if (meterProvider) {
9132
- await meterProvider.shutdown();
9133
- }
9149
+ async function flushOps() {
9150
+ if (!sandboxContext || pendingOps.length === 0) return;
9151
+ const ctx = sandboxContext;
9152
+ const ops = pendingOps;
9153
+ pendingOps = [];
9154
+ oldestPendingTime = null;
9155
+ await flushOpsWithContext(ctx, ops);
9134
9156
  }
9135
-
9136
- // src/lib/metrics/instruments.ts
9137
- var runnerOperationTotal = null;
9138
- var runnerOperationErrorsTotal = null;
9139
- var runnerOperationDuration = null;
9140
- var sandboxOperationTotal = null;
9141
- var sandboxOperationErrorsTotal = null;
9142
- var sandboxOperationDuration = null;
9143
- function getRunnerInstruments() {
9144
- if (!runnerOperationTotal) {
9145
- const meter = getMeter("vm0-runner");
9146
- runnerOperationTotal = meter.createCounter("runner_operation_total", {
9147
- description: "Total number of runner operations"
9148
- });
9149
- runnerOperationErrorsTotal = meter.createCounter(
9150
- "runner_operation_errors_total",
9151
- {
9152
- description: "Total number of runner operation errors"
9153
- }
9154
- );
9155
- runnerOperationDuration = meter.createHistogram(
9156
- "runner_operation_duration_ms",
9157
- {
9158
- description: "Runner operation duration in milliseconds",
9159
- unit: "ms"
9160
- }
9161
- );
9162
- }
9163
- return {
9164
- runnerOperationTotal,
9165
- runnerOperationErrorsTotal,
9166
- runnerOperationDuration
9157
+ async function flushOpsWithContext(ctx, ops) {
9158
+ const { apiUrl, runId, sandboxToken } = ctx;
9159
+ const headers = {
9160
+ Authorization: `Bearer ${sandboxToken}`,
9161
+ "Content-Type": "application/json"
9167
9162
  };
9168
- }
9169
- function getSandboxInstruments() {
9170
- if (!sandboxOperationTotal) {
9171
- const meter = getMeter("vm0-runner");
9172
- sandboxOperationTotal = meter.createCounter("sandbox_operation_total", {
9173
- description: "Total number of sandbox operations"
9163
+ const bypassSecret = process.env.VERCEL_AUTOMATION_BYPASS_SECRET;
9164
+ if (bypassSecret) {
9165
+ headers["x-vercel-protection-bypass"] = bypassSecret;
9166
+ }
9167
+ try {
9168
+ const response = await fetch(`${apiUrl}/api/webhooks/agent/telemetry`, {
9169
+ method: "POST",
9170
+ headers,
9171
+ body: JSON.stringify({
9172
+ runId,
9173
+ sandboxOperations: ops
9174
+ })
9174
9175
  });
9175
- sandboxOperationErrorsTotal = meter.createCounter(
9176
- "sandbox_operation_errors_total",
9177
- {
9178
- description: "Total number of sandbox operation errors"
9179
- }
9180
- );
9181
- sandboxOperationDuration = meter.createHistogram(
9182
- "sandbox_operation_duration_ms",
9183
- {
9184
- description: "Sandbox operation duration in milliseconds",
9185
- unit: "ms"
9186
- }
9187
- );
9176
+ await response.text();
9177
+ if (!response.ok) {
9178
+ console.warn(
9179
+ `[metrics] Failed to flush operations: HTTP ${response.status}`
9180
+ );
9181
+ }
9182
+ } catch (err) {
9183
+ console.warn(`[metrics] Failed to flush operations: ${err}`);
9188
9184
  }
9189
- return {
9190
- sandboxOperationTotal,
9191
- sandboxOperationErrorsTotal,
9192
- sandboxOperationDuration
9193
- };
9194
9185
  }
9195
- function recordRunnerOperation(attrs) {
9196
- if (!isMetricsEnabled()) return;
9197
- const {
9198
- runnerOperationTotal: runnerOperationTotal2,
9199
- runnerOperationErrorsTotal: runnerOperationErrorsTotal2,
9200
- runnerOperationDuration: runnerOperationDuration2
9201
- } = getRunnerInstruments();
9202
- const labels = {
9203
- action_type: attrs.actionType,
9204
- runner_label: getRunnerLabel()
9205
- };
9206
- runnerOperationTotal2.add(1, labels);
9207
- if (!attrs.success) {
9208
- runnerOperationErrorsTotal2.add(1, labels);
9186
+ function recordOperation(attrs) {
9187
+ if (!sandboxContext) {
9188
+ return;
9209
9189
  }
9210
- runnerOperationDuration2.record(attrs.durationMs, {
9211
- ...labels,
9212
- success: String(attrs.success)
9213
- });
9214
- }
9215
- function recordSandboxOperation(attrs) {
9216
- if (!isMetricsEnabled()) return;
9217
- const {
9218
- sandboxOperationTotal: sandboxOperationTotal2,
9219
- sandboxOperationErrorsTotal: sandboxOperationErrorsTotal2,
9220
- sandboxOperationDuration: sandboxOperationDuration2
9221
- } = getSandboxInstruments();
9222
- const labels = {
9223
- sandbox_type: "runner",
9224
- action_type: attrs.actionType
9225
- };
9226
- sandboxOperationTotal2.add(1, labels);
9227
- if (!attrs.success) {
9228
- sandboxOperationErrorsTotal2.add(1, labels);
9190
+ const now = Date.now();
9191
+ if (oldestPendingTime && now - oldestPendingTime >= FLUSH_THRESHOLD_MS) {
9192
+ flushOps().catch(() => {
9193
+ });
9229
9194
  }
9230
- sandboxOperationDuration2.record(attrs.durationMs, {
9231
- ...labels,
9232
- success: String(attrs.success)
9195
+ if (oldestPendingTime === null) {
9196
+ oldestPendingTime = now;
9197
+ }
9198
+ pendingOps.push({
9199
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
9200
+ action_type: attrs.actionType,
9201
+ duration_ms: attrs.durationMs,
9202
+ success: attrs.success
9233
9203
  });
9234
9204
  }
9235
9205
 
@@ -9243,7 +9213,7 @@ async function withRunnerTiming(actionType, fn) {
9243
9213
  success = false;
9244
9214
  throw error;
9245
9215
  } finally {
9246
- recordRunnerOperation({
9216
+ recordOperation({
9247
9217
  actionType,
9248
9218
  durationMs: Date.now() - startTime,
9249
9219
  success
@@ -9259,7 +9229,7 @@ async function withSandboxTiming(actionType, fn) {
9259
9229
  success = false;
9260
9230
  throw error;
9261
9231
  } finally {
9262
- recordSandboxOperation({
9232
+ recordOperation({
9263
9233
  actionType,
9264
9234
  durationMs: Date.now() - startTime,
9265
9235
  success
@@ -9276,6 +9246,7 @@ function buildEnvironmentVariables(context, apiUrl) {
9276
9246
  VM0_API_TOKEN: context.sandboxToken,
9277
9247
  VM0_PROMPT: context.prompt,
9278
9248
  VM0_WORKING_DIR: context.workingDir,
9249
+ VM0_API_START_TIME: context.apiStartTime?.toString() ?? "",
9279
9250
  CLI_AGENT_TYPE: context.cliAgentType || "claude-code"
9280
9251
  };
9281
9252
  const vercelBypass = process.env.VERCEL_AUTOMATION_BYPASS_SECRET;
@@ -9426,15 +9397,18 @@ async function installProxyCA(guest, caCertPath) {
9426
9397
  );
9427
9398
  }
9428
9399
  const caCert = fs8.readFileSync(caCertPath, "utf-8");
9400
+ const certWithNewline = caCert.endsWith("\n") ? caCert : caCert + "\n";
9429
9401
  console.log(
9430
- `[Executor] Installing proxy CA certificate (${caCert.length} bytes)`
9402
+ `[Executor] Installing proxy CA certificate (${certWithNewline.length} bytes)`
9431
9403
  );
9432
9404
  await guest.writeFileWithSudo(
9433
9405
  "/usr/local/share/ca-certificates/vm0-proxy-ca.crt",
9434
- caCert
9406
+ certWithNewline
9435
9407
  );
9436
- await guest.execOrThrow("sudo update-ca-certificates");
9437
- console.log(`[Executor] Proxy CA certificate installed successfully`);
9408
+ await guest.execOrThrow(
9409
+ "cat /usr/local/share/ca-certificates/vm0-proxy-ca.crt | sudo tee -a /etc/ssl/certs/ca-certificates.crt > /dev/null"
9410
+ );
9411
+ console.log("[Executor] Proxy CA certificate installed successfully");
9438
9412
  }
9439
9413
 
9440
9414
  // src/lib/executor.ts
@@ -9492,6 +9466,18 @@ async function reportPreflightFailure(apiUrl, runId, sandboxToken, error, bypass
9492
9466
  }
9493
9467
  }
9494
9468
  async function executeJob(context, config, options = {}) {
9469
+ setSandboxContext({
9470
+ apiUrl: config.server.url,
9471
+ runId: context.runId,
9472
+ sandboxToken: context.sandboxToken
9473
+ });
9474
+ if (context.apiStartTime) {
9475
+ recordOperation({
9476
+ actionType: "api_to_vm_start",
9477
+ durationMs: Date.now() - context.apiStartTime,
9478
+ success: true
9479
+ });
9480
+ }
9495
9481
  const vmId = getVmIdFromRunId(context.runId);
9496
9482
  let vm = null;
9497
9483
  let guestIp = null;
@@ -9533,19 +9519,25 @@ async function executeJob(context, config, options = {}) {
9533
9519
  log(
9534
9520
  `[Executor] Setting up network security for VM ${guestIp} (mitm=${mitmEnabled}, sealSecrets=${sealSecretsEnabled})`
9535
9521
  );
9536
- await setupVMProxyRules(guestIp, config.proxy.port, config.name);
9537
- getVMRegistry().register(guestIp, context.runId, context.sandboxToken, {
9538
- firewallRules: firewallConfig?.rules,
9539
- mitmEnabled,
9540
- sealSecretsEnabled
9541
- });
9542
- if (mitmEnabled) {
9543
- const caCertPath = path4.join(
9544
- config.proxy.ca_dir,
9545
- "mitmproxy-ca-cert.pem"
9522
+ await withSandboxTiming("network_setup", async () => {
9523
+ getVMRegistry().register(
9524
+ guestIp,
9525
+ context.runId,
9526
+ context.sandboxToken,
9527
+ {
9528
+ firewallRules: firewallConfig?.rules,
9529
+ mitmEnabled,
9530
+ sealSecretsEnabled
9531
+ }
9546
9532
  );
9547
- await installProxyCA(guest, caCertPath);
9548
- }
9533
+ if (mitmEnabled) {
9534
+ const caCertPath = path4.join(
9535
+ config.proxy.ca_dir,
9536
+ "mitmproxy-ca-cert.pem"
9537
+ );
9538
+ await installProxyCA(guest, caCertPath);
9539
+ }
9540
+ });
9549
9541
  }
9550
9542
  if (context.storageManifest) {
9551
9543
  await withSandboxTiming(
@@ -9573,12 +9565,15 @@ async function executeJob(context, config, options = {}) {
9573
9565
  if (!options.benchmarkMode) {
9574
9566
  log(`[Executor] Running preflight connectivity check...`);
9575
9567
  const bypassSecret = process.env.VERCEL_AUTOMATION_BYPASS_SECRET;
9576
- const preflight = await runPreflightCheck(
9577
- guest,
9578
- config.server.url,
9579
- context.runId,
9580
- context.sandboxToken,
9581
- bypassSecret
9568
+ const preflight = await withSandboxTiming(
9569
+ "preflight_check",
9570
+ () => runPreflightCheck(
9571
+ guest,
9572
+ config.server.url,
9573
+ context.runId,
9574
+ context.sandboxToken,
9575
+ bypassSecret
9576
+ )
9582
9577
  );
9583
9578
  if (!preflight.success) {
9584
9579
  log(`[Executor] Preflight check failed: ${preflight.error}`);
@@ -9650,7 +9645,7 @@ async function executeJob(context, config, options = {}) {
9650
9645
  );
9651
9646
  }
9652
9647
  const durationMs2 = Date.now() - startTime;
9653
- recordRunnerOperation({
9648
+ recordOperation({
9654
9649
  actionType: "agent_execute",
9655
9650
  durationMs: durationMs2,
9656
9651
  success: false
@@ -9666,7 +9661,7 @@ async function executeJob(context, config, options = {}) {
9666
9661
  const duration = Math.round(durationMs / 1e3);
9667
9662
  if (!completed) {
9668
9663
  log(`[Executor] Agent timed out after ${duration}s`);
9669
- recordRunnerOperation({
9664
+ recordOperation({
9670
9665
  actionType: "agent_execute",
9671
9666
  durationMs,
9672
9667
  success: false
@@ -9676,7 +9671,7 @@ async function executeJob(context, config, options = {}) {
9676
9671
  error: `Agent execution timed out after ${duration}s`
9677
9672
  };
9678
9673
  }
9679
- recordRunnerOperation({
9674
+ recordOperation({
9680
9675
  actionType: "agent_execute",
9681
9676
  durationMs,
9682
9677
  success: exitCode === 0
@@ -9704,13 +9699,6 @@ async function executeJob(context, config, options = {}) {
9704
9699
  } finally {
9705
9700
  if (context.experimentalFirewall?.enabled && guestIp) {
9706
9701
  log(`[Executor] Cleaning up network security for VM ${guestIp}`);
9707
- try {
9708
- await removeVMProxyRules(guestIp, config.proxy.port, config.name);
9709
- } catch (err) {
9710
- console.error(
9711
- `[Executor] Failed to remove VM proxy rules: ${err instanceof Error ? err.message : "Unknown error"}`
9712
- );
9713
- }
9714
9702
  getVMRegistry().unregister(guestIp);
9715
9703
  if (!options.benchmarkMode) {
9716
9704
  try {
@@ -9730,6 +9718,7 @@ async function executeJob(context, config, options = {}) {
9730
9718
  log(`[Executor] Cleaning up VM ${vmId}...`);
9731
9719
  await withSandboxTiming("cleanup", () => vm.kill());
9732
9720
  }
9721
+ await clearSandboxContext();
9733
9722
  }
9734
9723
  }
9735
9724
 
@@ -9765,18 +9754,6 @@ function createStatusUpdater(statusFilePath, state) {
9765
9754
  // src/lib/runner/setup.ts
9766
9755
  async function setupEnvironment(options) {
9767
9756
  const { config } = options;
9768
- const datasetSuffix = process.env.AXIOM_DATASET_SUFFIX;
9769
- if (!datasetSuffix) {
9770
- throw new Error(
9771
- "AXIOM_DATASET_SUFFIX is required. Set to 'dev' or 'prod'."
9772
- );
9773
- }
9774
- initMetrics({
9775
- serviceName: "vm0-runner",
9776
- runnerLabel: config.name,
9777
- axiomToken: process.env.AXIOM_TOKEN,
9778
- environment: datasetSuffix
9779
- });
9780
9757
  const networkCheck = checkNetworkPrerequisites();
9781
9758
  if (!networkCheck.ok) {
9782
9759
  console.error("Network prerequisites not met:");
@@ -9805,6 +9782,8 @@ async function setupEnvironment(options) {
9805
9782
  await proxyManager.start();
9806
9783
  proxyEnabled = true;
9807
9784
  console.log("Network proxy initialized successfully");
9785
+ console.log("Setting up CIDR proxy rules...");
9786
+ await setupCIDRProxyRules(config.proxy.port);
9808
9787
  } catch (err) {
9809
9788
  console.warn(
9810
9789
  `Network proxy not available: ${err instanceof Error ? err.message : "Unknown error"}`
@@ -9813,16 +9792,17 @@ async function setupEnvironment(options) {
9813
9792
  "Jobs with experimentalFirewall enabled will run without network interception"
9814
9793
  );
9815
9794
  }
9816
- return { proxyEnabled };
9795
+ return { proxyEnabled, proxyPort: config.proxy.port };
9817
9796
  }
9818
9797
  async function cleanupEnvironment(resources) {
9798
+ if (resources.proxyEnabled) {
9799
+ console.log("Cleaning up CIDR proxy rules...");
9800
+ await cleanupCIDRProxyRules(resources.proxyPort);
9801
+ }
9819
9802
  if (resources.proxyEnabled) {
9820
9803
  console.log("Stopping network proxy...");
9821
9804
  await getProxyManager().stop();
9822
9805
  }
9823
- console.log("Flushing metrics...");
9824
- await flushMetrics();
9825
- await shutdownMetrics();
9826
9806
  }
9827
9807
 
9828
9808
  // src/lib/runner/signals.ts
@@ -10659,7 +10639,7 @@ var benchmarkCommand = new Command4("benchmark").description(
10659
10639
  });
10660
10640
 
10661
10641
  // src/index.ts
10662
- var version = true ? "3.4.0" : "0.1.0";
10642
+ var version = true ? "3.6.0" : "0.1.0";
10663
10643
  program.name("vm0-runner").version(version).description("Self-hosted runner for VM0 agents");
10664
10644
  program.addCommand(startCommand);
10665
10645
  program.addCommand(doctorCommand);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vm0/runner",
3
- "version": "3.4.0",
3
+ "version": "3.6.0",
4
4
  "description": "Self-hosted runner for VM0 agents",
5
5
  "repository": {
6
6
  "type": "git",
@@ -15,11 +15,6 @@
15
15
  "."
16
16
  ],
17
17
  "dependencies": {
18
- "@opentelemetry/api": "^1.9.0",
19
- "@opentelemetry/exporter-metrics-otlp-proto": "^0.52.0",
20
- "@opentelemetry/resources": "^1.25.0",
21
- "@opentelemetry/sdk-metrics": "^1.25.0",
22
- "@opentelemetry/semantic-conventions": "^1.25.0",
23
18
  "ably": "^2.17.0",
24
19
  "commander": "^14.0.0",
25
20
  "yaml": "^2.3.4",