@vellumai/assistant 0.4.9 → 0.4.11
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 +24 -0
- package/Dockerfile +1 -1
- package/README.md +16 -9
- package/package.json +1 -1
- package/src/__tests__/account-registry.test.ts +1 -0
- package/src/__tests__/actor-token-service.test.ts +1 -0
- package/src/__tests__/app-builder-tool-scripts.test.ts +1 -0
- package/src/__tests__/asset-materialize-tool.test.ts +7 -0
- package/src/__tests__/asset-search-tool.test.ts +7 -0
- package/src/__tests__/browser-fill-credential.test.ts +1 -0
- package/src/__tests__/call-start-guardian-guard.test.ts +1 -0
- package/src/__tests__/channel-approval-routes.test.ts +29 -0
- package/src/__tests__/channel-guardian.test.ts +2143 -1546
- package/src/__tests__/channel-retry-sweep.test.ts +169 -14
- package/src/__tests__/claude-code-tool-profiles.test.ts +1 -0
- package/src/__tests__/computer-use-tools.test.ts +1 -0
- package/src/__tests__/contacts-tools.test.ts +1 -0
- package/src/__tests__/conversation-attention-telegram.test.ts +1 -0
- package/src/__tests__/credential-policy-validate.test.ts +97 -0
- package/src/__tests__/credential-security-e2e.test.ts +1 -0
- package/src/__tests__/credential-vault-unit.test.ts +1 -0
- package/src/__tests__/credential-vault.test.ts +1 -0
- package/src/__tests__/delete-managed-skill-tool.test.ts +1 -0
- package/src/__tests__/file-edit-tool.test.ts +1 -0
- package/src/__tests__/file-read-tool.test.ts +1 -0
- package/src/__tests__/file-write-tool.test.ts +1 -0
- package/src/__tests__/followup-tools.test.ts +1 -0
- package/src/__tests__/gateway-only-guard.test.ts +1 -1
- package/src/__tests__/guardian-control-plane-policy.test.ts +5 -4
- package/src/__tests__/guardian-grant-minting.test.ts +3 -0
- package/src/__tests__/guardian-principal-id-roundtrip.test.ts +4 -3
- package/src/__tests__/guardian-routing-state.test.ts +8 -0
- package/src/__tests__/headless-browser-interactions.test.ts +1 -0
- package/src/__tests__/headless-browser-navigate.test.ts +1 -0
- package/src/__tests__/headless-browser-read-tools.test.ts +1 -0
- package/src/__tests__/headless-browser-snapshot.test.ts +1 -0
- package/src/__tests__/host-file-edit-tool.test.ts +1 -0
- package/src/__tests__/host-file-read-tool.test.ts +1 -0
- package/src/__tests__/host-file-write-tool.test.ts +1 -0
- package/src/__tests__/host-shell-tool.test.ts +1 -0
- package/src/__tests__/lifecycle-docs-guard.test.ts +207 -0
- package/src/__tests__/managed-skill-lifecycle.test.ts +1 -0
- package/src/__tests__/media-reuse-story.e2e.test.ts +8 -0
- package/src/__tests__/messaging-send-tool.test.ts +1 -0
- package/src/__tests__/playbook-execution.test.ts +1 -0
- package/src/__tests__/playbook-tools.test.ts +1 -0
- package/src/__tests__/relay-server.test.ts +4 -0
- package/src/__tests__/scaffold-managed-skill-tool.test.ts +1 -0
- package/src/__tests__/schedule-tools.test.ts +1 -0
- package/src/__tests__/secret-onetime-send.test.ts +4 -0
- package/src/__tests__/secret-scanner-executor.test.ts +2 -0
- package/src/__tests__/send-notification-tool.test.ts +2 -0
- package/src/__tests__/shell-credential-ref.test.ts +1 -0
- package/src/__tests__/shell-tool-proxy-mode.test.ts +1 -0
- package/src/__tests__/skill-load-feature-flag.test.ts +1 -0
- package/src/__tests__/skill-load-tool.test.ts +1 -0
- package/src/__tests__/skill-script-runner-host.test.ts +1 -0
- package/src/__tests__/skill-script-runner-sandbox.test.ts +1 -0
- package/src/__tests__/skill-script-runner.test.ts +1 -0
- package/src/__tests__/skill-tool-factory.test.ts +1 -0
- package/src/__tests__/subagent-tools.test.ts +1 -1
- package/src/__tests__/swarm-recursion.test.ts +1 -0
- package/src/__tests__/swarm-session-integration.test.ts +1 -0
- package/src/__tests__/swarm-tool.test.ts +1 -0
- package/src/__tests__/task-management-tools.test.ts +1 -0
- package/src/__tests__/task-tools.test.ts +1 -0
- package/src/__tests__/terminal-tools.test.ts +1 -0
- package/src/__tests__/tool-approval-handler.test.ts +2 -2
- package/src/__tests__/tool-execution-abort-cleanup.test.ts +1 -0
- package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +1 -0
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +2 -0
- package/src/__tests__/tool-executor-shell-integration.test.ts +1 -0
- package/src/__tests__/tool-executor.test.ts +1 -0
- package/src/__tests__/trust-context-guards.test.ts +218 -0
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +6 -0
- package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +6 -0
- package/src/__tests__/trusted-contact-multichannel.test.ts +1 -0
- package/src/__tests__/trusted-contact-verification.test.ts +1 -0
- package/src/__tests__/view-image-tool.test.ts +1 -0
- package/src/calls/guardian-dispatch.ts +4 -4
- package/src/cli/mcp.ts +183 -3
- package/src/config/bundled-skills/agentmail/SKILL.md +4 -4
- package/src/config/bundled-skills/google-oauth-setup/SKILL.md +1 -0
- package/src/config/bundled-skills/phone-calls/SKILL.md +17 -119
- package/src/config/system-prompt.ts +4 -2
- package/src/config/vellum-skills/twilio-setup/SKILL.md +1 -1
- package/src/daemon/computer-use-session.ts +1 -0
- package/src/daemon/session-agent-loop.ts +1 -1
- package/src/daemon/session-memory.ts +2 -2
- package/src/daemon/session-runtime-assembly.ts +2 -2
- package/src/daemon/session-tool-setup.ts +1 -1
- package/src/mcp/client.ts +55 -6
- package/src/mcp/manager.ts +9 -0
- package/src/mcp/mcp-oauth-provider.ts +347 -0
- package/src/memory/channel-delivery-store.ts +1 -0
- package/src/memory/db-init.ts +4 -0
- package/src/memory/delivery-status.ts +43 -0
- package/src/memory/guardian-bindings.ts +3 -3
- package/src/memory/migrations/127-guardian-principal-id-not-null.ts +108 -0
- package/src/memory/migrations/index.ts +1 -0
- package/src/memory/migrations/registry.ts +6 -0
- package/src/memory/schema.ts +1 -1
- package/src/runtime/actor-trust-resolver.ts +13 -4
- package/src/runtime/channel-retry-sweep.ts +31 -14
- package/src/runtime/guardian-context-resolver.ts +25 -64
- package/src/runtime/guardian-outbound-actions.ts +399 -108
- package/src/runtime/guardian-vellum-migration.ts +1 -23
- package/src/runtime/guardian-verification-templates.ts +66 -30
- package/src/runtime/local-actor-identity.ts +4 -6
- package/src/runtime/middleware/actor-token.ts +2 -8
- package/src/runtime/routes/channel-route-shared.ts +0 -1
- package/src/runtime/routes/inbound-message-handler.ts +3 -4
- package/src/runtime/tool-grant-request-helper.ts +1 -1
- package/src/tools/credentials/policy-validate.ts +22 -0
- package/src/tools/guardian-control-plane-policy.ts +2 -2
- package/src/tools/types.ts +1 -1
|
@@ -6,26 +6,7 @@ metadata: {"vellum": {"emoji": "📞", "requires": {"config": ["calls.enabled"]}
|
|
|
6
6
|
includes: ["public-ingress"]
|
|
7
7
|
---
|
|
8
8
|
|
|
9
|
-
You are helping the user set up and manage phone calls via Twilio. This skill covers the
|
|
10
|
-
|
|
11
|
-
## Prerequisites — Shared Twilio Setup
|
|
12
|
-
|
|
13
|
-
Twilio credentials and phone number configuration are shared between voice calls and SMS messaging. If Twilio is not yet configured, install and load the **twilio-setup** skill first:
|
|
14
|
-
|
|
15
|
-
- Call `vellum_skills_catalog` with `action: "install"`, `skill_id: "twilio-setup"`, and `overwrite: true` (so it succeeds even if already installed).
|
|
16
|
-
- Then call `skill_load` with `skill: "twilio-setup"`.
|
|
17
|
-
|
|
18
|
-
The twilio-setup skill handles credential storage, phone number provisioning/assignment, and public ingress setup. Once complete, return here to enable the calls feature and start making calls.
|
|
19
|
-
|
|
20
|
-
Check if Twilio is already configured:
|
|
21
|
-
|
|
22
|
-
```bash
|
|
23
|
-
TOKEN=$(cat ~/.vellum/http-token)
|
|
24
|
-
curl -s "$INTERNAL_GATEWAY_BASE_URL/v1/integrations/twilio/config" \
|
|
25
|
-
-H "Authorization: Bearer $TOKEN"
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
If `hasCredentials` is `true` and `phoneNumber` is set, skip directly to **Step 5: Enable Calls** below.
|
|
9
|
+
You are helping the user set up and manage phone calls via Twilio. This skill covers enabling the calls feature, placing outbound calls, receiving inbound calls, and interacting with live calls. Twilio credential storage, phone number provisioning, and public ingress are handled by the **twilio-setup** skill.
|
|
29
10
|
|
|
30
11
|
## Overview
|
|
31
12
|
|
|
@@ -39,7 +20,7 @@ When a call is placed:
|
|
|
39
20
|
2. Twilio connects to the gateway's voice webhook, which returns TwiML
|
|
40
21
|
3. Twilio opens a ConversationRelay WebSocket for real-time voice streaming
|
|
41
22
|
4. An LLM-driven orchestrator manages the conversation — receiving caller speech (transcribed by Deepgram), generating responses via Claude, and streaming text back for TTS playback
|
|
42
|
-
5. The transcript is
|
|
23
|
+
5. The full transcript is stored in the database for later retrieval
|
|
43
24
|
|
|
44
25
|
### Inbound calls
|
|
45
26
|
|
|
@@ -61,9 +42,9 @@ You can keep using Twilio only — no changes needed. Enabling ElevenLabs can im
|
|
|
61
42
|
|
|
62
43
|
The user's assistant gets its own personal phone number through Twilio. All implicit calls (without an explicit mode) always use this assistant number. Optionally, users can call from their own phone number if it's authorized with the Twilio account — this must be explicitly requested per call via `caller_identity_mode="user_number"`.
|
|
63
44
|
|
|
64
|
-
## Step 1:
|
|
45
|
+
## Step 1: Verify Twilio Setup
|
|
65
46
|
|
|
66
|
-
|
|
47
|
+
Check whether Twilio credentials, phone number, and public ingress are already configured:
|
|
67
48
|
|
|
68
49
|
```bash
|
|
69
50
|
TOKEN=$(cat ~/.vellum/http-token)
|
|
@@ -71,73 +52,20 @@ curl -s "$INTERNAL_GATEWAY_BASE_URL/v1/integrations/twilio/config" \
|
|
|
71
52
|
-H "Authorization: Bearer $TOKEN"
|
|
72
53
|
```
|
|
73
54
|
|
|
74
|
-
Also check calls feature status:
|
|
75
|
-
|
|
76
55
|
```bash
|
|
77
56
|
vellum config get calls.enabled
|
|
78
57
|
```
|
|
79
58
|
|
|
80
|
-
If
|
|
81
|
-
|
|
82
|
-
## Step 2: Create a Twilio Account
|
|
83
|
-
|
|
84
|
-
If the user doesn't have a Twilio account yet, guide them through setup:
|
|
85
|
-
|
|
86
|
-
1. Tell the user: **"You'll need a Twilio account to make phone calls. Sign up at https://www.twilio.com/try-twilio — it's free to start and includes trial credit."**
|
|
87
|
-
2. Once they have an account, they need two pieces of information:
|
|
88
|
-
- **Account SID** — found on the Twilio Console dashboard at https://console.twilio.com
|
|
89
|
-
- **Auth Token** — found on the same dashboard (click "Show" to reveal it)
|
|
90
|
-
|
|
91
|
-
Tell the user: **"The assistant will get its own personal phone number through Twilio — the number that shows up on caller ID when calls are placed."**
|
|
92
|
-
|
|
93
|
-
## Step 3: Store Twilio Credentials
|
|
94
|
-
|
|
95
|
-
**IMPORTANT — Secure credential collection only:** Never use credentials pasted in plaintext chat. Always collect credentials through the secure credential prompt flow:
|
|
96
|
-
|
|
97
|
-
- Call `credential_store` with `action: "prompt"`, `service: "twilio"`, `field: "account_sid"`, `label: "Twilio Account SID"`, `description: "Enter your Account SID from the Twilio Console dashboard"`, and `placeholder: "ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"`.
|
|
98
|
-
- Call `credential_store` with `action: "prompt"`, `service: "twilio"`, `field: "auth_token"`, `label: "Twilio Auth Token"`, `description: "Enter your Auth Token from the Twilio Console dashboard"`, and `placeholder: "your_auth_token"`.
|
|
99
|
-
|
|
100
|
-
After both credentials are collected, send them to the gateway:
|
|
101
|
-
|
|
102
|
-
```bash
|
|
103
|
-
TOKEN=$(cat ~/.vellum/http-token)
|
|
104
|
-
curl -s -X POST "$INTERNAL_GATEWAY_BASE_URL/v1/integrations/twilio/credentials" \
|
|
105
|
-
-H "Authorization: Bearer $TOKEN" \
|
|
106
|
-
-H "Content-Type: application/json" \
|
|
107
|
-
-d '{"accountSid":"<value from credential_store for twilio/account_sid>","authToken":"<value from credential_store for twilio/auth_token>"}'
|
|
108
|
-
```
|
|
59
|
+
If `hasCredentials` is `true`, `phoneNumber` is set, and `calls.enabled` is `true`, skip to the **Making Outbound Calls** section.
|
|
109
60
|
|
|
110
|
-
|
|
61
|
+
If Twilio is not yet configured, load the **twilio-setup** skill — it handles credential storage, phone number provisioning, and public ingress setup:
|
|
111
62
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
## Step 4: Set Up Public Ingress
|
|
115
|
-
|
|
116
|
-
Twilio needs a publicly reachable URL to send voice webhooks and establish the ConversationRelay WebSocket. The **public-ingress** skill handles this via ngrok.
|
|
117
|
-
|
|
118
|
-
Check if ingress is already configured:
|
|
119
|
-
|
|
120
|
-
```bash
|
|
121
|
-
vellum config get ingress.publicBaseUrl
|
|
122
|
-
vellum config get ingress.enabled
|
|
123
|
-
```
|
|
124
|
-
|
|
125
|
-
If not configured, load and run the public-ingress skill:
|
|
126
|
-
|
|
127
|
-
```
|
|
128
|
-
skill_load skill=public-ingress
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
Follow the public-ingress skill's instructions to set up the ngrok tunnel. Once complete, the gateway will be reachable at the configured `ingress.publicBaseUrl`.
|
|
132
|
-
|
|
133
|
-
**Twilio needs these webhook endpoints (handled automatically by the gateway):**
|
|
134
|
-
- Voice webhook: `{publicBaseUrl}/webhooks/twilio/voice`
|
|
135
|
-
- Status callback: `{publicBaseUrl}/webhooks/twilio/status`
|
|
136
|
-
- ConversationRelay WebSocket: `{publicBaseUrl}/webhooks/twilio/relay` (wss://)
|
|
63
|
+
- Call `vellum_skills_catalog` with `action: "install"`, `skill_id: "twilio-setup"`, and `overwrite: true`.
|
|
64
|
+
- Then call `skill_load` with `skill: "twilio-setup"`.
|
|
137
65
|
|
|
138
|
-
|
|
66
|
+
Once twilio-setup completes, return here to enable calls.
|
|
139
67
|
|
|
140
|
-
## Step
|
|
68
|
+
## Step 2: Enable Calls
|
|
141
69
|
|
|
142
70
|
Enable the calls feature:
|
|
143
71
|
|
|
@@ -150,7 +78,7 @@ Verify:
|
|
|
150
78
|
vellum config get calls.enabled
|
|
151
79
|
```
|
|
152
80
|
|
|
153
|
-
## Step
|
|
81
|
+
## Step 3: Verify Setup (Test Call)
|
|
154
82
|
|
|
155
83
|
Before making real calls, offer a quick verification:
|
|
156
84
|
|
|
@@ -176,7 +104,7 @@ If the user wants a specific call to appear as coming from their own phone numbe
|
|
|
176
104
|
credential_store action=store service=twilio field=user_phone_number value=+14155559999
|
|
177
105
|
```
|
|
178
106
|
|
|
179
|
-
**To use it for a specific call**, pass `caller_identity_mode: 'user_number'` when calling `call_start` — see the Making Calls section for examples. User-number mode cannot be set as a global default; it must be requested explicitly per call.
|
|
107
|
+
**To use it for a specific call**, pass `caller_identity_mode: 'user_number'` when calling `call_start` — see the Making Outbound Calls section for examples. User-number mode cannot be set as a global default; it must be requested explicitly per call.
|
|
180
108
|
|
|
181
109
|
### Configuration reference
|
|
182
110
|
|
|
@@ -351,7 +279,7 @@ Once Twilio is configured and the assistant has a phone number, inbound calls wo
|
|
|
351
279
|
3. The LLM-driven orchestrator answers in receptionist mode — greeting the caller warmly and asking how it can help
|
|
352
280
|
4. The conversation proceeds naturally, with ASK_GUARDIAN dispatches to consult the user when needed
|
|
353
281
|
|
|
354
|
-
No additional configuration is needed beyond
|
|
282
|
+
No additional configuration is needed beyond Twilio setup and `calls.enabled` being `true`. As long as the phone number has been provisioned/assigned, inbound calls are handled automatically.
|
|
355
283
|
|
|
356
284
|
### Guardian voice verification for inbound calls
|
|
357
285
|
|
|
@@ -361,31 +289,9 @@ Once a guardian binding exists for the voice channel, inbound callers may be pro
|
|
|
361
289
|
|
|
362
290
|
This feature is separate from the outbound DTMF callee verification. It uses the channel guardian verification system rather than the per-call verification config.
|
|
363
291
|
|
|
364
|
-
## Live Call
|
|
292
|
+
## Interacting with a Live Call
|
|
365
293
|
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
By default, always show the live transcript of the call as it happens. When a call is in progress:
|
|
369
|
-
|
|
370
|
-
1. After placing the call with `call_start`, immediately begin polling with `call_status` to track the call state
|
|
371
|
-
2. The system fires transcript notifications as the conversation unfolds — both caller speech and assistant responses appear in real time in the conversation thread
|
|
372
|
-
3. Present each transcript entry clearly as it arrives:
|
|
373
|
-
|
|
374
|
-
```
|
|
375
|
-
📞 Call in progress...
|
|
376
|
-
|
|
377
|
-
🗣️ Assistant: "Hi, I'm calling on behalf of John to make a dinner reservation for tonight."
|
|
378
|
-
👤 Caller: "Sure, what time would you like?"
|
|
379
|
-
🗣️ Assistant: "We'd like a table for two at 7pm, please."
|
|
380
|
-
👤 Caller: "Let me check... yes, we have availability at 7pm."
|
|
381
|
-
🗣️ Assistant: "Wonderful! The reservation would be under John Smith."
|
|
382
|
-
```
|
|
383
|
-
|
|
384
|
-
4. Continue monitoring until the call completes or fails
|
|
385
|
-
|
|
386
|
-
### Interacting with a live call
|
|
387
|
-
|
|
388
|
-
During an active call, the user can interact with the AI voice agent via the HTTP API endpoints:
|
|
294
|
+
During an active call, the user can interact with the AI voice agent via the HTTP API endpoints. After placing a call with `call_start`, use `call_status` to poll the call state.
|
|
389
295
|
|
|
390
296
|
#### Answering questions
|
|
391
297
|
|
|
@@ -424,7 +330,7 @@ When there is **no pending question** but the call is still active, the user can
|
|
|
424
330
|
|
|
425
331
|
The instruction is injected into the AI voice agent's conversation context as high-priority input, and the agent adjusts its behavior accordingly.
|
|
426
332
|
|
|
427
|
-
**Note:**
|
|
333
|
+
**Note:** Steering is done via the HTTP API, not the desktop chat thread. The desktop thread only receives pointer/status messages about the call.
|
|
428
334
|
|
|
429
335
|
### Call status values
|
|
430
336
|
|
|
@@ -585,18 +491,10 @@ vellum config set calls.disclosure.text "Just so you know, this is an assistant
|
|
|
585
491
|
vellum config set calls.userConsultTimeoutSeconds 300
|
|
586
492
|
```
|
|
587
493
|
|
|
588
|
-
## Accepted Regressions
|
|
589
|
-
|
|
590
|
-
The following behavioral changes were introduced with the cross-channel guardian architecture (voice-cross-guardian):
|
|
591
|
-
|
|
592
|
-
- **No more mid-call steering via desktop chat.** The call bridge that routed desktop chat messages to the active call has been removed. The desktop chat thread only receives pointer/status messages about the call. To steer a call, use the HTTP API endpoints directly (`POST /v1/calls/:id/instruction`).
|
|
593
|
-
- **No live transcript mirror in the initiating chat.** The initiating desktop conversation no longer receives a real-time mirror of the call transcript. The initiating chat only gets pointer/status messages (call started, call ended, question asked, etc.).
|
|
594
|
-
- **Guardian questions are dispatched cross-channel.** Rather than appearing only in the initiating desktop thread, ASK_GUARDIAN questions are now dispatched to all configured guardian channels (mac desktop, Telegram, SMS) simultaneously. The first channel to respond wins.
|
|
595
|
-
|
|
596
494
|
## Troubleshooting
|
|
597
495
|
|
|
598
496
|
### "Twilio credentials not configured"
|
|
599
|
-
|
|
497
|
+
Load the `twilio-setup` skill to store your Account SID and Auth Token.
|
|
600
498
|
|
|
601
499
|
### "Calls feature is disabled"
|
|
602
500
|
Run `vellum config set calls.enabled true`.
|
|
@@ -410,7 +410,7 @@ function buildToolPermissionSection(): string {
|
|
|
410
410
|
'',
|
|
411
411
|
'### Always-Available Tools (No Approval Required)',
|
|
412
412
|
'',
|
|
413
|
-
'- **file_read** on your workspace directory — You can freely read any file under your `.vellum` workspace at any time. Use this proactively to check files, load context, and inform your responses without asking.',
|
|
413
|
+
'- **file_read** on your workspace directory — You can freely read any file under your `.vellum` workspace at any time. Use this proactively to check files, load context, and inform your responses without asking. **Always use `file_read` for workspace files (IDENTITY.md, USER.md, SOUL.md, etc.), never `host_file_read`.**',
|
|
414
414
|
'- **web_search** — You can search the web at any time without approval. Use this to look up documentation, current information, or anything you need.',
|
|
415
415
|
].join('\n');
|
|
416
416
|
}
|
|
@@ -688,7 +688,7 @@ function buildConfigSection(): string {
|
|
|
688
688
|
return [
|
|
689
689
|
'## Configuration',
|
|
690
690
|
`- **Active model**: \`${config.model}\` (provider: ${config.provider})`,
|
|
691
|
-
`${configPreamble}
|
|
691
|
+
`${configPreamble} **Always use \`file_read\` and \`file_edit\` (not \`host_file_read\` / \`host_file_edit\`) for these files** — they are inside your sandbox working directory and do not require host access or user approval:`,
|
|
692
692
|
'',
|
|
693
693
|
'- `IDENTITY.md` — Your name, nature, personality, and emoji. Updated during the first-run ritual.',
|
|
694
694
|
'- `SOUL.md` — Core principles, personality, and evolution guidance. Your behavioral foundation.',
|
|
@@ -723,6 +723,8 @@ function buildConfigSection(): string {
|
|
|
723
723
|
'- They rename you or change your role',
|
|
724
724
|
'- Your avatar appearance changes (update the `## Avatar` section with a description of the new look)',
|
|
725
725
|
'',
|
|
726
|
+
'When reading or updating workspace files, always use the sandbox tools (`file_read`, `file_edit`). Never use `host_file_read` or `host_file_edit` for workspace files — those are for host-only resources outside your workspace.',
|
|
727
|
+
'',
|
|
726
728
|
'When updating, read the file first, then make a targeted edit. Include all useful information, but don\'t bloat the files over time',
|
|
727
729
|
].join('\n');
|
|
728
730
|
}
|
|
@@ -46,7 +46,7 @@ In a multi-assistant environment (multiple assistants sharing the same daemon),
|
|
|
46
46
|
- `DELETE /v1/integrations/twilio/credentials` — Removes the globally stored Account SID and Auth Token. This affects all assistants.
|
|
47
47
|
|
|
48
48
|
**Assistant-scoped actions** (use `assistantId` query parameter to scope phone number configuration per assistant):
|
|
49
|
-
- `GET /v1/integrations/twilio/config` — Returns the phone number assigned to the specified assistant
|
|
49
|
+
- `GET /v1/integrations/twilio/config` — Returns the phone number assigned to the specified assistant.
|
|
50
50
|
- `POST /v1/integrations/twilio/numbers/assign` — Assigns a phone number to a specific assistant via the per-assistant mapping.
|
|
51
51
|
- `POST /v1/integrations/twilio/numbers/provision` — Provisions a new number and assigns it to the specified assistant.
|
|
52
52
|
- `GET /v1/integrations/twilio/numbers` — Lists all phone numbers on the shared Twilio account (uses global credentials).
|
|
@@ -499,6 +499,7 @@ export class ComputerUseSession {
|
|
|
499
499
|
workingDir: getSandboxWorkingDir(),
|
|
500
500
|
sessionId: this.sessionId,
|
|
501
501
|
conversationId: this.sessionId,
|
|
502
|
+
guardianTrustClass: 'guardian',
|
|
502
503
|
proxyToolResolver: proxyResolver,
|
|
503
504
|
allowedToolNames,
|
|
504
505
|
requestSecret: async (params) => {
|
|
@@ -299,7 +299,7 @@ export async function runAgentLoopImpl(
|
|
|
299
299
|
conflictGate: ctx.conflictGate,
|
|
300
300
|
scopeId: ctx.memoryPolicy.scopeId,
|
|
301
301
|
includeDefaultFallback: ctx.memoryPolicy.includeDefaultFallback,
|
|
302
|
-
guardianTrustClass: ctx.guardianContext?.trustClass,
|
|
302
|
+
guardianTrustClass: ctx.guardianContext?.trustClass ?? 'guardian',
|
|
303
303
|
isInteractive: options?.isInteractive ?? (!ctx.hasNoClient && !ctx.headlessLock),
|
|
304
304
|
},
|
|
305
305
|
content,
|
|
@@ -34,7 +34,7 @@ export interface MemoryPrepareContext {
|
|
|
34
34
|
conflictGate: ConflictGate;
|
|
35
35
|
scopeId: string;
|
|
36
36
|
includeDefaultFallback: boolean;
|
|
37
|
-
guardianTrustClass
|
|
37
|
+
guardianTrustClass: 'guardian' | 'trusted_contact' | 'unknown';
|
|
38
38
|
/** When false (e.g. scheduled tasks), skip conflict clarification prompts. */
|
|
39
39
|
isInteractive?: boolean;
|
|
40
40
|
}
|
|
@@ -64,7 +64,7 @@ export async function prepareMemoryContext(
|
|
|
64
64
|
// Provenance-based trust gating: untrusted actors skip all memory operations
|
|
65
65
|
// (recall, dynamic profile, conflict gate) to prevent untrusted content from
|
|
66
66
|
// influencing memory-augmented responses.
|
|
67
|
-
const isTrustedActor = ctx.guardianTrustClass === 'guardian'
|
|
67
|
+
const isTrustedActor = ctx.guardianTrustClass === 'guardian';
|
|
68
68
|
|
|
69
69
|
if (!isTrustedActor) {
|
|
70
70
|
return {
|
|
@@ -38,8 +38,8 @@ export interface GuardianRuntimeContext {
|
|
|
38
38
|
trustClass: 'guardian' | 'trusted_contact' | 'unknown';
|
|
39
39
|
guardianChatId?: string;
|
|
40
40
|
guardianExternalUserId?: string;
|
|
41
|
-
/** Canonical principal ID for the guardian binding.
|
|
42
|
-
guardianPrincipalId?: string
|
|
41
|
+
/** Canonical principal ID for the guardian binding. */
|
|
42
|
+
guardianPrincipalId?: string;
|
|
43
43
|
requesterIdentifier?: string;
|
|
44
44
|
requesterDisplayName?: string;
|
|
45
45
|
requesterSenderDisplayName?: string;
|
|
@@ -110,7 +110,7 @@ export function createToolExecutor(
|
|
|
110
110
|
assistantId: ctx.assistantId,
|
|
111
111
|
requestId: ctx.currentRequestId,
|
|
112
112
|
taskRunId: ctx.taskRunId,
|
|
113
|
-
guardianTrustClass: ctx.guardianContext?.trustClass,
|
|
113
|
+
guardianTrustClass: ctx.guardianContext?.trustClass ?? 'guardian',
|
|
114
114
|
executionChannel: ctx.guardianContext?.sourceChannel,
|
|
115
115
|
callSessionId: ctx.callSessionId,
|
|
116
116
|
requesterExternalUserId: ctx.guardianContext?.requesterExternalUserId,
|
package/src/mcp/client.ts
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
|
+
import { UnauthorizedError } from '@modelcontextprotocol/sdk/client/auth.js';
|
|
1
2
|
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
2
3
|
import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';
|
|
3
4
|
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
|
|
4
5
|
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
|
|
5
6
|
|
|
6
7
|
import type { McpTransport } from '../config/mcp-schema.js';
|
|
8
|
+
import { getSecureKeyAsync } from '../security/secure-keys.js';
|
|
7
9
|
import { getLogger } from '../util/logger.js';
|
|
10
|
+
import { McpOAuthProvider } from './mcp-oauth-provider.js';
|
|
8
11
|
|
|
9
12
|
const log = getLogger('mcp-client');
|
|
10
13
|
|
|
@@ -26,9 +29,16 @@ export class McpClient {
|
|
|
26
29
|
private client: Client;
|
|
27
30
|
private transport: StdioClientTransport | SSEClientTransport | StreamableHTTPClientTransport | null = null;
|
|
28
31
|
private connected = false;
|
|
32
|
+
private oauthProvider: McpOAuthProvider | null = null;
|
|
33
|
+
private quiet: boolean;
|
|
29
34
|
|
|
30
|
-
|
|
35
|
+
get isConnected(): boolean {
|
|
36
|
+
return this.connected;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
constructor(serverId: string, opts?: { quiet?: boolean }) {
|
|
31
40
|
this.serverId = serverId;
|
|
41
|
+
this.quiet = opts?.quiet ?? false;
|
|
32
42
|
this.client = new Client({
|
|
33
43
|
name: 'vellum-assistant',
|
|
34
44
|
version: '1.0.0',
|
|
@@ -38,8 +48,22 @@ export class McpClient {
|
|
|
38
48
|
async connect(transportConfig: McpTransport): Promise<void> {
|
|
39
49
|
if (this.connected) return;
|
|
40
50
|
|
|
41
|
-
|
|
51
|
+
const isHttpTransport = transportConfig.type === 'sse' || transportConfig.type === 'streamable-http';
|
|
52
|
+
|
|
53
|
+
// For HTTP transports, only attach an OAuth provider if cached tokens exist.
|
|
54
|
+
// This avoids triggering client registration (which binds to a random port)
|
|
55
|
+
// during daemon startup. If no tokens, try without auth — if the server
|
|
56
|
+
// requires it, skip silently.
|
|
57
|
+
if (isHttpTransport) {
|
|
58
|
+
const cachedTokens = await getSecureKeyAsync(`mcp:${this.serverId}:tokens`);
|
|
59
|
+
if (cachedTokens) {
|
|
60
|
+
this.oauthProvider = new McpOAuthProvider(this.serverId, transportConfig.url);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (!this.quiet) console.log(`[MCP] Connecting to server "${this.serverId}"...`);
|
|
42
65
|
this.transport = this.createTransport(transportConfig);
|
|
66
|
+
|
|
43
67
|
try {
|
|
44
68
|
await Promise.race([
|
|
45
69
|
this.client.connect(this.transport),
|
|
@@ -48,13 +72,31 @@ export class McpClient {
|
|
|
48
72
|
),
|
|
49
73
|
]);
|
|
50
74
|
} catch (err) {
|
|
51
|
-
// Clean up the transport on failure (e.g., kill spawned stdio process)
|
|
52
75
|
try { await this.client.close(); } catch { /* ignore cleanup errors */ }
|
|
53
76
|
this.transport = null;
|
|
77
|
+
|
|
78
|
+
if (isHttpTransport) {
|
|
79
|
+
const isAuthError = err instanceof UnauthorizedError
|
|
80
|
+
|| (err instanceof Error && /\b(401|403|unauthorized|forbidden)\b/i.test(err.message));
|
|
81
|
+
|
|
82
|
+
if (isAuthError) {
|
|
83
|
+
// Auth-related — user can run `vellum mcp auth <name>` to authenticate.
|
|
84
|
+
log.info({ serverId: this.serverId, err }, 'MCP server requires authentication');
|
|
85
|
+
if (!this.quiet) console.log(`[MCP] Server "${this.serverId}" requires authentication. Run "vellum mcp auth ${this.serverId}" to authenticate.`);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Non-auth error (DNS, TLS, timeout, etc.) — log and re-throw
|
|
90
|
+
log.error({ serverId: this.serverId, err }, 'MCP server connection failed');
|
|
91
|
+
if (!this.quiet) console.error(`[MCP] Server "${this.serverId}" connection failed: ${err instanceof Error ? err.message : err}`);
|
|
92
|
+
throw err;
|
|
93
|
+
}
|
|
94
|
+
|
|
54
95
|
throw err;
|
|
55
96
|
}
|
|
97
|
+
|
|
56
98
|
this.connected = true;
|
|
57
|
-
console.log(`[MCP] Server "${this.serverId}" connected successfully`);
|
|
99
|
+
if (!this.quiet) console.log(`[MCP] Server "${this.serverId}" connected successfully`);
|
|
58
100
|
log.info({ serverId: this.serverId }, 'MCP client connected');
|
|
59
101
|
}
|
|
60
102
|
|
|
@@ -140,13 +182,20 @@ export class McpClient {
|
|
|
140
182
|
case 'sse':
|
|
141
183
|
return new SSEClientTransport(
|
|
142
184
|
new URL(config.url),
|
|
143
|
-
{
|
|
185
|
+
{
|
|
186
|
+
authProvider: this.oauthProvider ?? undefined,
|
|
187
|
+
requestInit: config.headers ? { headers: config.headers } : undefined,
|
|
188
|
+
},
|
|
144
189
|
);
|
|
145
190
|
case 'streamable-http':
|
|
146
191
|
return new StreamableHTTPClientTransport(
|
|
147
192
|
new URL(config.url),
|
|
148
|
-
{
|
|
193
|
+
{
|
|
194
|
+
authProvider: this.oauthProvider ?? undefined,
|
|
195
|
+
requestInit: config.headers ? { headers: config.headers } : undefined,
|
|
196
|
+
},
|
|
149
197
|
);
|
|
150
198
|
}
|
|
151
199
|
}
|
|
152
200
|
}
|
|
201
|
+
|
package/src/mcp/manager.ts
CHANGED
|
@@ -27,8 +27,17 @@ export class McpServerManager {
|
|
|
27
27
|
|
|
28
28
|
try {
|
|
29
29
|
console.log(`[MCP] Starting server "${serverId}" (transport: ${serverConfig.transport.type})`);
|
|
30
|
+
if (serverConfig.transport.type === 'sse' || serverConfig.transport.type === 'streamable-http') {
|
|
31
|
+
log.debug({ serverId }, 'HTTP transport — OAuth provider will be available if server requires authentication');
|
|
32
|
+
}
|
|
30
33
|
const client = new McpClient(serverId);
|
|
31
34
|
await client.connect(serverConfig.transport);
|
|
35
|
+
|
|
36
|
+
if (!client.isConnected) {
|
|
37
|
+
// Server requires authentication — connect() logged guidance
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
|
|
32
41
|
this.clients.set(serverId, client);
|
|
33
42
|
this.serverConfigs.set(serverId, serverConfig);
|
|
34
43
|
|