TTS attempt records only recognized err.name === 'AbortError' as a
timeout, but the shared transport names its abort 'TimeoutError'
(fetch-timeout.ts) and provider operation deadlines throw plain Errors
ending in 'timed out'. Every real HTTP provider timeout was therefore
recorded as reasonCode 'provider_error', dead-ending diagnosis in
/tts status and telemetry — the timeout classifier was dead code.
Recognize all three shapes via one helper shared by the reason code
and the formatted error text. The existing classification test crafted
an AbortError by hand, green-lighting the misclassification; it now
uses the real TimeoutError transport shape.
* fix(google-meet): classify gateway CLI fallback with typed guards instead of message matching
Replaces the message-substring classifier in the Meet CLI's
gateway-unavailable local fallback with the structural
isGatewayTransportError/isGatewayClientRequestError guards (seam shared
verbatim with #125458). Decision surface chosen per Meet semantics:
uncoded transport closes (unreachable sockets, now incl. ETIMEDOUT/
ENETUNREACH) fall back; coded closes incl. 1006 deliberately do not,
since a live gateway may still own Meet browser sessions; unknown-method
fallback stays but requires a structural gateway request error. Adds
ECONNRESET to GATEWAY_UNREACHABLE_SOCKET_CODES so connect-time resets
wrap into the typed unreachable error instead of escaping raw.
* test(process): treat Linux zombies as terminated
The lock auto-releases only while the pre-side-effect validation marker is active; review_init marked side effects before the read-only metadata fetch, so a transient GitHub failure retained the lock and forced a lock-recover loop. The read-only fetch now runs before the marker.
* feat(ui): show worktrees only for Git group folders
* feat(ui): enrich group environment picker
* fix(ui): preserve worktree defaults on probe errors
* fix(ui): reject stale group repository probes
* test(ui): type repository inspection helper
* fix(openai): clamp realtime barge-in truncation
Clamp OpenAI Realtime barge-in truncation to the audio duration produced for each assistant item. This prevents interruption failures observed live when 3,700 ms of decoded audio was paired with a 3,760 ms transport-clock timestamp.
* fix(openai): propagate PCM sideband audio format
The browser sideband negotiated PCM16, but its bridge defaulted to G.711 for duration accounting. Propagate the negotiated format so truncation uses the actual PCM duration and closes ClawSweeper's in-scope finding.
Restore the block-axis padding reset lost in #125368 so Web Awesome tab labels remain vertically legible while retaining logical inline spacing. Add LTR and RTL browser geometry coverage for the visible label boundary.
gh exit status was treated as proof that stdout held a PR object. The Octopool cache shim reports upstream 5xx responses as exit 0 with empty stdout, so reads now use a bounded-retry validation helper. Convert 9 of 28 gh pr view sites where an empty payload could produce a wrong decision.
Revoked or expired OAuth candidates are now persisted into shared rotation health so healthy profiles win subsequent selection. Session-expired cooldowns retain targeted recovery semantics.
* fix(voice-call): survive gateway in-process restart and stop CLI dead-ends
The gateway's in-process restart (SIGUSR1 config reload) reuses the cached
plugin registry, so service stop/start run on the same retained voice-call
registration. Generation fencing from #120289 treated that restart as a stale
actor: stop retired the generation forever, the next start silently bailed,
and every voicecall.* RPC answered UNAVAILABLE "runtime generation is
retired" while the webhook never rebound.
- Registrations now hold a replaceable generation: service start after stop
mints a fresh generation, takes over a running slot owned by a retired
predecessor, and reports start failures to service health instead of
silently returning.
- The voicecall CLI classifies gateway failures with typed guards instead of
message substrings: standalone/store fallback only when the gateway is
genuinely absent; reachable-but-failed (request errors, auth, timeout)
exits with actionable text; a standalone webhook port collision explains
that a running Gateway probably owns the port instead of raw EADDRINUSE.
- Plugin SDK gateway-runtime exports structural isGatewayTransportError /
isGatewayClientRequestError guards (+2 documented surface budget).
- Regression coverage: same-registration stop/start restart, retired-owner
takeover, typed CLI fallback classification, and a real token-auth gateway
server routing voicecall.status through callGatewayFromCli.
* refactor(voice-call): split CLI modules and dedupe gateway fallbacks
Collapse the four duplicated gateway-or-runtime command blocks (speak, dtmf,
end, continue fallback) into one generic runGatewayManagerCommand helper —
the continue command owns its legacy-method fallback and operation polling
via a gatewayCall closure, so the helper carries no per-command policy.
Smoke reuses the shared initiateVoiceCall path instead of a bespoke
fallback.
Split the 988-line cli.ts into concept modules (cli-gateway-call,
cli-call-log, cli-command-io) and drop its grandfathered max-lines
suppression plus the now-stale max-lines and assertion-safety baseline
entries (shrink-only ratchet maintenance).
Behavior-frozen: stdout/exit semantics unchanged; net -2 production LOC.
* fix(voice-call): redact gateway URLs in CLI operational errors
ClawSweeper P1: the operational-error formatter interpolated the raw
connectionDetails.url, so a configured gateway URL with userinfo or query
tokens would print credentials into terminal output. Redact the composed
message once with the canonical net-policy redactor (also covers
remote-controlled close-reason text), exported through the plugin SDK
gateway-runtime subpath (+1 documented surface budget). Regression test
covers a credential-bearing URL in both the URL and message fields.
memory_search corpus=all silently swallowed a returned memory-manager
error (missing sqlite, embedding misconfiguration, corrupted index):
the error branch was gated on !shouldQuerySupplements, so the tool
served wiki-only results in a payload with no error/warning field and
recorded no cooldown — a degraded search read as complete, contradicting
the tool description's own disabled-flag contract. The thrown-error path
already surfaced this; only the returned-error path was silent.
Record the cooldown in both cases and, when supplements still serve,
carry a warning naming the unavailable memory corpus in the payload.
* feat(android): render durable progress card in chat
Remove Android consumption of the legacy stream:"plan" agent events and in-flight plan snapshots.\n\nHandle progressCard.changed through the durable progressCard.get store and remove the Android protocol coverage allowlist entry.
* fix(android): refetch progress card on unattributable poke
The changed event carries the server-derived observer scope key, which the
client only learns from a get response carrying a card. Before that, a
canonical-keyed poke (e.g. global session scope) failed both match checks and
was silently dropped until reconnect. Unknown attribution now triggers an
authoritative refetch instead.
* chore(i18n): refresh native source baseline for progress-card strings
* fix(android): render legacy plan events when the gateway lacks the progress-card store
Released gateways through v2026.7.x emit stream:"plan" events and do not advertise progressCard.get, so retain a negotiated Android fallback.
Remove this branch with the gateway legacy dual-emit after the minimum supported gateway ships the progress-card store.
* feat(control-ui): move the Ask OpenClaw toggle to the sidebar footer
The sidebar bottom now mirrors its top: big agent selector with inline
chevron + small new-session square above, big account identity card with
inline chevron + small lobster toggle below. The account chevron moves
from the row's far right to directly after the name, matching the top
header treatment; the freed edge hosts the toggle. The old top-left
shell-chrome placement is removed along with its four-button width
reservation (which read-scoped clients paid for without seeing the
button). Scope gating, palette entry, and panel wiring are unchanged;
gating regressions moved to the sidebar cases.
* fix(control-ui): keep Ask OpenClaw reachable while the nav is collapsed
Address the ClawSweeper P1: desktop collapse hides the sidebar entirely
(display:none, no rail), which would have removed the toggle's only
persistent surface. The lobster now joins the chrome strip exactly while
the nav is collapsed — the footer stays its home — and the strip's
collapsed width reserves four controls for that state. Regression covers
collapsed-present/expanded-absent/read-scope-absent.
A local-mode gateway (gateway.mode=local, no gateway.remote block) sent
CLI clients "set gateway.remote.token to match gateway.auth.token" on a
wrong-token connect. That hint is a dead end for local connections since
they resolve credentials from gateway.auth.token directly. The connect
pipeline already records isLocalClient at admission time; thread it into
the auth-failure message so local CLI hints point at gateway.auth.token
(or pairing) while remote CLI hints keep the gateway.remote.* guidance.
* fix(voice-call): expose realtime/streaming stream paths through tailscale serve/funnel
Tailscale serve/funnel now auto-exposes the realtime and streaming WebSocket stream paths when those audio modes are enabled; previously Twilio <Connect><Stream> could not reach wss://<ts-host><streamPath> and realtime calls dropped after ~1s with no log.
Also: CLI voicecall expose mounts/clears stream paths symmetrically, partial tailscale mounts roll back on failure, and a warning now fires when a call's stream WebSocket never arrives within the token TTL. Reported/observed live by Peter Steinberger.
* fix(voice-call): make tailscale stream exposure atomic
Preserve configured public Tailscale prefixes when mapping realtime and streaming routes, and roll back the full route set when any mount fails. The CLI now uses the same transactional setup path and reports failure instead of accepting a partial exposure.
* fix(voice-call): expose Twilio's configured streaming path
Keep realtime routes under the public webhook prefix they advertise, while mounting streaming.streamPath exactly as Twilio emits it. This preserves the canonical public-to-local route list without expanding the provider API outside the scoped repair.
The panel from #125199 rendered raw wire enums in code tags, used
form-grid/field markup whose styles the agents page never loads (bare
unstyled inputs outside the card), referenced non-existent avatar
classes, and put callouts inside the settings group. Rebuild it on the
canonical settings primitives: status rows with dot status and friendly
source/evidence labels, a System/This Agent segmented scope control,
the settings secret input (gains a disabled prop), a danger status row
for errors, and a quiet-inherit + primary-save action row. Retitle to
"GitHub Identity" and move it below Tool Access and Available Right
Now. Add a .settings-account primitive for the 20px round avatar.
Live-tested on an isolated dev gateway: native-credential verify via
the GitHub API, segmented scope switching, and the empty-token error
row. Controller behavior unchanged.
* feat(apps): migrate iOS/macOS plan surface to the durable progress card
Replace the legacy stream:"plan" agent-event pipeline (runId-scoped state,
run-gated pill) with the sessionKey-scoped progress-card store: the shared
chat surface now renders progressCard.get snapshots, refetches on
progressCard.changed pokes with revision dedupe, clears on null-revision
pokes, and persists the card after the run completes. The card renders
markdown through the shared markdown view plus typed steps. Legacy Apple-side
plan handling (agent-event case, run-snapshot plan reconciliation,
OpenClawChatPlanStep parsing) is deleted; gateway emission stays for Android.
Removes the ios progressCard.changed coverage allowlist entry so the check
enforces the handler.
* chore(i18n): refresh native inventory for the progress-card rename
* fix(apps): keep the last progress card when a refresh fails
A transient progressCard.get failure no longer clears an already-rendered
durable card; only a successful null fetch or a null-revision poke clears it.
* feat(ui): preserve composer drafts across restarts
Persist draft text and attachment Blobs in browser-local IndexedDB, scoped to Gateway credentials and session or New Session targets. Clean up durable data after successful send or creation, attachment removal, and session deletion.
* fix(ui): preserve text when draft attachments exceed cap
preserve text-only durable fallback for oversized attachments; serialize visible Incognito transition with its storage fence; document retention and disposal.
* test(ui): make draft retirement proof isolate-safe
The shared isolate:false UI suite exposed an order-dependent module mock; exercise and settle the real IndexedDB boundary instead.
* style(ui): format session mutation imports
* fix(ui): keep incognito drafts memory-only
* perf(ui): lazy-load durable draft storage
* fix(ui): sequence durable draft transitions
* fix(ui): restore text-only new session drafts
Programmatic draft restoration must not advance the user-mutation generation; real user input continues through setMessage.
Move navigation transition ownership into the existing handoff module as a behavior-neutral extraction that keeps the submission owner within max-lines.
* fix(ui): restore text-only drafts without import cycles
Complete the text-only restore fix by moving started-session route transitions into a leaf module. This keeps max-lines ownership clean without reintroducing the Madge cycle.
* fix(ui): reconcile attachment handoffs with durable drafts
Restore programmatic attachment handoffs without recording a user mutation. Cover stale navigation state losing to newer durable drafts across two pages and a fresh page.
* fix(ui): start durable attachment writes before teardown
Start each CAS IndexedDB write and retirement immediately so text and attachment transactions register before page teardown. IndexedDB readwrite ordering and draft revisions keep snapshots serialized.
* fix(ui): persist New Session drafts before teardown
Accept the committed predecessor or a known in-flight local write ID atomically so New Session writes can start before teardown without spurious local-lineage conflicts.
Reset cached lineage when authoritative storage is missing and re-snapshot the still-current edit.