@vellumai/assistant 0.4.11 → 0.4.12
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 +401 -385
- package/package.json +1 -1
- package/src/__tests__/guardian-verify-setup-skill-regression.test.ts +75 -61
- package/src/__tests__/registry.test.ts +235 -187
- package/src/__tests__/secure-keys.test.ts +27 -0
- package/src/__tests__/session-agent-loop.test.ts +521 -256
- package/src/__tests__/session-surfaces-task-progress.test.ts +1 -0
- package/src/__tests__/session-tool-setup-app-refresh.test.ts +1 -0
- package/src/__tests__/session-tool-setup-memory-scope.test.ts +1 -0
- package/src/__tests__/session-tool-setup-side-effect-flag.test.ts +1 -0
- package/src/__tests__/skills.test.ts +334 -276
- package/src/__tests__/starter-task-flow.test.ts +7 -17
- package/src/agent/loop.ts +9 -2
- package/src/config/bundled-skills/chatgpt-import/tools/chatgpt-import.ts +449 -0
- package/src/config/bundled-skills/doordash/SKILL.md +171 -0
- package/src/config/bundled-skills/doordash/__tests__/doordash-client.test.ts +203 -0
- package/src/config/bundled-skills/doordash/__tests__/doordash-session.test.ts +164 -0
- package/src/config/bundled-skills/doordash/doordash-cli.ts +1193 -0
- package/src/config/bundled-skills/doordash/doordash-entry.ts +22 -0
- package/src/config/bundled-skills/doordash/lib/cart-queries.ts +787 -0
- package/src/config/bundled-skills/doordash/lib/client.ts +1071 -0
- package/src/config/bundled-skills/doordash/lib/order-queries.ts +85 -0
- package/src/config/bundled-skills/doordash/lib/queries.ts +28 -0
- package/src/config/bundled-skills/doordash/lib/query-extractor.ts +94 -0
- package/src/config/bundled-skills/doordash/lib/search-queries.ts +203 -0
- package/src/config/bundled-skills/doordash/lib/session.ts +93 -0
- package/src/config/bundled-skills/doordash/lib/shared/errors.ts +61 -0
- package/src/config/bundled-skills/doordash/lib/shared/ipc.ts +32 -0
- package/src/config/bundled-skills/doordash/lib/shared/network-recorder.ts +380 -0
- package/src/config/bundled-skills/doordash/lib/shared/platform.ts +35 -0
- package/src/config/bundled-skills/doordash/lib/shared/recording-store.ts +43 -0
- package/src/config/bundled-skills/doordash/lib/shared/recording-types.ts +49 -0
- package/src/config/bundled-skills/doordash/lib/shared/truncate.ts +6 -0
- package/src/config/bundled-skills/doordash/lib/store-queries.ts +246 -0
- package/src/config/bundled-skills/doordash/lib/types.ts +367 -0
- package/src/config/bundled-skills/google-calendar/SKILL.md +4 -5
- package/src/config/bundled-skills/google-oauth-setup/SKILL.md +41 -41
- package/src/config/bundled-skills/messaging/SKILL.md +59 -42
- package/src/config/bundled-skills/messaging/TOOLS.json +2 -2
- package/src/config/bundled-skills/messaging/tools/gmail-archive-by-query.ts +5 -1
- package/src/config/bundled-skills/messaging/tools/gmail-batch-archive.ts +11 -2
- package/src/config/bundled-skills/messaging/tools/gmail-sender-digest.ts +10 -3
- package/src/config/bundled-skills/messaging/tools/gmail-unsubscribe.ts +5 -1
- package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +5 -1
- package/src/config/bundled-skills/messaging/tools/messaging-sender-digest.ts +2 -1
- package/src/config/bundled-skills/notion/SKILL.md +240 -0
- package/src/config/bundled-skills/notion-oauth-setup/SKILL.md +127 -0
- package/src/config/bundled-skills/oauth-setup/SKILL.md +144 -0
- package/src/config/bundled-skills/phone-calls/SKILL.md +76 -45
- package/src/config/bundled-skills/skills-catalog/SKILL.md +32 -29
- package/src/config/{vellum-skills → bundled-skills}/sms-setup/SKILL.md +29 -22
- package/src/config/{vellum-skills → bundled-skills}/telegram-setup/SKILL.md +17 -14
- package/src/config/{vellum-skills → bundled-skills}/twilio-setup/SKILL.md +20 -5
- package/src/config/bundled-tool-registry.ts +281 -267
- package/src/daemon/handlers/skills.ts +334 -234
- package/src/daemon/ipc-contract/messages.ts +2 -0
- package/src/daemon/ipc-contract/surfaces.ts +2 -0
- package/src/daemon/lifecycle.ts +358 -221
- package/src/daemon/response-tier.ts +2 -0
- package/src/daemon/server.ts +453 -193
- package/src/daemon/session-agent-loop-handlers.ts +42 -2
- package/src/daemon/session-agent-loop.ts +3 -0
- package/src/daemon/session-lifecycle.ts +3 -0
- package/src/daemon/session-process.ts +1 -0
- package/src/daemon/session-surfaces.ts +22 -20
- package/src/daemon/session-tool-setup.ts +1 -0
- package/src/daemon/session.ts +5 -2
- package/src/messaging/outreach-classifier.ts +12 -5
- package/src/messaging/provider-types.ts +2 -0
- package/src/messaging/providers/gmail/adapter.ts +9 -3
- package/src/messaging/providers/gmail/client.ts +2 -0
- package/src/runtime/http-errors.ts +33 -20
- package/src/runtime/http-server.ts +706 -291
- package/src/runtime/http-types.ts +26 -16
- package/src/runtime/routes/secret-routes.ts +57 -2
- package/src/runtime/routes/surface-action-routes.ts +66 -0
- package/src/runtime/routes/trust-rules-routes.ts +140 -0
- package/src/security/keychain-to-encrypted-migration.ts +59 -0
- package/src/security/secure-keys.ts +17 -0
- package/src/skills/frontmatter.ts +9 -7
- package/src/tools/apps/executors.ts +2 -1
- package/src/tools/tool-manifest.ts +44 -42
- package/src/tools/types.ts +9 -0
- package/src/__tests__/skill-mirror-parity.test.ts +0 -176
- package/src/config/vellum-skills/catalog.json +0 -63
- package/src/config/vellum-skills/chatgpt-import/tools/chatgpt-import.ts +0 -295
- package/src/skills/vellum-catalog-remote.ts +0 -166
- package/src/tools/skills/vellum-catalog.ts +0 -168
- /package/src/config/{vellum-skills → bundled-skills}/chatgpt-import/SKILL.md +0 -0
- /package/src/config/{vellum-skills → bundled-skills}/chatgpt-import/TOOLS.json +0 -0
- /package/src/config/{vellum-skills → bundled-skills}/deploy-fullstack-vercel/SKILL.md +0 -0
- /package/src/config/{vellum-skills → bundled-skills}/document-writer/SKILL.md +0 -0
- /package/src/config/{vellum-skills → bundled-skills}/guardian-verify-setup/SKILL.md +0 -0
- /package/src/config/{vellum-skills → bundled-skills}/slack-oauth-setup/SKILL.md +0 -0
- /package/src/config/{vellum-skills → bundled-skills}/trusted-contacts/SKILL.md +0 -0
package/ARCHITECTURE.md
CHANGED
|
@@ -62,19 +62,19 @@ Refresh tokens provide a rotating credential renewal mechanism that avoids re-bo
|
|
|
62
62
|
|
|
63
63
|
**Key source files:**
|
|
64
64
|
|
|
65
|
-
| File
|
|
66
|
-
|
|
67
|
-
| `src/runtime/actor-token-service.ts`
|
|
68
|
-
| `src/runtime/actor-token-store.ts`
|
|
69
|
-
| `src/runtime/actor-refresh-token-service.ts`
|
|
70
|
-
| `src/runtime/actor-refresh-token-store.ts`
|
|
71
|
-
| `src/runtime/middleware/actor-token.ts`
|
|
72
|
-
| `src/runtime/local-actor-identity.ts`
|
|
73
|
-
| `src/runtime/guardian-vellum-migration.ts`
|
|
74
|
-
| `src/runtime/routes/guardian-bootstrap-routes.ts` | `POST /v1/integrations/guardian/vellum/bootstrap` handler (initial issuance only)
|
|
75
|
-
| `src/runtime/routes/guardian-refresh-routes.ts`
|
|
76
|
-
| `src/runtime/routes/pairing-routes.ts`
|
|
77
|
-
| `src/memory/guardian-bindings.ts`
|
|
65
|
+
| File | Purpose |
|
|
66
|
+
| ------------------------------------------------- | -------------------------------------------------------------------------------------------------------- |
|
|
67
|
+
| `src/runtime/actor-token-service.ts` | HMAC-SHA256 mint/verify, signing key management, `hashToken` |
|
|
68
|
+
| `src/runtime/actor-token-store.ts` | Hash-only persistence: create, find by hash/device binding, revoke |
|
|
69
|
+
| `src/runtime/actor-refresh-token-service.ts` | Refresh token rotation, replay detection, family revocation |
|
|
70
|
+
| `src/runtime/actor-refresh-token-store.ts` | Refresh token hash-only persistence: create, find, rotate, revoke by family |
|
|
71
|
+
| `src/runtime/middleware/actor-token.ts` | HTTP middleware: `verifyHttpActorToken`, `verifyHttpActorTokenWithLocalFallback`, `isActorBoundGuardian` |
|
|
72
|
+
| `src/runtime/local-actor-identity.ts` | `resolveLocalIpcGuardianContext` — deterministic IPC identity |
|
|
73
|
+
| `src/runtime/guardian-vellum-migration.ts` | `ensureVellumGuardianBinding` — startup binding backfill |
|
|
74
|
+
| `src/runtime/routes/guardian-bootstrap-routes.ts` | `POST /v1/integrations/guardian/vellum/bootstrap` handler (initial issuance only) |
|
|
75
|
+
| `src/runtime/routes/guardian-refresh-routes.ts` | `POST /v1/integrations/guardian/vellum/refresh` handler (token rotation) |
|
|
76
|
+
| `src/runtime/routes/pairing-routes.ts` | `mintPairingActorToken` — actor token + refresh token in pairing response |
|
|
77
|
+
| `src/memory/guardian-bindings.ts` | Guardian binding persistence (shared across all channels) |
|
|
78
78
|
|
|
79
79
|
### Channel-Agnostic Scoped Approval Grants
|
|
80
80
|
|
|
@@ -86,10 +86,10 @@ All guardian approval decisions — regardless of how they arrive — route thro
|
|
|
86
86
|
|
|
87
87
|
**Core API:**
|
|
88
88
|
|
|
89
|
-
| Function
|
|
90
|
-
|
|
91
|
-
| `applyGuardianDecision(params)`
|
|
92
|
-
| `listGuardianDecisionPrompts({ conversationId })` | List pending prompts for a conversation, aggregating channel guardian approval requests and pending confirmation interactions into a uniform `GuardianDecisionPrompt` shape.
|
|
89
|
+
| Function | Purpose |
|
|
90
|
+
| ------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
91
|
+
| `applyGuardianDecision(params)` | Apply a guardian decision atomically: downgrade `approve_always` for guardian-on-behalf requests, capture approval info, resolve the pending interaction, update the approval record, and mint a scoped grant on approve. Returns `{ applied, reason?, requestId? }`. |
|
|
92
|
+
| `listGuardianDecisionPrompts({ conversationId })` | List pending prompts for a conversation, aggregating channel guardian approval requests and pending confirmation interactions into a uniform `GuardianDecisionPrompt` shape. |
|
|
93
93
|
|
|
94
94
|
**Security invariants enforced by the primitive:**
|
|
95
95
|
|
|
@@ -107,14 +107,14 @@ All guardian approval decisions — regardless of how they arrive — route thro
|
|
|
107
107
|
|
|
108
108
|
**Key source files:**
|
|
109
109
|
|
|
110
|
-
| File
|
|
111
|
-
|
|
112
|
-
| `src/approvals/guardian-decision-primitive.ts` | Unified decision application: downgrade, approval info capture, `handleChannelDecision`, record update, grant minting
|
|
113
|
-
| `src/runtime/guardian-decision-types.ts`
|
|
114
|
-
| `src/runtime/routes/guardian-action-routes.ts` | HTTP route handlers for `GET /v1/guardian-actions/pending` and `POST /v1/guardian-actions/decision`
|
|
115
|
-
| `src/daemon/handlers/guardian-actions.ts`
|
|
116
|
-
| `src/daemon/ipc-contract/guardian-actions.ts`
|
|
117
|
-
| `src/runtime/channel-approval-types.ts`
|
|
110
|
+
| File | Purpose |
|
|
111
|
+
| ---------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
112
|
+
| `src/approvals/guardian-decision-primitive.ts` | Unified decision application: downgrade, approval info capture, `handleChannelDecision`, record update, grant minting |
|
|
113
|
+
| `src/runtime/guardian-decision-types.ts` | Shared types: `GuardianDecisionPrompt`, `GuardianDecisionAction`, `buildDecisionActions`, `buildPlainTextFallback`, `ApplyGuardianDecisionResult` |
|
|
114
|
+
| `src/runtime/routes/guardian-action-routes.ts` | HTTP route handlers for `GET /v1/guardian-actions/pending` and `POST /v1/guardian-actions/decision` |
|
|
115
|
+
| `src/daemon/handlers/guardian-actions.ts` | IPC handlers wrapping the same logic for desktop socket clients |
|
|
116
|
+
| `src/daemon/ipc-contract/guardian-actions.ts` | IPC message type definitions for guardian action requests/responses |
|
|
117
|
+
| `src/runtime/channel-approval-types.ts` | Channel-facing approval action types and `toApprovalActionOptions` bridge |
|
|
118
118
|
|
|
119
119
|
### Canonical Guardian Request System
|
|
120
120
|
|
|
@@ -135,21 +135,22 @@ The canonical guardian request system provides a channel-agnostic, unified domai
|
|
|
135
135
|
**Resolver registry:** Kind-specific resolvers (`src/approvals/guardian-request-resolvers.ts`) handle side effects after CAS resolution. Built-in resolvers: `tool_approval` (channel/desktop approval path) and `pending_question` (voice call question path). New request kinds register resolvers without touching the core primitive.
|
|
136
136
|
|
|
137
137
|
**Expiry sweeps:** Three complementary sweeps run on 60-second intervals to clean up stale requests:
|
|
138
|
+
|
|
138
139
|
- `src/calls/guardian-action-sweep.ts` — voice call guardian action requests
|
|
139
140
|
- `src/runtime/routes/guardian-expiry-sweep.ts` — channel guardian approval requests
|
|
140
141
|
- `src/runtime/routes/canonical-guardian-expiry-sweep.ts` — canonical guardian requests (CAS-safe)
|
|
141
142
|
|
|
142
143
|
**Key source files:**
|
|
143
144
|
|
|
144
|
-
| File
|
|
145
|
-
|
|
146
|
-
| `src/memory/canonical-guardian-store.ts`
|
|
147
|
-
| `src/approvals/guardian-decision-primitive.ts`
|
|
148
|
-
| `src/approvals/guardian-request-resolvers.ts`
|
|
149
|
-
| `src/runtime/guardian-reply-router.ts`
|
|
150
|
-
| `src/runtime/routes/guardian-action-routes.ts`
|
|
151
|
-
| `src/daemon/handlers/guardian-actions.ts`
|
|
152
|
-
| `src/runtime/routes/canonical-guardian-expiry-sweep.ts` | Canonical request expiry sweep
|
|
145
|
+
| File | Purpose |
|
|
146
|
+
| ------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- |
|
|
147
|
+
| `src/memory/canonical-guardian-store.ts` | Canonical request and delivery persistence (CRUD, CAS resolve, list with filters) |
|
|
148
|
+
| `src/approvals/guardian-decision-primitive.ts` | Unified decision primitive: `applyCanonicalGuardianDecision` (canonical) and `applyGuardianDecision` (legacy) |
|
|
149
|
+
| `src/approvals/guardian-request-resolvers.ts` | Resolver registry: kind-specific side-effect dispatch after CAS resolution |
|
|
150
|
+
| `src/runtime/guardian-reply-router.ts` | Shared inbound router: callback -> code -> NL classification pipeline |
|
|
151
|
+
| `src/runtime/routes/guardian-action-routes.ts` | HTTP endpoints for prompt listing and decision submission |
|
|
152
|
+
| `src/daemon/handlers/guardian-actions.ts` | IPC handlers for desktop socket clients |
|
|
153
|
+
| `src/runtime/routes/canonical-guardian-expiry-sweep.ts` | Canonical request expiry sweep |
|
|
153
154
|
|
|
154
155
|
### Outbound Guardian Verification (HTTP Endpoints)
|
|
155
156
|
|
|
@@ -157,11 +158,11 @@ Guardian verification can be initiated through gateway HTTP endpoints (which for
|
|
|
157
158
|
|
|
158
159
|
**HTTP Endpoints:**
|
|
159
160
|
|
|
160
|
-
| Endpoint
|
|
161
|
-
|
|
162
|
-
| `/v1/integrations/guardian/outbound/start`
|
|
163
|
-
| `/v1/integrations/guardian/outbound/resend` | POST
|
|
164
|
-
| `/v1/integrations/guardian/outbound/cancel` | POST
|
|
161
|
+
| Endpoint | Method | Description |
|
|
162
|
+
| ------------------------------------------- | ------ | --------------------------------------------------------------------------------------------------- |
|
|
163
|
+
| `/v1/integrations/guardian/outbound/start` | POST | Start a new outbound verification session. Body: `{ channel, destination?, assistantId?, rebind? }` |
|
|
164
|
+
| `/v1/integrations/guardian/outbound/resend` | POST | Resend the verification code for an active session. Body: `{ channel, assistantId? }` |
|
|
165
|
+
| `/v1/integrations/guardian/outbound/cancel` | POST | Cancel an active outbound verification session. Body: `{ channel, assistantId? }` |
|
|
165
166
|
|
|
166
167
|
All endpoints are bearer-authenticated via the gateway token (`~/.vellum/http-token` in local setups). Skills and user-facing tooling should target the gateway URL (default `http://localhost:7830`), not the runtime port.
|
|
167
168
|
|
|
@@ -179,12 +180,12 @@ The HTTP route handlers (`integration-routes.ts`) and the legacy IPC handlers (`
|
|
|
179
180
|
|
|
180
181
|
**Key Source Files:**
|
|
181
182
|
|
|
182
|
-
| File
|
|
183
|
-
|
|
184
|
-
| `src/runtime/guardian-outbound-actions.ts`
|
|
185
|
-
| `src/runtime/routes/integration-routes.ts`
|
|
186
|
-
| `src/daemon/handlers/config-channels.ts`
|
|
187
|
-
| `src/config/
|
|
183
|
+
| File | Purpose |
|
|
184
|
+
| ---------------------------------------------------------- | ---------------------------------------------------------------------------------- |
|
|
185
|
+
| `src/runtime/guardian-outbound-actions.ts` | Shared business logic for start/resend/cancel outbound verification |
|
|
186
|
+
| `src/runtime/routes/integration-routes.ts` | HTTP route handlers for `/v1/integrations/guardian/outbound/*` |
|
|
187
|
+
| `src/daemon/handlers/config-channels.ts` | IPC handler that delegates to the same shared actions |
|
|
188
|
+
| `src/config/bundled-skills/guardian-verify-setup/SKILL.md` | Skill that teaches the assistant how to orchestrate guardian verification via chat |
|
|
188
189
|
|
|
189
190
|
**Guardian-Only Tool Invocation Gate:**
|
|
190
191
|
|
|
@@ -232,24 +233,25 @@ When a voice call's ASK_GUARDIAN consultation times out before the guardian resp
|
|
|
232
233
|
|
|
233
234
|
**Key source files:**
|
|
234
235
|
|
|
235
|
-
| File
|
|
236
|
-
|
|
237
|
-
| `src/memory/guardian-action-store.ts`
|
|
238
|
-
| `src/runtime/guardian-action-message-composer.ts`
|
|
239
|
-
| `src/runtime/guardian-action-conversation-turn.ts`
|
|
240
|
-
| `src/runtime/guardian-action-followup-executor.ts`
|
|
241
|
-
| `src/daemon/guardian-action-generators.ts`
|
|
242
|
-
| `src/calls/call-controller.ts`
|
|
243
|
-
| `src/runtime/routes/inbound-message-handler.ts`
|
|
244
|
-
| `src/daemon/session-process.ts`
|
|
245
|
-
| `src/calls/guardian-action-sweep.ts`
|
|
246
|
-
| `src/memory/migrations/030-guardian-action-followup.ts` | Schema migration adding follow-up columns (`followup_state`, `late_answer_text`, `late_answered_at`, `followup_action`, `followup_completed_at`)
|
|
236
|
+
| File | Purpose |
|
|
237
|
+
| ------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
238
|
+
| `src/memory/guardian-action-store.ts` | Follow-up state machine with atomic transitions (`startFollowupFromExpiredRequest`, `progressFollowupState`, `finalizeFollowup`) and query helpers for pending/expired/follow-up deliveries |
|
|
239
|
+
| `src/runtime/guardian-action-message-composer.ts` | 2-tier text generation: daemon-injected LLM generator with deterministic fallback templates. Covers all scenarios from timeout acknowledgment through follow-up completion |
|
|
240
|
+
| `src/runtime/guardian-action-conversation-turn.ts` | Follow-up decision engine: classifies guardian replies into `call_back`, `message_back`, `decline`, or `keep_pending` dispositions using LLM tool calling |
|
|
241
|
+
| `src/runtime/guardian-action-followup-executor.ts` | Action dispatch: resolves counterparty from call session, executes `message_back` (SMS via gateway) or `call_back` (outbound call via `startCall`), finalizes follow-up state |
|
|
242
|
+
| `src/daemon/guardian-action-generators.ts` | Daemon-injected generator factories: `createGuardianActionCopyGenerator` (latency-optimized text rewriting) and `createGuardianFollowUpConversationGenerator` (tool-calling intent classification) |
|
|
243
|
+
| `src/calls/call-controller.ts` | Voice timeout handling: marks requests as timed out, sends expiry notices, injects `[GUARDIAN_TIMEOUT]` instruction for generated voice response |
|
|
244
|
+
| `src/runtime/routes/inbound-message-handler.ts` | Late reply interception for Telegram/SMS channels: matches late answers to expired requests, routes follow-up conversation turns, dispatches actions |
|
|
245
|
+
| `src/daemon/session-process.ts` | Late reply interception for mac/IPC channel: same logic as inbound-message-handler but using conversation-ID-based delivery lookup |
|
|
246
|
+
| `src/calls/guardian-action-sweep.ts` | Periodic sweep for stale pending requests; sends expiry notices to guardian destinations |
|
|
247
|
+
| `src/memory/migrations/030-guardian-action-followup.ts` | Schema migration adding follow-up columns (`followup_state`, `late_answer_text`, `late_answered_at`, `followup_action`, `followup_completed_at`) |
|
|
247
248
|
|
|
248
249
|
### SMS Channel (Twilio)
|
|
249
250
|
|
|
250
251
|
The SMS channel provides text-only messaging via Twilio, sharing the same telephony provider as voice calls. It follows the same ingress/egress pattern as Telegram but uses Twilio's HMAC-SHA1 signature validation instead of a secret header.
|
|
251
252
|
|
|
252
253
|
**Ingress** (`POST /webhooks/twilio/sms`):
|
|
254
|
+
|
|
253
255
|
1. Twilio delivers an inbound SMS as a form-encoded POST to the gateway.
|
|
254
256
|
2. The gateway validates the `X-Twilio-Signature` header using HMAC-SHA1 with the Twilio Auth Token against the canonical request URL (reconstructed from `INGRESS_PUBLIC_BASE_URL` when behind a tunnel).
|
|
255
257
|
3. `MessageSid` deduplication prevents reprocessing retried webhooks.
|
|
@@ -260,6 +262,7 @@ The SMS channel provides text-only messaging via Twilio, sharing the same teleph
|
|
|
260
262
|
8. The event is forwarded to the runtime via `POST /channels/inbound`, including SMS-specific transport hints (`chat-first-medium`, `sms-character-limits`, etc.) and a `replyCallbackUrl` pointing to `/deliver/sms`.
|
|
261
263
|
|
|
262
264
|
**Egress** (`POST /deliver/sms`):
|
|
265
|
+
|
|
263
266
|
1. The runtime calls the gateway's `/deliver/sms` endpoint with `{ to, text }` or `{ chatId, text }`. The `chatId` field is an alias for `to`, allowing the runtime channel callback (which sends `{ chatId, text }`) to work without translation. When both `to` and `chatId` are provided, `to` takes precedence.
|
|
264
267
|
2. The gateway authenticates the request via bearer token (same fail-closed model as `/deliver/telegram`).
|
|
265
268
|
3. The gateway sends the SMS via the Twilio Messages API using the configured `TWILIO_PHONE_NUMBER` as the `From` number.
|
|
@@ -271,6 +274,7 @@ The SMS channel provides text-only messaging via Twilio, sharing the same teleph
|
|
|
271
274
|
**Credential Clearing Semantics**: `clear_credentials` removes only the authentication credentials (Account SID and Auth Token) from secure storage. The phone number is preserved in both the config file (`sms.phoneNumber`) and the secure key (`credential:twilio:phone_number`) so that re-entering credentials resumes working without needing to reassign the number.
|
|
272
275
|
|
|
273
276
|
**Webhook Lifecycle**: Twilio webhook URLs are managed through a shared `syncTwilioWebhooks` helper in `config.ts` that computes voice, status-callback, and SMS URLs from the ingress config and pushes them to Twilio. Webhooks are synchronized at three points:
|
|
277
|
+
|
|
274
278
|
1. **Number provisioning** (`provision_number`) — immediately after purchasing a number.
|
|
275
279
|
2. **Number assignment** (`assign_number`) — when an existing number is assigned to the assistant.
|
|
276
280
|
3. **Ingress URL change** (`ingress_config` set) — when the public ingress URL is updated or enabled, the daemon automatically re-synchronizes Twilio webhooks (fire-and-forget) if credentials and an assigned number are present. This ensures tunnel URL changes (e.g., ngrok restart) propagate without manual re-assignment.
|
|
@@ -295,12 +299,14 @@ The WhatsApp channel enables inbound and outbound messaging via the Meta WhatsAp
|
|
|
295
299
|
8. The event is forwarded to the runtime via `POST /channels/inbound` with WhatsApp-specific transport hints and a `replyCallbackUrl` pointing to `/deliver/whatsapp`.
|
|
296
300
|
|
|
297
301
|
**Egress** (`POST /deliver/whatsapp`):
|
|
302
|
+
|
|
298
303
|
1. The runtime calls the gateway's `/deliver/whatsapp` endpoint with `{ to, text }` or `{ chatId, text }` (alias).
|
|
299
304
|
2. The gateway authenticates the request via bearer token (same fail-closed model as other deliver endpoints).
|
|
300
305
|
3. The gateway sends the message via the WhatsApp Cloud API `/{phoneNumberId}/messages` endpoint using the configured access token.
|
|
301
306
|
4. Text is split at 4096 characters if needed.
|
|
302
307
|
|
|
303
308
|
**Required credentials**:
|
|
309
|
+
|
|
304
310
|
- `WHATSAPP_PHONE_NUMBER_ID` — the numeric WhatsApp Business phone number ID from Meta
|
|
305
311
|
- `WHATSAPP_ACCESS_TOKEN` — System User or temporary access token
|
|
306
312
|
- `WHATSAPP_APP_SECRET` — App secret for webhook signature verification
|
|
@@ -320,11 +326,11 @@ The Slack channel provides text-based messaging via Slack's Socket Mode API. Unl
|
|
|
320
326
|
|
|
321
327
|
**Control-plane endpoints** (`/v1/integrations/slack/channel/config`):
|
|
322
328
|
|
|
323
|
-
| Endpoint
|
|
324
|
-
|
|
325
|
-
| `/v1/integrations/slack/channel/config` | GET
|
|
326
|
-
| `/v1/integrations/slack/channel/config` | POST
|
|
327
|
-
| `/v1/integrations/slack/channel/config` | DELETE | Clears all Slack channel credentials from secure storage and credential metadata
|
|
329
|
+
| Endpoint | Method | Description |
|
|
330
|
+
| --------------------------------------- | ------ | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
331
|
+
| `/v1/integrations/slack/channel/config` | GET | Returns current config status: `hasBotToken`, `hasAppToken`, `connected`, plus workspace metadata (`teamId`, `teamName`, `botUserId`, `botUsername`) |
|
|
332
|
+
| `/v1/integrations/slack/channel/config` | POST | Validates and stores credentials. Body: `{ botToken?: string, appToken?: string }` |
|
|
333
|
+
| `/v1/integrations/slack/channel/config` | DELETE | Clears all Slack channel credentials from secure storage and credential metadata |
|
|
328
334
|
|
|
329
335
|
All endpoints are bearer-authenticated via the runtime HTTP token (`~/.vellum/http-token`).
|
|
330
336
|
|
|
@@ -332,9 +338,9 @@ All endpoints are bearer-authenticated via the runtime HTTP token (`~/.vellum/ht
|
|
|
332
338
|
|
|
333
339
|
Both tokens are stored in the secure key store (macOS Keychain with encrypted file fallback):
|
|
334
340
|
|
|
335
|
-
| Secure key
|
|
336
|
-
|
|
337
|
-
| `credential:slack_channel:bot_token` | Slack bot token (used for `chat.postMessage` and `auth.test`)
|
|
341
|
+
| Secure key | Content |
|
|
342
|
+
| ------------------------------------ | -------------------------------------------------------------------------- |
|
|
343
|
+
| `credential:slack_channel:bot_token` | Slack bot token (used for `chat.postMessage` and `auth.test`) |
|
|
338
344
|
| `credential:slack_channel:app_token` | Slack app token (`xapp-...`, used for Socket Mode `apps.connections.open`) |
|
|
339
345
|
|
|
340
346
|
Workspace metadata (team ID, team name, bot user ID, bot username) is stored as JSON in the credential metadata store under `('slack_channel', 'bot_token')`.
|
|
@@ -351,10 +357,10 @@ Both `GET` and `POST` endpoints report `connected: true` only when both `hasBotT
|
|
|
351
357
|
|
|
352
358
|
**Key source files:**
|
|
353
359
|
|
|
354
|
-
| File
|
|
355
|
-
|
|
356
|
-
| `src/daemon/handlers/config-slack-channel.ts` | Business logic for get/set/clear Slack channel config
|
|
357
|
-
| `src/runtime/routes/integration-routes.ts`
|
|
360
|
+
| File | Purpose |
|
|
361
|
+
| --------------------------------------------- | --------------------------------------------------------------- |
|
|
362
|
+
| `src/daemon/handlers/config-slack-channel.ts` | Business logic for get/set/clear Slack channel config |
|
|
363
|
+
| `src/runtime/routes/integration-routes.ts` | HTTP route handlers for `/v1/integrations/slack/channel/config` |
|
|
358
364
|
|
|
359
365
|
### Trusted Contact Access (Channel-Agnostic)
|
|
360
366
|
|
|
@@ -363,6 +369,7 @@ External users who are not the guardian can gain access to the assistant through
|
|
|
363
369
|
**Full design doc:** [`docs/trusted-contact-access.md`](docs/trusted-contact-access.md)
|
|
364
370
|
|
|
365
371
|
**Flow summary:**
|
|
372
|
+
|
|
366
373
|
1. Unknown user messages the assistant on any channel.
|
|
367
374
|
2. Ingress ACL (`inbound-message-handler.ts`) rejects the message and emits an `ingress.access_request` notification signal to the guardian.
|
|
368
375
|
3. Guardian approves or denies via callback button or conversational intent (routed through `guardian-approval-interception.ts`).
|
|
@@ -376,6 +383,7 @@ External users who are not the guardian can gain access to the assistant through
|
|
|
376
383
|
**Lifecycle states:** `requested → pending_guardian → verification_pending → active | denied | expired`
|
|
377
384
|
|
|
378
385
|
**Notification signals:** The flow emits signals at each lifecycle transition via `emitNotificationSignal()`:
|
|
386
|
+
|
|
379
387
|
- `ingress.access_request` — non-member denied, guardian notified
|
|
380
388
|
- `ingress.trusted_contact.guardian_decision` — guardian approved or denied
|
|
381
389
|
- `ingress.trusted_contact.verification_sent` — code created and delivered
|
|
@@ -384,26 +392,26 @@ External users who are not the guardian can gain access to the assistant through
|
|
|
384
392
|
|
|
385
393
|
**HTTP API (for management):**
|
|
386
394
|
|
|
387
|
-
| Endpoint
|
|
388
|
-
|
|
389
|
-
| `/v1/ingress/members`
|
|
390
|
-
| `/v1/ingress/members`
|
|
391
|
-
| `/v1/ingress/members/:id`
|
|
392
|
-
| `/v1/ingress/members/:id/block` | POST
|
|
395
|
+
| Endpoint | Method | Description |
|
|
396
|
+
| ------------------------------- | ------ | ------------------------------------------------------------- |
|
|
397
|
+
| `/v1/ingress/members` | GET | List trusted contacts (filterable by channel, status, policy) |
|
|
398
|
+
| `/v1/ingress/members` | POST | Upsert a member (add/update trusted contact) |
|
|
399
|
+
| `/v1/ingress/members/:id` | DELETE | Revoke a trusted contact |
|
|
400
|
+
| `/v1/ingress/members/:id/block` | POST | Block a member |
|
|
393
401
|
|
|
394
402
|
**Key source files:**
|
|
395
403
|
|
|
396
|
-
| File
|
|
397
|
-
|
|
398
|
-
| `src/runtime/routes/inbound-message-handler.ts`
|
|
399
|
-
| `src/runtime/routes/access-request-decision.ts`
|
|
404
|
+
| File | Purpose |
|
|
405
|
+
| ------------------------------------------------------ | ----------------------------------------------------------------------------- |
|
|
406
|
+
| `src/runtime/routes/inbound-message-handler.ts` | Ingress ACL, non-member rejection, verification code interception |
|
|
407
|
+
| `src/runtime/routes/access-request-decision.ts` | Guardian decision → verification session creation |
|
|
400
408
|
| `src/runtime/routes/guardian-approval-interception.ts` | Routes guardian decisions (button + conversational) to access request handler |
|
|
401
|
-
| `src/runtime/channel-guardian-service.ts`
|
|
402
|
-
| `src/runtime/routes/ingress-routes.ts`
|
|
403
|
-
| `src/runtime/ingress-service.ts`
|
|
404
|
-
| `src/memory/ingress-member-store.ts`
|
|
405
|
-
| `src/memory/channel-guardian-store.ts`
|
|
406
|
-
| `src/config/
|
|
409
|
+
| `src/runtime/channel-guardian-service.ts` | Verification challenge lifecycle, identity binding, rate limiting |
|
|
410
|
+
| `src/runtime/routes/ingress-routes.ts` | HTTP API handlers for member/invite management |
|
|
411
|
+
| `src/runtime/ingress-service.ts` | Business logic for member CRUD |
|
|
412
|
+
| `src/memory/ingress-member-store.ts` | Member record persistence |
|
|
413
|
+
| `src/memory/channel-guardian-store.ts` | Approval request and verification challenge persistence |
|
|
414
|
+
| `src/config/bundled-skills/trusted-contacts/SKILL.md` | Skill teaching the assistant to manage contacts via HTTP API |
|
|
407
415
|
|
|
408
416
|
### Guardian-Initiated Invite Links
|
|
409
417
|
|
|
@@ -448,24 +456,26 @@ A complementary access-granting flow where the guardian proactively creates a sh
|
|
|
448
456
|
|
|
449
457
|
**Channel adapter status:**
|
|
450
458
|
|
|
451
|
-
| Channel
|
|
452
|
-
|
|
453
|
-
| Telegram | Shipped
|
|
454
|
-
| Voice
|
|
455
|
-
| SMS
|
|
456
|
-
| Slack
|
|
459
|
+
| Channel | Status | Prerequisites |
|
|
460
|
+
| -------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
461
|
+
| Telegram | Shipped | Bot username resolved from credential metadata or `TELEGRAM_BOT_USERNAME` env |
|
|
462
|
+
| Voice | Shipped | Identity-bound voice code redemption via DTMF/speech in the relay state machine. Always-on canonical behavior with personalized friend/guardian name prompts. |
|
|
463
|
+
| SMS | Deferred | Needs a deep-link strategy compatible with SMS (short URL or web redemption page) |
|
|
464
|
+
| Slack | Deferred | Needs DM-safe ingress — Socket Mode handles channel messages but DM-initiated invite flows need routing |
|
|
457
465
|
|
|
458
466
|
### Voice Invite Flow (invite_redemption_pending)
|
|
459
467
|
|
|
460
468
|
Voice invites use a short numeric code (4-10 digits, default 6) instead of a URL token. The guardian creates an invite bound to the invitee's E.164 phone number; the invitee redeems it by entering the code during an inbound voice call.
|
|
461
469
|
|
|
462
470
|
**Creation flow:**
|
|
471
|
+
|
|
463
472
|
1. Guardian creates a voice invite via `POST /v1/ingress/invites` with `sourceChannel: "voice"` and `expectedExternalUserId` (E.164 phone).
|
|
464
473
|
2. `ingress-service.ts` generates a cryptographically random numeric code (`generateVoiceCode`), hashes it with SHA-256 (`hashVoiceCode`), and stores only the hash.
|
|
465
474
|
3. The one-time plaintext `voiceCode` is returned in the creation response. The raw token is NOT returned for voice invites — redemption uses the identity-bound code flow exclusively.
|
|
466
475
|
4. Guardian communicates the code to the invitee out-of-band.
|
|
467
476
|
|
|
468
477
|
**Call-time redemption subflow (`invite_redemption_pending`):**
|
|
478
|
+
|
|
469
479
|
1. Unknown caller dials in. `relay-server.ts` resolves trust via `resolveActorTrust`. Caller is `unknown`, no pending guardian challenge.
|
|
470
480
|
2. The relay checks `findActiveVoiceInvites` for invites bound to the caller's phone number.
|
|
471
481
|
3. If active, non-expired invites exist, the relay enters the `invite_redemption_pending` state (reuses the `verification_pending` connection state) and prompts the caller with personalized copy: `Welcome <friend-name>. Please enter the 6-digit code that <guardian-name> provided you to verify your identity.`
|
|
@@ -473,6 +483,7 @@ Voice invites use a short numeric code (4-10 digits, default 6) instead of a URL
|
|
|
473
483
|
5. On invalid/expired code, the caller hears deterministic failure copy: `Sorry, the code you provided is incorrect or has since expired. Please ask <guardian-name> for a new code. Goodbye.` and the call ends immediately.
|
|
474
484
|
|
|
475
485
|
**Security invariants:**
|
|
486
|
+
|
|
476
487
|
- The plaintext voice code is returned exactly once at creation time and never stored.
|
|
477
488
|
- Voice invites are identity-bound: `expectedExternalUserId` must match the caller's E.164 number. An attacker with the code but the wrong phone number cannot redeem.
|
|
478
489
|
- Failure responses are intentionally generic (`invalid_or_expired`) to prevent oracle attacks.
|
|
@@ -480,20 +491,20 @@ Voice invites use a short numeric code (4-10 digits, default 6) instead of a URL
|
|
|
480
491
|
|
|
481
492
|
**Key source files:**
|
|
482
493
|
|
|
483
|
-
| File
|
|
484
|
-
|
|
485
|
-
| `src/runtime/invite-redemption-service.ts`
|
|
486
|
-
| `src/runtime/invite-redemption-templates.ts`
|
|
487
|
-
| `src/runtime/channel-invite-transport.ts`
|
|
488
|
-
| `src/runtime/channel-invite-transports/telegram.ts` | Telegram adapter — `t.me/<bot>?start=iv_<token>` deep links, `/start iv_<token>` extraction
|
|
489
|
-
| `src/runtime/channel-invite-transports/voice.ts`
|
|
490
|
-
| `src/daemon/guardian-invite-intent.ts`
|
|
491
|
-
| `src/runtime/ingress-service.ts`
|
|
492
|
-
| `src/runtime/routes/ingress-routes.ts`
|
|
493
|
-
| `src/runtime/routes/inbound-message-handler.ts`
|
|
494
|
-
| `src/calls/relay-server.ts`
|
|
495
|
-
| `src/util/voice-code.ts`
|
|
496
|
-
| `src/memory/ingress-invite-store.ts`
|
|
494
|
+
| File | Purpose |
|
|
495
|
+
| --------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- |
|
|
496
|
+
| `src/runtime/invite-redemption-service.ts` | Core redemption engine — token validation, voice code redemption, member creation, discriminated-union outcomes |
|
|
497
|
+
| `src/runtime/invite-redemption-templates.ts` | Deterministic reply templates for each redemption outcome |
|
|
498
|
+
| `src/runtime/channel-invite-transport.ts` | Transport adapter registry with `buildShareableInvite` / `extractInboundToken` interface |
|
|
499
|
+
| `src/runtime/channel-invite-transports/telegram.ts` | Telegram adapter — `t.me/<bot>?start=iv_<token>` deep links, `/start iv_<token>` extraction |
|
|
500
|
+
| `src/runtime/channel-invite-transports/voice.ts` | Voice transport adapter — code-based redemption metadata |
|
|
501
|
+
| `src/daemon/guardian-invite-intent.ts` | Intent detection — routes create/list/revoke requests into the trusted-contacts skill |
|
|
502
|
+
| `src/runtime/ingress-service.ts` | Shared business logic for invite/member operations (used by both HTTP routes and IPC) |
|
|
503
|
+
| `src/runtime/routes/ingress-routes.ts` | HTTP API handlers for member/invite management including voice invite creation and redemption |
|
|
504
|
+
| `src/runtime/routes/inbound-message-handler.ts` | Invite token intercept in the inbound flow (non-member and inactive-member branches) |
|
|
505
|
+
| `src/calls/relay-server.ts` | Voice relay state machine — `invite_redemption_pending` subflow (always-on canonical behavior) |
|
|
506
|
+
| `src/util/voice-code.ts` | Cryptographic voice code generation and SHA-256 hashing |
|
|
507
|
+
| `src/memory/ingress-invite-store.ts` | Invite persistence including `findActiveVoiceInvites` for identity-bound lookup |
|
|
497
508
|
|
|
498
509
|
### Voice Inbound Security Model (Canonical)
|
|
499
510
|
|
|
@@ -545,6 +556,7 @@ When a pending voice guardian challenge exists (`getPendingChallenge`), the call
|
|
|
545
556
|
**Canonical decision routing:**
|
|
546
557
|
|
|
547
558
|
All guardian decisions for voice access requests flow through:
|
|
559
|
+
|
|
548
560
|
- `applyCanonicalGuardianDecision` (canonical guardian request system)
|
|
549
561
|
- `accessRequestResolver` in `guardian-request-resolvers.ts` (kind-specific resolver)
|
|
550
562
|
- For voice approvals: direct trusted-contact activation (no verification session needed since the caller is already on the line)
|
|
@@ -552,20 +564,21 @@ All guardian decisions for voice access requests flow through:
|
|
|
552
564
|
|
|
553
565
|
**Key source files:**
|
|
554
566
|
|
|
555
|
-
| File
|
|
556
|
-
|
|
557
|
-
| `src/calls/relay-server.ts`
|
|
558
|
-
| `src/runtime/access-request-helper.ts`
|
|
559
|
-
| `src/approvals/guardian-decision-primitive.ts` | `applyCanonicalGuardianDecision` — unified decision primitive
|
|
560
|
-
| `src/approvals/guardian-request-resolvers.ts`
|
|
561
|
-
| `src/runtime/actor-trust-resolver.ts`
|
|
562
|
-
| `src/memory/canonical-guardian-store.ts`
|
|
567
|
+
| File | Purpose |
|
|
568
|
+
| ---------------------------------------------- | -------------------------------------------------------------------------------------- |
|
|
569
|
+
| `src/calls/relay-server.ts` | Inbound call decision tree, name capture, guardian approval wait polling |
|
|
570
|
+
| `src/runtime/access-request-helper.ts` | Creates canonical access request and notifies guardian |
|
|
571
|
+
| `src/approvals/guardian-decision-primitive.ts` | `applyCanonicalGuardianDecision` — unified decision primitive |
|
|
572
|
+
| `src/approvals/guardian-request-resolvers.ts` | `access_request` resolver — voice direct activation, text-channel verification session |
|
|
573
|
+
| `src/runtime/actor-trust-resolver.ts` | `resolveActorTrust` — caller trust classification |
|
|
574
|
+
| `src/memory/canonical-guardian-store.ts` | Canonical request persistence and CAS resolution |
|
|
563
575
|
|
|
564
576
|
### Update Bulletin System
|
|
565
577
|
|
|
566
578
|
Release-driven update notification system that surfaces release notes to the assistant via the system prompt.
|
|
567
579
|
|
|
568
580
|
**Data flow:**
|
|
581
|
+
|
|
569
582
|
1. **Bundled template** (`src/config/templates/UPDATES.md`) — source of release notes, maintained per-release in the repo.
|
|
570
583
|
2. **Startup sync** (`syncUpdateBulletinOnStartup()` in `src/config/update-bulletin.ts`) — materializes the bundled template into the workspace `UPDATES.md` on daemon boot. Uses atomic write (temp + rename) for crash safety.
|
|
571
584
|
3. **System prompt injection** — `buildSystemPrompt()` reads workspace `UPDATES.md` and injects it as a `## Recent Updates` section with judgment-based handling instructions.
|
|
@@ -573,20 +586,21 @@ Release-driven update notification system that surfaces release notes to the ass
|
|
|
573
586
|
5. **Cross-release merge** — if pending updates from a prior release exist when a new release lands, both release blocks coexist in the same file.
|
|
574
587
|
|
|
575
588
|
**Checkpoint keys** (in `memory_checkpoints` table):
|
|
589
|
+
|
|
576
590
|
- `updates:active_releases` — JSON array of version strings currently active.
|
|
577
591
|
- `updates:completed_releases` — JSON array of version strings already completed.
|
|
578
592
|
|
|
579
593
|
**Key source files:**
|
|
580
594
|
|
|
581
|
-
| File
|
|
582
|
-
|
|
583
|
-
| `src/config/templates/UPDATES.md`
|
|
584
|
-
| `src/config/update-bulletin.ts`
|
|
585
|
-
| `src/config/update-bulletin-format.ts` | Release block formatter/parser helpers
|
|
586
|
-
| `src/config/update-bulletin-state.ts`
|
|
587
|
-
| `src/config/system-prompt.ts`
|
|
588
|
-
| `src/daemon/config-watcher.ts`
|
|
589
|
-
| `src/permissions/defaults.ts`
|
|
595
|
+
| File | Purpose |
|
|
596
|
+
| -------------------------------------- | --------------------------------------------------------- |
|
|
597
|
+
| `src/config/templates/UPDATES.md` | Bundled release-note template |
|
|
598
|
+
| `src/config/update-bulletin.ts` | Startup sync logic (materialize, delete-complete, merge) |
|
|
599
|
+
| `src/config/update-bulletin-format.ts` | Release block formatter/parser helpers |
|
|
600
|
+
| `src/config/update-bulletin-state.ts` | Checkpoint state helpers for active/completed releases |
|
|
601
|
+
| `src/config/system-prompt.ts` | Prompt injection of updates section |
|
|
602
|
+
| `src/daemon/config-watcher.ts` | File watcher — evicts sessions on UPDATES.md changes |
|
|
603
|
+
| `src/permissions/defaults.ts` | Auto-allow rules for file_read/write/edit + rm UPDATES.md |
|
|
590
604
|
|
|
591
605
|
---
|
|
592
606
|
|
|
@@ -597,6 +611,7 @@ The assistant feature-flag resolver (`src/config/assistant-feature-flags.ts`) is
|
|
|
597
611
|
**Canonical key format:** `feature_flags.<flag_id>.enabled` (e.g., `feature_flags.hatch-new-assistant.enabled`).
|
|
598
612
|
|
|
599
613
|
**Resolution priority** (highest wins):
|
|
614
|
+
|
|
600
615
|
1. `config.assistantFeatureFlagValues[key]` — canonical config section, written by the gateway's PATCH endpoint
|
|
601
616
|
2. Defaults registry `defaultEnabled` — from the unified registry (`meta/feature-flags/feature-flag-registry.json`, filtered to `scope: "assistant"`)
|
|
602
617
|
3. `true` — unknown/undeclared flags with no persisted override default to enabled
|
|
@@ -604,19 +619,20 @@ The assistant feature-flag resolver (`src/config/assistant-feature-flags.ts`) is
|
|
|
604
619
|
**Storage:** Flags are persisted in `~/.vellum/workspace/config.json`. New writes go to the `assistantFeatureFlagValues` section (managed by the gateway's `/v1/feature-flags` API — see [`gateway/ARCHITECTURE.md`](../gateway/ARCHITECTURE.md)). The legacy `featureFlags` section is still read for backward compatibility. The daemon's config watcher hot-reloads this file, so flag changes take effect on the next tool resolution or session.
|
|
605
620
|
|
|
606
621
|
**Public API:**
|
|
622
|
+
|
|
607
623
|
- `isAssistantFeatureFlagEnabled(key, config)` — full resolver with the canonical key
|
|
608
624
|
- `isAssistantSkillEnabled(skillId, config)` — convenience wrapper that constructs `feature_flags.<skillId>.enabled` and delegates
|
|
609
625
|
- `isSkillFeatureEnabled(skillId, config)` — deprecated legacy wrapper in `config/skill-state.ts`
|
|
610
626
|
|
|
611
627
|
**Skill-gating guarantee:** For skills that are explicitly mapped to declared assistant flags, when the flag is OFF the skill is unavailable everywhere — it cannot appear in client UIs, model context, or runtime tool execution. This is enforced at five independent points:
|
|
612
628
|
|
|
613
|
-
| Enforcement Point
|
|
614
|
-
|
|
615
|
-
| **1. Client skill list**
|
|
616
|
-
| **2. System prompt skill catalog** | `appendSkillsCatalog()` in `config/system-prompt.ts`
|
|
617
|
-
| **3. `skill_load` tool**
|
|
618
|
-
| **4. Runtime tool projection**
|
|
619
|
-
| **5. Included child skills**
|
|
629
|
+
| Enforcement Point | Module | Effect |
|
|
630
|
+
| ---------------------------------- | -------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
631
|
+
| **1. Client skill list** | `resolveSkillStates()` in `config/skill-state.ts` | Skills with flag OFF are excluded from the resolved list returned to IPC clients (macOS skill list, settings UI). The skill never appears in the client. |
|
|
632
|
+
| **2. System prompt skill catalog** | `appendSkillsCatalog()` in `config/system-prompt.ts` | The model-visible `## Skills Catalog` section in the system prompt filters out flagged-off skills. The model cannot see or reference them. |
|
|
633
|
+
| **3. `skill_load` tool** | `executeSkillLoad()` in `tools/skills/load.ts` | If the model attempts to load a flagged-off skill by name, the tool returns an error: `"skill is currently unavailable (disabled by feature flag)"`. |
|
|
634
|
+
| **4. Runtime tool projection** | `projectSkillTools()` in `daemon/session-skill-tools.ts` | Even if a skill was previously active in a session (has `<loaded_skill>` markers in history), the per-turn projection drops it when the flag is OFF. Already-registered tools are unregistered. |
|
|
635
|
+
| **5. Included child skills** | `executeSkillLoad()` in `tools/skills/load.ts` | When a parent skill includes children via the `includes` directive, each child is independently checked against its feature flag. Flagged-off children are silently excluded from the loaded skill content. |
|
|
620
636
|
|
|
621
637
|
All five enforcement points use `isAssistantSkillEnabled()` from `config/assistant-feature-flags.ts` for consistency.
|
|
622
638
|
|
|
@@ -624,18 +640,18 @@ All five enforcement points use `isAssistantSkillEnabled()` from `config/assista
|
|
|
624
640
|
|
|
625
641
|
**Key source files:**
|
|
626
642
|
|
|
627
|
-
| File
|
|
628
|
-
|
|
629
|
-
| `src/config/assistant-feature-flags.ts`
|
|
630
|
-
| `src/config/skill-state.ts`
|
|
631
|
-
| `src/config/system-prompt.ts`
|
|
632
|
-
| `src/tools/skills/load.ts`
|
|
633
|
-
| `src/daemon/session-skill-tools.ts`
|
|
634
|
-
| `src/config/schema.ts`
|
|
635
|
-
| `src/config/types.ts`
|
|
636
|
-
| `src/daemon/handlers/skills.ts`
|
|
637
|
-
| `meta/feature-flags/feature-flag-registry.json` | Unified feature flag registry (repo root) — all declared flags with scope, label, default values, and descriptions
|
|
638
|
-
| `src/config/feature-flag-registry.json`
|
|
643
|
+
| File | Purpose |
|
|
644
|
+
| ----------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
|
|
645
|
+
| `src/config/assistant-feature-flags.ts` | Canonical resolver: `isAssistantFeatureFlagEnabled()`, `isAssistantSkillEnabled()`, `getAssistantFeatureFlagDefaults()`, registry loader |
|
|
646
|
+
| `src/config/skill-state.ts` | `isSkillFeatureEnabled()` (deprecated wrapper) — delegates to canonical resolver; `resolveSkillStates()` — enforcement point 1 |
|
|
647
|
+
| `src/config/system-prompt.ts` | `appendSkillsCatalog()` — enforcement point 2 |
|
|
648
|
+
| `src/tools/skills/load.ts` | `executeSkillLoad()` — enforcement points 3 and 5 |
|
|
649
|
+
| `src/daemon/session-skill-tools.ts` | `projectSkillTools()` — enforcement point 4 |
|
|
650
|
+
| `src/config/schema.ts` | `featureFlags` and `assistantFeatureFlagValues` field definitions in `AssistantConfig` (Zod schema) |
|
|
651
|
+
| `src/config/types.ts` | Type definitions for `FeatureFlags` (legacy) and `AssistantFeatureFlagValues` (canonical) |
|
|
652
|
+
| `src/daemon/handlers/skills.ts` | `handleSkillsList()` — uses `resolveSkillStates()` for IPC client responses |
|
|
653
|
+
| `meta/feature-flags/feature-flag-registry.json` | Unified feature flag registry (repo root) — all declared flags with scope, label, default values, and descriptions |
|
|
654
|
+
| `src/config/feature-flag-registry.json` | Bundled copy of the unified registry for compiled binary resolution |
|
|
639
655
|
|
|
640
656
|
---
|
|
641
657
|
|
|
@@ -711,7 +727,6 @@ graph LR
|
|
|
711
727
|
|
|
712
728
|
---
|
|
713
729
|
|
|
714
|
-
|
|
715
730
|
---
|
|
716
731
|
|
|
717
732
|
## Web Server — Connection Modes
|
|
@@ -979,30 +994,31 @@ When the daemon receives a CU observation with blob refs, it attempts blob-first
|
|
|
979
994
|
|
|
980
995
|
The daemon emits two distinct error message types over IPC:
|
|
981
996
|
|
|
982
|
-
| Message type
|
|
983
|
-
|
|
997
|
+
| Message type | Scope | Purpose | Payload |
|
|
998
|
+
| --------------- | -------------- | -------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------- |
|
|
984
999
|
| `session_error` | Session-scoped | Typed, actionable failures during chat/session runtime (e.g., provider network error, rate limit, API failure) | `sessionId`, `code` (typed enum), `userMessage`, `retryable`, `debugDetails?` |
|
|
985
|
-
| `error`
|
|
1000
|
+
| `error` | Global | Generic, non-session failures (e.g., daemon startup errors, unknown message types) | `message` (string) |
|
|
986
1001
|
|
|
987
1002
|
**Design rationale:** `session_error` carries structured metadata (error code, retryable flag, debug details) so the client can present actionable UI — a toast with retry/dismiss buttons — rather than a generic error banner. The older `error` type is retained for backward compatibility with non-session contexts.
|
|
988
1003
|
|
|
989
1004
|
### Session Error Codes
|
|
990
1005
|
|
|
991
|
-
| Code
|
|
992
|
-
|
|
993
|
-
| `PROVIDER_NETWORK`
|
|
994
|
-
| `PROVIDER_RATE_LIMIT`
|
|
995
|
-
| `PROVIDER_API`
|
|
996
|
-
| `QUEUE_FULL`
|
|
997
|
-
| `SESSION_ABORTED`
|
|
998
|
-
| `SESSION_PROCESSING_FAILED` | Catch-all for unexpected processing failures
|
|
999
|
-
| `REGENERATE_FAILED`
|
|
1006
|
+
| Code | Meaning | Retryable |
|
|
1007
|
+
| --------------------------- | ------------------------------------------------------------------- | --------- |
|
|
1008
|
+
| `PROVIDER_NETWORK` | Unable to reach the LLM provider (connection refused, timeout, DNS) | Yes |
|
|
1009
|
+
| `PROVIDER_RATE_LIMIT` | LLM provider rate-limited the request (HTTP 429) | Yes |
|
|
1010
|
+
| `PROVIDER_API` | Provider returned a server error (5xx) | Yes |
|
|
1011
|
+
| `QUEUE_FULL` | The message queue is full | Yes |
|
|
1012
|
+
| `SESSION_ABORTED` | Non-user abort interrupted the request | Yes |
|
|
1013
|
+
| `SESSION_PROCESSING_FAILED` | Catch-all for unexpected processing failures | No |
|
|
1014
|
+
| `REGENERATE_FAILED` | Failed to regenerate a previous response | Yes |
|
|
1000
1015
|
|
|
1001
1016
|
### Error Classification
|
|
1002
1017
|
|
|
1003
1018
|
The daemon classifies errors via `classifySessionError()` in `session-error.ts`. Before classification, `isUserCancellation()` checks whether the error is a user-initiated abort (active abort signal or `AbortError`); if so, the daemon emits `generation_cancelled` instead of `session_error` — cancel never surfaces a session-error toast.
|
|
1004
1019
|
|
|
1005
1020
|
Classification uses a two-tier strategy:
|
|
1021
|
+
|
|
1006
1022
|
1. **Structured provider errors**: If the error is a `ProviderError` with a `statusCode`, the status code determines the category deterministically — `429` maps to `PROVIDER_RATE_LIMIT` (retryable), `5xx` to `PROVIDER_API` (retryable), other `4xx` to `PROVIDER_API` (not retryable).
|
|
1007
1023
|
2. **Regex fallback**: For non-provider errors or `ProviderError` without a status code, regex pattern matching against the error message detects network failures, rate limits, and API errors. Phase-specific overrides handle queue and regeneration contexts.
|
|
1008
1024
|
|
|
@@ -1038,7 +1054,7 @@ sequenceDiagram
|
|
|
1038
1054
|
end
|
|
1039
1055
|
```
|
|
1040
1056
|
|
|
1041
|
-
1. **Daemon** encounters a session-scoped failure, classifies it via `classifySessionError()`, and sends a `session_error` IPC message with the session ID, typed error code, user-facing message, retryable flag, and optional debug details. Session-scoped failures emit
|
|
1057
|
+
1. **Daemon** encounters a session-scoped failure, classifies it via `classifySessionError()`, and sends a `session_error` IPC message with the session ID, typed error code, user-facing message, retryable flag, and optional debug details. Session-scoped failures emit _only_ `session_error` (never the generic `error` type) to prevent cross-session bleed.
|
|
1042
1058
|
2. **ChatViewModel** receives the error via DaemonClient's `subscribe()` stream (each view model gets an independent stream), sets the `sessionError` property, and transitions out of the streaming/loading state so the UI is interactive. If the error arrives during an active cancel (`wasCancelling == true`), it is suppressed — cancel only shows `generation_cancelled` behavior.
|
|
1043
1059
|
3. **ChatView** observes the published `sessionError` and displays an actionable toast with a category-specific icon and accent color:
|
|
1044
1060
|
- **Retry** (shown when `retryable` is true): calls `retryAfterSessionError()`, which clears the error and sends a `regenerate` message to the daemon.
|
|
@@ -1088,14 +1104,14 @@ graph TB
|
|
|
1088
1104
|
|
|
1089
1105
|
The text_qa system prompt includes an action execution hierarchy that guides tool selection toward the least invasive method:
|
|
1090
1106
|
|
|
1091
|
-
| Priority
|
|
1092
|
-
|
|
1093
|
-
| **BEST**
|
|
1094
|
-
| **BETTER**
|
|
1095
|
-
| **GOOD**
|
|
1096
|
-
| **LAST RESORT** | Foreground computer use
|
|
1107
|
+
| Priority | Method | Tool | When to use |
|
|
1108
|
+
| --------------- | ------------------------------ | ------------------------------------- | ----------------------------------------------------------- |
|
|
1109
|
+
| **BEST** | Sandboxed filesystem/shell | `file_*`, `bash` | Work that can stay isolated in sandbox filesystem |
|
|
1110
|
+
| **BETTER** | Explicit host filesystem/shell | `host_file_*`, `host_bash` | Host reads/writes/commands that must touch the real machine |
|
|
1111
|
+
| **GOOD** | Headless browser | `browser_*` (bundled `browser` skill) | Web automation, form filling, scraping (background) |
|
|
1112
|
+
| **LAST RESORT** | Foreground computer use | `computer_use_request_control` | Only on explicit user request ("go ahead", "take over") |
|
|
1097
1113
|
|
|
1098
|
-
The `computer_use_request_control` tool is a core proxy tool available only to
|
|
1114
|
+
The `computer_use_request_control` tool is a core proxy tool available only to text*qa sessions. When invoked, the session's `surfaceProxyResolver` creates a CU session and sends a `task_routed` message to the client, effectively escalating from text_qa to foreground computer use. The CU session constructor sets `preactivatedSkillIds: ['computer-use']`, and its `getProjectedCuToolDefinitions()` calls `projectSkillTools()` to load the 12 `computer_use*\*`action tools from the bundled`computer-use` skill (via TOOLS.json). These tools are not core-registered at daemon startup; they exist only within CU sessions through skill projection.
|
|
1099
1115
|
|
|
1100
1116
|
### Sandbox Filesystem and Host Access
|
|
1101
1117
|
|
|
@@ -1159,6 +1175,7 @@ graph TB
|
|
|
1159
1175
|
```
|
|
1160
1176
|
|
|
1161
1177
|
Key behaviors:
|
|
1178
|
+
|
|
1162
1179
|
- **Known**: Content is rewritten via `rewriteKnownSlashCommandPrompt` to instruct the model to invoke the skill. Trailing arguments are preserved.
|
|
1163
1180
|
- **Unknown**: A deterministic `assistant_text_delta` + `message_complete` is emitted listing available slash commands. No message persistence or model call occurs.
|
|
1164
1181
|
- **Queue**: Queued messages receive the same slash resolution. Unknown slash commands in the queue emit their response and continue draining without stalling.
|
|
@@ -1227,6 +1244,7 @@ graph TB
|
|
|
1227
1244
|
```
|
|
1228
1245
|
|
|
1229
1246
|
**Key design decisions:**
|
|
1247
|
+
|
|
1230
1248
|
- `evaluate_typescript_code` always forces `sandbox.enabled = true` regardless of global config.
|
|
1231
1249
|
- Snippet contract: must export `default` or `run` with signature `(input: unknown) => unknown | Promise<unknown>`.
|
|
1232
1250
|
- Managed-store writes are atomic (tmp file + rename) to prevent partial `SKILL.md` or `SKILLS.md` files.
|
|
@@ -1256,18 +1274,19 @@ graph LR
|
|
|
1256
1274
|
```
|
|
1257
1275
|
|
|
1258
1276
|
**Validation rules:**
|
|
1277
|
+
|
|
1259
1278
|
- **Missing children**: If any skill in the recursive graph references an `includes` ID not found in the catalog, validation fails with the full path from root to the missing reference.
|
|
1260
1279
|
- **Cycles**: Three-state DFS (unseen → visiting → done) detects direct and indirect cycles. The error includes the cycle path.
|
|
1261
1280
|
- **Fail-closed**: On any validation error, `skill_load` returns `isError: true` with no `<loaded_skill>` marker, preventing the agent from using a skill with broken dependencies.
|
|
1262
1281
|
|
|
1263
1282
|
**Key constraint**: Include metadata is metadata-only. Child skills are **not** auto-activated — the agent must explicitly call `skill_load` for each child. The `projectSkillTools()` function only projects tools for skills with explicit `<loaded_skill>` markers in conversation history.
|
|
1264
1283
|
|
|
1265
|
-
| Source File
|
|
1266
|
-
|
|
1284
|
+
| Source File | Purpose |
|
|
1285
|
+
| --------------------------------------- | ------------------------------------------------------------------------------------------ |
|
|
1267
1286
|
| `assistant/src/skills/include-graph.ts` | `indexCatalogById()`, `getImmediateChildren()`, `validateIncludes()`, `traverseIncludes()` |
|
|
1268
|
-
| `assistant/src/tools/skills/load.ts`
|
|
1269
|
-
| `assistant/src/config/skills.ts`
|
|
1270
|
-
| `assistant/src/skills/managed-store.ts` | `includes` emission in `buildSkillMarkdown()`
|
|
1287
|
+
| `assistant/src/tools/skills/load.ts` | Include validation integration in `skill_load` execute path |
|
|
1288
|
+
| `assistant/src/config/skills.ts` | `includes` field parsing from SKILL.md frontmatter |
|
|
1289
|
+
| `assistant/src/skills/managed-store.ts` | `includes` emission in `buildSkillMarkdown()` |
|
|
1271
1290
|
|
|
1272
1291
|
---
|
|
1273
1292
|
|
|
@@ -1291,16 +1310,16 @@ skills/<skill-id>/
|
|
|
1291
1310
|
|
|
1292
1311
|
The following capabilities ship as bundled skills in `assistant/src/config/bundled-skills/`:
|
|
1293
1312
|
|
|
1294
|
-
| Skill ID
|
|
1295
|
-
|
|
1296
|
-
| `browser`
|
|
1297
|
-
| `gmail`
|
|
1298
|
-
| `claude-code`
|
|
1299
|
-
| `computer-use`
|
|
1300
|
-
| `weather`
|
|
1301
|
-
| `app-builder`
|
|
1302
|
-
| `self-upgrade`
|
|
1303
|
-
| `start-the-day` | (instruction-only)
|
|
1313
|
+
| Skill ID | Tools | Purpose |
|
|
1314
|
+
| --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
1315
|
+
| `browser` | `browser_navigate`, `browser_snapshot`, `browser_screenshot`, `browser_close`, `browser_click`, `browser_type`, `browser_press_key`, `browser_wait_for`, `browser_extract`, `browser_fill_credential` | Headless browser automation — web scraping, form filling, interaction (previously core-registered as `headless-browser`; now skill-provided with default allow rules) |
|
|
1316
|
+
| `gmail` | Gmail search, archive, send, etc. | Email management via OAuth2 integration |
|
|
1317
|
+
| `claude-code` | Claude Code tool | Delegate coding tasks to Claude Code subprocess |
|
|
1318
|
+
| `computer-use` | `computer_use_click`, `computer_use_double_click`, `computer_use_right_click`, `computer_use_type_text`, `computer_use_key`, `computer_use_scroll`, `computer_use_drag`, `computer_use_open_app`, `computer_use_run_applescript`, `computer_use_wait`, `computer_use_done`, `computer_use_respond` | Computer-use action tools — internally preactivated by `ComputerUseSession` via `preactivatedSkillIds`; not user-invocable or model-discoverable in text sessions. Each wrapper script forwards to `forwardComputerUseProxyTool()` which uses the session's proxy resolver to send actions to the macOS client. |
|
|
1319
|
+
| `weather` | `get-weather` | Fetch current weather data |
|
|
1320
|
+
| `app-builder` | `app_create`, `app_list`, `app_query`, `app_update`, `app_delete`, `app_file_list`, `app_file_read`, `app_file_edit`, `app_file_write` | Dynamic app authoring — CRUD and file-level editing for persistent apps (activated via `skill_load app-builder`; `app_open` remains a core proxy tool) |
|
|
1321
|
+
| `self-upgrade` | (instruction-only) | Self-improvement workflow |
|
|
1322
|
+
| `start-the-day` | (instruction-only) | Morning briefing routine |
|
|
1304
1323
|
|
|
1305
1324
|
### Activation and Projection Flow
|
|
1306
1325
|
|
|
@@ -1380,19 +1399,19 @@ graph TB
|
|
|
1380
1399
|
|
|
1381
1400
|
### Key Source Files
|
|
1382
1401
|
|
|
1383
|
-
| File
|
|
1384
|
-
|
|
1385
|
-
| `assistant/src/config/skills.ts`
|
|
1386
|
-
| `assistant/src/config/bundled-skills/`
|
|
1387
|
-
| `assistant/src/skills/tool-manifest.ts`
|
|
1388
|
-
| `assistant/src/skills/active-skill-tools.ts`
|
|
1389
|
-
| `assistant/src/skills/include-graph.ts`
|
|
1390
|
-
| `assistant/src/daemon/session-skill-tools.ts`
|
|
1391
|
-
| `assistant/src/tools/skills/skill-tool-factory.ts`
|
|
1392
|
-
| `assistant/src/tools/skills/skill-script-runner.ts` | Host runner: dynamic import + `run()` call
|
|
1393
|
-
| `assistant/src/tools/skills/sandbox-runner.ts`
|
|
1394
|
-
| `assistant/src/tools/registry.ts`
|
|
1395
|
-
| `assistant/src/permissions/checker.ts`
|
|
1402
|
+
| File | Role |
|
|
1403
|
+
| --------------------------------------------------- | ------------------------------------------------------------------------------------------ |
|
|
1404
|
+
| `assistant/src/config/skills.ts` | Skill catalog loading: bundled, managed, workspace, extra directories |
|
|
1405
|
+
| `assistant/src/config/bundled-skills/` | Bundled skill directories (browser, gmail, claude-code, computer-use, weather, etc.) |
|
|
1406
|
+
| `assistant/src/skills/tool-manifest.ts` | `TOOLS.json` parser and validator |
|
|
1407
|
+
| `assistant/src/skills/active-skill-tools.ts` | `deriveActiveSkillIds()` — scans history for `<loaded_skill>` markers |
|
|
1408
|
+
| `assistant/src/skills/include-graph.ts` | Include graph builder: `indexCatalogById()`, `validateIncludes()`, cycle/missing detection |
|
|
1409
|
+
| `assistant/src/daemon/session-skill-tools.ts` | `projectSkillTools()` — per-turn projection, register/unregister lifecycle |
|
|
1410
|
+
| `assistant/src/tools/skills/skill-tool-factory.ts` | `createSkillToolsFromManifest()` — manifest entries to Tool objects |
|
|
1411
|
+
| `assistant/src/tools/skills/skill-script-runner.ts` | Host runner: dynamic import + `run()` call |
|
|
1412
|
+
| `assistant/src/tools/skills/sandbox-runner.ts` | Sandbox runner: isolated subprocess execution |
|
|
1413
|
+
| `assistant/src/tools/registry.ts` | `registerSkillTools()` / `unregisterSkillTools()` — global tool registry |
|
|
1414
|
+
| `assistant/src/permissions/checker.ts` | Skill-origin default-ask permission policy |
|
|
1396
1415
|
|
|
1397
1416
|
---
|
|
1398
1417
|
|
|
@@ -1436,19 +1455,19 @@ graph TB
|
|
|
1436
1455
|
|
|
1437
1456
|
The `permissions.mode` config option (`workspace`, `strict`, or `legacy`) controls the default behavior when no trust rule matches a tool invocation. The default is `workspace`.
|
|
1438
1457
|
|
|
1439
|
-
| Behavior
|
|
1440
|
-
|
|
1441
|
-
| Workspace-scoped ops with no matching rule
|
|
1442
|
-
| Non-workspace low-risk tools with no matching rule | Auto-allowed
|
|
1443
|
-
| Medium-risk tools with no matching rule
|
|
1444
|
-
| High-risk tools with no matching rule
|
|
1445
|
-
| `skill_load` with no matching rule
|
|
1446
|
-
| `skill_load` with system default rule
|
|
1447
|
-
| `browser_*` skill tools with system default rules
|
|
1448
|
-
| Skill-origin tools with no matching rule
|
|
1449
|
-
| Allow rules for non-high-risk tools
|
|
1450
|
-
| Allow rules with `allowHighRisk: true`
|
|
1451
|
-
| Deny rules
|
|
1458
|
+
| Behavior | Workspace mode (default) | Strict mode | Legacy mode (deprecated) |
|
|
1459
|
+
| -------------------------------------------------- | --------------------------------------------- | --------------------------------------------- | --------------------------------------------- |
|
|
1460
|
+
| Workspace-scoped ops with no matching rule | Auto-allowed | Prompted | Auto-allowed (low risk) |
|
|
1461
|
+
| Non-workspace low-risk tools with no matching rule | Auto-allowed | Prompted | Auto-allowed |
|
|
1462
|
+
| Medium-risk tools with no matching rule | Prompted | Prompted | Prompted |
|
|
1463
|
+
| High-risk tools with no matching rule | Prompted | Prompted | Prompted |
|
|
1464
|
+
| `skill_load` with no matching rule | Prompted | Prompted | Auto-allowed (low risk) |
|
|
1465
|
+
| `skill_load` with system default rule | Auto-allowed (`skill_load:*` at priority 100) | Auto-allowed (`skill_load:*` at priority 100) | Auto-allowed (`skill_load:*` at priority 100) |
|
|
1466
|
+
| `browser_*` skill tools with system default rules | Auto-allowed (priority 100 allow rules) | Auto-allowed (priority 100 allow rules) | Auto-allowed (priority 100 allow rules) |
|
|
1467
|
+
| Skill-origin tools with no matching rule | Prompted | Prompted | Prompted |
|
|
1468
|
+
| Allow rules for non-high-risk tools | Auto-allowed | Auto-allowed | Auto-allowed |
|
|
1469
|
+
| Allow rules with `allowHighRisk: true` | Auto-allowed (even high risk) | Auto-allowed (even high risk) | Auto-allowed (even high risk) |
|
|
1470
|
+
| Deny rules | Blocked | Blocked | Blocked |
|
|
1452
1471
|
|
|
1453
1472
|
**Workspace mode** (default) auto-allows operations scoped to the workspace (file reads/writes/edits within the workspace directory, sandboxed bash) without prompting. Host operations, network requests, and operations outside the workspace still follow the normal approval flow. Explicit deny and ask rules override auto-allow.
|
|
1454
1473
|
|
|
@@ -1460,16 +1479,16 @@ The `permissions.mode` config option (`workspace`, `strict`, or `legacy`) contro
|
|
|
1460
1479
|
|
|
1461
1480
|
Rules are stored in `~/.vellum/protected/trust.json` with version `3`. Each rule can include the following fields:
|
|
1462
1481
|
|
|
1463
|
-
| Field
|
|
1464
|
-
|
|
1465
|
-
| `id`
|
|
1466
|
-
| `tool`
|
|
1467
|
-
| `pattern`
|
|
1468
|
-
| `scope`
|
|
1469
|
-
| `decision`
|
|
1470
|
-
| `priority`
|
|
1471
|
-
| `executionTarget` | `string?`
|
|
1472
|
-
| `allowHighRisk`
|
|
1482
|
+
| Field | Type | Purpose |
|
|
1483
|
+
| ----------------- | ---------------------- | ------------------------------------------------------------------------ |
|
|
1484
|
+
| `id` | `string` | Unique identifier (UUID for user rules, `default:*` for system defaults) |
|
|
1485
|
+
| `tool` | `string` | Tool name to match (e.g., `bash`, `file_write`, `skill_load`) |
|
|
1486
|
+
| `pattern` | `string` | Minimatch glob pattern for the command/target string |
|
|
1487
|
+
| `scope` | `string` | Path prefix or `everywhere` — restricts where the rule applies |
|
|
1488
|
+
| `decision` | `allow \| deny \| ask` | What to do when the rule matches |
|
|
1489
|
+
| `priority` | `number` | Higher priority wins; deny wins ties at equal priority |
|
|
1490
|
+
| `executionTarget` | `string?` | `sandbox` or `host` — restricts by execution context |
|
|
1491
|
+
| `allowHighRisk` | `boolean?` | When true, auto-allows even high-risk invocations |
|
|
1473
1492
|
|
|
1474
1493
|
Missing optional fields act as wildcards. A rule with no `executionTarget` matches any target.
|
|
1475
1494
|
|
|
@@ -1477,16 +1496,16 @@ Missing optional fields act as wildcards. A rule with no `executionTarget` match
|
|
|
1477
1496
|
|
|
1478
1497
|
The `classifyRisk()` function determines the risk level for each tool invocation:
|
|
1479
1498
|
|
|
1480
|
-
| Tool
|
|
1481
|
-
|
|
1482
|
-
| `file_read`, `web_search`, `skill_load`
|
|
1483
|
-
| `file_write`, `file_edit`
|
|
1484
|
-
| `file_write`, `file_edit` targeting skill source paths
|
|
1485
|
-
| `host_file_write`, `host_file_edit` targeting skill source paths | **High**
|
|
1486
|
-
| `bash`, `host_bash`
|
|
1487
|
-
| `scaffold_managed_skill`, `delete_managed_skill`
|
|
1488
|
-
| `evaluate_typescript_code`
|
|
1489
|
-
| Skill-origin tools with no matching rule
|
|
1499
|
+
| Tool | Risk level | Notes |
|
|
1500
|
+
| ---------------------------------------------------------------- | --------------------------- | -------------------------------------------------------------------------------------------- |
|
|
1501
|
+
| `file_read`, `web_search`, `skill_load` | Low | Read-only or informational |
|
|
1502
|
+
| `file_write`, `file_edit` | Medium (default) | Filesystem mutations |
|
|
1503
|
+
| `file_write`, `file_edit` targeting skill source paths | **High** | `isSkillSourcePath()` detects managed/bundled/workspace/extra skill roots |
|
|
1504
|
+
| `host_file_write`, `host_file_edit` targeting skill source paths | **High** | Same path classification, host variant |
|
|
1505
|
+
| `bash`, `host_bash` | Varies | Parsed via tree-sitter: low-risk programs = Low, high-risk programs = High, unknown = Medium |
|
|
1506
|
+
| `scaffold_managed_skill`, `delete_managed_skill` | High | Skill lifecycle mutations always high-risk |
|
|
1507
|
+
| `evaluate_typescript_code` | High | Arbitrary code execution |
|
|
1508
|
+
| Skill-origin tools with no matching rule | Prompted regardless of risk | Even Low-risk skill tools default to `ask` |
|
|
1490
1509
|
|
|
1491
1510
|
The escalation of skill source file mutations to High risk is a privilege-escalation defense: modifying skill source code could grant the agent new capabilities, so such operations always require explicit approval.
|
|
1492
1511
|
|
|
@@ -1504,14 +1523,14 @@ In strict mode, `skill_load` without a matching rule is always prompted. In lega
|
|
|
1504
1523
|
|
|
1505
1524
|
The starter bundle is an opt-in set of low-risk allow rules that reduces prompt noise, particularly in strict mode. It covers read-only tools that never mutate the filesystem or execute arbitrary code:
|
|
1506
1525
|
|
|
1507
|
-
| Rule
|
|
1508
|
-
|
|
1509
|
-
| `file_read`
|
|
1510
|
-
| `glob`
|
|
1511
|
-
| `grep`
|
|
1526
|
+
| Rule | Tool | Pattern |
|
|
1527
|
+
| ---------------- | ---------------- | ------------------- |
|
|
1528
|
+
| `file_read` | `file_read` | `file_read:**` |
|
|
1529
|
+
| `glob` | `glob` | `glob:**` |
|
|
1530
|
+
| `grep` | `grep` | `grep:**` |
|
|
1512
1531
|
| `list_directory` | `list_directory` | `list_directory:**` |
|
|
1513
|
-
| `web_search`
|
|
1514
|
-
| `web_fetch`
|
|
1532
|
+
| `web_search` | `web_search` | `web_search:**` |
|
|
1533
|
+
| `web_fetch` | `web_fetch` | `web_fetch:**` |
|
|
1515
1534
|
|
|
1516
1535
|
Acceptance is idempotent and persisted as `starterBundleAccepted: true` in `trust.json`. Rules are seeded at priority 90 (below user rules at 100, above system defaults at 50).
|
|
1517
1536
|
|
|
@@ -1519,19 +1538,19 @@ Acceptance is idempotent and persisted as `starterBundleAccepted: true` in `trus
|
|
|
1519
1538
|
|
|
1520
1539
|
In addition to the opt-in starter bundle, the permission system seeds unconditional default allow rules at priority 100 for two categories:
|
|
1521
1540
|
|
|
1522
|
-
| Rule ID
|
|
1523
|
-
|
|
1524
|
-
| `default:allow-skill_load-global`
|
|
1525
|
-
| `default:allow-browser_navigate-global`
|
|
1526
|
-
| `default:allow-browser_snapshot-global`
|
|
1527
|
-
| `default:allow-browser_screenshot-global`
|
|
1528
|
-
| `default:allow-browser_close-global`
|
|
1529
|
-
| `default:allow-browser_click-global`
|
|
1530
|
-
| `default:allow-browser_type-global`
|
|
1531
|
-
| `default:allow-browser_press_key-global`
|
|
1532
|
-
| `default:allow-browser_wait_for-global`
|
|
1533
|
-
| `default:allow-browser_extract-global`
|
|
1534
|
-
| `default:allow-browser_fill_credential-global` | `browser_fill_credential` | `browser_fill_credential:*` | (same)
|
|
1541
|
+
| Rule ID | Tool | Pattern | Rationale |
|
|
1542
|
+
| ---------------------------------------------- | ------------------------- | --------------------------- | -------------------------------------------------------------------------------------------------------- |
|
|
1543
|
+
| `default:allow-skill_load-global` | `skill_load` | `skill_load:*` | Loading any skill is globally allowed — no prompt for activating bundled, managed, or workspace skills |
|
|
1544
|
+
| `default:allow-browser_navigate-global` | `browser_navigate` | `browser_navigate:*` | Browser tools migrated from core to the bundled `browser` skill; default allow preserves frictionless UX |
|
|
1545
|
+
| `default:allow-browser_snapshot-global` | `browser_snapshot` | `browser_snapshot:*` | (same) |
|
|
1546
|
+
| `default:allow-browser_screenshot-global` | `browser_screenshot` | `browser_screenshot:*` | (same) |
|
|
1547
|
+
| `default:allow-browser_close-global` | `browser_close` | `browser_close:*` | (same) |
|
|
1548
|
+
| `default:allow-browser_click-global` | `browser_click` | `browser_click:*` | (same) |
|
|
1549
|
+
| `default:allow-browser_type-global` | `browser_type` | `browser_type:*` | (same) |
|
|
1550
|
+
| `default:allow-browser_press_key-global` | `browser_press_key` | `browser_press_key:*` | (same) |
|
|
1551
|
+
| `default:allow-browser_wait_for-global` | `browser_wait_for` | `browser_wait_for:*` | (same) |
|
|
1552
|
+
| `default:allow-browser_extract-global` | `browser_extract` | `browser_extract:*` | (same) |
|
|
1553
|
+
| `default:allow-browser_fill_credential-global` | `browser_fill_credential` | `browser_fill_credential:*` | (same) |
|
|
1535
1554
|
|
|
1536
1555
|
These rules are emitted by `getDefaultRuleTemplates()` in `assistant/src/permissions/defaults.ts`. Because they use priority 100 (equal to user rules), they take effect in both strict and legacy modes. The `skill_load` rule means skill activation never prompts; the `browser_*` rules mean the browser skill's tools behave identically to the old core `headless-browser` tool from a permission standpoint.
|
|
1537
1556
|
|
|
@@ -1554,14 +1573,14 @@ For `bash` and `host_bash` tool invocations, the permission system uses parser-d
|
|
|
1554
1573
|
|
|
1555
1574
|
When a permission prompt is sent to the client (via `confirmation_request` IPC message), it includes:
|
|
1556
1575
|
|
|
1557
|
-
| Field
|
|
1558
|
-
|
|
1559
|
-
| `toolName`
|
|
1560
|
-
| `input`
|
|
1561
|
-
| `riskLevel`
|
|
1562
|
-
| `executionTarget`
|
|
1563
|
-
| `allowlistOptions` | Suggested patterns for "always allow" rules
|
|
1564
|
-
| `scopeOptions`
|
|
1576
|
+
| Field | Content |
|
|
1577
|
+
| ------------------ | --------------------------------------------------- |
|
|
1578
|
+
| `toolName` | The tool being invoked |
|
|
1579
|
+
| `input` | Redacted tool input (sensitive fields removed) |
|
|
1580
|
+
| `riskLevel` | `low`, `medium`, or `high` |
|
|
1581
|
+
| `executionTarget` | `sandbox` or `host` — where the action will execute |
|
|
1582
|
+
| `allowlistOptions` | Suggested patterns for "always allow" rules |
|
|
1583
|
+
| `scopeOptions` | Suggested scopes for rule persistence |
|
|
1565
1584
|
|
|
1566
1585
|
The user can respond with: `allow` (one-time), `always_allow` (create allow rule), `always_allow_high_risk` (create allow rule with `allowHighRisk: true`), `deny` (one-time), or `always_deny` (create deny rule).
|
|
1567
1586
|
|
|
@@ -1571,19 +1590,19 @@ File tool candidates include canonical (symlink-resolved) absolute paths via `no
|
|
|
1571
1590
|
|
|
1572
1591
|
### Key Source Files
|
|
1573
1592
|
|
|
1574
|
-
| File
|
|
1575
|
-
|
|
1576
|
-
| `assistant/src/permissions/types.ts`
|
|
1577
|
-
| `assistant/src/permissions/checker.ts`
|
|
1593
|
+
| File | Role |
|
|
1594
|
+
| --------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
1595
|
+
| `assistant/src/permissions/types.ts` | `TrustRule`, `PolicyContext`, `RiskLevel`, `UserDecision` types |
|
|
1596
|
+
| `assistant/src/permissions/checker.ts` | `classifyRisk()`, `check()`, `buildCommandCandidates()`, allowlist/scope generation |
|
|
1578
1597
|
| `assistant/src/permissions/shell-identity.ts` | `analyzeShellCommand()`, `deriveShellActionKeys()`, `buildShellCommandCandidates()`, `buildShellAllowlistOptions()` — parser-based shell command identity and action key derivation |
|
|
1579
|
-
| `assistant/src/permissions/trust-store.ts`
|
|
1580
|
-
| `assistant/src/permissions/prompter.ts`
|
|
1581
|
-
| `assistant/src/permissions/defaults.ts`
|
|
1582
|
-
| `assistant/src/skills/version-hash.ts`
|
|
1583
|
-
| `assistant/src/skills/path-classifier.ts`
|
|
1584
|
-
| `assistant/src/config/schema.ts`
|
|
1585
|
-
| `assistant/src/tools/executor.ts`
|
|
1586
|
-
| `assistant/src/daemon/handlers/config.ts`
|
|
1598
|
+
| `assistant/src/permissions/trust-store.ts` | Rule persistence, `findHighestPriorityRule()`, execution-target matching, starter bundle |
|
|
1599
|
+
| `assistant/src/permissions/prompter.ts` | IPC prompt flow: `confirmation_request` → `confirmation_response` |
|
|
1600
|
+
| `assistant/src/permissions/defaults.ts` | Default rule templates (system ask rules for host tools, CU, etc.) |
|
|
1601
|
+
| `assistant/src/skills/version-hash.ts` | `computeSkillVersionHash()` — deterministic SHA-256 of skill source files |
|
|
1602
|
+
| `assistant/src/skills/path-classifier.ts` | `isSkillSourcePath()`, `normalizeFilePath()`, skill root detection |
|
|
1603
|
+
| `assistant/src/config/schema.ts` | `PermissionsConfigSchema` — `permissions.mode` (`workspace` / `strict` / `legacy`) |
|
|
1604
|
+
| `assistant/src/tools/executor.ts` | `ToolExecutor` — orchestrates risk classification, permission check, and execution |
|
|
1605
|
+
| `assistant/src/daemon/handlers/config.ts` | `handleToolPermissionSimulate()` — dry-run simulation handler |
|
|
1587
1606
|
|
|
1588
1607
|
### Permission Simulation (Tool Permission Tester)
|
|
1589
1608
|
|
|
@@ -1656,15 +1675,15 @@ sequenceDiagram
|
|
|
1656
1675
|
|
|
1657
1676
|
### Config knobs
|
|
1658
1677
|
|
|
1659
|
-
| Config key
|
|
1660
|
-
|
|
1661
|
-
| `swarm.enabled`
|
|
1662
|
-
| `swarm.maxWorkers`
|
|
1663
|
-
| `swarm.maxTasks`
|
|
1664
|
-
| `swarm.maxRetriesPerTask` |
|
|
1665
|
-
| `swarm.workerTimeoutSec`
|
|
1666
|
-
| `swarm.plannerModel`
|
|
1667
|
-
| `swarm.synthesizerModel`
|
|
1678
|
+
| Config key | Default | Purpose |
|
|
1679
|
+
| ------------------------- | -------: | ------------------------------------------------- |
|
|
1680
|
+
| `swarm.enabled` | `true` | Master switch for swarm orchestration |
|
|
1681
|
+
| `swarm.maxWorkers` | `3` | Max concurrent worker processes (hard ceiling: 6) |
|
|
1682
|
+
| `swarm.maxTasks` | `8` | Max tasks per plan (hard ceiling: 20) |
|
|
1683
|
+
| `swarm.maxRetriesPerTask` | `1` | Per-task retry limit (hard ceiling: 3) |
|
|
1684
|
+
| `swarm.workerTimeoutSec` | `900` | Worker timeout in seconds |
|
|
1685
|
+
| `swarm.plannerModel` | (varies) | Model used for plan generation |
|
|
1686
|
+
| `swarm.synthesizerModel` | (varies) | Model used for result synthesis |
|
|
1668
1687
|
|
|
1669
1688
|
---
|
|
1670
1689
|
|
|
@@ -1767,24 +1786,24 @@ sequenceDiagram
|
|
|
1767
1786
|
|
|
1768
1787
|
Events emitted during a session lifecycle:
|
|
1769
1788
|
|
|
1770
|
-
| Kind
|
|
1771
|
-
|
|
1772
|
-
| `request_received`
|
|
1773
|
-
| `request_queued`
|
|
1774
|
-
| `request_dequeued`
|
|
1775
|
-
| `llm_call_started`
|
|
1776
|
-
| `llm_call_finished`
|
|
1777
|
-
| `assistant_message`
|
|
1778
|
-
| `tool_started`
|
|
1779
|
-
| `tool_permission_requested` | ToolTraceListener
|
|
1780
|
-
| `tool_permission_decided`
|
|
1781
|
-
| `tool_finished`
|
|
1782
|
-
| `tool_failed`
|
|
1783
|
-
| `secret_detected`
|
|
1784
|
-
| `generation_handoff`
|
|
1785
|
-
| `message_complete`
|
|
1786
|
-
| `generation_cancelled`
|
|
1787
|
-
| `request_error`
|
|
1789
|
+
| Kind | Emitted by | When |
|
|
1790
|
+
| --------------------------- | ------------------ | ----------------------------------------------------------------------------------------------- |
|
|
1791
|
+
| `request_received` | Handlers / Session | User message or surface action arrives |
|
|
1792
|
+
| `request_queued` | Handlers / Session | Message queued while session is busy |
|
|
1793
|
+
| `request_dequeued` | Session | Queued message begins processing |
|
|
1794
|
+
| `llm_call_started` | Session | LLM API call initiated |
|
|
1795
|
+
| `llm_call_finished` | Session | LLM API call completed (carries `inputTokens`, `outputTokens`, `latencyMs`) |
|
|
1796
|
+
| `assistant_message` | Session | Assistant response assembled (carries `toolUseCount`) |
|
|
1797
|
+
| `tool_started` | ToolTraceListener | Tool execution begins |
|
|
1798
|
+
| `tool_permission_requested` | ToolTraceListener | Permission check needed (carries `riskLevel`) |
|
|
1799
|
+
| `tool_permission_decided` | ToolTraceListener | Permission granted or denied (carries `decision`) |
|
|
1800
|
+
| `tool_finished` | ToolTraceListener | Tool execution completed (carries `durationMs`) |
|
|
1801
|
+
| `tool_failed` | ToolTraceListener | Tool execution failed (carries `durationMs`) |
|
|
1802
|
+
| `secret_detected` | ToolTraceListener | Secret found in tool output |
|
|
1803
|
+
| `generation_handoff` | Session | Yielding to next queued message |
|
|
1804
|
+
| `message_complete` | Session | Full request processing finished |
|
|
1805
|
+
| `generation_cancelled` | Session | User cancelled the generation |
|
|
1806
|
+
| `request_error` | Handlers / Session | Unrecoverable error during processing (includes queue-full rejection and persist-failure paths) |
|
|
1788
1807
|
|
|
1789
1808
|
### Architecture
|
|
1790
1809
|
|
|
@@ -1796,7 +1815,6 @@ Events emitted during a session lifecycle:
|
|
|
1796
1815
|
|
|
1797
1816
|
---
|
|
1798
1817
|
|
|
1799
|
-
|
|
1800
1818
|
---
|
|
1801
1819
|
|
|
1802
1820
|
## Assistant Events — SSE Transport Layer
|
|
@@ -1843,13 +1861,13 @@ graph TB
|
|
|
1843
1861
|
|
|
1844
1862
|
Every event published through the hub is wrapped in an `AssistantEvent` (defined in `runtime/assistant-event.ts`):
|
|
1845
1863
|
|
|
1846
|
-
| Field
|
|
1847
|
-
|
|
1848
|
-
| `id`
|
|
1849
|
-
| `assistantId` | `string`
|
|
1850
|
-
| `sessionId`
|
|
1851
|
-
| `emittedAt`
|
|
1852
|
-
| `message`
|
|
1864
|
+
| Field | Type | Description |
|
|
1865
|
+
| ------------- | ------------------- | ----------------------------------------------------- |
|
|
1866
|
+
| `id` | `string` (UUID) | Globally unique event identifier |
|
|
1867
|
+
| `assistantId` | `string` | Logical assistant identifier (`"self"` for HTTP runs) |
|
|
1868
|
+
| `sessionId` | `string?` | Resolved conversation ID when available |
|
|
1869
|
+
| `emittedAt` | `string` (ISO-8601) | Server-side timestamp |
|
|
1870
|
+
| `message` | `ServerMessage` | Unchanged IPC outbound message — no schema fork |
|
|
1853
1871
|
|
|
1854
1872
|
### SSE Frame Format
|
|
1855
1873
|
|
|
@@ -1869,22 +1887,22 @@ Keep-alive heartbeats (every 30 s by default):
|
|
|
1869
1887
|
|
|
1870
1888
|
### Subscription Lifecycle
|
|
1871
1889
|
|
|
1872
|
-
| Event
|
|
1873
|
-
|
|
1874
|
-
| `GET /v1/events` received
|
|
1875
|
-
| Client disconnects / aborts
|
|
1876
|
-
| Client cancels reader
|
|
1877
|
-
| New connection pushes over cap (100)
|
|
1878
|
-
| Client buffer full (16 queued frames) | `desiredSize <= 0` guard sheds the subscriber immediately
|
|
1890
|
+
| Event | Action |
|
|
1891
|
+
| ------------------------------------- | -------------------------------------------------------------------------- |
|
|
1892
|
+
| `GET /v1/events` received | Hub subscribes eagerly before `ReadableStream` is created |
|
|
1893
|
+
| Client disconnects / aborts | `req.signal` abort listener disposes subscription and closes stream |
|
|
1894
|
+
| Client cancels reader | `ReadableStream.cancel()` disposes subscription and closes stream |
|
|
1895
|
+
| New connection pushes over cap (100) | Oldest subscriber evicted (FIFO); its `onEvict` callback closes its stream |
|
|
1896
|
+
| Client buffer full (16 queued frames) | `desiredSize <= 0` guard sheds the subscriber immediately |
|
|
1879
1897
|
|
|
1880
1898
|
### Key Source Files
|
|
1881
1899
|
|
|
1882
|
-
| File
|
|
1883
|
-
|
|
1884
|
-
| `assistant/src/runtime/assistant-event.ts`
|
|
1885
|
-
| `assistant/src/runtime/assistant-event-hub.ts`
|
|
1886
|
-
| `assistant/src/runtime/routes/events-routes.ts` | `handleSubscribeAssistantEvents()` — SSE route handler
|
|
1887
|
-
| `assistant/src/daemon/server.ts`
|
|
1900
|
+
| File | Role |
|
|
1901
|
+
| ----------------------------------------------- | ----------------------------------------------------------------------------------- |
|
|
1902
|
+
| `assistant/src/runtime/assistant-event.ts` | `AssistantEvent` type, `buildAssistantEvent()` factory, SSE framing helpers |
|
|
1903
|
+
| `assistant/src/runtime/assistant-event-hub.ts` | `AssistantEventHub` class and process-level singleton |
|
|
1904
|
+
| `assistant/src/runtime/routes/events-routes.ts` | `handleSubscribeAssistantEvents()` — SSE route handler |
|
|
1905
|
+
| `assistant/src/daemon/server.ts` | IPC send/broadcast paths that publish to the hub (`send` → `publishAssistantEvent`) |
|
|
1888
1906
|
|
|
1889
1907
|
---
|
|
1890
1908
|
|
|
@@ -1985,24 +2003,24 @@ Connected channels are resolved at signal emission time: vellum is always includ
|
|
|
1985
2003
|
|
|
1986
2004
|
**Key modules:**
|
|
1987
2005
|
|
|
1988
|
-
| Module
|
|
1989
|
-
|
|
1990
|
-
| `assistant/src/channels/config.ts`
|
|
1991
|
-
| `assistant/src/notifications/emit-signal.ts`
|
|
1992
|
-
| `assistant/src/notifications/decision-engine.ts`
|
|
1993
|
-
| `assistant/src/notifications/deterministic-checks.ts`
|
|
1994
|
-
| `assistant/src/notifications/broadcaster.ts`
|
|
1995
|
-
| `assistant/src/notifications/conversation-pairing.ts`
|
|
1996
|
-
| `assistant/src/notifications/thread-candidates.ts`
|
|
1997
|
-
| `assistant/src/notifications/adapters/macos.ts`
|
|
1998
|
-
| `assistant/src/notifications/adapters/telegram.ts`
|
|
1999
|
-
| `assistant/src/notifications/adapters/sms.ts`
|
|
2000
|
-
| `assistant/src/notifications/destination-resolver.ts`
|
|
2001
|
-
| `assistant/src/notifications/copy-composer.ts`
|
|
2002
|
-
| `assistant/src/notifications/preference-extractor.ts`
|
|
2003
|
-
| `assistant/src/notifications/preferences-store.ts`
|
|
2004
|
-
| `assistant/src/config/bundled-skills/messaging/tools/send-notification.ts` | Explicit producer tool for user-requested notifications; emits signals into the same routing pipeline
|
|
2005
|
-
| `assistant/src/calls/guardian-dispatch.ts`
|
|
2006
|
+
| Module | Purpose |
|
|
2007
|
+
| -------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
|
|
2008
|
+
| `assistant/src/channels/config.ts` | Channel policy registry — single source of truth for per-channel notification behavior |
|
|
2009
|
+
| `assistant/src/notifications/emit-signal.ts` | Single entry point for all producers; orchestrates the full pipeline |
|
|
2010
|
+
| `assistant/src/notifications/decision-engine.ts` | LLM-based routing decisions with deterministic fallback |
|
|
2011
|
+
| `assistant/src/notifications/deterministic-checks.ts` | Hard invariant checks (dedupe, source-active suppression, channel availability) |
|
|
2012
|
+
| `assistant/src/notifications/broadcaster.ts` | Dispatches decisions to channel adapters; emits `notification_thread_created` IPC (creation-only) |
|
|
2013
|
+
| `assistant/src/notifications/conversation-pairing.ts` | Materializes conversation + message per delivery; executes thread reuse decisions |
|
|
2014
|
+
| `assistant/src/notifications/thread-candidates.ts` | Builds per-channel candidate set of recent conversations for the decision engine |
|
|
2015
|
+
| `assistant/src/notifications/adapters/macos.ts` | Vellum adapter — broadcasts `notification_intent` via IPC with deep-link metadata |
|
|
2016
|
+
| `assistant/src/notifications/adapters/telegram.ts` | Telegram adapter — POSTs to gateway `/deliver/telegram` |
|
|
2017
|
+
| `assistant/src/notifications/adapters/sms.ts` | SMS adapter — POSTs to gateway `/deliver/sms` via Twilio Messages API |
|
|
2018
|
+
| `assistant/src/notifications/destination-resolver.ts` | Resolves per-channel endpoints (vellum IPC, Telegram chat ID from guardian binding) |
|
|
2019
|
+
| `assistant/src/notifications/copy-composer.ts` | Template-based fallback copy when LLM copy is unavailable |
|
|
2020
|
+
| `assistant/src/notifications/preference-extractor.ts` | Detects preference statements in conversation messages |
|
|
2021
|
+
| `assistant/src/notifications/preferences-store.ts` | CRUD for user notification preferences |
|
|
2022
|
+
| `assistant/src/config/bundled-skills/messaging/tools/send-notification.ts` | Explicit producer tool for user-requested notifications; emits signals into the same routing pipeline |
|
|
2023
|
+
| `assistant/src/calls/guardian-dispatch.ts` | Guardian question dispatch that reuses canonical notification pairing and records guardian delivery bookkeeping from pipeline results |
|
|
2006
2024
|
|
|
2007
2025
|
**Audit trail (SQLite):** `notification_events` → `notification_decisions` (with `threadActions` in validation results) → `notification_deliveries` (with `conversation_id`, `message_id`, `conversation_strategy`, `thread_action`, `thread_target_conversation_id`, `thread_decision_fallback_used`)
|
|
2008
2026
|
|
|
@@ -2012,50 +2030,48 @@ Connected channels are resolved at signal emission time: vellum is always includ
|
|
|
2012
2030
|
|
|
2013
2031
|
## Storage Summary
|
|
2014
2032
|
|
|
2015
|
-
| What
|
|
2016
|
-
|
|
2017
|
-
| API key
|
|
2018
|
-
| Credential secrets
|
|
2019
|
-
| Credential metadata
|
|
2020
|
-
| Integration OAuth tokens
|
|
2021
|
-
| User preferences
|
|
2022
|
-
| Session logs
|
|
2023
|
-
| Conversations & messages
|
|
2024
|
-
| Memory segments & FTS
|
|
2025
|
-
| Extracted facts
|
|
2026
|
-
| Conflict lifecycle rows
|
|
2027
|
-
| Entity graph (entities/relations/item links) | `~/.vellum/workspace/data/db/assistant.db`
|
|
2028
|
-
| Embeddings
|
|
2029
|
-
| Async job queue
|
|
2030
|
-
| Attachments
|
|
2031
|
-
| Sandbox filesystem
|
|
2032
|
-
| Tool permission rules
|
|
2033
|
-
| Web users & assistants
|
|
2034
|
-
| Trace events
|
|
2035
|
-
| Media embed settings
|
|
2036
|
-
| Media embed MIME cache
|
|
2037
|
-
| IPC blob payloads
|
|
2038
|
-
| Tasks & task runs
|
|
2039
|
-
| Work items (Task Queue)
|
|
2040
|
-
| Recurrence schedules & runs
|
|
2041
|
-
| Watchers & events
|
|
2042
|
-
| Proxy CA cert + key
|
|
2043
|
-
| Proxy leaf certs
|
|
2044
|
-
| Proxy sessions
|
|
2045
|
-
| Call sessions, events, pending questions
|
|
2046
|
-
| Active call controllers
|
|
2047
|
-
| Guardian bindings
|
|
2048
|
-
| Guardian verification challenges
|
|
2049
|
-
| Guardian approval requests
|
|
2050
|
-
| Ingress invites
|
|
2051
|
-
| Ingress members
|
|
2052
|
-
| Notification events
|
|
2053
|
-
| Notification decisions
|
|
2054
|
-
| Notification deliveries
|
|
2055
|
-
| Notification preferences
|
|
2056
|
-
| IPC transport
|
|
2057
|
-
|
|
2058
|
-
|
|
2033
|
+
| What | Where | Format | ORM/Driver | Retention |
|
|
2034
|
+
| -------------------------------------------- | ----------------------------------------------------------------- | ----------------------------------- | ---------------------------------- | ---------------------------------------------------------- |
|
|
2035
|
+
| API key | macOS Keychain | Encrypted binary | `/usr/bin/security` CLI | Permanent |
|
|
2036
|
+
| Credential secrets | macOS Keychain (or encrypted file fallback) | Encrypted binary | `secure-keys.ts` wrapper | Permanent (until deleted via tool) |
|
|
2037
|
+
| Credential metadata | `~/.vellum/workspace/data/credentials/metadata.json` | JSON | Atomic file write | Permanent (until deleted via tool) |
|
|
2038
|
+
| Integration OAuth tokens | macOS Keychain (or encrypted file fallback, via `secure-keys.ts`) | Encrypted binary | `TokenManager` auto-refresh | Until disconnected or revoked |
|
|
2039
|
+
| User preferences | UserDefaults | plist | Foundation | Permanent |
|
|
2040
|
+
| Session logs | `~/Library/.../logs/session-*.json` | JSON per session | Swift Codable | Unbounded |
|
|
2041
|
+
| Conversations & messages | `~/.vellum/workspace/data/db/assistant.db` | SQLite + WAL | Drizzle ORM (Bun) | Permanent |
|
|
2042
|
+
| Memory segments & FTS | `~/.vellum/workspace/data/db/assistant.db` | SQLite FTS5 | Drizzle ORM | Permanent |
|
|
2043
|
+
| Extracted facts | `~/.vellum/workspace/data/db/assistant.db` | SQLite | Drizzle ORM | Permanent, deduped |
|
|
2044
|
+
| Conflict lifecycle rows | `~/.vellum/workspace/data/db/assistant.db` | SQLite | Drizzle ORM | Pending until clarified, then retained as resolved history |
|
|
2045
|
+
| Entity graph (entities/relations/item links) | `~/.vellum/workspace/data/db/assistant.db` | SQLite | Drizzle ORM | Permanent, deduped by unique relation edge |
|
|
2046
|
+
| Embeddings | `~/.vellum/workspace/data/db/assistant.db` | JSON float arrays | Drizzle ORM | Permanent |
|
|
2047
|
+
| Async job queue | `~/.vellum/workspace/data/db/assistant.db` | SQLite | Drizzle ORM | Completed jobs persist |
|
|
2048
|
+
| Attachments | `~/.vellum/workspace/data/db/assistant.db` | Base64 in SQLite | Drizzle ORM | Permanent |
|
|
2049
|
+
| Sandbox filesystem | `~/.vellum/workspace` | Real filesystem tree | Node FS APIs | Persistent across sessions |
|
|
2050
|
+
| Tool permission rules | `~/.vellum/protected/trust.json` | JSON | File I/O | Permanent |
|
|
2051
|
+
| Web users & assistants | PostgreSQL | Relational | Drizzle ORM (pg) | Permanent |
|
|
2052
|
+
| Trace events | In-memory (TraceStore) | Structured events | Swift ObservableObject | Max 5,000 per session, ephemeral |
|
|
2053
|
+
| Media embed settings | `~/.vellum/workspace/config.json` (`ui.mediaEmbeds`) | JSON | `WorkspaceConfigIO` (atomic merge) | Permanent |
|
|
2054
|
+
| Media embed MIME cache | In-memory (`ImageMIMEProbe`) | `NSCache` (500 entries) | HTTP HEAD | Ephemeral; cleared on app restart |
|
|
2055
|
+
| IPC blob payloads | `~/.vellum/workspace/data/ipc-blobs/` | Binary files (UUID names) | File I/O (atomic write) | Ephemeral; consumed on hydration, stale sweep every 5min |
|
|
2056
|
+
| Tasks & task runs | `~/.vellum/workspace/data/db/assistant.db` | SQLite | Drizzle ORM | Permanent |
|
|
2057
|
+
| Work items (Task Queue) | `~/.vellum/workspace/data/db/assistant.db` | SQLite | Drizzle ORM | Permanent; archived items retained |
|
|
2058
|
+
| Recurrence schedules & runs | `~/.vellum/workspace/data/db/assistant.db` | SQLite | Drizzle ORM | Permanent; supports cron and RRULE syntax |
|
|
2059
|
+
| Watchers & events | `~/.vellum/workspace/data/db/assistant.db` | SQLite | Drizzle ORM | Permanent, cascade on watcher delete |
|
|
2060
|
+
| Proxy CA cert + key | `{dataDir}/proxy-ca/` | PEM files (ca.pem, ca-key.pem) | openssl CLI | Permanent (10-year validity) |
|
|
2061
|
+
| Proxy leaf certs | `{dataDir}/proxy-ca/issued/` | PEM files per hostname | openssl CLI, cached | 1-year validity, re-issued on CA change |
|
|
2062
|
+
| Proxy sessions | In-memory (SessionManager) | Map<ProxySessionId, ManagedSession> | Manual lifecycle | Ephemeral; 5min idle timeout, cleared on shutdown |
|
|
2063
|
+
| Call sessions, events, pending questions | `~/.vellum/workspace/data/db/assistant.db` | SQLite | Drizzle ORM | Permanent, cascade on session delete |
|
|
2064
|
+
| Active call controllers | In-memory (CallState) | Map<callSessionId, CallController> | Manual lifecycle | Ephemeral; cleared on call end or destroy |
|
|
2065
|
+
| Guardian bindings | `~/.vellum/workspace/data/db/assistant.db` | SQLite | Drizzle ORM | Permanent; revoked bindings retained |
|
|
2066
|
+
| Guardian verification challenges | `~/.vellum/workspace/data/db/assistant.db` | SQLite | Drizzle ORM | Permanent; consumed/expired challenges retained |
|
|
2067
|
+
| Guardian approval requests | `~/.vellum/workspace/data/db/assistant.db` | SQLite | Drizzle ORM | Permanent; decision outcome retained |
|
|
2068
|
+
| Ingress invites | `~/.vellum/workspace/data/db/assistant.db` | SQLite | Drizzle ORM | Permanent; token hash stored, raw token never persisted |
|
|
2069
|
+
| Ingress members | `~/.vellum/workspace/data/db/assistant.db` | SQLite | Drizzle ORM | Permanent; revoked/blocked members retained |
|
|
2070
|
+
| Notification events | `~/.vellum/workspace/data/db/assistant.db` | SQLite | Drizzle ORM | Permanent; deduplicated by dedupeKey |
|
|
2071
|
+
| Notification decisions | `~/.vellum/workspace/data/db/assistant.db` | SQLite | Drizzle ORM | Permanent; FK to notification_events |
|
|
2072
|
+
| Notification deliveries | `~/.vellum/workspace/data/db/assistant.db` | SQLite | Drizzle ORM | Permanent; FK to notification_decisions |
|
|
2073
|
+
| Notification preferences | `~/.vellum/workspace/data/db/assistant.db` | SQLite | Drizzle ORM | Permanent; per-assistant conversational preferences |
|
|
2074
|
+
| IPC transport | `~/.vellum/vellum.sock` | Unix domain socket | NWConnection (Swift) / Bun net | Ephemeral |
|
|
2059
2075
|
|
|
2060
2076
|
### Sensitive Tool Output Placeholder Substitution
|
|
2061
2077
|
|
|
@@ -2067,7 +2083,7 @@ Some tool outputs contain values that must reach the user's final reply but shou
|
|
|
2067
2083
|
|
|
2068
2084
|
3. **Post-generation substitution** (`src/agent/loop.ts`): Before emitting streamed `text_delta` events and before building the final `assistantMessage`, all placeholders are deterministically replaced with their real values. The substitution is chunk-safe for streaming (buffering partial placeholder prefixes across deltas).
|
|
2069
2085
|
|
|
2070
|
-
Key files: `src/tools/sensitive-output-placeholders.ts`, `src/tools/executor.ts` (extraction hook), `src/agent/loop.ts` (substitution), `src/config/
|
|
2086
|
+
Key files: `src/tools/sensitive-output-placeholders.ts`, `src/tools/executor.ts` (extraction hook), `src/agent/loop.ts` (substitution), `src/config/bundled-skills/trusted-contacts/SKILL.md` (invite flow adoption).
|
|
2071
2087
|
|
|
2072
2088
|
### Notifications
|
|
2073
2089
|
|
|
@@ -2081,10 +2097,10 @@ The daemon uses a single fixed internal scope constant — `DAEMON_INTERNAL_ASSI
|
|
|
2081
2097
|
|
|
2082
2098
|
**Key files:**
|
|
2083
2099
|
|
|
2084
|
-
| File
|
|
2085
|
-
|
|
2086
|
-
| `src/runtime/assistant-scope.ts`
|
|
2087
|
-
| `src/__tests__/assistant-id-boundary-guard.test.ts` | Guard tests enforcing the identity boundary
|
|
2100
|
+
| File | Purpose |
|
|
2101
|
+
| --------------------------------------------------- | ----------------------------------------------- |
|
|
2102
|
+
| `src/runtime/assistant-scope.ts` | Exports `DAEMON_INTERNAL_ASSISTANT_ID` constant |
|
|
2103
|
+
| `src/__tests__/assistant-id-boundary-guard.test.ts` | Guard tests enforcing the identity boundary |
|
|
2088
2104
|
|
|
2089
2105
|
### Canonical Trust-Context Model
|
|
2090
2106
|
|
|
@@ -2102,10 +2118,10 @@ The guardian trust system uses a three-valued `TrustClass` — `'guardian'`, `'t
|
|
|
2102
2118
|
|
|
2103
2119
|
**Key files:**
|
|
2104
2120
|
|
|
2105
|
-
| File
|
|
2106
|
-
|
|
2107
|
-
| `src/daemon/session-runtime-assembly.ts`
|
|
2108
|
-
| `src/tools/types.ts`
|
|
2109
|
-
| `src/runtime/channel-retry-sweep.ts`
|
|
2110
|
-
| `src/memory/guardian-bindings.ts`
|
|
2111
|
-
| `src/__tests__/trust-context-guards.test.ts` | Guard tests enforcing trust-context type invariants
|
|
2121
|
+
| File | Purpose |
|
|
2122
|
+
| -------------------------------------------- | ------------------------------------------------------ |
|
|
2123
|
+
| `src/daemon/session-runtime-assembly.ts` | `GuardianRuntimeContext` type definition |
|
|
2124
|
+
| `src/tools/types.ts` | `ToolContext.guardianTrustClass` (required trust gate) |
|
|
2125
|
+
| `src/runtime/channel-retry-sweep.ts` | Strict `trustClass` parser for retry sweep |
|
|
2126
|
+
| `src/memory/guardian-bindings.ts` | `GuardianBinding` with required `guardianPrincipalId` |
|
|
2127
|
+
| `src/__tests__/trust-context-guards.test.ts` | Guard tests enforcing trust-context type invariants |
|