From da46e86951e26089d0c9b217fa9dc8443ad44dfb Mon Sep 17 00:00:00 2001 From: scotthuang Date: Sun, 15 Mar 2026 17:16:52 +0800 Subject: [PATCH] fix: auto-namespace messageMeta by pluginId in hook runner instead of plugin self-wrapping --- extensions/memory-lancedb/index.ts | 14 ++++++------ src/plugins/hooks.ts | 34 +++++++++++++++++++++++++----- src/plugins/types.ts | 11 +++++----- 3 files changed, 40 insertions(+), 19 deletions(-) diff --git a/extensions/memory-lancedb/index.ts b/extensions/memory-lancedb/index.ts index 798333b34fe..81c94e69f3d 100644 --- a/extensions/memory-lancedb/index.ts +++ b/extensions/memory-lancedb/index.ts @@ -565,14 +565,12 @@ const memoryPlugin = { results.map((r) => ({ category: r.entry.category, text: r.entry.text })), ), messageMeta: { - "memory-lancedb": { - displayStripPatterns: [ - { - regex: - "<\\s*relevant[-_]memories\\b[^>]*>[\\s\\S]*?<\\s*/\\s*relevant[-_]memories\\s*>\\s*", - }, - ], - }, + displayStripPatterns: [ + { + regex: + "<\\s*relevant[-_]memories\\b[^>]*>[\\s\\S]*?<\\s*/\\s*relevant[-_]memories\\s*>\\s*", + }, + ], }, }; } catch (err) { diff --git a/src/plugins/hooks.ts b/src/plugins/hooks.ts index 70e45e9bda2..b5099fcb3a0 100644 --- a/src/plugins/hooks.ts +++ b/src/plugins/hooks.ts @@ -121,11 +121,12 @@ function getHooksForName( } /** - * Deep-merge two messageMeta bags. Top-level keys are plugin-namespaced - * (e.g. `"memory-lancedb"`) and each plugin's value is an object whose - * array-valued keys are concatenated so multiple hook phases can each - * contribute entries without overwriting each other. Scalar keys inside a - * plugin namespace use last-wins semantics. + * Deep-merge two messageMeta bags. Top-level keys are plugin IDs + * (auto-injected by the hook runner via {@link namespaceMessageMeta}) + * and each plugin's value is an object whose array-valued keys are + * concatenated so multiple hook phases can each contribute entries + * without overwriting each other. Scalar keys inside a plugin + * namespace use last-wins semantics. */ function mergeMessageMeta( acc: Record | undefined, @@ -170,6 +171,24 @@ function mergeMessageMeta( return merged; } +/** + * Wrap a handler result's `messageMeta` inside a `{ [pluginId]: ... }` envelope. + * This is called by the hook runner **before** merging so that each plugin's + * meta is isolated under its own key — a plugin returning `{ displayStripPatterns: [...] }` + * becomes `{ "memory-lancedb": { displayStripPatterns: [...] } }` automatically. + * Plugins cannot write into another plugin's namespace because the runner + * controls the wrapping. + */ +function namespaceMessageMeta( + result: { messageMeta?: Record } | undefined | null, + pluginId: string, +): void { + if (!result?.messageMeta || Object.keys(result.messageMeta).length === 0) { + return; + } + result.messageMeta = { [pluginId]: result.messageMeta }; +} + /** * Create a hook runner for a specific registry. */ @@ -300,6 +319,11 @@ export function createHookRunner(registry: PluginRegistry, options: HookRunnerOp )(event, ctx); if (handlerResult !== undefined && handlerResult !== null) { + // Auto-namespace messageMeta by pluginId so plugins cannot write + // into another plugin's namespace. The plugin returns flat meta + // (e.g. { displayStripPatterns: [...] }) and we wrap it as + // { [pluginId]: { displayStripPatterns: [...] } }. + namespaceMessageMeta(handlerResult, hook.pluginId); if (mergeResults && result !== undefined) { result = mergeResults(result, handlerResult); } else { diff --git a/src/plugins/types.ts b/src/plugins/types.ts index adccee11dd9..dc21bc51733 100644 --- a/src/plugins/types.ts +++ b/src/plugins/types.ts @@ -544,12 +544,11 @@ export type PluginHookBeforePromptBuildResult = { appendSystemContext?: string; /** * Generic key-value bag for plugins to attach metadata to the persisted - * user message. Top-level keys are namespaced by plugin ID (e.g. - * `"memory-lancedb"`) so each plugin's data is isolated from others. - * - * Example: memory-lancedb stores `displayStripPatterns` under its own - * namespace: `{ "memory-lancedb": { displayStripPatterns: [...] } }`. - * The UI reads only `messageMeta["memory-lancedb"]` to apply strip patterns. + * user message. Plugins return flat meta (e.g. `{ displayStripPatterns: [...] }`) + * and the hook runner automatically wraps it under the plugin's ID + * (e.g. `{ "memory-lancedb": { displayStripPatterns: [...] } }`). + * This ensures each plugin's data is isolated — a plugin cannot write + * into another plugin's namespace. */ messageMeta?: Record; };