@voicelayer/sdk 0.1.10 → 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,218 @@ 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
+ }));
1903
+ }
1904
+ });
1905
+ var E164, RoomPrefix, RecordingPrefix, Cidr, TelephonySettings, ProfileSource, EffectiveCallProfile;
1906
+ var init_telephony = __esm({
1907
+ "../contracts/src/telephony.ts"() {
1908
+ E164 = z.string().regex(/^\+[1-9]\d{6,14}$/, "must be E.164 (+15551234567)");
1909
+ RoomPrefix = z.string().regex(/^[a-z0-9][a-z0-9-]{0,30}[a-z0-9-]?$/, "lowercase letters, digits, dashes; max 32");
1910
+ RecordingPrefix = z.string().min(1).max(128).refine((v) => !v.startsWith("/") && !v.includes(".."), "relative key prefix only");
1911
+ Cidr = z.string().regex(/^\d{1,3}(\.\d{1,3}){3}\/\d{1,2}$/, "must be IPv4 CIDR (a.b.c.d/nn)");
1912
+ TelephonySettings = z.object({
1913
+ outboundTrunkId: z.string().min(1).max(128).nullable().default(null),
1914
+ defaultFromE164: E164.nullable().default(null),
1915
+ roomPrefix: RoomPrefix.nullable().default(null),
1916
+ sipAllowedCidrs: z.array(Cidr).max(32).nullable().default(null),
1917
+ recordingPrefix: RecordingPrefix.nullable().default(null),
1918
+ strictByok: z.boolean().default(false)
1919
+ });
1920
+ z.object({
1921
+ outboundTrunkId: z.string().min(1).max(128).nullable().optional(),
1922
+ defaultFromE164: E164.nullable().optional(),
1923
+ roomPrefix: RoomPrefix.nullable().optional(),
1924
+ sipAllowedCidrs: z.array(Cidr).max(32).nullable().optional(),
1925
+ recordingPrefix: RecordingPrefix.nullable().optional(),
1926
+ strictByok: z.boolean().optional()
1927
+ }).refine((v) => Object.keys(v).length > 0, { message: "nothing to update" });
1928
+ ProfileSource = z.enum(["org", "platform", "none"]);
1929
+ EffectiveCallProfile = z.object({
1930
+ projectId: z.string(),
1931
+ outboundTrunkId: z.string().nullable(),
1932
+ defaultFromE164: z.string().nullable(),
1933
+ roomPrefix: z.string(),
1934
+ sipAllowedCidrs: z.array(z.string()),
1935
+ recordingPrefix: z.string(),
1936
+ strictByok: z.boolean(),
1937
+ sources: z.object({
1938
+ outboundTrunkId: ProfileSource,
1939
+ defaultFromE164: ProfileSource,
1940
+ roomPrefix: ProfileSource,
1941
+ sipAllowedCidrs: ProfileSource,
1942
+ recordingPrefix: ProfileSource
1943
+ })
1944
+ });
1945
+ z.object({
1946
+ settings: TelephonySettings,
1947
+ profile: EffectiveCallProfile
1948
+ });
1949
+ }
1950
+ });
1951
+ var SHORTENER_HOSTS, PLACEHOLDER_HOSTS, PublicUrl, A2pBusinessType, A2pBusinessInfo, A2pOptInType, A2pUseCase, A2pMessagingProfile, A2pState;
1952
+ var init_a2p = __esm({
1953
+ "../contracts/src/a2p.ts"() {
1954
+ SHORTENER_HOSTS = [
1955
+ "bit.ly",
1956
+ "tinyurl.com",
1957
+ "t.co",
1958
+ "goo.gl",
1959
+ "ow.ly",
1960
+ "is.gd",
1961
+ "buff.ly",
1962
+ "rebrand.ly",
1963
+ "cutt.ly",
1964
+ "shorturl.at",
1965
+ "tiny.cc"
1966
+ ];
1967
+ PLACEHOLDER_HOSTS = ["acme.com", "example.com", "example.org", "test.com", "localhost"];
1968
+ PublicUrl = z.string().url().refine((v) => v.startsWith("https://"), "must be https").refine((v) => {
1969
+ try {
1970
+ const host = new URL(v).hostname.toLowerCase().replace(/^www\./, "");
1971
+ return !SHORTENER_HOSTS.includes(host);
1972
+ } catch {
1973
+ return false;
1974
+ }
1975
+ }, "public URL shorteners are rejected by carriers \u2014 use your own domain").refine((v) => {
1976
+ try {
1977
+ const host = new URL(v).hostname.toLowerCase().replace(/^www\./, "");
1978
+ return !PLACEHOLDER_HOSTS.some((p) => host === p || host.endsWith(`.${p}`));
1979
+ } catch {
1980
+ return false;
1981
+ }
1982
+ }, "placeholder domain \u2014 reviewers will follow this link and reject the campaign");
1983
+ A2pBusinessType = z.enum([
1984
+ "Sole Proprietorship",
1985
+ "Partnership",
1986
+ "Corporation",
1987
+ "Co-operative",
1988
+ "Limited Liability Corporation",
1989
+ "Non-profit Corporation"
1990
+ ]);
1991
+ A2pBusinessInfo = z.object({
1992
+ legalName: z.string().trim().min(2).max(200),
1993
+ /** EIN (US) or equivalent registration number. */
1994
+ registrationNumber: z.string().trim().min(4).max(50),
1995
+ businessType: A2pBusinessType,
1996
+ /** Publicly reachable production site — not staging, not a 404. */
1997
+ website: PublicUrl,
1998
+ industry: z.string().trim().min(2).max(60),
1999
+ address: z.object({
2000
+ street: z.string().trim().min(2).max(200),
2001
+ city: z.string().trim().min(1).max(100),
2002
+ region: z.string().trim().min(1).max(100),
2003
+ postalCode: z.string().trim().min(2).max(20),
2004
+ isoCountry: z.string().trim().length(2)
2005
+ }),
2006
+ authorizedRep: z.object({
2007
+ firstName: z.string().trim().min(1).max(100),
2008
+ lastName: z.string().trim().min(1).max(100),
2009
+ email: z.string().trim().email(),
2010
+ phone: z.string().trim().regex(/^\+[1-9]\d{6,14}$/, "must be E.164"),
2011
+ jobTitle: z.string().trim().min(2).max(100)
2012
+ })
2013
+ });
2014
+ A2pOptInType = z.enum(["WEB_FORM", "PAPER_FORM", "VERBAL", "VIA_TEXT", "MOBILE_QR_CODE"]);
2015
+ A2pUseCase = z.enum([
2016
+ "MIXED",
2017
+ "CUSTOMER_CARE",
2018
+ "MARKETING",
2019
+ "ACCOUNT_NOTIFICATION",
2020
+ "2FA",
2021
+ "DELIVERY_NOTIFICATION",
2022
+ "HIGHER_EDUCATION",
2023
+ "POLLING_VOTING",
2024
+ "PUBLIC_SERVICE_ANNOUNCEMENT",
2025
+ "LOW_VOLUME"
2026
+ ]);
2027
+ A2pMessagingProfile = z.object({
2028
+ useCase: A2pUseCase,
2029
+ /** Specific, not generic. "We send texts" gets rejected; describe the actual
2030
+ * messages and when they are sent. */
2031
+ description: z.string().trim().min(40).max(4096),
2032
+ /**
2033
+ * The single most-rejected field. Must describe HOW people opt in, state the
2034
+ * message frequency, include the "message and data rates may apply"
2035
+ * disclosure, and link to publicly reachable evidence. Twilio's API bounds it
2036
+ * to 40–2049 characters.
2037
+ */
2038
+ messageFlow: z.string().trim().min(40).max(2049),
2039
+ optInType: A2pOptInType,
2040
+ /** Publicly accessible screenshots/pages showing the opt-in. Reviewers open
2041
+ * these; anything behind a login fails. */
2042
+ optInEvidenceUrls: z.array(PublicUrl).min(1).max(5),
2043
+ /** Real messages the customer will send. Must reflect the declared use case
2044
+ * and carry opt-out language. */
2045
+ messageSamples: z.array(z.string().trim().min(10).max(1024)).min(2).max(5),
2046
+ privacyPolicyUrl: PublicUrl,
2047
+ termsAndConditionsUrl: PublicUrl,
2048
+ hasEmbeddedLinks: z.boolean().default(false),
2049
+ hasEmbeddedPhone: z.boolean().default(false)
2050
+ });
2051
+ z.object({
2052
+ business: A2pBusinessInfo,
2053
+ messaging: A2pMessagingProfile,
2054
+ /** Register against Twilio's mock endpoints — exercises the full pipeline
2055
+ * with no fees and no real carrier submission. Used in CI and staging. */
2056
+ mock: z.boolean().default(false)
2057
+ }).superRefine((v, ctx) => {
2058
+ const flow = v.messaging.messageFlow.toLowerCase();
2059
+ if (!/(msg|message)\s*(&|and)\s*data rates/.test(flow)) {
2060
+ ctx.addIssue({
2061
+ code: z.ZodIssueCode.custom,
2062
+ path: ["messaging", "messageFlow"],
2063
+ message: 'must include a "Message and data rates may apply" disclosure \u2014 carriers reject without it'
2064
+ });
2065
+ }
2066
+ if (!/\d/.test(flow) || !/(msg|message|text)/.test(flow)) {
2067
+ ctx.addIssue({
2068
+ code: z.ZodIssueCode.custom,
2069
+ path: ["messaging", "messageFlow"],
2070
+ message: 'must state message frequency, e.g. "Up to 4 msgs/month"'
2071
+ });
2072
+ }
2073
+ const hasOptOut = v.messaging.messageSamples.some((s) => /stop/i.test(s));
2074
+ if (!hasOptOut) {
2075
+ ctx.addIssue({
2076
+ code: z.ZodIssueCode.custom,
2077
+ path: ["messaging", "messageSamples"],
2078
+ message: 'at least one sample must include opt-out language (e.g. "Reply STOP to opt out")'
2079
+ });
2080
+ }
2081
+ });
2082
+ A2pState = z.enum([
2083
+ "none",
2084
+ "profile_pending",
2085
+ "profile_approved",
2086
+ "profile_failed",
2087
+ "brand_pending",
2088
+ "brand_approved",
2089
+ "brand_failed",
2090
+ "campaign_pending",
2091
+ "messaging_ready",
2092
+ "campaign_failed"
2093
+ ]);
2094
+ z.object({
2095
+ state: A2pState,
2096
+ customerProfileSid: z.string().nullable(),
2097
+ trustProductSid: z.string().nullable(),
2098
+ brandSid: z.string().nullable(),
2099
+ messagingServiceSid: z.string().nullable(),
2100
+ campaignSid: z.string().nullable(),
2101
+ mock: z.boolean(),
2102
+ /** Carrier/Twilio rejection details, surfaced verbatim so the customer can
2103
+ * fix the specific field rather than guess. */
2104
+ failures: z.array(z.object({ code: z.number().nullable(), field: z.string().nullable(), message: z.string() })).default([]),
2105
+ /** Plain-language next step for the dashboard. */
2106
+ nextAction: z.string().nullable(),
2107
+ updatedAt: z.string().nullable()
2108
+ });
1628
2109
  }
1629
2110
  });
1630
2111
  var ConnectorMode, ConnectorStatus, ConnectorNormalize;
@@ -1752,6 +2233,7 @@ var init_flow_enrichment = __esm({
1752
2233
  var init_flow_compile = __esm({
1753
2234
  "../contracts/src/flow-compile.ts"() {
1754
2235
  init_flow_enrichment();
2236
+ init_flow_validate();
1755
2237
  }
1756
2238
  });
1757
2239
  var EnvironmentSpec;
@@ -1896,6 +2378,1057 @@ var init_playbook_compile = __esm({
1896
2378
  "../contracts/src/playbook-compile.ts"() {
1897
2379
  }
1898
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
+ });
1899
3432
 
1900
3433
  // ../contracts/src/index.ts
1901
3434
  var init_src = __esm({
@@ -1914,6 +3447,8 @@ var init_src = __esm({
1914
3447
  init_forms();
1915
3448
  init_consultation();
1916
3449
  init_agent_config();
3450
+ init_agent_versions();
3451
+ init_providers();
1917
3452
  init_model_catalog();
1918
3453
  init_evaluations();
1919
3454
  init_tests();
@@ -1928,9 +3463,12 @@ var init_src = __esm({
1928
3463
  init_flow_validate();
1929
3464
  init_flow_builtins();
1930
3465
  init_compliance();
3466
+ init_sms_keywords();
1931
3467
  init_call_recording();
1932
3468
  init_outbound();
1933
3469
  init_connection();
3470
+ init_telephony();
3471
+ init_a2p();
1934
3472
  init_connector();
1935
3473
  init_connector_stream();
1936
3474
  init_flow_compile();
@@ -1940,20 +3478,27 @@ var init_src = __esm({
1940
3478
  init_playbooks();
1941
3479
  init_playbook_versions();
1942
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();
1943
3488
  }
1944
3489
  });
1945
3490
 
1946
3491
  // src/runtime/graph/interpolate.ts
1947
- function interpolate(template, scope) {
3492
+ function interpolate(template, scope2) {
1948
3493
  if (!template || template.indexOf("{{") === -1) return template;
1949
3494
  return template.replace(TOKEN, (_match, expr) => {
1950
- const value = resolvePath(scope, expr.trim());
3495
+ const value = resolvePath(scope2, expr.trim());
1951
3496
  return value === void 0 || value === null ? "" : String(value);
1952
3497
  });
1953
3498
  }
1954
- function resolvePath(scope, path) {
3499
+ function resolvePath(scope2, path) {
1955
3500
  const parts = path.split(".").map((p) => p.trim()).filter((p) => p.length > 0);
1956
- let current = scope;
3501
+ let current = scope2;
1957
3502
  for (const part of parts) {
1958
3503
  if (current === null || typeof current !== "object") return void 0;
1959
3504
  const obj = current;
@@ -2003,6 +3548,20 @@ var init_builtins = __esm({
2003
3548
  init_src();
2004
3549
  }
2005
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
+ }
2006
3565
  function defaultOpenAIOptions() {
2007
3566
  if (!credential) return {};
2008
3567
  return {
@@ -2010,17 +3569,283 @@ function defaultOpenAIOptions() {
2010
3569
  ...credential.baseURL ? { baseURL: credential.baseURL } : {}
2011
3570
  };
2012
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
+ }
2013
3580
  function defaultOpenAI() {
2014
- return cached ??= new OpenAI(defaultOpenAIOptions());
3581
+ return scope.getStore() ?? (cached ??= internalChatClient());
2015
3582
  }
2016
3583
  function hasOpenAiKey() {
2017
- return credential !== null || Boolean(process.env["OPENAI_API_KEY"]);
3584
+ return scope.getStore() !== void 0 || credential !== null || Boolean(process.env["OPENAI_API_KEY"]);
2018
3585
  }
2019
- var credential, cached;
3586
+ var credential, cached, scope;
2020
3587
  var init_openai_default = __esm({
2021
3588
  "src/runtime/openai-default.ts"() {
3589
+ init_dist();
2022
3590
  credential = null;
2023
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();
2024
3849
  }
2025
3850
  });
2026
3851
 
@@ -2033,6 +3858,7 @@ function dotProduct(a, b) {
2033
3858
  }
2034
3859
  function createOpenAIEmbedder() {
2035
3860
  const cache = /* @__PURE__ */ new Map();
3861
+ const inflight = /* @__PURE__ */ new Map();
2036
3862
  const available = () => hasOpenAiKey();
2037
3863
  function remember(key, vec) {
2038
3864
  if (cache.size >= CACHE_MAX) {
@@ -2051,24 +3877,31 @@ function createOpenAIEmbedder() {
2051
3877
  if (!available() || !key) return null;
2052
3878
  const hit = cache.get(key);
2053
3879
  if (hit) return hit;
2054
- const controller = new AbortController();
2055
- const timer = setTimeout(() => controller.abort(), EMBED_TIMEOUT_MS);
2056
- try {
2057
- const res = await defaultOpenAI().embeddings.create(
2058
- { model: EMBED_MODEL, input: key },
2059
- { signal: controller.signal }
2060
- );
2061
- const vec = res.data[0]?.embedding;
2062
- if (!Array.isArray(vec)) return null;
2063
- return remember(key, vec);
2064
- } catch (err) {
2065
- console.warn("[graph] embed failed; router will fall back to judge", {
2066
- err: err instanceof Error ? err.message : String(err)
2067
- });
2068
- return null;
2069
- } finally {
2070
- clearTimeout(timer);
2071
- }
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;
2072
3905
  },
2073
3906
  prewarm(text) {
2074
3907
  void this.embed(text).catch(() => {
@@ -2084,7 +3917,8 @@ function getEmbedder() {
2084
3917
  var EMBED_MODEL, ROUTE_THRESHOLD, EMBED_TIMEOUT_MS, CACHE_MAX, singleton, override;
2085
3918
  var init_embedder = __esm({
2086
3919
  "src/runtime/graph/embedder.ts"() {
2087
- init_openai_default();
3920
+ init_log();
3921
+ init_llm();
2088
3922
  EMBED_MODEL = process.env["VOICELAYER_EMBED_MODEL"] ?? "text-embedding-3-small";
2089
3923
  ROUTE_THRESHOLD = Number(process.env["VL_FLOW_ROUTE_THRESHOLD"] ?? "0.62");
2090
3924
  EMBED_TIMEOUT_MS = Number(process.env["VL_FLOW_EMBED_TIMEOUT_MS"] ?? "2500");
@@ -2146,7 +3980,7 @@ async function routeByEmbedding(nl, ctx) {
2146
3980
  if (!best || score > best.score) best = { to: edge.to, score };
2147
3981
  }
2148
3982
  if (best && best.score >= ROUTE_THRESHOLD) {
2149
- 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)) });
2150
3984
  return best.to;
2151
3985
  }
2152
3986
  return null;
@@ -2172,11 +4006,21 @@ async function evaluateCondition(condition, ctx) {
2172
4006
  function splitTop(text, op) {
2173
4007
  return text.split(op);
2174
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
+ }
2175
4019
  function evaluateDeterministic(atom, ctx) {
2176
4020
  const lower = atom.toLowerCase();
2177
4021
  if (lower === "true" || lower === "always") return true;
2178
4022
  if (lower === "false" || lower === "never") return false;
2179
- const toolMatch = atom.match(/^tool[.:]([A-Za-z0-9_-]+)(?:\.([\w.]+))?\s*(==|!=)?\s*(.*)$/);
4023
+ const toolMatch = atom.match(TOOL_ATOM);
2180
4024
  if (toolMatch) {
2181
4025
  const [, id, path, op, rawVal] = toolMatch;
2182
4026
  const result = ctx.tools[id];
@@ -2190,7 +4034,7 @@ function evaluateDeterministic(atom, ctx) {
2190
4034
  const eq = String(actual ?? "").toLowerCase() === (rawVal ?? "").trim().toLowerCase();
2191
4035
  return op === "!=" ? !eq : eq;
2192
4036
  }
2193
- const nodeMatch = atom.match(/^node:([A-Za-z0-9_-]+)(?:\.([\w.]+))?\s*(==|!=)?\s*(.*)$/);
4037
+ const nodeMatch = atom.match(NODE_ATOM);
2194
4038
  if (nodeMatch) {
2195
4039
  if (!ctx.nodeResults) return void 0;
2196
4040
  const [, id, path, op, rawVal] = nodeMatch;
@@ -2202,14 +4046,12 @@ function evaluateDeterministic(atom, ctx) {
2202
4046
  const eq = String(actual ?? "").toLowerCase() === (rawVal ?? "").trim().toLowerCase();
2203
4047
  return op === "!=" ? !eq : eq;
2204
4048
  }
2205
- const dtmfMatch = atom.match(/^dtmf:\s*(.+)$/i);
4049
+ const dtmfMatch = atom.match(DTMF_ATOM);
2206
4050
  if (dtmfMatch) {
2207
4051
  if (ctx.lastDtmf === void 0) return void 0;
2208
4052
  return ctx.lastDtmf === dtmfMatch[1].trim();
2209
4053
  }
2210
- const opMatch = atom.match(
2211
- /^(!?)\s*(?:slot:)?(.+?)\s*(==|!=|=|contains|starts_with|greater_than|less_than|>=|<=|>|<)\s*(.+)$/i
2212
- );
4054
+ const opMatch = atom.match(OP_ATOM);
2213
4055
  if (opMatch) {
2214
4056
  const [, neg, nameRaw, op, valRaw] = opMatch;
2215
4057
  const raw = resolvePath(ctx.slots, nameRaw.trim());
@@ -2231,14 +4073,14 @@ function evaluateDeterministic(atom, ctx) {
2231
4073
  else res = value === target;
2232
4074
  return neg ? !res : res;
2233
4075
  }
2234
- const slotPres = atom.match(/^(!?)\s*slot:(.+?)\s*(present|empty)?$/i);
4076
+ const slotPres = atom.match(SLOT_PRESENCE_ATOM);
2235
4077
  if (slotPres) {
2236
4078
  const [, neg, nameRaw, kw] = slotPres;
2237
4079
  const present = hasSlot(ctx.slots, nameRaw.trim());
2238
4080
  const res = (kw ?? "").toLowerCase() === "empty" ? !present : present;
2239
4081
  return neg ? !res : res;
2240
4082
  }
2241
- const bare = atom.match(/^(!?)\s*([\w.-]+)\s*(present|empty)?$/i);
4083
+ const bare = atom.match(BARE_ATOM);
2242
4084
  if (bare) {
2243
4085
  const [, neg, name, kw] = bare;
2244
4086
  const present = hasSlot(ctx.slots, name);
@@ -2249,13 +4091,27 @@ function evaluateDeterministic(atom, ctx) {
2249
4091
  }
2250
4092
  async function pickOption(options, ctx, hint) {
2251
4093
  if (options.length === 0) return null;
2252
- 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) {
2253
4109
  const list = options.map((o, i) => `${i + 1}. ${o.text}`).join("\n");
2254
4110
  const prompt = [
2255
4111
  "Pick which outcome best matches the current state of this voice conversation.",
2256
4112
  ...hint && hint.trim() ? [`What decides the path: ${hint.trim()}`] : [],
2257
4113
  `Information collected: ${collected || "(none)"}`,
2258
- `The caller most recently said: "${ctx.lastUtterance ?? ""}"`,
4114
+ `The caller most recently said: "${lastUtterance ?? ""}"`,
2259
4115
  "Outcomes:",
2260
4116
  list,
2261
4117
  'Answer with ONLY the outcome number, or "none" if nothing clearly matches.'
@@ -2280,7 +4136,7 @@ async function pickOption(options, ctx, hint) {
2280
4136
  if (!Number.isInteger(n) || n < 1 || n > options.length) return null;
2281
4137
  return options[n - 1]?.id ?? null;
2282
4138
  } catch (err) {
2283
- console.warn("[graph] option pick failed; treating as undecided", {
4139
+ graphWarn("option pick failed; treating as undecided", {
2284
4140
  err: err instanceof Error ? err.message : String(err)
2285
4141
  });
2286
4142
  return null;
@@ -2289,12 +4145,18 @@ async function pickOption(options, ctx, hint) {
2289
4145
  }
2290
4146
  }
2291
4147
  async function judge(condition, ctx) {
2292
- 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) {
2293
4151
  const prompt = [
2294
4152
  "You judge whether a condition about an ongoing voice conversation is currently TRUE.",
2295
4153
  `Information collected: ${collected || "(none)"}`,
2296
- `The caller most recently said: "${ctx.lastUtterance ?? ""}"`,
4154
+ `The caller most recently said: "${lastUtterance ?? ""}"`,
2297
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".',
2298
4160
  'Answer strictly "yes" or "no".'
2299
4161
  ].join("\n");
2300
4162
  const controller = new AbortController();
@@ -2314,7 +4176,7 @@ async function judge(condition, ctx) {
2314
4176
  );
2315
4177
  return (res.choices[0]?.message?.content ?? "").trim().toLowerCase().startsWith("y");
2316
4178
  } catch (err) {
2317
- console.warn("[graph] condition judge failed; treating as false", {
4179
+ graphWarn("condition judge failed; treating as false", {
2318
4180
  condition,
2319
4181
  err: err instanceof Error ? err.message : String(err)
2320
4182
  });
@@ -2323,13 +4185,20 @@ async function judge(condition, ctx) {
2323
4185
  clearTimeout(timer);
2324
4186
  }
2325
4187
  }
2326
- var JUDGE_TIMEOUT_MS;
4188
+ var JUDGE_TIMEOUT_MS, TOOL_ATOM, NODE_ATOM, DTMF_ATOM, OP_ATOM, SLOT_PRESENCE_ATOM, BARE_ATOM;
2327
4189
  var init_conditions = __esm({
2328
4190
  "src/runtime/graph/conditions.ts"() {
2329
- init_openai_default();
4191
+ init_log();
4192
+ init_llm();
2330
4193
  init_interpolate();
2331
4194
  init_embedder();
2332
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;
2333
4202
  }
2334
4203
  });
2335
4204
 
@@ -2372,7 +4241,7 @@ async function resolveBranch(branches, ctx, opts) {
2372
4241
  if (!best || score > best.score) best = { id: b.id, score };
2373
4242
  }
2374
4243
  if (best && best.score >= ROUTE_THRESHOLD) {
2375
- 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)) });
2376
4245
  return best.id;
2377
4246
  }
2378
4247
  }
@@ -2387,6 +4256,7 @@ async function resolveBranch(branches, ctx, opts) {
2387
4256
  var ELSE_BRANCH_ID;
2388
4257
  var init_branch_resolve = __esm({
2389
4258
  "src/runtime/graph/branch-resolve.ts"() {
4259
+ init_log();
2390
4260
  init_conditions();
2391
4261
  init_embedder();
2392
4262
  ELSE_BRANCH_ID = "else";
@@ -2486,13 +4356,18 @@ function scopeFor(state, deps) {
2486
4356
  function str(value) {
2487
4357
  return typeof value === "string" ? value : "";
2488
4358
  }
4359
+ async function settledSlots(deps) {
4360
+ await deps.utterances.settle?.();
4361
+ return deps.processRt.getData();
4362
+ }
2489
4363
  function evalCtxOf(state, deps) {
2490
4364
  return {
2491
4365
  slots: deps.processRt.getData(),
2492
4366
  tools: state.toolResults,
2493
4367
  nodeResults: state.nodeResults,
2494
4368
  ...state.lastUtterance !== void 0 ? { lastUtterance: state.lastUtterance } : {},
2495
- ...state.lastDtmf !== void 0 ? { lastDtmf: state.lastDtmf } : {}
4369
+ ...state.lastDtmf !== void 0 ? { lastDtmf: state.lastDtmf } : {},
4370
+ settle: () => settledSlots(deps)
2496
4371
  };
2497
4372
  }
2498
4373
  function deterministicEdgeTarget(edges, ctx) {
@@ -2507,7 +4382,7 @@ async function speak(text, deps, opts) {
2507
4382
  const allowInterruptions = opts?.allowInterruptions === true;
2508
4383
  if (deps.ctx.adapter) {
2509
4384
  const r = await deps.ctx.adapter.say(text, { allowInterruptions });
2510
- console.log("[graph] said", { chars: text.length, interrupted: r.interrupted });
4385
+ graphLog("said", { chars: text.length, interrupted: r.interrupted });
2511
4386
  return;
2512
4387
  }
2513
4388
  const session = deps.ctx.livekit.session;
@@ -2517,12 +4392,12 @@ async function speak(text, deps, opts) {
2517
4392
 
2518
4393
  ${text}`
2519
4394
  }).waitForPlayout();
2520
- console.log("[graph] said (realtime)", { chars: text.length });
4395
+ graphLog("said (realtime)", { chars: text.length });
2521
4396
  return;
2522
4397
  }
2523
4398
  const handle = session.say(text, { allowInterruptions });
2524
4399
  await handle.waitForPlayout();
2525
- console.log("[graph] said", { chars: text.length, interrupted: handle.interrupted });
4400
+ graphLog("said", { chars: text.length, interrupted: handle.interrupted });
2526
4401
  }
2527
4402
  async function genReply(deps, instructions) {
2528
4403
  if (deps.ctx.adapter) {
@@ -2531,10 +4406,13 @@ async function genReply(deps, instructions) {
2531
4406
  }
2532
4407
  await deps.ctx.livekit.session.generateReply({ instructions }).waitForPlayout();
2533
4408
  }
2534
- async function nextTurn(deps, reprompt) {
4409
+ async function nextTurn(deps, reprompt, opts) {
2535
4410
  let reprompted = false;
2536
4411
  for (; ; ) {
2537
- 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
+ });
2538
4416
  if (evt.kind === "hangup") return { result: { kind: "end" } };
2539
4417
  if (evt.kind === "control") return { result: evt.result };
2540
4418
  if (evt.kind === "timeout") {
@@ -2608,6 +4486,7 @@ function menuLabelOf(label) {
2608
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;
2609
4487
  var init_executors = __esm({
2610
4488
  "src/runtime/graph/executors.ts"() {
4489
+ init_log();
2611
4490
  init_src();
2612
4491
  init_interpolate();
2613
4492
  init_builtins();
@@ -2740,6 +4619,7 @@ var init_executors = __esm({
2740
4619
  const policy = historyPolicyOf(node);
2741
4620
  const maxTurns = hub ? HUB_MAX_TURNS : LLM_MAX_TURNS;
2742
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));
2743
4623
  for (let turn = 0; turn < maxTurns; turn++) {
2744
4624
  const collected = formatCollected(deps.processRt.getData());
2745
4625
  const parts = [base];
@@ -2752,25 +4632,19 @@ ${collected}`);
2752
4632
  () => guardedGenerate(node, state, deps, parts.join("\n\n"), base)
2753
4633
  );
2754
4634
  if (!hub && !terminal && conditional.length === 0) return { kind: "advance" };
2755
- const t = await nextTurn(deps);
4635
+ const t = await nextTurn(deps, void 0, { deferCapture });
2756
4636
  if ("result" in t) return t.result;
2757
4637
  state.lastUtterance = t.text;
2758
4638
  state.lastDtmf = t.dtmf;
2759
4639
  if (conditional.length > 0) {
2760
- const target = await selectEdge(out, {
2761
- slots: deps.processRt.getData(),
2762
- tools: state.toolResults,
2763
- nodeResults: state.nodeResults,
2764
- lastDtmf: state.lastDtmf,
2765
- lastUtterance: state.lastUtterance
2766
- });
4640
+ const target = await selectEdge(out, evalCtxOf(state, deps));
2767
4641
  if (target) {
2768
- console.log("[graph] llm: transition", { node: node.id, to: target, hub });
4642
+ graphLog("llm: transition", { node: node.id, to: target, hub });
2769
4643
  return { kind: "goto", to: target };
2770
4644
  }
2771
4645
  }
2772
4646
  }
2773
- console.log("[graph] llm: turn cap reached", { node: node.id, hub });
4647
+ graphLog("llm: turn cap reached", { node: node.id, hub });
2774
4648
  return terminal ? { kind: "end" } : { kind: "advance" };
2775
4649
  };
2776
4650
  toolExecutor = async (node, state, deps) => {
@@ -3129,7 +5003,7 @@ ${collected}`);
3129
5003
  const goal = str(cfg.goal);
3130
5004
  let instr = str(cfg.instructions);
3131
5005
  const toolNames = Array.isArray(cfg.tools) ? cfg.tools.filter((x) => typeof x === "string" && x.length > 0) : [];
3132
- let model = str(cfg.model) || void 0;
5006
+ let model2 = str(cfg.model) || void 0;
3133
5007
  let temperature = typeof cfg.temperature === "number" ? cfg.temperature : void 0;
3134
5008
  let libraryExits = [];
3135
5009
  const ref = str(cfg.playbookRef);
@@ -3139,7 +5013,7 @@ ${collected}`);
3139
5013
  if (!instr) instr = resolved.instructions;
3140
5014
  for (const f of resolved.requiredFields) if (!required.includes(f)) required.push(f);
3141
5015
  for (const t of resolved.tools ?? []) if (!toolNames.includes(t)) toolNames.push(t);
3142
- if (!model && resolved.model) model = resolved.model;
5016
+ if (!model2 && resolved.model) model2 = resolved.model;
3143
5017
  if (temperature === void 0 && resolved.temperature !== void 0) temperature = resolved.temperature;
3144
5018
  libraryExits = resolved.exits ?? [];
3145
5019
  }
@@ -3193,7 +5067,7 @@ ${collected}`);
3193
5067
  exits: exitOptions,
3194
5068
  captureVars: required,
3195
5069
  runTool: (name, args) => registry ? registry.run(name, args) : Promise.resolve({ ok: false, status: 0, data: { error: "no tool registry" } }),
3196
- ...model ? { model } : {},
5070
+ ...model2 ? { model: model2 } : {},
3197
5071
  ...temperature !== void 0 ? { temperature } : {}
3198
5072
  });
3199
5073
  } finally {
@@ -3386,18 +5260,18 @@ var interpreter_exports = {};
3386
5260
  __export(interpreter_exports, {
3387
5261
  runFlowGraph: () => runFlowGraph
3388
5262
  });
3389
- function pickEntry(model) {
3390
- return model.entry.length > 0 ? model.entry[0] : null;
5263
+ function pickEntry(model2) {
5264
+ return model2.entry.length > 0 ? model2.entry[0] : null;
3391
5265
  }
3392
- async function selectNext(model, from, result, state, deps) {
5266
+ async function selectNext(model2, from, result, state, deps) {
3393
5267
  if (result.kind === "goto") return result.to;
3394
5268
  if (result.kind === "take") {
3395
- const target = model.selectByPort(from, result.port);
5269
+ const target = model2.selectByPort(from, result.port);
3396
5270
  if (target !== null) return target;
3397
- 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);
3398
5272
  return def ? def.to : null;
3399
5273
  }
3400
- return selectEdge(model.out(from), {
5274
+ return selectEdge(model2.out(from), {
3401
5275
  slots: deps.processRt.getData(),
3402
5276
  tools: state.toolResults,
3403
5277
  nodeResults: state.nodeResults,
@@ -3406,10 +5280,10 @@ async function selectNext(model, from, result, state, deps) {
3406
5280
  });
3407
5281
  }
3408
5282
  async function runFlowGraph(program, deps) {
3409
- const model = buildGraphModel(program);
5283
+ const model2 = buildGraphModel(program);
3410
5284
  const maxNodeVisits = deps.maxNodeVisits ?? DEFAULT_MAX_NODE_VISITS;
3411
5285
  const maxSteps = deps.maxSteps ?? DEFAULT_MAX_STEPS;
3412
- let cursor = pickEntry(model);
5286
+ let cursor = pickEntry(model2);
3413
5287
  if (cursor === null) return { kind: "completed" };
3414
5288
  const state = { toolResults: {}, nodeResults: {}, cursor };
3415
5289
  const visits = /* @__PURE__ */ new Map();
@@ -3419,7 +5293,7 @@ async function runFlowGraph(program, deps) {
3419
5293
  deps.events.note("flow.loop_guard_tripped", { reason: "max_steps", steps });
3420
5294
  break;
3421
5295
  }
3422
- const node = model.node(cursor);
5296
+ const node = model2.node(cursor);
3423
5297
  if (!node) {
3424
5298
  deps.events.note("flow.missing_node", { nodeId: cursor });
3425
5299
  break;
@@ -3433,7 +5307,7 @@ async function runFlowGraph(program, deps) {
3433
5307
  }
3434
5308
  state.cursor = cursor;
3435
5309
  deps.events.enter(node.id, node.type);
3436
- console.log("[graph] enter", { node: node.id, type: node.type, step: steps });
5310
+ graphLog("enter", { node: node.id, type: node.type, step: steps });
3437
5311
  const executor = EXECUTORS[node.type] ?? passthroughExecutor;
3438
5312
  let result;
3439
5313
  try {
@@ -3442,7 +5316,7 @@ async function runFlowGraph(program, deps) {
3442
5316
  processRt: deps.processRt,
3443
5317
  utterances: deps.utterances,
3444
5318
  events: deps.events,
3445
- model,
5319
+ model: model2,
3446
5320
  ...deps.history ? { history: deps.history } : {},
3447
5321
  ...deps.realtime ? { realtime: true } : {},
3448
5322
  ...deps.resolvers ? { resolvers: deps.resolvers } : {},
@@ -3456,17 +5330,19 @@ async function runFlowGraph(program, deps) {
3456
5330
  return { kind: "ended", nodeId: node.id };
3457
5331
  }
3458
5332
  deps.events.note("node.errored", { nodeId: node.id, error: message });
3459
- const recovery = model.selectByPort(node.id, "error");
5333
+ const recovery = model2.selectByPort(node.id, "error");
3460
5334
  if (recovery !== null) {
3461
5335
  deps.events.traverse(node.id, recovery);
3462
5336
  cursor = recovery;
3463
5337
  continue;
3464
5338
  }
3465
5339
  return { kind: "error", nodeId: node.id, message };
5340
+ } finally {
5341
+ await deps.utterances.settle?.();
3466
5342
  }
3467
5343
  if (result.kind === "end") return { kind: "ended", nodeId: node.id };
3468
5344
  if (result.kind === "handoff") return { kind: "handoff", to: result.to };
3469
- const next = await selectNext(model, cursor, result, state, deps);
5345
+ const next = await selectNext(model2, cursor, result, state, deps);
3470
5346
  if (next !== null) deps.events.traverse(cursor, next);
3471
5347
  cursor = next;
3472
5348
  }
@@ -3475,6 +5351,7 @@ async function runFlowGraph(program, deps) {
3475
5351
  var DEFAULT_MAX_NODE_VISITS, DEFAULT_MAX_STEPS, HUB_MAX_NODE_VISITS;
3476
5352
  var init_interpreter = __esm({
3477
5353
  "src/runtime/graph/interpreter.ts"() {
5354
+ init_log();
3478
5355
  init_graph_model();
3479
5356
  init_executors();
3480
5357
  init_conditions();
@@ -3485,15 +5362,33 @@ var init_interpreter = __esm({
3485
5362
  });
3486
5363
 
3487
5364
  // src/runtime/graph/index.ts
5365
+ init_log();
3488
5366
  init_interpreter();
3489
5367
 
3490
5368
  // src/runtime/graph/utterances.ts
5369
+ init_log();
3491
5370
  var EVENT_UTTERANCE = "user_input_transcribed";
3492
5371
  var EVENT_CLOSE = "close";
3493
- 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) {
3494
5377
  const queue = [];
3495
5378
  let waiter = null;
3496
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
+ };
3497
5392
  const deliver = (e) => {
3498
5393
  if (hung && e.kind !== "hangup") return;
3499
5394
  if (waiter) {
@@ -3514,13 +5409,72 @@ function createUtteranceStream(session, processRt, ctx, rails, prewarm, wantsDtm
3514
5409
  };
3515
5410
  const unsubs = [];
3516
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
+ };
3517
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
+ };
3518
5449
  unsubs.push(
3519
5450
  a.on("user_input", (e) => {
3520
- if (!e.isFinal || hung) return;
3521
- 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);
5467
+ })
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());
3522
5475
  })
3523
5476
  );
5477
+ unsubs.push(clearTimers);
3524
5478
  unsubs.push(a.once("close", onClose));
3525
5479
  if (wantsDtmf) {
3526
5480
  if (typeof ctx.onDtmf === "function") {
@@ -3588,10 +5542,12 @@ function createUtteranceStream(session, processRt, ctx, rails, prewarm, wantsDtm
3588
5542
  } else if (hung) {
3589
5543
  return { kind: "hangup" };
3590
5544
  } else {
5545
+ if (onAwaitInput) onAwaitInput();
3591
5546
  raw = await awaitRaw(opts?.timeoutMs);
3592
5547
  }
3593
5548
  if (raw.kind === "hangup") return { kind: "hangup" };
3594
5549
  if (raw.kind === "timeout") return { kind: "timeout" };
5550
+ await settle();
3595
5551
  if (raw.kind === "dtmf") return { kind: "dtmf", digit: raw.digit };
3596
5552
  if (rails) {
3597
5553
  const outcome = await rails.intercept(raw.text, ctx);
@@ -3599,13 +5555,18 @@ function createUtteranceStream(session, processRt, ctx, rails, prewarm, wantsDtm
3599
5555
  if (outcome.kind === "control") return { kind: "control", result: outcome.result };
3600
5556
  }
3601
5557
  if (prewarm) prewarm(raw.text);
3602
- try {
3603
- await processRt.ingest(raw.text, ctx);
3604
- } 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 };
3605
5564
  }
5565
+ await capture(raw.text, false);
3606
5566
  return { kind: "utterance", text: raw.text };
3607
5567
  }
3608
5568
  },
5569
+ settle,
3609
5570
  close() {
3610
5571
  hung = true;
3611
5572
  for (const u of unsubs) u();
@@ -3614,6 +5575,7 @@ function createUtteranceStream(session, processRt, ctx, rails, prewarm, wantsDtm
3614
5575
  }
3615
5576
 
3616
5577
  // src/runtime/graph/history.ts
5578
+ init_log();
3617
5579
  function isConversational(it) {
3618
5580
  return it.type === "message" && (it.role === "user" || it.role === "assistant");
3619
5581
  }
@@ -3653,7 +5615,7 @@ function createHistoryController(agent) {
3653
5615
  next.items = prune(next.items, policy);
3654
5616
  await agent.updateChatCtx(next);
3655
5617
  } catch (err) {
3656
- console.warn("[graph] historyPolicy prune failed; using full history", {
5618
+ graphWarn("historyPolicy prune failed; using full history", {
3657
5619
  policy,
3658
5620
  err: err instanceof Error ? err.message : String(err)
3659
5621
  });
@@ -3666,7 +5628,7 @@ function createHistoryController(agent) {
3666
5628
  try {
3667
5629
  await agent.updateChatCtx(saved);
3668
5630
  } catch (err) {
3669
- console.warn("[graph] historyPolicy restore failed", {
5631
+ graphWarn("historyPolicy restore failed", {
3670
5632
  err: err instanceof Error ? err.message : String(err)
3671
5633
  });
3672
5634
  }
@@ -3703,13 +5665,17 @@ function createGraphEvents(processRt) {
3703
5665
  };
3704
5666
  }
3705
5667
 
5668
+ // src/runtime/graph/rails.ts
5669
+ init_log();
5670
+
3706
5671
  // src/runtime/graph/index.ts
3707
5672
  init_conditions();
3708
5673
  init_branch_resolve();
3709
5674
  init_embedder();
3710
5675
 
3711
5676
  // src/runtime/graph/agent-runner.ts
3712
- init_openai_default();
5677
+ init_log();
5678
+ init_llm();
3713
5679
  process.env["VOICELAYER_LLM_MODEL"] ?? process.env["OPENAI_LLM_MODEL"] ?? "gpt-4o";
3714
5680
  Number(process.env["VL_FLOW_AGENT_TURN_TIMEOUT_MS"] ?? "30000");
3715
5681
  Number(process.env["VL_FLOW_AGENT_MAX_TOOL_CALLS"] ?? "5");
@@ -3722,7 +5688,7 @@ init_graph_model();
3722
5688
  // src/runtime/graph/resolvers.ts
3723
5689
  init_src();
3724
5690
  init_embedder();
3725
- init_openai_default();
5691
+ init_llm();
3726
5692
  Number(process.env["VL_FLOW_CODE_TIMEOUT_MS"] ?? "1000");
3727
5693
  Number(process.env["VL_FLOW_CODE_MEMORY_BYTES"] ?? String(16 * 1024 * 1024));
3728
5694
 
@@ -3743,7 +5709,9 @@ async function runFlowProgram(program, ctx, opts) {
3743
5709
  ctx,
3744
5710
  opts.rails,
3745
5711
  prewarm,
3746
- wantsDtmf
5712
+ wantsDtmf,
5713
+ opts.onAwaitInput,
5714
+ opts.speech?.endpointing
3747
5715
  );
3748
5716
  const history = createHistoryController(opts.agent ?? null);
3749
5717
  try {
@@ -3759,15 +5727,16 @@ async function runFlowProgram(program, ctx, opts) {
3759
5727
  ...opts.resolvers ? { resolvers: opts.resolvers } : {},
3760
5728
  ...opts.busy ? { busy: opts.busy } : {}
3761
5729
  });
3762
- console.log("[graph] outcome", outcome);
5730
+ graphLog("outcome", outcome);
3763
5731
  return outcome;
3764
5732
  } finally {
5733
+ await utterances.settle?.();
3765
5734
  utterances.close();
3766
5735
  }
3767
5736
  }
3768
5737
 
3769
5738
  // src/runtime/process.ts
3770
- init_openai_default();
5739
+ init_llm();
3771
5740
 
3772
5741
  // ../primitives/process-schema/src/index.ts
3773
5742
  init_src();
@@ -4663,6 +6632,10 @@ function createTextSessionAdapter(transport) {
4663
6632
  await transport.send(text);
4664
6633
  return { interrupted: false };
4665
6634
  },
6635
+ async enqueueSay(text) {
6636
+ fire("agent_turn", { text });
6637
+ await transport.send(text);
6638
+ },
4666
6639
  async generateReply(opts) {
4667
6640
  if (transport.generate) {
4668
6641
  const text = await transport.generate(opts?.instructions);
@@ -4741,6 +6714,7 @@ function processSchemaToDefinition(schema) {
4741
6714
  }
4742
6715
 
4743
6716
  // src/runtime/text-session.ts
6717
+ init_openai_default();
4744
6718
  function buildTextContext(adapter, call, processRt) {
4745
6719
  return {
4746
6720
  call: {
@@ -4802,7 +6776,8 @@ function runTextSession(opts) {
4802
6776
  // createUtteranceStream prefers ctx.adapter; the session arg is unused here.
4803
6777
  session: {},
4804
6778
  events,
4805
- ...opts.maxSteps !== void 0 ? { maxSteps: opts.maxSteps } : {}
6779
+ ...opts.maxSteps !== void 0 ? { maxSteps: opts.maxSteps } : {},
6780
+ ...opts.onAwaitInput ? { onAwaitInput: opts.onAwaitInput } : {}
4806
6781
  });
4807
6782
  return {
4808
6783
  sendUserMessage: (text) => handle.push(text),
@@ -4848,12 +6823,12 @@ function definitionFor(opts) {
4848
6823
  }
4849
6824
  async function runTextTranscript(opts) {
4850
6825
  const replies = [];
4851
- const trace = [];
6826
+ const trace2 = [];
4852
6827
  const processRt = opts.processRt ?? createProcessRuntime(definitionFor(opts));
4853
6828
  const session = runTextSession({
4854
6829
  program: opts.program,
4855
6830
  onAgentText: (t) => void replies.push(t),
4856
- onTrace: (e) => void trace.push(e),
6831
+ onTrace: (e) => void trace2.push(e),
4857
6832
  ...processRt ? { processRt } : {},
4858
6833
  ...opts.generate ? { generate: opts.generate } : {},
4859
6834
  ...opts.call ? { call: opts.call } : {},
@@ -4862,7 +6837,57 @@ async function runTextTranscript(opts) {
4862
6837
  for (const m of opts.messages) session.sendUserMessage(m);
4863
6838
  session.end();
4864
6839
  const outcome = await session.result;
4865
- 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
+ };
4866
6891
  }
4867
6892
 
4868
- export { runTextSession, runTextTranscript };
6893
+ export { runLiveTextConversation, runTextSession, runTextTranscript, runWithOpenAIScope };