mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-02 14:17:14 +00:00
Gateway/Hooks: key transform cache by export openclaw#13855 thanks @mcaxtr
This commit is contained in:
@@ -13,6 +13,7 @@ Docs: https://docs.openclaw.ai
|
|||||||
|
|
||||||
### Fixes
|
### 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/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/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.
|
- 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 () => {
|
it("rejects missing message", async () => {
|
||||||
const mappings = resolveHookMappings({
|
const mappings = resolveHookMappings({
|
||||||
mappings: [{ match: { path: "noop" }, action: "agent" }],
|
mappings: [{ match: { path: "noop" }, action: "agent" }],
|
||||||
|
|||||||
@@ -325,14 +325,15 @@ function validateAction(action: HookAction): HookMappingResult {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function loadTransform(transform: HookMappingTransformResolved): Promise<HookTransformFn> {
|
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) {
|
if (cached) {
|
||||||
return cached;
|
return cached;
|
||||||
}
|
}
|
||||||
const url = pathToFileURL(transform.modulePath).href;
|
const url = pathToFileURL(transform.modulePath).href;
|
||||||
const mod = (await import(url)) as Record<string, unknown>;
|
const mod = (await import(url)) as Record<string, unknown>;
|
||||||
const fn = resolveTransformFn(mod, transform.exportName);
|
const fn = resolveTransformFn(mod, transform.exportName);
|
||||||
transformCache.set(transform.modulePath, fn);
|
transformCache.set(cacheKey, fn);
|
||||||
return fn;
|
return fn;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user