@vellumai/assistant 0.4.29 → 0.4.31
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/Dockerfile +14 -8
- package/README.md +7 -8
- package/docs/architecture/memory.md +28 -29
- package/docs/runbook-trusted-contacts.md +76 -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__/config-schema.test.ts +0 -9
- package/src/__tests__/conflict-policy.test.ts +76 -0
- package/src/__tests__/conflict-store.test.ts +14 -20
- package/src/__tests__/contacts-tools.test.ts +8 -61
- package/src/__tests__/contradiction-checker.test.ts +5 -1
- 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-decision-primitive-canonical.test.ts +5 -3
- package/src/__tests__/guardian-grant-minting.test.ts +6 -6
- package/src/__tests__/guardian-routing-invariants.test.ts +40 -15
- 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__/memory-lifecycle-e2e.test.ts +11 -10
- 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__/registry.test.ts +0 -10
- 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__/script-proxy-session-runtime.test.ts +6 -1
- package/src/__tests__/session-agent-loop.test.ts +0 -2
- package/src/__tests__/session-conflict-gate.test.ts +243 -388
- package/src/__tests__/session-media-retry.test.ts +147 -0
- package/src/__tests__/session-profile-injection.test.ts +0 -2
- package/src/__tests__/session-runtime-assembly.test.ts +2 -3
- package/src/__tests__/session-skill-tools.test.ts +0 -49
- package/src/__tests__/session-workspace-cache-state.test.ts +0 -1
- package/src/__tests__/session-workspace-injection.test.ts +0 -1
- package/src/__tests__/session-workspace-tool-tracking.test.ts +0 -1
- 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__/tool-grant-request-escalation.test.ts +2 -1
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +2 -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/approvals/guardian-decision-primitive.ts +11 -7
- package/src/approvals/guardian-request-resolvers.ts +5 -3
- package/src/calls/relay-server.ts +44 -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 +49 -53
- package/src/config/bundled-skills/contacts/TOOLS.json +26 -22
- package/src/config/bundled-skills/contacts/tools/contact-merge.ts +40 -62
- package/src/config/bundled-skills/contacts/tools/contact-search.ts +17 -43
- package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +18 -57
- 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-tool-registry.ts +2 -5
- package/src/config/channel-permission-profiles.ts +155 -0
- package/src/config/env.ts +4 -1
- package/src/config/memory-schema.ts +0 -10
- package/src/config/system-prompt.ts +6 -0
- package/src/contacts/contact-store.ts +221 -56
- package/src/contacts/contacts-write.ts +14 -3
- package/src/contacts/types.ts +35 -4
- 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-heartbeat.ts +1 -2
- package/src/daemon/handlers/config-inbox.ts +16 -134
- package/src/daemon/handlers/contacts.ts +2 -2
- package/src/daemon/handlers/guardian-actions.ts +21 -88
- package/src/daemon/handlers/sessions.ts +2 -2
- package/src/daemon/ipc-contract/apps.ts +0 -1
- package/src/daemon/ipc-contract/contacts.ts +2 -2
- 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 +2 -45
- package/src/daemon/session-attachments.ts +5 -1
- package/src/daemon/session-conflict-gate.ts +21 -82
- 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-memory.ts +7 -52
- package/src/daemon/session-process.ts +3 -1
- package/src/daemon/session-runtime-assembly.ts +18 -35
- 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/heartbeat/heartbeat-service.ts +5 -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/conflict-intent.ts +3 -6
- package/src/memory/conflict-policy.ts +34 -0
- package/src/memory/conflict-store.ts +10 -18
- package/src/memory/contradiction-checker.ts +2 -2
- package/src/memory/conversation-attention-store.ts +3 -1
- package/src/memory/db-init.ts +8 -0
- package/src/memory/job-handlers/conflict.ts +0 -7
- package/src/memory/migrations/133-assistant-contact-metadata.ts +21 -0
- package/src/memory/migrations/134-contacts-notes-column.ts +51 -0
- package/src/memory/migrations/135-backfill-contact-interaction-stats.ts +31 -0
- package/src/memory/migrations/index.ts +3 -0
- package/src/memory/schema.ts +12 -17
- package/src/memory/slack-thread-store.ts +187 -0
- package/src/messaging/index.ts +0 -1
- package/src/messaging/providers/slack/client.ts +84 -26
- package/src/messaging/providers/slack/types.ts +4 -0
- package/src/messaging/types.ts +0 -38
- 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 +128 -0
- package/src/runtime/guardian-outbound-actions.ts +3 -3
- package/src/runtime/guardian-reply-router.ts +4 -4
- package/src/runtime/guardian-verification-templates.ts +16 -1
- package/src/runtime/http-server.ts +29 -46
- 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/approval-strategies/guardian-callback-strategy.ts +6 -3
- package/src/runtime/routes/channel-route-shared.ts +35 -1
- package/src/runtime/routes/contact-routes.ts +494 -47
- package/src/runtime/routes/conversation-routes.ts +2 -1
- package/src/runtime/routes/global-search-routes.ts +2 -2
- package/src/runtime/routes/guardian-action-routes.ts +19 -111
- package/src/runtime/routes/guardian-approval-interception.ts +78 -1
- package/src/runtime/routes/guardian-bootstrap-routes.ts +6 -1
- package/src/runtime/routes/inbound-message-handler.ts +40 -12
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +227 -1
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +108 -0
- package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +2 -1
- package/src/runtime/routes/{ingress-routes.ts → invite-routes.ts} +10 -110
- package/src/runtime/routes/migration-routes.ts +17 -17
- 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/workspace/git-service.ts +6 -4
- package/src/__tests__/get-weather.test.ts +0 -393
- package/src/__tests__/weather-skill-regression.test.ts +0 -276
- package/src/autonomy/autonomy-resolver.ts +0 -62
- package/src/autonomy/autonomy-store.ts +0 -138
- package/src/autonomy/disposition-mapper.ts +0 -31
- package/src/autonomy/index.ts +0 -11
- package/src/autonomy/types.ts +0 -43
- package/src/config/bundled-skills/weather/SKILL.md +0 -38
- package/src/config/bundled-skills/weather/TOOLS.json +0 -32
- package/src/config/bundled-skills/weather/icon.svg +0 -24
- package/src/config/bundled-skills/weather/tools/get-weather.ts +0 -12
- package/src/messaging/triage-engine.ts +0 -344
- package/src/tools/weather/service.ts +0 -712
- /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
|
|
@@ -25,10 +25,7 @@ curl -s -X POST "$INTERNAL_GATEWAY_BASE_URL/v1/contacts" \
|
|
|
25
25
|
-H "Authorization: Bearer $GATEWAY_AUTH_TOKEN" \
|
|
26
26
|
-d '{
|
|
27
27
|
"displayName": "<name>",
|
|
28
|
-
"
|
|
29
|
-
"importance": 0.5,
|
|
30
|
-
"responseExpectation": "<speed>",
|
|
31
|
-
"preferredTone": "<tone>",
|
|
28
|
+
"notes": "<free-text notes about this contact>",
|
|
32
29
|
"channels": [
|
|
33
30
|
{
|
|
34
31
|
"type": "<channel_type>",
|
|
@@ -48,15 +45,12 @@ Required fields:
|
|
|
48
45
|
Optional fields:
|
|
49
46
|
|
|
50
47
|
- `id` -- contact ID to update (omit to create new, or auto-match by channel address)
|
|
51
|
-
- `
|
|
52
|
-
- `importance` -- score from 0 to 1 (default 0.5), higher means more important
|
|
53
|
-
- `responseExpectation` -- expected response speed: immediate, within_hours, within_day, casual
|
|
54
|
-
- `preferredTone` -- communication tone: formal, casual, friendly, professional
|
|
48
|
+
- `notes` -- free-text notes about this contact (e.g. relationship, communication preferences, response expectations)
|
|
55
49
|
- `channels` -- list of communication channels
|
|
56
50
|
|
|
57
51
|
### Search contacts
|
|
58
52
|
|
|
59
|
-
Search for contacts by name, channel address,
|
|
53
|
+
Search for contacts by name, channel address, or other criteria using the gateway API.
|
|
60
54
|
|
|
61
55
|
```bash
|
|
62
56
|
curl -s "$INTERNAL_GATEWAY_BASE_URL/v1/contacts?query=<search_term>" \
|
|
@@ -68,7 +62,6 @@ Optional query parameters:
|
|
|
68
62
|
- `query` -- search by display name (partial match)
|
|
69
63
|
- `channelAddress` -- search by channel address (email, phone, handle)
|
|
70
64
|
- `channelType` -- filter by channel type when searching by address
|
|
71
|
-
- `relationship` -- filter by relationship type (exact match)
|
|
72
65
|
- `limit` -- maximum results to return (default 50, max 100)
|
|
73
66
|
|
|
74
67
|
### Merge contacts
|
|
@@ -76,7 +69,7 @@ Optional query parameters:
|
|
|
76
69
|
When you discover two contacts are the same person (e.g. same person on email and Slack), merge them to consolidate. Merging:
|
|
77
70
|
|
|
78
71
|
- Combines all channels from both contacts
|
|
79
|
-
-
|
|
72
|
+
- Merges notes from both contacts
|
|
80
73
|
- Sums interaction counts
|
|
81
74
|
- Deletes the donor contact
|
|
82
75
|
|
|
@@ -96,10 +89,10 @@ Trusted contacts control who is allowed to send messages to the assistant throug
|
|
|
96
89
|
|
|
97
90
|
### Concepts
|
|
98
91
|
|
|
99
|
-
- **
|
|
100
|
-
- **Policy**: Controls what the
|
|
101
|
-
- **Status**: The
|
|
102
|
-
- **
|
|
92
|
+
- **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`.
|
|
93
|
+
- **Policy**: Controls what the contact channel can do -- `allow` (can message freely) or `deny` (blocked from messaging).
|
|
94
|
+
- **Status**: The channel's lifecycle state -- `active` (currently effective), `revoked` (access removed), or `blocked` (explicitly denied).
|
|
95
|
+
- **Channel type**: The messaging platform (e.g., `telegram`, `sms`, `voice`).
|
|
103
96
|
|
|
104
97
|
### List trusted contacts
|
|
105
98
|
|
|
@@ -146,27 +139,30 @@ Use this when the user wants to grant someone access to message the assistant. *
|
|
|
146
139
|
Ask the user: _"I'll add [name/identifier] on [channel] as an allowed contact. Should I proceed?"_
|
|
147
140
|
|
|
148
141
|
```bash
|
|
149
|
-
curl -s -X POST "$INTERNAL_GATEWAY_BASE_URL/v1/
|
|
142
|
+
curl -s -X POST "$INTERNAL_GATEWAY_BASE_URL/v1/contacts" \
|
|
150
143
|
-H "Content-Type: application/json" \
|
|
151
144
|
-H "Authorization: Bearer $GATEWAY_AUTH_TOKEN" \
|
|
152
145
|
-d '{
|
|
153
|
-
"sourceChannel": "<channel>",
|
|
154
|
-
"externalUserId": "<user_id>",
|
|
155
146
|
"displayName": "<display_name>",
|
|
156
|
-
"
|
|
157
|
-
|
|
147
|
+
"channels": [{
|
|
148
|
+
"type": "<channel>",
|
|
149
|
+
"address": "<user_id>",
|
|
150
|
+
"externalUserId": "<user_id>",
|
|
151
|
+
"status": "active",
|
|
152
|
+
"policy": "allow"
|
|
153
|
+
}]
|
|
158
154
|
}'
|
|
159
155
|
```
|
|
160
156
|
|
|
161
157
|
Required fields:
|
|
162
158
|
|
|
163
|
-
- `sourceChannel` -- the channel (e.g., `telegram`, `sms`)
|
|
164
|
-
- At least one of `externalUserId` or `externalChatId`
|
|
165
|
-
|
|
166
|
-
Optional fields:
|
|
167
|
-
|
|
168
159
|
- `displayName` -- human-readable name for the contact
|
|
169
|
-
- `
|
|
160
|
+
- `channels` -- at least one channel entry with:
|
|
161
|
+
- `type` -- the channel type (e.g., `telegram`, `sms`)
|
|
162
|
+
- `address` -- the channel-specific identifier
|
|
163
|
+
- `externalUserId` -- the user's ID on that channel (or `externalChatId` for chat-based channels)
|
|
164
|
+
- `status` -- set to `"active"` for immediate access
|
|
165
|
+
- `policy` -- set to `"allow"` to grant messaging access
|
|
170
166
|
|
|
171
167
|
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
168
|
|
|
@@ -176,16 +172,18 @@ Use this when the user wants to remove someone's access. **Always confirm with t
|
|
|
176
172
|
|
|
177
173
|
Ask the user: _"I'll revoke access for [name/identifier]. They will no longer be able to message the assistant. Should I proceed?"_
|
|
178
174
|
|
|
179
|
-
First, list
|
|
175
|
+
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:
|
|
176
|
+
|
|
177
|
+
**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
178
|
|
|
181
179
|
```bash
|
|
182
|
-
curl -s -X
|
|
183
|
-
-H "Authorization: Bearer $GATEWAY_AUTH_TOKEN" \
|
|
180
|
+
curl -s -X PATCH "$INTERNAL_GATEWAY_BASE_URL/v1/contacts/channels/<channel_id>" \
|
|
184
181
|
-H "Content-Type: application/json" \
|
|
185
|
-
-
|
|
182
|
+
-H "Authorization: Bearer $GATEWAY_AUTH_TOKEN" \
|
|
183
|
+
-d '{"status": "revoked", "reason": "<optional reason>"}'
|
|
186
184
|
```
|
|
187
185
|
|
|
188
|
-
Replace `<
|
|
186
|
+
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
187
|
|
|
190
188
|
### Block a user
|
|
191
189
|
|
|
@@ -194,12 +192,14 @@ Use this when the user wants to explicitly block someone. Blocking is stronger t
|
|
|
194
192
|
Ask the user: _"I'll block [name/identifier]. They will be permanently denied from messaging the assistant. Should I proceed?"_
|
|
195
193
|
|
|
196
194
|
```bash
|
|
197
|
-
curl -s -X
|
|
195
|
+
curl -s -X PATCH "$INTERNAL_GATEWAY_BASE_URL/v1/contacts/channels/<channel_id>" \
|
|
198
196
|
-H "Content-Type: application/json" \
|
|
199
197
|
-H "Authorization: Bearer $GATEWAY_AUTH_TOKEN" \
|
|
200
|
-
-d '{"reason": "<optional reason>"}'
|
|
198
|
+
-d '{"status": "blocked", "reason": "<optional reason>"}'
|
|
201
199
|
```
|
|
202
200
|
|
|
201
|
+
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).
|
|
202
|
+
|
|
203
203
|
## Invite Links
|
|
204
204
|
|
|
205
205
|
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 +211,7 @@ Use this when the guardian wants to invite someone to message the assistant on T
|
|
|
211
211
|
**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
212
|
|
|
213
213
|
```bash
|
|
214
|
-
INVITE_JSON=$(curl -s -X POST "$INTERNAL_GATEWAY_BASE_URL/v1/
|
|
214
|
+
INVITE_JSON=$(curl -s -X POST "$INTERNAL_GATEWAY_BASE_URL/v1/contacts/invites" \
|
|
215
215
|
-H "Content-Type: application/json" \
|
|
216
216
|
-H "Authorization: Bearer $GATEWAY_AUTH_TOKEN" \
|
|
217
217
|
-d '{
|
|
@@ -286,7 +286,7 @@ Use this when the guardian wants to authorize a specific phone number to call th
|
|
|
286
286
|
**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
287
|
|
|
288
288
|
```bash
|
|
289
|
-
INVITE_JSON=$(curl -s -X POST "$INTERNAL_GATEWAY_BASE_URL/v1/
|
|
289
|
+
INVITE_JSON=$(curl -s -X POST "$INTERNAL_GATEWAY_BASE_URL/v1/contacts/invites" \
|
|
290
290
|
-H "Content-Type: application/json" \
|
|
291
291
|
-H "Authorization: Bearer $GATEWAY_AUTH_TOKEN" \
|
|
292
292
|
-d '{
|
|
@@ -388,7 +388,7 @@ Ask the user: _"I'll revoke the invite [note or ID]. It will no longer be usable
|
|
|
388
388
|
First, list invites to find the invite's `id`, then revoke:
|
|
389
389
|
|
|
390
390
|
```bash
|
|
391
|
-
curl -s -X DELETE "$INTERNAL_GATEWAY_BASE_URL/v1/
|
|
391
|
+
curl -s -X DELETE "$INTERNAL_GATEWAY_BASE_URL/v1/contacts/invites/<invite_id>" \
|
|
392
392
|
-H "Authorization: Bearer $GATEWAY_AUTH_TOKEN"
|
|
393
393
|
```
|
|
394
394
|
|
|
@@ -397,10 +397,7 @@ Replace `<invite_id>` with the invite's `id` from the list response. The same re
|
|
|
397
397
|
## Contact Fields
|
|
398
398
|
|
|
399
399
|
- **displayName** -- the contact's name (required)
|
|
400
|
-
- **
|
|
401
|
-
- **importance** -- score from 0 to 1 (default 0.5), higher means more important
|
|
402
|
-
- **responseExpectation** -- expected response speed: immediate, within_hours, within_day, casual
|
|
403
|
-
- **preferredTone** -- communication tone: formal, casual, friendly, professional
|
|
400
|
+
- **notes** -- free-text notes about this contact (e.g. relationship, communication preferences, response expectations)
|
|
404
401
|
- **channels** -- list of communication channels (email, slack, whatsapp, phone, telegram, discord, other)
|
|
405
402
|
|
|
406
403
|
### Channel Types
|
|
@@ -425,10 +422,10 @@ Each channel has:
|
|
|
425
422
|
|
|
426
423
|
- If a request returns `{ ok: false, error: "..." }`, report the error message to the user.
|
|
427
424
|
- Common errors:
|
|
428
|
-
- `
|
|
429
|
-
- `
|
|
430
|
-
- `
|
|
431
|
-
- `
|
|
425
|
+
- `Channel not found` -- the channel ID may be invalid; list contacts to find the correct channel ID.
|
|
426
|
+
- `Channel already revoked` -- the channel has already been revoked.
|
|
427
|
+
- `Channel already blocked` -- the channel has already been blocked.
|
|
428
|
+
- `Cannot revoke a blocked channel` -- the channel is blocked; blocking is stronger than revoking. Tell the user the contact is already blocked.
|
|
432
429
|
- `sourceChannel is required for create` -- when creating an invite, always pass `"sourceChannel": "telegram"` for Telegram or `"sourceChannel": "voice"` for voice invites.
|
|
433
430
|
- `expectedExternalUserId is required for voice invites` -- voice invites must include the invitee's phone number.
|
|
434
431
|
- `expectedExternalUserId must be in E.164 format` -- the phone number must start with `+` followed by country code and number (e.g., `+15551234567`).
|
|
@@ -439,21 +436,20 @@ Each channel has:
|
|
|
439
436
|
## Tips
|
|
440
437
|
|
|
441
438
|
- Use contact search with `channelAddress` to find contacts by their email, phone, or handle.
|
|
442
|
-
- When creating follow-ups, provide a `contact_id` to link the follow-up to a specific contact
|
|
443
|
-
-
|
|
444
|
-
- When merging contacts, the surviving contact keeps the higher importance score and gains all channels from the donor.
|
|
439
|
+
- When creating follow-ups, provide a `contact_id` to link the follow-up to a specific contact.
|
|
440
|
+
- When merging contacts, the surviving contact gains all channels and merged notes from the donor.
|
|
445
441
|
|
|
446
442
|
## Typical Workflows
|
|
447
443
|
|
|
448
|
-
**"Who can message me?"** -- List all
|
|
444
|
+
**"Who can message me?"** -- List all contacts, present active channels as a formatted list.
|
|
449
445
|
|
|
450
|
-
**"Add my friend to Telegram"** -- Ask for their Telegram user ID (numeric) and
|
|
446
|
+
**"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
447
|
|
|
452
|
-
**"Remove [name]'s access"** -- List
|
|
448
|
+
**"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
449
|
|
|
454
|
-
**"Block [name]"** -- List
|
|
450
|
+
**"Block [name]"** -- List contacts to find them, identify the channel to block, confirm the block, then patch the channel status to `"blocked"`.
|
|
455
451
|
|
|
456
|
-
**"Show me blocked contacts"** -- List with `status
|
|
452
|
+
**"Show me blocked contacts"** -- List contacts and filter for channels with `status: "blocked"`.
|
|
457
453
|
|
|
458
454
|
**"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
455
|
|
|
@@ -17,21 +17,9 @@
|
|
|
17
17
|
"type": "string",
|
|
18
18
|
"description": "Display name for the contact"
|
|
19
19
|
},
|
|
20
|
-
"
|
|
20
|
+
"notes": {
|
|
21
21
|
"type": "string",
|
|
22
|
-
"description": "
|
|
23
|
-
},
|
|
24
|
-
"importance": {
|
|
25
|
-
"type": "number",
|
|
26
|
-
"description": "Importance score 0-1 (default 0.5). Higher = more important."
|
|
27
|
-
},
|
|
28
|
-
"response_expectation": {
|
|
29
|
-
"type": "string",
|
|
30
|
-
"description": "Expected response speed (e.g. immediate, within_hours, within_day, casual)"
|
|
31
|
-
},
|
|
32
|
-
"preferred_tone": {
|
|
33
|
-
"type": "string",
|
|
34
|
-
"description": "Preferred communication tone (e.g. formal, casual, friendly, professional)"
|
|
22
|
+
"description": "Free-text notes about this contact (e.g. relationship, communication preferences, response expectations)"
|
|
35
23
|
},
|
|
36
24
|
"channels": {
|
|
37
25
|
"type": "array",
|
|
@@ -41,7 +29,15 @@
|
|
|
41
29
|
"properties": {
|
|
42
30
|
"type": {
|
|
43
31
|
"type": "string",
|
|
44
|
-
"enum": [
|
|
32
|
+
"enum": [
|
|
33
|
+
"email",
|
|
34
|
+
"slack",
|
|
35
|
+
"whatsapp",
|
|
36
|
+
"phone",
|
|
37
|
+
"telegram",
|
|
38
|
+
"discord",
|
|
39
|
+
"other"
|
|
40
|
+
],
|
|
45
41
|
"description": "Channel type"
|
|
46
42
|
},
|
|
47
43
|
"address": {
|
|
@@ -55,6 +51,10 @@
|
|
|
55
51
|
},
|
|
56
52
|
"required": ["type", "address"]
|
|
57
53
|
}
|
|
54
|
+
},
|
|
55
|
+
"reason": {
|
|
56
|
+
"type": "string",
|
|
57
|
+
"description": "Brief non-technical explanation of why this tool is being called"
|
|
58
58
|
}
|
|
59
59
|
},
|
|
60
60
|
"required": ["display_name"]
|
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
},
|
|
65
65
|
{
|
|
66
66
|
"name": "contact_search",
|
|
67
|
-
"description": "Search for contacts by name, channel address,
|
|
67
|
+
"description": "Search for contacts by name, channel address, or other criteria. Returns matching contacts with their channel information.",
|
|
68
68
|
"category": "contacts",
|
|
69
69
|
"risk": "low",
|
|
70
70
|
"input_schema": {
|
|
@@ -76,19 +76,19 @@
|
|
|
76
76
|
},
|
|
77
77
|
"channel_address": {
|
|
78
78
|
"type": "string",
|
|
79
|
-
"description": "Search by channel address (email, phone, handle
|
|
79
|
+
"description": "Search by channel address (email, phone, handle \u2014 partial match)"
|
|
80
80
|
},
|
|
81
81
|
"channel_type": {
|
|
82
82
|
"type": "string",
|
|
83
83
|
"description": "Filter by channel type when searching by address (email, slack, whatsapp, phone, etc.)"
|
|
84
84
|
},
|
|
85
|
-
"relationship": {
|
|
86
|
-
"type": "string",
|
|
87
|
-
"description": "Filter by relationship type (exact match)"
|
|
88
|
-
},
|
|
89
85
|
"limit": {
|
|
90
86
|
"type": "number",
|
|
91
87
|
"description": "Maximum results to return (default 20, max 100)"
|
|
88
|
+
},
|
|
89
|
+
"reason": {
|
|
90
|
+
"type": "string",
|
|
91
|
+
"description": "Brief non-technical explanation of why this tool is being called"
|
|
92
92
|
}
|
|
93
93
|
},
|
|
94
94
|
"required": []
|
|
@@ -98,7 +98,7 @@
|
|
|
98
98
|
},
|
|
99
99
|
{
|
|
100
100
|
"name": "contact_merge",
|
|
101
|
-
"description": "Merge two contacts when you discover they are the same person (e.g. same person on email and Slack). Combines channels,
|
|
101
|
+
"description": "Merge two contacts when you discover they are the same person (e.g. same person on email and Slack). Combines channels, merges notes, and deletes the donor contact.",
|
|
102
102
|
"category": "contacts",
|
|
103
103
|
"risk": "medium",
|
|
104
104
|
"input_schema": {
|
|
@@ -111,6 +111,10 @@
|
|
|
111
111
|
"merge_id": {
|
|
112
112
|
"type": "string",
|
|
113
113
|
"description": "ID of the contact to merge into the kept contact (will be deleted)"
|
|
114
|
+
},
|
|
115
|
+
"reason": {
|
|
116
|
+
"type": "string",
|
|
117
|
+
"description": "Brief non-technical explanation of why this tool is being called"
|
|
114
118
|
}
|
|
115
119
|
},
|
|
116
120
|
"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,
|
|
@@ -14,8 +17,7 @@ interface ContactChannel {
|
|
|
14
17
|
interface ContactResponse {
|
|
15
18
|
id: string;
|
|
16
19
|
displayName: string;
|
|
17
|
-
|
|
18
|
-
importance: number;
|
|
20
|
+
notes: string | null;
|
|
19
21
|
interactionCount: number;
|
|
20
22
|
channels: ContactChannel[];
|
|
21
23
|
}
|
|
@@ -35,72 +37,49 @@ export async function executeContactMerge(
|
|
|
35
37
|
}
|
|
36
38
|
|
|
37
39
|
try {
|
|
38
|
-
const gatewayBase = getGatewayInternalBaseUrl();
|
|
39
|
-
const token = mintEdgeRelayToken();
|
|
40
|
-
const headers = {
|
|
41
|
-
Accept: "application/json",
|
|
42
|
-
Authorization: `Bearer ${token}`,
|
|
43
|
-
};
|
|
44
|
-
|
|
45
40
|
// Validate both contacts exist before merging
|
|
46
|
-
const [
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
headers,
|
|
54
|
-
}),
|
|
41
|
+
const [keepResult, mergeResult] = await Promise.allSettled([
|
|
42
|
+
gatewayGet<{ ok: boolean; contact: ContactResponse }>(
|
|
43
|
+
`/v1/contacts/${keepId}`,
|
|
44
|
+
),
|
|
45
|
+
gatewayGet<{ ok: boolean; contact: ContactResponse }>(
|
|
46
|
+
`/v1/contacts/${mergeId}`,
|
|
47
|
+
),
|
|
55
48
|
]);
|
|
56
49
|
|
|
57
|
-
if (
|
|
58
|
-
|
|
50
|
+
if (keepResult.status === "rejected") {
|
|
51
|
+
if (
|
|
52
|
+
keepResult.reason instanceof GatewayRequestError &&
|
|
53
|
+
keepResult.reason.statusCode === 404
|
|
54
|
+
) {
|
|
55
|
+
return {
|
|
56
|
+
content: `Error: Contact "${keepId}" not found`,
|
|
57
|
+
isError: true,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
throw keepResult.reason;
|
|
59
61
|
}
|
|
60
|
-
if (
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
62
|
+
if (mergeResult.status === "rejected") {
|
|
63
|
+
if (
|
|
64
|
+
mergeResult.reason instanceof GatewayRequestError &&
|
|
65
|
+
mergeResult.reason.statusCode === 404
|
|
66
|
+
) {
|
|
67
|
+
return {
|
|
68
|
+
content: `Error: Contact "${mergeId}" not found`,
|
|
69
|
+
isError: true,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
throw mergeResult.reason;
|
|
65
73
|
}
|
|
66
74
|
|
|
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;
|
|
75
|
+
const keepContact = keepResult.value.contact;
|
|
76
|
+
const mergeContact = mergeResult.value.contact;
|
|
77
77
|
|
|
78
78
|
// 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 {
|
|
79
|
+
const { data: resultData } = await gatewayPost<{
|
|
101
80
|
ok: boolean;
|
|
102
81
|
contact: ContactResponse;
|
|
103
|
-
};
|
|
82
|
+
}>("/v1/contacts/merge", { keepId, mergeId });
|
|
104
83
|
const merged = resultData.contact;
|
|
105
84
|
|
|
106
85
|
const channelList = merged.channels
|
|
@@ -116,9 +95,8 @@ export async function executeContactMerge(
|
|
|
116
95
|
``,
|
|
117
96
|
`Surviving contact (${merged.id}):`,
|
|
118
97
|
` Name: ${merged.displayName}`,
|
|
119
|
-
` Importance: ${merged.importance.toFixed(2)}`,
|
|
120
98
|
` Interactions: ${merged.interactionCount}`,
|
|
121
|
-
merged.
|
|
99
|
+
merged.notes ? ` Notes: ${merged.notes}` : null,
|
|
122
100
|
merged.channels.length > 0 ? ` Channels:\n${channelList}` : null,
|
|
123
101
|
``,
|
|
124
102
|
`Deleted contact: ${mergeContact.displayName} (${mergeId})`,
|
|
@@ -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,
|
|
@@ -14,20 +16,16 @@ interface ContactChannel {
|
|
|
14
16
|
interface ContactResponse {
|
|
15
17
|
id: string;
|
|
16
18
|
displayName: string;
|
|
17
|
-
|
|
18
|
-
importance: number;
|
|
19
|
+
notes: string | null;
|
|
19
20
|
interactionCount: number;
|
|
20
21
|
channels: ContactChannel[];
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
function formatContactSummary(c: ContactResponse): string {
|
|
24
25
|
const parts = [`- **${c.displayName}** (ID: ${c.id})`];
|
|
25
|
-
if (c.
|
|
26
|
-
|
|
27
|
-
`
|
|
28
|
-
c.interactionCount
|
|
29
|
-
}`,
|
|
30
|
-
);
|
|
26
|
+
if (c.notes) parts.push(` Notes: ${c.notes}`);
|
|
27
|
+
if (c.interactionCount > 0)
|
|
28
|
+
parts.push(` Interactions: ${c.interactionCount}`);
|
|
31
29
|
if (c.channels.length > 0) {
|
|
32
30
|
const channelList = c.channels
|
|
33
31
|
.map((ch) => `${ch.type}:${ch.address}${ch.isPrimary ? "*" : ""}`)
|
|
@@ -44,55 +42,27 @@ export async function executeContactSearch(
|
|
|
44
42
|
const query = input.query as string | undefined;
|
|
45
43
|
const channelAddress = input.channel_address as string | undefined;
|
|
46
44
|
const channelType = input.channel_type as string | undefined;
|
|
47
|
-
const relationship = input.relationship as string | undefined;
|
|
48
45
|
const limit = input.limit as number | undefined;
|
|
49
46
|
|
|
50
|
-
if (!query && !channelAddress
|
|
47
|
+
if (!query && !channelAddress) {
|
|
51
48
|
return {
|
|
52
49
|
content:
|
|
53
|
-
"Error: At least one search criterion is required (query
|
|
50
|
+
"Error: At least one search criterion is required (query or channel_address)",
|
|
54
51
|
isError: true,
|
|
55
52
|
};
|
|
56
53
|
}
|
|
57
54
|
|
|
58
55
|
try {
|
|
59
|
-
const gatewayBase = getGatewayInternalBaseUrl();
|
|
60
|
-
const token = mintEdgeRelayToken();
|
|
61
|
-
|
|
62
56
|
const params = new URLSearchParams();
|
|
63
57
|
if (query) params.set("query", query);
|
|
64
58
|
if (channelAddress) params.set("channelAddress", channelAddress);
|
|
65
59
|
if (channelType) params.set("channelType", channelType);
|
|
66
|
-
if (relationship) params.set("relationship", relationship);
|
|
67
60
|
if (limit !== undefined) params.set("limit", String(limit));
|
|
68
61
|
|
|
69
62
|
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
|
-
};
|
|
63
|
+
const data = await gatewayGet<{ ok: boolean; contacts: ContactResponse[] }>(
|
|
64
|
+
`/v1/contacts${qs ? `?${qs}` : ""}`,
|
|
65
|
+
);
|
|
96
66
|
const results = data.contacts;
|
|
97
67
|
|
|
98
68
|
if (results.length === 0) {
|
|
@@ -109,6 +79,10 @@ export async function executeContactSearch(
|
|
|
109
79
|
|
|
110
80
|
return { content: lines.join("\n"), isError: false };
|
|
111
81
|
} catch (err) {
|
|
82
|
+
if (err instanceof GatewayRequestError) {
|
|
83
|
+
const message = err.gatewayError ?? err.message;
|
|
84
|
+
return { content: `Error: ${message}`, isError: true };
|
|
85
|
+
}
|
|
112
86
|
const msg = err instanceof Error ? err.message : String(err);
|
|
113
87
|
return { content: `Error: ${msg}`, isError: true };
|
|
114
88
|
}
|