* fix(gateway): restart channels after secret reload
* fix(gateway): serialize secrets.reload and isolate channel restart errors
Address review feedback from Greptile (P1), Codex (P2), and Aisle (Medium,
CWE-362) on #70720:
- Serialize the entire secrets.reload path through a promise tail lock so
concurrent callers cannot overlap the stop/start loop or diff against a
stale pre-activation snapshot.
- Wrap each channel's stop/start pair in a try/catch so one channel failing
to restart does not leave other changed channels unrestarted.
- Register slack/zalo/discord channel plugins with reload.configPrefixes in
the test setup so channels.<id>.* diff paths actually match a restart rule
(without this, the diff falls through to restart-gateway and the handler
never enters the per-channel restart branch).
- Add tests covering concurrent-reload serialization and per-channel
restart-failure isolation.
* fix(gateway): surface channel restart failures from secrets.reload
Address review feedback on the previous commit:
- Codex P1: `secrets.reload` swallowed per-channel restart failures and
still returned `{ ok: true }`, so a rotation that left a channel on the
old secret looked successful to the caller. The handler now collects
restart failures during the loop and throws an aggregate error after
attempting every channel, so the client-side RPC response surfaces the
partial failure while unaffected channels still restart (preserving the
original Greptile P1 non-cascading semantic).
- Greptile P2: test mock-call assertions sorted the captured channel
arguments so they no longer depend on `Set`/object-key iteration order,
which is not a stable contract of the handler.
* fix(gateway): harden secrets reload followups
* docs(changelog): note secret-backed channel restart on secrets.reload
* test(gateway): align secrets reload snapshot activation
* test(gateway): reset plugin runtime state in aux handlers
* fix(gateway): refresh reload rules and roll back channels
* fix(gateway): harden secrets.reload rollback tests
* test(gateway): inject aux handler reload plan
* test(gateway): avoid resettable reload-plan mocks
* test(gateway): isolate aux handler tests from skip env-var leakage
test-helpers.mocks.ts and test-helpers.server.ts set
OPENCLAW_SKIP_CHANNELS=1 / OPENCLAW_SKIP_PROVIDERS=1 at module load. When
a shared vitest worker imports those helpers before this file's tests
run, the leaked env vars route the secrets.reload skip-mode branch and
the channel restart loop never fires. Add a beforeEach that clears both
env vars so the suite is independent of worker import order.
* fix(gateway): restore required generation on secrets.reload rollback
setCurrentSharedGatewaySessionGeneration can clear `required` as a side
effect of activating a new generation. The previous rollback path
restored only `current`, leaving `required` cleared and weakening
shared-gateway auth-generation enforcement after a failed reload (Aisle
CWE-287). Capture both fields before activation and restore both in the
catch block. Add a focused regression test that locks in the contract.
* fix(gateway): track restart channels for rollback before stopChannel awaits
Pushing to stoppedChannels only after `await stopChannel` succeeded meant
that if stopChannel rejected mid-call (for example, a plugin stopAccount
hook throws after the runtime already closed the socket), the rollback
loop skipped that channel entirely. A failed secrets.reload could then
leave the channel down. Track the channel before awaiting so rollback
always attempts to bring it back, and add a regression test.
Export diagnostics OTEL logs through bounded diagnostic log events while keeping core log records off the public plugin diagnostic stream.\n\nIncludes security hardening for log payload redaction, bounded attributes, prototype-pollution keys, OTEL export failure reporting, and extension SDK seam usage.
* fix(slack): route stream-fallback delivery through chunked sender
deliverPendingStreamFallback was calling chat.postMessage directly for
err.pendingText, which bypasses the chunked reply path used everywhere
else. For Slack Connect cases where appendSlackStream throws
SlackStreamNotDeliveredError with a large pending buffer, the single
raw post could fail (msg_too_long) and drop the unsent tail.
Two changes:
1. deliverPendingStreamFallback now routes through deliverReplies so
long pendingText is chunked by the normal sender and the fallback
honors the configured replyToMode / identity.
2. The non-benign streaming-error branch in deliverWithStreaming now
clears the session via markSlackStreamFallbackDelivered before
falling back to deliverNormally. Without this, pendingText stays
populated and the post-loop finalize (stopSlackStream →
SlackStreamNotDeliveredError → fallback) re-posts the same chunk
that deliverNormally already sent.
Addresses the three Codex P1 findings on #70370 about bypassing the
chunked sender, and the related "avoid reposting buffered text after
append fallback" P1 about duplicate delivery. Tests updated to assert
deliverReplies routing (instead of raw postMessage) and a new case
covers the non-benign-error dedup.
Follow-up to #70370.
* fix(slack): preserve pending buffered text on non-benign stream errors
Address Codex P1 on #71124: `markSlackStreamFallbackDelivered` was
clearing `pendingText` before `deliverNormally` ran, so any earlier
buffered chunk was lost. E.g. chunk A buffered in the SDK, then
appending chunk B throws a generic network error → previous fix
dropped A+B and only sent B via `deliverNormally`, silently truncating
the final reply.
Route the full buffered `pendingText` through
`deliverPendingStreamFallback` with a synthetic
`SlackStreamNotDeliveredError`, then skip `deliverNormally` entirely
(pendingText already contains this payload's text, per
`appendSlackStream` accumulating before throw). If the chunked
fallback fails, fall back to `deliverNormally` so at least the current
payload lands.
Test updated to assert the full pendingText ("first buffered\nsecond
payload") gets routed through the chunked sender, not the
chunk-B-only partial send.
* fix(slack): harden stream fallback docs and chunking test (#71124)
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
Verified against Carbon 0.16.0 source:
- Client constructor calls plugin.registerClient(this) without awaiting it.
- GatewayPlugin.registerClient publishes client before its awaited metadata fetch.
- identify() silently returns when client is missing.
This patch matches Carbon's ordering in OpenClaw's subclass, avoids a second super.registerClient call if lifecycle connect already opened the socket during metadata loading, and keeps regression coverage for both ws and isConnecting cases.
Local proof:
- pnpm test extensions/discord/src/monitor/provider.proxy.test.ts extensions/discord/src/monitor/gateway-plugin.test.ts
- pnpm lint:tmp:no-raw-channel-fetch
- pnpm check:changed
- pnpm check
- pnpm test
GitHub checks green for 72547825e1.
Fixes outbound Twilio realtime conversations so the TwiML fetch returns the realtime <Connect><Stream> path for outbound directions and the answered-call path does not overwrite it with legacy <Say> TwiML.
Local proof:
- pnpm test extensions/voice-call/src/manager.notify.test.ts extensions/voice-call/src/webhook.test.ts
- pnpm check:changed
- pnpm check
- pnpm build
- local VoiceCallWebhookServer + CallManager smoke for Direction=outbound-api
Closes#68713.
Keep WebChat runtime context available to the model while persisting only the transcript-facing user prompt across gateway, CLI, queued follow-up, and embedded Pi paths.
Adds regression coverage for history sanitization, CLI transcript persistence, media-only auto-reply prompts, and embedded Pi prompt rewrite against a real SessionManager file.
Co-authored-by: 91wan <91wan@users.noreply.github.com>
Render assistant text avatars from IDENTITY.md consistently in the Control UI chat welcome state and transcript groups.
Also supports authenticated blob avatar URLs in grouped messages and rejects bidi/invisible controls in assistant text avatars.
Verification:
- pnpm test ui/src/ui/chat/grouped-render.test.ts ui/src/ui/views/chat.test.ts ui/src/styles/chat/layout.test.ts
- pnpm check:changed
- GitHub CI green
- Review threads resolved
Keep WhatsApp QR login state synced across gateway, macOS, and UI wait flows.
- Preserve the latest QR data URL/version while login polling rotates codes.
- Keep the wait-result protocol bounded to current QR metadata.
- Stabilize QR rendering and media fixture coverage after rebasing on main.
Validation:
- pnpm test extensions/whatsapp/src/login-qr.test.ts extensions/whatsapp/src/media.test.ts extensions/whatsapp/src/agent-tools-login.test.ts src/gateway/protocol/channels.schema.test.ts src/gateway/server-methods/web.start.test.ts ui/src/ui/controllers/channels.test.ts
- pnpm test:extension whatsapp
- cd apps/macos && swift test --filter ChannelsSettingsSmokeTests
- GitHub PR checks: 62 success, 5 skipped