@voicelayer/sdk 0.6.3 → 0.7.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.
package/dist/index.js
CHANGED
|
@@ -95,35 +95,6 @@ var init_log = __esm({
|
|
|
95
95
|
}
|
|
96
96
|
});
|
|
97
97
|
|
|
98
|
-
// src/runtime/graph/graph-model.ts
|
|
99
|
-
function buildGraphModel(program) {
|
|
100
|
-
return {
|
|
101
|
-
entry: program.entry,
|
|
102
|
-
node: (id) => program.nodes[id],
|
|
103
|
-
out: (id) => program.nodes[id]?.out ?? [],
|
|
104
|
-
selectByPort: (id, port) => {
|
|
105
|
-
const edges = program.nodes[id]?.out ?? [];
|
|
106
|
-
const byHandle = edges.find((e) => e.sourceHandle === port);
|
|
107
|
-
if (byHandle) return byHandle.to;
|
|
108
|
-
const byLabel = edges.find((e) => e.label === port);
|
|
109
|
-
return byLabel ? byLabel.to : null;
|
|
110
|
-
},
|
|
111
|
-
askNodeForSlot: (slot) => {
|
|
112
|
-
const want = slot.trim();
|
|
113
|
-
for (const node of Object.values(program.nodes)) {
|
|
114
|
-
if (node.type !== "ask") continue;
|
|
115
|
-
const s = typeof node.config.slot === "string" ? node.config.slot : node.id;
|
|
116
|
-
if (s.trim() === want) return node.id;
|
|
117
|
-
}
|
|
118
|
-
return void 0;
|
|
119
|
-
}
|
|
120
|
-
};
|
|
121
|
-
}
|
|
122
|
-
var init_graph_model = __esm({
|
|
123
|
-
"src/runtime/graph/graph-model.ts"() {
|
|
124
|
-
}
|
|
125
|
-
});
|
|
126
|
-
|
|
127
98
|
// ../contracts/src/effect-stack.ts
|
|
128
99
|
var EffectStack;
|
|
129
100
|
var init_effect_stack = __esm({
|
|
@@ -1126,11 +1097,10 @@ var init_agent_config = __esm({
|
|
|
1126
1097
|
projectId: z.string().min(1),
|
|
1127
1098
|
// Human-readable label. No `agentType` enum on purpose.
|
|
1128
1099
|
name: z.string().min(1).max(128),
|
|
1129
|
-
//
|
|
1130
|
-
//
|
|
1131
|
-
//
|
|
1132
|
-
//
|
|
1133
|
-
// honoured. Per-call override at call_start time is also allowed.
|
|
1100
|
+
// The agent's persona (the Behaviour page). For a FLOW agent the PUBLISHED value is the flow persona on every
|
|
1101
|
+
// transport (flowPersonaOf — voice boot and text replies alike; absent → DEFAULT_FLOW_PERSONA), read at call start as
|
|
1102
|
+
// a snapshot decision; mid-call edits are not honoured (burn-down G-46). A coded SDK agent's prompt is its code: this
|
|
1103
|
+
// field doesn't reach it. A call plan may still override the prompt per call.
|
|
1134
1104
|
systemPrompt: z.string().max(64e3).optional(),
|
|
1135
1105
|
// Routing/decision layer (Voiceflow "Instructions"): plain-language guidance the
|
|
1136
1106
|
// agent reads to decide WHEN to do things (vs systemPrompt = WHO it is). Injected
|
|
@@ -2089,12 +2059,143 @@ var init_call_result = __esm({
|
|
|
2089
2059
|
});
|
|
2090
2060
|
}
|
|
2091
2061
|
});
|
|
2062
|
+
function supportedZones() {
|
|
2063
|
+
if (!SUPPORTED_ZONES) {
|
|
2064
|
+
const intl = Intl;
|
|
2065
|
+
SUPPORTED_ZONES = new Set(intl.supportedValuesOf?.("timeZone") ?? []);
|
|
2066
|
+
}
|
|
2067
|
+
return SUPPORTED_ZONES;
|
|
2068
|
+
}
|
|
2069
|
+
function isIanaTimezone(tz) {
|
|
2070
|
+
if (tz === "UTC" || supportedZones().has(tz)) return true;
|
|
2071
|
+
if (!IANA_NAME.test(tz)) return false;
|
|
2072
|
+
try {
|
|
2073
|
+
new Intl.DateTimeFormat("en-US", { timeZone: tz });
|
|
2074
|
+
return true;
|
|
2075
|
+
} catch {
|
|
2076
|
+
return false;
|
|
2077
|
+
}
|
|
2078
|
+
}
|
|
2079
|
+
var BUDGET_SCOPE_KINDS, BudgetScopeKind, CREATABLE_BUDGET_SCOPE_KINDS, BUDGET_CHANNELS, BudgetScope, BudgetPeriodKind, BudgetEnforcement, BudgetInbound, BudgetOwner, DEFAULT_BUDGET_THRESHOLDS, BUDGET_MAX_USD, Thresholds, IANA_NAME, SUPPORTED_ZONES, Timezone, BudgetPeriodStatus, Budget, BudgetRefusalDetail, BillingNoticeKind, BillingNotice;
|
|
2080
|
+
var init_budgets = __esm({
|
|
2081
|
+
"../contracts/src/budgets.ts"() {
|
|
2082
|
+
BUDGET_SCOPE_KINDS = ["workspace", "agent", "api_key", "channel"];
|
|
2083
|
+
BudgetScopeKind = z.enum(BUDGET_SCOPE_KINDS);
|
|
2084
|
+
CREATABLE_BUDGET_SCOPE_KINDS = ["workspace", "api_key", "channel"];
|
|
2085
|
+
BUDGET_CHANNELS = ["voice", "sms", "email", "chat", "discord"];
|
|
2086
|
+
z.enum(BUDGET_CHANNELS);
|
|
2087
|
+
BudgetScope = z.object({
|
|
2088
|
+
kind: BudgetScopeKind,
|
|
2089
|
+
// null for the workspace; the API key's id, or the channel name.
|
|
2090
|
+
id: z.string().min(1).nullable()
|
|
2091
|
+
});
|
|
2092
|
+
BudgetPeriodKind = z.enum(["day", "month", "billing_period"]);
|
|
2093
|
+
BudgetEnforcement = z.enum(["soft", "hard"]);
|
|
2094
|
+
BudgetInbound = z.enum(["refuse", "answer"]);
|
|
2095
|
+
BudgetOwner = z.enum(["customer", "operator"]);
|
|
2096
|
+
DEFAULT_BUDGET_THRESHOLDS = [50, 80, 100];
|
|
2097
|
+
BUDGET_MAX_USD = 1e6;
|
|
2098
|
+
Thresholds = z.array(z.number().int().min(1).max(100)).max(5).refine((t) => new Set(t).size === t.length, "thresholds must be distinct").transform((t) => [...t].sort((a, b) => a - b));
|
|
2099
|
+
IANA_NAME = /^[A-Za-z]+(?:\/[A-Za-z0-9_+-]+)+$/;
|
|
2100
|
+
SUPPORTED_ZONES = null;
|
|
2101
|
+
Timezone = z.string().min(1).max(64).refine(isIanaTimezone, "use an IANA time zone name, like America/New_York or UTC");
|
|
2102
|
+
BudgetPeriodStatus = z.object({
|
|
2103
|
+
periodStart: z.string(),
|
|
2104
|
+
// When the budget resets.
|
|
2105
|
+
periodEnd: z.string(),
|
|
2106
|
+
// Charged usage this period.
|
|
2107
|
+
settledUsd: z.string(),
|
|
2108
|
+
// Reserved for calls in progress.
|
|
2109
|
+
inProgressUsd: z.string(),
|
|
2110
|
+
// settled + in progress — what the budget compares with its amount.
|
|
2111
|
+
spentUsd: z.string(),
|
|
2112
|
+
// spent ÷ amount, as a whole percent (may pass 100: calls in progress finish).
|
|
2113
|
+
percent: z.number().int().min(0),
|
|
2114
|
+
// A hard budget at its limit: new actions in its scope are refused until periodEnd.
|
|
2115
|
+
reached: z.boolean()
|
|
2116
|
+
});
|
|
2117
|
+
Budget = z.object({
|
|
2118
|
+
id: z.string(),
|
|
2119
|
+
scope: BudgetScope,
|
|
2120
|
+
period: BudgetPeriodKind,
|
|
2121
|
+
timezone: z.string(),
|
|
2122
|
+
amountUsd: z.string(),
|
|
2123
|
+
enforcement: BudgetEnforcement,
|
|
2124
|
+
thresholds: z.array(z.number().int()),
|
|
2125
|
+
notifyEmail: z.boolean(),
|
|
2126
|
+
notifyWebhook: z.boolean(),
|
|
2127
|
+
// Operator budgets are VoiceLayer's own safety limits: shown to the customer, read-only.
|
|
2128
|
+
owner: BudgetOwner,
|
|
2129
|
+
inbound: BudgetInbound,
|
|
2130
|
+
createdAt: z.string(),
|
|
2131
|
+
current: BudgetPeriodStatus
|
|
2132
|
+
});
|
|
2133
|
+
z.object({ budgets: z.array(Budget) });
|
|
2134
|
+
z.object({
|
|
2135
|
+
scope: z.object({ kind: z.enum(CREATABLE_BUDGET_SCOPE_KINDS), id: z.string().min(1).max(200).nullable().optional() }),
|
|
2136
|
+
period: BudgetPeriodKind,
|
|
2137
|
+
timezone: Timezone.default("UTC"),
|
|
2138
|
+
amountUsd: z.number().positive().max(BUDGET_MAX_USD),
|
|
2139
|
+
enforcement: BudgetEnforcement,
|
|
2140
|
+
thresholds: Thresholds.default([...DEFAULT_BUDGET_THRESHOLDS]),
|
|
2141
|
+
notifyEmail: z.boolean().default(true),
|
|
2142
|
+
notifyWebhook: z.boolean().default(true),
|
|
2143
|
+
// Hard budgets only; a customer budget refuses incoming calls unless told to answer them (MON-9).
|
|
2144
|
+
inbound: BudgetInbound.default("refuse")
|
|
2145
|
+
}).superRefine((b, ctx) => {
|
|
2146
|
+
const id = b.scope.id ?? null;
|
|
2147
|
+
if (b.scope.kind === "workspace" && id !== null) ctx.addIssue({ code: "custom", path: ["scope", "id"], message: "a workspace budget names no scope id" });
|
|
2148
|
+
if (b.scope.kind !== "workspace" && id === null) ctx.addIssue({ code: "custom", path: ["scope", "id"], message: "name the API key or channel" });
|
|
2149
|
+
if (b.scope.kind === "channel" && id !== null && !BUDGET_CHANNELS.includes(id)) {
|
|
2150
|
+
ctx.addIssue({ code: "custom", path: ["scope", "id"], message: `channel is one of ${BUDGET_CHANNELS.join(", ")}` });
|
|
2151
|
+
}
|
|
2152
|
+
});
|
|
2153
|
+
z.object({
|
|
2154
|
+
amountUsd: z.number().positive().max(BUDGET_MAX_USD),
|
|
2155
|
+
enforcement: BudgetEnforcement,
|
|
2156
|
+
thresholds: Thresholds,
|
|
2157
|
+
notifyEmail: z.boolean(),
|
|
2158
|
+
notifyWebhook: z.boolean(),
|
|
2159
|
+
inbound: BudgetInbound
|
|
2160
|
+
}).partial().refine((b) => Object.keys(b).length > 0, "nothing to change");
|
|
2161
|
+
BudgetRefusalDetail = z.object({
|
|
2162
|
+
budgetId: z.string(),
|
|
2163
|
+
scope: BudgetScope,
|
|
2164
|
+
period: BudgetPeriodKind,
|
|
2165
|
+
resetsAt: z.string()
|
|
2166
|
+
});
|
|
2167
|
+
BillingNoticeKind = z.enum([
|
|
2168
|
+
"balance_empty",
|
|
2169
|
+
"credit_limit_reached",
|
|
2170
|
+
"auto_topup_off",
|
|
2171
|
+
"wallet_frozen",
|
|
2172
|
+
"invoice_issued",
|
|
2173
|
+
"invoice_overdue",
|
|
2174
|
+
"workspace_paused",
|
|
2175
|
+
"workspace_restored",
|
|
2176
|
+
"budget_threshold",
|
|
2177
|
+
"budget_reached"
|
|
2178
|
+
]);
|
|
2179
|
+
BillingNotice = z.object({
|
|
2180
|
+
id: z.string(),
|
|
2181
|
+
kind: BillingNoticeKind,
|
|
2182
|
+
// The same words as the email's subject and first paragraph.
|
|
2183
|
+
title: z.string(),
|
|
2184
|
+
body: z.string(),
|
|
2185
|
+
// The budget a budget notice is about.
|
|
2186
|
+
budgetId: z.string().nullable(),
|
|
2187
|
+
createdAt: z.string()
|
|
2188
|
+
});
|
|
2189
|
+
z.object({ notices: z.array(BillingNotice) });
|
|
2190
|
+
}
|
|
2191
|
+
});
|
|
2092
2192
|
var WEBHOOK_EVENT_TYPES, CallWebhookDirection, CallWebhookOutcome, CallWebhookAttributes, Party, CALL_ENDED_PAYLOAD_VERSION, CallEndedTranscript;
|
|
2093
2193
|
var init_webhook_events = __esm({
|
|
2094
2194
|
"../contracts/src/webhook-events.ts"() {
|
|
2095
2195
|
init_call_result();
|
|
2096
2196
|
init_call_outcome();
|
|
2097
|
-
|
|
2197
|
+
init_budgets();
|
|
2198
|
+
WEBHOOK_EVENT_TYPES = ["call.started", "call.ended", "recording.ready", "billing.budget_threshold", "billing.budget_reached"];
|
|
2098
2199
|
z.enum(WEBHOOK_EVENT_TYPES);
|
|
2099
2200
|
CallWebhookDirection = z.enum(["inbound", "outbound", "web"]);
|
|
2100
2201
|
CallWebhookOutcome = z.enum(["completed", "dropped", "failed", "no_answer", "other"]);
|
|
@@ -2153,6 +2254,19 @@ var init_webhook_events = __esm({
|
|
|
2153
2254
|
recordingUrl: z.string().url(),
|
|
2154
2255
|
status: z.enum(["starting", "active", "completed", "failed", "aborted"])
|
|
2155
2256
|
});
|
|
2257
|
+
z.object({
|
|
2258
|
+
budgetId: z.string(),
|
|
2259
|
+
scope: BudgetScope,
|
|
2260
|
+
period: BudgetPeriodKind,
|
|
2261
|
+
periodStart: z.string().datetime({ offset: true }),
|
|
2262
|
+
periodEnd: z.string().datetime({ offset: true }),
|
|
2263
|
+
amountUsd: z.string(),
|
|
2264
|
+
spentUsd: z.string(),
|
|
2265
|
+
inProgressUsd: z.string(),
|
|
2266
|
+
// The threshold passed (budget_reached: 100).
|
|
2267
|
+
thresholdPct: z.number().int().min(1).max(100),
|
|
2268
|
+
enforcement: BudgetEnforcement
|
|
2269
|
+
});
|
|
2156
2270
|
}
|
|
2157
2271
|
});
|
|
2158
2272
|
var PhoneNumberStatus, PhoneNumberProvider, PhoneNumberCapabilities, PhoneNumberRecord;
|
|
@@ -2227,10 +2341,172 @@ var init_project_limits = __esm({
|
|
|
2227
2341
|
});
|
|
2228
2342
|
|
|
2229
2343
|
// ../contracts/src/flow-ports.ts
|
|
2230
|
-
var INCOMPLETE_PORT_ID;
|
|
2344
|
+
var INCOMPLETE_PORT_ID, ASK_NEXT_PORT_ID, UNFILLED_PORT_ID, MISSING_PORT_ID;
|
|
2231
2345
|
var init_flow_ports = __esm({
|
|
2232
2346
|
"../contracts/src/flow-ports.ts"() {
|
|
2233
2347
|
INCOMPLETE_PORT_ID = "incomplete";
|
|
2348
|
+
ASK_NEXT_PORT_ID = "next";
|
|
2349
|
+
UNFILLED_PORT_ID = "unfilled";
|
|
2350
|
+
MISSING_PORT_ID = "missing";
|
|
2351
|
+
}
|
|
2352
|
+
});
|
|
2353
|
+
|
|
2354
|
+
// ../contracts/src/flow-persona.ts
|
|
2355
|
+
function flowPersonaOf(config) {
|
|
2356
|
+
const prompt = config?.systemPrompt;
|
|
2357
|
+
return typeof prompt === "string" && prompt.trim() ? prompt : DEFAULT_FLOW_PERSONA;
|
|
2358
|
+
}
|
|
2359
|
+
function composeSystemPrompt(prompt, routingInstructions) {
|
|
2360
|
+
const routing = routingInstructions?.trim();
|
|
2361
|
+
return routing ? `${prompt}
|
|
2362
|
+
|
|
2363
|
+
# Routing
|
|
2364
|
+
${routing}` : prompt;
|
|
2365
|
+
}
|
|
2366
|
+
var DEFAULT_FLOW_PERSONA;
|
|
2367
|
+
var init_flow_persona = __esm({
|
|
2368
|
+
"../contracts/src/flow-persona.ts"() {
|
|
2369
|
+
DEFAULT_FLOW_PERSONA = "You are a helpful assistant. Keep replies short and natural, and follow the steps of the flow you have been given.";
|
|
2370
|
+
}
|
|
2371
|
+
});
|
|
2372
|
+
|
|
2373
|
+
// ../contracts/src/question-text.ts
|
|
2374
|
+
function lastSentences(line) {
|
|
2375
|
+
const text = line.trim();
|
|
2376
|
+
if (!text) return [];
|
|
2377
|
+
const sentences = [];
|
|
2378
|
+
let last = 0;
|
|
2379
|
+
for (const m of text.matchAll(SENTENCE_END)) {
|
|
2380
|
+
const end = (m.index ?? 0) + m[0].length;
|
|
2381
|
+
sentences.push(text.slice(last, end));
|
|
2382
|
+
last = end;
|
|
2383
|
+
}
|
|
2384
|
+
if (last < text.length) sentences.push(text.slice(last));
|
|
2385
|
+
return sentences.slice(-2);
|
|
2386
|
+
}
|
|
2387
|
+
function isQuestion(line) {
|
|
2388
|
+
return lastSentences(line).some((s) => QUESTION_MARK.test(s));
|
|
2389
|
+
}
|
|
2390
|
+
var QUESTION_MARK, SENTENCE_END;
|
|
2391
|
+
var init_question_text = __esm({
|
|
2392
|
+
"../contracts/src/question-text.ts"() {
|
|
2393
|
+
QUESTION_MARK = /[??؟]/;
|
|
2394
|
+
SENTENCE_END = /[.!?…。!?؟]+["'”’)\]]*\s*/g;
|
|
2395
|
+
}
|
|
2396
|
+
});
|
|
2397
|
+
|
|
2398
|
+
// ../contracts/src/flow-ask-lines.ts
|
|
2399
|
+
function norm(value) {
|
|
2400
|
+
return typeof value === "string" ? value.trim() : "";
|
|
2401
|
+
}
|
|
2402
|
+
function fnv1a64(text) {
|
|
2403
|
+
let hash = FNV_OFFSET;
|
|
2404
|
+
for (const byte of new TextEncoder().encode(text)) {
|
|
2405
|
+
hash ^= BigInt(byte);
|
|
2406
|
+
hash = BigInt.asUintN(64, hash * FNV_PRIME);
|
|
2407
|
+
}
|
|
2408
|
+
return hash.toString(16).padStart(16, "0");
|
|
2409
|
+
}
|
|
2410
|
+
function askLinesBasis(input) {
|
|
2411
|
+
const canonical = JSON.stringify([
|
|
2412
|
+
["persona", norm(input.persona)],
|
|
2413
|
+
["routing", norm(input.routing)],
|
|
2414
|
+
["language", norm(input.language).toLowerCase()],
|
|
2415
|
+
["ask", norm(input.ask)],
|
|
2416
|
+
["instructions", norm(input.instructions)]
|
|
2417
|
+
]);
|
|
2418
|
+
return fnv1a64(canonical);
|
|
2419
|
+
}
|
|
2420
|
+
function isEnglishLanguage(language) {
|
|
2421
|
+
return norm(language).toLowerCase().startsWith("en");
|
|
2422
|
+
}
|
|
2423
|
+
function readAskGeneratedLines(value) {
|
|
2424
|
+
if (!value || typeof value !== "object") return null;
|
|
2425
|
+
const v = value;
|
|
2426
|
+
if (typeof v.basis !== "string" || !v.basis) return null;
|
|
2427
|
+
const reasks = Array.isArray(v.reasks) ? v.reasks.filter((x) => typeof x === "string" && x.trim() !== "") : [];
|
|
2428
|
+
return {
|
|
2429
|
+
reasks,
|
|
2430
|
+
basis: v.basis,
|
|
2431
|
+
...typeof v.skipOffer === "string" && v.skipOffer.trim() ? { skipOffer: v.skipOffer } : {},
|
|
2432
|
+
...typeof v.voicedAsk === "string" && v.voicedAsk.trim() ? { voicedAsk: v.voicedAsk } : {}
|
|
2433
|
+
};
|
|
2434
|
+
}
|
|
2435
|
+
function authorReasksOf(value) {
|
|
2436
|
+
return Array.isArray(value) ? value.filter((x) => typeof x === "string" && x.trim() !== "").map((x) => x.trim().slice(0, ASK_LINE_MAX_CHARS)).slice(0, ASK_REASKS_MAX) : [];
|
|
2437
|
+
}
|
|
2438
|
+
function defaultAskText(slot, nodeId) {
|
|
2439
|
+
const authored = slot === nodeId ? "" : slot.trim();
|
|
2440
|
+
const label = authored.replace(/[_-]+/g, " ");
|
|
2441
|
+
return label ? `Could you tell me your ${label}?` : "Could you tell me a bit more about what you need?";
|
|
2442
|
+
}
|
|
2443
|
+
var ASK_LINE_MAX_CHARS, ASK_INSTRUCTIONS_MAX_CHARS, ASK_REASKS_MAX, FNV_OFFSET, FNV_PRIME, DEFAULT_ASK_EXHAUSTED_MESSAGE;
|
|
2444
|
+
var init_flow_ask_lines = __esm({
|
|
2445
|
+
"../contracts/src/flow-ask-lines.ts"() {
|
|
2446
|
+
init_question_text();
|
|
2447
|
+
ASK_LINE_MAX_CHARS = 300;
|
|
2448
|
+
ASK_INSTRUCTIONS_MAX_CHARS = 1e3;
|
|
2449
|
+
ASK_REASKS_MAX = 3;
|
|
2450
|
+
FNV_OFFSET = BigInt("0xcbf29ce484222325");
|
|
2451
|
+
FNV_PRIME = BigInt("0x100000001b3");
|
|
2452
|
+
DEFAULT_ASK_EXHAUSTED_MESSAGE = "I wasn't able to get everything we need on this call, so let me have someone follow up with you. Thanks for your time.";
|
|
2453
|
+
}
|
|
2454
|
+
});
|
|
2455
|
+
|
|
2456
|
+
// ../contracts/src/flow-required-inputs.ts
|
|
2457
|
+
function templateFieldRefs(text) {
|
|
2458
|
+
if (typeof text !== "string" || text.indexOf("{{") === -1) return [];
|
|
2459
|
+
const out = [];
|
|
2460
|
+
for (const m of text.matchAll(TOKEN)) {
|
|
2461
|
+
const expr = (m[1] ?? "").trim();
|
|
2462
|
+
const name = expr.startsWith("slots.") ? expr.slice("slots.".length).trim() : expr;
|
|
2463
|
+
if (name && !out.includes(name)) out.push(name);
|
|
2464
|
+
}
|
|
2465
|
+
return out;
|
|
2466
|
+
}
|
|
2467
|
+
function stepInputNames(type, config) {
|
|
2468
|
+
const out = [];
|
|
2469
|
+
const add = (names) => {
|
|
2470
|
+
for (const n of names) if (!out.includes(n)) out.push(n);
|
|
2471
|
+
};
|
|
2472
|
+
if (type === "email") {
|
|
2473
|
+
for (const key of EMAIL_TEMPLATE_KEYS) add(templateFieldRefs(config[key]));
|
|
2474
|
+
return out;
|
|
2475
|
+
}
|
|
2476
|
+
if (type === "tool") {
|
|
2477
|
+
if (typeof config.toolRef === "string" && config.toolRef.trim()) return out;
|
|
2478
|
+
if (config.input && typeof config.input === "object") add(Object.keys(config.input));
|
|
2479
|
+
if (config.headers && typeof config.headers === "object") {
|
|
2480
|
+
for (const v of Object.values(config.headers)) add(templateFieldRefs(v));
|
|
2481
|
+
}
|
|
2482
|
+
}
|
|
2483
|
+
return out;
|
|
2484
|
+
}
|
|
2485
|
+
function requiredInputsFor(type, config, isRequired) {
|
|
2486
|
+
return stepInputNames(type, config).filter((n) => isRequired(n));
|
|
2487
|
+
}
|
|
2488
|
+
function requiredFieldNamesOf(nodes) {
|
|
2489
|
+
const out = /* @__PURE__ */ new Set();
|
|
2490
|
+
for (const node of nodes) {
|
|
2491
|
+
const config = node.config;
|
|
2492
|
+
if (node.type === "ask" && config.required !== false) {
|
|
2493
|
+
out.add((typeof config.slot === "string" && config.slot.trim() ? config.slot : node.id).trim());
|
|
2494
|
+
}
|
|
2495
|
+
if ((node.type === "playbook" || node.type === "gate") && Array.isArray(config.requiredFields)) {
|
|
2496
|
+
for (const f of config.requiredFields) if (typeof f === "string" && f.trim()) out.add(f.trim());
|
|
2497
|
+
}
|
|
2498
|
+
}
|
|
2499
|
+
return out;
|
|
2500
|
+
}
|
|
2501
|
+
function requiredFieldNames(graph) {
|
|
2502
|
+
return requiredFieldNamesOf(graph.nodes.map((n) => ({ id: n.id, type: n.type, config: n.data })));
|
|
2503
|
+
}
|
|
2504
|
+
var EMAIL_TEMPLATE_KEYS, TOKEN;
|
|
2505
|
+
var init_flow_required_inputs = __esm({
|
|
2506
|
+
"../contracts/src/flow-required-inputs.ts"() {
|
|
2507
|
+
init_flow_ports();
|
|
2508
|
+
EMAIL_TEMPLATE_KEYS = ["to", "subject", "body", "from", "replyTo", "fromName"];
|
|
2509
|
+
TOKEN = /\{\{\s*([^}]+?)\s*\}\}/g;
|
|
2234
2510
|
}
|
|
2235
2511
|
});
|
|
2236
2512
|
|
|
@@ -2333,6 +2609,15 @@ function asAdherence(value) {
|
|
|
2333
2609
|
function asStepEntry(value) {
|
|
2334
2610
|
return typeof value === "string" && STEP_ENTRIES.has(value) ? value : "auto";
|
|
2335
2611
|
}
|
|
2612
|
+
function asMaxAttempts(value) {
|
|
2613
|
+
return typeof value === "number" && Number.isFinite(value) ? Math.min(6, Math.max(1, Math.round(value))) : ASK_DEFAULT_MAX_ATTEMPTS;
|
|
2614
|
+
}
|
|
2615
|
+
function asExhaustionPolicy(value) {
|
|
2616
|
+
return value === "fallback" || value === "end" ? value : "offer_skip";
|
|
2617
|
+
}
|
|
2618
|
+
function asRephrase(value) {
|
|
2619
|
+
return value === "all" || value === "never" ? value : "reasks";
|
|
2620
|
+
}
|
|
2336
2621
|
function branchesFor(data) {
|
|
2337
2622
|
return Array.isArray(data.branches) ? data.branches.filter((b) => b && typeof b.id === "string" && typeof b.label === "string").map((b) => ({
|
|
2338
2623
|
id: b.id,
|
|
@@ -2350,18 +2635,32 @@ function nodeConfigFor(node) {
|
|
|
2350
2635
|
case "say":
|
|
2351
2636
|
case "confirm":
|
|
2352
2637
|
return { text: typeof data.text === "string" ? data.text : "" };
|
|
2353
|
-
case "ask":
|
|
2638
|
+
case "ask": {
|
|
2639
|
+
const required = data.required !== false;
|
|
2640
|
+
const instructions = typeof data.instructions === "string" ? data.instructions.trim() : "";
|
|
2641
|
+
const reasks = authorReasksOf(data.reasks);
|
|
2642
|
+
const exhaustedMessage = typeof data.exhaustedMessage === "string" ? data.exhaustedMessage.trim() : "";
|
|
2354
2643
|
return {
|
|
2355
2644
|
slot: typeof data.slot === "string" ? data.slot : node.id,
|
|
2356
|
-
required
|
|
2645
|
+
required,
|
|
2357
2646
|
type: asFieldType(data.type),
|
|
2358
2647
|
historyPolicy: asHistoryPolicy(data.historyPolicy),
|
|
2359
2648
|
...typeof data.ask === "string" ? { ask: data.ask } : {},
|
|
2360
2649
|
...typeof data.pattern === "string" ? { pattern: data.pattern } : {},
|
|
2361
2650
|
...typeof data.min === "number" ? { min: data.min } : {},
|
|
2362
2651
|
...typeof data.max === "number" ? { max: data.max } : {},
|
|
2363
|
-
...Array.isArray(data.enum) ? { enum: data.enum.filter((x) => typeof x === "string") } : {}
|
|
2652
|
+
...Array.isArray(data.enum) ? { enum: data.enum.filter((x) => typeof x === "string") } : {},
|
|
2653
|
+
// burn-down G-46/47/48. `maxAttempts` is ALWAYS emitted: its absence marks a program compiled before the
|
|
2654
|
+
// bounded ask, which the runtime keeps on today's loop until it is re-deployed.
|
|
2655
|
+
maxAttempts: asMaxAttempts(data.maxAttempts),
|
|
2656
|
+
...required ? { onExhausted: asExhaustionPolicy(data.onExhausted) } : {},
|
|
2657
|
+
...required && exhaustedMessage ? { exhaustedMessage } : {},
|
|
2658
|
+
rephrase: asRephrase(data.rephrase),
|
|
2659
|
+
...instructions ? { instructions: instructions.slice(0, ASK_INSTRUCTIONS_MAX_CHARS) } : {},
|
|
2660
|
+
// the author's own re-ask wording wins and is never regenerated
|
|
2661
|
+
...reasks.length ? { reasks } : {}
|
|
2364
2662
|
};
|
|
2663
|
+
}
|
|
2365
2664
|
case "llm":
|
|
2366
2665
|
return {
|
|
2367
2666
|
instructions: typeof data.instructions === "string" ? data.instructions : typeof data.text === "string" ? data.text : "",
|
|
@@ -2539,6 +2838,13 @@ function nodeConfigFor(node) {
|
|
|
2539
2838
|
return typeof data.label === "string" ? { label: data.label } : {};
|
|
2540
2839
|
}
|
|
2541
2840
|
}
|
|
2841
|
+
function portless(edge) {
|
|
2842
|
+
return {
|
|
2843
|
+
to: edge.to,
|
|
2844
|
+
...edge.condition !== void 0 ? { condition: edge.condition } : {},
|
|
2845
|
+
...edge.label !== void 0 ? { label: edge.label } : {}
|
|
2846
|
+
};
|
|
2847
|
+
}
|
|
2542
2848
|
function compileFlowToProgram(graph, id = "flow") {
|
|
2543
2849
|
const stepNodes = graph.nodes.filter(
|
|
2544
2850
|
(n) => !CAPABILITY_NODE_TYPES.has(n.type) && !ANNOTATION_NODE_TYPES.has(n.type)
|
|
@@ -2565,13 +2871,28 @@ function compileFlowToProgram(graph, id = "flow") {
|
|
|
2565
2871
|
outBySource.set(edge.source, list);
|
|
2566
2872
|
indeg.set(edge.target, (indeg.get(edge.target) ?? 0) + 1);
|
|
2567
2873
|
}
|
|
2874
|
+
for (const node of stepNodes) {
|
|
2875
|
+
if (node.type !== "ask") continue;
|
|
2876
|
+
const list = outBySource.get(node.id);
|
|
2877
|
+
if (!list || !list.some((e) => e.sourceHandle === ASK_NEXT_PORT_ID)) continue;
|
|
2878
|
+
outBySource.set(
|
|
2879
|
+
node.id,
|
|
2880
|
+
list.filter((e) => e.sourceHandle !== void 0).map((e) => e.sourceHandle === ASK_NEXT_PORT_ID ? portless(e) : e)
|
|
2881
|
+
);
|
|
2882
|
+
}
|
|
2883
|
+
const requiredFields = requiredFieldNames(graph);
|
|
2884
|
+
const isRequired = (f) => requiredFields.has(f.trim());
|
|
2568
2885
|
const nodes = {};
|
|
2569
2886
|
const unsupportedNodes = [];
|
|
2570
2887
|
for (const node of stepNodes) {
|
|
2888
|
+
const config = nodeConfigFor(node);
|
|
2889
|
+
if (node.type === "email" || node.type === "tool" && !config.toolRef) {
|
|
2890
|
+
config.requiredInputs = requiredInputsFor(node.type, config, isRequired);
|
|
2891
|
+
}
|
|
2571
2892
|
nodes[node.id] = {
|
|
2572
2893
|
id: node.id,
|
|
2573
2894
|
type: node.type,
|
|
2574
|
-
config
|
|
2895
|
+
config,
|
|
2575
2896
|
// Defensive: the FlowProgram schema caps out[] at 64 — slice rather than
|
|
2576
2897
|
// letting an over-wired node fail the read-side parse (which silently
|
|
2577
2898
|
// degrades the whole call to the generic base prompt).
|
|
@@ -2600,11 +2921,14 @@ function compileFlowToProgram(graph, id = "flow") {
|
|
|
2600
2921
|
}
|
|
2601
2922
|
};
|
|
2602
2923
|
}
|
|
2603
|
-
var FIELD_TYPES, HTTP_METHODS, HISTORY_POLICIES, ADHERENCE_MODES, STEP_ENTRIES, UNSUPPORTED_NODE_TYPES, CAPABILITY_NODE_TYPES, ANNOTATION_NODE_TYPES;
|
|
2924
|
+
var FIELD_TYPES, HTTP_METHODS, HISTORY_POLICIES, ADHERENCE_MODES, STEP_ENTRIES, ASK_DEFAULT_MAX_ATTEMPTS, UNSUPPORTED_NODE_TYPES, CAPABILITY_NODE_TYPES, ANNOTATION_NODE_TYPES;
|
|
2604
2925
|
var init_flow_compile = __esm({
|
|
2605
2926
|
"../contracts/src/flow-compile.ts"() {
|
|
2606
2927
|
init_flow_enrichment();
|
|
2607
2928
|
init_flow_edges();
|
|
2929
|
+
init_flow_ports();
|
|
2930
|
+
init_flow_ask_lines();
|
|
2931
|
+
init_flow_required_inputs();
|
|
2608
2932
|
FIELD_TYPES = [
|
|
2609
2933
|
"string",
|
|
2610
2934
|
"string[]",
|
|
@@ -2618,6 +2942,7 @@ var init_flow_compile = __esm({
|
|
|
2618
2942
|
HISTORY_POLICIES = /* @__PURE__ */ new Set(["full", "last-turn", "slots-only"]);
|
|
2619
2943
|
ADHERENCE_MODES = /* @__PURE__ */ new Set(["off", "observe", "enforce"]);
|
|
2620
2944
|
STEP_ENTRIES = /* @__PURE__ */ new Set(["auto", "speak", "listen"]);
|
|
2945
|
+
ASK_DEFAULT_MAX_ATTEMPTS = 3;
|
|
2621
2946
|
UNSUPPORTED_NODE_TYPES = /* @__PURE__ */ new Set([]);
|
|
2622
2947
|
CAPABILITY_NODE_TYPES = /* @__PURE__ */ new Set(["memory", "trigger", "speech"]);
|
|
2623
2948
|
ANNOTATION_NODE_TYPES = /* @__PURE__ */ new Set(["note"]);
|
|
@@ -2808,6 +3133,8 @@ var init_flow_validate = __esm({
|
|
|
2808
3133
|
init_flow_ports();
|
|
2809
3134
|
init_flow_edges();
|
|
2810
3135
|
init_flow_compile_check();
|
|
3136
|
+
init_flow_required_inputs();
|
|
3137
|
+
init_flow_ask_lines();
|
|
2811
3138
|
init_secret_headers();
|
|
2812
3139
|
init_network_address();
|
|
2813
3140
|
init_dispatch_metadata();
|
|
@@ -4335,6 +4662,7 @@ var init_monitoring = __esm({
|
|
|
4335
4662
|
var WalletRefusalCode, WalletMode, WalletInvoiceStatus, WalletInvoice, WalletEntryKind, WalletEntry, TOPUP_MIN_USD, TOPUP_MAX_USD;
|
|
4336
4663
|
var init_wallet = __esm({
|
|
4337
4664
|
"../contracts/src/wallet.ts"() {
|
|
4665
|
+
init_budgets();
|
|
4338
4666
|
WalletRefusalCode = z.enum([
|
|
4339
4667
|
// Prepaid balance (or an invoiced credit limit) cannot cover the action.
|
|
4340
4668
|
"insufficient_balance",
|
|
@@ -4344,14 +4672,18 @@ var init_wallet = __esm({
|
|
|
4344
4672
|
// still answered. Paying the invoice restores the workspace.
|
|
4345
4673
|
"account_suspended",
|
|
4346
4674
|
// The action's reservation already closed (e.g. a call ended or expired) — it cannot be re-admitted.
|
|
4347
|
-
"hold_closed"
|
|
4675
|
+
"hold_closed",
|
|
4676
|
+
// A hard budget (docs/specs/monetization LLD §8a) would be passed: new actions in its scope wait for its reset.
|
|
4677
|
+
"budget_exceeded"
|
|
4348
4678
|
]);
|
|
4349
4679
|
z.object({
|
|
4350
4680
|
error: WalletRefusalCode,
|
|
4351
4681
|
code: WalletRefusalCode,
|
|
4352
4682
|
availableUsd: z.string(),
|
|
4353
4683
|
needUsd: z.string(),
|
|
4354
|
-
message: z.string()
|
|
4684
|
+
message: z.string(),
|
|
4685
|
+
// budget_exceeded only: which budget, and when it resets.
|
|
4686
|
+
budget: BudgetRefusalDetail.optional()
|
|
4355
4687
|
});
|
|
4356
4688
|
WalletMode = z.enum(["prepaid", "invoiced"]);
|
|
4357
4689
|
z.object({
|
|
@@ -4392,7 +4724,7 @@ var init_wallet = __esm({
|
|
|
4392
4724
|
payUrl: z.string().nullable()
|
|
4393
4725
|
});
|
|
4394
4726
|
z.object({ invoices: z.array(WalletInvoice) });
|
|
4395
|
-
WalletEntryKind = z.enum(["grant", "topup", "usage", "refund", "refund_reversal", "dispute", "dispute_reinstated", "adjustment", "invoice_payment"]);
|
|
4727
|
+
WalletEntryKind = z.enum(["grant", "topup", "usage", "usage_correction", "refund", "refund_reversal", "dispute", "dispute_reinstated", "adjustment", "invoice_payment"]);
|
|
4396
4728
|
WalletEntry = z.object({
|
|
4397
4729
|
id: z.string(),
|
|
4398
4730
|
kind: WalletEntryKind,
|
|
@@ -4436,6 +4768,81 @@ var init_pipeline_keys = __esm({
|
|
|
4436
4768
|
});
|
|
4437
4769
|
}
|
|
4438
4770
|
});
|
|
4771
|
+
var METERING_SCHEMA_VERSION, METER_IDS, MeterId, KEY_OWNERS, READING_SOURCES, SUBJECT_KINDS, SubjectKind, MeteringSubject, DimValue, IdentityDims, Attributes, SERIES_PATTERN, SeriesName, Quantity, MeterReadingInput;
|
|
4772
|
+
var init_metering = __esm({
|
|
4773
|
+
"../contracts/src/metering.ts"() {
|
|
4774
|
+
METERING_SCHEMA_VERSION = 2;
|
|
4775
|
+
METER_IDS = [
|
|
4776
|
+
// A model's prompt tokens as the provider counts them (cached ones included); `cached_input_tokens` is the cached
|
|
4777
|
+
// subset and `reasoning_tokens` the reasoning subset of `output_tokens`. Rating prices the totals until it prices the
|
|
4778
|
+
// subsets on their own (monetization MON-14).
|
|
4779
|
+
"llm.input_tokens",
|
|
4780
|
+
"llm.cached_input_tokens",
|
|
4781
|
+
"llm.output_tokens",
|
|
4782
|
+
"llm.reasoning_tokens",
|
|
4783
|
+
"embedding.input_tokens",
|
|
4784
|
+
"realtime.audio_input_tokens",
|
|
4785
|
+
"realtime.audio_output_tokens",
|
|
4786
|
+
"realtime.text_input_tokens",
|
|
4787
|
+
"realtime.cached_input_tokens",
|
|
4788
|
+
"realtime.text_output_tokens",
|
|
4789
|
+
"stt.billed_seconds",
|
|
4790
|
+
"tts.characters",
|
|
4791
|
+
"call.connected_seconds",
|
|
4792
|
+
"telephony.carrier_seconds",
|
|
4793
|
+
"media.participant_seconds",
|
|
4794
|
+
"media.sip_seconds",
|
|
4795
|
+
"recording.seconds",
|
|
4796
|
+
"storage.byte_days",
|
|
4797
|
+
"message.segments",
|
|
4798
|
+
"email.messages",
|
|
4799
|
+
// One per billable text action — a text turn, or a single model action the API takes for a workspace (a QA review,
|
|
4800
|
+
// an evaluation, a planner call, a knowledge ingest): the per-message fee's basis.
|
|
4801
|
+
"text.turns",
|
|
4802
|
+
// Informational: number rental is a recurring fee on charge_cycles (🔒 B-21).
|
|
4803
|
+
"number.held_days",
|
|
4804
|
+
"provider_fee.micros"
|
|
4805
|
+
];
|
|
4806
|
+
MeterId = z.enum(METER_IDS);
|
|
4807
|
+
KEY_OWNERS = ["platform", "workspace_key", "workspace_plan", "workspace_brain", "self_hosted"];
|
|
4808
|
+
z.enum(KEY_OWNERS);
|
|
4809
|
+
READING_SOURCES = ["server_observed", "platform_wire", "platform_estimate", "customer_report"];
|
|
4810
|
+
z.enum(READING_SOURCES);
|
|
4811
|
+
SUBJECT_KINDS = ["call", "turn", "job", "message", "email", "number"];
|
|
4812
|
+
SubjectKind = z.enum(SUBJECT_KINDS);
|
|
4813
|
+
MeteringSubject = z.object({ kind: SubjectKind, id: z.string().min(1).max(200) });
|
|
4814
|
+
DimValue = z.string().max(200).nullable();
|
|
4815
|
+
IdentityDims = z.record(z.string(), DimValue);
|
|
4816
|
+
Attributes = z.object({
|
|
4817
|
+
served_model: z.string().max(200).nullable(),
|
|
4818
|
+
channel: z.string().max(64).nullable(),
|
|
4819
|
+
direction: z.string().max(32).nullable(),
|
|
4820
|
+
leg_kind: z.string().max(64).nullable(),
|
|
4821
|
+
country: z.string().max(8).nullable(),
|
|
4822
|
+
worker_owner: z.enum(["platform", "self_hosted", "unknown"]).nullable()
|
|
4823
|
+
});
|
|
4824
|
+
SERIES_PATTERN = /^(req|ws|srv):[A-Za-z0-9._:-]{1,60}$/;
|
|
4825
|
+
SeriesName = z.string().max(64).regex(SERIES_PATTERN);
|
|
4826
|
+
Quantity = z.string().regex(/^\d{1,14}(\.\d{1,6})?$/);
|
|
4827
|
+
MeterReadingInput = z.object({
|
|
4828
|
+
schemaVersion: z.literal(METERING_SCHEMA_VERSION),
|
|
4829
|
+
subject: MeteringSubject,
|
|
4830
|
+
meter: MeterId,
|
|
4831
|
+
identity: IdentityDims,
|
|
4832
|
+
series: SeriesName,
|
|
4833
|
+
quantity: Quantity,
|
|
4834
|
+
final: z.boolean(),
|
|
4835
|
+
void: z.literal(true).optional(),
|
|
4836
|
+
// The pre-send input estimate of a request that may never answer (LLD §4.1). A client never names a source: the
|
|
4837
|
+
// server derives it from the principal (§4.4), and this flag is the one thing only the producer knows. Honoured
|
|
4838
|
+
// only from the call's own worker token; it ranks below the request's wire count, which supersedes it.
|
|
4839
|
+
estimate: z.literal(true).optional(),
|
|
4840
|
+
occurredAt: z.string().datetime({ offset: true }),
|
|
4841
|
+
attributes: Attributes.partial().optional()
|
|
4842
|
+
}).strict();
|
|
4843
|
+
z.object({ readings: z.array(MeterReadingInput).min(1).max(200) }).strict();
|
|
4844
|
+
}
|
|
4845
|
+
});
|
|
4439
4846
|
|
|
4440
4847
|
// ../contracts/src/index.ts
|
|
4441
4848
|
var init_src = __esm({
|
|
@@ -4472,6 +4879,10 @@ var init_src = __esm({
|
|
|
4472
4879
|
init_flow();
|
|
4473
4880
|
init_flow_program();
|
|
4474
4881
|
init_flow_ports();
|
|
4882
|
+
init_flow_persona();
|
|
4883
|
+
init_flow_ask_lines();
|
|
4884
|
+
init_flow_required_inputs();
|
|
4885
|
+
init_question_text();
|
|
4475
4886
|
init_flow_validate();
|
|
4476
4887
|
init_flow_builtins();
|
|
4477
4888
|
init_compliance();
|
|
@@ -4500,16 +4911,73 @@ var init_src = __esm({
|
|
|
4500
4911
|
init_channel_connector();
|
|
4501
4912
|
init_monitoring();
|
|
4502
4913
|
init_wallet();
|
|
4914
|
+
init_budgets();
|
|
4503
4915
|
init_network_address();
|
|
4504
4916
|
init_dispatch_metadata();
|
|
4505
4917
|
init_pipeline_keys();
|
|
4918
|
+
init_metering();
|
|
4919
|
+
}
|
|
4920
|
+
});
|
|
4921
|
+
|
|
4922
|
+
// src/runtime/graph/graph-model.ts
|
|
4923
|
+
function buildGraphModel(program) {
|
|
4924
|
+
return {
|
|
4925
|
+
// the program's completion gate is the compiler's own subset of these (a gate's fields, else the required asks') —
|
|
4926
|
+
// read too, so a program carries one answer however it was produced
|
|
4927
|
+
requiredFields: /* @__PURE__ */ new Set([
|
|
4928
|
+
...requiredFieldNamesOf(Object.values(program.nodes)),
|
|
4929
|
+
...program.completionGate.requiredFields.map((f) => f.trim())
|
|
4930
|
+
]),
|
|
4931
|
+
entry: program.entry,
|
|
4932
|
+
node: (id) => program.nodes[id],
|
|
4933
|
+
out: (id) => program.nodes[id]?.out ?? [],
|
|
4934
|
+
selectByPort: (id, port) => {
|
|
4935
|
+
const edges = program.nodes[id]?.out ?? [];
|
|
4936
|
+
const byHandle = edges.find((e) => e.sourceHandle === port);
|
|
4937
|
+
if (byHandle) return byHandle.to;
|
|
4938
|
+
const byLabel = edges.find((e) => e.label === port);
|
|
4939
|
+
return byLabel ? byLabel.to : null;
|
|
4940
|
+
},
|
|
4941
|
+
askNodeForSlot: (slot) => {
|
|
4942
|
+
const want = slot.trim();
|
|
4943
|
+
for (const node of Object.values(program.nodes)) {
|
|
4944
|
+
if (node.type !== "ask") continue;
|
|
4945
|
+
const s = typeof node.config.slot === "string" ? node.config.slot : node.id;
|
|
4946
|
+
if (s.trim() === want) return node.id;
|
|
4947
|
+
}
|
|
4948
|
+
return void 0;
|
|
4949
|
+
}
|
|
4950
|
+
};
|
|
4951
|
+
}
|
|
4952
|
+
var init_graph_model = __esm({
|
|
4953
|
+
"src/runtime/graph/graph-model.ts"() {
|
|
4954
|
+
init_src();
|
|
4955
|
+
}
|
|
4956
|
+
});
|
|
4957
|
+
|
|
4958
|
+
// src/runtime/graph/reask-templates.ts
|
|
4959
|
+
function templateReasks(ask) {
|
|
4960
|
+
const q = ask.trim();
|
|
4961
|
+
return [`${NOT_CAUGHT} ${q}`, `Let me ask that another way \u2014 ${q}`, `Just so I get it right: ${q}`];
|
|
4962
|
+
}
|
|
4963
|
+
function notCaught(line) {
|
|
4964
|
+
return `${NOT_CAUGHT} ${line.trim()}`;
|
|
4965
|
+
}
|
|
4966
|
+
function skipOfferTemplate(label) {
|
|
4967
|
+
const what = label.trim() ? `your ${label.trim()}` : "that detail";
|
|
4968
|
+
return `No problem if you don't have ${what} handy \u2014 shall we skip it for now?`;
|
|
4969
|
+
}
|
|
4970
|
+
var NOT_CAUGHT;
|
|
4971
|
+
var init_reask_templates = __esm({
|
|
4972
|
+
"src/runtime/graph/reask-templates.ts"() {
|
|
4973
|
+
NOT_CAUGHT = "Sorry, I didn't catch that.";
|
|
4506
4974
|
}
|
|
4507
4975
|
});
|
|
4508
4976
|
|
|
4509
4977
|
// src/runtime/graph/interpolate.ts
|
|
4510
4978
|
function interpolate(template, scope2) {
|
|
4511
4979
|
if (!template || template.indexOf("{{") === -1) return template;
|
|
4512
|
-
return template.replace(
|
|
4980
|
+
return template.replace(TOKEN2, (_match, expr) => {
|
|
4513
4981
|
const value = resolvePath(scope2, expr.trim());
|
|
4514
4982
|
return value === void 0 || value === null ? "" : String(value);
|
|
4515
4983
|
});
|
|
@@ -4534,10 +5002,10 @@ function hasSlot(slots, name) {
|
|
|
4534
5002
|
const v = resolvePath(slots, name.trim());
|
|
4535
5003
|
return v !== void 0 && v !== null && v !== "";
|
|
4536
5004
|
}
|
|
4537
|
-
var
|
|
5005
|
+
var TOKEN2;
|
|
4538
5006
|
var init_interpolate = __esm({
|
|
4539
5007
|
"src/runtime/graph/interpolate.ts"() {
|
|
4540
|
-
|
|
5008
|
+
TOKEN2 = /\{\{\s*([^}]+?)\s*\}\}/g;
|
|
4541
5009
|
}
|
|
4542
5010
|
});
|
|
4543
5011
|
|
|
@@ -5305,15 +5773,22 @@ function start(opts) {
|
|
|
5305
5773
|
]
|
|
5306
5774
|
});
|
|
5307
5775
|
sdk.start();
|
|
5308
|
-
|
|
5309
|
-
|
|
5310
|
-
|
|
5311
|
-
|
|
5312
|
-
|
|
5313
|
-
|
|
5314
|
-
|
|
5315
|
-
|
|
5316
|
-
|
|
5776
|
+
if (opts.signals === false)
|
|
5777
|
+
return;
|
|
5778
|
+
process.once("SIGTERM", () => void shutdownTelemetry().then(() => process.exit(0)));
|
|
5779
|
+
process.once("SIGINT", () => void shutdownTelemetry().then(() => process.exit(0)));
|
|
5780
|
+
}
|
|
5781
|
+
async function shutdownTelemetry() {
|
|
5782
|
+
const running = sdk;
|
|
5783
|
+
const logger = loggerProvider;
|
|
5784
|
+
sdk = null;
|
|
5785
|
+
loggerProvider = null;
|
|
5786
|
+
metricReader = null;
|
|
5787
|
+
try {
|
|
5788
|
+
await running?.shutdown();
|
|
5789
|
+
await logger?.shutdown();
|
|
5790
|
+
} catch {
|
|
5791
|
+
}
|
|
5317
5792
|
}
|
|
5318
5793
|
async function flushTelemetry() {
|
|
5319
5794
|
const flushes = [];
|
|
@@ -6493,7 +6968,9 @@ function evaluateDeterministic(atom, ctx) {
|
|
|
6493
6968
|
if (result === void 0) return false;
|
|
6494
6969
|
if (!path || path === "ok") {
|
|
6495
6970
|
const ok = result.ok === true || result.kind === "ok";
|
|
6496
|
-
|
|
6971
|
+
const want = (rawVal ?? "").trim().toLowerCase();
|
|
6972
|
+
const eq2 = want === "" ? ok : String(ok) === want;
|
|
6973
|
+
return op === "!=" ? !eq2 : eq2;
|
|
6497
6974
|
}
|
|
6498
6975
|
const actual = resolvePath(result, path);
|
|
6499
6976
|
if (!op) return actual !== void 0 && actual !== null && actual !== "";
|
|
@@ -6688,28 +7165,12 @@ var init_conditions = __esm({
|
|
|
6688
7165
|
});
|
|
6689
7166
|
|
|
6690
7167
|
// src/runtime/graph/turn-text.ts
|
|
6691
|
-
function lastSentences(line) {
|
|
6692
|
-
const text = line.trim();
|
|
6693
|
-
if (!text) return [];
|
|
6694
|
-
const sentences = [];
|
|
6695
|
-
let last = 0;
|
|
6696
|
-
for (const m of text.matchAll(SENTENCE_END)) {
|
|
6697
|
-
const end = (m.index ?? 0) + m[0].length;
|
|
6698
|
-
sentences.push(text.slice(last, end));
|
|
6699
|
-
last = end;
|
|
6700
|
-
}
|
|
6701
|
-
if (last < text.length) sentences.push(text.slice(last));
|
|
6702
|
-
return sentences.slice(-2);
|
|
6703
|
-
}
|
|
6704
|
-
function isQuestion(line) {
|
|
6705
|
-
return lastSentences(line).some((s) => QUESTION_MARK.test(s));
|
|
6706
|
-
}
|
|
6707
7168
|
function wordsOf(field) {
|
|
6708
7169
|
return field.toLowerCase().split(/[^a-z0-9]+/).filter(Boolean);
|
|
6709
7170
|
}
|
|
6710
7171
|
function asksFor(line, fields) {
|
|
6711
7172
|
if (!line) return false;
|
|
6712
|
-
const questions = lastSentences(line).filter((s) =>
|
|
7173
|
+
const questions = lastSentences(line).filter((s) => QUESTION_MARK2.test(s)).map((s) => s.toLowerCase().replace(/^[\s"'“(]*(?:and|so|ok|okay|great|thanks|sure|alright|now)[,\s]+/, "").trim());
|
|
6713
7174
|
return questions.some((q) => {
|
|
6714
7175
|
if (YES_NO.test(q) && !POLITE_REQUEST.test(q)) return false;
|
|
6715
7176
|
const words = new Set(q.match(/[a-z0-9]+/g) ?? []);
|
|
@@ -6719,11 +7180,11 @@ function asksFor(line, fields) {
|
|
|
6719
7180
|
});
|
|
6720
7181
|
});
|
|
6721
7182
|
}
|
|
6722
|
-
var
|
|
7183
|
+
var QUESTION_MARK2, POLITE_REQUEST, YES_NO;
|
|
6723
7184
|
var init_turn_text = __esm({
|
|
6724
7185
|
"src/runtime/graph/turn-text.ts"() {
|
|
6725
|
-
|
|
6726
|
-
|
|
7186
|
+
init_src();
|
|
7187
|
+
QUESTION_MARK2 = /[??؟]/;
|
|
6727
7188
|
POLITE_REQUEST = /^(?:(?:could|can|would|will) you (?:please )?(?:tell|give|share|say|spell|repeat|read|provide|confirm|let me know)|(?:could|can|may) (?:i|we) (?:please )?(?:get|have|take|grab))\b/;
|
|
6728
7189
|
YES_NO = /^(?:would|do|does|did|is|are|was|were|can|could|shall|should|will|have|has|had|may|might|am)\b/;
|
|
6729
7190
|
}
|
|
@@ -7306,7 +7767,8 @@ async function nextTurn(deps, state, reprompt, opts) {
|
|
|
7306
7767
|
...opts?.deferCapture ? { deferCapture: true } : {},
|
|
7307
7768
|
...opts?.number ? { number: opts.number } : {},
|
|
7308
7769
|
...opts?.fields?.length ? { fields: opts.fields } : {},
|
|
7309
|
-
...opts?.answering && opts.answering.fields.length > 0 ? { answering: opts.answering } : {}
|
|
7770
|
+
...opts?.answering && opts.answering.fields.length > 0 ? { answering: opts.answering } : {},
|
|
7771
|
+
...opts?.captureAfterRead ? { captureAfterRead: true } : {}
|
|
7310
7772
|
});
|
|
7311
7773
|
if (evt.kind === "hangup") return { result: { kind: "end" } };
|
|
7312
7774
|
if (evt.kind === "control") return { result: evt.result };
|
|
@@ -7333,7 +7795,7 @@ async function nextTurn(deps, state, reprompt, opts) {
|
|
|
7333
7795
|
state.lastNotInContext = evt.notInContext;
|
|
7334
7796
|
state.lastAgentText = evt.agentText;
|
|
7335
7797
|
state.lastDtmf = void 0;
|
|
7336
|
-
return { text: evt.text };
|
|
7798
|
+
return { text: evt.text, ...evt.capture ? { capture: evt.capture } : {} };
|
|
7337
7799
|
}
|
|
7338
7800
|
}
|
|
7339
7801
|
function detectYesNo(text) {
|
|
@@ -7343,6 +7805,58 @@ function detectYesNo(text) {
|
|
|
7343
7805
|
if (no && !yes) return "no";
|
|
7344
7806
|
return null;
|
|
7345
7807
|
}
|
|
7808
|
+
function renderLine(template, state, deps) {
|
|
7809
|
+
return { spoken: interpolate(template, scopeFor(state, deps)), model: interpolate(template, modelScopeFor(state, deps)) };
|
|
7810
|
+
}
|
|
7811
|
+
function askLinesFor(node, state, deps, askText, label, rephrase) {
|
|
7812
|
+
const render = (line) => renderLine(line, state, deps);
|
|
7813
|
+
const ctx = deps.askContext;
|
|
7814
|
+
const english = isEnglishLanguage(ctx?.language);
|
|
7815
|
+
const generated = readAskGeneratedLines(node.config.generated);
|
|
7816
|
+
const current = generated !== null && ctx !== void 0 && generated.basis === askLinesBasis({
|
|
7817
|
+
persona: ctx.persona,
|
|
7818
|
+
routing: ctx.routing ?? null,
|
|
7819
|
+
language: ctx.language ?? null,
|
|
7820
|
+
ask: str(node.config.ask),
|
|
7821
|
+
instructions: str(node.config.instructions)
|
|
7822
|
+
});
|
|
7823
|
+
if (generated && !current) deps.events.note("ask.reask_lines_stale", { node: node.id });
|
|
7824
|
+
const usable = current && rephrase !== "never" ? generated : null;
|
|
7825
|
+
const author = authorReasksOf(node.config.reasks).map(render);
|
|
7826
|
+
const pool = author.length ? author : usable && usable.reasks.length ? usable.reasks.map(render) : english ? templateReasks(askText.spoken).map((spoken, i) => ({ spoken, model: templateReasks(askText.model)[i] })) : rephrase === "never" ? [askText] : [];
|
|
7827
|
+
const offerTemplate = english ? skipOfferTemplate(label) : void 0;
|
|
7828
|
+
const skipOffer = usable?.skipOffer ? render(usable.skipOffer) : offerTemplate ? { spoken: offerTemplate, model: offerTemplate } : void 0;
|
|
7829
|
+
const voicedAsk = rephrase === "all" && usable?.voicedAsk ? render(usable.voicedAsk) : void 0;
|
|
7830
|
+
return { pool, ...skipOffer ? { skipOffer } : {}, ...voicedAsk ? { voicedAsk } : {} };
|
|
7831
|
+
}
|
|
7832
|
+
function skipOfferVerdict(text, dtmf) {
|
|
7833
|
+
if (dtmf === "1") return "yes";
|
|
7834
|
+
if (dtmf === "2") return "no";
|
|
7835
|
+
if (!text.trim()) return null;
|
|
7836
|
+
if (KEEP_ASKING.test(text)) return "no";
|
|
7837
|
+
if (SKIP_INTENT.test(text)) return OFFERS_IT.test(text) ? null : "yes";
|
|
7838
|
+
return detectYesNo(text);
|
|
7839
|
+
}
|
|
7840
|
+
async function readYesNo(t, state, deps) {
|
|
7841
|
+
const words = skipOfferVerdict(t.text, t.dtmf);
|
|
7842
|
+
if (words) return words;
|
|
7843
|
+
if (!t.text.trim()) return null;
|
|
7844
|
+
return await resolveBranch(
|
|
7845
|
+
[
|
|
7846
|
+
{ id: "yes", label: "The caller agrees to skip it for now." },
|
|
7847
|
+
{ id: "no", label: "The caller does not want to skip it \u2014 they will give it." }
|
|
7848
|
+
],
|
|
7849
|
+
evalCtxOf(state, deps)
|
|
7850
|
+
);
|
|
7851
|
+
}
|
|
7852
|
+
function briefingScope(template, state, deps) {
|
|
7853
|
+
const scope2 = scopeFor(state, deps);
|
|
7854
|
+
const data = deps.processRt.getData();
|
|
7855
|
+
const notProvided = templateFieldRefs(template).filter((f) => isRequiredField(deps, f) && !hasSlot(data, f));
|
|
7856
|
+
if (notProvided.length === 0) return scope2;
|
|
7857
|
+
const filled = Object.fromEntries(notProvided.map((f) => [f, "not provided"]));
|
|
7858
|
+
return { ...scope2, ...filled, slots: { ...scope2.slots, ...filled } };
|
|
7859
|
+
}
|
|
7346
7860
|
function formatCollected(deps) {
|
|
7347
7861
|
return Object.entries(deps.processRt.getData()).filter(([, v]) => v !== void 0 && v !== null && v !== "").map(([k, v]) => `${k.trim()}: ${String(forModel(deps, k, v))}`).join("\n");
|
|
7348
7862
|
}
|
|
@@ -7396,6 +7910,27 @@ function positiveMs(raw, fallback) {
|
|
|
7396
7910
|
const n = Number(raw);
|
|
7397
7911
|
return Number.isFinite(n) && n > 0 ? n : fallback;
|
|
7398
7912
|
}
|
|
7913
|
+
function isRequiredField(deps, name) {
|
|
7914
|
+
return deps.model.requiredFields.has(name.trim());
|
|
7915
|
+
}
|
|
7916
|
+
function requiredInputsOf(node, state, deps) {
|
|
7917
|
+
const baked = node.config.requiredInputs;
|
|
7918
|
+
if (Array.isArray(baked)) return baked.filter((x) => typeof x === "string");
|
|
7919
|
+
const cached2 = state.derivedInputs?.get(node.id);
|
|
7920
|
+
if (cached2) return cached2;
|
|
7921
|
+
const derived = requiredInputsFor(node.type, node.config, (f) => isRequiredField(deps, f));
|
|
7922
|
+
(state.derivedInputs ??= /* @__PURE__ */ new Map()).set(node.id, derived);
|
|
7923
|
+
return derived;
|
|
7924
|
+
}
|
|
7925
|
+
function blockOnMissingInputs(node, state, deps, inputs, tool) {
|
|
7926
|
+
const data = deps.processRt.getData();
|
|
7927
|
+
const missing = inputs.filter((f) => !hasSlot(data, f)).map((f) => f.trim());
|
|
7928
|
+
if (missing.length === 0) return null;
|
|
7929
|
+
deps.events.note("step.missing_inputs", { node: node.id, missing });
|
|
7930
|
+
state.nodeResults[node.id] = { ok: false, error: "missing_required", missing };
|
|
7931
|
+
if (tool) state.toolResults[node.id] = { ok: false, status: 0, data: { kind: "blocked", reason: "missing_required", missing } };
|
|
7932
|
+
return deps.model.selectByPort(node.id, MISSING_PORT_ID) !== null ? { kind: "take", port: MISSING_PORT_ID } : routeByPort(node, deps, "fail");
|
|
7933
|
+
}
|
|
7399
7934
|
function routeByPort(node, deps, port) {
|
|
7400
7935
|
return deps.model.selectByPort(node.id, port) !== null ? { kind: "take", port } : { kind: "advance" };
|
|
7401
7936
|
}
|
|
@@ -7407,11 +7942,15 @@ function menuDigitOf(b) {
|
|
|
7407
7942
|
function menuLabelOf(label) {
|
|
7408
7943
|
return label.replace(/^\s*[0-9*#]\s*[-—–:.]*\s*/, "").trim() || label.trim();
|
|
7409
7944
|
}
|
|
7410
|
-
|
|
7945
|
+
async function speakLine(text, deps) {
|
|
7946
|
+
await speak(text, deps);
|
|
7947
|
+
}
|
|
7948
|
+
var ASK_MAX_CLARIFY, COLLECT_MAX_STALLED_TURNS, LLM_MAX_TURNS, HUB_MAX_TURNS, NO_INPUT_MS, sayExecutor, YES_WORDS, NO_WORDS, confirmExecutor, legacyAskExecutor, ASK_POLICIES, askExecutor, KEEP_ASKING, SKIP_INTENT, OFFERS_IT, gateExecutor, endExecutor, handoffExecutor, OPENING_SILENCE_MS, llmExecutor, toolExecutor, ragExecutor, codeExecutor, decisionExecutor, MENU_MAX_INPUTS, menuExecutor, classifyExecutor, routerExecutor, playbookExecutor, setExecutor, MAX_SUBFLOW_DEPTH, subflowExecutor, EmailStepTimeout, EMAIL_SEND_TIMEOUT_MS, emailExecutor, passthroughExecutor, EXECUTORS;
|
|
7411
7949
|
var init_executors = __esm({
|
|
7412
7950
|
"src/runtime/graph/executors.ts"() {
|
|
7413
7951
|
init_log();
|
|
7414
7952
|
init_src();
|
|
7953
|
+
init_reask_templates();
|
|
7415
7954
|
init_interpolate();
|
|
7416
7955
|
init_builtins();
|
|
7417
7956
|
init_conditions();
|
|
@@ -7433,9 +7972,11 @@ var init_executors = __esm({
|
|
|
7433
7972
|
NO_WORDS = /\b(no|nope|nah|wrong|incorrect|negative|not right|that'?s not|change|actually)\b/i;
|
|
7434
7973
|
confirmExecutor = async (node, state, deps) => {
|
|
7435
7974
|
const text = interpolate(str(node.config.text), scopeFor(state, deps));
|
|
7436
|
-
|
|
7975
|
+
const line = text || "Is that correct?";
|
|
7976
|
+
await speak(line, deps);
|
|
7977
|
+
const reprompt = isEnglishLanguage(deps.askContext?.language) ? notCaught(line) : line;
|
|
7437
7978
|
for (let attempt = 0; attempt < 2; attempt++) {
|
|
7438
|
-
const t = await nextTurn(deps, state,
|
|
7979
|
+
const t = await nextTurn(deps, state, reprompt);
|
|
7439
7980
|
if ("result" in t) return t.result;
|
|
7440
7981
|
let verdict = null;
|
|
7441
7982
|
if (t.dtmf === "1") verdict = "yes";
|
|
@@ -7463,7 +8004,7 @@ var init_executors = __esm({
|
|
|
7463
8004
|
deps.events.note("confirm.unresolved", { node: node.id });
|
|
7464
8005
|
return { kind: "advance" };
|
|
7465
8006
|
};
|
|
7466
|
-
|
|
8007
|
+
legacyAskExecutor = async (node, state, deps) => {
|
|
7467
8008
|
const slot = str(node.config.slot) || node.id;
|
|
7468
8009
|
const authoredSlot = slot === node.id ? "" : slot.trim();
|
|
7469
8010
|
const label = authoredSlot.replace(/[_-]+/g, " ");
|
|
@@ -7504,6 +8045,165 @@ var init_executors = __esm({
|
|
|
7504
8045
|
deps.events.note("ask.unfilled", { slot: slot.trim() });
|
|
7505
8046
|
return { kind: "advance" };
|
|
7506
8047
|
};
|
|
8048
|
+
ASK_POLICIES = /* @__PURE__ */ new Set(["offer_skip", "fallback", "end"]);
|
|
8049
|
+
askExecutor = async (node, state, deps) => {
|
|
8050
|
+
const cfg = node.config;
|
|
8051
|
+
if (typeof cfg.maxAttempts !== "number") return legacyAskExecutor(node, state, deps);
|
|
8052
|
+
const slot = str(cfg.slot) || node.id;
|
|
8053
|
+
const field = slot.trim();
|
|
8054
|
+
const label = (slot === node.id ? "" : field).replace(/[_-]+/g, " ");
|
|
8055
|
+
const required = cfg.required !== false;
|
|
8056
|
+
const maxAttempts = Math.min(6, Math.max(1, Math.round(cfg.maxAttempts)));
|
|
8057
|
+
const policy = ASK_POLICIES.has(str(cfg.onExhausted)) ? str(cfg.onExhausted) : "offer_skip";
|
|
8058
|
+
const rephrase = cfg.rephrase === "all" || cfg.rephrase === "never" ? cfg.rephrase : "reasks";
|
|
8059
|
+
const unfilledWired = deps.model.selectByPort(node.id, UNFILLED_PORT_ID) !== null;
|
|
8060
|
+
const captured = () => hasSlot(deps.processRt.getData(), slot);
|
|
8061
|
+
const exitNext = { kind: "advance" };
|
|
8062
|
+
if (captured()) {
|
|
8063
|
+
deps.events.note("ask.skipped", { slot: field, reason: "already known" });
|
|
8064
|
+
return exitNext;
|
|
8065
|
+
}
|
|
8066
|
+
const askText = renderLine(str(cfg.ask) || defaultAskText(slot, node.id), state, deps);
|
|
8067
|
+
const lines = askLinesFor(node, state, deps, askText, label, rephrase);
|
|
8068
|
+
const closing = () => interpolate(str(cfg.exhaustedMessage) || DEFAULT_ASK_EXHAUSTED_MESSAGE, scopeFor(state, deps));
|
|
8069
|
+
const giveUp = async () => {
|
|
8070
|
+
state.closingLine = closing();
|
|
8071
|
+
if (unfilledWired) return { kind: "take", port: UNFILLED_PORT_ID };
|
|
8072
|
+
await speak(state.closingLine, deps);
|
|
8073
|
+
return { kind: "end" };
|
|
8074
|
+
};
|
|
8075
|
+
let lastLine = askText;
|
|
8076
|
+
let rotation = 0;
|
|
8077
|
+
const nextLine = () => {
|
|
8078
|
+
const pool = lines.pool;
|
|
8079
|
+
if (pool.length === 0) return askText;
|
|
8080
|
+
let line = pool[rotation++ % pool.length];
|
|
8081
|
+
if (line.spoken === lastLine.spoken && pool.length > 1) line = pool[rotation++ % pool.length];
|
|
8082
|
+
return line;
|
|
8083
|
+
};
|
|
8084
|
+
const sayLine = async (line) => {
|
|
8085
|
+
lastLine = line;
|
|
8086
|
+
await speak(line.spoken, deps);
|
|
8087
|
+
};
|
|
8088
|
+
const kind = cfg.type === "phone" || cfg.type === "number" ? cfg.type : void 0;
|
|
8089
|
+
const turnOpts = (since) => ({
|
|
8090
|
+
fields: [slot],
|
|
8091
|
+
// the ask's question is about its slot by definition: every turn here answers it (a free-text slot takes the turn)
|
|
8092
|
+
answering: { fields: answerable(deps, [slot]), step: node.id, since },
|
|
8093
|
+
...kind ? { number: { field: slot, kind } } : {}
|
|
8094
|
+
});
|
|
8095
|
+
let askedAt = Date.now();
|
|
8096
|
+
if (required && state.skipped?.[field]) {
|
|
8097
|
+
const revisits = (state.skipRevisits?.[field] ?? 0) + 1;
|
|
8098
|
+
state.skipRevisits = { ...state.skipRevisits, [field]: revisits };
|
|
8099
|
+
if (revisits >= 2) {
|
|
8100
|
+
deps.events.note("ask.revisit_exhausted", { slot: field });
|
|
8101
|
+
return giveUp();
|
|
8102
|
+
}
|
|
8103
|
+
deps.events.note("ask.revisit", { slot: field });
|
|
8104
|
+
await sayLine(askText);
|
|
8105
|
+
const t = await nextTurn(deps, state, () => sayLine(nextLine()), turnOpts(askedAt));
|
|
8106
|
+
if ("result" in t) return t.result;
|
|
8107
|
+
if (captured()) {
|
|
8108
|
+
deps.events.note("ask.captured", { slot: field });
|
|
8109
|
+
return exitNext;
|
|
8110
|
+
}
|
|
8111
|
+
deps.events.note("ask.revisit_unfilled", { slot: field });
|
|
8112
|
+
return giveUp();
|
|
8113
|
+
}
|
|
8114
|
+
const clarify = async (question) => {
|
|
8115
|
+
const endOn = lines.pool.length > 0 ? nextLine() : null;
|
|
8116
|
+
const what = label || "the detail just asked about";
|
|
8117
|
+
const parts = [
|
|
8118
|
+
`You are collecting "${what}" from the caller. They haven't given a clear, valid value yet.`,
|
|
8119
|
+
question ? "The caller asked a question: answer it briefly and honestly first." : "Acknowledge what they said in a few words.",
|
|
8120
|
+
// the model-safe copies only: caller-given values fenced and tokenised, never raw (§B.9)
|
|
8121
|
+
endOn ? `Then ask for just that again, ending on this question: "${endOn.model}"` : "Then briefly ask again for just that.",
|
|
8122
|
+
`Don't repeat this line word for word: "${lastLine.model}"`
|
|
8123
|
+
];
|
|
8124
|
+
const instructions = interpolate(str(cfg.instructions), modelScopeFor(state, deps));
|
|
8125
|
+
if (instructions) parts.push(`About this question (from the flow's author): ${instructions}`);
|
|
8126
|
+
if (endOn) lastLine = endOn;
|
|
8127
|
+
await withHistory(deps, historyPolicyOf(node), () => genReply(deps, parts.join("\n")));
|
|
8128
|
+
};
|
|
8129
|
+
const round = async (attempts) => {
|
|
8130
|
+
let tries = 0;
|
|
8131
|
+
for (let turn = 1; ; turn++) {
|
|
8132
|
+
const t = await nextTurn(deps, state, () => sayLine(nextLine()), turnOpts(askedAt));
|
|
8133
|
+
if ("result" in t) return t.result;
|
|
8134
|
+
if (captured()) return "captured";
|
|
8135
|
+
const text = t.text.trim();
|
|
8136
|
+
const filler = isFiller(text);
|
|
8137
|
+
const question = !filler && isQuestion(text);
|
|
8138
|
+
if (!question) tries += 1;
|
|
8139
|
+
if (tries >= attempts) return "exhausted";
|
|
8140
|
+
if (turn >= 2 * attempts) {
|
|
8141
|
+
deps.events.note("ask.turn_cap", { slot: field, turns: turn });
|
|
8142
|
+
return "exhausted";
|
|
8143
|
+
}
|
|
8144
|
+
deps.events.note("ask.clarify", { slot: field, turn, kind: filler ? "filler" : question ? "question" : "answer" });
|
|
8145
|
+
if (filler && lines.pool.length > 0 || rephrase === "never") await sayLine(nextLine());
|
|
8146
|
+
else await clarify(question);
|
|
8147
|
+
}
|
|
8148
|
+
};
|
|
8149
|
+
await sayLine(lines.voicedAsk ?? askText);
|
|
8150
|
+
let outcome = await round(maxAttempts);
|
|
8151
|
+
if (outcome === "captured") {
|
|
8152
|
+
deps.events.note("ask.captured", { slot: field });
|
|
8153
|
+
return exitNext;
|
|
8154
|
+
}
|
|
8155
|
+
if (outcome !== "exhausted") return outcome;
|
|
8156
|
+
if (!required) {
|
|
8157
|
+
deps.events.note("ask.unfilled", { slot: field });
|
|
8158
|
+
return exitNext;
|
|
8159
|
+
}
|
|
8160
|
+
deps.events.note("ask.exhausted", { slot: field, policy });
|
|
8161
|
+
if (policy === "end" || policy === "fallback") return giveUp();
|
|
8162
|
+
const offer = lines.skipOffer;
|
|
8163
|
+
if (!offer) return giveUp();
|
|
8164
|
+
state.closingLine = closing();
|
|
8165
|
+
const skip = (reason) => {
|
|
8166
|
+
state.skipped = { ...state.skipped, [field]: reason };
|
|
8167
|
+
deps.events.note("ask.skipped_by_caller", { slot: field, reason });
|
|
8168
|
+
return unfilledWired ? { kind: "take", port: UNFILLED_PORT_ID } : exitNext;
|
|
8169
|
+
};
|
|
8170
|
+
let unclear = 0;
|
|
8171
|
+
await sayLine(offer);
|
|
8172
|
+
for (; ; ) {
|
|
8173
|
+
const t = await nextTurn(deps, state, offer.spoken, { fields: [slot], captureAfterRead: true });
|
|
8174
|
+
if ("result" in t) return t.result;
|
|
8175
|
+
const verdict = await readYesNo(t, state, deps);
|
|
8176
|
+
if (verdict === "yes") {
|
|
8177
|
+
await t.capture?.({ exclude: [slot] });
|
|
8178
|
+
return skip("caller");
|
|
8179
|
+
}
|
|
8180
|
+
if (verdict === "no") {
|
|
8181
|
+
await t.capture?.({ exclude: [slot] });
|
|
8182
|
+
deps.events.note("ask.skip_declined", { slot: field });
|
|
8183
|
+
askedAt = Date.now();
|
|
8184
|
+
await sayLine(nextLine());
|
|
8185
|
+
outcome = await round(maxAttempts);
|
|
8186
|
+
if (outcome === "captured") {
|
|
8187
|
+
deps.events.note("ask.captured", { slot: field });
|
|
8188
|
+
return exitNext;
|
|
8189
|
+
}
|
|
8190
|
+
if (outcome !== "exhausted") return outcome;
|
|
8191
|
+
deps.events.note("ask.exhausted", { slot: field, policy, round: 2 });
|
|
8192
|
+
return giveUp();
|
|
8193
|
+
}
|
|
8194
|
+
await t.capture?.();
|
|
8195
|
+
if (captured()) {
|
|
8196
|
+
deps.events.note("ask.captured", { slot: field });
|
|
8197
|
+
return exitNext;
|
|
8198
|
+
}
|
|
8199
|
+
unclear += 1;
|
|
8200
|
+
if (unclear >= 2) return skip("unclear");
|
|
8201
|
+
await sayLine(offer);
|
|
8202
|
+
}
|
|
8203
|
+
};
|
|
8204
|
+
KEEP_ASKING = /\b(?:don'?t|do not|not|never|no need to)(?:\s+\w+){0,3}\s+skip\b|\bi(?:'ve| have)(?: got)? it\b/i;
|
|
8205
|
+
SKIP_INTENT = /\b(?:skip|don'?t have|do not have|haven'?t got|move on|pass on (?:it|that|this))\b/i;
|
|
8206
|
+
OFFERS_IT = /\b(?:give|here|hold on|let me|it'?s|my number)\b/i;
|
|
7507
8207
|
gateExecutor = async (node, _state, deps) => {
|
|
7508
8208
|
const required = Array.isArray(node.config.requiredFields) ? node.config.requiredFields.filter((x) => typeof x === "string") : [];
|
|
7509
8209
|
const data = deps.processRt.getData();
|
|
@@ -7526,7 +8226,7 @@ var init_executors = __esm({
|
|
|
7526
8226
|
}
|
|
7527
8227
|
const reason = str(cfg.reason) || `flow:${node.id}`;
|
|
7528
8228
|
const mode = cfg.mode === "warm" || cfg.mode === "cold" ? cfg.mode : void 0;
|
|
7529
|
-
const briefing = str(cfg.briefing) ? interpolate(str(cfg.briefing),
|
|
8229
|
+
const briefing = str(cfg.briefing) ? interpolate(str(cfg.briefing), briefingScope(str(cfg.briefing), state, deps)) : void 0;
|
|
7530
8230
|
deps.events.note("handoff.start", { node: node.id, to: target, ...mode ? { mode } : {} });
|
|
7531
8231
|
try {
|
|
7532
8232
|
await deps.ctx.handoff(target, {
|
|
@@ -7630,9 +8330,17 @@ ${collected}`);
|
|
|
7630
8330
|
const toolRef = str(cfg.toolRef);
|
|
7631
8331
|
const registry = deps.resolvers?.registryTools;
|
|
7632
8332
|
if (toolRef && registry) {
|
|
7633
|
-
deps.events.note("tool.invoked", { node: node.id, name: toolRef, ref: true });
|
|
7634
8333
|
try {
|
|
7635
8334
|
const spec = (await registry.catalog()).find((t) => t.name === toolRef);
|
|
8335
|
+
const blocked2 = blockOnMissingInputs(
|
|
8336
|
+
node,
|
|
8337
|
+
state,
|
|
8338
|
+
deps,
|
|
8339
|
+
Object.keys(spec?.input ?? {}).filter((f) => isRequiredField(deps, f)),
|
|
8340
|
+
true
|
|
8341
|
+
);
|
|
8342
|
+
if (blocked2) return blocked2;
|
|
8343
|
+
deps.events.note("tool.invoked", { node: node.id, name: toolRef, ref: true });
|
|
7636
8344
|
const slots2 = deps.processRt.getData();
|
|
7637
8345
|
const bag2 = {};
|
|
7638
8346
|
for (const field of Object.keys(spec?.input ?? {})) {
|
|
@@ -7665,6 +8373,8 @@ ${collected}`);
|
|
|
7665
8373
|
deps.events.note("tool.unavailable", { node: node.id, name: toolRef });
|
|
7666
8374
|
return routeByOutcome();
|
|
7667
8375
|
}
|
|
8376
|
+
const blocked = blockOnMissingInputs(node, state, deps, requiredInputsOf(node, state, deps), true);
|
|
8377
|
+
if (blocked) return blocked;
|
|
7668
8378
|
const url = str(cfg.url);
|
|
7669
8379
|
if (!url) {
|
|
7670
8380
|
state.toolResults[node.id] = { ok: false, status: 0, data: null };
|
|
@@ -7890,7 +8600,7 @@ ${collected}`);
|
|
|
7890
8600
|
deps.events.note("menu.selected", { node: node.id, choice });
|
|
7891
8601
|
return { kind: "take", port: choice };
|
|
7892
8602
|
}
|
|
7893
|
-
await speak(prompt, deps);
|
|
8603
|
+
await speak(isEnglishLanguage(deps.askContext?.language) && prompt ? notCaught(prompt) : prompt, deps);
|
|
7894
8604
|
}
|
|
7895
8605
|
}
|
|
7896
8606
|
deps.events.note("menu.unmatched", { node: node.id });
|
|
@@ -8163,7 +8873,7 @@ ${collected}`);
|
|
|
8163
8873
|
}
|
|
8164
8874
|
await speak(
|
|
8165
8875
|
interpolate(
|
|
8166
|
-
str(cfg.incompleteMessage) ||
|
|
8876
|
+
str(cfg.incompleteMessage) || DEFAULT_ASK_EXHAUSTED_MESSAGE,
|
|
8167
8877
|
scopeFor(state, deps)
|
|
8168
8878
|
),
|
|
8169
8879
|
deps
|
|
@@ -8219,6 +8929,7 @@ ${collected}`);
|
|
|
8219
8929
|
...deps.resolvers ? { resolvers: deps.resolvers } : {},
|
|
8220
8930
|
...deps.live ? { live: deps.live } : {},
|
|
8221
8931
|
...deps.security ? { security: deps.security } : {},
|
|
8932
|
+
...deps.askContext ? { askContext: deps.askContext } : {},
|
|
8222
8933
|
depth: depth + 1
|
|
8223
8934
|
});
|
|
8224
8935
|
deps.events.note("subflow.exit", { node: node.id, ref, outcome: outcome.kind });
|
|
@@ -8231,6 +8942,8 @@ ${collected}`);
|
|
|
8231
8942
|
EMAIL_SEND_TIMEOUT_MS = 1e4;
|
|
8232
8943
|
emailExecutor = async (node, state, deps) => {
|
|
8233
8944
|
const cfg = node.config;
|
|
8945
|
+
const blocked = blockOnMissingInputs(node, state, deps, requiredInputsOf(node, state, deps), false);
|
|
8946
|
+
if (blocked) return blocked;
|
|
8234
8947
|
const scope2 = scopeFor(state, deps);
|
|
8235
8948
|
const render = (key) => interpolate(str(cfg[key]), scope2).trim();
|
|
8236
8949
|
const to = render("to");
|
|
@@ -8340,6 +9053,12 @@ async function selectNext(model2, from, result, state, deps) {
|
|
|
8340
9053
|
...state.lastDtmf !== void 0 ? { lastDtmf: state.lastDtmf } : {}
|
|
8341
9054
|
});
|
|
8342
9055
|
}
|
|
9056
|
+
async function closeAtLoopGuard(state, deps) {
|
|
9057
|
+
try {
|
|
9058
|
+
await speakLine(state.closingLine ?? DEFAULT_ASK_EXHAUSTED_MESSAGE, { ctx: deps.ctx, ...deps.realtime ? { realtime: true } : {} });
|
|
9059
|
+
} catch {
|
|
9060
|
+
}
|
|
9061
|
+
}
|
|
8343
9062
|
async function runFlowGraph(program, deps) {
|
|
8344
9063
|
const model2 = buildGraphModel(program);
|
|
8345
9064
|
const maxNodeVisits = deps.maxNodeVisits ?? DEFAULT_MAX_NODE_VISITS;
|
|
@@ -8352,6 +9071,7 @@ async function runFlowGraph(program, deps) {
|
|
|
8352
9071
|
while (cursor !== null) {
|
|
8353
9072
|
if (++steps > maxSteps) {
|
|
8354
9073
|
deps.events.note("flow.loop_guard_tripped", { reason: "max_steps", steps });
|
|
9074
|
+
await closeAtLoopGuard(state, deps);
|
|
8355
9075
|
break;
|
|
8356
9076
|
}
|
|
8357
9077
|
const node = model2.node(cursor);
|
|
@@ -8364,6 +9084,7 @@ async function runFlowGraph(program, deps) {
|
|
|
8364
9084
|
const visitCap = node.config.hub === true ? HUB_MAX_NODE_VISITS : maxNodeVisits;
|
|
8365
9085
|
if (visit > visitCap) {
|
|
8366
9086
|
deps.events.note("flow.loop_guard_tripped", { reason: "max_node_visits", nodeId: cursor });
|
|
9087
|
+
await closeAtLoopGuard(state, deps);
|
|
8367
9088
|
break;
|
|
8368
9089
|
}
|
|
8369
9090
|
state.cursor = cursor;
|
|
@@ -8384,7 +9105,8 @@ async function runFlowGraph(program, deps) {
|
|
|
8384
9105
|
...deps.depth !== void 0 ? { depth: deps.depth } : {},
|
|
8385
9106
|
...deps.busy ? { busy: deps.busy } : {},
|
|
8386
9107
|
...deps.live ? { live: deps.live } : {},
|
|
8387
|
-
...deps.security ? { security: deps.security } : {}
|
|
9108
|
+
...deps.security ? { security: deps.security } : {},
|
|
9109
|
+
...deps.askContext ? { askContext: deps.askContext } : {}
|
|
8388
9110
|
});
|
|
8389
9111
|
} catch (err) {
|
|
8390
9112
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -8417,6 +9139,7 @@ var init_interpreter = __esm({
|
|
|
8417
9139
|
init_log();
|
|
8418
9140
|
init_graph_model();
|
|
8419
9141
|
init_executors();
|
|
9142
|
+
init_src();
|
|
8420
9143
|
init_conditions();
|
|
8421
9144
|
DEFAULT_MAX_NODE_VISITS = 25;
|
|
8422
9145
|
DEFAULT_MAX_STEPS = 200;
|
|
@@ -9377,6 +10100,13 @@ function createUtteranceStream(session, processRt, ctx, rails, prewarm, wantsDtm
|
|
|
9377
10100
|
...number ? { numberField: number.field } : {},
|
|
9378
10101
|
...answer ? { answering: answer.field } : {}
|
|
9379
10102
|
};
|
|
10103
|
+
if (opts?.captureAfterRead) {
|
|
10104
|
+
const text = raw.text;
|
|
10105
|
+
return {
|
|
10106
|
+
...turn,
|
|
10107
|
+
capture: (o) => capture(text, false, { ...step, ...o?.exclude?.length ? { exclude: o.exclude } : {} }, answer)
|
|
10108
|
+
};
|
|
10109
|
+
}
|
|
9380
10110
|
if (opts?.deferCapture) {
|
|
9381
10111
|
const run = capture(raw.text, true, step, answer).then(() => {
|
|
9382
10112
|
if (pending === run) pending = null;
|
|
@@ -10131,7 +10861,8 @@ var NOT_SENT = /* @__PURE__ */ new Set([
|
|
|
10131
10861
|
"email_provider_error",
|
|
10132
10862
|
"email_send_failed",
|
|
10133
10863
|
"insufficient_balance",
|
|
10134
|
-
|
|
10864
|
+
// a hard budget refused it (the key's monthly budget replaced the old per-key spend cap — monetization MON2)
|
|
10865
|
+
"budget_exceeded"
|
|
10135
10866
|
]);
|
|
10136
10867
|
function dedupeConversationEmails(resolver) {
|
|
10137
10868
|
const sends = /* @__PURE__ */ new Map();
|
|
@@ -10429,7 +11160,8 @@ async function runFlowProgram(program, ctx, opts) {
|
|
|
10429
11160
|
...opts.resolvers ? { resolvers: opts.resolvers } : {},
|
|
10430
11161
|
...opts.busy ? { busy: opts.busy } : {},
|
|
10431
11162
|
...opts.live ? { live: opts.live } : {},
|
|
10432
|
-
...security ? { security } : {}
|
|
11163
|
+
...security ? { security } : {},
|
|
11164
|
+
...opts.askContext ? { askContext: opts.askContext } : {}
|
|
10433
11165
|
});
|
|
10434
11166
|
graphLog("outcome", outcome);
|
|
10435
11167
|
return outcome;
|
|
@@ -10945,14 +11677,15 @@ function createProcessRuntime(def) {
|
|
|
10945
11677
|
console.error("[process] onField threw", err);
|
|
10946
11678
|
}
|
|
10947
11679
|
};
|
|
10948
|
-
const
|
|
11680
|
+
const excluded = (name) => opts?.exclude?.some((f) => f.trim() === name.trim()) === true;
|
|
11681
|
+
const answering = opts?.answering !== void 0 && !excluded(opts.answering) ? opts.answering : void 0;
|
|
10949
11682
|
const answerField = answering !== void 0 ? def.collect[answering] : void 0;
|
|
10950
11683
|
const answerWasOpen = answering !== void 0 && !callerGave.has(answering);
|
|
10951
11684
|
const status = primitive.getFieldStatus();
|
|
10952
11685
|
const captured = (name) => status.find((f) => f.name === name)?.captured === true;
|
|
10953
11686
|
const stepFields = opts?.stepFields;
|
|
10954
11687
|
const named = (name) => stepFields?.some((f) => f.trim() === name.trim()) === true;
|
|
10955
|
-
const spoken = Object.entries(def.collect).filter(([, f]) => f.fromCaller !== false);
|
|
11688
|
+
const spoken = Object.entries(def.collect).filter(([name, f]) => f.fromCaller !== false && !excluded(name));
|
|
10956
11689
|
const open = (name) => !captured(name) || named(name) && !callerGave.has(name);
|
|
10957
11690
|
const remaining = spoken.filter(([name]) => open(name) && (!completed || stepFields === void 0 || named(name)));
|
|
10958
11691
|
const correctable = spoken.filter(([name]) => captured(name) && named(name) && callerGave.has(name));
|
|
@@ -11475,13 +12208,13 @@ function createTextSessionAdapter(transport) {
|
|
|
11475
12208
|
};
|
|
11476
12209
|
function setState(next) {
|
|
11477
12210
|
const raw = String(next);
|
|
11478
|
-
const
|
|
12211
|
+
const norm2 = normalizeState(raw);
|
|
11479
12212
|
const oldState = currentState;
|
|
11480
12213
|
const rawOld = currentRaw;
|
|
11481
|
-
currentState =
|
|
12214
|
+
currentState = norm2;
|
|
11482
12215
|
currentRaw = raw;
|
|
11483
|
-
fire("state", { oldState, newState:
|
|
11484
|
-
if (NON_BUSY_STATES.has(
|
|
12216
|
+
fire("state", { oldState, newState: norm2, rawOldState: rawOld, rawNewState: raw });
|
|
12217
|
+
if (NON_BUSY_STATES.has(norm2)) releaseIdleWaiters("idle");
|
|
11485
12218
|
}
|
|
11486
12219
|
function push(text, opts) {
|
|
11487
12220
|
if (closed || text.length === 0) return;
|
|
@@ -11541,7 +12274,6 @@ function processSchemaToDefinition(schema) {
|
|
|
11541
12274
|
} : {}
|
|
11542
12275
|
};
|
|
11543
12276
|
}
|
|
11544
|
-
var DEFAULT_FLOW_PROMPT = "You are a helpful voice assistant. Keep replies short and natural.";
|
|
11545
12277
|
function toTriggerCondition(on) {
|
|
11546
12278
|
return on.startsWith("signal:") ? on : new RegExp(on, "i");
|
|
11547
12279
|
}
|
|
@@ -11561,7 +12293,7 @@ function processSchemaToAgentParts(schema, opts = {}) {
|
|
|
11561
12293
|
if (p.text.trim()) promptParts.push(p.text.trim());
|
|
11562
12294
|
}
|
|
11563
12295
|
}
|
|
11564
|
-
const prompt = promptParts.length > 0 ? promptParts.join("\n\n") :
|
|
12296
|
+
const prompt = promptParts.length > 0 ? promptParts.join("\n\n") : DEFAULT_FLOW_PERSONA;
|
|
11565
12297
|
let triggers;
|
|
11566
12298
|
if (schema.triggers && schema.triggers.length > 0) {
|
|
11567
12299
|
triggers = {};
|
|
@@ -11738,7 +12470,8 @@ function runTextSession(opts) {
|
|
|
11738
12470
|
events,
|
|
11739
12471
|
...opts.maxSteps !== void 0 ? { maxSteps: opts.maxSteps } : {},
|
|
11740
12472
|
...opts.onAwaitInput ? { onAwaitInput: opts.onAwaitInput } : {},
|
|
11741
|
-
...opts.resolvers ? { resolvers: opts.resolvers } : {}
|
|
12473
|
+
...opts.resolvers ? { resolvers: opts.resolvers } : {},
|
|
12474
|
+
...opts.askContext ? { askContext: opts.askContext } : {}
|
|
11742
12475
|
});
|
|
11743
12476
|
return {
|
|
11744
12477
|
sendUserMessage: (text) => {
|
|
@@ -13196,6 +13929,40 @@ function computeEmptyRequired(view) {
|
|
|
13196
13929
|
return empty;
|
|
13197
13930
|
}
|
|
13198
13931
|
|
|
13932
|
+
// src/flow-persona-boot.ts
|
|
13933
|
+
init_src();
|
|
13934
|
+
var VOICE_FLOW_CUE = "You are on a voice call: speak as in a natural spoken conversation.";
|
|
13935
|
+
async function resolveFlowPersona(agentId, fetchConfig, emit) {
|
|
13936
|
+
let stored = null;
|
|
13937
|
+
let unavailable = false;
|
|
13938
|
+
try {
|
|
13939
|
+
stored = await fetchConfig();
|
|
13940
|
+
} catch {
|
|
13941
|
+
unavailable = true;
|
|
13942
|
+
emit("flow.persona_unavailable", { agentId });
|
|
13943
|
+
}
|
|
13944
|
+
const persona = unavailable ? DEFAULT_FLOW_PERSONA : flowPersonaOf(stored);
|
|
13945
|
+
return {
|
|
13946
|
+
stored,
|
|
13947
|
+
unavailable,
|
|
13948
|
+
persona,
|
|
13949
|
+
basePrompt: `${persona}
|
|
13950
|
+
|
|
13951
|
+
${VOICE_FLOW_CUE}`,
|
|
13952
|
+
askContext: {
|
|
13953
|
+
persona,
|
|
13954
|
+
routing: stored?.routingInstructions ?? null,
|
|
13955
|
+
language: stored?.language ?? null
|
|
13956
|
+
}
|
|
13957
|
+
};
|
|
13958
|
+
}
|
|
13959
|
+
function recordBootNotes(notes, graph, process2) {
|
|
13960
|
+
for (const n of notes) {
|
|
13961
|
+
if (graph) graph.note(n.kind, n.data);
|
|
13962
|
+
else process2.recordEvent({ kind: n.kind, data: n.data });
|
|
13963
|
+
}
|
|
13964
|
+
}
|
|
13965
|
+
|
|
13199
13966
|
// src/agent-defaults.ts
|
|
13200
13967
|
init_providers2();
|
|
13201
13968
|
init_registry();
|
|
@@ -16768,13 +17535,7 @@ function isSipParticipant2(p) {
|
|
|
16768
17535
|
}
|
|
16769
17536
|
|
|
16770
17537
|
// src/runtime/prompt-compose.ts
|
|
16771
|
-
|
|
16772
|
-
const routing = routingInstructions?.trim();
|
|
16773
|
-
return routing ? `${prompt}
|
|
16774
|
-
|
|
16775
|
-
# Routing
|
|
16776
|
-
${routing}` : prompt;
|
|
16777
|
-
}
|
|
17538
|
+
init_src();
|
|
16778
17539
|
function composeAgentInstructions(input) {
|
|
16779
17540
|
const composed = composeSystemPrompt(input.prompt, input.routingInstructions);
|
|
16780
17541
|
return input.graph ? composed : input.augment(composed);
|
|
@@ -17355,14 +18116,14 @@ function normalize(text) {
|
|
|
17355
18116
|
return text.toLowerCase().replace(/\s+/g, " ").trim();
|
|
17356
18117
|
}
|
|
17357
18118
|
function detectVoicemailGreeting(text) {
|
|
17358
|
-
const
|
|
17359
|
-
if (!
|
|
17360
|
-
return VOICEMAIL_PATTERNS.some((re) => re.test(
|
|
18119
|
+
const norm2 = normalize(text);
|
|
18120
|
+
if (!norm2) return false;
|
|
18121
|
+
return VOICEMAIL_PATTERNS.some((re) => re.test(norm2));
|
|
17361
18122
|
}
|
|
17362
18123
|
function detectBeepCue(text) {
|
|
17363
|
-
const
|
|
17364
|
-
if (!
|
|
17365
|
-
return BEEP_CUE_PATTERNS.some((re) => re.test(
|
|
18124
|
+
const norm2 = normalize(text);
|
|
18125
|
+
if (!norm2) return false;
|
|
18126
|
+
return BEEP_CUE_PATTERNS.some((re) => re.test(norm2));
|
|
17366
18127
|
}
|
|
17367
18128
|
var DisabledAmdRuntime = class {
|
|
17368
18129
|
enabled = false;
|
|
@@ -19578,6 +20339,8 @@ var Agent = class {
|
|
|
19578
20339
|
flowBootFailure = "worker_identity_refused";
|
|
19579
20340
|
}
|
|
19580
20341
|
let flowAgentName = null;
|
|
20342
|
+
let flowAskContext;
|
|
20343
|
+
const bootNotes = [];
|
|
19581
20344
|
let loadedPlan = null;
|
|
19582
20345
|
const planIdValue = stringValue(dispatchMdEarly["planId"]);
|
|
19583
20346
|
if (planIdValue && sdkClient) {
|
|
@@ -19666,9 +20429,14 @@ var Agent = class {
|
|
|
19666
20429
|
} else {
|
|
19667
20430
|
const graphOn = process.env["VL_FLOW_GRAPH_RUNTIME"] !== "0" && !!schema.program;
|
|
19668
20431
|
const schemaToolExecutor = buildGraphResolvers(callCredential)?.toolExecutor;
|
|
20432
|
+
const personaBoot = await resolveFlowPersona(flowAgentId, () => sdkClient.agents.getConfig(flowAgentId), (kind, data) => {
|
|
20433
|
+
console.warn(`[agent] ${kind}: the flow's published config couldn't be read; the default persona runs`, data);
|
|
20434
|
+
bootNotes.push({ kind, data: { ...data } });
|
|
20435
|
+
});
|
|
20436
|
+
flowAskContext = personaBoot.askContext;
|
|
19669
20437
|
const parts = processSchemaToAgentParts(schema, {
|
|
19670
20438
|
...schemaToolExecutor ? { toolExecutor: schemaToolExecutor } : {},
|
|
19671
|
-
|
|
20439
|
+
basePrompt: personaBoot.basePrompt,
|
|
19672
20440
|
...graphOn && schema.program ? { graphNodeIds: new Set(Object.keys(schema.program.nodes)) } : {}
|
|
19673
20441
|
});
|
|
19674
20442
|
effectiveConfig = {
|
|
@@ -19693,11 +20461,12 @@ var Agent = class {
|
|
|
19693
20461
|
sdkClient,
|
|
19694
20462
|
flowAgentId,
|
|
19695
20463
|
effectiveConfig,
|
|
19696
|
-
void 0
|
|
20464
|
+
void 0,
|
|
20465
|
+
personaBoot.unavailable ? void 0 : { stored: personaBoot.stored }
|
|
19697
20466
|
)).config;
|
|
19698
20467
|
flowAgentName = await flowAgentDisplayName(
|
|
19699
20468
|
schema,
|
|
19700
|
-
async () => (await sdkClient.agents.getConfig(flowAgentId))?.name ?? null
|
|
20469
|
+
async () => personaBoot.unavailable ? (await sdkClient.agents.getConfig(flowAgentId))?.name ?? null : personaBoot.stored?.name ?? null
|
|
19701
20470
|
);
|
|
19702
20471
|
if (graphOn && schema.program) {
|
|
19703
20472
|
graphProgram = schema.program;
|
|
@@ -19753,6 +20522,7 @@ var Agent = class {
|
|
|
19753
20522
|
pipeline.llm.onIncident((incident) => emitOpEvent(`engine.llm.${incident.kind}`, { ...incident }));
|
|
19754
20523
|
}
|
|
19755
20524
|
const graphEvents = graphMode ? withOpEventFanout(createGraphEvents(processRt), emitOpEvent) : null;
|
|
20525
|
+
recordBootNotes(bootNotes, graphEvents, processRt);
|
|
19756
20526
|
const isOutbound = stringValue(dispatchMdEarly["direction"]) === "outbound";
|
|
19757
20527
|
const perCallAmd = stringValue(dispatchMdEarly["amd"]);
|
|
19758
20528
|
const amdMode = perCallAmd === "off" || perCallAmd === "detect" || perCallAmd === "detectMessageEnd" ? perCallAmd : effectiveConfig.outbound?.amd ?? "off";
|
|
@@ -20567,7 +21337,9 @@ ${callIntent}`
|
|
|
20567
21337
|
// Manual turn detection never runs SecureAgent.onUserTurnCompleted: each caller turn is guarded and sealed
|
|
20568
21338
|
// as the stream admits it, before it joins the model's conversation (burn-down G-25, security §B.7). The cast:
|
|
20569
21339
|
// lkAgent IS the SecureAgent when lkAgentIsSecure, whose factory type is deliberately loose (secure-agent.ts).
|
|
20570
|
-
...security && securityCallContext && lkAgentIsSecure ? { security: flowTurnSecurity(lkAgent, security, securityCallContext) } : {}
|
|
21340
|
+
...security && securityCallContext && lkAgentIsSecure ? { security: flowTurnSecurity(lkAgent, security, securityCallContext) } : {},
|
|
21341
|
+
// the published persona / routing / language the ask steps' pre-built lines are checked against (G-48)
|
|
21342
|
+
...flowAskContext ? { askContext: flowAskContext } : {}
|
|
20571
21343
|
});
|
|
20572
21344
|
if (outcome.kind === "error") {
|
|
20573
21345
|
console.error("[agent] flow graph run errored", {
|
|
@@ -20704,9 +21476,9 @@ function sharedBrainPubSub() {
|
|
|
20704
21476
|
}
|
|
20705
21477
|
return _brainPubSub;
|
|
20706
21478
|
}
|
|
20707
|
-
async function applyStoredPipeline(client, agentId, base, codeMode) {
|
|
21479
|
+
async function applyStoredPipeline(client, agentId, base, codeMode, prefetched) {
|
|
20708
21480
|
try {
|
|
20709
|
-
const stored = await client.agents.getConfig(agentId);
|
|
21481
|
+
const stored = prefetched ? prefetched.stored : await client.agents.getConfig(agentId);
|
|
20710
21482
|
if (!stored) return { config: base, brain: "none" };
|
|
20711
21483
|
const mode = codeMode ?? stored.mode;
|
|
20712
21484
|
const providers = /* @__PURE__ */ new Set();
|