@opencxh/domain 1.91.0 → 1.94.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/entities/activity/types.d.ts +36 -1
- package/dist/entities/ai-profile/types.d.ts +16 -0
- package/dist/entities/analytics/dashboard.d.ts +58 -0
- package/dist/entities/analytics/dimensions.d.ts +57 -0
- package/dist/entities/analytics/fact.d.ts +58 -0
- package/dist/entities/analytics/index.d.ts +7 -0
- package/dist/entities/analytics/metric.d.ts +38 -0
- package/dist/entities/analytics/period.d.ts +7 -0
- package/dist/entities/analytics/report.d.ts +75 -0
- package/dist/entities/analytics/source.d.ts +52 -0
- package/dist/entities/interaction/types.d.ts +6 -0
- package/dist/entities/playbook/index.d.ts +1 -0
- package/dist/entities/playbook/types.d.ts +154 -0
- package/dist/index.cjs +7 -2
- package/dist/index.d.ts +3 -0
- package/dist/index.js +168 -81
- package/dist/text/message.d.ts +8 -0
- package/package.json +1 -1
|
@@ -3,7 +3,7 @@ import { TranscriptSegment } from '../../platform/media';
|
|
|
3
3
|
export type CallStatus = "new" | "connecting" | "ringing" | "connected" | "held" | "ended" | "failed";
|
|
4
4
|
export type CallDirection = "inbound" | "outbound";
|
|
5
5
|
export type CallType = "audio" | "video" | "data" | "screen-share";
|
|
6
|
-
export type ActivityType = "VOICE_CALL_STARTED" | "VOICE_CALL_ANSWERED" | "VOICE_CALL_HOLD" | "VOICE_CALL_UNHOLD" | "VOICE_CALL_ENDED" | "VOICE_CALL_MISSED" | "VOICE_CALL_VOICEMAIL" | "VIDEO_CALL_STARTED" | "VIDEO_CALL_ANSWERED" | "VIDEO_CALL_HOLD" | "VIDEO_CALL_UNHOLD" | "VIDEO_CALL_ENDED" | "VIDEO_CALL_MISSED" | "EMAIL_RECEIVED" | "EMAIL_SENT" | "CHAT_MESSAGE_SENT" | "CHAT_MESSAGE_RECEIVED" | "CHAT_MEMBER_JOINED" | "CHAT_MEMBER_LEFT" | "CHAT_RENAMED" | "CHAT_CALL_STARTED" | "CHAT_CALL_ENDED" | "CHAT_EVENT" | "AI_MESSAGE_ADDED" | "MEETING_SCHEDULED" | "MEETING_STARTED" | "MEETING_ENDED" | "MEETING_PARTICIPANT_JOINED" | "MEETING_PARTICIPANT_LEFT" | "COMMENT_ADDED" | "FILE_UPLOADED" | "INTERACTION_CREATED" | "INTERACTION_STATUS_CHANGED" | "INTERACTION_ASSIGNED" | "VOICE_CALL_FAILED" | "VIDEO_CALL_FAILED" | "TRANSCRIPT_ADDED";
|
|
6
|
+
export type ActivityType = "VOICE_CALL_STARTED" | "VOICE_CALL_ANSWERED" | "VOICE_CALL_HOLD" | "VOICE_CALL_UNHOLD" | "VOICE_CALL_ENDED" | "VOICE_CALL_MISSED" | "VOICE_CALL_VOICEMAIL" | "VIDEO_CALL_STARTED" | "VIDEO_CALL_ANSWERED" | "VIDEO_CALL_HOLD" | "VIDEO_CALL_UNHOLD" | "VIDEO_CALL_ENDED" | "VIDEO_CALL_MISSED" | "EMAIL_RECEIVED" | "EMAIL_SENT" | "CHAT_MESSAGE_SENT" | "CHAT_MESSAGE_RECEIVED" | "CHAT_MEMBER_JOINED" | "CHAT_MEMBER_LEFT" | "CHAT_RENAMED" | "CHAT_CALL_STARTED" | "CHAT_CALL_ENDED" | "CHAT_EVENT" | "AI_MESSAGE_ADDED" | "AI_ACTION_PROPOSED" | "PLAYBOOK_STARTED" | "PLAYBOOK_COMPLETED" | "PLAYBOOK_ESCALATED" | "MEETING_SCHEDULED" | "MEETING_STARTED" | "MEETING_ENDED" | "MEETING_PARTICIPANT_JOINED" | "MEETING_PARTICIPANT_LEFT" | "COMMENT_ADDED" | "FILE_UPLOADED" | "INTERACTION_CREATED" | "INTERACTION_STATUS_CHANGED" | "INTERACTION_ASSIGNED" | "VOICE_CALL_FAILED" | "VIDEO_CALL_FAILED" | "TRANSCRIPT_ADDED";
|
|
7
7
|
export interface Attachment {
|
|
8
8
|
id: string;
|
|
9
9
|
filename: string;
|
|
@@ -183,6 +183,29 @@ export type AIMessageAddedPayload = {
|
|
|
183
183
|
accountId?: string;
|
|
184
184
|
model?: string;
|
|
185
185
|
};
|
|
186
|
+
/**
|
|
187
|
+
* A playbook proposes one or more actions for a human to approve/reject (autonomy:
|
|
188
|
+
* suggest). Rendered inline in the interaction timeline; approve/reject call the ai
|
|
189
|
+
* app's `playbook-run` endpoints. System-authored, so it does not re-trigger playbooks.
|
|
190
|
+
*/
|
|
191
|
+
export type AIActionProposedPayload = {
|
|
192
|
+
runId: string;
|
|
193
|
+
playbookId?: string;
|
|
194
|
+
playbookName?: string;
|
|
195
|
+
/** The held actions, e.g. { action: "tool:mcp__jira__create_issue", params }. */
|
|
196
|
+
actions: {
|
|
197
|
+
action: string;
|
|
198
|
+
params?: Record<string, unknown>;
|
|
199
|
+
}[];
|
|
200
|
+
status: "pending" | "approved" | "rejected";
|
|
201
|
+
};
|
|
202
|
+
/** Playbook lifecycle marker in the interaction timeline (system-authored). */
|
|
203
|
+
export type PlaybookLifecyclePayload = {
|
|
204
|
+
runId: string;
|
|
205
|
+
playbookId?: string;
|
|
206
|
+
playbookName?: string;
|
|
207
|
+
status?: string;
|
|
208
|
+
};
|
|
186
209
|
export type Activity = (BaseActivity & {
|
|
187
210
|
type: "VOICE_CALL_STARTED";
|
|
188
211
|
payload: VoiceCallPayload;
|
|
@@ -264,6 +287,18 @@ export type Activity = (BaseActivity & {
|
|
|
264
287
|
}) | (BaseActivity & {
|
|
265
288
|
type: "AI_MESSAGE_ADDED";
|
|
266
289
|
payload: AIMessageAddedPayload;
|
|
290
|
+
}) | (BaseActivity & {
|
|
291
|
+
type: "AI_ACTION_PROPOSED";
|
|
292
|
+
payload: AIActionProposedPayload;
|
|
293
|
+
}) | (BaseActivity & {
|
|
294
|
+
type: "PLAYBOOK_STARTED";
|
|
295
|
+
payload: PlaybookLifecyclePayload;
|
|
296
|
+
}) | (BaseActivity & {
|
|
297
|
+
type: "PLAYBOOK_COMPLETED";
|
|
298
|
+
payload: PlaybookLifecyclePayload;
|
|
299
|
+
}) | (BaseActivity & {
|
|
300
|
+
type: "PLAYBOOK_ESCALATED";
|
|
301
|
+
payload: PlaybookLifecyclePayload;
|
|
267
302
|
}) | (BaseActivity & {
|
|
268
303
|
type: "MEETING_SCHEDULED";
|
|
269
304
|
payload: MeetingPayload;
|
|
@@ -22,6 +22,22 @@ export interface AIProfile<T extends Record<string, any> = Record<string, any>>
|
|
|
22
22
|
predefinedPrompts?: PredefinedPrompt[];
|
|
23
23
|
/** Namespaced tool names this profile may use (default: none enabled). */
|
|
24
24
|
enabledTools?: string[];
|
|
25
|
+
/**
|
|
26
|
+
* Per-tool confirmation policy. Stored as a LIST (not a keyed map) on purpose:
|
|
27
|
+
* namespaced tool names contain "__", and the SDK client transforms every
|
|
28
|
+
* object KEY between camelCase/snake_case — which mangles map keys (e.g.
|
|
29
|
+
* `communication__compose_email` -> `communication_ComposeEmail`) so they no
|
|
30
|
+
* longer match `enabledTools`. As a list the tool name lives in a string VALUE,
|
|
31
|
+
* which the transform leaves untouched.
|
|
32
|
+
*
|
|
33
|
+
* Under autonomy "suggest", write tools are held for approval; reads run freely.
|
|
34
|
+
* A tool absent from this list defaults to "write" (safe). Also usable by the
|
|
35
|
+
* interactive assistant's requiresConfirmation flow.
|
|
36
|
+
*/
|
|
37
|
+
toolPolicy?: {
|
|
38
|
+
name: string;
|
|
39
|
+
policy: "read" | "write";
|
|
40
|
+
}[];
|
|
25
41
|
/**
|
|
26
42
|
* Whether the acting user's PERSONAL MCP tools (their own per-user connections)
|
|
27
43
|
* are available on this profile, on top of the admin-curated `enabledTools`.
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { OwnerScope } from '../contact/types';
|
|
2
|
+
import { DimensionId } from './dimensions';
|
|
3
|
+
import { WidgetConfig } from './report';
|
|
4
|
+
/**
|
|
5
|
+
* A dashboard SECTION, declaratively. The analytics frontend runs the right widget(s) for the viz and
|
|
6
|
+
* renders the matching component. `title` and the parent def's `title` are i18n keys resolved against the
|
|
7
|
+
* merged locale bundle the owning app ships (see AnalyticsSourceDescription.locales).
|
|
8
|
+
*/
|
|
9
|
+
export type SectionViz = "kpis" | "line" | "bar" | "heatmap" | "table";
|
|
10
|
+
export interface DashboardSection {
|
|
11
|
+
viz: SectionViz;
|
|
12
|
+
/** i18n key (namespaced per owning app, e.g. "comms.dash.busiest_times"). */
|
|
13
|
+
title?: string;
|
|
14
|
+
/** kpis: one card per id; line: one series per id; bar/heatmap: switchable set; table: columns. */
|
|
15
|
+
metricIds: string[];
|
|
16
|
+
/** Row dimension for bar/table (line/heatmap imply the time axis). */
|
|
17
|
+
groupBy?: DimensionId;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* A preset dashboard shipped by an owning app (or by analytics for cross-source ones). Discovered like
|
|
21
|
+
* metrics/dimensions via /provider/analytics/describe; the frontend composes all sources' dashboards.
|
|
22
|
+
*/
|
|
23
|
+
export interface DashboardDef {
|
|
24
|
+
/** Stable, ideally app-namespaced key, e.g. "comms.workload". */
|
|
25
|
+
key: string;
|
|
26
|
+
/** i18n key for the display name (resolved via the source's locale bundle). */
|
|
27
|
+
title: string;
|
|
28
|
+
sections: DashboardSection[];
|
|
29
|
+
/** Owning app (filled by discovery); handy for grouping in the list. */
|
|
30
|
+
source?: string;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* A single localized string. Kept as a flat LIST (not a lang->key->value map) because the app-sdk http
|
|
34
|
+
* client mangles object KEYS (snake<->camel) — i18n keys with underscores/dots as map keys get corrupted
|
|
35
|
+
* (e.g. "comms.dash.resolution_over_time"). As list VALUES they pass through untouched.
|
|
36
|
+
*/
|
|
37
|
+
export interface LocaleString {
|
|
38
|
+
lang: string;
|
|
39
|
+
key: string;
|
|
40
|
+
value: string;
|
|
41
|
+
}
|
|
42
|
+
export type LocaleBundle = LocaleString[];
|
|
43
|
+
/** Authoring convenience: turn a nested { lang: { key: value } } map into a transport-safe LocaleBundle. */
|
|
44
|
+
export declare function flattenLocales(nested: Record<string, Record<string, string>>): LocaleBundle;
|
|
45
|
+
/**
|
|
46
|
+
* Persisted, user-saved dashboard (future: the custom builder). Distinct from DashboardDef: it stores
|
|
47
|
+
* fully-resolved widget configs rather than a preset template.
|
|
48
|
+
*/
|
|
49
|
+
export interface DashboardConfig {
|
|
50
|
+
id: string;
|
|
51
|
+
organizationId: string;
|
|
52
|
+
name: string;
|
|
53
|
+
/** Reuse the platform's ownerScope (org/team/personal). */
|
|
54
|
+
ownerScope?: OwnerScope;
|
|
55
|
+
widgets: WidgetConfig[];
|
|
56
|
+
createdAt?: number;
|
|
57
|
+
updatedAt?: number;
|
|
58
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dimensions — the axes you group or filter reports by.
|
|
3
|
+
*
|
|
4
|
+
* Dimensions are declared/discovered exactly like metrics (see ./metric): there is NO closed enum.
|
|
5
|
+
* Two tiers:
|
|
6
|
+
* - CORE_DIMENSIONS (below): shared and cross-app, with the same key space everywhere
|
|
7
|
+
* (`agent` = userId, `team` = teamId, `channel` = the communication channelId, …). Every source
|
|
8
|
+
* app may emit facts on these. Needed so you can group across sources (comms + voice on `agent`).
|
|
9
|
+
* - App-owned dimensions: declared by the owning app via `GET /analytics/describe`, namespaced
|
|
10
|
+
* (`comms.tag`, `comms.topic`, `comms.cf.<field>`). Only that app emits facts on them.
|
|
11
|
+
*
|
|
12
|
+
* Contract note: a core dimension only merges correctly across sources when every source uses the
|
|
13
|
+
* SAME canonical entity id. An app that cannot guarantee that must declare an app-owned dimension
|
|
14
|
+
* (e.g. `voice.line`) instead of emitting on a core dim.
|
|
15
|
+
*/
|
|
16
|
+
/** Dimension id: core (e.g. "agent") or app-namespaced (e.g. "comms.topic"). Open string, not an enum. */
|
|
17
|
+
export type DimensionId = string;
|
|
18
|
+
/** Who handled the work. Core dimension "handledBy"; derived from activity.author.type. NOT separate metrics. */
|
|
19
|
+
export type HandledBy = "teammate" | "autopilot" | "ai_assistant";
|
|
20
|
+
/** How the frontend turns a raw dimension key into a display label. */
|
|
21
|
+
export type DimensionLabelSource = "user" | "team" | "inbox" | "channel" | "topic" | "raw";
|
|
22
|
+
export interface DimensionDefinition {
|
|
23
|
+
id: DimensionId;
|
|
24
|
+
label: string;
|
|
25
|
+
/**
|
|
26
|
+
* Multi-valued: one source record can map to several values (e.g. tags). The rollup then fans out
|
|
27
|
+
* (one fact per value). NB: totals over a multi-valued dim can exceed the conversation count — that
|
|
28
|
+
* is correct/expected; see the multi-valued totals note in docs/ANALYTICS.md §13.
|
|
29
|
+
*/
|
|
30
|
+
multiValued?: boolean;
|
|
31
|
+
labelSource?: DimensionLabelSource;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Shared base dimensions — any source app may emit facts on these. Kept un-namespaced on purpose.
|
|
35
|
+
* "time" is special: its value comes from MetricFact.period, not from the dims map.
|
|
36
|
+
*/
|
|
37
|
+
export declare const CORE_DIMENSIONS: ({
|
|
38
|
+
id: string;
|
|
39
|
+
label: string;
|
|
40
|
+
labelSource: "user";
|
|
41
|
+
} | {
|
|
42
|
+
id: string;
|
|
43
|
+
label: string;
|
|
44
|
+
labelSource: "team";
|
|
45
|
+
} | {
|
|
46
|
+
id: string;
|
|
47
|
+
label: string;
|
|
48
|
+
labelSource: "inbox";
|
|
49
|
+
} | {
|
|
50
|
+
id: string;
|
|
51
|
+
label: string;
|
|
52
|
+
labelSource: "channel";
|
|
53
|
+
} | {
|
|
54
|
+
id: string;
|
|
55
|
+
label: string;
|
|
56
|
+
labelSource: "raw";
|
|
57
|
+
})[];
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { DimensionId } from './dimensions';
|
|
2
|
+
/** Rollup bucket granularity. Coarser grains (week/month) are re-bucketed from these at read time. */
|
|
3
|
+
export type Grain = "hour" | "day";
|
|
4
|
+
/**
|
|
5
|
+
* Open map of dimension-id → value (e.g. { agent: "u_123", channel: "c_9", "comms.topic": "t_4" }).
|
|
6
|
+
* A missing key means that dimension is n/a for this fact. Open map (not a fixed interface) so that a
|
|
7
|
+
* newly declared dimension costs ZERO type changes. "time" is NOT stored here — it comes from `period`.
|
|
8
|
+
*/
|
|
9
|
+
export type MetricDimensions = Record<DimensionId, string>;
|
|
10
|
+
/**
|
|
11
|
+
* The measure is an extensible, per-kind shaped payload. This is what makes a non-additive metric
|
|
12
|
+
* (distinct / percentile / min-max) an ADDITIVE extension later: you add a `kind` + a finalize branch
|
|
13
|
+
* without breaking existing facts or the contract. Every kind is bucket-mergeable (additive sums,
|
|
14
|
+
* distinct HLL-unions, distribution sums buckets, extent takes min/max) and snapshot-replaceable (§6).
|
|
15
|
+
*
|
|
16
|
+
* Phase 1 emits & folds only "additive".
|
|
17
|
+
*/
|
|
18
|
+
export type MetricMeasure =
|
|
19
|
+
/** sum/avg/rate. count metric: sum = #events; duration/currency: sum = total; count = denominator for avg. */
|
|
20
|
+
{
|
|
21
|
+
kind: "additive";
|
|
22
|
+
sum: number;
|
|
23
|
+
count: number;
|
|
24
|
+
}
|
|
25
|
+
/** distinct counts (e.g. Active accounts) — serialized HyperLogLog sketch. (later) */
|
|
26
|
+
| {
|
|
27
|
+
kind: "distinct";
|
|
28
|
+
hll: string;
|
|
29
|
+
}
|
|
30
|
+
/** histogram/percentile — counts per value bucket, e.g. { "[0,3600000)": 12, "[3600000,)": 30 }. (later) */
|
|
31
|
+
| {
|
|
32
|
+
kind: "distribution";
|
|
33
|
+
buckets: Record<string, number>;
|
|
34
|
+
}
|
|
35
|
+
/** min/max. (later) */
|
|
36
|
+
| {
|
|
37
|
+
kind: "extent";
|
|
38
|
+
min: number;
|
|
39
|
+
max: number;
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* A MetricFact is the aggregated outcome of one metric, for one period bucket, for one combination of
|
|
43
|
+
* dimension values. It is both what a source app pushes and what analytics stores. It is a declarative
|
|
44
|
+
* SNAPSHOT, not a delta — ingest replaces the measure for a given key (see docs/ANALYTICS.md §5/§6).
|
|
45
|
+
*
|
|
46
|
+
* NB: the stored server entity adds framework-generated `id/createdAt/updatedAt` (not declared in the
|
|
47
|
+
* schema) plus a `source` field so analytics can replace a bucket per source.
|
|
48
|
+
*/
|
|
49
|
+
export interface MetricFact {
|
|
50
|
+
organizationId: string;
|
|
51
|
+
metricId: string;
|
|
52
|
+
grain: Grain;
|
|
53
|
+
/** Period key: "2026-07-25T14" (hour) or "2026-07-25" (day). Feeds the "time" dimension. */
|
|
54
|
+
period: string;
|
|
55
|
+
dims: MetricDimensions;
|
|
56
|
+
/** Phase 1 carries only `{ kind: "additive", … }`; the kind follows the metric's aggregation (./metric). */
|
|
57
|
+
measure: MetricMeasure;
|
|
58
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { DimensionId } from './dimensions';
|
|
2
|
+
/**
|
|
3
|
+
* Metric register — the declarative building block. Fixed in code as a typed `const` in the owning app
|
|
4
|
+
* (no DB), but with exactly this type so we can later move to a DB registry without breaking consumers.
|
|
5
|
+
*
|
|
6
|
+
* `valueType` and `aggregation` are a deliberately BOUNDED central vocabulary (the engine must know how
|
|
7
|
+
* to fold and format). Extending them is a central, additive change — see docs/ANALYTICS.md §13.
|
|
8
|
+
*/
|
|
9
|
+
export type MetricValueType = "count" | "duration_ms" | "percent" | "currency";
|
|
10
|
+
export type MetricAggregation = "sum" | "avg" | "rate" | "distinct" | "p50" | "p90" | "min" | "max";
|
|
11
|
+
export interface MetricDefinition {
|
|
12
|
+
/**
|
|
13
|
+
* Globally-unique, app-namespaced id: "<source>.<family>.<name>", e.g. "comms.reply_time.first",
|
|
14
|
+
* "voice.calls.answered". The prefix guarantees disjoint ownership → no double counting across apps.
|
|
15
|
+
*/
|
|
16
|
+
id: string;
|
|
17
|
+
/** Human-readable (source language; the UI may localize). */
|
|
18
|
+
label: string;
|
|
19
|
+
/** Picker bucket, e.g. "Volume" | "Responsiviteit" | "Resolutie" | "Taken". */
|
|
20
|
+
category: string;
|
|
21
|
+
/** How a single value is interpreted & formatted. */
|
|
22
|
+
valueType: MetricValueType;
|
|
23
|
+
/** How measures fold across periods and grouped rows. Determines the required measure kind (./fact). */
|
|
24
|
+
aggregation: MetricAggregation;
|
|
25
|
+
/** Only for aggregation:"rate" — ids of the numerator/denominator base metrics (must exist in the catalog). */
|
|
26
|
+
numeratorId?: string;
|
|
27
|
+
denominatorId?: string;
|
|
28
|
+
/**
|
|
29
|
+
* Which dimensions are meaningful to group/filter this metric on.
|
|
30
|
+
* NB: duplicates knowledge held by the extractor — a metric that declares a dim its extractor never
|
|
31
|
+
* emits yields empty groups. Keep them in sync (docs/ANALYTICS.md §13).
|
|
32
|
+
*/
|
|
33
|
+
supportedDimensions: DimensionId[];
|
|
34
|
+
/** Optional longer description for tooltips. */
|
|
35
|
+
description?: string;
|
|
36
|
+
/** Hidden from the picker (e.g. a numerator/denominator base metric of a rate). */
|
|
37
|
+
hidden?: boolean;
|
|
38
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { Grain } from './fact';
|
|
2
|
+
/** hour -> "YYYY-MM-DDTHH", day -> "YYYY-MM-DD". */
|
|
3
|
+
export declare function formatPeriod(ms: number, grain: Grain): string;
|
|
4
|
+
/** Floor an instant to the start of its bucket (UTC). */
|
|
5
|
+
export declare function floorToPeriod(ms: number, grain: Grain): number;
|
|
6
|
+
/** All period keys from the bucket containing `start` up to and including the bucket of `end`. */
|
|
7
|
+
export declare function periodsInRange(start: number, end: number, grain: Grain): string[];
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { DimensionId } from './dimensions';
|
|
2
|
+
import { Grain } from './fact';
|
|
3
|
+
import { MetricDefinition } from './metric';
|
|
4
|
+
/**
|
|
5
|
+
* Report / widget contract (frontend → analytics). One config shape drives tables, counters and the
|
|
6
|
+
* builder. The engine reads only MetricFact rows (never a source store), groups by dims[groupBy], folds
|
|
7
|
+
* the measure and finalizes per the metric's aggregation.
|
|
8
|
+
*
|
|
9
|
+
* The widget/render vocabulary grows centrally by design — genericity lives in data/metrics/dimensions,
|
|
10
|
+
* not in presentation (docs/ANALYTICS.md §13). A future `matrix` widget (rows × cols × 1 metric) covers
|
|
11
|
+
* cross-tabs and heatmaps; distribution widgets cover histograms — both purely additive to this union.
|
|
12
|
+
*/
|
|
13
|
+
export type WidgetType = "table" | "counter";
|
|
14
|
+
/**
|
|
15
|
+
* One filter clause. Filters (and cells/totals below) are LISTS, not maps — the app-sdk http client
|
|
16
|
+
* deep-mangles every object KEY (camel<->snake), which would corrupt dimension ids / metric ids used as
|
|
17
|
+
* keys (they contain dots and mixed case). As list VALUES they pass through untouched. This mirrors the
|
|
18
|
+
* platform's existing {name,value}[] pattern (see AIProfile.toolPolicy).
|
|
19
|
+
*/
|
|
20
|
+
export interface FilterClause {
|
|
21
|
+
dimensionId: DimensionId;
|
|
22
|
+
value: string;
|
|
23
|
+
}
|
|
24
|
+
export type ReportFilters = FilterClause[];
|
|
25
|
+
/** One metric value in a row/footer. A list entry, not a map key — see FilterClause above. */
|
|
26
|
+
export interface MetricCell {
|
|
27
|
+
metricId: string;
|
|
28
|
+
value: number | null;
|
|
29
|
+
}
|
|
30
|
+
export interface DateRange {
|
|
31
|
+
from: number;
|
|
32
|
+
to: number;
|
|
33
|
+
}
|
|
34
|
+
export interface WidgetConfig {
|
|
35
|
+
type: WidgetType;
|
|
36
|
+
/** counter: exactly 1 metric; table: 1..n columns. */
|
|
37
|
+
metricIds: string[];
|
|
38
|
+
/** Row grouping. Required for table; omit for counter (grand aggregate). "time" → time-series rows. */
|
|
39
|
+
groupBy?: DimensionId;
|
|
40
|
+
filters?: ReportFilters;
|
|
41
|
+
range: DateRange;
|
|
42
|
+
grain: Grain;
|
|
43
|
+
/** counter only: also compute the delta vs. the equally-long previous period. */
|
|
44
|
+
compareToPrevious?: boolean;
|
|
45
|
+
}
|
|
46
|
+
export interface ReportColumn {
|
|
47
|
+
metricId: string;
|
|
48
|
+
/** Sent along so the UI knows valueType/label without a separate lookup. */
|
|
49
|
+
definition: MetricDefinition;
|
|
50
|
+
}
|
|
51
|
+
export interface ReportRow {
|
|
52
|
+
/** Dimension value (agentId/channelId/…) or period key when groupBy = "time". */
|
|
53
|
+
key: string;
|
|
54
|
+
/** One entry per column metric (order matches `columns`); value null = no data. */
|
|
55
|
+
cells: MetricCell[];
|
|
56
|
+
}
|
|
57
|
+
export interface TableResult {
|
|
58
|
+
type: "table";
|
|
59
|
+
groupBy: DimensionId;
|
|
60
|
+
columns: ReportColumn[];
|
|
61
|
+
rows: ReportRow[];
|
|
62
|
+
/** Footer: sum for count metrics, weighted average for avg. Suppress/label for multi-valued groupings. */
|
|
63
|
+
totals: MetricCell[];
|
|
64
|
+
}
|
|
65
|
+
export interface CounterResult {
|
|
66
|
+
type: "counter";
|
|
67
|
+
metricId: string;
|
|
68
|
+
definition: MetricDefinition;
|
|
69
|
+
value: number | null;
|
|
70
|
+
/** Filled when compareToPrevious. */
|
|
71
|
+
previous?: number | null;
|
|
72
|
+
/** (value - previous) / previous. */
|
|
73
|
+
deltaRatio?: number | null;
|
|
74
|
+
}
|
|
75
|
+
export type WidgetResult = TableResult | CounterResult;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { DashboardDef, LocaleBundle } from './dashboard';
|
|
2
|
+
import { DimensionDefinition } from './dimensions';
|
|
3
|
+
import { MetricDefinition } from './metric';
|
|
4
|
+
import { Grain, MetricFact } from './fact';
|
|
5
|
+
/**
|
|
6
|
+
* Source discovery + ingest — the app↔app contract.
|
|
7
|
+
*
|
|
8
|
+
* Each source app declares `app.role({ type: "analytics-source" })` and implements
|
|
9
|
+
* `GET /analytics/describe` → AnalyticsSourceDescription. Analytics enumerates those roles (mirroring
|
|
10
|
+
* the `ai-tool` role auto-discovery) and composes the catalog.
|
|
11
|
+
*/
|
|
12
|
+
export interface AnalyticsSourceDescription {
|
|
13
|
+
/** The declaring app's name (== manifest.name == req.source.app). */
|
|
14
|
+
source: string;
|
|
15
|
+
/** Metric definitions this app owns. */
|
|
16
|
+
metrics: MetricDefinition[];
|
|
17
|
+
/** App-owned dimensions this app owns (namespaced). Do NOT repeat core dimensions. */
|
|
18
|
+
dimensions: DimensionDefinition[];
|
|
19
|
+
/** Preset dashboards this app ships (single-source). Titles are i18n keys resolved via `locales`. */
|
|
20
|
+
dashboards?: DashboardDef[];
|
|
21
|
+
/** Locale bundle for this app's dashboard/section title keys (lang -> key -> string). */
|
|
22
|
+
locales?: LocaleBundle;
|
|
23
|
+
}
|
|
24
|
+
/** The composed catalog analytics serves to the frontend (`GET /analytics/metrics`). */
|
|
25
|
+
export interface MetricCatalog {
|
|
26
|
+
/** Merged across all sources. */
|
|
27
|
+
metrics: MetricDefinition[];
|
|
28
|
+
/** CORE_DIMENSIONS + every app-declared dimension. */
|
|
29
|
+
dimensions: DimensionDefinition[];
|
|
30
|
+
}
|
|
31
|
+
/** The composed dashboard catalog analytics serves to the frontend (`GET /dashboards`). */
|
|
32
|
+
export interface DashboardCatalog {
|
|
33
|
+
/** All sources' preset dashboards (+ analytics' own cross-source ones), in discovery order. */
|
|
34
|
+
dashboards: DashboardDef[];
|
|
35
|
+
/** Merged locale bundle across all sources, for resolving dashboard/section title keys. */
|
|
36
|
+
locales: LocaleBundle;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* A source app's rollup cron pushes its facts to `POST http://analytics/facts/ingest` (app-to-app auth,
|
|
40
|
+
* org via Bridge.tenant.id). Facts are snapshots: analytics replaces every existing fact within each
|
|
41
|
+
* (source, grain, period) by the pushed set, so a disappeared dimension leaves no orphan rows.
|
|
42
|
+
*/
|
|
43
|
+
export interface FactIngestRequest {
|
|
44
|
+
/** Caller org (== Bridge.tenant.id); included for validation. */
|
|
45
|
+
organizationId: string;
|
|
46
|
+
/** Declaring app (== req.source.app). */
|
|
47
|
+
source: string;
|
|
48
|
+
grain: Grain;
|
|
49
|
+
/** The period buckets this push fully covers — analytics replaces within each of these. */
|
|
50
|
+
periods: string[];
|
|
51
|
+
facts: MetricFact[];
|
|
52
|
+
}
|
|
@@ -93,6 +93,12 @@ export interface Interaction {
|
|
|
93
93
|
updatedAt?: number;
|
|
94
94
|
lastActivityAt?: number;
|
|
95
95
|
lastActivityPreview?: ActivityPreview;
|
|
96
|
+
/**
|
|
97
|
+
* Epoch-ms van de eerste uitgaande reactie op deze interactie. Eenmalig gezet door
|
|
98
|
+
* `applyActivityToInteraction` bij de eerste outbound-activity; drijft de first-response-time
|
|
99
|
+
* analytics-metric (firstResponseAt - createdAt). Afwezig = nog geen reactie.
|
|
100
|
+
*/
|
|
101
|
+
firstResponseAt?: number;
|
|
96
102
|
/**
|
|
97
103
|
* Provider-side folder waar deze thread onder gearchiveerd staat (mail).
|
|
98
104
|
* Gezet door de sync (folder-membership van already-engaged threads) en door
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './types';
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { OwnerScope } from '../contact/types';
|
|
2
|
+
/**
|
|
3
|
+
* Playbooks — event-driven automation with AI steps. See docs/PLAYBOOKS.md.
|
|
4
|
+
* Fase 0 covers the deterministic subset; AI steps are typed here but the
|
|
5
|
+
* executor rejects them until Fase 2.
|
|
6
|
+
*/
|
|
7
|
+
export type Autonomy = "suggest" | "auto";
|
|
8
|
+
export type PlaybookTrigger = {
|
|
9
|
+
kind: "activity";
|
|
10
|
+
activityTypes: string[];
|
|
11
|
+
/** Channel kinds: "mail" | "chat" | "voice" (derived from the activity type). */
|
|
12
|
+
channels?: string[];
|
|
13
|
+
/** Specific channel ids (e.g. per mailbox). Matched against activity.channelId. */
|
|
14
|
+
channelIds?: string[];
|
|
15
|
+
filter?: unknown;
|
|
16
|
+
} | {
|
|
17
|
+
kind: "manual";
|
|
18
|
+
};
|
|
19
|
+
export interface Guardrails {
|
|
20
|
+
maxSteps?: number;
|
|
21
|
+
maxToolCalls?: number;
|
|
22
|
+
policyCaps?: Record<string, unknown>;
|
|
23
|
+
}
|
|
24
|
+
export interface ConditionStep {
|
|
25
|
+
type: "condition";
|
|
26
|
+
/** JSONLogic expression evaluated against the run vars. */
|
|
27
|
+
if: unknown;
|
|
28
|
+
/** Gate: what to do when the condition is false. "then" = the steps that follow. */
|
|
29
|
+
onFalse: "stop" | "escalate" | "continue";
|
|
30
|
+
}
|
|
31
|
+
export interface ForEachStep {
|
|
32
|
+
type: "for-each";
|
|
33
|
+
in: string;
|
|
34
|
+
as: string;
|
|
35
|
+
body: Step[];
|
|
36
|
+
}
|
|
37
|
+
export interface LookupStep {
|
|
38
|
+
type: "lookup";
|
|
39
|
+
/** "tool:<namespaced>" (Fase 1) or a local source ("interactions"/"tasks"). */
|
|
40
|
+
source: string;
|
|
41
|
+
filter?: unknown;
|
|
42
|
+
as: string;
|
|
43
|
+
}
|
|
44
|
+
export interface ActionStep {
|
|
45
|
+
type: "action";
|
|
46
|
+
/** Local action ("task.create"/"interaction.set"/"notify.user") or "tool:<namespaced>" (Fase 1). */
|
|
47
|
+
action: string;
|
|
48
|
+
params: Record<string, unknown>;
|
|
49
|
+
autonomy?: Autonomy;
|
|
50
|
+
}
|
|
51
|
+
/** AI steps — typed in Fase 0, executed from Fase 2. */
|
|
52
|
+
export interface ClassifyStep {
|
|
53
|
+
type: "classify";
|
|
54
|
+
as: string;
|
|
55
|
+
accountId: string;
|
|
56
|
+
model: string;
|
|
57
|
+
mode: "topics" | "question" | "extract";
|
|
58
|
+
topicIds?: string[];
|
|
59
|
+
question?: string;
|
|
60
|
+
fields?: unknown;
|
|
61
|
+
instruction?: string;
|
|
62
|
+
}
|
|
63
|
+
export interface GenerateStep {
|
|
64
|
+
type: "generate";
|
|
65
|
+
profileId: string;
|
|
66
|
+
goal?: string;
|
|
67
|
+
kind: "reply" | "compose" | "note";
|
|
68
|
+
as?: string;
|
|
69
|
+
}
|
|
70
|
+
export interface AgentStep {
|
|
71
|
+
type: "agent";
|
|
72
|
+
profileId: string;
|
|
73
|
+
goal: string;
|
|
74
|
+
guardrails: Guardrails;
|
|
75
|
+
toolsOverride?: string[];
|
|
76
|
+
autonomy?: Autonomy;
|
|
77
|
+
as?: string;
|
|
78
|
+
}
|
|
79
|
+
export interface WaitForReplyStep {
|
|
80
|
+
type: "wait-for-reply";
|
|
81
|
+
deadline?: number;
|
|
82
|
+
}
|
|
83
|
+
export type Step = ConditionStep | ForEachStep | LookupStep | ActionStep | ClassifyStep | GenerateStep | AgentStep | WaitForReplyStep;
|
|
84
|
+
export interface Playbook {
|
|
85
|
+
id: string;
|
|
86
|
+
organizationId: string;
|
|
87
|
+
ownerScope: OwnerScope;
|
|
88
|
+
name: string;
|
|
89
|
+
description?: string;
|
|
90
|
+
enabled: boolean;
|
|
91
|
+
autonomy: Autonomy;
|
|
92
|
+
trigger: PlaybookTrigger;
|
|
93
|
+
steps: Step[];
|
|
94
|
+
/** Bumped on every change; runs pin to the version they started on. */
|
|
95
|
+
version: number;
|
|
96
|
+
/** Actor of autonomous actions. */
|
|
97
|
+
createdBy: string;
|
|
98
|
+
lastRunAt?: number;
|
|
99
|
+
stats?: {
|
|
100
|
+
runs: number;
|
|
101
|
+
proposals: number;
|
|
102
|
+
autoActions: number;
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
export type RunStatus = "pending" | "running" | "awaiting_approval" | "awaiting_reply" | "done" | "escalated" | "failed" | "timed_out" | "stopped";
|
|
106
|
+
export interface RunStepTrace {
|
|
107
|
+
path: string;
|
|
108
|
+
type: string;
|
|
109
|
+
ok: boolean;
|
|
110
|
+
note?: string;
|
|
111
|
+
ms?: number;
|
|
112
|
+
}
|
|
113
|
+
export interface PlaybookRun {
|
|
114
|
+
id: string;
|
|
115
|
+
organizationId: string;
|
|
116
|
+
playbookId: string;
|
|
117
|
+
playbookVersion: number;
|
|
118
|
+
interactionId?: string;
|
|
119
|
+
status: RunStatus;
|
|
120
|
+
trigger: {
|
|
121
|
+
activityType?: string;
|
|
122
|
+
activityId?: string;
|
|
123
|
+
event: Record<string, unknown>;
|
|
124
|
+
};
|
|
125
|
+
vars: Record<string, unknown>;
|
|
126
|
+
trace: RunStepTrace[];
|
|
127
|
+
/** Snapshot of the playbook steps at run start — version-pinned resume (docs 11.4). */
|
|
128
|
+
steps?: Step[];
|
|
129
|
+
/** Held action(s) awaiting human approval (autonomy: suggest) — a batch per paused step. */
|
|
130
|
+
pending?: {
|
|
131
|
+
stepIndex: number;
|
|
132
|
+
actions: {
|
|
133
|
+
action: string;
|
|
134
|
+
params: Record<string, unknown>;
|
|
135
|
+
}[];
|
|
136
|
+
};
|
|
137
|
+
/** The AI_ACTION_PROPOSED activity created for the current pending batch (for resolve on approve/reject). */
|
|
138
|
+
proposalActivityId?: string;
|
|
139
|
+
/** Parked run (awaiting_reply): where to continue + correlation + round counter. */
|
|
140
|
+
awaitFor?: {
|
|
141
|
+
interactionId?: string;
|
|
142
|
+
};
|
|
143
|
+
resumeIndex?: number;
|
|
144
|
+
rounds?: number;
|
|
145
|
+
/** Denormalized interaction audience for efficient list-filtering. */
|
|
146
|
+
audience?: string[];
|
|
147
|
+
createdBy: string;
|
|
148
|
+
approvedBy?: string;
|
|
149
|
+
result?: {
|
|
150
|
+
ok: boolean;
|
|
151
|
+
error?: string;
|
|
152
|
+
};
|
|
153
|
+
lastStepAt?: number;
|
|
154
|
+
}
|
package/dist/index.cjs
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const E=140;function p(e){return e.replace(/<style[\s\S]*?<\/style>/gi," ").replace(/<script[\s\S]*?<\/script>/gi," ").replace(/<[^>]+>/g," ").replace(/ /g," ").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,'"').replace(/'/g,"'").replace(/\s+/g," ").trim()}function b(e){const t=e.trim();return t.length<=E?t:t.slice(0,E-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 D(e){switch(e.type){case"EMAIL_RECEIVED":case"EMAIL_SENT":{const t=e.payload,n=t.bodySnippet?.trim()||p(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 N(e){return{activityId:e.id,type:e.type,snippet:b(D(e)),authorName:e.author?.name??"",direction:e.direction,createdAt:e.createdAt??Date.now()}}function O(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,o])=>o>=r&&!a.has(s)).map(([s])=>s)}const R=[{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 C(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 i(e){return e<10?`0${e}`:`${e}`}function A(e,t){const n=new Date(e),r=`${n.getUTCFullYear()}-${i(n.getUTCMonth()+1)}-${i(n.getUTCDate())}`;return t==="day"?r:`${r}T${i(n.getUTCHours())}`}function g(e,t){const n=new Date(e);return n.setUTCMinutes(0,0,0),t==="day"&&n.setUTCHours(0),n.getTime()}const L=36e5,M=864e5;function m(e,t,n){const r=n==="hour"?L:M,a=[];for(let s=g(e,n);s<=t;s+=r)a.push(A(s,n));return a}const P=new Set(["mail","message"]);function T(e,t){const n=e.settings?.signatures?.[t];return!n||!n.enabled||!n.body||n.body.trim()===""?null:n}function y(e,t,n,r="html"){if(!e)return n;const a=T(e,t);return a?`${n}${r==="text"?`
|
|
2
2
|
|
|
3
|
-
`:t==="mail"?"<br><br>-- <br>":"<br><br>"}${
|
|
3
|
+
`:t==="mail"?"<br><br>-- <br>":"<br><br>"}${a.body}`:n}function h(e){return e.endpoints??[]}const U=e=>!!e.assignedUserId||!!e.assignedInboxId,$=e=>e.status==="closed",G=e=>({urgent:10,high:5,normal:2,low:1})[e.priority]||0,H=(e,t=30)=>e.title?.length<=t?e.title:`${e.title.substring(0,t)}...`;function V(e,t){return e.lastActivityAt?!(e.seenBy??[]).includes(t):!1}const F=[/<blockquote/i,/class="?gmail_quote/i,/id="?[^"]*divRplyFwdMsg/i,/-{3,}\s*Original Message\s*-{3,}/i,/\n_{5,}\s*\n/,/\bOn\b[\s\S]{0,200}?\bwrote:/i];function c(e){let t=e;return t=t.replace(/<(script|style)[\s\S]*?<\/\1>/gi,""),t=t.replace(/<br\s*\/?>/gi,`
|
|
4
|
+
`),t=t.replace(/<\/(p|div|li|tr|h[1-6]|ul|ol|table)>/gi,`
|
|
5
|
+
`),t=t.replace(/<[^>]+>/g,""),t=t.replace(/<[^>]*$/,""),t}function u(e){return e.replace(/ /gi," ").replace(/</gi,"<").replace(/>/gi,">").replace(/"/gi,'"').replace(/'/gi,"'").replace(/&#(\d+);/g,(t,n)=>String.fromCharCode(Number(n))).replace(/&/gi,"&")}function d(e){return e.replace(/\r/g,"").replace(/[ \t]+/g," ").replace(/ *\n */g,`
|
|
6
|
+
`).replace(/\n{3,}/g,`
|
|
7
|
+
|
|
8
|
+
`).trim()}function k(e){if(typeof e!="string"||!e)return"";let t=e.length;for(const a of F){const s=e.search(a);s>=0&&s<t&&(t=s)}const n=e.slice(0,t);let r=d(u(c(n)));return r||(r=d(u(c(e)))),r}function I(e,t){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]));if(!e.extraIntents||e.extraIntents.length===0)return a;const s=new Set(a.map(o=>o.intent));for(const o of e.extraIntents)s.has(o.intent)||(a.push(o),s.add(o.intent));return a}function _(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 w(e,t){const n=_(e,t);return Object.entries(n).filter(([,r])=>!r).map(([r])=>r)}function x(e,t){return t?{intent:t.intent??e.intent,targetSchemes:t.targetSchemes??e.targetSchemes,transport:t.transport??e.transport}:e}function B(e,t){const n=[];for(const r of e){if(!r.enabled)continue;const a=t[r.providerId];if(a)for(const s of I(r,a))n.push({channel:r,description:a,capability:s})}return n}function W(e,t){return t.filter(n=>n.capability.intent===e)}function f(e,t){return t.filter(n=>n.capability.targetSchemes.includes(e))}function j(e,t){return f(e.scheme,t)}function K(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 S=(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))(S||{});const Y="message_window",q="message_templates",z={FROM:"from",TO:"to",CC:"cc",BCC:"bcc",ORGANIZER:"organizer",PRESENTER:"presenter",ATTENDEE:"attendee",OWNER:"owner",MEMBER:"member",GUEST:"guest",HOST:"host",PARTICIPANT:"participant"},J=(e,t)=>({intent:e,...t}),X="folder_management",Q="remote_search",Z={ok:!1,code:"UNSUPPORTED",message:"Provider does not support this op"},v=9e4,ee="installation-id";exports.CORE_DIMENSIONS=R;exports.CommunicationScheme=S;exports.FEATURE_FOLDER_MANAGEMENT=X;exports.FEATURE_REMOTE_SEARCH=Q;exports.INSTALLATION_HEADER=ee;exports.InteractionParticipantRole=z;exports.PRESENCE_ONLINE_WINDOW_MS=v;exports.PROVIDER_FEATURE_MESSAGE_TEMPLATES=q;exports.PROVIDER_FEATURE_MESSAGE_WINDOW=Y;exports.SIGNATURE_INTENTS=P;exports.UNSUPPORTED=Z;exports.applySignature=y;exports.buildActivityPreview=N;exports.buildChannelIntents=B;exports.cleanMessageText=k;exports.defineIntent=J;exports.disabledIntentsFromCapabilities=w;exports.filterByEndpoint=j;exports.filterByIntent=W;exports.filterByTargetScheme=f;exports.flattenLocales=C;exports.floorToPeriod=g;exports.formatPeriod=A;exports.getActivitySeenUserIds=O;exports.getContactEndpoints=h;exports.getShortTitle=H;exports.getUrgencyScore=G;exports.isAssigned=U;exports.isClosed=$;exports.isInteractionUnseen=V;exports.matchContactToIntents=K;exports.periodsInRange=m;exports.resolveAccountCapabilities=_;exports.resolveChannelIntents=I;exports.resolveSignature=T;exports.stripHtml=p;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export * from './entities/activity';
|
|
2
|
+
export * from './entities/analytics';
|
|
3
|
+
export * from './entities/playbook';
|
|
2
4
|
export * from './entities/kb';
|
|
3
5
|
export * from './entities/topic';
|
|
4
6
|
export * from './entities/ai-account';
|
|
@@ -22,6 +24,7 @@ export * from './entities/organization';
|
|
|
22
24
|
export * from './entities/shopify';
|
|
23
25
|
export * from './entities/transcript';
|
|
24
26
|
export * from './entities/user';
|
|
27
|
+
export * from './text/message';
|
|
25
28
|
export * from './platform/ai-tools';
|
|
26
29
|
export * from './platform/api';
|
|
27
30
|
export * from './platform/common';
|
package/dist/index.js
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
function
|
|
1
|
+
function p(e) {
|
|
2
2
|
return e.replace(/<style[\s\S]*?<\/style>/gi, " ").replace(/<script[\s\S]*?<\/script>/gi, " ").replace(/<[^>]+>/g, " ").replace(/ /g, " ").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"').replace(/'/g, "'").replace(/\s+/g, " ").trim();
|
|
3
3
|
}
|
|
4
|
-
function
|
|
4
|
+
function d(e) {
|
|
5
5
|
const t = e.trim();
|
|
6
6
|
return t.length <= 140 ? t : t.slice(0, 139).trimEnd() + "…";
|
|
7
7
|
}
|
|
8
|
-
function
|
|
8
|
+
function l(e) {
|
|
9
9
|
if (!e || e < 0) return "";
|
|
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
|
|
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() ||
|
|
17
|
+
const t = e.payload, n = t.bodySnippet?.trim() || p(t.body ?? "");
|
|
18
18
|
return t.subject ? `${t.subject} — ${n}` : n;
|
|
19
19
|
}
|
|
20
20
|
case "CHAT_MESSAGE_SENT":
|
|
@@ -29,7 +29,7 @@ function p(e) {
|
|
|
29
29
|
case "CHAT_CALL_STARTED":
|
|
30
30
|
return "Gesprek gestart";
|
|
31
31
|
case "CHAT_CALL_ENDED":
|
|
32
|
-
return `Gesprek beëindigd${e.payload.duration ? ` (${
|
|
32
|
+
return `Gesprek beëindigd${e.payload.duration ? ` (${l(e.payload.duration)})` : ""}`;
|
|
33
33
|
case "CHAT_EVENT":
|
|
34
34
|
return e.payload.text ?? e.payload.eventType;
|
|
35
35
|
case "VOICE_CALL_STARTED":
|
|
@@ -46,7 +46,7 @@ function p(e) {
|
|
|
46
46
|
return "Hervat";
|
|
47
47
|
case "VOICE_CALL_ENDED":
|
|
48
48
|
case "VIDEO_CALL_ENDED":
|
|
49
|
-
return `Gesprek beëindigd${e.payload.duration ? ` (${
|
|
49
|
+
return `Gesprek beëindigd${e.payload.duration ? ` (${l(e.payload.duration)})` : ""}`;
|
|
50
50
|
case "VOICE_CALL_MISSED":
|
|
51
51
|
case "VIDEO_CALL_MISSED":
|
|
52
52
|
return "Gemiste oproep";
|
|
@@ -59,12 +59,20 @@ function p(e) {
|
|
|
59
59
|
return (e.payload.segments ?? []).map((t) => t.text).join(" ");
|
|
60
60
|
case "AI_MESSAGE_ADDED":
|
|
61
61
|
return e.payload.output?.text ?? e.payload.input?.text ?? "";
|
|
62
|
+
case "AI_ACTION_PROPOSED":
|
|
63
|
+
return `Voorstel wacht op goedkeuring (${e.payload.actions?.length ?? 0} actie(s))`;
|
|
64
|
+
case "PLAYBOOK_STARTED":
|
|
65
|
+
return `Playbook gestart${e.payload.playbookName ? `: ${e.payload.playbookName}` : ""}`;
|
|
66
|
+
case "PLAYBOOK_COMPLETED":
|
|
67
|
+
return "Playbook afgerond";
|
|
68
|
+
case "PLAYBOOK_ESCALATED":
|
|
69
|
+
return "Playbook geëscaleerd naar een mens";
|
|
62
70
|
case "MEETING_SCHEDULED":
|
|
63
71
|
return `Vergadering gepland: ${e.payload.title}`;
|
|
64
72
|
case "MEETING_STARTED":
|
|
65
73
|
return `Vergadering gestart: ${e.payload.title}`;
|
|
66
74
|
case "MEETING_ENDED":
|
|
67
|
-
return `Vergadering beëindigd${e.payload.duration ? ` (${
|
|
75
|
+
return `Vergadering beëindigd${e.payload.duration ? ` (${l(e.payload.duration)})` : ""}`;
|
|
68
76
|
case "MEETING_PARTICIPANT_JOINED":
|
|
69
77
|
return `${e.payload.name} deelgenomen`;
|
|
70
78
|
case "MEETING_PARTICIPANT_LEFT":
|
|
@@ -83,103 +91,176 @@ function p(e) {
|
|
|
83
91
|
return "";
|
|
84
92
|
}
|
|
85
93
|
}
|
|
86
|
-
function
|
|
94
|
+
function C(e) {
|
|
87
95
|
return {
|
|
88
96
|
activityId: e.id,
|
|
89
97
|
type: e.type,
|
|
90
|
-
snippet:
|
|
98
|
+
snippet: d(A(e)),
|
|
91
99
|
authorName: e.author?.name ?? "",
|
|
92
100
|
direction: e.direction,
|
|
93
101
|
createdAt: e.createdAt ?? Date.now()
|
|
94
102
|
};
|
|
95
103
|
}
|
|
96
|
-
function
|
|
104
|
+
function R(e, t, n = []) {
|
|
97
105
|
const r = e.createdAt;
|
|
98
106
|
if (!r) return [];
|
|
99
|
-
const
|
|
100
|
-
return e.author.type === "user" && e.author.id &&
|
|
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
|
+
}
|
|
110
|
+
const M = [
|
|
111
|
+
{ id: "agent", label: "Agent", labelSource: "user" },
|
|
112
|
+
{ id: "team", label: "Team", labelSource: "team" },
|
|
113
|
+
{ id: "inbox", label: "Inbox", labelSource: "inbox" },
|
|
114
|
+
{ id: "channel", label: "Kanaal", labelSource: "channel" },
|
|
115
|
+
{ id: "provider", label: "Provider", labelSource: "raw" },
|
|
116
|
+
{ id: "status", label: "Status", labelSource: "raw" },
|
|
117
|
+
{ id: "handledBy", label: "Afhandelaar", labelSource: "raw" },
|
|
118
|
+
{ id: "time", label: "Tijd", labelSource: "raw" }
|
|
119
|
+
];
|
|
120
|
+
function m(e) {
|
|
121
|
+
const t = [];
|
|
122
|
+
for (const n of Object.keys(e))
|
|
123
|
+
for (const r of Object.keys(e[n]))
|
|
124
|
+
t.push({ lang: n, key: r, value: e[n][r] });
|
|
125
|
+
return t;
|
|
126
|
+
}
|
|
127
|
+
function u(e) {
|
|
128
|
+
return e < 10 ? `0${e}` : `${e}`;
|
|
129
|
+
}
|
|
130
|
+
function g(e, t) {
|
|
131
|
+
const n = new Date(e), r = `${n.getUTCFullYear()}-${u(n.getUTCMonth() + 1)}-${u(n.getUTCDate())}`;
|
|
132
|
+
return t === "day" ? r : `${r}T${u(n.getUTCHours())}`;
|
|
133
|
+
}
|
|
134
|
+
function T(e, t) {
|
|
135
|
+
const n = new Date(e);
|
|
136
|
+
return n.setUTCMinutes(0, 0, 0), t === "day" && n.setUTCHours(0), n.getTime();
|
|
137
|
+
}
|
|
138
|
+
const f = 36e5, _ = 864e5;
|
|
139
|
+
function P(e, t, n) {
|
|
140
|
+
const r = n === "hour" ? f : _, a = [];
|
|
141
|
+
for (let s = T(e, n); s <= t; s += r)
|
|
142
|
+
a.push(g(s, n));
|
|
143
|
+
return a;
|
|
101
144
|
}
|
|
102
|
-
const
|
|
103
|
-
function
|
|
145
|
+
const $ = /* @__PURE__ */ new Set(["mail", "message"]);
|
|
146
|
+
function I(e, t) {
|
|
104
147
|
const n = e.settings?.signatures?.[t];
|
|
105
148
|
return !n || !n.enabled || !n.body || n.body.trim() === "" ? null : n;
|
|
106
149
|
}
|
|
107
|
-
function
|
|
150
|
+
function h(e, t, n, r = "html") {
|
|
108
151
|
if (!e) return n;
|
|
109
|
-
const
|
|
110
|
-
return
|
|
152
|
+
const a = I(e, t);
|
|
153
|
+
return a ? `${n}${r === "text" ? `
|
|
111
154
|
|
|
112
|
-
` : t === "mail" ? "<br><br>-- <br>" : "<br><br>"}${
|
|
155
|
+
` : t === "mail" ? "<br><br>-- <br>" : "<br><br>"}${a.body}` : n;
|
|
113
156
|
}
|
|
114
|
-
function
|
|
157
|
+
function y(e) {
|
|
115
158
|
return e.endpoints ?? [];
|
|
116
159
|
}
|
|
117
|
-
const
|
|
160
|
+
const U = (e) => !!e.assignedUserId || !!e.assignedInboxId, H = (e) => e.status === "closed", V = (e) => ({
|
|
118
161
|
urgent: 10,
|
|
119
162
|
high: 5,
|
|
120
163
|
normal: 2,
|
|
121
164
|
low: 1
|
|
122
|
-
})[e.priority] || 0,
|
|
123
|
-
function
|
|
165
|
+
})[e.priority] || 0, G = (e, t = 30) => e.title?.length <= t ? e.title : `${e.title.substring(0, t)}...`;
|
|
166
|
+
function k(e, t) {
|
|
124
167
|
return e.lastActivityAt ? !(e.seenBy ?? []).includes(t) : !1;
|
|
125
168
|
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
169
|
+
const S = [
|
|
170
|
+
/<blockquote/i,
|
|
171
|
+
/class="?gmail_quote/i,
|
|
172
|
+
// Gmail quote container
|
|
173
|
+
/id="?[^"]*divRplyFwdMsg/i,
|
|
174
|
+
// Outlook web reply/forward header
|
|
175
|
+
/-{3,}\s*Original Message\s*-{3,}/i,
|
|
176
|
+
/\n_{5,}\s*\n/,
|
|
177
|
+
// Outlook underscore separator before "From:"
|
|
178
|
+
/\bOn\b[\s\S]{0,200}?\bwrote:/i
|
|
179
|
+
// Gmail "On <date> <name> wrote:"
|
|
180
|
+
];
|
|
181
|
+
function c(e) {
|
|
182
|
+
let t = e;
|
|
183
|
+
return t = t.replace(/<(script|style)[\s\S]*?<\/\1>/gi, ""), t = t.replace(/<br\s*\/?>/gi, `
|
|
184
|
+
`), t = t.replace(/<\/(p|div|li|tr|h[1-6]|ul|ol|table)>/gi, `
|
|
185
|
+
`), t = t.replace(/<[^>]+>/g, ""), t = t.replace(/<[^>]*$/, ""), t;
|
|
186
|
+
}
|
|
187
|
+
function E(e) {
|
|
188
|
+
return e.replace(/ /gi, " ").replace(/</gi, "<").replace(/>/gi, ">").replace(/"/gi, '"').replace(/'/gi, "'").replace(/&#(\d+);/g, (t, n) => String.fromCharCode(Number(n))).replace(/&/gi, "&");
|
|
189
|
+
}
|
|
190
|
+
function i(e) {
|
|
191
|
+
return e.replace(/\r/g, "").replace(/[ \t]+/g, " ").replace(/ *\n */g, `
|
|
192
|
+
`).replace(/\n{3,}/g, `
|
|
193
|
+
|
|
194
|
+
`).trim();
|
|
195
|
+
}
|
|
196
|
+
function w(e) {
|
|
197
|
+
if (typeof e != "string" || !e) return "";
|
|
198
|
+
let t = e.length;
|
|
199
|
+
for (const a of S) {
|
|
200
|
+
const s = e.search(a);
|
|
201
|
+
s >= 0 && s < t && (t = s);
|
|
202
|
+
}
|
|
203
|
+
const n = e.slice(0, t);
|
|
204
|
+
let r = i(E(c(n)));
|
|
205
|
+
return r || (r = i(E(c(e)))), r;
|
|
206
|
+
}
|
|
207
|
+
function b(e, t) {
|
|
208
|
+
const n = new Set(e.disabledIntents ?? []), r = e.intentOverrides ?? {}, a = t.intents.filter((o) => !n.has(o.intent)).map((o) => O(o, r[o.intent]));
|
|
209
|
+
if (!e.extraIntents || e.extraIntents.length === 0) return a;
|
|
210
|
+
const s = new Set(a.map((o) => o.intent));
|
|
130
211
|
for (const o of e.extraIntents)
|
|
131
|
-
|
|
132
|
-
return
|
|
212
|
+
s.has(o.intent) || (a.push(o), s.add(o.intent));
|
|
213
|
+
return a;
|
|
133
214
|
}
|
|
134
|
-
function
|
|
215
|
+
function D(e, t) {
|
|
135
216
|
const n = {};
|
|
136
217
|
for (const r of e.intents) {
|
|
137
218
|
if (!r.togglable) continue;
|
|
138
|
-
const
|
|
139
|
-
n[r.intent] =
|
|
219
|
+
const a = t?.[r.intent];
|
|
220
|
+
n[r.intent] = a ?? r.defaultEnabled ?? !0;
|
|
140
221
|
}
|
|
141
222
|
return n;
|
|
142
223
|
}
|
|
143
|
-
function
|
|
144
|
-
const n =
|
|
224
|
+
function x(e, t) {
|
|
225
|
+
const n = D(e, t);
|
|
145
226
|
return Object.entries(n).filter(([, r]) => !r).map(([r]) => r);
|
|
146
227
|
}
|
|
147
|
-
function
|
|
228
|
+
function O(e, t) {
|
|
148
229
|
return t ? {
|
|
149
230
|
intent: t.intent ?? e.intent,
|
|
150
231
|
targetSchemes: t.targetSchemes ?? e.targetSchemes,
|
|
151
232
|
transport: t.transport ?? e.transport
|
|
152
233
|
} : e;
|
|
153
234
|
}
|
|
154
|
-
function
|
|
235
|
+
function F(e, t) {
|
|
155
236
|
const n = [];
|
|
156
237
|
for (const r of e) {
|
|
157
238
|
if (!r.enabled) continue;
|
|
158
|
-
const
|
|
159
|
-
if (
|
|
160
|
-
for (const
|
|
161
|
-
n.push({ channel: r, description:
|
|
239
|
+
const a = t[r.providerId];
|
|
240
|
+
if (a)
|
|
241
|
+
for (const s of b(r, a))
|
|
242
|
+
n.push({ channel: r, description: a, capability: s });
|
|
162
243
|
}
|
|
163
244
|
return n;
|
|
164
245
|
}
|
|
165
|
-
function
|
|
246
|
+
function B(e, t) {
|
|
166
247
|
return t.filter((n) => n.capability.intent === e);
|
|
167
248
|
}
|
|
168
|
-
function
|
|
249
|
+
function N(e, t) {
|
|
169
250
|
return t.filter((n) => n.capability.targetSchemes.includes(e));
|
|
170
251
|
}
|
|
171
|
-
function
|
|
172
|
-
return
|
|
252
|
+
function j(e, t) {
|
|
253
|
+
return N(e.scheme, t);
|
|
173
254
|
}
|
|
174
|
-
function
|
|
255
|
+
function W(e, t, n) {
|
|
175
256
|
const r = [];
|
|
176
|
-
for (const
|
|
177
|
-
for (const
|
|
178
|
-
|
|
257
|
+
for (const a of e)
|
|
258
|
+
for (const s of n)
|
|
259
|
+
s.capability.intent === t && s.capability.targetSchemes.includes(a.scheme) && r.push({ channelIntent: s, endpoint: a });
|
|
179
260
|
return r;
|
|
180
261
|
}
|
|
181
|
-
var
|
|
182
|
-
const
|
|
262
|
+
var L = /* @__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))(L || {});
|
|
263
|
+
const K = "message_window", Y = "message_templates", q = {
|
|
183
264
|
// E-mail
|
|
184
265
|
FROM: "from",
|
|
185
266
|
TO: "to",
|
|
@@ -196,36 +277,42 @@ const V = "message_window", $ = "message_templates", H = {
|
|
|
196
277
|
// Voice/conference
|
|
197
278
|
HOST: "host",
|
|
198
279
|
PARTICIPANT: "participant"
|
|
199
|
-
},
|
|
280
|
+
}, X = (e, t) => ({ intent: e, ...t }), z = "folder_management", J = "remote_search", Q = { ok: !1, code: "UNSUPPORTED", message: "Provider does not support this op" }, Z = 9e4, v = "installation-id";
|
|
200
281
|
export {
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
N as
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
G as
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
282
|
+
M as CORE_DIMENSIONS,
|
|
283
|
+
L as CommunicationScheme,
|
|
284
|
+
z as FEATURE_FOLDER_MANAGEMENT,
|
|
285
|
+
J as FEATURE_REMOTE_SEARCH,
|
|
286
|
+
v as INSTALLATION_HEADER,
|
|
287
|
+
q as InteractionParticipantRole,
|
|
288
|
+
Z as PRESENCE_ONLINE_WINDOW_MS,
|
|
289
|
+
Y as PROVIDER_FEATURE_MESSAGE_TEMPLATES,
|
|
290
|
+
K as PROVIDER_FEATURE_MESSAGE_WINDOW,
|
|
291
|
+
$ as SIGNATURE_INTENTS,
|
|
292
|
+
Q as UNSUPPORTED,
|
|
293
|
+
h as applySignature,
|
|
294
|
+
C as buildActivityPreview,
|
|
295
|
+
F as buildChannelIntents,
|
|
296
|
+
w as cleanMessageText,
|
|
297
|
+
X as defineIntent,
|
|
298
|
+
x as disabledIntentsFromCapabilities,
|
|
299
|
+
j as filterByEndpoint,
|
|
300
|
+
B as filterByIntent,
|
|
301
|
+
N as filterByTargetScheme,
|
|
302
|
+
m as flattenLocales,
|
|
303
|
+
T as floorToPeriod,
|
|
304
|
+
g as formatPeriod,
|
|
305
|
+
R as getActivitySeenUserIds,
|
|
306
|
+
y as getContactEndpoints,
|
|
307
|
+
G as getShortTitle,
|
|
308
|
+
V as getUrgencyScore,
|
|
309
|
+
U as isAssigned,
|
|
310
|
+
H as isClosed,
|
|
311
|
+
k as isInteractionUnseen,
|
|
312
|
+
W as matchContactToIntents,
|
|
313
|
+
P as periodsInRange,
|
|
314
|
+
D as resolveAccountCapabilities,
|
|
315
|
+
b as resolveChannelIntents,
|
|
316
|
+
I as resolveSignature,
|
|
317
|
+
p as stripHtml
|
|
231
318
|
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Clean an inbound message body for display/LLM use: cut the quoted reply /
|
|
3
|
+
* forwarded thread, strip HTML, decode common entities, collapse whitespace.
|
|
4
|
+
* Without this the raw HTML + full Gmail/Outlook quote leaks the OLD message into
|
|
5
|
+
* classify/extract/agent (or history views) instead of the sender's new content.
|
|
6
|
+
* Pure, dependency-free (lives in domain so both server apps can share it).
|
|
7
|
+
*/
|
|
8
|
+
export declare function cleanMessageText(raw: unknown): string;
|