@voicelayer/sdk 0.1.11 → 0.2.0

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.
@@ -1,5 +1,9 @@
1
1
  import { z } from 'zod';
2
2
  import OpenAI from 'openai';
3
+ import { AsyncLocalStorage } from 'async_hooks';
4
+ import 'dns';
5
+ import 'net';
6
+ import '@opentelemetry/api';
3
7
  import 'quickjs-emscripten';
4
8
  import { setup, createActor } from 'xstate';
5
9
  import { randomUUID } from 'crypto';
@@ -14,6 +18,18 @@ var __export = (target, all) => {
14
18
  __defProp(target, name, { get: all[name], enumerable: true });
15
19
  };
16
20
 
21
+ // src/runtime/graph/log.ts
22
+ function graphLog(event, fields) {
23
+ console.log(`[graph] ${event}`, { ...fields, t: Date.now() });
24
+ }
25
+ function graphWarn(event, fields) {
26
+ console.warn(`[graph] ${event}`, { ...fields, t: Date.now() });
27
+ }
28
+ var init_log = __esm({
29
+ "src/runtime/graph/log.ts"() {
30
+ }
31
+ });
32
+
17
33
  // src/runtime/graph/graph-model.ts
18
34
  function buildGraphModel(program) {
19
35
  return {
@@ -43,11 +59,100 @@ var init_graph_model = __esm({
43
59
  }
44
60
  });
45
61
 
62
+ // ../contracts/src/effect-stack.ts
63
+ var EffectStack;
64
+ var init_effect_stack = __esm({
65
+ "../contracts/src/effect-stack.ts"() {
66
+ EffectStack = class {
67
+ #disposers = [];
68
+ #disposed = false;
69
+ #draining = false;
70
+ #drain = null;
71
+ #onError;
72
+ constructor(options = {}) {
73
+ this.#onError = options.onDisposeError ?? ((error) => {
74
+ console.error("[EffectStack] disposer threw during teardown", error);
75
+ });
76
+ }
77
+ /**
78
+ * Register an acquire's inverse. Returns a handle that reverts THIS effect
79
+ * early (at most once). The handle is a no-op after it has fired and after
80
+ * dispose() has already drained the disposer.
81
+ */
82
+ defer(disposer) {
83
+ if (this.#disposed && !this.#draining) {
84
+ const ran = this.#run(disposer);
85
+ return async () => {
86
+ await ran;
87
+ };
88
+ }
89
+ this.#disposers.push(disposer);
90
+ let fired = false;
91
+ return async () => {
92
+ if (fired) return;
93
+ fired = true;
94
+ const i = this.#disposers.lastIndexOf(disposer);
95
+ if (i < 0) return;
96
+ this.#disposers.splice(i, 1);
97
+ await this.#run(disposer);
98
+ };
99
+ }
100
+ /** Number of pending disposers. */
101
+ get size() {
102
+ return this.#disposers.length;
103
+ }
104
+ get disposed() {
105
+ return this.#disposed;
106
+ }
107
+ /**
108
+ * Run every pending disposer in LIFO order, each awaited before the next.
109
+ * Idempotent AND concurrency-safe (contract 3): overlapping calls all await
110
+ * the one in-flight drain. A disposer that registers another disposer during
111
+ * teardown is drained too, in LIFO position.
112
+ */
113
+ async dispose() {
114
+ return this.#drain ??= this.#runDrain();
115
+ }
116
+ async #runDrain() {
117
+ this.#disposed = true;
118
+ this.#draining = true;
119
+ try {
120
+ for (let next = this.#disposers.pop(); next !== void 0; next = this.#disposers.pop()) {
121
+ await this.#run(next);
122
+ }
123
+ } finally {
124
+ this.#draining = false;
125
+ }
126
+ }
127
+ /** Enables `await using stack = new EffectStack()` on hosts that support it. */
128
+ async [Symbol.asyncDispose]() {
129
+ await this.dispose();
130
+ }
131
+ async #run(disposer) {
132
+ try {
133
+ await disposer();
134
+ } catch (error) {
135
+ this.#onError(error);
136
+ }
137
+ }
138
+ };
139
+ }
140
+ });
141
+
46
142
  // ../contracts/src/primitive.ts
47
143
  var BasePrimitive;
48
144
  var init_primitive = __esm({
49
145
  "../contracts/src/primitive.ts"() {
146
+ init_effect_stack();
50
147
  BasePrimitive = class {
148
+ /**
149
+ * Instance-lifetime effects. Subclasses that acquire a connection, queue, or
150
+ * timer in their constructor register its inverse here via
151
+ * `this.effects.defer(...)`; `[Symbol.asyncDispose]()` replays them LIFO. This
152
+ * is the structural fix for "a primitive that opens a resource cannot close
153
+ * it" (GROUNDING F1) — per-call resources still use onCallEnd.
154
+ */
155
+ effects = new EffectStack();
51
156
  async onError(err, _ctx) {
52
157
  return {
53
158
  kind: "fail",
@@ -81,6 +186,10 @@ var init_primitive = __esm({
81
186
  return () => {
82
187
  };
83
188
  }
189
+ /** Release instance-lifetime resources. Idempotent. */
190
+ async [Symbol.asyncDispose]() {
191
+ await this.effects.dispose();
192
+ }
84
193
  };
85
194
  }
86
195
  });
@@ -255,7 +364,13 @@ var init_flow_program = __esm({
255
364
  droppedEdges: z.array(z.string().max(128)).max(256),
256
365
  // Node ids needing external plumbing not wired yet (rag/classify/code) — the
257
366
  // interpreter passes through them.
258
- unsupportedNodes: z.array(z.string().max(128)).max(256)
367
+ unsupportedNodes: z.array(z.string().max(128)).max(256),
368
+ // Port-less edges out of a conversational node (llm/ask/playbook) that carry
369
+ // a display `label` but no `condition` — compiled as the UNCONDITIONAL
370
+ // default (the label never routes). Mirrors validateFlowGraph's
371
+ // `edge_label_not_condition` warning. Optional: programs stored before this
372
+ // field existed must still pass the read-side parse.
373
+ labelOnlyEdges: z.array(z.string().max(128)).max(256).optional()
259
374
  });
260
375
  FlowProgram = z.object({
261
376
  v: z.literal(1),
@@ -372,6 +487,12 @@ var init_agents = __esm({
372
487
  readiness: z.array(ReadinessReport).max(64).optional(),
373
488
  metadata: AgentMetadata.optional(),
374
489
  processSchema: ProcessSchemaDTO.nullable().optional(),
490
+ // A code-first agent's base prompt (its instructions live in code, not the DB).
491
+ // Synced here so a generic worker can run it over text (P2). Omit for flow agents.
492
+ // Cap matches AgentConfig.systemPrompt (64k) so a large-but-valid prompt doesn't
493
+ // fail registration outright (which agent.ts swallows → agent runs but never
494
+ // registers/heartbeats).
495
+ prompt: z.string().max(64e3).nullable().optional(),
375
496
  // Opt into MCP exposure. When true the API upserts a matching agent_config
376
497
  // row keyed by (project_id, name, environment) so MCP / dashboard / REST
377
498
  // can list and inspect this agent's identity. When false / omitted, the
@@ -391,6 +512,13 @@ var init_agents = __esm({
391
512
  lastSeenAt: z.string().datetime().nullable(),
392
513
  metadata: AgentMetadata,
393
514
  processSchema: ProcessSchemaDTO.nullable().optional(),
515
+ // Whether this agent can run through the engine over text: a flow agent (its
516
+ // compiled graph rides in processSchema.program) OR a code-first agent whose
517
+ // base prompt was synced on register. Drives the dashboard's chat gate so it
518
+ // no longer hard-codes "flow only". Computed server-side; not stored. OPTIONAL
519
+ // on the wire so a new worker validating a response from an OLD api pod during
520
+ // a rolling deploy doesn't fail register/heartbeat on a missing field.
521
+ textRunnable: z.boolean().optional(),
394
522
  createdAt: z.string().datetime(),
395
523
  updatedAt: z.string().datetime()
396
524
  });
@@ -400,6 +528,9 @@ var init_agents = __esm({
400
528
  z.object({
401
529
  processSchema: ProcessSchemaDTO.nullable()
402
530
  });
531
+ z.object({
532
+ prompt: z.string().nullable()
533
+ });
403
534
  z.object({
404
535
  agent: AgentDTO,
405
536
  // Server-controlled cadence so we can tune without an SDK release.
@@ -486,7 +617,10 @@ var init_consultation = __esm({
486
617
  consultationId: z.string().uuid(),
487
618
  guidance: z.string().min(1).max(4e3),
488
619
  // Optional bridge phrase the agent may speak before delivering guidance.
489
- resumePhrase: z.string().max(500).optional()
620
+ resumePhrase: z.string().max(500).optional(),
621
+ // G-5: the API's id for this command; the agent confirms it (CallControlAck). Optional so a command from an older
622
+ // API is still acted on (just not confirmed).
623
+ commandId: z.string().uuid().optional()
490
624
  });
491
625
  ConsultationPolicy = z.object({
492
626
  enabled: z.boolean().default(true),
@@ -511,7 +645,7 @@ var init_consultation = __esm({
511
645
  });
512
646
  }
513
647
  });
514
- var CallEventKind, OP_CALL_EVENT_KIND, OpCallEventKind, SayCommand, HangupCommand, DtmfCommand, InstructCommand;
648
+ var CallEventKind, OP_CALL_EVENT_KIND, OpCallEventKind, commandId, SayCommand, HangupCommand, DtmfCommand, InstructCommand;
515
649
  var init_call_events = __esm({
516
650
  "../contracts/src/call-events.ts"() {
517
651
  init_consultation();
@@ -536,7 +670,7 @@ var init_call_events = __esm({
536
670
  "dtmf.received",
537
671
  "dtmf.sent"
538
672
  ]);
539
- OP_CALL_EVENT_KIND = /^(tool|handoff|lookup|record|notify)\.[a-z0-9_]{1,48}(\.[a-z0-9_]{1,48})?$/;
673
+ OP_CALL_EVENT_KIND = /^(tool|handoff|lookup|record|notify|engine)\.[a-z0-9_]{1,48}(\.[a-z0-9_]{1,48})?$/;
540
674
  OpCallEventKind = z.string().regex(OP_CALL_EVENT_KIND);
541
675
  z.object({
542
676
  reason: z.string().optional(),
@@ -581,8 +715,10 @@ var init_call_events = __esm({
581
715
  ts: z.string().datetime()
582
716
  });
583
717
  z.enum(["say", "hangup", "dtmf", "instruct", "guidance"]);
718
+ commandId = z.string().uuid().optional();
584
719
  SayCommand = z.object({
585
720
  kind: z.literal("say"),
721
+ commandId,
586
722
  text: z.string().min(1),
587
723
  // 'interrupt' replaces any in-flight TTS; 'queue' waits for current to end.
588
724
  mode: z.enum(["interrupt", "queue"]).default("interrupt"),
@@ -590,14 +726,17 @@ var init_call_events = __esm({
590
726
  });
591
727
  HangupCommand = z.object({
592
728
  kind: z.literal("hangup"),
729
+ commandId,
593
730
  reason: z.string().optional()
594
731
  });
595
732
  DtmfCommand = z.object({
596
733
  kind: z.literal("dtmf"),
734
+ commandId,
597
735
  digits: z.string().regex(/^[0-9*#]+$/)
598
736
  });
599
737
  InstructCommand = z.object({
600
738
  kind: z.literal("instruct"),
739
+ commandId,
601
740
  text: z.string().min(1)
602
741
  });
603
742
  z.discriminatedUnion("kind", [
@@ -607,6 +746,14 @@ var init_call_events = __esm({
607
746
  InstructCommand,
608
747
  GuidanceCommand
609
748
  ]);
749
+ z.object({
750
+ commandId: z.string().uuid(),
751
+ outcome: z.enum(["applied", "refused", "failed"]),
752
+ // machine code: output_blocked · not_supported · unknown_consultation · no_consultation · invalid_command ·
753
+ // say_unavailable · dtmf_unavailable
754
+ code: z.string().max(64).optional(),
755
+ detail: z.string().max(500).optional()
756
+ });
610
757
  }
611
758
  });
612
759
  var VoiceConfig, ModelConfig, SttConfig, RealtimeConfig, PipelineMode, ToolBinding, ProcessFieldType, ProcessFieldSpec, ProcessCompleteWhen, ProcessBackendAckSpec, ProcessSpec, TriggerConditionSpec, TriggerActionSpec, TriggerSpec, RequiredInfoField, AgentConfig;
@@ -1022,6 +1169,113 @@ var init_forms = __esm({
1022
1169
  "../contracts/src/forms.ts"() {
1023
1170
  }
1024
1171
  });
1172
+ var AgentConfigVersionDTO, AgentConfigDiffField, AgentConfigChange;
1173
+ var init_agent_versions = __esm({
1174
+ "../contracts/src/agent-versions.ts"() {
1175
+ init_agent_config();
1176
+ init_agents();
1177
+ init_consultation();
1178
+ z.object({
1179
+ systemPrompt: z.string().max(64e3),
1180
+ routingInstructions: z.string().max(32e3).nullable(),
1181
+ voice: VoiceConfig,
1182
+ model: ModelConfig,
1183
+ stt: SttConfig.nullable(),
1184
+ mode: PipelineMode,
1185
+ realtime: RealtimeConfig.nullable(),
1186
+ language: z.string().min(2).max(16),
1187
+ tools: z.array(ToolBinding).max(64),
1188
+ consultation: ConsultationPolicy,
1189
+ tags: z.array(z.string().max(64)).max(32).nullable(),
1190
+ brainConnectorId: z.string().uuid().nullable(),
1191
+ flowVersion: z.number().int().nullable(),
1192
+ processSchema: ProcessSchemaDTO.nullable()
1193
+ });
1194
+ z.enum(["published", "draft"]);
1195
+ AgentConfigVersionDTO = z.object({
1196
+ id: z.string().uuid(),
1197
+ version: z.number().int().min(1),
1198
+ createdAt: z.string().datetime(),
1199
+ // 'user:<clerkId>' · 'api_key:<id>' · 'system:backfill' · 'system:deploy' · 'system:register'
1200
+ createdBy: z.string().nullable(),
1201
+ note: z.string().nullable(),
1202
+ flowVersion: z.number().int().nullable(),
1203
+ isPublished: z.boolean()
1204
+ });
1205
+ z.object({
1206
+ versions: z.array(AgentConfigVersionDTO),
1207
+ publishedVersion: z.number().int().min(1)
1208
+ });
1209
+ z.object({
1210
+ note: z.string().trim().max(500).optional()
1211
+ });
1212
+ z.object({
1213
+ version: AgentConfigVersionDTO
1214
+ });
1215
+ AgentConfigDiffField = z.enum([
1216
+ "systemPrompt",
1217
+ "routingInstructions",
1218
+ "mode",
1219
+ "model",
1220
+ "voice",
1221
+ "stt",
1222
+ "realtime",
1223
+ "language",
1224
+ "tools",
1225
+ "brainConnectorId",
1226
+ "consultation",
1227
+ "tags",
1228
+ "flowVersion",
1229
+ "processSchema"
1230
+ ]);
1231
+ AgentConfigChange = z.object({
1232
+ field: AgentConfigDiffField,
1233
+ label: z.string(),
1234
+ /** The published value in plain words. */
1235
+ from: z.string(),
1236
+ /** The draft value in plain words. */
1237
+ to: z.string(),
1238
+ /** True for long text (prompts) — the sheet renders it in a scrollable block. */
1239
+ longText: z.boolean()
1240
+ });
1241
+ z.object({
1242
+ published: z.object({
1243
+ version: z.number().int().min(1),
1244
+ createdAt: z.string().datetime(),
1245
+ note: z.string().nullable()
1246
+ }),
1247
+ hasChanges: z.boolean(),
1248
+ changes: z.array(AgentConfigChange)
1249
+ });
1250
+ }
1251
+ });
1252
+
1253
+ // ../contracts/src/providers.ts
1254
+ function providerIdentity(id) {
1255
+ if (!id) return void 0;
1256
+ return PROVIDERS[PROVIDER_ALIASES[id] ?? id];
1257
+ }
1258
+ function byokProviders() {
1259
+ return Object.values(PROVIDERS).filter((p) => p.byok);
1260
+ }
1261
+ var PROVIDERS, PROVIDER_ALIASES;
1262
+ var init_providers = __esm({
1263
+ "../contracts/src/providers.ts"() {
1264
+ PROVIDERS = {
1265
+ openai: { id: "openai", label: "OpenAI", capabilities: ["llm", "tts", "realtime"], byok: true, supportsBaseUrl: true, keyPlaceholder: "sk-..." },
1266
+ anthropic: { id: "anthropic", label: "Anthropic", capabilities: ["llm"], byok: true, supportsBaseUrl: true, keyPlaceholder: "sk-ant-..." },
1267
+ deepgram: { id: "deepgram", label: "Deepgram", capabilities: ["stt", "tts"], byok: true, supportsBaseUrl: false, keyPlaceholder: "Deepgram API key" },
1268
+ elevenlabs: { id: "elevenlabs", label: "ElevenLabs", capabilities: ["tts"], byok: true, supportsBaseUrl: false, keyPlaceholder: "ElevenLabs API key" },
1269
+ cartesia: { id: "cartesia", label: "Cartesia", capabilities: ["stt", "tts"], byok: true, supportsBaseUrl: false, keyPlaceholder: "Cartesia API key" },
1270
+ assemblyai: { id: "assemblyai", label: "AssemblyAI", capabilities: ["stt"], byok: true, supportsBaseUrl: false, keyPlaceholder: "AssemblyAI API key" },
1271
+ google: { id: "google", label: "Google (Gemini)", capabilities: ["llm", "realtime"], byok: true, supportsBaseUrl: false, keyPlaceholder: "AIza..." },
1272
+ groq: { id: "groq", label: "Groq", capabilities: ["llm"], byok: true, supportsBaseUrl: true, keyPlaceholder: "gsk_..." },
1273
+ // Runtime-only providers (no external key): local VAD.
1274
+ silero: { id: "silero", label: "Silero", capabilities: ["vad"], byok: false, supportsBaseUrl: false }
1275
+ };
1276
+ PROVIDER_ALIASES = { gemini: "google" };
1277
+ }
1278
+ });
1025
1279
  var ProviderKind, CatalogVoice, CatalogModel, CatalogProvider, CostEstimateAssumptions, CostEstimatePerMinute, CostEstimateBilledPerMinute;
1026
1280
  var init_model_catalog = __esm({
1027
1281
  "../contracts/src/model-catalog.ts"() {
@@ -1419,6 +1673,8 @@ var init_numbers = __esm({
1419
1673
  agentId: z.string().uuid().nullable(),
1420
1674
  // Environments pin (draft/live). null = no pin → live (today's behaviour).
1421
1675
  agentConfigEnv: z.enum(["draft", "live"]).nullable(),
1676
+ // "Agent answers texts": the bound agent replies to inbound SMS (burn-down G-1). Off by default.
1677
+ smsAgentReplies: z.boolean(),
1422
1678
  status: PhoneNumberStatus,
1423
1679
  friendlyName: z.string().nullable(),
1424
1680
  region: z.string().nullable(),
@@ -1442,6 +1698,7 @@ var init_numbers = __esm({
1442
1698
  // null detaches.
1443
1699
  agentId: z.string().uuid().nullable()
1444
1700
  });
1701
+ z.object({ enabled: z.boolean() });
1445
1702
  }
1446
1703
  });
1447
1704
  var init_project_limits = __esm({
@@ -1548,6 +1805,12 @@ var init_compliance = __esm({
1548
1805
  ComplianceConfig.partial();
1549
1806
  }
1550
1807
  });
1808
+
1809
+ // ../contracts/src/sms-keywords.ts
1810
+ var init_sms_keywords = __esm({
1811
+ "../contracts/src/sms-keywords.ts"() {
1812
+ }
1813
+ });
1551
1814
  var init_call_recording = __esm({
1552
1815
  "../contracts/src/call-recording.ts"() {
1553
1816
  z.object({
@@ -1584,10 +1847,16 @@ var init_outbound = __esm({
1584
1847
  var ConnectionAuthType, ConnectionStatus;
1585
1848
  var init_connection = __esm({
1586
1849
  "../contracts/src/connection.ts"() {
1850
+ init_providers();
1587
1851
  ConnectionAuthType = z.enum(["oauth2", "api_key", "bearer", "basic", "none"]);
1588
1852
  ConnectionStatus = z.enum(["active", "revoked", "error"]);
1589
1853
  z.object({
1590
1854
  v: z.number().int(),
1855
+ // Key id (short fingerprint) of the key that encrypted this envelope — lets the
1856
+ // cipher pick the right key from its ring so CREDENTIAL_ENCRYPTION_KEY can rotate
1857
+ // in-band (Tier-1 item 4). Optional/absent on legacy v1 envelopes, which decrypt
1858
+ // with the designated legacy key.
1859
+ kid: z.string().optional(),
1591
1860
  iv: z.string(),
1592
1861
  ct: z.string(),
1593
1862
  tag: z.string()
@@ -1625,6 +1894,12 @@ var init_connection = __esm({
1625
1894
  }).refine((v) => v.name !== void 0 || v.secret !== void 0 || v.metadata !== void 0, {
1626
1895
  message: "nothing to update"
1627
1896
  });
1897
+ byokProviders().map((p) => ({
1898
+ id: p.id,
1899
+ label: p.label,
1900
+ keyPlaceholder: p.keyPlaceholder ?? "",
1901
+ supportsBaseUrl: p.supportsBaseUrl
1902
+ }));
1628
1903
  }
1629
1904
  });
1630
1905
  var E164, RoomPrefix, RecordingPrefix, Cidr, TelephonySettings, ProfileSource, EffectiveCallProfile;
@@ -1958,6 +2233,7 @@ var init_flow_enrichment = __esm({
1958
2233
  var init_flow_compile = __esm({
1959
2234
  "../contracts/src/flow-compile.ts"() {
1960
2235
  init_flow_enrichment();
2236
+ init_flow_validate();
1961
2237
  }
1962
2238
  });
1963
2239
  var EnvironmentSpec;
@@ -2102,6 +2378,1057 @@ var init_playbook_compile = __esm({
2102
2378
  "../contracts/src/playbook-compile.ts"() {
2103
2379
  }
2104
2380
  });
2381
+ var EngineSeamBinding, EnginePluginDTO;
2382
+ var init_engine = __esm({
2383
+ "../contracts/src/engine.ts"() {
2384
+ EngineSeamBinding = z.object({
2385
+ key: z.string(),
2386
+ bound: z.boolean()
2387
+ });
2388
+ EnginePluginDTO = z.object({
2389
+ name: z.string(),
2390
+ provides: z.array(EngineSeamBinding),
2391
+ injects: z.array(z.string())
2392
+ });
2393
+ z.object({
2394
+ plugins: z.array(EnginePluginDTO)
2395
+ });
2396
+ }
2397
+ });
2398
+
2399
+ // ../contracts/src/identity.ts
2400
+ var init_identity = __esm({
2401
+ "../contracts/src/identity.ts"() {
2402
+ }
2403
+ });
2404
+
2405
+ // ../contracts/src/interaction.ts
2406
+ var init_interaction = __esm({
2407
+ "../contracts/src/interaction.ts"() {
2408
+ }
2409
+ });
2410
+
2411
+ // ../contracts/src/channel-connector/capability-state.ts
2412
+ var CONDITION_CLASSES, CONDITION_TYPES, SURFACES, TRANSPORT_KINDS, AGENT_INITIATION, TEMPLATE_REQUIREMENT, WINDOW_OUTSIDE, PEER_ID_SCOPES, RECEIPT_KINDS;
2413
+ var init_capability_state = __esm({
2414
+ "../contracts/src/channel-connector/capability-state.ts"() {
2415
+ CONDITION_CLASSES = ["authority", "operational", "observability"];
2416
+ CONDITION_TYPES = [
2417
+ { id: "CredentialsValid", defaultClass: "authority", summary: "credential lease resolves and is not expired/revoked" },
2418
+ { id: "ProviderAccountGood", defaultClass: "authority", summary: "provider account / sender standing is not suspended or restricted" },
2419
+ { id: "RegistrationApproved", defaultClass: "authority", summary: "required registration / approval is current" },
2420
+ { id: "ResourceOwnershipConfirmed", defaultClass: "authority", summary: "the native object is confirmed to belong to this principal" },
2421
+ { id: "InstallationPresent", defaultClass: "authority", summary: "the container installation exists and is not revoked" },
2422
+ { id: "PermissionsHeld", defaultClass: "authority", summary: "required provider permissions/scopes/intents are held" },
2423
+ { id: "ConsentResolved", defaultClass: "authority", summary: "consent verdict available (ch. 02; not_evaluated until it ships)" },
2424
+ { id: "WindowOpen", defaultClass: "authority", summary: "the reply window for the peer is open" },
2425
+ { id: "TemplateAvailable", defaultClass: "authority", summary: "an approved template exists for the operation" },
2426
+ { id: "PeerReachable", defaultClass: "authority", summary: "the peer can be reached on this channel (e.g. RCS capability, not blocked)" },
2427
+ { id: "WebhookRegistered", defaultClass: "operational", summary: "the provider has our ingress registered" },
2428
+ { id: "InboundVerificationReady", defaultClass: "authority", summary: "the material the declared inbound-auth strategy needs (for the selected authorization method) resolves \u2014 REST auth alone never satisfies it (#306 L4)" },
2429
+ { id: "WebhookHealthy", defaultClass: "operational", summary: "recent verified inbound / delivery health" },
2430
+ { id: "TransportConnected", defaultClass: "operational", summary: "a transport lease is live (gateway / long-poll / pub-sub)" },
2431
+ { id: "ReconcileFresh", defaultClass: "operational", summary: "the last reconcile is within its window" },
2432
+ { id: "ProviderLatency", defaultClass: "observability", summary: "provider latency / error-rate telemetry" }
2433
+ ];
2434
+ new Set(CONDITION_TYPES.map((c) => c.id));
2435
+ SURFACES = ["direct", "group", "channel", "thread", "broadcast"];
2436
+ TRANSPORT_KINDS = ["webhook", "long_poll", "gateway", "pubsub", "http_interactions"];
2437
+ AGENT_INITIATION = ["always", "installed_only", "opt_in_required", "template_only", "never"];
2438
+ TEMPLATE_REQUIREMENT = ["never", "outside_window", "always"];
2439
+ WINDOW_OUTSIDE = ["tagged_only", "template_only", "blocked"];
2440
+ PEER_ID_SCOPES = ["global", "app_scoped", "workspace_scoped", "installation_scoped"];
2441
+ RECEIPT_KINDS = ["sent", "delivered", "read", "failed"];
2442
+ }
2443
+ });
2444
+
2445
+ // ../contracts/src/channel-connector/channels.ts
2446
+ function baseline(channel, partial) {
2447
+ return {
2448
+ channel,
2449
+ connector: null,
2450
+ version: "1.0",
2451
+ requiredPermissions: [],
2452
+ registration: [],
2453
+ readinessGates: [
2454
+ { condition: "CredentialsValid", class: "authority", blocks: "*", ttlSec: 300 },
2455
+ { condition: "ProviderAccountGood", class: "authority", blocks: "*", ttlSec: 3600 },
2456
+ { condition: "WebhookHealthy", class: "operational", blocks: ["receive.message", "call.receive"], ttlSec: 900, evidence: "runtime" }
2457
+ ],
2458
+ ...partial
2459
+ };
2460
+ }
2461
+ var TEXT_ONLY, RICH_MESSAGING, MESSAGE_CORE, CONTAINER_GATES, CHANNELS, CHANNEL_IDS;
2462
+ var init_channels = __esm({
2463
+ "../contracts/src/channel-connector/channels.ts"() {
2464
+ init_capability_state();
2465
+ TEXT_ONLY = ["text"];
2466
+ RICH_MESSAGING = ["text", "image", "audio", "video", "document", "location", "contact", "sticker", "reaction", "interactive", "template"];
2467
+ MESSAGE_CORE = ["receive.message", "send.text", "send.media", "initiate.conversation", "reply.in_window"];
2468
+ CONTAINER_GATES = [
2469
+ { condition: "CredentialsValid", class: "authority", blocks: "*", ttlSec: 300 },
2470
+ { condition: "InstallationPresent", class: "authority", blocks: "*", ttlSec: 3600 },
2471
+ { condition: "PermissionsHeld", class: "authority", blocks: ["send.text", "send.media", "channel.post", "thread.reply", "group.post"], ttlSec: 3600 },
2472
+ { condition: "WebhookHealthy", class: "operational", blocks: ["receive.message"], ttlSec: 900, evidence: "runtime" }
2473
+ ];
2474
+ CHANNELS = [
2475
+ {
2476
+ id: "sms",
2477
+ displayName: "SMS",
2478
+ addressSpace: "phone",
2479
+ surfaces: ["direct"],
2480
+ bindingCapabilities: ["sms"],
2481
+ baseline: baseline("sms", {
2482
+ operations: [...MESSAGE_CORE, "read.receipt"],
2483
+ direction: { inbound: true, outbound: true },
2484
+ initiation: { agentInitiated: "always", userInitiated: true, firstContactRule: "agent-initiated with registration (10DLC / TFV per country); STOP/HELP keywords mandatory" },
2485
+ windows: [],
2486
+ templates: { required: "never", reviewed: false },
2487
+ surfaces: ["direct"],
2488
+ messageTypes: TEXT_ONLY,
2489
+ // GSM-7 160/153, UCS-2 70/67 per segment (doc 01 §3); no body cap is a channel fact.
2490
+ limits: { segments: true, note: "GSM-7 160/153 \xB7 UCS-2 70/67 per segment" },
2491
+ receipts: ["sent", "delivered", "failed"],
2492
+ edits: { edit: false, delete: false, reactions: false, commands: false },
2493
+ transport: ["webhook"],
2494
+ identity: { peerIdKind: "phone", peerIdScope: "global" },
2495
+ registration: [{ kind: "campaign_vetting", blocks: ["send.text", "send.media", "initiate.conversation"] }]
2496
+ })
2497
+ },
2498
+ {
2499
+ id: "mms",
2500
+ displayName: "MMS",
2501
+ addressSpace: "phone",
2502
+ surfaces: ["direct", "group"],
2503
+ bindingCapabilities: ["mms"],
2504
+ baseline: baseline("mms", {
2505
+ operations: [...MESSAGE_CORE, "group.post"],
2506
+ direction: { inbound: true, outbound: true },
2507
+ initiation: { agentInitiated: "always", userInitiated: true, firstContactRule: "as SMS; carrier MMS size caps apply" },
2508
+ windows: [],
2509
+ templates: { required: "never", reviewed: false },
2510
+ surfaces: ["direct", "group"],
2511
+ messageTypes: ["text", "image", "audio", "video", "document"],
2512
+ limits: { mediaBytes: 1e6, note: "carrier MMS \u22480.6\u20131 MB (doc 01 \xA73)" },
2513
+ receipts: ["sent", "delivered", "failed"],
2514
+ edits: { edit: false, delete: false, reactions: false, commands: false },
2515
+ transport: ["webhook"],
2516
+ identity: { peerIdKind: "phone", peerIdScope: "global" },
2517
+ registration: [{ kind: "campaign_vetting", blocks: ["send.text", "send.media", "initiate.conversation"] }]
2518
+ })
2519
+ },
2520
+ {
2521
+ id: "voice",
2522
+ displayName: "Voice (PSTN)",
2523
+ addressSpace: "phone",
2524
+ surfaces: ["direct"],
2525
+ bindingCapabilities: ["voice"],
2526
+ baseline: baseline("voice", {
2527
+ operations: ["call.receive", "call.place"],
2528
+ direction: { inbound: true, outbound: true },
2529
+ initiation: { agentInitiated: "always", userInitiated: true, firstContactRule: "agent-initiated; STIR/SHAKEN attestation; emergency addressing; trunk config" },
2530
+ windows: [],
2531
+ templates: { required: "never", reviewed: false },
2532
+ surfaces: ["direct"],
2533
+ messageTypes: [],
2534
+ limits: {},
2535
+ receipts: [],
2536
+ edits: { edit: false, delete: false, reactions: false, commands: false },
2537
+ transport: ["webhook"],
2538
+ identity: { peerIdKind: "phone", peerIdScope: "global" }
2539
+ })
2540
+ },
2541
+ {
2542
+ id: "whatsapp",
2543
+ displayName: "WhatsApp",
2544
+ addressSpace: "whatsapp",
2545
+ surfaces: ["direct", "group"],
2546
+ bindingCapabilities: ["whatsapp.message"],
2547
+ baseline: baseline("whatsapp", {
2548
+ operations: [...MESSAGE_CORE, "send.template", "send.interactive", "send.reaction", "read.receipt", "typing.indicate"],
2549
+ direction: { inbound: true, outbound: true },
2550
+ initiation: { agentInitiated: "template_only", userInitiated: true, firstContactRule: "user-initiated or a reviewed template outside the 24-h window; opt-in mandatory" },
2551
+ windows: [{ kind: "reply", durationSec: 86400, opensOn: "inbound_message", outside: "template_only" }],
2552
+ templates: { required: "outside_window", reviewed: true },
2553
+ surfaces: ["direct", "group"],
2554
+ messageTypes: RICH_MESSAGING,
2555
+ limits: {
2556
+ textChars: 4096,
2557
+ mediaBytes: 1e8,
2558
+ perSecond: 80,
2559
+ formats: { audio: { mime: ["audio/ogg", "audio/mpeg", "audio/mp4", "audio/aac", "audio/amr"], variants: ["voice_note", "file"] } }
2560
+ },
2561
+ receipts: ["sent", "delivered", "read", "failed"],
2562
+ edits: { edit: false, delete: false, reactions: true, commands: false },
2563
+ transport: ["webhook"],
2564
+ identity: { peerIdKind: "whatsapp", peerIdScope: "global" },
2565
+ registration: [{ kind: "business_verification", blocks: ["initiate.conversation", "send.template"] }]
2566
+ })
2567
+ },
2568
+ {
2569
+ id: "instagram",
2570
+ displayName: "Instagram DM",
2571
+ addressSpace: "app",
2572
+ surfaces: ["direct"],
2573
+ bindingCapabilities: ["instagram.message"],
2574
+ baseline: baseline("instagram", {
2575
+ operations: ["receive.message", "send.text", "send.media", "send.reaction", "reply.in_window", "delete.message", "read.receipt"],
2576
+ direction: { inbound: true, outbound: true },
2577
+ initiation: { agentInitiated: "never", userInitiated: true, firstContactRule: "user-initiated only \u2014 the peer identifier exists only after the user messages or clicks" },
2578
+ windows: [{ kind: "reply", durationSec: 86400, opensOn: "inbound_message", outside: "tagged_only" }],
2579
+ templates: { required: "never", reviewed: false },
2580
+ surfaces: ["direct"],
2581
+ messageTypes: ["text", "image", "audio", "video", "document", "sticker", "reaction", "interactive", "template"],
2582
+ limits: { textBytes: 1e3, mediaBytes: 25e6 },
2583
+ receipts: ["read"],
2584
+ edits: { edit: false, delete: true, reactions: true, commands: false },
2585
+ transport: ["webhook"],
2586
+ // doc 07 §3: per person × professional account, stable across apps — the
2587
+ // account scope is embedded in the normalized value, so the id is global.
2588
+ identity: { peerIdKind: "meta_account_user", peerIdScope: "global" },
2589
+ registration: [{ kind: "app_review", blocks: ["send.text", "send.media"] }]
2590
+ })
2591
+ },
2592
+ {
2593
+ id: "messenger",
2594
+ displayName: "Messenger",
2595
+ addressSpace: "app",
2596
+ surfaces: ["direct"],
2597
+ bindingCapabilities: ["messenger.message"],
2598
+ baseline: baseline("messenger", {
2599
+ operations: ["receive.message", "send.text", "send.media", "send.reaction", "reply.in_window", "edit.message", "read.receipt", "typing.indicate"],
2600
+ direction: { inbound: true, outbound: true },
2601
+ initiation: { agentInitiated: "never", userInitiated: true, firstContactRule: "as Instagram plus Get Started / link / ads entry points" },
2602
+ windows: [{ kind: "reply", durationSec: 86400, opensOn: "inbound_message", outside: "tagged_only" }],
2603
+ templates: { required: "never", reviewed: false },
2604
+ surfaces: ["direct"],
2605
+ messageTypes: RICH_MESSAGING,
2606
+ limits: { mediaBytes: 25e6 },
2607
+ receipts: ["delivered", "read"],
2608
+ edits: { edit: true, delete: false, reactions: true, commands: false },
2609
+ transport: ["webhook"],
2610
+ identity: { peerIdKind: "meta_account_user", peerIdScope: "global" },
2611
+ registration: [{ kind: "app_review", blocks: ["send.text", "send.media"] }]
2612
+ })
2613
+ },
2614
+ {
2615
+ id: "telegram",
2616
+ displayName: "Telegram",
2617
+ addressSpace: "app",
2618
+ surfaces: ["direct", "group", "channel", "thread"],
2619
+ bindingCapabilities: ["telegram.message"],
2620
+ baseline: baseline("telegram", {
2621
+ operations: [...MESSAGE_CORE, "send.interactive", "send.reaction", "edit.message", "delete.message", "command.register", "group.post", "channel.post", "thread.reply"],
2622
+ direction: { inbound: true, outbound: true },
2623
+ // Plain bot: the user must start; no reply window. The business-account
2624
+ // 24-h rule is an installation-level narrowing, not the channel ceiling.
2625
+ initiation: { agentInitiated: "never", userInitiated: true, firstContactRule: "the user must start the bot (/start, deep link, group add) before it may message them" },
2626
+ windows: [],
2627
+ templates: { required: "never", reviewed: false },
2628
+ surfaces: ["direct", "group", "channel", "thread"],
2629
+ messageTypes: ["text", "image", "audio", "video", "document", "location", "contact", "sticker", "reaction", "interactive", "poll"],
2630
+ limits: { textChars: 4096, mediaBytes: 5e7, formats: { audio: { mime: ["audio/ogg", "audio/mpeg"], variants: ["voice_note", "file"] } } },
2631
+ receipts: [],
2632
+ edits: { edit: true, delete: true, reactions: true, commands: true },
2633
+ transport: ["webhook", "long_poll"],
2634
+ identity: { peerIdKind: "telegram_user", peerIdScope: "global" }
2635
+ })
2636
+ },
2637
+ {
2638
+ id: "discord",
2639
+ displayName: "Discord",
2640
+ addressSpace: "app",
2641
+ surfaces: ["direct", "group", "channel", "thread"],
2642
+ bindingCapabilities: ["discord.message", "discord.command"],
2643
+ baseline: baseline("discord", {
2644
+ // The channel exposes BOTH modes (doc 04 §1): text via `send.*`, realtime
2645
+ // audio via `call.*`. Mode is per OPERATION — the channel id never implies it.
2646
+ operations: [...MESSAGE_CORE, "send.interactive", "send.reaction", "edit.message", "delete.message", "command.register", "channel.post", "thread.reply", "call.receive", "call.place"],
2647
+ direction: { inbound: true, outbound: true },
2648
+ initiation: { agentInitiated: "installed_only", userInitiated: true, firstContactRule: "guild channels by permission; DMs need a mutual guild or a user install" },
2649
+ windows: [],
2650
+ templates: { required: "never", reviewed: false },
2651
+ surfaces: ["direct", "group", "channel", "thread"],
2652
+ messageTypes: ["text", "image", "audio", "video", "document", "reaction", "interactive", "card", "poll"],
2653
+ limits: { textChars: 2e3, mediaBytes: 10485760 },
2654
+ receipts: [],
2655
+ edits: { edit: true, delete: true, reactions: true, commands: true },
2656
+ transport: ["gateway", "http_interactions"],
2657
+ identity: { peerIdKind: "discord_user", peerIdScope: "global" },
2658
+ readinessGates: [
2659
+ { condition: "CredentialsValid", class: "authority", blocks: "*", ttlSec: 300 },
2660
+ { condition: "InstallationPresent", class: "authority", blocks: "*", ttlSec: 3600 },
2661
+ { condition: "PermissionsHeld", class: "authority", blocks: ["send.text", "send.media", "channel.post", "thread.reply", "call.receive", "call.place"], ttlSec: 3600 },
2662
+ { condition: "TransportConnected", class: "operational", blocks: ["receive.message", "call.receive"], ttlSec: 120 }
2663
+ ]
2664
+ })
2665
+ },
2666
+ {
2667
+ // G-3: the embeddable website chat widget. The widget key IS the identity and its evidence (a `web`-space Binding
2668
+ // at `widget:<keyId>`, no provider connection), so no provider readiness gate applies. The agent only ever answers
2669
+ // inside the visitor's own request — it never starts or pushes a message.
2670
+ id: "web",
2671
+ displayName: "Website chat",
2672
+ addressSpace: "web",
2673
+ surfaces: ["direct"],
2674
+ bindingCapabilities: ["web.message"],
2675
+ baseline: baseline("web", {
2676
+ operations: ["receive.message", "reply.in_window"],
2677
+ direction: { inbound: true, outbound: true },
2678
+ initiation: { agentInitiated: "never", userInitiated: true, firstContactRule: "the visitor opens the widget and writes first" },
2679
+ windows: [],
2680
+ templates: { required: "never", reviewed: false },
2681
+ surfaces: ["direct"],
2682
+ messageTypes: TEXT_ONLY,
2683
+ limits: { textChars: 8e3 },
2684
+ receipts: [],
2685
+ edits: { edit: false, delete: false, reactions: false, commands: false },
2686
+ transport: ["http_interactions"],
2687
+ identity: { peerIdKind: "web_visitor", peerIdScope: "app_scoped" },
2688
+ readinessGates: []
2689
+ })
2690
+ },
2691
+ {
2692
+ id: "slack",
2693
+ displayName: "Slack",
2694
+ addressSpace: "app",
2695
+ surfaces: ["direct", "group", "channel", "thread"],
2696
+ bindingCapabilities: ["slack.message"],
2697
+ baseline: baseline("slack", {
2698
+ operations: [...MESSAGE_CORE, "send.interactive", "send.reaction", "edit.message", "delete.message", "command.register", "channel.post", "thread.reply"],
2699
+ direction: { inbound: true, outbound: true },
2700
+ initiation: { agentInitiated: "installed_only", userInitiated: true, firstContactRule: "any channel the bot is in; DMs" },
2701
+ windows: [],
2702
+ templates: { required: "never", reviewed: false },
2703
+ surfaces: ["direct", "group", "channel", "thread"],
2704
+ messageTypes: ["text", "image", "audio", "video", "document", "reaction", "interactive", "card"],
2705
+ limits: { textChars: 4e3, note: "truncated at 40,000 (doc 01 \xA73)" },
2706
+ receipts: [],
2707
+ edits: { edit: true, delete: true, reactions: true, commands: true },
2708
+ transport: ["webhook", "gateway"],
2709
+ identity: { peerIdKind: "slack_user", peerIdScope: "workspace_scoped" },
2710
+ readinessGates: CONTAINER_GATES
2711
+ })
2712
+ },
2713
+ {
2714
+ id: "teams",
2715
+ displayName: "Microsoft Teams",
2716
+ addressSpace: "app",
2717
+ surfaces: ["direct", "group", "channel", "thread"],
2718
+ bindingCapabilities: ["teams.message"],
2719
+ baseline: baseline("teams", {
2720
+ operations: [...MESSAGE_CORE, "send.interactive", "edit.message", "delete.message", "channel.post", "thread.reply", "read.receipt"],
2721
+ direction: { inbound: true, outbound: true },
2722
+ initiation: { agentInitiated: "installed_only", userInitiated: true, firstContactRule: "proactive send needs an installation in scope plus a conversation reference" },
2723
+ windows: [],
2724
+ templates: { required: "never", reviewed: false },
2725
+ surfaces: ["direct", "group", "channel", "thread"],
2726
+ messageTypes: ["text", "image", "document", "card", "interactive", "reaction"],
2727
+ limits: { textBytes: 102400, note: "message payload \u2264100 KB (doc 01 \xA73)" },
2728
+ receipts: ["read"],
2729
+ edits: { edit: true, delete: true, reactions: false, commands: false },
2730
+ transport: ["webhook"],
2731
+ identity: { peerIdKind: "teams_user", peerIdScope: "installation_scoped" },
2732
+ readinessGates: CONTAINER_GATES
2733
+ })
2734
+ },
2735
+ {
2736
+ id: "rcs",
2737
+ displayName: "RCS",
2738
+ addressSpace: "rcs",
2739
+ surfaces: ["direct"],
2740
+ bindingCapabilities: ["rcs.message"],
2741
+ baseline: baseline("rcs", {
2742
+ operations: [...MESSAGE_CORE, "send.interactive", "read.receipt", "typing.indicate", "delete.message"],
2743
+ direction: { inbound: true, outbound: true },
2744
+ initiation: { agentInitiated: "opt_in_required", userInitiated: true, firstContactRule: "agent-initiated only with consent per AUP; STOP / UNSUBSCRIBE brand-wide" },
2745
+ windows: [],
2746
+ templates: { required: "never", reviewed: false },
2747
+ surfaces: ["direct"],
2748
+ messageTypes: ["text", "image", "audio", "video", "document", "card", "carousel", "interactive"],
2749
+ limits: { textChars: 3072, mediaBytes: 104857600 },
2750
+ receipts: ["delivered", "read"],
2751
+ edits: { edit: false, delete: true, reactions: false, commands: false },
2752
+ transport: ["webhook"],
2753
+ identity: { peerIdKind: "phone", peerIdScope: "global" },
2754
+ registration: [{ kind: "agent_launch", blocks: ["send.text", "send.media", "send.interactive", "initiate.conversation"] }],
2755
+ readinessGates: [
2756
+ { condition: "CredentialsValid", class: "authority", blocks: "*", ttlSec: 300 },
2757
+ { condition: "RegistrationApproved", class: "authority", blocks: ["send.text", "send.media", "send.interactive", "initiate.conversation"], ttlSec: 86400 },
2758
+ { condition: "PeerReachable", class: "authority", blocks: ["send.text", "send.media", "send.interactive", "initiate.conversation"], ttlSec: 86400 },
2759
+ { condition: "WebhookHealthy", class: "operational", blocks: ["receive.message"], ttlSec: 900, evidence: "runtime" }
2760
+ ]
2761
+ })
2762
+ }
2763
+ ];
2764
+ new Map(CHANNELS.map((c) => [c.id, c]));
2765
+ CHANNEL_IDS = new Set(CHANNELS.map((c) => c.id));
2766
+ }
2767
+ });
2768
+
2769
+ // ../contracts/src/channel-connector/operations.ts
2770
+ var OPERATION_MODES, OPERATIONS, OPERATION_IDS;
2771
+ var init_operations = __esm({
2772
+ "../contracts/src/channel-connector/operations.ts"() {
2773
+ OPERATION_MODES = ["message", "realtime"];
2774
+ OPERATIONS = [
2775
+ { id: "receive.message", mode: "message", summary: "accept an inbound message" },
2776
+ { id: "send.text", mode: "message", summary: "send a text message" },
2777
+ { id: "send.media", mode: "message", summary: "send a message carrying a content item (image, audio, video, document)" },
2778
+ { id: "send.template", mode: "message", summary: "send a reviewed template" },
2779
+ { id: "send.interactive", mode: "message", summary: "send structured/interactive content" },
2780
+ { id: "send.reaction", mode: "message", summary: "react to a message" },
2781
+ { id: "edit.message", mode: "message", summary: "edit a sent message" },
2782
+ { id: "delete.message", mode: "message", summary: "delete a sent message" },
2783
+ { id: "initiate.conversation", mode: "message", summary: "first contact / outside any window" },
2784
+ { id: "reply.in_window", mode: "message", summary: "reply inside an open window" },
2785
+ { id: "call.receive", mode: "realtime", summary: "accept an inbound realtime session" },
2786
+ { id: "call.place", mode: "realtime", summary: "place an outbound realtime session" },
2787
+ { id: "read.receipt", mode: "message", summary: "mark a message as read" },
2788
+ { id: "typing.indicate", mode: "message", summary: "typing indicator" },
2789
+ { id: "command.register", mode: "message", summary: "register slash/bot commands" },
2790
+ { id: "thread.reply", mode: "message", summary: "reply inside a thread" },
2791
+ { id: "group.post", mode: "message", summary: "post to a group surface" },
2792
+ { id: "channel.post", mode: "message", summary: "post to a channel surface" }
2793
+ ];
2794
+ new Map(
2795
+ OPERATIONS.map((op) => [op.id, op])
2796
+ );
2797
+ OPERATION_IDS = new Set(OPERATIONS.map((op) => op.id));
2798
+ }
2799
+ });
2800
+
2801
+ // ../contracts/src/channel-connector/ids.ts
2802
+ var A2_SHIPPED_ROOTS, A2_PLATFORM_ROOTS, NAMESPACE_RE, SEMVER_RE;
2803
+ var init_ids2 = __esm({
2804
+ "../contracts/src/channel-connector/ids.ts"() {
2805
+ init_channels();
2806
+ init_operations();
2807
+ A2_SHIPPED_ROOTS = [
2808
+ "llm",
2809
+ "conversation",
2810
+ "channels",
2811
+ "tools",
2812
+ "guard",
2813
+ "store",
2814
+ "compliance",
2815
+ "carriers",
2816
+ "agents",
2817
+ "agentRun",
2818
+ "telemetry"
2819
+ ];
2820
+ A2_PLATFORM_ROOTS = [
2821
+ ...A2_SHIPPED_ROOTS,
2822
+ "policy",
2823
+ "identity",
2824
+ "binding",
2825
+ "provider",
2826
+ "workflow",
2827
+ "session",
2828
+ "engine",
2829
+ "message",
2830
+ "connector",
2831
+ "communication",
2832
+ "event",
2833
+ "reach",
2834
+ "handler",
2835
+ "plugin",
2836
+ "scope",
2837
+ "snapshot"
2838
+ ];
2839
+ /* @__PURE__ */ new Set([
2840
+ ...A2_PLATFORM_ROOTS,
2841
+ ...[...OPERATION_IDS].map((id) => id.split(".")[0]),
2842
+ ...CHANNEL_IDS
2843
+ ]);
2844
+ NAMESPACE_RE = /^[a-z0-9]+\.[a-z0-9]+$/;
2845
+ SEMVER_RE = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(-[0-9A-Za-z.-]+)?(\+[0-9A-Za-z.-]+)?$/;
2846
+ }
2847
+ });
2848
+ function parseContentRef(ref) {
2849
+ const m = CONTENT_REF_RE.exec(ref);
2850
+ return m ? { principalId: m[1], contentId: m[2] } : null;
2851
+ }
2852
+ function isContentRef(value) {
2853
+ return typeof value === "string" && CONTENT_REF_RE.test(value);
2854
+ }
2855
+ function looksLikeProviderLocator(value) {
2856
+ return /^(https?:\/\/|s3:\/\/|gs:\/\/)/i.test(value) || value.includes("?") || value.includes("://");
2857
+ }
2858
+ var MODALITY_KINDS, STRUCTURED_KINDS, CONTENT_KINDS, LOSSINESS, CONTENT_REF_RE, MessageContentProvenance, noLocator, MessageContentMetadata, contentRefSchema, OriginalMessageContent, DerivedMessageContent;
2859
+ var init_content_kinds = __esm({
2860
+ "../contracts/src/channel-connector/content-kinds.ts"() {
2861
+ MODALITY_KINDS = ["text", "image", "audio", "video", "document"];
2862
+ STRUCTURED_KINDS = [
2863
+ "location",
2864
+ "contact",
2865
+ "sticker",
2866
+ "reaction",
2867
+ "interactive",
2868
+ "card",
2869
+ "carousel",
2870
+ "poll",
2871
+ "template"
2872
+ ];
2873
+ CONTENT_KINDS = [...MODALITY_KINDS, ...STRUCTURED_KINDS];
2874
+ new Set(CONTENT_KINDS);
2875
+ LOSSINESS = ["lossless", "lossy", "semantic"];
2876
+ CONTENT_REF_RE = /^content:([A-Za-z0-9_-]+)\/([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})$/;
2877
+ MessageContentProvenance = z.object({
2878
+ /** The connector that produced the item (A7 id). */
2879
+ connector: z.string(),
2880
+ /** The provider-native identifier — reconciliation only; never agent-facing. */
2881
+ providerRef: z.string().optional(),
2882
+ receivedAt: z.string().datetime().optional()
2883
+ }).strict();
2884
+ noLocator = (label) => z.string().refine((v) => !looksLikeProviderLocator(v), { message: `${label} must not carry a provider locator \u2014 provider ids live in provenance.providerRef only` });
2885
+ MessageContentMetadata = z.object({
2886
+ bytes: z.number().int().nonnegative().optional(),
2887
+ durationMs: z.number().int().nonnegative().optional(),
2888
+ width: z.number().int().positive().optional(),
2889
+ height: z.number().int().positive().optional(),
2890
+ filename: noLocator("metadata.filename").optional(),
2891
+ caption: noLocator("metadata.caption").optional(),
2892
+ /** e.g. `voice_note` for audio. */
2893
+ variant: z.string().regex(/^[a-z0-9_]+$/).optional(),
2894
+ language: z.string().max(16).optional()
2895
+ }).strict();
2896
+ contentRefSchema = z.string().refine(isContentRef, {
2897
+ message: "ref must be an opaque platform content reference `content:<principal>/<uuid>`"
2898
+ });
2899
+ OriginalMessageContent = z.object({
2900
+ kind: z.enum(CONTENT_KINDS),
2901
+ /** MIME type or registry format id. */
2902
+ format: z.string().min(1),
2903
+ ref: contentRefSchema,
2904
+ metadata: MessageContentMetadata.default({}),
2905
+ provenance: MessageContentProvenance,
2906
+ /** Inline text for `text` kind only (the ref still names the stored item). */
2907
+ text: z.string().optional()
2908
+ }).strict().refine((item) => item.text === void 0 || item.kind === "text", { message: "inline text is only valid on kind=text" });
2909
+ DerivedMessageContent = OriginalMessageContent.innerType().extend({
2910
+ derivedFrom: contentRefSchema,
2911
+ /** The transform capability id (registry or extension id). */
2912
+ transform: z.string().min(1),
2913
+ /** A7 actor ref of the producer. */
2914
+ producer: z.string().min(1),
2915
+ lossiness: z.enum(LOSSINESS)
2916
+ }).strict().refine((item) => item.text === void 0 || item.kind === "text", { message: "inline text is only valid on kind=text" }).refine((item) => parseContentRef(item.derivedFrom)?.principalId === parseContentRef(item.ref)?.principalId, {
2917
+ message: "a derived item must derive from content of the same principal"
2918
+ });
2919
+ z.union([DerivedMessageContent, OriginalMessageContent]);
2920
+ }
2921
+ });
2922
+ var STEP_KINDS, OWNERSHIP_MODES, WorkflowStep, ConnectorWorkflow;
2923
+ var init_onboarding = __esm({
2924
+ "../contracts/src/channel-connector/onboarding.ts"() {
2925
+ STEP_KINDS = [
2926
+ "FORM",
2927
+ "SECRET",
2928
+ "OAUTH",
2929
+ "APP_INSTALL",
2930
+ "RESOURCE_SELECT",
2931
+ "RESOURCE_CREATE",
2932
+ "REGISTER",
2933
+ "SUBMIT_APPROVAL",
2934
+ "WAIT",
2935
+ "ACTION_REQUIRED",
2936
+ "GATEWAY_CONNECT",
2937
+ "PROBE",
2938
+ "ACTIVATE"
2939
+ ];
2940
+ OWNERSHIP_MODES = ["platform_managed", "customer_owned"];
2941
+ new Set(OWNERSHIP_MODES);
2942
+ new Set(STEP_KINDS);
2943
+ WorkflowStep = z.object({
2944
+ id: z.string().min(1),
2945
+ kind: z.enum(STEP_KINDS),
2946
+ title: z.string().min(1),
2947
+ /** JSON Schema for FORM/SECRET inputs — schema-driven, no connector UI code. */
2948
+ schema: z.record(z.unknown()).optional(),
2949
+ /** Credential role for SECRET/OAUTH outputs. */
2950
+ role: z.string().optional(),
2951
+ approvalKind: z.string().optional(),
2952
+ resourceKind: z.string().optional(),
2953
+ mode: z.string().optional(),
2954
+ dependsOn: z.array(z.string()).default([]),
2955
+ optional: z.boolean().default(false),
2956
+ /**
2957
+ * Ownership modes this step applies to (absent = all). A REQUIRED step out of the run's mode is ownership-skipped
2958
+ * (`OUT_OF_MODE`), which is distinct from `optional` — the engine must skip it regardless of `optional` (#306 §9).
2959
+ */
2960
+ ownership: z.array(z.enum(OWNERSHIP_MODES)).optional(),
2961
+ repeatable: z.boolean().default(false),
2962
+ producesChannels: z.array(z.string()).default([])
2963
+ }).strict();
2964
+ ConnectorWorkflow = z.object({
2965
+ version: z.string().min(1),
2966
+ steps: z.array(WorkflowStep).min(1),
2967
+ /** What ACTIVATE requires (condition type ids). */
2968
+ activationGates: z.array(z.string()),
2969
+ /** The single step that re-establishes credentials. */
2970
+ reauth: z.object({ stepId: z.string() }).strict(),
2971
+ /** Steps that adopt existing resources on transfer / reconnect. */
2972
+ relink: z.object({ stepIds: z.array(z.string()) }).strict()
2973
+ }).strict();
2974
+ }
2975
+ });
2976
+ var EXCLUSIVITY, TIMEOUT_EFFECTS, RECONCILE_BY, ABSENCE_PROOF_METHODS, DUPLICATE_COSTS, RESOURCE_KIND_RE, ResourceKindDeclaration;
2977
+ var init_external_resources = __esm({
2978
+ "../contracts/src/channel-connector/external-resources.ts"() {
2979
+ init_ids2();
2980
+ EXCLUSIVITY = ["exclusive", "per_connection", "shared"];
2981
+ TIMEOUT_EFFECTS = ["UNKNOWN", "NO_EFFECT", "PROVIDER_ACCEPTS", "PROVIDER_REJECTS"];
2982
+ RECONCILE_BY = ["provider_ref", "correlation_tag", "listing", "none"];
2983
+ ABSENCE_PROOF_METHODS = ["provider_ref", "correlation_tag", "listing"];
2984
+ DUPLICATE_COSTS = ["low", "high"];
2985
+ RESOURCE_KIND_RE = /^[a-z0-9]+(\.[a-z0-9_]+)+$/;
2986
+ ResourceKindDeclaration = z.object({
2987
+ kind: z.string().regex(RESOURCE_KIND_RE, "resource kind must be a namespaced dotted id"),
2988
+ /** Customer-facing name of the kind (presentation only; the connector owns its provider vocabulary). */
2989
+ label: z.string().min(1).optional(),
2990
+ parentKinds: z.array(z.string()).default([]),
2991
+ /** Channels this kind enables (registry ids), if any. */
2992
+ channels: z.array(z.string()).default([]),
2993
+ /** An installation target (guild, workspace, tenant, business account). */
2994
+ isContainer: z.boolean().default(false),
2995
+ /** A channel identity (sender). */
2996
+ isSender: z.boolean().default(false),
2997
+ isRegulatory: z.boolean().default(false),
2998
+ exclusivity: z.enum(EXCLUSIVITY),
2999
+ /** Provider-side sharing cap, manifest DATA never a core constant (e.g. Meta WABA = 2). */
3000
+ maxHolders: z.number().int().positive().optional(),
3001
+ /** doc 06 `reconcile { by, absenceProof?, duplicateCost }`. */
3002
+ reconcile: z.object({
3003
+ by: z.enum(RECONCILE_BY),
3004
+ absenceProof: z.object({ minAgeSec: z.number().int().nonnegative(), method: z.enum(ABSENCE_PROOF_METHODS) }).strict().optional(),
3005
+ duplicateCost: z.enum(DUPLICATE_COSTS)
3006
+ }).strict(),
3007
+ /** doc 06 `release { destructive }`. */
3008
+ release: z.object({ destructive: z.boolean() }).strict().default({ destructive: false }),
3009
+ /** Timeout semantics for the effect that creates this kind (D-23). */
3010
+ createTimeoutEffect: z.enum(TIMEOUT_EFFECTS).default("UNKNOWN")
3011
+ }).strict().superRefine((decl, ctx) => {
3012
+ if (decl.isContainer && decl.exclusivity === "exclusive") {
3013
+ ctx.addIssue({ code: z.ZodIssueCode.custom, message: `container kind ${decl.kind} cannot be exclusive` });
3014
+ }
3015
+ if (decl.maxHolders !== void 0 && decl.exclusivity === "exclusive" && decl.maxHolders !== 1) {
3016
+ ctx.addIssue({ code: z.ZodIssueCode.custom, message: `exclusive kind ${decl.kind} cannot declare maxHolders > 1` });
3017
+ }
3018
+ if (decl.exclusivity === "shared" && (decl.isSender || decl.isContainer)) {
3019
+ ctx.addIssue({ code: z.ZodIssueCode.custom, message: `shared kind ${decl.kind} is referenced by value and cannot be a sender or container` });
3020
+ }
3021
+ if (decl.reconcile.absenceProof && decl.reconcile.by === "none") {
3022
+ ctx.addIssue({ code: z.ZodIssueCode.custom, message: `kind ${decl.kind}: absenceProof needs a reconcile method other than none` });
3023
+ }
3024
+ });
3025
+ }
3026
+ });
3027
+
3028
+ // ../contracts/src/channel-connector/approvals.ts
3029
+ var APPROVAL_KINDS;
3030
+ var init_approvals = __esm({
3031
+ "../contracts/src/channel-connector/approvals.ts"() {
3032
+ APPROVAL_KINDS = [
3033
+ "business_verification",
3034
+ "campaign_vetting",
3035
+ "toll_free_verification",
3036
+ "app_review",
3037
+ "privileged_permission_review",
3038
+ "admin_consent",
3039
+ "display_name_review",
3040
+ "template_review",
3041
+ "agent_launch",
3042
+ "brand_verification"
3043
+ ];
3044
+ new Set(APPROVAL_KINDS);
3045
+ }
3046
+ });
3047
+ var DEPLOYMENT_MODELS, CREDENTIAL_STRENGTHS, CREDENTIAL_ROLES, INSTALLATION_KINDS, AUTHORIZATION_METHODS, AUTHORIZATION_CAPABILITIES, AuthorizationMethodDeclaration, INBOUND_AUTH_AVAILABILITY, InboundAuthMethodDeclaration, MutationApprovalDeclaration, ReadinessGateSchema, FormatsSchema, LimitsSchema, DescriptorOverride, DecisionRequestDeclaration;
3048
+ var init_manifest = __esm({
3049
+ "../contracts/src/channel-connector/manifest.ts"() {
3050
+ init_identity();
3051
+ init_approvals();
3052
+ init_capability_state();
3053
+ init_channels();
3054
+ init_content_kinds();
3055
+ init_external_resources();
3056
+ init_ids2();
3057
+ init_onboarding();
3058
+ init_operations();
3059
+ init_onboarding();
3060
+ DEPLOYMENT_MODELS = ["per_tenant_app", "platform_app", "both"];
3061
+ CREDENTIAL_STRENGTHS = ["ephemeral", "brokered", "exported_static"];
3062
+ CREDENTIAL_ROLES = ["primary", "signing", "webhook", "refresh", "app_signing", "oauth_client", "partner"];
3063
+ INSTALLATION_KINDS = ["guild", "user", "workspace", "org", "tenant", "team", "business_account", "chat"];
3064
+ new Set(INSTALLATION_KINDS);
3065
+ z.string().regex(/^cred:[a-z0-9_-]+\/[^\s]+$/, "A5 credential ref `cred:<store>/<path>`");
3066
+ AUTHORIZATION_METHODS = ["oauth", "api_key", "api_secret", "account_token", "bot_token", "delegated_app", "installation_token", "certificate", "other"];
3067
+ AUTHORIZATION_CAPABILITIES = ["discover", "provision", "send", "receive", "manage_resources", "manage_compliance", "manage_webhooks", "manage_installations", "manage_accounts"];
3068
+ AuthorizationMethodDeclaration = z.object({
3069
+ id: z.string().min(1),
3070
+ method: z.enum(AUTHORIZATION_METHODS),
3071
+ credentialRoles: z.array(z.enum(CREDENTIAL_ROLES)).min(1),
3072
+ capabilities: z.array(z.enum(AUTHORIZATION_CAPABILITIES)).default([]),
3073
+ scope: z.enum(["account", "container"]).optional()
3074
+ }).strict();
3075
+ INBOUND_AUTH_AVAILABILITY = ["ga", "beta", "private_beta", "unavailable"];
3076
+ InboundAuthMethodDeclaration = z.object({
3077
+ id: z.string().min(1),
3078
+ verifier: z.string().min(1),
3079
+ credentialRole: z.enum(CREDENTIAL_ROLES),
3080
+ compatibleWith: z.array(z.object({ authMethodId: z.string().min(1), reusesAuthCredential: z.boolean() }).strict()).default([]),
3081
+ availability: z.enum(INBOUND_AUTH_AVAILABILITY).default("ga")
3082
+ }).strict();
3083
+ MutationApprovalDeclaration = z.object({
3084
+ effect: z.string().min(1),
3085
+ approval: z.enum(["explicit", "implicit"]),
3086
+ capturePriorState: z.boolean().default(false)
3087
+ }).strict();
3088
+ ReadinessGateSchema = z.object({
3089
+ condition: z.string().min(1),
3090
+ class: z.enum(CONDITION_CLASSES),
3091
+ blocks: z.union([z.literal("*"), z.array(z.string())]),
3092
+ ttlSec: z.number().int().nonnegative(),
3093
+ evidence: z.enum(["configuration", "runtime"]).optional()
3094
+ }).strict();
3095
+ FormatsSchema = z.record(
3096
+ z.enum(CONTENT_KINDS),
3097
+ z.object({ mime: z.array(z.string()).optional(), variants: z.array(z.string()).optional() }).strict()
3098
+ );
3099
+ LimitsSchema = z.object({
3100
+ textChars: z.number().int().positive().optional(),
3101
+ textBytes: z.number().int().positive().optional(),
3102
+ mediaBytes: z.number().int().positive().optional(),
3103
+ segments: z.boolean().optional(),
3104
+ perSecond: z.number().positive().optional(),
3105
+ perRecipientPerSecond: z.number().positive().optional(),
3106
+ formats: FormatsSchema.optional(),
3107
+ note: z.string().optional()
3108
+ }).strict();
3109
+ DescriptorOverride = z.object({
3110
+ version: z.string().min(1).optional(),
3111
+ operations: z.array(z.string()).optional(),
3112
+ direction: z.object({ inbound: z.boolean(), outbound: z.boolean() }).strict().optional(),
3113
+ initiation: z.object({ agentInitiated: z.enum(AGENT_INITIATION), userInitiated: z.boolean(), firstContactRule: z.string() }).strict().optional(),
3114
+ windows: z.array(
3115
+ z.object({
3116
+ kind: z.literal("reply"),
3117
+ durationSec: z.number().int().positive(),
3118
+ opensOn: z.enum(["inbound_message", "user_action"]),
3119
+ outside: z.enum(WINDOW_OUTSIDE)
3120
+ }).strict()
3121
+ ).optional(),
3122
+ templates: z.object({ required: z.enum(TEMPLATE_REQUIREMENT), reviewed: z.boolean(), categories: z.array(z.string()).optional() }).strict().optional(),
3123
+ surfaces: z.array(z.enum(SURFACES)).optional(),
3124
+ messageTypes: z.array(z.enum(CONTENT_KINDS)).optional(),
3125
+ limits: LimitsSchema.optional(),
3126
+ receipts: z.array(z.enum(RECEIPT_KINDS)).optional(),
3127
+ edits: z.object({ edit: z.boolean(), delete: z.boolean(), reactions: z.boolean(), commands: z.boolean() }).strict().optional(),
3128
+ transport: z.array(z.enum(TRANSPORT_KINDS)).optional(),
3129
+ identity: z.object({ peerIdKind: z.string(), peerIdScope: z.enum(PEER_ID_SCOPES) }).strict().optional(),
3130
+ requiredPermissions: z.array(z.string()).optional(),
3131
+ registration: z.array(z.object({ kind: z.string(), blocks: z.array(z.string()) }).strict()).optional(),
3132
+ readinessGates: z.array(ReadinessGateSchema).optional()
3133
+ }).strict();
3134
+ DecisionRequestDeclaration = z.object({
3135
+ kind: z.string().min(1),
3136
+ responseDeadlineSec: z.number().int().positive(),
3137
+ /** The provider's law when we stay silent. */
3138
+ defaultOutcome: z.enum(TIMEOUT_EFFECTS)
3139
+ }).strict();
3140
+ z.object({
3141
+ /** A7 `<publisher>/<name>`. */
3142
+ id: z.string().min(3),
3143
+ /** A7: semver. */
3144
+ version: z.string().regex(SEMVER_RE, "A7: plugin versions are semver"),
3145
+ displayName: z.string().min(1),
3146
+ publisher: z.string().min(1),
3147
+ /** A STABLE https URL of the provider's own console (the `provider_console` remediation carries it); absent ⇒ remediation has no link. */
3148
+ consoleUrl: z.string().url().optional(),
3149
+ /** The publisher's reverse-domain namespace (`com.acme`) under which every contributed extension id must sit; verified in ch. 05 (D-9). Absent for first-party connectors, which use registry ids only. */
3150
+ namespace: z.string().regex(NAMESPACE_RE, "namespace must be <root>.<label>").optional(),
3151
+ deployment: z.enum(DEPLOYMENT_MODELS),
3152
+ credentialStrength: z.enum(CREDENTIAL_STRENGTHS),
3153
+ /** Roles this connector's connections store refs under. */
3154
+ credentialRoles: z.array(z.enum(CREDENTIAL_ROLES)).default(["primary"]),
3155
+ /** Channels served, each with its descriptor diff over the baseline. */
3156
+ channels: z.array(z.object({ channel: z.string().min(1), descriptor: DescriptorOverride.default({}) }).strict()).min(1),
3157
+ /** Operations the connector implements (registry ids or its own extension ops). */
3158
+ operations: z.array(z.string()).min(1),
3159
+ /** Extension operations this connector defines — each MUST declare a mode. */
3160
+ extensionOperations: z.array(z.object({ id: z.string(), mode: z.enum(OPERATION_MODES), summary: z.string() }).strict()).default([]),
3161
+ onboarding: ConnectorWorkflow,
3162
+ resourceKinds: z.array(ResourceKindDeclaration).default([]),
3163
+ installationKinds: z.array(z.string()).default([]),
3164
+ transports: z.array(z.enum(TRANSPORT_KINDS)).min(1),
3165
+ /** doc 05 §2: transports the provider makes mutually exclusive (e.g. webhook vs long_poll). */
3166
+ transportsExclusive: z.array(z.array(z.enum(TRANSPORT_KINDS)).min(2)).default([]),
3167
+ /** Verifier id the ingress runs before parsing; required with the `webhook` transport. */
3168
+ webhookVerifier: z.string().min(1).optional(),
3169
+ /** Reconcile handler ids by resource kind (doc 02). */
3170
+ reconcilers: z.array(z.object({ resourceKind: z.string(), handler: z.string().min(1) }).strict()).default([]),
3171
+ /** Inbound decision requests with provider-defined defaults (D-23). */
3172
+ decisionRequests: z.array(DecisionRequestDeclaration).default([]),
3173
+ /** Identifier kinds contributed to the ch. 00 B-1 vocabulary. */
3174
+ identifierKinds: z.array(z.string()).default([]),
3175
+ /** Approval kinds used (registry or extension ids). */
3176
+ approvalKinds: z.array(z.string()).default([]),
3177
+ /** Attribution key names used under `platform_app` (doc 08 §4a step 3). */
3178
+ attributionKeys: z.array(z.string()).default([]),
3179
+ /** Quarantine window for PENDING_ATTRIBUTION (D-21), seconds. */
3180
+ pendingAttributionTtlSec: z.number().int().positive().default(7 * 86400),
3181
+ metadata: z.record(z.unknown()).default({}),
3182
+ /** Which relationship ownerships this connector serves (#306 §A/C1). A managed-capable connector MUST declare it explicitly; the default serves only the common OAuth-BYO connector. */
3183
+ ownershipModes: z.array(z.enum(OWNERSHIP_MODES)).min(1).default(["customer_owned"]),
3184
+ /** Authorization methods the connector supports (#306 §4/C3). */
3185
+ authorizationMethods: z.array(AuthorizationMethodDeclaration).default([]),
3186
+ /** Inbound-event authentication strategies (#306 §4/C4); `webhookVerifier` is the single-strategy shorthand, normalized by `resolveInboundAuthMethods`. */
3187
+ inboundAuthMethods: z.array(InboundAuthMethodDeclaration).default([]),
3188
+ /** Provider effects that require an explicit decision, keyed on stable effect ids (#306 L5/C5). */
3189
+ mutationApprovals: z.array(MutationApprovalDeclaration).default([])
3190
+ }).strict();
3191
+ }
3192
+ });
3193
+
3194
+ // ../contracts/src/channel-connector/index.ts
3195
+ var init_channel_connector = __esm({
3196
+ "../contracts/src/channel-connector/index.ts"() {
3197
+ init_ids2();
3198
+ init_operations();
3199
+ init_content_kinds();
3200
+ init_capability_state();
3201
+ init_onboarding();
3202
+ init_external_resources();
3203
+ init_approvals();
3204
+ init_channels();
3205
+ init_manifest();
3206
+ }
3207
+ });
3208
+ function isSafeMonitoringHref(href) {
3209
+ if (href !== href.trim() || UNSAFE_CHARS.test(href) || href.includes("\\") || /%2e|%2f|%5c|%25/i.test(href)) return false;
3210
+ if (href.startsWith("/")) {
3211
+ if (href.startsWith("//")) return false;
3212
+ try {
3213
+ const u = new URL(href, "https://dashboard.invalid");
3214
+ return u.origin === "https://dashboard.invalid" && u.pathname.startsWith("/dashboard/") && u.pathname + u.search + u.hash === href;
3215
+ } catch {
3216
+ return false;
3217
+ }
3218
+ }
3219
+ if (!href.startsWith("https://")) return false;
3220
+ try {
3221
+ const u = new URL(href);
3222
+ return u.protocol === "https:" && !u.username && !u.password && Boolean(u.hostname);
3223
+ } catch {
3224
+ return false;
3225
+ }
3226
+ }
3227
+ var Label, Short, Body, Iso, MonitoringAuthor, MonitoringTone, UNSAFE_CHARS, MonitoringBlock, PresentedCommunication, MonitoringChannelId, MonitoringChannelRef, MonitoredPeer, MonitoredConversationSummary, MonitoredCommunication, MonitoringUnlinkedHistory, MONITORING_DIRECTIONS, MONITORING_STATES, MONITORING_MAX_ID_FILTERS, MONITORING_MAX_CHANNEL_FILTERS;
3228
+ var init_monitoring = __esm({
3229
+ "../contracts/src/monitoring.ts"() {
3230
+ Label = z.string().trim().min(1).max(60);
3231
+ Short = z.string().max(300);
3232
+ Body = z.string().max(4e3);
3233
+ Iso = z.string().datetime();
3234
+ MonitoringAuthor = z.enum(["peer", "agent", "system"]);
3235
+ MonitoringTone = z.enum(["ok", "warn", "bad", "info", "neutral"]);
3236
+ UNSAFE_CHARS = /[\u0000-\u001f\u007f-\u009f\u00ad\u061c\u180e\u200b-\u200f\u202a-\u202e\u2060-\u2064\u2066-\u2069\ufeff\u{e0000}-\u{e007f}]/u;
3237
+ new RegExp(UNSAFE_CHARS.source, "gu");
3238
+ MonitoringBlock = z.discriminatedUnion("type", [
3239
+ /** One line of the conversation: what was said, by whom. */
3240
+ z.object({ type: z.literal("message"), author: MonitoringAuthor, text: Body, at: Iso.optional() }).strict(),
3241
+ /** Channel facts worth seeing at a glance (Discord server/channel, SMS from/to, call duration). */
3242
+ z.object({ type: z.literal("facts"), items: z.array(z.object({ label: Label, value: Short }).strict()).min(1).max(12) }).strict(),
3243
+ /** The communication's state in words, with a tone. */
3244
+ z.object({ type: z.literal("status"), label: Label, tone: MonitoringTone }).strict(),
3245
+ /** Somewhere to go: the provider's own view of it (https) or a deeper dashboard page. */
3246
+ z.object({ type: z.literal("link"), label: Label, href: z.string().max(2e3).refine(isSafeMonitoringHref, "href must be https or a /dashboard/ path") }).strict(),
3247
+ /** A dated lifecycle fact (sent, delivered, answered, ended). */
3248
+ z.object({ type: z.literal("event"), label: z.string().trim().min(1).max(120), at: Iso }).strict(),
3249
+ /** A spoken exchange, turn by turn. */
3250
+ z.object({
3251
+ type: z.literal("transcript"),
3252
+ turns: z.array(z.object({ author: MonitoringAuthor, text: Body, offsetMs: z.number().int().nonnegative().optional() }).strict()).max(500)
3253
+ }).strict()
3254
+ ]);
3255
+ PresentedCommunication = z.object({
3256
+ /** One line for lists ("Asked: What is 2 + 2?"). */
3257
+ summary: z.string().max(140).optional(),
3258
+ /**
3259
+ * Something new starts at this communication — e.g. the agent began a fresh session with no memory of what came
3260
+ * before. The page draws a labelled divider before it. Additive to v1: a reader that ignores it loses nothing else.
3261
+ */
3262
+ boundary: z.object({ label: z.string().trim().min(1).max(80) }).strict().optional(),
3263
+ blocks: z.array(MonitoringBlock).max(50)
3264
+ }).strict();
3265
+ MonitoringChannelId = z.string().regex(/^[a-z][a-z0-9_.-]{0,39}$/);
3266
+ MonitoringChannelRef = z.object({ id: z.string(), label: z.string() }).strict();
3267
+ MonitoredPeer = z.object({
3268
+ kind: z.string().nullable(),
3269
+ address: z.string().nullable(),
3270
+ /** From the channel's presenter (`describePeer`); null when it has no better name than the address. */
3271
+ displayName: z.string().nullable()
3272
+ }).strict();
3273
+ MonitoredConversationSummary = z.object({
3274
+ id: z.string().uuid(),
3275
+ status: z.string(),
3276
+ openedAt: z.string(),
3277
+ lastActivityAt: z.string(),
3278
+ agent: z.object({ id: z.string(), name: z.string() }).strict().nullable(),
3279
+ peer: MonitoredPeer,
3280
+ channels: z.array(MonitoringChannelRef),
3281
+ communications: z.number().int().nonnegative(),
3282
+ /** The latest INBOUND communication's one-liner (else the latest), as its channel presents it. */
3283
+ summary: z.string().nullable(),
3284
+ /** Something in it failed (a message not delivered, a call that failed). */
3285
+ failed: z.boolean(),
3286
+ /** A call in it is still in progress. */
3287
+ live: z.boolean()
3288
+ }).strict();
3289
+ MonitoredCommunication = z.object({
3290
+ id: z.string().uuid(),
3291
+ type: z.string(),
3292
+ channel: MonitoringChannelRef,
3293
+ direction: z.string(),
3294
+ status: z.string(),
3295
+ startedAt: z.string(),
3296
+ endedAt: z.string().nullable(),
3297
+ /** Which presenter shaped it (`vl/discord`, `vl/sms`, `generic`), for support. */
3298
+ presenter: z.string(),
3299
+ presented: PresentedCommunication
3300
+ }).strict();
3301
+ MonitoringUnlinkedHistory = z.object({ calls: z.boolean(), messages: z.boolean() }).strict();
3302
+ z.object({
3303
+ blocksVersion: z.number().int(),
3304
+ scope: z.string(),
3305
+ conversations: z.array(MonitoredConversationSummary),
3306
+ /** Opaque; pass back as `cursor` for the next (older) page. Null on the last page. */
3307
+ nextCursor: z.string().nullable(),
3308
+ /** History from before a channel was linked to the ledger (not shown here, 🔒 control-plane F8/F12). */
3309
+ unlinkedHistory: MonitoringUnlinkedHistory
3310
+ }).strict();
3311
+ z.object({
3312
+ blocksVersion: z.number().int(),
3313
+ scope: z.string(),
3314
+ conversation: MonitoredConversationSummary,
3315
+ /** Oldest first. The page holds the LATEST communications; `earlierCursor` fetches the ones before them. */
3316
+ communications: z.array(MonitoredCommunication),
3317
+ earlierCursor: z.string().nullable()
3318
+ }).strict();
3319
+ z.object({
3320
+ scope: z.string(),
3321
+ /** Channels a presenter currently shows, named by that presenter. */
3322
+ channels: z.array(MonitoringChannelRef),
3323
+ /** The Lines (numbers, bots) conversations in this scope arrived on. */
3324
+ lines: z.array(z.object({ id: z.string().uuid(), label: z.string() }).strict())
3325
+ }).strict();
3326
+ MONITORING_DIRECTIONS = ["inbound", "outbound"];
3327
+ MONITORING_STATES = ["live", "failed", "ok"];
3328
+ MONITORING_MAX_ID_FILTERS = 50;
3329
+ MONITORING_MAX_CHANNEL_FILTERS = 20;
3330
+ z.object({
3331
+ scope: z.enum(["env:dev", "env:stg", "env:prd"]).optional(),
3332
+ limit: z.coerce.number().int().min(1).max(100).optional(),
3333
+ cursor: z.string().max(200).optional(),
3334
+ from: z.string().datetime().optional(),
3335
+ to: z.string().datetime().optional(),
3336
+ channel: z.array(MonitoringChannelId).max(MONITORING_MAX_CHANNEL_FILTERS).optional(),
3337
+ direction: z.array(z.enum(MONITORING_DIRECTIONS)).max(2).optional(),
3338
+ line: z.array(z.string().uuid()).max(MONITORING_MAX_ID_FILTERS).optional(),
3339
+ agent: z.array(z.string().uuid()).max(MONITORING_MAX_ID_FILTERS).optional(),
3340
+ status: z.array(z.enum(MONITORING_STATES)).max(3).optional(),
3341
+ customer: z.string().trim().max(100).optional()
3342
+ }).strict();
3343
+ }
3344
+ });
3345
+ var WalletRefusalCode, WalletMode, WalletInvoiceStatus, WalletInvoice, WalletEntryKind, WalletEntry, TOPUP_MIN_USD, TOPUP_MAX_USD;
3346
+ var init_wallet = __esm({
3347
+ "../contracts/src/wallet.ts"() {
3348
+ WalletRefusalCode = z.enum([
3349
+ // Prepaid balance (or an invoiced credit limit) cannot cover the action.
3350
+ "insufficient_balance",
3351
+ // The wallet is frozen after a card dispute, until an operator unfreezes it.
3352
+ "account_frozen",
3353
+ // An invoice is unpaid past its 7 days' grace (B-19): new calls, messages and answers are refused; inbound calls are
3354
+ // still answered. Paying the invoice restores the workspace.
3355
+ "account_suspended",
3356
+ // The action's reservation already closed (e.g. a call ended or expired) — it cannot be re-admitted.
3357
+ "hold_closed"
3358
+ ]);
3359
+ z.object({
3360
+ error: WalletRefusalCode,
3361
+ code: WalletRefusalCode,
3362
+ availableUsd: z.string(),
3363
+ needUsd: z.string(),
3364
+ message: z.string()
3365
+ });
3366
+ WalletMode = z.enum(["prepaid", "invoiced"]);
3367
+ z.object({
3368
+ mode: WalletMode,
3369
+ status: z.enum(["active", "frozen"]),
3370
+ // Decimal strings, six places (the ledger's micro-dollar precision).
3371
+ balanceUsd: z.string(),
3372
+ // Reserved for calls in progress.
3373
+ heldUsd: z.string(),
3374
+ // balance − held (+ credit limit when invoiced); null = unlimited.
3375
+ availableUsd: z.string().nullable(),
3376
+ creditLimitUsd: z.string().nullable(),
3377
+ // A card saved by the first top-up — what auto top-up charges.
3378
+ cardOnFile: z.boolean(),
3379
+ autoTopup: z.object({ enabled: z.boolean(), thresholdUsd: z.string(), amountUsd: z.string() }),
3380
+ // B-19: 'past_due' = an invoice is overdue (a pay-now banner); 'suspended' = paused for it (or by an operator).
3381
+ standing: z.enum(["good", "past_due", "suspended"]),
3382
+ // Why it is suspended: an invoice unpaid past grace (paying restores it) or an operator (only support lifts it).
3383
+ suspension: z.enum(["unpaid_invoice", "operator"]).nullable(),
3384
+ // The oldest overdue invoice, when there is one.
3385
+ overdueInvoice: z.object({ amountUsd: z.string(), dueAt: z.string(), payUrl: z.string().nullable() }).nullable(),
3386
+ // Invoiced wallets: what the next invoice holds so far (charges since the last invoice); null when prepaid.
3387
+ uninvoicedUsd: z.string().nullable()
3388
+ });
3389
+ WalletInvoiceStatus = z.enum(["draft", "open", "paid", "void", "uncollectible"]);
3390
+ WalletInvoice = z.object({
3391
+ id: z.string(),
3392
+ // 'final' = issued when the workspace moved to prepaid.
3393
+ kind: z.enum(["monthly", "final"]),
3394
+ periodStart: z.string(),
3395
+ periodEnd: z.string(),
3396
+ amountUsd: z.string(),
3397
+ status: WalletInvoiceStatus,
3398
+ issuedAt: z.string().nullable(),
3399
+ dueAt: z.string().nullable(),
3400
+ paidAt: z.string().nullable(),
3401
+ // Stripe's hosted page to view and pay it (null until issued).
3402
+ payUrl: z.string().nullable()
3403
+ });
3404
+ z.object({ invoices: z.array(WalletInvoice) });
3405
+ WalletEntryKind = z.enum(["grant", "topup", "usage", "refund", "refund_reversal", "dispute", "dispute_reinstated", "adjustment", "invoice_payment"]);
3406
+ WalletEntry = z.object({
3407
+ id: z.string(),
3408
+ kind: WalletEntryKind,
3409
+ // Signed: a credit is positive, a debit negative.
3410
+ amountUsd: z.string(),
3411
+ balanceAfterUsd: z.string(),
3412
+ // The usage row a debit charged (usage entries only).
3413
+ usageEventId: z.string().nullable(),
3414
+ note: z.string().nullable(),
3415
+ createdAt: z.string()
3416
+ });
3417
+ z.object({
3418
+ entries: z.array(WalletEntry),
3419
+ // Pass as `before` for the next (older) page; null when there is none.
3420
+ nextBefore: z.string().nullable()
3421
+ });
3422
+ TOPUP_MIN_USD = 10;
3423
+ TOPUP_MAX_USD = 500;
3424
+ z.object({ amountUsd: z.number().int().min(TOPUP_MIN_USD).max(TOPUP_MAX_USD) });
3425
+ z.object({
3426
+ enabled: z.boolean(),
3427
+ thresholdUsd: z.number().min(1).max(TOPUP_MAX_USD),
3428
+ amountUsd: z.number().int().min(TOPUP_MIN_USD).max(TOPUP_MAX_USD)
3429
+ });
3430
+ }
3431
+ });
2105
3432
 
2106
3433
  // ../contracts/src/index.ts
2107
3434
  var init_src = __esm({
@@ -2120,6 +3447,8 @@ var init_src = __esm({
2120
3447
  init_forms();
2121
3448
  init_consultation();
2122
3449
  init_agent_config();
3450
+ init_agent_versions();
3451
+ init_providers();
2123
3452
  init_model_catalog();
2124
3453
  init_evaluations();
2125
3454
  init_tests();
@@ -2134,6 +3463,7 @@ var init_src = __esm({
2134
3463
  init_flow_validate();
2135
3464
  init_flow_builtins();
2136
3465
  init_compliance();
3466
+ init_sms_keywords();
2137
3467
  init_call_recording();
2138
3468
  init_outbound();
2139
3469
  init_connection();
@@ -2148,20 +3478,27 @@ var init_src = __esm({
2148
3478
  init_playbooks();
2149
3479
  init_playbook_versions();
2150
3480
  init_playbook_compile();
3481
+ init_effect_stack();
3482
+ init_engine();
3483
+ init_identity();
3484
+ init_interaction();
3485
+ init_channel_connector();
3486
+ init_monitoring();
3487
+ init_wallet();
2151
3488
  }
2152
3489
  });
2153
3490
 
2154
3491
  // src/runtime/graph/interpolate.ts
2155
- function interpolate(template, scope) {
3492
+ function interpolate(template, scope2) {
2156
3493
  if (!template || template.indexOf("{{") === -1) return template;
2157
3494
  return template.replace(TOKEN, (_match, expr) => {
2158
- const value = resolvePath(scope, expr.trim());
3495
+ const value = resolvePath(scope2, expr.trim());
2159
3496
  return value === void 0 || value === null ? "" : String(value);
2160
3497
  });
2161
3498
  }
2162
- function resolvePath(scope, path) {
3499
+ function resolvePath(scope2, path) {
2163
3500
  const parts = path.split(".").map((p) => p.trim()).filter((p) => p.length > 0);
2164
- let current = scope;
3501
+ let current = scope2;
2165
3502
  for (const part of parts) {
2166
3503
  if (current === null || typeof current !== "object") return void 0;
2167
3504
  const obj = current;
@@ -2211,6 +3548,20 @@ var init_builtins = __esm({
2211
3548
  init_src();
2212
3549
  }
2213
3550
  });
3551
+ function createChatClient(config = {}) {
3552
+ return new OpenAI({
3553
+ ...config.apiKey ? { apiKey: config.apiKey } : {},
3554
+ ...config.baseURL ? { baseURL: config.baseURL } : {},
3555
+ ...config.timeoutMs ? { timeout: config.timeoutMs } : {}
3556
+ });
3557
+ }
3558
+ var init_dist = __esm({
3559
+ "../llm-client/dist/index.js"() {
3560
+ }
3561
+ });
3562
+ function runWithOpenAIScope(client, fn) {
3563
+ return scope.run(client, fn);
3564
+ }
2214
3565
  function defaultOpenAIOptions() {
2215
3566
  if (!credential) return {};
2216
3567
  return {
@@ -2218,17 +3569,283 @@ function defaultOpenAIOptions() {
2218
3569
  ...credential.baseURL ? { baseURL: credential.baseURL } : {}
2219
3570
  };
2220
3571
  }
3572
+ function internalChatClient(opts) {
3573
+ const cred = defaultOpenAIOptions();
3574
+ return createChatClient({
3575
+ ...cred.apiKey ? { apiKey: cred.apiKey } : {},
3576
+ ...cred.baseURL ? { baseURL: cred.baseURL } : {},
3577
+ ...{}
3578
+ });
3579
+ }
2221
3580
  function defaultOpenAI() {
2222
- return cached ??= new OpenAI(defaultOpenAIOptions());
3581
+ return scope.getStore() ?? (cached ??= internalChatClient());
2223
3582
  }
2224
3583
  function hasOpenAiKey() {
2225
- return credential !== null || Boolean(process.env["OPENAI_API_KEY"]);
3584
+ return scope.getStore() !== void 0 || credential !== null || Boolean(process.env["OPENAI_API_KEY"]);
2226
3585
  }
2227
- var credential, cached;
3586
+ var credential, cached, scope;
2228
3587
  var init_openai_default = __esm({
2229
3588
  "src/runtime/openai-default.ts"() {
3589
+ init_dist();
2230
3590
  credential = null;
2231
3591
  cached = null;
3592
+ scope = new AsyncLocalStorage();
3593
+ }
3594
+ });
3595
+ var init_ssrf = __esm({
3596
+ "src/brain/ssrf.ts"() {
3597
+ }
3598
+ });
3599
+
3600
+ // src/brain/serialize.ts
3601
+ var init_serialize = __esm({
3602
+ "src/brain/serialize.ts"() {
3603
+ }
3604
+ });
3605
+
3606
+ // src/brain/transport-callback.ts
3607
+ var init_transport_callback = __esm({
3608
+ "src/brain/transport-callback.ts"() {
3609
+ init_serialize();
3610
+ }
3611
+ });
3612
+
3613
+ // src/brain/connector-llm.ts
3614
+ var init_connector_llm = __esm({
3615
+ "src/brain/connector-llm.ts"() {
3616
+ init_serialize();
3617
+ }
3618
+ });
3619
+ var init_wrap = __esm({
3620
+ "src/providers/wrap.ts"() {
3621
+ }
3622
+ });
3623
+
3624
+ // src/providers/index.ts
3625
+ async function importOptional(spec, hint) {
3626
+ try {
3627
+ return await import(spec);
3628
+ } catch {
3629
+ throw new Error(`${hint}: "${spec}" is not installed. Add it with: pnpm add ${spec}`);
3630
+ }
3631
+ }
3632
+ function withCreds(opts, creds2) {
3633
+ if (creds2?.apiKey) opts["apiKey"] = creds2.apiKey;
3634
+ if (creds2?.baseURL) opts["baseURL"] = creds2.baseURL;
3635
+ return opts;
3636
+ }
3637
+ var deepgram, openai, anthropic, cartesia, elevenlabs, assemblyai, google, silero;
3638
+ var init_providers2 = __esm({
3639
+ "src/providers/index.ts"() {
3640
+ init_ssrf();
3641
+ init_transport_callback();
3642
+ init_connector_llm();
3643
+ init_wrap();
3644
+ deepgram = {
3645
+ stt(options = {}) {
3646
+ return async () => {
3647
+ const dg = await importOptional("@voicelayer/agents-plugin-deepgram", "deepgram.stt");
3648
+ return new dg.STT(
3649
+ withCreds(
3650
+ { model: options.model ?? "nova-3", language: options.language ?? "en-US" },
3651
+ options
3652
+ )
3653
+ );
3654
+ };
3655
+ },
3656
+ tts(options = {}) {
3657
+ return async () => {
3658
+ const dg = await importOptional("@voicelayer/agents-plugin-deepgram", "deepgram.tts");
3659
+ return new dg.TTS(
3660
+ withCreds({ model: options.model ?? "aura-2-harmonia-en" }, options)
3661
+ );
3662
+ };
3663
+ }
3664
+ };
3665
+ openai = {
3666
+ llm(options = {}) {
3667
+ return async () => {
3668
+ const oa = await importOptional("@voicelayer/agents-plugin-openai", "openai.llm");
3669
+ return new oa.LLM(
3670
+ withCreds({ model: options.model ?? "gpt-4o-mini" }, options)
3671
+ );
3672
+ };
3673
+ },
3674
+ tts(options = {}) {
3675
+ return async () => {
3676
+ const oa = await importOptional("@voicelayer/agents-plugin-openai", "openai.tts");
3677
+ const opts = { model: options.model ?? "tts-1" };
3678
+ if (options.voice) opts["voice"] = options.voice;
3679
+ if (options.instructions) opts["instructions"] = options.instructions;
3680
+ return new oa.TTS(withCreds(opts, options));
3681
+ };
3682
+ },
3683
+ // Speech-to-speech via the OpenAI Realtime API. Returns a RealtimeProvider
3684
+ // that the SDK uses in place of the stt/llm/tts pipeline.
3685
+ realtime(options = {}) {
3686
+ return async () => {
3687
+ const oa = await importOptional("@voicelayer/agents-plugin-openai", "openai.realtime");
3688
+ const opts = {};
3689
+ if (options.model) opts["model"] = options.model;
3690
+ if (options.voice) opts["voice"] = options.voice;
3691
+ return new oa.realtime.RealtimeModel(withCreds(opts, options));
3692
+ };
3693
+ }
3694
+ };
3695
+ anthropic = {
3696
+ llm(options = {}) {
3697
+ return async () => {
3698
+ const an = await importOptional("@livekit/agents-plugin-anthropic", "anthropic.llm");
3699
+ return new an.LLM(
3700
+ withCreds({ model: options.model ?? "claude-sonnet-4" }, options)
3701
+ );
3702
+ };
3703
+ }
3704
+ };
3705
+ cartesia = {
3706
+ tts(options = {}) {
3707
+ return async () => {
3708
+ const ct = await importOptional("@voicelayer/agents-plugin-cartesia", "cartesia.tts");
3709
+ return new ct.TTS(options);
3710
+ };
3711
+ },
3712
+ stt(options = {}) {
3713
+ return async () => {
3714
+ const ct = await importOptional("@voicelayer/agents-plugin-cartesia", "cartesia.stt");
3715
+ const opts = {};
3716
+ if (options.model) opts["model"] = options.model;
3717
+ if (options.language) opts["language"] = options.language;
3718
+ return new ct.STT(withCreds(opts, options));
3719
+ };
3720
+ }
3721
+ };
3722
+ elevenlabs = {
3723
+ tts(options = {}) {
3724
+ return async () => {
3725
+ const el = await importOptional("@voicelayer/agents-plugin-elevenlabs", "elevenlabs.tts");
3726
+ const opts = {};
3727
+ if (options.voice) opts["voiceId"] = options.voice;
3728
+ if (options.model) opts["model"] = options.model;
3729
+ return new el.TTS(withCreds(opts, options));
3730
+ };
3731
+ }
3732
+ };
3733
+ assemblyai = {
3734
+ stt(options = {}) {
3735
+ return async () => {
3736
+ const aai = await importOptional("@voicelayer/agents-plugin-assemblyai", "assemblyai.stt");
3737
+ const opts = {};
3738
+ if (options.language) opts["language"] = options.language;
3739
+ return new aai.STT(withCreds(opts, options));
3740
+ };
3741
+ }
3742
+ };
3743
+ google = {
3744
+ llm(options = {}) {
3745
+ return async () => {
3746
+ const g = await importOptional("@voicelayer/agents-plugin-google", "google.llm");
3747
+ return new g.LLM(
3748
+ withCreds({ model: options.model ?? "gemini-2.0-flash" }, options)
3749
+ );
3750
+ };
3751
+ },
3752
+ realtime(options = {}) {
3753
+ return async () => {
3754
+ const g = await importOptional("@voicelayer/agents-plugin-google", "google.realtime");
3755
+ const opts = {};
3756
+ if (options.model) opts["model"] = options.model;
3757
+ if (options.voice) opts["voice"] = options.voice;
3758
+ return new g.beta.realtime.RealtimeModel(withCreds(opts, options));
3759
+ };
3760
+ }
3761
+ };
3762
+ silero = {
3763
+ vad() {
3764
+ return async () => {
3765
+ const sil = await importOptional("@voicelayer/agents-plugin-silero", "silero.vad");
3766
+ return await sil.VAD.load();
3767
+ };
3768
+ }
3769
+ };
3770
+ }
3771
+ });
3772
+
3773
+ // src/providers/registry.ts
3774
+ function entry(id, bits) {
3775
+ const identity = providerIdentity(id);
3776
+ if (!identity) throw new Error(`PROVIDER_REGISTRY: no provider identity in contracts for '${id}'`);
3777
+ return { name: identity.id, capabilities: identity.capabilities, ...bits };
3778
+ }
3779
+ var model, lang, voice, creds;
3780
+ var init_registry = __esm({
3781
+ "src/providers/registry.ts"() {
3782
+ init_providers2();
3783
+ init_src();
3784
+ model = (o) => o.model ? { model: o.model } : {};
3785
+ lang = (o) => o.language ? { language: o.language } : {};
3786
+ voice = (o) => o.voice ? { voice: o.voice } : {};
3787
+ creds = (o) => o.creds ?? {};
3788
+ ({
3789
+ deepgram: entry("deepgram", {
3790
+ failureClass: "vendor-api",
3791
+ engines: ["livekit"],
3792
+ stt: (o) => deepgram.stt({ ...model(o), ...lang(o), ...creds(o) }),
3793
+ // Deepgram folds the voice into the model id (aura-2-<name>-en): wire config
3794
+ // passes a voice, code config passes a model — both land as `model`.
3795
+ tts: (o) => {
3796
+ const m = o.voice ?? o.model;
3797
+ return deepgram.tts({ ...m ? { model: m } : {}, ...creds(o) });
3798
+ }
3799
+ }),
3800
+ openai: entry("openai", {
3801
+ failureClass: "vendor-api",
3802
+ engines: ["livekit", "text"],
3803
+ llm: (o) => openai.llm({ ...model(o), ...creds(o) }),
3804
+ tts: (o) => openai.tts({ ...voice(o), ...model(o), ...creds(o) }),
3805
+ realtime: (o) => openai.realtime({ ...model(o), ...voice(o), ...creds(o) })
3806
+ }),
3807
+ anthropic: entry("anthropic", {
3808
+ failureClass: "vendor-api",
3809
+ engines: ["livekit", "text"],
3810
+ llm: (o) => anthropic.llm({ ...model(o), ...creds(o) })
3811
+ }),
3812
+ google: entry("google", {
3813
+ failureClass: "vendor-api",
3814
+ engines: ["livekit", "text"],
3815
+ llm: (o) => google.llm({ ...model(o), ...creds(o) }),
3816
+ realtime: (o) => google.realtime({ ...model(o), ...voice(o), ...creds(o) })
3817
+ }),
3818
+ cartesia: entry("cartesia", {
3819
+ failureClass: "vendor-api",
3820
+ engines: ["livekit"],
3821
+ stt: (o) => cartesia.stt({ ...model(o), ...lang(o), ...creds(o) }),
3822
+ tts: (o) => cartesia.tts({ ...voice(o), ...model(o), ...creds(o) })
3823
+ }),
3824
+ elevenlabs: entry("elevenlabs", {
3825
+ failureClass: "vendor-api",
3826
+ engines: ["livekit"],
3827
+ tts: (o) => elevenlabs.tts({ ...voice(o), ...model(o), ...creds(o) })
3828
+ }),
3829
+ assemblyai: entry("assemblyai", {
3830
+ failureClass: "vendor-api",
3831
+ engines: ["livekit"],
3832
+ stt: (o) => assemblyai.stt({ ...lang(o), ...creds(o) })
3833
+ // no model knob
3834
+ }),
3835
+ silero: entry("silero", {
3836
+ failureClass: "local",
3837
+ engines: ["livekit"],
3838
+ vad: () => silero.vad()
3839
+ })
3840
+ });
3841
+ }
3842
+ });
3843
+
3844
+ // src/providers/llm.ts
3845
+ var init_llm = __esm({
3846
+ "src/providers/llm.ts"() {
3847
+ init_openai_default();
3848
+ init_registry();
2232
3849
  }
2233
3850
  });
2234
3851
 
@@ -2241,6 +3858,7 @@ function dotProduct(a, b) {
2241
3858
  }
2242
3859
  function createOpenAIEmbedder() {
2243
3860
  const cache = /* @__PURE__ */ new Map();
3861
+ const inflight = /* @__PURE__ */ new Map();
2244
3862
  const available = () => hasOpenAiKey();
2245
3863
  function remember(key, vec) {
2246
3864
  if (cache.size >= CACHE_MAX) {
@@ -2259,24 +3877,31 @@ function createOpenAIEmbedder() {
2259
3877
  if (!available() || !key) return null;
2260
3878
  const hit = cache.get(key);
2261
3879
  if (hit) return hit;
2262
- const controller = new AbortController();
2263
- const timer = setTimeout(() => controller.abort(), EMBED_TIMEOUT_MS);
2264
- try {
2265
- const res = await defaultOpenAI().embeddings.create(
2266
- { model: EMBED_MODEL, input: key },
2267
- { signal: controller.signal }
2268
- );
2269
- const vec = res.data[0]?.embedding;
2270
- if (!Array.isArray(vec)) return null;
2271
- return remember(key, vec);
2272
- } catch (err) {
2273
- console.warn("[graph] embed failed; router will fall back to judge", {
2274
- err: err instanceof Error ? err.message : String(err)
2275
- });
2276
- return null;
2277
- } finally {
2278
- clearTimeout(timer);
2279
- }
3880
+ const pending = inflight.get(key);
3881
+ if (pending) return pending;
3882
+ const request = (async () => {
3883
+ const controller = new AbortController();
3884
+ const timer = setTimeout(() => controller.abort(), EMBED_TIMEOUT_MS);
3885
+ try {
3886
+ const res = await defaultOpenAI().embeddings.create(
3887
+ { model: EMBED_MODEL, input: key },
3888
+ { signal: controller.signal }
3889
+ );
3890
+ const vec = res.data[0]?.embedding;
3891
+ if (!Array.isArray(vec)) return null;
3892
+ return remember(key, vec);
3893
+ } catch (err) {
3894
+ graphWarn("embed failed; router will fall back to judge", {
3895
+ err: err instanceof Error ? err.message : String(err)
3896
+ });
3897
+ return null;
3898
+ } finally {
3899
+ clearTimeout(timer);
3900
+ inflight.delete(key);
3901
+ }
3902
+ })();
3903
+ inflight.set(key, request);
3904
+ return request;
2280
3905
  },
2281
3906
  prewarm(text) {
2282
3907
  void this.embed(text).catch(() => {
@@ -2292,7 +3917,8 @@ function getEmbedder() {
2292
3917
  var EMBED_MODEL, ROUTE_THRESHOLD, EMBED_TIMEOUT_MS, CACHE_MAX, singleton, override;
2293
3918
  var init_embedder = __esm({
2294
3919
  "src/runtime/graph/embedder.ts"() {
2295
- init_openai_default();
3920
+ init_log();
3921
+ init_llm();
2296
3922
  EMBED_MODEL = process.env["VOICELAYER_EMBED_MODEL"] ?? "text-embedding-3-small";
2297
3923
  ROUTE_THRESHOLD = Number(process.env["VL_FLOW_ROUTE_THRESHOLD"] ?? "0.62");
2298
3924
  EMBED_TIMEOUT_MS = Number(process.env["VL_FLOW_EMBED_TIMEOUT_MS"] ?? "2500");
@@ -2354,7 +3980,7 @@ async function routeByEmbedding(nl, ctx) {
2354
3980
  if (!best || score > best.score) best = { to: edge.to, score };
2355
3981
  }
2356
3982
  if (best && best.score >= ROUTE_THRESHOLD) {
2357
- console.log("[graph] route: embedding", { to: best.to, score: Number(best.score.toFixed(3)) });
3983
+ graphLog("route: embedding", { to: best.to, score: Number(best.score.toFixed(3)) });
2358
3984
  return best.to;
2359
3985
  }
2360
3986
  return null;
@@ -2380,11 +4006,21 @@ async function evaluateCondition(condition, ctx) {
2380
4006
  function splitTop(text, op) {
2381
4007
  return text.split(op);
2382
4008
  }
4009
+ function conditionReadsSlots(condition) {
4010
+ const text = condition.trim();
4011
+ if (!text) return false;
4012
+ return splitTop(text, "||").flatMap((part) => splitTop(part, "&&")).map((atom) => atom.trim()).some((atom) => {
4013
+ const lower = atom.toLowerCase();
4014
+ if (lower === "true" || lower === "always" || lower === "false" || lower === "never") return false;
4015
+ if (TOOL_ATOM.test(atom) || NODE_ATOM.test(atom) || DTMF_ATOM.test(atom)) return false;
4016
+ return OP_ATOM.test(atom) || SLOT_PRESENCE_ATOM.test(atom) || BARE_ATOM.test(atom);
4017
+ });
4018
+ }
2383
4019
  function evaluateDeterministic(atom, ctx) {
2384
4020
  const lower = atom.toLowerCase();
2385
4021
  if (lower === "true" || lower === "always") return true;
2386
4022
  if (lower === "false" || lower === "never") return false;
2387
- const toolMatch = atom.match(/^tool[.:]([A-Za-z0-9_-]+)(?:\.([\w.]+))?\s*(==|!=)?\s*(.*)$/);
4023
+ const toolMatch = atom.match(TOOL_ATOM);
2388
4024
  if (toolMatch) {
2389
4025
  const [, id, path, op, rawVal] = toolMatch;
2390
4026
  const result = ctx.tools[id];
@@ -2398,7 +4034,7 @@ function evaluateDeterministic(atom, ctx) {
2398
4034
  const eq = String(actual ?? "").toLowerCase() === (rawVal ?? "").trim().toLowerCase();
2399
4035
  return op === "!=" ? !eq : eq;
2400
4036
  }
2401
- const nodeMatch = atom.match(/^node:([A-Za-z0-9_-]+)(?:\.([\w.]+))?\s*(==|!=)?\s*(.*)$/);
4037
+ const nodeMatch = atom.match(NODE_ATOM);
2402
4038
  if (nodeMatch) {
2403
4039
  if (!ctx.nodeResults) return void 0;
2404
4040
  const [, id, path, op, rawVal] = nodeMatch;
@@ -2410,14 +4046,12 @@ function evaluateDeterministic(atom, ctx) {
2410
4046
  const eq = String(actual ?? "").toLowerCase() === (rawVal ?? "").trim().toLowerCase();
2411
4047
  return op === "!=" ? !eq : eq;
2412
4048
  }
2413
- const dtmfMatch = atom.match(/^dtmf:\s*(.+)$/i);
4049
+ const dtmfMatch = atom.match(DTMF_ATOM);
2414
4050
  if (dtmfMatch) {
2415
4051
  if (ctx.lastDtmf === void 0) return void 0;
2416
4052
  return ctx.lastDtmf === dtmfMatch[1].trim();
2417
4053
  }
2418
- const opMatch = atom.match(
2419
- /^(!?)\s*(?:slot:)?(.+?)\s*(==|!=|=|contains|starts_with|greater_than|less_than|>=|<=|>|<)\s*(.+)$/i
2420
- );
4054
+ const opMatch = atom.match(OP_ATOM);
2421
4055
  if (opMatch) {
2422
4056
  const [, neg, nameRaw, op, valRaw] = opMatch;
2423
4057
  const raw = resolvePath(ctx.slots, nameRaw.trim());
@@ -2439,14 +4073,14 @@ function evaluateDeterministic(atom, ctx) {
2439
4073
  else res = value === target;
2440
4074
  return neg ? !res : res;
2441
4075
  }
2442
- const slotPres = atom.match(/^(!?)\s*slot:(.+?)\s*(present|empty)?$/i);
4076
+ const slotPres = atom.match(SLOT_PRESENCE_ATOM);
2443
4077
  if (slotPres) {
2444
4078
  const [, neg, nameRaw, kw] = slotPres;
2445
4079
  const present = hasSlot(ctx.slots, nameRaw.trim());
2446
4080
  const res = (kw ?? "").toLowerCase() === "empty" ? !present : present;
2447
4081
  return neg ? !res : res;
2448
4082
  }
2449
- const bare = atom.match(/^(!?)\s*([\w.-]+)\s*(present|empty)?$/i);
4083
+ const bare = atom.match(BARE_ATOM);
2450
4084
  if (bare) {
2451
4085
  const [, neg, name, kw] = bare;
2452
4086
  const present = hasSlot(ctx.slots, name);
@@ -2457,13 +4091,27 @@ function evaluateDeterministic(atom, ctx) {
2457
4091
  }
2458
4092
  async function pickOption(options, ctx, hint) {
2459
4093
  if (options.length === 0) return null;
2460
- const collected = Object.entries(ctx.slots).filter(([, v]) => v !== void 0 && v !== null && v !== "").map(([k, v]) => `${k.trim()}: ${String(v)}`).join("; ");
4094
+ return judgeSettled(ctx, (collected) => pickWith(options, collected, ctx.lastUtterance, hint));
4095
+ }
4096
+ function collectedOf(slots) {
4097
+ return Object.entries(slots).filter(([, v]) => v !== void 0 && v !== null && v !== "").map(([k, v]) => `${k.trim()}: ${String(v)}`).join("; ");
4098
+ }
4099
+ async function judgeSettled(ctx, run) {
4100
+ const before = collectedOf(ctx.slots);
4101
+ if (!ctx.settle) return run(before);
4102
+ const [speculative, fresh] = await Promise.all([run(before), ctx.settle()]);
4103
+ const after = collectedOf(fresh);
4104
+ if (after === before) return speculative;
4105
+ graphLog("judge: capture changed context; re-judging");
4106
+ return run(after);
4107
+ }
4108
+ async function pickWith(options, collected, lastUtterance, hint) {
2461
4109
  const list = options.map((o, i) => `${i + 1}. ${o.text}`).join("\n");
2462
4110
  const prompt = [
2463
4111
  "Pick which outcome best matches the current state of this voice conversation.",
2464
4112
  ...hint && hint.trim() ? [`What decides the path: ${hint.trim()}`] : [],
2465
4113
  `Information collected: ${collected || "(none)"}`,
2466
- `The caller most recently said: "${ctx.lastUtterance ?? ""}"`,
4114
+ `The caller most recently said: "${lastUtterance ?? ""}"`,
2467
4115
  "Outcomes:",
2468
4116
  list,
2469
4117
  'Answer with ONLY the outcome number, or "none" if nothing clearly matches.'
@@ -2488,7 +4136,7 @@ async function pickOption(options, ctx, hint) {
2488
4136
  if (!Number.isInteger(n) || n < 1 || n > options.length) return null;
2489
4137
  return options[n - 1]?.id ?? null;
2490
4138
  } catch (err) {
2491
- console.warn("[graph] option pick failed; treating as undecided", {
4139
+ graphWarn("option pick failed; treating as undecided", {
2492
4140
  err: err instanceof Error ? err.message : String(err)
2493
4141
  });
2494
4142
  return null;
@@ -2497,12 +4145,18 @@ async function pickOption(options, ctx, hint) {
2497
4145
  }
2498
4146
  }
2499
4147
  async function judge(condition, ctx) {
2500
- const collected = Object.entries(ctx.slots).filter(([, v]) => v !== void 0 && v !== null && v !== "").map(([k, v]) => `${k.trim()}: ${String(v)}`).join("; ");
4148
+ return judgeSettled(ctx, (collected) => judgeWith(condition, collected, ctx.lastUtterance));
4149
+ }
4150
+ async function judgeWith(condition, collected, lastUtterance) {
2501
4151
  const prompt = [
2502
4152
  "You judge whether a condition about an ongoing voice conversation is currently TRUE.",
2503
4153
  `Information collected: ${collected || "(none)"}`,
2504
- `The caller most recently said: "${ctx.lastUtterance ?? ""}"`,
4154
+ `The caller most recently said: "${lastUtterance ?? ""}"`,
2505
4155
  `Condition: ${condition}`,
4156
+ // Observed live: "Keystone Arena." (a site name) was judged to satisfy
4157
+ // "caller described a network problem". The judge must not fill in what
4158
+ // the caller probably meant — only what they actually said counts.
4159
+ 'Judge only from what the caller actually said and the information collected \u2014 never infer or assume what they probably meant. If what they said does not itself establish the condition, answer "no".',
2506
4160
  'Answer strictly "yes" or "no".'
2507
4161
  ].join("\n");
2508
4162
  const controller = new AbortController();
@@ -2522,7 +4176,7 @@ async function judge(condition, ctx) {
2522
4176
  );
2523
4177
  return (res.choices[0]?.message?.content ?? "").trim().toLowerCase().startsWith("y");
2524
4178
  } catch (err) {
2525
- console.warn("[graph] condition judge failed; treating as false", {
4179
+ graphWarn("condition judge failed; treating as false", {
2526
4180
  condition,
2527
4181
  err: err instanceof Error ? err.message : String(err)
2528
4182
  });
@@ -2531,13 +4185,20 @@ async function judge(condition, ctx) {
2531
4185
  clearTimeout(timer);
2532
4186
  }
2533
4187
  }
2534
- var JUDGE_TIMEOUT_MS;
4188
+ var JUDGE_TIMEOUT_MS, TOOL_ATOM, NODE_ATOM, DTMF_ATOM, OP_ATOM, SLOT_PRESENCE_ATOM, BARE_ATOM;
2535
4189
  var init_conditions = __esm({
2536
4190
  "src/runtime/graph/conditions.ts"() {
2537
- init_openai_default();
4191
+ init_log();
4192
+ init_llm();
2538
4193
  init_interpolate();
2539
4194
  init_embedder();
2540
4195
  JUDGE_TIMEOUT_MS = 4e3;
4196
+ TOOL_ATOM = /^tool[.:]([A-Za-z0-9_-]+)(?:\.([\w.]+))?\s*(==|!=)?\s*(.*)$/;
4197
+ NODE_ATOM = /^node:([A-Za-z0-9_-]+)(?:\.([\w.]+))?\s*(==|!=)?\s*(.*)$/;
4198
+ DTMF_ATOM = /^dtmf:\s*(.+)$/i;
4199
+ OP_ATOM = /^(!?)\s*(?:slot:)?(.+?)\s*(==|!=|=|contains|starts_with|greater_than|less_than|>=|<=|>|<)\s*(.+)$/i;
4200
+ SLOT_PRESENCE_ATOM = /^(!?)\s*slot:(.+?)\s*(present|empty)?$/i;
4201
+ BARE_ATOM = /^(!?)\s*([\w.-]+)\s*(present|empty)?$/i;
2541
4202
  }
2542
4203
  });
2543
4204
 
@@ -2580,7 +4241,7 @@ async function resolveBranch(branches, ctx, opts) {
2580
4241
  if (!best || score > best.score) best = { id: b.id, score };
2581
4242
  }
2582
4243
  if (best && best.score >= ROUTE_THRESHOLD) {
2583
- console.log("[graph] branch: embedding", { id: best.id, score: Number(best.score.toFixed(3)) });
4244
+ graphLog("branch: embedding", { id: best.id, score: Number(best.score.toFixed(3)) });
2584
4245
  return best.id;
2585
4246
  }
2586
4247
  }
@@ -2595,6 +4256,7 @@ async function resolveBranch(branches, ctx, opts) {
2595
4256
  var ELSE_BRANCH_ID;
2596
4257
  var init_branch_resolve = __esm({
2597
4258
  "src/runtime/graph/branch-resolve.ts"() {
4259
+ init_log();
2598
4260
  init_conditions();
2599
4261
  init_embedder();
2600
4262
  ELSE_BRANCH_ID = "else";
@@ -2694,13 +4356,18 @@ function scopeFor(state, deps) {
2694
4356
  function str(value) {
2695
4357
  return typeof value === "string" ? value : "";
2696
4358
  }
4359
+ async function settledSlots(deps) {
4360
+ await deps.utterances.settle?.();
4361
+ return deps.processRt.getData();
4362
+ }
2697
4363
  function evalCtxOf(state, deps) {
2698
4364
  return {
2699
4365
  slots: deps.processRt.getData(),
2700
4366
  tools: state.toolResults,
2701
4367
  nodeResults: state.nodeResults,
2702
4368
  ...state.lastUtterance !== void 0 ? { lastUtterance: state.lastUtterance } : {},
2703
- ...state.lastDtmf !== void 0 ? { lastDtmf: state.lastDtmf } : {}
4369
+ ...state.lastDtmf !== void 0 ? { lastDtmf: state.lastDtmf } : {},
4370
+ settle: () => settledSlots(deps)
2704
4371
  };
2705
4372
  }
2706
4373
  function deterministicEdgeTarget(edges, ctx) {
@@ -2715,7 +4382,7 @@ async function speak(text, deps, opts) {
2715
4382
  const allowInterruptions = opts?.allowInterruptions === true;
2716
4383
  if (deps.ctx.adapter) {
2717
4384
  const r = await deps.ctx.adapter.say(text, { allowInterruptions });
2718
- console.log("[graph] said", { chars: text.length, interrupted: r.interrupted });
4385
+ graphLog("said", { chars: text.length, interrupted: r.interrupted });
2719
4386
  return;
2720
4387
  }
2721
4388
  const session = deps.ctx.livekit.session;
@@ -2725,12 +4392,12 @@ async function speak(text, deps, opts) {
2725
4392
 
2726
4393
  ${text}`
2727
4394
  }).waitForPlayout();
2728
- console.log("[graph] said (realtime)", { chars: text.length });
4395
+ graphLog("said (realtime)", { chars: text.length });
2729
4396
  return;
2730
4397
  }
2731
4398
  const handle = session.say(text, { allowInterruptions });
2732
4399
  await handle.waitForPlayout();
2733
- console.log("[graph] said", { chars: text.length, interrupted: handle.interrupted });
4400
+ graphLog("said", { chars: text.length, interrupted: handle.interrupted });
2734
4401
  }
2735
4402
  async function genReply(deps, instructions) {
2736
4403
  if (deps.ctx.adapter) {
@@ -2739,10 +4406,13 @@ async function genReply(deps, instructions) {
2739
4406
  }
2740
4407
  await deps.ctx.livekit.session.generateReply({ instructions }).waitForPlayout();
2741
4408
  }
2742
- async function nextTurn(deps, reprompt) {
4409
+ async function nextTurn(deps, reprompt, opts) {
2743
4410
  let reprompted = false;
2744
4411
  for (; ; ) {
2745
- const evt = await deps.utterances.next({ timeoutMs: NO_INPUT_MS });
4412
+ const evt = await deps.utterances.next({
4413
+ timeoutMs: NO_INPUT_MS,
4414
+ ...opts?.deferCapture ? { deferCapture: true } : {}
4415
+ });
2746
4416
  if (evt.kind === "hangup") return { result: { kind: "end" } };
2747
4417
  if (evt.kind === "control") return { result: evt.result };
2748
4418
  if (evt.kind === "timeout") {
@@ -2816,6 +4486,7 @@ function menuLabelOf(label) {
2816
4486
  var ASK_MAX_CLARIFY, LLM_MAX_TURNS, HUB_MAX_TURNS, NO_INPUT_MS, sayExecutor, YES_WORDS, NO_WORDS, confirmExecutor, askExecutor, gateExecutor, endExecutor, handoffExecutor, llmExecutor, toolExecutor, ragExecutor, codeExecutor, decisionExecutor, MENU_MAX_INPUTS, menuExecutor, classifyExecutor, routerExecutor, playbookExecutor, setExecutor, MAX_SUBFLOW_DEPTH, subflowExecutor, passthroughExecutor, EXECUTORS;
2817
4487
  var init_executors = __esm({
2818
4488
  "src/runtime/graph/executors.ts"() {
4489
+ init_log();
2819
4490
  init_src();
2820
4491
  init_interpolate();
2821
4492
  init_builtins();
@@ -2948,6 +4619,7 @@ var init_executors = __esm({
2948
4619
  const policy = historyPolicyOf(node);
2949
4620
  const maxTurns = hub ? HUB_MAX_TURNS : LLM_MAX_TURNS;
2950
4621
  const base = interpolate(str(node.config.instructions), scopeFor(state, deps)) || "Help the caller using everything collected so far. Keep replies short and natural.";
4622
+ const deferCapture = !conditional.some((e) => conditionReadsSlots(e.condition));
2951
4623
  for (let turn = 0; turn < maxTurns; turn++) {
2952
4624
  const collected = formatCollected(deps.processRt.getData());
2953
4625
  const parts = [base];
@@ -2960,25 +4632,19 @@ ${collected}`);
2960
4632
  () => guardedGenerate(node, state, deps, parts.join("\n\n"), base)
2961
4633
  );
2962
4634
  if (!hub && !terminal && conditional.length === 0) return { kind: "advance" };
2963
- const t = await nextTurn(deps);
4635
+ const t = await nextTurn(deps, void 0, { deferCapture });
2964
4636
  if ("result" in t) return t.result;
2965
4637
  state.lastUtterance = t.text;
2966
4638
  state.lastDtmf = t.dtmf;
2967
4639
  if (conditional.length > 0) {
2968
- const target = await selectEdge(out, {
2969
- slots: deps.processRt.getData(),
2970
- tools: state.toolResults,
2971
- nodeResults: state.nodeResults,
2972
- lastDtmf: state.lastDtmf,
2973
- lastUtterance: state.lastUtterance
2974
- });
4640
+ const target = await selectEdge(out, evalCtxOf(state, deps));
2975
4641
  if (target) {
2976
- console.log("[graph] llm: transition", { node: node.id, to: target, hub });
4642
+ graphLog("llm: transition", { node: node.id, to: target, hub });
2977
4643
  return { kind: "goto", to: target };
2978
4644
  }
2979
4645
  }
2980
4646
  }
2981
- console.log("[graph] llm: turn cap reached", { node: node.id, hub });
4647
+ graphLog("llm: turn cap reached", { node: node.id, hub });
2982
4648
  return terminal ? { kind: "end" } : { kind: "advance" };
2983
4649
  };
2984
4650
  toolExecutor = async (node, state, deps) => {
@@ -3337,7 +5003,7 @@ ${collected}`);
3337
5003
  const goal = str(cfg.goal);
3338
5004
  let instr = str(cfg.instructions);
3339
5005
  const toolNames = Array.isArray(cfg.tools) ? cfg.tools.filter((x) => typeof x === "string" && x.length > 0) : [];
3340
- let model = str(cfg.model) || void 0;
5006
+ let model2 = str(cfg.model) || void 0;
3341
5007
  let temperature = typeof cfg.temperature === "number" ? cfg.temperature : void 0;
3342
5008
  let libraryExits = [];
3343
5009
  const ref = str(cfg.playbookRef);
@@ -3347,7 +5013,7 @@ ${collected}`);
3347
5013
  if (!instr) instr = resolved.instructions;
3348
5014
  for (const f of resolved.requiredFields) if (!required.includes(f)) required.push(f);
3349
5015
  for (const t of resolved.tools ?? []) if (!toolNames.includes(t)) toolNames.push(t);
3350
- if (!model && resolved.model) model = resolved.model;
5016
+ if (!model2 && resolved.model) model2 = resolved.model;
3351
5017
  if (temperature === void 0 && resolved.temperature !== void 0) temperature = resolved.temperature;
3352
5018
  libraryExits = resolved.exits ?? [];
3353
5019
  }
@@ -3401,7 +5067,7 @@ ${collected}`);
3401
5067
  exits: exitOptions,
3402
5068
  captureVars: required,
3403
5069
  runTool: (name, args) => registry ? registry.run(name, args) : Promise.resolve({ ok: false, status: 0, data: { error: "no tool registry" } }),
3404
- ...model ? { model } : {},
5070
+ ...model2 ? { model: model2 } : {},
3405
5071
  ...temperature !== void 0 ? { temperature } : {}
3406
5072
  });
3407
5073
  } finally {
@@ -3594,18 +5260,18 @@ var interpreter_exports = {};
3594
5260
  __export(interpreter_exports, {
3595
5261
  runFlowGraph: () => runFlowGraph
3596
5262
  });
3597
- function pickEntry(model) {
3598
- return model.entry.length > 0 ? model.entry[0] : null;
5263
+ function pickEntry(model2) {
5264
+ return model2.entry.length > 0 ? model2.entry[0] : null;
3599
5265
  }
3600
- async function selectNext(model, from, result, state, deps) {
5266
+ async function selectNext(model2, from, result, state, deps) {
3601
5267
  if (result.kind === "goto") return result.to;
3602
5268
  if (result.kind === "take") {
3603
- const target = model.selectByPort(from, result.port);
5269
+ const target = model2.selectByPort(from, result.port);
3604
5270
  if (target !== null) return target;
3605
- const def = model.out(from).find((e) => (!e.condition || !e.condition.trim()) && !e.sourceHandle);
5271
+ const def = model2.out(from).find((e) => (!e.condition || !e.condition.trim()) && !e.sourceHandle);
3606
5272
  return def ? def.to : null;
3607
5273
  }
3608
- return selectEdge(model.out(from), {
5274
+ return selectEdge(model2.out(from), {
3609
5275
  slots: deps.processRt.getData(),
3610
5276
  tools: state.toolResults,
3611
5277
  nodeResults: state.nodeResults,
@@ -3614,10 +5280,10 @@ async function selectNext(model, from, result, state, deps) {
3614
5280
  });
3615
5281
  }
3616
5282
  async function runFlowGraph(program, deps) {
3617
- const model = buildGraphModel(program);
5283
+ const model2 = buildGraphModel(program);
3618
5284
  const maxNodeVisits = deps.maxNodeVisits ?? DEFAULT_MAX_NODE_VISITS;
3619
5285
  const maxSteps = deps.maxSteps ?? DEFAULT_MAX_STEPS;
3620
- let cursor = pickEntry(model);
5286
+ let cursor = pickEntry(model2);
3621
5287
  if (cursor === null) return { kind: "completed" };
3622
5288
  const state = { toolResults: {}, nodeResults: {}, cursor };
3623
5289
  const visits = /* @__PURE__ */ new Map();
@@ -3627,7 +5293,7 @@ async function runFlowGraph(program, deps) {
3627
5293
  deps.events.note("flow.loop_guard_tripped", { reason: "max_steps", steps });
3628
5294
  break;
3629
5295
  }
3630
- const node = model.node(cursor);
5296
+ const node = model2.node(cursor);
3631
5297
  if (!node) {
3632
5298
  deps.events.note("flow.missing_node", { nodeId: cursor });
3633
5299
  break;
@@ -3641,7 +5307,7 @@ async function runFlowGraph(program, deps) {
3641
5307
  }
3642
5308
  state.cursor = cursor;
3643
5309
  deps.events.enter(node.id, node.type);
3644
- console.log("[graph] enter", { node: node.id, type: node.type, step: steps });
5310
+ graphLog("enter", { node: node.id, type: node.type, step: steps });
3645
5311
  const executor = EXECUTORS[node.type] ?? passthroughExecutor;
3646
5312
  let result;
3647
5313
  try {
@@ -3650,7 +5316,7 @@ async function runFlowGraph(program, deps) {
3650
5316
  processRt: deps.processRt,
3651
5317
  utterances: deps.utterances,
3652
5318
  events: deps.events,
3653
- model,
5319
+ model: model2,
3654
5320
  ...deps.history ? { history: deps.history } : {},
3655
5321
  ...deps.realtime ? { realtime: true } : {},
3656
5322
  ...deps.resolvers ? { resolvers: deps.resolvers } : {},
@@ -3664,17 +5330,19 @@ async function runFlowGraph(program, deps) {
3664
5330
  return { kind: "ended", nodeId: node.id };
3665
5331
  }
3666
5332
  deps.events.note("node.errored", { nodeId: node.id, error: message });
3667
- const recovery = model.selectByPort(node.id, "error");
5333
+ const recovery = model2.selectByPort(node.id, "error");
3668
5334
  if (recovery !== null) {
3669
5335
  deps.events.traverse(node.id, recovery);
3670
5336
  cursor = recovery;
3671
5337
  continue;
3672
5338
  }
3673
5339
  return { kind: "error", nodeId: node.id, message };
5340
+ } finally {
5341
+ await deps.utterances.settle?.();
3674
5342
  }
3675
5343
  if (result.kind === "end") return { kind: "ended", nodeId: node.id };
3676
5344
  if (result.kind === "handoff") return { kind: "handoff", to: result.to };
3677
- const next = await selectNext(model, cursor, result, state, deps);
5345
+ const next = await selectNext(model2, cursor, result, state, deps);
3678
5346
  if (next !== null) deps.events.traverse(cursor, next);
3679
5347
  cursor = next;
3680
5348
  }
@@ -3683,6 +5351,7 @@ async function runFlowGraph(program, deps) {
3683
5351
  var DEFAULT_MAX_NODE_VISITS, DEFAULT_MAX_STEPS, HUB_MAX_NODE_VISITS;
3684
5352
  var init_interpreter = __esm({
3685
5353
  "src/runtime/graph/interpreter.ts"() {
5354
+ init_log();
3686
5355
  init_graph_model();
3687
5356
  init_executors();
3688
5357
  init_conditions();
@@ -3693,15 +5362,33 @@ var init_interpreter = __esm({
3693
5362
  });
3694
5363
 
3695
5364
  // src/runtime/graph/index.ts
5365
+ init_log();
3696
5366
  init_interpreter();
3697
5367
 
3698
5368
  // src/runtime/graph/utterances.ts
5369
+ init_log();
3699
5370
  var EVENT_UTTERANCE = "user_input_transcribed";
3700
5371
  var EVENT_CLOSE = "close";
3701
- function createUtteranceStream(session, processRt, ctx, rails, prewarm, wantsDtmf) {
5372
+ var DEFAULT_MIN_DELAY_MS = 600;
5373
+ var DEFAULT_MAX_DELAY_MS = 3e3;
5374
+ var VAD_SILENCE_MS = 550;
5375
+ var STT_LAG_MS = 400;
5376
+ function createUtteranceStream(session, processRt, ctx, rails, prewarm, wantsDtmf, onAwaitInput, endpointing) {
3702
5377
  const queue = [];
3703
5378
  let waiter = null;
3704
5379
  let hung = false;
5380
+ let pending = null;
5381
+ const capture = async (text, deferred) => {
5382
+ const t0 = Date.now();
5383
+ try {
5384
+ await processRt.ingest(text, ctx);
5385
+ } catch {
5386
+ }
5387
+ if (deferred) graphLog("capture", { deferred: true, ms: Date.now() - t0 });
5388
+ };
5389
+ const settle = async () => {
5390
+ if (pending) await pending;
5391
+ };
3705
5392
  const deliver = (e) => {
3706
5393
  if (hung && e.kind !== "hangup") return;
3707
5394
  if (waiter) {
@@ -3722,13 +5409,72 @@ function createUtteranceStream(session, processRt, ctx, rails, prewarm, wantsDtm
3722
5409
  };
3723
5410
  const unsubs = [];
3724
5411
  if (ctx.adapter) {
5412
+ let onSettle2 = function() {
5413
+ settle2 = null;
5414
+ settleDue = Number.POSITIVE_INFINITY;
5415
+ const idle = Date.now() - lastActivity;
5416
+ if (speaking && idle < minDelay + STT_LAG_MS) settleAt(minDelay + STT_LAG_MS - idle);
5417
+ else flush();
5418
+ };
3725
5419
  const a = ctx.adapter;
5420
+ const voice2 = a.capabilities?.channel === "voice";
5421
+ const minDelay = Math.max(0, endpointing?.minDelay ?? DEFAULT_MIN_DELAY_MS);
5422
+ const maxDelay = Math.max(minDelay, endpointing?.maxDelay ?? DEFAULT_MAX_DELAY_MS);
5423
+ const parts = [];
5424
+ let speaking = false;
5425
+ let lastActivity = 0;
5426
+ let settle2 = null;
5427
+ let settleDue = Number.POSITIVE_INFINITY;
5428
+ let cap = null;
5429
+ const clearTimers = () => {
5430
+ if (settle2) clearTimeout(settle2);
5431
+ if (cap) clearTimeout(cap);
5432
+ settle2 = null;
5433
+ cap = null;
5434
+ settleDue = Number.POSITIVE_INFINITY;
5435
+ };
5436
+ const flush = () => {
5437
+ clearTimers();
5438
+ const text = parts.splice(0).join(" ").trim();
5439
+ if (!text) return;
5440
+ graphLog("turn", { chars: text.length, quietMs: Date.now() - lastActivity, vadSpeaking: speaking });
5441
+ deliver({ kind: "utterance", text });
5442
+ };
5443
+ const settleAt = (ms) => {
5444
+ if (settle2) clearTimeout(settle2);
5445
+ const wait = Math.max(0, ms);
5446
+ settleDue = Date.now() + wait;
5447
+ settle2 = setTimeout(onSettle2, wait);
5448
+ };
3726
5449
  unsubs.push(
3727
5450
  a.on("user_input", (e) => {
3728
- if (!e.isFinal || hung) return;
3729
- deliver({ kind: "utterance", text: e.transcript });
5451
+ if (hung) return;
5452
+ lastActivity = Date.now();
5453
+ if (!e.isFinal) {
5454
+ if (parts.length > 0) settleAt(minDelay);
5455
+ return;
5456
+ }
5457
+ const text = e.transcript.trim();
5458
+ if (!text) return;
5459
+ if (!voice2 || minDelay === 0) {
5460
+ deliver({ kind: "utterance", text });
5461
+ return;
5462
+ }
5463
+ parts.push(text);
5464
+ if (cap) clearTimeout(cap);
5465
+ cap = setTimeout(flush, maxDelay);
5466
+ settleAt(minDelay);
3730
5467
  })
3731
5468
  );
5469
+ unsubs.push(
5470
+ a.on("user_state", (e) => {
5471
+ speaking = e.speaking;
5472
+ if (speaking || parts.length === 0) return;
5473
+ const due = e.at - VAD_SILENCE_MS + minDelay;
5474
+ if (due < settleDue) settleAt(due - Date.now());
5475
+ })
5476
+ );
5477
+ unsubs.push(clearTimers);
3732
5478
  unsubs.push(a.once("close", onClose));
3733
5479
  if (wantsDtmf) {
3734
5480
  if (typeof ctx.onDtmf === "function") {
@@ -3796,10 +5542,12 @@ function createUtteranceStream(session, processRt, ctx, rails, prewarm, wantsDtm
3796
5542
  } else if (hung) {
3797
5543
  return { kind: "hangup" };
3798
5544
  } else {
5545
+ if (onAwaitInput) onAwaitInput();
3799
5546
  raw = await awaitRaw(opts?.timeoutMs);
3800
5547
  }
3801
5548
  if (raw.kind === "hangup") return { kind: "hangup" };
3802
5549
  if (raw.kind === "timeout") return { kind: "timeout" };
5550
+ await settle();
3803
5551
  if (raw.kind === "dtmf") return { kind: "dtmf", digit: raw.digit };
3804
5552
  if (rails) {
3805
5553
  const outcome = await rails.intercept(raw.text, ctx);
@@ -3807,13 +5555,18 @@ function createUtteranceStream(session, processRt, ctx, rails, prewarm, wantsDtm
3807
5555
  if (outcome.kind === "control") return { kind: "control", result: outcome.result };
3808
5556
  }
3809
5557
  if (prewarm) prewarm(raw.text);
3810
- try {
3811
- await processRt.ingest(raw.text, ctx);
3812
- } catch {
5558
+ if (opts?.deferCapture) {
5559
+ const run = capture(raw.text, true).then(() => {
5560
+ if (pending === run) pending = null;
5561
+ });
5562
+ pending = run;
5563
+ return { kind: "utterance", text: raw.text };
3813
5564
  }
5565
+ await capture(raw.text, false);
3814
5566
  return { kind: "utterance", text: raw.text };
3815
5567
  }
3816
5568
  },
5569
+ settle,
3817
5570
  close() {
3818
5571
  hung = true;
3819
5572
  for (const u of unsubs) u();
@@ -3822,6 +5575,7 @@ function createUtteranceStream(session, processRt, ctx, rails, prewarm, wantsDtm
3822
5575
  }
3823
5576
 
3824
5577
  // src/runtime/graph/history.ts
5578
+ init_log();
3825
5579
  function isConversational(it) {
3826
5580
  return it.type === "message" && (it.role === "user" || it.role === "assistant");
3827
5581
  }
@@ -3861,7 +5615,7 @@ function createHistoryController(agent) {
3861
5615
  next.items = prune(next.items, policy);
3862
5616
  await agent.updateChatCtx(next);
3863
5617
  } catch (err) {
3864
- console.warn("[graph] historyPolicy prune failed; using full history", {
5618
+ graphWarn("historyPolicy prune failed; using full history", {
3865
5619
  policy,
3866
5620
  err: err instanceof Error ? err.message : String(err)
3867
5621
  });
@@ -3874,7 +5628,7 @@ function createHistoryController(agent) {
3874
5628
  try {
3875
5629
  await agent.updateChatCtx(saved);
3876
5630
  } catch (err) {
3877
- console.warn("[graph] historyPolicy restore failed", {
5631
+ graphWarn("historyPolicy restore failed", {
3878
5632
  err: err instanceof Error ? err.message : String(err)
3879
5633
  });
3880
5634
  }
@@ -3911,13 +5665,17 @@ function createGraphEvents(processRt) {
3911
5665
  };
3912
5666
  }
3913
5667
 
5668
+ // src/runtime/graph/rails.ts
5669
+ init_log();
5670
+
3914
5671
  // src/runtime/graph/index.ts
3915
5672
  init_conditions();
3916
5673
  init_branch_resolve();
3917
5674
  init_embedder();
3918
5675
 
3919
5676
  // src/runtime/graph/agent-runner.ts
3920
- init_openai_default();
5677
+ init_log();
5678
+ init_llm();
3921
5679
  process.env["VOICELAYER_LLM_MODEL"] ?? process.env["OPENAI_LLM_MODEL"] ?? "gpt-4o";
3922
5680
  Number(process.env["VL_FLOW_AGENT_TURN_TIMEOUT_MS"] ?? "30000");
3923
5681
  Number(process.env["VL_FLOW_AGENT_MAX_TOOL_CALLS"] ?? "5");
@@ -3930,7 +5688,7 @@ init_graph_model();
3930
5688
  // src/runtime/graph/resolvers.ts
3931
5689
  init_src();
3932
5690
  init_embedder();
3933
- init_openai_default();
5691
+ init_llm();
3934
5692
  Number(process.env["VL_FLOW_CODE_TIMEOUT_MS"] ?? "1000");
3935
5693
  Number(process.env["VL_FLOW_CODE_MEMORY_BYTES"] ?? String(16 * 1024 * 1024));
3936
5694
 
@@ -3951,7 +5709,9 @@ async function runFlowProgram(program, ctx, opts) {
3951
5709
  ctx,
3952
5710
  opts.rails,
3953
5711
  prewarm,
3954
- wantsDtmf
5712
+ wantsDtmf,
5713
+ opts.onAwaitInput,
5714
+ opts.speech?.endpointing
3955
5715
  );
3956
5716
  const history = createHistoryController(opts.agent ?? null);
3957
5717
  try {
@@ -3967,15 +5727,16 @@ async function runFlowProgram(program, ctx, opts) {
3967
5727
  ...opts.resolvers ? { resolvers: opts.resolvers } : {},
3968
5728
  ...opts.busy ? { busy: opts.busy } : {}
3969
5729
  });
3970
- console.log("[graph] outcome", outcome);
5730
+ graphLog("outcome", outcome);
3971
5731
  return outcome;
3972
5732
  } finally {
5733
+ await utterances.settle?.();
3973
5734
  utterances.close();
3974
5735
  }
3975
5736
  }
3976
5737
 
3977
5738
  // src/runtime/process.ts
3978
- init_openai_default();
5739
+ init_llm();
3979
5740
 
3980
5741
  // ../primitives/process-schema/src/index.ts
3981
5742
  init_src();
@@ -4871,6 +6632,10 @@ function createTextSessionAdapter(transport) {
4871
6632
  await transport.send(text);
4872
6633
  return { interrupted: false };
4873
6634
  },
6635
+ async enqueueSay(text) {
6636
+ fire("agent_turn", { text });
6637
+ await transport.send(text);
6638
+ },
4874
6639
  async generateReply(opts) {
4875
6640
  if (transport.generate) {
4876
6641
  const text = await transport.generate(opts?.instructions);
@@ -4949,6 +6714,7 @@ function processSchemaToDefinition(schema) {
4949
6714
  }
4950
6715
 
4951
6716
  // src/runtime/text-session.ts
6717
+ init_openai_default();
4952
6718
  function buildTextContext(adapter, call, processRt) {
4953
6719
  return {
4954
6720
  call: {
@@ -5010,7 +6776,8 @@ function runTextSession(opts) {
5010
6776
  // createUtteranceStream prefers ctx.adapter; the session arg is unused here.
5011
6777
  session: {},
5012
6778
  events,
5013
- ...opts.maxSteps !== void 0 ? { maxSteps: opts.maxSteps } : {}
6779
+ ...opts.maxSteps !== void 0 ? { maxSteps: opts.maxSteps } : {},
6780
+ ...opts.onAwaitInput ? { onAwaitInput: opts.onAwaitInput } : {}
5014
6781
  });
5015
6782
  return {
5016
6783
  sendUserMessage: (text) => handle.push(text),
@@ -5056,12 +6823,12 @@ function definitionFor(opts) {
5056
6823
  }
5057
6824
  async function runTextTranscript(opts) {
5058
6825
  const replies = [];
5059
- const trace = [];
6826
+ const trace2 = [];
5060
6827
  const processRt = opts.processRt ?? createProcessRuntime(definitionFor(opts));
5061
6828
  const session = runTextSession({
5062
6829
  program: opts.program,
5063
6830
  onAgentText: (t) => void replies.push(t),
5064
- onTrace: (e) => void trace.push(e),
6831
+ onTrace: (e) => void trace2.push(e),
5065
6832
  ...processRt ? { processRt } : {},
5066
6833
  ...opts.generate ? { generate: opts.generate } : {},
5067
6834
  ...opts.call ? { call: opts.call } : {},
@@ -5070,7 +6837,57 @@ async function runTextTranscript(opts) {
5070
6837
  for (const m of opts.messages) session.sendUserMessage(m);
5071
6838
  session.end();
5072
6839
  const outcome = await session.result;
5073
- return { replies, outcome, trace };
6840
+ return { replies, outcome, trace: trace2 };
6841
+ }
6842
+ function runLiveTextConversation(opts) {
6843
+ let buffer = [];
6844
+ let settle = null;
6845
+ let parkedPending = false;
6846
+ let ended = false;
6847
+ let outcome;
6848
+ const signal = () => {
6849
+ if (settle) {
6850
+ const s = settle;
6851
+ settle = null;
6852
+ s();
6853
+ } else {
6854
+ parkedPending = true;
6855
+ }
6856
+ };
6857
+ const processRt = opts.processRt ?? createProcessRuntime(definitionFor({ program: opts.program, ...opts.schema ? { schema: opts.schema } : {}, messages: [] }));
6858
+ const session = runTextSession({
6859
+ ...opts,
6860
+ processRt,
6861
+ onAgentText: (t) => {
6862
+ buffer.push(t);
6863
+ return opts.onAgentText?.(t);
6864
+ },
6865
+ onAwaitInput: signal
6866
+ });
6867
+ void session.result.then((o) => {
6868
+ outcome = o;
6869
+ ended = true;
6870
+ signal();
6871
+ });
6872
+ const collect = async () => {
6873
+ if (!parkedPending) await new Promise((res) => settle = res);
6874
+ parkedPending = false;
6875
+ const replies = buffer.slice();
6876
+ buffer = [];
6877
+ return { replies, done: ended, ...ended && outcome ? { outcome } : {} };
6878
+ };
6879
+ return {
6880
+ start: () => collect(),
6881
+ async turn(text) {
6882
+ if (ended) return { replies: [], done: true, ...outcome ? { outcome } : {} };
6883
+ session.sendUserMessage(text);
6884
+ return collect();
6885
+ },
6886
+ async end() {
6887
+ session.end();
6888
+ return session.result;
6889
+ }
6890
+ };
5074
6891
  }
5075
6892
 
5076
- export { runTextSession, runTextTranscript };
6893
+ export { runLiveTextConversation, runTextSession, runTextTranscript, runWithOpenAIScope };