@proteos/sdk 0.27.0 → 0.29.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.
@@ -74,12 +74,15 @@ export interface UserRef {
74
74
 
75
75
  /**
76
76
  * Inline snapshot of a person on a message/reaction — the integration-side
77
- * identity plus, when resolved via email match, the platform user.
77
+ * identity plus, when resolved, the platform user and the contact id.
78
+ * (Renamed from ParticipantRef in the contacts model; wire keys unchanged.)
78
79
  */
79
- export interface ParticipantRef {
80
+ export interface ContactRef {
80
81
  external_id?: string
81
82
  name: string
82
83
  email?: string
84
+ /** The resolved person; absent on pre-contacts snapshots and wire fallbacks. */
85
+ contact_id?: string
83
86
  /** Resolved platform identity (email match); absent when unresolved. */
84
87
  platform_user?: UserRef
85
88
  }
@@ -90,7 +93,7 @@ export interface ParticipantRef {
90
93
  * renders "who it's with" as the non-self subset (1 → single avatar, ≥2 →
91
94
  * stacked).
92
95
  */
93
- export interface ConversationParticipant extends ParticipantRef {
96
+ export interface ConversationParticipant extends ContactRef {
94
97
  is_self: boolean
95
98
  }
96
99
 
@@ -101,7 +104,7 @@ export type RecipientRole = 'to' | 'cc' | 'bcc'
101
104
  * One addressee of a message with the role it was addressed under. Bcc is only
102
105
  * present on messages the platform itself sent (inbound bcc is SMTP-stripped).
103
106
  */
104
- export interface MessageRecipient extends ParticipantRef {
107
+ export interface MessageRecipient extends ContactRef {
105
108
  role: RecipientRole
106
109
  }
107
110
 
@@ -164,7 +167,7 @@ export interface Connection {
164
167
  scope: ConnectionScope
165
168
  /** Set only when scope=user (the connecting user); absent for org-wide. */
166
169
  owner?: UserRef
167
- external_workspace_id: string
170
+ external_account_id: string
168
171
  credentials: ConnectionCredentials
169
172
  settings: Record<string, unknown>
170
173
  status: ConnectionStatus
@@ -232,7 +235,7 @@ export interface Conversation {
232
235
  latest_message?: MessagePreview
233
236
  messages_total?: number
234
237
  /** Up to 3 most recent distinct senders excluding the root sender. */
235
- last_repliers?: ParticipantRef[]
238
+ last_repliers?: ContactRef[]
236
239
  /** Requesting user's read marker; absent = never opened. */
237
240
  last_read_at?: string
238
241
  /**
@@ -252,7 +255,7 @@ export interface Conversation {
252
255
  * ~500 chars of text, direction/status for outbound identity.
253
256
  */
254
257
  export interface MessagePreview {
255
- sender: ParticipantRef
258
+ sender: ContactRef
256
259
  text: string
257
260
  direction: MessageDirection
258
261
  status: MessageStatus
@@ -290,7 +293,7 @@ export interface Attachment {
290
293
  export interface Reaction {
291
294
  emoji: string
292
295
  count: number
293
- participants: ParticipantRef[]
296
+ participants: ContactRef[]
294
297
  }
295
298
 
296
299
  export interface Message {
@@ -301,7 +304,7 @@ export interface Message {
301
304
  channel: Channel
302
305
  direction: MessageDirection
303
306
  external_message_id: string
304
- sender: ParticipantRef
307
+ sender: ContactRef
305
308
  /** Who the message was addressed to (To/Cc/Bcc), each tagged with its role. */
306
309
  recipients?: MessageRecipient[]
307
310
  content: ContentBlock[]
@@ -533,7 +536,8 @@ export interface ListConversationsQuery extends PaginationQuery {
533
536
  */
534
537
  parent_conversation_id?: string
535
538
  /** Threads whose roster contains this external party — the person stream. */
536
- participant_external_id?: string
539
+ /** Contact stream: threads whose roster contains the resolved person. */
540
+ contact_id?: string
537
541
  /** Opt into the root/latest message projection on each row. */
538
542
  include?: 'messages_summary'
539
543
  }
@@ -562,41 +566,261 @@ export interface ListAgentListenersQuery extends PaginationQuery {
562
566
  is_enabled?: boolean
563
567
  }
564
568
 
565
- /** Discriminates a send target: a person or a venue. */
566
- export type RecipientKind = 'participant' | 'room'
567
- export type ParticipantSource = 'sync' | 'ingest'
569
+ /**
570
+ * Conversation filters: ingest-time rules that drop matching inbound messages
571
+ * BEFORE persistence (no message, no contact — only a content-free audit
572
+ * event). Scope-first evaluation: connection-scoped rules are final when one
573
+ * matches; global rules apply otherwise. Specificity within a scope:
574
+ * address > domain > role_based > automated > internal_conversations > all,
575
+ * allow beats block within a class.
576
+ */
577
+ export type ConversationFilterType =
578
+ | 'address'
579
+ | 'domain'
580
+ | 'role_based'
581
+ | 'automated'
582
+ | 'internal_conversations'
583
+ | 'all'
584
+ export type ConversationFilterAction = 'block' | 'allow'
585
+ /** Which side of the message address/domain rules test (default: sender). */
586
+ export type FilterMatchOn = 'sender' | 'any_participant'
587
+ /** Deterministic machine-mail header signals the automated filter matches. */
588
+ export type AutomatedSignal =
589
+ | 'auto_submitted'
590
+ | 'bulk'
591
+ | 'mailing_list'
592
+ | 'bounce'
593
+ | 'auto_response_suppress'
594
+
595
+ /** Exact canonical address (lowercased email / E.164 phone / provider id), any channel kind. */
596
+ export interface AddressFilterConfig {
597
+ kind: ContactAddressKind
598
+ value: string
599
+ match_on?: FilterMatchOn
600
+ }
601
+ /** Equals on the parsed email domain (lowercase, no leading '@'). */
602
+ export interface DomainFilterConfig {
603
+ domain: string
604
+ match_on?: FilterMatchOn
605
+ }
606
+ /** Sender local-part is a role mailbox; empty prefixes = the built-in set. Email-only. */
607
+ export interface RoleBasedFilterConfig {
608
+ prefixes?: string[]
609
+ }
610
+ /** Header-signal machine-mail detection; empty signals = all. Email-only. */
611
+ export interface AutomatedFilterConfig {
612
+ signals?: AutomatedSignal[]
613
+ }
614
+ /** Drops only when sender AND every recipient are on these domains. Always block. */
615
+ export interface InternalConversationsFilterConfig {
616
+ domains: string[]
617
+ }
618
+ /** Matches every message (empty config) — the scope-control primitive. */
619
+ export type AllFilterConfig = Record<string, never>
620
+
621
+ export type ConversationFilterConfig =
622
+ | AddressFilterConfig
623
+ | DomainFilterConfig
624
+ | RoleBasedFilterConfig
625
+ | AutomatedFilterConfig
626
+ | InternalConversationsFilterConfig
627
+ | AllFilterConfig
628
+
629
+ export interface ConversationFilter {
630
+ id: string
631
+ org_id: string
632
+ /** Empty = global (org-wide); set = scoped to that connection. */
633
+ connection_id: string
634
+ filter_type: ConversationFilterType
635
+ action: ConversationFilterAction
636
+ filter_config?: ConversationFilterConfig
637
+ is_enabled: boolean
638
+ /** Read-only rollup: messages this rule dropped in the last 30 days. */
639
+ recent_event_count?: number
640
+ created_at: string
641
+ created_by: UserRef
642
+ updated_at: string
643
+ updated_by: UserRef
644
+ }
645
+
646
+ /** One drop-audit entry (content-free: routing/identity facts only). */
647
+ export interface ConversationFilterEvent {
648
+ id: string
649
+ org_id: string
650
+ connection_id: string
651
+ conversation_filter_id: string
652
+ filter_type: ConversationFilterType
653
+ reason: string
654
+ sender_kind?: ContactAddressKind
655
+ sender_value?: string
656
+ external_message_id?: string
657
+ occurred_at: string
658
+ }
659
+
660
+ export interface CreateConversationFilterRequest {
661
+ connection_id?: string
662
+ filter_type: ConversationFilterType
663
+ action: ConversationFilterAction
664
+ filter_config?: Record<string, unknown>
665
+ is_enabled?: boolean
666
+ /** Opts past the own-domain guard (self_domain_block). */
667
+ is_self_domain_acknowledged?: boolean
668
+ }
669
+
670
+ export interface UpdateConversationFilterRequest {
671
+ /** filter_type and filter_config must be sent together. */
672
+ filter_type?: ConversationFilterType
673
+ action?: ConversationFilterAction
674
+ filter_config?: Record<string, unknown>
675
+ is_enabled?: boolean
676
+ is_self_domain_acknowledged?: boolean
677
+ }
678
+
679
+ export interface ListConversationFiltersQuery extends PaginationQuery {
680
+ /** Omit = all; '' = global-only; an id = that connection's rules. */
681
+ connection_id?: string
682
+ filter_type?: ConversationFilterType
683
+ action?: ConversationFilterAction
684
+ is_enabled?: boolean
685
+ }
686
+
687
+ export type ListConversationFilterEventsQuery = PaginationQuery
688
+
689
+ /**
690
+ * Discriminates a send target: a person's contact address or a venue. The
691
+ * server also accepts the legacy 'participant' value as an alias for
692
+ * 'contact-address'.
693
+ */
694
+ export type RecipientKind = 'contact-address' | 'room'
695
+ export type ContactAddressKind =
696
+ | 'email'
697
+ | 'phone'
698
+ | 'slack'
699
+ | 'linkedin'
700
+ | 'telegram'
701
+ | 'instagram'
702
+ | 'messenger'
703
+ | 'x'
704
+ | 'whatsapp'
705
+ export type ContactAddressSource = 'sync' | 'ingest' | 'manual' | 'merge'
706
+ export type ContactSource = 'sync' | 'ingest' | 'manual' | 'merge'
707
+ export type ContactStatus = 'active' | 'merged' | 'archived' | 'erased'
708
+ export type ConsentStatus = 'unknown' | 'opted_in' | 'opted_out'
709
+ export type PermissionEventType = 'opt_in' | 'opt_out' | 'block' | 'unblock'
710
+ export type PermissionEventSource = 'manual' | 'import' | 'link_click' | 'reply' | 'api' | 'system'
711
+ export type MergeProposalStatus = 'proposed' | 'approved' | 'rejected' | 'superseded'
712
+ export type ErasureRequestStatus = 'requested' | 'completed'
568
713
  export type RoomSource = 'sync' | 'ingest'
569
714
 
570
715
  /**
571
716
  * Send-time addressing VO: who/where an originated message goes. Points at a
572
- * Participant or a Room by kind + external id (never a stored entity itself).
717
+ * ContactAddress or a Room by kind + external id (never a stored entity itself).
573
718
  */
574
719
  export interface SendRecipient {
575
720
  kind: RecipientKind
576
- /** The connector-side id: a room's (Slack channel id) or a participant's (Slack user id, email address). */
721
+ /** The connector-side id: a room's (Slack channel id) or a contact address's wire identifier (Slack user id, email address). */
577
722
  external_id: string
578
723
  }
579
724
 
580
- /** A PERSON known on a connection (directory row; resolvable to a platform user). */
581
- export interface Participant {
725
+ /** The actual PERSON an org communicates with org-scoped, channel-agnostic. */
726
+ export interface Contact {
582
727
  id: string
583
728
  org_id: string
584
- connection_id: string
585
- /** Slack user id / email address — the id to DM or email. */
586
- external_id: string
587
729
  name: string
588
- email?: string
589
- /** Resolved platform identity (email match); absent when unresolved. */
730
+ status: ContactStatus
731
+ /** Person-level suppression only block/unblock events move it. */
732
+ is_blocked: boolean
733
+ has_legal_hold: boolean
734
+ consent_status: ConsentStatus
735
+ consent_updated_at?: string
736
+ /** Resolved platform identity when the person is a colleague. */
590
737
  platform_user?: UserRef
738
+ /** Merge tombstone redirect (set when status is 'merged'). */
739
+ merged_into_contact_id?: string
740
+ source: ContactSource
741
+ has_manual_edits: boolean
742
+ /** The contact's reachable endpoints, embedded on reads. */
743
+ addresses?: ContactAddress[]
744
+ created_at: string
745
+ created_by: UserRef
746
+ updated_at: string
747
+ updated_by: UserRef
748
+ }
749
+
750
+ /** ONE canonical digital endpoint to reach a contact — the dedup backbone. */
751
+ export interface ContactAddress {
752
+ id: string
753
+ org_id: string
754
+ contact_id: string
755
+ kind: ContactAddressKind
756
+ /** Provider-tenant qualifier (Slack workspace id); empty for global kinds. */
757
+ scope?: string
758
+ /** Canonical scalar (lowercased email, E.164 phone) — the lookup key. */
759
+ value: string
760
+ /** The identifier as observed on the wire (WhatsApp JID, original casing). */
761
+ raw_value?: string
762
+ /** Per-channel display name. */
763
+ name: string
764
+ consent_status: ConsentStatus
765
+ consent_updated_at?: string
766
+ is_blocked: boolean
767
+ source: ContactAddressSource
591
768
  metadata: Record<string, unknown>
592
- source: ParticipantSource
593
- last_seen_at: string
594
769
  created_at: string
595
770
  created_by: UserRef
596
771
  updated_at: string
597
772
  updated_by: UserRef
598
773
  }
599
774
 
775
+ /** One entry in the append-only permission ledger (consent + suppression). */
776
+ export interface ContactPermissionEvent {
777
+ id: string
778
+ org_id: string
779
+ contact_id: string
780
+ /** Scopes the event to one address; absent/empty = contact-level. */
781
+ contact_address_id?: string
782
+ event: PermissionEventType
783
+ basis?: string
784
+ source: PermissionEventSource
785
+ occurred_at: string
786
+ note?: string
787
+ evidence?: Record<string, unknown>
788
+ created_at: string
789
+ created_by: UserRef
790
+ }
791
+
792
+ /** One candidate duplicate pair awaiting review. */
793
+ export interface ContactMergeProposal {
794
+ id: string
795
+ org_id: string
796
+ contact_a_id: string
797
+ contact_b_id: string
798
+ suggested_winner_id?: string
799
+ confidence?: number
800
+ evidence?: Record<string, unknown>
801
+ verdict?: Record<string, unknown>
802
+ status: MergeProposalStatus
803
+ resolved_by?: UserRef
804
+ resolved_at?: string
805
+ created_at: string
806
+ created_by: UserRef
807
+ updated_at: string
808
+ updated_by: UserRef
809
+ }
810
+
811
+ /** A GDPR Art. 17 erasure request (requested → completed). */
812
+ export interface ContactErasureRequest {
813
+ id: string
814
+ org_id: string
815
+ contact_id: string
816
+ identifier_hashes: Array<{ kind: ContactAddressKind; hmac: string }>
817
+ status: ErasureRequestStatus
818
+ completed_at?: string
819
+ stats?: Record<string, unknown>
820
+ created_at: string
821
+ created_by: UserRef
822
+ }
823
+
600
824
  /** An addressable VENUE on a connection (Slack channel; later meeting rooms). */
601
825
  export interface Room {
602
826
  id: string
@@ -615,9 +839,53 @@ export interface Room {
615
839
  updated_by: UserRef
616
840
  }
617
841
 
618
- export interface ListParticipantsQuery extends PaginationQuery {
619
- /** Free-text needle matched against display name + external id. */
842
+ export interface ListContactAddressesQuery extends PaginationQuery {
843
+ /** Free-text needle matched against display name + canonical value. */
844
+ q?: string
845
+ kinds?: ContactAddressKind[]
846
+ scope?: string
847
+ }
848
+
849
+ export interface ListContactsQuery extends PaginationQuery {
850
+ /** Free-text needle matched against contact name + address values. */
620
851
  q?: string
852
+ status?: ContactStatus
853
+ }
854
+
855
+ export interface UpdateContactRequest {
856
+ name?: string
857
+ status?: 'active' | 'archived'
858
+ has_legal_hold?: boolean
859
+ }
860
+
861
+ export interface AttachContactAddressRequest {
862
+ kind: ContactAddressKind
863
+ scope?: string
864
+ value: string
865
+ name?: string
866
+ }
867
+
868
+ export interface MergeContactsRequest {
869
+ source_contact_id: string
870
+ }
871
+
872
+ export interface RecordPermissionEventRequest {
873
+ contact_address_id?: string
874
+ event: PermissionEventType
875
+ basis?: string
876
+ source?: PermissionEventSource
877
+ occurred_at?: string
878
+ note?: string
879
+ evidence?: Record<string, unknown>
880
+ }
881
+
882
+ export interface BlockContactRequest {
883
+ contact_address_id?: string
884
+ note?: string
885
+ }
886
+
887
+ export interface ListContactMergeProposalsQuery extends PaginationQuery {
888
+ status?: MergeProposalStatus
621
889
  }
622
890
 
623
891
  export interface ListRoomsQuery extends PaginationQuery {
package/src/index.ts CHANGED
@@ -196,6 +196,7 @@ export type {
196
196
  ContentBlock as MessageContentBlock,
197
197
  Conversation,
198
198
  ConversationParticipant,
199
+ ContactService,
199
200
  ConversationService,
200
201
  ConversationStatus,
201
202
  CreateAgentListenerRequest,
@@ -210,7 +211,9 @@ export type {
210
211
  ListConversationsQuery,
211
212
  ListGlossaryTermsQuery,
212
213
  ListMessagesQuery,
213
- ListParticipantsQuery,
214
+ ListContactAddressesQuery,
215
+ ListContactMergeProposalsQuery,
216
+ ListContactsQuery,
214
217
  // Conversation-local page envelope ({meta, data}) — distinct from the
215
218
  // PageIterator-based ListResult used by the other modules.
216
219
  ListResponse,
@@ -222,9 +225,43 @@ export type {
222
225
  MessageRecipient,
223
226
  MessageService,
224
227
  MessageStatus,
225
- Participant,
226
- ParticipantRef,
227
- ParticipantSource,
228
+ AttachContactAddressRequest,
229
+ BlockContactRequest,
230
+ ConsentStatus,
231
+ Contact,
232
+ ContactAddress,
233
+ ContactAddressKind,
234
+ ContactAddressSource,
235
+ ContactErasureRequest,
236
+ ContactMergeProposal,
237
+ ContactRef,
238
+ ContactSource,
239
+ ContactStatus,
240
+ AddressFilterConfig,
241
+ AllFilterConfig,
242
+ AutomatedFilterConfig,
243
+ AutomatedSignal,
244
+ ConversationFilter,
245
+ ConversationFilterAction,
246
+ ConversationFilterConfig,
247
+ ConversationFilterEvent,
248
+ ConversationFilterService,
249
+ ConversationFilterType,
250
+ CreateConversationFilterRequest,
251
+ DomainFilterConfig,
252
+ FilterMatchOn,
253
+ InternalConversationsFilterConfig,
254
+ ListConversationFilterEventsQuery,
255
+ ListConversationFiltersQuery,
256
+ RoleBasedFilterConfig,
257
+ UpdateConversationFilterRequest,
258
+ ErasureRequestStatus,
259
+ MergeContactsRequest,
260
+ MergeProposalStatus,
261
+ PermissionEventSource,
262
+ PermissionEventType,
263
+ RecordPermissionEventRequest,
264
+ UpdateContactRequest,
228
265
  Reaction,
229
266
  ReactionCapability,
230
267
  ReactionOption,
@@ -88,6 +88,12 @@ export type RelatedListElement = CommonProps & {
88
88
  related_entity_slug: string
89
89
  via_attribute: string
90
90
  list_slug?: string
91
+ /**
92
+ * When absent or true, the list enters row-edit mode together with the
93
+ * host page's edit mode. False keeps the list independent — rows are only
94
+ * editable through the list's own "Edit records" toggle.
95
+ */
96
+ follows_parent_edit_mode?: boolean
91
97
  }
92
98
 
93
99
  export type ComponentElement = CommonProps & {
@@ -182,6 +188,7 @@ export const LayoutElementSchema: z.ZodType<LayoutElement> = z.lazy(() =>
182
188
  related_entity_slug: z.string().min(1),
183
189
  via_attribute: z.string().min(1),
184
190
  list_slug: z.string().min(1).optional(),
191
+ follows_parent_edit_mode: z.boolean().optional(),
185
192
  }),
186
193
  z.object({
187
194
  type: z.literal('component'),