@stackbone/cli 0.9.1 → 0.9.2

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.
@@ -0,0 +1,16 @@
1
+ -- 0122_model_routing_object.sql — a provider routing is OpenRouter's whole
2
+ -- `provider` object (`{ "only": ["deepinfra/fp8"], "allow_fallbacks": false }`),
3
+ -- not only its `sort`.
4
+ --
5
+ -- `routing` holds the object. `sort` stays, now nullable, and carries the routing
6
+ -- only when it is nothing but a plain sort: an SDK older than the object reads
7
+ -- `sort` alone, and it must see a routing it cannot apply as no routing, never
8
+ -- apply half of it. Rows written before 0122 keep `routing` NULL; every reader
9
+ -- falls back to `{ "sort": sort }`.
10
+ --
11
+ -- No CHECK on `routing`, for the reason 0121 has none on `sort`.
12
+ ALTER TABLE stackbone_platform.model_routing
13
+ ADD COLUMN IF NOT EXISTS routing jsonb;
14
+
15
+ ALTER TABLE stackbone_platform.model_routing
16
+ ALTER COLUMN sort DROP NOT NULL;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stackbone/cli",
3
- "version": "0.9.1",
3
+ "version": "0.9.2",
4
4
  "license": "UNLICENSED",
5
5
  "type": "module",
6
6
  "bin": {
Binary file
@@ -149,7 +149,7 @@ var loadVad = async () => {
149
149
  };
150
150
 
151
151
  // libs/runtime/voice-worker/src/worker.ts
152
- import { log as log4, voice as voice4 } from "@livekit/agents";
152
+ import { log as log5, voice as voice4 } from "@livekit/agents";
153
153
  import {
154
154
  BackgroundVoiceCancellation,
155
155
  TelephonyBackgroundVoiceCancellation
@@ -975,8 +975,12 @@ var turnSchema = z8.strictObject({
975
975
  mode: z8.enum(["stt", "livekit"]),
976
976
  minEndpointingDelayMs: millisecondsSchema,
977
977
  maxEndpointingDelayMs: millisecondsSchema,
978
- detectorThreshold: thresholdSchema
978
+ detectorThreshold: thresholdSchema,
979
+ // Flux refuses under 0.3 and above its own end-of-turn threshold (0.7 by default,
980
+ // not a setting), so the bound is the range Flux takes with that default.
981
+ eagerThreshold: z8.number().min(0.3).max(0.7)
979
982
  });
983
+ var VOICE_EAGER_THRESHOLD_DEFAULT = 0.3;
980
984
  var interruptionsSchema = z8.strictObject({
981
985
  enabled: z8.boolean(),
982
986
  minDurationMs: millisecondsSchema,
@@ -1017,7 +1021,12 @@ var effectiveVoiceSettingsObjectSchema = z8.strictObject({
1017
1021
  sttModel: z8.enum(VOICE_STT_MODELS),
1018
1022
  keyterms: keytermsSchema.default(() => []),
1019
1023
  vad: vadSchema,
1020
- turn: turnSchema,
1024
+ // Defaulted here, on the read side only: a box older than STA3-94 answers, and
1025
+ // dispatches, a `turn` without it. Not on `turnSchema`, whose `.partial()` layers
1026
+ // would then all declare it.
1027
+ turn: turnSchema.extend({
1028
+ eagerThreshold: turnSchema.shape.eagerThreshold.default(VOICE_EAGER_THRESHOLD_DEFAULT)
1029
+ }),
1021
1030
  interruptions: interruptionsSchema,
1022
1031
  ttsModel: z8.enum(VOICE_TTS_MODELS),
1023
1032
  /**
@@ -1105,6 +1114,7 @@ var VOICE_SETTINGS_LEAF_PATHS = [
1105
1114
  "turn.minEndpointingDelayMs",
1106
1115
  "turn.maxEndpointingDelayMs",
1107
1116
  "turn.detectorThreshold",
1117
+ "turn.eagerThreshold",
1108
1118
  "interruptions.enabled",
1109
1119
  "interruptions.minDurationMs",
1110
1120
  "interruptions.minWords",
@@ -1129,11 +1139,19 @@ var VOICE_SETTINGS_LEAF_PATHS = [
1129
1139
  "refusalPhrase"
1130
1140
  ];
1131
1141
  var voiceSettingOriginSchema = z8.enum(["studio", "code", "default"]);
1142
+ var LEAVES_NEWER_THAN_SOME_BOXES = /* @__PURE__ */ new Set([
1143
+ "turn.eagerThreshold"
1144
+ ]);
1132
1145
  var effectiveVoiceSettingsViewSchema = z8.strictObject({
1133
1146
  value: effectiveVoiceSettingsObjectSchema,
1134
1147
  origins: z8.strictObject(
1135
1148
  Object.fromEntries(
1136
- VOICE_SETTINGS_LEAF_PATHS.map((path) => [path, voiceSettingOriginSchema])
1149
+ // A leaf newer than the box answering does not come back: that box runs its default.
1150
+ // Every other leaf stays required, so a box that drops one is still caught.
1151
+ VOICE_SETTINGS_LEAF_PATHS.map((path) => [
1152
+ path,
1153
+ LEAVES_NEWER_THAN_SOME_BOXES.has(path) ? voiceSettingOriginSchema.default("default") : voiceSettingOriginSchema
1154
+ ])
1137
1155
  )
1138
1156
  )
1139
1157
  });
@@ -1247,6 +1265,9 @@ var VOICE_CALLS_PATH = "/voice/v1/calls";
1247
1265
  var voiceCallBootstrapSchema = z8.strictObject({
1248
1266
  identities: integrationIdentitiesSchema.optional()
1249
1267
  });
1268
+ var voiceCallKnowledgeSchema = z8.strictObject({
1269
+ text: z8.string().min(1).max(HEARD_TEXT_MAX)
1270
+ });
1250
1271
  var voiceCallTranscriptSchema = z8.array(
1251
1272
  z8.strictObject({
1252
1273
  role: z8.enum(["user", "assistant"]),
@@ -1266,7 +1287,9 @@ var voiceCallTurnMetricsSchema = z8.strictObject({
1266
1287
  transcriptionMs: turnLatencyMs,
1267
1288
  llmTtftMs: turnLatencyMs,
1268
1289
  ttsTtfbMs: turnLatencyMs,
1269
- e2eMs: turnLatencyMs
1290
+ e2eMs: turnLatencyMs,
1291
+ /** How long before the final transcript the turn's last eager end of turn left for the box (STA3-94). */
1292
+ preflightLeadMs: turnLatencyMs
1270
1293
  });
1271
1294
  var voiceCallTurnsSchema = z8.array(voiceCallTurnMetricsSchema).max(VOICE_CALL_TURNS_MAX);
1272
1295
  var voiceCallCompleteSchema = z8.strictObject({
@@ -1316,6 +1339,7 @@ async function* readSseData(body) {
1316
1339
  // libs/runtime/voice-worker/src/box-wire.ts
1317
1340
  var BODY_EXCERPT_CHARS = 300;
1318
1341
  var TURN_IDLE_TIMEOUT_MS = 3e4;
1342
+ var KNOWLEDGE_TIMEOUT_MS = 2e3;
1319
1343
  var NON_ASCII = /[\u007f-\uffff]/g;
1320
1344
  var runErrorOf = ({ message, code }) => new Error(`box run failed${code ? ` (${code})` : ""}: ${message}`);
1321
1345
  var BoxTurnError = class extends Error {
@@ -1424,23 +1448,39 @@ var toWireEvent = (ev) => {
1424
1448
  return { kind: "other", type };
1425
1449
  }
1426
1450
  };
1451
+ var postCallOperation = (wire, operation, payload, timeoutMs) => fetch(
1452
+ boxEndpoint(
1453
+ wire.boxUrl,
1454
+ `${VOICE_CALLS_PATH}/${encodeURIComponent(wire.sessionKey)}/${operation}`
1455
+ ),
1456
+ {
1457
+ method: "POST",
1458
+ headers: { "content-type": "application/json", authorization: `Bearer ${wire.token}` },
1459
+ signal: AbortSignal.timeout(timeoutMs),
1460
+ body: JSON.stringify(payload)
1461
+ }
1462
+ );
1427
1463
  var voiceCallRequest = async (wire, operation, payload) => {
1428
- const response = await fetch(
1429
- boxEndpoint(
1430
- wire.boxUrl,
1431
- `${VOICE_CALLS_PATH}/${encodeURIComponent(wire.sessionKey)}/${operation}`
1432
- ),
1433
- {
1434
- method: "POST",
1435
- headers: { "content-type": "application/json", authorization: `Bearer ${wire.token}` },
1436
- signal: AbortSignal.timeout(TURN_IDLE_TIMEOUT_MS),
1437
- body: JSON.stringify(payload)
1438
- }
1439
- );
1464
+ const response = await postCallOperation(wire, operation, payload, TURN_IDLE_TIMEOUT_MS);
1440
1465
  if (!response.ok)
1441
1466
  throw new BoxTurnError(wire.agent, response.status, await readExcerpt(response));
1442
1467
  voiceCallLifecycleResponseSchema.parse(await response.json());
1443
1468
  };
1469
+ var voiceCallKnowledge = async (wire, text) => {
1470
+ try {
1471
+ const response = await postCallOperation(
1472
+ wire,
1473
+ "knowledge",
1474
+ { text },
1475
+ KNOWLEDGE_TIMEOUT_MS
1476
+ );
1477
+ await response.body?.cancel().catch(() => void 0);
1478
+ if (response.ok) return "sent";
1479
+ return response.status === 404 ? "gone" : "failed";
1480
+ } catch {
1481
+ return "failed";
1482
+ }
1483
+ };
1444
1484
 
1445
1485
  // libs/runtime/voice-worker/src/heard.ts
1446
1486
  var VOICE_RESPONSE_MARKER = "stackboneVoiceResponse";
@@ -1621,11 +1661,13 @@ var defined = (metrics) => Object.fromEntries(Object.entries(metrics).filter(([,
1621
1661
  var watchTurnMetrics = (session, write, options = {}) => {
1622
1662
  let caller;
1623
1663
  let delivered;
1664
+ let lead;
1624
1665
  session.on(voice2.AgentSessionEventTypes.ConversationItemAdded, ({ item }) => {
1625
1666
  if (!(item instanceof llm.ChatMessage)) return;
1626
1667
  if (item.role === "user") {
1627
1668
  caller = item.metrics;
1628
1669
  delivered = options.endOfTurnMs?.();
1670
+ lead = options.preflightLeadMs?.();
1629
1671
  return;
1630
1672
  }
1631
1673
  if (item.role !== "assistant") return;
@@ -1637,10 +1679,12 @@ var watchTurnMetrics = (session, write, options = {}) => {
1637
1679
  transcriptionMs: options.endOfTurnMs ? void 0 : ms(caller?.transcriptionDelay),
1638
1680
  llmTtftMs: ms(reply.llmNodeTtft),
1639
1681
  ttsTtfbMs: ms(reply.ttsNodeTtfb),
1640
- e2eMs: e2eMs === void 0 || delivered === void 0 ? e2eMs : e2eMs + delivered
1682
+ e2eMs: e2eMs === void 0 || delivered === void 0 ? e2eMs : e2eMs + delivered,
1683
+ preflightLeadMs: lead === void 0 ? void 0 : Math.round(lead)
1641
1684
  });
1642
1685
  caller = void 0;
1643
1686
  delivered = void 0;
1687
+ lead = void 0;
1644
1688
  if (Object.keys(metrics).length === 0) return;
1645
1689
  write(typeof runId === "string" ? { ...metrics, runId } : metrics);
1646
1690
  });
@@ -1979,15 +2023,56 @@ var createHangUp = ({
1979
2023
  // libs/runtime/voice-worker/src/session-options.ts
1980
2024
  import { tts, voice as voice3 } from "@livekit/agents";
1981
2025
 
1982
- // libs/runtime/voice-worker/src/number-text.ts
2026
+ // libs/runtime/voice-worker/src/knowledge-prefetch.ts
2027
+ import { log as log3, stt as stt2 } from "@livekit/agents";
1983
2028
  import { TransformStream as TransformStream2 } from "node:stream/web";
2029
+ var knowledgeTap = (send, { onLead, now = Date.now } = {}) => {
2030
+ let sentThisTurn;
2031
+ let sentAt;
2032
+ let gone = false;
2033
+ let failureLogged = false;
2034
+ const tap = (event) => {
2035
+ if (gone || typeof event === "string") return;
2036
+ if (event.type === stt2.SpeechEventType.FINAL_TRANSCRIPT) {
2037
+ if (sentAt !== void 0) onLead?.(now() - sentAt);
2038
+ sentThisTurn = void 0;
2039
+ sentAt = void 0;
2040
+ return;
2041
+ }
2042
+ if (event.type !== stt2.SpeechEventType.PREFLIGHT_TRANSCRIPT) return;
2043
+ const text = event.alternatives?.[0]?.text.trim();
2044
+ if (!text || text === sentThisTurn) return;
2045
+ sentThisTurn = text;
2046
+ sentAt = now();
2047
+ void send(text).then((answer) => {
2048
+ if (answer === "gone") gone = true;
2049
+ if (answer === "failed" && !failureLogged) {
2050
+ failureLogged = true;
2051
+ log3().warn(
2052
+ "voice worker: the box refused a knowledge prefetch; turns search on their own"
2053
+ );
2054
+ }
2055
+ }).catch(() => void 0);
2056
+ };
2057
+ return (events) => events.pipeThrough(
2058
+ new TransformStream2({
2059
+ transform(event, controller) {
2060
+ tap(event);
2061
+ controller.enqueue(event);
2062
+ }
2063
+ })
2064
+ );
2065
+ };
2066
+
2067
+ // libs/runtime/voice-worker/src/number-text.ts
2068
+ import { TransformStream as TransformStream3 } from "node:stream/web";
1984
2069
  var THOUSANDS_COMMA = /(\d),(?=\d{3}(?!\d))/g;
1985
2070
  var TRAILING_NUMBER = /[\d.,]+$/;
1986
2071
  var rewrite = (text) => text.replace(THOUSANDS_COMMA, "$1");
1987
2072
  var dropThousandsComma = (input) => {
1988
2073
  let pending = "";
1989
2074
  return input.pipeThrough(
1990
- new TransformStream2({
2075
+ new TransformStream3({
1991
2076
  transform(chunk, controller) {
1992
2077
  const text = pending + chunk;
1993
2078
  const tail = TRAILING_NUMBER.exec(text)?.[0] ?? "";
@@ -2004,7 +2089,7 @@ var dropThousandsComma = (input) => {
2004
2089
 
2005
2090
  // libs/runtime/voice-worker/src/phone-voice.ts
2006
2091
  import { AudioFrame as AudioFrame2 } from "@livekit/rtc-node";
2007
- import { TransformStream as TransformStream3 } from "node:stream/web";
2092
+ import { TransformStream as TransformStream4 } from "node:stream/web";
2008
2093
  var LOW_CUT_HZ = 300;
2009
2094
  var HIGH_CUT_HZ = 3400;
2010
2095
  var Q = Math.SQRT1_2;
@@ -2040,7 +2125,7 @@ var phoneLine = (audio) => {
2040
2125
  let chainRate = 0;
2041
2126
  const norm = Math.tanh(DRIVE);
2042
2127
  return audio.pipeThrough(
2043
- new TransformStream3({
2128
+ new TransformStream4({
2044
2129
  transform(frame, controller) {
2045
2130
  if (!chain || chainRate !== frame.sampleRate) {
2046
2131
  chainRate = frame.sampleRate;
@@ -2066,11 +2151,18 @@ var phoneLine = (audio) => {
2066
2151
  };
2067
2152
 
2068
2153
  // libs/runtime/voice-worker/src/session-options.ts
2069
- var sttOptions = ({ sttModel, language, keyterms }) => {
2154
+ var sttOptions = ({ sttModel, language, keyterms, turn }) => {
2070
2155
  const terms = keyterms.length > 0 ? [...keyterms] : void 0;
2071
2156
  const keyterm = terms ? { keyterm: terms } : void 0;
2072
2157
  if (sttModel.startsWith("deepgram/flux")) {
2073
- return { model: sttModel, modelOptions: { language_hint: language, ...keyterm } };
2158
+ return {
2159
+ model: sttModel,
2160
+ modelOptions: {
2161
+ language_hint: language,
2162
+ ...keyterm,
2163
+ eager_eot_threshold: turn.eagerThreshold
2164
+ }
2165
+ };
2074
2166
  }
2075
2167
  if (sttModel.startsWith("assemblyai/")) {
2076
2168
  return {
@@ -2094,12 +2186,28 @@ var markupContext = (expressive, speech) => {
2094
2186
  return guide === void 0 ? [] : [{ description: VOICE_TTS_MARKUP_CONTEXT, value: guide }];
2095
2187
  };
2096
2188
  var VoiceCallAgent = class extends voice3.Agent {
2097
- constructor() {
2098
- super(...arguments);
2189
+ constructor({ sendKnowledge, ...options }) {
2190
+ super(options);
2099
2191
  this.sttDelivery = createSttDeliveryMeter();
2192
+ this.#knowledge = sendKnowledge && knowledgeTap(sendKnowledge, {
2193
+ onLead: (ms2) => {
2194
+ this.#preflightLead = ms2;
2195
+ }
2196
+ });
2197
+ }
2198
+ #knowledge;
2199
+ #preflightLead;
2200
+ /** The last turn's preflight head start in ms, once: a turn never reuses another's. */
2201
+ takePreflightLead() {
2202
+ const lead = this.#preflightLead;
2203
+ this.#preflightLead = void 0;
2204
+ return lead;
2100
2205
  }
2101
2206
  async sttNode(audio, modelSettings) {
2102
- return this.sttDelivery.wrap(audio, (counted) => super.sttNode(counted, modelSettings));
2207
+ return this.sttDelivery.wrap(audio, async (counted) => {
2208
+ const events = await super.sttNode(counted, modelSettings);
2209
+ return events && this.#knowledge ? this.#knowledge(events) : events;
2210
+ });
2103
2211
  }
2104
2212
  };
2105
2213
  var PhoneLineAgent = class extends VoiceCallAgent {
@@ -2116,6 +2224,11 @@ var voiceSessionOptions = (settings, pieces) => {
2116
2224
  minSilenceDuration: vad.minSilenceMs
2117
2225
  });
2118
2226
  const speech = pieces.createTts(ttsOptions(settings));
2227
+ const agentOptions = {
2228
+ // The box holds the prompt: LiveKit's own chat context stays prompt-less.
2229
+ instructions: "",
2230
+ sendKnowledge: pieces.sendKnowledge
2231
+ };
2119
2232
  return {
2120
2233
  session: {
2121
2234
  vad: pieces.vad,
@@ -2155,14 +2268,13 @@ var voiceSessionOptions = (settings, pieces) => {
2155
2268
  preemptiveGeneration: { enabled: false, preemptiveTts: false }
2156
2269
  }
2157
2270
  },
2158
- // The box holds the prompt: LiveKit's own chat context stays prompt-less.
2159
- agent: settings.phoneLine ? new PhoneLineAgent({ instructions: "" }) : new VoiceCallAgent({ instructions: "" }),
2271
+ agent: settings.phoneLine ? new PhoneLineAgent(agentOptions) : new VoiceCallAgent(agentOptions),
2160
2272
  boxContext: markupContext(settings.expressive, speech)
2161
2273
  };
2162
2274
  };
2163
2275
 
2164
2276
  // libs/runtime/voice-worker/src/telemetry/call-traces.ts
2165
- import { log as log3, telemetry } from "@livekit/agents";
2277
+ import { log as log4, telemetry } from "@livekit/agents";
2166
2278
  import { ExportResultCode } from "@opentelemetry/core";
2167
2279
  import { defaultResource, resourceFromAttributes } from "@opentelemetry/resources";
2168
2280
  import {
@@ -2174,7 +2286,7 @@ var warnOnRefusedBatch = (exporter, sessionKey) => ({
2174
2286
  export: (spans, done) => exporter.export(spans, (result) => {
2175
2287
  try {
2176
2288
  if (result.code !== ExportResultCode.SUCCESS) {
2177
- log3().warn(
2289
+ log4().warn(
2178
2290
  { sessionKey, error: result.error?.message ?? "unknown" },
2179
2291
  "voice worker: a batch of call spans failed to export to the box"
2180
2292
  );
@@ -2288,14 +2400,14 @@ var callerNoiseCancellation = (caller) => {
2288
2400
  try {
2289
2401
  return caller.kind === ParticipantKind.SIP ? TelephonyBackgroundVoiceCancellation() : BackgroundVoiceCancellation();
2290
2402
  } catch (err) {
2291
- log4().warn({ error: messageOf(err) }, "voice worker: no noise cancellation on this call");
2403
+ log5().warn({ error: messageOf(err) }, "voice worker: no noise cancellation on this call");
2292
2404
  return void 0;
2293
2405
  }
2294
2406
  };
2295
2407
  var runVoiceJob = async (ctx, deps) => {
2296
2408
  const dispatch = readDispatch(ctx.job.metadata);
2297
2409
  if (!dispatch) {
2298
- log4().warn(
2410
+ log5().warn(
2299
2411
  { metadataChars: ctx.job.metadata?.length ?? 0 },
2300
2412
  "voice worker: refused a job without a valid dispatch"
2301
2413
  );
@@ -2304,7 +2416,7 @@ var runVoiceJob = async (ctx, deps) => {
2304
2416
  }
2305
2417
  await ctx.connect();
2306
2418
  const caller = await ctx.waitForParticipant().catch((err) => {
2307
- log4().info(
2419
+ log5().info(
2308
2420
  { reason: messageOf(err) },
2309
2421
  "voice worker: the room closed before the caller joined"
2310
2422
  );
@@ -2332,22 +2444,22 @@ var runVoiceJob = async (ctx, deps) => {
2332
2444
  try {
2333
2445
  session.say(text);
2334
2446
  } catch (err) {
2335
- log4().info({ error: messageOf(err) }, `voice worker: the ${what} was not spoken`);
2447
+ log5().info({ error: messageOf(err) }, `voice worker: the ${what} was not spoken`);
2336
2448
  }
2337
2449
  };
2338
2450
  const hangUpAfter = (played, reason) => {
2339
2451
  ending = true;
2340
2452
  session.input.setAudioEnabled(false);
2341
2453
  void Promise.resolve().then(played).catch((err) => {
2342
- log4().info({ error: messageOf(err) }, "voice worker: the closing phrase was not spoken");
2454
+ log5().info({ error: messageOf(err) }, "voice worker: the closing phrase was not spoken");
2343
2455
  }).then(() => ctx.deleteRoom()).catch((err) => {
2344
- log4().warn({ error: messageOf(err) }, "voice worker: the room was not deleted");
2456
+ log5().warn({ error: messageOf(err) }, "voice worker: the room was not deleted");
2345
2457
  }).then(
2346
2458
  () => finishCall(
2347
2459
  reason === "turns kept failing" || reason === "box refused the call token" ? "failed" : "completed"
2348
2460
  )
2349
2461
  ).catch(() => {
2350
- log4().warn(
2462
+ log5().warn(
2351
2463
  { agent: dispatch.agent, sessionKey: dispatch.sessionKey, reason: "completion_failed" },
2352
2464
  "voice worker: call finalization failed"
2353
2465
  );
@@ -2367,12 +2479,12 @@ var runVoiceJob = async (ctx, deps) => {
2367
2479
  });
2368
2480
  const onTurnError = (err) => {
2369
2481
  if (err instanceof BoxTurnError && err.status === 401) {
2370
- log4().error(boxFailure(err), "voice worker: the box refused the call token (401)");
2482
+ log5().error(boxFailure(err), "voice worker: the box refused the call token (401)");
2371
2483
  if (!ending) endCall(CALL_CLOSING_PHRASE, "box refused the call token");
2372
2484
  return;
2373
2485
  }
2374
2486
  failedInARow += 1;
2375
- log4().warn({ ...boxFailure(err), failedInARow }, "voice worker: a turn failed");
2487
+ log5().warn({ ...boxFailure(err), failedInARow }, "voice worker: a turn failed");
2376
2488
  if (ending) return;
2377
2489
  if (failedInARow >= MAX_FAILED_TURNS_IN_A_ROW) {
2378
2490
  endCall(CALL_CLOSING_PHRASE, "turns kept failing");
@@ -2384,13 +2496,20 @@ var runVoiceJob = async (ctx, deps) => {
2384
2496
  failedInARow = 0;
2385
2497
  if (outcome === "interrupt" && !ending) endCall(PAUSED_RUN_PHRASE, "run paused for review");
2386
2498
  };
2387
- const plan = voiceSessionOptions(settings, { ...deps, vad: ctx.proc.userData.vad });
2388
- const wire = {
2499
+ const call = {
2389
2500
  boxUrl: dispatch.boxUrl,
2390
2501
  token: dispatch.callToken,
2391
2502
  agent: dispatch.agent,
2392
2503
  sessionKey: dispatch.sessionKey,
2393
- tags: dispatch.tags,
2504
+ tags: dispatch.tags
2505
+ };
2506
+ const plan = voiceSessionOptions(settings, {
2507
+ ...deps,
2508
+ vad: ctx.proc.userData.vad,
2509
+ sendKnowledge: (text) => voiceCallKnowledge(call, text)
2510
+ });
2511
+ const wire = {
2512
+ ...call,
2394
2513
  // With `expressive`, the TTS markup guide: LiveKit gives it only to its own chat context.
2395
2514
  context: plan.boxContext
2396
2515
  };
@@ -2406,7 +2525,7 @@ var runVoiceJob = async (ctx, deps) => {
2406
2525
  hangUpAfter,
2407
2526
  isEnding: () => ending,
2408
2527
  onResumeFailed: (err) => {
2409
- log4().warn(boxFailure(err), "voice worker: the hang_up resume failed");
2528
+ log5().warn(boxFailure(err), "voice worker: the hang_up resume failed");
2410
2529
  }
2411
2530
  });
2412
2531
  const brain = new BoxLLM({
@@ -2438,14 +2557,14 @@ var runVoiceJob = async (ctx, deps) => {
2438
2557
  if (!(turns.length > 0 && error instanceof BoxTurnError && error.status === 400)) {
2439
2558
  throw error;
2440
2559
  }
2441
- log4().warn(
2560
+ log5().warn(
2442
2561
  { agent: dispatch.agent, sessionKey: dispatch.sessionKey, status: error.status },
2443
2562
  "voice worker: the box refused the turn metrics, completing without them"
2444
2563
  );
2445
2564
  await complete({ transcript, outcome });
2446
2565
  }
2447
2566
  }).catch((error) => {
2448
- log4().warn(
2567
+ log5().warn(
2449
2568
  { agent: dispatch.agent, sessionKey: dispatch.sessionKey, reason: "completion_failed" },
2450
2569
  "voice worker: call finalization failed"
2451
2570
  );
@@ -2466,12 +2585,15 @@ var runVoiceJob = async (ctx, deps) => {
2466
2585
  session,
2467
2586
  (metrics) => {
2468
2587
  if (turns.length < VOICE_CALL_TURNS_MAX) turns.push(metrics);
2469
- log4().info(
2588
+ log5().info(
2470
2589
  { agent: dispatch.agent, sessionKey: dispatch.sessionKey, room: ctx.room.name, ...metrics },
2471
2590
  VOICE_TURN_METRICS_MESSAGE
2472
2591
  );
2473
2592
  },
2474
- deliversItsOwnEndOfTurn(settings) ? { endOfTurnMs: () => plan.agent.sttDelivery.take() } : {}
2593
+ {
2594
+ ...deliversItsOwnEndOfTurn(settings) ? { endOfTurnMs: () => plan.agent.sttDelivery.take() } : {},
2595
+ preflightLeadMs: () => plan.agent.takePreflightLead()
2596
+ }
2475
2597
  );
2476
2598
  const noiseCancellation = callerNoiseCancellation(participant);
2477
2599
  await session.start({
@@ -2481,7 +2603,7 @@ var runVoiceJob = async (ctx, deps) => {
2481
2603
  });
2482
2604
  if (settings.greeting !== null) sayIfOpen(settings.greeting, "greeting");
2483
2605
  await deps.createCue({ room: ctx.room, session, audio: settings.backgroundAudio }).then(cue.attach, (err) => {
2484
- log4().warn({ error: messageOf(err) }, "voice worker: the background audio did not start");
2606
+ log5().warn({ error: messageOf(err) }, "voice worker: the background audio did not start");
2485
2607
  });
2486
2608
  };
2487
2609