@voicelayer/sdk 0.1.6 → 0.1.8

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
  });
@@ -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,18 @@ ${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.processRt.captureField(slot, value);
3257
+ deps.events.note("tool.output_captured", { node: node.id, slot });
3258
+ }
3259
+ }
3260
+ }
3210
3261
  return routeByPort(node, deps, ok ? "ok" : "fail");
3211
3262
  };
3212
3263
  const toolRef = str(cfg.toolRef);
@@ -5262,12 +5313,15 @@ var CallsClient = class {
5262
5313
  );
5263
5314
  }
5264
5315
  /**
5265
- * Append a structured call event. Today the API accepts consultation
5266
- * lifecycle kinds only — see apps/api/src/routes/calls.ts AppendEventBody.
5316
+ * Append a structured call event — consultation lifecycle kinds, or the
5317
+ * operational vocabulary the call-detail Timeline renders between
5318
+ * transcript rows (tool.* / handoff.* / lookup.* / record.* / notify.*).
5319
+ * See apps/api/src/routes/calls.ts AppendEventBody for the accepted shapes.
5267
5320
  *
5268
5321
  * 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.
5322
+ * / end; the handoff runtime and graph interpreter emit op events through it
5323
+ * so the operational story (dial → answered → briefed → dropped, tool calls)
5324
+ * is visible in the dashboard, not just the spoken transcript.
5271
5325
  */
5272
5326
  async appendEvent(callId, input) {
5273
5327
  await this.transport.request(
@@ -5686,6 +5740,18 @@ init_conditions();
5686
5740
  init_interpreter();
5687
5741
 
5688
5742
  // src/runtime/graph/events.ts
5743
+ var OP_EVENT_NOTE = /^(tool|handoff|lookup|record|notify)\./;
5744
+ function withOpEventFanout(events, notify) {
5745
+ return {
5746
+ enter: (nodeId, type) => events.enter(nodeId, type),
5747
+ traverse: (from, to, via) => events.traverse(from, to, via),
5748
+ note(kind, data) {
5749
+ events.note(kind, data);
5750
+ if (OP_EVENT_NOTE.test(kind)) notify(kind, data ?? {});
5751
+ },
5752
+ currentNodeId: () => events.currentNodeId()
5753
+ };
5754
+ }
5689
5755
  function createGraphEvents(processRt) {
5690
5756
  let current = null;
5691
5757
  return {
@@ -8214,13 +8280,33 @@ function processSchemaToAgentParts(schema, opts = {}) {
8214
8280
  );
8215
8281
  }
8216
8282
  }
8283
+ let speech;
8284
+ if (schema.speech) {
8285
+ const i = schema.speech.interruption;
8286
+ const e = schema.speech.endpointing;
8287
+ const interruption = i ? {
8288
+ ...i.enabled !== void 0 ? { enabled: i.enabled } : {},
8289
+ ...i.minWords !== void 0 ? { minWords: i.minWords } : {},
8290
+ ...i.minDuration !== void 0 ? { minDuration: i.minDuration } : {}
8291
+ } : void 0;
8292
+ const endpointing = e ? {
8293
+ ...e.minDelay !== void 0 ? { minDelay: e.minDelay } : {},
8294
+ ...e.maxDelay !== void 0 ? { maxDelay: e.maxDelay } : {}
8295
+ } : void 0;
8296
+ speech = {
8297
+ ...interruption && Object.keys(interruption).length ? { interruption } : {},
8298
+ ...endpointing && Object.keys(endpointing).length ? { endpointing } : {}
8299
+ };
8300
+ if (Object.keys(speech).length === 0) speech = void 0;
8301
+ }
8217
8302
  return {
8218
8303
  prompt,
8219
8304
  process: process2,
8220
8305
  ...triggers ? { triggers } : {},
8221
8306
  ...handoff ? { handoff } : {},
8222
8307
  ...memory ? { memory } : {},
8223
- ...tools ? { tools } : {}
8308
+ ...tools ? { tools } : {},
8309
+ ...speech ? { speech } : {}
8224
8310
  };
8225
8311
  }
8226
8312
  var BrainConfigError = class extends Error {
@@ -10858,40 +10944,64 @@ function resolveRoute(config, key) {
10858
10944
  if (!config) return void 0;
10859
10945
  return config.routes?.[key] ?? config.fallback;
10860
10946
  }
10861
- async function announce(deps, instructions) {
10947
+ async function sayFixed(deps, text) {
10862
10948
  try {
10863
- await deps.session.generateReply({ instructions });
10949
+ const session = deps.session;
10950
+ const handle = session.say?.(text, { allowInterruptions: false });
10951
+ if (handle?.waitForPlayout) await handle.waitForPlayout();
10864
10952
  } catch (err) {
10865
10953
  console.warn("[handoff] announcement failed", err);
10866
10954
  }
10867
10955
  }
10956
+ async function quiesce(deps) {
10957
+ try {
10958
+ await deps.session.interrupt?.();
10959
+ } catch {
10960
+ }
10961
+ }
10868
10962
  async function executeHandoff(args, config, deps) {
10869
10963
  const briefing = args.briefing ?? await buildBriefing(config, deps) ?? `Handoff to ${args.to}`;
10870
10964
  const mode = args.mode ?? config?.mode ?? "warm";
10871
10965
  const sipOps = deps.sipOps ?? buildDefaultSipOps();
10966
+ const notify = deps.notify ?? (() => {
10967
+ });
10872
10968
  if (mode === "warm" && sipOps && sipOps.canConference) {
10873
10969
  try {
10874
- await announce(deps, "Tell the caller you are bringing a teammate onto the line now. One short sentence.");
10970
+ await sayFixed(deps, "One moment \u2014 I'm bringing our on-call engineer onto the line now. Stay with me.");
10971
+ notify("handoff.dialing", { to: args.to, mode: "warm" });
10875
10972
  await sipOps.addParticipant(deps.roomName, normalizeSipTarget(args.to));
10876
- await announce(
10877
- deps,
10878
- `Briefly brief the teammate who just joined, then hand off. Context: ${briefing}`
10879
- );
10973
+ notify("handoff.answered", { to: args.to });
10974
+ await sayFixed(deps, briefing);
10975
+ notify("handoff.briefed", { to: args.to, briefing });
10976
+ await sayFixed(deps, "You're both connected now \u2014 I'll drop off.");
10977
+ await quiesce(deps);
10880
10978
  await deps.disconnect();
10979
+ notify("handoff.agent_dropped", { to: args.to });
10881
10980
  return { ok: true, via: "conference", target: args.to };
10882
10981
  } catch (err) {
10883
10982
  console.error("[handoff] conference bridge failed, falling back to REFER", err);
10983
+ notify("handoff.failed", {
10984
+ to: args.to,
10985
+ via: "conference",
10986
+ error: err instanceof Error ? err.message : String(err)
10987
+ });
10884
10988
  }
10885
10989
  }
10886
10990
  if (sipOps) {
10887
10991
  if (mode === "warm") {
10888
- await announce(deps, "Tell the caller you are transferring them to a teammate now. One short sentence.");
10992
+ await sayFixed(deps, "I'm transferring you now \u2014 one moment.");
10889
10993
  }
10890
10994
  try {
10891
10995
  await sipOps.refer(deps.roomName, deps.callerIdentity, normalizeSipTarget(args.to));
10996
+ notify("handoff.refer", { to: args.to });
10892
10997
  return { ok: true, via: "sip", target: args.to };
10893
10998
  } catch (err) {
10894
10999
  console.error("[handoff] SIP REFER failed, falling back to disconnect", err);
11000
+ notify("handoff.failed", {
11001
+ to: args.to,
11002
+ via: "refer",
11003
+ error: err instanceof Error ? err.message : String(err)
11004
+ });
10895
11005
  }
10896
11006
  }
10897
11007
  try {
@@ -10935,7 +11045,8 @@ function buildDefaultSipOps() {
10935
11045
  const identity = `xfer-${target.replace(/[^0-9+]/g, "")}`;
10936
11046
  await client.createSipParticipant(trunkId, target.replace(/^tel:/, ""), roomName, {
10937
11047
  participantIdentity: identity,
10938
- playDialtone: false
11048
+ playDialtone: false,
11049
+ waitUntilAnswered: true
10939
11050
  });
10940
11051
  return { identity };
10941
11052
  }
@@ -12637,7 +12748,11 @@ var Agent = class {
12637
12748
  ...parts.triggers ? { triggers: parts.triggers } : {},
12638
12749
  ...parts.handoff ? { handoff: parts.handoff } : {},
12639
12750
  ...parts.memory ? { memory: parts.memory } : {},
12640
- ...parts.tools ? { tools: { ...effectiveConfig.tools ?? {}, ...parts.tools } } : {}
12751
+ ...parts.tools ? { tools: { ...effectiveConfig.tools ?? {}, ...parts.tools } } : {},
12752
+ // Flow-authored turn-taking (schema.speech). Merged over any
12753
+ // worker-level speech config; applySpeechTurnHandling reads this
12754
+ // off effectiveConfig at session build.
12755
+ ...parts.speech ? { speech: { ...effectiveConfig.speech ?? {}, ...parts.speech } } : {}
12641
12756
  };
12642
12757
  console.log("[agent] booted from flow", {
12643
12758
  agentId: flowAgentId,
@@ -12698,7 +12813,13 @@ var Agent = class {
12698
12813
  const processRt = createProcessRuntime(effectiveConfig.process);
12699
12814
  seedCallPrefill(processRt, dispatchMdEarly);
12700
12815
  await seedProjectVariables(processRt);
12701
- const graphEvents = graphMode ? createGraphEvents(processRt) : null;
12816
+ let opEventCallId = null;
12817
+ const emitOpEvent = (kind, payload) => {
12818
+ if (!sdkClient || !opEventCallId) return;
12819
+ void sdkClient.calls.appendEvent(opEventCallId, { kind, payload }).catch(() => {
12820
+ });
12821
+ };
12822
+ const graphEvents = graphMode ? withOpEventFanout(createGraphEvents(processRt), emitOpEvent) : null;
12702
12823
  const isOutbound = stringValue(dispatchMdEarly["direction"]) === "outbound";
12703
12824
  const perCallAmd = stringValue(dispatchMdEarly["amd"]);
12704
12825
  const amdMode = perCallAmd === "off" || perCallAmd === "detect" || perCallAmd === "detectMessageEnd" ? perCallAmd : effectiveConfig.outbound?.amd ?? "off";
@@ -12790,6 +12911,7 @@ var Agent = class {
12790
12911
  });
12791
12912
  }
12792
12913
  }
12914
+ opEventCallId = consultationCallId;
12793
12915
  let agentSpeechState = "initializing";
12794
12916
  const idleWaiters = /* @__PURE__ */ new Set();
12795
12917
  const waitUntilAgentIdle = async (timeoutMs) => {
@@ -12973,7 +13095,8 @@ ${callIntent}`
12973
13095
  }
12974
13096
  },
12975
13097
  getProcessData: () => processRt.getData(),
12976
- transcript
13098
+ transcript,
13099
+ notify: (kind, payload) => emitOpEvent(kind, payload)
12977
13100
  });
12978
13101
  const triggersRt = createTriggersRuntime(effectiveConfig.triggers, handoffRt);
12979
13102
  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 { a6 as GraphTraceEvent, q as RunTextSessionOptions, r as RunTextTranscriptOptions, T as TextSession, O as TextTranscriptResult, a3 as runTextSession, a4 as runTextTranscript } from '../text-session-BoUmDEX9.js';
2
2
  import '@livekit/agents';
3
3
  import 'zod';
@@ -97,6 +97,9 @@ var init_flow = __esm({
97
97
  "llm",
98
98
  "rag",
99
99
  "memory",
100
+ // Call-wide voice/turn-taking capability (barge-in + endpointing tuning);
101
+ // managed in the Capabilities panel like `memory`, never a graph step.
102
+ "speech",
100
103
  "classify",
101
104
  // Agentic router: per-turn, selects 1-of-N skills by their plain-language
102
105
  // descriptions and takes that port (the resident routing surface / hub).
@@ -295,6 +298,19 @@ var init_agents = __esm({
295
298
  }).optional(),
296
299
  // Tool capabilities compiled from tool nodes (maps to AgentConfig.tools).
297
300
  tools: z.array(ProcessToolDTO).max(32).optional(),
301
+ // Voice turn-taking compiled from the flow's speech capability node (maps to
302
+ // AgentConfig.speech → applySpeechTurnHandling). Absent = platform defaults.
303
+ speech: z.object({
304
+ interruption: z.object({
305
+ enabled: z.boolean().optional(),
306
+ minWords: z.number().int().min(0).max(10).optional(),
307
+ minDuration: z.number().int().min(0).max(5e3).optional()
308
+ }).optional(),
309
+ endpointing: z.object({
310
+ minDelay: z.number().int().min(0).max(5e3).optional(),
311
+ maxDelay: z.number().int().min(0).max(15e3).optional()
312
+ }).optional()
313
+ }).optional(),
298
314
  // Graph-preserving compiled program. When present and the flow-runtime graph
299
315
  // mode is enabled, the worker executes this node-graph deterministically
300
316
  // (the interpreter) instead of the flat goals+rails form above. Embedded here
@@ -452,7 +468,7 @@ var init_consultation = __esm({
452
468
  });
453
469
  }
454
470
  });
455
- var CallEventKind, SayCommand, HangupCommand, DtmfCommand, InstructCommand;
471
+ var CallEventKind, OP_CALL_EVENT_KIND, OpCallEventKind, SayCommand, HangupCommand, DtmfCommand, InstructCommand;
456
472
  var init_call_events = __esm({
457
473
  "../contracts/src/call-events.ts"() {
458
474
  init_consultation();
@@ -477,6 +493,8 @@ var init_call_events = __esm({
477
493
  "dtmf.received",
478
494
  "dtmf.sent"
479
495
  ]);
496
+ OP_CALL_EVENT_KIND = /^(tool|handoff|lookup|record|notify)\.[a-z0-9_]{1,48}(\.[a-z0-9_]{1,48})?$/;
497
+ OpCallEventKind = z.string().regex(OP_CALL_EVENT_KIND);
480
498
  z.object({
481
499
  reason: z.string().optional(),
482
500
  durationMs: z.number().int().min(0).optional(),
@@ -514,7 +532,8 @@ var init_call_events = __esm({
514
532
  seq: z.number().int().min(0),
515
533
  callId: z.string().uuid(),
516
534
  projectId: z.string().min(1),
517
- kind: CallEventKind,
535
+ // Closed lifecycle kinds OR the namespaced operational vocabulary.
536
+ kind: z.union([CallEventKind, OpCallEventKind]),
518
537
  payload: z.unknown(),
519
538
  ts: z.string().datetime()
520
539
  });
@@ -2645,7 +2664,7 @@ var init_executors = __esm({
2645
2664
  if (msg) await speak(interpolate(msg, scopeFor(state, deps)), deps);
2646
2665
  return { kind: "end" };
2647
2666
  };
2648
- handoffExecutor = async (node, _state, deps) => {
2667
+ handoffExecutor = async (node, state, deps) => {
2649
2668
  const cfg = node.config;
2650
2669
  const target = str(cfg.to) || str(cfg.fallback);
2651
2670
  if (!target) {
@@ -2654,9 +2673,14 @@ var init_executors = __esm({
2654
2673
  }
2655
2674
  const reason = str(cfg.reason) || `flow:${node.id}`;
2656
2675
  const mode = cfg.mode === "warm" || cfg.mode === "cold" ? cfg.mode : void 0;
2676
+ const briefing = str(cfg.briefing) ? interpolate(str(cfg.briefing), scopeFor(state, deps)) : void 0;
2657
2677
  deps.events.note("handoff.start", { node: node.id, to: target, ...mode ? { mode } : {} });
2658
2678
  try {
2659
- await deps.ctx.handoff(target, { reason, ...mode ? { mode } : {} });
2679
+ await deps.ctx.handoff(target, {
2680
+ reason,
2681
+ ...mode ? { mode } : {},
2682
+ ...briefing ? { briefing } : {}
2683
+ });
2660
2684
  } catch (err) {
2661
2685
  deps.events.note("handoff.failed", {
2662
2686
  node: node.id,
@@ -2710,7 +2734,18 @@ ${collected}`);
2710
2734
  const cfg = node.config;
2711
2735
  const name = str(cfg.name) || node.id;
2712
2736
  const routeByOutcome = () => {
2713
- const ok = state.toolResults[node.id]?.ok === true;
2737
+ const result = state.toolResults[node.id];
2738
+ const ok = result?.ok === true;
2739
+ if (ok && cfg.outputAs && typeof cfg.outputAs === "object") {
2740
+ for (const [slot, path] of Object.entries(cfg.outputAs)) {
2741
+ if (typeof path !== "string" || !path.trim()) continue;
2742
+ const value = resolvePath(result, path.trim());
2743
+ if (value !== void 0 && value !== null) {
2744
+ deps.processRt.captureField(slot, value);
2745
+ deps.events.note("tool.output_captured", { node: node.id, slot });
2746
+ }
2747
+ }
2748
+ }
2714
2749
  return routeByPort(node, deps, ok ? "ok" : "fail");
2715
2750
  };
2716
2751
  const toolRef = str(cfg.toolRef);
@@ -90,7 +90,7 @@ declare const FlowProgram: z.ZodObject<{
90
90
  entry: z.ZodArray<z.ZodString, "many">;
91
91
  nodes: z.ZodRecord<z.ZodString, z.ZodObject<{
92
92
  id: z.ZodString;
93
- type: z.ZodEnum<["start", "say", "ask", "confirm", "tool", "decision", "handoff", "end", "trigger", "gate", "playbook", "menu", "code", "set", "subflow", "llm", "rag", "memory", "classify", "router", "note"]>;
93
+ type: z.ZodEnum<["start", "say", "ask", "confirm", "tool", "decision", "handoff", "end", "trigger", "gate", "playbook", "menu", "code", "set", "subflow", "llm", "rag", "memory", "speech", "classify", "router", "note"]>;
94
94
  config: z.ZodRecord<z.ZodString, z.ZodUnknown>;
95
95
  out: z.ZodArray<z.ZodObject<{
96
96
  to: z.ZodString;
@@ -99,34 +99,34 @@ declare const FlowProgram: z.ZodObject<{
99
99
  label: z.ZodOptional<z.ZodString>;
100
100
  }, "strip", z.ZodTypeAny, {
101
101
  to: string;
102
+ condition?: string | undefined;
102
103
  sourceHandle?: string | undefined;
103
104
  label?: string | undefined;
104
- condition?: string | undefined;
105
105
  }, {
106
106
  to: string;
107
+ condition?: string | undefined;
107
108
  sourceHandle?: string | undefined;
108
109
  label?: string | undefined;
109
- condition?: string | undefined;
110
110
  }>, "many">;
111
111
  }, "strip", z.ZodTypeAny, {
112
- type: "code" | "start" | "say" | "ask" | "confirm" | "tool" | "decision" | "handoff" | "end" | "trigger" | "gate" | "playbook" | "menu" | "set" | "subflow" | "llm" | "rag" | "memory" | "classify" | "router" | "note";
112
+ type: "code" | "set" | "start" | "say" | "ask" | "confirm" | "tool" | "decision" | "handoff" | "end" | "trigger" | "gate" | "playbook" | "menu" | "subflow" | "llm" | "rag" | "memory" | "speech" | "classify" | "router" | "note";
113
113
  id: string;
114
114
  config: Record<string, unknown>;
115
115
  out: {
116
116
  to: string;
117
+ condition?: string | undefined;
117
118
  sourceHandle?: string | undefined;
118
119
  label?: string | undefined;
119
- condition?: string | undefined;
120
120
  }[];
121
121
  }, {
122
- type: "code" | "start" | "say" | "ask" | "confirm" | "tool" | "decision" | "handoff" | "end" | "trigger" | "gate" | "playbook" | "menu" | "set" | "subflow" | "llm" | "rag" | "memory" | "classify" | "router" | "note";
122
+ type: "code" | "set" | "start" | "say" | "ask" | "confirm" | "tool" | "decision" | "handoff" | "end" | "trigger" | "gate" | "playbook" | "menu" | "subflow" | "llm" | "rag" | "memory" | "speech" | "classify" | "router" | "note";
123
123
  id: string;
124
124
  config: Record<string, unknown>;
125
125
  out: {
126
126
  to: string;
127
+ condition?: string | undefined;
127
128
  sourceHandle?: string | undefined;
128
129
  label?: string | undefined;
129
- condition?: string | undefined;
130
130
  }[];
131
131
  }>>;
132
132
  completionGate: z.ZodObject<{
@@ -152,18 +152,18 @@ declare const FlowProgram: z.ZodObject<{
152
152
  }, "strip", z.ZodTypeAny, {
153
153
  v: 1;
154
154
  id: string;
155
+ entry: string[];
155
156
  nodes: Record<string, {
156
- type: "code" | "start" | "say" | "ask" | "confirm" | "tool" | "decision" | "handoff" | "end" | "trigger" | "gate" | "playbook" | "menu" | "set" | "subflow" | "llm" | "rag" | "memory" | "classify" | "router" | "note";
157
+ type: "code" | "set" | "start" | "say" | "ask" | "confirm" | "tool" | "decision" | "handoff" | "end" | "trigger" | "gate" | "playbook" | "menu" | "subflow" | "llm" | "rag" | "memory" | "speech" | "classify" | "router" | "note";
157
158
  id: string;
158
159
  config: Record<string, unknown>;
159
160
  out: {
160
161
  to: string;
162
+ condition?: string | undefined;
161
163
  sourceHandle?: string | undefined;
162
164
  label?: string | undefined;
163
- condition?: string | undefined;
164
165
  }[];
165
166
  }>;
166
- entry: string[];
167
167
  completionGate: {
168
168
  requiredFields: string[];
169
169
  };
@@ -175,18 +175,18 @@ declare const FlowProgram: z.ZodObject<{
175
175
  }, {
176
176
  v: 1;
177
177
  id: string;
178
+ entry: string[];
178
179
  nodes: Record<string, {
179
- type: "code" | "start" | "say" | "ask" | "confirm" | "tool" | "decision" | "handoff" | "end" | "trigger" | "gate" | "playbook" | "menu" | "set" | "subflow" | "llm" | "rag" | "memory" | "classify" | "router" | "note";
180
+ type: "code" | "set" | "start" | "say" | "ask" | "confirm" | "tool" | "decision" | "handoff" | "end" | "trigger" | "gate" | "playbook" | "menu" | "subflow" | "llm" | "rag" | "memory" | "speech" | "classify" | "router" | "note";
180
181
  id: string;
181
182
  config: Record<string, unknown>;
182
183
  out: {
183
184
  to: string;
185
+ condition?: string | undefined;
184
186
  sourceHandle?: string | undefined;
185
187
  label?: string | undefined;
186
- condition?: string | undefined;
187
188
  }[];
188
189
  }>;
189
- entry: string[];
190
190
  completionGate: {
191
191
  requiredFields: string[];
192
192
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voicelayer/sdk",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
4
4
  "description": "Official SDK for VoiceLayer — define, run, and orchestrate voice AI agents on the VoiceLayer control plane.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "VoiceLayer",
@@ -82,13 +82,13 @@
82
82
  "@voicelayer/contracts": "0.1.0",
83
83
  "@voicelayer/observability": "0.1.0",
84
84
  "@voicelayer/primitive-audio-experience": "0.1.0",
85
- "@voicelayer/primitive-process-schema": "0.1.0",
86
85
  "@voicelayer/primitive-audio-routing": "0.1.0",
86
+ "@voicelayer/primitive-process-schema": "0.1.0",
87
+ "@voicelayer/primitive-security": "0.1.0",
87
88
  "@voicelayer/agents-plugin-deepgram": "1.0.0",
88
89
  "@voicelayer/agents-plugin-livekit": "1.0.0",
89
- "@voicelayer/primitive-security": "0.1.0",
90
- "@voicelayer/agents-plugin-openai": "1.0.0",
91
- "@voicelayer/agents-plugin-silero": "1.0.0"
90
+ "@voicelayer/agents-plugin-silero": "1.0.0",
91
+ "@voicelayer/agents-plugin-openai": "1.0.0"
92
92
  },
93
93
  "scripts": {
94
94
  "typecheck": "tsc -b --pretty false",