@vellumai/assistant 0.4.45 → 0.4.48

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 (236) hide show
  1. package/ARCHITECTURE.md +6 -6
  2. package/docs/architecture/memory.md +1 -1
  3. package/docs/architecture/scheduling.md +2 -3
  4. package/docs/architecture/security.md +5 -5
  5. package/docs/trusted-contact-access.md +5 -6
  6. package/package.json +4 -1
  7. package/src/__tests__/avatar-e2e.test.ts +18 -219
  8. package/src/__tests__/avatar-generator.test.ts +5 -57
  9. package/src/__tests__/browser-fill-credential.test.ts +5 -2
  10. package/src/__tests__/bundled-skill-retrieval-guard.test.ts +2 -1
  11. package/src/__tests__/channel-readiness-routes.test.ts +20 -19
  12. package/src/__tests__/cli.test.ts +23 -0
  13. package/src/__tests__/credential-broker-browser-fill.test.ts +23 -22
  14. package/src/__tests__/credential-broker-server-use.test.ts +22 -21
  15. package/src/__tests__/credential-broker.test.ts +2 -1
  16. package/src/__tests__/credential-metadata-store.test.ts +240 -18
  17. package/src/__tests__/credential-resolve.test.ts +5 -4
  18. package/src/__tests__/credential-security-e2e.test.ts +8 -8
  19. package/src/__tests__/credential-security-invariants.test.ts +104 -7
  20. package/src/__tests__/credential-vault-unit.test.ts +22 -20
  21. package/src/__tests__/credential-vault.test.ts +284 -12
  22. package/src/__tests__/credentials-cli.test.ts +11 -6
  23. package/src/__tests__/gateway-only-enforcement.test.ts +4 -2
  24. package/src/__tests__/gemini-image-service.test.ts +75 -45
  25. package/src/__tests__/gemini-provider.test.ts +9 -6
  26. package/src/__tests__/guardian-action-conversation-turn.test.ts +1 -33
  27. package/src/__tests__/guardian-action-copy-generator.test.ts +0 -20
  28. package/src/__tests__/guardian-action-followup-executor.test.ts +1 -28
  29. package/src/__tests__/guardian-action-followup-store.test.ts +1 -1
  30. package/src/__tests__/guardian-grant-minting.test.ts +35 -0
  31. package/src/__tests__/integration-status.test.ts +53 -21
  32. package/src/__tests__/managed-proxy-context.test.ts +5 -3
  33. package/src/__tests__/media-generate-image.test.ts +63 -2
  34. package/src/__tests__/media-reuse-story.e2e.test.ts +7 -3
  35. package/src/__tests__/messaging-send-tool.test.ts +4 -6
  36. package/src/__tests__/provider-fail-open-selection.test.ts +3 -1
  37. package/src/__tests__/provider-managed-proxy-integration.test.ts +70 -6
  38. package/src/__tests__/schedule-store.test.ts +1 -1
  39. package/src/__tests__/schema-transforms.test.ts +226 -0
  40. package/src/__tests__/script-proxy-injection-runtime.test.ts +23 -13
  41. package/src/__tests__/script-proxy-policy-runtime.test.ts +1 -1
  42. package/src/__tests__/script-proxy-session-manager.test.ts +1 -1
  43. package/src/__tests__/secret-onetime-send.test.ts +5 -3
  44. package/src/__tests__/session-messaging-secret-redirect.test.ts +5 -4
  45. package/src/__tests__/skills-uninstall.test.ts +2 -2
  46. package/src/__tests__/skills.test.ts +0 -9
  47. package/src/__tests__/slack-channel-config.test.ts +9 -8
  48. package/src/__tests__/slack-share-routes.test.ts +11 -6
  49. package/src/__tests__/telegram-bot-username-resolution.test.ts +3 -0
  50. package/src/__tests__/twilio-config.test.ts +2 -1
  51. package/src/__tests__/twilio-provider.test.ts +4 -2
  52. package/src/__tests__/twilio-routes.test.ts +5 -4
  53. package/src/__tests__/verification-control-plane-policy.test.ts +1 -1
  54. package/src/approvals/AGENTS.md +1 -1
  55. package/src/calls/call-domain.ts +7 -4
  56. package/src/calls/twilio-config.ts +2 -1
  57. package/src/calls/twilio-provider.ts +2 -1
  58. package/src/calls/twilio-rest.ts +2 -2
  59. package/src/cli/commands/browser-relay.ts +40 -15
  60. package/src/cli/commands/credentials.ts +9 -8
  61. package/src/cli/commands/oauth.ts +1 -1
  62. package/src/cli.ts +3 -2
  63. package/src/config/bundled-skills/claude-code/TOOLS.json +0 -4
  64. package/src/config/bundled-skills/contacts/tools/google-contacts.ts +29 -32
  65. package/src/config/bundled-skills/gmail/SKILL.md +4 -4
  66. package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +54 -61
  67. package/src/config/bundled-skills/gmail/tools/gmail-attachments.ts +25 -28
  68. package/src/config/bundled-skills/gmail/tools/gmail-draft.ts +14 -17
  69. package/src/config/bundled-skills/gmail/tools/gmail-filters.ts +39 -44
  70. package/src/config/bundled-skills/gmail/tools/gmail-follow-up.ts +61 -58
  71. package/src/config/bundled-skills/gmail/tools/gmail-forward.ts +50 -49
  72. package/src/config/bundled-skills/gmail/tools/gmail-label.ts +11 -13
  73. package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +148 -146
  74. package/src/config/bundled-skills/gmail/tools/gmail-send-draft.ts +4 -7
  75. package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +175 -173
  76. package/src/config/bundled-skills/gmail/tools/gmail-trash.ts +4 -7
  77. package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +71 -76
  78. package/src/config/bundled-skills/gmail/tools/gmail-vacation.ts +32 -38
  79. package/src/config/bundled-skills/google-calendar/SKILL.md +2 -2
  80. package/src/config/bundled-skills/google-calendar/calendar-client.ts +70 -29
  81. package/src/config/bundled-skills/google-calendar/tools/calendar-check-availability.ts +9 -10
  82. package/src/config/bundled-skills/google-calendar/tools/calendar-create-event.ts +5 -6
  83. package/src/config/bundled-skills/google-calendar/tools/calendar-get-event.ts +4 -5
  84. package/src/config/bundled-skills/google-calendar/tools/calendar-list-events.ts +14 -15
  85. package/src/config/bundled-skills/google-calendar/tools/calendar-rsvp.ts +37 -37
  86. package/src/config/bundled-skills/google-calendar/tools/shared.ts +4 -9
  87. package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +24 -3
  88. package/src/config/bundled-skills/messaging/SKILL.md +6 -6
  89. package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +62 -63
  90. package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +15 -16
  91. package/src/config/bundled-skills/messaging/tools/messaging-auth-test.ts +4 -5
  92. package/src/config/bundled-skills/messaging/tools/messaging-list-conversations.ts +6 -7
  93. package/src/config/bundled-skills/messaging/tools/messaging-mark-read.ts +4 -5
  94. package/src/config/bundled-skills/messaging/tools/messaging-read.ts +14 -15
  95. package/src/config/bundled-skills/messaging/tools/messaging-search.ts +4 -5
  96. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +128 -128
  97. package/src/config/bundled-skills/messaging/tools/messaging-sender-digest.ts +33 -34
  98. package/src/config/bundled-skills/messaging/tools/shared.ts +11 -11
  99. package/src/config/bundled-skills/notifications/SKILL.md +1 -1
  100. package/src/config/bundled-skills/phone-calls/SKILL.md +5 -5
  101. package/src/config/bundled-skills/schedule/SKILL.md +1 -1
  102. package/src/config/bundled-skills/skill-management/SKILL.md +1 -1
  103. package/src/config/bundled-skills/slack/tools/shared.ts +4 -10
  104. package/src/config/bundled-skills/slack/tools/slack-add-reaction.ts +4 -5
  105. package/src/config/bundled-skills/slack/tools/slack-channel-details.ts +15 -16
  106. package/src/config/bundled-skills/slack/tools/slack-delete-message.ts +4 -5
  107. package/src/config/bundled-skills/slack/tools/slack-edit-message.ts +4 -5
  108. package/src/config/bundled-skills/slack/tools/slack-leave-channel.ts +4 -5
  109. package/src/config/bundled-skills/slack/tools/slack-scan-digest.ts +95 -92
  110. package/src/config/loader.ts +6 -0
  111. package/src/daemon/computer-use-session.ts +7 -1
  112. package/src/daemon/guardian-action-generators.ts +4 -5
  113. package/src/daemon/handlers/config-slack-channel.ts +37 -20
  114. package/src/daemon/handlers/config-telegram.ts +33 -20
  115. package/src/daemon/lifecycle.ts +9 -1
  116. package/src/daemon/message-types/integrations.ts +1 -0
  117. package/src/daemon/ride-shotgun-handler.ts +3 -1
  118. package/src/daemon/session-messaging.ts +3 -1
  119. package/src/daemon/session-tool-setup.ts +18 -2
  120. package/src/daemon/session.ts +1 -1
  121. package/src/email/providers/index.ts +2 -1
  122. package/src/instrument.ts +15 -1
  123. package/src/media/app-icon-generator.ts +30 -4
  124. package/src/media/avatar-router.ts +28 -62
  125. package/src/media/gemini-image-service.ts +28 -2
  126. package/src/memory/canonical-guardian-store.ts +1 -1
  127. package/src/memory/guardian-action-store.ts +1 -1
  128. package/src/memory/schema/guardian.ts +1 -1
  129. package/src/messaging/provider.ts +16 -10
  130. package/src/messaging/providers/gmail/adapter.ts +40 -23
  131. package/src/messaging/providers/gmail/client.ts +203 -122
  132. package/src/messaging/providers/gmail/people-client.ts +26 -18
  133. package/src/messaging/providers/slack/adapter.ts +29 -19
  134. package/src/messaging/providers/slack/client.ts +265 -78
  135. package/src/messaging/providers/telegram-bot/adapter.ts +5 -4
  136. package/src/messaging/providers/whatsapp/adapter.ts +6 -3
  137. package/src/messaging/registry.ts +2 -1
  138. package/src/oauth/byo-connection.test.ts +436 -0
  139. package/src/oauth/byo-connection.ts +112 -0
  140. package/src/oauth/connect-orchestrator.ts +27 -0
  141. package/src/oauth/connection-resolver.ts +34 -0
  142. package/src/oauth/connection.ts +38 -0
  143. package/src/oauth/platform-connection.test.ts +163 -0
  144. package/src/oauth/platform-connection.ts +110 -0
  145. package/src/oauth/provider-base-urls.ts +21 -0
  146. package/src/oauth/provider-profiles.ts +1 -1
  147. package/src/oauth/token-persistence.ts +20 -20
  148. package/src/permissions/checker.ts +6 -1
  149. package/src/prompts/system-prompt.ts +52 -15
  150. package/src/prompts/templates/BOOTSTRAP.md +1 -1
  151. package/src/providers/gemini/client.ts +15 -6
  152. package/src/providers/managed-proxy/constants.ts +2 -2
  153. package/src/providers/managed-proxy/context.ts +5 -1
  154. package/src/providers/ratelimit.ts +17 -0
  155. package/src/providers/registry.ts +2 -2
  156. package/src/runtime/AGENTS.md +18 -1
  157. package/src/runtime/auth/route-policy.ts +1 -0
  158. package/src/runtime/channel-invite-transports/telegram.ts +2 -1
  159. package/src/runtime/channel-readiness-service.ts +168 -195
  160. package/src/runtime/channel-readiness-types.ts +4 -0
  161. package/src/runtime/guardian-action-conversation-turn.ts +1 -3
  162. package/src/runtime/guardian-action-followup-executor.ts +1 -2
  163. package/src/runtime/guardian-action-message-composer.ts +3 -23
  164. package/src/runtime/http-server.ts +9 -4
  165. package/src/runtime/http-types.ts +0 -1
  166. package/src/runtime/middleware/rate-limiter.ts +74 -20
  167. package/src/runtime/middleware/twilio-validation.ts +1 -3
  168. package/src/runtime/routes/channel-readiness-routes.ts +2 -0
  169. package/src/runtime/routes/diagnostics-routes.ts +11 -9
  170. package/src/runtime/routes/guardian-approval-interception.ts +20 -5
  171. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +71 -25
  172. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +12 -5
  173. package/src/runtime/routes/integrations/slack/share.ts +3 -2
  174. package/src/runtime/routes/integrations/twilio.ts +6 -5
  175. package/src/runtime/routes/secret-routes.ts +3 -2
  176. package/src/runtime/routes/settings-routes.ts +75 -17
  177. package/src/runtime/telegram-streaming-delivery.test.ts +132 -0
  178. package/src/runtime/telegram-streaming-delivery.ts +11 -1
  179. package/src/schedule/integration-status.ts +5 -4
  180. package/src/security/credential-key.ts +170 -0
  181. package/src/security/token-manager.ts +36 -7
  182. package/src/tools/apps/definitions.ts +0 -5
  183. package/src/tools/assets/materialize.ts +0 -5
  184. package/src/tools/assets/search.ts +0 -5
  185. package/src/tools/browser/headless-browser.ts +1 -67
  186. package/src/tools/claude-code/claude-code.ts +0 -5
  187. package/src/tools/computer-use/request-computer-control.ts +0 -5
  188. package/src/tools/credentials/broker.ts +6 -4
  189. package/src/tools/credentials/metadata-store.ts +72 -20
  190. package/src/tools/credentials/resolve.ts +2 -1
  191. package/src/tools/credentials/vault.ts +77 -16
  192. package/src/tools/filesystem/edit.ts +1 -6
  193. package/src/tools/filesystem/read.ts +0 -5
  194. package/src/tools/filesystem/write.ts +1 -6
  195. package/src/tools/host-filesystem/edit.ts +1 -6
  196. package/src/tools/host-filesystem/read.ts +1 -6
  197. package/src/tools/host-filesystem/write.ts +1 -6
  198. package/src/tools/mcp/mcp-tool-factory.ts +18 -1
  199. package/src/tools/memory/definitions.ts +0 -5
  200. package/src/tools/network/web-fetch.ts +0 -5
  201. package/src/tools/network/web-search.ts +0 -5
  202. package/src/tools/schema-transforms.ts +99 -0
  203. package/src/tools/skills/load.ts +0 -5
  204. package/src/tools/swarm/delegate.ts +0 -5
  205. package/src/tools/system/avatar-generator.ts +3 -44
  206. package/src/tools/ui-surface/definitions.ts +0 -15
  207. package/src/tools/watch/screen-watch.ts +0 -5
  208. package/src/version.ts +10 -0
  209. package/src/watcher/providers/github.ts +51 -52
  210. package/src/watcher/providers/gmail.ts +88 -80
  211. package/src/watcher/providers/google-calendar.ts +93 -86
  212. package/src/watcher/providers/linear.ts +87 -93
  213. package/src/__tests__/avatar-router.test.ts +0 -149
  214. package/src/__tests__/managed-avatar-client.test.ts +0 -337
  215. package/src/config/bundled-skills/doordash/SKILL.md +0 -170
  216. package/src/config/bundled-skills/doordash/__tests__/doordash-client.test.ts +0 -205
  217. package/src/config/bundled-skills/doordash/__tests__/doordash-session.test.ts +0 -74
  218. package/src/config/bundled-skills/doordash/doordash-cli.ts +0 -1081
  219. package/src/config/bundled-skills/doordash/doordash-entry.ts +0 -22
  220. package/src/config/bundled-skills/doordash/lib/cart-queries.ts +0 -787
  221. package/src/config/bundled-skills/doordash/lib/client.ts +0 -1069
  222. package/src/config/bundled-skills/doordash/lib/order-queries.ts +0 -85
  223. package/src/config/bundled-skills/doordash/lib/queries.ts +0 -28
  224. package/src/config/bundled-skills/doordash/lib/query-extractor.ts +0 -94
  225. package/src/config/bundled-skills/doordash/lib/search-queries.ts +0 -203
  226. package/src/config/bundled-skills/doordash/lib/session.ts +0 -96
  227. package/src/config/bundled-skills/doordash/lib/shared/errors.ts +0 -61
  228. package/src/config/bundled-skills/doordash/lib/shared/network-recorder.ts +0 -380
  229. package/src/config/bundled-skills/doordash/lib/shared/platform.ts +0 -55
  230. package/src/config/bundled-skills/doordash/lib/shared/recording-store.ts +0 -43
  231. package/src/config/bundled-skills/doordash/lib/shared/recording-types.ts +0 -49
  232. package/src/config/bundled-skills/doordash/lib/shared/truncate.ts +0 -6
  233. package/src/config/bundled-skills/doordash/lib/store-queries.ts +0 -246
  234. package/src/config/bundled-skills/doordash/lib/types.ts +0 -367
  235. package/src/media/avatar-types.ts +0 -53
  236. package/src/media/managed-avatar-client.ts +0 -225
@@ -23,6 +23,10 @@ import type {
23
23
  import { allComputerUseTools } from "../tools/computer-use/definitions.js";
24
24
  import { ToolExecutor } from "../tools/executor.js";
25
25
  import { getTool, registerSkillTools } from "../tools/registry.js";
26
+ import {
27
+ injectReasonField,
28
+ REASON_SKIP_SET,
29
+ } from "../tools/schema-transforms.js";
26
30
  import type { Tool, ToolExecutionResult } from "../tools/types.js";
27
31
  import { allUiSurfaceTools } from "../tools/ui-surface/definitions.js";
28
32
  import { getLogger } from "../util/logger.js";
@@ -581,6 +585,8 @@ export class ComputerUseSession {
581
585
  },
582
586
  };
583
587
 
588
+ const toolDefsWithReason = injectReasonField(toolDefs, REASON_SKIP_SET);
589
+
584
590
  const cuConfig = getConfig();
585
591
  const agentLoop = new AgentLoop(
586
592
  compactingProvider,
@@ -590,7 +596,7 @@ export class ComputerUseSession {
590
596
  maxInputTokens: cuConfig.contextWindow.maxInputTokens,
591
597
  toolChoice: { type: "any" },
592
598
  },
593
- toolDefs,
599
+ toolDefsWithReason,
594
600
  toolExecutor,
595
601
  );
596
602
 
@@ -83,8 +83,8 @@ const FOLLOWUP_CONVERSATION_MAX_TOKENS = 300;
83
83
  const FOLLOWUP_CONVERSATION_SYSTEM_PROMPT =
84
84
  "You are an assistant helping route a guardian's reply to a post-timeout follow-up message. " +
85
85
  "A voice caller asked a question, but the call timed out before the guardian could answer. " +
86
- "The guardian has now replied late, and was asked whether they want to call the caller back, " +
87
- "send them a text message, or skip it. " +
86
+ "The guardian has now replied late, and was asked whether they want to call the caller back " +
87
+ "or skip it. " +
88
88
  "Analyze the guardian's latest reply to determine their intent. " +
89
89
  "When uncertain, default to keep_pending and ask a clarifying question. " +
90
90
  "Always provide a natural, helpful reply along with your decision.";
@@ -101,10 +101,10 @@ const FOLLOWUP_CONVERSATION_TOOL_SCHEMA = {
101
101
  properties: {
102
102
  disposition: {
103
103
  type: "string",
104
- enum: ["call_back", "message_back", "decline", "keep_pending"],
104
+ enum: ["call_back", "decline", "keep_pending"],
105
105
  description:
106
106
  "The guardian's intent: call_back to call the original caller, " +
107
- "message_back to send a text message, decline to skip the follow-up, " +
107
+ "decline to skip the follow-up, " +
108
108
  "keep_pending if the intent is unclear (ask for clarification).",
109
109
  },
110
110
  replyText: {
@@ -118,7 +118,6 @@ const FOLLOWUP_CONVERSATION_TOOL_SCHEMA = {
118
118
 
119
119
  const VALID_FOLLOWUP_DISPOSITIONS: ReadonlySet<string> = new Set([
120
120
  "call_back",
121
- "message_back",
122
121
  "decline",
123
122
  "keep_pending",
124
123
  ]);
@@ -5,6 +5,7 @@ import {
5
5
  saveRawConfig,
6
6
  setNestedValue,
7
7
  } from "../../config/loader.js";
8
+ import { credentialKey } from "../../security/credential-key.js";
8
9
  import {
9
10
  deleteSecureKeyAsync,
10
11
  getSecureKey,
@@ -34,8 +35,12 @@ export interface SlackChannelConfigResult {
34
35
  // -- Business logic --
35
36
 
36
37
  export function getSlackChannelConfig(): SlackChannelConfigResult {
37
- const hasBotToken = !!getSecureKey("credential:slack_channel:bot_token");
38
- const hasAppToken = !!getSecureKey("credential:slack_channel:app_token");
38
+ const hasBotToken = !!getSecureKey(
39
+ credentialKey("slack_channel", "bot_token"),
40
+ );
41
+ const hasAppToken = !!getSecureKey(
42
+ credentialKey("slack_channel", "app_token"),
43
+ );
39
44
  const { teamId, teamName, botUserId, botUsername } = getConfig().slack;
40
45
  return {
41
46
  success: true,
@@ -79,10 +84,10 @@ export async function setSlackChannelConfig(
79
84
  };
80
85
  if (!data.ok) {
81
86
  const storedBotToken = !!getSecureKey(
82
- "credential:slack_channel:bot_token",
87
+ credentialKey("slack_channel", "bot_token"),
83
88
  );
84
89
  const storedAppToken = !!getSecureKey(
85
- "credential:slack_channel:app_token",
90
+ credentialKey("slack_channel", "app_token"),
86
91
  );
87
92
  return {
88
93
  success: false,
@@ -103,10 +108,10 @@ export async function setSlackChannelConfig(
103
108
  } catch (err) {
104
109
  const message = err instanceof Error ? err.message : String(err);
105
110
  const storedBotToken = !!getSecureKey(
106
- "credential:slack_channel:bot_token",
111
+ credentialKey("slack_channel", "bot_token"),
107
112
  );
108
113
  const storedAppToken = !!getSecureKey(
109
- "credential:slack_channel:app_token",
114
+ credentialKey("slack_channel", "app_token"),
110
115
  );
111
116
  return {
112
117
  success: false,
@@ -118,15 +123,15 @@ export async function setSlackChannelConfig(
118
123
  }
119
124
 
120
125
  const stored = await setSecureKeyAsync(
121
- "credential:slack_channel:bot_token",
126
+ credentialKey("slack_channel", "bot_token"),
122
127
  botToken,
123
128
  );
124
129
  if (!stored) {
125
130
  const storedBotToken = !!getSecureKey(
126
- "credential:slack_channel:bot_token",
131
+ credentialKey("slack_channel", "bot_token"),
127
132
  );
128
133
  const storedAppToken = !!getSecureKey(
129
- "credential:slack_channel:app_token",
134
+ credentialKey("slack_channel", "app_token"),
130
135
  );
131
136
  return {
132
137
  success: false,
@@ -161,10 +166,10 @@ export async function setSlackChannelConfig(
161
166
  if (appToken) {
162
167
  if (!appToken.startsWith("xapp-")) {
163
168
  const storedBotToken = !!getSecureKey(
164
- "credential:slack_channel:bot_token",
169
+ credentialKey("slack_channel", "bot_token"),
165
170
  );
166
171
  const storedAppToken = !!getSecureKey(
167
- "credential:slack_channel:app_token",
172
+ credentialKey("slack_channel", "app_token"),
168
173
  );
169
174
  return {
170
175
  success: false,
@@ -176,15 +181,15 @@ export async function setSlackChannelConfig(
176
181
  }
177
182
 
178
183
  const stored = await setSecureKeyAsync(
179
- "credential:slack_channel:app_token",
184
+ credentialKey("slack_channel", "app_token"),
180
185
  appToken,
181
186
  );
182
187
  if (!stored) {
183
188
  const storedBotToken = !!getSecureKey(
184
- "credential:slack_channel:bot_token",
189
+ credentialKey("slack_channel", "bot_token"),
185
190
  );
186
191
  const storedAppToken = !!getSecureKey(
187
- "credential:slack_channel:app_token",
192
+ credentialKey("slack_channel", "app_token"),
188
193
  );
189
194
  return {
190
195
  success: false,
@@ -198,8 +203,12 @@ export async function setSlackChannelConfig(
198
203
  upsertCredentialMetadata("slack_channel", "app_token", {});
199
204
  }
200
205
 
201
- const hasBotToken = !!getSecureKey("credential:slack_channel:bot_token");
202
- const hasAppToken = !!getSecureKey("credential:slack_channel:app_token");
206
+ const hasBotToken = !!getSecureKey(
207
+ credentialKey("slack_channel", "bot_token"),
208
+ );
209
+ const hasAppToken = !!getSecureKey(
210
+ credentialKey("slack_channel", "app_token"),
211
+ );
203
212
 
204
213
  if (hasBotToken && !hasAppToken) {
205
214
  warning =
@@ -220,12 +229,20 @@ export async function setSlackChannelConfig(
220
229
  }
221
230
 
222
231
  export async function clearSlackChannelConfig(): Promise<SlackChannelConfigResult> {
223
- const r1 = await deleteSecureKeyAsync("credential:slack_channel:bot_token");
224
- const r2 = await deleteSecureKeyAsync("credential:slack_channel:app_token");
232
+ const r1 = await deleteSecureKeyAsync(
233
+ credentialKey("slack_channel", "bot_token"),
234
+ );
235
+ const r2 = await deleteSecureKeyAsync(
236
+ credentialKey("slack_channel", "app_token"),
237
+ );
225
238
 
226
239
  if (r1 === "error" || r2 === "error") {
227
- const hasBotToken = !!getSecureKey("credential:slack_channel:bot_token");
228
- const hasAppToken = !!getSecureKey("credential:slack_channel:app_token");
240
+ const hasBotToken = !!getSecureKey(
241
+ credentialKey("slack_channel", "bot_token"),
242
+ );
243
+ const hasAppToken = !!getSecureKey(
244
+ credentialKey("slack_channel", "app_token"),
245
+ );
229
246
  return {
230
247
  success: false,
231
248
  hasBotToken,
@@ -8,6 +8,7 @@ import {
8
8
  registerCallbackRoute,
9
9
  shouldUsePlatformCallbacks,
10
10
  } from "../../inbound/platform-callback-registration.js";
11
+ import { credentialKey } from "../../security/credential-key.js";
11
12
  import {
12
13
  deleteSecureKeyAsync,
13
14
  getSecureKey,
@@ -60,8 +61,10 @@ export type TelegramConfigResult = Omit<TelegramConfigResponse, "type">;
60
61
  // -- Extracted business logic functions --
61
62
 
62
63
  export function getTelegramConfig(): TelegramConfigResult {
63
- const hasBotToken = !!getSecureKey("credential:telegram:bot_token");
64
- const hasWebhookSecret = !!getSecureKey("credential:telegram:webhook_secret");
64
+ const hasBotToken = !!getSecureKey(credentialKey("telegram", "bot_token"));
65
+ const hasWebhookSecret = !!getSecureKey(
66
+ credentialKey("telegram", "webhook_secret"),
67
+ );
65
68
  const botUsername = getTelegramBotUsername();
66
69
  return {
67
70
  success: true,
@@ -79,7 +82,7 @@ export async function setTelegramConfig(
79
82
  // Track provenance so we only rollback tokens that were freshly provided.
80
83
  const isNewToken = !!botToken;
81
84
  const resolvedToken =
82
- botToken || getSecureKey("credential:telegram:bot_token");
85
+ botToken || getSecureKey(credentialKey("telegram", "bot_token"));
83
86
  if (!resolvedToken) {
84
87
  return {
85
88
  success: false,
@@ -133,7 +136,7 @@ export async function setTelegramConfig(
133
136
 
134
137
  // Store bot token securely (async — writes broker + encrypted store)
135
138
  const stored = await setSecureKeyAsync(
136
- "credential:telegram:bot_token",
139
+ credentialKey("telegram", "bot_token"),
137
140
  resolvedToken,
138
141
  );
139
142
  if (!stored) {
@@ -156,12 +159,14 @@ export async function setTelegramConfig(
156
159
  invalidateConfigCache();
157
160
 
158
161
  // Ensure webhook secret exists (generate if missing)
159
- let hasWebhookSecret = !!getSecureKey("credential:telegram:webhook_secret");
162
+ let hasWebhookSecret = !!getSecureKey(
163
+ credentialKey("telegram", "webhook_secret"),
164
+ );
160
165
  if (!hasWebhookSecret) {
161
166
  const { randomUUID } = await import("node:crypto");
162
167
  const webhookSecret = randomUUID();
163
168
  const secretStored = await setSecureKeyAsync(
164
- "credential:telegram:webhook_secret",
169
+ credentialKey("telegram", "webhook_secret"),
165
170
  webhookSecret,
166
171
  );
167
172
  if (secretStored) {
@@ -172,7 +177,7 @@ export async function setTelegramConfig(
172
177
  // When the token came from secure storage it was already valid
173
178
  // configuration; deleting it would destroy working state.
174
179
  if (isNewToken) {
175
- await deleteSecureKeyAsync("credential:telegram:bot_token");
180
+ await deleteSecureKeyAsync(credentialKey("telegram", "bot_token"));
176
181
  deleteCredentialMetadata("telegram", "bot_token");
177
182
  }
178
183
  // Always revert the config write — the botUsername was written
@@ -222,7 +227,7 @@ export async function clearTelegramConfig(): Promise<TelegramConfigResult> {
222
227
  // The gateway reconcile short-circuits when credentials are absent,
223
228
  // so we must call the Telegram API directly while the token is still
224
229
  // available.
225
- const botToken = getSecureKey("credential:telegram:bot_token");
230
+ const botToken = getSecureKey(credentialKey("telegram", "bot_token"));
226
231
  if (botToken) {
227
232
  try {
228
233
  await fetch(`https://api.telegram.org/bot${botToken}/deleteWebhook`);
@@ -234,13 +239,15 @@ export async function clearTelegramConfig(): Promise<TelegramConfigResult> {
234
239
  }
235
240
  }
236
241
 
237
- const r1 = await deleteSecureKeyAsync("credential:telegram:bot_token");
238
- const r2 = await deleteSecureKeyAsync("credential:telegram:webhook_secret");
242
+ const r1 = await deleteSecureKeyAsync(credentialKey("telegram", "bot_token"));
243
+ const r2 = await deleteSecureKeyAsync(
244
+ credentialKey("telegram", "webhook_secret"),
245
+ );
239
246
 
240
247
  if (r1 === "error" || r2 === "error") {
241
- const hasBotToken = !!getSecureKey("credential:telegram:bot_token");
248
+ const hasBotToken = !!getSecureKey(credentialKey("telegram", "bot_token"));
242
249
  const hasWebhookSecret = !!getSecureKey(
243
- "credential:telegram:webhook_secret",
250
+ credentialKey("telegram", "webhook_secret"),
244
251
  );
245
252
  return {
246
253
  success: false,
@@ -272,7 +279,7 @@ export async function clearTelegramConfig(): Promise<TelegramConfigResult> {
272
279
  export async function setTelegramCommands(
273
280
  commands?: Array<{ command: string; description: string }>,
274
281
  ): Promise<TelegramConfigResult> {
275
- const storedToken = getSecureKey("credential:telegram:bot_token");
282
+ const storedToken = getSecureKey(credentialKey("telegram", "bot_token"));
276
283
  if (!storedToken) {
277
284
  return {
278
285
  success: false,
@@ -302,8 +309,10 @@ export async function setTelegramCommands(
302
309
  return {
303
310
  success: false,
304
311
  hasBotToken: true,
305
- connected: !!getSecureKey("credential:telegram:webhook_secret"),
306
- hasWebhookSecret: !!getSecureKey("credential:telegram:webhook_secret"),
312
+ connected: !!getSecureKey(credentialKey("telegram", "webhook_secret")),
313
+ hasWebhookSecret: !!getSecureKey(
314
+ credentialKey("telegram", "webhook_secret"),
315
+ ),
307
316
  error: `Failed to set bot commands: ${body}`,
308
317
  };
309
318
  }
@@ -312,14 +321,18 @@ export async function setTelegramCommands(
312
321
  return {
313
322
  success: false,
314
323
  hasBotToken: true,
315
- connected: !!getSecureKey("credential:telegram:webhook_secret"),
316
- hasWebhookSecret: !!getSecureKey("credential:telegram:webhook_secret"),
324
+ connected: !!getSecureKey(credentialKey("telegram", "webhook_secret")),
325
+ hasWebhookSecret: !!getSecureKey(
326
+ credentialKey("telegram", "webhook_secret"),
327
+ ),
317
328
  error: `Failed to set bot commands: ${message}`,
318
329
  };
319
330
  }
320
331
 
321
- const hasBotToken = !!getSecureKey("credential:telegram:bot_token");
322
- const hasWebhookSecret = !!getSecureKey("credential:telegram:webhook_secret");
332
+ const hasBotToken = !!getSecureKey(credentialKey("telegram", "bot_token"));
333
+ const hasWebhookSecret = !!getSecureKey(
334
+ credentialKey("telegram", "webhook_secret"),
335
+ );
323
336
  return {
324
337
  success: true,
325
338
  hasBotToken,
@@ -401,4 +414,4 @@ export async function handleTelegramConfig(
401
414
  error: message,
402
415
  });
403
416
  }
404
- }
417
+ }
@@ -54,6 +54,7 @@ import {
54
54
  import { ensureVellumGuardianBinding } from "../runtime/guardian-vellum-migration.js";
55
55
  import { RuntimeHttpServer } from "../runtime/http-server.js";
56
56
  import { startScheduler } from "../schedule/scheduler.js";
57
+ import { migrateKeys } from "../security/credential-key.js";
57
58
  import { watchSessions } from "../tools/watch/watch-state.js";
58
59
  import { getLogger, initLogger } from "../util/logger.js";
59
60
  import {
@@ -136,6 +137,11 @@ export async function runDaemon(): Promise<void> {
136
137
 
137
138
  ensureDataDir();
138
139
 
140
+ // Migrate legacy colon-delimited credential keys to the new
141
+ // slash-delimited format. Must run after ensureDataDir() so the
142
+ // secure key store is available, and before any credential reads.
143
+ migrateKeys();
144
+
139
145
  // Load (or generate + persist) the auth signing key so tokens survive
140
146
  // daemon restarts. Must happen after ensureDataDir() creates the
141
147
  // protected directory.
@@ -193,7 +199,9 @@ export async function runDaemon(): Promise<void> {
193
199
  const httpTokenPath = join(getRootDir(), "http-token");
194
200
  writeFileSync(httpTokenPath, credentials.accessToken, { mode: 0o600 });
195
201
  chmodSync(httpTokenPath, 0o600);
196
- log.info("Daemon startup: CLI edge token written to credential store and http-token");
202
+ log.info(
203
+ "Daemon startup: CLI edge token written to credential store and http-token",
204
+ );
197
205
  } else {
198
206
  log.warn("No guardian principal available — CLI edge token not written");
199
207
  }
@@ -194,6 +194,7 @@ export interface IntegrationConnectResult {
194
194
  export interface OAuthConnectResultResponse {
195
195
  type: "oauth_connect_result";
196
196
  success: boolean;
197
+ service?: string;
197
198
  grantedScopes?: string[];
198
199
  accountInfo?: string;
199
200
  error?: string;
@@ -501,13 +501,15 @@ async function finalizeLearnRecording(
501
501
  // Save cookies to the encrypted credential store (keyed by target domain)
502
502
  // so they don't need to be persisted in the plaintext recording file.
503
503
  if (session.targetDomain && cookies.length > 0) {
504
+ const { credentialKey: credKey } =
505
+ await import("../security/credential-key.js");
504
506
  const { setSecureKeyAsync } = await import("../security/secure-keys.js");
505
507
  const { upsertCredentialMetadata } =
506
508
  await import("../tools/credentials/metadata-store.js");
507
509
 
508
510
  const service = session.targetDomain;
509
511
  const field = "session:cookies";
510
- const storageKey = `credential:${service}:${field}`;
512
+ const storageKey = credKey(service, field);
511
513
  const stored = await setSecureKeyAsync(
512
514
  storageKey,
513
515
  JSON.stringify(cookies),
@@ -446,7 +446,9 @@ export function redirectToSecurePrompt(
446
446
  "Ingress redirect: transient credential injected",
447
447
  );
448
448
  } else {
449
- const key = `credential:${target.service}:${target.field}`;
449
+ const { credentialKey: credKey } =
450
+ await import("../security/credential-key.js");
451
+ const key = credKey(target.service, target.field);
450
452
  const stored = await setSecureKeyAsync(key, result.value);
451
453
  if (stored) {
452
454
  try {
@@ -31,6 +31,10 @@ import {
31
31
  getAllToolDefinitions,
32
32
  getMcpToolDefinitions,
33
33
  } from "../tools/registry.js";
34
+ import {
35
+ injectReasonField,
36
+ REASON_SKIP_SET,
37
+ } from "../tools/schema-transforms.js";
34
38
  import type {
35
39
  ProxyApprovalCallback,
36
40
  ProxyApprovalRequest,
@@ -335,11 +339,23 @@ export function createToolExecutor(
335
339
  // with the real tool name.
336
340
  if (name === "skill_execute") {
337
341
  const toolName = typeof input.tool === "string" ? input.tool : "";
338
- const toolInput =
342
+ const rawToolInput =
339
343
  input.input != null && typeof input.input === "object"
340
344
  ? (input.input as Record<string, unknown>)
341
345
  : {};
342
346
 
347
+ // Clone to avoid mutating shared input objects
348
+ const toolInput = { ...rawToolInput };
349
+
350
+ // Propagate outer reason when inner input lacks a valid one
351
+ if (
352
+ typeof input.reason === "string" &&
353
+ input.reason &&
354
+ (typeof toolInput.reason !== "string" || toolInput.reason.length === 0)
355
+ ) {
356
+ toolInput.reason = input.reason;
357
+ }
358
+
343
359
  if (!toolName) {
344
360
  return {
345
361
  content:
@@ -663,6 +679,6 @@ export function createResolveToolsCallback(
663
679
  turnAllowed.add(name);
664
680
  }
665
681
  ctx.allowedToolNames = turnAllowed;
666
- return allBaseDefs;
682
+ return injectReasonField(allBaseDefs, REASON_SKIP_SET);
667
683
  };
668
684
  }
@@ -324,7 +324,7 @@ export class Session {
324
324
  const resolved = {
325
325
  systemPrompt: hasSystemPromptOverride
326
326
  ? systemPrompt
327
- : buildSystemPrompt(),
327
+ : buildSystemPrompt({ hasNoClient: this.hasNoClient }),
328
328
  maxTokens: configuredMaxTokens,
329
329
  };
330
330
  return resolved;
@@ -5,6 +5,7 @@
5
5
  */
6
6
 
7
7
  import { getNestedValue, loadRawConfig } from "../../config/loader.js";
8
+ import { credentialKey } from "../../security/credential-key.js";
8
9
  import { getSecureKey } from "../../security/secure-keys.js";
9
10
  import { ConfigError } from "../../util/errors.js";
10
11
  import type { EmailProvider } from "../provider.js";
@@ -13,7 +14,7 @@ export const SUPPORTED_PROVIDERS = ["agentmail"] as const;
13
14
  export type SupportedProvider = (typeof SUPPORTED_PROVIDERS)[number];
14
15
 
15
16
  const PROVIDER_KEY_MAP: Record<SupportedProvider, string[]> = {
16
- agentmail: ["agentmail", "credential:agentmail:api_key"],
17
+ agentmail: ["agentmail", credentialKey("agentmail", "api_key")],
17
18
  };
18
19
 
19
20
  /**
package/src/instrument.ts CHANGED
@@ -1,7 +1,9 @@
1
+ import { arch, platform, release } from "node:os";
2
+
1
3
  import * as Sentry from "@sentry/node";
2
4
 
3
5
  import { getSentryDsn } from "./config/env.js";
4
- import { APP_VERSION } from "./version.js";
6
+ import { APP_VERSION, COMMIT_SHA } from "./version.js";
5
7
 
6
8
  /** Patterns that match sensitive data in Sentry event values. */
7
9
  const PII_PATTERNS = [
@@ -42,8 +44,20 @@ export function initSentry(): void {
42
44
  Sentry.init({
43
45
  dsn: getSentryDsn(),
44
46
  release: `vellum-assistant@${APP_VERSION}`,
47
+ dist: COMMIT_SHA,
45
48
  environment: APP_VERSION === "0.0.0-dev" ? "development" : "production",
46
49
  sendDefaultPii: false,
50
+ initialScope: {
51
+ tags: {
52
+ commit: COMMIT_SHA,
53
+ os_platform: platform(),
54
+ os_release: release(),
55
+ os_arch: arch(),
56
+ runtime: "bun",
57
+ runtime_version:
58
+ typeof Bun !== "undefined" ? Bun.version : process.version,
59
+ },
60
+ },
47
61
  beforeSend(event) {
48
62
  if (event.exception?.values) {
49
63
  event.exception.values = event.exception.values.map((ex) => ({
@@ -11,8 +11,16 @@ import { join } from "node:path";
11
11
 
12
12
  import { getConfig } from "../config/loader.js";
13
13
  import { getAppsDir } from "../memory/app-store.js";
14
+ import {
15
+ buildManagedBaseUrl,
16
+ resolveManagedProxyContext,
17
+ } from "../providers/managed-proxy/context.js";
14
18
  import { getLogger } from "../util/logger.js";
15
- import { generateImage, mapGeminiError } from "./gemini-image-service.js";
19
+ import {
20
+ generateImage,
21
+ type ImageGenCredentials,
22
+ mapGeminiError,
23
+ } from "./gemini-image-service.js";
16
24
 
17
25
  const log = getLogger("app-icon-generator");
18
26
 
@@ -29,8 +37,26 @@ export async function generateAppIcon(
29
37
  ): Promise<void> {
30
38
  const config = getConfig();
31
39
  const apiKey = config.apiKeys.gemini ?? process.env.GEMINI_API_KEY;
32
- if (!apiKey) {
33
- log.debug("No Gemini API key — skipping app icon generation");
40
+
41
+ let credentials: ImageGenCredentials | undefined;
42
+ if (apiKey) {
43
+ credentials = { type: "direct", apiKey };
44
+ } else {
45
+ const managedBaseUrl = buildManagedBaseUrl("vertex");
46
+ if (managedBaseUrl) {
47
+ const ctx = resolveManagedProxyContext();
48
+ credentials = {
49
+ type: "managed-proxy",
50
+ assistantApiKey: ctx.assistantApiKey,
51
+ baseUrl: managedBaseUrl,
52
+ };
53
+ }
54
+ }
55
+
56
+ if (!credentials) {
57
+ log.debug(
58
+ "No Gemini API key or managed proxy — skipping app icon generation",
59
+ );
34
60
  return;
35
61
  }
36
62
 
@@ -58,7 +84,7 @@ export async function generateAppIcon(
58
84
  try {
59
85
  log.info({ appId, appName }, "Generating app icon via Gemini");
60
86
 
61
- const result = await generateImage(apiKey, {
87
+ const result = await generateImage(credentials, {
62
88
  prompt,
63
89
  mode: "generate",
64
90
  model: config.imageGenModel,