@vellumai/assistant 0.4.52 → 0.4.54

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (380) hide show
  1. package/ARCHITECTURE.md +2 -2
  2. package/bun.lock +62 -349
  3. package/docs/architecture/integrations.md +1 -1
  4. package/docs/architecture/keychain-broker.md +91 -40
  5. package/docs/architecture/memory.md +3 -3
  6. package/docs/architecture/security.md +2 -2
  7. package/knip.json +7 -29
  8. package/package.json +2 -9
  9. package/src/__tests__/agent-loop.test.ts +1 -1
  10. package/src/__tests__/app-git-history.test.ts +0 -2
  11. package/src/__tests__/app-git-service.test.ts +1 -6
  12. package/src/__tests__/approval-cascade.test.ts +3 -2
  13. package/src/__tests__/approval-routes-http.test.ts +0 -1
  14. package/src/__tests__/asset-materialize-tool.test.ts +0 -1
  15. package/src/__tests__/asset-search-tool.test.ts +0 -1
  16. package/src/__tests__/assistant-events-sse-hardening.test.ts +0 -1
  17. package/src/__tests__/attachments-store.test.ts +0 -1
  18. package/src/__tests__/avatar-e2e.test.ts +5 -1
  19. package/src/__tests__/browser-fill-credential.test.ts +4 -6
  20. package/src/__tests__/btw-routes.test.ts +39 -0
  21. package/src/__tests__/call-controller.test.ts +0 -1
  22. package/src/__tests__/call-domain.test.ts +1 -1
  23. package/src/__tests__/call-routes-http.test.ts +1 -3
  24. package/src/__tests__/canonical-guardian-store.test.ts +33 -2
  25. package/src/__tests__/channel-guardian.test.ts +4 -4
  26. package/src/__tests__/channel-readiness-routes.test.ts +0 -1
  27. package/src/__tests__/channel-readiness-service.test.ts +1 -1
  28. package/src/__tests__/checker.test.ts +13 -11
  29. package/src/__tests__/claude-code-skill-regression.test.ts +5 -2
  30. package/src/__tests__/claude-code-tool-profiles.test.ts +7 -3
  31. package/src/__tests__/config-loader-backfill.test.ts +1 -5
  32. package/src/__tests__/config-schema.test.ts +9 -46
  33. package/src/__tests__/config-watcher.test.ts +11 -3
  34. package/src/__tests__/conversation-routes-slash-commands.test.ts +0 -1
  35. package/src/__tests__/credential-broker-browser-fill.test.ts +27 -24
  36. package/src/__tests__/credential-broker-server-use.test.ts +76 -40
  37. package/src/__tests__/credential-security-e2e.test.ts +1 -6
  38. package/src/__tests__/credential-security-invariants.test.ts +27 -8
  39. package/src/__tests__/credential-vault-unit.test.ts +32 -16
  40. package/src/__tests__/credential-vault.test.ts +40 -28
  41. package/src/__tests__/credentials-cli.test.ts +1 -21
  42. package/src/__tests__/email-invite-adapter.test.ts +0 -1
  43. package/src/__tests__/error-handler-friendly-messages.test.ts +4 -5
  44. package/src/__tests__/fixtures/credential-security-fixtures.ts +3 -3
  45. package/src/__tests__/fixtures/media-reuse-fixtures.ts +3 -79
  46. package/src/__tests__/gateway-only-enforcement.test.ts +1 -23
  47. package/src/__tests__/guardian-action-conversation-turn.test.ts +8 -8
  48. package/src/__tests__/guardian-action-late-reply.test.ts +13 -14
  49. package/src/__tests__/guardian-action-store.test.ts +0 -57
  50. package/src/__tests__/guardian-outbound-http.test.ts +1 -1
  51. package/src/__tests__/guardian-verification-voice-binding.test.ts +1 -3
  52. package/src/__tests__/hooks-blocking.test.ts +1 -1
  53. package/src/__tests__/hooks-config.test.ts +5 -29
  54. package/src/__tests__/hooks-discovery.test.ts +1 -1
  55. package/src/__tests__/hooks-integration.test.ts +1 -1
  56. package/src/__tests__/hooks-manager.test.ts +1 -1
  57. package/src/__tests__/hooks-runner.test.ts +1 -23
  58. package/src/__tests__/hooks-settings.test.ts +1 -1
  59. package/src/__tests__/hooks-templates.test.ts +1 -1
  60. package/src/__tests__/host-shell-tool.test.ts +0 -1
  61. package/src/__tests__/http-user-message-parity.test.ts +19 -0
  62. package/src/__tests__/integration-status.test.ts +0 -1
  63. package/src/__tests__/invite-routes-http.test.ts +0 -3
  64. package/src/__tests__/list-messages-attachments.test.ts +0 -1
  65. package/src/__tests__/llm-usage-store.test.ts +50 -0
  66. package/src/__tests__/log-export-workspace.test.ts +233 -0
  67. package/src/__tests__/managed-proxy-context.test.ts +41 -41
  68. package/src/__tests__/managed-skill-lifecycle.test.ts +0 -1
  69. package/src/__tests__/media-generate-image.test.ts +9 -4
  70. package/src/__tests__/media-reuse-story.e2e.test.ts +1 -7
  71. package/src/__tests__/memory-regressions.experimental.test.ts +4 -4
  72. package/src/__tests__/memory-regressions.test.ts +27 -28
  73. package/src/__tests__/memory-retrieval.benchmark.test.ts +1 -1
  74. package/src/__tests__/memory-upsert-concurrency.test.ts +4 -4
  75. package/src/__tests__/migration-cross-version-compatibility.test.ts +0 -1
  76. package/src/__tests__/migration-export-http.test.ts +0 -1
  77. package/src/__tests__/migration-import-commit-http.test.ts +0 -1
  78. package/src/__tests__/migration-import-preflight-http.test.ts +0 -1
  79. package/src/__tests__/migration-validate-http.test.ts +0 -1
  80. package/src/__tests__/notification-decision-fallback.test.ts +1 -1
  81. package/src/__tests__/notification-schedule-dedup.test.ts +237 -0
  82. package/src/__tests__/oauth-cli.test.ts +2 -14
  83. package/src/__tests__/oauth-store.test.ts +3 -7
  84. package/src/__tests__/oauth2-gateway-transport.test.ts +5 -4
  85. package/src/__tests__/onboarding-starter-tasks.test.ts +1 -1
  86. package/src/__tests__/onboarding-template-contract.test.ts +1 -2
  87. package/src/__tests__/openai-provider.test.ts +7 -7
  88. package/src/__tests__/platform.test.ts +14 -4
  89. package/src/__tests__/pricing.test.ts +0 -234
  90. package/src/__tests__/provider-commit-message-generator.test.ts +19 -15
  91. package/src/__tests__/provider-fail-open-selection.test.ts +67 -62
  92. package/src/__tests__/provider-managed-proxy-integration.test.ts +88 -85
  93. package/src/__tests__/provider-registry-ollama.test.ts +10 -4
  94. package/src/__tests__/public-ingress-urls.test.ts +1 -1
  95. package/src/__tests__/recording-handler.test.ts +0 -1
  96. package/src/__tests__/registry.test.ts +3 -103
  97. package/src/__tests__/relay-server.test.ts +0 -1
  98. package/src/__tests__/runtime-attachment-metadata.test.ts +0 -1
  99. package/src/__tests__/runtime-events-sse-parity.test.ts +0 -1
  100. package/src/__tests__/runtime-events-sse.test.ts +0 -1
  101. package/src/__tests__/script-proxy-injection-runtime.test.ts +2 -7
  102. package/src/__tests__/secret-onetime-send.test.ts +1 -6
  103. package/src/__tests__/secret-routes-managed-proxy.test.ts +6 -14
  104. package/src/__tests__/secret-scanner-executor.test.ts +0 -1
  105. package/src/__tests__/secure-keys.test.ts +241 -229
  106. package/src/__tests__/send-endpoint-busy.test.ts +0 -1
  107. package/src/__tests__/session-abort-tool-results.test.ts +3 -2
  108. package/src/__tests__/session-agent-loop-overflow.test.ts +1012 -838
  109. package/src/__tests__/session-agent-loop.test.ts +2 -2
  110. package/src/__tests__/session-confirmation-signals.test.ts +3 -2
  111. package/src/__tests__/session-error.test.ts +5 -4
  112. package/src/__tests__/session-history-web-search.test.ts +34 -9
  113. package/src/__tests__/session-messaging-secret-redirect.test.ts +1 -7
  114. package/src/__tests__/session-pre-run-repair.test.ts +3 -2
  115. package/src/__tests__/session-provider-retry-repair.test.ts +31 -27
  116. package/src/__tests__/session-queue.test.ts +5 -5
  117. package/src/__tests__/session-runtime-assembly.test.ts +118 -0
  118. package/src/__tests__/session-slash-known.test.ts +31 -14
  119. package/src/__tests__/session-slash-queue.test.ts +3 -2
  120. package/src/__tests__/session-slash-unknown.test.ts +3 -2
  121. package/src/__tests__/session-workspace-cache-state.test.ts +3 -1
  122. package/src/__tests__/session-workspace-injection.test.ts +3 -2
  123. package/src/__tests__/session-workspace-tool-tracking.test.ts +3 -2
  124. package/src/__tests__/shell-tool-proxy-mode.test.ts +0 -1
  125. package/src/__tests__/skill-projection-feature-flag.test.ts +0 -1
  126. package/src/__tests__/skill-script-runner-sandbox.test.ts +0 -1
  127. package/src/__tests__/skillssh-registry.test.ts +21 -0
  128. package/src/__tests__/slack-channel-config.test.ts +1 -7
  129. package/src/__tests__/slack-share-routes.test.ts +1 -1
  130. package/src/__tests__/swarm-recursion.test.ts +4 -1
  131. package/src/__tests__/swarm-session-integration.test.ts +24 -14
  132. package/src/__tests__/swarm-tool.test.ts +4 -2
  133. package/src/__tests__/task-compiler.test.ts +1 -1
  134. package/src/__tests__/telegram-bot-username-resolution.test.ts +2 -4
  135. package/src/__tests__/test-support/browser-skill-harness.ts +0 -18
  136. package/src/__tests__/test-support/computer-use-skill-harness.ts +0 -23
  137. package/src/__tests__/token-estimator-accuracy.benchmark.test.ts +1521 -0
  138. package/src/__tests__/tool-execution-abort-cleanup.test.ts +0 -1
  139. package/src/__tests__/tool-executor-lifecycle-events.test.ts +0 -1
  140. package/src/__tests__/tool-executor-shell-integration.test.ts +0 -1
  141. package/src/__tests__/tool-executor.test.ts +1 -2
  142. package/src/__tests__/trust-store.test.ts +8 -83
  143. package/src/__tests__/twilio-config.test.ts +0 -1
  144. package/src/__tests__/twilio-provider.test.ts +0 -5
  145. package/src/__tests__/twilio-routes.test.ts +2 -3
  146. package/src/__tests__/usage-cache-backfill-migration.test.ts +10 -10
  147. package/src/__tests__/verification-control-plane-policy.test.ts +0 -1
  148. package/src/__tests__/voice-quality.test.ts +2 -1
  149. package/src/__tests__/voice-scoped-grant-consumer.test.ts +0 -1
  150. package/src/__tests__/web-search.test.ts +1 -1
  151. package/src/agent/loop.ts +17 -1
  152. package/src/bundler/app-bundler.ts +40 -24
  153. package/src/calls/call-controller.ts +16 -0
  154. package/src/calls/guardian-question-copy.ts +1 -1
  155. package/src/calls/relay-server.ts +29 -13
  156. package/src/calls/voice-control-protocol.ts +1 -0
  157. package/src/calls/voice-quality.ts +1 -1
  158. package/src/calls/voice-session-bridge.ts +9 -3
  159. package/src/channels/types.ts +16 -0
  160. package/src/cli/commands/bash.ts +173 -0
  161. package/src/cli/commands/doctor.ts +15 -57
  162. package/src/cli/commands/memory.ts +3 -5
  163. package/src/cli/commands/oauth/connections.ts +4 -2
  164. package/src/cli/commands/oauth/providers.ts +1 -13
  165. package/src/cli/commands/sessions.ts +1 -1
  166. package/src/cli/commands/usage.ts +359 -0
  167. package/src/cli/http-client.ts +22 -12
  168. package/src/cli/program.ts +4 -0
  169. package/src/cli/reference.ts +2 -0
  170. package/src/cli.ts +251 -181
  171. package/src/config/assistant-feature-flags.ts +0 -7
  172. package/src/config/bundled-skills/chatgpt-import/tools/chatgpt-import.ts +1 -1
  173. package/src/config/bundled-skills/claude-code/SKILL.md +1 -1
  174. package/src/config/bundled-skills/claude-code/TOOLS.json +1 -1
  175. package/src/config/bundled-skills/gmail/SKILL.md +0 -1
  176. package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +4 -3
  177. package/src/config/bundled-skills/media-processing/services/reduce.ts +1 -1
  178. package/src/config/bundled-skills/media-processing/tools/analyze-keyframes.ts +3 -5
  179. package/src/config/bundled-skills/media-processing/tools/extract-keyframes.ts +2 -3
  180. package/src/config/bundled-skills/messaging/SKILL.md +0 -1
  181. package/src/config/bundled-skills/phone-calls/references/CONFIG.md +1 -1
  182. package/src/config/bundled-skills/sequences/SKILL.md +0 -1
  183. package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +5 -6
  184. package/src/config/env.ts +13 -0
  185. package/src/config/feature-flag-registry.json +15 -39
  186. package/src/config/loader.ts +7 -135
  187. package/src/config/schema.ts +0 -6
  188. package/src/config/schemas/channels.ts +1 -0
  189. package/src/config/schemas/elevenlabs.ts +2 -2
  190. package/src/config/schemas/security.ts +1 -2
  191. package/src/config/skills.ts +1 -1
  192. package/src/contacts/contact-store.ts +21 -75
  193. package/src/contacts/contacts-write.ts +6 -6
  194. package/src/contacts/types.ts +2 -0
  195. package/src/context/token-estimator.ts +35 -2
  196. package/src/context/window-manager.ts +16 -2
  197. package/src/daemon/approved-devices-store.ts +0 -44
  198. package/src/daemon/classifier.ts +1 -1
  199. package/src/daemon/config-watcher.ts +35 -11
  200. package/src/daemon/context-overflow-reducer.ts +13 -2
  201. package/src/daemon/handlers/config-ingress.ts +25 -8
  202. package/src/daemon/handlers/config-model.ts +22 -16
  203. package/src/daemon/handlers/config-telegram.ts +18 -6
  204. package/src/daemon/handlers/dictation.ts +0 -429
  205. package/src/daemon/handlers/sessions.ts +4 -116
  206. package/src/daemon/handlers/skills.ts +2 -201
  207. package/src/daemon/lifecycle.ts +21 -20
  208. package/src/daemon/message-types/contacts.ts +2 -0
  209. package/src/daemon/message-types/integrations.ts +1 -0
  210. package/src/daemon/message-types/sessions.ts +2 -0
  211. package/src/daemon/parse-actual-tokens-from-error.test.ts +75 -0
  212. package/src/daemon/providers-setup.ts +1 -1
  213. package/src/daemon/server.ts +42 -5
  214. package/src/daemon/session-agent-loop-handlers.ts +1 -1
  215. package/src/daemon/session-agent-loop.ts +27 -79
  216. package/src/daemon/session-error.ts +5 -4
  217. package/src/daemon/session-process.ts +17 -10
  218. package/src/daemon/session-runtime-assembly.ts +50 -0
  219. package/src/daemon/session-slash.ts +34 -22
  220. package/src/daemon/session.ts +1 -0
  221. package/src/daemon/shutdown-handlers.ts +15 -0
  222. package/src/daemon/watch-handler.ts +2 -2
  223. package/src/email/guardrails.ts +1 -1
  224. package/src/email/service.ts +0 -5
  225. package/src/events/domain-events.ts +1 -0
  226. package/src/hooks/templates.ts +1 -1
  227. package/src/media/app-icon-generator.ts +4 -3
  228. package/src/media/avatar-router.ts +5 -4
  229. package/src/media/gemini-image-service.ts +5 -5
  230. package/src/memory/admin.ts +2 -2
  231. package/src/memory/app-git-service.ts +0 -7
  232. package/src/memory/canonical-guardian-store.ts +25 -3
  233. package/src/memory/conversation-crud.ts +1 -1
  234. package/src/memory/conversation-title-service.ts +2 -2
  235. package/src/memory/db-init.ts +12 -0
  236. package/src/memory/embedding-backend.ts +46 -33
  237. package/src/memory/external-conversation-store.ts +0 -30
  238. package/src/memory/guardian-action-store.ts +0 -31
  239. package/src/memory/guardian-approvals.ts +1 -56
  240. package/src/memory/indexer.ts +4 -3
  241. package/src/memory/items-extractor.ts +1 -1
  242. package/src/memory/job-handlers/backfill.ts +5 -2
  243. package/src/memory/job-handlers/index-maintenance.ts +2 -2
  244. package/src/memory/job-handlers/media-processing.ts +2 -2
  245. package/src/memory/job-handlers/summarization.ts +1 -1
  246. package/src/memory/job-utils.ts +1 -2
  247. package/src/memory/jobs-worker.ts +2 -2
  248. package/src/memory/llm-usage-store.ts +57 -11
  249. package/src/memory/media-store.ts +4 -535
  250. package/src/memory/migrations/032-guardian-delivery-conversation-index.ts +2 -2
  251. package/src/memory/migrations/110-channel-guardian.ts +0 -1
  252. package/src/memory/migrations/158-channel-interaction-columns.ts +18 -0
  253. package/src/memory/migrations/159-drop-contact-interaction-columns.ts +16 -0
  254. package/src/memory/migrations/160-drop-loopback-port-column.ts +13 -0
  255. package/src/memory/migrations/index.ts +3 -0
  256. package/src/memory/published-pages-store.ts +0 -83
  257. package/src/memory/qdrant-circuit-breaker.ts +0 -8
  258. package/src/memory/retriever.test.ts +19 -12
  259. package/src/memory/retriever.ts +1 -1
  260. package/src/memory/schema/contacts.ts +2 -2
  261. package/src/memory/schema/oauth.ts +0 -1
  262. package/src/memory/search/semantic.ts +1 -8
  263. package/src/memory/shared-app-links-store.ts +0 -15
  264. package/src/messaging/registry.ts +0 -5
  265. package/src/messaging/style-analyzer.ts +1 -1
  266. package/src/notifications/copy-composer.ts +5 -13
  267. package/src/notifications/decision-engine.ts +2 -2
  268. package/src/notifications/deliveries-store.ts +0 -39
  269. package/src/notifications/guardian-question-mode.ts +6 -10
  270. package/src/notifications/preference-extractor.ts +1 -1
  271. package/src/oauth/byo-connection.test.ts +29 -20
  272. package/src/oauth/connect-orchestrator.ts +5 -3
  273. package/src/oauth/connect-types.ts +9 -2
  274. package/src/oauth/manual-token-connection.ts +9 -7
  275. package/src/oauth/oauth-store.ts +2 -8
  276. package/src/oauth/provider-behaviors.ts +11 -1
  277. package/src/oauth/seed-providers.ts +13 -5
  278. package/src/permissions/checker.ts +21 -2
  279. package/src/permissions/shell-identity.ts +0 -5
  280. package/src/permissions/trust-store.ts +0 -37
  281. package/src/prompts/__tests__/build-cli-reference-section.test.ts +1 -1
  282. package/src/prompts/system-prompt.ts +5 -14
  283. package/src/prompts/templates/BOOTSTRAP.md +1 -3
  284. package/src/providers/anthropic/client.ts +16 -8
  285. package/src/providers/managed-proxy/constants.ts +9 -11
  286. package/src/providers/managed-proxy/context.ts +14 -9
  287. package/src/providers/provider-send-message.ts +4 -52
  288. package/src/providers/registry.ts +29 -57
  289. package/src/providers/types.ts +1 -1
  290. package/src/runtime/actor-token-store.ts +0 -23
  291. package/src/runtime/auth/route-policy.ts +4 -0
  292. package/src/runtime/channel-invite-transports/telegram.ts +12 -6
  293. package/src/runtime/channel-retry-sweep.ts +6 -0
  294. package/src/runtime/http-router.ts +5 -1
  295. package/src/runtime/http-server.ts +101 -4
  296. package/src/runtime/http-types.ts +1 -0
  297. package/src/runtime/invite-instruction-generator.ts +25 -51
  298. package/src/runtime/invite-service.ts +0 -20
  299. package/src/runtime/middleware/error-handler.ts +1 -2
  300. package/src/runtime/routes/app-management-routes.ts +1 -0
  301. package/src/runtime/routes/attachment-routes.ts +1 -1
  302. package/src/runtime/routes/brain-graph-routes.ts +1 -1
  303. package/src/runtime/routes/btw-routes.ts +20 -1
  304. package/src/runtime/routes/call-routes.ts +1 -1
  305. package/src/runtime/routes/conversation-routes.ts +64 -24
  306. package/src/runtime/routes/debug-routes.ts +1 -1
  307. package/src/runtime/routes/diagnostics-routes.ts +2 -2
  308. package/src/runtime/routes/documents-routes.ts +3 -3
  309. package/src/runtime/routes/global-search-routes.ts +1 -1
  310. package/src/runtime/routes/guardian-bootstrap-routes.ts +0 -20
  311. package/src/runtime/routes/guardian-refresh-routes.ts +0 -20
  312. package/src/runtime/routes/inbound-message-handler.ts +10 -2
  313. package/src/runtime/routes/inbound-stages/background-dispatch.ts +4 -0
  314. package/src/runtime/routes/inbound-stages/edit-intercept.ts +5 -5
  315. package/src/runtime/routes/integrations/slack/share.ts +5 -5
  316. package/src/runtime/routes/log-export-routes.ts +122 -10
  317. package/src/runtime/routes/secret-routes.ts +4 -4
  318. package/src/runtime/routes/session-query-routes.ts +3 -3
  319. package/src/runtime/routes/settings-routes.ts +53 -0
  320. package/src/runtime/routes/trust-rules-routes.ts +1 -1
  321. package/src/runtime/routes/workspace-routes.ts +3 -0
  322. package/src/runtime/verification-templates.ts +1 -1
  323. package/src/security/credential-backend.ts +148 -0
  324. package/src/security/oauth2.ts +5 -5
  325. package/src/security/secret-allowlist.ts +1 -1
  326. package/src/security/secure-keys.ts +98 -160
  327. package/src/security/token-manager.ts +0 -7
  328. package/src/sequence/guardrails.ts +0 -4
  329. package/src/sequence/store.ts +1 -20
  330. package/src/sequence/types.ts +1 -36
  331. package/src/signals/bash.ts +157 -0
  332. package/src/signals/cancel.ts +69 -0
  333. package/src/signals/conversation-undo.ts +127 -0
  334. package/src/signals/trust-rule.ts +174 -0
  335. package/src/skills/clawhub.ts +5 -5
  336. package/src/skills/managed-store.ts +4 -4
  337. package/src/skills/skillssh-registry.ts +6 -1
  338. package/src/swarm/backend-claude-code.ts +6 -6
  339. package/src/swarm/worker-backend.ts +1 -1
  340. package/src/swarm/worker-runner.ts +1 -1
  341. package/src/telegram/bot-username.ts +11 -0
  342. package/src/telemetry/usage-telemetry-reporter.test.ts +366 -0
  343. package/src/telemetry/usage-telemetry-reporter.ts +181 -0
  344. package/src/tools/claude-code/claude-code.ts +6 -6
  345. package/src/tools/credentials/broker.ts +7 -5
  346. package/src/tools/credentials/vault.ts +11 -6
  347. package/src/tools/memory/handlers.test.ts +24 -26
  348. package/src/tools/memory/handlers.ts +1 -13
  349. package/src/tools/network/__tests__/web-search.test.ts +18 -86
  350. package/src/tools/network/web-search.ts +9 -15
  351. package/src/tools/registry.ts +5 -100
  352. package/src/tools/terminal/parser.ts +34 -4
  353. package/src/tools/tool-manifest.ts +0 -10
  354. package/src/usage/actors.ts +0 -12
  355. package/src/util/canonicalize-identity.ts +0 -9
  356. package/src/util/errors.ts +0 -3
  357. package/src/util/platform.ts +31 -8
  358. package/src/util/pricing.ts +0 -39
  359. package/src/watcher/constants.ts +0 -7
  360. package/src/watcher/providers/linear.ts +1 -1
  361. package/src/work-items/work-item-store.ts +4 -4
  362. package/src/workspace/commit-message-provider.ts +1 -1
  363. package/src/workspace/git-service.ts +44 -1
  364. package/src/workspace/provider-commit-message-generator.ts +11 -7
  365. package/src/__tests__/fixtures/proxy-fixtures.ts +0 -147
  366. package/src/browser-extension-relay/client.ts +0 -155
  367. package/src/contacts/index.ts +0 -18
  368. package/src/daemon/tls-certs.ts +0 -270
  369. package/src/errors.ts +0 -41
  370. package/src/events/index.ts +0 -18
  371. package/src/followups/index.ts +0 -10
  372. package/src/playbooks/index.ts +0 -10
  373. package/src/runtime/auth/index.ts +0 -44
  374. package/src/tasks/candidate-store.ts +0 -95
  375. package/src/tools/browser/api-map.ts +0 -313
  376. package/src/tools/browser/auto-navigate.ts +0 -469
  377. package/src/tools/browser/headless-browser.ts +0 -590
  378. package/src/tools/browser/recording-store.ts +0 -75
  379. package/src/tools/computer-use/registry.ts +0 -21
  380. package/src/tools/tasks/index.ts +0 -27
@@ -25,7 +25,6 @@ mock.module("../config/loader.js", () => ({
25
25
 
26
26
  provider: "anthropic",
27
27
  model: "test",
28
- apiKeys: {},
29
28
  maxTokens: 4096,
30
29
  dataDir: "/tmp",
31
30
  timeouts: {
@@ -8,7 +8,6 @@ import type {
8
8
  const mockConfig = {
9
9
  provider: "anthropic",
10
10
  model: "test",
11
- apiKeys: {},
12
11
  maxTokens: 4096,
13
12
  dataDir: "/tmp",
14
13
  timeouts: {
@@ -17,7 +17,6 @@ import type { ToolContext } from "../tools/types.js";
17
17
  const mockConfig = {
18
18
  provider: "anthropic",
19
19
  model: "test",
20
- apiKeys: {},
21
20
  maxTokens: 4096,
22
21
  dataDir: "/tmp",
23
22
  timeouts: {
@@ -26,7 +26,6 @@ import type {
26
26
  const mockConfig = {
27
27
  provider: "anthropic",
28
28
  model: "test",
29
- apiKeys: {},
30
29
  maxTokens: 4096,
31
30
  dataDir: "/tmp",
32
31
  timeouts: {
@@ -197,7 +196,7 @@ describe("ToolExecutor allowedToolNames gating", () => {
197
196
  }
198
197
  });
199
198
 
200
- test("executes normally when allowedToolNames is not set (backward compat)", async () => {
199
+ test("executes normally when allowedToolNames is not set", async () => {
201
200
  const executor = new ToolExecutor(makePrompter());
202
201
  const result = await executor.execute(
203
202
  "file_read",
@@ -57,7 +57,6 @@ import {
57
57
  } from "../permissions/trust-store.js";
58
58
 
59
59
  const trustPath = join(testDir, "protected", "trust.json");
60
- const legacyTrustPath = join(testDir, "trust.json");
61
60
  const DEFAULT_TEMPLATES = getDefaultRuleTemplates();
62
61
  const NUM_DEFAULTS = DEFAULT_TEMPLATES.length;
63
62
  const DEFAULT_PRIORITY_BY_ID = new Map(
@@ -73,11 +72,6 @@ describe("Trust Store", () => {
73
72
  } catch {
74
73
  /* may not exist */
75
74
  }
76
- try {
77
- rmSync(legacyTrustPath);
78
- } catch {
79
- /* may not exist */
80
- }
81
75
  });
82
76
 
83
77
  // Intentionally do not remove `testDir` in afterAll.
@@ -1099,7 +1093,7 @@ describe("Trust Store", () => {
1099
1093
 
1100
1094
  // ── default allow: browser tools ────────────────────────────
1101
1095
 
1102
- test("all 10 browser tools have default allow rules", () => {
1096
+ test("all 14 browser tools have default allow rules", () => {
1103
1097
  const templates = getDefaultRuleTemplates();
1104
1098
  const browserTools = [
1105
1099
  "browser_navigate",
@@ -1109,8 +1103,12 @@ describe("Trust Store", () => {
1109
1103
  "browser_click",
1110
1104
  "browser_type",
1111
1105
  "browser_press_key",
1106
+ "browser_scroll",
1107
+ "browser_select_option",
1108
+ "browser_hover",
1112
1109
  "browser_wait_for",
1113
1110
  "browser_extract",
1111
+ "browser_wait_for_download",
1114
1112
  "browser_fill_credential",
1115
1113
  ];
1116
1114
 
@@ -1223,79 +1221,6 @@ describe("Trust Store", () => {
1223
1221
  // ── loadFromDisk resilience (misc) ──────────────────────────────
1224
1222
 
1225
1223
  describe("loadFromDisk resilience (misc)", () => {
1226
- test("migrates legacy root trust file to protected path", () => {
1227
- mkdirSync(dirname(legacyTrustPath), { recursive: true });
1228
- writeFileSync(
1229
- legacyTrustPath,
1230
- JSON.stringify({
1231
- version: 3,
1232
- rules: [
1233
- {
1234
- id: "legacy-deny",
1235
- tool: "host_bash",
1236
- pattern: "rm -rf *",
1237
- scope: "everywhere",
1238
- decision: "deny",
1239
- priority: 200,
1240
- createdAt: 123,
1241
- },
1242
- ],
1243
- }),
1244
- );
1245
-
1246
- clearCache();
1247
- const rules = getAllRules();
1248
-
1249
- expect(rules.find((r) => r.id === "legacy-deny")).toBeDefined();
1250
- expect(readFileSync(trustPath, "utf-8")).toContain("legacy-deny");
1251
- expect(() => readFileSync(legacyTrustPath, "utf-8")).toThrow();
1252
- });
1253
-
1254
- test("prefers protected trust file when both protected and legacy files exist", () => {
1255
- mkdirSync(dirname(trustPath), { recursive: true });
1256
- writeFileSync(
1257
- trustPath,
1258
- JSON.stringify({
1259
- version: 3,
1260
- rules: [
1261
- {
1262
- id: "protected-rule",
1263
- tool: "bash",
1264
- pattern: "protected *",
1265
- scope: "/tmp",
1266
- decision: "allow",
1267
- priority: 100,
1268
- createdAt: 1,
1269
- },
1270
- ],
1271
- }),
1272
- );
1273
- writeFileSync(
1274
- legacyTrustPath,
1275
- JSON.stringify({
1276
- version: 3,
1277
- rules: [
1278
- {
1279
- id: "legacy-rule",
1280
- tool: "bash",
1281
- pattern: "legacy *",
1282
- scope: "/tmp",
1283
- decision: "deny",
1284
- priority: 200,
1285
- createdAt: 2,
1286
- },
1287
- ],
1288
- }),
1289
- );
1290
-
1291
- clearCache();
1292
- const rules = getAllRules();
1293
-
1294
- expect(rules.find((r) => r.id === "protected-rule")).toBeDefined();
1295
- expect(rules.find((r) => r.id === "legacy-rule")).toBeUndefined();
1296
- expect(readFileSync(legacyTrustPath, "utf-8")).toContain("legacy-rule");
1297
- });
1298
-
1299
1224
  test("malformed file (valid JSON but null) is handled gracefully", () => {
1300
1225
  mkdirSync(dirname(trustPath), { recursive: true });
1301
1226
  writeFileSync(trustPath, "null");
@@ -1536,10 +1461,10 @@ describe("Trust Store", () => {
1536
1461
  });
1537
1462
  });
1538
1463
 
1539
- // ── backward compatibility ────────────────────────────────────
1464
+ // ── optional ctx parameter ────────────────────────────────────
1540
1465
 
1541
- describe("backward compatibility", () => {
1542
- test("existing callers without ctx parameter still work", () => {
1466
+ describe("optional ctx parameter", () => {
1467
+ test("callers without ctx parameter still work", () => {
1543
1468
  addRule("bash", "git *", "/tmp", "allow", 200);
1544
1469
  // Calling without the 4th argument — must still match
1545
1470
  const match = findHighestPriorityRule("bash", ["git status"], "/tmp");
@@ -13,7 +13,6 @@ mock.module("../util/logger.js", () => ({
13
13
  }));
14
14
 
15
15
  mock.module("../security/secure-keys.js", () => ({
16
- getSecureKey: (key: string) => mockSecureKeys[key] ?? null,
17
16
  getSecureKeyAsync: async (key: string) => mockSecureKeys[key] ?? null,
18
17
  }));
19
18
 
@@ -41,11 +41,6 @@ mock.module("../config/loader.js", () => ({
41
41
  }));
42
42
 
43
43
  mock.module("../security/secure-keys.js", () => ({
44
- getSecureKey: (key: string) => {
45
- if (key === credentialKey("twilio", "auth_token")) return mockAuthToken;
46
- if (key === credentialKey("twilio", "account_sid")) return mockAccountSid;
47
- return undefined;
48
- },
49
44
  getSecureKeyAsync: async (key: string) => {
50
45
  if (key === credentialKey("twilio", "auth_token")) return mockAuthToken;
51
46
  if (key === credentialKey("twilio", "account_sid")) return mockAccountSid;
@@ -120,11 +120,10 @@ mock.module("../util/logger.js", () => ({
120
120
  const mockConfigObj = {
121
121
  model: "test",
122
122
  provider: "test",
123
- apiKeys: {},
124
123
  memory: { enabled: false },
125
124
  rateLimit: { maxRequestsPerMinute: 0, maxTokensPerSession: 0 },
126
125
  secretDetection: { enabled: false },
127
- elevenlabs: { voiceId: "21m00Tcm4TlvDq8ikWAM" },
126
+ elevenlabs: { voiceId: DEFAULT_ELEVENLABS_VOICE_ID },
128
127
  calls: {
129
128
  voice: {
130
129
  language: "en-US",
@@ -144,7 +143,6 @@ mock.module("../config/loader.js", () => ({
144
143
  }));
145
144
 
146
145
  mock.module("../security/secure-keys.js", () => ({
147
- getSecureKey: (key: string) => mockSecureKeyStore[key],
148
146
  setSecureKeyAsync: async (key: string, value: string) => {
149
147
  mockSecureKeyStore[key] = value;
150
148
  return true;
@@ -322,6 +320,7 @@ import {
322
320
  handleStatusCallback,
323
321
  handleVoiceWebhook,
324
322
  } from "../calls/twilio-routes.js";
323
+ import { DEFAULT_ELEVENLABS_VOICE_ID } from "../config/schemas/elevenlabs.js";
325
324
  import { getDb, initializeDb, resetDb } from "../memory/db.js";
326
325
  import { conversations } from "../memory/schema.js";
327
326
  import {
@@ -46,7 +46,7 @@ import {
46
46
  import { migrateBackfillUsageCacheAccounting } from "../memory/migrations/140-backfill-usage-cache-accounting.js";
47
47
  import type { PricingUsage } from "../usage/types.js";
48
48
  import {
49
- estimateCost,
49
+ resolvePricing,
50
50
  resolvePricingForUsageWithOverrides,
51
51
  } from "../util/pricing.js";
52
52
 
@@ -187,7 +187,8 @@ describe("migrateBackfillUsageCacheAccounting", () => {
187
187
  model,
188
188
  inputTokens: 700,
189
189
  outputTokens: 70,
190
- estimatedCostUsd: estimateCost(700, 70, model, "anthropic"),
190
+ estimatedCostUsd:
191
+ resolvePricing("anthropic", model, 700, 70).estimatedCostUsd ?? 0,
191
192
  });
192
193
  insertRequestLog({
193
194
  id: "log-prev",
@@ -201,12 +202,9 @@ describe("migrateBackfillUsageCacheAccounting", () => {
201
202
  }),
202
203
  });
203
204
 
204
- const flattenedHistoricalCost = estimateCost(
205
- 3_420_218,
206
- 11_768,
207
- model,
208
- "anthropic",
209
- );
205
+ const flattenedHistoricalCost =
206
+ resolvePricing("anthropic", model, 3_420_218, 11_768).estimatedCostUsd ??
207
+ 0;
210
208
  insertUsageEvent({
211
209
  id: "usage-target",
212
210
  conversationId: "conv-usage-1",
@@ -245,7 +243,8 @@ describe("migrateBackfillUsageCacheAccounting", () => {
245
243
  }),
246
244
  });
247
245
 
248
- const noLogCost = estimateCost(1_234, 56, model, "anthropic");
246
+ const noLogCost =
247
+ resolvePricing("anthropic", model, 1_234, 56).estimatedCostUsd ?? 0;
249
248
  insertUsageEvent({
250
249
  id: "usage-no-logs",
251
250
  conversationId: "conv-usage-2",
@@ -346,7 +345,8 @@ describe("migrateBackfillUsageCacheAccounting", () => {
346
345
  model,
347
346
  inputTokens: 1_200,
348
347
  outputTokens: 80,
349
- estimatedCostUsd: estimateCost(1_200, 80, model, "anthropic"),
348
+ estimatedCostUsd:
349
+ resolvePricing("anthropic", model, 1_200, 80).estimatedCostUsd ?? 0,
350
350
  });
351
351
  insertRequestLog({
352
352
  id: "log-target",
@@ -19,7 +19,6 @@ import type {
19
19
  const mockConfig = {
20
20
  provider: "anthropic",
21
21
  model: "test",
22
- apiKeys: {},
23
22
  maxTokens: 4096,
24
23
  dataDir: "/tmp",
25
24
  timeouts: {
@@ -10,6 +10,7 @@ import {
10
10
  buildElevenLabsVoiceSpec,
11
11
  resolveVoiceQualityProfile,
12
12
  } from "../calls/voice-quality.js";
13
+ import { DEFAULT_ELEVENLABS_VOICE_ID } from "../config/schemas/elevenlabs.js";
13
14
 
14
15
  describe("buildElevenLabsVoiceSpec", () => {
15
16
  test("returns bare voiceId when no model is set", () => {
@@ -63,7 +64,7 @@ describe("buildElevenLabsVoiceSpec", () => {
63
64
  describe("resolveVoiceQualityProfile", () => {
64
65
  test("always returns ElevenLabs ttsProvider", () => {
65
66
  mockConfig = {
66
- elevenlabs: { voiceId: "21m00Tcm4TlvDq8ikWAM" },
67
+ elevenlabs: { voiceId: DEFAULT_ELEVENLABS_VOICE_ID },
67
68
  calls: {
68
69
  voice: {
69
70
  language: "en-US",
@@ -55,7 +55,6 @@ mock.module("../config/loader.js", () => ({
55
55
 
56
56
  provider: "anthropic",
57
57
  providerOrder: ["anthropic"],
58
- apiKeys: { anthropic: "test-key" },
59
58
  calls: {
60
59
  enabled: true,
61
60
  provider: "twilio",
@@ -548,7 +548,7 @@ async function executeWebSearch(
548
548
  if (!apiKey) {
549
549
  return {
550
550
  content:
551
- "Error: No web search API key configured. Provide a PERPLEXITY_API_KEY or BRAVE_API_KEY here in the chat using the secure credential prompt, or set it from the Settings page.",
551
+ "Error: No web search API key configured. Set it via `keys set perplexity <key>` or `keys set brave <key>`, or configure it from the Settings page under API Keys.",
552
552
  isError: true,
553
553
  };
554
554
  }
package/src/agent/loop.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import * as Sentry from "@sentry/node";
2
2
 
3
+ import { estimateToolsTokens } from "../context/token-estimator.js";
3
4
  import { truncateOversizedToolResults } from "../context/tool-result-truncation.js";
4
5
  import { getHookManager } from "../hooks/manager.js";
5
6
  import type {
@@ -78,7 +79,7 @@ export type AgentEvent =
78
79
  toolUseId: string;
79
80
  input: Record<string, unknown>;
80
81
  }
81
- | { type: "server_tool_complete"; toolUseId: string }
82
+ | { type: "server_tool_complete"; toolUseId: string; isError: boolean }
82
83
  | { type: "error"; error: Error }
83
84
  | {
84
85
  type: "usage";
@@ -175,6 +176,20 @@ export class AgentLoop {
175
176
  this.toolExecutor = toolExecutor ?? null;
176
177
  }
177
178
 
179
+ /**
180
+ * Estimate token cost of the tool definitions sent to the provider.
181
+ *
182
+ * When `history` is provided and a dynamic `resolveTools` callback
183
+ * exists, the budget is derived from the resolved tool list for that
184
+ * turn — matching what `run()` actually sends. Without `history` (or
185
+ * without a resolver), falls back to the static `this.tools`.
186
+ */
187
+ getToolTokenBudget(history?: Message[]): number {
188
+ const tools =
189
+ history && this.resolveTools ? this.resolveTools(history) : this.tools;
190
+ return estimateToolsTokens(tools);
191
+ }
192
+
178
193
  async run(
179
194
  messages: Message[],
180
195
  onEvent: (event: AgentEvent) => void | Promise<void>,
@@ -318,6 +333,7 @@ export class AgentLoop {
318
333
  onEvent({
319
334
  type: "server_tool_complete",
320
335
  toolUseId: event.toolUseId,
336
+ isError: event.isError,
321
337
  });
322
338
  }
323
339
  },
@@ -14,7 +14,7 @@ import { join } from "node:path";
14
14
  import archiver from "archiver";
15
15
  import JSZip from "jszip";
16
16
 
17
- import { getApp, getAppsDir } from "../memory/app-store.js";
17
+ import { getApp, getAppsDir, isMultifileApp } from "../memory/app-store.js";
18
18
  import { computeContentId } from "../util/content-id.js";
19
19
  import { getLogger } from "../util/logger.js";
20
20
  import { compileApp } from "./app-compiler.js";
@@ -60,8 +60,10 @@ export async function packageApp(
60
60
  const version = app.version ?? "1.0.0";
61
61
  const contentId = computeContentId(app.name);
62
62
 
63
+ const multifile = isMultifileApp(app);
64
+
63
65
  const manifest: AppManifest = {
64
- format_version: 2,
66
+ format_version: multifile ? 2 : 1,
65
67
  name: app.name,
66
68
  ...(app.description ? { description: app.description } : {}),
67
69
  ...(app.icon ? { icon: app.icon } : {}),
@@ -74,34 +76,48 @@ export async function packageApp(
74
76
  content_id: contentId,
75
77
  };
76
78
 
77
- // Compile the app and bundle the dist/ output.
79
+ // Compile the app and bundle the output.
78
80
  const compiledFiles: { name: string; data: Buffer }[] = [];
79
81
 
80
82
  const appDir = join(getAppsDir(), appId);
81
- const compileResult = await compileApp(appDir);
82
- if (!compileResult.ok) {
83
- const messages = compileResult.errors
84
- .map((e) => {
85
- const loc = e.location
86
- ? ` (${e.location.file}:${e.location.line}:${e.location.column})`
87
- : "";
88
- return `${e.text}${loc}`;
89
- })
90
- .join("\n");
91
- throw new Error(`Compilation failed for app "${app.name}":\n${messages}`);
92
- }
93
83
 
94
- const distDir = join(appDir, "dist");
95
- const indexHtml = await readFile(join(distDir, "index.html"), "utf-8");
96
- const mainJs = await readFile(join(distDir, "main.js"));
84
+ if (multifile) {
85
+ // Multi-file TSX app: compile src/ -> dist/
86
+ const compileResult = await compileApp(appDir);
87
+ if (!compileResult.ok) {
88
+ const messages = compileResult.errors
89
+ .map((e) => {
90
+ const loc = e.location
91
+ ? ` (${e.location.file}:${e.location.line}:${e.location.column})`
92
+ : "";
93
+ return `${e.text}${loc}`;
94
+ })
95
+ .join("\n");
96
+ throw new Error(`Compilation failed for app "${app.name}":\n${messages}`);
97
+ }
98
+
99
+ const distDir = join(appDir, "dist");
100
+ const indexHtml = await readFile(join(distDir, "index.html"), "utf-8");
101
+ const mainJs = await readFile(join(distDir, "main.js"));
97
102
 
98
- compiledFiles.push({ name: "index.html", data: Buffer.from(indexHtml) });
99
- compiledFiles.push({ name: "main.js", data: mainJs });
103
+ compiledFiles.push({ name: "index.html", data: Buffer.from(indexHtml) });
104
+ compiledFiles.push({ name: "main.js", data: mainJs });
100
105
 
101
- // main.css is optional — only produced when the app imports CSS
102
- const cssPath = join(distDir, "main.css");
103
- if (existsSync(cssPath)) {
104
- compiledFiles.push({ name: "main.css", data: await readFile(cssPath) });
106
+ // main.css is optional — only produced when the app imports CSS
107
+ const cssPath = join(distDir, "main.css");
108
+ if (existsSync(cssPath)) {
109
+ compiledFiles.push({ name: "main.css", data: await readFile(cssPath) });
110
+ }
111
+ } else {
112
+ // Single-file HTML app: bundle index.html directly
113
+ const indexHtmlPath = join(appDir, "index.html");
114
+ if (!existsSync(indexHtmlPath)) {
115
+ throw new Error(
116
+ `App "${app.name}" has no src/ directory and no index.html`,
117
+ );
118
+ }
119
+ const indexHtml = await readFile(indexHtmlPath, "utf-8");
120
+ compiledFiles.push({ name: "index.html", data: Buffer.from(indexHtml) });
105
121
  }
106
122
 
107
123
  // Create the zip archive
@@ -50,6 +50,7 @@ import {
50
50
  ASK_GUARDIAN_CAPTURE_REGEX,
51
51
  CALL_OPENING_ACK_MARKER,
52
52
  CALL_OPENING_MARKER,
53
+ CALL_VERIFICATION_COMPLETE_MARKER,
53
54
  couldBeControlMarker,
54
55
  END_CALL_MARKER,
55
56
  extractBalancedJson,
@@ -209,6 +210,21 @@ export class CallController {
209
210
  await this.runTurn(CALL_OPENING_MARKER);
210
211
  }
211
212
 
213
+ /**
214
+ * Kick off the first utterance after the caller has completed outbound
215
+ * phone verification. Sends a verification-aware marker so the LLM can
216
+ * greet naturally with context that verification just happened.
217
+ */
218
+ async startPostVerificationGreeting(): Promise<void> {
219
+ if (this.initialGreetingStarted) return;
220
+ if (this.state !== "idle") return;
221
+
222
+ this.initialGreetingStarted = true;
223
+ this.resetSilenceTimer();
224
+ this.lastSentWasOpener = true;
225
+ await this.runTurn(CALL_VERIFICATION_COMPLETE_MARKER);
226
+ }
227
+
212
228
  /**
213
229
  * Handle a final caller utterance from the ConversationRelay.
214
230
  * Caller utterances always trigger normal turns, even when a guardian
@@ -52,7 +52,7 @@ export async function generateGuardianCopy(
52
52
  const fallback = buildFallbackCopy(questionText);
53
53
 
54
54
  // If no provider is configured, return fallback immediately
55
- const resolved = resolveConfiguredProvider();
55
+ const resolved = await resolveConfiguredProvider();
56
56
  if (!resolved) {
57
57
  log.debug(
58
58
  "No provider available for guardian copy generation, using fallback",
@@ -595,7 +595,7 @@ export class RelayConnection {
595
595
  (resolved.actorTrust.trustClass === "guardian" ||
596
596
  resolved.actorTrust.trustClass === "trusted_contact")
597
597
  ) {
598
- touchContactInteraction(resolved.actorTrust.memberRecord.contact.id);
598
+ touchContactInteraction(resolved.actorTrust.memberRecord.channel.id);
599
599
  }
600
600
  if (this.controller && resolved.actorTrust.trustClass !== "unknown") {
601
601
  this.controller.setTrustContext(
@@ -612,7 +612,7 @@ export class RelayConnection {
612
612
  resolved.actorTrust.trustClass === "trusted_contact")
613
613
  ) {
614
614
  touchContactInteraction(
615
- resolved.actorTrust.memberRecord.contact.id,
615
+ resolved.actorTrust.memberRecord.channel.id,
616
616
  );
617
617
  }
618
618
  if (this.controller && resolved.actorTrust.trustClass !== "unknown") {
@@ -992,14 +992,7 @@ export class RelayConnection {
992
992
  }
993
993
 
994
994
  if (isOutbound) {
995
- this.connectionState = "disconnecting";
996
- this.sendTextToken(result.ttsMessage!, true);
997
-
998
- updateCallSession(this.callSessionId, {
999
- status: "completed",
1000
- endedAt: Date.now(),
1001
- });
1002
-
995
+ // Keep the pointer message back to the initiating conversation
1003
996
  const successSession = getCallSession(this.callSessionId);
1004
997
  if (successSession?.initiatedFromConversationId) {
1005
998
  addPointerMessage(
@@ -1018,9 +1011,32 @@ export class RelayConnection {
1018
1011
  });
1019
1012
  }
1020
1013
 
1021
- setTimeout(() => {
1022
- this.endSession("Verified — guardian challenge passed");
1023
- }, getTtsPlaybackDelayMs());
1014
+ // Update trust context on the controller so the LLM knows this is the guardian
1015
+ if (this.controller) {
1016
+ const verifiedActorTrust = resolveActorTrust({
1017
+ assistantId,
1018
+ sourceChannel: "phone",
1019
+ conversationExternalId: fromNumber,
1020
+ actorExternalId: fromNumber,
1021
+ });
1022
+ this.controller.setTrustContext(
1023
+ toTrustContext(verifiedActorTrust, fromNumber),
1024
+ );
1025
+ }
1026
+
1027
+ // Mark session as in-progress and transition to guardian conversation
1028
+ // with verification context so the LLM greets naturally.
1029
+ updateCallSession(this.callSessionId, { status: "in_progress" });
1030
+ if (this.controller) {
1031
+ this.controller
1032
+ .startPostVerificationGreeting()
1033
+ .catch((err) =>
1034
+ log.error(
1035
+ { err, callSessionId: this.callSessionId },
1036
+ "Failed to start post-verification greeting",
1037
+ ),
1038
+ );
1039
+ }
1024
1040
  } else if (result.verificationType === "trusted_contact") {
1025
1041
  this.continueCallAfterTrustedContactActivation({
1026
1042
  assistantId,
@@ -11,6 +11,7 @@
11
11
 
12
12
  export const CALL_OPENING_MARKER = "[CALL_OPENING]";
13
13
  export const CALL_OPENING_ACK_MARKER = "[CALL_OPENING_ACK]";
14
+ export const CALL_VERIFICATION_COMPLETE_MARKER = "[CALL_VERIFICATION_COMPLETE]";
14
15
  export const END_CALL_MARKER = "[END_CALL]";
15
16
 
16
17
  // ---------------------------------------------------------------------------
@@ -44,7 +44,7 @@ export function buildElevenLabsVoiceSpec(config: {
44
44
  *
45
45
  * Always uses ElevenLabs TTS via Twilio ConversationRelay.
46
46
  * The voice ID comes from the shared `elevenlabs.voiceId` config
47
- * (defaults to Rachel21m00Tcm4TlvDq8ikWAM).
47
+ * (defaults to AmeliaZF6FPAbjXT4488VcRRnw).
48
48
  */
49
49
  export function resolveVoiceQualityProfile(
50
50
  config?: ReturnType<typeof loadConfig>,
@@ -24,7 +24,10 @@ import { checkIngressForSecrets } from "../security/secret-ingress.js";
24
24
  import { computeToolApprovalDigest } from "../security/tool-approval-digest.js";
25
25
  import { IngressBlockedError } from "../util/errors.js";
26
26
  import { getLogger } from "../util/logger.js";
27
- import { CALL_OPENING_MARKER } from "./voice-control-protocol.js";
27
+ import {
28
+ CALL_OPENING_MARKER,
29
+ CALL_VERIFICATION_COMPLETE_MARKER,
30
+ } from "./voice-control-protocol.js";
28
31
 
29
32
  const log = getLogger("voice-session-bridge");
30
33
 
@@ -197,7 +200,8 @@ function buildVoiceCallControlPrompt(opts: {
197
200
  ? " However, the disclosure text from rule 0 is separate from self-introduction and must always be included in your opening greeting, even if the Task does not mention introducing yourself."
198
201
  : "";
199
202
  lines.push(
200
- `7. If the latest user turn is "(call connecteddeliver opening greeting)", deliver your opening greeting based solely on the Task context above. The Task already describes how to open the call follow it directly without adding any extra introduction on top. If the Task says to introduce yourself, do so once. If the Task does not mention introducing yourself, skip the introduction.${disclosureReminder} Vary the wording naturally; do not use a fixed template.`,
203
+ '7. If the latest user turn is "(verification completedtransitioning into conversation)", the caller just completed a phone verification code challenge on this call. Greet them naturally and ask if there is anything you can help with. Keep it casual and brief.',
204
+ `If the latest user turn is "(call connected — deliver opening greeting)", deliver your opening greeting based solely on the Task context above. The Task already describes how to open the call — follow it directly without adding any extra introduction on top. If the Task says to introduce yourself, do so once. If the Task does not mention introducing yourself, skip the introduction.${disclosureReminder} Vary the wording naturally; do not use a fixed template.`,
201
205
  "8. If the latest user turn includes [CALL_OPENING_ACK], treat it as the callee acknowledging your opener and continue the conversation naturally without re-introducing yourself or repeating the initial check-in question.",
202
206
  );
203
207
  }
@@ -269,7 +273,9 @@ export async function startVoiceTurn(
269
273
  const persistedContent =
270
274
  opts.content === CALL_OPENING_MARKER
271
275
  ? "(call connected — deliver opening greeting)"
272
- : opts.content;
276
+ : opts.content === CALL_VERIFICATION_COMPLETE_MARKER
277
+ ? "(verification completed — transitioning into conversation)"
278
+ : opts.content;
273
279
 
274
280
  // Build the call-control protocol prompt so the model knows how to emit
275
281
  // control markers (ASK_GUARDIAN, END_CALL, etc.) and recognize opener turns.
@@ -74,6 +74,22 @@ export function assertInterfaceId(value: unknown, field: string): InterfaceId {
74
74
  return value;
75
75
  }
76
76
 
77
+ /**
78
+ * Interfaces that have an SSE client capable of displaying interactive
79
+ * permission prompts. Channel interfaces (telegram, slack, etc.) route
80
+ * approvals through the guardian system and have no interactive prompter UI.
81
+ */
82
+ export const INTERACTIVE_INTERFACES: ReadonlySet<InterfaceId> = new Set([
83
+ "macos",
84
+ "ios",
85
+ "cli",
86
+ "vellum",
87
+ ]);
88
+
89
+ export function isInteractiveInterface(id: InterfaceId): boolean {
90
+ return INTERACTIVE_INTERFACES.has(id);
91
+ }
92
+
77
93
  export interface TurnInterfaceContext {
78
94
  userMessageInterface: InterfaceId;
79
95
  assistantMessageInterface: InterfaceId;