Commit Graph

64297 Commits

Author SHA1 Message Date
github-actions[bot]
cff46028f4 chore(ui): refresh control ui locales 2026-07-05 21:06:02 +00:00
github-actions[bot]
660d752b34 chore(i18n): refresh native locales 2026-07-05 21:05:06 +00:00
Peter Steinberger
51771c3a14 feat(ui): declutter the Control UI sidebar with customizable pinned nav and a More section (#100296)
* feat(ui): declutter sidebar with pinned nav, More section, and customize menu

* test(ui): normalize sidebar e2e labels

* docs: refresh generated docs map

* fix(ui): keep tablet navigation drawer expandable

* chore(ui): refresh i18n raw-copy baseline
2026-07-05 21:56:51 +01:00
frank-beans
664464c750 Preserve provider settings during onboarding updates (#100107)
* Preserve provider settings during onboarding updates

* fix(onboarding): clear omitted request auth

* fix(onboarding): retain canonical provider keys

* fix(onboarding): canonicalize provider updates

* fix(minimax): preserve models across provider aliases

* fix(minimax): preserve secret references during onboarding

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-07-05 21:49:58 +01:00
Peter Steinberger
77575d74f6 feat(android): add durable offline command outbox for chat sends (#100290) 2026-07-05 21:45:36 +01:00
Peter Steinberger
bf156386aa feat(chat-ui): structured haptic feedback for chat events (#100416) 2026-07-05 21:42:44 +01:00
Peter Steinberger
5e0504aa43 feat(ui): simplify Talk settings (#100453)
Co-authored-by: Peter Steinberger <steipete@openai.com>
2026-07-05 21:31:52 +01:00
Vincent Koc
13e5902578 fix(ci): keep release helper args historical 2026-07-05 22:18:09 +02:00
Vincent Koc
1408deede7 fix(ci): keep npm debug diagnostics best-effort 2026-07-05 22:18:09 +02:00
Vincent Koc
79f7d90cb9 fix(ci): satisfy changed-gate npm log lint 2026-07-05 22:18:09 +02:00
Vincent Koc
fdb112970f fix(ci): preserve cross-os candidate fallbacks 2026-07-05 22:18:09 +02:00
Vincent Koc
8a378fe84f fix(ci): honor npm logs-dir diagnostics 2026-07-05 22:18:09 +02:00
Vincent Koc
63257a2b0e fix(ci): scope package tarball runtime checks 2026-07-05 22:18:09 +02:00
Vincent Koc
035d9975a2 fix(ci): preserve npm install failure cause 2026-07-05 22:18:08 +02:00
Vincent Koc
0cd2bc474f fix(ci): bundle private AI runtime in release candidates 2026-07-05 22:18:08 +02:00
Vincent Koc
d5ac8555bf fix(ci): accept pnpm pack destination paths 2026-07-05 22:18:08 +02:00
Vincent Koc
20626d4580 fix(ci): parse pnpm pack json output 2026-07-05 22:18:08 +02:00
Vincent Koc
b3feeec542 fix(ci): use pnpm pack ignore-scripts config 2026-07-05 22:18:08 +02:00
Vincent Koc
a1aaa1c6de fix(ci): pack cross-os candidates with pnpm 2026-07-05 22:18:08 +02:00
Vincent Koc
5e1d675a76 fix(ci): capture npm debug logs in release checks 2026-07-05 22:18:08 +02:00
Peter Steinberger
09f9a85145 feat(android): read-only offline cache for chat sessions and transcripts (#100227)
* feat(android): add read-only offline cache for chat sessions and transcripts

Cache-first cold open plus offline browsing of the session list and cached
transcripts. Room DB (KSP) scoped by stable gateway identity, bounded to
50 sessions / 200 text-only messages per session with eviction on write,
destructive migrations only. Live chat.history/sessions.list responses
replace cached rows wholesale via the existing generation-tracked
reconciliation path; sending stays behind the existing health gate.
Pairing/auth resets and gateway-side session deletes purge cached rows.

Part of #100194

* chore(android): sync native i18n inventory

* fix(android): bind transcript cache writes to gateway scope

* docs(changelog): note Android offline chat cache

* chore(android): refresh native i18n inventory

* docs(changelog): keep Android offline cache unreleased

* docs(changelog): drop PR-carried entry; release generation owns CHANGELOG
2026-07-05 21:14:46 +01:00
Peter Steinberger
426010dfb9 test: shorten daemon timeout proof 2026-07-05 16:13:25 -04:00
Gio Della-Libera
85ad4cb200 fix(voice-call): avoid OpenAI realtime double greeting (#86285)
* fix(voice-call): avoid OpenAI realtime double greeting

* fix(voice-call): keep openai greeting proactive

* fix(openai): preserve deferred realtime instructions

* fix(openai): preserve instructions across response retry

* fix(openai): clear stale deferred response instructions

* fix(openai): keep latest deferred response intent

* fix(openai): keep base instructions for explicit speech

* fix(openai): only dedupe prefixed response instructions

* test(openai): narrow realtime user message callback

* fix(voice-call): suppress duplicate OpenAI realtime greeting

* fix(voice-call): keep OpenAI manual speech semantics

* fix(voice-call): keep OpenAI greeting trigger active

* fix(openai): suppress realtime auto responses during manual turns

* test(openai): import realtime bridge type

* refactor(openai): narrow realtime response suppression

* refactor(openai): share realtime turn detection config

* fix(openai): correlate realtime response errors

* fix(openai): flush queued realtime responses

* fix(openai): correlate realtime cancellation errors

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-07-05 21:13:20 +01:00
Wynne668
d405cda95a fix(browser): resolve act targetId aliases before mismatch check (#96178)
* fix(browser): resolve act targetId aliases before mismatch check

The /act top-level and batch targetId guards compared the caller-supplied
targetId against the resolved canonical tab.targetId with raw string
equality. Any supported alias form (tabId, label, suggestedTargetId, or a
unique id prefix) resolves to a different canonical id, so act requests that
followed the documented 'prefer suggestedTargetId/tabId/label' guidance were
rejected with 403 ACT_TARGET_ID_MISMATCH even though they named the correct
tab. snapshot/open/close/tabs lack this guard and kept working, matching the
reported symptom matrix.

Resolve the action targetId through the same tab alias resolution the route
used and reject only ids that resolve to a different tab.

* fix(browser): canonicalize act targetId aliases before Playwright dispatch

The /act gate accepted tabId/label/suggested/prefix aliases of the request
tab but left them on action.targetId. The managed executor reads
action.targetId ?? targetId for an exact page lookup (executeSingleAction ->
getPageForTargetId), so an alias missed the lookup and broke the action at
runtime whenever more than one page was open (single-page masked it via the
pages.length===1 fallback). Canonicalize the action targetId (top-level and
nested batch sub-actions) to the resolved tab id before dispatch; reject ids
that resolve to a different tab. Replace the mock-masked contract assertions
with executor-action assertions plus direct canonicalizer unit tests.

* test(browser): brace act-targetId guard clauses for curly lint

* docs(changelog): note browser target alias fix

* docs(changelog): note browser target alias fix

* fix(browser): reject ambiguous batch target aliases

* test(browser): type targetless act fixture

* docs(changelog): move browser alias fix to unreleased

* chore: drop nonessential browser changelog entry

---------

Co-authored-by: Peter Steinberger <peter@steipete.me>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-07-05 21:12:13 +01:00
Peter Steinberger
a04b6ced4f test: stabilize load-sensitive test families that break gates under parallel load (#100420)
* test: stabilize process-heavy fixtures under load

* test: stabilize channel mocks under load
2026-07-05 12:53:06 -07:00
Yuval Dinodia
4b9c51c5c5 fix(cron): preserve delivery thread id type across SQLite round-trip (#98699)
* fix(cron): preserve delivery thread id type across SQLite reload

A numeric delivery threadId such as a Telegram forum topic id reloaded as
a string after a gateway restart because the split delivery_thread_id
column stores TEXT. resolveCronDeliveryPlan and channel delivery are type
sensitive, so the numeric topic id was forwarded as a string and dropped
the configured topic.

Source the delivery threadId type from the canonical job_json config on
read, falling back to the raw column text. Both freshly written rows and
legacy bare-text rows keep string versus number identity without any
column format change or migration.

* fix(cron): recover typed delivery thread id when split column is null

* fix(cron): persist delivery thread ID type

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-07-05 12:53:02 -07:00
github-actions[bot]
ae65251fdf chore(i18n): refresh native locales 2026-07-05 19:52:02 +00:00
Peter Steinberger
1afb61f7b7 docs(changelog): record five landed fixes (#100441) 2026-07-05 12:51:00 -07:00
lzyyzznl
8e7cd40719 fix(ui): render direct tool-result image blocks inline in chat (#50779) (#100295)
* fix(ui): render direct tool-result images inline

Co-authored-by: 黄剑雄0668001315 <huang.jianxiong@xydigit.com>

* test(ui): stabilize delayed-send scroll precondition

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
Co-authored-by: 黄剑雄0668001315 <huang.jianxiong@xydigit.com>
2026-07-05 12:48:29 -07:00
Moeed Ahmed
04857c78ee fix: surface terminal agent run failures (#99304)
* fix: surface empty terminal run failures

Co-authored-by: Moeed Ahmed <5780040+moeedahmed@users.noreply.github.com>

* docs: note terminal failure replies

* fix: normalize terminal failure replies

* fix: preserve terminal failure reply threading

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
Co-authored-by: Moeed Ahmed <5780040+moeedahmed@users.noreply.github.com>
2026-07-05 12:46:56 -07:00
PollyBot13
a320f775f0 fix(ios): defer QR pairing after scanner dismissal (#99572)
* fix(ios): defer QR pairing after scanner dismissal

* fix(ios): process QR pairing after scanner dismissal

* fix(ios): harden QR scanner handoff

* fix(ios): give QR scanner dismissal more time

* fix(ios): keep onboarding open for QR trust prompt

* fix(ios): keep QR trust prompt owned by onboarding

* fix(ios): recover operator pairing after QR bootstrap

* fix(ios): cancel stale QR scanner handoffs

Co-authored-by: PollyBot13 <pollybot13@gmail.com>

* fix(ios): defer QR setup until onboarding closes

* fix(ios): keep QR setup links with visible settings

* fix(ios): consume setup links during onboarding

* fix(ios): handle setup links during onboarding launch

* fix(ios): route setup links through active onboarding

* fix(ios): harden QR gateway handoff

* fix(ios): cancel superseded gateway attempts

* fix(ios): serialize scanner result delivery

* fix(ios): prevent stale gateway reconnects

* fix(ios): serialize gateway target handoff

* fix(ios): disable stale gateway relaunch route

* fix(ios): await staged bootstrap reset

* test(ios): bound gateway reset handoff

* fix(ios): preserve explicit gateway handoff

* fix(ios): harden gateway lifecycle ownership

* chore(ios): sync native i18n inventory

* test(ios): align gateway ownership assertions

* refactor(ios): remove superseded gateway helpers

* fix(ios): keep gateway auth route scoped

* fix(ios): restore gateway target review state

* fix(protocol): refresh Swift plugin approval model

* test(ios): isolate state directory overrides

* fix(ios): preserve watch alerts across gateway switches

* fix(ios): bind deferred work to gateway ownership

* docs(changelog): credit iOS gateway handoff fix

* chore(i18n): sync native app inventory

* test(ios): remove unused Watch approval hooks

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-07-05 12:31:53 -07:00
tzy-17
a4499d698d fix(agents): surface real plugin approval rejection reason to agent (#100337)
* fix(agents): surface real plugin approval rejection reason to agent

Distinguish gateway-rejection errors from transport failures in the
plugin approval catch block. When the error is a GatewayClientRequestError
(with a structured gatewayCode), the gateway is reachable and actively
rejected the request — surface the real rejection reason. Otherwise keep
the existing "gateway unavailable" message for genuine transport failures.

Previously every approval gateway error was reported as "gateway
unavailable", even when the gateway was healthy and returned a
structured rejection like INVALID_REQUEST. This misdirected operators
and agents toward connectivity debugging instead of the actual schema
or policy violation.

Fixes #100212

* fix(agents): report plugin approval rejections accurately

Co-authored-by: 唐梓夷0668001293 <tang.ziyi@xydigit.com>

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-07-05 12:31:31 -07:00
Peter Steinberger
f1641571f1 feat(android): restore in-flight runs after reconnect (#100384)
Consume the gateway's canonical reconnect snapshot (chat.history inFlightRun)
to re-adopt still-streaming runs after reconnect, cold start, and seq-gap
recovery in the Android ChatController. Mirrors the iOS contract from #100277.
Part of #100197.
2026-07-05 12:30:02 -07:00
Peter Steinberger
1833c0d771 test(ios): add deterministic streaming replay harness for shared chat pipeline (#100201) 2026-07-05 12:27:57 -07:00
Peter Steinberger
f141408e77 fix(test): bound local full-suite RAM (#100437) 2026-07-05 12:16:41 -07:00
Peter Steinberger
8ccacd9f4e test: batch PowerShell function probes 2026-07-05 15:16:11 -04:00
llagy009
685c22dfb7 fix(irc): chunk PRIVMSG on UTF-16 boundary to avoid lone surrogates (#96572)
* fix(irc): chunk PRIVMSG on UTF-16 boundary to avoid lone surrogates

sendPrivmsg split long messages with String.slice on a UTF-16 code-unit
index, so an emoji (or other astral character) straddling the split
point was cut into a lone high/low surrogate, sending broken bytes in
the PRIVMSG to the IRC server.

Slice each chunk with sliceUtf16Safe so a surrogate pair is never split.
When the budget is too small to fit even the leading astral character,
emit that character whole so chunking still makes progress.

Adds a socket-level test (fake net/tls socket) asserting no PRIVMSG
chunk contains a lone surrogate, including the one-code-unit budget
edge case, while the existing space-preferring split is preserved.

* refactor(irc): make surrogate-safe chunking direct

* fix(irc): preserve one-unit chunk limits

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-07-05 12:14:05 -07:00
tzy-17
ad118f4b51 fix(gateway): preserve non-text MCP content blocks through loopback normalizer (#100336)
* fix(gateway): preserve valid MCP tool content

Co-authored-by: 唐梓夷0668001293 <tang.ziyi@xydigit.com>

* fix(gateway): honor negotiated MCP content types

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-07-05 12:12:40 -07:00
ml12580
fb22fd9e0e fix(cli): tolerate deleted startup directories (#93636)
Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-07-05 12:10:24 -07:00
Peter Steinberger
b6a7898c7c docs(logbook): complete setup and privacy guide 2026-07-05 15:09:43 -04:00
Peter Steinberger
bbda36ed57 fix(gateway): clarify URL override auth recovery (#100418)
Co-authored-by: Gabriel A. Mays <gmays@users.noreply.github.com>
2026-07-05 12:09:17 -07:00
Darren2030
39b5bf38f7 fix(voice-call): resolve completed calls from the persisted store on status misses (#99797)
* fix(voice-call): resolve completed calls from the persisted store on status misses

get_status, the legacy status mode, and the voicecall.status gateway method
only consulted the in-memory call manager. Once a call was evicted (finalize,
gateway restart, or max-duration expiry) they reported { found: false } even
though the full record remained on disk. Fall back to the persisted call
history and resolve the NEWEST matching snapshot — history is oldest-first, so
a forward find() returns a stale record (the regression in the prior attempt).

Closes #96586

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test(voice-call): use bracket access for mocked getCallHistory assertion

Avoids the typescript(unbound-method) lint rule that flags referencing a
typed method (`runtimeStub.manager.getCallHistory`) as an unbound value.
Bracket access matches the existing mock-assertion pattern in this file
(e.g. `runtimeStub.manager["sendDtmf"]`).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* ci: re-trigger QA Smoke (transient build-OOM, also failing main run 28695162780)

* fix(voice-call): resolve persisted calls in the CLI status fallback too

The local CLI `voicecall status` gateway-unavailable fallback only consulted
the in-memory manager, so a completed/evicted call still returned
{ found: false } even though the gateway/tool/legacy status paths now fall
back to the persisted store. Align this fourth status reader: consult
getCallByProviderCallId in addition to getCall, and on an active miss resolve
the NEWEST matching persisted snapshot via getCallHistory(100) +
toReversed().find(...) (history is oldest-first), mirroring the gateway/tool
paths. Return shape is unchanged.

Per review on #96586 (align CLI status before merge).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* refactor(voice-call): centralize persisted status lookup

Co-authored-by: 曾文锋0668000834 <zeng.wenfeng@xydigit.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-07-05 12:05:01 -07:00
Peter Steinberger
c730d8f1f1 feat(logbook): automatic work journal plugin with Control UI timeline tab (#99930)
* feat(logbook): automatic work journal plugin with a plugin-contributed Control UI tab

Squash of PR #99930 work for rebase onto the Control UI route refactor:
- extensions/logbook: Dayflow-style capture -> observations -> timeline cards
  pipeline with SQLite store, node capture commands, standup/ask, retention
- plugin SDK/gateway seam: surface "tab" Control UI descriptors projected
  into hello-ok controlUiTabs (scope-filtered, deterministic order)
- Control UI: dynamic plugin tabs with bundled Logbook view
- docs, tests, labeler wiring

* feat(ui): port plugin tabs and Logbook to the route-owned Control UI architecture

- shared /plugin route carries the tab id in the query (?id=<tab>), matching
  the router's exact-path contract
- openclaw-plugin-page renders bundled views (Logbook), sandboxed plugin
  frames (descriptor path), or the unavailable card
- sidebar renders hello controlUiTabs after each group's static routes
- Logbook view/controller live under ui/src/pages/plugin/

* fix(ui): namespace plugin tabs by pluginId to prevent cross-plugin tab id collisions

* fix(logbook): prefer app capture nodes and rotate off failing nodes

* fix(plugins): reject protocol-relative Control UI tab paths

* fix(logbook): harden automatic journal

* docs(changelog): remove maintainer self-credit

* chore(ui): refresh locale metadata after rebase

* fix(logbook): preserve analysis window boundaries

* fix(logbook): align status privacy and timezone

* fix(ui): stop hidden plugin tab polling
2026-07-05 11:50:44 -07:00
Peter Steinberger
3f380635f5 feat(chat-ui): syntax-highlighted code blocks and native tables in chat markdown (#100207)
* feat(chat-ui): syntax-highlighted code blocks and native GFM tables in shared chat markdown (#100195)

* chore(chat-ui): sync native i18n inventory

* fix(chat-ui): harden native markdown rendering

* refactor(chat-ui): split HTML block patterns

* refactor(chat-ui): parse markdown blocks with swift-markdown

* style(chat-ui): satisfy SwiftFormat lint

* fix(ios): bundle markdown dependency licenses

* style(ios): normalize markdown license file

* fix(ios): keep native markdown cards legible
2026-07-05 11:38:05 -07:00
Peter Steinberger
d7e2096dc7 test: avoid redundant skip-report process 2026-07-05 14:25:57 -04:00
Peter Steinberger
f04d18a394 test: avoid redundant env-report processes 2026-07-05 14:19:13 -04:00
Peter Steinberger
b22c36f112 fix: land ten small reliability fixes (#100399)
* fix(cron): reject sub-millisecond durations

* fix(skill-workshop): preserve proposal terminal newline

Preserve proposal_content exactly at the agent tool boundary and make
renderProposalMarkdown defensively emit a terminal newline.

Add focused regressions for the tool write path and markdown renderer.

* fix(skill-workshop): reject blank raw proposal content

* fix: treat empty-string optional integer tool params as unset

Optional positive-integer tool params (e.g. Telegram replyTo/threadId)
threw ToolInputError when a tool-calling model populated them with an
empty-string or whitespace-only default. Those defaults carry no value,
so readPositiveIntegerParam/readNonNegativeIntegerParam now treat a
blank string as unset (undefined) instead of throwing, while still
rejecting genuinely invalid present values (0, "42.5", "-3"). This
prevents silent message-delivery failures when models emit empty
routing-param defaults. Adds unit tests covering blank vs invalid.

* fix(gateway): log start session persistence failures

The gateway session-lifecycle "start" event persistence
(persistGatewaySessionLifecycleEvent, which surfaces write failures via
requireWriteSuccess) was fired as void ...catch(() => undefined), swallowing
the rejection with zero logging. A failed start-marker write silently dropped
the run's start record from restart-recovery accounting, with no
operator-visible trace.

The sibling terminal-phase catch already logs this since #97839; the start
path was the unfixed sibling. Mirror that fix: log the swallowed start-phase
persistence failure with the same redacted message shape via formatForLog,
keeping the fire-and-forget semantics unchanged. Adds a focused regression
test asserting the log fires on start-persist rejection.

* fix(memory): report close-time pending work failures

* fix(shared): return "" from sliceUtf16Safe when end <= start, matching native .slice

sliceUtf16Safe silently swapped reversed bounds (to < from) instead of
returning "" like String.prototype.slice, creating a subtle footgun for
callers with dynamic start/end pairs.

Caller scan across src/, extensions/, and packages/ confirmed no production
code relies on the old swap behavior — all callers use (text, 0, N) or
(text, -N) forms only.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(plugins): require plugin manifest in npm verifier

* fix(android): propagate CancellationException in CameraHandler catch blocks

Catch (err: Throwable) swallows kotlinx.coroutines.CancellationException,
breaking structured concurrency when the coroutine scope is cancelled
during camera operations (handleList/handleSnap/handleClip).

Add CancellationException rethrow before each Throwable catch to match
the existing pattern used in GatewaySession and TalkModeManager.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(android): propagate CancellationException past GatewaySession invoke boundary

* chore: sync native i18n inventory after gateway session line shift

* fix(agents): prevent native hook relay bridge race condition on renew and registration

Remove synchronous bridge record write after server.listen() that races
before the TCP server binds, and guard renew handler with server.listening
check to prevent stale relay registrations.

Closes #98650

* fix: harden small reliability fixes

Co-authored-by: cxbAsDev <cxbAsDev@users.noreply.github.com>

* docs(agents): explain buffered LSP spawn failures

* docs(agents): clarify LSP spawn timeout invariant

---------

Co-authored-by: qingminlong <qing.minlong@xydigit.com>
Co-authored-by: anyech <anyech@gmail.com>
Co-authored-by: snotty <snotty@users.noreply.github.com>
Co-authored-by: masatohoshino <g515hoshino@gmail.com>
Co-authored-by: lin-hongkuan <lin-hongkuan@users.noreply.github.com>
Co-authored-by: simon-w <weng.qimeng@xydigit.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: 宇宙熊Yzx <53250620+849261680@users.noreply.github.com>
Co-authored-by: xialonglee <li.xialong@xydigit.com>
Co-authored-by: nankingjing <1079826437@qq.com>
Co-authored-by: cxbAsDev <cxbAsDev@users.noreply.github.com>
2026-07-05 11:12:55 -07:00
Peter Steinberger
c757675f34 improve: keep isolated tests under one second (#100019)
* test: speed up isolated test suite

* test: finish isolated latency cleanup

* test: eliminate remaining isolated latency spikes

* test: remove final isolated timing outliers

* test: bound full-suite tooling processes

* test: bound native test process lifetime

* test: warm isolated runtime suites

* test: eliminate final isolated timing outliers

* test: fix isolated timing fixture types

* test: make timeout cleanup timing deterministic

* test: pin media manifests to source checkout

* test: isolate provider manifest contracts

* test: eliminate residual isolated timing spikes

* test: restore final isolated timing fixes

* test: eliminate remaining isolated timing spikes

* test: warm Zalo lifecycle imports

* test: keep isolated suites below one second

* test: use readable browser response fixtures
2026-07-05 10:57:19 -07:00
Peter Steinberger
91f188301d docs: explain remote Android screen mirroring (#100398)
* docs(android): document remote screen mirroring

* docs: refresh documentation map
2026-07-05 10:55:03 -07:00
Peter Steinberger
8a187447f9 feat(ui): show provider costs in context popover (#100379)
* feat(ui): show estimated run cost

* feat(ui): show provider cost breakdown
2026-07-05 10:49:06 -07:00