@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
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
const READ_ONLY_KINDS = new Set(['compute', 'boolCompute', 'caseContextView']);
|
|
2
|
+
/** Names safe to cache: `caseContextView` plus every compute / boolCompute. */
|
|
3
|
+
export function readOnlyComputeNames(functions) {
|
|
4
|
+
const names = new Set(['caseContextView']);
|
|
5
|
+
if (!functions)
|
|
6
|
+
return names;
|
|
7
|
+
for (const [name, entry] of Object.entries(functions)) {
|
|
8
|
+
if (READ_ONLY_KINDS.has(entry.kind))
|
|
9
|
+
names.add(name);
|
|
10
|
+
}
|
|
11
|
+
return names;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Cache read-only `execute` results by function name + the single input object.
|
|
15
|
+
* Calls with extra args bypass the cache so a shared snapshot cannot alias
|
|
16
|
+
* two different argument lists.
|
|
17
|
+
*
|
|
18
|
+
* Do not wrap a host used by derived recompute: that path mutates one snapshot
|
|
19
|
+
* in place between `compute` calls. Stage walk / when-eval is safe (new snapshot
|
|
20
|
+
* after writes).
|
|
21
|
+
*/
|
|
22
|
+
export function withReadOnlyComputeCache(host, options) {
|
|
23
|
+
const allow = resolveAllow(options?.readOnly);
|
|
24
|
+
const cache = new WeakMap();
|
|
25
|
+
return {
|
|
26
|
+
async execute(name, args = []) {
|
|
27
|
+
const input = args[0];
|
|
28
|
+
if (!allow(name) || args.length !== 1 || input == null || typeof input !== 'object') {
|
|
29
|
+
return host.execute(name, args);
|
|
30
|
+
}
|
|
31
|
+
let byName = cache.get(input);
|
|
32
|
+
if (!byName) {
|
|
33
|
+
byName = new Map();
|
|
34
|
+
cache.set(input, byName);
|
|
35
|
+
}
|
|
36
|
+
const hit = byName.get(name);
|
|
37
|
+
if (hit)
|
|
38
|
+
return hit;
|
|
39
|
+
const pending = host.execute(name, args);
|
|
40
|
+
byName.set(name, pending);
|
|
41
|
+
try {
|
|
42
|
+
return await pending;
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
if (byName.get(name) === pending)
|
|
46
|
+
byName.delete(name);
|
|
47
|
+
throw error;
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
dispose() {
|
|
51
|
+
host.dispose();
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
function resolveAllow(readOnly) {
|
|
56
|
+
if (typeof readOnly === 'function')
|
|
57
|
+
return readOnly;
|
|
58
|
+
const names = new Set(readOnly ?? ['caseContextView']);
|
|
59
|
+
return (name) => names.has(name);
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=read-only-cache.js.map
|
|
@@ -12,6 +12,10 @@ export type SourceComputeHostOptions = {
|
|
|
12
12
|
data?: ActionDataSlice;
|
|
13
13
|
/** When set with data.config.backendUrl, uses Http RpcApiHost. */
|
|
14
14
|
runToken?: string;
|
|
15
|
+
/** Runtime version forwarded on every `ctx.api` RPC. Required when RpcApiHost is used. */
|
|
16
|
+
runtimeVersion?: string;
|
|
17
|
+
/** Runtime dependency source forwarded with runtimeVersion. Required when RpcApiHost is used. */
|
|
18
|
+
runtimeSource?: 'local-file' | 'npm';
|
|
15
19
|
/** Forwarded on every `ctx.api` RPC as `x-request-id`. */
|
|
16
20
|
getRequestId?: () => string | undefined;
|
|
17
21
|
/** Forwarded to script log RPC calls. */
|
|
@@ -49,11 +49,16 @@ export function createSourceComputeHost(options) {
|
|
|
49
49
|
const caseContext = viewFn(requireSlots(name, args));
|
|
50
50
|
const ctx = await systemContext();
|
|
51
51
|
const fn = def.main;
|
|
52
|
-
return fn(caseContext, ctx);
|
|
52
|
+
return fn(caseContext, { ...ctx, params: args[1] ?? {} });
|
|
53
53
|
}
|
|
54
54
|
const params = resolveSystemFnParams(args);
|
|
55
|
+
const contextStarted = Date.now();
|
|
55
56
|
const ctx = await systemContext();
|
|
56
|
-
|
|
57
|
+
console.log(`[source timing] ${name} context ${Date.now() - contextStarted}ms`);
|
|
58
|
+
const functionStarted = Date.now();
|
|
59
|
+
const result = await def.main({ ...ctx, params });
|
|
60
|
+
console.log(`[source timing] ${name} function ${Date.now() - functionStarted}ms`);
|
|
61
|
+
return result;
|
|
57
62
|
},
|
|
58
63
|
dispose() { },
|
|
59
64
|
};
|
|
@@ -62,9 +67,14 @@ function resolveApiHost(options) {
|
|
|
62
67
|
if (options.apiHost)
|
|
63
68
|
return options.apiHost;
|
|
64
69
|
if (options.runToken && options.data?.config.backendUrl) {
|
|
70
|
+
if (!options.runtimeVersion || !options.runtimeSource) {
|
|
71
|
+
throw new Error('runtimeVersion and runtimeSource are required when using RpcApiHost');
|
|
72
|
+
}
|
|
65
73
|
return createRpcApiHost({
|
|
66
74
|
baseUrl: options.data.config.backendUrl,
|
|
67
75
|
getToken: () => options.runToken,
|
|
76
|
+
runtimeVersion: options.runtimeVersion,
|
|
77
|
+
runtimeSource: options.runtimeSource,
|
|
68
78
|
getRequestId: options.getRequestId,
|
|
69
79
|
getTags: options.getTags,
|
|
70
80
|
});
|
|
@@ -19,7 +19,25 @@ export type Slots = Record<string, unknown>;
|
|
|
19
19
|
* With `view.ts`, use `CaseContextOf<typeof caseContextView>` from that module.
|
|
20
20
|
*/
|
|
21
21
|
export type CaseContext = Record<string, unknown>;
|
|
22
|
-
|
|
22
|
+
/** Flat writes for single-row tables, or the current-bound row of an entity table. */
|
|
23
|
+
export type FieldWrites = Record<FieldPath, unknown>;
|
|
24
|
+
/** Address one row of a multi-row table (`children`, `contact`, custom arrays). */
|
|
25
|
+
export type RowWrite = {
|
|
26
|
+
table: string;
|
|
27
|
+
rowId: string;
|
|
28
|
+
values: Record<string, unknown>;
|
|
29
|
+
};
|
|
30
|
+
/** Mix flat field writes with explicit row writes in one return. */
|
|
31
|
+
export type StructuredWrites = {
|
|
32
|
+
fields?: FieldWrites;
|
|
33
|
+
rows?: RowWrite[];
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* Function / platform write payload.
|
|
37
|
+
* Flat `{ '表.字段': 值 }` for single-row (or current-party) fields;
|
|
38
|
+
* `{ table, rowId, values }` to write a specific multi-row.
|
|
39
|
+
*/
|
|
40
|
+
export type Writes = FieldWrites | RowWrite | StructuredWrites;
|
|
23
41
|
export type Logger = {
|
|
24
42
|
info: (message: string, extra?: Record<string, unknown>) => void;
|
|
25
43
|
warn: (message: string, extra?: Record<string, unknown>) => void;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import type { CustomFn, DefinedFunc, FuncOptions } from '../types.js';
|
|
2
2
|
import type { CaseContext } from '../../context/types.js';
|
|
3
|
-
export declare function defineCustomFunc<S = CaseContext>(main: CustomFn<S>, options?: FuncOptions): DefinedFunc;
|
|
3
|
+
export declare function defineCustomFunc<S = CaseContext, P extends object = object>(main: CustomFn<S, P>, options?: FuncOptions): DefinedFunc;
|
|
4
4
|
//# sourceMappingURL=custom.d.ts.map
|
|
@@ -6,16 +6,16 @@ export type FuncOptions = {
|
|
|
6
6
|
};
|
|
7
7
|
/** First arg is CaseContext (or Slots when the sequence has no view). */
|
|
8
8
|
export type ComputeFn<S = CaseContext, R = unknown> = (caseContext: S) => R | Promise<R>;
|
|
9
|
-
export type
|
|
9
|
+
export type FunctionCtx<Params extends object> = SystemContext & {
|
|
10
|
+
params: Params;
|
|
11
|
+
};
|
|
12
|
+
export type CustomFn<S = CaseContext, P extends object = object> = (caseContext: S, ctx: FunctionCtx<P>) => Promise<Writes | null>;
|
|
10
13
|
/** Inbound webhook request payload. */
|
|
11
14
|
export type WebHookFunctionParams = {
|
|
12
15
|
headers: Record<string, string | string[] | undefined>;
|
|
13
16
|
body: unknown;
|
|
14
17
|
rawBody?: string;
|
|
15
18
|
};
|
|
16
|
-
export type FunctionCtx<Params extends object> = SystemContext & {
|
|
17
|
-
params: Params;
|
|
18
|
-
};
|
|
19
19
|
export type FunctionCall<Params extends object, Return> = (ctx: FunctionCtx<Params>) => Promise<Return>;
|
|
20
20
|
export type WebHookFunction = FunctionCall<WebHookFunctionParams, void>;
|
|
21
21
|
export type GetFirstMessageParams<S = CaseContext> = {
|
|
@@ -9,7 +9,7 @@ export { isValidDataTypeValue, validateExtractedField, validateBareValue } from
|
|
|
9
9
|
export { buildSlotFillingJsonSchema, buildFieldCatalog, type SfSchemaOptions } from './sf-schema.js';
|
|
10
10
|
export { validateSlotFillingOutput, type ValidateSlotFillingOptions } from './sf-validate.js';
|
|
11
11
|
export { snapshotFrom, cloneSnapshot, getSingleField, getSnapshotTable, type SnapshotSeed } from './snapshot.js';
|
|
12
|
-
export { applyFieldPatch, applyWrites, applySlotFilling, markAskedUnknown, type ApplySlotFillingOptions, type FieldPatchInput, } from './write.js';
|
|
12
|
+
export { applyFieldPatch, applyWrites, applySlotFilling, markAskedUnknown, isRowWrite, isStructuredWrites, normalizeWrites, type ApplySlotFillingOptions, type FieldPatchInput, } from './write.js';
|
|
13
13
|
export type { ComputeFnMap, ComputeHost, ComputeHostOrFns, LocalComputeHostOptions, RecomputeOptions, } from './derived.js';
|
|
14
14
|
export { createLocalComputeHost, createSourceComputeHost, resolveComputeHost, recomputeDerived, applyFieldPatchAndRecompute, applyWritesAndRecompute, applySlotFillingAndRecompute, } from './derived.js';
|
|
15
15
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/core/slot/index.js
CHANGED
|
@@ -9,6 +9,6 @@ export { isValidDataTypeValue, validateExtractedField, validateBareValue } from
|
|
|
9
9
|
export { buildSlotFillingJsonSchema, buildFieldCatalog } from './sf-schema.js';
|
|
10
10
|
export { validateSlotFillingOutput } from './sf-validate.js';
|
|
11
11
|
export { snapshotFrom, cloneSnapshot, getSingleField, getSnapshotTable } from './snapshot.js';
|
|
12
|
-
export { applyFieldPatch, applyWrites, applySlotFilling, markAskedUnknown, } from './write.js';
|
|
12
|
+
export { applyFieldPatch, applyWrites, applySlotFilling, markAskedUnknown, isRowWrite, isStructuredWrites, normalizeWrites, } from './write.js';
|
|
13
13
|
export { createLocalComputeHost, createSourceComputeHost, resolveComputeHost, recomputeDerived, applyFieldPatchAndRecompute, applyWritesAndRecompute, applySlotFillingAndRecompute, } from './derived.js';
|
|
14
14
|
//# sourceMappingURL=index.js.map
|
|
@@ -1,9 +1,16 @@
|
|
|
1
|
-
import type { FieldState, Writes } from '../context/types.js';
|
|
1
|
+
import type { FieldState, FieldWrites, RowWrite, StructuredWrites, Writes } from '../context/types.js';
|
|
2
2
|
import type { FieldPath } from './path.js';
|
|
3
3
|
import type { ApplyWritesOptions, ApplyWritesResult, CompiledSlotSchema, RuntimeSlotsSnapshot, SlotFillingOutput, UpdatedBy } from './types.js';
|
|
4
|
+
export declare function isRowWrite(value: unknown): value is RowWrite;
|
|
5
|
+
export declare function isStructuredWrites(value: unknown): value is StructuredWrites;
|
|
6
|
+
export declare function normalizeWrites(writes: Writes | null | undefined): {
|
|
7
|
+
fields: FieldWrites;
|
|
8
|
+
rows: RowWrite[];
|
|
9
|
+
} | null;
|
|
4
10
|
/**
|
|
5
11
|
* Apply bare Writes (function / human / import / platform) onto a snapshot.
|
|
6
12
|
* Validates type/enum and write permissions. Detects C5 changes and appends history.
|
|
13
|
+
* Multi-row tables: pass `{ table, rowId, values }` (or `rows` on StructuredWrites).
|
|
7
14
|
*/
|
|
8
15
|
export declare function applyWrites(schema: CompiledSlotSchema, snapshot: RuntimeSlotsSnapshot, writes: Writes | null | undefined, options: ApplyWritesOptions): ApplyWritesResult;
|
|
9
16
|
export type FieldPatchInput = {
|
package/dist/core/slot/write.js
CHANGED
|
@@ -4,6 +4,70 @@ import { formatFieldPath, parseFieldPath, requireFieldPath } from './path.js';
|
|
|
4
4
|
import { getRowField, isReservedTableName, isRowIdField, mintRowId, slotFieldNames } from './row.js';
|
|
5
5
|
import { cloneSnapshot, getSnapshotTable } from './snapshot.js';
|
|
6
6
|
import { validateBareValue, validateExtractedField } from './value.js';
|
|
7
|
+
export function isRowWrite(value) {
|
|
8
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
9
|
+
return false;
|
|
10
|
+
const o = value;
|
|
11
|
+
return (typeof o.table === 'string' &&
|
|
12
|
+
typeof o.rowId === 'string' &&
|
|
13
|
+
o.values !== null &&
|
|
14
|
+
typeof o.values === 'object' &&
|
|
15
|
+
!Array.isArray(o.values));
|
|
16
|
+
}
|
|
17
|
+
export function isStructuredWrites(value) {
|
|
18
|
+
if (!value || typeof value !== 'object' || Array.isArray(value) || isRowWrite(value))
|
|
19
|
+
return false;
|
|
20
|
+
const keys = Object.keys(value);
|
|
21
|
+
return keys.length > 0 && keys.every((key) => key === 'fields' || key === 'rows');
|
|
22
|
+
}
|
|
23
|
+
export function normalizeWrites(writes) {
|
|
24
|
+
if (writes == null)
|
|
25
|
+
return null;
|
|
26
|
+
if (isRowWrite(writes)) {
|
|
27
|
+
return { fields: {}, rows: [writes] };
|
|
28
|
+
}
|
|
29
|
+
if (isStructuredWrites(writes)) {
|
|
30
|
+
const fields = writes.fields && typeof writes.fields === 'object' && !Array.isArray(writes.fields) ? writes.fields : {};
|
|
31
|
+
const rows = Array.isArray(writes.rows) ? writes.rows.filter(isRowWrite) : [];
|
|
32
|
+
if (Object.keys(fields).length === 0 && rows.length === 0)
|
|
33
|
+
return null;
|
|
34
|
+
return { fields, rows };
|
|
35
|
+
}
|
|
36
|
+
if (Object.keys(writes).length === 0)
|
|
37
|
+
return null;
|
|
38
|
+
return { fields: writes, rows: [] };
|
|
39
|
+
}
|
|
40
|
+
function boundRowId(tableName, current) {
|
|
41
|
+
if (!current)
|
|
42
|
+
return undefined;
|
|
43
|
+
if (tableName === 'contact')
|
|
44
|
+
return current.contactId;
|
|
45
|
+
if (tableName === 'case_contact')
|
|
46
|
+
return current.caseContactId;
|
|
47
|
+
if (tableName === 'case_file')
|
|
48
|
+
return current.caseFileId;
|
|
49
|
+
return undefined;
|
|
50
|
+
}
|
|
51
|
+
function writeTargetRow(schema, snapshot, tableName, rowId) {
|
|
52
|
+
const table = getCompiledTable(schema, tableName);
|
|
53
|
+
if (!table)
|
|
54
|
+
return null;
|
|
55
|
+
const data = getSnapshotTable(snapshot, tableName);
|
|
56
|
+
if (!data)
|
|
57
|
+
return null;
|
|
58
|
+
if (isSnapshotSingleRow(table) && !Array.isArray(data)) {
|
|
59
|
+
if (rowId && data.id && data.id !== rowId)
|
|
60
|
+
return null;
|
|
61
|
+
return data;
|
|
62
|
+
}
|
|
63
|
+
if (Array.isArray(data)) {
|
|
64
|
+
const id = rowId ?? boundRowId(tableName, snapshot.current);
|
|
65
|
+
if (!id)
|
|
66
|
+
return null;
|
|
67
|
+
return data.find((row) => row.id === id) ?? null;
|
|
68
|
+
}
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
7
71
|
function extractedToFieldValue(extracted, updatedBy, now) {
|
|
8
72
|
return makeFieldValue({
|
|
9
73
|
reason: extracted.reason,
|
|
@@ -13,7 +77,7 @@ function extractedToFieldValue(extracted, updatedBy, now) {
|
|
|
13
77
|
updatedAt: now,
|
|
14
78
|
});
|
|
15
79
|
}
|
|
16
|
-
function assertWritableNormal(schema, path, options) {
|
|
80
|
+
function assertWritableNormal(schema, snapshot, path, options, rowId) {
|
|
17
81
|
const { table, field: fieldName } = requireFieldPath(path);
|
|
18
82
|
const tableDef = getCompiledTable(schema, table);
|
|
19
83
|
const field = tableDef?.fields[fieldName];
|
|
@@ -49,23 +113,75 @@ function assertWritableNormal(schema, path, options) {
|
|
|
49
113
|
},
|
|
50
114
|
];
|
|
51
115
|
}
|
|
52
|
-
if (!isSnapshotSingleRow(tableDef)) {
|
|
116
|
+
if (!isSnapshotSingleRow(tableDef) && !writeTargetRow(schema, snapshot, table, rowId)) {
|
|
53
117
|
return [
|
|
54
118
|
{
|
|
55
|
-
code: 'multi-path-write',
|
|
56
|
-
message:
|
|
119
|
+
code: rowId ? 'row-not-found' : 'multi-path-write',
|
|
120
|
+
message: rowId
|
|
121
|
+
? `Row ${rowId} not found in ${table}`
|
|
122
|
+
: `Use Slot Filling or { table, rowId, values } to write multi-row field ${path}`,
|
|
57
123
|
path,
|
|
58
124
|
},
|
|
59
125
|
];
|
|
60
126
|
}
|
|
61
127
|
return [];
|
|
62
128
|
}
|
|
129
|
+
function applyBareFieldWrite(schema, original, next, path, value, rowId, options, now, errors, changed, history) {
|
|
130
|
+
const writeIssues = assertWritableNormal(schema, original, path, options, rowId);
|
|
131
|
+
if (writeIssues.length > 0) {
|
|
132
|
+
errors.push(...writeIssues);
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
const parsed = parseFieldPath(path);
|
|
136
|
+
if (!parsed) {
|
|
137
|
+
errors.push({ code: 'unknown-field', message: `Invalid field path: ${path}`, path });
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
const field = getCompiledTable(schema, parsed.table)?.fields[parsed.field];
|
|
141
|
+
if (!field) {
|
|
142
|
+
errors.push({ code: 'unknown-field', message: `Unknown field: ${path}`, path });
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
const valueIssues = validateBareValue(field, value, path, {
|
|
146
|
+
allowNull: !field.derived && field.nullable,
|
|
147
|
+
});
|
|
148
|
+
if (valueIssues.length > 0) {
|
|
149
|
+
errors.push(...valueIssues);
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
const tableData = writeTargetRow(schema, next, parsed.table, rowId);
|
|
153
|
+
if (!tableData)
|
|
154
|
+
return;
|
|
155
|
+
const prev = getRowField(tableData, parsed.field);
|
|
156
|
+
if (!prev)
|
|
157
|
+
return;
|
|
158
|
+
const committed = commitFieldWrite(prev, makeFieldValue({
|
|
159
|
+
value: (value ?? null),
|
|
160
|
+
state: 'filled',
|
|
161
|
+
reason: null,
|
|
162
|
+
updatedBy: options.source,
|
|
163
|
+
updatedAt: now,
|
|
164
|
+
}));
|
|
165
|
+
tableData[parsed.field] = committed.field;
|
|
166
|
+
if (committed.changed) {
|
|
167
|
+
changed.push(parsed.path);
|
|
168
|
+
if (committed.historyEntry) {
|
|
169
|
+
history.push({
|
|
170
|
+
path: parsed.path,
|
|
171
|
+
entry: committed.historyEntry,
|
|
172
|
+
rowId: typeof tableData.id === 'string' && tableData.id ? tableData.id : undefined,
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
63
177
|
/**
|
|
64
178
|
* Apply bare Writes (function / human / import / platform) onto a snapshot.
|
|
65
179
|
* Validates type/enum and write permissions. Detects C5 changes and appends history.
|
|
180
|
+
* Multi-row tables: pass `{ table, rowId, values }` (or `rows` on StructuredWrites).
|
|
66
181
|
*/
|
|
67
182
|
export function applyWrites(schema, snapshot, writes, options) {
|
|
68
|
-
|
|
183
|
+
const normalized = normalizeWrites(writes);
|
|
184
|
+
if (!normalized) {
|
|
69
185
|
return { snapshot, changed: [], history: [] };
|
|
70
186
|
}
|
|
71
187
|
const now = options.now ?? Date.now();
|
|
@@ -73,38 +189,21 @@ export function applyWrites(schema, snapshot, writes, options) {
|
|
|
73
189
|
const changed = [];
|
|
74
190
|
const history = [];
|
|
75
191
|
const errors = [];
|
|
76
|
-
for (const [path, value] of Object.entries(
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
192
|
+
for (const [path, value] of Object.entries(normalized.fields)) {
|
|
193
|
+
applyBareFieldWrite(schema, snapshot, next, path, value, undefined, options, now, errors, changed, history);
|
|
194
|
+
}
|
|
195
|
+
for (const row of normalized.rows) {
|
|
196
|
+
if (!row.table || !row.rowId) {
|
|
197
|
+
errors.push({
|
|
198
|
+
code: 'row-not-found',
|
|
199
|
+
message: 'Row write requires table and rowId',
|
|
200
|
+
path: row.table || undefined,
|
|
201
|
+
});
|
|
80
202
|
continue;
|
|
81
203
|
}
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
allowNull: !field.derived && field.nullable,
|
|
86
|
-
});
|
|
87
|
-
if (valueIssues.length > 0) {
|
|
88
|
-
errors.push(...valueIssues);
|
|
89
|
-
continue;
|
|
90
|
-
}
|
|
91
|
-
const tableData = getSnapshotTable(next, parsed.table);
|
|
92
|
-
const prev = getRowField(tableData, parsed.field);
|
|
93
|
-
if (!prev)
|
|
94
|
-
continue;
|
|
95
|
-
const committed = commitFieldWrite(prev, makeFieldValue({
|
|
96
|
-
value: (value ?? null),
|
|
97
|
-
state: 'filled',
|
|
98
|
-
reason: null,
|
|
99
|
-
updatedBy: options.source,
|
|
100
|
-
updatedAt: now,
|
|
101
|
-
}));
|
|
102
|
-
tableData[parsed.field] = committed.field;
|
|
103
|
-
if (committed.changed) {
|
|
104
|
-
changed.push(parsed.path);
|
|
105
|
-
if (committed.historyEntry) {
|
|
106
|
-
history.push({ path: parsed.path, entry: committed.historyEntry });
|
|
107
|
-
}
|
|
204
|
+
for (const [fieldName, value] of Object.entries(row.values)) {
|
|
205
|
+
const path = formatFieldPath(row.table, fieldName);
|
|
206
|
+
applyBareFieldWrite(schema, snapshot, next, path, value, row.rowId, options, now, errors, changed, history);
|
|
108
207
|
}
|
|
109
208
|
}
|
|
110
209
|
if (errors.length > 0) {
|
|
@@ -201,7 +300,11 @@ export function applySlotFilling(schema, snapshot, output, options = {}) {
|
|
|
201
300
|
if (committed.changed) {
|
|
202
301
|
changed.add(path);
|
|
203
302
|
if (committed.historyEntry) {
|
|
204
|
-
history.push({
|
|
303
|
+
history.push({
|
|
304
|
+
path,
|
|
305
|
+
entry: committed.historyEntry,
|
|
306
|
+
rowId: typeof tableData.id === 'string' && tableData.id ? tableData.id : undefined,
|
|
307
|
+
});
|
|
205
308
|
}
|
|
206
309
|
}
|
|
207
310
|
}
|
|
@@ -241,7 +344,7 @@ export function applySlotFilling(schema, snapshot, output, options = {}) {
|
|
|
241
344
|
if (committed.changed) {
|
|
242
345
|
changed.add(path);
|
|
243
346
|
if (committed.historyEntry) {
|
|
244
|
-
history.push({ path, entry: committed.historyEntry });
|
|
347
|
+
history.push({ path, entry: committed.historyEntry, rowId: id });
|
|
245
348
|
}
|
|
246
349
|
}
|
|
247
350
|
}
|
|
@@ -310,7 +413,11 @@ export function markAskedUnknown(schema, snapshot, fields, options = {}) {
|
|
|
310
413
|
if (committed.changed) {
|
|
311
414
|
changed.push(parsed.path);
|
|
312
415
|
if (committed.historyEntry) {
|
|
313
|
-
history.push({
|
|
416
|
+
history.push({
|
|
417
|
+
path: parsed.path,
|
|
418
|
+
entry: committed.historyEntry,
|
|
419
|
+
rowId: typeof tableData.id === 'string' && tableData.id ? tableData.id : undefined,
|
|
420
|
+
});
|
|
314
421
|
}
|
|
315
422
|
}
|
|
316
423
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { FuncRef } from '../functions/func-ref.js';
|
|
2
2
|
import type { FieldPath } from '../slot/path.js';
|
|
3
|
-
import type { ActionNode, AgendaHeader, AgendaTree, AuthoringNode, AuthoringWhen, BranchNode, FinishWhen, OnNoProgress, SectionNode, SwitchOnNode, TopicNode, TransferNode } from './types.js';
|
|
3
|
+
import type { ActionNode, AgendaHeader, AgendaTree, AuthoringNode, AuthoringWhen, BranchNode, FinishWhen, IvrDigit, IvrFallbackAction, IvrNode, OnNoProgress, SectionNode, SwitchOnNode, TopicNode, TransferNode } from './types.js';
|
|
4
|
+
import type { TransferDial, TransferMode, TransferSay } from './transfer-spec.js';
|
|
4
5
|
export declare function topic(id: string, config: {
|
|
5
6
|
label: string;
|
|
6
7
|
applyWhen: AuthoringWhen;
|
|
@@ -20,10 +21,27 @@ export declare function transfer(id: string, config: {
|
|
|
20
21
|
label: string;
|
|
21
22
|
applyWhen: AuthoringWhen;
|
|
22
23
|
numbers: string[];
|
|
23
|
-
mode:
|
|
24
|
-
say:
|
|
25
|
-
unsuccessfulSay:
|
|
26
|
-
}): TransferNode;
|
|
24
|
+
mode: TransferMode;
|
|
25
|
+
say: TransferSay;
|
|
26
|
+
unsuccessfulSay: TransferSay | null;
|
|
27
|
+
} & Omit<TransferDial, 'numbers' | 'mode' | 'say' | 'unsuccessfulSay'>): TransferNode;
|
|
28
|
+
export declare function ivr(id: string, config: {
|
|
29
|
+
label: string;
|
|
30
|
+
applyWhen: AuthoringWhen;
|
|
31
|
+
onDtmfResult: FuncRef;
|
|
32
|
+
finishWhen: FinishWhen;
|
|
33
|
+
fallbackAction: IvrFallbackAction;
|
|
34
|
+
confirmTimeoutMs: number;
|
|
35
|
+
pauseAfterSayMs: number;
|
|
36
|
+
loopSay: string;
|
|
37
|
+
enterSay: string | null;
|
|
38
|
+
succeedSay: string | null;
|
|
39
|
+
unsuccessSay: string | null;
|
|
40
|
+
finishOnKey?: IvrDigit | null;
|
|
41
|
+
minDigits?: number | null;
|
|
42
|
+
maxDigits?: number | null;
|
|
43
|
+
validateDigits?: IvrDigit[] | null;
|
|
44
|
+
}): IvrNode;
|
|
27
45
|
export declare function section(id: string, config: {
|
|
28
46
|
label: string;
|
|
29
47
|
applyWhen: AuthoringWhen;
|
|
@@ -7,6 +7,9 @@ export function action(id, config) {
|
|
|
7
7
|
export function transfer(id, config) {
|
|
8
8
|
return { kind: 'transfer', id, ...config };
|
|
9
9
|
}
|
|
10
|
+
export function ivr(id, config) {
|
|
11
|
+
return { kind: 'ivr', id, ...config };
|
|
12
|
+
}
|
|
10
13
|
export function section(id, config, children) {
|
|
11
14
|
return { kind: 'section', id, label: config.label, applyWhen: config.applyWhen, children };
|
|
12
15
|
}
|
|
@@ -21,7 +21,7 @@ export type StageDriveResult = {
|
|
|
21
21
|
fused: boolean;
|
|
22
22
|
};
|
|
23
23
|
/**
|
|
24
|
-
* Walk until a topic/transfer/complete, executing actions inline.
|
|
24
|
+
* Walk until a topic/transfer/ivr/complete, executing actions inline.
|
|
25
25
|
* Action returning null is skipped for this tick; writes trigger a full re-walk.
|
|
26
26
|
*/
|
|
27
27
|
export declare function driveAgenda(items: FlatAgendaItem[], state: StageDriveState, compute: ComputeHost, hooks: StageDriveHooks, options?: {
|
package/dist/core/stage/drive.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
import { normalizeWrites } from '../slot/write.js';
|
|
1
2
|
import { evaluateWhen } from './finish-when.js';
|
|
2
3
|
import { unresolvedFinishWhenFields, walkAgenda } from './walk.js';
|
|
3
4
|
export const DEFAULT_MAX_ACTIONS_PER_TURN = 10;
|
|
4
5
|
/**
|
|
5
|
-
* Walk until a topic/transfer/complete, executing actions inline.
|
|
6
|
+
* Walk until a topic/transfer/ivr/complete, executing actions inline.
|
|
6
7
|
* Action returning null is skipped for this tick; writes trigger a full re-walk.
|
|
7
8
|
*/
|
|
8
9
|
export async function driveAgenda(items, state, compute, hooks, options = {}) {
|
|
@@ -11,11 +12,11 @@ export async function driveAgenda(items, state, compute, hooks, options = {}) {
|
|
|
11
12
|
let currentPlacementId = state.currentPlacementId;
|
|
12
13
|
const skip = new Set();
|
|
13
14
|
let actionsRun = 0;
|
|
14
|
-
let lastWalk
|
|
15
|
+
let lastWalk;
|
|
15
16
|
while (true) {
|
|
16
17
|
lastWalk = await walkAgenda(items, snapshot, { currentPlacementId, compute, skipPlacementIds: skip });
|
|
17
18
|
const hit = lastWalk.hit;
|
|
18
|
-
if (!hit || hit.kind === 'topic' || hit.kind === 'transfer') {
|
|
19
|
+
if (!hit || hit.kind === 'topic' || hit.kind === 'transfer' || hit.kind === 'ivr') {
|
|
19
20
|
return {
|
|
20
21
|
snapshot,
|
|
21
22
|
currentPlacementId: hit?.placementId ?? currentPlacementId,
|
|
@@ -35,13 +36,13 @@ export async function driveAgenda(items, state, compute, hooks, options = {}) {
|
|
|
35
36
|
}
|
|
36
37
|
actionsRun += 1;
|
|
37
38
|
const writes = await hooks.runAction(hit, snapshot);
|
|
38
|
-
if (writes
|
|
39
|
-
|
|
40
|
-
skip.clear();
|
|
41
|
-
currentPlacementId = hit.placementId;
|
|
39
|
+
if (!writes || !normalizeWrites(writes)) {
|
|
40
|
+
skip.add(hit.placementId);
|
|
42
41
|
continue;
|
|
43
42
|
}
|
|
44
|
-
|
|
43
|
+
snapshot = await hooks.applyWrites(writes, snapshot);
|
|
44
|
+
skip.clear();
|
|
45
|
+
currentPlacementId = hit.placementId;
|
|
45
46
|
}
|
|
46
47
|
}
|
|
47
48
|
export async function advanceAgenda(items, state, compute, hooks, options = {}) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { resolveFuncName } from '../functions/func-ref.js';
|
|
2
2
|
import { isOtherwise, jsonRules } from './condition.js';
|
|
3
3
|
import { andCompiledWhen, compileAuthoringWhen, compileWhen, whenFields } from './finish-when.js';
|
|
4
|
+
import { ivrResultField } from './ivr-fields.js';
|
|
4
5
|
import { transferResultField } from './transfer-fields.js';
|
|
5
6
|
function isTopicNode(node) {
|
|
6
7
|
return node.kind === 'topic';
|
|
@@ -11,6 +12,9 @@ function isActionNode(node) {
|
|
|
11
12
|
function isTransferNode(node) {
|
|
12
13
|
return node.kind === 'transfer';
|
|
13
14
|
}
|
|
15
|
+
function isIvrNode(node) {
|
|
16
|
+
return node.kind === 'ivr';
|
|
17
|
+
}
|
|
14
18
|
function isSectionNode(node) {
|
|
15
19
|
return node.kind === 'section';
|
|
16
20
|
}
|
|
@@ -41,15 +45,8 @@ function toAgendaBlock(node) {
|
|
|
41
45
|
say: node.say,
|
|
42
46
|
};
|
|
43
47
|
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
id: node.id,
|
|
47
|
-
label: node.label,
|
|
48
|
-
numbers: node.numbers,
|
|
49
|
-
mode: node.mode,
|
|
50
|
-
say: node.say,
|
|
51
|
-
unsuccessfulSay: node.unsuccessfulSay,
|
|
52
|
-
};
|
|
48
|
+
const { applyWhen: _applyWhen, ...block } = node;
|
|
49
|
+
return block;
|
|
53
50
|
}
|
|
54
51
|
function compileBlock(block, functions, issues, path) {
|
|
55
52
|
try {
|
|
@@ -63,6 +60,11 @@ function compileBlock(block, functions, issues, path) {
|
|
|
63
60
|
const run = resolveFuncName(block.run, functions);
|
|
64
61
|
return { ...block, run, finishWhen };
|
|
65
62
|
}
|
|
63
|
+
if (block.kind === 'ivr') {
|
|
64
|
+
const finishWhen = compileWhen(block.finishWhen, functions);
|
|
65
|
+
const onDtmfResult = resolveFuncName(block.onDtmfResult, functions);
|
|
66
|
+
return { ...block, onDtmfResult, finishWhen, resultField: ivrResultField(block.id) };
|
|
67
|
+
}
|
|
66
68
|
const resultField = transferResultField(block.id);
|
|
67
69
|
const finishWhen = compileWhen(jsonRules({
|
|
68
70
|
fact: resultField,
|
|
@@ -341,7 +343,7 @@ function asSectionPlacement(node, usedIds, path, issues) {
|
|
|
341
343
|
children: node.children.map((child, i) => asPlacement(child, usedIds, `${path}/${node.id}[${i}]`, issues)),
|
|
342
344
|
};
|
|
343
345
|
}
|
|
344
|
-
if (isTopicNode(node) || isActionNode(node) || isTransferNode(node)) {
|
|
346
|
+
if (isTopicNode(node) || isActionNode(node) || isTransferNode(node) || isIvrNode(node)) {
|
|
345
347
|
return {
|
|
346
348
|
kind: 'node',
|
|
347
349
|
placementId: uniquePlacementId(node.id, usedIds),
|
|
@@ -371,7 +373,7 @@ export function extractBlocksFromTree(tree, blocks = {}) {
|
|
|
371
373
|
return blocks;
|
|
372
374
|
function visit(nodes) {
|
|
373
375
|
for (const node of nodes) {
|
|
374
|
-
if (isTopicNode(node) || isActionNode(node) || isTransferNode(node)) {
|
|
376
|
+
if (isTopicNode(node) || isActionNode(node) || isTransferNode(node) || isIvrNode(node)) {
|
|
375
377
|
blocks[node.id] = toAgendaBlock(node);
|
|
376
378
|
}
|
|
377
379
|
else if (isSectionNode(node) || node.kind === 'branch') {
|
|
@@ -1,11 +1,15 @@
|
|
|
1
|
-
export type { ActionBlock, ActionNode, AgendaBlock, AgendaConfig, AgendaHeader, AgendaTree, ApplyWhen, AuthoringNode, AuthoringWhen, BranchNode, Channel, CompiledActionBlock, CompiledAgenda, CompiledAgendaBlock, CompiledTopicBlock, CompiledTransferBlock, CompiledWhen, Condition, FirstMessage, FinishWhen, FlatActionItem, FlatAgendaItem, FlatNodePlacement, FlatTopicItem, FlatTransferItem, Interrupt, SequenceAgendas, JsonRulesCondition, NodePlacement, OnNoProgress, OtherwiseSentinel, Placement, SectionNode, SectionPlacement, SwitchOnNode, TopicBlock, TopicNode, TransferBlock, TransferNode, When, } from './types.js';
|
|
1
|
+
export type { ActionBlock, ActionNode, AgendaBlock, AgendaConfig, AgendaHeader, AgendaTree, ApplyWhen, AuthoringNode, AuthoringWhen, BranchNode, Channel, CompiledActionBlock, CompiledAgenda, CompiledAgendaBlock, CompiledTopicBlock, CompiledIvrBlock, CompiledTransferBlock, CompiledWhen, Condition, FirstMessage, FinishWhen, FlatActionItem, FlatAgendaItem, FlatIvrItem, FlatNodePlacement, FlatTopicItem, FlatTransferItem, DtmfResult, IvrBlock, IvrDigit, IvrFallbackAction, IvrNode, Interrupt, SequenceAgendas, JsonRulesCondition, NodePlacement, OnNoProgress, OtherwiseSentinel, Placement, SectionNode, SectionPlacement, SwitchOnNode, TopicBlock, TopicNode, TransferBlock, TransferInterruptAction, TransferNode, When, } from './types.js';
|
|
2
|
+
export type { TransferArguments, TransferCallerIdShown, TransferConfirmKey, TransferConfirmMethod, TransferDial, TransferFallbackAction, TransferMode, TransferSay, } from './transfer-spec.js';
|
|
3
|
+
export { E164_NUMBER, SIP_URI, interpolateFromSnapshot, interpolateTemplate, isTransferDestinationLiteral, resolveTransferArguments, resolveTransferDestinations, resolveTransferSay, transferArgumentsJson, validateTransferDestinations, } from './transfer-spec.js';
|
|
2
4
|
export { collectJsonRulesFacts, evaluateJsonRules, extractTemplateFields, isOtherwise, jsonRules, looksLikeJsonRules, normalizeJsonRules, otherwise, parseJsonRules, type ConditionEval, } from './condition.js';
|
|
3
5
|
export { andCompiledWhen, compileAuthoringWhen, compileFinishWhen, compileWhen, evaluateFinishWhen, evaluateWhen, finishWhenFields, notAnyWhen, whenFields, type FinishWhenEval, type WhenEval, } from './finish-when.js';
|
|
4
|
-
export { action, agenda, branch, section, switchOn, topic, transfer } from './builders.js';
|
|
6
|
+
export { action, agenda, branch, ivr, section, switchOn, topic, transfer } from './builders.js';
|
|
5
7
|
export { extractBlocksFromTree, flattenAgendaConfig, flattenAgendaPlacements, flattenAgendaTree, isAgendaTree, isSequenceAgendas, normalizeSequenceAgendas, treeToPlacements, type FlattenOptions, type FlattenResult, } from './flatten.js';
|
|
6
|
-
export { LAST_TRANSFER_RESULT_FIELD, collectTransferBlocks, registerTransferSessionFields, transferFieldKey, transferResultField, } from './transfer-fields.js';
|
|
8
|
+
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';
|
|
9
|
+
export { collectIvrBlocks, ivrAttempted, ivrResultField, registerIvrSessionFields, type IvrAttemptResult, } from './ivr-fields.js';
|
|
7
10
|
export { fatalAgendaIssues, validateCompiledAgenda, type ValidateAgendaOptions } from './validate.js';
|
|
8
|
-
export {
|
|
11
|
+
export { RESERVED_INTERRUPT_TOOL_NAMES, collectTransferInterruptHeaders, headerHasTransferInterrupt, CONTACT_LANGUAGE_FIELD, getContactLang, inboundIvrCollect, isSpokenLanguage, ivrOf, languageFieldOf, validateInterrupts, type SpokenLanguage, } from './interrupts.js';
|
|
12
|
+
export { unresolvedFinishWhenFields, walkAgenda, type WalkDecision, type WalkHitReason, type WalkOptions, type WalkResult, type WalkSkip, type WalkSkipReason, type WalkStats, } from './walk.js';
|
|
9
13
|
export { simulateAgenda, type SimulateResult, type SimulatedItem, type SimulatedItemStatus } from './simulate.js';
|
|
10
14
|
export { DEFAULT_MAX_ACTIONS_PER_TURN, advanceAgenda, driveAgenda, transferWrites, watchdogHint, type AdvanceResult, type StageDriveHooks, type StageDriveResult, type StageDriveState, } from './drive.js';
|
|
11
15
|
//# sourceMappingURL=index.d.ts.map
|