Skip to content

Face rooms: held Bot replies live on their card; rejected ones read Not sent; one answer per line - #461

Merged
Birfy merged 2 commits into
ThinkFlowLab:mainfrom
Birfy:fix/456-face-held-replies
Oct 19, 2026
Merged

Birfy merged 2 commits into
ThinkFlowLab:mainfrom
Birfy:fix/456-face-held-replies

Conversation

@Birfy

@Birfy Birfy commented Oct 19, 2026

Copy link
Copy Markdown
Collaborator

Fixes #456

Summary

Face-to-face rooms, following the owner's decision on #456:

  • A held Bot reply lives only on its approval card (greyed preview) and becomes a room bubble only after Approve. The host now tracks the held line on the room (room.heldLines, saved in state.json). Before, it guessed by matching text against pendingApprovals. fanoutRoomLine records a local Bot's line as sending before the send. The social transport answers { held: true, approvalId } when Bot Social holds it, and the record becomes pending. social/decide-approval then calls flybot.rooms.settleHeld(id, decision).
  • Reject → one grey <Bot> · Not sent line (option a). It stays that way after a reload and in Past rooms because the rejected record is saved with the room. No Rejected. note is posted into the face room.
  • Each Bot answers a person's line at most once (the Face rooms: a plain line should let every Bot in the room answer (one round, capped), not only the first (follow-up to #287) #295 rule). The room keeps faceLine (seq, answered, asked exchange ids) for the latest person line, covering both the owner's own line and a friend's attested plain line. A Bot that already answered is passed with no model call, a second answer in the same round is dropped, and lateRoomReply drops a late answer to the same line. A newer person line resets it. Separately, applyFaceSnapshot posts a face event for a round only when the core room logged it just now (applyFaceLine refuses an event id it already has), so one event read twice can't start a second round.
  • A decided card leaves no stub: no "Approved" / "Rejected" line. This applies to every card kind.
  • The card title puts the person first: a face card's from is the room's own Friend's Bot rows: two name formats ('Helper · SelfB0023's Bot' vs 'SelfA0023's Chief'), two-line wrap, no preview, inset highlight, faint marker, same avatar as own Chief #384 label for that local Bot, e.g. Researcher (Alice) → the room.

Minor details decided (owner said to decide these)

  • An approved card collapses with no stub. A rejected one does the same, and its room line reads Not sent.
  • The Not sent line is a grey event row with the Bot's mark: Researcher · Not sent (zh {name} · 未发出). It does not show the rejected words.
  • A line still held when the room ends (left/closed, Past rooms) also reads Not sent.
  • A sending record older than 60 s (a restart mid-send) falls back to a normal bubble, so a line is never hidden forever.
  • The card sub-title keeps the X → Y format of other cards and uses the room's Bot (Person) label. It falls back to the Bot name when no label is known.
  • No "Rejected." notice in face rooms. Friend-chat reject notes are unchanged.

Approval rules checked

Tests

  • core/test/held-line.test.js: sending/pending → no bubble and no stub; approved → bubble; rejected → Chief · Not sent with no words; closed room → Not sent (Past rooms); late post matched by Bot+text; stale send → bubble; zh string and CSS.
  • core/test/rounds.test.js:
  • plugins/social/test/social.test.js:
    • A held Bot line answers {held, approvalId} without approving anything.
    • Reject/approve call settleHeld, and no Rejected. note goes to the face room.
    • A decided card renders null.
    • The face card shows Researcher (Alice) → the room and ignores a friend's Bot with the same name.
    • Two concurrent polls of one event post it once.
  • Ran locally:
    • ARCH_DOCS_BASE=origin/main TZ=UTC npm run verify: core 1016 pass, social 276, tree 73, timeline 80, all with 0 skipped.
    • The CI plugins jobs (node scripts/lint-plugins.mjs <p> plus DSH_HARNESS=… npm test for tree/social/timeline).
    • check-pack.mjs and tsc --strict types/*.d.ts.
  • Not run: a live two-home face-room session on Bot Social.

Docs: docs/subsystems/core-host.md, core-client.md, plugin-runtime.md, social.md. Changelog: changelog.d/456-face-held-replies.fixed.md.

Risk tier: T2/T3 (expected; touches the approval flow — how held Bot lines show and the decide-approval path. Best estimate T3).

Birfy added 2 commits October 20, 2026 05:17
…ot sent; one answer per line

A Bot line Bot Social holds for the owner's approval is tracked on the room
(heldLines) and drawn only on its approval card until Approve; Reject turns it
into one grey "<Bot> · Not sent" line that survives a reload and Past rooms.
Decided cards leave no stub, the face card names the Bot with its person, no
"Rejected." note is posted into a face room, and each Bot answers a person's
plain line at most once (a second delivery or a late second answer is dropped).

Fixes ThinkFlowLab#456
@Birfy Birfy added bug Something isn't working agent-pr Pull request opened by an agent area:social Friends, invites, grants, Bot Social area:ui Web/desktop UI and copy self-test Filed by the product self-test routine (issues only) labels Oct 19, 2026
@Birfy Birfy self-assigned this Oct 19, 2026
@Birfy

Birfy commented Oct 19, 2026

Copy link
Copy Markdown
Collaborator Author

Self-review: PASS
Tip SHA: 52de556
Ran:

  • ARCH_DOCS_BASE=origin/main TZ=UTC npm run verify: core 1016, social 276, tree 73, timeline 80, all with 0 skipped. Also lint, arch-docs, build-client --check, ds-bot-patches check, interop and live-e2e lint/tests.
  • node scripts/lint-plugins.mjs plus DSH_HARNESS=… npm test for tree, social and timeline.
  • check-pack.mjs and tsc --strict types/*.d.ts.
  • PR CI: all checks green.
    Not run: a live two-home face-room session on Bot Social, and the smoke test.

Owner decision: a held reply is drawn only on its card until Approve; Reject gives a grey <Bot> · Not sent line, saved with the room so it stays after a reload and in Past rooms; each Bot answers a person's line at most once.
Approval rules: approval happens only through the Approve/Reject buttons → social/decide-approval. The new { held, approvalId } answer only hides a line and approves nothing. A Bot line is never shown as the owner's, and the owner's typed line keeps the #179 path (typing is the approval) and is never recorded as held.

@Birfy Birfy added the awaiting-approval PR is green and waits for the maintainer's approval (T2/T3) or a review PASS at its tip label Oct 19, 2026
@github-actions github-actions Bot added the risk:T3 Risk tier T3 (needs a human approval), set by the controller from the diff label Oct 19, 2026
@Birfy
Birfy added this pull request to the merge queue Oct 19, 2026
Merged via the queue into ThinkFlowLab:main with commit 954d857 Oct 19, 2026
8 checks passed
@Birfy
Birfy deleted the fix/456-face-held-replies branch October 19, 2026 23:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agent-pr Pull request opened by an agent area:social Friends, invites, grants, Bot Social area:ui Web/desktop UI and copy awaiting-approval PR is green and waits for the maintainer's approval (T2/T3) or a review PASS at its tip bug Something isn't working risk:T3 Risk tier T3 (needs a human approval), set by the controller from the diff self-test Filed by the product self-test routine (issues only)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Face room: held Bot replies show as sent bubbles before approval and a rejected one stays; one Bot answers a single line twice

1 participant