@vm0/runner 2.6.1 → 2.7.1

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 +815 -224
  2. package/package.json +6 -1
package/index.js CHANGED
@@ -37,6 +37,42 @@ var runnerConfigSchema = z.object({
37
37
  port: z.number().int().min(1024).max(65535).default(8080)
38
38
  }).default({})
39
39
  });
40
+ var debugConfigSchema = z.object({
41
+ name: z.string().default("debug-runner"),
42
+ group: z.string().default("debug/local"),
43
+ server: z.object({
44
+ url: z.string().url().default("http://localhost:3000"),
45
+ token: z.string().default("debug-token")
46
+ }).default({}),
47
+ sandbox: z.object({
48
+ max_concurrent: z.number().int().min(1).default(1),
49
+ vcpu: z.number().int().min(1).default(2),
50
+ memory_mb: z.number().int().min(128).default(2048),
51
+ poll_interval_ms: z.number().int().min(1e3).default(5e3)
52
+ }).default({}),
53
+ firecracker: z.object({
54
+ binary: z.string().min(1, "Firecracker binary path is required"),
55
+ kernel: z.string().min(1, "Kernel path is required"),
56
+ rootfs: z.string().min(1, "Rootfs path is required")
57
+ }),
58
+ proxy: z.object({
59
+ port: z.number().int().min(1024).max(65535).default(8080)
60
+ }).default({})
61
+ });
62
+ function loadDebugConfig(configPath) {
63
+ if (!fs.existsSync(configPath)) {
64
+ throw new Error(`Config file not found: ${configPath}`);
65
+ }
66
+ const content = fs.readFileSync(configPath, "utf-8");
67
+ const raw = yaml.parse(content);
68
+ const result = debugConfigSchema.safeParse(raw);
69
+ if (!result.success) {
70
+ const errors = result.error.errors.map((e) => ` - ${e.path.join(".")}: ${e.message}`).join("\n");
71
+ throw new Error(`Invalid configuration:
72
+ ${errors}`);
73
+ }
74
+ return result.data;
75
+ }
40
76
  function loadConfig(configPath) {
41
77
  if (!fs.existsSync(configPath)) {
42
78
  throw new Error(`runner.yaml not found: ${configPath}`);
@@ -142,7 +178,7 @@ import path4 from "path";
142
178
  import fs6 from "fs";
143
179
 
144
180
  // src/lib/firecracker/vm.ts
145
- import { spawn } from "child_process";
181
+ import { execSync as execSync2, spawn } from "child_process";
146
182
  import fs2 from "fs";
147
183
  import path from "path";
148
184
  import readline from "readline";
@@ -545,13 +581,13 @@ var FirecrackerVM = class {
545
581
  state = "created";
546
582
  workDir;
547
583
  socketPath;
548
- vmRootfsPath;
549
- // Per-VM copy of rootfs
584
+ vmOverlayPath;
585
+ // Per-VM sparse overlay for writes
550
586
  constructor(config) {
551
587
  this.config = config;
552
588
  this.workDir = config.workDir || `/tmp/vm0-vm-${config.vmId}`;
553
589
  this.socketPath = path.join(this.workDir, "firecracker.sock");
554
- this.vmRootfsPath = path.join(this.workDir, "rootfs.ext4");
590
+ this.vmOverlayPath = path.join(this.workDir, "overlay.ext4");
555
591
  }
556
592
  /**
557
593
  * Get current VM state
@@ -590,8 +626,12 @@ var FirecrackerVM = class {
590
626
  if (fs2.existsSync(this.socketPath)) {
591
627
  fs2.unlinkSync(this.socketPath);
592
628
  }
593
- console.log(`[VM ${this.config.vmId}] Copying rootfs for isolation...`);
594
- fs2.copyFileSync(this.config.rootfsPath, this.vmRootfsPath);
629
+ console.log(`[VM ${this.config.vmId}] Creating sparse overlay file...`);
630
+ const overlaySize = 2 * 1024 * 1024 * 1024;
631
+ const fd = fs2.openSync(this.vmOverlayPath, "w");
632
+ fs2.ftruncateSync(fd, overlaySize);
633
+ fs2.closeSync(fd);
634
+ execSync2(`mkfs.ext4 -F -q "${this.vmOverlayPath}"`, { stdio: "ignore" });
595
635
  console.log(`[VM ${this.config.vmId}] Setting up network...`);
596
636
  this.networkConfig = await createTapDevice(this.config.vmId);
597
637
  console.log(`[VM ${this.config.vmId}] Starting Firecracker...`);
@@ -668,19 +708,27 @@ var FirecrackerVM = class {
668
708
  mem_size_mib: this.config.memoryMb
669
709
  });
670
710
  const networkBootArgs = generateNetworkBootArgs(this.networkConfig);
671
- const bootArgs = `console=ttyS0 reboot=k panic=1 pci=off ${networkBootArgs}`;
711
+ const bootArgs = `console=ttyS0 reboot=k panic=1 pci=off init=/sbin/overlay-init ${networkBootArgs}`;
672
712
  console.log(`[VM ${this.config.vmId}] Boot args: ${bootArgs}`);
673
713
  await this.client.setBootSource({
674
714
  kernel_image_path: this.config.kernelPath,
675
715
  boot_args: bootArgs
676
716
  });
677
- console.log(`[VM ${this.config.vmId}] Rootfs: ${this.vmRootfsPath}`);
717
+ console.log(
718
+ `[VM ${this.config.vmId}] Base rootfs: ${this.config.rootfsPath}`
719
+ );
678
720
  await this.client.setDrive({
679
721
  drive_id: "rootfs",
680
- path_on_host: this.vmRootfsPath,
722
+ path_on_host: this.config.rootfsPath,
681
723
  is_root_device: true,
724
+ is_read_only: true
725
+ });
726
+ console.log(`[VM ${this.config.vmId}] Overlay: ${this.vmOverlayPath}`);
727
+ await this.client.setDrive({
728
+ drive_id: "overlay",
729
+ path_on_host: this.vmOverlayPath,
730
+ is_root_device: false,
682
731
  is_read_only: false
683
- // Need write access for agent execution
684
732
  });
685
733
  console.log(
686
734
  `[VM ${this.config.vmId}] Network: ${this.networkConfig.tapDevice}`
@@ -770,7 +818,7 @@ var FirecrackerVM = class {
770
818
  };
771
819
 
772
820
  // src/lib/firecracker/guest.ts
773
- import { exec as exec2, execSync as execSync2 } from "child_process";
821
+ import { exec as exec2, execSync as execSync3 } from "child_process";
774
822
  import { promisify as promisify2 } from "util";
775
823
  import fs3 from "fs";
776
824
  import path2 from "path";
@@ -6239,9 +6287,207 @@ var checkpointsByIdContract = c10.router({
6239
6287
  }
6240
6288
  });
6241
6289
 
6242
- // ../../packages/core/src/contracts/public/common.ts
6290
+ // ../../packages/core/src/contracts/schedules.ts
6243
6291
  import { z as z15 } from "zod";
6244
- var publicApiErrorTypeSchema = z15.enum([
6292
+ var c11 = initContract();
6293
+ var scheduleTriggerSchema = z15.object({
6294
+ cron: z15.string().optional(),
6295
+ at: z15.string().optional(),
6296
+ timezone: z15.string().default("UTC")
6297
+ }).refine((data) => data.cron && !data.at || !data.cron && data.at, {
6298
+ message: "Exactly one of 'cron' or 'at' must be specified"
6299
+ });
6300
+ var scheduleRunConfigSchema = z15.object({
6301
+ agent: z15.string().min(1, "Agent reference required"),
6302
+ prompt: z15.string().min(1, "Prompt required"),
6303
+ vars: z15.record(z15.string(), z15.string()).optional(),
6304
+ secrets: z15.record(z15.string(), z15.string()).optional(),
6305
+ artifactName: z15.string().optional(),
6306
+ artifactVersion: z15.string().optional(),
6307
+ volumeVersions: z15.record(z15.string(), z15.string()).optional()
6308
+ });
6309
+ var scheduleDefinitionSchema = z15.object({
6310
+ on: scheduleTriggerSchema,
6311
+ run: scheduleRunConfigSchema
6312
+ });
6313
+ var scheduleYamlSchema = z15.object({
6314
+ version: z15.literal("1.0"),
6315
+ schedules: z15.record(z15.string(), scheduleDefinitionSchema)
6316
+ });
6317
+ var deployScheduleRequestSchema = z15.object({
6318
+ name: z15.string().min(1).max(64, "Schedule name max 64 chars"),
6319
+ cronExpression: z15.string().optional(),
6320
+ atTime: z15.string().optional(),
6321
+ timezone: z15.string().default("UTC"),
6322
+ prompt: z15.string().min(1, "Prompt required"),
6323
+ vars: z15.record(z15.string(), z15.string()).optional(),
6324
+ secrets: z15.record(z15.string(), z15.string()).optional(),
6325
+ artifactName: z15.string().optional(),
6326
+ artifactVersion: z15.string().optional(),
6327
+ volumeVersions: z15.record(z15.string(), z15.string()).optional(),
6328
+ // Resolved agent compose ID (CLI resolves scope/name:version → composeId)
6329
+ composeId: z15.string().uuid("Invalid compose ID")
6330
+ }).refine(
6331
+ (data) => data.cronExpression && !data.atTime || !data.cronExpression && data.atTime,
6332
+ {
6333
+ message: "Exactly one of 'cronExpression' or 'atTime' must be specified"
6334
+ }
6335
+ );
6336
+ var scheduleResponseSchema = z15.object({
6337
+ id: z15.string().uuid(),
6338
+ composeId: z15.string().uuid(),
6339
+ composeName: z15.string(),
6340
+ scopeSlug: z15.string(),
6341
+ name: z15.string(),
6342
+ cronExpression: z15.string().nullable(),
6343
+ atTime: z15.string().nullable(),
6344
+ timezone: z15.string(),
6345
+ prompt: z15.string(),
6346
+ vars: z15.record(z15.string(), z15.string()).nullable(),
6347
+ // Secret names only (values are never returned)
6348
+ secretNames: z15.array(z15.string()).nullable(),
6349
+ artifactName: z15.string().nullable(),
6350
+ artifactVersion: z15.string().nullable(),
6351
+ volumeVersions: z15.record(z15.string(), z15.string()).nullable(),
6352
+ enabled: z15.boolean(),
6353
+ nextRunAt: z15.string().nullable(),
6354
+ lastRunAt: z15.string().nullable(),
6355
+ lastRunId: z15.string().nullable(),
6356
+ createdAt: z15.string(),
6357
+ updatedAt: z15.string()
6358
+ });
6359
+ var scheduleListResponseSchema = z15.object({
6360
+ schedules: z15.array(scheduleResponseSchema)
6361
+ });
6362
+ var deployScheduleResponseSchema = z15.object({
6363
+ schedule: scheduleResponseSchema,
6364
+ created: z15.boolean()
6365
+ // true if created, false if updated
6366
+ });
6367
+ var schedulesMainContract = c11.router({
6368
+ /**
6369
+ * POST /api/agent/schedules
6370
+ * Deploy (create or update) a schedule
6371
+ */
6372
+ deploy: {
6373
+ method: "POST",
6374
+ path: "/api/agent/schedules",
6375
+ body: deployScheduleRequestSchema,
6376
+ responses: {
6377
+ 200: deployScheduleResponseSchema,
6378
+ // Updated
6379
+ 201: deployScheduleResponseSchema,
6380
+ // Created
6381
+ 400: apiErrorSchema,
6382
+ 401: apiErrorSchema,
6383
+ 404: apiErrorSchema,
6384
+ 409: apiErrorSchema
6385
+ // Schedule limit reached
6386
+ },
6387
+ summary: "Deploy schedule (create or update)"
6388
+ },
6389
+ /**
6390
+ * GET /api/agent/schedules
6391
+ * List all schedules for the user
6392
+ */
6393
+ list: {
6394
+ method: "GET",
6395
+ path: "/api/agent/schedules",
6396
+ responses: {
6397
+ 200: scheduleListResponseSchema,
6398
+ 401: apiErrorSchema
6399
+ },
6400
+ summary: "List all schedules"
6401
+ }
6402
+ });
6403
+ var schedulesByNameContract = c11.router({
6404
+ /**
6405
+ * GET /api/agent/schedules/:name
6406
+ * Get schedule by name
6407
+ */
6408
+ getByName: {
6409
+ method: "GET",
6410
+ path: "/api/agent/schedules/:name",
6411
+ pathParams: z15.object({
6412
+ name: z15.string().min(1, "Schedule name required")
6413
+ }),
6414
+ query: z15.object({
6415
+ composeId: z15.string().uuid("Compose ID required")
6416
+ }),
6417
+ responses: {
6418
+ 200: scheduleResponseSchema,
6419
+ 401: apiErrorSchema,
6420
+ 404: apiErrorSchema
6421
+ },
6422
+ summary: "Get schedule by name"
6423
+ },
6424
+ /**
6425
+ * DELETE /api/agent/schedules/:name
6426
+ * Delete schedule by name
6427
+ */
6428
+ delete: {
6429
+ method: "DELETE",
6430
+ path: "/api/agent/schedules/:name",
6431
+ pathParams: z15.object({
6432
+ name: z15.string().min(1, "Schedule name required")
6433
+ }),
6434
+ query: z15.object({
6435
+ composeId: z15.string().uuid("Compose ID required")
6436
+ }),
6437
+ responses: {
6438
+ 204: z15.undefined(),
6439
+ 401: apiErrorSchema,
6440
+ 404: apiErrorSchema
6441
+ },
6442
+ summary: "Delete schedule"
6443
+ }
6444
+ });
6445
+ var schedulesEnableContract = c11.router({
6446
+ /**
6447
+ * POST /api/agent/schedules/:name/enable
6448
+ * Enable a disabled schedule
6449
+ */
6450
+ enable: {
6451
+ method: "POST",
6452
+ path: "/api/agent/schedules/:name/enable",
6453
+ pathParams: z15.object({
6454
+ name: z15.string().min(1, "Schedule name required")
6455
+ }),
6456
+ body: z15.object({
6457
+ composeId: z15.string().uuid("Compose ID required")
6458
+ }),
6459
+ responses: {
6460
+ 200: scheduleResponseSchema,
6461
+ 401: apiErrorSchema,
6462
+ 404: apiErrorSchema
6463
+ },
6464
+ summary: "Enable schedule"
6465
+ },
6466
+ /**
6467
+ * POST /api/agent/schedules/:name/disable
6468
+ * Disable an enabled schedule
6469
+ */
6470
+ disable: {
6471
+ method: "POST",
6472
+ path: "/api/agent/schedules/:name/disable",
6473
+ pathParams: z15.object({
6474
+ name: z15.string().min(1, "Schedule name required")
6475
+ }),
6476
+ body: z15.object({
6477
+ composeId: z15.string().uuid("Compose ID required")
6478
+ }),
6479
+ responses: {
6480
+ 200: scheduleResponseSchema,
6481
+ 401: apiErrorSchema,
6482
+ 404: apiErrorSchema
6483
+ },
6484
+ summary: "Disable schedule"
6485
+ }
6486
+ });
6487
+
6488
+ // ../../packages/core/src/contracts/public/common.ts
6489
+ import { z as z16 } from "zod";
6490
+ var publicApiErrorTypeSchema = z16.enum([
6245
6491
  "api_error",
6246
6492
  // Internal server error (5xx)
6247
6493
  "invalid_request_error",
@@ -6253,55 +6499,55 @@ var publicApiErrorTypeSchema = z15.enum([
6253
6499
  "conflict_error"
6254
6500
  // Resource conflict (409)
6255
6501
  ]);
6256
- var publicApiErrorSchema = z15.object({
6257
- error: z15.object({
6502
+ var publicApiErrorSchema = z16.object({
6503
+ error: z16.object({
6258
6504
  type: publicApiErrorTypeSchema,
6259
- code: z15.string(),
6260
- message: z15.string(),
6261
- param: z15.string().optional(),
6262
- doc_url: z15.string().url().optional()
6505
+ code: z16.string(),
6506
+ message: z16.string(),
6507
+ param: z16.string().optional(),
6508
+ doc_url: z16.string().url().optional()
6263
6509
  })
6264
6510
  });
6265
- var paginationSchema = z15.object({
6266
- has_more: z15.boolean(),
6267
- next_cursor: z15.string().nullable()
6511
+ var paginationSchema = z16.object({
6512
+ has_more: z16.boolean(),
6513
+ next_cursor: z16.string().nullable()
6268
6514
  });
6269
6515
  function createPaginatedResponseSchema(dataSchema) {
6270
- return z15.object({
6271
- data: z15.array(dataSchema),
6516
+ return z16.object({
6517
+ data: z16.array(dataSchema),
6272
6518
  pagination: paginationSchema
6273
6519
  });
6274
6520
  }
6275
- var listQuerySchema = z15.object({
6276
- cursor: z15.string().optional(),
6277
- limit: z15.coerce.number().min(1).max(100).default(20)
6521
+ var listQuerySchema = z16.object({
6522
+ cursor: z16.string().optional(),
6523
+ limit: z16.coerce.number().min(1).max(100).default(20)
6278
6524
  });
6279
- var requestIdSchema = z15.string().uuid();
6280
- var timestampSchema = z15.string().datetime();
6525
+ var requestIdSchema = z16.string().uuid();
6526
+ var timestampSchema = z16.string().datetime();
6281
6527
 
6282
6528
  // ../../packages/core/src/contracts/public/agents.ts
6283
- import { z as z16 } from "zod";
6284
- var c11 = initContract();
6285
- var publicAgentSchema = z16.object({
6286
- id: z16.string(),
6287
- name: z16.string(),
6288
- current_version_id: z16.string().nullable(),
6529
+ import { z as z17 } from "zod";
6530
+ var c12 = initContract();
6531
+ var publicAgentSchema = z17.object({
6532
+ id: z17.string(),
6533
+ name: z17.string(),
6534
+ current_version_id: z17.string().nullable(),
6289
6535
  created_at: timestampSchema,
6290
6536
  updated_at: timestampSchema
6291
6537
  });
6292
- var agentVersionSchema = z16.object({
6293
- id: z16.string(),
6294
- agent_id: z16.string(),
6295
- version_number: z16.number(),
6538
+ var agentVersionSchema = z17.object({
6539
+ id: z17.string(),
6540
+ agent_id: z17.string(),
6541
+ version_number: z17.number(),
6296
6542
  created_at: timestampSchema
6297
6543
  });
6298
6544
  var publicAgentDetailSchema = publicAgentSchema;
6299
6545
  var paginatedAgentsSchema = createPaginatedResponseSchema(publicAgentSchema);
6300
6546
  var paginatedAgentVersionsSchema = createPaginatedResponseSchema(agentVersionSchema);
6301
6547
  var agentListQuerySchema = listQuerySchema.extend({
6302
- name: z16.string().optional()
6548
+ name: z17.string().optional()
6303
6549
  });
6304
- var publicAgentsListContract = c11.router({
6550
+ var publicAgentsListContract = c12.router({
6305
6551
  list: {
6306
6552
  method: "GET",
6307
6553
  path: "/v1/agents",
@@ -6315,12 +6561,12 @@ var publicAgentsListContract = c11.router({
6315
6561
  description: "List all agents in the current scope with pagination. Use the `name` query parameter to filter by agent name."
6316
6562
  }
6317
6563
  });
6318
- var publicAgentByIdContract = c11.router({
6564
+ var publicAgentByIdContract = c12.router({
6319
6565
  get: {
6320
6566
  method: "GET",
6321
6567
  path: "/v1/agents/:id",
6322
- pathParams: z16.object({
6323
- id: z16.string().min(1, "Agent ID is required")
6568
+ pathParams: z17.object({
6569
+ id: z17.string().min(1, "Agent ID is required")
6324
6570
  }),
6325
6571
  responses: {
6326
6572
  200: publicAgentDetailSchema,
@@ -6332,12 +6578,12 @@ var publicAgentByIdContract = c11.router({
6332
6578
  description: "Get agent details by ID"
6333
6579
  }
6334
6580
  });
6335
- var publicAgentVersionsContract = c11.router({
6581
+ var publicAgentVersionsContract = c12.router({
6336
6582
  list: {
6337
6583
  method: "GET",
6338
6584
  path: "/v1/agents/:id/versions",
6339
- pathParams: z16.object({
6340
- id: z16.string().min(1, "Agent ID is required")
6585
+ pathParams: z17.object({
6586
+ id: z17.string().min(1, "Agent ID is required")
6341
6587
  }),
6342
6588
  query: listQuerySchema,
6343
6589
  responses: {
@@ -6352,9 +6598,9 @@ var publicAgentVersionsContract = c11.router({
6352
6598
  });
6353
6599
 
6354
6600
  // ../../packages/core/src/contracts/public/runs.ts
6355
- import { z as z17 } from "zod";
6356
- var c12 = initContract();
6357
- var publicRunStatusSchema = z17.enum([
6601
+ import { z as z18 } from "zod";
6602
+ var c13 = initContract();
6603
+ var publicRunStatusSchema = z18.enum([
6358
6604
  "pending",
6359
6605
  "running",
6360
6606
  "completed",
@@ -6362,56 +6608,56 @@ var publicRunStatusSchema = z17.enum([
6362
6608
  "timeout",
6363
6609
  "cancelled"
6364
6610
  ]);
6365
- var publicRunSchema = z17.object({
6366
- id: z17.string(),
6367
- agent_id: z17.string(),
6368
- agent_name: z17.string(),
6611
+ var publicRunSchema = z18.object({
6612
+ id: z18.string(),
6613
+ agent_id: z18.string(),
6614
+ agent_name: z18.string(),
6369
6615
  status: publicRunStatusSchema,
6370
- prompt: z17.string(),
6616
+ prompt: z18.string(),
6371
6617
  created_at: timestampSchema,
6372
6618
  started_at: timestampSchema.nullable(),
6373
6619
  completed_at: timestampSchema.nullable()
6374
6620
  });
6375
6621
  var publicRunDetailSchema = publicRunSchema.extend({
6376
- error: z17.string().nullable(),
6377
- execution_time_ms: z17.number().nullable(),
6378
- checkpoint_id: z17.string().nullable(),
6379
- session_id: z17.string().nullable(),
6380
- artifact_name: z17.string().nullable(),
6381
- artifact_version: z17.string().nullable(),
6382
- volumes: z17.record(z17.string(), z17.string()).optional()
6622
+ error: z18.string().nullable(),
6623
+ execution_time_ms: z18.number().nullable(),
6624
+ checkpoint_id: z18.string().nullable(),
6625
+ session_id: z18.string().nullable(),
6626
+ artifact_name: z18.string().nullable(),
6627
+ artifact_version: z18.string().nullable(),
6628
+ volumes: z18.record(z18.string(), z18.string()).optional()
6383
6629
  });
6384
6630
  var paginatedRunsSchema = createPaginatedResponseSchema(publicRunSchema);
6385
- var createRunRequestSchema = z17.object({
6631
+ var createRunRequestSchema = z18.object({
6386
6632
  // Agent identification (one of: agent, agent_id, session_id, checkpoint_id)
6387
- agent: z17.string().optional(),
6633
+ agent: z18.string().optional(),
6388
6634
  // Agent name
6389
- agent_id: z17.string().optional(),
6635
+ agent_id: z18.string().optional(),
6390
6636
  // Agent ID
6391
- agent_version: z17.string().optional(),
6637
+ agent_version: z18.string().optional(),
6392
6638
  // Version specifier (e.g., "latest", "v1", specific ID)
6393
6639
  // Continue session
6394
- session_id: z17.string().optional(),
6640
+ session_id: z18.string().optional(),
6395
6641
  // Resume from checkpoint
6396
- checkpoint_id: z17.string().optional(),
6642
+ checkpoint_id: z18.string().optional(),
6397
6643
  // Required
6398
- prompt: z17.string().min(1, "Prompt is required"),
6644
+ prompt: z18.string().min(1, "Prompt is required"),
6399
6645
  // Optional configuration
6400
- variables: z17.record(z17.string(), z17.string()).optional(),
6401
- secrets: z17.record(z17.string(), z17.string()).optional(),
6402
- artifact_name: z17.string().optional(),
6646
+ variables: z18.record(z18.string(), z18.string()).optional(),
6647
+ secrets: z18.record(z18.string(), z18.string()).optional(),
6648
+ artifact_name: z18.string().optional(),
6403
6649
  // Artifact name to mount
6404
- artifact_version: z17.string().optional(),
6650
+ artifact_version: z18.string().optional(),
6405
6651
  // Artifact version (defaults to latest)
6406
- volumes: z17.record(z17.string(), z17.string()).optional()
6652
+ volumes: z18.record(z18.string(), z18.string()).optional()
6407
6653
  // volume_name -> version
6408
6654
  });
6409
6655
  var runListQuerySchema = listQuerySchema.extend({
6410
- agent_id: z17.string().optional(),
6656
+ agent_id: z18.string().optional(),
6411
6657
  status: publicRunStatusSchema.optional(),
6412
6658
  since: timestampSchema.optional()
6413
6659
  });
6414
- var publicRunsListContract = c12.router({
6660
+ var publicRunsListContract = c13.router({
6415
6661
  list: {
6416
6662
  method: "GET",
6417
6663
  path: "/v1/runs",
@@ -6440,12 +6686,12 @@ var publicRunsListContract = c12.router({
6440
6686
  description: "Create and execute a new agent run. Returns 202 Accepted as runs execute asynchronously."
6441
6687
  }
6442
6688
  });
6443
- var publicRunByIdContract = c12.router({
6689
+ var publicRunByIdContract = c13.router({
6444
6690
  get: {
6445
6691
  method: "GET",
6446
6692
  path: "/v1/runs/:id",
6447
- pathParams: z17.object({
6448
- id: z17.string().min(1, "Run ID is required")
6693
+ pathParams: z18.object({
6694
+ id: z18.string().min(1, "Run ID is required")
6449
6695
  }),
6450
6696
  responses: {
6451
6697
  200: publicRunDetailSchema,
@@ -6457,14 +6703,14 @@ var publicRunByIdContract = c12.router({
6457
6703
  description: "Get run details by ID"
6458
6704
  }
6459
6705
  });
6460
- var publicRunCancelContract = c12.router({
6706
+ var publicRunCancelContract = c13.router({
6461
6707
  cancel: {
6462
6708
  method: "POST",
6463
6709
  path: "/v1/runs/:id/cancel",
6464
- pathParams: z17.object({
6465
- id: z17.string().min(1, "Run ID is required")
6710
+ pathParams: z18.object({
6711
+ id: z18.string().min(1, "Run ID is required")
6466
6712
  }),
6467
- body: z17.undefined(),
6713
+ body: z18.undefined(),
6468
6714
  responses: {
6469
6715
  200: publicRunDetailSchema,
6470
6716
  400: publicApiErrorSchema,
@@ -6477,26 +6723,26 @@ var publicRunCancelContract = c12.router({
6477
6723
  description: "Cancel a pending or running execution"
6478
6724
  }
6479
6725
  });
6480
- var logEntrySchema = z17.object({
6726
+ var logEntrySchema = z18.object({
6481
6727
  timestamp: timestampSchema,
6482
- type: z17.enum(["agent", "system", "network"]),
6483
- level: z17.enum(["debug", "info", "warn", "error"]),
6484
- message: z17.string(),
6485
- metadata: z17.record(z17.string(), z17.unknown()).optional()
6728
+ type: z18.enum(["agent", "system", "network"]),
6729
+ level: z18.enum(["debug", "info", "warn", "error"]),
6730
+ message: z18.string(),
6731
+ metadata: z18.record(z18.string(), z18.unknown()).optional()
6486
6732
  });
6487
6733
  var paginatedLogsSchema = createPaginatedResponseSchema(logEntrySchema);
6488
6734
  var logsQuerySchema = listQuerySchema.extend({
6489
- type: z17.enum(["agent", "system", "network", "all"]).default("all"),
6735
+ type: z18.enum(["agent", "system", "network", "all"]).default("all"),
6490
6736
  since: timestampSchema.optional(),
6491
6737
  until: timestampSchema.optional(),
6492
- order: z17.enum(["asc", "desc"]).default("asc")
6738
+ order: z18.enum(["asc", "desc"]).default("asc")
6493
6739
  });
6494
- var publicRunLogsContract = c12.router({
6740
+ var publicRunLogsContract = c13.router({
6495
6741
  getLogs: {
6496
6742
  method: "GET",
6497
6743
  path: "/v1/runs/:id/logs",
6498
- pathParams: z17.object({
6499
- id: z17.string().min(1, "Run ID is required")
6744
+ pathParams: z18.object({
6745
+ id: z18.string().min(1, "Run ID is required")
6500
6746
  }),
6501
6747
  query: logsQuerySchema,
6502
6748
  responses: {
@@ -6509,29 +6755,29 @@ var publicRunLogsContract = c12.router({
6509
6755
  description: "Get unified logs for a run. Combines agent, system, and network logs."
6510
6756
  }
6511
6757
  });
6512
- var metricPointSchema = z17.object({
6758
+ var metricPointSchema = z18.object({
6513
6759
  timestamp: timestampSchema,
6514
- cpu_percent: z17.number(),
6515
- memory_used_mb: z17.number(),
6516
- memory_total_mb: z17.number(),
6517
- disk_used_mb: z17.number(),
6518
- disk_total_mb: z17.number()
6519
- });
6520
- var metricsSummarySchema = z17.object({
6521
- avg_cpu_percent: z17.number(),
6522
- max_memory_used_mb: z17.number(),
6523
- total_duration_ms: z17.number().nullable()
6524
- });
6525
- var metricsResponseSchema2 = z17.object({
6526
- data: z17.array(metricPointSchema),
6760
+ cpu_percent: z18.number(),
6761
+ memory_used_mb: z18.number(),
6762
+ memory_total_mb: z18.number(),
6763
+ disk_used_mb: z18.number(),
6764
+ disk_total_mb: z18.number()
6765
+ });
6766
+ var metricsSummarySchema = z18.object({
6767
+ avg_cpu_percent: z18.number(),
6768
+ max_memory_used_mb: z18.number(),
6769
+ total_duration_ms: z18.number().nullable()
6770
+ });
6771
+ var metricsResponseSchema2 = z18.object({
6772
+ data: z18.array(metricPointSchema),
6527
6773
  summary: metricsSummarySchema
6528
6774
  });
6529
- var publicRunMetricsContract = c12.router({
6775
+ var publicRunMetricsContract = c13.router({
6530
6776
  getMetrics: {
6531
6777
  method: "GET",
6532
6778
  path: "/v1/runs/:id/metrics",
6533
- pathParams: z17.object({
6534
- id: z17.string().min(1, "Run ID is required")
6779
+ pathParams: z18.object({
6780
+ id: z18.string().min(1, "Run ID is required")
6535
6781
  }),
6536
6782
  responses: {
6537
6783
  200: metricsResponseSchema2,
@@ -6543,7 +6789,7 @@ var publicRunMetricsContract = c12.router({
6543
6789
  description: "Get CPU, memory, and disk metrics for a run"
6544
6790
  }
6545
6791
  });
6546
- var sseEventTypeSchema = z17.enum([
6792
+ var sseEventTypeSchema = z18.enum([
6547
6793
  "status",
6548
6794
  // Run status change
6549
6795
  "output",
@@ -6555,25 +6801,25 @@ var sseEventTypeSchema = z17.enum([
6555
6801
  "heartbeat"
6556
6802
  // Keep-alive
6557
6803
  ]);
6558
- var sseEventSchema = z17.object({
6804
+ var sseEventSchema = z18.object({
6559
6805
  event: sseEventTypeSchema,
6560
- data: z17.unknown(),
6561
- id: z17.string().optional()
6806
+ data: z18.unknown(),
6807
+ id: z18.string().optional()
6562
6808
  // For Last-Event-ID reconnection
6563
6809
  });
6564
- var publicRunEventsContract = c12.router({
6810
+ var publicRunEventsContract = c13.router({
6565
6811
  streamEvents: {
6566
6812
  method: "GET",
6567
6813
  path: "/v1/runs/:id/events",
6568
- pathParams: z17.object({
6569
- id: z17.string().min(1, "Run ID is required")
6814
+ pathParams: z18.object({
6815
+ id: z18.string().min(1, "Run ID is required")
6570
6816
  }),
6571
- query: z17.object({
6572
- last_event_id: z17.string().optional()
6817
+ query: z18.object({
6818
+ last_event_id: z18.string().optional()
6573
6819
  // For reconnection
6574
6820
  }),
6575
6821
  responses: {
6576
- 200: z17.any(),
6822
+ 200: z18.any(),
6577
6823
  // SSE stream - actual content is text/event-stream
6578
6824
  401: publicApiErrorSchema,
6579
6825
  404: publicApiErrorSchema,
@@ -6585,28 +6831,28 @@ var publicRunEventsContract = c12.router({
6585
6831
  });
6586
6832
 
6587
6833
  // ../../packages/core/src/contracts/public/artifacts.ts
6588
- import { z as z18 } from "zod";
6589
- var c13 = initContract();
6590
- var publicArtifactSchema = z18.object({
6591
- id: z18.string(),
6592
- name: z18.string(),
6593
- current_version_id: z18.string().nullable(),
6594
- size: z18.number(),
6834
+ import { z as z19 } from "zod";
6835
+ var c14 = initContract();
6836
+ var publicArtifactSchema = z19.object({
6837
+ id: z19.string(),
6838
+ name: z19.string(),
6839
+ current_version_id: z19.string().nullable(),
6840
+ size: z19.number(),
6595
6841
  // Total size in bytes
6596
- file_count: z18.number(),
6842
+ file_count: z19.number(),
6597
6843
  created_at: timestampSchema,
6598
6844
  updated_at: timestampSchema
6599
6845
  });
6600
- var artifactVersionSchema = z18.object({
6601
- id: z18.string(),
6846
+ var artifactVersionSchema = z19.object({
6847
+ id: z19.string(),
6602
6848
  // SHA-256 content hash
6603
- artifact_id: z18.string(),
6604
- size: z18.number(),
6849
+ artifact_id: z19.string(),
6850
+ size: z19.number(),
6605
6851
  // Size in bytes
6606
- file_count: z18.number(),
6607
- message: z18.string().nullable(),
6852
+ file_count: z19.number(),
6853
+ message: z19.string().nullable(),
6608
6854
  // Optional commit message
6609
- created_by: z18.string(),
6855
+ created_by: z19.string(),
6610
6856
  created_at: timestampSchema
6611
6857
  });
6612
6858
  var publicArtifactDetailSchema = publicArtifactSchema.extend({
@@ -6616,7 +6862,7 @@ var paginatedArtifactsSchema = createPaginatedResponseSchema(publicArtifactSchem
6616
6862
  var paginatedArtifactVersionsSchema = createPaginatedResponseSchema(
6617
6863
  artifactVersionSchema
6618
6864
  );
6619
- var publicArtifactsListContract = c13.router({
6865
+ var publicArtifactsListContract = c14.router({
6620
6866
  list: {
6621
6867
  method: "GET",
6622
6868
  path: "/v1/artifacts",
@@ -6630,12 +6876,12 @@ var publicArtifactsListContract = c13.router({
6630
6876
  description: "List all artifacts in the current scope with pagination"
6631
6877
  }
6632
6878
  });
6633
- var publicArtifactByIdContract = c13.router({
6879
+ var publicArtifactByIdContract = c14.router({
6634
6880
  get: {
6635
6881
  method: "GET",
6636
6882
  path: "/v1/artifacts/:id",
6637
- pathParams: z18.object({
6638
- id: z18.string().min(1, "Artifact ID is required")
6883
+ pathParams: z19.object({
6884
+ id: z19.string().min(1, "Artifact ID is required")
6639
6885
  }),
6640
6886
  responses: {
6641
6887
  200: publicArtifactDetailSchema,
@@ -6647,12 +6893,12 @@ var publicArtifactByIdContract = c13.router({
6647
6893
  description: "Get artifact details by ID"
6648
6894
  }
6649
6895
  });
6650
- var publicArtifactVersionsContract = c13.router({
6896
+ var publicArtifactVersionsContract = c14.router({
6651
6897
  list: {
6652
6898
  method: "GET",
6653
6899
  path: "/v1/artifacts/:id/versions",
6654
- pathParams: z18.object({
6655
- id: z18.string().min(1, "Artifact ID is required")
6900
+ pathParams: z19.object({
6901
+ id: z19.string().min(1, "Artifact ID is required")
6656
6902
  }),
6657
6903
  query: listQuerySchema,
6658
6904
  responses: {
@@ -6665,19 +6911,19 @@ var publicArtifactVersionsContract = c13.router({
6665
6911
  description: "List all versions of an artifact with pagination"
6666
6912
  }
6667
6913
  });
6668
- var publicArtifactDownloadContract = c13.router({
6914
+ var publicArtifactDownloadContract = c14.router({
6669
6915
  download: {
6670
6916
  method: "GET",
6671
6917
  path: "/v1/artifacts/:id/download",
6672
- pathParams: z18.object({
6673
- id: z18.string().min(1, "Artifact ID is required")
6918
+ pathParams: z19.object({
6919
+ id: z19.string().min(1, "Artifact ID is required")
6674
6920
  }),
6675
- query: z18.object({
6676
- version_id: z18.string().optional()
6921
+ query: z19.object({
6922
+ version_id: z19.string().optional()
6677
6923
  // Defaults to current version
6678
6924
  }),
6679
6925
  responses: {
6680
- 302: z18.undefined(),
6926
+ 302: z19.undefined(),
6681
6927
  // Redirect to presigned URL
6682
6928
  401: publicApiErrorSchema,
6683
6929
  404: publicApiErrorSchema,
@@ -6689,28 +6935,28 @@ var publicArtifactDownloadContract = c13.router({
6689
6935
  });
6690
6936
 
6691
6937
  // ../../packages/core/src/contracts/public/volumes.ts
6692
- import { z as z19 } from "zod";
6693
- var c14 = initContract();
6694
- var publicVolumeSchema = z19.object({
6695
- id: z19.string(),
6696
- name: z19.string(),
6697
- current_version_id: z19.string().nullable(),
6698
- size: z19.number(),
6938
+ import { z as z20 } from "zod";
6939
+ var c15 = initContract();
6940
+ var publicVolumeSchema = z20.object({
6941
+ id: z20.string(),
6942
+ name: z20.string(),
6943
+ current_version_id: z20.string().nullable(),
6944
+ size: z20.number(),
6699
6945
  // Total size in bytes
6700
- file_count: z19.number(),
6946
+ file_count: z20.number(),
6701
6947
  created_at: timestampSchema,
6702
6948
  updated_at: timestampSchema
6703
6949
  });
6704
- var volumeVersionSchema = z19.object({
6705
- id: z19.string(),
6950
+ var volumeVersionSchema = z20.object({
6951
+ id: z20.string(),
6706
6952
  // SHA-256 content hash
6707
- volume_id: z19.string(),
6708
- size: z19.number(),
6953
+ volume_id: z20.string(),
6954
+ size: z20.number(),
6709
6955
  // Size in bytes
6710
- file_count: z19.number(),
6711
- message: z19.string().nullable(),
6956
+ file_count: z20.number(),
6957
+ message: z20.string().nullable(),
6712
6958
  // Optional commit message
6713
- created_by: z19.string(),
6959
+ created_by: z20.string(),
6714
6960
  created_at: timestampSchema
6715
6961
  });
6716
6962
  var publicVolumeDetailSchema = publicVolumeSchema.extend({
@@ -6718,7 +6964,7 @@ var publicVolumeDetailSchema = publicVolumeSchema.extend({
6718
6964
  });
6719
6965
  var paginatedVolumesSchema = createPaginatedResponseSchema(publicVolumeSchema);
6720
6966
  var paginatedVolumeVersionsSchema = createPaginatedResponseSchema(volumeVersionSchema);
6721
- var publicVolumesListContract = c14.router({
6967
+ var publicVolumesListContract = c15.router({
6722
6968
  list: {
6723
6969
  method: "GET",
6724
6970
  path: "/v1/volumes",
@@ -6732,12 +6978,12 @@ var publicVolumesListContract = c14.router({
6732
6978
  description: "List all volumes in the current scope with pagination"
6733
6979
  }
6734
6980
  });
6735
- var publicVolumeByIdContract = c14.router({
6981
+ var publicVolumeByIdContract = c15.router({
6736
6982
  get: {
6737
6983
  method: "GET",
6738
6984
  path: "/v1/volumes/:id",
6739
- pathParams: z19.object({
6740
- id: z19.string().min(1, "Volume ID is required")
6985
+ pathParams: z20.object({
6986
+ id: z20.string().min(1, "Volume ID is required")
6741
6987
  }),
6742
6988
  responses: {
6743
6989
  200: publicVolumeDetailSchema,
@@ -6749,12 +6995,12 @@ var publicVolumeByIdContract = c14.router({
6749
6995
  description: "Get volume details by ID"
6750
6996
  }
6751
6997
  });
6752
- var publicVolumeVersionsContract = c14.router({
6998
+ var publicVolumeVersionsContract = c15.router({
6753
6999
  list: {
6754
7000
  method: "GET",
6755
7001
  path: "/v1/volumes/:id/versions",
6756
- pathParams: z19.object({
6757
- id: z19.string().min(1, "Volume ID is required")
7002
+ pathParams: z20.object({
7003
+ id: z20.string().min(1, "Volume ID is required")
6758
7004
  }),
6759
7005
  query: listQuerySchema,
6760
7006
  responses: {
@@ -6767,19 +7013,19 @@ var publicVolumeVersionsContract = c14.router({
6767
7013
  description: "List all versions of a volume with pagination"
6768
7014
  }
6769
7015
  });
6770
- var publicVolumeDownloadContract = c14.router({
7016
+ var publicVolumeDownloadContract = c15.router({
6771
7017
  download: {
6772
7018
  method: "GET",
6773
7019
  path: "/v1/volumes/:id/download",
6774
- pathParams: z19.object({
6775
- id: z19.string().min(1, "Volume ID is required")
7020
+ pathParams: z20.object({
7021
+ id: z20.string().min(1, "Volume ID is required")
6776
7022
  }),
6777
- query: z19.object({
6778
- version_id: z19.string().optional()
7023
+ query: z20.object({
7024
+ version_id: z20.string().optional()
6779
7025
  // Defaults to current version
6780
7026
  }),
6781
7027
  responses: {
6782
- 302: z19.undefined(),
7028
+ 302: z20.undefined(),
6783
7029
  // Redirect to presigned URL
6784
7030
  401: publicApiErrorSchema,
6785
7031
  404: publicApiErrorSchema,
@@ -9955,6 +10201,208 @@ function initProxyManager(config) {
9955
10201
  return globalProxyManager;
9956
10202
  }
9957
10203
 
10204
+ // src/lib/metrics/provider.ts
10205
+ import {
10206
+ MeterProvider,
10207
+ PeriodicExportingMetricReader
10208
+ } from "@opentelemetry/sdk-metrics";
10209
+ import { OTLPMetricExporter } from "@opentelemetry/exporter-metrics-otlp-proto";
10210
+ import { Resource } from "@opentelemetry/resources";
10211
+ import { ATTR_SERVICE_NAME } from "@opentelemetry/semantic-conventions";
10212
+ import { metrics } from "@opentelemetry/api";
10213
+ var meterProvider = null;
10214
+ var initialized = false;
10215
+ var enabled = false;
10216
+ var _runnerLabel = "";
10217
+ function initMetrics(config) {
10218
+ if (initialized) return;
10219
+ initialized = true;
10220
+ _runnerLabel = config.runnerLabel;
10221
+ if (!config.axiomToken) {
10222
+ console.log("[metrics] AXIOM_TOKEN not configured, metrics disabled");
10223
+ return;
10224
+ }
10225
+ const env = config.environment ?? "dev";
10226
+ const exporter = new OTLPMetricExporter({
10227
+ url: "https://api.axiom.co/v1/metrics",
10228
+ headers: {
10229
+ Authorization: `Bearer ${config.axiomToken}`,
10230
+ "X-Axiom-Dataset": `runner-metrics-${env}`
10231
+ }
10232
+ });
10233
+ meterProvider = new MeterProvider({
10234
+ resource: new Resource({
10235
+ [ATTR_SERVICE_NAME]: config.serviceName,
10236
+ "deployment.environment": env,
10237
+ "runner.label": config.runnerLabel
10238
+ }),
10239
+ readers: [
10240
+ new PeriodicExportingMetricReader({
10241
+ exporter,
10242
+ exportIntervalMillis: config.exportIntervalMs ?? 3e4
10243
+ })
10244
+ ]
10245
+ });
10246
+ metrics.setGlobalMeterProvider(meterProvider);
10247
+ enabled = true;
10248
+ console.log(
10249
+ `[metrics] initialized for ${config.serviceName} (${env}), runner: ${config.runnerLabel}`
10250
+ );
10251
+ }
10252
+ function isMetricsEnabled() {
10253
+ return enabled;
10254
+ }
10255
+ function getRunnerLabel() {
10256
+ return _runnerLabel;
10257
+ }
10258
+ function getMeter(name) {
10259
+ return metrics.getMeter(name);
10260
+ }
10261
+ async function flushMetrics() {
10262
+ if (meterProvider) {
10263
+ await meterProvider.forceFlush();
10264
+ }
10265
+ }
10266
+ async function shutdownMetrics() {
10267
+ if (meterProvider) {
10268
+ await meterProvider.shutdown();
10269
+ }
10270
+ }
10271
+
10272
+ // src/lib/metrics/instruments.ts
10273
+ var runnerOperationTotal = null;
10274
+ var runnerOperationErrorsTotal = null;
10275
+ var runnerOperationDuration = null;
10276
+ var sandboxOperationTotal = null;
10277
+ var sandboxOperationErrorsTotal = null;
10278
+ var sandboxOperationDuration = null;
10279
+ function getRunnerInstruments() {
10280
+ if (!runnerOperationTotal) {
10281
+ const meter = getMeter("vm0-runner");
10282
+ runnerOperationTotal = meter.createCounter("runner_operation_total", {
10283
+ description: "Total number of runner operations"
10284
+ });
10285
+ runnerOperationErrorsTotal = meter.createCounter(
10286
+ "runner_operation_errors_total",
10287
+ {
10288
+ description: "Total number of runner operation errors"
10289
+ }
10290
+ );
10291
+ runnerOperationDuration = meter.createHistogram(
10292
+ "runner_operation_duration_ms",
10293
+ {
10294
+ description: "Runner operation duration in milliseconds",
10295
+ unit: "ms"
10296
+ }
10297
+ );
10298
+ }
10299
+ return {
10300
+ runnerOperationTotal,
10301
+ runnerOperationErrorsTotal,
10302
+ runnerOperationDuration
10303
+ };
10304
+ }
10305
+ function getSandboxInstruments() {
10306
+ if (!sandboxOperationTotal) {
10307
+ const meter = getMeter("vm0-runner");
10308
+ sandboxOperationTotal = meter.createCounter("sandbox_operation_total", {
10309
+ description: "Total number of sandbox operations"
10310
+ });
10311
+ sandboxOperationErrorsTotal = meter.createCounter(
10312
+ "sandbox_operation_errors_total",
10313
+ {
10314
+ description: "Total number of sandbox operation errors"
10315
+ }
10316
+ );
10317
+ sandboxOperationDuration = meter.createHistogram(
10318
+ "sandbox_operation_duration_ms",
10319
+ {
10320
+ description: "Sandbox operation duration in milliseconds",
10321
+ unit: "ms"
10322
+ }
10323
+ );
10324
+ }
10325
+ return {
10326
+ sandboxOperationTotal,
10327
+ sandboxOperationErrorsTotal,
10328
+ sandboxOperationDuration
10329
+ };
10330
+ }
10331
+ function recordRunnerOperation(attrs) {
10332
+ if (!isMetricsEnabled()) return;
10333
+ const {
10334
+ runnerOperationTotal: runnerOperationTotal2,
10335
+ runnerOperationErrorsTotal: runnerOperationErrorsTotal2,
10336
+ runnerOperationDuration: runnerOperationDuration2
10337
+ } = getRunnerInstruments();
10338
+ const labels = {
10339
+ action_type: attrs.actionType,
10340
+ runner_label: getRunnerLabel()
10341
+ };
10342
+ runnerOperationTotal2.add(1, labels);
10343
+ if (!attrs.success) {
10344
+ runnerOperationErrorsTotal2.add(1, labels);
10345
+ }
10346
+ runnerOperationDuration2.record(attrs.durationMs, {
10347
+ ...labels,
10348
+ success: String(attrs.success)
10349
+ });
10350
+ }
10351
+ function recordSandboxOperation(attrs) {
10352
+ if (!isMetricsEnabled()) return;
10353
+ const {
10354
+ sandboxOperationTotal: sandboxOperationTotal2,
10355
+ sandboxOperationErrorsTotal: sandboxOperationErrorsTotal2,
10356
+ sandboxOperationDuration: sandboxOperationDuration2
10357
+ } = getSandboxInstruments();
10358
+ const labels = {
10359
+ sandbox_type: "runner",
10360
+ action_type: attrs.actionType
10361
+ };
10362
+ sandboxOperationTotal2.add(1, labels);
10363
+ if (!attrs.success) {
10364
+ sandboxOperationErrorsTotal2.add(1, labels);
10365
+ }
10366
+ sandboxOperationDuration2.record(attrs.durationMs, {
10367
+ ...labels,
10368
+ success: String(attrs.success)
10369
+ });
10370
+ }
10371
+
10372
+ // src/lib/metrics/timing.ts
10373
+ async function withRunnerTiming(actionType, fn) {
10374
+ const startTime = Date.now();
10375
+ let success = true;
10376
+ try {
10377
+ return await fn();
10378
+ } catch (error) {
10379
+ success = false;
10380
+ throw error;
10381
+ } finally {
10382
+ recordRunnerOperation({
10383
+ actionType,
10384
+ durationMs: Date.now() - startTime,
10385
+ success
10386
+ });
10387
+ }
10388
+ }
10389
+ async function withSandboxTiming(actionType, fn) {
10390
+ const startTime = Date.now();
10391
+ let success = true;
10392
+ try {
10393
+ return await fn();
10394
+ } catch (error) {
10395
+ success = false;
10396
+ throw error;
10397
+ } finally {
10398
+ recordSandboxOperation({
10399
+ actionType,
10400
+ durationMs: Date.now() - startTime,
10401
+ success
10402
+ });
10403
+ }
10404
+ }
10405
+
9958
10406
  // src/lib/executor.ts
9959
10407
  function getVmIdFromRunId(runId) {
9960
10408
  return runId.split("-")[0] || runId.substring(0, 8);
@@ -10142,11 +10590,12 @@ nameserver 1.1.1.1`;
10142
10590
  `sudo sh -c 'rm -f /etc/resolv.conf && echo "${dnsConfig}" > /etc/resolv.conf'`
10143
10591
  );
10144
10592
  }
10145
- async function executeJob(context, config) {
10593
+ async function executeJob(context, config, options = {}) {
10146
10594
  const vmId = getVmIdFromRunId(context.runId);
10147
10595
  let vm = null;
10148
10596
  let guestIp = null;
10149
- console.log(`[Executor] Starting job ${context.runId} in VM ${vmId}`);
10597
+ const log = options.logger ?? ((msg) => console.log(msg));
10598
+ log(`[Executor] Starting job ${context.runId} in VM ${vmId}`);
10150
10599
  try {
10151
10600
  const workspacesDir = path4.join(process.cwd(), "workspaces");
10152
10601
  const vmConfig = {
@@ -10158,24 +10607,27 @@ async function executeJob(context, config) {
10158
10607
  firecrackerBinary: config.firecracker.binary,
10159
10608
  workDir: path4.join(workspacesDir, `vm0-${vmId}`)
10160
10609
  };
10161
- console.log(`[Executor] Creating VM ${vmId}...`);
10610
+ log(`[Executor] Creating VM ${vmId}...`);
10162
10611
  vm = new FirecrackerVM(vmConfig);
10163
- await vm.start();
10612
+ await withSandboxTiming("vm_create", () => vm.start());
10164
10613
  guestIp = vm.getGuestIp();
10165
10614
  if (!guestIp) {
10166
10615
  throw new Error("VM started but no IP address available");
10167
10616
  }
10168
- console.log(`[Executor] VM ${vmId} started, guest IP: ${guestIp}`);
10617
+ log(`[Executor] VM ${vmId} started, guest IP: ${guestIp}`);
10169
10618
  const privateKeyPath = getRunnerSSHKeyPath();
10170
10619
  const ssh = createVMSSHClient(guestIp, "user", privateKeyPath || void 0);
10171
- console.log(`[Executor] Waiting for SSH on ${guestIp}...`);
10172
- await ssh.waitUntilReachable(12e4, 2e3);
10173
- console.log(`[Executor] SSH ready on ${guestIp}`);
10620
+ log(`[Executor] Waiting for SSH on ${guestIp}...`);
10621
+ await withSandboxTiming(
10622
+ "ssh_wait",
10623
+ () => ssh.waitUntilReachable(12e4, 2e3)
10624
+ );
10625
+ log(`[Executor] SSH ready on ${guestIp}`);
10174
10626
  const firewallConfig = context.experimentalFirewall;
10175
10627
  if (firewallConfig?.enabled) {
10176
10628
  const mitmEnabled = firewallConfig.experimental_mitm ?? false;
10177
10629
  const sealSecretsEnabled = firewallConfig.experimental_seal_secrets ?? false;
10178
- console.log(
10630
+ log(
10179
10631
  `[Executor] Setting up network security for VM ${guestIp} (mitm=${mitmEnabled}, sealSecrets=${sealSecretsEnabled})`
10180
10632
  );
10181
10633
  await setupVMProxyRules(guestIp, config.proxy.port);
@@ -10188,36 +10640,50 @@ async function executeJob(context, config) {
10188
10640
  await installProxyCA(ssh);
10189
10641
  }
10190
10642
  }
10191
- console.log(`[Executor] Configuring DNS...`);
10643
+ log(`[Executor] Configuring DNS...`);
10192
10644
  await configureDNS(ssh);
10193
- console.log(`[Executor] Uploading scripts...`);
10194
- await uploadScripts(ssh);
10195
- console.log(`[Executor] Scripts uploaded to ${SCRIPT_PATHS.baseDir}`);
10645
+ log(`[Executor] Uploading scripts...`);
10646
+ await withSandboxTiming("script_upload", () => uploadScripts(ssh));
10647
+ log(`[Executor] Scripts uploaded to ${SCRIPT_PATHS.baseDir}`);
10196
10648
  if (context.storageManifest) {
10197
- await downloadStorages(ssh, context.storageManifest);
10649
+ await withSandboxTiming(
10650
+ "storage_download",
10651
+ () => downloadStorages(ssh, context.storageManifest)
10652
+ );
10198
10653
  }
10199
10654
  if (context.resumeSession) {
10200
- await restoreSessionHistory(
10201
- ssh,
10202
- context.resumeSession,
10203
- context.workingDir,
10204
- context.cliAgentType || "claude-code"
10655
+ await withSandboxTiming(
10656
+ "session_restore",
10657
+ () => restoreSessionHistory(
10658
+ ssh,
10659
+ context.resumeSession,
10660
+ context.workingDir,
10661
+ context.cliAgentType || "claude-code"
10662
+ )
10205
10663
  );
10206
10664
  }
10207
10665
  const envVars = buildEnvironmentVariables(context, config.server.url);
10208
10666
  const envJson = JSON.stringify(envVars);
10209
- console.log(
10667
+ log(
10210
10668
  `[Executor] Writing env JSON (${envJson.length} bytes) to ${ENV_JSON_PATH}`
10211
10669
  );
10212
10670
  await ssh.writeFile(ENV_JSON_PATH, envJson);
10213
10671
  const systemLogFile = `/tmp/vm0-main-${context.runId}.log`;
10214
10672
  const exitCodeFile = `/tmp/vm0-exit-${context.runId}`;
10215
- console.log(`[Executor] Running agent via env-loader (background)...`);
10216
10673
  const startTime = Date.now();
10217
- await ssh.exec(
10218
- `nohup sh -c 'python3 -u ${ENV_LOADER_PATH}; echo $? > ${exitCodeFile}' > ${systemLogFile} 2>&1 &`
10219
- );
10220
- console.log(`[Executor] Agent started in background`);
10674
+ if (options.benchmarkMode) {
10675
+ log(`[Executor] Running command directly (benchmark mode)...`);
10676
+ await ssh.exec(
10677
+ `nohup sh -c '${context.prompt}; echo $? > ${exitCodeFile}' > ${systemLogFile} 2>&1 &`
10678
+ );
10679
+ log(`[Executor] Command started in background`);
10680
+ } else {
10681
+ log(`[Executor] Running agent via env-loader (background)...`);
10682
+ await ssh.exec(
10683
+ `nohup sh -c 'python3 -u ${ENV_LOADER_PATH}; echo $? > ${exitCodeFile}' > ${systemLogFile} 2>&1 &`
10684
+ );
10685
+ log(`[Executor] Agent started in background`);
10686
+ }
10221
10687
  const pollIntervalMs = 2e3;
10222
10688
  const maxWaitMs = 24 * 60 * 60 * 1e3;
10223
10689
  let exitCode = 1;
@@ -10226,25 +10692,35 @@ async function executeJob(context, config) {
10226
10692
  await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
10227
10693
  const checkResult = await ssh.exec(`cat ${exitCodeFile} 2>/dev/null`);
10228
10694
  if (checkResult.exitCode === 0 && checkResult.stdout.trim()) {
10229
- exitCode = parseInt(checkResult.stdout.trim(), 10) || 1;
10695
+ const parsed = parseInt(checkResult.stdout.trim(), 10);
10696
+ exitCode = Number.isNaN(parsed) ? 1 : parsed;
10230
10697
  completed = true;
10231
10698
  break;
10232
10699
  }
10233
10700
  }
10234
- const duration = Math.round((Date.now() - startTime) / 1e3);
10701
+ const durationMs = Date.now() - startTime;
10702
+ const duration = Math.round(durationMs / 1e3);
10235
10703
  if (!completed) {
10236
- console.log(`[Executor] Agent timed out after ${duration}s`);
10704
+ log(`[Executor] Agent timed out after ${duration}s`);
10705
+ recordRunnerOperation({
10706
+ actionType: "agent_execute",
10707
+ durationMs,
10708
+ success: false
10709
+ });
10237
10710
  return {
10238
10711
  exitCode: 1,
10239
10712
  error: `Agent execution timed out after ${duration}s`
10240
10713
  };
10241
10714
  }
10242
- console.log(
10243
- `[Executor] Agent finished in ${duration}s with exit code ${exitCode}`
10244
- );
10715
+ recordRunnerOperation({
10716
+ actionType: "agent_execute",
10717
+ durationMs,
10718
+ success: exitCode === 0
10719
+ });
10720
+ log(`[Executor] Agent finished in ${duration}s with exit code ${exitCode}`);
10245
10721
  const logResult = await ssh.exec(`tail -100 ${systemLogFile} 2>/dev/null`);
10246
10722
  if (logResult.stdout) {
10247
- console.log(
10723
+ log(
10248
10724
  `[Executor] Log output (${logResult.stdout.length} chars): ${logResult.stdout.substring(0, 500)}`
10249
10725
  );
10250
10726
  }
@@ -10261,7 +10737,7 @@ async function executeJob(context, config) {
10261
10737
  };
10262
10738
  } finally {
10263
10739
  if (context.experimentalFirewall?.enabled && guestIp) {
10264
- console.log(`[Executor] Cleaning up network security for VM ${guestIp}`);
10740
+ log(`[Executor] Cleaning up network security for VM ${guestIp}`);
10265
10741
  try {
10266
10742
  await removeVMProxyRules(guestIp, config.proxy.port);
10267
10743
  } catch (err) {
@@ -10270,21 +10746,23 @@ async function executeJob(context, config) {
10270
10746
  );
10271
10747
  }
10272
10748
  getVMRegistry().unregister(guestIp);
10273
- try {
10274
- await uploadNetworkLogs(
10275
- config.server.url,
10276
- context.sandboxToken,
10277
- context.runId
10278
- );
10279
- } catch (err) {
10280
- console.error(
10281
- `[Executor] Failed to upload network logs: ${err instanceof Error ? err.message : "Unknown error"}`
10282
- );
10749
+ if (!options.benchmarkMode) {
10750
+ try {
10751
+ await uploadNetworkLogs(
10752
+ config.server.url,
10753
+ context.sandboxToken,
10754
+ context.runId
10755
+ );
10756
+ } catch (err) {
10757
+ console.error(
10758
+ `[Executor] Failed to upload network logs: ${err instanceof Error ? err.message : "Unknown error"}`
10759
+ );
10760
+ }
10283
10761
  }
10284
10762
  }
10285
10763
  if (vm) {
10286
- console.log(`[Executor] Cleaning up VM ${vmId}...`);
10287
- await vm.kill();
10764
+ log(`[Executor] Cleaning up VM ${vmId}...`);
10765
+ await withSandboxTiming("cleanup", () => vm.kill());
10288
10766
  }
10289
10767
  }
10290
10768
  }
@@ -10331,6 +10809,18 @@ var startCommand = new Command("start").description("Start the runner").option("
10331
10809
  const config = loadConfig(options.config);
10332
10810
  validateFirecrackerPaths(config.firecracker);
10333
10811
  console.log("Config valid");
10812
+ const datasetSuffix = process.env.AXIOM_DATASET_SUFFIX;
10813
+ if (!datasetSuffix) {
10814
+ throw new Error(
10815
+ "AXIOM_DATASET_SUFFIX is required. Set to 'dev' or 'prod'."
10816
+ );
10817
+ }
10818
+ initMetrics({
10819
+ serviceName: "vm0-runner",
10820
+ runnerLabel: config.name,
10821
+ axiomToken: process.env.AXIOM_TOKEN,
10822
+ environment: datasetSuffix
10823
+ });
10334
10824
  const networkCheck = checkNetworkPrerequisites();
10335
10825
  if (!networkCheck.ok) {
10336
10826
  console.error("Network prerequisites not met:");
@@ -10419,7 +10909,10 @@ var startCommand = new Command("start").description("Start the runner").option("
10419
10909
  continue;
10420
10910
  }
10421
10911
  try {
10422
- const job = await pollForJob(config.server, config.group);
10912
+ const job = await withRunnerTiming(
10913
+ "poll",
10914
+ () => pollForJob(config.server, config.group)
10915
+ );
10423
10916
  if (!job) {
10424
10917
  await new Promise(
10425
10918
  (resolve) => setTimeout(resolve, config.sandbox.poll_interval_ms)
@@ -10428,7 +10921,10 @@ var startCommand = new Command("start").description("Start the runner").option("
10428
10921
  }
10429
10922
  console.log(`Found job: ${job.runId}`);
10430
10923
  try {
10431
- const context = await claimJob(config.server, job.runId);
10924
+ const context = await withRunnerTiming(
10925
+ "claim",
10926
+ () => claimJob(config.server, job.runId)
10927
+ );
10432
10928
  console.log(`Claimed job: ${context.runId}`);
10433
10929
  activeJobs.add(context.runId);
10434
10930
  updateStatus();
@@ -10467,6 +10963,9 @@ var startCommand = new Command("start").description("Start the runner").option("
10467
10963
  console.log("Stopping network proxy...");
10468
10964
  await getProxyManager().stop();
10469
10965
  }
10966
+ console.log("Flushing metrics...");
10967
+ await flushMetrics();
10968
+ await shutdownMetrics();
10470
10969
  state.mode = "stopped";
10471
10970
  updateStatus();
10472
10971
  console.log("Runner stopped");
@@ -10505,10 +11004,102 @@ var statusCommand = new Command2("status").description("Check runner connectivit
10505
11004
  }
10506
11005
  });
10507
11006
 
11007
+ // src/commands/benchmark.ts
11008
+ import { Command as Command3 } from "commander";
11009
+ import crypto from "crypto";
11010
+
11011
+ // src/lib/timing.ts
11012
+ var Timer = class {
11013
+ startTime;
11014
+ constructor() {
11015
+ this.startTime = Date.now();
11016
+ }
11017
+ /**
11018
+ * Get elapsed time formatted as [MM:SS.s]
11019
+ */
11020
+ elapsed() {
11021
+ const ms = Date.now() - this.startTime;
11022
+ const totalSeconds = ms / 1e3;
11023
+ const minutes = Math.floor(totalSeconds / 60);
11024
+ const seconds = (totalSeconds % 60).toFixed(1);
11025
+ return `[${String(minutes).padStart(2, "0")}:${seconds.padStart(4, "0")}]`;
11026
+ }
11027
+ /**
11028
+ * Log message with timestamp
11029
+ */
11030
+ log(message) {
11031
+ console.log(`${this.elapsed()} ${message}`);
11032
+ }
11033
+ /**
11034
+ * Get total elapsed time in seconds
11035
+ */
11036
+ totalSeconds() {
11037
+ return (Date.now() - this.startTime) / 1e3;
11038
+ }
11039
+ };
11040
+
11041
+ // src/commands/benchmark.ts
11042
+ function createBenchmarkContext(prompt, options) {
11043
+ return {
11044
+ runId: crypto.randomUUID(),
11045
+ prompt,
11046
+ agentComposeVersionId: "benchmark-local",
11047
+ vars: null,
11048
+ secretNames: null,
11049
+ checkpointId: null,
11050
+ sandboxToken: "benchmark-token-not-used",
11051
+ workingDir: options.workingDir,
11052
+ storageManifest: null,
11053
+ environment: null,
11054
+ resumeSession: null,
11055
+ secretValues: null,
11056
+ cliAgentType: options.agentType
11057
+ };
11058
+ }
11059
+ var benchmarkCommand = new Command3("benchmark").description(
11060
+ "Run a VM performance benchmark (executes bash command directly)"
11061
+ ).argument("<prompt>", "The bash command to execute in the VM").option("--config <path>", "Config file path", "./runner.yaml").option("--working-dir <path>", "Working directory in VM", "/home/user").option("--agent-type <type>", "Agent type", "claude-code").action(async (prompt, options) => {
11062
+ const timer = new Timer();
11063
+ try {
11064
+ timer.log("Loading configuration...");
11065
+ const config = loadDebugConfig(options.config);
11066
+ validateFirecrackerPaths(config.firecracker);
11067
+ timer.log("Checking network prerequisites...");
11068
+ const networkCheck = checkNetworkPrerequisites();
11069
+ if (!networkCheck.ok) {
11070
+ console.error("Network prerequisites not met:");
11071
+ for (const error of networkCheck.errors) {
11072
+ console.error(` - ${error}`);
11073
+ }
11074
+ process.exit(1);
11075
+ }
11076
+ timer.log("Setting up network bridge...");
11077
+ await setupBridge();
11078
+ timer.log(`Executing command: ${prompt}`);
11079
+ const context = createBenchmarkContext(prompt, options);
11080
+ const result = await executeJob(context, config, {
11081
+ benchmarkMode: true,
11082
+ logger: timer.log.bind(timer)
11083
+ });
11084
+ timer.log(`Exit code: ${result.exitCode}`);
11085
+ if (result.error) {
11086
+ timer.log(`Error: ${result.error}`);
11087
+ }
11088
+ timer.log(`Total time: ${timer.totalSeconds().toFixed(1)}s`);
11089
+ process.exit(result.exitCode);
11090
+ } catch (error) {
11091
+ timer.log(
11092
+ `Error: ${error instanceof Error ? error.message : "Unknown error"}`
11093
+ );
11094
+ process.exit(1);
11095
+ }
11096
+ });
11097
+
10508
11098
  // src/index.ts
10509
- var version = true ? "2.6.1" : "0.1.0";
11099
+ var version = true ? "2.7.1" : "0.1.0";
10510
11100
  program.name("vm0-runner").version(version).description("Self-hosted runner for VM0 agents");
10511
11101
  program.addCommand(startCommand);
10512
11102
  program.addCommand(statusCommand);
11103
+ program.addCommand(benchmarkCommand);
10513
11104
  program.parse();
10514
11105
  //# sourceMappingURL=index.js.map