Live-append voice transcripts into the agent session and persist a per-agent SQLite call record across relay and client transcript paths.
Add run-scoped spoken confirmation for high-impact actions, mutation digests, bootstrap-context injection, talk.client.transcript and talk.client.close protocol methods, and Control UI adoption. This adds zero new configuration.
Co-authored-by: Clifton King <clifton@users.noreply.github.com>
* feat(gateway): auto-approve trusted-proxy browser device pairing
Adds gateway.auth.trustedProxy.deviceAutoApprove so team gateways behind an
identity-aware proxy (Cloudflare Access, oauth2-proxy, Pomerium) can skip the
manual `openclaw devices approve` step for new Control UI/WebChat devices.
Auto-approval fires only for a new (unpaired) operator browser device on a
connection that already passed trusted-proxy auth with a resolved allowUsers
user. Scope upgrades on existing devices and node pairing stay manual. Granted
scopes are capped to the configured set intersected with the connection's
x-openclaw-scopes proxy cap, operator.admin is rejected at config validation,
and the pairing-store approval rechecks new-device status under the store lock
so a repair/upgrade or concurrent approval can never be silently widened. Each
auto-approval emits an audit log line with the proxy user and granted scopes,
and `openclaw security audit` warns when the mode is enabled.
* docs: regenerate docs map for trusted-proxy auto-approval section
* fix(gateway): emit config.changed from the reload committer for every accepted write
Agent config_set, CLI, and doctor writes reach the gateway through the file
watcher's reload path, which never broadcast config.changed - only direct RPC
writes did. Move the broadcast to a single onConfigCandidateCommitted hook in
the reload committer so all writers notify connected UIs, including
runtime-skipped commits (writer-intent echo suppression, reload mode off),
and delete the duplicate RPC-side broadcasts.
* feat(ui): sync chat follow-up and commentary prefs across devices
Add chatPersistCommentary and chatFollowUpMode to the ui.prefs sync surface
(schema, types, docs) with clearable null removal for the follow-up override.
Rewrite server-prefs around a descriptor table so a synced pref is one entry
instead of five hand-written code sites. Caption synced rows/sections
(chat prefs, theme, language) and refresh the stale Settings subtitle.
* refactor(ui): dedupe settings constants, drop dead composer branch, refresh copy and docs
Derive the Security tool-profile options from PROFILE_OPTIONS and General's
thinking levels from BASE_THINKING_LEVELS instead of parallel literals.
Remove the unused composer mode:"model" branch, the orphaned
.config-view-toggle CSS family, and stale quick-settings wording. Add static
settings-search entries for the Sessions and Managed Worktrees pages plus a
provenance-row test, and update docs/cli/openclaw.md to describe the current
system-agent config-write policy (denylist roots, route-backing plugin guard,
per-agent routing escalations).
* chore: refresh plugin SDK API baseline after rebase onto current main
- gateway broadcasts a hash-only config.changed event after every
persisted config write (operator.read scoped); the Control UI refreshes
its snapshot on the event so agent-approved ui.prefs changes apply live
on every connected client (skipped while a local draft is dirty)
- the chat composer's model picker shows provenance — 'Using default
from Settings' vs 'Session override' with an icon reset back to the
default — mirroring the existing reasoning-row anatomy
- the AI & Agents settings page is renamed Agent Defaults to separate it
from the per-agent Agents manager
- Memory Import renders through the settings design language (sections,
rows, toggle, status badges); only the collection review list, apply
report, skeleton, and confirm dialog keep custom markup, and the dead
bespoke CSS is pruned
* improve(diagnostics): surface run startup phases as session-correlated events
The embedded runner reports ordered startup milestones (workspace, auth,
context_engine, ..., model_call_started) through the private
onExecutionPhase callback, so they reach typing indicators and cron
watchdogs but never the diagnostic bus. External status surfaces that
already consume run.attempt / run.progress / model.call.* cannot see
where a turn is during startup — exactly the window that matters when a
run hangs before its first model call.
Wrap params.onExecutionPhase once at the runner entry so every phase
transition also emits a session-correlated run.execution_phase event
(runId, sessionId, sessionKey, closed EmbeddedAgentExecutionPhase +
provider/model/tool context). The event rides the async diagnostic lane
like the other high-frequency lifecycle events, so ordering against
model.call.* is preserved and no subscriber work runs on the hot path.
The wrapper tracks session rotation via onSessionIdChanged so
post-compaction phases carry the current session id. Stability records
project the milestone into their dedicated phase/provider/model fields.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(diagnostics): preserve trusted phase observability
* chore(plugin-sdk): refresh diagnostic API baseline
* fix(diagnostics): publish execution phases to plugins
* refactor(logging): keep public diagnostic recorder path
* fix(plugin-sdk): keep deprecated budget exact
* fix(plugin-sdk): refresh diagnostic surface budget
* fix(diagnostics): preserve execution phase coverage
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(secrets): use resolveStateDir for .env path to cover legacy .clawdbot dir
* fix(secrets): replace override-based legacy .env test with true automatic fallback, add apply-path coverage
* fix(config-cli): emit JSON error on config get --json for missing paths
When is used with a nonexistent
path, the command emits no JSON on stdout, only a text error on stderr.
This breaks automated consumers that expect machine-readable errors.
Fix: output JSON to stdout before exit(1) when
opts.json is set. This matches the existing JSON error output pattern
already used by runConfigValidate and handleConfigMutationError.
Also re-throw synthetic exit errors in the catch block so the
--json branch's writeRuntimeJson+exit is not caught and re-emitted
as a text error.
* fix(config-cli): use typed ExitError signal instead of __exit__ string sentinel
* fix(secrets): reuse resolved stateDir for .env path in audit
* fix(secrets): pass resolved stateDir into apply .env scrubbing, add root-switch regression
* test(config-cli): migrate remaining __exit__ sentinel to ExitError
* fix(secrets): cover config and state dotenv roots
* style(secrets): format dotenv path helper
* test(secrets): track temporary directories
* test(config): expect typed exit for missing patch files
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
Co-authored-by: Peter Steinberger <peter@steipete.me>
* refactor(ui): restructure Settings IA around user-facing pages
Settings sidebar shrinks to settings-only destinations grouped by attention:
General/Appearance/Notifications up top, Connections, Agents & Tools,
Privacy & Security, System. Sessions, Worktrees, Activity, and Memory
Import leave the takeover: Sessions/Activity return to the workspace
sidebar, Worktrees becomes a Sessions hub tab, Memory Import is reached
from the Agents memory panel. Model Setup becomes a Models subpage.
New config pages: Notifications (web push), Privacy & Security (curated
policy rows + security/approvals schema sections), Advanced (uncurated
schema sections + raw editor, replacing the General quick/advanced
toggle). General slims to model defaults, language, and gateway host;
identity moves to Profile, lobster appearance rows to Appearance.
* feat(agents): widen system-agent config writes to operator parity
Every config surface the Control UI can edit is now agent-writable behind
the exact-operation human approval gate, except a small documented
denylist ($include, auth, env, models, secrets). tools.* and
plugins.entries.* writes are allowed; plugin_uninstall works for plugins
that provably do not back the active default inference route (fail closed
otherwise); set_default_model takes an optional agentId and live-tests a
per-agent model with the same rigor as the default route. Per-agent
routing fields on non-default agents are direct-writable; the default
agent's route keeps the verified set_default_model/onboard paths. A new
config-write-parity contract test forces explicit classification of every
top-level config key.
* feat(ui): sync operator display prefs through config ui.prefs
Theme, theme mode, text scale, locale, and chat display prefs get a
canonical server-side home in config ui.prefs. The Control UI mirrors
them into localStorage for instant boot, writes local changes through to
the gateway (best-effort; viewer-scope or offline clients stay
device-local), and applies server-side deltas on connect and on every
config snapshot reload — so an agent changing the theme through the
approval gate takes effect in the UI, and prefs follow the operator
across devices.
* chore: prune quick.ts from max-lines baseline after slim-down
* style: oxfmt changed files
* chore: regenerate plugin-sdk api baseline for ui.prefs config surface
* fix: dedupe runPluginUninstall dep type and narrow locale extraction
* docs: update Control UI settings structure and ui.prefs sync
* fix: address autoreview findings on parity guards and prefs sync
- plugins.entries.* config writes get the same active-route ownership
check as plugin_uninstall (fail closed when unprovable)
- system-agent prompt now describes the guarded write policy instead of
the old blanket refusals
- per-agent set_default_model logs the agent-scoped label
- legacy /config?section=... deep links redirect to the Advanced page
- prefs sync: pushes coalesce and drain serially (no CAS races dropping
updates), replaced config hashes mark pre-patch snapshots as stale, and
the last-seen server value persists per gateway scope so reloads cannot
revert unpushable viewer-scope edits
- lint: theme membership checks use Sets
* fix: harden uninstall route guard and scope prefs queue per gateway
- plugin uninstall ownership now covers execution owners (runtime policy,
embedded harness override) in addition to the provider, and re-verifies
inside the commit boundary so a concurrent route switch cannot slip a
destructive uninstall past the pre-approval check
- prefs push queue binds to one gateway client; switching gateways drops
the old queue (device-local fallback) instead of cross-writing prefs
* style: top-level type import in config-page test
* fix: close review races in config-write guards and prefs reconciliation
- config_set/config_set_ref re-verify the inference-route guard inside
the commit boundary so conditional verdicts (per-agent routing, plugin
entries) cannot be flipped by a concurrent edit after approval
- server prefs apply per changed field, so a server change to one pref
no longer reverts unpushable local edits of the others
- replaced-hash staleness retires once post-patch state is observed, so
a genuine restore of the pre-patch config becomes authoritative again
- moved-section deep links (communications notifications, automation
approvals) redirect to their new pages
* style: satisfy no-useless-assignment and loop-condition lints in prefs sync
* fix(ci): resolve unhandled profile-test rejections and knip unused exports
The profile page's new identity section reads context.config; the test
context mock now provides it, eliminating the unhandled rejections that
failed checks-ui and the compact-large shard. Exports flagged by the
dependency gate (extract/patch helpers, section-key and prop types) go
module-private; extraction coverage moves behind applyServerUiPrefs.
* refactor(agents): move config-write policy to its own module
The static path classifier and documented denylist move to
config-write-policy.ts, shared by the execution guard and the parity
contract test — no test-only exports left for the dependency gate to
flag. Blocked-root refusals now cite the denylist's documented
escalation for that root.
* feat(slack): add user-identity config surface (session token/cookie)
Introduce an account-level identity discriminator ("bot" default | "user") for
the Slack channel, plus sessionToken/sessionCookie secret fields for the
browser-session (user) path. Schema enforces structural coupling only
(identity="user" rejects appToken/signingSecret/relay/non-default mode);
credential presence is deferred to runtime like bot/app tokens. Account entries
inherit the top-level identity. Adds env fallback (SLACK_SESSION_TOKEN/COOKIE,
default account only), secret-contract registration, provenance *Source fields,
and UI hints. Config surface only; resolver/transport are later phases.
* refactor(slack): identity-aware operation-token resolver + dedup
Route Slack read/write token selection through a single identity-aware
resolveSlackOperationToken: identity="user" selects the browser-session token
for both operations (companion cookie attached later at client construction),
identity="bot" keeps the exact userToken/botToken + userTokenReadOnly behavior.
Delete the duplicated token-selection gate in action-runtime and route direct
sends/DM opens through the shared resolver with identity-neutral error wording.
Behavior-neutral for bot identity; net non-test LOC -2.
* refactor(slack): retarget user identity to xoxp + companion app
Replace the abandoned browser-session (xoxc/xoxd) approach for identity="user"
with the official path: authenticate as the human via the existing userToken
(xoxp) while a companion app (appToken/socket or signingSecret/http) carries
events. Remove sessionToken/sessionCookie config, env vars, secret-contract
entries, resolver fields, and their tests. Invert the schema coupling so
identity="user" permits the companion-app transport credentials, and drop the
now-vestigial user-identity structural guard entirely (identity is a permissive
discriminator; credential presence is a runtime concern). Resolver returns the
user token for user-identity reads and writes. Behavior-neutral for bot
identity.
* feat(slack): user-identity provider transport (xoxp + companion app)
Wire the identity="user" event transport. The companion Slack app runs Socket
Mode (appToken) or HTTP (signingSecret) while Bolt authenticates as the human
via the user token (no bot token). Startup auth.test runs on the user token and
its user_id becomes the self/mention target and self-send dedupe id (Bolt's
lazy per-event authorize populates context.botUserId=user_id, so the existing
self-event middleware drops the human's own messages). Identity-aware token
requirements and diagnostics; the bot-token identity warning is suppressed for
user identity. Behavior-neutral for identity="bot".
* feat(slack): user-identity setup, doctor, and docs
Add the setup wizard, doctor/status, and documentation for identity="user".
The wizard collects a user token plus the companion-app transport credential
(app token for socket, signing secret for http) and only persists identity when
"user" so bot setups keep byte-identical generated config; doctor validates the
user token via auth.test and warns on a missing transport credential; account
inspection reports user-token status. Message actions are gated on the active
identity's credential so user accounts expose actions. Docs add the exact
companion-app recipe (user scopes, on-behalf-of-user message events, socket/http
examples, the DM/group-DM-only-via-user-scope note, and self-send dedupe).
Behavior-neutral for identity="bot".
* test(slack): isolate user-identity provider tests to fix parallel flake
The new user-identity provider tests shared the globalThis-stored Slack test
client/handlers across files under parallel isolate:false, so their reaction
closures could reference a prior file's state and intermittently fail sibling
monitor tests (monitor.tool-result reactions). Track started monitors and
abort/await them all in afterEach (even on failed assertions), reset the shared
auth.test/mock state, and dispose the global Slack test runtime per file via a
new disposeSlackTestRuntime helper. Full Slack suite green across repeated
parallel runs.
* fix(slack): regenerate channel config metadata after rebase
* fix(slack): resolve setup consistent-return and doctor dead-export lint
* style: format install script test
* docs(slack): regenerate docs map for user-identity section
* test(slack): use renamed createSlackBoltApp token param in interop test
The rebase folded main's new wrapReceiver test into the renamed
createSlackBoltApp signature (botToken -> token); update the call site.
* feat(onboarding): recommend plugins and skills from installed apps
Scan installed macOS apps during classic onboarding (TCC-free), gather
candidates from official catalogs + ClawHub search, let the configured
model pick genuine matches, and offer an opt-in multiselect install step.
Adds a device.apps node-host command (default-off sharing, Android-parity
envelope) so remote gateways can request a paired Mac's inventory, and a
wizard.appRecommendations kill switch. Custom setup-inference completions
no longer inherit the 32-token verification-probe output cap.
* feat(onboarding): recommend apps in guided flow
* fix(onboarding): harden app recommendations against ClawHub self-promotion
Third-party ClawHub skills are never pre-selected regardless of model tier
(publisher-controlled listing text reaches the matcher prompt and could
promote itself); their labels now say they install third-party code.
Installed-app scans follow symlinked .app bundles. Matcher output stays
bounded by the resolved model's own maxTokens budget (documented invariant).
* fix(onboarding): key official catalog candidates by resolved plugin id
Real catalog entries are package manifests without a top-level id; keying the
candidate map and channel/provider classification by entry.id collapsed the
whole official catalog into one undefined-keyed entry, so no official plugin
or channel was ever recommended. Regression test runs against the bundled
catalogs.
* fix(onboarding): satisfy lint, types, deadcode, and migration gates
Split the guided-onboarding test into a self-contained custodian suite to stay
under max-lines. Narrow app-recommendation exports (drop dead node-payload
normalizer, unexport internal types/helpers, route candidate tests through the
public API), replace map-spread with a helper, unexport device.apps result
types, add installedAppsSharing to node-host migration expectations, cast the
wizard multiselect mock, and regenerate the docs map.
* test(onboarding): register new live test in the shard classifier
* feat(onboard): guided onboarding becomes the custodian flow
Question zero asks for discovery consent up front (full access recommended,
guarded asks once or configures manually; persisted as wizard.accessMode and
honored on reruns). Discovery quietly collects auto-ladder failures into one
summary with details behind 'See other options', quips about detected coding
agents, and announces the working route as a default with an easy undo.
After inference and the memory offer (skipped when discovery was declined),
fresh installs auto-apply the standard setup plan and hatch the agent
directly in the terminal; configured installs (pre-existing model or gateway
config) stay verification-only and never reinstall or restart the Gateway
service. Setup-apply failures fall back to the conversational chat.
Remote-gateway onboarding keeps its legacy chat handoff explicitly.
listManualSetupInferenceOptions lists manual providers without scanning.
* docs(onboarding): add custodian redesign implementation plan (living doc)
* docs: regenerate docs map for onboarding-redesign page