@vellumai/assistant 0.3.4 → 0.3.6

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 (506) hide show
  1. package/Dockerfile +2 -0
  2. package/README.md +88 -2
  3. package/eslint.config.mjs +31 -0
  4. package/package.json +1 -1
  5. package/scripts/ipc/check-swift-decoder-drift.ts +4 -1
  6. package/scripts/ipc/generate-swift.ts +31 -2
  7. package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +438 -1
  8. package/src/__tests__/approval-conversation-turn.test.ts +214 -0
  9. package/src/__tests__/approval-hardcoded-copy-guard.test.ts +41 -0
  10. package/src/__tests__/approval-message-composer.test.ts +253 -0
  11. package/src/__tests__/browser-manager.test.ts +1 -0
  12. package/src/__tests__/call-conversation-messages.test.ts +130 -0
  13. package/src/__tests__/call-domain.test.ts +12 -2
  14. package/src/__tests__/call-orchestrator.test.ts +799 -249
  15. package/src/__tests__/call-pointer-messages.test.ts +148 -0
  16. package/src/__tests__/call-recovery.test.ts +3 -0
  17. package/src/__tests__/call-routes-http.test.ts +32 -2
  18. package/src/__tests__/call-store.test.ts +3 -0
  19. package/src/__tests__/channel-approval-routes.test.ts +1277 -98
  20. package/src/__tests__/channel-approval.test.ts +37 -0
  21. package/src/__tests__/channel-approvals.test.ts +36 -50
  22. package/src/__tests__/channel-guardian.test.ts +630 -22
  23. package/src/__tests__/channel-readiness-service.test.ts +324 -0
  24. package/src/__tests__/checker.test.ts +14 -7
  25. package/src/__tests__/clarification-resolver.test.ts +44 -24
  26. package/src/__tests__/commit-message-enrichment-service.test.ts +9 -4
  27. package/src/__tests__/computer-use-session-working-dir.test.ts +8 -0
  28. package/src/__tests__/config-schema.test.ts +14 -8
  29. package/src/__tests__/context-window-manager.test.ts +30 -2
  30. package/src/__tests__/contradiction-checker.test.ts +20 -5
  31. package/src/__tests__/credential-security-invariants.test.ts +7 -2
  32. package/src/__tests__/daemon-lifecycle.test.ts +13 -12
  33. package/src/__tests__/db-migration-rollback.test.ts +752 -0
  34. package/src/__tests__/dictation-mode-detection.test.ts +63 -0
  35. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +2 -0
  36. package/src/__tests__/entity-search.test.ts +615 -0
  37. package/src/__tests__/fuzzy-match-property.test.ts +5 -5
  38. package/src/__tests__/guardian-action-store.test.ts +123 -0
  39. package/src/__tests__/guardian-action-sweep.test.ts +277 -0
  40. package/src/__tests__/guardian-dispatch.test.ts +389 -0
  41. package/src/__tests__/guardian-question-copy.test.ts +47 -0
  42. package/src/__tests__/handlers-telegram-config.test.ts +4 -2
  43. package/src/__tests__/handlers-twilio-config.test.ts +533 -0
  44. package/src/__tests__/intent-routing.test.ts +2 -0
  45. package/src/__tests__/ipc-snapshot.test.ts +291 -1
  46. package/src/__tests__/memory-upsert-concurrency.test.ts +828 -0
  47. package/src/__tests__/messaging-send-tool.test.ts +65 -0
  48. package/src/__tests__/model-intents.test.ts +96 -0
  49. package/src/__tests__/no-direct-anthropic-sdk-imports.test.ts +42 -0
  50. package/src/__tests__/oauth2-gateway-transport.test.ts +130 -0
  51. package/src/__tests__/onboarding-starter-tasks.test.ts +2 -0
  52. package/src/__tests__/provider-commit-message-generator.test.ts +89 -13
  53. package/src/__tests__/provider-error-scenarios.test.ts +621 -0
  54. package/src/__tests__/provider-fail-open-selection.test.ts +119 -0
  55. package/src/__tests__/qdrant-manager.test.ts +27 -20
  56. package/src/__tests__/relay-server.test.ts +779 -40
  57. package/src/__tests__/run-orchestrator-assistant-events.test.ts +6 -0
  58. package/src/__tests__/run-orchestrator.test.ts +42 -4
  59. package/src/__tests__/runtime-runs-http.test.ts +17 -1
  60. package/src/__tests__/runtime-runs.test.ts +16 -0
  61. package/src/__tests__/schedule-store.test.ts +18 -4
  62. package/src/__tests__/scheduler-recurrence.test.ts +13 -4
  63. package/src/__tests__/session-abort-tool-results.test.ts +6 -0
  64. package/src/__tests__/session-agent-loop.test.ts +857 -0
  65. package/src/__tests__/session-conflict-gate.test.ts +6 -0
  66. package/src/__tests__/session-pre-run-repair.test.ts +6 -0
  67. package/src/__tests__/session-profile-injection.test.ts +6 -0
  68. package/src/__tests__/session-provider-retry-repair.test.ts +6 -0
  69. package/src/__tests__/session-queue.test.ts +6 -0
  70. package/src/__tests__/session-runtime-assembly.test.ts +321 -13
  71. package/src/__tests__/session-slash-known.test.ts +6 -0
  72. package/src/__tests__/session-slash-queue.test.ts +6 -0
  73. package/src/__tests__/session-slash-unknown.test.ts +6 -0
  74. package/src/__tests__/session-surfaces-task-progress.test.ts +2 -0
  75. package/src/__tests__/session-tool-setup-app-refresh.test.ts +1 -0
  76. package/src/__tests__/session-tool-setup-memory-scope.test.ts +1 -0
  77. package/src/__tests__/session-tool-setup-side-effect-flag.test.ts +1 -0
  78. package/src/__tests__/session-workspace-injection.test.ts +6 -0
  79. package/src/__tests__/session-workspace-tool-tracking.test.ts +6 -0
  80. package/src/__tests__/skills.test.ts +2 -0
  81. package/src/__tests__/sms-messaging-provider.test.ts +126 -0
  82. package/src/__tests__/starter-task-flow.test.ts +2 -0
  83. package/src/__tests__/swarm-dag-pathological.test.ts +535 -0
  84. package/src/__tests__/system-prompt.test.ts +2 -0
  85. package/src/__tests__/task-management-tools.test.ts +2 -2
  86. package/src/__tests__/task-runner.test.ts +14 -4
  87. package/src/__tests__/terminal-tools.test.ts +25 -19
  88. package/src/__tests__/tool-execution-abort-cleanup.test.ts +545 -0
  89. package/src/__tests__/tool-executor-shell-integration.test.ts +11 -11
  90. package/src/__tests__/tool-executor.test.ts +23 -24
  91. package/src/__tests__/trust-store.test.ts +3 -3
  92. package/src/__tests__/twilio-rest.test.ts +29 -0
  93. package/src/__tests__/twilio-routes-elevenlabs.test.ts +3 -0
  94. package/src/__tests__/twilio-routes-twiml.test.ts +11 -0
  95. package/src/__tests__/twilio-routes.test.ts +167 -11
  96. package/src/__tests__/twitter-cli-error-shaping.test.ts +2 -2
  97. package/src/__tests__/user-reference.test.ts +2 -0
  98. package/src/__tests__/voice-quality.test.ts +222 -0
  99. package/src/__tests__/web-search.test.ts +46 -30
  100. package/src/__tests__/work-item-output.test.ts +110 -0
  101. package/src/agent/loop.ts +1 -1
  102. package/src/agent-heartbeat/agent-heartbeat-service.ts +2 -10
  103. package/src/amazon/client.ts +1418 -0
  104. package/src/amazon/request-extractor.ts +135 -0
  105. package/src/amazon/session.ts +109 -0
  106. package/src/autonomy/autonomy-store.ts +5 -5
  107. package/src/browser-extension-relay/client.ts +124 -0
  108. package/src/browser-extension-relay/protocol.ts +63 -0
  109. package/src/browser-extension-relay/server.ts +177 -0
  110. package/src/bundler/app-bundler.ts +3 -3
  111. package/src/bundler/bundle-signer.ts +1 -1
  112. package/src/bundler/signature-verifier.ts +1 -1
  113. package/src/calls/call-conversation-messages.ts +33 -0
  114. package/src/calls/call-domain.ts +114 -10
  115. package/src/calls/call-orchestrator.ts +268 -59
  116. package/src/calls/call-pointer-messages.ts +53 -0
  117. package/src/calls/call-recovery.ts +3 -8
  118. package/src/calls/call-store.ts +69 -87
  119. package/src/calls/elevenlabs-config.ts +3 -2
  120. package/src/calls/guardian-action-sweep.ts +105 -0
  121. package/src/calls/guardian-dispatch.ts +203 -0
  122. package/src/calls/guardian-question-copy.ts +133 -0
  123. package/src/calls/relay-server.ts +466 -8
  124. package/src/calls/speaker-identification.ts +1 -1
  125. package/src/calls/twilio-config.ts +22 -14
  126. package/src/calls/twilio-provider.ts +6 -4
  127. package/src/calls/twilio-rest.ts +308 -7
  128. package/src/calls/twilio-routes.ts +65 -12
  129. package/src/calls/types.ts +3 -1
  130. package/src/channels/types.ts +25 -0
  131. package/src/cli/amazon.ts +815 -0
  132. package/src/cli/config-commands.ts +2 -2
  133. package/src/cli/core-commands.ts +4 -3
  134. package/src/cli/influencer.ts +244 -0
  135. package/src/cli/map.ts +89 -6
  136. package/src/cli.ts +1 -1
  137. package/src/config/agent-schema.ts +171 -0
  138. package/src/config/bundled-skills/amazon/SKILL.md +127 -0
  139. package/src/config/bundled-skills/amazon/icon.svg +13 -0
  140. package/src/config/bundled-skills/api-mapping/SKILL.md +78 -0
  141. package/src/config/bundled-skills/browser/SKILL.md +1 -0
  142. package/src/config/bundled-skills/browser/TOOLS.json +17 -0
  143. package/src/config/bundled-skills/browser/tools/browser-wait-for-download.ts +25 -0
  144. package/src/config/bundled-skills/doordash/SKILL.md +51 -51
  145. package/src/config/bundled-skills/email-setup/SKILL.md +14 -5
  146. package/src/config/bundled-skills/google-oauth-setup/SKILL.md +183 -0
  147. package/src/config/bundled-skills/influencer/SKILL.md +144 -0
  148. package/src/config/bundled-skills/knowledge-graph/SKILL.md +15 -0
  149. package/src/config/bundled-skills/knowledge-graph/TOOLS.json +56 -0
  150. package/src/config/bundled-skills/knowledge-graph/tools/graph-query.ts +185 -0
  151. package/src/config/bundled-skills/macos-automation/icon.svg +12 -0
  152. package/src/config/bundled-skills/media-processing/SKILL.md +176 -0
  153. package/src/config/bundled-skills/media-processing/TOOLS.json +230 -0
  154. package/src/config/bundled-skills/media-processing/__tests__/concurrency-pool.test.ts +77 -0
  155. package/src/config/bundled-skills/media-processing/__tests__/cost-tracker.test.ts +69 -0
  156. package/src/config/bundled-skills/media-processing/__tests__/preprocess.test.ts +303 -0
  157. package/src/config/bundled-skills/media-processing/services/concurrency-pool.ts +55 -0
  158. package/src/config/bundled-skills/media-processing/services/cost-tracker.ts +86 -0
  159. package/src/config/bundled-skills/media-processing/services/gemini-map.ts +339 -0
  160. package/src/config/bundled-skills/media-processing/services/preprocess.ts +551 -0
  161. package/src/config/bundled-skills/media-processing/services/processing-pipeline.ts +259 -0
  162. package/src/config/bundled-skills/media-processing/services/reduce.ts +197 -0
  163. package/src/config/bundled-skills/media-processing/tools/analyze-keyframes.ts +136 -0
  164. package/src/config/bundled-skills/media-processing/tools/extract-keyframes.ts +59 -0
  165. package/src/config/bundled-skills/media-processing/tools/generate-clip.ts +195 -0
  166. package/src/config/bundled-skills/media-processing/tools/ingest-media.ts +197 -0
  167. package/src/config/bundled-skills/media-processing/tools/media-diagnostics.ts +143 -0
  168. package/src/config/bundled-skills/media-processing/tools/media-status.ts +75 -0
  169. package/src/config/bundled-skills/media-processing/tools/query-media-events.ts +65 -0
  170. package/src/config/bundled-skills/messaging/SKILL.md +33 -8
  171. package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +4 -7
  172. package/src/config/bundled-skills/messaging/tools/messaging-reply.ts +2 -1
  173. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +5 -1
  174. package/src/config/bundled-skills/phone-calls/SKILL.md +88 -23
  175. package/src/config/bundled-skills/twitter/SKILL.md +19 -3
  176. package/src/config/bundled-skills/twitter/icon.svg +14 -0
  177. package/src/config/bundled-tool-registry.ts +310 -0
  178. package/src/config/calls-schema.ts +181 -0
  179. package/src/config/core-schema.ts +309 -0
  180. package/src/config/defaults.ts +28 -3
  181. package/src/config/env-registry.ts +162 -0
  182. package/src/config/env.ts +175 -0
  183. package/src/config/loader.ts +6 -6
  184. package/src/config/memory-schema.ts +528 -0
  185. package/src/config/sandbox-schema.ts +55 -0
  186. package/src/config/schema.ts +158 -1133
  187. package/src/config/skill-state.ts +1 -1
  188. package/src/config/skills-schema.ts +32 -0
  189. package/src/config/skills.ts +35 -24
  190. package/src/config/system-prompt.ts +131 -56
  191. package/src/config/templates/IDENTITY.md +2 -2
  192. package/src/config/templates/SOUL.md +1 -1
  193. package/src/config/types.ts +1 -0
  194. package/src/config/user-reference.ts +4 -9
  195. package/src/config/vellum-skills/catalog.json +6 -7
  196. package/src/config/vellum-skills/chatgpt-import/tools/chatgpt-import.ts +5 -1
  197. package/src/config/vellum-skills/slack-oauth-setup/SKILL.md +4 -3
  198. package/src/config/vellum-skills/sms-setup/SKILL.md +216 -0
  199. package/src/config/vellum-skills/twilio-setup/SKILL.md +40 -8
  200. package/src/context/window-manager.ts +27 -7
  201. package/src/daemon/approval-generators.ts +186 -0
  202. package/src/daemon/approved-devices-store.ts +140 -0
  203. package/src/daemon/assistant-attachments.ts +1 -1
  204. package/src/daemon/classifier.ts +35 -32
  205. package/src/daemon/config-watcher.ts +1 -1
  206. package/src/daemon/daemon-control.ts +217 -0
  207. package/src/daemon/handlers/apps.ts +2 -3
  208. package/src/daemon/handlers/config-channels.ts +158 -0
  209. package/src/daemon/handlers/config-inbox.ts +540 -0
  210. package/src/daemon/handlers/config-ingress.ts +231 -0
  211. package/src/daemon/handlers/config-integrations.ts +258 -0
  212. package/src/daemon/handlers/config-model.ts +143 -0
  213. package/src/daemon/handlers/config-parental.ts +163 -0
  214. package/src/daemon/handlers/config-scheduling.ts +172 -0
  215. package/src/daemon/handlers/config-slack.ts +92 -0
  216. package/src/daemon/handlers/config-telegram.ts +301 -0
  217. package/src/daemon/handlers/config-tools.ts +177 -0
  218. package/src/daemon/handlers/config-trust.ts +104 -0
  219. package/src/daemon/handlers/config-twilio.ts +1080 -0
  220. package/src/daemon/handlers/config.ts +53 -1689
  221. package/src/daemon/handlers/diagnostics.ts +1 -1
  222. package/src/daemon/handlers/dictation.ts +180 -0
  223. package/src/daemon/handlers/documents.ts +18 -32
  224. package/src/daemon/handlers/identity.ts +14 -23
  225. package/src/daemon/handlers/index.ts +11 -0
  226. package/src/daemon/handlers/misc.ts +3 -5
  227. package/src/daemon/handlers/pairing.ts +98 -0
  228. package/src/daemon/handlers/sessions.ts +56 -5
  229. package/src/daemon/handlers/shared.ts +6 -1
  230. package/src/daemon/handlers/skills.ts +1 -1
  231. package/src/daemon/handlers/twitter-auth.ts +2 -0
  232. package/src/daemon/handlers/work-items.ts +17 -9
  233. package/src/daemon/handlers/workspace-files.ts +4 -3
  234. package/src/daemon/install-cli-launchers.ts +113 -0
  235. package/src/daemon/ipc-contract/apps.ts +356 -0
  236. package/src/daemon/ipc-contract/browser.ts +74 -0
  237. package/src/daemon/ipc-contract/computer-use.ts +151 -0
  238. package/src/daemon/ipc-contract/diagnostics.ts +56 -0
  239. package/src/daemon/ipc-contract/documents.ts +74 -0
  240. package/src/daemon/ipc-contract/inbox.ts +209 -0
  241. package/src/daemon/ipc-contract/integrations.ts +284 -0
  242. package/src/daemon/ipc-contract/memory.ts +48 -0
  243. package/src/daemon/ipc-contract/messages.ts +211 -0
  244. package/src/daemon/ipc-contract/pairing.ts +45 -0
  245. package/src/daemon/ipc-contract/parental-control.ts +95 -0
  246. package/src/daemon/ipc-contract/schedules.ts +97 -0
  247. package/src/daemon/ipc-contract/sessions.ts +315 -0
  248. package/src/daemon/ipc-contract/shared.ts +42 -0
  249. package/src/daemon/ipc-contract/skills.ts +120 -0
  250. package/src/daemon/ipc-contract/subagents.ts +58 -0
  251. package/src/daemon/ipc-contract/surfaces.ts +250 -0
  252. package/src/daemon/ipc-contract/trust.ts +60 -0
  253. package/src/daemon/ipc-contract/work-items.ts +225 -0
  254. package/src/daemon/ipc-contract/workspace.ts +113 -0
  255. package/src/daemon/ipc-contract-inventory.json +70 -0
  256. package/src/daemon/ipc-contract-inventory.ts +55 -29
  257. package/src/daemon/ipc-contract.ts +229 -2426
  258. package/src/daemon/ipc-protocol.ts +1 -1
  259. package/src/daemon/ipc-validate.ts +7 -0
  260. package/src/daemon/lifecycle.ts +97 -377
  261. package/src/daemon/pairing-store.ts +177 -0
  262. package/src/daemon/providers-setup.ts +43 -0
  263. package/src/daemon/ride-shotgun-handler.ts +68 -3
  264. package/src/daemon/server.ts +66 -46
  265. package/src/daemon/session-agent-loop-handlers.ts +421 -0
  266. package/src/daemon/session-agent-loop.ts +117 -275
  267. package/src/daemon/session-dynamic-profile.ts +1 -1
  268. package/src/daemon/session-history.ts +1 -1
  269. package/src/daemon/session-media-retry.ts +1 -1
  270. package/src/daemon/session-messaging.ts +37 -2
  271. package/src/daemon/session-notifiers.ts +5 -25
  272. package/src/daemon/session-process.ts +99 -59
  273. package/src/daemon/session-queue-manager.ts +96 -4
  274. package/src/daemon/session-runtime-assembly.ts +199 -10
  275. package/src/daemon/session-surfaces.ts +19 -4
  276. package/src/daemon/session-tool-setup.ts +30 -30
  277. package/src/daemon/session-workspace.ts +1 -1
  278. package/src/daemon/session.ts +35 -2
  279. package/src/daemon/shutdown-handlers.ts +122 -0
  280. package/src/daemon/trace-emitter.ts +1 -1
  281. package/src/daemon/watch-handler.ts +36 -33
  282. package/src/doordash/cart-queries.ts +787 -0
  283. package/src/doordash/client.ts +144 -127
  284. package/src/doordash/order-queries.ts +85 -0
  285. package/src/doordash/queries.ts +10 -1308
  286. package/src/doordash/search-queries.ts +203 -0
  287. package/src/doordash/session.ts +3 -2
  288. package/src/doordash/store-queries.ts +246 -0
  289. package/src/doordash/types.ts +367 -0
  290. package/src/email/providers/agentmail.ts +2 -1
  291. package/src/email/providers/index.ts +3 -2
  292. package/src/email/service.ts +3 -2
  293. package/src/errors.ts +43 -0
  294. package/src/home-base/prebuilt/seed.ts +1 -1
  295. package/src/hooks/cli.ts +6 -5
  296. package/src/hooks/config.ts +6 -8
  297. package/src/hooks/discovery.ts +6 -5
  298. package/src/hooks/manager.ts +4 -3
  299. package/src/hooks/runner.ts +2 -2
  300. package/src/hooks/templates.ts +5 -5
  301. package/src/inbound/public-ingress-urls.ts +6 -4
  302. package/src/index.ts +4 -2
  303. package/src/influencer/client.ts +1104 -0
  304. package/src/instrument.ts +4 -3
  305. package/src/logfire.ts +4 -3
  306. package/src/memory/admin.ts +25 -35
  307. package/src/memory/attachments-store.ts +4 -7
  308. package/src/memory/channel-delivery-store.ts +30 -1
  309. package/src/memory/channel-guardian-store.ts +202 -2
  310. package/src/memory/clarification-resolver.ts +37 -33
  311. package/src/memory/conflict-store.ts +67 -61
  312. package/src/memory/contradiction-checker.ts +141 -117
  313. package/src/memory/conversation-store.ts +335 -51
  314. package/src/memory/db-connection.ts +27 -4
  315. package/src/memory/db-init.ts +265 -4
  316. package/src/memory/db.ts +14 -1
  317. package/src/memory/embedding-backend.ts +27 -5
  318. package/src/memory/embedding-ollama.ts +2 -1
  319. package/src/memory/entity-extractor.ts +38 -35
  320. package/src/memory/guardian-action-store.ts +430 -0
  321. package/src/memory/inbox-escalation-projection.ts +59 -0
  322. package/src/memory/inbox-thread-store.ts +218 -0
  323. package/src/memory/ingress-invite-store.ts +338 -0
  324. package/src/memory/ingress-member-store.ts +350 -0
  325. package/src/memory/items-extractor.ts +91 -97
  326. package/src/memory/job-handlers/index-maintenance.ts +3 -3
  327. package/src/memory/job-handlers/media-processing.ts +69 -0
  328. package/src/memory/job-handlers/summarization.ts +32 -26
  329. package/src/memory/job-utils.ts +3 -10
  330. package/src/memory/jobs-store.ts +8 -10
  331. package/src/memory/jobs-worker.ts +55 -36
  332. package/src/memory/media-store.ts +759 -0
  333. package/src/memory/migrations/001-job-deferrals.ts +45 -0
  334. package/src/memory/migrations/002-tool-invocations-fk.ts +43 -0
  335. package/src/memory/migrations/003-memory-fts-backfill.ts +24 -0
  336. package/src/memory/migrations/004-entity-relation-dedup.ts +87 -0
  337. package/src/memory/migrations/005-fingerprint-scope-unique.ts +80 -0
  338. package/src/memory/migrations/006-scope-salted-fingerprints.ts +62 -0
  339. package/src/memory/migrations/007-assistant-id-to-self.ts +254 -0
  340. package/src/memory/migrations/008-remove-assistant-id-columns.ts +208 -0
  341. package/src/memory/migrations/009-llm-usage-events-drop-assistant-id.ts +83 -0
  342. package/src/memory/migrations/010-ext-conv-bindings-channel-chat-unique.ts +56 -0
  343. package/src/memory/migrations/011-call-sessions-provider-sid-dedup.ts +63 -0
  344. package/src/memory/migrations/012-call-sessions-add-initiated-from.ts +19 -0
  345. package/src/memory/migrations/013-guardian-action-tables.ts +68 -0
  346. package/src/memory/migrations/014-backfill-inbox-thread-state.ts +76 -0
  347. package/src/memory/migrations/015-drop-active-search-index.ts +27 -0
  348. package/src/memory/migrations/016-memory-segments-indexes.ts +11 -0
  349. package/src/memory/migrations/017-memory-items-indexes.ts +10 -0
  350. package/src/memory/migrations/018-remaining-table-indexes.ts +13 -0
  351. package/src/memory/migrations/index.ts +24 -0
  352. package/src/memory/migrations/registry.ts +79 -0
  353. package/src/memory/migrations/validate-migration-state.ts +69 -0
  354. package/src/memory/qdrant-manager.ts +49 -8
  355. package/src/memory/query-builder.ts +1 -1
  356. package/src/memory/raw-query.ts +119 -0
  357. package/src/memory/recall-cache.ts +4 -1
  358. package/src/memory/retriever.ts +165 -47
  359. package/src/memory/schema-migration.ts +25 -984
  360. package/src/memory/schema.ts +228 -7
  361. package/src/memory/search/entity.ts +205 -31
  362. package/src/memory/search/lexical.ts +81 -52
  363. package/src/memory/search/ranking.ts +27 -23
  364. package/src/memory/search/semantic.ts +157 -19
  365. package/src/memory/search/types.ts +24 -0
  366. package/src/memory/shared-app-links-store.ts +4 -5
  367. package/src/memory/validation.ts +19 -0
  368. package/src/messaging/draft-store.ts +5 -6
  369. package/src/messaging/provider-types.ts +2 -0
  370. package/src/messaging/providers/sms/adapter.ts +201 -0
  371. package/src/messaging/providers/sms/client.ts +93 -0
  372. package/src/messaging/providers/sms/types.ts +7 -0
  373. package/src/messaging/providers/telegram-bot/adapter.ts +2 -5
  374. package/src/messaging/providers/whatsapp/adapter.ts +136 -0
  375. package/src/messaging/providers/whatsapp/client.ts +67 -0
  376. package/src/messaging/style-analyzer.ts +5 -4
  377. package/src/messaging/thread-summarizer.ts +61 -69
  378. package/src/messaging/triage-engine.ts +62 -71
  379. package/src/migrations/config-merge.ts +53 -0
  380. package/src/migrations/data-layout.ts +68 -0
  381. package/src/migrations/data-merge.ts +33 -0
  382. package/src/migrations/hooks-merge.ts +90 -0
  383. package/src/migrations/index.ts +6 -0
  384. package/src/migrations/log.ts +23 -0
  385. package/src/migrations/skills-merge.ts +33 -0
  386. package/src/migrations/workspace-layout.ts +79 -0
  387. package/src/permissions/checker.ts +133 -11
  388. package/src/permissions/prompter.ts +14 -0
  389. package/src/permissions/shell-identity.ts +31 -1
  390. package/src/permissions/trust-store.ts +21 -1
  391. package/src/providers/anthropic/client.ts +4 -4
  392. package/src/providers/failover.ts +2 -2
  393. package/src/providers/model-intents.ts +70 -0
  394. package/src/providers/ollama/client.ts +2 -1
  395. package/src/providers/provider-send-message.ts +176 -0
  396. package/src/providers/registry.ts +71 -30
  397. package/src/providers/retry.ts +35 -1
  398. package/src/providers/types.ts +12 -1
  399. package/src/runtime/approval-conversation-turn.ts +97 -0
  400. package/src/runtime/approval-message-composer.ts +253 -0
  401. package/src/runtime/channel-approval-parser.ts +36 -2
  402. package/src/runtime/channel-approvals.ts +11 -24
  403. package/src/runtime/channel-guardian-service.ts +88 -21
  404. package/src/runtime/channel-readiness-service.ts +418 -0
  405. package/src/runtime/channel-readiness-types.ts +35 -0
  406. package/src/runtime/channel-retry-sweep.ts +184 -0
  407. package/src/runtime/guardian-context-resolver.ts +108 -0
  408. package/src/runtime/http-server.ts +275 -717
  409. package/src/runtime/http-types.ts +59 -3
  410. package/src/runtime/middleware/auth.ts +116 -0
  411. package/src/runtime/middleware/error-handler.ts +33 -0
  412. package/src/runtime/middleware/twilio-validation.ts +127 -0
  413. package/src/runtime/routes/app-routes.ts +1 -1
  414. package/src/runtime/routes/call-routes.ts +51 -7
  415. package/src/runtime/routes/channel-delivery-routes.ts +170 -0
  416. package/src/runtime/routes/channel-guardian-routes.ts +1191 -0
  417. package/src/runtime/routes/channel-inbound-routes.ts +1152 -0
  418. package/src/runtime/routes/channel-route-shared.ts +144 -0
  419. package/src/runtime/routes/channel-routes.ts +32 -1588
  420. package/src/runtime/routes/conversation-routes.ts +50 -7
  421. package/src/runtime/routes/events-routes.ts +2 -2
  422. package/src/runtime/routes/identity-routes.ts +126 -0
  423. package/src/runtime/routes/pairing-routes.ts +143 -0
  424. package/src/runtime/routes/run-routes.ts +15 -1
  425. package/src/runtime/run-orchestrator.ts +86 -35
  426. package/src/schedule/schedule-store.ts +36 -32
  427. package/src/schedule/scheduler.ts +3 -3
  428. package/src/security/encrypted-store.ts +5 -7
  429. package/src/security/oauth2.ts +45 -15
  430. package/src/security/parental-control-store.ts +183 -0
  431. package/src/security/secret-allowlist.ts +4 -3
  432. package/src/security/secret-scanner.ts +5 -5
  433. package/src/security/secure-keys.ts +1 -1
  434. package/src/security/token-manager.ts +3 -2
  435. package/src/services/vercel-deploy.ts +6 -2
  436. package/src/skills/tool-manifest.ts +3 -3
  437. package/src/skills/vellum-catalog-remote.ts +75 -16
  438. package/src/slack/slack-webhook.ts +2 -1
  439. package/src/swarm/orchestrator.ts +92 -1
  440. package/src/swarm/router-planner.ts +6 -9
  441. package/src/swarm/worker-prompts.ts +9 -12
  442. package/src/tasks/task-compiler.ts +19 -28
  443. package/src/tasks/task-runner.ts +1 -1
  444. package/src/tools/assets/materialize.ts +2 -2
  445. package/src/tools/assets/search.ts +15 -14
  446. package/src/tools/browser/__tests__/auth-detector.test.ts +1 -0
  447. package/src/tools/browser/auto-navigate.ts +1 -0
  448. package/src/tools/browser/browser-execution.ts +10 -1
  449. package/src/tools/browser/browser-manager.ts +119 -4
  450. package/src/tools/browser/network-recorder.ts +5 -0
  451. package/src/tools/calls/call-start.ts +1 -0
  452. package/src/tools/credentials/broker.ts +11 -2
  453. package/src/tools/credentials/metadata-store.ts +18 -14
  454. package/src/tools/credentials/post-connect-hooks.ts +61 -0
  455. package/src/tools/credentials/vault.ts +49 -23
  456. package/src/tools/execution-target.ts +11 -1
  457. package/src/tools/executor.ts +68 -9
  458. package/src/tools/host-terminal/cli-discover.ts +1 -1
  459. package/src/tools/network/script-proxy/http-forwarder.ts +1 -1
  460. package/src/tools/network/script-proxy/mitm-handler.ts +1 -1
  461. package/src/tools/network/script-proxy/server.ts +1 -1
  462. package/src/tools/network/script-proxy/session-manager.ts +6 -5
  463. package/src/tools/network/web-fetch.ts +18 -2
  464. package/src/tools/network/web-search.ts +8 -4
  465. package/src/tools/reminder/reminder-store.ts +14 -15
  466. package/src/tools/schedule/create.ts +1 -0
  467. package/src/tools/schedule/list.ts +2 -1
  468. package/src/tools/shared/filesystem/file-ops-service.ts +5 -7
  469. package/src/tools/skills/skill-script-runner.ts +24 -9
  470. package/src/tools/skills/skill-tool-factory.ts +1 -0
  471. package/src/tools/tasks/work-item-enqueue.ts +2 -2
  472. package/src/tools/terminal/evaluate-typescript.ts +21 -12
  473. package/src/tools/terminal/parser.ts +50 -0
  474. package/src/tools/types.ts +2 -0
  475. package/src/tools/watcher/delete.ts +6 -0
  476. package/src/tools/weather/service.ts +1 -1
  477. package/src/twitter/client.ts +190 -24
  478. package/src/twitter/router.ts +1 -1
  479. package/src/twitter/session.ts +4 -3
  480. package/src/util/clipboard.ts +1 -1
  481. package/src/util/errors.ts +65 -8
  482. package/src/util/fs.ts +40 -0
  483. package/src/util/json.ts +10 -0
  484. package/src/util/log-redact.ts +189 -0
  485. package/src/util/logger.ts +19 -17
  486. package/src/util/object.ts +3 -0
  487. package/src/util/platform.ts +105 -363
  488. package/src/util/pricing.ts +1 -1
  489. package/src/util/promise-guard.ts +1 -1
  490. package/src/util/retry.ts +19 -0
  491. package/src/util/row-mapper.ts +79 -0
  492. package/src/util/silently.ts +21 -0
  493. package/src/watcher/engine.ts +5 -1
  494. package/src/watcher/provider-types.ts +20 -0
  495. package/src/watcher/providers/github.ts +156 -0
  496. package/src/watcher/providers/gmail.ts +1 -0
  497. package/src/watcher/providers/google-calendar.ts +1 -0
  498. package/src/watcher/providers/linear.ts +460 -0
  499. package/src/watcher/providers/slack.ts +1 -0
  500. package/src/work-items/work-item-runner.ts +1 -1
  501. package/src/workspace/git-service.ts +1 -1
  502. package/src/workspace/provider-commit-message-generator.ts +51 -22
  503. package/src/__tests__/call-bridge.test.ts +0 -517
  504. package/src/__tests__/session-process-bridge.test.ts +0 -244
  505. package/src/calls/call-bridge.ts +0 -168
  506. package/src/config/vellum-skills/google-oauth-setup/SKILL.md +0 -199
@@ -0,0 +1,65 @@
1
+ import { beforeEach, describe, expect, mock, test } from 'bun:test';
2
+ import type { MessagingProvider } from '../messaging/provider.js';
3
+ import type { SendOptions } from '../messaging/provider-types.js';
4
+
5
+ const sendMessageMock = mock(async (..._args: unknown[]) => ({
6
+ id: 'msg-1',
7
+ timestamp: 123,
8
+ conversationId: 'conv-1',
9
+ }));
10
+
11
+ const provider: MessagingProvider = {
12
+ id: 'sms',
13
+ displayName: 'SMS',
14
+ credentialService: 'twilio',
15
+ capabilities: new Set(['send']),
16
+ testConnection: async () => ({ connected: true, user: 'x', platform: 'sms' }),
17
+ listConversations: async () => [],
18
+ getHistory: async () => [],
19
+ search: async () => ({ total: 0, messages: [], hasMore: false }),
20
+ sendMessage: (token: string, conversationId: string, text: string, options?: SendOptions) =>
21
+ sendMessageMock(token, conversationId, text, options),
22
+ };
23
+
24
+ mock.module('../config/bundled-skills/messaging/tools/shared.js', () => ({
25
+ resolveProvider: () => provider,
26
+ withProviderToken: async (_provider: MessagingProvider, fn: (token: string) => Promise<unknown>) => fn('provider-token'),
27
+ ok: (content: string) => ({ content, isError: false }),
28
+ err: (content: string) => ({ content, isError: true }),
29
+ }));
30
+
31
+ import { run } from '../config/bundled-skills/messaging/tools/messaging-send.js';
32
+
33
+ describe('messaging-send tool', () => {
34
+ beforeEach(() => {
35
+ sendMessageMock.mockClear();
36
+ });
37
+
38
+ test('passes assistantId from tool context to provider send options', async () => {
39
+ const result = await run(
40
+ {
41
+ platform: 'sms',
42
+ conversation_id: '+15550004444',
43
+ text: 'test message',
44
+ },
45
+ {
46
+ workingDir: '/tmp',
47
+ sessionId: 'sess-1',
48
+ conversationId: 'conv-1',
49
+ assistantId: 'ast-alpha',
50
+ },
51
+ );
52
+
53
+ expect(result.isError).toBe(false);
54
+ expect(sendMessageMock).toHaveBeenCalledWith(
55
+ 'provider-token',
56
+ '+15550004444',
57
+ 'test message',
58
+ {
59
+ subject: undefined,
60
+ inReplyTo: undefined,
61
+ assistantId: 'ast-alpha',
62
+ },
63
+ );
64
+ });
65
+ });
@@ -0,0 +1,96 @@
1
+ import { describe, expect, test } from 'bun:test';
2
+ import type { Message, Provider, ProviderResponse, SendMessageOptions } from '../providers/types.js';
3
+ import { RetryProvider } from '../providers/retry.js';
4
+ import { getProviderDefaultModel, isModelIntent, resolveModelIntent } from '../providers/model-intents.js';
5
+
6
+ const DUMMY_MESSAGES: Message[] = [
7
+ { role: 'user', content: [{ type: 'text', text: 'hello' }] },
8
+ ];
9
+
10
+ function makeResponse(model: string): ProviderResponse {
11
+ return {
12
+ content: [{ type: 'text', text: 'ok' }],
13
+ model,
14
+ usage: {
15
+ inputTokens: 1,
16
+ outputTokens: 1,
17
+ },
18
+ stopReason: 'end_turn',
19
+ };
20
+ }
21
+
22
+ function makeProvider(
23
+ name: string,
24
+ onCall: (options: SendMessageOptions | undefined) => void,
25
+ ): Provider {
26
+ return {
27
+ name,
28
+ async sendMessage(_messages, _tools, _systemPrompt, options) {
29
+ onCall(options);
30
+ const config = options?.config as Record<string, unknown> | undefined;
31
+ return makeResponse((config?.model as string | undefined) ?? 'default-model');
32
+ },
33
+ };
34
+ }
35
+
36
+ describe('model intents', () => {
37
+ test('validates model intent strings', () => {
38
+ expect(isModelIntent('latency-optimized')).toBe(true);
39
+ expect(isModelIntent('quality-optimized')).toBe(true);
40
+ expect(isModelIntent('vision-optimized')).toBe(true);
41
+ expect(isModelIntent('fastest-model')).toBe(false);
42
+ expect(isModelIntent(undefined)).toBe(false);
43
+ });
44
+
45
+ test('resolves intent to provider-specific model', () => {
46
+ expect(resolveModelIntent('anthropic', 'latency-optimized')).toBe('claude-haiku-4-5-20251001');
47
+ expect(resolveModelIntent('anthropic', 'quality-optimized')).toBe('claude-opus-4-6');
48
+ expect(resolveModelIntent('anthropic', 'vision-optimized')).toBe('claude-sonnet-4-6');
49
+ expect(resolveModelIntent('openai', 'latency-optimized')).toBe('gpt-4o-mini');
50
+ });
51
+
52
+ test('falls back to provider default for unknown providers', () => {
53
+ expect(getProviderDefaultModel('unknown-provider')).toBe('claude-opus-4-6');
54
+ expect(resolveModelIntent('unknown-provider', 'quality-optimized')).toBe('claude-opus-4-6');
55
+ });
56
+ });
57
+
58
+ describe('RetryProvider model intent normalization', () => {
59
+ test('translates modelIntent into concrete model and strips modelIntent key', async () => {
60
+ let seen: SendMessageOptions | undefined;
61
+ const wrapped = new RetryProvider(makeProvider('anthropic', (options) => {
62
+ seen = options;
63
+ }));
64
+
65
+ await wrapped.sendMessage(DUMMY_MESSAGES, undefined, undefined, {
66
+ config: {
67
+ modelIntent: 'quality-optimized',
68
+ max_tokens: 123,
69
+ },
70
+ });
71
+
72
+ const config = seen?.config as Record<string, unknown>;
73
+ expect(config.model).toBe('claude-opus-4-6');
74
+ expect(config.modelIntent).toBeUndefined();
75
+ expect(config.max_tokens).toBe(123);
76
+ });
77
+
78
+ test('explicit model override wins over modelIntent', async () => {
79
+ let seen: SendMessageOptions | undefined;
80
+ const wrapped = new RetryProvider(makeProvider('openai', (options) => {
81
+ seen = options;
82
+ }));
83
+
84
+ await wrapped.sendMessage(DUMMY_MESSAGES, undefined, undefined, {
85
+ config: {
86
+ model: 'custom-model-v1',
87
+ modelIntent: 'latency-optimized',
88
+ },
89
+ });
90
+
91
+ const config = seen?.config as Record<string, unknown>;
92
+ expect(config.model).toBe('custom-model-v1');
93
+ expect(config.modelIntent).toBeUndefined();
94
+ });
95
+ });
96
+
@@ -0,0 +1,42 @@
1
+ import { execSync } from 'node:child_process';
2
+ import { describe, expect, test } from 'bun:test';
3
+
4
+ /**
5
+ * Guard test: production files under assistant/src must not import
6
+ * `@anthropic-ai/sdk` directly. Only the canonical provider adapter is
7
+ * allowed to use the SDK.
8
+ *
9
+ * Allowlist entries should be kept minimal — add a path here only if it
10
+ * genuinely needs to talk to the Anthropic SDK without going through the
11
+ * provider abstraction.
12
+ */
13
+ const ALLOWED_FILES = new Set([
14
+ 'assistant/src/providers/anthropic/client.ts',
15
+ ]);
16
+
17
+ describe('no direct @anthropic-ai/sdk imports', () => {
18
+ test('production files do not import @anthropic-ai/sdk outside allowlist', () => {
19
+ let grepOutput = '';
20
+ try {
21
+ grepOutput = execSync(
22
+ `git grep -l "@anthropic-ai/sdk" -- 'assistant/src/**/*.ts'`,
23
+ { encoding: 'utf-8', cwd: process.cwd() + '/..' },
24
+ ).trim();
25
+ } catch (err) {
26
+ // Exit code 1 means no matches — that's the happy path
27
+ if ((err as { status?: number }).status === 1) {
28
+ return;
29
+ }
30
+ throw err;
31
+ }
32
+
33
+ const files = grepOutput
34
+ .split('\n')
35
+ .filter((f) => f.length > 0)
36
+ // Exclude test files — they legitimately mock the SDK
37
+ .filter((f) => !f.includes('/__tests__/'));
38
+ const violations = files.filter((f) => !ALLOWED_FILES.has(f));
39
+
40
+ expect(violations).toEqual([]);
41
+ });
42
+ });
@@ -60,6 +60,10 @@ mock.module('../inbound/public-ingress-urls.js', () => ({
60
60
  },
61
61
  }));
62
62
 
63
+ // Track token exchange request
64
+ let lastTokenRequestBody: URLSearchParams | null = null;
65
+ let lastTokenRequestHeaders: Record<string, string> = {};
66
+
63
67
  // Mock fetch for token exchange
64
68
  let mockTokenResponse: { ok: boolean; status: number; body: Record<string, unknown> } = {
65
69
  ok: true,
@@ -77,6 +81,13 @@ const originalFetch = globalThis.fetch;
77
81
  globalThis.fetch = (async (input: RequestInfo | URL, init?: RequestInit) => {
78
82
  const url = typeof input === 'string' ? input : input instanceof URL ? input.toString() : input.url;
79
83
  if (url.includes('token')) {
84
+ // Capture request body and headers for assertions
85
+ if (init?.body) {
86
+ lastTokenRequestBody = new URLSearchParams(init.body as string);
87
+ }
88
+ if (init?.headers) {
89
+ lastTokenRequestHeaders = init.headers as Record<string, string>;
90
+ }
80
91
  if (!mockTokenResponse.ok) {
81
92
  return new Response(JSON.stringify({ error: 'invalid_grant' }), {
82
93
  status: mockTokenResponse.status,
@@ -108,6 +119,8 @@ beforeEach(() => {
108
119
  mockPublicBaseUrl = '';
109
120
  mockOAuthCallbackUrl = 'https://gw.example.com/webhooks/oauth/callback';
110
121
  pendingCallbacks.clear();
122
+ lastTokenRequestBody = null;
123
+ lastTokenRequestHeaders = {};
111
124
  mockTokenResponse = {
112
125
  ok: true,
113
126
  status: 200,
@@ -282,4 +295,121 @@ describe('OAuth2 gateway transport', () => {
282
295
  await expect(flowPromise).rejects.toThrow('OAuth2 token exchange failed');
283
296
  });
284
297
  });
298
+
299
+ describe('PKCE with client secret', () => {
300
+ test('includes PKCE params in auth URL even when clientSecret is provided', async () => {
301
+ mockPublicBaseUrl = 'https://gw.example.com';
302
+
303
+ const configWithSecret: OAuth2Config = {
304
+ ...BASE_OAUTH_CONFIG,
305
+ clientSecret: 'test-client-secret',
306
+ };
307
+
308
+ let capturedAuthUrl = '';
309
+ const flowPromise = startOAuth2Flow(configWithSecret, {
310
+ openUrl: (url) => { capturedAuthUrl = url; },
311
+ });
312
+
313
+ await new Promise((r) => setTimeout(r, 10));
314
+
315
+ // Auth URL must include PKCE challenge params despite having a client secret
316
+ expect(capturedAuthUrl).toContain('code_challenge=');
317
+ expect(capturedAuthUrl).toContain('code_challenge_method=S256');
318
+
319
+ const entries = Array.from(pendingCallbacks.entries());
320
+ expect(entries.length).toBe(1);
321
+ entries[0][1].resolve('pkce-with-secret-code');
322
+
323
+ const result = await flowPromise;
324
+ expect(result.tokens.accessToken).toBe('test-access-token');
325
+
326
+ // Token exchange must include code_verifier
327
+ expect(lastTokenRequestBody).not.toBeNull();
328
+ expect(lastTokenRequestBody!.get('code_verifier')).toBeTruthy();
329
+ });
330
+
331
+ test('sends Basic Auth header and omits client_id/client_secret from body when tokenEndpointAuthMethod is client_secret_basic', async () => {
332
+ mockPublicBaseUrl = 'https://gw.example.com';
333
+
334
+ const configWithSecret: OAuth2Config = {
335
+ ...BASE_OAUTH_CONFIG,
336
+ clientSecret: 'test-client-secret',
337
+ tokenEndpointAuthMethod: 'client_secret_basic',
338
+ };
339
+
340
+ const flowPromise = startOAuth2Flow(configWithSecret, {
341
+ openUrl: () => {},
342
+ });
343
+
344
+ await new Promise((r) => setTimeout(r, 10));
345
+
346
+ const entries = Array.from(pendingCallbacks.entries());
347
+ entries[0][1].resolve('basic-auth-code');
348
+
349
+ await flowPromise;
350
+
351
+ // Should send Basic Auth header with base64(client_id:client_secret)
352
+ const expectedCredentials = Buffer.from('test-client-id:test-client-secret').toString('base64');
353
+ expect(lastTokenRequestHeaders['Authorization']).toBe(`Basic ${expectedCredentials}`);
354
+
355
+ // Body should NOT contain client_id or client_secret
356
+ expect(lastTokenRequestBody).not.toBeNull();
357
+ expect(lastTokenRequestBody!.has('client_id')).toBe(false);
358
+ expect(lastTokenRequestBody!.has('client_secret')).toBe(false);
359
+
360
+ // Body should still contain code_verifier
361
+ expect(lastTokenRequestBody!.get('code_verifier')).toBeTruthy();
362
+ });
363
+
364
+ test('sends client_id and client_secret in body when tokenEndpointAuthMethod is client_secret_post (default)', async () => {
365
+ mockPublicBaseUrl = 'https://gw.example.com';
366
+
367
+ const configWithSecret: OAuth2Config = {
368
+ ...BASE_OAUTH_CONFIG,
369
+ clientSecret: 'test-client-secret',
370
+ };
371
+
372
+ const flowPromise = startOAuth2Flow(configWithSecret, {
373
+ openUrl: () => {},
374
+ });
375
+
376
+ await new Promise((r) => setTimeout(r, 10));
377
+
378
+ const entries = Array.from(pendingCallbacks.entries());
379
+ entries[0][1].resolve('post-auth-code');
380
+
381
+ await flowPromise;
382
+
383
+ // No Authorization header for client_secret_post
384
+ expect(lastTokenRequestHeaders['Authorization']).toBeUndefined();
385
+
386
+ // Body should contain client_id and client_secret
387
+ expect(lastTokenRequestBody).not.toBeNull();
388
+ expect(lastTokenRequestBody!.get('client_id')).toBe('test-client-id');
389
+ expect(lastTokenRequestBody!.get('client_secret')).toBe('test-client-secret');
390
+ });
391
+
392
+ test('sends client_id in body without Basic Auth when no clientSecret', async () => {
393
+ mockPublicBaseUrl = 'https://gw.example.com';
394
+
395
+ const flowPromise = startOAuth2Flow(BASE_OAUTH_CONFIG, {
396
+ openUrl: () => {},
397
+ });
398
+
399
+ await new Promise((r) => setTimeout(r, 10));
400
+
401
+ const entries = Array.from(pendingCallbacks.entries());
402
+ entries[0][1].resolve('public-client-code');
403
+
404
+ await flowPromise;
405
+
406
+ // No Authorization header for public clients
407
+ expect(lastTokenRequestHeaders['Authorization']).toBeUndefined();
408
+
409
+ // Body should contain client_id
410
+ expect(lastTokenRequestBody).not.toBeNull();
411
+ expect(lastTokenRequestBody!.get('client_id')).toBe('test-client-id');
412
+ expect(lastTokenRequestBody!.has('client_secret')).toBe(false);
413
+ });
414
+ });
285
415
  });
@@ -41,6 +41,8 @@ mock.module('../util/logger.js', () => ({
41
41
  getLogger: () => new Proxy({} as Record<string, unknown>, {
42
42
  get: () => () => {},
43
43
  }),
44
+ isDebug: () => false,
45
+ truncateForLog: (v: string) => v,
44
46
  }));
45
47
 
46
48
  const { buildStarterTaskPlaybookSection, buildSystemPrompt } = await import('../config/system-prompt.js');
@@ -55,18 +55,21 @@ const mockProvider: Provider = {
55
55
  name: 'mock-provider',
56
56
  sendMessage: mockSendMessage,
57
57
  };
58
- let getProviderShouldThrow = false;
59
-
60
- mock.module('../providers/registry.js', () => ({
61
- getProvider: (_name: string) => {
62
- if (getProviderShouldThrow) {
63
- throw new Error('Provider not initialized');
64
- }
65
- return mockProvider;
66
- },
67
- registerProvider: () => {},
68
- listProviders: () => ['mock-provider'],
69
- initializeProviders: () => {},
58
+
59
+ let resolvedProvider: {
60
+ provider: Provider;
61
+ configuredProviderName: string;
62
+ selectedProviderName: string;
63
+ usedFallbackPrimary: boolean;
64
+ } | null = {
65
+ provider: mockProvider,
66
+ configuredProviderName: 'anthropic',
67
+ selectedProviderName: 'anthropic',
68
+ usedFallbackPrimary: false,
69
+ };
70
+
71
+ mock.module('../providers/provider-send-message.js', () => ({
72
+ resolveConfiguredProvider: () => resolvedProvider,
70
73
  }));
71
74
 
72
75
  // ---------------------------------------------------------------------------
@@ -113,7 +116,12 @@ describe('ProviderCommitMessageGenerator', () => {
113
116
  _resetCommitMessageGenerator();
114
117
  currentConfig = cloneConfig();
115
118
  mockSendMessage.mockReset();
116
- getProviderShouldThrow = false;
119
+ resolvedProvider = {
120
+ provider: mockProvider,
121
+ configuredProviderName: 'anthropic',
122
+ selectedProviderName: 'anthropic',
123
+ usedFallbackPrimary: false,
124
+ };
117
125
  });
118
126
 
119
127
  // 1. disabled
@@ -150,6 +158,32 @@ describe('ProviderCommitMessageGenerator', () => {
150
158
  expect(mockSendMessage).not.toHaveBeenCalled();
151
159
  });
152
160
 
161
+ // 3b. No resolvable provider and no keys
162
+ test('no resolvable provider + no keys → returns deterministic, reason "missing_provider_api_key"', async () => {
163
+ currentConfig.apiKeys = {} as Record<string, string>;
164
+ resolvedProvider = null;
165
+ const gen = getCommitMessageGenerator();
166
+ const result = await gen.generateCommitMessage(baseContext, {
167
+ changedFiles: baseContext.changedFiles,
168
+ });
169
+ expect(result.source).toBe('deterministic');
170
+ expect(result.reason).toBe('missing_provider_api_key');
171
+ expect(mockSendMessage).not.toHaveBeenCalled();
172
+ });
173
+
174
+ // 3c. No resolvable provider despite keys
175
+ test('no resolvable provider with keys present → returns deterministic, reason "provider_not_initialized"', async () => {
176
+ currentConfig.apiKeys = { anthropic: 'sk-test-key' } as Record<string, string>;
177
+ resolvedProvider = null;
178
+ const gen = getCommitMessageGenerator();
179
+ const result = await gen.generateCommitMessage(baseContext, {
180
+ changedFiles: baseContext.changedFiles,
181
+ });
182
+ expect(result.source).toBe('deterministic');
183
+ expect(result.reason).toBe('provider_not_initialized');
184
+ expect(mockSendMessage).not.toHaveBeenCalled();
185
+ });
186
+
153
187
  // 4. breaker open
154
188
  test('breaker open → returns deterministic, reason "breaker_open"', async () => {
155
189
  // Force the breaker open by simulating enough failures
@@ -295,6 +329,12 @@ describe('ProviderCommitMessageGenerator', () => {
295
329
  test('Ollama without API key or fast model → returns deterministic, reason "missing_fast_model"', async () => {
296
330
  (currentConfig as Record<string, unknown>).provider = 'ollama';
297
331
  currentConfig.apiKeys = {} as Record<string, string>;
332
+ resolvedProvider = {
333
+ provider: mockProvider,
334
+ configuredProviderName: 'ollama',
335
+ selectedProviderName: 'ollama',
336
+ usedFallbackPrimary: false,
337
+ };
298
338
  const gen = getCommitMessageGenerator();
299
339
  const result = await gen.generateCommitMessage(baseContext, {
300
340
  changedFiles: baseContext.changedFiles,
@@ -309,6 +349,12 @@ describe('ProviderCommitMessageGenerator', () => {
309
349
  test('Unknown provider without fast model default → returns deterministic, reason "missing_fast_model"', async () => {
310
350
  (currentConfig as Record<string, unknown>).provider = 'exotic-provider';
311
351
  currentConfig.apiKeys = { 'exotic-provider': 'sk-exotic' } as Record<string, string>;
352
+ resolvedProvider = {
353
+ provider: mockProvider,
354
+ configuredProviderName: 'exotic-provider',
355
+ selectedProviderName: 'exotic-provider',
356
+ usedFallbackPrimary: false,
357
+ };
312
358
  const gen = getCommitMessageGenerator();
313
359
  const result = await gen.generateCommitMessage(baseContext, {
314
360
  changedFiles: baseContext.changedFiles,
@@ -322,6 +368,12 @@ describe('ProviderCommitMessageGenerator', () => {
322
368
  test('fast-model override enables LLM path for provider without built-in default', async () => {
323
369
  (currentConfig as Record<string, unknown>).provider = 'ollama';
324
370
  currentConfig.apiKeys = {} as Record<string, string>; // Ollama is keyless
371
+ resolvedProvider = {
372
+ provider: mockProvider,
373
+ configuredProviderName: 'ollama',
374
+ selectedProviderName: 'ollama',
375
+ usedFallbackPrimary: false,
376
+ };
325
377
  currentConfig.workspaceGit.commitMessageLLM.providerFastModelOverrides = {
326
378
  ollama: 'llama3.2:3b',
327
379
  };
@@ -339,4 +391,28 @@ describe('ProviderCommitMessageGenerator', () => {
339
391
  const options = callArgs[3] as { config: { model: string } };
340
392
  expect(options.config.model).toBe('llama3.2:3b');
341
393
  });
394
+
395
+ // 15. Fail-open fallback provider uses fallback provider's fast-model mapping
396
+ test('configured provider unavailable -> selected fallback provider model mapping is used', async () => {
397
+ currentConfig.provider = 'anthropic';
398
+ currentConfig.providerOrder = ['openai'];
399
+ currentConfig.apiKeys = { openai: 'sk-openai' } as Record<string, string>;
400
+ resolvedProvider = {
401
+ provider: mockProvider,
402
+ configuredProviderName: 'anthropic',
403
+ selectedProviderName: 'openai',
404
+ usedFallbackPrimary: true,
405
+ };
406
+ mockSendMessage.mockResolvedValueOnce(makeSuccessResponse('fix: fail-open commit'));
407
+
408
+ const gen = getCommitMessageGenerator();
409
+ const result = await gen.generateCommitMessage(baseContext, {
410
+ changedFiles: baseContext.changedFiles,
411
+ });
412
+
413
+ expect(result.source).toBe('llm');
414
+ const callArgs = mockSendMessage.mock.calls[0];
415
+ const options = callArgs[3] as { config: { model: string } };
416
+ expect(options.config.model).toBe('gpt-4o-mini');
417
+ });
342
418
  });