@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
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Route handlers for Vercel integration config endpoints.
3
+ *
4
+ * GET /v1/integrations/vercel/config — check if a Vercel API token is stored
5
+ * POST /v1/integrations/vercel/config — set or delete token (dispatched via action field)
6
+ * DELETE /v1/integrations/vercel/config — delete the stored Vercel API token
7
+ *
8
+ * The Swift client sends all mutations as POST with an `action` field
9
+ * ("set" or "delete") rather than using HTTP verbs directly.
10
+ */
11
+
12
+ import {
13
+ deleteVercelConfig,
14
+ getVercelConfig,
15
+ setVercelConfig,
16
+ } from "../../../daemon/handlers/config-vercel.js";
17
+ import type { RouteDefinition } from "../../http-router.js";
18
+
19
+ /**
20
+ * GET /v1/integrations/vercel/config
21
+ */
22
+ export async function handleGetVercelConfig(): Promise<Response> {
23
+ const result = await getVercelConfig();
24
+ return Response.json(result);
25
+ }
26
+
27
+ /**
28
+ * POST /v1/integrations/vercel/config
29
+ *
30
+ * Body: { action: "set" | "delete"; apiToken?: string }
31
+ *
32
+ * The Swift client uses POST for both set and delete operations,
33
+ * distinguished by the `action` field.
34
+ */
35
+ export async function handlePostVercelConfig(req: Request): Promise<Response> {
36
+ const body = (await req.json()) as {
37
+ action?: "get" | "set" | "delete";
38
+ apiToken?: string;
39
+ };
40
+
41
+ switch (body.action) {
42
+ case "delete": {
43
+ const result = await deleteVercelConfig();
44
+ return Response.json(result);
45
+ }
46
+ case "get": {
47
+ const result = await getVercelConfig();
48
+ return Response.json(result);
49
+ }
50
+ case "set":
51
+ default: {
52
+ const result = await setVercelConfig(body.apiToken);
53
+ const status = result.success ? 200 : 400;
54
+ return Response.json(result, { status });
55
+ }
56
+ }
57
+ }
58
+
59
+ /**
60
+ * DELETE /v1/integrations/vercel/config
61
+ */
62
+ export async function handleDeleteVercelConfig(): Promise<Response> {
63
+ const result = await deleteVercelConfig();
64
+ return Response.json(result);
65
+ }
66
+
67
+ // ---------------------------------------------------------------------------
68
+ // Route definitions
69
+ // ---------------------------------------------------------------------------
70
+
71
+ export function vercelRouteDefinitions(): RouteDefinition[] {
72
+ return [
73
+ {
74
+ endpoint: "integrations/vercel/config",
75
+ method: "GET",
76
+ handler: () => handleGetVercelConfig(),
77
+ },
78
+ {
79
+ endpoint: "integrations/vercel/config",
80
+ method: "POST",
81
+ handler: async ({ req }) => handlePostVercelConfig(req),
82
+ },
83
+ {
84
+ endpoint: "integrations/vercel/config",
85
+ method: "DELETE",
86
+ handler: async () => handleDeleteVercelConfig(),
87
+ },
88
+ ];
89
+ }
@@ -37,6 +37,7 @@ import {
37
37
  getWorkspaceConfigPath,
38
38
  getWorkspaceDir,
39
39
  } from "../../util/platform.js";
40
+ import { APP_VERSION, COMMIT_SHA } from "../../version.js";
40
41
  import { httpError } from "../http-errors.js";
41
42
  import type { RouteDefinition } from "../http-router.js";
42
43
 
@@ -264,12 +265,16 @@ async function handleExport(body: ExportRequestBody): Promise<Response> {
264
265
  ? {
265
266
  type: "conversation-export" as const,
266
267
  conversationId,
268
+ assistantVersion: APP_VERSION,
269
+ commitSha: COMMIT_SHA,
267
270
  ...(startTime !== undefined ? { startTime } : {}),
268
271
  ...(endTime !== undefined ? { endTime } : {}),
269
272
  exportedAt: new Date().toISOString(),
270
273
  }
271
274
  : {
272
275
  type: "global-export" as const,
276
+ assistantVersion: APP_VERSION,
277
+ commitSha: COMMIT_SHA,
273
278
  exportedAt: new Date().toISOString(),
274
279
  };
275
280
  writeFileSync(
@@ -45,6 +45,7 @@ const VALID_KINDS = [
45
45
  "decision",
46
46
  "constraint",
47
47
  "event",
48
+ "capability",
48
49
  ] as const;
49
50
 
50
51
  type MemoryItemKind = (typeof VALID_KINDS)[number];
@@ -154,7 +155,7 @@ async function searchItemsSemantic(
154
155
  const sparse = generateSparseEmbedding(query);
155
156
  const sparseVector = { indices: sparse.indices, values: sparse.values };
156
157
 
157
- // Build Qdrant filter — items only, exclude capability kind and sentinel
158
+ // Build Qdrant filter — items only, exclude sentinel
158
159
  const mustConditions: Array<Record<string, unknown>> = [
159
160
  { key: "target_type", match: { value: "item" } },
160
161
  ];
@@ -168,7 +169,6 @@ async function searchItemsSemantic(
168
169
  const filter = {
169
170
  must: mustConditions,
170
171
  must_not: [
171
- { key: "kind", match: { value: "capability" } },
172
172
  { key: "_meta", match: { value: true } },
173
173
  ],
174
174
  };
@@ -185,6 +185,11 @@ async function searchItemsSemantic(
185
185
  );
186
186
 
187
187
  const ids = results.map((r) => r.payload.target_id);
188
+
189
+ // Use the vector search result count as the pagination total.
190
+ // A DB-wide COUNT would include items with no embedding yet (lagging) and
191
+ // items irrelevant to the search query, inflating the total and causing
192
+ // clients to paginate into empty pages.
188
193
  return { ids, total: ids.length };
189
194
  } catch (err) {
190
195
  log.warn({ err }, "Semantic memory search failed, falling back to SQL");
@@ -249,11 +254,26 @@ export async function handleListMemoryItems(url: URL): Promise<Response> {
249
254
  offsetParam + limitParam,
250
255
  );
251
256
 
252
- // Batch-fetch full rows from SQLite
257
+ if (pageIds.length === 0) {
258
+ return Response.json({ items: [], total: semanticResult.total });
259
+ }
260
+
261
+ // Re-apply the same DB-side filters used in the SQL path as defense-
262
+ // in-depth against stale Qdrant payloads leaking deleted/mismatched rows.
263
+ const hydrationConditions = [
264
+ inArray(memoryItems.id, pageIds),
265
+ ];
266
+ if (statusParam && statusParam !== "all") {
267
+ hydrationConditions.push(eq(memoryItems.status, statusParam));
268
+ }
269
+ if (kindParam) {
270
+ hydrationConditions.push(eq(memoryItems.kind, kindParam));
271
+ }
272
+
253
273
  const rows = db
254
274
  .select()
255
275
  .from(memoryItems)
256
- .where(inArray(memoryItems.id, pageIds))
276
+ .where(and(...hydrationConditions))
257
277
  .all();
258
278
 
259
279
  // Preserve Qdrant relevance ordering
@@ -279,8 +299,6 @@ export async function handleListMemoryItems(url: URL): Promise<Response> {
279
299
 
280
300
  // ── SQL path (default or fallback) ──────────────────────────────────
281
301
  const conditions = [];
282
- // Hide system-managed capability memories (skill announcements) from the UI
283
- conditions.push(ne(memoryItems.kind, "capability"));
284
302
  if (statusParam && statusParam !== "all") {
285
303
  conditions.push(eq(memoryItems.status, statusParam));
286
304
  }
@@ -0,0 +1,209 @@
1
+ /**
2
+ * Migration rollback endpoint — rolls back DB and/or workspace migrations
3
+ * to a specified target version/migration ID.
4
+ *
5
+ * Protected by a route policy restricting access to gateway service
6
+ * principals only (`svc_gateway` with `internal.write` scope), following
7
+ * the same pattern as other gateway-forwarded control-plane endpoints.
8
+ */
9
+
10
+ import { getDb } from "../../memory/db-connection.js";
11
+ import { getMaxMigrationVersion } from "../../memory/migrations/registry.js";
12
+ import { rollbackMemoryMigration } from "../../memory/migrations/validate-migration-state.js";
13
+ import { getWorkspaceDir } from "../../util/platform.js";
14
+ import { WORKSPACE_MIGRATIONS } from "../../workspace/migrations/registry.js";
15
+ import {
16
+ getLastWorkspaceMigrationId,
17
+ loadCheckpoints,
18
+ rollbackWorkspaceMigrations,
19
+ } from "../../workspace/migrations/runner.js";
20
+ import { httpError } from "../http-errors.js";
21
+ import type { RouteDefinition } from "../http-router.js";
22
+
23
+ export function migrationRollbackRouteDefinitions(): RouteDefinition[] {
24
+ return [
25
+ {
26
+ endpoint: "admin/rollback-migrations",
27
+ method: "POST",
28
+ handler: async ({ req }) => {
29
+ let body: unknown;
30
+ try {
31
+ body = await req.json();
32
+ } catch {
33
+ return httpError("BAD_REQUEST", "Invalid JSON body", 400);
34
+ }
35
+
36
+ if (!body || typeof body !== "object") {
37
+ return httpError(
38
+ "BAD_REQUEST",
39
+ "Request body must be a JSON object",
40
+ 400,
41
+ );
42
+ }
43
+
44
+ const {
45
+ targetDbVersion,
46
+ targetWorkspaceMigrationId,
47
+ rollbackToRegistryCeiling,
48
+ } = body as {
49
+ targetDbVersion?: unknown;
50
+ targetWorkspaceMigrationId?: unknown;
51
+ rollbackToRegistryCeiling?: unknown;
52
+ };
53
+
54
+ // When rollbackToRegistryCeiling is true, auto-determine targets
55
+ // from this daemon's own migration registry ceilings.
56
+ let effectiveDbVersion = targetDbVersion as number | undefined;
57
+ let effectiveWorkspaceMigrationId = targetWorkspaceMigrationId as
58
+ | string
59
+ | undefined;
60
+
61
+ if (rollbackToRegistryCeiling === true) {
62
+ if (effectiveDbVersion === undefined)
63
+ effectiveDbVersion = getMaxMigrationVersion();
64
+ if (effectiveWorkspaceMigrationId === undefined)
65
+ effectiveWorkspaceMigrationId =
66
+ getLastWorkspaceMigrationId(WORKSPACE_MIGRATIONS) ?? undefined;
67
+ }
68
+
69
+ // At least one rollback target must be specified.
70
+ if (
71
+ effectiveDbVersion === undefined &&
72
+ effectiveWorkspaceMigrationId === undefined
73
+ ) {
74
+ return httpError(
75
+ "BAD_REQUEST",
76
+ "At least one of targetDbVersion or targetWorkspaceMigrationId must be provided",
77
+ 400,
78
+ );
79
+ }
80
+
81
+ // Validate effectiveDbVersion when provided.
82
+ if (effectiveDbVersion !== undefined) {
83
+ if (
84
+ typeof effectiveDbVersion !== "number" ||
85
+ !Number.isInteger(effectiveDbVersion) ||
86
+ effectiveDbVersion < 0
87
+ ) {
88
+ return httpError(
89
+ "BAD_REQUEST",
90
+ "targetDbVersion must be a non-negative integer",
91
+ 400,
92
+ );
93
+ }
94
+ }
95
+
96
+ // Validate effectiveWorkspaceMigrationId when provided.
97
+ if (effectiveWorkspaceMigrationId !== undefined) {
98
+ if (
99
+ typeof effectiveWorkspaceMigrationId !== "string" ||
100
+ effectiveWorkspaceMigrationId.length === 0
101
+ ) {
102
+ return httpError(
103
+ "BAD_REQUEST",
104
+ "targetWorkspaceMigrationId must be a non-empty string",
105
+ 400,
106
+ );
107
+ }
108
+ }
109
+
110
+ // Preflight: validate that the workspace migration ID exists in the
111
+ // registry BEFORE executing any mutations. This prevents the DB
112
+ // rollback from committing when the workspace target is invalid.
113
+ let resolvedTargetIndex = -1;
114
+ if (effectiveWorkspaceMigrationId !== undefined) {
115
+ const targetId = effectiveWorkspaceMigrationId as string;
116
+ resolvedTargetIndex = WORKSPACE_MIGRATIONS.findIndex(
117
+ (m) => m.id === targetId,
118
+ );
119
+ if (resolvedTargetIndex === -1) {
120
+ return httpError(
121
+ "BAD_REQUEST",
122
+ `Target workspace migration "${targetId}" not found in the registry`,
123
+ 400,
124
+ );
125
+ }
126
+ }
127
+
128
+ const rolledBack: { db: string[]; workspace: string[] } = {
129
+ db: [],
130
+ workspace: [],
131
+ };
132
+
133
+ // Roll back DB migrations if requested.
134
+ if (effectiveDbVersion !== undefined) {
135
+ try {
136
+ rolledBack.db = rollbackMemoryMigration(
137
+ getDb(),
138
+ effectiveDbVersion,
139
+ );
140
+ } catch (err) {
141
+ const detail = err instanceof Error ? err.message : "Unknown error";
142
+ return httpError(
143
+ "INTERNAL_ERROR",
144
+ `DB migration rollback failed: ${detail}`,
145
+ 500,
146
+ );
147
+ }
148
+ }
149
+
150
+ // Roll back workspace migrations if requested.
151
+ if (effectiveWorkspaceMigrationId !== undefined) {
152
+ const workspaceDir = getWorkspaceDir();
153
+
154
+ // Compute which migrations are candidates for rollback before
155
+ // executing, since rollbackWorkspaceMigrations returns void.
156
+ const targetId = effectiveWorkspaceMigrationId;
157
+
158
+ const checkpointsBefore = loadCheckpoints(workspaceDir);
159
+ const candidateIds = WORKSPACE_MIGRATIONS.slice(
160
+ resolvedTargetIndex + 1,
161
+ )
162
+ .filter((m) => {
163
+ const entry = checkpointsBefore.applied[m.id];
164
+ return (
165
+ entry &&
166
+ entry.status !== "started" &&
167
+ entry.status !== "rolling_back"
168
+ );
169
+ })
170
+ .map((m) => m.id);
171
+
172
+ try {
173
+ await rollbackWorkspaceMigrations(
174
+ workspaceDir,
175
+ WORKSPACE_MIGRATIONS,
176
+ targetId,
177
+ );
178
+
179
+ rolledBack.workspace = candidateIds;
180
+ } catch (err) {
181
+ // Re-read checkpoints to determine which migrations were actually
182
+ // rolled back before the error occurred. A candidate whose entry
183
+ // is no longer present in the checkpoint file was successfully
184
+ // reverted.
185
+ const checkpointsAfter = loadCheckpoints(workspaceDir);
186
+ const actuallyRolledBack = candidateIds.filter(
187
+ (id) => !checkpointsAfter.applied[id],
188
+ );
189
+
190
+ const detail = err instanceof Error ? err.message : "Unknown error";
191
+ return httpError(
192
+ "INTERNAL_ERROR",
193
+ `Workspace migration rollback failed: ${detail}`,
194
+ 500,
195
+ {
196
+ partialRolledBack: {
197
+ db: rolledBack.db,
198
+ workspace: actuallyRolledBack,
199
+ },
200
+ },
201
+ );
202
+ }
203
+ }
204
+
205
+ return Response.json({ ok: true, rolledBack });
206
+ },
207
+ },
208
+ ];
209
+ }
@@ -15,7 +15,8 @@ import { join } from "node:path";
15
15
  import { Database } from "bun:sqlite";
16
16
 
17
17
  import { invalidateConfigCache } from "../../config/loader.js";
18
- import { resetDb } from "../../memory/db-connection.js";
18
+ import { getDb, resetDb } from "../../memory/db-connection.js";
19
+ import { validateMigrationState } from "../../memory/migrations/validate-migration-state.js";
19
20
  import { clearCache as clearTrustCache } from "../../permissions/trust-store.js";
20
21
  import { getLogger } from "../../util/logger.js";
21
22
  import {
@@ -438,6 +439,21 @@ export async function handleMigrationImport(req: Request): Promise<Response> {
438
439
  invalidateConfigCache();
439
440
  clearTrustCache();
440
441
 
442
+ // Check whether the imported database contains migration checkpoints from
443
+ // a newer version. This is non-blocking — the import has already
444
+ // succeeded — but we surface a warning so the caller knows some data may
445
+ // not be fully compatible with this daemon's schema.
446
+ try {
447
+ const migrationValidation = validateMigrationState(getDb());
448
+ if (migrationValidation.unknownCheckpoints.length > 0) {
449
+ result.report.warnings.push(
450
+ `Imported data contains ${migrationValidation.unknownCheckpoints.length} migration(s) from a newer version. Some data may not be fully compatible.`,
451
+ );
452
+ }
453
+ } catch {
454
+ // Don't fail the import if validation itself errors
455
+ }
456
+
441
457
  return Response.json(result.report);
442
458
  } catch (err) {
443
459
  log.error({ err }, "Unexpected error during import commit");
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Route handlers for notification delivery acknowledgments.
3
+ *
4
+ * Provides a REST endpoint for clients to report the outcome of
5
+ * local notification delivery (UNUserNotificationCenter.add).
6
+ */
7
+
8
+ import { eq } from "drizzle-orm";
9
+
10
+ import { getDb } from "../../memory/db.js";
11
+ import { notificationDeliveries } from "../../memory/schema.js";
12
+ import { httpError } from "../http-errors.js";
13
+ import type { RouteDefinition } from "../http-router.js";
14
+
15
+ export function notificationRouteDefinitions(): RouteDefinition[] {
16
+ return [
17
+ // POST /v1/notification-intent-result — client ack for notification delivery
18
+ {
19
+ endpoint: "notification-intent-result",
20
+ method: "POST",
21
+ policyKey: "notification-intent-result",
22
+ handler: async ({ req }) => {
23
+ const body = (await req.json()) as {
24
+ deliveryId?: string;
25
+ success?: boolean;
26
+ errorMessage?: string;
27
+ errorCode?: string;
28
+ };
29
+
30
+ if (!body.deliveryId || typeof body.deliveryId !== "string") {
31
+ return httpError("BAD_REQUEST", "deliveryId is required", 400);
32
+ }
33
+
34
+ const db = getDb();
35
+ const now = Date.now();
36
+
37
+ const updates: Record<string, unknown> = {
38
+ clientDeliveryStatus: body.success ? "delivered" : "client_failed",
39
+ clientDeliveryAt: now,
40
+ updatedAt: now,
41
+ };
42
+ if (body.errorMessage) {
43
+ updates.clientDeliveryError = body.errorMessage;
44
+ }
45
+ if (body.errorCode) {
46
+ updates.errorCode = body.errorCode;
47
+ }
48
+
49
+ db.update(notificationDeliveries)
50
+ .set(updates)
51
+ .where(eq(notificationDeliveries.id, body.deliveryId))
52
+ .run();
53
+
54
+ return Response.json({ ok: true });
55
+ },
56
+ },
57
+ ];
58
+ }
@@ -99,6 +99,59 @@ function handleCancelSchedule(id: string): Response {
99
99
  return handleListSchedules();
100
100
  }
101
101
 
102
+ const VALID_MODES = ["notify", "execute"] as const;
103
+ const VALID_ROUTING_INTENTS = [
104
+ "single_channel",
105
+ "multi_channel",
106
+ "all_channels",
107
+ ] as const;
108
+
109
+ function handleUpdateSchedule(
110
+ id: string,
111
+ body: Record<string, unknown>,
112
+ ): Response {
113
+ const updates: Record<string, unknown> = {};
114
+
115
+ if ("mode" in body && !VALID_MODES.includes(body.mode as (typeof VALID_MODES)[number])) {
116
+ return httpError("BAD_REQUEST", `Invalid mode: must be one of ${VALID_MODES.join(", ")}`, 400);
117
+ }
118
+ if ("routingIntent" in body && !VALID_ROUTING_INTENTS.includes(body.routingIntent as (typeof VALID_ROUTING_INTENTS)[number])) {
119
+ return httpError("BAD_REQUEST", `Invalid routingIntent: must be one of ${VALID_ROUTING_INTENTS.join(", ")}`, 400);
120
+ }
121
+
122
+ for (const key of [
123
+ "name",
124
+ "expression",
125
+ "timezone",
126
+ "message",
127
+ "mode",
128
+ "routingIntent",
129
+ "quiet",
130
+ ] as const) {
131
+ if (key in body) {
132
+ updates[key] = body[key];
133
+ }
134
+ }
135
+
136
+ try {
137
+ const updated = updateSchedule(id, updates);
138
+ if (!updated) {
139
+ return httpError("NOT_FOUND", "Schedule not found", 404);
140
+ }
141
+ log.info({ id, updates }, "Schedule updated via HTTP");
142
+ } catch (err) {
143
+ if (
144
+ err instanceof Error &&
145
+ (err.message.includes("Invalid") || err.message.includes("invalid"))
146
+ ) {
147
+ return httpError("BAD_REQUEST", err.message, 400);
148
+ }
149
+ log.error({ err }, "Failed to update schedule");
150
+ return httpError("INTERNAL_ERROR", "Failed to update schedule", 500);
151
+ }
152
+ return handleListSchedules();
153
+ }
154
+
102
155
  async function handleRunScheduleNow(
103
156
  id: string,
104
157
  sendMessageDeps?: SendMessageDeps,
@@ -243,6 +296,18 @@ export function scheduleRouteDefinitions(deps: {
243
296
  policyKey: "schedules",
244
297
  handler: ({ params }) => handleDeleteSchedule(params.id),
245
298
  },
299
+ {
300
+ endpoint: "schedules/:id",
301
+ method: "PATCH",
302
+ policyKey: "schedules",
303
+ handler: async ({ req, params }) => {
304
+ const body: unknown = await req.json();
305
+ if (typeof body !== "object" || !body || Array.isArray(body)) {
306
+ return httpError("BAD_REQUEST", "Request body must be a JSON object", 400);
307
+ }
308
+ return handleUpdateSchedule(params.id, body as Record<string, unknown>);
309
+ },
310
+ },
246
311
  {
247
312
  endpoint: "schedules/:id/run",
248
313
  method: "POST",
@@ -10,7 +10,10 @@
10
10
  import { readFileSync } from "node:fs";
11
11
  import { join } from "node:path";
12
12
 
13
- import { setIngressPublicBaseUrl } from "../../config/env.js";
13
+ import {
14
+ getPlatformBaseUrl,
15
+ setIngressPublicBaseUrl,
16
+ } from "../../config/env.js";
14
17
  import { loadRawConfig, saveRawConfig } from "../../config/loader.js";
15
18
  import { loadSkillCatalog } from "../../config/skills.js";
16
19
  import {
@@ -728,6 +731,43 @@ export function settingsRouteDefinitions(): RouteDefinition[] {
728
731
  handler: () => handleEnvVars(),
729
732
  },
730
733
 
734
+ // Platform config (GET / PUT)
735
+ {
736
+ endpoint: "config/platform",
737
+ method: "GET",
738
+ policyKey: "config/platform:GET",
739
+ handler: () => {
740
+ const raw = loadRawConfig();
741
+ const platform = (raw?.platform ?? {}) as Record<string, unknown>;
742
+ const baseUrl =
743
+ (platform.baseUrl as string | undefined) || getPlatformBaseUrl();
744
+ return Response.json({ baseUrl, success: true });
745
+ },
746
+ },
747
+ {
748
+ endpoint: "config/platform",
749
+ method: "PUT",
750
+ policyKey: "config/platform",
751
+ handler: async ({ req }) => {
752
+ try {
753
+ const body = (await req.json()) as { baseUrl?: string };
754
+ const value = (body.baseUrl ?? "").trim().replace(/\/+$/, "");
755
+ const raw = loadRawConfig();
756
+ const platform = (raw?.platform ?? {}) as Record<string, unknown>;
757
+ platform.baseUrl = value || undefined;
758
+ saveRawConfig({ ...raw, platform });
759
+ return Response.json({ baseUrl: value, success: true });
760
+ } catch (err) {
761
+ const message = err instanceof Error ? err.message : String(err);
762
+ log.error({ err }, "Failed to update platform config via HTTP");
763
+ return Response.json(
764
+ { baseUrl: "", success: false, error: message },
765
+ { status: 500 },
766
+ );
767
+ }
768
+ },
769
+ },
770
+
731
771
  // Ingress config (GET / PUT)
732
772
  {
733
773
  endpoint: "integrations/ingress/config",