@opencxh/domain 1.84.0 → 1.86.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";
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,
@@ -294,6 +294,68 @@ export interface RemoteStreamEvent {
294
294
  speaker?: 'local' | 'remote';
295
295
  label?: string;
296
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
+ /**
336
+ * One ICE server entry. Structurally a subset of the browser's `RTCIceServer`,
337
+ * but kept DOM-free so the comm server can build it too (server tsconfig has no
338
+ * DOM lib). The client passes these straight to `new RTCPeerConnection`.
339
+ */
340
+ export interface IceServerConfig {
341
+ urls: string | string[];
342
+ username?: string;
343
+ credential?: string;
344
+ }
345
+ /**
346
+ * Response of `GET /webrtc/ice`: the STUN list plus TURN servers with
347
+ * short-lived (per-user, time-limited) credentials, and how long they last.
348
+ */
349
+ export interface IceConfigResponse {
350
+ iceServers: IceServerConfig[];
351
+ /** Seconds the TURN credentials remain valid. */
352
+ ttl: number;
353
+ }
354
+ /** What the callee receives over SSE — same envelope, server stamps `from`. */
355
+ export interface WebRtcSignalEvent extends WebRtcSignalRequest {
356
+ /** Sender userId, stamped by the relay from the authenticated subject. */
357
+ from: string;
358
+ }
297
359
  /**
298
360
  * Helper voor compactere intent-declaraties in `/provider/describe`.
299
361
  * 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
+ }
@@ -35,6 +35,25 @@ export interface FilePointer {
35
35
  createdAt?: number;
36
36
  tags?: string[];
37
37
  }
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;
49
+ }
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")). */
53
+ id: string;
54
+ displayName: string;
55
+ accountFields: StorageAccountField[];
56
+ }
38
57
  /**
39
58
  * A linked storage backend account (credentials to an S3 bucket, an FTP host, …).
40
59
  * Backed by the canonical ManagedAccount store, mirroring the AI app's account
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opencxh/domain",
3
- "version": "1.84.0",
3
+ "version": "1.86.0",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",