@proteos/sdk 0.26.3 → 0.28.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,141 @@ 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
+ * Discriminates a send target: a person's contact address or a venue. The
571
+ * server also accepts the legacy 'participant' value as an alias for
572
+ * 'contact-address'.
573
+ */
574
+ export type RecipientKind = 'contact-address' | 'room'
575
+ export type ContactAddressKind =
576
+ | 'email'
577
+ | 'phone'
578
+ | 'slack'
579
+ | 'linkedin'
580
+ | 'telegram'
581
+ | 'instagram'
582
+ | 'messenger'
583
+ | 'x'
584
+ | 'whatsapp'
585
+ export type ContactAddressSource = 'sync' | 'ingest' | 'manual' | 'merge'
586
+ export type ContactSource = 'sync' | 'ingest' | 'manual' | 'merge'
587
+ export type ContactStatus = 'active' | 'merged' | 'archived' | 'erased'
588
+ export type ConsentStatus = 'unknown' | 'opted_in' | 'opted_out'
589
+ export type PermissionEventType = 'opt_in' | 'opt_out' | 'block' | 'unblock'
590
+ export type PermissionEventSource = 'manual' | 'import' | 'link_click' | 'reply' | 'api' | 'system'
591
+ export type MergeProposalStatus = 'proposed' | 'approved' | 'rejected' | 'superseded'
592
+ export type ErasureRequestStatus = 'requested' | 'completed'
568
593
  export type RoomSource = 'sync' | 'ingest'
569
594
 
570
595
  /**
571
596
  * 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).
597
+ * ContactAddress or a Room by kind + external id (never a stored entity itself).
573
598
  */
574
599
  export interface SendRecipient {
575
600
  kind: RecipientKind
576
- /** The connector-side id: a room's (Slack channel id) or a participant's (Slack user id, email address). */
601
+ /** The connector-side id: a room's (Slack channel id) or a contact address's wire identifier (Slack user id, email address). */
577
602
  external_id: string
578
603
  }
579
604
 
580
- /** A PERSON known on a connection (directory row; resolvable to a platform user). */
581
- export interface Participant {
605
+ /** The actual PERSON an org communicates with org-scoped, channel-agnostic. */
606
+ export interface Contact {
582
607
  id: string
583
608
  org_id: string
584
- connection_id: string
585
- /** Slack user id / email address — the id to DM or email. */
586
- external_id: string
587
609
  name: string
588
- email?: string
589
- /** Resolved platform identity (email match); absent when unresolved. */
610
+ status: ContactStatus
611
+ /** Person-level suppression only block/unblock events move it. */
612
+ is_blocked: boolean
613
+ has_legal_hold: boolean
614
+ consent_status: ConsentStatus
615
+ consent_updated_at?: string
616
+ /** Resolved platform identity when the person is a colleague. */
590
617
  platform_user?: UserRef
618
+ /** Merge tombstone redirect (set when status is 'merged'). */
619
+ merged_into_contact_id?: string
620
+ source: ContactSource
621
+ has_manual_edits: boolean
622
+ /** The contact's reachable endpoints, embedded on reads. */
623
+ addresses?: ContactAddress[]
624
+ created_at: string
625
+ created_by: UserRef
626
+ updated_at: string
627
+ updated_by: UserRef
628
+ }
629
+
630
+ /** ONE canonical digital endpoint to reach a contact — the dedup backbone. */
631
+ export interface ContactAddress {
632
+ id: string
633
+ org_id: string
634
+ contact_id: string
635
+ kind: ContactAddressKind
636
+ /** Provider-tenant qualifier (Slack workspace id); empty for global kinds. */
637
+ scope?: string
638
+ /** Canonical scalar (lowercased email, E.164 phone) — the lookup key. */
639
+ value: string
640
+ /** The identifier as observed on the wire (WhatsApp JID, original casing). */
641
+ raw_value?: string
642
+ /** Per-channel display name. */
643
+ name: string
644
+ consent_status: ConsentStatus
645
+ consent_updated_at?: string
646
+ is_blocked: boolean
647
+ source: ContactAddressSource
591
648
  metadata: Record<string, unknown>
592
- source: ParticipantSource
593
- last_seen_at: string
594
649
  created_at: string
595
650
  created_by: UserRef
596
651
  updated_at: string
597
652
  updated_by: UserRef
598
653
  }
599
654
 
655
+ /** One entry in the append-only permission ledger (consent + suppression). */
656
+ export interface ContactPermissionEvent {
657
+ id: string
658
+ org_id: string
659
+ contact_id: string
660
+ /** Scopes the event to one address; absent/empty = contact-level. */
661
+ contact_address_id?: string
662
+ event: PermissionEventType
663
+ basis?: string
664
+ source: PermissionEventSource
665
+ occurred_at: string
666
+ note?: string
667
+ evidence?: Record<string, unknown>
668
+ created_at: string
669
+ created_by: UserRef
670
+ }
671
+
672
+ /** One candidate duplicate pair awaiting review. */
673
+ export interface ContactMergeProposal {
674
+ id: string
675
+ org_id: string
676
+ contact_a_id: string
677
+ contact_b_id: string
678
+ suggested_winner_id?: string
679
+ confidence?: number
680
+ evidence?: Record<string, unknown>
681
+ verdict?: Record<string, unknown>
682
+ status: MergeProposalStatus
683
+ resolved_by?: UserRef
684
+ resolved_at?: string
685
+ created_at: string
686
+ created_by: UserRef
687
+ updated_at: string
688
+ updated_by: UserRef
689
+ }
690
+
691
+ /** A GDPR Art. 17 erasure request (requested → completed). */
692
+ export interface ContactErasureRequest {
693
+ id: string
694
+ org_id: string
695
+ contact_id: string
696
+ identifier_hashes: Array<{ kind: ContactAddressKind; hmac: string }>
697
+ status: ErasureRequestStatus
698
+ completed_at?: string
699
+ stats?: Record<string, unknown>
700
+ created_at: string
701
+ created_by: UserRef
702
+ }
703
+
600
704
  /** An addressable VENUE on a connection (Slack channel; later meeting rooms). */
601
705
  export interface Room {
602
706
  id: string
@@ -615,9 +719,53 @@ export interface Room {
615
719
  updated_by: UserRef
616
720
  }
617
721
 
618
- export interface ListParticipantsQuery extends PaginationQuery {
619
- /** Free-text needle matched against display name + external id. */
722
+ export interface ListContactAddressesQuery extends PaginationQuery {
723
+ /** Free-text needle matched against display name + canonical value. */
724
+ q?: string
725
+ kinds?: ContactAddressKind[]
726
+ scope?: string
727
+ }
728
+
729
+ export interface ListContactsQuery extends PaginationQuery {
730
+ /** Free-text needle matched against contact name + address values. */
620
731
  q?: string
732
+ status?: ContactStatus
733
+ }
734
+
735
+ export interface UpdateContactRequest {
736
+ name?: string
737
+ status?: 'active' | 'archived'
738
+ has_legal_hold?: boolean
739
+ }
740
+
741
+ export interface AttachContactAddressRequest {
742
+ kind: ContactAddressKind
743
+ scope?: string
744
+ value: string
745
+ name?: string
746
+ }
747
+
748
+ export interface MergeContactsRequest {
749
+ source_contact_id: string
750
+ }
751
+
752
+ export interface RecordPermissionEventRequest {
753
+ contact_address_id?: string
754
+ event: PermissionEventType
755
+ basis?: string
756
+ source?: PermissionEventSource
757
+ occurred_at?: string
758
+ note?: string
759
+ evidence?: Record<string, unknown>
760
+ }
761
+
762
+ export interface BlockContactRequest {
763
+ contact_address_id?: string
764
+ note?: string
765
+ }
766
+
767
+ export interface ListContactMergeProposalsQuery extends PaginationQuery {
768
+ status?: MergeProposalStatus
621
769
  }
622
770
 
623
771
  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,25 @@ 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
+ ErasureRequestStatus,
241
+ MergeContactsRequest,
242
+ MergeProposalStatus,
243
+ PermissionEventSource,
244
+ PermissionEventType,
245
+ RecordPermissionEventRequest,
246
+ UpdateContactRequest,
228
247
  Reaction,
229
248
  ReactionCapability,
230
249
  ReactionOption,
@@ -23,7 +23,8 @@
23
23
  "user-picker",
24
24
  "currency",
25
25
  "knowledge-text",
26
- "file"
26
+ "file",
27
+ "file-viewer"
27
28
  ],
28
29
  "controls": {
29
30
  "string": {
@@ -65,6 +66,6 @@
65
66
  "user": { "primary": "user-picker", "compatible": ["user-picker"] },
66
67
  "currency": { "primary": "currency", "compatible": ["currency"] },
67
68
  "knowledge-text": { "primary": "knowledge-text", "compatible": ["knowledge-text"] },
68
- "file": { "primary": "file", "compatible": ["file"] }
69
+ "file": { "primary": "file", "compatible": ["file", "file-viewer"] }
69
70
  }
70
71
  }
@@ -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'),
@@ -40,11 +40,15 @@ export interface FileService {
40
40
  create(metadata: CreateFileMetadata): Promise<StorageFile>
41
41
 
42
42
  /**
43
- * Updates a file's metadata (name, `public_access`). Only the fields present
44
- * in `metadata` are changed. Setting `public_access: ["read"]` exposes the
45
- * file on the UNAUTHENTICATED public download route; `[]` revokes it.
43
+ * Updates a file's metadata (name, `public_access`) and, when `file` is
44
+ * given, replaces the bytes IN PLACE a new version under the SAME id, so
45
+ * every `FileRef { id }` pointing at it now resolves to the new content.
46
+ * Only the metadata fields present are changed. Setting
47
+ * `public_access: ["read"]` exposes the file on the UNAUTHENTICATED public
48
+ * download route; `[]` revokes it. Replacing a locked file is rejected by
49
+ * the storage-service.
46
50
  */
47
- update(id: string, metadata: UpdateFileMetadata): Promise<StorageFile>
51
+ update(id: string, metadata: UpdateFileMetadata, file?: File): Promise<StorageFile>
48
52
 
49
53
  /**
50
54
  * Mints a short-lived upload URL for a file. The bytes are PUT directly to this URL
@@ -157,12 +161,16 @@ export class FileServiceImpl implements FileService {
157
161
  return response.data
158
162
  }
159
163
 
160
- async update(id: string, metadata: UpdateFileMetadata): Promise<StorageFile> {
164
+ async update(id: string, metadata: UpdateFileMetadata, file?: File): Promise<StorageFile> {
165
+ // Part order is load-bearing: the storage-service reads `metadata` first
166
+ // and stops consuming parts at `file` — metadata appended after the file
167
+ // part would be silently dropped.
161
168
  const formData = new FormData()
162
169
  formData.append(
163
170
  'metadata',
164
171
  new Blob([JSON.stringify(metadata)], { type: 'application/json' }),
165
172
  )
173
+ if (file) formData.append('file', file, metadata.name ?? file.name)
166
174
  const response = await this.client.requestMultipart<{ data: StorageFile }>(
167
175
  'PATCH',
168
176
  `${FILES_BASE_PATH}/${id}`,
@@ -103,13 +103,16 @@ export const PLATFORM_USER_ID = 'platform'
103
103
  /**
104
104
  * A reference to a stored file — the value stored by every `file` attribute.
105
105
  * `id` is the storage-service file id; `name` is the denormalised filename the
106
- * record carries so it has a human label without a storage round-trip. Size and
106
+ * record carries so it has a human label without a storage round-trip.
107
+ * `content_type` is an optional denormalised MIME hint so renderers can pick a
108
+ * viewer without a storage round-trip; when absent (legacy values), size and
107
109
  * content type are resolved from the storage-service by `id` on read. Mirrors
108
110
  * the backend's `common.FileRef`.
109
111
  */
110
112
  export interface FileRef {
111
113
  id: string
112
114
  name: string
115
+ content_type?: string
113
116
  }
114
117
 
115
118
  /**
@@ -118,6 +121,8 @@ export interface FileRef {
118
121
  export const FileRefSchema = z.object({
119
122
  id: z.string(),
120
123
  name: z.string(),
124
+ // Mirrors the server-side validator: when present, must be non-empty.
125
+ content_type: z.string().min(1).optional(),
121
126
  })
122
127
 
123
128
  /**