Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 32 additions & 1 deletion apps/web/src/chat/threads-api.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { describe, expect, test } from "bun:test";

import { agentDeploySourceAssetName } from "../agent-deploy";
import { MYRA_SOURCE_CONFIG } from "../myra-source";
import { displayAgentName } from "./threads-api";
import { displayAgentName, resolveAvatarName, type RoomParticipant } from "./threads-api";

describe("displayAgentName", () => {
test("renders Myra's fixed display name for her asset", () => {
Expand All @@ -18,3 +18,34 @@ describe("displayAgentName", () => {
expect(displayAgentName("some-other-asset")).toBe("some-other-asset");
});
});

describe("resolveAvatarName", () => {
const participants: readonly RoomParticipant[] = [
{ id: "p1", kind: "person", name: "alice", address: "alice@example.com" },
{ id: "a1", kind: "agent", name: "Myra", address: "myra@example.com" },
];

test("uses the person's own real name for their own turn, never 'You'", () => {
const name = resolveAvatarName(
{ author: "me", authorName: "You", address: "alice@example.com" },
participants,
);
expect(name).toBe("alice");
});

test("uses the matching participant's name for another author", () => {
const name = resolveAvatarName(
{ author: "other", authorName: "myra", address: "myra@example.com" },
participants,
);
expect(name).toBe("Myra");
});

test("falls back to authorName when no participant matches", () => {
const name = resolveAvatarName(
{ author: "other", authorName: "someone", address: "unknown@example.com" },
participants,
);
expect(name).toBe("someone");
});
});
12 changes: 12 additions & 0 deletions apps/web/src/chat/threads-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -687,6 +687,18 @@ export function resolveParticipantName(
);
}

/** A turn's avatar name: the matching participant's real name, including
* the person's own — never the "You" transcript label, which stays for the
* row's own text elsewhere. Falls back to `resolveParticipantName` when no
* participant matches the turn's address. */
export function resolveAvatarName(
message: Pick<RoomMessage, "author" | "authorName" | "address">,
participants: readonly RoomParticipant[],
): string {
const matched = participants.find((participant) => participant.address === message.address);
return matched?.name ?? resolveParticipantName(message, participants);
}

type RoomTurn = {
readonly id: string;
readonly messageId: string;
Expand Down
10 changes: 6 additions & 4 deletions apps/web/src/pages/workbench-room-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import {
ancestorChain,
listRoomParticipants,
readRoom,
resolveAvatarName,
resolveParticipantName,
sendToRoom,
subscribeToInbox,
Expand Down Expand Up @@ -60,9 +61,10 @@ function RoomMessageRow({
/** Undefined in the sub-thread panel, where a row is read-only context. */
readonly onReply?: (message: RoomMessage) => void;
}) {
// Avatars read off the participant's display name (Myra → "M"), never the
// run address local part a mail turn otherwise carries.
const displayName = resolveParticipantName(message, participants);
// Avatars read off the participant's real name — the person's own
// included, never the "You" transcript label — falling back to the
// address local part a mail turn otherwise carries.
const avatarName = resolveAvatarName(message, participants);
const matched = participants.find((participant) => participant.address === message.address);
const kind = message.author !== "me" && matched?.kind === "agent" ? "agent" : "person";
// The person's own send carries a trailing roster block so agents in the
Expand All @@ -75,7 +77,7 @@ function RoomMessageRow({
<span className="shell-ch-avatar">
<IdentityAvatar
kind={kind}
name={displayName}
name={avatarName}
principalId={matched?.id ?? message.address}
/>
</span>
Expand Down
Loading