|
| 1 | +# Channel Session-Aware Delivery |
| 2 | + |
| 3 | +## Problem |
| 4 | + |
| 5 | +Channel sessions are already independent, but a few adapters keep delivery-only |
| 6 | +state at chat or process scope. When two sessions run in the same chat, a later |
| 7 | +message can replace the reply target of an earlier QQ response, one completed |
| 8 | +Weixin session can clear another session's typing indicator, and the plugin |
| 9 | +example can associate overlapping responses with the wrong inbound message. |
| 10 | + |
| 11 | +## Scope |
| 12 | + |
| 13 | +This change makes existing same-chat delivery state session-aware. It does not |
| 14 | +add named sessions, task selection, session persistence, result labels, or |
| 15 | +worktree creation. It does not change `ChannelBase` or the public channel |
| 16 | +adapter interface. |
| 17 | + |
| 18 | +`ChannelOutputSegmentContext` already carries the originating session, run, |
| 19 | +target, and message ID. `ChannelBase` also retains the active prompt's message |
| 20 | +ID until response delivery completes. Adapters can therefore preserve the |
| 21 | +origin without introducing another shared delivery abstraction. |
| 22 | + |
| 23 | +## QQ |
| 24 | + |
| 25 | +QQ passive replies require the original inbound `msg_id`, and streaming blocks |
| 26 | +for that message share a `msg_seq` counter. The adapter keeps a bounded |
| 27 | +in-memory context for every recently accepted message while retaining the |
| 28 | +persisted per-chat latest message as a compatibility pointer. |
| 29 | + |
| 30 | +Prompt output uses the message ID from the active prompt or output segment. |
| 31 | +Streaming state copies that reply context so delayed flushes and retries do not |
| 32 | +depend on mutable chat state. Replies produced while handling an inbound |
| 33 | +command use async-local inbound context. Background and cron delivery is |
| 34 | +explicitly active and never borrows the latest inbound message. |
| 35 | + |
| 36 | +An expired or missing explicit context falls back to active delivery, never to |
| 37 | +a different message in the same chat. Sequence counters remain isolated by |
| 38 | +message ID and are reclaimed with the message context's existing five-minute |
| 39 | +TTL. The persisted schema is unchanged; restore keeps sequence counters only |
| 40 | +for restored valid reply contexts. |
| 41 | + |
| 42 | +## Weixin |
| 43 | + |
| 44 | +Weixin typing ownership is tracked as `chatId -> sessionId -> startedAt`. |
| 45 | +Starting the first session enables typing and starts one chat-level keepalive. |
| 46 | +Additional sessions only add owners. A terminal event removes its own owner, |
| 47 | +and only the last owner disables typing. |
| 48 | + |
| 49 | +The existing generation guard continues to reject stale asynchronous typing |
| 50 | +results. The keepalive backstop expires individual sessions rather than the |
| 51 | +whole chat, so an old wedged session cannot clear a newer session's indicator. |
| 52 | +Session death removes only that session; disconnect clears all state. |
| 53 | + |
| 54 | +## Plugin example |
| 55 | + |
| 56 | +The example replaces its process-global pending message ID with async-local |
| 57 | +inbound context for command replies and uses output-segment or active-prompt |
| 58 | +message IDs for agent output. This demonstrates the same correlation contract |
| 59 | +to third-party adapter authors without changing the protocol. |
| 60 | + |
| 61 | +## Failure semantics |
| 62 | + |
| 63 | +- A missing or expired QQ passive context uses active delivery and remains |
| 64 | + subject to the existing active-message policy. |
| 65 | +- A failed QQ passive attempt rolls back only its original message's sequence |
| 66 | + before the existing active fallback. |
| 67 | +- A failed Weixin typing request does not discard live owners; the bounded |
| 68 | + keepalive retries while at least one owner remains. |
| 69 | +- Cleanup on session death, group removal, and disconnect cannot remove state |
| 70 | + owned by another live session. |
| 71 | + |
| 72 | +## Verification |
| 73 | + |
| 74 | +Focused tests cover two sessions in one chat, delayed streaming flushes, |
| 75 | +independent QQ sequence rollback, expired reply contexts, background delivery, |
| 76 | +Weixin first-owner/last-owner transitions, stale async typing results, |
| 77 | +per-session backstop expiry, and overlapping plugin messages. Package tests are |
| 78 | +followed by the repository build, typecheck, and lint checks. |
0 commit comments