@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
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
const CLIENT_ID_PATTERN = /^[a-zA-Z0-9._:-]{1,128}$/;
|
|
4
|
+
const normalizeClientId = (raw)=>{
|
|
5
|
+
let decoded;
|
|
6
|
+
try {
|
|
7
|
+
decoded = decodeURIComponent(raw);
|
|
8
|
+
} catch {
|
|
9
|
+
return null;
|
|
10
|
+
}
|
|
11
|
+
const trimmed = decoded.trim();
|
|
12
|
+
if (!trimmed) return null;
|
|
13
|
+
if (!CLIENT_ID_PATTERN.test(trimmed)) return null;
|
|
14
|
+
return trimmed;
|
|
15
|
+
};
|
|
16
|
+
const createNodeApprovalStore = (resolveConfigDirPath)=>{
|
|
17
|
+
const resolvePath = ()=>{
|
|
18
|
+
const configDir = resolveConfigDirPath();
|
|
19
|
+
mkdirSync(configDir, {
|
|
20
|
+
recursive: true
|
|
21
|
+
});
|
|
22
|
+
return join(configDir, "nodes.json");
|
|
23
|
+
};
|
|
24
|
+
const readRecords = ()=>{
|
|
25
|
+
const path = resolvePath();
|
|
26
|
+
if (!existsSync(path)) return [];
|
|
27
|
+
try {
|
|
28
|
+
const raw = readFileSync(path, "utf-8");
|
|
29
|
+
const parsed = JSON.parse(raw);
|
|
30
|
+
if (!Array.isArray(parsed)) return [];
|
|
31
|
+
const records = [];
|
|
32
|
+
for (const entry of parsed){
|
|
33
|
+
if (!entry || "object" != typeof entry) continue;
|
|
34
|
+
const typed = entry;
|
|
35
|
+
const clientId = "string" == typeof typed.clientId ? typed.clientId : "";
|
|
36
|
+
if (clientId.trim()) records.push({
|
|
37
|
+
clientId: clientId.trim(),
|
|
38
|
+
name: "string" == typeof typed.name && typed.name.trim() ? typed.name.trim() : void 0,
|
|
39
|
+
enabled: false !== typed.enabled,
|
|
40
|
+
createdAt: "number" == typeof typed.createdAt ? typed.createdAt : Date.now(),
|
|
41
|
+
updatedAt: "number" == typeof typed.updatedAt ? typed.updatedAt : Date.now(),
|
|
42
|
+
lastSeenAt: "number" == typeof typed.lastSeenAt ? typed.lastSeenAt : void 0
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
return records;
|
|
46
|
+
} catch {
|
|
47
|
+
return [];
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
const writeRecords = (records)=>{
|
|
51
|
+
const path = resolvePath();
|
|
52
|
+
writeFileSync(path, JSON.stringify(records, null, 2));
|
|
53
|
+
};
|
|
54
|
+
const replaceRecord = (records, nextRecord)=>{
|
|
55
|
+
const index = records.findIndex((record)=>record.clientId === nextRecord.clientId);
|
|
56
|
+
if (index >= 0) records[index] = nextRecord;
|
|
57
|
+
else records.unshift(nextRecord);
|
|
58
|
+
return records;
|
|
59
|
+
};
|
|
60
|
+
return {
|
|
61
|
+
load: ()=>readRecords(),
|
|
62
|
+
save: (records)=>writeRecords(records),
|
|
63
|
+
isEnabled: (clientId)=>{
|
|
64
|
+
const trimmed = clientId.trim();
|
|
65
|
+
if (!trimmed) return false;
|
|
66
|
+
return readRecords().some((record)=>record.clientId === trimmed && record.enabled);
|
|
67
|
+
},
|
|
68
|
+
setEnabled: (clientId, enabled, name, lastSeenAt)=>{
|
|
69
|
+
const trimmed = clientId.trim();
|
|
70
|
+
const now = Date.now();
|
|
71
|
+
const records = readRecords();
|
|
72
|
+
const existing = records.find((record)=>record.clientId === trimmed);
|
|
73
|
+
const nextRecord = {
|
|
74
|
+
clientId: trimmed,
|
|
75
|
+
name: "string" == typeof name && name.trim() ? name.trim() : existing?.name,
|
|
76
|
+
enabled,
|
|
77
|
+
createdAt: existing?.createdAt ?? now,
|
|
78
|
+
updatedAt: now,
|
|
79
|
+
lastSeenAt: "number" == typeof lastSeenAt ? lastSeenAt : existing?.lastSeenAt
|
|
80
|
+
};
|
|
81
|
+
writeRecords(replaceRecord(records, nextRecord));
|
|
82
|
+
return nextRecord;
|
|
83
|
+
},
|
|
84
|
+
markSeen: (clientId, name)=>{
|
|
85
|
+
const trimmed = clientId.trim();
|
|
86
|
+
if (!trimmed) return null;
|
|
87
|
+
const records = readRecords();
|
|
88
|
+
const existing = records.find((record)=>record.clientId === trimmed);
|
|
89
|
+
if (!existing) return null;
|
|
90
|
+
const now = Date.now();
|
|
91
|
+
const nextRecord = {
|
|
92
|
+
...existing,
|
|
93
|
+
name: "string" == typeof name && name.trim() ? name.trim() : existing.name,
|
|
94
|
+
updatedAt: now,
|
|
95
|
+
lastSeenAt: now
|
|
96
|
+
};
|
|
97
|
+
writeRecords(replaceRecord(records, nextRecord));
|
|
98
|
+
return nextRecord;
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
};
|
|
102
|
+
const handleNodesApi = async (_ctx, nodeManager, store, req, url)=>{
|
|
103
|
+
if ("/api/nodes" === url.pathname && "GET" === req.method) {
|
|
104
|
+
const approvals = store.load();
|
|
105
|
+
const connectedNodes = nodeManager.getAllNodes();
|
|
106
|
+
const connectedByClientId = new Map();
|
|
107
|
+
for (const node of connectedNodes){
|
|
108
|
+
if (!node.clientId) continue;
|
|
109
|
+
const bucket = connectedByClientId.get(node.clientId) || [];
|
|
110
|
+
bucket.push(node);
|
|
111
|
+
connectedByClientId.set(node.clientId, bucket);
|
|
112
|
+
}
|
|
113
|
+
const clientIds = new Set([
|
|
114
|
+
...approvals.map((record)=>record.clientId),
|
|
115
|
+
...connectedByClientId.keys()
|
|
116
|
+
]);
|
|
117
|
+
const nodes = Array.from(clientIds).map((clientId)=>{
|
|
118
|
+
const approval = approvals.find((record)=>record.clientId === clientId);
|
|
119
|
+
const connected = connectedByClientId.get(clientId) || [];
|
|
120
|
+
const capabilities = Array.from(new Set(connected.flatMap((node)=>Array.isArray(node.capabilities) ? node.capabilities : [])));
|
|
121
|
+
return {
|
|
122
|
+
clientId,
|
|
123
|
+
name: approval?.name || connected[0]?.name || clientId,
|
|
124
|
+
enabled: approval?.enabled ?? false,
|
|
125
|
+
createdAt: approval?.createdAt,
|
|
126
|
+
updatedAt: approval?.updatedAt,
|
|
127
|
+
lastSeenAt: approval?.lastSeenAt,
|
|
128
|
+
connected: connected.length > 0,
|
|
129
|
+
nodeIds: connected.map((node)=>node.id),
|
|
130
|
+
capabilities
|
|
131
|
+
};
|
|
132
|
+
}).sort((a, b)=>{
|
|
133
|
+
const aTime = a.updatedAt || 0;
|
|
134
|
+
const bTime = b.updatedAt || 0;
|
|
135
|
+
return bTime - aTime;
|
|
136
|
+
});
|
|
137
|
+
return new Response(JSON.stringify({
|
|
138
|
+
nodes
|
|
139
|
+
}, null, 2), {
|
|
140
|
+
headers: {
|
|
141
|
+
"Content-Type": "application/json"
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
const clientMatch = url.pathname.match(/^\/api\/nodes\/([^/]+)$/);
|
|
146
|
+
if (!clientMatch) return null;
|
|
147
|
+
const clientId = normalizeClientId(clientMatch[1]);
|
|
148
|
+
if (!clientId) return new Response("valid clientId required", {
|
|
149
|
+
status: 400
|
|
150
|
+
});
|
|
151
|
+
if ("PUT" === req.method) {
|
|
152
|
+
let body;
|
|
153
|
+
try {
|
|
154
|
+
body = await req.json();
|
|
155
|
+
} catch {
|
|
156
|
+
return new Response("Invalid JSON body", {
|
|
157
|
+
status: 400
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
if ("boolean" != typeof body.enabled) return new Response("enabled boolean required", {
|
|
161
|
+
status: 400
|
|
162
|
+
});
|
|
163
|
+
const nextRecord = store.setEnabled(clientId, body.enabled, body.name, body.enabled ? Date.now() : void 0);
|
|
164
|
+
if (!nextRecord.enabled) {
|
|
165
|
+
const activeNodes = nodeManager.getNodesByClientId(clientId);
|
|
166
|
+
for (const node of activeNodes)nodeManager.unregisterNode(node.id);
|
|
167
|
+
}
|
|
168
|
+
return new Response(JSON.stringify(nextRecord, null, 2), {
|
|
169
|
+
headers: {
|
|
170
|
+
"Content-Type": "application/json"
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
if ("DELETE" === req.method) {
|
|
175
|
+
const nextRecord = store.setEnabled(clientId, false);
|
|
176
|
+
const activeNodes = nodeManager.getNodesByClientId(clientId);
|
|
177
|
+
for (const node of activeNodes)nodeManager.unregisterNode(node.id);
|
|
178
|
+
return new Response(JSON.stringify(nextRecord, null, 2), {
|
|
179
|
+
headers: {
|
|
180
|
+
"Content-Type": "application/json"
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
if ("GET" === req.method) {
|
|
185
|
+
const approvals = store.load();
|
|
186
|
+
const approval = approvals.find((record)=>record.clientId === clientId);
|
|
187
|
+
const connected = nodeManager.getNodesByClientId(clientId);
|
|
188
|
+
if (!approval && 0 === connected.length) return new Response("Node not found", {
|
|
189
|
+
status: 404
|
|
190
|
+
});
|
|
191
|
+
return new Response(JSON.stringify({
|
|
192
|
+
clientId,
|
|
193
|
+
name: approval?.name || connected[0]?.name || clientId,
|
|
194
|
+
enabled: approval?.enabled ?? false,
|
|
195
|
+
createdAt: approval?.createdAt,
|
|
196
|
+
updatedAt: approval?.updatedAt,
|
|
197
|
+
lastSeenAt: approval?.lastSeenAt,
|
|
198
|
+
connected: connected.length > 0,
|
|
199
|
+
nodeIds: connected.map((node)=>node.id)
|
|
200
|
+
}, null, 2), {
|
|
201
|
+
headers: {
|
|
202
|
+
"Content-Type": "application/json"
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
return new Response("Method Not Allowed", {
|
|
207
|
+
status: 405
|
|
208
|
+
});
|
|
209
|
+
};
|
|
210
|
+
export { createNodeApprovalStore, handleNodesApi };
|
package/dist/gateway/node.cjs
CHANGED
|
@@ -40,12 +40,13 @@ function _define_property(obj, key, value) {
|
|
|
40
40
|
}
|
|
41
41
|
const logger = (0, external_logger_cjs_namespaceObject.createLogger)();
|
|
42
42
|
class NodeManager {
|
|
43
|
-
registerNode(ws, name, capabilities, sessionId, agentName) {
|
|
43
|
+
registerNode(ws, name, capabilities, sessionId, agentName, clientId) {
|
|
44
44
|
if (this.nodes.size >= this.maxNodes) return null;
|
|
45
45
|
const id = this.generateNodeId();
|
|
46
46
|
const node = {
|
|
47
47
|
id,
|
|
48
48
|
name,
|
|
49
|
+
clientId,
|
|
49
50
|
capabilities,
|
|
50
51
|
groups: new Set(),
|
|
51
52
|
connectedAt: Date.now(),
|
|
@@ -54,6 +55,7 @@ class NodeManager {
|
|
|
54
55
|
agentName
|
|
55
56
|
};
|
|
56
57
|
ws.data = {
|
|
58
|
+
...ws.data,
|
|
57
59
|
nodeId: id
|
|
58
60
|
};
|
|
59
61
|
this.nodes.set(id, node);
|
|
@@ -73,12 +75,18 @@ class NodeManager {
|
|
|
73
75
|
getAllNodes() {
|
|
74
76
|
return Array.from(this.nodes.values());
|
|
75
77
|
}
|
|
78
|
+
getNodesByClientId(clientId) {
|
|
79
|
+
const trimmed = clientId.trim();
|
|
80
|
+
if (!trimmed) return [];
|
|
81
|
+
return Array.from(this.nodes.values()).filter((node)=>node.clientId === trimmed);
|
|
82
|
+
}
|
|
76
83
|
getNodeMetadata(nodeId) {
|
|
77
84
|
const node = this.nodes.get(nodeId);
|
|
78
85
|
if (!node) return;
|
|
79
86
|
return {
|
|
80
87
|
id: node.id,
|
|
81
88
|
name: node.name,
|
|
89
|
+
clientId: node.clientId,
|
|
82
90
|
capabilities: node.capabilities,
|
|
83
91
|
groups: node.groups,
|
|
84
92
|
connectedAt: node.connectedAt,
|
|
@@ -190,6 +198,7 @@ class NodeManager {
|
|
|
190
198
|
nodes: Array.from(this.nodes.values()).map((n)=>({
|
|
191
199
|
id: n.id,
|
|
192
200
|
name: n.name,
|
|
201
|
+
clientId: n.clientId,
|
|
193
202
|
groupCount: n.groups.size,
|
|
194
203
|
connectedAt: n.connectedAt,
|
|
195
204
|
lastPing: n.lastPing,
|
package/dist/gateway/node.d.ts
CHANGED
|
@@ -14,7 +14,11 @@ export declare class NodeManager {
|
|
|
14
14
|
*/
|
|
15
15
|
registerNode(ws: ServerWebSocket<{
|
|
16
16
|
nodeId: string;
|
|
17
|
-
|
|
17
|
+
clientId?: string;
|
|
18
|
+
clientType?: string;
|
|
19
|
+
authenticated?: boolean;
|
|
20
|
+
tailscaleUser?: string;
|
|
21
|
+
}>, name: string, capabilities?: string[], sessionId?: string, agentName?: string, clientId?: string): Node | null;
|
|
18
22
|
/**
|
|
19
23
|
* Unregister a node
|
|
20
24
|
*/
|
|
@@ -27,6 +31,10 @@ export declare class NodeManager {
|
|
|
27
31
|
* Get all nodes
|
|
28
32
|
*/
|
|
29
33
|
getAllNodes(): Node[];
|
|
34
|
+
/**
|
|
35
|
+
* Get all nodes for a stable client ID
|
|
36
|
+
*/
|
|
37
|
+
getNodesByClientId(clientId: string): Node[];
|
|
30
38
|
/**
|
|
31
39
|
* Get node metadata (without WebSocket)
|
|
32
40
|
*/
|
|
@@ -102,6 +110,7 @@ export declare class NodeManager {
|
|
|
102
110
|
nodes: {
|
|
103
111
|
id: string;
|
|
104
112
|
name: string;
|
|
113
|
+
clientId: string | undefined;
|
|
105
114
|
groupCount: number;
|
|
106
115
|
connectedAt: number;
|
|
107
116
|
lastPing: number | undefined;
|
package/dist/gateway/node.js
CHANGED
|
@@ -12,12 +12,13 @@ function _define_property(obj, key, value) {
|
|
|
12
12
|
}
|
|
13
13
|
const logger = createLogger();
|
|
14
14
|
class NodeManager {
|
|
15
|
-
registerNode(ws, name, capabilities, sessionId, agentName) {
|
|
15
|
+
registerNode(ws, name, capabilities, sessionId, agentName, clientId) {
|
|
16
16
|
if (this.nodes.size >= this.maxNodes) return null;
|
|
17
17
|
const id = this.generateNodeId();
|
|
18
18
|
const node = {
|
|
19
19
|
id,
|
|
20
20
|
name,
|
|
21
|
+
clientId,
|
|
21
22
|
capabilities,
|
|
22
23
|
groups: new Set(),
|
|
23
24
|
connectedAt: Date.now(),
|
|
@@ -26,6 +27,7 @@ class NodeManager {
|
|
|
26
27
|
agentName
|
|
27
28
|
};
|
|
28
29
|
ws.data = {
|
|
30
|
+
...ws.data,
|
|
29
31
|
nodeId: id
|
|
30
32
|
};
|
|
31
33
|
this.nodes.set(id, node);
|
|
@@ -45,12 +47,18 @@ class NodeManager {
|
|
|
45
47
|
getAllNodes() {
|
|
46
48
|
return Array.from(this.nodes.values());
|
|
47
49
|
}
|
|
50
|
+
getNodesByClientId(clientId) {
|
|
51
|
+
const trimmed = clientId.trim();
|
|
52
|
+
if (!trimmed) return [];
|
|
53
|
+
return Array.from(this.nodes.values()).filter((node)=>node.clientId === trimmed);
|
|
54
|
+
}
|
|
48
55
|
getNodeMetadata(nodeId) {
|
|
49
56
|
const node = this.nodes.get(nodeId);
|
|
50
57
|
if (!node) return;
|
|
51
58
|
return {
|
|
52
59
|
id: node.id,
|
|
53
60
|
name: node.name,
|
|
61
|
+
clientId: node.clientId,
|
|
54
62
|
capabilities: node.capabilities,
|
|
55
63
|
groups: node.groups,
|
|
56
64
|
connectedAt: node.connectedAt,
|
|
@@ -162,6 +170,7 @@ class NodeManager {
|
|
|
162
170
|
nodes: Array.from(this.nodes.values()).map((n)=>({
|
|
163
171
|
id: n.id,
|
|
164
172
|
name: n.name,
|
|
173
|
+
clientId: n.clientId,
|
|
165
174
|
groupCount: n.groups.size,
|
|
166
175
|
connectedAt: n.connectedAt,
|
|
167
176
|
lastPing: n.lastPing,
|