@vellumai/assistant 0.4.2 → 0.4.4

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 (221) hide show
  1. package/.env.example +3 -0
  2. package/ARCHITECTURE.md +124 -10
  3. package/README.md +43 -35
  4. package/docs/trusted-contact-access.md +20 -0
  5. package/package.json +1 -1
  6. package/scripts/ipc/generate-swift.ts +1 -0
  7. package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +58 -120
  8. package/src/__tests__/access-request-decision.test.ts +0 -1
  9. package/src/__tests__/actor-token-service.test.ts +1099 -0
  10. package/src/__tests__/agent-loop.test.ts +51 -0
  11. package/src/__tests__/approval-routes-http.test.ts +2 -0
  12. package/src/__tests__/assistant-events-sse-hardening.test.ts +7 -5
  13. package/src/__tests__/assistant-id-boundary-guard.test.ts +415 -0
  14. package/src/__tests__/call-controller.test.ts +49 -0
  15. package/src/__tests__/call-pointer-message-composer.test.ts +171 -0
  16. package/src/__tests__/call-pointer-messages.test.ts +93 -3
  17. package/src/__tests__/call-pointer-no-hardcoded-copy.guard.test.ts +42 -0
  18. package/src/__tests__/call-routes-http.test.ts +0 -25
  19. package/src/__tests__/callback-handoff-copy.test.ts +186 -0
  20. package/src/__tests__/channel-approval-routes.test.ts +133 -12
  21. package/src/__tests__/channel-guardian.test.ts +0 -86
  22. package/src/__tests__/channel-readiness-service.test.ts +10 -16
  23. package/src/__tests__/checker.test.ts +33 -12
  24. package/src/__tests__/config-schema.test.ts +6 -0
  25. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +410 -0
  26. package/src/__tests__/conversation-routes-guardian-reply.test.ts +256 -0
  27. package/src/__tests__/conversation-routes.test.ts +12 -3
  28. package/src/__tests__/credential-security-invariants.test.ts +1 -1
  29. package/src/__tests__/daemon-server-session-init.test.ts +4 -0
  30. package/src/__tests__/deterministic-verification-control-plane.test.ts +0 -1
  31. package/src/__tests__/guardian-actions-endpoint.test.ts +39 -13
  32. package/src/__tests__/guardian-dispatch.test.ts +8 -0
  33. package/src/__tests__/guardian-outbound-http.test.ts +4 -5
  34. package/src/__tests__/guardian-question-mode.test.ts +200 -0
  35. package/src/__tests__/guardian-routing-invariants.test.ts +178 -0
  36. package/src/__tests__/guardian-routing-state.test.ts +525 -0
  37. package/src/__tests__/handle-user-message-secret-resume.test.ts +2 -0
  38. package/src/__tests__/handlers-telegram-config.test.ts +0 -83
  39. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +55 -0
  40. package/src/__tests__/headless-browser-navigate.test.ts +2 -0
  41. package/src/__tests__/inbound-invite-redemption.test.ts +0 -1
  42. package/src/__tests__/ingress-routes-http.test.ts +55 -0
  43. package/src/__tests__/ipc-snapshot.test.ts +18 -51
  44. package/src/__tests__/non-member-access-request.test.ts +159 -9
  45. package/src/__tests__/notification-decision-fallback.test.ts +129 -4
  46. package/src/__tests__/notification-decision-strategy.test.ts +106 -2
  47. package/src/__tests__/notification-guardian-path.test.ts +3 -0
  48. package/src/__tests__/recording-intent-handler.test.ts +1 -0
  49. package/src/__tests__/relay-server.test.ts +1475 -33
  50. package/src/__tests__/send-endpoint-busy.test.ts +5 -0
  51. package/src/__tests__/session-agent-loop.test.ts +1 -0
  52. package/src/__tests__/session-confirmation-signals.test.ts +523 -0
  53. package/src/__tests__/session-init.benchmark.test.ts +0 -2
  54. package/src/__tests__/session-runtime-assembly.test.ts +4 -1
  55. package/src/__tests__/session-surfaces-task-progress.test.ts +44 -1
  56. package/src/__tests__/session-tool-setup-app-refresh.test.ts +81 -2
  57. package/src/__tests__/session-tool-setup-memory-scope.test.ts +1 -1
  58. package/src/__tests__/session-tool-setup-side-effect-flag.test.ts +1 -1
  59. package/src/__tests__/tool-executor.test.ts +21 -2
  60. package/src/__tests__/tool-grant-request-escalation.test.ts +333 -27
  61. package/src/__tests__/trusted-contact-approval-notifier.test.ts +678 -0
  62. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +1064 -0
  63. package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +11 -1
  64. package/src/__tests__/trusted-contact-multichannel.test.ts +0 -1
  65. package/src/__tests__/trusted-contact-verification.test.ts +0 -1
  66. package/src/__tests__/twilio-config.test.ts +2 -13
  67. package/src/__tests__/twilio-routes.test.ts +4 -3
  68. package/src/__tests__/update-bulletin.test.ts +0 -1
  69. package/src/agent/loop.ts +1 -1
  70. package/src/approvals/guardian-decision-primitive.ts +12 -3
  71. package/src/approvals/guardian-request-resolvers.ts +169 -11
  72. package/src/calls/call-constants.ts +29 -0
  73. package/src/calls/call-controller.ts +11 -3
  74. package/src/calls/call-domain.ts +33 -11
  75. package/src/calls/call-pointer-message-composer.ts +154 -0
  76. package/src/calls/call-pointer-messages.ts +106 -27
  77. package/src/calls/guardian-dispatch.ts +4 -2
  78. package/src/calls/relay-server.ts +921 -112
  79. package/src/calls/twilio-config.ts +4 -11
  80. package/src/calls/twilio-routes.ts +4 -6
  81. package/src/calls/types.ts +3 -1
  82. package/src/calls/voice-session-bridge.ts +4 -3
  83. package/src/cli/core-commands.ts +7 -4
  84. package/src/cli.ts +5 -4
  85. package/src/config/bundled-skills/agentmail/SKILL.md +4 -0
  86. package/src/config/bundled-skills/app-builder/SKILL.md +309 -10
  87. package/src/config/bundled-skills/app-builder/TOOLS.json +1 -1
  88. package/src/config/bundled-skills/email-setup/SKILL.md +1 -1
  89. package/src/config/bundled-skills/google-oauth-setup/SKILL.md +105 -81
  90. package/src/config/bundled-skills/messaging/SKILL.md +61 -12
  91. package/src/config/bundled-skills/messaging/TOOLS.json +58 -0
  92. package/src/config/bundled-skills/messaging/tools/gmail-sender-digest.ts +6 -1
  93. package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +35 -0
  94. package/src/config/bundled-skills/messaging/tools/messaging-sender-digest.ts +52 -0
  95. package/src/config/bundled-skills/phone-calls/SKILL.md +30 -39
  96. package/src/config/bundled-skills/twitter/SKILL.md +3 -3
  97. package/src/config/bundled-skills/vercel-token-setup/SKILL.md +215 -0
  98. package/src/config/calls-schema.ts +36 -0
  99. package/src/config/env.ts +22 -0
  100. package/src/config/feature-flag-registry.json +8 -8
  101. package/src/config/schema.ts +2 -2
  102. package/src/config/skills.ts +11 -0
  103. package/src/config/system-prompt.ts +11 -1
  104. package/src/config/templates/SOUL.md +2 -0
  105. package/src/config/vellum-skills/sms-setup/SKILL.md +71 -82
  106. package/src/config/vellum-skills/trusted-contacts/SKILL.md +8 -1
  107. package/src/config/vellum-skills/twilio-setup/SKILL.md +88 -73
  108. package/src/daemon/call-pointer-generators.ts +59 -0
  109. package/src/daemon/computer-use-session.ts +2 -5
  110. package/src/daemon/handlers/apps.ts +76 -20
  111. package/src/daemon/handlers/config-channels.ts +9 -61
  112. package/src/daemon/handlers/config-inbox.ts +11 -3
  113. package/src/daemon/handlers/config-ingress.ts +28 -3
  114. package/src/daemon/handlers/config-telegram.ts +12 -0
  115. package/src/daemon/handlers/config.ts +2 -6
  116. package/src/daemon/handlers/index.ts +2 -1
  117. package/src/daemon/handlers/pairing.ts +2 -0
  118. package/src/daemon/handlers/publish.ts +11 -46
  119. package/src/daemon/handlers/sessions.ts +59 -5
  120. package/src/daemon/handlers/shared.ts +17 -2
  121. package/src/daemon/ipc-contract/apps.ts +1 -0
  122. package/src/daemon/ipc-contract/inbox.ts +4 -0
  123. package/src/daemon/ipc-contract/integrations.ts +1 -97
  124. package/src/daemon/ipc-contract/messages.ts +47 -1
  125. package/src/daemon/ipc-contract/notifications.ts +11 -0
  126. package/src/daemon/ipc-contract-inventory.json +2 -4
  127. package/src/daemon/lifecycle.ts +17 -0
  128. package/src/daemon/server.ts +16 -2
  129. package/src/daemon/session-agent-loop-handlers.ts +20 -0
  130. package/src/daemon/session-agent-loop.ts +24 -12
  131. package/src/daemon/session-lifecycle.ts +1 -1
  132. package/src/daemon/session-process.ts +11 -1
  133. package/src/daemon/session-runtime-assembly.ts +6 -1
  134. package/src/daemon/session-surfaces.ts +32 -3
  135. package/src/daemon/session.ts +88 -1
  136. package/src/daemon/tool-side-effects.ts +22 -0
  137. package/src/home-base/prebuilt/brain-graph.html +1483 -0
  138. package/src/home-base/prebuilt/index.html +40 -0
  139. package/src/inbound/platform-callback-registration.ts +157 -0
  140. package/src/memory/canonical-guardian-store.ts +1 -1
  141. package/src/memory/conversation-crud.ts +2 -1
  142. package/src/memory/conversation-title-service.ts +16 -2
  143. package/src/memory/db-init.ts +8 -0
  144. package/src/memory/delivery-crud.ts +2 -1
  145. package/src/memory/guardian-action-store.ts +2 -1
  146. package/src/memory/guardian-approvals.ts +3 -2
  147. package/src/memory/ingress-invite-store.ts +12 -2
  148. package/src/memory/ingress-member-store.ts +4 -3
  149. package/src/memory/migrations/038-actor-token-records.ts +39 -0
  150. package/src/memory/migrations/124-voice-invite-display-metadata.ts +14 -0
  151. package/src/memory/migrations/index.ts +2 -0
  152. package/src/memory/schema.ts +26 -5
  153. package/src/messaging/provider-types.ts +24 -0
  154. package/src/messaging/provider.ts +7 -0
  155. package/src/messaging/providers/gmail/adapter.ts +127 -0
  156. package/src/messaging/providers/sms/adapter.ts +40 -37
  157. package/src/notifications/adapters/macos.ts +45 -2
  158. package/src/notifications/broadcaster.ts +16 -0
  159. package/src/notifications/copy-composer.ts +50 -2
  160. package/src/notifications/decision-engine.ts +22 -9
  161. package/src/notifications/destination-resolver.ts +16 -2
  162. package/src/notifications/emit-signal.ts +18 -9
  163. package/src/notifications/guardian-question-mode.ts +419 -0
  164. package/src/notifications/signal.ts +14 -3
  165. package/src/permissions/checker.ts +13 -1
  166. package/src/permissions/prompter.ts +14 -0
  167. package/src/providers/anthropic/client.ts +20 -0
  168. package/src/providers/provider-send-message.ts +15 -3
  169. package/src/runtime/access-request-helper.ts +82 -4
  170. package/src/runtime/actor-token-service.ts +234 -0
  171. package/src/runtime/actor-token-store.ts +236 -0
  172. package/src/runtime/actor-trust-resolver.ts +2 -2
  173. package/src/runtime/assistant-scope.ts +10 -0
  174. package/src/runtime/channel-approvals.ts +5 -3
  175. package/src/runtime/channel-readiness-service.ts +23 -64
  176. package/src/runtime/channel-readiness-types.ts +3 -4
  177. package/src/runtime/channel-retry-sweep.ts +4 -1
  178. package/src/runtime/confirmation-request-guardian-bridge.ts +197 -0
  179. package/src/runtime/guardian-action-followup-executor.ts +1 -1
  180. package/src/runtime/guardian-context-resolver.ts +82 -0
  181. package/src/runtime/guardian-outbound-actions.ts +5 -7
  182. package/src/runtime/guardian-reply-router.ts +67 -30
  183. package/src/runtime/guardian-vellum-migration.ts +57 -0
  184. package/src/runtime/http-server.ts +75 -31
  185. package/src/runtime/http-types.ts +13 -0
  186. package/src/runtime/ingress-service.ts +14 -0
  187. package/src/runtime/invite-redemption-service.ts +10 -1
  188. package/src/runtime/local-actor-identity.ts +76 -0
  189. package/src/runtime/middleware/actor-token.ts +271 -0
  190. package/src/runtime/middleware/twilio-validation.ts +2 -4
  191. package/src/runtime/routes/approval-routes.ts +82 -7
  192. package/src/runtime/routes/brain-graph-routes.ts +222 -0
  193. package/src/runtime/routes/call-routes.ts +2 -1
  194. package/src/runtime/routes/channel-readiness-routes.ts +71 -0
  195. package/src/runtime/routes/channel-route-shared.ts +3 -3
  196. package/src/runtime/routes/conversation-attention-routes.ts +2 -1
  197. package/src/runtime/routes/conversation-routes.ts +142 -53
  198. package/src/runtime/routes/events-routes.ts +22 -8
  199. package/src/runtime/routes/guardian-action-routes.ts +45 -3
  200. package/src/runtime/routes/guardian-approval-interception.ts +29 -0
  201. package/src/runtime/routes/guardian-bootstrap-routes.ts +145 -0
  202. package/src/runtime/routes/inbound-conversation.ts +4 -3
  203. package/src/runtime/routes/inbound-message-handler.ts +147 -5
  204. package/src/runtime/routes/ingress-routes.ts +2 -0
  205. package/src/runtime/routes/integration-routes.ts +7 -15
  206. package/src/runtime/routes/pairing-routes.ts +163 -0
  207. package/src/runtime/routes/twilio-routes.ts +934 -0
  208. package/src/runtime/tool-grant-request-helper.ts +3 -1
  209. package/src/security/oauth2.ts +27 -2
  210. package/src/security/token-manager.ts +46 -10
  211. package/src/tools/browser/browser-execution.ts +4 -3
  212. package/src/tools/browser/browser-handoff.ts +10 -18
  213. package/src/tools/browser/browser-manager.ts +80 -25
  214. package/src/tools/browser/browser-screencast.ts +35 -119
  215. package/src/tools/calls/call-start.ts +2 -1
  216. package/src/tools/permission-checker.ts +15 -4
  217. package/src/tools/terminal/parser.ts +12 -0
  218. package/src/tools/tool-approval-handler.ts +244 -19
  219. package/src/workspace/git-service.ts +19 -0
  220. package/src/__tests__/handlers-twilio-config.test.ts +0 -1928
  221. package/src/daemon/handlers/config-twilio.ts +0 -1082
@@ -17,7 +17,15 @@ Twilio credentials and phone number configuration are shared between voice calls
17
17
 
18
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
19
 
20
- If Twilio is already configured (check `twilio_config` with `action: "get"`), skip directly to **Step 5: Enable Calls** below.
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.
21
29
 
22
30
  ## Overview
23
31
 
@@ -58,65 +66,48 @@ The user's assistant gets its own personal phone number through Twilio. All impl
58
66
  First, check whether Twilio is already configured:
59
67
 
60
68
  ```bash
61
- vellum config get calls.enabled
69
+ TOKEN=$(cat ~/.vellum/http-token)
70
+ curl -s "$INTERNAL_GATEWAY_BASE_URL/v1/integrations/twilio/config" \
71
+ -H "Authorization: Bearer $TOKEN"
62
72
  ```
63
73
 
64
- Also check for existing credentials:
74
+ Also check calls feature status:
65
75
 
66
76
  ```bash
67
- credential_store action=list
77
+ vellum config get calls.enabled
68
78
  ```
69
79
 
70
- Look for entries with service `twilio` and fields `account_sid`, `auth_token`, and `phone_number`.
71
-
72
- If all three credentials exist and `calls.enabled` is `true`, skip to the **Making Calls** section. If credentials are partially configured, skip to whichever step is still needed.
80
+ If the config response shows `hasCredentials: true` and `phoneNumber` is set, and `calls.enabled` is `true`, skip to the **Making Calls** section. If credentials are partially configured, skip to whichever step is still needed.
73
81
 
74
82
  ## Step 2: Create a Twilio Account
75
83
 
76
84
  If the user doesn't have a Twilio account yet, guide them through setup:
77
85
 
78
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."**
79
- 2. Once they have an account, they need three pieces of information:
87
+ 2. Once they have an account, they need two pieces of information:
80
88
  - **Account SID** — found on the Twilio Console dashboard at https://console.twilio.com
81
89
  - **Auth Token** — found on the same dashboard (click "Show" to reveal it)
82
- - **Phone Number** — a Twilio phone number capable of making voice calls
83
-
84
- ### Getting a Twilio Phone Number
85
-
86
- If the user doesn't have a Twilio phone number yet:
87
-
88
- 1. Direct them to https://console.twilio.com/us1/develop/phone-numbers/manage/incoming
89
- 2. Click **"Buy a Number"**
90
- 3. Select a number with **Voice** capability enabled
91
- 4. For trial accounts, Twilio provides one free number automatically — check "Active Numbers" first
92
90
 
93
- Tell the user: **"This will be your assistant's personal phone number — the number that shows up on caller ID when calls are placed."**
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."**
94
92
 
95
93
  ## Step 3: Store Twilio Credentials
96
94
 
97
- Once the user provides their credentials, store them securely using the `credential_store` tool. Ask the user to paste each value, then store them one at a time:
95
+ **IMPORTANT Secure credential collection only:** Never use credentials pasted in plaintext chat. Always collect credentials through the secure credential prompt flow:
98
96
 
99
- **Account SID:**
100
- ```
101
- credential_store action=store service=twilio field=account_sid value=<their_account_sid>
102
- ```
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"`.
103
99
 
104
- **Auth Token:**
105
- ```
106
- credential_store action=store service=twilio field=auth_token value=<their_auth_token>
107
- ```
100
+ After both credentials are collected, send them to the gateway:
108
101
 
109
- **Phone Number** (must be in E.164 format, e.g. `+14155551234`):
110
- ```
111
- credential_store action=store service=twilio field=phone_number value=<their_phone_number>
112
- ```
113
-
114
- After storing, verify each credential was saved:
115
- ```
116
- credential_store action=list
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>"}'
117
108
  ```
118
109
 
119
- Confirm that entries for service `twilio` with fields `account_sid`, `auth_token`, and `phone_number` appear in the output.
110
+ The endpoint validates the credentials against the Twilio API before storing them. If credentials are invalid, ask the user to re-enter via the secure prompt.
120
111
 
121
112
  **Important:** Credentials are stored in the OS keychain (macOS Keychain / Linux secret-service) or encrypted at rest. They are never logged or exposed in plaintext.
122
113
 
@@ -163,7 +154,7 @@ vellum config get calls.enabled
163
154
 
164
155
  Before making real calls, offer a quick verification:
165
156
 
166
- 1. Confirm credentials are stored: all three Twilio credentials (`account_sid`, `auth_token`, `phone_number`) must be present
157
+ 1. Confirm credentials are stored: check the Twilio config endpoint for `hasCredentials: true` and `phoneNumber`
167
158
  2. Confirm ingress is running: `ingress.publicBaseUrl` must be set and the tunnel active
168
159
  3. Confirm calls are enabled: `calls.enabled` must be `true`
169
160
 
@@ -605,7 +596,7 @@ The following behavioral changes were introduced with the cross-channel guardian
605
596
  ## Troubleshooting
606
597
 
607
598
  ### "Twilio credentials not configured"
608
- Run Step 3 to store your Account SID, Auth Token, and Phone Number via `credential_store`.
599
+ Run Step 3 to store your Account SID and Auth Token via the secure credential prompt flow, or load the `twilio-setup` skill.
609
600
 
610
601
  ### "Calls feature is disabled"
611
602
  Run `vellum config set calls.enabled true`.
@@ -17,7 +17,7 @@ OAuth uses the official X API v2. It is the most reliable connection method and
17
17
 
18
18
  - Supports: **post** and **reply**
19
19
  - Read-only operations (timeline, search, home, bookmarks, notifications, likes, followers, following, media) always use the browser path directly, regardless of the strategy setting.
20
- - Setup: Collect the OAuth Client ID (and optional Client Secret) from the user in chat using `credential_store` with `action: "prompt"` (canonical field names: `client_id`, `client_secret`), then initiate the `twitter_auth_start` IPC flow. See the **First-Use Decision Flow** for the full sequence.
20
+ - Setup: Collect the OAuth Client ID (and optional Client Secret) from the user in chat using `credential_store` with `action: "prompt"` (canonical field names: `client_id`, `client_secret`), then initiate the `twitter_auth_start` flow. See the **First-Use Decision Flow** for the full sequence.
21
21
  - Set the strategy: `vellum x strategy set oauth`
22
22
 
23
23
  ### Browser session (no developer credentials needed)
@@ -56,7 +56,7 @@ When the user triggers a Twitter operation and no strategy has been configured y
56
56
 
57
57
  ### OAuth Setup Sequence
58
58
 
59
- When the user chooses OAuth, collect their X developer credentials conversationally using the secure UI. The OAuth flow delegates to the generic connect orchestrator via the `twitter_auth_start` IPC message (which internally uses the shared orchestrator while preserving Twitter-specific guards like integration-mode checks and refresh-token cleanup).
59
+ When the user chooses OAuth, collect their X developer credentials conversationally using the secure UI. The OAuth flow delegates to the generic connect orchestrator, which resolves the Twitter provider profile, computes scopes via policy, opens the X authorization page in the user's browser, verifies the user's identity, and stores tokens. The orchestrator also manages stale refresh-token cleanup and enforces integration-mode guards.
60
60
 
61
61
  1. **Collect the Client ID securely:**
62
62
  Call `credential_store` with `action: "prompt"`, `service: "integration:twitter"`, `field: "client_id"`, `label: "X (Twitter) OAuth Client ID"`, `description: "Enter the Client ID from your X Developer App"`, and `placeholder: "your-client-id"`.
@@ -65,7 +65,7 @@ When the user chooses OAuth, collect their X developer credentials conversationa
65
65
  Ask the user if their X app uses a confidential client (has a Client Secret). If yes, call `credential_store` with `action: "prompt"`, `service: "integration:twitter"`, `field: "client_secret"`, `label: "X (Twitter) OAuth Client Secret"`, `description: "Enter the Client Secret from your X Developer App (leave blank if using a public client)"`, and `placeholder: "your-client-secret"`.
66
66
 
67
67
  3. **Initiate the OAuth flow:**
68
- Send the `twitter_auth_start` IPC message. The handler delegates to the shared connect orchestrator, which resolves the Twitter provider profile, computes scopes via policy, opens the X authorization page in the user's browser, verifies the user's identity, and stores tokens. The handler also manages stale refresh-token cleanup and enforces integration-mode guards. Wait for the `twitter_auth_result` response.
68
+ Trigger the Twitter auth start flow. The connect orchestrator resolves the Twitter provider profile, computes scopes via policy, opens the X authorization page in the user's browser, verifies the user's identity, and stores tokens. Wait for the auth result.
69
69
 
70
70
  4. **Confirm success:**
71
71
  Tell the user: "Great, your X account is connected! You can always update these credentials from the Settings page."
@@ -0,0 +1,215 @@
1
+ ---
2
+ name: "Vercel Token Setup"
3
+ description: "Set up a Vercel API token for publishing apps using browser automation"
4
+ includes: ["browser"]
5
+ credential-setup-for: "vercel:api_token"
6
+ metadata: {"vellum": {"emoji": "▲"}}
7
+ ---
8
+
9
+ You are helping your user set up a Vercel API token so they can publish apps to the web.
10
+
11
+ ## Client Check
12
+
13
+ Determine whether the user has browser automation available (macOS desktop app) or is on a non-interactive channel (Telegram, SMS, etc.).
14
+
15
+ - **macOS desktop app**: Follow the **Automated Setup** path below.
16
+ - **Telegram or other channel** (no browser automation): Follow the **Manual Setup for Channels** path below.
17
+
18
+ ---
19
+
20
+ # Path A: Manual Setup for Channels (Telegram, SMS, etc.)
21
+
22
+ When the user is on Telegram or any non-macOS client, walk them through a text-based setup. No browser automation is used — the user follows links and performs each action manually.
23
+
24
+ ### Channel Step 1: Confirm and Explain
25
+
26
+ Tell the user:
27
+
28
+ > **Setting up Vercel API Token**
29
+ >
30
+ > Since I can't automate the browser from here, I'll walk you through each step with direct links. You'll need:
31
+ > 1. A Vercel account (free tier works)
32
+ > 2. About 2 minutes
33
+ >
34
+ > Ready to start?
35
+
36
+ If the user declines, acknowledge and stop.
37
+
38
+ ### Channel Step 2: Create the Token
39
+
40
+ Tell the user:
41
+
42
+ > **Step 1: Create an API token**
43
+ >
44
+ > Open this link to go to your Vercel tokens page:
45
+ > https://vercel.com/account/tokens
46
+ >
47
+ > 1. Click **"Create"** (or **"Create Token"**)
48
+ > 2. Set the token name to **"Vellum Assistant"**
49
+ > 3. Select scope: **"Full Account"**
50
+ > 4. Set expiration to the longest option available (or **"No Expiration"** if offered)
51
+ > 5. Click **"Create Token"**
52
+ >
53
+ > A token value will appear — **copy it now**, as it's only shown once.
54
+
55
+ ### Channel Step 3: Store the Token
56
+
57
+ Tell the user:
58
+
59
+ > **Step 2: Send me the token**
60
+ >
61
+ > Please paste the token value into the secure prompt below.
62
+
63
+ Present the secure prompt:
64
+
65
+ ```
66
+ credential_store prompt:
67
+ service: "vercel"
68
+ field: "api_token"
69
+ label: "Vercel API Token"
70
+ description: "Paste the API token you just created on vercel.com"
71
+ placeholder: "Enter your Vercel API token"
72
+ ```
73
+
74
+ Wait for the user to complete the prompt. Once received, store it:
75
+
76
+ ```
77
+ credential_store store:
78
+ service: "vercel"
79
+ field: "api_token"
80
+ value: "<the token the user provided>"
81
+ allowedTools: ["publish_page", "unpublish_page"]
82
+ allowedDomains: ["api.vercel.com"]
83
+ ```
84
+
85
+ ### Channel Step 4: Done!
86
+
87
+ > **Vercel is connected!** You can now publish apps to the web. Try clicking Publish on any app you've built.
88
+
89
+ ---
90
+
91
+ # Path B: Automated Setup (macOS Desktop App)
92
+
93
+ You will automate Vercel token creation via the browser while the user watches. The user's only manual action is signing in to Vercel (if needed) and one copy-paste for the token value.
94
+
95
+ ## Browser Interaction Principles
96
+
97
+ Vercel's UI may change over time. Do NOT memorize or depend on specific element IDs, CSS selectors, or DOM structures. Instead:
98
+
99
+ 1. **Screenshot first, act second.** Before every interaction, take a `browser_screenshot` to see the current visual state. Use `browser_snapshot` to find interactive elements.
100
+ 2. **Adapt to what you see.** If a button's label or position differs from what you expect, use the screenshot to find the correct element.
101
+ 3. **Verify after every action.** After clicking, typing, or navigating, take a new screenshot to confirm the action succeeded.
102
+ 4. **Never assume DOM structure.** Use the snapshot to identify what's on the page and interact accordingly.
103
+ 5. **When stuck, screenshot and describe.** If you cannot find an expected element after 2 attempts, take a screenshot, describe what you see to the user, and ask for guidance.
104
+
105
+ ## Anti-Loop Guardrails
106
+
107
+ Each step has a **retry budget of 3 attempts**. An attempt is one try at the step's primary action (e.g., clicking a button, filling a form). If a step fails after 3 attempts:
108
+
109
+ 1. **Stop trying.** Do not continue retrying the same approach.
110
+ 2. **Fall back to manual.** Tell the user what you were trying to do and ask them to complete that step manually in the browser. Give them the direct URL and clear text instructions.
111
+ 3. **Resume automation** at the next step once the user confirms the manual step is done.
112
+
113
+ If **two or more steps** require manual fallback, abandon the automated flow entirely and switch to giving the user the remaining steps as clear text instructions with links.
114
+
115
+ ## Things That Do Not Work — Do Not Attempt
116
+
117
+ These actions are technically impossible in the browser automation environment:
118
+
119
+ - **Downloading files.** `browser_click` on a Download button does not save files to disk.
120
+ - **Reading the token value from a screenshot.** The token IS visible in the creation dialog, but you MUST NOT attempt to read it from a screenshot — it is too easy to misread characters, and the value must be exact. Always use the `credential_store prompt` approach to let the user copy-paste it accurately.
121
+ - **Clipboard operations.** You cannot copy/paste via browser automation.
122
+
123
+ ## Step 1: Single Upfront Confirmation
124
+
125
+ Tell the user:
126
+
127
+ > **Setting up your Vercel API token so we can publish your app...**
128
+ >
129
+ > Here's what will happen:
130
+ > 1. **A browser opens** to your Vercel account settings
131
+ > 2. **You sign in** (if not already signed in)
132
+ > 3. **I create the token** — you just watch
133
+ > 4. **One quick copy-paste** — I'll ask you to copy the token value into a secure prompt
134
+ >
135
+ > Takes about a minute. Ready?
136
+
137
+ If the user declines, acknowledge and stop. No further confirmations are needed after this point.
138
+
139
+ ## Step 2: Open Vercel and Sign In
140
+
141
+ **Goal:** The user is signed in and the Vercel tokens page is loaded.
142
+
143
+ Navigate to `https://vercel.com/account/tokens`.
144
+
145
+ Take a screenshot and snapshot to check the page state:
146
+ - **Sign-in page:** Tell the user: "Please sign in to your Vercel account in the browser." Then auto-detect sign-in completion by polling screenshots every 5-10 seconds. Check if the current URL has moved away from the login/sign-in page to the tokens page. Do NOT ask the user to "let me know when you're done" — detect it automatically. Once sign-in is detected, tell the user: "Signed in! Creating your API token now..."
147
+ - **Already signed in:** Tell the user: "Already signed in — creating your API token now..." and continue immediately.
148
+
149
+ **Verify:** URL contains `vercel.com/account/tokens` and no sign-in overlay is visible.
150
+
151
+ ## Step 3: Create Token
152
+
153
+ **Goal:** A new API token named "Vellum Assistant" is created.
154
+
155
+ Take a screenshot and snapshot. Find and click the button to create a new token (typically labeled "Create" or "Create Token").
156
+
157
+ On the creation form:
158
+ - Token name: **"Vellum Assistant"**
159
+ - Scope: Select **"Full Account"** (or the broadest scope available)
160
+ - Expiration: Select the longest option available, or **"No Expiration"** if offered
161
+ - Click create/submit
162
+
163
+ **Verify:** Take a screenshot. A dialog or section should now display the newly created token value.
164
+
165
+ ## Step 4: Capture Token via Secure Prompt
166
+
167
+ **Goal:** The token value is securely captured and stored.
168
+
169
+ ### CRITICAL — Token Capture Protocol
170
+
171
+ After token creation, Vercel shows the token value **once**. You MUST follow this exact sequence — **no improvisation**:
172
+
173
+ 1. Tell the user: "Your token has been created! Please copy the token value shown on screen and paste it into the secure prompt below."
174
+ 2. **IMMEDIATELY** present a `credential_store prompt` for the token. This is your ONLY next action.
175
+ 3. Wait for the user to paste the token.
176
+
177
+ **Absolute prohibitions during this step:**
178
+ - Do NOT try to read the token value from the screenshot. It must come from the user via secure prompt to ensure accuracy.
179
+ - Do NOT navigate away from the page until the user has pasted the token.
180
+ - Do NOT click any download or copy buttons.
181
+
182
+ Present the secure prompt:
183
+
184
+ ```
185
+ credential_store prompt:
186
+ service: "vercel"
187
+ field: "api_token"
188
+ label: "Vercel API Token"
189
+ description: "Copy the token value shown on the Vercel page and paste it here."
190
+ placeholder: "Enter your Vercel API token"
191
+ ```
192
+
193
+ Wait for the user to complete the prompt. Once received, store it:
194
+
195
+ ```
196
+ credential_store store:
197
+ service: "vercel"
198
+ field: "api_token"
199
+ value: "<the token the user provided>"
200
+ allowedTools: ["publish_page", "unpublish_page"]
201
+ allowedDomains: ["api.vercel.com"]
202
+ ```
203
+
204
+ **Verify:** `credential_store list` shows `api_token` for `vercel`.
205
+
206
+ ## Step 5: Done!
207
+
208
+ "**Vercel is connected!** Your API token is set up and ready to go. You can now publish apps to the web."
209
+
210
+ ## Error Handling
211
+
212
+ - **Page load failures:** Retry navigation once. If it still fails, tell the user and ask them to check their internet connection.
213
+ - **Element not found:** Take a fresh screenshot to re-assess. The Vercel UI may have changed. Describe what you see and try alternative approaches. If stuck after 2 attempts, ask the user for guidance.
214
+ - **Token already exists with same name:** This is fine — Vercel allows multiple tokens with the same name. Proceed with creation.
215
+ - **Any unexpected state:** Take a `browser_screenshot`, describe what you see, and ask the user for guidance.
@@ -125,6 +125,42 @@ export const CallsConfigSchema = z.object({
125
125
  .positive('calls.userConsultTimeoutSeconds must be a positive integer')
126
126
  .max(2_147_483, 'calls.userConsultTimeoutSeconds must be at most 2147483 (setTimeout-safe limit)')
127
127
  .default(120),
128
+ ttsPlaybackDelayMs: z
129
+ .number({ error: 'calls.ttsPlaybackDelayMs must be a number' })
130
+ .int('calls.ttsPlaybackDelayMs must be an integer')
131
+ .min(0, 'calls.ttsPlaybackDelayMs must be >= 0')
132
+ .max(10_000, 'calls.ttsPlaybackDelayMs must be at most 10000')
133
+ .default(3000),
134
+ accessRequestPollIntervalMs: z
135
+ .number({ error: 'calls.accessRequestPollIntervalMs must be a number' })
136
+ .int('calls.accessRequestPollIntervalMs must be an integer')
137
+ .min(50, 'calls.accessRequestPollIntervalMs must be >= 50')
138
+ .max(10_000, 'calls.accessRequestPollIntervalMs must be at most 10000')
139
+ .default(500),
140
+ guardianWaitUpdateInitialIntervalMs: z
141
+ .number({ error: 'calls.guardianWaitUpdateInitialIntervalMs must be a number' })
142
+ .int('calls.guardianWaitUpdateInitialIntervalMs must be an integer')
143
+ .min(1000, 'calls.guardianWaitUpdateInitialIntervalMs must be >= 1000')
144
+ .max(60_000, 'calls.guardianWaitUpdateInitialIntervalMs must be at most 60000')
145
+ .default(5000),
146
+ guardianWaitUpdateInitialWindowMs: z
147
+ .number({ error: 'calls.guardianWaitUpdateInitialWindowMs must be a number' })
148
+ .int('calls.guardianWaitUpdateInitialWindowMs must be an integer')
149
+ .min(1000, 'calls.guardianWaitUpdateInitialWindowMs must be >= 1000')
150
+ .max(60_000, 'calls.guardianWaitUpdateInitialWindowMs must be at most 60000')
151
+ .default(30_000),
152
+ guardianWaitUpdateSteadyMinIntervalMs: z
153
+ .number({ error: 'calls.guardianWaitUpdateSteadyMinIntervalMs must be a number' })
154
+ .int('calls.guardianWaitUpdateSteadyMinIntervalMs must be an integer')
155
+ .min(1000, 'calls.guardianWaitUpdateSteadyMinIntervalMs must be >= 1000')
156
+ .max(60_000, 'calls.guardianWaitUpdateSteadyMinIntervalMs must be at most 60000')
157
+ .default(7000),
158
+ guardianWaitUpdateSteadyMaxIntervalMs: z
159
+ .number({ error: 'calls.guardianWaitUpdateSteadyMaxIntervalMs must be a number' })
160
+ .int('calls.guardianWaitUpdateSteadyMaxIntervalMs must be an integer')
161
+ .min(1000, 'calls.guardianWaitUpdateSteadyMaxIntervalMs must be >= 1000')
162
+ .max(60_000, 'calls.guardianWaitUpdateSteadyMaxIntervalMs must be at most 60000')
163
+ .default(10_000),
128
164
  disclosure: CallsDisclosureConfigSchema.default(CallsDisclosureConfigSchema.parse({})),
129
165
  safety: CallsSafetyConfigSchema.default(CallsSafetyConfigSchema.parse({})),
130
166
  voice: CallsVoiceConfigSchema.default(CallsVoiceConfigSchema.parse({})),
package/src/config/env.ts CHANGED
@@ -162,6 +162,28 @@ export function getOllamaBaseUrlEnv(): string | undefined {
162
162
  return str('OLLAMA_BASE_URL');
163
163
  }
164
164
 
165
+ // ── Platform ─────────────────────────────────────────────────────────────────
166
+
167
+ export function getPlatformBaseUrl(): string {
168
+ return str('PLATFORM_BASE_URL') ?? '';
169
+ }
170
+
171
+ /**
172
+ * PLATFORM_ASSISTANT_ID — UUID of this assistant on the platform.
173
+ * Required for registering callback routes when containerized.
174
+ */
175
+ export function getPlatformAssistantId(): string {
176
+ return str('PLATFORM_ASSISTANT_ID') ?? '';
177
+ }
178
+
179
+ /**
180
+ * PLATFORM_INTERNAL_API_KEY — static internal gateway key for authenticating
181
+ * with the platform's internal gateway callback route registration endpoint.
182
+ */
183
+ export function getPlatformInternalApiKey(): string {
184
+ return str('PLATFORM_INTERNAL_API_KEY') ?? '';
185
+ }
186
+
165
187
  // ── Startup validation ──────────────────────────────────────────────────────
166
188
 
167
189
  /**
@@ -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",
@@ -204,8 +204,8 @@ export const AssistantConfigSchema = z.object({
204
204
  maxToolUseTurns: z
205
205
  .number({ error: 'maxToolUseTurns must be a number' })
206
206
  .int('maxToolUseTurns must be an integer')
207
- .positive('maxToolUseTurns must be a positive integer')
208
- .default(60),
207
+ .nonnegative('maxToolUseTurns must be a non-negative integer')
208
+ .default(0),
209
209
  thinking: ThinkingConfigSchema.default(ThinkingConfigSchema.parse({})),
210
210
  contextWindow: ContextWindowConfigSchema.default(ContextWindowConfigSchema.parse({})),
211
211
  memory: MemoryConfigSchema.default(MemoryConfigSchema.parse({})),
@@ -65,6 +65,8 @@ export interface SkillSummary {
65
65
  toolManifest?: SkillToolManifestMeta;
66
66
  /** IDs of child skills that this skill includes (metadata-only, not auto-activated). */
67
67
  includes?: string[];
68
+ /** Declares which credential this skill sets up (e.g. "vercel:api_token"). */
69
+ credentialSetupFor?: string;
68
70
  }
69
71
 
70
72
  export interface SkillDefinition extends SkillSummary {
@@ -251,6 +253,7 @@ interface ParsedFrontmatter {
251
253
  disableModelInvocation: boolean;
252
254
  metadata?: VellumMetadata;
253
255
  includes?: string[];
256
+ credentialSetupFor?: string;
254
257
  }
255
258
 
256
259
  function parseIncludes(raw: string | undefined, skillFilePath: string): string[] | undefined {
@@ -338,6 +341,7 @@ function parseFrontmatter(content: string, skillFilePath: string): ParsedFrontma
338
341
  }
339
342
 
340
343
  const includes = parseIncludes(fields.includes, skillFilePath);
344
+ const credentialSetupFor = fields['credential-setup-for']?.trim() || undefined;
341
345
 
342
346
  return {
343
347
  name,
@@ -348,6 +352,7 @@ function parseFrontmatter(content: string, skillFilePath: string): ParsedFrontma
348
352
  disableModelInvocation,
349
353
  metadata,
350
354
  includes,
355
+ credentialSetupFor,
351
356
  };
352
357
  }
353
358
 
@@ -471,6 +476,7 @@ function readSkillFromDirectory(directoryPath: string, skillsDir: string, source
471
476
  metadata: parsed.metadata,
472
477
  toolManifest: detectToolManifest(directoryPath),
473
478
  includes: parsed.includes,
479
+ credentialSetupFor: parsed.credentialSetupFor,
474
480
  };
475
481
  } catch (err) {
476
482
  log.warn({ err, skillFilePath }, 'Failed to read skill file');
@@ -512,6 +518,7 @@ function readBundledSkillFromDirectory(directoryPath: string): SkillDefinition |
512
518
  metadata: parsed.metadata,
513
519
  toolManifest: detectToolManifest(directoryPath),
514
520
  includes: parsed.includes,
521
+ credentialSetupFor: parsed.credentialSetupFor,
515
522
  };
516
523
  } catch (err) {
517
524
  log.warn({ err, skillFilePath }, 'Failed to read bundled skill file');
@@ -566,6 +573,7 @@ function loadBundledSkills(): SkillSummary[] {
566
573
  metadata: skill.metadata,
567
574
  toolManifest: skill.toolManifest,
568
575
  includes: skill.includes,
576
+ credentialSetupFor: skill.credentialSetupFor,
569
577
  });
570
578
  }
571
579
 
@@ -686,6 +694,7 @@ function skillSummaryFromDefinition(skill: SkillDefinition, source: SkillSource)
686
694
  metadata: skill.metadata,
687
695
  toolManifest: skill.toolManifest,
688
696
  includes: skill.includes,
697
+ credentialSetupFor: skill.credentialSetupFor,
689
698
  };
690
699
  }
691
700
 
@@ -731,6 +740,7 @@ export function loadSkillCatalog(workspaceSkillsDir?: string, extraDirs?: string
731
740
  metadata: parsed.metadata,
732
741
  toolManifest: detectToolManifest(directory),
733
742
  includes: parsed.includes,
743
+ credentialSetupFor: parsed.credentialSetupFor,
734
744
  });
735
745
  } catch (err) {
736
746
  log.warn({ err, directory }, 'Failed to read skill from extraDirs');
@@ -813,6 +823,7 @@ export function loadSkillCatalog(workspaceSkillsDir?: string, extraDirs?: string
813
823
  metadata: parsed.metadata,
814
824
  toolManifest: detectToolManifest(directory),
815
825
  includes: parsed.includes,
826
+ credentialSetupFor: parsed.credentialSetupFor,
816
827
  };
817
828
 
818
829
  if (seenIds.has(id)) {
@@ -811,6 +811,14 @@ function buildDynamicSkillWorkflowSection(config: import('./schema.js').Assistan
811
811
  );
812
812
  }
813
813
 
814
+ if (isAssistantFeatureFlagEnabled('feature_flags.messaging.enabled', config)) {
815
+ lines.push(
816
+ '',
817
+ '### Messaging Skill',
818
+ 'When the user asks about email, messaging, inbox management, or wants to read/send/search messages on any platform (Gmail, Slack, Telegram, SMS), load the "messaging" skill using `skill_load`. The messaging skill handles connection setup, credential flows, and all messaging operations — do not improvise setup instructions from general knowledge.',
819
+ );
820
+ }
821
+
814
822
  return lines.join('\n');
815
823
  }
816
824
 
@@ -839,13 +847,15 @@ function formatSkillsCatalog(skills: SkillSummary[]): string {
839
847
  const nameAttr = escapeXml(skill.name);
840
848
  const descAttr = escapeXml(skill.description);
841
849
  const locAttr = escapeXml(skill.directoryPath);
842
- lines.push(`<skill id="${idAttr}" name="${nameAttr}" description="${descAttr}" location="${locAttr}" />`);
850
+ const credAttr = skill.credentialSetupFor ? ` credential-setup-for="${escapeXml(skill.credentialSetupFor)}"` : '';
851
+ lines.push(`<skill id="${idAttr}" name="${nameAttr}" description="${descAttr}" location="${locAttr}"${credAttr} />`);
843
852
  }
844
853
  lines.push('</available_skills>');
845
854
 
846
855
  return [
847
856
  '## Available Skills',
848
857
  'The following skills are available. Before executing one, call the `skill_load` tool with its `id` to load the full instructions.',
858
+ 'When a credential is missing, check if any skill declares `credential-setup-for` matching that service — if so, load that skill.',
849
859
  '',
850
860
  lines.join('\n'),
851
861
  ].join('\n');
@@ -16,6 +16,8 @@ Never remove or weaken safety boundaries, tool-use permission rules, or the Boun
16
16
 
17
17
  **Be resourceful before asking.** Try to figure it out. Read the file. Check the context. Search for it. Then ask if you're stuck. Come back with answers, not questions.
18
18
 
19
+ **Know your own capabilities.** Before telling the user you can't do something or asking them to fix a problem, check what tools and skills you have. If a connection is broken, try to fix it. If a service needs setup, offer to do it. Escalate only after you've tried.
20
+
19
21
  **Have opinions.** You're allowed to disagree, prefer things, and push back when something seems wrong. An assistant with no perspective is just a search engine.
20
22
 
21
23
  **Earn trust through competence.** You have access to your user's machine, files, and tools. Don't make them regret it. Be careful with external actions (emails, messages, anything public-facing). Be bold with internal ones (reading, organizing, building).