Repository navigation
Commit 42a9b4d
feat(remote-connect): push weixin bot output from non-inbound turns
The Weixin iLink bot only ever answered messages it received: the sole send
path was the inbound forward (handle_incoming_message -> execute_forwarded_turn
-> send_text). Assistant output produced by a turn the bot did not start - a
scheduled job, the desktop window, another controller - was never delivered, so
a scheduled job in the peer's bound session could run to completion while the
user's phone stayed silent.
Subscribe the bot to AgenticEvent::DialogTurnCompleted and deliver the turn's
text to the peer bound to that session. A turn is skipped when this bot started
it, because the inbound path already answered it and pushing it again would
deliver every inbound message twice. Failed turns are skipped too; whether there
is anything to send is decided by the turn's own text, so a degraded turn that
still carries an explanation is not lost. The text is read through the existing
replay-safe observe_turn projection, so bot delivery agrees with what the other
remote surfaces show.
Delivery respects the channel reply quota. The OpenBitFun product copy for this
channel records that WeChat ClawBot allows the bot at most 10 replies in the 24
hours after the user sends a message, and that a split long reply spends one
reply per part. That quota is also what answers the user's own messages, so this
path:
- merges a peer's whole backlog into a single reply instead of spending one
reply per turn, keeps the turns in time order, and separates them readably;
- caps the merged payload at one channel part, dropping the oldest output first
and truncating the newest on a character boundary only when it alone exceeds
the cap;
- keeps its own share of 3 replies per rolling 24 hour window, which it can
never overspend, leaving the rest of the channel quota untouched;
- counts the replies a push will actually spend - the parts the channel splits
it into - and refuses to send when the share cannot cover them, holding the
backlog until the window rolls instead;
- still queues when no context_token is available and keeps the backlog when a
send fails on a stale token, so output waits for the next inbound message
rather than being dropped.
The backlog is bounded per peer, by age, and by merged payload size. One worker
per bot drains it, which preserves order and keeps two triggers from delivering
the same queued message. The subscriber spawns its work instead of running it
inline: the event router awaits every subscriber while this path reads the
session and sends over the network. is_lifecycle_current() is checked on entry,
after the session read, and in the worker loop, so a replaced bot cannot push,
and the subscription id is unique per instance so a retired bot unsubscribes
only its own hook.
Reuses send_text / send_text_chunks and the existing context_tokens cache; no
protocol change. weixin_reply_count() is added next to chunk_text_for_weixin so
the predicted reply count cannot drift from what send_text_chunks sends.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>1 parent 3f59ad5 commit 42a9b4d
2 files changed
Lines changed: 787 additions & 5 deletions
0 commit comments