@superpanel-sdk/runtime 0.2.1 → 0.2.3
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/builder/codegen/generate.js +15 -9
- package/dist/builder/company-import-graph.d.ts +11 -0
- package/dist/builder/company-import-graph.js +105 -0
- package/dist/builder/compile/compile-agenda.js +9 -4
- package/dist/builder/compile/compile-sequence.d.ts +4 -1
- package/dist/builder/compile/compile-sequence.js +28 -9
- package/dist/builder/load.js +3 -3
- package/dist/builder/types.d.ts +5 -0
- package/dist/cli/serve/index.js +107 -47
- package/dist/compute-host/index.d.ts +2 -0
- package/dist/compute-host/index.js +1 -0
- package/dist/compute-host/read-only-cache.d.ts +20 -0
- package/dist/compute-host/read-only-cache.js +61 -0
- package/dist/compute-host/source-compute-host.d.ts +4 -0
- package/dist/compute-host/source-compute-host.js +12 -2
- package/dist/core/context/types.d.ts +19 -1
- package/dist/core/functions/define/custom.d.ts +1 -1
- package/dist/core/functions/types.d.ts +4 -4
- package/dist/core/slot/index.d.ts +1 -1
- package/dist/core/slot/index.js +1 -1
- package/dist/core/slot/types.d.ts +2 -0
- package/dist/core/slot/write.d.ts +8 -1
- package/dist/core/slot/write.js +145 -38
- package/dist/core/stage/builders.d.ts +23 -5
- package/dist/core/stage/builders.js +3 -0
- package/dist/core/stage/drive.d.ts +1 -1
- package/dist/core/stage/drive.js +9 -8
- package/dist/core/stage/flatten.js +13 -11
- package/dist/core/stage/index.d.ts +8 -4
- package/dist/core/stage/index.js +5 -2
- package/dist/core/stage/interrupts.d.ts +28 -0
- package/dist/core/stage/interrupts.js +120 -0
- package/dist/core/stage/ivr-fields.d.ts +9 -0
- package/dist/core/stage/ivr-fields.js +75 -0
- package/dist/core/stage/transfer-fields.d.ts +6 -1
- package/dist/core/stage/transfer-fields.js +44 -3
- package/dist/core/stage/transfer-spec.d.ts +48 -0
- package/dist/core/stage/transfer-spec.js +113 -0
- package/dist/core/stage/types.d.ts +45 -14
- package/dist/core/stage/validate.js +105 -1
- package/dist/core/stage/walk.d.ts +5 -0
- package/dist/core/stage/walk.js +33 -10
- package/dist/fn-context/index.d.ts +1 -1
- package/dist/fn-context/index.js +1 -1
- package/dist/fn-context/rpc/index.d.ts +1 -1
- package/dist/fn-context/rpc/index.js +1 -1
- package/dist/fn-context/rpc/request-id.d.ts +5 -0
- package/dist/fn-context/rpc/request-id.js +11 -0
- package/dist/fn-context/rpc/trpc-client.d.ts +2 -0
- package/dist/fn-context/rpc/trpc-client.js +5 -1
- package/dist/fn-context/rpc/trpc-init.d.ts +1 -0
- package/dist/index.d.ts +7 -7
- package/dist/index.js +4 -4
- package/dist/voice-service/types.d.ts +3 -0
- package/package.json +1 -1
package/dist/core/stage/index.js
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
|
+
export { E164_NUMBER, SIP_URI, interpolateFromSnapshot, interpolateTemplate, isTransferDestinationLiteral, resolveTransferArguments, resolveTransferDestinations, resolveTransferSay, transferArgumentsJson, validateTransferDestinations, } from './transfer-spec.js';
|
|
1
2
|
export { collectJsonRulesFacts, evaluateJsonRules, extractTemplateFields, isOtherwise, jsonRules, looksLikeJsonRules, normalizeJsonRules, otherwise, parseJsonRules, } from './condition.js';
|
|
2
3
|
export { andCompiledWhen, compileAuthoringWhen, compileFinishWhen, compileWhen, evaluateFinishWhen, evaluateWhen, finishWhenFields, notAnyWhen, whenFields, } from './finish-when.js';
|
|
3
|
-
export { action, agenda, branch, section, switchOn, topic, transfer } from './builders.js';
|
|
4
|
+
export { action, agenda, branch, ivr, section, switchOn, topic, transfer } from './builders.js';
|
|
4
5
|
export { extractBlocksFromTree, flattenAgendaConfig, flattenAgendaPlacements, flattenAgendaTree, isAgendaTree, isSequenceAgendas, normalizeSequenceAgendas, treeToPlacements, } from './flatten.js';
|
|
5
|
-
export { LAST_TRANSFER_RESULT_FIELD, collectTransferBlocks, registerTransferSessionFields, transferFieldKey, transferResultField, } from './transfer-fields.js';
|
|
6
|
+
export { CASE_LAST_TRANSFER_AT, CASE_LAST_TRANSFER_RESULT, CASE_TRANSFER_ATTEMPT_COUNT, LAST_TRANSFER_RESULT_FIELD, collectTransferBlocks, registerTransferSessionFields, transferFieldKey, transferResultField, } from './transfer-fields.js';
|
|
7
|
+
export { collectIvrBlocks, ivrAttempted, ivrResultField, registerIvrSessionFields, } from './ivr-fields.js';
|
|
6
8
|
export { fatalAgendaIssues, validateCompiledAgenda } from './validate.js';
|
|
9
|
+
export { RESERVED_INTERRUPT_TOOL_NAMES, collectTransferInterruptHeaders, headerHasTransferInterrupt, CONTACT_LANGUAGE_FIELD, getContactLang, inboundIvrCollect, isSpokenLanguage, ivrOf, languageFieldOf, validateInterrupts, } from './interrupts.js';
|
|
7
10
|
export { unresolvedFinishWhenFields, walkAgenda, } from './walk.js';
|
|
8
11
|
export { simulateAgenda } from './simulate.js';
|
|
9
12
|
export { DEFAULT_MAX_ACTIONS_PER_TURN, advanceAgenda, driveAgenda, transferWrites, watchdogHint, } from './drive.js';
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { FieldPath } from '../slot/path.js';
|
|
2
|
+
import type { CompiledSlotSchema, ValidationIssue } from '../slot/types.js';
|
|
3
|
+
import type { AgendaHeader, FlatAgendaItem, FlatIvrItem, Interrupt, IvrDigit, IvrFallbackAction } from './types.js';
|
|
4
|
+
export type SpokenLanguage = string;
|
|
5
|
+
export declare const CONTACT_LANGUAGE_FIELD: "contact.language";
|
|
6
|
+
export declare const RESERVED_INTERRUPT_TOOL_NAMES: Set<string>;
|
|
7
|
+
export declare function isSpokenLanguage(value: unknown): value is SpokenLanguage;
|
|
8
|
+
export declare function ivrOf(items: FlatAgendaItem[] | null | undefined): FlatIvrItem | null;
|
|
9
|
+
export declare function languageFieldOf(): FieldPath;
|
|
10
|
+
/** Voice language. Empty / missing / non-language value → english. */
|
|
11
|
+
export declare function getContactLang(readField?: (path: FieldPath) => unknown): SpokenLanguage;
|
|
12
|
+
export declare function inboundIvrCollect(items: FlatAgendaItem[] | null | undefined): {
|
|
13
|
+
id: string;
|
|
14
|
+
fallbackAction: IvrFallbackAction;
|
|
15
|
+
confirmTimeoutMs: number;
|
|
16
|
+
pauseAfterSayMs: number;
|
|
17
|
+
finishOnKey?: IvrDigit | null;
|
|
18
|
+
minDigits?: number | null;
|
|
19
|
+
maxDigits?: number | null;
|
|
20
|
+
validateDigits: IvrDigit[];
|
|
21
|
+
} | null;
|
|
22
|
+
export declare function headerHasTransferInterrupt(header: AgendaHeader | null | undefined): boolean;
|
|
23
|
+
export declare function collectTransferInterruptHeaders(headers: Array<AgendaHeader | null | undefined>): boolean;
|
|
24
|
+
export declare function validateInterrupts(interrupts: Interrupt[], options: {
|
|
25
|
+
schema: CompiledSlotSchema;
|
|
26
|
+
agendaId?: string;
|
|
27
|
+
}): ValidationIssue[];
|
|
28
|
+
//# sourceMappingURL=interrupts.d.ts.map
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { getCompiledTable } from '../slot/compile-schema.js';
|
|
2
|
+
import { validateTransferDestinations } from './transfer-spec.js';
|
|
3
|
+
export const CONTACT_LANGUAGE_FIELD = 'contact.language';
|
|
4
|
+
export const RESERVED_INTERRUPT_TOOL_NAMES = new Set(['voice_turn', 'record_opt_out', 'advance']);
|
|
5
|
+
export function isSpokenLanguage(value) {
|
|
6
|
+
return typeof value === 'string' && value.trim().length > 0;
|
|
7
|
+
}
|
|
8
|
+
export function ivrOf(items) {
|
|
9
|
+
return items?.find((item) => item.kind === 'ivr') ?? null;
|
|
10
|
+
}
|
|
11
|
+
export function languageFieldOf() {
|
|
12
|
+
return CONTACT_LANGUAGE_FIELD;
|
|
13
|
+
}
|
|
14
|
+
function looksLikeSpokenLanguage(value) {
|
|
15
|
+
return isSpokenLanguage(value) && /[a-zA-Z]/.test(value);
|
|
16
|
+
}
|
|
17
|
+
/** Voice language. Empty / missing / non-language value → english. */
|
|
18
|
+
export function getContactLang(readField) {
|
|
19
|
+
if (!readField)
|
|
20
|
+
return 'english';
|
|
21
|
+
const raw = readField(CONTACT_LANGUAGE_FIELD);
|
|
22
|
+
return looksLikeSpokenLanguage(raw) ? raw.trim() : 'english';
|
|
23
|
+
}
|
|
24
|
+
export function inboundIvrCollect(items) {
|
|
25
|
+
const ivr = ivrOf(items);
|
|
26
|
+
if (!ivr)
|
|
27
|
+
return null;
|
|
28
|
+
const validateDigits = (ivr.validateDigits ?? []).filter((digit) => Boolean(digit?.trim()));
|
|
29
|
+
if (!ivr.finishOnKey && validateDigits.length === 0)
|
|
30
|
+
return null;
|
|
31
|
+
return {
|
|
32
|
+
id: ivr.id,
|
|
33
|
+
fallbackAction: ivr.fallbackAction,
|
|
34
|
+
confirmTimeoutMs: ivr.confirmTimeoutMs,
|
|
35
|
+
pauseAfterSayMs: ivr.pauseAfterSayMs,
|
|
36
|
+
finishOnKey: ivr.finishOnKey,
|
|
37
|
+
minDigits: ivr.minDigits,
|
|
38
|
+
maxDigits: ivr.maxDigits,
|
|
39
|
+
validateDigits,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
export function headerHasTransferInterrupt(header) {
|
|
43
|
+
return header?.interrupts.some((interrupt) => interrupt.action.type === 'transfer') ?? false;
|
|
44
|
+
}
|
|
45
|
+
export function collectTransferInterruptHeaders(headers) {
|
|
46
|
+
return headers.some((header) => headerHasTransferInterrupt(header));
|
|
47
|
+
}
|
|
48
|
+
export function validateInterrupts(interrupts, options) {
|
|
49
|
+
const issues = [];
|
|
50
|
+
const prefix = `${options.agendaId ?? 'agenda'}/header/interrupts`;
|
|
51
|
+
const names = new Set();
|
|
52
|
+
const priorities = new Set();
|
|
53
|
+
for (const interrupt of interrupts) {
|
|
54
|
+
const path = `${prefix}/${interrupt.id}`;
|
|
55
|
+
if (RESERVED_INTERRUPT_TOOL_NAMES.has(interrupt.tool.name)) {
|
|
56
|
+
issues.push({
|
|
57
|
+
code: 'interrupt-tool-reserved',
|
|
58
|
+
message: `Interrupt tool name '${interrupt.tool.name}' is reserved`,
|
|
59
|
+
path,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
if (names.has(interrupt.tool.name)) {
|
|
63
|
+
issues.push({
|
|
64
|
+
code: 'interrupt-tool-duplicate',
|
|
65
|
+
message: `Interrupt tool name '${interrupt.tool.name}' is not unique`,
|
|
66
|
+
path,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
names.add(interrupt.tool.name);
|
|
70
|
+
if (priorities.has(interrupt.priority)) {
|
|
71
|
+
issues.push({
|
|
72
|
+
code: 'interrupt-priority-tie',
|
|
73
|
+
message: `Interrupt priority ${interrupt.priority} is not unique`,
|
|
74
|
+
path,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
priorities.add(interrupt.priority);
|
|
78
|
+
if (interrupt.action.type === 'switch-language') {
|
|
79
|
+
if (interrupt.action.languages.length === 0) {
|
|
80
|
+
issues.push({
|
|
81
|
+
code: 'interrupt-languages-empty',
|
|
82
|
+
message: 'switch-language languages must not be empty',
|
|
83
|
+
path: `${path}/languages`,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
if (!getCompiledTable(options.schema, 'contact')?.fields.language) {
|
|
87
|
+
issues.push({
|
|
88
|
+
code: 'interrupt-language-field',
|
|
89
|
+
message: `switch-language requires ${CONTACT_LANGUAGE_FIELD}`,
|
|
90
|
+
path: `${path}/languages`,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
if (interrupt.action.type === 'transfer') {
|
|
95
|
+
issues.push(...validateTransferDestinations(interrupt.action.numbers, {
|
|
96
|
+
path: `${path}/numbers`,
|
|
97
|
+
code: 'interrupt-transfer-number',
|
|
98
|
+
}));
|
|
99
|
+
issues.push(...validateCriteriaList(interrupt.action.escalateCriteria, `${path}/escalateCriteria`));
|
|
100
|
+
issues.push(...validateCriteriaList(interrupt.action.noEscalationCriteria, `${path}/noEscalationCriteria`));
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return issues;
|
|
104
|
+
}
|
|
105
|
+
function validateCriteriaList(list, path) {
|
|
106
|
+
if (!list)
|
|
107
|
+
return [];
|
|
108
|
+
const issues = [];
|
|
109
|
+
for (let index = 0; index < list.length; index++) {
|
|
110
|
+
if (list[index]?.trim())
|
|
111
|
+
continue;
|
|
112
|
+
issues.push({
|
|
113
|
+
code: 'interrupt-escalation-criteria',
|
|
114
|
+
message: 'escalation criteria entry must not be empty',
|
|
115
|
+
path: `${path}[${index}]`,
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
return issues;
|
|
119
|
+
}
|
|
120
|
+
//# sourceMappingURL=interrupts.js.map
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { FieldPath } from '../slot/path.js';
|
|
2
|
+
import type { CompiledSlotSchema, RuntimeSlotsSnapshot } from '../slot/types.js';
|
|
3
|
+
import type { AgendaBlock, AuthoringNode, FlatIvrItem, IvrBlock } from './types.js';
|
|
4
|
+
export type IvrAttemptResult = 'collected' | 'failed';
|
|
5
|
+
export declare function ivrResultField(nodeId: string): FieldPath;
|
|
6
|
+
export declare function ivrAttempted(item: FlatIvrItem, snapshot: RuntimeSlotsSnapshot): boolean;
|
|
7
|
+
export declare function collectIvrBlocks(blocks: Record<string, AgendaBlock>, nodes?: AuthoringNode[]): IvrBlock[];
|
|
8
|
+
export declare function registerIvrSessionFields(schema: CompiledSlotSchema, ivrIds: Iterable<string>): CompiledSlotSchema;
|
|
9
|
+
//# sourceMappingURL=ivr-fields.d.ts.map
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { idField } from '../slot/schema.js';
|
|
2
|
+
import { isResolved } from '../slot/field.js';
|
|
3
|
+
import { getSingleField } from '../slot/snapshot.js';
|
|
4
|
+
import { getCompiledTable } from '../slot/compile-schema.js';
|
|
5
|
+
import { formatFieldPath } from '../slot/path.js';
|
|
6
|
+
import { transferFieldKey } from './transfer-fields.js';
|
|
7
|
+
const RESULT_ENUM = ['collected', 'failed'];
|
|
8
|
+
function ivrFieldCore(label) {
|
|
9
|
+
return {
|
|
10
|
+
derived: false,
|
|
11
|
+
label,
|
|
12
|
+
dataType: 'string',
|
|
13
|
+
source: 'system',
|
|
14
|
+
extraction: 'Platform IVR result',
|
|
15
|
+
enum: [...RESULT_ENUM],
|
|
16
|
+
nullable: true,
|
|
17
|
+
identifier: null,
|
|
18
|
+
ref: null,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
export function ivrResultField(nodeId) {
|
|
22
|
+
return formatFieldPath('session', `ivr_${transferFieldKey(nodeId)}_result`);
|
|
23
|
+
}
|
|
24
|
+
export function ivrAttempted(item, snapshot) {
|
|
25
|
+
return isResolved(getSingleField(snapshot, item.resultField));
|
|
26
|
+
}
|
|
27
|
+
export function collectIvrBlocks(blocks, nodes = []) {
|
|
28
|
+
const out = [];
|
|
29
|
+
const seen = new Set();
|
|
30
|
+
for (const block of Object.values(blocks)) {
|
|
31
|
+
if (block.kind === 'ivr' && !seen.has(block.id)) {
|
|
32
|
+
seen.add(block.id);
|
|
33
|
+
out.push(block);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
visit(nodes);
|
|
37
|
+
return out;
|
|
38
|
+
function visit(list) {
|
|
39
|
+
for (const node of list) {
|
|
40
|
+
if (node.kind === 'ivr' && !seen.has(node.id)) {
|
|
41
|
+
seen.add(node.id);
|
|
42
|
+
out.push(node);
|
|
43
|
+
}
|
|
44
|
+
else if (node.kind === 'section' || node.kind === 'branch') {
|
|
45
|
+
visit(node.children);
|
|
46
|
+
}
|
|
47
|
+
else if (node.kind === 'switchOn') {
|
|
48
|
+
for (const children of Object.values(node.cases))
|
|
49
|
+
visit(children);
|
|
50
|
+
if (node.otherwise)
|
|
51
|
+
visit(node.otherwise);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
export function registerIvrSessionFields(schema, ivrIds) {
|
|
57
|
+
const ids = [...new Set(ivrIds)];
|
|
58
|
+
if (ids.length === 0)
|
|
59
|
+
return schema;
|
|
60
|
+
const session = getCompiledTable(schema, 'session');
|
|
61
|
+
if (!session)
|
|
62
|
+
return schema;
|
|
63
|
+
const fields = { ...session.fields };
|
|
64
|
+
if (!fields.id)
|
|
65
|
+
fields.id = { ...idField, ref: null };
|
|
66
|
+
for (const id of ids) {
|
|
67
|
+
const name = `ivr_${transferFieldKey(id)}_result`;
|
|
68
|
+
if (!fields[name]) {
|
|
69
|
+
fields[name] = ivrFieldCore(`IVR ${id} result`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
const nextSession = { ...session, fields };
|
|
73
|
+
return { ...schema, session: nextSession };
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=ivr-fields.js.map
|
|
@@ -5,10 +5,15 @@ import type { AgendaBlock, AuthoringNode, TransferBlock } from './types.js';
|
|
|
5
5
|
export declare function transferFieldKey(nodeId: string): string;
|
|
6
6
|
export declare function transferResultField(nodeId: string): FieldPath;
|
|
7
7
|
export declare const LAST_TRANSFER_RESULT_FIELD: `${string}.${string}`;
|
|
8
|
+
export declare const CASE_TRANSFER_ATTEMPT_COUNT: `${string}.${string}`;
|
|
9
|
+
export declare const CASE_LAST_TRANSFER_RESULT: `${string}.${string}`;
|
|
10
|
+
export declare const CASE_LAST_TRANSFER_AT: `${string}.${string}`;
|
|
8
11
|
export declare function collectTransferBlocks(blocks: Record<string, AgendaBlock>, nodes?: AuthoringNode[]): TransferBlock[];
|
|
9
12
|
/**
|
|
10
13
|
* Register per-node `session.transfer_<id>_result` and `session.last_transfer_result`.
|
|
11
14
|
* Existing session fields with the same name are left untouched.
|
|
12
15
|
*/
|
|
13
|
-
export declare function registerTransferSessionFields(schema: CompiledSlotSchema, transferIds: Iterable<string
|
|
16
|
+
export declare function registerTransferSessionFields(schema: CompiledSlotSchema, transferIds: Iterable<string>, options?: {
|
|
17
|
+
lastResult?: boolean;
|
|
18
|
+
}): CompiledSlotSchema;
|
|
14
19
|
//# sourceMappingURL=transfer-fields.d.ts.map
|
|
@@ -24,6 +24,9 @@ export function transferResultField(nodeId) {
|
|
|
24
24
|
return formatFieldPath('session', `transfer_${transferFieldKey(nodeId)}_result`);
|
|
25
25
|
}
|
|
26
26
|
export const LAST_TRANSFER_RESULT_FIELD = formatFieldPath('session', 'last_transfer_result');
|
|
27
|
+
export const CASE_TRANSFER_ATTEMPT_COUNT = formatFieldPath('case_file', 'transfer_attempt_count');
|
|
28
|
+
export const CASE_LAST_TRANSFER_RESULT = formatFieldPath('case_file', 'last_transfer_result');
|
|
29
|
+
export const CASE_LAST_TRANSFER_AT = formatFieldPath('case_file', 'last_transfer_at');
|
|
27
30
|
export function collectTransferBlocks(blocks, nodes = []) {
|
|
28
31
|
const out = [];
|
|
29
32
|
const seen = new Set();
|
|
@@ -57,9 +60,9 @@ export function collectTransferBlocks(blocks, nodes = []) {
|
|
|
57
60
|
* Register per-node `session.transfer_<id>_result` and `session.last_transfer_result`.
|
|
58
61
|
* Existing session fields with the same name are left untouched.
|
|
59
62
|
*/
|
|
60
|
-
export function registerTransferSessionFields(schema, transferIds) {
|
|
63
|
+
export function registerTransferSessionFields(schema, transferIds, options) {
|
|
61
64
|
const ids = [...new Set(transferIds)];
|
|
62
|
-
if (ids.length === 0)
|
|
65
|
+
if (ids.length === 0 && !options?.lastResult)
|
|
63
66
|
return schema;
|
|
64
67
|
const session = getCompiledTable(schema, 'session');
|
|
65
68
|
if (!session)
|
|
@@ -73,16 +76,54 @@ export function registerTransferSessionFields(schema, transferIds) {
|
|
|
73
76
|
fields[name] = transferFieldCore(`Transfer ${id} result`);
|
|
74
77
|
}
|
|
75
78
|
}
|
|
76
|
-
if (!fields.last_transfer_result) {
|
|
79
|
+
if ((ids.length > 0 || options?.lastResult) && !fields.last_transfer_result) {
|
|
77
80
|
fields.last_transfer_result = transferFieldCore('Last transfer result');
|
|
78
81
|
}
|
|
79
82
|
const nextSession = {
|
|
80
83
|
...session,
|
|
81
84
|
fields,
|
|
82
85
|
};
|
|
86
|
+
const caseFile = getCompiledTable(schema, 'case_file');
|
|
87
|
+
let nextCase = caseFile;
|
|
88
|
+
if (caseFile && (ids.length > 0 || options?.lastResult)) {
|
|
89
|
+
const caseFields = { ...caseFile.fields };
|
|
90
|
+
if (!caseFields.id)
|
|
91
|
+
caseFields.id = { ...idField, ref: null };
|
|
92
|
+
if (!caseFields.transfer_attempt_count) {
|
|
93
|
+
caseFields.transfer_attempt_count = {
|
|
94
|
+
derived: false,
|
|
95
|
+
label: 'Transfer attempt count',
|
|
96
|
+
dataType: 'number',
|
|
97
|
+
source: 'system',
|
|
98
|
+
extraction: 'Platform transfer attempt count',
|
|
99
|
+
enum: null,
|
|
100
|
+
nullable: true,
|
|
101
|
+
identifier: null,
|
|
102
|
+
ref: null,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
if (!caseFields.last_transfer_result) {
|
|
106
|
+
caseFields.last_transfer_result = transferFieldCore('Case last transfer result');
|
|
107
|
+
}
|
|
108
|
+
if (!caseFields.last_transfer_at) {
|
|
109
|
+
caseFields.last_transfer_at = {
|
|
110
|
+
derived: false,
|
|
111
|
+
label: 'Last transfer at',
|
|
112
|
+
dataType: 'datetime',
|
|
113
|
+
source: 'system',
|
|
114
|
+
extraction: 'Platform last transfer time',
|
|
115
|
+
enum: null,
|
|
116
|
+
nullable: true,
|
|
117
|
+
identifier: null,
|
|
118
|
+
ref: null,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
nextCase = { ...caseFile, fields: caseFields };
|
|
122
|
+
}
|
|
83
123
|
return {
|
|
84
124
|
...schema,
|
|
85
125
|
session: nextSession,
|
|
126
|
+
...(nextCase ? { case_file: nextCase } : {}),
|
|
86
127
|
};
|
|
87
128
|
}
|
|
88
129
|
//# sourceMappingURL=transfer-fields.js.map
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { FieldPath } from '../slot/path.js';
|
|
2
|
+
import type { RuntimeSlotsSnapshot, ValidationIssue } from '../slot/types.js';
|
|
3
|
+
export type TransferMode = 'cold' | 'warm' | 'async-warm';
|
|
4
|
+
export type TransferCallerIdShown = 'client' | 'service';
|
|
5
|
+
export type TransferConfirmMethod = 'keypad' | 'llm';
|
|
6
|
+
export type TransferConfirmKey = '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | '*' | '#';
|
|
7
|
+
export type TransferSay = string | Record<string, string>;
|
|
8
|
+
export type TransferFallbackAction = 'end-call' | 'restore-normal-call-state';
|
|
9
|
+
/** mars-voice-service `TransferCallArgs`. String fields may contain `{{table.field}}`. */
|
|
10
|
+
export type TransferArguments = {
|
|
11
|
+
sendDigits?: string;
|
|
12
|
+
sipUrl?: string;
|
|
13
|
+
sipUsername?: string;
|
|
14
|
+
sipPassword?: string;
|
|
15
|
+
transferCallerIdShown?: TransferCallerIdShown;
|
|
16
|
+
};
|
|
17
|
+
/** Shared dial config for agenda transfer nodes and interrupt transfer actions. */
|
|
18
|
+
export type TransferDial = {
|
|
19
|
+
numbers: string[];
|
|
20
|
+
mode: TransferMode;
|
|
21
|
+
say?: TransferSay | null;
|
|
22
|
+
unsuccessfulSay?: TransferSay | null;
|
|
23
|
+
fallbackAction?: TransferFallbackAction | null;
|
|
24
|
+
arguments?: TransferArguments | null;
|
|
25
|
+
warmConfirmMode?: TransferConfirmMethod[] | null;
|
|
26
|
+
warmConfirmKeypad?: TransferConfirmKey | null;
|
|
27
|
+
warmConfirmTimeoutMs?: number | null;
|
|
28
|
+
warmLoopSay?: string | null;
|
|
29
|
+
warmBriefingPrompt?: string | null;
|
|
30
|
+
asyncWarmBridge?: {
|
|
31
|
+
targetSayTemplate?: string;
|
|
32
|
+
customerSayTemplate?: string;
|
|
33
|
+
} | null;
|
|
34
|
+
};
|
|
35
|
+
export declare const E164_NUMBER: RegExp;
|
|
36
|
+
export declare const SIP_URI: RegExp;
|
|
37
|
+
export declare function isTransferDestinationLiteral(value: string): boolean;
|
|
38
|
+
export declare function interpolateTemplate(text: string, read: (path: FieldPath) => string | number | boolean | null | undefined): string;
|
|
39
|
+
export declare function interpolateFromSnapshot(text: string, snapshot: RuntimeSlotsSnapshot): string;
|
|
40
|
+
export declare function resolveTransferDestinations(numbers: string[], snapshot: RuntimeSlotsSnapshot): string[];
|
|
41
|
+
export declare function resolveTransferSay(say: TransferSay | null | undefined, language: string | null | undefined, fallback: string): string;
|
|
42
|
+
export declare function resolveTransferArguments(dial: TransferDial, snapshot: RuntimeSlotsSnapshot): TransferArguments;
|
|
43
|
+
export declare function transferArgumentsJson(dial: TransferDial, snapshot: RuntimeSlotsSnapshot): string;
|
|
44
|
+
export declare function validateTransferDestinations(numbers: string[], options: {
|
|
45
|
+
path: string;
|
|
46
|
+
code: string;
|
|
47
|
+
}): ValidationIssue[];
|
|
48
|
+
//# sourceMappingURL=transfer-spec.d.ts.map
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { parseFieldPath } from '../slot/path.js';
|
|
2
|
+
import { getRowField } from '../slot/row.js';
|
|
3
|
+
import { getSnapshotTable } from '../slot/snapshot.js';
|
|
4
|
+
import { extractTemplateFields } from './condition.js';
|
|
5
|
+
export const E164_NUMBER = /^\+[1-9]\d{7,14}$/;
|
|
6
|
+
export const SIP_URI = /^sips?:[^@\s]+@[^@\s]+$/i;
|
|
7
|
+
const TEMPLATE_RE = /\{\{\s*([A-Za-z_][A-Za-z0-9_]*\.[A-Za-z_][A-Za-z0-9_]*)\s*\}\}/g;
|
|
8
|
+
export function isTransferDestinationLiteral(value) {
|
|
9
|
+
return E164_NUMBER.test(value) || SIP_URI.test(value);
|
|
10
|
+
}
|
|
11
|
+
export function interpolateTemplate(text, read) {
|
|
12
|
+
return text.replace(TEMPLATE_RE, (_all, path) => {
|
|
13
|
+
const value = read(path);
|
|
14
|
+
return value == null ? '' : String(value);
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
function boundRowId(tableName, current) {
|
|
18
|
+
if (!current)
|
|
19
|
+
return undefined;
|
|
20
|
+
if (tableName === 'contact')
|
|
21
|
+
return current.contactId;
|
|
22
|
+
if (tableName === 'case_contact')
|
|
23
|
+
return current.caseContactId;
|
|
24
|
+
if (tableName === 'case_file')
|
|
25
|
+
return current.caseFileId;
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
export function interpolateFromSnapshot(text, snapshot) {
|
|
29
|
+
return interpolateTemplate(text, (path) => {
|
|
30
|
+
const parsed = parseFieldPath(path);
|
|
31
|
+
if (!parsed)
|
|
32
|
+
return '';
|
|
33
|
+
const data = getSnapshotTable(snapshot, parsed.table);
|
|
34
|
+
if (!data)
|
|
35
|
+
return '';
|
|
36
|
+
if (!Array.isArray(data))
|
|
37
|
+
return getRowField(data, parsed.field)?.value ?? '';
|
|
38
|
+
const id = boundRowId(parsed.table, snapshot.current);
|
|
39
|
+
if (!id)
|
|
40
|
+
return '';
|
|
41
|
+
const row = data.find((entry) => entry.id === id);
|
|
42
|
+
return row ? (getRowField(row, parsed.field)?.value ?? '') : '';
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
export function resolveTransferDestinations(numbers, snapshot) {
|
|
46
|
+
const out = [];
|
|
47
|
+
for (const raw of numbers) {
|
|
48
|
+
const resolved = interpolateFromSnapshot(raw, snapshot).trim();
|
|
49
|
+
if (resolved)
|
|
50
|
+
out.push(resolved);
|
|
51
|
+
}
|
|
52
|
+
return out;
|
|
53
|
+
}
|
|
54
|
+
export function resolveTransferSay(say, language, fallback) {
|
|
55
|
+
if (say == null)
|
|
56
|
+
return fallback;
|
|
57
|
+
if (typeof say === 'string')
|
|
58
|
+
return say.trim() || fallback;
|
|
59
|
+
const key = language?.trim().toLowerCase() || 'english';
|
|
60
|
+
const picked = say[key] ?? say.english ?? Object.values(say).find((value) => value.trim().length > 0);
|
|
61
|
+
return picked?.trim() || fallback;
|
|
62
|
+
}
|
|
63
|
+
function interpolatedArg(value, snapshot) {
|
|
64
|
+
return value ? interpolateFromSnapshot(value, snapshot).trim() : '';
|
|
65
|
+
}
|
|
66
|
+
export function resolveTransferArguments(dial, snapshot) {
|
|
67
|
+
const src = dial.arguments;
|
|
68
|
+
if (!src)
|
|
69
|
+
return {};
|
|
70
|
+
const out = {};
|
|
71
|
+
const sendDigits = interpolatedArg(src.sendDigits, snapshot);
|
|
72
|
+
if (sendDigits)
|
|
73
|
+
out.sendDigits = sendDigits;
|
|
74
|
+
const sipUrl = interpolatedArg(src.sipUrl, snapshot);
|
|
75
|
+
if (sipUrl)
|
|
76
|
+
out.sipUrl = sipUrl;
|
|
77
|
+
const sipUsername = interpolatedArg(src.sipUsername, snapshot);
|
|
78
|
+
if (sipUsername)
|
|
79
|
+
out.sipUsername = sipUsername;
|
|
80
|
+
const sipPassword = interpolatedArg(src.sipPassword, snapshot);
|
|
81
|
+
if (sipPassword)
|
|
82
|
+
out.sipPassword = sipPassword;
|
|
83
|
+
if (src.transferCallerIdShown)
|
|
84
|
+
out.transferCallerIdShown = src.transferCallerIdShown;
|
|
85
|
+
return out;
|
|
86
|
+
}
|
|
87
|
+
export function transferArgumentsJson(dial, snapshot) {
|
|
88
|
+
return JSON.stringify(resolveTransferArguments(dial, snapshot));
|
|
89
|
+
}
|
|
90
|
+
export function validateTransferDestinations(numbers, options) {
|
|
91
|
+
const issues = [];
|
|
92
|
+
if (numbers.length === 0) {
|
|
93
|
+
issues.push({
|
|
94
|
+
code: options.code,
|
|
95
|
+
message: 'transfer numbers must not be empty',
|
|
96
|
+
path: options.path,
|
|
97
|
+
});
|
|
98
|
+
return issues;
|
|
99
|
+
}
|
|
100
|
+
for (const number of numbers) {
|
|
101
|
+
if (extractTemplateFields(number).length > 0)
|
|
102
|
+
continue;
|
|
103
|
+
if (!isTransferDestinationLiteral(number)) {
|
|
104
|
+
issues.push({
|
|
105
|
+
code: options.code,
|
|
106
|
+
message: `Transfer target must be E.164 or sip/sips URI: ${number}`,
|
|
107
|
+
path: options.path,
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return issues;
|
|
112
|
+
}
|
|
113
|
+
//# sourceMappingURL=transfer-spec.js.map
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { TopLevelCondition } from 'json-rules-engine';
|
|
2
2
|
import type { FuncRef } from '../functions/func-ref.js';
|
|
3
3
|
import type { FieldPath } from '../slot/path.js';
|
|
4
|
+
import type { TransferDial, TransferFallbackAction } from './transfer-spec.js';
|
|
4
5
|
export type Channel = 'call' | 'sms' | 'email' | 'webchat';
|
|
5
6
|
/** Nested condition from the engine (`all` members): leaf or `TopLevelCondition`. */
|
|
6
7
|
export type Condition = Extract<TopLevelCondition, {
|
|
@@ -40,6 +41,11 @@ export type FirstMessage = {
|
|
|
40
41
|
kind: 'fn';
|
|
41
42
|
fn: string;
|
|
42
43
|
};
|
|
44
|
+
export type TransferInterruptAction = {
|
|
45
|
+
type: 'transfer';
|
|
46
|
+
escalateCriteria?: string[];
|
|
47
|
+
noEscalationCriteria?: string[];
|
|
48
|
+
} & TransferDial;
|
|
43
49
|
export type Interrupt = {
|
|
44
50
|
id: string;
|
|
45
51
|
label: string;
|
|
@@ -47,11 +53,7 @@ export type Interrupt = {
|
|
|
47
53
|
name: string;
|
|
48
54
|
description: string;
|
|
49
55
|
};
|
|
50
|
-
action: {
|
|
51
|
-
type: 'transfer';
|
|
52
|
-
numbers: string[];
|
|
53
|
-
mode: 'cold' | 'warm';
|
|
54
|
-
} | {
|
|
56
|
+
action: TransferInterruptAction | {
|
|
55
57
|
type: 'switch-language';
|
|
56
58
|
languages: string[];
|
|
57
59
|
};
|
|
@@ -86,12 +88,32 @@ export type TransferBlock = {
|
|
|
86
88
|
kind: 'transfer';
|
|
87
89
|
id: string;
|
|
88
90
|
label: string;
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
91
|
+
} & TransferDial;
|
|
92
|
+
export type IvrDigit = '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | '*' | '#';
|
|
93
|
+
export type IvrFallbackAction = TransferFallbackAction;
|
|
94
|
+
export type DtmfResult = {
|
|
95
|
+
result: IvrDigit[];
|
|
96
|
+
value: string;
|
|
97
|
+
};
|
|
98
|
+
export type IvrBlock = {
|
|
99
|
+
kind: 'ivr';
|
|
100
|
+
id: string;
|
|
101
|
+
label: string;
|
|
102
|
+
onDtmfResult: FuncRef;
|
|
103
|
+
finishWhen: FinishWhen;
|
|
104
|
+
fallbackAction: IvrFallbackAction;
|
|
105
|
+
confirmTimeoutMs: number;
|
|
106
|
+
pauseAfterSayMs: number;
|
|
107
|
+
loopSay: string;
|
|
108
|
+
enterSay: string | null;
|
|
109
|
+
succeedSay: string | null;
|
|
110
|
+
unsuccessSay: string | null;
|
|
111
|
+
finishOnKey?: IvrDigit | null;
|
|
112
|
+
minDigits?: number | null;
|
|
113
|
+
maxDigits?: number | null;
|
|
114
|
+
validateDigits?: IvrDigit[] | null;
|
|
115
|
+
};
|
|
116
|
+
export type AgendaBlock = TopicBlock | ActionBlock | TransferBlock | IvrBlock;
|
|
95
117
|
export type CompiledTopicBlock = Omit<TopicBlock, 'fields' | 'finishWhen'> & {
|
|
96
118
|
fields: FieldPath[];
|
|
97
119
|
finishWhen: CompiledWhen;
|
|
@@ -104,7 +126,12 @@ export type CompiledTransferBlock = TransferBlock & {
|
|
|
104
126
|
finishWhen: CompiledWhen;
|
|
105
127
|
resultField: FieldPath;
|
|
106
128
|
};
|
|
107
|
-
export type
|
|
129
|
+
export type CompiledIvrBlock = Omit<IvrBlock, 'onDtmfResult' | 'finishWhen'> & {
|
|
130
|
+
onDtmfResult: string;
|
|
131
|
+
finishWhen: CompiledWhen;
|
|
132
|
+
resultField: FieldPath;
|
|
133
|
+
};
|
|
134
|
+
export type CompiledAgendaBlock = CompiledTopicBlock | CompiledActionBlock | CompiledTransferBlock | CompiledIvrBlock;
|
|
108
135
|
export type SectionPlacement = {
|
|
109
136
|
kind: 'section';
|
|
110
137
|
placementId: string;
|
|
@@ -136,7 +163,8 @@ export type FlatNodePlacement = {
|
|
|
136
163
|
export type FlatTopicItem = CompiledTopicBlock & FlatNodePlacement;
|
|
137
164
|
export type FlatActionItem = CompiledActionBlock & FlatNodePlacement;
|
|
138
165
|
export type FlatTransferItem = CompiledTransferBlock & FlatNodePlacement;
|
|
139
|
-
export type
|
|
166
|
+
export type FlatIvrItem = CompiledIvrBlock & FlatNodePlacement;
|
|
167
|
+
export type FlatAgendaItem = FlatTopicItem | FlatActionItem | FlatTransferItem | FlatIvrItem;
|
|
140
168
|
export type CompiledAgenda = {
|
|
141
169
|
header: AgendaHeader | null;
|
|
142
170
|
flat: FlatAgendaItem[];
|
|
@@ -150,6 +178,9 @@ export type ActionNode = ActionBlock & {
|
|
|
150
178
|
export type TransferNode = TransferBlock & {
|
|
151
179
|
applyWhen: AuthoringWhen;
|
|
152
180
|
};
|
|
181
|
+
export type IvrNode = IvrBlock & {
|
|
182
|
+
applyWhen: AuthoringWhen;
|
|
183
|
+
};
|
|
153
184
|
export type SectionNode = {
|
|
154
185
|
kind: 'section';
|
|
155
186
|
id: string;
|
|
@@ -167,7 +198,7 @@ export type SwitchOnNode = {
|
|
|
167
198
|
cases: Record<string, AuthoringNode[]>;
|
|
168
199
|
otherwise?: AuthoringNode[];
|
|
169
200
|
};
|
|
170
|
-
export type AuthoringNode = TopicNode | ActionNode | TransferNode | SectionNode | BranchNode | SwitchOnNode;
|
|
201
|
+
export type AuthoringNode = TopicNode | ActionNode | TransferNode | IvrNode | SectionNode | BranchNode | SwitchOnNode;
|
|
171
202
|
export type AgendaTree = {
|
|
172
203
|
kind: 'agenda';
|
|
173
204
|
id: string;
|