Regression: the drain IIFE finally (`drain.ts:263-271`) performed an
unconditional `FOLLOWUP_QUEUES.delete(key)` + `clearFollowupDrainCallback(key)`
using only the key, without checking whether the captured `queue` still matched
the map entry. Under the `/stop` + immediate followup sequence, a late-returning
D1 finally could delete the map entry for a fresh Q2 and orphan it until the
next enqueue.
Fix: only remove the map entry and drain callback when `FOLLOWUP_QUEUES.get(key)
=== queue`. Mirrors the identity pattern noted in
`subagent-announce-queue.ts:62-64`.
Adds `src/auto-reply/reply/queue/drain.identity-guard.test.ts` which uses real
`enqueueFollowupRun` / `scheduleFollowupDrain` / `clearSessionQueues` (no
module mocks) and a Deferred gate to park D1 inside `runFollowup`. The test
uses `restartIfIdle=false` on the Q2 enqueue so D1's finally is the only
mutator that can touch the map entry, producing deterministic pre/post-fix
differentiation:
pre-fix : get(key) === undefined (Q2 orphaned), depth === 0
post-fix : get(key) === Q2, depth === 1
AI-assisted (fully tested). 1082 auto-reply/reply tests pass, pnpm check +
pnpm build clean.
Rewrites the always-on reply handling so group/channel rooms default to message-tool-visible output, while `messages.groupChat.visibleReplies: \"automatic\"` preserves legacy auto-posting.\n\nThanks @scoootscooob.
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>
* improve trace raw diagnostics and command acks
* address trace review feedback
* avoid sync transcript reads in raw trace
* preserve raw cli output for trace
* gate trace emission at reply time
* reflect raw trace mode in status surfaces
After a drain loop empties the queue it deletes the key from
FOLLOWUP_QUEUES. If a new message arrives at that moment
enqueueFollowupRun creates a fresh queue object with draining:false
but never starts a drain, leaving the message stranded until the
next run completes and calls finalizeWithFollowup.
Fix: persist the most recent runFollowup callback per queue key in
FOLLOWUP_RUN_CALLBACKS (drain.ts). enqueueFollowupRun now calls
kickFollowupDrainIfIdle after a successful push; if a cached
callback exists and no drain is running it calls scheduleFollowupDrain
to restart immediately. clearSessionQueues cleans up the callback
cache alongside the queue state.