* fix(openai): bound Codex OAuth token response body reads with readResponseWithLimit
Replace unbounded response.arrayBuffer() in postTokenForm with
readResponseWithLimit using a 1 MiB cap to prevent OOM from oversized
token endpoint responses. Add real node:http loopback server tests.
* fix(openai): wrap readResponseWithLimit result in Uint8Array for TS BodyInit compat
- Fixes TS2345: Buffer<ArrayBufferLike> not assignable to BodyInit
- Resolves check-prod-types, check-test-types, and
check-additional-extension-package-boundary CI failures
Ref. https://github.com/openclaw/openclaw/pull/99479
* test(openai): verify OAuth response release
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
The single InstalledDistScanBudget is consumed across all three prune
walks (legacy-deps prepass, dist file listing, empty-dir sweep). During
an npm upgrade the dist dir transiently holds old+new content-hashed
files, so a real upgrade scan totals ~24k entries against the 25k cap;
one more release of dist growth would make 'npm install -g openclaw'
throw InstalledDistScanLimitError for every upgrading user. Raise the
cap to 100k for real headroom while keeping the unbounded-scan guard,
and ratchet the cap in the test so it cannot be lowered back.
The sessions screen gains a debounced search field under the filter pills,
wired to server-side sessions.list search in Recent and Archived scopes.
Results flow through the existing filter/sort/grouping pipeline so pinned
and grouped rendering stay intact while searching.
ChatController.fetchSessionList performs the one-shot search without touching
live list state, falls back to locally filtering the cached active list when
the gateway is unreachable (archived rows exist only server-side, so archived
search is empty offline), and rethrows CancellationException before the
fallback so a superseded search never repaints stale rows over a newer query.
Refs #100712
Budget context engine assembly against the reserve and rendered prompt
pressure, and carry the preflight estimated prompt tokens, prompt budget,
and overflow tokens into the outer overflow recovery loop so compaction
engines compact against the prompt OpenClaw actually rendered instead of
a minimally over-budget guess.
* fix(discord): drain queued outbound deliveries after gateway reconnect (#56610)
Discord sends that failed while the gateway websocket was reconnecting
(close codes 1005/1006) stayed stuck in the outbound delivery queue and
were silently dropped. Classify sends that fail while the registered
gateway is disconnected as retryable, and drain pending discord queue
entries when the gateway reconnects, matching the WhatsApp/Telegram
reconnect drains.
* fix(discord): harden reconnect recovery
Co-authored-by: tiffanychum <71036662+tiffanychum@users.noreply.github.com>
* refactor(discord): remove unsafe reconnect replay
* fix(discord): bound reconnect retry per request
Co-authored-by: tiffanychum <71036662+tiffanychum@users.noreply.github.com>
* chore(changelog): leave reconnect fix to release flow
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* feat(android): record and send voice notes from the chat composer
Adds tap-to-record voice notes to both Android chat composers: MediaRecorder
AAC/m4a mono capture with a 3-minute cap, lazy RECORD_AUDIO permission,
cancel/finish controls, audio attachment chips with duration, and voice-note
transcript rows. Recording is mutually exclusive with MicCaptureManager voice
capture (no mic arbiter exists), and the MPEG-4 container brand is normalized
to M4A so gateway byte sniffing classifies the upload as audio, not video.
Attachments stay online-only per the outbox contract.
Related: #100709
* fix(android): coordinate voice note microphone ownership
* chore(android): refresh native localization inventory
* fix(discord): download attachments at receipt time, not after the run queue
Discord's CDN attachment URLs carry an expiring `ex` TTL. Media was
downloaded in processDiscordMessageInner, after the inbound run queue,
so messages delayed behind a busy run lost their attachments silently.
Resolve attachments/forwarded media during preflightDiscordMessage
instead, before the message is enqueued, and carry the result forward
on the context for process to reuse.
Fixes#96165
* refactor(discord): carry one prepared media snapshot
Co-authored-by: ZacharyYW <zachary.w.yuan123@gmail.com>
* fix(discord): guard bot media before download
* chore(discord): leave release notes to release flow
* docs(changelog): credit Discord attachment fix
* chore(changelog): leave attachment fix to release flow
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
Translate chat.composer.realtimeTalkRequiresMicrophone across all
locales; it shipped as a recorded English fallback with the chat
composer redesign and broke the control-ui-i18n CI gate on main.
* fix(discord): bound gateway metadata response body reads to prevent OOM
The materializeGuardedResponse helper in the Discord gateway metadata path
buffered the full upstream Response body via response.arrayBuffer() without
any size cap. A malicious or malfunctioning /gateway/bot endpoint that returns
an oversized payload could exhaust gateway memory.
Replace arrayBuffer() with readResponseWithLimit(4 MiB), consistent with
DISCORD_API_RESPONSE_BODY_LIMIT_BYTES in api.ts. Overflow throws an Error
with the byte counts.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(discord): wrap readResponseWithLimit result in Uint8Array for type compat
readResponseWithLimit returns Buffer which is not assignable to BodyInit in
the undici Response constructor type. Wrap in new Uint8Array() to satisfy the
boundary dts check. Also remove testExports export and mock fetchWithSsrFGuard
in tests via vi.hoisted + vi.mock to avoid leaking internal test-only exports.
Co-Authored-By: Claude <noreply@anthropic.com>
* test(discord): tighten gateway metadata proof
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(device-pair): remove INADDR_ANY and IPv6 unspecified from isLoopbackHost
0.0.0.0 (INADDR_ANY — bind to all interfaces) and :: (IPv6 unspecified)
are NOT loopback addresses. The canonical implementation in
src/gateway/net.ts explicitly excludes both. Classifying them as
loopback in isLoopbackHost causes incorrect behavior in mobile
pairing cleartext host validation.
Co-Authored-By: Claude <noreply@anthropic.com>
* test(device-pair): reject unspecified setup URLs
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(edit): show candidate lines on oldText match failure
When edit tool cannot find oldText, include up to 3 near-match candidate
lines with similarity scores and line numbers. Caps input size (MAX_LINES
3000, MAX_LINE_LEN 200) to avoid unbounded CPU on large files. Reuses
existing levenshteinDistance helper.
Closes#97032
* fix(edit): use toSorted instead of sort for candidate ranking
Fixes lint violation: unicorn(no-array-sort)
* fix(edit): keep candidate truncation branch-compatible
---------
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
* fix(browser): guard readFields JSON.parse against malformed user input
Wraps JSON.parse(payload) in readFields() with try/catch, throwing a
descriptive Error when CLI --fields or --fields-file input contains
malformed JSON instead of raw SyntaxError.
8/8 proof assertions pass using standalone script that imports and
calls the real exported readFields() with malformed field strings.
Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: lsr911 <liao.shirong@xydigit.com>
* fix(browser): add vitest malformed JSON tests, remove standalone proof
ClawSweeper feedback: added 2 vitest tests (malformed JSON, empty fields)
to the existing shared.test.ts, removed standalone proof script.
Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: lsr911 <liao.shirong@xydigit.com>
* fix(browser): cover malformed fields at CLI boundary
---------
Signed-off-by: lsr911 <liao.shirong@xydigit.com>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
sessions.patch becomes a params-aware dynamic-scope method: operator.write
now authorizes patches that touch only user-level chat-organization fields
(label, category, pinned, archived, unread); every other field, mixed
patches, and unknown keys keep requiring operator.admin (fail closed).
This unblocks the session controls shipped in #100814 for the Android app,
whose bounded operator session intentionally never requests operator.admin.
Also: session list ordering gains a deterministic key tiebreaker for equal
pinnedAt/updatedAt (stable offset paging and prompt-cache friendliness), a
user-assigned label now beats stored channel-derived display names in the
row projection so renames survive refreshes, and subagent spawn pins its
gateway calls to admin via the params-aware least-privilege resolver
instead of the static admin-only method check (#59428 contract preserved).
Refs #100712