@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
@@ -0,0 +1,366 @@
1
+ /**
2
+ * Tests for UsageTelemetryReporter.
3
+ *
4
+ * Covers both auth modes (authenticated / anonymous), watermark advancement,
5
+ * error handling, batch recursion, installation ID persistence, and payload shape.
6
+ */
7
+ import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
8
+
9
+ // ---------------------------------------------------------------------------
10
+ // Module mocks (must precede production imports)
11
+ // ---------------------------------------------------------------------------
12
+
13
+ const mockGetMemoryCheckpoint = mock<(key: string) => string | null>(
14
+ () => null,
15
+ );
16
+ const mockSetMemoryCheckpoint = mock<(key: string, value: string) => void>(
17
+ () => {},
18
+ );
19
+
20
+ mock.module("../memory/checkpoints.js", () => ({
21
+ getMemoryCheckpoint: mockGetMemoryCheckpoint,
22
+ setMemoryCheckpoint: mockSetMemoryCheckpoint,
23
+ }));
24
+
25
+ const mockQueryUnreportedUsageEvents = mock(
26
+ () =>
27
+ [] as ReturnType<
28
+ typeof import("../memory/llm-usage-store.js").queryUnreportedUsageEvents
29
+ >,
30
+ );
31
+
32
+ mock.module("../memory/llm-usage-store.js", () => ({
33
+ queryUnreportedUsageEvents: mockQueryUnreportedUsageEvents,
34
+ }));
35
+
36
+ const mockResolveManagedProxyContext = mock(async () => ({
37
+ enabled: false,
38
+ platformBaseUrl: "",
39
+ assistantApiKey: "",
40
+ }));
41
+
42
+ mock.module("../providers/managed-proxy/context.js", () => ({
43
+ resolveManagedProxyContext: mockResolveManagedProxyContext,
44
+ }));
45
+
46
+ const mockGetTelemetryPlatformUrl = mock(() => "https://platform.vellum.ai");
47
+ const mockGetTelemetryAppToken = mock(() => "");
48
+
49
+ mock.module("../config/env.js", () => ({
50
+ getTelemetryPlatformUrl: mockGetTelemetryPlatformUrl,
51
+ getTelemetryAppToken: mockGetTelemetryAppToken,
52
+ // Re-export anything else the module might import transitively
53
+ str: () => undefined,
54
+ num: () => undefined,
55
+ bool: () => false,
56
+ }));
57
+
58
+ mock.module("../util/logger.js", () => ({
59
+ getLogger: () =>
60
+ new Proxy({} as Record<string, unknown>, {
61
+ get: () => () => {},
62
+ }),
63
+ }));
64
+
65
+ // ---------------------------------------------------------------------------
66
+ // Production import (after mocks)
67
+ // ---------------------------------------------------------------------------
68
+
69
+ import type { UsageEvent } from "../usage/types.js";
70
+ import { UsageTelemetryReporter } from "./usage-telemetry-reporter.js";
71
+
72
+ // ---------------------------------------------------------------------------
73
+ // Helpers
74
+ // ---------------------------------------------------------------------------
75
+
76
+ let eventIdCounter = 0;
77
+
78
+ function makeUsageEvent(overrides: Partial<UsageEvent> = {}): UsageEvent {
79
+ eventIdCounter += 1;
80
+ return {
81
+ id: `evt-${eventIdCounter}`,
82
+ createdAt: 1700000000000 + eventIdCounter * 1000,
83
+ provider: "anthropic",
84
+ model: "claude-sonnet-4-20250514",
85
+ inputTokens: 100,
86
+ outputTokens: 50,
87
+ cacheCreationInputTokens: 10,
88
+ cacheReadInputTokens: 5,
89
+ actor: "main_agent",
90
+ conversationId: "conv-1",
91
+ runId: null,
92
+ requestId: null,
93
+ estimatedCostUsd: 0.001,
94
+ pricingStatus: "priced",
95
+ ...overrides,
96
+ };
97
+ }
98
+
99
+ const originalFetch = globalThis.fetch;
100
+ let mockFetch: ReturnType<typeof mock>;
101
+
102
+ // ---------------------------------------------------------------------------
103
+ // Setup / Teardown
104
+ // ---------------------------------------------------------------------------
105
+
106
+ beforeEach(() => {
107
+ eventIdCounter = 0;
108
+ mockGetMemoryCheckpoint.mockReset();
109
+ mockSetMemoryCheckpoint.mockReset();
110
+ mockQueryUnreportedUsageEvents.mockReset();
111
+ mockResolveManagedProxyContext.mockReset();
112
+ mockGetTelemetryPlatformUrl.mockReset();
113
+ mockGetTelemetryAppToken.mockReset();
114
+
115
+ // Defaults
116
+ mockGetMemoryCheckpoint.mockReturnValue(null);
117
+ mockResolveManagedProxyContext.mockResolvedValue({
118
+ enabled: false,
119
+ platformBaseUrl: "",
120
+ assistantApiKey: "",
121
+ });
122
+ mockGetTelemetryPlatformUrl.mockReturnValue("https://platform.vellum.ai");
123
+ mockGetTelemetryAppToken.mockReturnValue("default-test-token");
124
+
125
+ mockFetch = mock(() =>
126
+ Promise.resolve(new Response('{"accepted":0}', { status: 200 })),
127
+ );
128
+ globalThis.fetch = mockFetch as unknown as typeof fetch;
129
+ });
130
+
131
+ afterEach(() => {
132
+ globalThis.fetch = originalFetch;
133
+ });
134
+
135
+ // ---------------------------------------------------------------------------
136
+ // Tests
137
+ // ---------------------------------------------------------------------------
138
+
139
+ describe("UsageTelemetryReporter", () => {
140
+ test("authenticated flush uses Api-Key header and proxy URL", async () => {
141
+ mockResolveManagedProxyContext.mockResolvedValue({
142
+ enabled: true,
143
+ platformBaseUrl: "https://test.vellum.ai",
144
+ assistantApiKey: "test-key",
145
+ });
146
+ const events = [makeUsageEvent(), makeUsageEvent()];
147
+ mockQueryUnreportedUsageEvents.mockReturnValue(events);
148
+ mockFetch.mockImplementation(() =>
149
+ Promise.resolve(
150
+ new Response(`{"accepted":${events.length}}`, { status: 200 }),
151
+ ),
152
+ );
153
+
154
+ const reporter = new UsageTelemetryReporter();
155
+ await reporter.flush();
156
+
157
+ expect(mockFetch).toHaveBeenCalledTimes(1);
158
+ const [url, opts] = mockFetch.mock.calls[0] as [string, RequestInit];
159
+ expect(url).toBe(
160
+ "https://test.vellum.ai/v1/assistants/self-hosted-local/telemetry/usage/",
161
+ );
162
+ expect((opts.headers as Record<string, string>)["Authorization"]).toBe(
163
+ "Api-Key test-key",
164
+ );
165
+ });
166
+
167
+ test("anonymous flush uses X-Telemetry-Token and default URL", async () => {
168
+ mockResolveManagedProxyContext.mockResolvedValue({
169
+ enabled: false,
170
+ platformBaseUrl: "",
171
+ assistantApiKey: "",
172
+ });
173
+ mockGetTelemetryPlatformUrl.mockReturnValue("https://platform.test.ai");
174
+ mockGetTelemetryAppToken.mockReturnValue("anon-token");
175
+
176
+ const events = [makeUsageEvent()];
177
+ mockQueryUnreportedUsageEvents.mockReturnValue(events);
178
+ mockFetch.mockImplementation(() =>
179
+ Promise.resolve(new Response('{"accepted":1}', { status: 200 })),
180
+ );
181
+
182
+ const reporter = new UsageTelemetryReporter();
183
+ await reporter.flush();
184
+
185
+ expect(mockFetch).toHaveBeenCalledTimes(1);
186
+ const [url, opts] = mockFetch.mock.calls[0] as [string, RequestInit];
187
+ expect(url).toStartWith("https://platform.test.ai");
188
+ expect((opts.headers as Record<string, string>)["X-Telemetry-Token"]).toBe(
189
+ "anon-token",
190
+ );
191
+ });
192
+
193
+ test("watermark advances on successful upload", async () => {
194
+ const events = [
195
+ makeUsageEvent({ id: "evt-w1", createdAt: 1700000001000 }),
196
+ makeUsageEvent({ id: "evt-w2", createdAt: 1700000002000 }),
197
+ ];
198
+ mockQueryUnreportedUsageEvents.mockReturnValue(events);
199
+ mockFetch.mockImplementation(() =>
200
+ Promise.resolve(new Response('{"accepted":2}', { status: 200 })),
201
+ );
202
+
203
+ const reporter = new UsageTelemetryReporter();
204
+ await reporter.flush();
205
+
206
+ const watermarkCalls = mockSetMemoryCheckpoint.mock.calls.filter(
207
+ (c) => c[0] === "telemetry:usage:last_reported_at",
208
+ );
209
+ expect(watermarkCalls.length).toBeGreaterThanOrEqual(1);
210
+ // The watermark should be set to the createdAt of the last event
211
+ expect(watermarkCalls[watermarkCalls.length - 1][1]).toBe(
212
+ String(1700000002000),
213
+ );
214
+
215
+ // The compound cursor ID should also be set to the last event's id
216
+ const idCalls = mockSetMemoryCheckpoint.mock.calls.filter(
217
+ (c) => c[0] === "telemetry:usage:last_reported_id",
218
+ );
219
+ expect(idCalls.length).toBeGreaterThanOrEqual(1);
220
+ expect(idCalls[idCalls.length - 1][1]).toBe("evt-w2");
221
+ });
222
+
223
+ test("watermark stays on failed upload", async () => {
224
+ const events = [makeUsageEvent()];
225
+ mockQueryUnreportedUsageEvents.mockReturnValue(events);
226
+ mockFetch.mockImplementation(() =>
227
+ Promise.resolve(new Response("error", { status: 500 })),
228
+ );
229
+
230
+ const reporter = new UsageTelemetryReporter();
231
+ await reporter.flush();
232
+
233
+ const watermarkCalls = mockSetMemoryCheckpoint.mock.calls.filter(
234
+ (c) => c[0] === "telemetry:usage:last_reported_at",
235
+ );
236
+ expect(watermarkCalls.length).toBe(0);
237
+ });
238
+
239
+ test("installation ID generated on first flush, reused thereafter", async () => {
240
+ let storedInstallId: string | null = null;
241
+
242
+ mockGetMemoryCheckpoint.mockImplementation((key: string) => {
243
+ if (key === "telemetry:installation_id") return storedInstallId;
244
+ return null;
245
+ });
246
+ mockSetMemoryCheckpoint.mockImplementation((key: string, value: string) => {
247
+ if (key === "telemetry:installation_id") storedInstallId = value;
248
+ });
249
+
250
+ const events = [makeUsageEvent()];
251
+ mockQueryUnreportedUsageEvents.mockReturnValue(events);
252
+ mockFetch.mockImplementation(() =>
253
+ Promise.resolve(new Response('{"accepted":1}', { status: 200 })),
254
+ );
255
+
256
+ const reporter = new UsageTelemetryReporter();
257
+
258
+ // First flush — should generate and store a new installation ID
259
+ await reporter.flush();
260
+ expect(mockFetch).toHaveBeenCalledTimes(1);
261
+ const body1 = JSON.parse(
262
+ (mockFetch.mock.calls[0] as [string, RequestInit])[1].body as string,
263
+ );
264
+ expect(body1.installation_id).toBeTruthy();
265
+ expect(typeof body1.installation_id).toBe("string");
266
+
267
+ // Second flush
268
+ mockQueryUnreportedUsageEvents.mockReturnValue([makeUsageEvent()]);
269
+ await reporter.flush();
270
+ expect(mockFetch).toHaveBeenCalledTimes(2);
271
+ const body2 = JSON.parse(
272
+ (mockFetch.mock.calls[1] as [string, RequestInit])[1].body as string,
273
+ );
274
+
275
+ // Both flushes should use the same installation ID
276
+ expect(body2.installation_id).toBe(body1.installation_id);
277
+ });
278
+
279
+ test("empty batch makes no HTTP call", async () => {
280
+ mockQueryUnreportedUsageEvents.mockReturnValue([]);
281
+
282
+ const reporter = new UsageTelemetryReporter();
283
+ await reporter.flush();
284
+
285
+ expect(mockFetch).not.toHaveBeenCalled();
286
+ });
287
+
288
+ test("batch recursion when full, capped at 10", async () => {
289
+ // Always return exactly 500 events (BATCH_SIZE) to trigger recursion
290
+ const fullBatch = Array.from({ length: 500 }, (_, i) =>
291
+ makeUsageEvent({ id: `evt-batch-${i}`, createdAt: 1700000000000 + i }),
292
+ );
293
+ mockQueryUnreportedUsageEvents.mockReturnValue(fullBatch);
294
+ mockFetch.mockImplementation(() =>
295
+ Promise.resolve(new Response('{"accepted":500}', { status: 200 })),
296
+ );
297
+
298
+ const reporter = new UsageTelemetryReporter();
299
+ await reporter.flush();
300
+
301
+ // MAX_CONSECUTIVE_BATCHES = 10
302
+ expect(mockFetch).toHaveBeenCalledTimes(10);
303
+ });
304
+
305
+ test("stop() performs final flush", async () => {
306
+ const events = [makeUsageEvent()];
307
+ mockQueryUnreportedUsageEvents.mockReturnValue(events);
308
+ mockFetch.mockImplementation(() =>
309
+ Promise.resolve(new Response('{"accepted":1}', { status: 200 })),
310
+ );
311
+
312
+ const reporter = new UsageTelemetryReporter();
313
+ reporter.start();
314
+
315
+ // Wait a tick so start()'s immediate flush settles.
316
+ await new Promise((resolve) => setTimeout(resolve, 50));
317
+ const callsBeforeStop = mockFetch.mock.calls.length;
318
+
319
+ await reporter.stop();
320
+
321
+ // stop() must trigger at least one additional flush beyond what start() did.
322
+ expect(mockFetch.mock.calls.length).toBeGreaterThan(callsBeforeStop);
323
+ });
324
+
325
+ test("payload shape is correct", async () => {
326
+ const event = makeUsageEvent({
327
+ id: "evt-shape-test",
328
+ provider: "anthropic",
329
+ model: "claude-sonnet-4-20250514",
330
+ inputTokens: 200,
331
+ outputTokens: 100,
332
+ cacheCreationInputTokens: 20,
333
+ cacheReadInputTokens: 15,
334
+ actor: "context_compactor",
335
+ createdAt: 1700000099000,
336
+ });
337
+ mockQueryUnreportedUsageEvents.mockReturnValue([event]);
338
+ mockFetch.mockImplementation(() =>
339
+ Promise.resolve(new Response('{"accepted":1}', { status: 200 })),
340
+ );
341
+
342
+ const reporter = new UsageTelemetryReporter();
343
+ await reporter.flush();
344
+
345
+ expect(mockFetch).toHaveBeenCalledTimes(1);
346
+ const body = JSON.parse(
347
+ (mockFetch.mock.calls[0] as [string, RequestInit])[1].body as string,
348
+ );
349
+
350
+ // Top-level: installation_id and events array
351
+ expect(typeof body.installation_id).toBe("string");
352
+ expect(Array.isArray(body.events)).toBe(true);
353
+ expect(body.events.length).toBe(1);
354
+
355
+ const e = body.events[0];
356
+ expect(e.daemon_event_id).toBe("evt-shape-test");
357
+ expect(e.provider).toBe("anthropic");
358
+ expect(e.model).toBe("claude-sonnet-4-20250514");
359
+ expect(e.input_tokens).toBe(200);
360
+ expect(e.output_tokens).toBe(100);
361
+ expect(e.cache_creation_input_tokens).toBe(20);
362
+ expect(e.cache_read_input_tokens).toBe(15);
363
+ expect(e.actor).toBe("context_compactor");
364
+ expect(e.recorded_at).toBe(1700000099000);
365
+ });
366
+ });
@@ -0,0 +1,181 @@
1
+ /**
2
+ * Usage telemetry reporter.
3
+ *
4
+ * Periodically flushes LLM usage events from the local SQLite
5
+ * `llm_usage_events` table and POSTs them to the platform telemetry endpoint.
6
+ *
7
+ * Two auth modes:
8
+ * - Authenticated: Api-Key header via managed proxy context
9
+ * - Anonymous: X-Telemetry-Token static token from env
10
+ */
11
+
12
+ import { v4 as uuid } from "uuid";
13
+
14
+ import {
15
+ getTelemetryAppToken,
16
+ getTelemetryPlatformUrl,
17
+ } from "../config/env.js";
18
+ import {
19
+ getMemoryCheckpoint,
20
+ setMemoryCheckpoint,
21
+ } from "../memory/checkpoints.js";
22
+ import { queryUnreportedUsageEvents } from "../memory/llm-usage-store.js";
23
+ import { resolveManagedProxyContext } from "../providers/managed-proxy/context.js";
24
+ import { getLogger } from "../util/logger.js";
25
+
26
+ const log = getLogger("usage-telemetry");
27
+
28
+ // ---------------------------------------------------------------------------
29
+ // Constants
30
+ // ---------------------------------------------------------------------------
31
+
32
+ const CHECKPOINT_KEY_WATERMARK = "telemetry:usage:last_reported_at";
33
+ const CHECKPOINT_KEY_WATERMARK_ID = "telemetry:usage:last_reported_id";
34
+ const CHECKPOINT_KEY_INSTALL_ID = "telemetry:installation_id";
35
+ const REPORT_INTERVAL_MS = 5 * 60 * 1000;
36
+ const BATCH_SIZE = 500;
37
+ const MAX_CONSECUTIVE_BATCHES = 10;
38
+ const TELEMETRY_PATH = "/v1/assistants/self-hosted-local/telemetry/usage/";
39
+
40
+ // ---------------------------------------------------------------------------
41
+ // Installation ID
42
+ // ---------------------------------------------------------------------------
43
+
44
+ function getOrCreateInstallationId(): string {
45
+ const existing = getMemoryCheckpoint(CHECKPOINT_KEY_INSTALL_ID);
46
+ if (existing) return existing;
47
+
48
+ const id = uuid();
49
+ setMemoryCheckpoint(CHECKPOINT_KEY_INSTALL_ID, id);
50
+ return id;
51
+ }
52
+
53
+ // ---------------------------------------------------------------------------
54
+ // Reporter
55
+ // ---------------------------------------------------------------------------
56
+
57
+ export class UsageTelemetryReporter {
58
+ private timer: ReturnType<typeof setInterval> | null = null;
59
+ private activeFlush: Promise<void> | null = null;
60
+
61
+ start(): void {
62
+ this.flush().catch((err) => {
63
+ log.warn({ err }, "Initial usage telemetry flush failed");
64
+ });
65
+ this.timer = setInterval(() => {
66
+ this.flush().catch((err) => {
67
+ log.warn({ err }, "Scheduled usage telemetry flush failed");
68
+ });
69
+ }, REPORT_INTERVAL_MS);
70
+ }
71
+
72
+ async stop(): Promise<void> {
73
+ if (this.timer) {
74
+ clearInterval(this.timer);
75
+ this.timer = null;
76
+ }
77
+ if (this.activeFlush) {
78
+ await this.activeFlush;
79
+ }
80
+ await this.flush();
81
+ }
82
+
83
+ async flush(): Promise<void> {
84
+ if (this.activeFlush) return; // overlap guard
85
+ this.activeFlush = this._doFlush();
86
+ try {
87
+ await this.activeFlush;
88
+ } finally {
89
+ this.activeFlush = null;
90
+ }
91
+ }
92
+
93
+ private async _doFlush(batchCount = 0): Promise<void> {
94
+ try {
95
+ if (batchCount >= MAX_CONSECUTIVE_BATCHES) return;
96
+
97
+ // Read watermark (compound cursor: createdAt + id)
98
+ const watermark = Number(
99
+ getMemoryCheckpoint(CHECKPOINT_KEY_WATERMARK) ?? "0",
100
+ );
101
+ const watermarkId =
102
+ getMemoryCheckpoint(CHECKPOINT_KEY_WATERMARK_ID) ?? undefined;
103
+
104
+ // Query unreported events
105
+ const events = queryUnreportedUsageEvents(
106
+ watermark,
107
+ watermarkId,
108
+ BATCH_SIZE,
109
+ );
110
+ if (events.length === 0) return;
111
+
112
+ // Resolve auth context — skip flush when neither auth mode is viable
113
+ const proxyCtx = await resolveManagedProxyContext();
114
+ if (!proxyCtx.enabled && !getTelemetryAppToken()) {
115
+ return;
116
+ }
117
+
118
+ let url: string;
119
+ let authHeaders: Record<string, string>;
120
+
121
+ if (proxyCtx.enabled) {
122
+ url = `${proxyCtx.platformBaseUrl}${TELEMETRY_PATH}`;
123
+ authHeaders = { Authorization: `Api-Key ${proxyCtx.assistantApiKey}` };
124
+ } else {
125
+ url = `${getTelemetryPlatformUrl()}${TELEMETRY_PATH}`;
126
+ authHeaders = { "X-Telemetry-Token": getTelemetryAppToken() };
127
+ }
128
+
129
+ // Build payload
130
+ const payload = {
131
+ installation_id: getOrCreateInstallationId(),
132
+ events: events.map((e) => ({
133
+ daemon_event_id: e.id,
134
+ provider: e.provider,
135
+ model: e.model,
136
+ input_tokens: e.inputTokens,
137
+ output_tokens: e.outputTokens,
138
+ cache_creation_input_tokens: e.cacheCreationInputTokens ?? null,
139
+ cache_read_input_tokens: e.cacheReadInputTokens ?? null,
140
+ actor: e.actor,
141
+ recorded_at: e.createdAt,
142
+ })),
143
+ };
144
+
145
+ // Send
146
+ const resp = await fetch(url, {
147
+ method: "POST",
148
+ headers: {
149
+ "Content-Type": "application/json",
150
+ ...authHeaders,
151
+ },
152
+ body: JSON.stringify(payload),
153
+ });
154
+
155
+ if (!resp.ok) {
156
+ await resp.text(); // consume body to release connection
157
+ log.warn(
158
+ { status: resp.status, url },
159
+ "Usage telemetry POST failed — will retry next cycle",
160
+ );
161
+ return;
162
+ }
163
+ await resp.text(); // consume body to release connection
164
+
165
+ // Advance watermark (compound cursor)
166
+ const lastEvent = events[events.length - 1];
167
+ setMemoryCheckpoint(
168
+ CHECKPOINT_KEY_WATERMARK,
169
+ String(lastEvent.createdAt),
170
+ );
171
+ setMemoryCheckpoint(CHECKPOINT_KEY_WATERMARK_ID, lastEvent.id);
172
+
173
+ // If we got a full batch, there may be more events — recurse
174
+ if (events.length === BATCH_SIZE) {
175
+ await this._doFlush(batchCount + 1);
176
+ }
177
+ } catch (err) {
178
+ log.warn({ err }, "Usage telemetry flush error — non-fatal, will retry");
179
+ }
180
+ }
181
+ }
@@ -2,9 +2,9 @@ import {
2
2
  getCCCommand,
3
3
  loadCCCommandTemplate,
4
4
  } from "../../commands/cc-command-registry.js";
5
- import { getConfig } from "../../config/loader.js";
6
5
  import { RiskLevel } from "../../permissions/types.js";
7
6
  import type { ToolDefinition } from "../../providers/types.js";
7
+ import { getSecureKeyAsync } from "../../security/secure-keys.js";
8
8
  import type { WorkerProfile } from "../../swarm/worker-backend.js";
9
9
  import { getProfilePolicy } from "../../swarm/worker-backend.js";
10
10
  import { getLogger } from "../../util/logger.js";
@@ -121,7 +121,7 @@ export const claudeCodeTool: Tool = {
121
121
  type: "string",
122
122
  enum: ["general", "researcher", "coder", "reviewer"],
123
123
  description:
124
- "Worker profile that scopes tool access. Defaults to general (backward compatible).",
124
+ "Worker profile that scopes tool access. Defaults to general.",
125
125
  },
126
126
  },
127
127
  },
@@ -203,12 +203,12 @@ export const claudeCodeTool: Tool = {
203
203
  const profilePolicy = getProfilePolicy(profileName);
204
204
 
205
205
  // Validate API key
206
- const config = getConfig();
207
- const apiKey = config.apiKeys.anthropic ?? process.env.ANTHROPIC_API_KEY;
206
+ const apiKey =
207
+ (await getSecureKeyAsync("anthropic")) ?? process.env.ANTHROPIC_API_KEY;
208
208
  if (!apiKey) {
209
209
  return {
210
210
  content:
211
- "Error: No Anthropic API key configured. Set it via config or ANTHROPIC_API_KEY environment variable.",
211
+ "Error: No Anthropic API key configured. Set it via `keys set anthropic <key>` or configure it from the Settings page under API Keys.",
212
212
  isError: true,
213
213
  };
214
214
  }
@@ -261,7 +261,7 @@ export const claudeCodeTool: Tool = {
261
261
  return { behavior: "allow" as const };
262
262
  }
263
263
 
264
- // Auto-approve safe read-only tools (backward compat for general profile)
264
+ // Auto-approve safe read-only tools (general profile default)
265
265
  if (AUTO_APPROVE_TOOLS.has(toolName)) {
266
266
  return { behavior: "allow" as const };
267
267
  }
@@ -1,7 +1,7 @@
1
1
  import { v4 as uuid } from "uuid";
2
2
 
3
3
  import { credentialKey } from "../../security/credential-key.js";
4
- import { getSecureKey } from "../../security/secure-keys.js";
4
+ import { getSecureKeyAsync } from "../../security/secure-keys.js";
5
5
  import { getLogger } from "../../util/logger.js";
6
6
  import type {
7
7
  AuthorizeRequest,
@@ -217,7 +217,7 @@ export class CredentialBroker {
217
217
  // Deletion is deferred until after a successful fill so the value survives
218
218
  // transient failures (e.g. stale element, page navigation, Playwright timeout).
219
219
  const transient = this.transientValues.get(storageKey);
220
- const value = transient?.value ?? getSecureKey(storageKey);
220
+ const value = transient?.value ?? (await getSecureKeyAsync(storageKey));
221
221
  if (!value) {
222
222
  return {
223
223
  success: false,
@@ -302,7 +302,7 @@ export class CredentialBroker {
302
302
 
303
303
  const storageKey = credentialKey(request.service, request.field);
304
304
  const transient = this.transientValues.get(storageKey);
305
- const value = transient?.value ?? getSecureKey(storageKey);
305
+ const value = transient?.value ?? (await getSecureKeyAsync(storageKey));
306
306
  if (!value) {
307
307
  return {
308
308
  success: false,
@@ -344,7 +344,9 @@ export class CredentialBroker {
344
344
  * never included in the result — the proxy reads it separately via
345
345
  * the secure key backend at injection time.
346
346
  */
347
- serverUseById(request: ServerUseByIdRequest): ServerUseByIdResult {
347
+ async serverUseById(
348
+ request: ServerUseByIdRequest,
349
+ ): Promise<ServerUseByIdResult> {
348
350
  const resolved = resolveById(request.credentialId);
349
351
  if (!resolved) {
350
352
  return {
@@ -383,7 +385,7 @@ export class CredentialBroker {
383
385
 
384
386
  // Fail-closed: verify the secret value actually exists in secure storage.
385
387
  // Without this, downstream proxy code would attempt unauthenticated requests.
386
- const value = getSecureKey(resolved.storageKey);
388
+ const value = await getSecureKeyAsync(resolved.storageKey);
387
389
  if (!value) {
388
390
  return {
389
391
  success: false,
@@ -21,7 +21,7 @@ import { credentialKey } from "../../security/credential-key.js";
21
21
  import {
22
22
  deleteSecureKeyAsync,
23
23
  getSecureKeyAsync,
24
- listSecureKeys,
24
+ listSecureKeysAsync,
25
25
  setSecureKeyAsync,
26
26
  } from "../../security/secure-keys.js";
27
27
  import { getLogger } from "../../util/logger.js";
@@ -367,10 +367,12 @@ class CredentialStoreTool implements Tool {
367
367
 
368
368
  const allMetadata = listCredentialMetadata();
369
369
  // Verify secrets still exist by reading all key names once (instead of
370
- // per-entry getSecureKey calls that each re-read/re-derive the store).
370
+ // per-entry getSecureKeyAsync calls that each re-read/re-derive the store).
371
+ // Uses the async variant to include keys from both the primary backend
372
+ // (e.g. keychain) and the encrypted store (legacy keys).
371
373
  let secureKeySet: Set<string> | undefined;
372
374
  try {
373
- secureKeySet = new Set(listSecureKeys());
375
+ secureKeySet = new Set(await listSecureKeysAsync());
374
376
  } catch (err) {
375
377
  log.error(
376
378
  { err },
@@ -765,7 +767,9 @@ class CredentialStoreTool implements Tool {
765
767
  if (dbApp) {
766
768
  if (!clientId) clientId = dbApp.clientId;
767
769
  if (!clientSecret) {
768
- clientSecret = await getSecureKeyAsync(dbApp.clientSecretCredentialPath);
770
+ clientSecret = await getSecureKeyAsync(
771
+ dbApp.clientSecretCredentialPath,
772
+ );
769
773
  }
770
774
  }
771
775
  }
@@ -900,8 +904,9 @@ class CredentialStoreTool implements Tool {
900
904
  | "loopback"
901
905
  | "gateway"
902
906
  | null) ?? "gateway";
903
- if (transport === "loopback" && descProviderRow.loopbackPort) {
904
- redirectUri = `http://127.0.0.1:${descProviderRow.loopbackPort}/oauth/callback`;
907
+ const loopbackPort = descBehavior?.loopbackPort;
908
+ if (transport === "loopback" && loopbackPort) {
909
+ redirectUri = `http://localhost:${loopbackPort}/oauth/callback`;
905
910
  } else if (transport === "loopback") {
906
911
  redirectUri =
907
912
  "(automatic — no redirect URI needed, uses random localhost port)";