* refactor(config): retire flat streaming keys from the last six channel schemas
signal, irc, nextcloud-talk, whatsapp, googlechat, and mattermost now accept
only the nested streaming.{chunkMode,block.enabled,block.coalesce} shape
(mattermost also drops scalar/boolean streaming); flat spellings migrate via
each channel's defineChannelAliasMigration doctor contract with root seeding
for their wholesale-replace account merges.
* feat(channels): warn once per key when the deprecated flat streaming fallback is used
Bundled schemas now reject the flat delivery keys, so the streaming.ts
fallback only serves external SDK plugin configs; emit a once-per-process
per-key subsystem warning and pin the removal plan to the next release train.
* chore(config): regenerate bundled channel config metadata for nested-only streaming
* docs: describe nested-only channel streaming config and the SDK flat-key deprecation window
* fix(whatsapp): seed migrated named-account streaming from the accounts.default layer
WhatsApp resolution layers accounts.default shared config between root and
named accounts, so doctor-materialized account streaming objects now inherit
default-account settings over root ones; mattermost schema test moves to the
nested-only shape with explicit rejection coverage; scope flat-key deprecation
notes to the pending Matrix/Feishu migrations.
* fix(whatsapp): resolve the default account case-insensitively when seeding migrated streaming
* chore(plugin-sdk): repin API baseline for nested-only channel streaming types
* fix(irc): chunk PRIVMSG by UTF-8 byte budget so long non-ASCII messages are not truncated
IRC caps a full protocol line at 512 bytes, but sendPrivmsg split outbound
text by UTF-16 code units against the 350 char default. Multi-byte text such
as CJK or emoji produced lines far beyond 512 bytes and servers silently
dropped the tail of every oversized chunk. The chunker now also enforces a
UTF-8 byte budget derived from the line framing overhead, splitting on code
point boundaries and preferring spaces, while ASCII chunking is unchanged.
* test(irc): move loopback IRC server helper to shared test helpers
The colocated node:net import tripped the network-runtime-boundary PR diff
scan for extensions/irc/src. The loopback server now lives in test/helpers,
outside the scanned network runtime paths, and the CJK, emoji, and ASCII
chunking cases keep driving the real client over a real TCP socket.
* fix(irc): decouple byte budget from character cap and move loopback helper to irc test-support
The byte budget is now derived only from the 512-byte line limit and framing
overhead, independent of messageChunkMaxChars, so low character caps with
multibyte text keep advancing instead of dropping the message. The loopback
IRC server helper moves from test/helpers to the extension-local package-root
test-support surface so extension tests stay off repo helper bridges and raw
socket use stays outside extensions/irc/src.
* fix(irc): enforce UTF-8 wire limits
* test(irc): satisfy loopback harness lint
* test(irc): avoid implicit Promise return
* test(irc): handle loopback close errors
* fix(irc): preserve boundary word splitting
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(irc): chunk PRIVMSG on UTF-16 boundary to avoid lone surrogates
sendPrivmsg split long messages with String.slice on a UTF-16 code-unit
index, so an emoji (or other astral character) straddling the split
point was cut into a lone high/low surrogate, sending broken bytes in
the PRIVMSG to the IRC server.
Slice each chunk with sliceUtf16Safe so a surrogate pair is never split.
When the budget is too small to fit even the leading astral character,
emit that character whole so chunking still makes progress.
Adds a socket-level test (fake net/tls socket) asserting no PRIVMSG
chunk contains a lone surrogate, including the one-code-unit budget
edge case, while the existing space-preferring split is preserved.
* refactor(irc): make surrogate-safe chunking direct
* fix(irc): preserve one-unit chunk limits
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
An IRC sender mask is nick!user@host where only host is server verified;
nick and user (ident) are client supplied and spoofable. The allowlist
identity classifier treated any entry containing "!" or "@" as a verified
stable identity, so a host-less nick!user entry was classified stable and
matched by the host-less nick!user subject candidate. With
dangerouslyAllowNameMatching at its secure default (off), the mutable
identifier policy only strips entries owned by a dangerous field, so the
host-less entry was never stripped and a remote sender presenting the same
nick and ident was admitted regardless of host.
Require a verified @host component before an entry or subject is classified
stable. Host-less nick and host-less nick!user are now both routed to a
dangerous (mutable) field so they are gated by the same name-matching policy.
The doctor mutable-allowlist detector now also flags host-less nick!user
entries so operators who typed that undocumented shape get a warning. The
documented full nick!user@host mask stays stable and unaffected.
Two-step decode in decodeLiteralEscapes:
- Step 1: regex anchored to high-surrogate range (U+D800–U+DBFF) so a
preceding BMP escape (e.g. \u0041) cannot consume the high-surrogate
half of a valid pair like \uD83D\uDE00 (😀), leaving \uDE00 lone.
- Step 2: decode remaining BMP codepoints; preserve lone surrogates as
six-character literals instead of corrupting them to U+FFFD in the
outbound IRC UTF-8 stream.
* fix(irc): prevent ghost nick collisions on rejoin after network delay
* test(irc): add regression tests for fallback nick uniqueness
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: wendy-chsy <wan.wenyan@xydigit.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* fix(irc): sanitize internal tool-trace lines from outbound text
* fix(irc): sanitize internal tool-trace lines from outbound text
---------
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
Followup nits from the #84711 review:
- Narrow the inspectTokenFile catch in
extensions/telegram/src/account-inspect.ts to FsSafeError so only
fs-safe validation throws map to configured_unavailable; any other
throw (programmer error, unexpected I/O) is rethrown.
- Add a regression test for the IRC NickServ password file symlink
rejection path (extensions/irc/src/accounts.ts:118), paralleling the
existing top-level passwordFile test.
- Add a regression test for the Telegram account-level tokenFile
symlink rejection path (extensions/telegram/src/token.ts:149),
paralleling the existing channel-level tokenFile test.
Behavior was already correct after #84711; this just locks coverage and
tightens the catch.
* fix(infra): restore symlink rejection in tryReadSecretFileSync
The local wrapper added in 9e4eca00ff swallowed all errors from
@openclaw/fs-safe@0.2.7's tryReadSecretFileSync via a bare try/catch,
silently downgrading every rejectSymlink: true caller (Telegram, LINE,
Zalo, IRC, Nextcloud Talk credential files) to accept symlinked
credential files. It also broke the infra-state CI shard's symlink
expectation that #84595 had just realigned with the new fail-closed
upstream contract.
Restore the direct re-export so the upstream contract surfaces:
undefined for blank/missing/not-found, FsSafeError for symlink,
oversize, non-regular file, and hardlink validation failures.
* test(plugins): align stale symlink tests with fail-closed contract
5 token/account resolver tests still asserted the pre-fs-safe-0.2.7
"silent skip" behavior (token: "", source: "none") on rejected symlinks;
they passed only because the swallow-all wrapper in secret-file.ts hid
the throw. Restoring the upstream fail-closed contract surfaces the
throw, so update the tests to expect FsSafeError.
inspectTelegramAccount reports credential status (its return type has an
explicit configured_unavailable state for "configured but unreadable"),
so its callsite is the right boundary to catch the FsSafeError and map
it to configured_unavailable rather than letting the throw bubble.
Affected:
- extensions/zalo/src/token.test.ts
- extensions/line/src/accounts.test.ts
- extensions/telegram/src/token.test.ts
- extensions/irc/src/accounts.test.ts
- extensions/nextcloud-talk/src/setup.test.ts
- extensions/telegram/src/account-inspect.ts (catch + report status)
Summary:
- The branch adds an opt-in Feishu top-level group-send fallback for withdrawn or missing normal quoted thread replies, plus regression coverage, a changelog entry, and CI/lint typing and baseline refreshes.
- Reproducibility: yes. at source level. Current main hard-errors withdrawn/not-found Feishu reply targets when `replyInThread` is true, and the existing regression test asserts that no top-level create fallback occurs.
Automerge notes:
- PR branch already contained follow-up commit before automerge: fix(feishu): fall back from missing thread replies
- PR branch already contained follow-up commit before automerge: fix(clawsweeper): address review for automerge-openclaw-openclaw-8030…
- PR branch already contained follow-up commit before automerge: fix(clawsweeper): reconcile automerge-openclaw-openclaw-80306 with ma…
- PR branch already contained follow-up commit before automerge: fix(ci): satisfy stricter lint and test types
- PR branch already contained follow-up commit before automerge: fix(ci): align Node 24 test typing
Validation:
- ClawSweeper review passed for head 93146f9d13.
- Required merge gates passed before the squash merge.
Prepared head SHA: 93146f9d13
Review: https://github.com/openclaw/openclaw/pull/80306#issuecomment-4415604729
Co-authored-by: Peter Steinberger <steipete@gmail.com>
Co-authored-by: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com>