@agent-vm/gateway-interface 0.0.70 → 0.0.72
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.d.ts +179 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +175 -1
- package/dist/index.js.map +1 -1
- package/package.json +4 -3
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { MediatedSecretSpec, SecretResolver } from "@agent-vm/
|
|
1
|
+
import { MediatedSecretSpec, SecretResolver } from "@agent-vm/secret-management";
|
|
2
2
|
import { VfsMountSpec } from "@agent-vm/gondolin-adapter";
|
|
3
3
|
|
|
4
4
|
//#region src/gateway-runtime-contract.d.ts
|
|
@@ -20,6 +20,73 @@ declare function targetsAudience(configAudience: VmAudience, runtimeAudience: Ru
|
|
|
20
20
|
declare function egressHostsForAudience(egressHosts: readonly EgressHostConfig[], runtimeAudience: RuntimeVmAudience): readonly string[];
|
|
21
21
|
declare function gatewayVmAllowedHosts(egressHosts: readonly EgressHostConfig[]): readonly string[];
|
|
22
22
|
//#endregion
|
|
23
|
+
//#region src/force-ipv4-egress.d.ts
|
|
24
|
+
/**
|
|
25
|
+
* Canonical NODE_OPTIONS value for forcing IPv4-preference egress
|
|
26
|
+
* in agent-vm VMs.
|
|
27
|
+
*
|
|
28
|
+
* Background: Gondolin's synthetic DNS (when tcpHosts is enabled)
|
|
29
|
+
* returns a per-host IPv4 (reverse-lookable) and a single shared
|
|
30
|
+
* IPv4-mapped IPv6 (::ffff:198.18.0.1, NOT reverse-lookable). Node
|
|
31
|
+
* 20+'s fetch (via undici, autoSelectFamily: true) races both
|
|
32
|
+
* families; when the IPv6 race wins (~5-20% under sequential load),
|
|
33
|
+
* gondolin cannot route it and the request fails with a non-JSON
|
|
34
|
+
* 400 (HTTP) or 403 (TLS). The two flags below stop the race:
|
|
35
|
+
*
|
|
36
|
+
* --dns-result-order=ipv4first changes dns.lookup() so
|
|
37
|
+
* IPv4 addresses are listed
|
|
38
|
+
* before IPv6.
|
|
39
|
+
*
|
|
40
|
+
* --no-network-family-autoselection disables Node's Happy
|
|
41
|
+
* Eyeballs entirely. This is
|
|
42
|
+
* the load-bearing flag —
|
|
43
|
+
* --dns-result-order alone
|
|
44
|
+
* doesn't prevent Node from
|
|
45
|
+
* racing both families if
|
|
46
|
+
* IPv4 is slow.
|
|
47
|
+
*
|
|
48
|
+
* Composition: NODE_OPTIONS is whitespace-separated. To add more
|
|
49
|
+
* flags downstream, append rather than replace. Example:
|
|
50
|
+
*
|
|
51
|
+
* NODE_OPTIONS: `${FORCE_IPV4_EGRESS_NODE_OPTIONS} --inspect`
|
|
52
|
+
*
|
|
53
|
+
* Reference: see `shravan-claw@0ddf5f2:docs/wip/debugging/
|
|
54
|
+
* 2026-05-21-lease-keepalive-400-and-discord-403-ipv6-race.md`
|
|
55
|
+
* for the full root-cause analysis. Node-side flag references:
|
|
56
|
+
* https://github.com/nodejs/node/issues/54359 (autoSelectFamily
|
|
57
|
+
* revert recommendation by the Node core team).
|
|
58
|
+
*/
|
|
59
|
+
declare const FORCE_IPV4_EGRESS_NODE_OPTIONS = "--dns-result-order=ipv4first --no-network-family-autoselection";
|
|
60
|
+
/**
|
|
61
|
+
* Compose the forced IPv4-preference flags with a user-provided
|
|
62
|
+
* NODE_OPTIONS value (if any).
|
|
63
|
+
*
|
|
64
|
+
* Use this at every site where NODE_OPTIONS is set into a VM env
|
|
65
|
+
* block AFTER a spread of user-controlled secrets, to guarantee
|
|
66
|
+
* the forced flags are always present in the final value even if
|
|
67
|
+
* a zone secret happens to provide its own NODE_OPTIONS.
|
|
68
|
+
*
|
|
69
|
+
* Forced flags come FIRST so they are unambiguously applied.
|
|
70
|
+
* User-provided flags are appended verbatim. Node treats NODE_OPTIONS
|
|
71
|
+
* as a whitespace-separated list and all flags apply.
|
|
72
|
+
*
|
|
73
|
+
* Returns just the forced flags if the user value is undefined,
|
|
74
|
+
* empty, or whitespace-only.
|
|
75
|
+
*
|
|
76
|
+
* Examples:
|
|
77
|
+
*
|
|
78
|
+
* composeNodeOptions(undefined)
|
|
79
|
+
* ──► '--dns-result-order=ipv4first --no-network-family-autoselection'
|
|
80
|
+
*
|
|
81
|
+
* composeNodeOptions('')
|
|
82
|
+
* ──► '--dns-result-order=ipv4first --no-network-family-autoselection'
|
|
83
|
+
*
|
|
84
|
+
* composeNodeOptions('--inspect=0.0.0.0:9229')
|
|
85
|
+
* ──► '--dns-result-order=ipv4first --no-network-family-autoselection
|
|
86
|
+
* --inspect=0.0.0.0:9229'
|
|
87
|
+
*/
|
|
88
|
+
declare function composeNodeOptions(userValue: string | undefined): string;
|
|
89
|
+
//#endregion
|
|
23
90
|
//#region src/gateway-process-spec.d.ts
|
|
24
91
|
type GatewayHealthCheck = {
|
|
25
92
|
readonly type: 'http';
|
|
@@ -78,7 +145,7 @@ interface GatewayAuthConfig {
|
|
|
78
145
|
}) => string;
|
|
79
146
|
}
|
|
80
147
|
interface GatewayAuthProfilesRef {
|
|
81
|
-
readonly source: '1password' | 'environment';
|
|
148
|
+
readonly source: '1password' | 'config' | 'environment';
|
|
82
149
|
}
|
|
83
150
|
interface OnePasswordGatewayAuthProfilesRef extends GatewayAuthProfilesRef {
|
|
84
151
|
readonly source: '1password';
|
|
@@ -88,10 +155,17 @@ interface EnvironmentGatewayAuthProfilesRef extends GatewayAuthProfilesRef {
|
|
|
88
155
|
readonly source: 'environment';
|
|
89
156
|
readonly envVar: string;
|
|
90
157
|
}
|
|
158
|
+
interface ConfigGatewayAuthProfilesRef extends GatewayAuthProfilesRef {
|
|
159
|
+
readonly source: 'config';
|
|
160
|
+
readonly value: string;
|
|
161
|
+
}
|
|
91
162
|
type GatewaySshSecretEnvMode = 'always' | 'explicit' | 'never';
|
|
92
163
|
interface GatewaySshConfig {
|
|
93
164
|
readonly secretEnv: GatewaySshSecretEnvMode;
|
|
94
165
|
}
|
|
166
|
+
interface GatewayControllerAuthConfig {
|
|
167
|
+
readonly secret: string;
|
|
168
|
+
}
|
|
95
169
|
interface GatewayZoneBaseGatewayConfig {
|
|
96
170
|
readonly type: GatewayType;
|
|
97
171
|
readonly memory: string;
|
|
@@ -100,12 +174,13 @@ interface GatewayZoneBaseGatewayConfig {
|
|
|
100
174
|
readonly config: string;
|
|
101
175
|
readonly stateDir: string;
|
|
102
176
|
readonly ssh: GatewaySshConfig;
|
|
103
|
-
readonly authProfilesRef?: OnePasswordGatewayAuthProfilesRef | EnvironmentGatewayAuthProfilesRef | undefined;
|
|
177
|
+
readonly authProfilesRef?: ConfigGatewayAuthProfilesRef | OnePasswordGatewayAuthProfilesRef | EnvironmentGatewayAuthProfilesRef | undefined;
|
|
104
178
|
}
|
|
105
179
|
interface OpenClawGatewayZoneGatewayConfig extends GatewayZoneBaseGatewayConfig {
|
|
106
180
|
readonly type: 'openclaw';
|
|
107
181
|
readonly zoneFilesDir: string;
|
|
108
|
-
readonly
|
|
182
|
+
readonly controllerAuth: GatewayControllerAuthConfig;
|
|
183
|
+
readonly authProfilesByAgent?: Readonly<Record<string, ConfigGatewayAuthProfilesRef | OnePasswordGatewayAuthProfilesRef | EnvironmentGatewayAuthProfilesRef>>;
|
|
109
184
|
readonly rawEnvSecrets?: readonly string[];
|
|
110
185
|
}
|
|
111
186
|
interface WorkerGatewayZoneGatewayConfig extends GatewayZoneBaseGatewayConfig {
|
|
@@ -120,7 +195,11 @@ interface EnvironmentSecretSourceConfig {
|
|
|
120
195
|
readonly source: 'environment';
|
|
121
196
|
readonly envVar: string;
|
|
122
197
|
}
|
|
123
|
-
|
|
198
|
+
interface ConfigSecretSourceConfig {
|
|
199
|
+
readonly source: 'config';
|
|
200
|
+
readonly value: string;
|
|
201
|
+
}
|
|
202
|
+
type SecretSourceConfig = OnePasswordSecretSourceConfig | EnvironmentSecretSourceConfig | ConfigSecretSourceConfig;
|
|
124
203
|
type EnvInjectedGatewaySecretConfig = SecretSourceConfig & {
|
|
125
204
|
readonly audience: 'gateway';
|
|
126
205
|
readonly injection: 'env';
|
|
@@ -216,5 +295,99 @@ type SplitResolvedGatewaySecretsResult = SplitResolvedSecretsResult;
|
|
|
216
295
|
declare function splitResolvedGatewaySecrets(zone: GatewayZoneConfig, resolvedSecrets: Record<string, string>): SplitResolvedGatewaySecretsResult;
|
|
217
296
|
declare function mergeRuntimeGatewaySecrets(baseSecrets: SplitResolvedSecretsResult, options?: MergeRuntimeGatewaySecretsOptions): SplitResolvedSecretsResult;
|
|
218
297
|
//#endregion
|
|
219
|
-
|
|
298
|
+
//#region src/tool-vm-active-use.d.ts
|
|
299
|
+
type ToolVmActiveUseOutcome = 'abandoned' | 'cancelled' | 'completed' | 'failed' | 'timed-out';
|
|
300
|
+
interface ToolVmActiveUseCorrelation {
|
|
301
|
+
readonly agentId?: string;
|
|
302
|
+
readonly sessionId?: string;
|
|
303
|
+
readonly sessionKey?: string;
|
|
304
|
+
readonly toolCallId?: string;
|
|
305
|
+
readonly toolName?: string;
|
|
306
|
+
}
|
|
307
|
+
interface StartToolVmActiveUseRequest {
|
|
308
|
+
readonly correlation?: ToolVmActiveUseCorrelation;
|
|
309
|
+
readonly useId: string;
|
|
310
|
+
}
|
|
311
|
+
interface StartToolVmActiveUseResponse {
|
|
312
|
+
readonly expiresAt: number;
|
|
313
|
+
readonly heartbeatAfterMs: number;
|
|
314
|
+
readonly useId: string;
|
|
315
|
+
}
|
|
316
|
+
interface HeartbeatToolVmActiveUseResponse {
|
|
317
|
+
readonly expiresAt: number;
|
|
318
|
+
readonly heartbeatAfterMs: number;
|
|
319
|
+
}
|
|
320
|
+
interface EndToolVmActiveUseRequest {
|
|
321
|
+
readonly outcome: ToolVmActiveUseOutcome;
|
|
322
|
+
}
|
|
323
|
+
interface ToolVmActiveUseHandle {
|
|
324
|
+
readonly useId: string;
|
|
325
|
+
dispose(outcome?: ToolVmActiveUseOutcome): Promise<void>;
|
|
326
|
+
end(outcome?: ToolVmActiveUseOutcome): Promise<void>;
|
|
327
|
+
}
|
|
328
|
+
interface CreateToolVmActiveUseHandleOptions {
|
|
329
|
+
readonly correlation?: ToolVmActiveUseCorrelation;
|
|
330
|
+
readonly endActiveUse: (useId: string, request: EndToolVmActiveUseRequest) => Promise<void>;
|
|
331
|
+
readonly heartbeatActiveUse: (useId: string) => Promise<HeartbeatToolVmActiveUseResponse>;
|
|
332
|
+
readonly isEndErrorTolerable?: (error: unknown) => boolean;
|
|
333
|
+
readonly logEndFailure?: (error: unknown) => void;
|
|
334
|
+
readonly logHeartbeatFailure?: (error: unknown) => void;
|
|
335
|
+
readonly maxHeartbeatDurationMs?: number;
|
|
336
|
+
readonly nowImpl?: () => number;
|
|
337
|
+
readonly startActiveUse: (request: StartToolVmActiveUseRequest) => Promise<StartToolVmActiveUseResponse>;
|
|
338
|
+
readonly setTimeoutImpl?: typeof setTimeout;
|
|
339
|
+
readonly clearTimeoutImpl?: typeof clearTimeout;
|
|
340
|
+
}
|
|
341
|
+
declare function createToolVmActiveUseId(): string;
|
|
342
|
+
declare function isToolVmActiveUseId(value: string): boolean;
|
|
343
|
+
declare function createToolVmActiveUseHandle(options: CreateToolVmActiveUseHandleOptions): Promise<ToolVmActiveUseHandle>;
|
|
344
|
+
//#endregion
|
|
345
|
+
//#region src/vm-capability-lease.d.ts
|
|
346
|
+
/**
|
|
347
|
+
* Small host-issued capability envelope shared by VM-backed transports. The
|
|
348
|
+
* transport tag keeps SSH Tool VM leases distinct from future host-side
|
|
349
|
+
* Gondolin RPC or bridge capabilities without inventing a transport object.
|
|
350
|
+
*/
|
|
351
|
+
interface VmCapabilityLease<TTransport extends string> {
|
|
352
|
+
readonly leaseId: string;
|
|
353
|
+
readonly transport: TTransport;
|
|
354
|
+
}
|
|
355
|
+
interface VmSshEndpoint {
|
|
356
|
+
readonly host: string;
|
|
357
|
+
readonly identityPem: string;
|
|
358
|
+
readonly knownHostsLine: string;
|
|
359
|
+
readonly port: number;
|
|
360
|
+
readonly user: string;
|
|
361
|
+
}
|
|
362
|
+
interface VmSshPublicEndpoint {
|
|
363
|
+
readonly host: string;
|
|
364
|
+
readonly port: number;
|
|
365
|
+
readonly user: string;
|
|
366
|
+
}
|
|
367
|
+
interface VmSshLease<TTransport extends string> extends VmCapabilityLease<TTransport> {
|
|
368
|
+
readonly ssh: VmSshEndpoint;
|
|
369
|
+
}
|
|
370
|
+
declare function isVmCapabilityLease<TTransport extends string>(value: unknown, transport: TTransport): value is VmCapabilityLease<TTransport>;
|
|
371
|
+
declare function isVmSshEndpoint(value: unknown): value is VmSshEndpoint;
|
|
372
|
+
declare function isVmSshPublicEndpoint(value: unknown): value is VmSshPublicEndpoint;
|
|
373
|
+
//#endregion
|
|
374
|
+
//#region src/tool-vm-lease.d.ts
|
|
375
|
+
interface ToolVmSshLease extends VmSshLease<'ssh-sandbox'> {
|
|
376
|
+
readonly tcpSlot: number;
|
|
377
|
+
readonly workdir: string;
|
|
378
|
+
}
|
|
379
|
+
interface ToolVmLeasePeek extends VmCapabilityLease<'ssh-sandbox'> {
|
|
380
|
+
readonly createdAt: number;
|
|
381
|
+
readonly lastUsedAt: number;
|
|
382
|
+
readonly profileId: string;
|
|
383
|
+
readonly scopeKey: string;
|
|
384
|
+
readonly ssh: VmSshPublicEndpoint;
|
|
385
|
+
readonly tcpSlot: number;
|
|
386
|
+
readonly workdir: string;
|
|
387
|
+
readonly zoneId: string;
|
|
388
|
+
}
|
|
389
|
+
declare function isToolVmSshLease(value: unknown): value is ToolVmSshLease;
|
|
390
|
+
declare function isToolVmLeasePeek(value: unknown): value is ToolVmLeasePeek;
|
|
391
|
+
//#endregion
|
|
392
|
+
export { type BuildGatewayVmSpecOptions, type CreateToolVmActiveUseHandleOptions, type EgressHostConfig, type EndToolVmActiveUseRequest, type EnvInjectedGatewaySecretConfig, FORCE_IPV4_EGRESS_NODE_OPTIONS, type GatewayAuthConfig, type GatewayHealthCheck, type GatewayLifecycle, type GatewayProcessSpec, type GatewaySecretConfig, type GatewayType, type GatewayVmSpec, type GatewayZoneAgentConfig, type GatewayZoneConfig, type GatewayZoneMcpPortalConfig, type HeartbeatToolVmActiveUseResponse, type HttpMediatedGatewaySecretConfig, type RuntimeVmAudience, type SecretInjectionConfig, type SplitResolvedGatewaySecretsResult, type SplitResolvedSecretsResult, type StartToolVmActiveUseRequest, type StartToolVmActiveUseResponse, type ToolVmActiveUseCorrelation, type ToolVmActiveUseHandle, type ToolVmActiveUseOutcome, type ToolVmLeasePeek, type ToolVmSshLease, type VmAudience, type VmCapabilityLease, type VmSshEndpoint, type VmSshLease, type VmSshPublicEndpoint, buildGatewaySessionLabel, buildToolSessionLabel, composeNodeOptions, controllerVmHost, createToolVmActiveUseHandle, createToolVmActiveUseId, egressHostsForAudience, gatewayTypeValues, gatewayVmAllowedHosts, isToolVmActiveUseId, isToolVmLeasePeek, isToolVmSshLease, isVmCapabilityLease, isVmSshEndpoint, isVmSshPublicEndpoint, mergeRuntimeGatewaySecrets, splitResolvedGatewaySecrets, splitResolvedSecretsByInjection, targetsAudience, vmAudienceValues };
|
|
220
393
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/gateway-runtime-contract.ts","../src/audience.ts","../src/gateway-process-spec.ts","../src/gateway-vm-spec.ts","../src/gateway-lifecycle.ts","../src/split-resolved-gateway-secrets.ts"],"mappings":";;;;cAAa,iBAAA;AAAA,KAED,WAAA,WAAsB,iBAAA;AAAA,iBAElB,wBAAA,CAAyB,gBAAA,UAA0B,MAAA;AAAA,iBAInD,qBAAA,CACf,gBAAA,UACA,MAAA,UACA,OAAA;;;cCXY,gBAAA;AAAA,KAED,UAAA,WAAqB,gBAAA;AAAA,KACrB,iBAAA,GAAoB,OAAA,CAAQ,UAAA;AAAA,UAEvB,gBAAA;EAAA,SACP,IAAA;EAAA,SACA,QAAA,EAAU,UAAA;AAAA;AAAA,cAGP,gBAAA;AAAA,iBAEG,eAAA,CACf,cAAA,EAAgB,UAAA,EAChB,eAAA,EAAiB,iBAAA;AAAA,iBAKF,sBAAA,CACf,WAAA,WAAsB,gBAAA,IACtB,eAAA,EAAiB,iBAAA;AAAA,iBAOF,qBAAA,CAAsB,WAAA,WAAsB,gBAAA;;;
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/gateway-runtime-contract.ts","../src/audience.ts","../src/force-ipv4-egress.ts","../src/gateway-process-spec.ts","../src/gateway-vm-spec.ts","../src/gateway-lifecycle.ts","../src/split-resolved-gateway-secrets.ts","../src/tool-vm-active-use.ts","../src/vm-capability-lease.ts","../src/tool-vm-lease.ts"],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { v7, validate, version } from "uuid";
|
|
1
2
|
//#region src/gateway-runtime-contract.ts
|
|
2
3
|
const gatewayTypeValues = ["openclaw", "worker"];
|
|
3
4
|
function buildGatewaySessionLabel(projectNamespace, zoneId) {
|
|
@@ -24,6 +25,77 @@ function gatewayVmAllowedHosts(egressHosts) {
|
|
|
24
25
|
return Array.from(new Set([controllerVmHost, ...egressHostsForAudience(egressHosts, "gateway")]));
|
|
25
26
|
}
|
|
26
27
|
//#endregion
|
|
28
|
+
//#region src/force-ipv4-egress.ts
|
|
29
|
+
/**
|
|
30
|
+
* Canonical NODE_OPTIONS value for forcing IPv4-preference egress
|
|
31
|
+
* in agent-vm VMs.
|
|
32
|
+
*
|
|
33
|
+
* Background: Gondolin's synthetic DNS (when tcpHosts is enabled)
|
|
34
|
+
* returns a per-host IPv4 (reverse-lookable) and a single shared
|
|
35
|
+
* IPv4-mapped IPv6 (::ffff:198.18.0.1, NOT reverse-lookable). Node
|
|
36
|
+
* 20+'s fetch (via undici, autoSelectFamily: true) races both
|
|
37
|
+
* families; when the IPv6 race wins (~5-20% under sequential load),
|
|
38
|
+
* gondolin cannot route it and the request fails with a non-JSON
|
|
39
|
+
* 400 (HTTP) or 403 (TLS). The two flags below stop the race:
|
|
40
|
+
*
|
|
41
|
+
* --dns-result-order=ipv4first changes dns.lookup() so
|
|
42
|
+
* IPv4 addresses are listed
|
|
43
|
+
* before IPv6.
|
|
44
|
+
*
|
|
45
|
+
* --no-network-family-autoselection disables Node's Happy
|
|
46
|
+
* Eyeballs entirely. This is
|
|
47
|
+
* the load-bearing flag —
|
|
48
|
+
* --dns-result-order alone
|
|
49
|
+
* doesn't prevent Node from
|
|
50
|
+
* racing both families if
|
|
51
|
+
* IPv4 is slow.
|
|
52
|
+
*
|
|
53
|
+
* Composition: NODE_OPTIONS is whitespace-separated. To add more
|
|
54
|
+
* flags downstream, append rather than replace. Example:
|
|
55
|
+
*
|
|
56
|
+
* NODE_OPTIONS: `${FORCE_IPV4_EGRESS_NODE_OPTIONS} --inspect`
|
|
57
|
+
*
|
|
58
|
+
* Reference: see `shravan-claw@0ddf5f2:docs/wip/debugging/
|
|
59
|
+
* 2026-05-21-lease-keepalive-400-and-discord-403-ipv6-race.md`
|
|
60
|
+
* for the full root-cause analysis. Node-side flag references:
|
|
61
|
+
* https://github.com/nodejs/node/issues/54359 (autoSelectFamily
|
|
62
|
+
* revert recommendation by the Node core team).
|
|
63
|
+
*/
|
|
64
|
+
const FORCE_IPV4_EGRESS_NODE_OPTIONS = "--dns-result-order=ipv4first --no-network-family-autoselection";
|
|
65
|
+
/**
|
|
66
|
+
* Compose the forced IPv4-preference flags with a user-provided
|
|
67
|
+
* NODE_OPTIONS value (if any).
|
|
68
|
+
*
|
|
69
|
+
* Use this at every site where NODE_OPTIONS is set into a VM env
|
|
70
|
+
* block AFTER a spread of user-controlled secrets, to guarantee
|
|
71
|
+
* the forced flags are always present in the final value even if
|
|
72
|
+
* a zone secret happens to provide its own NODE_OPTIONS.
|
|
73
|
+
*
|
|
74
|
+
* Forced flags come FIRST so they are unambiguously applied.
|
|
75
|
+
* User-provided flags are appended verbatim. Node treats NODE_OPTIONS
|
|
76
|
+
* as a whitespace-separated list and all flags apply.
|
|
77
|
+
*
|
|
78
|
+
* Returns just the forced flags if the user value is undefined,
|
|
79
|
+
* empty, or whitespace-only.
|
|
80
|
+
*
|
|
81
|
+
* Examples:
|
|
82
|
+
*
|
|
83
|
+
* composeNodeOptions(undefined)
|
|
84
|
+
* ──► '--dns-result-order=ipv4first --no-network-family-autoselection'
|
|
85
|
+
*
|
|
86
|
+
* composeNodeOptions('')
|
|
87
|
+
* ──► '--dns-result-order=ipv4first --no-network-family-autoselection'
|
|
88
|
+
*
|
|
89
|
+
* composeNodeOptions('--inspect=0.0.0.0:9229')
|
|
90
|
+
* ──► '--dns-result-order=ipv4first --no-network-family-autoselection
|
|
91
|
+
* --inspect=0.0.0.0:9229'
|
|
92
|
+
*/
|
|
93
|
+
function composeNodeOptions(userValue) {
|
|
94
|
+
const trimmed = userValue?.trim() ?? "";
|
|
95
|
+
if (trimmed === "") return FORCE_IPV4_EGRESS_NODE_OPTIONS;
|
|
96
|
+
return `${FORCE_IPV4_EGRESS_NODE_OPTIONS} ${trimmed}`;
|
|
97
|
+
}
|
|
98
|
+
//#endregion
|
|
27
99
|
//#region src/split-resolved-gateway-secrets.ts
|
|
28
100
|
function splitResolvedSecretsByInjection(secretConfigs, resolvedSecrets, options) {
|
|
29
101
|
const environmentSecrets = {};
|
|
@@ -79,6 +151,108 @@ function mergeRuntimeGatewaySecrets(baseSecrets, options = {}) {
|
|
|
79
151
|
};
|
|
80
152
|
}
|
|
81
153
|
//#endregion
|
|
82
|
-
|
|
154
|
+
//#region src/tool-vm-active-use.ts
|
|
155
|
+
const defaultMaxHeartbeatDurationMs = 720 * 60 * 1e3;
|
|
156
|
+
function createToolVmActiveUseId() {
|
|
157
|
+
return v7();
|
|
158
|
+
}
|
|
159
|
+
function isToolVmActiveUseId(value) {
|
|
160
|
+
return validate(value) && version(value) === 7;
|
|
161
|
+
}
|
|
162
|
+
async function createToolVmActiveUseHandle(options) {
|
|
163
|
+
const useId = createToolVmActiveUseId();
|
|
164
|
+
const startedUse = await options.startActiveUse({
|
|
165
|
+
...options.correlation ? { correlation: options.correlation } : {},
|
|
166
|
+
useId
|
|
167
|
+
});
|
|
168
|
+
const setTimeoutImpl = options.setTimeoutImpl ?? setTimeout;
|
|
169
|
+
const clearTimeoutImpl = options.clearTimeoutImpl ?? clearTimeout;
|
|
170
|
+
const now = options.nowImpl ?? Date.now;
|
|
171
|
+
const startedAt = now();
|
|
172
|
+
const maxHeartbeatDurationMs = options.maxHeartbeatDurationMs ?? defaultMaxHeartbeatDurationMs;
|
|
173
|
+
let ended = false;
|
|
174
|
+
let heartbeatTimer;
|
|
175
|
+
const clearHeartbeatTimer = () => {
|
|
176
|
+
if (heartbeatTimer) {
|
|
177
|
+
clearTimeoutImpl(heartbeatTimer);
|
|
178
|
+
heartbeatTimer = void 0;
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
const scheduleHeartbeat = (delayMs) => {
|
|
182
|
+
if (now() - startedAt >= maxHeartbeatDurationMs) return;
|
|
183
|
+
clearHeartbeatTimer();
|
|
184
|
+
heartbeatTimer = setTimeoutImpl(() => {
|
|
185
|
+
if (now() - startedAt >= maxHeartbeatDurationMs) return;
|
|
186
|
+
options.heartbeatActiveUse(startedUse.useId).then((heartbeat) => {
|
|
187
|
+
if (!ended) scheduleHeartbeat(heartbeat.heartbeatAfterMs);
|
|
188
|
+
}).catch((error) => {
|
|
189
|
+
options.logHeartbeatFailure?.(error);
|
|
190
|
+
if (!ended) scheduleHeartbeat(startedUse.heartbeatAfterMs);
|
|
191
|
+
});
|
|
192
|
+
}, delayMs);
|
|
193
|
+
};
|
|
194
|
+
scheduleHeartbeat(startedUse.heartbeatAfterMs);
|
|
195
|
+
const end = async (outcome = "completed") => {
|
|
196
|
+
if (ended) return;
|
|
197
|
+
ended = true;
|
|
198
|
+
clearHeartbeatTimer();
|
|
199
|
+
try {
|
|
200
|
+
await options.endActiveUse(startedUse.useId, { outcome });
|
|
201
|
+
} catch (error) {
|
|
202
|
+
if (options.isEndErrorTolerable?.(error) === true) {
|
|
203
|
+
options.logEndFailure?.(error);
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
throw error;
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
return {
|
|
210
|
+
useId: startedUse.useId,
|
|
211
|
+
dispose: end,
|
|
212
|
+
end
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
//#endregion
|
|
216
|
+
//#region src/vm-capability-lease.ts
|
|
217
|
+
const VM_SSH_PUBLIC_ENDPOINT_KEYS = new Set([
|
|
218
|
+
"host",
|
|
219
|
+
"port",
|
|
220
|
+
"user"
|
|
221
|
+
]);
|
|
222
|
+
function objectValue$1(value) {
|
|
223
|
+
return typeof value === "object" && value !== null ? value : void 0;
|
|
224
|
+
}
|
|
225
|
+
function isNonEmptyString(value) {
|
|
226
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
227
|
+
}
|
|
228
|
+
function isVmCapabilityLease(value, transport) {
|
|
229
|
+
const record = objectValue$1(value);
|
|
230
|
+
return record !== void 0 && typeof Reflect.get(record, "leaseId") === "string" && Reflect.get(record, "transport") === transport;
|
|
231
|
+
}
|
|
232
|
+
function isVmSshEndpoint(value) {
|
|
233
|
+
const record = objectValue$1(value);
|
|
234
|
+
return record !== void 0 && typeof Reflect.get(record, "host") === "string" && isNonEmptyString(Reflect.get(record, "identityPem")) && typeof Reflect.get(record, "knownHostsLine") === "string" && typeof Reflect.get(record, "port") === "number" && typeof Reflect.get(record, "user") === "string";
|
|
235
|
+
}
|
|
236
|
+
function isVmSshPublicEndpoint(value) {
|
|
237
|
+
const record = objectValue$1(value);
|
|
238
|
+
if (record === void 0) return false;
|
|
239
|
+
for (const key of Object.keys(record)) if (!VM_SSH_PUBLIC_ENDPOINT_KEYS.has(key)) return false;
|
|
240
|
+
return typeof Reflect.get(record, "host") === "string" && typeof Reflect.get(record, "port") === "number" && typeof Reflect.get(record, "user") === "string";
|
|
241
|
+
}
|
|
242
|
+
//#endregion
|
|
243
|
+
//#region src/tool-vm-lease.ts
|
|
244
|
+
function objectValue(value) {
|
|
245
|
+
return typeof value === "object" && value !== null ? value : void 0;
|
|
246
|
+
}
|
|
247
|
+
function isToolVmSshLease(value) {
|
|
248
|
+
const record = objectValue(value);
|
|
249
|
+
return isVmCapabilityLease(record, "ssh-sandbox") && isVmSshEndpoint(Reflect.get(record, "ssh")) && typeof Reflect.get(record, "tcpSlot") === "number" && typeof Reflect.get(record, "workdir") === "string";
|
|
250
|
+
}
|
|
251
|
+
function isToolVmLeasePeek(value) {
|
|
252
|
+
const record = objectValue(value);
|
|
253
|
+
return isVmCapabilityLease(record, "ssh-sandbox") && typeof Reflect.get(record, "createdAt") === "number" && typeof Reflect.get(record, "lastUsedAt") === "number" && typeof Reflect.get(record, "profileId") === "string" && typeof Reflect.get(record, "scopeKey") === "string" && isVmSshPublicEndpoint(Reflect.get(record, "ssh")) && typeof Reflect.get(record, "tcpSlot") === "number" && typeof Reflect.get(record, "workdir") === "string" && typeof Reflect.get(record, "zoneId") === "string";
|
|
254
|
+
}
|
|
255
|
+
//#endregion
|
|
256
|
+
export { FORCE_IPV4_EGRESS_NODE_OPTIONS, buildGatewaySessionLabel, buildToolSessionLabel, composeNodeOptions, controllerVmHost, createToolVmActiveUseHandle, createToolVmActiveUseId, egressHostsForAudience, gatewayTypeValues, gatewayVmAllowedHosts, isToolVmActiveUseId, isToolVmLeasePeek, isToolVmSshLease, isVmCapabilityLease, isVmSshEndpoint, isVmSshPublicEndpoint, mergeRuntimeGatewaySecrets, splitResolvedGatewaySecrets, splitResolvedSecretsByInjection, targetsAudience, vmAudienceValues };
|
|
83
257
|
|
|
84
258
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../src/gateway-runtime-contract.ts","../src/audience.ts","../src/split-resolved-gateway-secrets.ts"],"sourcesContent":["export const gatewayTypeValues = ['openclaw', 'worker'] as const;\n\nexport type GatewayType = (typeof gatewayTypeValues)[number];\n\nexport function buildGatewaySessionLabel(projectNamespace: string, zoneId: string): string {\n\treturn `${projectNamespace}:${zoneId}:gateway`;\n}\n\nexport function buildToolSessionLabel(\n\tprojectNamespace: string,\n\tzoneId: string,\n\ttcpSlot: number,\n): string {\n\treturn `${projectNamespace}:${zoneId}:tool:${tcpSlot}`;\n}\n","export const vmAudienceValues = ['gateway', 'tool-vm', 'both'] as const;\n\nexport type VmAudience = (typeof vmAudienceValues)[number];\nexport type RuntimeVmAudience = Exclude<VmAudience, 'both'>;\n\nexport interface EgressHostConfig {\n\treadonly host: string;\n\treadonly audience: VmAudience;\n}\n\nexport const controllerVmHost = 'controller.vm.host';\n\nexport function targetsAudience(\n\tconfigAudience: VmAudience,\n\truntimeAudience: RuntimeVmAudience,\n): boolean {\n\treturn configAudience === runtimeAudience || configAudience === 'both';\n}\n\nexport function egressHostsForAudience(\n\tegressHosts: readonly EgressHostConfig[],\n\truntimeAudience: RuntimeVmAudience,\n): readonly string[] {\n\treturn egressHosts\n\t\t.filter((egressHost) => targetsAudience(egressHost.audience, runtimeAudience))\n\t\t.map((egressHost) => egressHost.host);\n}\n\nexport function gatewayVmAllowedHosts(egressHosts: readonly EgressHostConfig[]): readonly string[] {\n\treturn Array.from(new Set([controllerVmHost, ...egressHostsForAudience(egressHosts, 'gateway')]));\n}\n","import type { MediatedSecretSpec } from '@agent-vm/secrets';\n\nimport { targetsAudience, type RuntimeVmAudience } from './audience.js';\nimport type { GatewaySecretConfig, GatewayZoneConfig } from './gateway-lifecycle.js';\n\nexport interface SplitResolvedSecretsResult {\n\treadonly environmentSecrets: Record<string, string>;\n\treadonly mediatedSecrets: Record<string, MediatedSecretSpec>;\n}\n\nexport interface MergeRuntimeGatewaySecretsOptions {\n\treadonly logPrefix?: string;\n\treadonly runtimeEnvironment?: Readonly<Record<string, string>> | undefined;\n\treadonly runtimeMediatedSecrets?: Readonly<Record<string, MediatedSecretSpec>> | undefined;\n}\n\nexport type SecretInjectionConfig = GatewaySecretConfig;\n\nexport interface SplitResolvedSecretsOptions {\n\treadonly audience: RuntimeVmAudience;\n\treadonly logPrefix?: string;\n}\n\nexport function splitResolvedSecretsByInjection(\n\tsecretConfigs: Readonly<Record<string, SecretInjectionConfig>>,\n\tresolvedSecrets: Record<string, string>,\n\toptions: SplitResolvedSecretsOptions,\n): SplitResolvedSecretsResult {\n\tconst environmentSecrets: Record<string, string> = {};\n\tconst mediatedSecrets: Record<string, MediatedSecretSpec> = {};\n\tconst logPrefix = options.logPrefix ?? 'split-resolved-secrets';\n\n\tfor (const [secretName, secretValue] of Object.entries(resolvedSecrets)) {\n\t\tconst secretConfig = secretConfigs[secretName];\n\t\tif (!secretConfig) {\n\t\t\tthrow new Error(\n\t\t\t\t`[${logPrefix}] Secret '${secretName}' was resolved but has no matching secret config.`,\n\t\t\t);\n\t\t}\n\t\tif (!targetsAudience(secretConfig.audience, options.audience)) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (secretConfig.injection === 'http-mediation') {\n\t\t\tif (secretConfig.hosts.length === 0) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`[${logPrefix}] Secret '${secretName}' uses http-mediation but declares no hosts.`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tmediatedSecrets[secretName] = {\n\t\t\t\thosts: [...secretConfig.hosts],\n\t\t\t\tvalue: secretValue,\n\t\t\t};\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst envSecretAudience = (secretConfig as { readonly audience: string }).audience;\n\t\tif (envSecretAudience !== 'gateway') {\n\t\t\tthrow new Error(\n\t\t\t\t`[${logPrefix}] Secret '${secretName}' uses env injection with non-gateway audience '${envSecretAudience}'.`,\n\t\t\t);\n\t\t}\n\t\tif (options.audience === 'gateway') {\n\t\t\tenvironmentSecrets[secretName] = secretValue;\n\t\t}\n\t}\n\n\treturn { environmentSecrets, mediatedSecrets };\n}\n\nexport type SplitResolvedGatewaySecretsResult = SplitResolvedSecretsResult;\n\nexport function splitResolvedGatewaySecrets(\n\tzone: GatewayZoneConfig,\n\tresolvedSecrets: Record<string, string>,\n): SplitResolvedGatewaySecretsResult {\n\treturn splitResolvedSecretsByInjection(zone.secrets, resolvedSecrets, {\n\t\taudience: 'gateway',\n\t\tlogPrefix: 'split-resolved-gateway-secrets',\n\t});\n}\n\nfunction assertNoRuntimeSecretCollision(\n\tsecretName: string,\n\ttarget: 'environment' | 'http-mediation',\n\tbaseSecrets: SplitResolvedSecretsResult,\n\truntimeSeen: Set<string>,\n\tlogPrefix: string,\n): void {\n\tif (runtimeSeen.has(secretName)) {\n\t\tthrow new Error(\n\t\t\t`[${logPrefix}] Runtime gateway secret '${secretName}' is declared for both environment and http-mediation injection.`,\n\t\t);\n\t}\n\tif (secretName in baseSecrets.environmentSecrets) {\n\t\tthrow new Error(\n\t\t\t`[${logPrefix}] Runtime gateway ${target} secret '${secretName}' would overwrite an authored environment secret.`,\n\t\t);\n\t}\n\tif (secretName in baseSecrets.mediatedSecrets) {\n\t\tthrow new Error(\n\t\t\t`[${logPrefix}] Runtime gateway ${target} secret '${secretName}' would overwrite an authored http-mediation secret.`,\n\t\t);\n\t}\n\truntimeSeen.add(secretName);\n}\n\nexport function mergeRuntimeGatewaySecrets(\n\tbaseSecrets: SplitResolvedSecretsResult,\n\toptions: MergeRuntimeGatewaySecretsOptions = {},\n): SplitResolvedSecretsResult {\n\tconst logPrefix = options.logPrefix ?? 'merge-runtime-gateway-secrets';\n\tconst runtimeSeen = new Set<string>();\n\tfor (const secretName of Object.keys(options.runtimeEnvironment ?? {})) {\n\t\tassertNoRuntimeSecretCollision(secretName, 'environment', baseSecrets, runtimeSeen, logPrefix);\n\t}\n\tfor (const secretName of Object.keys(options.runtimeMediatedSecrets ?? {})) {\n\t\tassertNoRuntimeSecretCollision(\n\t\t\tsecretName,\n\t\t\t'http-mediation',\n\t\t\tbaseSecrets,\n\t\t\truntimeSeen,\n\t\t\tlogPrefix,\n\t\t);\n\t}\n\n\treturn {\n\t\tenvironmentSecrets: {\n\t\t\t...baseSecrets.environmentSecrets,\n\t\t\t...options.runtimeEnvironment,\n\t\t},\n\t\tmediatedSecrets: {\n\t\t\t...baseSecrets.mediatedSecrets,\n\t\t\t...options.runtimeMediatedSecrets,\n\t\t},\n\t};\n}\n"],"mappings":";AAAA,MAAa,oBAAoB,CAAC,YAAY,SAAS;AAIvD,SAAgB,yBAAyB,kBAA0B,QAAwB;CAC1F,OAAO,GAAG,iBAAiB,GAAG,OAAO;;AAGtC,SAAgB,sBACf,kBACA,QACA,SACS;CACT,OAAO,GAAG,iBAAiB,GAAG,OAAO,QAAQ;;;;ACb9C,MAAa,mBAAmB;CAAC;CAAW;CAAW;CAAO;AAU9D,MAAa,mBAAmB;AAEhC,SAAgB,gBACf,gBACA,iBACU;CACV,OAAO,mBAAmB,mBAAmB,mBAAmB;;AAGjE,SAAgB,uBACf,aACA,iBACoB;CACpB,OAAO,YACL,QAAQ,eAAe,gBAAgB,WAAW,UAAU,gBAAgB,CAAC,CAC7E,KAAK,eAAe,WAAW,KAAK;;AAGvC,SAAgB,sBAAsB,aAA6D;CAClG,OAAO,MAAM,KAAK,IAAI,IAAI,CAAC,kBAAkB,GAAG,uBAAuB,aAAa,UAAU,CAAC,CAAC,CAAC;;;;ACNlG,SAAgB,gCACf,eACA,iBACA,SAC6B;CAC7B,MAAM,qBAA6C,EAAE;CACrD,MAAM,kBAAsD,EAAE;CAC9D,MAAM,YAAY,QAAQ,aAAa;CAEvC,KAAK,MAAM,CAAC,YAAY,gBAAgB,OAAO,QAAQ,gBAAgB,EAAE;EACxE,MAAM,eAAe,cAAc;EACnC,IAAI,CAAC,cACJ,MAAM,IAAI,MACT,IAAI,UAAU,YAAY,WAAW,mDACrC;EAEF,IAAI,CAAC,gBAAgB,aAAa,UAAU,QAAQ,SAAS,EAC5D;EAGD,IAAI,aAAa,cAAc,kBAAkB;GAChD,IAAI,aAAa,MAAM,WAAW,GACjC,MAAM,IAAI,MACT,IAAI,UAAU,YAAY,WAAW,8CACrC;GAEF,gBAAgB,cAAc;IAC7B,OAAO,CAAC,GAAG,aAAa,MAAM;IAC9B,OAAO;IACP;GACD;;EAGD,MAAM,oBAAqB,aAA+C;EAC1E,IAAI,sBAAsB,WACzB,MAAM,IAAI,MACT,IAAI,UAAU,YAAY,WAAW,kDAAkD,kBAAkB,IACzG;EAEF,IAAI,QAAQ,aAAa,WACxB,mBAAmB,cAAc;;CAInC,OAAO;EAAE;EAAoB;EAAiB;;AAK/C,SAAgB,4BACf,MACA,iBACoC;CACpC,OAAO,gCAAgC,KAAK,SAAS,iBAAiB;EACrE,UAAU;EACV,WAAW;EACX,CAAC;;AAGH,SAAS,+BACR,YACA,QACA,aACA,aACA,WACO;CACP,IAAI,YAAY,IAAI,WAAW,EAC9B,MAAM,IAAI,MACT,IAAI,UAAU,4BAA4B,WAAW,kEACrD;CAEF,IAAI,cAAc,YAAY,oBAC7B,MAAM,IAAI,MACT,IAAI,UAAU,oBAAoB,OAAO,WAAW,WAAW,mDAC/D;CAEF,IAAI,cAAc,YAAY,iBAC7B,MAAM,IAAI,MACT,IAAI,UAAU,oBAAoB,OAAO,WAAW,WAAW,sDAC/D;CAEF,YAAY,IAAI,WAAW;;AAG5B,SAAgB,2BACf,aACA,UAA6C,EAAE,EAClB;CAC7B,MAAM,YAAY,QAAQ,aAAa;CACvC,MAAM,8BAAc,IAAI,KAAa;CACrC,KAAK,MAAM,cAAc,OAAO,KAAK,QAAQ,sBAAsB,EAAE,CAAC,EACrE,+BAA+B,YAAY,eAAe,aAAa,aAAa,UAAU;CAE/F,KAAK,MAAM,cAAc,OAAO,KAAK,QAAQ,0BAA0B,EAAE,CAAC,EACzE,+BACC,YACA,kBACA,aACA,aACA,UACA;CAGF,OAAO;EACN,oBAAoB;GACnB,GAAG,YAAY;GACf,GAAG,QAAQ;GACX;EACD,iBAAiB;GAChB,GAAG,YAAY;GACf,GAAG,QAAQ;GACX;EACD"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["uuidv7","validateUuid","uuidVersion","objectValue"],"sources":["../src/gateway-runtime-contract.ts","../src/audience.ts","../src/force-ipv4-egress.ts","../src/split-resolved-gateway-secrets.ts","../src/tool-vm-active-use.ts","../src/vm-capability-lease.ts","../src/tool-vm-lease.ts"],"sourcesContent":["export const gatewayTypeValues = ['openclaw', 'worker'] as const;\n\nexport type GatewayType = (typeof gatewayTypeValues)[number];\n\nexport function buildGatewaySessionLabel(projectNamespace: string, zoneId: string): string {\n\treturn `${projectNamespace}:${zoneId}:gateway`;\n}\n\nexport function buildToolSessionLabel(\n\tprojectNamespace: string,\n\tzoneId: string,\n\ttcpSlot: number,\n): string {\n\treturn `${projectNamespace}:${zoneId}:tool:${tcpSlot}`;\n}\n","export const vmAudienceValues = ['gateway', 'tool-vm', 'both'] as const;\n\nexport type VmAudience = (typeof vmAudienceValues)[number];\nexport type RuntimeVmAudience = Exclude<VmAudience, 'both'>;\n\nexport interface EgressHostConfig {\n\treadonly host: string;\n\treadonly audience: VmAudience;\n}\n\nexport const controllerVmHost = 'controller.vm.host';\n\nexport function targetsAudience(\n\tconfigAudience: VmAudience,\n\truntimeAudience: RuntimeVmAudience,\n): boolean {\n\treturn configAudience === runtimeAudience || configAudience === 'both';\n}\n\nexport function egressHostsForAudience(\n\tegressHosts: readonly EgressHostConfig[],\n\truntimeAudience: RuntimeVmAudience,\n): readonly string[] {\n\treturn egressHosts\n\t\t.filter((egressHost) => targetsAudience(egressHost.audience, runtimeAudience))\n\t\t.map((egressHost) => egressHost.host);\n}\n\nexport function gatewayVmAllowedHosts(egressHosts: readonly EgressHostConfig[]): readonly string[] {\n\treturn Array.from(new Set([controllerVmHost, ...egressHostsForAudience(egressHosts, 'gateway')]));\n}\n","/**\n * Canonical NODE_OPTIONS value for forcing IPv4-preference egress\n * in agent-vm VMs.\n *\n * Background: Gondolin's synthetic DNS (when tcpHosts is enabled)\n * returns a per-host IPv4 (reverse-lookable) and a single shared\n * IPv4-mapped IPv6 (::ffff:198.18.0.1, NOT reverse-lookable). Node\n * 20+'s fetch (via undici, autoSelectFamily: true) races both\n * families; when the IPv6 race wins (~5-20% under sequential load),\n * gondolin cannot route it and the request fails with a non-JSON\n * 400 (HTTP) or 403 (TLS). The two flags below stop the race:\n *\n * --dns-result-order=ipv4first changes dns.lookup() so\n * IPv4 addresses are listed\n * before IPv6.\n *\n * --no-network-family-autoselection disables Node's Happy\n * Eyeballs entirely. This is\n * the load-bearing flag —\n * --dns-result-order alone\n * doesn't prevent Node from\n * racing both families if\n * IPv4 is slow.\n *\n * Composition: NODE_OPTIONS is whitespace-separated. To add more\n * flags downstream, append rather than replace. Example:\n *\n * NODE_OPTIONS: `${FORCE_IPV4_EGRESS_NODE_OPTIONS} --inspect`\n *\n * Reference: see `shravan-claw@0ddf5f2:docs/wip/debugging/\n * 2026-05-21-lease-keepalive-400-and-discord-403-ipv6-race.md`\n * for the full root-cause analysis. Node-side flag references:\n * https://github.com/nodejs/node/issues/54359 (autoSelectFamily\n * revert recommendation by the Node core team).\n */\nexport const FORCE_IPV4_EGRESS_NODE_OPTIONS =\n\t'--dns-result-order=ipv4first --no-network-family-autoselection';\n\n/**\n * Compose the forced IPv4-preference flags with a user-provided\n * NODE_OPTIONS value (if any).\n *\n * Use this at every site where NODE_OPTIONS is set into a VM env\n * block AFTER a spread of user-controlled secrets, to guarantee\n * the forced flags are always present in the final value even if\n * a zone secret happens to provide its own NODE_OPTIONS.\n *\n * Forced flags come FIRST so they are unambiguously applied.\n * User-provided flags are appended verbatim. Node treats NODE_OPTIONS\n * as a whitespace-separated list and all flags apply.\n *\n * Returns just the forced flags if the user value is undefined,\n * empty, or whitespace-only.\n *\n * Examples:\n *\n * composeNodeOptions(undefined)\n * ──► '--dns-result-order=ipv4first --no-network-family-autoselection'\n *\n * composeNodeOptions('')\n * ──► '--dns-result-order=ipv4first --no-network-family-autoselection'\n *\n * composeNodeOptions('--inspect=0.0.0.0:9229')\n * ──► '--dns-result-order=ipv4first --no-network-family-autoselection\n * --inspect=0.0.0.0:9229'\n */\nexport function composeNodeOptions(userValue: string | undefined): string {\n\tconst trimmed = userValue?.trim() ?? '';\n\tif (trimmed === '') {\n\t\treturn FORCE_IPV4_EGRESS_NODE_OPTIONS;\n\t}\n\treturn `${FORCE_IPV4_EGRESS_NODE_OPTIONS} ${trimmed}`;\n}\n","import type { MediatedSecretSpec } from '@agent-vm/secret-management';\n\nimport { targetsAudience, type RuntimeVmAudience } from './audience.js';\nimport type { GatewaySecretConfig, GatewayZoneConfig } from './gateway-lifecycle.js';\n\nexport interface SplitResolvedSecretsResult {\n\treadonly environmentSecrets: Record<string, string>;\n\treadonly mediatedSecrets: Record<string, MediatedSecretSpec>;\n}\n\nexport interface MergeRuntimeGatewaySecretsOptions {\n\treadonly logPrefix?: string;\n\treadonly runtimeEnvironment?: Readonly<Record<string, string>> | undefined;\n\treadonly runtimeMediatedSecrets?: Readonly<Record<string, MediatedSecretSpec>> | undefined;\n}\n\nexport type SecretInjectionConfig = GatewaySecretConfig;\n\nexport interface SplitResolvedSecretsOptions {\n\treadonly audience: RuntimeVmAudience;\n\treadonly logPrefix?: string;\n}\n\nexport function splitResolvedSecretsByInjection(\n\tsecretConfigs: Readonly<Record<string, SecretInjectionConfig>>,\n\tresolvedSecrets: Record<string, string>,\n\toptions: SplitResolvedSecretsOptions,\n): SplitResolvedSecretsResult {\n\tconst environmentSecrets: Record<string, string> = {};\n\tconst mediatedSecrets: Record<string, MediatedSecretSpec> = {};\n\tconst logPrefix = options.logPrefix ?? 'split-resolved-secrets';\n\n\tfor (const [secretName, secretValue] of Object.entries(resolvedSecrets)) {\n\t\tconst secretConfig = secretConfigs[secretName];\n\t\tif (!secretConfig) {\n\t\t\tthrow new Error(\n\t\t\t\t`[${logPrefix}] Secret '${secretName}' was resolved but has no matching secret config.`,\n\t\t\t);\n\t\t}\n\t\tif (!targetsAudience(secretConfig.audience, options.audience)) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (secretConfig.injection === 'http-mediation') {\n\t\t\tif (secretConfig.hosts.length === 0) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`[${logPrefix}] Secret '${secretName}' uses http-mediation but declares no hosts.`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tmediatedSecrets[secretName] = {\n\t\t\t\thosts: [...secretConfig.hosts],\n\t\t\t\tvalue: secretValue,\n\t\t\t};\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst envSecretAudience = (secretConfig as { readonly audience: string }).audience;\n\t\tif (envSecretAudience !== 'gateway') {\n\t\t\tthrow new Error(\n\t\t\t\t`[${logPrefix}] Secret '${secretName}' uses env injection with non-gateway audience '${envSecretAudience}'.`,\n\t\t\t);\n\t\t}\n\t\tif (options.audience === 'gateway') {\n\t\t\tenvironmentSecrets[secretName] = secretValue;\n\t\t}\n\t}\n\n\treturn { environmentSecrets, mediatedSecrets };\n}\n\nexport type SplitResolvedGatewaySecretsResult = SplitResolvedSecretsResult;\n\nexport function splitResolvedGatewaySecrets(\n\tzone: GatewayZoneConfig,\n\tresolvedSecrets: Record<string, string>,\n): SplitResolvedGatewaySecretsResult {\n\treturn splitResolvedSecretsByInjection(zone.secrets, resolvedSecrets, {\n\t\taudience: 'gateway',\n\t\tlogPrefix: 'split-resolved-gateway-secrets',\n\t});\n}\n\nfunction assertNoRuntimeSecretCollision(\n\tsecretName: string,\n\ttarget: 'environment' | 'http-mediation',\n\tbaseSecrets: SplitResolvedSecretsResult,\n\truntimeSeen: Set<string>,\n\tlogPrefix: string,\n): void {\n\tif (runtimeSeen.has(secretName)) {\n\t\tthrow new Error(\n\t\t\t`[${logPrefix}] Runtime gateway secret '${secretName}' is declared for both environment and http-mediation injection.`,\n\t\t);\n\t}\n\tif (secretName in baseSecrets.environmentSecrets) {\n\t\tthrow new Error(\n\t\t\t`[${logPrefix}] Runtime gateway ${target} secret '${secretName}' would overwrite an authored environment secret.`,\n\t\t);\n\t}\n\tif (secretName in baseSecrets.mediatedSecrets) {\n\t\tthrow new Error(\n\t\t\t`[${logPrefix}] Runtime gateway ${target} secret '${secretName}' would overwrite an authored http-mediation secret.`,\n\t\t);\n\t}\n\truntimeSeen.add(secretName);\n}\n\nexport function mergeRuntimeGatewaySecrets(\n\tbaseSecrets: SplitResolvedSecretsResult,\n\toptions: MergeRuntimeGatewaySecretsOptions = {},\n): SplitResolvedSecretsResult {\n\tconst logPrefix = options.logPrefix ?? 'merge-runtime-gateway-secrets';\n\tconst runtimeSeen = new Set<string>();\n\tfor (const secretName of Object.keys(options.runtimeEnvironment ?? {})) {\n\t\tassertNoRuntimeSecretCollision(secretName, 'environment', baseSecrets, runtimeSeen, logPrefix);\n\t}\n\tfor (const secretName of Object.keys(options.runtimeMediatedSecrets ?? {})) {\n\t\tassertNoRuntimeSecretCollision(\n\t\t\tsecretName,\n\t\t\t'http-mediation',\n\t\t\tbaseSecrets,\n\t\t\truntimeSeen,\n\t\t\tlogPrefix,\n\t\t);\n\t}\n\n\treturn {\n\t\tenvironmentSecrets: {\n\t\t\t...baseSecrets.environmentSecrets,\n\t\t\t...options.runtimeEnvironment,\n\t\t},\n\t\tmediatedSecrets: {\n\t\t\t...baseSecrets.mediatedSecrets,\n\t\t\t...options.runtimeMediatedSecrets,\n\t\t},\n\t};\n}\n","import { v7 as uuidv7, validate as validateUuid, version as uuidVersion } from 'uuid';\n\nexport type ToolVmActiveUseOutcome =\n\t| 'abandoned'\n\t| 'cancelled'\n\t| 'completed'\n\t| 'failed'\n\t| 'timed-out';\n\nexport interface ToolVmActiveUseCorrelation {\n\treadonly agentId?: string;\n\treadonly sessionId?: string;\n\treadonly sessionKey?: string;\n\treadonly toolCallId?: string;\n\treadonly toolName?: string;\n}\n\nexport interface StartToolVmActiveUseRequest {\n\treadonly correlation?: ToolVmActiveUseCorrelation;\n\treadonly useId: string;\n}\n\nexport interface StartToolVmActiveUseResponse {\n\treadonly expiresAt: number;\n\treadonly heartbeatAfterMs: number;\n\treadonly useId: string;\n}\n\nexport interface HeartbeatToolVmActiveUseResponse {\n\treadonly expiresAt: number;\n\treadonly heartbeatAfterMs: number;\n}\n\nexport interface EndToolVmActiveUseRequest {\n\treadonly outcome: ToolVmActiveUseOutcome;\n}\n\nexport interface ToolVmActiveUseHandle {\n\treadonly useId: string;\n\tdispose(outcome?: ToolVmActiveUseOutcome): Promise<void>;\n\tend(outcome?: ToolVmActiveUseOutcome): Promise<void>;\n}\n\nexport interface CreateToolVmActiveUseHandleOptions {\n\treadonly correlation?: ToolVmActiveUseCorrelation;\n\treadonly endActiveUse: (useId: string, request: EndToolVmActiveUseRequest) => Promise<void>;\n\treadonly heartbeatActiveUse: (useId: string) => Promise<HeartbeatToolVmActiveUseResponse>;\n\treadonly isEndErrorTolerable?: (error: unknown) => boolean;\n\treadonly logEndFailure?: (error: unknown) => void;\n\treadonly logHeartbeatFailure?: (error: unknown) => void;\n\treadonly maxHeartbeatDurationMs?: number;\n\treadonly nowImpl?: () => number;\n\treadonly startActiveUse: (\n\t\trequest: StartToolVmActiveUseRequest,\n\t) => Promise<StartToolVmActiveUseResponse>;\n\treadonly setTimeoutImpl?: typeof setTimeout;\n\treadonly clearTimeoutImpl?: typeof clearTimeout;\n}\n\ntype HeartbeatTimer = ReturnType<typeof setTimeout>;\n\nconst defaultMaxHeartbeatDurationMs = 12 * 60 * 60 * 1000;\n\nexport function createToolVmActiveUseId(): string {\n\treturn uuidv7();\n}\n\nexport function isToolVmActiveUseId(value: string): boolean {\n\treturn validateUuid(value) && uuidVersion(value) === 7;\n}\n\nexport async function createToolVmActiveUseHandle(\n\toptions: CreateToolVmActiveUseHandleOptions,\n): Promise<ToolVmActiveUseHandle> {\n\tconst useId = createToolVmActiveUseId();\n\tconst startedUse = await options.startActiveUse({\n\t\t...(options.correlation ? { correlation: options.correlation } : {}),\n\t\tuseId,\n\t});\n\tconst setTimeoutImpl = options.setTimeoutImpl ?? setTimeout;\n\tconst clearTimeoutImpl = options.clearTimeoutImpl ?? clearTimeout;\n\tconst now = options.nowImpl ?? Date.now;\n\tconst startedAt = now();\n\tconst maxHeartbeatDurationMs = options.maxHeartbeatDurationMs ?? defaultMaxHeartbeatDurationMs;\n\tlet ended = false;\n\tlet heartbeatTimer: HeartbeatTimer | undefined;\n\n\tconst clearHeartbeatTimer = (): void => {\n\t\tif (heartbeatTimer) {\n\t\t\tclearTimeoutImpl(heartbeatTimer);\n\t\t\theartbeatTimer = undefined;\n\t\t}\n\t};\n\n\tconst scheduleHeartbeat = (delayMs: number): void => {\n\t\tif (now() - startedAt >= maxHeartbeatDurationMs) {\n\t\t\treturn;\n\t\t}\n\t\tclearHeartbeatTimer();\n\t\theartbeatTimer = setTimeoutImpl(() => {\n\t\t\tif (now() - startedAt >= maxHeartbeatDurationMs) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tvoid options\n\t\t\t\t.heartbeatActiveUse(startedUse.useId)\n\t\t\t\t.then((heartbeat) => {\n\t\t\t\t\tif (!ended) {\n\t\t\t\t\t\tscheduleHeartbeat(heartbeat.heartbeatAfterMs);\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t\t.catch((error: unknown) => {\n\t\t\t\t\toptions.logHeartbeatFailure?.(error);\n\t\t\t\t\tif (!ended) {\n\t\t\t\t\t\tscheduleHeartbeat(startedUse.heartbeatAfterMs);\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t}, delayMs);\n\t};\n\n\tscheduleHeartbeat(startedUse.heartbeatAfterMs);\n\n\tconst end = async (outcome: ToolVmActiveUseOutcome = 'completed'): Promise<void> => {\n\t\tif (ended) {\n\t\t\treturn;\n\t\t}\n\t\tended = true;\n\t\tclearHeartbeatTimer();\n\t\ttry {\n\t\t\tawait options.endActiveUse(startedUse.useId, { outcome });\n\t\t} catch (error) {\n\t\t\tif (options.isEndErrorTolerable?.(error) === true) {\n\t\t\t\toptions.logEndFailure?.(error);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tthrow error;\n\t\t}\n\t};\n\n\treturn {\n\t\tuseId: startedUse.useId,\n\t\tdispose: end,\n\t\tend,\n\t};\n}\n","const VM_SSH_PUBLIC_ENDPOINT_KEYS = new Set(['host', 'port', 'user']);\n\n/**\n * Small host-issued capability envelope shared by VM-backed transports. The\n * transport tag keeps SSH Tool VM leases distinct from future host-side\n * Gondolin RPC or bridge capabilities without inventing a transport object.\n */\nexport interface VmCapabilityLease<TTransport extends string> {\n\treadonly leaseId: string;\n\treadonly transport: TTransport;\n}\n\nexport interface VmSshEndpoint {\n\treadonly host: string;\n\treadonly identityPem: string;\n\treadonly knownHostsLine: string;\n\treadonly port: number;\n\treadonly user: string;\n}\n\nexport interface VmSshPublicEndpoint {\n\treadonly host: string;\n\treadonly port: number;\n\treadonly user: string;\n}\n\nexport interface VmSshLease<TTransport extends string> extends VmCapabilityLease<TTransport> {\n\treadonly ssh: VmSshEndpoint;\n}\n\nfunction objectValue(value: unknown): object | undefined {\n\treturn typeof value === 'object' && value !== null ? value : undefined;\n}\n\nfunction isNonEmptyString(value: unknown): value is string {\n\treturn typeof value === 'string' && value.trim().length > 0;\n}\n\nexport function isVmCapabilityLease<TTransport extends string>(\n\tvalue: unknown,\n\ttransport: TTransport,\n): value is VmCapabilityLease<TTransport> {\n\tconst record = objectValue(value);\n\treturn (\n\t\trecord !== undefined &&\n\t\ttypeof Reflect.get(record, 'leaseId') === 'string' &&\n\t\tReflect.get(record, 'transport') === transport\n\t);\n}\n\nexport function isVmSshEndpoint(value: unknown): value is VmSshEndpoint {\n\tconst record = objectValue(value);\n\treturn (\n\t\trecord !== undefined &&\n\t\ttypeof Reflect.get(record, 'host') === 'string' &&\n\t\tisNonEmptyString(Reflect.get(record, 'identityPem')) &&\n\t\ttypeof Reflect.get(record, 'knownHostsLine') === 'string' &&\n\t\ttypeof Reflect.get(record, 'port') === 'number' &&\n\t\ttypeof Reflect.get(record, 'user') === 'string'\n\t);\n}\n\nexport function isVmSshPublicEndpoint(value: unknown): value is VmSshPublicEndpoint {\n\tconst record = objectValue(value);\n\tif (record === undefined) {\n\t\treturn false;\n\t}\n\tfor (const key of Object.keys(record)) {\n\t\tif (!VM_SSH_PUBLIC_ENDPOINT_KEYS.has(key)) {\n\t\t\treturn false;\n\t\t}\n\t}\n\treturn (\n\t\ttypeof Reflect.get(record, 'host') === 'string' &&\n\t\ttypeof Reflect.get(record, 'port') === 'number' &&\n\t\ttypeof Reflect.get(record, 'user') === 'string'\n\t);\n}\n","import {\n\tisVmCapabilityLease,\n\tisVmSshEndpoint,\n\tisVmSshPublicEndpoint,\n\ttype VmCapabilityLease,\n\ttype VmSshLease,\n\ttype VmSshPublicEndpoint,\n} from './vm-capability-lease.js';\n\nexport interface ToolVmSshLease extends VmSshLease<'ssh-sandbox'> {\n\treadonly tcpSlot: number;\n\treadonly workdir: string;\n}\n\nexport interface ToolVmLeasePeek extends VmCapabilityLease<'ssh-sandbox'> {\n\treadonly createdAt: number;\n\treadonly lastUsedAt: number;\n\treadonly profileId: string;\n\treadonly scopeKey: string;\n\treadonly ssh: VmSshPublicEndpoint;\n\treadonly tcpSlot: number;\n\treadonly workdir: string;\n\treadonly zoneId: string;\n}\n\nfunction objectValue(value: unknown): object | undefined {\n\treturn typeof value === 'object' && value !== null ? value : undefined;\n}\n\nexport function isToolVmSshLease(value: unknown): value is ToolVmSshLease {\n\tconst record = objectValue(value);\n\treturn (\n\t\tisVmCapabilityLease(record, 'ssh-sandbox') &&\n\t\tisVmSshEndpoint(Reflect.get(record, 'ssh')) &&\n\t\ttypeof Reflect.get(record, 'tcpSlot') === 'number' &&\n\t\ttypeof Reflect.get(record, 'workdir') === 'string'\n\t);\n}\n\nexport function isToolVmLeasePeek(value: unknown): value is ToolVmLeasePeek {\n\tconst record = objectValue(value);\n\treturn (\n\t\tisVmCapabilityLease(record, 'ssh-sandbox') &&\n\t\ttypeof Reflect.get(record, 'createdAt') === 'number' &&\n\t\ttypeof Reflect.get(record, 'lastUsedAt') === 'number' &&\n\t\ttypeof Reflect.get(record, 'profileId') === 'string' &&\n\t\ttypeof Reflect.get(record, 'scopeKey') === 'string' &&\n\t\tisVmSshPublicEndpoint(Reflect.get(record, 'ssh')) &&\n\t\ttypeof Reflect.get(record, 'tcpSlot') === 'number' &&\n\t\ttypeof Reflect.get(record, 'workdir') === 'string' &&\n\t\ttypeof Reflect.get(record, 'zoneId') === 'string'\n\t);\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-vm/gateway-interface",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.72",
|
|
4
4
|
"description": "Shared TypeScript interfaces for VM gateway lifecycles, VmSpec, and ProcessSpec.",
|
|
5
5
|
"homepage": "https://github.com/ShravanSunder/agent-vm#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -29,8 +29,9 @@
|
|
|
29
29
|
"access": "public"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"
|
|
33
|
-
"@agent-vm/
|
|
32
|
+
"uuid": "^11.1.1",
|
|
33
|
+
"@agent-vm/secret-management": "0.0.72",
|
|
34
|
+
"@agent-vm/gondolin-adapter": "0.0.72"
|
|
34
35
|
},
|
|
35
36
|
"scripts": {
|
|
36
37
|
"build": "tsdown",
|