@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
@@ -57,21 +57,62 @@ export function textToSlackBlocks(text: string): Block[] | undefined {
57
57
 
58
58
  if (segment.type === "code") {
59
59
  const lang = segment.lang ?? "";
60
- const codeText = "```" + lang + "\n" + segment.content + "\n```";
61
- blocks.push({
62
- type: "section",
63
- text: { type: "mrkdwn", text: codeText },
64
- });
60
+ const codeChunks = splitCodeSegmentContent(segment.content, lang);
61
+ for (let c = 0; c < codeChunks.length; c++) {
62
+ if (c > 0) {
63
+ blocks.push({ type: "divider" });
64
+ }
65
+ blocks.push({
66
+ type: "section",
67
+ text: {
68
+ type: "mrkdwn",
69
+ text: "```" + lang + "\n" + codeChunks[c] + "\n```",
70
+ },
71
+ });
72
+ }
65
73
  } else if (segment.type === "header") {
66
74
  blocks.push({
67
75
  type: "header",
68
76
  text: { type: "plain_text", text: segment.content },
69
77
  });
78
+ } else if (segment.type === "table") {
79
+ const structured = convertTableToStructuredText(
80
+ segment.headers,
81
+ segment.rows,
82
+ );
83
+ const mrkdwn = markdownToMrkdwn(structured);
84
+ const chunks = splitLongTextSegment(mrkdwn);
85
+ for (let c = 0; c < chunks.length; c++) {
86
+ if (c > 0) {
87
+ blocks.push({ type: "divider" });
88
+ }
89
+ blocks.push({
90
+ type: "section",
91
+ text: { type: "mrkdwn", text: chunks[c] },
92
+ });
93
+ }
70
94
  } else {
71
- blocks.push({
72
- type: "section",
73
- text: { type: "mrkdwn", text: markdownToMrkdwn(segment.content) },
74
- });
95
+ // Transform to Slack mrkdwn FIRST, then split. Splitting raw markdown
96
+ // can bisect `[link text](url)` spans or `**bold**` markers, leaving
97
+ // orphan tokens that `markdownToMrkdwn`'s regex won't match — raw
98
+ // markdown would then leak through to Slack. The splitter is also
99
+ // span-aware (see `splitLongTextSegment`) so candidate boundaries
100
+ // landing inside a `<url|text>` or `*bold*` span are rejected, which
101
+ // matters when link text contains `. `/`! `/`? ` (e.g.
102
+ // `<url|First sentence. Second sentence>`) or when a span straddles
103
+ // the maxChars window. The preceding table branch uses the same
104
+ // ordering.
105
+ const mrkdwn = markdownToMrkdwn(segment.content);
106
+ const chunks = splitLongTextSegment(mrkdwn);
107
+ for (let c = 0; c < chunks.length; c++) {
108
+ if (c > 0) {
109
+ blocks.push({ type: "divider" });
110
+ }
111
+ blocks.push({
112
+ type: "section",
113
+ text: { type: "mrkdwn", text: chunks[c] },
114
+ });
115
+ }
75
116
  }
76
117
  }
77
118
 
@@ -113,7 +154,13 @@ interface HeaderSegment {
113
154
  content: string;
114
155
  }
115
156
 
116
- type Segment = TextSegment | CodeSegment | HeaderSegment;
157
+ interface TableSegment {
158
+ type: "table";
159
+ headers: string[];
160
+ rows: string[][];
161
+ }
162
+
163
+ type Segment = TextSegment | CodeSegment | HeaderSegment | TableSegment;
117
164
 
118
165
  function splitIntoSegments(text: string): Segment[] {
119
166
  const lines = text.split("\n");
@@ -155,6 +202,15 @@ function splitIntoSegments(text: string): Segment[] {
155
202
  continue;
156
203
  }
157
204
 
205
+ // Detect markdown tables: header row + separator row + data rows
206
+ const tableSegment = tryParseTable(lines, i);
207
+ if (tableSegment) {
208
+ flushText();
209
+ segments.push(tableSegment.segment);
210
+ i = tableSegment.nextIndex;
211
+ continue;
212
+ }
213
+
158
214
  currentTextLines.push(line);
159
215
  i++;
160
216
  }
@@ -163,6 +219,104 @@ function splitIntoSegments(text: string): Segment[] {
163
219
  return segments;
164
220
  }
165
221
 
222
+ /**
223
+ * Parse cells from a pipe-delimited table row, trimming whitespace.
224
+ */
225
+ function parseTableRow(line: string): string[] {
226
+ const ESCAPED_PIPE_PLACEHOLDER = "\x00PIPE\x00";
227
+ const ESCAPED_BACKSLASH_PLACEHOLDER = "\x00BSLASH\x00";
228
+ return line
229
+ // First, protect escaped backslashes (\\) so they don't interfere
230
+ .replace(/\\\\/g, ESCAPED_BACKSLASH_PLACEHOLDER)
231
+ // Now a remaining \| is a genuinely escaped pipe (odd backslash)
232
+ .replace(/\\\|/g, ESCAPED_PIPE_PLACEHOLDER)
233
+ .replace(/^\|/, "")
234
+ .replace(/\|$/, "")
235
+ .split("|")
236
+ .map((cell) =>
237
+ cell
238
+ .replaceAll(ESCAPED_PIPE_PLACEHOLDER, "|")
239
+ .replaceAll(ESCAPED_BACKSLASH_PLACEHOLDER, "\\\\")
240
+ .trim(),
241
+ );
242
+ }
243
+
244
+ /**
245
+ * Check if a line is a markdown table separator row (e.g., |---|---|).
246
+ */
247
+ function isSeparatorRow(line: string): boolean {
248
+ return /^\|[\s:-]+(\|[\s:-]+)*\|?\s*$/.test(line);
249
+ }
250
+
251
+ /**
252
+ * Try to parse a markdown table starting at the given line index.
253
+ * Returns the parsed table segment and the next line index, or null
254
+ * if the lines don't form a valid table (header + separator + at least
255
+ * one data row).
256
+ */
257
+ function tryParseTable(
258
+ lines: string[],
259
+ startIndex: number,
260
+ ): { segment: TableSegment; nextIndex: number } | null {
261
+ // Need at least 3 lines: header, separator, one data row
262
+ if (startIndex + 2 >= lines.length) return null;
263
+
264
+ const headerLine = lines[startIndex];
265
+ const separatorLine = lines[startIndex + 1];
266
+
267
+ // Header must be a pipe-delimited row
268
+ if (!headerLine.includes("|") || !headerLine.match(/^\|.*\|$/)) return null;
269
+ if (!isSeparatorRow(separatorLine)) return null;
270
+
271
+ const headers = parseTableRow(headerLine);
272
+
273
+ // Collect data rows
274
+ const rows: string[][] = [];
275
+ let i = startIndex + 2;
276
+ while (
277
+ i < lines.length &&
278
+ lines[i].includes("|") &&
279
+ lines[i].match(/^\|.*\|$/)
280
+ ) {
281
+ if (!isSeparatorRow(lines[i])) {
282
+ rows.push(parseTableRow(lines[i]));
283
+ }
284
+ i++;
285
+ }
286
+
287
+ // Need at least one data row to qualify as a table
288
+ if (rows.length === 0) return null;
289
+
290
+ return {
291
+ segment: { type: "table", headers, rows },
292
+ nextIndex: i,
293
+ };
294
+ }
295
+
296
+ /**
297
+ * Convert a parsed table into structured bullet-point text suitable
298
+ * for Slack rendering. Uses the first column as the entry label,
299
+ * listing remaining columns as sub-bullets.
300
+ */
301
+ function convertTableToStructuredText(
302
+ headers: string[],
303
+ rows: string[][],
304
+ ): string {
305
+ const lines: string[] = [];
306
+
307
+ for (const row of rows) {
308
+ const label = row[0] ?? "";
309
+ lines.push(`**${label}**`);
310
+ for (let c = 1; c < headers.length; c++) {
311
+ const value = row[c] ?? "";
312
+ lines.push(` - ${headers[c]}: ${value}`);
313
+ }
314
+ lines.push("");
315
+ }
316
+
317
+ return lines.join("\n").trim();
318
+ }
319
+
166
320
  function markdownToMrkdwn(text: string): string {
167
321
  let result = text;
168
322
  // [text](url) → <url|text>
@@ -174,3 +328,276 @@ function markdownToMrkdwn(text: string): string {
174
328
  result = result.replace(/\*\*(.+?)\*\*/g, "*$1*");
175
329
  return result;
176
330
  }
331
+
332
+ // ---------------------------------------------------------------------------
333
+ // Long-text splitting
334
+ // ---------------------------------------------------------------------------
335
+
336
+ /**
337
+ * Slack's `section` block has a documented ~3000-character limit on the
338
+ * `text.text` value. Keep a margin under that so downstream transforms
339
+ * (e.g. `markdownToMrkdwn` expansions) don't push a chunk over.
340
+ */
341
+ export const SLACK_SECTION_MAX_CHARS = 2800;
342
+
343
+ /**
344
+ * Split `text` into chunks no larger than `maxChars`, preferring natural
345
+ * boundaries. Pure helper: no Block Kit knowledge, safe to unit test.
346
+ *
347
+ * Preference order for split points:
348
+ * 1. Paragraph boundary (`\n\n`)
349
+ * 2. Single newline (`\n`)
350
+ * 3. Sentence boundary (`. `, `! `, `? `)
351
+ * 4. Hard slice at `maxChars` (backed up to the start of any straddling
352
+ * mrkdwn span, when possible)
353
+ *
354
+ * Span-aware: candidate boundaries that fall strictly inside a Slack
355
+ * `<...>` link/user token or a `*...*` bold span are rejected so chunks
356
+ * never end with an unclosed `<` or orphan `*`. This matters because
357
+ * `markdownToMrkdwn` runs before splitting, and link text can legitimately
358
+ * contain `. `/`! `/`? ` (e.g. `<url|First sentence. Second sentence>`).
359
+ *
360
+ * Short inputs (length ≤ `maxChars`) return `[text]` unchanged. Each chunk
361
+ * is trimmed of leading/trailing whitespace at chunk boundaries; interior
362
+ * whitespace is preserved.
363
+ */
364
+ export function splitLongTextSegment(
365
+ text: string,
366
+ maxChars: number = SLACK_SECTION_MAX_CHARS,
367
+ ): string[] {
368
+ if (maxChars <= 0 || text.length <= maxChars) return [text];
369
+
370
+ const chunks: string[] = [];
371
+ let remaining = text;
372
+
373
+ while (remaining.length > maxChars) {
374
+ const window = remaining.slice(0, maxChars);
375
+ const spans = computeMrkdwnSpans(window);
376
+ let splitAt = findBoundary(window, ["\n\n"], spans);
377
+ if (splitAt < 0) splitAt = findBoundary(window, ["\n"], spans);
378
+ if (splitAt < 0) splitAt = findBoundary(window, [". ", "! ", "? "], spans);
379
+ if (splitAt < 0) splitAt = hardSliceAvoidingSpans(maxChars, spans);
380
+
381
+ const chunk = remaining.slice(0, splitAt).trim();
382
+ if (chunk.length > 0) chunks.push(chunk);
383
+ remaining = remaining.slice(splitAt);
384
+ }
385
+
386
+ const tail = remaining.trim();
387
+ if (tail.length > 0) chunks.push(tail);
388
+
389
+ return chunks;
390
+ }
391
+
392
+ /**
393
+ * Slack link/mention tokens always start with one of these prefixes after
394
+ * the opening `<`. A plain `<` in technical prose (e.g. `a < b`) does not
395
+ * match any of these and must not be treated as a span start, or the
396
+ * splitter would reject every boundary past the `<` and fall back to a
397
+ * hard slice that can land mid-word.
398
+ */
399
+ const SLACK_LINK_PREFIXES = [
400
+ "http://",
401
+ "https://",
402
+ "mailto:",
403
+ "#C", // channel ref: <#C0123ABCD|name>
404
+ "@U", // user mention: <@U0123ABCD>
405
+ "@W", // workspace user: <@W0123ABCD>
406
+ "!channel",
407
+ "!here",
408
+ "!everyone",
409
+ "!subteam",
410
+ "!date",
411
+ ];
412
+
413
+ // Matches `scheme://` at the start of a string where `scheme` follows RFC 3986
414
+ // syntax: `ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )`. Covers http, https,
415
+ // ftp, ssh, git+ssh, custom app schemes, etc.
416
+ const URL_SCHEME_RE = /^[a-z][a-z0-9+.-]*:\/\//i;
417
+
418
+ function looksLikeLinkStart(window: string, openIdx: number): boolean {
419
+ const rest = window.slice(openIdx + 1);
420
+ if (rest.length === 0) return false;
421
+ const first = rest.charCodeAt(0);
422
+ // Fast reject: whitespace or another `<` immediately after `<` is never a link.
423
+ if (first === 0x20 || first === 0x09 || first === 0x0a || first === 0x3c) {
424
+ return false;
425
+ }
426
+ for (const prefix of SLACK_LINK_PREFIXES) {
427
+ if (rest.startsWith(prefix)) return true;
428
+ // Near the window edge the prefix may be truncated — e.g. `<https`
429
+ // when `://` is past the cutoff. If `rest` is a strict prefix of a
430
+ // known link prefix, treat it as link-shaped so the continuation
431
+ // past the window is still protected from mid-token hard slicing.
432
+ if (rest.length < prefix.length && prefix.startsWith(rest)) return true;
433
+ }
434
+ // `markdownToMrkdwn` wraps any markdown link target in `<url|text>`,
435
+ // including schemes beyond the whitelist above (ftp, ssh, custom app
436
+ // schemes, etc.). Recognize any `scheme://` prefix so those spans are
437
+ // protected too. Also treat a truncated `<scheme` at the window edge as
438
+ // link-shaped so the continuation past the window is not hard-sliced.
439
+ if (URL_SCHEME_RE.test(rest)) return true;
440
+ const colonIdx = rest.indexOf(":");
441
+ if (colonIdx < 0) {
442
+ if (/^[a-z][a-z0-9+.-]*$/i.test(rest)) return true;
443
+ } else if (colonIdx + 1 === rest.length) {
444
+ if (/^[a-z][a-z0-9+.-]*:$/i.test(rest)) return true;
445
+ } else if (rest[colonIdx + 1] === "/" && colonIdx + 2 === rest.length) {
446
+ if (/^[a-z][a-z0-9+.-]*:\/$/i.test(rest)) return true;
447
+ }
448
+ return false;
449
+ }
450
+
451
+ /**
452
+ * Find half-open intervals `[start, end)` covering Slack mrkdwn spans in
453
+ * `window` that the splitter must not bisect: `<...>` link/mention tokens
454
+ * and `*...*` single-asterisk bold runs. A `<` is only treated as a span
455
+ * start when it is followed by a recognized Slack link/mention prefix
456
+ * (see `SLACK_LINK_PREFIXES`); plain `<` in technical text is skipped.
457
+ * An unclosed link-shaped `<...` is treated as a span extending to the
458
+ * end of the window so the splitter avoids landing inside a span that
459
+ * straddles the cutoff.
460
+ */
461
+ function computeMrkdwnSpans(window: string): Array<[number, number]> {
462
+ const intervals: Array<[number, number]> = [];
463
+
464
+ let i = 0;
465
+ while (i < window.length) {
466
+ const open = window.indexOf("<", i);
467
+ if (open < 0) break;
468
+ if (!looksLikeLinkStart(window, open)) {
469
+ i = open + 1;
470
+ continue;
471
+ }
472
+ const close = window.indexOf(">", open + 1);
473
+ // For unclosed `<...` (span continues past the window), use a sentinel
474
+ // larger than any in-window position so split-point checks treat the
475
+ // window's right edge as inside the span and back the hard-slice up.
476
+ const end = close < 0 ? window.length + 1 : close + 1;
477
+ intervals.push([open, end]);
478
+ i = close < 0 ? window.length : end;
479
+ }
480
+
481
+ // Bold spans cannot contain `*` or `\n`.
482
+ const boldRe = /\*[^*\n]+\*/g;
483
+ let m: RegExpExecArray | null;
484
+ while ((m = boldRe.exec(window)) !== null) {
485
+ intervals.push([m.index, m.index + m[0].length]);
486
+ }
487
+
488
+ return intervals;
489
+ }
490
+
491
+ function isInsideSpan(
492
+ pos: number,
493
+ spans: Array<[number, number]>,
494
+ ): boolean {
495
+ for (const [start, end] of spans) {
496
+ if (pos > start && pos < end) return true;
497
+ }
498
+ return false;
499
+ }
500
+
501
+ function hardSliceAvoidingSpans(
502
+ maxChars: number,
503
+ spans: Array<[number, number]>,
504
+ ): number {
505
+ for (const [start, end] of spans) {
506
+ if (maxChars > start && maxChars < end) {
507
+ // Back up to the start of the straddling span so it stays intact in
508
+ // the next chunk. If the span starts at 0, we have no choice but to
509
+ // hard-slice through it.
510
+ return start > 0 ? start : maxChars;
511
+ }
512
+ }
513
+ return maxChars;
514
+ }
515
+
516
+ /**
517
+ * Split a code segment's inner content into chunks that, once wrapped in
518
+ * ```lang … ``` fences, each fit inside Slack's section-text limit.
519
+ *
520
+ * Prefers line boundaries so code stays readable across chunks; falls back
521
+ * to a hard character slice for pathological single-line content.
522
+ */
523
+ export function splitCodeSegmentContent(
524
+ content: string,
525
+ lang: string = "",
526
+ maxChars: number = SLACK_SECTION_MAX_CHARS,
527
+ ): string[] {
528
+ // Fence overhead: "```" + lang + "\n" + content + "\n```"
529
+ const overhead = 3 + lang.length + 1 + 1 + 3;
530
+ const budget = maxChars - overhead;
531
+ if (budget <= 0 || content.length <= budget) return [content];
532
+
533
+ const chunks: string[] = [];
534
+ const lines = content.split("\n");
535
+ let current: string[] = [];
536
+ let currentLen = 0;
537
+
538
+ const flush = (): void => {
539
+ if (current.length > 0) {
540
+ chunks.push(current.join("\n"));
541
+ current = [];
542
+ currentLen = 0;
543
+ }
544
+ };
545
+
546
+ for (const line of lines) {
547
+ // +1 for the joining "\n" (only if current is non-empty)
548
+ const added = current.length === 0 ? line.length : line.length + 1;
549
+ if (currentLen + added <= budget) {
550
+ current.push(line);
551
+ currentLen += added;
552
+ continue;
553
+ }
554
+
555
+ flush();
556
+
557
+ // A single line longer than the budget — hard-slice it across chunks.
558
+ if (line.length > budget) {
559
+ let remaining = line;
560
+ while (remaining.length > budget) {
561
+ chunks.push(remaining.slice(0, budget));
562
+ remaining = remaining.slice(budget);
563
+ }
564
+ if (remaining.length > 0) {
565
+ current.push(remaining);
566
+ currentLen = remaining.length;
567
+ }
568
+ } else {
569
+ current.push(line);
570
+ currentLen = line.length;
571
+ }
572
+ }
573
+
574
+ flush();
575
+ return chunks;
576
+ }
577
+
578
+ /**
579
+ * Return the end index of the last occurrence of any delimiter in `window`
580
+ * whose split point does not fall inside a mrkdwn span. Returns -1 if no
581
+ * delimiter has a valid split point.
582
+ */
583
+ function findBoundary(
584
+ window: string,
585
+ delimiters: string[],
586
+ spans: Array<[number, number]> = [],
587
+ ): number {
588
+ let best = -1;
589
+ for (const delim of delimiters) {
590
+ let from = window.length;
591
+ while (from > 0) {
592
+ const idx = window.lastIndexOf(delim, from - 1);
593
+ if (idx < 0) break;
594
+ const end = idx + delim.length;
595
+ if (!isInsideSpan(end, spans)) {
596
+ if (end > best) best = end;
597
+ break;
598
+ }
599
+ from = idx;
600
+ }
601
+ }
602
+ return best;
603
+ }
@@ -1,3 +1,4 @@
1
+ import { emitFeedEvent } from "../home/emit-feed-event.js";
1
2
  import { bootstrapConversation } from "../memory/conversation-bootstrap.js";
2
3
  import { getConversation } from "../memory/conversation-crud.js";
3
4
  import { invalidateAssistantInferredItemsForConversation } from "../memory/task-memory-cleanup.js";
@@ -141,11 +142,21 @@ async function runScheduleOnce(
141
142
  });
142
143
  if (isOneShot) {
143
144
  completeOneShot(job.id);
145
+ emitScheduleFeedEvent({
146
+ title: job.name,
147
+ summary: "Reminder fired.",
148
+ dedupKey: `schedule-notify-oneshot:${job.id}`,
149
+ });
144
150
  } else {
145
151
  // Track recurring notify-mode success so lastStatus resets to ok
146
152
  // and retryCount clears after a transient failure.
147
153
  const runId = createScheduleRun(job.id, `notify-ok:${job.id}`);
148
154
  completeScheduleRun(runId, { status: "ok" });
155
+ emitScheduleFeedEvent({
156
+ title: job.name,
157
+ summary: "Scheduled notification fired.",
158
+ dedupKey: `schedule-run:${runId}`,
159
+ });
149
160
  }
150
161
  } catch (err) {
151
162
  log.warn(
@@ -223,6 +234,11 @@ async function runScheduleOnce(
223
234
  completeScheduleRun(runId, { status: "ok" });
224
235
  if (!job.quiet) {
225
236
  notifySchedule({ id: job.id, name: job.name });
237
+ emitScheduleFeedEvent({
238
+ title: job.name,
239
+ summary: "Scheduled task ran.",
240
+ dedupKey: `schedule-run:${runId}`,
241
+ });
226
242
  }
227
243
  if (isOneShot) completeOneShot(job.id);
228
244
  }
@@ -317,6 +333,11 @@ async function runScheduleOnce(
317
333
  completeScheduleRun(runId, { status: "ok" });
318
334
  if (!job.quiet) {
319
335
  notifySchedule({ id: job.id, name: job.name });
336
+ emitScheduleFeedEvent({
337
+ title: job.name,
338
+ summary: isOneShot ? "One-shot reminder ran." : "Scheduled job ran.",
339
+ dedupKey: `schedule-run:${runId}`,
340
+ });
320
341
  }
321
342
  if (isOneShot) completeOneShot(job.id);
322
343
  processed += 1;
@@ -384,3 +405,32 @@ async function runScheduleOnce(
384
405
  }
385
406
  return processed;
386
407
  }
408
+
409
+ /**
410
+ * Fire-and-forget home-feed emit for a successful schedule run.
411
+ *
412
+ * Wraps {@link emitFeedEvent} with local error handling so a schema
413
+ * failure or writer hiccup can never interrupt the scheduler tick
414
+ * loop. The dedupKey is always derived from the schedule run id (or
415
+ * the job id, for one-shot notify-mode which fires before a run
416
+ * record is created) so each run lands as its own entry in the
417
+ * activity log — the writer's per-source cap keeps total volume
418
+ * bounded.
419
+ */
420
+ function emitScheduleFeedEvent(params: {
421
+ title: string;
422
+ summary: string;
423
+ dedupKey: string;
424
+ }): void {
425
+ void emitFeedEvent({
426
+ source: "assistant",
427
+ title: params.title,
428
+ summary: params.summary,
429
+ dedupKey: params.dedupKey,
430
+ }).catch((err) => {
431
+ log.warn(
432
+ { err, dedupKey: params.dedupKey },
433
+ "Failed to emit schedule feed event",
434
+ );
435
+ });
436
+ }
@@ -56,6 +56,13 @@ export interface OAuth2Config {
56
56
  * use `","` (comma).
57
57
  */
58
58
  scopeSeparator: string;
59
+ /**
60
+ * Body encoding format for the token exchange and refresh requests.
61
+ * - `"form"` (default): `application/x-www-form-urlencoded` with `URLSearchParams`.
62
+ * - `"json"`: `application/json` with `JSON.stringify`.
63
+ * Providers like Notion require JSON-encoded bodies at their token endpoint.
64
+ */
65
+ tokenExchangeBodyFormat?: "form" | "json";
59
66
  }
60
67
 
61
68
  export interface OAuth2TokenResult {
@@ -113,6 +120,7 @@ async function exchangeCodeForTokens(
113
120
  codeVerifier: string,
114
121
  ): Promise<OAuth2FlowResult> {
115
122
  const authMethod = config.tokenEndpointAuthMethod ?? "client_secret_post";
123
+ const bodyFormat = config.tokenExchangeBodyFormat ?? "form";
116
124
 
117
125
  const tokenBody: Record<string, string> = {
118
126
  grant_type: "authorization_code",
@@ -122,7 +130,11 @@ async function exchangeCodeForTokens(
122
130
  };
123
131
 
124
132
  const headers: Record<string, string> = {
125
- "Content-Type": "application/x-www-form-urlencoded",
133
+ "Content-Type":
134
+ bodyFormat === "json"
135
+ ? "application/json"
136
+ : "application/x-www-form-urlencoded",
137
+ Accept: "application/json",
126
138
  };
127
139
 
128
140
  if (config.clientSecret && authMethod === "client_secret_basic") {
@@ -140,7 +152,10 @@ async function exchangeCodeForTokens(
140
152
  const tokenResp = await fetch(config.tokenExchangeUrl, {
141
153
  method: "POST",
142
154
  headers,
143
- body: new URLSearchParams(tokenBody),
155
+ body:
156
+ bodyFormat === "json"
157
+ ? JSON.stringify(tokenBody)
158
+ : new URLSearchParams(tokenBody),
144
159
  });
145
160
 
146
161
  if (!tokenResp.ok) {
@@ -785,8 +800,10 @@ export async function refreshOAuth2Token(
785
800
  refreshToken: string,
786
801
  clientSecret?: string,
787
802
  tokenEndpointAuthMethod?: TokenEndpointAuthMethod,
803
+ tokenExchangeBodyFormat?: "form" | "json",
788
804
  ): Promise<OAuth2TokenResult> {
789
805
  const authMethod = tokenEndpointAuthMethod ?? "client_secret_post";
806
+ const bodyFormat = tokenExchangeBodyFormat ?? "form";
790
807
 
791
808
  const body: Record<string, string> = {
792
809
  grant_type: "refresh_token",
@@ -794,7 +811,11 @@ export async function refreshOAuth2Token(
794
811
  };
795
812
 
796
813
  const headers: Record<string, string> = {
797
- "Content-Type": "application/x-www-form-urlencoded",
814
+ "Content-Type":
815
+ bodyFormat === "json"
816
+ ? "application/json"
817
+ : "application/x-www-form-urlencoded",
818
+ Accept: "application/json",
798
819
  };
799
820
 
800
821
  if (clientSecret && authMethod === "client_secret_basic") {
@@ -812,7 +833,8 @@ export async function refreshOAuth2Token(
812
833
  const resp = await fetch(tokenExchangeUrl, {
813
834
  method: "POST",
814
835
  headers,
815
- body: new URLSearchParams(body),
836
+ body:
837
+ bodyFormat === "json" ? JSON.stringify(body) : new URLSearchParams(body),
816
838
  });
817
839
 
818
840
  if (!resp.ok) {