@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.
Files changed (55) hide show
  1. package/dist/builder/codegen/generate.js +15 -9
  2. package/dist/builder/company-import-graph.d.ts +11 -0
  3. package/dist/builder/company-import-graph.js +105 -0
  4. package/dist/builder/compile/compile-agenda.js +9 -4
  5. package/dist/builder/compile/compile-sequence.d.ts +4 -1
  6. package/dist/builder/compile/compile-sequence.js +28 -9
  7. package/dist/builder/load.js +3 -3
  8. package/dist/builder/types.d.ts +5 -0
  9. package/dist/cli/serve/index.js +107 -47
  10. package/dist/compute-host/index.d.ts +2 -0
  11. package/dist/compute-host/index.js +1 -0
  12. package/dist/compute-host/read-only-cache.d.ts +20 -0
  13. package/dist/compute-host/read-only-cache.js +61 -0
  14. package/dist/compute-host/source-compute-host.d.ts +4 -0
  15. package/dist/compute-host/source-compute-host.js +12 -2
  16. package/dist/core/context/types.d.ts +19 -1
  17. package/dist/core/functions/define/custom.d.ts +1 -1
  18. package/dist/core/functions/types.d.ts +4 -4
  19. package/dist/core/slot/index.d.ts +1 -1
  20. package/dist/core/slot/index.js +1 -1
  21. package/dist/core/slot/types.d.ts +2 -0
  22. package/dist/core/slot/write.d.ts +8 -1
  23. package/dist/core/slot/write.js +145 -38
  24. package/dist/core/stage/builders.d.ts +23 -5
  25. package/dist/core/stage/builders.js +3 -0
  26. package/dist/core/stage/drive.d.ts +1 -1
  27. package/dist/core/stage/drive.js +9 -8
  28. package/dist/core/stage/flatten.js +13 -11
  29. package/dist/core/stage/index.d.ts +8 -4
  30. package/dist/core/stage/index.js +5 -2
  31. package/dist/core/stage/interrupts.d.ts +28 -0
  32. package/dist/core/stage/interrupts.js +120 -0
  33. package/dist/core/stage/ivr-fields.d.ts +9 -0
  34. package/dist/core/stage/ivr-fields.js +75 -0
  35. package/dist/core/stage/transfer-fields.d.ts +6 -1
  36. package/dist/core/stage/transfer-fields.js +44 -3
  37. package/dist/core/stage/transfer-spec.d.ts +48 -0
  38. package/dist/core/stage/transfer-spec.js +113 -0
  39. package/dist/core/stage/types.d.ts +45 -14
  40. package/dist/core/stage/validate.js +105 -1
  41. package/dist/core/stage/walk.d.ts +5 -0
  42. package/dist/core/stage/walk.js +33 -10
  43. package/dist/fn-context/index.d.ts +1 -1
  44. package/dist/fn-context/index.js +1 -1
  45. package/dist/fn-context/rpc/index.d.ts +1 -1
  46. package/dist/fn-context/rpc/index.js +1 -1
  47. package/dist/fn-context/rpc/request-id.d.ts +5 -0
  48. package/dist/fn-context/rpc/request-id.js +11 -0
  49. package/dist/fn-context/rpc/trpc-client.d.ts +2 -0
  50. package/dist/fn-context/rpc/trpc-client.js +5 -1
  51. package/dist/fn-context/rpc/trpc-init.d.ts +1 -0
  52. package/dist/index.d.ts +7 -7
  53. package/dist/index.js +4 -4
  54. package/dist/voice-service/types.d.ts +3 -0
  55. 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
- return def.main({ ...ctx, params });
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
- export type Writes = Record<FieldPath, unknown>;
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 CustomFn<S = CaseContext> = (caseContext: S, ctx: SystemContext) => Promise<Writes | null>;
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
@@ -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
@@ -23,6 +23,8 @@ export type FieldValue = {
23
23
  export type HistoryAppend = {
24
24
  path: FieldPath;
25
25
  entry: FieldHistoryEntry;
26
+ /** Set when the write targeted a specific snapshot row. */
27
+ rowId?: string;
26
28
  };
27
29
  export type ExtractedField = {
28
30
  reason: string;
@@ -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 = {
@@ -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: `Use Slot Filling / row APIs to write multi-row field ${path}`,
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
- if (!writes || Object.keys(writes).length === 0) {
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(writes)) {
77
- const writeIssues = assertWritableNormal(schema, path, options);
78
- if (writeIssues.length > 0) {
79
- errors.push(...writeIssues);
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 parsed = parseFieldPath(path);
83
- const field = getCompiledTable(schema, parsed.table).fields[parsed.field];
84
- const valueIssues = validateBareValue(field, value, path, {
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({ path, entry: committed.historyEntry });
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({ path: parsed.path, entry: committed.historyEntry });
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: 'cold' | 'warm';
24
- say: string;
25
- unsuccessfulSay: string | null;
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?: {
@@ -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 = await walkAgenda(items, snapshot, { currentPlacementId, compute, skipPlacementIds: skip });
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 && Object.keys(writes).length > 0) {
39
- snapshot = await hooks.applyWrites(writes, snapshot);
40
- skip.clear();
41
- currentPlacementId = hit.placementId;
39
+ if (!writes || !normalizeWrites(writes)) {
40
+ skip.add(hit.placementId);
42
41
  continue;
43
42
  }
44
- skip.add(hit.placementId);
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
- return {
45
- kind: 'transfer',
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 { unresolvedFinishWhenFields, walkAgenda, type WalkDecision, type WalkHitReason, type WalkOptions, type WalkResult, type WalkSkip, type WalkSkipReason, } from './walk.js';
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