Codex P1: resolveLastToRaw's inter-session sentinel was unconditionally
returning persistedLastTo. When resolveLastChannelRaw falls back to a
session-key hint (e.g. agent:navi:discord:direct:* → 'discord') because
persistedLastChannel is not yet external (e.g. still 'webchat'), blindly
returning persistedLastTo creates a mismatched lastChannel/lastTo pair
(discord channel + stale webchat target), causing subsequent 'last'
replies to be routed to an invalid destination.
Fix: mirror resolveLastChannelRaw's sentinel logic — only preserve
persistedLastTo when persistedLastChannel is already an external routing
channel (consistent pair). Return undefined otherwise so the caller
derives an appropriate target from the resolved channel.
Adds two new tests:
- 'returns undefined when channel resolves via session-key hint'
- 'preserves persistedLastTo when persisted channel is already external'
21/21 session-delivery tests, 19/19 sessions tests, 23/23 install tests.
Codex P2: validatePluginId in install.ts only blocks the npm package
install path. A channel loaded via discovery/config can still call
registerChannel with id='inter_session', bypassing the guard.
- Move RESERVED_CHANNEL_IDS to message-channel.ts (canonical location
for channel constants) and export it
- Import RESERVED_CHANNEL_IDS in both install.ts and registry.ts
- Add guard in registerChannel: push error diagnostic and return early
if a plugin attempts to register a reserved channel ID
All three reservation paths are now covered:
1. Package install (validatePluginId in install.ts)
2. Runtime channel registration (registerChannel in registry.ts)
3. isInterSessionChannel deliverability guard (existing)
60/60 tests green.
Codex P2: a plugin named 'inter_session' would make
isInterSessionChannel return false (deliverable guard), silently
disabling the sentinel routing path in sessions_send.
Add RESERVED_CHANNEL_IDS set to validatePluginId() covering both
INTER_SESSION_CHANNEL and INTERNAL_MESSAGE_CHANNEL. Any attempt to
install a channel plugin with those ids now returns a clear error.
Adds regression test covering both sentinel ids.
Greptile: originatingChannel was computed before the isInterSessionChannel guard
but never used inside it, wasting a normalizeMessageChannel call on the fast path.
Moved the declaration to after the guard where it is actually needed.
Codex P2: isInterSessionChannel had no protection against a real deliverable plugin
channel named 'inter_session'. Added isDeliverableMessageChannel check so that if a
plugin registers that id, the sentinel path is skipped and normal routing applies.
Test: added invariant test documenting the Codex P2 guard (19 tests green).
Codex P2 on #43353: threading agentChannel alone without paired to/accountId/threadId
causes resolveLastToRaw to fall back to a stale persistedLastTo, producing mismatched
channel+to state (e.g. channel=discord but routing to an old Telegram destination).
Instead, use a new INTER_SESSION_CHANNEL ("inter_session") sentinel. Both
resolveLastChannelRaw and resolveLastToRaw handle it explicitly:
- resolveLastChannelRaw: returns the persisted external channel (or session-key hint)
without injecting the sender's channel or flipping to webchat
- resolveLastToRaw: returns persistedLastTo, preserving the receiver's destination
The sender's agentChannel is still captured in inputProvenance.sourceChannel for
observability but is no longer used to mutate the receiver's routing state.
Tests:
- sessions.test.ts: updated two agentChannel-threading tests to assert inter_session
sentinel is used (not webchat, not sender channel)
- session-delivery.test.ts: 6 new tests covering INTER_SESSION_CHANNEL routing cases
including the original webchat-flip regression and the stale-to preservation
Fixes https://github.com/openclaw/openclaw/issues/43318
Add two regression tests for #43318:
- asserts that when agentChannel is provided, the 'agent' gateway call
receives that channel (not 'webchat') in sendParams
- asserts that when agentChannel is absent, channel falls back to webchat
(preserving existing behaviour for internal spawns)
Closes#43318
When sessions_send injects a message into a target session, it was
hardcoding channel: INTERNAL_MESSAGE_CHANNEL ('webchat'). For main
sessions with an active external channel route (Discord, Telegram etc),
this caused resolveLastChannelRaw to flip lastChannel to 'webchat' for
that turn, silently routing the agent's reply to the control UI instead
of the user's channel.
Fix: pass opts?.agentChannel ?? INTERNAL_MESSAGE_CHANNEL so external-
originated A2A sends carry the correct source channel through. Falls
back to INTERNAL_MESSAGE_CHANNEL for internal spawns with no agentChannel.
Fixes: openclaw/openclaw#43318
Preserves explicit `supportsUsageInStreaming` overrides from built-in provider
catalogs and user config instead of unconditionally forcing `false` on non-native
openai-completions endpoints.
Adds `applyNativeStreamingUsageCompat()` to set `supportsUsageInStreaming: true`
on ModelStudio (DashScope) and Moonshot models at config build time so their
native streaming usage works out of the box.
Closes#46142
Co-authored-by: pezy <peizhe.chen@vbot.cn>