diff --git a/docs/.generated/plugin-sdk-api-baseline.sha256 b/docs/.generated/plugin-sdk-api-baseline.sha256
index d28e49ffd824..1d6e724edca5 100644
--- a/docs/.generated/plugin-sdk-api-baseline.sha256
+++ b/docs/.generated/plugin-sdk-api-baseline.sha256
@@ -1,2 +1,2 @@
-5843643ebc635ab35cf50668e0e1a14ec599f5d297bfb0bd0e019048caf425f0 plugin-sdk-api-baseline.json
-2102fd06e7da61320aa6a1113a2227f6aeca64444cb7a8c4a3a7bc4886957845 plugin-sdk-api-baseline.jsonl
+5ddfbeaef2941a299f3677c7f8dd951f112f80ceefef7c6cbd8e4b1d5ec0670f plugin-sdk-api-baseline.json
+df6272053970ab3c15f586f7a19c2abe0c9709b8938187e1ec0a85368e5ccd6c plugin-sdk-api-baseline.jsonl
diff --git a/docs/plugins/sdk-subpaths.md b/docs/plugins/sdk-subpaths.md
index 1381d3460971..ba60b100f94d 100644
--- a/docs/plugins/sdk-subpaths.md
+++ b/docs/plugins/sdk-subpaths.md
@@ -336,6 +336,7 @@ usage endpoint failed or returned no usable usage data.
| `plugin-sdk/session-binding-runtime` | Current conversation binding state without configured binding routing or pairing stores |
| `plugin-sdk/context-visibility-runtime` | Context visibility resolution and supplemental context filtering without broad config/security imports |
| `plugin-sdk/string-coerce-runtime` | Narrow primitive record/string coercion and normalization helpers without markdown/logging imports |
+ | `plugin-sdk/text-utility-runtime` | Low-level text and path helpers, including five-entity HTML escaping |
| `plugin-sdk/host-runtime` | Hostname and SCP host normalization helpers |
| `plugin-sdk/retry-runtime` | Retry config and retry runner helpers |
| `plugin-sdk/agent-runtime` | Deprecated broad barrel for agent dir/identity/workspace helpers, including `resolveAgentDir`, `resolveDefaultAgentDir`, and the deprecated `resolveOpenClawAgentDir` compatibility export; prefer focused agent/runtime subpaths |
diff --git a/extensions/canvas/src/documents.ts b/extensions/canvas/src/documents.ts
index 2e2438a85ae3..e8d1c9f2ef87 100644
--- a/extensions/canvas/src/documents.ts
+++ b/extensions/canvas/src/documents.ts
@@ -7,7 +7,7 @@ import fs from "node:fs/promises";
import path from "node:path";
import { root as fsRoot, sanitizeUntrustedFileName } from "openclaw/plugin-sdk/security-runtime";
import { resolveStateDir } from "openclaw/plugin-sdk/state-paths";
-import { resolveUserPath } from "openclaw/plugin-sdk/text-utility-runtime";
+import { escapeHtml, resolveUserPath } from "openclaw/plugin-sdk/text-utility-runtime";
import { CANVAS_HOST_PATH } from "./host/a2ui.js";
type CanvasDocumentKind = "html_bundle" | "url_embed" | "document" | "image" | "video_asset";
@@ -72,15 +72,6 @@ function buildPdfWrapper(url: string): string {
return `
`;
}
-function escapeHtml(value: string): string {
- return value
- .replaceAll("&", "&")
- .replaceAll("<", "<")
- .replaceAll(">", ">")
- .replaceAll('"', """)
- .replaceAll("'", "'");
-}
-
function normalizeLogicalPath(value: string): string {
const normalized = value.replaceAll("\\", "/").replace(/^\/+/, "");
const parts = normalized.split("/").filter(Boolean);
diff --git a/extensions/canvas/src/widget-tool.ts b/extensions/canvas/src/widget-tool.ts
index ed466b2fe538..29df1d60fb57 100644
--- a/extensions/canvas/src/widget-tool.ts
+++ b/extensions/canvas/src/widget-tool.ts
@@ -3,6 +3,7 @@ import { createHash } from "node:crypto";
import { jsonResult, readStringParam } from "openclaw/plugin-sdk/channel-actions";
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";
import type { AnyAgentTool } from "openclaw/plugin-sdk/plugin-entry";
+import { escapeHtml } from "openclaw/plugin-sdk/text-utility-runtime";
import { resolveCanvasHostConfig } from "./config.js";
import { createCanvasDocument } from "./documents.js";
import { SHOW_WIDGET_REQUIRED_CLIENT_CAPS, ShowWidgetToolSchema } from "./tool-schema.js";
@@ -24,15 +25,6 @@ class WidgetToolInputError extends Error {
}
}
-function escapeHtml(value: string): string {
- return value
- .replaceAll("&", "&")
- .replaceAll("<", "<")
- .replaceAll(">", ">")
- .replaceAll('"', """)
- .replaceAll("'", "'");
-}
-
function buildWidgetDocument(title: string, widgetCode: string): string {
const isSvg = /^