@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/dist/react.cjs CHANGED
@@ -73,6 +73,7 @@ __export(react_exports, {
73
73
  createOutbox: () => createOutbox,
74
74
  createReadCache: () => createReadCache,
75
75
  createWebOutboxStorage: () => createWebOutboxStorage,
76
+ effectiveActorKey: () => effectiveActorKey,
76
77
  extractReferences: () => extractReferences,
77
78
  formatConversationTime: () => formatConversationTime,
78
79
  formatDateSeparator: () => formatDateSeparator,
@@ -1303,6 +1304,11 @@ function createMessagingEngine(options) {
1303
1304
  if (openChannels.has(id) || disposed) return;
1304
1305
  const handle = manager.open({
1305
1306
  topic: conversationTopic(id),
1307
+ // Presence callbacks are JOIN-TIME in supabase-js. Declare the
1308
+ // read-only room shape on the first holder (the engine) so the
1309
+ // composer/typist/online hooks can attach without forcing a rebuild
1310
+ // after this durable channel has already subscribed.
1311
+ presence: { publish: false, state: {} },
1306
1312
  postgresChanges: [
1307
1313
  {
1308
1314
  event: "*",
@@ -2048,18 +2054,20 @@ function groupMessages(messages, args = {}) {
2048
2054
  let current = null;
2049
2055
  let previousDay = null;
2050
2056
  for (const message of messages) {
2057
+ const actorKey = args.actorKey?.(message) ?? `principal:${message.senderId}`;
2051
2058
  const day = message.createdAt.slice(0, 10);
2052
2059
  const startsNewDay = day !== previousDay;
2053
2060
  previousDay = day;
2054
2061
  const last = current?.messages.at(-1);
2055
2062
  const withinWindow = last !== void 0 && Math.abs(Date.parse(message.createdAt) - Date.parse(last.createdAt)) <= windowMs;
2056
- if (current !== null && current.senderId === message.senderId && withinWindow && !startsNewDay) {
2063
+ if (current !== null && current.senderId === message.senderId && current.actorKey === actorKey && withinWindow && !startsNewDay) {
2057
2064
  current.messages.push(message);
2058
2065
  continue;
2059
2066
  }
2060
2067
  if (current !== null) groups.push(current);
2061
2068
  current = {
2062
2069
  senderId: message.senderId,
2070
+ actorKey,
2063
2071
  messages: [message],
2064
2072
  dateSeparator: startsNewDay ? formatDateSeparator(message.createdAt, now, args.locale) : null
2065
2073
  };
@@ -2381,19 +2389,38 @@ var import_react5 = require("react");
2381
2389
 
2382
2390
  // src/core/actor.ts
2383
2391
  function readActorHint(metadata) {
2384
- const raw = metadata["actor"];
2385
- if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return null;
2386
- const record = raw;
2387
- const agentId = record["agentId"] ?? record["agent_id"];
2388
- if (typeof agentId !== "string" || agentId.length === 0) return null;
2389
- const name = record["agentName"] ?? record["agent_name"];
2390
- const avatar = record["agentAvatarUrl"] ?? record["agent_avatar_url"];
2392
+ const envelope = metadata;
2393
+ const raw = envelope["actor"];
2394
+ if (typeof raw === "object" && raw !== null && !Array.isArray(raw)) {
2395
+ const record = raw;
2396
+ const agentId = record["agentId"] ?? record["agent_id"];
2397
+ if (typeof agentId !== "string" || agentId.length === 0) return null;
2398
+ const name2 = record["agentName"] ?? record["agent_name"];
2399
+ const avatar2 = record["agentAvatarUrl"] ?? record["agent_avatar_url"];
2400
+ return {
2401
+ agentId,
2402
+ showPrincipalAttribution: true,
2403
+ ...typeof name2 === "string" && name2.length > 0 ? { agentName: name2 } : {},
2404
+ ...typeof avatar2 === "string" && avatar2.length > 0 ? { agentAvatarUrl: avatar2 } : {}
2405
+ };
2406
+ }
2407
+ const actorKind = envelope["actor_kind"] ?? envelope["actorKind"];
2408
+ if (actorKind !== "agent") return null;
2409
+ const id = envelope["actor_id"] ?? envelope["actorId"];
2410
+ const name = envelope["actor_label"] ?? envelope["actorLabel"];
2411
+ const avatar = envelope["actor_avatar_url"] ?? envelope["actorAvatarUrl"];
2391
2412
  return {
2392
- agentId,
2413
+ showPrincipalAttribution: false,
2414
+ ...typeof id === "string" && id.length > 0 ? { agentId: id } : {},
2393
2415
  ...typeof name === "string" && name.length > 0 ? { agentName: name } : {},
2394
2416
  ...typeof avatar === "string" && avatar.length > 0 ? { agentAvatarUrl: avatar } : {}
2395
2417
  };
2396
2418
  }
2419
+ function effectiveActorKey(message) {
2420
+ const hint = readActorHint(message.metadata);
2421
+ if (hint === null) return `principal:${message.senderId}`;
2422
+ return `agent:${hint.agentId ?? hint.agentName ?? "anonymous"}`;
2423
+ }
2397
2424
  function resolveActor(message, sender) {
2398
2425
  const hint = readActorHint(message.metadata);
2399
2426
  const humanName = sender?.displayName ?? message.senderId;
@@ -2405,7 +2432,11 @@ function resolveActor(message, sender) {
2405
2432
  displayName: hint.agentName ?? "Agent",
2406
2433
  avatarUrl: hint.agentAvatarUrl ?? null,
2407
2434
  isAgent: true,
2408
- onBehalfOfName: humanName,
2435
+ // The nested envelope explicitly models an agent acting on behalf of a
2436
+ // session principal. Platform event rows use the flat actor envelope as
2437
+ // the complete presentation identity; surfacing their audit writer as
2438
+ // "via Human" wrongly re-attaches that person to an automated event.
2439
+ onBehalfOfName: hint.showPrincipalAttribution ? humanName : null,
2409
2440
  principalUserId: message.senderId
2410
2441
  };
2411
2442
  }
@@ -2789,7 +2820,7 @@ function ConversationView(props) {
2789
2820
  );
2790
2821
  const anyOnline = others.some((participant) => online.has(participant.userId));
2791
2822
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: `mx-msg__thread${props.className !== void 0 ? ` ${props.className}` : ""}`, children: [
2792
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("header", { className: "mx-msg__header", children: [
2823
+ props.showHeader !== false ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("header", { className: "mx-msg__header", children: [
2793
2824
  props.onBack !== void 0 ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2794
2825
  "button",
2795
2826
  {
@@ -2824,8 +2855,8 @@ function ConversationView(props) {
2824
2855
  children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(UsersIcon, {})
2825
2856
  }
2826
2857
  ) : null })
2827
- ] }),
2828
- ai.available.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "mx-msg__ai-bar", children: ai.available.map((capability) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
2858
+ ] }) : null,
2859
+ props.showAi !== false && ai.available.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "mx-msg__ai-bar", children: ai.available.map((capability) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
2829
2860
  "button",
2830
2861
  {
2831
2862
  type: "button",
@@ -2839,7 +2870,7 @@ function ConversationView(props) {
2839
2870
  },
2840
2871
  capability
2841
2872
  )) }) : null,
2842
- ai.isRunning ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "mx-msg__ai-output", "aria-busy": "true", "aria-live": "polite", children: ai.partialText.length > 0 ? ai.partialText : /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
2873
+ props.showAi !== false && ai.isRunning ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "mx-msg__ai-output", "aria-busy": "true", "aria-live": "polite", children: ai.partialText.length > 0 ? ai.partialText : /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
2843
2874
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "mx-msg__ai-output-label", children: ai.activeCapability !== null ? `${AI_LABELS[ai.activeCapability]}\u2026` : "Working\u2026" }),
2844
2875
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2845
2876
  "span",
@@ -2848,7 +2879,7 @@ function ConversationView(props) {
2848
2879
  style: { display: "block", height: 11, width: "72%" }
2849
2880
  }
2850
2881
  )
2851
- ] }) }) : ai.error !== null ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "mx-msg__ai-output", style: { color: "var(--mx-msg-danger)" }, children: ai.error }) : ai.result !== null ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("p", { className: "mx-msg__ai-output", children: [
2882
+ ] }) }) : props.showAi !== false && ai.error !== null ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "mx-msg__ai-output", style: { color: "var(--mx-msg-danger)" }, children: ai.error }) : props.showAi !== false && ai.result !== null ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("p", { className: "mx-msg__ai-output", children: [
2852
2883
  ai.result.text,
2853
2884
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2854
2885
  "button",
@@ -2873,26 +2904,24 @@ function ConversationView(props) {
2873
2904
  children: "Load earlier messages"
2874
2905
  }
2875
2906
  ) : null,
2876
- groupMessages(messages).map((group) => {
2907
+ groupMessages(messages, { actorKey: effectiveActorKey }).map((group) => {
2877
2908
  const first = group.messages[0];
2878
2909
  if (first === void 0) return null;
2879
- const isMine = group.senderId === selfId;
2880
2910
  const sender = conversation.participants.find(
2881
2911
  (participant) => participant.userId === group.senderId
2882
2912
  ) ?? null;
2913
+ const actor = resolveActor(first, sender);
2914
+ const isMine = group.senderId === selfId && !actor.isAgent;
2883
2915
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { children: [
2884
2916
  group.dateSeparator !== null ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "mx-msg__day", children: group.dateSeparator }) : null,
2885
2917
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2886
2918
  MessageGroupView,
2887
2919
  {
2888
2920
  messages: group.messages,
2889
- sender,
2921
+ actor,
2890
2922
  isMine,
2891
2923
  onRetry: conversation.retry,
2892
2924
  onReply: composer.setReplyTo,
2893
- pendingIds: new Set(
2894
- conversation.pending.filter((entry) => entry.state === "failed").map((entry) => entry.clientMessageId)
2895
- ),
2896
2925
  failedEntryIdByClientId: new Map(
2897
2926
  conversation.pending.map((entry) => [entry.clientMessageId, entry.id])
2898
2927
  )
@@ -2905,13 +2934,20 @@ function ConversationView(props) {
2905
2934
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(TypingDots, {}),
2906
2935
  typists.label
2907
2936
  ] }) : null }),
2908
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ComposerView, { composer, disabled: false })
2937
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2938
+ ComposerView,
2939
+ {
2940
+ composer,
2941
+ disabled: false,
2942
+ ...props.renderComposerInput !== void 0 ? { renderInput: props.renderComposerInput } : {}
2943
+ }
2944
+ )
2909
2945
  ] });
2910
2946
  }
2911
2947
  function MessageGroupView(props) {
2912
2948
  const first = props.messages[0];
2913
2949
  if (first === void 0) return null;
2914
- const actor = resolveActor(first, props.sender);
2950
+ const actor = props.actor;
2915
2951
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: `mx-msg__group${props.isMine ? " mx-msg__group--mine" : ""}`, children: [
2916
2952
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2917
2953
  Avatar,
@@ -3098,7 +3134,24 @@ function ComposerView(props) {
3098
3134
  }
3099
3135
  )
3100
3136
  ] }) : null,
3101
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "mx-msg__composer-row", children: [
3137
+ props.renderInput !== void 0 ? props.renderInput({
3138
+ value: composer.value,
3139
+ disabled: props.disabled,
3140
+ canSend: composer.canSend,
3141
+ placeholder: "Write a reply\u2026",
3142
+ onChange: (value) => {
3143
+ composer.setValue(value);
3144
+ composer.onKeystroke();
3145
+ },
3146
+ onKeyDown: (event) => {
3147
+ if (event.key !== "Enter" || event.shiftKey) return;
3148
+ const isTouch = typeof globalThis.matchMedia === "function" && globalThis.matchMedia("(pointer: coarse)").matches;
3149
+ if (isTouch) return;
3150
+ event.preventDefault();
3151
+ composer.send();
3152
+ },
3153
+ onSubmit: composer.send
3154
+ }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "mx-msg__composer-row", children: [
3102
3155
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
3103
3156
  "textarea",
3104
3157
  {