@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.cjs CHANGED
@@ -95,6 +95,7 @@ __export(react_exports, {
95
95
  resolveActor: () => resolveActor,
96
96
  splitText: () => splitText,
97
97
  summarizeText: () => summarizeText,
98
+ unreadCutoff: () => unreadCutoff,
98
99
  useComposer: () => useComposer,
99
100
  useConversation: () => useConversation,
100
101
  useConversations: () => useConversations,
@@ -354,6 +355,12 @@ ${JSON.stringify(references, null, 2)}
354
355
  }
355
356
 
356
357
  // src/core/ai.ts
358
+ function unreadCutoff(messages, unreadCount) {
359
+ if (unreadCount <= 0) return null;
360
+ const index = messages.length - unreadCount - 1;
361
+ if (index < 0) return null;
362
+ return messages[index]?.createdAt ?? null;
363
+ }
357
364
  function nameOf(participants, senderId) {
358
365
  return participants.find((p) => p.userId === senderId)?.displayName ?? senderId;
359
366
  }
@@ -374,22 +381,23 @@ function buildTranscript(messages, participants, limit, since) {
374
381
  }
375
382
  function createMessagingAi(options) {
376
383
  const limit = options.maxTranscriptMessages ?? 200;
377
- function agentFor(capability) {
378
- const agentId = options.agents[capability];
379
- if (typeof agentId !== "string" || agentId.length === 0) {
384
+ function identityFor(capability) {
385
+ const identity = options.agents[capability];
386
+ if (identity === void 0 || identity.agentId.length === 0) {
380
387
  throw new MessagingError(
381
388
  "misconfigured",
382
389
  `Messaging AI capability "${capability}" has no agent configured`,
383
- `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.`
390
+ `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.`
384
391
  );
385
392
  }
386
- return agentId;
393
+ return identity;
387
394
  }
388
- async function run(capability, variables, userInput, signal) {
389
- const agentId = agentFor(capability);
395
+ async function run(capability, variables, userInput, signal, onText) {
396
+ const identity = identityFor(capability);
397
+ const overrides = identity.configOverrides;
390
398
  const completed = await (0, import_matrx.runAgentToCompletion)(
391
399
  options.transport,
392
- agentId,
400
+ identity.agentId,
393
401
  {
394
402
  ...(0, import_matrx.newEphemeralConversationStart)(),
395
403
  organization_id: options.organizationId,
@@ -398,9 +406,15 @@ function createMessagingAi(options) {
398
406
  initiation: "user",
399
407
  // THE USER-INPUT LAW: structured content is NEVER here.
400
408
  ...userInput !== null ? { user_input: userInput } : {},
409
+ // The settings half of the injected identity, verbatim. An absent one
410
+ // is OMITTED rather than sent as null: the agent's own settings stand.
411
+ ...overrides != null ? { config_overrides: overrides } : {},
401
412
  variables
402
413
  },
403
- signal !== void 0 ? { signal } : {}
414
+ {
415
+ ...signal !== void 0 ? { signal } : {},
416
+ ...onText !== void 0 ? { onChunk: onText } : {}
417
+ }
404
418
  );
405
419
  return {
406
420
  capability,
@@ -433,27 +447,26 @@ function createMessagingAi(options) {
433
447
  ...args.since != null ? { unread_since: args.since } : {}
434
448
  };
435
449
  }
450
+ function configured(capability) {
451
+ const identity = options.agents[capability];
452
+ return identity !== void 0 && identity.agentId.length > 0;
453
+ }
436
454
  return {
437
455
  available: () => ["catchUp", "summarize", "actionItems", "draftReply"].filter(
438
- (capability) => {
439
- const id = options.agents[capability];
440
- return typeof id === "string" && id.length > 0;
441
- }
456
+ configured
442
457
  ),
443
- isAvailable(capability) {
444
- const id = options.agents[capability];
445
- return typeof id === "string" && id.length > 0;
446
- },
447
- catchMeUp: (args) => run("catchUp", variablesFor(args), null, args.signal),
448
- summarize: (args) => run("summarize", variablesFor(args), null, args.signal),
449
- extractActionItems: (args) => run("actionItems", variablesFor(args), null, args.signal),
458
+ isAvailable: configured,
459
+ catchMeUp: (args) => run("catchUp", variablesFor(args), null, args.signal, args.onText),
460
+ summarize: (args) => run("summarize", variablesFor(args), null, args.signal, args.onText),
461
+ extractActionItems: (args) => run("actionItems", variablesFor(args), null, args.signal, args.onText),
450
462
  draftReply: (args) => run(
451
463
  "draftReply",
452
464
  variablesFor(args),
453
465
  // The ONE genuine human utterance in this module: what the user asked
454
466
  // the drafter for. Everything else rode `variables`.
455
467
  args.instruction !== void 0 && args.instruction.trim().length > 0 ? args.instruction.trim() : null,
456
- args.signal
468
+ args.signal,
469
+ args.onText
457
470
  )
458
471
  };
459
472
  }
@@ -1479,12 +1492,13 @@ function requireOrg(organizationId, operation) {
1479
1492
  return organizationId;
1480
1493
  }
1481
1494
  function createMessagingRepository(options) {
1482
- const { client, identity } = options;
1495
+ const { identity } = options;
1483
1496
  const org = requireOrg(identity.organizationId, "createMessagingRepository");
1484
1497
  const userCache = createReadCache({
1485
1498
  ttlMs: options.userTtlMs ?? 5 * 6e4,
1486
1499
  ...options.now !== void 0 ? { now: options.now } : {}
1487
1500
  });
1501
+ const client = options.client;
1488
1502
  const db = () => client.schema(MESSAGING_SCHEMA);
1489
1503
  const rpcDb = () => client.schema(MESSAGING_RPC_SCHEMA);
1490
1504
  async function withSessionRetry(operation, run) {
@@ -1812,7 +1826,14 @@ function MessagingProvider(props) {
1812
1826
  const ready = client != null && typeof userId === "string" && userId.length > 0 && typeof organizationId === "string" && organizationId.length > 0;
1813
1827
  const hostProvidesRealtime = (0, import_react2.useIsRealtimeProviderMounted)();
1814
1828
  if (hostProvidesRealtime) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MessagingRuntime, { ...props });
1815
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react2.RealtimeProvider, { client: ready ? client : null, actorId: userId ?? void 0, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MessagingRuntime, { ...props }) });
1829
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1830
+ import_react2.RealtimeProvider,
1831
+ {
1832
+ client: ready ? client : null,
1833
+ actorId: userId ?? void 0,
1834
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MessagingRuntime, { ...props })
1835
+ }
1836
+ );
1816
1837
  }
1817
1838
  function MessagingRuntime(props) {
1818
1839
  const { client, userId, organizationId, children } = props;
@@ -1841,6 +1862,10 @@ function MessagingRuntime(props) {
1841
1862
  (diagnosticRef.current ?? defaultDiagnostics)(event);
1842
1863
  };
1843
1864
  const repository = createMessagingRepository({
1865
+ // THE ONE NARROWING, and it lives INSIDE the package. The prop is shallow
1866
+ // so a host's fully-typed client is assignable at all (see
1867
+ // supabase-shape.ts); everything below this line works against the rich
1868
+ // contract, which `supabase-shape.test.ts` proves a real client honors.
1844
1869
  client,
1845
1870
  identity,
1846
1871
  ...props.resolveSession !== void 0 ? { resolveSession: props.resolveSession } : {}
@@ -1883,14 +1908,16 @@ function MessagingRuntime(props) {
1883
1908
  }, [engine]);
1884
1909
  const transport = props.transport;
1885
1910
  const agents = props.agents;
1911
+ const maxTranscriptMessages = props.maxTranscriptMessages;
1886
1912
  const ai = (0, import_react.useMemo)(() => {
1887
1913
  if (transport === void 0 || agents === void 0 || !ready) return null;
1888
1914
  return createMessagingAi({
1889
1915
  transport,
1890
1916
  organizationId,
1891
- agents
1917
+ agents,
1918
+ ...maxTranscriptMessages !== void 0 ? { maxTranscriptMessages } : {}
1892
1919
  });
1893
- }, [transport, agents, ready, organizationId]);
1920
+ }, [transport, agents, ready, organizationId, maxTranscriptMessages]);
1894
1921
  const renderers = props.actionRenderers;
1895
1922
  const rendererMap = (0, import_react.useMemo)(() => {
1896
1923
  const map = /* @__PURE__ */ new Map();
@@ -2218,6 +2245,10 @@ function useMessagingAi(conversationId) {
2218
2245
  const host = useMessagingHost();
2219
2246
  const conversation = useConversation(conversationId);
2220
2247
  const [isRunning, setRunning] = (0, import_react3.useState)(false);
2248
+ const [partialText, setPartialText] = (0, import_react3.useState)("");
2249
+ const [activeCapability, setActiveCapability] = (0, import_react3.useState)(
2250
+ null
2251
+ );
2221
2252
  const [result, setResult] = (0, import_react3.useState)(null);
2222
2253
  const [error, setError] = (0, import_react3.useState)(null);
2223
2254
  const abortRef = (0, import_react3.useRef)(null);
@@ -2231,11 +2262,15 @@ function useMessagingAi(conversationId) {
2231
2262
  return {
2232
2263
  available: ai?.available() ?? [],
2233
2264
  isRunning,
2265
+ partialText,
2266
+ activeCapability,
2234
2267
  result,
2235
2268
  error,
2236
2269
  clear: () => {
2237
2270
  setResult(null);
2238
2271
  setError(null);
2272
+ setPartialText("");
2273
+ setActiveCapability(null);
2239
2274
  },
2240
2275
  run: (capability, args = {}) => {
2241
2276
  if (ai === null || conversationId === null) return;
@@ -2244,22 +2279,32 @@ function useMessagingAi(conversationId) {
2244
2279
  abortRef.current = controller;
2245
2280
  setRunning(true);
2246
2281
  setError(null);
2282
+ setResult(null);
2283
+ setPartialText("");
2284
+ setActiveCapability(capability);
2247
2285
  const base = {
2248
2286
  conversationId,
2249
2287
  messages: conversation.messages,
2250
2288
  participants: conversation.participants,
2251
- signal: controller.signal
2289
+ signal: controller.signal,
2290
+ // Live progress. Aborted runs stop painting immediately — a cancelled
2291
+ // answer must not keep growing on screen.
2292
+ onText: (text) => {
2293
+ if (!controller.signal.aborted) setPartialText(text);
2294
+ }
2252
2295
  };
2253
2296
  const call = () => {
2254
2297
  switch (capability) {
2255
- case "catchUp": {
2256
- const summary = conversation.summary;
2257
- const self = summary?.participants.find(
2258
- (participant) => participant.userId === host?.identity.userId
2259
- );
2260
- void self;
2261
- return ai.catchMeUp({ ...base, since: null });
2262
- }
2298
+ case "catchUp":
2299
+ return ai.catchMeUp({
2300
+ ...base,
2301
+ // "What did I miss" is about the UNREAD tail. Sending the whole
2302
+ // window made this capability a second Summarize.
2303
+ since: unreadCutoff(
2304
+ conversation.messages,
2305
+ conversation.summary?.unreadCount ?? 0
2306
+ )
2307
+ });
2263
2308
  case "summarize":
2264
2309
  return ai.summarize(base);
2265
2310
  case "actionItems":
@@ -2782,7 +2827,16 @@ function ConversationView(props) {
2782
2827
  },
2783
2828
  capability
2784
2829
  )) }) : null,
2785
- ai.isRunning ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "mx-msg__ai-output", "aria-busy": "true", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "mx-msg__skeleton", style: { display: "block", height: 11, width: "72%" } }) }) : 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: [
2830
+ 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: [
2831
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "mx-msg__ai-output-label", children: ai.activeCapability !== null ? `${AI_LABELS[ai.activeCapability]}\u2026` : "Working\u2026" }),
2832
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2833
+ "span",
2834
+ {
2835
+ className: "mx-msg__skeleton",
2836
+ style: { display: "block", height: 11, width: "72%" }
2837
+ }
2838
+ )
2839
+ ] }) }) : 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: [
2786
2840
  ai.result.text,
2787
2841
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2788
2842
  "button",