Skip to content

Commit cf61952

Browse files
authored
Name a friend's Bot one way and approval cards by the receiving person (#502)
Fixes #498 **What.** A friend's Bot now has one name in a friend chat, on both homes. Approval cards name the person who gets the message. - `friendBotTitle(bot)` / `friendBotOwner(bot)` in `core/src/shared/remote-label.js` are the one formatter for a friend's chat. The sidebar row, chat header, tab title, composer placeholder and @ menu call it, so they all read `Chief (SelfB1233)` (#384/#305). The sidebar row is now one string, without the muted owner part. The @ menu no longer falls back to the handle (`@chief.selfb1233.home (SelfB1233)`) for a friend's chat. The social plugin's classic script reads the same functions from `globalThis.__flybot__.names`, set in `flybot-slots.js` `registry()`. - Approval cards (the held-reply card on the bubble, the overlay/pointer, and the social dock for local and Bot Social holds) name the receiving person: the stored `recipientName`/`toOwner` first, then the friend chat's owner. A card reads `Chief → SelfB1233` (#418) and is the same before and after a reload. It never shows `Chief → Chief (SelfB1233)` again. - Face rooms are unchanged: they stay person-first per #457. The composer placeholder only changes through the shared formatter, and the #499 placeholder regression is left to #499. **Why.** The owner's decision (option a, 2026-10-20) was to unify friend Bot names as `Chief (SelfB1233)` through one formatter, and have approval cards show the receiver. **Test.** `ARCH_DOCS_BASE=origin/main TZ=UTC npm run verify` passes with 0 failures (core 1079, social 297, tree/timeline/scripts/interop/live-e2e all pass; plugin lint OK). New and updated tests: - the formatter names both homes' Chiefs and the owner (`remote-room.test.js`) - the held card reads the same with and without the friend's chat in the roster (`held-reply.test.js`) - the overlay pointer names the person (`overlay-approvals.test.js`) - the @ menu names a host-shaped friend chat (`at-menu.test.js`) - the sidebar row and other surfaces use the formatter (`flybot-client.test.js`) - the social dock names the person through the host formatter (`social.test.js`) Not run: the live two-home Mac instances and the smoke bench (client-only change). Risk tier: T3 (estimate, from the risk-gate report: remote-label.js and displayName paths; the controller sets the label) --------- Co-authored-by: Birfy <15378933+Birfy@users.noreply.github.com>
1 parent b6e89d2 commit cf61952

36 files changed

Lines changed: 367 additions & 204 deletions
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
A friend's Bot now has one name everywhere in a friend chat, on both homes: the sidebar row, chat header, tab, composer placeholder and @ menu all read `Chief (SelfB1233)`, from one shared formatter, and the @ menu no longer shows the handle as the Bot's name. A held message's approval card names the person who gets it (`Chief → SelfB1233`), the same before and after a reload, instead of `Chief → Chief (SelfB1233)` ([#498](https://github.com/ThinkFlowLab/FlyBot/issues/498)).

‎core/client.js‎

Lines changed: 30 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1467,6 +1467,21 @@ function remoteBotTitle(record) {
14671467
function remoteOwnerBadge(record) {
14681468
return ownerNameOf(record) || cleanPeerName(record?.handle, "Friend");
14691469
}
1470+
function friendRecord(bot) {
1471+
const remote = bot?.remote && typeof bot.remote === "object" ? bot.remote : {};
1472+
return {
1473+
displayName: remote.displayName || remote.name || bot?.name,
1474+
ownerName: remote.ownerName || remote.owner || remote.homeName,
1475+
handle: remote.handle ?? bot?.handle,
1476+
isChief: remote.isChief
1477+
};
1478+
}
1479+
function friendBotTitle(bot) {
1480+
return remoteBotTitle(friendRecord(bot));
1481+
}
1482+
function friendBotOwner(bot) {
1483+
return remoteOwnerBadge(friendRecord(bot));
1484+
}
14701485
function sharedRoomTitle(inviterName) {
14711486
return `Shared room from ${cleanPeerName(inviterName, "a friend")}`;
14721487
}
@@ -2143,6 +2158,7 @@ function registry2() {
21432158
globalThis.__flybot__.BotMark ??= BotMark;
21442159
globalThis.__flybot__.BotAvatar ??= BotAvatar;
21452160
globalThis.__flybot__.lookOf ??= lookOf;
2161+
globalThis.__flybot__.names ??= { friendBotTitle, friendBotOwner };
21462162
return slots;
21472163
}
21482164
var createClientApi2 = (options = {}) => createClientApi({ call: call2, registry: registry2, ...options });
@@ -2258,12 +2274,7 @@ var chatOf = (approval, roster) => approval.request?.meta?.roomId ?? approval.re
22582274
function recipientName(approval, roster) {
22592275
const handle = handleOf(approval.request?.to);
22602276
const bot = friendChatOf(approval, roster);
2261-
if (bot) {
2262-
const owner = bot.remote.owner || bot.remote.homeName;
2263-
if (owner) return `${bot.name} (${owner})`;
2264-
return bot.name;
2265-
}
2266-
return approval.recipientName ?? approval.targetName ?? handle ?? String(approval.request?.to ?? "");
2277+
return approval.recipientName || (bot ? friendBotOwner(bot) : "") || approval.targetName || handle || String(approval.request?.to ?? "");
22672278
}
22682279
function FlyBotApprovals({ useRoster, actions } = {}) {
22692280
const entries2 = useSlot("flybot.approvals");
@@ -3214,7 +3225,6 @@ body[data-bt-motion=quiet] .bt-mark[data-poke]{cursor:default}
32143225
.bt-row-body{display:flex;flex-direction:column;min-width:0;flex:1 1 auto}
32153226
.bt-row-name{display:flex;align-items:center;gap:6px;min-width:0;font-size:14px;line-height:20px;font-weight:400}
32163227
.bt-row-title{min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
3217-
.bt-row-owner{flex:0 100 auto;min-width:24px;font-size:13px;color:var(--bt-ink-2);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
32183228
.bt-row-remote{flex:none;display:inline-flex;align-items:center;color:var(--bt-ink-2)}
32193229
.bt-tag{flex:none;max-width:96px;font-size:12px;line-height:16px;font-weight:400;padding:1px 6px;border-radius:4px;background:var(--bt-tag-bg);color:var(--bt-tag-ink);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
32203230
.bt-row-preview{font-size:13px;line-height:18px;color:var(--bt-ink-2);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
@@ -4245,12 +4255,7 @@ function sessionTabLabel(roster, sessionId) {
42454255
const bot = roster.byId?.[sessionId];
42464256
const room2 = roster.roomsById?.[sessionId];
42474257
if (bot?.remote) {
4248-
return remoteBotTitle({
4249-
displayName: bot.name,
4250-
ownerName: bot.remote.homeName || bot.remote.owner,
4251-
handle: bot.remote.handle,
4252-
isChief: bot.remote.isChief
4253-
});
4258+
return friendBotTitle(bot);
42544259
}
42554260
if (room2 && room2.origin === "face-to-face" && (room2.face?.status === "closed" || room2.face?.status === "left") && isAutoFaceTitle(room2.name)) return "Face-to-face · closed";
42564261
if (room2) return room2.displayName || (room2.sharedFrom ? sharedRoomTitle(room2.sharedFrom.inviterName) : room2.name) || "";
@@ -5413,8 +5418,8 @@ function AssistantCell({ node, groupPart, sessionId, openFile, fileMentions, use
54135418
}
54145419
function HeldReply({ held, roster, sessionId, actions, closing, children }) {
54155420
const bot = roster.byId[sessionId];
5416-
const chat = (roster.bots ?? []).find((item) => item.remote?.handle === held.to && isListedRemoteChat(item));
5417-
const toName = chat ? remoteBotTitle({ displayName: chat.name, ownerName: chat.remote.homeName || chat.remote.owner, handle: chat.remote.handle, isChief: chat.remote.isChief }) : held.toOwner || held.to || t("a friend");
5421+
const chat = (roster.bots ?? []).find((item) => item.remote?.handle === held.to);
5422+
const toName = cleanPeerName(held.toOwner) || (chat ? friendBotOwner(chat) : ownerNameOf({ handle: held.to })) || held.to || t("a friend");
54185423
return (0, import_react8.createElement)(
54195424
"div",
54205425
{ className: "bt-held-reply", "data-state": held.status, "data-held": held.status === "pending" ? "" : void 0 },
@@ -6988,11 +6993,9 @@ function BotRow({ bot, roster, actions, useSessions, useSessionStatus, useSessio
69886993
actions.openMenu({ kind: "bot", id, x: event.clientX, y: event.clientY });
69896994
};
69906995
const pinned = bot.pinned === true && !tile;
6991-
const remoteRecord2 = bot.remote ? { displayName: bot.name, ownerName: bot.remote.homeName || bot.remote.owner, handle: bot.remote.handle, isChief: bot.remote.isChief } : null;
6992-
const shown = remoteRecord2 ? remoteBotTitle(remoteRecord2) : bot.name;
6993-
const rowName = remoteRecord2 ? cleanPeerName(bot.name, shown) : shown;
6994-
const owner = remoteRecord2 ? ownerNameOf(remoteRecord2) || cleanPeerName(bot.remote.handle) : "";
6995-
const secondLine = awayLine || preview || (remoteRecord2 ? t("Friend's Bot") : "");
6996+
const remote = Boolean(bot.remote);
6997+
const shown = remote ? friendBotTitle(bot) : bot.name;
6998+
const secondLine = awayLine || preview || (remote ? t("Friend's Bot") : "");
69966999
const label = [shown, main ? t("Main Bot") : "", pinned ? t("Pinned") : "", running || away ? t("Working") : "", waiting ? t("Waiting for your approval") : alert ? t("Waiting for your answer") : "", unread ? t("Unread activity") : ""].filter(Boolean).join(", ");
69977000
if (tile && wide) {
69987001
return (0, import_react12.createElement)("div", { className: "bt-tile-wrap" }, (0, import_react12.createElement)(
@@ -7024,7 +7027,7 @@ function BotRow({ bot, roster, actions, useSessions, useSessionStatus, useSessio
70247027
"aria-current": active ? "page" : void 0,
70257028
// An own Bot's hover text names its model (#366); a friend's Bot runs on its owner's model.
70267029
"data-bot-id": id,
7027-
title: remoteRecord2 ? shown : botRowTitle(shown, modelOf(bot, roster)?.name, wide),
7030+
title: remote ? shown : botRowTitle(shown, modelOf(bot, roster)?.name, wide),
70287031
onClick: () => actions.openSession(id),
70297032
onContextMenu
70307033
},
@@ -7036,8 +7039,7 @@ function BotRow({ bot, roster, actions, useSessions, useSessionStatus, useSessio
70367039
(0, import_react12.createElement)(
70377040
"span",
70387041
{ className: "bt-row-name" },
7039-
(0, import_react12.createElement)("span", { className: "bt-row-title" }, rowName),
7040-
owner ? (0, import_react12.createElement)("span", { className: "bt-row-owner" }, owner) : null,
7042+
(0, import_react12.createElement)("span", { className: "bt-row-title" }, shown),
70417043
bot.remote ? (0, import_react12.createElement)(RemoteMark, { remote: bot.remote }) : null,
70427044
pinned ? (0, import_react12.createElement)(PinMark) : null
70437045
),
@@ -7908,7 +7910,7 @@ function HeaderPill({ sessionId, useRoster, useSessions, useSessionStatus, useSe
79087910
const bot = roster.byId[sessionId];
79097911
const room2 = roster.roomsById[sessionId];
79107912
const detailsOpen = useUi((state) => state.details === sessionId);
7911-
const name = bot?.remote ? remoteBotTitle({ displayName: bot.name, ownerName: bot.remote.homeName || bot.remote.owner, handle: bot.remote.handle, isChief: bot.remote.isChief }) : room2?.displayName || (room2?.sharedFrom ? sharedRoomTitle(room2.sharedFrom.inviterName) : room2?.name) || bot?.name || "";
7913+
const name = bot?.remote ? friendBotTitle(bot) : room2?.displayName || (room2?.sharedFrom ? sharedRoomTitle(room2.sharedFrom.inviterName) : room2?.name) || bot?.name || "";
79127914
const useNews = useMemoryNews ?? noNews;
79137915
const memoryIds = bot ? [bot.id] : room2?.members ?? [];
79147916
const notice = useMemoryNotice(memoryIds, useNews);
@@ -8408,8 +8410,9 @@ function atSources({ roster, sessions, launched, iconFor }) {
84088410
const snapshot = roster.getSnapshot();
84098411
const face = isFaceRoom(snapshot.roomsById?.[session.sessionId]);
84108412
const team = (mentionRows(snapshot, session.sessionId, query) ?? []).map((row) => ({
8411-
// A friend's Bot by its one name, "Chief (Bob)"; its handle is the second line (#384).
8412-
name: row.bot?.remote && !face ? remoteBotTitle(remoteRecord(row.bot)) : row.bot ? row.bot.label ?? row.bot.name : row.remote ? remoteBotTitle({ ...row.remote, displayName: row.remote.name, ownerName: row.remote.owner || row.remote.homeName }) : t("Everyone"),
8413+
// A friend's Bot by its one name, "Chief (Bob)"; its handle is the second line (#384). A seat
8414+
// carries its own name; a friend's chat is named as its sidebar row and header (#498).
8415+
name: row.bot?.remote && !face ? row.bot.kind === "bot" ? remoteBotTitle(remoteRecord(row.bot)) : friendBotTitle(row.bot) : row.bot ? row.bot.label ?? row.bot.name : row.remote ? remoteBotTitle({ ...row.remote, displayName: row.remote.name, ownerName: row.remote.owner || row.remote.homeName }) : t("Everyone"),
84138416
...row.bot?.role && !face ? { description: row.bot.role } : {},
84148417
...row.remote ? { description: row.remote.handle } : {},
84158418
...row.bot?.remote && !face && remoteRecord(row.bot).handle ? { description: remoteRecord(row.bot).handle } : {},
@@ -16627,7 +16630,7 @@ function mentionableBots(roster, sessionId, { fromFriend = false } = {}) {
1662716630
const bots = room2 ? room2.members.map((id) => roster.byId[id]).filter(Boolean) : roster.byId[sessionId] ? roster.bots : [];
1662816631
const humans = (room2?.participants ?? []).filter((p) => p.kind === "human" && p.leftSeq === void 0).map((p) => ({ id: p.id, name: p.displayName || p.id, kind: "human", role: p.role }));
1662916632
const remotes = (room2?.participants ?? []).filter((p) => p.remote && p.leftSeq === void 0).map((p) => ({ id: p.handle || p.id, name: remoteBotTitle(p), kind: "bot", remote: p, mentionLabel: remoteBotTitle(p) }));
16630-
return bots.concat(humans, remotes, bots.filter((bot) => typeof bot?.remote?.handle === "string").map((bot) => ({ ...bot, name: bot.remote.handle.replace(/^@+/, ""), mentionLabel: remoteBotTitle({ displayName: bot.name, ownerName: bot.remote.homeName || bot.remote.owner, handle: bot.remote.handle, isChief: bot.remote.isChief }) })));
16633+
return bots.concat(humans, remotes, bots.filter((bot) => typeof bot?.remote?.handle === "string").map((bot) => ({ ...bot, name: bot.remote.handle.replace(/^@+/, ""), mentionLabel: friendBotTitle(bot) })));
1663116634
}
1663216635
function faceMentionables(room2, roster, fromFriend) {
1663316636
const humans = (room2.participants ?? []).filter((p) => p.kind === "human" && p.leftSeq === void 0).map((p) => ({ id: p.id, name: p.displayName || p.id, kind: "human", role: p.role }));

‎core/src/client/at-menu.js‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
// rows stay beside them, because a source cannot be hidden for one Session (a face
66
// room filters the session rows out of view: face-at-filter.js).
77
import { mentionCandidates, remoteMentionRows } from './flybot-registry.js'
8-
import { cleanPeerName, ownerNameOf, remoteBotTitle, listedChatBots } from '../shared/remote-label.js'
8+
import { cleanPeerName, friendBotTitle, ownerNameOf, remoteBotTitle, listedChatBots } from '../shared/remote-label.js'
99
import { t } from './i18n.js'
1010
import { currentPart, relativeShort } from './text.js'
1111

@@ -138,8 +138,9 @@ export function atSources({ roster, sessions, launched, iconFor }) {
138138
const snapshot = roster.getSnapshot()
139139
const face = isFaceRoom(snapshot.roomsById?.[session.sessionId])
140140
const team = (mentionRows(snapshot, session.sessionId, query) ?? []).map(row => ({
141-
// A friend's Bot by its one name, "Chief (Bob)"; its handle is the second line (#384).
142-
name: row.bot?.remote && !face ? remoteBotTitle(remoteRecord(row.bot)) : row.bot ? (row.bot.label ?? row.bot.name) : row.remote ? remoteBotTitle({ ...row.remote, displayName: row.remote.name, ownerName: row.remote.owner || row.remote.homeName }) : t('Everyone'),
141+
// A friend's Bot by its one name, "Chief (Bob)"; its handle is the second line (#384). A seat
142+
// carries its own name; a friend's chat is named as its sidebar row and header (#498).
143+
name: row.bot?.remote && !face ? (row.bot.kind === 'bot' ? remoteBotTitle(remoteRecord(row.bot)) : friendBotTitle(row.bot)) : row.bot ? (row.bot.label ?? row.bot.name) : row.remote ? remoteBotTitle({ ...row.remote, displayName: row.remote.name, ownerName: row.remote.owner || row.remote.homeName }) : t('Everyone'),
143144
...(row.bot?.role && !face ? { description: row.bot.role } : {}),
144145
...(row.remote ? { description: row.remote.handle } : {}),
145146
...(row.bot?.remote && !face && remoteRecord(row.bot).handle ? { description: remoteRecord(row.bot).handle } : {}),

‎core/src/client/brand.js‎

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// Paths stay here so tests can import them without React.
33

44
import { isAutoFaceTitle } from '../shared/face-title.js'
5-
import { remoteBotTitle, sharedRoomTitle } from '../shared/remote-label.js'
5+
import { friendBotTitle, sharedRoomTitle } from '../shared/remote-label.js'
66

77
export const FLYBOT_WING = 'M150 238 C 96 200, 40 196, 22 214 C 8 230, 40 262, 150 300 Z'
88
export const FLYBOT_WING_LOW = 'M150 318 C 104 300, 62 306, 54 322 C 48 338, 84 352, 150 352 Z'
@@ -32,12 +32,7 @@ export function sessionTabLabel(roster, sessionId) {
3232
const bot = roster.byId?.[sessionId]
3333
const room = roster.roomsById?.[sessionId]
3434
if (bot?.remote) {
35-
return remoteBotTitle({
36-
displayName: bot.name,
37-
ownerName: bot.remote.homeName || bot.remote.owner,
38-
handle: bot.remote.handle,
39-
isChief: bot.remote.isChief,
40-
})
35+
return friendBotTitle(bot)
4136
}
4237
// An ended face-to-face room (closed, or someone left) no longer lists people who are not in it (#250).
4338
// A name the user chose is kept.

‎core/src/client/cells.js‎

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { AnsweredCell, SettledQuestion, PendingQuestion } from './questions.js'
99
import { SecretToolCell } from './secret-card.js'
1010
import { dateLocale, t } from './i18n.js'
1111
import { remoteMessageText } from './flybot-registry.js'
12-
import { isListedRemoteChat, normRemoteHandle, remoteBotTitle } from '../shared/remote-label.js'
12+
import { cleanPeerName, friendBotOwner, normRemoteHandle, ownerNameOf, remoteBotTitle } from '../shared/remote-label.js'
1313
import { faceBotLabel, faceSeatLabel, faceSeats } from './face-seats.js'
1414
import { fileSize } from './flybot-slots.js'
1515
import { HeldReplyActions } from './held-reply.js'
@@ -193,11 +193,10 @@ export function AssistantCell({ node, groupPart, sessionId, openFile, fileMentio
193193
// turn's closing text carries the buttons; an earlier text of the same turn is only marked.
194194
function HeldReply({ held, roster, sessionId, actions, closing, children }) {
195195
const bot = roster.byId[sessionId]
196-
// The friend's Bot by name and owner when its chat is listed (#384), else the person (#418).
197-
const chat = (roster.bots ?? []).find(item => item.remote?.handle === held.to && isListedRemoteChat(item))
198-
const toName = chat
199-
? remoteBotTitle({ displayName: chat.name, ownerName: chat.remote.homeName || chat.remote.owner, handle: chat.remote.handle, isChief: chat.remote.isChief })
200-
: held.toOwner || held.to || t('a friend')
196+
// The person who gets the reply (#418, #498): the host stored it with the hold, so the card reads
197+
// the same live and after a reload; "Chief → Chief (SelfB1233)" named two Bots and no person.
198+
const chat = (roster.bots ?? []).find(item => item.remote?.handle === held.to)
199+
const toName = cleanPeerName(held.toOwner) || (chat ? friendBotOwner(chat) : ownerNameOf({ handle: held.to })) || held.to || t('a friend')
201200
return h('div', { className: 'bt-held-reply', 'data-state': held.status, 'data-held': held.status === 'pending' ? '' : undefined },
202201
children,
203202
closing ? h(HeldReplyActions, { held: { ...held, toName, from: bot?.name }, actions }) : null)

‎core/src/client/flybot-slots.js‎

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,13 @@
2525
// Section(title) and Row(label, hint) so the page looks like the others and reads zh in a zh UI
2626
//
2727
// The same registry object also carries the host Bot look so a plugin can draw a Bot
28-
// exactly as the sidebar does: __flybot__.BotAvatar / BotMark / lookOf (set in registry()).
28+
// exactly as the sidebar does: __flybot__.BotAvatar / BotMark / lookOf, and name a friend's Bot
29+
// or its owner the same way: __flybot__.names.friendBotTitle / friendBotOwner (set in registry()).
2930
//
3031
// Types: ds-bot/types/flybot.d.ts (FlyBotClientRegistry).
3132
import { Component, createElement as h, Fragment, useEffect, useState } from 'react'
3233
import { call as callHost } from './api.js'
33-
import { isListedRemoteChat } from '../shared/remote-label.js'
34+
import { friendBotOwner, friendBotTitle, isListedRemoteChat } from '../shared/remote-label.js'
3435
import { registry as baseRegistry } from './flybot-registry.js'
3536
import { BotAvatar, BotMark, lookOf } from './mark.js'
3637
import { t } from './i18n.js'
@@ -52,6 +53,8 @@ export function registry() {
5253
globalThis.__flybot__.BotMark ??= BotMark
5354
globalThis.__flybot__.BotAvatar ??= BotAvatar
5455
globalThis.__flybot__.lookOf ??= lookOf
56+
// The same for a friend's Bot's one name and its owner (#498), so the social plugin says it as the sidebar does.
57+
globalThis.__flybot__.names ??= { friendBotTitle, friendBotOwner }
5558
return slots
5659
}
5760

@@ -182,16 +185,12 @@ const friendChatOf = (approval, roster) => {
182185
// Bot that wrote it (#278). The chat's dock draws its card; the pointer opens that chat.
183186
// A Bot's held reply to a friend is decided on its bubble in the chat it answers (#418).
184187
const chatOf = (approval, roster) => approval.request?.meta?.roomId ?? approval.request?.meta?.replyIn?.sessionId ?? friendChatOf(approval, roster)?.id ?? (typeof approval.request?.from === 'string' ? approval.request.from : undefined)
185-
// A friend's Bot by name and owner when the roster knows its chat; otherwise what the host called it.
188+
// The person who gets it (#418, #498): the host's `recipientName`, else the owner of the friend's chat,
189+
// else what the host called it. "Chief → SelfB1233", never two Bot names side by side.
186190
function recipientName(approval, roster) {
187191
const handle = handleOf(approval.request?.to)
188192
const bot = friendChatOf(approval, roster)
189-
if (bot) {
190-
const owner = bot.remote.owner || bot.remote.homeName
191-
if (owner) return `${bot.name} (${owner})`
192-
return bot.name
193-
}
194-
return approval.recipientName ?? approval.targetName ?? handle ?? String(approval.request?.to ?? '')
193+
return approval.recipientName || (bot ? friendBotOwner(bot) : '') || approval.targetName || handle || String(approval.request?.to ?? '')
195194
}
196195

197196
export function FlyBotApprovals({ useRoster, actions } = {}) {

0 commit comments

Comments
 (0)