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