@vellumai/assistant 0.6.3 → 0.6.4

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 (667) hide show
  1. package/ARCHITECTURE.md +273 -10
  2. package/Dockerfile +2 -3
  3. package/bun.lock +5 -13
  4. package/docs/backup-troubleshooting.md +52 -0
  5. package/docs/browser-use-architecture-phase2.md +174 -0
  6. package/docs/stt-provider-onboarding.md +120 -0
  7. package/knip.json +12 -2
  8. package/node_modules/@vellumai/ces-contracts/bun.lock +8 -6
  9. package/node_modules/@vellumai/ces-contracts/package.json +3 -3
  10. package/openapi.yaml +982 -72
  11. package/package.json +4 -6
  12. package/scripts/generate-openapi.ts +0 -1
  13. package/scripts/test.sh +73 -18
  14. package/src/__tests__/agent-image-optimize.test.ts +28 -0
  15. package/src/__tests__/agent-loop.test.ts +123 -0
  16. package/src/__tests__/anthropic-provider.test.ts +263 -10
  17. package/src/__tests__/auto-analysis-end-to-end.test.ts +550 -0
  18. package/src/__tests__/auto-analysis-prompt.test.ts +50 -0
  19. package/src/__tests__/browser-fill-credential.test.ts +11 -0
  20. package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +2 -2
  21. package/src/__tests__/browser-skill-endstate.test.ts +31 -7
  22. package/src/__tests__/btw-routes.test.ts +7 -0
  23. package/src/__tests__/call-controller.test.ts +581 -20
  24. package/src/__tests__/catalog-files.test.ts +138 -0
  25. package/src/__tests__/channel-invite-transport.test.ts +2 -2
  26. package/src/__tests__/channel-readiness-routes.test.ts +16 -20
  27. package/src/__tests__/channel-readiness-service.test.ts +12 -7
  28. package/src/__tests__/checker.test.ts +157 -10
  29. package/src/__tests__/clawhub-files.test.ts +347 -0
  30. package/src/__tests__/commit-message-enrichment-service.test.ts +36 -19
  31. package/src/__tests__/config-analysis.test.ts +100 -0
  32. package/src/__tests__/config-schema.test.ts +1013 -66
  33. package/src/__tests__/config-watcher-cleanup-throttle.test.ts +339 -0
  34. package/src/__tests__/config-watcher.test.ts +43 -8
  35. package/src/__tests__/contact-store-user-file.test.ts +512 -0
  36. package/src/__tests__/contacts-write.test.ts +197 -0
  37. package/src/__tests__/context-window-manager.test.ts +88 -0
  38. package/src/__tests__/conversation-abort-tool-results.test.ts +2 -0
  39. package/src/__tests__/conversation-agent-loop-overflow.test.ts +1 -0
  40. package/src/__tests__/conversation-agent-loop.test.ts +98 -2
  41. package/src/__tests__/conversation-confirmation-signals.test.ts +135 -0
  42. package/src/__tests__/conversation-error.test.ts +70 -0
  43. package/src/__tests__/conversation-history-web-search.test.ts +11 -4
  44. package/src/__tests__/conversation-init.benchmark.test.ts +6 -1
  45. package/src/__tests__/conversation-launcher-skill-regression.test.ts +51 -0
  46. package/src/__tests__/conversation-list-source.test.ts +145 -0
  47. package/src/__tests__/conversation-pre-run-repair.test.ts +2 -0
  48. package/src/__tests__/conversation-provider-retry-repair.test.ts +2 -0
  49. package/src/__tests__/conversation-queue.test.ts +901 -60
  50. package/src/__tests__/conversation-routes-disk-view.test.ts +270 -0
  51. package/src/__tests__/conversation-runtime-assembly.test.ts +55 -0
  52. package/src/__tests__/conversation-skill-tools.test.ts +7 -4
  53. package/src/__tests__/conversation-slash-commands.test.ts +33 -0
  54. package/src/__tests__/conversation-slash-queue.test.ts +89 -18
  55. package/src/__tests__/conversation-slash-unknown.test.ts +2 -0
  56. package/src/__tests__/conversation-tool-setup-batch-authorized.test.ts +226 -0
  57. package/src/__tests__/conversation-workspace-injection.test.ts +2 -0
  58. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +2 -0
  59. package/src/__tests__/credential-health-service.test.ts +352 -0
  60. package/src/__tests__/credential-security-invariants.test.ts +5 -3
  61. package/src/__tests__/credential-vault-unit.test.ts +379 -3
  62. package/src/__tests__/credentials-cli.test.ts +40 -16
  63. package/src/__tests__/cross-provider-web-search.test.ts +146 -35
  64. package/src/__tests__/deterministic-verification-control-plane.test.ts +10 -1
  65. package/src/__tests__/device-id.test.ts +112 -0
  66. package/src/__tests__/docker-signing-key-bootstrap.test.ts +167 -4
  67. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +1 -3
  68. package/src/__tests__/email-html-renderer.test.ts +71 -0
  69. package/src/__tests__/email-invite-adapter.test.ts +36 -32
  70. package/src/__tests__/emit-event-signal.test.ts +71 -0
  71. package/src/__tests__/extension-id-sync-guard.test.ts +75 -8
  72. package/src/__tests__/fixtures/mock-chrome-extension.ts +11 -0
  73. package/src/__tests__/gateway-only-enforcement.test.ts +206 -1
  74. package/src/__tests__/gateway-only-guard.test.ts +0 -1
  75. package/src/__tests__/gemini-provider.test.ts +64 -0
  76. package/src/__tests__/get-skill-detail-audit.test.ts +325 -0
  77. package/src/__tests__/gmail-archive-fallback.test.ts +193 -0
  78. package/src/__tests__/gmail-archive-gate.test.ts +246 -0
  79. package/src/__tests__/gmail-preferences.test.ts +117 -0
  80. package/src/__tests__/headless-browser-interactions.test.ts +43 -0
  81. package/src/__tests__/headless-browser-mode.test.ts +614 -0
  82. package/src/__tests__/headless-browser-navigate.test.ts +142 -5
  83. package/src/__tests__/headless-browser-read-tools.test.ts +11 -0
  84. package/src/__tests__/headless-browser-snapshot.test.ts +10 -0
  85. package/src/__tests__/heartbeat-service.test.ts +70 -17
  86. package/src/__tests__/home-state-routes.test.ts +162 -0
  87. package/src/__tests__/host-bash-proxy.test.ts +0 -5
  88. package/src/__tests__/host-browser-e2e-cloud.test.ts +138 -4
  89. package/src/__tests__/host-browser-e2e-self-hosted.test.ts +4 -4
  90. package/src/__tests__/host-browser-ws-events-e2e.test.ts +103 -0
  91. package/src/__tests__/host-cu-proxy.test.ts +0 -5
  92. package/src/__tests__/identity-intro-cache.test.ts +40 -10
  93. package/src/__tests__/init-feature-flag-overrides.test.ts +38 -112
  94. package/src/__tests__/jobs-store-upsert-debounced.test.ts +141 -0
  95. package/src/__tests__/llm-context-normalization.test.ts +488 -0
  96. package/src/__tests__/llm-context-route-provider.test.ts +86 -5
  97. package/src/__tests__/llm-usage-store.test.ts +363 -0
  98. package/src/__tests__/media-stream-output.test.ts +555 -0
  99. package/src/__tests__/media-stream-parser.test.ts +374 -0
  100. package/src/__tests__/media-stream-server-integration.test.ts +1234 -0
  101. package/src/__tests__/media-stream-stt-session.test.ts +588 -0
  102. package/src/__tests__/media-turn-detector.test.ts +440 -0
  103. package/src/__tests__/message-queue.test.ts +125 -0
  104. package/src/__tests__/migration-export-http.test.ts +6 -6
  105. package/src/__tests__/migration-import-commit-http.test.ts +8 -6
  106. package/src/__tests__/migration-import-preflight-http.test.ts +6 -5
  107. package/src/__tests__/migration-validate-http.test.ts +3 -3
  108. package/src/__tests__/mock-gateway-ipc.ts +151 -0
  109. package/src/__tests__/model-intents.test.ts +2 -2
  110. package/src/__tests__/oauth-apps-routes.test.ts +1 -0
  111. package/src/__tests__/oauth-cli.test.ts +2 -0
  112. package/src/__tests__/oauth-connect-orchestrator.test.ts +2 -0
  113. package/src/__tests__/oauth-provider-serializer.test.ts +1 -0
  114. package/src/__tests__/oauth-providers-routes.test.ts +2 -0
  115. package/src/__tests__/oauth-store.test.ts +85 -0
  116. package/src/__tests__/oauth2-gateway-transport.test.ts +249 -6
  117. package/src/__tests__/onboarding-template-contract.test.ts +6 -13
  118. package/src/__tests__/openai-provider.test.ts +176 -0
  119. package/src/__tests__/openai-responses-cutover-guard.test.ts +184 -0
  120. package/src/__tests__/openai-responses-provider.test.ts +1105 -0
  121. package/src/__tests__/openrouter-token-estimation.test.ts +100 -0
  122. package/src/__tests__/outlook-unsubscribe.test.ts +31 -2
  123. package/src/__tests__/persona-resolver.test.ts +251 -0
  124. package/src/__tests__/platform-bash-auto-approve.test.ts +4 -0
  125. package/src/__tests__/platform.test.ts +92 -1
  126. package/src/__tests__/post-turn-tool-result-truncation.test.ts +47 -0
  127. package/src/__tests__/prechat-onboarding-contract.test.ts +267 -0
  128. package/src/__tests__/pricing.test.ts +174 -0
  129. package/src/__tests__/qdrant-manager.test.ts +29 -8
  130. package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +194 -0
  131. package/src/__tests__/relationship-state-contract.test.ts +175 -0
  132. package/src/__tests__/relay-server.test.ts +423 -5
  133. package/src/__tests__/search-skills-unified.test.ts +118 -0
  134. package/src/__tests__/secret-scanner-executor.test.ts +4 -0
  135. package/src/__tests__/secure-keys.test.ts +107 -0
  136. package/src/__tests__/send-endpoint-busy.test.ts +5 -1
  137. package/src/__tests__/sequence-store.test.ts +1 -1
  138. package/src/__tests__/server-history-render.test.ts +49 -0
  139. package/src/__tests__/settings-routes.test.ts +201 -0
  140. package/src/__tests__/skill-load-feature-flag.test.ts +1 -0
  141. package/src/__tests__/skills-file-content-endpoint.test.ts +276 -145
  142. package/src/__tests__/skills-files-catalog-fallback.test.ts +381 -93
  143. package/src/__tests__/skills.test.ts +5 -2
  144. package/src/__tests__/skillssh-files.test.ts +446 -0
  145. package/src/__tests__/slack-block-formatting.test.ts +110 -0
  146. package/src/__tests__/slack-channel-config.test.ts +564 -1
  147. package/src/__tests__/stt-catalog-parity.test.ts +282 -0
  148. package/src/__tests__/stt-stream-session.test.ts +535 -0
  149. package/src/__tests__/system-prompt.test.ts +112 -26
  150. package/src/__tests__/telephony-stt-routing.test.ts +329 -0
  151. package/src/__tests__/terminal-tools.test.ts +18 -7
  152. package/src/__tests__/test-preload.ts +18 -0
  153. package/src/__tests__/test-support/browser-skill-harness.ts +4 -1
  154. package/src/__tests__/tool-executor-lifecycle-events.test.ts +9 -5
  155. package/src/__tests__/tool-executor-shell-integration.test.ts +4 -0
  156. package/src/__tests__/tool-executor.test.ts +33 -24
  157. package/src/__tests__/tool-result-truncation.test.ts +36 -0
  158. package/src/__tests__/trust-store.test.ts +7 -1
  159. package/src/__tests__/trusted-contact-approval-notifier.test.ts +1 -1
  160. package/src/__tests__/tts-catalog-parity.test.ts +345 -0
  161. package/src/__tests__/twilio-routes-twiml.test.ts +512 -114
  162. package/src/__tests__/twilio-routes.test.ts +376 -0
  163. package/src/__tests__/unicode.test.ts +293 -0
  164. package/src/__tests__/update-bulletin-format.test.ts +59 -0
  165. package/src/__tests__/update-bulletin.test.ts +206 -5
  166. package/src/__tests__/usage-routes.test.ts +25 -4
  167. package/src/__tests__/user-reference.test.ts +46 -61
  168. package/src/__tests__/verification-control-plane-policy.test.ts +4 -0
  169. package/src/__tests__/voice-config-update.test.ts +403 -0
  170. package/src/__tests__/voice-quality.test.ts +434 -19
  171. package/src/__tests__/workspace-heartbeat-service.test.ts +7 -0
  172. package/src/__tests__/workspace-migration-033-stt-service-explicit-config.test.ts +547 -0
  173. package/src/__tests__/workspace-migration-034-remove-calls-voice-transcription-provider.test.ts +596 -0
  174. package/src/__tests__/workspace-migration-drop-user-md.test.ts +368 -0
  175. package/src/__tests__/workspace-migration-meets.test.ts +244 -0
  176. package/src/__tests__/workspace-migration-seed-device-id.test.ts +14 -20
  177. package/src/__tests__/workspace-policy.test.ts +2 -0
  178. package/src/agent/image-optimize.ts +24 -12
  179. package/src/agent/loop.ts +43 -3
  180. package/src/backup/__tests__/backup-key.test.ts +152 -0
  181. package/src/backup/__tests__/backup-worker.test.ts +767 -0
  182. package/src/backup/__tests__/list-snapshots.test.ts +87 -0
  183. package/src/backup/__tests__/local-writer.test.ts +218 -0
  184. package/src/backup/__tests__/offsite-writer.test.ts +641 -0
  185. package/src/backup/__tests__/paths.test.ts +300 -0
  186. package/src/backup/__tests__/restore.test.ts +498 -0
  187. package/src/backup/__tests__/snapshot-lock.test.ts +352 -0
  188. package/src/backup/__tests__/stream-crypt.test.ts +228 -0
  189. package/src/backup/backup-key.ts +137 -0
  190. package/src/backup/backup-worker.ts +459 -0
  191. package/src/backup/list-snapshots.ts +147 -0
  192. package/src/backup/local-writer.ts +133 -0
  193. package/src/backup/offsite-writer.ts +222 -0
  194. package/src/backup/paths.ts +226 -0
  195. package/src/backup/restore.ts +322 -0
  196. package/src/backup/snapshot-lock.ts +431 -0
  197. package/src/backup/stream-crypt.ts +263 -0
  198. package/src/bundler/package-resolver.ts +4 -0
  199. package/src/calls/audio-store.ts +11 -5
  200. package/src/calls/call-controller.ts +226 -71
  201. package/src/calls/call-domain.ts +9 -0
  202. package/src/calls/call-speech-output.ts +190 -0
  203. package/src/calls/call-transport.ts +77 -0
  204. package/src/calls/media-stream-audio-transcode.ts +173 -0
  205. package/src/calls/media-stream-output.ts +660 -0
  206. package/src/calls/media-stream-parser.ts +300 -0
  207. package/src/calls/media-stream-protocol.ts +166 -0
  208. package/src/calls/media-stream-server.ts +592 -0
  209. package/src/calls/media-stream-stt-session.ts +460 -0
  210. package/src/calls/media-turn-detector.ts +230 -0
  211. package/src/calls/relay-server.ts +90 -75
  212. package/src/calls/resolve-call-tts-provider.ts +136 -0
  213. package/src/calls/telephony-stt-routing.ts +145 -0
  214. package/src/calls/tts-call-strategy.ts +161 -0
  215. package/src/calls/tts-text-sanitizer.ts +32 -16
  216. package/src/calls/twilio-routes.ts +281 -17
  217. package/src/calls/voice-quality.ts +78 -35
  218. package/src/calls/voice-session-bridge.ts +8 -1
  219. package/src/channels/types.ts +16 -0
  220. package/src/cli/__tests__/run-assistant-command.ts +11 -1
  221. package/src/cli/commands/__tests__/backup.test.ts +1165 -0
  222. package/src/cli/commands/__tests__/domain-register.test.ts +234 -0
  223. package/src/cli/commands/__tests__/domain-status.test.ts +132 -0
  224. package/src/cli/commands/__tests__/email-attachment.test.ts +422 -0
  225. package/src/cli/commands/__tests__/email-download.test.ts +16 -1
  226. package/src/cli/commands/__tests__/email-list.test.ts +22 -4
  227. package/src/cli/commands/__tests__/email-register.test.ts +4 -4
  228. package/src/cli/commands/__tests__/email-send.test.ts +37 -4
  229. package/src/cli/commands/__tests__/email-status.test.ts +5 -1
  230. package/src/cli/commands/__tests__/email-unregister.test.ts +34 -5
  231. package/src/cli/commands/backup.ts +993 -0
  232. package/src/cli/commands/conversations.ts +77 -0
  233. package/src/cli/commands/credentials.ts +0 -1
  234. package/src/cli/commands/domain.ts +210 -0
  235. package/src/cli/commands/email.ts +255 -3
  236. package/src/cli/commands/oauth/__tests__/connect.test.ts +12 -0
  237. package/src/cli/commands/oauth/__tests__/providers-delete.test.ts +1 -0
  238. package/src/cli/commands/oauth/__tests__/providers-register.test.ts +1 -0
  239. package/src/cli/commands/oauth/__tests__/providers-update.test.ts +1 -0
  240. package/src/cli/commands/oauth/mode.ts +12 -3
  241. package/src/cli/commands/oauth/providers.ts +15 -0
  242. package/src/cli/commands/oauth/shared.ts +2 -1
  243. package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +4 -9
  244. package/src/cli/commands/platform/__tests__/connect.test.ts +6 -0
  245. package/src/cli/commands/platform/__tests__/disconnect.test.ts +7 -1
  246. package/src/cli/commands/platform/__tests__/status.test.ts +6 -0
  247. package/src/cli/program.ts +30 -4
  248. package/src/config/__tests__/backup-schema.test.ts +134 -0
  249. package/src/config/assistant-feature-flags.ts +61 -62
  250. package/src/config/bundled-skills/app-builder/references/CUSTOM_ROUTES.md +37 -1
  251. package/src/config/bundled-skills/browser/SKILL.md +30 -5
  252. package/src/config/bundled-skills/browser/TOOLS.json +123 -0
  253. package/src/config/bundled-skills/browser/tools/browser-attach.ts +12 -0
  254. package/src/config/bundled-skills/browser/tools/browser-detach.ts +12 -0
  255. package/src/config/bundled-skills/browser/tools/browser-status.ts +12 -0
  256. package/src/config/bundled-skills/browser/tools/browser-wait-for-download.ts +17 -0
  257. package/src/config/bundled-skills/contacts/SKILL.md +2 -2
  258. package/src/config/bundled-skills/gmail/SKILL.md +53 -7
  259. package/src/config/bundled-skills/gmail/TOOLS.json +33 -3
  260. package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +116 -9
  261. package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +138 -11
  262. package/src/config/bundled-skills/gmail/tools/gmail-preferences-tool.ts +59 -0
  263. package/src/config/bundled-skills/gmail/tools/gmail-preferences.ts +82 -0
  264. package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +113 -17
  265. package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +2 -2
  266. package/src/config/bundled-skills/media-processing/SKILL.md +3 -9
  267. package/src/config/bundled-skills/media-processing/TOOLS.json +1 -6
  268. package/src/config/bundled-skills/media-processing/__tests__/audio-transcribe.test.ts +125 -0
  269. package/src/config/bundled-skills/media-processing/__tests__/extract-keyframes.test.ts +181 -0
  270. package/src/config/bundled-skills/media-processing/__tests__/preprocess-audio.test.ts +141 -0
  271. package/src/config/bundled-skills/media-processing/services/audio-transcribe.ts +32 -87
  272. package/src/config/bundled-skills/media-processing/services/preprocess.ts +8 -4
  273. package/src/config/bundled-skills/media-processing/tools/extract-keyframes.ts +0 -10
  274. package/src/config/bundled-skills/messaging/SKILL.md +3 -3
  275. package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +2 -2
  276. package/src/config/bundled-skills/outlook/SKILL.md +2 -2
  277. package/src/config/bundled-skills/outlook/tools/outlook-unsubscribe.ts +2 -2
  278. package/src/config/bundled-skills/phone-calls/SKILL.md +2 -2
  279. package/src/config/bundled-skills/phone-calls/references/CONFIG.md +27 -18
  280. package/src/config/bundled-skills/phone-calls/references/TROUBLESHOOTING.md +3 -3
  281. package/src/config/bundled-skills/settings/TOOLS.json +3 -3
  282. package/src/config/bundled-skills/settings/tools/voice-config-update.ts +26 -22
  283. package/src/config/bundled-skills/slack/SKILL.md +1 -0
  284. package/src/config/bundled-skills/transcribe/SKILL.md +9 -14
  285. package/src/config/bundled-skills/transcribe/TOOLS.json +2 -7
  286. package/src/config/bundled-skills/transcribe/tools/transcribe-media.test.ts +256 -0
  287. package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +38 -188
  288. package/src/config/bundled-tool-registry.ts +8 -0
  289. package/src/config/env-registry.ts +24 -0
  290. package/src/config/env.ts +34 -10
  291. package/src/config/feature-flag-registry.json +46 -14
  292. package/src/config/loader.ts +26 -12
  293. package/src/config/schema.ts +35 -10
  294. package/src/config/schemas/__tests__/stt.test.ts +43 -0
  295. package/src/config/schemas/analysis.ts +51 -0
  296. package/src/config/schemas/backup.ts +72 -0
  297. package/src/config/schemas/calls.ts +1 -26
  298. package/src/config/schemas/elevenlabs.ts +0 -59
  299. package/src/config/schemas/filing.ts +47 -7
  300. package/src/config/schemas/heartbeat.ts +27 -5
  301. package/src/config/schemas/host-browser.ts +47 -1
  302. package/src/config/schemas/inference.ts +1 -1
  303. package/src/config/schemas/memory-lifecycle.ts +14 -2
  304. package/src/config/schemas/services.ts +44 -0
  305. package/src/config/schemas/stt.ts +59 -0
  306. package/src/config/schemas/tts.ts +230 -0
  307. package/src/config/schemas/updates.ts +14 -0
  308. package/src/config/skills.ts +4 -0
  309. package/src/config/types.ts +4 -0
  310. package/src/contacts/contact-store.ts +56 -11
  311. package/src/contacts/contacts-write.ts +38 -1
  312. package/src/context/post-turn-tool-result-truncation.ts +3 -2
  313. package/src/context/tool-result-truncation.ts +2 -1
  314. package/src/context/window-manager.ts +45 -12
  315. package/src/credential-execution/executable-discovery.ts +12 -2
  316. package/src/credential-execution/process-manager.ts +33 -2
  317. package/src/credential-health/credential-health-service.ts +366 -0
  318. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +324 -0
  319. package/src/daemon/__tests__/conversation-surfaces-launch.test.ts +497 -0
  320. package/src/daemon/__tests__/conversation-tool-setup.test.ts +17 -8
  321. package/src/daemon/__tests__/lifecycle-startup-ordering.test.ts +127 -0
  322. package/src/daemon/config-watcher.ts +99 -5
  323. package/src/daemon/conversation-agent-loop-handlers.ts +6 -0
  324. package/src/daemon/conversation-agent-loop.ts +101 -24
  325. package/src/daemon/conversation-error.ts +11 -0
  326. package/src/daemon/conversation-history.ts +40 -6
  327. package/src/daemon/conversation-launch.ts +220 -0
  328. package/src/daemon/conversation-lifecycle.ts +59 -9
  329. package/src/daemon/conversation-messaging.ts +37 -3
  330. package/src/daemon/conversation-notifiers.ts +5 -0
  331. package/src/daemon/conversation-process.ts +581 -19
  332. package/src/daemon/conversation-queue-manager.ts +24 -0
  333. package/src/daemon/conversation-runtime-assembly.ts +11 -1
  334. package/src/daemon/conversation-slash.ts +36 -0
  335. package/src/daemon/conversation-surfaces.ts +94 -4
  336. package/src/daemon/conversation-tool-setup.ts +25 -0
  337. package/src/daemon/conversation-usage.ts +7 -4
  338. package/src/daemon/conversation.ts +86 -28
  339. package/src/daemon/handlers/config-slack-channel.ts +269 -94
  340. package/src/daemon/handlers/conversations.ts +4 -1
  341. package/src/daemon/handlers/shared.ts +22 -0
  342. package/src/daemon/handlers/skills.ts +321 -77
  343. package/src/daemon/host-browser-proxy.ts +2 -1
  344. package/src/daemon/lifecycle.ts +122 -25
  345. package/src/daemon/message-protocol.ts +6 -0
  346. package/src/daemon/message-types/conversations.ts +34 -1
  347. package/src/daemon/message-types/home.ts +40 -0
  348. package/src/daemon/message-types/meet.ts +143 -0
  349. package/src/daemon/message-types/messages.ts +14 -0
  350. package/src/daemon/message-types/schedules.ts +34 -2
  351. package/src/daemon/message-types/skills.ts +16 -0
  352. package/src/daemon/message-types/surfaces.ts +2 -0
  353. package/src/daemon/server.ts +347 -2
  354. package/src/daemon/shutdown-handlers.ts +32 -4
  355. package/src/daemon/shutdown-registry.ts +40 -0
  356. package/src/daemon/tool-side-effects.ts +9 -0
  357. package/src/email/html-renderer.ts +76 -0
  358. package/src/heartbeat/heartbeat-service.ts +93 -7
  359. package/src/home/__tests__/assistant-feed-authoring.test.ts +156 -0
  360. package/src/home/__tests__/emit-feed-event.test.ts +169 -0
  361. package/src/home/__tests__/feed-scheduler.test.ts +194 -0
  362. package/src/home/__tests__/feed-types.test.ts +275 -0
  363. package/src/home/__tests__/feed-writer.test.ts +688 -0
  364. package/src/home/__tests__/phase5-exit-criteria.test.ts +212 -0
  365. package/src/home/__tests__/platform-gmail-digest.test.ts +222 -0
  366. package/src/home/__tests__/progress-formula.test.ts +213 -0
  367. package/src/home/__tests__/relationship-state-writer.test.ts +740 -0
  368. package/src/home/__tests__/rollup-producer.test.ts +398 -0
  369. package/src/home/assistant-feed-authoring.ts +124 -0
  370. package/src/home/emit-feed-event.ts +158 -0
  371. package/src/home/feed-scheduler.ts +247 -0
  372. package/src/home/feed-types.ts +181 -0
  373. package/src/home/feed-writer.ts +469 -0
  374. package/src/home/platform-gmail-digest.ts +163 -0
  375. package/src/home/progress-formula.ts +86 -0
  376. package/src/home/relationship-state-writer.ts +824 -0
  377. package/src/home/relationship-state.ts +143 -0
  378. package/src/home/rollup-producer.ts +384 -0
  379. package/src/hooks/runner.ts +7 -0
  380. package/src/inbound/platform-callback-registration.ts +12 -3
  381. package/src/inbound/public-ingress-urls.ts +12 -0
  382. package/src/instrument.ts +1 -1
  383. package/src/ipc/__tests__/cli-ipc.test.ts +200 -0
  384. package/src/ipc/cli-client.ts +151 -0
  385. package/src/ipc/cli-server.ts +234 -0
  386. package/src/ipc/gateway-client.ts +180 -0
  387. package/src/ipc/routes/index.ts +5 -0
  388. package/src/ipc/routes/wake-conversation.ts +19 -0
  389. package/src/memory/__tests__/auto-analysis-enqueue.test.ts +356 -0
  390. package/src/memory/__tests__/auto-analysis-guard.test.ts +57 -0
  391. package/src/memory/__tests__/conversation-analyze-job.test.ts +232 -0
  392. package/src/memory/__tests__/find-analysis-conversation.test.ts +196 -0
  393. package/src/memory/app-store.ts +1 -1
  394. package/src/memory/attachments-store.ts +70 -0
  395. package/src/memory/auto-analysis-enqueue.ts +127 -0
  396. package/src/memory/auto-analysis-guard.ts +27 -0
  397. package/src/memory/cleanup-schedule-state.ts +37 -0
  398. package/src/memory/conversation-analyze-job.ts +73 -0
  399. package/src/memory/conversation-crud.ts +99 -0
  400. package/src/memory/conversation-disk-view.ts +7 -0
  401. package/src/memory/conversation-group-migration.ts +34 -2
  402. package/src/memory/conversation-queries.ts +6 -5
  403. package/src/memory/db-init.ts +6 -0
  404. package/src/memory/db-maintenance.ts +108 -0
  405. package/src/memory/db.ts +1 -0
  406. package/src/memory/graph/conversation-graph-memory.ts +15 -0
  407. package/src/memory/graph/extraction.test.ts +23 -0
  408. package/src/memory/graph/extraction.ts +8 -0
  409. package/src/memory/graph/retriever.ts +27 -18
  410. package/src/memory/graph/scoring.test.ts +186 -0
  411. package/src/memory/graph/scoring.ts +31 -1
  412. package/src/memory/graph/tools.ts +1 -1
  413. package/src/memory/group-crud.ts +6 -1
  414. package/src/memory/indexer.ts +95 -16
  415. package/src/memory/job-handlers/cleanup.ts +11 -8
  416. package/src/memory/job-handlers/conversation-starters.ts +16 -10
  417. package/src/memory/jobs-store.ts +64 -4
  418. package/src/memory/jobs-worker.ts +22 -9
  419. package/src/memory/llm-usage-store.ts +92 -56
  420. package/src/memory/migrations/219-oauth-providers-token-exchange-body-format.ts +15 -0
  421. package/src/memory/migrations/220-normalize-user-file-by-principal.ts +190 -0
  422. package/src/memory/migrations/221-conversations-archived-at.ts +16 -0
  423. package/src/memory/migrations/index.ts +6 -0
  424. package/src/memory/migrations/registry.ts +8 -0
  425. package/src/memory/qdrant-manager.ts +43 -16
  426. package/src/memory/schema/conversations.ts +2 -0
  427. package/src/memory/schema/oauth.ts +3 -0
  428. package/src/memory/usage-buckets.ts +396 -0
  429. package/src/messaging/providers/gmail/client.ts +57 -6
  430. package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +282 -0
  431. package/src/messaging/providers/slack/adapter.ts +143 -38
  432. package/src/messaging/providers/slack/client.ts +16 -0
  433. package/src/messaging/providers/slack/types.ts +4 -0
  434. package/src/notifications/decision-engine.ts +3 -3
  435. package/src/notifications/signal.ts +5 -0
  436. package/src/oauth/__tests__/identity-verifier.test.ts +1 -0
  437. package/src/oauth/byo-connection.test.ts +18 -1
  438. package/src/oauth/byo-connection.ts +3 -1
  439. package/src/oauth/connect-orchestrator.ts +2 -0
  440. package/src/oauth/connection-resolver.ts +6 -2
  441. package/src/oauth/connection.ts +2 -0
  442. package/src/oauth/oauth-store.ts +9 -0
  443. package/src/oauth/platform-connection.test.ts +98 -0
  444. package/src/oauth/platform-connection.ts +52 -31
  445. package/src/oauth/seed-providers.ts +7 -0
  446. package/src/permissions/checker.ts +16 -6
  447. package/src/permissions/defaults.ts +49 -1
  448. package/src/permissions/trust-store.ts +3 -3
  449. package/src/permissions/workspace-policy.ts +3 -0
  450. package/src/platform/client.test.ts +10 -0
  451. package/src/platform/sync-identity.ts +129 -0
  452. package/src/prompts/persona-resolver.ts +126 -2
  453. package/src/prompts/system-prompt.ts +59 -18
  454. package/src/prompts/templates/BOOTSTRAP.md +5 -5
  455. package/src/prompts/templates/SOUL.md +3 -1
  456. package/src/prompts/templates/UPDATES.md +12 -0
  457. package/src/prompts/templates/channels/slack.md +20 -0
  458. package/src/prompts/update-bulletin-format.ts +26 -9
  459. package/src/prompts/update-bulletin.ts +34 -23
  460. package/src/prompts/user-reference.ts +20 -17
  461. package/src/providers/__tests__/provider-secret-catalog.test.ts +42 -0
  462. package/src/providers/anthropic/client.ts +157 -61
  463. package/src/providers/fireworks/client.ts +2 -2
  464. package/src/providers/gemini/client.ts +9 -1
  465. package/src/providers/model-catalog.ts +6 -0
  466. package/src/providers/model-intents.ts +4 -4
  467. package/src/providers/ollama/client.ts +2 -2
  468. package/src/providers/openai/chat-completions-provider.ts +474 -0
  469. package/src/providers/openai/client.ts +25 -440
  470. package/src/providers/openai/responses-provider.ts +502 -0
  471. package/src/providers/openrouter/client.ts +101 -4
  472. package/src/providers/provider-secret-catalog.ts +139 -0
  473. package/src/providers/registry.ts +2 -2
  474. package/src/providers/retry.ts +14 -3
  475. package/src/providers/speech-to-text/__tests__/provider-catalog.test.ts +251 -0
  476. package/src/providers/speech-to-text/__tests__/resolve.test.ts +828 -0
  477. package/src/providers/speech-to-text/deepgram-realtime.test.ts +980 -0
  478. package/src/providers/speech-to-text/deepgram-realtime.ts +767 -0
  479. package/src/providers/speech-to-text/deepgram.test.ts +332 -0
  480. package/src/providers/speech-to-text/deepgram.ts +115 -0
  481. package/src/providers/speech-to-text/google-gemini-live-stream.test.ts +743 -0
  482. package/src/providers/speech-to-text/google-gemini-live-stream.ts +625 -0
  483. package/src/providers/speech-to-text/google-gemini.test.ts +226 -0
  484. package/src/providers/speech-to-text/google-gemini.ts +101 -0
  485. package/src/providers/speech-to-text/openai-whisper-stream.test.ts +564 -0
  486. package/src/providers/speech-to-text/openai-whisper-stream.ts +381 -0
  487. package/src/providers/speech-to-text/openai-whisper.test.ts +1 -37
  488. package/src/providers/speech-to-text/openai-whisper.ts +63 -33
  489. package/src/providers/speech-to-text/provider-catalog.ts +306 -0
  490. package/src/providers/speech-to-text/resolve.ts +386 -6
  491. package/src/providers/types.ts +9 -0
  492. package/src/runtime/AGENTS.md +43 -1
  493. package/src/runtime/__tests__/agent-wake.test.ts +831 -0
  494. package/src/runtime/__tests__/runtime-mode.test.ts +62 -0
  495. package/src/runtime/__tests__/slack-block-formatting.test.ts +481 -0
  496. package/src/runtime/agent-wake.ts +512 -0
  497. package/src/runtime/auth/__tests__/route-policy.test.ts +40 -0
  498. package/src/runtime/auth/route-policy.ts +30 -5
  499. package/src/runtime/auth/token-service.ts +56 -1
  500. package/src/runtime/btw-sidechain.ts +2 -0
  501. package/src/runtime/capability-tokens.ts +10 -10
  502. package/src/runtime/channel-invite-transport.ts +1 -1
  503. package/src/runtime/channel-invite-transports/email.ts +14 -6
  504. package/src/runtime/channel-readiness-service.ts +12 -22
  505. package/src/runtime/chrome-extension-registry.ts +38 -2
  506. package/src/runtime/http-server.ts +395 -10
  507. package/src/runtime/http-types.ts +6 -2
  508. package/src/runtime/migrations/__tests__/vbundle-import-credentials.test.ts +36 -0
  509. package/src/runtime/migrations/__tests__/vbundle-legacy-user-md.test.ts +360 -0
  510. package/src/runtime/migrations/migration-transport.ts +1 -0
  511. package/src/runtime/migrations/migration-wizard.ts +1 -0
  512. package/src/runtime/migrations/vbundle-import-analyzer.ts +77 -1
  513. package/src/runtime/migrations/vbundle-importer.ts +34 -0
  514. package/src/runtime/pending-interactions.ts +0 -11
  515. package/src/runtime/routes/__tests__/backup-routes.test.ts +967 -0
  516. package/src/runtime/routes/__tests__/home-feed-routes.test.ts +507 -0
  517. package/src/runtime/routes/__tests__/migration-import-credential-filter.test.ts +208 -0
  518. package/src/runtime/routes/__tests__/stt-routes.test.ts +406 -0
  519. package/src/runtime/routes/__tests__/tts-routes.test.ts +474 -0
  520. package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +148 -17
  521. package/src/runtime/routes/app-management-routes.ts +12 -18
  522. package/src/runtime/routes/attachment-routes.test.ts +9 -3
  523. package/src/runtime/routes/attachment-routes.ts +216 -17
  524. package/src/runtime/routes/backup-routes.ts +519 -0
  525. package/src/runtime/routes/browser-extension-pair-routes.ts +82 -23
  526. package/src/runtime/routes/btw-routes.ts +8 -6
  527. package/src/runtime/routes/contact-routes.test.ts +298 -0
  528. package/src/runtime/routes/contact-routes.ts +132 -5
  529. package/src/runtime/routes/conversation-analysis-routes.ts +22 -142
  530. package/src/runtime/routes/conversation-management-routes.ts +115 -0
  531. package/src/runtime/routes/conversation-routes.ts +367 -146
  532. package/src/runtime/routes/filing-routes.ts +93 -0
  533. package/src/runtime/routes/home-feed-routes.ts +334 -0
  534. package/src/runtime/routes/home-state-routes.ts +138 -0
  535. package/src/runtime/routes/host-browser-routes.ts +3 -14
  536. package/src/runtime/routes/identity-intro-cache.ts +7 -3
  537. package/src/runtime/routes/identity-routes.ts +3 -17
  538. package/src/runtime/routes/inbound-stages/transcribe-audio.test.ts +46 -39
  539. package/src/runtime/routes/inbound-stages/transcribe-audio.ts +15 -15
  540. package/src/runtime/routes/integrations/slack/__tests__/channel.test.ts +137 -0
  541. package/src/runtime/routes/integrations/slack/__tests__/share.test.ts +179 -0
  542. package/src/runtime/routes/integrations/slack/channel.ts +11 -3
  543. package/src/runtime/routes/integrations/slack/share.ts +45 -7
  544. package/src/runtime/routes/llm-context-normalization.ts +303 -0
  545. package/src/runtime/routes/memory-item-routes.test.ts +3 -2
  546. package/src/runtime/routes/migration-routes.ts +40 -5
  547. package/src/runtime/routes/settings-routes.ts +22 -5
  548. package/src/runtime/routes/skills-routes.ts +76 -7
  549. package/src/runtime/routes/stt-routes.ts +233 -0
  550. package/src/runtime/routes/surface-action-routes.ts +41 -2
  551. package/src/runtime/routes/tts-routes.ts +108 -24
  552. package/src/runtime/routes/usage-routes.ts +30 -2
  553. package/src/runtime/routes/user-route-dispatcher.ts +50 -5
  554. package/src/runtime/routes/user-routes.ts +13 -1
  555. package/src/runtime/routes/work-items-routes.ts +8 -1
  556. package/src/runtime/runtime-mode.ts +33 -0
  557. package/src/runtime/services/__tests__/analyze-conversation.test.ts +444 -0
  558. package/src/runtime/services/__tests__/analyze-deps-singleton.test.ts +67 -0
  559. package/src/runtime/services/__tests__/auto-analysis-prompt.test.ts +53 -0
  560. package/src/runtime/services/__tests__/manual-analysis-prompt.test.ts +41 -0
  561. package/src/runtime/services/analyze-conversation.ts +344 -0
  562. package/src/runtime/services/analyze-deps-singleton.ts +32 -0
  563. package/src/runtime/services/auto-analysis-prompt.ts +55 -0
  564. package/src/runtime/skill-route-registry.ts +49 -0
  565. package/src/runtime/slack-block-formatting.ts +437 -10
  566. package/src/schedule/scheduler.ts +50 -0
  567. package/src/security/oauth2.ts +26 -4
  568. package/src/security/secure-keys.ts +25 -2
  569. package/src/security/token-manager.ts +8 -0
  570. package/src/sequence/engine.ts +23 -0
  571. package/src/sequence/types.ts +1 -1
  572. package/src/skills/catalog-files.ts +64 -2
  573. package/src/skills/category-inference.ts +122 -0
  574. package/src/skills/clawhub-files.ts +213 -0
  575. package/src/skills/clawhub.ts +84 -23
  576. package/src/skills/skill-file-provider.ts +40 -0
  577. package/src/skills/skillssh-files.ts +395 -0
  578. package/src/skills/skillssh-registry.ts +4 -4
  579. package/src/stt/__tests__/daemon-batch-transcriber.test.ts +392 -0
  580. package/src/stt/__tests__/types.test.ts +89 -0
  581. package/src/stt/daemon-batch-transcriber.ts +195 -0
  582. package/src/stt/stt-stream-session.ts +499 -0
  583. package/src/stt/types.ts +330 -0
  584. package/src/stt/wav-encoder.test.ts +373 -0
  585. package/src/stt/wav-encoder.ts +175 -0
  586. package/src/subagent/manager.ts +38 -14
  587. package/src/tools/browser/__tests__/browser-mode.test.ts +119 -0
  588. package/src/tools/browser/__tests__/browser-status.test.ts +123 -0
  589. package/src/tools/browser/browser-execution.ts +1163 -23
  590. package/src/tools/browser/browser-manager.ts +45 -0
  591. package/src/tools/browser/browser-mode-constants.ts +12 -0
  592. package/src/tools/browser/browser-mode.ts +92 -0
  593. package/src/tools/browser/browser-status-constants.ts +33 -0
  594. package/src/tools/browser/cdp-client/__tests__/cdp-inspect-client.test.ts +393 -0
  595. package/src/tools/browser/cdp-client/__tests__/extension-cdp-client.test.ts +29 -0
  596. package/src/tools/browser/cdp-client/__tests__/factory.test.ts +1648 -32
  597. package/src/tools/browser/cdp-client/cdp-inspect/__tests__/discovery.test.ts +264 -0
  598. package/src/tools/browser/cdp-client/cdp-inspect/discovery.ts +183 -17
  599. package/src/tools/browser/cdp-client/cdp-inspect-client.ts +254 -21
  600. package/src/tools/browser/cdp-client/errors.ts +15 -0
  601. package/src/tools/browser/cdp-client/extension-cdp-client.ts +39 -16
  602. package/src/tools/browser/cdp-client/factory.ts +797 -87
  603. package/src/tools/browser/cdp-client/index.ts +16 -2
  604. package/src/tools/browser/cdp-client/types.ts +68 -0
  605. package/src/tools/credentials/vault.ts +35 -6
  606. package/src/tools/network/web-fetch.ts +5 -2
  607. package/src/tools/network/web-search.ts +5 -2
  608. package/src/tools/shared/shell-output.ts +3 -1
  609. package/src/tools/side-effects.ts +2 -0
  610. package/src/tools/skills/sandbox-runner.ts +3 -2
  611. package/src/tools/terminal/safe-env.ts +10 -2
  612. package/src/tools/terminal/shell.ts +15 -4
  613. package/src/tools/tool-manifest.ts +21 -0
  614. package/src/tools/types.ts +17 -0
  615. package/src/tools/ui-surface/definitions.ts +6 -1
  616. package/src/tts/__tests__/provider-adapters.test.ts +834 -0
  617. package/src/tts/__tests__/provider-catalog-consistency.test.ts +196 -0
  618. package/src/tts/__tests__/provider-catalog.test.ts +183 -0
  619. package/src/tts/__tests__/provider-registry.test.ts +90 -0
  620. package/src/tts/provider-catalog.ts +201 -0
  621. package/src/tts/provider-registry.ts +73 -0
  622. package/src/tts/providers/deepgram-provider.ts +219 -0
  623. package/src/tts/providers/elevenlabs-provider.ts +211 -0
  624. package/src/tts/providers/fish-audio-provider.ts +183 -0
  625. package/src/tts/providers/index.ts +42 -0
  626. package/src/tts/providers/register-builtins.ts +130 -0
  627. package/src/tts/synthesize-text.ts +110 -0
  628. package/src/tts/tts-config-resolver.ts +78 -0
  629. package/src/tts/types.ts +153 -0
  630. package/src/types/onboarding-context.ts +7 -0
  631. package/src/util/abort-reasons.ts +58 -0
  632. package/src/util/device-id.ts +32 -16
  633. package/src/util/errors.ts +9 -1
  634. package/src/util/platform.ts +54 -10
  635. package/src/util/pricing.ts +66 -3
  636. package/src/util/spawn.ts +1 -1
  637. package/src/util/truncate.ts +4 -2
  638. package/src/util/unicode.ts +201 -0
  639. package/src/version.ts +19 -24
  640. package/src/watcher/engine.ts +23 -0
  641. package/src/watcher/watcher-store.ts +31 -0
  642. package/src/workspace/migrations/003-seed-device-id.ts +9 -3
  643. package/src/workspace/migrations/017-seed-persona-dirs.ts +68 -4
  644. package/src/workspace/migrations/029-seed-pkb.ts +1 -1
  645. package/src/workspace/migrations/031-drop-user-md.ts +317 -0
  646. package/src/workspace/migrations/031-llm-log-retention-zero-to-null.ts +73 -0
  647. package/src/workspace/migrations/032-tts-provider-unification.ts +227 -0
  648. package/src/workspace/migrations/033-stt-service-explicit-config.ts +122 -0
  649. package/src/workspace/migrations/034-remove-calls-voice-transcription-provider.ts +215 -0
  650. package/src/workspace/migrations/035-seed-slack-channel-persona.ts +50 -0
  651. package/src/workspace/migrations/036-update-pkb-index-bar.ts +37 -0
  652. package/src/workspace/migrations/037-create-meets-dir.ts +61 -0
  653. package/src/workspace/migrations/registry.ts +16 -0
  654. package/src/workspace/top-level-renderer.ts +13 -1
  655. package/src/workspace/turn-commit.ts +31 -0
  656. package/src/__tests__/email-cli.test.ts +0 -297
  657. package/src/__tests__/email-service-config-fallback.test.ts +0 -102
  658. package/src/cli/commands/browser-relay.ts +0 -466
  659. package/src/email/guardrails.ts +0 -221
  660. package/src/email/provider.ts +0 -117
  661. package/src/email/providers/agentmail.ts +0 -361
  662. package/src/email/providers/index.ts +0 -65
  663. package/src/email/service.ts +0 -384
  664. package/src/email/types.ts +0 -126
  665. package/src/prompts/templates/USER.md +0 -13
  666. package/src/providers/speech-to-text/types.ts +0 -17
  667. package/src/runtime/routes/browser-cdp-routes.ts +0 -229
@@ -0,0 +1,282 @@
1
+ /**
2
+ * Guard test that verifies the Slack adapter routes reads and writes through
3
+ * the correct cached auth.
4
+ *
5
+ * Covers the bot-token-only case (reads and writes both use the bot token),
6
+ * the dual-token case (bot + user): reads MUST use the user token while
7
+ * content-creating writes (postMessage) MUST stay on the bot token so posts
8
+ * come from the bot identity, and the runtime fallback when a stored user
9
+ * token is rejected with an auth error.
10
+ */
11
+
12
+ import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
13
+
14
+ import type { OAuthConnection } from "../../../../oauth/connection.js";
15
+ import { credentialKey } from "../../../../security/credential-key.js";
16
+
17
+ // ── Module mocks ────────────────────────────────────────────────────────────
18
+
19
+ const getSecureKeyAsyncMock = mock(
20
+ async (_key: string): Promise<string | null> => null,
21
+ );
22
+ mock.module("../../../../security/secure-keys.js", () => ({
23
+ getSecureKeyAsync: getSecureKeyAsyncMock,
24
+ }));
25
+
26
+ // OAuth helpers are exercised only when no bot_token is cached. The adapter
27
+ // imports them at module load — route them through a stub that signals any
28
+ // OAuth fallback with a distinctive error so tests can assert on it.
29
+ const OAUTH_FALLBACK_SENTINEL = "OAUTH_FALLBACK_NOT_STUBBED";
30
+ mock.module("../../../../oauth/connection-resolver.js", () => ({
31
+ resolveOAuthConnection: async (): Promise<OAuthConnection> => {
32
+ throw new Error(OAUTH_FALLBACK_SENTINEL);
33
+ },
34
+ }));
35
+ mock.module("../../../../oauth/oauth-store.js", () => ({
36
+ isProviderConnected: async () => false,
37
+ }));
38
+
39
+ // Stub contact DB access so the adapter doesn't touch SQLite during the test.
40
+ mock.module("../../../../contacts/contact-store.js", () => ({
41
+ findContactChannel: () => undefined,
42
+ }));
43
+ mock.module("../../../../contacts/contacts-write.js", () => ({
44
+ upsertContactChannel: () => {},
45
+ }));
46
+
47
+ import { slackProvider } from "../adapter.js";
48
+
49
+ // ── fetch capture ───────────────────────────────────────────────────────────
50
+
51
+ type CapturedRequest = {
52
+ url: string;
53
+ method: string;
54
+ authorization: string | null;
55
+ };
56
+
57
+ const captured: CapturedRequest[] = [];
58
+ const originalFetch = globalThis.fetch;
59
+
60
+ function installFetchStub() {
61
+ globalThis.fetch = (async (
62
+ input: RequestInfo | URL,
63
+ init?: RequestInit,
64
+ ): Promise<Response> => {
65
+ const url =
66
+ typeof input === "string"
67
+ ? input
68
+ : input instanceof URL
69
+ ? input.toString()
70
+ : input.url;
71
+ const method = (init?.method ?? "GET").toUpperCase();
72
+ const headers = new Headers(init?.headers ?? {});
73
+ captured.push({
74
+ url,
75
+ method,
76
+ authorization: headers.get("authorization"),
77
+ });
78
+
79
+ // Craft a minimal OK Slack API envelope per endpoint so the adapter's
80
+ // post-call mapping doesn't throw.
81
+ const body = fakeSlackResponse(url);
82
+ return new Response(JSON.stringify(body), {
83
+ status: 200,
84
+ headers: { "Content-Type": "application/json" },
85
+ });
86
+ }) as typeof fetch;
87
+ }
88
+
89
+ function fakeSlackResponse(url: string): Record<string, unknown> {
90
+ if (url.includes("/conversations.list")) {
91
+ return { ok: true, channels: [], response_metadata: { next_cursor: "" } };
92
+ }
93
+ if (url.includes("/conversations.history")) {
94
+ return { ok: true, messages: [], has_more: false };
95
+ }
96
+ if (url.includes("/conversations.mark")) {
97
+ return { ok: true };
98
+ }
99
+ if (url.includes("/chat.postMessage")) {
100
+ return { ok: true, ts: "1700000000.000100", channel: "C123" };
101
+ }
102
+ // Default envelope for any other method the adapter might call.
103
+ return { ok: true };
104
+ }
105
+
106
+ // ── Test setup ──────────────────────────────────────────────────────────────
107
+
108
+ const BOT_TOKEN = "xoxb-BOT";
109
+ const USER_TOKEN = "xoxp-USER";
110
+
111
+ describe("Slack adapter token routing", () => {
112
+ beforeEach(() => {
113
+ captured.length = 0;
114
+ getSecureKeyAsyncMock.mockReset();
115
+ getSecureKeyAsyncMock.mockImplementation(async (key: string) => {
116
+ if (key === credentialKey("slack_channel", "bot_token")) return BOT_TOKEN;
117
+ return null;
118
+ });
119
+ installFetchStub();
120
+ });
121
+
122
+ afterEach(() => {
123
+ globalThis.fetch = originalFetch;
124
+ });
125
+
126
+ test("bot-token only: reads and writes both authenticate with the bot token (regression guard for pre-user-token behavior)", async () => {
127
+ // With only a bot token stored, reads must fall back to the bot token
128
+ // so the adapter keeps working for installs that haven't re-consented
129
+ // the user scope. Writes stay on the bot token always.
130
+ const resolved = await slackProvider.resolveConnection!();
131
+ expect(resolved).toBeUndefined();
132
+
133
+ // Read path: listConversations → /conversations.list must use bot token.
134
+ await slackProvider.listConversations(undefined);
135
+ const readCall = captured.find((c) =>
136
+ c.url.includes("/conversations.list"),
137
+ );
138
+ expect(readCall).toBeDefined();
139
+ expect(readCall!.authorization).toBe(`Bearer ${BOT_TOKEN}`);
140
+
141
+ // Write path: sendMessage → /chat.postMessage must also use bot token.
142
+ await slackProvider.sendMessage(undefined, "C123", "hello");
143
+ const writeCall = captured.find((c) => c.url.includes("/chat.postMessage"));
144
+ expect(writeCall).toBeDefined();
145
+ expect(writeCall!.authorization).toBe(`Bearer ${BOT_TOKEN}`);
146
+ });
147
+
148
+ test("bot + user tokens: reads authenticate with the user token, writes with the bot token", async () => {
149
+ // With both tokens stored, reads MUST flip to the user token so the
150
+ // adapter can see channels the user is in but the bot isn't. Writes
151
+ // MUST stay on the bot token so posts come from the bot identity.
152
+ getSecureKeyAsyncMock.mockImplementation(async (key: string) => {
153
+ if (key === credentialKey("slack_channel", "bot_token")) return BOT_TOKEN;
154
+ if (key === credentialKey("slack_channel", "user_token"))
155
+ return USER_TOKEN;
156
+ return null;
157
+ });
158
+
159
+ const resolved = await slackProvider.resolveConnection!();
160
+ expect(resolved).toBeUndefined();
161
+
162
+ // Reads: listConversations → user token.
163
+ await slackProvider.listConversations(undefined);
164
+ const listCall = captured.find((c) =>
165
+ c.url.includes("/conversations.list"),
166
+ );
167
+ expect(listCall).toBeDefined();
168
+ expect(listCall!.authorization).toBe(`Bearer ${USER_TOKEN}`);
169
+
170
+ // Reads: getHistory → user token.
171
+ await slackProvider.getHistory(undefined, "C123");
172
+ const historyCall = captured.find((c) =>
173
+ c.url.includes("/conversations.history"),
174
+ );
175
+ expect(historyCall).toBeDefined();
176
+ expect(historyCall!.authorization).toBe(`Bearer ${USER_TOKEN}`);
177
+
178
+ // Writes: sendMessage → bot token.
179
+ await slackProvider.sendMessage(undefined, "C123", "hello");
180
+ const sendCall = captured.find((c) => c.url.includes("/chat.postMessage"));
181
+ expect(sendCall).toBeDefined();
182
+ expect(sendCall!.authorization).toBe(`Bearer ${BOT_TOKEN}`);
183
+
184
+ // markRead → user token. conversations.mark sets the read cursor for
185
+ // the authenticated identity, so it must use the same token whose unread
186
+ // counts the adapter exposes.
187
+ await slackProvider.markRead!(undefined, "C123", "1700000000.000100");
188
+ const markCall = captured.find((c) =>
189
+ c.url.includes("/conversations.mark"),
190
+ );
191
+ expect(markCall).toBeDefined();
192
+ expect(markCall!.authorization).toBe(`Bearer ${USER_TOKEN}`);
193
+ });
194
+
195
+ test("user token rejected: read calls fall back to bot token and stay on it", async () => {
196
+ // If the cached user token is revoked/expired, the next read returns
197
+ // invalid_auth (HTTP 200 with ok:false). The adapter must retry the call
198
+ // with the bot token and reset the read cache so subsequent reads in the
199
+ // same session don't re-hit the failure path.
200
+ getSecureKeyAsyncMock.mockImplementation(async (key: string) => {
201
+ if (key === credentialKey("slack_channel", "bot_token")) return BOT_TOKEN;
202
+ if (key === credentialKey("slack_channel", "user_token"))
203
+ return USER_TOKEN;
204
+ return null;
205
+ });
206
+
207
+ // Replace the default fetch stub: first user-token call to history fails
208
+ // with invalid_auth; everything else succeeds.
209
+ let userTokenHistoryCalls = 0;
210
+ globalThis.fetch = (async (
211
+ input: RequestInfo | URL,
212
+ init?: RequestInit,
213
+ ): Promise<Response> => {
214
+ const url =
215
+ typeof input === "string"
216
+ ? input
217
+ : input instanceof URL
218
+ ? input.toString()
219
+ : input.url;
220
+ const headers = new Headers(init?.headers ?? {});
221
+ const auth = headers.get("authorization");
222
+ captured.push({
223
+ url,
224
+ method: (init?.method ?? "GET").toUpperCase(),
225
+ authorization: auth,
226
+ });
227
+ if (
228
+ url.includes("/conversations.history") &&
229
+ auth === `Bearer ${USER_TOKEN}`
230
+ ) {
231
+ userTokenHistoryCalls += 1;
232
+ return new Response(
233
+ JSON.stringify({ ok: false, error: "invalid_auth" }),
234
+ { status: 200, headers: { "Content-Type": "application/json" } },
235
+ );
236
+ }
237
+ return new Response(JSON.stringify(fakeSlackResponse(url)), {
238
+ status: 200,
239
+ headers: { "Content-Type": "application/json" },
240
+ });
241
+ }) as typeof fetch;
242
+
243
+ const resolved = await slackProvider.resolveConnection!();
244
+ expect(resolved).toBeUndefined();
245
+
246
+ // First read: tries user token, fails, retries with bot token.
247
+ await slackProvider.getHistory(undefined, "C123");
248
+ expect(userTokenHistoryCalls).toBe(1);
249
+ const historyCalls = captured.filter((c) =>
250
+ c.url.includes("/conversations.history"),
251
+ );
252
+ expect(historyCalls).toHaveLength(2);
253
+ expect(historyCalls[0].authorization).toBe(`Bearer ${USER_TOKEN}`);
254
+ expect(historyCalls[1].authorization).toBe(`Bearer ${BOT_TOKEN}`);
255
+
256
+ // Subsequent read: cache reset, only bot token is used (no retry needed).
257
+ captured.length = 0;
258
+ await slackProvider.getHistory(undefined, "C456");
259
+ const next = captured.filter((c) =>
260
+ c.url.includes("/conversations.history"),
261
+ );
262
+ expect(next).toHaveLength(1);
263
+ expect(next[0].authorization).toBe(`Bearer ${BOT_TOKEN}`);
264
+ });
265
+
266
+ test("user-token only (no bot token): falls through to the OAuth path", async () => {
267
+ // Edge case: if only a user token is stored with no bot token, we do NOT
268
+ // have Socket Mode credentials, so resolveConnection() falls through to
269
+ // the legacy OAuth path. The mocked resolveOAuthConnection throws, which
270
+ // documents current behavior — user-token-only without an OAuth
271
+ // connection is not a supported install configuration.
272
+ getSecureKeyAsyncMock.mockImplementation(async (key: string) => {
273
+ if (key === credentialKey("slack_channel", "user_token"))
274
+ return USER_TOKEN;
275
+ return null;
276
+ });
277
+
278
+ await expect(slackProvider.resolveConnection!()).rejects.toThrow(
279
+ OAUTH_FALLBACK_SENTINEL,
280
+ );
281
+ });
282
+ });
@@ -25,6 +25,7 @@ import type {
25
25
  SendResult,
26
26
  } from "../../provider-types.js";
27
27
  import * as slack from "./client.js";
28
+ import { SlackApiError } from "./client.js";
28
29
  import type {
29
30
  SlackConversation,
30
31
  SlackMessage,
@@ -35,27 +36,95 @@ import type {
35
36
  const userNameCache = new Map<string, string>();
36
37
 
37
38
  /**
38
- * Cached auth resolved during resolveConnection().
39
+ * Cached auth resolved during resolveConnection(), split by direction.
39
40
  *
40
- * For Socket Mode this holds a raw bot token string; for OAuth it holds the
41
+ * Read and write auth are tracked separately so reads can use a user OAuth
42
+ * token (xoxp-) — giving visibility into channels the user is in but the
43
+ * bot isn't — while writes continue to use the bot token (xoxb-) so posts
44
+ * come from the bot identity. When no user_token is stored, reads fall
45
+ * back to the bot token. If a stored user_token is rejected at runtime
46
+ * (revoked/expired), the read cache is reset to the bot token for the rest
47
+ * of the session — see runReadWithFallback().
48
+ *
49
+ * For Socket Mode these hold a raw bot token string; for OAuth they hold the
41
50
  * OAuthConnection. The Slack client functions accept both via their own
42
- * OAuthConnection | string union, so we can pass this value through directly.
51
+ * OAuthConnection | string union, so we can pass the cached value through
52
+ * directly.
43
53
  */
44
- let _cachedSlackAuth: OAuthConnection | string | null = null;
54
+ let _cachedSlackWriteAuth: OAuthConnection | string | null = null;
55
+ let _cachedSlackReadAuth: OAuthConnection | string | null = null;
45
56
 
46
57
  /**
47
58
  * Get the Slack auth value to pass to Slack client functions.
48
59
  * Prefers the explicit connection from the caller; falls back to the cached
49
- * value set during resolveConnection().
60
+ * write auth. Callers that care about read vs write semantics should use
61
+ * getReadAuth() / getWriteAuth() directly.
50
62
  */
51
63
  function getSlackAuth(connection?: OAuthConnection): OAuthConnection | string {
52
64
  if (connection) return connection;
53
- if (_cachedSlackAuth) return _cachedSlackAuth;
65
+ if (_cachedSlackWriteAuth) return _cachedSlackWriteAuth;
66
+ if (_cachedSlackReadAuth) return _cachedSlackReadAuth;
54
67
  throw new Error(
55
68
  "Slack: no connection or cached token available. Was resolveConnection() called?",
56
69
  );
57
70
  }
58
71
 
72
+ /**
73
+ * Resolve auth for read operations (listConversations, getHistory,
74
+ * conversation replies, search, users.info lookups).
75
+ */
76
+ function getReadAuth(connection?: OAuthConnection): OAuthConnection | string {
77
+ if (connection) return connection;
78
+ if (_cachedSlackReadAuth) return _cachedSlackReadAuth;
79
+ return getSlackAuth(connection);
80
+ }
81
+
82
+ // SAFETY: content-creating writes (postMessage, updateMessage, deleteMessage,
83
+ // reactions) MUST use the bot token. Using the user token would post as the
84
+ // user, not as the bot. State-changing methods that target the authenticated
85
+ // identity's own state (e.g. conversations.mark) should use the read auth so
86
+ // the cursor matches the perspective the adapter exposes.
87
+ /**
88
+ * Resolve auth for content-creating write operations (postMessage and any
89
+ * future reactions, joins, leaves, updates, or deletes).
90
+ */
91
+ function getWriteAuth(connection?: OAuthConnection): OAuthConnection | string {
92
+ if (connection) return connection;
93
+ if (_cachedSlackWriteAuth) return _cachedSlackWriteAuth;
94
+ return getSlackAuth(connection);
95
+ }
96
+
97
+ /**
98
+ * Run a read-path Slack call, falling back to the bot token if the cached
99
+ * user token is rejected with an auth error. On fallback, the read cache is
100
+ * reset to the bot token so subsequent reads in this session don't re-pay
101
+ * the round trip. Caller-supplied connections are passed through unchanged
102
+ * (no fallback) since the caller owns that auth.
103
+ */
104
+ async function runReadWithFallback<T>(
105
+ connection: OAuthConnection | undefined,
106
+ call: (auth: OAuthConnection | string) => Promise<T>,
107
+ ): Promise<T> {
108
+ if (connection) return call(connection);
109
+ const auth = getReadAuth(undefined);
110
+ const usingUserToken =
111
+ _cachedSlackWriteAuth !== null && _cachedSlackReadAuth !== _cachedSlackWriteAuth;
112
+ try {
113
+ return await call(auth);
114
+ } catch (err) {
115
+ if (
116
+ usingUserToken &&
117
+ err instanceof SlackApiError &&
118
+ err.status === 401 &&
119
+ _cachedSlackWriteAuth
120
+ ) {
121
+ _cachedSlackReadAuth = _cachedSlackWriteAuth;
122
+ return call(_cachedSlackWriteAuth);
123
+ }
124
+ throw err;
125
+ }
126
+ }
127
+
59
128
  async function resolveUserName(
60
129
  auth: OAuthConnection | string,
61
130
  userId: string,
@@ -165,7 +234,7 @@ export const slackProvider: MessagingProvider = {
165
234
  id: "slack",
166
235
  displayName: "Slack",
167
236
  credentialService: "slack",
168
- capabilities: new Set(["reactions", "threads", "leave_channel"]),
237
+ capabilities: new Set(["reactions", "threads", "join_channel", "leave_channel"]),
169
238
 
170
239
  async isConnected(): Promise<boolean> {
171
240
  // Socket Mode: check for bot token directly in credential store.
@@ -184,16 +253,26 @@ export const slackProvider: MessagingProvider = {
184
253
  ): Promise<OAuthConnection | undefined> {
185
254
  // Socket Mode: cache the raw bot token for use in adapter methods.
186
255
  // Token presence is sufficient — no connection row required.
256
+ //
257
+ // When a user_token is also stored, prefer it for reads so the adapter
258
+ // can see channels the user is in but the bot isn't (conversations.list,
259
+ // conversations.history, search.messages). Writes always stay on the
260
+ // bot token — see SAFETY note above getWriteAuth().
187
261
  const botToken = await getSecureKeyAsync(
188
262
  credentialKey("slack_channel", "bot_token"),
189
263
  );
264
+ const userToken = await getSecureKeyAsync(
265
+ credentialKey("slack_channel", "user_token"),
266
+ );
190
267
  if (botToken) {
191
- _cachedSlackAuth = botToken;
268
+ _cachedSlackWriteAuth = botToken;
269
+ _cachedSlackReadAuth = userToken ?? botToken;
192
270
  return undefined;
193
271
  }
194
272
  // Preserve existing OAuth path for backwards compat.
195
273
  const conn = await resolveOAuthConnection("slack", { account });
196
- _cachedSlackAuth = conn;
274
+ _cachedSlackWriteAuth = conn;
275
+ _cachedSlackReadAuth = conn;
197
276
  return conn;
198
277
  },
199
278
 
@@ -212,7 +291,6 @@ export const slackProvider: MessagingProvider = {
212
291
  connection: OAuthConnection | undefined,
213
292
  options?: ListOptions,
214
293
  ): Promise<Conversation[]> {
215
- const auth = getSlackAuth(connection);
216
294
  const typeMap: Record<string, string> = {
217
295
  channel: "public_channel,private_channel",
218
296
  dm: "im",
@@ -228,16 +306,32 @@ export const slackProvider: MessagingProvider = {
228
306
 
229
307
  const conversations: Conversation[] = [];
230
308
  let cursor: string | undefined = options?.cursor;
309
+ let auth = getReadAuth(connection);
231
310
 
232
- // Paginate through all results
311
+ // Paginate through all results. The first page is wrapped in
312
+ // runReadWithFallback so that a 401 on the user token retries with the
313
+ // bot token before we commit to the rest of the pagination.
314
+ let firstPage = true;
233
315
  do {
234
- const resp = await slack.listConversations(
235
- auth,
236
- types,
237
- options?.excludeArchived ?? true,
238
- options?.limit ?? 200,
239
- cursor,
240
- );
316
+ const resp = firstPage
317
+ ? await runReadWithFallback(connection, async (a) => {
318
+ auth = a;
319
+ return slack.listConversations(
320
+ a,
321
+ types,
322
+ options?.excludeArchived ?? true,
323
+ options?.limit ?? 200,
324
+ cursor,
325
+ );
326
+ })
327
+ : await slack.listConversations(
328
+ auth,
329
+ types,
330
+ options?.excludeArchived ?? true,
331
+ options?.limit ?? 200,
332
+ cursor,
333
+ );
334
+ firstPage = false;
241
335
  conversations.push(...resp.channels.map(mapConversation));
242
336
  cursor = resp.response_metadata?.next_cursor || undefined;
243
337
  } while (
@@ -280,14 +374,17 @@ export const slackProvider: MessagingProvider = {
280
374
  conversationId: string,
281
375
  options?: HistoryOptions,
282
376
  ): Promise<Message[]> {
283
- const auth = getSlackAuth(connection);
284
- const resp = await slack.conversationHistory(
285
- auth,
286
- conversationId,
287
- options?.limit ?? 50,
288
- options?.before,
289
- options?.after,
290
- );
377
+ let auth: OAuthConnection | string = getReadAuth(connection);
378
+ const resp = await runReadWithFallback(connection, async (a) => {
379
+ auth = a;
380
+ return slack.conversationHistory(
381
+ a,
382
+ conversationId,
383
+ options?.limit ?? 50,
384
+ options?.before,
385
+ options?.after,
386
+ );
387
+ });
291
388
 
292
389
  const messages: Message[] = [];
293
390
  for (const msg of resp.messages) {
@@ -303,8 +400,9 @@ export const slackProvider: MessagingProvider = {
303
400
  query: string,
304
401
  options?: SearchOptions,
305
402
  ): Promise<SearchResult> {
306
- const auth = getSlackAuth(connection);
307
- const resp = await slack.searchMessages(auth, query, options?.count ?? 20);
403
+ const resp = await runReadWithFallback(connection, (auth) =>
404
+ slack.searchMessages(auth, query, options?.count ?? 20),
405
+ );
308
406
  return {
309
407
  total: resp.messages.total,
310
408
  messages: resp.messages.matches.map(mapSearchMatch),
@@ -318,7 +416,7 @@ export const slackProvider: MessagingProvider = {
318
416
  text: string,
319
417
  options?: SendOptions,
320
418
  ): Promise<SendResult> {
321
- const auth = getSlackAuth(connection);
419
+ const auth = getWriteAuth(connection);
322
420
  const resp = await slack.postMessage(
323
421
  auth,
324
422
  conversationId,
@@ -338,13 +436,16 @@ export const slackProvider: MessagingProvider = {
338
436
  threadId: string,
339
437
  options?: HistoryOptions,
340
438
  ): Promise<Message[]> {
341
- const auth = getSlackAuth(connection);
342
- const resp = await slack.conversationReplies(
343
- auth,
344
- conversationId,
345
- threadId,
346
- options?.limit ?? 50,
347
- );
439
+ let auth: OAuthConnection | string = getReadAuth(connection);
440
+ const resp = await runReadWithFallback(connection, async (a) => {
441
+ auth = a;
442
+ return slack.conversationReplies(
443
+ a,
444
+ conversationId,
445
+ threadId,
446
+ options?.limit ?? 50,
447
+ );
448
+ });
348
449
  const messages: Message[] = [];
349
450
  for (const msg of resp.messages) {
350
451
  const name = await resolveUserName(auth, msg.user ?? "");
@@ -358,9 +459,13 @@ export const slackProvider: MessagingProvider = {
358
459
  conversationId: string,
359
460
  messageId?: string,
360
461
  ): Promise<void> {
361
- const auth = getSlackAuth(connection);
462
+ // conversations.mark sets the read cursor for the authenticated identity.
463
+ // It must use the same token as the read path so the cursor matches the
464
+ // perspective the adapter exposes (unread counts in listConversations).
362
465
  // Slack's conversations.mark requires a timestamp — use the provided one or "now"
363
466
  const ts = messageId ?? String(Date.now() / 1000);
364
- await slack.conversationMark(auth, conversationId, ts);
467
+ await runReadWithFallback(connection, (auth) =>
468
+ slack.conversationMark(auth, conversationId, ts),
469
+ );
365
470
  },
366
471
  };
@@ -18,6 +18,7 @@ import type {
18
18
  SlackChatUpdateResponse,
19
19
  SlackConversationHistoryResponse,
20
20
  SlackConversationInfoResponse,
21
+ SlackConversationJoinResponse,
21
22
  SlackConversationLeaveResponse,
22
23
  SlackConversationMarkResponse,
23
24
  SlackConversationRepliesResponse,
@@ -517,6 +518,20 @@ export async function deleteMessage(
517
518
  );
518
519
  }
519
520
 
521
+ export async function joinConversation(
522
+ connectionOrToken: OAuthConnection | string,
523
+ channel: string,
524
+ ): Promise<SlackConversationJoinResponse> {
525
+ return request<SlackConversationJoinResponse>(
526
+ connectionOrToken,
527
+ "conversations.join",
528
+ undefined,
529
+ {
530
+ channel,
531
+ },
532
+ );
533
+ }
534
+
520
535
  export async function leaveConversation(
521
536
  connectionOrToken: OAuthConnection | string,
522
537
  channel: string,
@@ -530,3 +545,4 @@ export async function leaveConversation(
530
545
  },
531
546
  );
532
547
  }
548
+
@@ -119,6 +119,10 @@ export interface SlackConversationsOpenResponse extends SlackApiResponse {
119
119
 
120
120
  export type SlackReactionAddResponse = SlackApiResponse;
121
121
 
122
+ export type SlackConversationJoinResponse = SlackApiResponse & {
123
+ channel?: SlackConversation;
124
+ };
125
+
122
126
  export type SlackConversationLeaveResponse = SlackApiResponse;
123
127
 
124
128
  export interface SlackChatDeleteResponse extends SlackApiResponse {
@@ -61,9 +61,9 @@ const PROMPT_VERSION = "v4";
61
61
  /**
62
62
  * Maximum character budget for identity context injected into the notification
63
63
  * decision prompt. We truncate to prevent oversized prompts when SOUL.md /
64
- * IDENTITY.md / USER.md are large — exceeding the provider context window
65
- * would cause the LLM call to fail and silently degrade to deterministic
66
- * fallback for all notifications.
64
+ * IDENTITY.md / users/<slug>.md are large — exceeding the provider context
65
+ * window would cause the LLM call to fail and silently degrade to
66
+ * deterministic fallback for all notifications.
67
67
  */
68
68
  const MAX_IDENTITY_CONTEXT_CHARS = 2000;
69
69
 
@@ -97,6 +97,11 @@ export const NOTIFICATION_SOURCE_EVENT_NAMES = [
97
97
  id: "voice.response_ready",
98
98
  description: "Voice response ready for playback",
99
99
  },
100
+ {
101
+ id: "credential.health_alert",
102
+ description:
103
+ "OAuth credential health issue detected (expired, revoked, missing scopes)",
104
+ },
100
105
  ] as const;
101
106
 
102
107
  export type NotificationSourceEventName =
@@ -34,6 +34,7 @@ function makeProviderRow(
34
34
  tokenExchangeUrl: "https://example.com/token",
35
35
  refreshUrl: null,
36
36
  tokenEndpointAuthMethod: "client_secret_post",
37
+ tokenExchangeBodyFormat: "form",
37
38
  userinfoUrl: null,
38
39
  baseUrl: null,
39
40
  defaultScopes: "[]",