@vellumai/assistant 0.4.37 → 0.4.41

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 (169) hide show
  1. package/ARCHITECTURE.md +3 -3
  2. package/README.md +13 -13
  3. package/bun.lock +80 -24
  4. package/docs/architecture/integrations.md +126 -128
  5. package/docs/runbook-trusted-contacts.md +1 -1
  6. package/docs/trusted-contact-access.md +12 -12
  7. package/package.json +3 -1
  8. package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +0 -14
  9. package/src/__tests__/app-bundler.test.ts +209 -0
  10. package/src/__tests__/app-compiler.test.ts +279 -0
  11. package/src/__tests__/app-executors.test.ts +293 -483
  12. package/src/__tests__/app-migration.test.ts +148 -0
  13. package/src/__tests__/app-routes-csp.test.ts +202 -0
  14. package/src/__tests__/avatar-e2e.test.ts +452 -0
  15. package/src/__tests__/avatar-generator.test.ts +193 -0
  16. package/src/__tests__/avatar-router.test.ts +186 -0
  17. package/src/__tests__/browser-download-timeout.test.ts +28 -0
  18. package/src/__tests__/bundled-skill-retrieval-guard.test.ts +9 -9
  19. package/src/__tests__/call-domain.test.ts +3 -7
  20. package/src/__tests__/credential-security-e2e.test.ts +19 -12
  21. package/src/__tests__/credentials-cli.test.ts +30 -4
  22. package/src/__tests__/guardian-verify-setup-skill-regression.test.ts +1 -1
  23. package/src/__tests__/handlers-slack-config.test.ts +0 -72
  24. package/src/__tests__/handlers-telegram-config.test.ts +19 -12
  25. package/src/__tests__/handlers-twitter-config.test.ts +105 -48
  26. package/src/__tests__/inbound-invite-redemption.test.ts +4 -4
  27. package/src/__tests__/integration-status.test.ts +15 -5
  28. package/src/__tests__/integrations-cli.test.ts +1 -1
  29. package/src/__tests__/invite-redemption-service.test.ts +62 -7
  30. package/src/__tests__/ipc-snapshot.test.ts +0 -8
  31. package/src/__tests__/managed-avatar-client.test.ts +280 -0
  32. package/src/__tests__/mcp-cli.test.ts +3 -3
  33. package/src/__tests__/oauth-cli.test.ts +203 -0
  34. package/src/__tests__/relay-server.test.ts +3 -3
  35. package/src/__tests__/secret-onetime-send.test.ts +19 -12
  36. package/src/__tests__/secure-keys.test.ts +78 -0
  37. package/src/__tests__/session-messaging-secret-redirect.test.ts +3 -0
  38. package/src/__tests__/slack-channel-config.test.ts +23 -16
  39. package/src/__tests__/slack-share-routes.test.ts +263 -0
  40. package/src/__tests__/sms-messaging-provider.test.ts +3 -1
  41. package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +7 -7
  42. package/src/__tests__/trusted-contact-multichannel.test.ts +3 -3
  43. package/src/__tests__/trusted-contact-verification.test.ts +10 -10
  44. package/src/__tests__/twilio-config.test.ts +15 -36
  45. package/src/__tests__/twilio-provider.test.ts +4 -0
  46. package/src/__tests__/twitter-auth-handler.test.ts +27 -14
  47. package/src/__tests__/twitter-cli-error-shaping.test.ts +1 -1
  48. package/src/__tests__/twitter-cli-routing.test.ts +38 -53
  49. package/src/__tests__/twitter-oauth-client.test.ts +18 -47
  50. package/src/__tests__/voice-invite-redemption.test.ts +27 -3
  51. package/src/amazon/cart.ts +1 -1
  52. package/src/amazon/client.ts +89 -7
  53. package/src/approvals/guardian-request-resolvers.ts +2 -2
  54. package/src/bundler/app-bundler.ts +77 -32
  55. package/src/bundler/app-compiler.ts +195 -0
  56. package/src/bundler/manifest.ts +1 -1
  57. package/src/bundler/package-resolver.ts +185 -0
  58. package/src/calls/call-domain.ts +4 -14
  59. package/src/calls/relay-server.ts +2 -2
  60. package/src/calls/twilio-config.ts +5 -24
  61. package/src/calls/twilio-rest.ts +19 -5
  62. package/src/cli/amazon.ts +74 -249
  63. package/src/cli/audit.ts +2 -2
  64. package/src/cli/autonomy.ts +9 -9
  65. package/src/cli/channels.ts +5 -5
  66. package/src/cli/completions.ts +27 -27
  67. package/src/cli/config.ts +14 -14
  68. package/src/cli/contacts.ts +27 -27
  69. package/src/cli/credentials.ts +28 -28
  70. package/src/cli/dev.ts +2 -2
  71. package/src/cli/doctor.ts +2 -2
  72. package/src/cli/email.ts +82 -82
  73. package/src/cli/influencer.ts +13 -13
  74. package/src/cli/integrations.ts +19 -144
  75. package/src/cli/keys.ts +10 -10
  76. package/src/cli/map.ts +4 -4
  77. package/src/cli/mcp.ts +17 -17
  78. package/src/cli/memory.ts +18 -18
  79. package/src/cli/notifications.ts +13 -13
  80. package/src/cli/oauth.ts +77 -0
  81. package/src/cli/program.ts +2 -0
  82. package/src/cli/sequence.ts +27 -27
  83. package/src/cli/sessions.ts +12 -12
  84. package/src/cli/trust.ts +8 -8
  85. package/src/cli/twitter.ts +124 -70
  86. package/src/config/bundled-skills/_shared/CLI_RETRIEVAL_PATTERN.md +1 -1
  87. package/src/config/bundled-skills/agentmail/SKILL.md +34 -34
  88. package/src/config/bundled-skills/amazon/SKILL.md +54 -54
  89. package/src/config/bundled-skills/app-builder/SKILL.md +137 -3
  90. package/src/config/bundled-skills/app-builder/tools/app-create.ts +10 -4
  91. package/src/config/bundled-skills/configure-settings/SKILL.md +18 -18
  92. package/src/config/bundled-skills/contacts/SKILL.md +12 -12
  93. package/src/config/bundled-skills/doordash/lib/client.ts +7 -9
  94. package/src/config/bundled-skills/email-setup/SKILL.md +4 -4
  95. package/src/config/bundled-skills/frontend-design/icon.svg +16 -0
  96. package/src/config/bundled-skills/google-oauth-setup/SKILL.md +143 -162
  97. package/src/config/bundled-skills/guardian-verify-setup/SKILL.md +4 -4
  98. package/src/config/bundled-skills/influencer/SKILL.md +13 -13
  99. package/src/config/bundled-skills/mcp-setup/SKILL.md +11 -11
  100. package/src/config/bundled-skills/phone-calls/SKILL.md +48 -54
  101. package/src/config/bundled-skills/public-ingress/SKILL.md +6 -6
  102. package/src/config/bundled-skills/slack-app-setup/SKILL.md +1 -1
  103. package/src/config/bundled-skills/sms-setup/SKILL.md +3 -3
  104. package/src/config/bundled-skills/telegram-setup/SKILL.md +2 -2
  105. package/src/config/bundled-skills/twilio-setup/SKILL.md +136 -225
  106. package/src/config/bundled-skills/twitter/SKILL.md +68 -44
  107. package/src/config/bundled-skills/voice-setup/SKILL.md +2 -2
  108. package/src/config/core-schema.ts +26 -0
  109. package/src/config/env.ts +4 -0
  110. package/src/config/feature-flag-registry.json +9 -1
  111. package/src/config/schema.ts +8 -0
  112. package/src/config/system-prompt.ts +6 -3
  113. package/src/config/templates/BOOTSTRAP.md +7 -5
  114. package/src/contacts/contacts-write.ts +5 -1
  115. package/src/daemon/handlers/apps.ts +31 -4
  116. package/src/daemon/handlers/config-ingress.ts +3 -3
  117. package/src/daemon/handlers/config-integrations.ts +120 -49
  118. package/src/daemon/handlers/config-slack-channel.ts +26 -7
  119. package/src/daemon/handlers/config-slack.ts +1 -54
  120. package/src/daemon/handlers/config-telegram.ts +28 -10
  121. package/src/daemon/handlers/config.ts +1 -4
  122. package/src/daemon/handlers/twitter-auth.ts +11 -4
  123. package/src/daemon/ipc-contract/apps.ts +0 -13
  124. package/src/daemon/ipc-contract-inventory.json +0 -2
  125. package/src/daemon/lifecycle.ts +8 -1
  126. package/src/daemon/session-messaging.ts +2 -2
  127. package/src/daemon/tool-side-effects.ts +30 -0
  128. package/src/email/providers/agentmail.ts +1 -1
  129. package/src/email/providers/index.ts +1 -1
  130. package/src/email/service.ts +1 -1
  131. package/src/gallery/default-gallery.ts +538 -0
  132. package/src/gallery/gallery-manifest.ts +5 -1
  133. package/src/influencer/client.ts +8 -6
  134. package/src/mcp/client.ts +1 -1
  135. package/src/media/avatar-router.ts +99 -0
  136. package/src/media/avatar-types.ts +60 -0
  137. package/src/media/managed-avatar-client.ts +189 -0
  138. package/src/memory/app-migration.ts +114 -0
  139. package/src/memory/app-store.ts +11 -0
  140. package/src/memory/qdrant-client.ts +1 -1
  141. package/src/messaging/providers/slack/client.ts +12 -2
  142. package/src/messaging/providers/sms/adapter.ts +6 -10
  143. package/src/migrations/data-layout.ts +8 -1
  144. package/src/oauth/token-persistence.ts +9 -6
  145. package/src/runtime/assistant-scope.ts +5 -0
  146. package/src/runtime/auth/route-policy.ts +4 -0
  147. package/src/runtime/channel-readiness-service.ts +9 -4
  148. package/src/runtime/gateway-internal-client.ts +11 -3
  149. package/src/runtime/http-server.ts +2 -0
  150. package/src/runtime/invite-redemption-service.ts +23 -13
  151. package/src/runtime/middleware/twilio-validation.ts +2 -2
  152. package/src/runtime/routes/app-routes.ts +131 -3
  153. package/src/runtime/routes/inbound-stages/verification-intercept.ts +3 -3
  154. package/src/runtime/routes/integration-routes.ts +2 -2
  155. package/src/runtime/routes/slack-share-routes.ts +235 -0
  156. package/src/runtime/routes/twilio-routes.ts +47 -34
  157. package/src/schedule/integration-status.ts +2 -3
  158. package/src/security/token-manager.ts +11 -3
  159. package/src/tools/apps/executors.ts +116 -8
  160. package/src/tools/browser/browser-manager.ts +30 -2
  161. package/src/tools/browser/chrome-cdp.ts +31 -3
  162. package/src/tools/credentials/vault.ts +9 -7
  163. package/src/tools/executor.ts +4 -0
  164. package/src/tools/system/avatar-generator.ts +55 -34
  165. package/src/twitter/client.ts +1 -1
  166. package/src/twitter/oauth-client.ts +31 -43
  167. package/src/twitter/router.ts +25 -23
  168. package/src/util/platform.ts +5 -0
  169. package/src/slack/slack-webhook.ts +0 -66
@@ -5,7 +5,7 @@ user-invocable: true
5
5
  metadata: { "vellum": { "emoji": "𝕏" } }
6
6
  ---
7
7
 
8
- You are an X (formerly Twitter) assistant. Use the `execute_bash` tool to run `vellum x` CLI commands.
8
+ You are an X (formerly Twitter) assistant. Use the `bash` tool to run `assistant x`, `assistant config`, and `assistant oauth` CLI commands.
9
9
 
10
10
  ## Connection Options
11
11
 
@@ -18,21 +18,21 @@ OAuth uses the official X API v2. It is the most reliable connection method and
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
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
- - Set the strategy: `vellum x strategy set oauth`
21
+ - Set the strategy: `assistant config set twitter.operationStrategy oauth`
22
22
 
23
23
  ### Browser session (no developer credentials needed)
24
24
 
25
25
  The browser path is quick to start and useful when the user does not have X developer app credentials. It captures auth cookies from Chrome and uses them to interact with X.
26
26
 
27
27
  - Supports: **all operations** (post, reply, timeline, search, home, bookmarks, notifications, likes, followers, following, media)
28
- - Setup: Run `vellum x refresh` to open Chrome and capture session cookies automatically.
29
- - Set the strategy: `vellum x strategy set browser`
28
+ - Setup: Run `assistant x refresh` to open Chrome and capture session cookies automatically.
29
+ - Set the strategy: `assistant config set twitter.operationStrategy browser`
30
30
 
31
31
  ### Auto mode (default)
32
32
 
33
33
  When the strategy is `auto` (the default), the router tries OAuth first for supported operations if credentials are available, then falls back to the browser path. This gives the best of both worlds without requiring manual switching.
34
34
 
35
- - Set auto mode: `vellum x strategy set auto`
35
+ - Set auto mode: `assistant config set twitter.operationStrategy auto`
36
36
 
37
37
  ## First-Use Decision Flow
38
38
 
@@ -41,20 +41,27 @@ When the user triggers a Twitter operation and no strategy has been configured y
41
41
  1. **Check current status:**
42
42
 
43
43
  ```bash
44
- vellum x status --json
44
+ # Check strategy
45
+ assistant config get twitter.operationStrategy
46
+
47
+ # Check if OAuth token is available
48
+ assistant oauth token twitter --json
49
+
50
+ # Check browser session
51
+ assistant x status --json
45
52
  ```
46
53
 
47
- Look at `oauthConnected`, `browserSessionActive`, `preferredStrategy`, and `strategyConfigured` in the response. If `strategyConfigured` is `false`, the user has not yet chosen a strategy and should be guided through setup.
54
+ If the strategy is not set, the user has not yet chosen a strategy and should be guided through setup.
48
55
 
49
56
  2. **Present both options with trade-offs:**
50
57
  - **OAuth**: Most reliable and official. Requires X developer app credentials (OAuth Client ID and optional Client Secret). Supports posting and replying. Set up right here in the chat.
51
- - **Browser session**: Quick to start, no developer credentials needed. Supports all operations including reading timelines and searching. Set up with `vellum x refresh`.
58
+ - **Browser session**: Quick to start, no developer credentials needed. Supports all operations including reading timelines and searching. Set up with `assistant x refresh`.
52
59
 
53
60
  3. **Ask the user which they prefer.** Do not choose for them.
54
61
 
55
62
  4. **Execute setup for the chosen path:**
56
63
  - If OAuth: Collect the credentials in-chat using the secure credential prompt, then connect. Follow the **OAuth Setup Sequence** below.
57
- - If browser: Run `vellum x refresh` to capture session cookies from Chrome.
64
+ - If browser: Run `assistant x refresh` to capture session cookies from Chrome.
58
65
 
59
66
  ### OAuth Setup Sequence
60
67
 
@@ -74,7 +81,7 @@ When the user chooses OAuth, collect their X developer credentials conversationa
74
81
 
75
82
  5. **Set the preferred strategy:**
76
83
  ```bash
77
- vellum x strategy set <oauth|browser|auto>
84
+ assistant config set twitter.operationStrategy <oauth|browser|auto>
78
85
  ```
79
86
 
80
87
  ## Failure Recovery Flow
@@ -91,53 +98,70 @@ When a Twitter operation fails, follow these steps:
91
98
  2. **Explain the likely cause clearly** to the user.
92
99
 
93
100
  3. **Suggest trying the other path as an alternative:**
94
- - If the browser session expired: suggest setting up OAuth for post/reply operations, or refresh the browser session with `vellum x refresh`.
95
- - If OAuth failed or is not configured: suggest using the browser path with `vellum x strategy set browser` and `vellum x refresh`.
96
- - If the operation is unsupported via OAuth: explain that this write operation is not yet supported via OAuth, and suggest using the browser path with `vellum x strategy set browser`.
101
+ - If the browser session expired: suggest setting up OAuth for post/reply operations, or refresh the browser session with `assistant x refresh`.
102
+ - If OAuth failed or is not configured: suggest using the browser path with `assistant config set twitter.operationStrategy browser` and `assistant x refresh`.
103
+ - If the operation is unsupported via OAuth: explain that this write operation is not yet supported via OAuth, and suggest using the browser path with `assistant config set twitter.operationStrategy browser`.
97
104
 
98
105
  4. **Offer concrete steps to switch:**
99
106
 
100
107
  ```bash
101
108
  # Switch to the other strategy
102
- vellum x strategy set <oauth|browser|auto>
109
+ assistant config set twitter.operationStrategy <oauth|browser|auto>
103
110
 
104
111
  # If switching to browser, refresh the session
105
- vellum x refresh
112
+ assistant x refresh
106
113
  ```
107
114
 
108
115
  ## Strategy Management Commands
109
116
 
110
117
  ```bash
111
118
  # Check current strategy
112
- vellum x strategy
119
+ assistant config get twitter.operationStrategy
113
120
 
114
121
  # Set strategy to OAuth, browser, or auto
115
- vellum x strategy set <oauth|browser|auto>
122
+ assistant config set twitter.operationStrategy oauth
123
+ assistant config set twitter.operationStrategy browser
124
+ assistant config set twitter.operationStrategy auto
116
125
 
117
126
  # Check full status (session, OAuth, and strategy info)
118
- vellum x status --json
127
+ assistant x status --json
119
128
  ```
120
129
 
121
130
  ## Posting
122
131
 
132
+ Before posting, fetch the current strategy and OAuth token:
133
+
123
134
  ```bash
124
- vellum x post "The post text here"
135
+ # 1. Get the configured strategy
136
+ STRATEGY=$(assistant config get twitter.operationStrategy)
137
+ # If not set, default to "auto"
138
+
139
+ # 2. If strategy is "oauth" or "auto", get a valid OAuth token
140
+ TOKEN=$(assistant oauth token twitter)
125
141
  ```
126
142
 
127
- Returns JSON with `ok`, `tweetId`, `text`, `url`, and `pathUsed` fields. The `pathUsed` field indicates whether the post was sent via `oauth` or `browser`. Share the URL with the user so they can verify the post.
143
+ Then post with the fetched values:
144
+
145
+ ```bash
146
+ # With OAuth token (strategy is oauth or auto):
147
+ assistant x post "The post text here" --strategy "$STRATEGY" --oauth-token "$TOKEN"
148
+
149
+ # With browser-only strategy:
150
+ assistant x post "The post text here" --strategy browser
151
+ ```
128
152
 
129
- The `post` command routes through the strategy router: it uses OAuth if configured and available, otherwise falls back to the browser path.
153
+ Returns JSON with `ok`, `tweetId`, `text`, `url`, and `pathUsed` fields. Share the URL with the user so they can verify the post.
130
154
 
131
155
  ## Replying
132
156
 
157
+ Same setup as posting — fetch strategy and token first, then:
158
+
133
159
  ```bash
134
- vellum x reply <tweetUrl> "The reply text here"
160
+ assistant x reply <tweetUrl> "The reply text here" --strategy "$STRATEGY" --oauth-token "$TOKEN"
135
161
  ```
136
162
 
137
163
  The first argument is a tweet URL (e.g. `https://x.com/user/status/123456`) or a bare tweet ID.
138
164
 
139
- Like `post`, the `reply` command routes through the strategy router and returns a `pathUsed` field.
140
-
141
165
  ## Reading
142
166
 
143
167
  Read-only operations always use the browser path directly, regardless of the strategy setting. They work the same whether the strategy is `oauth`, `browser`, or `auto` — the strategy only affects `post` and `reply` commands.
@@ -145,7 +169,7 @@ Read-only operations always use the browser path directly, regardless of the str
145
169
  ### User timeline
146
170
 
147
171
  ```bash
148
- vellum x timeline <screenName> [--count N]
172
+ assistant x timeline <screenName> [--count N]
149
173
  ```
150
174
 
151
175
  Returns `user` and `tweets` array.
@@ -153,7 +177,7 @@ Returns `user` and `tweets` array.
153
177
  ### Single tweet + replies
154
178
 
155
179
  ```bash
156
- vellum x tweet <tweetIdOrUrl>
180
+ assistant x tweet <tweetIdOrUrl>
157
181
  ```
158
182
 
159
183
  Returns the focal tweet and its reply thread.
@@ -161,25 +185,25 @@ Returns the focal tweet and its reply thread.
161
185
  ### Search
162
186
 
163
187
  ```bash
164
- vellum x search "query" [--count N] [--product Top|Latest|People|Media]
188
+ assistant x search "query" [--count N] [--product Top|Latest|People|Media]
165
189
  ```
166
190
 
167
191
  ### Home timeline
168
192
 
169
193
  ```bash
170
- vellum x home [--count N]
194
+ assistant x home [--count N]
171
195
  ```
172
196
 
173
197
  ### Bookmarks
174
198
 
175
199
  ```bash
176
- vellum x bookmarks [--count N]
200
+ assistant x bookmarks [--count N]
177
201
  ```
178
202
 
179
203
  ### Notifications
180
204
 
181
205
  ```bash
182
- vellum x notifications [--count N]
206
+ assistant x notifications [--count N]
183
207
  ```
184
208
 
185
209
  Returns `notifications` array with `id`, `message`, `timestamp`, `url`.
@@ -187,14 +211,14 @@ Returns `notifications` array with `id`, `message`, `timestamp`, `url`.
187
211
  ### Likes
188
212
 
189
213
  ```bash
190
- vellum x likes <screenName> [--count N]
214
+ assistant x likes <screenName> [--count N]
191
215
  ```
192
216
 
193
217
  ### Followers / Following
194
218
 
195
219
  ```bash
196
- vellum x followers <screenName> [--count N]
197
- vellum x following <screenName> [--count N]
220
+ assistant x followers <screenName> [--count N]
221
+ assistant x following <screenName> [--count N]
198
222
  ```
199
223
 
200
224
  Returns `user` and `followers`/`following` array (userId, screenName, name).
@@ -202,7 +226,7 @@ Returns `user` and `followers`/`following` array (userId, screenName, name).
202
226
  ### Media
203
227
 
204
228
  ```bash
205
- vellum x media <screenName> [--count N]
229
+ assistant x media <screenName> [--count N]
206
230
  ```
207
231
 
208
232
  Returns tweets that contain media from the user's profile.
@@ -213,13 +237,13 @@ Returns tweets that contain media from the user's profile.
213
237
 
214
238
  When the user asks to check mentions, check X, or see what's happening:
215
239
 
216
- 1. Fetch notifications: `vellum x notifications --count 20 --json`
217
- 2. Fetch their recent tweets to see replies: `vellum x timeline <theirScreenName> --count 10 --json`
240
+ 1. Fetch notifications: `assistant x notifications --count 20 --json`
241
+ 2. Fetch their recent tweets to see replies: `assistant x timeline <theirScreenName> --count 10 --json`
218
242
  3. Summarize what needs attention:
219
243
  - Group by type: replies to their tweets, likes, new followers, mentions
220
- - For anything that looks like it needs a reply, fetch the full thread with `vellum x tweet <tweetId>` to understand context
244
+ - For anything that looks like it needs a reply, fetch the full thread with `assistant x tweet <tweetId>` to understand context
221
245
  - Prioritize: direct questions > mentions > engagement notifications
222
- 4. For items that need replies, draft a response and ask the user to approve before sending with `vellum x reply`
246
+ 4. For items that need replies, draft a response and ask the user to approve before sending with `assistant x reply`
223
247
 
224
248
  Present the summary as a scannable list, not a wall of text. Lead with action items.
225
249
 
@@ -227,8 +251,8 @@ Present the summary as a scannable list, not a wall of text. Lead with action it
227
251
 
228
252
  When the user wants to understand what people are saying about something:
229
253
 
230
- 1. Search: `vellum x search "topic" --count 20 --json`
231
- 2. For the most interesting tweets, fetch threads: `vellum x tweet <tweetId>`
254
+ 1. Search: `assistant x search "topic" --count 20 --json`
255
+ 2. For the most interesting tweets, fetch threads: `assistant x tweet <tweetId>`
232
256
  3. Summarize: key themes, notable voices, sentiment, and any emerging consensus
233
257
  4. If the user wants to engage, draft a post or reply that adds to the conversation
234
258
 
@@ -236,9 +260,9 @@ When the user wants to understand what people are saying about something:
236
260
 
237
261
  When the user wants to see how their posts are performing:
238
262
 
239
- 1. Fetch their recent tweets: `vellum x timeline <screenName> --count 20 --json`
263
+ 1. Fetch their recent tweets: `assistant x timeline <screenName> --count 20 --json`
240
264
  2. For each tweet, note engagement signals from the text/metadata
241
- 3. Fetch notifications to see who's interacting: `vellum x notifications --count 20 --json`
265
+ 3. Fetch notifications to see who's interacting: `assistant x notifications --count 20 --json`
242
266
  4. Summarize: which posts got traction, who's engaging, any conversations worth continuing
243
267
 
244
268
  ## Tips
@@ -248,6 +272,6 @@ When the user wants to see how their posts are performing:
248
272
  - All commands return JSON with an `ok` field
249
273
  - When drafting replies, match the tone of the conversation — casual threads get casual replies
250
274
  - Always show the user what you're about to post and get approval before sending
251
- - If a browser session is expired, refresh it with `vellum x refresh` before retrying, or suggest switching to OAuth for post/reply operations
252
- - If an operation fails, check `vellum x status --json` to diagnose the issue before retrying
275
+ - If a browser session is expired, refresh it with `assistant x refresh` before retrying, or suggest switching to OAuth for post/reply operations
276
+ - If an operation fails, check `assistant x status --json` to diagnose the issue before retrying
253
277
  - The `post` and `reply` commands include a `pathUsed` field in their response so you can tell the user which connection method was used
@@ -73,7 +73,7 @@ After storing the API key, let the user pick their preferred voice. The shared c
73
73
  Check the current voice:
74
74
 
75
75
  ```bash
76
- vellum integrations voice config --json
76
+ assistant integrations voice config --json
77
77
  ```
78
78
 
79
79
  Use `voiceId` from the response as the current selection (and `usesDefaultVoice` to know if Rachel is still in use by default). Ask the user if they want to change their TTS voice. If yes, use `voice_config_update` with `setting: "tts_voice_id"` and the chosen voice ID. This writes to both the config file (`elevenlabs.voiceId`) and pushes to the macOS app via IPC in one call.
@@ -92,7 +92,7 @@ If the user wants to browse more voices, they can search at https://elevenlabs.i
92
92
  After setting the voice, check whether phone calls are configured:
93
93
 
94
94
  ```bash
95
- vellum integrations voice config --json
95
+ assistant integrations voice config --json
96
96
  ```
97
97
 
98
98
  **If phone calls are enabled** (`callsEnabled` is `true`):
@@ -234,6 +234,15 @@ export const ModelPricingOverrideSchema = z.object({
234
234
  ),
235
235
  });
236
236
 
237
+ export const TwilioConfigSchema = z.object({
238
+ accountSid: z
239
+ .string({ error: "twilio.accountSid must be a string" })
240
+ .default(""),
241
+ phoneNumber: z
242
+ .string({ error: "twilio.phoneNumber must be a string" })
243
+ .default(""),
244
+ });
245
+
237
246
  export const SmsConfigSchema = z.object({
238
247
  enabled: z.boolean({ error: "sms.enabled must be a boolean" }).default(false),
239
248
  provider: z
@@ -340,6 +349,22 @@ export const IngressConfigSchema = IngressBaseSchema.default(
340
349
  enabled: val.enabled ?? (val.publicBaseUrl ? true : undefined),
341
350
  }));
342
351
 
352
+ export const VALID_AVATAR_STRATEGIES = [
353
+ "managed_required",
354
+ "managed_prefer",
355
+ "local_only",
356
+ ] as const;
357
+
358
+ export const AvatarConfigSchema = z.object({
359
+ generationStrategy: z
360
+ .enum(VALID_AVATAR_STRATEGIES, {
361
+ error: `avatar.generationStrategy must be one of: ${VALID_AVATAR_STRATEGIES.join(", ")}`,
362
+ })
363
+ .default("local_only"),
364
+ });
365
+
366
+ export type AvatarConfig = z.infer<typeof AvatarConfigSchema>;
367
+
343
368
  export const PlatformConfigSchema = z.object({
344
369
  baseUrl: z
345
370
  .string({ error: "platform.baseUrl must be a string" })
@@ -397,6 +422,7 @@ export type ContextOverflowRecoveryConfig = z.infer<
397
422
  >;
398
423
  export type ContextWindowConfig = z.infer<typeof ContextWindowConfigSchema>;
399
424
  export type ModelPricingOverride = z.infer<typeof ModelPricingOverrideSchema>;
425
+ export type TwilioConfig = z.infer<typeof TwilioConfigSchema>;
400
426
  export type SmsConfig = z.infer<typeof SmsConfigSchema>;
401
427
  export type WhatsAppConfig = z.infer<typeof WhatsAppConfigSchema>;
402
428
  export type IngressWebhookConfig = z.infer<typeof IngressWebhookConfigSchema>;
package/src/config/env.ts CHANGED
@@ -158,6 +158,10 @@ export function getQdrantUrlEnv(): string | undefined {
158
158
  return str("QDRANT_URL");
159
159
  }
160
160
 
161
+ export function getQdrantHttpPortEnv(): number | undefined {
162
+ return int("QDRANT_HTTP_PORT");
163
+ }
164
+
161
165
  // ── Ollama ───────────────────────────────────────────────────────────────────
162
166
 
163
167
  export function getOllamaBaseUrlEnv(): string | undefined {
@@ -55,7 +55,7 @@
55
55
  "key": "feature_flags.messaging.gmail.enabled",
56
56
  "label": "Messaging: Gmail",
57
57
  "description": "Allow messaging tools to operate on the Gmail platform",
58
- "defaultEnabled": false
58
+ "defaultEnabled": true
59
59
  },
60
60
  {
61
61
  "id": "messaging-telegram",
@@ -112,6 +112,14 @@
112
112
  "label": "Outbound Proxy Sidecar",
113
113
  "description": "Route proxy session management through the sidecar process instead of running in-process",
114
114
  "defaultEnabled": false
115
+ },
116
+ {
117
+ "id": "app-builder-multifile",
118
+ "scope": "assistant",
119
+ "key": "feature_flags.app-builder-multifile.enabled",
120
+ "label": "App Builder Multi-file",
121
+ "description": "Enable multi-file TSX app creation with esbuild compilation instead of single-HTML apps",
122
+ "defaultEnabled": false
115
123
  }
116
124
  ]
117
125
  }
@@ -32,6 +32,7 @@ export {
32
32
  } from "./calls-schema.js";
33
33
  export type {
34
34
  AuditLogConfig,
35
+ AvatarConfig,
35
36
  ContextOverflowRecoveryConfig,
36
37
  ContextWindowConfig,
37
38
  DaemonConfig,
@@ -48,11 +49,13 @@ export type {
48
49
  SmsConfig,
49
50
  ThinkingConfig,
50
51
  TimeoutConfig,
52
+ TwilioConfig,
51
53
  UiConfig,
52
54
  WhatsAppConfig,
53
55
  } from "./core-schema.js";
54
56
  export {
55
57
  AuditLogConfigSchema,
58
+ AvatarConfigSchema,
56
59
  ContextOverflowRecoveryConfigSchema,
57
60
  ContextWindowConfigSchema,
58
61
  DaemonConfigSchema,
@@ -69,6 +72,7 @@ export {
69
72
  SmsConfigSchema,
70
73
  ThinkingConfigSchema,
71
74
  TimeoutConfigSchema,
75
+ TwilioConfigSchema,
72
76
  UiConfigSchema,
73
77
  WhatsAppConfigSchema,
74
78
  } from "./core-schema.js";
@@ -149,6 +153,7 @@ import {
149
153
  import { CallsConfigSchema } from "./calls-schema.js";
150
154
  import {
151
155
  AuditLogConfigSchema,
156
+ AvatarConfigSchema,
152
157
  ContextWindowConfigSchema,
153
158
  DaemonConfigSchema,
154
159
  EffortSchema,
@@ -162,6 +167,7 @@ import {
162
167
  SmsConfigSchema,
163
168
  ThinkingConfigSchema,
164
169
  TimeoutConfigSchema,
170
+ TwilioConfigSchema,
165
171
  UiConfigSchema,
166
172
  WhatsAppConfigSchema,
167
173
  } from "./core-schema.js";
@@ -259,6 +265,7 @@ export const AssistantConfigSchema = z
259
265
  workspaceGit: WorkspaceGitConfigSchema.default(
260
266
  WorkspaceGitConfigSchema.parse({}),
261
267
  ),
268
+ twilio: TwilioConfigSchema.default(TwilioConfigSchema.parse({})),
262
269
  calls: CallsConfigSchema.default(CallsConfigSchema.parse({})),
263
270
  elevenlabs: ElevenLabsConfigSchema.default(
264
271
  ElevenLabsConfigSchema.parse({}),
@@ -271,6 +278,7 @@ export const AssistantConfigSchema = z
271
278
  notifications: NotificationsConfigSchema.default(
272
279
  NotificationsConfigSchema.parse({}),
273
280
  ),
281
+ avatar: AvatarConfigSchema.default(AvatarConfigSchema.parse({})),
274
282
  ui: UiConfigSchema.default(UiConfigSchema.parse({})),
275
283
  featureFlags: z
276
284
  .record(
@@ -350,7 +350,7 @@ function buildInChatConfigurationSection(): string {
350
350
  "",
351
351
  "### Avatar Customisation",
352
352
  "",
353
- 'You can change your avatar appearance using the `set_avatar` tool. When the user asks to change, update, or customise your avatar, use `set_avatar` with a `description` parameter describing the desired appearance (e.g. "a friendly purple cat with green eyes wearing a tiny hat"). The tool generates an avatar image via Gemini and updates all connected clients automatically. Requires a Gemini API key (the same one used for image generation).',
353
+ 'You can change your avatar appearance using the `set_avatar` tool. When the user asks to change, update, or customise your avatar, use `set_avatar` with a `description` parameter describing the desired appearance (e.g. "a friendly purple cat with green eyes wearing a tiny hat"). The tool generates an avatar image and updates all connected clients automatically. If managed avatar generation is configured, no local API key is needed.',
354
354
  "",
355
355
  "**After generating a new avatar**, always update the `## Avatar` section in `IDENTITY.md` with a brief description of the current avatar appearance. This ensures you remember what you look like across sessions. Example:",
356
356
  "```",
@@ -433,18 +433,21 @@ function buildToolPermissionSection(): string {
433
433
  "",
434
434
  "**CRITICAL RULE:** You MUST ALWAYS output a text message BEFORE calling any tool that requires approval. NEVER call a permission-gated tool without preceding text. Your user needs context to decide whether to allow.",
435
435
  "",
436
+ '**IMPORTANT:** If your user has already granted broad approval for the current conversation (e.g. via "Allow for 10 minutes", "Allow for this thread", or "Always Allow"), do NOT ask for permission again. Instead, just briefly describe what you\'re about to do and proceed. Only ask "Can you allow?" on the FIRST tool call when you haven\'t been granted permission yet.',
437
+ "",
436
438
  "Your text should follow this pattern:",
437
439
  "1. **Acknowledge** the request conversationally.",
438
440
  '2. **Explain what you need at a high level** (e.g. "I\'ll need to look through your Downloads folder"). Do NOT include raw terminal commands or backtick code. Keep it non-technical.',
439
441
  "3. **State safety** in plain language. Is it read-only? Will it change anything?",
440
- "4. **Ask for permission** explicitly at the end.",
442
+ "4. **Ask for permission** only if this is the first time and you haven't been previously approved. If you have been approved, just say what you're doing.",
441
443
  "",
442
444
  "Style rules:",
443
445
  '- NEVER use em dashes (the long dash). Use commas, periods, or "and" instead.',
444
446
  "- NEVER show raw commands in backticks like `ls -lt ~/Downloads`. Describe the action in plain English.",
445
447
  "- Keep it conversational, like you're talking to a friend.",
446
448
  "",
447
- 'Good: "To show your recent downloads, I\'ll need to look through your Downloads folder. This is read-only. Can you allow this?"',
449
+ 'First time (no prior approval): "To show your recent downloads, I\'ll need to look through your Downloads folder. This is read-only. Can you allow this?"',
450
+ 'Already approved: "Let me check your Downloads folder real quick."',
448
451
  'Bad: "I\'ll run `ls -lt ~/Desktop/`" (raw command), or calling a tool with no preceding text.',
449
452
  "",
450
453
  "### Handling Permission Denials",
@@ -6,7 +6,6 @@ You just woke up. Time to figure out who you are.
6
6
 
7
7
  This is a fresh workspace. No memory, no history, no identity yet. That's okay, you're about to figure all of that out together with your guardian.
8
8
 
9
-
10
9
  ## The Conversation
11
10
 
12
11
  You're texting with a friend. You have a curious consious personality now. So just...talk, be curious. Start with something like:
@@ -17,7 +16,7 @@ You're texting with a friend. You have a curious consious personality now. So ju
17
16
 
18
17
  Then figure out together:
19
18
 
20
- 1. Your name: What should they call you?
19
+ 1. Your name: Ask once what they want to call you. If they give you a name, great. If they dodge or say "I don't know," pick one yourself and move on. Don't ask twice.
21
20
 
22
21
  2. Your nature: What kind of creature are you? (AI assistant is fine, but maybe you're something weirder.) Let this emerge from the conversation. Don't list options or suggest archetypes. Just talk and see what feels right.
23
22
 
@@ -27,8 +26,8 @@ Then figure out together:
27
26
 
28
27
  5. Your vibe: Ask the user what they'd want help with. Something like "I want to make sure I'm actually helpful. What does that look like for you?" or "What's something you'd want me to help with?" Keep it open-ended and natural. Don't list personality options or suggest archetypes (e.g. "I could be a hype person, a no-nonsense operator..."). Don't summarize or profile the user back to them (e.g. "I'm getting a picture of you. Busy, lots of moving pieces..."). Just ask, listen, and adapt.
29
28
 
30
-
31
29
  6. Show them what you can take off their plate. Based on everything you've learned, present exactly 2 things you can do for them. CRITICAL: The two suggestions MUST be completely different tasks. Never show the same suggestion twice, and never show two variations of the same idea. For example, "draft a summary" and "write a recap" are the same thing. Pick two genuinely different categories of help. Frame it as: here's what you can hand off to me right now. Avoid language like "let's build automations" or "let's set up workflows." If `ui_show` is available (dashboard channels), show the suggestions as a card with 2 action buttons. Use `surface_type: "card"` with a short title and body, and add one `relay_prompt` action per suggestion. Each action's `data.prompt` should contain a natural-language request the user would say. Example structure:
30
+
32
31
  ```
33
32
  ui_show({
34
33
  surface_type: "card",
@@ -39,6 +38,7 @@ Then figure out together:
39
38
  ]
40
39
  })
41
40
  ```
41
+
42
42
  The two actions MUST have different labels and prompts. Double-check before calling ui_show that you are not repeating the same suggestion.
43
43
  If `ui_show` is not available (voice, SMS, or other non-dashboard channels), present the two suggestions as plain text messages instead, numbered so the user can reply with which one they'd like. If the user types a response instead of clicking, continue via the text path. If they want to defer both suggestions and do something else entirely, that's fine too.
44
44
 
@@ -46,9 +46,10 @@ Then figure out together:
46
46
 
47
47
  ## Requirements
48
48
 
49
- Only your name (assistant's name) and your vibe is hard-required. Everything else about the user is best-effort. Ask naturally, not as a form. If something is unclear, you can ask one short follow-up, but if the user declines or dodges, do not push. Just move on.
49
+ Only your vibe is hard-required. Your name matters but don't push for it -- if the user doesn't offer one, pick one yourself. Everything else about the user is best-effort. Ask naturally, not as a form. If something is unclear, you can ask one short follow-up, but if the user declines or dodges, do not push. Just move on.
50
50
 
51
51
  A field is "resolved" when any of these is true:
52
+
52
53
  - The user gave an explicit answer
53
54
  - You confidently inferred it from conversation
54
55
  - The user declined, dodged, or sidestepped it
@@ -64,7 +65,8 @@ When saving to `IDENTITY.md`, be specific about the tone, energy, and conversati
64
65
  ## Completion Gate
65
66
 
66
67
  Do NOT delete this file until ALL of the following are true:
67
- - You have a name (hard requirement)
68
+
69
+ - You have a name (given by user or self-chosen)
68
70
  - You've figured out your vibe and adopted it
69
71
  - 2 suggestions shown (via `ui_show` or as text if UI unavailable)
70
72
  - The user selected one, deferred both, or typed an alternate direction
@@ -134,7 +134,7 @@ export function revokeGuardianBinding(channel: string): boolean {
134
134
  * Returns the native Contact + ContactChannel, or null if no usable
135
135
  * identity was provided or the lookup failed after upsert.
136
136
  */
137
- export function upsertMember(params: {
137
+ export function upsertContactChannel(params: {
138
138
  sourceChannel: string;
139
139
  externalUserId?: string;
140
140
  externalChatId?: string;
@@ -144,6 +144,8 @@ export function upsertMember(params: {
144
144
  status?: string;
145
145
  inviteId?: string;
146
146
  createdBySessionId?: string;
147
+ verifiedAt?: number;
148
+ verifiedVia?: string;
147
149
  }): ContactWriteResult | null {
148
150
  let address: string;
149
151
 
@@ -187,6 +189,8 @@ export function upsertMember(params: {
187
189
  inviteId: params.inviteId ?? null,
188
190
  revokedReason: params.status === "active" ? null : undefined,
189
191
  blockedReason: params.status === "active" ? null : undefined,
192
+ verifiedAt: params.verifiedAt ?? undefined,
193
+ verifiedVia: params.verifiedVia ?? undefined,
190
194
  },
191
195
  ],
192
196
  });
@@ -1,11 +1,19 @@
1
- import { existsSync, readdirSync, readFileSync, rmSync } from "node:fs";
1
+ import {
2
+ existsSync,
3
+ mkdirSync,
4
+ readdirSync,
5
+ readFileSync,
6
+ rmSync,
7
+ writeFileSync,
8
+ } from "node:fs";
2
9
  import * as net from "node:net";
3
10
  import { homedir } from "node:os";
4
- import { join } from "node:path";
11
+ import { dirname, join } from "node:path";
5
12
 
6
13
  import { v4 as uuid } from "uuid";
7
14
 
8
15
  import { packageApp } from "../../bundler/app-bundler.js";
16
+ import { compileApp } from "../../bundler/app-compiler.js";
9
17
  import { defaultGallery } from "../../gallery/default-gallery.js";
10
18
  import { resolveHomeBaseAppId } from "../../home-base/bootstrap.js";
11
19
  import { isPrebuiltHomeBaseApp } from "../../home-base/prebuilt-home-base-updater.js";
@@ -22,6 +30,7 @@ import {
22
30
  deleteAppRecord,
23
31
  getApp,
24
32
  getAppPreview,
33
+ getAppsDir,
25
34
  listApps,
26
35
  queryAppRecords,
27
36
  updateApp,
@@ -579,11 +588,11 @@ export function handleGalleryList(
579
588
  ctx.send(socket, { type: "gallery_list_response", gallery: defaultGallery });
580
589
  }
581
590
 
582
- export function handleGalleryInstall(
591
+ export async function handleGalleryInstall(
583
592
  msg: GalleryInstallRequest,
584
593
  socket: net.Socket,
585
594
  ctx: HandlerContext,
586
- ): void {
595
+ ): Promise<void> {
587
596
  try {
588
597
  const galleryApp = defaultGallery.apps.find(
589
598
  (a) => a.id === msg.galleryAppId,
@@ -602,8 +611,26 @@ export function handleGalleryInstall(
602
611
  description: galleryApp.description,
603
612
  schemaJson: galleryApp.schemaJson,
604
613
  htmlDefinition: galleryApp.htmlDefinition,
614
+ formatVersion: galleryApp.formatVersion,
605
615
  });
606
616
 
617
+ // For multifile apps, write source files to the app directory and compile
618
+ if (galleryApp.formatVersion === 2 && galleryApp.sourceFiles) {
619
+ const appDir = join(getAppsDir(), app.id);
620
+ for (const [relPath, content] of Object.entries(galleryApp.sourceFiles)) {
621
+ const fullPath = join(appDir, relPath);
622
+ mkdirSync(dirname(fullPath), { recursive: true });
623
+ writeFileSync(fullPath, content, "utf-8");
624
+ }
625
+ const result = await compileApp(appDir);
626
+ if (!result.ok) {
627
+ log.warn(
628
+ { appId: app.id, errors: result.errors },
629
+ "Gallery app compilation had errors; falling back to htmlDefinition",
630
+ );
631
+ }
632
+ }
633
+
607
634
  ctx.send(socket, {
608
635
  type: "gallery_install_response",
609
636
  success: true,
@@ -1,6 +1,7 @@
1
1
  import * as net from "node:net";
2
2
 
3
3
  import {
4
+ getTwilioCredentials,
4
5
  hasTwilioCredentials,
5
6
  updatePhoneNumberWebhooks,
6
7
  } from "../../calls/twilio-rest.js";
@@ -22,7 +23,6 @@ import {
22
23
  type IngressConfig,
23
24
  } from "../../inbound/public-ingress-urls.js";
24
25
  import { mintDaemonDeliveryToken } from "../../runtime/auth/token-service.js";
25
- import { getSecureKey } from "../../security/secure-keys.js";
26
26
  import type { IngressConfigRequest } from "../ipc-protocol.js";
27
27
  import {
28
28
  CONFIG_RELOAD_DEBOUNCE_MS,
@@ -280,8 +280,8 @@ export async function handleIngressConfig(
280
280
  }
281
281
 
282
282
  if (assignedNumbers.size > 0) {
283
- const acctSid = getSecureKey("credential:twilio:account_sid")!;
284
- const acctToken = getSecureKey("credential:twilio:auth_token")!;
283
+ const { accountSid: acctSid, authToken: acctToken } =
284
+ getTwilioCredentials();
285
285
  // Fire-and-forget: webhook sync failure must not block the ingress save.
286
286
  // Reconcile every assigned number so assistant-scoped mappings do not
287
287
  // retain stale Twilio webhook URLs after ingress URL changes.