@opencxh/domain 1.126.0 → 1.128.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.
@@ -1 +1,2 @@
1
1
  export * from './types';
2
+ export * from './vendors';
@@ -3,6 +3,12 @@ export interface AiAccount {
3
3
  organizationId: string;
4
4
  vendor: string;
5
5
  apiKey: string;
6
+ /**
7
+ * API-host voor dit account. Alleen in te vullen bij vendors zonder vaste
8
+ * host (self-hosted: Ollama, vLLM, een eigen gateway) — bij de rest komt de
9
+ * host uit `AI_VENDORS` en wordt dit veld genegeerd.
10
+ */
11
+ baseUrl?: string;
6
12
  isDefaultLlm?: boolean;
7
13
  isDefaultStt?: boolean;
8
14
  isDefaultTts?: boolean;
@@ -0,0 +1,47 @@
1
+ /**
2
+ * De AI-vendors waar de ai-app een adapter voor heeft.
3
+ *
4
+ * Eén bron voor zowel de UI (accountformulier, tabellabels) als de server
5
+ * (default-model, API-host). Eerder stonden die lijsten los van elkaar en
6
+ * liepen ze uiteen — de UI noemde vendors waarvoor geen adapter bestond.
7
+ *
8
+ * Deze catalogus beschrijft *wat* een vendor is. *Hoe* je ertegen praat (welke
9
+ * adapterklasse, welk model-filter) staat server-side in `providers/registry`,
10
+ * die zich tegen deze lijst verantwoordt: een vendor die hier `"chat"` claimt
11
+ * en daar geen chat-adapter heeft, is een testfout.
12
+ *
13
+ * `id` is wat er in `AiAccount.vendor` belandt; die string is dus onderdeel van
14
+ * het opslagformaat en mag niet zomaar hernoemd worden.
15
+ */
16
+ export type AiVendorCapability = "chat" | "transcription" | "embedding";
17
+ export interface AiVendor {
18
+ id: string;
19
+ label: string;
20
+ /** Wat deze vendor kan. Bepaalt waar hij in de UI als optie verschijnt. */
21
+ capabilities: readonly AiVendorCapability[];
22
+ /**
23
+ * Model waarmee een aanroep draait die zelf geen model meekrijgt. Afwezig
24
+ * voor vendors zonder chat-adapter.
25
+ */
26
+ defaultModel?: string;
27
+ /**
28
+ * Vaste API-host. Afwezig betekent dat de user er zelf een moet invullen
29
+ * op het account (`AiAccount.baseUrl`) — dat is het geval bij self-hosted
30
+ * runtimes en eigen gateways.
31
+ */
32
+ baseUrl?: string;
33
+ }
34
+ export declare const AI_VENDORS: readonly AiVendor[];
35
+ export declare function findAiVendor(vendorId: string): AiVendor | undefined;
36
+ /** Leesbare naam voor een opgeslagen vendor-id; valt terug op de id zelf. */
37
+ export declare function aiVendorLabel(vendorId: string): string;
38
+ /**
39
+ * Default-model voor een vendor. De fallback is bewust een bestaand model en
40
+ * geen lege string: callers gebruiken dit juist omdat ze zelf geen keuze
41
+ * hebben.
42
+ */
43
+ export declare function aiVendorDefaultModel(vendorId: string, fallback?: string): string;
44
+ /** Vendors die deze capability aanbieden — voor capability-gefilterde keuzelijsten. */
45
+ export declare function aiVendorsWith(capability: AiVendorCapability): AiVendor[];
46
+ /** True wanneer de user zelf een host moet invullen (vendor heeft er geen vaste). */
47
+ export declare function aiVendorNeedsBaseUrl(vendorId: string): boolean;
@@ -5,3 +5,4 @@ export * from './source';
5
5
  export * from './report';
6
6
  export * from './dashboard';
7
7
  export * from './period';
8
+ export * from './usage';
@@ -0,0 +1,56 @@
1
+ import { MetricDefinition } from './metric';
2
+ /**
3
+ * Gedeeld vocabulaire voor verbruik — wat een app aan externe middelen opmaakt.
4
+ *
5
+ * Bewust géén centrale usage-tabel. Apps bezitten hun eigen ruwe rijen (dat is
6
+ * hoe de rest van de analytics-keten al werkt) en rollen die zelf op. Een
7
+ * centrale tabel zou apps die de data al hebben — een voip-app heeft z'n
8
+ * gesprekken al staan — dwingen alles dubbel weg te schrijven.
9
+ *
10
+ * Wat hier wél gedeeld wordt is de *taal*: dezelfde assen en dezelfde
11
+ * metric-naamgeving, zodat AI-tokens en belminuten naast elkaar in één
12
+ * dashboard passen en je ze op `provider` tegen elkaar kunt afzetten.
13
+ *
14
+ * Een app die verbruik wil rapporteren:
15
+ * 1. rolt z'n eigen rijen op met `FactAccumulator` (platform-api)
16
+ * 2. emit op de assen hieronder + de core-dimensie `provider`
17
+ * 3. declareert `usageMetrics("voip", [{ unit: "call_minutes", ... }])`
18
+ * in z'n `/analytics/describe`
19
+ */
20
+ /**
21
+ * Waar het verbruik in wordt uitgedrukt. Open string — een app mag een eigen
22
+ * eenheid declareren; deze staan hier omdat ze naar verwachting terugkomen.
23
+ */
24
+ export type UsageUnit = "input_tokens" | "output_tokens" | "cached_input_tokens" | "requests" | "seconds" | "bytes" | (string & {});
25
+ /**
26
+ * Verbruiksdimensies die meerdere apps delen. Aanvullend op de core-dimensie
27
+ * `provider` (welke leverancier), die hiervoor al bestond.
28
+ *
29
+ * `resource` — het specifieke ding dat verbruikt is: een model-id bij AI, een
30
+ * lijn of nummer bij voip, een bucket bij opslag.
31
+ * `origin` — de functie die het verbruik veroorzaakte: een gesprek, een
32
+ * playbook, een samenvatting. Beantwoordt "waar gaat het geld heen", wat een
33
+ * andere vraag is dan "bij wie".
34
+ */
35
+ export declare const USAGE_DIMENSIONS: {
36
+ id: string;
37
+ label: string;
38
+ labelSource: "raw";
39
+ }[];
40
+ export declare const USAGE_DIMENSION_IDS: string[];
41
+ /** Metric-id volgens de gedeelde conventie: `<app>.usage.<unit>`. */
42
+ export declare function usageMetricId(app: string, unit: UsageUnit): string;
43
+ export interface UsageMetricSpec {
44
+ unit: UsageUnit;
45
+ label: string;
46
+ /** Default `count` — tokens, requests en bytes zijn allemaal tellingen. */
47
+ valueType?: MetricDefinition["valueType"];
48
+ /** Extra assen bovenop provider/resource/origin/time. */
49
+ extraDimensions?: string[];
50
+ }
51
+ /**
52
+ * Bouwt de `MetricDefinition`s voor een app z'n verbruiksmetrics, met overal
53
+ * dezelfde assen. Scheelt elke app dezelfde zes regels boilerplate en zorgt dat
54
+ * de definities niet per app uiteenlopen.
55
+ */
56
+ export declare function usageMetrics(app: string, specs: UsageMetricSpec[]): MetricDefinition[];
@@ -0,0 +1,19 @@
1
+ import { MemorySubjectKey } from './item';
2
+ /**
3
+ * Twee subjects die hetzelfde blijken te zijn (een merge van interacties, later een echte
4
+ * identiteitslaag). Het geheugen verhuist niet: de alias wordt bij het lezen opgelost.
5
+ *
6
+ * **Eén niveau diep by construction**: een alias wiens `canonical` zelf een alias is wordt
7
+ * geweigerd bij het schrijven. Resolutie is daarmee twee queries en nooit recursief — geen
8
+ * cyclus-detectie, geen diepte-limiet, geen verrassing op het hete pad.
9
+ */
10
+ export interface MemorySubjectAlias {
11
+ id: string;
12
+ organizationId: string;
13
+ /** Het subject dat is opgegaan in `canonical`. Unique per org. */
14
+ subject: MemorySubjectKey;
15
+ canonical: MemorySubjectKey;
16
+ /** De app die de samenvoeging meldde. */
17
+ source: string;
18
+ createdAt?: number;
19
+ }
@@ -0,0 +1,5 @@
1
+ export * from './alias';
2
+ export * from './ingest';
3
+ export * from './item';
4
+ export * from './kind';
5
+ export * from './query';
@@ -0,0 +1,61 @@
1
+ import { MemoryKindId, MemoryLink, MemorySubjectKey } from './item';
2
+ /**
3
+ * Schrijven is een **CAS-upsert**, geen snapshot-replace zoals analytics.
4
+ *
5
+ * Analytics mag een hele periode weggooien en opnieuw wegschrijven omdat een fact
6
+ * herberekenbaar is uit de bronstore. Een delta-gevouwen verhaal is de opgetelde uitkomst
7
+ * van N modelcalls en is dat niet. Bovendien is remove+insert niet atomair: een lezer kan
8
+ * "geen item" observeren op een subject dat wél een verhaal heeft.
9
+ */
10
+ export interface MemoryUpsertRequest {
11
+ organizationId: string;
12
+ /** De schrijvende app; de hub vult dit uit `req.source.app` en vertrouwt de body hier niet. */
13
+ source: string;
14
+ subject: MemorySubjectKey;
15
+ kind: MemoryKindId;
16
+ /** Default bij `cardinality: "single"`: `"<kind>:<subject>"`. */
17
+ sourceRef?: string;
18
+ title: string;
19
+ body: string;
20
+ occurredAt?: number;
21
+ /** Watermerk van het bron-event dat deze schrijfactie veroorzaakte. */
22
+ throughRef?: string;
23
+ throughAt?: number;
24
+ /** Mismatch => `accepted: false` met `reason: "stale-version"`; de caller herplant. */
25
+ expectedVersion?: number;
26
+ audienceRef?: string;
27
+ locale?: string;
28
+ /** Weglaten: de hub extraheert keywords zelf uit titel + body (één tokenizer, schrijf én lees). */
29
+ keywords?: string[];
30
+ tags?: string[];
31
+ links?: MemoryLink[];
32
+ }
33
+ export type MemoryUpsertRejection = "stale-version" | "stale-watermark" | "unknown-kind" | "kind-disabled" | "subject-kind-mismatch" | "not-owner";
34
+ export interface MemoryUpsertResult {
35
+ accepted: boolean;
36
+ /** De versie zoals die nu in de store staat — ook bij een afwijzing, zodat de caller kan herplannen. */
37
+ version: number;
38
+ itemId?: string;
39
+ reason?: MemoryUpsertRejection;
40
+ }
41
+ /**
42
+ * Expliciet destructief: gooit alle items van (subject, kind) van deze bron weg en zet
43
+ * `items` ervoor terug. Alleen voor **herberekenbare** kinds, bv. een nachtelijke re-fold.
44
+ */
45
+ export interface MemoryReplaceRequest {
46
+ organizationId: string;
47
+ source: string;
48
+ subject: MemorySubjectKey;
49
+ kind: MemoryKindId;
50
+ items: MemoryUpsertRequest[];
51
+ }
52
+ /**
53
+ * Bron-gestuurd wissen. De **bron** pusht dit; de hub interpreteert geen deletes: de
54
+ * mapping van een verwijderd contact naar zijn interacties leeft in comms, en
55
+ * `contacts:deleted` publiceert alleen `{ id }` — zonder `organizationId`.
56
+ */
57
+ export interface MemoryPurgeRequest {
58
+ organizationId: string;
59
+ source: string;
60
+ subjects: MemorySubjectKey[];
61
+ }
@@ -0,0 +1,77 @@
1
+ /**
2
+ * De geheugenlaag (`apps/context`): één durende laag per **subject**, waarbij de
3
+ * organisatie zelf per event bepaalt wat er onthouden wordt.
4
+ *
5
+ * `Memory*` en niet `Context*`: `platform/context.ts` exporteert al `HostContext` en
6
+ * `context.collect` (vluchtige route-context) uit dezelfde platte barrel.
7
+ */
8
+ /** Een bestaande scopeKey: `interaction:i_1`, `contact:c_9`. Een latere identiteitslaag is gewoon een ander prefix. */
9
+ export type MemorySubjectKey = string;
10
+ /** `comms.resolution` (app-gedeclareerd) of `sales_besluitvormer` (org-eigen). */
11
+ export type MemoryKindId = string;
12
+ /**
13
+ * Een verwijzing naar iets anders — intern (`interaction:i_2`) of extern
14
+ * (`hubspot:5591`, `clickup:86ab2`).
15
+ *
16
+ * Een **lijst**, nooit een object met de relatie als key: de app-sdk http-client
17
+ * mangelt elke object-key camel<->snake, dus `{ "crm.company": "..." }` komt verminkt
18
+ * aan de andere kant aan.
19
+ */
20
+ export interface MemoryLink {
21
+ rel: string;
22
+ target: string;
23
+ }
24
+ /** Harde grens op de body. Geheugen is een samenvatting; een mailbox-schaduwkopie is het niet. */
25
+ export declare const MAX_MEMORY_CHARS = 2000;
26
+ export interface MemoryItem {
27
+ id: string;
28
+ organizationId: string;
29
+ subject: MemorySubjectKey;
30
+ kind: MemoryKindId;
31
+ title: string;
32
+ /** DE inhoud: markdown, begrensd op {@link MAX_MEMORY_CHARS}. Nooit een genest object. */
33
+ body: string;
34
+ /** Schrijvende app (== `req.source.app`); playbook-schrijfacties komen binnen als "ai". */
35
+ source: string;
36
+ /** Identiteit binnen (subject, kind) voor de bron. Bij `cardinality: "single"` de vouw-sleutel. */
37
+ sourceRef: string;
38
+ occurredAt: number;
39
+ /**
40
+ * Watermerk: tot en met welk bron-event dit item gevouwen is. Maakt dubbele events en
41
+ * retries gratis — een oudere `throughAt` wordt afgewezen in plaats van overschreven.
42
+ */
43
+ throughRef?: string;
44
+ throughAt?: number;
45
+ /** Optimistic concurrency; de hub doet compare-and-swap op dit veld. */
46
+ version: number;
47
+ /** Overgenomen uit de kind-definitie, niet uit de call — zie `MemoryKindDefinition.visibility`. */
48
+ visibility: MemoryVisibility;
49
+ /**
50
+ * Zichtbaarheids-ANKER (inbox-id / OwnerScope-sleutel), **geen ledenlijst**: een
51
+ * gesnapshotte ledenlijst veroudert bij elke inbox-join en reassignment, en
52
+ * stale-permissive is een lek.
53
+ */
54
+ audienceRef?: string;
55
+ /** Cosine over taalgrenzen wordt geweigerd; de skip wordt geteld, niet verzwegen. */
56
+ locale: string;
57
+ /** Gedenormaliseerde termen: het enige selectieve structurele filter (er is geen substring-operator). */
58
+ keywords?: string[];
59
+ /** Snelle alternatieve sleutels, bv. `tel:+31612345678` voor het rinkelmoment. */
60
+ tags?: string[];
61
+ links?: MemoryLink[];
62
+ /** Model waarmee de vector is gemaakt; retrieval vergelijkt alleen binnen hetzelfde model. */
63
+ embeddingModel?: string;
64
+ /** Uit een live pull (fase 3); nooit gepersisteerd. */
65
+ live?: boolean;
66
+ createdAt?: number;
67
+ updatedAt?: number;
68
+ }
69
+ /**
70
+ * `"audience"` = alleen voor wie het subject mag zien. `"org"` = org-breed leesbaar en
71
+ * daarmee vindbaar in het cross-subject-pad ("is dit eerder bij een andere klant
72
+ * voorgekomen?").
73
+ *
74
+ * Per **kind** gedeclareerd en niet per item: het cross-subject-pad kan zich geen
75
+ * autorisatie-round-trip per hit veroorloven, dus het filtert structureel op deze kolom.
76
+ */
77
+ export type MemoryVisibility = "audience" | "org";
@@ -0,0 +1,66 @@
1
+ import { LocaleBundle } from '../analytics/dashboard';
2
+ import { OwnerScope } from '../contact/types';
3
+ import { MemoryKindId, MemoryVisibility } from './item';
4
+ /**
5
+ * Wat er onthouden mág worden, en met welke regels.
6
+ *
7
+ * Twee bronnen, één type:
8
+ * - **app-gedeclareerd** via `GET /provider/memory/describe` (de ingebouwde defaults);
9
+ * - **org-eigen** als rij in `memory_kind`. Mal: `CustomFieldDef` — de organisatie
10
+ * definieert de sleutel, een generieke store bewaart de waarde.
11
+ *
12
+ * `ownerScope` is wat "iedereen wil zijn eigen relevante data" mogelijk maakt: sales legt
13
+ * andere dingen vast dan support, en een persoonlijk kind is alleen van die gebruiker.
14
+ */
15
+ export interface MemoryKindDefinition {
16
+ /** Org-eigen: `[a-z0-9_]+` (geen punt — die leest als een genest form-pad). App-eigen: `<app>.<naam>`. */
17
+ id: MemoryKindId;
18
+ label: string;
19
+ description?: string;
20
+ /** Subject-soorten waarop dit kind mag landen, bv. `["interaction"]` of `["contact"]`. */
21
+ subjectKinds: string[];
22
+ visibility: MemoryVisibility;
23
+ /** `"single"` = één item per (subject, kind), doorvouwen. `"many"` = losse items. */
24
+ cardinality: "single" | "many";
25
+ /** Ordening onder het tekenbudget van de bundel. */
26
+ priority: number;
27
+ /** Mag dit kind in het cross-subject-pad meedoen? Alleen zinvol bij `visibility: "org"`. */
28
+ crossSubjectSearchable: boolean;
29
+ /** Embedden kost geld; per kind aan/uit. */
30
+ embed: boolean;
31
+ retentionDays?: number;
32
+ /** Rang-multiplier: een opgeloste casus verslaat een losse notitie. Default 1. */
33
+ weight?: number;
34
+ /**
35
+ * Wie dit kind bezit. Bepaalt zowel wie mag schrijven als de **ordening** voor een
36
+ * lezer (eigen team eerst). Afwezig = de hele organisatie.
37
+ */
38
+ ownerScope?: OwnerScope;
39
+ /** Wie hem declareerde. De org kan een app-kind niet oprekken, alleen uitzetten. */
40
+ origin: "app" | "org";
41
+ enabled: boolean;
42
+ }
43
+ /**
44
+ * Bare payload van `GET /provider/memory/describe` — **niet** in `ResponseFactory`
45
+ * verpakt (mal: `AnalyticsSourceDescription`).
46
+ */
47
+ export interface MemorySourceDescription {
48
+ /** De declarerende app (== `manifest.name` == `req.source.app`). */
49
+ source: string;
50
+ kinds: MemoryKindDefinition[];
51
+ /** Vlakke LIJST: keys met een punt worden gemangeld. */
52
+ locales?: LocaleBundle;
53
+ /** Fase 3: welke subjects/kinds deze bron live kan leveren. */
54
+ live?: {
55
+ subjectKinds: string[];
56
+ kinds: MemoryKindId[];
57
+ };
58
+ }
59
+ /** De samengestelde catalogus die de hub aan de frontend levert (`GET /kinds`). */
60
+ export interface MemoryKindCatalog {
61
+ /** App-gedeclareerd + org-eigen, gemengd. */
62
+ kinds: MemoryKindDefinition[];
63
+ /** Bronnen die meededen aan de fan-out (voor "wie levert dit?"). */
64
+ sources: string[];
65
+ locales: LocaleBundle;
66
+ }
@@ -0,0 +1,71 @@
1
+ import { MemoryItem, MemoryKindId, MemorySubjectKey } from './item';
2
+ /** Grenzen van het tekenbudget van een bundel. Buiten bereik wordt geklemd, niet geweigerd. */
3
+ export declare const MIN_MEMORY_BUDGET = 500;
4
+ export declare const MAX_MEMORY_BUDGET = 12000;
5
+ export declare const DEFAULT_MEMORY_BUDGET = 4000;
6
+ /**
7
+ * Één query-contract voor het dossier-paneel, de brief tijdens het rinkelen en elke
8
+ * AI-tool. Geen tweede codepad: het verschil tussen die drie is `budget`, niet code.
9
+ */
10
+ export interface MemoryQuery {
11
+ /** Weglaten = cross-subject zoeken ("vergelijkbare cases"). */
12
+ subject?: MemorySubjectKey;
13
+ /** Extra subjects die bij hetzelfde beeld horen, bv. het contact naast de interactie. */
14
+ alsoSubjects?: MemorySubjectKey[];
15
+ /** Default true. */
16
+ resolveAliases?: boolean;
17
+ kinds?: MemoryKindId[];
18
+ tags?: string[];
19
+ /** Vrije tekst. Afwezig = puur structurele ordening (priority x recency x weight) en dus geen embedding-call. */
20
+ text?: string;
21
+ since?: number;
22
+ limit?: number;
23
+ /** Zo bedoel je "bij ANDERE klanten". */
24
+ excludeSubjects?: MemorySubjectKey[];
25
+ format?: "items" | "bundle" | "both";
26
+ /** Geklemd op [{@link MIN_MEMORY_BUDGET}, {@link MAX_MEMORY_BUDGET}]. */
27
+ budget?: number;
28
+ /** Fase 3: ook live bronnen bevragen, met harde timeout en eigen sub-budget. */
29
+ live?: boolean;
30
+ }
31
+ export interface MemoryHit {
32
+ item: MemoryItem;
33
+ score: number;
34
+ snippet: string;
35
+ /** Ruw materiaal voor een confidence-gate (fase 4): waar kwam de score vandaan? */
36
+ matched: {
37
+ keyword: number;
38
+ semantic: number;
39
+ recency: number;
40
+ };
41
+ }
42
+ export interface MemoryQueryResult {
43
+ hits: MemoryHit[];
44
+ bundle?: string;
45
+ /** Hoeveel rijen het structurele voorfilter opleverde. */
46
+ scanned: number;
47
+ /** Scan-cap geraakt OF hits uit de bundel gevallen — nooit stil afkappen. */
48
+ truncated?: boolean;
49
+ /** `false` = keyword-only (geen embedding-account, of geen enkele vergelijkbare vector). */
50
+ semantic: boolean;
51
+ /** Hits die de her-autorisatie van de top-K niet overleefden. */
52
+ withheld?: number;
53
+ /**
54
+ * Kandidaten overgeslagen door model- of taal-mismatch. Zonder deze teller degradeert
55
+ * een modelwissel volkomen stil: `cosineSimilarity` geeft bij dimensie-mismatch 0, dus
56
+ * na het omzetten van `isDefaultEmbedding` scoort élk bestaand item 0 en valt retrieval
57
+ * geruisloos terug op keyword.
58
+ */
59
+ vectorSkipped?: number;
60
+ /**
61
+ * Waarom er niets terugkwam, als dat een structurele reden heeft in plaats van "niets
62
+ * gevonden".
63
+ *
64
+ * `"no-cross-subject-kinds"` is de belangrijkste: zoeken bij ándere klanten leest alleen
65
+ * geheugensoorten die als `visibility: "org"` én `crossSubjectSearchable` zijn
66
+ * gedeclareerd. Is er geen enkele, dan is een leeg resultaat geen zoekuitkomst maar een
67
+ * configuratiefeit — en dat hoort de caller te weten in plaats van te concluderen dat er
68
+ * niets vergelijkbaars bestaat.
69
+ */
70
+ reason?: "no-cross-subject-kinds" | "no-subject-access";
71
+ }
@@ -40,6 +40,12 @@ export declare const TRIGGER_VARS: readonly [{
40
40
  }, {
41
41
  readonly name: "textRaw";
42
42
  readonly type: "string";
43
+ }, {
44
+ readonly name: "fromStatus";
45
+ readonly type: "string";
46
+ }, {
47
+ readonly name: "toStatus";
48
+ readonly type: "string";
43
49
  }];
44
50
  /** De variabelen die een classify-stap oplevert, per mode. */
45
51
  export declare const CLASSIFY_VARS: {
@@ -14,6 +14,19 @@ export type PlaybookTrigger = {
14
14
  /** Specific channel ids (e.g. per mailbox). Matched against activity.channelId. */
15
15
  channelIds?: string[];
16
16
  filter?: unknown;
17
+ /**
18
+ * Wacht dit aantal milliseconden en draai dan **één** run voor de berichten in dat
19
+ * venster, in plaats van een run per bericht.
20
+ *
21
+ * Voor een playbook dat bij elk bericht een modelcall doet is dit het verschil tussen
22
+ * vier calls en één bij een mailwisseling van vier berichten in tien minuten. De run
23
+ * draait op het **nieuwste** bericht uit het venster; wat ertussen zat is in de
24
+ * samenvatting van dat gesprek al meegenomen via de lookup-stap.
25
+ *
26
+ * Bewust op de trigger en niet in de geheugenlaag: coalesceren is een eigenschap van
27
+ * "hoe vaak wil ik hierop reageren", niet van wat je onthoudt.
28
+ */
29
+ debounceMs?: number;
17
30
  } | {
18
31
  kind: "manual";
19
32
  };
@@ -59,6 +72,15 @@ export interface ClassifyStep {
59
72
  question?: string;
60
73
  fields?: unknown;
61
74
  instruction?: string;
75
+ /**
76
+ * Wélke tekst geclassificeerd wordt, als `$ref` in de run-vars. Default `$trigger.text`.
77
+ *
78
+ * Nodig zodra de trigger zelf geen tekst draagt. Een `INTERACTION_STATUS_CHANGED`-activity
79
+ * heeft alleen `{ fromStatus, toStatus }` in zijn payload, dus `trigger.text` is leeg — en
80
+ * dan krijgt het model een lege invoer. Met `input: "$thread"` leest de stap wat een
81
+ * eerdere `lookup` heeft opgehaald, zoals de berichten van het gesprek.
82
+ */
83
+ input?: string;
62
84
  }
63
85
  export interface GenerateStep {
64
86
  type: "generate";
package/dist/index.cjs CHANGED
@@ -1,10 +1,10 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const c=140;function g(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 M(e){const t=e.trim();return t.length<=c?t:t.slice(0,c-1).trimEnd()+"…"}function i(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 C(e){switch(e.type){case"EMAIL_RECEIVED":case"EMAIL_SENT":{const t=e.payload,n=t.bodySnippet?.trim()||g(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?` (${i(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?` (${i(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"AI_ACTION_PROPOSED":return`Voorstel wacht op goedkeuring (${e.payload.actions?.length??0} actie(s))`;case"PLAYBOOK_STARTED":return`Playbook gestart${e.payload.playbookName?`: ${e.payload.playbookName}`:""}`;case"PLAYBOOK_COMPLETED":return"Playbook afgerond";case"PLAYBOOK_ESCALATED":return"Playbook geëscaleerd naar een mens";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?` (${i(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 h(e){return{activityId:e.id,type:e.type,snippet:M(C(e)),authorName:e.author?.name??"",direction:e.direction,createdAt:e.createdAt??Date.now()}}function U(e,t,n=[]){const r=e.createdAt;if(!r)return[];const s=new Set(n);return e.author.type==="user"&&e.author.id&&s.add(e.author.id),Object.entries(t).filter(([a,o])=>o>=r&&!s.has(a)).map(([a])=>a)}const P=[{id:"agent",label:"Agent",labelSource:"user"},{id:"team",label:"Team",labelSource:"team"},{id:"inbox",label:"Inbox",labelSource:"inbox"},{id:"channel",label:"Kanaal",labelSource:"channel"},{id:"provider",label:"Provider",labelSource:"raw"},{id:"status",label:"Status",labelSource:"raw"},{id:"handledBy",label:"Afhandelaar",labelSource:"raw"},{id:"time",label:"Tijd",labelSource:"raw"}];function $(e){const t=[];for(const n of Object.keys(e))for(const r of Object.keys(e[n]))t.push({lang:n,key:r,value:e[n][r]});return t}function l(e){return e<10?`0${e}`:`${e}`}function A(e,t){const n=new Date(e),r=`${n.getUTCFullYear()}-${l(n.getUTCMonth()+1)}-${l(n.getUTCDate())}`;return t==="day"?r:`${r}T${l(n.getUTCHours())}`}function f(e,t){const n=new Date(e);return n.setUTCMinutes(0,0,0),t==="day"&&n.setUTCHours(0),n.getTime()}const k=36e5,G=864e5;function V(e,t,n){const r=n==="hour"?k:G,s=[];for(let a=f(e,n);a<=t;a+=r)s.push(A(a,n));return s}function H(e){return(e.startsWith("tool:")?e.slice(5):e).replace(/__/g," · ").replace(/\./g," · ").replace(/_/g," ")}const w=[{name:"text",type:"string"},{name:"subject",type:"string"},{name:"from",type:"string"},{name:"type",type:"string"},{name:"direction",type:"string"},{name:"channelId",type:"string"},{name:"interactionId",type:"string"},{name:"activityId",type:"string"},{name:"authorType",type:"string"},{name:"authorName",type:"string"},{name:"textRaw",type:"string"}],F={topics:[{name:"topic",type:"string"},{name:"confidence",type:"number"}],question:[{name:"answer",type:"boolean"},{name:"confidence",type:"number"}],extract:[]};function I(e){switch(e.kind){case"user":return`user:${e.userId}`;case"team":return`team:${e.teamId}`;case"org":return"org"}}function x(e){if(typeof e!="string")return;const t=e.trim();if(t==="org")return{kind:"org"};const n=t.indexOf(":");if(n<=0)return;const r=t.slice(0,n),s=t.slice(n+1).trim();if(s){if(r==="user")return{kind:"user",userId:s};if(r==="team")return{kind:"team",teamId:s}}}function _(e){return I(e)}function B(e){switch(e.kind){case"personal":return{kind:"user",userId:e.userId};case"team":return{kind:"team",teamId:e.teamId};case"org":return{kind:"org"}}}function j(e){return _(e)}const S=["done","escalated","failed","timed_out","stopped"],T=["awaiting_approval","awaiting_reply"];function m(e){return S.includes(e)}function W(e){return m(e)}function K(e){return T.includes(e)}const Y=new Set(["mail","message"]);function R(e,t){const n=e.settings?.signatures?.[t];return!n||!n.enabled||!n.body||n.body.trim()===""?null:n}function q(e,t,n,r="html"){if(!e)return n;const s=R(e,t);return s?`${n}${r==="text"?`
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const d=140;function f(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 G(e){const t=e.trim();return t.length<=d?t:t.slice(0,d-1).trimEnd()+"…"}function l(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 k(e){switch(e.type){case"EMAIL_RECEIVED":case"EMAIL_SENT":{const t=e.payload,n=t.bodySnippet?.trim()||f(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?` (${l(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?` (${l(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"AI_ACTION_PROPOSED":return`Voorstel wacht op goedkeuring (${e.payload.actions?.length??0} actie(s))`;case"PLAYBOOK_STARTED":return`Playbook gestart${e.payload.playbookName?`: ${e.payload.playbookName}`:""}`;case"PLAYBOOK_COMPLETED":return"Playbook afgerond";case"PLAYBOOK_ESCALATED":return"Playbook geëscaleerd naar een mens";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?` (${l(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 V(e){return{activityId:e.id,type:e.type,snippet:G(k(e)),authorName:e.author?.name??"",direction:e.direction,createdAt:e.createdAt??Date.now()}}function $(e,t,n=[]){const r=e.createdAt;if(!r)return[];const a=new Set(n);return e.author.type==="user"&&e.author.id&&a.add(e.author.id),Object.entries(t).filter(([s,i])=>i>=r&&!a.has(s)).map(([s])=>s)}const H=[{id:"agent",label:"Agent",labelSource:"user"},{id:"team",label:"Team",labelSource:"team"},{id:"inbox",label:"Inbox",labelSource:"inbox"},{id:"channel",label:"Kanaal",labelSource:"channel"},{id:"provider",label:"Provider",labelSource:"raw"},{id:"status",label:"Status",labelSource:"raw"},{id:"handledBy",label:"Afhandelaar",labelSource:"raw"},{id:"time",label:"Tijd",labelSource:"raw"}];function w(e){const t=[];for(const n of Object.keys(e))for(const r of Object.keys(e[n]))t.push({lang:n,key:r,value:e[n][r]});return t}function c(e){return e<10?`0${e}`:`${e}`}function _(e,t){const n=new Date(e),r=`${n.getUTCFullYear()}-${c(n.getUTCMonth()+1)}-${c(n.getUTCDate())}`;return t==="day"?r:`${r}T${c(n.getUTCHours())}`}function I(e,t){const n=new Date(e);return n.setUTCMinutes(0,0,0),t==="day"&&n.setUTCHours(0),n.getTime()}const B=36e5,F=864e5;function x(e,t,n){const r=n==="hour"?B:F,a=[];for(let s=I(e,n);s<=t;s+=r)a.push(_(s,n));return a}const S=[{id:"resource",label:"Resource",labelSource:"raw"},{id:"origin",label:"Herkomst",labelSource:"raw"}],b=S.map(e=>e.id);function T(e,t){return`${e}.usage.${t}`}function v(e,t){return t.map(n=>({id:T(e,n.unit),label:n.label,category:"Verbruik",valueType:n.valueType??"count",aggregation:"sum",supportedDimensions:["provider",...b,...n.extraDimensions??[],"time"]}))}function Y(e){return(e.startsWith("tool:")?e.slice(5):e).replace(/__/g," · ").replace(/\./g," · ").replace(/_/g," ")}const W=[{name:"text",type:"string"},{name:"subject",type:"string"},{name:"from",type:"string"},{name:"type",type:"string"},{name:"direction",type:"string"},{name:"channelId",type:"string"},{name:"interactionId",type:"string"},{name:"activityId",type:"string"},{name:"authorType",type:"string"},{name:"authorName",type:"string"},{name:"textRaw",type:"string"},{name:"fromStatus",type:"string"},{name:"toStatus",type:"string"}],j={topics:[{name:"topic",type:"string"},{name:"confidence",type:"number"}],question:[{name:"answer",type:"boolean"},{name:"confidence",type:"number"}],extract:[]};function m(e){switch(e.kind){case"user":return`user:${e.userId}`;case"team":return`team:${e.teamId}`;case"org":return"org"}}function K(e){if(typeof e!="string")return;const t=e.trim();if(t==="org")return{kind:"org"};const n=t.indexOf(":");if(n<=0)return;const r=t.slice(0,n),a=t.slice(n+1).trim();if(a){if(r==="user")return{kind:"user",userId:a};if(r==="team")return{kind:"team",teamId:a}}}function M(e){return m(e)}function q(e){switch(e.kind){case"personal":return{kind:"user",userId:e.userId};case"team":return{kind:"team",teamId:e.teamId};case"org":return{kind:"org"}}}function X(e){return M(e)}const R=["done","escalated","failed","timed_out","stopped"],N=["awaiting_approval","awaiting_reply"];function D(e){return R.includes(e)}function z(e){return D(e)}function J(e){return N.includes(e)}const u=[{id:"openai",label:"OpenAI",capabilities:["chat","transcription","embedding"],defaultModel:"gpt-5-mini",baseUrl:"https://api.openai.com/v1"},{id:"gemini",label:"Google Gemini",capabilities:["chat","transcription","embedding"],defaultModel:"gemini-3.5-flash",baseUrl:"https://generativelanguage.googleapis.com/v1beta"},{id:"anthropic",label:"Anthropic",capabilities:["chat"],defaultModel:"claude-haiku-4-5",baseUrl:"https://api.anthropic.com/v1"},{id:"elevenlabs",label:"ElevenLabs",capabilities:["transcription"],baseUrl:"https://api.elevenlabs.io/v1"},{id:"moonshot",label:"Moonshot (Kimi)",capabilities:["chat"],defaultModel:"kimi-k3",baseUrl:"https://api.moonshot.ai/v1"},{id:"deepseek",label:"DeepSeek",capabilities:["chat"],defaultModel:"deepseek-v4-flash",baseUrl:"https://api.deepseek.com/v1"},{id:"groq",label:"Groq",capabilities:["chat"],defaultModel:"openai/gpt-oss-120b",baseUrl:"https://api.groq.com/openai/v1"},{id:"mistral",label:"Mistral",capabilities:["chat"],defaultModel:"mistral-large-latest",baseUrl:"https://api.mistral.ai/v1"},{id:"openrouter",label:"OpenRouter",capabilities:["chat"],defaultModel:"openrouter/auto",baseUrl:"https://openrouter.ai/api/v1"},{id:"ollama",label:"Ollama (self-hosted)",capabilities:["chat"],defaultModel:"llama3.3"},{id:"openai-compatible",label:"OpenAI-compatible (eigen endpoint)",capabilities:["chat"],defaultModel:"default"}];function o(e){return u.find(t=>t.id===e)}function Q(e){return o(e)?.label??e}function Z(e,t="gpt-5-mini"){return o(e)?.defaultModel??t}function ee(e){return u.filter(t=>t.capabilities.includes(e))}function te(e){const t=o(e);return!!t&&!t.baseUrl}const ne=new Set(["mail","message"]);function O(e,t){const n=e.settings?.signatures?.[t];return!n||!n.enabled||!n.body||n.body.trim()===""?null:n}function re(e,t,n,r="html"){if(!e)return n;const a=O(e,t);return a?`${n}${r==="text"?`
2
2
 
3
- `:t==="mail"?"<br><br>-- <br>":"<br><br>"}${s.body}`:n}function z(e){return e.endpoints??[]}const v=e=>!!e.assignedUserId||!!e.assignedInboxId,J=e=>e.status==="closed",Q=e=>({urgent:10,high:5,normal:2,low:1})[e.priority]||0,X=(e,t=30)=>e.title?.length<=t?e.title:`${e.title.substring(0,t)}...`;function Z(e,t){return e.lastActivityAt?!(e.seenBy??[]).includes(t):!1}const ee=[/<blockquote/i,/class="?gmail_quote/i,/id="?[^"]*divRplyFwdMsg/i,/id="?[^"]*mail-editor-reference-message-container/i,/-{3,}\s*Original Message\s*-{3,}/i,/\n_{5,}\s*\n/,/\bOn\b[\s\S]{0,200}?\bwrote:/i],u=/^\s*(?:>+\s*)?(?:from|van|sent|verzonden|to|aan|cc|subject|onderwerp|date|datum)\s*:/i,te=/^\s*(?:on|op)\b.{0,200}?\b(?:wrote|schreef|geschreven)\s*:?\s*$/i;function ne(e){const t=e.split(`
4
- `);for(let n=0;n<t.length;n++)if(te.test(t[n])||u.test(t[n])&&t.slice(n+1,n+4).some(r=>u.test(r)))return t.slice(0,n).join(`
3
+ `:t==="mail"?"<br><br>-- <br>":"<br><br>"}${a.body}`:n}function ae(e){return e.endpoints??[]}const se=e=>!!e.assignedUserId||!!e.assignedInboxId,ie=e=>e.status==="closed",oe=e=>({urgent:10,high:5,normal:2,low:1})[e.priority]||0,le=(e,t=30)=>e.title?.length<=t?e.title:`${e.title.substring(0,t)}...`;function ce(e,t){return e.lastActivityAt?!(e.seenBy??[]).includes(t):!1}const ue=2e3,de=500,pe=12e3,Ee=4e3,ge=[/<blockquote/i,/class="?gmail_quote/i,/id="?[^"]*divRplyFwdMsg/i,/id="?[^"]*mail-editor-reference-message-container/i,/-{3,}\s*Original Message\s*-{3,}/i,/\n_{5,}\s*\n/,/\bOn\b[\s\S]{0,200}?\bwrote:/i],p=/^\s*(?:>+\s*)?(?:from|van|sent|verzonden|to|aan|cc|subject|onderwerp|date|datum)\s*:/i,Ae=/^\s*(?:on|op)\b.{0,200}?\b(?:wrote|schreef|geschreven)\s*:?\s*$/i;function fe(e){const t=e.split(`
4
+ `);for(let n=0;n<t.length;n++)if(Ae.test(t[n])||p.test(t[n])&&t.slice(n+1,n+4).some(r=>p.test(r)))return t.slice(0,n).join(`
5
5
  `).trim();return e}function E(e){let t=e;return t=t.replace(/<(script|style)[\s\S]*?<\/\1>/gi,""),t=t.replace(/<br\s*\/?>/gi,`
6
6
  `),t=t.replace(/<\/(p|div|li|tr|h[1-6]|ul|ol|table)>/gi,`
7
- `),t=t.replace(/<[^>]+>/g,""),t=t.replace(/<[^>]*$/,""),t}function d(e){return e.replace(/&nbsp;/gi," ").replace(/&lt;/gi,"<").replace(/&gt;/gi,">").replace(/&quot;/gi,'"').replace(/&#39;/gi,"'").replace(/&#(\d+);/g,(t,n)=>String.fromCharCode(Number(n))).replace(/&amp;/gi,"&")}function p(e){return e.replace(/\r/g,"").replace(/[ \t]+/g," ").replace(/ *\n */g,`
7
+ `),t=t.replace(/<[^>]+>/g,""),t=t.replace(/<[^>]*$/,""),t}function g(e){return e.replace(/&nbsp;/gi," ").replace(/&lt;/gi,"<").replace(/&gt;/gi,">").replace(/&quot;/gi,'"').replace(/&#39;/gi,"'").replace(/&#(\d+);/g,(t,n)=>String.fromCharCode(Number(n))).replace(/&amp;/gi,"&")}function A(e){return e.replace(/\r/g,"").replace(/[ \t]+/g," ").replace(/ *\n */g,`
8
8
  `).replace(/\n{3,}/g,`
9
9
 
10
- `).trim()}function re(e){if(typeof e!="string"||!e)return"";let t=e.length;for(const a of ee){const o=e.search(a);o>=0&&o<t&&(t=o)}const n=e.slice(0,t);let r=p(d(E(n)));return r||(r=p(d(E(e)))),ne(r)||r}const se=/(^|[._+-])(no-?reply|noreply|donotreply|do-not-reply|newsletter|nieuwsbrief|mailer|mailing|notifications?|bounce|postmaster|marketing)@/i,ae=/\b(unsubscribe|afmelden|uitschrijven|opt[\s-]?out|manage (your )?preferences|geen (e-?mails?|nieuwsbrief|berichten) meer)\b/i;function oe(e,t){return!!(e&&se.test(e)||t&&ae.test(t))}const b=3,N=600;function ie(e,t){return e>=b||t>=N}function D(e,t){const n=new Set(e.disabledIntents??[]),r=e.intentOverrides??{},s=t.intents.filter(o=>!n.has(o.intent)).map(o=>ce(o,r[o.intent]));if(!e.extraIntents||e.extraIntents.length===0)return s;const a=new Set(s.map(o=>o.intent));for(const o of e.extraIntents)a.has(o.intent)||(s.push(o),a.add(o.intent));return s}function y(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 le(e,t){const n=y(e,t);return Object.entries(n).filter(([,r])=>!r).map(([r])=>r)}function ce(e,t){return t?{intent:t.intent??e.intent,targetSchemes:t.targetSchemes??e.targetSchemes,transport:t.transport??e.transport}:e}function ue(e,t){const n=[];for(const r of e){if(!r.enabled)continue;const s=t[r.providerId];if(s)for(const a of D(r,s))n.push({channel:r,description:s,capability:a})}return n}function Ee(e,t){return t.filter(n=>n.capability.intent===e)}function L(e,t){return t.filter(n=>n.capability.targetSchemes.includes(e))}function de(e,t){return L(e.scheme,t)}function pe(e,t,n){const r=[];for(const s of e)for(const a of n)a.capability.intent===t&&a.capability.targetSchemes.includes(s.scheme)&&r.push({channelIntent:a,endpoint:s});return r}var O=(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))(O||{});const ge="message_window",Ae="message_templates",fe={FROM:"from",TO:"to",CC:"cc",BCC:"bcc",ORGANIZER:"organizer",PRESENTER:"presenter",ATTENDEE:"attendee",OWNER:"owner",MEMBER:"member",GUEST:"guest",HOST:"host",PARTICIPANT:"participant"},Ie=(e,t)=>({intent:e,...t}),_e="folder_management",Se="remote_search",Te={ok:!1,code:"UNSUPPORTED",message:"Provider does not support this op"},me=9e4,Re="installation-id";exports.CLASSIFY_VARS=F;exports.CORE_DIMENSIONS=P;exports.CommunicationScheme=O;exports.FEATURE_FOLDER_MANAGEMENT=_e;exports.FEATURE_REMOTE_SEARCH=Se;exports.INSTALLATION_HEADER=Re;exports.InteractionParticipantRole=fe;exports.PAUSED_RUN_STATUSES=T;exports.PRESENCE_ONLINE_WINDOW_MS=me;exports.PROVIDER_FEATURE_MESSAGE_TEMPLATES=Ae;exports.PROVIDER_FEATURE_MESSAGE_WINDOW=ge;exports.SIGNATURE_INTENTS=Y;exports.SUMMARY_MIN_LAST_CHARS=N;exports.SUMMARY_MIN_MESSAGES=b;exports.TERMINAL_RUN_STATUSES=S;exports.TRIGGER_VARS=w;exports.UNSUPPORTED=Te;exports.actingIdentityKey=_;exports.actorKey=j;exports.applySignature=q;exports.buildActivityPreview=h;exports.buildChannelIntents=ue;exports.cleanMessageText=re;exports.defineIntent=Ie;exports.disabledIntentsFromCapabilities=le;exports.filterByEndpoint=de;exports.filterByIntent=Ee;exports.filterByTargetScheme=L;exports.flattenLocales=$;exports.floorToPeriod=f;exports.formatActingIdentity=I;exports.formatPeriod=A;exports.getActivitySeenUserIds=U;exports.getContactEndpoints=z;exports.getShortTitle=X;exports.getUrgencyScore=Q;exports.humanizeAction=H;exports.isAssigned=v;exports.isClosed=J;exports.isInteractionUnseen=Z;exports.isLikelyBulk=oe;exports.isPaused=K;exports.isRetryable=W;exports.isTerminal=m;exports.isThreadLongEnough=ie;exports.matchContactToIntents=pe;exports.parseActingIdentity=x;exports.periodsInRange=V;exports.playbookActor=B;exports.resolveAccountCapabilities=y;exports.resolveChannelIntents=D;exports.resolveSignature=R;exports.stripHtml=g;
10
+ `).trim()}function _e(e){if(typeof e!="string"||!e)return"";let t=e.length;for(const s of ge){const i=e.search(s);i>=0&&i<t&&(t=i)}const n=e.slice(0,t);let r=A(g(E(n)));return r||(r=A(g(E(e)))),fe(r)||r}const Ie=/(^|[._+-])(no-?reply|noreply|donotreply|do-not-reply|newsletter|nieuwsbrief|mailer|mailing|notifications?|bounce|postmaster|marketing)@/i,Se=/\b(unsubscribe|afmelden|uitschrijven|opt[\s-]?out|manage (your )?preferences|geen (e-?mails?|nieuwsbrief|berichten) meer)\b/i;function be(e,t){return!!(e&&Ie.test(e)||t&&Se.test(t))}const h=3,y=600;function Te(e,t){return e>=h||t>=y}function L(e,t){const n=new Set(e.disabledIntents??[]),r=e.intentOverrides??{},a=t.intents.filter(i=>!n.has(i.intent)).map(i=>Me(i,r[i.intent]));if(!e.extraIntents||e.extraIntents.length===0)return a;const s=new Set(a.map(i=>i.intent));for(const i of e.extraIntents)s.has(i.intent)||(a.push(i),s.add(i.intent));return a}function U(e,t){const n={};for(const r of e.intents){if(!r.togglable)continue;const a=t?.[r.intent];n[r.intent]=a??r.defaultEnabled??!0}return n}function me(e,t){const n=U(e,t);return Object.entries(n).filter(([,r])=>!r).map(([r])=>r)}function Me(e,t){return t?{intent:t.intent??e.intent,targetSchemes:t.targetSchemes??e.targetSchemes,transport:t.transport??e.transport}:e}function Re(e,t){const n=[];for(const r of e){if(!r.enabled)continue;const a=t[r.providerId];if(a)for(const s of L(r,a))n.push({channel:r,description:a,capability:s})}return n}function Ne(e,t){return t.filter(n=>n.capability.intent===e)}function C(e,t){return t.filter(n=>n.capability.targetSchemes.includes(e))}function De(e,t){return C(e.scheme,t)}function Oe(e,t,n){const r=[];for(const a of e)for(const s of n)s.capability.intent===t&&s.capability.targetSchemes.includes(a.scheme)&&r.push({channelIntent:s,endpoint:a});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 he="message_window",ye="message_templates",Le={FROM:"from",TO:"to",CC:"cc",BCC:"bcc",ORGANIZER:"organizer",PRESENTER:"presenter",ATTENDEE:"attendee",OWNER:"owner",MEMBER:"member",GUEST:"guest",HOST:"host",PARTICIPANT:"participant"},Ue=(e,t)=>({intent:e,...t}),Ce="folder_management",Pe="remote_search",Ge={ok:!1,code:"UNSUPPORTED",message:"Provider does not support this op"},ke=9e4,Ve="installation-id";exports.AI_VENDORS=u;exports.CLASSIFY_VARS=j;exports.CORE_DIMENSIONS=H;exports.CommunicationScheme=P;exports.DEFAULT_MEMORY_BUDGET=Ee;exports.FEATURE_FOLDER_MANAGEMENT=Ce;exports.FEATURE_REMOTE_SEARCH=Pe;exports.INSTALLATION_HEADER=Ve;exports.InteractionParticipantRole=Le;exports.MAX_MEMORY_BUDGET=pe;exports.MAX_MEMORY_CHARS=ue;exports.MIN_MEMORY_BUDGET=de;exports.PAUSED_RUN_STATUSES=N;exports.PRESENCE_ONLINE_WINDOW_MS=ke;exports.PROVIDER_FEATURE_MESSAGE_TEMPLATES=ye;exports.PROVIDER_FEATURE_MESSAGE_WINDOW=he;exports.SIGNATURE_INTENTS=ne;exports.SUMMARY_MIN_LAST_CHARS=y;exports.SUMMARY_MIN_MESSAGES=h;exports.TERMINAL_RUN_STATUSES=R;exports.TRIGGER_VARS=W;exports.UNSUPPORTED=Ge;exports.USAGE_DIMENSIONS=S;exports.USAGE_DIMENSION_IDS=b;exports.actingIdentityKey=M;exports.actorKey=X;exports.aiVendorDefaultModel=Z;exports.aiVendorLabel=Q;exports.aiVendorNeedsBaseUrl=te;exports.aiVendorsWith=ee;exports.applySignature=re;exports.buildActivityPreview=V;exports.buildChannelIntents=Re;exports.cleanMessageText=_e;exports.defineIntent=Ue;exports.disabledIntentsFromCapabilities=me;exports.filterByEndpoint=De;exports.filterByIntent=Ne;exports.filterByTargetScheme=C;exports.findAiVendor=o;exports.flattenLocales=w;exports.floorToPeriod=I;exports.formatActingIdentity=m;exports.formatPeriod=_;exports.getActivitySeenUserIds=$;exports.getContactEndpoints=ae;exports.getShortTitle=le;exports.getUrgencyScore=oe;exports.humanizeAction=Y;exports.isAssigned=se;exports.isClosed=ie;exports.isInteractionUnseen=ce;exports.isLikelyBulk=be;exports.isPaused=J;exports.isRetryable=z;exports.isTerminal=D;exports.isThreadLongEnough=Te;exports.matchContactToIntents=Oe;exports.parseActingIdentity=K;exports.periodsInRange=x;exports.playbookActor=q;exports.resolveAccountCapabilities=U;exports.resolveChannelIntents=L;exports.resolveSignature=O;exports.stripHtml=f;exports.usageMetricId=T;exports.usageMetrics=v;
package/dist/index.d.ts CHANGED
@@ -18,6 +18,7 @@ export * from './entities/calendar-event';
18
18
  export * from './entities/draft';
19
19
  export * from './entities/inbox';
20
20
  export * from './entities/interaction';
21
+ export * from './entities/memory';
21
22
  export * from './entities/task';
22
23
  export * from './entities/note';
23
24
  export * from './entities/read-state';
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
- function E(e) {
1
+ function f(e) {
2
2
  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();
3
3
  }
4
- function g(e) {
4
+ function b(e) {
5
5
  const t = e.trim();
6
6
  return t.length <= 140 ? t : t.slice(0, 139).trimEnd() + "…";
7
7
  }
@@ -10,11 +10,11 @@ function i(e) {
10
10
  const t = Math.floor(e / 60), n = Math.floor(e % 60);
11
11
  return `${t}:${n.toString().padStart(2, "0")}`;
12
12
  }
13
- function f(e) {
13
+ function A(e) {
14
14
  switch (e.type) {
15
15
  case "EMAIL_RECEIVED":
16
16
  case "EMAIL_SENT": {
17
- const t = e.payload, n = t.bodySnippet?.trim() || E(t.body ?? "");
17
+ const t = e.payload, n = t.bodySnippet?.trim() || f(t.body ?? "");
18
18
  return t.subject ? `${t.subject} — ${n}` : n;
19
19
  }
20
20
  case "CHAT_MESSAGE_SENT":
@@ -91,23 +91,23 @@ function f(e) {
91
91
  return "";
92
92
  }
93
93
  }
94
- function H(e) {
94
+ function j(e) {
95
95
  return {
96
96
  activityId: e.id,
97
97
  type: e.type,
98
- snippet: g(f(e)),
98
+ snippet: b(A(e)),
99
99
  authorName: e.author?.name ?? "",
100
100
  direction: e.direction,
101
101
  createdAt: e.createdAt ?? Date.now()
102
102
  };
103
103
  }
104
- function V(e, t, n = []) {
104
+ function v(e, t, n = []) {
105
105
  const r = e.createdAt;
106
106
  if (!r) return [];
107
- const s = new Set(n);
108
- return e.author.type === "user" && e.author.id && s.add(e.author.id), Object.entries(t).filter(([a, o]) => o >= r && !s.has(a)).map(([a]) => a);
107
+ const a = new Set(n);
108
+ return e.author.type === "user" && e.author.id && a.add(e.author.id), Object.entries(t).filter(([s, o]) => o >= r && !a.has(s)).map(([s]) => s);
109
109
  }
110
- const x = [
110
+ const W = [
111
111
  { id: "agent", label: "Agent", labelSource: "user" },
112
112
  { id: "team", label: "Team", labelSource: "team" },
113
113
  { id: "inbox", label: "Inbox", labelSource: "inbox" },
@@ -117,7 +117,7 @@ const x = [
117
117
  { id: "handledBy", label: "Afhandelaar", labelSource: "raw" },
118
118
  { id: "time", label: "Tijd", labelSource: "raw" }
119
119
  ];
120
- function B(e) {
120
+ function Y(e) {
121
121
  const t = [];
122
122
  for (const n of Object.keys(e))
123
123
  for (const r of Object.keys(e[n]))
@@ -127,7 +127,7 @@ function B(e) {
127
127
  function l(e) {
128
128
  return e < 10 ? `0${e}` : `${e}`;
129
129
  }
130
- function A(e, t) {
130
+ function m(e, t) {
131
131
  const n = new Date(e), r = `${n.getUTCFullYear()}-${l(n.getUTCMonth() + 1)}-${l(n.getUTCDate())}`;
132
132
  return t === "day" ? r : `${r}T${l(n.getUTCHours())}`;
133
133
  }
@@ -136,16 +136,38 @@ function _(e, t) {
136
136
  return n.setUTCMinutes(0, 0, 0), t === "day" && n.setUTCHours(0), n.getTime();
137
137
  }
138
138
  const I = 36e5, T = 864e5;
139
- function F(e, t, n) {
140
- const r = n === "hour" ? I : T, s = [];
141
- for (let a = _(e, n); a <= t; a += r)
142
- s.push(A(a, n));
143
- return s;
139
+ function K(e, t, n) {
140
+ const r = n === "hour" ? I : T, a = [];
141
+ for (let s = _(e, n); s <= t; s += r)
142
+ a.push(m(s, n));
143
+ return a;
144
+ }
145
+ const S = [
146
+ { id: "resource", label: "Resource", labelSource: "raw" },
147
+ { id: "origin", label: "Herkomst", labelSource: "raw" }
148
+ ], M = S.map((e) => e.id);
149
+ function D(e, t) {
150
+ return `${e}.usage.${t}`;
151
+ }
152
+ function q(e, t) {
153
+ return t.map((n) => ({
154
+ id: D(e, n.unit),
155
+ label: n.label,
156
+ category: "Verbruik",
157
+ valueType: n.valueType ?? "count",
158
+ aggregation: "sum",
159
+ supportedDimensions: [
160
+ "provider",
161
+ ...M,
162
+ ...n.extraDimensions ?? [],
163
+ "time"
164
+ ]
165
+ }));
144
166
  }
145
- function j(e) {
167
+ function X(e) {
146
168
  return (e.startsWith("tool:") ? e.slice(5) : e).replace(/__/g, " · ").replace(/\./g, " · ").replace(/_/g, " ");
147
169
  }
148
- const W = [
170
+ const z = [
149
171
  { name: "text", type: "string" },
150
172
  { name: "subject", type: "string" },
151
173
  { name: "from", type: "string" },
@@ -156,8 +178,16 @@ const W = [
156
178
  { name: "activityId", type: "string" },
157
179
  { name: "authorType", type: "string" },
158
180
  { name: "authorName", type: "string" },
159
- { name: "textRaw", type: "string" }
160
- ], K = {
181
+ { name: "textRaw", type: "string" },
182
+ // Alleen gevuld bij INTERACTION_STATUS_CHANGED; een filter als
183
+ // `trigger.toStatus == "closed"` is de trigger voor "onthoud dit bij afsluiten".
184
+ { name: "fromStatus", type: "string" },
185
+ { name: "toStatus", type: "string" }
186
+ // Bewust GEEN `contactId`. Een interactie draagt geen contact-id; dat zou per run een
187
+ // lookup via `remoteParty` naar de contactenlijst kosten, en dan hangt geheugen op
188
+ // klantniveau aan een gok in plaats van aan een vastgelegde identiteit. Zodra partijen
189
+ // wél in de database staan (party/CRM) komt dit terug als een echt veld.
190
+ ], J = {
161
191
  topics: [
162
192
  { name: "topic", type: "string" },
163
193
  { name: "confidence", type: "number" }
@@ -168,7 +198,7 @@ const W = [
168
198
  ],
169
199
  extract: []
170
200
  };
171
- function S(e) {
201
+ function N(e) {
172
202
  switch (e.kind) {
173
203
  case "user":
174
204
  return `user:${e.userId}`;
@@ -178,22 +208,22 @@ function S(e) {
178
208
  return "org";
179
209
  }
180
210
  }
181
- function Y(e) {
211
+ function Q(e) {
182
212
  if (typeof e != "string") return;
183
213
  const t = e.trim();
184
214
  if (t === "org") return { kind: "org" };
185
215
  const n = t.indexOf(":");
186
216
  if (n <= 0) return;
187
- const r = t.slice(0, n), s = t.slice(n + 1).trim();
188
- if (s) {
189
- if (r === "user") return { kind: "user", userId: s };
190
- if (r === "team") return { kind: "team", teamId: s };
217
+ const r = t.slice(0, n), a = t.slice(n + 1).trim();
218
+ if (a) {
219
+ if (r === "user") return { kind: "user", userId: a };
220
+ if (r === "team") return { kind: "team", teamId: a };
191
221
  }
192
222
  }
193
- function m(e) {
194
- return S(e);
223
+ function R(e) {
224
+ return N(e);
195
225
  }
196
- function q(e) {
226
+ function Z(e) {
197
227
  switch (e.kind) {
198
228
  case "personal":
199
229
  return { kind: "user", userId: e.userId };
@@ -203,44 +233,148 @@ function q(e) {
203
233
  return { kind: "org" };
204
234
  }
205
235
  }
206
- function z(e) {
207
- return m(e);
236
+ function ee(e) {
237
+ return R(e);
238
+ }
239
+ const O = ["done", "escalated", "failed", "timed_out", "stopped"], h = ["awaiting_approval", "awaiting_reply"];
240
+ function L(e) {
241
+ return O.includes(e);
242
+ }
243
+ function te(e) {
244
+ return L(e);
245
+ }
246
+ function ne(e) {
247
+ return h.includes(e);
248
+ }
249
+ const g = [
250
+ {
251
+ id: "openai",
252
+ label: "OpenAI",
253
+ capabilities: ["chat", "transcription", "embedding"],
254
+ defaultModel: "gpt-5-mini",
255
+ baseUrl: "https://api.openai.com/v1"
256
+ },
257
+ {
258
+ id: "gemini",
259
+ label: "Google Gemini",
260
+ capabilities: ["chat", "transcription", "embedding"],
261
+ defaultModel: "gemini-3.5-flash",
262
+ baseUrl: "https://generativelanguage.googleapis.com/v1beta"
263
+ },
264
+ {
265
+ id: "anthropic",
266
+ label: "Anthropic",
267
+ capabilities: ["chat"],
268
+ defaultModel: "claude-haiku-4-5",
269
+ baseUrl: "https://api.anthropic.com/v1"
270
+ },
271
+ {
272
+ id: "elevenlabs",
273
+ label: "ElevenLabs",
274
+ capabilities: ["transcription"],
275
+ baseUrl: "https://api.elevenlabs.io/v1"
276
+ },
277
+ // --- Chat Completions-vendors ------------------------------------------
278
+ // Alles hieronder draait op één adapter (`OpenAiCompatibleProvider`). Een
279
+ // nieuwe toevoegen is een rij hier + een regel in `providers/registry`.
280
+ //
281
+ // `defaultModel` is bewust een alias waar de vendor er een aanbiedt
282
+ // (`mistral-large-latest`, `openrouter/auto`): losse model-ids verlopen,
283
+ // aliassen niet. Waar dat niet kan, staat het nieuwste vlaggenschip.
284
+ {
285
+ id: "moonshot",
286
+ label: "Moonshot (Kimi)",
287
+ capabilities: ["chat"],
288
+ defaultModel: "kimi-k3",
289
+ baseUrl: "https://api.moonshot.ai/v1"
290
+ },
291
+ {
292
+ id: "deepseek",
293
+ label: "DeepSeek",
294
+ capabilities: ["chat"],
295
+ defaultModel: "deepseek-v4-flash",
296
+ baseUrl: "https://api.deepseek.com/v1"
297
+ },
298
+ {
299
+ id: "groq",
300
+ label: "Groq",
301
+ capabilities: ["chat"],
302
+ defaultModel: "openai/gpt-oss-120b",
303
+ baseUrl: "https://api.groq.com/openai/v1"
304
+ },
305
+ {
306
+ id: "mistral",
307
+ label: "Mistral",
308
+ capabilities: ["chat"],
309
+ defaultModel: "mistral-large-latest",
310
+ baseUrl: "https://api.mistral.ai/v1"
311
+ },
312
+ {
313
+ id: "openrouter",
314
+ label: "OpenRouter",
315
+ capabilities: ["chat"],
316
+ // De Auto Router kiest zelf een model per prompt, dus dit verloopt niet
317
+ // mee met de wekelijks wisselende slugs in de catalogus.
318
+ defaultModel: "openrouter/auto",
319
+ baseUrl: "https://openrouter.ai/api/v1"
320
+ },
321
+ // Self-hosted: geen vaste host, de user vult er zelf een in op het account.
322
+ // Praten allemaal Chat Completions, dus dezelfde adapter.
323
+ {
324
+ id: "ollama",
325
+ label: "Ollama (self-hosted)",
326
+ capabilities: ["chat"],
327
+ defaultModel: "llama3.3"
328
+ },
329
+ {
330
+ id: "openai-compatible",
331
+ label: "OpenAI-compatible (eigen endpoint)",
332
+ capabilities: ["chat"],
333
+ // vLLM, LM Studio, een eigen gateway — model en host komen van de user.
334
+ defaultModel: "default"
335
+ }
336
+ ];
337
+ function c(e) {
338
+ return g.find((t) => t.id === e);
208
339
  }
209
- const b = ["done", "escalated", "failed", "timed_out", "stopped"], N = ["awaiting_approval", "awaiting_reply"];
210
- function D(e) {
211
- return b.includes(e);
340
+ function re(e) {
341
+ return c(e)?.label ?? e;
212
342
  }
213
- function X(e) {
214
- return D(e);
343
+ function ae(e, t = "gpt-5-mini") {
344
+ return c(e)?.defaultModel ?? t;
345
+ }
346
+ function se(e) {
347
+ return g.filter((t) => t.capabilities.includes(e));
215
348
  }
216
- function J(e) {
217
- return N.includes(e);
349
+ function oe(e) {
350
+ const t = c(e);
351
+ return !!t && !t.baseUrl;
218
352
  }
219
- const Q = /* @__PURE__ */ new Set(["mail", "message"]);
220
- function R(e, t) {
353
+ const ie = /* @__PURE__ */ new Set(["mail", "message"]);
354
+ function y(e, t) {
221
355
  const n = e.settings?.signatures?.[t];
222
356
  return !n || !n.enabled || !n.body || n.body.trim() === "" ? null : n;
223
357
  }
224
- function Z(e, t, n, r = "html") {
358
+ function le(e, t, n, r = "html") {
225
359
  if (!e) return n;
226
- const s = R(e, t);
227
- return s ? `${n}${r === "text" ? `
360
+ const a = y(e, t);
361
+ return a ? `${n}${r === "text" ? `
228
362
 
229
- ` : t === "mail" ? "<br><br>-- <br>" : "<br><br>"}${s.body}` : n;
363
+ ` : t === "mail" ? "<br><br>-- <br>" : "<br><br>"}${a.body}` : n;
230
364
  }
231
- function v(e) {
365
+ function ce(e) {
232
366
  return e.endpoints ?? [];
233
367
  }
234
- const ee = (e) => !!e.assignedUserId || !!e.assignedInboxId, te = (e) => e.status === "closed", ne = (e) => ({
368
+ const ue = (e) => !!e.assignedUserId || !!e.assignedInboxId, de = (e) => e.status === "closed", pe = (e) => ({
235
369
  urgent: 10,
236
370
  high: 5,
237
371
  normal: 2,
238
372
  low: 1
239
- })[e.priority] || 0, re = (e, t = 30) => e.title?.length <= t ? e.title : `${e.title.substring(0, t)}...`;
240
- function se(e, t) {
373
+ })[e.priority] || 0, Ee = (e, t = 30) => e.title?.length <= t ? e.title : `${e.title.substring(0, t)}...`;
374
+ function ge(e, t) {
241
375
  return e.lastActivityAt ? !(e.seenBy ?? []).includes(t) : !1;
242
376
  }
243
- const L = [
377
+ const fe = 2e3, be = 500, Ae = 12e3, me = 4e3, C = [
244
378
  /<blockquote/i,
245
379
  /class="?gmail_quote/i,
246
380
  // Gmail quote container
@@ -253,107 +387,107 @@ const L = [
253
387
  // Outlook underscore separator before "From:"
254
388
  /\bOn\b[\s\S]{0,200}?\bwrote:/i
255
389
  // Gmail "On <date> <name> wrote:"
256
- ], u = /^\s*(?:>+\s*)?(?:from|van|sent|verzonden|to|aan|cc|subject|onderwerp|date|datum)\s*:/i, O = /^\s*(?:on|op)\b.{0,200}?\b(?:wrote|schreef|geschreven)\s*:?\s*$/i;
257
- function y(e) {
390
+ ], u = /^\s*(?:>+\s*)?(?:from|van|sent|verzonden|to|aan|cc|subject|onderwerp|date|datum)\s*:/i, U = /^\s*(?:on|op)\b.{0,200}?\b(?:wrote|schreef|geschreven)\s*:?\s*$/i;
391
+ function k(e) {
258
392
  const t = e.split(`
259
393
  `);
260
394
  for (let n = 0; n < t.length; n++)
261
- if (O.test(t[n]) || u.test(t[n]) && t.slice(n + 1, n + 4).some((r) => u.test(r)))
395
+ if (U.test(t[n]) || u.test(t[n]) && t.slice(n + 1, n + 4).some((r) => u.test(r)))
262
396
  return t.slice(0, n).join(`
263
397
  `).trim();
264
398
  return e;
265
399
  }
266
- function c(e) {
400
+ function d(e) {
267
401
  let t = e;
268
402
  return t = t.replace(/<(script|style)[\s\S]*?<\/\1>/gi, ""), t = t.replace(/<br\s*\/?>/gi, `
269
403
  `), t = t.replace(/<\/(p|div|li|tr|h[1-6]|ul|ol|table)>/gi, `
270
404
  `), t = t.replace(/<[^>]+>/g, ""), t = t.replace(/<[^>]*$/, ""), t;
271
405
  }
272
- function d(e) {
406
+ function p(e) {
273
407
  return e.replace(/&nbsp;/gi, " ").replace(/&lt;/gi, "<").replace(/&gt;/gi, ">").replace(/&quot;/gi, '"').replace(/&#39;/gi, "'").replace(/&#(\d+);/g, (t, n) => String.fromCharCode(Number(n))).replace(/&amp;/gi, "&");
274
408
  }
275
- function p(e) {
409
+ function E(e) {
276
410
  return e.replace(/\r/g, "").replace(/[ \t]+/g, " ").replace(/ *\n */g, `
277
411
  `).replace(/\n{3,}/g, `
278
412
 
279
413
  `).trim();
280
414
  }
281
- function ae(e) {
415
+ function _e(e) {
282
416
  if (typeof e != "string" || !e) return "";
283
417
  let t = e.length;
284
- for (const a of L) {
285
- const o = e.search(a);
418
+ for (const s of C) {
419
+ const o = e.search(s);
286
420
  o >= 0 && o < t && (t = o);
287
421
  }
288
422
  const n = e.slice(0, t);
289
- let r = p(d(c(n)));
290
- return r || (r = p(d(c(e)))), y(r) || r;
423
+ let r = E(p(d(n)));
424
+ return r || (r = E(p(d(e)))), k(r) || r;
291
425
  }
292
- const C = /(^|[._+-])(no-?reply|noreply|donotreply|do-not-reply|newsletter|nieuwsbrief|mailer|mailing|notifications?|bounce|postmaster|marketing)@/i, M = /\b(unsubscribe|afmelden|uitschrijven|opt[\s-]?out|manage (your )?preferences|geen (e-?mails?|nieuwsbrief|berichten) meer)\b/i;
293
- function oe(e, t) {
294
- return !!(e && C.test(e) || t && M.test(t));
426
+ const P = /(^|[._+-])(no-?reply|noreply|donotreply|do-not-reply|newsletter|nieuwsbrief|mailer|mailing|notifications?|bounce|postmaster|marketing)@/i, $ = /\b(unsubscribe|afmelden|uitschrijven|opt[\s-]?out|manage (your )?preferences|geen (e-?mails?|nieuwsbrief|berichten) meer)\b/i;
427
+ function Ie(e, t) {
428
+ return !!(e && P.test(e) || t && $.test(t));
295
429
  }
296
- const h = 3, P = 600;
297
- function ie(e, t) {
298
- return e >= h || t >= P;
430
+ const G = 3, V = 600;
431
+ function Te(e, t) {
432
+ return e >= G || t >= V;
299
433
  }
300
- function U(e, t) {
301
- const n = new Set(e.disabledIntents ?? []), r = e.intentOverrides ?? {}, s = t.intents.filter((o) => !n.has(o.intent)).map((o) => k(o, r[o.intent]));
302
- if (!e.extraIntents || e.extraIntents.length === 0) return s;
303
- const a = new Set(s.map((o) => o.intent));
434
+ function w(e, t) {
435
+ const n = new Set(e.disabledIntents ?? []), r = e.intentOverrides ?? {}, a = t.intents.filter((o) => !n.has(o.intent)).map((o) => x(o, r[o.intent]));
436
+ if (!e.extraIntents || e.extraIntents.length === 0) return a;
437
+ const s = new Set(a.map((o) => o.intent));
304
438
  for (const o of e.extraIntents)
305
- a.has(o.intent) || (s.push(o), a.add(o.intent));
306
- return s;
439
+ s.has(o.intent) || (a.push(o), s.add(o.intent));
440
+ return a;
307
441
  }
308
- function $(e, t) {
442
+ function H(e, t) {
309
443
  const n = {};
310
444
  for (const r of e.intents) {
311
445
  if (!r.togglable) continue;
312
- const s = t?.[r.intent];
313
- n[r.intent] = s ?? r.defaultEnabled ?? !0;
446
+ const a = t?.[r.intent];
447
+ n[r.intent] = a ?? r.defaultEnabled ?? !0;
314
448
  }
315
449
  return n;
316
450
  }
317
- function le(e, t) {
318
- const n = $(e, t);
451
+ function Se(e, t) {
452
+ const n = H(e, t);
319
453
  return Object.entries(n).filter(([, r]) => !r).map(([r]) => r);
320
454
  }
321
- function k(e, t) {
455
+ function x(e, t) {
322
456
  return t ? {
323
457
  intent: t.intent ?? e.intent,
324
458
  targetSchemes: t.targetSchemes ?? e.targetSchemes,
325
459
  transport: t.transport ?? e.transport
326
460
  } : e;
327
461
  }
328
- function ue(e, t) {
462
+ function Me(e, t) {
329
463
  const n = [];
330
464
  for (const r of e) {
331
465
  if (!r.enabled) continue;
332
- const s = t[r.providerId];
333
- if (s)
334
- for (const a of U(r, s))
335
- n.push({ channel: r, description: s, capability: a });
466
+ const a = t[r.providerId];
467
+ if (a)
468
+ for (const s of w(r, a))
469
+ n.push({ channel: r, description: a, capability: s });
336
470
  }
337
471
  return n;
338
472
  }
339
- function ce(e, t) {
473
+ function De(e, t) {
340
474
  return t.filter((n) => n.capability.intent === e);
341
475
  }
342
- function w(e, t) {
476
+ function B(e, t) {
343
477
  return t.filter((n) => n.capability.targetSchemes.includes(e));
344
478
  }
345
- function de(e, t) {
346
- return w(e.scheme, t);
479
+ function Ne(e, t) {
480
+ return B(e.scheme, t);
347
481
  }
348
- function pe(e, t, n) {
482
+ function Re(e, t, n) {
349
483
  const r = [];
350
- for (const s of e)
351
- for (const a of n)
352
- a.capability.intent === t && a.capability.targetSchemes.includes(s.scheme) && r.push({ channelIntent: a, endpoint: s });
484
+ for (const a of e)
485
+ for (const s of n)
486
+ s.capability.intent === t && s.capability.targetSchemes.includes(a.scheme) && r.push({ channelIntent: s, endpoint: a });
353
487
  return r;
354
488
  }
355
- var G = /* @__PURE__ */ ((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))(G || {});
356
- const Ee = "message_window", ge = "message_templates", fe = {
489
+ var F = /* @__PURE__ */ ((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))(F || {});
490
+ const Oe = "message_window", he = "message_templates", Le = {
357
491
  // E-mail
358
492
  FROM: "from",
359
493
  TO: "to",
@@ -370,59 +504,73 @@ const Ee = "message_window", ge = "message_templates", fe = {
370
504
  // Voice/conference
371
505
  HOST: "host",
372
506
  PARTICIPANT: "participant"
373
- }, Ae = (e, t) => ({ intent: e, ...t }), _e = "folder_management", Ie = "remote_search", Te = { ok: !1, code: "UNSUPPORTED", message: "Provider does not support this op" }, Se = 9e4, me = "installation-id";
507
+ }, ye = (e, t) => ({ intent: e, ...t }), Ce = "folder_management", Ue = "remote_search", ke = { ok: !1, code: "UNSUPPORTED", message: "Provider does not support this op" }, Pe = 9e4, $e = "installation-id";
374
508
  export {
375
- K as CLASSIFY_VARS,
376
- x as CORE_DIMENSIONS,
377
- G as CommunicationScheme,
378
- _e as FEATURE_FOLDER_MANAGEMENT,
379
- Ie as FEATURE_REMOTE_SEARCH,
380
- me as INSTALLATION_HEADER,
381
- fe as InteractionParticipantRole,
382
- N as PAUSED_RUN_STATUSES,
383
- Se as PRESENCE_ONLINE_WINDOW_MS,
384
- ge as PROVIDER_FEATURE_MESSAGE_TEMPLATES,
385
- Ee as PROVIDER_FEATURE_MESSAGE_WINDOW,
386
- Q as SIGNATURE_INTENTS,
387
- P as SUMMARY_MIN_LAST_CHARS,
388
- h as SUMMARY_MIN_MESSAGES,
389
- b as TERMINAL_RUN_STATUSES,
390
- W as TRIGGER_VARS,
391
- Te as UNSUPPORTED,
392
- m as actingIdentityKey,
393
- z as actorKey,
394
- Z as applySignature,
395
- H as buildActivityPreview,
396
- ue as buildChannelIntents,
397
- ae as cleanMessageText,
398
- Ae as defineIntent,
399
- le as disabledIntentsFromCapabilities,
400
- de as filterByEndpoint,
401
- ce as filterByIntent,
402
- w as filterByTargetScheme,
403
- B as flattenLocales,
509
+ g as AI_VENDORS,
510
+ J as CLASSIFY_VARS,
511
+ W as CORE_DIMENSIONS,
512
+ F as CommunicationScheme,
513
+ me as DEFAULT_MEMORY_BUDGET,
514
+ Ce as FEATURE_FOLDER_MANAGEMENT,
515
+ Ue as FEATURE_REMOTE_SEARCH,
516
+ $e as INSTALLATION_HEADER,
517
+ Le as InteractionParticipantRole,
518
+ Ae as MAX_MEMORY_BUDGET,
519
+ fe as MAX_MEMORY_CHARS,
520
+ be as MIN_MEMORY_BUDGET,
521
+ h as PAUSED_RUN_STATUSES,
522
+ Pe as PRESENCE_ONLINE_WINDOW_MS,
523
+ he as PROVIDER_FEATURE_MESSAGE_TEMPLATES,
524
+ Oe as PROVIDER_FEATURE_MESSAGE_WINDOW,
525
+ ie as SIGNATURE_INTENTS,
526
+ V as SUMMARY_MIN_LAST_CHARS,
527
+ G as SUMMARY_MIN_MESSAGES,
528
+ O as TERMINAL_RUN_STATUSES,
529
+ z as TRIGGER_VARS,
530
+ ke as UNSUPPORTED,
531
+ S as USAGE_DIMENSIONS,
532
+ M as USAGE_DIMENSION_IDS,
533
+ R as actingIdentityKey,
534
+ ee as actorKey,
535
+ ae as aiVendorDefaultModel,
536
+ re as aiVendorLabel,
537
+ oe as aiVendorNeedsBaseUrl,
538
+ se as aiVendorsWith,
539
+ le as applySignature,
540
+ j as buildActivityPreview,
541
+ Me as buildChannelIntents,
542
+ _e as cleanMessageText,
543
+ ye as defineIntent,
544
+ Se as disabledIntentsFromCapabilities,
545
+ Ne as filterByEndpoint,
546
+ De as filterByIntent,
547
+ B as filterByTargetScheme,
548
+ c as findAiVendor,
549
+ Y as flattenLocales,
404
550
  _ as floorToPeriod,
405
- S as formatActingIdentity,
406
- A as formatPeriod,
407
- V as getActivitySeenUserIds,
408
- v as getContactEndpoints,
409
- re as getShortTitle,
410
- ne as getUrgencyScore,
411
- j as humanizeAction,
412
- ee as isAssigned,
413
- te as isClosed,
414
- se as isInteractionUnseen,
415
- oe as isLikelyBulk,
416
- J as isPaused,
417
- X as isRetryable,
418
- D as isTerminal,
419
- ie as isThreadLongEnough,
420
- pe as matchContactToIntents,
421
- Y as parseActingIdentity,
422
- F as periodsInRange,
423
- q as playbookActor,
424
- $ as resolveAccountCapabilities,
425
- U as resolveChannelIntents,
426
- R as resolveSignature,
427
- E as stripHtml
551
+ N as formatActingIdentity,
552
+ m as formatPeriod,
553
+ v as getActivitySeenUserIds,
554
+ ce as getContactEndpoints,
555
+ Ee as getShortTitle,
556
+ pe as getUrgencyScore,
557
+ X as humanizeAction,
558
+ ue as isAssigned,
559
+ de as isClosed,
560
+ ge as isInteractionUnseen,
561
+ Ie as isLikelyBulk,
562
+ ne as isPaused,
563
+ te as isRetryable,
564
+ L as isTerminal,
565
+ Te as isThreadLongEnough,
566
+ Re as matchContactToIntents,
567
+ Q as parseActingIdentity,
568
+ K as periodsInRange,
569
+ Z as playbookActor,
570
+ H as resolveAccountCapabilities,
571
+ w as resolveChannelIntents,
572
+ y as resolveSignature,
573
+ f as stripHtml,
574
+ D as usageMetricId,
575
+ q as usageMetrics
428
576
  };
@@ -59,6 +59,23 @@ export interface AiToolDescriptor extends AiTool {
59
59
  * overal.
60
60
  */
61
61
  surface?: "interactive";
62
+ /**
63
+ * Reikt deze tool naar **buiten** de organisatie, of blijft het intern?
64
+ *
65
+ * `"outward"` = er gaat iets de deur uit of er verschijnt iets bij een derde: een mail
66
+ * versturen, een ticket aanmaken, in Slack posten. Twee autonome playbooks die dat
67
+ * tegelijk op hetzelfde gesprek doen leveren dubbele berichten aan een klant, en dáár
68
+ * hoort een mens tussen te staan.
69
+ *
70
+ * `"internal"` = het resultaat blijft binnen het platform: een attribuut, een
71
+ * geheugen-item, een notitie. Twee playbooks die dat tegelijk doen zitten elkaar niet in
72
+ * de weg — verschillende rijen, en de concurrency wordt al door de store afgehandeld.
73
+ *
74
+ * **Afwezig betekent `"outward"`.** Bewust de voorzichtige kant: een tool die niets
75
+ * declareert hoort niet stilzwijgend als onschuldig te gelden. Zie
76
+ * `shouldDemoteAuto` in de playbook-engine, dat hierop de suggest-degradatie bepaalt.
77
+ */
78
+ effect?: "internal" | "outward";
62
79
  }
63
80
  /**
64
81
  * Body van `POST /provider/ai-tools/invoke`. Vervangt vijf inline structurele
@@ -1,9 +1,26 @@
1
+ import { OwnerScope } from '../entities/contact/types';
1
2
  import { ActingIdentity } from './identity';
2
3
  /** Access decision + whether the scope anchor is team-shared + the SSE audience. */
3
4
  export interface ScopeAuth {
4
5
  allowed: boolean;
5
6
  shared?: boolean;
6
7
  audience?: string[];
8
+ /**
9
+ * Van wie deze resource is, zoals de eigenaar-app het ziet: een persoonlijke mailbox, een
10
+ * teaminbox, of de organisatie.
11
+ *
12
+ * Los van `shared` en `audience`, die iets anders zeggen. `shared` gaat over of een
13
+ * AI-gesprek op deze scope met het team gedeeld wordt (voor een interactie altijd waar), en
14
+ * `audience` is de momentane ledenlijst voor SSE. Dit veld gaat over **eigenaarschap**, en
15
+ * dat is wat een consument nodig heeft om te beslissen of afgeleide kennis breder mag dan
16
+ * de resource zelf.
17
+ *
18
+ * Eerste consument is de geheugenlaag: een org-brede geheugensoort wordt bij het schrijven
19
+ * versmald naar `audience` als het bronsubject persoonlijk is, zodat een gesprek uit iemands
20
+ * eigen mailbox niet in de cross-klant-zoekresultaten van een collega opduikt. Afwezig =
21
+ * onbekend, en dat wordt daar als "versmallen" gelezen: de veilige kant.
22
+ */
23
+ ownerScope?: OwnerScope;
7
24
  }
8
25
  /** GET /provider/scope/describe response: the scopeKey prefixes an app owns. */
9
26
  export interface ScopeDescribe {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opencxh/domain",
3
- "version": "1.126.0",
3
+ "version": "1.128.0",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",