upgrade tool extension hook to generic get_tool_message_handler

Replace the restrictive `get_tool_message_badge` extension hook with a generic `get_tool_message_handler` hook. This allows plugins to provide a full handler function for their specific `type === "tool"` rows. Plugins can now completely take over rendering of their tool rows, or they can import and delegate to `drawMessageToolSimple` if they simply want the standard UI with a custom badge. Updated the `_browser_agent` plugin to use the new hook and documented the change in `AGENTS.plugins.md`. feat: add get_tool_message_badge plugin hook Add a WebUI extension point `get_tool_message_badge` so plugins can set short tool-row badge codes (`code`) and optional `displayKvps` after core handling for built-in tools (skills, vision, search, memory). Core `drawMessageTool` is now async and calls `callJsExtensions("get_tool_message_badge", …)` on the fallback path. Remove the hardcoded `browser_agent` → `WWW` branch from `messages.js`. The `_browser_agent` plugin registers `WWW` via `extensions/webui/get_tool_message_badge/browser-tool-badge.js`. Document the hook in `docs/agents/AGENTS.plugins.md`. With the plugin disabled, `browser_agent` tool rows no longer get a `WWW` badge from core (generic tool row).

Alessandro committed Mar 23, 2026 at 15:18 UTC 338c86a05811c5d3a1066e453156663b337d2f73
3 files changed +31 -6
docs/agents/AGENTS.plugins.md
+2
@@ -168,6 +168,8 @@ Place *.js files in extensions/webui/<extension_point>/ and export a default asy
168
169 Core JS hooks can also expose runtime UI surfaces when static HTML breakpoints are not a fit. For example, `confirm_dialog_after_render` runs after the shared confirm dialog is built and receives the rendered dialog/body/footer nodes plus any caller-provided `extensionContext`.
170
171 +For tool chat rows (`type === "tool"`), after built-in badge rules, core calls `get_tool_message_handler` with a mutable object containing `tool_name`, `kvps`, and `handler`. Plugins can set `handler` to entirely take over rendering for their `_tool_name`. A plugin can import `drawMessageToolSimple` and call it internally (e.g., passing `{ ...args, code: "WWW" }`) if it just wants standard tool row styling with a custom badge.
172 +
173 ### User Feedback: Notifications, Not Inline Errors
174 Plugin UI must use the **A0 notification system** for errors, success, and warnings. Do not render dedicated error/success boxes (e.g. a red block bound to `store.error`). Use the notification store so toasts and notification history stay consistent across the app.
175
plugins/_browser_agent/extensions/webui/get_tool_message_handler/browser-tool-handler.js new
+15
@@ -0,0 +1,15 @@
1 +import { drawMessageToolSimple } from "/js/messages.js";
2 +
3 +/**
4 + * Registers the browser_agent tool message handler to set the custom badge.
5 + * @param {object} extData
6 + */
7 +export default async function registerBrowserToolHandler(extData) {
8 + if (extData?.tool_name === "browser_agent") {
9 + extData.handler = drawBrowserTool;
10 + }
11 +}
12 +
13 +function drawBrowserTool(args) {
14 + return drawMessageToolSimple({ ...args, code: "WWW" });
15 +}
webui/js/messages.js
+14 -6
@@ -1122,9 +1122,9 @@ export function drawMessageUser({
1122
1123 /**
1124 * @param {MessageHandlerArgs & Record<string, any>} param0
1125 - * @returns {MessageHandlerResult}
1125 + * @returns {Promise<MessageHandlerResult>}
1126 */
1127 -export function drawMessageTool({
1127 +export async function drawMessageTool({
1128 id,
1129 type,
1130 heading,
@@ -1146,13 +1146,21 @@ export function drawMessageTool({
1146 return drawMessageToolSimple({ ...arguments[0], code: "EYE" });
1147 } else if (kvps._tool_name === "search_engine") {
1148 return drawMessageToolSimple({ ...arguments[0], code: "WEB" });
1149 - } else if (kvps._tool_name === "browser_agent") {
1150 - return drawMessageToolSimple({ ...arguments[0], code: "WWW" });
1149 } else if (kvps._tool_name.startsWith("memory_")) {
1150 return drawMessageToolSimple({ ...arguments[0], code: "MEM" });
1153 - } else {
1154 - return drawMessageToolSimple({ ...arguments[0] });
1151 }
1152 +
1153 + /** @type {{ tool_name: string, kvps: any, handler: Function | undefined }} */
1154 + const extData = {
1155 + tool_name,
1156 + kvps,
1157 + handler: undefined,
1158 + };
1159 + await callJsExtensions("get_tool_message_handler", extData);
1160 + if (typeof extData.handler === "function") {
1161 + return extData.handler(arguments[0]);
1162 + }
1163 + return drawMessageToolSimple({ ...arguments[0] });
1164 }
1165
1166 /**