@voicelayer/sdk 0.4.0 → 0.4.2
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/README.md +18 -9
- package/dist/brain/index.d.ts +2 -2
- package/dist/brain/index.js +77 -3
- package/dist/index.d.ts +255 -249
- package/dist/index.js +964 -817
- package/dist/runtime/text-session.d.ts +1 -1
- package/dist/runtime/text-session.js +119 -15
- package/dist/{text-session-BB0dLLH2.d.ts → text-session-CtXihRr6.d.ts} +98 -98
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -495,9 +495,11 @@ var init_agents = __esm({
|
|
|
495
495
|
});
|
|
496
496
|
ProcessSchemaDTO = z.object({
|
|
497
497
|
id: z.string().min(1).max(64),
|
|
498
|
-
// System-prompt fragments compiled from say/confirm nodes. The SDK bridge
|
|
499
|
-
// composes these into the agent's prompt.
|
|
500
|
-
|
|
498
|
+
// System-prompt fragments compiled from say/confirm/llm nodes. The SDK bridge
|
|
499
|
+
// composes these into the agent's prompt. An AI step's instructions are a
|
|
500
|
+
// system prompt in their own right, so the cap is generous (2,000 refused real
|
|
501
|
+
// flows — G-27); publish checks it (validateFlowGraph → compiledSchemaIssues).
|
|
502
|
+
prompts: z.array(z.object({ id: z.string().min(1).max(128), text: z.string().max(16e3) })).max(64).optional(),
|
|
501
503
|
fields: z.array(ProcessFieldDTO).max(64),
|
|
502
504
|
completionGate: z.object({
|
|
503
505
|
requiredFields: z.array(z.string()).max(64),
|
|
@@ -1794,6 +1796,46 @@ var init_call_result = __esm({
|
|
|
1794
1796
|
});
|
|
1795
1797
|
}
|
|
1796
1798
|
});
|
|
1799
|
+
var WEBHOOK_EVENT_TYPES, CallWebhookDirection, CallWebhookOutcome, CallWebhookAttributes, Party;
|
|
1800
|
+
var init_webhook_events = __esm({
|
|
1801
|
+
"../contracts/src/webhook-events.ts"() {
|
|
1802
|
+
WEBHOOK_EVENT_TYPES = ["call.started", "call.ended", "recording.ready"];
|
|
1803
|
+
z.enum(WEBHOOK_EVENT_TYPES);
|
|
1804
|
+
CallWebhookDirection = z.enum(["inbound", "outbound", "web"]);
|
|
1805
|
+
CallWebhookOutcome = z.enum(["completed", "dropped", "failed", "no_answer", "other"]);
|
|
1806
|
+
CallWebhookAttributes = z.record(z.union([z.string(), z.number(), z.boolean()]));
|
|
1807
|
+
Party = z.string().min(1).nullable();
|
|
1808
|
+
z.object({
|
|
1809
|
+
callId: z.string().uuid(),
|
|
1810
|
+
startedAt: z.string().datetime(),
|
|
1811
|
+
agentId: z.string().nullable(),
|
|
1812
|
+
phoneNumberId: z.string().nullable(),
|
|
1813
|
+
callerE164: Party,
|
|
1814
|
+
toE164: Party,
|
|
1815
|
+
// Null when it can't be told yet: an inbound phone call and a web session look alike until the caller joins. When
|
|
1816
|
+
// set, it is the same value `call.ended` carries; `call.ended` always has one.
|
|
1817
|
+
direction: CallWebhookDirection.nullable()
|
|
1818
|
+
});
|
|
1819
|
+
z.object({
|
|
1820
|
+
callId: z.string().uuid(),
|
|
1821
|
+
durationMs: z.number().int().min(0),
|
|
1822
|
+
endedAt: z.string().datetime(),
|
|
1823
|
+
endReason: z.string(),
|
|
1824
|
+
agentId: z.string().nullable(),
|
|
1825
|
+
phoneNumberId: z.string().nullable(),
|
|
1826
|
+
callerE164: Party,
|
|
1827
|
+
toE164: Party,
|
|
1828
|
+
direction: CallWebhookDirection,
|
|
1829
|
+
outcome: CallWebhookOutcome,
|
|
1830
|
+
attributes: CallWebhookAttributes
|
|
1831
|
+
});
|
|
1832
|
+
z.object({
|
|
1833
|
+
callId: z.string().uuid(),
|
|
1834
|
+
recordingUrl: z.string().url(),
|
|
1835
|
+
status: z.enum(["starting", "active", "completed", "failed", "aborted"])
|
|
1836
|
+
});
|
|
1837
|
+
}
|
|
1838
|
+
});
|
|
1797
1839
|
var PhoneNumberStatus, PhoneNumberProvider, PhoneNumberCapabilities, PhoneNumberRecord;
|
|
1798
1840
|
var init_numbers = __esm({
|
|
1799
1841
|
"../contracts/src/numbers.ts"() {
|
|
@@ -1872,28 +1914,17 @@ var init_flow_ports = __esm({
|
|
|
1872
1914
|
INCOMPLETE_PORT_ID = "incomplete";
|
|
1873
1915
|
}
|
|
1874
1916
|
});
|
|
1917
|
+
|
|
1918
|
+
// ../contracts/src/flow-edges.ts
|
|
1875
1919
|
function isLabelOnlyEdge(edge, sourceType) {
|
|
1876
1920
|
if (!CONVERSATIONAL_NODE_TYPES.has(sourceType)) return false;
|
|
1877
1921
|
if (edge.sourceHandle) return false;
|
|
1878
1922
|
if (typeof edge.condition === "string" && edge.condition.trim()) return false;
|
|
1879
1923
|
return typeof edge.label === "string" && edge.label.trim().length > 0;
|
|
1880
1924
|
}
|
|
1881
|
-
var
|
|
1882
|
-
var
|
|
1883
|
-
"../contracts/src/flow-
|
|
1884
|
-
init_flow_ports();
|
|
1885
|
-
FlowIssueSeverity = z.enum(["error", "warning"]);
|
|
1886
|
-
FlowIssue = z.object({
|
|
1887
|
-
code: z.string().max(64),
|
|
1888
|
-
severity: FlowIssueSeverity,
|
|
1889
|
-
message: z.string().max(500),
|
|
1890
|
-
nodeId: z.string().max(128).optional(),
|
|
1891
|
-
edgeId: z.string().max(128).optional()
|
|
1892
|
-
});
|
|
1893
|
-
z.object({
|
|
1894
|
-
valid: z.boolean(),
|
|
1895
|
-
issues: z.array(FlowIssue).max(256)
|
|
1896
|
-
});
|
|
1925
|
+
var CONVERSATIONAL_NODE_TYPES;
|
|
1926
|
+
var init_flow_edges = __esm({
|
|
1927
|
+
"../contracts/src/flow-edges.ts"() {
|
|
1897
1928
|
CONVERSATIONAL_NODE_TYPES = /* @__PURE__ */ new Set([
|
|
1898
1929
|
"llm",
|
|
1899
1930
|
"ask",
|
|
@@ -1902,838 +1933,909 @@ var init_flow_validate = __esm({
|
|
|
1902
1933
|
}
|
|
1903
1934
|
});
|
|
1904
1935
|
|
|
1905
|
-
// ../contracts/src/flow-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
ComplianceDecision = z.enum(["allowed", "blocked"]);
|
|
1915
|
-
ComplianceConfig = z.object({
|
|
1916
|
-
// Local calling window for the CALLED party — TCPA default 8am–9pm.
|
|
1917
|
-
quietHoursStartHour: z.number().int().min(0).max(23).default(8),
|
|
1918
|
-
quietHoursEndHour: z.number().int().min(1).max(24).default(21),
|
|
1919
|
-
enforceDnc: z.boolean().default(true),
|
|
1920
|
-
enforceQuietHours: z.boolean().default(true),
|
|
1921
|
-
// Max dial attempts to one number per rolling 24h. 0 = unlimited.
|
|
1922
|
-
maxAttemptsPerDay: z.number().int().min(0).default(0),
|
|
1923
|
-
// Require a recorded consent flag before dialing.
|
|
1924
|
-
requireConsent: z.boolean().default(false),
|
|
1925
|
-
// Fallback IANA tz when the number's region can't be resolved.
|
|
1926
|
-
fallbackTimeZone: z.string().min(1).default("America/New_York")
|
|
1927
|
-
});
|
|
1928
|
-
z.object({
|
|
1929
|
-
phoneE164: z.string().regex(/^\+[1-9]\d{1,14}$/, "must be E.164"),
|
|
1930
|
-
// ISO instant the check is evaluated at (defaults to now server-side).
|
|
1931
|
-
now: z.string().datetime().optional(),
|
|
1932
|
-
// Override the resolved timezone (else derived from the number's area code).
|
|
1933
|
-
timeZone: z.string().optional(),
|
|
1934
|
-
// Recent attempt count in the last 24h (for the frequency cap), if known.
|
|
1935
|
-
recentAttempts: z.number().int().min(0).optional(),
|
|
1936
|
-
// Whether consent is on file (for requireConsent).
|
|
1937
|
-
hasConsent: z.boolean().optional()
|
|
1938
|
-
});
|
|
1939
|
-
z.object({
|
|
1940
|
-
decision: ComplianceDecision,
|
|
1941
|
-
// The first rule that blocked, if any.
|
|
1942
|
-
rule: ComplianceRule.nullable(),
|
|
1943
|
-
reason: z.string().nullable()
|
|
1944
|
-
});
|
|
1945
|
-
z.object({
|
|
1946
|
-
id: z.string().uuid(),
|
|
1947
|
-
projectId: z.string().min(1),
|
|
1948
|
-
phoneE164: z.string(),
|
|
1949
|
-
reason: z.string().nullable(),
|
|
1950
|
-
source: z.string().nullable(),
|
|
1951
|
-
createdAt: z.string().datetime()
|
|
1952
|
-
});
|
|
1953
|
-
z.object({
|
|
1954
|
-
id: z.string().uuid(),
|
|
1955
|
-
projectId: z.string().min(1),
|
|
1956
|
-
phoneE164: z.string(),
|
|
1957
|
-
decision: ComplianceDecision,
|
|
1958
|
-
rule: ComplianceRule.nullable(),
|
|
1959
|
-
reason: z.string().nullable(),
|
|
1960
|
-
checkedAt: z.string().datetime()
|
|
1961
|
-
});
|
|
1962
|
-
z.object({
|
|
1963
|
-
phoneE164: z.string().regex(/^\+[1-9]\d{1,14}$/, "must be E.164"),
|
|
1964
|
-
reason: z.string().max(500).optional(),
|
|
1965
|
-
source: z.string().max(120).optional()
|
|
1966
|
-
});
|
|
1967
|
-
ComplianceConfig.partial();
|
|
1968
|
-
}
|
|
1969
|
-
});
|
|
1970
|
-
|
|
1971
|
-
// ../contracts/src/sms-keywords.ts
|
|
1972
|
-
var init_sms_keywords = __esm({
|
|
1973
|
-
"../contracts/src/sms-keywords.ts"() {
|
|
1936
|
+
// ../contracts/src/flow-enrichment.ts
|
|
1937
|
+
function planEnrichment(graph) {
|
|
1938
|
+
const ids = graph.nodes.map((n) => n.id);
|
|
1939
|
+
const indeg = new Map(ids.map((id) => [id, 0]));
|
|
1940
|
+
const adj = new Map(ids.map((id) => [id, []]));
|
|
1941
|
+
for (const edge of graph.edges) {
|
|
1942
|
+
if (!adj.has(edge.source) || !indeg.has(edge.target)) continue;
|
|
1943
|
+
adj.get(edge.source).push(edge.target);
|
|
1944
|
+
indeg.set(edge.target, (indeg.get(edge.target) ?? 0) + 1);
|
|
1974
1945
|
}
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
}
|
|
1985
|
-
z.object({
|
|
1986
|
-
recordingEnabled: z.boolean().optional(),
|
|
1987
|
-
consentAnnouncement: z.string().max(1e3).nullable().optional()
|
|
1988
|
-
});
|
|
1946
|
+
const queue = ids.filter((id) => (indeg.get(id) ?? 0) === 0);
|
|
1947
|
+
const order = [];
|
|
1948
|
+
while (queue.length > 0) {
|
|
1949
|
+
const id = queue.shift();
|
|
1950
|
+
order.push(id);
|
|
1951
|
+
for (const next of adj.get(id) ?? []) {
|
|
1952
|
+
const left = (indeg.get(next) ?? 0) - 1;
|
|
1953
|
+
indeg.set(next, left);
|
|
1954
|
+
if (left === 0) queue.push(next);
|
|
1955
|
+
}
|
|
1989
1956
|
}
|
|
1990
|
-
}
|
|
1991
|
-
|
|
1992
|
-
var
|
|
1993
|
-
"../contracts/src/
|
|
1994
|
-
OutboundAmdMode = z.enum(["off", "detect", "detectMessageEnd"]);
|
|
1995
|
-
z.object({
|
|
1996
|
-
// Callee in E.164 (e.g. +14155551234). call-router re-validates strictly.
|
|
1997
|
-
to: z.string().regex(/^\+[1-9]\d{6,14}$/, "must be E.164, e.g. +14155551234"),
|
|
1998
|
-
// Caller-id phone number (must belong to the calling project).
|
|
1999
|
-
phoneNumberId: z.string().min(1),
|
|
2000
|
-
// Optional agent override; falls back to the number's bound agent.
|
|
2001
|
-
agentId: z.string().min(1).optional(),
|
|
2002
|
-
// Template/context variables forwarded to the agent as dispatch metadata.
|
|
2003
|
-
variables: z.record(z.string(), z.string()).optional(),
|
|
2004
|
-
// Per-call AMD override; takes precedence over the agent's stored outbound.amd.
|
|
2005
|
-
amd: OutboundAmdMode.optional()
|
|
2006
|
-
});
|
|
1957
|
+
return { order, acyclic: order.length === ids.length };
|
|
1958
|
+
}
|
|
1959
|
+
var init_flow_enrichment = __esm({
|
|
1960
|
+
"../contracts/src/flow-enrichment.ts"() {
|
|
2007
1961
|
}
|
|
2008
1962
|
});
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
metadata: z.record(z.unknown()).optional(),
|
|
2048
|
-
expiresAt: z.string().datetime().optional()
|
|
2049
|
-
});
|
|
2050
|
-
z.object({
|
|
2051
|
-
name: z.string().trim().min(1).max(128).optional(),
|
|
2052
|
-
// New secret material — re-encrypted at rest; the old envelope is replaced.
|
|
2053
|
-
secret: z.string().min(1).max(8192).optional(),
|
|
2054
|
-
// Merged over the existing metadata (top-level keys win).
|
|
2055
|
-
metadata: z.record(z.unknown()).optional()
|
|
2056
|
-
}).refine((v) => v.name !== void 0 || v.secret !== void 0 || v.metadata !== void 0, {
|
|
2057
|
-
message: "nothing to update"
|
|
2058
|
-
});
|
|
2059
|
-
byokProviders().map((p) => ({
|
|
2060
|
-
id: p.id,
|
|
2061
|
-
label: p.label,
|
|
2062
|
-
keyPlaceholder: p.keyPlaceholder ?? "",
|
|
2063
|
-
supportsBaseUrl: p.supportsBaseUrl
|
|
2064
|
-
}));
|
|
1963
|
+
|
|
1964
|
+
// ../contracts/src/flow-compile.ts
|
|
1965
|
+
function compileConversationFlow(graph, id = "flow") {
|
|
1966
|
+
const fields = [];
|
|
1967
|
+
const prompts = [];
|
|
1968
|
+
const triggers = [];
|
|
1969
|
+
let gateRequired = null;
|
|
1970
|
+
for (const node of graph.nodes) {
|
|
1971
|
+
const data = node.data;
|
|
1972
|
+
switch (node.type) {
|
|
1973
|
+
case "ask": {
|
|
1974
|
+
const name = typeof data.slot === "string" ? data.slot : node.id;
|
|
1975
|
+
const required = data.required !== false;
|
|
1976
|
+
fields.push({
|
|
1977
|
+
name,
|
|
1978
|
+
required,
|
|
1979
|
+
...typeof data.ask === "string" ? { prompt: data.ask } : {}
|
|
1980
|
+
});
|
|
1981
|
+
break;
|
|
1982
|
+
}
|
|
1983
|
+
case "say":
|
|
1984
|
+
case "confirm": {
|
|
1985
|
+
if (typeof data.text === "string") prompts.push({ id: node.id, text: data.text });
|
|
1986
|
+
break;
|
|
1987
|
+
}
|
|
1988
|
+
case "trigger": {
|
|
1989
|
+
const on = typeof data.on === "string" ? data.on : "";
|
|
1990
|
+
const then = typeof data.then === "string" ? data.then : "handoff";
|
|
1991
|
+
if (on) triggers.push({ on, then });
|
|
1992
|
+
break;
|
|
1993
|
+
}
|
|
1994
|
+
case "gate": {
|
|
1995
|
+
if (Array.isArray(data.requiredFields)) {
|
|
1996
|
+
gateRequired = data.requiredFields.filter((x) => typeof x === "string");
|
|
1997
|
+
}
|
|
1998
|
+
break;
|
|
1999
|
+
}
|
|
2000
|
+
}
|
|
2065
2001
|
}
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2002
|
+
const requiredFields = gateRequired ?? fields.filter((f) => f.required).map((f) => f.name);
|
|
2003
|
+
return { id, fields, completionGate: { requiredFields }, triggers, prompts };
|
|
2004
|
+
}
|
|
2005
|
+
function asFieldType(value) {
|
|
2006
|
+
return typeof value === "string" && FIELD_TYPES.includes(value) ? value : "string";
|
|
2007
|
+
}
|
|
2008
|
+
function asHistoryPolicy(value) {
|
|
2009
|
+
return typeof value === "string" && HISTORY_POLICIES.has(value) ? value : "full";
|
|
2010
|
+
}
|
|
2011
|
+
function asAdherence(value) {
|
|
2012
|
+
return typeof value === "string" && ADHERENCE_MODES.has(value) ? value : "off";
|
|
2013
|
+
}
|
|
2014
|
+
function branchesFor(data) {
|
|
2015
|
+
return Array.isArray(data.branches) ? data.branches.filter((b) => b && typeof b.id === "string" && typeof b.label === "string").map((b) => ({
|
|
2016
|
+
id: b.id,
|
|
2017
|
+
label: b.label,
|
|
2018
|
+
...typeof b.when === "string" && b.when.trim() ? { when: b.when } : {},
|
|
2019
|
+
...typeof b.digit === "string" && b.digit.trim() ? { digit: b.digit.trim() } : {},
|
|
2020
|
+
...typeof b.description === "string" && b.description.trim() ? { description: b.description } : {}
|
|
2021
|
+
})) : [];
|
|
2022
|
+
}
|
|
2023
|
+
function nodeConfigFor(node) {
|
|
2024
|
+
const data = node.data;
|
|
2025
|
+
switch (node.type) {
|
|
2026
|
+
case "start":
|
|
2027
|
+
return {};
|
|
2028
|
+
case "say":
|
|
2029
|
+
case "confirm":
|
|
2030
|
+
return { text: typeof data.text === "string" ? data.text : "" };
|
|
2031
|
+
case "ask":
|
|
2032
|
+
return {
|
|
2033
|
+
slot: typeof data.slot === "string" ? data.slot : node.id,
|
|
2034
|
+
required: data.required !== false,
|
|
2035
|
+
type: asFieldType(data.type),
|
|
2036
|
+
historyPolicy: asHistoryPolicy(data.historyPolicy),
|
|
2037
|
+
...typeof data.ask === "string" ? { ask: data.ask } : {},
|
|
2038
|
+
...typeof data.pattern === "string" ? { pattern: data.pattern } : {},
|
|
2039
|
+
...typeof data.min === "number" ? { min: data.min } : {},
|
|
2040
|
+
...typeof data.max === "number" ? { max: data.max } : {},
|
|
2041
|
+
...Array.isArray(data.enum) ? { enum: data.enum.filter((x) => typeof x === "string") } : {}
|
|
2042
|
+
};
|
|
2043
|
+
case "llm":
|
|
2044
|
+
return {
|
|
2045
|
+
instructions: typeof data.instructions === "string" ? data.instructions : typeof data.text === "string" ? data.text : "",
|
|
2046
|
+
// Chat-hub valve: a resident conversational node (entry/return point)
|
|
2047
|
+
// that routes to task subgraphs on its conditional edges and absorbs
|
|
2048
|
+
// off-script turns in place instead of dead-ending. See the interpreter.
|
|
2049
|
+
hub: data.hub === true,
|
|
2050
|
+
historyPolicy: asHistoryPolicy(data.historyPolicy),
|
|
2051
|
+
// Drift-gate (supervisor-lite): verify the reply still localizes to this
|
|
2052
|
+
// node. enforce → reject-and-regenerate, then speak `fallback` verbatim.
|
|
2053
|
+
adherence: asAdherence(data.adherence),
|
|
2054
|
+
...typeof data.fallback === "string" ? { fallback: data.fallback } : {}
|
|
2055
|
+
};
|
|
2056
|
+
case "tool": {
|
|
2057
|
+
const url = typeof data.url === "string" ? data.url : "";
|
|
2058
|
+
const rawName = typeof data.name === "string" ? data.name : "";
|
|
2059
|
+
const name = /^[a-z][a-z0-9_]*$/.test(rawName) ? rawName : `tool_${node.id.replace(/[^a-z0-9]/gi, "").slice(0, 16).toLowerCase() || "fn"}`;
|
|
2060
|
+
const input = {};
|
|
2061
|
+
if (data.input && typeof data.input === "object") {
|
|
2062
|
+
for (const [key, val] of Object.entries(data.input)) {
|
|
2063
|
+
if (val === "string" || val === "number" || val === "boolean") input[key] = val;
|
|
2064
|
+
}
|
|
2065
|
+
}
|
|
2066
|
+
const headers = {};
|
|
2067
|
+
if (data.headers && typeof data.headers === "object") {
|
|
2068
|
+
for (const [key, val] of Object.entries(data.headers)) {
|
|
2069
|
+
if (typeof val === "string") headers[key] = val;
|
|
2070
|
+
}
|
|
2071
|
+
}
|
|
2072
|
+
return {
|
|
2073
|
+
name,
|
|
2074
|
+
description: typeof data.description === "string" && data.description ? data.description : name,
|
|
2075
|
+
input,
|
|
2076
|
+
url,
|
|
2077
|
+
method: HTTP_METHODS.has(data.method) ? data.method : "POST",
|
|
2078
|
+
...Object.keys(headers).length ? { headers } : {},
|
|
2079
|
+
auth: data.auth === "connection" ? "connection" : "none",
|
|
2080
|
+
...typeof data.connectionRef === "string" ? { connectionRef: data.connectionRef } : {},
|
|
2081
|
+
// Registry reference: run the project HTTP tool with this NAME via the
|
|
2082
|
+
// server-side proxy instead of the inline url/method/auth above.
|
|
2083
|
+
...typeof data.toolRef === "string" && data.toolRef ? { toolRef: data.toolRef } : {},
|
|
2084
|
+
// Promote result fields into NAMED slots on success:
|
|
2085
|
+
// { slotName: "data.path" }. Later nodes then read {{ slotName }}
|
|
2086
|
+
// directly instead of {{ tool.<nodeId>.data.path }} via a set node.
|
|
2087
|
+
...(() => {
|
|
2088
|
+
if (!data.outputAs || typeof data.outputAs !== "object") return {};
|
|
2089
|
+
const outputAs = {};
|
|
2090
|
+
for (const [slot, path] of Object.entries(data.outputAs)) {
|
|
2091
|
+
if (typeof path === "string" && path.trim() && /^[a-zA-Z_][\w.]*$/.test(slot)) {
|
|
2092
|
+
outputAs[slot] = path.trim();
|
|
2093
|
+
}
|
|
2094
|
+
}
|
|
2095
|
+
return Object.keys(outputAs).length ? { outputAs } : {};
|
|
2096
|
+
})()
|
|
2097
|
+
};
|
|
2098
|
+
}
|
|
2099
|
+
case "gate":
|
|
2100
|
+
return {
|
|
2101
|
+
requiredFields: Array.isArray(data.requiredFields) ? data.requiredFields.filter((x) => typeof x === "string") : []
|
|
2102
|
+
};
|
|
2103
|
+
case "handoff":
|
|
2104
|
+
return {
|
|
2105
|
+
...typeof data.to === "string" ? { to: data.to } : {},
|
|
2106
|
+
...typeof data.fallback === "string" ? { fallback: data.fallback } : {},
|
|
2107
|
+
...typeof data.on === "string" ? { on: data.on } : {},
|
|
2108
|
+
// Per-node warm (conference brief) / cold (blind REFER) override.
|
|
2109
|
+
...data.mode === "warm" || data.mode === "cold" ? { mode: data.mode } : {},
|
|
2110
|
+
// What the joining human HEARS (warm mode speaks it verbatim once they
|
|
2111
|
+
// answer). {{ }}-interpolated at runtime against the slot scope.
|
|
2112
|
+
...typeof data.briefing === "string" && data.briefing.trim() ? { briefing: data.briefing } : {}
|
|
2113
|
+
};
|
|
2114
|
+
case "end":
|
|
2115
|
+
return {
|
|
2116
|
+
...typeof data.on === "string" ? { on: data.on } : {},
|
|
2117
|
+
...typeof data.voicemail === "boolean" ? { voicemail: data.voicemail } : {},
|
|
2118
|
+
// Optional closing line spoken before hang-up (interpolated at runtime).
|
|
2119
|
+
...typeof data.endMessage === "string" ? { endMessage: data.endMessage } : {}
|
|
2120
|
+
};
|
|
2121
|
+
case "trigger":
|
|
2122
|
+
return {
|
|
2123
|
+
on: typeof data.on === "string" ? data.on : "",
|
|
2124
|
+
then: typeof data.then === "string" ? data.then : "handoff",
|
|
2125
|
+
...typeof data.onlyWhile === "string" ? { onlyWhile: data.onlyWhile } : {}
|
|
2126
|
+
};
|
|
2127
|
+
case "decision":
|
|
2128
|
+
return {
|
|
2129
|
+
...typeof data.condition === "string" ? { condition: data.condition } : {},
|
|
2130
|
+
branches: branchesFor(data)
|
|
2131
|
+
};
|
|
2132
|
+
case "playbook":
|
|
2133
|
+
return {
|
|
2134
|
+
...typeof data.goal === "string" ? { goal: data.goal } : {},
|
|
2135
|
+
...typeof data.instructions === "string" ? { instructions: data.instructions } : {},
|
|
2136
|
+
// Optional reference to a reusable library playbook. playbookId is the
|
|
2137
|
+
// rename-safe key (pinned at deploy); playbookRef (name) remains for
|
|
2138
|
+
// display + legacy live resolution.
|
|
2139
|
+
...typeof data.playbookRef === "string" ? { playbookRef: data.playbookRef } : {},
|
|
2140
|
+
...typeof data.playbookId === "string" && data.playbookId ? { playbookId: data.playbookId } : {},
|
|
2141
|
+
requiredFields: Array.isArray(data.requiredFields) ? data.requiredFields.filter((x) => typeof x === "string") : [],
|
|
2142
|
+
// Agent loop: scoped registry-tool names + explicit opt-in + model knobs.
|
|
2143
|
+
// With tools (or agentLoop:true) the runtime swaps the plain conversational
|
|
2144
|
+
// turn for the LLM-with-tools runner and the structured exit tool.
|
|
2145
|
+
...Array.isArray(data.tools) ? { tools: data.tools.filter((x) => typeof x === "string" && x.length > 0) } : {},
|
|
2146
|
+
...data.agentLoop === true ? { agentLoop: true } : {},
|
|
2147
|
+
...typeof data.model === "string" && data.model ? { model: data.model } : {},
|
|
2148
|
+
...typeof data.temperature === "number" ? { temperature: data.temperature } : {},
|
|
2149
|
+
// Spoken close when the turn budget exhausts with required fields missing
|
|
2150
|
+
// and no `incomplete` port is wired (the gate never silently advances).
|
|
2151
|
+
...typeof data.incompleteMessage === "string" && data.incompleteMessage ? { incompleteMessage: data.incompleteMessage } : {},
|
|
2152
|
+
// Turn policy: agent-generated speech is interruptible by default;
|
|
2153
|
+
// false locks it (verbatim lines are always locked regardless).
|
|
2154
|
+
...data.interruptible === false ? { interruptible: false } : {},
|
|
2155
|
+
branches: branchesFor(data)
|
|
2156
|
+
};
|
|
2157
|
+
case "menu":
|
|
2158
|
+
return {
|
|
2159
|
+
...typeof data.prompt === "string" ? { prompt: data.prompt } : {},
|
|
2160
|
+
branches: branchesFor(data)
|
|
2161
|
+
};
|
|
2162
|
+
case "classify":
|
|
2163
|
+
return {
|
|
2164
|
+
...typeof data.instructions === "string" ? { instructions: data.instructions } : {},
|
|
2165
|
+
branches: branchesFor(data)
|
|
2166
|
+
};
|
|
2167
|
+
case "router":
|
|
2168
|
+
return {
|
|
2169
|
+
hub: true,
|
|
2170
|
+
...typeof data.instructions === "string" ? { instructions: data.instructions } : {},
|
|
2171
|
+
branches: Array.isArray(data.branches) ? data.branches.filter((b) => b && typeof b.id === "string" && typeof b.label === "string").map((b) => ({
|
|
2172
|
+
id: b.id,
|
|
2173
|
+
label: b.label,
|
|
2174
|
+
...typeof b.description === "string" && b.description.trim() ? { description: b.description } : {}
|
|
2175
|
+
})) : []
|
|
2176
|
+
};
|
|
2177
|
+
case "rag":
|
|
2178
|
+
return {
|
|
2179
|
+
...typeof data.query === "string" ? { query: data.query } : {},
|
|
2180
|
+
...typeof data.topK === "number" ? { topK: data.topK } : {}
|
|
2181
|
+
};
|
|
2182
|
+
case "code":
|
|
2183
|
+
return {
|
|
2184
|
+
...typeof data.expression === "string" ? { expression: data.expression } : {},
|
|
2185
|
+
...typeof data.description === "string" ? { description: data.description } : {}
|
|
2186
|
+
};
|
|
2187
|
+
case "set":
|
|
2188
|
+
return {
|
|
2189
|
+
assignments: Array.isArray(data.assignments) ? data.assignments.filter((a) => a && typeof a.name === "string" && a.name.trim() !== "").map((a) => ({
|
|
2190
|
+
name: a.name,
|
|
2191
|
+
value: typeof a.value === "string" ? a.value : ""
|
|
2192
|
+
})) : []
|
|
2193
|
+
};
|
|
2194
|
+
case "subflow":
|
|
2195
|
+
return {
|
|
2196
|
+
...typeof data.flowRef === "string" ? { flowRef: data.flowRef } : {}
|
|
2197
|
+
};
|
|
2198
|
+
default:
|
|
2199
|
+
return typeof data.label === "string" ? { label: data.label } : {};
|
|
2200
|
+
}
|
|
2201
|
+
}
|
|
2202
|
+
function compileFlowToProgram(graph, id = "flow") {
|
|
2203
|
+
const stepNodes = graph.nodes.filter(
|
|
2204
|
+
(n) => !CAPABILITY_NODE_TYPES.has(n.type) && !ANNOTATION_NODE_TYPES.has(n.type)
|
|
2205
|
+
);
|
|
2206
|
+
const byId = new Map(stepNodes.map((n) => [n.id, n]));
|
|
2207
|
+
const indeg = new Map(stepNodes.map((n) => [n.id, 0]));
|
|
2208
|
+
const outBySource = /* @__PURE__ */ new Map();
|
|
2209
|
+
const droppedEdges = [];
|
|
2210
|
+
const labelOnlyEdges = [];
|
|
2211
|
+
for (const edge of graph.edges) {
|
|
2212
|
+
const source = byId.get(edge.source);
|
|
2213
|
+
if (!source || !byId.has(edge.target)) {
|
|
2214
|
+
droppedEdges.push(edge.id);
|
|
2215
|
+
continue;
|
|
2216
|
+
}
|
|
2217
|
+
if (isLabelOnlyEdge(edge, source.type)) labelOnlyEdges.push(edge.id);
|
|
2218
|
+
const list = outBySource.get(edge.source) ?? [];
|
|
2219
|
+
list.push({
|
|
2220
|
+
to: edge.target,
|
|
2221
|
+
...edge.condition ? { condition: edge.condition } : {},
|
|
2222
|
+
...edge.sourceHandle ? { sourceHandle: edge.sourceHandle } : {},
|
|
2223
|
+
...edge.label ? { label: edge.label } : {}
|
|
2110
2224
|
});
|
|
2225
|
+
outBySource.set(edge.source, list);
|
|
2226
|
+
indeg.set(edge.target, (indeg.get(edge.target) ?? 0) + 1);
|
|
2111
2227
|
}
|
|
2112
|
-
}
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2228
|
+
const nodes = {};
|
|
2229
|
+
const unsupportedNodes = [];
|
|
2230
|
+
for (const node of stepNodes) {
|
|
2231
|
+
nodes[node.id] = {
|
|
2232
|
+
id: node.id,
|
|
2233
|
+
type: node.type,
|
|
2234
|
+
config: nodeConfigFor(node),
|
|
2235
|
+
// Defensive: the FlowProgram schema caps out[] at 64 — slice rather than
|
|
2236
|
+
// letting an over-wired node fail the read-side parse (which silently
|
|
2237
|
+
// degrades the whole call to the generic base prompt).
|
|
2238
|
+
out: (outBySource.get(node.id) ?? []).slice(0, 64)
|
|
2239
|
+
};
|
|
2240
|
+
if (UNSUPPORTED_NODE_TYPES.has(node.type)) unsupportedNodes.push(node.id);
|
|
2241
|
+
}
|
|
2242
|
+
const startNodes = stepNodes.filter((n) => n.type === "start").map((n) => n.id);
|
|
2243
|
+
let entry2 = startNodes.length ? startNodes : stepNodes.filter((n) => (indeg.get(n.id) ?? 0) === 0).map((n) => n.id);
|
|
2244
|
+
if (entry2.length === 0 && stepNodes.length > 0) entry2 = [stepNodes[0].id];
|
|
2245
|
+
const { acyclic } = planEnrichment(graph);
|
|
2246
|
+
const { completionGate } = compileConversationFlow(graph, id);
|
|
2247
|
+
return {
|
|
2248
|
+
v: 1,
|
|
2249
|
+
id: id.slice(0, 64),
|
|
2250
|
+
entry: entry2.slice(0, 64),
|
|
2251
|
+
nodes,
|
|
2252
|
+
completionGate: { requiredFields: completionGate.requiredFields.slice(0, 64) },
|
|
2253
|
+
// Diagnostics arrays are schema-capped at 256 — slice so a pathological
|
|
2254
|
+
// graph can't fail the read-side parse.
|
|
2255
|
+
diagnostics: {
|
|
2256
|
+
acyclic,
|
|
2257
|
+
droppedEdges: droppedEdges.slice(0, 256),
|
|
2258
|
+
unsupportedNodes: unsupportedNodes.slice(0, 256),
|
|
2259
|
+
labelOnlyEdges: labelOnlyEdges.slice(0, 256)
|
|
2260
|
+
}
|
|
2261
|
+
};
|
|
2262
|
+
}
|
|
2263
|
+
var FIELD_TYPES, HTTP_METHODS, HISTORY_POLICIES, ADHERENCE_MODES, UNSUPPORTED_NODE_TYPES, CAPABILITY_NODE_TYPES, ANNOTATION_NODE_TYPES;
|
|
2264
|
+
var init_flow_compile = __esm({
|
|
2265
|
+
"../contracts/src/flow-compile.ts"() {
|
|
2266
|
+
init_flow_enrichment();
|
|
2267
|
+
init_flow_edges();
|
|
2268
|
+
FIELD_TYPES = [
|
|
2269
|
+
"string",
|
|
2270
|
+
"string[]",
|
|
2271
|
+
"number",
|
|
2272
|
+
"boolean",
|
|
2273
|
+
"datetime",
|
|
2274
|
+
"email",
|
|
2275
|
+
"phone"
|
|
2128
2276
|
];
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
]);
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
city: z.string().trim().min(1).max(100),
|
|
2164
|
-
region: z.string().trim().min(1).max(100),
|
|
2165
|
-
postalCode: z.string().trim().min(2).max(20),
|
|
2166
|
-
isoCountry: z.string().trim().length(2)
|
|
2167
|
-
}),
|
|
2168
|
-
authorizedRep: z.object({
|
|
2169
|
-
firstName: z.string().trim().min(1).max(100),
|
|
2170
|
-
lastName: z.string().trim().min(1).max(100),
|
|
2171
|
-
email: z.string().trim().email(),
|
|
2172
|
-
phone: z.string().trim().regex(/^\+[1-9]\d{6,14}$/, "must be E.164"),
|
|
2173
|
-
jobTitle: z.string().trim().min(2).max(100)
|
|
2174
|
-
})
|
|
2277
|
+
HTTP_METHODS = /* @__PURE__ */ new Set(["GET", "POST", "PUT", "PATCH", "DELETE"]);
|
|
2278
|
+
HISTORY_POLICIES = /* @__PURE__ */ new Set(["full", "last-turn", "slots-only"]);
|
|
2279
|
+
ADHERENCE_MODES = /* @__PURE__ */ new Set(["off", "observe", "enforce"]);
|
|
2280
|
+
UNSUPPORTED_NODE_TYPES = /* @__PURE__ */ new Set([]);
|
|
2281
|
+
CAPABILITY_NODE_TYPES = /* @__PURE__ */ new Set(["memory", "trigger", "speech"]);
|
|
2282
|
+
ANNOTATION_NODE_TYPES = /* @__PURE__ */ new Set(["note"]);
|
|
2283
|
+
}
|
|
2284
|
+
});
|
|
2285
|
+
|
|
2286
|
+
// ../contracts/src/flow-compile-check.ts
|
|
2287
|
+
var init_flow_compile_check = __esm({
|
|
2288
|
+
"../contracts/src/flow-compile-check.ts"() {
|
|
2289
|
+
init_agents();
|
|
2290
|
+
init_flow_compile();
|
|
2291
|
+
}
|
|
2292
|
+
});
|
|
2293
|
+
var FlowIssueSeverity, FlowIssue;
|
|
2294
|
+
var init_flow_validate = __esm({
|
|
2295
|
+
"../contracts/src/flow-validate.ts"() {
|
|
2296
|
+
init_flow_ports();
|
|
2297
|
+
init_flow_edges();
|
|
2298
|
+
init_flow_compile_check();
|
|
2299
|
+
init_flow_edges();
|
|
2300
|
+
FlowIssueSeverity = z.enum(["error", "warning"]);
|
|
2301
|
+
FlowIssue = z.object({
|
|
2302
|
+
code: z.string().max(64),
|
|
2303
|
+
severity: FlowIssueSeverity,
|
|
2304
|
+
message: z.string().max(500),
|
|
2305
|
+
nodeId: z.string().max(128).optional(),
|
|
2306
|
+
edgeId: z.string().max(128).optional()
|
|
2307
|
+
});
|
|
2308
|
+
z.object({
|
|
2309
|
+
valid: z.boolean(),
|
|
2310
|
+
issues: z.array(FlowIssue).max(256)
|
|
2175
2311
|
});
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
]);
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
optInType: A2pOptInType,
|
|
2202
|
-
/** Publicly accessible screenshots/pages showing the opt-in. Reviewers open
|
|
2203
|
-
* these; anything behind a login fails. */
|
|
2204
|
-
optInEvidenceUrls: z.array(PublicUrl).min(1).max(5),
|
|
2205
|
-
/** Real messages the customer will send. Must reflect the declared use case
|
|
2206
|
-
* and carry opt-out language. */
|
|
2207
|
-
messageSamples: z.array(z.string().trim().min(10).max(1024)).min(2).max(5),
|
|
2208
|
-
privacyPolicyUrl: PublicUrl,
|
|
2209
|
-
termsAndConditionsUrl: PublicUrl,
|
|
2210
|
-
hasEmbeddedLinks: z.boolean().default(false),
|
|
2211
|
-
hasEmbeddedPhone: z.boolean().default(false)
|
|
2312
|
+
}
|
|
2313
|
+
});
|
|
2314
|
+
|
|
2315
|
+
// ../contracts/src/flow-builtins.ts
|
|
2316
|
+
var init_flow_builtins = __esm({
|
|
2317
|
+
"../contracts/src/flow-builtins.ts"() {
|
|
2318
|
+
}
|
|
2319
|
+
});
|
|
2320
|
+
var ComplianceRule, ComplianceDecision, ComplianceConfig;
|
|
2321
|
+
var init_compliance = __esm({
|
|
2322
|
+
"../contracts/src/compliance.ts"() {
|
|
2323
|
+
ComplianceRule = z.enum(["dnc", "consent", "quiet_hours", "frequency_cap"]);
|
|
2324
|
+
ComplianceDecision = z.enum(["allowed", "blocked"]);
|
|
2325
|
+
ComplianceConfig = z.object({
|
|
2326
|
+
// Local calling window for the CALLED party — TCPA default 8am–9pm.
|
|
2327
|
+
quietHoursStartHour: z.number().int().min(0).max(23).default(8),
|
|
2328
|
+
quietHoursEndHour: z.number().int().min(1).max(24).default(21),
|
|
2329
|
+
enforceDnc: z.boolean().default(true),
|
|
2330
|
+
enforceQuietHours: z.boolean().default(true),
|
|
2331
|
+
// Max dial attempts to one number per rolling 24h. 0 = unlimited.
|
|
2332
|
+
maxAttemptsPerDay: z.number().int().min(0).default(0),
|
|
2333
|
+
// Require a recorded consent flag before dialing.
|
|
2334
|
+
requireConsent: z.boolean().default(false),
|
|
2335
|
+
// Fallback IANA tz when the number's region can't be resolved.
|
|
2336
|
+
fallbackTimeZone: z.string().min(1).default("America/New_York")
|
|
2212
2337
|
});
|
|
2213
2338
|
z.object({
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
code: z.ZodIssueCode.custom,
|
|
2224
|
-
path: ["messaging", "messageFlow"],
|
|
2225
|
-
message: 'must include a "Message and data rates may apply" disclosure \u2014 carriers reject without it'
|
|
2226
|
-
});
|
|
2227
|
-
}
|
|
2228
|
-
if (!/\d/.test(flow) || !/(msg|message|text)/.test(flow)) {
|
|
2229
|
-
ctx.addIssue({
|
|
2230
|
-
code: z.ZodIssueCode.custom,
|
|
2231
|
-
path: ["messaging", "messageFlow"],
|
|
2232
|
-
message: 'must state message frequency, e.g. "Up to 4 msgs/month"'
|
|
2233
|
-
});
|
|
2234
|
-
}
|
|
2235
|
-
const hasOptOut = v.messaging.messageSamples.some((s) => /stop/i.test(s));
|
|
2236
|
-
if (!hasOptOut) {
|
|
2237
|
-
ctx.addIssue({
|
|
2238
|
-
code: z.ZodIssueCode.custom,
|
|
2239
|
-
path: ["messaging", "messageSamples"],
|
|
2240
|
-
message: 'at least one sample must include opt-out language (e.g. "Reply STOP to opt out")'
|
|
2241
|
-
});
|
|
2242
|
-
}
|
|
2339
|
+
phoneE164: z.string().regex(/^\+[1-9]\d{1,14}$/, "must be E.164"),
|
|
2340
|
+
// ISO instant the check is evaluated at (defaults to now server-side).
|
|
2341
|
+
now: z.string().datetime().optional(),
|
|
2342
|
+
// Override the resolved timezone (else derived from the number's area code).
|
|
2343
|
+
timeZone: z.string().optional(),
|
|
2344
|
+
// Recent attempt count in the last 24h (for the frequency cap), if known.
|
|
2345
|
+
recentAttempts: z.number().int().min(0).optional(),
|
|
2346
|
+
// Whether consent is on file (for requireConsent).
|
|
2347
|
+
hasConsent: z.boolean().optional()
|
|
2243
2348
|
});
|
|
2244
|
-
A2pState = z.enum([
|
|
2245
|
-
"none",
|
|
2246
|
-
"profile_pending",
|
|
2247
|
-
"profile_approved",
|
|
2248
|
-
"profile_failed",
|
|
2249
|
-
"brand_pending",
|
|
2250
|
-
"brand_approved",
|
|
2251
|
-
"brand_failed",
|
|
2252
|
-
"campaign_pending",
|
|
2253
|
-
"messaging_ready",
|
|
2254
|
-
"campaign_failed"
|
|
2255
|
-
]);
|
|
2256
2349
|
z.object({
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
messagingServiceSid: z.string().nullable(),
|
|
2262
|
-
campaignSid: z.string().nullable(),
|
|
2263
|
-
mock: z.boolean(),
|
|
2264
|
-
/** Carrier/Twilio rejection details, surfaced verbatim so the customer can
|
|
2265
|
-
* fix the specific field rather than guess. */
|
|
2266
|
-
failures: z.array(z.object({ code: z.number().nullable(), field: z.string().nullable(), message: z.string() })).default([]),
|
|
2267
|
-
/** Plain-language next step for the dashboard. */
|
|
2268
|
-
nextAction: z.string().nullable(),
|
|
2269
|
-
updatedAt: z.string().nullable()
|
|
2350
|
+
decision: ComplianceDecision,
|
|
2351
|
+
// The first rule that blocked, if any.
|
|
2352
|
+
rule: ComplianceRule.nullable(),
|
|
2353
|
+
reason: z.string().nullable()
|
|
2270
2354
|
});
|
|
2271
|
-
}
|
|
2272
|
-
});
|
|
2273
|
-
var ConnectorMode, ConnectorStatus, ConnectorNormalize;
|
|
2274
|
-
var init_connector = __esm({
|
|
2275
|
-
"../contracts/src/connector.ts"() {
|
|
2276
|
-
ConnectorMode = z.enum(["tunnel", "direct"]);
|
|
2277
|
-
ConnectorStatus = z.enum(["online", "degraded", "offline"]);
|
|
2278
|
-
ConnectorNormalize = z.enum(["auto", "on", "off"]);
|
|
2279
2355
|
z.object({
|
|
2280
2356
|
id: z.string().uuid(),
|
|
2281
2357
|
projectId: z.string().min(1),
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
// Direct mode: the connections-vault row holding the URL + key. Null for tunnel.
|
|
2287
|
-
connectionId: z.string().uuid().nullable(),
|
|
2288
|
-
// Upstream model id / alias. Null → the brain's own default.
|
|
2289
|
-
defaultModel: z.string().nullable(),
|
|
2290
|
-
normalize: ConnectorNormalize,
|
|
2291
|
-
enabled: z.boolean(),
|
|
2292
|
-
status: ConnectorStatus,
|
|
2293
|
-
lastSeenAt: z.string().datetime().nullable(),
|
|
2294
|
-
metadata: z.record(z.unknown()),
|
|
2295
|
-
createdAt: z.string().datetime(),
|
|
2296
|
-
updatedAt: z.string().datetime()
|
|
2358
|
+
phoneE164: z.string(),
|
|
2359
|
+
reason: z.string().nullable(),
|
|
2360
|
+
source: z.string().nullable(),
|
|
2361
|
+
createdAt: z.string().datetime()
|
|
2297
2362
|
});
|
|
2298
2363
|
z.object({
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2364
|
+
id: z.string().uuid(),
|
|
2365
|
+
projectId: z.string().min(1),
|
|
2366
|
+
phoneE164: z.string(),
|
|
2367
|
+
decision: ComplianceDecision,
|
|
2368
|
+
rule: ComplianceRule.nullable(),
|
|
2369
|
+
reason: z.string().nullable(),
|
|
2370
|
+
checkedAt: z.string().datetime()
|
|
2306
2371
|
});
|
|
2307
2372
|
z.object({
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
normalize: ConnectorNormalize.optional(),
|
|
2312
|
-
enabled: z.boolean().optional()
|
|
2373
|
+
phoneE164: z.string().regex(/^\+[1-9]\d{1,14}$/, "must be E.164"),
|
|
2374
|
+
reason: z.string().max(500).optional(),
|
|
2375
|
+
source: z.string().max(120).optional()
|
|
2313
2376
|
});
|
|
2377
|
+
ComplianceConfig.partial();
|
|
2314
2378
|
}
|
|
2315
2379
|
});
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2380
|
+
|
|
2381
|
+
// ../contracts/src/sms-keywords.ts
|
|
2382
|
+
var init_sms_keywords = __esm({
|
|
2383
|
+
"../contracts/src/sms-keywords.ts"() {
|
|
2384
|
+
}
|
|
2385
|
+
});
|
|
2386
|
+
var init_call_recording = __esm({
|
|
2387
|
+
"../contracts/src/call-recording.ts"() {
|
|
2388
|
+
z.object({
|
|
2389
|
+
// When false, the call-event sink skips egress entirely — no recording is made.
|
|
2390
|
+
recordingEnabled: z.boolean().default(true),
|
|
2391
|
+
// Optional consent preamble the agent speaks first on every call. null/empty =
|
|
2392
|
+
// no announcement. Capped so it stays a short notice, not a monologue.
|
|
2393
|
+
consentAnnouncement: z.string().max(1e3).nullable().default(null)
|
|
2324
2394
|
});
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2395
|
+
z.object({
|
|
2396
|
+
recordingEnabled: z.boolean().optional(),
|
|
2397
|
+
consentAnnouncement: z.string().max(1e3).nullable().optional()
|
|
2398
|
+
});
|
|
2399
|
+
}
|
|
2400
|
+
});
|
|
2401
|
+
var OutboundAmdMode;
|
|
2402
|
+
var init_outbound = __esm({
|
|
2403
|
+
"../contracts/src/outbound.ts"() {
|
|
2404
|
+
OutboundAmdMode = z.enum(["off", "detect", "detectMessageEnd"]);
|
|
2405
|
+
z.object({
|
|
2406
|
+
// Callee in E.164 (e.g. +14155551234). call-router re-validates strictly.
|
|
2407
|
+
to: z.string().regex(/^\+[1-9]\d{6,14}$/, "must be E.164, e.g. +14155551234"),
|
|
2408
|
+
// Caller-id phone number (must belong to the calling project).
|
|
2409
|
+
phoneNumberId: z.string().min(1),
|
|
2410
|
+
// Optional agent override; falls back to the number's bound agent.
|
|
2411
|
+
agentId: z.string().min(1).optional(),
|
|
2412
|
+
// Template/context variables forwarded to the agent as dispatch metadata.
|
|
2413
|
+
variables: z.record(z.string(), z.string()).optional(),
|
|
2414
|
+
// Per-call AMD override; takes precedence over the agent's stored outbound.amd.
|
|
2415
|
+
amd: OutboundAmdMode.optional()
|
|
2416
|
+
});
|
|
2417
|
+
}
|
|
2418
|
+
});
|
|
2419
|
+
var ConnectionAuthType, ConnectionStatus;
|
|
2420
|
+
var init_connection = __esm({
|
|
2421
|
+
"../contracts/src/connection.ts"() {
|
|
2422
|
+
init_providers();
|
|
2423
|
+
ConnectionAuthType = z.enum(["oauth2", "api_key", "bearer", "basic", "none"]);
|
|
2424
|
+
ConnectionStatus = z.enum(["active", "revoked", "error"]);
|
|
2425
|
+
z.object({
|
|
2426
|
+
v: z.number().int(),
|
|
2427
|
+
// Key id (short fingerprint) of the key that encrypted this envelope — lets the
|
|
2428
|
+
// cipher pick the right key from its ring so CREDENTIAL_ENCRYPTION_KEY can rotate
|
|
2429
|
+
// in-band (Tier-1 item 4). Optional/absent on legacy v1 envelopes, which decrypt
|
|
2430
|
+
// with the designated legacy key.
|
|
2431
|
+
kid: z.string().optional(),
|
|
2432
|
+
iv: z.string(),
|
|
2433
|
+
ct: z.string(),
|
|
2434
|
+
tag: z.string()
|
|
2435
|
+
});
|
|
2436
|
+
z.object({
|
|
2437
|
+
id: z.string().uuid(),
|
|
2438
|
+
projectId: z.string().min(1),
|
|
2439
|
+
name: z.string().min(1).max(128),
|
|
2440
|
+
// Open string — customers name providers freely (e.g. 'google_calendar',
|
|
2441
|
+
// 'salesforce', 'n8n', 'http').
|
|
2442
|
+
provider: z.string().min(1).max(120),
|
|
2443
|
+
authType: ConnectionAuthType,
|
|
2444
|
+
status: ConnectionStatus,
|
|
2445
|
+
// Non-secret metadata (scopes, account label, base URL, …).
|
|
2446
|
+
metadata: z.record(z.unknown()),
|
|
2447
|
+
expiresAt: z.string().datetime().nullable(),
|
|
2448
|
+
createdAt: z.string().datetime(),
|
|
2449
|
+
updatedAt: z.string().datetime()
|
|
2331
2450
|
});
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2451
|
+
z.object({
|
|
2452
|
+
name: z.string().trim().min(1).max(128),
|
|
2453
|
+
provider: z.string().trim().min(1).max(120),
|
|
2454
|
+
authType: ConnectionAuthType,
|
|
2455
|
+
// The secret material to encrypt at rest. Never stored or returned in plaintext.
|
|
2456
|
+
secret: z.string().min(1).max(8192),
|
|
2457
|
+
metadata: z.record(z.unknown()).optional(),
|
|
2458
|
+
expiresAt: z.string().datetime().optional()
|
|
2337
2459
|
});
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2460
|
+
z.object({
|
|
2461
|
+
name: z.string().trim().min(1).max(128).optional(),
|
|
2462
|
+
// New secret material — re-encrypted at rest; the old envelope is replaced.
|
|
2463
|
+
secret: z.string().min(1).max(8192).optional(),
|
|
2464
|
+
// Merged over the existing metadata (top-level keys win).
|
|
2465
|
+
metadata: z.record(z.unknown()).optional()
|
|
2466
|
+
}).refine((v) => v.name !== void 0 || v.secret !== void 0 || v.metadata !== void 0, {
|
|
2467
|
+
message: "nothing to update"
|
|
2346
2468
|
});
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2469
|
+
byokProviders().map((p) => ({
|
|
2470
|
+
id: p.id,
|
|
2471
|
+
label: p.label,
|
|
2472
|
+
keyPlaceholder: p.keyPlaceholder ?? "",
|
|
2473
|
+
supportsBaseUrl: p.supportsBaseUrl
|
|
2474
|
+
}));
|
|
2475
|
+
z.object({
|
|
2476
|
+
id: z.string().min(1),
|
|
2477
|
+
label: z.string().min(1),
|
|
2478
|
+
/** The vault `provider` the resulting connection is stored under. */
|
|
2479
|
+
vaultProvider: z.string().min(1),
|
|
2480
|
+
category: z.enum(["model", "tool"])
|
|
2350
2481
|
});
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
streamId: z.string().min(1),
|
|
2354
|
-
content: z.string()
|
|
2482
|
+
z.object({
|
|
2483
|
+
authorizeUrl: z.string().url()
|
|
2355
2484
|
});
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
finishReason: z.string()
|
|
2485
|
+
z.object({
|
|
2486
|
+
state: z.string().min(1).max(512),
|
|
2487
|
+
code: z.string().min(1).max(4096)
|
|
2360
2488
|
});
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2489
|
+
z.object({
|
|
2490
|
+
providerId: z.string().min(1),
|
|
2491
|
+
/** The provider's display name ("ChatGPT"). */
|
|
2492
|
+
providerLabel: z.string().min(1),
|
|
2493
|
+
/** What the connection is for — which group the dashboard files it under. */
|
|
2494
|
+
category: z.enum(["model", "tool"]),
|
|
2495
|
+
/** The account's stable id at the provider (OIDC `sub`); reconnecting the same account updates the same row. */
|
|
2496
|
+
subject: z.string().min(1).nullable(),
|
|
2497
|
+
email: z.string().nullable(),
|
|
2498
|
+
displayName: z.string().nullable(),
|
|
2499
|
+
scopes: z.array(z.string()),
|
|
2500
|
+
/**
|
|
2501
|
+
* The workspace user who signed in. A person's model plan (category 'model') serves only them — never teammates or
|
|
2502
|
+
* API keys. Absent on rows connected before it was recorded.
|
|
2503
|
+
*/
|
|
2504
|
+
connectedBy: z.string().nullable().optional(),
|
|
2505
|
+
/** Set when a refresh was refused (e.g. `invalid_grant`): the owner must connect again. */
|
|
2506
|
+
reauthRequired: z.boolean().optional()
|
|
2372
2507
|
});
|
|
2373
|
-
ConnectorUpFrame = z.discriminatedUnion("kind", [
|
|
2374
|
-
HelloFrame,
|
|
2375
|
-
PongFrame,
|
|
2376
|
-
BrainDeltaFrame,
|
|
2377
|
-
BrainDoneFrame,
|
|
2378
|
-
BrainErrorFrame
|
|
2379
|
-
]);
|
|
2380
|
-
z.discriminatedUnion("kind", [
|
|
2381
|
-
RegisteredFrame,
|
|
2382
|
-
PingFrame,
|
|
2383
|
-
ShutdownFrame,
|
|
2384
|
-
BrainRequestFrame,
|
|
2385
|
-
BrainCancelFrame
|
|
2386
|
-
]);
|
|
2387
|
-
}
|
|
2388
|
-
});
|
|
2389
|
-
|
|
2390
|
-
// ../contracts/src/flow-enrichment.ts
|
|
2391
|
-
function planEnrichment(graph) {
|
|
2392
|
-
const ids = graph.nodes.map((n) => n.id);
|
|
2393
|
-
const indeg = new Map(ids.map((id) => [id, 0]));
|
|
2394
|
-
const adj = new Map(ids.map((id) => [id, []]));
|
|
2395
|
-
for (const edge of graph.edges) {
|
|
2396
|
-
if (!adj.has(edge.source) || !indeg.has(edge.target)) continue;
|
|
2397
|
-
adj.get(edge.source).push(edge.target);
|
|
2398
|
-
indeg.set(edge.target, (indeg.get(edge.target) ?? 0) + 1);
|
|
2399
|
-
}
|
|
2400
|
-
const queue = ids.filter((id) => (indeg.get(id) ?? 0) === 0);
|
|
2401
|
-
const order = [];
|
|
2402
|
-
while (queue.length > 0) {
|
|
2403
|
-
const id = queue.shift();
|
|
2404
|
-
order.push(id);
|
|
2405
|
-
for (const next of adj.get(id) ?? []) {
|
|
2406
|
-
const left = (indeg.get(next) ?? 0) - 1;
|
|
2407
|
-
indeg.set(next, left);
|
|
2408
|
-
if (left === 0) queue.push(next);
|
|
2409
|
-
}
|
|
2410
|
-
}
|
|
2411
|
-
return { order, acyclic: order.length === ids.length };
|
|
2412
|
-
}
|
|
2413
|
-
var init_flow_enrichment = __esm({
|
|
2414
|
-
"../contracts/src/flow-enrichment.ts"() {
|
|
2415
2508
|
}
|
|
2416
2509
|
});
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
}
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
return {
|
|
2487
|
-
slot: typeof data.slot === "string" ? data.slot : node.id,
|
|
2488
|
-
required: data.required !== false,
|
|
2489
|
-
type: asFieldType(data.type),
|
|
2490
|
-
historyPolicy: asHistoryPolicy(data.historyPolicy),
|
|
2491
|
-
...typeof data.ask === "string" ? { ask: data.ask } : {},
|
|
2492
|
-
...typeof data.pattern === "string" ? { pattern: data.pattern } : {},
|
|
2493
|
-
...typeof data.min === "number" ? { min: data.min } : {},
|
|
2494
|
-
...typeof data.max === "number" ? { max: data.max } : {},
|
|
2495
|
-
...Array.isArray(data.enum) ? { enum: data.enum.filter((x) => typeof x === "string") } : {}
|
|
2496
|
-
};
|
|
2497
|
-
case "llm":
|
|
2498
|
-
return {
|
|
2499
|
-
instructions: typeof data.instructions === "string" ? data.instructions : typeof data.text === "string" ? data.text : "",
|
|
2500
|
-
// Chat-hub valve: a resident conversational node (entry/return point)
|
|
2501
|
-
// that routes to task subgraphs on its conditional edges and absorbs
|
|
2502
|
-
// off-script turns in place instead of dead-ending. See the interpreter.
|
|
2503
|
-
hub: data.hub === true,
|
|
2504
|
-
historyPolicy: asHistoryPolicy(data.historyPolicy),
|
|
2505
|
-
// Drift-gate (supervisor-lite): verify the reply still localizes to this
|
|
2506
|
-
// node. enforce → reject-and-regenerate, then speak `fallback` verbatim.
|
|
2507
|
-
adherence: asAdherence(data.adherence),
|
|
2508
|
-
...typeof data.fallback === "string" ? { fallback: data.fallback } : {}
|
|
2509
|
-
};
|
|
2510
|
-
case "tool": {
|
|
2511
|
-
const url = typeof data.url === "string" ? data.url : "";
|
|
2512
|
-
const rawName = typeof data.name === "string" ? data.name : "";
|
|
2513
|
-
const name = /^[a-z][a-z0-9_]*$/.test(rawName) ? rawName : `tool_${node.id.replace(/[^a-z0-9]/gi, "").slice(0, 16).toLowerCase() || "fn"}`;
|
|
2514
|
-
const input = {};
|
|
2515
|
-
if (data.input && typeof data.input === "object") {
|
|
2516
|
-
for (const [key, val] of Object.entries(data.input)) {
|
|
2517
|
-
if (val === "string" || val === "number" || val === "boolean") input[key] = val;
|
|
2518
|
-
}
|
|
2510
|
+
var E164, RoomPrefix, RecordingPrefix, Cidr, TelephonySettings, ProfileSource, EffectiveCallProfile;
|
|
2511
|
+
var init_telephony = __esm({
|
|
2512
|
+
"../contracts/src/telephony.ts"() {
|
|
2513
|
+
E164 = z.string().regex(/^\+[1-9]\d{6,14}$/, "must be E.164 (+15551234567)");
|
|
2514
|
+
RoomPrefix = z.string().regex(/^[a-z0-9][a-z0-9-]{0,30}[a-z0-9-]?$/, "lowercase letters, digits, dashes; max 32");
|
|
2515
|
+
RecordingPrefix = z.string().min(1).max(128).refine((v) => !v.startsWith("/") && !v.includes(".."), "relative key prefix only");
|
|
2516
|
+
Cidr = z.string().regex(/^\d{1,3}(\.\d{1,3}){3}\/\d{1,2}$/, "must be IPv4 CIDR (a.b.c.d/nn)");
|
|
2517
|
+
TelephonySettings = z.object({
|
|
2518
|
+
outboundTrunkId: z.string().min(1).max(128).nullable().default(null),
|
|
2519
|
+
defaultFromE164: E164.nullable().default(null),
|
|
2520
|
+
roomPrefix: RoomPrefix.nullable().default(null),
|
|
2521
|
+
sipAllowedCidrs: z.array(Cidr).max(32).nullable().default(null),
|
|
2522
|
+
recordingPrefix: RecordingPrefix.nullable().default(null),
|
|
2523
|
+
strictByok: z.boolean().default(false)
|
|
2524
|
+
});
|
|
2525
|
+
z.object({
|
|
2526
|
+
outboundTrunkId: z.string().min(1).max(128).nullable().optional(),
|
|
2527
|
+
defaultFromE164: E164.nullable().optional(),
|
|
2528
|
+
roomPrefix: RoomPrefix.nullable().optional(),
|
|
2529
|
+
sipAllowedCidrs: z.array(Cidr).max(32).nullable().optional(),
|
|
2530
|
+
recordingPrefix: RecordingPrefix.nullable().optional(),
|
|
2531
|
+
strictByok: z.boolean().optional()
|
|
2532
|
+
}).refine((v) => Object.keys(v).length > 0, { message: "nothing to update" });
|
|
2533
|
+
ProfileSource = z.enum(["org", "platform", "none"]);
|
|
2534
|
+
EffectiveCallProfile = z.object({
|
|
2535
|
+
projectId: z.string(),
|
|
2536
|
+
outboundTrunkId: z.string().nullable(),
|
|
2537
|
+
defaultFromE164: z.string().nullable(),
|
|
2538
|
+
roomPrefix: z.string(),
|
|
2539
|
+
sipAllowedCidrs: z.array(z.string()),
|
|
2540
|
+
recordingPrefix: z.string(),
|
|
2541
|
+
strictByok: z.boolean(),
|
|
2542
|
+
sources: z.object({
|
|
2543
|
+
outboundTrunkId: ProfileSource,
|
|
2544
|
+
defaultFromE164: ProfileSource,
|
|
2545
|
+
roomPrefix: ProfileSource,
|
|
2546
|
+
sipAllowedCidrs: ProfileSource,
|
|
2547
|
+
recordingPrefix: ProfileSource
|
|
2548
|
+
})
|
|
2549
|
+
});
|
|
2550
|
+
z.object({
|
|
2551
|
+
settings: TelephonySettings,
|
|
2552
|
+
profile: EffectiveCallProfile
|
|
2553
|
+
});
|
|
2554
|
+
}
|
|
2555
|
+
});
|
|
2556
|
+
var SHORTENER_HOSTS, PLACEHOLDER_HOSTS, PublicUrl, A2pBusinessType, A2pBusinessInfo, A2pOptInType, A2pUseCase, A2pMessagingProfile, A2pState;
|
|
2557
|
+
var init_a2p = __esm({
|
|
2558
|
+
"../contracts/src/a2p.ts"() {
|
|
2559
|
+
SHORTENER_HOSTS = [
|
|
2560
|
+
"bit.ly",
|
|
2561
|
+
"tinyurl.com",
|
|
2562
|
+
"t.co",
|
|
2563
|
+
"goo.gl",
|
|
2564
|
+
"ow.ly",
|
|
2565
|
+
"is.gd",
|
|
2566
|
+
"buff.ly",
|
|
2567
|
+
"rebrand.ly",
|
|
2568
|
+
"cutt.ly",
|
|
2569
|
+
"shorturl.at",
|
|
2570
|
+
"tiny.cc"
|
|
2571
|
+
];
|
|
2572
|
+
PLACEHOLDER_HOSTS = ["acme.com", "example.com", "example.org", "test.com", "localhost"];
|
|
2573
|
+
PublicUrl = z.string().url().refine((v) => v.startsWith("https://"), "must be https").refine((v) => {
|
|
2574
|
+
try {
|
|
2575
|
+
const host = new URL(v).hostname.toLowerCase().replace(/^www\./, "");
|
|
2576
|
+
return !SHORTENER_HOSTS.includes(host);
|
|
2577
|
+
} catch {
|
|
2578
|
+
return false;
|
|
2519
2579
|
}
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2580
|
+
}, "public URL shorteners are rejected by carriers \u2014 use your own domain").refine((v) => {
|
|
2581
|
+
try {
|
|
2582
|
+
const host = new URL(v).hostname.toLowerCase().replace(/^www\./, "");
|
|
2583
|
+
return !PLACEHOLDER_HOSTS.some((p) => host === p || host.endsWith(`.${p}`));
|
|
2584
|
+
} catch {
|
|
2585
|
+
return false;
|
|
2525
2586
|
}
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
}
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
return typeof data.label === "string" ? { label: data.label } : {};
|
|
2587
|
+
}, "placeholder domain \u2014 reviewers will follow this link and reject the campaign");
|
|
2588
|
+
A2pBusinessType = z.enum([
|
|
2589
|
+
"Sole Proprietorship",
|
|
2590
|
+
"Partnership",
|
|
2591
|
+
"Corporation",
|
|
2592
|
+
"Co-operative",
|
|
2593
|
+
"Limited Liability Corporation",
|
|
2594
|
+
"Non-profit Corporation"
|
|
2595
|
+
]);
|
|
2596
|
+
A2pBusinessInfo = z.object({
|
|
2597
|
+
legalName: z.string().trim().min(2).max(200),
|
|
2598
|
+
/** EIN (US) or equivalent registration number. */
|
|
2599
|
+
registrationNumber: z.string().trim().min(4).max(50),
|
|
2600
|
+
businessType: A2pBusinessType,
|
|
2601
|
+
/** Publicly reachable production site — not staging, not a 404. */
|
|
2602
|
+
website: PublicUrl,
|
|
2603
|
+
industry: z.string().trim().min(2).max(60),
|
|
2604
|
+
address: z.object({
|
|
2605
|
+
street: z.string().trim().min(2).max(200),
|
|
2606
|
+
city: z.string().trim().min(1).max(100),
|
|
2607
|
+
region: z.string().trim().min(1).max(100),
|
|
2608
|
+
postalCode: z.string().trim().min(2).max(20),
|
|
2609
|
+
isoCountry: z.string().trim().length(2)
|
|
2610
|
+
}),
|
|
2611
|
+
authorizedRep: z.object({
|
|
2612
|
+
firstName: z.string().trim().min(1).max(100),
|
|
2613
|
+
lastName: z.string().trim().min(1).max(100),
|
|
2614
|
+
email: z.string().trim().email(),
|
|
2615
|
+
phone: z.string().trim().regex(/^\+[1-9]\d{6,14}$/, "must be E.164"),
|
|
2616
|
+
jobTitle: z.string().trim().min(2).max(100)
|
|
2617
|
+
})
|
|
2618
|
+
});
|
|
2619
|
+
A2pOptInType = z.enum(["WEB_FORM", "PAPER_FORM", "VERBAL", "VIA_TEXT", "MOBILE_QR_CODE"]);
|
|
2620
|
+
A2pUseCase = z.enum([
|
|
2621
|
+
"MIXED",
|
|
2622
|
+
"CUSTOMER_CARE",
|
|
2623
|
+
"MARKETING",
|
|
2624
|
+
"ACCOUNT_NOTIFICATION",
|
|
2625
|
+
"2FA",
|
|
2626
|
+
"DELIVERY_NOTIFICATION",
|
|
2627
|
+
"HIGHER_EDUCATION",
|
|
2628
|
+
"POLLING_VOTING",
|
|
2629
|
+
"PUBLIC_SERVICE_ANNOUNCEMENT",
|
|
2630
|
+
"LOW_VOLUME"
|
|
2631
|
+
]);
|
|
2632
|
+
A2pMessagingProfile = z.object({
|
|
2633
|
+
useCase: A2pUseCase,
|
|
2634
|
+
/** Specific, not generic. "We send texts" gets rejected; describe the actual
|
|
2635
|
+
* messages and when they are sent. */
|
|
2636
|
+
description: z.string().trim().min(40).max(4096),
|
|
2637
|
+
/**
|
|
2638
|
+
* The single most-rejected field. Must describe HOW people opt in, state the
|
|
2639
|
+
* message frequency, include the "message and data rates may apply"
|
|
2640
|
+
* disclosure, and link to publicly reachable evidence. Twilio's API bounds it
|
|
2641
|
+
* to 40–2049 characters.
|
|
2642
|
+
*/
|
|
2643
|
+
messageFlow: z.string().trim().min(40).max(2049),
|
|
2644
|
+
optInType: A2pOptInType,
|
|
2645
|
+
/** Publicly accessible screenshots/pages showing the opt-in. Reviewers open
|
|
2646
|
+
* these; anything behind a login fails. */
|
|
2647
|
+
optInEvidenceUrls: z.array(PublicUrl).min(1).max(5),
|
|
2648
|
+
/** Real messages the customer will send. Must reflect the declared use case
|
|
2649
|
+
* and carry opt-out language. */
|
|
2650
|
+
messageSamples: z.array(z.string().trim().min(10).max(1024)).min(2).max(5),
|
|
2651
|
+
privacyPolicyUrl: PublicUrl,
|
|
2652
|
+
termsAndConditionsUrl: PublicUrl,
|
|
2653
|
+
hasEmbeddedLinks: z.boolean().default(false),
|
|
2654
|
+
hasEmbeddedPhone: z.boolean().default(false)
|
|
2655
|
+
});
|
|
2656
|
+
z.object({
|
|
2657
|
+
business: A2pBusinessInfo,
|
|
2658
|
+
messaging: A2pMessagingProfile,
|
|
2659
|
+
/** Register against Twilio's mock endpoints — exercises the full pipeline
|
|
2660
|
+
* with no fees and no real carrier submission. Used in CI and staging. */
|
|
2661
|
+
mock: z.boolean().default(false)
|
|
2662
|
+
}).superRefine((v, ctx) => {
|
|
2663
|
+
const flow = v.messaging.messageFlow.toLowerCase();
|
|
2664
|
+
if (!/(msg|message)\s*(&|and)\s*data rates/.test(flow)) {
|
|
2665
|
+
ctx.addIssue({
|
|
2666
|
+
code: z.ZodIssueCode.custom,
|
|
2667
|
+
path: ["messaging", "messageFlow"],
|
|
2668
|
+
message: 'must include a "Message and data rates may apply" disclosure \u2014 carriers reject without it'
|
|
2669
|
+
});
|
|
2670
|
+
}
|
|
2671
|
+
if (!/\d/.test(flow) || !/(msg|message|text)/.test(flow)) {
|
|
2672
|
+
ctx.addIssue({
|
|
2673
|
+
code: z.ZodIssueCode.custom,
|
|
2674
|
+
path: ["messaging", "messageFlow"],
|
|
2675
|
+
message: 'must state message frequency, e.g. "Up to 4 msgs/month"'
|
|
2676
|
+
});
|
|
2677
|
+
}
|
|
2678
|
+
const hasOptOut = v.messaging.messageSamples.some((s) => /stop/i.test(s));
|
|
2679
|
+
if (!hasOptOut) {
|
|
2680
|
+
ctx.addIssue({
|
|
2681
|
+
code: z.ZodIssueCode.custom,
|
|
2682
|
+
path: ["messaging", "messageSamples"],
|
|
2683
|
+
message: 'at least one sample must include opt-out language (e.g. "Reply STOP to opt out")'
|
|
2684
|
+
});
|
|
2685
|
+
}
|
|
2686
|
+
});
|
|
2687
|
+
A2pState = z.enum([
|
|
2688
|
+
"none",
|
|
2689
|
+
"profile_pending",
|
|
2690
|
+
"profile_approved",
|
|
2691
|
+
"profile_failed",
|
|
2692
|
+
"brand_pending",
|
|
2693
|
+
"brand_approved",
|
|
2694
|
+
"brand_failed",
|
|
2695
|
+
"campaign_pending",
|
|
2696
|
+
"messaging_ready",
|
|
2697
|
+
"campaign_failed"
|
|
2698
|
+
]);
|
|
2699
|
+
z.object({
|
|
2700
|
+
state: A2pState,
|
|
2701
|
+
customerProfileSid: z.string().nullable(),
|
|
2702
|
+
trustProductSid: z.string().nullable(),
|
|
2703
|
+
brandSid: z.string().nullable(),
|
|
2704
|
+
messagingServiceSid: z.string().nullable(),
|
|
2705
|
+
campaignSid: z.string().nullable(),
|
|
2706
|
+
mock: z.boolean(),
|
|
2707
|
+
/** Carrier/Twilio rejection details, surfaced verbatim so the customer can
|
|
2708
|
+
* fix the specific field rather than guess. */
|
|
2709
|
+
failures: z.array(z.object({ code: z.number().nullable(), field: z.string().nullable(), message: z.string() })).default([]),
|
|
2710
|
+
/** Plain-language next step for the dashboard. */
|
|
2711
|
+
nextAction: z.string().nullable(),
|
|
2712
|
+
updatedAt: z.string().nullable()
|
|
2713
|
+
});
|
|
2654
2714
|
}
|
|
2655
|
-
}
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2715
|
+
});
|
|
2716
|
+
var ConnectorMode, ConnectorStatus, ConnectorNormalize;
|
|
2717
|
+
var init_connector = __esm({
|
|
2718
|
+
"../contracts/src/connector.ts"() {
|
|
2719
|
+
ConnectorMode = z.enum(["tunnel", "direct"]);
|
|
2720
|
+
ConnectorStatus = z.enum(["online", "degraded", "offline"]);
|
|
2721
|
+
ConnectorNormalize = z.enum(["auto", "on", "off"]);
|
|
2722
|
+
z.object({
|
|
2723
|
+
id: z.string().uuid(),
|
|
2724
|
+
projectId: z.string().min(1),
|
|
2725
|
+
name: z.string().min(1).max(128),
|
|
2726
|
+
mode: ConnectorMode,
|
|
2727
|
+
// Tunnel mode: a short non-secret preview of the daemon token. Null for direct.
|
|
2728
|
+
tokenPreview: z.string().nullable(),
|
|
2729
|
+
// Direct mode: the connections-vault row holding the URL + key. Null for tunnel.
|
|
2730
|
+
connectionId: z.string().uuid().nullable(),
|
|
2731
|
+
// Upstream model id / alias. Null → the brain's own default.
|
|
2732
|
+
defaultModel: z.string().nullable(),
|
|
2733
|
+
normalize: ConnectorNormalize,
|
|
2734
|
+
enabled: z.boolean(),
|
|
2735
|
+
status: ConnectorStatus,
|
|
2736
|
+
lastSeenAt: z.string().datetime().nullable(),
|
|
2737
|
+
metadata: z.record(z.unknown()),
|
|
2738
|
+
createdAt: z.string().datetime(),
|
|
2739
|
+
updatedAt: z.string().datetime()
|
|
2740
|
+
});
|
|
2741
|
+
z.object({
|
|
2742
|
+
name: z.string().trim().min(1).max(128),
|
|
2743
|
+
mode: ConnectorMode,
|
|
2744
|
+
// Required when mode === 'direct': the vault connection holding URL + key.
|
|
2745
|
+
connectionId: z.string().uuid().optional(),
|
|
2746
|
+
defaultModel: z.string().trim().min(1).max(200).optional(),
|
|
2747
|
+
normalize: ConnectorNormalize.optional(),
|
|
2748
|
+
metadata: z.record(z.unknown()).optional()
|
|
2749
|
+
});
|
|
2750
|
+
z.object({
|
|
2751
|
+
name: z.string().trim().min(1).max(128).optional(),
|
|
2752
|
+
connectionId: z.string().uuid().nullable().optional(),
|
|
2753
|
+
defaultModel: z.string().trim().min(1).max(200).nullable().optional(),
|
|
2754
|
+
normalize: ConnectorNormalize.optional(),
|
|
2755
|
+
enabled: z.boolean().optional()
|
|
2678
2756
|
});
|
|
2679
|
-
outBySource.set(edge.source, list);
|
|
2680
|
-
indeg.set(edge.target, (indeg.get(edge.target) ?? 0) + 1);
|
|
2681
2757
|
}
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2758
|
+
});
|
|
2759
|
+
var CONNECTOR_REQUEST_CHANNEL, CONNECTOR_REPLY_CHANNEL, BrainWireMessage, HelloFrame, PongFrame, RegisteredFrame, PingFrame, ShutdownFrame, BrainRequestFrame, BrainCancelFrame, BrainDeltaFrame, BrainDoneFrame, BrainErrorFrame, ConnectorUpFrame;
|
|
2760
|
+
var init_connector_stream = __esm({
|
|
2761
|
+
"../contracts/src/connector-stream.ts"() {
|
|
2762
|
+
CONNECTOR_REQUEST_CHANNEL = (connectorId) => `brain-req:${connectorId}`;
|
|
2763
|
+
CONNECTOR_REPLY_CHANNEL = (streamId) => `brain-reply:${streamId}`;
|
|
2764
|
+
BrainWireMessage = z.object({
|
|
2765
|
+
role: z.enum(["system", "user", "assistant"]),
|
|
2766
|
+
content: z.string()
|
|
2767
|
+
});
|
|
2768
|
+
HelloFrame = z.object({
|
|
2769
|
+
kind: z.literal("hello"),
|
|
2770
|
+
token: z.string().min(1),
|
|
2771
|
+
replicaId: z.string().min(1),
|
|
2772
|
+
version: z.string().min(1),
|
|
2773
|
+
caps: z.object({ streaming: z.boolean(), tools: z.boolean(), litellm: z.boolean() })
|
|
2774
|
+
});
|
|
2775
|
+
PongFrame = z.object({ kind: z.literal("pong"), ts: z.number().int() });
|
|
2776
|
+
RegisteredFrame = z.object({
|
|
2777
|
+
kind: z.literal("registered"),
|
|
2778
|
+
connectorId: z.string().uuid(),
|
|
2779
|
+
projectId: z.string().min(1)
|
|
2780
|
+
});
|
|
2781
|
+
PingFrame = z.object({ kind: z.literal("ping"), ts: z.number().int() });
|
|
2782
|
+
ShutdownFrame = z.object({ kind: z.literal("shutdown"), reason: z.string().optional() });
|
|
2783
|
+
BrainRequestFrame = z.object({
|
|
2784
|
+
kind: z.literal("brain.request"),
|
|
2785
|
+
streamId: z.string().min(1),
|
|
2786
|
+
model: z.string().optional(),
|
|
2787
|
+
temperature: z.number().optional(),
|
|
2788
|
+
messages: z.array(BrainWireMessage)
|
|
2789
|
+
});
|
|
2790
|
+
BrainCancelFrame = z.object({
|
|
2791
|
+
kind: z.literal("brain.cancel"),
|
|
2792
|
+
streamId: z.string().min(1)
|
|
2793
|
+
});
|
|
2794
|
+
BrainDeltaFrame = z.object({
|
|
2795
|
+
kind: z.literal("brain.delta"),
|
|
2796
|
+
streamId: z.string().min(1),
|
|
2797
|
+
content: z.string()
|
|
2798
|
+
});
|
|
2799
|
+
BrainDoneFrame = z.object({
|
|
2800
|
+
kind: z.literal("brain.done"),
|
|
2801
|
+
streamId: z.string().min(1),
|
|
2802
|
+
finishReason: z.string()
|
|
2803
|
+
});
|
|
2804
|
+
BrainErrorFrame = z.object({
|
|
2805
|
+
kind: z.literal("brain.error"),
|
|
2806
|
+
streamId: z.string().min(1),
|
|
2807
|
+
code: z.enum([
|
|
2808
|
+
"upstream_timeout",
|
|
2809
|
+
"upstream_error",
|
|
2810
|
+
"bad_response",
|
|
2811
|
+
"normalize_failed",
|
|
2812
|
+
"unreachable"
|
|
2813
|
+
]),
|
|
2814
|
+
message: z.string()
|
|
2815
|
+
});
|
|
2816
|
+
ConnectorUpFrame = z.discriminatedUnion("kind", [
|
|
2817
|
+
HelloFrame,
|
|
2818
|
+
PongFrame,
|
|
2819
|
+
BrainDeltaFrame,
|
|
2820
|
+
BrainDoneFrame,
|
|
2821
|
+
BrainErrorFrame
|
|
2822
|
+
]);
|
|
2823
|
+
z.discriminatedUnion("kind", [
|
|
2824
|
+
RegisteredFrame,
|
|
2825
|
+
PingFrame,
|
|
2826
|
+
ShutdownFrame,
|
|
2827
|
+
BrainRequestFrame,
|
|
2828
|
+
BrainCancelFrame
|
|
2829
|
+
]);
|
|
2695
2830
|
}
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
entry: entry2.slice(0, 64),
|
|
2705
|
-
nodes,
|
|
2706
|
-
completionGate: { requiredFields: completionGate.requiredFields.slice(0, 64) },
|
|
2707
|
-
// Diagnostics arrays are schema-capped at 256 — slice so a pathological
|
|
2708
|
-
// graph can't fail the read-side parse.
|
|
2709
|
-
diagnostics: {
|
|
2710
|
-
acyclic,
|
|
2711
|
-
droppedEdges: droppedEdges.slice(0, 256),
|
|
2712
|
-
unsupportedNodes: unsupportedNodes.slice(0, 256),
|
|
2713
|
-
labelOnlyEdges: labelOnlyEdges.slice(0, 256)
|
|
2714
|
-
}
|
|
2715
|
-
};
|
|
2716
|
-
}
|
|
2717
|
-
var FIELD_TYPES, HTTP_METHODS, HISTORY_POLICIES, ADHERENCE_MODES, UNSUPPORTED_NODE_TYPES, CAPABILITY_NODE_TYPES, ANNOTATION_NODE_TYPES;
|
|
2718
|
-
var init_flow_compile = __esm({
|
|
2719
|
-
"../contracts/src/flow-compile.ts"() {
|
|
2720
|
-
init_flow_enrichment();
|
|
2721
|
-
init_flow_validate();
|
|
2722
|
-
FIELD_TYPES = [
|
|
2723
|
-
"string",
|
|
2724
|
-
"string[]",
|
|
2725
|
-
"number",
|
|
2726
|
-
"boolean",
|
|
2727
|
-
"datetime",
|
|
2728
|
-
"email",
|
|
2729
|
-
"phone"
|
|
2730
|
-
];
|
|
2731
|
-
HTTP_METHODS = /* @__PURE__ */ new Set(["GET", "POST", "PUT", "PATCH", "DELETE"]);
|
|
2732
|
-
HISTORY_POLICIES = /* @__PURE__ */ new Set(["full", "last-turn", "slots-only"]);
|
|
2733
|
-
ADHERENCE_MODES = /* @__PURE__ */ new Set(["off", "observe", "enforce"]);
|
|
2734
|
-
UNSUPPORTED_NODE_TYPES = /* @__PURE__ */ new Set([]);
|
|
2735
|
-
CAPABILITY_NODE_TYPES = /* @__PURE__ */ new Set(["memory", "trigger", "speech"]);
|
|
2736
|
-
ANNOTATION_NODE_TYPES = /* @__PURE__ */ new Set(["note"]);
|
|
2831
|
+
});
|
|
2832
|
+
var FLOW_BOOT_FAILURES;
|
|
2833
|
+
var init_call_end_reason = __esm({
|
|
2834
|
+
"../contracts/src/call-end-reason.ts"() {
|
|
2835
|
+
FLOW_BOOT_FAILURES = ["schema_fetch_failed", "no_process_schema", "missing_agent_id", "no_api_key"];
|
|
2836
|
+
z.enum(
|
|
2837
|
+
FLOW_BOOT_FAILURES.map((cause) => `flow_boot:${cause}`)
|
|
2838
|
+
);
|
|
2737
2839
|
}
|
|
2738
2840
|
});
|
|
2739
2841
|
var EnvironmentSpec;
|
|
@@ -3955,6 +4057,7 @@ var init_src = __esm({
|
|
|
3955
4057
|
init_tests();
|
|
3956
4058
|
init_call_review();
|
|
3957
4059
|
init_call_result();
|
|
4060
|
+
init_webhook_events();
|
|
3958
4061
|
init_numbers();
|
|
3959
4062
|
init_project_limits();
|
|
3960
4063
|
init_flow();
|
|
@@ -3972,6 +4075,8 @@ var init_src = __esm({
|
|
|
3972
4075
|
init_connector();
|
|
3973
4076
|
init_connector_stream();
|
|
3974
4077
|
init_flow_compile();
|
|
4078
|
+
init_flow_compile_check();
|
|
4079
|
+
init_call_end_reason();
|
|
3975
4080
|
init_flow_enrichment();
|
|
3976
4081
|
init_environments();
|
|
3977
4082
|
init_variables();
|
|
@@ -9288,6 +9393,12 @@ function createAgentEventEmitter() {
|
|
|
9288
9393
|
var TextTurnReplyAccepted = z.object({ accepted: z.literal(true) });
|
|
9289
9394
|
var DeleteAgentResponseSchema = z.object({ ok: z.literal(true) });
|
|
9290
9395
|
var HEARTBEAT_FAILURE_DEGRADE_THRESHOLD = 3;
|
|
9396
|
+
function isAgentNameReservedError(err) {
|
|
9397
|
+
return err instanceof VoiceLayerHttpError && err.status === 422 && err.body?.error === "agent_name_reserved";
|
|
9398
|
+
}
|
|
9399
|
+
function agentNameReservedMessage(name) {
|
|
9400
|
+
return `[voicelayer] registration REFUSED: the agent name "${name}" is reserved for VoiceLayer's own workers (names starting "voicelayer-", the platform worker names, or ending "::text"). The agent will not appear online. Rename it in defineAgent({ name }) and restart.`;
|
|
9401
|
+
}
|
|
9291
9402
|
var AgentsClient = class {
|
|
9292
9403
|
constructor(transport, options = {}) {
|
|
9293
9404
|
this.transport = transport;
|
|
@@ -9409,10 +9520,16 @@ var AgentsClient = class {
|
|
|
9409
9520
|
...input.mcpExposed !== void 0 ? { mcpExposed: input.mcpExposed } : {},
|
|
9410
9521
|
metadata
|
|
9411
9522
|
});
|
|
9412
|
-
|
|
9413
|
-
|
|
9414
|
-
|
|
9415
|
-
|
|
9523
|
+
let response;
|
|
9524
|
+
try {
|
|
9525
|
+
response = await this.transport.request(
|
|
9526
|
+
{ method: "POST", path: "/v1/agents/register", body },
|
|
9527
|
+
RegisterAgentResponse
|
|
9528
|
+
);
|
|
9529
|
+
} catch (err) {
|
|
9530
|
+
if (isAgentNameReservedError(err)) console.error(agentNameReservedMessage(body.name));
|
|
9531
|
+
throw err;
|
|
9532
|
+
}
|
|
9416
9533
|
events.emit({ type: "registered", agent: response.agent });
|
|
9417
9534
|
const cadence = opts.heartbeatIntervalMs ?? response.heartbeatIntervalMs ?? AGENT_HEARTBEAT_INTERVAL_MS;
|
|
9418
9535
|
const state = {
|
|
@@ -9607,11 +9724,16 @@ var HeartbeatTicker = class {
|
|
|
9607
9724
|
this.state.consecutiveFailures = 0;
|
|
9608
9725
|
} catch (reErr) {
|
|
9609
9726
|
this.state.consecutiveFailures += 1;
|
|
9727
|
+
const reserved = isAgentNameReservedError(reErr);
|
|
9728
|
+
if (reserved) {
|
|
9729
|
+
this.stopped = true;
|
|
9730
|
+
console.error(agentNameReservedMessage(this.state.registerInput.body.name));
|
|
9731
|
+
}
|
|
9610
9732
|
this.opts.events.emit({
|
|
9611
9733
|
type: "heartbeat_error",
|
|
9612
9734
|
error: reErr instanceof Error ? reErr : new Error(String(reErr)),
|
|
9613
9735
|
consecutiveFailures: this.state.consecutiveFailures,
|
|
9614
|
-
willRetry:
|
|
9736
|
+
willRetry: !reserved
|
|
9615
9737
|
});
|
|
9616
9738
|
}
|
|
9617
9739
|
return;
|
|
@@ -9823,7 +9945,8 @@ var CallsClient = class {
|
|
|
9823
9945
|
body: {
|
|
9824
9946
|
...input.attributes !== void 0 ? { attributes: input.attributes } : {},
|
|
9825
9947
|
...input.participants !== void 0 ? { participants: input.participants } : {},
|
|
9826
|
-
...input.legs !== void 0 ? { legs: input.legs } : {}
|
|
9948
|
+
...input.legs !== void 0 ? { legs: input.legs } : {},
|
|
9949
|
+
...input.endReason !== void 0 ? { endReason: input.endReason } : {}
|
|
9827
9950
|
}
|
|
9828
9951
|
},
|
|
9829
9952
|
CallStateSyncResponse
|
|
@@ -14433,6 +14556,7 @@ function guardIsActive(opts) {
|
|
|
14433
14556
|
}
|
|
14434
14557
|
|
|
14435
14558
|
// src/runtime/call-sync.ts
|
|
14559
|
+
var END_REASON_WAIT_MS = 2e3;
|
|
14436
14560
|
function makeNoopHandle(callId) {
|
|
14437
14561
|
return {
|
|
14438
14562
|
callId,
|
|
@@ -14441,6 +14565,8 @@ function makeNoopHandle(callId) {
|
|
|
14441
14565
|
syncState() {
|
|
14442
14566
|
},
|
|
14443
14567
|
publishDtmf() {
|
|
14568
|
+
},
|
|
14569
|
+
async recordEndReason() {
|
|
14444
14570
|
}
|
|
14445
14571
|
};
|
|
14446
14572
|
}
|
|
@@ -14481,6 +14607,24 @@ async function createCallSyncHandle(init) {
|
|
|
14481
14607
|
});
|
|
14482
14608
|
});
|
|
14483
14609
|
},
|
|
14610
|
+
async recordEndReason(reason) {
|
|
14611
|
+
let timer;
|
|
14612
|
+
try {
|
|
14613
|
+
await Promise.race([
|
|
14614
|
+
init.client.calls.syncState(callId, { endReason: reason }),
|
|
14615
|
+
new Promise((resolve) => {
|
|
14616
|
+
timer = setTimeout(resolve, END_REASON_WAIT_MS);
|
|
14617
|
+
})
|
|
14618
|
+
]);
|
|
14619
|
+
} catch (err) {
|
|
14620
|
+
console.warn("[sdk.call-sync] end reason not recorded", {
|
|
14621
|
+
reason,
|
|
14622
|
+
err: err instanceof Error ? err.message : err
|
|
14623
|
+
});
|
|
14624
|
+
} finally {
|
|
14625
|
+
clearTimeout(timer);
|
|
14626
|
+
}
|
|
14627
|
+
},
|
|
14484
14628
|
publishDtmf(input) {
|
|
14485
14629
|
void init.client.calls.appendDtmfEvent(callId, input).catch((err) => {
|
|
14486
14630
|
console.warn("[sdk.call-sync] dtmf publish failed", {
|
|
@@ -15942,6 +16086,7 @@ var Agent = class {
|
|
|
15942
16086
|
break;
|
|
15943
16087
|
} catch (err) {
|
|
15944
16088
|
fetchError = err;
|
|
16089
|
+
if (err instanceof VoiceLayerValidationError) break;
|
|
15945
16090
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
15946
16091
|
}
|
|
15947
16092
|
}
|
|
@@ -16756,6 +16901,7 @@ ${callIntent}`
|
|
|
16756
16901
|
}
|
|
16757
16902
|
}
|
|
16758
16903
|
} else if (flowBootFailure) {
|
|
16904
|
+
const reported = callSync.recordEndReason(`flow_boot:${flowBootFailure}`);
|
|
16759
16905
|
const apology = this.config.flowRuntime?.bootFailureMessage ?? "Sorry \u2014 we're having a technical issue starting this call. Please try again in a few minutes. Goodbye.";
|
|
16760
16906
|
try {
|
|
16761
16907
|
await adapter.generateReply({
|
|
@@ -16766,6 +16912,7 @@ ${apology}`
|
|
|
16766
16912
|
} catch (err) {
|
|
16767
16913
|
console.warn("[agent] flow boot apology failed to play", err);
|
|
16768
16914
|
}
|
|
16915
|
+
await reported;
|
|
16769
16916
|
await endCallController.hangUp({ trigger: "flow", reason: `flow_boot:${flowBootFailure}` });
|
|
16770
16917
|
} else if (this.config.onCall) {
|
|
16771
16918
|
await this.config.onCall(ctx);
|