@vm0/runner 2.11.1 → 2.13.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.
- package/index.js +1354 -401
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -5,8 +5,8 @@ import { program } from "commander";
|
|
|
5
5
|
|
|
6
6
|
// src/commands/start.ts
|
|
7
7
|
import { Command } from "commander";
|
|
8
|
-
import { writeFileSync } from "fs";
|
|
9
|
-
import { dirname, join } from "path";
|
|
8
|
+
import { writeFileSync as writeFileSync2 } from "fs";
|
|
9
|
+
import { dirname, join as join2 } from "path";
|
|
10
10
|
|
|
11
11
|
// src/lib/config.ts
|
|
12
12
|
import { z } from "zod";
|
|
@@ -187,12 +187,12 @@ async function completeJob(apiUrl, context, exitCode, error) {
|
|
|
187
187
|
}
|
|
188
188
|
|
|
189
189
|
// src/lib/executor.ts
|
|
190
|
-
import
|
|
190
|
+
import path5 from "path";
|
|
191
191
|
|
|
192
192
|
// src/lib/firecracker/vm.ts
|
|
193
193
|
import { execSync as execSync2, spawn } from "child_process";
|
|
194
|
-
import
|
|
195
|
-
import
|
|
194
|
+
import fs3 from "fs";
|
|
195
|
+
import path2 from "path";
|
|
196
196
|
import readline from "readline";
|
|
197
197
|
|
|
198
198
|
// src/lib/firecracker/client.ts
|
|
@@ -205,7 +205,7 @@ var FirecrackerClient = class {
|
|
|
205
205
|
/**
|
|
206
206
|
* Make HTTP request to Firecracker API
|
|
207
207
|
*/
|
|
208
|
-
async request(method,
|
|
208
|
+
async request(method, path7, body) {
|
|
209
209
|
return new Promise((resolve, reject) => {
|
|
210
210
|
const bodyStr = body !== void 0 ? JSON.stringify(body) : void 0;
|
|
211
211
|
const headers = {
|
|
@@ -218,11 +218,11 @@ var FirecrackerClient = class {
|
|
|
218
218
|
headers["Content-Length"] = Buffer.byteLength(bodyStr);
|
|
219
219
|
}
|
|
220
220
|
console.log(
|
|
221
|
-
`[FC API] ${method} ${
|
|
221
|
+
`[FC API] ${method} ${path7}${bodyStr ? ` (${Buffer.byteLength(bodyStr)} bytes)` : ""}`
|
|
222
222
|
);
|
|
223
223
|
const options = {
|
|
224
224
|
socketPath: this.socketPath,
|
|
225
|
-
path:
|
|
225
|
+
path: path7,
|
|
226
226
|
method,
|
|
227
227
|
headers,
|
|
228
228
|
// Disable agent to ensure fresh connection for each request
|
|
@@ -356,10 +356,214 @@ var FirecrackerClient = class {
|
|
|
356
356
|
};
|
|
357
357
|
|
|
358
358
|
// src/lib/firecracker/network.ts
|
|
359
|
-
import { execSync, exec } from "child_process";
|
|
359
|
+
import { execSync, exec as exec2 } from "child_process";
|
|
360
|
+
import { promisify as promisify2 } from "util";
|
|
361
|
+
|
|
362
|
+
// src/lib/firecracker/ip-pool.ts
|
|
363
|
+
import { exec } from "child_process";
|
|
360
364
|
import { promisify } from "util";
|
|
365
|
+
import * as fs2 from "fs";
|
|
366
|
+
import * as path from "path";
|
|
361
367
|
var execAsync = promisify(exec);
|
|
368
|
+
var VM0_RUN_DIR = "/var/run/vm0";
|
|
369
|
+
var REGISTRY_FILE_PATH = path.join(VM0_RUN_DIR, "ip-registry.json");
|
|
362
370
|
var BRIDGE_NAME = "vm0br0";
|
|
371
|
+
var IP_PREFIX = "172.16.0.";
|
|
372
|
+
var IP_START = 2;
|
|
373
|
+
var IP_END = 254;
|
|
374
|
+
var LOCK_TIMEOUT_MS = 1e4;
|
|
375
|
+
var LOCK_RETRY_INTERVAL_MS = 100;
|
|
376
|
+
var ALLOCATION_GRACE_PERIOD_MS = 3e4;
|
|
377
|
+
async function ensureRunDir() {
|
|
378
|
+
if (!fs2.existsSync(VM0_RUN_DIR)) {
|
|
379
|
+
await execAsync(`sudo mkdir -p ${VM0_RUN_DIR}`);
|
|
380
|
+
await execAsync(`sudo chmod 777 ${VM0_RUN_DIR}`);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
async function withLock(fn) {
|
|
384
|
+
await ensureRunDir();
|
|
385
|
+
const lockMarker = path.join(VM0_RUN_DIR, "ip-pool.lock.active");
|
|
386
|
+
const startTime = Date.now();
|
|
387
|
+
let lockAcquired = false;
|
|
388
|
+
while (Date.now() - startTime < LOCK_TIMEOUT_MS) {
|
|
389
|
+
try {
|
|
390
|
+
fs2.writeFileSync(lockMarker, process.pid.toString(), { flag: "wx" });
|
|
391
|
+
lockAcquired = true;
|
|
392
|
+
break;
|
|
393
|
+
} catch {
|
|
394
|
+
try {
|
|
395
|
+
const pidStr = fs2.readFileSync(lockMarker, "utf-8");
|
|
396
|
+
const pid = parseInt(pidStr, 10);
|
|
397
|
+
try {
|
|
398
|
+
process.kill(pid, 0);
|
|
399
|
+
} catch {
|
|
400
|
+
fs2.unlinkSync(lockMarker);
|
|
401
|
+
continue;
|
|
402
|
+
}
|
|
403
|
+
} catch {
|
|
404
|
+
}
|
|
405
|
+
await new Promise(
|
|
406
|
+
(resolve) => setTimeout(resolve, LOCK_RETRY_INTERVAL_MS)
|
|
407
|
+
);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
if (!lockAcquired) {
|
|
411
|
+
throw new Error(
|
|
412
|
+
`Failed to acquire IP pool lock after ${LOCK_TIMEOUT_MS}ms`
|
|
413
|
+
);
|
|
414
|
+
}
|
|
415
|
+
try {
|
|
416
|
+
return await fn();
|
|
417
|
+
} finally {
|
|
418
|
+
try {
|
|
419
|
+
fs2.unlinkSync(lockMarker);
|
|
420
|
+
} catch {
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
function readRegistry() {
|
|
425
|
+
try {
|
|
426
|
+
if (fs2.existsSync(REGISTRY_FILE_PATH)) {
|
|
427
|
+
const content = fs2.readFileSync(REGISTRY_FILE_PATH, "utf-8");
|
|
428
|
+
return JSON.parse(content);
|
|
429
|
+
}
|
|
430
|
+
} catch {
|
|
431
|
+
}
|
|
432
|
+
return { allocations: {} };
|
|
433
|
+
}
|
|
434
|
+
function writeRegistry(registry) {
|
|
435
|
+
fs2.writeFileSync(REGISTRY_FILE_PATH, JSON.stringify(registry, null, 2));
|
|
436
|
+
}
|
|
437
|
+
function getAllocations() {
|
|
438
|
+
const registry = readRegistry();
|
|
439
|
+
return new Map(Object.entries(registry.allocations));
|
|
440
|
+
}
|
|
441
|
+
function getIPForVm(vmId) {
|
|
442
|
+
const registry = readRegistry();
|
|
443
|
+
for (const [ip, allocation] of Object.entries(registry.allocations)) {
|
|
444
|
+
if (allocation.vmId === vmId) {
|
|
445
|
+
return ip;
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
return void 0;
|
|
449
|
+
}
|
|
450
|
+
async function scanTapDevices() {
|
|
451
|
+
const tapDevices = /* @__PURE__ */ new Map();
|
|
452
|
+
try {
|
|
453
|
+
const { stdout } = await execAsync(
|
|
454
|
+
`ip link show master ${BRIDGE_NAME} 2>/dev/null || true`
|
|
455
|
+
);
|
|
456
|
+
const lines = stdout.split("\n");
|
|
457
|
+
for (const line of lines) {
|
|
458
|
+
const match = line.match(/^\d+:\s+(tap[a-f0-9]+):/);
|
|
459
|
+
if (match && match[1]) {
|
|
460
|
+
const tapName = match[1];
|
|
461
|
+
const vmIdPrefix = tapName.substring(3);
|
|
462
|
+
tapDevices.set(tapName, vmIdPrefix);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
} catch {
|
|
466
|
+
}
|
|
467
|
+
return tapDevices;
|
|
468
|
+
}
|
|
469
|
+
function reconcileRegistry(registry, activeTaps) {
|
|
470
|
+
const reconciled = { allocations: {} };
|
|
471
|
+
const activeTapNames = new Set(activeTaps.keys());
|
|
472
|
+
const now = Date.now();
|
|
473
|
+
for (const [ip, allocation] of Object.entries(registry.allocations)) {
|
|
474
|
+
const allocatedTime = new Date(allocation.allocatedAt).getTime();
|
|
475
|
+
const isWithinGracePeriod = now - allocatedTime < ALLOCATION_GRACE_PERIOD_MS;
|
|
476
|
+
if (activeTapNames.has(allocation.tapDevice)) {
|
|
477
|
+
reconciled.allocations[ip] = allocation;
|
|
478
|
+
} else if (isWithinGracePeriod) {
|
|
479
|
+
reconciled.allocations[ip] = allocation;
|
|
480
|
+
} else {
|
|
481
|
+
console.log(
|
|
482
|
+
`[IP Pool] Removing stale allocation for ${ip} (TAP ${allocation.tapDevice} no longer exists)`
|
|
483
|
+
);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
return reconciled;
|
|
487
|
+
}
|
|
488
|
+
function findFreeIP(registry) {
|
|
489
|
+
const allocatedIPs = new Set(Object.keys(registry.allocations));
|
|
490
|
+
for (let octet = IP_START; octet <= IP_END; octet++) {
|
|
491
|
+
const ip = `${IP_PREFIX}${octet}`;
|
|
492
|
+
if (!allocatedIPs.has(ip)) {
|
|
493
|
+
return ip;
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
return null;
|
|
497
|
+
}
|
|
498
|
+
async function allocateIP(vmId) {
|
|
499
|
+
const tapDevice = `tap${vmId.substring(0, 8)}`;
|
|
500
|
+
return withLock(async () => {
|
|
501
|
+
const registry = readRegistry();
|
|
502
|
+
const ip = findFreeIP(registry);
|
|
503
|
+
if (!ip) {
|
|
504
|
+
throw new Error(
|
|
505
|
+
"No free IP addresses available in pool (172.16.0.2-254)"
|
|
506
|
+
);
|
|
507
|
+
}
|
|
508
|
+
const allocatedCount = Object.keys(registry.allocations).length;
|
|
509
|
+
const allocatedIPs = Object.keys(registry.allocations).sort();
|
|
510
|
+
console.log(
|
|
511
|
+
`[IP Pool] Current state: ${allocatedCount} IPs allocated [${allocatedIPs.join(", ")}], assigning ${ip}`
|
|
512
|
+
);
|
|
513
|
+
registry.allocations[ip] = {
|
|
514
|
+
vmId,
|
|
515
|
+
tapDevice,
|
|
516
|
+
allocatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
517
|
+
};
|
|
518
|
+
writeRegistry(registry);
|
|
519
|
+
console.log(`[IP Pool] Allocated ${ip} for VM ${vmId} (TAP ${tapDevice})`);
|
|
520
|
+
return ip;
|
|
521
|
+
});
|
|
522
|
+
}
|
|
523
|
+
async function releaseIP(ip) {
|
|
524
|
+
return withLock(async () => {
|
|
525
|
+
const registry = readRegistry();
|
|
526
|
+
if (registry.allocations[ip]) {
|
|
527
|
+
const allocation = registry.allocations[ip];
|
|
528
|
+
delete registry.allocations[ip];
|
|
529
|
+
writeRegistry(registry);
|
|
530
|
+
console.log(
|
|
531
|
+
`[IP Pool] Released ${ip} (was allocated to VM ${allocation.vmId})`
|
|
532
|
+
);
|
|
533
|
+
} else {
|
|
534
|
+
console.log(`[IP Pool] IP ${ip} was not in registry, nothing to release`);
|
|
535
|
+
}
|
|
536
|
+
});
|
|
537
|
+
}
|
|
538
|
+
async function cleanupOrphanedAllocations() {
|
|
539
|
+
return withLock(async () => {
|
|
540
|
+
console.log("[IP Pool] Cleaning up orphaned allocations...");
|
|
541
|
+
const registry = readRegistry();
|
|
542
|
+
const beforeCount = Object.keys(registry.allocations).length;
|
|
543
|
+
if (beforeCount === 0) {
|
|
544
|
+
console.log("[IP Pool] No allocations in registry, nothing to clean up");
|
|
545
|
+
return;
|
|
546
|
+
}
|
|
547
|
+
const activeTaps = await scanTapDevices();
|
|
548
|
+
console.log(
|
|
549
|
+
`[IP Pool] Found ${activeTaps.size} active TAP device(s) on bridge`
|
|
550
|
+
);
|
|
551
|
+
const reconciled = reconcileRegistry(registry, activeTaps);
|
|
552
|
+
const afterCount = Object.keys(reconciled.allocations).length;
|
|
553
|
+
if (afterCount !== beforeCount) {
|
|
554
|
+
writeRegistry(reconciled);
|
|
555
|
+
console.log(
|
|
556
|
+
`[IP Pool] Cleaned up ${beforeCount - afterCount} orphaned allocation(s)`
|
|
557
|
+
);
|
|
558
|
+
} else {
|
|
559
|
+
console.log("[IP Pool] No orphaned allocations found");
|
|
560
|
+
}
|
|
561
|
+
});
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
// src/lib/firecracker/network.ts
|
|
565
|
+
var execAsync2 = promisify2(exec2);
|
|
566
|
+
var BRIDGE_NAME2 = "vm0br0";
|
|
363
567
|
var BRIDGE_IP = "172.16.0.1";
|
|
364
568
|
var BRIDGE_NETMASK = "255.255.255.0";
|
|
365
569
|
var BRIDGE_CIDR = "172.16.0.0/24";
|
|
@@ -379,11 +583,6 @@ function generateMacAddress(vmId) {
|
|
|
379
583
|
const b3 = hash & 255;
|
|
380
584
|
return `02:00:00:${b1.toString(16).padStart(2, "0")}:${b2.toString(16).padStart(2, "0")}:${b3.toString(16).padStart(2, "0")}`;
|
|
381
585
|
}
|
|
382
|
-
function generateGuestIp(vmId) {
|
|
383
|
-
const hash = hashString(vmId);
|
|
384
|
-
const lastOctet = hash % 253 + 2;
|
|
385
|
-
return `172.16.0.${lastOctet}`;
|
|
386
|
-
}
|
|
387
586
|
function commandExists(cmd) {
|
|
388
587
|
try {
|
|
389
588
|
execSync(`which ${cmd}`, { stdio: "ignore" });
|
|
@@ -395,7 +594,7 @@ function commandExists(cmd) {
|
|
|
395
594
|
async function execCommand(cmd, sudo = true) {
|
|
396
595
|
const fullCmd = sudo ? `sudo ${cmd}` : cmd;
|
|
397
596
|
try {
|
|
398
|
-
const { stdout } = await
|
|
597
|
+
const { stdout } = await execAsync2(fullCmd);
|
|
399
598
|
return stdout.trim();
|
|
400
599
|
} catch (error) {
|
|
401
600
|
const execError = error;
|
|
@@ -415,33 +614,33 @@ async function getDefaultInterface() {
|
|
|
415
614
|
}
|
|
416
615
|
async function setupForwardRules() {
|
|
417
616
|
const extIface = await getDefaultInterface();
|
|
418
|
-
console.log(`Setting up FORWARD rules for ${
|
|
617
|
+
console.log(`Setting up FORWARD rules for ${BRIDGE_NAME2} <-> ${extIface}`);
|
|
419
618
|
try {
|
|
420
619
|
await execCommand(
|
|
421
|
-
`iptables -C FORWARD -i ${
|
|
620
|
+
`iptables -C FORWARD -i ${BRIDGE_NAME2} -o ${extIface} -j ACCEPT`
|
|
422
621
|
);
|
|
423
622
|
console.log("FORWARD outbound rule already exists");
|
|
424
623
|
} catch {
|
|
425
624
|
await execCommand(
|
|
426
|
-
`iptables -I FORWARD -i ${
|
|
625
|
+
`iptables -I FORWARD -i ${BRIDGE_NAME2} -o ${extIface} -j ACCEPT`
|
|
427
626
|
);
|
|
428
627
|
console.log("FORWARD outbound rule added");
|
|
429
628
|
}
|
|
430
629
|
try {
|
|
431
630
|
await execCommand(
|
|
432
|
-
`iptables -C FORWARD -i ${extIface} -o ${
|
|
631
|
+
`iptables -C FORWARD -i ${extIface} -o ${BRIDGE_NAME2} -m state --state RELATED,ESTABLISHED -j ACCEPT`
|
|
433
632
|
);
|
|
434
633
|
console.log("FORWARD inbound rule already exists");
|
|
435
634
|
} catch {
|
|
436
635
|
await execCommand(
|
|
437
|
-
`iptables -I FORWARD -i ${extIface} -o ${
|
|
636
|
+
`iptables -I FORWARD -i ${extIface} -o ${BRIDGE_NAME2} -m state --state RELATED,ESTABLISHED -j ACCEPT`
|
|
438
637
|
);
|
|
439
638
|
console.log("FORWARD inbound rule added");
|
|
440
639
|
}
|
|
441
640
|
}
|
|
442
641
|
async function bridgeExists() {
|
|
443
642
|
try {
|
|
444
|
-
await execCommand(`ip link show ${
|
|
643
|
+
await execCommand(`ip link show ${BRIDGE_NAME2}`, true);
|
|
445
644
|
return true;
|
|
446
645
|
} catch {
|
|
447
646
|
return false;
|
|
@@ -449,16 +648,16 @@ async function bridgeExists() {
|
|
|
449
648
|
}
|
|
450
649
|
async function setupBridge() {
|
|
451
650
|
if (await bridgeExists()) {
|
|
452
|
-
console.log(`Bridge ${
|
|
651
|
+
console.log(`Bridge ${BRIDGE_NAME2} already exists`);
|
|
453
652
|
await setupForwardRules();
|
|
454
653
|
return;
|
|
455
654
|
}
|
|
456
|
-
console.log(`Creating bridge ${
|
|
457
|
-
await execCommand(`ip link add name ${
|
|
655
|
+
console.log(`Creating bridge ${BRIDGE_NAME2}...`);
|
|
656
|
+
await execCommand(`ip link add name ${BRIDGE_NAME2} type bridge`);
|
|
458
657
|
await execCommand(
|
|
459
|
-
`ip addr add ${BRIDGE_IP}/${BRIDGE_NETMASK} dev ${
|
|
658
|
+
`ip addr add ${BRIDGE_IP}/${BRIDGE_NETMASK} dev ${BRIDGE_NAME2}`
|
|
460
659
|
);
|
|
461
|
-
await execCommand(`ip link set ${
|
|
660
|
+
await execCommand(`ip link set ${BRIDGE_NAME2} up`);
|
|
462
661
|
await execCommand(`sysctl -w net.ipv4.ip_forward=1`);
|
|
463
662
|
try {
|
|
464
663
|
await execCommand(
|
|
@@ -472,7 +671,7 @@ async function setupBridge() {
|
|
|
472
671
|
console.log("NAT rule added");
|
|
473
672
|
}
|
|
474
673
|
await setupForwardRules();
|
|
475
|
-
console.log(`Bridge ${
|
|
674
|
+
console.log(`Bridge ${BRIDGE_NAME2} configured with IP ${BRIDGE_IP}`);
|
|
476
675
|
}
|
|
477
676
|
async function tapDeviceExists(tapDevice) {
|
|
478
677
|
try {
|
|
@@ -482,10 +681,34 @@ async function tapDeviceExists(tapDevice) {
|
|
|
482
681
|
return false;
|
|
483
682
|
}
|
|
484
683
|
}
|
|
684
|
+
async function clearStaleIptablesRulesForIP(ip) {
|
|
685
|
+
try {
|
|
686
|
+
const { stdout } = await execAsync2(
|
|
687
|
+
"sudo iptables -t nat -S PREROUTING 2>/dev/null || true"
|
|
688
|
+
);
|
|
689
|
+
const lines = stdout.split("\n");
|
|
690
|
+
const rulesForIP = lines.filter((line) => line.includes(`-s ${ip}`));
|
|
691
|
+
if (rulesForIP.length === 0) {
|
|
692
|
+
return;
|
|
693
|
+
}
|
|
694
|
+
console.log(
|
|
695
|
+
`Clearing ${rulesForIP.length} stale iptables rule(s) for IP ${ip}`
|
|
696
|
+
);
|
|
697
|
+
for (const rule of rulesForIP) {
|
|
698
|
+
const deleteRule = rule.replace("-A ", "-D ");
|
|
699
|
+
try {
|
|
700
|
+
await execCommand(`iptables -t nat ${deleteRule}`);
|
|
701
|
+
} catch {
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
} catch {
|
|
705
|
+
}
|
|
706
|
+
}
|
|
485
707
|
async function createTapDevice(vmId) {
|
|
486
708
|
const tapDevice = `tap${vmId.substring(0, 8)}`;
|
|
487
709
|
const guestMac = generateMacAddress(vmId);
|
|
488
|
-
const guestIp =
|
|
710
|
+
const guestIp = await allocateIP(vmId);
|
|
711
|
+
await clearStaleIptablesRulesForIP(guestIp);
|
|
489
712
|
console.log(`Creating TAP device ${tapDevice} for VM ${vmId}...`);
|
|
490
713
|
await setupBridge();
|
|
491
714
|
if (await tapDeviceExists(tapDevice)) {
|
|
@@ -493,7 +716,7 @@ async function createTapDevice(vmId) {
|
|
|
493
716
|
await deleteTapDevice(tapDevice);
|
|
494
717
|
}
|
|
495
718
|
await execCommand(`ip tuntap add ${tapDevice} mode tap`);
|
|
496
|
-
await execCommand(`ip link set ${tapDevice} master ${
|
|
719
|
+
await execCommand(`ip link set ${tapDevice} master ${BRIDGE_NAME2}`);
|
|
497
720
|
await execCommand(`ip link set ${tapDevice} up`);
|
|
498
721
|
console.log(
|
|
499
722
|
`TAP ${tapDevice} created: guest MAC ${guestMac}, guest IP ${guestIp}`
|
|
@@ -506,13 +729,23 @@ async function createTapDevice(vmId) {
|
|
|
506
729
|
netmask: BRIDGE_NETMASK
|
|
507
730
|
};
|
|
508
731
|
}
|
|
509
|
-
async function deleteTapDevice(tapDevice) {
|
|
732
|
+
async function deleteTapDevice(tapDevice, guestIp) {
|
|
510
733
|
if (!await tapDeviceExists(tapDevice)) {
|
|
511
734
|
console.log(`TAP device ${tapDevice} does not exist, skipping delete`);
|
|
512
|
-
|
|
735
|
+
} else {
|
|
736
|
+
await execCommand(`ip link delete ${tapDevice}`);
|
|
737
|
+
console.log(`TAP device ${tapDevice} deleted`);
|
|
738
|
+
}
|
|
739
|
+
if (guestIp) {
|
|
740
|
+
try {
|
|
741
|
+
await execCommand(`ip neigh del ${guestIp} dev ${BRIDGE_NAME2}`, true);
|
|
742
|
+
console.log(`ARP entry cleared for ${guestIp}`);
|
|
743
|
+
} catch {
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
if (guestIp) {
|
|
747
|
+
await releaseIP(guestIp);
|
|
513
748
|
}
|
|
514
|
-
await execCommand(`ip link delete ${tapDevice}`);
|
|
515
|
-
console.log(`TAP device ${tapDevice} deleted`);
|
|
516
749
|
}
|
|
517
750
|
function generateNetworkBootArgs(config) {
|
|
518
751
|
return `ip=${config.guestIp}::${config.gatewayIp}:${config.netmask}:vm0-guest:eth0:off`;
|
|
@@ -537,52 +770,191 @@ function checkNetworkPrerequisites() {
|
|
|
537
770
|
errors
|
|
538
771
|
};
|
|
539
772
|
}
|
|
540
|
-
async function setupVMProxyRules(vmIp, proxyPort) {
|
|
773
|
+
async function setupVMProxyRules(vmIp, proxyPort, runnerName) {
|
|
774
|
+
const comment = `vm0:runner:${runnerName}`;
|
|
541
775
|
console.log(
|
|
542
|
-
`Setting up proxy rules for VM ${vmIp} -> localhost:${proxyPort}`
|
|
776
|
+
`Setting up proxy rules for VM ${vmIp} -> localhost:${proxyPort} (comment: ${comment})`
|
|
543
777
|
);
|
|
544
778
|
try {
|
|
545
779
|
await execCommand(
|
|
546
|
-
`iptables -t nat -C PREROUTING -s ${vmIp} -p tcp --dport 80 -j REDIRECT --to-port ${proxyPort}`
|
|
780
|
+
`iptables -t nat -C PREROUTING -s ${vmIp} -p tcp --dport 80 -j REDIRECT --to-port ${proxyPort} -m comment --comment "${comment}"`
|
|
547
781
|
);
|
|
548
782
|
console.log(`Proxy rule for ${vmIp}:80 already exists`);
|
|
549
783
|
} catch {
|
|
550
784
|
await execCommand(
|
|
551
|
-
`iptables -t nat -A PREROUTING -s ${vmIp} -p tcp --dport 80 -j REDIRECT --to-port ${proxyPort}`
|
|
785
|
+
`iptables -t nat -A PREROUTING -s ${vmIp} -p tcp --dport 80 -j REDIRECT --to-port ${proxyPort} -m comment --comment "${comment}"`
|
|
552
786
|
);
|
|
553
787
|
console.log(`Proxy rule for ${vmIp}:80 added`);
|
|
554
788
|
}
|
|
555
789
|
try {
|
|
556
790
|
await execCommand(
|
|
557
|
-
`iptables -t nat -C PREROUTING -s ${vmIp} -p tcp --dport 443 -j REDIRECT --to-port ${proxyPort}`
|
|
791
|
+
`iptables -t nat -C PREROUTING -s ${vmIp} -p tcp --dport 443 -j REDIRECT --to-port ${proxyPort} -m comment --comment "${comment}"`
|
|
558
792
|
);
|
|
559
793
|
console.log(`Proxy rule for ${vmIp}:443 already exists`);
|
|
560
794
|
} catch {
|
|
561
795
|
await execCommand(
|
|
562
|
-
`iptables -t nat -A PREROUTING -s ${vmIp} -p tcp --dport 443 -j REDIRECT --to-port ${proxyPort}`
|
|
796
|
+
`iptables -t nat -A PREROUTING -s ${vmIp} -p tcp --dport 443 -j REDIRECT --to-port ${proxyPort} -m comment --comment "${comment}"`
|
|
563
797
|
);
|
|
564
798
|
console.log(`Proxy rule for ${vmIp}:443 added`);
|
|
565
799
|
}
|
|
566
800
|
console.log(`Proxy rules configured for VM ${vmIp}`);
|
|
567
801
|
}
|
|
568
|
-
async function removeVMProxyRules(vmIp, proxyPort) {
|
|
802
|
+
async function removeVMProxyRules(vmIp, proxyPort, runnerName) {
|
|
803
|
+
const comment = `vm0:runner:${runnerName}`;
|
|
569
804
|
console.log(`Removing proxy rules for VM ${vmIp}...`);
|
|
570
805
|
try {
|
|
571
806
|
await execCommand(
|
|
572
|
-
`iptables -t nat -D PREROUTING -s ${vmIp} -p tcp --dport 80 -j REDIRECT --to-port ${proxyPort}`
|
|
807
|
+
`iptables -t nat -D PREROUTING -s ${vmIp} -p tcp --dport 80 -j REDIRECT --to-port ${proxyPort} -m comment --comment "${comment}"`
|
|
573
808
|
);
|
|
574
809
|
console.log(`Proxy rule for ${vmIp}:80 removed`);
|
|
575
810
|
} catch {
|
|
576
811
|
}
|
|
577
812
|
try {
|
|
578
813
|
await execCommand(
|
|
579
|
-
`iptables -t nat -D PREROUTING -s ${vmIp} -p tcp --dport 443 -j REDIRECT --to-port ${proxyPort}`
|
|
814
|
+
`iptables -t nat -D PREROUTING -s ${vmIp} -p tcp --dport 443 -j REDIRECT --to-port ${proxyPort} -m comment --comment "${comment}"`
|
|
580
815
|
);
|
|
581
816
|
console.log(`Proxy rule for ${vmIp}:443 removed`);
|
|
582
817
|
} catch {
|
|
583
818
|
}
|
|
584
819
|
console.log(`Proxy rules cleanup complete for VM ${vmIp}`);
|
|
585
820
|
}
|
|
821
|
+
async function listTapDevices() {
|
|
822
|
+
try {
|
|
823
|
+
const result = await execCommand("ip -o link show type tuntap", false);
|
|
824
|
+
const devices = [];
|
|
825
|
+
const lines = result.split("\n");
|
|
826
|
+
for (const line of lines) {
|
|
827
|
+
const match = line.match(/^\d+:\s+(tap[a-f0-9]{8}):/);
|
|
828
|
+
if (match && match[1]) {
|
|
829
|
+
devices.push(match[1]);
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
return devices;
|
|
833
|
+
} catch {
|
|
834
|
+
return [];
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
async function checkBridgeStatus() {
|
|
838
|
+
try {
|
|
839
|
+
const result = await execCommand(`ip -o addr show ${BRIDGE_NAME2}`, false);
|
|
840
|
+
const ipMatch = result.match(/inet\s+(\d+\.\d+\.\d+\.\d+)/);
|
|
841
|
+
const upMatch = result.includes("UP") || result.includes("state UP");
|
|
842
|
+
return {
|
|
843
|
+
exists: true,
|
|
844
|
+
ip: ipMatch?.[1] ?? BRIDGE_IP,
|
|
845
|
+
up: upMatch
|
|
846
|
+
};
|
|
847
|
+
} catch {
|
|
848
|
+
return { exists: false };
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
async function isPortInUse(port) {
|
|
852
|
+
try {
|
|
853
|
+
await execCommand(`ss -tln | grep -q ":${port} "`, false);
|
|
854
|
+
return true;
|
|
855
|
+
} catch {
|
|
856
|
+
return false;
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
async function listIptablesNatRules() {
|
|
860
|
+
try {
|
|
861
|
+
const result = await execCommand("iptables -t nat -L PREROUTING -n", true);
|
|
862
|
+
const rules = [];
|
|
863
|
+
const lines = result.split("\n");
|
|
864
|
+
for (const line of lines) {
|
|
865
|
+
const match = line.match(
|
|
866
|
+
/REDIRECT\s+tcp\s+--\s+(172\.16\.0\.\d+)\s+\S+\s+tcp\s+dpt:(\d+)\s+redir\s+ports\s+(\d+)/
|
|
867
|
+
);
|
|
868
|
+
if (match && match[1] && match[2] && match[3]) {
|
|
869
|
+
rules.push({
|
|
870
|
+
sourceIp: match[1],
|
|
871
|
+
destPort: parseInt(match[2], 10),
|
|
872
|
+
redirectPort: parseInt(match[3], 10)
|
|
873
|
+
});
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
return rules;
|
|
877
|
+
} catch {
|
|
878
|
+
return [];
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
async function findOrphanedIptablesRules(rules, activeVmIps, expectedProxyPort) {
|
|
882
|
+
const orphaned = [];
|
|
883
|
+
for (const rule of rules) {
|
|
884
|
+
const isActiveVm = activeVmIps.has(rule.sourceIp);
|
|
885
|
+
const correctPort = rule.redirectPort === expectedProxyPort;
|
|
886
|
+
if (!isActiveVm || !correctPort) {
|
|
887
|
+
const portListening = await isPortInUse(rule.redirectPort);
|
|
888
|
+
if (!portListening) {
|
|
889
|
+
orphaned.push(rule);
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
return orphaned;
|
|
894
|
+
}
|
|
895
|
+
async function flushBridgeArpCache() {
|
|
896
|
+
console.log(`Flushing ARP cache on bridge ${BRIDGE_NAME2}...`);
|
|
897
|
+
try {
|
|
898
|
+
if (!await bridgeExists()) {
|
|
899
|
+
console.log("Bridge does not exist, skipping ARP flush");
|
|
900
|
+
return;
|
|
901
|
+
}
|
|
902
|
+
const { stdout } = await execAsync2(
|
|
903
|
+
`ip neigh show dev ${BRIDGE_NAME2} 2>/dev/null || true`
|
|
904
|
+
);
|
|
905
|
+
if (!stdout.trim()) {
|
|
906
|
+
console.log("No ARP entries on bridge");
|
|
907
|
+
return;
|
|
908
|
+
}
|
|
909
|
+
const lines = stdout.split("\n").filter((line) => line.trim());
|
|
910
|
+
let cleared = 0;
|
|
911
|
+
for (const line of lines) {
|
|
912
|
+
const match = line.match(/^(\d+\.\d+\.\d+\.\d+)\s/);
|
|
913
|
+
if (match && match[1]) {
|
|
914
|
+
const ip = match[1];
|
|
915
|
+
try {
|
|
916
|
+
await execCommand(`ip neigh del ${ip} dev ${BRIDGE_NAME2}`, true);
|
|
917
|
+
cleared++;
|
|
918
|
+
} catch {
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
console.log(`Cleared ${cleared} ARP entries from bridge`);
|
|
923
|
+
} catch (error) {
|
|
924
|
+
console.log(
|
|
925
|
+
`Warning: Could not flush ARP cache: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
926
|
+
);
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
async function cleanupOrphanedProxyRules(runnerName) {
|
|
930
|
+
const comment = `vm0:runner:${runnerName}`;
|
|
931
|
+
console.log(`Cleaning up orphaned proxy rules for runner '${runnerName}'...`);
|
|
932
|
+
try {
|
|
933
|
+
const rules = await execCommand("iptables -t nat -S PREROUTING", false);
|
|
934
|
+
const ourRules = rules.split("\n").filter((rule) => rule.includes(comment));
|
|
935
|
+
if (ourRules.length === 0) {
|
|
936
|
+
console.log("No orphaned proxy rules found");
|
|
937
|
+
return;
|
|
938
|
+
}
|
|
939
|
+
console.log(`Found ${ourRules.length} orphaned rule(s) to clean up`);
|
|
940
|
+
for (const rule of ourRules) {
|
|
941
|
+
const deleteRule = rule.replace("-A ", "-D ");
|
|
942
|
+
try {
|
|
943
|
+
await execCommand(`iptables -t nat ${deleteRule}`);
|
|
944
|
+
console.log(`Deleted orphaned rule: ${rule.substring(0, 80)}...`);
|
|
945
|
+
} catch {
|
|
946
|
+
console.log(
|
|
947
|
+
`Failed to delete rule (may already be gone): ${rule.substring(0, 80)}...`
|
|
948
|
+
);
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
console.log("Orphaned proxy rules cleanup complete");
|
|
952
|
+
} catch (error) {
|
|
953
|
+
console.log(
|
|
954
|
+
`Warning: Could not clean up orphaned rules: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
955
|
+
);
|
|
956
|
+
}
|
|
957
|
+
}
|
|
586
958
|
|
|
587
959
|
// src/lib/firecracker/vm.ts
|
|
588
960
|
var FirecrackerVM = class {
|
|
@@ -598,8 +970,8 @@ var FirecrackerVM = class {
|
|
|
598
970
|
constructor(config) {
|
|
599
971
|
this.config = config;
|
|
600
972
|
this.workDir = config.workDir || `/tmp/vm0-vm-${config.vmId}`;
|
|
601
|
-
this.socketPath =
|
|
602
|
-
this.vmOverlayPath =
|
|
973
|
+
this.socketPath = path2.join(this.workDir, "firecracker.sock");
|
|
974
|
+
this.vmOverlayPath = path2.join(this.workDir, "overlay.ext4");
|
|
603
975
|
}
|
|
604
976
|
/**
|
|
605
977
|
* Get current VM state
|
|
@@ -634,15 +1006,15 @@ var FirecrackerVM = class {
|
|
|
634
1006
|
throw new Error(`Cannot start VM in state: ${this.state}`);
|
|
635
1007
|
}
|
|
636
1008
|
try {
|
|
637
|
-
|
|
638
|
-
if (
|
|
639
|
-
|
|
1009
|
+
fs3.mkdirSync(this.workDir, { recursive: true });
|
|
1010
|
+
if (fs3.existsSync(this.socketPath)) {
|
|
1011
|
+
fs3.unlinkSync(this.socketPath);
|
|
640
1012
|
}
|
|
641
1013
|
console.log(`[VM ${this.config.vmId}] Creating sparse overlay file...`);
|
|
642
1014
|
const overlaySize = 2 * 1024 * 1024 * 1024;
|
|
643
|
-
const fd =
|
|
644
|
-
|
|
645
|
-
|
|
1015
|
+
const fd = fs3.openSync(this.vmOverlayPath, "w");
|
|
1016
|
+
fs3.ftruncateSync(fd, overlaySize);
|
|
1017
|
+
fs3.closeSync(fd);
|
|
646
1018
|
execSync2(`mkfs.ext4 -F -q "${this.vmOverlayPath}"`, { stdio: "ignore" });
|
|
647
1019
|
console.log(`[VM ${this.config.vmId}] Setting up network...`);
|
|
648
1020
|
this.networkConfig = await createTapDevice(this.config.vmId);
|
|
@@ -792,11 +1164,14 @@ var FirecrackerVM = class {
|
|
|
792
1164
|
this.process = null;
|
|
793
1165
|
}
|
|
794
1166
|
if (this.networkConfig) {
|
|
795
|
-
await deleteTapDevice(
|
|
1167
|
+
await deleteTapDevice(
|
|
1168
|
+
this.networkConfig.tapDevice,
|
|
1169
|
+
this.networkConfig.guestIp
|
|
1170
|
+
);
|
|
796
1171
|
this.networkConfig = null;
|
|
797
1172
|
}
|
|
798
|
-
if (
|
|
799
|
-
|
|
1173
|
+
if (fs3.existsSync(this.workDir)) {
|
|
1174
|
+
fs3.rmSync(this.workDir, { recursive: true, force: true });
|
|
800
1175
|
}
|
|
801
1176
|
this.client = null;
|
|
802
1177
|
this.state = "stopped";
|
|
@@ -830,12 +1205,12 @@ var FirecrackerVM = class {
|
|
|
830
1205
|
};
|
|
831
1206
|
|
|
832
1207
|
// src/lib/firecracker/guest.ts
|
|
833
|
-
import { exec as
|
|
834
|
-
import { promisify as
|
|
835
|
-
import
|
|
836
|
-
import
|
|
1208
|
+
import { exec as exec3, execSync as execSync3 } from "child_process";
|
|
1209
|
+
import { promisify as promisify3 } from "util";
|
|
1210
|
+
import fs4 from "fs";
|
|
1211
|
+
import path3 from "path";
|
|
837
1212
|
import os from "os";
|
|
838
|
-
var
|
|
1213
|
+
var execAsync3 = promisify3(exec3);
|
|
839
1214
|
var DEFAULT_SSH_OPTIONS = [
|
|
840
1215
|
"-o",
|
|
841
1216
|
"StrictHostKeyChecking=no",
|
|
@@ -886,7 +1261,7 @@ var SSHClient = class {
|
|
|
886
1261
|
const escapedCommand = command.replace(/'/g, "'\\''");
|
|
887
1262
|
const fullCmd = [...sshCmd, `'${escapedCommand}'`].join(" ");
|
|
888
1263
|
try {
|
|
889
|
-
const { stdout, stderr } = await
|
|
1264
|
+
const { stdout, stderr } = await execAsync3(fullCmd, {
|
|
890
1265
|
maxBuffer: 50 * 1024 * 1024,
|
|
891
1266
|
// 50MB buffer
|
|
892
1267
|
timeout: timeoutMs ?? 3e5
|
|
@@ -1033,11 +1408,11 @@ function createVMSSHClient(guestIp, user = "root", privateKeyPath) {
|
|
|
1033
1408
|
}
|
|
1034
1409
|
function getRunnerSSHKeyPath() {
|
|
1035
1410
|
const runnerKeyPath = "/opt/vm0-runner/ssh/id_rsa";
|
|
1036
|
-
if (
|
|
1411
|
+
if (fs4.existsSync(runnerKeyPath)) {
|
|
1037
1412
|
return runnerKeyPath;
|
|
1038
1413
|
}
|
|
1039
|
-
const userKeyPath =
|
|
1040
|
-
if (
|
|
1414
|
+
const userKeyPath = path3.join(os.homedir(), ".ssh", "id_rsa");
|
|
1415
|
+
if (fs4.existsSync(userKeyPath)) {
|
|
1041
1416
|
return userKeyPath;
|
|
1042
1417
|
}
|
|
1043
1418
|
return "";
|
|
@@ -1391,8 +1766,8 @@ function getErrorMap() {
|
|
|
1391
1766
|
return overrideErrorMap;
|
|
1392
1767
|
}
|
|
1393
1768
|
var makeIssue = (params) => {
|
|
1394
|
-
const { data, path:
|
|
1395
|
-
const fullPath = [...
|
|
1769
|
+
const { data, path: path7, errorMaps, issueData } = params;
|
|
1770
|
+
const fullPath = [...path7, ...issueData.path || []];
|
|
1396
1771
|
const fullIssue = {
|
|
1397
1772
|
...issueData,
|
|
1398
1773
|
path: fullPath
|
|
@@ -1491,11 +1866,11 @@ var errorUtil;
|
|
|
1491
1866
|
errorUtil2.toString = (message) => typeof message === "string" ? message : message === null || message === void 0 ? void 0 : message.message;
|
|
1492
1867
|
})(errorUtil || (errorUtil = {}));
|
|
1493
1868
|
var ParseInputLazyPath = class {
|
|
1494
|
-
constructor(parent, value,
|
|
1869
|
+
constructor(parent, value, path7, key) {
|
|
1495
1870
|
this._cachedPath = [];
|
|
1496
1871
|
this.parent = parent;
|
|
1497
1872
|
this.data = value;
|
|
1498
|
-
this._path =
|
|
1873
|
+
this._path = path7;
|
|
1499
1874
|
this._key = key;
|
|
1500
1875
|
}
|
|
1501
1876
|
get path() {
|
|
@@ -4954,7 +5329,9 @@ var storedExecutionContextSchema = z4.object({
|
|
|
4954
5329
|
encryptedSecrets: z4.string().nullable(),
|
|
4955
5330
|
// AES-256-GCM encrypted secrets
|
|
4956
5331
|
cliAgentType: z4.string(),
|
|
4957
|
-
experimentalFirewall: experimentalFirewallSchema.optional()
|
|
5332
|
+
experimentalFirewall: experimentalFirewallSchema.optional(),
|
|
5333
|
+
// Debug flag to force real Claude in mock environments (internal use only)
|
|
5334
|
+
debugNoMockClaude: z4.boolean().optional()
|
|
4958
5335
|
});
|
|
4959
5336
|
var executionContextSchema = z4.object({
|
|
4960
5337
|
runId: z4.string().uuid(),
|
|
@@ -4972,7 +5349,9 @@ var executionContextSchema = z4.object({
|
|
|
4972
5349
|
secretValues: z4.array(z4.string()).nullable(),
|
|
4973
5350
|
cliAgentType: z4.string(),
|
|
4974
5351
|
// Experimental firewall configuration
|
|
4975
|
-
experimentalFirewall: experimentalFirewallSchema.optional()
|
|
5352
|
+
experimentalFirewall: experimentalFirewallSchema.optional(),
|
|
5353
|
+
// Debug flag to force real Claude in mock environments (internal use only)
|
|
5354
|
+
debugNoMockClaude: z4.boolean().optional()
|
|
4976
5355
|
});
|
|
4977
5356
|
var runnersJobClaimContract = c.router({
|
|
4978
5357
|
claim: {
|
|
@@ -5255,6 +5634,8 @@ var unifiedRunRequestSchema = z6.object({
|
|
|
5255
5634
|
vars: z6.record(z6.string(), z6.string()).optional(),
|
|
5256
5635
|
secrets: z6.record(z6.string(), z6.string()).optional(),
|
|
5257
5636
|
volumeVersions: z6.record(z6.string(), z6.string()).optional(),
|
|
5637
|
+
// Debug flag to force real Claude in mock environments (internal use only)
|
|
5638
|
+
debugNoMockClaude: z6.boolean().optional(),
|
|
5258
5639
|
// Required
|
|
5259
5640
|
prompt: z6.string().min(1, "Missing prompt")
|
|
5260
5641
|
});
|
|
@@ -6261,43 +6642,137 @@ var scopeContract = c9.router({
|
|
|
6261
6642
|
}
|
|
6262
6643
|
});
|
|
6263
6644
|
|
|
6264
|
-
// ../../packages/core/src/contracts/
|
|
6645
|
+
// ../../packages/core/src/contracts/credentials.ts
|
|
6265
6646
|
import { z as z14 } from "zod";
|
|
6266
6647
|
var c10 = initContract();
|
|
6267
|
-
var
|
|
6268
|
-
|
|
6269
|
-
|
|
6270
|
-
|
|
6271
|
-
|
|
6272
|
-
|
|
6273
|
-
|
|
6274
|
-
|
|
6275
|
-
volumeVersions: z14.record(z14.string(), z14.string()).nullable(),
|
|
6648
|
+
var credentialNameSchema = z14.string().min(1, "Credential name is required").max(255, "Credential name must be at most 255 characters").regex(
|
|
6649
|
+
/^[A-Z][A-Z0-9_]*$/,
|
|
6650
|
+
"Credential name must contain only uppercase letters, numbers, and underscores, and must start with a letter (e.g., MY_API_KEY)"
|
|
6651
|
+
);
|
|
6652
|
+
var credentialResponseSchema = z14.object({
|
|
6653
|
+
id: z14.string().uuid(),
|
|
6654
|
+
name: z14.string(),
|
|
6655
|
+
description: z14.string().nullable(),
|
|
6276
6656
|
createdAt: z14.string(),
|
|
6277
6657
|
updatedAt: z14.string()
|
|
6278
6658
|
});
|
|
6279
|
-
var
|
|
6280
|
-
|
|
6281
|
-
|
|
6282
|
-
|
|
6659
|
+
var credentialListResponseSchema = z14.object({
|
|
6660
|
+
credentials: z14.array(credentialResponseSchema)
|
|
6661
|
+
});
|
|
6662
|
+
var setCredentialRequestSchema = z14.object({
|
|
6663
|
+
name: credentialNameSchema,
|
|
6664
|
+
value: z14.string().min(1, "Credential value is required"),
|
|
6665
|
+
description: z14.string().max(1e3).optional()
|
|
6666
|
+
});
|
|
6667
|
+
var credentialsMainContract = c10.router({
|
|
6668
|
+
/**
|
|
6669
|
+
* GET /api/credentials
|
|
6670
|
+
* List all credentials for the current user's scope (metadata only)
|
|
6671
|
+
*/
|
|
6672
|
+
list: {
|
|
6673
|
+
method: "GET",
|
|
6674
|
+
path: "/api/credentials",
|
|
6675
|
+
responses: {
|
|
6676
|
+
200: credentialListResponseSchema,
|
|
6677
|
+
401: apiErrorSchema,
|
|
6678
|
+
500: apiErrorSchema
|
|
6679
|
+
},
|
|
6680
|
+
summary: "List all credentials (metadata only)"
|
|
6681
|
+
},
|
|
6682
|
+
/**
|
|
6683
|
+
* PUT /api/credentials
|
|
6684
|
+
* Create or update a credential
|
|
6685
|
+
*/
|
|
6686
|
+
set: {
|
|
6687
|
+
method: "PUT",
|
|
6688
|
+
path: "/api/credentials",
|
|
6689
|
+
body: setCredentialRequestSchema,
|
|
6690
|
+
responses: {
|
|
6691
|
+
200: credentialResponseSchema,
|
|
6692
|
+
201: credentialResponseSchema,
|
|
6693
|
+
400: apiErrorSchema,
|
|
6694
|
+
401: apiErrorSchema,
|
|
6695
|
+
500: apiErrorSchema
|
|
6696
|
+
},
|
|
6697
|
+
summary: "Create or update a credential"
|
|
6698
|
+
}
|
|
6699
|
+
});
|
|
6700
|
+
var credentialsByNameContract = c10.router({
|
|
6701
|
+
/**
|
|
6702
|
+
* GET /api/credentials/:name
|
|
6703
|
+
* Get a credential by name (metadata only)
|
|
6704
|
+
*/
|
|
6705
|
+
get: {
|
|
6706
|
+
method: "GET",
|
|
6707
|
+
path: "/api/credentials/:name",
|
|
6708
|
+
pathParams: z14.object({
|
|
6709
|
+
name: credentialNameSchema
|
|
6710
|
+
}),
|
|
6711
|
+
responses: {
|
|
6712
|
+
200: credentialResponseSchema,
|
|
6713
|
+
401: apiErrorSchema,
|
|
6714
|
+
404: apiErrorSchema,
|
|
6715
|
+
500: apiErrorSchema
|
|
6716
|
+
},
|
|
6717
|
+
summary: "Get credential metadata by name"
|
|
6718
|
+
},
|
|
6719
|
+
/**
|
|
6720
|
+
* DELETE /api/credentials/:name
|
|
6721
|
+
* Delete a credential by name
|
|
6722
|
+
*/
|
|
6723
|
+
delete: {
|
|
6724
|
+
method: "DELETE",
|
|
6725
|
+
path: "/api/credentials/:name",
|
|
6726
|
+
pathParams: z14.object({
|
|
6727
|
+
name: credentialNameSchema
|
|
6728
|
+
}),
|
|
6729
|
+
responses: {
|
|
6730
|
+
204: z14.undefined(),
|
|
6731
|
+
401: apiErrorSchema,
|
|
6732
|
+
404: apiErrorSchema,
|
|
6733
|
+
500: apiErrorSchema
|
|
6734
|
+
},
|
|
6735
|
+
summary: "Delete a credential"
|
|
6736
|
+
}
|
|
6283
6737
|
});
|
|
6284
|
-
|
|
6285
|
-
|
|
6286
|
-
|
|
6738
|
+
|
|
6739
|
+
// ../../packages/core/src/contracts/sessions.ts
|
|
6740
|
+
import { z as z15 } from "zod";
|
|
6741
|
+
var c11 = initContract();
|
|
6742
|
+
var sessionResponseSchema = z15.object({
|
|
6743
|
+
id: z15.string(),
|
|
6744
|
+
agentComposeId: z15.string(),
|
|
6745
|
+
agentComposeVersionId: z15.string().nullable(),
|
|
6746
|
+
conversationId: z15.string().nullable(),
|
|
6747
|
+
artifactName: z15.string().nullable(),
|
|
6748
|
+
vars: z15.record(z15.string(), z15.string()).nullable(),
|
|
6749
|
+
secretNames: z15.array(z15.string()).nullable(),
|
|
6750
|
+
volumeVersions: z15.record(z15.string(), z15.string()).nullable(),
|
|
6751
|
+
createdAt: z15.string(),
|
|
6752
|
+
updatedAt: z15.string()
|
|
6287
6753
|
});
|
|
6288
|
-
var
|
|
6289
|
-
|
|
6754
|
+
var agentComposeSnapshotSchema = z15.object({
|
|
6755
|
+
agentComposeVersionId: z15.string(),
|
|
6756
|
+
vars: z15.record(z15.string(), z15.string()).optional(),
|
|
6757
|
+
secretNames: z15.array(z15.string()).optional()
|
|
6758
|
+
});
|
|
6759
|
+
var artifactSnapshotSchema2 = z15.object({
|
|
6760
|
+
artifactName: z15.string(),
|
|
6761
|
+
artifactVersion: z15.string()
|
|
6290
6762
|
});
|
|
6291
|
-
var
|
|
6292
|
-
|
|
6293
|
-
|
|
6294
|
-
|
|
6763
|
+
var volumeVersionsSnapshotSchema2 = z15.object({
|
|
6764
|
+
versions: z15.record(z15.string(), z15.string())
|
|
6765
|
+
});
|
|
6766
|
+
var checkpointResponseSchema = z15.object({
|
|
6767
|
+
id: z15.string(),
|
|
6768
|
+
runId: z15.string(),
|
|
6769
|
+
conversationId: z15.string(),
|
|
6295
6770
|
agentComposeSnapshot: agentComposeSnapshotSchema,
|
|
6296
6771
|
artifactSnapshot: artifactSnapshotSchema2.nullable(),
|
|
6297
6772
|
volumeVersionsSnapshot: volumeVersionsSnapshotSchema2.nullable(),
|
|
6298
|
-
createdAt:
|
|
6773
|
+
createdAt: z15.string()
|
|
6299
6774
|
});
|
|
6300
|
-
var sessionsByIdContract =
|
|
6775
|
+
var sessionsByIdContract = c11.router({
|
|
6301
6776
|
/**
|
|
6302
6777
|
* GET /api/agent/sessions/:id
|
|
6303
6778
|
* Get session by ID
|
|
@@ -6305,8 +6780,8 @@ var sessionsByIdContract = c10.router({
|
|
|
6305
6780
|
getById: {
|
|
6306
6781
|
method: "GET",
|
|
6307
6782
|
path: "/api/agent/sessions/:id",
|
|
6308
|
-
pathParams:
|
|
6309
|
-
id:
|
|
6783
|
+
pathParams: z15.object({
|
|
6784
|
+
id: z15.string().min(1, "Session ID is required")
|
|
6310
6785
|
}),
|
|
6311
6786
|
responses: {
|
|
6312
6787
|
200: sessionResponseSchema,
|
|
@@ -6317,7 +6792,7 @@ var sessionsByIdContract = c10.router({
|
|
|
6317
6792
|
summary: "Get session by ID"
|
|
6318
6793
|
}
|
|
6319
6794
|
});
|
|
6320
|
-
var checkpointsByIdContract =
|
|
6795
|
+
var checkpointsByIdContract = c11.router({
|
|
6321
6796
|
/**
|
|
6322
6797
|
* GET /api/agent/checkpoints/:id
|
|
6323
6798
|
* Get checkpoint by ID
|
|
@@ -6325,8 +6800,8 @@ var checkpointsByIdContract = c10.router({
|
|
|
6325
6800
|
getById: {
|
|
6326
6801
|
method: "GET",
|
|
6327
6802
|
path: "/api/agent/checkpoints/:id",
|
|
6328
|
-
pathParams:
|
|
6329
|
-
id:
|
|
6803
|
+
pathParams: z15.object({
|
|
6804
|
+
id: z15.string().min(1, "Checkpoint ID is required")
|
|
6330
6805
|
}),
|
|
6331
6806
|
responses: {
|
|
6332
6807
|
200: checkpointResponseSchema,
|
|
@@ -6339,91 +6814,91 @@ var checkpointsByIdContract = c10.router({
|
|
|
6339
6814
|
});
|
|
6340
6815
|
|
|
6341
6816
|
// ../../packages/core/src/contracts/schedules.ts
|
|
6342
|
-
import { z as
|
|
6343
|
-
var
|
|
6344
|
-
var scheduleTriggerSchema =
|
|
6345
|
-
cron:
|
|
6346
|
-
at:
|
|
6347
|
-
timezone:
|
|
6817
|
+
import { z as z16 } from "zod";
|
|
6818
|
+
var c12 = initContract();
|
|
6819
|
+
var scheduleTriggerSchema = z16.object({
|
|
6820
|
+
cron: z16.string().optional(),
|
|
6821
|
+
at: z16.string().optional(),
|
|
6822
|
+
timezone: z16.string().default("UTC")
|
|
6348
6823
|
}).refine((data) => data.cron && !data.at || !data.cron && data.at, {
|
|
6349
6824
|
message: "Exactly one of 'cron' or 'at' must be specified"
|
|
6350
6825
|
});
|
|
6351
|
-
var scheduleRunConfigSchema =
|
|
6352
|
-
agent:
|
|
6353
|
-
prompt:
|
|
6354
|
-
vars:
|
|
6355
|
-
secrets:
|
|
6356
|
-
artifactName:
|
|
6357
|
-
artifactVersion:
|
|
6358
|
-
volumeVersions:
|
|
6826
|
+
var scheduleRunConfigSchema = z16.object({
|
|
6827
|
+
agent: z16.string().min(1, "Agent reference required"),
|
|
6828
|
+
prompt: z16.string().min(1, "Prompt required"),
|
|
6829
|
+
vars: z16.record(z16.string(), z16.string()).optional(),
|
|
6830
|
+
secrets: z16.record(z16.string(), z16.string()).optional(),
|
|
6831
|
+
artifactName: z16.string().optional(),
|
|
6832
|
+
artifactVersion: z16.string().optional(),
|
|
6833
|
+
volumeVersions: z16.record(z16.string(), z16.string()).optional()
|
|
6359
6834
|
});
|
|
6360
|
-
var scheduleDefinitionSchema =
|
|
6835
|
+
var scheduleDefinitionSchema = z16.object({
|
|
6361
6836
|
on: scheduleTriggerSchema,
|
|
6362
6837
|
run: scheduleRunConfigSchema
|
|
6363
6838
|
});
|
|
6364
|
-
var scheduleYamlSchema =
|
|
6365
|
-
version:
|
|
6366
|
-
schedules:
|
|
6367
|
-
});
|
|
6368
|
-
var deployScheduleRequestSchema =
|
|
6369
|
-
name:
|
|
6370
|
-
cronExpression:
|
|
6371
|
-
atTime:
|
|
6372
|
-
timezone:
|
|
6373
|
-
prompt:
|
|
6374
|
-
vars:
|
|
6375
|
-
secrets:
|
|
6376
|
-
artifactName:
|
|
6377
|
-
artifactVersion:
|
|
6378
|
-
volumeVersions:
|
|
6839
|
+
var scheduleYamlSchema = z16.object({
|
|
6840
|
+
version: z16.literal("1.0"),
|
|
6841
|
+
schedules: z16.record(z16.string(), scheduleDefinitionSchema)
|
|
6842
|
+
});
|
|
6843
|
+
var deployScheduleRequestSchema = z16.object({
|
|
6844
|
+
name: z16.string().min(1).max(64, "Schedule name max 64 chars"),
|
|
6845
|
+
cronExpression: z16.string().optional(),
|
|
6846
|
+
atTime: z16.string().optional(),
|
|
6847
|
+
timezone: z16.string().default("UTC"),
|
|
6848
|
+
prompt: z16.string().min(1, "Prompt required"),
|
|
6849
|
+
vars: z16.record(z16.string(), z16.string()).optional(),
|
|
6850
|
+
secrets: z16.record(z16.string(), z16.string()).optional(),
|
|
6851
|
+
artifactName: z16.string().optional(),
|
|
6852
|
+
artifactVersion: z16.string().optional(),
|
|
6853
|
+
volumeVersions: z16.record(z16.string(), z16.string()).optional(),
|
|
6379
6854
|
// Resolved agent compose ID (CLI resolves scope/name:version → composeId)
|
|
6380
|
-
composeId:
|
|
6855
|
+
composeId: z16.string().uuid("Invalid compose ID")
|
|
6381
6856
|
}).refine(
|
|
6382
6857
|
(data) => data.cronExpression && !data.atTime || !data.cronExpression && data.atTime,
|
|
6383
6858
|
{
|
|
6384
6859
|
message: "Exactly one of 'cronExpression' or 'atTime' must be specified"
|
|
6385
6860
|
}
|
|
6386
6861
|
);
|
|
6387
|
-
var scheduleResponseSchema =
|
|
6388
|
-
id:
|
|
6389
|
-
composeId:
|
|
6390
|
-
composeName:
|
|
6391
|
-
scopeSlug:
|
|
6392
|
-
name:
|
|
6393
|
-
cronExpression:
|
|
6394
|
-
atTime:
|
|
6395
|
-
timezone:
|
|
6396
|
-
prompt:
|
|
6397
|
-
vars:
|
|
6862
|
+
var scheduleResponseSchema = z16.object({
|
|
6863
|
+
id: z16.string().uuid(),
|
|
6864
|
+
composeId: z16.string().uuid(),
|
|
6865
|
+
composeName: z16.string(),
|
|
6866
|
+
scopeSlug: z16.string(),
|
|
6867
|
+
name: z16.string(),
|
|
6868
|
+
cronExpression: z16.string().nullable(),
|
|
6869
|
+
atTime: z16.string().nullable(),
|
|
6870
|
+
timezone: z16.string(),
|
|
6871
|
+
prompt: z16.string(),
|
|
6872
|
+
vars: z16.record(z16.string(), z16.string()).nullable(),
|
|
6398
6873
|
// Secret names only (values are never returned)
|
|
6399
|
-
secretNames:
|
|
6400
|
-
artifactName:
|
|
6401
|
-
artifactVersion:
|
|
6402
|
-
volumeVersions:
|
|
6403
|
-
enabled:
|
|
6404
|
-
nextRunAt:
|
|
6405
|
-
createdAt:
|
|
6406
|
-
updatedAt:
|
|
6407
|
-
});
|
|
6408
|
-
var runSummarySchema =
|
|
6409
|
-
id:
|
|
6410
|
-
status:
|
|
6411
|
-
createdAt:
|
|
6412
|
-
completedAt:
|
|
6413
|
-
error:
|
|
6414
|
-
});
|
|
6415
|
-
var scheduleRunsResponseSchema =
|
|
6416
|
-
runs:
|
|
6417
|
-
});
|
|
6418
|
-
var scheduleListResponseSchema =
|
|
6419
|
-
schedules:
|
|
6420
|
-
});
|
|
6421
|
-
var deployScheduleResponseSchema =
|
|
6874
|
+
secretNames: z16.array(z16.string()).nullable(),
|
|
6875
|
+
artifactName: z16.string().nullable(),
|
|
6876
|
+
artifactVersion: z16.string().nullable(),
|
|
6877
|
+
volumeVersions: z16.record(z16.string(), z16.string()).nullable(),
|
|
6878
|
+
enabled: z16.boolean(),
|
|
6879
|
+
nextRunAt: z16.string().nullable(),
|
|
6880
|
+
createdAt: z16.string(),
|
|
6881
|
+
updatedAt: z16.string()
|
|
6882
|
+
});
|
|
6883
|
+
var runSummarySchema = z16.object({
|
|
6884
|
+
id: z16.string().uuid(),
|
|
6885
|
+
status: z16.enum(["pending", "running", "completed", "failed", "timeout"]),
|
|
6886
|
+
createdAt: z16.string(),
|
|
6887
|
+
completedAt: z16.string().nullable(),
|
|
6888
|
+
error: z16.string().nullable()
|
|
6889
|
+
});
|
|
6890
|
+
var scheduleRunsResponseSchema = z16.object({
|
|
6891
|
+
runs: z16.array(runSummarySchema)
|
|
6892
|
+
});
|
|
6893
|
+
var scheduleListResponseSchema = z16.object({
|
|
6894
|
+
schedules: z16.array(scheduleResponseSchema)
|
|
6895
|
+
});
|
|
6896
|
+
var deployScheduleResponseSchema = z16.object({
|
|
6422
6897
|
schedule: scheduleResponseSchema,
|
|
6423
|
-
created:
|
|
6898
|
+
created: z16.boolean()
|
|
6424
6899
|
// true if created, false if updated
|
|
6425
6900
|
});
|
|
6426
|
-
var schedulesMainContract =
|
|
6901
|
+
var schedulesMainContract = c12.router({
|
|
6427
6902
|
/**
|
|
6428
6903
|
* POST /api/agent/schedules
|
|
6429
6904
|
* Deploy (create or update) a schedule
|
|
@@ -6459,7 +6934,7 @@ var schedulesMainContract = c11.router({
|
|
|
6459
6934
|
summary: "List all schedules"
|
|
6460
6935
|
}
|
|
6461
6936
|
});
|
|
6462
|
-
var schedulesByNameContract =
|
|
6937
|
+
var schedulesByNameContract = c12.router({
|
|
6463
6938
|
/**
|
|
6464
6939
|
* GET /api/agent/schedules/:name
|
|
6465
6940
|
* Get schedule by name
|
|
@@ -6467,11 +6942,11 @@ var schedulesByNameContract = c11.router({
|
|
|
6467
6942
|
getByName: {
|
|
6468
6943
|
method: "GET",
|
|
6469
6944
|
path: "/api/agent/schedules/:name",
|
|
6470
|
-
pathParams:
|
|
6471
|
-
name:
|
|
6945
|
+
pathParams: z16.object({
|
|
6946
|
+
name: z16.string().min(1, "Schedule name required")
|
|
6472
6947
|
}),
|
|
6473
|
-
query:
|
|
6474
|
-
composeId:
|
|
6948
|
+
query: z16.object({
|
|
6949
|
+
composeId: z16.string().uuid("Compose ID required")
|
|
6475
6950
|
}),
|
|
6476
6951
|
responses: {
|
|
6477
6952
|
200: scheduleResponseSchema,
|
|
@@ -6487,21 +6962,21 @@ var schedulesByNameContract = c11.router({
|
|
|
6487
6962
|
delete: {
|
|
6488
6963
|
method: "DELETE",
|
|
6489
6964
|
path: "/api/agent/schedules/:name",
|
|
6490
|
-
pathParams:
|
|
6491
|
-
name:
|
|
6965
|
+
pathParams: z16.object({
|
|
6966
|
+
name: z16.string().min(1, "Schedule name required")
|
|
6492
6967
|
}),
|
|
6493
|
-
query:
|
|
6494
|
-
composeId:
|
|
6968
|
+
query: z16.object({
|
|
6969
|
+
composeId: z16.string().uuid("Compose ID required")
|
|
6495
6970
|
}),
|
|
6496
6971
|
responses: {
|
|
6497
|
-
204:
|
|
6972
|
+
204: z16.undefined(),
|
|
6498
6973
|
401: apiErrorSchema,
|
|
6499
6974
|
404: apiErrorSchema
|
|
6500
6975
|
},
|
|
6501
6976
|
summary: "Delete schedule"
|
|
6502
6977
|
}
|
|
6503
6978
|
});
|
|
6504
|
-
var schedulesEnableContract =
|
|
6979
|
+
var schedulesEnableContract = c12.router({
|
|
6505
6980
|
/**
|
|
6506
6981
|
* POST /api/agent/schedules/:name/enable
|
|
6507
6982
|
* Enable a disabled schedule
|
|
@@ -6509,11 +6984,11 @@ var schedulesEnableContract = c11.router({
|
|
|
6509
6984
|
enable: {
|
|
6510
6985
|
method: "POST",
|
|
6511
6986
|
path: "/api/agent/schedules/:name/enable",
|
|
6512
|
-
pathParams:
|
|
6513
|
-
name:
|
|
6987
|
+
pathParams: z16.object({
|
|
6988
|
+
name: z16.string().min(1, "Schedule name required")
|
|
6514
6989
|
}),
|
|
6515
|
-
body:
|
|
6516
|
-
composeId:
|
|
6990
|
+
body: z16.object({
|
|
6991
|
+
composeId: z16.string().uuid("Compose ID required")
|
|
6517
6992
|
}),
|
|
6518
6993
|
responses: {
|
|
6519
6994
|
200: scheduleResponseSchema,
|
|
@@ -6529,11 +7004,11 @@ var schedulesEnableContract = c11.router({
|
|
|
6529
7004
|
disable: {
|
|
6530
7005
|
method: "POST",
|
|
6531
7006
|
path: "/api/agent/schedules/:name/disable",
|
|
6532
|
-
pathParams:
|
|
6533
|
-
name:
|
|
7007
|
+
pathParams: z16.object({
|
|
7008
|
+
name: z16.string().min(1, "Schedule name required")
|
|
6534
7009
|
}),
|
|
6535
|
-
body:
|
|
6536
|
-
composeId:
|
|
7010
|
+
body: z16.object({
|
|
7011
|
+
composeId: z16.string().uuid("Compose ID required")
|
|
6537
7012
|
}),
|
|
6538
7013
|
responses: {
|
|
6539
7014
|
200: scheduleResponseSchema,
|
|
@@ -6543,7 +7018,7 @@ var schedulesEnableContract = c11.router({
|
|
|
6543
7018
|
summary: "Disable schedule"
|
|
6544
7019
|
}
|
|
6545
7020
|
});
|
|
6546
|
-
var scheduleRunsContract =
|
|
7021
|
+
var scheduleRunsContract = c12.router({
|
|
6547
7022
|
/**
|
|
6548
7023
|
* GET /api/agent/schedules/:name/runs
|
|
6549
7024
|
* List recent runs for a schedule
|
|
@@ -6551,12 +7026,12 @@ var scheduleRunsContract = c11.router({
|
|
|
6551
7026
|
listRuns: {
|
|
6552
7027
|
method: "GET",
|
|
6553
7028
|
path: "/api/agent/schedules/:name/runs",
|
|
6554
|
-
pathParams:
|
|
6555
|
-
name:
|
|
7029
|
+
pathParams: z16.object({
|
|
7030
|
+
name: z16.string().min(1, "Schedule name required")
|
|
6556
7031
|
}),
|
|
6557
|
-
query:
|
|
6558
|
-
composeId:
|
|
6559
|
-
limit:
|
|
7032
|
+
query: z16.object({
|
|
7033
|
+
composeId: z16.string().uuid("Compose ID required"),
|
|
7034
|
+
limit: z16.coerce.number().min(0).max(100).default(5)
|
|
6560
7035
|
}),
|
|
6561
7036
|
responses: {
|
|
6562
7037
|
200: scheduleRunsResponseSchema,
|
|
@@ -6568,8 +7043,8 @@ var scheduleRunsContract = c11.router({
|
|
|
6568
7043
|
});
|
|
6569
7044
|
|
|
6570
7045
|
// ../../packages/core/src/contracts/public/common.ts
|
|
6571
|
-
import { z as
|
|
6572
|
-
var publicApiErrorTypeSchema =
|
|
7046
|
+
import { z as z17 } from "zod";
|
|
7047
|
+
var publicApiErrorTypeSchema = z17.enum([
|
|
6573
7048
|
"api_error",
|
|
6574
7049
|
// Internal server error (5xx)
|
|
6575
7050
|
"invalid_request_error",
|
|
@@ -6581,55 +7056,55 @@ var publicApiErrorTypeSchema = z16.enum([
|
|
|
6581
7056
|
"conflict_error"
|
|
6582
7057
|
// Resource conflict (409)
|
|
6583
7058
|
]);
|
|
6584
|
-
var publicApiErrorSchema =
|
|
6585
|
-
error:
|
|
7059
|
+
var publicApiErrorSchema = z17.object({
|
|
7060
|
+
error: z17.object({
|
|
6586
7061
|
type: publicApiErrorTypeSchema,
|
|
6587
|
-
code:
|
|
6588
|
-
message:
|
|
6589
|
-
param:
|
|
6590
|
-
doc_url:
|
|
7062
|
+
code: z17.string(),
|
|
7063
|
+
message: z17.string(),
|
|
7064
|
+
param: z17.string().optional(),
|
|
7065
|
+
doc_url: z17.string().url().optional()
|
|
6591
7066
|
})
|
|
6592
7067
|
});
|
|
6593
|
-
var paginationSchema =
|
|
6594
|
-
has_more:
|
|
6595
|
-
next_cursor:
|
|
7068
|
+
var paginationSchema = z17.object({
|
|
7069
|
+
has_more: z17.boolean(),
|
|
7070
|
+
next_cursor: z17.string().nullable()
|
|
6596
7071
|
});
|
|
6597
7072
|
function createPaginatedResponseSchema(dataSchema) {
|
|
6598
|
-
return
|
|
6599
|
-
data:
|
|
7073
|
+
return z17.object({
|
|
7074
|
+
data: z17.array(dataSchema),
|
|
6600
7075
|
pagination: paginationSchema
|
|
6601
7076
|
});
|
|
6602
7077
|
}
|
|
6603
|
-
var listQuerySchema =
|
|
6604
|
-
cursor:
|
|
6605
|
-
limit:
|
|
7078
|
+
var listQuerySchema = z17.object({
|
|
7079
|
+
cursor: z17.string().optional(),
|
|
7080
|
+
limit: z17.coerce.number().min(1).max(100).default(20)
|
|
6606
7081
|
});
|
|
6607
|
-
var requestIdSchema =
|
|
6608
|
-
var timestampSchema =
|
|
7082
|
+
var requestIdSchema = z17.string().uuid();
|
|
7083
|
+
var timestampSchema = z17.string().datetime();
|
|
6609
7084
|
|
|
6610
7085
|
// ../../packages/core/src/contracts/public/agents.ts
|
|
6611
|
-
import { z as
|
|
6612
|
-
var
|
|
6613
|
-
var publicAgentSchema =
|
|
6614
|
-
id:
|
|
6615
|
-
name:
|
|
6616
|
-
current_version_id:
|
|
7086
|
+
import { z as z18 } from "zod";
|
|
7087
|
+
var c13 = initContract();
|
|
7088
|
+
var publicAgentSchema = z18.object({
|
|
7089
|
+
id: z18.string(),
|
|
7090
|
+
name: z18.string(),
|
|
7091
|
+
current_version_id: z18.string().nullable(),
|
|
6617
7092
|
created_at: timestampSchema,
|
|
6618
7093
|
updated_at: timestampSchema
|
|
6619
7094
|
});
|
|
6620
|
-
var agentVersionSchema =
|
|
6621
|
-
id:
|
|
6622
|
-
agent_id:
|
|
6623
|
-
version_number:
|
|
7095
|
+
var agentVersionSchema = z18.object({
|
|
7096
|
+
id: z18.string(),
|
|
7097
|
+
agent_id: z18.string(),
|
|
7098
|
+
version_number: z18.number(),
|
|
6624
7099
|
created_at: timestampSchema
|
|
6625
7100
|
});
|
|
6626
7101
|
var publicAgentDetailSchema = publicAgentSchema;
|
|
6627
7102
|
var paginatedAgentsSchema = createPaginatedResponseSchema(publicAgentSchema);
|
|
6628
7103
|
var paginatedAgentVersionsSchema = createPaginatedResponseSchema(agentVersionSchema);
|
|
6629
7104
|
var agentListQuerySchema = listQuerySchema.extend({
|
|
6630
|
-
name:
|
|
7105
|
+
name: z18.string().optional()
|
|
6631
7106
|
});
|
|
6632
|
-
var publicAgentsListContract =
|
|
7107
|
+
var publicAgentsListContract = c13.router({
|
|
6633
7108
|
list: {
|
|
6634
7109
|
method: "GET",
|
|
6635
7110
|
path: "/v1/agents",
|
|
@@ -6643,12 +7118,12 @@ var publicAgentsListContract = c12.router({
|
|
|
6643
7118
|
description: "List all agents in the current scope with pagination. Use the `name` query parameter to filter by agent name."
|
|
6644
7119
|
}
|
|
6645
7120
|
});
|
|
6646
|
-
var publicAgentByIdContract =
|
|
7121
|
+
var publicAgentByIdContract = c13.router({
|
|
6647
7122
|
get: {
|
|
6648
7123
|
method: "GET",
|
|
6649
7124
|
path: "/v1/agents/:id",
|
|
6650
|
-
pathParams:
|
|
6651
|
-
id:
|
|
7125
|
+
pathParams: z18.object({
|
|
7126
|
+
id: z18.string().min(1, "Agent ID is required")
|
|
6652
7127
|
}),
|
|
6653
7128
|
responses: {
|
|
6654
7129
|
200: publicAgentDetailSchema,
|
|
@@ -6660,12 +7135,12 @@ var publicAgentByIdContract = c12.router({
|
|
|
6660
7135
|
description: "Get agent details by ID"
|
|
6661
7136
|
}
|
|
6662
7137
|
});
|
|
6663
|
-
var publicAgentVersionsContract =
|
|
7138
|
+
var publicAgentVersionsContract = c13.router({
|
|
6664
7139
|
list: {
|
|
6665
7140
|
method: "GET",
|
|
6666
7141
|
path: "/v1/agents/:id/versions",
|
|
6667
|
-
pathParams:
|
|
6668
|
-
id:
|
|
7142
|
+
pathParams: z18.object({
|
|
7143
|
+
id: z18.string().min(1, "Agent ID is required")
|
|
6669
7144
|
}),
|
|
6670
7145
|
query: listQuerySchema,
|
|
6671
7146
|
responses: {
|
|
@@ -6680,9 +7155,9 @@ var publicAgentVersionsContract = c12.router({
|
|
|
6680
7155
|
});
|
|
6681
7156
|
|
|
6682
7157
|
// ../../packages/core/src/contracts/public/runs.ts
|
|
6683
|
-
import { z as
|
|
6684
|
-
var
|
|
6685
|
-
var publicRunStatusSchema =
|
|
7158
|
+
import { z as z19 } from "zod";
|
|
7159
|
+
var c14 = initContract();
|
|
7160
|
+
var publicRunStatusSchema = z19.enum([
|
|
6686
7161
|
"pending",
|
|
6687
7162
|
"running",
|
|
6688
7163
|
"completed",
|
|
@@ -6690,56 +7165,56 @@ var publicRunStatusSchema = z18.enum([
|
|
|
6690
7165
|
"timeout",
|
|
6691
7166
|
"cancelled"
|
|
6692
7167
|
]);
|
|
6693
|
-
var publicRunSchema =
|
|
6694
|
-
id:
|
|
6695
|
-
agent_id:
|
|
6696
|
-
agent_name:
|
|
7168
|
+
var publicRunSchema = z19.object({
|
|
7169
|
+
id: z19.string(),
|
|
7170
|
+
agent_id: z19.string(),
|
|
7171
|
+
agent_name: z19.string(),
|
|
6697
7172
|
status: publicRunStatusSchema,
|
|
6698
|
-
prompt:
|
|
7173
|
+
prompt: z19.string(),
|
|
6699
7174
|
created_at: timestampSchema,
|
|
6700
7175
|
started_at: timestampSchema.nullable(),
|
|
6701
7176
|
completed_at: timestampSchema.nullable()
|
|
6702
7177
|
});
|
|
6703
7178
|
var publicRunDetailSchema = publicRunSchema.extend({
|
|
6704
|
-
error:
|
|
6705
|
-
execution_time_ms:
|
|
6706
|
-
checkpoint_id:
|
|
6707
|
-
session_id:
|
|
6708
|
-
artifact_name:
|
|
6709
|
-
artifact_version:
|
|
6710
|
-
volumes:
|
|
7179
|
+
error: z19.string().nullable(),
|
|
7180
|
+
execution_time_ms: z19.number().nullable(),
|
|
7181
|
+
checkpoint_id: z19.string().nullable(),
|
|
7182
|
+
session_id: z19.string().nullable(),
|
|
7183
|
+
artifact_name: z19.string().nullable(),
|
|
7184
|
+
artifact_version: z19.string().nullable(),
|
|
7185
|
+
volumes: z19.record(z19.string(), z19.string()).optional()
|
|
6711
7186
|
});
|
|
6712
7187
|
var paginatedRunsSchema = createPaginatedResponseSchema(publicRunSchema);
|
|
6713
|
-
var createRunRequestSchema =
|
|
7188
|
+
var createRunRequestSchema = z19.object({
|
|
6714
7189
|
// Agent identification (one of: agent, agent_id, session_id, checkpoint_id)
|
|
6715
|
-
agent:
|
|
7190
|
+
agent: z19.string().optional(),
|
|
6716
7191
|
// Agent name
|
|
6717
|
-
agent_id:
|
|
7192
|
+
agent_id: z19.string().optional(),
|
|
6718
7193
|
// Agent ID
|
|
6719
|
-
agent_version:
|
|
7194
|
+
agent_version: z19.string().optional(),
|
|
6720
7195
|
// Version specifier (e.g., "latest", "v1", specific ID)
|
|
6721
7196
|
// Continue session
|
|
6722
|
-
session_id:
|
|
7197
|
+
session_id: z19.string().optional(),
|
|
6723
7198
|
// Resume from checkpoint
|
|
6724
|
-
checkpoint_id:
|
|
7199
|
+
checkpoint_id: z19.string().optional(),
|
|
6725
7200
|
// Required
|
|
6726
|
-
prompt:
|
|
7201
|
+
prompt: z19.string().min(1, "Prompt is required"),
|
|
6727
7202
|
// Optional configuration
|
|
6728
|
-
variables:
|
|
6729
|
-
secrets:
|
|
6730
|
-
artifact_name:
|
|
7203
|
+
variables: z19.record(z19.string(), z19.string()).optional(),
|
|
7204
|
+
secrets: z19.record(z19.string(), z19.string()).optional(),
|
|
7205
|
+
artifact_name: z19.string().optional(),
|
|
6731
7206
|
// Artifact name to mount
|
|
6732
|
-
artifact_version:
|
|
7207
|
+
artifact_version: z19.string().optional(),
|
|
6733
7208
|
// Artifact version (defaults to latest)
|
|
6734
|
-
volumes:
|
|
7209
|
+
volumes: z19.record(z19.string(), z19.string()).optional()
|
|
6735
7210
|
// volume_name -> version
|
|
6736
7211
|
});
|
|
6737
7212
|
var runListQuerySchema = listQuerySchema.extend({
|
|
6738
|
-
agent_id:
|
|
7213
|
+
agent_id: z19.string().optional(),
|
|
6739
7214
|
status: publicRunStatusSchema.optional(),
|
|
6740
7215
|
since: timestampSchema.optional()
|
|
6741
7216
|
});
|
|
6742
|
-
var publicRunsListContract =
|
|
7217
|
+
var publicRunsListContract = c14.router({
|
|
6743
7218
|
list: {
|
|
6744
7219
|
method: "GET",
|
|
6745
7220
|
path: "/v1/runs",
|
|
@@ -6768,12 +7243,12 @@ var publicRunsListContract = c13.router({
|
|
|
6768
7243
|
description: "Create and execute a new agent run. Returns 202 Accepted as runs execute asynchronously."
|
|
6769
7244
|
}
|
|
6770
7245
|
});
|
|
6771
|
-
var publicRunByIdContract =
|
|
7246
|
+
var publicRunByIdContract = c14.router({
|
|
6772
7247
|
get: {
|
|
6773
7248
|
method: "GET",
|
|
6774
7249
|
path: "/v1/runs/:id",
|
|
6775
|
-
pathParams:
|
|
6776
|
-
id:
|
|
7250
|
+
pathParams: z19.object({
|
|
7251
|
+
id: z19.string().min(1, "Run ID is required")
|
|
6777
7252
|
}),
|
|
6778
7253
|
responses: {
|
|
6779
7254
|
200: publicRunDetailSchema,
|
|
@@ -6785,14 +7260,14 @@ var publicRunByIdContract = c13.router({
|
|
|
6785
7260
|
description: "Get run details by ID"
|
|
6786
7261
|
}
|
|
6787
7262
|
});
|
|
6788
|
-
var publicRunCancelContract =
|
|
7263
|
+
var publicRunCancelContract = c14.router({
|
|
6789
7264
|
cancel: {
|
|
6790
7265
|
method: "POST",
|
|
6791
7266
|
path: "/v1/runs/:id/cancel",
|
|
6792
|
-
pathParams:
|
|
6793
|
-
id:
|
|
7267
|
+
pathParams: z19.object({
|
|
7268
|
+
id: z19.string().min(1, "Run ID is required")
|
|
6794
7269
|
}),
|
|
6795
|
-
body:
|
|
7270
|
+
body: z19.undefined(),
|
|
6796
7271
|
responses: {
|
|
6797
7272
|
200: publicRunDetailSchema,
|
|
6798
7273
|
400: publicApiErrorSchema,
|
|
@@ -6805,26 +7280,26 @@ var publicRunCancelContract = c13.router({
|
|
|
6805
7280
|
description: "Cancel a pending or running execution"
|
|
6806
7281
|
}
|
|
6807
7282
|
});
|
|
6808
|
-
var logEntrySchema =
|
|
7283
|
+
var logEntrySchema = z19.object({
|
|
6809
7284
|
timestamp: timestampSchema,
|
|
6810
|
-
type:
|
|
6811
|
-
level:
|
|
6812
|
-
message:
|
|
6813
|
-
metadata:
|
|
7285
|
+
type: z19.enum(["agent", "system", "network"]),
|
|
7286
|
+
level: z19.enum(["debug", "info", "warn", "error"]),
|
|
7287
|
+
message: z19.string(),
|
|
7288
|
+
metadata: z19.record(z19.string(), z19.unknown()).optional()
|
|
6814
7289
|
});
|
|
6815
7290
|
var paginatedLogsSchema = createPaginatedResponseSchema(logEntrySchema);
|
|
6816
7291
|
var logsQuerySchema = listQuerySchema.extend({
|
|
6817
|
-
type:
|
|
7292
|
+
type: z19.enum(["agent", "system", "network", "all"]).default("all"),
|
|
6818
7293
|
since: timestampSchema.optional(),
|
|
6819
7294
|
until: timestampSchema.optional(),
|
|
6820
|
-
order:
|
|
7295
|
+
order: z19.enum(["asc", "desc"]).default("asc")
|
|
6821
7296
|
});
|
|
6822
|
-
var publicRunLogsContract =
|
|
7297
|
+
var publicRunLogsContract = c14.router({
|
|
6823
7298
|
getLogs: {
|
|
6824
7299
|
method: "GET",
|
|
6825
7300
|
path: "/v1/runs/:id/logs",
|
|
6826
|
-
pathParams:
|
|
6827
|
-
id:
|
|
7301
|
+
pathParams: z19.object({
|
|
7302
|
+
id: z19.string().min(1, "Run ID is required")
|
|
6828
7303
|
}),
|
|
6829
7304
|
query: logsQuerySchema,
|
|
6830
7305
|
responses: {
|
|
@@ -6837,29 +7312,29 @@ var publicRunLogsContract = c13.router({
|
|
|
6837
7312
|
description: "Get unified logs for a run. Combines agent, system, and network logs."
|
|
6838
7313
|
}
|
|
6839
7314
|
});
|
|
6840
|
-
var metricPointSchema =
|
|
7315
|
+
var metricPointSchema = z19.object({
|
|
6841
7316
|
timestamp: timestampSchema,
|
|
6842
|
-
cpu_percent:
|
|
6843
|
-
memory_used_mb:
|
|
6844
|
-
memory_total_mb:
|
|
6845
|
-
disk_used_mb:
|
|
6846
|
-
disk_total_mb:
|
|
6847
|
-
});
|
|
6848
|
-
var metricsSummarySchema =
|
|
6849
|
-
avg_cpu_percent:
|
|
6850
|
-
max_memory_used_mb:
|
|
6851
|
-
total_duration_ms:
|
|
6852
|
-
});
|
|
6853
|
-
var metricsResponseSchema2 =
|
|
6854
|
-
data:
|
|
7317
|
+
cpu_percent: z19.number(),
|
|
7318
|
+
memory_used_mb: z19.number(),
|
|
7319
|
+
memory_total_mb: z19.number(),
|
|
7320
|
+
disk_used_mb: z19.number(),
|
|
7321
|
+
disk_total_mb: z19.number()
|
|
7322
|
+
});
|
|
7323
|
+
var metricsSummarySchema = z19.object({
|
|
7324
|
+
avg_cpu_percent: z19.number(),
|
|
7325
|
+
max_memory_used_mb: z19.number(),
|
|
7326
|
+
total_duration_ms: z19.number().nullable()
|
|
7327
|
+
});
|
|
7328
|
+
var metricsResponseSchema2 = z19.object({
|
|
7329
|
+
data: z19.array(metricPointSchema),
|
|
6855
7330
|
summary: metricsSummarySchema
|
|
6856
7331
|
});
|
|
6857
|
-
var publicRunMetricsContract =
|
|
7332
|
+
var publicRunMetricsContract = c14.router({
|
|
6858
7333
|
getMetrics: {
|
|
6859
7334
|
method: "GET",
|
|
6860
7335
|
path: "/v1/runs/:id/metrics",
|
|
6861
|
-
pathParams:
|
|
6862
|
-
id:
|
|
7336
|
+
pathParams: z19.object({
|
|
7337
|
+
id: z19.string().min(1, "Run ID is required")
|
|
6863
7338
|
}),
|
|
6864
7339
|
responses: {
|
|
6865
7340
|
200: metricsResponseSchema2,
|
|
@@ -6871,7 +7346,7 @@ var publicRunMetricsContract = c13.router({
|
|
|
6871
7346
|
description: "Get CPU, memory, and disk metrics for a run"
|
|
6872
7347
|
}
|
|
6873
7348
|
});
|
|
6874
|
-
var sseEventTypeSchema =
|
|
7349
|
+
var sseEventTypeSchema = z19.enum([
|
|
6875
7350
|
"status",
|
|
6876
7351
|
// Run status change
|
|
6877
7352
|
"output",
|
|
@@ -6883,25 +7358,25 @@ var sseEventTypeSchema = z18.enum([
|
|
|
6883
7358
|
"heartbeat"
|
|
6884
7359
|
// Keep-alive
|
|
6885
7360
|
]);
|
|
6886
|
-
var sseEventSchema =
|
|
7361
|
+
var sseEventSchema = z19.object({
|
|
6887
7362
|
event: sseEventTypeSchema,
|
|
6888
|
-
data:
|
|
6889
|
-
id:
|
|
7363
|
+
data: z19.unknown(),
|
|
7364
|
+
id: z19.string().optional()
|
|
6890
7365
|
// For Last-Event-ID reconnection
|
|
6891
7366
|
});
|
|
6892
|
-
var publicRunEventsContract =
|
|
7367
|
+
var publicRunEventsContract = c14.router({
|
|
6893
7368
|
streamEvents: {
|
|
6894
7369
|
method: "GET",
|
|
6895
7370
|
path: "/v1/runs/:id/events",
|
|
6896
|
-
pathParams:
|
|
6897
|
-
id:
|
|
7371
|
+
pathParams: z19.object({
|
|
7372
|
+
id: z19.string().min(1, "Run ID is required")
|
|
6898
7373
|
}),
|
|
6899
|
-
query:
|
|
6900
|
-
last_event_id:
|
|
7374
|
+
query: z19.object({
|
|
7375
|
+
last_event_id: z19.string().optional()
|
|
6901
7376
|
// For reconnection
|
|
6902
7377
|
}),
|
|
6903
7378
|
responses: {
|
|
6904
|
-
200:
|
|
7379
|
+
200: z19.any(),
|
|
6905
7380
|
// SSE stream - actual content is text/event-stream
|
|
6906
7381
|
401: publicApiErrorSchema,
|
|
6907
7382
|
404: publicApiErrorSchema,
|
|
@@ -6913,28 +7388,28 @@ var publicRunEventsContract = c13.router({
|
|
|
6913
7388
|
});
|
|
6914
7389
|
|
|
6915
7390
|
// ../../packages/core/src/contracts/public/artifacts.ts
|
|
6916
|
-
import { z as
|
|
6917
|
-
var
|
|
6918
|
-
var publicArtifactSchema =
|
|
6919
|
-
id:
|
|
6920
|
-
name:
|
|
6921
|
-
current_version_id:
|
|
6922
|
-
size:
|
|
7391
|
+
import { z as z20 } from "zod";
|
|
7392
|
+
var c15 = initContract();
|
|
7393
|
+
var publicArtifactSchema = z20.object({
|
|
7394
|
+
id: z20.string(),
|
|
7395
|
+
name: z20.string(),
|
|
7396
|
+
current_version_id: z20.string().nullable(),
|
|
7397
|
+
size: z20.number(),
|
|
6923
7398
|
// Total size in bytes
|
|
6924
|
-
file_count:
|
|
7399
|
+
file_count: z20.number(),
|
|
6925
7400
|
created_at: timestampSchema,
|
|
6926
7401
|
updated_at: timestampSchema
|
|
6927
7402
|
});
|
|
6928
|
-
var artifactVersionSchema =
|
|
6929
|
-
id:
|
|
7403
|
+
var artifactVersionSchema = z20.object({
|
|
7404
|
+
id: z20.string(),
|
|
6930
7405
|
// SHA-256 content hash
|
|
6931
|
-
artifact_id:
|
|
6932
|
-
size:
|
|
7406
|
+
artifact_id: z20.string(),
|
|
7407
|
+
size: z20.number(),
|
|
6933
7408
|
// Size in bytes
|
|
6934
|
-
file_count:
|
|
6935
|
-
message:
|
|
7409
|
+
file_count: z20.number(),
|
|
7410
|
+
message: z20.string().nullable(),
|
|
6936
7411
|
// Optional commit message
|
|
6937
|
-
created_by:
|
|
7412
|
+
created_by: z20.string(),
|
|
6938
7413
|
created_at: timestampSchema
|
|
6939
7414
|
});
|
|
6940
7415
|
var publicArtifactDetailSchema = publicArtifactSchema.extend({
|
|
@@ -6944,7 +7419,7 @@ var paginatedArtifactsSchema = createPaginatedResponseSchema(publicArtifactSchem
|
|
|
6944
7419
|
var paginatedArtifactVersionsSchema = createPaginatedResponseSchema(
|
|
6945
7420
|
artifactVersionSchema
|
|
6946
7421
|
);
|
|
6947
|
-
var publicArtifactsListContract =
|
|
7422
|
+
var publicArtifactsListContract = c15.router({
|
|
6948
7423
|
list: {
|
|
6949
7424
|
method: "GET",
|
|
6950
7425
|
path: "/v1/artifacts",
|
|
@@ -6958,12 +7433,12 @@ var publicArtifactsListContract = c14.router({
|
|
|
6958
7433
|
description: "List all artifacts in the current scope with pagination"
|
|
6959
7434
|
}
|
|
6960
7435
|
});
|
|
6961
|
-
var publicArtifactByIdContract =
|
|
7436
|
+
var publicArtifactByIdContract = c15.router({
|
|
6962
7437
|
get: {
|
|
6963
7438
|
method: "GET",
|
|
6964
7439
|
path: "/v1/artifacts/:id",
|
|
6965
|
-
pathParams:
|
|
6966
|
-
id:
|
|
7440
|
+
pathParams: z20.object({
|
|
7441
|
+
id: z20.string().min(1, "Artifact ID is required")
|
|
6967
7442
|
}),
|
|
6968
7443
|
responses: {
|
|
6969
7444
|
200: publicArtifactDetailSchema,
|
|
@@ -6975,12 +7450,12 @@ var publicArtifactByIdContract = c14.router({
|
|
|
6975
7450
|
description: "Get artifact details by ID"
|
|
6976
7451
|
}
|
|
6977
7452
|
});
|
|
6978
|
-
var publicArtifactVersionsContract =
|
|
7453
|
+
var publicArtifactVersionsContract = c15.router({
|
|
6979
7454
|
list: {
|
|
6980
7455
|
method: "GET",
|
|
6981
7456
|
path: "/v1/artifacts/:id/versions",
|
|
6982
|
-
pathParams:
|
|
6983
|
-
id:
|
|
7457
|
+
pathParams: z20.object({
|
|
7458
|
+
id: z20.string().min(1, "Artifact ID is required")
|
|
6984
7459
|
}),
|
|
6985
7460
|
query: listQuerySchema,
|
|
6986
7461
|
responses: {
|
|
@@ -6993,19 +7468,19 @@ var publicArtifactVersionsContract = c14.router({
|
|
|
6993
7468
|
description: "List all versions of an artifact with pagination"
|
|
6994
7469
|
}
|
|
6995
7470
|
});
|
|
6996
|
-
var publicArtifactDownloadContract =
|
|
7471
|
+
var publicArtifactDownloadContract = c15.router({
|
|
6997
7472
|
download: {
|
|
6998
7473
|
method: "GET",
|
|
6999
7474
|
path: "/v1/artifacts/:id/download",
|
|
7000
|
-
pathParams:
|
|
7001
|
-
id:
|
|
7475
|
+
pathParams: z20.object({
|
|
7476
|
+
id: z20.string().min(1, "Artifact ID is required")
|
|
7002
7477
|
}),
|
|
7003
|
-
query:
|
|
7004
|
-
version_id:
|
|
7478
|
+
query: z20.object({
|
|
7479
|
+
version_id: z20.string().optional()
|
|
7005
7480
|
// Defaults to current version
|
|
7006
7481
|
}),
|
|
7007
7482
|
responses: {
|
|
7008
|
-
302:
|
|
7483
|
+
302: z20.undefined(),
|
|
7009
7484
|
// Redirect to presigned URL
|
|
7010
7485
|
401: publicApiErrorSchema,
|
|
7011
7486
|
404: publicApiErrorSchema,
|
|
@@ -7017,28 +7492,28 @@ var publicArtifactDownloadContract = c14.router({
|
|
|
7017
7492
|
});
|
|
7018
7493
|
|
|
7019
7494
|
// ../../packages/core/src/contracts/public/volumes.ts
|
|
7020
|
-
import { z as
|
|
7021
|
-
var
|
|
7022
|
-
var publicVolumeSchema =
|
|
7023
|
-
id:
|
|
7024
|
-
name:
|
|
7025
|
-
current_version_id:
|
|
7026
|
-
size:
|
|
7495
|
+
import { z as z21 } from "zod";
|
|
7496
|
+
var c16 = initContract();
|
|
7497
|
+
var publicVolumeSchema = z21.object({
|
|
7498
|
+
id: z21.string(),
|
|
7499
|
+
name: z21.string(),
|
|
7500
|
+
current_version_id: z21.string().nullable(),
|
|
7501
|
+
size: z21.number(),
|
|
7027
7502
|
// Total size in bytes
|
|
7028
|
-
file_count:
|
|
7503
|
+
file_count: z21.number(),
|
|
7029
7504
|
created_at: timestampSchema,
|
|
7030
7505
|
updated_at: timestampSchema
|
|
7031
7506
|
});
|
|
7032
|
-
var volumeVersionSchema =
|
|
7033
|
-
id:
|
|
7507
|
+
var volumeVersionSchema = z21.object({
|
|
7508
|
+
id: z21.string(),
|
|
7034
7509
|
// SHA-256 content hash
|
|
7035
|
-
volume_id:
|
|
7036
|
-
size:
|
|
7510
|
+
volume_id: z21.string(),
|
|
7511
|
+
size: z21.number(),
|
|
7037
7512
|
// Size in bytes
|
|
7038
|
-
file_count:
|
|
7039
|
-
message:
|
|
7513
|
+
file_count: z21.number(),
|
|
7514
|
+
message: z21.string().nullable(),
|
|
7040
7515
|
// Optional commit message
|
|
7041
|
-
created_by:
|
|
7516
|
+
created_by: z21.string(),
|
|
7042
7517
|
created_at: timestampSchema
|
|
7043
7518
|
});
|
|
7044
7519
|
var publicVolumeDetailSchema = publicVolumeSchema.extend({
|
|
@@ -7046,7 +7521,7 @@ var publicVolumeDetailSchema = publicVolumeSchema.extend({
|
|
|
7046
7521
|
});
|
|
7047
7522
|
var paginatedVolumesSchema = createPaginatedResponseSchema(publicVolumeSchema);
|
|
7048
7523
|
var paginatedVolumeVersionsSchema = createPaginatedResponseSchema(volumeVersionSchema);
|
|
7049
|
-
var publicVolumesListContract =
|
|
7524
|
+
var publicVolumesListContract = c16.router({
|
|
7050
7525
|
list: {
|
|
7051
7526
|
method: "GET",
|
|
7052
7527
|
path: "/v1/volumes",
|
|
@@ -7060,12 +7535,12 @@ var publicVolumesListContract = c15.router({
|
|
|
7060
7535
|
description: "List all volumes in the current scope with pagination"
|
|
7061
7536
|
}
|
|
7062
7537
|
});
|
|
7063
|
-
var publicVolumeByIdContract =
|
|
7538
|
+
var publicVolumeByIdContract = c16.router({
|
|
7064
7539
|
get: {
|
|
7065
7540
|
method: "GET",
|
|
7066
7541
|
path: "/v1/volumes/:id",
|
|
7067
|
-
pathParams:
|
|
7068
|
-
id:
|
|
7542
|
+
pathParams: z21.object({
|
|
7543
|
+
id: z21.string().min(1, "Volume ID is required")
|
|
7069
7544
|
}),
|
|
7070
7545
|
responses: {
|
|
7071
7546
|
200: publicVolumeDetailSchema,
|
|
@@ -7077,12 +7552,12 @@ var publicVolumeByIdContract = c15.router({
|
|
|
7077
7552
|
description: "Get volume details by ID"
|
|
7078
7553
|
}
|
|
7079
7554
|
});
|
|
7080
|
-
var publicVolumeVersionsContract =
|
|
7555
|
+
var publicVolumeVersionsContract = c16.router({
|
|
7081
7556
|
list: {
|
|
7082
7557
|
method: "GET",
|
|
7083
7558
|
path: "/v1/volumes/:id/versions",
|
|
7084
|
-
pathParams:
|
|
7085
|
-
id:
|
|
7559
|
+
pathParams: z21.object({
|
|
7560
|
+
id: z21.string().min(1, "Volume ID is required")
|
|
7086
7561
|
}),
|
|
7087
7562
|
query: listQuerySchema,
|
|
7088
7563
|
responses: {
|
|
@@ -7095,19 +7570,19 @@ var publicVolumeVersionsContract = c15.router({
|
|
|
7095
7570
|
description: "List all versions of a volume with pagination"
|
|
7096
7571
|
}
|
|
7097
7572
|
});
|
|
7098
|
-
var publicVolumeDownloadContract =
|
|
7573
|
+
var publicVolumeDownloadContract = c16.router({
|
|
7099
7574
|
download: {
|
|
7100
7575
|
method: "GET",
|
|
7101
7576
|
path: "/v1/volumes/:id/download",
|
|
7102
|
-
pathParams:
|
|
7103
|
-
id:
|
|
7577
|
+
pathParams: z21.object({
|
|
7578
|
+
id: z21.string().min(1, "Volume ID is required")
|
|
7104
7579
|
}),
|
|
7105
|
-
query:
|
|
7106
|
-
version_id:
|
|
7580
|
+
query: z21.object({
|
|
7581
|
+
version_id: z21.string().optional()
|
|
7107
7582
|
// Defaults to current version
|
|
7108
7583
|
}),
|
|
7109
7584
|
responses: {
|
|
7110
|
-
302:
|
|
7585
|
+
302: z21.undefined(),
|
|
7111
7586
|
// Redirect to presigned URL
|
|
7112
7587
|
401: publicApiErrorSchema,
|
|
7113
7588
|
404: publicApiErrorSchema,
|
|
@@ -7138,11 +7613,21 @@ var SCRIPT_PATHS = {
|
|
|
7138
7613
|
envLoader: "/usr/local/bin/vm0-agent/env-loader.mjs"
|
|
7139
7614
|
};
|
|
7140
7615
|
|
|
7616
|
+
// ../../packages/core/src/feature-switch.ts
|
|
7617
|
+
var PricingSwitch = {
|
|
7618
|
+
key: "pricing" /* Pricing */,
|
|
7619
|
+
maintainer: "ethan@vm0.ai",
|
|
7620
|
+
enabled: false
|
|
7621
|
+
};
|
|
7622
|
+
var FEATURE_SWITCHES = {
|
|
7623
|
+
["pricing" /* Pricing */]: PricingSwitch
|
|
7624
|
+
};
|
|
7625
|
+
|
|
7141
7626
|
// src/lib/scripts/index.ts
|
|
7142
7627
|
var ENV_LOADER_PATH = "/usr/local/bin/vm0-agent/env-loader.mjs";
|
|
7143
7628
|
|
|
7144
7629
|
// src/lib/proxy/vm-registry.ts
|
|
7145
|
-
import
|
|
7630
|
+
import fs5 from "fs";
|
|
7146
7631
|
var DEFAULT_REGISTRY_PATH = "/tmp/vm0-vm-registry.json";
|
|
7147
7632
|
var VMRegistry = class {
|
|
7148
7633
|
registryPath;
|
|
@@ -7156,8 +7641,8 @@ var VMRegistry = class {
|
|
|
7156
7641
|
*/
|
|
7157
7642
|
load() {
|
|
7158
7643
|
try {
|
|
7159
|
-
if (
|
|
7160
|
-
const content =
|
|
7644
|
+
if (fs5.existsSync(this.registryPath)) {
|
|
7645
|
+
const content = fs5.readFileSync(this.registryPath, "utf-8");
|
|
7161
7646
|
return JSON.parse(content);
|
|
7162
7647
|
}
|
|
7163
7648
|
} catch {
|
|
@@ -7171,8 +7656,8 @@ var VMRegistry = class {
|
|
|
7171
7656
|
this.data.updatedAt = Date.now();
|
|
7172
7657
|
const content = JSON.stringify(this.data, null, 2);
|
|
7173
7658
|
const tempPath = `${this.registryPath}.tmp`;
|
|
7174
|
-
|
|
7175
|
-
|
|
7659
|
+
fs5.writeFileSync(tempPath, content, { mode: 420 });
|
|
7660
|
+
fs5.renameSync(tempPath, this.registryPath);
|
|
7176
7661
|
}
|
|
7177
7662
|
/**
|
|
7178
7663
|
* Register a VM with its IP address
|
|
@@ -7247,8 +7732,8 @@ function initVMRegistry(registryPath) {
|
|
|
7247
7732
|
|
|
7248
7733
|
// src/lib/proxy/proxy-manager.ts
|
|
7249
7734
|
import { spawn as spawn2 } from "child_process";
|
|
7250
|
-
import
|
|
7251
|
-
import
|
|
7735
|
+
import fs6 from "fs";
|
|
7736
|
+
import path4 from "path";
|
|
7252
7737
|
|
|
7253
7738
|
// src/lib/proxy/mitm-addon-script.ts
|
|
7254
7739
|
var RUNNER_MITM_ADDON_SCRIPT = `#!/usr/bin/env python3
|
|
@@ -7765,11 +8250,11 @@ var ProxyManager = class {
|
|
|
7765
8250
|
* Ensure the addon script exists at the configured path
|
|
7766
8251
|
*/
|
|
7767
8252
|
ensureAddonScript() {
|
|
7768
|
-
const addonDir =
|
|
7769
|
-
if (!
|
|
7770
|
-
|
|
8253
|
+
const addonDir = path4.dirname(this.config.addonPath);
|
|
8254
|
+
if (!fs6.existsSync(addonDir)) {
|
|
8255
|
+
fs6.mkdirSync(addonDir, { recursive: true });
|
|
7771
8256
|
}
|
|
7772
|
-
|
|
8257
|
+
fs6.writeFileSync(this.config.addonPath, RUNNER_MITM_ADDON_SCRIPT, {
|
|
7773
8258
|
mode: 493
|
|
7774
8259
|
});
|
|
7775
8260
|
console.log(
|
|
@@ -7780,11 +8265,11 @@ var ProxyManager = class {
|
|
|
7780
8265
|
* Validate proxy configuration
|
|
7781
8266
|
*/
|
|
7782
8267
|
validateConfig() {
|
|
7783
|
-
if (!
|
|
8268
|
+
if (!fs6.existsSync(this.config.caDir)) {
|
|
7784
8269
|
throw new Error(`Proxy CA directory not found: ${this.config.caDir}`);
|
|
7785
8270
|
}
|
|
7786
|
-
const caCertPath =
|
|
7787
|
-
if (!
|
|
8271
|
+
const caCertPath = path4.join(this.config.caDir, "mitmproxy-ca.pem");
|
|
8272
|
+
if (!fs6.existsSync(caCertPath)) {
|
|
7788
8273
|
throw new Error(`Proxy CA certificate not found: ${caCertPath}`);
|
|
7789
8274
|
}
|
|
7790
8275
|
this.ensureAddonScript();
|
|
@@ -8142,7 +8627,7 @@ function buildEnvironmentVariables(context, apiUrl) {
|
|
|
8142
8627
|
envVars.VERCEL_PROTECTION_BYPASS = vercelBypass;
|
|
8143
8628
|
}
|
|
8144
8629
|
const useMockClaude = process.env.USE_MOCK_CLAUDE;
|
|
8145
|
-
if (useMockClaude) {
|
|
8630
|
+
if (useMockClaude && !context.debugNoMockClaude) {
|
|
8146
8631
|
envVars.USE_MOCK_CLAUDE = useMockClaude;
|
|
8147
8632
|
}
|
|
8148
8633
|
if (context.storageManifest?.artifact) {
|
|
@@ -8173,17 +8658,17 @@ function buildEnvironmentVariables(context, apiUrl) {
|
|
|
8173
8658
|
}
|
|
8174
8659
|
|
|
8175
8660
|
// src/lib/network-logs/network-logs.ts
|
|
8176
|
-
import
|
|
8661
|
+
import fs7 from "fs";
|
|
8177
8662
|
function getNetworkLogPath(runId) {
|
|
8178
8663
|
return `/tmp/vm0-network-${runId}.jsonl`;
|
|
8179
8664
|
}
|
|
8180
8665
|
function readNetworkLogs(runId) {
|
|
8181
8666
|
const logPath = getNetworkLogPath(runId);
|
|
8182
|
-
if (!
|
|
8667
|
+
if (!fs7.existsSync(logPath)) {
|
|
8183
8668
|
return [];
|
|
8184
8669
|
}
|
|
8185
8670
|
try {
|
|
8186
|
-
const content =
|
|
8671
|
+
const content = fs7.readFileSync(logPath, "utf-8");
|
|
8187
8672
|
const lines = content.split("\n").filter((line) => line.trim());
|
|
8188
8673
|
return lines.map((line) => JSON.parse(line));
|
|
8189
8674
|
} catch (err) {
|
|
@@ -8196,8 +8681,8 @@ function readNetworkLogs(runId) {
|
|
|
8196
8681
|
function cleanupNetworkLogs(runId) {
|
|
8197
8682
|
const logPath = getNetworkLogPath(runId);
|
|
8198
8683
|
try {
|
|
8199
|
-
if (
|
|
8200
|
-
|
|
8684
|
+
if (fs7.existsSync(logPath)) {
|
|
8685
|
+
fs7.unlinkSync(logPath);
|
|
8201
8686
|
}
|
|
8202
8687
|
} catch (err) {
|
|
8203
8688
|
console.error(
|
|
@@ -8240,7 +8725,7 @@ async function uploadNetworkLogs(apiUrl, sandboxToken, runId) {
|
|
|
8240
8725
|
}
|
|
8241
8726
|
|
|
8242
8727
|
// src/lib/vm-setup/vm-setup.ts
|
|
8243
|
-
import
|
|
8728
|
+
import fs8 from "fs";
|
|
8244
8729
|
|
|
8245
8730
|
// src/lib/scripts/utils.ts
|
|
8246
8731
|
function getAllScripts() {
|
|
@@ -8302,12 +8787,12 @@ async function restoreSessionHistory(ssh, resumeSession, workingDir, cliAgentTyp
|
|
|
8302
8787
|
);
|
|
8303
8788
|
}
|
|
8304
8789
|
async function installProxyCA(ssh) {
|
|
8305
|
-
if (!
|
|
8790
|
+
if (!fs8.existsSync(PROXY_CA_CERT_PATH)) {
|
|
8306
8791
|
throw new Error(
|
|
8307
8792
|
`Proxy CA certificate not found at ${PROXY_CA_CERT_PATH}. Run generate-proxy-ca.sh first.`
|
|
8308
8793
|
);
|
|
8309
8794
|
}
|
|
8310
|
-
const caCert =
|
|
8795
|
+
const caCert = fs8.readFileSync(PROXY_CA_CERT_PATH, "utf-8");
|
|
8311
8796
|
console.log(
|
|
8312
8797
|
`[Executor] Installing proxy CA certificate (${caCert.length} bytes)`
|
|
8313
8798
|
);
|
|
@@ -8388,7 +8873,7 @@ async function executeJob(context, config, options = {}) {
|
|
|
8388
8873
|
const log = options.logger ?? ((msg) => console.log(msg));
|
|
8389
8874
|
log(`[Executor] Starting job ${context.runId} in VM ${vmId}`);
|
|
8390
8875
|
try {
|
|
8391
|
-
const workspacesDir =
|
|
8876
|
+
const workspacesDir = path5.join(process.cwd(), "workspaces");
|
|
8392
8877
|
const vmConfig = {
|
|
8393
8878
|
vmId,
|
|
8394
8879
|
vcpus: config.sandbox.vcpu,
|
|
@@ -8396,7 +8881,7 @@ async function executeJob(context, config, options = {}) {
|
|
|
8396
8881
|
kernelPath: config.firecracker.kernel,
|
|
8397
8882
|
rootfsPath: config.firecracker.rootfs,
|
|
8398
8883
|
firecrackerBinary: config.firecracker.binary,
|
|
8399
|
-
workDir:
|
|
8884
|
+
workDir: path5.join(workspacesDir, `vm0-${vmId}`)
|
|
8400
8885
|
};
|
|
8401
8886
|
log(`[Executor] Creating VM ${vmId}...`);
|
|
8402
8887
|
vm = new FirecrackerVM(vmConfig);
|
|
@@ -8421,7 +8906,7 @@ async function executeJob(context, config, options = {}) {
|
|
|
8421
8906
|
log(
|
|
8422
8907
|
`[Executor] Setting up network security for VM ${guestIp} (mitm=${mitmEnabled}, sealSecrets=${sealSecretsEnabled})`
|
|
8423
8908
|
);
|
|
8424
|
-
await setupVMProxyRules(guestIp, config.proxy.port);
|
|
8909
|
+
await setupVMProxyRules(guestIp, config.proxy.port, config.name);
|
|
8425
8910
|
getVMRegistry().register(guestIp, context.runId, context.sandboxToken, {
|
|
8426
8911
|
firewallRules: firewallConfig?.rules,
|
|
8427
8912
|
mitmEnabled,
|
|
@@ -8592,7 +9077,7 @@ async function executeJob(context, config, options = {}) {
|
|
|
8592
9077
|
if (context.experimentalFirewall?.enabled && guestIp) {
|
|
8593
9078
|
log(`[Executor] Cleaning up network security for VM ${guestIp}`);
|
|
8594
9079
|
try {
|
|
8595
|
-
await removeVMProxyRules(guestIp, config.proxy.port);
|
|
9080
|
+
await removeVMProxyRules(guestIp, config.proxy.port, config.name);
|
|
8596
9081
|
} catch (err) {
|
|
8597
9082
|
console.error(
|
|
8598
9083
|
`[Executor] Failed to remove VM proxy rules: ${err instanceof Error ? err.message : "Unknown error"}`
|
|
@@ -8631,7 +9116,7 @@ function writeStatusFile(statusFilePath, mode, startedAt) {
|
|
|
8631
9116
|
updated_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
8632
9117
|
};
|
|
8633
9118
|
try {
|
|
8634
|
-
|
|
9119
|
+
writeFileSync2(statusFilePath, JSON.stringify(status, null, 2));
|
|
8635
9120
|
} catch (err) {
|
|
8636
9121
|
console.error(
|
|
8637
9122
|
`Failed to write status file: ${err instanceof Error ? err.message : "Unknown error"}`
|
|
@@ -8684,6 +9169,12 @@ var startCommand = new Command("start").description("Start the runner").option("
|
|
|
8684
9169
|
}
|
|
8685
9170
|
console.log("Setting up network bridge...");
|
|
8686
9171
|
await setupBridge();
|
|
9172
|
+
console.log("Flushing bridge ARP cache...");
|
|
9173
|
+
await flushBridgeArpCache();
|
|
9174
|
+
console.log("Cleaning up orphaned proxy rules...");
|
|
9175
|
+
await cleanupOrphanedProxyRules(config.name);
|
|
9176
|
+
console.log("Cleaning up orphaned IP allocations...");
|
|
9177
|
+
await cleanupOrphanedAllocations();
|
|
8687
9178
|
console.log("Initializing network proxy...");
|
|
8688
9179
|
initVMRegistry();
|
|
8689
9180
|
const proxyManager = initProxyManager({
|
|
@@ -8703,7 +9194,7 @@ var startCommand = new Command("start").description("Start the runner").option("
|
|
|
8703
9194
|
"Jobs with experimentalFirewall enabled will run without network interception"
|
|
8704
9195
|
);
|
|
8705
9196
|
}
|
|
8706
|
-
const statusFilePath =
|
|
9197
|
+
const statusFilePath = join2(dirname(options.config), "status.json");
|
|
8707
9198
|
const startedAt = /* @__PURE__ */ new Date();
|
|
8708
9199
|
const state = { mode: "running" };
|
|
8709
9200
|
const updateStatus = () => {
|
|
@@ -8833,32 +9324,493 @@ var startCommand = new Command("start").description("Start the runner").option("
|
|
|
8833
9324
|
}
|
|
8834
9325
|
});
|
|
8835
9326
|
|
|
8836
|
-
// src/commands/
|
|
9327
|
+
// src/commands/doctor.ts
|
|
8837
9328
|
import { Command as Command2 } from "commander";
|
|
8838
|
-
|
|
9329
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3, readdirSync as readdirSync2 } from "fs";
|
|
9330
|
+
import { dirname as dirname2, join as join3 } from "path";
|
|
9331
|
+
|
|
9332
|
+
// src/lib/firecracker/process.ts
|
|
9333
|
+
import { readdirSync, readFileSync as readFileSync2, existsSync as existsSync2 } from "fs";
|
|
9334
|
+
import path6 from "path";
|
|
9335
|
+
function parseFirecrackerCmdline(cmdline) {
|
|
9336
|
+
const args = cmdline.split("\0");
|
|
9337
|
+
if (!args[0]?.includes("firecracker")) return null;
|
|
9338
|
+
const sockIdx = args.indexOf("--api-sock");
|
|
9339
|
+
const socketPath = args[sockIdx + 1];
|
|
9340
|
+
if (sockIdx === -1 || !socketPath) return null;
|
|
9341
|
+
const match = socketPath.match(/vm0-([a-f0-9]+)\/firecracker\.sock$/);
|
|
9342
|
+
if (!match?.[1]) return null;
|
|
9343
|
+
return { vmId: match[1], socketPath };
|
|
9344
|
+
}
|
|
9345
|
+
function parseMitmproxyCmdline(cmdline) {
|
|
9346
|
+
if (!cmdline.includes("mitmproxy") && !cmdline.includes("mitmdump")) {
|
|
9347
|
+
return null;
|
|
9348
|
+
}
|
|
9349
|
+
const args = cmdline.split("\0");
|
|
9350
|
+
const portIdx = args.findIndex((a) => a === "-p" || a === "--listen-port");
|
|
9351
|
+
const portArg = args[portIdx + 1];
|
|
9352
|
+
const port = portIdx !== -1 && portArg ? parseInt(portArg, 10) : void 0;
|
|
9353
|
+
return { port };
|
|
9354
|
+
}
|
|
9355
|
+
function findFirecrackerProcesses() {
|
|
9356
|
+
const processes = [];
|
|
9357
|
+
const procDir = "/proc";
|
|
9358
|
+
let entries;
|
|
9359
|
+
try {
|
|
9360
|
+
entries = readdirSync(procDir);
|
|
9361
|
+
} catch {
|
|
9362
|
+
return [];
|
|
9363
|
+
}
|
|
9364
|
+
for (const entry of entries) {
|
|
9365
|
+
if (!/^\d+$/.test(entry)) continue;
|
|
9366
|
+
const pid = parseInt(entry, 10);
|
|
9367
|
+
const cmdlinePath = path6.join(procDir, entry, "cmdline");
|
|
9368
|
+
if (!existsSync2(cmdlinePath)) continue;
|
|
9369
|
+
try {
|
|
9370
|
+
const cmdline = readFileSync2(cmdlinePath, "utf-8");
|
|
9371
|
+
const parsed = parseFirecrackerCmdline(cmdline);
|
|
9372
|
+
if (parsed) {
|
|
9373
|
+
processes.push({ pid, ...parsed });
|
|
9374
|
+
}
|
|
9375
|
+
} catch {
|
|
9376
|
+
continue;
|
|
9377
|
+
}
|
|
9378
|
+
}
|
|
9379
|
+
return processes;
|
|
9380
|
+
}
|
|
9381
|
+
function findProcessByVmId(vmId) {
|
|
9382
|
+
const processes = findFirecrackerProcesses();
|
|
9383
|
+
return processes.find((p) => p.vmId === vmId) || null;
|
|
9384
|
+
}
|
|
9385
|
+
function isProcessRunning(pid) {
|
|
9386
|
+
try {
|
|
9387
|
+
process.kill(pid, 0);
|
|
9388
|
+
return true;
|
|
9389
|
+
} catch {
|
|
9390
|
+
return false;
|
|
9391
|
+
}
|
|
9392
|
+
}
|
|
9393
|
+
async function killProcess(pid, timeoutMs = 5e3) {
|
|
9394
|
+
if (!isProcessRunning(pid)) return true;
|
|
9395
|
+
try {
|
|
9396
|
+
process.kill(pid, "SIGTERM");
|
|
9397
|
+
} catch {
|
|
9398
|
+
return !isProcessRunning(pid);
|
|
9399
|
+
}
|
|
9400
|
+
const startTime = Date.now();
|
|
9401
|
+
while (Date.now() - startTime < timeoutMs) {
|
|
9402
|
+
if (!isProcessRunning(pid)) return true;
|
|
9403
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
9404
|
+
}
|
|
9405
|
+
if (isProcessRunning(pid)) {
|
|
9406
|
+
try {
|
|
9407
|
+
process.kill(pid, "SIGKILL");
|
|
9408
|
+
} catch {
|
|
9409
|
+
}
|
|
9410
|
+
}
|
|
9411
|
+
return !isProcessRunning(pid);
|
|
9412
|
+
}
|
|
9413
|
+
function findMitmproxyProcess() {
|
|
9414
|
+
const procDir = "/proc";
|
|
9415
|
+
let entries;
|
|
9416
|
+
try {
|
|
9417
|
+
entries = readdirSync(procDir);
|
|
9418
|
+
} catch {
|
|
9419
|
+
return null;
|
|
9420
|
+
}
|
|
9421
|
+
for (const entry of entries) {
|
|
9422
|
+
if (!/^\d+$/.test(entry)) continue;
|
|
9423
|
+
const pid = parseInt(entry, 10);
|
|
9424
|
+
const cmdlinePath = path6.join(procDir, entry, "cmdline");
|
|
9425
|
+
if (!existsSync2(cmdlinePath)) continue;
|
|
9426
|
+
try {
|
|
9427
|
+
const cmdline = readFileSync2(cmdlinePath, "utf-8");
|
|
9428
|
+
const parsed = parseMitmproxyCmdline(cmdline);
|
|
9429
|
+
if (parsed) {
|
|
9430
|
+
return { pid, port: parsed.port };
|
|
9431
|
+
}
|
|
9432
|
+
} catch {
|
|
9433
|
+
continue;
|
|
9434
|
+
}
|
|
9435
|
+
}
|
|
9436
|
+
return null;
|
|
9437
|
+
}
|
|
9438
|
+
|
|
9439
|
+
// src/commands/doctor.ts
|
|
9440
|
+
var doctorCommand = new Command2("doctor").description("Diagnose runner health, check network, and detect issues").option("--config <path>", "Config file path", "./runner.yaml").action(async (options) => {
|
|
8839
9441
|
try {
|
|
8840
9442
|
const config = loadConfig(options.config);
|
|
8841
|
-
|
|
8842
|
-
|
|
8843
|
-
|
|
9443
|
+
const configDir = dirname2(options.config);
|
|
9444
|
+
const statusFilePath = join3(configDir, "status.json");
|
|
9445
|
+
const workspacesDir = join3(configDir, "workspaces");
|
|
9446
|
+
console.log(`Runner: ${config.name}`);
|
|
9447
|
+
let status = null;
|
|
9448
|
+
if (existsSync3(statusFilePath)) {
|
|
9449
|
+
try {
|
|
9450
|
+
status = JSON.parse(
|
|
9451
|
+
readFileSync3(statusFilePath, "utf-8")
|
|
9452
|
+
);
|
|
9453
|
+
console.log(`Mode: ${status.mode}`);
|
|
9454
|
+
if (status.started_at) {
|
|
9455
|
+
const started = new Date(status.started_at);
|
|
9456
|
+
const uptime = formatUptime(Date.now() - started.getTime());
|
|
9457
|
+
console.log(
|
|
9458
|
+
`Started: ${started.toLocaleString()} (uptime: ${uptime})`
|
|
9459
|
+
);
|
|
9460
|
+
}
|
|
9461
|
+
} catch {
|
|
9462
|
+
console.log("Mode: unknown (status.json unreadable)");
|
|
9463
|
+
}
|
|
9464
|
+
} else {
|
|
9465
|
+
console.log("Mode: unknown (no status.json)");
|
|
9466
|
+
}
|
|
8844
9467
|
console.log("");
|
|
8845
|
-
console.log("
|
|
8846
|
-
|
|
8847
|
-
|
|
8848
|
-
|
|
8849
|
-
|
|
9468
|
+
console.log("API Connectivity:");
|
|
9469
|
+
try {
|
|
9470
|
+
await pollForJob(config.server, config.group);
|
|
9471
|
+
console.log(` \u2713 Connected to ${config.server.url}`);
|
|
9472
|
+
console.log(" \u2713 Authentication: OK");
|
|
9473
|
+
} catch (error) {
|
|
9474
|
+
console.log(` \u2717 Cannot connect to ${config.server.url}`);
|
|
9475
|
+
console.log(
|
|
9476
|
+
` Error: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
9477
|
+
);
|
|
9478
|
+
}
|
|
9479
|
+
console.log("");
|
|
9480
|
+
console.log("Network:");
|
|
9481
|
+
const warnings = [];
|
|
9482
|
+
const bridgeStatus = await checkBridgeStatus();
|
|
9483
|
+
if (bridgeStatus.exists) {
|
|
9484
|
+
console.log(` \u2713 Bridge ${BRIDGE_NAME2} (${bridgeStatus.ip})`);
|
|
9485
|
+
} else {
|
|
9486
|
+
console.log(` \u2717 Bridge ${BRIDGE_NAME2} not found`);
|
|
9487
|
+
warnings.push({
|
|
9488
|
+
message: `Network bridge ${BRIDGE_NAME2} does not exist`
|
|
9489
|
+
});
|
|
9490
|
+
}
|
|
9491
|
+
const proxyPort = config.proxy.port;
|
|
9492
|
+
const mitmProc = findMitmproxyProcess();
|
|
9493
|
+
const portInUse = await isPortInUse(proxyPort);
|
|
9494
|
+
if (mitmProc) {
|
|
9495
|
+
console.log(
|
|
9496
|
+
` \u2713 Proxy mitmproxy (PID ${mitmProc.pid}) on :${proxyPort}`
|
|
9497
|
+
);
|
|
9498
|
+
} else if (portInUse) {
|
|
9499
|
+
console.log(
|
|
9500
|
+
` \u26A0\uFE0F Proxy port :${proxyPort} in use but mitmproxy process not found`
|
|
9501
|
+
);
|
|
9502
|
+
warnings.push({
|
|
9503
|
+
message: `Port ${proxyPort} is in use but mitmproxy process not detected`
|
|
9504
|
+
});
|
|
9505
|
+
} else {
|
|
9506
|
+
console.log(` \u2717 Proxy mitmproxy not running`);
|
|
9507
|
+
warnings.push({ message: "Proxy mitmproxy is not running" });
|
|
9508
|
+
}
|
|
9509
|
+
console.log("");
|
|
9510
|
+
const processes = findFirecrackerProcesses();
|
|
9511
|
+
const tapDevices = await listTapDevices();
|
|
9512
|
+
const workspaces = existsSync3(workspacesDir) ? readdirSync2(workspacesDir).filter((d) => d.startsWith("vm0-")) : [];
|
|
9513
|
+
const jobs = [];
|
|
9514
|
+
const statusVmIds = /* @__PURE__ */ new Set();
|
|
9515
|
+
const allocations = getAllocations();
|
|
9516
|
+
if (status?.active_job_ids) {
|
|
9517
|
+
for (const runId of status.active_job_ids) {
|
|
9518
|
+
const vmId = runId.split("-")[0];
|
|
9519
|
+
if (!vmId) continue;
|
|
9520
|
+
statusVmIds.add(vmId);
|
|
9521
|
+
const proc = processes.find((p) => p.vmId === vmId);
|
|
9522
|
+
const ip = getIPForVm(vmId) ?? "not allocated";
|
|
9523
|
+
jobs.push({
|
|
9524
|
+
runId,
|
|
9525
|
+
vmId,
|
|
9526
|
+
ip,
|
|
9527
|
+
hasProcess: !!proc,
|
|
9528
|
+
pid: proc?.pid
|
|
9529
|
+
});
|
|
9530
|
+
}
|
|
9531
|
+
}
|
|
9532
|
+
const ipToVmIds = /* @__PURE__ */ new Map();
|
|
9533
|
+
for (const [ip, allocation] of allocations) {
|
|
9534
|
+
const existing = ipToVmIds.get(ip) ?? [];
|
|
9535
|
+
existing.push(allocation.vmId);
|
|
9536
|
+
ipToVmIds.set(ip, existing);
|
|
9537
|
+
}
|
|
9538
|
+
const maxConcurrent = config.sandbox.max_concurrent;
|
|
9539
|
+
console.log(`Jobs (${jobs.length} active, max ${maxConcurrent}):`);
|
|
9540
|
+
if (jobs.length === 0) {
|
|
9541
|
+
console.log(" No active jobs");
|
|
9542
|
+
} else {
|
|
9543
|
+
console.log(" ID VM ID IP Status");
|
|
9544
|
+
for (const job of jobs) {
|
|
9545
|
+
const shortId = job.runId.substring(0, 12) + "...";
|
|
9546
|
+
const ipConflict = (ipToVmIds.get(job.ip)?.length ?? 0) > 1;
|
|
9547
|
+
let statusText;
|
|
9548
|
+
if (ipConflict) {
|
|
9549
|
+
statusText = "\u26A0\uFE0F IP conflict!";
|
|
9550
|
+
} else if (job.hasProcess) {
|
|
9551
|
+
statusText = `\u2713 Running (PID ${job.pid})`;
|
|
9552
|
+
} else {
|
|
9553
|
+
statusText = "\u26A0\uFE0F No process";
|
|
9554
|
+
}
|
|
9555
|
+
console.log(
|
|
9556
|
+
` ${shortId} ${job.vmId} ${job.ip.padEnd(15)} ${statusText}`
|
|
9557
|
+
);
|
|
9558
|
+
}
|
|
9559
|
+
}
|
|
9560
|
+
console.log("");
|
|
9561
|
+
for (const job of jobs) {
|
|
9562
|
+
if (!job.hasProcess) {
|
|
9563
|
+
warnings.push({
|
|
9564
|
+
message: `Job ${job.vmId} in status.json but no Firecracker process running`
|
|
9565
|
+
});
|
|
9566
|
+
}
|
|
9567
|
+
}
|
|
9568
|
+
for (const [ip, vmIds] of ipToVmIds) {
|
|
9569
|
+
if (vmIds.length > 1) {
|
|
9570
|
+
warnings.push({
|
|
9571
|
+
message: `IP conflict: ${ip} assigned to ${vmIds.join(", ")}`
|
|
9572
|
+
});
|
|
9573
|
+
}
|
|
9574
|
+
}
|
|
9575
|
+
const processVmIds = new Set(processes.map((p) => p.vmId));
|
|
9576
|
+
for (const proc of processes) {
|
|
9577
|
+
if (!statusVmIds.has(proc.vmId)) {
|
|
9578
|
+
warnings.push({
|
|
9579
|
+
message: `Orphan process: PID ${proc.pid} (vmId ${proc.vmId}) not in status.json`
|
|
9580
|
+
});
|
|
9581
|
+
}
|
|
9582
|
+
}
|
|
9583
|
+
for (const tap of tapDevices) {
|
|
9584
|
+
const vmId = tap.replace("tap", "");
|
|
9585
|
+
if (!processVmIds.has(vmId) && !statusVmIds.has(vmId)) {
|
|
9586
|
+
warnings.push({
|
|
9587
|
+
message: `Orphan TAP device: ${tap} (no matching job or process)`
|
|
9588
|
+
});
|
|
9589
|
+
}
|
|
9590
|
+
}
|
|
9591
|
+
for (const ws of workspaces) {
|
|
9592
|
+
const vmId = ws.replace("vm0-", "");
|
|
9593
|
+
if (!processVmIds.has(vmId) && !statusVmIds.has(vmId)) {
|
|
9594
|
+
warnings.push({
|
|
9595
|
+
message: `Orphan workspace: ${ws} (no matching job or process)`
|
|
9596
|
+
});
|
|
9597
|
+
}
|
|
9598
|
+
}
|
|
9599
|
+
const activeVmIps = new Set(jobs.map((j) => j.ip));
|
|
9600
|
+
const iptablesRules = await listIptablesNatRules();
|
|
9601
|
+
const orphanedIptables = await findOrphanedIptablesRules(
|
|
9602
|
+
iptablesRules,
|
|
9603
|
+
activeVmIps,
|
|
9604
|
+
proxyPort
|
|
9605
|
+
);
|
|
9606
|
+
for (const rule of orphanedIptables) {
|
|
9607
|
+
warnings.push({
|
|
9608
|
+
message: `Orphan iptables rule: redirect ${rule.sourceIp}:${rule.destPort} -> :${rule.redirectPort}`
|
|
9609
|
+
});
|
|
9610
|
+
}
|
|
9611
|
+
console.log("Warnings:");
|
|
9612
|
+
if (warnings.length === 0) {
|
|
9613
|
+
console.log(" None");
|
|
9614
|
+
} else {
|
|
9615
|
+
for (const w of warnings) {
|
|
9616
|
+
console.log(` - ${w.message}`);
|
|
9617
|
+
}
|
|
9618
|
+
}
|
|
9619
|
+
process.exit(warnings.length > 0 ? 1 : 0);
|
|
8850
9620
|
} catch (error) {
|
|
8851
|
-
console.error("");
|
|
8852
|
-
console.error("\u2717 Runner cannot connect to API");
|
|
8853
9621
|
console.error(
|
|
8854
|
-
`
|
|
9622
|
+
`Error: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
8855
9623
|
);
|
|
8856
9624
|
process.exit(1);
|
|
8857
9625
|
}
|
|
8858
9626
|
});
|
|
9627
|
+
function formatUptime(ms) {
|
|
9628
|
+
const seconds = Math.floor(ms / 1e3);
|
|
9629
|
+
const minutes = Math.floor(seconds / 60);
|
|
9630
|
+
const hours = Math.floor(minutes / 60);
|
|
9631
|
+
const days = Math.floor(hours / 24);
|
|
9632
|
+
if (days > 0) return `${days}d ${hours % 24}h`;
|
|
9633
|
+
if (hours > 0) return `${hours}h ${minutes % 60}m`;
|
|
9634
|
+
if (minutes > 0) return `${minutes}m`;
|
|
9635
|
+
return `${seconds}s`;
|
|
9636
|
+
}
|
|
8859
9637
|
|
|
8860
|
-
// src/commands/
|
|
9638
|
+
// src/commands/kill.ts
|
|
8861
9639
|
import { Command as Command3 } from "commander";
|
|
9640
|
+
import { existsSync as existsSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync3, rmSync } from "fs";
|
|
9641
|
+
import { dirname as dirname3, join as join4 } from "path";
|
|
9642
|
+
import * as readline2 from "readline";
|
|
9643
|
+
var killCommand = new Command3("kill").description("Force terminate a job and clean up all resources").argument("<job-id>", "Job ID (full runId UUID or short 8-char vmId)").option("--config <path>", "Config file path", "./runner.yaml").option("--force", "Skip confirmation prompt").action(
|
|
9644
|
+
async (jobId, options) => {
|
|
9645
|
+
try {
|
|
9646
|
+
loadConfig(options.config);
|
|
9647
|
+
const configDir = dirname3(options.config);
|
|
9648
|
+
const statusFilePath = join4(configDir, "status.json");
|
|
9649
|
+
const workspacesDir = join4(configDir, "workspaces");
|
|
9650
|
+
const { vmId, runId } = resolveJobId(jobId, statusFilePath);
|
|
9651
|
+
console.log(`Killing job ${vmId}...`);
|
|
9652
|
+
const proc = findProcessByVmId(vmId);
|
|
9653
|
+
const tapDevice = `tap${vmId}`;
|
|
9654
|
+
const workspaceDir = join4(workspacesDir, `vm0-${vmId}`);
|
|
9655
|
+
console.log("");
|
|
9656
|
+
console.log("Resources to clean up:");
|
|
9657
|
+
if (proc) {
|
|
9658
|
+
console.log(` - Firecracker process (PID ${proc.pid})`);
|
|
9659
|
+
} else {
|
|
9660
|
+
console.log(" - Firecracker process: not found");
|
|
9661
|
+
}
|
|
9662
|
+
console.log(` - TAP device: ${tapDevice}`);
|
|
9663
|
+
console.log(` - Workspace: ${workspaceDir}`);
|
|
9664
|
+
if (runId) {
|
|
9665
|
+
console.log(` - status.json entry: ${runId.substring(0, 12)}...`);
|
|
9666
|
+
}
|
|
9667
|
+
console.log("");
|
|
9668
|
+
if (!options.force) {
|
|
9669
|
+
const confirmed = await confirm("Proceed with cleanup?");
|
|
9670
|
+
if (!confirmed) {
|
|
9671
|
+
console.log("Aborted.");
|
|
9672
|
+
process.exit(0);
|
|
9673
|
+
}
|
|
9674
|
+
}
|
|
9675
|
+
const results = [];
|
|
9676
|
+
if (proc) {
|
|
9677
|
+
const killed = await killProcess(proc.pid);
|
|
9678
|
+
results.push({
|
|
9679
|
+
step: "Firecracker process",
|
|
9680
|
+
success: killed,
|
|
9681
|
+
message: killed ? `PID ${proc.pid} terminated` : `Failed to kill PID ${proc.pid}`
|
|
9682
|
+
});
|
|
9683
|
+
} else {
|
|
9684
|
+
results.push({
|
|
9685
|
+
step: "Firecracker process",
|
|
9686
|
+
success: true,
|
|
9687
|
+
message: "Not running"
|
|
9688
|
+
});
|
|
9689
|
+
}
|
|
9690
|
+
try {
|
|
9691
|
+
await deleteTapDevice(tapDevice);
|
|
9692
|
+
results.push({
|
|
9693
|
+
step: "TAP device",
|
|
9694
|
+
success: true,
|
|
9695
|
+
message: `${tapDevice} deleted`
|
|
9696
|
+
});
|
|
9697
|
+
} catch (error) {
|
|
9698
|
+
results.push({
|
|
9699
|
+
step: "TAP device",
|
|
9700
|
+
success: false,
|
|
9701
|
+
message: error instanceof Error ? error.message : "Unknown error"
|
|
9702
|
+
});
|
|
9703
|
+
}
|
|
9704
|
+
if (existsSync4(workspaceDir)) {
|
|
9705
|
+
try {
|
|
9706
|
+
rmSync(workspaceDir, { recursive: true, force: true });
|
|
9707
|
+
results.push({
|
|
9708
|
+
step: "Workspace",
|
|
9709
|
+
success: true,
|
|
9710
|
+
message: `${workspaceDir} removed`
|
|
9711
|
+
});
|
|
9712
|
+
} catch (error) {
|
|
9713
|
+
results.push({
|
|
9714
|
+
step: "Workspace",
|
|
9715
|
+
success: false,
|
|
9716
|
+
message: error instanceof Error ? error.message : "Unknown error"
|
|
9717
|
+
});
|
|
9718
|
+
}
|
|
9719
|
+
} else {
|
|
9720
|
+
results.push({
|
|
9721
|
+
step: "Workspace",
|
|
9722
|
+
success: true,
|
|
9723
|
+
message: "Not found (already cleaned)"
|
|
9724
|
+
});
|
|
9725
|
+
}
|
|
9726
|
+
if (runId && existsSync4(statusFilePath)) {
|
|
9727
|
+
try {
|
|
9728
|
+
const status = JSON.parse(
|
|
9729
|
+
readFileSync4(statusFilePath, "utf-8")
|
|
9730
|
+
);
|
|
9731
|
+
const oldCount = status.active_jobs;
|
|
9732
|
+
status.active_job_ids = status.active_job_ids.filter(
|
|
9733
|
+
(id) => id !== runId
|
|
9734
|
+
);
|
|
9735
|
+
status.active_jobs = status.active_job_ids.length;
|
|
9736
|
+
status.updated_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
9737
|
+
writeFileSync3(statusFilePath, JSON.stringify(status, null, 2));
|
|
9738
|
+
results.push({
|
|
9739
|
+
step: "status.json",
|
|
9740
|
+
success: true,
|
|
9741
|
+
message: `Updated (active_jobs: ${oldCount} -> ${status.active_jobs})`
|
|
9742
|
+
});
|
|
9743
|
+
} catch (error) {
|
|
9744
|
+
results.push({
|
|
9745
|
+
step: "status.json",
|
|
9746
|
+
success: false,
|
|
9747
|
+
message: error instanceof Error ? error.message : "Unknown error"
|
|
9748
|
+
});
|
|
9749
|
+
}
|
|
9750
|
+
} else {
|
|
9751
|
+
results.push({
|
|
9752
|
+
step: "status.json",
|
|
9753
|
+
success: true,
|
|
9754
|
+
message: "No update needed"
|
|
9755
|
+
});
|
|
9756
|
+
}
|
|
9757
|
+
console.log("");
|
|
9758
|
+
let allSuccess = true;
|
|
9759
|
+
for (const r of results) {
|
|
9760
|
+
const icon = r.success ? "\u2713" : "\u2717";
|
|
9761
|
+
console.log(` ${icon} ${r.step}: ${r.message}`);
|
|
9762
|
+
if (!r.success) allSuccess = false;
|
|
9763
|
+
}
|
|
9764
|
+
console.log("");
|
|
9765
|
+
if (allSuccess) {
|
|
9766
|
+
console.log(`Job ${vmId} killed successfully.`);
|
|
9767
|
+
process.exit(0);
|
|
9768
|
+
} else {
|
|
9769
|
+
console.log(`Job ${vmId} cleanup completed with errors.`);
|
|
9770
|
+
process.exit(1);
|
|
9771
|
+
}
|
|
9772
|
+
} catch (error) {
|
|
9773
|
+
console.error(
|
|
9774
|
+
`Error: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
9775
|
+
);
|
|
9776
|
+
process.exit(1);
|
|
9777
|
+
}
|
|
9778
|
+
}
|
|
9779
|
+
);
|
|
9780
|
+
function resolveJobId(input, statusFilePath) {
|
|
9781
|
+
if (input.includes("-")) {
|
|
9782
|
+
const vmId = input.split("-")[0];
|
|
9783
|
+
return { vmId: vmId ?? input, runId: input };
|
|
9784
|
+
}
|
|
9785
|
+
if (existsSync4(statusFilePath)) {
|
|
9786
|
+
try {
|
|
9787
|
+
const status = JSON.parse(
|
|
9788
|
+
readFileSync4(statusFilePath, "utf-8")
|
|
9789
|
+
);
|
|
9790
|
+
const match = status.active_job_ids.find((id) => id.startsWith(input));
|
|
9791
|
+
if (match) {
|
|
9792
|
+
return { vmId: input, runId: match };
|
|
9793
|
+
}
|
|
9794
|
+
} catch {
|
|
9795
|
+
}
|
|
9796
|
+
}
|
|
9797
|
+
return { vmId: input, runId: null };
|
|
9798
|
+
}
|
|
9799
|
+
async function confirm(message) {
|
|
9800
|
+
const rl = readline2.createInterface({
|
|
9801
|
+
input: process.stdin,
|
|
9802
|
+
output: process.stdout
|
|
9803
|
+
});
|
|
9804
|
+
return new Promise((resolve) => {
|
|
9805
|
+
rl.question(`${message} [y/N] `, (answer) => {
|
|
9806
|
+
rl.close();
|
|
9807
|
+
resolve(answer.toLowerCase() === "y" || answer.toLowerCase() === "yes");
|
|
9808
|
+
});
|
|
9809
|
+
});
|
|
9810
|
+
}
|
|
9811
|
+
|
|
9812
|
+
// src/commands/benchmark.ts
|
|
9813
|
+
import { Command as Command4 } from "commander";
|
|
8862
9814
|
import crypto from "crypto";
|
|
8863
9815
|
|
|
8864
9816
|
// src/lib/timing.ts
|
|
@@ -8909,7 +9861,7 @@ function createBenchmarkContext(prompt, options) {
|
|
|
8909
9861
|
cliAgentType: options.agentType
|
|
8910
9862
|
};
|
|
8911
9863
|
}
|
|
8912
|
-
var benchmarkCommand = new
|
|
9864
|
+
var benchmarkCommand = new Command4("benchmark").description(
|
|
8913
9865
|
"Run a VM performance benchmark (executes bash command directly)"
|
|
8914
9866
|
).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) => {
|
|
8915
9867
|
const timer = new Timer();
|
|
@@ -8949,10 +9901,11 @@ var benchmarkCommand = new Command3("benchmark").description(
|
|
|
8949
9901
|
});
|
|
8950
9902
|
|
|
8951
9903
|
// src/index.ts
|
|
8952
|
-
var version = true ? "2.
|
|
9904
|
+
var version = true ? "2.13.0" : "0.1.0";
|
|
8953
9905
|
program.name("vm0-runner").version(version).description("Self-hosted runner for VM0 agents");
|
|
8954
9906
|
program.addCommand(startCommand);
|
|
8955
|
-
program.addCommand(
|
|
9907
|
+
program.addCommand(doctorCommand);
|
|
9908
|
+
program.addCommand(killCommand);
|
|
8956
9909
|
program.addCommand(benchmarkCommand);
|
|
8957
9910
|
program.parse();
|
|
8958
9911
|
//# sourceMappingURL=index.js.map
|