@vellumai/assistant 0.4.3 → 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.
- package/.env.example +3 -0
- package/ARCHITECTURE.md +40 -3
- package/README.md +43 -35
- package/package.json +1 -1
- package/scripts/ipc/generate-swift.ts +1 -0
- package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +58 -120
- package/src/__tests__/actor-token-service.test.ts +1099 -0
- package/src/__tests__/agent-loop.test.ts +51 -0
- package/src/__tests__/approval-routes-http.test.ts +2 -0
- package/src/__tests__/assistant-events-sse-hardening.test.ts +7 -5
- package/src/__tests__/assistant-id-boundary-guard.test.ts +125 -0
- package/src/__tests__/call-controller.test.ts +49 -0
- package/src/__tests__/call-pointer-message-composer.test.ts +171 -0
- package/src/__tests__/call-pointer-messages.test.ts +93 -3
- package/src/__tests__/call-pointer-no-hardcoded-copy.guard.test.ts +42 -0
- package/src/__tests__/callback-handoff-copy.test.ts +186 -0
- package/src/__tests__/channel-approval-routes.test.ts +133 -12
- package/src/__tests__/channel-guardian.test.ts +0 -87
- package/src/__tests__/channel-readiness-service.test.ts +10 -16
- package/src/__tests__/checker.test.ts +33 -12
- package/src/__tests__/config-schema.test.ts +4 -0
- package/src/__tests__/confirmation-request-guardian-bridge.test.ts +410 -0
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +256 -0
- package/src/__tests__/conversation-routes.test.ts +12 -3
- package/src/__tests__/credential-security-invariants.test.ts +1 -1
- package/src/__tests__/daemon-server-session-init.test.ts +4 -0
- package/src/__tests__/guardian-actions-endpoint.test.ts +19 -14
- package/src/__tests__/guardian-dispatch.test.ts +8 -0
- package/src/__tests__/guardian-outbound-http.test.ts +4 -4
- package/src/__tests__/guardian-question-mode.test.ts +200 -0
- package/src/__tests__/guardian-routing-invariants.test.ts +178 -0
- package/src/__tests__/guardian-routing-state.test.ts +525 -0
- package/src/__tests__/handle-user-message-secret-resume.test.ts +2 -0
- package/src/__tests__/handlers-telegram-config.test.ts +0 -83
- package/src/__tests__/handlers-user-message-approval-consumption.test.ts +55 -0
- package/src/__tests__/headless-browser-navigate.test.ts +2 -0
- package/src/__tests__/ipc-snapshot.test.ts +18 -51
- package/src/__tests__/non-member-access-request.test.ts +131 -8
- package/src/__tests__/notification-decision-fallback.test.ts +129 -4
- package/src/__tests__/notification-decision-strategy.test.ts +62 -2
- package/src/__tests__/notification-guardian-path.test.ts +3 -0
- package/src/__tests__/recording-intent-handler.test.ts +1 -0
- package/src/__tests__/relay-server.test.ts +841 -39
- package/src/__tests__/send-endpoint-busy.test.ts +5 -0
- package/src/__tests__/session-agent-loop.test.ts +1 -0
- package/src/__tests__/session-confirmation-signals.test.ts +523 -0
- package/src/__tests__/session-init.benchmark.test.ts +0 -1
- package/src/__tests__/session-surfaces-task-progress.test.ts +1 -1
- package/src/__tests__/session-tool-setup-app-refresh.test.ts +81 -2
- package/src/__tests__/session-tool-setup-memory-scope.test.ts +1 -1
- package/src/__tests__/session-tool-setup-side-effect-flag.test.ts +1 -1
- package/src/__tests__/tool-executor.test.ts +21 -2
- package/src/__tests__/tool-grant-request-escalation.test.ts +333 -27
- package/src/__tests__/trusted-contact-approval-notifier.test.ts +678 -0
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +1064 -0
- package/src/__tests__/twilio-config.test.ts +2 -13
- package/src/agent/loop.ts +1 -1
- package/src/approvals/guardian-decision-primitive.ts +10 -2
- package/src/approvals/guardian-request-resolvers.ts +128 -9
- package/src/calls/call-constants.ts +21 -0
- package/src/calls/call-controller.ts +9 -2
- package/src/calls/call-domain.ts +28 -7
- package/src/calls/call-pointer-message-composer.ts +154 -0
- package/src/calls/call-pointer-messages.ts +106 -27
- package/src/calls/guardian-dispatch.ts +4 -2
- package/src/calls/relay-server.ts +424 -12
- package/src/calls/twilio-config.ts +4 -11
- package/src/calls/twilio-routes.ts +1 -1
- package/src/calls/types.ts +3 -1
- package/src/cli.ts +5 -4
- package/src/config/bundled-skills/agentmail/SKILL.md +4 -0
- package/src/config/bundled-skills/app-builder/SKILL.md +146 -10
- package/src/config/bundled-skills/app-builder/TOOLS.json +1 -1
- package/src/config/bundled-skills/email-setup/SKILL.md +1 -1
- package/src/config/bundled-skills/google-oauth-setup/SKILL.md +105 -81
- package/src/config/bundled-skills/messaging/SKILL.md +61 -12
- package/src/config/bundled-skills/messaging/TOOLS.json +58 -0
- package/src/config/bundled-skills/messaging/tools/gmail-sender-digest.ts +6 -1
- package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +35 -0
- package/src/config/bundled-skills/messaging/tools/messaging-sender-digest.ts +52 -0
- package/src/config/bundled-skills/phone-calls/SKILL.md +30 -39
- package/src/config/bundled-skills/twitter/SKILL.md +3 -3
- package/src/config/bundled-skills/vercel-token-setup/SKILL.md +1 -0
- package/src/config/calls-schema.ts +24 -0
- package/src/config/env.ts +22 -0
- package/src/config/feature-flag-registry.json +8 -0
- package/src/config/schema.ts +2 -2
- package/src/config/skills.ts +11 -0
- package/src/config/system-prompt.ts +11 -1
- package/src/config/templates/SOUL.md +2 -0
- package/src/config/vellum-skills/sms-setup/SKILL.md +71 -82
- package/src/config/vellum-skills/trusted-contacts/SKILL.md +10 -9
- package/src/config/vellum-skills/twilio-setup/SKILL.md +88 -73
- package/src/daemon/call-pointer-generators.ts +59 -0
- package/src/daemon/computer-use-session.ts +2 -5
- package/src/daemon/handlers/apps.ts +76 -20
- package/src/daemon/handlers/config-channels.ts +5 -55
- package/src/daemon/handlers/config-inbox.ts +9 -3
- package/src/daemon/handlers/config-ingress.ts +28 -3
- package/src/daemon/handlers/config-telegram.ts +12 -0
- package/src/daemon/handlers/config.ts +2 -6
- package/src/daemon/handlers/pairing.ts +2 -0
- package/src/daemon/handlers/sessions.ts +48 -3
- package/src/daemon/handlers/shared.ts +17 -2
- package/src/daemon/ipc-contract/integrations.ts +1 -99
- package/src/daemon/ipc-contract/messages.ts +47 -1
- package/src/daemon/ipc-contract/notifications.ts +11 -0
- package/src/daemon/ipc-contract-inventory.json +2 -4
- package/src/daemon/lifecycle.ts +17 -0
- package/src/daemon/server.ts +14 -1
- package/src/daemon/session-agent-loop-handlers.ts +20 -0
- package/src/daemon/session-agent-loop.ts +22 -11
- package/src/daemon/session-lifecycle.ts +1 -1
- package/src/daemon/session-process.ts +11 -1
- package/src/daemon/session-runtime-assembly.ts +3 -0
- package/src/daemon/session-surfaces.ts +3 -2
- package/src/daemon/session.ts +88 -1
- package/src/daemon/tool-side-effects.ts +22 -0
- package/src/home-base/prebuilt/brain-graph.html +1483 -0
- package/src/home-base/prebuilt/index.html +40 -0
- package/src/inbound/platform-callback-registration.ts +157 -0
- package/src/memory/canonical-guardian-store.ts +1 -1
- package/src/memory/db-init.ts +4 -0
- package/src/memory/migrations/038-actor-token-records.ts +39 -0
- package/src/memory/migrations/index.ts +1 -0
- package/src/memory/schema.ts +16 -0
- package/src/messaging/provider-types.ts +24 -0
- package/src/messaging/provider.ts +7 -0
- package/src/messaging/providers/gmail/adapter.ts +127 -0
- package/src/messaging/providers/sms/adapter.ts +40 -37
- package/src/notifications/adapters/macos.ts +45 -2
- package/src/notifications/broadcaster.ts +16 -0
- package/src/notifications/copy-composer.ts +39 -1
- package/src/notifications/decision-engine.ts +22 -9
- package/src/notifications/destination-resolver.ts +16 -2
- package/src/notifications/emit-signal.ts +16 -8
- package/src/notifications/guardian-question-mode.ts +419 -0
- package/src/notifications/signal.ts +14 -3
- package/src/permissions/checker.ts +13 -1
- package/src/permissions/prompter.ts +14 -0
- package/src/providers/anthropic/client.ts +20 -0
- package/src/providers/provider-send-message.ts +15 -3
- package/src/runtime/access-request-helper.ts +71 -1
- package/src/runtime/actor-token-service.ts +234 -0
- package/src/runtime/actor-token-store.ts +236 -0
- package/src/runtime/channel-approvals.ts +5 -3
- package/src/runtime/channel-readiness-service.ts +23 -64
- package/src/runtime/channel-readiness-types.ts +3 -4
- package/src/runtime/channel-retry-sweep.ts +4 -1
- package/src/runtime/confirmation-request-guardian-bridge.ts +197 -0
- package/src/runtime/guardian-action-followup-executor.ts +1 -1
- package/src/runtime/guardian-context-resolver.ts +82 -0
- package/src/runtime/guardian-outbound-actions.ts +0 -3
- package/src/runtime/guardian-reply-router.ts +67 -30
- package/src/runtime/guardian-vellum-migration.ts +57 -0
- package/src/runtime/http-server.ts +65 -12
- package/src/runtime/http-types.ts +13 -0
- package/src/runtime/invite-redemption-service.ts +8 -0
- package/src/runtime/local-actor-identity.ts +76 -0
- package/src/runtime/middleware/actor-token.ts +271 -0
- package/src/runtime/routes/approval-routes.ts +82 -7
- package/src/runtime/routes/brain-graph-routes.ts +222 -0
- package/src/runtime/routes/channel-readiness-routes.ts +71 -0
- package/src/runtime/routes/conversation-routes.ts +140 -52
- package/src/runtime/routes/events-routes.ts +20 -5
- package/src/runtime/routes/guardian-action-routes.ts +45 -3
- package/src/runtime/routes/guardian-approval-interception.ts +29 -0
- package/src/runtime/routes/guardian-bootstrap-routes.ts +145 -0
- package/src/runtime/routes/inbound-message-handler.ts +143 -2
- package/src/runtime/routes/integration-routes.ts +7 -15
- package/src/runtime/routes/pairing-routes.ts +163 -0
- package/src/runtime/routes/twilio-routes.ts +934 -0
- package/src/runtime/tool-grant-request-helper.ts +3 -1
- package/src/security/oauth2.ts +27 -2
- package/src/security/token-manager.ts +46 -10
- package/src/tools/browser/browser-execution.ts +4 -3
- package/src/tools/browser/browser-handoff.ts +10 -18
- package/src/tools/browser/browser-manager.ts +80 -25
- package/src/tools/browser/browser-screencast.ts +35 -119
- package/src/tools/permission-checker.ts +15 -4
- package/src/tools/tool-approval-handler.ts +242 -18
- package/src/__tests__/handlers-twilio-config.test.ts +0 -1928
- package/src/daemon/handlers/config-twilio.ts +0 -1082
|
@@ -7,6 +7,21 @@ metadata: {"vellum": {"emoji": "💬"}}
|
|
|
7
7
|
|
|
8
8
|
You are a unified messaging assistant with access to multiple platforms (Slack, Gmail, Telegram, and more). Use the messaging tools to help users read, search, organize, draft, and send messages across all connected platforms.
|
|
9
9
|
|
|
10
|
+
## Email Routing Priority
|
|
11
|
+
|
|
12
|
+
When the user mentions "email" — sending, reading, checking, decluttering, drafting, or anything else — **always default to the user's own email (Gmail)** unless they explicitly ask about the assistant's own email address (e.g., "set up your email", "send from your address", "check your inbox"). The vast majority of email requests are about the user's Gmail, not the assistant's AgentMail address.
|
|
13
|
+
|
|
14
|
+
Do not offer AgentMail as an option or mention it unless the user specifically asks. If Gmail is not connected, guide them through Gmail setup — do not suggest AgentMail as an alternative.
|
|
15
|
+
|
|
16
|
+
## Communication Style
|
|
17
|
+
|
|
18
|
+
- **Be action-oriented.** When the user asks to do something ("declutter", "check my email"), start doing it immediately. Don't ask for permission to read their inbox — that's obviously what they want.
|
|
19
|
+
- **Keep it human.** Never mention OAuth, tokens, APIs, sandboxes, credential proxies, or other technical internals. If something isn't working, say "Gmail needs to be reconnected" — not "the OAuth2 access token for integration:gmail has expired."
|
|
20
|
+
- **Show progress.** When running a tool that scans many emails, tell the user what you're doing: "Scanning your inbox for clutter..." Don't go silent.
|
|
21
|
+
- **Be brief and warm.** One or two sentences per update is plenty. Don't over-explain what you're about to do — just do it and narrate lightly.
|
|
22
|
+
|
|
23
|
+
When a platform is connected (auth test succeeds), always use the messaging API tools for that platform. Never fall back to browser automation, shell commands (bash, curl), or any other approach for operations that messaging tools can handle. The messaging tools handle authentication internally — never try to access tokens or call APIs directly. Browser automation is only appropriate for initial credential setup (OAuth consent screens), not for day-to-day messaging operations.
|
|
24
|
+
|
|
10
25
|
## Connection Setup
|
|
11
26
|
|
|
12
27
|
Before using any messaging tool, verify that the platform is connected by calling `messaging_auth_test` with the appropriate `platform` parameter. If the call fails with a token/authorization error, follow the steps below.
|
|
@@ -15,6 +30,14 @@ Before using any messaging tool, verify that the platform is connected by callin
|
|
|
15
30
|
|
|
16
31
|
Gmail, Slack, and Telegram setup all require a publicly reachable URL for OAuth callbacks or webhook delivery. The **public-ingress** skill handles ngrok tunnel setup and persists the URL as `ingress.publicBaseUrl`. Each setup skill below declares `public-ingress` as a dependency and will prompt you to run it if `ingress.publicBaseUrl` is not configured.
|
|
17
32
|
|
|
33
|
+
### Email Connection Flow
|
|
34
|
+
|
|
35
|
+
When the user asks to "connect my email", "set up email", "manage my email", or similar — and has not named a specific provider:
|
|
36
|
+
|
|
37
|
+
1. **Discover what's connected.** Call `messaging_auth_test` for `gmail` (and any other email-capable platforms). If one succeeds, tell the user it's already connected and proceed with their request.
|
|
38
|
+
2. **If nothing is connected**, ask which provider they use — but keep it brief and conversational (e.g., "Which email do you use — Gmail, Outlook, etc.?"), not a numbered list of options with descriptions.
|
|
39
|
+
3. **Once the provider is known, act immediately.** Don't present setup options or explain OAuth. If it's Gmail, follow the Gmail section below. For any other provider, let the user know that only Gmail is fully supported right now, and offer to set up Gmail instead.
|
|
40
|
+
|
|
18
41
|
### Gmail
|
|
19
42
|
1. **Try connecting directly first.** Call `credential_store` with `action: "oauth2_connect"` and `service: "gmail"`. The tool auto-fills Google's OAuth endpoints and looks up any previously stored client credentials — so this single call may be all that's needed.
|
|
20
43
|
2. **If it fails because no client_id is found:** The user needs to create Google Cloud OAuth credentials first. Install and load the **google-oauth-setup** skill (which depends on **public-ingress** for the redirect URI):
|
|
@@ -70,12 +93,21 @@ If the user asks to verify their guardian identity for any channel (SMS, voice,
|
|
|
70
93
|
|
|
71
94
|
The guardian-verify-setup skill handles the full outbound verification flow for all supported channels. It collects the user's destination (phone number or Telegram chat ID/handle), initiates an outbound verification session, and guides the user through entering or replying with the verification code. This is the single source of truth for guardian verification setup -- do not duplicate the verification flow inline.
|
|
72
95
|
|
|
96
|
+
## Error Recovery
|
|
97
|
+
|
|
98
|
+
When a messaging tool fails with a token or authorization error:
|
|
99
|
+
|
|
100
|
+
1. **Try to reconnect silently.** Call `credential_store` with `action: "oauth2_connect"` and the appropriate `service`. This often resolves expired tokens automatically.
|
|
101
|
+
2. **If reconnection fails, go straight to setup.** Don't present options, ask which route the user prefers, or explain what went wrong technically. Just tell the user briefly (e.g., "Gmail needs to be reconnected — let me set that up") and immediately follow the connection setup flow for that platform (e.g., install and load **google-oauth-setup** for Gmail). The user came to you to get something done, not to troubleshoot OAuth — make it seamless.
|
|
102
|
+
3. **Never try alternative approaches.** Don't use bash, curl, browser automation, or any workaround. If the messaging tools can't do it, the reconnection flow is the answer.
|
|
103
|
+
4. **Never expose error details.** The user doesn't need to see error messages about tokens, OAuth, or API failures. Translate errors into plain language.
|
|
104
|
+
|
|
73
105
|
## Platform Selection
|
|
74
106
|
|
|
75
107
|
- If the user specifies a platform (e.g., "check my Slack"), pass it as the `platform` parameter.
|
|
76
108
|
- If only one platform is connected, it is auto-selected.
|
|
77
109
|
- If multiple platforms are connected and the user doesn't specify, ask which platform they mean — or search across all of them.
|
|
78
|
-
- **
|
|
110
|
+
- **Be action-oriented with email.** When the user says "email" and wants to *do* something (declutter, check, search, send), check what's connected first. If nothing is connected, ask which provider briefly and then go straight into setup — don't present menus, options lists, or explain the setup process. Just do it.
|
|
79
111
|
|
|
80
112
|
## Capabilities
|
|
81
113
|
|
|
@@ -217,27 +249,44 @@ Medium and high risk tools require a confidence score between 0 and 1:
|
|
|
217
249
|
|
|
218
250
|
Use `messaging_analyze_activity` to classify channels or conversations by activity level (high, medium, low, dead). Useful for decluttering — suggest leaving dead channels or archiving old emails.
|
|
219
251
|
|
|
220
|
-
##
|
|
252
|
+
## Email Decluttering
|
|
253
|
+
|
|
254
|
+
When a user asks to declutter, clean up, or organize their email — start scanning immediately. Don't ask what kind of cleanup they want or request permission to read their inbox. Go straight to scanning — but once results are ready, always show them via `ui_show` and let the user choose actions before archiving or unsubscribing.
|
|
255
|
+
|
|
256
|
+
### Provider Selection
|
|
221
257
|
|
|
222
|
-
Use `gmail_sender_digest`
|
|
258
|
+
- **Gmail connected**: Use the Gmail-specific tools (`gmail_sender_digest`, `gmail_archive_by_query`, `gmail_unsubscribe`, `gmail_filters`) — they have richer features like unsubscribe support and filter creation.
|
|
259
|
+
- **Non-Gmail email connected**: Use the generic tools (`messaging_sender_digest`, `messaging_archive_by_sender`) — they work with any provider that supports these operations. Skip unsubscribe and filter offers since they are Gmail-specific.
|
|
260
|
+
- **Nothing connected**: Ask which email provider they use. If it's Gmail, go straight into the Gmail connection flow. For other providers, let the user know only Gmail is supported right now and offer to set up Gmail instead. Don't present a menu of options or explain what OAuth is.
|
|
223
261
|
|
|
224
262
|
### Workflow
|
|
225
263
|
|
|
226
|
-
1. **Scan**: Call `gmail_sender_digest` (
|
|
264
|
+
1. **Scan**: Call `gmail_sender_digest` (or `messaging_sender_digest` for non-Gmail). Default query targets promotions from the last 90 days.
|
|
227
265
|
2. **Present**: Show results as a `ui_show` table with `selectionMode: "multiple"`:
|
|
228
|
-
-
|
|
229
|
-
-
|
|
230
|
-
|
|
231
|
-
-
|
|
232
|
-
-
|
|
233
|
-
-
|
|
234
|
-
|
|
266
|
+
- **Gmail columns (exactly 3)**: Sender, Emails Found, Unsub?
|
|
267
|
+
- **Non-Gmail columns (exactly 2)**: Sender, Emails Found (omit the Unsub? column — unsubscribe is not available)
|
|
268
|
+
- **Pre-select all rows** (`selected: true`) — users deselect what they want to keep
|
|
269
|
+
- **Caption**: "Showing emails from last 90 days in Promotions" (or adjusted to match the query used)
|
|
270
|
+
- **Gmail action buttons (exactly 2)**: "Archive & Unsubscribe" (primary), "Archive Only" (secondary). **NEVER offer Delete, Trash, or any destructive action.**
|
|
271
|
+
- **Non-Gmail action button (exactly 1)**: "Archive Selected" (primary). Do not offer an unsubscribe button — it is Gmail-specific. **NEVER offer Delete, Trash, or any destructive action.**
|
|
272
|
+
3. **Live progress**: After the user clicks an action button:
|
|
273
|
+
- **Dismiss the table immediately** with `ui_dismiss` — it collapses to a completion chip
|
|
274
|
+
- **Show a `task_progress` card** with one step per selected sender (e.g., "Archiving TechCrunch (247 emails)"). Update each step from `in_progress` → `completed` as each sender finishes.
|
|
275
|
+
- When all senders are processed, set the progress card's `status: "completed"`.
|
|
276
|
+
4. **Act on selection**: For each selected sender:
|
|
277
|
+
- Use `gmail_archive_by_query` (or `messaging_archive_by_sender` for non-Gmail) with the sender's `search_query` — this archives all matching messages in one call, regardless of volume
|
|
278
|
+
- If Gmail and the action is "Archive & Unsubscribe" and `has_unsubscribe` is true, call `gmail_unsubscribe` with the sender's `newest_message_id`
|
|
279
|
+
5. **Accurate summary**: Use the **actual counts returned by the archive tool**, not the scan counts from the digest. The scan is a sample; the archive is comprehensive. Format: "Cleaned up [total_archived] emails from [sender_count] senders." For Gmail, append: "Unsubscribed from [unsub_count]."
|
|
280
|
+
6. **Ongoing protection offer (Gmail only)**: After reporting results, offer auto-archive filters:
|
|
281
|
+
- "Want me to set up auto-archive filters so future emails from these senders skip your inbox?"
|
|
282
|
+
- If yes, call `gmail_filters` with `action: "create"` for each sender with `from` set to the sender's email and `remove_label_ids: ["INBOX"]`.
|
|
283
|
+
- Then offer a recurring declutter schedule: "Want me to scan for new clutter monthly?" If yes, use `schedule_create` to set up a monthly declutter check.
|
|
235
284
|
|
|
236
285
|
### Edge Cases
|
|
237
286
|
|
|
238
287
|
- **Zero results**: Tell the user "No newsletter emails found" and suggest broadening the query (e.g. removing the category filter or extending the date range)
|
|
239
288
|
- **Unsubscribe failures**: Report per-sender success/failure; the existing `gmail_unsubscribe` tool handles edge cases
|
|
240
|
-
- **Large sender counts**: The `has_more` flag indicates a sender had more messages than collected —
|
|
289
|
+
- **Large sender counts**: The `has_more` flag indicates a sender had more messages than collected — `gmail_archive_by_query` handles this automatically via its own pagination
|
|
241
290
|
|
|
242
291
|
## Batch Operations
|
|
243
292
|
|
|
@@ -1017,6 +1017,64 @@
|
|
|
1017
1017
|
"executor": "tools/gmail-sender-digest.ts",
|
|
1018
1018
|
"execution_target": "host"
|
|
1019
1019
|
},
|
|
1020
|
+
{
|
|
1021
|
+
"name": "messaging_sender_digest",
|
|
1022
|
+
"description": "Scan connected email platform and group messages by sender to identify high-volume senders (e.g. newsletters). Works with any email provider that supports sender digest. Returns top senders sorted by message count with metadata for bulk cleanup.",
|
|
1023
|
+
"category": "messaging",
|
|
1024
|
+
"risk": "low",
|
|
1025
|
+
"input_schema": {
|
|
1026
|
+
"type": "object",
|
|
1027
|
+
"properties": {
|
|
1028
|
+
"platform": {
|
|
1029
|
+
"type": "string",
|
|
1030
|
+
"description": "Platform (e.g. \"gmail\"). Auto-detected if only one is connected."
|
|
1031
|
+
},
|
|
1032
|
+
"query": {
|
|
1033
|
+
"type": "string",
|
|
1034
|
+
"description": "Search query (default 'category:promotions newer_than:90d')"
|
|
1035
|
+
},
|
|
1036
|
+
"max_messages": {
|
|
1037
|
+
"type": "number",
|
|
1038
|
+
"description": "Maximum messages to scan (default 500, cap 2000)"
|
|
1039
|
+
},
|
|
1040
|
+
"max_senders": {
|
|
1041
|
+
"type": "number",
|
|
1042
|
+
"description": "Maximum senders to return (default 30)"
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
},
|
|
1046
|
+
"executor": "tools/messaging-sender-digest.ts",
|
|
1047
|
+
"execution_target": "host"
|
|
1048
|
+
},
|
|
1049
|
+
{
|
|
1050
|
+
"name": "messaging_archive_by_sender",
|
|
1051
|
+
"description": "Archive all messages matching a search query on the connected email platform. Paginates through all results and archives in bulk. Works with any email provider that supports archive by query. Include a confidence score (0-1).",
|
|
1052
|
+
"category": "messaging",
|
|
1053
|
+
"risk": "medium",
|
|
1054
|
+
"input_schema": {
|
|
1055
|
+
"type": "object",
|
|
1056
|
+
"properties": {
|
|
1057
|
+
"platform": {
|
|
1058
|
+
"type": "string",
|
|
1059
|
+
"description": "Platform (e.g. \"gmail\"). Auto-detected if only one is connected."
|
|
1060
|
+
},
|
|
1061
|
+
"query": {
|
|
1062
|
+
"type": "string",
|
|
1063
|
+
"description": "Search query (e.g. \"from:marketing@example.com category:promotions newer_than:90d\")"
|
|
1064
|
+
},
|
|
1065
|
+
"confidence": {
|
|
1066
|
+
"type": "number",
|
|
1067
|
+
"description": "Confidence score (0-1) for this action"
|
|
1068
|
+
}
|
|
1069
|
+
},
|
|
1070
|
+
"required": [
|
|
1071
|
+
"query",
|
|
1072
|
+
"confidence"
|
|
1073
|
+
]
|
|
1074
|
+
},
|
|
1075
|
+
"executor": "tools/messaging-archive-by-sender.ts",
|
|
1076
|
+
"execution_target": "host"
|
|
1077
|
+
},
|
|
1020
1078
|
{
|
|
1021
1079
|
"name": "gmail_outreach_scan",
|
|
1022
1080
|
"description": "Scan Gmail for cold outreach emails (sales, recruiting, marketing) using LLM classification. Returns top outreach senders with suggested cleanup actions. Read-only \u2014 use gmail_batch_archive and gmail_filters for cleanup.",
|
|
@@ -161,7 +161,12 @@ export async function run(input: Record<string, unknown>, _context: ToolContext)
|
|
|
161
161
|
sample_subjects: s.sampleSubjects,
|
|
162
162
|
}));
|
|
163
163
|
|
|
164
|
-
return ok(JSON.stringify({
|
|
164
|
+
return ok(JSON.stringify({
|
|
165
|
+
senders: result,
|
|
166
|
+
total_scanned: allMessageIds.length,
|
|
167
|
+
query_used: query,
|
|
168
|
+
note: `message_count reflects emails found per sender within the ${allMessageIds.length} messages scanned. The archive tool may find additional messages beyond this sample.`,
|
|
169
|
+
}));
|
|
165
170
|
});
|
|
166
171
|
} catch (e) {
|
|
167
172
|
return err(e instanceof Error ? e.message : String(e));
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { ToolContext, ToolExecutionResult } from '../../../../tools/types.js';
|
|
2
|
+
import { err, ok, resolveProvider, withProviderToken } from './shared.js';
|
|
3
|
+
|
|
4
|
+
export async function run(input: Record<string, unknown>, _context: ToolContext): Promise<ToolExecutionResult> {
|
|
5
|
+
const platform = input.platform as string | undefined;
|
|
6
|
+
const query = input.query as string;
|
|
7
|
+
|
|
8
|
+
if (!query) {
|
|
9
|
+
return err('query is required.');
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
try {
|
|
13
|
+
const provider = resolveProvider(platform);
|
|
14
|
+
|
|
15
|
+
if (!provider.archiveByQuery) {
|
|
16
|
+
return err(`The ${provider.displayName} provider does not support archive by query.`);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return withProviderToken(provider, async (token) => {
|
|
20
|
+
const result = await provider.archiveByQuery!(token, query);
|
|
21
|
+
|
|
22
|
+
if (result.archived === 0) {
|
|
23
|
+
return ok('No messages matched the query. Nothing archived.');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const summary = `Archived ${result.archived} message(s) matching query: ${query}`;
|
|
27
|
+
if (result.truncated) {
|
|
28
|
+
return ok(`${summary}\n\nNote: this operation was capped at 5000 messages. Additional messages matching the query may remain in the inbox. Run the command again to archive more.`);
|
|
29
|
+
}
|
|
30
|
+
return ok(summary);
|
|
31
|
+
});
|
|
32
|
+
} catch (e) {
|
|
33
|
+
return err(e instanceof Error ? e.message : String(e));
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { ToolContext, ToolExecutionResult } from '../../../../tools/types.js';
|
|
2
|
+
import { err, ok, resolveProvider, withProviderToken } from './shared.js';
|
|
3
|
+
|
|
4
|
+
export async function run(input: Record<string, unknown>, _context: ToolContext): Promise<ToolExecutionResult> {
|
|
5
|
+
const platform = input.platform as string | undefined;
|
|
6
|
+
const query = (input.query as string) ?? 'category:promotions newer_than:90d';
|
|
7
|
+
const maxMessages = input.max_messages as number | undefined;
|
|
8
|
+
const maxSenders = input.max_senders as number | undefined;
|
|
9
|
+
|
|
10
|
+
try {
|
|
11
|
+
const provider = resolveProvider(platform);
|
|
12
|
+
|
|
13
|
+
if (!provider.senderDigest) {
|
|
14
|
+
return err(`The ${provider.displayName} provider does not support sender digest scanning.`);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return withProviderToken(provider, async (token) => {
|
|
18
|
+
const result = await provider.senderDigest!(token, query, { maxMessages, maxSenders });
|
|
19
|
+
|
|
20
|
+
if (result.senders.length === 0) {
|
|
21
|
+
return ok(JSON.stringify({
|
|
22
|
+
senders: [],
|
|
23
|
+
total_scanned: result.totalScanned,
|
|
24
|
+
query_used: result.queryUsed,
|
|
25
|
+
message: 'No emails found matching the query. Try broadening the search (e.g. remove category filter or extend date range).',
|
|
26
|
+
}));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Map to snake_case output format for LLM consumption
|
|
30
|
+
const senders = result.senders.map((s) => ({
|
|
31
|
+
id: s.id,
|
|
32
|
+
display_name: s.displayName,
|
|
33
|
+
email: s.email,
|
|
34
|
+
message_count: s.messageCount,
|
|
35
|
+
has_unsubscribe: s.hasUnsubscribe,
|
|
36
|
+
newest_message_id: s.newestMessageId,
|
|
37
|
+
search_query: s.searchQuery,
|
|
38
|
+
message_ids: s.messageIds,
|
|
39
|
+
has_more: s.hasMore,
|
|
40
|
+
}));
|
|
41
|
+
|
|
42
|
+
return ok(JSON.stringify({
|
|
43
|
+
senders,
|
|
44
|
+
total_scanned: result.totalScanned,
|
|
45
|
+
query_used: result.queryUsed,
|
|
46
|
+
note: `message_count reflects emails found per sender within the ${result.totalScanned} messages scanned. The archive tool may find additional messages beyond this sample.`,
|
|
47
|
+
}));
|
|
48
|
+
});
|
|
49
|
+
} catch (e) {
|
|
50
|
+
return err(e instanceof Error ? e.message : String(e));
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -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
|
-
|
|
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
|
|
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
|
|
74
|
+
Also check calls feature status:
|
|
65
75
|
|
|
66
76
|
```bash
|
|
67
|
-
|
|
77
|
+
vellum config get calls.enabled
|
|
68
78
|
```
|
|
69
79
|
|
|
70
|
-
|
|
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
|
|
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: **"
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
|
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`
|
|
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
|
|
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
|
-
|
|
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."
|
|
@@ -137,6 +137,30 @@ export const CallsConfigSchema = z.object({
|
|
|
137
137
|
.min(50, 'calls.accessRequestPollIntervalMs must be >= 50')
|
|
138
138
|
.max(10_000, 'calls.accessRequestPollIntervalMs must be at most 10000')
|
|
139
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),
|
|
140
164
|
disclosure: CallsDisclosureConfigSchema.default(CallsDisclosureConfigSchema.parse({})),
|
|
141
165
|
safety: CallsSafetyConfigSchema.default(CallsSafetyConfigSchema.parse({})),
|
|
142
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",
|
package/src/config/schema.ts
CHANGED
|
@@ -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
|
-
.
|
|
208
|
-
.default(
|
|
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({})),
|
package/src/config/skills.ts
CHANGED
|
@@ -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)) {
|