@vellumai/assistant 0.4.53 → 0.4.55

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 (255) hide show
  1. package/bun.lock +62 -349
  2. package/docs/architecture/integrations.md +1 -1
  3. package/docs/architecture/keychain-broker.md +94 -29
  4. package/docs/architecture/security.md +2 -2
  5. package/knip.json +7 -29
  6. package/package.json +2 -9
  7. package/src/__tests__/agent-loop.test.ts +1 -1
  8. package/src/__tests__/app-git-history.test.ts +0 -2
  9. package/src/__tests__/app-git-service.test.ts +1 -6
  10. package/src/__tests__/approval-cascade.test.ts +0 -1
  11. package/src/__tests__/avatar-e2e.test.ts +0 -1
  12. package/src/__tests__/browser-fill-credential.test.ts +1 -6
  13. package/src/__tests__/call-domain.test.ts +0 -1
  14. package/src/__tests__/call-routes-http.test.ts +0 -1
  15. package/src/__tests__/channel-guardian.test.ts +4 -4
  16. package/src/__tests__/channel-readiness-routes.test.ts +0 -1
  17. package/src/__tests__/channel-readiness-service.test.ts +0 -1
  18. package/src/__tests__/checker.test.ts +13 -11
  19. package/src/__tests__/claude-code-skill-regression.test.ts +0 -1
  20. package/src/__tests__/claude-code-tool-profiles.test.ts +1 -2
  21. package/src/__tests__/config-loader-backfill.test.ts +0 -3
  22. package/src/__tests__/config-schema.test.ts +3 -9
  23. package/src/__tests__/config-watcher.test.ts +11 -3
  24. package/src/__tests__/credential-broker-browser-fill.test.ts +27 -24
  25. package/src/__tests__/credential-broker-server-use.test.ts +60 -24
  26. package/src/__tests__/credential-security-e2e.test.ts +1 -6
  27. package/src/__tests__/credential-security-invariants.test.ts +13 -8
  28. package/src/__tests__/credential-vault-unit.test.ts +28 -12
  29. package/src/__tests__/credential-vault.test.ts +40 -28
  30. package/src/__tests__/credentials-cli.test.ts +1 -21
  31. package/src/__tests__/email-invite-adapter.test.ts +0 -1
  32. package/src/__tests__/fixtures/credential-security-fixtures.ts +3 -3
  33. package/src/__tests__/fixtures/media-reuse-fixtures.ts +3 -79
  34. package/src/__tests__/gateway-only-enforcement.test.ts +1 -21
  35. package/src/__tests__/guardian-action-conversation-turn.test.ts +8 -8
  36. package/src/__tests__/guardian-action-late-reply.test.ts +13 -14
  37. package/src/__tests__/guardian-action-store.test.ts +0 -57
  38. package/src/__tests__/guardian-outbound-http.test.ts +1 -1
  39. package/src/__tests__/guardian-verification-voice-binding.test.ts +1 -3
  40. package/src/__tests__/hooks-blocking.test.ts +1 -1
  41. package/src/__tests__/hooks-config.test.ts +5 -29
  42. package/src/__tests__/hooks-discovery.test.ts +1 -1
  43. package/src/__tests__/hooks-integration.test.ts +1 -1
  44. package/src/__tests__/hooks-manager.test.ts +1 -1
  45. package/src/__tests__/hooks-runner.test.ts +1 -23
  46. package/src/__tests__/hooks-settings.test.ts +1 -1
  47. package/src/__tests__/hooks-templates.test.ts +1 -1
  48. package/src/__tests__/integration-status.test.ts +0 -1
  49. package/src/__tests__/invite-routes-http.test.ts +0 -3
  50. package/src/__tests__/list-messages-attachments.test.ts +4 -4
  51. package/src/__tests__/llm-usage-store.test.ts +50 -0
  52. package/src/__tests__/managed-proxy-context.test.ts +41 -41
  53. package/src/__tests__/media-generate-image.test.ts +2 -2
  54. package/src/__tests__/media-reuse-story.e2e.test.ts +1 -6
  55. package/src/__tests__/memory-regressions.experimental.test.ts +4 -4
  56. package/src/__tests__/memory-regressions.test.ts +27 -27
  57. package/src/__tests__/memory-retrieval.benchmark.test.ts +1 -1
  58. package/src/__tests__/memory-upsert-concurrency.test.ts +4 -4
  59. package/src/__tests__/notification-decision-fallback.test.ts +1 -1
  60. package/src/__tests__/oauth-cli.test.ts +1 -4
  61. package/src/__tests__/oauth-store.test.ts +1 -3
  62. package/src/__tests__/openai-provider.test.ts +7 -7
  63. package/src/__tests__/platform.test.ts +14 -4
  64. package/src/__tests__/pricing.test.ts +0 -223
  65. package/src/__tests__/provider-commit-message-generator.test.ts +1 -4
  66. package/src/__tests__/provider-fail-open-selection.test.ts +58 -54
  67. package/src/__tests__/provider-managed-proxy-integration.test.ts +63 -63
  68. package/src/__tests__/provider-registry-ollama.test.ts +3 -3
  69. package/src/__tests__/public-ingress-urls.test.ts +1 -1
  70. package/src/__tests__/registry.test.ts +3 -103
  71. package/src/__tests__/script-proxy-injection-runtime.test.ts +2 -7
  72. package/src/__tests__/secret-onetime-send.test.ts +1 -6
  73. package/src/__tests__/secret-routes-managed-proxy.test.ts +6 -13
  74. package/src/__tests__/secure-keys.test.ts +241 -229
  75. package/src/__tests__/session-abort-tool-results.test.ts +0 -1
  76. package/src/__tests__/session-confirmation-signals.test.ts +0 -1
  77. package/src/__tests__/session-messaging-secret-redirect.test.ts +1 -7
  78. package/src/__tests__/session-pre-run-repair.test.ts +0 -1
  79. package/src/__tests__/session-provider-retry-repair.test.ts +0 -1
  80. package/src/__tests__/session-queue.test.ts +2 -4
  81. package/src/__tests__/session-slash-known.test.ts +0 -1
  82. package/src/__tests__/session-slash-queue.test.ts +0 -1
  83. package/src/__tests__/session-slash-unknown.test.ts +0 -1
  84. package/src/__tests__/session-workspace-injection.test.ts +0 -1
  85. package/src/__tests__/session-workspace-tool-tracking.test.ts +0 -1
  86. package/src/__tests__/skill-projection-feature-flag.test.ts +0 -1
  87. package/src/__tests__/slack-channel-config.test.ts +1 -7
  88. package/src/__tests__/swarm-recursion.test.ts +0 -1
  89. package/src/__tests__/swarm-session-integration.test.ts +0 -1
  90. package/src/__tests__/swarm-tool.test.ts +0 -1
  91. package/src/__tests__/task-compiler.test.ts +1 -1
  92. package/src/__tests__/test-support/browser-skill-harness.ts +0 -18
  93. package/src/__tests__/test-support/computer-use-skill-harness.ts +0 -23
  94. package/src/__tests__/tool-executor.test.ts +1 -1
  95. package/src/__tests__/trust-store.test.ts +3 -82
  96. package/src/__tests__/twilio-config.test.ts +0 -1
  97. package/src/__tests__/twilio-provider.test.ts +0 -5
  98. package/src/__tests__/twilio-routes.test.ts +0 -1
  99. package/src/__tests__/usage-cache-backfill-migration.test.ts +10 -10
  100. package/src/calls/guardian-question-copy.ts +1 -1
  101. package/src/cli/commands/bash.ts +3 -0
  102. package/src/cli/commands/doctor.ts +10 -34
  103. package/src/cli/commands/memory.ts +3 -5
  104. package/src/cli/commands/sessions.ts +1 -1
  105. package/src/cli/commands/usage.ts +359 -0
  106. package/src/cli/http-client.ts +22 -12
  107. package/src/cli/program.ts +2 -0
  108. package/src/cli/reference.ts +1 -0
  109. package/src/cli.ts +251 -181
  110. package/src/config/assistant-feature-flags.ts +0 -7
  111. package/src/config/bundled-skills/chatgpt-import/tools/chatgpt-import.ts +1 -1
  112. package/src/config/bundled-skills/claude-code/SKILL.md +1 -1
  113. package/src/config/bundled-skills/claude-code/TOOLS.json +1 -1
  114. package/src/config/bundled-skills/gmail/SKILL.md +0 -1
  115. package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +2 -2
  116. package/src/config/bundled-skills/media-processing/services/reduce.ts +1 -1
  117. package/src/config/bundled-skills/messaging/SKILL.md +0 -1
  118. package/src/config/bundled-skills/sequences/SKILL.md +0 -1
  119. package/src/config/env.ts +13 -0
  120. package/src/config/feature-flag-registry.json +9 -41
  121. package/src/config/schemas/security.ts +1 -2
  122. package/src/config/skills.ts +1 -1
  123. package/src/contacts/contact-store.ts +0 -50
  124. package/src/daemon/approved-devices-store.ts +0 -44
  125. package/src/daemon/classifier.ts +1 -1
  126. package/src/daemon/config-watcher.ts +14 -8
  127. package/src/daemon/handlers/config-model.ts +1 -1
  128. package/src/daemon/handlers/sessions.ts +4 -116
  129. package/src/daemon/handlers/skills.ts +1 -1
  130. package/src/daemon/lifecycle.ts +13 -15
  131. package/src/daemon/providers-setup.ts +1 -1
  132. package/src/daemon/server.ts +20 -3
  133. package/src/daemon/session-slash.ts +2 -2
  134. package/src/daemon/shutdown-handlers.ts +15 -0
  135. package/src/daemon/watch-handler.ts +2 -2
  136. package/src/email/guardrails.ts +1 -1
  137. package/src/email/service.ts +0 -5
  138. package/src/hooks/templates.ts +1 -1
  139. package/src/media/app-icon-generator.ts +2 -2
  140. package/src/media/avatar-router.ts +2 -2
  141. package/src/media/gemini-image-service.ts +5 -5
  142. package/src/memory/admin.ts +2 -2
  143. package/src/memory/app-git-service.ts +0 -7
  144. package/src/memory/conversation-crud.ts +1 -1
  145. package/src/memory/conversation-title-service.ts +2 -2
  146. package/src/memory/embedding-backend.ts +30 -26
  147. package/src/memory/external-conversation-store.ts +0 -30
  148. package/src/memory/guardian-action-store.ts +0 -31
  149. package/src/memory/guardian-approvals.ts +1 -56
  150. package/src/memory/indexer.ts +4 -3
  151. package/src/memory/items-extractor.ts +1 -1
  152. package/src/memory/job-handlers/backfill.ts +5 -2
  153. package/src/memory/job-handlers/index-maintenance.ts +2 -2
  154. package/src/memory/job-handlers/media-processing.ts +2 -2
  155. package/src/memory/job-handlers/summarization.ts +1 -1
  156. package/src/memory/job-utils.ts +1 -2
  157. package/src/memory/jobs-worker.ts +2 -2
  158. package/src/memory/llm-usage-store.ts +57 -11
  159. package/src/memory/media-store.ts +4 -535
  160. package/src/memory/migrations/032-guardian-delivery-conversation-index.ts +2 -2
  161. package/src/memory/migrations/110-channel-guardian.ts +0 -1
  162. package/src/memory/published-pages-store.ts +0 -83
  163. package/src/memory/qdrant-circuit-breaker.ts +0 -8
  164. package/src/memory/retriever.ts +1 -1
  165. package/src/memory/schema/calls.ts +0 -67
  166. package/src/memory/search/semantic.ts +1 -8
  167. package/src/memory/shared-app-links-store.ts +0 -15
  168. package/src/messaging/registry.ts +0 -5
  169. package/src/messaging/style-analyzer.ts +1 -1
  170. package/src/notifications/copy-composer.ts +5 -13
  171. package/src/notifications/decision-engine.ts +2 -2
  172. package/src/notifications/deliveries-store.ts +0 -39
  173. package/src/notifications/guardian-question-mode.ts +6 -10
  174. package/src/notifications/preference-extractor.ts +1 -1
  175. package/src/oauth/byo-connection.test.ts +29 -20
  176. package/src/oauth/provider-behaviors.ts +1 -1
  177. package/src/permissions/checker.ts +1 -1
  178. package/src/permissions/shell-identity.ts +0 -5
  179. package/src/permissions/trust-store.ts +0 -37
  180. package/src/prompts/system-prompt.ts +4 -4
  181. package/src/prompts/templates/SOUL.md +1 -1
  182. package/src/providers/managed-proxy/constants.ts +8 -10
  183. package/src/providers/managed-proxy/context.ts +14 -9
  184. package/src/providers/provider-send-message.ts +4 -52
  185. package/src/providers/registry.ts +16 -50
  186. package/src/runtime/actor-token-store.ts +0 -23
  187. package/src/runtime/auth/__tests__/guard-tests.test.ts +64 -0
  188. package/src/runtime/http-router.ts +5 -1
  189. package/src/runtime/http-server.ts +101 -4
  190. package/src/runtime/invite-instruction-generator.ts +25 -51
  191. package/src/runtime/invite-service.ts +0 -20
  192. package/src/runtime/routes/attachment-routes.ts +1 -1
  193. package/src/runtime/routes/brain-graph-routes.ts +1 -1
  194. package/src/runtime/routes/call-routes.ts +1 -1
  195. package/src/runtime/routes/conversation-routes.ts +32 -11
  196. package/src/runtime/routes/debug-routes.ts +1 -1
  197. package/src/runtime/routes/diagnostics-routes.ts +2 -2
  198. package/src/runtime/routes/documents-routes.ts +3 -3
  199. package/src/runtime/routes/global-search-routes.ts +1 -1
  200. package/src/runtime/routes/guardian-bootstrap-routes.ts +0 -20
  201. package/src/runtime/routes/guardian-refresh-routes.ts +0 -20
  202. package/src/runtime/routes/secret-routes.ts +4 -4
  203. package/src/runtime/routes/session-management-routes.ts +27 -0
  204. package/src/runtime/routes/trust-rules-routes.ts +1 -1
  205. package/src/security/credential-backend.ts +148 -0
  206. package/src/security/oauth2.ts +1 -1
  207. package/src/security/secret-allowlist.ts +1 -1
  208. package/src/security/secure-keys.ts +98 -160
  209. package/src/security/token-manager.ts +0 -7
  210. package/src/sequence/guardrails.ts +0 -4
  211. package/src/sequence/store.ts +1 -20
  212. package/src/sequence/types.ts +1 -36
  213. package/src/signals/bash.ts +33 -0
  214. package/src/signals/cancel.ts +69 -0
  215. package/src/signals/conversation-undo.ts +127 -0
  216. package/src/signals/trust-rule.ts +174 -0
  217. package/src/skills/clawhub.ts +5 -5
  218. package/src/skills/managed-store.ts +4 -4
  219. package/src/subagent/manager.ts +8 -1
  220. package/src/telemetry/usage-telemetry-reporter.test.ts +366 -0
  221. package/src/telemetry/usage-telemetry-reporter.ts +181 -0
  222. package/src/tools/claude-code/claude-code.ts +2 -2
  223. package/src/tools/credentials/vault.ts +8 -4
  224. package/src/tools/memory/handlers.test.ts +24 -26
  225. package/src/tools/memory/handlers.ts +1 -13
  226. package/src/tools/registry.ts +5 -100
  227. package/src/tools/terminal/parser.ts +34 -4
  228. package/src/tools/tool-manifest.ts +0 -10
  229. package/src/usage/actors.ts +0 -12
  230. package/src/util/canonicalize-identity.ts +0 -9
  231. package/src/util/errors.ts +0 -3
  232. package/src/util/platform.ts +24 -7
  233. package/src/util/pricing.ts +0 -38
  234. package/src/watcher/constants.ts +0 -7
  235. package/src/watcher/providers/linear.ts +1 -1
  236. package/src/work-items/work-item-store.ts +4 -4
  237. package/src/workspace/commit-message-provider.ts +1 -1
  238. package/src/workspace/git-service.ts +44 -1
  239. package/src/workspace/provider-commit-message-generator.ts +1 -1
  240. package/src/__tests__/fixtures/proxy-fixtures.ts +0 -147
  241. package/src/browser-extension-relay/client.ts +0 -155
  242. package/src/contacts/index.ts +0 -18
  243. package/src/daemon/tls-certs.ts +0 -270
  244. package/src/errors.ts +0 -41
  245. package/src/events/index.ts +0 -18
  246. package/src/followups/index.ts +0 -10
  247. package/src/playbooks/index.ts +0 -10
  248. package/src/runtime/auth/index.ts +0 -44
  249. package/src/tasks/candidate-store.ts +0 -95
  250. package/src/tools/browser/api-map.ts +0 -313
  251. package/src/tools/browser/auto-navigate.ts +0 -469
  252. package/src/tools/browser/headless-browser.ts +0 -590
  253. package/src/tools/browser/recording-store.ts +0 -75
  254. package/src/tools/computer-use/registry.ts +0 -21
  255. package/src/tools/tasks/index.ts +0 -27
@@ -152,7 +152,7 @@ sequenceDiagram
152
152
  Note over UI,API: Tool Execution Flow
153
153
  Tool->>TokenMgr: withValidToken("gmail", callback)
154
154
  TokenMgr->>Store: getConnectionByProvider("integration:google")
155
- TokenMgr->>Vault: getSecureKey("oauth_connection/{conn.id}/access_token")
155
+ TokenMgr->>Vault: getSecureKeyAsync("oauth_connection/{conn.id}/access_token")
156
156
  TokenMgr->>Store: check oauth_connections.expires_at
157
157
  alt Token expired
158
158
  TokenMgr->>Store: resolveRefreshConfig() → tokenUrl, clientId from provider/app rows
@@ -1,13 +1,15 @@
1
1
  # macOS Keychain Broker Architecture
2
2
 
3
3
  **Status:** Accepted
4
- **Last Updated:** 2026-03-05
4
+ **Last Updated:** 2026-03-14
5
5
  **Owners:** macOS client + assistant runtime
6
6
 
7
7
  ## Decision
8
8
 
9
9
  Embed the keychain broker in the macOS app process rather than running a standalone daemon. The app exposes SecItem keychain operations over a Unix domain socket to the assistant runtime and gateway. Debug builds skip the broker entirely (`#if !DEBUG` guard) so developers never see keychain authorization prompts during rebuilds.
10
10
 
11
+ Credential storage is abstracted behind a `CredentialBackend` interface with two implementations: a keychain backend (backed by the broker) and an encrypted file store backend. Each process resolves a single primary backend at startup and uses single-writer semantics -- all writes go to the resolved backend only, with no dual-writing.
12
+
11
13
  ## Problem Statement
12
14
 
13
15
  We want macOS to use Keychain as the primary secret store, but direct keychain access from the daemon process causes repeated authorization prompts. The prompts are especially problematic during development with ad-hoc signed builds, where every rebuild changes the signing identity and triggers a new keychain prompt.
@@ -20,6 +22,21 @@ Prior state:
20
22
 
21
23
  ## Architecture
22
24
 
25
+ ### CredentialBackend interface
26
+
27
+ All credential storage operations are routed through the `CredentialBackend` interface, which provides a uniform API for get, set, delete, and list operations. This abstraction decouples `secure-keys.ts` from any specific storage mechanism.
28
+
29
+ Two implementations exist:
30
+
31
+ | Backend | Backing store | When used |
32
+ | ---------------- | -------------------------------------------- | ----------------------------------------------------------------------- |
33
+ | Keychain backend | macOS Keychain via the broker UDS connection | Production app context (`VELLUM_DEV` is NOT `"1"`) and broker available |
34
+ | Encrypted store | `~/.vellum/protected/keys.enc` (AES-256-GCM) | `VELLUM_DEV=1`, broker unavailable, CLI-only, headless, CI |
35
+
36
+ The interface is intentionally minimal to make adding future backends trivial -- e.g., Linux secret service (libsecret/D-Bus), 1Password CLI, or cloud KMS. A new backend only needs to implement the `CredentialBackend` interface and be wired into the backend resolution logic.
37
+
38
+ ### Broker topology
39
+
23
40
  ```mermaid
24
41
  graph LR
25
42
  subgraph "macOS App Process"
@@ -29,11 +46,19 @@ graph LR
29
46
  SERVICE --> KC["macOS Keychain"]
30
47
  end
31
48
 
32
- RUNTIME["Assistant Runtime (Bun)"] -->|"UDS JSON"| SERVER
33
- GATEWAY["Gateway (Bun)"] -->|"UDS JSON<br/>(async)"| SERVER
49
+ subgraph "Backend Resolution"
50
+ RESOLVE{"resolveBackend()"}
51
+ KB["Keychain Backend"]
52
+ EB["Encrypted Store Backend"]
53
+ RESOLVE -->|"VELLUM_DEV!=1<br/>+ broker available"| KB
54
+ RESOLVE -->|"VELLUM_DEV=1<br/>OR broker unavailable"| EB
55
+ end
56
+
57
+ KB -->|"UDS JSON"| SERVER
58
+ RUNTIME["Assistant Runtime (Bun)"] --> RESOLVE
59
+ GATEWAY["Gateway (Bun)"] -->|"read-only"| RESOLVE
34
60
 
35
- RUNTIME -.->|"fallback<br/>(sync + broker unavailable)"| ENC["Encrypted Store<br/>(~/.vellum/protected/keys.enc)"]
36
- GATEWAY -.->|"fallback<br/>(broker unavailable)"| ENC
61
+ EB --> ENC["Encrypted Store<br/>(~/.vellum/protected/keys.enc)"]
37
62
  ```
38
63
 
39
64
  ### Key properties
@@ -42,7 +67,8 @@ graph LR
42
67
  - **No auth bootstrap problem.** The app writes the broker auth token to disk before launching the daemon, so the daemon always has a valid token at startup.
43
68
  - **No keychain prompts on signed builds.** Items are stored with `kSecAttrAccessibleAfterFirstUnlock` under the `vellum-assistant` service name. A stable code-signing identity means macOS grants access without prompting after the first unlock.
44
69
  - **No keychain interaction on debug builds.** The entire `KeychainBrokerServer` is compiled out with `#if !DEBUG`, so development builds use the encrypted file store exclusively.
45
- - **Encrypted file store as permanent fallback.** CLI-only, headless, and development environments always have the encrypted store (`~/.vellum/protected/keys.enc`) available. Async callers that use the broker also write through to the encrypted store so sync callers see a consistent view.
70
+ - **Single-writer to resolved backend.** Each process resolves exactly one primary backend at startup. Writes go only to that backend. There is no dual-writing.
71
+ - **Encrypted file store as permanent fallback.** CLI-only, headless, and development environments always have the encrypted store (`~/.vellum/protected/keys.enc`) available.
46
72
 
47
73
  ## Components
48
74
 
@@ -55,11 +81,12 @@ graph LR
55
81
 
56
82
  ### TypeScript side (runtime + gateway)
57
83
 
58
- | File | Role |
59
- | -------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
60
- | `assistant/src/security/keychain-broker-client.ts` | Async UDS client for the runtime. Persistent socket connection, request/response correlation, auth token caching with auto-refresh on `UNAUTHORIZED`. Falls back gracefully (returns safe defaults, never throws). |
61
- | `assistant/src/security/secure-keys.ts` | Unified API surface. Sync variants use encrypted store only. Async variants (`getSecureKeyAsync`, `setSecureKeyAsync`, `deleteSecureKeyAsync`) check the encrypted store first for reads (instant), falling back to the broker for keys that may exist only in the macOS Keychain. **Writes** go to both stores; return `false` on broker failure (no encrypted-store fallback). **Deletes** return `"deleted"`, `"not-found"`, or `"error"` to let callers distinguish idempotent no-ops from real failures. |
62
- | `gateway/src/credential-reader.ts` | Read-only credential reader. Tries broker via native async UDS connection (`node:net`), falls back to encrypted store. All public credential read functions are async. |
84
+ | File | Role |
85
+ | -------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
86
+ | `assistant/src/security/credential-backend.ts` | `CredentialBackend` interface definition (`get`, `set`, `delete`, `list`) plus both adapter implementations: `KeychainBackend` (backed by the broker UDS client) and `EncryptedStoreBackend` (backed by `encrypted-store.ts` / `~/.vellum/protected/keys.enc`). Also exports factory functions `createKeychainBackend()` and `createEncryptedStoreBackend()`. |
87
+ | `assistant/src/security/keychain-broker-client.ts` | Async UDS client for the runtime. Persistent socket connection, request/response correlation, auth token caching with auto-refresh on `UNAUTHORIZED`. Falls back gracefully (returns safe defaults, never throws). |
88
+ | `assistant/src/security/secure-keys.ts` | Unified API surface. Resolves a single primary `CredentialBackend` at startup based on environment and broker availability. All writes go to the resolved backend only (single-writer). Reads check the primary backend first, with legacy fallback to the encrypted store for migration. Deletes clean up both stores to prevent stale keys. |
89
+ | `gateway/src/credential-reader.ts` | Read-only credential reader. Tries broker via native async UDS connection (`node:net`), falls back to encrypted store. All public credential read functions are async. |
63
90
 
64
91
  ## Message Contract
65
92
 
@@ -161,39 +188,77 @@ XPC provides stronger caller identity guarantees via audit tokens and code requi
161
188
  - **Release builds:** The broker starts automatically with the app. The daemon discovers the broker via the derived socket path (`join(getRootDir(), "keychain-broker.sock")`) and token file. No configuration needed.
162
189
  - **CLI-only / headless:** No macOS app means no broker socket. All storage uses the encrypted file store. This is the expected path for CI, servers, and non-macOS platforms.
163
190
 
164
- ## Callsite Policy
191
+ ## Store-Routing Policy
165
192
 
166
- ### Async-first policy
193
+ ### Backend resolution
167
194
 
168
- **All credential access should use the async functions** (`getSecureKeyAsync`, `setSecureKeyAsync`, `deleteSecureKeyAsync`). The async variants are the primary API: they check the encrypted store first (instant) and fall back to the keychain broker, ensuring secrets stored in the macOS Keychain are always reachable. The sync variants (`getSecureKey`, `setSecureKey`, `deleteSecureKey`) are **deprecated** and bypass the keychain broker entirely.
195
+ At process startup, `secure-keys.ts` resolves a single primary `CredentialBackend` for the lifetime of the process:
169
196
 
170
- New code must not introduce sync secure-key calls. Existing sync call sites should be converted to async when their surrounding code paths support it.
197
+ | Condition | Resolved backend |
198
+ | ----------------------------------------------------- | ---------------- |
199
+ | `VELLUM_DEV` is NOT `"1"` **and** broker is available | Keychain backend |
200
+ | `VELLUM_DEV=1` **or** broker is unavailable | Encrypted store |
171
201
 
172
- ### Runtime request handlers (secret-routes, etc.)
202
+ Once resolved, the backend does not change during the process lifetime. There is no runtime switching between backends.
173
203
 
174
- All runtime HTTP handlers that write or delete secrets **must** use the async APIs (`setSecureKeyAsync`, `deleteSecureKeyAsync`). These are the primary entry points for macOS app flows and must go through the broker to reach keychain.
204
+ ### Read strategy
175
205
 
176
- ### Gateway (credential-reader)
206
+ Reads go to the **primary backend first**. If the primary backend is the keychain backend and the key is not found, a legacy fallback read is attempted against the encrypted store. This fallback handles the migration period where keys written before the single-writer policy may still exist only in the encrypted store. When the primary backend is the encrypted store, there is no fallback -- the encrypted store is the sole source of truth.
207
+
208
+ ### Write strategy
209
+
210
+ Writes go **only to the resolved primary backend**. There is no dual-writing. This eliminates the consistency problems that dual-writing introduced (partial failures leaving stores out of sync, unclear source of truth).
211
+
212
+ - Keychain backend resolved: writes go to the keychain via broker only.
213
+ - Encrypted store resolved: writes go to the encrypted file store only.
214
+
215
+ ### Delete strategy
177
216
 
178
- The gateway reads credentials via async `readCredential()` which tries the broker first (native async UDS), falling back to the encrypted store. The gateway never writes credentials that responsibility belongs to the assistant runtime.
217
+ Deletes **always clean up both stores** regardless of the resolved backend. This ensures that stale keys do not linger in the non-primary store, which could cause unexpected reads through the legacy fallback path.
179
218
 
180
- ### Known sync exceptions
219
+ ### `VELLUM_DEV` environment variable
181
220
 
182
- The migration from sync to async secure-key functions is complete for all call sites except provider initialization paths that require synchronous access. The following call sites still use the deprecated sync variants.
221
+ Setting `VELLUM_DEV=1` forces the encrypted store as the sole backend, bypassing the keychain broker entirely even when the broker socket is available. This serves two purposes:
183
222
 
184
- #### Provider initialization (must remain sync)
223
+ 1. **Development ergonomics.** Developers running the daemon outside the macOS app context (e.g., `bun run` directly) avoid broker connectivity issues and keychain prompt noise.
224
+ 2. **Deterministic testing.** Tests and CI environments get predictable encrypted-store-only behavior without depending on macOS Keychain infrastructure.
185
225
 
186
- These call sites run in synchronous initialization contexts where async I/O is not feasible:
226
+ When `VELLUM_DEV` is not set or is set to any value other than `"1"`, backend resolution proceeds normally (broker availability check).
187
227
 
188
- | File | Sync functions used | Reason |
189
- | -------------------------------------------------- | ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
190
- | `assistant/src/providers/managed-proxy/context.ts` | `getSecureKey` | Provider context initialization is synchronous. The managed proxy context must resolve credentials before the first request can be processed, and the initialization path does not support awaiting. |
191
- | `assistant/src/providers/registry.ts` | `getSecureKey` | Provider registry initialization is synchronous. API keys must be resolved at provider construction time, and the call chain from config watcher through to provider init is fully synchronous. |
228
+ ## Callsite Policy
229
+
230
+ ### Async-only policy
231
+
232
+ **All credential access uses the async functions** (`getSecureKeyAsync`, `setSecureKeyAsync`, `deleteSecureKeyAsync`). These route through the resolved `CredentialBackend`, with legacy fallback reads to the encrypted store when the keychain backend is primary. The migration from sync to async is complete: the sync variants (`getSecureKey`, `setSecureKey`, `deleteSecureKey`) have been deleted and no longer exist in the codebase.
233
+
234
+ ### Runtime request handlers (secret-routes, etc.)
235
+
236
+ All runtime HTTP handlers that write or delete secrets use the async APIs (`setSecureKeyAsync`, `deleteSecureKeyAsync`). These are the primary entry points for macOS app flows and route through the resolved backend.
237
+
238
+ ### Gateway (credential-reader)
192
239
 
193
- Any new sync usage requires explicit justification and should be documented here.
240
+ The gateway reads credentials via async `readCredential()` which tries the broker first (native async UDS), falling back to the encrypted store. The gateway never writes credentials -- that responsibility belongs to the assistant runtime.
241
+
242
+ ### Sync function removal
243
+
244
+ The sync secure-key functions (`getSecureKey`, `setSecureKey`, `deleteSecureKey`) have been deleted. All code uses the async variants (`getSecureKeyAsync`, `setSecureKeyAsync`, `deleteSecureKeyAsync`).
194
245
 
195
246
  ## Migration
196
247
 
197
- Existing encrypted store keys remain accessible async reads check the encrypted store **first** (instant), falling back to the broker for keys that may exist only in the macOS Keychain. Successful writes from async code paths go to both the broker (keychain) and the encrypted store, keeping both in sync. If a broker write or delete fails, the operation returns `false` without falling back to the encrypted store alone, preventing stale divergence. Callers must inspect the boolean return value and handle failures (typically by logging a warning). There is no one-time migration step required.
248
+ Existing encrypted store keys remain accessible through the legacy read fallback. When the keychain backend is the resolved primary, reads that miss in the keychain fall back to the encrypted store, so keys written before the single-writer policy are still reachable without a one-time migration step.
249
+
250
+ Over time, as keys are re-written through normal credential update flows, they will be written only to the resolved primary backend. The legacy fallback read path will eventually become a no-op as all keys migrate naturally to the keychain.
251
+
252
+ Deletes always clean up both stores, so stale keys in the non-primary store are removed as part of normal credential lifecycle operations.
198
253
 
199
254
  The old `keychain.ts` module (which called `/usr/bin/security` CLI directly) has been deleted. The old keychain-to-encrypted migration code has been removed. All keychain access now flows exclusively through the broker.
255
+
256
+ ## Extensibility
257
+
258
+ The `CredentialBackend` interface is the extension point for adding new storage backends. To add a new backend (e.g., Linux secret service via libsecret/D-Bus, 1Password CLI, cloud KMS):
259
+
260
+ 1. Implement the `CredentialBackend` interface in a new module.
261
+ 2. Wire the new backend into the resolution logic in `secure-keys.ts`.
262
+ 3. The rest of the codebase is unaffected -- all callers go through the existing async API surface.
263
+
264
+ No changes to callsites, the gateway, or the broker protocol are needed when adding a backend.
@@ -233,7 +233,7 @@ sequenceDiagram
233
233
  UI->>HTTP: secret_response {requestId, value, delivery: "store"}
234
234
  HTTP->>Prompter: resolve(value, "store")
235
235
  Prompter->>Vault: {value, delivery: "store"}
236
- Vault->>Keychain: setSecureKey("credential/svc/field", value)
236
+ Vault->>Keychain: setSecureKeyAsync("credential/svc/field", value)
237
237
  Vault->>Model: "Credential stored securely" (no value in output)
238
238
  else One-Time Send (if enabled)
239
239
  UI->>HTTP: secret_response {requestId, value, delivery: "transient_send"}
@@ -272,7 +272,7 @@ graph TB
272
272
  TOOL["Tool (e.g. browser_fill_credential)"] --> BROKER["CredentialBroker.use(service, field, tool, domain)"]
273
273
  BROKER --> POLICY{"Check policy:<br/>allowedTools + allowedDomains"}
274
274
  POLICY -->|denied| REJECT["PolicyDenied error"]
275
- POLICY -->|allowed| FETCH["getSecureKey(credential/svc/field)"]
275
+ POLICY -->|allowed| FETCH["getSecureKeyAsync(credential/svc/field)"]
276
276
  FETCH --> INJECT["Inject value into tool execution<br/>(never returned to model)"]
277
277
  ```
278
278
 
package/knip.json CHANGED
@@ -1,32 +1,10 @@
1
1
  {
2
- "entry": ["src/**/*.test.ts", "src/**/__tests__/**/*.ts", "scripts/**/*.ts"],
3
- "project": ["src/**/*.ts", "src/**/*.tsx", "scripts/**/*.ts"],
4
- "ignore": [
5
- "src/browser-extension-relay/client.ts",
6
- "src/contacts/index.ts",
7
- "src/daemon/main.ts",
8
- "src/daemon/tls-certs.ts",
9
- "src/errors.ts",
10
- "src/events/index.ts",
11
- "src/followups/index.ts",
12
- "src/playbooks/index.ts",
13
- "src/runtime/auth/index.ts",
14
- "src/tasks/candidate-store.ts",
15
- "src/tools/browser/api-map.ts",
16
- "src/tools/browser/auto-navigate.ts",
17
- "src/tools/browser/headless-browser.ts",
18
- "src/tools/browser/recording-store.ts",
19
- "src/tools/tasks/index.ts"
2
+ "entry": [
3
+ "src/**/*.test.ts",
4
+ "src/**/__tests__/**/*.ts",
5
+ "scripts/**/*.ts",
6
+ "src/daemon/main.ts"
20
7
  ],
21
- "ignoreDependencies": [
22
- "@hono/node-server",
23
- "@vellumai/cli",
24
- "esbuild",
25
- "hono",
26
- "ink",
27
- "preact",
28
- "quicktype-core",
29
- "tree-sitter-bash",
30
- "typescript-json-schema"
31
- ]
8
+ "project": ["src/**/*.ts", "src/**/*.tsx", "scripts/**/*.ts"],
9
+ "ignoreDependencies": ["@vellumai/cli"]
32
10
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vellumai/assistant",
3
- "version": "0.4.53",
3
+ "version": "0.4.55",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": "./src/index.ts"
@@ -29,7 +29,6 @@
29
29
  "@anthropic-ai/claude-agent-sdk": "^0.2.42",
30
30
  "@anthropic-ai/sdk": "^0.78.0",
31
31
  "@google/genai": "^1.40.0",
32
- "@hono/node-server": "^1.19.11",
33
32
  "@modelcontextprotocol/sdk": "^1.15.1",
34
33
  "@qdrant/js-client-rest": "^1.16.2",
35
34
  "@sentry/node": "^10.38.0",
@@ -39,9 +38,6 @@
39
38
  "croner": "^10.0.1",
40
39
  "dotenv": "^17.3.1",
41
40
  "drizzle-orm": "^0.38.4",
42
- "esbuild": "^0.24.0",
43
- "hono": "^4.12.5",
44
- "ink": "^6.7.0",
45
41
  "jszip": "^3.10.1",
46
42
  "minimatch": "^10.2.4",
47
43
  "openai": "^6.18.0",
@@ -49,7 +45,6 @@
49
45
  "pino-pretty": "^13.1.3",
50
46
  "playwright": "^1.58.2",
51
47
  "postgres": "^3.4.8",
52
- "preact": "^10.25.0",
53
48
  "qrcode": "^1.5.4",
54
49
  "react": "^19.2.4",
55
50
  "rrule": "^2.8.1",
@@ -76,9 +71,7 @@
76
71
  "fast-check": "^4.5.3",
77
72
  "knip": "^5.83.1",
78
73
  "prettier": "^3.8.1",
79
- "quicktype-core": "^23.2.6",
80
74
  "typescript": "^5.7.3",
81
- "typescript-eslint": "^8.54.0",
82
- "typescript-json-schema": "^0.67.1"
75
+ "typescript-eslint": "^8.54.0"
83
76
  }
84
77
  }
@@ -1206,7 +1206,7 @@ describe("AgentLoop", () => {
1206
1206
  // Dynamic tool resolver (resolveTools) tests
1207
1207
  // ---------------------------------------------------------------------------
1208
1208
 
1209
- // 25. Without resolveTools, static tools are used (backward compatible)
1209
+ // 25. Without resolveTools, static tools are used
1210
1210
  test("without resolveTools, static tools are passed to provider", async () => {
1211
1211
  const { provider, calls } = createMockProvider([textResponse("Hi")]);
1212
1212
  const loop = new AgentLoop(provider, "system", {}, dummyTools);
@@ -3,7 +3,6 @@ import { tmpdir } from "node:os";
3
3
  import { join } from "node:path";
4
4
  import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
5
5
 
6
- import { _resetAppGitState } from "../memory/app-git-service.js";
7
6
  import { _resetGitServiceRegistry } from "../workspace/git-service.js";
8
7
 
9
8
  // Mock getDataDir to use a temp directory
@@ -35,7 +34,6 @@ describe("App Git History", () => {
35
34
  );
36
35
  mkdirSync(join(testDataDir, "apps"), { recursive: true });
37
36
  _resetGitServiceRegistry();
38
- _resetAppGitState();
39
37
  });
40
38
 
41
39
  afterEach(() => {
@@ -4,10 +4,7 @@ import { tmpdir } from "node:os";
4
4
  import { join } from "node:path";
5
5
  import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
6
6
 
7
- import {
8
- _resetAppGitState,
9
- commitAppTurnChanges,
10
- } from "../memory/app-git-service.js";
7
+ import { commitAppTurnChanges } from "../memory/app-git-service.js";
11
8
  import { _resetGitServiceRegistry } from "../workspace/git-service.js";
12
9
 
13
10
  // Mock getDataDir to use a temp directory
@@ -36,7 +33,6 @@ describe("App Git Service", () => {
36
33
  );
37
34
  mkdirSync(join(testDataDir, "apps"), { recursive: true });
38
35
  _resetGitServiceRegistry();
39
- _resetAppGitState();
40
36
  });
41
37
 
42
38
  afterEach(() => {
@@ -128,7 +124,6 @@ describe("App Git Service", () => {
128
124
  });
129
125
 
130
126
  test("commitAppTurnChanges swallows errors gracefully", async () => {
131
- _resetAppGitState();
132
127
  // This should not throw
133
128
  await commitAppTurnChanges("test", 1);
134
129
  });
@@ -54,7 +54,6 @@ mock.module("../util/platform.js", () => ({
54
54
  }));
55
55
 
56
56
  mock.module("../memory/guardian-action-store.js", () => ({
57
- getPendingDeliveryByConversation: () => null,
58
57
  getGuardianActionRequest: () => null,
59
58
  resolveGuardianActionRequest: () => {},
60
59
  }));
@@ -34,7 +34,6 @@ mock.module("../config/loader.js", () => ({
34
34
  mock.module("../security/secure-keys.js", () => ({
35
35
  getSecureKeyAsync: async (name: string) =>
36
36
  name === "gemini" ? mockGeminiKey : null,
37
- getSecureKey: () => null,
38
37
  }));
39
38
 
40
39
  mock.module("../util/platform.js", () => ({
@@ -58,16 +58,11 @@ let mockGetSecureKey: ReturnType<typeof mock>;
58
58
  let mockGetCredentialMetadata: ReturnType<typeof mock>;
59
59
 
60
60
  mock.module("../security/secure-keys.js", () => ({
61
- getSecureKey: (...args: unknown[]) => mockGetSecureKey(...args),
62
61
  getSecureKeyAsync: async (...args: unknown[]) => mockGetSecureKey(...args),
63
- setSecureKey: () => true,
64
62
  setSecureKeyAsync: async () => true,
65
- deleteSecureKey: () => "deleted",
66
63
  deleteSecureKeyAsync: async () => "deleted",
67
- listSecureKeys: () => [],
68
- getBackendType: () => "encrypted",
64
+ listSecureKeysAsync: async () => [],
69
65
  _resetBackend: () => {},
70
- _setBackend: () => {},
71
66
  }));
72
67
 
73
68
  mock.module("../tools/credentials/metadata-store.js", () => ({
@@ -114,7 +114,6 @@ mock.module("../calls/twilio-provider.js", () => ({
114
114
  }));
115
115
 
116
116
  mock.module("../security/secure-keys.js", () => ({
117
- getSecureKey: () => null,
118
117
  getSecureKeyAsync: async () => null,
119
118
  }));
120
119
 
@@ -103,7 +103,6 @@ mock.module("../calls/twilio-config.js", () => ({
103
103
 
104
104
  // Mock secure keys
105
105
  mock.module("../security/secure-keys.js", () => ({
106
- getSecureKey: () => null,
107
106
  getSecureKeyAsync: async () => null,
108
107
  }));
109
108
 
@@ -1441,7 +1441,7 @@ describe("HTTP handler channel-aware guardian status", () => {
1441
1441
  expect(resp!.guardianDisplayName).toBe("Guardian Name");
1442
1442
  });
1443
1443
 
1444
- test("status action defaults channel to telegram when omitted (backward compat)", async () => {
1444
+ test("status action defaults channel to telegram when omitted", async () => {
1445
1445
  const { ctx, lastResponse } = createMockCtx();
1446
1446
  const msg: ChannelVerificationSessionRequest = {
1447
1447
  type: "channel_verification_session",
@@ -1457,7 +1457,7 @@ describe("HTTP handler channel-aware guardian status", () => {
1457
1457
  expect(resp!.assistantId).toBe("self");
1458
1458
  });
1459
1459
 
1460
- test("status action defaults assistantId to self when omitted (backward compat)", async () => {
1460
+ test("status action defaults assistantId to self when omitted", async () => {
1461
1461
  const { ctx, lastResponse } = createMockCtx();
1462
1462
  const msg: ChannelVerificationSessionRequest = {
1463
1463
  type: "channel_verification_session",
@@ -2282,9 +2282,9 @@ describe("outbound verification sessions", () => {
2282
2282
  expect(result2.success).toBe(false);
2283
2283
  });
2284
2284
 
2285
- // ── Backward compat: existing inbound-only flow still works ──
2285
+ // ── Inbound-only verification flow ──
2286
2286
 
2287
- test("backward compat: inbound-only challenge without expected identity still works", () => {
2287
+ test("inbound-only challenge without expected identity still works", () => {
2288
2288
  const { secret } = createInboundVerificationSession("telegram");
2289
2289
 
2290
2290
  const result = validateAndConsumeVerification(
@@ -48,7 +48,6 @@ mock.module("../config/loader.js", () => ({
48
48
  }));
49
49
 
50
50
  mock.module("../security/secure-keys.js", () => ({
51
- getSecureKey: (key: string) => mockSecureKeys[key] ?? null,
52
51
  getSecureKeyAsync: async (key: string) => mockSecureKeys[key] ?? null,
53
52
  }));
54
53
 
@@ -48,7 +48,6 @@ mock.module("../email/service.js", () => ({
48
48
  }));
49
49
 
50
50
  mock.module("../security/secure-keys.js", () => ({
51
- getSecureKey: (key: string) => mockSecureKeys[key] ?? null,
52
51
  getSecureKeyAsync: async (key: string) => mockSecureKeys[key] ?? null,
53
52
  }));
54
53
 
@@ -134,8 +134,10 @@ registerTool(mockBundledSkillTool);
134
134
 
135
135
  // Register CU tools so classifyRisk returns their declared Low risk level
136
136
  // instead of falling through to Medium (unknown tool).
137
- import { registerComputerUseActionTools } from "../tools/computer-use/registry.js";
138
- registerComputerUseActionTools();
137
+ import { allComputerUseTools } from "../tools/computer-use/definitions.js";
138
+ for (const tool of allComputerUseTools) {
139
+ registerTool(tool);
140
+ }
139
141
 
140
142
  function writeSkill(
141
143
  skillId: string,
@@ -1945,7 +1947,7 @@ describe("Permission Checker", () => {
1945
1947
  ).toHaveLength(0);
1946
1948
  });
1947
1949
 
1948
- test("returns directory options when toolName is omitted (backward compat)", () => {
1950
+ test("returns directory options when toolName is omitted", () => {
1949
1951
  const options = generateScopeOptions("/home/user/project");
1950
1952
  expect(options).toHaveLength(3);
1951
1953
  expect(options[0].scope).toBe("/home/user/project");
@@ -2119,11 +2121,11 @@ describe("Permission Checker", () => {
2119
2121
  });
2120
2122
  });
2121
2123
 
2122
- // ── backward compat: addRule basics (PR 2/40) ──
2124
+ // ── addRule basics ──
2123
2125
  // These tests verify that addRule() creates standard rules that
2124
2126
  // match by tool name, pattern glob, and scope prefix.
2125
2127
 
2126
- describe("backward compat: addRule basics (PR 2/40)", () => {
2128
+ describe("addRule basics", () => {
2127
2129
  test("rule matches by tool/pattern/scope", async () => {
2128
2130
  addRule("skill_test_tool", "skill_test_tool:*", "/tmp", "allow", 2000);
2129
2131
  const result = await check("skill_test_tool", {}, "/tmp");
@@ -2172,7 +2174,7 @@ describe("Permission Checker", () => {
2172
2174
  expect(v2Result.matchedRule?.id).toBe(v1Result.matchedRule?.id);
2173
2175
  });
2174
2176
 
2175
- test("findHighestPriorityRule works without policy context (backward compat)", () => {
2177
+ test("findHighestPriorityRule works without policy context", () => {
2176
2178
  // Calling findHighestPriorityRule without the optional 4th ctx
2177
2179
  // parameter still works — wildcard rules match any caller.
2178
2180
  addRule("skill_test_tool", "skill_test_tool:*", "/tmp", "allow", 2000);
@@ -2197,14 +2199,14 @@ describe("Permission Checker", () => {
2197
2199
  });
2198
2200
  });
2199
2201
 
2200
- // ── checker policy context backward compat (PR 17) ─────────────
2202
+ // ── optional policyContext parameter ─────────────
2201
2203
 
2202
- describe("checker policy context backward compat (PR 17)", () => {
2203
- test("check() without policyContext still works (backward compatible)", async () => {
2204
- addRule("bash", "echo backward-compat", "/tmp", "allow", 2000);
2204
+ describe("optional policyContext parameter", () => {
2205
+ test("check() without policyContext still works", async () => {
2206
+ addRule("bash", "echo test-optional-ctx", "/tmp", "allow", 2000);
2205
2207
  const result = await check(
2206
2208
  "bash",
2207
- { command: "echo backward-compat" },
2209
+ { command: "echo test-optional-ctx" },
2208
2210
  "/tmp",
2209
2211
  );
2210
2212
  expect(result.decision).toBe("allow");
@@ -35,7 +35,6 @@ mock.module("../config/loader.js", () => ({
35
35
  mock.module("../security/secure-keys.js", () => ({
36
36
  getSecureKeyAsync: async (name: string) =>
37
37
  name === "anthropic" ? "fake-anthropic-key" : null,
38
- getSecureKey: () => null,
39
38
  }));
40
39
 
41
40
  // ---------------------------------------------------------------------------
@@ -40,7 +40,6 @@ mock.module("../config/loader.js", () => ({
40
40
  mock.module("../security/secure-keys.js", () => ({
41
41
  getSecureKeyAsync: async (name: string) =>
42
42
  name === "anthropic" ? "fake-anthropic-key" : null,
43
- getSecureKey: () => null,
44
43
  }));
45
44
 
46
45
  import { claudeCodeTool } from "../tools/claude-code/claude-code.js";
@@ -88,7 +87,7 @@ describe("claude_code tool profile support", () => {
88
87
  }
89
88
  });
90
89
 
91
- test("omitted profile defaults to general (backward compat)", async () => {
90
+ test("omitted profile defaults to general", async () => {
92
91
  const result = await claudeCodeTool.execute(
93
92
  { prompt: "test" },
94
93
  makeContext(),
@@ -88,7 +88,6 @@ import {
88
88
  loadConfig,
89
89
  } from "../config/loader.js";
90
90
  import { _setStorePath } from "../security/encrypted-store.js";
91
- import { _setBackend } from "../security/secure-keys.js";
92
91
 
93
92
  // ---------------------------------------------------------------------------
94
93
  // Helpers
@@ -183,13 +182,11 @@ describe("config loader backfill", () => {
183
182
  }
184
183
  ensureTestDir();
185
184
  _setStorePath(join(TEST_DIR, "keys.enc"));
186
- _setBackend("encrypted");
187
185
  invalidateConfigCache();
188
186
  });
189
187
 
190
188
  afterEach(() => {
191
189
  _setStorePath(null);
192
- _setBackend(undefined);
193
190
  invalidateConfigCache();
194
191
  });
195
192