@ai-matrx/messaging 0.10.1 → 0.10.3

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,45 @@
1
1
  # Changelog — `@ai-matrx/messaging`
2
2
 
3
+ ## 0.10.3 — 2026-09-08
4
+
5
+ ### Fixed
6
+
7
+ - Flat platform agent-event envelopes no longer display the human audit principal as a visible
8
+ “via” author. The principal remains immutable in `sender_id` and `principalUserId`; it is not
9
+ presentation identity for an automated event.
10
+ - The engine declares the conversation room's read-only presence shape on the first holder,
11
+ before subscribe. Typing and online-roster hooks can now attach without forcing a late-binding
12
+ room rebuild and its `channel.room.binding.refused` diagnostics.
13
+
14
+ ### Consumer action (C28)
15
+
16
+ None. Upgrade `@ai-matrx/realtime` alongside this package so presence/typing holders also receive
17
+ the ephemeral-roster backfill fix.
18
+
19
+ ## 0.10.2 — 2026-09-08
20
+
21
+ **Agent-authored review events wore the human audit principal's name, avatar, alignment, and
22
+ delivery tick.** The event rows already carried the canonical flat
23
+ `actor_kind: "agent"` / `actor_label` envelope, but effective-actor resolution recognized only
24
+ the older nested `metadata.actor` shape. The UI therefore presented an automated review as if
25
+ the signed-in reviewer had written it.
26
+
27
+ ### Fixed
28
+
29
+ - Flat canonical actor envelopes now resolve to an agent presentation without inheriting the
30
+ human principal's name or avatar. Adjacent agent and human events sharing one audit principal
31
+ are grouped separately, and agent events never receive the human's right-aligned/delivery-tick
32
+ treatment.
33
+ - `<ConversationView showHeader showAi>` lets an embedding workspace remove duplicate thread
34
+ chrome and capabilities it does not offer.
35
+ - `<ConversationView renderComposerInput>` lets a host supply its canonical textarea component
36
+ while this package retains draft, reply, typing, Enter-key, and send ownership.
37
+
38
+ ### Consumer action (C28)
39
+
40
+ None for existing screens. Embedded workspaces may set `showHeader={false}`, `showAi={false}`,
41
+ or inject their canonical composer through `renderComposerInput`.
42
+
3
43
  ## 0.10.1 — 2026-09-07
4
44
 
5
45
  **Every AI call would have been refused by the real server.** `<MessagingProvider>` never forwarded
package/dist/index.cjs CHANGED
@@ -42,6 +42,7 @@ __export(src_exports, {
42
42
  createOutbox: () => createOutbox,
43
43
  createReadCache: () => createReadCache,
44
44
  createWebOutboxStorage: () => createWebOutboxStorage,
45
+ effectiveActorKey: () => effectiveActorKey,
45
46
  extractReferences: () => extractReferences,
46
47
  formatConversationTime: () => formatConversationTime,
47
48
  formatDateSeparator: () => formatDateSeparator,
@@ -141,19 +142,38 @@ function createActionRegistry() {
141
142
 
142
143
  // src/core/actor.ts
143
144
  function readActorHint(metadata) {
144
- const raw = metadata["actor"];
145
- if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return null;
146
- const record = raw;
147
- const agentId = record["agentId"] ?? record["agent_id"];
148
- if (typeof agentId !== "string" || agentId.length === 0) return null;
149
- const name = record["agentName"] ?? record["agent_name"];
150
- const avatar = record["agentAvatarUrl"] ?? record["agent_avatar_url"];
145
+ const envelope = metadata;
146
+ const raw = envelope["actor"];
147
+ if (typeof raw === "object" && raw !== null && !Array.isArray(raw)) {
148
+ const record = raw;
149
+ const agentId = record["agentId"] ?? record["agent_id"];
150
+ if (typeof agentId !== "string" || agentId.length === 0) return null;
151
+ const name2 = record["agentName"] ?? record["agent_name"];
152
+ const avatar2 = record["agentAvatarUrl"] ?? record["agent_avatar_url"];
153
+ return {
154
+ agentId,
155
+ showPrincipalAttribution: true,
156
+ ...typeof name2 === "string" && name2.length > 0 ? { agentName: name2 } : {},
157
+ ...typeof avatar2 === "string" && avatar2.length > 0 ? { agentAvatarUrl: avatar2 } : {}
158
+ };
159
+ }
160
+ const actorKind = envelope["actor_kind"] ?? envelope["actorKind"];
161
+ if (actorKind !== "agent") return null;
162
+ const id = envelope["actor_id"] ?? envelope["actorId"];
163
+ const name = envelope["actor_label"] ?? envelope["actorLabel"];
164
+ const avatar = envelope["actor_avatar_url"] ?? envelope["actorAvatarUrl"];
151
165
  return {
152
- agentId,
166
+ showPrincipalAttribution: false,
167
+ ...typeof id === "string" && id.length > 0 ? { agentId: id } : {},
153
168
  ...typeof name === "string" && name.length > 0 ? { agentName: name } : {},
154
169
  ...typeof avatar === "string" && avatar.length > 0 ? { agentAvatarUrl: avatar } : {}
155
170
  };
156
171
  }
172
+ function effectiveActorKey(message) {
173
+ const hint = readActorHint(message.metadata);
174
+ if (hint === null) return `principal:${message.senderId}`;
175
+ return `agent:${hint.agentId ?? hint.agentName ?? "anonymous"}`;
176
+ }
157
177
  function resolveActor(message, sender) {
158
178
  const hint = readActorHint(message.metadata);
159
179
  const humanName = sender?.displayName ?? message.senderId;
@@ -165,7 +185,11 @@ function resolveActor(message, sender) {
165
185
  displayName: hint.agentName ?? "Agent",
166
186
  avatarUrl: hint.agentAvatarUrl ?? null,
167
187
  isAgent: true,
168
- onBehalfOfName: humanName,
188
+ // The nested envelope explicitly models an agent acting on behalf of a
189
+ // session principal. Platform event rows use the flat actor envelope as
190
+ // the complete presentation identity; surfacing their audit writer as
191
+ // "via Human" wrongly re-attaches that person to an automated event.
192
+ onBehalfOfName: hint.showPrincipalAttribution ? humanName : null,
169
193
  principalUserId: message.senderId
170
194
  };
171
195
  }
@@ -1352,6 +1376,11 @@ function createMessagingEngine(options) {
1352
1376
  if (openChannels.has(id) || disposed) return;
1353
1377
  const handle = manager.open({
1354
1378
  topic: conversationTopic(id),
1379
+ // Presence callbacks are JOIN-TIME in supabase-js. Declare the
1380
+ // read-only room shape on the first holder (the engine) so the
1381
+ // composer/typist/online hooks can attach without forcing a rebuild
1382
+ // after this durable channel has already subscribed.
1383
+ presence: { publish: false, state: {} },
1355
1384
  postgresChanges: [
1356
1385
  {
1357
1386
  event: "*",
@@ -1517,18 +1546,20 @@ function groupMessages(messages, args = {}) {
1517
1546
  let current = null;
1518
1547
  let previousDay = null;
1519
1548
  for (const message of messages) {
1549
+ const actorKey = args.actorKey?.(message) ?? `principal:${message.senderId}`;
1520
1550
  const day = message.createdAt.slice(0, 10);
1521
1551
  const startsNewDay = day !== previousDay;
1522
1552
  previousDay = day;
1523
1553
  const last = current?.messages.at(-1);
1524
1554
  const withinWindow = last !== void 0 && Math.abs(Date.parse(message.createdAt) - Date.parse(last.createdAt)) <= windowMs;
1525
- if (current !== null && current.senderId === message.senderId && withinWindow && !startsNewDay) {
1555
+ if (current !== null && current.senderId === message.senderId && current.actorKey === actorKey && withinWindow && !startsNewDay) {
1526
1556
  current.messages.push(message);
1527
1557
  continue;
1528
1558
  }
1529
1559
  if (current !== null) groups.push(current);
1530
1560
  current = {
1531
1561
  senderId: message.senderId,
1562
+ actorKey,
1532
1563
  messages: [message],
1533
1564
  dateSeparator: startsNewDay ? formatDateSeparator(message.createdAt, now, args.locale) : null
1534
1565
  };