@vellumai/assistant 0.3.19 → 0.3.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (199) hide show
  1. package/ARCHITECTURE.md +151 -15
  2. package/Dockerfile +1 -0
  3. package/README.md +40 -4
  4. package/bun.lock +139 -2
  5. package/docs/architecture/integrations.md +7 -11
  6. package/package.json +2 -1
  7. package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +54 -0
  8. package/src/__tests__/approval-primitive.test.ts +540 -0
  9. package/src/__tests__/assistant-feature-flag-guard.test.ts +206 -0
  10. package/src/__tests__/assistant-feature-flag-guardrails.test.ts +198 -0
  11. package/src/__tests__/assistant-feature-flags-integration.test.ts +272 -0
  12. package/src/__tests__/call-controller.test.ts +439 -108
  13. package/src/__tests__/channel-invite-transport.test.ts +264 -0
  14. package/src/__tests__/cli.test.ts +42 -1
  15. package/src/__tests__/config-schema.test.ts +11 -127
  16. package/src/__tests__/config-watcher.test.ts +0 -8
  17. package/src/__tests__/daemon-lifecycle.test.ts +1 -0
  18. package/src/__tests__/daemon-server-session-init.test.ts +8 -2
  19. package/src/__tests__/diff.test.ts +22 -0
  20. package/src/__tests__/guardian-action-copy-generator.test.ts +5 -0
  21. package/src/__tests__/guardian-action-grant-mint-consume.test.ts +300 -32
  22. package/src/__tests__/guardian-action-late-reply.test.ts +546 -1
  23. package/src/__tests__/guardian-actions-endpoint.test.ts +774 -0
  24. package/src/__tests__/guardian-control-plane-policy.test.ts +36 -3
  25. package/src/__tests__/guardian-dispatch.test.ts +124 -0
  26. package/src/__tests__/guardian-grant-minting.test.ts +6 -17
  27. package/src/__tests__/inbound-invite-redemption.test.ts +367 -0
  28. package/src/__tests__/invite-redemption-service.test.ts +306 -0
  29. package/src/__tests__/ipc-snapshot.test.ts +57 -0
  30. package/src/__tests__/notification-decision-fallback.test.ts +88 -0
  31. package/src/__tests__/sandbox-diagnostics.test.ts +6 -249
  32. package/src/__tests__/sandbox-host-parity.test.ts +6 -13
  33. package/src/__tests__/scoped-approval-grants.test.ts +6 -6
  34. package/src/__tests__/scoped-grant-security-matrix.test.ts +5 -4
  35. package/src/__tests__/script-proxy-session-manager.test.ts +1 -19
  36. package/src/__tests__/session-load-history-repair.test.ts +169 -2
  37. package/src/__tests__/session-runtime-assembly.test.ts +33 -5
  38. package/src/__tests__/skill-feature-flags-integration.test.ts +171 -0
  39. package/src/__tests__/skill-feature-flags.test.ts +188 -0
  40. package/src/__tests__/skill-load-feature-flag.test.ts +141 -0
  41. package/src/__tests__/skill-mirror-parity.test.ts +1 -0
  42. package/src/__tests__/skill-projection-feature-flag.test.ts +363 -0
  43. package/src/__tests__/system-prompt.test.ts +1 -1
  44. package/src/__tests__/terminal-sandbox.test.ts +142 -9
  45. package/src/__tests__/terminal-tools.test.ts +2 -93
  46. package/src/__tests__/thread-seed-composer.test.ts +18 -0
  47. package/src/__tests__/tool-approval-handler.test.ts +350 -0
  48. package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +8 -10
  49. package/src/__tests__/voice-scoped-grant-consumer.test.ts +46 -84
  50. package/src/agent/loop.ts +36 -1
  51. package/src/approvals/approval-primitive.ts +381 -0
  52. package/src/approvals/guardian-decision-primitive.ts +191 -0
  53. package/src/calls/call-controller.ts +252 -209
  54. package/src/calls/call-domain.ts +44 -6
  55. package/src/calls/guardian-dispatch.ts +48 -0
  56. package/src/calls/types.ts +1 -1
  57. package/src/calls/voice-session-bridge.ts +46 -30
  58. package/src/cli/core-commands.ts +0 -4
  59. package/src/cli/mcp.ts +58 -0
  60. package/src/cli.ts +76 -34
  61. package/src/config/__tests__/feature-flag-registry-guard.test.ts +179 -0
  62. package/src/config/assistant-feature-flags.ts +162 -0
  63. package/src/config/bundled-skills/api-mapping/icon.svg +18 -0
  64. package/src/config/bundled-skills/messaging/TOOLS.json +30 -0
  65. package/src/config/bundled-skills/messaging/tools/slack-delete-message.ts +24 -0
  66. package/src/config/bundled-skills/notifications/SKILL.md +1 -1
  67. package/src/config/bundled-skills/reminder/SKILL.md +49 -2
  68. package/src/config/bundled-skills/time-based-actions/SKILL.md +49 -2
  69. package/src/config/bundled-skills/voice-setup/SKILL.md +122 -0
  70. package/src/config/core-schema.ts +1 -1
  71. package/src/config/env-registry.ts +10 -0
  72. package/src/config/feature-flag-registry.json +61 -0
  73. package/src/config/loader.ts +22 -1
  74. package/src/config/mcp-schema.ts +46 -0
  75. package/src/config/sandbox-schema.ts +0 -39
  76. package/src/config/schema.ts +18 -2
  77. package/src/config/skill-state.ts +34 -0
  78. package/src/config/skills-schema.ts +0 -1
  79. package/src/config/skills.ts +9 -0
  80. package/src/config/system-prompt.ts +110 -46
  81. package/src/config/templates/SOUL.md +1 -1
  82. package/src/config/types.ts +19 -1
  83. package/src/config/vellum-skills/catalog.json +1 -1
  84. package/src/config/vellum-skills/guardian-verify-setup/SKILL.md +1 -0
  85. package/src/config/vellum-skills/sms-setup/SKILL.md +1 -1
  86. package/src/config/vellum-skills/telegram-setup/SKILL.md +6 -5
  87. package/src/config/vellum-skills/trusted-contacts/SKILL.md +105 -3
  88. package/src/config/vellum-skills/twilio-setup/SKILL.md +1 -1
  89. package/src/daemon/config-watcher.ts +0 -1
  90. package/src/daemon/daemon-control.ts +1 -1
  91. package/src/daemon/guardian-invite-intent.ts +124 -0
  92. package/src/daemon/handlers/avatar.ts +68 -0
  93. package/src/daemon/handlers/browser.ts +2 -2
  94. package/src/daemon/handlers/guardian-actions.ts +120 -0
  95. package/src/daemon/handlers/index.ts +4 -0
  96. package/src/daemon/handlers/sessions.ts +19 -0
  97. package/src/daemon/handlers/shared.ts +3 -1
  98. package/src/daemon/install-cli-launchers.ts +58 -13
  99. package/src/daemon/ipc-contract/guardian-actions.ts +53 -0
  100. package/src/daemon/ipc-contract/sessions.ts +8 -2
  101. package/src/daemon/ipc-contract/settings.ts +25 -2
  102. package/src/daemon/ipc-contract-inventory.json +10 -0
  103. package/src/daemon/ipc-contract.ts +4 -0
  104. package/src/daemon/lifecycle.ts +14 -2
  105. package/src/daemon/main.ts +1 -0
  106. package/src/daemon/providers-setup.ts +26 -1
  107. package/src/daemon/server.ts +1 -0
  108. package/src/daemon/session-lifecycle.ts +52 -7
  109. package/src/daemon/session-memory.ts +45 -0
  110. package/src/daemon/session-process.ts +258 -432
  111. package/src/daemon/session-runtime-assembly.ts +12 -0
  112. package/src/daemon/session-skill-tools.ts +14 -1
  113. package/src/daemon/session-tool-setup.ts +5 -0
  114. package/src/daemon/session.ts +11 -0
  115. package/src/daemon/shutdown-handlers.ts +11 -0
  116. package/src/daemon/tool-side-effects.ts +35 -9
  117. package/src/index.ts +2 -2
  118. package/src/mcp/client.ts +152 -0
  119. package/src/mcp/manager.ts +139 -0
  120. package/src/memory/conversation-display-order-migration.ts +44 -0
  121. package/src/memory/conversation-queries.ts +2 -0
  122. package/src/memory/conversation-store.ts +91 -0
  123. package/src/memory/db-init.ts +5 -1
  124. package/src/memory/embedding-local.ts +13 -8
  125. package/src/memory/guardian-action-store.ts +125 -2
  126. package/src/memory/ingress-invite-store.ts +95 -1
  127. package/src/memory/migrations/035-guardian-action-supersession.ts +23 -0
  128. package/src/memory/migrations/index.ts +2 -1
  129. package/src/memory/schema.ts +5 -1
  130. package/src/memory/scoped-approval-grants.ts +14 -5
  131. package/src/messaging/providers/slack/client.ts +12 -0
  132. package/src/messaging/providers/slack/types.ts +5 -0
  133. package/src/notifications/decision-engine.ts +49 -12
  134. package/src/notifications/emit-signal.ts +7 -0
  135. package/src/notifications/signal.ts +7 -0
  136. package/src/notifications/thread-seed-composer.ts +2 -1
  137. package/src/runtime/channel-approval-types.ts +16 -6
  138. package/src/runtime/channel-approvals.ts +19 -15
  139. package/src/runtime/channel-invite-transport.ts +85 -0
  140. package/src/runtime/channel-invite-transports/telegram.ts +105 -0
  141. package/src/runtime/guardian-action-grant-minter.ts +92 -35
  142. package/src/runtime/guardian-action-message-composer.ts +30 -0
  143. package/src/runtime/guardian-decision-types.ts +91 -0
  144. package/src/runtime/http-server.ts +23 -1
  145. package/src/runtime/ingress-service.ts +22 -0
  146. package/src/runtime/invite-redemption-service.ts +181 -0
  147. package/src/runtime/invite-redemption-templates.ts +39 -0
  148. package/src/runtime/routes/call-routes.ts +2 -1
  149. package/src/runtime/routes/guardian-action-routes.ts +206 -0
  150. package/src/runtime/routes/guardian-approval-interception.ts +66 -190
  151. package/src/runtime/routes/identity-routes.ts +73 -0
  152. package/src/runtime/routes/inbound-message-handler.ts +486 -394
  153. package/src/runtime/routes/pairing-routes.ts +4 -0
  154. package/src/security/encrypted-store.ts +31 -17
  155. package/src/security/keychain.ts +176 -2
  156. package/src/security/secure-keys.ts +97 -0
  157. package/src/security/tool-approval-digest.ts +1 -1
  158. package/src/tools/browser/browser-execution.ts +2 -2
  159. package/src/tools/browser/browser-manager.ts +46 -32
  160. package/src/tools/browser/browser-screencast.ts +2 -2
  161. package/src/tools/calls/call-start.ts +1 -1
  162. package/src/tools/executor.ts +22 -17
  163. package/src/tools/mcp/mcp-tool-factory.ts +100 -0
  164. package/src/tools/network/script-proxy/session-manager.ts +1 -5
  165. package/src/tools/registry.ts +64 -1
  166. package/src/tools/skills/load.ts +22 -8
  167. package/src/tools/system/avatar-generator.ts +119 -0
  168. package/src/tools/system/navigate-settings.ts +65 -0
  169. package/src/tools/system/open-system-settings.ts +75 -0
  170. package/src/tools/system/voice-config.ts +121 -32
  171. package/src/tools/terminal/backends/native.ts +40 -19
  172. package/src/tools/terminal/backends/types.ts +3 -3
  173. package/src/tools/terminal/parser.ts +1 -1
  174. package/src/tools/terminal/sandbox-diagnostics.ts +6 -87
  175. package/src/tools/terminal/sandbox.ts +1 -12
  176. package/src/tools/terminal/shell.ts +3 -31
  177. package/src/tools/tool-approval-handler.ts +141 -3
  178. package/src/tools/tool-manifest.ts +6 -0
  179. package/src/tools/types.ts +10 -2
  180. package/src/util/diff.ts +36 -13
  181. package/Dockerfile.sandbox +0 -5
  182. package/src/__tests__/doordash-client.test.ts +0 -187
  183. package/src/__tests__/doordash-session.test.ts +0 -154
  184. package/src/__tests__/signup-e2e.test.ts +0 -354
  185. package/src/__tests__/terminal-sandbox-docker.test.ts +0 -1065
  186. package/src/__tests__/terminal-sandbox.integration.test.ts +0 -180
  187. package/src/cli/doordash.ts +0 -1057
  188. package/src/config/bundled-skills/doordash/SKILL.md +0 -163
  189. package/src/config/templates/LOOKS.md +0 -25
  190. package/src/doordash/cart-queries.ts +0 -787
  191. package/src/doordash/client.ts +0 -1016
  192. package/src/doordash/order-queries.ts +0 -85
  193. package/src/doordash/queries.ts +0 -13
  194. package/src/doordash/query-extractor.ts +0 -94
  195. package/src/doordash/search-queries.ts +0 -203
  196. package/src/doordash/session.ts +0 -84
  197. package/src/doordash/store-queries.ts +0 -246
  198. package/src/doordash/types.ts +0 -367
  199. package/src/tools/terminal/backends/docker.ts +0 -379
package/ARCHITECTURE.md CHANGED
@@ -26,9 +26,45 @@ This document owns assistant-runtime architecture details. The repo-level archit
26
26
 
27
27
  Scoped approval grants allow a guardian's approval decision on one channel (e.g., Telegram) to authorize a tool execution on a different channel (e.g., voice). Two scope modes exist: `request_id` (bound to a specific pending request) and `tool_signature` (bound to `toolName` + canonical `inputDigest`). Grants are one-time-use, exact-match, fail-closed, and TTL-bound. Full architecture details (lifecycle flow, security invariants, key files) live in [`docs/architecture/security.md`](docs/architecture/security.md#channel-agnostic-scoped-approval-grants).
28
28
 
29
+ ### Guardian Decision Primitive (Dual-Mode Approval)
30
+
31
+ All guardian approval decisions — regardless of how they arrive — route through a single unified primitive in `src/approvals/guardian-decision-primitive.ts`. This centralizes decision logic that was previously duplicated across callback button handlers, the conversational approval engine, the legacy text parser, and the requester self-cancel path.
32
+
33
+ **Core API:**
34
+
35
+ | Function | Purpose |
36
+ |----------|---------|
37
+ | `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? }`. |
38
+ | `listGuardianDecisionPrompts({ conversationId })` | List pending prompts for a conversation, aggregating channel guardian approval requests and pending confirmation interactions into a uniform `GuardianDecisionPrompt` shape. |
39
+
40
+ **Security invariants enforced by the primitive:**
41
+
42
+ - Decision application is identity-bound to the expected guardian identity.
43
+ - Decisions are first-response-wins (CAS-like stale protection via `handleChannelDecision`).
44
+ - `approve_always` is downgraded to `approve_once` for guardian-on-behalf requests (guardians cannot permanently allowlist tools for requesters).
45
+ - Scoped grant minting only fires on explicit approve for requests with tool metadata.
46
+
47
+ **Dual-mode approval UX:** Guardians can respond to approval prompts via two modes, both routing through `applyGuardianDecision`:
48
+
49
+ 1. **Button UI (deterministic):** Desktop clients and channel adapters render structured `GuardianDecisionPrompt` objects as tappable buttons. Desktop clients use HTTP endpoints (`GET /v1/guardian-actions/pending`, `POST /v1/guardian-actions/decision`) or IPC (`guardian_actions_pending_request`, `guardian_action_decision`). Channel adapters (Telegram inline keyboards, WhatsApp) encode actions in callback data.
50
+ 2. **Conversational (NL parsing):** Text replies are classified by the conversational approval engine or parsed by the legacy text parser. The resulting `ApprovalDecisionResult` is passed to `applyGuardianDecision` identically.
51
+
52
+ **Shared type system:** `GuardianDecisionPrompt` and `GuardianDecisionAction` (in `src/runtime/guardian-decision-types.ts`) define the structured prompt model. `buildDecisionActions()` computes the action set respecting `persistentDecisionsAllowed` and `forGuardianOnBehalf` flags. `buildPlainTextFallback()` generates parser-compatible text instructions. Channel adapters map these to channel-specific formats via `toApprovalActionOptions()` in `channel-approval-types.ts`.
53
+
54
+ **Key source files:**
55
+
56
+ | File | Purpose |
57
+ |------|---------|
58
+ | `src/approvals/guardian-decision-primitive.ts` | Unified decision application: downgrade, approval info capture, `handleChannelDecision`, record update, grant minting |
59
+ | `src/runtime/guardian-decision-types.ts` | Shared types: `GuardianDecisionPrompt`, `GuardianDecisionAction`, `buildDecisionActions`, `buildPlainTextFallback`, `ApplyGuardianDecisionResult` |
60
+ | `src/runtime/routes/guardian-action-routes.ts` | HTTP route handlers for `GET /v1/guardian-actions/pending` and `POST /v1/guardian-actions/decision` |
61
+ | `src/daemon/handlers/guardian-actions.ts` | IPC handlers wrapping the same logic for desktop socket clients |
62
+ | `src/daemon/ipc-contract/guardian-actions.ts` | IPC message type definitions for guardian action requests/responses |
63
+ | `src/runtime/channel-approval-types.ts` | Channel-facing approval action types and `toApprovalActionOptions` bridge |
64
+
29
65
  ### Outbound Guardian Verification (HTTP Endpoints)
30
66
 
31
- Guardian verification can be initiated through the runtime HTTP API as an alternative to the legacy IPC-only flow. This enables chat-first verification where the assistant guides the user through guardian setup via normal conversation.
67
+ Guardian verification can be initiated through gateway HTTP endpoints (which forward to runtime handlers) as an alternative to the legacy IPC-only flow. This enables chat-first verification where the assistant guides the user through guardian setup via normal conversation.
32
68
 
33
69
  **HTTP Endpoints:**
34
70
 
@@ -38,11 +74,11 @@ Guardian verification can be initiated through the runtime HTTP API as an altern
38
74
  | `/v1/integrations/guardian/outbound/resend` | POST | Resend the verification code for an active session. Body: `{ channel, assistantId? }` |
39
75
  | `/v1/integrations/guardian/outbound/cancel` | POST | Cancel an active outbound verification session. Body: `{ channel, assistantId? }` |
40
76
 
41
- All endpoints are bearer-authenticated via the runtime HTTP token (`~/.vellum/http-token`).
77
+ 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.
42
78
 
43
79
  **Shared Business Logic:**
44
80
 
45
- The HTTP route handlers (`integration-routes.ts`) and the legacy IPC handlers (`config-channels.ts`) both delegate to the same action functions in `guardian-outbound-actions.ts`. This module contains transport-agnostic business logic for starting, resending, and cancelling outbound verification flows across SMS, Telegram, and voice channels. It returns `OutboundActionResult` objects that the transport layer (HTTP or IPC) maps to its respective response format.
81
+ The HTTP route handlers (`integration-routes.ts`) and the legacy IPC handlers (`config-channels.ts`) both delegate to the same action functions in `guardian-outbound-actions.ts`. This module contains transport-agnostic business logic for starting, resending, and cancelling outbound verification flows across SMS, Telegram, and voice channels. It returns `OutboundActionResult` objects that the transport layer (gateway-forwarded HTTP or IPC) maps to its respective response format.
46
82
 
47
83
  **Chat-First Orchestration Flow:**
48
84
 
@@ -280,6 +316,68 @@ External users who are not the guardian can gain access to the assistant through
280
316
  | `src/memory/channel-guardian-store.ts` | Approval request and verification challenge persistence |
281
317
  | `src/config/vellum-skills/trusted-contacts/SKILL.md` | Skill teaching the assistant to manage contacts via HTTP API |
282
318
 
319
+ ### Guardian-Initiated Invite Links
320
+
321
+ A complementary access-granting flow where the guardian proactively creates a shareable invite link rather than waiting for an unknown user to request access. Currently implemented for Telegram; the architecture supports future channel adapters.
322
+
323
+ **Three-layer architecture:**
324
+
325
+ ```
326
+ ┌─────────────────────────────────────────────────────────────┐
327
+ │ Conversational Orchestration (guardian-invite-intent.ts) │
328
+ │ Pattern-based intent detection → forces trusted-contacts │
329
+ │ skill load for create / list / revoke actions │
330
+ ├─────────────────────────────────────────────────────────────┤
331
+ │ Channel Transport Adapters (channel-invite-transport.ts) │
332
+ │ Registry of per-channel adapters: │
333
+ │ • buildShareableInvite(token) → { url, displayText } │
334
+ │ • extractInboundToken(payload) → token | undefined │
335
+ │ Registered: Telegram │ Deferred: SMS, Slack, Voice │
336
+ ├─────────────────────────────────────────────────────────────┤
337
+ │ Core Redemption Engine (invite-redemption-service.ts) │
338
+ │ Channel-agnostic token validation, expiry, use-count, │
339
+ │ channel-match enforcement, member creation/reactivation │
340
+ │ Returns: InviteRedemptionOutcome (discriminated union) │
341
+ │ Reply templates: invite-redemption-templates.ts │
342
+ └─────────────────────────────────────────────────────────────┘
343
+ ```
344
+
345
+ **Invite link flow (Telegram):**
346
+
347
+ 1. Guardian asks the assistant to create an invite via desktop chat.
348
+ 2. `guardian-invite-intent.ts` detects the intent and rewrites the message to force-load the `trusted-contacts` skill.
349
+ 3. The skill calls the ingress HTTP API to create an invite token, then calls the Telegram transport adapter to build a deep link: `https://t.me/<bot>?start=iv_<token>`.
350
+ 4. Guardian shares the link with the invitee out-of-band.
351
+ 5. Invitee clicks the link, opening Telegram which sends `/start iv_<token>` to the bot.
352
+ 6. The gateway forwards the message to `/channels/inbound`. The inbound handler calls `getTransport('telegram').extractInboundToken()` to parse the `iv_` token.
353
+ 7. The token is redeemed via `invite-redemption-service.ts`, which validates, creates an active member record, and returns a `redeemed` outcome.
354
+ 8. A deterministic welcome message is delivered to the invitee (bypasses the LLM pipeline).
355
+
356
+ **Token prefix convention:** The `iv_` prefix distinguishes invite tokens from `gv_` (guardian verification) tokens. Both use the same Telegram `/start` deep-link mechanism but are routed to different handlers.
357
+
358
+ **Inbound intercept points:** Invite token extraction runs early in the inbound handler, before ACL denial, so valid invites short-circuit the membership check. Two intercept branches handle: (a) non-members — the invite creates their first member record; (b) inactive members (revoked/pending) — the invite reactivates them.
359
+
360
+ **Deferred channel adapters:**
361
+
362
+ | Channel | Status | Prerequisites |
363
+ |---------|--------|--------------|
364
+ | Telegram | Shipped | Bot username resolved from credential metadata or `TELEGRAM_BOT_USERNAME` env |
365
+ | SMS | Deferred | Needs a deep-link strategy compatible with SMS (short URL or web redemption page) |
366
+ | Slack | Deferred | Needs DM-safe ingress — Socket Mode handles channel messages but DM-initiated invite flows need routing |
367
+ | Voice | Deferred | Needs DTMF or speech-based token capture integrated with the voice relay state machine |
368
+
369
+ **Key source files:**
370
+
371
+ | File | Purpose |
372
+ |------|---------|
373
+ | `src/runtime/invite-redemption-service.ts` | Core redemption engine — token validation, member creation, discriminated-union outcomes |
374
+ | `src/runtime/invite-redemption-templates.ts` | Deterministic reply templates for each redemption outcome |
375
+ | `src/runtime/channel-invite-transport.ts` | Transport adapter registry with `buildShareableInvite` / `extractInboundToken` interface |
376
+ | `src/runtime/channel-invite-transports/telegram.ts` | Telegram adapter — `t.me/<bot>?start=iv_<token>` deep links, `/start iv_<token>` extraction |
377
+ | `src/daemon/guardian-invite-intent.ts` | Intent detection — routes create/list/revoke requests into the trusted-contacts skill |
378
+ | `src/runtime/ingress-service.ts` | Shared business logic for invite/member operations (used by both HTTP routes and IPC) |
379
+ | `src/runtime/routes/inbound-message-handler.ts` | Invite token intercept in the inbound flow (non-member and inactive-member branches) |
380
+
283
381
  ### Update Bulletin System
284
382
 
285
383
  Release-driven update notification system that surfaces release notes to the assistant via the system prompt.
@@ -309,6 +407,52 @@ Release-driven update notification system that surfaces release notes to the ass
309
407
 
310
408
  ---
311
409
 
410
+ ### Assistant Feature Flags — Resolver and Enforcement Points
411
+
412
+ The assistant feature-flag resolver (`src/config/assistant-feature-flags.ts`) is the canonical module for determining whether an assistant feature flag is enabled. It loads default values from the unified registry at `meta/feature-flags/feature-flag-registry.json` (bundled copy at `src/config/feature-flag-registry.json`) and resolves the effective state for each declared assistant-scope flag. Assistant feature flags are declaration-driven assistant-scoped booleans that can gate any assistant behavior; skill availability is one consumer.
413
+
414
+ **Canonical key format:** `feature_flags.<flag_id>.enabled` (e.g., `feature_flags.hatch-new-assistant.enabled`).
415
+
416
+ **Resolution priority** (highest wins):
417
+ 1. `config.assistantFeatureFlagValues[key]` — canonical config section, written by the gateway's PATCH endpoint
418
+ 2. Defaults registry `defaultEnabled` — from the unified registry (`meta/feature-flags/feature-flag-registry.json`, filtered to `scope: "assistant"`)
419
+ 3. `true` — unknown/undeclared flags with no persisted override default to enabled
420
+
421
+ **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.
422
+
423
+ **Public API:**
424
+ - `isAssistantFeatureFlagEnabled(key, config)` — full resolver with the canonical key
425
+ - `isAssistantSkillEnabled(skillId, config)` — convenience wrapper that constructs `feature_flags.<skillId>.enabled` and delegates
426
+ - `isSkillFeatureEnabled(skillId, config)` — deprecated legacy wrapper in `config/skill-state.ts`
427
+
428
+ **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:
429
+
430
+ | Enforcement Point | Module | Effect |
431
+ |-------------------|--------|--------|
432
+ | **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. |
433
+ | **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. |
434
+ | **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)"`. |
435
+ | **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. |
436
+ | **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. |
437
+
438
+ All five enforcement points use `isAssistantSkillEnabled()` from `config/assistant-feature-flags.ts` for consistency.
439
+
440
+ **Migration path:** The legacy `skills.<id>.enabled` key format is no longer supported. All code must use the canonical `feature_flags.<id>.enabled` format. Guard tests enforce canonical key usage and declaration coverage for literal key references in the unified registry.
441
+
442
+ **Key source files:**
443
+
444
+ | File | Purpose |
445
+ |------|---------|
446
+ | `src/config/assistant-feature-flags.ts` | Canonical resolver: `isAssistantFeatureFlagEnabled()`, `isAssistantSkillEnabled()`, `getAssistantFeatureFlagDefaults()`, registry loader |
447
+ | `src/config/skill-state.ts` | `isSkillFeatureEnabled()` (deprecated wrapper) — delegates to canonical resolver; `resolveSkillStates()` — enforcement point 1 |
448
+ | `src/config/system-prompt.ts` | `appendSkillsCatalog()` — enforcement point 2 |
449
+ | `src/tools/skills/load.ts` | `executeSkillLoad()` — enforcement points 3 and 5 |
450
+ | `src/daemon/session-skill-tools.ts` | `projectSkillTools()` — enforcement point 4 |
451
+ | `src/config/schema.ts` | `featureFlags` and `assistantFeatureFlagValues` field definitions in `AssistantConfig` (Zod schema) |
452
+ | `src/config/types.ts` | Type definitions for `FeatureFlags` (legacy) and `AssistantFeatureFlagValues` (canonical) |
453
+ | `src/daemon/handlers/skills.ts` | `handleSkillsList()` — uses `resolveSkillStates()` for IPC client responses |
454
+ | `meta/feature-flags/feature-flag-registry.json` | Unified feature flag registry (repo root) — all declared flags with scope, label, default values, and descriptions |
455
+ | `src/config/feature-flag-registry.json` | Bundled copy of the unified registry for compiled binary resolution |
312
456
 
313
457
  ---
314
458
 
@@ -366,10 +510,11 @@ graph LR
366
510
  subgraph "~/.vellum/ (Root Files)"
367
511
  SOCK["vellum.sock<br/>Unix domain socket"]
368
512
  TRUST["protected/trust.json<br/>Tool permission rules"]
513
+ FF_TOKEN["feature-flag-token<br/>Dedicated auth for PATCH /v1/feature-flags"]
369
514
  end
370
515
 
371
516
  subgraph "~/.vellum/workspace/ (Workspace Files)"
372
- CONFIG["config files<br/>Hot-reloaded by daemon"]
517
+ CONFIG["config files<br/>Hot-reloaded by daemon<br/>(includes featureFlags)"]
373
518
  ONBOARD_PLAYBOOKS["onboarding/playbooks/<br/>[channel]_onboarding.md<br/>assistant-updatable checklists"]
374
519
  ONBOARD_REGISTRY["onboarding/playbooks/registry.json<br/>channel-start index for fast-path + reconciliation"]
375
520
  APPS_STORE["data/apps/<br/><app-id>.json + pages/*.html<br/>prebuilt Home Base seeded here"]
@@ -780,20 +925,13 @@ graph TB
780
925
 
781
926
  EXEC -->|"bash"| WRAP["wrapCommand()<br/>sandbox.ts"]
782
927
 
783
- WRAP --> BACKEND_CHECK{"sandbox.backend?"}
784
- BACKEND_CHECK -->|"native"| NATIVE["NativeBackend"]
785
- BACKEND_CHECK -->|"docker (default)"| DOCKER["DockerBackend"]
928
+ WRAP --> NATIVE["NativeBackend"]
786
929
 
787
930
  NATIVE -->|"macOS"| SBPL["sandbox-exec<br/>SBPL profile<br/>deny-default + allow workdir"]
788
931
  NATIVE -->|"Linux"| BWRAP["bwrap<br/>bubblewrap<br/>ro-root + rw-workdir<br/>unshare-net + unshare-pid"]
789
932
  SBPL --> SB_FS["Sandbox filesystem root<br/>~/.vellum/workspace"]
790
933
  BWRAP --> SB_FS
791
934
 
792
- DOCKER --> PREFLIGHT["Preflight checks<br/>CLI → daemon → image → mount"]
793
- PREFLIGHT -->|"all pass"| CONTAINER["docker run --rm<br/>bind-mount /workspace<br/>--cap-drop=ALL<br/>--read-only<br/>--network=none"]
794
- PREFLIGHT -->|"any fail"| FAIL_CLOSED["ToolError<br/>(fail closed, no fallback)"]
795
- CONTAINER --> SB_FS
796
-
797
935
  EXEC -->|"host_file_* / host_bash / computer_use_request_control"| HOST_TOOLS["Host-target tools<br/>(unchanged by backend choice)"]
798
936
  EXEC -->|"computer_use_* (skill-projected<br/>in CU sessions only)"| SKILL_CU_TOOLS["CU skill tools<br/>(bundled computer-use skill)"]
799
937
  HOST_TOOLS --> CHECK["Permission checker + trust-store"]
@@ -806,10 +944,8 @@ graph TB
806
944
  USER --> CHECK
807
945
  ```
808
946
 
809
- - **Backend selection**: The `sandbox.backend` config option (`"native"` or `"docker"`) determines how `bash` commands are sandboxed. The default is `"docker"`.
810
947
  - **Native backend**: Uses OS-level sandboxing — `sandbox-exec` with SBPL profiles on macOS, `bwrap` (bubblewrap) on Linux. Denies network access and restricts filesystem writes to the sandbox root, `/tmp`, `/private/tmp`, and `/var/folders` (macOS) or the sandbox root and `/tmp` (Linux).
811
- - **Docker backend**: Wraps each command in an ephemeral `docker run --rm` container. The canonical sandbox filesystem root (`~/.vellum/workspace`) is always bind-mounted to `/workspace`, regardless of which subdirectory the command runs in. Commands are wrapped with `bash -c`. Containers run with all capabilities dropped, a read-only root filesystem, no network access, and host UID:GID forwarding. The default image is `vellum-sandbox:latest`, built from `assistant/Dockerfile.sandbox` (extends `node:20-slim` with `curl`, `ca-certificates`, and `bash`). The image is auto-built on first use if not found locally.
812
- - **Fail-closed**: Both backends refuse to execute unsandboxed if their prerequisites are unavailable. The Docker backend runs preflight checks (CLI, daemon, image, writable mount probe via `test -w /workspace`) and throws `ToolError` with actionable messages on failure. Positive preflight results are cached; negative results are rechecked on every call. The `vellum doctor` command validates the same checks against the same sandbox path.
948
+ - **Fail-closed**: The native backend refuses to execute unsandboxed if its prerequisites are unavailable, throwing `ToolError` with actionable messages on failure.
813
949
  - **Host tools unchanged**: `host_bash`, `host_file_read`, `host_file_write`, and `host_file_edit` always execute directly on the host regardless of which sandbox backend is active.
814
950
  - Sandbox defaults: `file_*` and `bash` execute within `~/.vellum/workspace`.
815
951
  - Host access is explicit: `host_file_read`, `host_file_write`, `host_file_edit`, and `host_bash` are separate tools.
package/Dockerfile CHANGED
@@ -96,6 +96,7 @@ EXPOSE 3001
96
96
  ENV RUNTIME_HTTP_PORT=3001
97
97
  ENV VELLUM_DAEMON_SOCKET=/home/assistant/.vellum/vellum.sock
98
98
  ENV BASE_DATA_DIR=/data
99
+ ENV IS_CONTAINERIZED=true
99
100
 
100
101
  # Run the daemon + http server
101
102
  CMD ["bun", "run", "src/daemon/main.ts"]
package/README.md CHANGED
@@ -125,7 +125,6 @@ assistant/
125
125
  ├── docs/ # Internal documentation
126
126
  ├── scripts/ # Test runners and IPC codegen
127
127
  ├── Dockerfile # Production container image
128
- ├── Dockerfile.sandbox # Sandbox container for bash tool
129
128
  └── package.json
130
129
  ```
131
130
 
@@ -339,7 +338,7 @@ Guardian verification can also be initiated through normal desktop chat. When th
339
338
  3. Call the outbound HTTP endpoints to start, resend, or cancel verification.
340
339
  4. Guide the user through the verification lifecycle conversationally.
341
340
 
342
- **Outbound HTTP Endpoints** (available when the runtime HTTP server is running):
341
+ **Outbound HTTP Endpoints** (exposed via the gateway API and forwarded to the runtime):
343
342
 
344
343
  | Endpoint | Method | Description |
345
344
  |----------|--------|-------------|
@@ -347,7 +346,7 @@ Guardian verification can also be initiated through normal desktop chat. When th
347
346
  | `/v1/integrations/guardian/outbound/resend` | POST | Resend verification code. Body: `{ channel, assistantId? }` |
348
347
  | `/v1/integrations/guardian/outbound/cancel` | POST | Cancel active session. Body: `{ channel, assistantId? }` |
349
348
 
350
- These endpoints share the same business logic as the IPC-based verification flow via `guardian-outbound-actions.ts`.
349
+ These endpoints share the same business logic as the IPC-based verification flow via `guardian-outbound-actions.ts`. Skills and clients should call the gateway URL (default `http://localhost:7830`) rather than the runtime port directly.
351
350
 
352
351
  **Security constraint:** Guardian verification control-plane endpoints are restricted to guardian and desktop (trusted) actors only. Non-guardian and unverified-channel actors cannot invoke these endpoints conversationally via tools. Attempts are denied with a message explaining that guardian verification actions are restricted to guardian users.
353
352
 
@@ -385,7 +384,38 @@ Secure cross-user messaging allows external users (non-guardians) to interact wi
385
384
 
386
385
  ### Ingress Membership
387
386
 
388
- External users join through **invite tokens** the owner creates an invite via IPC, and the external user redeems the token by sending it as a channel message. Redemption auto-creates a **member** record with an access policy:
387
+ External users join through **invite tokens**. There are two invite flows:
388
+
389
+ 1. **IPC-based (legacy)** — The owner creates an invite via IPC, obtains the raw token, and shares it manually. The external user redeems the token by sending it as a channel message.
390
+ 2. **Guardian-initiated invite links (Telegram)** — The guardian asks the assistant to create an invite link via desktop chat. The assistant creates an invite, builds a channel-specific deep link, and presents it for sharing. The invitee clicks the link and is automatically granted access.
391
+
392
+ #### Guardian-Initiated Invite Link Flow (Telegram)
393
+
394
+ 1. **Guardian requests invite** — The guardian asks the assistant (via desktop chat) to create a Telegram invite link. The `guardian-invite-intent.ts` module detects the intent and routes the request into the `trusted-contacts` skill.
395
+ 2. **Invite creation** — The skill creates an invite token via the ingress HTTP API, looks up the Telegram bot username from the integration config endpoint, and constructs a shareable deep link: `https://t.me/<bot>?start=iv_<token>`.
396
+ 3. **Guardian shares link** — The guardian copies the deep link and shares it with the invitee through any messaging channel.
397
+ 4. **Invitee redeems** — The invitee clicks the link, which opens Telegram and sends `/start iv_<token>` to the bot. The inbound message handler extracts the token via the transport adapter, redeems it through the invite redemption service, and auto-creates an active member record.
398
+ 5. **Access granted** — The invitee receives a welcome message and all subsequent messages pass the ingress ACL.
399
+
400
+ The `iv_` prefix distinguishes invite tokens from `gv_` (guardian verification) tokens, which use the same Telegram `/start` deep-link mechanism.
401
+
402
+ #### Invite Redemption Architecture
403
+
404
+ The invite redemption system uses a three-layer architecture:
405
+
406
+ - **Core redemption engine** (`invite-redemption-service.ts`) — Channel-agnostic business logic that validates tokens, enforces expiry/use-count/channel-match constraints, handles member reactivation, and returns a discriminated-union `InviteRedemptionOutcome`. Deterministic reply templates (`invite-redemption-templates.ts`) map each outcome to a user-facing message without passing through the LLM.
407
+ - **Channel transport adapters** (`channel-invite-transport.ts` + `channel-invite-transports/`) — A registry of per-channel adapters that know how to build shareable deep links (`buildShareableInvite`) and extract inbound tokens (`extractInboundToken`). Currently only the Telegram adapter is implemented.
408
+ - **Conversational orchestration** (`guardian-invite-intent.ts`) — Pattern-based intent detection that intercepts guardian invite management requests (create, list, revoke) in the session pipeline and forces immediate entry into the `trusted-contacts` skill, bypassing the normal agent loop.
409
+
410
+ #### Deferred Channel Support
411
+
412
+ The transport adapter registry is architecturally extensible to additional channels. The following are not yet implemented:
413
+
414
+ - **SMS** — Requires a deep-link strategy compatible with SMS (e.g., a short URL that redirects to an SMS reply flow or web-based redemption page). The core redemption engine is channel-agnostic and ready.
415
+ - **Slack** — Requires DM-safe ingress (Socket Mode currently handles channel messages but DM-initiated invite flows need additional routing). The adapter would build Slack deep links or slash-command payloads.
416
+ - **Voice** — Requires DTMF or speech-based token capture during an inbound call. The adapter would need to integrate with the voice relay state machine for token entry.
417
+
418
+ Redemption auto-creates a **member** record with an access policy:
389
419
 
390
420
  - **`allow`** — Messages are processed normally through the agent pipeline.
391
421
  - **`deny`** — Messages are rejected with a refusal notice.
@@ -417,6 +447,12 @@ If no guardian binding exists, escalation fails closed — the message is denied
417
447
  | `src/daemon/handlers/config-inbox.ts` | IPC handlers for ingress invite and member contracts |
418
448
  | `src/daemon/ipc-contract/inbox.ts` | TypeScript type definitions for ingress IPC messages |
419
449
  | `src/runtime/routes/channel-routes.ts` | ACL enforcement point — member lookup, policy check, escalation creation |
450
+ | `src/runtime/invite-redemption-service.ts` | Core redemption engine — token validation, member creation, discriminated-union outcomes |
451
+ | `src/runtime/invite-redemption-templates.ts` | Deterministic reply templates for each redemption outcome |
452
+ | `src/runtime/channel-invite-transport.ts` | Transport adapter registry — `buildShareableInvite` / `extractInboundToken` per channel |
453
+ | `src/runtime/channel-invite-transports/telegram.ts` | Telegram adapter — builds `t.me/<bot>?start=iv_<token>` deep links, extracts `iv_` tokens from `/start` commands |
454
+ | `src/daemon/guardian-invite-intent.ts` | Intent detection — routes guardian invite management requests into the `trusted-contacts` skill |
455
+ | `src/runtime/ingress-service.ts` | Shared business logic for invite/member operations (HTTP + IPC) |
420
456
 
421
457
  ## Database
422
458