@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,151 @@
1
+ /**
2
+ * Global test utility for mocking gateway IPC calls via `node:net`.
3
+ *
4
+ * Usage:
5
+ * import { mockGatewayIpc, resetMockGatewayIpc } from "../__tests__/mock-gateway-ipc.js";
6
+ *
7
+ * beforeEach(() => resetMockGatewayIpc());
8
+ * afterEach(() => resetMockGatewayIpc());
9
+ *
10
+ * it("uses IPC flags", async () => {
11
+ * mockGatewayIpc({ "my-flag": true });
12
+ * await initFeatureFlagOverrides();
13
+ * ...
14
+ * });
15
+ *
16
+ * it("simulates socket error", async () => {
17
+ * mockGatewayIpc(null, { error: true, code: "ENOENT" });
18
+ * ...
19
+ * });
20
+ *
21
+ * The mock is registered in the test preload (test-preload.ts) so every test
22
+ * file gets a no-op IPC layer by default — no test accidentally connects to
23
+ * a real gateway socket. Call `mockGatewayIpc()` to configure specific
24
+ * responses when the test cares about the IPC result.
25
+ *
26
+ * Mocks `node:net` at the socket level, but ONLY intercepts connections to
27
+ * `gateway.sock` paths. All other `node:net` exports and non-gateway
28
+ * `connect()` calls pass through to the real implementation so that proxy /
29
+ * tunnel tests continue to work.
30
+ */
31
+
32
+ import { EventEmitter } from "node:events";
33
+ import { mock } from "bun:test";
34
+
35
+ // ---------------------------------------------------------------------------
36
+ // Configurable state
37
+ // ---------------------------------------------------------------------------
38
+
39
+ /** IPC result the fake gateway will return (keyed by method name). */
40
+ let ipcResults: Record<string, unknown> = {};
41
+
42
+ /** Whether the fake socket should simulate a connection error. */
43
+ let simulateError = false;
44
+
45
+ /** Error code to use when simulating an error. */
46
+ let simulateErrorCode = "ENOENT";
47
+
48
+ // ---------------------------------------------------------------------------
49
+ // FakeSocket — simulates the gateway IPC protocol
50
+ // ---------------------------------------------------------------------------
51
+
52
+ class FakeSocket extends EventEmitter {
53
+ unref() {
54
+ /* no-op */
55
+ }
56
+ destroy() {
57
+ /* no-op */
58
+ }
59
+ write(data: string) {
60
+ try {
61
+ const req = JSON.parse(data.trim());
62
+ const result =
63
+ req.method in ipcResults ? ipcResults[req.method] : undefined;
64
+ const response = JSON.stringify({ id: req.id, result });
65
+ queueMicrotask(() => {
66
+ this.emit("data", Buffer.from(response + "\n"));
67
+ });
68
+ } catch {
69
+ // Malformed request — ignore
70
+ }
71
+ }
72
+ }
73
+
74
+ // ---------------------------------------------------------------------------
75
+ // Register the mock (called once from test-preload.ts)
76
+ // ---------------------------------------------------------------------------
77
+
78
+ export function installGatewayIpcMock(): void {
79
+ // Snapshot the real node:net exports BEFORE mock.module replaces them.
80
+ // require() returns the current (real) module synchronously; after
81
+ // mock.module() the namespace is replaced for all future importers
82
+ // (like gateway-client.ts), but our captured references stay real.
83
+ // eslint-disable-next-line @typescript-eslint/no-require-imports -- require() is intentional: we need a synchronous snapshot of the real module before mock.module() replaces it
84
+ const realNet = require("node:net") as typeof import("node:net");
85
+ const realConnect = realNet.connect;
86
+
87
+ mock.module("node:net", () => ({
88
+ ...realNet,
89
+ connect(...args: unknown[]) {
90
+ // Only intercept Unix domain socket connections to gateway.sock.
91
+ // Everything else (TCP ports, other Unix sockets) passes through
92
+ // to the real node:net so proxy / tunnel tests keep working.
93
+ if (
94
+ typeof args[0] === "string" &&
95
+ (args[0] as string).endsWith("gateway.sock")
96
+ ) {
97
+ const socket = new FakeSocket();
98
+ queueMicrotask(() => {
99
+ if (simulateError) {
100
+ const err = new Error(simulateErrorCode) as NodeJS.ErrnoException;
101
+ err.code = simulateErrorCode;
102
+ socket.emit("error", err);
103
+ socket.emit("close");
104
+ } else {
105
+ socket.emit("connect");
106
+ }
107
+ });
108
+ return socket;
109
+ }
110
+ return realConnect(...(args as Parameters<typeof realConnect>));
111
+ },
112
+ }));
113
+ }
114
+
115
+ // ---------------------------------------------------------------------------
116
+ // Public API for tests
117
+ // ---------------------------------------------------------------------------
118
+
119
+ /**
120
+ * Configure the fake gateway IPC response.
121
+ *
122
+ * @param flags — feature flag map returned by `get_feature_flags`. Pass
123
+ * `null` to skip setting a result (useful when only simulating errors).
124
+ * @param opts.error — simulate a socket connection error
125
+ * @param opts.code — error code (default "ENOENT")
126
+ * @param opts.results — raw method→result map for arbitrary IPC methods
127
+ */
128
+ export function mockGatewayIpc(
129
+ flags?: Record<string, boolean> | null,
130
+ opts?: { error?: boolean; code?: string; results?: Record<string, unknown> },
131
+ ): void {
132
+ if (flags != null) {
133
+ ipcResults["get_feature_flags"] = flags;
134
+ }
135
+ if (opts?.results) {
136
+ Object.assign(ipcResults, opts.results);
137
+ }
138
+ if (opts?.error) {
139
+ simulateError = true;
140
+ simulateErrorCode = opts.code ?? "ENOENT";
141
+ }
142
+ }
143
+
144
+ /**
145
+ * Reset all IPC mock state back to defaults (empty flags, no errors).
146
+ */
147
+ export function resetMockGatewayIpc(): void {
148
+ ipcResults = {};
149
+ simulateError = false;
150
+ simulateErrorCode = "ENOENT";
151
+ }
@@ -59,7 +59,7 @@ describe("model intents", () => {
59
59
  "claude-haiku-4-5-20251001",
60
60
  );
61
61
  expect(resolveModelIntent("anthropic", "quality-optimized")).toBe(
62
- "claude-opus-4-6",
62
+ "claude-opus-4-7",
63
63
  );
64
64
  expect(resolveModelIntent("anthropic", "vision-optimized")).toBe(
65
65
  "claude-opus-4-6",
@@ -94,7 +94,7 @@ describe("RetryProvider model intent normalization", () => {
94
94
  });
95
95
 
96
96
  const config = seen?.config as Record<string, unknown>;
97
- expect(config.model).toBe("claude-opus-4-6");
97
+ expect(config.model).toBe("claude-opus-4-7");
98
98
  expect(config.modelIntent).toBeUndefined();
99
99
  expect(config.max_tokens).toBe(123);
100
100
  });
@@ -52,6 +52,7 @@ mock.module("../oauth/oauth-store.js", () => ({
52
52
  tokenExchangeUrl: "https://oauth2.googleapis.com/token",
53
53
  refreshUrl: null,
54
54
  tokenEndpointAuthMethod: "client_secret_post",
55
+ tokenExchangeBodyFormat: "form",
55
56
  userinfoUrl: null,
56
57
  baseUrl: null,
57
58
  defaultScopes: "[]",
@@ -173,6 +173,8 @@ mock.module("../oauth/oauth-store.js", () => ({
173
173
  tokenEndpointAuthMethod:
174
174
  (params.tokenEndpointAuthMethod as string | undefined) ||
175
175
  "client_secret_post",
176
+ tokenExchangeBodyFormat:
177
+ (params.tokenExchangeBodyFormat as string | undefined) ?? "form",
176
178
  userinfoUrl: params.userinfoUrl ?? null,
177
179
  baseUrl: params.baseUrl ?? null,
178
180
  defaultScopes: JSON.stringify(params.defaultScopes ?? []),
@@ -91,6 +91,7 @@ type ProviderRow = {
91
91
  tokenExchangeUrl: string;
92
92
  refreshUrl: string | null;
93
93
  tokenEndpointAuthMethod: string;
94
+ tokenExchangeBodyFormat: string;
94
95
  userinfoUrl: string | null;
95
96
  baseUrl: string | null;
96
97
  defaultScopes: string;
@@ -169,6 +170,7 @@ function makeProviderRow(
169
170
  tokenExchangeUrl: "https://provider.example.com/token",
170
171
  refreshUrl: null,
171
172
  tokenEndpointAuthMethod: "client_secret_post",
173
+ tokenExchangeBodyFormat: "form",
172
174
  userinfoUrl: null,
173
175
  baseUrl: null,
174
176
  defaultScopes: '["openid","email"]',
@@ -15,6 +15,7 @@ function makeRow(overrides: Partial<OAuthProviderRow> = {}): OAuthProviderRow {
15
15
  tokenExchangeUrl: "https://auth.example.com/token",
16
16
  refreshUrl: null,
17
17
  tokenEndpointAuthMethod: "client_secret_post",
18
+ tokenExchangeBodyFormat: "form",
18
19
  userinfoUrl: null,
19
20
  baseUrl: null,
20
21
  defaultScopes: "[]",
@@ -14,6 +14,7 @@ const mockListProviders = mock(() => [
14
14
  tokenExchangeUrl: "https://oauth2.googleapis.com/token",
15
15
  refreshUrl: null,
16
16
  tokenEndpointAuthMethod: "client_secret_post",
17
+ tokenExchangeBodyFormat: "form",
17
18
  userinfoUrl: null,
18
19
  baseUrl: null,
19
20
  defaultScopes: "[]",
@@ -55,6 +56,7 @@ const mockListProviders = mock(() => [
55
56
  tokenExchangeUrl: "https://github.com/login/oauth/access_token",
56
57
  refreshUrl: null,
57
58
  tokenEndpointAuthMethod: "client_secret_post",
59
+ tokenExchangeBodyFormat: "form",
58
60
  userinfoUrl: null,
59
61
  baseUrl: null,
60
62
  defaultScopes: "[]",
@@ -592,6 +592,38 @@ describe("provider operations", () => {
592
592
  expect(row!.tokenEndpointAuthMethod).toBe("client_secret_post");
593
593
  });
594
594
 
595
+ test("defaults tokenExchangeBodyFormat to 'form' when omitted from seed", () => {
596
+ seedProviders([
597
+ {
598
+ provider: "no-body-format-provider",
599
+ authorizeUrl: "https://example.com/authorize",
600
+ tokenExchangeUrl: "https://example.com/token",
601
+ defaultScopes: [],
602
+ scopePolicy: {},
603
+ // Note: tokenExchangeBodyFormat intentionally omitted
604
+ },
605
+ ]);
606
+ const row = getProvider("no-body-format-provider");
607
+ expect(row).toBeDefined();
608
+ expect(row!.tokenExchangeBodyFormat).toBe("form");
609
+ });
610
+
611
+ test("persists explicit tokenExchangeBodyFormat value on seed", () => {
612
+ seedProviders([
613
+ {
614
+ provider: "json-body-format-provider",
615
+ authorizeUrl: "https://example.com/authorize",
616
+ tokenExchangeUrl: "https://example.com/token",
617
+ defaultScopes: [],
618
+ scopePolicy: {},
619
+ tokenExchangeBodyFormat: "json",
620
+ },
621
+ ]);
622
+ const row = getProvider("json-body-format-provider");
623
+ expect(row).toBeDefined();
624
+ expect(row!.tokenExchangeBodyFormat).toBe("json");
625
+ });
626
+
595
627
  test("migration 216 backfills NULL token_endpoint_auth_method to client_secret_post", () => {
596
628
  // Use raw SQLite to bypass Drizzle's NOT NULL enforcement and insert
597
629
  // a legacy-shaped row with NULL token_endpoint_auth_method.
@@ -813,6 +845,33 @@ describe("provider operations", () => {
813
845
  expect(row.tokenEndpointAuthMethod).toBe("client_secret_basic");
814
846
  });
815
847
 
848
+ test("defaults tokenExchangeBodyFormat to 'form' when omitted", () => {
849
+ const row = registerProvider({
850
+ provider: "no-body-format-test",
851
+ authorizeUrl: "https://example.com/authorize",
852
+ tokenExchangeUrl: "https://example.com/token",
853
+ defaultScopes: [],
854
+ scopePolicy: {},
855
+ // Note: tokenExchangeBodyFormat intentionally omitted
856
+ });
857
+ expect(row.tokenExchangeBodyFormat).toBe("form");
858
+
859
+ const fetched = getProvider("no-body-format-test");
860
+ expect(fetched!.tokenExchangeBodyFormat).toBe("form");
861
+ });
862
+
863
+ test("persists explicit tokenExchangeBodyFormat 'json' when registering a provider", () => {
864
+ const row = registerProvider({
865
+ provider: "json-body-format-test",
866
+ authorizeUrl: "https://example.com/authorize",
867
+ tokenExchangeUrl: "https://example.com/token",
868
+ defaultScopes: [],
869
+ scopePolicy: {},
870
+ tokenExchangeBodyFormat: "json",
871
+ });
872
+ expect(row.tokenExchangeBodyFormat).toBe("json");
873
+ });
874
+
816
875
  test("stores logoUrl when provided", () => {
817
876
  registerProvider({
818
877
  provider: "notion",
@@ -1059,6 +1118,32 @@ describe("provider operations", () => {
1059
1118
  expect(row!.tokenEndpointAuthMethod).toBe("client_secret_post");
1060
1119
  });
1061
1120
 
1121
+ test("coerces empty string tokenExchangeBodyFormat to 'form'", () => {
1122
+ seedProviders([
1123
+ {
1124
+ provider: "update-empty-body-format-test",
1125
+ authorizeUrl: "https://example.com/authorize",
1126
+ tokenExchangeUrl: "https://example.com/token",
1127
+ tokenExchangeBodyFormat: "json",
1128
+ defaultScopes: [],
1129
+ scopePolicy: {},
1130
+ },
1131
+ ]);
1132
+
1133
+ expect(
1134
+ getProvider("update-empty-body-format-test")!.tokenExchangeBodyFormat,
1135
+ ).toBe("json");
1136
+
1137
+ const updated = updateProvider("update-empty-body-format-test", {
1138
+ tokenExchangeBodyFormat: "",
1139
+ });
1140
+ expect(updated).toBeDefined();
1141
+ expect(updated!.tokenExchangeBodyFormat).toBe("form");
1142
+
1143
+ const row = getProvider("update-empty-body-format-test");
1144
+ expect(row!.tokenExchangeBodyFormat).toBe("form");
1145
+ });
1146
+
1062
1147
  test("sets logoUrl on an existing row where it was previously null", () => {
1063
1148
  registerProvider({
1064
1149
  provider: "linear",
@@ -83,6 +83,7 @@ mock.module("../inbound/platform-callback-registration.js", () => ({
83
83
  // Track token exchange request
84
84
  let lastTokenRequestBody: URLSearchParams | null = null;
85
85
  let lastTokenRequestHeaders: Record<string, string> = {};
86
+ let lastTokenRequestRawBody: string | null = null;
86
87
 
87
88
  // Mock fetch for token exchange
88
89
  let mockTokenResponse: {
@@ -112,7 +113,12 @@ globalThis.fetch = (async (input: RequestInfo | URL, init?: RequestInit) => {
112
113
  if (url.includes("token")) {
113
114
  // Capture request body and headers for assertions
114
115
  if (init?.body) {
115
- lastTokenRequestBody = new URLSearchParams(init.body as string);
116
+ lastTokenRequestRawBody = String(init.body);
117
+ try {
118
+ lastTokenRequestBody = new URLSearchParams(init.body as string);
119
+ } catch {
120
+ lastTokenRequestBody = null;
121
+ }
116
122
  }
117
123
  if (init?.headers) {
118
124
  lastTokenRequestHeaders = init.headers as Record<string, string>;
@@ -135,7 +141,11 @@ globalThis.fetch = (async (input: RequestInfo | URL, init?: RequestInit) => {
135
141
  // Import module under test AFTER mocks are in place
136
142
  // ---------------------------------------------------------------------------
137
143
 
138
- import { type OAuth2Config, startOAuth2Flow } from "../security/oauth2.js";
144
+ import {
145
+ type OAuth2Config,
146
+ refreshOAuth2Token,
147
+ startOAuth2Flow,
148
+ } from "../security/oauth2.js";
139
149
 
140
150
  const BASE_OAUTH_CONFIG: OAuth2Config = {
141
151
  authorizeUrl: "https://provider.example.com/authorize",
@@ -151,6 +161,7 @@ beforeEach(() => {
151
161
  pendingCallbacks.clear();
152
162
  lastTokenRequestBody = null;
153
163
  lastTokenRequestHeaders = {};
164
+ lastTokenRequestRawBody = null;
154
165
  mockTokenResponse = {
155
166
  ok: true,
156
167
  status: 200,
@@ -204,15 +215,19 @@ describe("OAuth2 gateway transport", () => {
204
215
  test("falls back to loopback transport when ingress.publicBaseUrl is not configured", async () => {
205
216
  mockPublicBaseUrl = "";
206
217
 
207
- let capturedAuthUrl = "";
218
+ let resolveOpenUrl!: (url: string) => void;
219
+ const openUrlPromise = new Promise<string>((resolve) => {
220
+ resolveOpenUrl = resolve;
221
+ });
208
222
  const flowPromise = startOAuth2Flow(BASE_OAUTH_CONFIG, {
209
223
  openUrl: (url) => {
210
- capturedAuthUrl = url;
224
+ resolveOpenUrl(url);
211
225
  },
212
226
  });
213
227
 
214
- // Give the loopback server time to start
215
- await new Promise((r) => setTimeout(r, 50));
228
+ // Wait for the loopback server to bind and build the auth URL.
229
+ // Awaiting the openUrl callback instead of a fixed timer avoids CI-load flakes.
230
+ const capturedAuthUrl = await openUrlPromise;
216
231
 
217
232
  // Auth URL should use a localhost redirect_uri
218
233
  expect(capturedAuthUrl).toContain("redirect_uri=");
@@ -844,4 +859,232 @@ describe("OAuth2 gateway transport", () => {
844
859
  expect(result.grantedScopes).toEqual(["read", "write", "admin"]);
845
860
  });
846
861
  });
862
+
863
+ describe("tokenExchangeBodyFormat", () => {
864
+ test("sends JSON body when tokenExchangeBodyFormat is 'json'", async () => {
865
+ mockPublicBaseUrl = "https://gw.example.com";
866
+
867
+ const jsonConfig: OAuth2Config = {
868
+ ...BASE_OAUTH_CONFIG,
869
+ clientSecret: "test-client-secret",
870
+ tokenExchangeBodyFormat: "json",
871
+ };
872
+
873
+ const flowPromise = startOAuth2Flow(
874
+ jsonConfig,
875
+ { openUrl: () => {} },
876
+ { callbackTransport: "gateway" },
877
+ );
878
+
879
+ await new Promise((r) => setTimeout(r, 10));
880
+
881
+ const entries = Array.from(pendingCallbacks.entries());
882
+ entries[0][1].resolve("json-body-code");
883
+
884
+ await flowPromise;
885
+
886
+ // Content-Type should be application/json
887
+ expect(lastTokenRequestHeaders["Content-Type"]).toBe("application/json");
888
+
889
+ // Body should be valid JSON
890
+ expect(lastTokenRequestRawBody).not.toBeNull();
891
+ const parsed = JSON.parse(lastTokenRequestRawBody!);
892
+ expect(parsed.grant_type).toBe("authorization_code");
893
+ expect(parsed.client_id).toBe("test-client-id");
894
+ expect(parsed.client_secret).toBe("test-client-secret");
895
+ expect(parsed.code).toBe("json-body-code");
896
+ expect(parsed.code_verifier).toBeTruthy();
897
+ });
898
+
899
+ test("sends form-encoded body by default (tokenExchangeBodyFormat omitted)", async () => {
900
+ mockPublicBaseUrl = "https://gw.example.com";
901
+
902
+ const flowPromise = startOAuth2Flow(
903
+ BASE_OAUTH_CONFIG,
904
+ { openUrl: () => {} },
905
+ { callbackTransport: "gateway" },
906
+ );
907
+
908
+ await new Promise((r) => setTimeout(r, 10));
909
+
910
+ const entries = Array.from(pendingCallbacks.entries());
911
+ entries[0][1].resolve("form-body-code");
912
+
913
+ await flowPromise;
914
+
915
+ // Content-Type should be form-encoded
916
+ expect(lastTokenRequestHeaders["Content-Type"]).toBe(
917
+ "application/x-www-form-urlencoded",
918
+ );
919
+
920
+ // Body should be parseable as URLSearchParams
921
+ expect(lastTokenRequestBody).not.toBeNull();
922
+ expect(lastTokenRequestBody!.get("grant_type")).toBe(
923
+ "authorization_code",
924
+ );
925
+ expect(lastTokenRequestBody!.get("client_id")).toBe("test-client-id");
926
+ });
927
+
928
+ test("sends form-encoded body when tokenExchangeBodyFormat is explicitly 'form'", async () => {
929
+ mockPublicBaseUrl = "https://gw.example.com";
930
+
931
+ const formConfig: OAuth2Config = {
932
+ ...BASE_OAUTH_CONFIG,
933
+ tokenExchangeBodyFormat: "form",
934
+ };
935
+
936
+ const flowPromise = startOAuth2Flow(
937
+ formConfig,
938
+ { openUrl: () => {} },
939
+ { callbackTransport: "gateway" },
940
+ );
941
+
942
+ await new Promise((r) => setTimeout(r, 10));
943
+
944
+ const entries = Array.from(pendingCallbacks.entries());
945
+ entries[0][1].resolve("explicit-form-code");
946
+
947
+ await flowPromise;
948
+
949
+ // Content-Type should be form-encoded
950
+ expect(lastTokenRequestHeaders["Content-Type"]).toBe(
951
+ "application/x-www-form-urlencoded",
952
+ );
953
+
954
+ // Body should be parseable as URLSearchParams
955
+ expect(lastTokenRequestBody).not.toBeNull();
956
+ expect(lastTokenRequestBody!.get("grant_type")).toBe(
957
+ "authorization_code",
958
+ );
959
+ });
960
+
961
+ test("JSON body format works with client_secret_basic auth method", async () => {
962
+ mockPublicBaseUrl = "https://gw.example.com";
963
+
964
+ const jsonBasicConfig: OAuth2Config = {
965
+ ...BASE_OAUTH_CONFIG,
966
+ clientSecret: "test-client-secret",
967
+ tokenEndpointAuthMethod: "client_secret_basic",
968
+ tokenExchangeBodyFormat: "json",
969
+ };
970
+
971
+ const flowPromise = startOAuth2Flow(
972
+ jsonBasicConfig,
973
+ { openUrl: () => {} },
974
+ { callbackTransport: "gateway" },
975
+ );
976
+
977
+ await new Promise((r) => setTimeout(r, 10));
978
+
979
+ const entries = Array.from(pendingCallbacks.entries());
980
+ entries[0][1].resolve("json-basic-code");
981
+
982
+ await flowPromise;
983
+
984
+ // Content-Type should be application/json
985
+ expect(lastTokenRequestHeaders["Content-Type"]).toBe("application/json");
986
+
987
+ // Should have Basic Auth header
988
+ const expectedCredentials = Buffer.from(
989
+ "test-client-id:test-client-secret",
990
+ ).toString("base64");
991
+ expect(lastTokenRequestHeaders["Authorization"]).toBe(
992
+ `Basic ${expectedCredentials}`,
993
+ );
994
+
995
+ // Body should be valid JSON without client_id/client_secret
996
+ const parsed = JSON.parse(lastTokenRequestRawBody!);
997
+ expect(parsed.grant_type).toBe("authorization_code");
998
+ expect(parsed.client_id).toBeUndefined();
999
+ expect(parsed.client_secret).toBeUndefined();
1000
+ expect(parsed.code_verifier).toBeTruthy();
1001
+ });
1002
+ });
1003
+
1004
+ describe("refreshOAuth2Token", () => {
1005
+ test("sends JSON body when tokenExchangeBodyFormat is 'json'", async () => {
1006
+ const result = await refreshOAuth2Token(
1007
+ "https://provider.example.com/token",
1008
+ "test-client-id",
1009
+ "test-refresh-token",
1010
+ "test-client-secret",
1011
+ undefined, // tokenEndpointAuthMethod defaults to client_secret_post
1012
+ "json",
1013
+ );
1014
+
1015
+ // Content-Type should be application/json
1016
+ expect(lastTokenRequestHeaders["Content-Type"]).toBe("application/json");
1017
+
1018
+ // Body should be valid JSON with refresh_token grant
1019
+ expect(lastTokenRequestRawBody).not.toBeNull();
1020
+ const parsed = JSON.parse(lastTokenRequestRawBody!);
1021
+ expect(parsed.grant_type).toBe("refresh_token");
1022
+ expect(parsed.refresh_token).toBe("test-refresh-token");
1023
+ expect(parsed.client_id).toBe("test-client-id");
1024
+ expect(parsed.client_secret).toBe("test-client-secret");
1025
+
1026
+ // Result should contain the tokens
1027
+ expect(result.accessToken).toBe("test-access-token");
1028
+ expect(result.refreshToken).toBe("test-refresh-token");
1029
+ });
1030
+
1031
+ test("sends form-encoded body by default", async () => {
1032
+ const result = await refreshOAuth2Token(
1033
+ "https://provider.example.com/token",
1034
+ "test-client-id",
1035
+ "test-refresh-token",
1036
+ "test-client-secret",
1037
+ );
1038
+
1039
+ // Content-Type should be form-encoded
1040
+ expect(lastTokenRequestHeaders["Content-Type"]).toBe(
1041
+ "application/x-www-form-urlencoded",
1042
+ );
1043
+
1044
+ // Body should be parseable as URLSearchParams
1045
+ expect(lastTokenRequestBody).not.toBeNull();
1046
+ expect(lastTokenRequestBody!.get("grant_type")).toBe("refresh_token");
1047
+ expect(lastTokenRequestBody!.get("refresh_token")).toBe(
1048
+ "test-refresh-token",
1049
+ );
1050
+ expect(lastTokenRequestBody!.get("client_id")).toBe("test-client-id");
1051
+ expect(lastTokenRequestBody!.get("client_secret")).toBe(
1052
+ "test-client-secret",
1053
+ );
1054
+
1055
+ expect(result.accessToken).toBe("test-access-token");
1056
+ });
1057
+
1058
+ test("JSON body format works with client_secret_basic auth method", async () => {
1059
+ const result = await refreshOAuth2Token(
1060
+ "https://provider.example.com/token",
1061
+ "test-client-id",
1062
+ "test-refresh-token",
1063
+ "test-client-secret",
1064
+ "client_secret_basic",
1065
+ "json",
1066
+ );
1067
+
1068
+ // Content-Type should be application/json
1069
+ expect(lastTokenRequestHeaders["Content-Type"]).toBe("application/json");
1070
+
1071
+ // Should have Basic Auth header
1072
+ const expectedCredentials = Buffer.from(
1073
+ "test-client-id:test-client-secret",
1074
+ ).toString("base64");
1075
+ expect(lastTokenRequestHeaders["Authorization"]).toBe(
1076
+ `Basic ${expectedCredentials}`,
1077
+ );
1078
+
1079
+ // Body should be valid JSON without client_id/client_secret (basic auth puts them in header)
1080
+ expect(lastTokenRequestRawBody).not.toBeNull();
1081
+ const parsed = JSON.parse(lastTokenRequestRawBody!);
1082
+ expect(parsed.grant_type).toBe("refresh_token");
1083
+ expect(parsed.refresh_token).toBe("test-refresh-token");
1084
+ expect(parsed.client_id).toBeUndefined();
1085
+ expect(parsed.client_secret).toBeUndefined();
1086
+
1087
+ expect(result.accessToken).toBe("test-access-token");
1088
+ });
1089
+ });
847
1090
  });
@@ -9,7 +9,6 @@ const bootstrapRef = readFileSync(
9
9
  "utf-8",
10
10
  );
11
11
  const identity = readFileSync(join(templatesDir, "IDENTITY.md"), "utf-8");
12
- const user = readFileSync(join(templatesDir, "USER.md"), "utf-8");
13
12
 
14
13
  describe("onboarding template contracts", () => {
15
14
  describe("BOOTSTRAP.md", () => {
@@ -49,10 +48,10 @@ describe("onboarding template contracts", () => {
49
48
  expect(lower).toContain("declined");
50
49
  });
51
50
 
52
- test("instructs saving to IDENTITY.md, USER.md, and SOUL.md via file_edit", () => {
51
+ test("instructs saving to IDENTITY.md, SOUL.md, and user persona file via file_edit", () => {
53
52
  expect(bootstrap).toContain("IDENTITY.md");
54
- expect(bootstrap).toContain("USER.md");
55
53
  expect(bootstrap).toContain("SOUL.md");
54
+ expect(bootstrap).toContain("{{USER_PERSONA_FILE}}");
56
55
  expect(bootstrap).toContain("file_edit");
57
56
  });
58
57
 
@@ -165,14 +164,8 @@ describe("onboarding template contracts", () => {
165
164
  });
166
165
  });
167
166
 
168
- describe("USER.md", () => {
169
- test("contains profile fields", () => {
170
- expect(user).toContain("Preferred name/reference:");
171
- expect(user).toContain("Goals:");
172
- expect(user).toContain("Locale:");
173
- expect(user).toContain("Work role:");
174
- expect(user).toContain("Hobbies/fun:");
175
- expect(user).toContain("Daily tools:");
176
- });
177
- });
167
+ // Legacy `templates/USER.md` was removed by workspace migration
168
+ // `031-drop-user-md`. Guardian persona content is now seeded via
169
+ // `GUARDIAN_PERSONA_TEMPLATE` in `prompts/persona-resolver.ts` and
170
+ // lives on disk at `users/<slug>.md`.
178
171
  });