@vellumai/assistant 0.3.18 → 0.3.20

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 (202) hide show
  1. package/ARCHITECTURE.md +155 -15
  2. package/Dockerfile +1 -0
  3. package/README.md +40 -4
  4. package/docs/architecture/integrations.md +7 -11
  5. package/docs/architecture/security.md +80 -0
  6. package/package.json +1 -1
  7. package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +58 -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 +605 -104
  13. package/src/__tests__/channel-invite-transport.test.ts +264 -0
  14. package/src/__tests__/checker.test.ts +60 -0
  15. package/src/__tests__/cli.test.ts +42 -1
  16. package/src/__tests__/config-schema.test.ts +11 -127
  17. package/src/__tests__/config-watcher.test.ts +0 -8
  18. package/src/__tests__/daemon-lifecycle.test.ts +1 -0
  19. package/src/__tests__/daemon-server-session-init.test.ts +8 -2
  20. package/src/__tests__/diff.test.ts +22 -0
  21. package/src/__tests__/guardian-action-copy-generator.test.ts +5 -0
  22. package/src/__tests__/guardian-action-grant-mint-consume.test.ts +779 -0
  23. package/src/__tests__/guardian-action-late-reply.test.ts +546 -1
  24. package/src/__tests__/guardian-actions-endpoint.test.ts +774 -0
  25. package/src/__tests__/guardian-control-plane-policy.test.ts +36 -3
  26. package/src/__tests__/guardian-dispatch.test.ts +185 -1
  27. package/src/__tests__/guardian-grant-minting.test.ts +532 -0
  28. package/src/__tests__/inbound-invite-redemption.test.ts +367 -0
  29. package/src/__tests__/invite-redemption-service.test.ts +306 -0
  30. package/src/__tests__/ipc-snapshot.test.ts +58 -0
  31. package/src/__tests__/notification-decision-fallback.test.ts +88 -0
  32. package/src/__tests__/remote-skill-policy.test.ts +215 -0
  33. package/src/__tests__/sandbox-diagnostics.test.ts +6 -249
  34. package/src/__tests__/sandbox-host-parity.test.ts +6 -13
  35. package/src/__tests__/scoped-approval-grants.test.ts +521 -0
  36. package/src/__tests__/scoped-grant-security-matrix.test.ts +444 -0
  37. package/src/__tests__/script-proxy-session-manager.test.ts +1 -19
  38. package/src/__tests__/session-load-history-repair.test.ts +169 -2
  39. package/src/__tests__/session-runtime-assembly.test.ts +33 -5
  40. package/src/__tests__/skill-feature-flags-integration.test.ts +171 -0
  41. package/src/__tests__/skill-feature-flags.test.ts +188 -0
  42. package/src/__tests__/skill-load-feature-flag.test.ts +141 -0
  43. package/src/__tests__/skill-mirror-parity.test.ts +1 -0
  44. package/src/__tests__/skill-projection-feature-flag.test.ts +363 -0
  45. package/src/__tests__/system-prompt.test.ts +1 -1
  46. package/src/__tests__/terminal-sandbox.test.ts +142 -9
  47. package/src/__tests__/terminal-tools.test.ts +2 -93
  48. package/src/__tests__/thread-seed-composer.test.ts +18 -0
  49. package/src/__tests__/tool-approval-handler.test.ts +350 -0
  50. package/src/__tests__/trust-store.test.ts +2 -0
  51. package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +8 -10
  52. package/src/__tests__/voice-scoped-grant-consumer.test.ts +533 -0
  53. package/src/agent/loop.ts +36 -1
  54. package/src/approvals/approval-primitive.ts +381 -0
  55. package/src/approvals/guardian-decision-primitive.ts +191 -0
  56. package/src/calls/call-controller.ts +276 -212
  57. package/src/calls/call-domain.ts +56 -6
  58. package/src/calls/guardian-dispatch.ts +56 -0
  59. package/src/calls/relay-server.ts +13 -0
  60. package/src/calls/types.ts +1 -1
  61. package/src/calls/voice-session-bridge.ts +59 -4
  62. package/src/cli/core-commands.ts +0 -4
  63. package/src/cli.ts +76 -34
  64. package/src/config/__tests__/feature-flag-registry-guard.test.ts +179 -0
  65. package/src/config/assistant-feature-flags.ts +162 -0
  66. package/src/config/bundled-skills/api-mapping/icon.svg +18 -0
  67. package/src/config/bundled-skills/messaging/TOOLS.json +30 -0
  68. package/src/config/bundled-skills/messaging/tools/slack-delete-message.ts +24 -0
  69. package/src/config/bundled-skills/notifications/SKILL.md +18 -0
  70. package/src/config/bundled-skills/reminder/SKILL.md +49 -2
  71. package/src/config/bundled-skills/time-based-actions/SKILL.md +49 -2
  72. package/src/config/bundled-skills/voice-setup/SKILL.md +122 -0
  73. package/src/config/core-schema.ts +1 -1
  74. package/src/config/env-registry.ts +10 -0
  75. package/src/config/feature-flag-registry.json +61 -0
  76. package/src/config/loader.ts +22 -1
  77. package/src/config/sandbox-schema.ts +0 -39
  78. package/src/config/schema.ts +12 -2
  79. package/src/config/skill-state.ts +34 -0
  80. package/src/config/skills-schema.ts +26 -0
  81. package/src/config/skills.ts +9 -0
  82. package/src/config/system-prompt.ts +110 -46
  83. package/src/config/templates/SOUL.md +1 -1
  84. package/src/config/types.ts +19 -1
  85. package/src/config/vellum-skills/catalog.json +1 -1
  86. package/src/config/vellum-skills/guardian-verify-setup/SKILL.md +1 -0
  87. package/src/config/vellum-skills/sms-setup/SKILL.md +1 -1
  88. package/src/config/vellum-skills/telegram-setup/SKILL.md +1 -1
  89. package/src/config/vellum-skills/trusted-contacts/SKILL.md +104 -3
  90. package/src/config/vellum-skills/twilio-setup/SKILL.md +1 -1
  91. package/src/daemon/config-watcher.ts +0 -1
  92. package/src/daemon/daemon-control.ts +1 -1
  93. package/src/daemon/guardian-invite-intent.ts +124 -0
  94. package/src/daemon/handlers/avatar.ts +68 -0
  95. package/src/daemon/handlers/browser.ts +2 -2
  96. package/src/daemon/handlers/config-channels.ts +18 -0
  97. package/src/daemon/handlers/guardian-actions.ts +120 -0
  98. package/src/daemon/handlers/index.ts +4 -0
  99. package/src/daemon/handlers/sessions.ts +19 -0
  100. package/src/daemon/handlers/shared.ts +3 -1
  101. package/src/daemon/handlers/skills.ts +45 -2
  102. package/src/daemon/install-cli-launchers.ts +58 -13
  103. package/src/daemon/ipc-contract/guardian-actions.ts +53 -0
  104. package/src/daemon/ipc-contract/sessions.ts +8 -2
  105. package/src/daemon/ipc-contract/settings.ts +25 -2
  106. package/src/daemon/ipc-contract/skills.ts +1 -0
  107. package/src/daemon/ipc-contract-inventory.json +10 -0
  108. package/src/daemon/ipc-contract.ts +4 -0
  109. package/src/daemon/lifecycle.ts +6 -2
  110. package/src/daemon/main.ts +1 -0
  111. package/src/daemon/server.ts +1 -0
  112. package/src/daemon/session-lifecycle.ts +52 -7
  113. package/src/daemon/session-memory.ts +45 -0
  114. package/src/daemon/session-process.ts +260 -422
  115. package/src/daemon/session-runtime-assembly.ts +12 -0
  116. package/src/daemon/session-skill-tools.ts +14 -1
  117. package/src/daemon/session-tool-setup.ts +5 -0
  118. package/src/daemon/session.ts +11 -0
  119. package/src/daemon/tool-side-effects.ts +35 -9
  120. package/src/index.ts +0 -2
  121. package/src/memory/conversation-display-order-migration.ts +44 -0
  122. package/src/memory/conversation-queries.ts +2 -0
  123. package/src/memory/conversation-store.ts +91 -0
  124. package/src/memory/db-init.ts +13 -1
  125. package/src/memory/embedding-local.ts +22 -8
  126. package/src/memory/guardian-action-store.ts +133 -2
  127. package/src/memory/guardian-verification.ts +1 -1
  128. package/src/memory/ingress-invite-store.ts +95 -1
  129. package/src/memory/migrations/033-scoped-approval-grants.ts +51 -0
  130. package/src/memory/migrations/034-guardian-action-tool-metadata.ts +12 -0
  131. package/src/memory/migrations/035-guardian-action-supersession.ts +23 -0
  132. package/src/memory/migrations/index.ts +3 -0
  133. package/src/memory/schema.ts +35 -1
  134. package/src/memory/scoped-approval-grants.ts +518 -0
  135. package/src/messaging/providers/slack/client.ts +12 -0
  136. package/src/messaging/providers/slack/types.ts +5 -0
  137. package/src/notifications/decision-engine.ts +49 -12
  138. package/src/notifications/emit-signal.ts +7 -0
  139. package/src/notifications/signal.ts +7 -0
  140. package/src/notifications/thread-seed-composer.ts +2 -1
  141. package/src/permissions/checker.ts +27 -0
  142. package/src/runtime/channel-approval-types.ts +16 -6
  143. package/src/runtime/channel-approvals.ts +19 -15
  144. package/src/runtime/channel-invite-transport.ts +85 -0
  145. package/src/runtime/channel-invite-transports/telegram.ts +105 -0
  146. package/src/runtime/guardian-action-grant-minter.ts +154 -0
  147. package/src/runtime/guardian-action-message-composer.ts +30 -0
  148. package/src/runtime/guardian-decision-types.ts +91 -0
  149. package/src/runtime/http-server.ts +23 -1
  150. package/src/runtime/ingress-service.ts +22 -0
  151. package/src/runtime/invite-redemption-service.ts +181 -0
  152. package/src/runtime/invite-redemption-templates.ts +39 -0
  153. package/src/runtime/routes/call-routes.ts +2 -1
  154. package/src/runtime/routes/guardian-action-routes.ts +206 -0
  155. package/src/runtime/routes/guardian-approval-interception.ts +66 -74
  156. package/src/runtime/routes/inbound-message-handler.ts +568 -409
  157. package/src/runtime/routes/pairing-routes.ts +4 -0
  158. package/src/security/encrypted-store.ts +31 -17
  159. package/src/security/keychain.ts +176 -2
  160. package/src/security/secure-keys.ts +97 -0
  161. package/src/security/tool-approval-digest.ts +67 -0
  162. package/src/skills/remote-skill-policy.ts +131 -0
  163. package/src/tools/browser/browser-execution.ts +2 -2
  164. package/src/tools/browser/browser-manager.ts +46 -32
  165. package/src/tools/browser/browser-screencast.ts +2 -2
  166. package/src/tools/calls/call-start.ts +1 -1
  167. package/src/tools/executor.ts +22 -17
  168. package/src/tools/network/script-proxy/session-manager.ts +1 -5
  169. package/src/tools/skills/load.ts +22 -8
  170. package/src/tools/system/avatar-generator.ts +119 -0
  171. package/src/tools/system/navigate-settings.ts +65 -0
  172. package/src/tools/system/open-system-settings.ts +75 -0
  173. package/src/tools/system/voice-config.ts +121 -32
  174. package/src/tools/terminal/backends/native.ts +40 -19
  175. package/src/tools/terminal/backends/types.ts +3 -3
  176. package/src/tools/terminal/parser.ts +1 -1
  177. package/src/tools/terminal/sandbox-diagnostics.ts +6 -87
  178. package/src/tools/terminal/sandbox.ts +1 -12
  179. package/src/tools/terminal/shell.ts +3 -31
  180. package/src/tools/tool-approval-handler.ts +141 -3
  181. package/src/tools/tool-manifest.ts +6 -0
  182. package/src/tools/types.ts +6 -0
  183. package/src/util/diff.ts +36 -13
  184. package/Dockerfile.sandbox +0 -5
  185. package/src/__tests__/doordash-client.test.ts +0 -187
  186. package/src/__tests__/doordash-session.test.ts +0 -154
  187. package/src/__tests__/signup-e2e.test.ts +0 -354
  188. package/src/__tests__/terminal-sandbox-docker.test.ts +0 -1065
  189. package/src/__tests__/terminal-sandbox.integration.test.ts +0 -180
  190. package/src/cli/doordash.ts +0 -1057
  191. package/src/config/bundled-skills/doordash/SKILL.md +0 -163
  192. package/src/config/templates/LOOKS.md +0 -25
  193. package/src/doordash/cart-queries.ts +0 -787
  194. package/src/doordash/client.ts +0 -1016
  195. package/src/doordash/order-queries.ts +0 -85
  196. package/src/doordash/queries.ts +0 -13
  197. package/src/doordash/query-extractor.ts +0 -94
  198. package/src/doordash/search-queries.ts +0 -203
  199. package/src/doordash/session.ts +0 -84
  200. package/src/doordash/store-queries.ts +0 -246
  201. package/src/doordash/types.ts +0 -367
  202. package/src/tools/terminal/backends/docker.ts +0 -379
@@ -484,15 +484,15 @@ describe('ToolExecutor guardian-only policy gate', () => {
484
484
  expect(result.content).toBe('ok');
485
485
  });
486
486
 
487
- test('non-guardian invocation of unrelated endpoint is unaffected', async () => {
487
+ test('non-guardian invocation of unrelated bash command is blocked by guardian approval gate', async () => {
488
488
  const executor = new ToolExecutor(makePrompter());
489
489
  const result = await executor.execute(
490
490
  'bash',
491
491
  { command: 'curl http://localhost:3000/v1/messages' },
492
492
  makeContext({ guardianActorRole: 'non-guardian' }),
493
493
  );
494
- expect(result.isError).toBe(false);
495
- expect(result.content).toBe('ok');
494
+ expect(result.isError).toBe(true);
495
+ expect(result.content).toContain('requires guardian approval');
496
496
  });
497
497
 
498
498
  test('non-guardian invocation of unrelated tool is unaffected', async () => {
@@ -579,4 +579,37 @@ describe('ToolExecutor guardian-only policy gate', () => {
579
579
  expect(result.content).toContain('restricted to guardian users');
580
580
  }
581
581
  });
582
+
583
+ test('non-guardian actor is blocked from host read tools (host execution)', async () => {
584
+ const executor = new ToolExecutor(makePrompter());
585
+ const result = await executor.execute(
586
+ 'host_file_read',
587
+ { path: '/Users/noaflaherty/.ssh/config' },
588
+ makeContext({ guardianActorRole: 'non-guardian' }),
589
+ );
590
+ expect(result.isError).toBe(true);
591
+ expect(result.content).toContain('requires guardian approval');
592
+ });
593
+
594
+ test('unverified channel actor is blocked from side-effect tools', async () => {
595
+ const executor = new ToolExecutor(makePrompter());
596
+ const result = await executor.execute(
597
+ 'reminder_create',
598
+ { fire_at: '2026-02-27T12:00:00-05:00', label: 'test', message: 'hello' },
599
+ makeContext({ guardianActorRole: 'unverified_channel' }),
600
+ );
601
+ expect(result.isError).toBe(true);
602
+ expect(result.content).toContain('verified channel identity');
603
+ });
604
+
605
+ test('guardian actor can execute side-effect tools', async () => {
606
+ const executor = new ToolExecutor(makePrompter());
607
+ const result = await executor.execute(
608
+ 'reminder_create',
609
+ { fire_at: '2026-02-27T12:00:00-05:00', label: 'test', message: 'hello' },
610
+ makeContext({ guardianActorRole: 'guardian' }),
611
+ );
612
+ expect(result.isError).toBe(false);
613
+ expect(result.content).toBe('ok');
614
+ });
582
615
  });
@@ -336,10 +336,70 @@ describe('guardian-dispatch', () => {
336
336
  expect(vellumDelivery!.destination_conversation_id).toBe('conv-from-thread-created');
337
337
  });
338
338
 
339
- test('includes activeGuardianRequestCount in context payload', async () => {
339
+ test('persists toolName and inputDigest on guardian action request for tool-approval dispatches', async () => {
340
340
  const convId = 'conv-dispatch-5';
341
341
  ensureConversation(convId);
342
342
 
343
+ const session = createCallSession({
344
+ conversationId: convId,
345
+ provider: 'twilio',
346
+ fromNumber: '+15550001111',
347
+ toNumber: '+15550002222',
348
+ });
349
+ const pq = createPendingQuestion(session.id, 'Allow send_email to bob@example.com?');
350
+
351
+ await dispatchGuardianQuestion({
352
+ callSessionId: session.id,
353
+ conversationId: convId,
354
+ assistantId: 'self',
355
+ pendingQuestion: pq,
356
+ toolName: 'send_email',
357
+ inputDigest: 'abc123def456',
358
+ });
359
+
360
+ const db = getDb();
361
+ const raw = (db as unknown as { $client: import('bun:sqlite').Database }).$client;
362
+ const request = raw.query('SELECT * FROM guardian_action_requests WHERE call_session_id = ?').get(session.id) as
363
+ | { id: string; tool_name: string | null; input_digest: string | null }
364
+ | undefined;
365
+ expect(request).toBeDefined();
366
+ expect(request!.tool_name).toBe('send_email');
367
+ expect(request!.input_digest).toBe('abc123def456');
368
+ });
369
+
370
+ test('omitting toolName and inputDigest stores null for informational ASK_GUARDIAN dispatches', async () => {
371
+ const convId = 'conv-dispatch-6';
372
+ ensureConversation(convId);
373
+
374
+ const session = createCallSession({
375
+ conversationId: convId,
376
+ provider: 'twilio',
377
+ fromNumber: '+15550001111',
378
+ toNumber: '+15550002222',
379
+ });
380
+ const pq = createPendingQuestion(session.id, 'What time works?');
381
+
382
+ await dispatchGuardianQuestion({
383
+ callSessionId: session.id,
384
+ conversationId: convId,
385
+ assistantId: 'self',
386
+ pendingQuestion: pq,
387
+ });
388
+
389
+ const db = getDb();
390
+ const raw = (db as unknown as { $client: import('bun:sqlite').Database }).$client;
391
+ const request = raw.query('SELECT * FROM guardian_action_requests WHERE call_session_id = ?').get(session.id) as
392
+ | { id: string; tool_name: string | null; input_digest: string | null }
393
+ | undefined;
394
+ expect(request).toBeDefined();
395
+ expect(request!.tool_name).toBeNull();
396
+ expect(request!.input_digest).toBeNull();
397
+ });
398
+
399
+ test('includes activeGuardianRequestCount in context payload', async () => {
400
+ const convId = 'conv-dispatch-7';
401
+ ensureConversation(convId);
402
+
343
403
  const session = createCallSession({
344
404
  conversationId: convId,
345
405
  provider: 'twilio',
@@ -455,4 +515,128 @@ describe('guardian-dispatch', () => {
455
515
  const secondPayload = (emitCalls[0] as Record<string, unknown>).contextPayload as Record<string, unknown>;
456
516
  expect(secondPayload.activeGuardianRequestCount).toBe(2);
457
517
  });
518
+
519
+ test('second guardian question in same call session passes conversationAffinityHint with first conversation ID', async () => {
520
+ const convId = 'conv-dispatch-affinity-1';
521
+ ensureConversation(convId);
522
+
523
+ const sharedConversationId = 'conv-affinity-guardian';
524
+
525
+ const session = createCallSession({
526
+ conversationId: convId,
527
+ provider: 'twilio',
528
+ fromNumber: '+15550001111',
529
+ toNumber: '+15550002222',
530
+ });
531
+
532
+ // First dispatch — no affinity hint expected (no prior delivery exists)
533
+ const pq1 = createPendingQuestion(session.id, 'First question');
534
+ mockEmitResult = {
535
+ signalId: 'sig-affinity-1',
536
+ deduplicated: false,
537
+ dispatched: true,
538
+ reason: 'ok',
539
+ deliveryResults: [
540
+ {
541
+ channel: 'vellum',
542
+ destination: 'vellum',
543
+ status: 'sent',
544
+ conversationId: sharedConversationId,
545
+ },
546
+ ],
547
+ };
548
+
549
+ await dispatchGuardianQuestion({
550
+ callSessionId: session.id,
551
+ conversationId: convId,
552
+ assistantId: 'self',
553
+ pendingQuestion: pq1,
554
+ });
555
+
556
+ const firstParams = emitCalls[0] as Record<string, unknown>;
557
+ // First dispatch should not have an affinity hint
558
+ expect(firstParams.conversationAffinityHint).toBeUndefined();
559
+
560
+ // Second dispatch — should carry the affinity hint from the first delivery
561
+ emitCalls.length = 0;
562
+ const pq2 = createPendingQuestion(session.id, 'Second question');
563
+ mockEmitResult = {
564
+ signalId: 'sig-affinity-2',
565
+ deduplicated: false,
566
+ dispatched: true,
567
+ reason: 'ok',
568
+ deliveryResults: [
569
+ {
570
+ channel: 'vellum',
571
+ destination: 'vellum',
572
+ status: 'sent',
573
+ conversationId: sharedConversationId,
574
+ },
575
+ ],
576
+ };
577
+
578
+ await dispatchGuardianQuestion({
579
+ callSessionId: session.id,
580
+ conversationId: convId,
581
+ assistantId: 'self',
582
+ pendingQuestion: pq2,
583
+ });
584
+
585
+ const secondParams = emitCalls[0] as Record<string, unknown>;
586
+ expect(secondParams.conversationAffinityHint).toEqual({
587
+ vellum: sharedConversationId,
588
+ });
589
+ });
590
+
591
+ test('third guardian question in same call session also carries affinity hint', async () => {
592
+ const convId = 'conv-dispatch-affinity-2';
593
+ ensureConversation(convId);
594
+
595
+ const sharedConversationId = 'conv-affinity-triple';
596
+
597
+ const session = createCallSession({
598
+ conversationId: convId,
599
+ provider: 'twilio',
600
+ fromNumber: '+15550001111',
601
+ toNumber: '+15550002222',
602
+ });
603
+
604
+ // Dispatch three guardian questions in the same call session
605
+ for (let i = 0; i < 3; i++) {
606
+ emitCalls.length = 0;
607
+ const pq = createPendingQuestion(session.id, `Question ${i + 1}`);
608
+ mockEmitResult = {
609
+ signalId: `sig-triple-${i}`,
610
+ deduplicated: false,
611
+ dispatched: true,
612
+ reason: 'ok',
613
+ deliveryResults: [
614
+ {
615
+ channel: 'vellum',
616
+ destination: 'vellum',
617
+ status: 'sent',
618
+ conversationId: sharedConversationId,
619
+ },
620
+ ],
621
+ };
622
+
623
+ await dispatchGuardianQuestion({
624
+ callSessionId: session.id,
625
+ conversationId: convId,
626
+ assistantId: 'self',
627
+ pendingQuestion: pq,
628
+ });
629
+
630
+ const params = emitCalls[0] as Record<string, unknown>;
631
+ if (i === 0) {
632
+ // First dispatch — no affinity hint
633
+ expect(params.conversationAffinityHint).toBeUndefined();
634
+ } else {
635
+ // Subsequent dispatches — affinity hint points to the shared conversation
636
+ expect(params.conversationAffinityHint).toEqual({
637
+ vellum: sharedConversationId,
638
+ });
639
+ }
640
+ }
641
+ });
458
642
  });