hasInboundAudio previously accepted <media:audio> placeholder bodies,
[Audio transcript headers, and filename sniffing alongside MediaTypes.
It now consumes only the structured facts channels already carry
(MediaType/MediaTypes, kind audio or audio/*), removing the string
reverse-engineering ahead of the placeholder-elimination program.
Discord — the one channel with a coverage hole — now carries its native
audio classification (MIME, voice fields, constrained filename fallback)
as structured kind: "audio", with definitive fetched/declared MIME
precedence so generic container types cannot erase declared audio and
filename hints cannot override definitive non-audio MIME.
* feat(browser): send pages to the main session from the Chrome extension
One-click page share in the OpenClaw Chrome extension: toolbar popup with an
optional note, page/selection context menu, and Alt+Shift+S. Capture is
selection-first with a readability heuristic, X/Twitter thread extraction, and
Google Docs plain-text export via the user's session cookies. Payloads ride the
existing paired relay WebSocket as a new pageShare message; the gateway-only
page-share sink wraps page text in the external-content safety boundary, then
enqueues a main-session system event and requests an immediate heartbeat
(hooks/wake semantics). Node-hosted relays report a clear unsupported error.
Capture heuristics adapted from Nat Eliason's MIT-licensed send-to-openclaw.
Co-authored-by: Codex <codex@openai.com>
* fix(browser): keep page-controlled metadata inside the share safety boundary
Review findings: move title/URL inside wrapExternalContent (a hostile <title>
must not become trusted header text), prefer the user's selection over the
full Google Docs export, and pass the context-menu selectionText through so
iframe selections and selections cleared during relay reconnect still win.
* fix(browser): bind context-menu shares to the click-time document
Selection shares from the context menu now send the click snapshot directly
(no recapture), so navigations during relay reconnect cannot mislabel the
source and iframe selections are preserved. The Google Docs selection probe
scans all accessible frames before falling back to the full-document export.
* test(browser): expect the page-share handler in relay server args
* fix(browser): probe only the main frame for Google Docs selections
All-frame injection rejects wholesale when one frame is inaccessible and
returns child frames in nondeterministic order, so the probe now reads the
main frame only. Child-frame selections still share correctly through the
context menu's click-time selectionText; toolbar/shortcut entry sends the
full page for that case (named tradeoff in the code comment).
* fix(browser): satisfy page-share CI gates
---------
Co-authored-by: Codex <codex@openai.com>
* feat(codex): fork upstream-linked sessions at a message via thread/fork
* fix(gateway): fail closed for rewind and branch switch on upstream-linked sessions
* fix(codex): fail closed on first-message forks, image-only prompts, and orphan archival
* fix(codex): baseline retained history and reject paginated threads on upstream fork
* fix(codex): validate the full fork prefix and fail closed across crash windows
* fix(codex): treat all non-text inputs as unverifiable in fork drift checks
* fix(codex): support first-message forks as empty-history upstream cuts
* fix(codex): reject source-id reuse and unverifiable hidden inputs in fork boundaries
* refactor(codex): materialize upstream forks from verified thread read-back
* fix(codex): satisfy strict type lanes and knip for upstream fork
* fix(telegram): pace fast empty ingress polls
Some Bot API deployments (notably self-hosted telegram-bot-api servers and
intermediary proxies) can answer getUpdates immediately instead of holding
the connection for the requested long-poll timeout. With no pending updates
that turns the polling ingress worker loop into a busy spin: each empty
poll completes in milliseconds and the next one starts right away, pinning
a CPU core.
Enforce a 1s floor between consecutive empty getUpdates cycles, measured
from poll start so responses that honor the long-poll timeout are never
delayed. The wait listens to the worker stop signal, keeping shutdown
immediate. Non-empty polls are not paced, so pending traffic still drains
at full speed.
* fix(telegram): adapt empty poll backoff
---------
Co-authored-by: Arseniy Palagin <valeradzigurda3@gmail.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(line): deliver rich replies on the reply token instead of the push quota
A reply token carries five messages for free, while push spends the account's
monthly quota. Text rode the token and the rich/media bubbles were pushed after
it, so an extracted table vanished with a 429 once that quota was gone. Bundle
both onto the token and push only the overflow.
Quick replies now ride the trailing message instead of the last reply-token
slot: LINE hides them as soon as a newer message arrives, so an overflowing
batch pushed its bubbles after the buttons and wiped them.
* refactor(line): unify auto-reply batching
* test(line): remove unused token fixture
* refactor(line): delete legacy reply chunk path
* fix(line): recover text from rejected mixed batches
* test(line): split auto-reply recovery coverage
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(googlechat): keep accounts.default streaming when doctor migrates named accounts
* fix(googlechat): canonicalize migrated streaming
Replace the layered doctor migration with canonical root/default/account materialization and prove runtime resolution matches the stored config.\n\nCo-authored-by: tiffanychum <71036662+tiffanychum@users.noreply.github.com>
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(line): retry inbound media through the ingress drain on transient download failure
* fix(line): retry all transient inbound media failures
Co-authored-by: 許元豪 <146086744+edenfunf@users.noreply.github.com>
* style(line): format media retry test
* test(line): use scanner-safe quote token
* fix(line): narrow durable media retries
* fix(line): preserve status after body cleanup
* chore(changelog): avoid concurrent entry conflict
* style(line): format retry helper signature
* test(line): assert rejected media chunk
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
Model-call spans wrap outbound provider inference requests, so classify them as CLIENT independently of the optional GenAI naming convention. Generic tool spans remain unchanged because their shared events can represent local work.
Release note: operators filtering dashboards or alerts on span kind will see model-call spans move from INTERNAL to CLIENT.
Co-authored-by: Peter Steinberger <steipete@gmail.com>