Skip to content

Commit 60ab3dd

Browse files
author
ai-hpc
committed
perf(plugins): reuse compatible gateway startup registry
1 parent c81271e commit 60ab3dd

3 files changed

Lines changed: 233 additions & 46 deletions

File tree

src/plugins/loader.runtime-registry.test.ts

Lines changed: 66 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -368,6 +368,9 @@ describe("getCompatibleActivePluginRegistry", () => {
368368
config: startupOptions.config,
369369
workspaceDir: "/tmp/workspace-a",
370370
onlyPluginIds: ["acpx", "telegram"],
371+
runtimeOptions: {
372+
allowGatewaySubagentBinding: true,
373+
},
371374
}),
372375
).toBe(registry);
373376
});
@@ -501,29 +504,87 @@ describe("getCompatibleActivePluginRegistry", () => {
501504
{ id: "telegram" } as (typeof registry.plugins)[number],
502505
);
503506
registry.coreGatewayMethodNames = ["sessions.get", "sessions.list"];
507+
const config = {
508+
plugins: {
509+
allow: ["acpx", "telegram"],
510+
},
511+
};
504512
const startupOptions = {
505-
config: {
506-
plugins: {
507-
allow: ["acpx", "telegram"],
508-
},
513+
config,
514+
activationSourceConfig: config,
515+
autoEnabledReasons: {},
516+
workspaceDir: "/tmp/workspace-a",
517+
onlyPluginIds: ["acpx", "telegram"],
518+
coreGatewayMethodNames: ["sessions.get", "sessions.list"],
519+
runtimeOptions: {
520+
allowGatewaySubagentBinding: true,
521+
},
522+
};
523+
const { cacheKey } = testing.resolvePluginLoadCacheContext(startupOptions);
524+
setActivePluginRegistry(registry, cacheKey, "gateway-bindable");
525+
526+
expect(
527+
testing.getCompatibleActivePluginRegistry({
528+
config,
529+
workspaceDir: "/tmp/workspace-a",
530+
onlyPluginIds: ["acpx", "telegram"],
531+
}),
532+
).toBe(registry);
533+
});
534+
535+
it("reuses a scoped gateway startup registry when dispatch omits built artifact preference", () => {
536+
const registry = createEmptyPluginRegistry();
537+
registry.plugins.push(
538+
{ id: "acpx" } as (typeof registry.plugins)[number],
539+
{ id: "telegram" } as (typeof registry.plugins)[number],
540+
);
541+
registry.coreGatewayMethodNames = ["sessions.get", "sessions.list"];
542+
const config = {
543+
plugins: {
544+
allow: ["acpx", "telegram"],
509545
},
546+
};
547+
const startupOptions = {
548+
config,
549+
activationSourceConfig: config,
550+
autoEnabledReasons: {},
510551
workspaceDir: "/tmp/workspace-a",
511552
onlyPluginIds: ["acpx", "telegram"],
512553
coreGatewayMethodNames: ["sessions.get", "sessions.list"],
513554
runtimeOptions: {
514555
allowGatewaySubagentBinding: true,
515556
},
557+
preferBuiltPluginArtifacts: true,
516558
};
517559
const { cacheKey } = testing.resolvePluginLoadCacheContext(startupOptions);
518560
setActivePluginRegistry(registry, cacheKey, "gateway-bindable");
519561

520562
expect(
521563
testing.getCompatibleActivePluginRegistry({
522-
config: startupOptions.config,
564+
config,
523565
workspaceDir: "/tmp/workspace-a",
524566
onlyPluginIds: ["acpx", "telegram"],
567+
runtimeOptions: {
568+
allowGatewaySubagentBinding: true,
569+
},
525570
}),
526571
).toBe(registry);
572+
573+
expect(
574+
testing.getCompatibleActivePluginRegistry({
575+
config: {
576+
plugins: {
577+
allow: ["acpx", "telegram"],
578+
load: { paths: ["/tmp/changed.js"] },
579+
},
580+
},
581+
workspaceDir: "/tmp/workspace-a",
582+
onlyPluginIds: ["acpx", "telegram"],
583+
runtimeOptions: {
584+
allowGatewaySubagentBinding: true,
585+
},
586+
}),
587+
).toBeUndefined();
527588
});
528589
});
529590

src/plugins/loader.ts

Lines changed: 51 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1226,81 +1226,91 @@ function getCompatibleActivePluginRegistry(
12261226
activeCacheKey,
12271227
);
12281228
};
1229+
const matchesCompatibleActiveRegistry = (candidate: PluginLoadOptions): boolean => {
1230+
if (matchesActiveCacheKey(candidate)) {
1231+
return true;
1232+
}
1233+
if (
1234+
scopedPluginLoadOptionsMatchWiderActiveCacheKey(candidate, activeCacheKey, activeRegistry)
1235+
) {
1236+
return true;
1237+
}
1238+
return pluginToolDiscoveryOptionsMatchActiveCacheKey(candidate, activeCacheKey);
1239+
};
12291240

1230-
if (matchesActiveCacheKey(options)) {
1231-
return activeRegistry;
1232-
}
1233-
if (scopedPluginLoadOptionsMatchWiderActiveCacheKey(options, activeCacheKey, activeRegistry)) {
1241+
if (matchesCompatibleActiveRegistry(options)) {
12341242
return activeRegistry;
12351243
}
12361244
if (!loadContext.shouldActivate) {
12371245
const activatingOptions = {
12381246
...options,
12391247
activate: true,
12401248
};
1241-
if (matchesActiveCacheKey(activatingOptions)) {
1249+
if (matchesCompatibleActiveRegistry(activatingOptions)) {
12421250
return activeRegistry;
12431251
}
1244-
if (
1245-
scopedPluginLoadOptionsMatchWiderActiveCacheKey(
1246-
activatingOptions,
1247-
activeCacheKey,
1248-
activeRegistry,
1249-
)
1250-
) {
1252+
}
1253+
const activeRuntimeSubagentMode = getActivePluginRuntimeSubagentMode();
1254+
if (activeRuntimeSubagentMode === "gateway-bindable") {
1255+
const gatewayStartupOptions: PluginLoadOptions = {
1256+
...options,
1257+
preferBuiltPluginArtifacts: true,
1258+
};
1259+
if (matchesCompatibleActiveRegistry(gatewayStartupOptions)) {
12511260
return activeRegistry;
12521261
}
1253-
}
1254-
if (pluginToolDiscoveryOptionsMatchActiveCacheKey(options, activeCacheKey)) {
1255-
return activeRegistry;
1262+
if (!loadContext.shouldActivate) {
1263+
const activatingGatewayStartupOptions: PluginLoadOptions = {
1264+
...options,
1265+
activate: true,
1266+
preferBuiltPluginArtifacts: true,
1267+
};
1268+
if (matchesCompatibleActiveRegistry(activatingGatewayStartupOptions)) {
1269+
return activeRegistry;
1270+
}
1271+
}
12561272
}
12571273
if (
12581274
loadContext.runtimeSubagentMode === "default" &&
1259-
getActivePluginRuntimeSubagentMode() === "gateway-bindable"
1275+
activeRuntimeSubagentMode === "gateway-bindable"
12601276
) {
1261-
const gatewayBindableOptions = {
1277+
const gatewayBindableOptions: PluginLoadOptions = {
12621278
...options,
12631279
runtimeOptions: {
12641280
...options.runtimeOptions,
12651281
allowGatewaySubagentBinding: true,
12661282
},
12671283
};
1268-
if (matchesActiveCacheKey(gatewayBindableOptions)) {
1269-
return activeRegistry;
1270-
}
1271-
if (
1272-
scopedPluginLoadOptionsMatchWiderActiveCacheKey(
1273-
gatewayBindableOptions,
1274-
activeCacheKey,
1275-
activeRegistry,
1276-
)
1277-
) {
1278-
return activeRegistry;
1279-
}
1280-
if (pluginToolDiscoveryOptionsMatchActiveCacheKey(gatewayBindableOptions, activeCacheKey)) {
1281-
return activeRegistry;
1282-
}
1284+
const gatewayStartupOptions: PluginLoadOptions = {
1285+
...gatewayBindableOptions,
1286+
preferBuiltPluginArtifacts: true,
1287+
};
12831288
if (!loadContext.shouldActivate) {
1284-
const activatingGatewayBindableOptions = {
1289+
const activatingGatewayBindableOptions: PluginLoadOptions = {
12851290
...options,
12861291
activate: true,
12871292
runtimeOptions: {
12881293
...options.runtimeOptions,
12891294
allowGatewaySubagentBinding: true,
12901295
},
12911296
};
1292-
if (matchesActiveCacheKey(activatingGatewayBindableOptions)) {
1293-
return activeRegistry;
1294-
}
1297+
const activatingGatewayStartupOptions: PluginLoadOptions = {
1298+
...activatingGatewayBindableOptions,
1299+
preferBuiltPluginArtifacts: true,
1300+
};
12951301
if (
1296-
scopedPluginLoadOptionsMatchWiderActiveCacheKey(
1297-
activatingGatewayBindableOptions,
1298-
activeCacheKey,
1299-
activeRegistry,
1300-
)
1302+
matchesCompatibleActiveRegistry(gatewayBindableOptions) ||
1303+
matchesCompatibleActiveRegistry(gatewayStartupOptions) ||
1304+
matchesCompatibleActiveRegistry(activatingGatewayBindableOptions) ||
1305+
matchesCompatibleActiveRegistry(activatingGatewayStartupOptions)
13011306
) {
13021307
return activeRegistry;
13031308
}
1309+
} else if (
1310+
matchesCompatibleActiveRegistry(gatewayBindableOptions) ||
1311+
matchesCompatibleActiveRegistry(gatewayStartupOptions)
1312+
) {
1313+
return activeRegistry;
13041314
}
13051315
}
13061316
return undefined;
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
2+
import { clearPluginLoaderCache, testing } from "../loader.js";
3+
import { createEmptyPluginRegistry } from "../registry-empty.js";
4+
import type { PluginRegistry } from "../registry-types.js";
5+
import { resetPluginRuntimeStateForTest, setActivePluginRegistry } from "../runtime.js";
6+
7+
const loaderMocks = vi.hoisted(() => ({
8+
loadOpenClawPlugins: vi.fn<typeof import("../loader.js").loadOpenClawPlugins>(),
9+
}));
10+
11+
vi.mock("../loader.js", async (importOriginal) => {
12+
const actual = await importOriginal<typeof import("../loader.js")>();
13+
return {
14+
...actual,
15+
loadOpenClawPlugins: (...args: Parameters<typeof loaderMocks.loadOpenClawPlugins>) =>
16+
loaderMocks.loadOpenClawPlugins(...args),
17+
};
18+
});
19+
20+
const { ensureStandaloneRuntimePluginRegistryLoaded } =
21+
await import("./standalone-runtime-registry-loader.js");
22+
23+
function createRegistryWithPlugin(pluginId: string): PluginRegistry {
24+
const registry = createEmptyPluginRegistry();
25+
registry.plugins.push({
26+
id: pluginId,
27+
status: "loaded",
28+
} as never);
29+
return registry;
30+
}
31+
32+
beforeEach(() => {
33+
loaderMocks.loadOpenClawPlugins.mockReset();
34+
});
35+
36+
afterEach(() => {
37+
clearPluginLoaderCache();
38+
resetPluginRuntimeStateForTest();
39+
});
40+
41+
describe("ensureStandaloneRuntimePluginRegistryLoaded", () => {
42+
it("reuses a compatible gateway startup registry for gateway-bindable dispatch load options", () => {
43+
const activeRegistry = createRegistryWithPlugin("telegram");
44+
activeRegistry.coreGatewayMethodNames = ["sessions.get", "sessions.list"];
45+
const config = { plugins: { allow: ["telegram"] } };
46+
const startupLoadOptions = {
47+
config,
48+
activationSourceConfig: config,
49+
autoEnabledReasons: {},
50+
workspaceDir: "/tmp/ws",
51+
onlyPluginIds: ["telegram"],
52+
coreGatewayMethodNames: ["sessions.get", "sessions.list"],
53+
runtimeOptions: {
54+
allowGatewaySubagentBinding: true,
55+
},
56+
preferBuiltPluginArtifacts: true,
57+
};
58+
const { cacheKey } = testing.resolvePluginLoadCacheContext(startupLoadOptions);
59+
setActivePluginRegistry(activeRegistry, cacheKey, "gateway-bindable", "/tmp/ws");
60+
61+
const result = ensureStandaloneRuntimePluginRegistryLoaded({
62+
loadOptions: {
63+
config,
64+
onlyPluginIds: ["telegram"],
65+
runtimeOptions: {
66+
allowGatewaySubagentBinding: true,
67+
},
68+
workspaceDir: "/tmp/ws",
69+
},
70+
});
71+
72+
expect(result).toBe(activeRegistry);
73+
expect(loaderMocks.loadOpenClawPlugins).not.toHaveBeenCalled();
74+
});
75+
76+
it("loads a fresh registry when dispatch config is not startup-compatible", () => {
77+
const activeRegistry = createRegistryWithPlugin("telegram");
78+
activeRegistry.coreGatewayMethodNames = ["sessions.get", "sessions.list"];
79+
const config = { plugins: { allow: ["telegram"] } };
80+
const startupLoadOptions = {
81+
config,
82+
activationSourceConfig: config,
83+
autoEnabledReasons: {},
84+
workspaceDir: "/tmp/ws",
85+
onlyPluginIds: ["telegram"],
86+
coreGatewayMethodNames: ["sessions.get", "sessions.list"],
87+
runtimeOptions: {
88+
allowGatewaySubagentBinding: true,
89+
},
90+
preferBuiltPluginArtifacts: true,
91+
};
92+
const { cacheKey } = testing.resolvePluginLoadCacheContext(startupLoadOptions);
93+
setActivePluginRegistry(activeRegistry, cacheKey, "gateway-bindable", "/tmp/ws");
94+
const loadedRegistry = createRegistryWithPlugin("telegram");
95+
loaderMocks.loadOpenClawPlugins.mockReturnValue(loadedRegistry);
96+
97+
const result = ensureStandaloneRuntimePluginRegistryLoaded({
98+
loadOptions: {
99+
config: {
100+
plugins: {
101+
allow: ["telegram"],
102+
load: { paths: ["/tmp/changed.js"] },
103+
},
104+
},
105+
onlyPluginIds: ["telegram"],
106+
runtimeOptions: {
107+
allowGatewaySubagentBinding: true,
108+
},
109+
workspaceDir: "/tmp/ws",
110+
},
111+
});
112+
113+
expect(result).toBe(loadedRegistry);
114+
expect(loaderMocks.loadOpenClawPlugins).toHaveBeenCalledOnce();
115+
});
116+
});

0 commit comments

Comments
 (0)