@vellumai/vellum-gateway 0.7.0 → 0.7.2

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 (162) hide show
  1. package/AGENTS.md +4 -0
  2. package/ARCHITECTURE.md +67 -25
  3. package/Dockerfile +2 -0
  4. package/README.md +50 -13
  5. package/bun.lock +16 -2
  6. package/knip.json +3 -1
  7. package/package.json +3 -1
  8. package/src/__tests__/auto-approve-thresholds.test.ts +49 -22
  9. package/src/__tests__/channel-verification-session-proxy.test.ts +0 -1
  10. package/src/__tests__/config-file-watcher.test.ts +181 -0
  11. package/src/__tests__/config.test.ts +0 -1
  12. package/src/__tests__/contacts-control-plane-proxy.test.ts +0 -1
  13. package/src/__tests__/credential-watcher-managed-bootstrap.test.ts +10 -2
  14. package/src/__tests__/credential-watcher.test.ts +30 -2
  15. package/src/__tests__/db-connection-isolation.test.ts +157 -0
  16. package/src/__tests__/fake-assistant-ipc.ts +39 -0
  17. package/src/__tests__/feature-flags-route.test.ts +8 -8
  18. package/src/__tests__/guardian-init-lockfile.test.ts +30 -4
  19. package/src/__tests__/ipc-feature-flag-routes.test.ts +1 -1
  20. package/src/__tests__/live-voice-websocket.test.ts +0 -1
  21. package/src/__tests__/load-guards.test.ts +0 -1
  22. package/src/__tests__/migration-teleport-gcs-proxy.test.ts +0 -1
  23. package/src/__tests__/oauth-callback.test.ts +0 -1
  24. package/src/__tests__/pair-origin-allowlist.test.ts +155 -0
  25. package/src/__tests__/rate-limit-loopback.test.ts +1 -1
  26. package/src/__tests__/remote-feature-flag-sync.test.ts +47 -7
  27. package/src/__tests__/resolve-assistant.test.ts +0 -1
  28. package/src/__tests__/route-schema-guard.test.ts +42 -6
  29. package/src/__tests__/runtime-client.test.ts +0 -1
  30. package/src/__tests__/runtime-health-proxy.test.ts +0 -1
  31. package/src/__tests__/runtime-proxy-auth.test.ts +0 -1
  32. package/src/__tests__/runtime-proxy.test.ts +0 -1
  33. package/src/__tests__/slack-control-plane-proxy.test.ts +0 -1
  34. package/src/__tests__/slack-display-name.test.ts +66 -1
  35. package/src/__tests__/slack-normalize.test.ts +158 -4
  36. package/src/__tests__/slack-reaction-normalize.test.ts +0 -1
  37. package/src/__tests__/slack-socket-mode-catchup.test.ts +857 -0
  38. package/src/__tests__/slack-socket-mode-scopes.test.ts +52 -0
  39. package/src/__tests__/slack-socket-mode-thread-tracking.test.ts +654 -0
  40. package/src/__tests__/stt-stream-websocket.test.ts +0 -1
  41. package/src/__tests__/telegram-control-plane-proxy.test.ts +0 -1
  42. package/src/__tests__/telegram-send-attachments.test.ts +0 -1
  43. package/src/__tests__/telegram-webhook-handler.test.ts +0 -1
  44. package/src/__tests__/text-verification-helpers.test.ts +136 -0
  45. package/src/__tests__/twilio-media-websocket.test.ts +0 -1
  46. package/src/__tests__/twilio-relay-websocket.test.ts +0 -1
  47. package/src/__tests__/twilio-webhooks.test.ts +220 -3
  48. package/src/__tests__/upstream-transport.test.ts +0 -36
  49. package/src/__tests__/whatsapp-download.test.ts +0 -1
  50. package/src/__tests__/whatsapp-webhook.test.ts +0 -1
  51. package/src/auth/guardian-refresh.ts +4 -18
  52. package/src/auth/ipc-route-policy.ts +217 -0
  53. package/src/backup/backup-key.ts +138 -0
  54. package/src/backup/backup-routes.ts +159 -0
  55. package/src/backup/backup-worker.ts +374 -0
  56. package/src/backup/list-snapshots.ts +97 -0
  57. package/src/backup/local-writer.ts +87 -0
  58. package/src/backup/offsite-writer.ts +182 -0
  59. package/src/backup/paths.ts +123 -0
  60. package/src/backup/stream-crypt.ts +258 -0
  61. package/src/chrome-extension-origins.ts +28 -0
  62. package/src/cli/enable-proxy.ts +0 -1
  63. package/src/config-file-cache.ts +3 -19
  64. package/src/config-file-utils.ts +124 -0
  65. package/src/config-file-watcher.ts +57 -25
  66. package/src/config.ts +4 -7
  67. package/src/db/connection.ts +65 -3
  68. package/src/db/contact-store.ts +30 -1
  69. package/src/db/data-migrations/index.ts +2 -0
  70. package/src/db/data-migrations/m0003-recover-backup-key.ts +71 -0
  71. package/src/db/schema.ts +92 -0
  72. package/src/db/slack-store.ts +144 -11
  73. package/src/feature-flag-registry.json +40 -152
  74. package/src/handlers/handle-inbound.ts +123 -0
  75. package/src/http/middleware/auth.ts +44 -1
  76. package/src/http/middleware/cors.ts +84 -0
  77. package/src/http/middleware/rate-limit.ts +6 -8
  78. package/src/http/routes/auto-approve-thresholds.ts +17 -1
  79. package/src/http/routes/brain-graph-proxy.ts +1 -1
  80. package/src/http/routes/channel-readiness-proxy.ts +2 -2
  81. package/src/http/routes/channel-verification-session-proxy.ts +19 -37
  82. package/src/http/routes/contact-prompt.ts +149 -0
  83. package/src/http/routes/contacts-control-plane-proxy.ts +2 -2
  84. package/src/http/routes/email-webhook.test.ts +0 -1
  85. package/src/http/routes/ipc-runtime-proxy.test.ts +197 -1
  86. package/src/http/routes/ipc-runtime-proxy.ts +95 -0
  87. package/src/http/routes/log-export.test.ts +0 -1
  88. package/src/http/routes/log-tail.test.ts +336 -0
  89. package/src/http/routes/log-tail.ts +87 -0
  90. package/src/http/routes/migration-proxy.ts +1 -2
  91. package/src/http/routes/oauth-apps-proxy.ts +2 -2
  92. package/src/http/routes/oauth-providers-proxy.ts +2 -2
  93. package/src/http/routes/pair.ts +322 -0
  94. package/src/http/routes/privacy-config.ts +65 -79
  95. package/src/http/routes/runtime-health-proxy.ts +2 -2
  96. package/src/http/routes/runtime-proxy.ts +3 -1
  97. package/src/http/routes/slack-control-plane-proxy.ts +3 -20
  98. package/src/http/routes/stt-stream-websocket.ts +2 -3
  99. package/src/http/routes/telegram-control-plane-proxy.ts +2 -2
  100. package/src/http/routes/telegram-webhook.test.ts +0 -1
  101. package/src/http/routes/telegram-webhook.ts +6 -0
  102. package/src/http/routes/trust-rules.suggest.test.ts +25 -0
  103. package/src/http/routes/trust-rules.ts +7 -0
  104. package/src/http/routes/twilio-control-plane-proxy.ts +2 -2
  105. package/src/http/routes/twilio-media-websocket.ts +5 -5
  106. package/src/http/routes/twilio-voice-verify-callback.ts +310 -0
  107. package/src/http/routes/twilio-voice-webhook.test.ts +65 -1
  108. package/src/http/routes/twilio-voice-webhook.ts +45 -1
  109. package/src/http/routes/whatsapp-webhook.test.ts +0 -1
  110. package/src/index.ts +357 -278
  111. package/src/ipc/assistant-client.ts +8 -4
  112. package/src/ipc/contact-handlers.ts +88 -3
  113. package/src/ipc/threshold-handlers.ts +2 -0
  114. package/src/post-assistant-ready.ts +5 -3
  115. package/src/risk/bash-risk-classifier.test.ts +35 -27
  116. package/src/risk/bash-risk-classifier.ts +44 -14
  117. package/src/risk/command-registry/commands/assistant.ts +8 -19
  118. package/src/risk/command-registry.test.ts +0 -15
  119. package/src/risk/risk-classifier-parity.test.ts +1 -3
  120. package/src/runtime/client.ts +58 -3
  121. package/src/schema.ts +277 -104
  122. package/src/slack/normalize.test.ts +98 -0
  123. package/src/slack/normalize.ts +107 -32
  124. package/src/slack/slack-web.ts +213 -0
  125. package/src/slack/socket-mode.ts +701 -39
  126. package/src/telegram/send.test.ts +0 -1
  127. package/src/twilio/validate-webhook.ts +53 -14
  128. package/src/twilio/webhook-sync-trigger.ts +58 -0
  129. package/src/twilio/webhook-sync.test.ts +286 -0
  130. package/src/twilio/webhook-sync.ts +84 -0
  131. package/src/util/is-loopback-address.ts +27 -0
  132. package/src/velay/bridge-utils.ts +228 -0
  133. package/src/velay/client.test.ts +939 -0
  134. package/src/velay/client.ts +555 -0
  135. package/src/velay/http-bridge.test.ts +217 -0
  136. package/src/velay/http-bridge.ts +83 -0
  137. package/src/velay/protocol.ts +178 -0
  138. package/src/velay/test-fake-websocket.ts +69 -0
  139. package/src/velay/websocket-bridge.test.ts +367 -0
  140. package/src/velay/websocket-bridge.ts +324 -0
  141. package/src/verification/binding-helpers.ts +107 -0
  142. package/src/verification/code-parsing.ts +44 -0
  143. package/src/verification/contact-helpers.ts +342 -0
  144. package/src/verification/identity-match.ts +68 -0
  145. package/src/verification/identity.ts +61 -0
  146. package/src/verification/rate-limit-helpers.ts +205 -0
  147. package/src/verification/reply-delivery.ts +109 -0
  148. package/src/verification/session-helpers.ts +164 -0
  149. package/src/verification/text-verification.ts +372 -0
  150. package/src/version.ts +35 -0
  151. package/src/voice/verification.ts +456 -0
  152. package/src/webhook-pipeline.ts +4 -0
  153. package/src/__tests__/browser-relay-websocket.test.ts +0 -698
  154. package/src/__tests__/telegram-only-default.test.ts +0 -133
  155. package/src/auth/capability-tokens.ts +0 -248
  156. package/src/http/routes/browser-extension-pair.ts +0 -455
  157. package/src/http/routes/browser-relay-websocket.ts +0 -381
  158. package/src/http/routes/config-file-utils.ts +0 -73
  159. package/src/ipc/capability-token-handlers.ts +0 -30
  160. package/src/pairing/approved-devices-store.ts +0 -110
  161. package/src/pairing/pairing-routes.ts +0 -379
  162. package/src/pairing/pairing-store.ts +0 -218
@@ -2,8 +2,12 @@ import { describe, test, expect } from "bun:test";
2
2
  import {
3
3
  stripBotMention,
4
4
  normalizeSlackAppMention,
5
+ normalizeSlackChannelMessage,
6
+ normalizeSlackDirectMessage,
5
7
  normalizeSlackMessageEdit,
6
8
  type SlackAppMentionEvent,
9
+ type SlackChannelMessageEvent,
10
+ type SlackDirectMessageEvent,
7
11
  type SlackMessageChangedEvent,
8
12
  } from "../slack/normalize.js";
9
13
  import type { GatewayConfig } from "../config.js";
@@ -26,7 +30,6 @@ function makeConfig(overrides: Partial<GatewayConfig> = {}): GatewayConfig {
26
30
  routingEntries: [],
27
31
  runtimeInitialBackoffMs: 500,
28
32
  runtimeMaxRetries: 2,
29
- runtimeProxyEnabled: false,
30
33
  runtimeProxyRequireAuth: false,
31
34
  runtimeTimeoutMs: 30000,
32
35
  shutdownDrainMs: 5000,
@@ -54,8 +57,16 @@ describe("stripBotMention", () => {
54
57
  expect(stripBotMention("<@U123BOT> hello world")).toBe("hello world");
55
58
  });
56
59
 
57
- test("strips multiple leading bot mentions", () => {
58
- expect(stripBotMention("<@U123BOT> <@U456OTHER> hello")).toBe("hello");
60
+ test("strips repeated leading bot mentions while preserving a following human mention", () => {
61
+ expect(
62
+ stripBotMention("<@U123BOT> <@U123BOT> <@U456OTHER> hello", "U123BOT"),
63
+ ).toBe("<@U456OTHER> hello");
64
+ });
65
+
66
+ test("fallback strips only the first leading mention", () => {
67
+ expect(stripBotMention("<@U123BOT> <@U456OTHER> hello")).toBe(
68
+ "<@U456OTHER> hello",
69
+ );
59
70
  });
60
71
 
61
72
  test("falls back to original text when stripping produces empty string", () => {
@@ -133,7 +144,37 @@ describe("normalizeSlackAppMention", () => {
133
144
  const result = await normalizeSlackAppMention(event, "evt-006", config);
134
145
 
135
146
  expect(result).not.toBeNull();
136
- expect(result!.event.message.content).toBe("<@U123BOT>");
147
+ expect(result!.event.message.content).toBe("@unknown-user");
148
+ });
149
+
150
+ test("renders a human mention after stripping the app mention", async () => {
151
+ const config = makeConfig();
152
+ const event = makeEvent({ text: "<@UBOT> <@ULEO> can you check?" });
153
+ const result = await normalizeSlackAppMention(
154
+ event,
155
+ "evt-mention-label",
156
+ config,
157
+ "UBOT",
158
+ undefined,
159
+ { userLabels: { ULEO: "leo" } },
160
+ );
161
+
162
+ expect(result).not.toBeNull();
163
+ expect(result!.event.message.content).toBe("@leo can you check?");
164
+ });
165
+
166
+ test("renders unresolved user mentions with the unknown-user fallback", async () => {
167
+ const config = makeConfig();
168
+ const event = makeEvent({ text: "<@UBOT> <@UUNKNOWN> can you check?" });
169
+ const result = await normalizeSlackAppMention(
170
+ event,
171
+ "evt-unknown-mention",
172
+ config,
173
+ "UBOT",
174
+ );
175
+
176
+ expect(result).not.toBeNull();
177
+ expect(result!.event.message.content).toBe("@unknown-user can you check?");
137
178
  });
138
179
 
139
180
  test("thread_ts is preserved in return value", async () => {
@@ -205,6 +246,119 @@ describe("normalizeSlackAppMention", () => {
205
246
  });
206
247
  });
207
248
 
249
+ function makeDirectMessageEvent(
250
+ overrides: Partial<SlackDirectMessageEvent> = {},
251
+ ): SlackDirectMessageEvent {
252
+ return {
253
+ type: "message",
254
+ user: "U_USER123",
255
+ text: "hello world",
256
+ ts: "1700000000.000100",
257
+ channel: "D_DIRECT1",
258
+ channel_type: "im",
259
+ ...overrides,
260
+ };
261
+ }
262
+
263
+ function makeChannelMessageEvent(
264
+ overrides: Partial<SlackChannelMessageEvent> = {},
265
+ ): SlackChannelMessageEvent {
266
+ return {
267
+ type: "message",
268
+ user: "U_USER123",
269
+ text: "hello world",
270
+ ts: "1700000000.000100",
271
+ channel: "C_CHANNEL1",
272
+ channel_type: "channel",
273
+ ...overrides,
274
+ };
275
+ }
276
+
277
+ describe("Slack inbound mention rendering", () => {
278
+ test("direct messages render mentions without stripping the bot mention", () => {
279
+ const config = makeConfig();
280
+ const event = makeDirectMessageEvent({
281
+ text: "<@UBOT> <@ULEO> hello",
282
+ });
283
+ const result = normalizeSlackDirectMessage(
284
+ event,
285
+ "evt-dm-render",
286
+ config,
287
+ "UBOT",
288
+ undefined,
289
+ { userLabels: { UBOT: "assistant", ULEO: "leo" } },
290
+ );
291
+
292
+ expect(result).not.toBeNull();
293
+ expect(result!.event.message.content).toBe("@assistant @leo hello");
294
+ expect(result!.event.actor.actorExternalId).toBe("U_USER123");
295
+ expect(result!.event.message.conversationExternalId).toBe("D_DIRECT1");
296
+ });
297
+
298
+ test("channel messages strip only the configured bot mention and render remaining mentions", () => {
299
+ const config = makeConfig();
300
+ const event = makeChannelMessageEvent({
301
+ text: "<@UBOT> <@ULEO> hello",
302
+ });
303
+ const result = normalizeSlackChannelMessage(
304
+ event,
305
+ "evt-channel-render",
306
+ config,
307
+ "UBOT",
308
+ undefined,
309
+ { userLabels: { ULEO: "leo" } },
310
+ );
311
+
312
+ expect(result).not.toBeNull();
313
+ expect(result!.event.message.content).toBe("@leo hello");
314
+ expect(result!.event.actor.actorExternalId).toBe("U_USER123");
315
+ expect(result!.event.message.conversationExternalId).toBe("C_CHANNEL1");
316
+ });
317
+
318
+ test("channel messages without bot user ID strip only the first leading mention fallback", () => {
319
+ const config = makeConfig();
320
+ const event = makeChannelMessageEvent({
321
+ text: "<@UBOT> <@ULEO> hello",
322
+ });
323
+ const result = normalizeSlackChannelMessage(
324
+ event,
325
+ "evt-channel-fallback-render",
326
+ config,
327
+ undefined,
328
+ undefined,
329
+ { userLabels: { ULEO: "leo" } },
330
+ );
331
+
332
+ expect(result).not.toBeNull();
333
+ expect(result!.event.message.content).toBe("@leo hello");
334
+ });
335
+
336
+ test("message edits render mentions and preserve edit metadata", () => {
337
+ const config = makeConfig();
338
+ const event = makeMessageChangedEvent({
339
+ message: {
340
+ user: "U_USER123",
341
+ text: "<@UBOT> <@ULEO> edited",
342
+ ts: "1700000000.000100",
343
+ },
344
+ });
345
+ const result = normalizeSlackMessageEdit(
346
+ event,
347
+ "evt-edit-render",
348
+ config,
349
+ "UBOT",
350
+ { userLabels: { ULEO: "leo" } },
351
+ );
352
+
353
+ expect(result).not.toBeNull();
354
+ expect(result!.event.message.content).toBe("@leo edited");
355
+ expect(result!.event.message.isEdit).toBe(true);
356
+ expect(result!.event.message.externalMessageId).toBe("evt-edit-render");
357
+ expect(result!.event.source.messageId).toBe("1700000000.000100");
358
+ expect(result!.event.actor.actorExternalId).toBe("U_USER123");
359
+ });
360
+ });
361
+
208
362
  function makeMessageChangedEvent(
209
363
  overrides: Partial<SlackMessageChangedEvent> = {},
210
364
  ): SlackMessageChangedEvent {
@@ -23,7 +23,6 @@ function makeConfig(overrides: Partial<GatewayConfig> = {}): GatewayConfig {
23
23
  routingEntries: [],
24
24
  runtimeInitialBackoffMs: 500,
25
25
  runtimeMaxRetries: 2,
26
- runtimeProxyEnabled: false,
27
26
  runtimeProxyRequireAuth: false,
28
27
  runtimeTimeoutMs: 30000,
29
28
  shutdownDrainMs: 5000,