* fix(imessage): cap per-chat group-allowlist warn-once cache
Replace unbounded perChatWarned Set with createDedupeCache(maxSize=512)
to keep long-running iMessage monitor memory stable. The cache grows
with every distinct group chat the gateway sees; without a cap it can
accumulate entries indefinitely.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(imessage): prove warning cache eviction
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* feat(usage): route claude-cli to Anthropic plan usage with plan label and billing
* feat(webchat): redesign context popover with plan-usage bars and API-cost gating
* fix(usage): match plan usage across CLI provider aliases and source plan label from synced auth profile
* docs(plugins): document plan metadata on usage auth token
* chore(usage): document provider-level billing attribution limits, fix overview test types
* fix(webchat): avoid map-spread in quota group collection
* test(webchat): deflake subscription popover reset fixture
* fix(googlechat): add 30 s request timeout to withGoogleChatResponse
* fix(googlechat): satisfy ResolvedGoogleChatAccount in api.test.ts
* fix(googlechat): bound control and media requests
Co-authored-by: NIO <nocodet@mail.com>
---------
Co-authored-by: NIO <nocodet@mail.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(codex-dynamic-tools): use truncateUtf16Safe for notice text truncation
* fix(codex): keep dynamic tool text UTF-16 safe
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(google-meet): use truncateUtf16Safe for log value truncation
One .slice(0, 180) truncation site in the Google Meet extension
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.
* test(google-meet): cover UTF-16 model log boundary
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(migrate-claude): use truncateUtf16Safe for skill description truncation
One .slice(0, 180) truncation site in the migrate-claude extension
may cut UTF-16 surrogate pairs in half when the skill description
contains multi-byte characters such as emoji. Replace it with
truncateUtf16Safe to keep the truncated output valid Unicode.
* test(migrate-claude): cover UTF-16 skill description
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* 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>