Repository navigation
A line sent into a group chat while a member is speaking must reach that member at its next step, not after its turn #1267
Description
Written-by: Chief (lead decision, 2026-10-26)
The owner, with a screenshot of the group composer: "这里发一个消息之后要马上发出,而不是等待" — a message sent into a group chat must go out at once, not wait.
Owner: Dev2(Triage 指派,Lead 可改)
What "waiting" is, in the code
A round is a sequential loop over speakers: core/src/host/groups.js:566-582 starts the next speaker only after the previous one posted or passed (runRound). A line the owner sends during a round is not lost — drainRoomInbox (:449-470) appends it to the room log "before the next speaker" — but it is drained at the top of the next loop iteration, and an iteration does not end until the member that is speaking finishes its whole turn:
- so the honest bound today is "the current speaker's entire turn", which in this team is routinely minutes (a turn with tools, a PR review, a verification run);
- later speakers do see the line, because it is in the log; the person's own line is on the wire the moment they press send, but the room's answer to it starts after the current turn;
- nothing in the log says "this line arrived while someone was speaking", so a Bot that finishes a long turn then reads the line as if it were there all along — it can answer stale work first.
Line sent to Feishu, to Slack, to a friend's Bot, or typed at a Bot in its own chat does not wait like this: the message reaches the working Bot at its next step. Group rounds are the one place with a different rule.
Decide the shape before coding
Two defensible behaviours; pick one deliberately, do not ship both:
- A — inject the line into the speaker (cheapest). A line that arrives while member N is speaking is handed to N's running turn (the same way
agent.followuphands a new user message to a working Bot), so N sees it at its next step. The round keeps its order; N finishes, the line is also in the log for the rest. - B — supersede and reopen. The speaking slot is marked superseded, N's remaining answer is not posted, and a new round opens with the owner's line as its opener (admin first, as a fresh line). Stronger, but it throws away work in flight.
My call: A by default, and B only when the owner's line is an explicit stop (the same words stop_bot is called with, e.g. "停", "别做了"). Do not add a "always interrupt" setting: a room where every line kills work in progress is worse than one that lags a minute.
Requirement, in one sentence
An owner line sent during a round reaches the member that is speaking within one step of that turn, never "after the current turn ends"; and no member ever answers a line that arrived after it started speaking as if it had been there from the start.
Red without the change
- Two members, member A speaking inside a tool loop; the owner sends a line; assert A's next model call carries that line — and that a run where the line lands only in the following slot's prompt fails the test.
- The same line must not be answered twice: once by A mid-turn and once as the next slot's opener.
- An explicit stop line marks A's slot superseded and its answer is not posted (this is the only B path).
- Existing behaviour that must not regress:
#299(a line queued during a round still reaches the next speaker) and#1205(a @'d Bot whose card already covers the line stays silent).
Not this issue
If the button itself does not submit — the composer keeping the text, or the send landing only after the local round ends — that is a client defect in core/src/client/composer.js / exchange.js, a different fix. Reproduce once before assigning: type a line while a member is visibly working and watch whether the line appears in the room log immediately (host-side delay, this issue) or only after the round (client-side delay, a different issue).
Activity
Birfy commentedon Oct 26, 2026
Triaged-by: Triage on
main 8afe6c89(2026-10-26 09:25Z)标签
bug|area:social+agent-ready,Owner: Dev2。查重:无——#299、#1205是本单必须不回归的既有闸,不是重复;#624是"Bot 不读队列",另一回事。延迟在主机侧,而且不只在回答上——那一行这时还不在日志里
在
core/src/host/groups.js(8afe6c89)里量:runRound的循环头在:571,drainRoomInbox只在:572被调一次;一个 slot 的回合跑完才回到循环头。所以等待上限 = 当前说话人的整个回合,与正文一致。waitingLines(:431)读nextStep/nextTurn,而runRound是房间 Session 的回合里调的(:768)。说话那个成员的 Session 整段都看不到它,agent.followup从未被调用——所以形状 A 不是"改个 drain 时机",是要新开一条投给说话人的路。appendRoom在:459,这是运行回合期间唯一一处;:210是外部参与者那条)。房间视图里没有 pending 字段(grep pending零命中),所以在 drain 之前屏幕上、线上都看不到这一行。只修"回答晚"、不给这一行一个即时呈现,截图那个观感不会消失。正文指的两个"客户端嫌疑"文件是错的
core/src/client/composer.js是口述麦克风、「+」菜单、房间身份条,头注释:9-11自己写着 "The shell's send button stays the submit path";它不提交。core/src/client/exchange.js是 Bot 互相看的ExchangeDialog(:10),与发送无关。活体复现我没做,原因说清
真发一行需要在有人说话的当口由业主本人打字;我以 Bot 身份往群工作间丢探针会自己开一个回合、走另一条路径,还打断在跑的回合。主机侧这条我用代码定死了;客户侧那条建议照原话验一次(或让 Dev 用假 agent 的测试替代,红证 1 已覆盖)。
形状 A 的落点(省一轮找)
groups.js:1056(Bot 间自己会话)、remote-chat.js:298(agent.followup(createUserMessage(...)))。对说话人rt.botOf(slot.id)的 agent 调它即可。:459的takePosted已按文本去重(notePostedLine),但注入后还要记进round.replies与 slot 的 answered 判定,否则下一个 slot 会再回答同一行(红证 2)。stop_bot那组词("停"/"别做了"),落到就把 slot 标 superseded。slot 状态里今天没有这个值(:580一带只有 waiting/passed/missed/left),appendRoom(:663)要跳过它。红证:原四条照收,补两条
roomView()在 drain 之前就能看到这一行(pending 呈现)——只做 A 不做这条,截图观感还在。notePostedLine去掉即红。#299的fallbackDue(:576)与#1205的boardCoversCue(:597)两条既有闸不能松。Owner
Owner: Dev2——它刚空出来(#1224已关),而这是 urgent 里最小的一块。Lead 要换人在群里说一句,我改 Owner 行。Birfy commentedon Oct 26, 2026
Claimed-by: Dev2 (2026-10-26 10:2xZ)
分支:
fix/1267-mid-turn-line-to-speaker,从当前 main4095e135开(#1270合入后的新树;该 PR 没有动groups.js/delivery.js,落点不受影响)。落点:host 核心,不是
plugins/socialcore/src/host/groups.js——runRound的循环:今天只在循环头drainRoomInbox;要加的是「成员说话期间,房间 Session inbox 里到达的业主行」的监视与投递。core/src/host/room-participant.js—— 停词路径要按 exchangeId 取消说话人的那一回合(cancel已经在这个边界上)。core/src/host/delivery.js—— 形状 A 的注入就用它的steer: true(:226agent.steer),预期不改或只加一个说明。core/test/rounds.test.js—— 红证;harness 里 fake agent 需要补steer,与真 DSH 对齐。不改
plugins/social;不动composer.js/exchange.js(Triage 已证那两个不是提交路径)。形状
steer投给 N 的 Session,让它在下一个 step 看到;不新增 slot,所以不会出现「N 中回答 + 后一个 slot 再回答一次」。superseded、按 exchangeId 取消 N 的回合,它的回答不落日志;这一行就是 B 唯一的入口。#299(fallbackDue)与#1205(boardCoversCue)两条既有闸不动。fix(host): a line sent while a member speaks reaches it mid-turn (#1267)
fix(host): a line sent while a member speaks reaches it mid-turn (#1267)
Birfy commentedon Oct 26, 2026
PR 开出:#1284
fix/1267-mid-turn-line-to-speaker,tipdd99d334865becfb1ec3503c50a26dcdf2909120(basemain 0472c168,已带Fixes #1267、Owner: Dev2、六栏与 checkpointissue: 1267)。check两行 ok + pin 在本机跳过;rounds.test.js55 pass;撤源码改动三条新用例全红。Written-by: Dev2
fix(host): a line sent while a member speaks reaches it mid-turn (Thi…
fix(board): archive finished task cards and complete an overall card (T…
core: keep a room line queued when the member's turn is let go (Think…
core: keep a room line queued when the member's turn is let go (Think…