* feat(migrate): add Hermes memory-only import and a shared memory-import core
* feat(onboarding): offer detected memory imports during CLI setup and guided onboarding
* feat(ui): show a first-run memory-import offer in Control UI onboarding mode
* feat(linux-app): open the first-run dashboard in onboarding mode
* feat(macos): add the onboarding memory-import page
* docs: document the onboarding memory-import page across surfaces
* chore(i18n): translate onboarding memory-import strings for control-ui and native locales
* refactor: keep memory-import internals unexported for deadcode gates
* fix(ci): resolve lint findings in onboarding memory import
* chore(i18n): refresh native inventory after lint refactor
* fix(ci): skip updater artifacts in the unsigned Linux companion PR lane
* chore(i18n): reconcile locale artifacts after rebase
* fix(xai): cancel realtime reconnect backoff on close
* test(xai): cover reconnect after bridge close
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
Without OP_LOAD_DESKTOP_APP_SETTINGS=false and OP_BIOMETRIC_UNLOCK_ENABLED=false,
op 2.35 on macOS reads the 1Password desktop app's settings even on the
service-account path and can block on a per-PID App Data Protection dialog,
hanging the broker until timeoutMs on Mac gateway hosts.
* fix(discord): open latest activity widget on fallback
* chore: remove release-owned changelog entry
* fix(discord): order activity fallback by delivery
* fix(discord): require atomic widget delivery updates
* test(discord): type activity delivery fixtures
* test(discord): model missing atomic state support
* fix(discord): order activity fallback by message snowflake
* docs(discord): clarify activity fallback order
* fix(feishu): honor abortSignal in drive comment reply retry loop
The inter-attempt delay in monitor.comment.ts used a bare setTimeout and
never received the owning monitor account's AbortSignal, so an account
teardown (ws disconnect, webhook stop, config reload) left the in-flight
comment reply retry running for up to ~6s after the rest of the lifecycle
settled. delayMs now wraps waitForAbortableDelay from async.ts and the
abortSignal is threaded through fetchDriveCommentContext →
resolveDriveCommentEventTurn → handleFeishuCommentEvent →
createFeishuDriveCommentNoticeHandler, sourced from
monitorSingleAccount.
The retry loop checks the waitMs return value and the abortSignal flag,
breaking out of the next 1s tick instead of running the full 6×1s poll
budget. Sibling surface monitor.bot-identity.ts already uses the same
pattern; #104431 explicitly skipped the comment-monitor retry because
it is a result-predicate poll, not an error retry, and this fix is
orthogonal to that decision.
Not tested: live Feishu tenant, real bot identity, real drive comment,
and account restart. requestFeishuOpenApi itself does not accept
AbortSignal — that is a separate, larger refactor and is out of scope.
* test(feishu): prove comment retry abort
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(acpx): migrate codex adapter to maintained @agentclientprotocol/codex-acp
The bundled codex ACP adapter pinned the npm-deprecated
@zed-industries/codex-acp@0.16.0, which embeds codex crates from the
rust-v0.137.0 era: GPT-5.6-era models are unknown to it and sessions fail
with 'requires a newer version of Codex'. Migrate to the maintained
@agentclientprotocol/codex-acp@1.1.4 (Node CLI, depends on @openai/codex
^0.144.4 matching the codex plugin pin):
- Wrapper generation resolves the adapter bin from its package manifest and
delivers startup config via CODEX_CONFIG; OpenClaw-owned wrapper args are
stripped before launch. Explicit new-adapter commands keep their own
-c/--config args untouched; only retired Zed commands migrate, with their
-c TOML overrides (dotted/quoted keys, attached forms, MCP config)
faithfully converted.
- Model overrides emit the bare model id plus a separate
model_reasoning_effort; composite model/effort ids are accepted as parse
input but never emitted.
- Process reaper recognizes the maintained adapter tree and packaged codex
app-server children without claiming native codex plugin processes.
Live proof: direct adapter smoke with local subscription auth initialized
1.1.4, advertised model gpt-5.6-sol with separate medium effort, and
completed a turn. Fixes#108664
* fix(acpx): pin codex-acp 1.1.2 to satisfy the lockfile release-age gate
1.1.4 is under the 48-hour minimumReleaseAge and fails lockfile
verification repo-wide. 1.1.2 (2026-07-09) passes the gate, carries the
same CODEX_CONFIG startup contract, and its ^0.144.0 codex dependency
resolves to the same 0.144.4 the codex plugin pins. Lockfile re-resolved
from main without any release-age bypass; shrinkwrap regenerated.
* chore(acpx): split oversized auth-bridge test file, register adapter as CLI-resolved dep
codex-auth-bridge.test.ts crossed the max-lines gate after the migration
regressions; the command-migration cluster moves to
codex-auth-bridge.migration.test.ts. knip's CLI-resolved dependency list
swaps the retired zed adapter for @agentclientprotocol/codex-acp (invoked
via generated wrapper/npx, never imported).
* fix(openai): cancel realtime reconnect backoff on close
* test(openai): cover reconnect after bridge close
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(acp): skip unsupported automatic thinking config in manager gate
When an ACP backend does not advertise thinking-related config options
(e.g. OpenCode advertising only mode/model), the manager's automatic
startup-control path would reject the session with
ACP_BACKEND_UNSUPPORTED_CONTROL.
Fix:
- Add THINKING_CONFIG_KEYS set and isThinkingConfigKey helper to
manager.runtime-controls.ts, following the existing OPTIONAL_TIMEOUT_CONFIG_KEYS pattern
- Skip thinking keys in the advertised-keys validation gate (auto-derived
options are best-effort)
- Skip unsupported-control rejection for thinking keys at the
setConfigOption catch level
- Extract isUnsupportedControlRejection helper to avoid duplicating the
error code check
- Forward getCapabilities input handle in ACPX runtime so session-scoped
capability queries work
- Inject thinking/thought_level aliases for Codex ACP sessions when
reasoning_effort is advertised
Closes#103802
* fix(acp): honor thinking control capabilities
---------
Co-authored-by: Erick Kinnee <erick@ekinnee.dev>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(gradium): treat blank env API key as unconfigured in synthesis paths
The isGradiumProviderConfigured helper already uses trimToUndefined on
GRADIUM_API_KEY, but synthesize and synthesizeTelephony still used the
bare env var. This made whitespace-only credentials pass the
missing-key guard and send unusable bearer tokens.
- Wrap process.env.GRADIUM_API_KEY with trimToUndefined in synthesize
- Wrap process.env.GRADIUM_API_KEY with trimToUndefined in
synthesizeTelephony
- Add test for whitespace-only env key rejection in synthesize
This completes the blank-credential guard for the Gradium provider,
matching the pattern from OpenAI (#108212), Xiaomi (#108558),
Deepgram (#108565), and Inworld (#108783).
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(gradium): centralize speech API key resolution
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(inworld): treat blank env API key as unconfigured in speech provider
A whitespace-only INWORLD_API_KEY made the Inworld speech provider
report configured, then sent an unusable bearer credential from
synthesis, telephony synthesis, and voice listing.
- Wrap process.env.INWORLD_API_KEY with trimToUndefined in isConfigured
so blank values are treated as not configured
- Wrap process.env.INWORLD_API_KEY with trimToUndefined in synthesize,
synthesizeTelephony, and listVoices so blank values are rejected
locally before any network request
- Add test coverage for whitespace-only env key in isConfigured and
synthesize paths
This matches the blank-credential guard pattern applied in the OpenAI
(#108212), Xiaomi (#108558), and Deepgram (#108565) providers.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(inworld): centralize speech API key resolution
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(xiaomi): treat blank env API key as unconfigured in speech provider
A whitespace-only XIAOMI_API_KEY made the Xiaomi MiMo speech provider
report configured, then sent an unusable bearer credential from synthesis.
- Wrap process.env.XIAOMI_API_KEY with trimToUndefined in isConfigured
so blank values are treated as not configured
- Wrap process.env.XIAOMI_API_KEY with trimToUndefined in synthesize
so blank values are rejected locally before any network request
- Add test coverage for whitespace-only and blank env key in both paths
This matches the blank-credential guard applied in the OpenAI speech
provider (PR #108212).
* refactor(xiaomi): centralize speech key resolution
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* feat(canvas): let widget buttons send prompts into web chat
Inline show_widget documents get a sendPrompt(text) bridge: a private
MessageChannel offered to the Control UI before widget code runs. The chat
adopts only the first offer per frame, requires transient user activation,
a visible focused frame, plain text (no slash commands), and rate limits
10 prompts/minute per widget. Accepted prompts run the normal user send
path of the owning chat pane.
* fix(canvas): appease lint/knip/docs gates for widget prompts
Split widget prompt tests into their own file, drive validation and rate
limits through the real port path instead of exported internals, use
addEventListener on the adopted port, and regenerate docs_map.
* fix(canvas): install widget listeners per window for non-isolated tests
Module-boolean listener flags broke in shared vitest workers where each test
file gets a fresh jsdom window; key installation by window instead and give
port flushing more headroom.
* feat(presentation): support hosted widget actions
* refactor(discord): route widget launches through presentation
* chore: keep changelog release-owned
* test(interactive): type the invalid web-app boundary case explicitly
* test: type web-app action fixtures against the split union
The two-member web-app union no longer accepts a distributed
type: "url" | "web-app" literal or an empty receipt in typed
callbacks; use explicit action casts and the Discord receipt factory.
* fix(clickclack): commit websocket cursors after processing
Serialize realtime events and wait for queued handlers before reconnecting so only successfully processed cursors advance.
Co-Authored-By: OpenAI Codex <noreply@openai.com>
* fix(clickclack): replay failed websocket events
Keep the last successful cursor when realtime handling fails, then reconnect through durable backlog recovery before reopening the stream.
Co-authored-by: 詹幸心0668001037 <zhan.xingxin@xydigit.com>
---------
Co-authored-by: OpenAI Codex <noreply@openai.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>