@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
@@ -2,6 +2,7 @@
2
2
  * Approval interception: checks for pending approvals and handles inbound
3
3
  * messages as decisions, reminders, or conversational follow-ups.
4
4
  */
5
+ import { applyGuardianDecision } from '../../approvals/guardian-decision-primitive.js';
5
6
  import type { ChannelId } from '../../channels/types.js';
6
7
  import {
7
8
  getAllPendingApprovalsByGuardianChat,
@@ -185,13 +186,6 @@ export async function handleApprovalInterception(
185
186
  }
186
187
 
187
188
  if (callbackDecision) {
188
- // approve_always is not available for guardian approvals — guardians
189
- // should not be able to permanently allowlist tools on behalf of the
190
- // requester. Downgrade to approve_once.
191
- if (callbackDecision.action === 'approve_always') {
192
- callbackDecision = { ...callbackDecision, action: 'approve_once' };
193
- }
194
-
195
189
  // Access request approvals don't have a pending interaction in the
196
190
  // session tracker, so they need a separate decision path that creates
197
191
  // a verification session instead of resuming an agent loop.
@@ -207,27 +201,22 @@ export async function handleApprovalInterception(
207
201
  return accessResult;
208
202
  }
209
203
 
210
- // Apply the decision to the underlying session using the requester's
211
- // conversation context
212
- const result = handleChannelDecision(
213
- guardianApproval.conversationId,
214
- callbackDecision,
215
- );
204
+ // Apply the decision through the unified guardian decision primitive.
205
+ // The primitive handles approve_always downgrade, approval info capture,
206
+ // record update, and scoped grant minting.
207
+ const result = applyGuardianDecision({
208
+ approval: guardianApproval,
209
+ decision: callbackDecision,
210
+ actorExternalUserId: senderExternalUserId,
211
+ actorChannel: sourceChannel,
212
+ });
216
213
 
217
214
  if (result.applied) {
218
- // Update the guardian approval request record only when the decision
219
- // was actually applied. If the request was already resolved (race with
220
- // expiry sweep or concurrent callback), skip to avoid inconsistency.
221
- const approvalStatus = callbackDecision.action === 'reject' ? 'denied' as const : 'approved' as const;
222
- updateApprovalDecision(guardianApproval.id, {
223
- status: approvalStatus,
224
- decidedByExternalUserId: senderExternalUserId,
225
- });
226
-
227
215
  // Notify the requester's chat about the outcome with the tool name
216
+ const effectiveAction = callbackDecision.action === 'approve_always' ? 'approve_once' : callbackDecision.action;
228
217
  const outcomeText = await composeApprovalMessageGenerative({
229
218
  scenario: 'guardian_decision_outcome',
230
- decision: callbackDecision.action === 'reject' ? 'denied' : 'approved',
219
+ decision: effectiveAction === 'reject' ? 'denied' : 'approved',
231
220
  toolName: guardianApproval.toolName,
232
221
  channel: sourceChannel,
233
222
  }, {}, approvalCopyGenerator);
@@ -346,21 +335,15 @@ export async function handleApprovalInterception(
346
335
  ...(engineResult.targetRequestId ? { requestId: engineResult.targetRequestId } : {}),
347
336
  };
348
337
 
349
- const result = handleChannelDecision(
350
- targetApproval.conversationId,
351
- engineDecision,
352
- );
338
+ // Apply the decision through the unified guardian decision primitive.
339
+ const result = applyGuardianDecision({
340
+ approval: targetApproval,
341
+ decision: engineDecision,
342
+ actorExternalUserId: senderExternalUserId,
343
+ actorChannel: sourceChannel,
344
+ });
353
345
 
354
346
  if (result.applied) {
355
- // Update the guardian approval request record only when the decision
356
- // was actually applied. If the request was already resolved (race with
357
- // expiry sweep or concurrent callback), skip to avoid inconsistency.
358
- const approvalStatus = decisionAction === 'reject' ? 'denied' as const : 'approved' as const;
359
- updateApprovalDecision(targetApproval.id, {
360
- status: approvalStatus,
361
- decidedByExternalUserId: senderExternalUserId,
362
- });
363
-
364
347
  // Notify the requester's chat about the outcome
365
348
  const outcomeText = await composeApprovalMessageGenerative({
366
349
  scenario: 'guardian_decision_outcome',
@@ -492,18 +475,15 @@ export async function handleApprovalInterception(
492
475
  return accessResult;
493
476
  }
494
477
 
495
- const result = handleChannelDecision(
496
- targetLegacyApproval.conversationId,
497
- legacyGuardianDecision,
498
- );
478
+ // Apply the decision through the unified guardian decision primitive.
479
+ const result = applyGuardianDecision({
480
+ approval: targetLegacyApproval,
481
+ decision: legacyGuardianDecision,
482
+ actorExternalUserId: senderExternalUserId,
483
+ actorChannel: sourceChannel,
484
+ });
499
485
 
500
486
  if (result.applied) {
501
- const approvalStatus = legacyGuardianDecision.action === 'reject' ? 'denied' as const : 'approved' as const;
502
- updateApprovalDecision(targetLegacyApproval.id, {
503
- status: approvalStatus,
504
- decidedByExternalUserId: senderExternalUserId,
505
- });
506
-
507
487
  // Notify the requester's chat about the outcome
508
488
  const outcomeText = await composeApprovalMessageGenerative({
509
489
  scenario: 'guardian_decision_outcome',
@@ -626,13 +606,15 @@ export async function handleApprovalInterception(
626
606
  action: 'reject',
627
607
  source: 'plain_text',
628
608
  };
629
- const cancelApplyResult = handleChannelDecision(conversationId, rejectDecision);
609
+ // Apply the cancel decision through the unified primitive.
610
+ // The primitive handles record update and (no-op) grant logic.
611
+ const cancelApplyResult = applyGuardianDecision({
612
+ approval: guardianApprovalForRequest,
613
+ decision: rejectDecision,
614
+ actorExternalUserId: senderExternalUserId,
615
+ actorChannel: sourceChannel,
616
+ });
630
617
  if (cancelApplyResult.applied) {
631
- updateApprovalDecision(guardianApprovalForRequest.id, {
632
- status: 'denied',
633
- decidedByExternalUserId: senderExternalUserId,
634
- });
635
-
636
618
  // Notify requester
637
619
  const replyText = cancelReplyText ?? await composeApprovalMessageGenerative({
638
620
  scenario: 'requester_cancel',
@@ -1036,29 +1018,39 @@ async function handleAccessRequestApproval(
1036
1018
  });
1037
1019
  }
1038
1020
 
1039
- // Emit guardian_decision (approved) signal
1040
- void emitNotificationSignal({
1041
- sourceEventName: 'ingress.trusted_contact.guardian_decision',
1042
- sourceChannel: approval.channel,
1043
- sourceSessionId: approval.conversationId,
1044
- assistantId,
1045
- attentionHints: {
1046
- requiresAction: false,
1047
- urgency: 'medium',
1048
- isAsyncBackground: false,
1049
- visibleInSourceNow: false,
1050
- },
1051
- contextPayload: {
1021
+ // Don't emit guardian_decision for approvals that still require code
1022
+ // verification — the guardian already received the code, and emitting
1023
+ // this signal prematurely causes the notification pipeline to deliver
1024
+ // a confusing "approved" message before the requester has verified.
1025
+ // The guardian_decision signal should only fire once access is fully granted
1026
+ // (i.e. after code consumption), which is handled in the verification path.
1027
+ if (!decisionResult.verificationSessionId) {
1028
+ void emitNotificationSignal({
1029
+ sourceEventName: 'ingress.trusted_contact.guardian_decision',
1052
1030
  sourceChannel: approval.channel,
1053
- requesterExternalUserId: approval.requesterExternalUserId,
1054
- requesterChatId: approval.requesterChatId,
1055
- decidedByExternalUserId,
1056
- decision: 'approved',
1057
- },
1058
- dedupeKey: `trusted-contact:guardian-decision:${approval.id}`,
1059
- });
1031
+ sourceSessionId: approval.conversationId,
1032
+ assistantId,
1033
+ attentionHints: {
1034
+ requiresAction: false,
1035
+ urgency: 'medium',
1036
+ isAsyncBackground: false,
1037
+ visibleInSourceNow: false,
1038
+ },
1039
+ contextPayload: {
1040
+ sourceChannel: approval.channel,
1041
+ requesterExternalUserId: approval.requesterExternalUserId,
1042
+ requesterChatId: approval.requesterChatId,
1043
+ decidedByExternalUserId,
1044
+ decision: 'approved',
1045
+ },
1046
+ dedupeKey: `trusted-contact:guardian-decision:${approval.id}`,
1047
+ });
1048
+ }
1060
1049
 
1061
- // Only emit verification_sent when the code was actually delivered to the guardian.
1050
+ // Emit verification_sent with visibleInSourceNow=true so the notification
1051
+ // pipeline suppresses delivery — the guardian already received the
1052
+ // verification code directly. Without this flag, the pipeline generates
1053
+ // a redundant LLM message like "Good news! Your request has been approved."
1062
1054
  if (decisionResult.verificationSessionId && codeDelivered) {
1063
1055
  void emitNotificationSignal({
1064
1056
  sourceEventName: 'ingress.trusted_contact.verification_sent',
@@ -1069,7 +1061,7 @@ async function handleAccessRequestApproval(
1069
1061
  requiresAction: false,
1070
1062
  urgency: 'low',
1071
1063
  isAsyncBackground: true,
1072
- visibleInSourceNow: false,
1064
+ visibleInSourceNow: true,
1073
1065
  },
1074
1066
  contextPayload: {
1075
1067
  sourceChannel: approval.channel,