@ai-matrx/messaging 0.8.0 → 0.10.0

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.js CHANGED
@@ -261,6 +261,12 @@ ${JSON.stringify(references, null, 2)}
261
261
  }
262
262
 
263
263
  // src/core/ai.ts
264
+ function unreadCutoff(messages, unreadCount) {
265
+ if (unreadCount <= 0) return null;
266
+ const index = messages.length - unreadCount - 1;
267
+ if (index < 0) return null;
268
+ return messages[index]?.createdAt ?? null;
269
+ }
264
270
  function nameOf(participants, senderId) {
265
271
  return participants.find((p) => p.userId === senderId)?.displayName ?? senderId;
266
272
  }
@@ -281,22 +287,23 @@ function buildTranscript(messages, participants, limit, since) {
281
287
  }
282
288
  function createMessagingAi(options) {
283
289
  const limit = options.maxTranscriptMessages ?? 200;
284
- function agentFor(capability) {
285
- const agentId = options.agents[capability];
286
- if (typeof agentId !== "string" || agentId.length === 0) {
290
+ function identityFor(capability) {
291
+ const identity = options.agents[capability];
292
+ if (identity === void 0 || identity.agentId.length === 0) {
287
293
  throw new MessagingError(
288
294
  "misconfigured",
289
295
  `Messaging AI capability "${capability}" has no agent configured`,
290
- `Pass agents={{ ${capability}: "<agent-id>" }} to <MessagingProvider>. Agent definitions live in the database, never in this package \u2014 the id is the only part a host injects. Until then the UI hides this action rather than offering a button that cannot work.`
296
+ `Pass agents={{ ${capability}: { agentId: "<agent-id>" } }} to <MessagingProvider>. Agent definitions live in the database, never in this package \u2014 the identity is the only part a host injects. Until then the UI hides this action rather than offering a button that cannot work.`
291
297
  );
292
298
  }
293
- return agentId;
299
+ return identity;
294
300
  }
295
- async function run(capability, variables, userInput, signal) {
296
- const agentId = agentFor(capability);
301
+ async function run(capability, variables, userInput, signal, onText) {
302
+ const identity = identityFor(capability);
303
+ const overrides = identity.configOverrides;
297
304
  const completed = await runAgentToCompletion(
298
305
  options.transport,
299
- agentId,
306
+ identity.agentId,
300
307
  {
301
308
  ...newEphemeralConversationStart(),
302
309
  organization_id: options.organizationId,
@@ -305,9 +312,15 @@ function createMessagingAi(options) {
305
312
  initiation: "user",
306
313
  // THE USER-INPUT LAW: structured content is NEVER here.
307
314
  ...userInput !== null ? { user_input: userInput } : {},
315
+ // The settings half of the injected identity, verbatim. An absent one
316
+ // is OMITTED rather than sent as null: the agent's own settings stand.
317
+ ...overrides != null ? { config_overrides: overrides } : {},
308
318
  variables
309
319
  },
310
- signal !== void 0 ? { signal } : {}
320
+ {
321
+ ...signal !== void 0 ? { signal } : {},
322
+ ...onText !== void 0 ? { onChunk: onText } : {}
323
+ }
311
324
  );
312
325
  return {
313
326
  capability,
@@ -340,27 +353,26 @@ function createMessagingAi(options) {
340
353
  ...args.since != null ? { unread_since: args.since } : {}
341
354
  };
342
355
  }
356
+ function configured(capability) {
357
+ const identity = options.agents[capability];
358
+ return identity !== void 0 && identity.agentId.length > 0;
359
+ }
343
360
  return {
344
361
  available: () => ["catchUp", "summarize", "actionItems", "draftReply"].filter(
345
- (capability) => {
346
- const id = options.agents[capability];
347
- return typeof id === "string" && id.length > 0;
348
- }
362
+ configured
349
363
  ),
350
- isAvailable(capability) {
351
- const id = options.agents[capability];
352
- return typeof id === "string" && id.length > 0;
353
- },
354
- catchMeUp: (args) => run("catchUp", variablesFor(args), null, args.signal),
355
- summarize: (args) => run("summarize", variablesFor(args), null, args.signal),
356
- extractActionItems: (args) => run("actionItems", variablesFor(args), null, args.signal),
364
+ isAvailable: configured,
365
+ catchMeUp: (args) => run("catchUp", variablesFor(args), null, args.signal, args.onText),
366
+ summarize: (args) => run("summarize", variablesFor(args), null, args.signal, args.onText),
367
+ extractActionItems: (args) => run("actionItems", variablesFor(args), null, args.signal, args.onText),
357
368
  draftReply: (args) => run(
358
369
  "draftReply",
359
370
  variablesFor(args),
360
371
  // The ONE genuine human utterance in this module: what the user asked
361
372
  // the drafter for. Everything else rode `variables`.
362
373
  args.instruction !== void 0 && args.instruction.trim().length > 0 ? args.instruction.trim() : null,
363
- args.signal
374
+ args.signal,
375
+ args.onText
364
376
  )
365
377
  };
366
378
  }
@@ -1388,12 +1400,13 @@ function requireOrg(organizationId, operation) {
1388
1400
  return organizationId;
1389
1401
  }
1390
1402
  function createMessagingRepository(options) {
1391
- const { client, identity } = options;
1403
+ const { identity } = options;
1392
1404
  const org = requireOrg(identity.organizationId, "createMessagingRepository");
1393
1405
  const userCache = createReadCache({
1394
1406
  ttlMs: options.userTtlMs ?? 5 * 6e4,
1395
1407
  ...options.now !== void 0 ? { now: options.now } : {}
1396
1408
  });
1409
+ const client = options.client;
1397
1410
  const db = () => client.schema(MESSAGING_SCHEMA);
1398
1411
  const rpcDb = () => client.schema(MESSAGING_RPC_SCHEMA);
1399
1412
  async function withSessionRetry(operation, run) {
@@ -1721,7 +1734,14 @@ function MessagingProvider(props) {
1721
1734
  const ready = client != null && typeof userId === "string" && userId.length > 0 && typeof organizationId === "string" && organizationId.length > 0;
1722
1735
  const hostProvidesRealtime = useIsRealtimeProviderMounted();
1723
1736
  if (hostProvidesRealtime) return /* @__PURE__ */ jsx(MessagingRuntime, { ...props });
1724
- return /* @__PURE__ */ jsx(RealtimeProvider, { client: ready ? client : null, actorId: userId ?? void 0, children: /* @__PURE__ */ jsx(MessagingRuntime, { ...props }) });
1737
+ return /* @__PURE__ */ jsx(
1738
+ RealtimeProvider,
1739
+ {
1740
+ client: ready ? client : null,
1741
+ actorId: userId ?? void 0,
1742
+ children: /* @__PURE__ */ jsx(MessagingRuntime, { ...props })
1743
+ }
1744
+ );
1725
1745
  }
1726
1746
  function MessagingRuntime(props) {
1727
1747
  const { client, userId, organizationId, children } = props;
@@ -1750,6 +1770,10 @@ function MessagingRuntime(props) {
1750
1770
  (diagnosticRef.current ?? defaultDiagnostics)(event);
1751
1771
  };
1752
1772
  const repository = createMessagingRepository({
1773
+ // THE ONE NARROWING, and it lives INSIDE the package. The prop is shallow
1774
+ // so a host's fully-typed client is assignable at all (see
1775
+ // supabase-shape.ts); everything below this line works against the rich
1776
+ // contract, which `supabase-shape.test.ts` proves a real client honors.
1753
1777
  client,
1754
1778
  identity,
1755
1779
  ...props.resolveSession !== void 0 ? { resolveSession: props.resolveSession } : {}
@@ -1792,14 +1816,16 @@ function MessagingRuntime(props) {
1792
1816
  }, [engine]);
1793
1817
  const transport = props.transport;
1794
1818
  const agents = props.agents;
1819
+ const maxTranscriptMessages = props.maxTranscriptMessages;
1795
1820
  const ai = useMemo(() => {
1796
1821
  if (transport === void 0 || agents === void 0 || !ready) return null;
1797
1822
  return createMessagingAi({
1798
1823
  transport,
1799
1824
  organizationId,
1800
- agents
1825
+ agents,
1826
+ ...maxTranscriptMessages !== void 0 ? { maxTranscriptMessages } : {}
1801
1827
  });
1802
- }, [transport, agents, ready, organizationId]);
1828
+ }, [transport, agents, ready, organizationId, maxTranscriptMessages]);
1803
1829
  const renderers = props.actionRenderers;
1804
1830
  const rendererMap = useMemo(() => {
1805
1831
  const map = /* @__PURE__ */ new Map();
@@ -2127,6 +2153,10 @@ function useMessagingAi(conversationId) {
2127
2153
  const host = useMessagingHost();
2128
2154
  const conversation = useConversation(conversationId);
2129
2155
  const [isRunning, setRunning] = useState(false);
2156
+ const [partialText, setPartialText] = useState("");
2157
+ const [activeCapability, setActiveCapability] = useState(
2158
+ null
2159
+ );
2130
2160
  const [result, setResult] = useState(null);
2131
2161
  const [error, setError] = useState(null);
2132
2162
  const abortRef = useRef2(null);
@@ -2140,11 +2170,15 @@ function useMessagingAi(conversationId) {
2140
2170
  return {
2141
2171
  available: ai?.available() ?? [],
2142
2172
  isRunning,
2173
+ partialText,
2174
+ activeCapability,
2143
2175
  result,
2144
2176
  error,
2145
2177
  clear: () => {
2146
2178
  setResult(null);
2147
2179
  setError(null);
2180
+ setPartialText("");
2181
+ setActiveCapability(null);
2148
2182
  },
2149
2183
  run: (capability, args = {}) => {
2150
2184
  if (ai === null || conversationId === null) return;
@@ -2153,22 +2187,32 @@ function useMessagingAi(conversationId) {
2153
2187
  abortRef.current = controller;
2154
2188
  setRunning(true);
2155
2189
  setError(null);
2190
+ setResult(null);
2191
+ setPartialText("");
2192
+ setActiveCapability(capability);
2156
2193
  const base = {
2157
2194
  conversationId,
2158
2195
  messages: conversation.messages,
2159
2196
  participants: conversation.participants,
2160
- signal: controller.signal
2197
+ signal: controller.signal,
2198
+ // Live progress. Aborted runs stop painting immediately — a cancelled
2199
+ // answer must not keep growing on screen.
2200
+ onText: (text) => {
2201
+ if (!controller.signal.aborted) setPartialText(text);
2202
+ }
2161
2203
  };
2162
2204
  const call = () => {
2163
2205
  switch (capability) {
2164
- case "catchUp": {
2165
- const summary = conversation.summary;
2166
- const self = summary?.participants.find(
2167
- (participant) => participant.userId === host?.identity.userId
2168
- );
2169
- void self;
2170
- return ai.catchMeUp({ ...base, since: null });
2171
- }
2206
+ case "catchUp":
2207
+ return ai.catchMeUp({
2208
+ ...base,
2209
+ // "What did I miss" is about the UNREAD tail. Sending the whole
2210
+ // window made this capability a second Summarize.
2211
+ since: unreadCutoff(
2212
+ conversation.messages,
2213
+ conversation.summary?.unreadCount ?? 0
2214
+ )
2215
+ });
2172
2216
  case "summarize":
2173
2217
  return ai.summarize(base);
2174
2218
  case "actionItems":
@@ -2691,7 +2735,16 @@ function ConversationView(props) {
2691
2735
  },
2692
2736
  capability
2693
2737
  )) }) : null,
2694
- ai.isRunning ? /* @__PURE__ */ jsx4("p", { className: "mx-msg__ai-output", "aria-busy": "true", children: /* @__PURE__ */ jsx4("span", { className: "mx-msg__skeleton", style: { display: "block", height: 11, width: "72%" } }) }) : ai.error !== null ? /* @__PURE__ */ jsx4("p", { className: "mx-msg__ai-output", style: { color: "var(--mx-msg-danger)" }, children: ai.error }) : ai.result !== null ? /* @__PURE__ */ jsxs2("p", { className: "mx-msg__ai-output", children: [
2738
+ ai.isRunning ? /* @__PURE__ */ jsx4("p", { className: "mx-msg__ai-output", "aria-busy": "true", "aria-live": "polite", children: ai.partialText.length > 0 ? ai.partialText : /* @__PURE__ */ jsxs2(Fragment, { children: [
2739
+ /* @__PURE__ */ jsx4("span", { className: "mx-msg__ai-output-label", children: ai.activeCapability !== null ? `${AI_LABELS[ai.activeCapability]}\u2026` : "Working\u2026" }),
2740
+ /* @__PURE__ */ jsx4(
2741
+ "span",
2742
+ {
2743
+ className: "mx-msg__skeleton",
2744
+ style: { display: "block", height: 11, width: "72%" }
2745
+ }
2746
+ )
2747
+ ] }) }) : ai.error !== null ? /* @__PURE__ */ jsx4("p", { className: "mx-msg__ai-output", style: { color: "var(--mx-msg-danger)" }, children: ai.error }) : ai.result !== null ? /* @__PURE__ */ jsxs2("p", { className: "mx-msg__ai-output", children: [
2695
2748
  ai.result.text,
2696
2749
  /* @__PURE__ */ jsx4(
2697
2750
  "button",
@@ -3094,6 +3147,7 @@ export {
3094
3147
  resolveActor,
3095
3148
  splitText,
3096
3149
  summarizeText,
3150
+ unreadCutoff,
3097
3151
  useComposer,
3098
3152
  useConversation,
3099
3153
  useConversations,