@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
@@ -1,4 +1,5 @@
1
1
  import type { ContentBlock, Message } from "../providers/types.js";
2
+ import { parseImageDimensions } from "./image-dimensions.js";
2
3
 
3
4
  const CHARS_PER_TOKEN = 4;
4
5
  const MESSAGE_OVERHEAD_TOKENS = 4;
@@ -12,6 +13,22 @@ const OTHER_BLOCK_TOKENS = 16;
12
13
  const SYSTEM_PROMPT_OVERHEAD_TOKENS = 8;
13
14
  const GEMINI_INLINE_FILE_MIME_TYPES = new Set(["application/pdf"]);
14
15
 
16
+ // Anthropic scales images to fit within 1568x1568 maintaining aspect ratio,
17
+ // then charges ~(width * height) / 750 tokens.
18
+ const ANTHROPIC_IMAGE_MAX_DIMENSION = 1568;
19
+ const ANTHROPIC_IMAGE_TOKENS_PER_PIXEL = 1 / 750;
20
+ const ANTHROPIC_IMAGE_MAX_TOKENS = Math.ceil(
21
+ ANTHROPIC_IMAGE_MAX_DIMENSION *
22
+ ANTHROPIC_IMAGE_MAX_DIMENSION *
23
+ ANTHROPIC_IMAGE_TOKENS_PER_PIXEL,
24
+ ); // ~3,277 tokens
25
+
26
+ // Anthropic renders each PDF page as an image (~1,568 tokens at standard
27
+ // resolution) plus any extracted text. Typical PDF pages are 50-150 KB.
28
+ // Using ~100 KB/page and ~1,600 tokens/page gives ~0.016 tokens/byte.
29
+ const ANTHROPIC_PDF_TOKENS_PER_BYTE = 0.016;
30
+ const ANTHROPIC_PDF_MIN_TOKENS = 1600; // At least one page
31
+
15
32
  export interface TokenEstimatorOptions {
16
33
  providerName?: string;
17
34
  }
@@ -21,21 +38,69 @@ export function estimateTextTokens(text: string): number {
21
38
  return Math.ceil(text.length / CHARS_PER_TOKEN);
22
39
  }
23
40
 
24
- function shouldCountFileSourceData(
41
+ function estimateAnthropicPdfTokens(base64Data: string): number {
42
+ const rawBytes = Math.ceil((base64Data.length * 3) / 4);
43
+ return Math.max(
44
+ ANTHROPIC_PDF_MIN_TOKENS,
45
+ Math.ceil(rawBytes * ANTHROPIC_PDF_TOKENS_PER_BYTE),
46
+ );
47
+ }
48
+
49
+ function estimateFileDataTokens(
25
50
  block: Extract<ContentBlock, { type: "file" }>,
26
51
  options?: TokenEstimatorOptions,
27
- ): boolean {
28
- if (options?.providerName !== "gemini") {
29
- return false;
52
+ ): number {
53
+ const providerName = options?.providerName;
54
+
55
+ // Anthropic sends PDFs as native document blocks and renders each page as an image
56
+ if (
57
+ providerName === "anthropic" &&
58
+ block.source.media_type === "application/pdf"
59
+ ) {
60
+ return estimateAnthropicPdfTokens(block.source.data);
61
+ }
62
+
63
+ // Gemini sends certain file types inline as base64
64
+ if (
65
+ providerName === "gemini" &&
66
+ GEMINI_INLINE_FILE_MIME_TYPES.has(block.source.media_type)
67
+ ) {
68
+ return estimateTextTokens(block.source.data);
30
69
  }
31
- return GEMINI_INLINE_FILE_MIME_TYPES.has(block.source.media_type);
70
+
71
+ return 0;
72
+ }
73
+
74
+ function estimateAnthropicImageTokens(width: number, height: number): number {
75
+ // Scale down to fit within 1568x1568 bounding box, maintaining aspect ratio
76
+ const scale = Math.min(
77
+ 1,
78
+ ANTHROPIC_IMAGE_MAX_DIMENSION / Math.max(width, height),
79
+ );
80
+ const scaledWidth = Math.round(width * scale);
81
+ const scaledHeight = Math.round(height * scale);
82
+ return Math.max(
83
+ IMAGE_BLOCK_TOKENS, // minimum 1024
84
+ Math.ceil(scaledWidth * scaledHeight * ANTHROPIC_IMAGE_TOKENS_PER_PIXEL),
85
+ );
32
86
  }
33
87
 
34
- function estimateImageSourceDataTokens(
88
+ function estimateImageTokens(
35
89
  block: Extract<ContentBlock, { type: "image" }>,
90
+ options?: TokenEstimatorOptions,
36
91
  ): number {
37
- // Image payloads are carried inline as base64 for all currently supported
38
- // providers, so estimator must scale with payload size (not fixed per image).
92
+ if (options?.providerName === "anthropic") {
93
+ const dims = parseImageDimensions(
94
+ block.source.data,
95
+ block.source.media_type,
96
+ );
97
+ if (dims) {
98
+ return estimateAnthropicImageTokens(dims.width, dims.height);
99
+ }
100
+ // Fallback: if dimensions can't be parsed, use Anthropic's max
101
+ return ANTHROPIC_IMAGE_MAX_TOKENS;
102
+ }
103
+ // Non-Anthropic: keep existing base64-size heuristic
39
104
  return estimateTextTokens(block.source.data);
40
105
  }
41
106
 
@@ -69,16 +134,14 @@ export function estimateContentBlockTokens(
69
134
  IMAGE_BLOCK_TOKENS,
70
135
  IMAGE_BLOCK_OVERHEAD_TOKENS +
71
136
  estimateTextTokens(block.source.media_type) +
72
- estimateImageSourceDataTokens(block),
137
+ estimateImageTokens(block, options),
73
138
  );
74
139
  case "file":
75
140
  return (
76
141
  FILE_BLOCK_OVERHEAD_TOKENS +
77
142
  estimateTextTokens(block.source.filename) +
78
143
  estimateTextTokens(block.source.media_type) +
79
- (shouldCountFileSourceData(block, options)
80
- ? estimateTextTokens(block.source.data)
81
- : 0) +
144
+ estimateFileDataTokens(block, options) +
82
145
  estimateTextTokens(block.extracted_text ?? "")
83
146
  );
84
147
  case "thinking":
@@ -83,21 +83,44 @@ export interface ContextWindowCompactOptions {
83
83
 
84
84
  export interface ContextWindowManagerOptions {
85
85
  provider: Provider;
86
- systemPrompt: string;
86
+ systemPrompt: string | (() => string);
87
87
  config: ContextWindowConfig;
88
88
  }
89
89
 
90
90
  export class ContextWindowManager {
91
91
  private readonly provider: Provider;
92
- private readonly systemPrompt: string;
92
+ private readonly _systemPrompt: string | (() => string);
93
93
  private readonly config: ContextWindowConfig;
94
+ /**
95
+ * Cached resolved system prompt. Lazily populated on first access via the
96
+ * `systemPrompt` getter and cleared after each compaction pass so the next
97
+ * pass picks up any prompt changes.
98
+ */
99
+ private _resolvedSystemPrompt: string | undefined;
94
100
 
95
101
  constructor(options: ContextWindowManagerOptions) {
96
102
  this.provider = options.provider;
97
- this.systemPrompt = options.systemPrompt;
103
+ this._systemPrompt = options.systemPrompt;
98
104
  this.config = options.config;
99
105
  }
100
106
 
107
+ /** Lazily resolve and cache the system prompt for the duration of a compaction pass. */
108
+ private get systemPrompt(): string {
109
+ if (this._resolvedSystemPrompt !== undefined) {
110
+ return this._resolvedSystemPrompt;
111
+ }
112
+ const resolved =
113
+ typeof this._systemPrompt === "function"
114
+ ? this._systemPrompt()
115
+ : this._systemPrompt;
116
+ this._resolvedSystemPrompt = resolved;
117
+ return resolved;
118
+ }
119
+
120
+ private clearSystemPromptCache(): void {
121
+ this._resolvedSystemPrompt = undefined;
122
+ }
123
+
101
124
  /**
102
125
  * Cheap pre-check: returns whether the estimated token count exceeds
103
126
  * the compaction threshold, along with the estimated token count so
@@ -106,19 +129,35 @@ export class ContextWindowManager {
106
129
  */
107
130
  shouldCompact(messages: Message[]): ShouldCompactResult {
108
131
  if (!this.config.enabled) return { needed: false, estimatedTokens: 0 };
109
- const estimated = estimatePromptTokens(messages, this.systemPrompt, {
110
- providerName: this.provider.name,
111
- });
112
- const threshold = Math.floor(
113
- this.config.maxInputTokens * this.config.compactThreshold,
114
- );
115
- return { needed: estimated >= threshold, estimatedTokens: estimated };
132
+ try {
133
+ const estimated = estimatePromptTokens(messages, this.systemPrompt, {
134
+ providerName: this.provider.name,
135
+ });
136
+ const threshold = Math.floor(
137
+ this.config.maxInputTokens * this.config.compactThreshold,
138
+ );
139
+ return { needed: estimated >= threshold, estimatedTokens: estimated };
140
+ } finally {
141
+ this.clearSystemPromptCache();
142
+ }
116
143
  }
117
144
 
118
145
  async maybeCompact(
119
146
  messages: Message[],
120
147
  signal?: AbortSignal,
121
148
  options?: ContextWindowCompactOptions,
149
+ ): Promise<ContextWindowResult> {
150
+ try {
151
+ return await this._maybeCompact(messages, signal, options);
152
+ } finally {
153
+ this.clearSystemPromptCache();
154
+ }
155
+ }
156
+
157
+ private async _maybeCompact(
158
+ messages: Message[],
159
+ signal?: AbortSignal,
160
+ options?: ContextWindowCompactOptions,
122
161
  ): Promise<ContextWindowResult> {
123
162
  const previousEstimatedInputTokens =
124
163
  options?.precomputedEstimate ??
@@ -17,9 +17,6 @@ import {
17
17
  // Constants
18
18
  // ---------------------------------------------------------------------------
19
19
 
20
- /** Maximum number of attachments the assistant may emit per turn. */
21
- export const MAX_ASSISTANT_ATTACHMENTS = 5;
22
-
23
20
  /** Maximum size in bytes for a single assistant attachment (20 MB). */
24
21
  export const MAX_ASSISTANT_ATTACHMENT_BYTES = 20 * 1024 * 1024;
25
22
 
@@ -122,10 +119,9 @@ export interface ValidatedDrafts {
122
119
  }
123
120
 
124
121
  /**
125
- * Enforce per-turn attachment caps.
122
+ * Enforce per-attachment size cap.
126
123
  *
127
124
  * - Rejects individual drafts that exceed `MAX_ASSISTANT_ATTACHMENT_BYTES`.
128
- * - Truncates the list at `MAX_ASSISTANT_ATTACHMENTS`.
129
125
  */
130
126
  export function validateDrafts(
131
127
  drafts: AssistantAttachmentDraft[],
@@ -144,14 +140,6 @@ export function validateDrafts(
144
140
  continue;
145
141
  }
146
142
 
147
- if (accepted.length >= MAX_ASSISTANT_ATTACHMENTS) {
148
- warnings.push(
149
- `Skipped attachment "${draft.filename}": ` +
150
- `exceeded maximum of ${MAX_ASSISTANT_ATTACHMENTS} attachments per turn.`,
151
- );
152
- continue;
153
- }
154
-
155
143
  accepted.push(draft);
156
144
  }
157
145
 
@@ -83,8 +83,8 @@ const FOLLOWUP_CONVERSATION_MAX_TOKENS = 300;
83
83
  const FOLLOWUP_CONVERSATION_SYSTEM_PROMPT =
84
84
  "You are an assistant helping route a guardian's reply to a post-timeout follow-up message. " +
85
85
  "A voice caller asked a question, but the call timed out before the guardian could answer. " +
86
- "The guardian has now replied late, and was asked whether they want to call the caller back, " +
87
- "send them a text message, or skip it. " +
86
+ "The guardian has now replied late, and was asked whether they want to call the caller back " +
87
+ "or skip it. " +
88
88
  "Analyze the guardian's latest reply to determine their intent. " +
89
89
  "When uncertain, default to keep_pending and ask a clarifying question. " +
90
90
  "Always provide a natural, helpful reply along with your decision.";
@@ -101,10 +101,10 @@ const FOLLOWUP_CONVERSATION_TOOL_SCHEMA = {
101
101
  properties: {
102
102
  disposition: {
103
103
  type: "string",
104
- enum: ["call_back", "message_back", "decline", "keep_pending"],
104
+ enum: ["call_back", "decline", "keep_pending"],
105
105
  description:
106
106
  "The guardian's intent: call_back to call the original caller, " +
107
- "message_back to send a text message, decline to skip the follow-up, " +
107
+ "decline to skip the follow-up, " +
108
108
  "keep_pending if the intent is unclear (ask for clarification).",
109
109
  },
110
110
  replyText: {
@@ -118,7 +118,6 @@ const FOLLOWUP_CONVERSATION_TOOL_SCHEMA = {
118
118
 
119
119
  const VALID_FOLLOWUP_DISPOSITIONS: ReadonlySet<string> = new Set([
120
120
  "call_back",
121
- "message_back",
122
121
  "decline",
123
122
  "keep_pending",
124
123
  ]);
@@ -5,7 +5,6 @@ import {
5
5
  } from "../../calls/twilio-rest.js";
6
6
  import {
7
7
  getGatewayInternalBaseUrl,
8
- getIngressPublicBaseUrl,
9
8
  setIngressPublicBaseUrl,
10
9
  } from "../../config/env.js";
11
10
  import { loadRawConfig, saveRawConfig } from "../../config/loader.js";
@@ -26,20 +25,6 @@ import {
26
25
  log,
27
26
  } from "./shared.js";
28
27
 
29
- // Lazily capture the env-provided INGRESS_PUBLIC_BASE_URL on first access
30
- // rather than at module load time. The daemon loads ~/.vellum/.env inside
31
- // runDaemon() (see lifecycle.ts), which runs AFTER static ES module imports
32
- // resolve. A module-level snapshot would miss dotenv-provided values.
33
- let _originalIngressEnvCaptured = false;
34
- let _originalIngressEnv: string | undefined;
35
- function getOriginalIngressEnv(): string | undefined {
36
- if (!_originalIngressEnvCaptured) {
37
- _originalIngressEnv = getIngressPublicBaseUrl();
38
- _originalIngressEnvCaptured = true;
39
- }
40
- return _originalIngressEnv;
41
- }
42
-
43
28
  export function computeGatewayTarget(): string {
44
29
  return getGatewayInternalBaseUrl();
45
30
  }
@@ -108,13 +93,11 @@ export async function handleIngressConfig(
108
93
  });
109
94
  } else if (msg.action === "set") {
110
95
  const value = (msg.publicBaseUrl ?? "").trim().replace(/\/+$/, "");
111
- // Ensure we capture the original env value before any mutation below
112
- getOriginalIngressEnv();
113
96
  const raw = loadRawConfig();
114
97
 
115
98
  // Update ingress.publicBaseUrl — this is the single source of truth for
116
- // the canonical public ingress URL. The gateway receives this value via
117
- // the INGRESS_PUBLIC_BASE_URL env var at spawn time (see hatch.ts).
99
+ // the canonical public ingress URL. The gateway reads this value from
100
+ // the workspace config file via ConfigFileCache.
118
101
  // The gateway also validates Twilio signatures against forwarded public
119
102
  // URL headers, so local tunnel updates generally apply without restarts.
120
103
  const ingress = (raw?.ingress ?? {}) as Record<string, unknown>;
@@ -139,24 +122,17 @@ export async function handleIngressConfig(
139
122
  CONFIG_RELOAD_DEBOUNCE_MS,
140
123
  );
141
124
 
142
- // Propagate to the gateway's process environment so it picks up the
143
- // new URL when it is restarted. For the local-deployment path the
144
- // gateway runs as a child process that inherited the assistant's env,
145
- // so updating process.env here ensures the value is visible when the
146
- // gateway is restarted (e.g. by the self-upgrade skill or a manual
147
- // `pkill -f gateway`).
148
- // Only export the URL when ingress is enabled; clearing it when
125
+ // Propagate to module-level state so the assistant's in-process URL
126
+ // resolution stays in sync. The gateway reads from the workspace config
127
+ // file directly via ConfigFileCache, so no env var propagation is needed.
128
+ // Only set the URL when ingress is enabled; clearing it when
149
129
  // disabled ensures the gateway stops accepting inbound webhooks.
150
130
  const isEnabled = (ingress.enabled as boolean | undefined) ?? false;
151
131
  if (value && isEnabled) {
152
132
  setIngressPublicBaseUrl(value);
153
- } else if (isEnabled && getOriginalIngressEnv() !== undefined) {
154
- // Ingress is enabled but the user cleared the URL — fall back to the
155
- // env var that was present when the process started.
156
- setIngressPublicBaseUrl(getOriginalIngressEnv()!);
157
133
  } else {
158
- // Ingress is disabled or no URL is configured and no startup env var
159
- // exists — remove the env var so the gateway stops accepting webhooks.
134
+ // Ingress is disabled or no URL is configured clear the module-level
135
+ // URL so the gateway stops accepting webhooks.
160
136
  setIngressPublicBaseUrl(undefined);
161
137
  }
162
138
 
@@ -250,4 +226,3 @@ export async function handleIngressConfig(
250
226
  });
251
227
  }
252
228
  }
253
-
@@ -5,6 +5,12 @@ import {
5
5
  saveRawConfig,
6
6
  setNestedValue,
7
7
  } from "../../config/loader.js";
8
+ import {
9
+ ensureManualTokenConnection,
10
+ removeManualTokenConnection,
11
+ } from "../../oauth/manual-token-connection.js";
12
+ import { getConnectionByProvider } from "../../oauth/oauth-store.js";
13
+ import { credentialKey } from "../../security/credential-key.js";
8
14
  import {
9
15
  deleteSecureKeyAsync,
10
16
  getSecureKey,
@@ -34,14 +40,21 @@ export interface SlackChannelConfigResult {
34
40
  // -- Business logic --
35
41
 
36
42
  export function getSlackChannelConfig(): SlackChannelConfigResult {
37
- const hasBotToken = !!getSecureKey("credential:slack_channel:bot_token");
38
- const hasAppToken = !!getSecureKey("credential:slack_channel:app_token");
43
+ const hasBotToken = !!getSecureKey(
44
+ credentialKey("slack_channel", "bot_token"),
45
+ );
46
+ const hasAppToken = !!getSecureKey(
47
+ credentialKey("slack_channel", "app_token"),
48
+ );
49
+ const conn = getConnectionByProvider("slack_channel");
50
+ const connected =
51
+ !!(conn && conn.status === "active") && hasBotToken && hasAppToken;
39
52
  const { teamId, teamName, botUserId, botUsername } = getConfig().slack;
40
53
  return {
41
54
  success: true,
42
55
  hasBotToken,
43
56
  hasAppToken,
44
- connected: hasBotToken && hasAppToken,
57
+ connected,
45
58
  ...(teamId ? { teamId } : {}),
46
59
  ...(teamName ? { teamName } : {}),
47
60
  ...(botUserId ? { botUserId } : {}),
@@ -78,17 +91,13 @@ export async function setSlackChannelConfig(
78
91
  user?: string;
79
92
  };
80
93
  if (!data.ok) {
81
- const storedBotToken = !!getSecureKey(
82
- "credential:slack_channel:bot_token",
83
- );
84
- const storedAppToken = !!getSecureKey(
85
- "credential:slack_channel:app_token",
86
- );
94
+ const errConn = getConnectionByProvider("slack_channel");
95
+ const errConnected = !!(errConn && errConn.status === "active");
87
96
  return {
88
97
  success: false,
89
- hasBotToken: storedBotToken,
90
- hasAppToken: storedAppToken,
91
- connected: storedBotToken && storedAppToken,
98
+ hasBotToken: errConnected,
99
+ hasAppToken: errConnected,
100
+ connected: errConnected,
92
101
  error: `Slack API validation failed: ${
93
102
  data.error ?? "unknown error"
94
103
  }`,
@@ -102,37 +111,29 @@ export async function setSlackChannelConfig(
102
111
  };
103
112
  } catch (err) {
104
113
  const message = err instanceof Error ? err.message : String(err);
105
- const storedBotToken = !!getSecureKey(
106
- "credential:slack_channel:bot_token",
107
- );
108
- const storedAppToken = !!getSecureKey(
109
- "credential:slack_channel:app_token",
110
- );
114
+ const errConn = getConnectionByProvider("slack_channel");
115
+ const errConnected = !!(errConn && errConn.status === "active");
111
116
  return {
112
117
  success: false,
113
- hasBotToken: storedBotToken,
114
- hasAppToken: storedAppToken,
115
- connected: storedBotToken && storedAppToken,
118
+ hasBotToken: errConnected,
119
+ hasAppToken: errConnected,
120
+ connected: errConnected,
116
121
  error: `Failed to validate bot token: ${message}`,
117
122
  };
118
123
  }
119
124
 
120
125
  const stored = await setSecureKeyAsync(
121
- "credential:slack_channel:bot_token",
126
+ credentialKey("slack_channel", "bot_token"),
122
127
  botToken,
123
128
  );
124
129
  if (!stored) {
125
- const storedBotToken = !!getSecureKey(
126
- "credential:slack_channel:bot_token",
127
- );
128
- const storedAppToken = !!getSecureKey(
129
- "credential:slack_channel:app_token",
130
- );
130
+ const errConn = getConnectionByProvider("slack_channel");
131
+ const errConnected = !!(errConn && errConn.status === "active");
131
132
  return {
132
133
  success: false,
133
- hasBotToken: storedBotToken,
134
- hasAppToken: storedAppToken,
135
- connected: storedBotToken && storedAppToken,
134
+ hasBotToken: errConnected,
135
+ hasAppToken: errConnected,
136
+ connected: errConnected,
136
137
  error: "Failed to store bot token in secure storage",
137
138
  };
138
139
  }
@@ -160,37 +161,29 @@ export async function setSlackChannelConfig(
160
161
  // Validate and store app token
161
162
  if (appToken) {
162
163
  if (!appToken.startsWith("xapp-")) {
163
- const storedBotToken = !!getSecureKey(
164
- "credential:slack_channel:bot_token",
165
- );
166
- const storedAppToken = !!getSecureKey(
167
- "credential:slack_channel:app_token",
168
- );
164
+ const errConn = getConnectionByProvider("slack_channel");
165
+ const errConnected = !!(errConn && errConn.status === "active");
169
166
  return {
170
167
  success: false,
171
- hasBotToken: storedBotToken,
172
- hasAppToken: storedAppToken,
173
- connected: storedBotToken && storedAppToken,
168
+ hasBotToken: errConnected,
169
+ hasAppToken: errConnected,
170
+ connected: errConnected,
174
171
  error: 'Invalid app token: must start with "xapp-"',
175
172
  };
176
173
  }
177
174
 
178
175
  const stored = await setSecureKeyAsync(
179
- "credential:slack_channel:app_token",
176
+ credentialKey("slack_channel", "app_token"),
180
177
  appToken,
181
178
  );
182
179
  if (!stored) {
183
- const storedBotToken = !!getSecureKey(
184
- "credential:slack_channel:bot_token",
185
- );
186
- const storedAppToken = !!getSecureKey(
187
- "credential:slack_channel:app_token",
188
- );
180
+ const errConn = getConnectionByProvider("slack_channel");
181
+ const errConnected = !!(errConn && errConn.status === "active");
189
182
  return {
190
183
  success: false,
191
- hasBotToken: storedBotToken,
192
- hasAppToken: storedAppToken,
193
- connected: storedBotToken && storedAppToken,
184
+ hasBotToken: errConnected,
185
+ hasAppToken: errConnected,
186
+ connected: errConnected,
194
187
  error: "Failed to store app token in secure storage",
195
188
  };
196
189
  }
@@ -198,8 +191,12 @@ export async function setSlackChannelConfig(
198
191
  upsertCredentialMetadata("slack_channel", "app_token", {});
199
192
  }
200
193
 
201
- const hasBotToken = !!getSecureKey("credential:slack_channel:bot_token");
202
- const hasAppToken = !!getSecureKey("credential:slack_channel:app_token");
194
+ const hasBotToken = !!getSecureKey(
195
+ credentialKey("slack_channel", "bot_token"),
196
+ );
197
+ const hasAppToken = !!getSecureKey(
198
+ credentialKey("slack_channel", "app_token"),
199
+ );
203
200
 
204
201
  if (hasBotToken && !hasAppToken) {
205
202
  warning =
@@ -209,6 +206,17 @@ export async function setSlackChannelConfig(
209
206
  "App token stored but bot token is missing — connection incomplete.";
210
207
  }
211
208
 
209
+ // Sync oauth_connection record so getConnectionByProvider("slack_channel")
210
+ // reflects the current credential state.
211
+ if (hasBotToken && hasAppToken) {
212
+ const accountInfo = metadata.teamName
213
+ ? `${metadata.teamName}${metadata.botUsername ? ` (@${metadata.botUsername})` : ""}`
214
+ : undefined;
215
+ await ensureManualTokenConnection("slack_channel", accountInfo);
216
+ } else {
217
+ removeManualTokenConnection("slack_channel");
218
+ }
219
+
212
220
  return {
213
221
  success: true,
214
222
  hasBotToken,
@@ -220,12 +228,21 @@ export async function setSlackChannelConfig(
220
228
  }
221
229
 
222
230
  export async function clearSlackChannelConfig(): Promise<SlackChannelConfigResult> {
223
- const r1 = await deleteSecureKeyAsync("credential:slack_channel:bot_token");
224
- const r2 = await deleteSecureKeyAsync("credential:slack_channel:app_token");
231
+ const r1 = await deleteSecureKeyAsync(
232
+ credentialKey("slack_channel", "bot_token"),
233
+ );
234
+ const r2 = await deleteSecureKeyAsync(
235
+ credentialKey("slack_channel", "app_token"),
236
+ );
225
237
 
226
238
  if (r1 === "error" || r2 === "error") {
227
- const hasBotToken = !!getSecureKey("credential:slack_channel:bot_token");
228
- const hasAppToken = !!getSecureKey("credential:slack_channel:app_token");
239
+ // Check each key individually so partial deletions report accurate status.
240
+ const hasBotToken = !!getSecureKey(
241
+ credentialKey("slack_channel", "bot_token"),
242
+ );
243
+ const hasAppToken = !!getSecureKey(
244
+ credentialKey("slack_channel", "app_token"),
245
+ );
229
246
  return {
230
247
  success: false,
231
248
  hasBotToken,
@@ -238,6 +255,9 @@ export async function clearSlackChannelConfig(): Promise<SlackChannelConfigResul
238
255
  deleteCredentialMetadata("slack_channel", "bot_token");
239
256
  deleteCredentialMetadata("slack_channel", "app_token");
240
257
 
258
+ // Remove the oauth_connection row so getConnectionByProvider returns undefined.
259
+ removeManualTokenConnection("slack_channel");
260
+
241
261
  const raw = loadRawConfig();
242
262
  setNestedValue(raw, "slack.teamId", "");
243
263
  setNestedValue(raw, "slack.teamName", "");