* fix(slack): use truncateUtf16Safe for message body preview truncation
One .slice(0, 160) truncation site in the Slack message handler may
cut UTF-16 surrogate pairs in half when the message body preview
contains multi-byte characters such as emoji. Replace it with
truncateUtf16Safe to keep the truncated output valid Unicode.
* test(slack): cover UTF-16 preview boundary
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(browser): remove hardcoded profile="user" suggestion from browser tool description
The browser tool description hardcoded a suggestion to use profile="user"
for logged-in sessions, even when the operator has configured a different
default profile (e.g. browser.defaultProfile: "openclaw").
Replace with profile-agnostic guidance that references the configured
browser.defaultProfile setting instead of prescribing a specific value.
Fixes#102566
* test: update browser tool description assertion for profile-agnostic wording
* refactor(browser): unify tool profile guidance
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
Adds client-capability-gated tool availability: gateway clients declare
capabilities at connect (new inline-widgets cap), chat.send stamps them into
the run context, and every tool assembly path (embedded runner, queued
followups, Codex app-server harness, plugin-only construction plans) drops
tools whose requiredClientCaps the originating client did not declare. The
Canvas plugin ships the first such tool, show_widget: agents pass SVG or an
HTML fragment plus a title; the plugin hosts it as a bounded, retention-scoped
Canvas document and returns the existing canvas preview handle, which web chat
renders as a sandboxed iframe fitted to the widget's reported content height.
Widget frames never get allow-same-origin (per-preview sandbox ceiling,
including the sidebar path) and the Canvas host serves widget documents with a
CSP sandbox header so direct navigation runs in an opaque origin. Verified
live end-to-end on a Testbox with gpt-5.5 (screenshots on the PR). CLI-backed
model backends do not carry client caps yet and stay fail-closed (#102577).
Closes#101790
* fix(openai): use truncateUtf16Safe for image gen log value truncation
One .slice(0, N) truncation site in the OpenAI image generation
provider may cut UTF-16 surrogate pairs in half when the log value
contains multi-byte characters such as emoji. Replace it with
truncateUtf16Safe to keep the truncated output valid Unicode.
* fix(openai): keep image auth logs UTF-16 safe
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(msteams): surface quoted message body in Teams quote replies
Teams sends the quoted text of a 1:1 DM quote-reply in <p itemprop="preview">
(a truncated snippet), not <p itemprop="copy"> which the parser matched. The
match failed, so quoteInfo was undefined and nothing was surfaced to the agent.
Fix 1 (primary): extractMSTeamsQuoteInfo now accepts copy OR preview (prefers
copy, falls back to preview) and captures the blockquote itemid as the quoted
message id.
Fix 2 (enhancement): when the quoted message id is known, fetch the full text
via the app-only Graph endpoint GET /chats/{chatId}/messages/{id} (permitted
with Chat.Read.All, unlike the delegated /me/chats listing) and use it as the
quote body. Restricted to chats (DM + group) whose Graph chat id is a 19: id;
any failure degrades to the truncated preview so message handling never breaks.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(msteams): address review — DM-only full-text fetch, drop unsupported $select, fix mocks
Addresses ClawSweeper review on the quote-reply PR:
- Security (P1): restrict the app-only Graph full-text quote fetch to 1:1 DMs.
Previously it ran for any non-channel chat, so in a group an allowlisted
sender could quote a non-allowlisted member and the fetched full body would
bypass the supplemental-quote visibility allowlist. Group/channel quotes keep
the (now-surfaced) truncated preview from fix 1.
- Graph contract (P2): the get-chatMessage endpoint does not support OData
query params; drop the ?$select=id,body that tenants enforcing the contract
would reject (which would silently fall back to the preview).
- Tests (P1): add fetchChatMessageText to the two vi.mock(../graph-thread.js)
factories prod now touches, and add a group-chat regression test proving the
Graph full-text fetch does not fire for group quotes.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* docs(msteams): add redacted real Teams quote-reply proof screenshots
Real-behavior evidence for the quote-reply fix (personal names + avatars
redacted): a 1:1 DM where the bot now reads back the full quoted message.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* docs(msteams): remove committed proof screenshots from branch
Proof media should live as external PR artifacts, not in the product tree.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* test(msteams): bound and prove quote enrichment
* test(msteams): use valid open DM fixture
---------
Co-authored-by: Yash Inani <yashinani@Yashs-MacBook-Pro.local>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(plugin-sdk): update ssrf-runtime import path in cdp-proxy-bypass.ts
The import path `openclaw/plugin-sdk/ssrf-runtime-internal` was renamed
to `openclaw/plugin-sdk/ssrf-runtime` in a previous version, but
cdp-proxy-bypass.ts still referenced the old path. The neighbouring
cdp.helpers.ts already uses the correct path. This caused the browser
plugin to fail to load with "Package subpath is not defined by exports".
Fixes#96639
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(plugin-sdk): update ssrf-runtime import path in cdp-proxy-bypass
The import path `openclaw/plugin-sdk/ssrf-runtime-internal` was renamed
to `openclaw/plugin-sdk/ssrf-runtime` in a previous version, but
cdp-proxy-bypass.ts still referenced the old path. The neighbouring
cdp.helpers.ts already uses the correct path. This caused the browser
plugin to fail to load with "Package subpath is not defined by exports".
Update the corresponding vi.mock path in the test file to match.
Fixes#96639
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(plugin-sdk): promote managed-proxy bypass exports to public ssrf-runtime
Export registerManagedProxyBrowserCdpBypass and fetchConfiguredLocalOriginWithSsrFGuard
from the public ssrf-runtime path so that the browser plugin and ollama embedding
provider can lazy-load without hitting the private ssrf-runtime-internal subpath
that is excluded from package.json exports.
Fixes#96639
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(build): bundle private SSRF runtime for packaged plugins
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(inworld): use truncateUtf16Safe for error body and parse error truncation
Two .slice(0, N) truncation sites in the Inworld TTS extension may
cut UTF-16 surrogate pairs in half when the error body or parse error
message contains multi-byte characters such as emoji. Replace both
with truncateUtf16Safe to keep the truncated output valid Unicode.
* test(inworld): cover UTF-16-safe TTS errors
Co-authored-by: lizeyu-xydt <li.zeyu@xydigit.com>
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(browser): preserve strict CDP discovery policy on target-list lookup
findPageByTargetIdViaTargetList fetched the CDP /json/list endpoint without
passing an ssrfPolicy argument, so the caller's discovery policy was dropped at
the fetch layer while the sibling tryTerminateExecutionViaCdp scoped it. Build
the scoped control policy with scopeCdpPolicyToConfiguredEndpoint and pass it to
fetchJson, matching the sibling lookup path.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test(browser): tighten target-list CDP policy test comment
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test(browser): consolidate target-list SSRF coverage
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(ollama): use truncateUtf16Safe for malformed NDJSON log warning
The parseNdjsonStream function uses naive .slice(0, 120) on malformed
NDJSON data in log warnings which can split surrogate pairs. Replace
with truncateUtf16Safe().
* test(ollama): cover UTF-16-safe NDJSON warnings
* test(ollama): satisfy lint in NDJSON coverage
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(parallel): use truncateUtf16Safe for MCP error JSON truncation
The extractMcpToolPayload function and error messages use naive
.slice(0, 500) on JSON-serialized errors and results which can split
surrogate pairs. Replace with truncateUtf16Safe().
* test(parallel): cover UTF-16-safe MCP errors
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(telegram): use truncateUtf16Safe for raw update log truncation
* refactor(telegram): own raw log bounds in formatter
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(active-memory): use truncateUtf16Safe for log value truncation
* fix(active-memory): use truncateUtf16Safe for log value truncation
* test(active-memory): cover UTF-16-safe log limits
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(tts-local-cli): handle stdout/stderr stream errors in speech provider
CLI TTS speech provider spawns a child process to generate audio and
registers stdout/stderr data listeners but omits stream error handlers.
stdout carries synthesized audio data. A pipe error mid-generation must
reject the promise so the caller does not silently receive truncated
audio when the child later exits zero. stderr carries diagnostic logs
only — errors there are benign and should not crash the provider.
Apply separate strategies matching vincentkoc's requirement:
- stdout (audio): error → reject(Promise) — surface the failure
- stderr (diagnostic): error → ignore — does not affect audio output
* fix lint: remove unused signal param from mock kill()
* fix(tts-local-cli): contain child stream failures
Co-authored-by: 赵旺0668001248 <zhao.wang1@xydigit.com>
* chore(tts-local-cli): keep release notes in PR body
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(diagnostics-prometheus): use truncateUtf16Safe for error message truncation
The safeErrorMessage function uses naive .slice(0, 500) on error messages
which can split surrogate pairs. Replace with truncateUtf16Safe().
* test(diagnostics-prometheus): cover UTF-16-safe errors
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(codex-supervisor): use truncateUtf16Safe for stderr tail truncation
StdioCodexJsonRpcConnection includes stderr output in the transport-closed
error message with a naive .slice(0, 1200) which can split surrogate pairs.
Replace with truncateUtf16Safe().
* test(codex-supervisor): cover UTF-16-safe stderr tails
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(codex): use truncateUtf16Safe for tool transcript output truncation
Replace naive .slice(0, N) with truncateUtf16Safe() in event-projector
tool transcript output text to prevent surrogate pair splitting.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(codex): keep streamed output utf16-safe
* refactor(codex): use plugin sdk text runtime
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(oc-path): use truncateUtf16Safe for error message path truncation
Replace naive .slice(0, 80) with truncateUtf16Safe() at three
error-reporting call sites to prevent surrogate pair splitting
in oc:// path validation error messages.
Co-Authored-By: Claude <noreply@anthropic.com>
* test(oc-path): cover utf16 error truncation
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(discord): use truncateUtf16Safe for deploy error body truncation
* test(discord): cover utf16 deploy errors
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>