@vellumai/assistant 0.4.52 → 0.4.54

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 (380) hide show
  1. package/ARCHITECTURE.md +2 -2
  2. package/bun.lock +62 -349
  3. package/docs/architecture/integrations.md +1 -1
  4. package/docs/architecture/keychain-broker.md +91 -40
  5. package/docs/architecture/memory.md +3 -3
  6. package/docs/architecture/security.md +2 -2
  7. package/knip.json +7 -29
  8. package/package.json +2 -9
  9. package/src/__tests__/agent-loop.test.ts +1 -1
  10. package/src/__tests__/app-git-history.test.ts +0 -2
  11. package/src/__tests__/app-git-service.test.ts +1 -6
  12. package/src/__tests__/approval-cascade.test.ts +3 -2
  13. package/src/__tests__/approval-routes-http.test.ts +0 -1
  14. package/src/__tests__/asset-materialize-tool.test.ts +0 -1
  15. package/src/__tests__/asset-search-tool.test.ts +0 -1
  16. package/src/__tests__/assistant-events-sse-hardening.test.ts +0 -1
  17. package/src/__tests__/attachments-store.test.ts +0 -1
  18. package/src/__tests__/avatar-e2e.test.ts +5 -1
  19. package/src/__tests__/browser-fill-credential.test.ts +4 -6
  20. package/src/__tests__/btw-routes.test.ts +39 -0
  21. package/src/__tests__/call-controller.test.ts +0 -1
  22. package/src/__tests__/call-domain.test.ts +1 -1
  23. package/src/__tests__/call-routes-http.test.ts +1 -3
  24. package/src/__tests__/canonical-guardian-store.test.ts +33 -2
  25. package/src/__tests__/channel-guardian.test.ts +4 -4
  26. package/src/__tests__/channel-readiness-routes.test.ts +0 -1
  27. package/src/__tests__/channel-readiness-service.test.ts +1 -1
  28. package/src/__tests__/checker.test.ts +13 -11
  29. package/src/__tests__/claude-code-skill-regression.test.ts +5 -2
  30. package/src/__tests__/claude-code-tool-profiles.test.ts +7 -3
  31. package/src/__tests__/config-loader-backfill.test.ts +1 -5
  32. package/src/__tests__/config-schema.test.ts +9 -46
  33. package/src/__tests__/config-watcher.test.ts +11 -3
  34. package/src/__tests__/conversation-routes-slash-commands.test.ts +0 -1
  35. package/src/__tests__/credential-broker-browser-fill.test.ts +27 -24
  36. package/src/__tests__/credential-broker-server-use.test.ts +76 -40
  37. package/src/__tests__/credential-security-e2e.test.ts +1 -6
  38. package/src/__tests__/credential-security-invariants.test.ts +27 -8
  39. package/src/__tests__/credential-vault-unit.test.ts +32 -16
  40. package/src/__tests__/credential-vault.test.ts +40 -28
  41. package/src/__tests__/credentials-cli.test.ts +1 -21
  42. package/src/__tests__/email-invite-adapter.test.ts +0 -1
  43. package/src/__tests__/error-handler-friendly-messages.test.ts +4 -5
  44. package/src/__tests__/fixtures/credential-security-fixtures.ts +3 -3
  45. package/src/__tests__/fixtures/media-reuse-fixtures.ts +3 -79
  46. package/src/__tests__/gateway-only-enforcement.test.ts +1 -23
  47. package/src/__tests__/guardian-action-conversation-turn.test.ts +8 -8
  48. package/src/__tests__/guardian-action-late-reply.test.ts +13 -14
  49. package/src/__tests__/guardian-action-store.test.ts +0 -57
  50. package/src/__tests__/guardian-outbound-http.test.ts +1 -1
  51. package/src/__tests__/guardian-verification-voice-binding.test.ts +1 -3
  52. package/src/__tests__/hooks-blocking.test.ts +1 -1
  53. package/src/__tests__/hooks-config.test.ts +5 -29
  54. package/src/__tests__/hooks-discovery.test.ts +1 -1
  55. package/src/__tests__/hooks-integration.test.ts +1 -1
  56. package/src/__tests__/hooks-manager.test.ts +1 -1
  57. package/src/__tests__/hooks-runner.test.ts +1 -23
  58. package/src/__tests__/hooks-settings.test.ts +1 -1
  59. package/src/__tests__/hooks-templates.test.ts +1 -1
  60. package/src/__tests__/host-shell-tool.test.ts +0 -1
  61. package/src/__tests__/http-user-message-parity.test.ts +19 -0
  62. package/src/__tests__/integration-status.test.ts +0 -1
  63. package/src/__tests__/invite-routes-http.test.ts +0 -3
  64. package/src/__tests__/list-messages-attachments.test.ts +0 -1
  65. package/src/__tests__/llm-usage-store.test.ts +50 -0
  66. package/src/__tests__/log-export-workspace.test.ts +233 -0
  67. package/src/__tests__/managed-proxy-context.test.ts +41 -41
  68. package/src/__tests__/managed-skill-lifecycle.test.ts +0 -1
  69. package/src/__tests__/media-generate-image.test.ts +9 -4
  70. package/src/__tests__/media-reuse-story.e2e.test.ts +1 -7
  71. package/src/__tests__/memory-regressions.experimental.test.ts +4 -4
  72. package/src/__tests__/memory-regressions.test.ts +27 -28
  73. package/src/__tests__/memory-retrieval.benchmark.test.ts +1 -1
  74. package/src/__tests__/memory-upsert-concurrency.test.ts +4 -4
  75. package/src/__tests__/migration-cross-version-compatibility.test.ts +0 -1
  76. package/src/__tests__/migration-export-http.test.ts +0 -1
  77. package/src/__tests__/migration-import-commit-http.test.ts +0 -1
  78. package/src/__tests__/migration-import-preflight-http.test.ts +0 -1
  79. package/src/__tests__/migration-validate-http.test.ts +0 -1
  80. package/src/__tests__/notification-decision-fallback.test.ts +1 -1
  81. package/src/__tests__/notification-schedule-dedup.test.ts +237 -0
  82. package/src/__tests__/oauth-cli.test.ts +2 -14
  83. package/src/__tests__/oauth-store.test.ts +3 -7
  84. package/src/__tests__/oauth2-gateway-transport.test.ts +5 -4
  85. package/src/__tests__/onboarding-starter-tasks.test.ts +1 -1
  86. package/src/__tests__/onboarding-template-contract.test.ts +1 -2
  87. package/src/__tests__/openai-provider.test.ts +7 -7
  88. package/src/__tests__/platform.test.ts +14 -4
  89. package/src/__tests__/pricing.test.ts +0 -234
  90. package/src/__tests__/provider-commit-message-generator.test.ts +19 -15
  91. package/src/__tests__/provider-fail-open-selection.test.ts +67 -62
  92. package/src/__tests__/provider-managed-proxy-integration.test.ts +88 -85
  93. package/src/__tests__/provider-registry-ollama.test.ts +10 -4
  94. package/src/__tests__/public-ingress-urls.test.ts +1 -1
  95. package/src/__tests__/recording-handler.test.ts +0 -1
  96. package/src/__tests__/registry.test.ts +3 -103
  97. package/src/__tests__/relay-server.test.ts +0 -1
  98. package/src/__tests__/runtime-attachment-metadata.test.ts +0 -1
  99. package/src/__tests__/runtime-events-sse-parity.test.ts +0 -1
  100. package/src/__tests__/runtime-events-sse.test.ts +0 -1
  101. package/src/__tests__/script-proxy-injection-runtime.test.ts +2 -7
  102. package/src/__tests__/secret-onetime-send.test.ts +1 -6
  103. package/src/__tests__/secret-routes-managed-proxy.test.ts +6 -14
  104. package/src/__tests__/secret-scanner-executor.test.ts +0 -1
  105. package/src/__tests__/secure-keys.test.ts +241 -229
  106. package/src/__tests__/send-endpoint-busy.test.ts +0 -1
  107. package/src/__tests__/session-abort-tool-results.test.ts +3 -2
  108. package/src/__tests__/session-agent-loop-overflow.test.ts +1012 -838
  109. package/src/__tests__/session-agent-loop.test.ts +2 -2
  110. package/src/__tests__/session-confirmation-signals.test.ts +3 -2
  111. package/src/__tests__/session-error.test.ts +5 -4
  112. package/src/__tests__/session-history-web-search.test.ts +34 -9
  113. package/src/__tests__/session-messaging-secret-redirect.test.ts +1 -7
  114. package/src/__tests__/session-pre-run-repair.test.ts +3 -2
  115. package/src/__tests__/session-provider-retry-repair.test.ts +31 -27
  116. package/src/__tests__/session-queue.test.ts +5 -5
  117. package/src/__tests__/session-runtime-assembly.test.ts +118 -0
  118. package/src/__tests__/session-slash-known.test.ts +31 -14
  119. package/src/__tests__/session-slash-queue.test.ts +3 -2
  120. package/src/__tests__/session-slash-unknown.test.ts +3 -2
  121. package/src/__tests__/session-workspace-cache-state.test.ts +3 -1
  122. package/src/__tests__/session-workspace-injection.test.ts +3 -2
  123. package/src/__tests__/session-workspace-tool-tracking.test.ts +3 -2
  124. package/src/__tests__/shell-tool-proxy-mode.test.ts +0 -1
  125. package/src/__tests__/skill-projection-feature-flag.test.ts +0 -1
  126. package/src/__tests__/skill-script-runner-sandbox.test.ts +0 -1
  127. package/src/__tests__/skillssh-registry.test.ts +21 -0
  128. package/src/__tests__/slack-channel-config.test.ts +1 -7
  129. package/src/__tests__/slack-share-routes.test.ts +1 -1
  130. package/src/__tests__/swarm-recursion.test.ts +4 -1
  131. package/src/__tests__/swarm-session-integration.test.ts +24 -14
  132. package/src/__tests__/swarm-tool.test.ts +4 -2
  133. package/src/__tests__/task-compiler.test.ts +1 -1
  134. package/src/__tests__/telegram-bot-username-resolution.test.ts +2 -4
  135. package/src/__tests__/test-support/browser-skill-harness.ts +0 -18
  136. package/src/__tests__/test-support/computer-use-skill-harness.ts +0 -23
  137. package/src/__tests__/token-estimator-accuracy.benchmark.test.ts +1521 -0
  138. package/src/__tests__/tool-execution-abort-cleanup.test.ts +0 -1
  139. package/src/__tests__/tool-executor-lifecycle-events.test.ts +0 -1
  140. package/src/__tests__/tool-executor-shell-integration.test.ts +0 -1
  141. package/src/__tests__/tool-executor.test.ts +1 -2
  142. package/src/__tests__/trust-store.test.ts +8 -83
  143. package/src/__tests__/twilio-config.test.ts +0 -1
  144. package/src/__tests__/twilio-provider.test.ts +0 -5
  145. package/src/__tests__/twilio-routes.test.ts +2 -3
  146. package/src/__tests__/usage-cache-backfill-migration.test.ts +10 -10
  147. package/src/__tests__/verification-control-plane-policy.test.ts +0 -1
  148. package/src/__tests__/voice-quality.test.ts +2 -1
  149. package/src/__tests__/voice-scoped-grant-consumer.test.ts +0 -1
  150. package/src/__tests__/web-search.test.ts +1 -1
  151. package/src/agent/loop.ts +17 -1
  152. package/src/bundler/app-bundler.ts +40 -24
  153. package/src/calls/call-controller.ts +16 -0
  154. package/src/calls/guardian-question-copy.ts +1 -1
  155. package/src/calls/relay-server.ts +29 -13
  156. package/src/calls/voice-control-protocol.ts +1 -0
  157. package/src/calls/voice-quality.ts +1 -1
  158. package/src/calls/voice-session-bridge.ts +9 -3
  159. package/src/channels/types.ts +16 -0
  160. package/src/cli/commands/bash.ts +173 -0
  161. package/src/cli/commands/doctor.ts +15 -57
  162. package/src/cli/commands/memory.ts +3 -5
  163. package/src/cli/commands/oauth/connections.ts +4 -2
  164. package/src/cli/commands/oauth/providers.ts +1 -13
  165. package/src/cli/commands/sessions.ts +1 -1
  166. package/src/cli/commands/usage.ts +359 -0
  167. package/src/cli/http-client.ts +22 -12
  168. package/src/cli/program.ts +4 -0
  169. package/src/cli/reference.ts +2 -0
  170. package/src/cli.ts +251 -181
  171. package/src/config/assistant-feature-flags.ts +0 -7
  172. package/src/config/bundled-skills/chatgpt-import/tools/chatgpt-import.ts +1 -1
  173. package/src/config/bundled-skills/claude-code/SKILL.md +1 -1
  174. package/src/config/bundled-skills/claude-code/TOOLS.json +1 -1
  175. package/src/config/bundled-skills/gmail/SKILL.md +0 -1
  176. package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +4 -3
  177. package/src/config/bundled-skills/media-processing/services/reduce.ts +1 -1
  178. package/src/config/bundled-skills/media-processing/tools/analyze-keyframes.ts +3 -5
  179. package/src/config/bundled-skills/media-processing/tools/extract-keyframes.ts +2 -3
  180. package/src/config/bundled-skills/messaging/SKILL.md +0 -1
  181. package/src/config/bundled-skills/phone-calls/references/CONFIG.md +1 -1
  182. package/src/config/bundled-skills/sequences/SKILL.md +0 -1
  183. package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +5 -6
  184. package/src/config/env.ts +13 -0
  185. package/src/config/feature-flag-registry.json +15 -39
  186. package/src/config/loader.ts +7 -135
  187. package/src/config/schema.ts +0 -6
  188. package/src/config/schemas/channels.ts +1 -0
  189. package/src/config/schemas/elevenlabs.ts +2 -2
  190. package/src/config/schemas/security.ts +1 -2
  191. package/src/config/skills.ts +1 -1
  192. package/src/contacts/contact-store.ts +21 -75
  193. package/src/contacts/contacts-write.ts +6 -6
  194. package/src/contacts/types.ts +2 -0
  195. package/src/context/token-estimator.ts +35 -2
  196. package/src/context/window-manager.ts +16 -2
  197. package/src/daemon/approved-devices-store.ts +0 -44
  198. package/src/daemon/classifier.ts +1 -1
  199. package/src/daemon/config-watcher.ts +35 -11
  200. package/src/daemon/context-overflow-reducer.ts +13 -2
  201. package/src/daemon/handlers/config-ingress.ts +25 -8
  202. package/src/daemon/handlers/config-model.ts +22 -16
  203. package/src/daemon/handlers/config-telegram.ts +18 -6
  204. package/src/daemon/handlers/dictation.ts +0 -429
  205. package/src/daemon/handlers/sessions.ts +4 -116
  206. package/src/daemon/handlers/skills.ts +2 -201
  207. package/src/daemon/lifecycle.ts +21 -20
  208. package/src/daemon/message-types/contacts.ts +2 -0
  209. package/src/daemon/message-types/integrations.ts +1 -0
  210. package/src/daemon/message-types/sessions.ts +2 -0
  211. package/src/daemon/parse-actual-tokens-from-error.test.ts +75 -0
  212. package/src/daemon/providers-setup.ts +1 -1
  213. package/src/daemon/server.ts +42 -5
  214. package/src/daemon/session-agent-loop-handlers.ts +1 -1
  215. package/src/daemon/session-agent-loop.ts +27 -79
  216. package/src/daemon/session-error.ts +5 -4
  217. package/src/daemon/session-process.ts +17 -10
  218. package/src/daemon/session-runtime-assembly.ts +50 -0
  219. package/src/daemon/session-slash.ts +34 -22
  220. package/src/daemon/session.ts +1 -0
  221. package/src/daemon/shutdown-handlers.ts +15 -0
  222. package/src/daemon/watch-handler.ts +2 -2
  223. package/src/email/guardrails.ts +1 -1
  224. package/src/email/service.ts +0 -5
  225. package/src/events/domain-events.ts +1 -0
  226. package/src/hooks/templates.ts +1 -1
  227. package/src/media/app-icon-generator.ts +4 -3
  228. package/src/media/avatar-router.ts +5 -4
  229. package/src/media/gemini-image-service.ts +5 -5
  230. package/src/memory/admin.ts +2 -2
  231. package/src/memory/app-git-service.ts +0 -7
  232. package/src/memory/canonical-guardian-store.ts +25 -3
  233. package/src/memory/conversation-crud.ts +1 -1
  234. package/src/memory/conversation-title-service.ts +2 -2
  235. package/src/memory/db-init.ts +12 -0
  236. package/src/memory/embedding-backend.ts +46 -33
  237. package/src/memory/external-conversation-store.ts +0 -30
  238. package/src/memory/guardian-action-store.ts +0 -31
  239. package/src/memory/guardian-approvals.ts +1 -56
  240. package/src/memory/indexer.ts +4 -3
  241. package/src/memory/items-extractor.ts +1 -1
  242. package/src/memory/job-handlers/backfill.ts +5 -2
  243. package/src/memory/job-handlers/index-maintenance.ts +2 -2
  244. package/src/memory/job-handlers/media-processing.ts +2 -2
  245. package/src/memory/job-handlers/summarization.ts +1 -1
  246. package/src/memory/job-utils.ts +1 -2
  247. package/src/memory/jobs-worker.ts +2 -2
  248. package/src/memory/llm-usage-store.ts +57 -11
  249. package/src/memory/media-store.ts +4 -535
  250. package/src/memory/migrations/032-guardian-delivery-conversation-index.ts +2 -2
  251. package/src/memory/migrations/110-channel-guardian.ts +0 -1
  252. package/src/memory/migrations/158-channel-interaction-columns.ts +18 -0
  253. package/src/memory/migrations/159-drop-contact-interaction-columns.ts +16 -0
  254. package/src/memory/migrations/160-drop-loopback-port-column.ts +13 -0
  255. package/src/memory/migrations/index.ts +3 -0
  256. package/src/memory/published-pages-store.ts +0 -83
  257. package/src/memory/qdrant-circuit-breaker.ts +0 -8
  258. package/src/memory/retriever.test.ts +19 -12
  259. package/src/memory/retriever.ts +1 -1
  260. package/src/memory/schema/contacts.ts +2 -2
  261. package/src/memory/schema/oauth.ts +0 -1
  262. package/src/memory/search/semantic.ts +1 -8
  263. package/src/memory/shared-app-links-store.ts +0 -15
  264. package/src/messaging/registry.ts +0 -5
  265. package/src/messaging/style-analyzer.ts +1 -1
  266. package/src/notifications/copy-composer.ts +5 -13
  267. package/src/notifications/decision-engine.ts +2 -2
  268. package/src/notifications/deliveries-store.ts +0 -39
  269. package/src/notifications/guardian-question-mode.ts +6 -10
  270. package/src/notifications/preference-extractor.ts +1 -1
  271. package/src/oauth/byo-connection.test.ts +29 -20
  272. package/src/oauth/connect-orchestrator.ts +5 -3
  273. package/src/oauth/connect-types.ts +9 -2
  274. package/src/oauth/manual-token-connection.ts +9 -7
  275. package/src/oauth/oauth-store.ts +2 -8
  276. package/src/oauth/provider-behaviors.ts +11 -1
  277. package/src/oauth/seed-providers.ts +13 -5
  278. package/src/permissions/checker.ts +21 -2
  279. package/src/permissions/shell-identity.ts +0 -5
  280. package/src/permissions/trust-store.ts +0 -37
  281. package/src/prompts/__tests__/build-cli-reference-section.test.ts +1 -1
  282. package/src/prompts/system-prompt.ts +5 -14
  283. package/src/prompts/templates/BOOTSTRAP.md +1 -3
  284. package/src/providers/anthropic/client.ts +16 -8
  285. package/src/providers/managed-proxy/constants.ts +9 -11
  286. package/src/providers/managed-proxy/context.ts +14 -9
  287. package/src/providers/provider-send-message.ts +4 -52
  288. package/src/providers/registry.ts +29 -57
  289. package/src/providers/types.ts +1 -1
  290. package/src/runtime/actor-token-store.ts +0 -23
  291. package/src/runtime/auth/route-policy.ts +4 -0
  292. package/src/runtime/channel-invite-transports/telegram.ts +12 -6
  293. package/src/runtime/channel-retry-sweep.ts +6 -0
  294. package/src/runtime/http-router.ts +5 -1
  295. package/src/runtime/http-server.ts +101 -4
  296. package/src/runtime/http-types.ts +1 -0
  297. package/src/runtime/invite-instruction-generator.ts +25 -51
  298. package/src/runtime/invite-service.ts +0 -20
  299. package/src/runtime/middleware/error-handler.ts +1 -2
  300. package/src/runtime/routes/app-management-routes.ts +1 -0
  301. package/src/runtime/routes/attachment-routes.ts +1 -1
  302. package/src/runtime/routes/brain-graph-routes.ts +1 -1
  303. package/src/runtime/routes/btw-routes.ts +20 -1
  304. package/src/runtime/routes/call-routes.ts +1 -1
  305. package/src/runtime/routes/conversation-routes.ts +64 -24
  306. package/src/runtime/routes/debug-routes.ts +1 -1
  307. package/src/runtime/routes/diagnostics-routes.ts +2 -2
  308. package/src/runtime/routes/documents-routes.ts +3 -3
  309. package/src/runtime/routes/global-search-routes.ts +1 -1
  310. package/src/runtime/routes/guardian-bootstrap-routes.ts +0 -20
  311. package/src/runtime/routes/guardian-refresh-routes.ts +0 -20
  312. package/src/runtime/routes/inbound-message-handler.ts +10 -2
  313. package/src/runtime/routes/inbound-stages/background-dispatch.ts +4 -0
  314. package/src/runtime/routes/inbound-stages/edit-intercept.ts +5 -5
  315. package/src/runtime/routes/integrations/slack/share.ts +5 -5
  316. package/src/runtime/routes/log-export-routes.ts +122 -10
  317. package/src/runtime/routes/secret-routes.ts +4 -4
  318. package/src/runtime/routes/session-query-routes.ts +3 -3
  319. package/src/runtime/routes/settings-routes.ts +53 -0
  320. package/src/runtime/routes/trust-rules-routes.ts +1 -1
  321. package/src/runtime/routes/workspace-routes.ts +3 -0
  322. package/src/runtime/verification-templates.ts +1 -1
  323. package/src/security/credential-backend.ts +148 -0
  324. package/src/security/oauth2.ts +5 -5
  325. package/src/security/secret-allowlist.ts +1 -1
  326. package/src/security/secure-keys.ts +98 -160
  327. package/src/security/token-manager.ts +0 -7
  328. package/src/sequence/guardrails.ts +0 -4
  329. package/src/sequence/store.ts +1 -20
  330. package/src/sequence/types.ts +1 -36
  331. package/src/signals/bash.ts +157 -0
  332. package/src/signals/cancel.ts +69 -0
  333. package/src/signals/conversation-undo.ts +127 -0
  334. package/src/signals/trust-rule.ts +174 -0
  335. package/src/skills/clawhub.ts +5 -5
  336. package/src/skills/managed-store.ts +4 -4
  337. package/src/skills/skillssh-registry.ts +6 -1
  338. package/src/swarm/backend-claude-code.ts +6 -6
  339. package/src/swarm/worker-backend.ts +1 -1
  340. package/src/swarm/worker-runner.ts +1 -1
  341. package/src/telegram/bot-username.ts +11 -0
  342. package/src/telemetry/usage-telemetry-reporter.test.ts +366 -0
  343. package/src/telemetry/usage-telemetry-reporter.ts +181 -0
  344. package/src/tools/claude-code/claude-code.ts +6 -6
  345. package/src/tools/credentials/broker.ts +7 -5
  346. package/src/tools/credentials/vault.ts +11 -6
  347. package/src/tools/memory/handlers.test.ts +24 -26
  348. package/src/tools/memory/handlers.ts +1 -13
  349. package/src/tools/network/__tests__/web-search.test.ts +18 -86
  350. package/src/tools/network/web-search.ts +9 -15
  351. package/src/tools/registry.ts +5 -100
  352. package/src/tools/terminal/parser.ts +34 -4
  353. package/src/tools/tool-manifest.ts +0 -10
  354. package/src/usage/actors.ts +0 -12
  355. package/src/util/canonicalize-identity.ts +0 -9
  356. package/src/util/errors.ts +0 -3
  357. package/src/util/platform.ts +31 -8
  358. package/src/util/pricing.ts +0 -39
  359. package/src/watcher/constants.ts +0 -7
  360. package/src/watcher/providers/linear.ts +1 -1
  361. package/src/work-items/work-item-store.ts +4 -4
  362. package/src/workspace/commit-message-provider.ts +1 -1
  363. package/src/workspace/git-service.ts +44 -1
  364. package/src/workspace/provider-commit-message-generator.ts +11 -7
  365. package/src/__tests__/fixtures/proxy-fixtures.ts +0 -147
  366. package/src/browser-extension-relay/client.ts +0 -155
  367. package/src/contacts/index.ts +0 -18
  368. package/src/daemon/tls-certs.ts +0 -270
  369. package/src/errors.ts +0 -41
  370. package/src/events/index.ts +0 -18
  371. package/src/followups/index.ts +0 -10
  372. package/src/playbooks/index.ts +0 -10
  373. package/src/runtime/auth/index.ts +0 -44
  374. package/src/tasks/candidate-store.ts +0 -95
  375. package/src/tools/browser/api-map.ts +0 -313
  376. package/src/tools/browser/auto-navigate.ts +0 -469
  377. package/src/tools/browser/headless-browser.ts +0 -590
  378. package/src/tools/browser/recording-store.ts +0 -75
  379. package/src/tools/computer-use/registry.ts +0 -21
  380. package/src/tools/tasks/index.ts +0 -27
@@ -1,10 +1,10 @@
1
1
  import { dirname, join } from "node:path";
2
2
 
3
- import { getConfig } from "../../../../config/loader.js";
4
3
  import {
5
4
  getKeyframesForAsset,
6
5
  getMediaAssetById,
7
6
  } from "../../../../memory/media-store.js";
7
+ import { getSecureKeyAsync } from "../../../../security/secure-keys.js";
8
8
  import type {
9
9
  ToolContext,
10
10
  ToolExecutionResult,
@@ -31,8 +31,7 @@ export async function run(
31
31
 
32
32
  let openaiApiKey: string | undefined;
33
33
  if (includeAudio && (!transcriptionMode || transcriptionMode === "api")) {
34
- const config = getConfig();
35
- openaiApiKey = config.apiKeys.openai;
34
+ openaiApiKey = (await getSecureKeyAsync("openai")) ?? undefined;
36
35
  }
37
36
 
38
37
  const options: PreprocessOptions = {
@@ -7,7 +7,6 @@ metadata:
7
7
  vellum:
8
8
  display-name: "Messaging"
9
9
  user-invocable: true
10
- feature-flag: "messaging"
11
10
  ---
12
11
 
13
12
  You are a unified messaging assistant with access to multiple platforms (Slack, Gmail, Telegram, and more). Use the messaging tools to help users read, search, organize, draft, and send messages across all connected platforms.
@@ -15,7 +15,7 @@ All call-related settings can be managed via `assistant config`:
15
15
  | `calls.callerIdentity.userNumber` | E.164 phone number for user-number mode | _(empty)_ |
16
16
  | `calls.voice.language` | Language code for TTS and transcription | `en-US` |
17
17
  | `calls.voice.transcriptionProvider` | Speech-to-text provider (`Deepgram`, `Google`) | `Deepgram` |
18
- | `elevenlabs.voiceId` | ElevenLabs voice ID used by both in-app TTS and phone calls. Set during setup from the curated voice list. Defaults to Rachel | `21m00Tcm4TlvDq8ikWAM` |
18
+ | `elevenlabs.voiceId` | ElevenLabs voice ID used by both in-app TTS and phone calls. Set during setup from the curated voice list. Defaults to Amelia | `ZF6FPAbjXT4488VcRRnw` |
19
19
  | `elevenlabs.voiceModelId` | Optional Twilio ConversationRelay model suffix. Leave empty to send bare `voiceId` | _(empty)_ |
20
20
  | `elevenlabs.speed` | Playback speed (`0.7` – `1.2`) | `1.0` |
21
21
  | `elevenlabs.stability` | Voice stability (`0.0` – `1.0`) | `0.5` |
@@ -7,7 +7,6 @@ metadata:
7
7
  vellum:
8
8
  display-name: "Email Sequences"
9
9
  user-invocable: true
10
- feature-flag: "sequences"
11
10
  ---
12
11
 
13
12
  You are an email sequence assistant. Use the sequence tools to help users create and manage automated multi-step email drip campaigns.
@@ -10,8 +10,8 @@ import {
10
10
  import { tmpdir } from "node:os";
11
11
  import { extname, join } from "node:path";
12
12
 
13
- import { getConfig } from "../../../../config/loader.js";
14
13
  import { getAttachmentsByIds } from "../../../../memory/attachments-store.js";
14
+ import { getSecureKeyAsync } from "../../../../security/secure-keys.js";
15
15
  import type {
16
16
  ToolContext,
17
17
  ToolExecutionResult,
@@ -417,10 +417,10 @@ export async function run(
417
417
  }
418
418
 
419
419
  // Validate API key for api mode
420
+ let openaiKey: string | undefined;
420
421
  if (mode === "api") {
421
- const config = getConfig();
422
- const apiKey = config.apiKeys.openai;
423
- if (!apiKey) {
422
+ openaiKey = await getSecureKeyAsync("openai");
423
+ if (!openaiKey) {
424
424
  return {
425
425
  content:
426
426
  'No OpenAI API key configured. Set your OpenAI API key to use cloud transcription, or use mode "local" for on-device transcription with whisper.cpp.',
@@ -441,8 +441,7 @@ export async function run(
441
441
 
442
442
  let text: string;
443
443
  if (mode === "api") {
444
- const config = getConfig();
445
- text = await transcribeViaApi(wavPath, config.apiKeys.openai!, context);
444
+ text = await transcribeViaApi(wavPath, openaiKey!, context);
446
445
  } else {
447
446
  text = await transcribeViaLocal(wavPath, context);
448
447
  }
package/src/config/env.ts CHANGED
@@ -154,6 +154,19 @@ export function getPlatformInternalApiKey(): string {
154
154
  return str("PLATFORM_INTERNAL_API_KEY") ?? "";
155
155
  }
156
156
 
157
+ // ── Telemetry ──────────────────────────────────────────────────────────────────
158
+
159
+ export function getTelemetryPlatformUrl(): string {
160
+ return str("TELEMETRY_PLATFORM_URL") ?? "https://platform.vellum.ai";
161
+ }
162
+
163
+ export function getTelemetryAppToken(): string {
164
+ return (
165
+ str("TELEMETRY_APP_TOKEN") ??
166
+ "e01cf85768cc3617e986f0a7f1966b72e25316526c5db54c8b94a9c3c5c9eaed"
167
+ );
168
+ }
169
+
157
170
  // ── Startup validation ──────────────────────────────────────────────────────
158
171
 
159
172
  /**
@@ -25,20 +25,12 @@
25
25
  "description": "Enable browser skill prerequisites section in the system prompt",
26
26
  "defaultEnabled": true
27
27
  },
28
- {
29
- "id": "messaging",
30
- "scope": "assistant",
31
- "key": "feature_flags.messaging.enabled",
32
- "label": "Messaging",
33
- "description": "Enable messaging skill section in the system prompt",
34
- "defaultEnabled": true
35
- },
36
28
  {
37
29
  "id": "collect-usage-data",
38
30
  "scope": "assistant",
39
31
  "key": "feature_flags.collect-usage-data.enabled",
40
32
  "label": "Collect usage data",
41
- "description": "Send crash reports and error diagnostics to help improve the app",
33
+ "description": "Send crash reports, error diagnostics, and anonymized usage telemetry to help improve the app",
42
34
  "defaultEnabled": true
43
35
  },
44
36
  {
@@ -49,14 +41,6 @@
49
41
  "description": "Enable user-hosted onboarding flow",
50
42
  "defaultEnabled": false
51
43
  },
52
- {
53
- "id": "command-palette",
54
- "scope": "assistant",
55
- "key": "feature_flags.command-palette.enabled",
56
- "label": "Command Palette",
57
- "description": "Enable the CMD+K command palette for unified search across conversations, memories, schedules, and contacts",
58
- "defaultEnabled": false
59
- },
60
44
  {
61
45
  "id": "contacts",
62
46
  "scope": "assistant",
@@ -73,14 +57,6 @@
73
57
  "description": "Show the Email channel card on the Contacts page and enable the email-setup skill",
74
58
  "defaultEnabled": false
75
59
  },
76
- {
77
- "id": "outbound-proxy-sidecar",
78
- "scope": "assistant",
79
- "key": "feature_flags.outbound-proxy-sidecar.enabled",
80
- "label": "Outbound Proxy Sidecar",
81
- "description": "Route proxy session management through the sidecar process instead of running in-process",
82
- "defaultEnabled": false
83
- },
84
60
  {
85
61
  "id": "app-builder-multifile",
86
62
  "scope": "assistant",
@@ -98,20 +74,12 @@
98
74
  "defaultEnabled": false
99
75
  },
100
76
  {
101
- "id": "gmail",
102
- "scope": "assistant",
103
- "key": "feature_flags.gmail.enabled",
104
- "label": "Gmail",
105
- "description": "Enable Gmail management skill (archive, label, declutter)",
106
- "defaultEnabled": true
107
- },
108
- {
109
- "id": "sequences",
110
- "scope": "assistant",
111
- "key": "feature_flags.sequences.enabled",
112
- "label": "Email Sequences",
113
- "description": "Enable email sequence management skill",
114
- "defaultEnabled": true
77
+ "id": "mobile-pairing",
78
+ "scope": "macos",
79
+ "key": "mobile_pairing_enabled",
80
+ "label": "Mobile Pairing",
81
+ "description": "Show the Mobile (iOS) pairing card in Settings > Account",
82
+ "defaultEnabled": false
115
83
  },
116
84
  {
117
85
  "id": "settings-developer-nav",
@@ -121,6 +89,14 @@
121
89
  "description": "Control Developer nav visibility in macOS settings",
122
90
  "defaultEnabled": true
123
91
  },
92
+ {
93
+ "id": "developer-menu-items",
94
+ "scope": "macos",
95
+ "key": "developer_menu_items_enabled",
96
+ "label": "Developer Menu Items",
97
+ "description": "Show Component Gallery and Replay Onboarding in the menu bar",
98
+ "defaultEnabled": false
99
+ },
124
100
  {
125
101
  "id": "logfire",
126
102
  "scope": "assistant",
@@ -7,11 +7,6 @@ import {
7
7
  } from "node:fs";
8
8
  import { dirname } from "node:path";
9
9
 
10
- import {
11
- deleteSecureKey,
12
- getSecureKey,
13
- setSecureKey,
14
- } from "../security/secure-keys.js";
15
10
  import { ConfigError } from "../util/errors.js";
16
11
  import { getLogger } from "../util/logger.js";
17
12
  import {
@@ -162,7 +157,6 @@ export function deepMergeMissing(
162
157
  /**
163
158
  * Read the existing config.json from disk, merge any missing schema-default
164
159
  * keys, and rewrite only when there is an effective change.
165
- * Preserves exclusions: apiKeys and dataDir are never written.
166
160
  */
167
161
  function backfillConfigDefaults(
168
162
  configPath: string,
@@ -197,9 +191,9 @@ function backfillConfigDefaults(
197
191
  export function loadConfig(): AssistantConfig {
198
192
  if (cached) return cached;
199
193
 
200
- // Re-entrancy guard: log calls during loading (e.g. file-mode warning,
201
- // invalid apiKeys) can trigger loadConfig again. Return defaults to
202
- // break the cycle instead of recursing to stack overflow.
194
+ // Re-entrancy guard: log calls during loading (e.g. file-mode warning)
195
+ // can trigger loadConfig again. Return defaults to break the cycle
196
+ // instead of recursing to stack overflow.
203
197
  if (loading) return cloneDefaultConfig();
204
198
  loading = true;
205
199
 
@@ -230,67 +224,6 @@ export function loadConfig(): AssistantConfig {
230
224
  configFileExisted = false;
231
225
  }
232
226
 
233
- // Pre-validate apiKeys shape before migration (must be a plain object)
234
- if (
235
- fileConfig.apiKeys !== undefined &&
236
- (typeof fileConfig.apiKeys !== "object" ||
237
- fileConfig.apiKeys == null ||
238
- Array.isArray(fileConfig.apiKeys))
239
- ) {
240
- log.warn(
241
- "Invalid apiKeys in config file: must be an object with string values. Ignoring.",
242
- );
243
- delete fileConfig.apiKeys;
244
- }
245
-
246
- // Auto-migrate plaintext apiKeys from config.json to secure storage
247
- if (fileConfig.apiKeys && typeof fileConfig.apiKeys === "object") {
248
- const apiKeysObj = fileConfig.apiKeys as Record<string, unknown>;
249
- const plaintextKeys = Object.entries(apiKeysObj).filter(
250
- ([, v]) => typeof v === "string" && (v as string).length > 0,
251
- );
252
- if (plaintextKeys.length > 0) {
253
- const migratedProviders: string[] = [];
254
- for (const [provider, value] of plaintextKeys) {
255
- if (setSecureKey(provider, value as string)) {
256
- migratedProviders.push(provider);
257
- } else {
258
- log.warn(
259
- `Failed to migrate API key for "${provider}" to secure storage`,
260
- );
261
- }
262
- }
263
- if (migratedProviders.length > 0) {
264
- // Rewrite config.json without successfully migrated apiKeys
265
- try {
266
- const rawJson = JSON.parse(readFileSync(configPath, "utf-8"));
267
- for (const p of migratedProviders) {
268
- delete rawJson.apiKeys[p];
269
- }
270
- if (Object.keys(rawJson.apiKeys).length === 0) {
271
- delete rawJson.apiKeys;
272
- }
273
- writeFileSync(configPath, JSON.stringify(rawJson, null, 2) + "\n");
274
- log.info(
275
- `Migrated ${migratedProviders.length} API key(s) from config.json to secure storage`,
276
- );
277
- } catch (err) {
278
- log.warn(
279
- { err },
280
- "Failed to remove migrated keys from config.json",
281
- );
282
- }
283
- }
284
- // Clear only migrated keys from fileConfig so failed keys still flow into config
285
- for (const p of migratedProviders) {
286
- delete apiKeysObj[p];
287
- }
288
- if (Object.keys(apiKeysObj).length === 0) {
289
- delete fileConfig.apiKeys;
290
- }
291
- }
292
- }
293
-
294
227
  // Validate and apply defaults via Zod schema
295
228
  const config = validateWithSchema(fileConfig);
296
229
 
@@ -303,8 +236,8 @@ export function loadConfig(): AssistantConfig {
303
236
  if (!existsSync(dir)) {
304
237
  mkdirSync(dir, { recursive: true });
305
238
  }
306
- // Strip apiKeys (managed in secure storage) and dataDir (runtime-derived)
307
- const { apiKeys: _, dataDir: _d, ...persistable } = config;
239
+ // Strip dataDir (runtime-derived) from the persisted config
240
+ const { dataDir: _, ...persistable } = config;
308
241
 
309
242
  if (!configFileExisted) {
310
243
  writeFileSync(configPath, JSON.stringify(persistable, null, 2) + "\n");
@@ -316,48 +249,8 @@ export function loadConfig(): AssistantConfig {
316
249
  log.warn({ err }, "Failed to write/backfill config file");
317
250
  }
318
251
 
319
- // Set cached before secure-key/env overrides so re-entrant calls
320
- // return the in-flight config instead of bare defaults.
321
252
  cached = config;
322
253
 
323
- // Secure storage keys override plaintext config file
324
- try {
325
- for (const provider of API_KEY_PROVIDERS) {
326
- const secureKey = getSecureKey(provider);
327
- if (secureKey) {
328
- config.apiKeys[provider] = secureKey;
329
- }
330
- }
331
- } catch (err) {
332
- log.debug({ err }, "Failed to load keys from secure storage");
333
- }
334
-
335
- // Environment variables override everything
336
- if (process.env.ANTHROPIC_API_KEY) {
337
- config.apiKeys.anthropic = process.env.ANTHROPIC_API_KEY;
338
- }
339
- if (process.env.OPENAI_API_KEY) {
340
- config.apiKeys.openai = process.env.OPENAI_API_KEY;
341
- }
342
- if (process.env.GEMINI_API_KEY) {
343
- config.apiKeys.gemini = process.env.GEMINI_API_KEY;
344
- }
345
- if (process.env.OLLAMA_API_KEY) {
346
- config.apiKeys.ollama = process.env.OLLAMA_API_KEY;
347
- }
348
- if (process.env.FIREWORKS_API_KEY) {
349
- config.apiKeys.fireworks = process.env.FIREWORKS_API_KEY;
350
- }
351
- if (process.env.OPENROUTER_API_KEY) {
352
- config.apiKeys.openrouter = process.env.OPENROUTER_API_KEY;
353
- }
354
- if (process.env.BRAVE_API_KEY) {
355
- config.apiKeys.brave = process.env.BRAVE_API_KEY;
356
- }
357
- if (process.env.PERPLEXITY_API_KEY) {
358
- config.apiKeys.perplexity = process.env.PERPLEXITY_API_KEY;
359
- }
360
-
361
254
  loading = false;
362
255
  return config;
363
256
  } catch (err) {
@@ -372,25 +265,7 @@ export function saveConfig(config: AssistantConfig): void {
372
265
  ensureMigratedDataDir();
373
266
  const configPath = getConfigPath();
374
267
 
375
- // Route apiKeys to secure storage, write config without them
376
- for (const [provider, value] of Object.entries(config.apiKeys)) {
377
- if (typeof value === "string" && value.length > 0) {
378
- if (!setSecureKey(provider, value)) {
379
- throw new ConfigError(
380
- `Failed to save API key for "${provider}" to secure storage`,
381
- );
382
- }
383
- }
384
- }
385
- // Delete secure keys for providers no longer in apiKeys or with empty values
386
- for (const provider of API_KEY_PROVIDERS) {
387
- const value = config.apiKeys[provider];
388
- if (!value || (typeof value === "string" && value.length === 0)) {
389
- deleteSecureKey(provider);
390
- }
391
- }
392
- const { apiKeys: _, ...rest } = config;
393
- writeFileSync(configPath, JSON.stringify(rest, null, 2) + "\n");
268
+ writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
394
269
 
395
270
  cached = config;
396
271
  }
@@ -428,10 +303,7 @@ export function saveRawConfig(config: Record<string, unknown>): void {
428
303
  ensureMigratedDataDir();
429
304
  const configPath = getConfigPath();
430
305
 
431
- // Strip apiKeys from plaintext config secure storage is managed
432
- // by saveConfig() and `assistant keys` commands, not here.
433
- const { apiKeys: _, ...rest } = config;
434
- writeFileSync(configPath, JSON.stringify(rest, null, 2) + "\n");
306
+ writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
435
307
 
436
308
  cached = null; // invalidate cache
437
309
  }
@@ -232,12 +232,6 @@ export const AssistantConfigSchema = z
232
232
  imageGenModel: z
233
233
  .string({ error: "imageGenModel must be a string" })
234
234
  .default("gemini-2.5-flash-image"),
235
- apiKeys: z
236
- .record(
237
- z.string(),
238
- z.string({ error: "Each apiKeys value must be a string" }),
239
- )
240
- .default({} as Record<string, string>),
241
235
  webSearchProvider: z
242
236
  .enum(VALID_WEB_SEARCH_PROVIDERS, {
243
237
  error: `webSearchProvider must be one of: ${VALID_WEB_SEARCH_PROVIDERS.join(
@@ -34,6 +34,7 @@ export const WhatsAppConfigSchema = z.object({
34
34
  });
35
35
 
36
36
  export const TelegramConfigSchema = z.object({
37
+ botId: z.string({ error: "telegram.botId must be a string" }).default(""),
37
38
  botUsername: z
38
39
  .string({ error: "telegram.botUsername must be a string" })
39
40
  .default(""),
@@ -1,9 +1,9 @@
1
1
  import { z } from "zod";
2
2
 
3
- // Default ElevenLabs voice — "Rachel" (calm, warm, conversational).
3
+ // Default ElevenLabs voice — "Amelia" (expressive, enthusiastic, British English).
4
4
  // Used by both in-app TTS and phone calls (via Twilio ConversationRelay).
5
5
  // Mirrored in: clients/macos/.../OpenAIVoiceService.swift (defaultVoiceId)
6
- export const DEFAULT_ELEVENLABS_VOICE_ID = "21m00Tcm4TlvDq8ikWAM";
6
+ export const DEFAULT_ELEVENLABS_VOICE_ID = "ZF6FPAbjXT4488VcRRnw";
7
7
 
8
8
  export const ElevenLabsConfigSchema = z.object({
9
9
  voiceId: z
@@ -5,7 +5,7 @@ const VALID_PERMISSIONS_MODES = ["strict", "workspace"] as const;
5
5
 
6
6
  export { VALID_PERMISSIONS_MODES, VALID_SECRET_ACTIONS };
7
7
 
8
- export const CustomSecretPatternSchema = z.object({
8
+ const CustomSecretPatternSchema = z.object({
9
9
  label: z.string({
10
10
  error: "secretDetection.customPatterns[].label must be a string",
11
11
  }),
@@ -49,6 +49,5 @@ export const PermissionsConfigSchema = z.object({
49
49
  .default("workspace"),
50
50
  });
51
51
 
52
- export type CustomSecretPattern = z.infer<typeof CustomSecretPatternSchema>;
53
52
  export type SecretDetectionConfig = z.infer<typeof SecretDetectionConfigSchema>;
54
53
  export type PermissionsConfig = z.infer<typeof PermissionsConfigSchema>;
@@ -1294,7 +1294,7 @@ async function generateSkillIcon(
1294
1294
  name: string,
1295
1295
  description: string,
1296
1296
  ): Promise<string> {
1297
- const provider = getConfiguredProvider();
1297
+ const provider = await getConfiguredProvider();
1298
1298
  if (!provider) {
1299
1299
  throw new Error("Configured provider unavailable for icon generation");
1300
1300
  }
@@ -2,7 +2,6 @@ import { and, asc, desc, eq, like, sql } from "drizzle-orm";
2
2
  import { v4 as uuid } from "uuid";
3
3
 
4
4
  import { getDb } from "../memory/db.js";
5
- import { rawChanges } from "../memory/raw-query.js";
6
5
  import {
7
6
  assistantContactMetadata,
8
7
  contactChannels,
@@ -34,8 +33,8 @@ function parseContact(row: typeof contacts.$inferSelect): Contact {
34
33
  id: row.id,
35
34
  displayName: row.displayName,
36
35
  notes: row.notes,
37
- lastInteraction: row.lastInteraction,
38
- interactionCount: row.interactionCount,
36
+ lastInteraction: null,
37
+ interactionCount: 0,
39
38
  createdAt: row.createdAt,
40
39
  updatedAt: row.updatedAt,
41
40
  role: row.role as Contact["role"],
@@ -63,6 +62,8 @@ function parseChannel(
63
62
  revokedReason: row.revokedReason,
64
63
  blockedReason: row.blockedReason,
65
64
  lastSeenAt: row.lastSeenAt,
65
+ interactionCount: row.interactionCount,
66
+ lastInteraction: row.lastInteraction,
66
67
  updatedAt: row.updatedAt,
67
68
  createdAt: row.createdAt,
68
69
  };
@@ -80,7 +81,15 @@ function getChannelsForContact(contactId: string): ContactChannel[] {
80
81
  }
81
82
 
82
83
  function withChannels(contact: Contact): ContactWithChannels {
83
- return { ...contact, channels: getChannelsForContact(contact.id) };
84
+ const channels = getChannelsForContact(contact.id);
85
+ const interactionCount = channels.reduce(
86
+ (sum, ch) => sum + ch.interactionCount,
87
+ 0,
88
+ );
89
+ const lastInteraction =
90
+ channels.reduce((max, ch) => Math.max(max, ch.lastInteraction ?? 0), 0) ||
91
+ null;
92
+ return { ...contact, interactionCount, lastInteraction, channels };
84
93
  }
85
94
 
86
95
  // ── Channel data type for syncChannels ───────────────────────────────
@@ -235,8 +244,6 @@ export function upsertContact(params: {
235
244
  id: contactId,
236
245
  displayName: params.displayName,
237
246
  notes: params.notes ?? null,
238
- lastInteraction: null,
239
- interactionCount: 0,
240
247
  role: params.role ?? "contact",
241
248
  contactType: params.contactType ?? "human",
242
249
  principalId: params.principalId ?? null,
@@ -488,7 +495,7 @@ export function searchContacts(params: {
488
495
  .from(contacts)
489
496
  .innerJoin(contactChannels, eq(contacts.id, contactChannels.contactId))
490
497
  .where(whereClause)
491
- .orderBy(desc(contacts.updatedAt), desc(contacts.lastInteraction))
498
+ .orderBy(desc(contacts.updatedAt))
492
499
  .all();
493
500
 
494
501
  const contactIds = [...new Set(rows.map((r) => r.contactId))];
@@ -509,7 +516,7 @@ export function searchContacts(params: {
509
516
  .select()
510
517
  .from(contacts)
511
518
  .where(whereClause)
512
- .orderBy(desc(contacts.updatedAt), desc(contacts.lastInteraction))
519
+ .orderBy(desc(contacts.updatedAt))
513
520
  .limit(limit)
514
521
  .all();
515
522
 
@@ -531,11 +538,7 @@ export function listContacts(
531
538
  .select()
532
539
  .from(contacts)
533
540
  .where(conditions.length === 1 ? conditions[0] : and(...conditions))
534
- .orderBy(
535
- sql`${contacts.role} = 'guardian' DESC`,
536
- desc(contacts.updatedAt),
537
- desc(contacts.lastInteraction),
538
- )
541
+ .orderBy(sql`${contacts.role} = 'guardian' DESC`, desc(contacts.updatedAt))
539
542
  .limit(effectiveLimit)
540
543
  .all();
541
544
  return rows.map((r) => withChannels(parseContact(r)));
@@ -571,16 +574,8 @@ export function mergeContacts(
571
574
  .get();
572
575
  if (!merge) throw new Error(`Contact "${mergeId}" not found`);
573
576
 
574
- // Resolve merged field values — pick the better/more recent value
575
- const mergedInteractionCount =
576
- keep.interactionCount + merge.interactionCount;
577
- const mergedLastInteraction =
578
- Math.max(keep.lastInteraction ?? 0, merge.lastInteraction ?? 0) || null;
579
-
580
577
  tx.update(contacts)
581
578
  .set({
582
- interactionCount: mergedInteractionCount,
583
- lastInteraction: mergedLastInteraction,
584
579
  notes: [keep.notes, merge.notes].filter(Boolean).join("\n") || null,
585
580
  updatedAt: now,
586
581
  })
@@ -792,46 +787,6 @@ export function findGuardianForChannel(
792
787
  };
793
788
  }
794
789
 
795
- /**
796
- * Revoke the guardian's active channel of the given type by setting its
797
- * status to 'revoked'. This ensures findGuardianForChannel() no longer
798
- * returns stale data after a binding is revoked.
799
- *
800
- * Returns true if a channel was found and revoked, false otherwise.
801
- */
802
- export function revokeGuardianChannel(channelType: string): boolean {
803
- const db = getDb();
804
- const conditions = [
805
- eq(contacts.role, "guardian"),
806
- eq(contactChannels.type, channelType),
807
- eq(contactChannels.status, "active"),
808
- ];
809
- const rows = db
810
- .select({
811
- channelId: contactChannels.id,
812
- })
813
- .from(contacts)
814
- .innerJoin(contactChannels, eq(contacts.id, contactChannels.contactId))
815
- .where(and(...conditions))
816
- .all();
817
-
818
- if (rows.length === 0) return false;
819
-
820
- const now = Date.now();
821
- for (const row of rows) {
822
- db.update(contactChannels)
823
- .set({
824
- status: "revoked",
825
- revokedReason: "guardian_binding_revoked",
826
- updatedAt: now,
827
- })
828
- .where(eq(contactChannels.id, row.channelId))
829
- .run();
830
- }
831
-
832
- return true;
833
- }
834
-
835
790
  /**
836
791
  * List all active channels for guardian contacts.
837
792
  * This is the contacts-based equivalent of listActiveBindingsByAssistant(assistantId).
@@ -934,19 +889,19 @@ export function updateChannelLastSeenById(channelId: string): void {
934
889
  }
935
890
 
936
891
  /**
937
- * Atomically increment interactionCount and set lastInteraction on a contact.
892
+ * Atomically increment interactionCount and set lastInteraction on a contact channel.
938
893
  * Optimized for the hot path — single UPDATE with no prior SELECT.
939
894
  */
940
- export function updateContactInteraction(contactId: string): void {
895
+ export function updateChannelInteraction(channelId: string): void {
941
896
  const db = getDb();
942
897
  const now = Date.now();
943
- db.update(contacts)
898
+ db.update(contactChannels)
944
899
  .set({
945
900
  lastInteraction: now,
946
- interactionCount: sql`${contacts.interactionCount} + 1`,
901
+ interactionCount: sql`${contactChannels.interactionCount} + 1`,
947
902
  updatedAt: now,
948
903
  })
949
- .where(eq(contacts.id, contactId))
904
+ .where(eq(contactChannels.id, channelId))
950
905
  .run();
951
906
  }
952
907
 
@@ -1037,12 +992,3 @@ export function getAssistantContactMetadata(
1037
992
  if (!row) return null;
1038
993
  return parseAssistantMetadata(row);
1039
994
  }
1040
-
1041
- export function deleteAssistantContactMetadata(contactId: string): boolean {
1042
- const db = getDb();
1043
- db.delete(assistantContactMetadata)
1044
- .where(eq(assistantContactMetadata.contactId, contactId))
1045
- .run();
1046
-
1047
- return rawChanges() > 0;
1048
- }
@@ -16,9 +16,9 @@ import {
16
16
  findGuardianForChannel,
17
17
  getChannelById,
18
18
  getContactInternal,
19
+ updateChannelInteraction,
19
20
  updateChannelLastSeenById,
20
21
  updateChannelStatus,
21
- updateContactInteraction,
22
22
  upsertContact,
23
23
  } from "./contact-store.js";
24
24
  import type {
@@ -287,13 +287,13 @@ export function touchChannelLastSeen(channelId: string): void {
287
287
  }
288
288
 
289
289
  /**
290
- * Increment the interaction count and update lastInteraction on a contact.
291
- * Expects a plain contact UUID (Contact.id).
290
+ * Track an interaction on the specific channel that received it.
291
+ * Swallows errors to avoid disrupting the inbound message hot path.
292
292
  */
293
- export function touchContactInteraction(contactId: string): void {
293
+ export function touchContactInteraction(channelId: string): void {
294
294
  try {
295
- updateContactInteraction(contactId);
295
+ updateChannelInteraction(channelId);
296
296
  } catch (err) {
297
- log.warn({ err }, "Failed to update contact interaction stats");
297
+ log.warn({ err }, "Failed to update channel interaction stats");
298
298
  }
299
299
  }