@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
@@ -35,18 +35,23 @@ import {
35
35
  } from "../events/tool-profiling-listener.js";
36
36
  import { registerToolTraceListener } from "../events/tool-trace-listener.js";
37
37
  import { getHookManager } from "../hooks/manager.js";
38
+ import { resolveCanonicalGuardianRequest } from "../memory/canonical-guardian-store.js";
38
39
  import { PermissionPrompter } from "../permissions/prompter.js";
39
40
  import { SecretPrompter } from "../permissions/secret-prompter.js";
41
+ import { patternMatchesCandidate } from "../permissions/trust-store.js";
40
42
  import type { UserDecision } from "../permissions/types.js";
41
43
  import { buildSystemPrompt } from "../prompts/system-prompt.js";
42
44
  import type { Message } from "../providers/types.js";
43
45
  import type { Provider } from "../providers/types.js";
44
46
  import type { TrustClass } from "../runtime/actor-trust-resolver.js";
45
47
  import type { AuthContext } from "../runtime/auth/types.js";
48
+ import * as pendingInteractions from "../runtime/pending-interactions.js";
46
49
  import * as approvalOverrides from "../runtime/session-approval-overrides.js";
47
50
  import { ToolExecutor } from "../tools/executor.js";
48
51
  import type { AssistantAttachmentDraft } from "./assistant-attachments.js";
49
52
  import { HostBashProxy } from "./host-bash-proxy.js";
53
+ import type { CuObservationResult } from "./host-cu-proxy.js";
54
+ import { HostCuProxy } from "./host-cu-proxy.js";
50
55
  import { HostFileProxy } from "./host-file-proxy.js";
51
56
  import type {
52
57
  ServerMessage,
@@ -161,6 +166,7 @@ export class Session {
161
166
  /** @internal */ taskRunId?: string;
162
167
  /** @internal */ callSessionId?: string;
163
168
  /** @internal */ hostBashProxy?: HostBashProxy;
169
+ /** @internal */ hostCuProxy?: HostCuProxy;
164
170
  /** @internal */ hostFileProxy?: HostFileProxy;
165
171
  /** @internal */ readonly queue = new MessageQueue();
166
172
  /** @internal */ currentActiveSurfaceId?: string;
@@ -199,10 +205,6 @@ export class Session {
199
205
  actions?: Array<{ id: string; label: string; style?: string }>;
200
206
  display?: string;
201
207
  }> = [];
202
- /** @internal */ onEscalateToComputerUse?: (
203
- task: string,
204
- sourceSessionId: string,
205
- ) => boolean;
206
208
  /** @internal */ workspaceTopLevelContext: string | null = null;
207
209
  /** @internal */ workspaceTopLevelDirty = true;
208
210
  public readonly traceEmitter: TraceEmitter;
@@ -324,7 +326,7 @@ export class Session {
324
326
  const resolved = {
325
327
  systemPrompt: hasSystemPromptOverride
326
328
  ? systemPrompt
327
- : buildSystemPrompt(),
329
+ : buildSystemPrompt({ hasNoClient: this.hasNoClient }),
328
330
  maxTokens: configuredMaxTokens,
329
331
  };
330
332
  return resolved;
@@ -346,7 +348,7 @@ export class Session {
346
348
  );
347
349
  this.contextWindowManager = new ContextWindowManager({
348
350
  provider,
349
- systemPrompt,
351
+ systemPrompt: () => resolveSystemPromptCallback([]).systemPrompt,
350
352
  config: config.contextWindow,
351
353
  });
352
354
 
@@ -388,6 +390,7 @@ export class Session {
388
390
  this.traceEmitter.updateSender(sendToClient);
389
391
  if (!opts?.skipProxySenderUpdate) {
390
392
  this.hostBashProxy?.updateSender(sendToClient, !hasNoClient);
393
+ this.hostCuProxy?.updateSender(sendToClient, !hasNoClient);
391
394
  this.hostFileProxy?.updateSender(sendToClient, !hasNoClient);
392
395
  }
393
396
  }
@@ -400,6 +403,7 @@ export class Session {
400
403
  /** Mark host proxies as unavailable so tool execution uses local fallback. */
401
404
  clearProxyAvailability(): void {
402
405
  this.hostBashProxy?.updateSender(this.sendToClient, false);
406
+ this.hostCuProxy?.updateSender(this.sendToClient, false);
403
407
  this.hostFileProxy?.updateSender(this.sendToClient, false);
404
408
  }
405
409
 
@@ -407,6 +411,7 @@ export class Session {
407
411
  restoreProxyAvailability(): void {
408
412
  if (!this.hasNoClient) {
409
413
  this.hostBashProxy?.updateSender(this.sendToClient, true);
414
+ this.hostCuProxy?.updateSender(this.sendToClient, true);
410
415
  this.hostFileProxy?.updateSender(this.sendToClient, true);
411
416
  }
412
417
  }
@@ -415,16 +420,6 @@ export class Session {
415
420
  this.sandboxOverride = enabled;
416
421
  }
417
422
 
418
- setEscalationHandler(
419
- handler: (task: string, sourceSessionId: string) => boolean,
420
- ): void {
421
- this.onEscalateToComputerUse = handler;
422
- }
423
-
424
- hasEscalationHandler(): boolean {
425
- return this.onEscalateToComputerUse !== undefined;
426
- }
427
-
428
423
  isProcessing(): boolean {
429
424
  return this.processing;
430
425
  }
@@ -447,6 +442,7 @@ export class Session {
447
442
  dispose(): void {
448
443
  approvalOverrides.clearMode(this.conversationId);
449
444
  this.hostBashProxy?.dispose();
445
+ this.hostCuProxy?.dispose();
450
446
  this.hostFileProxy?.dispose();
451
447
  disposeSession(this);
452
448
  }
@@ -589,6 +585,122 @@ export class Session {
589
585
  undefined,
590
586
  "Resuming after approval",
591
587
  );
588
+
589
+ // Cascade to other pending confirmations that match this decision
590
+ this.cascadePendingApprovals(requestId, decision, selectedPattern);
591
+ }
592
+
593
+ /**
594
+ * After resolving one confirmation, auto-resolve other pending
595
+ * confirmations in the same conversation that match the decision.
596
+ *
597
+ * - allow_10m / allow_thread → approve ALL pending in conversation
598
+ * - always_allow / always_allow_high_risk → approve pattern-matching pending
599
+ * - always_deny → deny pattern-matching pending
600
+ * - allow / deny (one-time) → no cascading
601
+ */
602
+ private cascadePendingApprovals(
603
+ primaryRequestId: string,
604
+ decision: UserDecision,
605
+ selectedPattern?: string,
606
+ ): void {
607
+ // Single-action decisions don't cascade
608
+ if (decision === "allow" || decision === "deny") return;
609
+
610
+ const pendingRequestIds = this.prompter.getPendingRequestIds();
611
+ if (pendingRequestIds.length === 0) return;
612
+
613
+ for (const candidateId of pendingRequestIds) {
614
+ if (candidateId === primaryRequestId) continue;
615
+
616
+ const interaction = pendingInteractions.get(candidateId);
617
+ if (!interaction) continue;
618
+ if (interaction.conversationId !== this.conversationId) continue;
619
+ if (interaction.kind !== "confirmation") continue;
620
+
621
+ const cascadeResult = this.shouldCascade(
622
+ decision,
623
+ selectedPattern,
624
+ interaction.confirmationDetails,
625
+ );
626
+ if (!cascadeResult) continue;
627
+
628
+ // Consume from pending-interactions tracker
629
+ pendingInteractions.resolve(candidateId);
630
+
631
+ // Resolve via handleConfirmationResponse which emits events.
632
+ // Use simple "allow"/"deny" so the permission-checker won't save
633
+ // duplicate rules or re-activate temporary modes. Recursion
634
+ // terminates because allow/deny exit cascadePendingApprovals early.
635
+ this.handleConfirmationResponse(
636
+ candidateId,
637
+ cascadeResult.allow ? "allow" : "deny",
638
+ undefined,
639
+ undefined,
640
+ undefined,
641
+ {
642
+ source: "system",
643
+ causedByRequestId: primaryRequestId,
644
+ },
645
+ );
646
+
647
+ // Sync the canonical guardian request status for the cascaded request.
648
+ // Best-effort: canonical request tracking should not break the cascade flow.
649
+ try {
650
+ const targetStatus = cascadeResult.allow ? "approved" : "denied";
651
+ resolveCanonicalGuardianRequest(candidateId, "pending", {
652
+ status: targetStatus,
653
+ });
654
+ } catch {
655
+ // Ignore — canonical request tracking is best-effort
656
+ }
657
+ }
658
+ }
659
+
660
+ /**
661
+ * Determine whether a pending confirmation should be auto-resolved
662
+ * based on the cascading decision and pattern.
663
+ */
664
+ private shouldCascade(
665
+ decision: UserDecision,
666
+ selectedPattern: string | undefined,
667
+ details?: import("../runtime/pending-interactions.js").ConfirmationDetails,
668
+ ): { allow: boolean } | null {
669
+ // Temporary overrides apply to the entire conversation
670
+ if (decision === "allow_10m" || decision === "allow_thread") {
671
+ return { allow: true };
672
+ }
673
+
674
+ // Persistent allow: cascade if the pattern matches any allowlist candidate.
675
+ // "always_allow" must NOT cascade to high-risk pending confirmations —
676
+ // only "always_allow_high_risk" has consent for those.
677
+ if (
678
+ (decision === "always_allow" || decision === "always_allow_high_risk") &&
679
+ selectedPattern &&
680
+ details
681
+ ) {
682
+ if (decision === "always_allow" && details.riskLevel === "high") {
683
+ return null;
684
+ }
685
+ for (const option of details.allowlistOptions) {
686
+ if (patternMatchesCandidate(selectedPattern, option.pattern)) {
687
+ return { allow: true };
688
+ }
689
+ }
690
+ return null;
691
+ }
692
+
693
+ // Persistent deny: cascade denial if the pattern matches
694
+ if (decision === "always_deny" && selectedPattern && details) {
695
+ for (const option of details.allowlistOptions) {
696
+ if (patternMatchesCandidate(selectedPattern, option.pattern)) {
697
+ return { allow: false };
698
+ }
699
+ }
700
+ return null;
701
+ }
702
+
703
+ return null;
592
704
  }
593
705
 
594
706
  handleSecretResponse(
@@ -632,6 +744,17 @@ export class Session {
632
744
  this.hostFileProxy = proxy;
633
745
  }
634
746
 
747
+ resolveHostCu(requestId: string, observation: CuObservationResult): void {
748
+ this.hostCuProxy?.resolve(requestId, observation);
749
+ }
750
+
751
+ setHostCuProxy(proxy: HostCuProxy | undefined): void {
752
+ if (this.hostCuProxy && this.hostCuProxy !== proxy) {
753
+ this.hostCuProxy.dispose();
754
+ }
755
+ this.hostCuProxy = proxy;
756
+ }
757
+
635
758
  // ── Server-authoritative state signals ─────────────────────────────
636
759
 
637
760
  emitConfirmationStateChanged(
@@ -220,9 +220,7 @@ registerHook(
220
220
  const SETTING_TO_KEY: Record<string, string> = {
221
221
  activation_key: "pttActivationKey",
222
222
  tts_voice_id: "ttsVoiceId",
223
- wake_word_enabled: "wakeWordEnabled",
224
- wake_word_keyword: "wakeWordKeyword",
225
- wake_word_timeout: "wakeWordTimeoutSeconds",
223
+ conversation_timeout: "voiceConversationTimeoutSeconds",
226
224
  };
227
225
  const key = SETTING_TO_KEY[setting];
228
226
  if (!key) return;
@@ -231,12 +229,8 @@ registerHook(
231
229
  // the validation logic in the tool's execute method.
232
230
  const raw = input.value;
233
231
  let coerced: string | boolean | number = raw as string;
234
- if (setting === "wake_word_enabled") {
235
- coerced = raw === true || raw === "true";
236
- } else if (setting === "wake_word_timeout") {
232
+ if (setting === "conversation_timeout") {
237
233
  coerced = typeof raw === "number" ? raw : Number(raw);
238
- } else if (setting === "wake_word_keyword" && typeof raw === "string") {
239
- coerced = raw.trim();
240
234
  } else if (setting === "tts_voice_id" && typeof raw === "string") {
241
235
  coerced = raw.trim();
242
236
  }
@@ -79,8 +79,8 @@ export async function handleWatchObservation(
79
79
  "Observation added to session",
80
80
  );
81
81
 
82
- // 4. Every 3 observations: call the LLM for live commentary (chat-initiated watch only)
83
- if (!session.isRideShotgun && session.observations.length % 3 === 0) {
82
+ // 4. Every 3 observations: call the LLM for live commentary
83
+ if (session.observations.length % 3 === 0) {
84
84
  log.debug(
85
85
  { watchId: msg.watchId, observationCount: session.observations.length },
86
86
  "Triggering commentary generation (every 3rd observation)",
@@ -5,6 +5,7 @@
5
5
  */
6
6
 
7
7
  import { getNestedValue, loadRawConfig } from "../../config/loader.js";
8
+ import { credentialKey } from "../../security/credential-key.js";
8
9
  import { getSecureKey } from "../../security/secure-keys.js";
9
10
  import { ConfigError } from "../../util/errors.js";
10
11
  import type { EmailProvider } from "../provider.js";
@@ -13,7 +14,7 @@ export const SUPPORTED_PROVIDERS = ["agentmail"] as const;
13
14
  export type SupportedProvider = (typeof SUPPORTED_PROVIDERS)[number];
14
15
 
15
16
  const PROVIDER_KEY_MAP: Record<SupportedProvider, string[]> = {
16
- agentmail: ["agentmail", "credential:agentmail:api_key"],
17
+ agentmail: ["agentmail", credentialKey("agentmail", "api_key")],
17
18
  };
18
19
 
19
20
  /**
@@ -1,4 +1,4 @@
1
- import { getLogger, isDebug, truncateForLog } from "../util/logger.js";
1
+ import { getLogger, truncateForLog } from "../util/logger.js";
2
2
  import type { EventBus, Subscription } from "./bus.js";
3
3
  import type { AssistantDomainEvents } from "./domain-events.js";
4
4
 
@@ -23,7 +23,7 @@ export function registerToolMetricsLoggingListener(
23
23
  options?: MetricsListenerOptions,
24
24
  ): Subscription {
25
25
  const logger = options?.logger ?? defaultLogger;
26
- const debugEnabled = options?.debugEnabled ?? isDebug;
26
+ const debugEnabled = options?.debugEnabled ?? (() => false);
27
27
  const truncate = options?.truncate ?? truncateForLog;
28
28
 
29
29
  return eventBus.onAny((event) => {
@@ -2,7 +2,7 @@ import { type FSWatcher, watch } from "node:fs";
2
2
 
3
3
  import { Debouncer } from "../util/debounce.js";
4
4
  import { pathExists } from "../util/fs.js";
5
- import { getLogger, isDebug } from "../util/logger.js";
5
+ import { getLogger } from "../util/logger.js";
6
6
  import { getHooksDir } from "../util/platform.js";
7
7
  import { discoverHooks } from "./discovery.js";
8
8
  import { runHookScript } from "./runner.js";
@@ -73,9 +73,6 @@ export class HookManager {
73
73
  "Hook exited with non-zero code",
74
74
  );
75
75
  }
76
- if (result.stderr && isDebug()) {
77
- process.stderr.write(result.stderr);
78
- }
79
76
  } catch (err) {
80
77
  log.warn({ err, hook: hook.name, event }, "Hook execution failed");
81
78
  }
@@ -8,13 +8,13 @@
8
8
  * 1. **User Settings** (`config.ingress.publicBaseUrl`) — set via
9
9
  * the in-chat config flow, the Settings UI, or `config set ingress.publicBaseUrl`. This is the
10
10
  * primary source of truth. When the assistant spawns or restarts
11
- * the gateway, this value is forwarded as the `INGRESS_PUBLIC_BASE_URL`
12
- * environment variable so both processes agree on the same URL.
11
+ * the gateway, the workspace config file is read so both processes
12
+ * agree on the same URL.
13
13
  *
14
- * 2. **Environment variable** (`INGRESS_PUBLIC_BASE_URL`) — serves as a
15
- * fallback for operational use (e.g. direct gateway-only deployments
16
- * without the assistant, or CI overrides). When the assistant is
17
- * managing the gateway, the env var is set automatically from (1).
14
+ * 2. **Module-level state** (`getIngressPublicBaseUrl()`) — serves as a
15
+ * fallback for operational use (e.g. runtime tunnel updates). When
16
+ * tunnels start or stop, `setIngressPublicBaseUrl()` updates this
17
+ * value in-process.
18
18
  *
19
19
  * This chain ensures that:
20
20
  * - The assistant's outbound callback URLs (Twilio webhooks, OAuth
@@ -70,7 +70,7 @@ export function getPublicBaseUrl(config: IngressConfig): string {
70
70
  }
71
71
 
72
72
  throw new Error(
73
- "No public base URL configured. Set ingress.publicBaseUrl in config or INGRESS_PUBLIC_BASE_URL env var.",
73
+ "No public base URL configured. Set ingress.publicBaseUrl in config.",
74
74
  );
75
75
  }
76
76
 
package/src/instrument.ts CHANGED
@@ -1,7 +1,9 @@
1
+ import { arch, platform, release } from "node:os";
2
+
1
3
  import * as Sentry from "@sentry/node";
2
4
 
3
5
  import { getSentryDsn } from "./config/env.js";
4
- import { APP_VERSION } from "./version.js";
6
+ import { APP_VERSION, COMMIT_SHA } from "./version.js";
5
7
 
6
8
  /** Patterns that match sensitive data in Sentry event values. */
7
9
  const PII_PATTERNS = [
@@ -42,8 +44,20 @@ export function initSentry(): void {
42
44
  Sentry.init({
43
45
  dsn: getSentryDsn(),
44
46
  release: `vellum-assistant@${APP_VERSION}`,
47
+ dist: COMMIT_SHA,
45
48
  environment: APP_VERSION === "0.0.0-dev" ? "development" : "production",
46
49
  sendDefaultPii: false,
50
+ initialScope: {
51
+ tags: {
52
+ commit: COMMIT_SHA,
53
+ os_platform: platform(),
54
+ os_release: release(),
55
+ os_arch: arch(),
56
+ runtime: "bun",
57
+ runtime_version:
58
+ typeof Bun !== "undefined" ? Bun.version : process.version,
59
+ },
60
+ },
47
61
  beforeSend(event) {
48
62
  if (event.exception?.values) {
49
63
  event.exception.values = event.exception.values.map((ex) => ({
package/src/logfire.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { getLogfireToken, isMonitoringEnabled } from "./config/env.js";
1
+ import { getLogfireToken } from "./config/env.js";
2
2
  import type {
3
3
  Message,
4
4
  Provider,
@@ -13,7 +13,7 @@ const log = getLogger("logfire");
13
13
 
14
14
  type LogfireModule = typeof import("@pydantic/logfire-node");
15
15
 
16
- const LOGFIRE_ENABLED: boolean = !!getLogfireToken() && isMonitoringEnabled();
16
+ let logfireEnabled: boolean = !!getLogfireToken();
17
17
 
18
18
  let logfireInstance: LogfireModule | null = null;
19
19
 
@@ -23,7 +23,7 @@ let logfireInstance: LogfireModule | null = null;
23
23
  * Non-fatal on failure (logs warning and continues).
24
24
  */
25
25
  export async function initLogfire(): Promise<void> {
26
- if (!LOGFIRE_ENABLED) return;
26
+ if (!logfireEnabled) return;
27
27
 
28
28
  try {
29
29
  const logfire = await import("@pydantic/logfire-node");
@@ -42,12 +42,23 @@ export async function initLogfire(): Promise<void> {
42
42
  }
43
43
  }
44
44
 
45
+ /**
46
+ * Disable Logfire after early initialization. Called when the user has opted
47
+ * out via the feature flag. Nulls out the instance so future wrapWithLogfire
48
+ * calls become no-ops.
49
+ */
50
+ export function disableLogfire(): void {
51
+ logfireEnabled = false;
52
+ logfireInstance = null;
53
+ log.info("Logfire disabled by feature flag");
54
+ }
55
+
45
56
  /**
46
57
  * Wraps a provider with Logfire tracing spans.
47
- * When LOGFIRE_ENABLED is false, returns the provider as-is (no wrapper allocated).
58
+ * When logfireEnabled is false, returns the provider as-is (no wrapper allocated).
48
59
  */
49
60
  export function wrapWithLogfire(provider: Provider): Provider {
50
- if (!LOGFIRE_ENABLED) return provider;
61
+ if (!logfireEnabled) return provider;
51
62
  return new LogfireProvider(provider);
52
63
  }
53
64
 
@@ -11,8 +11,16 @@ import { join } from "node:path";
11
11
 
12
12
  import { getConfig } from "../config/loader.js";
13
13
  import { getAppsDir } from "../memory/app-store.js";
14
+ import {
15
+ buildManagedBaseUrl,
16
+ resolveManagedProxyContext,
17
+ } from "../providers/managed-proxy/context.js";
14
18
  import { getLogger } from "../util/logger.js";
15
- import { generateImage, mapGeminiError } from "./gemini-image-service.js";
19
+ import {
20
+ generateImage,
21
+ type ImageGenCredentials,
22
+ mapGeminiError,
23
+ } from "./gemini-image-service.js";
16
24
 
17
25
  const log = getLogger("app-icon-generator");
18
26
 
@@ -29,8 +37,26 @@ export async function generateAppIcon(
29
37
  ): Promise<void> {
30
38
  const config = getConfig();
31
39
  const apiKey = config.apiKeys.gemini ?? process.env.GEMINI_API_KEY;
32
- if (!apiKey) {
33
- log.debug("No Gemini API key — skipping app icon generation");
40
+
41
+ let credentials: ImageGenCredentials | undefined;
42
+ if (apiKey) {
43
+ credentials = { type: "direct", apiKey };
44
+ } else {
45
+ const managedBaseUrl = buildManagedBaseUrl("vertex");
46
+ if (managedBaseUrl) {
47
+ const ctx = resolveManagedProxyContext();
48
+ credentials = {
49
+ type: "managed-proxy",
50
+ assistantApiKey: ctx.assistantApiKey,
51
+ baseUrl: managedBaseUrl,
52
+ };
53
+ }
54
+ }
55
+
56
+ if (!credentials) {
57
+ log.debug(
58
+ "No Gemini API key or managed proxy — skipping app icon generation",
59
+ );
34
60
  return;
35
61
  }
36
62
 
@@ -58,7 +84,7 @@ export async function generateAppIcon(
58
84
  try {
59
85
  log.info({ appId, appName }, "Generating app icon via Gemini");
60
86
 
61
- const result = await generateImage(apiKey, {
87
+ const result = await generateImage(credentials, {
62
88
  prompt,
63
89
  mode: "generate",
64
90
  model: config.imageGenModel,
@@ -1,19 +1,42 @@
1
1
  import { getConfig } from "../config/loader.js";
2
+ import {
3
+ buildManagedBaseUrl,
4
+ resolveManagedProxyContext,
5
+ } from "../providers/managed-proxy/context.js";
2
6
  import { ConfigError, ProviderError } from "../util/errors.js";
3
- import { generateImage } from "./gemini-image-service.js";
7
+ import {
8
+ generateImage,
9
+ type ImageGenCredentials,
10
+ } from "./gemini-image-service.js";
4
11
 
5
12
  export async function generateAvatar(
6
13
  prompt: string,
7
14
  ): Promise<{ imageBase64: string; mimeType: string }> {
8
15
  const config = getConfig();
9
16
  const geminiKey = config.apiKeys.gemini ?? process.env.GEMINI_API_KEY;
10
- if (!geminiKey) {
17
+
18
+ let credentials: ImageGenCredentials | undefined;
19
+ if (geminiKey) {
20
+ credentials = { type: "direct", apiKey: geminiKey };
21
+ } else {
22
+ const managedBaseUrl = buildManagedBaseUrl("vertex");
23
+ if (managedBaseUrl) {
24
+ const ctx = resolveManagedProxyContext();
25
+ credentials = {
26
+ type: "managed-proxy",
27
+ assistantApiKey: ctx.assistantApiKey,
28
+ baseUrl: managedBaseUrl,
29
+ };
30
+ }
31
+ }
32
+
33
+ if (!credentials) {
11
34
  throw new ConfigError(
12
35
  "Gemini API key is not configured. Set it via `config set apiKeys.gemini <key>` or the GEMINI_API_KEY environment variable.",
13
36
  );
14
37
  }
15
38
 
16
- const result = await generateImage(geminiKey, {
39
+ const result = await generateImage(credentials, {
17
40
  prompt,
18
41
  mode: "generate",
19
42
  model: config.imageGenModel,
@@ -13,6 +13,21 @@ export interface ImageGenerationRequest {
13
13
  variants?: number;
14
14
  }
15
15
 
16
+ /** Credentials for direct Gemini API access. */
17
+ export interface DirectCredentials {
18
+ type: "direct";
19
+ apiKey: string;
20
+ }
21
+
22
+ /** Credentials for managed proxy access via Vertex AI. */
23
+ export interface ManagedProxyCredentials {
24
+ type: "managed-proxy";
25
+ assistantApiKey: string;
26
+ baseUrl: string;
27
+ }
28
+
29
+ export type ImageGenCredentials = DirectCredentials | ManagedProxyCredentials;
30
+
16
31
  export interface GeneratedImage {
17
32
  mimeType: string;
18
33
  dataBase64: string;
@@ -64,7 +79,7 @@ export function mapGeminiError(error: unknown): string {
64
79
  // --- Core function ---
65
80
 
66
81
  export async function generateImage(
67
- apiKey: string,
82
+ credentials: ImageGenCredentials,
68
83
  request: ImageGenerationRequest,
69
84
  ): Promise<ImageGenerationResult> {
70
85
  const model =
@@ -74,7 +89,18 @@ export async function generateImage(
74
89
 
75
90
  const variants = Math.max(1, Math.min(request.variants ?? 1, MAX_VARIANTS));
76
91
 
77
- const client = new GoogleGenAI({ apiKey });
92
+ const client =
93
+ credentials.type === "managed-proxy"
94
+ ? new GoogleGenAI({
95
+ vertexai: true,
96
+ project: "proxy",
97
+ location: "us-central1",
98
+ httpOptions: {
99
+ baseUrl: credentials.baseUrl,
100
+ headers: { Authorization: `Bearer ${credentials.assistantApiKey}` },
101
+ },
102
+ })
103
+ : new GoogleGenAI({ apiKey: credentials.apiKey });
78
104
 
79
105
  // Build contents array — append a title request so the model's text
80
106
  // response contains a short filename-safe title for the generated image.
@@ -117,6 +117,27 @@ export function getOrCreateConversation(conversationKey: string): {
117
117
  return { conversationId: existing.conversationId, created: false };
118
118
  }
119
119
 
120
+ // Check if the conversationKey itself is an existing conversation ID.
121
+ // This happens when the client loads a thread from the conversations list
122
+ // and uses the server's conversationId as its local sessionId / conversationKey.
123
+ const existingConversation = tx
124
+ .select({ id: conversations.id })
125
+ .from(conversations)
126
+ .where(eq(conversations.id, conversationKey))
127
+ .get();
128
+
129
+ if (existingConversation) {
130
+ tx.insert(conversationKeys)
131
+ .values({
132
+ id: uuid(),
133
+ conversationKey,
134
+ conversationId: existingConversation.id,
135
+ createdAt: Date.now(),
136
+ })
137
+ .run();
138
+ return { conversationId: existingConversation.id, created: false };
139
+ }
140
+
120
141
  const now = Date.now();
121
142
  const conversationId = uuid();
122
143
 
@@ -28,6 +28,7 @@ import {
28
28
  createMediaAssetsTables,
29
29
  createMessagesFts,
30
30
  createNotificationTables,
31
+ createOAuthTables,
31
32
  createScopedApprovalGrantsTable,
32
33
  createSequenceTables,
33
34
  createTasksAndWorkItemsTables,
@@ -340,6 +341,9 @@ export function initializeDb(): void {
340
341
  // 52. Drop the legacy reminders table after data migration
341
342
  migrateDropRemindersTable(database);
342
343
 
344
+ // 53. OAuth provider/app/connection tables
345
+ createOAuthTables(database);
346
+
343
347
  validateMigrationState(database);
344
348
 
345
349
  if (process.env.BUN_TEST === "1") {
@@ -44,7 +44,7 @@ export type FollowupState =
44
44
  | "completed"
45
45
  | "declined"
46
46
  | "failed";
47
- export type FollowupAction = "call_back" | "message_back" | "decline";
47
+ export type FollowupAction = "call_back" | "decline";
48
48
 
49
49
  export interface GuardianActionRequest {
50
50
  id: string;