Track full dispatch completion separately for error handling and shutdown drain while allowing same-session follow-ups to steer active runs. Fixes#113180.
Co-authored-by: Taksh <takshkothari09@gmail.com>
* fix(slack): make MPIM app mentions type-safe
Resolve typeless app mentions through explicit metadata, the scoped event cache, then conversations.info so modern C-prefixed MPIMs cannot be misclassified by event ordering. Add a real Slack QA scenario that requires one reply and cleans up its temporary MPIM.
Co-authored-by: moeealii <75953662+moeealii@users.noreply.github.com>
* fix(slack): type unresolved mention metadata
* fix(slack): satisfy MPIM type contracts
---------
Co-authored-by: moeealii <75953662+moeealii@users.noreply.github.com>
resolveSshConfig and startSshPortForward spawned a hard-coded /usr/bin/ssh,
so SSH config discovery and gateway tunneling failed wherever the system ssh
client lives elsewhere: Windows (built-in OpenSSH under System32\OpenSSH) and
NixOS (/run/current-system/sw/bin).
Route both helpers through the existing resolveSystemBin("ssh", { trust:
"strict" }) resolver, and add the Windows built-in OpenSSH directory to the
resolver's trusted Windows locations (it had System32 but not the
System32\OpenSSH subdirectory where ssh.exe actually ships). Fail closed with
a clear diagnostic when no system ssh client is present.
Closes#83289.
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* feat(talk): emit realtime models and voices in talk.catalog and mirror create-time readiness
* feat(ui): add curated Talk settings page with catalog-driven pickers
* docs(talk): correct stale claims and add one-page GPT-Live setup path
* fix(ui): refresh Talk catalog on config-hash advance and neutralize GPT-Live badge
* fix(ui): provider-aware Talk selection, atomic provider switch, focus refresh
* fix(ui): resolve Talk provider fallbacks via catalog and make Default a true reset
* fix(ui): provider-compatible transports and race-free Talk catalog loads
* fix(ui): never resolve an unknown explicit Talk provider to the active one
* docs(talk): note Android relay readiness caveat for browser-only models
* fix(ui): keep the relay transport when switching Talk provider to Auto
* fix(ui): align section-ownership test and drop unused export after rebase
* feat(gateway): share the canonical browser-origin policy with plugins
Export resolveAcceptedBrowserOrigin through openclaw/plugin-sdk/webhook-request-guards
so browser-facing plugin routes reuse the Gateway's real origin contract instead of a
narrow allowedOrigins array check. Private LAN/Tailnet Control UI loads and the
Host-header fallback were previously rejected with 403 by plugin offer routes while the
Control UI itself worked.
Moves the loopback/forwarded-header helpers to net.ts (re-exported from auth.ts) so the
guard can delegate without importing gateway auth, and migrates the Codex realtime
broker onto the shared seam.
* feat(talk): let providers own agent delegation for realtime voice
Adds an optional runAgentConsult callback to the browser-session create request and
injects the existing embedded consult runtime from talk-client, bound to the same agent
and session key the GA tool path uses. Providers whose realtime protocol delegates work
through their own control channel (rather than GA function calls) can now reach the
OpenClaw agent without a client round-trip.
Threads the effective per-session model into browser-session capability resolution so a
request-level model override selects the right capability set, and propagates a caller
abort signal into consultRealtimeVoiceAgent so a superseded delegation stops its run.
* feat(openai): support GPT-Live realtime voice over ChatGPT OAuth
Implements OpenAI's quicksilver/frameless session natively for Talk browser sessions.
The Gateway creates the WebRTC call (multipart sdp+session to https://api.openai.com/v1/live)
and owns the sideband control socket, so the browser never holds upstream credentials;
delegation.created events run through the OpenClaw agent and stream back as speakable
context appends.
Verified end-to-end on 2026-07-28 against a ChatGPT Pro OAuth profile: call create 201
with an rtc_* id and answer SDP, sideband session.started, session.close teardown.
ChatGPT OAuth is preferred over a Platform API key because /v1/live access for platform
keys is waitlist-gated; the legacy chatgpt.com backend route returns 403 for every model
and protocol version and is not used.
Accepted models are gpt-live-1-codex and gpt-live-1-boulder-alpha; the voice allowlist is
the ten values the route actually accepts, since an invalid voice is rejected at call
creation and cannot be repaired afterwards (session.update reports immutable_field_update).
* docs: document GPT-Live Talk support and its route gotchas
Records the working route and auth, the accepted models and voices, the browser-only
scope, and the two traps that cost the most time: the chatgpt.com backend route returns
403 Voice session access denied for every model, and that same 403 is also what an
invalid voice returns, so it must not be read as an account entitlement block.
* fix(openai): resolve GPT-Live CI failures
* refactor(openai): own zod runtime dependency
* fix(openai): satisfy lint and live-shard gates for GPT-Live
Types the retry-delay finish callback as Error so the rejection reason is provably an
Error at the call site; the abort path already normalized a non-Error AbortSignal reason,
but the unknown parameter type hid that from static analysis.
Registers the new GPT-Live live test in the native-live-extensions-openai shard
expectation. The shard selector already picked the file up from the real tree; only the
hardcoded list in the tooling test lagged.
* fix(openai): clean up post-rebase capability resolver
* fix(openai): preserve GPT-Live delegation fragments
* fix(openai): close GPT-Live sideband handoff race
* fix(openai): accept UUID GPT-Live call ids
* style: apply oxfmt to GPT-Live sources and Talk docs
* style: format Talk docs after rebase
* fix(openai): keep GPT-Live transcript context across ignored delegations
* fix(sessions): stop rejecting header-less persisted transcripts as legacy
Sessions whose SQLite transcript has no session header row were treated as
version 1 and hard-failed every run with "Persisted legacy session transcripts
require doctor/import migration before runtime use". Only an actual header now
declares a legacy version.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix(sessions): keep entry ids stable for header-less transcripts
Rebuild the header at the current version instead of inferring v1, and route
header-less transcripts that still hold legacy-shaped entries to doctor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix(sessions): repair headerless transcripts in doctor
---------
Co-authored-by: Galin Iliev <galin.iliev@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix(ios): honor speech locale for system voice
* chore(ios): refresh talk locale i18n inventory
* fix(ios): fall through unavailable system voices
* chore(ios): refresh talk locale i18n inventory
---------
Co-authored-by: Colin Johnson <colin@solvely.net>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* docs: add local speech TTS setup
* docs: clarify local HTTP release requirement
* docs: require Speech Swift v0.0.23
* docs: use released speech-core Windows package
* docs: use current TTS config in local recipes
Co-authored-by: ivan-digital <root@ivan.digital>
---------
Co-authored-by: ivan-digital <root@ivan.digital>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* docs: link macOS onboarding pages to app download
Add download handoff to both macOS app onboarding entry points so
first-time users can reach the app before following first-run steps.
- onboarding.md: add <Tip> linking to /platforms/macos#download
- onboarding-overview.md: update comparison table Command row and
macOS section opening sentence with download link
Closes#111338
* docs: tighten macOS download handoff
Co-authored-by: WangYan <wang.yan29@xydigit.com>
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* docs: recommend Node 26 in quickstart, landing, and platform install docs
- Problem: the user-facing quickstart (docs/start/getting-started.md), landing
page (docs/index.md), and Linux/macOS platform install docs still mark
Node 24 as the recommended default, contradicting maintainer PR #114399
('recommend Node 26 as the OpenClaw runtime') and the installer default
(scripts/install.sh NODE_DEFAULT_MAJOR=26, openclaw.mjs
RECOMMENDED_NODE_MAJOR=26). Users following the quickstart pick a slower,
heavier runtime against the project's stated recommendation.
- Fix: align the 4 missed user-facing install surfaces to 'Node 26
recommended', matching the phrasing already in docs/install/node.md,
docs/install/index.md, docs/install/ansible.md, docs/install/bun.md, and
docs/help/faq-first-run.md. Leave docs/start/setup.md (the source/dev
workflow where CI pins Node 24) unchanged on purpose.
- Verification: docs-only diff reviewed; supported-version floors
(22.22.3+/24.15+/25.9+) unchanged; consistent across all install surfaces.
* docs: complete Node 26 installation guidance
Co-authored-by: Santhi Prakash <b.santhiprakash@gmail.com>
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(state): name the refusing install in schema and exec-approval refusals
The newer-schema refusal told operators not to downgrade and to upgrade
OpenClaw, neither of which is actionable when two builds share one release
version string. It now names the install root that refused, both schema
versions, and warns that a linked source checkout reports its git HEAD even
when its built dist is older.
The exec-approvals gate told operators to run `openclaw doctor --fix` without
naming the state directory, so a bare invocation repaired the default root
while the scoped install stayed blocked. Both the TypeScript gate and its
Swift sibling now scope the command to the blocked state directory.
Refs #115008
* fix(gateway): name the refusing install in the startup schema refusal
* fix(mac): keep the exec-approvals gate message buildable on iOS
* fix(exec-approvals): shell-quote the state directory in the repair command
* fix(exec-approvals): state the repair directory in prose so every shell can follow it
* fix(channels): make channel health reflect inbound ingress, not just the transport
`connected` is a transport signal, so a channel holding a healthy socket while its
durable ingress queue is unavailable evaluated as `health: healthy` and admitted
nothing. On a production gateway that hid 26+ hours of completely dead inbound Slack
behind `connected, bot:config, app:config, health:healthy`; outbound kept working,
which is what kept the transport looking fine.
Add inbound admission as its own health dimension rather than overloading `connected`
(deliberately tri-state since 30fda068aa, and 17 of 27 channel plugins depend on
absent meaning "no transport signal"). The shared ingress monitor now rethrows a
typed ChannelIngressUnavailableError when it cannot open its durable queue, the
channel supervisor records that as `ingressUnavailable` on the account, and
`evaluateChannelHealth` reports `ingress-unavailable` ahead of every lifecycle
window. Absence stays "unknown", never "fine", so socketless and simply quiet
channels are untouched; no traffic-staleness heuristic is introduced.
The health monitor deliberately does not restart such an account. A denied or
unusable ingress queue is a capability/config failure that a restart cannot fix, and
the channel's own bounded backoff ladder already tried. Today that case instead loops
at the 10-restarts-per-hour cap forever, so this removes a restart treadmill rather
than adding one.
* fix(channels): keep automatic recovery for a dead-ingress account
The health-monitor skip latched a transient queue-open failure into a permanent
inbound outage: ingressUnavailable is only cleared by a later start, and skipping
the restart meant that start never happened. Report the dimension and name the
restart reason instead of suppressing recovery.
* fix(channels): scope the ingress dimension to running accounts
Shadowing not-running would have made readiness flap during the supervisor's
documented restart-backoff grace. The blind spot the dimension exists for is a
running channel with a live transport and dead inbound, so evaluate it there and
leave stopped accounts on their lifecycle reason. Also applies oxfmt.
* fix(channels): keep the ingress reason and give readiness its own backoff grace
Two review cycles pulled in opposite directions on the same bounded window: the
health policy must keep the ingress cause in the not-running state a failed start
actually lands in, while readiness must not flap during restart backoff. Put each
in its owner -- the policy states the truth, and readiness extends its existing
restart-handoff grace to the ingress reason. Once the ladder stops setting
restartPending, the account stays red instead of hiding dead inbound.
* style: reshape the readiness backoff predicate for oxfmt
* fix(ci): regenerate the docs map and drop an unused ingress export
The new health.md section needs docs/docs_map.md regenerated, and knip flagged
CHANNEL_INGRESS_UNAVAILABLE_CODE as an unused production export -- it only has
callers inside its own module.