@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/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
- import { MatrxTransport } from '@ai-matrx/agents/matrx';
2
- import { RealtimeManager } from '@ai-matrx/realtime';
1
+ import { MatrxJsonObject, MatrxTransport } from '@ai-matrx/agents/matrx';
2
+ import { RealtimeClientLike, RealtimeManager } from '@ai-matrx/realtime';
3
3
 
4
4
  /**
5
5
  * THE MESSAGING DOMAIN TYPES.
@@ -309,21 +309,47 @@ declare function resolveActor(message: Message, sender: UserSummary | null): Act
309
309
  * every conversation it touches. Every call below sends `variables` and either
310
310
  * a short human-shaped `user_input` or none.
311
311
  *
312
- * 🚨 **AGENT DEFINITIONS LIVE IN THE DATABASE.** The package takes agent IDs as
313
- * injected identity. When an id is not configured the capability reports
314
- * `unavailable` WITH the remedy it never silently no-ops, and the UI hides
315
- * the action rather than offering a dead button (no dead ends).
312
+ * 🚨 **AGENT DEFINITIONS LIVE IN THE DATABASE.** The package takes an injected
313
+ * IDENTITY per capability the database agent id and the settings that came
314
+ * with it and nothing else. When an identity is not configured the capability
315
+ * reports `unavailable` WITH the remedy; it never silently no-ops, and the UI
316
+ * hides the action rather than offering a dead button (no dead ends).
317
+ *
318
+ * 🚨 **AN IDENTITY IS BOTH HALVES.** A host that resolves a capability's agent
319
+ * from its own switching layer resolves a PAIR: which agent, and the settings
320
+ * that layer decided for it. Taking only the id silently drops the settings
321
+ * half, so `configOverrides` rides with the id and is sent verbatim as the
322
+ * turn's `config_overrides`.
323
+ *
324
+ * 🚨 **A RUN THE USER WATCHES STREAMS.** Every call takes `onText` and reports
325
+ * the accumulated answer as it arrives. A skeleton that sits until a run
326
+ * finishes is a spinner standing in for an answer; the caller renders the
327
+ * partial text instead.
316
328
  */
317
329
 
318
330
  /**
319
- * Which platform agent backs each capability. Every field is a DATABASE row id
320
- * supplied by the host; an omitted one disables exactly that capability.
331
+ * WHO fulfils one capability, as the host resolved it. Both halves travel
332
+ * together: an id alone is half an answer whenever the host's switching layer
333
+ * also decided settings for the job.
334
+ */
335
+ interface MessagingAgentIdentity {
336
+ /** The DATABASE agent row id. Never a name, never a prompt. */
337
+ readonly agentId: string;
338
+ /**
339
+ * The settings half, in LLMParams shape, sent verbatim as the turn's
340
+ * `config_overrides`. `null`/omitted = the agent's own settings stand.
341
+ */
342
+ readonly configOverrides?: MatrxJsonObject | null | undefined;
343
+ }
344
+ /**
345
+ * Which platform agent backs each capability. Every field is an identity the
346
+ * host injects; an omitted one disables exactly that capability.
321
347
  */
322
348
  interface MessagingAgents {
323
- readonly catchUp?: string | undefined;
324
- readonly summarize?: string | undefined;
325
- readonly actionItems?: string | undefined;
326
- readonly draftReply?: string | undefined;
349
+ readonly catchUp?: MessagingAgentIdentity | undefined;
350
+ readonly summarize?: MessagingAgentIdentity | undefined;
351
+ readonly actionItems?: MessagingAgentIdentity | undefined;
352
+ readonly draftReply?: MessagingAgentIdentity | undefined;
327
353
  }
328
354
  interface MessagingAiOptions {
329
355
  transport: MatrxTransport;
@@ -341,38 +367,44 @@ interface AiResult {
341
367
  readonly text: string;
342
368
  readonly conversationId: string | null;
343
369
  }
370
+ /** What every capability takes: the conversation, and how to watch the run. */
371
+ interface AiCallArgs {
372
+ conversationId: ConversationId;
373
+ messages: readonly Message[];
374
+ participants: readonly UserSummary[];
375
+ signal?: AbortSignal | undefined;
376
+ /** The accumulated answer so far, after every streamed chunk. */
377
+ onText?: ((fullText: string) => void) | undefined;
378
+ }
344
379
  interface MessagingAi {
345
380
  /** Which capabilities this host actually configured. Drives what the UI offers. */
346
381
  available(): readonly AiCapability[];
347
382
  isAvailable(capability: AiCapability): boolean;
348
- catchMeUp(args: {
349
- conversationId: ConversationId;
350
- messages: readonly Message[];
351
- participants: readonly UserSummary[];
383
+ catchMeUp(args: AiCallArgs & {
384
+ /**
385
+ * Exclusive cutoff — only messages after it are sent. `null` means the
386
+ * caller genuinely has no read mark and the whole window applies.
387
+ */
352
388
  since: string | null;
353
- signal?: AbortSignal;
354
- }): Promise<AiResult>;
355
- summarize(args: {
356
- conversationId: ConversationId;
357
- messages: readonly Message[];
358
- participants: readonly UserSummary[];
359
- signal?: AbortSignal;
360
- }): Promise<AiResult>;
361
- extractActionItems(args: {
362
- conversationId: ConversationId;
363
- messages: readonly Message[];
364
- participants: readonly UserSummary[];
365
- signal?: AbortSignal;
366
389
  }): Promise<AiResult>;
367
- draftReply(args: {
368
- conversationId: ConversationId;
369
- messages: readonly Message[];
370
- participants: readonly UserSummary[];
390
+ summarize(args: AiCallArgs): Promise<AiResult>;
391
+ extractActionItems(args: AiCallArgs): Promise<AiResult>;
392
+ draftReply(args: AiCallArgs & {
371
393
  /** What the human asked for, if anything — this IS a human utterance. */
372
394
  instruction?: string | undefined;
373
- signal?: AbortSignal;
374
395
  }): Promise<AiResult>;
375
396
  }
397
+ /**
398
+ * The exclusive cutoff "what did I miss" means, derived from what the store
399
+ * already knows: the message just BEFORE the first unread one. No unread
400
+ * messages, or a loaded window that does not reach back that far, means there
401
+ * is no honest cutoff — the whole window applies, and the caller says so by
402
+ * passing `null` rather than guessing a timestamp.
403
+ *
404
+ * Framework-free on purpose: a Redux or React Native consumer must not have to
405
+ * re-derive what "unread" means.
406
+ */
407
+ declare function unreadCutoff(messages: readonly Message[], unreadCount: number): string | null;
376
408
  declare function createMessagingAi(options: MessagingAiOptions): MessagingAi;
377
409
 
378
410
  /**
@@ -609,6 +641,38 @@ interface SchemaLike {
609
641
  interface SupabaseLike extends SchemaLike {
610
642
  schema(name: string): SchemaLike;
611
643
  }
644
+ /**
645
+ * 🚨 THE PUBLIC PROP TYPE IS DELIBERATELY SHALLOW.
646
+ *
647
+ * `<MessagingProvider client={supabase}>` must accept the client the host
648
+ * ALREADY HAS — a `SupabaseClient<Database>` built from generated types. It did
649
+ * not: checking a fully-typed client against the rich `SupabaseLike` above
650
+ * makes tsc chase PostgREST's schema machinery until it bails with **TS2589,
651
+ * "Type instantiation is excessively deep"**, followed by a page of "not
652
+ * assignable". The first real consumer hit both on the one line the README
653
+ * tells every consumer to write, and the only escape was the cast this
654
+ * package's own header forbids.
655
+ *
656
+ * So the boundary is shallow — method names and arity, results as `unknown` —
657
+ * which a typed client satisfies without the compiler unrolling anything. The
658
+ * RICH shape (`SupabaseLike`) is still the internal contract, and the provider
659
+ * narrows to it ONCE, inside the package. That is the data 0.2.1 lesson applied
660
+ * to a client instead of a JSON column: type a public boundary over `unknown`,
661
+ * never over a strict recursive type the host's generator also produces.
662
+ *
663
+ * `supabase-shape.test.ts` proves BOTH legs at compile time: every call this
664
+ * package makes works on a real client, AND a fully-typed real client is
665
+ * assignable to this type.
666
+ */
667
+ interface MessagingSupabaseClient {
668
+ schema(name: string): unknown;
669
+ from(table: string): unknown;
670
+ rpc(fn: string, args?: Record<string, unknown>): unknown;
671
+ channel(topic: string, params?: unknown): unknown;
672
+ removeChannel(channel: never): unknown;
673
+ }
674
+ /** The internal client contract — what the repository and the manager see. */
675
+ type MessagingSupabaseInternal = SupabaseLike & RealtimeClientLike;
612
676
 
613
677
  /**
614
678
  * THE DATA CONTRACT — the ONE place a messaging table or RPC name exists.
@@ -677,7 +741,15 @@ declare const RPCS: {
677
741
  */
678
742
  type SessionResolver = () => Promise<unknown>;
679
743
  interface RepositoryOptions {
680
- client: SupabaseLike;
744
+ /**
745
+ * The host's Supabase client. Typed over the SHALLOW public shape for the
746
+ * same reason the provider's prop is (see `supabase-shape.ts`): a fully-typed
747
+ * `SupabaseClient<Database>` checked against the rich contract makes tsc bail
748
+ * with TS2589. A service module builds this repository directly — the
749
+ * framework-free path in the README — so it hits the boundary exactly like
750
+ * the provider does.
751
+ */
752
+ client: MessagingSupabaseClient;
681
753
  identity: MessagingIdentity;
682
754
  /** Called once before a retry when a read fails with a missing session. */
683
755
  resolveSession?: SessionResolver | undefined;
@@ -1037,4 +1109,4 @@ declare function summarizeText(content: string, maxLength?: number): string;
1037
1109
  /** Serialize picked references into a fence the platform's other readers accept. */
1038
1110
  declare function composeFence(references: readonly MatrxReference[]): string;
1039
1111
 
1040
- export { type ActionChoice, type ActionContext, type ActionHandler, type ActionOutcome, type ActionReceipt, type ActionRegistry, type ActorPresentation, type AiCapability, type AiResult, type Attachment, type ClientMessageId, type Conversation, type ConversationCursor, type ConversationId, type ConversationSummary, type ConversationThread, type ConversationType, type DeliveryState, type DraftMessage, type EngineDiagnostic, type JsonObject, type JsonValue, MESSAGING_EVENTS, MESSAGING_RPC_SCHEMA, MESSAGING_SCHEMA, type MatrxReference, type Message, type MessageAction, type MessageCursor, type MessageGroup, type MessageId, type MessageKind, type MessagingAgents, type MessagingAi, type MessagingAiOptions, type MessagingEngine, type MessagingEngineOptions, MessagingError, type MessagingErrorCode, type MessagingIdentity, type MessagingRepository, type MessagingSnapshot, type MessagingStore, type OrganizationId, type Outbox, type OutboxEntry, type OutboxOptions, type OutboxStorage, type Page, type Participant, type ParticipantRole, type PostgrestFilterLike, type PostgrestLikeResponse, type PostgrestTableLike, RPCS, type ReadCache, type ReadCacheOptions, type RepositoryOptions, type SchemaLike, type SessionResolver, type SupabaseLike, TABLES, type TextSegment, type UserId, type UserSummary, asClientMessageId, asConversationId, asMessageId, asOrganizationId, asUserId, composeFence, conversationTopic, createActionRegistry, createMemoryOutboxStorage, createMessagingAi, createMessagingEngine, createMessagingRepository, createMessagingStore, createOutbox, createReadCache, createWebOutboxStorage, extractReferences, formatConversationTime, formatDateSeparator, formatLastSeen, formatMessageTime, formatTypists, groupMessages, inboxTopic, invalidResponse, isSameDay, messagingClientId, normalizeMessagingError, optimisticMessage, participantNames, projectConversationSummary, projectMessage, projectMessageAction, projectParticipantRole, projectUserSummary, resolveActor, splitText, summarizeText };
1112
+ export { type ActionChoice, type ActionContext, type ActionHandler, type ActionOutcome, type ActionReceipt, type ActionRegistry, type ActorPresentation, type AiCallArgs, type AiCapability, type AiResult, type Attachment, type ClientMessageId, type Conversation, type ConversationCursor, type ConversationId, type ConversationSummary, type ConversationThread, type ConversationType, type DeliveryState, type DraftMessage, type EngineDiagnostic, type JsonObject, type JsonValue, MESSAGING_EVENTS, MESSAGING_RPC_SCHEMA, MESSAGING_SCHEMA, type MatrxReference, type Message, type MessageAction, type MessageCursor, type MessageGroup, type MessageId, type MessageKind, type MessagingAgentIdentity, type MessagingAgents, type MessagingAi, type MessagingAiOptions, type MessagingEngine, type MessagingEngineOptions, MessagingError, type MessagingErrorCode, type MessagingIdentity, type MessagingRepository, type MessagingSnapshot, type MessagingStore, type MessagingSupabaseClient, type MessagingSupabaseInternal, type OrganizationId, type Outbox, type OutboxEntry, type OutboxOptions, type OutboxStorage, type Page, type Participant, type ParticipantRole, type PostgrestFilterLike, type PostgrestLikeResponse, type PostgrestTableLike, RPCS, type ReadCache, type ReadCacheOptions, type RepositoryOptions, type SchemaLike, type SessionResolver, type SupabaseLike, TABLES, type TextSegment, type UserId, type UserSummary, asClientMessageId, asConversationId, asMessageId, asOrganizationId, asUserId, composeFence, conversationTopic, createActionRegistry, createMemoryOutboxStorage, createMessagingAi, createMessagingEngine, createMessagingRepository, createMessagingStore, createOutbox, createReadCache, createWebOutboxStorage, extractReferences, formatConversationTime, formatDateSeparator, formatLastSeen, formatMessageTime, formatTypists, groupMessages, inboxTopic, invalidResponse, isSameDay, messagingClientId, normalizeMessagingError, optimisticMessage, participantNames, projectConversationSummary, projectMessage, projectMessageAction, projectParticipantRole, projectUserSummary, resolveActor, splitText, summarizeText, unreadCutoff };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { MatrxTransport } from '@ai-matrx/agents/matrx';
2
- import { RealtimeManager } from '@ai-matrx/realtime';
1
+ import { MatrxJsonObject, MatrxTransport } from '@ai-matrx/agents/matrx';
2
+ import { RealtimeClientLike, RealtimeManager } from '@ai-matrx/realtime';
3
3
 
4
4
  /**
5
5
  * THE MESSAGING DOMAIN TYPES.
@@ -309,21 +309,47 @@ declare function resolveActor(message: Message, sender: UserSummary | null): Act
309
309
  * every conversation it touches. Every call below sends `variables` and either
310
310
  * a short human-shaped `user_input` or none.
311
311
  *
312
- * 🚨 **AGENT DEFINITIONS LIVE IN THE DATABASE.** The package takes agent IDs as
313
- * injected identity. When an id is not configured the capability reports
314
- * `unavailable` WITH the remedy it never silently no-ops, and the UI hides
315
- * the action rather than offering a dead button (no dead ends).
312
+ * 🚨 **AGENT DEFINITIONS LIVE IN THE DATABASE.** The package takes an injected
313
+ * IDENTITY per capability the database agent id and the settings that came
314
+ * with it and nothing else. When an identity is not configured the capability
315
+ * reports `unavailable` WITH the remedy; it never silently no-ops, and the UI
316
+ * hides the action rather than offering a dead button (no dead ends).
317
+ *
318
+ * 🚨 **AN IDENTITY IS BOTH HALVES.** A host that resolves a capability's agent
319
+ * from its own switching layer resolves a PAIR: which agent, and the settings
320
+ * that layer decided for it. Taking only the id silently drops the settings
321
+ * half, so `configOverrides` rides with the id and is sent verbatim as the
322
+ * turn's `config_overrides`.
323
+ *
324
+ * 🚨 **A RUN THE USER WATCHES STREAMS.** Every call takes `onText` and reports
325
+ * the accumulated answer as it arrives. A skeleton that sits until a run
326
+ * finishes is a spinner standing in for an answer; the caller renders the
327
+ * partial text instead.
316
328
  */
317
329
 
318
330
  /**
319
- * Which platform agent backs each capability. Every field is a DATABASE row id
320
- * supplied by the host; an omitted one disables exactly that capability.
331
+ * WHO fulfils one capability, as the host resolved it. Both halves travel
332
+ * together: an id alone is half an answer whenever the host's switching layer
333
+ * also decided settings for the job.
334
+ */
335
+ interface MessagingAgentIdentity {
336
+ /** The DATABASE agent row id. Never a name, never a prompt. */
337
+ readonly agentId: string;
338
+ /**
339
+ * The settings half, in LLMParams shape, sent verbatim as the turn's
340
+ * `config_overrides`. `null`/omitted = the agent's own settings stand.
341
+ */
342
+ readonly configOverrides?: MatrxJsonObject | null | undefined;
343
+ }
344
+ /**
345
+ * Which platform agent backs each capability. Every field is an identity the
346
+ * host injects; an omitted one disables exactly that capability.
321
347
  */
322
348
  interface MessagingAgents {
323
- readonly catchUp?: string | undefined;
324
- readonly summarize?: string | undefined;
325
- readonly actionItems?: string | undefined;
326
- readonly draftReply?: string | undefined;
349
+ readonly catchUp?: MessagingAgentIdentity | undefined;
350
+ readonly summarize?: MessagingAgentIdentity | undefined;
351
+ readonly actionItems?: MessagingAgentIdentity | undefined;
352
+ readonly draftReply?: MessagingAgentIdentity | undefined;
327
353
  }
328
354
  interface MessagingAiOptions {
329
355
  transport: MatrxTransport;
@@ -341,38 +367,44 @@ interface AiResult {
341
367
  readonly text: string;
342
368
  readonly conversationId: string | null;
343
369
  }
370
+ /** What every capability takes: the conversation, and how to watch the run. */
371
+ interface AiCallArgs {
372
+ conversationId: ConversationId;
373
+ messages: readonly Message[];
374
+ participants: readonly UserSummary[];
375
+ signal?: AbortSignal | undefined;
376
+ /** The accumulated answer so far, after every streamed chunk. */
377
+ onText?: ((fullText: string) => void) | undefined;
378
+ }
344
379
  interface MessagingAi {
345
380
  /** Which capabilities this host actually configured. Drives what the UI offers. */
346
381
  available(): readonly AiCapability[];
347
382
  isAvailable(capability: AiCapability): boolean;
348
- catchMeUp(args: {
349
- conversationId: ConversationId;
350
- messages: readonly Message[];
351
- participants: readonly UserSummary[];
383
+ catchMeUp(args: AiCallArgs & {
384
+ /**
385
+ * Exclusive cutoff — only messages after it are sent. `null` means the
386
+ * caller genuinely has no read mark and the whole window applies.
387
+ */
352
388
  since: string | null;
353
- signal?: AbortSignal;
354
- }): Promise<AiResult>;
355
- summarize(args: {
356
- conversationId: ConversationId;
357
- messages: readonly Message[];
358
- participants: readonly UserSummary[];
359
- signal?: AbortSignal;
360
- }): Promise<AiResult>;
361
- extractActionItems(args: {
362
- conversationId: ConversationId;
363
- messages: readonly Message[];
364
- participants: readonly UserSummary[];
365
- signal?: AbortSignal;
366
389
  }): Promise<AiResult>;
367
- draftReply(args: {
368
- conversationId: ConversationId;
369
- messages: readonly Message[];
370
- participants: readonly UserSummary[];
390
+ summarize(args: AiCallArgs): Promise<AiResult>;
391
+ extractActionItems(args: AiCallArgs): Promise<AiResult>;
392
+ draftReply(args: AiCallArgs & {
371
393
  /** What the human asked for, if anything — this IS a human utterance. */
372
394
  instruction?: string | undefined;
373
- signal?: AbortSignal;
374
395
  }): Promise<AiResult>;
375
396
  }
397
+ /**
398
+ * The exclusive cutoff "what did I miss" means, derived from what the store
399
+ * already knows: the message just BEFORE the first unread one. No unread
400
+ * messages, or a loaded window that does not reach back that far, means there
401
+ * is no honest cutoff — the whole window applies, and the caller says so by
402
+ * passing `null` rather than guessing a timestamp.
403
+ *
404
+ * Framework-free on purpose: a Redux or React Native consumer must not have to
405
+ * re-derive what "unread" means.
406
+ */
407
+ declare function unreadCutoff(messages: readonly Message[], unreadCount: number): string | null;
376
408
  declare function createMessagingAi(options: MessagingAiOptions): MessagingAi;
377
409
 
378
410
  /**
@@ -609,6 +641,38 @@ interface SchemaLike {
609
641
  interface SupabaseLike extends SchemaLike {
610
642
  schema(name: string): SchemaLike;
611
643
  }
644
+ /**
645
+ * 🚨 THE PUBLIC PROP TYPE IS DELIBERATELY SHALLOW.
646
+ *
647
+ * `<MessagingProvider client={supabase}>` must accept the client the host
648
+ * ALREADY HAS — a `SupabaseClient<Database>` built from generated types. It did
649
+ * not: checking a fully-typed client against the rich `SupabaseLike` above
650
+ * makes tsc chase PostgREST's schema machinery until it bails with **TS2589,
651
+ * "Type instantiation is excessively deep"**, followed by a page of "not
652
+ * assignable". The first real consumer hit both on the one line the README
653
+ * tells every consumer to write, and the only escape was the cast this
654
+ * package's own header forbids.
655
+ *
656
+ * So the boundary is shallow — method names and arity, results as `unknown` —
657
+ * which a typed client satisfies without the compiler unrolling anything. The
658
+ * RICH shape (`SupabaseLike`) is still the internal contract, and the provider
659
+ * narrows to it ONCE, inside the package. That is the data 0.2.1 lesson applied
660
+ * to a client instead of a JSON column: type a public boundary over `unknown`,
661
+ * never over a strict recursive type the host's generator also produces.
662
+ *
663
+ * `supabase-shape.test.ts` proves BOTH legs at compile time: every call this
664
+ * package makes works on a real client, AND a fully-typed real client is
665
+ * assignable to this type.
666
+ */
667
+ interface MessagingSupabaseClient {
668
+ schema(name: string): unknown;
669
+ from(table: string): unknown;
670
+ rpc(fn: string, args?: Record<string, unknown>): unknown;
671
+ channel(topic: string, params?: unknown): unknown;
672
+ removeChannel(channel: never): unknown;
673
+ }
674
+ /** The internal client contract — what the repository and the manager see. */
675
+ type MessagingSupabaseInternal = SupabaseLike & RealtimeClientLike;
612
676
 
613
677
  /**
614
678
  * THE DATA CONTRACT — the ONE place a messaging table or RPC name exists.
@@ -677,7 +741,15 @@ declare const RPCS: {
677
741
  */
678
742
  type SessionResolver = () => Promise<unknown>;
679
743
  interface RepositoryOptions {
680
- client: SupabaseLike;
744
+ /**
745
+ * The host's Supabase client. Typed over the SHALLOW public shape for the
746
+ * same reason the provider's prop is (see `supabase-shape.ts`): a fully-typed
747
+ * `SupabaseClient<Database>` checked against the rich contract makes tsc bail
748
+ * with TS2589. A service module builds this repository directly — the
749
+ * framework-free path in the README — so it hits the boundary exactly like
750
+ * the provider does.
751
+ */
752
+ client: MessagingSupabaseClient;
681
753
  identity: MessagingIdentity;
682
754
  /** Called once before a retry when a read fails with a missing session. */
683
755
  resolveSession?: SessionResolver | undefined;
@@ -1037,4 +1109,4 @@ declare function summarizeText(content: string, maxLength?: number): string;
1037
1109
  /** Serialize picked references into a fence the platform's other readers accept. */
1038
1110
  declare function composeFence(references: readonly MatrxReference[]): string;
1039
1111
 
1040
- export { type ActionChoice, type ActionContext, type ActionHandler, type ActionOutcome, type ActionReceipt, type ActionRegistry, type ActorPresentation, type AiCapability, type AiResult, type Attachment, type ClientMessageId, type Conversation, type ConversationCursor, type ConversationId, type ConversationSummary, type ConversationThread, type ConversationType, type DeliveryState, type DraftMessage, type EngineDiagnostic, type JsonObject, type JsonValue, MESSAGING_EVENTS, MESSAGING_RPC_SCHEMA, MESSAGING_SCHEMA, type MatrxReference, type Message, type MessageAction, type MessageCursor, type MessageGroup, type MessageId, type MessageKind, type MessagingAgents, type MessagingAi, type MessagingAiOptions, type MessagingEngine, type MessagingEngineOptions, MessagingError, type MessagingErrorCode, type MessagingIdentity, type MessagingRepository, type MessagingSnapshot, type MessagingStore, type OrganizationId, type Outbox, type OutboxEntry, type OutboxOptions, type OutboxStorage, type Page, type Participant, type ParticipantRole, type PostgrestFilterLike, type PostgrestLikeResponse, type PostgrestTableLike, RPCS, type ReadCache, type ReadCacheOptions, type RepositoryOptions, type SchemaLike, type SessionResolver, type SupabaseLike, TABLES, type TextSegment, type UserId, type UserSummary, asClientMessageId, asConversationId, asMessageId, asOrganizationId, asUserId, composeFence, conversationTopic, createActionRegistry, createMemoryOutboxStorage, createMessagingAi, createMessagingEngine, createMessagingRepository, createMessagingStore, createOutbox, createReadCache, createWebOutboxStorage, extractReferences, formatConversationTime, formatDateSeparator, formatLastSeen, formatMessageTime, formatTypists, groupMessages, inboxTopic, invalidResponse, isSameDay, messagingClientId, normalizeMessagingError, optimisticMessage, participantNames, projectConversationSummary, projectMessage, projectMessageAction, projectParticipantRole, projectUserSummary, resolveActor, splitText, summarizeText };
1112
+ export { type ActionChoice, type ActionContext, type ActionHandler, type ActionOutcome, type ActionReceipt, type ActionRegistry, type ActorPresentation, type AiCallArgs, type AiCapability, type AiResult, type Attachment, type ClientMessageId, type Conversation, type ConversationCursor, type ConversationId, type ConversationSummary, type ConversationThread, type ConversationType, type DeliveryState, type DraftMessage, type EngineDiagnostic, type JsonObject, type JsonValue, MESSAGING_EVENTS, MESSAGING_RPC_SCHEMA, MESSAGING_SCHEMA, type MatrxReference, type Message, type MessageAction, type MessageCursor, type MessageGroup, type MessageId, type MessageKind, type MessagingAgentIdentity, type MessagingAgents, type MessagingAi, type MessagingAiOptions, type MessagingEngine, type MessagingEngineOptions, MessagingError, type MessagingErrorCode, type MessagingIdentity, type MessagingRepository, type MessagingSnapshot, type MessagingStore, type MessagingSupabaseClient, type MessagingSupabaseInternal, type OrganizationId, type Outbox, type OutboxEntry, type OutboxOptions, type OutboxStorage, type Page, type Participant, type ParticipantRole, type PostgrestFilterLike, type PostgrestLikeResponse, type PostgrestTableLike, RPCS, type ReadCache, type ReadCacheOptions, type RepositoryOptions, type SchemaLike, type SessionResolver, type SupabaseLike, TABLES, type TextSegment, type UserId, type UserSummary, asClientMessageId, asConversationId, asMessageId, asOrganizationId, asUserId, composeFence, conversationTopic, createActionRegistry, createMemoryOutboxStorage, createMessagingAi, createMessagingEngine, createMessagingRepository, createMessagingStore, createOutbox, createReadCache, createWebOutboxStorage, extractReferences, formatConversationTime, formatDateSeparator, formatLastSeen, formatMessageTime, formatTypists, groupMessages, inboxTopic, invalidResponse, isSameDay, messagingClientId, normalizeMessagingError, optimisticMessage, participantNames, projectConversationSummary, projectMessage, projectMessageAction, projectParticipantRole, projectUserSummary, resolveActor, splitText, summarizeText, unreadCutoff };
package/dist/index.js CHANGED
@@ -291,6 +291,12 @@ ${JSON.stringify(references, null, 2)}
291
291
  }
292
292
 
293
293
  // src/core/ai.ts
294
+ function unreadCutoff(messages, unreadCount) {
295
+ if (unreadCount <= 0) return null;
296
+ const index = messages.length - unreadCount - 1;
297
+ if (index < 0) return null;
298
+ return messages[index]?.createdAt ?? null;
299
+ }
294
300
  function nameOf(participants, senderId) {
295
301
  return participants.find((p) => p.userId === senderId)?.displayName ?? senderId;
296
302
  }
@@ -311,22 +317,23 @@ function buildTranscript(messages, participants, limit, since) {
311
317
  }
312
318
  function createMessagingAi(options) {
313
319
  const limit = options.maxTranscriptMessages ?? 200;
314
- function agentFor(capability) {
315
- const agentId = options.agents[capability];
316
- if (typeof agentId !== "string" || agentId.length === 0) {
320
+ function identityFor(capability) {
321
+ const identity = options.agents[capability];
322
+ if (identity === void 0 || identity.agentId.length === 0) {
317
323
  throw new MessagingError(
318
324
  "misconfigured",
319
325
  `Messaging AI capability "${capability}" has no agent configured`,
320
- `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.`
326
+ `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.`
321
327
  );
322
328
  }
323
- return agentId;
329
+ return identity;
324
330
  }
325
- async function run(capability, variables, userInput, signal) {
326
- const agentId = agentFor(capability);
331
+ async function run(capability, variables, userInput, signal, onText) {
332
+ const identity = identityFor(capability);
333
+ const overrides = identity.configOverrides;
327
334
  const completed = await runAgentToCompletion(
328
335
  options.transport,
329
- agentId,
336
+ identity.agentId,
330
337
  {
331
338
  ...newEphemeralConversationStart(),
332
339
  organization_id: options.organizationId,
@@ -335,9 +342,15 @@ function createMessagingAi(options) {
335
342
  initiation: "user",
336
343
  // THE USER-INPUT LAW: structured content is NEVER here.
337
344
  ...userInput !== null ? { user_input: userInput } : {},
345
+ // The settings half of the injected identity, verbatim. An absent one
346
+ // is OMITTED rather than sent as null: the agent's own settings stand.
347
+ ...overrides != null ? { config_overrides: overrides } : {},
338
348
  variables
339
349
  },
340
- signal !== void 0 ? { signal } : {}
350
+ {
351
+ ...signal !== void 0 ? { signal } : {},
352
+ ...onText !== void 0 ? { onChunk: onText } : {}
353
+ }
341
354
  );
342
355
  return {
343
356
  capability,
@@ -370,27 +383,26 @@ function createMessagingAi(options) {
370
383
  ...args.since != null ? { unread_since: args.since } : {}
371
384
  };
372
385
  }
386
+ function configured(capability) {
387
+ const identity = options.agents[capability];
388
+ return identity !== void 0 && identity.agentId.length > 0;
389
+ }
373
390
  return {
374
391
  available: () => ["catchUp", "summarize", "actionItems", "draftReply"].filter(
375
- (capability) => {
376
- const id = options.agents[capability];
377
- return typeof id === "string" && id.length > 0;
378
- }
392
+ configured
379
393
  ),
380
- isAvailable(capability) {
381
- const id = options.agents[capability];
382
- return typeof id === "string" && id.length > 0;
383
- },
384
- catchMeUp: (args) => run("catchUp", variablesFor(args), null, args.signal),
385
- summarize: (args) => run("summarize", variablesFor(args), null, args.signal),
386
- extractActionItems: (args) => run("actionItems", variablesFor(args), null, args.signal),
394
+ isAvailable: configured,
395
+ catchMeUp: (args) => run("catchUp", variablesFor(args), null, args.signal, args.onText),
396
+ summarize: (args) => run("summarize", variablesFor(args), null, args.signal, args.onText),
397
+ extractActionItems: (args) => run("actionItems", variablesFor(args), null, args.signal, args.onText),
387
398
  draftReply: (args) => run(
388
399
  "draftReply",
389
400
  variablesFor(args),
390
401
  // The ONE genuine human utterance in this module: what the user asked
391
402
  // the drafter for. Everything else rode `variables`.
392
403
  args.instruction !== void 0 && args.instruction.trim().length > 0 ? args.instruction.trim() : null,
393
- args.signal
404
+ args.signal,
405
+ args.onText
394
406
  )
395
407
  };
396
408
  }
@@ -1507,12 +1519,13 @@ function requireOrg(organizationId, operation) {
1507
1519
  return organizationId;
1508
1520
  }
1509
1521
  function createMessagingRepository(options) {
1510
- const { client, identity } = options;
1522
+ const { identity } = options;
1511
1523
  const org = requireOrg(identity.organizationId, "createMessagingRepository");
1512
1524
  const userCache = createReadCache({
1513
1525
  ttlMs: options.userTtlMs ?? 5 * 6e4,
1514
1526
  ...options.now !== void 0 ? { now: options.now } : {}
1515
1527
  });
1528
+ const client = options.client;
1516
1529
  const db = () => client.schema(MESSAGING_SCHEMA);
1517
1530
  const rpcDb = () => client.schema(MESSAGING_RPC_SCHEMA);
1518
1531
  async function withSessionRetry(operation, run) {
@@ -1870,6 +1883,7 @@ export {
1870
1883
  projectUserSummary,
1871
1884
  resolveActor,
1872
1885
  splitText,
1873
- summarizeText
1886
+ summarizeText,
1887
+ unreadCutoff
1874
1888
  };
1875
1889
  //# sourceMappingURL=index.js.map