@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
@@ -100,10 +100,13 @@ describe("generateImage", () => {
100
100
  test("generate mode returns images from response parts", async () => {
101
101
  fakeResponse = imageResponse("image/png", "abc123");
102
102
 
103
- const result = await generateImage("test-key", {
104
- prompt: "a cat",
105
- mode: "generate",
106
- });
103
+ const result = await generateImage(
104
+ { type: "direct", apiKey: "test-key" },
105
+ {
106
+ prompt: "a cat",
107
+ mode: "generate",
108
+ },
109
+ );
107
110
 
108
111
  expect(result.images).toHaveLength(1);
109
112
  expect(result.images[0].mimeType).toBe("image/png");
@@ -114,10 +117,13 @@ describe("generateImage", () => {
114
117
  test("generate mode collects text commentary from response", async () => {
115
118
  fakeResponse = imageWithTextResponse("Here is your image");
116
119
 
117
- const result = await generateImage("test-key", {
118
- prompt: "a dog",
119
- mode: "generate",
120
- });
120
+ const result = await generateImage(
121
+ { type: "direct", apiKey: "test-key" },
122
+ {
123
+ prompt: "a dog",
124
+ mode: "generate",
125
+ },
126
+ );
121
127
 
122
128
  expect(result.text).toBe("Here is your image");
123
129
  expect(result.images).toHaveLength(1);
@@ -126,11 +132,14 @@ describe("generateImage", () => {
126
132
  test("edit mode passes source images as inline data", async () => {
127
133
  fakeResponse = imageResponse();
128
134
 
129
- await generateImage("test-key", {
130
- prompt: "remove background",
131
- mode: "edit",
132
- sourceImages: [{ mimeType: "image/jpeg", dataBase64: "srcdata" }],
133
- });
135
+ await generateImage(
136
+ { type: "direct", apiKey: "test-key" },
137
+ {
138
+ prompt: "remove background",
139
+ mode: "edit",
140
+ sourceImages: [{ mimeType: "image/jpeg", dataBase64: "srcdata" }],
141
+ },
142
+ );
134
143
 
135
144
  expect(lastGenerateParams).not.toBeNull();
136
145
  const contents = (lastGenerateParams as Record<string, unknown>)
@@ -148,11 +157,14 @@ describe("generateImage", () => {
148
157
  test("model validation rejects unknown models and defaults", async () => {
149
158
  fakeResponse = imageResponse();
150
159
 
151
- await generateImage("test-key", {
152
- prompt: "test",
153
- mode: "generate",
154
- model: "invalid-model",
155
- });
160
+ await generateImage(
161
+ { type: "direct", apiKey: "test-key" },
162
+ {
163
+ prompt: "test",
164
+ mode: "generate",
165
+ model: "invalid-model",
166
+ },
167
+ );
156
168
 
157
169
  expect(lastGenerateParams).not.toBeNull();
158
170
  expect((lastGenerateParams as Record<string, unknown>).model).toBe(
@@ -163,11 +175,14 @@ describe("generateImage", () => {
163
175
  test("model validation accepts allowed models", async () => {
164
176
  fakeResponse = imageResponse();
165
177
 
166
- await generateImage("test-key", {
167
- prompt: "test",
168
- mode: "generate",
169
- model: "gemini-3-pro-image",
170
- });
178
+ await generateImage(
179
+ { type: "direct", apiKey: "test-key" },
180
+ {
181
+ prompt: "test",
182
+ mode: "generate",
183
+ model: "gemini-3-pro-image",
184
+ },
185
+ );
171
186
 
172
187
  expect((lastGenerateParams as Record<string, unknown>).model).toBe(
173
188
  "gemini-3-pro-image",
@@ -177,11 +192,14 @@ describe("generateImage", () => {
177
192
  test("variants makes parallel calls", async () => {
178
193
  fakeResponse = imageResponse();
179
194
 
180
- const result = await generateImage("test-key", {
181
- prompt: "test",
182
- mode: "generate",
183
- variants: 3,
184
- });
195
+ const result = await generateImage(
196
+ { type: "direct", apiKey: "test-key" },
197
+ {
198
+ prompt: "test",
199
+ mode: "generate",
200
+ variants: 3,
201
+ },
202
+ );
185
203
 
186
204
  expect(generateCallCount).toBe(3);
187
205
  expect(result.images).toHaveLength(3);
@@ -190,11 +208,14 @@ describe("generateImage", () => {
190
208
  test("variants are clamped to 1-4", async () => {
191
209
  fakeResponse = imageResponse();
192
210
 
193
- await generateImage("test-key", {
194
- prompt: "test",
195
- mode: "generate",
196
- variants: 10,
197
- });
211
+ await generateImage(
212
+ { type: "direct", apiKey: "test-key" },
213
+ {
214
+ prompt: "test",
215
+ mode: "generate",
216
+ variants: 10,
217
+ },
218
+ );
198
219
 
199
220
  expect(generateCallCount).toBe(4);
200
221
  });
@@ -202,10 +223,13 @@ describe("generateImage", () => {
202
223
  test("variants defaults to 1", async () => {
203
224
  fakeResponse = imageResponse();
204
225
 
205
- await generateImage("test-key", {
206
- prompt: "test",
207
- mode: "generate",
208
- });
226
+ await generateImage(
227
+ { type: "direct", apiKey: "test-key" },
228
+ {
229
+ prompt: "test",
230
+ mode: "generate",
231
+ },
232
+ );
209
233
 
210
234
  expect(generateCallCount).toBe(1);
211
235
  });
@@ -213,10 +237,13 @@ describe("generateImage", () => {
213
237
  test("handles empty candidates gracefully", async () => {
214
238
  fakeResponse = { candidates: [] };
215
239
 
216
- const result = await generateImage("test-key", {
217
- prompt: "test",
218
- mode: "generate",
219
- });
240
+ const result = await generateImage(
241
+ { type: "direct", apiKey: "test-key" },
242
+ {
243
+ prompt: "test",
244
+ mode: "generate",
245
+ },
246
+ );
220
247
 
221
248
  expect(result.images).toHaveLength(0);
222
249
  expect(result.text).toBeUndefined();
@@ -225,10 +252,13 @@ describe("generateImage", () => {
225
252
  test("response config includes TEXT and IMAGE modalities", async () => {
226
253
  fakeResponse = imageResponse();
227
254
 
228
- await generateImage("test-key", {
229
- prompt: "test",
230
- mode: "generate",
231
- });
255
+ await generateImage(
256
+ { type: "direct", apiKey: "test-key" },
257
+ {
258
+ prompt: "test",
259
+ mode: "generate",
260
+ },
261
+ );
232
262
 
233
263
  const config = (lastGenerateParams as Record<string, unknown>)
234
264
  .config as Record<string, unknown>;
@@ -739,14 +739,17 @@ describe("GeminiProvider", () => {
739
739
  expect(lastConstructorOpts).toEqual({ apiKey: "test-key" });
740
740
  });
741
741
 
742
- test("sets httpOptions.baseUrl when managedBaseUrl is provided", () => {
742
+ test("sets vertexai mode with httpOptions.baseUrl when managedBaseUrl is provided", () => {
743
743
  new GeminiProvider("managed-key", "gemini-3-flash", {
744
- managedBaseUrl: "https://platform.example.com/v1/runtime-proxy/gemini",
744
+ managedBaseUrl: "https://platform.example.com/v1/runtime-proxy/vertex",
745
745
  });
746
746
  expect(lastConstructorOpts).toEqual({
747
- apiKey: "managed-key",
747
+ vertexai: true,
748
+ project: "proxy",
749
+ location: "us-central1",
748
750
  httpOptions: {
749
- baseUrl: "https://platform.example.com/v1/runtime-proxy/gemini",
751
+ baseUrl: "https://platform.example.com/v1/runtime-proxy/vertex",
752
+ headers: { Authorization: "Bearer managed-key" },
750
753
  },
751
754
  });
752
755
  });
@@ -756,7 +759,7 @@ describe("GeminiProvider", () => {
756
759
  "managed-key",
757
760
  "gemini-3-flash",
758
761
  {
759
- managedBaseUrl: "https://platform.example.com/v1/runtime-proxy/gemini",
762
+ managedBaseUrl: "https://platform.example.com/v1/runtime-proxy/vertex",
760
763
  },
761
764
  );
762
765
 
@@ -781,7 +784,7 @@ describe("GeminiProvider", () => {
781
784
  "managed-key",
782
785
  "gemini-3-flash",
783
786
  {
784
- managedBaseUrl: "https://platform.example.com/v1/runtime-proxy/gemini",
787
+ managedBaseUrl: "https://platform.example.com/v1/runtime-proxy/vertex",
785
788
  },
786
789
  );
787
790
 
@@ -181,25 +181,6 @@ describe("guardian-action-conversation-turn", () => {
181
181
  expect(result.replyText).toBe("Sure, I'll call them back right away.");
182
182
  });
183
183
 
184
- test('classifies "send a text message" as message_back', async () => {
185
- const generator = createMockGenerator({
186
- disposition: "message_back",
187
- replyText: "Got it, I'll send them a text message.",
188
- });
189
-
190
- const result = await processGuardianFollowUpTurn(
191
- {
192
- questionText: "What is the gate code?",
193
- lateAnswerText: "The gate code is 1234",
194
- guardianReply: "Just send them a text",
195
- },
196
- generator,
197
- );
198
-
199
- expect(result.disposition).toBe("message_back");
200
- expect(result.replyText).toBe("Got it, I'll send them a text message.");
201
- });
202
-
203
184
  test('classifies "never mind" as decline', async () => {
204
185
  const generator = createMockGenerator({
205
186
  disposition: "decline",
@@ -448,19 +429,6 @@ describe("guardian-action-conversation-turn", () => {
448
429
  expect(updated!.followupAction).toBe("call_back");
449
430
  });
450
431
 
451
- test("message_back disposition transitions to dispatching with message_back action", () => {
452
- const { request } = createAwaitingChoiceRequest("conv-turn-7");
453
-
454
- const updated = progressFollowupState(
455
- request.id,
456
- "dispatching",
457
- "message_back",
458
- );
459
- expect(updated).not.toBeNull();
460
- expect(updated!.followupState).toBe("dispatching");
461
- expect(updated!.followupAction).toBe("message_back");
462
- });
463
-
464
432
  test("decline disposition finalizes to declined", () => {
465
433
  const { request } = createAwaitingChoiceRequest("conv-turn-8");
466
434
 
@@ -488,7 +456,7 @@ describe("guardian-action-conversation-turn", () => {
488
456
  const second = progressFollowupState(
489
457
  request.id,
490
458
  "dispatching",
491
- "message_back",
459
+ "call_back",
492
460
  );
493
461
  expect(second).toBeNull();
494
462
 
@@ -33,8 +33,6 @@ const ALL_SCENARIOS: GuardianActionMessageScenario[] = [
33
33
  "guardian_superseded_remap",
34
34
  "guardian_unknown_code",
35
35
  "guardian_auto_matched",
36
- "outbound_message_copy",
37
- "followup_message_sent",
38
36
  "followup_call_started",
39
37
  "followup_action_failed",
40
38
  "guardian_answer_delivery_failed",
@@ -110,24 +108,6 @@ describe("guardian-action-copy-generator", () => {
110
108
  });
111
109
  expect(msg).toContain("QFOO12");
112
110
  });
113
-
114
- test("outbound_message_copy includes lateAnswerText and questionText", () => {
115
- const msg = getGuardianActionFallbackMessage({
116
- scenario: "outbound_message_copy",
117
- questionText: "When is the appointment?",
118
- lateAnswerText: "It is at 3pm tomorrow.",
119
- });
120
- expect(msg).toContain("When is the appointment?");
121
- expect(msg).toContain("It is at 3pm tomorrow.");
122
- });
123
-
124
- test("outbound_message_copy without lateAnswerText still includes questionText", () => {
125
- const msg = getGuardianActionFallbackMessage({
126
- scenario: "outbound_message_copy",
127
- questionText: "Is the office open?",
128
- });
129
- expect(msg).toContain("Is the office open?");
130
- });
131
111
  });
132
112
 
133
113
  // -----------------------------------------------------------------------
@@ -127,10 +127,7 @@ function resetTables(): void {
127
127
  * Create a request in `dispatching` state ready for the executor.
128
128
  * The call session has fromNumber='+15550001111' (the counterparty).
129
129
  */
130
- function createDispatchingRequest(
131
- convId: string,
132
- action: "call_back" | "message_back",
133
- ) {
130
+ function createDispatchingRequest(convId: string, action: "call_back") {
134
131
  ensureConversation(convId);
135
132
  const session = createCallSession({
136
133
  conversationId: convId,
@@ -284,30 +281,6 @@ describe("guardian-action-followup-executor", () => {
284
281
  });
285
282
  });
286
283
 
287
- // ── message_back (unsupported) error path ─────────────────────────────
288
-
289
- describe("message_back", () => {
290
- test("returns failure and finalizes as failed when message_back is dispatched", async () => {
291
- const { request } = createDispatchingRequest(
292
- "exec-msg-1",
293
- "message_back",
294
- );
295
-
296
- const result = await executeFollowupAction(request.id, "message_back");
297
-
298
- expect(result.ok).toBe(false);
299
- if (!result.ok) {
300
- expect(result.error).toContain("Unsupported action");
301
- expect(result.error).toContain("message_back");
302
- }
303
- expect(result.guardianReplyText.length).toBeGreaterThan(0);
304
-
305
- const updated = getGuardianActionRequest(request.id);
306
- expect(updated!.followupState).toBe("failed");
307
- expect(updated!.followupCompletedAt).toBeGreaterThan(0);
308
- });
309
- });
310
-
311
284
  // ── Error handling ──────────────────────────────────────────────────
312
285
 
313
286
  describe("error handling", () => {
@@ -314,7 +314,7 @@ describe("guardian-action-followup-store", () => {
314
314
  const request = createTestRequest("conv-followup-15");
315
315
  markTimedOutWithReason(request.id, "call_timeout");
316
316
  startFollowupFromExpiredRequest(request.id, "Late answer");
317
- progressFollowupState(request.id, "dispatching", "message_back");
317
+ progressFollowupState(request.id, "dispatching", "call_back");
318
318
 
319
319
  const result = finalizeFollowup(request.id, "failed");
320
320
  expect(result).not.toBeNull();
@@ -50,6 +50,7 @@ import type { TrustContext } from "../daemon/session-runtime-assembly.js";
50
50
  import { getDb, initializeDb, resetDb } from "../memory/db.js";
51
51
  import {
52
52
  createApprovalRequest,
53
+ getPendingApprovalForRequest,
53
54
  type GuardianApprovalRequest,
54
55
  } from "../memory/guardian-approvals.js";
55
56
  import * as approvalMessageComposer from "../runtime/approval-message-composer.js";
@@ -247,6 +248,40 @@ describe("guardian grant minting on tool-approval decisions", () => {
247
248
  composeSpy.mockRestore();
248
249
  });
249
250
 
251
+ test("guardian reaction approve_always is downgraded to one-time approval", async () => {
252
+ const requestId = "req-grant-reaction-1";
253
+ const approval = createTestGuardianApproval(requestId, {
254
+ conversationId: CONVERSATION_ID,
255
+ channel: "slack",
256
+ guardianChatId: GUARDIAN_CHAT,
257
+ });
258
+ const handleConfirmationResponse = registerPendingInteraction(
259
+ requestId,
260
+ CONVERSATION_ID,
261
+ TOOL_NAME,
262
+ TOOL_INPUT,
263
+ );
264
+
265
+ const result = await handleApprovalInterception({
266
+ conversationId: "guardian-conv-1",
267
+ callbackData: "reaction:white_check_mark",
268
+ content: "",
269
+ conversationExternalId: GUARDIAN_CHAT,
270
+ sourceChannel: "slack",
271
+ actorExternalId: GUARDIAN_USER,
272
+ replyCallbackUrl: "https://gateway.test/deliver",
273
+ trustCtx: makeTrustContext(),
274
+ assistantId: ASSISTANT_ID,
275
+ });
276
+
277
+ expect(result.handled).toBe(true);
278
+ expect(result.type).toBe("guardian_decision_applied");
279
+ expect(handleConfirmationResponse).toHaveBeenCalledWith(requestId, "allow");
280
+
281
+ const updatedApproval = getPendingApprovalForRequest(approval.requestId);
282
+ expect(updatedApproval).toBeNull();
283
+ });
284
+
250
285
  // ── 2. approve_once for non-tool-approval does NOT mint a grant ──
251
286
 
252
287
  test("approve_once for informational request (no toolName) does NOT mint a grant", async () => {
@@ -1,5 +1,7 @@
1
1
  import { beforeEach, describe, expect, mock, test } from "bun:test";
2
2
 
3
+ import { credentialKey } from "../security/credential-key.js";
4
+
3
5
  const secureKeyValues = new Map<string, string>();
4
6
  let mockTwilioAccountSid: string | undefined;
5
7
 
@@ -36,12 +38,21 @@ describe("integration-status", () => {
36
38
  });
37
39
 
38
40
  test("returns all connected when all keys are set", () => {
39
- secureKeyValues.set("credential:integration:gmail:access_token", "tok");
40
- secureKeyValues.set("credential:integration:slack:access_token", "tok");
41
+ secureKeyValues.set(
42
+ credentialKey("integration:gmail", "access_token"),
43
+ "tok",
44
+ );
45
+ secureKeyValues.set(
46
+ credentialKey("integration:slack", "access_token"),
47
+ "tok",
48
+ );
41
49
  mockTwilioAccountSid = "sid";
42
- secureKeyValues.set("credential:twilio:auth_token", "auth");
43
- secureKeyValues.set("credential:telegram:bot_token", "tok");
44
- secureKeyValues.set("credential:telegram:webhook_secret", "secret");
50
+ secureKeyValues.set(credentialKey("twilio", "auth_token"), "auth");
51
+ secureKeyValues.set(credentialKey("telegram", "bot_token"), "tok");
52
+ secureKeyValues.set(
53
+ credentialKey("telegram", "webhook_secret"),
54
+ "secret",
55
+ );
45
56
 
46
57
  const summary = getIntegrationSummary();
47
58
  expect(summary.every((s: { connected: boolean }) => s.connected)).toBe(
@@ -51,9 +62,12 @@ describe("integration-status", () => {
51
62
 
52
63
  test("returns mixed status", () => {
53
64
  mockTwilioAccountSid = "sid";
54
- secureKeyValues.set("credential:twilio:auth_token", "auth");
55
- secureKeyValues.set("credential:telegram:bot_token", "tok");
56
- secureKeyValues.set("credential:telegram:webhook_secret", "secret");
65
+ secureKeyValues.set(credentialKey("twilio", "auth_token"), "auth");
66
+ secureKeyValues.set(credentialKey("telegram", "bot_token"), "tok");
67
+ secureKeyValues.set(
68
+ credentialKey("telegram", "webhook_secret"),
69
+ "secret",
70
+ );
57
71
 
58
72
  const summary = getIntegrationSummary();
59
73
  const connected = summary.filter(
@@ -82,7 +96,7 @@ describe("integration-status", () => {
82
96
  });
83
97
 
84
98
  test("Telegram disconnected when only bot_token is set (missing webhook_secret)", () => {
85
- secureKeyValues.set("credential:telegram:bot_token", "tok");
99
+ secureKeyValues.set(credentialKey("telegram", "bot_token"), "tok");
86
100
 
87
101
  const summary = getIntegrationSummary();
88
102
  const telegram = summary.find(
@@ -95,9 +109,12 @@ describe("integration-status", () => {
95
109
  describe("formatIntegrationSummary", () => {
96
110
  test("shows checkmarks and crosses", () => {
97
111
  mockTwilioAccountSid = "sid";
98
- secureKeyValues.set("credential:twilio:auth_token", "auth");
99
- secureKeyValues.set("credential:telegram:bot_token", "tok");
100
- secureKeyValues.set("credential:telegram:webhook_secret", "secret");
112
+ secureKeyValues.set(credentialKey("twilio", "auth_token"), "auth");
113
+ secureKeyValues.set(credentialKey("telegram", "bot_token"), "tok");
114
+ secureKeyValues.set(
115
+ credentialKey("telegram", "webhook_secret"),
116
+ "secret",
117
+ );
101
118
 
102
119
  const result = formatIntegrationSummary();
103
120
  expect(result).toBe(
@@ -113,12 +130,21 @@ describe("integration-status", () => {
113
130
  });
114
131
 
115
132
  test("all connected", () => {
116
- secureKeyValues.set("credential:integration:gmail:access_token", "tok");
117
- secureKeyValues.set("credential:integration:slack:access_token", "tok");
133
+ secureKeyValues.set(
134
+ credentialKey("integration:gmail", "access_token"),
135
+ "tok",
136
+ );
137
+ secureKeyValues.set(
138
+ credentialKey("integration:slack", "access_token"),
139
+ "tok",
140
+ );
118
141
  mockTwilioAccountSid = "sid";
119
- secureKeyValues.set("credential:twilio:auth_token", "auth");
120
- secureKeyValues.set("credential:telegram:bot_token", "tok");
121
- secureKeyValues.set("credential:telegram:webhook_secret", "secret");
142
+ secureKeyValues.set(credentialKey("twilio", "auth_token"), "auth");
143
+ secureKeyValues.set(credentialKey("telegram", "bot_token"), "tok");
144
+ secureKeyValues.set(
145
+ credentialKey("telegram", "webhook_secret"),
146
+ "secret",
147
+ );
122
148
 
123
149
  const result = formatIntegrationSummary();
124
150
  expect(result).toBe(
@@ -134,13 +160,16 @@ describe("integration-status", () => {
134
160
  });
135
161
 
136
162
  test("returns true when any integration in category is connected", () => {
137
- secureKeyValues.set("credential:telegram:bot_token", "tok");
138
- secureKeyValues.set("credential:telegram:webhook_secret", "secret");
163
+ secureKeyValues.set(credentialKey("telegram", "bot_token"), "tok");
164
+ secureKeyValues.set(
165
+ credentialKey("telegram", "webhook_secret"),
166
+ "secret",
167
+ );
139
168
  expect(hasCapability("messaging")).toBe(true);
140
169
  });
141
170
 
142
171
  test("returns false when only partial credentials exist for category integrations", () => {
143
- secureKeyValues.set("credential:telegram:bot_token", "tok");
172
+ secureKeyValues.set(credentialKey("telegram", "bot_token"), "tok");
144
173
  // Missing webhook_secret — Telegram should not count as connected
145
174
  expect(hasCapability("messaging")).toBe(false);
146
175
  });
@@ -150,7 +179,10 @@ describe("integration-status", () => {
150
179
  });
151
180
 
152
181
  test("email category checks Gmail", () => {
153
- secureKeyValues.set("credential:integration:gmail:access_token", "tok");
182
+ secureKeyValues.set(
183
+ credentialKey("integration:gmail", "access_token"),
184
+ "tok",
185
+ );
154
186
  expect(hasCapability("email")).toBe(true);
155
187
  });
156
188
  });
@@ -1,5 +1,7 @@
1
1
  import { beforeEach, describe, expect, mock, test } from "bun:test";
2
2
 
3
+ import { credentialKey } from "../security/credential-key.js";
4
+
3
5
  // Mock logger to suppress output
4
6
  mock.module("../util/logger.js", () => ({
5
7
  getLogger: () =>
@@ -18,7 +20,7 @@ mock.module("../config/env.js", () => ({
18
20
 
19
21
  mock.module("../security/secure-keys.js", () => ({
20
22
  getSecureKey: (key: string) => {
21
- if (key === "credential:vellum:assistant_api_key") {
23
+ if (key === credentialKey("vellum", "assistant_api_key")) {
22
24
  return mockAssistantApiKey;
23
25
  }
24
26
  return null;
@@ -108,10 +110,10 @@ describe("buildManagedBaseUrl", () => {
108
110
  "https://platform.example.com/v1/runtime-proxy/openai",
109
111
  );
110
112
  expect(buildManagedBaseUrl("anthropic")).toBe(
111
- "https://platform.example.com/v1/runtime-proxy/anthropic",
113
+ "https://platform.example.com/v1/runtime-proxy/vertex",
112
114
  );
113
115
  expect(buildManagedBaseUrl("gemini")).toBe(
114
- "https://platform.example.com/v1/runtime-proxy/gemini",
116
+ "https://platform.example.com/v1/runtime-proxy/vertex",
115
117
  );
116
118
  expect(buildManagedBaseUrl("fireworks")).toBe(
117
119
  "https://platform.example.com/v1/runtime-proxy/fireworks",