@vellumai/assistant 0.5.6 → 0.5.7

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 (305) hide show
  1. package/.env.example +16 -2
  2. package/ARCHITECTURE.md +6 -75
  3. package/Dockerfile +1 -1
  4. package/README.md +0 -2
  5. package/bun.lock +0 -414
  6. package/docs/architecture/keychain-broker.md +45 -240
  7. package/docs/architecture/security.md +0 -17
  8. package/docs/credential-execution-service.md +2 -2
  9. package/node_modules/@vellumai/ces-contracts/package.json +1 -0
  10. package/node_modules/@vellumai/ces-contracts/src/rpc.ts +119 -0
  11. package/node_modules/@vellumai/credential-storage/package.json +1 -0
  12. package/node_modules/@vellumai/egress-proxy/package.json +1 -0
  13. package/package.json +2 -3
  14. package/src/__tests__/actor-token-service.test.ts +0 -114
  15. package/src/__tests__/assistant-feature-flags-integration.test.ts +30 -29
  16. package/src/__tests__/browser-skill-endstate.test.ts +6 -5
  17. package/src/__tests__/btw-routes.test.ts +0 -39
  18. package/src/__tests__/call-domain.test.ts +0 -128
  19. package/src/__tests__/ces-rpc-credential-backend.test.ts +199 -0
  20. package/src/__tests__/channel-approval-routes.test.ts +0 -5
  21. package/src/__tests__/channel-readiness-service.test.ts +1 -60
  22. package/src/__tests__/checker.test.ts +4 -2
  23. package/src/__tests__/cli-command-risk-guard.test.ts +112 -0
  24. package/src/__tests__/config-schema-cmd.test.ts +0 -1
  25. package/src/__tests__/config-schema.test.ts +1 -1
  26. package/src/__tests__/conversation-attention-telegram.test.ts +0 -5
  27. package/src/__tests__/conversation-init.benchmark.test.ts +0 -2
  28. package/src/__tests__/conversation-skill-tools.test.ts +0 -54
  29. package/src/__tests__/conversation-title-service.test.ts +87 -0
  30. package/src/__tests__/credential-execution-feature-gates.test.ts +28 -14
  31. package/src/__tests__/credential-execution-managed-contract.test.ts +33 -18
  32. package/src/__tests__/credential-security-e2e.test.ts +0 -66
  33. package/src/__tests__/credential-security-invariants.test.ts +4 -45
  34. package/src/__tests__/credentials-cli.test.ts +78 -0
  35. package/src/__tests__/db-migration-rollback.test.ts +2015 -1
  36. package/src/__tests__/docker-signing-key-bootstrap.test.ts +34 -143
  37. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +6 -4
  38. package/src/__tests__/guardian-routing-state.test.ts +0 -5
  39. package/src/__tests__/host-shell-tool.test.ts +6 -7
  40. package/src/__tests__/http-user-message-parity.test.ts +3 -103
  41. package/src/__tests__/inbound-invite-redemption.test.ts +0 -4
  42. package/src/__tests__/inline-skill-load-permissions.test.ts +6 -8
  43. package/src/__tests__/intent-routing.test.ts +0 -13
  44. package/src/__tests__/jobs-store-qdrant-breaker.test.ts +178 -0
  45. package/src/__tests__/keychain-broker-client.test.ts +161 -22
  46. package/src/__tests__/memory-jobs-worker-backoff.test.ts +150 -0
  47. package/src/__tests__/migration-export-http.test.ts +2 -2
  48. package/src/__tests__/migration-import-commit-http.test.ts +2 -2
  49. package/src/__tests__/migration-import-preflight-http.test.ts +2 -2
  50. package/src/__tests__/migration-validate-http.test.ts +2 -2
  51. package/src/__tests__/non-member-access-request.test.ts +0 -5
  52. package/src/__tests__/notification-decision-fallback.test.ts +4 -0
  53. package/src/__tests__/notification-decision-identity.test.ts +4 -0
  54. package/src/__tests__/permission-types.test.ts +1 -0
  55. package/src/__tests__/provider-managed-proxy-integration.test.ts +5 -6
  56. package/src/__tests__/qdrant-manager.test.ts +28 -2
  57. package/src/__tests__/registry.test.ts +0 -6
  58. package/src/__tests__/runtime-attachment-metadata.test.ts +0 -4
  59. package/src/__tests__/secret-routes-managed-proxy.test.ts +0 -4
  60. package/src/__tests__/secure-keys.test.ts +83 -263
  61. package/src/__tests__/shell-identity.test.ts +96 -6
  62. package/src/__tests__/skill-feature-flags-integration.test.ts +22 -14
  63. package/src/__tests__/skill-feature-flags.test.ts +46 -45
  64. package/src/__tests__/skill-load-feature-flag.test.ts +7 -10
  65. package/src/__tests__/skill-load-inline-command.test.ts +8 -12
  66. package/src/__tests__/skill-load-inline-includes.test.ts +6 -10
  67. package/src/__tests__/skill-load-tool.test.ts +0 -2
  68. package/src/__tests__/skill-projection-feature-flag.test.ts +33 -29
  69. package/src/__tests__/skills.test.ts +0 -2
  70. package/src/__tests__/slack-inbound-verification.test.ts +0 -4
  71. package/src/__tests__/suggestion-routes.test.ts +1 -32
  72. package/src/__tests__/system-prompt.test.ts +0 -1
  73. package/src/__tests__/tool-executor-shell-integration.test.ts +5 -3
  74. package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +0 -5
  75. package/src/__tests__/trusted-contact-multichannel.test.ts +0 -4
  76. package/src/__tests__/update-bulletin.test.ts +0 -2
  77. package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +6 -9
  78. package/src/__tests__/voice-scoped-grant-consumer.test.ts +0 -6
  79. package/src/__tests__/workspace-migration-015-migrate-credentials-to-keychain.test.ts +252 -0
  80. package/src/__tests__/workspace-migration-016-migrate-credentials-from-keychain.test.ts +218 -0
  81. package/src/__tests__/workspace-migration-down-functions.test.ts +1009 -0
  82. package/src/__tests__/workspace-migrations-runner.test.ts +114 -0
  83. package/src/calls/audio-store.test.ts +97 -0
  84. package/src/calls/audio-store.ts +205 -0
  85. package/src/calls/call-controller.ts +85 -7
  86. package/src/calls/call-domain.ts +3 -0
  87. package/src/calls/call-store.ts +10 -3
  88. package/src/calls/fish-audio-client.ts +117 -0
  89. package/src/calls/relay-server.ts +27 -0
  90. package/src/calls/twilio-routes.ts +2 -1
  91. package/src/calls/types.ts +1 -0
  92. package/src/calls/voice-ingress-preflight.ts +0 -42
  93. package/src/calls/voice-quality.ts +26 -5
  94. package/src/calls/voice-session-bridge.ts +6 -12
  95. package/src/cli/commands/config.ts +1 -4
  96. package/src/cli/commands/credentials.ts +34 -4
  97. package/src/cli/commands/oauth/index.ts +7 -0
  98. package/src/cli/commands/oauth/platform.ts +179 -0
  99. package/src/cli/commands/platform.ts +3 -3
  100. package/src/config/assistant-feature-flags.ts +186 -5
  101. package/src/config/bundled-skills/messaging/SKILL.md +5 -5
  102. package/src/config/bundled-skills/phone-calls/TOOLS.json +4 -0
  103. package/src/config/bundled-skills/settings/TOOLS.json +2 -2
  104. package/src/config/bundled-skills/settings/tools/voice-config-update.ts +42 -0
  105. package/src/config/bundled-tool-registry.ts +1 -11
  106. package/src/config/env-registry.ts +1 -1
  107. package/src/config/env.ts +8 -14
  108. package/src/config/feature-flag-registry.json +48 -8
  109. package/src/config/loader.ts +98 -31
  110. package/src/config/schema.ts +4 -13
  111. package/src/config/schemas/calls.ts +13 -0
  112. package/src/config/schemas/fish-audio.ts +39 -0
  113. package/src/config/schemas/security.ts +0 -4
  114. package/src/config/types.ts +0 -1
  115. package/src/contacts/contact-store.ts +39 -0
  116. package/src/contacts/types.ts +2 -0
  117. package/src/credential-execution/approval-bridge.ts +1 -0
  118. package/src/credential-execution/executable-discovery.ts +28 -4
  119. package/src/credential-execution/feature-gates.ts +16 -0
  120. package/src/credential-execution/process-manager.ts +38 -0
  121. package/src/daemon/assistant-attachments.ts +9 -0
  122. package/src/daemon/config-watcher.ts +5 -0
  123. package/src/daemon/conversation-tool-setup.ts +0 -105
  124. package/src/daemon/conversation.ts +10 -1
  125. package/src/daemon/handlers/config-vercel.ts +92 -0
  126. package/src/daemon/handlers/skills.ts +2 -15
  127. package/src/daemon/install-symlink.ts +195 -0
  128. package/src/daemon/lifecycle.ts +227 -51
  129. package/src/daemon/message-types/conversations.ts +3 -4
  130. package/src/daemon/message-types/diagnostics.ts +3 -22
  131. package/src/daemon/message-types/messages.ts +0 -2
  132. package/src/daemon/message-types/upgrades.ts +8 -0
  133. package/src/daemon/server.ts +30 -92
  134. package/src/events/domain-events.ts +2 -1
  135. package/src/inbound/platform-callback-registration.ts +3 -3
  136. package/src/instrument.ts +8 -5
  137. package/src/memory/conversation-title-service.ts +50 -1
  138. package/src/memory/db-init.ts +12 -0
  139. package/src/memory/items-extractor.ts +15 -1
  140. package/src/memory/job-handlers/conversation-starters.ts +4 -1
  141. package/src/memory/jobs-store.ts +30 -5
  142. package/src/memory/jobs-worker.ts +31 -7
  143. package/src/memory/migrations/001-job-deferrals.ts +19 -0
  144. package/src/memory/migrations/004-entity-relation-dedup.ts +10 -0
  145. package/src/memory/migrations/005-fingerprint-scope-unique.ts +76 -0
  146. package/src/memory/migrations/006-scope-salted-fingerprints.ts +50 -0
  147. package/src/memory/migrations/007-assistant-id-to-self.ts +10 -0
  148. package/src/memory/migrations/008-remove-assistant-id-columns.ts +34 -0
  149. package/src/memory/migrations/009-llm-usage-events-drop-assistant-id.ts +26 -0
  150. package/src/memory/migrations/014-backfill-inbox-thread-state.ts +10 -0
  151. package/src/memory/migrations/015-drop-active-search-index.ts +17 -0
  152. package/src/memory/migrations/019-notification-tables-schema-migration.ts +12 -0
  153. package/src/memory/migrations/020-rename-macos-ios-channel-to-vellum.ts +121 -0
  154. package/src/memory/migrations/024-embedding-vector-blob.ts +74 -0
  155. package/src/memory/migrations/026a-embeddings-nullable-vector-json.ts +82 -0
  156. package/src/memory/migrations/036-normalize-phone-identities.ts +11 -0
  157. package/src/memory/migrations/116-messages-fts.ts +106 -1
  158. package/src/memory/migrations/126-backfill-guardian-principal-id.ts +52 -0
  159. package/src/memory/migrations/127-guardian-principal-id-not-null.ts +77 -0
  160. package/src/memory/migrations/134-contacts-notes-column.ts +13 -0
  161. package/src/memory/migrations/135-backfill-contact-interaction-stats.ts +20 -0
  162. package/src/memory/migrations/136-drop-assistant-id-columns.ts +52 -0
  163. package/src/memory/migrations/140-backfill-usage-cache-accounting.ts +13 -0
  164. package/src/memory/migrations/141-rename-verification-table.ts +54 -0
  165. package/src/memory/migrations/142-rename-verification-session-id-column.ts +25 -0
  166. package/src/memory/migrations/143-rename-guardian-verification-values.ts +35 -0
  167. package/src/memory/migrations/144-rename-voice-to-phone.ts +136 -0
  168. package/src/memory/migrations/145-drop-accounts-table.ts +32 -0
  169. package/src/memory/migrations/147-migrate-reminders-to-schedules.ts +14 -1
  170. package/src/memory/migrations/148-drop-reminders-table.ts +35 -1
  171. package/src/memory/migrations/150-oauth-apps-client-secret-path.ts +69 -1
  172. package/src/memory/migrations/162-guardian-timestamps-epoch-ms.ts +290 -0
  173. package/src/memory/migrations/169-rename-gmail-provider-key-to-google.ts +51 -1
  174. package/src/memory/migrations/174-rename-thread-starters-table.ts +47 -1
  175. package/src/memory/migrations/176-drop-capability-card-state.ts +13 -0
  176. package/src/memory/migrations/180-backfill-inline-attachments-to-disk.ts +16 -0
  177. package/src/memory/migrations/181-rename-thread-starters-checkpoints.ts +28 -1
  178. package/src/memory/migrations/190-call-session-skip-disclosure.ts +15 -0
  179. package/src/memory/migrations/191-backfill-audio-attachment-mime-types.ts +64 -0
  180. package/src/memory/migrations/192-contacts-user-file-column.ts +15 -0
  181. package/src/memory/migrations/index.ts +4 -0
  182. package/src/memory/migrations/registry.ts +90 -0
  183. package/src/memory/migrations/validate-migration-state.ts +137 -11
  184. package/src/memory/qdrant-circuit-breaker.ts +9 -0
  185. package/src/memory/qdrant-manager.ts +64 -7
  186. package/src/memory/schema/calls.ts +1 -0
  187. package/src/memory/schema/contacts.ts +1 -0
  188. package/src/notifications/decision-engine.ts +4 -1
  189. package/src/oauth/connection-resolver.ts +6 -4
  190. package/src/permissions/checker.ts +0 -38
  191. package/src/permissions/shell-identity.ts +76 -22
  192. package/src/permissions/types.ts +4 -2
  193. package/src/platform/client.ts +35 -7
  194. package/src/prompts/persona-resolver.ts +138 -0
  195. package/src/prompts/system-prompt.ts +36 -4
  196. package/src/prompts/templates/users/default.md +1 -0
  197. package/src/providers/registry.ts +27 -40
  198. package/src/runtime/auth/__tests__/credential-service.test.ts +0 -1
  199. package/src/runtime/auth/__tests__/external-assistant-id.test.ts +13 -68
  200. package/src/runtime/auth/external-assistant-id.ts +13 -59
  201. package/src/runtime/auth/route-policy.ts +15 -1
  202. package/src/runtime/auth/token-service.ts +43 -138
  203. package/src/runtime/channel-readiness-service.ts +1 -16
  204. package/src/runtime/http-server.ts +27 -2
  205. package/src/runtime/middleware/error-handler.ts +1 -9
  206. package/src/runtime/routes/audio-routes.ts +40 -0
  207. package/src/runtime/routes/btw-routes.ts +0 -17
  208. package/src/runtime/routes/conversation-query-routes.ts +63 -1
  209. package/src/runtime/routes/conversation-routes.ts +4 -44
  210. package/src/runtime/routes/diagnostics-routes.ts +1 -477
  211. package/src/runtime/routes/identity-routes.ts +18 -29
  212. package/src/runtime/routes/inbound-stages/secret-ingress-check.ts +4 -33
  213. package/src/runtime/routes/inbound-stages/transcribe-audio.test.ts +1 -1
  214. package/src/runtime/routes/integrations/vercel.ts +89 -0
  215. package/src/runtime/routes/log-export-routes.ts +5 -0
  216. package/src/runtime/routes/memory-item-routes.ts +24 -6
  217. package/src/runtime/routes/migration-rollback-routes.ts +209 -0
  218. package/src/runtime/routes/migration-routes.ts +17 -1
  219. package/src/runtime/routes/notification-routes.ts +58 -0
  220. package/src/runtime/routes/schedule-routes.ts +65 -0
  221. package/src/runtime/routes/settings-routes.ts +41 -1
  222. package/src/runtime/routes/tts-routes.ts +86 -0
  223. package/src/runtime/routes/upgrade-broadcast-routes.ts +26 -2
  224. package/src/runtime/routes/workspace-commit-routes.ts +62 -0
  225. package/src/runtime/routes/workspace-routes.test.ts +22 -1
  226. package/src/runtime/routes/workspace-routes.ts +1 -1
  227. package/src/runtime/routes/workspace-utils.ts +86 -2
  228. package/src/security/ces-credential-client.ts +59 -22
  229. package/src/security/ces-rpc-credential-backend.ts +85 -0
  230. package/src/security/credential-backend.ts +12 -88
  231. package/src/security/keychain-broker-client.ts +10 -2
  232. package/src/security/secure-keys.ts +94 -113
  233. package/src/skills/catalog-install.ts +13 -7
  234. package/src/telemetry/usage-telemetry-reporter.ts +4 -2
  235. package/src/tools/calls/call-start.ts +1 -0
  236. package/src/tools/executor.ts +0 -4
  237. package/src/tools/network/script-proxy/session-manager.ts +19 -4
  238. package/src/tools/network/web-fetch.ts +3 -1
  239. package/src/tools/skills/execute.ts +1 -1
  240. package/src/tools/types.ts +0 -8
  241. package/src/util/errors.ts +0 -12
  242. package/src/util/platform.ts +3 -50
  243. package/src/workspace/git-service.ts +5 -2
  244. package/src/workspace/migrations/001-avatar-rename.ts +15 -0
  245. package/src/workspace/migrations/003-seed-device-id.ts +17 -1
  246. package/src/workspace/migrations/004-extract-collect-usage-data.ts +33 -0
  247. package/src/workspace/migrations/005-add-send-diagnostics.ts +3 -0
  248. package/src/workspace/migrations/006-services-config.ts +49 -0
  249. package/src/workspace/migrations/007-web-search-provider-rename.ts +27 -0
  250. package/src/workspace/migrations/008-voice-timeout-and-max-steps.ts +3 -0
  251. package/src/workspace/migrations/009-backfill-conversation-disk-view.ts +4 -0
  252. package/src/workspace/migrations/010-app-dir-rename.ts +78 -0
  253. package/src/workspace/migrations/011-backfill-installation-id.ts +11 -0
  254. package/src/workspace/migrations/012-rename-conversation-disk-view-dirs.ts +44 -0
  255. package/src/workspace/migrations/013-repair-conversation-disk-view.ts +5 -0
  256. package/src/workspace/migrations/015-migrate-credentials-to-keychain.ts +153 -0
  257. package/src/workspace/migrations/016-extract-feature-flags-to-protected.ts +156 -0
  258. package/src/workspace/migrations/016-migrate-credentials-from-keychain.ts +150 -0
  259. package/src/workspace/migrations/017-seed-persona-dirs.ts +95 -0
  260. package/src/workspace/migrations/migrate-to-workspace-volume.ts +23 -1
  261. package/src/workspace/migrations/registry.ts +8 -0
  262. package/src/workspace/migrations/runner.ts +106 -2
  263. package/src/workspace/migrations/types.ts +4 -0
  264. package/src/__tests__/claude-code-skill-regression.test.ts +0 -206
  265. package/src/__tests__/claude-code-tool-profiles.test.ts +0 -99
  266. package/src/__tests__/diagnostics-export.test.ts +0 -288
  267. package/src/__tests__/local-gateway-health.test.ts +0 -209
  268. package/src/__tests__/secret-ingress-handler.test.ts +0 -120
  269. package/src/__tests__/swarm-conversation-integration.test.ts +0 -358
  270. package/src/__tests__/swarm-dag-pathological.test.ts +0 -547
  271. package/src/__tests__/swarm-orchestrator.test.ts +0 -463
  272. package/src/__tests__/swarm-plan-validator.test.ts +0 -384
  273. package/src/__tests__/swarm-recursion.test.ts +0 -197
  274. package/src/__tests__/swarm-router-planner.test.ts +0 -234
  275. package/src/__tests__/swarm-tool.test.ts +0 -185
  276. package/src/__tests__/swarm-worker-backend.test.ts +0 -144
  277. package/src/__tests__/swarm-worker-runner.test.ts +0 -288
  278. package/src/commands/__tests__/cc-command-registry.test.ts +0 -396
  279. package/src/commands/cc-command-registry.ts +0 -248
  280. package/src/config/bundled-skills/claude-code/SKILL.md +0 -53
  281. package/src/config/bundled-skills/claude-code/TOOLS.json +0 -47
  282. package/src/config/bundled-skills/claude-code/tools/claude-code.ts +0 -12
  283. package/src/config/bundled-skills/orchestration/SKILL.md +0 -33
  284. package/src/config/bundled-skills/orchestration/TOOLS.json +0 -35
  285. package/src/config/bundled-skills/orchestration/tools/swarm-delegate.ts +0 -12
  286. package/src/config/schemas/swarm.ts +0 -82
  287. package/src/logfire.ts +0 -135
  288. package/src/runtime/local-gateway-health.ts +0 -275
  289. package/src/security/secret-ingress.ts +0 -68
  290. package/src/swarm/backend-claude-code.ts +0 -225
  291. package/src/swarm/checkpoint.ts +0 -137
  292. package/src/swarm/graph-utils.ts +0 -53
  293. package/src/swarm/index.ts +0 -55
  294. package/src/swarm/limits.ts +0 -66
  295. package/src/swarm/orchestrator.ts +0 -424
  296. package/src/swarm/plan-validator.ts +0 -117
  297. package/src/swarm/router-planner.ts +0 -162
  298. package/src/swarm/router-prompts.ts +0 -39
  299. package/src/swarm/synthesizer.ts +0 -81
  300. package/src/swarm/types.ts +0 -72
  301. package/src/swarm/worker-backend.ts +0 -131
  302. package/src/swarm/worker-prompts.ts +0 -80
  303. package/src/swarm/worker-runner.ts +0 -170
  304. package/src/tools/claude-code/claude-code.ts +0 -610
  305. package/src/tools/swarm/delegate.ts +0 -205
@@ -17,7 +17,6 @@ mock.module("../../../util/platform.js", () => ({
17
17
  getDataDir: () => testDir,
18
18
  getDbPath: () => join(testDir, "test.db"),
19
19
  normalizeAssistantId: (id: string) => (id === "self" ? "self" : id),
20
- readLockfile: () => ({ assistants: [{ assistantId: "vellum-test-eel" }] }),
21
20
  isMacOS: () => process.platform === "darwin",
22
21
  isLinux: () => process.platform === "linux",
23
22
  isWindows: () => process.platform === "win32",
@@ -1,21 +1,7 @@
1
1
  /**
2
- * Tests for getExternalAssistantId resolution order.
2
+ * Tests for getExternalAssistantId.
3
3
  */
4
- import { afterEach, describe, expect, mock, test } from "bun:test";
5
-
6
- mock.module("../../../util/logger.js", () => ({
7
- getLogger: () =>
8
- new Proxy({} as Record<string, unknown>, {
9
- get: () => () => {},
10
- }),
11
- }));
12
-
13
- // Controllable mock for readLockfile — defaults to null (no lockfile data)
14
- const mockReadLockfile = mock(() => null as Record<string, unknown> | null);
15
-
16
- mock.module("../../../util/platform.js", () => ({
17
- readLockfile: mockReadLockfile,
18
- }));
4
+ import { afterEach, describe, expect, test } from "bun:test";
19
5
 
20
6
  import {
21
7
  getExternalAssistantId,
@@ -24,65 +10,24 @@ import {
24
10
 
25
11
  afterEach(() => {
26
12
  resetExternalAssistantIdCache();
27
- mockReadLockfile.mockReset();
28
- mockReadLockfile.mockImplementation(() => null);
29
- delete process.env.BASE_DATA_DIR;
13
+ delete process.env.VELLUM_ASSISTANT_NAME;
30
14
  });
31
15
 
32
16
  describe("getExternalAssistantId", () => {
33
- test("resolves from lockfile assistants array (most recently hatched)", () => {
34
- mockReadLockfile.mockImplementation(() => ({
35
- assistants: [
36
- { assistantId: "vellum-old-fox", hatchedAt: "2025-01-01T00:00:00Z" },
37
- { assistantId: "vellum-new-eel", hatchedAt: "2025-06-15T12:00:00Z" },
38
- ],
39
- }));
40
- expect(getExternalAssistantId()).toBe("vellum-new-eel");
41
- });
42
-
43
- test("resolves from lockfile with single assistant entry", () => {
44
- mockReadLockfile.mockImplementation(() => ({
45
- assistants: [
46
- { assistantId: "vellum-solo-cat", hatchedAt: "2025-03-01T00:00:00Z" },
47
- ],
48
- }));
49
- expect(getExternalAssistantId()).toBe("vellum-solo-cat");
50
- });
51
-
52
- test("resolves from BASE_DATA_DIR when lockfile has no data", () => {
53
- process.env.BASE_DATA_DIR = "/tmp/vellum/assistants/vellum-true-eel";
54
- expect(getExternalAssistantId()).toBe("vellum-true-eel");
55
- });
56
-
57
- test("resolves from BASE_DATA_DIR with trailing slash", () => {
58
- process.env.BASE_DATA_DIR = "/tmp/vellum/assistants/vellum-cool-heron/";
59
- expect(getExternalAssistantId()).toBe("vellum-cool-heron");
60
- });
61
-
62
- test("resolves from BASE_DATA_DIR with Windows-style backslashes", () => {
63
- process.env.BASE_DATA_DIR =
64
- "C:\\Users\\user\\.local\\share\\vellum\\assistants\\vellum-nice-fox";
65
- expect(getExternalAssistantId()).toBe("vellum-nice-fox");
66
- });
67
-
68
- test("resolves from BASE_DATA_DIR with /instances/<name> path", () => {
69
- process.env.BASE_DATA_DIR = "/home/user/.vellum/instances/vellum-swift-owl";
70
- expect(getExternalAssistantId()).toBe("vellum-swift-owl");
71
- });
72
-
73
- test("resolves from BASE_DATA_DIR with /instances/<name> trailing slash", () => {
74
- process.env.BASE_DATA_DIR =
75
- "/home/user/.vellum/instances/vellum-swift-owl/";
76
- expect(getExternalAssistantId()).toBe("vellum-swift-owl");
17
+ test("resolves from VELLUM_ASSISTANT_NAME env var", () => {
18
+ process.env.VELLUM_ASSISTANT_NAME = "vellum-cool-eel";
19
+ expect(getExternalAssistantId()).toBe("vellum-cool-eel");
77
20
  });
78
21
 
79
- test("falls back to undefined when BASE_DATA_DIR does not match known patterns", () => {
80
- process.env.BASE_DATA_DIR = "/tmp/some-other-path";
81
- expect(getExternalAssistantId()).toBe(undefined);
22
+ test("caches the resolved value", () => {
23
+ process.env.VELLUM_ASSISTANT_NAME = "vellum-cool-eel";
24
+ expect(getExternalAssistantId()).toBe("vellum-cool-eel");
25
+ // Change env var — cached value should still be returned
26
+ process.env.VELLUM_ASSISTANT_NAME = "vellum-other-fox";
27
+ expect(getExternalAssistantId()).toBe("vellum-cool-eel");
82
28
  });
83
29
 
84
- test("falls back to undefined when BASE_DATA_DIR is not set", () => {
85
- delete process.env.BASE_DATA_DIR;
30
+ test("returns undefined when env var is not set", () => {
86
31
  expect(getExternalAssistantId()).toBe(undefined);
87
32
  });
88
33
  });
@@ -6,81 +6,35 @@
6
6
  * must identify which assistant the token belongs to, while the daemon
7
7
  * internally uses 'self'.
8
8
  *
9
- * Resolution order:
10
- * 1. Cached in-memory value (populated on first call)
11
- * 2. Most recently hatched entry in lockfile assistants array
12
- * (sorted by `hatchedAt` descending) → assistantId
13
- * 3. BASE_DATA_DIR path matching `/assistants/<name>` or `/instances/<name>` suffix
14
- * 4. `undefined` — callers must handle the missing value
9
+ * Reads from the VELLUM_ASSISTANT_NAME env var, which is set by CLI
10
+ * hatch and Docker setup. Returns `undefined` if the env var is not set.
15
11
  *
16
- * The value is cached in memory after the first successful read.
12
+ * The value is cached in memory after the first read.
17
13
  */
18
14
 
19
- import { getBaseDataDir } from "../../config/env-registry.js";
20
15
  import { getLogger } from "../../util/logger.js";
21
- import { readLockfile } from "../../util/platform.js";
22
16
 
23
17
  const log = getLogger("external-assistant-id");
24
18
 
25
19
  let cached: string | null | undefined;
26
20
 
27
21
  /**
28
- * Get the external assistant ID.
29
- *
30
- * Resolution order:
31
- * 1. Cached in-memory value (populated on first call)
32
- * 2. Most recently hatched entry in lockfile assistants array
33
- * (sorted by `hatchedAt` descending) → assistantId
34
- * 3. BASE_DATA_DIR path matching `/assistants/<name>` or `/instances/<name>` suffix
35
- * 4. `undefined` when resolution fails entirely
22
+ * Get the external assistant ID from the VELLUM_ASSISTANT_NAME env var.
23
+ * Returns `undefined` when the env var is not set.
36
24
  */
37
25
  export function getExternalAssistantId(): string | undefined {
38
26
  if (cached !== undefined) {
39
27
  return cached ?? undefined;
40
28
  }
41
29
 
42
- try {
43
- const lockData = readLockfile();
44
- if (lockData) {
45
- const assistants = lockData.assistants as
46
- | Array<Record<string, unknown>>
47
- | undefined;
48
- if (assistants && assistants.length > 0) {
49
- // Sort by hatchedAt descending to use the most recent entry,
50
- // matching the pattern used elsewhere in the codebase.
51
- const sorted = [...assistants].sort((a, b) => {
52
- const dateA = new Date((a.hatchedAt as string) || 0).getTime();
53
- const dateB = new Date((b.hatchedAt as string) || 0).getTime();
54
- return dateB - dateA;
55
- });
56
- const latest = sorted[0];
57
- if (typeof latest.assistantId === "string") {
58
- cached = latest.assistantId;
59
- log.info(
60
- { externalAssistantId: cached },
61
- "Resolved external assistant ID from lockfile",
62
- );
63
- return cached;
64
- }
65
- }
66
- }
67
- } catch (err) {
68
- log.warn({ err }, "Failed to read lockfile for external assistant ID");
69
- }
70
-
71
- // Fallback: derive from BASE_DATA_DIR path
72
- const base = getBaseDataDir();
73
- if (base && typeof base === "string") {
74
- const normalized = base.replace(/\\/g, "/").replace(/\/+$/, "");
75
- const match = normalized.match(/\/(?:assistants|instances)\/([^/]+)$/);
76
- if (match) {
77
- cached = match[1];
78
- log.info(
79
- { externalAssistantId: cached },
80
- "Resolved external assistant ID from BASE_DATA_DIR",
81
- );
82
- return cached;
83
- }
30
+ const envName = process.env.VELLUM_ASSISTANT_NAME;
31
+ if (envName) {
32
+ cached = envName;
33
+ log.info(
34
+ { externalAssistantId: cached },
35
+ "Resolved external assistant ID from VELLUM_ASSISTANT_NAME",
36
+ );
37
+ return cached;
84
38
  }
85
39
 
86
40
  cached = null;
@@ -352,6 +352,10 @@ const ACTOR_ENDPOINTS: Array<{ endpoint: string; scopes: Scope[] }> = [
352
352
  { endpoint: "config/permissions/skip:GET", scopes: ["settings.read"] },
353
353
  { endpoint: "config/permissions/skip:PUT", scopes: ["settings.write"] },
354
354
 
355
+ // Generic config read/patch
356
+ { endpoint: "config:GET", scopes: ["settings.read"] },
357
+ { endpoint: "config:PATCH", scopes: ["settings.write"] },
358
+
355
359
  // Conversation management
356
360
  { endpoint: "conversations:DELETE", scopes: ["chat.write"] },
357
361
  { endpoint: "conversations/wipe", scopes: ["chat.write"] },
@@ -366,6 +370,7 @@ const ACTOR_ENDPOINTS: Array<{ endpoint: string; scopes: Scope[] }> = [
366
370
  // Message content
367
371
  { endpoint: "messages/content", scopes: ["chat.read"] },
368
372
  { endpoint: "messages/llm-context", scopes: ["chat.read"] },
373
+ { endpoint: "messages/tts", scopes: ["chat.read"] },
369
374
 
370
375
  // Queued message deletion
371
376
  { endpoint: "messages/queued", scopes: ["chat.write"] },
@@ -443,7 +448,6 @@ const ACTOR_ENDPOINTS: Array<{ endpoint: string; scopes: Scope[] }> = [
443
448
 
444
449
  // Diagnostics
445
450
  { endpoint: "export", scopes: ["settings.read"] },
446
- { endpoint: "diagnostics/export", scopes: ["settings.read"] },
447
451
  { endpoint: "diagnostics/env-vars", scopes: ["settings.read"] },
448
452
 
449
453
  // Dictation
@@ -512,3 +516,13 @@ registerPolicy("admin/upgrade-broadcast", {
512
516
  requiredScopes: ["internal.write"],
513
517
  allowedPrincipalTypes: ["svc_gateway"],
514
518
  });
519
+
520
+ registerPolicy("admin/workspace-commit", {
521
+ requiredScopes: ["internal.write"],
522
+ allowedPrincipalTypes: ["svc_gateway"],
523
+ });
524
+
525
+ registerPolicy("admin/rollback-migrations", {
526
+ requiredScopes: ["internal.write"],
527
+ allowedPrincipalTypes: ["svc_gateway"],
528
+ });
@@ -28,22 +28,6 @@ import type { ScopeProfile, TokenAudience, TokenClaims } from "./types.js";
28
28
 
29
29
  const log = getLogger("token-service");
30
30
 
31
- // ---------------------------------------------------------------------------
32
- // Bootstrap sentinel error
33
- // ---------------------------------------------------------------------------
34
-
35
- /**
36
- * Thrown when the gateway's signing-key bootstrap endpoint returns 403,
37
- * indicating that bootstrap has already completed (daemon restart case).
38
- * The caller should fall back to loading the key from disk.
39
- */
40
- export class BootstrapAlreadyCompleted extends Error {
41
- constructor() {
42
- super("Gateway signing key bootstrap already completed");
43
- this.name = "BootstrapAlreadyCompleted";
44
- }
45
- }
46
-
47
31
  // ---------------------------------------------------------------------------
48
32
  // Signing key management
49
33
  // ---------------------------------------------------------------------------
@@ -58,28 +42,44 @@ function getSigningKeyPath(): string {
58
42
  return join(getRootDir(), "protected", "actor-token-signing-key");
59
43
  }
60
44
 
45
+ /**
46
+ * Load a signing key from disk. Returns the key buffer if found and valid,
47
+ * or undefined if the file does not exist or is invalid.
48
+ *
49
+ * Used in the Docker 403-fallback path where generating a new key would
50
+ * create a mismatch with the gateway's already-bootstrapped key.
51
+ */
52
+ export function loadSigningKey(): Buffer | undefined {
53
+ const keyPath = getSigningKeyPath();
54
+ if (!existsSync(keyPath)) {
55
+ return undefined;
56
+ }
57
+ try {
58
+ const raw = readFileSync(keyPath);
59
+ if (raw.length === 32) {
60
+ log.info("Auth signing key loaded from disk");
61
+ return raw;
62
+ }
63
+ log.warn("Signing key file has unexpected length");
64
+ return undefined;
65
+ } catch (err) {
66
+ log.warn({ err }, "Failed to read signing key file");
67
+ return undefined;
68
+ }
69
+ }
70
+
61
71
  /**
62
72
  * Load a signing key from disk or generate and persist a new one.
63
73
  * Uses atomic-write + chmod 0o600 for safe persistence.
64
74
  */
65
75
  export function loadOrCreateSigningKey(): Buffer {
66
- const keyPath = getSigningKeyPath();
67
-
68
- // Try to load existing key
69
- if (existsSync(keyPath)) {
70
- try {
71
- const raw = readFileSync(keyPath);
72
- if (raw.length === 32) {
73
- log.info("Auth signing key loaded from disk");
74
- return raw;
75
- }
76
- log.warn("Signing key file has unexpected length, regenerating");
77
- } catch (err) {
78
- log.warn({ err }, "Failed to read signing key file, regenerating");
79
- }
76
+ const existing = loadSigningKey();
77
+ if (existing) {
78
+ return existing;
80
79
  }
81
80
 
82
81
  // Generate and persist a new key
82
+ const keyPath = getSigningKeyPath();
83
83
  const newKey = randomBytes(32);
84
84
  const dir = dirname(keyPath);
85
85
  if (!existsSync(dir)) {
@@ -94,120 +94,25 @@ export function loadOrCreateSigningKey(): Buffer {
94
94
  return newKey;
95
95
  }
96
96
 
97
- /**
98
- * Fetch the shared signing key from the gateway's bootstrap endpoint.
99
- *
100
- * Used in Docker mode where the gateway owns the signing key and the daemon
101
- * must fetch it at startup. Retries up to 30 times with 1s intervals to
102
- * tolerate gateway startup delays.
103
- *
104
- * @returns A 32-byte Buffer containing the signing key.
105
- * @throws {BootstrapAlreadyCompleted} If the gateway returns 403 (bootstrap
106
- * already completed — daemon restart case). Caller should fall back to
107
- * loading the key from disk.
108
- * @throws {Error} If the gateway is unreachable after all retry attempts.
109
- */
110
- export async function fetchSigningKeyFromGateway(): Promise<Buffer> {
111
- const gatewayUrl = process.env.GATEWAY_INTERNAL_URL;
112
- if (!gatewayUrl) {
113
- throw new Error("GATEWAY_INTERNAL_URL not set — cannot fetch signing key");
114
- }
115
-
116
- const maxAttempts = 30;
117
- const intervalMs = 1000;
118
-
119
- for (let attempt = 1; attempt <= maxAttempts; attempt++) {
120
- let resp: Response | undefined;
121
- try {
122
- resp = await fetch(`${gatewayUrl}/internal/signing-key-bootstrap`, {
123
- signal: AbortSignal.timeout(5000),
124
- });
125
- } catch (err) {
126
- log.warn(
127
- { err, attempt },
128
- "Signing key bootstrap: connection failed, retrying",
129
- );
130
- await Bun.sleep(intervalMs);
131
- continue;
132
- }
133
-
134
- if (resp.ok) {
135
- const body = (await resp.json()) as { key: string };
136
- const keyBuf = Buffer.from(body.key, "hex");
137
- if (keyBuf.length !== 32) {
138
- throw new Error(`Invalid signing key length: ${keyBuf.length}`);
139
- }
140
- log.info("Signing key fetched from gateway bootstrap endpoint");
141
- return keyBuf;
142
- }
143
-
144
- if (resp.status === 403) {
145
- // Bootstrap already completed — fall through to file-based load.
146
- // This happens on daemon restart when the gateway lockfile persists.
147
- log.info(
148
- "Gateway signing key bootstrap already completed — loading from disk",
149
- );
150
- throw new BootstrapAlreadyCompleted();
151
- }
152
-
153
- log.warn(
154
- { status: resp.status, attempt },
155
- "Signing key bootstrap: gateway not ready, retrying",
156
- );
157
-
158
- await Bun.sleep(intervalMs);
159
- }
160
-
161
- throw new Error("Signing key bootstrap: timed out waiting for gateway");
162
- }
163
-
164
- /**
165
- * Persist a signing key to disk using an atomic-write pattern.
166
- * Used after fetching the key from the gateway so daemon restarts can
167
- * load it from disk when the gateway returns 403.
168
- */
169
- function persistSigningKey(key: Buffer): void {
170
- const keyPath = getSigningKeyPath();
171
- const dir = dirname(keyPath);
172
- if (!existsSync(dir)) {
173
- mkdirSync(dir, { recursive: true });
174
- }
175
- const tmpPath = keyPath + ".tmp." + process.pid;
176
- writeFileSync(tmpPath, key, { mode: 0o600 });
177
- renameSync(tmpPath, keyPath);
178
- chmodSync(keyPath, 0o600);
179
- }
180
-
181
97
  /**
182
98
  * Resolve the signing key for the current environment.
183
99
  *
184
- * In Docker mode (IS_CONTAINERIZED=true + GATEWAY_INTERNAL_URL set), fetches
185
- * the key from the gateway's bootstrap endpoint and persists it locally for
186
- * restart resilience. On daemon restart (gateway returns 403), falls back to
187
- * loading the key from disk.
188
- *
189
- * In local mode, delegates to the existing file-based loadOrCreateSigningKey().
100
+ * Resolution order:
101
+ * 1. ACTOR_TOKEN_SIGNING_KEY env var (hex-encoded, set by CLI for Docker)
102
+ * 2. File-based load/create (~/.vellum/protected/actor-token-signing-key)
190
103
  */
191
- export async function resolveSigningKey(): Promise<Buffer> {
192
- const isContainerized = process.env.IS_CONTAINERIZED === "true";
193
- const gatewayUrl = process.env.GATEWAY_INTERNAL_URL;
194
-
195
- if (isContainerized && gatewayUrl) {
196
- try {
197
- const key = await fetchSigningKeyFromGateway();
198
- // Persist locally so daemon restarts (where gateway returns 403) load from disk.
199
- persistSigningKey(key);
200
- return key;
201
- } catch (err) {
202
- if (err instanceof BootstrapAlreadyCompleted) {
203
- // Gateway already bootstrapped (daemon restart) — load from disk.
204
- return loadOrCreateSigningKey();
205
- }
206
- throw err;
104
+ export function resolveSigningKey(): Buffer {
105
+ const envKey = process.env.ACTOR_TOKEN_SIGNING_KEY;
106
+ if (envKey) {
107
+ if (!/^[0-9a-f]{64}$/i.test(envKey)) {
108
+ throw new Error(
109
+ `Invalid ACTOR_TOKEN_SIGNING_KEY: expected 64 hex characters, got ${envKey.length} chars`,
110
+ );
207
111
  }
112
+ log.info("Signing key loaded from ACTOR_TOKEN_SIGNING_KEY env var");
113
+ return Buffer.from(envKey, "hex");
208
114
  }
209
115
 
210
- // Local mode: use file-based load/create (unchanged behavior).
211
116
  return loadOrCreateSigningKey();
212
117
  }
213
118
 
@@ -246,7 +151,7 @@ export function isSigningKeyInitialized(): boolean {
246
151
 
247
152
  /**
248
153
  * Returns a short hex fingerprint of the current signing key.
249
- * Used by daemon_status to let clients detect instance switches.
154
+ * Used by assistant_status to let clients detect instance switches.
250
155
  */
251
156
  export function getSigningKeyFingerprint(): string {
252
157
  return createHash("sha256")
@@ -15,7 +15,6 @@ import type {
15
15
  ReadinessCheckResult,
16
16
  SetupStatus,
17
17
  } from "./channel-readiness-types.js";
18
- import { probeLocalGatewayHealth } from "./local-gateway-health.js";
19
18
 
20
19
  /** Remote check results are cached for 5 minutes before being considered stale. */
21
20
  export const REMOTE_TTL_MS = 5 * 60 * 1000;
@@ -82,7 +81,7 @@ const voiceProbe: ChannelProbe = {
82
81
  const hasPhone = !!resolveTwilioPhoneNumber();
83
82
  const ingress = checkIngress();
84
83
 
85
- const checks: ReadinessCheckResult[] = [
84
+ return [
86
85
  check(
87
86
  "twilio_credentials",
88
87
  hasCreds,
@@ -97,20 +96,6 @@ const voiceProbe: ChannelProbe = {
97
96
  ),
98
97
  ingress,
99
98
  ];
100
-
101
- if (ingress.passed) {
102
- const gw = await probeLocalGatewayHealth();
103
- checks.push(
104
- check(
105
- "gateway_health",
106
- gw.healthy,
107
- `Local gateway is serving requests at ${gw.target}`,
108
- `Local gateway is not serving requests at ${gw.target}${gw.error ? `: ${gw.error}` : ""}`,
109
- ),
110
- );
111
- }
112
-
113
- return checks;
114
99
  },
115
100
  };
116
101
 
@@ -106,6 +106,7 @@ import { handleServePage } from "./routes/app-routes.js";
106
106
  import { appRouteDefinitions } from "./routes/app-routes.js";
107
107
  import { approvalRouteDefinitions } from "./routes/approval-routes.js";
108
108
  import { attachmentRouteDefinitions } from "./routes/attachment-routes.js";
109
+ import { handleGetAudio } from "./routes/audio-routes.js";
109
110
  import { avatarRouteDefinitions } from "./routes/avatar-routes.js";
110
111
  import { brainGraphRouteDefinitions } from "./routes/brain-graph-routes.js";
111
112
  import { btwRouteDefinitions } from "./routes/btw-routes.js";
@@ -144,16 +145,19 @@ import { handleGuardianRefresh } from "./routes/guardian-refresh-routes.js";
144
145
  import { hostBashRouteDefinitions } from "./routes/host-bash-routes.js";
145
146
  import { hostCuRouteDefinitions } from "./routes/host-cu-routes.js";
146
147
  import { hostFileRouteDefinitions } from "./routes/host-file-routes.js";
147
- import { handleHealth } from "./routes/identity-routes.js";
148
+ import { handleHealth, handleReadyz } from "./routes/identity-routes.js";
148
149
  import { identityRouteDefinitions } from "./routes/identity-routes.js";
149
150
  import { slackChannelRouteDefinitions } from "./routes/integrations/slack/channel.js";
150
151
  import { slackShareRouteDefinitions } from "./routes/integrations/slack/share.js";
151
152
  import { telegramRouteDefinitions } from "./routes/integrations/telegram.js";
152
153
  import { twilioRouteDefinitions } from "./routes/integrations/twilio.js";
154
+ import { vercelRouteDefinitions } from "./routes/integrations/vercel.js";
153
155
  import { inviteRouteDefinitions } from "./routes/invite-routes.js";
154
156
  import { logExportRouteDefinitions } from "./routes/log-export-routes.js";
155
157
  import { memoryItemRouteDefinitions } from "./routes/memory-item-routes.js";
158
+ import { migrationRollbackRouteDefinitions } from "./routes/migration-rollback-routes.js";
156
159
  import { migrationRouteDefinitions } from "./routes/migration-routes.js";
160
+ import { notificationRouteDefinitions } from "./routes/notification-routes.js";
157
161
  import { oauthAppsRouteDefinitions } from "./routes/oauth-apps.js";
158
162
  import type { PairingHandlerContext } from "./routes/pairing-routes.js";
159
163
  import {
@@ -172,10 +176,12 @@ import { surfaceContentRouteDefinitions } from "./routes/surface-content-routes.
172
176
  import { telemetryRouteDefinitions } from "./routes/telemetry-routes.js";
173
177
  import { traceEventRouteDefinitions } from "./routes/trace-event-routes.js";
174
178
  import { trustRulesRouteDefinitions } from "./routes/trust-rules-routes.js";
179
+ import { ttsRouteDefinitions } from "./routes/tts-routes.js";
175
180
  import { upgradeBroadcastRouteDefinitions } from "./routes/upgrade-broadcast-routes.js";
176
181
  import { usageRouteDefinitions } from "./routes/usage-routes.js";
177
182
  import { watchRouteDefinitions } from "./routes/watch-routes.js";
178
183
  import { workItemRouteDefinitions } from "./routes/work-items-routes.js";
184
+ import { workspaceCommitRouteDefinitions } from "./routes/workspace-commit-routes.js";
179
185
  import { workspaceRouteDefinitions } from "./routes/workspace-routes.js";
180
186
 
181
187
  // Re-export for consumers
@@ -464,7 +470,10 @@ export class RuntimeHttpServer {
464
470
  server.timeout(req, 1800);
465
471
  // Skip request logging for health-check probes to reduce log noise.
466
472
  const url = new URL(req.url);
467
- if (url.pathname === "/healthz" && req.method === "GET") {
473
+ if (
474
+ (url.pathname === "/healthz" || url.pathname === "/readyz") &&
475
+ req.method === "GET"
476
+ ) {
468
477
  return this.routeRequest(req, server);
469
478
  }
470
479
  return withRequestLogging(req, () => this.routeRequest(req, server));
@@ -481,6 +490,10 @@ export class RuntimeHttpServer {
481
490
  return handleHealth();
482
491
  }
483
492
 
493
+ if (path === "/readyz" && req.method === "GET") {
494
+ return handleReadyz();
495
+ }
496
+
484
497
  // WebSocket upgrade for the Chrome extension browser relay.
485
498
  if (
486
499
  path === "/v1/browser-relay" &&
@@ -503,6 +516,13 @@ export class RuntimeHttpServer {
503
516
  const twilioResponse = await this.handleTwilioWebhook(req, path);
504
517
  if (twilioResponse) return twilioResponse;
505
518
 
519
+ // Audio serving endpoint — before auth check because Twilio
520
+ // fetches these URLs directly. The audioId is an unguessable UUID.
521
+ const audioMatch = path.match(/^\/v1\/audio\/([^/]+)$/);
522
+ if (audioMatch && req.method === "GET") {
523
+ return handleGetAudio(audioMatch[1]);
524
+ }
525
+
506
526
  // Pairing endpoints (unauthenticated, secret-gated)
507
527
  if (path === "/v1/pairing/request" && req.method === "POST") {
508
528
  return await handlePairingRequest(req, this.pairingContext);
@@ -920,6 +940,8 @@ export class RuntimeHttpServer {
920
940
  }),
921
941
  ...identityRouteDefinitions(),
922
942
  ...upgradeBroadcastRouteDefinitions(),
943
+ ...workspaceCommitRouteDefinitions(),
944
+ ...migrationRollbackRouteDefinitions(),
923
945
  ...debugRouteDefinitions(),
924
946
  ...usageRouteDefinitions(),
925
947
  ...telemetryRouteDefinitions(),
@@ -931,6 +953,7 @@ export class RuntimeHttpServer {
931
953
  ...scheduleRouteDefinitions({
932
954
  sendMessageDeps: this.sendMessageDeps,
933
955
  }),
956
+ ...notificationRouteDefinitions(),
934
957
  ...diagnosticsRouteDefinitions(),
935
958
  ...logExportRouteDefinitions(),
936
959
  ...documentRouteDefinitions(),
@@ -961,6 +984,7 @@ export class RuntimeHttpServer {
961
984
  }
962
985
  : undefined,
963
986
  }),
987
+ ...ttsRouteDefinitions(),
964
988
 
965
989
  // Browser relay — not extracted into a domain module because
966
990
  // these two routes depend on the in-process extensionRelayServer
@@ -1165,6 +1189,7 @@ export class RuntimeHttpServer {
1165
1189
  ...slackChannelRouteDefinitions(),
1166
1190
  ...slackShareRouteDefinitions(),
1167
1191
  ...twilioRouteDefinitions(),
1192
+ ...vercelRouteDefinitions(),
1168
1193
  ...channelReadinessRouteDefinitions(),
1169
1194
  ...oauthAppsRouteDefinitions(),
1170
1195
  ...attachmentRouteDefinitions(),
@@ -4,7 +4,6 @@
4
4
 
5
5
  import {
6
6
  ConfigError,
7
- IngressBlockedError,
8
7
  ProviderNotConfiguredError,
9
8
  } from "../../util/errors.js";
10
9
  import { getLogger } from "../../util/logger.js";
@@ -14,7 +13,7 @@ const log = getLogger("runtime-http");
14
13
 
15
14
  /**
16
15
  * Wrap an async endpoint handler with standard error handling.
17
- * Catches IngressBlockedError (422), ConfigError (422), and generic errors (500).
16
+ * Catches ConfigError (422) and generic errors (500).
18
17
  */
19
18
  export async function withErrorHandling(
20
19
  endpoint: string,
@@ -23,13 +22,6 @@ export async function withErrorHandling(
23
22
  try {
24
23
  return await handler();
25
24
  } catch (err) {
26
- if (err instanceof IngressBlockedError) {
27
- log.warn(
28
- { endpoint, detectedTypes: err.detectedTypes },
29
- "Blocked HTTP request containing secrets",
30
- );
31
- return httpError("UNPROCESSABLE_ENTITY", err.message, 422);
32
- }
33
25
  if (err instanceof ProviderNotConfiguredError) {
34
26
  log.warn({ err, endpoint }, "No LLM provider configured");
35
27
  return httpError(