@wingman-ai/gateway 0.3.2 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -111
- package/dist/agent/config/agentConfig.cjs +2 -0
- package/dist/agent/config/agentConfig.d.ts +6 -0
- package/dist/agent/config/agentConfig.js +2 -0
- package/dist/agent/config/agentLoader.cjs +21 -18
- package/dist/agent/config/agentLoader.js +22 -19
- package/dist/agent/config/mcpClientManager.cjs +48 -9
- package/dist/agent/config/mcpClientManager.d.ts +12 -0
- package/dist/agent/config/mcpClientManager.js +48 -9
- package/dist/agent/config/toolRegistry.cjs +19 -0
- package/dist/agent/config/toolRegistry.d.ts +4 -0
- package/dist/agent/config/toolRegistry.js +17 -1
- package/dist/agent/middleware/additional-messages.cjs +115 -11
- package/dist/agent/middleware/additional-messages.d.ts +9 -0
- package/dist/agent/middleware/additional-messages.js +115 -11
- package/dist/agent/tests/agentLoader.test.cjs +45 -0
- package/dist/agent/tests/agentLoader.test.js +45 -0
- package/dist/agent/tests/mcpClientManager.test.cjs +50 -0
- package/dist/agent/tests/mcpClientManager.test.js +50 -0
- package/dist/agent/tests/toolRegistry.test.cjs +2 -0
- package/dist/agent/tests/toolRegistry.test.js +2 -0
- package/dist/agent/tools/node_invoke.cjs +146 -0
- package/dist/agent/tools/node_invoke.d.ts +86 -0
- package/dist/agent/tools/node_invoke.js +109 -0
- package/dist/cli/commands/gateway.cjs +1 -1
- package/dist/cli/commands/gateway.js +1 -1
- package/dist/cli/commands/skill.cjs +12 -4
- package/dist/cli/commands/skill.js +12 -4
- package/dist/cli/config/jsonSchema.cjs +55 -0
- package/dist/cli/config/jsonSchema.d.ts +2 -0
- package/dist/cli/config/jsonSchema.js +18 -0
- package/dist/cli/config/loader.cjs +33 -1
- package/dist/cli/config/loader.js +33 -1
- package/dist/cli/config/schema.cjs +119 -2
- package/dist/cli/config/schema.d.ts +40 -0
- package/dist/cli/config/schema.js +119 -2
- package/dist/cli/core/agentInvoker.cjs +25 -4
- package/dist/cli/core/agentInvoker.d.ts +13 -0
- package/dist/cli/core/agentInvoker.js +25 -4
- package/dist/cli/services/skillRepository.cjs +138 -20
- package/dist/cli/services/skillRepository.d.ts +10 -2
- package/dist/cli/services/skillRepository.js +138 -20
- package/dist/cli/services/skillSecurityScanner.cjs +158 -0
- package/dist/cli/services/skillSecurityScanner.d.ts +28 -0
- package/dist/cli/services/skillSecurityScanner.js +121 -0
- package/dist/cli/services/skillService.cjs +44 -12
- package/dist/cli/services/skillService.d.ts +2 -0
- package/dist/cli/services/skillService.js +46 -14
- package/dist/cli/types/skill.d.ts +9 -0
- package/dist/gateway/http/nodes.cjs +247 -0
- package/dist/gateway/http/nodes.d.ts +20 -0
- package/dist/gateway/http/nodes.js +210 -0
- package/dist/gateway/node.cjs +10 -1
- package/dist/gateway/node.d.ts +10 -1
- package/dist/gateway/node.js +10 -1
- package/dist/gateway/server.cjs +418 -27
- package/dist/gateway/server.d.ts +34 -0
- package/dist/gateway/server.js +412 -27
- package/dist/gateway/types.d.ts +15 -1
- package/dist/gateway/validation.cjs +2 -0
- package/dist/gateway/validation.d.ts +4 -0
- package/dist/gateway/validation.js +2 -0
- package/dist/tests/additionalMessageMiddleware.test.cjs +92 -0
- package/dist/tests/additionalMessageMiddleware.test.js +92 -0
- package/dist/tests/cli-config-loader.test.cjs +33 -1
- package/dist/tests/cli-config-loader.test.js +33 -1
- package/dist/tests/config-json-schema.test.cjs +25 -0
- package/dist/tests/config-json-schema.test.d.ts +1 -0
- package/dist/tests/config-json-schema.test.js +19 -0
- package/dist/tests/gateway-http-security.test.cjs +277 -0
- package/dist/tests/gateway-http-security.test.d.ts +1 -0
- package/dist/tests/gateway-http-security.test.js +271 -0
- package/dist/tests/gateway-node-mode.test.cjs +174 -0
- package/dist/tests/gateway-node-mode.test.d.ts +1 -0
- package/dist/tests/gateway-node-mode.test.js +168 -0
- package/dist/tests/gateway-origin-policy.test.cjs +60 -0
- package/dist/tests/gateway-origin-policy.test.d.ts +1 -0
- package/dist/tests/gateway-origin-policy.test.js +54 -0
- package/dist/tests/gateway.test.cjs +1 -0
- package/dist/tests/gateway.test.js +1 -0
- package/dist/tests/node-tools.test.cjs +77 -0
- package/dist/tests/node-tools.test.d.ts +1 -0
- package/dist/tests/node-tools.test.js +71 -0
- package/dist/tests/nodes-api.test.cjs +86 -0
- package/dist/tests/nodes-api.test.d.ts +1 -0
- package/dist/tests/nodes-api.test.js +80 -0
- package/dist/tests/skill-repository.test.cjs +106 -0
- package/dist/tests/skill-repository.test.d.ts +1 -0
- package/dist/tests/skill-repository.test.js +100 -0
- package/dist/tests/skill-security-scanner.test.cjs +126 -0
- package/dist/tests/skill-security-scanner.test.d.ts +1 -0
- package/dist/tests/skill-security-scanner.test.js +120 -0
- package/dist/tests/uv.test.cjs +47 -0
- package/dist/tests/uv.test.d.ts +1 -0
- package/dist/tests/uv.test.js +41 -0
- package/dist/utils/uv.cjs +64 -0
- package/dist/utils/uv.d.ts +3 -0
- package/dist/utils/uv.js +24 -0
- package/dist/webui/assets/{index-DHbfLOUR.js → index-BMekSELC.js} +106 -106
- package/dist/webui/index.html +1 -1
- package/package.json +2 -1
- package/skills/gog/SKILL.md +36 -0
- package/skills/weather/SKILL.md +49 -0
package/dist/gateway/server.cjs
CHANGED
|
@@ -27,9 +27,11 @@ var __webpack_require__ = {};
|
|
|
27
27
|
var __webpack_exports__ = {};
|
|
28
28
|
__webpack_require__.r(__webpack_exports__);
|
|
29
29
|
__webpack_require__.d(__webpack_exports__, {
|
|
30
|
+
isLoopbackHostname: ()=>isLoopbackHostname,
|
|
31
|
+
resolveExecutionConfigDirOverride: ()=>resolveExecutionConfigDirOverride,
|
|
30
32
|
resolveExecutionWorkspaceOverride: ()=>resolveExecutionWorkspaceOverride,
|
|
31
33
|
GatewayServer: ()=>GatewayServer,
|
|
32
|
-
|
|
34
|
+
isGatewayOriginAllowed: ()=>isGatewayOriginAllowed
|
|
33
35
|
});
|
|
34
36
|
const external_node_fs_namespaceObject = require("node:fs");
|
|
35
37
|
const external_node_os_namespaceObject = require("node:os");
|
|
@@ -41,15 +43,17 @@ const agentInvoker_cjs_namespaceObject = require("../cli/core/agentInvoker.cjs")
|
|
|
41
43
|
const outputManager_cjs_namespaceObject = require("../cli/core/outputManager.cjs");
|
|
42
44
|
const sessionManager_cjs_namespaceObject = require("../cli/core/sessionManager.cjs");
|
|
43
45
|
const external_logger_cjs_namespaceObject = require("../logger.cjs");
|
|
46
|
+
const uv_cjs_namespaceObject = require("../utils/uv.cjs");
|
|
44
47
|
const discord_cjs_namespaceObject = require("./adapters/discord.cjs");
|
|
45
48
|
const external_auth_cjs_namespaceObject = require("./auth.cjs");
|
|
46
|
-
const external_browserRelayServer_cjs_namespaceObject = require("./browserRelayServer.cjs");
|
|
47
49
|
const external_broadcast_cjs_namespaceObject = require("./broadcast.cjs");
|
|
50
|
+
const external_browserRelayServer_cjs_namespaceObject = require("./browserRelayServer.cjs");
|
|
48
51
|
const index_cjs_namespaceObject = require("./discovery/index.cjs");
|
|
49
52
|
const external_env_cjs_namespaceObject = require("./env.cjs");
|
|
50
53
|
const registry_cjs_namespaceObject = require("./hooks/registry.cjs");
|
|
51
54
|
const agents_cjs_namespaceObject = require("./http/agents.cjs");
|
|
52
55
|
const fs_cjs_namespaceObject = require("./http/fs.cjs");
|
|
56
|
+
const nodes_cjs_namespaceObject = require("./http/nodes.cjs");
|
|
53
57
|
const providers_cjs_namespaceObject = require("./http/providers.cjs");
|
|
54
58
|
const routines_cjs_namespaceObject = require("./http/routines.cjs");
|
|
55
59
|
const sessions_cjs_namespaceObject = require("./http/sessions.cjs");
|
|
@@ -69,19 +73,72 @@ function _define_property(obj, key, value) {
|
|
|
69
73
|
return obj;
|
|
70
74
|
}
|
|
71
75
|
const API_CORS_HEADERS = {
|
|
72
|
-
"Access-Control-Allow-Origin": "*",
|
|
73
76
|
"Access-Control-Allow-Methods": "GET,POST,PUT,DELETE,OPTIONS",
|
|
74
77
|
"Access-Control-Allow-Headers": "Content-Type, Authorization, X-Wingman-Token, X-Wingman-Password",
|
|
75
78
|
"Access-Control-Max-Age": "600"
|
|
76
79
|
};
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
80
|
+
const LOOPBACK_HOSTNAMES = new Set([
|
|
81
|
+
"127.0.0.1",
|
|
82
|
+
"localhost",
|
|
83
|
+
"::1"
|
|
84
|
+
]);
|
|
85
|
+
const CLIENT_ID_PATTERN = /^[a-zA-Z0-9._:-]{1,128}$/;
|
|
86
|
+
const CLIENT_TYPE_PATTERN = /^[a-zA-Z0-9._:-]{1,64}$/;
|
|
87
|
+
const NODE_EXECUTION_CAPABILITIES = new Set([
|
|
88
|
+
"system.notify",
|
|
89
|
+
"system.run"
|
|
90
|
+
]);
|
|
91
|
+
const DEFAULT_NODE_REQUEST_TIMEOUT_MS = 30000;
|
|
92
|
+
const MIN_NODE_REQUEST_TIMEOUT_MS = 1000;
|
|
93
|
+
const MAX_NODE_REQUEST_TIMEOUT_MS = 120000;
|
|
94
|
+
const MAX_PENDING_NODE_REQUESTS = 2000;
|
|
95
|
+
function normalizeHostname(hostname) {
|
|
96
|
+
const trimmed = hostname.trim().toLowerCase();
|
|
97
|
+
if (!trimmed) return "";
|
|
98
|
+
if (trimmed.startsWith("[") && trimmed.endsWith("]")) return trimmed.slice(1, -1);
|
|
99
|
+
return trimmed;
|
|
100
|
+
}
|
|
101
|
+
function defaultPortForProtocol(protocol) {
|
|
102
|
+
return "https:" === protocol ? "443" : "80";
|
|
103
|
+
}
|
|
104
|
+
function normalizeClientIdentifier(raw, pattern) {
|
|
105
|
+
const trimmed = raw.trim();
|
|
106
|
+
if (!trimmed) return null;
|
|
107
|
+
if (trimmed.length > 128) return null;
|
|
108
|
+
if (!pattern.test(trimmed)) return null;
|
|
109
|
+
return trimmed;
|
|
110
|
+
}
|
|
111
|
+
function includesNodeExecutionCapability(capabilities) {
|
|
112
|
+
if (!Array.isArray(capabilities)) return false;
|
|
113
|
+
for (const capability of capabilities)if ("string" == typeof capability && NODE_EXECUTION_CAPABILITIES.has(capability.trim())) return true;
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
function isLoopbackHostname(hostname) {
|
|
117
|
+
return LOOPBACK_HOSTNAMES.has(normalizeHostname(hostname));
|
|
118
|
+
}
|
|
119
|
+
function isGatewayOriginAllowed(params) {
|
|
120
|
+
let originUrl;
|
|
121
|
+
let requestUrl;
|
|
122
|
+
try {
|
|
123
|
+
originUrl = new URL(params.origin);
|
|
124
|
+
requestUrl = "string" == typeof params.requestUrl ? new URL(params.requestUrl) : params.requestUrl;
|
|
125
|
+
} catch {
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
if ("http:" !== originUrl.protocol && "https:" !== originUrl.protocol) return false;
|
|
129
|
+
const originHost = normalizeHostname(originUrl.hostname);
|
|
130
|
+
const requestHost = normalizeHostname(requestUrl.hostname);
|
|
131
|
+
const configHost = normalizeHostname(params.gatewayHost);
|
|
132
|
+
if (isLoopbackHostname(originHost) && isLoopbackHostname(requestHost)) return true;
|
|
133
|
+
if (!originHost || originHost !== requestHost) return false;
|
|
134
|
+
const originPort = originUrl.port || defaultPortForProtocol(originUrl.protocol);
|
|
135
|
+
const allowedPorts = new Set([
|
|
136
|
+
String(params.gatewayPort)
|
|
137
|
+
]);
|
|
138
|
+
if (params.controlUiEnabled) allowedPorts.add(String(params.controlUiPort));
|
|
139
|
+
if ("0.0.0.0" === configHost || "::" === configHost) return allowedPorts.has(originPort);
|
|
140
|
+
if (originHost !== configHost) return false;
|
|
141
|
+
return allowedPorts.has(originPort);
|
|
85
142
|
}
|
|
86
143
|
function resolveExecutionWorkspaceOverride(payload) {
|
|
87
144
|
const rawWorkspace = payload?.execution?.workspace;
|
|
@@ -99,9 +156,12 @@ function resolveExecutionConfigDirOverride(payload) {
|
|
|
99
156
|
class GatewayServer {
|
|
100
157
|
async start() {
|
|
101
158
|
if (void 0 === globalThis.Bun) throw new Error("Gateway server requires Bun runtime. Start with `bun ./bin/wingman gateway start`.");
|
|
159
|
+
const proxyConfig = this.wingmanConfig.gateway?.mcpProxy;
|
|
160
|
+
if (proxyConfig?.enabled) (0, uv_cjs_namespaceObject.ensureUvAvailableForFeature)(proxyConfig.command || "uvx", "gateway.mcpProxy.enabled");
|
|
102
161
|
this.startedAt = Date.now();
|
|
103
162
|
this.internalHooks = new registry_cjs_namespaceObject.InternalHookRegistry(this.getHttpContext(), this.wingmanConfig.hooks);
|
|
104
163
|
await this.internalHooks.load();
|
|
164
|
+
if (this.config.auth?.allowTailscale && !isLoopbackHostname(this.config.host)) this.log("warn", "Tailscale header-based auth bypass is disabled on non-loopback gateway hosts; use token/password auth instead.");
|
|
105
165
|
this.server = Bun.serve({
|
|
106
166
|
port: this.config.port,
|
|
107
167
|
hostname: this.config.host,
|
|
@@ -109,10 +169,21 @@ class GatewayServer {
|
|
|
109
169
|
const url = new URL(req.url);
|
|
110
170
|
if ("/health" === url.pathname) return this.handleHealthCheck();
|
|
111
171
|
if ("/stats" === url.pathname) return this.handleStats();
|
|
112
|
-
if ("/bridge/send" === url.pathname)
|
|
113
|
-
|
|
172
|
+
if ("/bridge/send" === url.pathname) {
|
|
173
|
+
const authFailure = this.requireHttpAuth(req);
|
|
174
|
+
if (authFailure) return authFailure;
|
|
175
|
+
return this.handleBridgeSend(req);
|
|
176
|
+
}
|
|
177
|
+
if ("/bridge/poll" === url.pathname) {
|
|
178
|
+
const authFailure = this.requireHttpAuth(req);
|
|
179
|
+
if (authFailure) return authFailure;
|
|
180
|
+
return this.handleBridgePoll(req);
|
|
181
|
+
}
|
|
114
182
|
if ("/ws" === url.pathname) {
|
|
115
|
-
|
|
183
|
+
if (!this.isRequestOriginAllowed(req, url)) return new Response("Forbidden origin", {
|
|
184
|
+
status: 403
|
|
185
|
+
});
|
|
186
|
+
const tailscaleUser = this.resolveTrustedTailscaleUser(req);
|
|
116
187
|
const upgraded = server.upgrade(req, {
|
|
117
188
|
data: {
|
|
118
189
|
nodeId: "",
|
|
@@ -302,6 +373,18 @@ class GatewayServer {
|
|
|
302
373
|
case "direct":
|
|
303
374
|
this.handleDirect(ws, msg);
|
|
304
375
|
break;
|
|
376
|
+
case "req:node":
|
|
377
|
+
this.handleNodeRequest(ws, msg);
|
|
378
|
+
break;
|
|
379
|
+
case "event:node":
|
|
380
|
+
this.handleNodeResponse(ws, msg);
|
|
381
|
+
break;
|
|
382
|
+
case "res":
|
|
383
|
+
this.handleNodeResponse(ws, msg);
|
|
384
|
+
break;
|
|
385
|
+
case "error":
|
|
386
|
+
this.handleNodeResponse(ws, msg);
|
|
387
|
+
break;
|
|
305
388
|
case "ping":
|
|
306
389
|
this.handlePing(ws, msg);
|
|
307
390
|
break;
|
|
@@ -323,6 +406,7 @@ class GatewayServer {
|
|
|
323
406
|
this.nodeManager.unregisterNode(nodeId);
|
|
324
407
|
this.log("info", `Node disconnected: ${nodeId}`);
|
|
325
408
|
}
|
|
409
|
+
this.cleanupPendingNodeRequestsForSocket(ws);
|
|
326
410
|
this.connectedClients.delete(ws);
|
|
327
411
|
this.clearSessionSubscriptions(ws);
|
|
328
412
|
this.cancelSocketAgentRequests(ws);
|
|
@@ -351,8 +435,21 @@ class GatewayServer {
|
|
|
351
435
|
ws.close();
|
|
352
436
|
return;
|
|
353
437
|
}
|
|
354
|
-
|
|
355
|
-
|
|
438
|
+
const clientId = normalizeClientIdentifier(msg.client.instanceId, CLIENT_ID_PATTERN);
|
|
439
|
+
const clientType = normalizeClientIdentifier(msg.client.clientType, CLIENT_TYPE_PATTERN);
|
|
440
|
+
if (!clientId || !clientType) {
|
|
441
|
+
this.sendMessage(ws, {
|
|
442
|
+
type: "res",
|
|
443
|
+
id: msg.id,
|
|
444
|
+
ok: false,
|
|
445
|
+
payload: "invalid client info",
|
|
446
|
+
timestamp: Date.now()
|
|
447
|
+
});
|
|
448
|
+
ws.close();
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
451
|
+
ws.data.clientId = clientId;
|
|
452
|
+
ws.data.clientType = clientType;
|
|
356
453
|
ws.data.authenticated = true;
|
|
357
454
|
this.connectedClients.add(ws);
|
|
358
455
|
this.sendMessage(ws, {
|
|
@@ -546,7 +643,12 @@ class GatewayServer {
|
|
|
546
643
|
sessionManager,
|
|
547
644
|
terminalSessionManager: this.terminalSessionManager,
|
|
548
645
|
workdir,
|
|
549
|
-
defaultOutputDir
|
|
646
|
+
defaultOutputDir,
|
|
647
|
+
mcpProxyConfig: this.wingmanConfig.gateway?.mcpProxy,
|
|
648
|
+
nodeInvoker: (request)=>this.invokeNodeTool(ws, request),
|
|
649
|
+
nodeDefaultTargetClientId: "desktop" === ws.data.clientType ? ws.data.clientId : void 0,
|
|
650
|
+
nodeConnectedIdsProvider: ()=>this.listConnectedNodeIdsForRequester(ws),
|
|
651
|
+
nodeConnectedTargetsProvider: ()=>this.listConnectedNodeTargetsForRequester(ws)
|
|
550
652
|
});
|
|
551
653
|
const abortController = new AbortController();
|
|
552
654
|
this.activeAgentRequests.set(msg.id, {
|
|
@@ -702,14 +804,19 @@ class GatewayServer {
|
|
|
702
804
|
}
|
|
703
805
|
handleRegister(ws, msg) {
|
|
704
806
|
const payload = msg.payload;
|
|
705
|
-
|
|
807
|
+
const hasSessionAuth = true === ws.data.authenticated;
|
|
808
|
+
const hasPayloadAuth = this.auth.validate({
|
|
706
809
|
token: payload.token
|
|
707
|
-
}, ws.data.tailscaleUser)
|
|
810
|
+
}, ws.data.tailscaleUser);
|
|
811
|
+
if (!hasSessionAuth && !hasPayloadAuth) {
|
|
708
812
|
this.sendError(ws, "AUTH_FAILED", "Authentication failed");
|
|
709
813
|
ws.close();
|
|
710
814
|
return;
|
|
711
815
|
}
|
|
712
|
-
const
|
|
816
|
+
const clientId = ws.data.clientId?.trim();
|
|
817
|
+
const requiresNodeApproval = this.nodePairingRequired && includesNodeExecutionCapability(payload.capabilities);
|
|
818
|
+
if (requiresNodeApproval && (!clientId || !this.nodeApprovalStore.isEnabled(clientId))) return void this.sendError(ws, "NODE_NOT_ENABLED", "This client is not approved for node execution");
|
|
819
|
+
const node = this.nodeManager.registerNode(ws, payload.name, payload.capabilities, payload.sessionId, payload.agentName, clientId);
|
|
713
820
|
if (!node) {
|
|
714
821
|
this.sendError(ws, "MAX_NODES_REACHED", "Maximum nodes reached");
|
|
715
822
|
ws.close();
|
|
@@ -726,6 +833,7 @@ class GatewayServer {
|
|
|
726
833
|
},
|
|
727
834
|
timestamp: Date.now()
|
|
728
835
|
});
|
|
836
|
+
if (clientId) this.nodeApprovalStore.markSeen(clientId, payload.name);
|
|
729
837
|
const sessionInfo = node.sessionId ? ` (session: ${node.sessionId})` : "";
|
|
730
838
|
this.log("info", `Node registered: ${node.id} (${node.name})${sessionInfo}`);
|
|
731
839
|
}
|
|
@@ -766,6 +874,7 @@ class GatewayServer {
|
|
|
766
874
|
this.nodeManager.unregisterNode(nodeId);
|
|
767
875
|
this.log("info", `Node unregistered: ${nodeId}`);
|
|
768
876
|
}
|
|
877
|
+
this.cleanupPendingNodeRequestsForSocket(ws);
|
|
769
878
|
}
|
|
770
879
|
handleJoinGroup(ws, msg) {
|
|
771
880
|
const nodeId = ws.data.nodeId;
|
|
@@ -832,6 +941,77 @@ class GatewayServer {
|
|
|
832
941
|
const sent = this.nodeManager.sendToNode(payload.targetNodeId, directMsg);
|
|
833
942
|
if (!sent) this.sendError(ws, "NODE_NOT_FOUND", "Target node not found");
|
|
834
943
|
}
|
|
944
|
+
handleNodeRequest(ws, msg) {
|
|
945
|
+
if (!ws.data.authenticated) return void this.sendError(ws, "AUTH_REQUIRED", "Client is not authenticated");
|
|
946
|
+
if (!msg.id) return void this.sendError(ws, "INVALID_REQUEST", "Node request id is required");
|
|
947
|
+
const targetNodeId = "string" == typeof msg.targetNodeId ? msg.targetNodeId.trim() : "";
|
|
948
|
+
if (!targetNodeId) return void this.sendError(ws, "INVALID_REQUEST", "targetNodeId is required");
|
|
949
|
+
const target = this.nodeManager.getNode(targetNodeId);
|
|
950
|
+
if (!target) return void this.sendError(ws, "NODE_NOT_FOUND", "Target node not found");
|
|
951
|
+
if (!this.isNodeApprovedForExecution(target.clientId)) return void this.sendError(ws, "NODE_REVOKED", "Target node has been revoked");
|
|
952
|
+
if (this.pendingNodeRequests.has(msg.id)) return void this.sendError(ws, "DUPLICATE_REQUEST_ID", "Node request id is already in use");
|
|
953
|
+
if (this.pendingNodeRequests.size >= MAX_PENDING_NODE_REQUESTS) return void this.sendError(ws, "NODE_REQUEST_OVERLOADED", "Too many pending node requests");
|
|
954
|
+
const timeoutMs = this.resolveNodeRequestTimeout(msg.payload);
|
|
955
|
+
const pendingRequest = {
|
|
956
|
+
id: msg.id,
|
|
957
|
+
requester: ws,
|
|
958
|
+
targetNodeId,
|
|
959
|
+
createdAt: Date.now()
|
|
960
|
+
};
|
|
961
|
+
pendingRequest.timeoutHandle = setTimeout(()=>{
|
|
962
|
+
if (!this.pendingNodeRequests.has(msg.id)) return;
|
|
963
|
+
this.clearPendingNodeRequest(msg.id, new Error(`Node request timed out after ${timeoutMs}ms`));
|
|
964
|
+
this.sendMessageWithRetry(ws, {
|
|
965
|
+
type: "error",
|
|
966
|
+
id: msg.id,
|
|
967
|
+
payload: {
|
|
968
|
+
code: "NODE_TIMEOUT",
|
|
969
|
+
message: `Node request timed out after ${timeoutMs}ms`
|
|
970
|
+
},
|
|
971
|
+
timestamp: Date.now()
|
|
972
|
+
});
|
|
973
|
+
}, timeoutMs);
|
|
974
|
+
this.pendingNodeRequests.set(msg.id, pendingRequest);
|
|
975
|
+
const forwarded = {
|
|
976
|
+
type: "req:node",
|
|
977
|
+
id: msg.id,
|
|
978
|
+
clientId: ws.data.clientId || ws.data.nodeId || "gateway",
|
|
979
|
+
targetNodeId,
|
|
980
|
+
payload: msg.payload,
|
|
981
|
+
timestamp: Date.now()
|
|
982
|
+
};
|
|
983
|
+
const sent = this.nodeManager.sendToNode(targetNodeId, forwarded);
|
|
984
|
+
if (!sent) {
|
|
985
|
+
this.clearPendingNodeRequest(msg.id);
|
|
986
|
+
this.sendError(ws, "NODE_NOT_FOUND", "Target node not found");
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
handleNodeResponse(ws, msg) {
|
|
990
|
+
if (!msg.id) return;
|
|
991
|
+
const pending = this.pendingNodeRequests.get(msg.id);
|
|
992
|
+
if (!pending) return;
|
|
993
|
+
const sourceNodeId = ws.data.nodeId;
|
|
994
|
+
if (!sourceNodeId || sourceNodeId !== pending.targetNodeId) return void this.sendError(ws, "NODE_RESPONSE_REJECTED", "Only the target node may respond to this request");
|
|
995
|
+
const forwarded = {
|
|
996
|
+
...msg,
|
|
997
|
+
nodeId: sourceNodeId,
|
|
998
|
+
timestamp: Date.now()
|
|
999
|
+
};
|
|
1000
|
+
if (pending.requester) this.sendMessageWithRetry(pending.requester, forwarded);
|
|
1001
|
+
if ("res" === msg.type) {
|
|
1002
|
+
if (false === msg.ok) pending.reject?.(new Error(this.extractNodeErrorMessage(msg)));
|
|
1003
|
+
else pending.resolve?.({
|
|
1004
|
+
nodeId: sourceNodeId,
|
|
1005
|
+
payload: msg.payload
|
|
1006
|
+
});
|
|
1007
|
+
this.clearPendingNodeRequest(msg.id);
|
|
1008
|
+
return;
|
|
1009
|
+
}
|
|
1010
|
+
if ("error" === msg.type) {
|
|
1011
|
+
pending.reject?.(new Error(this.extractNodeErrorMessage(msg)));
|
|
1012
|
+
this.clearPendingNodeRequest(msg.id);
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
835
1015
|
handlePing(ws, msg) {
|
|
836
1016
|
const nodeId = ws.data.nodeId;
|
|
837
1017
|
if (nodeId) this.nodeManager.updatePing(nodeId);
|
|
@@ -844,6 +1024,127 @@ class GatewayServer {
|
|
|
844
1024
|
const nodeId = ws.data.nodeId;
|
|
845
1025
|
if (nodeId) this.nodeManager.updatePing(nodeId);
|
|
846
1026
|
}
|
|
1027
|
+
cleanupPendingNodeRequestsForSocket(ws) {
|
|
1028
|
+
for (const [requestId, pending] of this.pendingNodeRequests)if (pending.requester === ws || pending.targetNodeId === ws.data.nodeId) this.clearPendingNodeRequest(requestId, new Error("Node request cancelled due to disconnect"));
|
|
1029
|
+
}
|
|
1030
|
+
clearPendingNodeRequest(requestId, error) {
|
|
1031
|
+
const pending = this.pendingNodeRequests.get(requestId);
|
|
1032
|
+
if (!pending) return;
|
|
1033
|
+
this.pendingNodeRequests.delete(requestId);
|
|
1034
|
+
if (pending.timeoutHandle) clearTimeout(pending.timeoutHandle);
|
|
1035
|
+
if (error) pending.reject?.(error);
|
|
1036
|
+
}
|
|
1037
|
+
extractNodeErrorMessage(msg) {
|
|
1038
|
+
const payload = msg.payload;
|
|
1039
|
+
if (payload && "object" == typeof payload && !Array.isArray(payload) && "string" == typeof payload.message) return String(payload.message);
|
|
1040
|
+
if ("string" == typeof payload && payload.trim()) return payload;
|
|
1041
|
+
return "Node invocation failed";
|
|
1042
|
+
}
|
|
1043
|
+
isNodeApprovedForExecution(clientId) {
|
|
1044
|
+
if (!this.nodePairingRequired) return true;
|
|
1045
|
+
const trimmed = "string" == typeof clientId ? clientId.trim() : "";
|
|
1046
|
+
if (!trimmed) return false;
|
|
1047
|
+
return this.nodeApprovalStore.isEnabled(trimmed);
|
|
1048
|
+
}
|
|
1049
|
+
listConnectedNodeIdsForRequester(requester) {
|
|
1050
|
+
return this.listConnectedNodeTargetsForRequester(requester).map((node)=>node.nodeId);
|
|
1051
|
+
}
|
|
1052
|
+
listConnectedNodeTargetsForRequester(requester) {
|
|
1053
|
+
const requesterClientId = "desktop" === requester.data.clientType ? requester.data.clientId?.trim() || "" : "";
|
|
1054
|
+
const nodes = this.nodeManager.getAllNodes().filter((node)=>{
|
|
1055
|
+
if (!this.isNodeApprovedForExecution(node.clientId)) return false;
|
|
1056
|
+
if (!includesNodeExecutionCapability(node.capabilities)) return false;
|
|
1057
|
+
return true;
|
|
1058
|
+
}).sort((a, b)=>b.connectedAt - a.connectedAt);
|
|
1059
|
+
const preferred = requesterClientId ? nodes.filter((node)=>node.clientId === requesterClientId) : [];
|
|
1060
|
+
const others = requesterClientId ? nodes.filter((node)=>node.clientId !== requesterClientId) : nodes;
|
|
1061
|
+
return [
|
|
1062
|
+
...preferred,
|
|
1063
|
+
...others
|
|
1064
|
+
].map((node)=>({
|
|
1065
|
+
nodeId: node.id,
|
|
1066
|
+
clientId: node.clientId,
|
|
1067
|
+
name: node.name,
|
|
1068
|
+
capabilities: Array.isArray(node.capabilities) ? node.capabilities.filter((capability)=>"string" == typeof capability) : void 0
|
|
1069
|
+
}));
|
|
1070
|
+
}
|
|
1071
|
+
resolveNodeRequestTimeout(payload) {
|
|
1072
|
+
let requestedTimeout;
|
|
1073
|
+
if (payload && "object" == typeof payload && !Array.isArray(payload)) {
|
|
1074
|
+
const timeoutValue = payload.timeoutMs;
|
|
1075
|
+
if ("number" == typeof timeoutValue && Number.isFinite(timeoutValue)) requestedTimeout = Math.trunc(timeoutValue);
|
|
1076
|
+
}
|
|
1077
|
+
const timeout = requestedTimeout ?? DEFAULT_NODE_REQUEST_TIMEOUT_MS;
|
|
1078
|
+
return Math.min(Math.max(timeout, MIN_NODE_REQUEST_TIMEOUT_MS), MAX_NODE_REQUEST_TIMEOUT_MS);
|
|
1079
|
+
}
|
|
1080
|
+
resolveNodeTarget(request, defaultTargetClientId) {
|
|
1081
|
+
const requiredCapability = "string" == typeof request.capability ? request.capability.trim() : "";
|
|
1082
|
+
const targetNodeId = "string" == typeof request.targetNodeId ? request.targetNodeId.trim() : "";
|
|
1083
|
+
const targetClientId = ("string" == typeof request.targetClientId ? request.targetClientId.trim() : "") || defaultTargetClientId?.trim() || "";
|
|
1084
|
+
const canUseNode = (node)=>{
|
|
1085
|
+
if (!this.isNodeApprovedForExecution(node.clientId)) return false;
|
|
1086
|
+
if (!requiredCapability) return true;
|
|
1087
|
+
return Array.isArray(node.capabilities) && node.capabilities.includes(requiredCapability);
|
|
1088
|
+
};
|
|
1089
|
+
if (targetNodeId) {
|
|
1090
|
+
const node = this.nodeManager.getNode(targetNodeId);
|
|
1091
|
+
if (!node) throw new Error("Target node not found");
|
|
1092
|
+
if (!canUseNode(node)) throw new Error(requiredCapability ? `Target node does not support capability ${requiredCapability}` : "Target node is not available");
|
|
1093
|
+
return {
|
|
1094
|
+
nodeId: node.id
|
|
1095
|
+
};
|
|
1096
|
+
}
|
|
1097
|
+
let candidates = this.nodeManager.getAllNodes().filter((node)=>canUseNode(node));
|
|
1098
|
+
if (targetClientId) {
|
|
1099
|
+
candidates = candidates.filter((node)=>node.clientId === targetClientId);
|
|
1100
|
+
if (0 === candidates.length) throw new Error(`No available node found for client ${targetClientId}`);
|
|
1101
|
+
}
|
|
1102
|
+
if (0 === candidates.length) throw new Error(requiredCapability ? `No available node supports capability ${requiredCapability}` : "No available node found");
|
|
1103
|
+
candidates.sort((a, b)=>b.connectedAt - a.connectedAt);
|
|
1104
|
+
const selected = candidates[0];
|
|
1105
|
+
return {
|
|
1106
|
+
nodeId: selected.id
|
|
1107
|
+
};
|
|
1108
|
+
}
|
|
1109
|
+
async invokeNodeTool(requester, request) {
|
|
1110
|
+
if (!requester.data.authenticated) throw new Error("Client is not authenticated");
|
|
1111
|
+
const toolName = request.tool;
|
|
1112
|
+
if ("system.notify" !== toolName && "system.run" !== toolName) throw new Error(`Unsupported node tool: ${toolName}`);
|
|
1113
|
+
const timeoutMsRaw = "number" == typeof request.timeoutMs ? Math.trunc(request.timeoutMs) : DEFAULT_NODE_REQUEST_TIMEOUT_MS;
|
|
1114
|
+
const timeoutMs = Math.min(Math.max(timeoutMsRaw, MIN_NODE_REQUEST_TIMEOUT_MS), MAX_NODE_REQUEST_TIMEOUT_MS);
|
|
1115
|
+
if (this.pendingNodeRequests.size >= MAX_PENDING_NODE_REQUESTS) throw new Error("Too many pending node requests");
|
|
1116
|
+
const target = this.resolveNodeTarget(request, "desktop" === requester.data.clientType ? requester.data.clientId : void 0);
|
|
1117
|
+
let requestId = "";
|
|
1118
|
+
do requestId = `node-tool-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
1119
|
+
while (this.pendingNodeRequests.has(requestId));
|
|
1120
|
+
return await new Promise((resolve, reject)=>{
|
|
1121
|
+
const pending = {
|
|
1122
|
+
id: requestId,
|
|
1123
|
+
targetNodeId: target.nodeId,
|
|
1124
|
+
createdAt: Date.now(),
|
|
1125
|
+
resolve,
|
|
1126
|
+
reject
|
|
1127
|
+
};
|
|
1128
|
+
pending.timeoutHandle = setTimeout(()=>{
|
|
1129
|
+
this.clearPendingNodeRequest(requestId, new Error(`Node invocation timed out after ${timeoutMs}ms`));
|
|
1130
|
+
}, timeoutMs);
|
|
1131
|
+
this.pendingNodeRequests.set(requestId, pending);
|
|
1132
|
+
const forwarded = {
|
|
1133
|
+
type: "req:node",
|
|
1134
|
+
id: requestId,
|
|
1135
|
+
clientId: requester.data.clientId || requester.data.nodeId || "gateway",
|
|
1136
|
+
targetNodeId: target.nodeId,
|
|
1137
|
+
payload: {
|
|
1138
|
+
tool: toolName,
|
|
1139
|
+
args: request.args || {},
|
|
1140
|
+
timeoutMs
|
|
1141
|
+
},
|
|
1142
|
+
timestamp: Date.now()
|
|
1143
|
+
};
|
|
1144
|
+
const sent = this.nodeManager.sendToNode(target.nodeId, forwarded);
|
|
1145
|
+
if (!sent) this.clearPendingNodeRequest(requestId, new Error("Target node not found"));
|
|
1146
|
+
});
|
|
1147
|
+
}
|
|
847
1148
|
sendMessage(ws, message) {
|
|
848
1149
|
try {
|
|
849
1150
|
const result = ws.send(JSON.stringify(message));
|
|
@@ -1208,15 +1509,87 @@ class GatewayServer {
|
|
|
1208
1509
|
break;
|
|
1209
1510
|
}
|
|
1210
1511
|
}
|
|
1512
|
+
isRequestOriginAllowed(req, url) {
|
|
1513
|
+
const origin = req.headers.get("origin");
|
|
1514
|
+
if (!origin) return true;
|
|
1515
|
+
return isGatewayOriginAllowed({
|
|
1516
|
+
origin,
|
|
1517
|
+
requestUrl: url,
|
|
1518
|
+
gatewayHost: this.config.host,
|
|
1519
|
+
gatewayPort: this.config.port,
|
|
1520
|
+
controlUiEnabled: this.controlUiEnabled,
|
|
1521
|
+
controlUiPort: this.controlUiPort
|
|
1522
|
+
});
|
|
1523
|
+
}
|
|
1524
|
+
withApiCors(req, url, response) {
|
|
1525
|
+
const headers = new Headers(response.headers);
|
|
1526
|
+
for (const [key, value] of Object.entries(API_CORS_HEADERS))headers.set(key, value);
|
|
1527
|
+
const existingVary = headers.get("Vary");
|
|
1528
|
+
if (existingVary) {
|
|
1529
|
+
if (!existingVary.toLowerCase().includes("origin")) headers.set("Vary", `${existingVary}, Origin`);
|
|
1530
|
+
} else headers.set("Vary", "Origin");
|
|
1531
|
+
const origin = req.headers.get("origin");
|
|
1532
|
+
if (origin && this.isRequestOriginAllowed(req, url)) headers.set("Access-Control-Allow-Origin", origin);
|
|
1533
|
+
return new Response(response.body, {
|
|
1534
|
+
status: response.status,
|
|
1535
|
+
statusText: response.statusText,
|
|
1536
|
+
headers
|
|
1537
|
+
});
|
|
1538
|
+
}
|
|
1539
|
+
resolveTrustedTailscaleUser(req) {
|
|
1540
|
+
if (!this.config.auth?.allowTailscale) return;
|
|
1541
|
+
if (!isLoopbackHostname(this.config.host)) return;
|
|
1542
|
+
const raw = req.headers.get("tailscale-user-login") || req.headers.get("ts-user-login") || "";
|
|
1543
|
+
const normalized = raw.trim();
|
|
1544
|
+
if (!normalized) return;
|
|
1545
|
+
if (normalized.length > 256) return;
|
|
1546
|
+
if (!/^[a-zA-Z0-9._:@+-]+$/.test(normalized)) return;
|
|
1547
|
+
return normalized;
|
|
1548
|
+
}
|
|
1549
|
+
resolveHttpAuthPayload(req) {
|
|
1550
|
+
const authorization = req.headers.get("authorization") || "";
|
|
1551
|
+
let bearerToken;
|
|
1552
|
+
if (authorization.toLowerCase().startsWith("bearer ")) {
|
|
1553
|
+
const value = authorization.slice(7).trim();
|
|
1554
|
+
bearerToken = value || void 0;
|
|
1555
|
+
}
|
|
1556
|
+
const headerToken = req.headers.get("x-wingman-token")?.trim();
|
|
1557
|
+
const password = req.headers.get("x-wingman-password")?.trim();
|
|
1558
|
+
return {
|
|
1559
|
+
token: headerToken || bearerToken,
|
|
1560
|
+
password: password || void 0
|
|
1561
|
+
};
|
|
1562
|
+
}
|
|
1563
|
+
requireHttpAuth(req) {
|
|
1564
|
+
if (!this.auth.isAuthRequired()) return null;
|
|
1565
|
+
const tailscaleUser = this.resolveTrustedTailscaleUser(req);
|
|
1566
|
+
const authPayload = this.resolveHttpAuthPayload(req);
|
|
1567
|
+
const allowed = this.auth.validate(authPayload, tailscaleUser);
|
|
1568
|
+
if (allowed) return null;
|
|
1569
|
+
return new Response("Unauthorized", {
|
|
1570
|
+
status: 401,
|
|
1571
|
+
headers: {
|
|
1572
|
+
"WWW-Authenticate": 'Bearer realm="wingman-gateway"'
|
|
1573
|
+
}
|
|
1574
|
+
});
|
|
1575
|
+
}
|
|
1211
1576
|
async handleUiRequest(req) {
|
|
1212
1577
|
const url = new URL(req.url);
|
|
1213
1578
|
const ctx = this.getHttpContext();
|
|
1214
1579
|
const webhookResponse = await (0, webhooks_cjs_namespaceObject.handleWebhookInvoke)(ctx, this.webhookStore, req, url);
|
|
1215
1580
|
if (webhookResponse) return webhookResponse;
|
|
1216
1581
|
if (url.pathname.startsWith("/api/")) {
|
|
1217
|
-
if (
|
|
1582
|
+
if (!this.isRequestOriginAllowed(req, url)) return this.withApiCors(req, url, new Response("Forbidden origin", {
|
|
1583
|
+
status: 403
|
|
1584
|
+
}));
|
|
1585
|
+
if ("OPTIONS" === req.method) return this.withApiCors(req, url, new Response(null, {
|
|
1218
1586
|
status: 204
|
|
1219
1587
|
}));
|
|
1588
|
+
const publicApiRoute = "/api/config" === url.pathname || "/api/health" === url.pathname;
|
|
1589
|
+
if (!publicApiRoute) {
|
|
1590
|
+
const authFailure = this.requireHttpAuth(req);
|
|
1591
|
+
if (authFailure) return this.withApiCors(req, url, authFailure);
|
|
1592
|
+
}
|
|
1220
1593
|
if ("/api/config" === url.pathname) {
|
|
1221
1594
|
const agents = this.wingmanConfig.agents?.list?.map((agent)=>({
|
|
1222
1595
|
id: agent.id,
|
|
@@ -1224,7 +1597,7 @@ class GatewayServer {
|
|
|
1224
1597
|
default: agent.default
|
|
1225
1598
|
})) || [];
|
|
1226
1599
|
const defaultAgentId = this.router.selectAgent();
|
|
1227
|
-
return withApiCors(new Response(JSON.stringify({
|
|
1600
|
+
return this.withApiCors(req, url, new Response(JSON.stringify({
|
|
1228
1601
|
gatewayHost: this.config.host,
|
|
1229
1602
|
gatewayPort: this.config.port,
|
|
1230
1603
|
requireAuth: this.auth.isAuthRequired(),
|
|
@@ -1239,11 +1612,11 @@ class GatewayServer {
|
|
|
1239
1612
|
}
|
|
1240
1613
|
}));
|
|
1241
1614
|
}
|
|
1242
|
-
const apiResponse = await (0, webhooks_cjs_namespaceObject.handleWebhooksApi)(ctx, this.webhookStore, req, url) || await (0, routines_cjs_namespaceObject.handleRoutinesApi)(ctx, this.routineStore, req, url) || await (0, agents_cjs_namespaceObject.handleAgentsApi)(ctx, req, url) || await (0, providers_cjs_namespaceObject.handleProvidersApi)(ctx, req, url) || await (0, voice_cjs_namespaceObject.handleVoiceApi)(ctx, req, url) || await (0, fs_cjs_namespaceObject.handleFsApi)(ctx, req, url) || await (0, sessions_cjs_namespaceObject.handleSessionsApi)(ctx, req, url);
|
|
1243
|
-
if (apiResponse) return withApiCors(apiResponse);
|
|
1244
|
-
if ("/api/health" === url.pathname) return withApiCors(this.handleHealthCheck());
|
|
1245
|
-
if ("/api/stats" === url.pathname) return withApiCors(this.handleStats());
|
|
1246
|
-
return withApiCors(new Response("Not Found", {
|
|
1615
|
+
const apiResponse = await (0, webhooks_cjs_namespaceObject.handleWebhooksApi)(ctx, this.webhookStore, req, url) || await (0, routines_cjs_namespaceObject.handleRoutinesApi)(ctx, this.routineStore, req, url) || await (0, nodes_cjs_namespaceObject.handleNodesApi)(ctx, this.nodeManager, this.nodeApprovalStore, req, url) || await (0, agents_cjs_namespaceObject.handleAgentsApi)(ctx, req, url) || await (0, providers_cjs_namespaceObject.handleProvidersApi)(ctx, req, url) || await (0, voice_cjs_namespaceObject.handleVoiceApi)(ctx, req, url) || await (0, fs_cjs_namespaceObject.handleFsApi)(ctx, req, url) || await (0, sessions_cjs_namespaceObject.handleSessionsApi)(ctx, req, url);
|
|
1616
|
+
if (apiResponse) return this.withApiCors(req, url, apiResponse);
|
|
1617
|
+
if ("/api/health" === url.pathname) return this.withApiCors(req, url, this.handleHealthCheck());
|
|
1618
|
+
if ("/api/stats" === url.pathname) return this.withApiCors(req, url, this.handleStats());
|
|
1619
|
+
return this.withApiCors(req, url, new Response("Not Found", {
|
|
1247
1620
|
status: 404
|
|
1248
1621
|
}));
|
|
1249
1622
|
}
|
|
@@ -1309,6 +1682,15 @@ class GatewayServer {
|
|
|
1309
1682
|
const validatedMessage = validation.data;
|
|
1310
1683
|
if ("register" === validatedMessage.type) {
|
|
1311
1684
|
const payload = validatedMessage.payload;
|
|
1685
|
+
const maxBridgeNodes = this.config.maxNodes || 1000;
|
|
1686
|
+
if (this.bridgeQueues.size >= maxBridgeNodes) return new Response(JSON.stringify({
|
|
1687
|
+
error: "Bridge capacity reached"
|
|
1688
|
+
}), {
|
|
1689
|
+
status: 429,
|
|
1690
|
+
headers: {
|
|
1691
|
+
"Content-Type": "application/json"
|
|
1692
|
+
}
|
|
1693
|
+
});
|
|
1312
1694
|
const nodeId = this.generateNodeId();
|
|
1313
1695
|
this.bridgeQueues.set(nodeId, []);
|
|
1314
1696
|
const response = {
|
|
@@ -1440,6 +1822,7 @@ class GatewayServer {
|
|
|
1440
1822
|
_define_property(this, "browserRelayServer", null);
|
|
1441
1823
|
_define_property(this, "webhookStore", void 0);
|
|
1442
1824
|
_define_property(this, "routineStore", void 0);
|
|
1825
|
+
_define_property(this, "nodeApprovalStore", void 0);
|
|
1443
1826
|
_define_property(this, "internalHooks", null);
|
|
1444
1827
|
_define_property(this, "discordAdapter", null);
|
|
1445
1828
|
_define_property(this, "sessionSubscriptions", new Map());
|
|
@@ -1449,7 +1832,9 @@ class GatewayServer {
|
|
|
1449
1832
|
_define_property(this, "activeSessionRequests", new Map());
|
|
1450
1833
|
_define_property(this, "queuedSessionRequests", new Map());
|
|
1451
1834
|
_define_property(this, "requestSessionKeys", new Map());
|
|
1835
|
+
_define_property(this, "pendingNodeRequests", new Map());
|
|
1452
1836
|
_define_property(this, "terminalSessionManager", void 0);
|
|
1837
|
+
_define_property(this, "nodePairingRequired", void 0);
|
|
1453
1838
|
_define_property(this, "bridgeQueues", new Map());
|
|
1454
1839
|
_define_property(this, "bridgePollWaiters", new Map());
|
|
1455
1840
|
this.workspace = config.workspace || process.cwd();
|
|
@@ -1459,6 +1844,7 @@ class GatewayServer {
|
|
|
1459
1844
|
this.router = new external_router_cjs_namespaceObject.GatewayRouter(this.wingmanConfig);
|
|
1460
1845
|
this.webhookStore = (0, webhooks_cjs_namespaceObject.createWebhookStore)(()=>this.resolveConfigDirPath());
|
|
1461
1846
|
this.routineStore = (0, routines_cjs_namespaceObject.createRoutineStore)(()=>this.resolveConfigDirPath());
|
|
1847
|
+
this.nodeApprovalStore = (0, nodes_cjs_namespaceObject.createNodeApprovalStore)(()=>this.resolveConfigDirPath());
|
|
1462
1848
|
const gatewayDefaults = this.wingmanConfig.gateway;
|
|
1463
1849
|
const envToken = (0, external_env_cjs_namespaceObject.getGatewayTokenFromEnv)();
|
|
1464
1850
|
const authFromConfig = config.auth?.mode === "token" ? {
|
|
@@ -1498,6 +1884,7 @@ class GatewayServer {
|
|
|
1498
1884
|
const controlUi = this.wingmanConfig.gateway?.controlUi;
|
|
1499
1885
|
this.controlUiEnabled = controlUi?.enabled ?? false;
|
|
1500
1886
|
this.controlUiPort = controlUi?.port || 18790;
|
|
1887
|
+
this.nodePairingRequired = controlUi?.pairingRequired ?? true;
|
|
1501
1888
|
this.controlUiSamePort = this.controlUiEnabled && this.controlUiPort === this.config.port;
|
|
1502
1889
|
this.uiDistDir = this.controlUiEnabled ? this.resolveControlUiDir() : null;
|
|
1503
1890
|
const relayConfig = this.wingmanConfig.browser?.relay;
|
|
@@ -1555,10 +1942,14 @@ function isFileAttachment(attachment) {
|
|
|
1555
1942
|
return "string" == typeof attachment.textContent;
|
|
1556
1943
|
}
|
|
1557
1944
|
exports.GatewayServer = __webpack_exports__.GatewayServer;
|
|
1945
|
+
exports.isGatewayOriginAllowed = __webpack_exports__.isGatewayOriginAllowed;
|
|
1946
|
+
exports.isLoopbackHostname = __webpack_exports__.isLoopbackHostname;
|
|
1558
1947
|
exports.resolveExecutionConfigDirOverride = __webpack_exports__.resolveExecutionConfigDirOverride;
|
|
1559
1948
|
exports.resolveExecutionWorkspaceOverride = __webpack_exports__.resolveExecutionWorkspaceOverride;
|
|
1560
1949
|
for(var __rspack_i in __webpack_exports__)if (-1 === [
|
|
1561
1950
|
"GatewayServer",
|
|
1951
|
+
"isGatewayOriginAllowed",
|
|
1952
|
+
"isLoopbackHostname",
|
|
1562
1953
|
"resolveExecutionConfigDirOverride",
|
|
1563
1954
|
"resolveExecutionWorkspaceOverride"
|
|
1564
1955
|
].indexOf(__rspack_i)) exports[__rspack_i] = __webpack_exports__[__rspack_i];
|