@vellumai/assistant 0.4.31 → 0.4.32

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 (121) hide show
  1. package/ARCHITECTURE.md +1 -1
  2. package/package.json +1 -1
  3. package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +0 -7
  4. package/src/__tests__/anthropic-provider.test.ts +86 -1
  5. package/src/__tests__/assistant-feature-flags-integration.test.ts +2 -2
  6. package/src/__tests__/checker.test.ts +37 -98
  7. package/src/__tests__/commit-message-enrichment-service.test.ts +15 -0
  8. package/src/__tests__/config-schema.test.ts +6 -5
  9. package/src/__tests__/credential-security-invariants.test.ts +1 -0
  10. package/src/__tests__/daemon-server-session-init.test.ts +1 -19
  11. package/src/__tests__/followup-tools.test.ts +0 -30
  12. package/src/__tests__/gemini-provider.test.ts +79 -1
  13. package/src/__tests__/ipc-snapshot.test.ts +0 -4
  14. package/src/__tests__/managed-proxy-context.test.ts +163 -0
  15. package/src/__tests__/memory-lifecycle-e2e.test.ts +2 -2
  16. package/src/__tests__/memory-regressions.test.ts +6 -6
  17. package/src/__tests__/openai-provider.test.ts +82 -0
  18. package/src/__tests__/provider-fail-open-selection.test.ts +134 -1
  19. package/src/__tests__/provider-managed-proxy-integration.test.ts +269 -0
  20. package/src/__tests__/recurrence-types.test.ts +0 -15
  21. package/src/__tests__/schedule-tools.test.ts +28 -44
  22. package/src/__tests__/skill-feature-flags.test.ts +2 -2
  23. package/src/__tests__/task-management-tools.test.ts +111 -0
  24. package/src/__tests__/twilio-config.test.ts +0 -3
  25. package/src/amazon/session.ts +30 -91
  26. package/src/calls/call-controller.ts +423 -571
  27. package/src/calls/finalize-call.ts +20 -0
  28. package/src/calls/relay-access-wait.ts +340 -0
  29. package/src/calls/relay-server.ts +267 -902
  30. package/src/calls/relay-setup-router.ts +307 -0
  31. package/src/calls/relay-verification.ts +280 -0
  32. package/src/calls/twilio-config.ts +1 -8
  33. package/src/calls/voice-control-protocol.ts +184 -0
  34. package/src/calls/voice-session-bridge.ts +1 -8
  35. package/src/config/agent-schema.ts +1 -1
  36. package/src/config/bundled-skills/followups/TOOLS.json +0 -4
  37. package/src/config/bundled-skills/schedule/SKILL.md +1 -1
  38. package/src/config/bundled-skills/schedule/TOOLS.json +2 -10
  39. package/src/config/core-schema.ts +1 -1
  40. package/src/config/env.ts +0 -10
  41. package/src/config/feature-flag-registry.json +1 -1
  42. package/src/config/loader.ts +19 -0
  43. package/src/config/schema.ts +2 -2
  44. package/src/daemon/handlers/session-history.ts +398 -0
  45. package/src/daemon/handlers/session-user-message.ts +982 -0
  46. package/src/daemon/handlers/sessions.ts +9 -1338
  47. package/src/daemon/ipc-contract/sessions.ts +0 -6
  48. package/src/daemon/ipc-contract-inventory.json +0 -1
  49. package/src/daemon/lifecycle.ts +0 -29
  50. package/src/home-base/app-link-store.ts +0 -7
  51. package/src/memory/conversation-attention-store.ts +1 -1
  52. package/src/memory/conversation-store.ts +0 -51
  53. package/src/memory/db-init.ts +5 -1
  54. package/src/memory/job-handlers/conflict.ts +24 -0
  55. package/src/memory/migrations/105-contacts-and-triage.ts +4 -7
  56. package/src/memory/migrations/134-contacts-notes-column.ts +50 -33
  57. package/src/memory/migrations/registry.ts +6 -0
  58. package/src/memory/recall-cache.ts +0 -5
  59. package/src/memory/schema/calls.ts +274 -0
  60. package/src/memory/schema/contacts.ts +125 -0
  61. package/src/memory/schema/conversations.ts +129 -0
  62. package/src/memory/schema/guardian.ts +172 -0
  63. package/src/memory/schema/index.ts +8 -0
  64. package/src/memory/schema/infrastructure.ts +205 -0
  65. package/src/memory/schema/memory-core.ts +196 -0
  66. package/src/memory/schema/notifications.ts +191 -0
  67. package/src/memory/schema/tasks.ts +78 -0
  68. package/src/memory/schema.ts +1 -1385
  69. package/src/memory/slack-thread-store.ts +0 -69
  70. package/src/notifications/decisions-store.ts +2 -105
  71. package/src/notifications/deliveries-store.ts +0 -11
  72. package/src/notifications/preferences-store.ts +1 -58
  73. package/src/permissions/checker.ts +6 -17
  74. package/src/providers/anthropic/client.ts +6 -2
  75. package/src/providers/gemini/client.ts +13 -2
  76. package/src/providers/managed-proxy/constants.ts +55 -0
  77. package/src/providers/managed-proxy/context.ts +77 -0
  78. package/src/providers/registry.ts +112 -0
  79. package/src/runtime/auth/__tests__/guard-tests.test.ts +51 -23
  80. package/src/runtime/http-server.ts +83 -722
  81. package/src/runtime/http-types.ts +0 -16
  82. package/src/runtime/middleware/auth.ts +0 -12
  83. package/src/runtime/routes/app-routes.ts +33 -0
  84. package/src/runtime/routes/approval-routes.ts +32 -0
  85. package/src/runtime/routes/attachment-routes.ts +32 -0
  86. package/src/runtime/routes/brain-graph-routes.ts +27 -0
  87. package/src/runtime/routes/call-routes.ts +41 -0
  88. package/src/runtime/routes/channel-readiness-routes.ts +20 -0
  89. package/src/runtime/routes/channel-routes.ts +70 -0
  90. package/src/runtime/routes/contact-routes.ts +63 -0
  91. package/src/runtime/routes/conversation-attention-routes.ts +15 -0
  92. package/src/runtime/routes/conversation-routes.ts +190 -193
  93. package/src/runtime/routes/debug-routes.ts +15 -0
  94. package/src/runtime/routes/events-routes.ts +16 -0
  95. package/src/runtime/routes/global-search-routes.ts +15 -0
  96. package/src/runtime/routes/guardian-action-routes.ts +22 -0
  97. package/src/runtime/routes/guardian-bootstrap-routes.ts +20 -0
  98. package/src/runtime/routes/guardian-refresh-routes.ts +20 -0
  99. package/src/runtime/routes/identity-routes.ts +20 -0
  100. package/src/runtime/routes/inbound-message-handler.ts +8 -0
  101. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +6 -6
  102. package/src/runtime/routes/integration-routes.ts +83 -0
  103. package/src/runtime/routes/invite-routes.ts +31 -0
  104. package/src/runtime/routes/migration-routes.ts +30 -0
  105. package/src/runtime/routes/pairing-routes.ts +18 -0
  106. package/src/runtime/routes/secret-routes.ts +20 -0
  107. package/src/runtime/routes/surface-action-routes.ts +26 -0
  108. package/src/runtime/routes/trust-rules-routes.ts +31 -0
  109. package/src/runtime/routes/twilio-routes.ts +79 -0
  110. package/src/schedule/recurrence-types.ts +1 -11
  111. package/src/tools/followups/followup_create.ts +9 -3
  112. package/src/tools/mcp/mcp-tool-factory.ts +0 -17
  113. package/src/tools/memory/definitions.ts +0 -6
  114. package/src/tools/network/script-proxy/session-manager.ts +38 -3
  115. package/src/tools/schedule/create.ts +1 -3
  116. package/src/tools/schedule/update.ts +9 -6
  117. package/src/twitter/session.ts +29 -77
  118. package/src/util/cookie-session.ts +114 -0
  119. package/src/__tests__/conversation-routes.test.ts +0 -99
  120. package/src/__tests__/task-tools.test.ts +0 -685
  121. package/src/contacts/startup-migration.ts +0 -21
@@ -48,6 +48,7 @@ import {
48
48
  startOutbound,
49
49
  } from "../guardian-outbound-actions.js";
50
50
  import { httpError } from "../http-errors.js";
51
+ import type { RouteDefinition } from "../http-router.js";
51
52
  import { guardianVerificationLimiter } from "../verification-rate-limiter.js";
52
53
 
53
54
  /**
@@ -296,3 +297,85 @@ export async function handleCancelOutbound(req: Request): Promise<Response> {
296
297
  const status = result.success ? 200 : 400;
297
298
  return Response.json(result, { status });
298
299
  }
300
+
301
+ // ---------------------------------------------------------------------------
302
+ // Route definitions
303
+ // ---------------------------------------------------------------------------
304
+
305
+ export function integrationRouteDefinitions(): RouteDefinition[] {
306
+ return [
307
+ // Telegram
308
+ {
309
+ endpoint: "integrations/telegram/config",
310
+ method: "GET",
311
+ handler: () => handleGetTelegramConfig(),
312
+ },
313
+ {
314
+ endpoint: "integrations/telegram/config",
315
+ method: "POST",
316
+ handler: async ({ req }) => handleSetTelegramConfig(req),
317
+ },
318
+ {
319
+ endpoint: "integrations/telegram/config",
320
+ method: "DELETE",
321
+ handler: async () => handleClearTelegramConfig(),
322
+ },
323
+ {
324
+ endpoint: "integrations/telegram/commands",
325
+ method: "POST",
326
+ handler: async ({ req }) => handleSetTelegramCommands(req),
327
+ },
328
+ {
329
+ endpoint: "integrations/telegram/setup",
330
+ method: "POST",
331
+ handler: async ({ req }) => handleSetupTelegram(req),
332
+ },
333
+ // Slack
334
+ {
335
+ endpoint: "integrations/slack/channel/config",
336
+ method: "GET",
337
+ handler: () => handleGetSlackChannelConfig(),
338
+ },
339
+ {
340
+ endpoint: "integrations/slack/channel/config",
341
+ method: "POST",
342
+ handler: async ({ req }) => handleSetSlackChannelConfig(req),
343
+ },
344
+ {
345
+ endpoint: "integrations/slack/channel/config",
346
+ method: "DELETE",
347
+ handler: () => handleClearSlackChannelConfig(),
348
+ },
349
+ // Guardian
350
+ {
351
+ endpoint: "integrations/guardian/challenge",
352
+ method: "POST",
353
+ handler: async ({ req }) => handleCreateGuardianChallenge(req),
354
+ },
355
+ {
356
+ endpoint: "integrations/guardian/status",
357
+ method: "GET",
358
+ handler: ({ url }) => handleGetGuardianStatus(url),
359
+ },
360
+ {
361
+ endpoint: "integrations/guardian/revoke",
362
+ method: "POST",
363
+ handler: async ({ req }) => handleRevokeGuardian(req),
364
+ },
365
+ {
366
+ endpoint: "integrations/guardian/outbound/start",
367
+ method: "POST",
368
+ handler: async ({ req }) => handleStartOutbound(req),
369
+ },
370
+ {
371
+ endpoint: "integrations/guardian/outbound/resend",
372
+ method: "POST",
373
+ handler: async ({ req }) => handleResendOutbound(req),
374
+ },
375
+ {
376
+ endpoint: "integrations/guardian/outbound/cancel",
377
+ method: "POST",
378
+ handler: async ({ req }) => handleCancelOutbound(req),
379
+ },
380
+ ];
381
+ }
@@ -8,6 +8,7 @@
8
8
  * POST /v1/contacts/invites/redeem — redeem an invite (token or voice code)
9
9
  */
10
10
 
11
+ import type { RouteDefinition } from "../http-router.js";
11
12
  import {
12
13
  createIngressInvite,
13
14
  listIngressInvites,
@@ -138,3 +139,33 @@ export async function handleRedeemInvite(req: Request): Promise<Response> {
138
139
  }
139
140
  return Response.json({ ok: true, invite: result.data });
140
141
  }
142
+
143
+ // ---------------------------------------------------------------------------
144
+ // Route definitions
145
+ // ---------------------------------------------------------------------------
146
+
147
+ export function inviteRouteDefinitions(): RouteDefinition[] {
148
+ return [
149
+ {
150
+ endpoint: "contacts/invites",
151
+ method: "GET",
152
+ handler: ({ url }) => handleListInvites(url),
153
+ },
154
+ {
155
+ endpoint: "contacts/invites",
156
+ method: "POST",
157
+ handler: async ({ req }) => handleCreateInvite(req),
158
+ },
159
+ {
160
+ endpoint: "contacts/invites/redeem",
161
+ method: "POST",
162
+ handler: async ({ req }) => handleRedeemInvite(req),
163
+ },
164
+ {
165
+ endpoint: "contacts/invites/:id",
166
+ method: "DELETE",
167
+ policyKey: "contacts/invites",
168
+ handler: ({ params }) => handleRevokeInvite(params.id),
169
+ },
170
+ ];
171
+ }
@@ -18,6 +18,7 @@ import { resetDb } from "../../memory/db-connection.js";
18
18
  import { getLogger } from "../../util/logger.js";
19
19
  import { getDbPath, getWorkspaceConfigPath } from "../../util/platform.js";
20
20
  import { httpError } from "../http-errors.js";
21
+ import type { RouteDefinition } from "../http-router.js";
21
22
  import { buildExportVBundle } from "../migrations/vbundle-builder.js";
22
23
  import {
23
24
  analyzeImport,
@@ -432,3 +433,32 @@ export async function handleMigrationImport(req: Request): Promise<Response> {
432
433
  );
433
434
  }
434
435
  }
436
+
437
+ // ---------------------------------------------------------------------------
438
+ // Route definitions
439
+ // ---------------------------------------------------------------------------
440
+
441
+ export function migrationRouteDefinitions(): RouteDefinition[] {
442
+ return [
443
+ {
444
+ endpoint: "migrations/validate",
445
+ method: "POST",
446
+ handler: async ({ req }) => handleMigrationValidate(req),
447
+ },
448
+ {
449
+ endpoint: "migrations/export",
450
+ method: "POST",
451
+ handler: async ({ req }) => handleMigrationExport(req),
452
+ },
453
+ {
454
+ endpoint: "migrations/import-preflight",
455
+ method: "POST",
456
+ handler: async ({ req }) => handleMigrationImportPreflight(req),
457
+ },
458
+ {
459
+ endpoint: "migrations/import",
460
+ method: "POST",
461
+ handler: async ({ req }) => handleMigrationImport(req),
462
+ },
463
+ ];
464
+ }
@@ -14,6 +14,7 @@ import { DAEMON_INTERNAL_ASSISTANT_ID } from "../assistant-scope.js";
14
14
  import { mintCredentialPair } from "../auth/credential-service.js";
15
15
  import { ensureVellumGuardianBinding } from "../guardian-vellum-migration.js";
16
16
  import { httpError } from "../http-errors.js";
17
+ import type { RouteDefinition } from "../http-router.js";
17
18
 
18
19
  const log = getLogger("runtime-http");
19
20
 
@@ -404,3 +405,20 @@ export function handlePairingStatus(
404
405
 
405
406
  return Response.json({ status: entry.status });
406
407
  }
408
+
409
+ // ---------------------------------------------------------------------------
410
+ // Route definitions
411
+ // ---------------------------------------------------------------------------
412
+
413
+ export function pairingRouteDefinitions(deps: {
414
+ getPairingContext: () => PairingHandlerContext;
415
+ }): RouteDefinition[] {
416
+ return [
417
+ {
418
+ endpoint: "pairing/register",
419
+ method: "POST",
420
+ handler: async ({ req }) =>
421
+ handlePairingRegister(req, deps.getPairingContext()),
422
+ },
423
+ ];
424
+ }
@@ -12,6 +12,7 @@ import {
12
12
  } from "../../tools/credentials/metadata-store.js";
13
13
  import { getLogger } from "../../util/logger.js";
14
14
  import { httpError } from "../http-errors.js";
15
+ import type { RouteDefinition } from "../http-router.js";
15
16
 
16
17
  const log = getLogger("runtime-http");
17
18
 
@@ -170,3 +171,22 @@ export async function handleDeleteSecret(req: Request): Promise<Response> {
170
171
  return httpError("INTERNAL_ERROR", message, 500);
171
172
  }
172
173
  }
174
+
175
+ // ---------------------------------------------------------------------------
176
+ // Route definitions
177
+ // ---------------------------------------------------------------------------
178
+
179
+ export function secretRouteDefinitions(): RouteDefinition[] {
180
+ return [
181
+ {
182
+ endpoint: "secrets",
183
+ method: "POST",
184
+ handler: async ({ req }) => handleAddSecret(req),
185
+ },
186
+ {
187
+ endpoint: "secrets",
188
+ method: "DELETE",
189
+ handler: async ({ req }) => handleDeleteSecret(req),
190
+ },
191
+ ];
192
+ }
@@ -6,6 +6,7 @@
6
6
  */
7
7
  import { getLogger } from "../../util/logger.js";
8
8
  import { httpError } from "../http-errors.js";
9
+ import type { RouteDefinition } from "../http-router.js";
9
10
 
10
11
  const log = getLogger("surface-action-routes");
11
12
 
@@ -74,3 +75,28 @@ export async function handleSurfaceAction(
74
75
  return httpError("INTERNAL_ERROR", "Failed to handle surface action", 500);
75
76
  }
76
77
  }
78
+
79
+ // ---------------------------------------------------------------------------
80
+ // Route definitions
81
+ // ---------------------------------------------------------------------------
82
+
83
+ export function surfaceActionRouteDefinitions(deps: {
84
+ findSession?: SessionLookup;
85
+ }): RouteDefinition[] {
86
+ return [
87
+ {
88
+ endpoint: "surface-actions",
89
+ method: "POST",
90
+ handler: async ({ req }) => {
91
+ if (!deps.findSession) {
92
+ return httpError(
93
+ "NOT_IMPLEMENTED",
94
+ "Surface actions not available",
95
+ 501,
96
+ );
97
+ }
98
+ return handleSurfaceAction(req, deps.findSession);
99
+ },
100
+ },
101
+ ];
102
+ }
@@ -13,6 +13,7 @@ import {
13
13
  } from "../../permissions/trust-store.js";
14
14
  import { getLogger } from "../../util/logger.js";
15
15
  import { httpError } from "../http-errors.js";
16
+ import type { RouteDefinition } from "../http-router.js";
16
17
 
17
18
  const log = getLogger("trust-rules-routes");
18
19
 
@@ -151,3 +152,33 @@ export async function handleUpdateTrustRuleManage(
151
152
  return httpError("INTERNAL_ERROR", "Failed to update trust rule", 500);
152
153
  }
153
154
  }
155
+
156
+ // ---------------------------------------------------------------------------
157
+ // Route definitions
158
+ // ---------------------------------------------------------------------------
159
+
160
+ export function trustRulesRouteDefinitions(): RouteDefinition[] {
161
+ return [
162
+ {
163
+ endpoint: "trust-rules/manage",
164
+ method: "GET",
165
+ handler: () => handleListTrustRules(),
166
+ },
167
+ {
168
+ endpoint: "trust-rules/manage",
169
+ method: "POST",
170
+ handler: async ({ req }) => handleAddTrustRuleManage(req),
171
+ },
172
+ {
173
+ endpoint: "trust-rules/manage/:id",
174
+ method: "DELETE",
175
+ handler: ({ params }) => handleRemoveTrustRuleManage(params.id),
176
+ },
177
+ {
178
+ endpoint: "trust-rules/manage/:id",
179
+ method: "PATCH",
180
+ handler: async ({ req, params }) =>
181
+ handleUpdateTrustRuleManage(req, params.id),
182
+ },
183
+ ];
184
+ }
@@ -46,6 +46,7 @@ import {
46
46
  upsertCredentialMetadata,
47
47
  } from "../../tools/credentials/metadata-store.js";
48
48
  import { mintDaemonDeliveryToken } from "../auth/token-service.js";
49
+ import type { RouteDefinition } from "../http-router.js";
49
50
 
50
51
  // ---------------------------------------------------------------------------
51
52
  // Shared helpers
@@ -1126,3 +1127,81 @@ export async function handleSmsDoctor(): Promise<Response> {
1126
1127
  },
1127
1128
  });
1128
1129
  }
1130
+
1131
+ // ---------------------------------------------------------------------------
1132
+ // Route definitions
1133
+ // ---------------------------------------------------------------------------
1134
+
1135
+ export function twilioRouteDefinitions(): RouteDefinition[] {
1136
+ return [
1137
+ {
1138
+ endpoint: "integrations/twilio/config",
1139
+ method: "GET",
1140
+ handler: () => handleGetTwilioConfig(),
1141
+ },
1142
+ {
1143
+ endpoint: "integrations/twilio/credentials",
1144
+ method: "POST",
1145
+ handler: async ({ req }) => handleSetTwilioCredentials(req),
1146
+ },
1147
+ {
1148
+ endpoint: "integrations/twilio/credentials",
1149
+ method: "DELETE",
1150
+ handler: () => handleClearTwilioCredentials(),
1151
+ },
1152
+ {
1153
+ endpoint: "integrations/twilio/numbers",
1154
+ method: "GET",
1155
+ handler: async () => handleListTwilioNumbers(),
1156
+ },
1157
+ {
1158
+ endpoint: "integrations/twilio/numbers/provision",
1159
+ method: "POST",
1160
+ handler: async ({ req }) => handleProvisionTwilioNumber(req),
1161
+ },
1162
+ {
1163
+ endpoint: "integrations/twilio/numbers/assign",
1164
+ method: "POST",
1165
+ handler: async ({ req }) => handleAssignTwilioNumber(req),
1166
+ },
1167
+ {
1168
+ endpoint: "integrations/twilio/numbers/release",
1169
+ method: "POST",
1170
+ handler: async ({ req }) => handleReleaseTwilioNumber(req),
1171
+ },
1172
+ {
1173
+ endpoint: "integrations/twilio/sms/compliance",
1174
+ method: "GET",
1175
+ handler: async () => handleGetSmsCompliance(),
1176
+ },
1177
+ {
1178
+ endpoint: "integrations/twilio/sms/compliance/tollfree",
1179
+ method: "POST",
1180
+ handler: async ({ req }) => handleSubmitTollfreeVerification(req),
1181
+ },
1182
+ {
1183
+ endpoint: "integrations/twilio/sms/compliance/tollfree/:sid",
1184
+ method: "PATCH",
1185
+ policyKey: "integrations/twilio/sms/compliance/tollfree",
1186
+ handler: async ({ req, params }) =>
1187
+ handleUpdateTollfreeVerification(req, params.sid),
1188
+ },
1189
+ {
1190
+ endpoint: "integrations/twilio/sms/compliance/tollfree/:sid",
1191
+ method: "DELETE",
1192
+ policyKey: "integrations/twilio/sms/compliance/tollfree",
1193
+ handler: async ({ params }) =>
1194
+ handleDeleteTollfreeVerification(params.sid),
1195
+ },
1196
+ {
1197
+ endpoint: "integrations/twilio/sms/test",
1198
+ method: "POST",
1199
+ handler: async ({ req }) => handleSmsSendTest(req),
1200
+ },
1201
+ {
1202
+ endpoint: "integrations/twilio/sms/doctor",
1203
+ method: "POST",
1204
+ handler: async () => handleSmsDoctor(),
1205
+ },
1206
+ ];
1207
+ }
@@ -34,13 +34,11 @@ export function detectScheduleSyntax(
34
34
  * Resolution order:
35
35
  * 1. If explicit `syntax` is provided, use it
36
36
  * 2. If `expression` is provided, auto-detect from expression
37
- * 3. If `legacyCronExpression` is provided, treat as cron
38
- * 4. Return null if nothing resolved
37
+ * 3. Return null if nothing resolved
39
38
  */
40
39
  export function normalizeScheduleSyntax(input: {
41
40
  syntax?: ScheduleSyntax;
42
41
  expression?: string;
43
- legacyCronExpression?: string;
44
42
  }): { syntax: ScheduleSyntax; expression: string } | null {
45
43
  // Explicit syntax + expression
46
44
  if (input.syntax && input.expression) {
@@ -60,13 +58,5 @@ export function normalizeScheduleSyntax(input: {
60
58
  return null;
61
59
  }
62
60
 
63
- // Legacy cron_expression fallback
64
- if (input.legacyCronExpression) {
65
- return {
66
- syntax: input.syntax ?? "cron",
67
- expression: input.legacyCronExpression,
68
- };
69
- }
70
-
71
61
  return null;
72
62
  }
@@ -47,13 +47,19 @@ export async function executeFollowupCreate(
47
47
  };
48
48
  }
49
49
 
50
+ if (input.reminder_cron_id !== undefined) {
51
+ return {
52
+ content:
53
+ 'Error: "reminder_cron_id" is deprecated. Use "reminder_schedule_id" instead.',
54
+ isError: true,
55
+ };
56
+ }
57
+
50
58
  const contactId = input.contact_id as string | undefined;
51
59
  const expectedResponseHours = input.expected_response_hours as
52
60
  | number
53
61
  | undefined;
54
- // Canonical: reminder_schedule_id; deprecated alias: reminder_cron_id
55
- const reminderScheduleId = (input.reminder_schedule_id ??
56
- input.reminder_cron_id) as string | undefined;
62
+ const reminderScheduleId = input.reminder_schedule_id as string | undefined;
57
63
 
58
64
  // Validate contact exists if provided
59
65
  if (contactId) {
@@ -18,23 +18,6 @@ export function mcpToolName(serverId: string, toolName: string): string {
18
18
  return `mcp__${serverId}__${toolName}`;
19
19
  }
20
20
 
21
- /**
22
- * Parse a namespaced MCP tool name back into serverId and original tool name.
23
- * Returns null if the name doesn't match the MCP naming convention.
24
- */
25
- export function parseMcpToolName(
26
- name: string,
27
- ): { serverId: string; toolName: string } | null {
28
- if (!name.startsWith("mcp__")) return null;
29
- const prefixRemoved = name.slice(5); // remove 'mcp__'
30
- const firstSep = prefixRemoved.indexOf("__");
31
- if (firstSep === -1) return null;
32
- return {
33
- serverId: prefixRemoved.slice(0, firstSep),
34
- toolName: prefixRemoved.slice(firstSep + 2),
35
- };
36
- }
37
-
38
21
  export interface McpToolMetadata {
39
22
  name: string;
40
23
  description: string;
@@ -93,9 +93,3 @@ export const memoryUpdateDefinition: ToolDefinition = {
93
93
  required: ["memory_id", "statement"],
94
94
  },
95
95
  };
96
-
97
- export const memoryToolDefinitions: ToolDefinition[] = [
98
- memorySearchDefinition,
99
- memorySaveDefinition,
100
- memoryUpdateDefinition,
101
- ];
@@ -42,6 +42,32 @@ const DEFAULT_CONFIG: ProxySessionConfig = {
42
42
  maxSessionsPerConversation: 3,
43
43
  };
44
44
 
45
+ /**
46
+ * Host patterns that are allowed by default through the proxy policy engine,
47
+ * regardless of session configuration. Supports exact matches (e.g.
48
+ * `"localhost"`) and wildcard subdomain patterns (e.g. `"*.vellum.ai"`
49
+ * matches `platform.vellum.ai`, `dev-platform.vellum.ai`, etc.).
50
+ */
51
+ const ALLOWED_HOST_PATTERNS: readonly string[] = ["*.vellum.ai", "localhost"];
52
+
53
+ /**
54
+ * Returns `true` when `hostname` matches any entry in
55
+ * {@link ALLOWED_HOST_PATTERNS}.
56
+ */
57
+ function isAllowedHost(hostname: string): boolean {
58
+ for (const pattern of ALLOWED_HOST_PATTERNS) {
59
+ if (pattern.startsWith("*.")) {
60
+ const suffix = pattern.slice(1); // e.g. ".vellum.ai"
61
+ if (hostname.endsWith(suffix) || hostname === pattern.slice(2)) {
62
+ return true;
63
+ }
64
+ } else if (hostname === pattern) {
65
+ return true;
66
+ }
67
+ }
68
+ return false;
69
+ }
70
+
45
71
  interface ManagedSession {
46
72
  session: ProxySession;
47
73
  server: Server | null;
@@ -309,6 +335,13 @@ export async function startSession(
309
335
  reqPath: string,
310
336
  scheme: "http" | "https",
311
337
  ) => {
338
+ // Allowed hosts are always passed through the proxy, regardless of
339
+ // session configuration or credential state.
340
+ if (isAllowedHost(hostname)) {
341
+ log.debug({ hostname }, "Allowing always-permitted host");
342
+ return {};
343
+ }
344
+
312
345
  const decision = evaluateRequestWithApproval(
313
346
  hostname,
314
347
  port,
@@ -506,7 +539,8 @@ export async function getOrStartSession(
506
539
  ): Promise<{ session: ProxySession; created: boolean }> {
507
540
  const requestedHost = options?.listenHost ?? "127.0.0.1";
508
541
 
509
- // Fast path — session already active with matching credentials and listen host, no lock needed.
542
+ // Fast path — session already active with matching credentials and listen
543
+ // host, no lock needed.
510
544
  const existing = getActiveSession(conversationId);
511
545
  if (existing && credentialIdsMatch(existing.credentialIds, credentialIds)) {
512
546
  const managed = sessions.get(existing.id);
@@ -533,8 +567,9 @@ export async function getOrStartSession(
533
567
  return { session, created: false };
534
568
  }
535
569
  }
536
- // Credential or listenHost mismatch — tear down and loop back to re-check
537
- // whether another waiter has already started a replacement session.
570
+ // Credential or listenHost mismatch — tear down and loop back to
571
+ // re-check whether another waiter has already started a replacement
572
+ // session.
538
573
  await stopSession(session.id);
539
574
  }
540
575
 
@@ -31,16 +31,14 @@ export async function executeScheduleCreate(
31
31
  };
32
32
  }
33
33
 
34
- // Resolve syntax and expression from new or legacy fields
35
34
  const resolved = normalizeScheduleSyntax({
36
35
  syntax: input.syntax as "cron" | "rrule" | undefined,
37
36
  expression: input.expression as string | undefined,
38
- legacyCronExpression: input.cron_expression as string | undefined,
39
37
  });
40
38
 
41
39
  if (!resolved) {
42
40
  return {
43
- content: "Error: expression (or cron_expression) is required",
41
+ content: "Error: expression is required",
44
42
  isError: true,
45
43
  };
46
44
  }
@@ -20,6 +20,14 @@ export async function executeScheduleUpdate(
20
20
  return { content: "Error: job_id is required", isError: true };
21
21
  }
22
22
 
23
+ if (input.cron_expression !== undefined) {
24
+ return {
25
+ content:
26
+ 'Error: "cron_expression" is deprecated. Use "expression" instead.',
27
+ isError: true,
28
+ };
29
+ }
30
+
23
31
  const updates: Record<string, unknown> = {};
24
32
  if (input.name !== undefined) updates.name = input.name;
25
33
  if (input.timezone !== undefined) updates.timezone = input.timezone;
@@ -27,13 +35,10 @@ export async function executeScheduleUpdate(
27
35
  if (input.enabled !== undefined) updates.enabled = input.enabled;
28
36
 
29
37
  // Auto-detect syntax when expression changes without explicit syntax
30
- const hasExpression =
31
- input.expression !== undefined || input.cron_expression !== undefined;
32
- if (hasExpression || input.syntax !== undefined) {
38
+ if (input.expression !== undefined || input.syntax !== undefined) {
33
39
  const resolved = normalizeScheduleSyntax({
34
40
  syntax: input.syntax as "cron" | "rrule" | undefined,
35
41
  expression: input.expression as string | undefined,
36
- legacyCronExpression: input.cron_expression as string | undefined,
37
42
  });
38
43
  if (resolved) {
39
44
  updates.syntax = resolved.syntax;
@@ -42,8 +47,6 @@ export async function executeScheduleUpdate(
42
47
  updates.expression = input.expression;
43
48
  const detected = detectScheduleSyntax(input.expression as string);
44
49
  if (detected) updates.syntax = detected;
45
- } else if (input.cron_expression !== undefined) {
46
- updates.cronExpression = input.cron_expression;
47
50
  }
48
51
  // When only syntax is provided (no expression), normalizeScheduleSyntax returns null
49
52
  // but we still need to persist the explicit syntax value.