fix(gateway): include export name in hook transform cache key (#13855)
Merged via /review-pr -> /prepare-pr -> /merge-pr. Prepared head SHA: a9eea919b88b33c3297620d62b38bac9cfa412bf Co-authored-by: mcaxtr <7562095+mcaxtr@users.noreply.github.com> Co-authored-by: mbelinky <132747814+mbelinky@users.noreply.github.com> Reviewed-by: @mbelinky
This commit is contained in:
@@ -13,6 +13,7 @@ Docs: https://docs.openclaw.ai
|
||||
|
||||
### Fixes
|
||||
|
||||
- Gateway/Hooks: include transform export name in hook-transform cache keys so distinct exports from the same module do not reuse the wrong cached transform function. (#13855) thanks @mcaxtr.
|
||||
- Gateway/Control UI: return 404 for missing static-asset paths instead of serving SPA fallback HTML, while preserving client-route fallback behavior for extensionless and non-asset dotted paths. (#12060) thanks @mcaxtr.
|
||||
- Gateway/Pairing: prevent device-token rotate scope escalation by enforcing an approved-scope baseline, preserving approved scopes across metadata updates, and rejecting rotate requests that exceed approved role scope implications. (#20703) thanks @coygeek.
|
||||
- Gateway/Security: require secure context and paired-device checks for Control UI auth even when `gateway.controlUi.allowInsecureAuth` is set, and align audit messaging with the hardened behavior. (#20684) thanks @coygeek.
|
||||
|
||||
@@ -294,6 +294,72 @@ describe("hooks mapping", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("caches transform functions by module path and export name", async () => {
|
||||
const configDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-hooks-export-"));
|
||||
const transformsRoot = path.join(configDir, "hooks", "transforms");
|
||||
fs.mkdirSync(transformsRoot, { recursive: true });
|
||||
const modPath = path.join(transformsRoot, "multi-export.mjs");
|
||||
fs.writeFileSync(
|
||||
modPath,
|
||||
[
|
||||
'export function transformA() { return { kind: "wake", text: "from-A" }; }',
|
||||
'export function transformB() { return { kind: "wake", text: "from-B" }; }',
|
||||
].join("\n"),
|
||||
);
|
||||
|
||||
const mappingsA = resolveHookMappings(
|
||||
{
|
||||
mappings: [
|
||||
{
|
||||
match: { path: "testA" },
|
||||
action: "agent",
|
||||
messageTemplate: "unused",
|
||||
transform: { module: "multi-export.mjs", export: "transformA" },
|
||||
},
|
||||
],
|
||||
},
|
||||
{ configDir },
|
||||
);
|
||||
|
||||
const mappingsB = resolveHookMappings(
|
||||
{
|
||||
mappings: [
|
||||
{
|
||||
match: { path: "testB" },
|
||||
action: "agent",
|
||||
messageTemplate: "unused",
|
||||
transform: { module: "multi-export.mjs", export: "transformB" },
|
||||
},
|
||||
],
|
||||
},
|
||||
{ configDir },
|
||||
);
|
||||
|
||||
const resultA = await applyHookMappings(mappingsA, {
|
||||
payload: {},
|
||||
headers: {},
|
||||
url: new URL("http://127.0.0.1:18789/hooks/testA"),
|
||||
path: "testA",
|
||||
});
|
||||
|
||||
const resultB = await applyHookMappings(mappingsB, {
|
||||
payload: {},
|
||||
headers: {},
|
||||
url: new URL("http://127.0.0.1:18789/hooks/testB"),
|
||||
path: "testB",
|
||||
});
|
||||
|
||||
expect(resultA?.ok).toBe(true);
|
||||
if (resultA?.ok && resultA.action?.kind === "wake") {
|
||||
expect(resultA.action.text).toBe("from-A");
|
||||
}
|
||||
|
||||
expect(resultB?.ok).toBe(true);
|
||||
if (resultB?.ok && resultB.action?.kind === "wake") {
|
||||
expect(resultB.action.text).toBe("from-B");
|
||||
}
|
||||
});
|
||||
|
||||
it("rejects missing message", async () => {
|
||||
const mappings = resolveHookMappings({
|
||||
mappings: [{ match: { path: "noop" }, action: "agent" }],
|
||||
|
||||
@@ -325,14 +325,15 @@ function validateAction(action: HookAction): HookMappingResult {
|
||||
}
|
||||
|
||||
async function loadTransform(transform: HookMappingTransformResolved): Promise<HookTransformFn> {
|
||||
const cached = transformCache.get(transform.modulePath);
|
||||
const cacheKey = `${transform.modulePath}::${transform.exportName ?? "default"}`;
|
||||
const cached = transformCache.get(cacheKey);
|
||||
if (cached) {
|
||||
return cached;
|
||||
}
|
||||
const url = pathToFileURL(transform.modulePath).href;
|
||||
const mod = (await import(url)) as Record<string, unknown>;
|
||||
const fn = resolveTransformFn(mod, transform.exportName);
|
||||
transformCache.set(transform.modulePath, fn);
|
||||
transformCache.set(cacheKey, fn);
|
||||
return fn;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user