@agent-vm/gateway-lifecycle 0.0.115

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/dist/index.js ADDED
@@ -0,0 +1,1323 @@
1
+ import { v7, validate, version } from "uuid";
2
+ //#region src/gateway-runtime-contract.ts
3
+ const gatewayTypeValues = [
4
+ "openclaw",
5
+ "hermes",
6
+ "worker"
7
+ ];
8
+ function buildGatewaySessionLabel(projectNamespace, zoneId) {
9
+ return `${projectNamespace}:${zoneId}:gateway`;
10
+ }
11
+ function buildToolSessionLabel(projectNamespace, zoneId, tcpSlot) {
12
+ return `${projectNamespace}:${zoneId}:tool:${tcpSlot}`;
13
+ }
14
+ //#endregion
15
+ //#region src/gateway-lifecycle.ts
16
+ const gatewayToolPortalTelemetryServiceName = "agent-vm-tool-portal";
17
+ const gatewayFrameworkTelemetryServiceNames = Object.freeze({
18
+ hermes: "agent-vm-hermes",
19
+ openclaw: "agent-vm-openclaw"
20
+ });
21
+ const gatewayTelemetrySourcePolicy = Object.freeze({
22
+ admitBaggage: false,
23
+ captureContent: false
24
+ });
25
+ const gatewayTelemetryAdmissionLimits = Object.freeze({
26
+ maxExportBatchRecords: 64,
27
+ maxQueuedRecordsPerSignal: 256,
28
+ maxRecordBytes: 65536
29
+ });
30
+ function createGatewayTelemetryProducerSafetyContract() {
31
+ if (gatewayTelemetryAdmissionLimits.maxExportBatchRecords > gatewayTelemetryAdmissionLimits.maxQueuedRecordsPerSignal) throw new Error("Gateway telemetry maxExportBatchRecords must not exceed maxQueuedRecordsPerSignal.");
32
+ return {
33
+ admissionLimits: { ...gatewayTelemetryAdmissionLimits },
34
+ sourcePolicy: { ...gatewayTelemetrySourcePolicy }
35
+ };
36
+ }
37
+ //#endregion
38
+ //#region src/audience.ts
39
+ const vmAudienceValues = [
40
+ "gateway",
41
+ "tool-vm",
42
+ "both"
43
+ ];
44
+ const controllerVmHost = "controller.vm.host";
45
+ function targetsAudience(configAudience, runtimeAudience) {
46
+ return configAudience === runtimeAudience || configAudience === "both";
47
+ }
48
+ function egressHostsForAudience(egressHosts, runtimeAudience) {
49
+ return egressHosts.filter((egressHost) => targetsAudience(egressHost.audience, runtimeAudience)).map((egressHost) => egressHost.host);
50
+ }
51
+ function gatewayVmAllowedHosts(egressHosts) {
52
+ return Array.from(new Set(egressHostsForAudience(egressHosts, "gateway").filter((host) => host !== controllerVmHost)));
53
+ }
54
+ function workerVmAllowedHosts(egressHosts) {
55
+ return Array.from(new Set(egressHostsForAudience(egressHosts, "gateway").filter((host) => host !== controllerVmHost)));
56
+ }
57
+ //#endregion
58
+ //#region src/websocket-upgrade-policy.ts
59
+ function escapeRegExpLiteral(value) {
60
+ return value.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
61
+ }
62
+ function hostMatchesPattern(host, pattern) {
63
+ const normalizedPattern = pattern.trim().toLowerCase();
64
+ if (normalizedPattern === "") return false;
65
+ if (normalizedPattern === "*") return true;
66
+ return new RegExp(`^${normalizedPattern.split("*").map(escapeRegExpLiteral).join(".*")}$`, "iu").test(host.toLowerCase());
67
+ }
68
+ function isWebSocketUpgradeRequest(request) {
69
+ const upgrade = request.headers.get("upgrade")?.toLowerCase() ?? "";
70
+ const connection = request.headers.get("connection")?.toLowerCase() ?? "";
71
+ return upgrade === "websocket" || connection.split(",").map((token) => token.trim()).includes("upgrade") || request.headers.has("sec-websocket-key") || request.headers.has("sec-websocket-version");
72
+ }
73
+ function requestSchemeForRule(url) {
74
+ if (url.protocol === "http:") return "ws";
75
+ if (url.protocol === "https:") return "wss";
76
+ return null;
77
+ }
78
+ function requestPortForScheme(url, scheme) {
79
+ if (url.port.length > 0) return Number.parseInt(url.port, 10);
80
+ return scheme === "wss" ? 443 : 80;
81
+ }
82
+ function defaultPortForScheme(scheme) {
83
+ return scheme === "wss" ? 443 : 80;
84
+ }
85
+ function websocketRuleMatchesRequest(rule, request) {
86
+ const url = new URL(request.url);
87
+ const requestScheme = requestSchemeForRule(url);
88
+ if (requestScheme !== rule.scheme) return false;
89
+ if (!hostMatchesPattern(url.hostname, rule.host)) return false;
90
+ if (requestPortForScheme(url, requestScheme) !== (rule.port ?? defaultPortForScheme(rule.scheme))) return false;
91
+ if (rule.path !== void 0 && url.pathname !== rule.path) return false;
92
+ return true;
93
+ }
94
+ function websocketUpgradesForAudience(rules, runtimeAudience) {
95
+ return (rules ?? []).filter((rule) => targetsAudience(rule.audience, runtimeAudience));
96
+ }
97
+ function createWebSocketUpgradeRequestGuard(options) {
98
+ const runtimeRules = websocketUpgradesForAudience(options.rules, options.runtimeAudience);
99
+ return async (request) => {
100
+ if (!isWebSocketUpgradeRequest(request)) return;
101
+ if (runtimeRules.some((rule) => websocketRuleMatchesRequest(rule, request))) return;
102
+ return new Response("WebSocket upgrade blocked by agent-vm policy", {
103
+ status: 403,
104
+ statusText: "Forbidden"
105
+ });
106
+ };
107
+ }
108
+ //#endregion
109
+ //#region src/gateway-control-private-environment.ts
110
+ const GATEWAY_CONTROL_CALLER_CONTEXT_PROOF_KEY_ENV = "AGENT_VM_GATEWAY_CONTROL_CALLER_CONTEXT_PROOF_KEY";
111
+ const GATEWAY_CONTROL_CALLER_CONTEXT_AGENT_AUTHORITY_KEYS_ENV = "AGENT_VM_GATEWAY_CONTROL_CALLER_CONTEXT_AGENT_AUTHORITY_KEYS";
112
+ const GATEWAY_CONTROL_PRIVATE_ENVIRONMENT_NAMES = [GATEWAY_CONTROL_CALLER_CONTEXT_AGENT_AUTHORITY_KEYS_ENV, GATEWAY_CONTROL_CALLER_CONTEXT_PROOF_KEY_ENV];
113
+ //#endregion
114
+ //#region src/health/controller-request-policy.ts
115
+ const gatewayInternalControllerRequestOperations = [
116
+ "workspace-git-push",
117
+ "lease-create",
118
+ "lease-get",
119
+ "lease-peek",
120
+ "lease-renew",
121
+ "lease-release",
122
+ "lease-use-start",
123
+ "lease-heartbeat",
124
+ "lease-use-end"
125
+ ];
126
+ const dedicatedControllerRequestHealthEventOperationSet = new Set(["lease-heartbeat", "lease-renew"]);
127
+ function isGenericControllerRequestEventOperation(operation) {
128
+ return !dedicatedControllerRequestHealthEventOperationSet.has(operation);
129
+ }
130
+ const genericControllerRequestEventOperations = [...gatewayInternalControllerRequestOperations].filter(isGenericControllerRequestEventOperation);
131
+ const externalControllerRoutes = [
132
+ "GET /controller-status",
133
+ "GET /zones/:zoneId/status",
134
+ "GET /zones/:zoneId/health",
135
+ "GET /zones/:zoneId/logs",
136
+ "POST /zones/:zoneId/credentials/refresh",
137
+ "POST /zones/:zoneId/destroy",
138
+ "POST /zones/:zoneId/upgrade",
139
+ "GET /zones/:zoneId/tasks/:taskId",
140
+ "POST /zones/:zoneId/worker-tasks",
141
+ "POST /zones/:zoneId/tasks/:taskId/close",
142
+ "POST /zones/:zoneId/enable-ssh",
143
+ "POST /zones/:zoneId/execute-command",
144
+ "POST /stop-controller"
145
+ ];
146
+ var ControllerRequestPolicyTransportError = class extends Error {
147
+ code;
148
+ operation;
149
+ constructor(options) {
150
+ const causeMessage = options.cause instanceof Error ? options.cause.message : String(options.cause);
151
+ super(`${options.operation} ${options.code}: ${causeMessage}`, { cause: options.cause });
152
+ this.code = options.code;
153
+ this.operation = options.operation;
154
+ }
155
+ };
156
+ async function drainControllerResponseBody(response) {
157
+ if (response.bodyUsed) return;
158
+ await response.text().catch(() => void 0);
159
+ }
160
+ const controllerRequestPolicies = {
161
+ "workspace-git-push": {
162
+ idempotency: "unsafe-mutation",
163
+ maxAttempts: 1,
164
+ retryBaseDelayMs: 0,
165
+ retryEnabled: false,
166
+ retryStatuses: [],
167
+ timeoutMs: 12e4
168
+ },
169
+ "lease-create": {
170
+ idempotency: "unsafe-mutation",
171
+ maxAttempts: 1,
172
+ retryBaseDelayMs: 0,
173
+ retryEnabled: false,
174
+ retryStatuses: [],
175
+ timeoutMs: 18e4
176
+ },
177
+ "lease-get": {
178
+ idempotency: "read",
179
+ maxAttempts: 2,
180
+ retryBaseDelayMs: 250,
181
+ retryEnabled: true,
182
+ retryStatuses: [503, 504],
183
+ timeoutMs: 5e3
184
+ },
185
+ "lease-peek": {
186
+ idempotency: "read",
187
+ maxAttempts: 2,
188
+ retryBaseDelayMs: 250,
189
+ retryEnabled: true,
190
+ retryStatuses: [503, 504],
191
+ timeoutMs: 5e3
192
+ },
193
+ "lease-renew": {
194
+ idempotency: "safe-mutation",
195
+ maxAttempts: 3,
196
+ retryBaseDelayMs: 250,
197
+ retryEnabled: true,
198
+ retryStatuses: [
199
+ 429,
200
+ 503,
201
+ 504
202
+ ],
203
+ timeoutMs: 1e4
204
+ },
205
+ "lease-release": {
206
+ idempotency: "safe-mutation",
207
+ maxAttempts: 2,
208
+ retryBaseDelayMs: 250,
209
+ retryEnabled: true,
210
+ retryStatuses: [503, 504],
211
+ timeoutMs: 5e3
212
+ },
213
+ "lease-use-start": {
214
+ idempotency: "safe-mutation",
215
+ maxAttempts: 2,
216
+ retryBaseDelayMs: 250,
217
+ retryEnabled: true,
218
+ retryStatuses: [
219
+ 429,
220
+ 503,
221
+ 504
222
+ ],
223
+ timeoutMs: 1e4
224
+ },
225
+ "lease-heartbeat": {
226
+ idempotency: "safe-mutation",
227
+ maxAttempts: 2,
228
+ retryBaseDelayMs: 250,
229
+ retryEnabled: true,
230
+ retryStatuses: [
231
+ 429,
232
+ 503,
233
+ 504
234
+ ],
235
+ timeoutMs: 5e3
236
+ },
237
+ "lease-use-end": {
238
+ idempotency: "safe-mutation",
239
+ maxAttempts: 2,
240
+ retryBaseDelayMs: 250,
241
+ retryEnabled: true,
242
+ retryStatuses: [503, 504],
243
+ timeoutMs: 5e3
244
+ }
245
+ };
246
+ //#endregion
247
+ //#region src/health/agent-vm-health.ts
248
+ const agentVmHealthEventKinds = [
249
+ "gateway-service-health",
250
+ "gateway-control-session",
251
+ "controller-request",
252
+ "lease-renew",
253
+ "lease-heartbeat",
254
+ "caller-context-rejection",
255
+ "tool-vm-ssh",
256
+ "gateway-plugin-health",
257
+ "agent-channel-provider-health",
258
+ "gateway-recovery",
259
+ "gateway-recovery-suspended"
260
+ ];
261
+ const agentVmHealthResultKinds = [
262
+ "ok",
263
+ "failed",
264
+ "timeout",
265
+ "stale"
266
+ ];
267
+ const toolVmLeaseLifecycleEventRoles = ["plugin_observation", "controller_final"];
268
+ const toolVmLeaseLifecycleTransitions = [
269
+ "current_to_stale",
270
+ "current_to_retired",
271
+ "deprecated_to_reacquired",
272
+ "deprecated_to_retired",
273
+ "stale_to_reacquired",
274
+ "stale_to_retired",
275
+ "retired_rejected"
276
+ ];
277
+ const toolVmLeaseReacquiredLifecycleTransitions = ["deprecated_to_reacquired", "stale_to_reacquired"];
278
+ const toolVmLeaseCallerContextStates = [
279
+ "ok",
280
+ "absent",
281
+ "stale",
282
+ "session_mismatch",
283
+ "not_applicable"
284
+ ];
285
+ const toolVmLeaseRejectionReasons = [
286
+ "caller_context_absent",
287
+ "caller_context_session_mismatch",
288
+ "caller_context_stale",
289
+ "lease_absent",
290
+ "lease_authority_absent",
291
+ "lease_force_released",
292
+ "lease_generation_stale",
293
+ "lease_reacquire_required",
294
+ "lease_releasing",
295
+ "lease_retired",
296
+ "lease_use_tombstoned",
297
+ "ownership_denied",
298
+ "runtime_not_ready"
299
+ ];
300
+ const agentChannelProviderHealthKinds = [
301
+ "healthy",
302
+ "transitioning",
303
+ "unhealthy-recoverable",
304
+ "unhealthy-unrecoverable"
305
+ ];
306
+ const agentChannelProviderHealthDetailKeys = [
307
+ "closeCode",
308
+ "providerType",
309
+ "reconnectAttempt",
310
+ "reconnecting",
311
+ "sleepResumeSuspected",
312
+ "statusCode"
313
+ ];
314
+ const gatewayRecoveryHealthReasons = [
315
+ "agent-channel-provider-unhealthy",
316
+ "gateway-control-session-unhealthy",
317
+ "gateway-service-unhealthy"
318
+ ];
319
+ const gatewayControlSessionHealthOperations = [
320
+ "control-session-hello",
321
+ "control-session-heartbeat",
322
+ "control-session-disconnect",
323
+ "control-session-reconnect"
324
+ ];
325
+ const zoneHealthStateKinds = [
326
+ "unknown",
327
+ "ok",
328
+ "stale",
329
+ "failed"
330
+ ];
331
+ const zoneHealthIssueKinds = [
332
+ "gateway-service-unhealthy",
333
+ "gateway-control-session-unhealthy",
334
+ "controller-request-failing",
335
+ "lease-heartbeat-failing",
336
+ "lease-renew-failing",
337
+ "tool-vm-ssh-failing",
338
+ "gateway-plugin-unhealthy",
339
+ "agent-channel-provider-unhealthy",
340
+ "gateway-recovery-failed",
341
+ "gateway-recovery-suspended",
342
+ "health-event-stale"
343
+ ];
344
+ function isRecord(value) {
345
+ return typeof value === "object" && value !== null && !Array.isArray(value);
346
+ }
347
+ function isOneOf(values, value) {
348
+ return typeof value === "string" && values.includes(value);
349
+ }
350
+ function isNonNegativeFiniteNumber(value) {
351
+ return typeof value === "number" && Number.isFinite(value) && value >= 0;
352
+ }
353
+ function hasBaseEventFields(record) {
354
+ return isNonNegativeFiniteNumber(record.observedAtMs) && isOneOf(agentVmHealthResultKinds, record.result) && typeof record.zoneId === "string" && record.zoneId.length > 0;
355
+ }
356
+ function optionalString(value) {
357
+ return value === void 0 || typeof value === "string";
358
+ }
359
+ function optionalNonEmptyString(value) {
360
+ return value === void 0 || typeof value === "string" && value.length > 0;
361
+ }
362
+ function optionalOneOf(values, value) {
363
+ return value === void 0 || isOneOf(values, value);
364
+ }
365
+ function isToolVmLeaseReacquiredTransition(value) {
366
+ return isOneOf(toolVmLeaseReacquiredLifecycleTransitions, value);
367
+ }
368
+ function optionalStatusCode(value) {
369
+ return value === void 0 || Number.isInteger(value);
370
+ }
371
+ function optionalNonNegativeInteger(value) {
372
+ return value === void 0 || Number.isInteger(value) && Number(value) >= 0;
373
+ }
374
+ function isNonNegativeInteger(value) {
375
+ return Number.isInteger(value) && Number(value) >= 0;
376
+ }
377
+ function isPositiveInteger(value) {
378
+ return Number.isInteger(value) && Number(value) > 0;
379
+ }
380
+ function isRedactedHealthDetails(value) {
381
+ if (value === void 0) return true;
382
+ if (!isRecord(value)) return false;
383
+ for (const [key, detailValue] of Object.entries(value)) {
384
+ if (!isOneOf(agentChannelProviderHealthDetailKeys, key)) return false;
385
+ if (!isChannelProviderHealthDetailValue(key, detailValue)) return false;
386
+ }
387
+ return true;
388
+ }
389
+ function isChannelProviderHealthDetailValue(key, value) {
390
+ switch (key) {
391
+ case "closeCode":
392
+ case "reconnectAttempt":
393
+ case "statusCode": return isNonNegativeInteger(value);
394
+ case "providerType": return typeof value === "string" && /^[a-z][a-z0-9._-]{0,31}$/iu.test(value);
395
+ case "reconnecting":
396
+ case "sleepResumeSuspected": return typeof value === "boolean";
397
+ }
398
+ return assertNeverAgentChannelProviderHealthDetailKey(key);
399
+ }
400
+ function assertNeverAgentChannelProviderHealthDetailKey(key) {
401
+ throw new Error(`Unhandled agent channel provider health detail key: ${String(key)}`);
402
+ }
403
+ function isGatewayRecoveryTimeoutErrorCode(value) {
404
+ return value === "recovery-callback-unconfigured" || value === "recovery-timeout";
405
+ }
406
+ function isAgentChannelProviderHealthResultConsistent(health, result) {
407
+ switch (health) {
408
+ case "healthy":
409
+ case "transitioning": return result === "ok";
410
+ case "unhealthy-recoverable":
411
+ case "unhealthy-unrecoverable": return result === "failed";
412
+ }
413
+ return assertNeverAgentChannelProviderHealth(health);
414
+ }
415
+ function hasValidToolVmLeaseLifecycleFields(value) {
416
+ if (!(value.activeUseId !== void 0 || value.callerContextState !== void 0 || value.leaseRejectionReason !== void 0 || value.lifecycleEventRole !== void 0 || value.lifecycleTransition !== void 0 || value.oldLeaseId !== void 0 || value.replacementLeaseId !== void 0 || value.transitionId !== void 0)) return true;
417
+ if (!(optionalNonEmptyString(value.activeUseId) && optionalOneOf(toolVmLeaseCallerContextStates, value.callerContextState) && optionalOneOf(toolVmLeaseRejectionReasons, value.leaseRejectionReason) && isOneOf(toolVmLeaseLifecycleEventRoles, value.lifecycleEventRole) && isOneOf(toolVmLeaseLifecycleTransitions, value.lifecycleTransition) && typeof value.oldLeaseId === "string" && value.oldLeaseId.length > 0 && typeof value.transitionId === "string" && value.transitionId.length > 0)) return false;
418
+ if (isToolVmLeaseReacquiredTransition(value.lifecycleTransition)) return typeof value.replacementLeaseId === "string" && value.replacementLeaseId.length > 0;
419
+ return value.replacementLeaseId === void 0 && optionalOneOf(toolVmLeaseLifecycleEventRoles, value.lifecycleEventRole) && optionalOneOf(toolVmLeaseLifecycleTransitions, value.lifecycleTransition);
420
+ }
421
+ function isAgentVmHealthEvent(value) {
422
+ if (!isRecord(value) || !hasBaseEventFields(value)) return false;
423
+ switch (value.kind) {
424
+ case "caller-context-rejection": return isOneOf([
425
+ "lease_create",
426
+ "lease_get",
427
+ "lease_peek",
428
+ "lease_reacquire",
429
+ "lease_release",
430
+ "lease_renew",
431
+ "lease_use_end",
432
+ "lease_use_heartbeat",
433
+ "lease_use_start"
434
+ ], value.operation) && isOneOf([
435
+ "caller_context_absent",
436
+ "caller_context_session_mismatch",
437
+ "caller_context_stale"
438
+ ], value.reason) && value.result === "failed";
439
+ case "gateway-service-health": return typeof value.path === "string" && value.path.length > 0 && Number.isInteger(value.port) && optionalStatusCode(value.statusCode);
440
+ case "gateway-control-session": return optionalString(value.bootId) && optionalString(value.connectionId) && value.domain === "gateway_control" && isNonNegativeFiniteNumber(value.elapsedMs) && isOneOf(gatewayControlSessionHealthOperations, value.operation) && typeof value.peerId === "string" && value.peerId.length > 0 && optionalString(value.sessionId);
441
+ case "controller-request": return Number.isInteger(value.attempt) && isNonNegativeFiniteNumber(value.elapsedMs) && optionalString(value.errorCode) && Number.isInteger(value.maxAttempts) && isOneOf(genericControllerRequestEventOperations, value.operation) && optionalStatusCode(value.statusCode);
442
+ case "lease-renew": return typeof value.agentId === "string" && isNonNegativeFiniteNumber(value.elapsedMs) && optionalString(value.errorCode) && typeof value.leaseId === "string";
443
+ case "lease-heartbeat": return typeof value.agentId === "string" && isNonNegativeFiniteNumber(value.elapsedMs) && optionalString(value.errorCode) && typeof value.leaseId === "string" && typeof value.useId === "string";
444
+ case "tool-vm-ssh": return typeof value.agentId === "string" && isNonNegativeFiniteNumber(value.elapsedMs) && optionalString(value.errorCode) && typeof value.leaseId === "string" && isOneOf([
445
+ "command",
446
+ "file-bridge",
447
+ "finalize",
448
+ "probe"
449
+ ], value.operation) && hasValidToolVmLeaseLifecycleFields(value);
450
+ case "gateway-plugin-health": return isOneOf(gatewayTypeValues, value.gatewayService) && isOneOf([
451
+ "starting",
452
+ "ready",
453
+ "stopping",
454
+ "failed"
455
+ ], value.state);
456
+ case "agent-channel-provider-health": return typeof value.channelProviderId === "string" && value.channelProviderId.length > 0 && isOneOf(agentChannelProviderHealthKinds, value.health) && isOneOf(agentVmHealthResultKinds, value.result) && isAgentChannelProviderHealthResultConsistent(value.health, value.result) && optionalNonNegativeInteger(value.transitionStartedAtMs) && optionalNonNegativeInteger(value.unhealthySinceMs) && isRedactedHealthDetails(value.details);
457
+ case "gateway-recovery":
458
+ if (!isOneOf([
459
+ "gateway-vm-cold-start",
460
+ "gateway-vm-restart",
461
+ "observe-only",
462
+ "operator-required"
463
+ ], value.action) || !isNonNegativeInteger(value.consecutiveFailures) || !isPositiveInteger(value.cooldownMs) || !isNonNegativeFiniteNumber(value.elapsedMs) || !isOneOf(gatewayRecoveryHealthReasons, value.reason) || !optionalString(value.operationId)) return false;
464
+ if (value.result === "ok") {
465
+ if (value.action !== "gateway-vm-cold-start" && value.action !== "gateway-vm-restart") return false;
466
+ if (!(isNonNegativeInteger(value.leaseReleaseFailureCount) && typeof value.newBootedAt === "string" && isNonNegativeInteger(value.newHostPid) && typeof value.newVmId === "string" && value.errorCode === void 0)) return false;
467
+ if (value.action === "gateway-vm-cold-start") return value.oldBootedAt === void 0 && value.oldHostPid === void 0 && value.oldVmId === void 0;
468
+ return typeof value.oldBootedAt === "string" && isNonNegativeInteger(value.oldHostPid) && typeof value.oldVmId === "string";
469
+ }
470
+ if (value.result === "failed") {
471
+ if (typeof value.errorCode !== "string" || value.errorCode.length === 0 || !optionalNonNegativeInteger(value.leaseReleaseFailureCount) || value.newBootedAt !== void 0 || value.newHostPid !== void 0 || value.newVmId !== void 0 || !optionalString(value.operationId)) return false;
472
+ if (value.action === "gateway-vm-restart") {
473
+ if (isGatewayRecoveryTimeoutErrorCode(value.errorCode)) return optionalString(value.oldBootedAt) && optionalNonNegativeInteger(value.oldHostPid) && optionalString(value.oldVmId);
474
+ return optionalString(value.oldBootedAt) && optionalNonNegativeInteger(value.oldHostPid) && typeof value.oldVmId === "string" && value.oldVmId.length > 0;
475
+ }
476
+ return (value.action === "gateway-vm-cold-start" || value.action === "observe-only" || value.action === "operator-required") && value.oldBootedAt === void 0 && value.oldHostPid === void 0 && value.oldVmId === void 0;
477
+ }
478
+ return false;
479
+ case "gateway-recovery-suspended": return isOneOf([
480
+ "gateway-vm-cold-start",
481
+ "gateway-vm-restart",
482
+ "observe-only",
483
+ "operator-required"
484
+ ], value.action) && isNonNegativeInteger(value.consecutiveFailedRecoveries) && isNonNegativeInteger(value.consecutiveFailures) && isPositiveInteger(value.cooldownMs) && value.errorCode === "max-failed-recoveries" && isPositiveInteger(value.failedRecoveryResetMs) && optionalString(value.operationId) && isOneOf(gatewayRecoveryHealthReasons, value.reason) && value.result === "failed";
485
+ default: return false;
486
+ }
487
+ }
488
+ function healthEventBucketKey(event) {
489
+ switch (event.kind) {
490
+ case "caller-context-rejection": return `${event.zoneId}:${event.kind}:${event.operation}:${event.reason}`;
491
+ case "gateway-control-session": return `${event.zoneId}:${event.kind}`;
492
+ case "gateway-service-health": return `${event.zoneId}:${event.kind}`;
493
+ case "controller-request": return `${event.zoneId}:${event.kind}:${event.operation}`;
494
+ case "lease-heartbeat": return `${event.zoneId}:${event.kind}:${event.leaseId}:${event.useId}`;
495
+ case "lease-renew": return `${event.zoneId}:${event.kind}:${event.leaseId}`;
496
+ case "tool-vm-ssh":
497
+ if (event.transitionId !== void 0) return `${event.zoneId}:${event.kind}:lifecycle:${event.transitionId}`;
498
+ return `${event.zoneId}:${event.kind}:${event.leaseId}:${event.operation}`;
499
+ case "gateway-plugin-health": return `${event.zoneId}:${event.kind}:${event.gatewayService}`;
500
+ case "agent-channel-provider-health": return `${event.zoneId}:${event.kind}:${event.channelProviderId}`;
501
+ case "gateway-recovery": return `${event.zoneId}:${event.kind}:${event.action}`;
502
+ case "gateway-recovery-suspended": return `${event.zoneId}:${event.kind}:${event.action}`;
503
+ }
504
+ return assertNeverHealthEvent(event);
505
+ }
506
+ function isHealthIssueBearingEvent(event) {
507
+ return event.kind !== "caller-context-rejection";
508
+ }
509
+ function failedIssueKindForEvent(event) {
510
+ switch (event.kind) {
511
+ case "gateway-service-health": return "gateway-service-unhealthy";
512
+ case "gateway-control-session": return "gateway-control-session-unhealthy";
513
+ case "controller-request": return "controller-request-failing";
514
+ case "lease-heartbeat": return "lease-heartbeat-failing";
515
+ case "lease-renew": return "lease-renew-failing";
516
+ case "tool-vm-ssh": return "tool-vm-ssh-failing";
517
+ case "gateway-plugin-health": return "gateway-plugin-unhealthy";
518
+ case "agent-channel-provider-health": return "agent-channel-provider-unhealthy";
519
+ case "gateway-recovery": return "gateway-recovery-failed";
520
+ case "gateway-recovery-suspended": return "gateway-recovery-suspended";
521
+ }
522
+ return assertNeverHealthEvent(event);
523
+ }
524
+ function assertNeverHealthEvent(event) {
525
+ throw new Error(`Unhandled health event kind: ${JSON.stringify(event)}`);
526
+ }
527
+ function assertNeverAgentChannelProviderHealth(health) {
528
+ throw new Error(`Unhandled agent channel provider health: ${String(health)}`);
529
+ }
530
+ function issueForEvent(event, options) {
531
+ if (!isHealthIssueBearingEvent(event)) return;
532
+ if (options.nowMs - event.observedAtMs > options.staleAfterMs && !isNonStalingSuccessfulEvent(event)) return {
533
+ kind: "health-event-stale",
534
+ latestEvent: event,
535
+ message: `${event.kind} health event is stale`,
536
+ sinceMs: event.observedAtMs
537
+ };
538
+ if (event.result === "failed" || event.result === "timeout" || event.result === "stale") return {
539
+ kind: failedIssueKindForEvent(event),
540
+ latestEvent: event,
541
+ message: `${event.kind} health event reported ${event.result}`,
542
+ sinceMs: event.observedAtMs
543
+ };
544
+ }
545
+ function isNonStalingSuccessfulEvent(event) {
546
+ return event.kind === "gateway-recovery" && event.result === "ok";
547
+ }
548
+ function isReacquiredControllerFinalEvent(event) {
549
+ return event.kind === "tool-vm-ssh" && event.lifecycleEventRole === "controller_final" && isToolVmLeaseReacquiredTransition(event.lifecycleTransition) && event.result === "ok";
550
+ }
551
+ function latestReacquiredAtMsByOldLeaseId(events) {
552
+ const latestByOldLeaseId = /* @__PURE__ */ new Map();
553
+ for (const event of events) {
554
+ if (!isReacquiredControllerFinalEvent(event)) continue;
555
+ const previousObservedAtMs = latestByOldLeaseId.get(event.oldLeaseId);
556
+ if (previousObservedAtMs === void 0 || previousObservedAtMs < event.observedAtMs) latestByOldLeaseId.set(event.oldLeaseId, event.observedAtMs);
557
+ }
558
+ return latestByOldLeaseId;
559
+ }
560
+ function isPlainToolVmSshEvent(event) {
561
+ return event.kind === "tool-vm-ssh" && event.transitionId === void 0;
562
+ }
563
+ function filterSupersededToolVmSshEvents(events) {
564
+ const latestReacquiredAtByOldLeaseId = latestReacquiredAtMsByOldLeaseId(events);
565
+ return events.filter((event) => {
566
+ if (!isPlainToolVmSshEvent(event)) return true;
567
+ const reacquiredAtMs = latestReacquiredAtByOldLeaseId.get(event.leaseId);
568
+ return reacquiredAtMs === void 0 || reacquiredAtMs < event.observedAtMs;
569
+ });
570
+ }
571
+ function deriveZoneHealthSnapshot(events, options) {
572
+ const latestByKey = /* @__PURE__ */ new Map();
573
+ for (const event of events) {
574
+ if (event.zoneId !== options.zoneId || event.kind === "caller-context-rejection") continue;
575
+ const key = healthEventBucketKey(event);
576
+ const previous = latestByKey.get(key);
577
+ if (!previous || previous.observedAtMs <= event.observedAtMs) latestByKey.set(key, event);
578
+ }
579
+ const latestEvents = filterSupersededToolVmSshEvents([...latestByKey.values()]).toSorted((first, second) => second.observedAtMs - first.observedAtMs);
580
+ if (latestEvents.length === 0) return {
581
+ kind: "unknown",
582
+ reason: "no-events",
583
+ zoneId: options.zoneId
584
+ };
585
+ const issues = latestEvents.map((event) => issueForEvent(event, options)).filter((issue) => issue !== void 0);
586
+ if (issues.length === 0) return {
587
+ kind: "ok",
588
+ latestEvents,
589
+ zoneId: options.zoneId
590
+ };
591
+ if (issues.some((issue) => issue.kind === "health-event-stale")) return {
592
+ issues,
593
+ kind: "stale",
594
+ latestEvents,
595
+ zoneId: options.zoneId
596
+ };
597
+ return {
598
+ issues,
599
+ kind: "failed",
600
+ latestEvents,
601
+ zoneId: options.zoneId
602
+ };
603
+ }
604
+ //#endregion
605
+ //#region src/git-read-allowlist.ts
606
+ function trimGitSuffix(repoPath) {
607
+ return repoPath.replace(/\.git$/u, "");
608
+ }
609
+ function normalizeRepoPath(repoPath) {
610
+ const trimmedRepoPath = trimGitSuffix(repoPath.trim().replace(/^\/+/u, ""));
611
+ const segments = trimmedRepoPath.split("/");
612
+ if (segments.length < 2 || segments.some((segment) => !/^[A-Za-z0-9_.-]+$/u.test(segment))) return;
613
+ return trimmedRepoPath;
614
+ }
615
+ function normalizeHost(host) {
616
+ const normalizedHost = host.trim().toLowerCase();
617
+ if (!/^[a-z0-9.-]+$/u.test(normalizedHost) || normalizedHost.startsWith(".")) return;
618
+ return normalizedHost;
619
+ }
620
+ function normalizeGitRepoForSshReadAllowlist(repoUrl) {
621
+ const trimmedRepoUrl = repoUrl.trim();
622
+ if (trimmedRepoUrl.length === 0) return;
623
+ let parsedUrl;
624
+ try {
625
+ parsedUrl = new URL(trimmedRepoUrl);
626
+ } catch {
627
+ parsedUrl = void 0;
628
+ }
629
+ if (parsedUrl !== void 0) {
630
+ const host = normalizeHost(parsedUrl.hostname);
631
+ const repoPath = normalizeRepoPath(parsedUrl.pathname);
632
+ return host === void 0 || repoPath === void 0 ? void 0 : {
633
+ host,
634
+ repoPath
635
+ };
636
+ }
637
+ const scpMatch = /^(?:[^@]+@)?(?<host>[A-Za-z0-9.-]+):(?<repoPath>[^:]+)$/u.exec(trimmedRepoUrl);
638
+ if (scpMatch?.groups?.host !== void 0 && scpMatch.groups.repoPath !== void 0) {
639
+ const host = normalizeHost(scpMatch.groups.host);
640
+ const repoPath = normalizeRepoPath(scpMatch.groups.repoPath);
641
+ return host === void 0 || repoPath === void 0 ? void 0 : {
642
+ host,
643
+ repoPath
644
+ };
645
+ }
646
+ const hostPathMatch = /^(?<host>[A-Za-z0-9.-]+)\/(?<repoPath>.+)$/u.exec(trimmedRepoUrl);
647
+ if (hostPathMatch?.groups?.host !== void 0 && hostPathMatch.groups.repoPath !== void 0) {
648
+ const repoPathWithoutHost = normalizeRepoPath(hostPathMatch.groups.repoPath);
649
+ const host = normalizeHost(hostPathMatch.groups.host);
650
+ if (host !== void 0 && repoPathWithoutHost !== void 0 && host.includes(".")) return {
651
+ host,
652
+ repoPath: repoPathWithoutHost
653
+ };
654
+ }
655
+ const bareRepoPath = normalizeRepoPath(trimmedRepoUrl);
656
+ if (bareRepoPath !== void 0) return {
657
+ host: "github.com",
658
+ repoPath: bareRepoPath
659
+ };
660
+ }
661
+ function normalizeGitReposForSshReadAllowlist(repoUrls) {
662
+ if (repoUrls === void 0) return {
663
+ allowedHosts: [],
664
+ allowedRepos: []
665
+ };
666
+ const allowedHosts = /* @__PURE__ */ new Set();
667
+ const allowedRepos = /* @__PURE__ */ new Set();
668
+ for (const repoUrl of repoUrls) {
669
+ const normalizedRepo = normalizeGitRepoForSshReadAllowlist(repoUrl);
670
+ if (normalizedRepo !== void 0) {
671
+ allowedHosts.add(normalizedRepo.host);
672
+ allowedRepos.add(normalizedRepo.repoPath);
673
+ }
674
+ }
675
+ return {
676
+ allowedHosts: [...allowedHosts].toSorted(),
677
+ allowedRepos: [...allowedRepos].toSorted()
678
+ };
679
+ }
680
+ function normalizeGitHubRepoForSshReadAllowlist(repoUrl) {
681
+ const trimmedRepoUrl = repoUrl.trim();
682
+ if (trimmedRepoUrl.length === 0) return;
683
+ const scpMatch = /^git@github\.com:(?<repoPath>[^:]+\/[^/]+)$/iu.exec(trimmedRepoUrl);
684
+ if (scpMatch?.groups?.repoPath !== void 0) return normalizeRepoPath(scpMatch.groups.repoPath);
685
+ const hostPathMatch = /^(?:github\.com\/)(?<repoPath>[^/]+\/[^/]+)$/iu.exec(trimmedRepoUrl);
686
+ if (hostPathMatch?.groups?.repoPath !== void 0) return normalizeRepoPath(hostPathMatch.groups.repoPath);
687
+ const bareRepoPath = normalizeRepoPath(trimmedRepoUrl);
688
+ if (bareRepoPath !== void 0) return bareRepoPath;
689
+ let parsedUrl;
690
+ try {
691
+ parsedUrl = new URL(trimmedRepoUrl);
692
+ } catch {
693
+ return;
694
+ }
695
+ if (parsedUrl.hostname.toLowerCase() !== "github.com") return;
696
+ return normalizeRepoPath(parsedUrl.pathname);
697
+ }
698
+ function normalizeGitHubReposForSshReadAllowlist(repoUrls) {
699
+ return normalizeGitReposForSshReadAllowlist(repoUrls).allowedRepos;
700
+ }
701
+ //#endregion
702
+ //#region src/force-ipv4-egress.ts
703
+ /**
704
+ * Canonical NODE_OPTIONS value for forcing IPv4-preference egress
705
+ * in agent-vm VMs.
706
+ *
707
+ * Background: the managed VM synthetic DNS path (when tcpHosts is enabled)
708
+ * returns a per-host IPv4 (reverse-lookable) and a single shared
709
+ * IPv4-mapped IPv6 (::ffff:198.18.0.1, NOT reverse-lookable). Node
710
+ * 20+'s fetch (via undici, autoSelectFamily: true) races both
711
+ * families; when the IPv6 race wins (~5-20% under sequential load),
712
+ * the VM egress router cannot route it and the request fails with a non-JSON
713
+ * 400 (HTTP) or 403 (TLS). The two flags below stop the race:
714
+ *
715
+ * --dns-result-order=ipv4first changes dns.lookup() so
716
+ * IPv4 addresses are listed
717
+ * before IPv6.
718
+ *
719
+ * --no-network-family-autoselection disables Node's Happy
720
+ * Eyeballs entirely. This is
721
+ * the load-bearing flag —
722
+ * --dns-result-order alone
723
+ * doesn't prevent Node from
724
+ * racing both families if
725
+ * IPv4 is slow.
726
+ *
727
+ * Composition: NODE_OPTIONS is whitespace-separated. To add more
728
+ * flags downstream, append rather than replace. Example:
729
+ *
730
+ * NODE_OPTIONS: `${FORCE_IPV4_EGRESS_NODE_OPTIONS} --inspect`
731
+ *
732
+ * Reference: see `shravan-claw@0ddf5f2:docs/wip/debugging/
733
+ * 2026-05-21-lease-keepalive-400-and-discord-403-ipv6-race.md`
734
+ * for the full root-cause analysis. Node-side flag references:
735
+ * https://github.com/nodejs/node/issues/54359 (autoSelectFamily
736
+ * revert recommendation by the Node core team).
737
+ */
738
+ const FORCE_IPV4_EGRESS_NODE_OPTIONS = "--dns-result-order=ipv4first --no-network-family-autoselection";
739
+ const FORCE_IPV4_EGRESS_NODE_OPTION_FLAGS = FORCE_IPV4_EGRESS_NODE_OPTIONS.split(/\s+/u);
740
+ /**
741
+ * Compose the forced IPv4-preference flags with a user-provided
742
+ * NODE_OPTIONS value (if any).
743
+ *
744
+ * Use this at every site where NODE_OPTIONS is set into a VM env
745
+ * block AFTER a spread of user-controlled secrets, to guarantee
746
+ * the forced flags are always present in the final value even if
747
+ * a zone secret happens to provide its own NODE_OPTIONS.
748
+ *
749
+ * Forced flags come FIRST so they are unambiguously applied.
750
+ * User-provided flags are appended verbatim except for duplicate
751
+ * forced IPv4-preference flags. Node treats NODE_OPTIONS as a
752
+ * whitespace-separated list and all flags apply.
753
+ *
754
+ * Returns just the forced flags if the user value is undefined,
755
+ * empty, or whitespace-only.
756
+ *
757
+ * Examples:
758
+ *
759
+ * composeNodeOptions(undefined)
760
+ * ──► '--dns-result-order=ipv4first --no-network-family-autoselection'
761
+ *
762
+ * composeNodeOptions('')
763
+ * ──► '--dns-result-order=ipv4first --no-network-family-autoselection'
764
+ *
765
+ * composeNodeOptions('--inspect=0.0.0.0:9229')
766
+ * ──► '--dns-result-order=ipv4first --no-network-family-autoselection
767
+ * --inspect=0.0.0.0:9229'
768
+ */
769
+ function composeNodeOptions(userValue) {
770
+ const trimmed = userValue?.trim() ?? "";
771
+ if (trimmed === "") return FORCE_IPV4_EGRESS_NODE_OPTIONS;
772
+ const userFlags = trimmed.split(/\s+/u).filter((flag) => !FORCE_IPV4_EGRESS_NODE_OPTION_FLAGS.includes(flag));
773
+ if (userFlags.length === 0) return FORCE_IPV4_EGRESS_NODE_OPTIONS;
774
+ return `${FORCE_IPV4_EGRESS_NODE_OPTIONS} ${userFlags.join(" ")}`;
775
+ }
776
+ //#endregion
777
+ //#region src/managed-gateway-boot-contract.ts
778
+ function assertPlainRecord(value, label) {
779
+ if (value === null || typeof value !== "object" || Array.isArray(value)) throw new Error(`${label} must be a plain object.`);
780
+ const prototype = Reflect.getPrototypeOf(value);
781
+ if (prototype !== Object.prototype && prototype !== null) throw new Error(`${label} must be a plain object.`);
782
+ }
783
+ function assertExactFields(record, label, requiredFields) {
784
+ const requiredFieldSet = new Set(requiredFields);
785
+ for (const fieldKey of Reflect.ownKeys(record)) {
786
+ if (typeof fieldKey !== "string") throw new Error(`${label} has an unknown symbol field.`);
787
+ const fieldName = fieldKey;
788
+ if (!requiredFieldSet.has(fieldName)) throw new Error(`${label} has unknown field '${fieldName}'.`);
789
+ const descriptor = Reflect.getOwnPropertyDescriptor(record, fieldName);
790
+ if (descriptor === void 0 || !Object.hasOwn(descriptor, "value")) throw new Error(`${label}.${fieldName} must be a data field.`);
791
+ }
792
+ for (const fieldName of requiredFields) if (!Object.hasOwn(record, fieldName)) throw new Error(`${label} is missing required field '${fieldName}'.`);
793
+ }
794
+ function parseLiteral(value, expected, label) {
795
+ if (value !== expected) throw new Error(`${label} must be ${JSON.stringify(expected)}.`);
796
+ return expected;
797
+ }
798
+ function parseNonEmptyString(value, label) {
799
+ if (typeof value !== "string" || value.length === 0) throw new Error(`${label} must be a non-empty string.`);
800
+ return value;
801
+ }
802
+ function parseAbsoluteGuestPath(value, label) {
803
+ const guestPath = parseNonEmptyString(value, label);
804
+ if (!guestPath.startsWith("/") || guestPath.includes("\0") || guestPath.includes("//") || guestPath.length > 1 && guestPath.endsWith("/") || guestPath.split("/").some((segment) => segment === "." || segment === "..")) throw new Error(`${label} must be a normalized absolute guest path.`);
805
+ return guestPath;
806
+ }
807
+ function parseHttpPath(value, label) {
808
+ const httpPath = parseNonEmptyString(value, label);
809
+ if (!httpPath.startsWith("/") || httpPath.includes("\0")) throw new Error(`${label} must be an absolute HTTP path.`);
810
+ return httpPath;
811
+ }
812
+ function parseGuestPort(value, label) {
813
+ if (!Number.isInteger(value) || typeof value !== "number" || value < 1 || value > 65535) throw new Error(`${label} must be an integer TCP port from 1 through 65535.`);
814
+ return value;
815
+ }
816
+ function parseLogIdentity(value, label) {
817
+ assertPlainRecord(value, label);
818
+ assertExactFields(value, label, ["guestPath", "serviceName"]);
819
+ return Object.freeze({
820
+ guestPath: parseAbsoluteGuestPath(value.guestPath, `${label}.guestPath`),
821
+ serviceName: parseNonEmptyString(value.serviceName, `${label}.serviceName`)
822
+ });
823
+ }
824
+ function parseToolPortalReadiness(value, label) {
825
+ assertPlainRecord(value, label);
826
+ assertExactFields(value, label, [
827
+ "evidencePath",
828
+ "kind",
829
+ "socketPath"
830
+ ]);
831
+ return Object.freeze({
832
+ evidencePath: parseAbsoluteGuestPath(value.evidencePath, `${label}.evidencePath`),
833
+ kind: parseLiteral(value.kind, "tool-portal-evidence", `${label}.kind`),
834
+ socketPath: parseAbsoluteGuestPath(value.socketPath, `${label}.socketPath`)
835
+ });
836
+ }
837
+ function parseFrameworkReadiness(value, label) {
838
+ assertPlainRecord(value, label);
839
+ assertExactFields(value, label, [
840
+ "guestPort",
841
+ "kind",
842
+ "path"
843
+ ]);
844
+ return Object.freeze({
845
+ guestPort: parseGuestPort(value.guestPort, `${label}.guestPort`),
846
+ kind: parseLiteral(value.kind, "framework-http", `${label}.kind`),
847
+ path: parseHttpPath(value.path, `${label}.path`)
848
+ });
849
+ }
850
+ function parseFrameworkIngress(value, label) {
851
+ assertPlainRecord(value, label);
852
+ assertExactFields(value, label, ["guestPort", "kind"]);
853
+ return Object.freeze({
854
+ guestPort: parseGuestPort(value.guestPort, `${label}.guestPort`),
855
+ kind: parseLiteral(value.kind, "framework-http", `${label}.kind`)
856
+ });
857
+ }
858
+ function parseToolPortalService(value) {
859
+ const label = "Managed Gateway boot contract toolPortalService";
860
+ assertPlainRecord(value, label);
861
+ assertExactFields(value, label, [
862
+ "bootEntry",
863
+ "configurationInputPath",
864
+ "environmentInputPath",
865
+ "logIdentity",
866
+ "readiness",
867
+ "role"
868
+ ]);
869
+ return Object.freeze({
870
+ bootEntry: parseLiteral(value.bootEntry, "agent-vm-gateway-runtime", `${label}.bootEntry`),
871
+ configurationInputPath: parseAbsoluteGuestPath(value.configurationInputPath, `${label}.configurationInputPath`),
872
+ environmentInputPath: parseAbsoluteGuestPath(value.environmentInputPath, `${label}.environmentInputPath`),
873
+ logIdentity: parseLogIdentity(value.logIdentity, `${label}.logIdentity`),
874
+ readiness: parseToolPortalReadiness(value.readiness, `${label}.readiness`),
875
+ role: parseLiteral(value.role, "tool-portal-service", `${label}.role`)
876
+ });
877
+ }
878
+ function parseFrameworkService(value) {
879
+ const label = "Managed Gateway boot contract frameworkService";
880
+ assertPlainRecord(value, label);
881
+ assertExactFields(value, label, [
882
+ "bootEntry",
883
+ "configurationInputPath",
884
+ "environmentInputPath",
885
+ "framework",
886
+ "ingress",
887
+ "logIdentity",
888
+ "readiness",
889
+ "role"
890
+ ]);
891
+ const framework = value.framework;
892
+ if (framework !== "openclaw" && framework !== "hermes") throw new Error(`${label}.framework must be "openclaw" or "hermes".`);
893
+ const ingress = parseFrameworkIngress(value.ingress, `${label}.ingress`);
894
+ const readiness = parseFrameworkReadiness(value.readiness, `${label}.readiness`);
895
+ if (ingress.guestPort !== readiness.guestPort) throw new Error(`${label} ingress and readiness must identify the same guest port.`);
896
+ const commonFields = {
897
+ configurationInputPath: parseAbsoluteGuestPath(value.configurationInputPath, `${label}.configurationInputPath`),
898
+ environmentInputPath: parseAbsoluteGuestPath(value.environmentInputPath, `${label}.environmentInputPath`),
899
+ ingress,
900
+ logIdentity: parseLogIdentity(value.logIdentity, `${label}.logIdentity`),
901
+ readiness,
902
+ role: parseLiteral(value.role, "framework-service", `${label}.role`)
903
+ };
904
+ if (framework === "openclaw") return Object.freeze({
905
+ ...commonFields,
906
+ bootEntry: parseLiteral(value.bootEntry, "openclaw-gateway", `${label}.bootEntry`),
907
+ framework
908
+ });
909
+ return Object.freeze({
910
+ ...commonFields,
911
+ bootEntry: parseLiteral(value.bootEntry, "hermes-gateway", `${label}.bootEntry`),
912
+ framework
913
+ });
914
+ }
915
+ /**
916
+ * Parses untrusted or serialized boot metadata into the exact V1 contract.
917
+ * Unknown fields are rejected at every level and a fresh deeply frozen value
918
+ * is returned so callers cannot retain mutation authority after validation.
919
+ */
920
+ function parseManagedGatewayBootContract(value) {
921
+ const label = "Managed Gateway boot contract";
922
+ assertPlainRecord(value, label);
923
+ assertExactFields(value, label, [
924
+ "contractVersion",
925
+ "frameworkService",
926
+ "kind",
927
+ "toolPortalService"
928
+ ]);
929
+ return Object.freeze({
930
+ contractVersion: parseLiteral(value.contractVersion, 1, `${label}.contractVersion`),
931
+ frameworkService: parseFrameworkService(value.frameworkService),
932
+ kind: parseLiteral(value.kind, "managed-gateway-exact-two-role", `${label}.kind`),
933
+ toolPortalService: parseToolPortalService(value.toolPortalService)
934
+ });
935
+ }
936
+ //#endregion
937
+ //#region src/split-resolved-gateway-secrets.ts
938
+ function splitResolvedSecretsByInjection(secretConfigs, resolvedSecrets, options) {
939
+ const environmentSecrets = {};
940
+ const mediatedSecrets = {};
941
+ const logPrefix = options.logPrefix ?? "split-resolved-secrets";
942
+ for (const [secretName, secretValue] of Object.entries(resolvedSecrets)) {
943
+ const secretConfig = secretConfigs[secretName];
944
+ if (!secretConfig) throw new Error(`[${logPrefix}] Secret '${secretName}' was resolved but has no matching secret config.`);
945
+ if (!targetsAudience(secretConfig.audience, options.audience)) continue;
946
+ if (secretConfig.injection === "http-mediation") {
947
+ if (secretConfig.hosts.length === 0) throw new Error(`[${logPrefix}] Secret '${secretName}' uses http-mediation but declares no hosts.`);
948
+ mediatedSecrets[secretName] = {
949
+ hosts: [...secretConfig.hosts],
950
+ value: secretValue
951
+ };
952
+ continue;
953
+ }
954
+ const envSecretAudience = secretConfig.audience;
955
+ if (envSecretAudience !== "gateway") throw new Error(`[${logPrefix}] Secret '${secretName}' uses env injection with non-gateway audience '${envSecretAudience}'.`);
956
+ if (options.audience === "gateway") environmentSecrets[secretName] = secretValue;
957
+ }
958
+ return {
959
+ environmentSecrets,
960
+ mediatedSecrets
961
+ };
962
+ }
963
+ function splitResolvedGatewaySecrets(zone, resolvedSecrets) {
964
+ return splitResolvedSecretsByInjection(zone.secrets, resolvedSecrets, {
965
+ audience: "gateway",
966
+ logPrefix: "split-resolved-gateway-secrets"
967
+ });
968
+ }
969
+ function assertNoRuntimeSecretCollision(secretName, target, baseSecrets, runtimeSeen, logPrefix) {
970
+ if (runtimeSeen.has(secretName)) throw new Error(`[${logPrefix}] Runtime gateway secret '${secretName}' is declared for both environment and http-mediation injection.`);
971
+ if (secretName in baseSecrets.environmentSecrets) throw new Error(`[${logPrefix}] Runtime gateway ${target} secret '${secretName}' would overwrite an authored environment secret.`);
972
+ if (secretName in baseSecrets.mediatedSecrets) throw new Error(`[${logPrefix}] Runtime gateway ${target} secret '${secretName}' would overwrite an authored http-mediation secret.`);
973
+ runtimeSeen.add(secretName);
974
+ }
975
+ function mergeRuntimeGatewaySecrets(baseSecrets, options = {}) {
976
+ const logPrefix = options.logPrefix ?? "merge-runtime-gateway-secrets";
977
+ const runtimeSeen = /* @__PURE__ */ new Set();
978
+ for (const secretName of Object.keys(options.runtimeEnvironment ?? {})) assertNoRuntimeSecretCollision(secretName, "environment", baseSecrets, runtimeSeen, logPrefix);
979
+ for (const secretName of Object.keys(options.runtimeMediatedSecrets ?? {})) assertNoRuntimeSecretCollision(secretName, "http-mediation", baseSecrets, runtimeSeen, logPrefix);
980
+ return {
981
+ environmentSecrets: {
982
+ ...baseSecrets.environmentSecrets,
983
+ ...options.runtimeEnvironment
984
+ },
985
+ mediatedSecrets: {
986
+ ...baseSecrets.mediatedSecrets,
987
+ ...options.runtimeMediatedSecrets
988
+ }
989
+ };
990
+ }
991
+ //#endregion
992
+ //#region src/tool-vm-active-use.ts
993
+ const defaultMaxHeartbeatDurationMs = 720 * 60 * 1e3;
994
+ function jitterDelayMs(params) {
995
+ if (params.jitterRatio <= 0) return params.delayMs;
996
+ const spreadMs = params.delayMs * params.jitterRatio;
997
+ const jitteredMs = params.delayMs - spreadMs + params.random() * spreadMs * 2;
998
+ return Math.max(1, Math.round(jitteredMs));
999
+ }
1000
+ function createToolVmActiveUseId() {
1001
+ return v7();
1002
+ }
1003
+ function isToolVmActiveUseId(value) {
1004
+ return validate(value) && version(value) === 7;
1005
+ }
1006
+ function normalizeToolVmActiveUseCorrelation(correlation) {
1007
+ if (!isToolVmActiveUseCorrelationRecord(correlation)) return;
1008
+ const getString = (key) => {
1009
+ const value = correlation[key];
1010
+ return typeof value === "string" && value.length > 0 ? value : void 0;
1011
+ };
1012
+ const normalizedCorrelation = {
1013
+ ...getString("messageId") !== void 0 ? { messageId: getString("messageId") } : {},
1014
+ ...getString("requestId") !== void 0 ? { requestId: getString("requestId") } : {},
1015
+ ...getString("runId") !== void 0 ? { runId: getString("runId") } : {},
1016
+ ...getString("sessionKeyDigest") !== void 0 ? { sessionKeyDigest: getString("sessionKeyDigest") } : {},
1017
+ ...getString("toolCallId") !== void 0 ? { toolCallId: getString("toolCallId") } : {},
1018
+ ...getString("traceId") !== void 0 ? { traceId: getString("traceId") } : {}
1019
+ };
1020
+ return Object.keys(normalizedCorrelation).length > 0 ? normalizedCorrelation : void 0;
1021
+ }
1022
+ function isToolVmActiveUseCorrelationRecord(value) {
1023
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1024
+ }
1025
+ async function createToolVmActiveUseHandle(options) {
1026
+ const useId = createToolVmActiveUseId();
1027
+ const startedUse = await options.startActiveUse({
1028
+ ...options.correlation ? { correlation: options.correlation } : {},
1029
+ useId
1030
+ });
1031
+ const setTimeoutImpl = options.setTimeoutImpl ?? setTimeout;
1032
+ const clearTimeoutImpl = options.clearTimeoutImpl ?? clearTimeout;
1033
+ const now = options.nowImpl ?? Date.now;
1034
+ const startedAt = now();
1035
+ const maxHeartbeatDurationMs = options.maxHeartbeatDurationMs ?? defaultMaxHeartbeatDurationMs;
1036
+ const heartbeatJitterRatio = options.heartbeatJitterRatio ?? .1;
1037
+ const random = options.randomImpl ?? Math.random;
1038
+ const operationAbortController = new AbortController();
1039
+ let ended = false;
1040
+ let heartbeatTimer;
1041
+ let latestReport;
1042
+ const clearHeartbeatTimer = () => {
1043
+ if (heartbeatTimer) {
1044
+ clearTimeoutImpl(heartbeatTimer);
1045
+ heartbeatTimer = void 0;
1046
+ }
1047
+ };
1048
+ const scheduleHeartbeat = (delayMs) => {
1049
+ if (now() - startedAt >= maxHeartbeatDurationMs) return;
1050
+ clearHeartbeatTimer();
1051
+ heartbeatTimer = setTimeoutImpl(() => {
1052
+ if (now() - startedAt >= maxHeartbeatDurationMs) return;
1053
+ const heartbeatRequest = latestReport === void 0 ? {} : { report: latestReport };
1054
+ options.heartbeatActiveUse(startedUse.useId, heartbeatRequest).then((heartbeat) => {
1055
+ if (!ended) scheduleHeartbeat(heartbeat.heartbeatAfterMs);
1056
+ }).catch((error) => {
1057
+ options.logHeartbeatFailure?.(error);
1058
+ if (options.isHeartbeatErrorRefreshable?.(error) === true && options.onRefreshableHeartbeatFailure) {
1059
+ operationAbortController.abort(error);
1060
+ ended = true;
1061
+ clearHeartbeatTimer();
1062
+ options.onRefreshableHeartbeatFailure(error).catch((staleError) => {
1063
+ options.logHeartbeatFailure?.(staleError);
1064
+ });
1065
+ return;
1066
+ }
1067
+ if (!ended) scheduleHeartbeat(startedUse.heartbeatAfterMs);
1068
+ });
1069
+ }, jitterDelayMs({
1070
+ delayMs,
1071
+ jitterRatio: heartbeatJitterRatio,
1072
+ random
1073
+ }));
1074
+ };
1075
+ scheduleHeartbeat(startedUse.heartbeatAfterMs);
1076
+ const end = async (outcome = "completed") => {
1077
+ if (ended) return;
1078
+ ended = true;
1079
+ clearHeartbeatTimer();
1080
+ try {
1081
+ await options.endActiveUse(startedUse.useId, {
1082
+ outcome,
1083
+ ...latestReport === void 0 ? {} : { report: latestReport }
1084
+ });
1085
+ } catch (error) {
1086
+ if (options.isEndErrorTolerable?.(error) === true) {
1087
+ options.logEndFailure?.(error);
1088
+ return;
1089
+ }
1090
+ throw error;
1091
+ }
1092
+ };
1093
+ return {
1094
+ signal: operationAbortController.signal,
1095
+ useId: startedUse.useId,
1096
+ dispose: end,
1097
+ end,
1098
+ report: (report) => {
1099
+ if (ended) return;
1100
+ latestReport = report;
1101
+ }
1102
+ };
1103
+ }
1104
+ //#endregion
1105
+ //#region src/runtime-paths/runtime-path-mapping.ts
1106
+ const TOOL_VM_SCRATCH_GUEST_ROOT = "/scratch";
1107
+ const OPENCLAW_STATE_VM_ROOT = "/home/openclaw/.openclaw/state";
1108
+ const OPENCLAW_STATE_SANDBOXES_VM_ROOT = `${OPENCLAW_STATE_VM_ROOT}/sandboxes`;
1109
+ const guidanceNamespaceOrder = [
1110
+ "tool-vm-guest",
1111
+ "openclaw-gateway",
1112
+ "controller-host"
1113
+ ];
1114
+ function pathContainsParentTraversal(inputPath) {
1115
+ return inputPath.split(/\/+/u).includes("..");
1116
+ }
1117
+ function normalizeAbsolutePath(inputPath) {
1118
+ return `/${inputPath.split("/").filter((segment) => segment !== "" && segment !== ".").join("/")}`;
1119
+ }
1120
+ function normalizeRoot(rootPath) {
1121
+ const normalizedRoot = normalizeAbsolutePath(rootPath);
1122
+ return normalizedRoot === "/" ? normalizedRoot : normalizedRoot.replace(/\/+$/u, "");
1123
+ }
1124
+ function pathMatchesRoot(candidatePath, rootPath) {
1125
+ return candidatePath === rootPath || candidatePath.startsWith(`${rootPath}/`);
1126
+ }
1127
+ function relativePathForRoot(candidatePath, rootPath) {
1128
+ return candidatePath === rootPath ? "" : candidatePath.slice(rootPath.length + 1);
1129
+ }
1130
+ function joinRootAndRelative(rootPath, relativePath) {
1131
+ return relativePath === "" ? rootPath : `${rootPath}/${relativePath}`;
1132
+ }
1133
+ function runtimeRootIsInvalid(rootPath) {
1134
+ return rootPath.trim() === "" || !rootPath.startsWith("/") || pathContainsParentTraversal(rootPath);
1135
+ }
1136
+ function namespaceShouldShowInGuidance(root, namespace) {
1137
+ if (root.showInGuidance?.[namespace] !== void 0) return root.showInGuidance[namespace];
1138
+ return namespace !== "controller-host";
1139
+ }
1140
+ function allowedPathFormsForMapping(mapping, purpose) {
1141
+ return mapping.roots.flatMap((root) => {
1142
+ if (!root.capabilities[purpose]) return [];
1143
+ const suffix = root.rootPathAllowed ? "[/subpath]" : "/<child>";
1144
+ return guidanceNamespaceOrder.flatMap((namespace) => {
1145
+ const rootPath = root.locations[namespace];
1146
+ if (rootPath === void 0 || !namespaceShouldShowInGuidance(root, namespace)) return [];
1147
+ return [`${normalizeRoot(rootPath)}${suffix}`];
1148
+ });
1149
+ });
1150
+ }
1151
+ function retryGuidanceForMapping(mapping, purpose) {
1152
+ return `Use one of the allowed path forms for ${mapping.id} ${purpose}: ${allowedPathFormsForMapping(mapping, purpose).join(", ")}.`;
1153
+ }
1154
+ function errorResult(params) {
1155
+ return {
1156
+ error: {
1157
+ allowedPathForms: allowedPathFormsForMapping(params.mapping, params.purpose),
1158
+ code: params.code,
1159
+ inputPath: params.inputPath,
1160
+ mappingId: params.mapping.id,
1161
+ message: params.message,
1162
+ purpose: params.purpose,
1163
+ retryGuidance: retryGuidanceForMapping(params.mapping, params.purpose)
1164
+ },
1165
+ ok: false
1166
+ };
1167
+ }
1168
+ function findBestRootMatch(params) {
1169
+ return params.mapping.roots.flatMap((root) => guidanceNamespaceOrder.flatMap((inputNamespace) => {
1170
+ const rootPath = root.locations[inputNamespace];
1171
+ if (rootPath === void 0) return [];
1172
+ if (params.sourceNamespace !== void 0 && inputNamespace !== params.sourceNamespace) return [];
1173
+ const normalizedRoot = normalizeRoot(rootPath);
1174
+ return pathMatchesRoot(params.inputPath, normalizedRoot) ? [{
1175
+ inputNamespace,
1176
+ matchedRoot: normalizedRoot,
1177
+ root
1178
+ }] : [];
1179
+ })).toSorted((left, right) => right.matchedRoot.length - left.matchedRoot.length)[0];
1180
+ }
1181
+ function findInvalidRoot(mapping) {
1182
+ for (const root of mapping.roots) for (const namespace of guidanceNamespaceOrder) {
1183
+ const rootPath = root.locations[namespace];
1184
+ if (rootPath !== void 0 && runtimeRootIsInvalid(rootPath)) return {
1185
+ rootId: root.id,
1186
+ rootPath
1187
+ };
1188
+ }
1189
+ }
1190
+ function translateRuntimePath(input) {
1191
+ if (!input.inputPath.startsWith("/")) return errorResult({
1192
+ code: "path-not-absolute",
1193
+ inputPath: input.inputPath,
1194
+ mapping: input.mapping,
1195
+ message: `Path '${input.inputPath}' must be absolute.`,
1196
+ purpose: input.purpose
1197
+ });
1198
+ if (pathContainsParentTraversal(input.inputPath)) return errorResult({
1199
+ code: "path-parent-traversal",
1200
+ inputPath: input.inputPath,
1201
+ mapping: input.mapping,
1202
+ message: `Path '${input.inputPath}' must not contain parent traversal.`,
1203
+ purpose: input.purpose
1204
+ });
1205
+ const invalidRoot = findInvalidRoot(input.mapping);
1206
+ if (invalidRoot !== void 0) return errorResult({
1207
+ code: "invalid-runtime-root",
1208
+ inputPath: input.inputPath,
1209
+ mapping: input.mapping,
1210
+ message: `Runtime path root '${invalidRoot.rootId}' has invalid path '${invalidRoot.rootPath}'.`,
1211
+ purpose: input.purpose
1212
+ });
1213
+ const normalizedInputPath = normalizeAbsolutePath(input.inputPath);
1214
+ const match = findBestRootMatch({
1215
+ inputPath: normalizedInputPath,
1216
+ mapping: input.mapping,
1217
+ ...input.sourceNamespace === void 0 ? {} : { sourceNamespace: input.sourceNamespace }
1218
+ });
1219
+ if (match === void 0) return errorResult({
1220
+ code: "unknown-runtime-path",
1221
+ inputPath: normalizedInputPath,
1222
+ mapping: input.mapping,
1223
+ message: `Path '${normalizedInputPath}' is not part of runtime path mapping '${input.mapping.id}'.`,
1224
+ purpose: input.purpose
1225
+ });
1226
+ const relativePath = relativePathForRoot(normalizedInputPath, match.matchedRoot);
1227
+ if (relativePath === "" && !match.root.rootPathAllowed) return errorResult({
1228
+ code: "root-path-not-allowed",
1229
+ inputPath: normalizedInputPath,
1230
+ mapping: input.mapping,
1231
+ message: `Path '${normalizedInputPath}' matched ${match.root.guidanceLabel}, but the root itself is not allowed for ${input.purpose}.`,
1232
+ purpose: input.purpose
1233
+ });
1234
+ if (!match.root.capabilities[input.purpose]) return errorResult({
1235
+ code: "purpose-not-allowed",
1236
+ inputPath: normalizedInputPath,
1237
+ mapping: input.mapping,
1238
+ message: `Path '${normalizedInputPath}' matched ${match.root.guidanceLabel} but cannot be used for ${input.purpose}.`,
1239
+ purpose: input.purpose
1240
+ });
1241
+ const targetRoot = match.root.locations[input.targetNamespace];
1242
+ if (targetRoot === void 0) return errorResult({
1243
+ code: "target-namespace-not-available",
1244
+ inputPath: normalizedInputPath,
1245
+ mapping: input.mapping,
1246
+ message: `Path '${normalizedInputPath}' matched ${match.root.guidanceLabel}, but '${input.targetNamespace}' is not available for that root.`,
1247
+ purpose: input.purpose
1248
+ });
1249
+ const normalizedTargetRoot = normalizeRoot(targetRoot);
1250
+ return {
1251
+ ok: true,
1252
+ value: {
1253
+ backing: match.root.backing,
1254
+ capabilities: match.root.capabilities,
1255
+ inputNamespace: match.inputNamespace,
1256
+ inputPath: normalizedInputPath,
1257
+ mappingId: input.mapping.id,
1258
+ outputNamespace: input.targetNamespace,
1259
+ outputPath: joinRootAndRelative(normalizedTargetRoot, relativePath),
1260
+ relativePath,
1261
+ rootId: match.root.id
1262
+ }
1263
+ };
1264
+ }
1265
+ //#endregion
1266
+ //#region src/tool-vm-lease-id.ts
1267
+ function createToolVmLeaseId() {
1268
+ return parseToolVmLeaseId(v7());
1269
+ }
1270
+ function isToolVmLeaseId(value) {
1271
+ return typeof value === "string" && validate(value) && version(value) === 7;
1272
+ }
1273
+ function parseToolVmLeaseId(value) {
1274
+ if (isToolVmLeaseId(value)) return value;
1275
+ throw new TypeError("Tool VM lease id must be an opaque UUIDv7 string.");
1276
+ }
1277
+ //#endregion
1278
+ //#region src/vm-capability-lease.ts
1279
+ const VM_SSH_PUBLIC_ENDPOINT_KEYS = new Set([
1280
+ "host",
1281
+ "port",
1282
+ "user"
1283
+ ]);
1284
+ function objectValue$1(value) {
1285
+ return typeof value === "object" && value !== null ? value : void 0;
1286
+ }
1287
+ function isNonEmptyString(value) {
1288
+ return typeof value === "string" && value.trim().length > 0;
1289
+ }
1290
+ function isVmCapabilityLease(value, transport) {
1291
+ const record = objectValue$1(value);
1292
+ return record !== void 0 && typeof Reflect.get(record, "leaseId") === "string" && Reflect.get(record, "transport") === transport;
1293
+ }
1294
+ function isVmSshEndpoint(value) {
1295
+ const record = objectValue$1(value);
1296
+ return record !== void 0 && typeof Reflect.get(record, "host") === "string" && isNonEmptyString(Reflect.get(record, "identityPem")) && isNonEmptyString(Reflect.get(record, "knownHostsLine")) && typeof Reflect.get(record, "port") === "number" && typeof Reflect.get(record, "user") === "string";
1297
+ }
1298
+ function isVmSshPublicEndpoint(value) {
1299
+ const record = objectValue$1(value);
1300
+ if (record === void 0) return false;
1301
+ for (const key of Object.keys(record)) if (!VM_SSH_PUBLIC_ENDPOINT_KEYS.has(key)) return false;
1302
+ return typeof Reflect.get(record, "host") === "string" && typeof Reflect.get(record, "port") === "number" && typeof Reflect.get(record, "user") === "string";
1303
+ }
1304
+ //#endregion
1305
+ //#region src/tool-vm-lease.ts
1306
+ const defaultToolVmLeaseAuthorityTombstoneTtlMs = 600 * 1e3;
1307
+ const TOOL_VM_WORK_GUEST_ROOT = "/work";
1308
+ function objectValue(value) {
1309
+ return typeof value === "object" && value !== null ? value : void 0;
1310
+ }
1311
+ const deprecatedScopeKeyPropertyName = ["scope", "Key"].join("");
1312
+ function isToolVmSshLease(value) {
1313
+ const record = objectValue(value);
1314
+ return isVmCapabilityLease(record, "ssh-sandbox") && isToolVmLeaseId(Reflect.get(record, "leaseId")) && isVmSshEndpoint(Reflect.get(record, "ssh")) && typeof Reflect.get(record, "agentId") === "string" && typeof Reflect.get(record, "idleTtlMs") === "number" && typeof Reflect.get(record, "tcpSlot") === "number" && typeof Reflect.get(record, "workdir") === "string" && !Reflect.has(record, deprecatedScopeKeyPropertyName);
1315
+ }
1316
+ function isToolVmLeasePeek(value) {
1317
+ const record = objectValue(value);
1318
+ return isVmCapabilityLease(record, "ssh-sandbox") && isToolVmLeaseId(Reflect.get(record, "leaseId")) && typeof Reflect.get(record, "agentId") === "string" && typeof Reflect.get(record, "createdAt") === "number" && typeof Reflect.get(record, "idleTtlMs") === "number" && typeof Reflect.get(record, "lastUsedAt") === "number" && typeof Reflect.get(record, "profileId") === "string" && isVmSshPublicEndpoint(Reflect.get(record, "ssh")) && typeof Reflect.get(record, "tcpSlot") === "number" && typeof Reflect.get(record, "workdir") === "string" && typeof Reflect.get(record, "zoneId") === "string" && !Reflect.has(record, deprecatedScopeKeyPropertyName);
1319
+ }
1320
+ //#endregion
1321
+ export { ControllerRequestPolicyTransportError, FORCE_IPV4_EGRESS_NODE_OPTIONS, GATEWAY_CONTROL_CALLER_CONTEXT_AGENT_AUTHORITY_KEYS_ENV, GATEWAY_CONTROL_CALLER_CONTEXT_PROOF_KEY_ENV, GATEWAY_CONTROL_PRIVATE_ENVIRONMENT_NAMES, OPENCLAW_STATE_SANDBOXES_VM_ROOT, OPENCLAW_STATE_VM_ROOT, TOOL_VM_SCRATCH_GUEST_ROOT, TOOL_VM_WORK_GUEST_ROOT, agentVmHealthEventKinds, agentVmHealthResultKinds, buildGatewaySessionLabel, buildToolSessionLabel, composeNodeOptions, controllerRequestPolicies, controllerVmHost, createGatewayTelemetryProducerSafetyContract, createToolVmActiveUseHandle, createToolVmActiveUseId, createToolVmLeaseId, createWebSocketUpgradeRequestGuard, defaultToolVmLeaseAuthorityTombstoneTtlMs, deriveZoneHealthSnapshot, drainControllerResponseBody, egressHostsForAudience, externalControllerRoutes, gatewayControlSessionHealthOperations, gatewayFrameworkTelemetryServiceNames, gatewayRecoveryHealthReasons, gatewayTelemetryAdmissionLimits, gatewayTelemetrySourcePolicy, gatewayToolPortalTelemetryServiceName, gatewayTypeValues, gatewayVmAllowedHosts, genericControllerRequestEventOperations, healthEventBucketKey, isAgentVmHealthEvent, isToolVmActiveUseId, isToolVmLeaseId, isToolVmLeasePeek, isToolVmSshLease, isVmCapabilityLease, isVmSshEndpoint, isVmSshPublicEndpoint, mergeRuntimeGatewaySecrets, normalizeGitHubRepoForSshReadAllowlist, normalizeGitHubReposForSshReadAllowlist, normalizeGitRepoForSshReadAllowlist, normalizeGitReposForSshReadAllowlist, normalizeToolVmActiveUseCorrelation, parseManagedGatewayBootContract, parseToolVmLeaseId, splitResolvedGatewaySecrets, splitResolvedSecretsByInjection, targetsAudience, translateRuntimePath, vmAudienceValues, websocketUpgradesForAudience, workerVmAllowedHosts, zoneHealthIssueKinds, zoneHealthStateKinds };
1322
+
1323
+ //# sourceMappingURL=index.js.map