relay-companion 0.1.366 → 0.1.368

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "relay-companion",
3
- "version": "0.1.366",
3
+ "version": "0.1.368",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "relay-companion",
9
- "version": "0.1.366",
9
+ "version": "0.1.368",
10
10
  "license": "MIT",
11
11
  "dependencies": {
12
12
  "@anthropic-ai/claude-agent-sdk": "0.3.195",
@@ -8051,7 +8051,17 @@
8051
8051
  const add = (person, role) => {
8052
8052
  if (!person) return;
8053
8053
  const email = String(person.email || "").trim().toLowerCase();
8054
- const name = String(person.name || "").trim();
8054
+ const currentUser = Boolean(email && email === String(account.email || "").trim().toLowerCase());
8055
+ // A group's stored roster belongs to its owner, so its labels can differ
8056
+ // from how the viewer has saved the same person. Mentions and group info
8057
+ // are viewer-facing: their own contact book is authoritative there.
8058
+ const savedContact = !currentUser && email
8059
+ ? contactsList.find((contact) => contactEmails(contact)
8060
+ .some((address) => String(address || "").trim().toLowerCase() === email))
8061
+ : null;
8062
+ const savedName = String((savedContact && savedContact.name) || "").trim();
8063
+ const shown = savedName ? { ...person, name:savedName } : person;
8064
+ const name = String(shown.name || "").trim();
8055
8065
  if (!email && !name && !person.userId && !person.contactId && !person.id) return;
8056
8066
  const key = email
8057
8067
  ? `email:${email}`
@@ -8063,9 +8073,9 @@
8063
8073
  if (!key || seen.has(key)) return;
8064
8074
  seen.add(key);
8065
8075
  roster.push({
8066
- ...person,
8076
+ ...shown,
8067
8077
  role,
8068
- currentUser: Boolean(email && email === String(account.email || "").trim().toLowerCase()),
8078
+ currentUser,
8069
8079
  });
8070
8080
  };
8071
8081
  // Owner mutations historically returned the compact owned view without an
@@ -9020,9 +9030,12 @@
9020
9030
  t.unreadCount = t.msgs.filter((m) => m.unread).length;
9021
9031
  // The thread's display party is the other side: prefer the latest
9022
9032
  // inbound sender, else the latest outbound recipient.
9023
- const lastIn = [...t.msgs].reverse().find((m) => m.direction === "in");
9024
- t.party = (lastIn && lastIn.party) || latest.party || "Conversation";
9025
- t.partyKey = (lastIn && lastIn.partyKey) || latest.partyKey || `name:${String(t.party).trim().toLowerCase()}`;
9033
+ const lastIn = [...t.msgs].reverse().find((m) => m.direction === "in" && !m.ownedAgent);
9034
+ const counterpart = lastIn || [...t.msgs].reverse().find((m) => !m.ownedAgent) || latest;
9035
+ // An owned agent is a speaking face inside the existing room, never a
9036
+ // new participant. Name and key the room from its human counterpart.
9037
+ t.party = counterpart.party || "Conversation";
9038
+ t.partyKey = counterpart.partyKey || `name:${String(t.party).trim().toLowerCase()}`;
9026
9039
  t.isGroup = t.msgs.some((m) => m.isGroup);
9027
9040
  t.groupId = (t.msgs.find((m) => m.groupId) || {}).groupId || "";
9028
9041
  t.groupName = (t.msgs.find((m) => m.groupName) || {}).groupName || "";
@@ -9040,7 +9053,7 @@
9040
9053
  const threads = buildThreads();
9041
9054
  for (const t of threads) {
9042
9055
  const peopleByIdentity = new Map();
9043
- for (const m of t.msgs.filter((m) => m.direction === "in" && m.party)) {
9056
+ for (const m of t.msgs.filter((m) => m.direction === "in" && !m.ownedAgent && m.party)) {
9044
9057
  peopleByIdentity.set(m.partyKey || `name:${String(m.party).trim().toLowerCase()}`, m.party);
9045
9058
  }
9046
9059
  const people = [...peopleByIdentity.values()];
@@ -9887,23 +9900,28 @@
9887
9900
  .map((message) => normalizedPartyName(message.party))
9888
9901
  .filter(Boolean),
9889
9902
  );
9890
- const participantNames = new Set(
9891
- (thread.msgs || [])
9892
- .filter((message) => message.direction === "in" && !message.ownedAgent)
9893
- .map((message) => String(message.party || "").trim())
9894
- .filter(Boolean),
9895
- );
9903
+ const participantNames = new Set();
9896
9904
  if (thread.isGroup) {
9897
9905
  const group = groupsList.find((candidate) => String(candidate.id || "") === String(thread.groupId || ""))
9898
9906
  || groupsList.find((candidate) => normalizedPartyName(candidate.name) === normalizedPartyName(thread.groupName));
9907
+ // The current roster decides who can be mentioned. Historical sender
9908
+ // labels can be stale (or belong to somebody who has left), and must
9909
+ // not sit beside the viewer's saved contact name as a second person.
9899
9910
  for (const member of group ? groupInfoRoster(group) : []) {
9900
9911
  if (!member.currentUser && member.name) participantNames.add(String(member.name).trim());
9901
9912
  }
9902
- } else if (thread.party && !ownedAgentParticipantNames.has(normalizedPartyName(thread.party))) {
9913
+ } else {
9914
+ for (const message of thread.msgs || []) {
9915
+ if (message.direction === "in" && !message.ownedAgent && message.party) {
9916
+ participantNames.add(String(message.party).trim());
9917
+ }
9918
+ }
9919
+ if (thread.party && !ownedAgentParticipantNames.has(normalizedPartyName(thread.party))) {
9903
9920
  // buildThreads names a wire thread after its newest inbound sender.
9904
9921
  // An owned-agent response is inbound too, but it is an execution face,
9905
9922
  // not a second participant beside @my_codex / @my_claude.
9906
- participantNames.add(String(thread.party).trim());
9923
+ participantNames.add(String(thread.party).trim());
9924
+ }
9907
9925
  }
9908
9926
  const mentionOptions = [
9909
9927
  ...(payload.features?.requests === true ? [
@@ -10050,7 +10068,7 @@
10050
10068
  id:`optimistic-agent:${idempotencyKey}:${provider}`, relayId:"", threadId:sendAnchor.threadId || thread.threadId,
10051
10069
  inReplyToRelayId:optimisticId, direction:"out", seen:false, seenAt:null, readReceipts:[],
10052
10070
  title:"I'm on it", body:"I'm on it", party:provider === "claude" ? "My Claude" : "My Codex",
10053
- partyKey:`owned-agent:${provider}`, at:new Date(Date.now() + index + 1).toISOString(), isGroup:Boolean(sendAnchor.isGroup || thread.isGroup),
10071
+ partyKey:sendAnchor.partyKey || thread.partyKey || "", at:new Date(Date.now() + index + 1).toISOString(), isGroup:Boolean(sendAnchor.isGroup || thread.isGroup),
10054
10072
  groupName:sendAnchor.groupName || thread.groupName || "", unread:false, textLike:true, request:false, pending:true,
10055
10073
  source:{ host:"relay-agent-run", surface:provider === "claude" ? "claude_code" : "codex" }, ownedAgent:true, agentInvocation:true,
10056
10074
  reactions:{ aggregates:[], events:[] }, attachments:[], preview:"",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "relay-companion",
3
- "version": "0.1.366",
3
+ "version": "0.1.368",
4
4
  "description": "Install Relay for Claude Code, Cowork, and Codex, then sign in from the Relay pill.",
5
5
  "homepage": "https://sendrelays.com/get-started",
6
6
  "repository": {