fix: refresh node plugin tools after plugin load

This commit is contained in:
Peter Steinberger
2026-06-04 23:17:28 +01:00
parent 32caafd4ed
commit 4f7b5d8f44
3 changed files with 57 additions and 32 deletions

View File

@@ -837,6 +837,34 @@ describe("gateway/node-registry", () => {
]);
});
it("refreshes node-hosted plugin tools after plugin descriptors load", () => {
const registry = createTestNodeRegistry();
registry.register(
makeClient("conn-1", "node-1", [], {
commands: ["demo.echo"],
nodePluginTools: [
{
pluginId: "demo",
name: "demo_echo",
description: "Echo through the node",
command: "demo.echo",
},
],
}),
{},
);
expect(registry.get("node-1")?.nodePluginTools).toEqual([]);
registerDemoNodePluginTool({ name: "demo_echo", command: "demo.echo" });
registry.refreshNodePluginTools();
expect(registry.get("node-1")?.nodePluginTools.map((tool) => tool.name)).toEqual(["demo_echo"]);
expect(listConnectedNodePluginTools().map((entry) => entry.descriptor.name)).toEqual([
"demo_echo",
]);
});
it("ignores node plugin tool updates from stale connections", () => {
registerDemoNodePluginTool({ name: "demo_echo", command: "demo.echo" });
const registry = createTestNodeRegistry();

View File

@@ -208,6 +208,28 @@ export class NodeRegistry {
});
}
private replaceEffectiveNodePluginTools(node: NodeSession): void {
const nodePluginTools = this.normalizePluginToolDescriptors({
tools: node.declaredNodePluginTools,
allowedCommands: node.commands,
});
node.nodePluginTools = nodePluginTools;
node.client.connect.nodePluginTools = nodePluginTools;
replaceConnectedNodePluginTools({
nodeId: node.nodeId,
displayName: node.displayName,
platform: node.platform,
remoteIp: node.remoteIp,
tools: nodePluginTools,
});
}
refreshNodePluginTools(): void {
for (const node of this.nodesById.values()) {
this.replaceEffectiveNodePluginTools(node);
}
}
/** Register a websocket client as the current connection for its node id. */
register(client: GatewayWsClient, opts: { remoteIp?: string | undefined }) {
const connect = client.connect;
@@ -238,9 +260,9 @@ export class NodeRegistry {
typeof (connect as { pathEnv?: string }).pathEnv === "string"
? (connect as { pathEnv?: string }).pathEnv
: undefined;
const declaredNodePluginTools = this.normalizePluginToolDescriptors({
tools: Array.isArray(connect.nodePluginTools) ? connect.nodePluginTools : [],
});
const declaredNodePluginTools = Array.isArray(connect.nodePluginTools)
? [...connect.nodePluginTools]
: [];
const nodePluginTools = this.normalizePluginToolDescriptors({
tools: declaredNodePluginTools,
allowedCommands: commands,
@@ -422,23 +444,8 @@ export class NodeRegistry {
if (!node || node.connId !== connId) {
return null;
}
const declaredNodePluginTools = this.normalizePluginToolDescriptors({
tools,
});
const nodePluginTools = this.normalizePluginToolDescriptors({
tools: declaredNodePluginTools,
allowedCommands: node.commands,
});
node.declaredNodePluginTools = declaredNodePluginTools;
node.nodePluginTools = nodePluginTools;
node.client.connect.nodePluginTools = nodePluginTools;
replaceConnectedNodePluginTools({
nodeId,
displayName: node.displayName,
platform: node.platform,
remoteIp: node.remoteIp,
tools: nodePluginTools,
});
node.declaredNodePluginTools = [...tools];
this.replaceEffectiveNodePluginTools(node);
return node;
}
@@ -460,18 +467,7 @@ export class NodeRegistry {
const nextCommands = surface.commands.filter((command) => declaredCommands.has(command));
node.commands = nextCommands;
(node.client.connect as { commands?: string[] }).commands = nextCommands;
node.nodePluginTools = this.normalizePluginToolDescriptors({
tools: node.declaredNodePluginTools,
allowedCommands: nextCommands,
});
node.client.connect.nodePluginTools = node.nodePluginTools;
replaceConnectedNodePluginTools({
nodeId,
displayName: node.displayName,
platform: node.platform,
remoteIp: node.remoteIp,
tools: node.nodePluginTools,
});
this.replaceEffectiveNodePluginTools(node);
if ("caps" in surface) {
const declaredCaps = new Set(node.declaredCaps);

View File

@@ -1229,6 +1229,7 @@ export async function startGatewayServer(
);
pinActivePluginHttpRouteRegistry(pluginRegistry);
pinActivePluginChannelRegistry(pluginRegistry);
nodeRegistry.refreshNodePluginTools();
};
const refreshAttachedGatewayDiscovery = async (nextPluginRegistry: typeof pluginRegistry) => {
if (minimalTestGateway) {