@vellumai/assistant 0.4.35 → 0.4.37

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 (239) hide show
  1. package/AGENTS.md +1 -1
  2. package/ARCHITECTURE.md +44 -49
  3. package/README.md +32 -20
  4. package/docs/architecture/keychain-broker.md +186 -0
  5. package/docs/architecture/security.md +110 -116
  6. package/docs/runbook-trusted-contacts.md +2 -2
  7. package/docs/skills.md +25 -25
  8. package/package.json +5 -2
  9. package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +11 -2
  10. package/src/__tests__/actor-token-service.test.ts +1 -0
  11. package/src/__tests__/amazon-cdp-integration.test.ts +74 -0
  12. package/src/__tests__/assistant-feature-flags-integration.test.ts +38 -9
  13. package/src/__tests__/assistant-id-boundary-guard.test.ts +29 -0
  14. package/src/__tests__/browser-fill-credential.test.ts +1 -1
  15. package/src/__tests__/bundle-scanner.test.ts +1 -1
  16. package/src/__tests__/channel-guardian.test.ts +102 -102
  17. package/src/__tests__/channel-invite-transport.test.ts +155 -256
  18. package/src/__tests__/channel-readiness-routes.test.ts +336 -0
  19. package/src/__tests__/checker.test.ts +6 -6
  20. package/src/__tests__/chrome-cdp.test.ts +350 -0
  21. package/src/__tests__/computer-use-session-lifecycle.test.ts +3 -3
  22. package/src/__tests__/computer-use-session-working-dir.test.ts +86 -52
  23. package/src/__tests__/computer-use-skill-lifecycle-cleanup.test.ts +1 -1
  24. package/src/__tests__/config-loader-migration.test.ts +85 -0
  25. package/src/__tests__/conversation-pairing.test.ts +370 -5
  26. package/src/__tests__/credential-broker-browser-fill.test.ts +1 -10
  27. package/src/__tests__/credential-broker-server-use.test.ts +1 -10
  28. package/src/__tests__/credential-security-e2e.test.ts +7 -1
  29. package/src/__tests__/credential-security-invariants.test.ts +14 -20
  30. package/src/__tests__/credential-vault-unit.test.ts +1 -11
  31. package/src/__tests__/credential-vault.test.ts +5 -19
  32. package/src/__tests__/credentials-cli.test.ts +814 -0
  33. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +23 -4
  34. package/src/__tests__/email-invite-adapter.test.ts +78 -0
  35. package/src/__tests__/email-service-config-fallback.test.ts +102 -0
  36. package/src/__tests__/encrypted-store.test.ts +6 -6
  37. package/src/__tests__/ephemeral-permissions.test.ts +3 -3
  38. package/src/__tests__/gateway-only-enforcement.test.ts +5 -1
  39. package/src/__tests__/guardian-actions-endpoint.test.ts +70 -12
  40. package/src/__tests__/guardian-outbound-http.test.ts +53 -47
  41. package/src/__tests__/handle-user-message-secret-resume.test.ts +23 -0
  42. package/src/__tests__/handlers-add-trust-rule-metadata.test.ts +32 -23
  43. package/src/__tests__/handlers-telegram-config.test.ts +8 -2
  44. package/src/__tests__/handlers-twitter-config.test.ts +2 -2
  45. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +108 -7
  46. package/src/__tests__/ingress-reconcile.test.ts +6 -0
  47. package/src/__tests__/intent-routing.test.ts +23 -4
  48. package/src/__tests__/invite-routes-http.test.ts +12 -0
  49. package/src/__tests__/ipc-snapshot.test.ts +8 -2
  50. package/src/__tests__/keychain-broker-client.test.ts +543 -0
  51. package/src/__tests__/llm-usage-store.test.ts +344 -0
  52. package/src/__tests__/mcp-client-auth.test.ts +2 -2
  53. package/src/__tests__/media-reuse-story.e2e.test.ts +1 -1
  54. package/src/__tests__/migration-transport.test.ts +49 -0
  55. package/src/__tests__/notification-broadcaster.test.ts +205 -5
  56. package/src/__tests__/notification-deep-link.test.ts +365 -1
  57. package/src/__tests__/oauth-connect-handler.test.ts +2 -2
  58. package/src/__tests__/onboarding-starter-tasks.test.ts +17 -4
  59. package/src/__tests__/proxy-approval-callback.test.ts +1 -1
  60. package/src/__tests__/recording-handler.test.ts +1 -1
  61. package/src/__tests__/recording-intent-handler.test.ts +6 -1
  62. package/src/__tests__/recording-state-machine.test.ts +1 -1
  63. package/src/__tests__/relay-server.test.ts +9 -1
  64. package/src/__tests__/ride-shotgun-handler.test.ts +499 -0
  65. package/src/__tests__/runtime-attachment-metadata.test.ts +160 -1
  66. package/src/__tests__/script-proxy-injection-runtime.test.ts +299 -2
  67. package/src/__tests__/script-proxy-profile-template-fallback.test.ts +1 -1
  68. package/src/__tests__/secret-onetime-send.test.ts +8 -2
  69. package/src/__tests__/secure-keys.test.ts +175 -216
  70. package/src/__tests__/session-confirmation-signals.test.ts +1 -1
  71. package/src/__tests__/session-messaging-secret-redirect.test.ts +1 -1
  72. package/src/__tests__/session-queue.test.ts +2 -1
  73. package/src/__tests__/session-tool-setup-app-refresh.test.ts +2 -2
  74. package/src/__tests__/skill-feature-flags-integration.test.ts +29 -4
  75. package/src/__tests__/skill-feature-flags.test.ts +12 -9
  76. package/src/__tests__/skill-load-feature-flag.test.ts +26 -5
  77. package/src/__tests__/skill-projection.benchmark.test.ts +0 -1
  78. package/src/__tests__/skills.test.ts +34 -4
  79. package/src/__tests__/slack-channel-config.test.ts +2 -2
  80. package/src/__tests__/system-prompt.test.ts +26 -4
  81. package/src/__tests__/telegram-bot-username-resolution.test.ts +212 -0
  82. package/src/__tests__/telegram-invite-adapter.test.ts +164 -0
  83. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +1 -1
  84. package/src/__tests__/tool-permission-simulate-handler.test.ts +8 -2
  85. package/src/__tests__/trusted-contact-approval-notifier.test.ts +9 -1
  86. package/src/__tests__/twitter-auth-handler.test.ts +2 -2
  87. package/src/__tests__/twitter-oauth-client.test.ts +1 -1
  88. package/src/__tests__/usage-routes.test.ts +339 -0
  89. package/src/__tests__/whatsapp-invite-adapter.test.ts +94 -0
  90. package/src/agent/loop.ts +3 -0
  91. package/src/amazon/checkout.ts +0 -1
  92. package/src/approvals/guardian-request-resolvers.ts +9 -1
  93. package/src/bundler/app-bundler.ts +28 -12
  94. package/src/bundler/bundle-scanner.ts +1 -1
  95. package/src/bundler/bundle-signer.ts +3 -3
  96. package/src/bundler/manifest.ts +1 -1
  97. package/src/bundler/signature-verifier.ts +3 -3
  98. package/src/channels/config.ts +1 -1
  99. package/src/cli/AGENTS.md +63 -0
  100. package/src/cli/__tests__/notifications.test.ts +470 -0
  101. package/src/cli/amazon.ts +344 -167
  102. package/src/cli/audit.ts +85 -0
  103. package/src/cli/autonomy.ts +369 -0
  104. package/src/cli/channels.ts +51 -0
  105. package/src/cli/completions.ts +208 -0
  106. package/src/cli/config.ts +220 -0
  107. package/src/cli/contacts.ts +471 -0
  108. package/src/cli/credentials.ts +564 -0
  109. package/src/cli/default-action.ts +14 -0
  110. package/src/cli/dev.ts +131 -0
  111. package/src/cli/doctor.ts +398 -0
  112. package/src/cli/email.ts +494 -0
  113. package/src/cli/influencer.ts +72 -0
  114. package/src/cli/integrations.ts +248 -57
  115. package/src/cli/keys.ts +114 -0
  116. package/src/cli/map.ts +46 -54
  117. package/src/cli/mcp.ts +111 -3
  118. package/src/cli/{config-commands.ts → memory.ts} +134 -245
  119. package/src/cli/notifications.ts +407 -0
  120. package/src/cli/program.ts +65 -0
  121. package/src/cli/reference.ts +48 -0
  122. package/src/cli/sequence.ts +154 -0
  123. package/src/cli/sessions.ts +262 -0
  124. package/src/cli/trust.ts +175 -0
  125. package/src/cli/twitter.ts +323 -106
  126. package/src/config/__tests__/build-cli-reference-section.test.ts +49 -0
  127. package/src/config/bundled-skills/amazon/SKILL.md +2 -2
  128. package/src/config/bundled-skills/app-builder/TOOLS.json +26 -0
  129. package/src/config/bundled-skills/app-builder/tools/app-generate-icon.ts +13 -0
  130. package/src/config/bundled-skills/contacts/SKILL.md +178 -10
  131. package/src/config/bundled-skills/doordash/doordash-cli.ts +23 -168
  132. package/src/config/bundled-skills/google-oauth-setup/SKILL.md +135 -34
  133. package/src/config/bundled-skills/messaging/tools/shared.ts +4 -1
  134. package/src/config/bundled-skills/twilio-setup/SKILL.md +70 -17
  135. package/src/config/bundled-tool-registry.ts +2 -0
  136. package/src/config/core-schema.ts +7 -0
  137. package/src/config/feature-flag-registry.json +16 -0
  138. package/src/config/loader.ts +26 -0
  139. package/src/config/schema.ts +4 -0
  140. package/src/config/skill-state.ts +0 -13
  141. package/src/config/system-prompt.ts +27 -0
  142. package/src/contacts/contact-store.ts +25 -0
  143. package/src/daemon/computer-use-session.ts +1 -1
  144. package/src/daemon/handlers/apps.ts +1 -0
  145. package/src/daemon/handlers/config-channels.ts +3 -3
  146. package/src/daemon/handlers/config-dispatch.ts +29 -0
  147. package/src/daemon/handlers/config-inbox.ts +4 -3
  148. package/src/daemon/handlers/config.ts +3 -43
  149. package/src/daemon/handlers/contacts.ts +34 -0
  150. package/src/daemon/handlers/index.ts +17 -3
  151. package/src/daemon/handlers/session-user-message.ts +7 -0
  152. package/src/daemon/handlers/sessions.ts +21 -2
  153. package/src/daemon/handlers/shared.ts +17 -0
  154. package/src/daemon/ipc-contract/apps.ts +2 -0
  155. package/src/daemon/ipc-contract/computer-use.ts +9 -0
  156. package/src/daemon/ipc-contract/contacts.ts +3 -3
  157. package/src/daemon/ipc-contract/inbox.ts +2 -0
  158. package/src/daemon/ipc-contract/messages.ts +4 -0
  159. package/src/daemon/ipc-contract/sessions.ts +8 -0
  160. package/src/daemon/ipc-contract-inventory.json +1 -0
  161. package/src/daemon/lifecycle.ts +0 -5
  162. package/src/daemon/ride-shotgun-handler.ts +139 -25
  163. package/src/daemon/session-agent-loop-handlers.ts +100 -0
  164. package/src/daemon/session-agent-loop.ts +72 -0
  165. package/src/daemon/session-tool-setup.ts +7 -0
  166. package/src/daemon/session.ts +23 -1
  167. package/src/daemon/tool-side-effects.ts +39 -1
  168. package/src/email/service.ts +59 -2
  169. package/src/index.ts +2 -60
  170. package/src/mcp/mcp-oauth-provider.ts +90 -8
  171. package/src/media/app-icon-generator.ts +86 -0
  172. package/src/memory/db-init.ts +11 -0
  173. package/src/memory/llm-usage-store.ts +186 -0
  174. package/src/memory/migrations/137-usage-dashboard-indexes.ts +26 -0
  175. package/src/memory/migrations/139-drop-usage-composite-indexes.ts +30 -0
  176. package/src/memory/migrations/index.ts +2 -0
  177. package/src/memory/schema-migration.ts +1 -0
  178. package/src/memory/shared-app-links-store.ts +1 -1
  179. package/src/messaging/registry.ts +27 -0
  180. package/src/notifications/README.md +79 -70
  181. package/src/notifications/broadcaster.ts +2 -1
  182. package/src/notifications/conversation-pairing.ts +147 -13
  183. package/src/notifications/copy-composer.ts +7 -3
  184. package/src/notifications/destination-resolver.ts +14 -1
  185. package/src/notifications/emit-signal.ts +3 -2
  186. package/src/notifications/signal.ts +105 -1
  187. package/src/notifications/types.ts +16 -0
  188. package/src/permissions/checker.ts +29 -3
  189. package/src/permissions/prompter.ts +11 -3
  190. package/src/runtime/access-request-helper.ts +2 -1
  191. package/src/runtime/auth/route-policy.ts +7 -1
  192. package/src/runtime/channel-invite-transport.ts +40 -63
  193. package/src/runtime/channel-invite-transports/email.ts +13 -39
  194. package/src/runtime/channel-invite-transports/slack.ts +5 -34
  195. package/src/runtime/channel-invite-transports/sms.ts +8 -29
  196. package/src/runtime/channel-invite-transports/telegram.ts +69 -28
  197. package/src/runtime/channel-invite-transports/voice.ts +0 -7
  198. package/src/runtime/channel-invite-transports/whatsapp.ts +43 -0
  199. package/src/runtime/channel-readiness-service.ts +202 -45
  200. package/src/runtime/confirmation-request-guardian-bridge.ts +2 -1
  201. package/src/runtime/guardian-outbound-actions.ts +8 -5
  202. package/src/runtime/http-server.ts +2 -0
  203. package/src/runtime/invite-instruction-generator.ts +178 -0
  204. package/src/runtime/invite-service.ts +22 -25
  205. package/src/runtime/migrations/migration-transport.ts +13 -0
  206. package/src/runtime/routes/app-routes.ts +1 -1
  207. package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +8 -7
  208. package/src/runtime/routes/channel-readiness-routes.ts +30 -11
  209. package/src/runtime/routes/contact-routes.ts +54 -26
  210. package/src/runtime/routes/inbound-stages/bootstrap-intercept.ts +1 -1
  211. package/src/runtime/routes/inbound-stages/escalation-intercept.ts +2 -1
  212. package/src/runtime/routes/inbound-stages/verification-intercept.ts +2 -1
  213. package/src/runtime/routes/integration-routes.ts +1 -1
  214. package/src/runtime/routes/invite-routes.ts +1 -1
  215. package/src/runtime/routes/secret-routes.ts +31 -7
  216. package/src/runtime/routes/twilio-routes.ts +32 -1
  217. package/src/runtime/routes/usage-routes.ts +114 -0
  218. package/src/runtime/tool-grant-request-helper.ts +2 -1
  219. package/src/security/encrypted-store.ts +9 -5
  220. package/src/security/keychain-broker-client.ts +393 -0
  221. package/src/security/secure-keys.ts +106 -321
  222. package/src/tools/apps/executors.ts +73 -0
  223. package/src/tools/browser/auto-navigate.ts +15 -6
  224. package/src/tools/browser/chrome-cdp.ts +211 -0
  225. package/src/tools/browser/network-recorder.test.ts +83 -0
  226. package/src/tools/browser/network-recorder.ts +8 -7
  227. package/src/tools/browser/x-auto-navigate.ts +12 -6
  228. package/src/tools/credentials/policy-types.ts +24 -0
  229. package/src/tools/credentials/vault.ts +22 -27
  230. package/src/tools/network/script-proxy/session-manager.ts +47 -3
  231. package/src/tools/permission-checker.ts +1 -0
  232. package/src/tools/types.ts +2 -0
  233. package/src/tools/ui-surface/definitions.ts +1 -2
  234. package/src/tools/watch/watch-state.ts +2 -0
  235. package/src/__tests__/key-migration.test.ts +0 -240
  236. package/src/__tests__/keychain.test.ts +0 -286
  237. package/src/cli/core-commands.ts +0 -899
  238. package/src/security/keychain-to-encrypted-migration.ts +0 -66
  239. package/src/security/keychain.ts +0 -490
@@ -3,7 +3,7 @@ name: "Google OAuth Setup"
3
3
  description: "Set up Google Cloud OAuth credentials for Gmail and Calendar"
4
4
  user-invocable: true
5
5
  credential-setup-for: "gmail"
6
- includes: ["public-ingress"]
6
+ includes: ["public-ingress", "browser"]
7
7
  metadata: { "vellum": { "emoji": "\ud83d\udd11" } }
8
8
  ---
9
9
 
@@ -88,6 +88,7 @@ Tell the user:
88
88
  > - `https://www.googleapis.com/auth/calendar.readonly`
89
89
  > - `https://www.googleapis.com/auth/calendar.events`
90
90
  > - `https://www.googleapis.com/auth/userinfo.email`
91
+ > - `https://www.googleapis.com/auth/contacts.readonly`
91
92
  > - Click **Update**, then **Save and Continue**
92
93
  > 5. On the Test users page, add **your email**, click **Save and Continue**
93
94
  > 6. On the Summary page, click **Back to Dashboard**
@@ -198,7 +199,42 @@ After the user authorizes (they'll come back and say so, or you can suggest they
198
199
 
199
200
  **IMPORTANT: Always use `host_bash` (not `bash`) for all commands in this path.** The `gcloud` and `gws` CLIs need host access for Homebrew/npm installation, browser-based authentication, and interactive terminal prompts — none of which are available inside the sandbox.
200
201
 
201
- You will set up Google Cloud OAuth credentials using the `gcloud` and `gws` command-line tools. This avoids browser automation entirely — the user only needs to sign in once via the browser and copy-paste credentials from terminal output into secure prompts.
202
+ You will set up Google Cloud OAuth credentials using the `gcloud` and `gws` command-line tools plus browser automation. The user signs in once via the browser, the CLI handles project setup, and the browser automates credential creation the user only needs to copy-paste the Client Secret.
203
+
204
+ ## Browser Interaction Principles
205
+
206
+ Google Cloud Console's UI may change over time. Do NOT memorize or depend on specific element IDs, CSS selectors, or DOM structures. Instead:
207
+
208
+ 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.
209
+ 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.
210
+ 3. **Verify after every action.** After clicking, typing, or navigating, take a new screenshot to confirm the action succeeded.
211
+ 4. **Never assume DOM structure.** Use the snapshot to identify what's on the page and interact accordingly.
212
+ 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.
213
+
214
+ ## Anti-Loop Guardrails
215
+
216
+ 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:
217
+
218
+ 1. **Stop trying.** Do not continue retrying the same approach.
219
+ 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.
220
+ 3. **Resume automation** at the next step once the user confirms the manual step is done.
221
+
222
+ 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.
223
+
224
+ ## Things That Do Not Work — Do Not Attempt
225
+
226
+ These actions are technically impossible in the browser automation environment:
227
+
228
+ - **Downloading files.** `browser_click` on a Download button does not save files to disk. Do NOT click "Download JSON" in the OAuth client creation dialog.
229
+ - **Reading the Client Secret from a screenshot.** The secret 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.
230
+ - **Clipboard operations.** You cannot copy/paste via browser automation.
231
+
232
+ ## Error Handling
233
+
234
+ - **Page load failures:** Retry navigation once. If it still fails, tell the user and ask them to check their internet connection.
235
+ - **Element not found:** Take a fresh screenshot to re-assess. The GCP Console UI may have changed. Describe what you see and try alternative approaches. If stuck after 2 attempts, ask the user for guidance.
236
+ - **OAuth client already exists with same name:** This is fine — GCP allows multiple clients with the same name. Proceed with creation.
237
+ - **Any unexpected state:** Take a `browser_screenshot`, describe what you see, and ask the user for guidance.
202
238
 
203
239
  ## CLI Step 1: Confirm
204
240
 
@@ -210,9 +246,10 @@ Use `ui_show` with `surface_type: "confirmation"`:
210
246
  >
211
247
  > 1. **Install CLI tools** (`gcloud` and `gws`) if not already installed
212
248
  > 2. **You sign in** to your Google account once via the browser
213
- > 3. **CLI automates everything** — project creation, APIs, consent screen, and credentials
214
- > 4. **You copy-paste credentials** from the terminal output into secure prompts
215
- > 5. **You authorize Vellum** with one click
249
+ > 3. **CLI automates everything** — project creation, APIs, and consent screen
250
+ > 4. **I create OAuth credentials** in the browser you just watch
251
+ > 5. **One quick copy-paste** you copy the Client Secret into a secure prompt
252
+ > 6. **You authorize Vellum** with one click
216
253
  >
217
254
  > Takes about a minute after first-time setup. Ready?
218
255
 
@@ -274,6 +311,8 @@ If the user is already authenticated (`gcloud auth list` shows an active account
274
311
 
275
312
  Tell the user: "Setting up your Google Cloud project, APIs, and credentials..."
276
313
 
314
+ ### Primary: `gws auth setup`
315
+
277
316
  ```bash
278
317
  gws auth setup
279
318
  ```
@@ -286,7 +325,52 @@ This command automates:
286
325
 
287
326
  Wait for the command to complete. It may have interactive prompts — let them run in the terminal and the user can respond if needed.
288
327
 
289
- Note the **project ID** from the output you'll need it for the next step.
328
+ If `gws auth setup` **succeeds**, note the **project ID** from the output and continue to **CLI Step 5**.
329
+
330
+ ### Fallback: `gcloud` CLI + browser automation
331
+
332
+ If `gws auth setup` **fails** (common with personal Google accounts due to workspace-admin scope errors), use `gcloud` CLI and browser automation instead.
333
+
334
+ **Step 4f-1: Create GCP project**
335
+
336
+ Generate a unique suffix (4-6 random alphanumeric characters):
337
+
338
+ ```bash
339
+ gcloud projects create vellum-assistant-SUFFIX --name="Vellum Assistant"
340
+ ```
341
+
342
+ If the project already exists, use it. Note the **project ID**.
343
+
344
+ **Step 4f-2: Enable APIs**
345
+
346
+ ```bash
347
+ gcloud services enable gmail.googleapis.com --project=PROJECT_ID
348
+ gcloud services enable calendar-json.googleapis.com --project=PROJECT_ID
349
+ gcloud services enable people.googleapis.com --project=PROJECT_ID
350
+ ```
351
+
352
+ **Step 4f-3: Configure OAuth consent screen via browser**
353
+
354
+ Navigate to `https://console.cloud.google.com/apis/credentials/consent?project=PROJECT_ID`.
355
+
356
+ Take a screenshot and snapshot, then:
357
+
358
+ 1. Select **"External"** user type, click **Create**
359
+ 2. Fill in the app registration form:
360
+ - App name: **"Vellum Assistant"**
361
+ - User support email: select the authenticated email from the dropdown
362
+ - Developer contact email: type the same email
363
+ - Click **Save and Continue**
364
+ 3. On the Scopes page, click **Save and Continue** (scopes are not needed for test-mode apps)
365
+ 4. On the Test users page:
366
+ - Click **+ Add Users**
367
+ - Enter the authenticated email address
368
+ - Click **Add**, then **Save and Continue**
369
+ 5. On the Summary page, click **Back to Dashboard**
370
+
371
+ **Verify:** Take a screenshot. The consent screen should show "Testing" publishing status.
372
+
373
+ After the fallback completes, skip CLI Step 5 (APIs already enabled above) and CLI Step 5c (test user already added above) — continue directly to **CLI Step 6**.
290
374
 
291
375
  ## CLI Step 5: Enable Additional APIs
292
376
 
@@ -299,56 +383,73 @@ gcloud services enable people.googleapis.com --project=PROJECT_ID
299
383
 
300
384
  If either command reports the API is already enabled, that's fine — continue.
301
385
 
302
- ## CLI Step 5b: Update OAuth Consent Screen Scopes
386
+ ## CLI Step 5c: Add Test User
303
387
 
304
- `gws auth setup` configures the consent screen with Gmail scopes only. Vellum also needs Calendar, Contacts, and userinfo scopes. Tell the user to add the missing scopes:
388
+ `gws auth setup` does not add test users to the consent screen, and there is no CLI/API for it. Use browser automation to add the authenticated email as a test user.
305
389
 
306
- > **Update consent screen scopes**
307
- >
308
- > Open: `https://console.cloud.google.com/apis/credentials/consent/edit?project=PROJECT_ID`
309
- >
310
- > 1. Click through to the **Scopes** page (click **Save and Continue** on the first page)
311
- > 2. Click **Add or Remove Scopes** and ensure all of these are present:
312
- > - `https://www.googleapis.com/auth/gmail.readonly`
313
- > - `https://www.googleapis.com/auth/gmail.modify`
314
- > - `https://www.googleapis.com/auth/gmail.send`
315
- > - `https://www.googleapis.com/auth/calendar.readonly`
316
- > - `https://www.googleapis.com/auth/calendar.events`
317
- > - `https://www.googleapis.com/auth/userinfo.email`
318
- > - `https://www.googleapis.com/auth/contacts.readonly`
319
- > 3. Click **Update**, then **Save and Continue** through the remaining pages
390
+ Navigate to `https://console.cloud.google.com/apis/credentials/consent?project=PROJECT_ID`.
320
391
 
321
- (Substitute the actual project ID into the URL.)
392
+ Take a screenshot and snapshot. Find the **Test users** section (you may need to click **Edit App** or navigate to the consent screen edit flow):
322
393
 
323
- The Gmail scopes may already be present from `gws auth setup` add any that are missing.
394
+ 1. Find and click the option to add test users (e.g., **+ Add Users** button)
395
+ 2. Enter the authenticated email address (from `gcloud auth list`)
396
+ 3. Click **Add** or **Save**
324
397
 
325
- ## CLI Step 6: Collect Credentials
398
+ **Verify:** Take a screenshot confirming the email appears in the test users list.
326
399
 
327
- The `gws auth setup` output or the GCP Console shows the Client ID and Client Secret. Ask the user to copy-paste them into secure prompts.
400
+ If the user is already listed as a test user, skip this step.
328
401
 
329
- **Client ID:**
402
+ ## CLI Step 6: Create OAuth Credentials via Browser
403
+
404
+ **Goal:** Create a Desktop OAuth client in GCP Console and capture both credentials.
405
+
406
+ Navigate to `https://console.cloud.google.com/apis/credentials?project=PROJECT_ID` (substitute the actual project ID from step 4).
407
+
408
+ Take a screenshot and snapshot to check the page state:
409
+
410
+ - **Sign-in page:** Tell the user: "Please sign in to your Google account in the browser." Then auto-detect sign-in completion by polling screenshots every 5-10 seconds. Once signed in, continue.
411
+ - **Already signed in / Credentials page loaded:** Continue immediately.
412
+
413
+ ### Step 6a: Create the OAuth Client
414
+
415
+ 1. Take a screenshot and snapshot. Find and click **+ Create Credentials**, then select **OAuth client ID**.
416
+ 2. On the creation form:
417
+ - Application type: Select **"Desktop app"**
418
+ - Name: **"Vellum Assistant"**
419
+ - Click **Create**
420
+ 3. **Verify:** Take a screenshot. A dialog should appear showing both the **Client ID** and **Client Secret**.
421
+
422
+ ### Step 6b: Extract Client ID
423
+
424
+ Use `browser_extract` to read the Client ID from the creation dialog. It looks like `123456789-xxxxx.apps.googleusercontent.com`.
425
+
426
+ Store it immediately:
330
427
 
331
428
  ```
332
- credential_store prompt:
429
+ credential_store store:
333
430
  service: "integration:gmail"
334
431
  field: "client_id"
335
- label: "Google OAuth Client ID"
336
- description: "Copy the Client ID from the setup output or GCP Console. It looks like 123456789-xxxxx.apps.googleusercontent.com"
337
- placeholder: "xxxxx.apps.googleusercontent.com"
432
+ value: "<the extracted Client ID>"
338
433
  ```
339
434
 
340
- **Client Secret:**
435
+ ### Step 6c: Capture Client Secret via Secure Prompt
436
+
437
+ The Client Secret is visible in the same dialog. Do NOT attempt to read it from the screenshot — use a secure prompt so the user copies it accurately.
438
+
439
+ Tell the user: "Your OAuth credentials have been created! Please copy the **Client Secret** shown in the dialog and paste it into the secure prompt below."
341
440
 
342
441
  ```
343
442
  credential_store prompt:
344
443
  service: "integration:gmail"
345
444
  field: "client_secret"
346
445
  label: "Google OAuth Client Secret"
347
- description: "Copy the Client Secret from the setup output or GCP Console. It starts with GOCSPX-"
446
+ description: "Copy the Client Secret from the dialog on screen. It starts with GOCSPX-"
348
447
  placeholder: "GOCSPX-..."
349
448
  ```
350
449
 
351
- Wait for both prompts to be completed before continuing.
450
+ **CRITICAL — do NOT dismiss the creation dialog before the user has copied the secret.** Wait for the secure prompt to be completed before clicking OK or navigating away.
451
+
452
+ After the secret is stored, you may close the dialog.
352
453
 
353
454
  ## CLI Step 7: Authorize
354
455
 
@@ -11,6 +11,7 @@ import type { MessagingProvider } from "../../../../messaging/provider.js";
11
11
  import {
12
12
  getConnectedProviders,
13
13
  getMessagingProvider,
14
+ isPlatformEnabled,
14
15
  } from "../../../../messaging/registry.js";
15
16
  import { withValidToken } from "../../../../security/token-manager.js";
16
17
  import type { ToolExecutionResult } from "../../../../tools/types.js";
@@ -32,7 +33,9 @@ export function err(message: string): ToolExecutionResult {
32
33
  export function resolveProvider(platformInput?: string): MessagingProvider {
33
34
  if (platformInput) return getMessagingProvider(platformInput);
34
35
 
35
- const connected = getConnectedProviders();
36
+ const connected = getConnectedProviders().filter((p) =>
37
+ isPlatformEnabled(p.id),
38
+ );
36
39
  if (connected.length === 1) return connected[0];
37
40
  if (connected.length === 0) {
38
41
  throw new Error(
@@ -17,10 +17,17 @@ vellum integrations twilio config --json
17
17
  curl -s -X POST "$INTERNAL_GATEWAY_BASE_URL/v1/integrations/twilio/credentials" \
18
18
  -H "Authorization: Bearer $GATEWAY_AUTH_TOKEN" -H "Content-Type: application/json" \
19
19
  -d '{"accountSid":"ACxxx","authToken":"xxx"}'
20
- # 3. Provision or assign a number
21
- curl -s -X POST "$INTERNAL_GATEWAY_BASE_URL/v1/integrations/twilio/numbers/provision" \
20
+ # 3. Get credential ID and Account SID for proxied calls
21
+ credential_store action=list # note credential_id for twilio/account_sid
22
+ vellum integrations twilio config --json | jq -r '.accountSid'
23
+ # 4. Search and provision via Twilio API (proxy injects auth automatically)
24
+ # bash network_mode=proxied credential_ids=["<cred_id>"]
25
+ curl -s "https://api.twilio.com/2010-04-01/Accounts/<SID>/AvailablePhoneNumbers/US/Local.json?SmsEnabled=true&VoiceEnabled=true"
26
+ curl -s -X POST "https://api.twilio.com/2010-04-01/Accounts/<SID>/IncomingPhoneNumbers.json" -d "PhoneNumber=+1xxx"
27
+ # 5. Assign locally (saves to config + sets up webhooks)
28
+ curl -s -X POST "$INTERNAL_GATEWAY_BASE_URL/v1/integrations/twilio/numbers/assign" \
22
29
  -H "Authorization: Bearer $GATEWAY_AUTH_TOKEN" -H "Content-Type: application/json" \
23
- -d '{"country":"US","areaCode":"415"}'
30
+ -d '{"phoneNumber":"+1xxx"}'
24
31
  ```
25
32
 
26
33
  For voice call setup after Twilio is configured, use `phone-calls` + `call_start`.
@@ -30,11 +37,11 @@ For voice call setup after Twilio is configured, use `phone-calls` + `call_start
30
37
  This skill manages the full Twilio lifecycle:
31
38
 
32
39
  - **Credential storage** — Account SID and Auth Token
33
- - **Phone number provisioning** — Buy a new number directly from Twilio
40
+ - **Direct Twilio API access** — Search and purchase numbers via proxied calls to the Twilio REST API (the proxy injects authentication automatically)
34
41
  - **Phone number assignment** — Assign an existing Twilio number to the assistant
35
42
  - **Status checking** — Verify credentials and assigned number
36
43
 
37
- Mutating operations use Twilio HTTP control-plane endpoints on the gateway. Status/list retrieval uses `vellum integrations ...` CLI reads.
44
+ Number search and purchase use proxied calls to the Twilio REST API (`bash` with `network_mode: "proxied"`). Local bookkeeping (assign, webhook sync) uses gateway control-plane endpoints. Status/list retrieval uses `vellum integrations ...` CLI reads.
38
45
 
39
46
  ### Multi-Assistant Setups
40
47
 
@@ -49,7 +56,7 @@ In a multi-assistant environment (multiple assistants sharing the same runtime),
49
56
 
50
57
  - `GET /v1/integrations/twilio/config` — Returns the phone number assigned to the specified assistant.
51
58
  - `POST /v1/integrations/twilio/numbers/assign` — Assigns a phone number to a specific assistant via the per-assistant mapping.
52
- - `POST /v1/integrations/twilio/numbers/provision` — Provisions a new number and assigns it to the specified assistant.
59
+ - `POST /v1/integrations/twilio/numbers/provision` — Legacy convenience endpoint that provisions a new number and assigns it to the specified assistant. The main flow now uses proxied Twilio API calls (search + purchase) followed by the assign endpoint.
53
60
  - `GET /v1/integrations/twilio/numbers` — Lists all phone numbers on the shared Twilio account (uses global credentials).
54
61
 
55
62
  Include `assistantId` as a query parameter in assistant-scoped requests whenever:
@@ -110,19 +117,61 @@ The assistant needs a phone number to make calls and send SMS. There are two pat
110
117
 
111
118
  If the user wants to buy a new number through Twilio:
112
119
 
120
+ **3a. Get the credential ID and Account SID:**
121
+
122
+ ```
123
+ credential_store action=list
124
+ ```
125
+
126
+ Find the entry with `service: "twilio"` and `field: "account_sid"`. Note its `credential_id`.
127
+
128
+ Then retrieve the Account SID (needed for Twilio URL paths):
129
+
113
130
  ```bash
114
- curl -s -X POST "$INTERNAL_GATEWAY_BASE_URL/v1/integrations/twilio/numbers/provision" \
131
+ vellum integrations twilio config --json | jq -r '.accountSid'
132
+ ```
133
+
134
+ **3b. Search for available numbers (proxied Twilio API):**
135
+
136
+ ```
137
+ bash:
138
+ network_mode: proxied
139
+ credential_ids: ["<credential_id from 3a>"]
140
+ command: |
141
+ curl -s "https://api.twilio.com/2010-04-01/Accounts/<ACCOUNT_SID>/AvailablePhoneNumbers/US/Local.json?SmsEnabled=true&VoiceEnabled=true&AreaCode=415"
142
+ ```
143
+
144
+ - `AreaCode` is optional — ask the user if they have a preferred area code
145
+ - Replace `US` with a different ISO 3166-1 alpha-2 country code if the user wants a non-US number
146
+ - The proxy automatically injects `Authorization: Basic <credentials>` — do not include an Authorization header
147
+
148
+ The response contains an `available_phone_numbers` array. Present the first few options to the user with their `phone_number` and `friendly_name`.
149
+
150
+ **3c. Purchase the chosen number (proxied Twilio API):**
151
+
152
+ ```
153
+ bash:
154
+ network_mode: proxied
155
+ credential_ids: ["<credential_id from 3a>"]
156
+ command: |
157
+ curl -s -X POST "https://api.twilio.com/2010-04-01/Accounts/<ACCOUNT_SID>/IncomingPhoneNumbers.json" \
158
+ -d "PhoneNumber=+14155551234"
159
+ ```
160
+
161
+ The response includes the purchased number's `phone_number` and `sid`.
162
+
163
+ **3d. Assign locally (saves to config + sets up webhooks):**
164
+
165
+ ```bash
166
+ curl -s -X POST "$INTERNAL_GATEWAY_BASE_URL/v1/integrations/twilio/numbers/assign" \
115
167
  -H "Authorization: Bearer $GATEWAY_AUTH_TOKEN" \
116
168
  -H "Content-Type: application/json" \
117
- -d '{"country":"US","areaCode":"415"}'
169
+ -d '{"phoneNumber":"+14155551234"}'
118
170
  ```
119
171
 
120
- - `areaCode` is optional ask the user if they have a preferred area code
121
- - `country` defaults to `"US"` — ask if they want a different country (ISO 3166-1 alpha-2)
172
+ This persists the number to secure storage and config, and configures Twilio webhooks (voice, status callback, SMS) if a public ingress URL is available. The response includes the new `phoneNumber`. No separate assign call is needed.
122
173
 
123
- The endpoint provisions the number via the Twilio API, automatically assigns it to the assistant (persisting to both secure storage and config), and configures Twilio webhooks (voice, status callback, SMS) if a public ingress URL is available. The response includes the new `phoneNumber`. No separate assign call is needed.
124
-
125
- **Webhook auto-configuration:** When `ingress.publicBaseUrl` is configured, the endpoint automatically sets the following webhooks on the Twilio phone number:
174
+ **Webhook auto-configuration:** When `ingress.publicBaseUrl` is configured, the assign endpoint automatically sets the following webhooks on the Twilio phone number:
126
175
 
127
176
  - Voice webhook: `{publicBaseUrl}/webhooks/twilio/voice`
128
177
  - Voice status callback: `{publicBaseUrl}/webhooks/twilio/status`
@@ -134,13 +183,17 @@ If ingress is not yet configured, webhook setup is skipped gracefully — the nu
134
183
 
135
184
  ### Option B: Assign an Existing Number
136
185
 
137
- If the user already has a Twilio phone number, first list available numbers:
186
+ If the user already has a Twilio phone number, first get the credential ID and Account SID (same as Option A, step 3a), then list available numbers:
138
187
 
139
- ```bash
140
- vellum integrations twilio numbers --json
188
+ ```
189
+ bash:
190
+ network_mode: proxied
191
+ credential_ids: ["<credential_id>"]
192
+ command: |
193
+ curl -s "https://api.twilio.com/2010-04-01/Accounts/<ACCOUNT_SID>/IncomingPhoneNumbers.json"
141
194
  ```
142
195
 
143
- 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.
196
+ The response includes an `incoming_phone_numbers` array with each number's `phone_number`, `friendly_name`, and `capabilities`. Present these to the user and let them choose.
144
197
 
145
198
  Then assign the chosen number:
146
199
 
@@ -20,6 +20,7 @@ import * as appFileEdit from "./bundled-skills/app-builder/tools/app-file-edit.j
20
20
  import * as appFileList from "./bundled-skills/app-builder/tools/app-file-list.js";
21
21
  import * as appFileRead from "./bundled-skills/app-builder/tools/app-file-read.js";
22
22
  import * as appFileWrite from "./bundled-skills/app-builder/tools/app-file-write.js";
23
+ import * as appGenerateIcon from "./bundled-skills/app-builder/tools/app-generate-icon.js";
23
24
  import * as appList from "./bundled-skills/app-builder/tools/app-list.js";
24
25
  import * as appQuery from "./bundled-skills/app-builder/tools/app-query.js";
25
26
  import * as appUpdate from "./bundled-skills/app-builder/tools/app-update.js";
@@ -190,6 +191,7 @@ export const bundledToolRegistry = new Map<string, SkillToolScript>([
190
191
  ["app-builder:tools/app-file-read.ts", appFileRead],
191
192
  ["app-builder:tools/app-file-edit.ts", appFileEdit],
192
193
  ["app-builder:tools/app-file-write.ts", appFileWrite],
194
+ ["app-builder:tools/app-generate-icon.ts", appGenerateIcon],
193
195
 
194
196
  // browser
195
197
  ["browser:tools/browser-navigate.ts", browserNavigate],
@@ -252,6 +252,12 @@ export const SmsConfigSchema = z.object({
252
252
  .optional(),
253
253
  });
254
254
 
255
+ export const WhatsAppConfigSchema = z.object({
256
+ phoneNumber: z
257
+ .string({ error: "whatsapp.phoneNumber must be a string" })
258
+ .default(""),
259
+ });
260
+
255
261
  export const IngressWebhookConfigSchema = z.object({
256
262
  secret: z
257
263
  .string({ error: "ingress.webhook.secret must be a string" })
@@ -392,6 +398,7 @@ export type ContextOverflowRecoveryConfig = z.infer<
392
398
  export type ContextWindowConfig = z.infer<typeof ContextWindowConfigSchema>;
393
399
  export type ModelPricingOverride = z.infer<typeof ModelPricingOverrideSchema>;
394
400
  export type SmsConfig = z.infer<typeof SmsConfigSchema>;
401
+ export type WhatsAppConfig = z.infer<typeof WhatsAppConfigSchema>;
395
402
  export type IngressWebhookConfig = z.infer<typeof IngressWebhookConfigSchema>;
396
403
  export type IngressRateLimitConfig = z.infer<
397
404
  typeof IngressRateLimitConfigSchema
@@ -47,6 +47,22 @@
47
47
  "key": "feature_flags.messaging.enabled",
48
48
  "label": "Messaging",
49
49
  "description": "Enable messaging skill section in the system prompt",
50
+ "defaultEnabled": true
51
+ },
52
+ {
53
+ "id": "messaging-gmail",
54
+ "scope": "assistant",
55
+ "key": "feature_flags.messaging.gmail.enabled",
56
+ "label": "Messaging: Gmail",
57
+ "description": "Allow messaging tools to operate on the Gmail platform",
58
+ "defaultEnabled": false
59
+ },
60
+ {
61
+ "id": "messaging-telegram",
62
+ "scope": "assistant",
63
+ "key": "feature_flags.messaging.telegram.enabled",
64
+ "label": "Messaging: Telegram",
65
+ "description": "Allow messaging tools to operate on the Telegram platform",
50
66
  "defaultEnabled": false
51
67
  },
52
68
  {
@@ -19,6 +19,8 @@ import {
19
19
  getWorkspaceConfigPath,
20
20
  migrateToDataLayout,
21
21
  migrateToWorkspaceLayout,
22
+ readLockfile,
23
+ writeLockfile,
22
24
  } from "../util/platform.js";
23
25
  import { AssistantConfigSchema } from "./schema.js";
24
26
  import type { AssistantConfig } from "./types.js";
@@ -422,6 +424,30 @@ export function saveRawConfig(config: Record<string, unknown>): void {
422
424
  cached = null; // invalidate cache
423
425
  }
424
426
 
427
+ /**
428
+ * Sync client-relevant config values (e.g. platform.baseUrl) to the lockfile
429
+ * so external tools (e.g. vel) can discover them without importing the full
430
+ * config schema. Mirrors the behaviour of `syncConfigToLockfile` in the
431
+ * lightweight CLI (`cli/src/lib/assistant-config.ts`).
432
+ */
433
+ export function syncConfigToLockfile(): void {
434
+ const configPath = getWorkspaceConfigPath();
435
+ if (!existsSync(configPath)) return;
436
+
437
+ try {
438
+ const raw = JSON.parse(readFileSync(configPath, "utf-8")) as Record<
439
+ string,
440
+ unknown
441
+ >;
442
+ const platform = raw.platform as Record<string, unknown> | undefined;
443
+ const data = readLockfile() ?? {};
444
+ data.platformBaseUrl = (platform?.baseUrl as string) || undefined;
445
+ writeLockfile(data);
446
+ } catch {
447
+ // Config file unreadable — skip sync
448
+ }
449
+ }
450
+
425
451
  export function getNestedValue(
426
452
  obj: Record<string, unknown>,
427
453
  path: string,
@@ -49,6 +49,7 @@ export type {
49
49
  ThinkingConfig,
50
50
  TimeoutConfig,
51
51
  UiConfig,
52
+ WhatsAppConfig,
52
53
  } from "./core-schema.js";
53
54
  export {
54
55
  AuditLogConfigSchema,
@@ -69,6 +70,7 @@ export {
69
70
  ThinkingConfigSchema,
70
71
  TimeoutConfigSchema,
71
72
  UiConfigSchema,
73
+ WhatsAppConfigSchema,
72
74
  } from "./core-schema.js";
73
75
  export type { ElevenLabsConfig } from "./elevenlabs-schema.js";
74
76
  export {
@@ -161,6 +163,7 @@ import {
161
163
  ThinkingConfigSchema,
162
164
  TimeoutConfigSchema,
163
165
  UiConfigSchema,
166
+ WhatsAppConfigSchema,
164
167
  } from "./core-schema.js";
165
168
  import { ElevenLabsConfigSchema } from "./elevenlabs-schema.js";
166
169
  import { McpConfigSchema } from "./mcp-schema.js";
@@ -261,6 +264,7 @@ export const AssistantConfigSchema = z
261
264
  ElevenLabsConfigSchema.parse({}),
262
265
  ),
263
266
  sms: SmsConfigSchema.default(SmsConfigSchema.parse({})),
267
+ whatsapp: WhatsAppConfigSchema.default(WhatsAppConfigSchema.parse({})),
264
268
  ingress: IngressConfigSchema,
265
269
  platform: PlatformConfigSchema.default(PlatformConfigSchema.parse({})),
266
270
  daemon: DaemonConfigSchema.default(DaemonConfigSchema.parse({})),
@@ -33,19 +33,6 @@ export function skillFlagKey(skillId: string): string {
33
33
  );
34
34
  }
35
35
 
36
- /**
37
- * @deprecated Use `isAssistantFeatureFlagEnabled` from `./assistant-feature-flags.js` instead.
38
- *
39
- * Thin backward-compatible wrapper that delegates to the canonical resolver.
40
- * Kept to avoid breaking existing call sites during migration.
41
- */
42
- export function isSkillFeatureEnabled(
43
- skillId: string,
44
- config: AssistantConfig,
45
- ): boolean {
46
- return isAssistantFeatureFlagEnabled(skillFlagKey(skillId), config);
47
- }
48
-
49
36
  export function resolveSkillStates(
50
37
  catalog: SkillSummary[],
51
38
  config: AssistantConfig,
@@ -1,6 +1,7 @@
1
1
  import { copyFileSync, existsSync, readFileSync } from "node:fs";
2
2
  import { join } from "node:path";
3
3
 
4
+ import { CLI_HELP_REFERENCE } from "../cli/reference.js";
4
5
  import { listCredentialMetadata } from "../tools/credentials/metadata-store.js";
5
6
  import { resolveBundledDir } from "../util/bundled-asset.js";
6
7
  import { getLogger } from "../util/logger.js";
@@ -20,6 +21,13 @@ const log = getLogger("system-prompt");
20
21
 
21
22
  const PROMPT_FILES = ["SOUL.md", "IDENTITY.md", "USER.md"] as const;
22
23
 
24
+ let cachedCliHelp: string | undefined;
25
+
26
+ /** @internal Reset the CLI help cache — exposed for testing only. */
27
+ export function _resetCliHelpCache(): void {
28
+ cachedCliHelp = undefined;
29
+ }
30
+
23
31
  /**
24
32
  * Copy template prompt files into the data directory if they don't already exist.
25
33
  * Called once during daemon startup so users always have discoverable files to edit.
@@ -149,6 +157,7 @@ export function buildSystemPrompt(): string {
149
157
  }
150
158
  if (getIsContainerized()) parts.push(buildContainerizedSection());
151
159
  parts.push(buildConfigSection());
160
+ parts.push(buildCliReferenceSection());
152
161
  parts.push(buildPostToolResponseSection());
153
162
  parts.push(buildExternalCommsIdentitySection());
154
163
  parts.push(buildChannelAwarenessSection());
@@ -778,6 +787,24 @@ function buildConfigSection(): string {
778
787
  ].join("\n");
779
788
  }
780
789
 
790
+ export function buildCliReferenceSection(): string {
791
+ if (cachedCliHelp === undefined) {
792
+ cachedCliHelp = CLI_HELP_REFERENCE.trim();
793
+ }
794
+
795
+ return [
796
+ "## Assistant CLI",
797
+ "",
798
+ "The `assistant` CLI is installed on the user's machine and available via `bash`.",
799
+ "",
800
+ "```",
801
+ cachedCliHelp,
802
+ "```",
803
+ "",
804
+ "Run `assistant <command> --help` for detailed help on any subcommand.",
805
+ ].join("\n");
806
+ }
807
+
781
808
  /**
782
809
  * Strip lines starting with `_` (comment convention for prompt .md files)
783
810
  * and collapse any resulting consecutive blank lines.
@@ -623,6 +623,31 @@ export function mergeContacts(
623
623
  return getContactInternal(keepId)!;
624
624
  }
625
625
 
626
+ /**
627
+ * Delete a contact by ID. Guardians cannot be deleted as a safety guard.
628
+ * Associated contactChannels and assistantContactMetadata rows are
629
+ * cascade-deleted by the DB schema's onDelete constraints.
630
+ */
631
+ export function deleteContact(
632
+ contactId: string,
633
+ ): "ok" | "not_found" | "is_guardian" {
634
+ const db = getDb();
635
+
636
+ const contact = db
637
+ .select()
638
+ .from(contacts)
639
+ .where(eq(contacts.id, contactId))
640
+ .get();
641
+
642
+ if (!contact) return "not_found";
643
+ if (contact.role === "guardian") return "is_guardian";
644
+
645
+ db.delete(contacts).where(eq(contacts.id, contactId)).run();
646
+
647
+ emitContactChange();
648
+ return "ok";
649
+ }
650
+
626
651
  /**
627
652
  * Find a contact by a specific channel address. Returns null if not found.
628
653
  */
@@ -103,7 +103,7 @@ export class ComputerUseSession {
103
103
  resolve: (result: ToolExecutionResult) => void;
104
104
  }
105
105
  >();
106
- private surfaceState = new Map<
106
+ /** @internal */ surfaceState = new Map<
107
107
  string,
108
108
  { surfaceType: SurfaceType; data: SurfaceData; title?: string }
109
109
  >();
@@ -559,6 +559,7 @@ export async function handleBundleApp(
559
559
  ctx.send(socket, {
560
560
  type: "bundle_app_response",
561
561
  bundlePath: result.bundlePath,
562
+ iconImageBase64: result.iconImageBase64,
562
563
  manifest: result.manifest,
563
564
  });
564
565
  } catch (err) {