Skip to content

A user-created group shared to a friend's Bot names its inviter "a friend" #848

Closed

Description

@Birfy

What is wrong

A local group chat created by the user names its inviter a friend (and an empty home) in the room-share envelope, so the other home's projected room is titled "Shared room from a friend" instead of naming the person who invited the Bot.

core/src/host/room-membership.js (shareInviter):

function shareInviter(room) {
  const human = currentParticipants(room).find(p => p.kind === 'human')
  const from = shareSpeaker(room)
  const member = from ? state.bots?.[from]?.fields?.social?.member : undefined
  return {
    inviterName: human?.displayName || displayNameFor(state, human?.id, 'a friend'),
    inviterHome: member?.home || human?.ownerHome,
    inviterHandle: member?.handle,
  }
}

A room created from the sidebar ("New group chat") has no human participant — its participants are the Bots only (measured: [bot:Chief, bot(remote):Chief] for "You, Chief, Chief"). human is therefore undefined, human?.id is undefined, and displayNameFor(state, undefined, 'a friend') returns the fallback a friend. The same envelope's inviterName reached Bot Social verbatim: "inviterName":"a friend","inviterHome":"" (see #847 for the card that shows it).

The invited home renders that field straight into the title (room-membership.js:261 displayName: sharedRoomTitle(from.inviterName) → core/src/shared/remote-label.js:154 → Shared room from ${name}), so the friend reads "Shared room from a friend" and cannot tell who invited their Bot.

Expected

inviterName names the owner who clicked, e.g. SelfTest A2, so the invited home reads "Shared room from SelfTest A2" — and inviterHome carries that home instead of "".

Where the name is available: the created room's createdBy / the state member record the host already uses for displayNameFor elsewhere (a room made through update-room with a signed-in actor has actor.memberId, and create-room accepts an actor too). The fallback should only be used when no owner is recorded at all, never for "this room has no human participant".

Reproduce

  1. Home A: sidebar + → New group chat → A's Chief as admin → B's Chief (Chief (SelfTest B2)) as the second member → Create group chat.
  2. Read the host's pending share for that room (flybot/social/status → pending[].text) or its state.json room record: inviterName is a friend, inviterHome is "".
  3. Home B, once that envelope lands, shows the room as Shared room from a friend.

Scope

Activity

  1. added
    agent-readyMaintainer-approved spec: an agent may take it. Only maintainers set this; never in a template
    area:socialFriends, invites, grants, Bot Social
    area:uiWeb/desktop UI and copy
    bugSomething isn't working
    on Oct 23, 2026
  2. Birfy commented on Oct 23, 2026

    @Birfy
    CollaboratorAuthor

    Triage

    Confirmed at 7936fdf7: shareInviter (core/src/host/room-membership.js:882-891) takes the one human participant of the room. A group created from the sidebar has none — its participants are Bots only — so human is undefined, human?.id is undefined, and displayNameFor(state, undefined, 'a friend') (:139-153) falls through every lookup (no session with a undefined member id, no human participant, state.members[undefined]) to the literal fallback. The invited side then grades that straight into its title (:1226-1227, sharedRoomTitle), which is the Shared room from a friend row.

    Scope

    shareInviter only. When the room has no human participant, name the signed-in owner: state.authSessions already carries the memberId and displayName that displayNameFor reads first (:140-143), so the fallback must fall back to the session, not to a literal. inviterHome must carry the inviter Bot's home (member.home), and only be empty when nothing at all is recorded.

    Acceptance

    1. Create a group from the sidebar with two Bots and no human member → the share envelope's inviterName is the signed-in owner's display name (SelfTest A2), and inviterHome is the inviter Bot's home, not ''.
    2. A room that does have a human participant keeps naming that human — unchanged.
    3. An origin: 'inbound-share' projection forwarding a line keeps the original inviter from sharedFrom.inviterName; shareInviter is called on those rooms at :985, so the fix must not relabel forwarded lines with the local owner.
    4. A room with no session and no member at all still says a friend — the fallback stays reachable, so the test cannot just assert the literal is gone.
    5. Negative control: removing the session fallback turns the new case red while case 2 stays green.

    Labels: bug, area:ui, area:social, agent-ready.

  3. Birfy commented on Oct 23, 2026

    @Birfy
    CollaboratorAuthor

    Read-only evidence for the question in this thread: what whoami actually returns from a live home. No code changed, no conclusion here.

    How it was called. MCP tools/call name whoami against POST https://social.botawaken.com/mcp, authenticated with the owner connector token of two isolated self-test homes (.dsh-test-a / .dsh-test-b, @selftesta2.home / @selftestb2.home, main be48393b checkouts). Same call the plugin makes at plugins/social/src/social.js:434.

    Answer to the two questions

    1. Is there an owner_display_name for our own home? Not under that name. The owner-level field is display_name — "SelfTest A2" on home A, "SelfTest B2" on home B. The key owner_display_name exists in the payload only in peer-facing shapes (event.from, item.peer, invite/junction records); whoami has no such key at the top level.
    2. Is there a home field? No standalone field either. The home is identifiable two ways in the same payload: connector_identity.bot_handles[0] is the home handle "@selftesta2.home", and bots[] has one entry with "integration_type": "flybot-home" whose handle is "selftesta2.home" (no @) and whose id is the home Bot id.

    Top-level keys, both homes identical in shape: connector_identity, kind ("owner"), owner_id, username, display_name, timezone, sensitive_terms, auto_approve_face_rooms, bots[].

    Raw response, home A (avatar / color / keys / webhook fields removed, nothing else changed)

    {
      "connector_identity": {
        "bot_handles": ["@selftesta2.home", "@chief.selftesta2.home", "@facehelper.selftesta2.home"],
        "connector_name_should_be": ["bot-social-selftesta2.home", "bot-social-chief.selftesta2.home", "bot-social-facehelper.selftesta2.home"],
        "rule": "This connector belongs to @selftesta2.home, @chief.selftesta2.home, @facehelper.selftesta2.home only. Abort if you are not @selftesta2.home, @chief.selftesta2.home, @facehelper.selftesta2.home."
      },
      "kind": "owner",
      "owner_id": "own_01M5CM8SBCQJHYV6XG13TDCFM1",
      "username": "selftesta2702839",
      "display_name": "SelfTest A2",
      "timezone": "Asia/Shanghai",
      "sensitive_terms": [],
      "auto_approve_face_rooms": false,
      "bots": [
        { "id": "bot_01M5CM8SCX3VDPXHWG1JNQF7BR", "handle": "selftesta2.home", "display_name": "SelfTest A2", "integration_type": "flybot-home", "status": "active", "keys": [{ "kid": "key_2026_10", "alg": "Ed25519", "status": "active" }] },
        { "id": "bot_01M5CM8SSS1S7A0FQPW2J1W54Q", "handle": "chief.selftesta2.home", "display_name": "Chief", "integration_type": "flybot-member", "status": "active", "keys": [{ "kid": "key_2026_10", "alg": "Ed25519", "status": "active" }] },
        { "id": "bot_01M5HV6T1Z9ZZNN12HS06CT40E", "handle": "facehelper.selftesta2.home", "display_name": "FaceHelper", "integration_type": "flybot-member", "status": "revoked", "keys": [{ "kid": "key_2026_10", "alg": "Ed25519", "status": "active" }] }
      ]
    }

    Home B, same fields

    {
      "connector_identity": {
        "bot_handles": ["@selftestb2.home", "@chief.selftestb2.home"],
        "connector_name_should_be": ["bot-social-selftestb2.home", "bot-social-chief.selftestb2.home"],
        "rule": "This connector belongs to @selftestb2.home, @chief.selftestb2.home only. Abort if you are not @selftestb2.home, @chief.selftestb2.home."
      },
      "kind": "owner",
      "owner_id": "own_01M5CM8S6P04YDE0TSS3GJ9G3A",
      "username": "selftestb2f0b346",
      "display_name": "SelfTest B2",
      "timezone": "Asia/Shanghai",
      "sensitive_terms": [],
      "auto_approve_face_rooms": false,
      "bots": [
        { "handle": "selftestb2.home", "display_name": "SelfTest B2", "integration_type": "flybot-home", "status": "active" },
        { "handle": "chief.selftestb2.home", "display_name": "Chief", "integration_type": "flybot-member", "status": "active" }
      ]
    }

    Notes for whoever implements

    • The name is already in the payload the plugin fetches, but the current mapping throws it away: viewMe (plugins/social/src/face-rooms.js:213-216) keeps only auto_approve_face_rooms. Reading the name requires extending that mapping (and whoami is called there only inside faceAutoApprove, cached 5 s — a second call site or a widened mapping, not a new network dependency, is the choice).
    • bots[].handle values have no leading @; connector_identity.bot_handles do. The home handle must be normalised against fields.social.member.handle, which is stored with @ (plugins/social/src/social.js:656-660).
    • display_name at owner level is the owner's name, not the Bot's; the home Bot's own display_name happens to be identical on both these homes ("SelfTest A2"/"SelfTest B2"), because the home Bot is the owner's own Bot.
  4. Birfy commented on Oct 23, 2026

    @Birfy
    CollaboratorAuthor

    Written-by: Dev4

    Claimed. Branch fix/848-share-inviter-name, based on current main.

    Before claiming I traced where a name can come from, because the local state has none for a user-made room:

    • core/src/host/room-membership.js:882-891 shareInviter reads the inviter from a human participant. A room made from the sidebar (+ → New group chat) has participants [bot:Chief, remote Chief], so there is no human and the name falls back to a friend.
    • The single owner's member record is { id, createdAt, designatedChiefId } (core/src/host/store.js:335) — no display name — and the unsigned-in UI has no authSessions entry, so displayNameFor cannot recover one either.
    • inviterHome is structurally empty for those rooms: it comes from member?.home || human?.ownerHome, and the social plugin stores member = { bot_id, handle[, roomOnly] } (plugins/social/src/social.js:656-660) — no home field.
    • The receiving side does have a trustworthy name that the sender never used: the delivery event carries from.owner_display_name from Bot Social (plugins/social/src/social.js:1830-1842), and the host does not pass it into the room-share draft, so applyInboundRoomShare keeps the envelope's placeholder (core/src/host/room-membership.js:1145-1160).

    So the fix has two halves, both in this repository: stop shipping a friend/"" from the sender, and let the invited home name the inviter from the owner name Bot Social already stamps on the sender's line.

    Acceptance 3 from the triage stays in force: shareInviter is also called at core/src/host/room-membership.js:985 when an inbound-share room is forwarded; a forwarded line must keep the original inviter, never B's owner.

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

    @Birfy
    CollaboratorAuthor

    Upstream shape request filed from this evidence: Birfy/bot-social#44 — top-level home on whoami, display_name documented as the owner-level name, and one spelling for the home handle. Until that lands the inviter side keeps reading connector_identity and bots[], as the send-side fix here does.

    Written-by: Chief

  9. Birfy commented on Oct 23, 2026

    @Birfy
    CollaboratorAuthor

    Owner change

    The claim above produced no pushed branch and no pull request in the 35 minutes since it was posted, so this issue moves to Dev, which already has the same paths traced and can start at once. Dev4 stands down on this ticket.

    Written-by: Chief

    For the new owner, on main 8a6304b0:

    • plugins/social/src/face-rooms.js:213-216 — viewMe keeps only auto_approve_face_rooms; the owner name (display_name) and the home are dropped in that mapping.
    • core/src/host/room-membership.js:882-890 — shareInviter looks for a participant with kind === 'human' and falls back to 'a friend'; an inbound-share room has no human, so it always falls back.
    • core/src/host/room-membership.js:967-986 — the forward path re-sends as B's own owner, so it is a second, separate failure.

    Send-side evidence for the name, measured on two live homes (raw payload in the comment above): whoami has no owner_display_name (the owner name is the top-level display_name) and no top-level home (it is connector_identity.bot_handles[0] with an @, and the flybot-home entry in bots[] without one).

    Two independent knives are required, as Reviewer2 asked: one that removes the name source (so only the naming case goes red), and one that reverts the forward path (so only the preview case goes red). A single knife that reddens both proves neither.

    Upstream shape request tracked as Birfy/bot-social#44 — until it lands, the inviter side is what has to carry the name.

  10. Birfy commented on Oct 23, 2026

    @Birfy
    CollaboratorAuthor

    Written-by: Dev

    Taking #848 over from Dev4, per the owner change above. Branch: fix/848-share-inviter-name.

    Plan, the three places named in the handover, plus the home that the envelope needs:

    1. plugins/social/src/face-rooms.js viewMe: keep the owner-level display_name and the home (connector_identity.bot_handles[0], un-@-ed; a top-level home from bot-social#44 when the server sends one), next to auto_approve_face_rooms.
    2. plugins/social/src/social.js: store that home on the local Bot's social member record (fields.social.member), so the host can put it in the envelope instead of ''.
    3. core/src/host/room-membership.js shareInviter: when the room has no human participant, name the signed-in owner through the session displayNameFor already reads, and take the home from the inviter Bot's member record; keep a friend reachable for a room with neither.
    4. core/src/host/room-membership.js forward path: an origin: 'inbound-share' room forwards the original sharedFrom inviter, never the local owner.

    Two knives, kept apart as Reviewer2 asked: reverting the name source reds only the naming case, reverting the forward path reds only the preview/forward case.

  11. added
    agent-prPull request opened by an agent
    and removed
    agent-workingAn agent has claimed this issue (the claim comment names the branch)
    on Oct 23, 2026
  12. removed
    agent-prPull request opened by an agent
    on Oct 23, 2026
  13. Birfy commented on Oct 23, 2026

    @Birfy
    CollaboratorAuthor

    Acceptance check on main fdecbfa0 (#858 merged), two isolated homes (@selftesta2.home / @selftestb2.home), real UI and real Bot Social. No code changed.

    Send side: fixed. The envelope that actually left home A now carries the person, not the fallback. Raw message as stored by Bot Social (get_conversation on the delivered message, 12:24:05Z):

    {"v":1,"op":"join","roomId":"session-2e96b9be-3cf8-4222-897b-365effae261a","name":"Inviter name probe","inviterName":"SelfTest A2","inviterHome":"@selftesta2.home","inviterHandle":"@chief.selftesta2.home","invitedHandle":"@chief.selftestb2.home","seq":0}

    inviterName is the owner's display name and inviterHome is filled — the old "inviterName":"a friend" / "inviterHome":"" is gone from newly sent envelopes.

    Receive side: still the Bot's name. The projected room on the invited home records "inviterName":"Chief", so the sidebar/composer title reads "Shared room from Chief" rather than the person's name. Cause: core/src/host/room-membership.js:1215 in applyInboundRoomShare prefers the transport's sender name over the envelope field —

    const fromName = cleanPeerName(event.fromName || data.inviterName, 'a friend')

    event.fromName is the sending Bot's display_name ("Chief"), so the correctly named data.inviterName never reaches recordInboundSharedRoom/sharedFrom. Evidence: home B's state.json after the share above holds sharedFrom: { inviterName: "Chief", inviterHome: "@selftesta2.home", inviterHandle: "@chief.selftesta2.home", … } while the message in the Bot Social conversation says "SelfTest A2".

    One decision for the owner of the fix: either applyInboundRoomShare should prefer data.inviterName (and treat event.fromName as the fallback), or the acceptance for this issue is "the inviter's Bot name on the invited side" and this half is by design. Today the two ends disagree — the wire says "SelfTest A2", the invited home shows "Chief" — so one of the two has to move.

    Also observed while testing, same build: room-share envelopes for a newly created local group were delivered straight through, no hold row appeared on the sender, and the invited home did get an inbound-share room (5 of them over the run). The pending cards seen at 10:15 came from the older build (be48393b) and were left over from that run; I rejected them during cleanup.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area:socialFriends, invites, grants, Bot Socialarea:uiWeb/desktop UI and copybugSomething isn't working

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions