@voctiv/agent-sdk 0.2.14 → 0.2.16

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/README.md CHANGED
@@ -70,10 +70,60 @@ export default defineScript(async ({ channel, logger, context }) => {
70
70
  });
71
71
  ```
72
72
 
73
+ ## Examples
74
+
75
+ The [`examples/`](./examples/) folder contains copy-paste-ready scripts:
76
+
77
+ | Example | Description |
78
+ |---------|-------------|
79
+ | [outbound-with-recall.ts](./examples/outbound-with-recall.ts) | Outbound with `recallCount` / `recallDelay` (automatic redial on failure) |
80
+ | [recall-routing-by-attempt.ts](./examples/recall-routing-by-attempt.ts) | Online recall: branch on `context.attempt` (`getScriptPhase` → `'online'`) |
81
+ | [schedule-call-with-defaults.ts](./examples/schedule-call-with-defaults.ts) | `platform.call()` without explicit recall — CMS defaults from `context` |
82
+ | [after-call-continuation.ts](./examples/after-call-continuation.ts) | `onSuccessCall` / `onFailedCall` vs recall (mutually exclusive) |
83
+ | [read-recall-from-params.ts](./examples/read-recall-from-params.ts) | `parseRecallDelaySeconds()` / `parseRecallCount()` on legacy params |
84
+
85
+ ### Quick recall example
86
+
87
+ ```ts
88
+ import { defineScript } from '@voctiv/agent-sdk';
89
+
90
+ export default defineScript(async ({ channel, context, platform, logger }) => {
91
+ channel.sip.answer();
92
+
93
+ if ((context.attempt ?? 0) > 0) {
94
+ logger.log('Online recall attempt', { attempt: context.attempt });
95
+ await channel.audio.say('Sorry we missed you earlier. Trying again now.');
96
+ }
97
+
98
+ // Schedule first outbound with up to 3 retries, 5 min apart (no entryPoint — same defineScript runs each leg).
99
+ await platform.call(context.msisdn!, {
100
+ recallCount: context.recallCount ?? 3,
101
+ recallDelay: context.recallDelay ?? 300,
102
+ });
103
+ });
104
+ ```
105
+
106
+ Parse legacy time strings from params when needed:
107
+
108
+ ```ts
109
+ import { parseRecallDelaySeconds, parseRecallCount } from '@voctiv/agent-sdk';
110
+
111
+ const delaySec = parseRecallDelaySeconds(context.dialogParams?.recall_delay); // "00:05:00" → 300
112
+ const maxAttempts = parseRecallCount(context.dialogParams?.recall_count);
113
+ ```
114
+
73
115
  ## What The SDK Contains
74
116
 
75
117
  `defineScript(fn)` marks the default export as the script entry point. It returns the same function and exists to give TypeScript the correct `ScriptContext` shape.
76
118
 
119
+ **Three different “context” names:**
120
+
121
+ | Name | Meaning |
122
+ | --- | --- |
123
+ | `ScriptContext` | Top-level injection: `{ channel, logger, context, platform }` |
124
+ | `context` (`ScriptDialogContext`) | Dialog identity, params, routing snapshot, `env$`, headless — see [Dialog Context](#dialog-context-and-persisted-env) |
125
+ | `options.context` on `platform.nlu.extract` | Opaque NLU disambiguation string/JSON — **not** dialog context |
126
+
77
127
  `ScriptContext` is the top-level object passed to a script:
78
128
 
79
129
  - `channel` is the media channel for SIP, WS, ASR, TTS, audio playback, LLM, and structured data messages.
@@ -178,19 +228,43 @@ If the call terminates before media becomes available, deferred audio resolves a
178
228
  On **SIP channels**, `channel.sip` exposes raw signalling beyond call state — useful for
179
229
  carrier routing, diversion chains, and vendor SDP attributes.
180
230
 
231
+ #### When to use what
232
+
233
+ | Need | API | When |
234
+ | --- | --- | --- |
235
+ | Routing / identity / locale from the **inbound INVITE** (`Diversion`, `X-Trunk-Id`, `X-Neuro-UUID`, `X-language`, …) | `channel.sip.inviteSipHeaders` | Call **start** only (snapshot) |
236
+ | Peer signal **during** the call (e.g. mid-call language in INFO body) | `channel.sip.sipInfo$` | After subscribe; each INFO |
237
+ | DTMF digits | `channel.sip.dtmf$` | Prefer over parsing INFO |
238
+ | SDP / codec / connection | `remoteSdp` / `getRemoteSdpDetails()` | May change (183 / 200 / re-INVITE) |
239
+ | SIP response code / phrase (180, 486, …) | `sipSignal$` | Low-level; **no** response headers |
240
+ | Live SIP headers on 200 / re-INVITE / BYE | — | **Not exposed** |
241
+
242
+ Decision guide:
243
+
244
+ - Value from the inbound INVITE at call start → `inviteSipHeaders`
245
+ - Mid-call signal from peer INFO → `sipInfo$` (`contentType` + `body` only)
246
+ - DTMF → `dtmf$`
247
+ - Media description → `remoteSdp` / `getRemoteSdpDetails()`
248
+ - Live SIP response headers → not available (`sipSignal$` has status/SDP only)
249
+
250
+ **Locale pattern:** read start language from `inviteSipHeaders` (e.g. `X-language`). Mid-call changes only work if the peer puts the language in the INFO **body** — INFO SIP headers are not forwarded. Monolingual agents can ignore both and use `context.language` / config.
251
+
252
+ Outbound INVITE headers you **send** go through `platform.call({ protoAdditional })` or `makeCall` options — not `inviteSipHeaders` (read-only inbound snapshot). SIP metadata does not auto-update `context` or `platform.dialog`.
253
+
181
254
  #### INVITE headers (`inviteSipHeaders`)
182
255
 
183
256
  Snapshot of SIP headers from an **inbound INVITE** at call setup. Includes standard and
184
- extension headers (`Diversion`, `P-Asserted-Identity`, `X-Trunk-Id`, `X-Neuro-UUID`, …).
257
+ extension headers (`Diversion`, `P-Asserted-Identity`, `X-Trunk-Id`, `X-Neuro-UUID`, `X-language`, …).
185
258
 
186
259
  | Property | Updates during call? |
187
260
  | --- | --- |
188
- | `inviteSipHeaders` | **No** — INVITE snapshot only; outbound B-legs usually `undefined` |
261
+ | `inviteSipHeaders` | **No** — INVITE snapshot only; outbound B-legs / WS / headless usually `undefined` |
189
262
 
190
263
  ```ts
191
264
  const h = channel.sip.inviteSipHeaders;
192
265
  const diversion = h?.Diversion; // string | string[] when multiple hops
193
266
  const trunkId = h?.['X-Trunk-Id'];
267
+ const lang = h?.['X-language'];
194
268
  ```
195
269
 
196
270
  Header names match what the host stack exposes (case-sensitive). Duplicate headers become
@@ -217,7 +291,9 @@ attributes. It is not a full SDP validator — use `remoteSdp` when you need the
217
291
 
218
292
  #### SIP INFO (`sipInfo$`)
219
293
 
220
- Live stream of incoming SIP INFO messages (`contentType` + `body`). Prefer `dtmf$` for DTMF.
294
+ Live stream of incoming SIP INFO messages. Each event is `{ contentType, body }` only
295
+ **INFO request headers are not exposed**. Prefer `dtmf$` for DTMF. Does not update
296
+ `inviteSipHeaders`.
221
297
 
222
298
  ```ts
223
299
  channel.sip.sipInfo$.subscribe(({ contentType, body }) => {
@@ -251,11 +327,14 @@ the per-event `sdp` on `sipSignal$`.
251
327
  - `bridge(other)` to cross-connect two SIP channels.
252
328
 
253
329
  `makeCall()` and `bridge()` are supported on SIP channels. The returned B-leg is a full
254
- `MediaChannel` with the same API as the main channel. WS and headless channels do not create real SIP legs.
330
+ `MediaChannel` with the same API as the main channel. WS and headless channels cannot create real SIP
331
+ legs, so both methods **throw** there rather than returning an inert leg you could talk into
332
+ unnoticed. Guard them with `context.headless` or `channel.type` when a script runs in both modes.
255
333
 
256
334
  ### `channel.sip` Reference
257
335
 
258
- SIP-only unless noted. WS/headless: most methods are no-ops; `state` behaves as synthetic `active`.
336
+ SIP-only unless noted. WS/headless: most methods are no-ops and `state` behaves as synthetic
337
+ `active`, except `makeCall()` and `bridge()`, which throw.
259
338
 
260
339
  | Member | Description |
261
340
  | --- | --- |
@@ -266,10 +345,10 @@ SIP-only unless noted. WS/headless: most methods are no-ops; `state` behaves as
266
345
  | `early$` | Emits once when RTP is up before final answer |
267
346
  | `answered$` | Emits once on 200 OK |
268
347
  | `dtmf$` | Remote DTMF digits (`DtmfEvent`: `digit`, `duration`) |
269
- | `sipInfo$` | Live incoming SIP INFO (`SipInfo`: `contentType`, `body`) |
270
- | `sipSignal$` | Low-level SIP stack events (`SipSignal`; `sdp` only on that callback) |
348
+ | `sipInfo$` | Mid-call SIP INFO (`contentType` + `body` only; INFO headers not exposed) |
349
+ | `sipSignal$` | Low-level stack events (`statusCode` / `statusPhrase` / optional `sdp`; no headers) |
271
350
  | `remoteSdp` | Latest negotiated remote SDP body; updates when new SDP arrives |
272
- | `inviteSipHeaders` | Inbound INVITE header snapshot (`SipInviteHeaders`); does not update |
351
+ | `inviteSipHeaders` | Start-of-call inbound INVITE header snapshot; does not update |
273
352
  | `getRemoteSdpDetails()` | Parse `remoteSdp` → `ParsedSdpDetails` (session + `a=` attributes) |
274
353
  | `sendProgress()` | Inbound: send 183 Session Progress → `early` |
275
354
  | `waitForEarly()` | Await `early` or `active` (Promise) |
@@ -285,8 +364,8 @@ SIP-only unless noted. WS/headless: most methods are no-ops; `state` behaves as
285
364
 
286
365
  Prefer `dtmf$` over `sipInfo$` for DTMF. Prefer `state$` / `early$` / `answered$` over raw
287
366
  `sipSignal$` for call lifecycle. Use `remoteSdp` / `getRemoteSdpDetails()` for negotiated
288
- media description; use `inviteSipHeaders` for routing headers from the inbound INVITE.
289
- See **SIP Signalling Metadata** above.
367
+ media description; use `inviteSipHeaders` for start-of-call INVITE headers and `sipInfo$`
368
+ for mid-call INFO body. See **When to use what** under SIP Signalling Metadata above.
290
369
 
291
370
  ### SIP Bridge
292
371
 
@@ -550,7 +629,7 @@ When phrase persistence is enabled, `preload()` can store decoded audio for late
550
629
 
551
630
  `ttsStrategy` controls how text is chunked:
552
631
 
553
- - `sentence`: split on sentence boundaries and synthesize each sentence. This is the default.
632
+ - `sentence`: split on sentence boundaries and synthesize each sentence. This is the default. Only one sentence is synthesized at a time; when synthesis of N finishes, N+1 starts immediately while N continues playing from the mixer queue. Sentence N is fired as soon as its text is complete — it never waits for sentence N+1. For ElevenLabs HTTP TTS, each request gets `previous_text` / `next_text` when those neighbor texts are already available (e.g. full string already split); on an LLM stream typically only `previous_text` is known at fire time. Scripts do not need to set these fields. `eleven_v3` does not support those fields (API 400), so they are omitted for that model. The TTS cache key is still the sentence text only (neighbors are not part of the key), so cache hits may replay audio synthesized under a different neighbor context.
554
633
  - `streaming`: send chunks incrementally for streaming-capable vendors.
555
634
  - `full`: accumulate the whole input and synthesize it as one segment after the input completes.
556
635
 
@@ -889,25 +968,82 @@ return {
889
968
  };
890
969
  ```
891
970
 
892
- - **`output`** — stored in dialog stats / host persistence.
971
+ - **`output`** — stored in dialog stats / host persistence. This is **not** the LE `dialog.result` lifecycle column — that is `platform.dialog.result` (see [Dialog State](#dialog-state)).
893
972
  - **`error`** — optional; usually auto-populated on crash, but scripts may set it explicitly.
894
973
 
895
974
  **Do not** return `env` from the script. Persist state via `context.env$`; the runtime snapshots it
896
975
  after completion into **`PersistedScriptResult.env`**.
897
976
 
898
- Use **`getScriptPhase(context)`** to branch on lifecycle (`online`, `messaging`, `recall`, etc.):
977
+ ## Script Lifecycle (`getScriptPhase`)
978
+
979
+ The host always runs your single **`defineScript` export** — there is no separate runtime entry per
980
+ name (unlike logic-executor Python `run_unit(entry_point=...)`). Use **`getScriptPhase(context)`**
981
+ to tell *why* the script is running now: live call, pre-call queue, post-call continuation,
982
+ messaging, etc.
983
+
984
+ **`context.headless === true` only means “no live SIP/media”.** It does **not** tell you whether
985
+ the run is before or after a call. For that, use `getScriptPhase`.
986
+
987
+ The return value is the `ScriptPhase` union — one of the phases in the table below.
988
+
989
+ | Phase | `context.headless` | When | Typical `context.entryPoint` |
990
+ |-------|-------------------|------|--------------------------------|
991
+ | `before_call` | `true` | Dialog queue / bulk outbound **before** the first platform call (often schedules `platform.call`) | empty, `main`, `default` |
992
+ | `online` | `false` | Live SIP session (inbound, outbound, **and automatic recall redials**) | any (ignored for phase) |
993
+ | `after_call_success` | `true` | Headless run **after** a successful call | `on_success_call`, `after_call_success`, `on_done_call` |
994
+ | `after_call_failed` | `true` | Headless run **after** failed attempts (when `onFailedCall` was configured) | `on_failed_call`, `after_call_failed` |
995
+ | `messaging` | `true` | Inbound message triggered the run | `on_message_api_received`, or `context.inboundMessage` set |
996
+ | `recall` | `true` | Headless recall leg (legacy `entry_point`) | `on_recall`, `recall` |
997
+ | `headless_other` | `true` | Any other headless run with a custom `entry_point` | your custom name |
998
+
999
+ **Automatic recall** (`recallCount` + `recallDelay`) creates new **online** SIP legs with an
1000
+ incremented `context.attempt`. Branch with `(context.attempt ?? 0) > 0`, **not** `phase === 'recall'`.
1001
+ See [examples/recall-routing-by-attempt.ts](./examples/recall-routing-by-attempt.ts).
1002
+
1003
+ **After-call continuation** uses `onSuccessCall` / `onFailedCall` on `platform.call()`. When the call
1004
+ ends, the host sets `dialog.params.entry_point` to that handler name and runs the same `defineScript`
1005
+ headlessly. See [examples/after-call-continuation.ts](./examples/after-call-continuation.ts).
1006
+
1007
+ These two failure models are **mutually exclusive** on one scheduled outbound — see
1008
+ [Failed outbound: automatic recall vs after-call continuation](#failed-outbound-automatic-recall-vs-after-call-continuation).
899
1009
 
900
1010
  ```ts
901
1011
  import { defineScript, getScriptPhase } from '@voctiv/agent-sdk';
902
1012
 
903
- export default defineScript(async ({ context }) => {
904
- switch (getScriptPhase(context)) {
905
- case 'online':
906
- // live call
907
- break;
1013
+ export default defineScript(async ({ channel, context, logger, platform }) => {
1014
+ const phase = getScriptPhase(context);
1015
+
1016
+ switch (phase) {
1017
+ case 'before_call':
1018
+ // Headless pre-call: queue worker, usually schedules outbound.
1019
+ await platform.call(context.msisdn!, { recallCount: 3, recallDelay: 300 });
1020
+ return { output: { phase } };
1021
+
1022
+ case 'after_call_success':
1023
+ logger.log('Post-call success branch', { entryPoint: context.entryPoint });
1024
+ return { output: { phase } };
1025
+
1026
+ case 'after_call_failed':
1027
+ logger.log('Post-call failure branch', { entryPoint: context.entryPoint });
1028
+ platform.dialog.result = 'done';
1029
+ return { output: { phase } };
1030
+
908
1031
  case 'messaging':
909
- // inbound message handling
910
- break;
1032
+ logger.log('Inbound message', { text: context.inboundMessage?.payload });
1033
+ return { output: { phase } };
1034
+
1035
+ case 'online':
1036
+ channel.sip.answer();
1037
+ if ((context.attempt ?? 0) > 0) {
1038
+ logger.log('Online recall leg', { attempt: context.attempt });
1039
+ }
1040
+ await channel.audio.say('Hello.');
1041
+ return;
1042
+
1043
+ default:
1044
+ // headless_other, recall (headless), etc.
1045
+ logger.log('Other headless run', { phase, entryPoint: context.entryPoint });
1046
+ return { output: { phase } };
911
1047
  }
912
1048
  });
913
1049
  ```
@@ -916,7 +1052,7 @@ export default defineScript(async ({ context }) => {
916
1052
 
917
1053
  `platform` exposes platform operations.
918
1054
 
919
- `platform.nlu.extract(utterance, options?)` runs intent/entity extraction. If `options.context` is omitted, current dialog params are serialized and used as NLU context.
1055
+ `platform.nlu.extract(utterance, options?)` runs intent/entity extraction. If `options.context` is omitted or `null`, the runtime sends **no** NLU context — there is no auto-fill from dialog params or `flag`. Pass it explicitly when needed (logic-executor scripts usually pass `context.flag`). Default NLU language comes from the LE agent (`_nlu.language`), not `context.lang`.
920
1056
 
921
1057
  `platform.nlu.extract$()` is an Observable wrapper — one `extract()` call per subscription, not a streaming NLU session.
922
1058
 
@@ -924,20 +1060,64 @@ export default defineScript(async ({ context }) => {
924
1060
  const result = await platform.nlu.extract('I want to reschedule', {
925
1061
  intents: ['reschedule', 'cancel'],
926
1062
  entities: ['date', 'time'],
1063
+ context: context.flag,
927
1064
  use_synonyms: true,
928
1065
  });
929
1066
  ```
930
1067
 
931
- Platform APIs (`platform.nlu`, `platform.call`, dialog writes, messaging, phrase records) are available when the host enables platform integration.
1068
+ Platform APIs (`platform.nlu`, `platform.call`, dialog writes, messaging, phrase records) are available when the host enables platform integration (`context.legacyV3Compat`).
932
1069
 
933
1070
  ### Dialog State
934
1071
 
1072
+ `platform.dialog` reads and writes the LE **dialog row** — not SIP media and not the script return value.
1073
+
1074
+ #### What `platform.dialog.result` is
1075
+
1076
+ `platform.dialog.result` maps to the PostgreSQL column **`dialog.result`**: the **lifecycle status** of the dialog entity in the CMS / offline queue (in queue, in progress, closed). It is **not** the outcome of a SIP leg.
1077
+
1078
+ | Value | Meaning |
1079
+ | --- | --- |
1080
+ | `null` | Often after-call continuation: dialog re-enters queue-api, then becomes `queued` |
1081
+ | `queued` | In the offline queue, not yet claimed |
1082
+ | `pending` | In progress (live SIP/WS or claimed queue row) |
1083
+ | `done` | Dialog closed successfully (terminal for the pipeline) |
1084
+ | `error` | Dialog closed with a logic/runtime error |
1085
+
1086
+ The **host** also moves these statuses (live session → `pending`; shutdown without continuation → often `done` / `error`; automatic recall → `pending`; after-call chain → `null` + `entry_point` in params). Scripts set `platform.dialog.result` when they want to **explicitly** fix the LE dialog status (commonly `'done'` in a headless after-call handler). That does **not** replace `channel.sip.hangup()`.
1087
+
1088
+ #### Do not confuse
1089
+
1090
+ | API | Layer | Does | Does not |
1091
+ | --- | --- | --- | --- |
1092
+ | `platform.dialog.result` | LE `dialog` row | Lifecycle status (`done`, `pending`, …) | Hang up SIP; equal `call.result`; equal `ScriptResult.output` |
1093
+ | `platform.dialog.entryPoint` | LE `dialog.params` | Persist routing hint for later headless/queue runs | Change SIP state; select another script export |
1094
+ | `context.entryPoint` | `ScriptDialogContext` | **Snapshot** of `entry_point` at script start (`getScriptPhase`) | Persist if you assign it — write via `platform.dialog.entryPoint` |
1095
+ | `return { output }` / `ScriptResult` | Script return | dialog_stats / host dump | LE `dialog.result` column |
1096
+ | `channel.sip.hangup()` / `terminated$` | Media leg | End or observe SIP/WS media | Set `platform.dialog.result` by itself |
1097
+ | `call.result` (LE call row) | Per-call | SIP terminal code/phrase for CMS logs | Same as `dialog.result` |
1098
+
1099
+ There is no `context.result` field — read/write dialog lifecycle only through `platform.dialog.result`.
1100
+
1101
+ #### `entryPoint` write vs read
1102
+
1103
+ ```ts
1104
+ // Persist routing for the next offline run (dialog.params.entry_point)
1105
+ platform.dialog.entryPoint = 'on_recall';
1106
+
1107
+ // Snapshot for this run — use with getScriptPhase(context)
1108
+ logger.log('branch', { entryPoint: context.entryPoint });
1109
+ ```
1110
+
1111
+ `ScheduleCallOptions.entryPoint` on `platform.call()` is stored on the **call** row at schedule time — different from assigning `platform.dialog.entryPoint` mid-script.
1112
+
1113
+ #### Example
1114
+
935
1115
  ```ts
936
1116
  platform.dialog.entryPoint = 'on_recall';
937
1117
  platform.dialog.result = 'done';
938
1118
  ```
939
1119
 
940
- Setters update the local value immediately and persist to the platform asynchronously. They are not awaitable and should not be used as transactional writes.
1120
+ Setters update the local value immediately and persist asynchronously. They are not awaitable and should not be used as transactional writes. See [examples/after-call-continuation.ts](./examples/after-call-continuation.ts).
941
1121
 
942
1122
  ### Platform-Scheduled Calls
943
1123
 
@@ -956,23 +1136,10 @@ By default, the platform schedules the call for immediate processing. Use `date`
956
1136
  ```ts
957
1137
  await platform.call('+12025551234', {
958
1138
  date: new Date(Date.now() + 15 * 60_000),
959
- entryPoint: 'on_callback',
960
1139
  });
961
1140
  ```
962
1141
 
963
- `entryPoint` is passed to the script when the scheduled call starts. Use it to route the callback into a specific branch:
964
-
965
- ```ts
966
- export default defineScript(async ({ context, channel }) => {
967
- if (context.entryPoint === 'on_callback') {
968
- channel.sip.answer();
969
- await channel.audio.say('Hello, this is your scheduled callback.');
970
- return;
971
- }
972
-
973
- await channel.audio.say('I will call you back in fifteen minutes.');
974
- });
975
- ```
1142
+ When the scheduled call connects, the host runs your **`defineScript` export** again. Branch inside that handler if needed (there is no separate runtime entry per name, unlike logic-executor Python `run_unit(entry_point=...)`).
976
1143
 
977
1144
  Use `dateEnd` to define the latest time when the call is still useful. If the platform cannot place the call before that deadline, it can skip the attempt.
978
1145
 
@@ -980,33 +1147,90 @@ Use `dateEnd` to define the latest time when the call is still useful. If the pl
980
1147
  await platform.call('+12025551234', {
981
1148
  date: new Date('2026-04-29T10:00:00Z'),
982
1149
  dateEnd: new Date('2026-04-29T10:30:00Z'),
983
- entryPoint: 'on_reminder',
984
1150
  });
985
1151
  ```
986
1152
 
987
- Retries are controlled with `recallCount` and `recallDelay`:
1153
+ Retries are controlled with `recallCount` and `recallDelay` (delay is always **seconds** in SDK options):
988
1154
 
989
1155
  ```ts
990
1156
  await platform.call('+12025551234', {
991
- entryPoint: 'on_follow_up',
992
1157
  recallCount: 3,
993
- recallDelay: 300,
1158
+ recallDelay: 300, // 5 minutes between failed outbound attempts
994
1159
  });
995
1160
  ```
996
1161
 
997
- This means the platform may retry up to three times, waiting about five minutes between attempts.
1162
+ This schedules automatic redials: on each **failed outbound** the host creates a new platform call
1163
+ with `date_added = now + recallDelay`. The dialer runs the script again with an incremented
1164
+ `context.attempt`. See [examples/outbound-with-recall.ts](./examples/outbound-with-recall.ts).
1165
+
1166
+ ### Failed outbound: automatic recall vs after-call continuation
1167
+
1168
+ After a failed **outbound** call the platform must choose **one** failure-handling strategy.
1169
+ `recallCount` + `recallDelay` and `onFailedCall` answer the same question in different ways, so
1170
+ they are **mutually exclusive** on `platform.call()` (logic-executor `nn.call` parity).
1171
+
1172
+ | Strategy | `platform.call()` options | What happens on failure | Next script run |
1173
+ |----------|---------------------------|-------------------------|-----------------|
1174
+ | **Automatic recall** | `recallCount` + `recallDelay` (both required on the `call` row) | Host schedules another outbound `call` after `recallDelay`; bumps `dialog.params.attempt` | **Online** SIP leg — same `defineScript`, branch on `context.attempt` |
1175
+ | **After-call continuation** | `onFailedCall` (and optionally `onSuccessCall`) | Host sets `dialog.params.entry_point` to the handler name and re-queues the dialog | **Headless** run — `getScriptPhase(context)` → `after_call_failed` |
1176
+
1177
+ **Why not both?** Recall is fully platform-driven (dialer redials without running your script between
1178
+ attempts). `onFailedCall` is script-driven (your handler decides logging, CRM, manual retry, etc.).
1179
+ If both were written to `call.params`, shutdown would be ambiguous. This host therefore **keeps
1180
+ `onFailedCall` and drops recall** at schedule time so the outbound still starts and the after-call
1181
+ branch runs on failure. (If both somehow land on an existing `call.params` row, recall still wins
1182
+ at shutdown — avoid writing both.)
998
1183
 
999
- When you omit these options, the host fills defaults from `context.recallCount` / `context.recallDelay`
1000
- (effective values for the current dialog). Those in turn fall back to CMS agent contact-rules
1184
+ **Do not** pass `onFailedCall` together with `recallCount` and `recallDelay` in the same
1185
+ `platform.call()` invocation. If both are present (script options, dialog params, or CMS
1186
+ defaults), the host **keeps `onFailedCall` and drops recall** so the call still schedules —
1187
+ after-call continuation wins over automatic redial. Prefer configuring only one strategy
1188
+ explicitly.
1189
+
1190
+ **Host default resolution:** when you omit recall options, the host may default them from
1191
+ `context.recallCount` / `context.recallDelay` (CMS contact-rules). That auto-fill applies only when
1192
+ the script did **not** pass explicit `onFailedCall` in `platform.call()` options.
1193
+ `onFailedCall` / `onSuccessCall` are **never** invented from `dialog.params` or Omni
1194
+ `scheduleOutbound` defaults — they are script kwargs only (LE `nn.call` parity). Stale
1195
+ `on_failed_call` left in dialog params must not block CMS recall.
1196
+
1197
+ Diagram-style legacy outbound (Megafon converter) typically uses `onFailedCall` plus
1198
+ `on_failed_call_system` (sleep + call the main block again) — **not** CMS automatic recall.
1199
+ See [examples/after-call-continuation.ts](./examples/after-call-continuation.ts).
1200
+
1201
+ Use either automatic recall **or** offline `onFailedCall` continuation — not both on one scheduled
1202
+ outbound leg.
1203
+
1204
+ Branch inside the script on recall attempts (`context.attempt`, not a separate entry function):
1205
+
1206
+ ```ts
1207
+ import { defineScript } from '@voctiv/agent-sdk';
1208
+
1209
+ export default defineScript(async ({ channel, context, logger }) => {
1210
+ channel.sip.answer();
1211
+
1212
+ if ((context.attempt ?? 0) > 0) {
1213
+ logger.log('Recall leg', { attempt: context.attempt });
1214
+ await channel.audio.say('Follow-up call. Please hold.');
1215
+ }
1216
+ });
1217
+ ```
1218
+
1219
+ See [examples/recall-routing-by-attempt.ts](./examples/recall-routing-by-attempt.ts).
1220
+
1221
+ When you omit recall options, the host fills them from `context.recallCount` / `context.recallDelay`
1222
+ (effective values for the current dialog) **only when `onFailedCall` is not configured** for that
1223
+ scheduled call. Those values fall back to CMS agent contact-rules
1001
1224
  (`context.agent?.recallCount` / `context.agent?.recallDelay`, legacy `nn.get_recall_count()` /
1002
- `nn.get_recall_delay()`).
1225
+ `nn.get_recall_delay()`). See [Failed outbound: automatic recall vs after-call continuation](#failed-outbound-automatic-recall-vs-after-call-continuation).
1003
1226
 
1004
1227
  Other scheduling options:
1005
1228
 
1006
1229
  - `priority`: higher-priority calls can be processed earlier by the dialer.
1007
1230
  - `timezone`: timezone offset used by the platform when interpreting scheduled dates.
1008
- - `onSuccessCall`: entry point to use after a successful call.
1009
- - `onFailedCall`: entry point to use after failed attempts.
1231
+ - `onSuccessCall`: headless handler name after a successful call (e.g. `'on_success_call'` → `getScriptPhase` `'after_call_success'`).
1232
+ - `onFailedCall`: headless handler name after failed attempts (mutually exclusive with recall).
1233
+ - `entryPoint` (optional): stored as `entry_point` in call params for LE DB compatibility. The host still runs the same `defineScript` export; use `context.entryPoint` only if **you** branch on it inside the handler.
1010
1234
  - `protoAdditional`: extra protocol-level parameters, such as SIP headers expected by your telephony setup.
1011
1235
 
1012
1236
  ### Messaging
@@ -1025,9 +1249,14 @@ await platform.messaging.send({
1025
1249
 
1026
1250
  Offline, or headless, sessions run a script without a live SIP call, WebSocket audio stream, RTP pipeline, ASR, or TTS playback. They are used for platform-driven background logic, queued dialog processing, and messaging events.
1027
1251
 
1028
- The script entry point is still the same `defineScript()` handler. Detect this mode with `context.headless`:
1252
+ The script entry point is still the same `defineScript()` handler. Detect offline mode with
1253
+ `context.headless`, then use **`getScriptPhase(context)`** to distinguish pre-call queue runs
1254
+ (`before_call`) from post-call continuations (`after_call_success` / `after_call_failed`). See
1255
+ [Script Lifecycle (`getScriptPhase`)](#script-lifecycle-getscriptphase).
1029
1256
 
1030
1257
  ```ts
1258
+ import { defineScript, getScriptPhase } from '@voctiv/agent-sdk';
1259
+
1031
1260
  export default defineScript(async ({ channel, context, logger, platform }) => {
1032
1261
  if (!context.headless) {
1033
1262
  channel.sip.answer();
@@ -1035,12 +1264,24 @@ export default defineScript(async ({ channel, context, logger, platform }) => {
1035
1264
  return;
1036
1265
  }
1037
1266
 
1267
+ const phase = getScriptPhase(context);
1038
1268
  logger.log('Running offline logic', {
1269
+ phase,
1039
1270
  dialogUuid: context.dialogUuid,
1040
1271
  entryPoint: context.entryPoint,
1041
1272
  });
1042
1273
 
1043
- // Offline logic usually works with text, params, env, NLU, LLM, and platform APIs.
1274
+ if (phase === 'before_call') {
1275
+ await platform.call(context.msisdn!);
1276
+ return;
1277
+ }
1278
+
1279
+ if (phase === 'after_call_success' || phase === 'after_call_failed') {
1280
+ // Post-call headless branch — see examples/after-call-continuation.ts
1281
+ return { output: { phase } };
1282
+ }
1283
+
1284
+ // messaging, headless_other, etc.
1044
1285
  });
1045
1286
  ```
1046
1287
 
@@ -1143,9 +1384,7 @@ export default defineScript(async ({ channel, context, platform }) => {
1143
1384
  const text = String(context.inboundMessage?.payload?.text ?? '');
1144
1385
 
1145
1386
  if (text.includes('call me')) {
1146
- await platform.call(context.msisdn, {
1147
- entryPoint: 'on_callback',
1148
- });
1387
+ await platform.call(context.msisdn);
1149
1388
  }
1150
1389
 
1151
1390
  return { output: { handledOffline: true } };
@@ -1182,19 +1421,25 @@ Recall behavior uses **two layers** on `context`:
1182
1421
  | Layer | Fields | Source | Use when |
1183
1422
  |-------|--------|--------|----------|
1184
1423
  | Agent defaults | `context.agent?.recallCount`, `context.agent?.recallDelay` | CMS contact-rules (`agent.recall_count`, `agent.delay` → seconds) | Compare with CMS settings; legacy `nn.get_recall_count()` / `get_recall_delay()` parity |
1185
- | Effective for this run | `context.recallCount`, `context.recallDelay` | `dialog.params` / `call.params`, then agent defaults | Schedule outbound calls, recall routing logic |
1424
+ | Effective for this run | `context.recallCount`, `context.recallDelay` | `dialog.params` / `call.params`, then agent defaults | Read in script; pass explicitly to `platform.call()` when using **automatic recall** |
1186
1425
 
1187
1426
  Precedence for effective values: **dialog/call params** (`recall_count`, `recall_delay`) **>** agent CMS defaults.
1188
1427
 
1428
+ **Important:** CMS/effective recall on `context` does **not** mean every `platform.call()` gets
1429
+ automatic recall. The host copies recall onto the scheduled `call` row only when you omit recall
1430
+ options **and** `onFailedCall` is not configured (see
1431
+ [Failed outbound: automatic recall vs after-call continuation](#failed-outbound-automatic-recall-vs-after-call-continuation)).
1432
+ Agents with both CMS contact-rules and diagram `on_failed_call` handlers use the after-call path;
1433
+ recall fields on `context` are informational unless your script passes them explicitly.
1434
+
1189
1435
  `context.attempt` is the current recall attempt counter from `dialog.params.attempt` (starts at 0).
1190
- `context.entryPoint` is the routing branch for this run (e.g. after a failed call).
1436
+ `context.entryPoint` is the routing branch **snapshot** for this run (from `dialog.params.entry_point` at start). To persist a new value, assign `platform.dialog.entryPoint` — see [Dialog State](#dialog-state).
1191
1437
 
1192
1438
  ```ts
1193
1439
  // Use effective values when scheduling the next outbound leg
1194
- await context.platform?.call?.(msisdn, {
1440
+ await platform.call(context.msisdn!, {
1195
1441
  recallCount: context.recallCount,
1196
1442
  recallDelay: context.recallDelay,
1197
- entryPoint: 'on_follow_up',
1198
1443
  });
1199
1444
 
1200
1445
  // Log CMS defaults vs per-dialog override
@@ -1235,14 +1480,16 @@ Important fields:
1235
1480
  - `context.flag`: business flag.
1236
1481
  - `context.initialData`: shallow snapshot of params at script start.
1237
1482
  - `context.dialogParams`: live param map for the run.
1238
- - `context.entryPoint`: current routing entry point.
1239
- - `context.attempt`: current recall attempt number (`dialog.params.attempt`).
1483
+ - `context.entryPoint`: routing entry point **snapshot** at script start (see [Script Lifecycle](#script-lifecycle-getscriptphase)); persist changes via `platform.dialog.entryPoint`.
1484
+ - `context.attempt`: current recall attempt number (`dialog.params.attempt`; use with `phase === 'online'`).
1240
1485
  - `context.recallCount` / `context.recallDelay`: effective recall settings for this dialog/call.
1241
1486
  - `context.agent?.recallCount` / `context.agent?.recallDelay`: CMS agent defaults (immutable snapshot).
1242
1487
  - `context.headless`: true for offline/queue/messaging sessions without a real media channel.
1243
1488
  - `context.runTime`: async execution budget helper.
1244
1489
  - `context.env$`: persisted dialog environment as an RxJS `BehaviorSubject`.
1245
1490
 
1491
+ Dialog lifecycle status (`dialog.result`) is **not** on `context` — use `platform.dialog.result` ([Dialog State](#dialog-state)).
1492
+
1246
1493
  Use `env$` for persisted script state:
1247
1494
 
1248
1495
  ```ts
@@ -1278,11 +1525,15 @@ WS channels behave like active media channels:
1278
1525
  Headless channels are for offline, queue, or messaging sessions:
1279
1526
 
1280
1527
  - Audio methods are no-ops that log warnings.
1281
- - SIP methods are mostly no-ops.
1282
- - `createAsr()` returns an inert handle.
1528
+ - SIP methods are no-ops, except `makeCall()` and `bridge()`, which throw: there is no real leg to
1529
+ create, and a silent no-op would hide the mistake.
1530
+ - `createAsr()` returns an inert handle whose observables complete immediately.
1283
1531
  - LLM, NLU, messaging, platform calls, dialog state, and `env$` still work.
1284
1532
 
1285
- Use `context.headless` to branch when a script must behave differently without a real media channel. See [Offline / Headless Logic](#offline--headless-logic) for details and examples.
1533
+ Use `context.headless` plus `getScriptPhase(context)` when a script must behave differently without a
1534
+ real media channel or across pre-call / post-call headless runs. See
1535
+ [Offline / Headless Logic](#offline--headless-logic) and
1536
+ [Script Lifecycle (`getScriptPhase`)](#script-lifecycle-getscriptphase).
1286
1537
 
1287
1538
  ## Text Input For Tests
1288
1539
 
package/dist/index.d.ts CHANGED
@@ -33,6 +33,7 @@ export type { ScriptDialogContext, ScriptPhase, ScriptRunTime, ScriptResult, Per
33
33
  export { getScriptPhase } from './types/script-context';
34
34
  export type { NluScriptApi, PlatformApi, DialogApi, ScheduleCallOptions, SendMessageOptions, InboundMessage, MessagingApi, } from './types/platform';
35
35
  export type { ScriptLogger } from './types/logger';
36
+ export { TranscriptionRole } from './types/logger';
36
37
  export type { MediaChannel, ChannelAudio, ChannelEvents, } from './types/media-channel';
37
38
  export type { ChannelLlm, LlmOptions, LlmStreamChunk, ExtractOptions, PersistentLlmStreamHandle, } from './types/llm';
38
39
  export type { ChannelSip, SipState, SipProgressEvent, SipInviteHeaders, ParsedSdpDetails, } from './types/sip';
@@ -44,4 +45,5 @@ export { LEGACY_PHRASE_RECORD_BRAND, isLegacyPhraseRecord, } from './types/legac
44
45
  export type { TextInput } from './types/text-input';
45
46
  export type { DtmfEvent, SipInfo, SipSignal, DataMessage, } from './types/events';
46
47
  export type { NluExtractOptions, NluInferResult } from './types/nlu';
48
+ export { parseRecallCount, parseRecallDelaySeconds } from './recall-utils';
47
49
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,YAAY,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAE/D,YAAY,EACV,mBAAmB,EACnB,WAAW,EACX,aAAa,EACb,YAAY,EACZ,qBAAqB,EACrB,WAAW,EACX,YAAY,EACZ,kBAAkB,EAClB,iBAAiB,GAClB,MAAM,wBAAwB,CAAC;AAEhC,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAExD,YAAY,EACV,YAAY,EACZ,WAAW,EACX,SAAS,EACT,mBAAmB,EACnB,kBAAkB,EAClB,cAAc,EACd,YAAY,GACb,MAAM,kBAAkB,CAAC;AAE1B,YAAY,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAEnD,YAAY,EACV,YAAY,EACZ,YAAY,EACZ,aAAa,GACd,MAAM,uBAAuB,CAAC;AAE/B,YAAY,EACV,UAAU,EACV,UAAU,EACV,cAAc,EACd,cAAc,EACd,yBAAyB,GAC1B,MAAM,aAAa,CAAC;AAErB,YAAY,EACV,UAAU,EACV,QAAQ,EACR,gBAAgB,EAChB,gBAAgB,EAChB,gBAAgB,GACjB,MAAM,aAAa,CAAC;AAErB,YAAY,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AACjD,YAAY,EAAE,SAAS,EAAE,SAAS,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AACjG,YAAY,EAAE,iBAAiB,EAAE,WAAW,EAAE,aAAa,EAAE,cAAc,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC3H,YAAY,EACV,kBAAkB,EAClB,sBAAsB,EACtB,YAAY,GACb,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,0BAA0B,EAC1B,oBAAoB,GACrB,MAAM,uBAAuB,CAAC;AAC/B,YAAY,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AACpD,YAAY,EACV,SAAS,EACT,OAAO,EACP,SAAS,EACT,WAAW,GACZ,MAAM,gBAAgB,CAAC;AACxB,YAAY,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,YAAY,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAE/D,YAAY,EACV,mBAAmB,EACnB,WAAW,EACX,aAAa,EACb,YAAY,EACZ,qBAAqB,EACrB,WAAW,EACX,YAAY,EACZ,kBAAkB,EAClB,iBAAiB,GAClB,MAAM,wBAAwB,CAAC;AAEhC,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAExD,YAAY,EACV,YAAY,EACZ,WAAW,EACX,SAAS,EACT,mBAAmB,EACnB,kBAAkB,EAClB,cAAc,EACd,YAAY,GACb,MAAM,kBAAkB,CAAC;AAE1B,YAAY,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AACnD,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAEnD,YAAY,EACV,YAAY,EACZ,YAAY,EACZ,aAAa,GACd,MAAM,uBAAuB,CAAC;AAE/B,YAAY,EACV,UAAU,EACV,UAAU,EACV,cAAc,EACd,cAAc,EACd,yBAAyB,GAC1B,MAAM,aAAa,CAAC;AAErB,YAAY,EACV,UAAU,EACV,QAAQ,EACR,gBAAgB,EAChB,gBAAgB,EAChB,gBAAgB,GACjB,MAAM,aAAa,CAAC;AAErB,YAAY,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AACjD,YAAY,EAAE,SAAS,EAAE,SAAS,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AACjG,YAAY,EAAE,iBAAiB,EAAE,WAAW,EAAE,aAAa,EAAE,cAAc,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC3H,YAAY,EACV,kBAAkB,EAClB,sBAAsB,EACtB,YAAY,GACb,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,0BAA0B,EAC1B,oBAAoB,GACrB,MAAM,uBAAuB,CAAC;AAC/B,YAAY,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AACpD,YAAY,EACV,SAAS,EACT,OAAO,EACP,SAAS,EACT,WAAW,GACZ,MAAM,gBAAgB,CAAC;AACxB,YAAY,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AACrE,OAAO,EAAE,gBAAgB,EAAE,uBAAuB,EAAE,MAAM,gBAAgB,CAAC"}
package/dist/index.js CHANGED
@@ -29,12 +29,17 @@
29
29
  * and live SIP INFO ({@link import('./types/events').SipInfo} on `sipInfo$`). See **`README.md`** → SIP Signalling Metadata.
30
30
  */
31
31
  Object.defineProperty(exports, "__esModule", { value: true });
32
- exports.isLegacyPhraseRecord = exports.LEGACY_PHRASE_RECORD_BRAND = exports.getScriptPhase = exports.defineScript = void 0;
32
+ exports.parseRecallDelaySeconds = exports.parseRecallCount = exports.isLegacyPhraseRecord = exports.LEGACY_PHRASE_RECORD_BRAND = exports.TranscriptionRole = exports.getScriptPhase = exports.defineScript = void 0;
33
33
  var define_script_1 = require("./define-script");
34
34
  Object.defineProperty(exports, "defineScript", { enumerable: true, get: function () { return define_script_1.defineScript; } });
35
35
  var script_context_1 = require("./types/script-context");
36
36
  Object.defineProperty(exports, "getScriptPhase", { enumerable: true, get: function () { return script_context_1.getScriptPhase; } });
37
+ var logger_1 = require("./types/logger");
38
+ Object.defineProperty(exports, "TranscriptionRole", { enumerable: true, get: function () { return logger_1.TranscriptionRole; } });
37
39
  var legacy_phrase_1 = require("./types/legacy-phrase");
38
40
  Object.defineProperty(exports, "LEGACY_PHRASE_RECORD_BRAND", { enumerable: true, get: function () { return legacy_phrase_1.LEGACY_PHRASE_RECORD_BRAND; } });
39
41
  Object.defineProperty(exports, "isLegacyPhraseRecord", { enumerable: true, get: function () { return legacy_phrase_1.isLegacyPhraseRecord; } });
42
+ var recall_utils_1 = require("./recall-utils");
43
+ Object.defineProperty(exports, "parseRecallCount", { enumerable: true, get: function () { return recall_utils_1.parseRecallCount; } });
44
+ Object.defineProperty(exports, "parseRecallDelaySeconds", { enumerable: true, get: function () { return recall_utils_1.parseRecallDelaySeconds; } });
40
45
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;;;AAEH,iDAA+C;AAAtC,6GAAA,YAAY,OAAA;AAerB,yDAAwD;AAA/C,gHAAA,cAAc,OAAA;AA4CvB,uDAG+B;AAF7B,2HAAA,0BAA0B,OAAA;AAC1B,qHAAA,oBAAoB,OAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;;;AAEH,iDAA+C;AAAtC,6GAAA,YAAY,OAAA;AAerB,yDAAwD;AAA/C,gHAAA,cAAc,OAAA;AAavB,yCAAmD;AAA1C,2GAAA,iBAAiB,OAAA;AAgC1B,uDAG+B;AAF7B,2HAAA,0BAA0B,OAAA;AAC1B,qHAAA,oBAAoB,OAAA;AAUtB,+CAA2E;AAAlE,gHAAA,gBAAgB,OAAA;AAAE,uHAAA,uBAAuB,OAAA"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Parse recall delay to seconds — parity with logic-executor `agent.delay` / `recall_delay`
3
+ * in call params (`HH:mm:ss`, integer seconds).
4
+ */
5
+ export declare function parseRecallDelaySeconds(value: unknown): number | undefined;
6
+ /** Parse recall attempt limit from call/dialog params. */
7
+ export declare function parseRecallCount(value: unknown): number | undefined;
8
+ //# sourceMappingURL=recall-utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recall-utils.d.ts","sourceRoot":"","sources":["../src/recall-utils.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CA4B1E;AAED,0DAA0D;AAC1D,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAWnE"}