@vellumai/assistant 0.5.6 → 0.5.8

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 (442) hide show
  1. package/.env.example +16 -2
  2. package/ARCHITECTURE.md +6 -75
  3. package/Dockerfile +3 -2
  4. package/README.md +0 -2
  5. package/bun.lock +0 -414
  6. package/docker-entrypoint.sh +9 -0
  7. package/docs/architecture/keychain-broker.md +45 -240
  8. package/docs/architecture/memory.md +13 -11
  9. package/docs/architecture/security.md +0 -17
  10. package/docs/credential-execution-service.md +2 -2
  11. package/node_modules/@vellumai/ces-contracts/package.json +1 -0
  12. package/node_modules/@vellumai/ces-contracts/src/error.ts +1 -1
  13. package/node_modules/@vellumai/ces-contracts/src/grants.ts +1 -1
  14. package/node_modules/@vellumai/ces-contracts/src/handles.ts +1 -1
  15. package/node_modules/@vellumai/ces-contracts/src/index.ts +1 -1
  16. package/node_modules/@vellumai/ces-contracts/src/rpc.ts +120 -1
  17. package/node_modules/@vellumai/credential-storage/package.json +1 -0
  18. package/node_modules/@vellumai/egress-proxy/package.json +1 -0
  19. package/package.json +2 -3
  20. package/src/__tests__/actor-token-service.test.ts +0 -114
  21. package/src/__tests__/approval-cascade.test.ts +0 -1
  22. package/src/__tests__/assistant-feature-flags-integration.test.ts +30 -29
  23. package/src/__tests__/browser-fill-credential.test.ts +1 -1
  24. package/src/__tests__/browser-skill-endstate.test.ts +6 -5
  25. package/src/__tests__/btw-routes.test.ts +0 -39
  26. package/src/__tests__/call-controller.test.ts +0 -1
  27. package/src/__tests__/call-domain.test.ts +0 -128
  28. package/src/__tests__/ces-rpc-credential-backend.test.ts +199 -0
  29. package/src/__tests__/ces-startup-timeout.test.ts +40 -0
  30. package/src/__tests__/channel-approval-routes.test.ts +0 -5
  31. package/src/__tests__/channel-readiness-service.test.ts +1 -60
  32. package/src/__tests__/checker.test.ts +4 -2
  33. package/src/__tests__/cli-command-risk-guard.test.ts +112 -0
  34. package/src/__tests__/config-schema-cmd.test.ts +0 -2
  35. package/src/__tests__/config-schema.test.ts +3 -1
  36. package/src/__tests__/conversation-abort-tool-results.test.ts +0 -1
  37. package/src/__tests__/conversation-agent-loop-overflow.test.ts +0 -2
  38. package/src/__tests__/conversation-agent-loop.test.ts +2 -4
  39. package/src/__tests__/conversation-attention-telegram.test.ts +0 -5
  40. package/src/__tests__/conversation-confirmation-signals.test.ts +0 -1
  41. package/src/__tests__/conversation-error.test.ts +15 -1
  42. package/src/__tests__/conversation-init.benchmark.test.ts +0 -2
  43. package/src/__tests__/conversation-messaging-secret-redirect.test.ts +1 -1
  44. package/src/__tests__/conversation-pre-run-repair.test.ts +0 -1
  45. package/src/__tests__/conversation-provider-retry-repair.test.ts +0 -1
  46. package/src/__tests__/conversation-queue.test.ts +0 -1
  47. package/src/__tests__/conversation-skill-tools.test.ts +0 -54
  48. package/src/__tests__/conversation-slash-queue.test.ts +0 -1
  49. package/src/__tests__/conversation-slash-unknown.test.ts +0 -1
  50. package/src/__tests__/conversation-title-service.test.ts +87 -0
  51. package/src/__tests__/conversation-workspace-injection.test.ts +0 -1
  52. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +0 -1
  53. package/src/__tests__/credential-execution-client.test.ts +5 -2
  54. package/src/__tests__/credential-execution-feature-gates.test.ts +59 -30
  55. package/src/__tests__/credential-execution-managed-contract.test.ts +35 -20
  56. package/src/__tests__/credential-security-e2e.test.ts +1 -67
  57. package/src/__tests__/credential-security-invariants.test.ts +6 -50
  58. package/src/__tests__/credentials-cli.test.ts +82 -3
  59. package/src/__tests__/daemon-credential-client.test.ts +123 -0
  60. package/src/__tests__/db-migration-rollback.test.ts +2015 -1
  61. package/src/__tests__/deterministic-verification-control-plane.test.ts +1 -0
  62. package/src/__tests__/docker-signing-key-bootstrap.test.ts +34 -143
  63. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +6 -4
  64. package/src/__tests__/gateway-client-managed-outbound.test.ts +79 -1
  65. package/src/__tests__/guardian-routing-state.test.ts +0 -5
  66. package/src/__tests__/host-shell-tool.test.ts +6 -7
  67. package/src/__tests__/http-user-message-parity.test.ts +3 -103
  68. package/src/__tests__/inbound-invite-redemption.test.ts +0 -4
  69. package/src/__tests__/inline-skill-load-permissions.test.ts +6 -8
  70. package/src/__tests__/intent-routing.test.ts +0 -13
  71. package/src/__tests__/jobs-store-qdrant-breaker.test.ts +178 -0
  72. package/src/__tests__/journal-context.test.ts +335 -0
  73. package/src/__tests__/keychain-broker-client.test.ts +161 -22
  74. package/src/__tests__/memory-context-benchmark.benchmark.test.ts +0 -3
  75. package/src/__tests__/memory-jobs-worker-backoff.test.ts +150 -0
  76. package/src/__tests__/memory-lifecycle-e2e.test.ts +70 -25
  77. package/src/__tests__/memory-recall-quality.test.ts +48 -17
  78. package/src/__tests__/memory-regressions.test.ts +408 -363
  79. package/src/__tests__/memory-retrieval.benchmark.test.ts +0 -3
  80. package/src/__tests__/migration-export-http.test.ts +2 -2
  81. package/src/__tests__/migration-import-commit-http.test.ts +2 -2
  82. package/src/__tests__/migration-import-preflight-http.test.ts +2 -2
  83. package/src/__tests__/migration-validate-http.test.ts +2 -2
  84. package/src/__tests__/non-member-access-request.test.ts +2 -7
  85. package/src/__tests__/notification-decision-fallback.test.ts +4 -0
  86. package/src/__tests__/notification-decision-identity.test.ts +4 -0
  87. package/src/__tests__/notification-decision-strategy.test.ts +71 -0
  88. package/src/__tests__/oauth-cli.test.ts +5 -1
  89. package/src/__tests__/permission-types.test.ts +1 -0
  90. package/src/__tests__/provider-commit-message-generator.test.ts +0 -37
  91. package/src/__tests__/provider-error-scenarios.test.ts +0 -267
  92. package/src/__tests__/provider-managed-proxy-integration.test.ts +5 -6
  93. package/src/__tests__/provider-streaming.benchmark.test.ts +2 -81
  94. package/src/__tests__/qdrant-manager.test.ts +28 -2
  95. package/src/__tests__/registry.test.ts +0 -6
  96. package/src/__tests__/relay-server.test.ts +1 -2
  97. package/src/__tests__/runtime-attachment-metadata.test.ts +0 -4
  98. package/src/__tests__/script-proxy-injection-runtime.test.ts +1 -1
  99. package/src/__tests__/secret-onetime-send.test.ts +1 -1
  100. package/src/__tests__/secret-routes-managed-proxy.test.ts +0 -4
  101. package/src/__tests__/secure-keys.test.ts +95 -272
  102. package/src/__tests__/shell-identity.test.ts +96 -6
  103. package/src/__tests__/skill-feature-flags-integration.test.ts +22 -14
  104. package/src/__tests__/skill-feature-flags.test.ts +46 -45
  105. package/src/__tests__/skill-load-feature-flag.test.ts +7 -10
  106. package/src/__tests__/skill-load-inline-command.test.ts +8 -12
  107. package/src/__tests__/skill-load-inline-includes.test.ts +6 -10
  108. package/src/__tests__/skill-load-tool.test.ts +0 -2
  109. package/src/__tests__/skill-memory.test.ts +17 -3
  110. package/src/__tests__/skill-projection-feature-flag.test.ts +33 -29
  111. package/src/__tests__/skills.test.ts +0 -2
  112. package/src/__tests__/slack-inbound-verification.test.ts +0 -4
  113. package/src/__tests__/stale-approval-dedup.test.ts +171 -0
  114. package/src/__tests__/stt-hints.test.ts +437 -0
  115. package/src/__tests__/suggestion-routes.test.ts +1 -32
  116. package/src/__tests__/system-prompt.test.ts +0 -1
  117. package/src/__tests__/task-memory-cleanup.test.ts +14 -0
  118. package/src/__tests__/tool-executor-shell-integration.test.ts +5 -3
  119. package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +0 -5
  120. package/src/__tests__/trusted-contact-multichannel.test.ts +0 -4
  121. package/src/__tests__/twilio-routes-twiml.test.ts +139 -1
  122. package/src/__tests__/update-bulletin.test.ts +0 -2
  123. package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +6 -9
  124. package/src/__tests__/voice-quality.test.ts +58 -0
  125. package/src/__tests__/voice-scoped-grant-consumer.test.ts +0 -7
  126. package/src/__tests__/workspace-migration-015-migrate-credentials-to-keychain.test.ts +252 -0
  127. package/src/__tests__/workspace-migration-016-migrate-credentials-from-keychain.test.ts +220 -0
  128. package/src/__tests__/workspace-migration-down-functions.test.ts +1009 -0
  129. package/src/__tests__/workspace-migrations-runner.test.ts +114 -0
  130. package/src/acp/agent-process.ts +9 -1
  131. package/src/agent/loop.ts +1 -1
  132. package/src/approvals/guardian-request-resolvers.ts +164 -38
  133. package/src/calls/__tests__/tts-text-sanitizer.test.ts +254 -0
  134. package/src/calls/audio-store.test.ts +97 -0
  135. package/src/calls/audio-store.ts +205 -0
  136. package/src/calls/call-controller.ts +90 -8
  137. package/src/calls/call-domain.ts +3 -0
  138. package/src/calls/call-store.ts +10 -3
  139. package/src/calls/fish-audio-client.ts +129 -0
  140. package/src/calls/relay-server.ts +27 -0
  141. package/src/calls/stt-hints.ts +189 -0
  142. package/src/calls/tts-text-sanitizer.ts +61 -0
  143. package/src/calls/twilio-routes.ts +34 -5
  144. package/src/calls/types.ts +1 -0
  145. package/src/calls/voice-ingress-preflight.ts +0 -42
  146. package/src/calls/voice-quality.ts +38 -5
  147. package/src/calls/voice-session-bridge.ts +7 -12
  148. package/src/cli/commands/avatar.ts +2 -2
  149. package/src/cli/commands/config.ts +1 -4
  150. package/src/cli/commands/credentials.ts +128 -82
  151. package/src/cli/commands/doctor.ts +2 -2
  152. package/src/cli/commands/keys.ts +7 -7
  153. package/src/cli/commands/memory.ts +1 -1
  154. package/src/cli/commands/oauth/connections.ts +11 -29
  155. package/src/cli/commands/oauth/index.ts +7 -0
  156. package/src/cli/commands/oauth/platform.ts +525 -0
  157. package/src/cli/commands/platform.ts +3 -3
  158. package/src/cli/lib/daemon-credential-client.ts +284 -0
  159. package/src/cli.ts +1 -1
  160. package/src/config/assistant-feature-flags.ts +186 -5
  161. package/src/config/bundled-skills/AGENTS.md +34 -0
  162. package/src/config/bundled-skills/acp/SKILL.md +10 -0
  163. package/src/config/bundled-skills/app-builder/SKILL.md +0 -4
  164. package/src/config/bundled-skills/messaging/SKILL.md +5 -5
  165. package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +2 -2
  166. package/src/config/bundled-skills/phone-calls/TOOLS.json +4 -0
  167. package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +1 -0
  168. package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +1 -0
  169. package/src/config/bundled-skills/settings/SKILL.md +15 -2
  170. package/src/config/bundled-skills/settings/TOOLS.json +47 -2
  171. package/src/config/bundled-skills/settings/tools/avatar-remove.ts +59 -0
  172. package/src/config/bundled-skills/settings/tools/avatar-update.ts +80 -0
  173. package/src/config/bundled-skills/settings/tools/voice-config-update.ts +42 -0
  174. package/src/config/bundled-skills/slack/SKILL.md +1 -1
  175. package/src/config/bundled-tool-registry.ts +5 -11
  176. package/src/config/defaults.ts +0 -2
  177. package/src/config/env-registry.ts +5 -5
  178. package/src/config/env.ts +21 -14
  179. package/src/config/feature-flag-registry.json +49 -9
  180. package/src/config/loader.ts +106 -42
  181. package/src/config/schema.ts +9 -29
  182. package/src/config/schemas/calls.ts +30 -0
  183. package/src/config/schemas/fish-audio.ts +39 -0
  184. package/src/config/schemas/inference.ts +2 -2
  185. package/src/config/schemas/journal.ts +16 -0
  186. package/src/config/schemas/memory-processing.ts +2 -2
  187. package/src/config/schemas/security.ts +0 -4
  188. package/src/config/types.ts +1 -1
  189. package/src/contacts/contact-store.ts +39 -0
  190. package/src/contacts/types.ts +2 -0
  191. package/src/credential-execution/approval-bridge.ts +1 -0
  192. package/src/credential-execution/executable-discovery.ts +28 -4
  193. package/src/credential-execution/feature-gates.ts +16 -0
  194. package/src/credential-execution/process-manager.ts +38 -0
  195. package/src/credential-execution/startup-timeout.ts +36 -0
  196. package/src/daemon/approval-generators.ts +3 -9
  197. package/src/daemon/assistant-attachments.ts +9 -0
  198. package/src/daemon/config-watcher.ts +5 -0
  199. package/src/daemon/conversation-error.ts +13 -1
  200. package/src/daemon/conversation-memory.ts +1 -2
  201. package/src/daemon/conversation-process.ts +18 -1
  202. package/src/daemon/conversation-surfaces.ts +30 -1
  203. package/src/daemon/conversation-tool-setup.ts +0 -105
  204. package/src/daemon/conversation.ts +21 -1
  205. package/src/daemon/guardian-action-generators.ts +3 -9
  206. package/src/daemon/handlers/config-vercel.ts +92 -0
  207. package/src/daemon/handlers/skills.ts +2 -15
  208. package/src/daemon/install-symlink.ts +195 -0
  209. package/src/daemon/lifecycle.ts +234 -51
  210. package/src/daemon/message-types/conversations.ts +4 -4
  211. package/src/daemon/message-types/diagnostics.ts +3 -22
  212. package/src/daemon/message-types/messages.ts +0 -2
  213. package/src/daemon/message-types/upgrades.ts +8 -0
  214. package/src/daemon/server.ts +32 -95
  215. package/src/events/domain-events.ts +2 -1
  216. package/src/inbound/platform-callback-registration.ts +3 -3
  217. package/src/instrument.ts +8 -5
  218. package/src/memory/app-store.ts +31 -0
  219. package/src/memory/conversation-title-service.ts +50 -1
  220. package/src/memory/db-init.ts +16 -0
  221. package/src/memory/indexer.ts +19 -10
  222. package/src/memory/items-extractor.ts +328 -321
  223. package/src/memory/job-handlers/conversation-starters.ts +4 -1
  224. package/src/memory/job-handlers/summarization.ts +26 -16
  225. package/src/memory/jobs-store.ts +63 -6
  226. package/src/memory/jobs-worker.ts +31 -7
  227. package/src/memory/journal-memory.ts +214 -0
  228. package/src/memory/migrations/001-job-deferrals.ts +19 -0
  229. package/src/memory/migrations/004-entity-relation-dedup.ts +10 -0
  230. package/src/memory/migrations/005-fingerprint-scope-unique.ts +76 -0
  231. package/src/memory/migrations/006-scope-salted-fingerprints.ts +50 -0
  232. package/src/memory/migrations/007-assistant-id-to-self.ts +10 -0
  233. package/src/memory/migrations/008-remove-assistant-id-columns.ts +34 -0
  234. package/src/memory/migrations/009-llm-usage-events-drop-assistant-id.ts +26 -0
  235. package/src/memory/migrations/014-backfill-inbox-thread-state.ts +10 -0
  236. package/src/memory/migrations/015-drop-active-search-index.ts +17 -0
  237. package/src/memory/migrations/019-notification-tables-schema-migration.ts +12 -0
  238. package/src/memory/migrations/020-rename-macos-ios-channel-to-vellum.ts +121 -0
  239. package/src/memory/migrations/024-embedding-vector-blob.ts +74 -0
  240. package/src/memory/migrations/026a-embeddings-nullable-vector-json.ts +82 -0
  241. package/src/memory/migrations/036-normalize-phone-identities.ts +11 -0
  242. package/src/memory/migrations/116-messages-fts.ts +106 -1
  243. package/src/memory/migrations/126-backfill-guardian-principal-id.ts +52 -0
  244. package/src/memory/migrations/127-guardian-principal-id-not-null.ts +77 -0
  245. package/src/memory/migrations/134-contacts-notes-column.ts +13 -0
  246. package/src/memory/migrations/135-backfill-contact-interaction-stats.ts +20 -0
  247. package/src/memory/migrations/136-drop-assistant-id-columns.ts +52 -0
  248. package/src/memory/migrations/140-backfill-usage-cache-accounting.ts +13 -0
  249. package/src/memory/migrations/141-rename-verification-table.ts +54 -0
  250. package/src/memory/migrations/142-rename-verification-session-id-column.ts +25 -0
  251. package/src/memory/migrations/143-rename-guardian-verification-values.ts +35 -0
  252. package/src/memory/migrations/144-rename-voice-to-phone.ts +136 -0
  253. package/src/memory/migrations/145-drop-accounts-table.ts +32 -0
  254. package/src/memory/migrations/147-migrate-reminders-to-schedules.ts +14 -1
  255. package/src/memory/migrations/148-drop-reminders-table.ts +35 -1
  256. package/src/memory/migrations/150-oauth-apps-client-secret-path.ts +69 -1
  257. package/src/memory/migrations/162-guardian-timestamps-epoch-ms.ts +290 -0
  258. package/src/memory/migrations/169-rename-gmail-provider-key-to-google.ts +51 -1
  259. package/src/memory/migrations/174-rename-thread-starters-table.ts +47 -1
  260. package/src/memory/migrations/176-drop-capability-card-state.ts +13 -0
  261. package/src/memory/migrations/180-backfill-inline-attachments-to-disk.ts +16 -0
  262. package/src/memory/migrations/181-rename-thread-starters-checkpoints.ts +28 -1
  263. package/src/memory/migrations/190-call-session-skip-disclosure.ts +15 -0
  264. package/src/memory/migrations/191-backfill-audio-attachment-mime-types.ts +64 -0
  265. package/src/memory/migrations/192-contacts-user-file-column.ts +15 -0
  266. package/src/memory/migrations/193-add-source-type-columns.ts +81 -0
  267. package/src/memory/migrations/index.ts +5 -0
  268. package/src/memory/migrations/registry.ts +98 -0
  269. package/src/memory/migrations/validate-migration-state.ts +137 -11
  270. package/src/memory/qdrant-circuit-breaker.ts +9 -0
  271. package/src/memory/qdrant-manager.ts +64 -7
  272. package/src/memory/retriever.test.ts +37 -25
  273. package/src/memory/retriever.ts +24 -49
  274. package/src/memory/schema/calls.ts +1 -0
  275. package/src/memory/schema/contacts.ts +1 -0
  276. package/src/memory/schema/memory-core.ts +2 -0
  277. package/src/memory/search/formatting.ts +7 -44
  278. package/src/memory/search/staleness.ts +4 -0
  279. package/src/memory/search/tier-classifier.ts +10 -2
  280. package/src/memory/search/types.ts +2 -5
  281. package/src/memory/task-memory-cleanup.ts +4 -3
  282. package/src/notifications/adapters/slack.ts +168 -6
  283. package/src/notifications/broadcaster.ts +1 -0
  284. package/src/notifications/copy-composer.ts +59 -2
  285. package/src/notifications/decision-engine.ts +4 -1
  286. package/src/notifications/signal.ts +2 -0
  287. package/src/notifications/types.ts +2 -0
  288. package/src/oauth/connection-resolver.ts +6 -4
  289. package/src/permissions/checker.ts +0 -38
  290. package/src/permissions/shell-identity.ts +76 -22
  291. package/src/permissions/types.ts +4 -2
  292. package/src/platform/client.ts +35 -7
  293. package/src/prompts/journal-context.ts +133 -0
  294. package/src/prompts/persona-resolver.ts +194 -0
  295. package/src/prompts/system-prompt.ts +44 -4
  296. package/src/prompts/templates/SOUL.md +10 -0
  297. package/src/prompts/templates/users/default.md +1 -0
  298. package/src/providers/provider-send-message.ts +3 -32
  299. package/src/providers/registry.ts +29 -179
  300. package/src/providers/types.ts +1 -1
  301. package/src/runtime/access-request-helper.ts +4 -0
  302. package/src/runtime/auth/__tests__/credential-service.test.ts +0 -1
  303. package/src/runtime/auth/__tests__/external-assistant-id.test.ts +13 -68
  304. package/src/runtime/auth/__tests__/guard-tests.test.ts +9 -50
  305. package/src/runtime/auth/external-assistant-id.ts +13 -59
  306. package/src/runtime/auth/route-policy.ts +17 -1
  307. package/src/runtime/auth/token-service.ts +43 -138
  308. package/src/runtime/channel-readiness-service.ts +1 -16
  309. package/src/runtime/gateway-client.ts +47 -4
  310. package/src/runtime/guardian-decision-types.ts +45 -4
  311. package/src/runtime/http-server.ts +31 -3
  312. package/src/runtime/middleware/error-handler.ts +1 -9
  313. package/src/runtime/routes/access-request-decision.ts +2 -2
  314. package/src/runtime/routes/app-management-routes.ts +2 -1
  315. package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +219 -30
  316. package/src/runtime/routes/approval-strategies/guardian-text-engine-strategy.ts +37 -14
  317. package/src/runtime/routes/audio-routes.ts +40 -0
  318. package/src/runtime/routes/btw-routes.ts +0 -17
  319. package/src/runtime/routes/channel-readiness-routes.ts +9 -4
  320. package/src/runtime/routes/conversation-query-routes.ts +63 -1
  321. package/src/runtime/routes/conversation-routes.ts +4 -44
  322. package/src/runtime/routes/debug-routes.ts +12 -9
  323. package/src/runtime/routes/diagnostics-routes.ts +1 -477
  324. package/src/runtime/routes/guardian-approval-interception.ts +168 -11
  325. package/src/runtime/routes/guardian-approval-prompt.ts +6 -1
  326. package/src/runtime/routes/guardian-approval-reply-helpers.ts +103 -21
  327. package/src/runtime/routes/identity-routes.ts +19 -30
  328. package/src/runtime/routes/inbound-message-handler.ts +31 -1
  329. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +64 -5
  330. package/src/runtime/routes/inbound-stages/background-dispatch.ts +52 -40
  331. package/src/runtime/routes/inbound-stages/secret-ingress-check.ts +4 -33
  332. package/src/runtime/routes/inbound-stages/transcribe-audio.test.ts +1 -1
  333. package/src/runtime/routes/integrations/twilio.ts +52 -10
  334. package/src/runtime/routes/integrations/vercel.ts +89 -0
  335. package/src/runtime/routes/log-export-routes.ts +5 -0
  336. package/src/runtime/routes/memory-item-routes.test.ts +3 -3
  337. package/src/runtime/routes/memory-item-routes.ts +46 -14
  338. package/src/runtime/routes/migration-rollback-routes.ts +209 -0
  339. package/src/runtime/routes/migration-routes.ts +17 -1
  340. package/src/runtime/routes/notification-routes.ts +58 -0
  341. package/src/runtime/routes/schedule-routes.ts +65 -0
  342. package/src/runtime/routes/secret-routes.ts +141 -10
  343. package/src/runtime/routes/settings-routes.ts +41 -1
  344. package/src/runtime/routes/tts-routes.ts +96 -0
  345. package/src/runtime/routes/upgrade-broadcast-routes.ts +26 -2
  346. package/src/runtime/routes/workspace-commit-routes.ts +62 -0
  347. package/src/runtime/routes/workspace-routes.test.ts +22 -1
  348. package/src/runtime/routes/workspace-routes.ts +1 -1
  349. package/src/runtime/routes/workspace-utils.ts +86 -2
  350. package/src/security/ces-credential-client.ts +75 -29
  351. package/src/security/ces-rpc-credential-backend.ts +86 -0
  352. package/src/security/credential-backend.ts +22 -92
  353. package/src/security/keychain-broker-client.ts +10 -2
  354. package/src/security/secure-keys.ts +113 -115
  355. package/src/skills/catalog-install.ts +6 -32
  356. package/src/skills/skill-memory.ts +1 -0
  357. package/src/subagent/manager.ts +2 -5
  358. package/src/telemetry/usage-telemetry-reporter.ts +4 -2
  359. package/src/tools/acp/spawn.ts +78 -1
  360. package/src/tools/calls/call-start.ts +1 -0
  361. package/src/tools/credentials/vault.ts +5 -3
  362. package/src/tools/executor.ts +0 -4
  363. package/src/tools/memory/definitions.ts +3 -2
  364. package/src/tools/memory/handlers.ts +10 -7
  365. package/src/tools/network/script-proxy/session-manager.ts +19 -4
  366. package/src/tools/network/web-fetch.ts +3 -1
  367. package/src/tools/skills/execute.ts +1 -1
  368. package/src/tools/terminal/safe-env.ts +1 -0
  369. package/src/tools/types.ts +0 -8
  370. package/src/util/browser.ts +15 -0
  371. package/src/util/errors.ts +0 -12
  372. package/src/util/platform.ts +4 -51
  373. package/src/workspace/git-service.ts +5 -2
  374. package/src/workspace/migrations/001-avatar-rename.ts +15 -0
  375. package/src/workspace/migrations/003-seed-device-id.ts +17 -1
  376. package/src/workspace/migrations/004-extract-collect-usage-data.ts +33 -0
  377. package/src/workspace/migrations/005-add-send-diagnostics.ts +3 -0
  378. package/src/workspace/migrations/006-services-config.ts +49 -0
  379. package/src/workspace/migrations/007-web-search-provider-rename.ts +27 -0
  380. package/src/workspace/migrations/008-voice-timeout-and-max-steps.ts +3 -0
  381. package/src/workspace/migrations/009-backfill-conversation-disk-view.ts +4 -0
  382. package/src/workspace/migrations/010-app-dir-rename.ts +78 -0
  383. package/src/workspace/migrations/011-backfill-installation-id.ts +11 -0
  384. package/src/workspace/migrations/012-rename-conversation-disk-view-dirs.ts +44 -0
  385. package/src/workspace/migrations/013-repair-conversation-disk-view.ts +5 -0
  386. package/src/workspace/migrations/015-migrate-credentials-to-keychain.ts +153 -0
  387. package/src/workspace/migrations/016-extract-feature-flags-to-protected.ts +156 -0
  388. package/src/workspace/migrations/016-migrate-credentials-from-keychain.ts +150 -0
  389. package/src/workspace/migrations/017-seed-persona-dirs.ts +96 -0
  390. package/src/workspace/migrations/018-rekey-compound-credential-keys.ts +184 -0
  391. package/src/workspace/migrations/019-scope-journal-to-guardian.ts +103 -0
  392. package/src/workspace/migrations/migrate-to-workspace-volume.ts +27 -5
  393. package/src/workspace/migrations/registry.ts +12 -0
  394. package/src/workspace/migrations/runner.ts +106 -2
  395. package/src/workspace/migrations/types.ts +4 -0
  396. package/src/workspace/provider-commit-message-generator.ts +12 -21
  397. package/src/__tests__/claude-code-skill-regression.test.ts +0 -206
  398. package/src/__tests__/claude-code-tool-profiles.test.ts +0 -99
  399. package/src/__tests__/diagnostics-export.test.ts +0 -288
  400. package/src/__tests__/local-gateway-health.test.ts +0 -209
  401. package/src/__tests__/provider-fail-open-selection.test.ts +0 -271
  402. package/src/__tests__/provider-failover-actual-provider.test.ts +0 -66
  403. package/src/__tests__/secret-ingress-handler.test.ts +0 -120
  404. package/src/__tests__/swarm-conversation-integration.test.ts +0 -358
  405. package/src/__tests__/swarm-dag-pathological.test.ts +0 -547
  406. package/src/__tests__/swarm-orchestrator.test.ts +0 -463
  407. package/src/__tests__/swarm-plan-validator.test.ts +0 -384
  408. package/src/__tests__/swarm-recursion.test.ts +0 -197
  409. package/src/__tests__/swarm-router-planner.test.ts +0 -234
  410. package/src/__tests__/swarm-tool.test.ts +0 -185
  411. package/src/__tests__/swarm-worker-backend.test.ts +0 -144
  412. package/src/__tests__/swarm-worker-runner.test.ts +0 -288
  413. package/src/commands/__tests__/cc-command-registry.test.ts +0 -396
  414. package/src/commands/cc-command-registry.ts +0 -248
  415. package/src/config/bundled-skills/claude-code/SKILL.md +0 -53
  416. package/src/config/bundled-skills/claude-code/TOOLS.json +0 -47
  417. package/src/config/bundled-skills/claude-code/tools/claude-code.ts +0 -12
  418. package/src/config/bundled-skills/orchestration/SKILL.md +0 -33
  419. package/src/config/bundled-skills/orchestration/TOOLS.json +0 -35
  420. package/src/config/bundled-skills/orchestration/tools/swarm-delegate.ts +0 -12
  421. package/src/config/schemas/swarm.ts +0 -82
  422. package/src/logfire.ts +0 -135
  423. package/src/memory/search/lexical.ts +0 -48
  424. package/src/providers/failover.ts +0 -186
  425. package/src/runtime/local-gateway-health.ts +0 -275
  426. package/src/security/secret-ingress.ts +0 -68
  427. package/src/swarm/backend-claude-code.ts +0 -225
  428. package/src/swarm/checkpoint.ts +0 -137
  429. package/src/swarm/graph-utils.ts +0 -53
  430. package/src/swarm/index.ts +0 -55
  431. package/src/swarm/limits.ts +0 -66
  432. package/src/swarm/orchestrator.ts +0 -424
  433. package/src/swarm/plan-validator.ts +0 -117
  434. package/src/swarm/router-planner.ts +0 -162
  435. package/src/swarm/router-prompts.ts +0 -39
  436. package/src/swarm/synthesizer.ts +0 -81
  437. package/src/swarm/types.ts +0 -72
  438. package/src/swarm/worker-backend.ts +0 -131
  439. package/src/swarm/worker-prompts.ts +0 -80
  440. package/src/swarm/worker-runner.ts +0 -170
  441. package/src/tools/claude-code/claude-code.ts +0 -610
  442. package/src/tools/swarm/delegate.ts +0 -205
@@ -0,0 +1,199 @@
1
+ import { beforeEach, describe, expect, mock, test } from "bun:test";
2
+
3
+ import { CesRpcMethod } from "@vellumai/ces-contracts";
4
+
5
+ // ---------------------------------------------------------------------------
6
+ // Mock state
7
+ // ---------------------------------------------------------------------------
8
+
9
+ const callFn = mock(
10
+ async (_method: string, _request: unknown): Promise<unknown> => ({}),
11
+ );
12
+
13
+ const isReadyFn = mock((): boolean => true);
14
+
15
+ // ---------------------------------------------------------------------------
16
+ // Mock modules — before importing module under test
17
+ // ---------------------------------------------------------------------------
18
+
19
+ mock.module("../util/logger.js", () => ({
20
+ getLogger: () =>
21
+ new Proxy({} as Record<string, unknown>, {
22
+ get: () => () => {},
23
+ }),
24
+ }));
25
+
26
+ // Import after mocking
27
+ import type { CesClient } from "../credential-execution/client.js";
28
+ import { CesRpcCredentialBackend } from "../security/ces-rpc-credential-backend.js";
29
+
30
+ // ---------------------------------------------------------------------------
31
+ // Helpers
32
+ // ---------------------------------------------------------------------------
33
+
34
+ function createMockClient(): CesClient {
35
+ return {
36
+ handshake: mock(async () => ({ accepted: true })),
37
+ call: callFn as CesClient["call"],
38
+ updateAssistantApiKey: mock(async () => ({ updated: true })),
39
+ isReady: isReadyFn,
40
+ close: mock(() => {}),
41
+ };
42
+ }
43
+
44
+ // ---------------------------------------------------------------------------
45
+ // Tests
46
+ // ---------------------------------------------------------------------------
47
+
48
+ describe("CesRpcCredentialBackend", () => {
49
+ let client: CesClient;
50
+ let backend: CesRpcCredentialBackend;
51
+
52
+ beforeEach(() => {
53
+ callFn.mockClear();
54
+ isReadyFn.mockClear();
55
+
56
+ isReadyFn.mockReturnValue(true);
57
+
58
+ client = createMockClient();
59
+ backend = new CesRpcCredentialBackend(client);
60
+ });
61
+
62
+ test("has name 'ces-rpc'", () => {
63
+ expect(backend.name).toBe("ces-rpc");
64
+ });
65
+
66
+ // -------------------------------------------------------------------------
67
+ // isAvailable
68
+ // -------------------------------------------------------------------------
69
+
70
+ describe("isAvailable", () => {
71
+ test("returns true when client is ready", () => {
72
+ isReadyFn.mockReturnValue(true);
73
+ expect(backend.isAvailable()).toBe(true);
74
+ });
75
+
76
+ test("returns false when client is not ready", () => {
77
+ isReadyFn.mockReturnValue(false);
78
+ expect(backend.isAvailable()).toBe(false);
79
+ });
80
+ });
81
+
82
+ // -------------------------------------------------------------------------
83
+ // get
84
+ // -------------------------------------------------------------------------
85
+
86
+ describe("get", () => {
87
+ test("delegates to CesRpcMethod.GetCredential and returns value when found", async () => {
88
+ callFn.mockResolvedValue({ found: true, value: "my-secret" });
89
+
90
+ const result = await backend.get("test-account");
91
+
92
+ expect(callFn).toHaveBeenCalledWith(CesRpcMethod.GetCredential, {
93
+ account: "test-account",
94
+ });
95
+ expect(result).toEqual({ value: "my-secret", unreachable: false });
96
+ });
97
+
98
+ test("returns undefined value when credential not found", async () => {
99
+ callFn.mockResolvedValue({ found: false });
100
+
101
+ const result = await backend.get("missing-account");
102
+
103
+ expect(callFn).toHaveBeenCalledWith(CesRpcMethod.GetCredential, {
104
+ account: "missing-account",
105
+ });
106
+ expect(result).toEqual({ value: undefined, unreachable: false });
107
+ });
108
+
109
+ test("returns unreachable when RPC call throws", async () => {
110
+ callFn.mockRejectedValue(new Error("transport error"));
111
+
112
+ const result = await backend.get("broken-account");
113
+
114
+ expect(result).toEqual({ value: undefined, unreachable: true });
115
+ });
116
+ });
117
+
118
+ // -------------------------------------------------------------------------
119
+ // set
120
+ // -------------------------------------------------------------------------
121
+
122
+ describe("set", () => {
123
+ test("delegates to CesRpcMethod.SetCredential and returns true on success", async () => {
124
+ callFn.mockResolvedValue({ ok: true });
125
+
126
+ const result = await backend.set("test-account", "new-secret");
127
+
128
+ expect(callFn).toHaveBeenCalledWith(CesRpcMethod.SetCredential, {
129
+ account: "test-account",
130
+ value: "new-secret",
131
+ });
132
+ expect(result).toBe(true);
133
+ });
134
+
135
+ test("returns false when RPC call throws", async () => {
136
+ callFn.mockRejectedValue(new Error("transport error"));
137
+
138
+ const result = await backend.set("test-account", "new-secret");
139
+
140
+ expect(result).toBe(false);
141
+ });
142
+ });
143
+
144
+ // -------------------------------------------------------------------------
145
+ // delete
146
+ // -------------------------------------------------------------------------
147
+
148
+ describe("delete", () => {
149
+ test("delegates to CesRpcMethod.DeleteCredential and returns the result", async () => {
150
+ callFn.mockResolvedValue({ result: "deleted" });
151
+
152
+ const result = await backend.delete("test-account");
153
+
154
+ expect(callFn).toHaveBeenCalledWith(CesRpcMethod.DeleteCredential, {
155
+ account: "test-account",
156
+ });
157
+ expect(result).toBe("deleted");
158
+ });
159
+
160
+ test("returns not-found result from CES", async () => {
161
+ callFn.mockResolvedValue({ result: "not-found" });
162
+
163
+ const result = await backend.delete("nonexistent-account");
164
+
165
+ expect(result).toBe("not-found");
166
+ });
167
+
168
+ test("returns 'error' when RPC call throws", async () => {
169
+ callFn.mockRejectedValue(new Error("transport error"));
170
+
171
+ const result = await backend.delete("test-account");
172
+
173
+ expect(result).toBe("error");
174
+ });
175
+ });
176
+
177
+ // -------------------------------------------------------------------------
178
+ // list
179
+ // -------------------------------------------------------------------------
180
+
181
+ describe("list", () => {
182
+ test("delegates to CesRpcMethod.ListCredentials and returns accounts", async () => {
183
+ callFn.mockResolvedValue({ accounts: ["account-a", "account-b"] });
184
+
185
+ const result = await backend.list();
186
+
187
+ expect(callFn).toHaveBeenCalledWith(CesRpcMethod.ListCredentials, {});
188
+ expect(result).toEqual({ accounts: ["account-a", "account-b"], unreachable: false });
189
+ });
190
+
191
+ test("returns unreachable when RPC call throws", async () => {
192
+ callFn.mockRejectedValue(new Error("transport error"));
193
+
194
+ const result = await backend.list();
195
+
196
+ expect(result).toEqual({ accounts: [], unreachable: true });
197
+ });
198
+ });
199
+ });
@@ -0,0 +1,40 @@
1
+ import { describe, expect, mock, test } from "bun:test";
2
+
3
+ import type { CesClient } from "../credential-execution/client.js";
4
+ import {
5
+ awaitCesClientWithTimeout,
6
+ DEFAULT_CES_STARTUP_TIMEOUT_MS,
7
+ } from "../credential-execution/startup-timeout.js";
8
+
9
+ describe("awaitCesClientWithTimeout", () => {
10
+ test("clears the fallback timer when the CES client resolves first", async () => {
11
+ const onTimeout = mock(() => {});
12
+ const client = { isReady: () => true } as unknown as CesClient;
13
+
14
+ const result = await awaitCesClientWithTimeout(Promise.resolve(client), {
15
+ timeoutMs: 25,
16
+ onTimeout,
17
+ });
18
+
19
+ expect(result).toBe(client);
20
+
21
+ await new Promise((resolve) => setTimeout(resolve, 40));
22
+ expect(onTimeout).not.toHaveBeenCalled();
23
+ });
24
+
25
+ test("returns undefined and runs the fallback handler when the timeout wins", async () => {
26
+ const onTimeout = mock(() => {});
27
+
28
+ const result = await awaitCesClientWithTimeout(new Promise(() => {}), {
29
+ timeoutMs: 10,
30
+ onTimeout,
31
+ });
32
+
33
+ expect(result).toBeUndefined();
34
+ expect(onTimeout).toHaveBeenCalledTimes(1);
35
+ });
36
+
37
+ test("exports the daemon CES startup timeout constant", () => {
38
+ expect(DEFAULT_CES_STARTUP_TIMEOUT_MS).toBe(20_000);
39
+ });
40
+ });
@@ -38,11 +38,6 @@ mock.module("../util/logger.js", () => ({
38
38
  }),
39
39
  }));
40
40
 
41
- // Mock security check to always pass
42
- mock.module("../security/secret-ingress.js", () => ({
43
- checkIngressForSecrets: () => ({ blocked: false }),
44
- }));
45
-
46
41
  // Mock render to return the raw content as text
47
42
  mock.module("../daemon/handlers/shared.js", () => ({
48
43
  renderHistoryContent: (content: unknown) => ({
@@ -1,15 +1,9 @@
1
- import { beforeEach, describe, expect, mock, spyOn, test } from "bun:test";
1
+ import { beforeEach, describe, expect, mock, test } from "bun:test";
2
2
 
3
3
  let mockTwilioPhoneNumber: string | undefined;
4
4
  let mockRawConfig: Record<string, unknown> | undefined;
5
5
  let mockSecureKeys: Record<string, string>;
6
6
  let mockHasTwilioCredentials: boolean;
7
- let mockGatewayHealth = {
8
- target: "http://127.0.0.1:7830",
9
- healthy: true,
10
- localDeployment: true,
11
- error: undefined as string | undefined,
12
- };
13
7
 
14
8
  mock.module("../calls/twilio-rest.js", () => ({
15
9
  getPhoneNumberSid: async () => null,
@@ -57,14 +51,12 @@ mock.module("../runtime/channel-invite-transports/whatsapp.js", () => ({
57
51
  import type { ChannelId } from "../channels/types.js";
58
52
  import {
59
53
  ChannelReadinessService,
60
- createReadinessService,
61
54
  REMOTE_TTL_MS,
62
55
  } from "../runtime/channel-readiness-service.js";
63
56
  import type {
64
57
  ChannelProbe,
65
58
  ReadinessCheckResult,
66
59
  } from "../runtime/channel-readiness-types.js";
67
- import * as localGatewayHealth from "../runtime/local-gateway-health.js";
68
60
 
69
61
  // ── Test helpers ────────────────────────────────────────────────────────────
70
62
 
@@ -104,12 +96,6 @@ describe("ChannelReadinessService", () => {
104
96
  mockRawConfig = undefined;
105
97
  mockSecureKeys = {};
106
98
  mockHasTwilioCredentials = false;
107
- mockGatewayHealth = {
108
- target: "http://127.0.0.1:7830",
109
- healthy: true,
110
- localDeployment: true,
111
- error: undefined,
112
- };
113
99
  });
114
100
 
115
101
  test("local checks run on every call (no caching of local results)", async () => {
@@ -403,49 +389,4 @@ describe("ChannelReadinessService", () => {
403
389
 
404
390
  expect(probe.remoteCallCount).toBe(1);
405
391
  });
406
-
407
- test("voice readiness includes gateway_health when ingress is configured", async () => {
408
- mockHasTwilioCredentials = true;
409
- mockTwilioPhoneNumber = "+15550001111";
410
- mockRawConfig = {
411
- ingress: {
412
- enabled: true,
413
- publicBaseUrl: "https://voice.example.com",
414
- },
415
- };
416
- mockGatewayHealth = {
417
- target: "http://127.0.0.1:7830",
418
- healthy: false,
419
- localDeployment: true,
420
- error: "connect ECONNREFUSED 127.0.0.1:7830",
421
- };
422
-
423
- const probeLocalGatewayHealthSpy = spyOn(
424
- localGatewayHealth,
425
- "probeLocalGatewayHealth",
426
- ).mockImplementation(async () => ({
427
- ...mockGatewayHealth,
428
- }));
429
-
430
- let snapshot: Awaited<
431
- ReturnType<ChannelReadinessService["getReadiness"]>
432
- >[number];
433
- try {
434
- const readinessService = createReadinessService();
435
- [snapshot] = await readinessService.getReadiness("phone");
436
- } finally {
437
- probeLocalGatewayHealthSpy.mockRestore();
438
- }
439
-
440
- const gatewayHealthCheck = snapshot.localChecks.find(
441
- (check) => check.name === "gateway_health",
442
- );
443
- expect(gatewayHealthCheck).toBeDefined();
444
- expect(gatewayHealthCheck?.passed).toBe(false);
445
- expect(snapshot.reasons).toContainEqual({
446
- code: "gateway_health",
447
- text: "Local gateway is not serving requests at http://127.0.0.1:7830: connect ECONNREFUSED 127.0.0.1:7830",
448
- });
449
- expect(snapshot.ready).toBe(false);
450
- });
451
392
  });
@@ -1606,13 +1606,15 @@ describe("Permission Checker", () => {
1606
1606
  expect(options[0].description).toContain("compound");
1607
1607
  });
1608
1608
 
1609
- test("compound command via pipeline yields exact-only allowlist option", async () => {
1609
+ test("compound command via pipeline yields exact + action-key allowlist options", async () => {
1610
1610
  const options = await generateAllowlistOptions("bash", {
1611
1611
  command: "git log | grep fix",
1612
1612
  });
1613
- expect(options).toHaveLength(1);
1613
+ expect(options.length).toBeGreaterThanOrEqual(2);
1614
1614
  expect(options[0].description).toContain("compound");
1615
1615
  expect(options[0].pattern).toBe("git log | grep fix");
1616
+ // Pipeline action keys should be offered as broader options
1617
+ expect(options.some((o) => o.pattern.startsWith("action:"))).toBe(true);
1616
1618
  });
1617
1619
 
1618
1620
  test("compound command via && yields exact-only allowlist option", async () => {
@@ -0,0 +1,112 @@
1
+ // Guard test: assistant CLI commands must always classify as Low risk.
2
+ //
3
+ // The assistant uses its own CLI tools during normal operation. If these
4
+ // commands require user approval, it blocks autonomous assistant workflows.
5
+ // See #18982 / #18998 for the regression that motivated this guard.
6
+
7
+ import { mkdtempSync } from "node:fs";
8
+ import { tmpdir } from "node:os";
9
+ import { join } from "node:path";
10
+ import { describe, expect, mock, test } from "bun:test";
11
+
12
+ const guardTestDir = mkdtempSync(join(tmpdir(), "cli-risk-guard-test-"));
13
+
14
+ mock.module("../util/platform.js", () => ({
15
+ getRootDir: () => guardTestDir,
16
+ getDataDir: () => join(guardTestDir, "data"),
17
+ getWorkspaceSkillsDir: () => join(guardTestDir, "skills"),
18
+ isMacOS: () => process.platform === "darwin",
19
+ isLinux: () => process.platform === "linux",
20
+ isWindows: () => process.platform === "win32",
21
+ getPidPath: () => join(guardTestDir, "test.pid"),
22
+ getDbPath: () => join(guardTestDir, "test.db"),
23
+ getLogPath: () => join(guardTestDir, "test.log"),
24
+ ensureDataDir: () => {},
25
+ }));
26
+
27
+ mock.module("../util/logger.js", () => ({
28
+ getLogger: () =>
29
+ new Proxy({} as Record<string, unknown>, {
30
+ get: (_target: Record<string, unknown>, _prop: string) => {
31
+ return () => {};
32
+ },
33
+ }),
34
+ }));
35
+
36
+ mock.module("../config/loader.js", () => ({
37
+ getConfig: () => ({
38
+ permissions: { mode: "workspace" },
39
+ skills: { load: { extraDirs: [] } },
40
+ sandbox: { enabled: true },
41
+ }),
42
+ loadConfig: () => ({}),
43
+ invalidateConfigCache: () => {},
44
+ saveConfig: () => {},
45
+ loadRawConfig: () => ({}),
46
+ saveRawConfig: () => {},
47
+ getNestedValue: () => undefined,
48
+ setNestedValue: () => {},
49
+ }));
50
+
51
+ import { buildCliProgram } from "../cli/program.js";
52
+ import { classifyRisk } from "../permissions/checker.js";
53
+ import { RiskLevel } from "../permissions/types.js";
54
+
55
+ /**
56
+ * Assert that a command classifies as Low risk, with a descriptive failure
57
+ * message that guides developers toward the correct fix.
58
+ */
59
+ function expectLowRisk(command: string, actual: RiskLevel): void {
60
+ if (actual !== RiskLevel.Low) {
61
+ throw new Error(
62
+ `"${command}" classified as ${actual} instead of Low. ` +
63
+ `assistant CLI commands must always be Low risk — the assistant ` +
64
+ `uses its own CLI during normal operation. If you need risk ` +
65
+ `escalation for specific subcommands, add them to an allowlist ` +
66
+ `in this guard test with justification.`,
67
+ );
68
+ }
69
+ expect(actual).toBe(RiskLevel.Low);
70
+ }
71
+
72
+ // Dynamically extract subcommand names from the CLI program definition.
73
+ // This ensures new commands added to program.ts are automatically covered
74
+ // by this guard test without manual list maintenance.
75
+ const program = buildCliProgram();
76
+ const ASSISTANT_SUBCOMMANDS = program.commands.map((c) => c.name());
77
+
78
+ describe("CLI command risk guard: assistant commands", () => {
79
+ test("subcommand discovery found a reasonable number of commands", () => {
80
+ // Sanity check: if mocking breaks and no commands are registered,
81
+ // the risk guard would vacuously pass. Require a minimum count to
82
+ // catch that failure mode. Update this threshold when commands are
83
+ // removed (but it should only grow).
84
+ expect(ASSISTANT_SUBCOMMANDS.length).toBeGreaterThanOrEqual(20);
85
+ });
86
+
87
+ test("all assistant CLI subcommands classify as Low risk", async () => {
88
+ for (const subcommand of ASSISTANT_SUBCOMMANDS) {
89
+ const command = `assistant ${subcommand}`;
90
+ const risk = await classifyRisk("bash", { command });
91
+ expectLowRisk(command, risk);
92
+ }
93
+ });
94
+
95
+ test("bare assistant command classifies as Low risk", async () => {
96
+ const risk = await classifyRisk("bash", { command: "assistant" });
97
+ expectLowRisk("assistant", risk);
98
+ });
99
+
100
+ test("assistant with flags classifies as Low risk", async () => {
101
+ const flagCommands = [
102
+ "assistant --version",
103
+ "assistant --help",
104
+ "assistant doctor --verbose",
105
+ ];
106
+
107
+ for (const command of flagCommands) {
108
+ const risk = await classifyRisk("bash", { command });
109
+ expectLowRisk(command, risk);
110
+ }
111
+ });
112
+ });
@@ -88,7 +88,6 @@ mock.module("../config/loader.js", () => ({
88
88
  invalidateConfigCache: () => {},
89
89
  getNestedValue: () => undefined,
90
90
  setNestedValue: () => {},
91
- syncConfigToLockfile: () => {},
92
91
  }));
93
92
 
94
93
  import { Command } from "commander";
@@ -205,7 +204,6 @@ describe("z.toJSONSchema integration", () => {
205
204
  expect(properties).toBeDefined();
206
205
  // Check that top-level keys are present
207
206
  expect(properties.services).toBeDefined();
208
- expect(properties.providerOrder).toBeDefined();
209
207
  expect(properties.maxTokens).toBeDefined();
210
208
  expect(properties.calls).toBeDefined();
211
209
  expect(properties.memory).toBeDefined();
@@ -137,7 +137,6 @@ describe("AssistantConfigSchema", () => {
137
137
  action: "redact",
138
138
  entropyThreshold: 4.0,
139
139
  allowOneTimeSend: false,
140
- blockIngress: true,
141
140
  });
142
141
  expect(result.auditLog).toEqual({ retentionDays: 0 });
143
142
  });
@@ -638,6 +637,9 @@ describe("AssistantConfigSchema", () => {
638
637
  voice: {
639
638
  language: "en-US",
640
639
  transcriptionProvider: "Deepgram",
640
+ ttsProvider: "elevenlabs",
641
+ hints: [],
642
+ interruptSensitivity: "low",
641
643
  },
642
644
  callerIdentity: {
643
645
  allowPerCallOverride: true,
@@ -129,7 +129,6 @@ mock.module("../memory/retriever.js", () => ({
129
129
  injectedText: "",
130
130
 
131
131
  semanticHits: 0,
132
- recencyHits: 0,
133
132
  injectedTokens: 0,
134
133
  latencyMs: 0,
135
134
  }),
@@ -168,7 +168,6 @@ mock.module("../memory/retriever.js", () => ({
168
168
  injectedText: "",
169
169
 
170
170
  semanticHits: 0,
171
- recencyHits: 0,
172
171
  injectedTokens: 0,
173
172
  latencyMs: 0,
174
173
  }),
@@ -199,7 +198,6 @@ mock.module("../daemon/conversation-memory.js", () => ({
199
198
  injectedText: "",
200
199
 
201
200
  semanticHits: 0,
202
- recencyHits: 0,
203
201
  injectedTokens: 0,
204
202
  latencyMs: 0,
205
203
  tier1Count: 0,
@@ -158,7 +158,6 @@ mock.module("../memory/retriever.js", () => ({
158
158
  injectedText: "",
159
159
 
160
160
  semanticHits: 0,
161
- recencyHits: 0,
162
161
  injectedTokens: 0,
163
162
  latencyMs: 0,
164
163
  }),
@@ -189,7 +188,6 @@ mock.module("../daemon/conversation-memory.js", () => ({
189
188
  injectedText: "",
190
189
 
191
190
  semanticHits: 0,
192
- recencyHits: 0,
193
191
  injectedTokens: 0,
194
192
  latencyMs: 0,
195
193
  tier1Count: 0,
@@ -614,7 +612,7 @@ describe("session-agent-loop", () => {
614
612
  });
615
613
 
616
614
  describe("LLM request log persistence", () => {
617
- test("record request log prefers the actual provider from failover", async () => {
615
+ test("record request log captures the actual provider name", async () => {
618
616
  const events: ServerMessage[] = [];
619
617
  const rawRequest = {
620
618
  model: "gpt-4.1",
@@ -768,7 +766,7 @@ describe("session-agent-loop", () => {
768
766
  });
769
767
 
770
768
  describe("usage accounting", () => {
771
- test("records the actual provider for failover-served usage", async () => {
769
+ test("records the actual provider for usage accounting", async () => {
772
770
  const events: ServerMessage[] = [];
773
771
 
774
772
  const agentLoopRun: AgentLoopRun = async (messages, onEvent) => {
@@ -31,11 +31,6 @@ mock.module("../util/logger.js", () => ({
31
31
  truncateForLog: (value: string) => value,
32
32
  }));
33
33
 
34
- // Mock security check to always pass
35
- mock.module("../security/secret-ingress.js", () => ({
36
- checkIngressForSecrets: () => ({ blocked: false }),
37
- }));
38
-
39
34
  // Mock render to return the raw content as text
40
35
  mock.module("../daemon/handlers/shared.js", () => ({
41
36
  renderHistoryContent: (content: unknown) => ({
@@ -154,7 +154,6 @@ mock.module("../memory/retriever.js", () => ({
154
154
  injectedText: "",
155
155
 
156
156
  semanticHits: 0,
157
- recencyHits: 0,
158
157
  injectedTokens: 0,
159
158
  latencyMs: 0,
160
159
  }),
@@ -6,7 +6,7 @@ import {
6
6
  classifyConversationError,
7
7
  isUserCancellation,
8
8
  } from "../daemon/conversation-error.js";
9
- import { ProviderError } from "../util/errors.js";
9
+ import { ProviderError, ProviderNotConfiguredError } from "../util/errors.js";
10
10
 
11
11
  describe("isUserCancellation", () => {
12
12
  it("returns false for non-AbortError even when abort flag is set", () => {
@@ -278,6 +278,20 @@ describe("classifyConversationError", () => {
278
278
  });
279
279
  });
280
280
 
281
+ describe("provider not configured errors", () => {
282
+ it("classifies ProviderNotConfiguredError as PROVIDER_NOT_CONFIGURED", () => {
283
+ const err = new ProviderNotConfiguredError("anthropic", []);
284
+ const result = classifyConversationError(err, baseCtx);
285
+ expect(result.code).toBe("PROVIDER_NOT_CONFIGURED");
286
+ expect(result.userMessage).toBe(
287
+ "No API key configured for inference. Add one in Settings to start chatting.",
288
+ );
289
+ expect(result.retryable).toBe(true);
290
+ expect(result.errorCategory).toBe("provider_not_configured");
291
+ expect(result.debugDetails).toBeDefined();
292
+ });
293
+ });
294
+
281
295
  describe("streaming corruption errors", () => {
282
296
  const cases = [
283
297
  "Unexpected event order, got message_start before receiving message_stop",
@@ -111,10 +111,8 @@ mock.module("../util/platform.js", () => ({
111
111
  getTCPPort: () => 8765,
112
112
  isIOSPairingEnabled: () => false,
113
113
  isTCPEnabled: () => false,
114
- readLockfile: () => null,
115
114
  readPlatformToken: () => null,
116
115
  readSessionToken: () => null,
117
- writeLockfile: () => {},
118
116
  ensureDataDir: () => {},
119
117
  }));
120
118
 
@@ -28,7 +28,7 @@ mock.module("../security/secure-keys.js", () => ({
28
28
  setSecureKeyAsync: async (key?: string, value?: string) =>
29
29
  setSecureKeyMock(key, value),
30
30
  deleteSecureKeyAsync: async () => "deleted" as const,
31
- listSecureKeysAsync: async () => [],
31
+ listSecureKeysAsync: async () => ({ accounts: [], unreachable: false }),
32
32
  _resetBackend: () => {},
33
33
  }));
34
34
 
@@ -119,7 +119,6 @@ mock.module("../memory/retriever.js", () => ({
119
119
  injectedText: "",
120
120
 
121
121
  semanticHits: 0,
122
- recencyHits: 0,
123
122
  injectedTokens: 0,
124
123
  latencyMs: 0,
125
124
  }),
@@ -188,7 +188,6 @@ mock.module("../memory/retriever.js", () => ({
188
188
  injectedText: "",
189
189
 
190
190
  semanticHits: 0,
191
- recencyHits: 0,
192
191
  injectedTokens: 0,
193
192
  latencyMs: 0,
194
193
  }),
@@ -204,7 +204,6 @@ mock.module("../memory/retriever.js", () => ({
204
204
  injectedText: "",
205
205
 
206
206
  semanticHits: 0,
207
- recencyHits: 0,
208
207
  injectedTokens: 0,
209
208
  latencyMs: 0,
210
209
  }),