@vellumai/assistant 0.4.46 → 0.4.49

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 (382) hide show
  1. package/ARCHITECTURE.md +7 -7
  2. package/README.md +2 -23
  3. package/docs/architecture/integrations.md +45 -41
  4. package/docs/architecture/keychain-broker.md +3 -3
  5. package/docs/architecture/security.md +5 -5
  6. package/docs/runbook-trusted-contacts.md +3 -8
  7. package/hook-templates/debug-prompt-logger/hook.json +1 -1
  8. package/hook-templates/debug-prompt-logger/run.sh +1 -3
  9. package/package.json +1 -1
  10. package/src/__tests__/actor-token-service.test.ts +0 -1
  11. package/src/__tests__/anthropic-provider.test.ts +156 -0
  12. package/src/__tests__/approval-cascade.test.ts +810 -0
  13. package/src/__tests__/approval-primitive.test.ts +0 -1
  14. package/src/__tests__/approval-routes-http.test.ts +2 -0
  15. package/src/__tests__/assistant-attachments.test.ts +12 -34
  16. package/src/__tests__/assistant-feature-flag-guardrails.test.ts +76 -0
  17. package/src/__tests__/assistant-feature-flags-integration.test.ts +0 -1
  18. package/src/__tests__/browser-fill-credential.test.ts +5 -2
  19. package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +2 -2
  20. package/src/__tests__/bundled-skill-retrieval-guard.test.ts +2 -1
  21. package/src/__tests__/channel-guardian.test.ts +0 -2
  22. package/src/__tests__/channel-readiness-routes.test.ts +35 -25
  23. package/src/__tests__/channel-readiness-service.test.ts +10 -9
  24. package/src/__tests__/checker.test.ts +9 -29
  25. package/src/__tests__/cli.test.ts +23 -0
  26. package/src/__tests__/computer-use-skill-manifest-regression.test.ts +1 -1
  27. package/src/__tests__/computer-use-tools.test.ts +2 -19
  28. package/src/__tests__/config-watcher.test.ts +0 -1
  29. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +0 -1
  30. package/src/__tests__/context-image-dimensions.test.ts +332 -0
  31. package/src/__tests__/context-token-estimator.test.ts +196 -13
  32. package/src/__tests__/conversation-attention-store.test.ts +0 -1
  33. package/src/__tests__/conversation-attention-telegram.test.ts +0 -1
  34. package/src/__tests__/conversation-routes-guardian-reply.test.ts +144 -0
  35. package/src/__tests__/conversation-routes-slash-commands.test.ts +1 -0
  36. package/src/__tests__/credential-broker-browser-fill.test.ts +23 -22
  37. package/src/__tests__/credential-broker-server-use.test.ts +22 -21
  38. package/src/__tests__/credential-broker.test.ts +2 -1
  39. package/src/__tests__/credential-metadata-store.test.ts +239 -26
  40. package/src/__tests__/credential-resolve.test.ts +5 -4
  41. package/src/__tests__/credential-security-e2e.test.ts +8 -8
  42. package/src/__tests__/credential-security-invariants.test.ts +111 -7
  43. package/src/__tests__/credential-vault-unit.test.ts +287 -54
  44. package/src/__tests__/credential-vault.test.ts +406 -12
  45. package/src/__tests__/credentials-cli.test.ts +82 -6
  46. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +0 -1
  47. package/src/__tests__/ephemeral-permissions.test.ts +3 -3
  48. package/src/__tests__/gateway-only-enforcement.test.ts +4 -2
  49. package/src/__tests__/gateway-only-guard.test.ts +0 -1
  50. package/src/__tests__/gemini-image-service.test.ts +75 -45
  51. package/src/__tests__/gemini-provider.test.ts +9 -6
  52. package/src/__tests__/guardian-action-conversation-turn.test.ts +1 -33
  53. package/src/__tests__/guardian-action-copy-generator.test.ts +0 -20
  54. package/src/__tests__/guardian-action-followup-executor.test.ts +1 -28
  55. package/src/__tests__/guardian-action-followup-store.test.ts +1 -1
  56. package/src/__tests__/guardian-action-grant-mint-consume.test.ts +0 -1
  57. package/src/__tests__/guardian-decision-primitive-canonical.test.ts +0 -1
  58. package/src/__tests__/guardian-grant-minting.test.ts +35 -0
  59. package/src/__tests__/guardian-routing-invariants.test.ts +0 -1
  60. package/src/__tests__/guardian-verification-voice-binding.test.ts +0 -1
  61. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +0 -39
  62. package/src/__tests__/heartbeat-service.test.ts +0 -1
  63. package/src/__tests__/host-cu-proxy.test.ts +629 -0
  64. package/src/__tests__/host-shell-tool.test.ts +27 -15
  65. package/src/__tests__/http-user-message-parity.test.ts +1 -0
  66. package/src/__tests__/ingress-url-consistency.test.ts +14 -21
  67. package/src/__tests__/integration-status.test.ts +38 -25
  68. package/src/__tests__/intent-routing.test.ts +0 -1
  69. package/src/__tests__/invite-routes-http.test.ts +10 -9
  70. package/src/__tests__/keychain-broker-client.test.ts +11 -43
  71. package/src/__tests__/managed-proxy-context.test.ts +5 -3
  72. package/src/__tests__/media-generate-image.test.ts +63 -2
  73. package/src/__tests__/media-reuse-story.e2e.test.ts +7 -3
  74. package/src/__tests__/messaging-send-tool.test.ts +4 -6
  75. package/src/__tests__/notification-routing-intent.test.ts +0 -1
  76. package/src/__tests__/oauth-cli.test.ts +373 -14
  77. package/src/__tests__/oauth-provider-profiles.test.ts +9 -9
  78. package/src/__tests__/oauth-scope-policy.test.ts +4 -6
  79. package/src/__tests__/oauth-store.test.ts +756 -0
  80. package/src/__tests__/onboarding-starter-tasks.test.ts +0 -1
  81. package/src/__tests__/provider-error-scenarios.test.ts +0 -1
  82. package/src/__tests__/provider-fail-open-selection.test.ts +3 -1
  83. package/src/__tests__/provider-managed-proxy-integration.test.ts +70 -6
  84. package/src/__tests__/provider-streaming.benchmark.test.ts +0 -1
  85. package/src/__tests__/public-ingress-urls.test.ts +15 -21
  86. package/src/__tests__/recording-handler.test.ts +3 -4
  87. package/src/__tests__/registry.test.ts +2 -2
  88. package/src/__tests__/runtime-events-sse.test.ts +55 -7
  89. package/src/__tests__/schedule-store.test.ts +0 -1
  90. package/src/__tests__/scheduler-recurrence.test.ts +0 -1
  91. package/src/__tests__/schema-transforms.test.ts +226 -0
  92. package/src/__tests__/scoped-approval-grants.test.ts +0 -1
  93. package/src/__tests__/scoped-grant-security-matrix.test.ts +0 -1
  94. package/src/__tests__/script-proxy-injection-runtime.test.ts +23 -13
  95. package/src/__tests__/script-proxy-policy-runtime.test.ts +1 -1
  96. package/src/__tests__/script-proxy-session-manager.test.ts +1 -1
  97. package/src/__tests__/secret-ingress-handler.test.ts +0 -1
  98. package/src/__tests__/secret-onetime-send.test.ts +5 -3
  99. package/src/__tests__/send-endpoint-busy.test.ts +21 -6
  100. package/src/__tests__/sequence-store.test.ts +0 -1
  101. package/src/__tests__/session-init.benchmark.test.ts +4 -5
  102. package/src/__tests__/session-messaging-secret-redirect.test.ts +5 -4
  103. package/src/__tests__/skill-include-graph.test.ts +66 -0
  104. package/src/__tests__/skill-load-feature-flag.test.ts +0 -1
  105. package/src/__tests__/skill-load-tool.test.ts +149 -1
  106. package/src/__tests__/skill-projection-feature-flag.test.ts +0 -1
  107. package/src/__tests__/skills-uninstall.test.ts +3 -3
  108. package/src/__tests__/skills.test.ts +3 -12
  109. package/src/__tests__/slack-channel-config.test.ts +76 -11
  110. package/src/__tests__/slack-share-routes.test.ts +17 -14
  111. package/src/__tests__/system-prompt.test.ts +0 -1
  112. package/src/__tests__/telegram-bot-username-resolution.test.ts +3 -0
  113. package/src/__tests__/telegram-invite-adapter.test.ts +18 -22
  114. package/src/__tests__/terminal-tools.test.ts +4 -3
  115. package/src/__tests__/test-support/computer-use-skill-harness.ts +3 -2
  116. package/src/__tests__/tool-approval-handler.test.ts +0 -1
  117. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +0 -1
  118. package/src/__tests__/tool-executor-lifecycle-events.test.ts +0 -1
  119. package/src/__tests__/tool-executor-shell-integration.test.ts +0 -1
  120. package/src/__tests__/tool-executor.test.ts +0 -1
  121. package/src/__tests__/tool-grant-request-escalation.test.ts +0 -1
  122. package/src/__tests__/trust-store-pattern-matches.test.ts +29 -0
  123. package/src/__tests__/trust-store.test.ts +1 -22
  124. package/src/__tests__/trusted-contact-approval-notifier.test.ts +0 -1
  125. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +0 -1
  126. package/src/__tests__/twilio-config.test.ts +2 -1
  127. package/src/__tests__/twilio-provider.test.ts +4 -2
  128. package/src/__tests__/twilio-routes.test.ts +5 -20
  129. package/src/__tests__/verification-control-plane-policy.test.ts +0 -1
  130. package/src/__tests__/voice-scoped-grant-consumer.test.ts +0 -1
  131. package/src/agent/ax-tree-compaction.test.ts +235 -0
  132. package/src/agent/loop.ts +76 -130
  133. package/src/calls/call-domain.ts +8 -10
  134. package/src/calls/relay-server.ts +9 -13
  135. package/src/calls/twilio-config.ts +4 -8
  136. package/src/calls/twilio-provider.ts +2 -1
  137. package/src/calls/twilio-rest.ts +2 -1
  138. package/src/calls/twilio-routes.ts +1 -2
  139. package/src/calls/voice-ingress-preflight.ts +1 -1
  140. package/src/cli/commands/browser-relay.ts +46 -15
  141. package/src/cli/commands/completions.ts +0 -3
  142. package/src/cli/commands/credentials.ts +110 -23
  143. package/src/cli/commands/oauth/apps.ts +255 -0
  144. package/src/cli/commands/oauth/connections.ts +299 -0
  145. package/src/cli/commands/oauth/index.ts +52 -0
  146. package/src/cli/commands/oauth/providers.ts +242 -0
  147. package/src/cli/commands/skills.ts +4 -338
  148. package/src/cli/program.ts +1 -5
  149. package/src/cli/reference.ts +1 -3
  150. package/src/cli.ts +3 -2
  151. package/src/config/assistant-feature-flags.ts +0 -3
  152. package/src/config/bundled-skills/_shared/CLI_RETRIEVAL_PATTERN.md +1 -1
  153. package/src/config/bundled-skills/claude-code/TOOLS.json +0 -4
  154. package/src/config/bundled-skills/computer-use/SKILL.md +3 -6
  155. package/src/config/bundled-skills/computer-use/TOOLS.json +22 -4
  156. package/src/config/bundled-skills/contacts/tools/google-contacts.ts +29 -32
  157. package/src/config/bundled-skills/gmail/SKILL.md +4 -4
  158. package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +54 -61
  159. package/src/config/bundled-skills/gmail/tools/gmail-attachments.ts +25 -28
  160. package/src/config/bundled-skills/gmail/tools/gmail-draft.ts +14 -17
  161. package/src/config/bundled-skills/gmail/tools/gmail-filters.ts +39 -44
  162. package/src/config/bundled-skills/gmail/tools/gmail-follow-up.ts +61 -58
  163. package/src/config/bundled-skills/gmail/tools/gmail-forward.ts +50 -49
  164. package/src/config/bundled-skills/gmail/tools/gmail-label.ts +11 -13
  165. package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +148 -146
  166. package/src/config/bundled-skills/gmail/tools/gmail-send-draft.ts +4 -7
  167. package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +175 -173
  168. package/src/config/bundled-skills/gmail/tools/gmail-trash.ts +4 -7
  169. package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +71 -76
  170. package/src/config/bundled-skills/gmail/tools/gmail-vacation.ts +32 -38
  171. package/src/config/bundled-skills/google-calendar/SKILL.md +2 -2
  172. package/src/config/bundled-skills/google-calendar/calendar-client.ts +90 -44
  173. package/src/config/bundled-skills/google-calendar/tools/calendar-check-availability.ts +9 -10
  174. package/src/config/bundled-skills/google-calendar/tools/calendar-create-event.ts +5 -6
  175. package/src/config/bundled-skills/google-calendar/tools/calendar-get-event.ts +4 -5
  176. package/src/config/bundled-skills/google-calendar/tools/calendar-list-events.ts +14 -15
  177. package/src/config/bundled-skills/google-calendar/tools/calendar-rsvp.ts +37 -37
  178. package/src/config/bundled-skills/google-calendar/tools/shared.ts +4 -9
  179. package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +24 -3
  180. package/src/config/bundled-skills/messaging/SKILL.md +6 -6
  181. package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +62 -63
  182. package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +15 -16
  183. package/src/config/bundled-skills/messaging/tools/messaging-auth-test.ts +4 -5
  184. package/src/config/bundled-skills/messaging/tools/messaging-list-conversations.ts +6 -7
  185. package/src/config/bundled-skills/messaging/tools/messaging-mark-read.ts +4 -5
  186. package/src/config/bundled-skills/messaging/tools/messaging-read.ts +14 -15
  187. package/src/config/bundled-skills/messaging/tools/messaging-search.ts +4 -5
  188. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +128 -128
  189. package/src/config/bundled-skills/messaging/tools/messaging-sender-digest.ts +33 -34
  190. package/src/config/bundled-skills/messaging/tools/shared.ts +12 -15
  191. package/src/config/bundled-skills/settings/SKILL.md +1 -1
  192. package/src/config/bundled-skills/settings/TOOLS.json +2 -8
  193. package/src/config/bundled-skills/settings/tools/voice-config-update.ts +5 -33
  194. package/src/config/bundled-skills/slack/tools/shared.ts +4 -10
  195. package/src/config/bundled-skills/slack/tools/slack-add-reaction.ts +4 -5
  196. package/src/config/bundled-skills/slack/tools/slack-channel-details.ts +15 -16
  197. package/src/config/bundled-skills/slack/tools/slack-delete-message.ts +4 -5
  198. package/src/config/bundled-skills/slack/tools/slack-edit-message.ts +4 -5
  199. package/src/config/bundled-skills/slack/tools/slack-leave-channel.ts +4 -5
  200. package/src/config/bundled-skills/slack/tools/slack-scan-digest.ts +95 -92
  201. package/src/config/env-registry.ts +14 -83
  202. package/src/config/env.ts +11 -50
  203. package/src/config/feature-flag-registry.json +16 -16
  204. package/src/config/schema.ts +3 -1
  205. package/src/config/skills.ts +21 -2
  206. package/src/context/image-dimensions.ts +229 -0
  207. package/src/context/token-estimator.ts +75 -12
  208. package/src/context/window-manager.ts +49 -10
  209. package/src/daemon/assistant-attachments.ts +1 -13
  210. package/src/daemon/guardian-action-generators.ts +4 -5
  211. package/src/daemon/handlers/config-ingress.ts +8 -33
  212. package/src/daemon/handlers/config-slack-channel.ts +76 -56
  213. package/src/daemon/handlers/config-telegram.ts +53 -24
  214. package/src/daemon/handlers/sessions.ts +10 -24
  215. package/src/daemon/handlers/shared.ts +0 -130
  216. package/src/daemon/host-cu-proxy.ts +401 -0
  217. package/src/daemon/lifecycle.ts +39 -63
  218. package/src/daemon/message-protocol.ts +3 -0
  219. package/src/daemon/message-types/computer-use.ts +2 -119
  220. package/src/daemon/message-types/host-cu.ts +19 -0
  221. package/src/daemon/message-types/integrations.ts +1 -0
  222. package/src/daemon/message-types/messages.ts +3 -0
  223. package/src/daemon/server.ts +14 -21
  224. package/src/daemon/session-agent-loop-handlers.ts +2 -0
  225. package/src/daemon/session-attachments.ts +1 -2
  226. package/src/daemon/session-messaging.ts +3 -1
  227. package/src/daemon/session-slash.ts +1 -1
  228. package/src/daemon/session-surfaces.ts +40 -28
  229. package/src/daemon/session-tool-setup.ts +20 -11
  230. package/src/daemon/session.ts +139 -16
  231. package/src/daemon/tool-side-effects.ts +2 -8
  232. package/src/daemon/watch-handler.ts +2 -2
  233. package/src/email/providers/index.ts +2 -1
  234. package/src/events/tool-metrics-listener.ts +2 -2
  235. package/src/hooks/manager.ts +1 -4
  236. package/src/inbound/public-ingress-urls.ts +7 -7
  237. package/src/instrument.ts +15 -1
  238. package/src/logfire.ts +16 -5
  239. package/src/media/app-icon-generator.ts +30 -4
  240. package/src/media/avatar-router.ts +26 -3
  241. package/src/media/gemini-image-service.ts +28 -2
  242. package/src/memory/conversation-key-store.ts +21 -0
  243. package/src/memory/db-init.ts +4 -0
  244. package/src/memory/guardian-action-store.ts +1 -1
  245. package/src/memory/migrations/149-oauth-tables.ts +60 -0
  246. package/src/memory/migrations/index.ts +1 -0
  247. package/src/memory/schema/guardian.ts +1 -1
  248. package/src/memory/schema/index.ts +1 -0
  249. package/src/memory/schema/oauth.ts +65 -0
  250. package/src/messaging/provider.ts +19 -13
  251. package/src/messaging/providers/gmail/adapter.ts +40 -23
  252. package/src/messaging/providers/gmail/client.ts +283 -122
  253. package/src/messaging/providers/gmail/people-client.ts +32 -24
  254. package/src/messaging/providers/slack/adapter.ts +29 -19
  255. package/src/messaging/providers/slack/client.ts +265 -78
  256. package/src/messaging/providers/telegram-bot/adapter.ts +19 -18
  257. package/src/messaging/providers/whatsapp/adapter.ts +17 -11
  258. package/src/messaging/registry.ts +2 -31
  259. package/src/notifications/copy-composer.ts +0 -5
  260. package/src/notifications/signal.ts +4 -5
  261. package/src/oauth/byo-connection.test.ts +537 -0
  262. package/src/oauth/byo-connection.ts +128 -0
  263. package/src/oauth/connect-orchestrator.ts +139 -56
  264. package/src/oauth/connect-types.ts +17 -23
  265. package/src/oauth/connection-resolver.ts +58 -0
  266. package/src/oauth/connection.ts +38 -0
  267. package/src/oauth/manual-token-connection.ts +104 -0
  268. package/src/oauth/oauth-store.ts +496 -0
  269. package/src/oauth/platform-connection.test.ts +192 -0
  270. package/src/oauth/platform-connection.ts +111 -0
  271. package/src/oauth/provider-behaviors.ts +124 -0
  272. package/src/oauth/scope-policy.ts +9 -2
  273. package/src/oauth/seed-providers.ts +161 -0
  274. package/src/oauth/token-persistence.ts +74 -78
  275. package/src/permissions/checker.ts +8 -4
  276. package/src/permissions/defaults.ts +0 -1
  277. package/src/permissions/prompter.ts +10 -1
  278. package/src/permissions/trust-store.ts +13 -0
  279. package/src/prompts/__tests__/build-cli-reference-section.test.ts +3 -1
  280. package/src/prompts/system-prompt.ts +70 -45
  281. package/src/providers/anthropic/client.ts +133 -24
  282. package/src/providers/gemini/client.ts +15 -6
  283. package/src/providers/managed-proxy/constants.ts +2 -2
  284. package/src/providers/managed-proxy/context.ts +5 -1
  285. package/src/providers/ratelimit.ts +17 -0
  286. package/src/providers/registry.ts +2 -2
  287. package/src/providers/retry.ts +1 -27
  288. package/src/runtime/AGENTS.md +17 -0
  289. package/src/runtime/auth/route-policy.ts +0 -3
  290. package/src/runtime/channel-invite-transports/telegram.ts +2 -1
  291. package/src/runtime/channel-readiness-service.ts +168 -195
  292. package/src/runtime/channel-readiness-types.ts +4 -0
  293. package/src/runtime/channel-reply-delivery.ts +0 -40
  294. package/src/runtime/gateway-client.ts +0 -7
  295. package/src/runtime/guardian-action-conversation-turn.ts +1 -3
  296. package/src/runtime/guardian-action-followup-executor.ts +1 -1
  297. package/src/runtime/guardian-action-message-composer.ts +3 -23
  298. package/src/runtime/http-server.ts +17 -10
  299. package/src/runtime/http-types.ts +2 -3
  300. package/src/runtime/middleware/rate-limiter.ts +74 -20
  301. package/src/runtime/middleware/twilio-validation.ts +1 -11
  302. package/src/runtime/pending-interactions.ts +14 -12
  303. package/src/runtime/routes/channel-delivery-routes.ts +0 -1
  304. package/src/runtime/routes/channel-readiness-routes.ts +2 -0
  305. package/src/runtime/routes/conversation-routes.ts +73 -19
  306. package/src/runtime/routes/diagnostics-routes.ts +11 -9
  307. package/src/runtime/routes/events-routes.ts +21 -11
  308. package/src/runtime/routes/guardian-approval-interception.ts +20 -5
  309. package/src/runtime/routes/host-cu-routes.ts +97 -0
  310. package/src/runtime/routes/inbound-stages/background-dispatch.ts +12 -111
  311. package/src/runtime/routes/integrations/slack/share.ts +6 -6
  312. package/src/runtime/routes/integrations/twilio.ts +6 -5
  313. package/src/runtime/routes/log-export-routes.ts +126 -8
  314. package/src/runtime/routes/secret-routes.ts +3 -2
  315. package/src/runtime/routes/settings-routes.ts +113 -48
  316. package/src/runtime/routes/surface-action-routes.ts +1 -1
  317. package/src/runtime/routes/watch-routes.ts +128 -0
  318. package/src/schedule/integration-status.ts +10 -8
  319. package/src/security/credential-key.ts +14 -0
  320. package/src/security/keychain-broker-client.ts +5 -6
  321. package/src/security/oauth2.ts +1 -1
  322. package/src/security/token-manager.ts +145 -43
  323. package/src/skills/catalog-install.ts +358 -0
  324. package/src/skills/include-graph.ts +32 -0
  325. package/src/telegram/bot-username.ts +2 -3
  326. package/src/tools/apps/definitions.ts +0 -5
  327. package/src/tools/assets/materialize.ts +0 -5
  328. package/src/tools/assets/search.ts +0 -5
  329. package/src/tools/browser/headless-browser.ts +1 -67
  330. package/src/tools/browser/network-recorder.ts +1 -1
  331. package/src/tools/browser/network-recording-types.ts +1 -1
  332. package/src/tools/claude-code/claude-code.ts +0 -5
  333. package/src/tools/computer-use/definitions.ts +46 -11
  334. package/src/tools/computer-use/registry.ts +4 -5
  335. package/src/tools/credentials/broker.ts +5 -4
  336. package/src/tools/credentials/metadata-store.ts +22 -74
  337. package/src/tools/credentials/resolve.ts +2 -1
  338. package/src/tools/credentials/vault.ts +139 -151
  339. package/src/tools/filesystem/edit.ts +1 -6
  340. package/src/tools/filesystem/read.ts +0 -5
  341. package/src/tools/filesystem/write.ts +1 -6
  342. package/src/tools/host-filesystem/edit.ts +1 -6
  343. package/src/tools/host-filesystem/read.ts +1 -6
  344. package/src/tools/host-filesystem/write.ts +1 -6
  345. package/src/tools/mcp/mcp-tool-factory.ts +18 -1
  346. package/src/tools/memory/definitions.ts +0 -5
  347. package/src/tools/network/web-fetch.ts +0 -5
  348. package/src/tools/network/web-search.ts +0 -5
  349. package/src/tools/registry.ts +2 -7
  350. package/src/tools/schema-transforms.ts +99 -0
  351. package/src/tools/skills/load.ts +62 -8
  352. package/src/tools/swarm/delegate.ts +0 -5
  353. package/src/tools/system/avatar-generator.ts +0 -5
  354. package/src/tools/ui-surface/definitions.ts +0 -15
  355. package/src/tools/watch/screen-watch.ts +0 -5
  356. package/src/tools/watch/watch-state.ts +0 -12
  357. package/src/util/logger.ts +7 -41
  358. package/src/util/platform.ts +9 -28
  359. package/src/version.ts +10 -0
  360. package/src/watcher/providers/github.ts +51 -52
  361. package/src/watcher/providers/gmail.ts +88 -80
  362. package/src/watcher/providers/google-calendar.ts +94 -86
  363. package/src/watcher/providers/linear.ts +87 -93
  364. package/src/__tests__/computer-use-session-compaction.test.ts +0 -143
  365. package/src/__tests__/computer-use-session-lifecycle.test.ts +0 -322
  366. package/src/__tests__/computer-use-session-working-dir.test.ts +0 -166
  367. package/src/__tests__/computer-use-skill-baseline.test.ts +0 -78
  368. package/src/__tests__/computer-use-skill-endstate.test.ts +0 -105
  369. package/src/__tests__/computer-use-skill-lifecycle-cleanup.test.ts +0 -249
  370. package/src/__tests__/ride-shotgun-handler.test.ts +0 -452
  371. package/src/cli/commands/dev.ts +0 -129
  372. package/src/cli/commands/map.ts +0 -391
  373. package/src/cli/commands/oauth.ts +0 -77
  374. package/src/config/bundled-skills/computer-use/tools/computer-use-request-control.ts +0 -16
  375. package/src/daemon/computer-use-session.ts +0 -1020
  376. package/src/daemon/ride-shotgun-handler.ts +0 -567
  377. package/src/oauth/provider-profiles.ts +0 -192
  378. package/src/prompts/computer-use-prompt.ts +0 -98
  379. package/src/runtime/routes/computer-use-routes.ts +0 -641
  380. package/src/runtime/telegram-streaming-delivery.test.ts +0 -597
  381. package/src/runtime/telegram-streaming-delivery.ts +0 -383
  382. package/src/tools/computer-use/request-computer-control.ts +0 -61
@@ -47,12 +47,12 @@ function readMockTwilioAccountSid(): string | undefined {
47
47
  const twilio = (mockRawConfigStore.twilio ?? {}) as Record<string, unknown>;
48
48
  return (
49
49
  (twilio.accountSid as string | undefined) ??
50
- mockSecureKeyStore["credential:twilio:account_sid"]
50
+ mockSecureKeyStore[credentialKey("twilio", "account_sid")]
51
51
  );
52
52
  }
53
53
 
54
54
  function readMockTwilioAuthToken(): string | undefined {
55
- return mockSecureKeyStore["credential:twilio:auth_token"];
55
+ return mockSecureKeyStore[credentialKey("twilio", "auth_token")];
56
56
  }
57
57
 
58
58
  function readMockTwilioPhoneNumber(): string | undefined {
@@ -77,7 +77,6 @@ function resolveIngressBaseUrlFromConfig(ingressConfig: unknown): string {
77
77
 
78
78
  mock.module("../config/env.js", () => ({
79
79
  isHttpAuthDisabled: () => true,
80
- getCallWelcomeGreeting: () => process.env.CALL_WELCOME_GREETING || undefined,
81
80
  getGatewayInternalBaseUrl: () => "http://gateway.internal:7830",
82
81
  getIngressPublicBaseUrl: () => mockIngressPublicBaseUrl,
83
82
  setIngressPublicBaseUrl: (value: string | undefined) => {
@@ -109,13 +108,11 @@ mock.module("../util/logger.js", () => ({
109
108
  debug: () => {},
110
109
  trace: () => {},
111
110
  fatal: () => {},
112
- isDebug: () => false,
113
111
  child: () => ({
114
112
  info: () => {},
115
113
  warn: () => {},
116
114
  error: () => {},
117
115
  debug: () => {},
118
- isDebug: () => false,
119
116
  }),
120
117
  }),
121
118
  }));
@@ -333,6 +330,7 @@ import {
333
330
  handleProvisionTwilioNumber,
334
331
  handleSetTwilioCredentials,
335
332
  } from "../runtime/routes/integrations/twilio.js";
333
+ import { credentialKey } from "../security/credential-key.js";
336
334
 
337
335
  initializeDb();
338
336
 
@@ -429,15 +427,14 @@ describe("twilio webhook routes", () => {
429
427
  twilio: { accountSid: "AC_existing", phoneNumber: "+15550001111" },
430
428
  };
431
429
  mockSecureKeyStore = {
432
- "credential:twilio:account_sid": "AC_existing",
433
- "credential:twilio:auth_token": "test-auth-token",
430
+ [credentialKey("twilio", "account_sid")]: "AC_existing",
431
+ [credentialKey("twilio", "auth_token")]: "test-auth-token",
434
432
  };
435
433
  mockAvailableNumbers = [{ phoneNumber: "+15556667777" }];
436
434
  mockProvisionedNumber = { phoneNumber: "+15556667777" };
437
435
  updatePhoneNumberWebhookCalls = [];
438
436
  mockTwilioApiValidationStatus = 200;
439
437
  mockTwilioApiValidationBody = JSON.stringify({ sid: "AC_validated" });
440
- delete process.env.CALL_WELCOME_GREETING;
441
438
 
442
439
  globalThis.fetch = (async (
443
440
  url: string | URL | Request,
@@ -818,18 +815,6 @@ describe("twilio webhook routes", () => {
818
815
  const twiml = await res.text();
819
816
  expect(twiml).not.toContain("welcomeGreeting=");
820
817
  });
821
-
822
- test("TwiML includes explicit welcome greeting override when configured", async () => {
823
- process.env.CALL_WELCOME_GREETING = "Custom transport greeting";
824
- const session = createTestSession("conv-twiml-4", "CA_twiml_4");
825
- const req = makeVoiceRequest(session.id, { CallSid: "CA_twiml_4" });
826
-
827
- const res = await handleVoiceWebhook(req);
828
-
829
- expect(res.status).toBe(200);
830
- const twiml = await res.text();
831
- expect(twiml).toContain('welcomeGreeting="Custom transport greeting"');
832
- });
833
818
  });
834
819
 
835
820
  // ── Handler-level idempotency concurrency tests ─────────────────
@@ -64,7 +64,6 @@ mock.module("../util/logger.js", () => ({
64
64
  new Proxy({} as Record<string, unknown>, {
65
65
  get: () => () => {},
66
66
  }),
67
- isDebug: () => false,
68
67
  truncateForLog: (value: string) => value,
69
68
  }));
70
69
 
@@ -44,7 +44,6 @@ mock.module("../util/logger.js", () => ({
44
44
  new Proxy({} as Record<string, unknown>, {
45
45
  get: () => () => {},
46
46
  }),
47
- isDebug: () => false,
48
47
  truncateForLog: (value: string) => value,
49
48
  }));
50
49
 
@@ -0,0 +1,235 @@
1
+ import { describe, expect, test } from "bun:test";
2
+
3
+ import type { Message } from "../providers/types.js";
4
+ import { compactAxTreeHistory, escapeAxTreeContent } from "./loop.js";
5
+
6
+ // ---------------------------------------------------------------------------
7
+ // Helpers
8
+ // ---------------------------------------------------------------------------
9
+
10
+ /** Build a user message with a single tool_result containing an AX tree. */
11
+ function axTreeToolResult(id: string, axContent: string): Message {
12
+ return {
13
+ role: "user",
14
+ content: [
15
+ {
16
+ type: "tool_result",
17
+ tool_use_id: id,
18
+ content: `Some preamble\n<ax-tree>\n${axContent}\n</ax-tree>`,
19
+ is_error: false,
20
+ },
21
+ ],
22
+ };
23
+ }
24
+
25
+ /** Build an assistant message (no tool use). */
26
+ function assistantText(text: string): Message {
27
+ return {
28
+ role: "assistant",
29
+ content: [{ type: "text", text }],
30
+ };
31
+ }
32
+
33
+ /** Build a user message without AX tree content. */
34
+ function userText(text: string): Message {
35
+ return {
36
+ role: "user",
37
+ content: [{ type: "text", text }],
38
+ };
39
+ }
40
+
41
+ // ---------------------------------------------------------------------------
42
+ // compactAxTreeHistory
43
+ // ---------------------------------------------------------------------------
44
+
45
+ describe("compactAxTreeHistory", () => {
46
+ test("returns messages unchanged when fewer than MAX_AX_TREES_IN_HISTORY AX trees", () => {
47
+ const messages: Message[] = [
48
+ axTreeToolResult("t1", "tree-1"),
49
+ assistantText("ok"),
50
+ axTreeToolResult("t2", "tree-2"),
51
+ ];
52
+ const result = compactAxTreeHistory(messages);
53
+ expect(result).toBe(messages); // same reference — no copy
54
+ });
55
+
56
+ test("returns messages unchanged when exactly MAX_AX_TREES_IN_HISTORY AX trees", () => {
57
+ const messages: Message[] = [
58
+ axTreeToolResult("t1", "tree-1"),
59
+ assistantText("ok"),
60
+ axTreeToolResult("t2", "tree-2"),
61
+ ];
62
+ const result = compactAxTreeHistory(messages);
63
+ expect(result).toBe(messages);
64
+ });
65
+
66
+ test("strips oldest AX trees, keeps only last 2 from 5", () => {
67
+ const messages: Message[] = [
68
+ axTreeToolResult("t1", "tree-1"),
69
+ assistantText("ok"),
70
+ axTreeToolResult("t2", "tree-2"),
71
+ assistantText("ok"),
72
+ axTreeToolResult("t3", "tree-3"),
73
+ assistantText("ok"),
74
+ axTreeToolResult("t4", "tree-4"),
75
+ assistantText("ok"),
76
+ axTreeToolResult("t5", "tree-5"),
77
+ ];
78
+
79
+ const result = compactAxTreeHistory(messages);
80
+
81
+ // Messages at indices 0, 2, 4 should have AX trees stripped (t1, t2, t3)
82
+ for (const idx of [0, 2, 4]) {
83
+ const block = result[idx].content[0];
84
+ expect(block.type).toBe("tool_result");
85
+ if (block.type === "tool_result") {
86
+ expect(block.content).not.toContain("<ax-tree>");
87
+ expect(block.content).toContain("<ax_tree_omitted />");
88
+ }
89
+ }
90
+
91
+ // Messages at indices 6, 8 should still have AX trees (t4, t5)
92
+ for (const idx of [6, 8]) {
93
+ const block = result[idx].content[0];
94
+ expect(block.type).toBe("tool_result");
95
+ if (block.type === "tool_result") {
96
+ expect(block.content).toContain("<ax-tree>");
97
+ expect(block.content).not.toContain("<ax_tree_omitted />");
98
+ }
99
+ }
100
+ });
101
+
102
+ test("does not modify assistant messages", () => {
103
+ const messages: Message[] = [
104
+ axTreeToolResult("t1", "tree-1"),
105
+ assistantText("ok"),
106
+ axTreeToolResult("t2", "tree-2"),
107
+ assistantText("response with <ax-tree>fake</ax-tree>"),
108
+ axTreeToolResult("t3", "tree-3"),
109
+ ];
110
+
111
+ const result = compactAxTreeHistory(messages);
112
+
113
+ // Assistant message should be unchanged
114
+ const assistantMsg = result[3];
115
+ expect(assistantMsg.content[0].type).toBe("text");
116
+ if (assistantMsg.content[0].type === "text") {
117
+ expect(assistantMsg.content[0].text).toContain("<ax-tree>");
118
+ }
119
+ });
120
+
121
+ test("does not modify user messages without tool_result blocks", () => {
122
+ const messages: Message[] = [
123
+ axTreeToolResult("t1", "tree-1"),
124
+ assistantText("ok"),
125
+ axTreeToolResult("t2", "tree-2"),
126
+ assistantText("ok"),
127
+ userText("Please help"),
128
+ axTreeToolResult("t3", "tree-3"),
129
+ ];
130
+
131
+ const result = compactAxTreeHistory(messages);
132
+
133
+ // The plain user text message should be untouched
134
+ expect(result[4]).toBe(messages[4]);
135
+ });
136
+
137
+ test("preserves non-AX-tree tool_result blocks in stripped messages", () => {
138
+ const messages: Message[] = [
139
+ {
140
+ role: "user",
141
+ content: [
142
+ {
143
+ type: "tool_result",
144
+ tool_use_id: "t1",
145
+ content: "normal result without ax tree",
146
+ is_error: false,
147
+ },
148
+ {
149
+ type: "tool_result",
150
+ tool_use_id: "t1-ax",
151
+ content: "<ax-tree>\ntree-1\n</ax-tree>",
152
+ is_error: false,
153
+ },
154
+ ],
155
+ },
156
+ assistantText("ok"),
157
+ axTreeToolResult("t2", "tree-2"),
158
+ assistantText("ok"),
159
+ axTreeToolResult("t3", "tree-3"),
160
+ ];
161
+
162
+ const result = compactAxTreeHistory(messages);
163
+
164
+ // First message should have the AX tree stripped but normal result preserved
165
+ const firstMsg = result[0];
166
+ const normalBlock = firstMsg.content[0];
167
+ expect(normalBlock.type).toBe("tool_result");
168
+ if (normalBlock.type === "tool_result") {
169
+ expect(normalBlock.content).toBe("normal result without ax tree");
170
+ }
171
+
172
+ const axBlock = firstMsg.content[1];
173
+ expect(axBlock.type).toBe("tool_result");
174
+ if (axBlock.type === "tool_result") {
175
+ expect(axBlock.content).toContain("<ax_tree_omitted />");
176
+ expect(axBlock.content).not.toContain("<ax-tree>");
177
+ }
178
+ });
179
+
180
+ test("returns empty array for empty input", () => {
181
+ const result = compactAxTreeHistory([]);
182
+ expect(result).toEqual([]);
183
+ });
184
+
185
+ test("is pure — does not mutate input messages", () => {
186
+ const messages: Message[] = [
187
+ axTreeToolResult("t1", "tree-1"),
188
+ assistantText("ok"),
189
+ axTreeToolResult("t2", "tree-2"),
190
+ assistantText("ok"),
191
+ axTreeToolResult("t3", "tree-3"),
192
+ ];
193
+
194
+ // Deep copy to compare later
195
+ const originalContent = messages[0].content[0];
196
+ const originalText =
197
+ originalContent.type === "tool_result" ? originalContent.content : "";
198
+
199
+ compactAxTreeHistory(messages);
200
+
201
+ // Original message should be unchanged
202
+ const afterContent = messages[0].content[0];
203
+ const afterText =
204
+ afterContent.type === "tool_result" ? afterContent.content : "";
205
+ expect(afterText).toBe(originalText);
206
+ });
207
+ });
208
+
209
+ // ---------------------------------------------------------------------------
210
+ // escapeAxTreeContent
211
+ // ---------------------------------------------------------------------------
212
+
213
+ describe("escapeAxTreeContent", () => {
214
+ test("escapes literal </ax-tree> inside content", () => {
215
+ const input = "Some XML: <div></ax-tree></div>";
216
+ const result = escapeAxTreeContent(input);
217
+ expect(result).toBe("Some XML: <div>&lt;/ax-tree&gt;</div>");
218
+ });
219
+
220
+ test("handles case-insensitive matches", () => {
221
+ const input = "</AX-TREE> and </Ax-Tree>";
222
+ const result = escapeAxTreeContent(input);
223
+ expect(result).toBe("&lt;/ax-tree&gt; and &lt;/ax-tree&gt;");
224
+ });
225
+
226
+ test("returns content unchanged when no closing tags present", () => {
227
+ const input = "Normal AX tree content with <ax-tree> opening tag";
228
+ const result = escapeAxTreeContent(input);
229
+ expect(result).toBe(input);
230
+ });
231
+
232
+ test("handles empty string", () => {
233
+ expect(escapeAxTreeContent("")).toBe("");
234
+ });
235
+ });
package/src/agent/loop.ts CHANGED
@@ -14,7 +14,7 @@ import {
14
14
  applyStreamingSubstitution,
15
15
  applySubstitutions,
16
16
  } from "../tools/sensitive-output-placeholders.js";
17
- import { getLogger, isDebug, truncateForLog } from "../util/logger.js";
17
+ import { getLogger } from "../util/logger.js";
18
18
 
19
19
  const log = getLogger("agent-loop");
20
20
 
@@ -179,7 +179,6 @@ export class AgentLoop {
179
179
  let toolUseTurns = 0;
180
180
  let nudgedForEmptyResponse = false;
181
181
  let lastLlmCallTime = 0;
182
- const debug = isDebug();
183
182
  const rlog = requestId ? log.child({ requestId }) : log;
184
183
 
185
184
  // Per-run substitution map for sensitive output placeholders.
@@ -191,7 +190,6 @@ export class AgentLoop {
191
190
  while (true) {
192
191
  if (signal?.aborted) break;
193
192
 
194
- const turnStart = Date.now();
195
193
  let toolUseBlocks: Extract<ContentBlock, { type: "tool_use" }>[] = [];
196
194
 
197
195
  try {
@@ -227,22 +225,6 @@ export class AgentLoop {
227
225
  providerConfig.tool_choice = this.config.toolChoice;
228
226
  }
229
227
 
230
- if (debug) {
231
- rlog.debug(
232
- {
233
- systemPrompt: truncateForLog(turnSystemPrompt, 200),
234
- messageCount: history.length,
235
- lastMessage:
236
- history.length > 0
237
- ? summarizeMessage(history[history.length - 1])
238
- : null,
239
- toolCount: currentTools.length,
240
- config: providerConfig,
241
- },
242
- "Sending request to provider",
243
- );
244
- }
245
-
246
228
  const preLlmResult = await getHookManager().trigger("pre-llm-call", {
247
229
  systemPrompt: turnSystemPrompt,
248
230
  messages: history,
@@ -275,7 +257,11 @@ export class AgentLoop {
275
257
  // screenshots from accumulating in the context window. The LLM
276
258
  // already saw each image on the turn it was captured; keeping
277
259
  // base64 blobs in history rapidly exhausts the context budget.
278
- const providerHistory = stripOldImageBlocks(history);
260
+ // Also strip old AX tree snapshots to keep TTFT from growing
261
+ // linearly with step count in computer-use sessions.
262
+ const providerHistory = compactAxTreeHistory(
263
+ stripOldImageBlocks(history),
264
+ );
279
265
 
280
266
  const response = await this.provider.sendMessage(
281
267
  providerHistory,
@@ -328,33 +314,6 @@ export class AgentLoop {
328
314
 
329
315
  const providerDurationMs = Date.now() - providerStart;
330
316
 
331
- if (debug) {
332
- rlog.debug(
333
- {
334
- providerDurationMs,
335
- model: response.model,
336
- stopReason: response.stopReason,
337
- inputTokens: response.usage.inputTokens,
338
- outputTokens: response.usage.outputTokens,
339
- cacheCreationInputTokens: response.usage.cacheCreationInputTokens,
340
- cacheReadInputTokens: response.usage.cacheReadInputTokens,
341
- contentBlocks: response.content.map((b) => ({
342
- type: b.type,
343
- ...(b.type === "text"
344
- ? { text: truncateForLog(b.text, 1200) }
345
- : {}),
346
- ...(b.type === "tool_use"
347
- ? {
348
- name: b.name,
349
- input: truncateForLog(JSON.stringify(b.input), 1200),
350
- }
351
- : {}),
352
- })),
353
- },
354
- "Provider response received",
355
- );
356
- }
357
-
358
317
  onEvent({
359
318
  type: "usage",
360
319
  inputTokens: response.usage.inputTokens,
@@ -443,16 +402,6 @@ export class AgentLoop {
443
402
  name: toolUse.name,
444
403
  input: toolUse.input,
445
404
  });
446
-
447
- if (debug) {
448
- rlog.debug(
449
- {
450
- tool: toolUse.name,
451
- input: truncateForLog(JSON.stringify(toolUse.input), 300),
452
- },
453
- "Executing tool",
454
- );
455
- }
456
405
  }
457
406
 
458
407
  // If already cancelled, synthesize cancelled results and stop
@@ -469,49 +418,11 @@ export class AgentLoop {
469
418
  break;
470
419
  }
471
420
 
472
- // Guard against dual-control-mode conflicts in a single turn.
473
- // If the model escalates to foreground computer control, browser_* tools
474
- // in the same response create competing browser sessions/windows and can
475
- // thrash renderer CPU. Reject browser_* calls in that turn.
476
- const hasComputerUseEscalation = toolUseBlocks.some(
477
- (toolUse) => toolUse.name === "computer_use_request_control",
478
- );
479
- const blockedBrowserToolIds = hasComputerUseEscalation
480
- ? new Set(
481
- toolUseBlocks
482
- .filter((toolUse) => toolUse.name.startsWith("browser_"))
483
- .map((toolUse) => toolUse.id),
484
- )
485
- : new Set<string>();
486
-
487
- if (blockedBrowserToolIds.size > 0) {
488
- log.warn(
489
- {
490
- blockedBrowserToolCount: blockedBrowserToolIds.size,
491
- toolNames: toolUseBlocks.map((toolUse) => toolUse.name),
492
- },
493
- "Blocking browser_* tools: computer_use_request_control was requested in same turn",
494
- );
495
- }
496
-
497
421
  // Execute all tools concurrently for reduced latency.
498
422
  // Race against the abort signal so cancellation isn't blocked by
499
423
  // stuck tools (e.g. a hung browser navigation).
500
424
  const toolExecutionPromise = Promise.all(
501
425
  toolUseBlocks.map(async (toolUse) => {
502
- const toolStart = Date.now();
503
-
504
- if (blockedBrowserToolIds.has(toolUse.id)) {
505
- return {
506
- toolUse,
507
- result: {
508
- content:
509
- "Error: browser_* tools cannot run in the same turn as computer_use_request_control. Continue using the foreground computer-use session only.",
510
- isError: true,
511
- },
512
- };
513
- }
514
-
515
426
  const result = await this.toolExecutor!(
516
427
  toolUse.name,
517
428
  toolUse.input,
@@ -525,20 +436,6 @@ export class AgentLoop {
525
436
  toolUse.id,
526
437
  );
527
438
 
528
- const toolDurationMs = Date.now() - toolStart;
529
-
530
- if (debug) {
531
- rlog.debug(
532
- {
533
- tool: toolUse.name,
534
- toolDurationMs,
535
- isError: result.isError,
536
- output: truncateForLog(result.content, 300),
537
- },
538
- "Tool execution complete",
539
- );
540
- }
541
-
542
439
  return { toolUse, result };
543
440
  }),
544
441
  );
@@ -658,19 +555,6 @@ export class AgentLoop {
658
555
  // Add tool results as a user message and continue the loop
659
556
  history.push({ role: "user", content: resultBlocks });
660
557
 
661
- if (debug) {
662
- const turnDurationMs = Date.now() - turnStart;
663
- rlog.debug(
664
- {
665
- turnDurationMs,
666
- providerDurationMs,
667
- toolCount: toolUseBlocks.length,
668
- turn: toolUseTurns,
669
- },
670
- "Turn complete",
671
- );
672
- }
673
-
674
558
  // Invoke checkpoint callback after tool results are in history
675
559
  if (onCheckpoint) {
676
560
  const decision = onCheckpoint({
@@ -715,14 +599,76 @@ export class AgentLoop {
715
599
  }
716
600
  }
717
601
 
718
- function summarizeMessage(msg: Message): {
719
- role: string;
720
- blockTypes: string[];
721
- } {
722
- return {
723
- role: msg.role,
724
- blockTypes: msg.content.map((b) => b.type),
725
- };
602
+ /** Number of most-recent AX tree snapshots to keep in conversation history. */
603
+ const MAX_AX_TREES_IN_HISTORY = 2;
604
+
605
+ /** Regex that matches the `<ax-tree>...</ax-tree>` markers. */
606
+ const AX_TREE_PATTERN = /<ax-tree>[\s\S]*?<\/ax-tree>/g;
607
+ const AX_TREE_PLACEHOLDER = "<ax_tree_omitted />";
608
+
609
+ /**
610
+ * Escapes any literal `</ax-tree>` occurrences inside AX tree content so
611
+ * that the non-greedy compaction regex (`AX_TREE_PATTERN`) does not stop
612
+ * prematurely when the user happens to be viewing XML/HTML source that
613
+ * contains the closing tag. The escaped content does not need to be
614
+ * unescaped because compaction replaces the entire block with a placeholder.
615
+ */
616
+ export function escapeAxTreeContent(content: string): string {
617
+ return content.replace(/<\/ax-tree>/gi, "&lt;/ax-tree&gt;");
618
+ }
619
+
620
+ /**
621
+ * Returns a shallow copy of `messages` where all but the most recent
622
+ * `MAX_AX_TREES_IN_HISTORY` `<ax-tree>` blocks have been replaced with a
623
+ * short placeholder. This keeps the conversation context small so that
624
+ * TTFT does not grow linearly with step count in computer-use sessions.
625
+ */
626
+ export function compactAxTreeHistory(messages: Message[]): Message[] {
627
+ // Collect indices of user messages that contain an <ax-tree> block
628
+ const indicesWithAxTree: number[] = [];
629
+ for (let i = 0; i < messages.length; i++) {
630
+ const msg = messages[i];
631
+ if (msg.role !== "user") continue;
632
+ for (const block of msg.content) {
633
+ if (
634
+ block.type === "tool_result" &&
635
+ typeof block.content === "string" &&
636
+ block.content.includes("<ax-tree>")
637
+ ) {
638
+ indicesWithAxTree.push(i);
639
+ break;
640
+ }
641
+ }
642
+ }
643
+
644
+ if (indicesWithAxTree.length <= MAX_AX_TREES_IN_HISTORY) {
645
+ return messages;
646
+ }
647
+
648
+ const toStrip = new Set(indicesWithAxTree.slice(0, -MAX_AX_TREES_IN_HISTORY));
649
+
650
+ return messages.map((msg, idx) => {
651
+ if (!toStrip.has(idx)) return msg;
652
+ return {
653
+ ...msg,
654
+ content: msg.content.map((block) => {
655
+ if (
656
+ block.type === "tool_result" &&
657
+ typeof block.content === "string" &&
658
+ block.content.includes("<ax-tree>")
659
+ ) {
660
+ return {
661
+ ...block,
662
+ content: block.content.replace(
663
+ AX_TREE_PATTERN,
664
+ AX_TREE_PLACEHOLDER,
665
+ ),
666
+ };
667
+ }
668
+ return block;
669
+ }),
670
+ };
671
+ });
726
672
  }
727
673
 
728
674
  /**
@@ -5,7 +5,6 @@
5
5
  * to these functions so business logic lives in one place.
6
6
  */
7
7
 
8
- import { getTwilioUserPhoneNumber } from "../config/env.js";
9
8
  import { loadConfig } from "../config/loader.js";
10
9
  import { VALID_CALLER_IDENTITY_MODES } from "../config/schema.js";
11
10
  import type { AssistantConfig } from "../config/types.js";
@@ -21,6 +20,7 @@ import { upsertBinding } from "../memory/external-conversation-store.js";
21
20
  import { revokeScopedApprovalGrantsForContext } from "../memory/scoped-approval-grants.js";
22
21
  import { DAEMON_INTERNAL_ASSISTANT_ID } from "../runtime/assistant-scope.js";
23
22
  import { isGuardian } from "../runtime/channel-verification-service.js";
23
+ import { credentialKey } from "../security/credential-key.js";
24
24
  import { getSecureKey } from "../security/secure-keys.js";
25
25
  import { getLogger } from "../util/logger.js";
26
26
  import { upsertActiveCallLease } from "./active-call-lease.js";
@@ -110,8 +110,7 @@ export type CallerIdentitySource =
110
110
  | "per_call_override"
111
111
  | "implicit_default"
112
112
  | "user_config"
113
- | "secure_key"
114
- | "env_var";
113
+ | "secure_key";
115
114
 
116
115
  export type CallerIdentityResult =
117
116
  | {
@@ -135,7 +134,7 @@ export type CallerIdentityResult =
135
134
  * For `assistant_number`: uses the Twilio phone number from
136
135
  * `getTwilioConfig()`. No eligibility check is performed — this is a fast path.
137
136
  * For `user_number`: uses `config.calls.callerIdentity.userNumber` or the
138
- * secure key `credential:twilio:user_phone_number`, then validates that the
137
+ * secure key `credential/twilio/user_phone_number`, then validates that the
139
138
  * number is usable as an outbound caller ID via the Twilio API.
140
139
  */
141
140
  export async function resolveCallerIdentity(
@@ -193,11 +192,10 @@ export async function resolveCallerIdentity(
193
192
  if (identityConfig.userNumber) {
194
193
  userNumber = identityConfig.userNumber;
195
194
  numberSource = "user_config";
196
- } else if (getTwilioUserPhoneNumber()) {
197
- userNumber = getTwilioUserPhoneNumber()!;
198
- numberSource = "env_var";
199
195
  } else {
200
- const secureKeyValue = getSecureKey("credential:twilio:user_phone_number");
196
+ const secureKeyValue = getSecureKey(
197
+ credentialKey("twilio", "user_phone_number"),
198
+ );
201
199
  if (secureKeyValue) {
202
200
  userNumber = secureKeyValue;
203
201
  numberSource = "secure_key";
@@ -212,7 +210,7 @@ export async function resolveCallerIdentity(
212
210
  return {
213
211
  ok: false,
214
212
  error:
215
- "user_number mode requires a user phone number. Set calls.callerIdentity.userNumber in config or store credential:twilio:user_phone_number via the credential_store tool.",
213
+ "user_number mode requires a user phone number. Set calls.callerIdentity.userNumber in config or store credential/twilio/user_phone_number via the credential_store tool.",
216
214
  };
217
215
  }
218
216
 
@@ -223,7 +221,7 @@ export async function resolveCallerIdentity(
223
221
  );
224
222
  return {
225
223
  ok: false,
226
- error: `User phone number "${userNumber}" is not in E.164 format (must start with + followed by digits, e.g. +14155551234). Check calls.callerIdentity.userNumber in config or credential:twilio:user_phone_number.`,
224
+ error: `User phone number "${userNumber}" is not in E.164 format (must start with + followed by digits, e.g. +14155551234). Check calls.callerIdentity.userNumber in config or credential/twilio/user_phone_number.`,
227
225
  };
228
226
  }
229
227