@vm0/runner 2.12.0 → 2.13.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.js +1346 -406
- 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() {
|
|
@@ -6267,43 +6642,137 @@ var scopeContract = c9.router({
|
|
|
6267
6642
|
}
|
|
6268
6643
|
});
|
|
6269
6644
|
|
|
6270
|
-
// ../../packages/core/src/contracts/
|
|
6645
|
+
// ../../packages/core/src/contracts/credentials.ts
|
|
6271
6646
|
import { z as z14 } from "zod";
|
|
6272
6647
|
var c10 = initContract();
|
|
6273
|
-
var
|
|
6274
|
-
|
|
6275
|
-
|
|
6276
|
-
|
|
6277
|
-
|
|
6278
|
-
|
|
6279
|
-
|
|
6280
|
-
|
|
6281
|
-
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(),
|
|
6282
6656
|
createdAt: z14.string(),
|
|
6283
6657
|
updatedAt: z14.string()
|
|
6284
6658
|
});
|
|
6285
|
-
var
|
|
6286
|
-
|
|
6287
|
-
|
|
6288
|
-
|
|
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
|
+
}
|
|
6289
6737
|
});
|
|
6290
|
-
|
|
6291
|
-
|
|
6292
|
-
|
|
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()
|
|
6293
6753
|
});
|
|
6294
|
-
var
|
|
6295
|
-
|
|
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()
|
|
6296
6762
|
});
|
|
6297
|
-
var
|
|
6298
|
-
|
|
6299
|
-
|
|
6300
|
-
|
|
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(),
|
|
6301
6770
|
agentComposeSnapshot: agentComposeSnapshotSchema,
|
|
6302
6771
|
artifactSnapshot: artifactSnapshotSchema2.nullable(),
|
|
6303
6772
|
volumeVersionsSnapshot: volumeVersionsSnapshotSchema2.nullable(),
|
|
6304
|
-
createdAt:
|
|
6773
|
+
createdAt: z15.string()
|
|
6305
6774
|
});
|
|
6306
|
-
var sessionsByIdContract =
|
|
6775
|
+
var sessionsByIdContract = c11.router({
|
|
6307
6776
|
/**
|
|
6308
6777
|
* GET /api/agent/sessions/:id
|
|
6309
6778
|
* Get session by ID
|
|
@@ -6311,8 +6780,8 @@ var sessionsByIdContract = c10.router({
|
|
|
6311
6780
|
getById: {
|
|
6312
6781
|
method: "GET",
|
|
6313
6782
|
path: "/api/agent/sessions/:id",
|
|
6314
|
-
pathParams:
|
|
6315
|
-
id:
|
|
6783
|
+
pathParams: z15.object({
|
|
6784
|
+
id: z15.string().min(1, "Session ID is required")
|
|
6316
6785
|
}),
|
|
6317
6786
|
responses: {
|
|
6318
6787
|
200: sessionResponseSchema,
|
|
@@ -6323,7 +6792,7 @@ var sessionsByIdContract = c10.router({
|
|
|
6323
6792
|
summary: "Get session by ID"
|
|
6324
6793
|
}
|
|
6325
6794
|
});
|
|
6326
|
-
var checkpointsByIdContract =
|
|
6795
|
+
var checkpointsByIdContract = c11.router({
|
|
6327
6796
|
/**
|
|
6328
6797
|
* GET /api/agent/checkpoints/:id
|
|
6329
6798
|
* Get checkpoint by ID
|
|
@@ -6331,8 +6800,8 @@ var checkpointsByIdContract = c10.router({
|
|
|
6331
6800
|
getById: {
|
|
6332
6801
|
method: "GET",
|
|
6333
6802
|
path: "/api/agent/checkpoints/:id",
|
|
6334
|
-
pathParams:
|
|
6335
|
-
id:
|
|
6803
|
+
pathParams: z15.object({
|
|
6804
|
+
id: z15.string().min(1, "Checkpoint ID is required")
|
|
6336
6805
|
}),
|
|
6337
6806
|
responses: {
|
|
6338
6807
|
200: checkpointResponseSchema,
|
|
@@ -6345,91 +6814,91 @@ var checkpointsByIdContract = c10.router({
|
|
|
6345
6814
|
});
|
|
6346
6815
|
|
|
6347
6816
|
// ../../packages/core/src/contracts/schedules.ts
|
|
6348
|
-
import { z as
|
|
6349
|
-
var
|
|
6350
|
-
var scheduleTriggerSchema =
|
|
6351
|
-
cron:
|
|
6352
|
-
at:
|
|
6353
|
-
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")
|
|
6354
6823
|
}).refine((data) => data.cron && !data.at || !data.cron && data.at, {
|
|
6355
6824
|
message: "Exactly one of 'cron' or 'at' must be specified"
|
|
6356
6825
|
});
|
|
6357
|
-
var scheduleRunConfigSchema =
|
|
6358
|
-
agent:
|
|
6359
|
-
prompt:
|
|
6360
|
-
vars:
|
|
6361
|
-
secrets:
|
|
6362
|
-
artifactName:
|
|
6363
|
-
artifactVersion:
|
|
6364
|
-
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()
|
|
6365
6834
|
});
|
|
6366
|
-
var scheduleDefinitionSchema =
|
|
6835
|
+
var scheduleDefinitionSchema = z16.object({
|
|
6367
6836
|
on: scheduleTriggerSchema,
|
|
6368
6837
|
run: scheduleRunConfigSchema
|
|
6369
6838
|
});
|
|
6370
|
-
var scheduleYamlSchema =
|
|
6371
|
-
version:
|
|
6372
|
-
schedules:
|
|
6373
|
-
});
|
|
6374
|
-
var deployScheduleRequestSchema =
|
|
6375
|
-
name:
|
|
6376
|
-
cronExpression:
|
|
6377
|
-
atTime:
|
|
6378
|
-
timezone:
|
|
6379
|
-
prompt:
|
|
6380
|
-
vars:
|
|
6381
|
-
secrets:
|
|
6382
|
-
artifactName:
|
|
6383
|
-
artifactVersion:
|
|
6384
|
-
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(),
|
|
6385
6854
|
// Resolved agent compose ID (CLI resolves scope/name:version → composeId)
|
|
6386
|
-
composeId:
|
|
6855
|
+
composeId: z16.string().uuid("Invalid compose ID")
|
|
6387
6856
|
}).refine(
|
|
6388
6857
|
(data) => data.cronExpression && !data.atTime || !data.cronExpression && data.atTime,
|
|
6389
6858
|
{
|
|
6390
6859
|
message: "Exactly one of 'cronExpression' or 'atTime' must be specified"
|
|
6391
6860
|
}
|
|
6392
6861
|
);
|
|
6393
|
-
var scheduleResponseSchema =
|
|
6394
|
-
id:
|
|
6395
|
-
composeId:
|
|
6396
|
-
composeName:
|
|
6397
|
-
scopeSlug:
|
|
6398
|
-
name:
|
|
6399
|
-
cronExpression:
|
|
6400
|
-
atTime:
|
|
6401
|
-
timezone:
|
|
6402
|
-
prompt:
|
|
6403
|
-
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(),
|
|
6404
6873
|
// Secret names only (values are never returned)
|
|
6405
|
-
secretNames:
|
|
6406
|
-
artifactName:
|
|
6407
|
-
artifactVersion:
|
|
6408
|
-
volumeVersions:
|
|
6409
|
-
enabled:
|
|
6410
|
-
nextRunAt:
|
|
6411
|
-
createdAt:
|
|
6412
|
-
updatedAt:
|
|
6413
|
-
});
|
|
6414
|
-
var runSummarySchema =
|
|
6415
|
-
id:
|
|
6416
|
-
status:
|
|
6417
|
-
createdAt:
|
|
6418
|
-
completedAt:
|
|
6419
|
-
error:
|
|
6420
|
-
});
|
|
6421
|
-
var scheduleRunsResponseSchema =
|
|
6422
|
-
runs:
|
|
6423
|
-
});
|
|
6424
|
-
var scheduleListResponseSchema =
|
|
6425
|
-
schedules:
|
|
6426
|
-
});
|
|
6427
|
-
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({
|
|
6428
6897
|
schedule: scheduleResponseSchema,
|
|
6429
|
-
created:
|
|
6898
|
+
created: z16.boolean()
|
|
6430
6899
|
// true if created, false if updated
|
|
6431
6900
|
});
|
|
6432
|
-
var schedulesMainContract =
|
|
6901
|
+
var schedulesMainContract = c12.router({
|
|
6433
6902
|
/**
|
|
6434
6903
|
* POST /api/agent/schedules
|
|
6435
6904
|
* Deploy (create or update) a schedule
|
|
@@ -6465,7 +6934,7 @@ var schedulesMainContract = c11.router({
|
|
|
6465
6934
|
summary: "List all schedules"
|
|
6466
6935
|
}
|
|
6467
6936
|
});
|
|
6468
|
-
var schedulesByNameContract =
|
|
6937
|
+
var schedulesByNameContract = c12.router({
|
|
6469
6938
|
/**
|
|
6470
6939
|
* GET /api/agent/schedules/:name
|
|
6471
6940
|
* Get schedule by name
|
|
@@ -6473,11 +6942,11 @@ var schedulesByNameContract = c11.router({
|
|
|
6473
6942
|
getByName: {
|
|
6474
6943
|
method: "GET",
|
|
6475
6944
|
path: "/api/agent/schedules/:name",
|
|
6476
|
-
pathParams:
|
|
6477
|
-
name:
|
|
6945
|
+
pathParams: z16.object({
|
|
6946
|
+
name: z16.string().min(1, "Schedule name required")
|
|
6478
6947
|
}),
|
|
6479
|
-
query:
|
|
6480
|
-
composeId:
|
|
6948
|
+
query: z16.object({
|
|
6949
|
+
composeId: z16.string().uuid("Compose ID required")
|
|
6481
6950
|
}),
|
|
6482
6951
|
responses: {
|
|
6483
6952
|
200: scheduleResponseSchema,
|
|
@@ -6493,21 +6962,21 @@ var schedulesByNameContract = c11.router({
|
|
|
6493
6962
|
delete: {
|
|
6494
6963
|
method: "DELETE",
|
|
6495
6964
|
path: "/api/agent/schedules/:name",
|
|
6496
|
-
pathParams:
|
|
6497
|
-
name:
|
|
6965
|
+
pathParams: z16.object({
|
|
6966
|
+
name: z16.string().min(1, "Schedule name required")
|
|
6498
6967
|
}),
|
|
6499
|
-
query:
|
|
6500
|
-
composeId:
|
|
6968
|
+
query: z16.object({
|
|
6969
|
+
composeId: z16.string().uuid("Compose ID required")
|
|
6501
6970
|
}),
|
|
6502
6971
|
responses: {
|
|
6503
|
-
204:
|
|
6972
|
+
204: z16.undefined(),
|
|
6504
6973
|
401: apiErrorSchema,
|
|
6505
6974
|
404: apiErrorSchema
|
|
6506
6975
|
},
|
|
6507
6976
|
summary: "Delete schedule"
|
|
6508
6977
|
}
|
|
6509
6978
|
});
|
|
6510
|
-
var schedulesEnableContract =
|
|
6979
|
+
var schedulesEnableContract = c12.router({
|
|
6511
6980
|
/**
|
|
6512
6981
|
* POST /api/agent/schedules/:name/enable
|
|
6513
6982
|
* Enable a disabled schedule
|
|
@@ -6515,11 +6984,11 @@ var schedulesEnableContract = c11.router({
|
|
|
6515
6984
|
enable: {
|
|
6516
6985
|
method: "POST",
|
|
6517
6986
|
path: "/api/agent/schedules/:name/enable",
|
|
6518
|
-
pathParams:
|
|
6519
|
-
name:
|
|
6987
|
+
pathParams: z16.object({
|
|
6988
|
+
name: z16.string().min(1, "Schedule name required")
|
|
6520
6989
|
}),
|
|
6521
|
-
body:
|
|
6522
|
-
composeId:
|
|
6990
|
+
body: z16.object({
|
|
6991
|
+
composeId: z16.string().uuid("Compose ID required")
|
|
6523
6992
|
}),
|
|
6524
6993
|
responses: {
|
|
6525
6994
|
200: scheduleResponseSchema,
|
|
@@ -6535,11 +7004,11 @@ var schedulesEnableContract = c11.router({
|
|
|
6535
7004
|
disable: {
|
|
6536
7005
|
method: "POST",
|
|
6537
7006
|
path: "/api/agent/schedules/:name/disable",
|
|
6538
|
-
pathParams:
|
|
6539
|
-
name:
|
|
7007
|
+
pathParams: z16.object({
|
|
7008
|
+
name: z16.string().min(1, "Schedule name required")
|
|
6540
7009
|
}),
|
|
6541
|
-
body:
|
|
6542
|
-
composeId:
|
|
7010
|
+
body: z16.object({
|
|
7011
|
+
composeId: z16.string().uuid("Compose ID required")
|
|
6543
7012
|
}),
|
|
6544
7013
|
responses: {
|
|
6545
7014
|
200: scheduleResponseSchema,
|
|
@@ -6549,7 +7018,7 @@ var schedulesEnableContract = c11.router({
|
|
|
6549
7018
|
summary: "Disable schedule"
|
|
6550
7019
|
}
|
|
6551
7020
|
});
|
|
6552
|
-
var scheduleRunsContract =
|
|
7021
|
+
var scheduleRunsContract = c12.router({
|
|
6553
7022
|
/**
|
|
6554
7023
|
* GET /api/agent/schedules/:name/runs
|
|
6555
7024
|
* List recent runs for a schedule
|
|
@@ -6557,12 +7026,12 @@ var scheduleRunsContract = c11.router({
|
|
|
6557
7026
|
listRuns: {
|
|
6558
7027
|
method: "GET",
|
|
6559
7028
|
path: "/api/agent/schedules/:name/runs",
|
|
6560
|
-
pathParams:
|
|
6561
|
-
name:
|
|
7029
|
+
pathParams: z16.object({
|
|
7030
|
+
name: z16.string().min(1, "Schedule name required")
|
|
6562
7031
|
}),
|
|
6563
|
-
query:
|
|
6564
|
-
composeId:
|
|
6565
|
-
limit:
|
|
7032
|
+
query: z16.object({
|
|
7033
|
+
composeId: z16.string().uuid("Compose ID required"),
|
|
7034
|
+
limit: z16.coerce.number().min(0).max(100).default(5)
|
|
6566
7035
|
}),
|
|
6567
7036
|
responses: {
|
|
6568
7037
|
200: scheduleRunsResponseSchema,
|
|
@@ -6574,8 +7043,8 @@ var scheduleRunsContract = c11.router({
|
|
|
6574
7043
|
});
|
|
6575
7044
|
|
|
6576
7045
|
// ../../packages/core/src/contracts/public/common.ts
|
|
6577
|
-
import { z as
|
|
6578
|
-
var publicApiErrorTypeSchema =
|
|
7046
|
+
import { z as z17 } from "zod";
|
|
7047
|
+
var publicApiErrorTypeSchema = z17.enum([
|
|
6579
7048
|
"api_error",
|
|
6580
7049
|
// Internal server error (5xx)
|
|
6581
7050
|
"invalid_request_error",
|
|
@@ -6587,55 +7056,55 @@ var publicApiErrorTypeSchema = z16.enum([
|
|
|
6587
7056
|
"conflict_error"
|
|
6588
7057
|
// Resource conflict (409)
|
|
6589
7058
|
]);
|
|
6590
|
-
var publicApiErrorSchema =
|
|
6591
|
-
error:
|
|
7059
|
+
var publicApiErrorSchema = z17.object({
|
|
7060
|
+
error: z17.object({
|
|
6592
7061
|
type: publicApiErrorTypeSchema,
|
|
6593
|
-
code:
|
|
6594
|
-
message:
|
|
6595
|
-
param:
|
|
6596
|
-
doc_url:
|
|
7062
|
+
code: z17.string(),
|
|
7063
|
+
message: z17.string(),
|
|
7064
|
+
param: z17.string().optional(),
|
|
7065
|
+
doc_url: z17.string().url().optional()
|
|
6597
7066
|
})
|
|
6598
7067
|
});
|
|
6599
|
-
var paginationSchema =
|
|
6600
|
-
has_more:
|
|
6601
|
-
next_cursor:
|
|
7068
|
+
var paginationSchema = z17.object({
|
|
7069
|
+
has_more: z17.boolean(),
|
|
7070
|
+
next_cursor: z17.string().nullable()
|
|
6602
7071
|
});
|
|
6603
7072
|
function createPaginatedResponseSchema(dataSchema) {
|
|
6604
|
-
return
|
|
6605
|
-
data:
|
|
7073
|
+
return z17.object({
|
|
7074
|
+
data: z17.array(dataSchema),
|
|
6606
7075
|
pagination: paginationSchema
|
|
6607
7076
|
});
|
|
6608
7077
|
}
|
|
6609
|
-
var listQuerySchema =
|
|
6610
|
-
cursor:
|
|
6611
|
-
limit:
|
|
7078
|
+
var listQuerySchema = z17.object({
|
|
7079
|
+
cursor: z17.string().optional(),
|
|
7080
|
+
limit: z17.coerce.number().min(1).max(100).default(20)
|
|
6612
7081
|
});
|
|
6613
|
-
var requestIdSchema =
|
|
6614
|
-
var timestampSchema =
|
|
7082
|
+
var requestIdSchema = z17.string().uuid();
|
|
7083
|
+
var timestampSchema = z17.string().datetime();
|
|
6615
7084
|
|
|
6616
7085
|
// ../../packages/core/src/contracts/public/agents.ts
|
|
6617
|
-
import { z as
|
|
6618
|
-
var
|
|
6619
|
-
var publicAgentSchema =
|
|
6620
|
-
id:
|
|
6621
|
-
name:
|
|
6622
|
-
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(),
|
|
6623
7092
|
created_at: timestampSchema,
|
|
6624
7093
|
updated_at: timestampSchema
|
|
6625
7094
|
});
|
|
6626
|
-
var agentVersionSchema =
|
|
6627
|
-
id:
|
|
6628
|
-
agent_id:
|
|
6629
|
-
version_number:
|
|
7095
|
+
var agentVersionSchema = z18.object({
|
|
7096
|
+
id: z18.string(),
|
|
7097
|
+
agent_id: z18.string(),
|
|
7098
|
+
version_number: z18.number(),
|
|
6630
7099
|
created_at: timestampSchema
|
|
6631
7100
|
});
|
|
6632
7101
|
var publicAgentDetailSchema = publicAgentSchema;
|
|
6633
7102
|
var paginatedAgentsSchema = createPaginatedResponseSchema(publicAgentSchema);
|
|
6634
7103
|
var paginatedAgentVersionsSchema = createPaginatedResponseSchema(agentVersionSchema);
|
|
6635
7104
|
var agentListQuerySchema = listQuerySchema.extend({
|
|
6636
|
-
name:
|
|
7105
|
+
name: z18.string().optional()
|
|
6637
7106
|
});
|
|
6638
|
-
var publicAgentsListContract =
|
|
7107
|
+
var publicAgentsListContract = c13.router({
|
|
6639
7108
|
list: {
|
|
6640
7109
|
method: "GET",
|
|
6641
7110
|
path: "/v1/agents",
|
|
@@ -6649,12 +7118,12 @@ var publicAgentsListContract = c12.router({
|
|
|
6649
7118
|
description: "List all agents in the current scope with pagination. Use the `name` query parameter to filter by agent name."
|
|
6650
7119
|
}
|
|
6651
7120
|
});
|
|
6652
|
-
var publicAgentByIdContract =
|
|
7121
|
+
var publicAgentByIdContract = c13.router({
|
|
6653
7122
|
get: {
|
|
6654
7123
|
method: "GET",
|
|
6655
7124
|
path: "/v1/agents/:id",
|
|
6656
|
-
pathParams:
|
|
6657
|
-
id:
|
|
7125
|
+
pathParams: z18.object({
|
|
7126
|
+
id: z18.string().min(1, "Agent ID is required")
|
|
6658
7127
|
}),
|
|
6659
7128
|
responses: {
|
|
6660
7129
|
200: publicAgentDetailSchema,
|
|
@@ -6666,12 +7135,12 @@ var publicAgentByIdContract = c12.router({
|
|
|
6666
7135
|
description: "Get agent details by ID"
|
|
6667
7136
|
}
|
|
6668
7137
|
});
|
|
6669
|
-
var publicAgentVersionsContract =
|
|
7138
|
+
var publicAgentVersionsContract = c13.router({
|
|
6670
7139
|
list: {
|
|
6671
7140
|
method: "GET",
|
|
6672
7141
|
path: "/v1/agents/:id/versions",
|
|
6673
|
-
pathParams:
|
|
6674
|
-
id:
|
|
7142
|
+
pathParams: z18.object({
|
|
7143
|
+
id: z18.string().min(1, "Agent ID is required")
|
|
6675
7144
|
}),
|
|
6676
7145
|
query: listQuerySchema,
|
|
6677
7146
|
responses: {
|
|
@@ -6686,9 +7155,9 @@ var publicAgentVersionsContract = c12.router({
|
|
|
6686
7155
|
});
|
|
6687
7156
|
|
|
6688
7157
|
// ../../packages/core/src/contracts/public/runs.ts
|
|
6689
|
-
import { z as
|
|
6690
|
-
var
|
|
6691
|
-
var publicRunStatusSchema =
|
|
7158
|
+
import { z as z19 } from "zod";
|
|
7159
|
+
var c14 = initContract();
|
|
7160
|
+
var publicRunStatusSchema = z19.enum([
|
|
6692
7161
|
"pending",
|
|
6693
7162
|
"running",
|
|
6694
7163
|
"completed",
|
|
@@ -6696,56 +7165,56 @@ var publicRunStatusSchema = z18.enum([
|
|
|
6696
7165
|
"timeout",
|
|
6697
7166
|
"cancelled"
|
|
6698
7167
|
]);
|
|
6699
|
-
var publicRunSchema =
|
|
6700
|
-
id:
|
|
6701
|
-
agent_id:
|
|
6702
|
-
agent_name:
|
|
7168
|
+
var publicRunSchema = z19.object({
|
|
7169
|
+
id: z19.string(),
|
|
7170
|
+
agent_id: z19.string(),
|
|
7171
|
+
agent_name: z19.string(),
|
|
6703
7172
|
status: publicRunStatusSchema,
|
|
6704
|
-
prompt:
|
|
7173
|
+
prompt: z19.string(),
|
|
6705
7174
|
created_at: timestampSchema,
|
|
6706
7175
|
started_at: timestampSchema.nullable(),
|
|
6707
7176
|
completed_at: timestampSchema.nullable()
|
|
6708
7177
|
});
|
|
6709
7178
|
var publicRunDetailSchema = publicRunSchema.extend({
|
|
6710
|
-
error:
|
|
6711
|
-
execution_time_ms:
|
|
6712
|
-
checkpoint_id:
|
|
6713
|
-
session_id:
|
|
6714
|
-
artifact_name:
|
|
6715
|
-
artifact_version:
|
|
6716
|
-
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()
|
|
6717
7186
|
});
|
|
6718
7187
|
var paginatedRunsSchema = createPaginatedResponseSchema(publicRunSchema);
|
|
6719
|
-
var createRunRequestSchema =
|
|
7188
|
+
var createRunRequestSchema = z19.object({
|
|
6720
7189
|
// Agent identification (one of: agent, agent_id, session_id, checkpoint_id)
|
|
6721
|
-
agent:
|
|
7190
|
+
agent: z19.string().optional(),
|
|
6722
7191
|
// Agent name
|
|
6723
|
-
agent_id:
|
|
7192
|
+
agent_id: z19.string().optional(),
|
|
6724
7193
|
// Agent ID
|
|
6725
|
-
agent_version:
|
|
7194
|
+
agent_version: z19.string().optional(),
|
|
6726
7195
|
// Version specifier (e.g., "latest", "v1", specific ID)
|
|
6727
7196
|
// Continue session
|
|
6728
|
-
session_id:
|
|
7197
|
+
session_id: z19.string().optional(),
|
|
6729
7198
|
// Resume from checkpoint
|
|
6730
|
-
checkpoint_id:
|
|
7199
|
+
checkpoint_id: z19.string().optional(),
|
|
6731
7200
|
// Required
|
|
6732
|
-
prompt:
|
|
7201
|
+
prompt: z19.string().min(1, "Prompt is required"),
|
|
6733
7202
|
// Optional configuration
|
|
6734
|
-
variables:
|
|
6735
|
-
secrets:
|
|
6736
|
-
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(),
|
|
6737
7206
|
// Artifact name to mount
|
|
6738
|
-
artifact_version:
|
|
7207
|
+
artifact_version: z19.string().optional(),
|
|
6739
7208
|
// Artifact version (defaults to latest)
|
|
6740
|
-
volumes:
|
|
7209
|
+
volumes: z19.record(z19.string(), z19.string()).optional()
|
|
6741
7210
|
// volume_name -> version
|
|
6742
7211
|
});
|
|
6743
7212
|
var runListQuerySchema = listQuerySchema.extend({
|
|
6744
|
-
agent_id:
|
|
7213
|
+
agent_id: z19.string().optional(),
|
|
6745
7214
|
status: publicRunStatusSchema.optional(),
|
|
6746
7215
|
since: timestampSchema.optional()
|
|
6747
7216
|
});
|
|
6748
|
-
var publicRunsListContract =
|
|
7217
|
+
var publicRunsListContract = c14.router({
|
|
6749
7218
|
list: {
|
|
6750
7219
|
method: "GET",
|
|
6751
7220
|
path: "/v1/runs",
|
|
@@ -6774,12 +7243,12 @@ var publicRunsListContract = c13.router({
|
|
|
6774
7243
|
description: "Create and execute a new agent run. Returns 202 Accepted as runs execute asynchronously."
|
|
6775
7244
|
}
|
|
6776
7245
|
});
|
|
6777
|
-
var publicRunByIdContract =
|
|
7246
|
+
var publicRunByIdContract = c14.router({
|
|
6778
7247
|
get: {
|
|
6779
7248
|
method: "GET",
|
|
6780
7249
|
path: "/v1/runs/:id",
|
|
6781
|
-
pathParams:
|
|
6782
|
-
id:
|
|
7250
|
+
pathParams: z19.object({
|
|
7251
|
+
id: z19.string().min(1, "Run ID is required")
|
|
6783
7252
|
}),
|
|
6784
7253
|
responses: {
|
|
6785
7254
|
200: publicRunDetailSchema,
|
|
@@ -6791,14 +7260,14 @@ var publicRunByIdContract = c13.router({
|
|
|
6791
7260
|
description: "Get run details by ID"
|
|
6792
7261
|
}
|
|
6793
7262
|
});
|
|
6794
|
-
var publicRunCancelContract =
|
|
7263
|
+
var publicRunCancelContract = c14.router({
|
|
6795
7264
|
cancel: {
|
|
6796
7265
|
method: "POST",
|
|
6797
7266
|
path: "/v1/runs/:id/cancel",
|
|
6798
|
-
pathParams:
|
|
6799
|
-
id:
|
|
7267
|
+
pathParams: z19.object({
|
|
7268
|
+
id: z19.string().min(1, "Run ID is required")
|
|
6800
7269
|
}),
|
|
6801
|
-
body:
|
|
7270
|
+
body: z19.undefined(),
|
|
6802
7271
|
responses: {
|
|
6803
7272
|
200: publicRunDetailSchema,
|
|
6804
7273
|
400: publicApiErrorSchema,
|
|
@@ -6811,26 +7280,26 @@ var publicRunCancelContract = c13.router({
|
|
|
6811
7280
|
description: "Cancel a pending or running execution"
|
|
6812
7281
|
}
|
|
6813
7282
|
});
|
|
6814
|
-
var logEntrySchema =
|
|
7283
|
+
var logEntrySchema = z19.object({
|
|
6815
7284
|
timestamp: timestampSchema,
|
|
6816
|
-
type:
|
|
6817
|
-
level:
|
|
6818
|
-
message:
|
|
6819
|
-
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()
|
|
6820
7289
|
});
|
|
6821
7290
|
var paginatedLogsSchema = createPaginatedResponseSchema(logEntrySchema);
|
|
6822
7291
|
var logsQuerySchema = listQuerySchema.extend({
|
|
6823
|
-
type:
|
|
7292
|
+
type: z19.enum(["agent", "system", "network", "all"]).default("all"),
|
|
6824
7293
|
since: timestampSchema.optional(),
|
|
6825
7294
|
until: timestampSchema.optional(),
|
|
6826
|
-
order:
|
|
7295
|
+
order: z19.enum(["asc", "desc"]).default("asc")
|
|
6827
7296
|
});
|
|
6828
|
-
var publicRunLogsContract =
|
|
7297
|
+
var publicRunLogsContract = c14.router({
|
|
6829
7298
|
getLogs: {
|
|
6830
7299
|
method: "GET",
|
|
6831
7300
|
path: "/v1/runs/:id/logs",
|
|
6832
|
-
pathParams:
|
|
6833
|
-
id:
|
|
7301
|
+
pathParams: z19.object({
|
|
7302
|
+
id: z19.string().min(1, "Run ID is required")
|
|
6834
7303
|
}),
|
|
6835
7304
|
query: logsQuerySchema,
|
|
6836
7305
|
responses: {
|
|
@@ -6843,29 +7312,29 @@ var publicRunLogsContract = c13.router({
|
|
|
6843
7312
|
description: "Get unified logs for a run. Combines agent, system, and network logs."
|
|
6844
7313
|
}
|
|
6845
7314
|
});
|
|
6846
|
-
var metricPointSchema =
|
|
7315
|
+
var metricPointSchema = z19.object({
|
|
6847
7316
|
timestamp: timestampSchema,
|
|
6848
|
-
cpu_percent:
|
|
6849
|
-
memory_used_mb:
|
|
6850
|
-
memory_total_mb:
|
|
6851
|
-
disk_used_mb:
|
|
6852
|
-
disk_total_mb:
|
|
6853
|
-
});
|
|
6854
|
-
var metricsSummarySchema =
|
|
6855
|
-
avg_cpu_percent:
|
|
6856
|
-
max_memory_used_mb:
|
|
6857
|
-
total_duration_ms:
|
|
6858
|
-
});
|
|
6859
|
-
var metricsResponseSchema2 =
|
|
6860
|
-
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),
|
|
6861
7330
|
summary: metricsSummarySchema
|
|
6862
7331
|
});
|
|
6863
|
-
var publicRunMetricsContract =
|
|
7332
|
+
var publicRunMetricsContract = c14.router({
|
|
6864
7333
|
getMetrics: {
|
|
6865
7334
|
method: "GET",
|
|
6866
7335
|
path: "/v1/runs/:id/metrics",
|
|
6867
|
-
pathParams:
|
|
6868
|
-
id:
|
|
7336
|
+
pathParams: z19.object({
|
|
7337
|
+
id: z19.string().min(1, "Run ID is required")
|
|
6869
7338
|
}),
|
|
6870
7339
|
responses: {
|
|
6871
7340
|
200: metricsResponseSchema2,
|
|
@@ -6877,7 +7346,7 @@ var publicRunMetricsContract = c13.router({
|
|
|
6877
7346
|
description: "Get CPU, memory, and disk metrics for a run"
|
|
6878
7347
|
}
|
|
6879
7348
|
});
|
|
6880
|
-
var sseEventTypeSchema =
|
|
7349
|
+
var sseEventTypeSchema = z19.enum([
|
|
6881
7350
|
"status",
|
|
6882
7351
|
// Run status change
|
|
6883
7352
|
"output",
|
|
@@ -6889,25 +7358,25 @@ var sseEventTypeSchema = z18.enum([
|
|
|
6889
7358
|
"heartbeat"
|
|
6890
7359
|
// Keep-alive
|
|
6891
7360
|
]);
|
|
6892
|
-
var sseEventSchema =
|
|
7361
|
+
var sseEventSchema = z19.object({
|
|
6893
7362
|
event: sseEventTypeSchema,
|
|
6894
|
-
data:
|
|
6895
|
-
id:
|
|
7363
|
+
data: z19.unknown(),
|
|
7364
|
+
id: z19.string().optional()
|
|
6896
7365
|
// For Last-Event-ID reconnection
|
|
6897
7366
|
});
|
|
6898
|
-
var publicRunEventsContract =
|
|
7367
|
+
var publicRunEventsContract = c14.router({
|
|
6899
7368
|
streamEvents: {
|
|
6900
7369
|
method: "GET",
|
|
6901
7370
|
path: "/v1/runs/:id/events",
|
|
6902
|
-
pathParams:
|
|
6903
|
-
id:
|
|
7371
|
+
pathParams: z19.object({
|
|
7372
|
+
id: z19.string().min(1, "Run ID is required")
|
|
6904
7373
|
}),
|
|
6905
|
-
query:
|
|
6906
|
-
last_event_id:
|
|
7374
|
+
query: z19.object({
|
|
7375
|
+
last_event_id: z19.string().optional()
|
|
6907
7376
|
// For reconnection
|
|
6908
7377
|
}),
|
|
6909
7378
|
responses: {
|
|
6910
|
-
200:
|
|
7379
|
+
200: z19.any(),
|
|
6911
7380
|
// SSE stream - actual content is text/event-stream
|
|
6912
7381
|
401: publicApiErrorSchema,
|
|
6913
7382
|
404: publicApiErrorSchema,
|
|
@@ -6919,28 +7388,28 @@ var publicRunEventsContract = c13.router({
|
|
|
6919
7388
|
});
|
|
6920
7389
|
|
|
6921
7390
|
// ../../packages/core/src/contracts/public/artifacts.ts
|
|
6922
|
-
import { z as
|
|
6923
|
-
var
|
|
6924
|
-
var publicArtifactSchema =
|
|
6925
|
-
id:
|
|
6926
|
-
name:
|
|
6927
|
-
current_version_id:
|
|
6928
|
-
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(),
|
|
6929
7398
|
// Total size in bytes
|
|
6930
|
-
file_count:
|
|
7399
|
+
file_count: z20.number(),
|
|
6931
7400
|
created_at: timestampSchema,
|
|
6932
7401
|
updated_at: timestampSchema
|
|
6933
7402
|
});
|
|
6934
|
-
var artifactVersionSchema =
|
|
6935
|
-
id:
|
|
7403
|
+
var artifactVersionSchema = z20.object({
|
|
7404
|
+
id: z20.string(),
|
|
6936
7405
|
// SHA-256 content hash
|
|
6937
|
-
artifact_id:
|
|
6938
|
-
size:
|
|
7406
|
+
artifact_id: z20.string(),
|
|
7407
|
+
size: z20.number(),
|
|
6939
7408
|
// Size in bytes
|
|
6940
|
-
file_count:
|
|
6941
|
-
message:
|
|
7409
|
+
file_count: z20.number(),
|
|
7410
|
+
message: z20.string().nullable(),
|
|
6942
7411
|
// Optional commit message
|
|
6943
|
-
created_by:
|
|
7412
|
+
created_by: z20.string(),
|
|
6944
7413
|
created_at: timestampSchema
|
|
6945
7414
|
});
|
|
6946
7415
|
var publicArtifactDetailSchema = publicArtifactSchema.extend({
|
|
@@ -6950,7 +7419,7 @@ var paginatedArtifactsSchema = createPaginatedResponseSchema(publicArtifactSchem
|
|
|
6950
7419
|
var paginatedArtifactVersionsSchema = createPaginatedResponseSchema(
|
|
6951
7420
|
artifactVersionSchema
|
|
6952
7421
|
);
|
|
6953
|
-
var publicArtifactsListContract =
|
|
7422
|
+
var publicArtifactsListContract = c15.router({
|
|
6954
7423
|
list: {
|
|
6955
7424
|
method: "GET",
|
|
6956
7425
|
path: "/v1/artifacts",
|
|
@@ -6964,12 +7433,12 @@ var publicArtifactsListContract = c14.router({
|
|
|
6964
7433
|
description: "List all artifacts in the current scope with pagination"
|
|
6965
7434
|
}
|
|
6966
7435
|
});
|
|
6967
|
-
var publicArtifactByIdContract =
|
|
7436
|
+
var publicArtifactByIdContract = c15.router({
|
|
6968
7437
|
get: {
|
|
6969
7438
|
method: "GET",
|
|
6970
7439
|
path: "/v1/artifacts/:id",
|
|
6971
|
-
pathParams:
|
|
6972
|
-
id:
|
|
7440
|
+
pathParams: z20.object({
|
|
7441
|
+
id: z20.string().min(1, "Artifact ID is required")
|
|
6973
7442
|
}),
|
|
6974
7443
|
responses: {
|
|
6975
7444
|
200: publicArtifactDetailSchema,
|
|
@@ -6981,12 +7450,12 @@ var publicArtifactByIdContract = c14.router({
|
|
|
6981
7450
|
description: "Get artifact details by ID"
|
|
6982
7451
|
}
|
|
6983
7452
|
});
|
|
6984
|
-
var publicArtifactVersionsContract =
|
|
7453
|
+
var publicArtifactVersionsContract = c15.router({
|
|
6985
7454
|
list: {
|
|
6986
7455
|
method: "GET",
|
|
6987
7456
|
path: "/v1/artifacts/:id/versions",
|
|
6988
|
-
pathParams:
|
|
6989
|
-
id:
|
|
7457
|
+
pathParams: z20.object({
|
|
7458
|
+
id: z20.string().min(1, "Artifact ID is required")
|
|
6990
7459
|
}),
|
|
6991
7460
|
query: listQuerySchema,
|
|
6992
7461
|
responses: {
|
|
@@ -6999,19 +7468,19 @@ var publicArtifactVersionsContract = c14.router({
|
|
|
6999
7468
|
description: "List all versions of an artifact with pagination"
|
|
7000
7469
|
}
|
|
7001
7470
|
});
|
|
7002
|
-
var publicArtifactDownloadContract =
|
|
7471
|
+
var publicArtifactDownloadContract = c15.router({
|
|
7003
7472
|
download: {
|
|
7004
7473
|
method: "GET",
|
|
7005
7474
|
path: "/v1/artifacts/:id/download",
|
|
7006
|
-
pathParams:
|
|
7007
|
-
id:
|
|
7475
|
+
pathParams: z20.object({
|
|
7476
|
+
id: z20.string().min(1, "Artifact ID is required")
|
|
7008
7477
|
}),
|
|
7009
|
-
query:
|
|
7010
|
-
version_id:
|
|
7478
|
+
query: z20.object({
|
|
7479
|
+
version_id: z20.string().optional()
|
|
7011
7480
|
// Defaults to current version
|
|
7012
7481
|
}),
|
|
7013
7482
|
responses: {
|
|
7014
|
-
302:
|
|
7483
|
+
302: z20.undefined(),
|
|
7015
7484
|
// Redirect to presigned URL
|
|
7016
7485
|
401: publicApiErrorSchema,
|
|
7017
7486
|
404: publicApiErrorSchema,
|
|
@@ -7023,28 +7492,28 @@ var publicArtifactDownloadContract = c14.router({
|
|
|
7023
7492
|
});
|
|
7024
7493
|
|
|
7025
7494
|
// ../../packages/core/src/contracts/public/volumes.ts
|
|
7026
|
-
import { z as
|
|
7027
|
-
var
|
|
7028
|
-
var publicVolumeSchema =
|
|
7029
|
-
id:
|
|
7030
|
-
name:
|
|
7031
|
-
current_version_id:
|
|
7032
|
-
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(),
|
|
7033
7502
|
// Total size in bytes
|
|
7034
|
-
file_count:
|
|
7503
|
+
file_count: z21.number(),
|
|
7035
7504
|
created_at: timestampSchema,
|
|
7036
7505
|
updated_at: timestampSchema
|
|
7037
7506
|
});
|
|
7038
|
-
var volumeVersionSchema =
|
|
7039
|
-
id:
|
|
7507
|
+
var volumeVersionSchema = z21.object({
|
|
7508
|
+
id: z21.string(),
|
|
7040
7509
|
// SHA-256 content hash
|
|
7041
|
-
volume_id:
|
|
7042
|
-
size:
|
|
7510
|
+
volume_id: z21.string(),
|
|
7511
|
+
size: z21.number(),
|
|
7043
7512
|
// Size in bytes
|
|
7044
|
-
file_count:
|
|
7045
|
-
message:
|
|
7513
|
+
file_count: z21.number(),
|
|
7514
|
+
message: z21.string().nullable(),
|
|
7046
7515
|
// Optional commit message
|
|
7047
|
-
created_by:
|
|
7516
|
+
created_by: z21.string(),
|
|
7048
7517
|
created_at: timestampSchema
|
|
7049
7518
|
});
|
|
7050
7519
|
var publicVolumeDetailSchema = publicVolumeSchema.extend({
|
|
@@ -7052,7 +7521,7 @@ var publicVolumeDetailSchema = publicVolumeSchema.extend({
|
|
|
7052
7521
|
});
|
|
7053
7522
|
var paginatedVolumesSchema = createPaginatedResponseSchema(publicVolumeSchema);
|
|
7054
7523
|
var paginatedVolumeVersionsSchema = createPaginatedResponseSchema(volumeVersionSchema);
|
|
7055
|
-
var publicVolumesListContract =
|
|
7524
|
+
var publicVolumesListContract = c16.router({
|
|
7056
7525
|
list: {
|
|
7057
7526
|
method: "GET",
|
|
7058
7527
|
path: "/v1/volumes",
|
|
@@ -7066,12 +7535,12 @@ var publicVolumesListContract = c15.router({
|
|
|
7066
7535
|
description: "List all volumes in the current scope with pagination"
|
|
7067
7536
|
}
|
|
7068
7537
|
});
|
|
7069
|
-
var publicVolumeByIdContract =
|
|
7538
|
+
var publicVolumeByIdContract = c16.router({
|
|
7070
7539
|
get: {
|
|
7071
7540
|
method: "GET",
|
|
7072
7541
|
path: "/v1/volumes/:id",
|
|
7073
|
-
pathParams:
|
|
7074
|
-
id:
|
|
7542
|
+
pathParams: z21.object({
|
|
7543
|
+
id: z21.string().min(1, "Volume ID is required")
|
|
7075
7544
|
}),
|
|
7076
7545
|
responses: {
|
|
7077
7546
|
200: publicVolumeDetailSchema,
|
|
@@ -7083,12 +7552,12 @@ var publicVolumeByIdContract = c15.router({
|
|
|
7083
7552
|
description: "Get volume details by ID"
|
|
7084
7553
|
}
|
|
7085
7554
|
});
|
|
7086
|
-
var publicVolumeVersionsContract =
|
|
7555
|
+
var publicVolumeVersionsContract = c16.router({
|
|
7087
7556
|
list: {
|
|
7088
7557
|
method: "GET",
|
|
7089
7558
|
path: "/v1/volumes/:id/versions",
|
|
7090
|
-
pathParams:
|
|
7091
|
-
id:
|
|
7559
|
+
pathParams: z21.object({
|
|
7560
|
+
id: z21.string().min(1, "Volume ID is required")
|
|
7092
7561
|
}),
|
|
7093
7562
|
query: listQuerySchema,
|
|
7094
7563
|
responses: {
|
|
@@ -7101,19 +7570,19 @@ var publicVolumeVersionsContract = c15.router({
|
|
|
7101
7570
|
description: "List all versions of a volume with pagination"
|
|
7102
7571
|
}
|
|
7103
7572
|
});
|
|
7104
|
-
var publicVolumeDownloadContract =
|
|
7573
|
+
var publicVolumeDownloadContract = c16.router({
|
|
7105
7574
|
download: {
|
|
7106
7575
|
method: "GET",
|
|
7107
7576
|
path: "/v1/volumes/:id/download",
|
|
7108
|
-
pathParams:
|
|
7109
|
-
id:
|
|
7577
|
+
pathParams: z21.object({
|
|
7578
|
+
id: z21.string().min(1, "Volume ID is required")
|
|
7110
7579
|
}),
|
|
7111
|
-
query:
|
|
7112
|
-
version_id:
|
|
7580
|
+
query: z21.object({
|
|
7581
|
+
version_id: z21.string().optional()
|
|
7113
7582
|
// Defaults to current version
|
|
7114
7583
|
}),
|
|
7115
7584
|
responses: {
|
|
7116
|
-
302:
|
|
7585
|
+
302: z21.undefined(),
|
|
7117
7586
|
// Redirect to presigned URL
|
|
7118
7587
|
401: publicApiErrorSchema,
|
|
7119
7588
|
404: publicApiErrorSchema,
|
|
@@ -7158,7 +7627,7 @@ var FEATURE_SWITCHES = {
|
|
|
7158
7627
|
var ENV_LOADER_PATH = "/usr/local/bin/vm0-agent/env-loader.mjs";
|
|
7159
7628
|
|
|
7160
7629
|
// src/lib/proxy/vm-registry.ts
|
|
7161
|
-
import
|
|
7630
|
+
import fs5 from "fs";
|
|
7162
7631
|
var DEFAULT_REGISTRY_PATH = "/tmp/vm0-vm-registry.json";
|
|
7163
7632
|
var VMRegistry = class {
|
|
7164
7633
|
registryPath;
|
|
@@ -7172,8 +7641,8 @@ var VMRegistry = class {
|
|
|
7172
7641
|
*/
|
|
7173
7642
|
load() {
|
|
7174
7643
|
try {
|
|
7175
|
-
if (
|
|
7176
|
-
const content =
|
|
7644
|
+
if (fs5.existsSync(this.registryPath)) {
|
|
7645
|
+
const content = fs5.readFileSync(this.registryPath, "utf-8");
|
|
7177
7646
|
return JSON.parse(content);
|
|
7178
7647
|
}
|
|
7179
7648
|
} catch {
|
|
@@ -7187,8 +7656,8 @@ var VMRegistry = class {
|
|
|
7187
7656
|
this.data.updatedAt = Date.now();
|
|
7188
7657
|
const content = JSON.stringify(this.data, null, 2);
|
|
7189
7658
|
const tempPath = `${this.registryPath}.tmp`;
|
|
7190
|
-
|
|
7191
|
-
|
|
7659
|
+
fs5.writeFileSync(tempPath, content, { mode: 420 });
|
|
7660
|
+
fs5.renameSync(tempPath, this.registryPath);
|
|
7192
7661
|
}
|
|
7193
7662
|
/**
|
|
7194
7663
|
* Register a VM with its IP address
|
|
@@ -7263,8 +7732,8 @@ function initVMRegistry(registryPath) {
|
|
|
7263
7732
|
|
|
7264
7733
|
// src/lib/proxy/proxy-manager.ts
|
|
7265
7734
|
import { spawn as spawn2 } from "child_process";
|
|
7266
|
-
import
|
|
7267
|
-
import
|
|
7735
|
+
import fs6 from "fs";
|
|
7736
|
+
import path4 from "path";
|
|
7268
7737
|
|
|
7269
7738
|
// src/lib/proxy/mitm-addon-script.ts
|
|
7270
7739
|
var RUNNER_MITM_ADDON_SCRIPT = `#!/usr/bin/env python3
|
|
@@ -7781,11 +8250,11 @@ var ProxyManager = class {
|
|
|
7781
8250
|
* Ensure the addon script exists at the configured path
|
|
7782
8251
|
*/
|
|
7783
8252
|
ensureAddonScript() {
|
|
7784
|
-
const addonDir =
|
|
7785
|
-
if (!
|
|
7786
|
-
|
|
8253
|
+
const addonDir = path4.dirname(this.config.addonPath);
|
|
8254
|
+
if (!fs6.existsSync(addonDir)) {
|
|
8255
|
+
fs6.mkdirSync(addonDir, { recursive: true });
|
|
7787
8256
|
}
|
|
7788
|
-
|
|
8257
|
+
fs6.writeFileSync(this.config.addonPath, RUNNER_MITM_ADDON_SCRIPT, {
|
|
7789
8258
|
mode: 493
|
|
7790
8259
|
});
|
|
7791
8260
|
console.log(
|
|
@@ -7796,11 +8265,11 @@ var ProxyManager = class {
|
|
|
7796
8265
|
* Validate proxy configuration
|
|
7797
8266
|
*/
|
|
7798
8267
|
validateConfig() {
|
|
7799
|
-
if (!
|
|
8268
|
+
if (!fs6.existsSync(this.config.caDir)) {
|
|
7800
8269
|
throw new Error(`Proxy CA directory not found: ${this.config.caDir}`);
|
|
7801
8270
|
}
|
|
7802
|
-
const caCertPath =
|
|
7803
|
-
if (!
|
|
8271
|
+
const caCertPath = path4.join(this.config.caDir, "mitmproxy-ca.pem");
|
|
8272
|
+
if (!fs6.existsSync(caCertPath)) {
|
|
7804
8273
|
throw new Error(`Proxy CA certificate not found: ${caCertPath}`);
|
|
7805
8274
|
}
|
|
7806
8275
|
this.ensureAddonScript();
|
|
@@ -8189,17 +8658,17 @@ function buildEnvironmentVariables(context, apiUrl) {
|
|
|
8189
8658
|
}
|
|
8190
8659
|
|
|
8191
8660
|
// src/lib/network-logs/network-logs.ts
|
|
8192
|
-
import
|
|
8661
|
+
import fs7 from "fs";
|
|
8193
8662
|
function getNetworkLogPath(runId) {
|
|
8194
8663
|
return `/tmp/vm0-network-${runId}.jsonl`;
|
|
8195
8664
|
}
|
|
8196
8665
|
function readNetworkLogs(runId) {
|
|
8197
8666
|
const logPath = getNetworkLogPath(runId);
|
|
8198
|
-
if (!
|
|
8667
|
+
if (!fs7.existsSync(logPath)) {
|
|
8199
8668
|
return [];
|
|
8200
8669
|
}
|
|
8201
8670
|
try {
|
|
8202
|
-
const content =
|
|
8671
|
+
const content = fs7.readFileSync(logPath, "utf-8");
|
|
8203
8672
|
const lines = content.split("\n").filter((line) => line.trim());
|
|
8204
8673
|
return lines.map((line) => JSON.parse(line));
|
|
8205
8674
|
} catch (err) {
|
|
@@ -8212,8 +8681,8 @@ function readNetworkLogs(runId) {
|
|
|
8212
8681
|
function cleanupNetworkLogs(runId) {
|
|
8213
8682
|
const logPath = getNetworkLogPath(runId);
|
|
8214
8683
|
try {
|
|
8215
|
-
if (
|
|
8216
|
-
|
|
8684
|
+
if (fs7.existsSync(logPath)) {
|
|
8685
|
+
fs7.unlinkSync(logPath);
|
|
8217
8686
|
}
|
|
8218
8687
|
} catch (err) {
|
|
8219
8688
|
console.error(
|
|
@@ -8256,7 +8725,7 @@ async function uploadNetworkLogs(apiUrl, sandboxToken, runId) {
|
|
|
8256
8725
|
}
|
|
8257
8726
|
|
|
8258
8727
|
// src/lib/vm-setup/vm-setup.ts
|
|
8259
|
-
import
|
|
8728
|
+
import fs8 from "fs";
|
|
8260
8729
|
|
|
8261
8730
|
// src/lib/scripts/utils.ts
|
|
8262
8731
|
function getAllScripts() {
|
|
@@ -8318,12 +8787,12 @@ async function restoreSessionHistory(ssh, resumeSession, workingDir, cliAgentTyp
|
|
|
8318
8787
|
);
|
|
8319
8788
|
}
|
|
8320
8789
|
async function installProxyCA(ssh) {
|
|
8321
|
-
if (!
|
|
8790
|
+
if (!fs8.existsSync(PROXY_CA_CERT_PATH)) {
|
|
8322
8791
|
throw new Error(
|
|
8323
8792
|
`Proxy CA certificate not found at ${PROXY_CA_CERT_PATH}. Run generate-proxy-ca.sh first.`
|
|
8324
8793
|
);
|
|
8325
8794
|
}
|
|
8326
|
-
const caCert =
|
|
8795
|
+
const caCert = fs8.readFileSync(PROXY_CA_CERT_PATH, "utf-8");
|
|
8327
8796
|
console.log(
|
|
8328
8797
|
`[Executor] Installing proxy CA certificate (${caCert.length} bytes)`
|
|
8329
8798
|
);
|
|
@@ -8404,7 +8873,7 @@ async function executeJob(context, config, options = {}) {
|
|
|
8404
8873
|
const log = options.logger ?? ((msg) => console.log(msg));
|
|
8405
8874
|
log(`[Executor] Starting job ${context.runId} in VM ${vmId}`);
|
|
8406
8875
|
try {
|
|
8407
|
-
const workspacesDir =
|
|
8876
|
+
const workspacesDir = path5.join(process.cwd(), "workspaces");
|
|
8408
8877
|
const vmConfig = {
|
|
8409
8878
|
vmId,
|
|
8410
8879
|
vcpus: config.sandbox.vcpu,
|
|
@@ -8412,7 +8881,7 @@ async function executeJob(context, config, options = {}) {
|
|
|
8412
8881
|
kernelPath: config.firecracker.kernel,
|
|
8413
8882
|
rootfsPath: config.firecracker.rootfs,
|
|
8414
8883
|
firecrackerBinary: config.firecracker.binary,
|
|
8415
|
-
workDir:
|
|
8884
|
+
workDir: path5.join(workspacesDir, `vm0-${vmId}`)
|
|
8416
8885
|
};
|
|
8417
8886
|
log(`[Executor] Creating VM ${vmId}...`);
|
|
8418
8887
|
vm = new FirecrackerVM(vmConfig);
|
|
@@ -8437,7 +8906,7 @@ async function executeJob(context, config, options = {}) {
|
|
|
8437
8906
|
log(
|
|
8438
8907
|
`[Executor] Setting up network security for VM ${guestIp} (mitm=${mitmEnabled}, sealSecrets=${sealSecretsEnabled})`
|
|
8439
8908
|
);
|
|
8440
|
-
await setupVMProxyRules(guestIp, config.proxy.port);
|
|
8909
|
+
await setupVMProxyRules(guestIp, config.proxy.port, config.name);
|
|
8441
8910
|
getVMRegistry().register(guestIp, context.runId, context.sandboxToken, {
|
|
8442
8911
|
firewallRules: firewallConfig?.rules,
|
|
8443
8912
|
mitmEnabled,
|
|
@@ -8608,7 +9077,7 @@ async function executeJob(context, config, options = {}) {
|
|
|
8608
9077
|
if (context.experimentalFirewall?.enabled && guestIp) {
|
|
8609
9078
|
log(`[Executor] Cleaning up network security for VM ${guestIp}`);
|
|
8610
9079
|
try {
|
|
8611
|
-
await removeVMProxyRules(guestIp, config.proxy.port);
|
|
9080
|
+
await removeVMProxyRules(guestIp, config.proxy.port, config.name);
|
|
8612
9081
|
} catch (err) {
|
|
8613
9082
|
console.error(
|
|
8614
9083
|
`[Executor] Failed to remove VM proxy rules: ${err instanceof Error ? err.message : "Unknown error"}`
|
|
@@ -8637,17 +9106,17 @@ async function executeJob(context, config, options = {}) {
|
|
|
8637
9106
|
}
|
|
8638
9107
|
|
|
8639
9108
|
// src/commands/start.ts
|
|
8640
|
-
var
|
|
9109
|
+
var activeRuns = /* @__PURE__ */ new Set();
|
|
8641
9110
|
function writeStatusFile(statusFilePath, mode, startedAt) {
|
|
8642
9111
|
const status = {
|
|
8643
9112
|
mode,
|
|
8644
|
-
|
|
8645
|
-
|
|
9113
|
+
active_runs: activeRuns.size,
|
|
9114
|
+
active_run_ids: Array.from(activeRuns),
|
|
8646
9115
|
started_at: startedAt.toISOString(),
|
|
8647
9116
|
updated_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
8648
9117
|
};
|
|
8649
9118
|
try {
|
|
8650
|
-
|
|
9119
|
+
writeFileSync2(statusFilePath, JSON.stringify(status, null, 2));
|
|
8651
9120
|
} catch (err) {
|
|
8652
9121
|
console.error(
|
|
8653
9122
|
`Failed to write status file: ${err instanceof Error ? err.message : "Unknown error"}`
|
|
@@ -8700,6 +9169,12 @@ var startCommand = new Command("start").description("Start the runner").option("
|
|
|
8700
9169
|
}
|
|
8701
9170
|
console.log("Setting up network bridge...");
|
|
8702
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();
|
|
8703
9178
|
console.log("Initializing network proxy...");
|
|
8704
9179
|
initVMRegistry();
|
|
8705
9180
|
const proxyManager = initProxyManager({
|
|
@@ -8719,7 +9194,7 @@ var startCommand = new Command("start").description("Start the runner").option("
|
|
|
8719
9194
|
"Jobs with experimentalFirewall enabled will run without network interception"
|
|
8720
9195
|
);
|
|
8721
9196
|
}
|
|
8722
|
-
const statusFilePath =
|
|
9197
|
+
const statusFilePath = join2(dirname(options.config), "status.json");
|
|
8723
9198
|
const startedAt = /* @__PURE__ */ new Date();
|
|
8724
9199
|
const state = { mode: "running" };
|
|
8725
9200
|
const updateStatus = () => {
|
|
@@ -8750,7 +9225,7 @@ var startCommand = new Command("start").description("Start the runner").option("
|
|
|
8750
9225
|
if (state.mode === "running") {
|
|
8751
9226
|
console.log("\n[Maintenance] Entering drain mode...");
|
|
8752
9227
|
console.log(
|
|
8753
|
-
`[Maintenance] Active jobs: ${
|
|
9228
|
+
`[Maintenance] Active jobs: ${activeRuns.size} (will wait for completion)`
|
|
8754
9229
|
);
|
|
8755
9230
|
state.mode = "draining";
|
|
8756
9231
|
updateStatus();
|
|
@@ -8759,7 +9234,7 @@ var startCommand = new Command("start").description("Start the runner").option("
|
|
|
8759
9234
|
const jobPromises = /* @__PURE__ */ new Set();
|
|
8760
9235
|
while (running) {
|
|
8761
9236
|
if (state.mode === "draining") {
|
|
8762
|
-
if (
|
|
9237
|
+
if (activeRuns.size === 0) {
|
|
8763
9238
|
console.log("[Maintenance] All jobs completed, exiting drain mode");
|
|
8764
9239
|
running = false;
|
|
8765
9240
|
break;
|
|
@@ -8770,7 +9245,7 @@ var startCommand = new Command("start").description("Start the runner").option("
|
|
|
8770
9245
|
}
|
|
8771
9246
|
continue;
|
|
8772
9247
|
}
|
|
8773
|
-
if (
|
|
9248
|
+
if (activeRuns.size >= config.sandbox.max_concurrent) {
|
|
8774
9249
|
if (jobPromises.size > 0) {
|
|
8775
9250
|
await Promise.race(jobPromises);
|
|
8776
9251
|
updateStatus();
|
|
@@ -8795,7 +9270,7 @@ var startCommand = new Command("start").description("Start the runner").option("
|
|
|
8795
9270
|
() => claimJob(config.server, job.runId)
|
|
8796
9271
|
);
|
|
8797
9272
|
console.log(`Claimed job: ${context.runId}`);
|
|
8798
|
-
|
|
9273
|
+
activeRuns.add(context.runId);
|
|
8799
9274
|
updateStatus();
|
|
8800
9275
|
const jobPromise = executeJob2(context, config).catch((error) => {
|
|
8801
9276
|
console.error(
|
|
@@ -8803,7 +9278,7 @@ var startCommand = new Command("start").description("Start the runner").option("
|
|
|
8803
9278
|
error instanceof Error ? error.message : "Unknown error"
|
|
8804
9279
|
);
|
|
8805
9280
|
}).finally(() => {
|
|
8806
|
-
|
|
9281
|
+
activeRuns.delete(context.runId);
|
|
8807
9282
|
jobPromises.delete(jobPromise);
|
|
8808
9283
|
updateStatus();
|
|
8809
9284
|
});
|
|
@@ -8849,32 +9324,496 @@ var startCommand = new Command("start").description("Start the runner").option("
|
|
|
8849
9324
|
}
|
|
8850
9325
|
});
|
|
8851
9326
|
|
|
8852
|
-
// src/commands/
|
|
9327
|
+
// src/commands/doctor.ts
|
|
8853
9328
|
import { Command as Command2 } from "commander";
|
|
8854
|
-
|
|
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) => {
|
|
8855
9441
|
try {
|
|
8856
9442
|
const config = loadConfig(options.config);
|
|
8857
|
-
|
|
8858
|
-
|
|
8859
|
-
|
|
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
|
+
}
|
|
8860
9467
|
console.log("");
|
|
8861
|
-
console.log("
|
|
8862
|
-
|
|
8863
|
-
|
|
8864
|
-
|
|
8865
|
-
|
|
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_run_ids) {
|
|
9517
|
+
for (const runId of status.active_run_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(`Runs (${jobs.length} active, max ${maxConcurrent}):`);
|
|
9540
|
+
if (jobs.length === 0) {
|
|
9541
|
+
console.log(" No active runs");
|
|
9542
|
+
} else {
|
|
9543
|
+
console.log(
|
|
9544
|
+
" Run ID VM ID IP Status"
|
|
9545
|
+
);
|
|
9546
|
+
for (const job of jobs) {
|
|
9547
|
+
const ipConflict = (ipToVmIds.get(job.ip)?.length ?? 0) > 1;
|
|
9548
|
+
let statusText;
|
|
9549
|
+
if (ipConflict) {
|
|
9550
|
+
statusText = "\u26A0\uFE0F IP conflict!";
|
|
9551
|
+
} else if (job.hasProcess) {
|
|
9552
|
+
statusText = `\u2713 Running (PID ${job.pid})`;
|
|
9553
|
+
} else {
|
|
9554
|
+
statusText = "\u26A0\uFE0F No process";
|
|
9555
|
+
}
|
|
9556
|
+
console.log(
|
|
9557
|
+
` ${job.runId} ${job.vmId} ${job.ip.padEnd(15)} ${statusText}`
|
|
9558
|
+
);
|
|
9559
|
+
}
|
|
9560
|
+
}
|
|
9561
|
+
console.log("");
|
|
9562
|
+
for (const job of jobs) {
|
|
9563
|
+
if (!job.hasProcess) {
|
|
9564
|
+
warnings.push({
|
|
9565
|
+
message: `Run ${job.vmId} in status.json but no Firecracker process running`
|
|
9566
|
+
});
|
|
9567
|
+
}
|
|
9568
|
+
}
|
|
9569
|
+
for (const [ip, vmIds] of ipToVmIds) {
|
|
9570
|
+
if (vmIds.length > 1) {
|
|
9571
|
+
warnings.push({
|
|
9572
|
+
message: `IP conflict: ${ip} assigned to ${vmIds.join(", ")}`
|
|
9573
|
+
});
|
|
9574
|
+
}
|
|
9575
|
+
}
|
|
9576
|
+
const processVmIds = new Set(processes.map((p) => p.vmId));
|
|
9577
|
+
for (const proc of processes) {
|
|
9578
|
+
if (!statusVmIds.has(proc.vmId)) {
|
|
9579
|
+
warnings.push({
|
|
9580
|
+
message: `Orphan process: PID ${proc.pid} (vmId ${proc.vmId}) not in status.json`
|
|
9581
|
+
});
|
|
9582
|
+
}
|
|
9583
|
+
}
|
|
9584
|
+
for (const tap of tapDevices) {
|
|
9585
|
+
const vmId = tap.replace("tap", "");
|
|
9586
|
+
if (!processVmIds.has(vmId) && !statusVmIds.has(vmId)) {
|
|
9587
|
+
warnings.push({
|
|
9588
|
+
message: `Orphan TAP device: ${tap} (no matching job or process)`
|
|
9589
|
+
});
|
|
9590
|
+
}
|
|
9591
|
+
}
|
|
9592
|
+
for (const ws of workspaces) {
|
|
9593
|
+
const vmId = ws.replace("vm0-", "");
|
|
9594
|
+
if (!processVmIds.has(vmId) && !statusVmIds.has(vmId)) {
|
|
9595
|
+
warnings.push({
|
|
9596
|
+
message: `Orphan workspace: ${ws} (no matching job or process)`
|
|
9597
|
+
});
|
|
9598
|
+
}
|
|
9599
|
+
}
|
|
9600
|
+
const activeVmIps = new Set(jobs.map((j) => j.ip));
|
|
9601
|
+
const iptablesRules = await listIptablesNatRules();
|
|
9602
|
+
const orphanedIptables = await findOrphanedIptablesRules(
|
|
9603
|
+
iptablesRules,
|
|
9604
|
+
activeVmIps,
|
|
9605
|
+
proxyPort
|
|
9606
|
+
);
|
|
9607
|
+
for (const rule of orphanedIptables) {
|
|
9608
|
+
warnings.push({
|
|
9609
|
+
message: `Orphan iptables rule: redirect ${rule.sourceIp}:${rule.destPort} -> :${rule.redirectPort}`
|
|
9610
|
+
});
|
|
9611
|
+
}
|
|
9612
|
+
console.log("Warnings:");
|
|
9613
|
+
if (warnings.length === 0) {
|
|
9614
|
+
console.log(" None");
|
|
9615
|
+
} else {
|
|
9616
|
+
for (const w of warnings) {
|
|
9617
|
+
console.log(` - ${w.message}`);
|
|
9618
|
+
}
|
|
9619
|
+
}
|
|
9620
|
+
process.exit(warnings.length > 0 ? 1 : 0);
|
|
8866
9621
|
} catch (error) {
|
|
8867
|
-
console.error("");
|
|
8868
|
-
console.error("\u2717 Runner cannot connect to API");
|
|
8869
9622
|
console.error(
|
|
8870
|
-
`
|
|
9623
|
+
`Error: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
8871
9624
|
);
|
|
8872
9625
|
process.exit(1);
|
|
8873
9626
|
}
|
|
8874
9627
|
});
|
|
9628
|
+
function formatUptime(ms) {
|
|
9629
|
+
const seconds = Math.floor(ms / 1e3);
|
|
9630
|
+
const minutes = Math.floor(seconds / 60);
|
|
9631
|
+
const hours = Math.floor(minutes / 60);
|
|
9632
|
+
const days = Math.floor(hours / 24);
|
|
9633
|
+
if (days > 0) return `${days}d ${hours % 24}h`;
|
|
9634
|
+
if (hours > 0) return `${hours}h ${minutes % 60}m`;
|
|
9635
|
+
if (minutes > 0) return `${minutes}m`;
|
|
9636
|
+
return `${seconds}s`;
|
|
9637
|
+
}
|
|
8875
9638
|
|
|
8876
|
-
// src/commands/
|
|
9639
|
+
// src/commands/kill.ts
|
|
8877
9640
|
import { Command as Command3 } from "commander";
|
|
9641
|
+
import { existsSync as existsSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync3, rmSync } from "fs";
|
|
9642
|
+
import { dirname as dirname3, join as join4 } from "path";
|
|
9643
|
+
import * as readline2 from "readline";
|
|
9644
|
+
var killCommand = new Command3("kill").description("Force terminate a run and clean up all resources").argument("<run-id>", "Run ID (full UUID or short 8-char vmId)").option("--config <path>", "Config file path", "./runner.yaml").option("--force", "Skip confirmation prompt").action(
|
|
9645
|
+
async (runIdArg, options) => {
|
|
9646
|
+
try {
|
|
9647
|
+
loadConfig(options.config);
|
|
9648
|
+
const configDir = dirname3(options.config);
|
|
9649
|
+
const statusFilePath = join4(configDir, "status.json");
|
|
9650
|
+
const workspacesDir = join4(configDir, "workspaces");
|
|
9651
|
+
const { vmId, runId } = resolveRunId(runIdArg, statusFilePath);
|
|
9652
|
+
console.log(`Killing run ${vmId}...`);
|
|
9653
|
+
const proc = findProcessByVmId(vmId);
|
|
9654
|
+
const tapDevice = `tap${vmId}`;
|
|
9655
|
+
const workspaceDir = join4(workspacesDir, `vm0-${vmId}`);
|
|
9656
|
+
console.log("");
|
|
9657
|
+
console.log("Resources to clean up:");
|
|
9658
|
+
if (proc) {
|
|
9659
|
+
console.log(` - Firecracker process (PID ${proc.pid})`);
|
|
9660
|
+
} else {
|
|
9661
|
+
console.log(" - Firecracker process: not found");
|
|
9662
|
+
}
|
|
9663
|
+
console.log(` - TAP device: ${tapDevice}`);
|
|
9664
|
+
console.log(` - Workspace: ${workspaceDir}`);
|
|
9665
|
+
if (runId) {
|
|
9666
|
+
console.log(` - status.json entry: ${runId.substring(0, 12)}...`);
|
|
9667
|
+
}
|
|
9668
|
+
console.log("");
|
|
9669
|
+
if (!options.force) {
|
|
9670
|
+
const confirmed = await confirm("Proceed with cleanup?");
|
|
9671
|
+
if (!confirmed) {
|
|
9672
|
+
console.log("Aborted.");
|
|
9673
|
+
process.exit(0);
|
|
9674
|
+
}
|
|
9675
|
+
}
|
|
9676
|
+
const results = [];
|
|
9677
|
+
if (proc) {
|
|
9678
|
+
const killed = await killProcess(proc.pid);
|
|
9679
|
+
results.push({
|
|
9680
|
+
step: "Firecracker process",
|
|
9681
|
+
success: killed,
|
|
9682
|
+
message: killed ? `PID ${proc.pid} terminated` : `Failed to kill PID ${proc.pid}`
|
|
9683
|
+
});
|
|
9684
|
+
} else {
|
|
9685
|
+
results.push({
|
|
9686
|
+
step: "Firecracker process",
|
|
9687
|
+
success: true,
|
|
9688
|
+
message: "Not running"
|
|
9689
|
+
});
|
|
9690
|
+
}
|
|
9691
|
+
try {
|
|
9692
|
+
await deleteTapDevice(tapDevice);
|
|
9693
|
+
results.push({
|
|
9694
|
+
step: "TAP device",
|
|
9695
|
+
success: true,
|
|
9696
|
+
message: `${tapDevice} deleted`
|
|
9697
|
+
});
|
|
9698
|
+
} catch (error) {
|
|
9699
|
+
results.push({
|
|
9700
|
+
step: "TAP device",
|
|
9701
|
+
success: false,
|
|
9702
|
+
message: error instanceof Error ? error.message : "Unknown error"
|
|
9703
|
+
});
|
|
9704
|
+
}
|
|
9705
|
+
if (existsSync4(workspaceDir)) {
|
|
9706
|
+
try {
|
|
9707
|
+
rmSync(workspaceDir, { recursive: true, force: true });
|
|
9708
|
+
results.push({
|
|
9709
|
+
step: "Workspace",
|
|
9710
|
+
success: true,
|
|
9711
|
+
message: `${workspaceDir} removed`
|
|
9712
|
+
});
|
|
9713
|
+
} catch (error) {
|
|
9714
|
+
results.push({
|
|
9715
|
+
step: "Workspace",
|
|
9716
|
+
success: false,
|
|
9717
|
+
message: error instanceof Error ? error.message : "Unknown error"
|
|
9718
|
+
});
|
|
9719
|
+
}
|
|
9720
|
+
} else {
|
|
9721
|
+
results.push({
|
|
9722
|
+
step: "Workspace",
|
|
9723
|
+
success: true,
|
|
9724
|
+
message: "Not found (already cleaned)"
|
|
9725
|
+
});
|
|
9726
|
+
}
|
|
9727
|
+
if (runId && existsSync4(statusFilePath)) {
|
|
9728
|
+
try {
|
|
9729
|
+
const status = JSON.parse(
|
|
9730
|
+
readFileSync4(statusFilePath, "utf-8")
|
|
9731
|
+
);
|
|
9732
|
+
const oldCount = status.active_runs;
|
|
9733
|
+
status.active_run_ids = status.active_run_ids.filter(
|
|
9734
|
+
(id) => id !== runId
|
|
9735
|
+
);
|
|
9736
|
+
status.active_runs = status.active_run_ids.length;
|
|
9737
|
+
status.updated_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
9738
|
+
writeFileSync3(statusFilePath, JSON.stringify(status, null, 2));
|
|
9739
|
+
results.push({
|
|
9740
|
+
step: "status.json",
|
|
9741
|
+
success: true,
|
|
9742
|
+
message: `Updated (active_runs: ${oldCount} -> ${status.active_runs})`
|
|
9743
|
+
});
|
|
9744
|
+
} catch (error) {
|
|
9745
|
+
results.push({
|
|
9746
|
+
step: "status.json",
|
|
9747
|
+
success: false,
|
|
9748
|
+
message: error instanceof Error ? error.message : "Unknown error"
|
|
9749
|
+
});
|
|
9750
|
+
}
|
|
9751
|
+
} else {
|
|
9752
|
+
results.push({
|
|
9753
|
+
step: "status.json",
|
|
9754
|
+
success: true,
|
|
9755
|
+
message: "No update needed"
|
|
9756
|
+
});
|
|
9757
|
+
}
|
|
9758
|
+
console.log("");
|
|
9759
|
+
let allSuccess = true;
|
|
9760
|
+
for (const r of results) {
|
|
9761
|
+
const icon = r.success ? "\u2713" : "\u2717";
|
|
9762
|
+
console.log(` ${icon} ${r.step}: ${r.message}`);
|
|
9763
|
+
if (!r.success) allSuccess = false;
|
|
9764
|
+
}
|
|
9765
|
+
console.log("");
|
|
9766
|
+
if (allSuccess) {
|
|
9767
|
+
console.log(`Run ${vmId} killed successfully.`);
|
|
9768
|
+
process.exit(0);
|
|
9769
|
+
} else {
|
|
9770
|
+
console.log(`Run ${vmId} cleanup completed with errors.`);
|
|
9771
|
+
process.exit(1);
|
|
9772
|
+
}
|
|
9773
|
+
} catch (error) {
|
|
9774
|
+
console.error(
|
|
9775
|
+
`Error: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
9776
|
+
);
|
|
9777
|
+
process.exit(1);
|
|
9778
|
+
}
|
|
9779
|
+
}
|
|
9780
|
+
);
|
|
9781
|
+
function resolveRunId(input, statusFilePath) {
|
|
9782
|
+
if (input.includes("-")) {
|
|
9783
|
+
const vmId = input.split("-")[0];
|
|
9784
|
+
return { vmId: vmId ?? input, runId: input };
|
|
9785
|
+
}
|
|
9786
|
+
if (existsSync4(statusFilePath)) {
|
|
9787
|
+
try {
|
|
9788
|
+
const status = JSON.parse(
|
|
9789
|
+
readFileSync4(statusFilePath, "utf-8")
|
|
9790
|
+
);
|
|
9791
|
+
const match = status.active_run_ids.find(
|
|
9792
|
+
(id) => id.startsWith(input)
|
|
9793
|
+
);
|
|
9794
|
+
if (match) {
|
|
9795
|
+
return { vmId: input, runId: match };
|
|
9796
|
+
}
|
|
9797
|
+
} catch {
|
|
9798
|
+
}
|
|
9799
|
+
}
|
|
9800
|
+
return { vmId: input, runId: null };
|
|
9801
|
+
}
|
|
9802
|
+
async function confirm(message) {
|
|
9803
|
+
const rl = readline2.createInterface({
|
|
9804
|
+
input: process.stdin,
|
|
9805
|
+
output: process.stdout
|
|
9806
|
+
});
|
|
9807
|
+
return new Promise((resolve) => {
|
|
9808
|
+
rl.question(`${message} [y/N] `, (answer) => {
|
|
9809
|
+
rl.close();
|
|
9810
|
+
resolve(answer.toLowerCase() === "y" || answer.toLowerCase() === "yes");
|
|
9811
|
+
});
|
|
9812
|
+
});
|
|
9813
|
+
}
|
|
9814
|
+
|
|
9815
|
+
// src/commands/benchmark.ts
|
|
9816
|
+
import { Command as Command4 } from "commander";
|
|
8878
9817
|
import crypto from "crypto";
|
|
8879
9818
|
|
|
8880
9819
|
// src/lib/timing.ts
|
|
@@ -8925,7 +9864,7 @@ function createBenchmarkContext(prompt, options) {
|
|
|
8925
9864
|
cliAgentType: options.agentType
|
|
8926
9865
|
};
|
|
8927
9866
|
}
|
|
8928
|
-
var benchmarkCommand = new
|
|
9867
|
+
var benchmarkCommand = new Command4("benchmark").description(
|
|
8929
9868
|
"Run a VM performance benchmark (executes bash command directly)"
|
|
8930
9869
|
).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) => {
|
|
8931
9870
|
const timer = new Timer();
|
|
@@ -8965,10 +9904,11 @@ var benchmarkCommand = new Command3("benchmark").description(
|
|
|
8965
9904
|
});
|
|
8966
9905
|
|
|
8967
9906
|
// src/index.ts
|
|
8968
|
-
var version = true ? "2.
|
|
9907
|
+
var version = true ? "2.13.1" : "0.1.0";
|
|
8969
9908
|
program.name("vm0-runner").version(version).description("Self-hosted runner for VM0 agents");
|
|
8970
9909
|
program.addCommand(startCommand);
|
|
8971
|
-
program.addCommand(
|
|
9910
|
+
program.addCommand(doctorCommand);
|
|
9911
|
+
program.addCommand(killCommand);
|
|
8972
9912
|
program.addCommand(benchmarkCommand);
|
|
8973
9913
|
program.parse();
|
|
8974
9914
|
//# sourceMappingURL=index.js.map
|