@parall/sdk 1.48.0 → 1.50.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/src/client.ts CHANGED
@@ -23,6 +23,10 @@ import type {
23
23
  Organization,
24
24
  OrgMember,
25
25
  OrgMemberRole,
26
+ MemberProfile,
27
+ UpdateMemberProfileRequest,
28
+ AgentInstructions,
29
+ UpdateAgentInstructionsRequest,
26
30
  Team,
27
31
  Chat,
28
32
  ChatMember,
@@ -167,6 +171,8 @@ import type {
167
171
  DispatchExpireResult,
168
172
  ResolveRefsResponse,
169
173
  BacklinksResponse,
174
+ OutboundRefsRequest,
175
+ OutboundRefsResponse,
170
176
  RefGraphResponse,
171
177
  BrokenRefsResponse,
172
178
  PushSubscribeRequest,
@@ -223,14 +229,23 @@ import type {
223
229
  BrowserProfileLifecycleRequest,
224
230
  BrowserViewerCommandRequest,
225
231
  BrowserViewerCommandResponse,
232
+ EdgeViewerCommandRequest,
233
+ EdgeViewerCommandResponse,
226
234
  GrantBrowserProfileConsentRequest,
227
235
  EdgeDevice,
228
236
  EdgePlacement,
229
237
  EdgeBrowserProfile,
238
+ EdgeProfileProxyStatus,
239
+ SetEdgeProfileProxyRequest,
230
240
  ClipConnection,
231
241
  EdgeOnboardingStatus,
232
242
  ExecEdgeClipRequest,
233
243
  EdgeClipExecResult,
244
+ ReactionSummary,
245
+ ToggleReactionResponse,
246
+ DeployTemplateRequest,
247
+ Template,
248
+ TemplateDeploymentReport,
234
249
  } from './types.js';
235
250
 
236
251
  export interface ParallClientOptions {
@@ -421,6 +436,8 @@ export class ParallClient {
421
436
  timeoutMs?: number;
422
437
  signal?: AbortSignal;
423
438
  keepalive?: boolean;
439
+ /** Additional request preconditions such as If-Match. */
440
+ headers?: Record<string, string>;
424
441
  /** Observes the final HTTP status of a successful request (e.g. 200-idempotent-replay vs 201-created). */
425
442
  onStatus?: (status: number) => void;
426
443
  },
@@ -443,7 +460,7 @@ export class ParallClient {
443
460
  if (qs) url += `?${qs}`;
444
461
  }
445
462
 
446
- const headers = this.buildHeaders(path);
463
+ const headers = this.buildHeaders(path, opts?.headers);
447
464
 
448
465
  // Cancellation: the caller's AbortSignal (e.g. a superseded search query)
449
466
  // races the per-request timeout. AbortSignal.any aborts as soon as either
@@ -743,6 +760,42 @@ export class ParallClient {
743
760
  return this.request('PATCH', ENDPOINTS.ORG_MEMBER(orgId, userId), { role });
744
761
  }
745
762
 
763
+ /** Org-scoped public profile (title / description) of any active member. */
764
+ async getMemberProfile(orgId: string, userId: string): Promise<MemberProfile> {
765
+ return this.request('GET', ENDPOINTS.ORG_MEMBER_PROFILE(orgId, userId));
766
+ }
767
+
768
+ /**
769
+ * CAS full-replacement of a member's public profile. Humans edit their
770
+ * own; Human org admins/owners edit agents'. A stale expected_version
771
+ * gets 409 PROFILE_VERSION_CONFLICT with error.details.current_version.
772
+ */
773
+ async updateMemberProfile(
774
+ orgId: string,
775
+ userId: string,
776
+ req: UpdateMemberProfileRequest,
777
+ ): Promise<MemberProfile> {
778
+ return this.request('PATCH', ENDPOINTS.ORG_MEMBER_PROFILE(orgId, userId), req);
779
+ }
780
+
781
+ /** Private agent Instructions — agent self or Human org admin/owner only. */
782
+ async getAgentInstructions(orgId: string, agentId: string): Promise<AgentInstructions> {
783
+ return this.request('GET', ENDPOINTS.AGENT_INSTRUCTIONS(orgId, agentId));
784
+ }
785
+
786
+ /**
787
+ * CAS update of the private Instructions (Human org admin/owner only).
788
+ * A stale expected_version gets 409 INSTRUCTIONS_VERSION_CONFLICT with
789
+ * error.details.current_version.
790
+ */
791
+ async updateAgentInstructions(
792
+ orgId: string,
793
+ agentId: string,
794
+ req: UpdateAgentInstructionsRequest,
795
+ ): Promise<AgentInstructions> {
796
+ return this.request('PATCH', ENDPOINTS.AGENT_INSTRUCTIONS(orgId, agentId), req);
797
+ }
798
+
746
799
  // Member activity surfaces — chats the member participates in within this org,
747
800
  // ordered by most recent activity. Powers the member profile Activity tab.
748
801
  async getMemberChats(
@@ -1095,6 +1148,20 @@ export class ParallClient {
1095
1148
  return this.request('GET', ENDPOINTS.MESSAGE_REPLIES(id), undefined, params);
1096
1149
  }
1097
1150
 
1151
+ // ---- Reactions ----
1152
+
1153
+ async toggleReaction(messageId: string, emoji: string): Promise<ToggleReactionResponse> {
1154
+ return this.request('PUT', ENDPOINTS.MESSAGE_REACTION(messageId, emoji));
1155
+ }
1156
+
1157
+ async listReactions(messageId: string): Promise<ReactionSummary[]> {
1158
+ const res = await this.request<{ reactions: ReactionSummary[] }>(
1159
+ 'GET',
1160
+ ENDPOINTS.MESSAGE_REACTIONS(messageId),
1161
+ );
1162
+ return res.reactions;
1163
+ }
1164
+
1098
1165
  // ---- File Upload ----
1099
1166
 
1100
1167
  async getUploadPresignUrl(orgId: string, req: PresignUploadRequest): Promise<PresignResponse> {
@@ -1757,6 +1824,28 @@ export class ParallClient {
1757
1824
  );
1758
1825
  }
1759
1826
 
1827
+ /**
1828
+ * Drive the Cloud Edge live viewer (V1b, design §6): WebRTC signaling + input +
1829
+ * tab nav for a hosted browser (Cloud Profile). Same request/reply shape as
1830
+ * browserViewerCommand, on the v3 edge pipe; additive — the v2 browser-profile
1831
+ * viewer is unchanged.
1832
+ */
1833
+ async edgeViewerCommand(
1834
+ orgId: string,
1835
+ edgeId: string,
1836
+ req: EdgeViewerCommandRequest,
1837
+ opts?: { timeoutMs?: number; keepalive?: boolean },
1838
+ ): Promise<EdgeViewerCommandResponse> {
1839
+ return this.request(
1840
+ 'POST',
1841
+ ENDPOINTS.ORG_EDGE_VIEWER_COMMAND(orgId, edgeId),
1842
+ req,
1843
+ undefined,
1844
+ false,
1845
+ opts,
1846
+ );
1847
+ }
1848
+
1760
1849
  async resizeMachine(
1761
1850
  orgId: string,
1762
1851
  machineId: string,
@@ -1813,9 +1902,23 @@ export class ParallClient {
1813
1902
 
1814
1903
  // ---- Unread ----
1815
1904
 
1816
- async getUnreadCounts(orgId?: string): Promise<Record<string, UnreadEntry>> {
1905
+ async getUnreadCounts(
1906
+ orgId?: string,
1907
+ opts?: {
1908
+ /** Add unread thread replies (per-thread cursors) to count/mentions.
1909
+ * Opt-in: only clients that can clear thread cursors should pass it. */
1910
+ includeThreadReplies?: boolean;
1911
+ },
1912
+ ): Promise<Record<string, UnreadEntry>> {
1817
1913
  const endpoint = orgId ? ENDPOINTS.ORG_UNREAD(orgId) : ENDPOINTS.UNREAD;
1818
- const res = await this.request<{ data: Record<string, UnreadEntry> }>('GET', endpoint);
1914
+ const res = await this.request<{ data: Record<string, UnreadEntry> }>(
1915
+ 'GET',
1916
+ endpoint,
1917
+ undefined,
1918
+ {
1919
+ include_thread_replies: opts?.includeThreadReplies ? 'true' : undefined,
1920
+ },
1921
+ );
1819
1922
  return res.data;
1820
1923
  }
1821
1924
 
@@ -1823,6 +1926,14 @@ export class ParallClient {
1823
1926
  return this.request('POST', ENDPOINTS.CHAT_READ(orgId, chatId), { message_id: messageId });
1824
1927
  }
1825
1928
 
1929
+ /** Mark everything in a chat read: the channel cursor jumps to the latest
1930
+ * top-level message and every thread cursor to its latest reply, in one
1931
+ * idempotent call. The server echoes the same per-cursor WS events the
1932
+ * single-cursor routes emit and auto-clears covered inbox items. */
1933
+ async markAllRead(orgId: string, chatId: string): Promise<void> {
1934
+ return this.request('POST', ENDPOINTS.CHAT_READ_ALL(orgId, chatId));
1935
+ }
1936
+
1826
1937
  async getThreadUnread(
1827
1938
  orgId: string,
1828
1939
  chatId: string,
@@ -3161,6 +3272,17 @@ export class ParallClient {
3161
3272
  return this.request('GET', ENDPOINTS.REFS_BACKLINKS(orgId), undefined, params);
3162
3273
  }
3163
3274
 
3275
+ /**
3276
+ * Outbound prll:// refs authored in a set of sources, as raw ref_links rows
3277
+ * (dedupe/group client-side). Pass `{ thread_root_id }` to list a whole
3278
+ * thread's refs (root + all replies, resolved server-side — the client's
3279
+ * reply window may be partial), or `{ source_type, source_ids }` for
3280
+ * explicit sources (max 500; v1 accepts only message sources).
3281
+ */
3282
+ async listOutboundRefs(orgId: string, req: OutboundRefsRequest): Promise<OutboundRefsResponse> {
3283
+ return this.request('POST', ENDPOINTS.REFS_OUTBOUND(orgId), req);
3284
+ }
3285
+
3164
3286
  /**
3165
3287
  * Bounded multi-hop walk of the prll:// reference graph around `uri`. `uri`
3166
3288
  * must be an entity-level prll:// URI — a refined URI (path/query/fragment) is
@@ -3211,6 +3333,40 @@ export class ParallClient {
3211
3333
  return this.request('GET', ENDPOINTS.FEATURE_FLAGS(orgId));
3212
3334
  }
3213
3335
 
3336
+ // ---- Official Template catalog (org-contextual owner/admin reads + Hire) ----
3337
+
3338
+ async listOfficialTemplates(orgId: string): Promise<Template[]> {
3339
+ const response = await this.request<{ templates: Template[] }>(
3340
+ 'GET',
3341
+ ENDPOINTS.TEMPLATES(orgId),
3342
+ );
3343
+ return response.templates ?? [];
3344
+ }
3345
+
3346
+ async getOfficialTemplate(orgId: string, templateId: string): Promise<Template> {
3347
+ return this.request('GET', ENDPOINTS.TEMPLATE(orgId, templateId));
3348
+ }
3349
+
3350
+ async deployTemplate(
3351
+ orgId: string,
3352
+ request: DeployTemplateRequest,
3353
+ ): Promise<TemplateDeploymentReport> {
3354
+ // The server grants this synchronous, non-idempotent operation a 3-minute
3355
+ // write deadline because it may provision several agents in sequence.
3356
+ // Keep the client alive slightly longer so it does not manufacture an
3357
+ // ambiguous timeout while the team is still being created.
3358
+ return this.request('POST', ENDPOINTS.TEMPLATE_DEPLOYMENTS(orgId), request, undefined, false, {
3359
+ timeoutMs: 185_000,
3360
+ });
3361
+ }
3362
+
3363
+ async getTemplateDeployment(
3364
+ orgId: string,
3365
+ deploymentId: string,
3366
+ ): Promise<TemplateDeploymentReport> {
3367
+ return this.request('GET', ENDPOINTS.TEMPLATE_DEPLOYMENT(orgId, deploymentId));
3368
+ }
3369
+
3214
3370
  // ---- Billing & Credits (org-scoped) ----
3215
3371
 
3216
3372
  async getBilling(orgId: string): Promise<BillingSummary> {
@@ -3567,6 +3723,75 @@ export class ParallClient {
3567
3723
  return this.request('GET', ENDPOINTS.ORG_EDGE_PROFILES(orgId, edgeId));
3568
3724
  }
3569
3725
 
3726
+ /**
3727
+ * Read a hosted Cloud Profile's egress-proxy status (manager-only: hosted
3728
+ * human maintainer or org admin). Sanitized — the password never comes back.
3729
+ * Typed errors: `EDGE_NOT_HOSTED` (BYOC device), `PROXY_CONFIG_CORRUPT` /
3730
+ * validation codes as 422 when a stored config no longer passes current rules.
3731
+ * `can_mutate` and `lease_status` are the authoritative idle gate; device
3732
+ * list status is not a substitute.
3733
+ */
3734
+ async getEdgeProfileProxy(
3735
+ orgId: string,
3736
+ edgeId: string,
3737
+ profileName: string,
3738
+ ): Promise<EdgeProfileProxyStatus> {
3739
+ return this.request('GET', ENDPOINTS.ORG_EDGE_PROFILE_PROXY(orgId, edgeId, profileName));
3740
+ }
3741
+
3742
+ /**
3743
+ * Set/replace the profile's egress proxy (full triple every time) — IDLE
3744
+ * ONLY: while the profile's hosted browser is running (a viewer session is
3745
+ * open or a pod is otherwise live) the server answers 409
3746
+ * `EDGE_PROFILE_IN_USE`; close the viewer, wait for idle scale-to-zero, and
3747
+ * retry. The next cold start uses the new egress; browser login state is
3748
+ * preserved across it. Other typed errors: the validation vocabulary
3749
+ * (`INVALID_PROXY_SERVER`, `PROXY_AUTH_INCOMPLETE`,
3750
+ * `PROXY_SERVER_FORBIDDEN_TARGET`, …), `EDGE_DELETING` (409), and
3751
+ * `SECRETBOX_UNCONFIGURED` (503 — server cannot store credentials safely),
3752
+ * and `EDGE_PROFILE_PROXY_STALE` (409 — expectedVersion lost a tab race).
3753
+ */
3754
+ async setEdgeProfileProxy(
3755
+ orgId: string,
3756
+ edgeId: string,
3757
+ profileName: string,
3758
+ req: SetEdgeProfileProxyRequest,
3759
+ expectedVersion?: string,
3760
+ ): Promise<EdgeProfileProxyStatus> {
3761
+ return this.request(
3762
+ 'PUT',
3763
+ ENDPOINTS.ORG_EDGE_PROFILE_PROXY(orgId, edgeId, profileName),
3764
+ req,
3765
+ undefined,
3766
+ false,
3767
+ {
3768
+ headers: expectedVersion ? { 'If-Match': `"proxy-${expectedVersion}"` } : undefined,
3769
+ },
3770
+ );
3771
+ }
3772
+
3773
+ /**
3774
+ * Clear the profile's egress proxy; the next pod start egresses directly.
3775
+ * Idle-only like set — 409 `EDGE_PROFILE_IN_USE` while the browser is live.
3776
+ */
3777
+ async clearEdgeProfileProxy(
3778
+ orgId: string,
3779
+ edgeId: string,
3780
+ profileName: string,
3781
+ expectedVersion?: string,
3782
+ ): Promise<EdgeProfileProxyStatus> {
3783
+ return this.request(
3784
+ 'DELETE',
3785
+ ENDPOINTS.ORG_EDGE_PROFILE_PROXY(orgId, edgeId, profileName),
3786
+ undefined,
3787
+ undefined,
3788
+ false,
3789
+ {
3790
+ headers: expectedVersion ? { 'If-Match': `"proxy-${expectedVersion}"` } : undefined,
3791
+ },
3792
+ );
3793
+ }
3794
+
3570
3795
  /**
3571
3796
  * Execute a registry clip command on an Edge device.
3572
3797
  *
@@ -3664,6 +3889,8 @@ function normalizeWikiChangeset(changeset: WikiChangeset): WikiChangeset {
3664
3889
 
3665
3890
  export class ApiError extends Error {
3666
3891
  extras?: Record<string, unknown>;
3892
+ /** Retry-After delta seconds when the server supplies one. */
3893
+ retryAfterSeconds?: number;
3667
3894
  /** Attempted action (authorization denials) — e.g. "chat.add_member". */
3668
3895
  action?: string;
3669
3896
  /** Target resource URI that was evaluated — e.g. "prll://cht_…". */
@@ -3691,7 +3918,7 @@ export class ApiError extends Error {
3691
3918
  * See docs/engineering-design/error-contract-design.md
3692
3919
  */
3693
3920
  function buildApiError(
3694
- res: { status: number; statusText: string },
3921
+ res: { status: number; statusText: string; headers?: { get(name: string): string | null } },
3695
3922
  rawErrorBody: unknown,
3696
3923
  ): ApiError {
3697
3924
  const errorBody =
@@ -3714,6 +3941,8 @@ function buildApiError(
3714
3941
  (typeof errorObj?.code === 'string' ? errorObj.code : undefined) ??
3715
3942
  (typeof errorBody.code === 'string' ? (errorBody.code as string) : undefined);
3716
3943
  const apiError = new ApiError(res.status, errMsg ?? res.statusText, errCode);
3944
+ const retryAfter = Number(res.headers?.get('Retry-After'));
3945
+ if (Number.isFinite(retryAfter) && retryAfter > 0) apiError.retryAfterSeconds = retryAfter;
3717
3946
  // Machine anchors: present under `error`, with a legacy flat fallback.
3718
3947
  const anchors = errorObj ?? errorBody;
3719
3948
  if (typeof anchors.action === 'string') apiError.action = anchors.action;
package/src/constants.ts CHANGED
@@ -377,6 +377,14 @@ export const ENDPOINTS = {
377
377
  `${API_BASE}/orgs/${orgId}/members/${memberId}/chats`,
378
378
  ORG_MEMBER_TASKS: (orgId: string, memberId: string) =>
379
379
  `${API_BASE}/orgs/${orgId}/members/${memberId}/tasks`,
380
+ // Org-scoped public profile (title / description). GET readable by every
381
+ // active member; PATCH is CAS-guarded (expected_version).
382
+ ORG_MEMBER_PROFILE: (orgId: string, userId: string) =>
383
+ `${API_BASE}/orgs/${orgId}/members/${userId}/profile`,
384
+ // Private agent Instructions — visibility gated server-side
385
+ // (agent self + Human org admin/owner). Never cached.
386
+ AGENT_INSTRUCTIONS: (orgId: string, agentId: string) =>
387
+ `${API_BASE}/orgs/${orgId}/agents/${agentId}/instructions`,
380
388
  REF_SEARCH: (orgId: string) => `${API_BASE}/orgs/${orgId}/refs/search`,
381
389
 
382
390
  // Direct messages (org-scoped, atomic find-or-create + send)
@@ -410,6 +418,9 @@ export const ENDPOINTS = {
410
418
  MESSAGE_WATCH: (id: string) => `${API_BASE}/messages/${id}/watch`,
411
419
  MESSAGE_WATCHERS: (id: string) => `${API_BASE}/messages/${id}/watchers`,
412
420
  MESSAGE_WATCHING: (id: string) => `${API_BASE}/messages/${id}/watching`,
421
+ MESSAGE_REACTIONS: (id: string) => `${API_BASE}/messages/${id}/reactions`,
422
+ MESSAGE_REACTION: (id: string, emoji: string) =>
423
+ `${API_BASE}/messages/${id}/reactions/${encodeURIComponent(emoji)}`,
413
424
 
414
425
  // Upload (org-scoped)
415
426
  UPLOAD_PRESIGN: (orgId: string) => `${API_BASE}/orgs/${orgId}/upload/presign`,
@@ -780,6 +791,8 @@ export const ENDPOINTS = {
780
791
  UNREAD: `${API_BASE}/me/unread`,
781
792
  ORG_UNREAD: (orgId: string) => `${API_BASE}/orgs/${orgId}/unread`,
782
793
  CHAT_READ: (orgId: string, chatId: string) => `${API_BASE}/orgs/${orgId}/chats/${chatId}/read`,
794
+ CHAT_READ_ALL: (orgId: string, chatId: string) =>
795
+ `${API_BASE}/orgs/${orgId}/chats/${chatId}/read-all`,
783
796
  THREAD_UNREAD: (orgId: string, chatId: string, threadRootId: string) =>
784
797
  `${API_BASE}/orgs/${orgId}/chats/${chatId}/threads/${threadRootId}/unread`,
785
798
  THREAD_READ: (orgId: string, chatId: string, threadRootId: string) =>
@@ -788,6 +801,7 @@ export const ENDPOINTS = {
788
801
  // References (org-scoped)
789
802
  REFS_RESOLVE: (orgId: string) => `${API_BASE}/orgs/${orgId}/refs/resolve`,
790
803
  REFS_BACKLINKS: (orgId: string) => `${API_BASE}/orgs/${orgId}/refs/backlinks`,
804
+ REFS_OUTBOUND: (orgId: string) => `${API_BASE}/orgs/${orgId}/refs/outbound`,
791
805
  REFS_GRAPH: (orgId: string) => `${API_BASE}/orgs/${orgId}/refs/graph`,
792
806
  REFS_CHECK: (orgId: string) => `${API_BASE}/orgs/${orgId}/refs/check`,
793
807
 
@@ -808,6 +822,14 @@ export const ENDPOINTS = {
808
822
  // Feature flags (org-scoped, server-evaluated)
809
823
  FEATURE_FLAGS: (orgId: string) => `${API_BASE}/orgs/${orgId}/feature-flags`,
810
824
 
825
+ // Team templates (org-scoped, owner/admin only)
826
+ TEMPLATES: (orgId: string) => `${API_BASE}/orgs/${orgId}/templates`,
827
+ TEMPLATE: (orgId: string, templateId: string) =>
828
+ `${API_BASE}/orgs/${orgId}/templates/${templateId}`,
829
+ TEMPLATE_DEPLOYMENTS: (orgId: string) => `${API_BASE}/orgs/${orgId}/template-deployments`,
830
+ TEMPLATE_DEPLOYMENT: (orgId: string, deploymentId: string) =>
831
+ `${API_BASE}/orgs/${orgId}/template-deployments/${deploymentId}`,
832
+
811
833
  // Billing & Credits (org-scoped)
812
834
  BILLING: (orgId: string) => `${API_BASE}/orgs/${orgId}/billing`,
813
835
  BILLING_TRANSACTIONS: (orgId: string) => `${API_BASE}/orgs/${orgId}/billing/transactions`,
@@ -876,7 +898,15 @@ export const ENDPOINTS = {
876
898
  ORG_EDGE_ONBOARDING: (orgId: string) => `/api/v1/orgs/${orgId}/edge/onboarding`,
877
899
  ORG_EDGE_PROFILES: (orgId: string, edgeId: string) =>
878
900
  `/api/v1/orgs/${orgId}/edge/${edgeId}/profiles`,
901
+ // Per-profile egress proxy (hosted Cloud Profiles; manager-only, human-only).
902
+ ORG_EDGE_PROFILE_PROXY: (orgId: string, edgeId: string, profileName: string) =>
903
+ `/api/v1/orgs/${orgId}/edge/${edgeId}/profiles/${encodeURIComponent(profileName)}/proxy`,
879
904
  ORG_EDGE_EXEC: (orgId: string) => `/api/v1/orgs/${orgId}/edge/exec`,
905
+ // Cloud Edge live viewer command (V1b) — api-server, gated on cap:edge-viewer.
906
+ // Same request/reply shape as the v2 browser-profile viewer, on the v3 edge
907
+ // pipe. Additive: does NOT replace BROWSER_PROFILE_VIEWER_COMMAND.
908
+ ORG_EDGE_VIEWER_COMMAND: (orgId: string, edgeId: string) =>
909
+ `/api/v1/orgs/${orgId}/edge/${edgeId}/viewer/command`,
880
910
  CLIP_CONNECTIONS: (orgId: string, clipId: string) =>
881
911
  `/api/v1/orgs/${orgId}/clip-registry/${clipId}/connections`,
882
912
  CLIP_CONNECTION: (orgId: string, connId: string) =>
@@ -941,6 +971,7 @@ export const WS_EVENTS = {
941
971
  MESSAGE_PATCH: 'message.patch',
942
972
  MESSAGE_EDIT: 'message.edit',
943
973
  MESSAGE_DELETE: 'message.delete',
974
+ MESSAGE_REACTION_UPDATED: 'message.reaction.updated',
944
975
  TYPING_UPDATE: 'typing.update',
945
976
  CHAT_UPDATE: 'chat.update',
946
977
  CHAT_DELETED: 'chat.deleted',
@@ -981,6 +1012,7 @@ export const WS_EVENTS = {
981
1012
  INBOX_UPDATE: 'inbox.update',
982
1013
  INBOX_BULK_UPDATE: 'inbox.bulk_update',
983
1014
  READ_POSITION_UPDATED: 'read_position.updated',
1015
+ THREAD_READ_POSITION_UPDATED: 'thread_read_position.updated',
984
1016
  DISPATCH_NEW: 'dispatch.new',
985
1017
  DISPATCH_RECEIVED: 'dispatch.received',
986
1018
  DISPATCH_RESOLVED: 'dispatch.resolved',