Skip to content

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

Closed

Description

@Birfy

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.followup hands 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

  1. 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.
  2. The same line must not be answered twice: once by A mid-turn and once as the next slot's opener.
  3. An explicit stop line marks A's slot superseded and its answer is not posted (this is the only B path).
  4. 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

  1. Birfy commented on Oct 26, 2026

    @Birfy
    CollaboratorAuthor

    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 的回合跑完才回到循环头。所以等待上限 = 当前说话人的整个回合,与正文一致。
    • 那一行进的是房间 Session 自己的 inbox:waitingLines(:431)读 nextStep/nextTurn,而 runRound 是房间 Session 的回合里调的(:768)。说话那个成员的 Session 整段都看不到它,agent.followup 从未被调用——所以形状 A 不是"改个 drain 时机",是要新开一条投给说话人的路。
    • 同一个根因还解释了截图里"按了没发出去"的观感:业主的行直到 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),与发送无关。
    • 真正的提交在 DSH 的 shell 里。如果客户端也把行压到本地回合结束才发,那是另一张单,但这张单的"客户端分支"要按 shell 的代码写,不是这两个文件。

    活体复现我没做,原因说清

    真发一行需要在有人说话的当口由业主本人打字;我以 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)。
    • 形状 B 只在显式停词时:stop_bot 那组词("停"/"别做了"),落到就把 slot 标 superseded。slot 状态里今天没有这个值(:580 一带只有 waiting/passed/missed/left),appendRoom(:663)要跳过它。

    红证:原四条照收,补两条

    1. roomView() 在 drain 之前就能看到这一行(pending 呈现)——只做 A 不做这条,截图观感还在。
    2. 同一行 drain 之后不得再 append 一次:把 notePostedLine 去掉即红。

    #299 的 fallbackDue(:576)与 #1205 的 boardCoversCue(:597)两条既有闸不能松。

    Owner

    Owner: Dev2——它刚空出来(#1224 已关),而这是 urgent 里最小的一块。Lead 要换人在群里说一句,我改 Owner 行。

  2. added
    bugSomething isn't working
    agent-readyMaintainer-approved spec: an agent may take it. Only maintainers set this; never in a template
    area:socialFriends, invites, grants, Bot Social
    on Oct 26, 2026
  3. Birfy commented on Oct 26, 2026

    @Birfy
    CollaboratorAuthor

    Claimed-by: Dev2 (2026-10-26 10:2xZ)

    分支:fix/1267-mid-turn-line-to-speaker,从当前 main 4095e135 开(#1270 合入后的新树;该 PR 没有动 groups.js/delivery.js,落点不受影响)。

    落点:host 核心,不是 plugins/social

    • core/src/host/groups.js —— runRound 的循环:今天只在循环头 drainRoomInbox;要加的是「成员说话期间,房间 Session inbox 里到达的业主行」的监视与投递。
    • core/src/host/room-participant.js —— 停词路径要按 exchangeId 取消说话人的那一回合(cancel 已经在这个边界上)。
    • core/src/host/delivery.js —— 形状 A 的注入就用它的 steer: true(:226 agent.steer),预期不改或只加一个说明。
    • core/test/rounds.test.js —— 红证;harness 里 fake agent 需要补 steer,与真 DSH 对齐。

    不改 plugins/social;不动 composer.js/exchange.js(Triage 已证那两个不是提交路径)。

    形状

    • A(默认):说话人 N 的回合在跑时到达的业主行,立刻 append 进房间日志(房内马上可见,不用等 drain)、从房间 Session 的 inbox 里摘掉、以 steer 投给 N 的 Session,让它在下一个 step 看到;不新增 slot,所以不会出现「N 中回答 + 后一个 slot 再回答一次」。
    • B(只在显式停词):停词把 N 的 slot 标 superseded、按 exchangeId 取消 N 的回合,它的回答不落日志;这一行就是 B 唯一的入口。

    #299(fallbackDue)与 #1205(boardCoversCue)两条既有闸不动。

  4. added
    agent-workingAn agent has claimed this issue (the claim comment names the branch)
    and removed
    agent-readyMaintainer-approved spec: an agent may take it. Only maintainers set this; never in a template
    on Oct 26, 2026
  5. Birfy commented on Oct 26, 2026

    @Birfy
    CollaboratorAuthor

    PR 开出:#1284

    fix/1267-mid-turn-line-to-speaker,tip dd99d334865becfb1ec3503c50a26dcdf2909120(base main 0472c168,已带 Fixes #1267、Owner: Dev2、六栏与 checkpoint issue: 1267)。check 两行 ok + pin 在本机跳过;rounds.test.js 55 pass;撤源码改动三条新用例全红。

    Written-by: Dev2

  6. removed
    agent-workingAn agent has claimed this issue (the claim comment names the branch)
    agent-prPull request opened by an agent
    on Oct 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:socialFriends, invites, grants, Bot SocialbugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions