@vellumai/assistant 0.4.18 → 0.4.20

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.
Files changed (36) hide show
  1. package/docs/runbook-trusted-contacts.md +5 -3
  2. package/package.json +1 -1
  3. package/src/__tests__/channel-approvals.test.ts +7 -1
  4. package/src/__tests__/conversation-routes-guardian-reply.test.ts +8 -0
  5. package/src/__tests__/daemon-server-session-init.test.ts +2 -0
  6. package/src/__tests__/gmail-integration.test.ts +13 -4
  7. package/src/__tests__/handle-user-message-secret-resume.test.ts +7 -1
  8. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +2 -0
  9. package/src/__tests__/ingress-reconcile.test.ts +13 -5
  10. package/src/__tests__/mcp-cli.test.ts +1 -1
  11. package/src/__tests__/recording-intent-handler.test.ts +9 -1
  12. package/src/__tests__/send-endpoint-busy.test.ts +8 -2
  13. package/src/__tests__/sms-messaging-provider.test.ts +4 -0
  14. package/src/__tests__/system-prompt.test.ts +18 -2
  15. package/src/__tests__/tool-execution-abort-cleanup.test.ts +1 -0
  16. package/src/agent/loop.ts +324 -163
  17. package/src/cli/mcp.ts +81 -28
  18. package/src/config/bundled-skills/app-builder/SKILL.md +7 -5
  19. package/src/config/bundled-skills/app-builder/TOOLS.json +2 -2
  20. package/src/config/bundled-skills/guardian-verify-setup/SKILL.md +6 -11
  21. package/src/config/bundled-skills/phone-calls/SKILL.md +1 -2
  22. package/src/config/bundled-skills/sms-setup/SKILL.md +8 -16
  23. package/src/config/bundled-skills/telegram-setup/SKILL.md +3 -3
  24. package/src/config/bundled-skills/trusted-contacts/SKILL.md +13 -25
  25. package/src/config/bundled-skills/twilio-setup/SKILL.md +13 -23
  26. package/src/config/system-prompt.ts +574 -518
  27. package/src/daemon/session-surfaces.ts +28 -0
  28. package/src/daemon/session.ts +255 -191
  29. package/src/daemon/tool-side-effects.ts +3 -13
  30. package/src/mcp/client.ts +2 -7
  31. package/src/security/secure-keys.ts +43 -3
  32. package/src/tools/apps/definitions.ts +5 -0
  33. package/src/tools/apps/executors.ts +18 -22
  34. package/src/tools/terminal/safe-env.ts +7 -0
  35. package/src/__tests__/response-tier.test.ts +0 -195
  36. package/src/daemon/response-tier.ts +0 -250
package/src/cli/mcp.ts CHANGED
@@ -15,7 +15,7 @@ const log = getCliLogger('cli');
15
15
  export const HEALTH_CHECK_TIMEOUT_MS = 10_000;
16
16
 
17
17
  export async function checkServerHealth(serverId: string, config: McpServerConfig, timeoutMs = HEALTH_CHECK_TIMEOUT_MS): Promise<string> {
18
- const client = new McpClient(serverId, { quiet: true });
18
+ const client = new McpClient(serverId);
19
19
  try {
20
20
  await Promise.race([
21
21
  client.connect(config.transport),
@@ -73,40 +73,93 @@ export function registerMcpCommand(program: Command): void {
73
73
 
74
74
  log.info(`${entries.length} MCP server(s) configured:\n`);
75
75
 
76
- let didHealthCheck = false;
77
- for (const [id, cfg] of entries) {
78
- if (!cfg || typeof cfg !== 'object') {
79
- log.info(` ${id} (invalid config — skipped)\n`);
80
- continue;
81
- }
82
- const enabled = cfg.enabled !== false;
83
- const transport = cfg.transport;
84
- const risk = cfg.defaultRiskLevel ?? 'high';
76
+ const isTTY = process.stdout.isTTY;
85
77
 
86
- let status: string;
87
- if (!enabled) {
88
- status = '✗ disabled';
89
- } else {
90
- status = await checkServerHealth(id, cfg);
91
- didHealthCheck = true;
78
+ if (isTTY) {
79
+ // TTY path: print placeholders, run health checks in parallel, update in-place with ANSI codes
80
+ let lineCount = 0;
81
+ const healthChecks: { id: string; cfg: McpServerConfig; statusLine: number }[] = [];
82
+
83
+ for (const [id, cfg] of entries) {
84
+ if (!cfg || typeof cfg !== 'object') {
85
+ log.info(` ${id} (invalid config — skipped)\n`);
86
+ lineCount += 2;
87
+ continue;
88
+ }
89
+ const enabled = cfg.enabled !== false;
90
+ const transport = cfg.transport;
91
+ const risk = cfg.defaultRiskLevel ?? 'high';
92
+ const statusText = !enabled ? '✗ disabled' : '⏳ Checking...';
93
+
94
+ log.info(` ${id}`);
95
+ lineCount++;
96
+ const statusLine = lineCount;
97
+ log.info(` Status: ${statusText}`);
98
+ lineCount++;
99
+ log.info(` Transport: ${transport?.type ?? 'unknown'}`);
100
+ lineCount++;
101
+ if (transport?.type === 'stdio') {
102
+ log.info(` Command: ${transport.command} ${(transport.args ?? []).join(' ')}`);
103
+ lineCount++;
104
+ } else if (transport && 'url' in transport) {
105
+ log.info(` URL: ${transport.url}`);
106
+ lineCount++;
107
+ }
108
+ log.info(` Risk: ${risk}`);
109
+ lineCount++;
110
+ if (cfg.allowedTools) { log.info(` Allowed: ${cfg.allowedTools.join(', ')}`); lineCount++; }
111
+ if (cfg.blockedTools) { log.info(` Blocked: ${cfg.blockedTools.join(', ')}`); lineCount++; }
112
+ log.info('');
113
+ lineCount++;
114
+
115
+ if (enabled) {
116
+ healthChecks.push({ id, cfg, statusLine });
117
+ }
92
118
  }
93
119
 
94
- log.info(` ${id}`);
95
- log.info(` Status: ${status}`);
96
- log.info(` Transport: ${transport?.type ?? 'unknown'}`);
97
- if (transport?.type === 'stdio') {
98
- log.info(` Command: ${transport.command} ${(transport.args ?? []).join(' ')}`);
99
- } else if (transport && 'url' in transport) {
100
- log.info(` URL: ${transport.url}`);
120
+ if (healthChecks.length === 0) return;
121
+
122
+ // Run health checks in parallel, update status lines in-place with ANSI codes
123
+ await Promise.all(healthChecks.map(async ({ id, cfg, statusLine }) => {
124
+ const health = await checkServerHealth(id, cfg);
125
+ const up = lineCount - statusLine;
126
+ process.stdout.write(`\x1b[${up}A\r\x1b[2K Status: ${health}\x1b[${up}B\r`);
127
+ }));
128
+ } else {
129
+ // Non-TTY path: run health checks sequentially, print final status directly (no ANSI codes)
130
+ for (const [id, cfg] of entries) {
131
+ if (!cfg || typeof cfg !== 'object') {
132
+ log.info(` ${id} (invalid config — skipped)\n`);
133
+ continue;
134
+ }
135
+ const enabled = cfg.enabled !== false;
136
+ const transport = cfg.transport;
137
+ const risk = cfg.defaultRiskLevel ?? 'high';
138
+
139
+ let statusText: string;
140
+ if (!enabled) {
141
+ statusText = '✗ disabled';
142
+ } else {
143
+ statusText = await checkServerHealth(id, cfg);
144
+ }
145
+
146
+ log.info(` ${id}`);
147
+ log.info(` Status: ${statusText}`);
148
+ log.info(` Transport: ${transport?.type ?? 'unknown'}`);
149
+ if (transport?.type === 'stdio') {
150
+ log.info(` Command: ${transport.command} ${(transport.args ?? []).join(' ')}`);
151
+ } else if (transport && 'url' in transport) {
152
+ log.info(` URL: ${transport.url}`);
153
+ }
154
+ log.info(` Risk: ${risk}`);
155
+ if (cfg.allowedTools) { log.info(` Allowed: ${cfg.allowedTools.join(', ')}`); }
156
+ if (cfg.blockedTools) { log.info(` Blocked: ${cfg.blockedTools.join(', ')}`); }
157
+ log.info('');
101
158
  }
102
- log.info(` Risk: ${risk}`);
103
- if (cfg.allowedTools) log.info(` Allowed: ${cfg.allowedTools.join(', ')}`);
104
- if (cfg.blockedTools) log.info(` Blocked: ${cfg.blockedTools.join(', ')}`);
105
- log.info('');
106
159
  }
107
160
 
108
161
  // Health checks may leave MCP transports alive — force exit
109
- if (didHealthCheck) process.exit(0);
162
+ process.exit(0);
110
163
  });
111
164
 
112
165
  mcp
@@ -1323,14 +1323,16 @@ Call `app_create` with:
1323
1323
  - `description`: One-sentence summary
1324
1324
  - `schema_json`: JSON schema as string
1325
1325
  - `html`: Complete HTML document as string
1326
- - `auto_open`: (optional, defaults to `true`) Opens the app immediately
1327
- - `preview`: (optional) Inline preview card — see below
1326
+ - `auto_open`: (optional, defaults to `true`) Shows an inline preview card in chat after creation
1327
+ - `preview`: Always include this — see below
1328
1328
 
1329
- Since `auto_open` defaults to `true`, you don't need to call `app_open` separately after `app_create`.
1329
+ Since `auto_open` defaults to `true`, an inline preview card is shown in chat after creation. The app is NOT opened in a workspace panel automatically — users open it explicitly if desired by clicking 'Open App' on the inline card. The app appears in Things (sidebar) immediately after creation via the `app_files_changed` broadcast.
1330
1330
 
1331
1331
  #### Preview metadata
1332
1332
 
1333
- Both `ui_show` and `app_create` support a `preview` object for an inline chat preview card. Always include it so the user sees a compact summary without opening the app.
1333
+ Always include preview metadata in `app_create` calls. The app shows as an inline card in chat first — no workspace opens automatically. Users can click 'Open App' on the inline card to open the workspace.
1334
+
1335
+ Both `ui_show` and `app_create` support a `preview` object for an inline chat preview card. Always include it so the user sees a compact summary of what was built.
1334
1336
 
1335
1337
  **With `ui_show`:**
1336
1338
  ```json
@@ -1387,7 +1389,7 @@ Both `ui_show` and `app_create` support a `preview` object for an inline chat pr
1387
1389
  }
1388
1390
  ```
1389
1391
 
1390
- Preview fields: `title` (required), `subtitle`, `description`, `icon`, `metrics` (up to 3 key-value pills). The `icon` field accepts an emoji or an image URL. **Prefer an image URL whenever you have a relevant one** — logos, favicons, product images, headshots, flags, album art, or any image you encountered during research. The preview card renders image URLs as a thumbnail automatically. Fall back to emoji only when there is no natural image. When `app_create` is called with `auto_open: true` (the default), the preview is forwarded through `app_open` automatically.
1392
+ Preview fields: `title` (required), `subtitle`, `description`, `icon`, `metrics` (up to 3 key-value pills). The `icon` field accepts an emoji or an image URL. **Prefer an image URL whenever you have a relevant one** — logos, favicons, product images, headshots, flags, album art, or any image you encountered during research. The preview card renders image URLs as a thumbnail automatically. Fall back to emoji only when there is no natural image. When `app_create` is called with `auto_open: true` (the default), the inline preview card is shown in chat — the app is NOT automatically opened in a workspace panel.
1391
1393
 
1392
1394
  ### 6. Handle Iteration
1393
1395
 
@@ -37,7 +37,7 @@
37
37
  },
38
38
  "auto_open": {
39
39
  "type": "boolean",
40
- "description": "Automatically open the app after creation. Defaults to true. When true, the app is immediately displayed in a dynamic_page surface without needing a separate app_open call."
40
+ "description": "When true (default), an inline preview card is shown in chat after creation. The app is NOT automatically opened in a workspace panel users can open it explicitly via the 'Open App' button on the inline card."
41
41
  },
42
42
  "set_as_home_base": {
43
43
  "type": "boolean",
@@ -45,7 +45,7 @@
45
45
  },
46
46
  "preview": {
47
47
  "type": "object",
48
- "description": "Optional inline preview card shown in chat. Provides a compact summary so the user sees what was built without opening the app.",
48
+ "description": "Inline preview card shown in chat after creation. Always include this so users see a compact summary of what was built. Required fields: title.",
49
49
  "properties": {
50
50
  "title": { "type": "string", "description": "Preview card title" },
51
51
  "subtitle": { "type": "string", "description": "Optional subtitle" },
@@ -11,7 +11,7 @@ You are helping your user set up guardian verification for a messaging channel (
11
11
 
12
12
  - Use the injected `INTERNAL_GATEWAY_BASE_URL` for gateway API calls.
13
13
  - Never call the daemon runtime port directly; always call the gateway URL.
14
- - The bearer token is stored at `~/.vellum/http-token`. Read it with: `TOKEN=$(cat ~/.vellum/http-token)`.
14
+ - The bearer token is available as the `$GATEWAY_AUTH_TOKEN` environment variable.
15
15
  - Run shell commands for this skill with `host_bash` (not sandbox `bash`) so host auth/token and gateway routing are reliable.
16
16
  - Keep narration minimal: execute required calls first, then provide a concise status update. Do not narrate internal install/check/load chatter unless something fails.
17
17
 
@@ -39,10 +39,9 @@ Based on the chosen channel, ask for the required destination:
39
39
  Execute the outbound start request:
40
40
 
41
41
  ```bash
42
- TOKEN=$(cat ~/.vellum/http-token)
43
42
  curl -s -X POST "$INTERNAL_GATEWAY_BASE_URL/v1/integrations/guardian/outbound/start" \
44
43
  -H "Content-Type: application/json" \
45
- -H "Authorization: Bearer $TOKEN" \
44
+ -H "Authorization: Bearer $GATEWAY_AUTH_TOKEN" \
46
45
  -d '{"channel": "<channel>", "destination": "<destination>"}'
47
46
  ```
48
47
 
@@ -77,10 +76,9 @@ Handle each error code:
77
76
  If the user says they did not receive the code or asks to resend:
78
77
 
79
78
  ```bash
80
- TOKEN=$(cat ~/.vellum/http-token)
81
79
  curl -s -X POST "$INTERNAL_GATEWAY_BASE_URL/v1/integrations/guardian/outbound/resend" \
82
80
  -H "Content-Type: application/json" \
83
- -H "Authorization: Bearer $TOKEN" \
81
+ -H "Authorization: Bearer $GATEWAY_AUTH_TOKEN" \
84
82
  -d '{"channel": "<channel>"}'
85
83
  ```
86
84
 
@@ -107,10 +105,9 @@ Handle each error code from the resend endpoint:
107
105
  If the user wants to cancel the verification:
108
106
 
109
107
  ```bash
110
- TOKEN=$(cat ~/.vellum/http-token)
111
108
  curl -s -X POST "$INTERNAL_GATEWAY_BASE_URL/v1/integrations/guardian/outbound/cancel" \
112
109
  -H "Content-Type: application/json" \
113
- -H "Authorization: Bearer $TOKEN" \
110
+ -H "Authorization: Bearer $GATEWAY_AUTH_TOKEN" \
114
111
  -d '{"channel": "<channel>"}'
115
112
  ```
116
113
 
@@ -126,9 +123,8 @@ For **voice** verification only: after telling the user their code and instructi
126
123
  2. Check the binding status:
127
124
 
128
125
  ```bash
129
- TOKEN=$(cat ~/.vellum/http-token)
130
126
  curl -s "$INTERNAL_GATEWAY_BASE_URL/v1/integrations/guardian/status?channel=voice" \
131
- -H "Authorization: Bearer $TOKEN"
127
+ -H "Authorization: Bearer $GATEWAY_AUTH_TOKEN"
132
128
  ```
133
129
 
134
130
  3. If the response shows `bound: true`: immediately send a proactive success message in the current chat — "Voice verification complete! Your phone number is now the trusted guardian." Stop polling.
@@ -153,9 +149,8 @@ When in a **rebind flow** (i.e., the `start_outbound` request included `"rebind"
153
149
  After the user reports entering the code, verify the binding was created:
154
150
 
155
151
  ```bash
156
- TOKEN=$(cat ~/.vellum/http-token)
157
152
  curl -s "$INTERNAL_GATEWAY_BASE_URL/v1/integrations/guardian/status?channel=<channel>" \
158
- -H "Authorization: Bearer $TOKEN"
153
+ -H "Authorization: Bearer $GATEWAY_AUTH_TOKEN"
159
154
  ```
160
155
 
161
156
  If the response shows the guardian is bound, confirm success: "Guardian verified! Your [channel] identity is now the trusted guardian."
@@ -49,9 +49,8 @@ The user's assistant gets its own personal phone number through Twilio. All impl
49
49
  Check whether Twilio credentials, phone number, and public ingress are already configured:
50
50
 
51
51
  ```bash
52
- TOKEN=$(cat ~/.vellum/http-token)
53
52
  curl -s "$INTERNAL_GATEWAY_BASE_URL/v1/integrations/twilio/config" \
54
- -H "Authorization: Bearer $TOKEN"
53
+ -H "Authorization: Bearer $GATEWAY_AUTH_TOKEN"
55
54
  ```
56
55
 
57
56
  ```bash
@@ -12,9 +12,8 @@ You are helping your user set up SMS messaging. This skill orchestrates Twilio s
12
12
  First, check the current SMS channel readiness state via the gateway:
13
13
 
14
14
  ```bash
15
- TOKEN=$(cat ~/.vellum/http-token)
16
15
  curl -s "$INTERNAL_GATEWAY_BASE_URL/v1/integrations/twilio/config" \
17
- -H "Authorization: Bearer $TOKEN"
16
+ -H "Authorization: Bearer $GATEWAY_AUTH_TOKEN"
18
17
  ```
19
18
 
20
19
  Inspect the response for `hasCredentials` and `phoneNumber`.
@@ -35,9 +34,8 @@ Tell the user: _"SMS needs Twilio configured first. I've loaded the Twilio setup
35
34
  After twilio-setup completes, re-check readiness by calling the config endpoint again:
36
35
 
37
36
  ```bash
38
- TOKEN=$(cat ~/.vellum/http-token)
39
37
  curl -s "$INTERNAL_GATEWAY_BASE_URL/v1/integrations/twilio/config" \
40
- -H "Authorization: Bearer $TOKEN"
38
+ -H "Authorization: Bearer $GATEWAY_AUTH_TOKEN"
41
39
  ```
42
40
 
43
41
  If baseline is still not ready, report the specific failures and ask the user to address them before continuing.
@@ -47,9 +45,8 @@ If baseline is still not ready, report the specific failures and ask the user to
47
45
  Once baseline is ready, check SMS compliance status including remote (Twilio API) checks:
48
46
 
49
47
  ```bash
50
- TOKEN=$(cat ~/.vellum/http-token)
51
48
  curl -s "$INTERNAL_GATEWAY_BASE_URL/v1/integrations/twilio/sms/compliance" \
52
- -H "Authorization: Bearer $TOKEN"
49
+ -H "Authorization: Bearer $GATEWAY_AUTH_TOKEN"
53
50
  ```
54
51
 
55
52
  Examine the compliance results:
@@ -90,9 +87,8 @@ The `tollfreePhoneNumberSid` is returned by the compliance status response in th
90
87
  **Step 3c: Submit verification:**
91
88
 
92
89
  ```bash
93
- TOKEN=$(cat ~/.vellum/http-token)
94
90
  curl -s -X POST "$INTERNAL_GATEWAY_BASE_URL/v1/integrations/twilio/sms/compliance/tollfree" \
95
- -H "Authorization: Bearer $TOKEN" \
91
+ -H "Authorization: Bearer $GATEWAY_AUTH_TOKEN" \
96
92
  -H "Content-Type: application/json" \
97
93
  -d '{
98
94
  "tollfreePhoneNumberSid": "<compliance.tollfreePhoneNumberSid from Step 3a>",
@@ -118,9 +114,8 @@ The endpoint validates all fields before submitting to Twilio and returns clear
118
114
  **Step 3d: Update a rejected verification** (if `editAllowed` is true):
119
115
 
120
116
  ```bash
121
- TOKEN=$(cat ~/.vellum/http-token)
122
117
  curl -s -X PATCH "$INTERNAL_GATEWAY_BASE_URL/v1/integrations/twilio/sms/compliance/tollfree/<verificationSid>" \
123
- -H "Authorization: Bearer $TOKEN" \
118
+ -H "Authorization: Bearer $GATEWAY_AUTH_TOKEN" \
124
119
  -H "Content-Type: application/json" \
125
120
  -d '{
126
121
  "businessName": "updated value",
@@ -133,9 +128,8 @@ Only include fields that need to change. The endpoint checks edit eligibility an
133
128
  **Step 3e: Delete and resubmit** (if editing is not allowed):
134
129
 
135
130
  ```bash
136
- TOKEN=$(cat ~/.vellum/http-token)
137
131
  curl -s -X DELETE "$INTERNAL_GATEWAY_BASE_URL/v1/integrations/twilio/sms/compliance/tollfree/<verificationSid>" \
138
- -H "Authorization: Bearer $TOKEN"
132
+ -H "Authorization: Bearer $GATEWAY_AUTH_TOKEN"
139
133
  ```
140
134
 
141
135
  After deletion, return to Step 3b to collect information and resubmit. Warn the user that deleting resets their position in the review queue.
@@ -183,9 +177,8 @@ Tell the user: _"Let's send a test SMS to verify everything works. What phone nu
183
177
  After the user provides a number, send a test message via the gateway:
184
178
 
185
179
  ```bash
186
- TOKEN=$(cat ~/.vellum/http-token)
187
180
  curl -s -X POST "$INTERNAL_GATEWAY_BASE_URL/v1/integrations/twilio/sms/test" \
188
- -H "Authorization: Bearer $TOKEN" \
181
+ -H "Authorization: Bearer $GATEWAY_AUTH_TOKEN" \
189
182
  -H "Content-Type: application/json" \
190
183
  -d '{"phoneNumber":"<recipient phone number>","text":"Test SMS from your Vellum assistant."}'
191
184
  ```
@@ -200,9 +193,8 @@ Report the result honestly:
200
193
  If the test fails or the user reports SMS issues, run the SMS doctor:
201
194
 
202
195
  ```bash
203
- TOKEN=$(cat ~/.vellum/http-token)
204
196
  curl -s -X POST "$INTERNAL_GATEWAY_BASE_URL/v1/integrations/twilio/sms/doctor" \
205
- -H "Authorization: Bearer $TOKEN"
197
+ -H "Authorization: Bearer $GATEWAY_AUTH_TOKEN"
206
198
  ```
207
199
 
208
200
  This runs a comprehensive health diagnostic, checking channel readiness, compliance/toll-free verification status, and the last test result. Report the diagnostics and actionable items to the user.
@@ -39,7 +39,7 @@ After the token is collected, call the composite setup endpoint which validates
39
39
 
40
40
  ```bash
41
41
  curl -sf -X POST "$INTERNAL_GATEWAY_BASE_URL/v1/integrations/telegram/setup" \
42
- -H "Authorization: Bearer $(cat ~/.vellum/http-token)" \
42
+ -H "Authorization: Bearer $GATEWAY_AUTH_TOKEN" \
43
43
  -H "Content-Type: application/json" \
44
44
  -d '{}'
45
45
  ```
@@ -101,7 +101,7 @@ Before reporting success, confirm the guardian binding was actually created. Che
101
101
 
102
102
  ```bash
103
103
  curl -sf "$INTERNAL_GATEWAY_BASE_URL/v1/integrations/guardian/status?channel=telegram" \
104
- -H "Authorization: Bearer $(cat ~/.vellum/http-token)"
104
+ -H "Authorization: Bearer $GATEWAY_AUTH_TOKEN"
105
105
  ```
106
106
 
107
107
  If the binding is absent and the user said they completed the verification:
@@ -120,7 +120,7 @@ Summarize what was done:
120
120
  - Guardian identity: {verified | not configured}
121
121
  - Guardian verification status: {verified via outbound flow | skipped}
122
122
  - Routing configuration validated
123
- - To re-check guardian status later, use: `curl -sf "$INTERNAL_GATEWAY_BASE_URL/v1/integrations/guardian/status?channel=telegram" -H "Authorization: Bearer $(cat ~/.vellum/http-token)"`
123
+ - To re-check guardian status later, use: `curl -sf "$INTERNAL_GATEWAY_BASE_URL/v1/integrations/guardian/status?channel=telegram" -H "Authorization: Bearer $GATEWAY_AUTH_TOKEN"`
124
124
 
125
125
  The gateway automatically detects credentials from the vault, reconciles the Telegram webhook registration, and begins accepting Telegram webhooks shortly. In single-assistant mode, routing is automatically configured — no manual environment variable configuration or webhook registration is needed. If the webhook secret changes later, the gateway's credential watcher will automatically re-register the webhook. If the ingress URL changes (e.g., tunnel restart), the assistant daemon triggers an immediate internal reconcile so the webhook re-registers automatically without a gateway restart.
126
126
 
@@ -11,7 +11,7 @@ You are helping your user manage trusted contacts and invite links for the Vellu
11
11
 
12
12
  - Use the injected `INTERNAL_GATEWAY_BASE_URL` for gateway API calls.
13
13
  - Use gateway control-plane routes only: this skill calls `/v1/ingress/*` and `/v1/integrations/telegram/config` on the gateway, never the daemon runtime port directly.
14
- - The bearer token is stored at `~/.vellum/http-token`. Read it with: `TOKEN=$(cat ~/.vellum/http-token)`.
14
+ - The bearer token is available as the `$GATEWAY_AUTH_TOKEN` environment variable.
15
15
 
16
16
  ## Concepts
17
17
 
@@ -29,9 +29,8 @@ You are helping your user manage trusted contacts and invite links for the Vellu
29
29
  Use this to show the user who currently has access, or to look up a specific contact.
30
30
 
31
31
  ```bash
32
- TOKEN=$(cat ~/.vellum/http-token)
33
32
  curl -s "$INTERNAL_GATEWAY_BASE_URL/v1/ingress/members" \
34
- -H "Authorization: Bearer $TOKEN"
33
+ -H "Authorization: Bearer $GATEWAY_AUTH_TOKEN"
35
34
  ```
36
35
 
37
36
  Optional query parameters for filtering:
@@ -42,7 +41,7 @@ Optional query parameters for filtering:
42
41
  Example with filters:
43
42
  ```bash
44
43
  curl -s "$INTERNAL_GATEWAY_BASE_URL/v1/ingress/members?sourceChannel=telegram&status=active" \
45
- -H "Authorization: Bearer $TOKEN"
44
+ -H "Authorization: Bearer $GATEWAY_AUTH_TOKEN"
46
45
  ```
47
46
 
48
47
  The response contains `{ ok: true, members: [...] }` where each member has:
@@ -65,10 +64,9 @@ Use this when the user wants to grant someone access to message the assistant. *
65
64
  Ask the user: *"I'll add [name/identifier] on [channel] as an allowed contact. Should I proceed?"*
66
65
 
67
66
  ```bash
68
- TOKEN=$(cat ~/.vellum/http-token)
69
67
  curl -s -X POST "$INTERNAL_GATEWAY_BASE_URL/v1/ingress/members" \
70
68
  -H "Content-Type: application/json" \
71
- -H "Authorization: Bearer $TOKEN" \
69
+ -H "Authorization: Bearer $GATEWAY_AUTH_TOKEN" \
72
70
  -d '{
73
71
  "sourceChannel": "<channel>",
74
72
  "externalUserId": "<user_id>",
@@ -97,9 +95,8 @@ Ask the user: *"I'll revoke access for [name/identifier]. They will no longer be
97
95
  First, list members to find the member's `id`, then revoke:
98
96
 
99
97
  ```bash
100
- TOKEN=$(cat ~/.vellum/http-token)
101
98
  curl -s -X DELETE "$INTERNAL_GATEWAY_BASE_URL/v1/ingress/members/<member_id>" \
102
- -H "Authorization: Bearer $TOKEN" \
99
+ -H "Authorization: Bearer $GATEWAY_AUTH_TOKEN" \
103
100
  -H "Content-Type: application/json" \
104
101
  -d '{"reason": "<optional reason>"}'
105
102
  ```
@@ -113,10 +110,9 @@ Use this when the user wants to explicitly block someone. Blocking is stronger t
113
110
  Ask the user: *"I'll block [name/identifier]. They will be permanently denied from messaging the assistant. Should I proceed?"*
114
111
 
115
112
  ```bash
116
- TOKEN=$(cat ~/.vellum/http-token)
117
113
  curl -s -X POST "$INTERNAL_GATEWAY_BASE_URL/v1/ingress/members/<member_id>/block" \
118
114
  -H "Content-Type: application/json" \
119
- -H "Authorization: Bearer $TOKEN" \
115
+ -H "Authorization: Bearer $GATEWAY_AUTH_TOKEN" \
120
116
  -d '{"reason": "<optional reason>"}'
121
117
  ```
122
118
 
@@ -127,11 +123,9 @@ Use this when the guardian wants to invite someone to message the assistant on T
127
123
  **Important**: The shell snippet below emits a `<vellum-sensitive-output>` directive containing the raw invite token. The tool executor automatically strips this directive and replaces the raw token with a placeholder so the LLM never sees it. The placeholder is resolved back to the real token in the final assistant reply.
128
124
 
129
125
  ```bash
130
- TOKEN=$(cat ~/.vellum/http-token)
131
-
132
126
  INVITE_JSON=$(curl -s -X POST "$INTERNAL_GATEWAY_BASE_URL/v1/ingress/invites" \
133
127
  -H "Content-Type: application/json" \
134
- -H "Authorization: Bearer $TOKEN" \
128
+ -H "Authorization: Bearer $GATEWAY_AUTH_TOKEN" \
135
129
  -d '{
136
130
  "sourceChannel": "telegram",
137
131
  "maxUses": 1,
@@ -160,7 +154,7 @@ fi
160
154
  # Prefer backend-provided canonical link when available.
161
155
  if [ -z "$INVITE_URL" ]; then
162
156
  BOT_CONFIG_JSON=$(curl -s "$INTERNAL_GATEWAY_BASE_URL/v1/integrations/telegram/config" \
163
- -H "Authorization: Bearer $TOKEN")
157
+ -H "Authorization: Bearer $GATEWAY_AUTH_TOKEN")
164
158
  BOT_USERNAME=$(printf '%s' "$BOT_CONFIG_JSON" | tr -d '\n' | sed -n 's/.*"botUsername"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p')
165
159
  if [ -z "$BOT_USERNAME" ]; then
166
160
  echo "error:no_share_url_or_bot_username"
@@ -201,9 +195,8 @@ If the Telegram bot username is not available (integration not set up), tell the
201
195
  Use this to show the guardian their active (and optionally all) invite links.
202
196
 
203
197
  ```bash
204
- TOKEN=$(cat ~/.vellum/http-token)
205
198
  curl -s "$INTERNAL_GATEWAY_BASE_URL/v1/ingress/invites?sourceChannel=telegram" \
206
- -H "Authorization: Bearer $TOKEN"
199
+ -H "Authorization: Bearer $GATEWAY_AUTH_TOKEN"
207
200
  ```
208
201
 
209
202
  Optional query parameters:
@@ -232,9 +225,8 @@ Ask the user: *"I'll revoke the invite link [note or ID]. It will no longer be u
232
225
  First, list invites to find the invite's `id`, then revoke:
233
226
 
234
227
  ```bash
235
- TOKEN=$(cat ~/.vellum/http-token)
236
228
  curl -s -X DELETE "$INTERNAL_GATEWAY_BASE_URL/v1/ingress/invites/<invite_id>" \
237
- -H "Authorization: Bearer $TOKEN"
229
+ -H "Authorization: Bearer $GATEWAY_AUTH_TOKEN"
238
230
  ```
239
231
 
240
232
  Replace `<invite_id>` with the invite's `id` from the list response.
@@ -246,11 +238,9 @@ Use this when the guardian wants to authorize a specific phone number to call th
246
238
  **Important**: The response includes a `voiceCode` field that is only returned at creation time and cannot be retrieved later. Extract and present it clearly.
247
239
 
248
240
  ```bash
249
- TOKEN=$(cat ~/.vellum/http-token)
250
-
251
241
  INVITE_JSON=$(curl -s -X POST "$INTERNAL_GATEWAY_BASE_URL/v1/ingress/invites" \
252
242
  -H "Content-Type: application/json" \
253
- -H "Authorization: Bearer $TOKEN" \
243
+ -H "Authorization: Bearer $GATEWAY_AUTH_TOKEN" \
254
244
  -d '{
255
245
  "sourceChannel": "voice",
256
246
  "expectedExternalUserId": "<phone_number_E164>",
@@ -303,9 +293,8 @@ If the user provides a phone number without the `+` country code prefix, ask the
303
293
  Use this to show the guardian their active voice invites.
304
294
 
305
295
  ```bash
306
- TOKEN=$(cat ~/.vellum/http-token)
307
296
  curl -s "$INTERNAL_GATEWAY_BASE_URL/v1/ingress/invites?sourceChannel=voice" \
308
- -H "Authorization: Bearer $TOKEN"
297
+ -H "Authorization: Bearer $GATEWAY_AUTH_TOKEN"
309
298
  ```
310
299
 
311
300
  Optional query parameters:
@@ -327,9 +316,8 @@ Ask the user: *"I'll revoke the voice invite for [phone number or note]. The cod
327
316
  First, list voice invites to find the invite's `id`, then revoke:
328
317
 
329
318
  ```bash
330
- TOKEN=$(cat ~/.vellum/http-token)
331
319
  curl -s -X DELETE "$INTERNAL_GATEWAY_BASE_URL/v1/ingress/invites/<invite_id>" \
332
- -H "Authorization: Bearer $TOKEN"
320
+ -H "Authorization: Bearer $GATEWAY_AUTH_TOKEN"
333
321
  ```
334
322
 
335
323
  Replace `<invite_id>` with the invite's `id` from the list response. The same revoke endpoint is used for both Telegram and voice invites.
@@ -11,17 +11,16 @@ You are helping your user configure Twilio for voice calls and SMS messaging. Tw
11
11
  ## Quick Start
12
12
 
13
13
  ```bash
14
- TOKEN=$(cat ~/.vellum/http-token)
15
14
  # 1. Check current status
16
15
  curl -s "$INTERNAL_GATEWAY_BASE_URL/v1/integrations/twilio/config" \
17
- -H "Authorization: Bearer $TOKEN"
16
+ -H "Authorization: Bearer $GATEWAY_AUTH_TOKEN"
18
17
  # 2. Store credentials (after collecting via credential_store prompt)
19
18
  curl -s -X POST "$INTERNAL_GATEWAY_BASE_URL/v1/integrations/twilio/credentials" \
20
- -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
19
+ -H "Authorization: Bearer $GATEWAY_AUTH_TOKEN" -H "Content-Type: application/json" \
21
20
  -d '{"accountSid":"ACxxx","authToken":"xxx"}'
22
21
  # 3. Provision or assign a number
23
22
  curl -s -X POST "$INTERNAL_GATEWAY_BASE_URL/v1/integrations/twilio/numbers/provision" \
24
- -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
23
+ -H "Authorization: Bearer $GATEWAY_AUTH_TOKEN" -H "Content-Type: application/json" \
25
24
  -d '{"country":"US","areaCode":"415"}'
26
25
  ```
27
26
 
@@ -67,9 +66,8 @@ All HTTP examples below include the optional `assistantId` query parameter in as
67
66
  First, check whether Twilio is already configured:
68
67
 
69
68
  ```bash
70
- TOKEN=$(cat ~/.vellum/http-token)
71
69
  curl -s "$INTERNAL_GATEWAY_BASE_URL/v1/integrations/twilio/config" \
72
- -H "Authorization: Bearer $TOKEN"
70
+ -H "Authorization: Bearer $GATEWAY_AUTH_TOKEN"
73
71
  ```
74
72
 
75
73
  The response includes:
@@ -96,9 +94,8 @@ If credentials are not yet stored, guide the user through Twilio account setup:
96
94
  After both credentials are collected, retrieve them from secure storage and send them to the gateway:
97
95
 
98
96
  ```bash
99
- TOKEN=$(cat ~/.vellum/http-token)
100
97
  curl -s -X POST "$INTERNAL_GATEWAY_BASE_URL/v1/integrations/twilio/credentials" \
101
- -H "Authorization: Bearer $TOKEN" \
98
+ -H "Authorization: Bearer $GATEWAY_AUTH_TOKEN" \
102
99
  -H "Content-Type: application/json" \
103
100
  -d '{"accountSid":"<value from credential_store for twilio/account_sid>","authToken":"<value from credential_store for twilio/auth_token>"}'
104
101
  ```
@@ -116,9 +113,8 @@ The assistant needs a phone number to make calls and send SMS. There are two pat
116
113
  If the user wants to buy a new number through Twilio:
117
114
 
118
115
  ```bash
119
- TOKEN=$(cat ~/.vellum/http-token)
120
116
  curl -s -X POST "$INTERNAL_GATEWAY_BASE_URL/v1/integrations/twilio/numbers/provision" \
121
- -H "Authorization: Bearer $TOKEN" \
117
+ -H "Authorization: Bearer $GATEWAY_AUTH_TOKEN" \
122
118
  -H "Content-Type: application/json" \
123
119
  -d '{"country":"US","areaCode":"415"}'
124
120
  ```
@@ -143,9 +139,8 @@ If ingress is not yet configured, webhook setup is skipped gracefully — the nu
143
139
  If the user already has a Twilio phone number, first list available numbers:
144
140
 
145
141
  ```bash
146
- TOKEN=$(cat ~/.vellum/http-token)
147
142
  curl -s "$INTERNAL_GATEWAY_BASE_URL/v1/integrations/twilio/numbers" \
148
- -H "Authorization: Bearer $TOKEN"
143
+ -H "Authorization: Bearer $GATEWAY_AUTH_TOKEN"
149
144
  ```
150
145
 
151
146
  The response includes a `numbers` array with each number's `phoneNumber`, `friendlyName`, and `capabilities` (voice, SMS). Present these to the user and let them choose.
@@ -153,9 +148,8 @@ The response includes a `numbers` array with each number's `phoneNumber`, `frien
153
148
  Then assign the chosen number:
154
149
 
155
150
  ```bash
156
- TOKEN=$(cat ~/.vellum/http-token)
157
151
  curl -s -X POST "$INTERNAL_GATEWAY_BASE_URL/v1/integrations/twilio/numbers/assign" \
158
- -H "Authorization: Bearer $TOKEN" \
152
+ -H "Authorization: Bearer $GATEWAY_AUTH_TOKEN" \
159
153
  -H "Content-Type: application/json" \
160
154
  -d '{"phoneNumber":"+14155551234"}'
161
155
  ```
@@ -173,9 +167,8 @@ credential_store action=store service=twilio field=phone_number value=+141555512
173
167
  Then assign it through the gateway:
174
168
 
175
169
  ```bash
176
- TOKEN=$(cat ~/.vellum/http-token)
177
170
  curl -s -X POST "$INTERNAL_GATEWAY_BASE_URL/v1/integrations/twilio/numbers/assign" \
178
- -H "Authorization: Bearer $TOKEN" \
171
+ -H "Authorization: Bearer $GATEWAY_AUTH_TOKEN" \
179
172
  -H "Content-Type: application/json" \
180
173
  -d '{"phoneNumber":"+14155551234"}'
181
174
  ```
@@ -211,9 +204,8 @@ Webhook URLs are automatically configured on the Twilio phone number when provis
211
204
  After configuration, verify by checking the config endpoint again.
212
205
 
213
206
  ```bash
214
- TOKEN=$(cat ~/.vellum/http-token)
215
207
  curl -s "$INTERNAL_GATEWAY_BASE_URL/v1/integrations/twilio/config" \
216
- -H "Authorization: Bearer $TOKEN"
208
+ -H "Authorization: Bearer $GATEWAY_AUTH_TOKEN"
217
209
  ```
218
210
 
219
211
  Confirm:
@@ -251,13 +243,12 @@ After the guardian-verify-setup skill completes verification for a channel, load
251
243
  To re-check guardian status later, query the channel(s) that were verified:
252
244
 
253
245
  ```bash
254
- TOKEN=$(cat ~/.vellum/http-token)
255
246
  # Check SMS guardian status
256
247
  curl -s "$INTERNAL_GATEWAY_BASE_URL/v1/integrations/guardian/status?channel=sms" \
257
- -H "Authorization: Bearer $TOKEN"
248
+ -H "Authorization: Bearer $GATEWAY_AUTH_TOKEN"
258
249
  # Check voice guardian status
259
250
  curl -s "$INTERNAL_GATEWAY_BASE_URL/v1/integrations/guardian/status?channel=voice" \
260
- -H "Authorization: Bearer $TOKEN"
251
+ -H "Authorization: Bearer $GATEWAY_AUTH_TOKEN"
261
252
  ```
262
253
 
263
254
  Check the status for whichever channel(s) the user actually verified (SMS, voice, or both). Report the guardian verification result per channel: **"Guardian identity — SMS: {verified | not configured}, Voice: {verified | not configured}."**
@@ -280,9 +271,8 @@ SMS is available automatically once Twilio is configured — no additional featu
280
271
  If the user wants to disconnect Twilio:
281
272
 
282
273
  ```bash
283
- TOKEN=$(cat ~/.vellum/http-token)
284
274
  curl -s -X DELETE "$INTERNAL_GATEWAY_BASE_URL/v1/integrations/twilio/credentials" \
285
- -H "Authorization: Bearer $TOKEN"
275
+ -H "Authorization: Bearer $GATEWAY_AUTH_TOKEN"
286
276
  ```
287
277
 
288
278
  This removes the stored Account SID and Auth Token. Phone number assignments are preserved. Voice calls and SMS will stop working until credentials are reconfigured.