@vellumai/vellum-gateway 0.4.3 → 0.4.5

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/ARCHITECTURE.md CHANGED
@@ -26,6 +26,8 @@ Internet
26
26
  Gateway (http://127.0.0.1:7830)
27
27
  |
28
28
  +-- Dedicated /v1/health --> Runtime /v1/health
29
+ +-- /v1/integrations/twilio/* --> Runtime (Twilio control-plane proxy)
30
+ +-- /v1/channels/readiness* --> Runtime (channel readiness proxy)
29
31
  +-- Runtime proxy /v1/* --> Runtime (http://127.0.0.1:7821)
30
32
  +-- /webhooks/* --> BLOCKED (404, never forwarded to runtime)
31
33
  ```
@@ -154,6 +156,65 @@ Telegram integration setup/config endpoints and ingress members/invites endpoint
154
156
  | `gateway/src/http/routes/ingress-control-plane-proxy.ts` | Ingress control-plane proxy handlers and upstream forwarding |
155
157
  | `gateway/src/index.ts` | Route registration and bearer-auth enforcement for `/v1/integrations/telegram/*` and `/v1/ingress/*` |
156
158
 
159
+ ### Twilio Control-Plane Proxy
160
+
161
+ Twilio integration setup/config endpoints are exposed directly by the gateway and forwarded to runtime handlers even when the broad runtime proxy is disabled. This keeps skills and clients on gateway URLs exclusively.
162
+
163
+ **Forwarded endpoints:**
164
+
165
+ | Method | Path |
166
+ |--------|------|
167
+ | GET | `/v1/integrations/twilio/config` |
168
+ | POST | `/v1/integrations/twilio/credentials` |
169
+ | DELETE | `/v1/integrations/twilio/credentials` |
170
+ | GET | `/v1/integrations/twilio/numbers` |
171
+ | POST | `/v1/integrations/twilio/numbers/provision` |
172
+ | POST | `/v1/integrations/twilio/numbers/assign` |
173
+ | POST | `/v1/integrations/twilio/numbers/release` |
174
+ | GET | `/v1/integrations/twilio/sms/compliance` |
175
+ | POST | `/v1/integrations/twilio/sms/compliance/tollfree` |
176
+ | PATCH | `/v1/integrations/twilio/sms/compliance/tollfree/:sid` |
177
+ | DELETE | `/v1/integrations/twilio/sms/compliance/tollfree/:sid` |
178
+ | POST | `/v1/integrations/twilio/sms/test` |
179
+ | POST | `/v1/integrations/twilio/sms/doctor` |
180
+
181
+ **Authentication boundary:**
182
+
183
+ - Gateway validates caller bearer auth against the runtime token.
184
+ - Gateway forwards requests to runtime with the runtime bearer token and `X-Gateway-Origin` proof header.
185
+ - Upstream 4xx/5xx responses are passed through, while connection errors return `502` and timeouts return `504`.
186
+
187
+ **Key source files:**
188
+
189
+ | File | Purpose |
190
+ |------|---------|
191
+ | `gateway/src/http/routes/twilio-control-plane-proxy.ts` | Twilio control-plane proxy handlers and upstream forwarding |
192
+ | `gateway/src/index.ts` | Route registration and bearer-auth enforcement for `/v1/integrations/twilio/*` |
193
+
194
+ ### Channel Readiness Proxy
195
+
196
+ Channel readiness endpoints are exposed directly by the gateway and forwarded to runtime handlers even when the broad runtime proxy is disabled.
197
+
198
+ **Forwarded endpoints:**
199
+
200
+ | Method | Path |
201
+ |--------|------|
202
+ | GET | `/v1/channels/readiness` |
203
+ | POST | `/v1/channels/readiness/refresh` |
204
+
205
+ **Authentication boundary:**
206
+
207
+ - Gateway validates caller bearer auth against the runtime token.
208
+ - Gateway forwards requests to runtime with the runtime bearer token and `X-Gateway-Origin` proof header.
209
+ - Upstream 4xx/5xx responses are passed through, while connection errors return `502` and timeouts return `504`.
210
+
211
+ **Key source files:**
212
+
213
+ | File | Purpose |
214
+ |------|---------|
215
+ | `gateway/src/http/routes/channel-readiness-proxy.ts` | Channel readiness proxy handlers and upstream forwarding |
216
+ | `gateway/src/index.ts` | Route registration and bearer-auth enforcement for `/v1/channels/readiness*` |
217
+
157
218
  ### Channel Binding Lifecycle (Lane Separation)
158
219
 
159
220
  Each channel (desktop, Telegram, etc.) operates in its own **lane**: conversations created by an external channel are never displayed in the desktop thread list, and desktop conversations are never exposed to external channels. The `channelBinding` metadata on a conversation is used solely for routing inbound/outbound messages within that lane and for filtering sessions during desktop session restoration.
@@ -296,7 +357,7 @@ The guardian system adds a cryptographic trust layer for channel-based interacti
296
357
 
297
358
  #### Canonical Assistant ID Scoping
298
359
 
299
- All channel ingress paths canonicalize the `assistantId` via `normalizeAssistantId()` (from `util/platform.ts`) before any DB operations. The system uses `"self"` as the canonical single-tenant identifier, but callers may pass the real assistant name (e.g., `"vellum-true-eel"`) or `"self"` depending on context. `normalizeAssistantId()` maps any known lockfile assistant ID to `"self"`, ensuring consistent DB key usage regardless of how the caller identifies the assistant. This canonicalization runs at every ingress boundary: the guardian IPC handler (`config-channels.ts`), the guardian context resolver, the relay server, and the inbound message handler.
360
+ The daemon uses the fixed constant `DAEMON_INTERNAL_ASSISTANT_ID` (`"self"`) from `runtime/assistant-scope.ts` for all assistant-scoped storage keys. Public/external assistant IDs are a gateway/platform concern and never leak into daemon scoping logic. The `normalizeAssistantId()` function in `util/platform.ts` exists solely for the gateway layer to canonicalize inbound requests before proxying -- daemon and runtime code must not call it or accept an `assistantId` parameter for scoping.
300
361
 
301
362
  #### Guardian Verify Code Parsing
302
363
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vellumai/vellum-gateway",
3
- "version": "0.4.3",
3
+ "version": "0.4.5",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "dev": "bun run --watch src/index.ts",
@@ -41,6 +41,14 @@
41
41
  "description": "Enable X (Twitter) skill section in the system prompt",
42
42
  "defaultEnabled": true
43
43
  },
44
+ {
45
+ "id": "messaging",
46
+ "scope": "assistant",
47
+ "key": "feature_flags.messaging.enabled",
48
+ "label": "Messaging",
49
+ "description": "Enable messaging skill section in the system prompt",
50
+ "defaultEnabled": true
51
+ },
44
52
  {
45
53
  "id": "collect-usage-data",
46
54
  "scope": "assistant",
@@ -49,14 +57,6 @@
49
57
  "description": "Send crash reports and error diagnostics to help improve the app",
50
58
  "defaultEnabled": true
51
59
  },
52
- {
53
- "id": "voice-invite-redemption",
54
- "scope": "assistant",
55
- "key": "feature_flags.voice-invite-redemption.enabled",
56
- "label": "Voice Invite Redemption",
57
- "description": "Enable voice invite code redemption for inbound callers with active voice invites",
58
- "defaultEnabled": false
59
- },
60
60
  {
61
61
  "id": "user-hosted-enabled",
62
62
  "scope": "macos",
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Gateway proxy endpoints for the brain graph knowledge-graph visualizer.
3
+ *
4
+ * Exposes GET /v1/brain-graph and GET /v1/brain-graph-ui through the gateway
5
+ * even when the broad runtime proxy is disabled.
6
+ */
7
+
8
+ import type { GatewayConfig } from "../../config.js";
9
+ import { fetchImpl } from "../../fetch.js";
10
+ import { getLogger } from "../../logger.js";
11
+ import { GATEWAY_ORIGIN_HEADER } from "../../runtime/client.js";
12
+ import { stripHopByHop } from "../../util/strip-hop-by-hop.js";
13
+
14
+ const log = getLogger("brain-graph-proxy");
15
+
16
+ export function createBrainGraphProxyHandler(config: GatewayConfig) {
17
+ async function proxyTo(req: Request, upstream: string): Promise<Response> {
18
+ const start = performance.now();
19
+
20
+ const reqHeaders = stripHopByHop(new Headers(req.headers));
21
+ reqHeaders.delete("host");
22
+ reqHeaders.delete("authorization");
23
+
24
+ if (config.runtimeBearerToken) {
25
+ reqHeaders.set("authorization", `Bearer ${config.runtimeBearerToken}`);
26
+ }
27
+ if (config.runtimeGatewayOriginSecret) {
28
+ reqHeaders.set(GATEWAY_ORIGIN_HEADER, config.runtimeGatewayOriginSecret);
29
+ }
30
+
31
+ const controller = new AbortController();
32
+ const timeoutId = setTimeout(() => {
33
+ controller.abort(new DOMException("The operation was aborted due to timeout", "TimeoutError"));
34
+ }, config.runtimeTimeoutMs);
35
+
36
+ let response: Response;
37
+ try {
38
+ response = await fetchImpl(upstream, {
39
+ method: "GET",
40
+ headers: reqHeaders,
41
+ signal: controller.signal,
42
+ });
43
+ clearTimeout(timeoutId);
44
+ } catch (err) {
45
+ clearTimeout(timeoutId);
46
+ const duration = Math.round(performance.now() - start);
47
+ if (err instanceof DOMException && err.name === "TimeoutError") {
48
+ log.error({ duration, upstream }, "Brain graph proxy upstream timed out");
49
+ return Response.json({ error: "Gateway Timeout" }, { status: 504 });
50
+ }
51
+ log.error({ err, duration, upstream }, "Brain graph proxy upstream connection failed");
52
+ return Response.json({ error: "Bad Gateway" }, { status: 502 });
53
+ }
54
+
55
+ const resHeaders = stripHopByHop(new Headers(response.headers));
56
+ const duration = Math.round(performance.now() - start);
57
+
58
+ if (response.status >= 400) {
59
+ const body = await response.text();
60
+ log.warn(
61
+ { status: response.status, duration, upstream },
62
+ "Brain graph proxy upstream error",
63
+ );
64
+ return new Response(body, { status: response.status, headers: resHeaders });
65
+ }
66
+
67
+ log.info({ status: response.status, duration, upstream }, "Brain graph proxy completed");
68
+ return new Response(response.body, { status: response.status, headers: resHeaders });
69
+ }
70
+
71
+ async function handleBrainGraph(req: Request): Promise<Response> {
72
+ return proxyTo(req, `${config.assistantRuntimeBaseUrl}/v1/brain-graph`);
73
+ }
74
+
75
+ async function handleBrainGraphUI(req: Request): Promise<Response> {
76
+ return proxyTo(req, `${config.assistantRuntimeBaseUrl}/v1/brain-graph-ui`);
77
+ }
78
+
79
+ async function handleHomeBaseUI(req: Request): Promise<Response> {
80
+ return proxyTo(req, `${config.assistantRuntimeBaseUrl}/v1/home-base-ui`);
81
+ }
82
+
83
+ return { handleBrainGraph, handleBrainGraphUI, handleHomeBaseUI };
84
+ }
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Gateway proxy endpoints for channel readiness control-plane routes.
3
+ *
4
+ * These routes remain available even when the broad runtime proxy is
5
+ * disabled, so skills and clients can use gateway URLs exclusively.
6
+ */
7
+
8
+ import type { GatewayConfig } from "../../config.js";
9
+ import { fetchImpl } from "../../fetch.js";
10
+ import { getLogger } from "../../logger.js";
11
+ import { GATEWAY_ORIGIN_HEADER } from "../../runtime/client.js";
12
+ import { stripHopByHop } from "../../util/strip-hop-by-hop.js";
13
+
14
+ const log = getLogger("channel-readiness-proxy");
15
+
16
+ export function createChannelReadinessProxyHandler(config: GatewayConfig) {
17
+ async function proxyToRuntime(
18
+ req: Request,
19
+ upstreamPath: string,
20
+ upstreamSearch: string,
21
+ ): Promise<Response> {
22
+ const start = performance.now();
23
+ const upstream = `${config.assistantRuntimeBaseUrl}${upstreamPath}${upstreamSearch}`;
24
+
25
+ const reqHeaders = stripHopByHop(new Headers(req.headers));
26
+ reqHeaders.delete("host");
27
+ reqHeaders.delete("authorization");
28
+
29
+ if (config.runtimeBearerToken) {
30
+ reqHeaders.set("authorization", `Bearer ${config.runtimeBearerToken}`);
31
+ }
32
+ if (config.runtimeGatewayOriginSecret) {
33
+ reqHeaders.set(GATEWAY_ORIGIN_HEADER, config.runtimeGatewayOriginSecret);
34
+ }
35
+
36
+ const hasBody = req.method !== "GET" && req.method !== "HEAD";
37
+ const bodyBuffer = hasBody ? await req.arrayBuffer() : null;
38
+ if (bodyBuffer !== null) {
39
+ reqHeaders.set("content-length", String(bodyBuffer.byteLength));
40
+ }
41
+
42
+ const controller = new AbortController();
43
+ const timeoutId = setTimeout(() => {
44
+ controller.abort(new DOMException("The operation was aborted due to timeout", "TimeoutError"));
45
+ }, config.runtimeTimeoutMs);
46
+
47
+ let response: Response;
48
+ try {
49
+ response = await fetchImpl(upstream, {
50
+ method: req.method,
51
+ headers: reqHeaders,
52
+ body: bodyBuffer,
53
+ signal: controller.signal,
54
+ });
55
+ clearTimeout(timeoutId);
56
+ } catch (err) {
57
+ clearTimeout(timeoutId);
58
+ const duration = Math.round(performance.now() - start);
59
+ if (err instanceof DOMException && err.name === "TimeoutError") {
60
+ log.error({ path: upstreamPath, duration }, "Channel readiness proxy upstream timed out");
61
+ return Response.json({ error: "Gateway Timeout" }, { status: 504 });
62
+ }
63
+ log.error({ err, path: upstreamPath, duration }, "Channel readiness proxy upstream connection failed");
64
+ return Response.json({ error: "Bad Gateway" }, { status: 502 });
65
+ }
66
+
67
+ const resHeaders = stripHopByHop(new Headers(response.headers));
68
+ const duration = Math.round(performance.now() - start);
69
+
70
+ if (response.status >= 400) {
71
+ const body = await response.text();
72
+ log.warn(
73
+ { path: upstreamPath, status: response.status, duration },
74
+ "Channel readiness proxy upstream error",
75
+ );
76
+ return new Response(body, { status: response.status, headers: resHeaders });
77
+ }
78
+
79
+ log.info(
80
+ { path: upstreamPath, status: response.status, duration },
81
+ "Channel readiness proxy completed",
82
+ );
83
+ return new Response(response.body, { status: response.status, headers: resHeaders });
84
+ }
85
+
86
+ return {
87
+ async handleGetChannelReadiness(req: Request): Promise<Response> {
88
+ const url = new URL(req.url);
89
+ return proxyToRuntime(req, "/v1/channels/readiness", url.search);
90
+ },
91
+
92
+ async handleRefreshChannelReadiness(req: Request): Promise<Response> {
93
+ return proxyToRuntime(req, "/v1/channels/readiness/refresh", "");
94
+ },
95
+ };
96
+ }
@@ -43,7 +43,7 @@ export function createTelegramDeliverHandler(config: GatewayConfig) {
43
43
  return Response.json({ error: "Invalid JSON" }, { status: 400 });
44
44
  }
45
45
 
46
- const { chatId, text, assistantId, attachments, approval, chatAction } = body;
46
+ const { chatId, text, assistantId: _assistantId, attachments, approval, chatAction } = body;
47
47
 
48
48
  if (!chatId || typeof chatId !== "string") {
49
49
  return Response.json({ error: "chatId is required" }, { status: 400 });
@@ -0,0 +1,139 @@
1
+ /**
2
+ * Gateway proxy endpoints for Twilio integration control-plane routes.
3
+ *
4
+ * These routes remain available even when the broad runtime proxy is
5
+ * disabled, so skills and clients can use gateway URLs exclusively.
6
+ */
7
+
8
+ import type { GatewayConfig } from "../../config.js";
9
+ import { fetchImpl } from "../../fetch.js";
10
+ import { getLogger } from "../../logger.js";
11
+ import { GATEWAY_ORIGIN_HEADER } from "../../runtime/client.js";
12
+ import { stripHopByHop } from "../../util/strip-hop-by-hop.js";
13
+
14
+ const log = getLogger("twilio-control-plane-proxy");
15
+
16
+ export function createTwilioControlPlaneProxyHandler(config: GatewayConfig) {
17
+ async function proxyToRuntime(
18
+ req: Request,
19
+ upstreamPath: string,
20
+ upstreamSearch: string,
21
+ ): Promise<Response> {
22
+ const start = performance.now();
23
+ const upstream = `${config.assistantRuntimeBaseUrl}${upstreamPath}${upstreamSearch}`;
24
+
25
+ const reqHeaders = stripHopByHop(new Headers(req.headers));
26
+ reqHeaders.delete("host");
27
+ reqHeaders.delete("authorization");
28
+
29
+ if (config.runtimeBearerToken) {
30
+ reqHeaders.set("authorization", `Bearer ${config.runtimeBearerToken}`);
31
+ }
32
+ if (config.runtimeGatewayOriginSecret) {
33
+ reqHeaders.set(GATEWAY_ORIGIN_HEADER, config.runtimeGatewayOriginSecret);
34
+ }
35
+
36
+ const hasBody = req.method !== "GET" && req.method !== "HEAD";
37
+ const bodyBuffer = hasBody ? await req.arrayBuffer() : null;
38
+ if (bodyBuffer !== null) {
39
+ reqHeaders.set("content-length", String(bodyBuffer.byteLength));
40
+ }
41
+
42
+ const controller = new AbortController();
43
+ const timeoutId = setTimeout(() => {
44
+ controller.abort(new DOMException("The operation was aborted due to timeout", "TimeoutError"));
45
+ }, config.runtimeTimeoutMs);
46
+
47
+ let response: Response;
48
+ try {
49
+ response = await fetchImpl(upstream, {
50
+ method: req.method,
51
+ headers: reqHeaders,
52
+ body: bodyBuffer,
53
+ signal: controller.signal,
54
+ });
55
+ clearTimeout(timeoutId);
56
+ } catch (err) {
57
+ clearTimeout(timeoutId);
58
+ const duration = Math.round(performance.now() - start);
59
+ if (err instanceof DOMException && err.name === "TimeoutError") {
60
+ log.error({ path: upstreamPath, duration }, "Twilio control-plane proxy upstream timed out");
61
+ return Response.json({ error: "Gateway Timeout" }, { status: 504 });
62
+ }
63
+ log.error({ err, path: upstreamPath, duration }, "Twilio control-plane proxy upstream connection failed");
64
+ return Response.json({ error: "Bad Gateway" }, { status: 502 });
65
+ }
66
+
67
+ const resHeaders = stripHopByHop(new Headers(response.headers));
68
+ const duration = Math.round(performance.now() - start);
69
+
70
+ if (response.status >= 400) {
71
+ const body = await response.text();
72
+ log.warn(
73
+ { path: upstreamPath, status: response.status, duration },
74
+ "Twilio control-plane proxy upstream error",
75
+ );
76
+ return new Response(body, { status: response.status, headers: resHeaders });
77
+ }
78
+
79
+ log.info(
80
+ { path: upstreamPath, status: response.status, duration },
81
+ "Twilio control-plane proxy completed",
82
+ );
83
+ return new Response(response.body, { status: response.status, headers: resHeaders });
84
+ }
85
+
86
+ return {
87
+ async handleGetTwilioConfig(req: Request): Promise<Response> {
88
+ return proxyToRuntime(req, "/v1/integrations/twilio/config", "");
89
+ },
90
+
91
+ async handleSetTwilioCredentials(req: Request): Promise<Response> {
92
+ return proxyToRuntime(req, "/v1/integrations/twilio/credentials", "");
93
+ },
94
+
95
+ async handleClearTwilioCredentials(req: Request): Promise<Response> {
96
+ return proxyToRuntime(req, "/v1/integrations/twilio/credentials", "");
97
+ },
98
+
99
+ async handleListTwilioNumbers(req: Request): Promise<Response> {
100
+ return proxyToRuntime(req, "/v1/integrations/twilio/numbers", "");
101
+ },
102
+
103
+ async handleProvisionTwilioNumber(req: Request): Promise<Response> {
104
+ return proxyToRuntime(req, "/v1/integrations/twilio/numbers/provision", "");
105
+ },
106
+
107
+ async handleAssignTwilioNumber(req: Request): Promise<Response> {
108
+ return proxyToRuntime(req, "/v1/integrations/twilio/numbers/assign", "");
109
+ },
110
+
111
+ async handleReleaseTwilioNumber(req: Request): Promise<Response> {
112
+ return proxyToRuntime(req, "/v1/integrations/twilio/numbers/release", "");
113
+ },
114
+
115
+ async handleGetSmsCompliance(req: Request): Promise<Response> {
116
+ return proxyToRuntime(req, "/v1/integrations/twilio/sms/compliance", "");
117
+ },
118
+
119
+ async handleSubmitTollfreeVerification(req: Request): Promise<Response> {
120
+ return proxyToRuntime(req, "/v1/integrations/twilio/sms/compliance/tollfree", "");
121
+ },
122
+
123
+ async handleUpdateTollfreeVerification(req: Request, verificationSid: string): Promise<Response> {
124
+ return proxyToRuntime(req, `/v1/integrations/twilio/sms/compliance/tollfree/${encodeURIComponent(verificationSid)}`, "");
125
+ },
126
+
127
+ async handleDeleteTollfreeVerification(req: Request, verificationSid: string): Promise<Response> {
128
+ return proxyToRuntime(req, `/v1/integrations/twilio/sms/compliance/tollfree/${encodeURIComponent(verificationSid)}`, "");
129
+ },
130
+
131
+ async handleSmsSendTest(req: Request): Promise<Response> {
132
+ return proxyToRuntime(req, "/v1/integrations/twilio/sms/test", "");
133
+ },
134
+
135
+ async handleSmsDoctor(req: Request): Promise<Response> {
136
+ return proxyToRuntime(req, "/v1/integrations/twilio/sms/doctor", "");
137
+ },
138
+ };
139
+ }
@@ -60,7 +60,7 @@ export function createWhatsAppDeliverHandler(config: GatewayConfig) {
60
60
  return Response.json({ error: "to is required" }, { status: 400 });
61
61
  }
62
62
 
63
- const { text, assistantId, attachments, approval } = body;
63
+ const { text, assistantId: _assistantId, attachments, approval } = body;
64
64
 
65
65
  if (text !== undefined && typeof text !== "string") {
66
66
  return Response.json({ error: "text must be a string" }, { status: 400 });
package/src/index.ts CHANGED
@@ -33,7 +33,10 @@ import { createGuardianControlPlaneProxyHandler } from "./http/routes/guardian-c
33
33
  import { createTelegramControlPlaneProxyHandler } from "./http/routes/telegram-control-plane-proxy.js";
34
34
  import { createIngressControlPlaneProxyHandler } from "./http/routes/ingress-control-plane-proxy.js";
35
35
  import { matchIngressControlPlaneRoute } from "./http/routes/ingress-control-plane-route-match.js";
36
+ import { createTwilioControlPlaneProxyHandler } from "./http/routes/twilio-control-plane-proxy.js";
37
+ import { createChannelReadinessProxyHandler } from "./http/routes/channel-readiness-proxy.js";
36
38
  import { createRuntimeHealthProxyHandler } from "./http/routes/runtime-health-proxy.js";
39
+ import { createBrainGraphProxyHandler } from "./http/routes/brain-graph-proxy.js";
37
40
  import { validateBearerToken } from "./http/auth/bearer.js";
38
41
  import { getLogger, initLogger } from "./logger.js";
39
42
  import { CircuitBreakerOpenError } from "./runtime/client.js";
@@ -204,7 +207,10 @@ function main() {
204
207
  const guardianControlPlaneProxy = createGuardianControlPlaneProxyHandler(config);
205
208
  const telegramControlPlaneProxy = createTelegramControlPlaneProxyHandler(config);
206
209
  const ingressControlPlaneProxy = createIngressControlPlaneProxyHandler(config);
210
+ const twilioControlPlaneProxy = createTwilioControlPlaneProxyHandler(config);
211
+ const channelReadinessProxy = createChannelReadinessProxyHandler(config);
207
212
  const runtimeHealthProxy = createRuntimeHealthProxyHandler(config);
213
+ const brainGraphProxy = createBrainGraphProxyHandler(config);
208
214
  const handleFeatureFlagsGet = createFeatureFlagsGetHandler();
209
215
  const handleFeatureFlagsPatch = createFeatureFlagsPatchHandler();
210
216
 
@@ -447,6 +453,23 @@ function main() {
447
453
  return runtimeHealthProxy.handleRuntimeHealth(tracedReq);
448
454
  }
449
455
 
456
+ // ── Brain graph proxy ──
457
+ if (url.pathname === "/v1/brain-graph" && req.method === "GET") {
458
+ const authError = requireRuntimeBearerAuth();
459
+ if (authError) return authError;
460
+ return brainGraphProxy.handleBrainGraph(tracedReq);
461
+ }
462
+ if (url.pathname === "/v1/brain-graph-ui" && req.method === "GET") {
463
+ const authError = requireRuntimeBearerAuth();
464
+ if (authError) return authError;
465
+ return brainGraphProxy.handleBrainGraphUI(tracedReq);
466
+ }
467
+ if (url.pathname === "/v1/home-base-ui" && req.method === "GET") {
468
+ const authError = requireRuntimeBearerAuth();
469
+ if (authError) return authError;
470
+ return brainGraphProxy.handleHomeBaseUI(tracedReq);
471
+ }
472
+
450
473
  // ── Telegram integration control-plane proxy ──
451
474
  if (
452
475
  (url.pathname === "/v1/integrations/telegram/config" && req.method === "GET")
@@ -525,6 +548,85 @@ function main() {
525
548
  return guardianControlPlaneProxy.handleCancelGuardianOutbound(tracedReq);
526
549
  }
527
550
 
551
+ // ── Twilio integration control-plane proxy ──
552
+ if (
553
+ (url.pathname === "/v1/integrations/twilio/config" && req.method === "GET")
554
+ || (url.pathname === "/v1/integrations/twilio/credentials" && req.method === "POST")
555
+ || (url.pathname === "/v1/integrations/twilio/credentials" && req.method === "DELETE")
556
+ || (url.pathname === "/v1/integrations/twilio/numbers" && req.method === "GET")
557
+ || (url.pathname === "/v1/integrations/twilio/numbers/provision" && req.method === "POST")
558
+ || (url.pathname === "/v1/integrations/twilio/numbers/assign" && req.method === "POST")
559
+ || (url.pathname === "/v1/integrations/twilio/numbers/release" && req.method === "POST")
560
+ || (url.pathname === "/v1/integrations/twilio/sms/compliance" && req.method === "GET")
561
+ || (url.pathname === "/v1/integrations/twilio/sms/compliance/tollfree" && req.method === "POST")
562
+ || (url.pathname === "/v1/integrations/twilio/sms/test" && req.method === "POST")
563
+ || (url.pathname === "/v1/integrations/twilio/sms/doctor" && req.method === "POST")
564
+ ) {
565
+ const authError = requireRuntimeBearerAuth();
566
+ if (authError) return authError;
567
+
568
+ if (url.pathname === "/v1/integrations/twilio/config" && req.method === "GET") {
569
+ return twilioControlPlaneProxy.handleGetTwilioConfig(tracedReq);
570
+ }
571
+ if (url.pathname === "/v1/integrations/twilio/credentials" && req.method === "POST") {
572
+ return twilioControlPlaneProxy.handleSetTwilioCredentials(tracedReq);
573
+ }
574
+ if (url.pathname === "/v1/integrations/twilio/credentials" && req.method === "DELETE") {
575
+ return twilioControlPlaneProxy.handleClearTwilioCredentials(tracedReq);
576
+ }
577
+ if (url.pathname === "/v1/integrations/twilio/numbers" && req.method === "GET") {
578
+ return twilioControlPlaneProxy.handleListTwilioNumbers(tracedReq);
579
+ }
580
+ if (url.pathname === "/v1/integrations/twilio/numbers/provision") {
581
+ return twilioControlPlaneProxy.handleProvisionTwilioNumber(tracedReq);
582
+ }
583
+ if (url.pathname === "/v1/integrations/twilio/numbers/assign") {
584
+ return twilioControlPlaneProxy.handleAssignTwilioNumber(tracedReq);
585
+ }
586
+ if (url.pathname === "/v1/integrations/twilio/numbers/release") {
587
+ return twilioControlPlaneProxy.handleReleaseTwilioNumber(tracedReq);
588
+ }
589
+ if (url.pathname === "/v1/integrations/twilio/sms/compliance" && req.method === "GET") {
590
+ return twilioControlPlaneProxy.handleGetSmsCompliance(tracedReq);
591
+ }
592
+ if (url.pathname === "/v1/integrations/twilio/sms/compliance/tollfree") {
593
+ return twilioControlPlaneProxy.handleSubmitTollfreeVerification(tracedReq);
594
+ }
595
+ if (url.pathname === "/v1/integrations/twilio/sms/test") {
596
+ return twilioControlPlaneProxy.handleSmsSendTest(tracedReq);
597
+ }
598
+ return twilioControlPlaneProxy.handleSmsDoctor(tracedReq);
599
+ }
600
+
601
+ // ── Twilio tollfree verification dynamic path routes ──
602
+ const tollfreeVerificationMatch = url.pathname.match(
603
+ /^\/v1\/integrations\/twilio\/sms\/compliance\/tollfree\/([^/]+)$/,
604
+ );
605
+ if (tollfreeVerificationMatch && (req.method === "PATCH" || req.method === "DELETE")) {
606
+ const authError = requireRuntimeBearerAuth();
607
+ if (authError) return authError;
608
+
609
+ const verificationSid = decodeURIComponent(tollfreeVerificationMatch[1]);
610
+ if (req.method === "PATCH") {
611
+ return twilioControlPlaneProxy.handleUpdateTollfreeVerification(tracedReq, verificationSid);
612
+ }
613
+ return twilioControlPlaneProxy.handleDeleteTollfreeVerification(tracedReq, verificationSid);
614
+ }
615
+
616
+ // ── Channel readiness proxy ──
617
+ if (
618
+ (url.pathname === "/v1/channels/readiness" && req.method === "GET")
619
+ || (url.pathname === "/v1/channels/readiness/refresh" && req.method === "POST")
620
+ ) {
621
+ const authError = requireRuntimeBearerAuth();
622
+ if (authError) return authError;
623
+
624
+ if (url.pathname === "/v1/channels/readiness" && req.method === "GET") {
625
+ return channelReadinessProxy.handleGetChannelReadiness(tracedReq);
626
+ }
627
+ return channelReadinessProxy.handleRefreshChannelReadiness(tracedReq);
628
+ }
629
+
528
630
  if (url.pathname === "/integrations/status" && req.method === "GET") {
529
631
  const authError = requireRuntimeBearerAuth();
530
632
  if (authError) return authError;
package/src/schema.ts CHANGED
@@ -1102,13 +1102,6 @@ export function buildSchema(): Record<string, unknown> {
1102
1102
  schema: { type: "string", enum: ["sms", "voice", "telegram"] },
1103
1103
  description: "Optional guardian channel filter.",
1104
1104
  },
1105
- {
1106
- name: "assistantId",
1107
- in: "query",
1108
- required: false,
1109
- schema: { type: "string" },
1110
- description: "Optional assistant identifier override.",
1111
- },
1112
1105
  ],
1113
1106
  responses: {
1114
1107
  "200": { description: "Guardian status returned" },
@@ -1196,6 +1189,339 @@ export function buildSchema(): Record<string, unknown> {
1196
1189
  },
1197
1190
  },
1198
1191
  },
1192
+ "/v1/integrations/twilio/config": {
1193
+ get: {
1194
+ summary: "Get Twilio integration config",
1195
+ description:
1196
+ "Authenticated gateway endpoint that returns current Twilio integration configuration from the assistant runtime.",
1197
+ operationId: "twilioConfigGet",
1198
+ security: [{ BearerAuth: [] }],
1199
+ responses: {
1200
+ "200": { description: "Twilio config returned" },
1201
+ "401": { description: "Unauthorized — missing or invalid bearer token" },
1202
+ "503": { description: "Bearer token not configured" },
1203
+ "502": { description: "Failed to reach assistant runtime" },
1204
+ "504": { description: "Assistant runtime request timed out" },
1205
+ },
1206
+ },
1207
+ },
1208
+ "/v1/integrations/twilio/credentials": {
1209
+ post: {
1210
+ summary: "Set Twilio credentials",
1211
+ description:
1212
+ "Authenticated gateway endpoint that stores Twilio account credentials via the assistant runtime.",
1213
+ operationId: "twilioCredentialsPost",
1214
+ security: [{ BearerAuth: [] }],
1215
+ requestBody: {
1216
+ required: true,
1217
+ content: {
1218
+ "application/json": {
1219
+ schema: { type: "object", additionalProperties: true },
1220
+ },
1221
+ },
1222
+ },
1223
+ responses: {
1224
+ "200": { description: "Twilio credentials stored" },
1225
+ "400": { description: "Invalid request payload" },
1226
+ "401": { description: "Unauthorized — missing or invalid bearer token" },
1227
+ "503": { description: "Bearer token not configured" },
1228
+ "502": { description: "Failed to reach assistant runtime" },
1229
+ "504": { description: "Assistant runtime request timed out" },
1230
+ },
1231
+ },
1232
+ delete: {
1233
+ summary: "Clear Twilio credentials",
1234
+ description:
1235
+ "Authenticated gateway endpoint that clears stored Twilio credentials via the assistant runtime.",
1236
+ operationId: "twilioCredentialsDelete",
1237
+ security: [{ BearerAuth: [] }],
1238
+ responses: {
1239
+ "200": { description: "Twilio credentials cleared" },
1240
+ "401": { description: "Unauthorized — missing or invalid bearer token" },
1241
+ "503": { description: "Bearer token not configured" },
1242
+ "502": { description: "Failed to reach assistant runtime" },
1243
+ "504": { description: "Assistant runtime request timed out" },
1244
+ },
1245
+ },
1246
+ },
1247
+ "/v1/integrations/twilio/numbers": {
1248
+ get: {
1249
+ summary: "List Twilio phone numbers",
1250
+ description:
1251
+ "Authenticated gateway endpoint that lists available Twilio phone numbers via the assistant runtime.",
1252
+ operationId: "twilioNumbersGet",
1253
+ security: [{ BearerAuth: [] }],
1254
+ responses: {
1255
+ "200": { description: "Twilio phone numbers returned" },
1256
+ "401": { description: "Unauthorized — missing or invalid bearer token" },
1257
+ "503": { description: "Bearer token not configured" },
1258
+ "502": { description: "Failed to reach assistant runtime" },
1259
+ "504": { description: "Assistant runtime request timed out" },
1260
+ },
1261
+ },
1262
+ },
1263
+ "/v1/integrations/twilio/numbers/provision": {
1264
+ post: {
1265
+ summary: "Provision a Twilio phone number",
1266
+ description:
1267
+ "Authenticated gateway endpoint that provisions a new Twilio phone number via the assistant runtime.",
1268
+ operationId: "twilioNumbersProvisionPost",
1269
+ security: [{ BearerAuth: [] }],
1270
+ requestBody: {
1271
+ required: true,
1272
+ content: {
1273
+ "application/json": {
1274
+ schema: { type: "object", additionalProperties: true },
1275
+ },
1276
+ },
1277
+ },
1278
+ responses: {
1279
+ "200": { description: "Phone number provisioned" },
1280
+ "400": { description: "Invalid request payload" },
1281
+ "401": { description: "Unauthorized — missing or invalid bearer token" },
1282
+ "503": { description: "Bearer token not configured" },
1283
+ "502": { description: "Failed to reach assistant runtime" },
1284
+ "504": { description: "Assistant runtime request timed out" },
1285
+ },
1286
+ },
1287
+ },
1288
+ "/v1/integrations/twilio/numbers/assign": {
1289
+ post: {
1290
+ summary: "Assign a Twilio phone number",
1291
+ description:
1292
+ "Authenticated gateway endpoint that assigns an existing Twilio phone number to the assistant via the runtime.",
1293
+ operationId: "twilioNumbersAssignPost",
1294
+ security: [{ BearerAuth: [] }],
1295
+ requestBody: {
1296
+ required: true,
1297
+ content: {
1298
+ "application/json": {
1299
+ schema: { type: "object", additionalProperties: true },
1300
+ },
1301
+ },
1302
+ },
1303
+ responses: {
1304
+ "200": { description: "Phone number assigned" },
1305
+ "400": { description: "Invalid request payload" },
1306
+ "401": { description: "Unauthorized — missing or invalid bearer token" },
1307
+ "503": { description: "Bearer token not configured" },
1308
+ "502": { description: "Failed to reach assistant runtime" },
1309
+ "504": { description: "Assistant runtime request timed out" },
1310
+ },
1311
+ },
1312
+ },
1313
+ "/v1/integrations/twilio/numbers/release": {
1314
+ post: {
1315
+ summary: "Release a Twilio phone number",
1316
+ description:
1317
+ "Authenticated gateway endpoint that releases an assigned Twilio phone number via the assistant runtime.",
1318
+ operationId: "twilioNumbersReleasePost",
1319
+ security: [{ BearerAuth: [] }],
1320
+ requestBody: {
1321
+ required: true,
1322
+ content: {
1323
+ "application/json": {
1324
+ schema: { type: "object", additionalProperties: true },
1325
+ },
1326
+ },
1327
+ },
1328
+ responses: {
1329
+ "200": { description: "Phone number released" },
1330
+ "400": { description: "Invalid request payload" },
1331
+ "401": { description: "Unauthorized — missing or invalid bearer token" },
1332
+ "503": { description: "Bearer token not configured" },
1333
+ "502": { description: "Failed to reach assistant runtime" },
1334
+ "504": { description: "Assistant runtime request timed out" },
1335
+ },
1336
+ },
1337
+ },
1338
+ "/v1/integrations/twilio/sms/compliance": {
1339
+ get: {
1340
+ summary: "Get SMS compliance status",
1341
+ description:
1342
+ "Authenticated gateway endpoint that returns SMS compliance/registration status from the assistant runtime.",
1343
+ operationId: "twilioSmsComplianceGet",
1344
+ security: [{ BearerAuth: [] }],
1345
+ responses: {
1346
+ "200": { description: "SMS compliance status returned" },
1347
+ "401": { description: "Unauthorized — missing or invalid bearer token" },
1348
+ "503": { description: "Bearer token not configured" },
1349
+ "502": { description: "Failed to reach assistant runtime" },
1350
+ "504": { description: "Assistant runtime request timed out" },
1351
+ },
1352
+ },
1353
+ },
1354
+ "/v1/integrations/twilio/sms/compliance/tollfree": {
1355
+ post: {
1356
+ summary: "Submit toll-free verification",
1357
+ description:
1358
+ "Authenticated gateway endpoint that submits a toll-free number verification request via the assistant runtime.",
1359
+ operationId: "twilioSmsTollfreePost",
1360
+ security: [{ BearerAuth: [] }],
1361
+ requestBody: {
1362
+ required: true,
1363
+ content: {
1364
+ "application/json": {
1365
+ schema: { type: "object", additionalProperties: true },
1366
+ },
1367
+ },
1368
+ },
1369
+ responses: {
1370
+ "200": { description: "Toll-free verification submitted" },
1371
+ "400": { description: "Invalid request payload" },
1372
+ "401": { description: "Unauthorized — missing or invalid bearer token" },
1373
+ "503": { description: "Bearer token not configured" },
1374
+ "502": { description: "Failed to reach assistant runtime" },
1375
+ "504": { description: "Assistant runtime request timed out" },
1376
+ },
1377
+ },
1378
+ },
1379
+ "/v1/integrations/twilio/sms/compliance/tollfree/{verificationSid}": {
1380
+ patch: {
1381
+ summary: "Update toll-free verification",
1382
+ description:
1383
+ "Authenticated gateway endpoint that updates an existing toll-free verification via the assistant runtime.",
1384
+ operationId: "twilioSmsTollfreePatch",
1385
+ security: [{ BearerAuth: [] }],
1386
+ parameters: [
1387
+ {
1388
+ name: "verificationSid",
1389
+ in: "path",
1390
+ required: true,
1391
+ schema: { type: "string" },
1392
+ },
1393
+ ],
1394
+ requestBody: {
1395
+ required: true,
1396
+ content: {
1397
+ "application/json": {
1398
+ schema: { type: "object", additionalProperties: true },
1399
+ },
1400
+ },
1401
+ },
1402
+ responses: {
1403
+ "200": { description: "Toll-free verification updated" },
1404
+ "400": { description: "Invalid request payload" },
1405
+ "401": { description: "Unauthorized — missing or invalid bearer token" },
1406
+ "503": { description: "Bearer token not configured" },
1407
+ "502": { description: "Failed to reach assistant runtime" },
1408
+ "504": { description: "Assistant runtime request timed out" },
1409
+ },
1410
+ },
1411
+ delete: {
1412
+ summary: "Delete toll-free verification",
1413
+ description:
1414
+ "Authenticated gateway endpoint that deletes a toll-free verification via the assistant runtime.",
1415
+ operationId: "twilioSmsTollfreeDelete",
1416
+ security: [{ BearerAuth: [] }],
1417
+ parameters: [
1418
+ {
1419
+ name: "verificationSid",
1420
+ in: "path",
1421
+ required: true,
1422
+ schema: { type: "string" },
1423
+ },
1424
+ ],
1425
+ responses: {
1426
+ "200": { description: "Toll-free verification deleted" },
1427
+ "401": { description: "Unauthorized — missing or invalid bearer token" },
1428
+ "404": { description: "Verification not found" },
1429
+ "503": { description: "Bearer token not configured" },
1430
+ "502": { description: "Failed to reach assistant runtime" },
1431
+ "504": { description: "Assistant runtime request timed out" },
1432
+ },
1433
+ },
1434
+ },
1435
+ "/v1/integrations/twilio/sms/test": {
1436
+ post: {
1437
+ summary: "Send a test SMS",
1438
+ description:
1439
+ "Authenticated gateway endpoint that sends a test SMS message via the assistant runtime.",
1440
+ operationId: "twilioSmsTestPost",
1441
+ security: [{ BearerAuth: [] }],
1442
+ requestBody: {
1443
+ required: true,
1444
+ content: {
1445
+ "application/json": {
1446
+ schema: { type: "object", additionalProperties: true },
1447
+ },
1448
+ },
1449
+ },
1450
+ responses: {
1451
+ "200": { description: "Test SMS sent" },
1452
+ "400": { description: "Invalid request payload" },
1453
+ "401": { description: "Unauthorized — missing or invalid bearer token" },
1454
+ "503": { description: "Bearer token not configured" },
1455
+ "502": { description: "Failed to reach assistant runtime" },
1456
+ "504": { description: "Assistant runtime request timed out" },
1457
+ },
1458
+ },
1459
+ },
1460
+ "/v1/integrations/twilio/sms/doctor": {
1461
+ post: {
1462
+ summary: "Run SMS doctor diagnostics",
1463
+ description:
1464
+ "Authenticated gateway endpoint that runs SMS integration diagnostics via the assistant runtime.",
1465
+ operationId: "twilioSmsDoctorPost",
1466
+ security: [{ BearerAuth: [] }],
1467
+ requestBody: {
1468
+ required: true,
1469
+ content: {
1470
+ "application/json": {
1471
+ schema: { type: "object", additionalProperties: true },
1472
+ },
1473
+ },
1474
+ },
1475
+ responses: {
1476
+ "200": { description: "SMS doctor diagnostics returned" },
1477
+ "400": { description: "Invalid request payload" },
1478
+ "401": { description: "Unauthorized — missing or invalid bearer token" },
1479
+ "503": { description: "Bearer token not configured" },
1480
+ "502": { description: "Failed to reach assistant runtime" },
1481
+ "504": { description: "Assistant runtime request timed out" },
1482
+ },
1483
+ },
1484
+ },
1485
+ "/v1/channels/readiness": {
1486
+ get: {
1487
+ summary: "Get channel readiness",
1488
+ description:
1489
+ "Authenticated gateway endpoint that returns the readiness status of all configured channels from the assistant runtime.",
1490
+ operationId: "channelReadinessGet",
1491
+ security: [{ BearerAuth: [] }],
1492
+ responses: {
1493
+ "200": { description: "Channel readiness status returned" },
1494
+ "401": { description: "Unauthorized — missing or invalid bearer token" },
1495
+ "503": { description: "Bearer token not configured" },
1496
+ "502": { description: "Failed to reach assistant runtime" },
1497
+ "504": { description: "Assistant runtime request timed out" },
1498
+ },
1499
+ },
1500
+ },
1501
+ "/v1/channels/readiness/refresh": {
1502
+ post: {
1503
+ summary: "Refresh channel readiness",
1504
+ description:
1505
+ "Authenticated gateway endpoint that triggers a fresh readiness check for all channels via the assistant runtime.",
1506
+ operationId: "channelReadinessRefreshPost",
1507
+ security: [{ BearerAuth: [] }],
1508
+ requestBody: {
1509
+ required: false,
1510
+ content: {
1511
+ "application/json": {
1512
+ schema: { type: "object", additionalProperties: true },
1513
+ },
1514
+ },
1515
+ },
1516
+ responses: {
1517
+ "200": { description: "Channel readiness refreshed" },
1518
+ "401": { description: "Unauthorized — missing or invalid bearer token" },
1519
+ "503": { description: "Bearer token not configured" },
1520
+ "502": { description: "Failed to reach assistant runtime" },
1521
+ "504": { description: "Assistant runtime request timed out" },
1522
+ },
1523
+ },
1524
+ },
1199
1525
  "/integrations/status": {
1200
1526
  get: {
1201
1527
  summary: "Integration status",