@vellumai/assistant 0.4.9 → 0.4.11

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 (116) hide show
  1. package/ARCHITECTURE.md +24 -0
  2. package/Dockerfile +1 -1
  3. package/README.md +16 -9
  4. package/package.json +1 -1
  5. package/src/__tests__/account-registry.test.ts +1 -0
  6. package/src/__tests__/actor-token-service.test.ts +1 -0
  7. package/src/__tests__/app-builder-tool-scripts.test.ts +1 -0
  8. package/src/__tests__/asset-materialize-tool.test.ts +7 -0
  9. package/src/__tests__/asset-search-tool.test.ts +7 -0
  10. package/src/__tests__/browser-fill-credential.test.ts +1 -0
  11. package/src/__tests__/call-start-guardian-guard.test.ts +1 -0
  12. package/src/__tests__/channel-approval-routes.test.ts +29 -0
  13. package/src/__tests__/channel-guardian.test.ts +2143 -1546
  14. package/src/__tests__/channel-retry-sweep.test.ts +169 -14
  15. package/src/__tests__/claude-code-tool-profiles.test.ts +1 -0
  16. package/src/__tests__/computer-use-tools.test.ts +1 -0
  17. package/src/__tests__/contacts-tools.test.ts +1 -0
  18. package/src/__tests__/conversation-attention-telegram.test.ts +1 -0
  19. package/src/__tests__/credential-policy-validate.test.ts +97 -0
  20. package/src/__tests__/credential-security-e2e.test.ts +1 -0
  21. package/src/__tests__/credential-vault-unit.test.ts +1 -0
  22. package/src/__tests__/credential-vault.test.ts +1 -0
  23. package/src/__tests__/delete-managed-skill-tool.test.ts +1 -0
  24. package/src/__tests__/file-edit-tool.test.ts +1 -0
  25. package/src/__tests__/file-read-tool.test.ts +1 -0
  26. package/src/__tests__/file-write-tool.test.ts +1 -0
  27. package/src/__tests__/followup-tools.test.ts +1 -0
  28. package/src/__tests__/gateway-only-guard.test.ts +1 -1
  29. package/src/__tests__/guardian-control-plane-policy.test.ts +5 -4
  30. package/src/__tests__/guardian-grant-minting.test.ts +3 -0
  31. package/src/__tests__/guardian-principal-id-roundtrip.test.ts +4 -3
  32. package/src/__tests__/guardian-routing-state.test.ts +8 -0
  33. package/src/__tests__/headless-browser-interactions.test.ts +1 -0
  34. package/src/__tests__/headless-browser-navigate.test.ts +1 -0
  35. package/src/__tests__/headless-browser-read-tools.test.ts +1 -0
  36. package/src/__tests__/headless-browser-snapshot.test.ts +1 -0
  37. package/src/__tests__/host-file-edit-tool.test.ts +1 -0
  38. package/src/__tests__/host-file-read-tool.test.ts +1 -0
  39. package/src/__tests__/host-file-write-tool.test.ts +1 -0
  40. package/src/__tests__/host-shell-tool.test.ts +1 -0
  41. package/src/__tests__/lifecycle-docs-guard.test.ts +207 -0
  42. package/src/__tests__/managed-skill-lifecycle.test.ts +1 -0
  43. package/src/__tests__/media-reuse-story.e2e.test.ts +8 -0
  44. package/src/__tests__/messaging-send-tool.test.ts +1 -0
  45. package/src/__tests__/playbook-execution.test.ts +1 -0
  46. package/src/__tests__/playbook-tools.test.ts +1 -0
  47. package/src/__tests__/relay-server.test.ts +4 -0
  48. package/src/__tests__/scaffold-managed-skill-tool.test.ts +1 -0
  49. package/src/__tests__/schedule-tools.test.ts +1 -0
  50. package/src/__tests__/secret-onetime-send.test.ts +4 -0
  51. package/src/__tests__/secret-scanner-executor.test.ts +2 -0
  52. package/src/__tests__/send-notification-tool.test.ts +2 -0
  53. package/src/__tests__/shell-credential-ref.test.ts +1 -0
  54. package/src/__tests__/shell-tool-proxy-mode.test.ts +1 -0
  55. package/src/__tests__/skill-load-feature-flag.test.ts +1 -0
  56. package/src/__tests__/skill-load-tool.test.ts +1 -0
  57. package/src/__tests__/skill-script-runner-host.test.ts +1 -0
  58. package/src/__tests__/skill-script-runner-sandbox.test.ts +1 -0
  59. package/src/__tests__/skill-script-runner.test.ts +1 -0
  60. package/src/__tests__/skill-tool-factory.test.ts +1 -0
  61. package/src/__tests__/subagent-tools.test.ts +1 -1
  62. package/src/__tests__/swarm-recursion.test.ts +1 -0
  63. package/src/__tests__/swarm-session-integration.test.ts +1 -0
  64. package/src/__tests__/swarm-tool.test.ts +1 -0
  65. package/src/__tests__/task-management-tools.test.ts +1 -0
  66. package/src/__tests__/task-tools.test.ts +1 -0
  67. package/src/__tests__/terminal-tools.test.ts +1 -0
  68. package/src/__tests__/tool-approval-handler.test.ts +2 -2
  69. package/src/__tests__/tool-execution-abort-cleanup.test.ts +1 -0
  70. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +1 -0
  71. package/src/__tests__/tool-executor-lifecycle-events.test.ts +2 -0
  72. package/src/__tests__/tool-executor-shell-integration.test.ts +1 -0
  73. package/src/__tests__/tool-executor.test.ts +1 -0
  74. package/src/__tests__/trust-context-guards.test.ts +218 -0
  75. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +6 -0
  76. package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +6 -0
  77. package/src/__tests__/trusted-contact-multichannel.test.ts +1 -0
  78. package/src/__tests__/trusted-contact-verification.test.ts +1 -0
  79. package/src/__tests__/view-image-tool.test.ts +1 -0
  80. package/src/calls/guardian-dispatch.ts +4 -4
  81. package/src/cli/mcp.ts +183 -3
  82. package/src/config/bundled-skills/agentmail/SKILL.md +4 -4
  83. package/src/config/bundled-skills/google-oauth-setup/SKILL.md +1 -0
  84. package/src/config/bundled-skills/phone-calls/SKILL.md +17 -119
  85. package/src/config/system-prompt.ts +4 -2
  86. package/src/config/vellum-skills/twilio-setup/SKILL.md +1 -1
  87. package/src/daemon/computer-use-session.ts +1 -0
  88. package/src/daemon/session-agent-loop.ts +1 -1
  89. package/src/daemon/session-memory.ts +2 -2
  90. package/src/daemon/session-runtime-assembly.ts +2 -2
  91. package/src/daemon/session-tool-setup.ts +1 -1
  92. package/src/mcp/client.ts +55 -6
  93. package/src/mcp/manager.ts +9 -0
  94. package/src/mcp/mcp-oauth-provider.ts +347 -0
  95. package/src/memory/channel-delivery-store.ts +1 -0
  96. package/src/memory/db-init.ts +4 -0
  97. package/src/memory/delivery-status.ts +43 -0
  98. package/src/memory/guardian-bindings.ts +3 -3
  99. package/src/memory/migrations/127-guardian-principal-id-not-null.ts +108 -0
  100. package/src/memory/migrations/index.ts +1 -0
  101. package/src/memory/migrations/registry.ts +6 -0
  102. package/src/memory/schema.ts +1 -1
  103. package/src/runtime/actor-trust-resolver.ts +13 -4
  104. package/src/runtime/channel-retry-sweep.ts +31 -14
  105. package/src/runtime/guardian-context-resolver.ts +25 -64
  106. package/src/runtime/guardian-outbound-actions.ts +399 -108
  107. package/src/runtime/guardian-vellum-migration.ts +1 -23
  108. package/src/runtime/guardian-verification-templates.ts +66 -30
  109. package/src/runtime/local-actor-identity.ts +4 -6
  110. package/src/runtime/middleware/actor-token.ts +2 -8
  111. package/src/runtime/routes/channel-route-shared.ts +0 -1
  112. package/src/runtime/routes/inbound-message-handler.ts +3 -4
  113. package/src/runtime/tool-grant-request-helper.ts +1 -1
  114. package/src/tools/credentials/policy-validate.ts +22 -0
  115. package/src/tools/guardian-control-plane-policy.ts +2 -2
  116. package/src/tools/types.ts +1 -1
@@ -5,22 +5,17 @@
5
5
  * 'vellum' channel with a guardianPrincipalId. This is required for
6
6
  * the identity-bound hatch bootstrap flow.
7
7
  *
8
- * - If a vellum binding already exists with a guardianPrincipalId, no-op.
9
- * - If a vellum binding exists but lacks guardianPrincipalId, backfill it
10
- * from the binding's guardianExternalUserId.
8
+ * - If a vellum binding already exists, returns its guardianPrincipalId.
11
9
  * - If no vellum binding exists, creates one with a fresh principal.
12
10
  * - Preserves existing guardian bindings for other channels unchanged.
13
11
  */
14
12
 
15
- import { eq } from 'drizzle-orm';
16
13
  import { v4 as uuid } from 'uuid';
17
14
 
18
- import { getDb } from '../memory/db.js';
19
15
  import {
20
16
  createBinding,
21
17
  getActiveBinding,
22
18
  } from '../memory/guardian-bindings.js';
23
- import { channelGuardianBindings } from '../memory/schema.js';
24
19
  import { getLogger } from '../util/logger.js';
25
20
  import { DAEMON_INTERNAL_ASSISTANT_ID } from './assistant-scope.js';
26
21
 
@@ -36,23 +31,6 @@ const log = getLogger('guardian-vellum-migration');
36
31
  export function ensureVellumGuardianBinding(assistantId: string = DAEMON_INTERNAL_ASSISTANT_ID): string {
37
32
  const existing = getActiveBinding(assistantId, 'vellum');
38
33
  if (existing) {
39
- // If the binding exists but is missing guardianPrincipalId, backfill it
40
- // from the binding's guardianExternalUserId (the canonical identity).
41
- if (!existing.guardianPrincipalId) {
42
- const principalId = existing.guardianExternalUserId;
43
- const db = getDb();
44
- db.update(channelGuardianBindings)
45
- .set({ guardianPrincipalId: principalId, updatedAt: Date.now() })
46
- .where(eq(channelGuardianBindings.id, existing.id))
47
- .run();
48
-
49
- log.info(
50
- { assistantId, guardianPrincipalId: principalId },
51
- 'Backfilled guardianPrincipalId on existing vellum binding',
52
- );
53
- return principalId;
54
- }
55
-
56
34
  log.debug(
57
35
  { assistantId, guardianPrincipalId: existing.guardianPrincipalId },
58
36
  'Vellum guardian binding already exists with principal',
@@ -12,43 +12,50 @@
12
12
 
13
13
  export const GUARDIAN_VERIFY_TEMPLATE_KEYS = {
14
14
  /** Initial outbound SMS with verification code. */
15
- CHALLENGE_REQUEST: 'guardian_verify.sms.challenge_request',
15
+ CHALLENGE_REQUEST: "guardian_verify.sms.challenge_request",
16
16
  /** Resend SMS with verification code. */
17
- RESEND: 'guardian_verify.sms.resend',
17
+ RESEND: "guardian_verify.sms.resend",
18
18
  /** Response when the user is already verified. */
19
- ALREADY_VERIFIED: 'guardian_verify.already_verified',
19
+ ALREADY_VERIFIED: "guardian_verify.already_verified",
20
20
  /** Initial outbound Telegram verification prompt (code is not included). */
21
- TELEGRAM_CHALLENGE_REQUEST: 'guardian_verify.telegram.challenge_request',
21
+ TELEGRAM_CHALLENGE_REQUEST: "guardian_verify.telegram.challenge_request",
22
22
  /** Resend Telegram verification prompt (code is not included). */
23
- TELEGRAM_RESEND: 'guardian_verify.telegram.resend',
23
+ TELEGRAM_RESEND: "guardian_verify.telegram.resend",
24
+ /** Initial outbound Slack DM verification prompt. */
25
+ SLACK_CHALLENGE_REQUEST: "guardian_verify.slack.challenge_request",
26
+ /** Resend Slack DM verification prompt. */
27
+ SLACK_RESEND: "guardian_verify.slack.resend",
24
28
  /** Outbound voice call intro prompt: asks guardian to enter verification code via keypad. */
25
- VOICE_CALL_INTRO: 'guardian_verify.voice.call_intro',
29
+ VOICE_CALL_INTRO: "guardian_verify.voice.call_intro",
26
30
  /** Voice retry prompt after an incorrect code entry. */
27
- VOICE_RETRY: 'guardian_verify.voice.retry',
31
+ VOICE_RETRY: "guardian_verify.voice.retry",
28
32
  /** Voice success prompt after successful verification. */
29
- VOICE_SUCCESS: 'guardian_verify.voice.success',
33
+ VOICE_SUCCESS: "guardian_verify.voice.success",
30
34
  /** Voice failure prompt after too many incorrect attempts. */
31
- VOICE_FAILURE: 'guardian_verify.voice.failure',
35
+ VOICE_FAILURE: "guardian_verify.voice.failure",
32
36
  /** Deterministic reply after successful channel verification command. */
33
- CHANNEL_VERIFY_SUCCESS: 'guardian_verify.channel.success',
37
+ CHANNEL_VERIFY_SUCCESS: "guardian_verify.channel.success",
34
38
  /** Deterministic reply after failed channel verification command. */
35
- CHANNEL_VERIFY_FAILED: 'guardian_verify.channel.failed',
39
+ CHANNEL_VERIFY_FAILED: "guardian_verify.channel.failed",
36
40
  /** Deterministic reply for bootstrap deep-link success. */
37
- CHANNEL_BOOTSTRAP_BOUND: 'guardian_verify.channel.bootstrap_bound',
41
+ CHANNEL_BOOTSTRAP_BOUND: "guardian_verify.channel.bootstrap_bound",
38
42
  /** Deterministic reply after successful trusted contact verification. */
39
- CHANNEL_TRUSTED_CONTACT_VERIFY_SUCCESS: 'guardian_verify.channel.trusted_contact_success',
43
+ CHANNEL_TRUSTED_CONTACT_VERIFY_SUCCESS:
44
+ "guardian_verify.channel.trusted_contact_success",
40
45
  } as const;
41
46
 
42
47
  export type GuardianVerifyTemplateKey =
43
48
  (typeof GUARDIAN_VERIFY_TEMPLATE_KEYS)[keyof typeof GUARDIAN_VERIFY_TEMPLATE_KEYS];
44
49
 
45
- /** Template keys for SMS/Telegram text-based verification messages. */
50
+ /** Template keys for SMS/Telegram/Slack text-based verification messages. */
46
51
  type TextVerifyTemplateKey =
47
52
  | typeof GUARDIAN_VERIFY_TEMPLATE_KEYS.CHALLENGE_REQUEST
48
53
  | typeof GUARDIAN_VERIFY_TEMPLATE_KEYS.RESEND
49
54
  | typeof GUARDIAN_VERIFY_TEMPLATE_KEYS.ALREADY_VERIFIED
50
55
  | typeof GUARDIAN_VERIFY_TEMPLATE_KEYS.TELEGRAM_CHALLENGE_REQUEST
51
- | typeof GUARDIAN_VERIFY_TEMPLATE_KEYS.TELEGRAM_RESEND;
56
+ | typeof GUARDIAN_VERIFY_TEMPLATE_KEYS.TELEGRAM_RESEND
57
+ | typeof GUARDIAN_VERIFY_TEMPLATE_KEYS.SLACK_CHALLENGE_REQUEST
58
+ | typeof GUARDIAN_VERIFY_TEMPLATE_KEYS.SLACK_RESEND;
52
59
 
53
60
  /** Template keys for deterministic channel verification reply messages. */
54
61
  export type ChannelVerifyReplyTemplateKey =
@@ -81,25 +88,36 @@ export interface ChannelVerifyReplyVars {
81
88
  // Template Composers
82
89
  // ---------------------------------------------------------------------------
83
90
 
84
- const templates: Record<TextVerifyTemplateKey, (vars: GuardianVerifyTemplateVars) => string> = {
91
+ const templates: Record<
92
+ TextVerifyTemplateKey,
93
+ (vars: GuardianVerifyTemplateVars) => string
94
+ > = {
85
95
  [GUARDIAN_VERIFY_TEMPLATE_KEYS.CHALLENGE_REQUEST]: (_vars) => {
86
- return 'Vellum assistant guardian verification requested. Reply with the 6-digit code you were given.';
96
+ return "Vellum assistant guardian verification requested. Reply with the 6-digit code you were given.";
87
97
  },
88
98
 
89
99
  [GUARDIAN_VERIFY_TEMPLATE_KEYS.RESEND]: (_vars) => {
90
- return 'Vellum assistant guardian verification requested. Reply with the 6-digit code you were given. (resent)';
100
+ return "Vellum assistant guardian verification requested. Reply with the 6-digit code you were given. (resent)";
91
101
  },
92
102
 
93
103
  [GUARDIAN_VERIFY_TEMPLATE_KEYS.ALREADY_VERIFIED]: (_vars) => {
94
- return 'This channel is already verified. No further action is needed.';
104
+ return "This channel is already verified. No further action is needed.";
95
105
  },
96
106
 
97
107
  [GUARDIAN_VERIFY_TEMPLATE_KEYS.TELEGRAM_CHALLENGE_REQUEST]: (_vars) => {
98
- return 'Vellum assistant guardian verification requested. Reply with the 6-digit code you were given.';
108
+ return "Vellum assistant guardian verification requested. Reply with the 6-digit code you were given.";
99
109
  },
100
110
 
101
111
  [GUARDIAN_VERIFY_TEMPLATE_KEYS.TELEGRAM_RESEND]: (_vars) => {
102
- return 'Vellum assistant guardian verification requested. Reply with the 6-digit code you were given. (resent)';
112
+ return "Vellum assistant guardian verification requested. Reply with the 6-digit code you were given. (resent)";
113
+ },
114
+
115
+ [GUARDIAN_VERIFY_TEMPLATE_KEYS.SLACK_CHALLENGE_REQUEST]: (_vars) => {
116
+ return "Vellum assistant guardian verification requested. Reply with the 6-digit code you were given.";
117
+ },
118
+
119
+ [GUARDIAN_VERIFY_TEMPLATE_KEYS.SLACK_RESEND]: (_vars) => {
120
+ return "Vellum assistant guardian verification requested. Reply with the 6-digit code you were given. (resent)";
103
121
  },
104
122
  };
105
123
 
@@ -115,6 +133,18 @@ export function composeVerificationSms(
115
133
  return composer(vars);
116
134
  }
117
135
 
136
+ /**
137
+ * Compose an outbound verification Slack DM from a template key and typed variables.
138
+ * Returns plain string content suitable for Slack delivery.
139
+ */
140
+ export function composeVerificationSlack(
141
+ templateKey: TextVerifyTemplateKey,
142
+ vars: GuardianVerifyTemplateVars,
143
+ ): string {
144
+ const composer = templates[templateKey];
145
+ return composer(vars);
146
+ }
147
+
118
148
  /**
119
149
  * Compose an outbound verification Telegram message from a template key and typed variables.
120
150
  * Returns plain string content suitable for Telegram delivery.
@@ -137,18 +167,21 @@ type VoiceTemplateKey =
137
167
  | typeof GUARDIAN_VERIFY_TEMPLATE_KEYS.VOICE_SUCCESS
138
168
  | typeof GUARDIAN_VERIFY_TEMPLATE_KEYS.VOICE_FAILURE;
139
169
 
140
- const voiceTemplates: Record<VoiceTemplateKey, (vars: GuardianVerifyVoiceTemplateVars) => string> = {
170
+ const voiceTemplates: Record<
171
+ VoiceTemplateKey,
172
+ (vars: GuardianVerifyVoiceTemplateVars) => string
173
+ > = {
141
174
  [GUARDIAN_VERIFY_TEMPLATE_KEYS.VOICE_CALL_INTRO]: (vars) =>
142
175
  `You are receiving a guardian verification call for your Vellum assistant. Please enter your ${vars.codeDigits}-digit verification code using your keypad.`,
143
176
 
144
177
  [GUARDIAN_VERIFY_TEMPLATE_KEYS.VOICE_RETRY]: (_vars) =>
145
- 'That code was incorrect. Please try again.',
178
+ "That code was incorrect. Please try again.",
146
179
 
147
180
  [GUARDIAN_VERIFY_TEMPLATE_KEYS.VOICE_SUCCESS]: (_vars) =>
148
- 'Verification successful. Thank you. Goodbye.',
181
+ "Verification successful. Thank you. Goodbye.",
149
182
 
150
183
  [GUARDIAN_VERIFY_TEMPLATE_KEYS.VOICE_FAILURE]: (_vars) =>
151
- 'Too many incorrect attempts. Goodbye.',
184
+ "Too many incorrect attempts. Goodbye.",
152
185
  };
153
186
 
154
187
  /**
@@ -167,18 +200,21 @@ export function composeVerificationVoice(
167
200
  // Channel Verification Reply Templates (deterministic, non-agent)
168
201
  // ---------------------------------------------------------------------------
169
202
 
170
- const channelVerifyReplyTemplates: Record<ChannelVerifyReplyTemplateKey, (vars: ChannelVerifyReplyVars) => string> = {
203
+ const channelVerifyReplyTemplates: Record<
204
+ ChannelVerifyReplyTemplateKey,
205
+ (vars: ChannelVerifyReplyVars) => string
206
+ > = {
171
207
  [GUARDIAN_VERIFY_TEMPLATE_KEYS.CHANNEL_VERIFY_SUCCESS]: () =>
172
- 'Verification successful. You are now set as the guardian for this channel.',
208
+ "Verification successful. You are now set as the guardian for this channel.",
173
209
 
174
210
  [GUARDIAN_VERIFY_TEMPLATE_KEYS.CHANNEL_VERIFY_FAILED]: (vars) =>
175
- vars.failureReason ?? 'The verification code is invalid or has expired.',
211
+ vars.failureReason ?? "The verification code is invalid or has expired.",
176
212
 
177
213
  [GUARDIAN_VERIFY_TEMPLATE_KEYS.CHANNEL_BOOTSTRAP_BOUND]: () =>
178
- 'Welcome! Your identity has been linked. Please check for a verification code message.',
214
+ "Welcome! Your identity has been linked. Please check for a verification code message.",
179
215
 
180
216
  [GUARDIAN_VERIFY_TEMPLATE_KEYS.CHANNEL_TRUSTED_CONTACT_VERIFY_SUCCESS]: () =>
181
- 'Verification successful! You can now message the assistant.',
217
+ "Verification successful! You can now message the assistant.",
182
218
  };
183
219
 
184
220
  /**
@@ -16,10 +16,7 @@ import type { GuardianRuntimeContext } from '../daemon/session-runtime-assembly.
16
16
  import { getActiveBinding } from '../memory/guardian-bindings.js';
17
17
  import { getLogger } from '../util/logger.js';
18
18
  import { DAEMON_INTERNAL_ASSISTANT_ID } from './assistant-scope.js';
19
- import {
20
- resolveGuardianContext,
21
- toGuardianRuntimeContext,
22
- } from './guardian-context-resolver.js';
19
+ import { resolveGuardianContext } from './guardian-context-resolver.js';
23
20
  import { ensureVellumGuardianBinding } from './guardian-vellum-migration.js';
24
21
 
25
22
  const log = getLogger('local-actor-identity');
@@ -58,7 +55,8 @@ export function resolveLocalIpcGuardianContext(
58
55
  conversationExternalId: 'local',
59
56
  actorExternalId: principalId,
60
57
  });
61
- return toGuardianRuntimeContext(sourceChannel, guardianCtx);
58
+ // Overlay the caller's actual sourceChannel onto the resolved context.
59
+ return { ...guardianCtx, sourceChannel };
62
60
  }
63
61
 
64
62
  const guardianPrincipalId = binding.guardianExternalUserId;
@@ -78,5 +76,5 @@ export function resolveLocalIpcGuardianContext(
78
76
 
79
77
  // Overlay the caller's actual sourceChannel onto the resolved context
80
78
  // so downstream consumers see the correct channel provenance.
81
- return toGuardianRuntimeContext(sourceChannel, guardianCtx);
79
+ return { ...guardianCtx, sourceChannel };
82
80
  }
@@ -14,17 +14,13 @@
14
14
  * guardian context pathway when no actor token is present.
15
15
  */
16
16
 
17
- import type { ChannelId } from '../../channels/types.js';
18
17
  import type { GuardianRuntimeContext } from '../../daemon/session-runtime-assembly.js';
19
18
  import { getActiveBinding } from '../../memory/guardian-bindings.js';
20
19
  import { getLogger } from '../../util/logger.js';
21
20
  import { type ActorTokenClaims, hashToken, verifyActorToken } from '../actor-token-service.js';
22
21
  import { findActiveByTokenHash } from '../actor-token-store.js';
23
22
  import { DAEMON_INTERNAL_ASSISTANT_ID } from '../assistant-scope.js';
24
- import {
25
- resolveGuardianContext,
26
- toGuardianRuntimeContext,
27
- } from '../guardian-context-resolver.js';
23
+ import { resolveGuardianContext } from '../guardian-context-resolver.js';
28
24
  import { resolveLocalIpcGuardianContext } from '../local-actor-identity.js';
29
25
 
30
26
  const log = getLogger('actor-token-middleware');
@@ -120,12 +116,10 @@ export function verifyHttpActorToken(req: Request): ActorTokenVerification {
120
116
  actorExternalId: claims.guardianPrincipalId,
121
117
  });
122
118
 
123
- const guardianContext = toGuardianRuntimeContext('vellum' as ChannelId, guardianCtx);
124
-
125
119
  return {
126
120
  ok: true,
127
121
  claims,
128
- guardianContext,
122
+ guardianContext: guardianCtx,
129
123
  };
130
124
  }
131
125
 
@@ -12,7 +12,6 @@ import type {
12
12
  } from '../channel-approval-types.js';
13
13
  import type { DenialReason } from '../guardian-context-resolver.js';
14
14
  export type { ActorTrustClass, DenialReason, GuardianContext } from '../guardian-context-resolver.js';
15
- export { toGuardianRuntimeContext } from '../guardian-context-resolver.js';
16
15
 
17
16
  /** Canonicalize assistantId for channel ingress paths. */
18
17
  export function canonicalChannelAssistantId(_assistantId: string): string {
@@ -70,7 +70,6 @@ import {
70
70
  GUARDIAN_APPROVAL_TTL_MS,
71
71
  type GuardianContext,
72
72
  stripVerificationFailurePrefix,
73
- toGuardianRuntimeContext,
74
73
  verifyGatewayOrigin,
75
74
  } from './channel-route-shared.js';
76
75
  import { handleApprovalInterception } from './guardian-approval-interception.js';
@@ -628,7 +627,7 @@ export async function handleChannelInbound(
628
627
  conversationId: result.conversationId,
629
628
  requesterExternalUserId: canonicalSenderId ?? rawSenderId ?? undefined,
630
629
  guardianExternalUserId: binding.guardianExternalUserId,
631
- guardianPrincipalId: binding.guardianPrincipalId ?? undefined,
630
+ guardianPrincipalId: binding.guardianPrincipalId,
632
631
  toolName: 'ingress_message',
633
632
  questionText: 'Ingress policy requires guardian approval',
634
633
  expiresAt: new Date(Date.now() + GUARDIAN_APPROVAL_TTL_MS).toISOString(),
@@ -1141,7 +1140,7 @@ export async function handleChannelInbound(
1141
1140
  senderName: body.actorDisplayName,
1142
1141
  senderExternalUserId: body.actorExternalId,
1143
1142
  senderUsername: body.actorUsername,
1144
- guardianCtx: toGuardianRuntimeContext(sourceChannel, guardianCtx),
1143
+ guardianCtx,
1145
1144
  replyCallbackUrl,
1146
1145
  assistantId: canonicalAssistantId,
1147
1146
  });
@@ -1687,7 +1686,7 @@ function processChannelMessageInBackground(params: BackgroundProcessingParams):
1687
1686
  uxBrief: metadataUxBrief,
1688
1687
  },
1689
1688
  assistantId,
1690
- guardianContext: toGuardianRuntimeContext(sourceChannel, guardianCtx),
1689
+ guardianContext: guardianCtx,
1691
1690
  isInteractive: resolveRoutingState(guardianCtx).promptWaitingAllowed,
1692
1691
  ...(cmdIntent ? { commandIntent: cmdIntent } : {}),
1693
1692
  },
@@ -123,7 +123,7 @@ export function createOrReuseToolGrantRequest(
123
123
  requesterExternalUserId,
124
124
  requesterChatId: requesterChatId ?? undefined,
125
125
  guardianExternalUserId: binding.guardianExternalUserId,
126
- guardianPrincipalId: binding.guardianPrincipalId ?? undefined,
126
+ guardianPrincipalId: binding.guardianPrincipalId,
127
127
  toolName,
128
128
  inputDigest,
129
129
  questionText,
@@ -8,6 +8,20 @@
8
8
 
9
9
  import type { CredentialPolicy, CredentialPolicyInput } from './policy-types.js';
10
10
 
11
+ /**
12
+ * Host tools that must never appear in credential allowed_tools.
13
+ *
14
+ * Credentials should only be accessible from sandboxed tools, not from
15
+ * host-level tools that execute directly on the user's machine.
16
+ * Map each blocked tool to its safe sandboxed equivalent.
17
+ */
18
+ const BLOCKED_HOST_TOOLS: ReadonlyMap<string, string> = new Map([
19
+ ['host_bash', 'bash'],
20
+ ['host_file_read', 'file_read'],
21
+ ['host_file_write', 'file_write'],
22
+ ['host_file_edit', 'file_edit'],
23
+ ]);
24
+
11
25
  export interface ValidationResult {
12
26
  valid: boolean;
13
27
  errors: string[];
@@ -29,6 +43,14 @@ export function validatePolicyInput(input: CredentialPolicyInput): ValidationRes
29
43
  const tool = input.allowed_tools[i];
30
44
  if (typeof tool !== 'string' || tool.trim().length === 0) {
31
45
  errors.push(`allowed_tools[${i}] must be a non-empty string`);
46
+ } else {
47
+ const replacement = BLOCKED_HOST_TOOLS.get(tool);
48
+ if (replacement !== undefined) {
49
+ errors.push(
50
+ `allowed_tools[${i}] "${tool}" is a host tool and cannot be used for credential access. ` +
51
+ `Use "${replacement}" instead.`,
52
+ );
53
+ }
32
54
  }
33
55
  }
34
56
  }
@@ -124,13 +124,13 @@ export function isGuardianControlPlaneInvocation(
124
124
  export function enforceGuardianOnlyPolicy(
125
125
  toolName: string,
126
126
  input: Record<string, unknown>,
127
- trustClass: string | undefined,
127
+ trustClass: string,
128
128
  ): { denied: boolean; reason?: string } {
129
129
  if (!isGuardianControlPlaneInvocation(toolName, input)) {
130
130
  return { denied: false };
131
131
  }
132
132
 
133
- if (trustClass === 'guardian' || trustClass === undefined) {
133
+ if (trustClass === 'guardian') {
134
134
  return { denied: false };
135
135
  }
136
136
 
@@ -138,7 +138,7 @@ export interface ToolContext {
138
138
  /** Optional principal identifier propagated to sub-tool confirmation flows. */
139
139
  principal?: string;
140
140
  /** Inbound trust classification for the session — used by trust/policy gates. */
141
- guardianTrustClass?: 'guardian' | 'trusted_contact' | 'unknown';
141
+ guardianTrustClass: 'guardian' | 'trusted_contact' | 'unknown';
142
142
  /** Channel through which the tool invocation originates (e.g. 'telegram', 'voice'). Used for scoped grant consumption. */
143
143
  executionChannel?: string;
144
144
  /** Voice/call session ID, if the invocation originates from a call. Used for scoped grant consumption. */