@wingman-ai/gateway 0.4.0 → 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.
Files changed (59) hide show
  1. package/README.md +29 -111
  2. package/dist/agent/config/agentConfig.cjs +2 -0
  3. package/dist/agent/config/agentConfig.d.ts +6 -0
  4. package/dist/agent/config/agentConfig.js +2 -0
  5. package/dist/agent/config/agentLoader.cjs +21 -18
  6. package/dist/agent/config/agentLoader.js +22 -19
  7. package/dist/agent/config/toolRegistry.cjs +19 -0
  8. package/dist/agent/config/toolRegistry.d.ts +4 -0
  9. package/dist/agent/config/toolRegistry.js +17 -1
  10. package/dist/agent/middleware/additional-messages.cjs +115 -11
  11. package/dist/agent/middleware/additional-messages.d.ts +9 -0
  12. package/dist/agent/middleware/additional-messages.js +115 -11
  13. package/dist/agent/tests/agentLoader.test.cjs +45 -0
  14. package/dist/agent/tests/agentLoader.test.js +45 -0
  15. package/dist/agent/tests/toolRegistry.test.cjs +2 -0
  16. package/dist/agent/tests/toolRegistry.test.js +2 -0
  17. package/dist/agent/tools/node_invoke.cjs +146 -0
  18. package/dist/agent/tools/node_invoke.d.ts +86 -0
  19. package/dist/agent/tools/node_invoke.js +109 -0
  20. package/dist/cli/commands/gateway.cjs +1 -1
  21. package/dist/cli/commands/gateway.js +1 -1
  22. package/dist/cli/core/agentInvoker.cjs +21 -3
  23. package/dist/cli/core/agentInvoker.d.ts +10 -0
  24. package/dist/cli/core/agentInvoker.js +21 -3
  25. package/dist/gateway/http/nodes.cjs +247 -0
  26. package/dist/gateway/http/nodes.d.ts +20 -0
  27. package/dist/gateway/http/nodes.js +210 -0
  28. package/dist/gateway/node.cjs +10 -1
  29. package/dist/gateway/node.d.ts +10 -1
  30. package/dist/gateway/node.js +10 -1
  31. package/dist/gateway/server.cjs +414 -27
  32. package/dist/gateway/server.d.ts +34 -0
  33. package/dist/gateway/server.js +408 -27
  34. package/dist/gateway/types.d.ts +6 -1
  35. package/dist/gateway/validation.cjs +2 -0
  36. package/dist/gateway/validation.d.ts +4 -0
  37. package/dist/gateway/validation.js +2 -0
  38. package/dist/tests/additionalMessageMiddleware.test.cjs +92 -0
  39. package/dist/tests/additionalMessageMiddleware.test.js +92 -0
  40. package/dist/tests/gateway-http-security.test.cjs +277 -0
  41. package/dist/tests/gateway-http-security.test.d.ts +1 -0
  42. package/dist/tests/gateway-http-security.test.js +271 -0
  43. package/dist/tests/gateway-node-mode.test.cjs +174 -0
  44. package/dist/tests/gateway-node-mode.test.d.ts +1 -0
  45. package/dist/tests/gateway-node-mode.test.js +168 -0
  46. package/dist/tests/gateway-origin-policy.test.cjs +60 -0
  47. package/dist/tests/gateway-origin-policy.test.d.ts +1 -0
  48. package/dist/tests/gateway-origin-policy.test.js +54 -0
  49. package/dist/tests/gateway.test.cjs +1 -0
  50. package/dist/tests/gateway.test.js +1 -0
  51. package/dist/tests/node-tools.test.cjs +77 -0
  52. package/dist/tests/node-tools.test.d.ts +1 -0
  53. package/dist/tests/node-tools.test.js +71 -0
  54. package/dist/tests/nodes-api.test.cjs +86 -0
  55. package/dist/tests/nodes-api.test.d.ts +1 -0
  56. package/dist/tests/nodes-api.test.js +80 -0
  57. package/dist/webui/assets/{index-DHbfLOUR.js → index-BMekSELC.js} +106 -106
  58. package/dist/webui/index.html +1 -1
  59. package/package.json +1 -1
@@ -2,7 +2,7 @@ import type { ServerWebSocket } from "bun";
2
2
  /**
3
3
  * Message types for gateway communication
4
4
  */
5
- export type MessageType = "connect" | "res" | "req:agent" | "req:agent:cancel" | "event:agent" | "session_subscribe" | "session_unsubscribe" | "register" | "registered" | "unregister" | "join_group" | "leave_group" | "broadcast" | "direct" | "ping" | "pong" | "error" | "ack" | "upgrade";
5
+ export type MessageType = "connect" | "res" | "req:agent" | "req:agent:cancel" | "event:agent" | "req:node" | "event:node" | "session_subscribe" | "session_unsubscribe" | "register" | "registered" | "unregister" | "join_group" | "leave_group" | "broadcast" | "direct" | "ping" | "pong" | "error" | "ack" | "upgrade";
6
6
  /**
7
7
  * Gateway message structure
8
8
  */
@@ -87,6 +87,7 @@ export interface RoutingInfo {
87
87
  export interface NodeMetadata {
88
88
  id: string;
89
89
  name: string;
90
+ clientId?: string;
90
91
  capabilities?: string[];
91
92
  groups: Set<string>;
92
93
  connectedAt: number;
@@ -102,6 +103,10 @@ export interface NodeMetadata {
102
103
  export interface Node extends NodeMetadata {
103
104
  ws: ServerWebSocket<{
104
105
  nodeId: string;
106
+ clientId?: string;
107
+ clientType?: string;
108
+ authenticated?: boolean;
109
+ tailscaleUser?: string;
105
110
  }>;
106
111
  }
107
112
  /**
@@ -44,6 +44,8 @@ const MessageTypeSchema = external_zod_namespaceObject["enum"]([
44
44
  "req:agent",
45
45
  "req:agent:cancel",
46
46
  "event:agent",
47
+ "req:node",
48
+ "event:node",
47
49
  "session_subscribe",
48
50
  "session_unsubscribe",
49
51
  "register",
@@ -10,6 +10,8 @@ export declare const MessageTypeSchema: z.ZodEnum<{
10
10
  "req:agent": "req:agent";
11
11
  "req:agent:cancel": "req:agent:cancel";
12
12
  "event:agent": "event:agent";
13
+ "req:node": "req:node";
14
+ "event:node": "event:node";
13
15
  session_subscribe: "session_subscribe";
14
16
  session_unsubscribe: "session_unsubscribe";
15
17
  register: "register";
@@ -34,6 +36,8 @@ export declare const GatewayMessageSchema: z.ZodObject<{
34
36
  "req:agent": "req:agent";
35
37
  "req:agent:cancel": "req:agent:cancel";
36
38
  "event:agent": "event:agent";
39
+ "req:node": "req:node";
40
+ "event:node": "event:node";
37
41
  session_subscribe: "session_subscribe";
38
42
  session_unsubscribe: "session_unsubscribe";
39
43
  register: "register";
@@ -5,6 +5,8 @@ const MessageTypeSchema = external_zod_enum([
5
5
  "req:agent",
6
6
  "req:agent:cancel",
7
7
  "event:agent",
8
+ "req:node",
9
+ "event:node",
8
10
  "session_subscribe",
9
11
  "session_unsubscribe",
10
12
  "register",
@@ -117,6 +117,98 @@ const additional_messages_cjs_namespaceObject = require("../agent/middleware/add
117
117
  }).length;
118
118
  (0, external_vitest_namespaceObject.expect)(injectedCount).toBe(1);
119
119
  });
120
+ (0, external_vitest_namespaceObject.it)("refreshes injected node context on each invocation", async ()=>{
121
+ let connectedIds = [
122
+ "node-a"
123
+ ];
124
+ const middleware = (0, additional_messages_cjs_namespaceObject.additionalMessageMiddleware)({
125
+ nodeConnectedIdsProvider: ()=>connectedIds
126
+ });
127
+ const beforeAgent = "function" == typeof middleware.beforeAgent ? middleware.beforeAgent : middleware.beforeAgent?.hook;
128
+ if (!beforeAgent) throw new Error("beforeAgent hook not configured");
129
+ const first = await beforeAgent({
130
+ messages: [
131
+ new external_langchain_namespaceObject.HumanMessage("Hello")
132
+ ]
133
+ }, {});
134
+ const firstContent = first.messages[0]?.content ?? "";
135
+ (0, external_vitest_namespaceObject.expect)(firstContent).toContain("Connected node IDs: node-a");
136
+ connectedIds = [
137
+ "node-b"
138
+ ];
139
+ const second = await beforeAgent({
140
+ messages: first.messages
141
+ }, {});
142
+ const secondContent = second.messages[0]?.content ?? "";
143
+ (0, external_vitest_namespaceObject.expect)(secondContent).toContain("Connected node IDs: node-b");
144
+ (0, external_vitest_namespaceObject.expect)(secondContent).not.toContain("Connected node IDs: node-a");
145
+ });
146
+ (0, external_vitest_namespaceObject.it)("injects connected node IDs for node tool targeting when provided", async ()=>{
147
+ const middleware = (0, additional_messages_cjs_namespaceObject.additionalMessageMiddleware)({
148
+ nodeConnectedIdsProvider: ()=>[
149
+ "node-b",
150
+ "node-a",
151
+ "",
152
+ "node-b"
153
+ ]
154
+ });
155
+ const input = {
156
+ messages: [
157
+ new external_langchain_namespaceObject.HumanMessage("Hello")
158
+ ]
159
+ };
160
+ const beforeAgent = "function" == typeof middleware.beforeAgent ? middleware.beforeAgent : middleware.beforeAgent?.hook;
161
+ if (!beforeAgent) throw new Error("beforeAgent hook not configured");
162
+ const result = await beforeAgent(input, {});
163
+ const content = result.messages[0]?.content ?? "";
164
+ (0, external_vitest_namespaceObject.expect)(content).toContain("Connected Node Targets");
165
+ (0, external_vitest_namespaceObject.expect)(content).toContain("Connected node IDs: node-b, node-a");
166
+ (0, external_vitest_namespaceObject.expect)(content).toContain("target.nodeId or target.clientId");
167
+ });
168
+ (0, external_vitest_namespaceObject.it)("injects default node target clientId when present", async ()=>{
169
+ const middleware = (0, additional_messages_cjs_namespaceObject.additionalMessageMiddleware)({
170
+ nodeConnectedIdsProvider: ()=>[],
171
+ defaultNodeTargetClientId: "desktop-abc123"
172
+ });
173
+ const input = {
174
+ messages: [
175
+ new external_langchain_namespaceObject.HumanMessage("Hello")
176
+ ]
177
+ };
178
+ const beforeAgent = "function" == typeof middleware.beforeAgent ? middleware.beforeAgent : middleware.beforeAgent?.hook;
179
+ if (!beforeAgent) throw new Error("beforeAgent hook not configured");
180
+ const result = await beforeAgent(input, {});
181
+ const content = result.messages[0]?.content ?? "";
182
+ (0, external_vitest_namespaceObject.expect)(content).toContain("Connected node IDs: (none currently connected)");
183
+ (0, external_vitest_namespaceObject.expect)(content).toContain("Default node target clientId for this request: desktop-abc123");
184
+ });
185
+ (0, external_vitest_namespaceObject.it)("injects connected node metadata when provided", async ()=>{
186
+ const middleware = (0, additional_messages_cjs_namespaceObject.additionalMessageMiddleware)({
187
+ nodeConnectedTargetsProvider: ()=>[
188
+ {
189
+ nodeId: "node-a",
190
+ clientId: "desktop-a",
191
+ name: "Russell MacBook",
192
+ capabilities: [
193
+ "system.notify",
194
+ "system.run"
195
+ ]
196
+ }
197
+ ]
198
+ });
199
+ const input = {
200
+ messages: [
201
+ new external_langchain_namespaceObject.HumanMessage("Hello")
202
+ ]
203
+ };
204
+ const beforeAgent = "function" == typeof middleware.beforeAgent ? middleware.beforeAgent : middleware.beforeAgent?.hook;
205
+ if (!beforeAgent) throw new Error("beforeAgent hook not configured");
206
+ const result = await beforeAgent(input, {});
207
+ const content = result.messages[0]?.content ?? "";
208
+ (0, external_vitest_namespaceObject.expect)(content).toContain("Connected node metadata:");
209
+ (0, external_vitest_namespaceObject.expect)(content).toContain("node-a (clientId: desktop-a; name: Russell MacBook;");
210
+ (0, external_vitest_namespaceObject.expect)(content).toContain("capabilities: system.notify, system.run");
211
+ });
120
212
  });
121
213
  for(var __rspack_i in __webpack_exports__)exports[__rspack_i] = __webpack_exports__[__rspack_i];
122
214
  Object.defineProperty(exports, '__esModule', {
@@ -93,4 +93,96 @@ describe("additionalMessageMiddleware", ()=>{
93
93
  }).length;
94
94
  expect(injectedCount).toBe(1);
95
95
  });
96
+ it("refreshes injected node context on each invocation", async ()=>{
97
+ let connectedIds = [
98
+ "node-a"
99
+ ];
100
+ const middleware = additionalMessageMiddleware({
101
+ nodeConnectedIdsProvider: ()=>connectedIds
102
+ });
103
+ const beforeAgent = "function" == typeof middleware.beforeAgent ? middleware.beforeAgent : middleware.beforeAgent?.hook;
104
+ if (!beforeAgent) throw new Error("beforeAgent hook not configured");
105
+ const first = await beforeAgent({
106
+ messages: [
107
+ new HumanMessage("Hello")
108
+ ]
109
+ }, {});
110
+ const firstContent = first.messages[0]?.content ?? "";
111
+ expect(firstContent).toContain("Connected node IDs: node-a");
112
+ connectedIds = [
113
+ "node-b"
114
+ ];
115
+ const second = await beforeAgent({
116
+ messages: first.messages
117
+ }, {});
118
+ const secondContent = second.messages[0]?.content ?? "";
119
+ expect(secondContent).toContain("Connected node IDs: node-b");
120
+ expect(secondContent).not.toContain("Connected node IDs: node-a");
121
+ });
122
+ it("injects connected node IDs for node tool targeting when provided", async ()=>{
123
+ const middleware = additionalMessageMiddleware({
124
+ nodeConnectedIdsProvider: ()=>[
125
+ "node-b",
126
+ "node-a",
127
+ "",
128
+ "node-b"
129
+ ]
130
+ });
131
+ const input = {
132
+ messages: [
133
+ new HumanMessage("Hello")
134
+ ]
135
+ };
136
+ const beforeAgent = "function" == typeof middleware.beforeAgent ? middleware.beforeAgent : middleware.beforeAgent?.hook;
137
+ if (!beforeAgent) throw new Error("beforeAgent hook not configured");
138
+ const result = await beforeAgent(input, {});
139
+ const content = result.messages[0]?.content ?? "";
140
+ expect(content).toContain("Connected Node Targets");
141
+ expect(content).toContain("Connected node IDs: node-b, node-a");
142
+ expect(content).toContain("target.nodeId or target.clientId");
143
+ });
144
+ it("injects default node target clientId when present", async ()=>{
145
+ const middleware = additionalMessageMiddleware({
146
+ nodeConnectedIdsProvider: ()=>[],
147
+ defaultNodeTargetClientId: "desktop-abc123"
148
+ });
149
+ const input = {
150
+ messages: [
151
+ new HumanMessage("Hello")
152
+ ]
153
+ };
154
+ const beforeAgent = "function" == typeof middleware.beforeAgent ? middleware.beforeAgent : middleware.beforeAgent?.hook;
155
+ if (!beforeAgent) throw new Error("beforeAgent hook not configured");
156
+ const result = await beforeAgent(input, {});
157
+ const content = result.messages[0]?.content ?? "";
158
+ expect(content).toContain("Connected node IDs: (none currently connected)");
159
+ expect(content).toContain("Default node target clientId for this request: desktop-abc123");
160
+ });
161
+ it("injects connected node metadata when provided", async ()=>{
162
+ const middleware = additionalMessageMiddleware({
163
+ nodeConnectedTargetsProvider: ()=>[
164
+ {
165
+ nodeId: "node-a",
166
+ clientId: "desktop-a",
167
+ name: "Russell MacBook",
168
+ capabilities: [
169
+ "system.notify",
170
+ "system.run"
171
+ ]
172
+ }
173
+ ]
174
+ });
175
+ const input = {
176
+ messages: [
177
+ new HumanMessage("Hello")
178
+ ]
179
+ };
180
+ const beforeAgent = "function" == typeof middleware.beforeAgent ? middleware.beforeAgent : middleware.beforeAgent?.hook;
181
+ if (!beforeAgent) throw new Error("beforeAgent hook not configured");
182
+ const result = await beforeAgent(input, {});
183
+ const content = result.messages[0]?.content ?? "";
184
+ expect(content).toContain("Connected node metadata:");
185
+ expect(content).toContain("node-a (clientId: desktop-a; name: Russell MacBook;");
186
+ expect(content).toContain("capabilities: system.notify, system.run");
187
+ });
96
188
  });
@@ -0,0 +1,277 @@
1
+ "use strict";
2
+ var __webpack_exports__ = {};
3
+ const external_node_fs_namespaceObject = require("node:fs");
4
+ const external_node_os_namespaceObject = require("node:os");
5
+ const external_node_path_namespaceObject = require("node:path");
6
+ const external_vitest_namespaceObject = require("vitest");
7
+ const server_cjs_namespaceObject = require("../gateway/server.cjs");
8
+ const tempWorkspaces = [];
9
+ function createGateway(config) {
10
+ const workspace = (0, external_node_fs_namespaceObject.mkdtempSync)((0, external_node_path_namespaceObject.join)((0, external_node_os_namespaceObject.tmpdir)(), "wingman-gateway-http-security-"));
11
+ tempWorkspaces.push(workspace);
12
+ return new server_cjs_namespaceObject.GatewayServer({
13
+ logLevel: "silent",
14
+ workspace,
15
+ configDir: ".wingman-http-security-config",
16
+ stateDir: ".wingman-http-security-state",
17
+ ...config
18
+ });
19
+ }
20
+ function getGatewayInternals(server) {
21
+ return server;
22
+ }
23
+ function createTestSocket(initialData) {
24
+ const messages = [];
25
+ const ws = {
26
+ data: {
27
+ ...initialData || {}
28
+ },
29
+ send: (serialized)=>{
30
+ messages.push(JSON.parse(serialized));
31
+ return 1;
32
+ },
33
+ close: ()=>{}
34
+ };
35
+ return {
36
+ ws,
37
+ messages
38
+ };
39
+ }
40
+ (0, external_vitest_namespaceObject.afterAll)(()=>{
41
+ for (const workspace of tempWorkspaces)(0, external_node_fs_namespaceObject.rmSync)(workspace, {
42
+ recursive: true,
43
+ force: true
44
+ });
45
+ });
46
+ (0, external_vitest_namespaceObject.describe)("gateway HTTP security", ()=>{
47
+ (0, external_vitest_namespaceObject.it)("requires auth for /api routes when token auth is enabled", async ()=>{
48
+ const server = createGateway({
49
+ host: "127.0.0.1",
50
+ port: 18789,
51
+ auth: {
52
+ mode: "token",
53
+ token: "test-token"
54
+ },
55
+ requireAuth: true
56
+ });
57
+ const internals = getGatewayInternals(server);
58
+ const unauthenticated = await internals.handleUiRequest(new Request("http://127.0.0.1:18789/api/providers"));
59
+ (0, external_vitest_namespaceObject.expect)(unauthenticated.status).toBe(401);
60
+ const authenticated = await internals.handleUiRequest(new Request("http://127.0.0.1:18789/api/providers", {
61
+ headers: {
62
+ Authorization: "Bearer test-token"
63
+ }
64
+ }));
65
+ (0, external_vitest_namespaceObject.expect)(authenticated.status).toBe(200);
66
+ });
67
+ (0, external_vitest_namespaceObject.it)("rejects disallowed origins and allows loopback development preflight", async ()=>{
68
+ const server = createGateway({
69
+ host: "127.0.0.1",
70
+ port: 18789,
71
+ auth: {
72
+ mode: "token",
73
+ token: "test-token"
74
+ },
75
+ requireAuth: true
76
+ });
77
+ const internals = getGatewayInternals(server);
78
+ const denied = await internals.handleUiRequest(new Request("http://127.0.0.1:18789/api/providers", {
79
+ method: "OPTIONS",
80
+ headers: {
81
+ Origin: "https://evil.example",
82
+ "Access-Control-Request-Method": "GET"
83
+ }
84
+ }));
85
+ (0, external_vitest_namespaceObject.expect)(denied.status).toBe(403);
86
+ const allowed = await internals.handleUiRequest(new Request("http://127.0.0.1:18789/api/providers", {
87
+ method: "OPTIONS",
88
+ headers: {
89
+ Origin: "http://localhost:5173",
90
+ "Access-Control-Request-Method": "GET"
91
+ }
92
+ }));
93
+ (0, external_vitest_namespaceObject.expect)(allowed.status).toBe(204);
94
+ (0, external_vitest_namespaceObject.expect)(allowed.headers.get("access-control-allow-origin")).toBe("http://localhost:5173");
95
+ });
96
+ (0, external_vitest_namespaceObject.it)("does not trust tailscale identity headers on non-loopback hosts", ()=>{
97
+ const server = createGateway({
98
+ host: "0.0.0.0",
99
+ port: 18789,
100
+ auth: {
101
+ mode: "token",
102
+ token: "tailscale-token",
103
+ allowTailscale: true
104
+ },
105
+ requireAuth: true
106
+ });
107
+ const internals = getGatewayInternals(server);
108
+ const bypassAttempt = internals.requireHttpAuth(new Request("http://127.0.0.1:18789/api/providers", {
109
+ headers: {
110
+ "tailscale-user-login": "attacker@example.com"
111
+ }
112
+ }));
113
+ (0, external_vitest_namespaceObject.expect)(bypassAttempt?.status).toBe(401);
114
+ const authenticated = internals.requireHttpAuth(new Request("http://127.0.0.1:18789/api/providers", {
115
+ headers: {
116
+ Authorization: "Bearer tailscale-token",
117
+ "tailscale-user-login": "attacker@example.com"
118
+ }
119
+ }));
120
+ (0, external_vitest_namespaceObject.expect)(authenticated).toBeNull();
121
+ });
122
+ (0, external_vitest_namespaceObject.it)("enforces bridge node capacity limits", async ()=>{
123
+ const server = createGateway({
124
+ host: "127.0.0.1",
125
+ port: 18789,
126
+ auth: {
127
+ mode: "none"
128
+ },
129
+ requireAuth: false,
130
+ maxNodes: 1
131
+ });
132
+ const internals = getGatewayInternals(server);
133
+ const registerBody = JSON.stringify({
134
+ type: "register",
135
+ payload: {
136
+ name: "bridge-node"
137
+ },
138
+ timestamp: Date.now()
139
+ });
140
+ const first = await internals.handleBridgeSend(new Request("http://127.0.0.1:18789/bridge/send", {
141
+ method: "POST",
142
+ headers: {
143
+ "Content-Type": "application/json"
144
+ },
145
+ body: registerBody
146
+ }));
147
+ (0, external_vitest_namespaceObject.expect)(first.status).toBe(200);
148
+ const second = await internals.handleBridgeSend(new Request("http://127.0.0.1:18789/bridge/send", {
149
+ method: "POST",
150
+ headers: {
151
+ "Content-Type": "application/json"
152
+ },
153
+ body: registerBody
154
+ }));
155
+ (0, external_vitest_namespaceObject.expect)(second.status).toBe(429);
156
+ });
157
+ (0, external_vitest_namespaceObject.it)("rejects malformed /api/nodes client IDs with 400", async ()=>{
158
+ const server = createGateway({
159
+ host: "127.0.0.1",
160
+ port: 18789,
161
+ auth: {
162
+ mode: "none"
163
+ },
164
+ requireAuth: false
165
+ });
166
+ const internals = getGatewayInternals(server);
167
+ const res = await internals.handleUiRequest(new Request("http://127.0.0.1:18789/api/nodes/%E0%A4%A", {
168
+ method: "PUT",
169
+ headers: {
170
+ "Content-Type": "application/json"
171
+ },
172
+ body: JSON.stringify({
173
+ enabled: true
174
+ })
175
+ }));
176
+ (0, external_vitest_namespaceObject.expect)(res.status).toBe(400);
177
+ });
178
+ (0, external_vitest_namespaceObject.it)("requires approved client identity for node execution capabilities", ()=>{
179
+ const server = createGateway({
180
+ host: "127.0.0.1",
181
+ port: 18789,
182
+ auth: {
183
+ mode: "none"
184
+ },
185
+ requireAuth: false
186
+ });
187
+ const internals = getGatewayInternals(server);
188
+ const blocked = createTestSocket({
189
+ authenticated: true
190
+ });
191
+ internals.handleRegister(blocked.ws, {
192
+ type: "register",
193
+ payload: {
194
+ name: "unpaired-node",
195
+ capabilities: [
196
+ "system.run"
197
+ ]
198
+ },
199
+ timestamp: Date.now()
200
+ });
201
+ (0, external_vitest_namespaceObject.expect)(blocked.messages.some((message)=>"error" === message.type && message.payload?.code === "NODE_NOT_ENABLED")).toBe(true);
202
+ });
203
+ (0, external_vitest_namespaceObject.it)("rejects duplicate pending node request IDs", async ()=>{
204
+ const server = createGateway({
205
+ host: "127.0.0.1",
206
+ port: 18789,
207
+ auth: {
208
+ mode: "none"
209
+ },
210
+ requireAuth: false
211
+ });
212
+ const internals = getGatewayInternals(server);
213
+ const enableTarget = await internals.handleUiRequest(new Request("http://127.0.0.1:18789/api/nodes/desktop-target", {
214
+ method: "PUT",
215
+ headers: {
216
+ "Content-Type": "application/json"
217
+ },
218
+ body: JSON.stringify({
219
+ enabled: true,
220
+ name: "Desktop Target"
221
+ })
222
+ }));
223
+ (0, external_vitest_namespaceObject.expect)(enableTarget.status).toBe(200);
224
+ const target = createTestSocket({
225
+ authenticated: true,
226
+ clientId: "desktop-target",
227
+ clientType: "desktop"
228
+ });
229
+ internals.handleRegister(target.ws, {
230
+ type: "register",
231
+ payload: {
232
+ name: "target-node",
233
+ capabilities: [
234
+ "system.notify"
235
+ ]
236
+ },
237
+ timestamp: Date.now()
238
+ });
239
+ const nodeId = target.messages.find((message)=>"ack" === message.type && "string" == typeof message.payload?.nodeId)?.payload?.nodeId;
240
+ (0, external_vitest_namespaceObject.expect)(nodeId).toBeTruthy();
241
+ const requester = createTestSocket({
242
+ authenticated: true,
243
+ clientId: "desktop-requester",
244
+ clientType: "desktop"
245
+ });
246
+ const duplicateRequestId = "dup-node-request-id";
247
+ internals.handleNodeRequest(requester.ws, {
248
+ type: "req:node",
249
+ id: duplicateRequestId,
250
+ targetNodeId: nodeId,
251
+ payload: {
252
+ tool: "system.notify",
253
+ args: {
254
+ title: "test"
255
+ }
256
+ },
257
+ timestamp: Date.now()
258
+ });
259
+ internals.handleNodeRequest(requester.ws, {
260
+ type: "req:node",
261
+ id: duplicateRequestId,
262
+ targetNodeId: nodeId,
263
+ payload: {
264
+ tool: "system.notify",
265
+ args: {
266
+ title: "test"
267
+ }
268
+ },
269
+ timestamp: Date.now()
270
+ });
271
+ (0, external_vitest_namespaceObject.expect)(requester.messages.some((message)=>"error" === message.type && message.payload?.code === "DUPLICATE_REQUEST_ID")).toBe(true);
272
+ });
273
+ });
274
+ for(var __rspack_i in __webpack_exports__)exports[__rspack_i] = __webpack_exports__[__rspack_i];
275
+ Object.defineProperty(exports, '__esModule', {
276
+ value: true
277
+ });
@@ -0,0 +1 @@
1
+ export {};