@voicelayer/sdk 0.1.7 → 0.1.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -137,6 +137,9 @@ var init_flow = __esm({
137
137
  "llm",
138
138
  "rag",
139
139
  "memory",
140
+ // Call-wide voice/turn-taking capability (barge-in + endpointing tuning);
141
+ // managed in the Capabilities panel like `memory`, never a graph step.
142
+ "speech",
140
143
  "classify",
141
144
  // Agentic router: per-turn, selects 1-of-N skills by their plain-language
142
145
  // descriptions and takes that port (the resident routing surface / hub).
@@ -335,6 +338,19 @@ var init_agents = __esm({
335
338
  }).optional(),
336
339
  // Tool capabilities compiled from tool nodes (maps to AgentConfig.tools).
337
340
  tools: z.array(ProcessToolDTO).max(32).optional(),
341
+ // Voice turn-taking compiled from the flow's speech capability node (maps to
342
+ // AgentConfig.speech → applySpeechTurnHandling). Absent = platform defaults.
343
+ speech: z.object({
344
+ interruption: z.object({
345
+ enabled: z.boolean().optional(),
346
+ minWords: z.number().int().min(0).max(10).optional(),
347
+ minDuration: z.number().int().min(0).max(5e3).optional()
348
+ }).optional(),
349
+ endpointing: z.object({
350
+ minDelay: z.number().int().min(0).max(5e3).optional(),
351
+ maxDelay: z.number().int().min(0).max(15e3).optional()
352
+ }).optional()
353
+ }).optional(),
338
354
  // Graph-preserving compiled program. When present and the flow-runtime graph
339
355
  // mode is enabled, the worker executes this node-graph deterministically
340
356
  // (the interpreter) instead of the flat goals+rails form above. Embedded here
@@ -494,7 +510,7 @@ var init_consultation = __esm({
494
510
  });
495
511
  }
496
512
  });
497
- var CALL_CONTROL_PUBSUB_CHANNEL, CallEventKind, SayCommand, HangupCommand, DtmfCommand, InstructCommand, CallControlCommand;
513
+ var CALL_CONTROL_PUBSUB_CHANNEL, CallEventKind, OP_CALL_EVENT_KIND, OpCallEventKind, SayCommand, HangupCommand, DtmfCommand, InstructCommand, CallControlCommand;
498
514
  var init_call_events = __esm({
499
515
  "../contracts/src/call-events.ts"() {
500
516
  init_consultation();
@@ -520,6 +536,8 @@ var init_call_events = __esm({
520
536
  "dtmf.received",
521
537
  "dtmf.sent"
522
538
  ]);
539
+ OP_CALL_EVENT_KIND = /^(tool|handoff|lookup|record|notify)\.[a-z0-9_]{1,48}(\.[a-z0-9_]{1,48})?$/;
540
+ OpCallEventKind = z.string().regex(OP_CALL_EVENT_KIND);
523
541
  z.object({
524
542
  reason: z.string().optional(),
525
543
  durationMs: z.number().int().min(0).optional(),
@@ -557,7 +575,8 @@ var init_call_events = __esm({
557
575
  seq: z.number().int().min(0),
558
576
  callId: z.string().uuid(),
559
577
  projectId: z.string().min(1),
560
- kind: CallEventKind,
578
+ // Closed lifecycle kinds OR the namespaced operational vocabulary.
579
+ kind: z.union([CallEventKind, OpCallEventKind]),
561
580
  payload: z.unknown(),
562
581
  ts: z.string().datetime()
563
582
  });
@@ -1914,7 +1933,20 @@ function nodeConfigFor(node) {
1914
1933
  ...typeof data.connectionRef === "string" ? { connectionRef: data.connectionRef } : {},
1915
1934
  // Registry reference: run the project HTTP tool with this NAME via the
1916
1935
  // server-side proxy instead of the inline url/method/auth above.
1917
- ...typeof data.toolRef === "string" && data.toolRef ? { toolRef: data.toolRef } : {}
1936
+ ...typeof data.toolRef === "string" && data.toolRef ? { toolRef: data.toolRef } : {},
1937
+ // Promote result fields into NAMED slots on success:
1938
+ // { slotName: "data.path" }. Later nodes then read {{ slotName }}
1939
+ // directly instead of {{ tool.<nodeId>.data.path }} via a set node.
1940
+ ...(() => {
1941
+ if (!data.outputAs || typeof data.outputAs !== "object") return {};
1942
+ const outputAs = {};
1943
+ for (const [slot, path] of Object.entries(data.outputAs)) {
1944
+ if (typeof path === "string" && path.trim() && /^[a-zA-Z_][\w.]*$/.test(slot)) {
1945
+ outputAs[slot] = path.trim();
1946
+ }
1947
+ }
1948
+ return Object.keys(outputAs).length ? { outputAs } : {};
1949
+ })()
1918
1950
  };
1919
1951
  }
1920
1952
  case "gate":
@@ -1927,7 +1959,10 @@ function nodeConfigFor(node) {
1927
1959
  ...typeof data.fallback === "string" ? { fallback: data.fallback } : {},
1928
1960
  ...typeof data.on === "string" ? { on: data.on } : {},
1929
1961
  // Per-node warm (conference brief) / cold (blind REFER) override.
1930
- ...data.mode === "warm" || data.mode === "cold" ? { mode: data.mode } : {}
1962
+ ...data.mode === "warm" || data.mode === "cold" ? { mode: data.mode } : {},
1963
+ // What the joining human HEARS (warm mode speaks it verbatim once they
1964
+ // answer). {{ }}-interpolated at runtime against the slot scope.
1965
+ ...typeof data.briefing === "string" && data.briefing.trim() ? { briefing: data.briefing } : {}
1931
1966
  };
1932
1967
  case "end":
1933
1968
  return {
@@ -2091,7 +2126,7 @@ var init_flow_compile = __esm({
2091
2126
  HISTORY_POLICIES = /* @__PURE__ */ new Set(["full", "last-turn", "slots-only"]);
2092
2127
  ADHERENCE_MODES = /* @__PURE__ */ new Set(["off", "observe", "enforce"]);
2093
2128
  UNSUPPORTED_NODE_TYPES = /* @__PURE__ */ new Set([]);
2094
- CAPABILITY_NODE_TYPES = /* @__PURE__ */ new Set(["memory", "trigger"]);
2129
+ CAPABILITY_NODE_TYPES = /* @__PURE__ */ new Set(["memory", "trigger", "speech"]);
2095
2130
  ANNOTATION_NODE_TYPES = /* @__PURE__ */ new Set(["note"]);
2096
2131
  }
2097
2132
  });
@@ -3098,7 +3133,7 @@ var init_executors = __esm({
3098
3133
  const authoredSlot = slot === node.id ? "" : slot.trim();
3099
3134
  const label = authoredSlot.replace(/[_-]+/g, " ");
3100
3135
  if (hasSlot(deps.processRt.getData(), slot)) {
3101
- console.log("[graph] ask: slot already known, skipping", { slot: slot.trim() });
3136
+ deps.events.note("ask.skipped", { slot: slot.trim(), reason: "already known" });
3102
3137
  return { kind: "advance" };
3103
3138
  }
3104
3139
  const askText = interpolate(
@@ -3112,10 +3147,10 @@ var init_executors = __esm({
3112
3147
  state.lastUtterance = t.text;
3113
3148
  state.lastDtmf = t.dtmf;
3114
3149
  if (hasSlot(deps.processRt.getData(), slot)) {
3115
- console.log("[graph] ask: captured", { slot: slot.trim() });
3150
+ deps.events.note("ask.captured", { slot: slot.trim() });
3116
3151
  return { kind: "advance" };
3117
3152
  }
3118
- console.log("[graph] ask: clarify", { slot: slot.trim(), turn });
3153
+ deps.events.note("ask.clarify", { slot: slot.trim(), turn });
3119
3154
  await withHistory(
3120
3155
  deps,
3121
3156
  historyPolicyOf(node),
@@ -3141,7 +3176,7 @@ var init_executors = __esm({
3141
3176
  if (msg) await speak(interpolate(msg, scopeFor(state, deps)), deps);
3142
3177
  return { kind: "end" };
3143
3178
  };
3144
- handoffExecutor = async (node, _state, deps) => {
3179
+ handoffExecutor = async (node, state, deps) => {
3145
3180
  const cfg = node.config;
3146
3181
  const target = str(cfg.to) || str(cfg.fallback);
3147
3182
  if (!target) {
@@ -3150,9 +3185,14 @@ var init_executors = __esm({
3150
3185
  }
3151
3186
  const reason = str(cfg.reason) || `flow:${node.id}`;
3152
3187
  const mode = cfg.mode === "warm" || cfg.mode === "cold" ? cfg.mode : void 0;
3188
+ const briefing = str(cfg.briefing) ? interpolate(str(cfg.briefing), scopeFor(state, deps)) : void 0;
3153
3189
  deps.events.note("handoff.start", { node: node.id, to: target, ...mode ? { mode } : {} });
3154
3190
  try {
3155
- await deps.ctx.handoff(target, { reason, ...mode ? { mode } : {} });
3191
+ await deps.ctx.handoff(target, {
3192
+ reason,
3193
+ ...mode ? { mode } : {},
3194
+ ...briefing ? { briefing } : {}
3195
+ });
3156
3196
  } catch (err) {
3157
3197
  deps.events.note("handoff.failed", {
3158
3198
  node: node.id,
@@ -3206,7 +3246,24 @@ ${collected}`);
3206
3246
  const cfg = node.config;
3207
3247
  const name = str(cfg.name) || node.id;
3208
3248
  const routeByOutcome = () => {
3209
- const ok = state.toolResults[node.id]?.ok === true;
3249
+ const result = state.toolResults[node.id];
3250
+ const ok = result?.ok === true;
3251
+ if (ok && cfg.outputAs && typeof cfg.outputAs === "object") {
3252
+ for (const [slot, path] of Object.entries(cfg.outputAs)) {
3253
+ if (typeof path !== "string" || !path.trim()) continue;
3254
+ const value = resolvePath(result, path.trim());
3255
+ if (value === void 0 || value === null) {
3256
+ deps.events.note("tool.output_missing", { node: node.id, slot, path });
3257
+ continue;
3258
+ }
3259
+ const captured = deps.processRt.captureField(slot, value);
3260
+ deps.events.note(captured ? "tool.output_captured" : "tool.output_rejected", {
3261
+ node: node.id,
3262
+ slot,
3263
+ ...captured ? {} : { reason: "slot not declared or value not coercible" }
3264
+ });
3265
+ }
3266
+ }
3210
3267
  return routeByPort(node, deps, ok ? "ok" : "fail");
3211
3268
  };
3212
3269
  const toolRef = str(cfg.toolRef);
@@ -3713,13 +3770,19 @@ ${collected}`);
3713
3770
  setExecutor = async (node, state, deps) => {
3714
3771
  const assignments = Array.isArray(node.config.assignments) ? node.config.assignments : [];
3715
3772
  let applied = 0;
3773
+ const rejected = [];
3716
3774
  for (const a of assignments) {
3717
3775
  if (!a || typeof a.name !== "string" || !a.name.trim()) continue;
3776
+ const name = a.name.trim();
3718
3777
  const value = interpolate(typeof a.value === "string" ? a.value : "", scopeFor(state, deps));
3719
- deps.processRt.captureField(a.name.trim(), value);
3720
- applied += 1;
3778
+ if (deps.processRt.captureField(name, value)) applied += 1;
3779
+ else rejected.push(name);
3721
3780
  }
3722
- deps.events.note("set.applied", { node: node.id, count: applied });
3781
+ deps.events.note("set.applied", {
3782
+ node: node.id,
3783
+ count: applied,
3784
+ ...rejected.length ? { rejected } : {}
3785
+ });
3723
3786
  return { kind: "advance" };
3724
3787
  };
3725
3788
  MAX_SUBFLOW_DEPTH = 3;
@@ -5262,12 +5325,15 @@ var CallsClient = class {
5262
5325
  );
5263
5326
  }
5264
5327
  /**
5265
- * Append a structured call event. Today the API accepts consultation
5266
- * lifecycle kinds only — see apps/api/src/routes/calls.ts AppendEventBody.
5328
+ * Append a structured call event — consultation lifecycle kinds, or the
5329
+ * operational vocabulary the call-detail Timeline renders between
5330
+ * transcript rows (tool.* / handoff.* / lookup.* / record.* / notify.*).
5331
+ * See apps/api/src/routes/calls.ts AppendEventBody for the accepted shapes.
5267
5332
  *
5268
5333
  * The agent SDK's built-in `ask_host` tool calls this on consultation start
5269
- * / end so the MCP host (via SSE) and dashboard see the "let me check" UX
5270
- * in real time.
5334
+ * / end; the handoff runtime and graph interpreter emit op events through it
5335
+ * so the operational story (dial → answered → briefed → dropped, tool calls)
5336
+ * is visible in the dashboard, not just the spoken transcript.
5271
5337
  */
5272
5338
  async appendEvent(callId, input) {
5273
5339
  await this.transport.request(
@@ -5686,6 +5752,18 @@ init_conditions();
5686
5752
  init_interpreter();
5687
5753
 
5688
5754
  // src/runtime/graph/events.ts
5755
+ var OP_EVENT_NOTE = /^(tool|handoff|lookup|record|notify)\./;
5756
+ function withOpEventFanout(events, notify) {
5757
+ return {
5758
+ enter: (nodeId, type) => events.enter(nodeId, type),
5759
+ traverse: (from, to, via) => events.traverse(from, to, via),
5760
+ note(kind, data) {
5761
+ events.note(kind, data);
5762
+ if (OP_EVENT_NOTE.test(kind)) notify(kind, data ?? {});
5763
+ },
5764
+ currentNodeId: () => events.currentNodeId()
5765
+ };
5766
+ }
5689
5767
  function createGraphEvents(processRt) {
5690
5768
  let current = null;
5691
5769
  return {
@@ -7299,6 +7377,125 @@ function createTextSessionAdapter(transport) {
7299
7377
  return { adapter, push, setState, close };
7300
7378
  }
7301
7379
 
7380
+ // src/flow-runtime.ts
7381
+ init_define_tool();
7382
+ function processSchemaToDefinition(schema) {
7383
+ const required = new Set(schema.completionGate.requiredFields);
7384
+ const collect = {};
7385
+ for (const f of schema.fields) {
7386
+ collect[f.name] = {
7387
+ type: f.type,
7388
+ required: required.has(f.name) || f.required,
7389
+ ...f.ask !== void 0 ? { ask: f.ask } : {},
7390
+ ...f.pattern !== void 0 ? { pattern: new RegExp(f.pattern) } : {},
7391
+ ...f.min !== void 0 ? { min: f.min } : {},
7392
+ ...f.max !== void 0 ? { max: f.max } : {},
7393
+ ...f.enum !== void 0 ? { enum: f.enum } : {}
7394
+ };
7395
+ }
7396
+ return {
7397
+ collect,
7398
+ completeWhen: "all-required-captured",
7399
+ ...schema.completionGate.backendAck !== void 0 ? { backendAck: schema.completionGate.backendAck } : {}
7400
+ };
7401
+ }
7402
+ var DEFAULT_FLOW_PROMPT = "You are a helpful voice assistant. Keep replies short and natural.";
7403
+ function toTriggerCondition(on) {
7404
+ return on.startsWith("signal:") ? on : new RegExp(on, "i");
7405
+ }
7406
+ function toTriggerAction(then, graphNodeIds) {
7407
+ if (then === "endCall" || then === "end") return "endCall";
7408
+ if (then === "handoff") return "handoff";
7409
+ if (then.startsWith("say:")) return { say: then.slice(4).trim() };
7410
+ if (graphNodeIds?.has(then)) return { goto: then };
7411
+ return "handoff";
7412
+ }
7413
+ function processSchemaToAgentParts(schema, opts = {}) {
7414
+ const process2 = processSchemaToDefinition(schema);
7415
+ const promptParts = [];
7416
+ if (opts.basePrompt && opts.basePrompt.trim()) promptParts.push(opts.basePrompt.trim());
7417
+ for (const p of schema.prompts ?? []) {
7418
+ if (p.text.trim()) promptParts.push(p.text.trim());
7419
+ }
7420
+ const prompt = promptParts.length > 0 ? promptParts.join("\n\n") : DEFAULT_FLOW_PROMPT;
7421
+ let triggers;
7422
+ if (schema.triggers && schema.triggers.length > 0) {
7423
+ triggers = {};
7424
+ for (const t of schema.triggers) {
7425
+ triggers[t.name] = {
7426
+ on: toTriggerCondition(t.on),
7427
+ then: toTriggerAction(t.then, opts.graphNodeIds),
7428
+ ...t.onlyWhile ? { onlyWhile: t.onlyWhile } : {}
7429
+ };
7430
+ }
7431
+ }
7432
+ const handoff = schema.handoff?.fallback ? {
7433
+ fallback: schema.handoff.fallback,
7434
+ ...schema.handoff.routes ? { routes: schema.handoff.routes } : {}
7435
+ } : void 0;
7436
+ const memory = schema.memory ? {
7437
+ ...schema.memory.scope ? { scope: schema.memory.scope } : {},
7438
+ ...schema.memory.retentionDays !== void 0 ? { retentionDays: schema.memory.retentionDays } : {},
7439
+ ...schema.memory.piiFields ? { piiFields: schema.memory.piiFields } : {}
7440
+ } : void 0;
7441
+ let tools;
7442
+ if (schema.tools && schema.tools.length > 0) {
7443
+ tools = {};
7444
+ for (const t of schema.tools) {
7445
+ if (t.auth === "connection") {
7446
+ tools[t.name] = {
7447
+ description: t.description,
7448
+ input: t.input,
7449
+ run: () => ({
7450
+ kind: "unavailable",
7451
+ reason: "connection-auth tools need the vault resolver (not wired yet)"
7452
+ })
7453
+ };
7454
+ continue;
7455
+ }
7456
+ Object.assign(
7457
+ tools,
7458
+ httpTool({
7459
+ name: t.name,
7460
+ description: t.description,
7461
+ input: t.input,
7462
+ url: t.url,
7463
+ method: t.method,
7464
+ ...t.headers ? { headers: t.headers } : {}
7465
+ })
7466
+ );
7467
+ }
7468
+ }
7469
+ let speech;
7470
+ if (schema.speech) {
7471
+ const i = schema.speech.interruption;
7472
+ const e = schema.speech.endpointing;
7473
+ const interruption = i ? {
7474
+ ...i.enabled !== void 0 ? { enabled: i.enabled } : {},
7475
+ ...i.minWords !== void 0 ? { minWords: i.minWords } : {},
7476
+ ...i.minDuration !== void 0 ? { minDuration: i.minDuration } : {}
7477
+ } : void 0;
7478
+ const endpointing = e ? {
7479
+ ...e.minDelay !== void 0 ? { minDelay: e.minDelay } : {},
7480
+ ...e.maxDelay !== void 0 ? { maxDelay: e.maxDelay } : {}
7481
+ } : void 0;
7482
+ speech = {
7483
+ ...interruption && Object.keys(interruption).length ? { interruption } : {},
7484
+ ...endpointing && Object.keys(endpointing).length ? { endpointing } : {}
7485
+ };
7486
+ if (Object.keys(speech).length === 0) speech = void 0;
7487
+ }
7488
+ return {
7489
+ prompt,
7490
+ process: process2,
7491
+ ...triggers ? { triggers } : {},
7492
+ ...handoff ? { handoff } : {},
7493
+ ...memory ? { memory } : {},
7494
+ ...tools ? { tools } : {},
7495
+ ...speech ? { speech } : {}
7496
+ };
7497
+ }
7498
+
7302
7499
  // src/runtime/text-session.ts
7303
7500
  function buildTextContext(adapter, call, processRt) {
7304
7501
  return {
@@ -7357,14 +7554,51 @@ function runTextSession(opts) {
7357
7554
  end: () => handle.close()
7358
7555
  };
7359
7556
  }
7557
+ function deriveCollectFromProgram(program) {
7558
+ const collect = {};
7559
+ for (const [nodeId, node] of Object.entries(program.nodes)) {
7560
+ const cfg = node.config;
7561
+ if (node.type === "ask") {
7562
+ const name = typeof cfg.slot === "string" && cfg.slot ? cfg.slot : nodeId;
7563
+ const type = cfg.type;
7564
+ collect[name] = {
7565
+ type: type === "number" || type === "boolean" || type === "datetime" || type === "string[]" ? type : "string",
7566
+ ...cfg.required !== false ? { required: true } : {},
7567
+ ...typeof cfg.ask === "string" ? { ask: cfg.ask } : {},
7568
+ ...typeof cfg.pattern === "string" ? { pattern: new RegExp(cfg.pattern) } : {}
7569
+ };
7570
+ }
7571
+ if (cfg.outputAs && typeof cfg.outputAs === "object") {
7572
+ for (const slot of Object.keys(cfg.outputAs)) {
7573
+ collect[slot] ??= { type: "string" };
7574
+ }
7575
+ }
7576
+ if (Array.isArray(cfg.assignments)) {
7577
+ for (const a of cfg.assignments) {
7578
+ if (a && typeof a.name === "string" && a.name.trim()) collect[a.name.trim()] ??= { type: "string" };
7579
+ }
7580
+ }
7581
+ }
7582
+ return { collect, completeWhen: "all-required-captured" };
7583
+ }
7584
+ function definitionFor(opts) {
7585
+ if (opts.schema) {
7586
+ try {
7587
+ return processSchemaToDefinition(opts.schema);
7588
+ } catch {
7589
+ }
7590
+ }
7591
+ return deriveCollectFromProgram(opts.program);
7592
+ }
7360
7593
  async function runTextTranscript(opts) {
7361
7594
  const replies = [];
7362
7595
  const trace7 = [];
7596
+ const processRt = opts.processRt ?? createProcessRuntime(definitionFor(opts));
7363
7597
  const session = runTextSession({
7364
7598
  program: opts.program,
7365
7599
  onAgentText: (t) => void replies.push(t),
7366
7600
  onTrace: (e) => void trace7.push(e),
7367
- ...opts.processRt ? { processRt: opts.processRt } : {},
7601
+ ...processRt ? { processRt } : {},
7368
7602
  ...opts.generate ? { generate: opts.generate } : {},
7369
7603
  ...opts.call ? { call: opts.call } : {},
7370
7604
  ...opts.maxSteps !== void 0 ? { maxSteps: opts.maxSteps } : {}
@@ -8124,105 +8358,6 @@ function computeEmptyRequired(view) {
8124
8358
  }
8125
8359
  return empty;
8126
8360
  }
8127
-
8128
- // src/flow-runtime.ts
8129
- init_define_tool();
8130
- function processSchemaToDefinition(schema) {
8131
- const required = new Set(schema.completionGate.requiredFields);
8132
- const collect = {};
8133
- for (const f of schema.fields) {
8134
- collect[f.name] = {
8135
- type: f.type,
8136
- required: required.has(f.name) || f.required,
8137
- ...f.ask !== void 0 ? { ask: f.ask } : {},
8138
- ...f.pattern !== void 0 ? { pattern: new RegExp(f.pattern) } : {},
8139
- ...f.min !== void 0 ? { min: f.min } : {},
8140
- ...f.max !== void 0 ? { max: f.max } : {},
8141
- ...f.enum !== void 0 ? { enum: f.enum } : {}
8142
- };
8143
- }
8144
- return {
8145
- collect,
8146
- completeWhen: "all-required-captured",
8147
- ...schema.completionGate.backendAck !== void 0 ? { backendAck: schema.completionGate.backendAck } : {}
8148
- };
8149
- }
8150
- var DEFAULT_FLOW_PROMPT = "You are a helpful voice assistant. Keep replies short and natural.";
8151
- function toTriggerCondition(on) {
8152
- return on.startsWith("signal:") ? on : new RegExp(on, "i");
8153
- }
8154
- function toTriggerAction(then, graphNodeIds) {
8155
- if (then === "endCall" || then === "end") return "endCall";
8156
- if (then === "handoff") return "handoff";
8157
- if (then.startsWith("say:")) return { say: then.slice(4).trim() };
8158
- if (graphNodeIds?.has(then)) return { goto: then };
8159
- return "handoff";
8160
- }
8161
- function processSchemaToAgentParts(schema, opts = {}) {
8162
- const process2 = processSchemaToDefinition(schema);
8163
- const promptParts = [];
8164
- if (opts.basePrompt && opts.basePrompt.trim()) promptParts.push(opts.basePrompt.trim());
8165
- for (const p of schema.prompts ?? []) {
8166
- if (p.text.trim()) promptParts.push(p.text.trim());
8167
- }
8168
- const prompt = promptParts.length > 0 ? promptParts.join("\n\n") : DEFAULT_FLOW_PROMPT;
8169
- let triggers;
8170
- if (schema.triggers && schema.triggers.length > 0) {
8171
- triggers = {};
8172
- for (const t of schema.triggers) {
8173
- triggers[t.name] = {
8174
- on: toTriggerCondition(t.on),
8175
- then: toTriggerAction(t.then, opts.graphNodeIds),
8176
- ...t.onlyWhile ? { onlyWhile: t.onlyWhile } : {}
8177
- };
8178
- }
8179
- }
8180
- const handoff = schema.handoff?.fallback ? {
8181
- fallback: schema.handoff.fallback,
8182
- ...schema.handoff.routes ? { routes: schema.handoff.routes } : {}
8183
- } : void 0;
8184
- const memory = schema.memory ? {
8185
- ...schema.memory.scope ? { scope: schema.memory.scope } : {},
8186
- ...schema.memory.retentionDays !== void 0 ? { retentionDays: schema.memory.retentionDays } : {},
8187
- ...schema.memory.piiFields ? { piiFields: schema.memory.piiFields } : {}
8188
- } : void 0;
8189
- let tools;
8190
- if (schema.tools && schema.tools.length > 0) {
8191
- tools = {};
8192
- for (const t of schema.tools) {
8193
- if (t.auth === "connection") {
8194
- tools[t.name] = {
8195
- description: t.description,
8196
- input: t.input,
8197
- run: () => ({
8198
- kind: "unavailable",
8199
- reason: "connection-auth tools need the vault resolver (not wired yet)"
8200
- })
8201
- };
8202
- continue;
8203
- }
8204
- Object.assign(
8205
- tools,
8206
- httpTool({
8207
- name: t.name,
8208
- description: t.description,
8209
- input: t.input,
8210
- url: t.url,
8211
- method: t.method,
8212
- ...t.headers ? { headers: t.headers } : {}
8213
- })
8214
- );
8215
- }
8216
- }
8217
- return {
8218
- prompt,
8219
- process: process2,
8220
- ...triggers ? { triggers } : {},
8221
- ...handoff ? { handoff } : {},
8222
- ...memory ? { memory } : {},
8223
- ...tools ? { tools } : {}
8224
- };
8225
- }
8226
8361
  var BrainConfigError = class extends Error {
8227
8362
  constructor(message) {
8228
8363
  super(message);
@@ -10877,29 +11012,59 @@ async function executeHandoff(args, config, deps) {
10877
11012
  const briefing = args.briefing ?? await buildBriefing(config, deps) ?? `Handoff to ${args.to}`;
10878
11013
  const mode = args.mode ?? config?.mode ?? "warm";
10879
11014
  const sipOps = deps.sipOps ?? buildDefaultSipOps();
11015
+ const notify = deps.notify ?? (() => {
11016
+ });
10880
11017
  if (mode === "warm" && sipOps && sipOps.canConference) {
10881
11018
  try {
10882
11019
  await sayFixed(deps, "One moment \u2014 I'm bringing our on-call engineer onto the line now. Stay with me.");
11020
+ notify("handoff.dialing", { to: args.to, mode: "warm" });
10883
11021
  await sipOps.addParticipant(deps.roomName, normalizeSipTarget(args.to));
11022
+ notify("handoff.answered", { to: args.to });
10884
11023
  await sayFixed(deps, briefing);
11024
+ notify("handoff.briefed", { to: args.to, briefing });
10885
11025
  await sayFixed(deps, "You're both connected now \u2014 I'll drop off.");
10886
11026
  await quiesce(deps);
10887
11027
  await deps.disconnect();
11028
+ notify("handoff.agent_dropped", { to: args.to });
10888
11029
  return { ok: true, via: "conference", target: args.to };
10889
11030
  } catch (err) {
10890
11031
  console.error("[handoff] conference bridge failed, falling back to REFER", err);
11032
+ notify("handoff.failed", {
11033
+ to: args.to,
11034
+ via: "conference",
11035
+ error: err instanceof Error ? err.message : String(err)
11036
+ });
10891
11037
  }
10892
11038
  }
10893
- if (sipOps) {
11039
+ if (sipOps && isSipParticipant(deps.callerIdentity)) {
10894
11040
  if (mode === "warm") {
10895
11041
  await sayFixed(deps, "I'm transferring you now \u2014 one moment.");
10896
11042
  }
10897
11043
  try {
10898
11044
  await sipOps.refer(deps.roomName, deps.callerIdentity, normalizeSipTarget(args.to));
11045
+ notify("handoff.refer", { to: args.to });
10899
11046
  return { ok: true, via: "sip", target: args.to };
10900
11047
  } catch (err) {
10901
11048
  console.error("[handoff] SIP REFER failed, falling back to disconnect", err);
11049
+ notify("handoff.failed", {
11050
+ to: args.to,
11051
+ via: "refer",
11052
+ error: err instanceof Error ? err.message : String(err)
11053
+ });
10902
11054
  }
11055
+ } else if (sipOps) {
11056
+ console.warn("[handoff] caller has no SIP leg; cannot transfer this channel", {
11057
+ callerIdentity: deps.callerIdentity,
11058
+ conferenceAvailable: sipOps.canConference
11059
+ });
11060
+ notify("handoff.unavailable", {
11061
+ to: args.to,
11062
+ reason: sipOps.canConference ? "caller has no SIP leg (web/playground call)" : "caller has no SIP leg and no outbound trunk is configured for conferencing"
11063
+ });
11064
+ await sayFixed(
11065
+ deps,
11066
+ "I'm not able to connect you to a person on this channel \u2014 please call our phone line and we'll pick up right where we left off."
11067
+ );
10903
11068
  }
10904
11069
  try {
10905
11070
  await deps.disconnect();
@@ -10949,6 +11114,9 @@ function buildDefaultSipOps() {
10949
11114
  }
10950
11115
  };
10951
11116
  }
11117
+ function isSipParticipant(identity) {
11118
+ return !/^(user[-_]|web[-_]|playground[-_]|vl-client)/i.test(identity.trim());
11119
+ }
10952
11120
  function normalizeSipTarget(target) {
10953
11121
  if (target.startsWith("sip:") || target.startsWith("tel:")) return target;
10954
11122
  return `tel:${target}`;
@@ -12645,7 +12813,11 @@ var Agent = class {
12645
12813
  ...parts.triggers ? { triggers: parts.triggers } : {},
12646
12814
  ...parts.handoff ? { handoff: parts.handoff } : {},
12647
12815
  ...parts.memory ? { memory: parts.memory } : {},
12648
- ...parts.tools ? { tools: { ...effectiveConfig.tools ?? {}, ...parts.tools } } : {}
12816
+ ...parts.tools ? { tools: { ...effectiveConfig.tools ?? {}, ...parts.tools } } : {},
12817
+ // Flow-authored turn-taking (schema.speech). Merged over any
12818
+ // worker-level speech config; applySpeechTurnHandling reads this
12819
+ // off effectiveConfig at session build.
12820
+ ...parts.speech ? { speech: { ...effectiveConfig.speech ?? {}, ...parts.speech } } : {}
12649
12821
  };
12650
12822
  console.log("[agent] booted from flow", {
12651
12823
  agentId: flowAgentId,
@@ -12706,7 +12878,13 @@ var Agent = class {
12706
12878
  const processRt = createProcessRuntime(effectiveConfig.process);
12707
12879
  seedCallPrefill(processRt, dispatchMdEarly);
12708
12880
  await seedProjectVariables(processRt);
12709
- const graphEvents = graphMode ? createGraphEvents(processRt) : null;
12881
+ let opEventCallId = null;
12882
+ const emitOpEvent = (kind, payload) => {
12883
+ if (!sdkClient || !opEventCallId) return;
12884
+ void sdkClient.calls.appendEvent(opEventCallId, { kind, payload }).catch(() => {
12885
+ });
12886
+ };
12887
+ const graphEvents = graphMode ? withOpEventFanout(createGraphEvents(processRt), emitOpEvent) : null;
12710
12888
  const isOutbound = stringValue(dispatchMdEarly["direction"]) === "outbound";
12711
12889
  const perCallAmd = stringValue(dispatchMdEarly["amd"]);
12712
12890
  const amdMode = perCallAmd === "off" || perCallAmd === "detect" || perCallAmd === "detectMessageEnd" ? perCallAmd : effectiveConfig.outbound?.amd ?? "off";
@@ -12798,6 +12976,7 @@ var Agent = class {
12798
12976
  });
12799
12977
  }
12800
12978
  }
12979
+ opEventCallId = consultationCallId;
12801
12980
  let agentSpeechState = "initializing";
12802
12981
  const idleWaiters = /* @__PURE__ */ new Set();
12803
12982
  const waitUntilAgentIdle = async (timeoutMs) => {
@@ -12981,7 +13160,8 @@ ${callIntent}`
12981
13160
  }
12982
13161
  },
12983
13162
  getProcessData: () => processRt.getData(),
12984
- transcript
13163
+ transcript,
13164
+ notify: (kind, payload) => emitOpEvent(kind, payload)
12985
13165
  });
12986
13166
  const triggersRt = createTriggersRuntime(effectiveConfig.triggers, handoffRt);
12987
13167
  const audioExperience = new AudioExperiencePrimitive();
@@ -1,3 +1,3 @@
1
- export { a6 as GraphTraceEvent, q as RunTextSessionOptions, r as RunTextTranscriptOptions, T as TextSession, O as TextTranscriptResult, a3 as runTextSession, a4 as runTextTranscript } from '../text-session-B8-jhQzv.js';
1
+ export { aa as GraphTraceEvent, u as RunTextSessionOptions, v as RunTextTranscriptOptions, V as TextSession, W as TextTranscriptResult, a7 as runTextSession, a8 as runTextTranscript } from '../text-session-D1j8nQac.js';
2
2
  import '@livekit/agents';
3
3
  import 'zod';