@vellumai/assistant 0.4.29 → 0.4.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ARCHITECTURE.md +39 -37
- package/README.md +5 -6
- package/docs/runbook-trusted-contacts.md +79 -43
- package/package.json +1 -1
- package/scripts/ipc/check-swift-decoder-drift.ts +2 -3
- package/scripts/test.sh +1 -1
- package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +4 -37
- package/src/__tests__/actor-token-service.test.ts +4 -3
- package/src/__tests__/app-executors.test.ts +7 -17
- package/src/__tests__/assistant-feature-flags-integration.test.ts +18 -10
- package/src/__tests__/browser-skill-endstate.test.ts +10 -1
- package/src/__tests__/bundled-skill-retrieval-guard.test.ts +1 -0
- package/src/__tests__/channel-approval-routes.test.ts +44 -44
- package/src/__tests__/channel-approval.test.ts +8 -0
- package/src/__tests__/channel-approvals.test.ts +39 -1
- package/src/__tests__/channel-guardian.test.ts +15 -5
- package/src/__tests__/channel-reply-delivery.test.ts +31 -0
- package/src/__tests__/commit-message-enrichment-service.test.ts +4 -0
- package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +9 -0
- package/src/__tests__/gateway-only-guard.test.ts +1 -0
- package/src/__tests__/gemini-image-service.test.ts +2 -2
- package/src/__tests__/guardian-grant-minting.test.ts +6 -6
- package/src/__tests__/guardian-routing-invariants.test.ts +34 -11
- package/src/__tests__/guardian-verify-setup-skill-regression.test.ts +4 -6
- package/src/__tests__/inbound-invite-redemption.test.ts +1 -1
- package/src/__tests__/integrations-cli.test.ts +3 -27
- package/src/__tests__/intent-routing.test.ts +3 -0
- package/src/__tests__/invite-redemption-service.test.ts +1 -1
- package/src/__tests__/{ingress-routes-http.test.ts → invite-routes-http.test.ts} +40 -320
- package/src/__tests__/ipc-snapshot.test.ts +4 -31
- package/src/__tests__/nl-approval-parser.test.ts +305 -0
- package/src/__tests__/oauth-provider-profiles.test.ts +34 -0
- package/src/__tests__/provider-error-scenarios.test.ts +68 -0
- package/src/__tests__/relay-server.test.ts +1 -1
- package/src/__tests__/retry-after-extraction.test.ts +111 -0
- package/src/__tests__/script-proxy-profile-template-fallback.test.ts +127 -0
- package/src/__tests__/session-media-retry.test.ts +147 -0
- package/src/__tests__/skill-feature-flags-integration.test.ts +9 -5
- package/src/__tests__/skill-feature-flags.test.ts +18 -12
- package/src/__tests__/skill-load-feature-flag.test.ts +4 -3
- package/src/__tests__/slack-block-formatting.test.ts +100 -0
- package/src/__tests__/slack-inbound-verification.test.ts +346 -0
- package/src/__tests__/slack-reaction-approvals.test.ts +77 -0
- package/src/__tests__/slack-skill.test.ts +3 -2
- package/src/__tests__/starter-task-flow.test.ts +0 -1
- package/src/__tests__/trusted-contact-verification.test.ts +3 -1
- package/src/__tests__/voice-invite-redemption.test.ts +1 -1
- package/src/amazon/client.ts +7 -24
- package/src/calls/relay-server.ts +39 -11
- package/src/channels/config.ts +1 -1
- package/src/cli/integrations.ts +10 -66
- package/src/config/bundled-skills/app-builder/SKILL.md +193 -1500
- package/src/config/bundled-skills/app-builder/TOOLS.json +70 -18
- package/src/config/bundled-skills/browser/TOOLS.json +59 -2
- package/src/config/bundled-skills/chatgpt-import/TOOLS.json +4 -0
- package/src/config/bundled-skills/computer-use/TOOLS.json +50 -2
- package/src/config/bundled-skills/contacts/SKILL.md +42 -35
- package/src/config/bundled-skills/contacts/TOOLS.json +22 -2
- package/src/config/bundled-skills/contacts/tools/contact-merge.ts +38 -58
- package/src/config/bundled-skills/contacts/tools/contact-search.ts +11 -31
- package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +19 -37
- package/src/config/bundled-skills/document/TOOLS.json +8 -0
- package/src/config/bundled-skills/email-setup/SKILL.md +10 -7
- package/src/config/bundled-skills/followups/TOOLS.json +12 -0
- package/src/config/bundled-skills/google-calendar/TOOLS.json +124 -26
- package/src/config/bundled-skills/guardian-verify-setup/SKILL.md +54 -21
- package/src/config/bundled-skills/image-studio/TOOLS.json +12 -2
- package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +14 -8
- package/src/config/bundled-skills/knowledge-graph/TOOLS.json +13 -3
- package/src/config/bundled-skills/media-processing/SKILL.md +1 -1
- package/src/config/bundled-skills/media-processing/TOOLS.json +28 -0
- package/src/config/bundled-skills/media-processing/tools/generate-clip.ts +26 -6
- package/src/config/bundled-skills/messaging/TOOLS.json +228 -182
- package/src/config/bundled-skills/notifications/SKILL.md +3 -2
- package/src/config/bundled-skills/notifications/TOOLS.json +7 -13
- package/src/config/bundled-skills/phone-calls/TOOLS.json +13 -1
- package/src/config/bundled-skills/playbooks/TOOLS.json +16 -0
- package/src/config/bundled-skills/reminder/TOOLS.json +15 -2
- package/src/config/bundled-skills/schedule/SKILL.md +33 -15
- package/src/config/bundled-skills/schedule/TOOLS.json +17 -1
- package/src/config/bundled-skills/slack/SKILL.md +30 -1
- package/src/config/bundled-skills/slack/TOOLS.json +89 -2
- package/src/config/bundled-skills/slack/tools/slack-channel-permissions.ts +146 -0
- package/src/config/bundled-skills/slack/tools/slack-scan-digest.ts +120 -0
- package/src/config/bundled-skills/slack-app-setup/SKILL.md +200 -0
- package/src/config/bundled-skills/subagent/TOOLS.json +22 -2
- package/src/config/bundled-skills/tasks/TOOLS.json +86 -14
- package/src/config/bundled-skills/transcribe/TOOLS.json +4 -0
- package/src/config/bundled-skills/watcher/TOOLS.json +20 -0
- package/src/config/bundled-skills/weather/TOOLS.json +4 -0
- package/src/config/bundled-tool-registry.ts +2 -0
- package/src/config/channel-permission-profiles.ts +155 -0
- package/src/config/env.ts +4 -1
- package/src/contacts/contact-store.ts +195 -4
- package/src/contacts/types.ts +26 -0
- package/src/daemon/assistant-attachments.ts +23 -3
- package/src/daemon/guardian-verification-intent.ts +7 -4
- package/src/daemon/handlers/apps.ts +1 -2
- package/src/daemon/handlers/config-inbox.ts +16 -134
- package/src/daemon/handlers/guardian-actions.ts +20 -87
- package/src/daemon/handlers/sessions.ts +0 -1
- package/src/daemon/ipc-contract/apps.ts +0 -1
- package/src/daemon/ipc-contract/inbox.ts +7 -66
- package/src/daemon/ipc-contract/sessions.ts +1 -0
- package/src/daemon/ipc-contract/surfaces.ts +0 -1
- package/src/daemon/ipc-contract-inventory.json +2 -4
- package/src/daemon/lifecycle.ts +14 -2
- package/src/daemon/session-agent-loop-handlers.ts +9 -0
- package/src/daemon/session-agent-loop.ts +1 -0
- package/src/daemon/session-attachments.ts +5 -1
- package/src/daemon/session-error.ts +18 -0
- package/src/daemon/session-lifecycle.ts +4 -5
- package/src/daemon/session-media-retry.ts +15 -1
- package/src/daemon/session-surfaces.ts +0 -1
- package/src/daemon/session-tool-setup.ts +7 -4
- package/src/events/domain-events.ts +2 -1
- package/src/home-base/prebuilt/seed.ts +0 -1
- package/src/influencer/client.ts +7 -24
- package/src/media/gemini-image-service.ts +48 -3
- package/src/memory/app-store.ts +0 -4
- package/src/memory/conversation-attention-store.ts +3 -1
- package/src/memory/db-init.ts +4 -0
- package/src/memory/migrations/133-assistant-contact-metadata.ts +21 -0
- package/src/memory/migrations/index.ts +1 -0
- package/src/memory/schema.ts +12 -0
- package/src/memory/slack-thread-store.ts +187 -0
- package/src/messaging/providers/slack/client.ts +84 -26
- package/src/messaging/providers/slack/types.ts +4 -0
- package/src/notifications/adapters/slack.ts +90 -0
- package/src/notifications/destination-resolver.ts +42 -1
- package/src/notifications/emit-signal.ts +17 -1
- package/src/oauth/provider-profiles.ts +22 -0
- package/src/providers/anthropic/client.ts +3 -0
- package/src/providers/openai/client.ts +3 -0
- package/src/providers/retry.ts +9 -1
- package/src/runtime/actor-trust-resolver.ts +8 -0
- package/src/runtime/auth/require-bound-guardian.ts +44 -0
- package/src/runtime/auth/route-policy.ts +4 -8
- package/src/runtime/channel-approval-types.ts +18 -0
- package/src/runtime/channel-approvals.ts +8 -0
- package/src/runtime/channel-invite-transport.ts +1 -1
- package/src/runtime/channel-reply-delivery.ts +62 -3
- package/src/runtime/gateway-client.ts +36 -2
- package/src/runtime/gateway-internal-client.ts +86 -0
- package/src/runtime/guardian-action-service.ts +127 -0
- package/src/runtime/guardian-verification-templates.ts +16 -1
- package/src/runtime/http-server.ts +20 -49
- package/src/runtime/invite-redemption-service.ts +1 -1
- package/src/runtime/{ingress-service.ts → invite-service.ts} +5 -157
- package/src/runtime/nl-approval-parser.ts +138 -0
- package/src/runtime/routes/approval-routes.ts +1 -40
- package/src/runtime/routes/channel-route-shared.ts +35 -1
- package/src/runtime/routes/contact-routes.ts +196 -28
- package/src/runtime/routes/guardian-action-routes.ts +19 -111
- package/src/runtime/routes/guardian-approval-interception.ts +76 -0
- package/src/runtime/routes/inbound-message-handler.ts +40 -12
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +222 -0
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +108 -0
- package/src/runtime/routes/{ingress-routes.ts → invite-routes.ts} +10 -110
- package/src/runtime/slack-block-formatting.ts +176 -0
- package/src/schedule/scheduler.ts +11 -2
- package/src/tools/apps/executors.ts +16 -15
- package/src/tools/calls/call-end.ts +1 -1
- package/src/tools/computer-use/definitions.ts +16 -0
- package/src/tools/credentials/vault.ts +86 -2
- package/src/tools/network/script-proxy/session-manager.ts +28 -3
- package/src/tools/permission-checker.ts +18 -0
- package/src/tools/terminal/shell.ts +15 -5
- package/src/tools/tool-approval-handler.ts +48 -4
- package/src/tools/types.ts +38 -1
- package/src/util/errors.ts +5 -1
- package/src/util/retry.ts +21 -0
- package/src/watcher/providers/slack.ts +33 -3
- /package/src/memory/{ingress-invite-store.ts → invite-store.ts} +0 -0
|
@@ -10,7 +10,7 @@ Manage the user's contacts, relationship graph, access control (trusted contacts
|
|
|
10
10
|
## Prerequisites
|
|
11
11
|
|
|
12
12
|
- Use the injected `INTERNAL_GATEWAY_BASE_URL` for gateway API calls.
|
|
13
|
-
- Use gateway control-plane routes only: this skill calls `/v1/contacts`, `/v1/
|
|
13
|
+
- Use gateway control-plane routes only: this skill calls `/v1/contacts`, `/v1/contacts/channels`, `/v1/contacts/invites`, and `/v1/integrations/telegram/config` on the gateway, never the assistant runtime port directly.
|
|
14
14
|
- The bearer token is available as the `$GATEWAY_AUTH_TOKEN` environment variable for control-plane `curl` requests.
|
|
15
15
|
|
|
16
16
|
## Contact Management
|
|
@@ -96,10 +96,10 @@ Trusted contacts control who is allowed to send messages to the assistant throug
|
|
|
96
96
|
|
|
97
97
|
### Concepts
|
|
98
98
|
|
|
99
|
-
- **
|
|
100
|
-
- **Policy**: Controls what the
|
|
101
|
-
- **Status**: The
|
|
102
|
-
- **
|
|
99
|
+
- **Contact channel**: A user identity (external user ID or chat ID) on a specific messaging platform, stored as an entry in a contact's `channels` array. Each channel entry has its own `status` and `policy`.
|
|
100
|
+
- **Policy**: Controls what the contact channel can do -- `allow` (can message freely) or `deny` (blocked from messaging).
|
|
101
|
+
- **Status**: The channel's lifecycle state -- `active` (currently effective), `revoked` (access removed), or `blocked` (explicitly denied).
|
|
102
|
+
- **Channel type**: The messaging platform (e.g., `telegram`, `sms`, `voice`).
|
|
103
103
|
|
|
104
104
|
### List trusted contacts
|
|
105
105
|
|
|
@@ -146,27 +146,30 @@ Use this when the user wants to grant someone access to message the assistant. *
|
|
|
146
146
|
Ask the user: _"I'll add [name/identifier] on [channel] as an allowed contact. Should I proceed?"_
|
|
147
147
|
|
|
148
148
|
```bash
|
|
149
|
-
curl -s -X POST "$INTERNAL_GATEWAY_BASE_URL/v1/
|
|
149
|
+
curl -s -X POST "$INTERNAL_GATEWAY_BASE_URL/v1/contacts" \
|
|
150
150
|
-H "Content-Type: application/json" \
|
|
151
151
|
-H "Authorization: Bearer $GATEWAY_AUTH_TOKEN" \
|
|
152
152
|
-d '{
|
|
153
|
-
"sourceChannel": "<channel>",
|
|
154
|
-
"externalUserId": "<user_id>",
|
|
155
153
|
"displayName": "<display_name>",
|
|
156
|
-
"
|
|
157
|
-
|
|
154
|
+
"channels": [{
|
|
155
|
+
"type": "<channel>",
|
|
156
|
+
"address": "<user_id>",
|
|
157
|
+
"externalUserId": "<user_id>",
|
|
158
|
+
"status": "active",
|
|
159
|
+
"policy": "allow"
|
|
160
|
+
}]
|
|
158
161
|
}'
|
|
159
162
|
```
|
|
160
163
|
|
|
161
164
|
Required fields:
|
|
162
165
|
|
|
163
|
-
- `sourceChannel` -- the channel (e.g., `telegram`, `sms`)
|
|
164
|
-
- At least one of `externalUserId` or `externalChatId`
|
|
165
|
-
|
|
166
|
-
Optional fields:
|
|
167
|
-
|
|
168
166
|
- `displayName` -- human-readable name for the contact
|
|
169
|
-
- `
|
|
167
|
+
- `channels` -- at least one channel entry with:
|
|
168
|
+
- `type` -- the channel type (e.g., `telegram`, `sms`)
|
|
169
|
+
- `address` -- the channel-specific identifier
|
|
170
|
+
- `externalUserId` -- the user's ID on that channel (or `externalChatId` for chat-based channels)
|
|
171
|
+
- `status` -- set to `"active"` for immediate access
|
|
172
|
+
- `policy` -- set to `"allow"` to grant messaging access
|
|
170
173
|
|
|
171
174
|
If the user provides a name but not an external ID, explain that you need the channel-specific user ID or chat ID to create the contact entry. For Telegram, this is a numeric user ID; for SMS, this is the phone number in E.164 format.
|
|
172
175
|
|
|
@@ -176,16 +179,18 @@ Use this when the user wants to remove someone's access. **Always confirm with t
|
|
|
176
179
|
|
|
177
180
|
Ask the user: _"I'll revoke access for [name/identifier]. They will no longer be able to message the assistant. Should I proceed?"_
|
|
178
181
|
|
|
179
|
-
First, list
|
|
182
|
+
First, list contacts to find the channel's `id` (each entry in a contact's `channels` array has an `id` field -- visible in `GET /v1/contacts` or `vellum contacts list --json` output), then revoke:
|
|
183
|
+
|
|
184
|
+
**Important**: Before revoking, check the channel's current `status`. If the channel is **blocked**, do not attempt to revoke it -- blocking is stronger than revoking. Inform the user that the contact is already blocked and revoking is not applicable. Only channels with `active` or `pending` status can be revoked.
|
|
180
185
|
|
|
181
186
|
```bash
|
|
182
|
-
curl -s -X
|
|
183
|
-
-H "Authorization: Bearer $GATEWAY_AUTH_TOKEN" \
|
|
187
|
+
curl -s -X PATCH "$INTERNAL_GATEWAY_BASE_URL/v1/contacts/channels/<channel_id>" \
|
|
184
188
|
-H "Content-Type: application/json" \
|
|
185
|
-
-
|
|
189
|
+
-H "Authorization: Bearer $GATEWAY_AUTH_TOKEN" \
|
|
190
|
+
-d '{"status": "revoked", "reason": "<optional reason>"}'
|
|
186
191
|
```
|
|
187
192
|
|
|
188
|
-
Replace `<
|
|
193
|
+
Replace `<channel_id>` with the channel's `id` from the contact's `channels` array. The API will return a `409 Conflict` error if the channel is currently blocked.
|
|
189
194
|
|
|
190
195
|
### Block a user
|
|
191
196
|
|
|
@@ -194,12 +199,14 @@ Use this when the user wants to explicitly block someone. Blocking is stronger t
|
|
|
194
199
|
Ask the user: _"I'll block [name/identifier]. They will be permanently denied from messaging the assistant. Should I proceed?"_
|
|
195
200
|
|
|
196
201
|
```bash
|
|
197
|
-
curl -s -X
|
|
202
|
+
curl -s -X PATCH "$INTERNAL_GATEWAY_BASE_URL/v1/contacts/channels/<channel_id>" \
|
|
198
203
|
-H "Content-Type: application/json" \
|
|
199
204
|
-H "Authorization: Bearer $GATEWAY_AUTH_TOKEN" \
|
|
200
|
-
-d '{"reason": "<optional reason>"}'
|
|
205
|
+
-d '{"status": "blocked", "reason": "<optional reason>"}'
|
|
201
206
|
```
|
|
202
207
|
|
|
208
|
+
Replace `<channel_id>` with the channel's `id` from the contact's `channels` array (visible in `GET /v1/contacts` or `vellum contacts list --json` output).
|
|
209
|
+
|
|
203
210
|
## Invite Links
|
|
204
211
|
|
|
205
212
|
Invite links let the guardian share a link or code that automatically grants access when used. Telegram invites use a deep link; voice invites use a phone number + numeric code.
|
|
@@ -211,7 +218,7 @@ Use this when the guardian wants to invite someone to message the assistant on T
|
|
|
211
218
|
**Important**: The shell snippet below emits a `<vellum-sensitive-output>` directive containing the raw invite token. The tool executor automatically strips this directive and replaces the raw token with a placeholder so the LLM never sees it. The placeholder is resolved back to the real token in the final assistant reply.
|
|
212
219
|
|
|
213
220
|
```bash
|
|
214
|
-
INVITE_JSON=$(curl -s -X POST "$INTERNAL_GATEWAY_BASE_URL/v1/
|
|
221
|
+
INVITE_JSON=$(curl -s -X POST "$INTERNAL_GATEWAY_BASE_URL/v1/contacts/invites" \
|
|
215
222
|
-H "Content-Type: application/json" \
|
|
216
223
|
-H "Authorization: Bearer $GATEWAY_AUTH_TOKEN" \
|
|
217
224
|
-d '{
|
|
@@ -286,7 +293,7 @@ Use this when the guardian wants to authorize a specific phone number to call th
|
|
|
286
293
|
**Important**: The response includes a `voiceCode` field that is only returned at creation time and cannot be retrieved later. Extract and present it clearly.
|
|
287
294
|
|
|
288
295
|
```bash
|
|
289
|
-
INVITE_JSON=$(curl -s -X POST "$INTERNAL_GATEWAY_BASE_URL/v1/
|
|
296
|
+
INVITE_JSON=$(curl -s -X POST "$INTERNAL_GATEWAY_BASE_URL/v1/contacts/invites" \
|
|
290
297
|
-H "Content-Type: application/json" \
|
|
291
298
|
-H "Authorization: Bearer $GATEWAY_AUTH_TOKEN" \
|
|
292
299
|
-d '{
|
|
@@ -388,7 +395,7 @@ Ask the user: _"I'll revoke the invite [note or ID]. It will no longer be usable
|
|
|
388
395
|
First, list invites to find the invite's `id`, then revoke:
|
|
389
396
|
|
|
390
397
|
```bash
|
|
391
|
-
curl -s -X DELETE "$INTERNAL_GATEWAY_BASE_URL/v1/
|
|
398
|
+
curl -s -X DELETE "$INTERNAL_GATEWAY_BASE_URL/v1/contacts/invites/<invite_id>" \
|
|
392
399
|
-H "Authorization: Bearer $GATEWAY_AUTH_TOKEN"
|
|
393
400
|
```
|
|
394
401
|
|
|
@@ -425,10 +432,10 @@ Each channel has:
|
|
|
425
432
|
|
|
426
433
|
- If a request returns `{ ok: false, error: "..." }`, report the error message to the user.
|
|
427
434
|
- Common errors:
|
|
428
|
-
- `
|
|
429
|
-
- `
|
|
430
|
-
- `
|
|
431
|
-
- `
|
|
435
|
+
- `Channel not found` -- the channel ID may be invalid; list contacts to find the correct channel ID.
|
|
436
|
+
- `Channel already revoked` -- the channel has already been revoked.
|
|
437
|
+
- `Channel already blocked` -- the channel has already been blocked.
|
|
438
|
+
- `Cannot revoke a blocked channel` -- the channel is blocked; blocking is stronger than revoking. Tell the user the contact is already blocked.
|
|
432
439
|
- `sourceChannel is required for create` -- when creating an invite, always pass `"sourceChannel": "telegram"` for Telegram or `"sourceChannel": "voice"` for voice invites.
|
|
433
440
|
- `expectedExternalUserId is required for voice invites` -- voice invites must include the invitee's phone number.
|
|
434
441
|
- `expectedExternalUserId must be in E.164 format` -- the phone number must start with `+` followed by country code and number (e.g., `+15551234567`).
|
|
@@ -445,15 +452,15 @@ Each channel has:
|
|
|
445
452
|
|
|
446
453
|
## Typical Workflows
|
|
447
454
|
|
|
448
|
-
**"Who can message me?"** -- List all
|
|
455
|
+
**"Who can message me?"** -- List all contacts, present active channels as a formatted list.
|
|
449
456
|
|
|
450
|
-
**"Add my friend to Telegram"** -- Ask for their Telegram user ID (numeric) and
|
|
457
|
+
**"Add my friend to Telegram"** -- Ask for their Telegram user ID (numeric) and display name, confirm, then create a contact with a channel entry with `policy: "allow"` and `status: "active"`.
|
|
451
458
|
|
|
452
|
-
**"Remove [name]'s access"** -- List
|
|
459
|
+
**"Remove [name]'s access"** -- List contacts to find them, identify the channel to revoke, confirm the revocation, then patch the channel status to `"revoked"`.
|
|
453
460
|
|
|
454
|
-
**"Block [name]"** -- List
|
|
461
|
+
**"Block [name]"** -- List contacts to find them, identify the channel to block, confirm the block, then patch the channel status to `"blocked"`.
|
|
455
462
|
|
|
456
|
-
**"Show me blocked contacts"** -- List with `status
|
|
463
|
+
**"Show me blocked contacts"** -- List contacts and filter for channels with `status: "blocked"`.
|
|
457
464
|
|
|
458
465
|
**"Create a Telegram invite link"** / **"Invite someone on Telegram"** -- Create an invite with `sourceChannel: "telegram"`, look up the bot username, build the deep link, and present it with sharing instructions.
|
|
459
466
|
|
|
@@ -41,7 +41,15 @@
|
|
|
41
41
|
"properties": {
|
|
42
42
|
"type": {
|
|
43
43
|
"type": "string",
|
|
44
|
-
"enum": [
|
|
44
|
+
"enum": [
|
|
45
|
+
"email",
|
|
46
|
+
"slack",
|
|
47
|
+
"whatsapp",
|
|
48
|
+
"phone",
|
|
49
|
+
"telegram",
|
|
50
|
+
"discord",
|
|
51
|
+
"other"
|
|
52
|
+
],
|
|
45
53
|
"description": "Channel type"
|
|
46
54
|
},
|
|
47
55
|
"address": {
|
|
@@ -55,6 +63,10 @@
|
|
|
55
63
|
},
|
|
56
64
|
"required": ["type", "address"]
|
|
57
65
|
}
|
|
66
|
+
},
|
|
67
|
+
"reason": {
|
|
68
|
+
"type": "string",
|
|
69
|
+
"description": "Brief non-technical explanation of why this tool is being called"
|
|
58
70
|
}
|
|
59
71
|
},
|
|
60
72
|
"required": ["display_name"]
|
|
@@ -76,7 +88,7 @@
|
|
|
76
88
|
},
|
|
77
89
|
"channel_address": {
|
|
78
90
|
"type": "string",
|
|
79
|
-
"description": "Search by channel address (email, phone, handle
|
|
91
|
+
"description": "Search by channel address (email, phone, handle \u2014 partial match)"
|
|
80
92
|
},
|
|
81
93
|
"channel_type": {
|
|
82
94
|
"type": "string",
|
|
@@ -89,6 +101,10 @@
|
|
|
89
101
|
"limit": {
|
|
90
102
|
"type": "number",
|
|
91
103
|
"description": "Maximum results to return (default 20, max 100)"
|
|
104
|
+
},
|
|
105
|
+
"reason": {
|
|
106
|
+
"type": "string",
|
|
107
|
+
"description": "Brief non-technical explanation of why this tool is being called"
|
|
92
108
|
}
|
|
93
109
|
},
|
|
94
110
|
"required": []
|
|
@@ -111,6 +127,10 @@
|
|
|
111
127
|
"merge_id": {
|
|
112
128
|
"type": "string",
|
|
113
129
|
"description": "ID of the contact to merge into the kept contact (will be deleted)"
|
|
130
|
+
},
|
|
131
|
+
"reason": {
|
|
132
|
+
"type": "string",
|
|
133
|
+
"description": "Brief non-technical explanation of why this tool is being called"
|
|
114
134
|
}
|
|
115
135
|
},
|
|
116
136
|
"required": ["keep_id", "merge_id"]
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import {
|
|
2
|
+
gatewayGet,
|
|
3
|
+
gatewayPost,
|
|
4
|
+
GatewayRequestError,
|
|
5
|
+
} from "../../../../runtime/gateway-internal-client.js";
|
|
3
6
|
import type {
|
|
4
7
|
ToolContext,
|
|
5
8
|
ToolExecutionResult,
|
|
@@ -35,72 +38,49 @@ export async function executeContactMerge(
|
|
|
35
38
|
}
|
|
36
39
|
|
|
37
40
|
try {
|
|
38
|
-
const gatewayBase = getGatewayInternalBaseUrl();
|
|
39
|
-
const token = mintEdgeRelayToken();
|
|
40
|
-
const headers = {
|
|
41
|
-
Accept: "application/json",
|
|
42
|
-
Authorization: `Bearer ${token}`,
|
|
43
|
-
};
|
|
44
|
-
|
|
45
41
|
// Validate both contacts exist before merging
|
|
46
|
-
const [
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
headers,
|
|
54
|
-
}),
|
|
42
|
+
const [keepResult, mergeResult] = await Promise.allSettled([
|
|
43
|
+
gatewayGet<{ ok: boolean; contact: ContactResponse }>(
|
|
44
|
+
`/v1/contacts/${keepId}`,
|
|
45
|
+
),
|
|
46
|
+
gatewayGet<{ ok: boolean; contact: ContactResponse }>(
|
|
47
|
+
`/v1/contacts/${mergeId}`,
|
|
48
|
+
),
|
|
55
49
|
]);
|
|
56
50
|
|
|
57
|
-
if (
|
|
58
|
-
|
|
51
|
+
if (keepResult.status === "rejected") {
|
|
52
|
+
if (
|
|
53
|
+
keepResult.reason instanceof GatewayRequestError &&
|
|
54
|
+
keepResult.reason.statusCode === 404
|
|
55
|
+
) {
|
|
56
|
+
return {
|
|
57
|
+
content: `Error: Contact "${keepId}" not found`,
|
|
58
|
+
isError: true,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
throw keepResult.reason;
|
|
59
62
|
}
|
|
60
|
-
if (
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
63
|
+
if (mergeResult.status === "rejected") {
|
|
64
|
+
if (
|
|
65
|
+
mergeResult.reason instanceof GatewayRequestError &&
|
|
66
|
+
mergeResult.reason.statusCode === 404
|
|
67
|
+
) {
|
|
68
|
+
return {
|
|
69
|
+
content: `Error: Contact "${mergeId}" not found`,
|
|
70
|
+
isError: true,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
throw mergeResult.reason;
|
|
65
74
|
}
|
|
66
75
|
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
contact: ContactResponse;
|
|
70
|
-
};
|
|
71
|
-
const mergeData = (await mergeResp.json()) as {
|
|
72
|
-
ok: boolean;
|
|
73
|
-
contact: ContactResponse;
|
|
74
|
-
};
|
|
75
|
-
const keepContact = keepData.contact;
|
|
76
|
-
const mergeContact = mergeData.contact;
|
|
76
|
+
const keepContact = keepResult.value.contact;
|
|
77
|
+
const mergeContact = mergeResult.value.contact;
|
|
77
78
|
|
|
78
79
|
// Execute the merge
|
|
79
|
-
const
|
|
80
|
-
method: "POST",
|
|
81
|
-
headers: {
|
|
82
|
-
...headers,
|
|
83
|
-
"Content-Type": "application/json",
|
|
84
|
-
},
|
|
85
|
-
body: JSON.stringify({ keepId, mergeId }),
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
if (!mergeResult.ok) {
|
|
89
|
-
const body = await mergeResult.text();
|
|
90
|
-
let message = `Gateway request failed (${mergeResult.status})`;
|
|
91
|
-
try {
|
|
92
|
-
const parsed = JSON.parse(body) as { error?: string };
|
|
93
|
-
if (parsed.error) message = parsed.error;
|
|
94
|
-
} catch {
|
|
95
|
-
if (body) message = body;
|
|
96
|
-
}
|
|
97
|
-
return { content: `Error: ${message}`, isError: true };
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
const resultData = (await mergeResult.json()) as {
|
|
80
|
+
const { data: resultData } = await gatewayPost<{
|
|
101
81
|
ok: boolean;
|
|
102
82
|
contact: ContactResponse;
|
|
103
|
-
};
|
|
83
|
+
}>("/v1/contacts/merge", { keepId, mergeId });
|
|
104
84
|
const merged = resultData.contact;
|
|
105
85
|
|
|
106
86
|
const channelList = merged.channels
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import {
|
|
2
|
+
gatewayGet,
|
|
3
|
+
GatewayRequestError,
|
|
4
|
+
} from "../../../../runtime/gateway-internal-client.js";
|
|
3
5
|
import type {
|
|
4
6
|
ToolContext,
|
|
5
7
|
ToolExecutionResult,
|
|
@@ -56,9 +58,6 @@ export async function executeContactSearch(
|
|
|
56
58
|
}
|
|
57
59
|
|
|
58
60
|
try {
|
|
59
|
-
const gatewayBase = getGatewayInternalBaseUrl();
|
|
60
|
-
const token = mintEdgeRelayToken();
|
|
61
|
-
|
|
62
61
|
const params = new URLSearchParams();
|
|
63
62
|
if (query) params.set("query", query);
|
|
64
63
|
if (channelAddress) params.set("channelAddress", channelAddress);
|
|
@@ -67,32 +66,9 @@ export async function executeContactSearch(
|
|
|
67
66
|
if (limit !== undefined) params.set("limit", String(limit));
|
|
68
67
|
|
|
69
68
|
const qs = params.toString();
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
method: "GET",
|
|
74
|
-
headers: {
|
|
75
|
-
Accept: "application/json",
|
|
76
|
-
Authorization: `Bearer ${token}`,
|
|
77
|
-
},
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
if (!resp.ok) {
|
|
81
|
-
const body = await resp.text();
|
|
82
|
-
let message = `Gateway request failed (${resp.status})`;
|
|
83
|
-
try {
|
|
84
|
-
const parsed = JSON.parse(body) as { error?: string };
|
|
85
|
-
if (parsed.error) message = parsed.error;
|
|
86
|
-
} catch {
|
|
87
|
-
if (body) message = body;
|
|
88
|
-
}
|
|
89
|
-
return { content: `Error: ${message}`, isError: true };
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
const data = (await resp.json()) as {
|
|
93
|
-
ok: boolean;
|
|
94
|
-
contacts: ContactResponse[];
|
|
95
|
-
};
|
|
69
|
+
const data = await gatewayGet<{ ok: boolean; contacts: ContactResponse[] }>(
|
|
70
|
+
`/v1/contacts${qs ? `?${qs}` : ""}`,
|
|
71
|
+
);
|
|
96
72
|
const results = data.contacts;
|
|
97
73
|
|
|
98
74
|
if (results.length === 0) {
|
|
@@ -109,6 +85,10 @@ export async function executeContactSearch(
|
|
|
109
85
|
|
|
110
86
|
return { content: lines.join("\n"), isError: false };
|
|
111
87
|
} catch (err) {
|
|
88
|
+
if (err instanceof GatewayRequestError) {
|
|
89
|
+
const message = err.gatewayError ?? err.message;
|
|
90
|
+
return { content: `Error: ${message}`, isError: true };
|
|
91
|
+
}
|
|
112
92
|
const msg = err instanceof Error ? err.message : String(err);
|
|
113
93
|
return { content: `Error: ${msg}`, isError: true };
|
|
114
94
|
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import {
|
|
2
|
+
gatewayPost,
|
|
3
|
+
GatewayRequestError,
|
|
4
|
+
} from "../../../../runtime/gateway-internal-client.js";
|
|
3
5
|
import type {
|
|
4
6
|
ToolContext,
|
|
5
7
|
ToolExecutionResult,
|
|
@@ -78,49 +80,29 @@ export async function executeContactUpsert(
|
|
|
78
80
|
}));
|
|
79
81
|
|
|
80
82
|
try {
|
|
81
|
-
const
|
|
82
|
-
const token = mintEdgeRelayToken();
|
|
83
|
-
|
|
84
|
-
const resp = await fetch(`${gatewayBase}/v1/contacts`, {
|
|
85
|
-
method: "POST",
|
|
86
|
-
headers: {
|
|
87
|
-
"Content-Type": "application/json",
|
|
88
|
-
Authorization: `Bearer ${token}`,
|
|
89
|
-
},
|
|
90
|
-
body: JSON.stringify({
|
|
91
|
-
id: input.id as string | undefined,
|
|
92
|
-
displayName: displayName.trim(),
|
|
93
|
-
relationship: input.relationship as string | undefined,
|
|
94
|
-
importance,
|
|
95
|
-
responseExpectation: input.response_expectation as string | undefined,
|
|
96
|
-
preferredTone: input.preferred_tone as string | undefined,
|
|
97
|
-
channels,
|
|
98
|
-
}),
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
if (!resp.ok) {
|
|
102
|
-
const body = await resp.text();
|
|
103
|
-
let message = `Gateway request failed (${resp.status})`;
|
|
104
|
-
try {
|
|
105
|
-
const parsed = JSON.parse(body) as { error?: string };
|
|
106
|
-
if (parsed.error) message = parsed.error;
|
|
107
|
-
} catch {
|
|
108
|
-
if (body) message = body;
|
|
109
|
-
}
|
|
110
|
-
return { content: `Error: ${message}`, isError: true };
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
const data = (await resp.json()) as {
|
|
83
|
+
const { status, data } = await gatewayPost<{
|
|
114
84
|
ok: boolean;
|
|
115
85
|
contact: ContactResponse;
|
|
116
|
-
}
|
|
117
|
-
|
|
86
|
+
}>("/v1/contacts", {
|
|
87
|
+
id: input.id as string | undefined,
|
|
88
|
+
displayName: displayName.trim(),
|
|
89
|
+
relationship: input.relationship as string | undefined,
|
|
90
|
+
importance,
|
|
91
|
+
responseExpectation: input.response_expectation as string | undefined,
|
|
92
|
+
preferredTone: input.preferred_tone as string | undefined,
|
|
93
|
+
channels,
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
const created = status === 201;
|
|
118
97
|
|
|
119
98
|
return {
|
|
120
99
|
content: `${created ? "Created" : "Updated"} contact:\n${formatContact(data.contact)}`,
|
|
121
100
|
isError: false,
|
|
122
101
|
};
|
|
123
102
|
} catch (err) {
|
|
103
|
+
if (err instanceof GatewayRequestError) {
|
|
104
|
+
return { content: `Error: ${err.message}`, isError: true };
|
|
105
|
+
}
|
|
124
106
|
const msg = err instanceof Error ? err.message : String(err);
|
|
125
107
|
return { content: `Error: ${msg}`, isError: true };
|
|
126
108
|
}
|
|
@@ -16,6 +16,10 @@
|
|
|
16
16
|
"initial_content": {
|
|
17
17
|
"type": "string",
|
|
18
18
|
"description": "Initial Markdown content to populate the editor (optional)"
|
|
19
|
+
},
|
|
20
|
+
"reason": {
|
|
21
|
+
"type": "string",
|
|
22
|
+
"description": "Brief non-technical explanation of why this tool is being called"
|
|
19
23
|
}
|
|
20
24
|
}
|
|
21
25
|
},
|
|
@@ -42,6 +46,10 @@
|
|
|
42
46
|
"type": "string",
|
|
43
47
|
"enum": ["replace", "append"],
|
|
44
48
|
"description": "Whether to replace all content or append to the end. Defaults to append."
|
|
49
|
+
},
|
|
50
|
+
"reason": {
|
|
51
|
+
"type": "string",
|
|
52
|
+
"description": "Brief non-technical explanation of why this tool is being called"
|
|
45
53
|
}
|
|
46
54
|
},
|
|
47
55
|
"required": ["surface_id", "content"]
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: "Email Setup"
|
|
3
3
|
description: "Create the assistant's own email address via the Vellum hosted API (one-time setup)"
|
|
4
4
|
user-invocable: true
|
|
5
|
-
metadata: {"vellum": {"emoji": "📧"}}
|
|
5
|
+
metadata: { "vellum": { "emoji": "📧" } }
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
You are setting up your own personal email address. This is a one-time operation — once you have an email, you do not need to run this again.
|
|
@@ -16,17 +16,17 @@ Only proceed if the user explicitly asks you to create or set up **your own** (t
|
|
|
16
16
|
Before doing anything, check whether you already have an email address configured:
|
|
17
17
|
|
|
18
18
|
```bash
|
|
19
|
-
vellum email status
|
|
19
|
+
vellum email status
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
-
Inspect `
|
|
22
|
+
Inspect `addresses` in the response. If at least one address exists, tell the user the existing address and stop — do NOT create another one.
|
|
23
23
|
|
|
24
24
|
## Step 2: Create Your Email
|
|
25
25
|
|
|
26
|
-
Create a new inbox through the
|
|
26
|
+
Create a new inbox through the CLI:
|
|
27
27
|
|
|
28
28
|
```bash
|
|
29
|
-
vellum email inbox create --username <your-username>
|
|
29
|
+
vellum email inbox create --username <your-username>
|
|
30
30
|
```
|
|
31
31
|
|
|
32
32
|
For `<your-username>`, use your assistant name (lowercased, alphanumeric only). Check your identity from `IDENTITY.md` or `USER.md` to determine your name. If you don't have a name yet, ask the user what username they'd like for your email.
|
|
@@ -36,10 +36,10 @@ Use the returned `inbox.address` (or `inbox.id` if `address` is empty) as the cr
|
|
|
36
36
|
## Step 3: Verify Status
|
|
37
37
|
|
|
38
38
|
```bash
|
|
39
|
-
vellum email status
|
|
39
|
+
vellum email status
|
|
40
40
|
```
|
|
41
41
|
|
|
42
|
-
Confirm the created inbox appears in `
|
|
42
|
+
Confirm the created inbox appears in `addresses`.
|
|
43
43
|
|
|
44
44
|
## Step 4: Confirm Setup
|
|
45
45
|
|
|
@@ -58,8 +58,11 @@ After the inbox is created and visible in status:
|
|
|
58
58
|
## Troubleshooting
|
|
59
59
|
|
|
60
60
|
### API key not configured
|
|
61
|
+
|
|
61
62
|
If you get an error about a missing API key, the email provider has not been set up. Tell the user:
|
|
63
|
+
|
|
62
64
|
> "Email isn't configured yet. Please set up the email integration first."
|
|
63
65
|
|
|
64
66
|
### Inbox creation failed
|
|
67
|
+
|
|
65
68
|
If inbox creation returns an error (e.g. username taken), try a variation of the name (append a number or use a nickname) and retry once. If it still fails, report the error to the user.
|
|
@@ -32,6 +32,10 @@
|
|
|
32
32
|
"reminder_cron_id": {
|
|
33
33
|
"type": "string",
|
|
34
34
|
"description": "Deprecated alias for reminder_schedule_id. Use reminder_schedule_id instead."
|
|
35
|
+
},
|
|
36
|
+
"reason": {
|
|
37
|
+
"type": "string",
|
|
38
|
+
"description": "Brief non-technical explanation of why this tool is being called"
|
|
35
39
|
}
|
|
36
40
|
},
|
|
37
41
|
"required": ["channel", "thread_id"]
|
|
@@ -63,6 +67,10 @@
|
|
|
63
67
|
"overdue_only": {
|
|
64
68
|
"type": "boolean",
|
|
65
69
|
"description": "When true, return only pending follow-ups past their expected response deadline"
|
|
70
|
+
},
|
|
71
|
+
"reason": {
|
|
72
|
+
"type": "string",
|
|
73
|
+
"description": "Brief non-technical explanation of why this tool is being called"
|
|
66
74
|
}
|
|
67
75
|
},
|
|
68
76
|
"required": []
|
|
@@ -89,6 +97,10 @@
|
|
|
89
97
|
"thread_id": {
|
|
90
98
|
"type": "string",
|
|
91
99
|
"description": "Thread ID to match (used with channel for auto-resolution)"
|
|
100
|
+
},
|
|
101
|
+
"reason": {
|
|
102
|
+
"type": "string",
|
|
103
|
+
"description": "Brief non-technical explanation of why this tool is being called"
|
|
92
104
|
}
|
|
93
105
|
},
|
|
94
106
|
"required": []
|