@opencxh/domain 1.83.0 → 1.85.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.
@@ -161,6 +161,8 @@ export type FileUploadedPayload = {
161
161
  fileUrl: string;
162
162
  fileType: string;
163
163
  size: number;
164
+ /** Storage FilePointer id, when the file lives in the storage app (download via storage.file.{id}.download). */
165
+ fileId?: string;
164
166
  };
165
167
  export type InteractionStatusChangedPayload = {
166
168
  fromStatus: "open" | "pending" | "closed";
@@ -1,3 +1,4 @@
1
+ import { OwnerScope } from '../contact/types';
1
2
  export interface PredefinedPrompt {
2
3
  name: string;
3
4
  prompt: string;
@@ -5,6 +6,12 @@ export interface PredefinedPrompt {
5
6
  export interface AIProfile<T extends Record<string, any> = Record<string, any>> {
6
7
  id: string;
7
8
  organizationId: string;
9
+ /**
10
+ * Who may use this profile. Company-wide (`org`) or bound to a `team`; profiles
11
+ * are never personal. Optional only during the migration window - the server
12
+ * defaults new profiles to `org` and backfills existing ones.
13
+ */
14
+ ownerScope?: OwnerScope;
8
15
  name: string;
9
16
  description?: string;
10
17
  accountId: string;
@@ -4,6 +4,15 @@ export interface ContactSearchQuery {
4
4
  text?: string;
5
5
  scope?: OwnerScope[];
6
6
  limit?: number;
7
+ /**
8
+ * Channels whose identities the search should cover. The composer passes the
9
+ * one acting channel; the intent starter passes every channel the user can
10
+ * act as. Providers search each identity they own among these and merge —
11
+ * e.g. Microsoft searches the relevant people of every shared mailbox in the
12
+ * list. Order matters: providers may cap the fan-out, so pass most-relevant
13
+ * first.
14
+ */
15
+ channelIds?: string[];
7
16
  }
8
17
  export interface ContactProvider {
9
18
  id: string;
package/dist/index.cjs CHANGED
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const l=140;function i(e){return e.replace(/<style[\s\S]*?<\/style>/gi," ").replace(/<script[\s\S]*?<\/script>/gi," ").replace(/<[^>]+>/g," ").replace(/&nbsp;/g," ").replace(/&amp;/g,"&").replace(/&lt;/g,"<").replace(/&gt;/g,">").replace(/&quot;/g,'"').replace(/&#39;/g,"'").replace(/\s+/g," ").trim()}function A(e){const t=e.trim();return t.length<=l?t:t.slice(0,l-1).trimEnd()+"…"}function E(e){if(!e||e<0)return"";const t=Math.floor(e/60),n=Math.floor(e%60);return`${t}:${n.toString().padStart(2,"0")}`}function I(e){switch(e.type){case"EMAIL_RECEIVED":case"EMAIL_SENT":{const t=e.payload,n=t.bodySnippet?.trim()||i(t.body??"");return t.subject?`${t.subject} — ${n}`:n}case"CHAT_MESSAGE_SENT":case"CHAT_MESSAGE_RECEIVED":return e.payload.text??"";case"CHAT_RENAMED":return`Hernoemd naar "${e.payload.newName}"`;case"CHAT_MEMBER_JOINED":return`${e.payload.members.map(t=>t.name).join(", ")} toegevoegd`;case"CHAT_MEMBER_LEFT":return`${e.payload.members.map(t=>t.name).join(", ")} verlaten`;case"CHAT_CALL_STARTED":return"Gesprek gestart";case"CHAT_CALL_ENDED":return`Gesprek beëindigd${e.payload.duration?` (${E(e.payload.duration)})`:""}`;case"CHAT_EVENT":return e.payload.text??e.payload.eventType;case"VOICE_CALL_STARTED":case"VIDEO_CALL_STARTED":return"Gesprek gestart";case"VOICE_CALL_ANSWERED":case"VIDEO_CALL_ANSWERED":return"Gesprek aangenomen";case"VOICE_CALL_HOLD":case"VIDEO_CALL_HOLD":return"In de wacht";case"VOICE_CALL_UNHOLD":case"VIDEO_CALL_UNHOLD":return"Hervat";case"VOICE_CALL_ENDED":case"VIDEO_CALL_ENDED":return`Gesprek beëindigd${e.payload.duration?` (${E(e.payload.duration)})`:""}`;case"VOICE_CALL_MISSED":case"VIDEO_CALL_MISSED":return"Gemiste oproep";case"VOICE_CALL_FAILED":case"VIDEO_CALL_FAILED":return"Gesprek mislukt";case"VOICE_CALL_VOICEMAIL":return e.payload.transcription?.trim()?e.payload.transcription:"Voicemail ontvangen";case"TRANSCRIPT_ADDED":return(e.payload.segments??[]).map(t=>t.text).join(" ");case"AI_MESSAGE_ADDED":return e.payload.output?.text??e.payload.input?.text??"";case"MEETING_SCHEDULED":return`Vergadering gepland: ${e.payload.title}`;case"MEETING_STARTED":return`Vergadering gestart: ${e.payload.title}`;case"MEETING_ENDED":return`Vergadering beëindigd${e.payload.duration?` (${E(e.payload.duration)})`:""}`;case"MEETING_PARTICIPANT_JOINED":return`${e.payload.name} deelgenomen`;case"MEETING_PARTICIPANT_LEFT":return`${e.payload.name} verlaten`;case"COMMENT_ADDED":return e.payload.text??"";case"FILE_UPLOADED":return`Bestand: ${e.payload.fileName}`;case"INTERACTION_CREATED":return"Interactie aangemaakt";case"INTERACTION_STATUS_CHANGED":return`Status: ${e.payload.fromStatus} → ${e.payload.toStatus}`;case"INTERACTION_ASSIGNED":return"Interactie toegewezen";default:return""}}function T(e){return{activityId:e.id,type:e.type,snippet:A(I(e)),authorName:e.author?.name??"",direction:e.direction,createdAt:e.createdAt??Date.now()}}function _(e,t){const n=e.settings?.signatures?.[t];return!n||!n.enabled||!n.body||n.body.trim()===""?null:n}function g(e){return e.endpoints??[]}const D=e=>!!e.assignedUserId||!!e.assignedInboxId,f=e=>e.status==="closed",S=e=>({urgent:10,high:5,normal:2,low:1})[e.priority]||0,R=(e,t=30)=>e.title?.length<=t?e.title:`${e.title.substring(0,t)}...`;function L(e,t){return e.lastActivityAt?!(e.seenBy??[]).includes(t):!1}function u(e,t){const n=new Set(e.disabledIntents??[]),r=e.intentOverrides??{},s=t.intents.filter(a=>!n.has(a.intent)).map(a=>C(a,r[a.intent]));if(!e.extraIntents||e.extraIntents.length===0)return s;const o=new Set(s.map(a=>a.intent));for(const a of e.extraIntents)o.has(a.intent)||(s.push(a),o.add(a.intent));return s}function c(e,t){const n={};for(const r of e.intents){if(!r.togglable)continue;const s=t?.[r.intent];n[r.intent]=s??r.defaultEnabled??!0}return n}function N(e,t){const n=c(e,t);return Object.entries(n).filter(([,r])=>!r).map(([r])=>r)}function C(e,t){return t?{intent:t.intent??e.intent,targetSchemes:t.targetSchemes??e.targetSchemes,transport:t.transport??e.transport}:e}function O(e,t){const n=[];for(const r of e){if(!r.enabled)continue;const s=t[r.providerId];if(s)for(const o of u(r,s))n.push({channel:r,description:s,capability:o})}return n}function M(e,t){return t.filter(n=>n.capability.intent===e)}function d(e,t){return t.filter(n=>n.capability.targetSchemes.includes(e))}function b(e,t){return d(e.scheme,t)}function P(e,t,n){const r=[];for(const s of e)for(const o of n)o.capability.intent===t&&o.capability.targetSchemes.includes(s.scheme)&&r.push({channelIntent:o,endpoint:s});return r}var p=(e=>(e.MAILTO="mailto",e.SIP="sip",e.TEL="tel",e.WEBHOOK="webhook",e.USERNAME="username",e.ID="id",e.CUSTOM="custom",e.URL="url",e.TELEGRAM="telegram",e.WHATSAPP="whatsapp",e.MESSENGER="messenger",e.INSTAGRAM="instagram",e.VIBER="viber",e.SMS="sms",e.FAX="fax",e.TEAMS="teams",e.CALENDAR="calendar",e))(p||{});const G="message_window",U="message_templates",V={FROM:"from",TO:"to",CC:"cc",BCC:"bcc",ORGANIZER:"organizer",PRESENTER:"presenter",ATTENDEE:"attendee",OWNER:"owner",MEMBER:"member",GUEST:"guest",HOST:"host",PARTICIPANT:"participant"},m=(e,t)=>({intent:e,...t}),y="folder_management",H="remote_search",h={ok:!1,code:"UNSUPPORTED",message:"Provider does not support this op"},$="installation-id";exports.CommunicationScheme=p;exports.FEATURE_FOLDER_MANAGEMENT=y;exports.FEATURE_REMOTE_SEARCH=H;exports.INSTALLATION_HEADER=$;exports.InteractionParticipantRole=V;exports.PROVIDER_FEATURE_MESSAGE_TEMPLATES=U;exports.PROVIDER_FEATURE_MESSAGE_WINDOW=G;exports.UNSUPPORTED=h;exports.buildActivityPreview=T;exports.buildChannelIntents=O;exports.defineIntent=m;exports.disabledIntentsFromCapabilities=N;exports.filterByEndpoint=b;exports.filterByIntent=M;exports.filterByTargetScheme=d;exports.getContactEndpoints=g;exports.getShortTitle=R;exports.getUrgencyScore=S;exports.isAssigned=D;exports.isClosed=f;exports.isInteractionUnseen=L;exports.matchContactToIntents=P;exports.resolveAccountCapabilities=c;exports.resolveChannelIntents=u;exports.resolveSignature=_;exports.stripHtml=i;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const l=140;function i(e){return e.replace(/<style[\s\S]*?<\/style>/gi," ").replace(/<script[\s\S]*?<\/script>/gi," ").replace(/<[^>]+>/g," ").replace(/&nbsp;/g," ").replace(/&amp;/g,"&").replace(/&lt;/g,"<").replace(/&gt;/g,">").replace(/&quot;/g,'"').replace(/&#39;/g,"'").replace(/\s+/g," ").trim()}function A(e){const t=e.trim();return t.length<=l?t:t.slice(0,l-1).trimEnd()+"…"}function o(e){if(!e||e<0)return"";const t=Math.floor(e/60),n=Math.floor(e%60);return`${t}:${n.toString().padStart(2,"0")}`}function I(e){switch(e.type){case"EMAIL_RECEIVED":case"EMAIL_SENT":{const t=e.payload,n=t.bodySnippet?.trim()||i(t.body??"");return t.subject?`${t.subject} — ${n}`:n}case"CHAT_MESSAGE_SENT":case"CHAT_MESSAGE_RECEIVED":return e.payload.text??"";case"CHAT_RENAMED":return`Hernoemd naar "${e.payload.newName}"`;case"CHAT_MEMBER_JOINED":return`${e.payload.members.map(t=>t.name).join(", ")} toegevoegd`;case"CHAT_MEMBER_LEFT":return`${e.payload.members.map(t=>t.name).join(", ")} verlaten`;case"CHAT_CALL_STARTED":return"Gesprek gestart";case"CHAT_CALL_ENDED":return`Gesprek beëindigd${e.payload.duration?` (${o(e.payload.duration)})`:""}`;case"CHAT_EVENT":return e.payload.text??e.payload.eventType;case"VOICE_CALL_STARTED":case"VIDEO_CALL_STARTED":return"Gesprek gestart";case"VOICE_CALL_ANSWERED":case"VIDEO_CALL_ANSWERED":return"Gesprek aangenomen";case"VOICE_CALL_HOLD":case"VIDEO_CALL_HOLD":return"In de wacht";case"VOICE_CALL_UNHOLD":case"VIDEO_CALL_UNHOLD":return"Hervat";case"VOICE_CALL_ENDED":case"VIDEO_CALL_ENDED":return`Gesprek beëindigd${e.payload.duration?` (${o(e.payload.duration)})`:""}`;case"VOICE_CALL_MISSED":case"VIDEO_CALL_MISSED":return"Gemiste oproep";case"VOICE_CALL_FAILED":case"VIDEO_CALL_FAILED":return"Gesprek mislukt";case"VOICE_CALL_VOICEMAIL":return e.payload.transcription?.trim()?e.payload.transcription:"Voicemail ontvangen";case"TRANSCRIPT_ADDED":return(e.payload.segments??[]).map(t=>t.text).join(" ");case"AI_MESSAGE_ADDED":return e.payload.output?.text??e.payload.input?.text??"";case"MEETING_SCHEDULED":return`Vergadering gepland: ${e.payload.title}`;case"MEETING_STARTED":return`Vergadering gestart: ${e.payload.title}`;case"MEETING_ENDED":return`Vergadering beëindigd${e.payload.duration?` (${o(e.payload.duration)})`:""}`;case"MEETING_PARTICIPANT_JOINED":return`${e.payload.name} deelgenomen`;case"MEETING_PARTICIPANT_LEFT":return`${e.payload.name} verlaten`;case"COMMENT_ADDED":return e.payload.text??"";case"FILE_UPLOADED":return`Bestand: ${e.payload.fileName}`;case"INTERACTION_CREATED":return"Interactie aangemaakt";case"INTERACTION_STATUS_CHANGED":return`Status: ${e.payload.fromStatus} → ${e.payload.toStatus}`;case"INTERACTION_ASSIGNED":return"Interactie toegewezen";default:return""}}function _(e){return{activityId:e.id,type:e.type,snippet:A(I(e)),authorName:e.author?.name??"",direction:e.direction,createdAt:e.createdAt??Date.now()}}function T(e,t){const n=e.settings?.signatures?.[t];return!n||!n.enabled||!n.body||n.body.trim()===""?null:n}function g(e){return e.endpoints??[]}const D=e=>!!e.assignedUserId||!!e.assignedInboxId,S=e=>e.status==="closed",f=e=>({urgent:10,high:5,normal:2,low:1})[e.priority]||0,N=(e,t=30)=>e.title?.length<=t?e.title:`${e.title.substring(0,t)}...`;function R(e,t){return e.lastActivityAt?!(e.seenBy??[]).includes(t):!1}function u(e,t){const n=new Set(e.disabledIntents??[]),r=e.intentOverrides??{},s=t.intents.filter(a=>!n.has(a.intent)).map(a=>O(a,r[a.intent]));if(!e.extraIntents||e.extraIntents.length===0)return s;const E=new Set(s.map(a=>a.intent));for(const a of e.extraIntents)E.has(a.intent)||(s.push(a),E.add(a.intent));return s}function c(e,t){const n={};for(const r of e.intents){if(!r.togglable)continue;const s=t?.[r.intent];n[r.intent]=s??r.defaultEnabled??!0}return n}function L(e,t){const n=c(e,t);return Object.entries(n).filter(([,r])=>!r).map(([r])=>r)}function O(e,t){return t?{intent:t.intent??e.intent,targetSchemes:t.targetSchemes??e.targetSchemes,transport:t.transport??e.transport}:e}function C(e,t){const n=[];for(const r of e){if(!r.enabled)continue;const s=t[r.providerId];if(s)for(const E of u(r,s))n.push({channel:r,description:s,capability:E})}return n}function M(e,t){return t.filter(n=>n.capability.intent===e)}function d(e,t){return t.filter(n=>n.capability.targetSchemes.includes(e))}function b(e,t){return d(e.scheme,t)}function P(e,t,n){const r=[];for(const s of e)for(const E of n)E.capability.intent===t&&E.capability.targetSchemes.includes(s.scheme)&&r.push({channelIntent:E,endpoint:s});return r}var p=(e=>(e.MAILTO="mailto",e.SIP="sip",e.TEL="tel",e.WEBHOOK="webhook",e.USERNAME="username",e.ID="id",e.CUSTOM="custom",e.URL="url",e.TELEGRAM="telegram",e.WHATSAPP="whatsapp",e.MESSENGER="messenger",e.INSTAGRAM="instagram",e.VIBER="viber",e.SMS="sms",e.FAX="fax",e.TEAMS="teams",e.CALENDAR="calendar",e))(p||{});const G="message_window",U="message_templates",V={FROM:"from",TO:"to",CC:"cc",BCC:"bcc",ORGANIZER:"organizer",PRESENTER:"presenter",ATTENDEE:"attendee",OWNER:"owner",MEMBER:"member",GUEST:"guest",HOST:"host",PARTICIPANT:"participant"},m=(e,t)=>({intent:e,...t}),y="folder_management",H="remote_search",h={ok:!1,code:"UNSUPPORTED",message:"Provider does not support this op"},$=9e4,F="installation-id";exports.CommunicationScheme=p;exports.FEATURE_FOLDER_MANAGEMENT=y;exports.FEATURE_REMOTE_SEARCH=H;exports.INSTALLATION_HEADER=F;exports.InteractionParticipantRole=V;exports.PRESENCE_ONLINE_WINDOW_MS=$;exports.PROVIDER_FEATURE_MESSAGE_TEMPLATES=U;exports.PROVIDER_FEATURE_MESSAGE_WINDOW=G;exports.UNSUPPORTED=h;exports.buildActivityPreview=_;exports.buildChannelIntents=C;exports.defineIntent=m;exports.disabledIntentsFromCapabilities=L;exports.filterByEndpoint=b;exports.filterByIntent=M;exports.filterByTargetScheme=d;exports.getContactEndpoints=g;exports.getShortTitle=N;exports.getUrgencyScore=f;exports.isAssigned=D;exports.isClosed=S;exports.isInteractionUnseen=R;exports.matchContactToIntents=P;exports.resolveAccountCapabilities=c;exports.resolveChannelIntents=u;exports.resolveSignature=T;exports.stripHtml=i;
package/dist/index.d.ts CHANGED
@@ -44,5 +44,6 @@ export * from './platform/ui';
44
44
  export * from './platform/media';
45
45
  export * from './platform/provider';
46
46
  export * from './platform/push';
47
+ export * from './platform/presence';
47
48
  export * from './platform/installation';
48
49
  export * from './platform/storage';
package/dist/index.js CHANGED
@@ -100,7 +100,7 @@ function T(e, t) {
100
100
  function g(e) {
101
101
  return e.endpoints ?? [];
102
102
  }
103
- const f = (e) => !!e.assignedUserId || !!e.assignedInboxId, D = (e) => e.status === "closed", L = (e) => ({
103
+ const D = (e) => !!e.assignedUserId || !!e.assignedInboxId, f = (e) => e.status === "closed", L = (e) => ({
104
104
  urgent: 10,
105
105
  high: 5,
106
106
  normal: 2,
@@ -137,7 +137,7 @@ function A(e, t) {
137
137
  transport: t.transport ?? e.transport
138
138
  } : e;
139
139
  }
140
- function C(e, t) {
140
+ function O(e, t) {
141
141
  const n = [];
142
142
  for (const r of e) {
143
143
  if (!r.enabled) continue;
@@ -148,7 +148,7 @@ function C(e, t) {
148
148
  }
149
149
  return n;
150
150
  }
151
- function O(e, t) {
151
+ function C(e, t) {
152
152
  return t.filter((n) => n.capability.intent === e);
153
153
  }
154
154
  function i(e, t) {
@@ -182,28 +182,29 @@ const P = "message_window", V = "message_templates", G = {
182
182
  // Voice/conference
183
183
  HOST: "host",
184
184
  PARTICIPANT: "participant"
185
- }, H = (e, t) => ({ intent: e, ...t }), $ = "folder_management", y = "remote_search", U = { ok: !1, code: "UNSUPPORTED", message: "Provider does not support this op" }, m = "installation-id";
185
+ }, H = (e, t) => ({ intent: e, ...t }), $ = "folder_management", y = "remote_search", U = { ok: !1, code: "UNSUPPORTED", message: "Provider does not support this op" }, m = 9e4, h = "installation-id";
186
186
  export {
187
187
  I as CommunicationScheme,
188
188
  $ as FEATURE_FOLDER_MANAGEMENT,
189
189
  y as FEATURE_REMOTE_SEARCH,
190
- m as INSTALLATION_HEADER,
190
+ h as INSTALLATION_HEADER,
191
191
  G as InteractionParticipantRole,
192
+ m as PRESENCE_ONLINE_WINDOW_MS,
192
193
  V as PROVIDER_FEATURE_MESSAGE_TEMPLATES,
193
194
  P as PROVIDER_FEATURE_MESSAGE_WINDOW,
194
195
  U as UNSUPPORTED,
195
196
  _ as buildActivityPreview,
196
- C as buildChannelIntents,
197
+ O as buildChannelIntents,
197
198
  H as defineIntent,
198
199
  R as disabledIntentsFromCapabilities,
199
200
  M as filterByEndpoint,
200
- O as filterByIntent,
201
+ C as filterByIntent,
201
202
  i as filterByTargetScheme,
202
203
  g as getContactEndpoints,
203
204
  N as getShortTitle,
204
205
  L as getUrgencyScore,
205
- f as isAssigned,
206
- D as isClosed,
206
+ D as isAssigned,
207
+ f as isClosed,
207
208
  S as isInteractionUnseen,
208
209
  b as matchContactToIntents,
209
210
  c as resolveAccountCapabilities,
@@ -5,6 +5,13 @@ export interface Session {
5
5
  providerId: string;
6
6
  direction: 'inbound' | 'outbound';
7
7
  remoteIdentity: string;
8
+ /**
9
+ * Number of remote participants, when the provider can report it. Used by
10
+ * transcript labeling: with more than one remote party the audio is a single
11
+ * mixed stream, so `remoteIdentity` can't be trusted to name the speaker and
12
+ * the label falls back to a generic one. Absent/1 = single remote party.
13
+ */
14
+ remoteParticipantCount?: number;
8
15
  state: SessionState;
9
16
  startTime?: number;
10
17
  hasVideo: boolean;
@@ -287,6 +294,49 @@ export interface RemoteStreamEvent {
287
294
  speaker?: 'local' | 'remote';
288
295
  label?: string;
289
296
  }
297
+ /**
298
+ * Peer-to-peer WebRTC signaling envelope. Exchanged between two users in the
299
+ * same tenant to negotiate a direct call — the media (audio) flows P2P; only
300
+ * these small SDP/ICE setup messages transit the server (relayed 1:1 via
301
+ * `Bridge.sse.push`). Transient: never persisted.
302
+ *
303
+ * - `offer`/`answer` → `data` is an `RTCSessionDescriptionInit`.
304
+ * - `ice` → `data` is an `RTCIceCandidateInit` (or `null` = end-of-candidates).
305
+ * - `bye` → `data` omitted; the sender tore the call down.
306
+ */
307
+ export type WebRtcSignalKind = "offer" | "answer" | "ice" | "bye" | "request";
308
+ /** Request body the initiator/answerer POSTs to the signaling relay. */
309
+ export interface WebRtcSignalRequest {
310
+ /** Target userId (the peer this signal is meant for). */
311
+ to: string;
312
+ /** Stable call id, minted by the caller on invite. */
313
+ sessionId: string;
314
+ kind: WebRtcSignalKind;
315
+ data?: unknown;
316
+ /** Sender display name, so the peer can label the call UI without a lookup. */
317
+ fromName?: string;
318
+ /**
319
+ * Which peer connection this signal belongs to. `call` (default) is an audio
320
+ * session handled by the transport provider; `file` is an AirDrop-style
321
+ * RTCDataChannel transfer handled by the file-beam service; `monitor` is a
322
+ * one-way "listen along" forward handled by the call-monitor service. Lets
323
+ * one relay serve all three without the connection types colliding.
324
+ */
325
+ channel?: "call" | "file" | "monitor";
326
+ }
327
+ /**
328
+ * Broadcast (event `webrtc:callstate`) telling the tenant whether a user is
329
+ * currently on a call, so a supervisor can see who is available to listen in.
330
+ */
331
+ export interface CallStateChange {
332
+ userId: string;
333
+ inCall: boolean;
334
+ }
335
+ /** What the callee receives over SSE — same envelope, server stamps `from`. */
336
+ export interface WebRtcSignalEvent extends WebRtcSignalRequest {
337
+ /** Sender userId, stamped by the relay from the authenticated subject. */
338
+ from: string;
339
+ }
290
340
  /**
291
341
  * Helper voor compactere intent-declaraties in `/provider/describe`.
292
342
  * defineIntent("mail", { targetSchemes: [MAILTO], transport: { kind: "dispatch" } })
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Presence — derived, not stored as its own entity. Online status is computed
3
+ * from `InstallationEntity.lastSeenAt`: a user is online when any of their
4
+ * non-revoked installations was seen within the freshness window. A light
5
+ * client heartbeat (~45s while the WebSocket is connected) keeps `lastSeenAt`
6
+ * fresh enough to reflect real reachability for calling.
7
+ */
8
+ /** How recently a user's last device activity must be to count as online (ms). */
9
+ export declare const PRESENCE_ONLINE_WINDOW_MS = 90000;
10
+ /** One tenant user with their derived online status, as returned by GET /presence. */
11
+ export interface PresenceEntry {
12
+ userId: string;
13
+ name?: string;
14
+ email?: string;
15
+ /** Max `lastSeenAt` across the user's non-revoked installations (ms epoch). */
16
+ lastSeenAt: number;
17
+ /** `now - lastSeenAt < PRESENCE_ONLINE_WINDOW_MS` at the time of the read. */
18
+ online: boolean;
19
+ }
20
+ /** SSE payload pushed on an offline->online transition (event `presence:changed`). */
21
+ export interface PresenceChange {
22
+ userId: string;
23
+ online: boolean;
24
+ lastSeenAt: number;
25
+ }
@@ -1,10 +1,26 @@
1
+ import { OwnerScope } from '../entities/contact/types';
2
+ /**
3
+ * A virtual folder: a named, scoped root that binds to a storage backend
4
+ * (a StorageAccount on a storage provider) at a base path. Its `ownerScope`
5
+ * decides who can see and use it (personal / team / org). Files live inside it.
6
+ *
7
+ * Kept named `VirtualMount` for continuity with existing consumers (ui-kit
8
+ * StorageInput, platform-api); surfaced to users as a "folder".
9
+ */
1
10
  export interface VirtualMount {
2
11
  id: string;
12
+ organizationId?: string;
3
13
  name: string;
14
+ /** Storage provider id (e.g. base64("s3:storage")). */
4
15
  providerId: string;
16
+ /** StorageAccount id backing this folder. Falls back to the provider default when omitted. */
17
+ accountId?: string;
5
18
  basePath: string;
6
- policyId: string;
19
+ /** Who may see and use this folder. */
20
+ ownerScope: OwnerScope;
7
21
  metadata?: Record<string, any>;
22
+ createdAt?: number;
23
+ updatedAt?: number;
8
24
  }
9
25
  export interface FilePointer {
10
26
  id: string;
@@ -19,15 +35,38 @@ export interface FilePointer {
19
35
  createdAt?: number;
20
36
  tags?: string[];
21
37
  }
22
- export type FileAction = 'file:Read' | 'file:Write' | 'file:Delete' | 'file:List' | 'file:Share';
23
- export interface FilePolicyStatement {
24
- effect: 'Allow' | 'Deny';
25
- principal: string[];
26
- action: FileAction[];
27
- resource: string[];
38
+ /**
39
+ * A field a storage provider needs to connect an account. Providers declare
40
+ * these (via /provider/describe); the accounts UI renders the form from them,
41
+ * so a new backend needs no UI changes.
42
+ */
43
+ export interface StorageAccountField {
44
+ name: string;
45
+ label: string;
46
+ type: "text" | "password" | "number" | "checkbox";
47
+ required?: boolean;
48
+ placeholder?: string;
28
49
  }
29
- export interface FilePolicy {
50
+ /** A connectable storage provider + the fields needed to connect an account. */
51
+ export interface StorageProviderInfo {
52
+ /** Storage-role provider id (e.g. base64("ftp:storage")). */
30
53
  id: string;
31
- name: string;
32
- statements: FilePolicyStatement[];
54
+ displayName: string;
55
+ accountFields: StorageAccountField[];
56
+ }
57
+ /**
58
+ * A linked storage backend account (credentials to an S3 bucket, an FTP host, …).
59
+ * Backed by the canonical ManagedAccount store, mirroring the AI app's account
60
+ * pattern. Secrets never reach the UI — this is the public view.
61
+ */
62
+ export interface StorageAccount {
63
+ id: string;
64
+ organizationId: string;
65
+ /** Account-provider id, e.g. base64("s3:account"). */
66
+ providerId: string;
67
+ displayName: string;
68
+ /** Whether this is the org's default account for its provider. */
69
+ isDefault?: boolean;
70
+ /** Provider-specific, non-secret fields (region, bucket, basePath, …). */
71
+ metadata?: Record<string, any>;
33
72
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opencxh/domain",
3
- "version": "1.83.0",
3
+ "version": "1.85.0",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",