@vellumai/assistant 0.8.2 → 0.8.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 (503) hide show
  1. package/ARCHITECTURE.md +11 -12
  2. package/docker-entrypoint.sh +13 -2
  3. package/docker-init-apt-root.sh +79 -6
  4. package/node_modules/@vellumai/gateway-client/src/types.ts +2 -0
  5. package/openapi.yaml +945 -36
  6. package/package.json +1 -1
  7. package/src/__tests__/agent-loop-exit-reason.test.ts +271 -0
  8. package/src/__tests__/agent-loop-override-profile.test.ts +1 -1
  9. package/src/__tests__/agent-loop-provider-error-recording.test.ts +195 -0
  10. package/src/__tests__/agent-loop.test.ts +88 -3
  11. package/src/__tests__/anthropic-provider.test.ts +272 -0
  12. package/src/__tests__/approval-cascade.test.ts +1 -1
  13. package/src/__tests__/background-workers-disk-pressure.test.ts +2 -1
  14. package/src/__tests__/channel-delivery-store.test.ts +193 -0
  15. package/src/__tests__/channel-reply-delivery.test.ts +284 -5
  16. package/src/__tests__/channel-retry-sweep.test.ts +274 -1
  17. package/src/__tests__/compaction-events.test.ts +1 -1
  18. package/src/__tests__/compactor-preserved-tail-count.test.ts +110 -0
  19. package/src/__tests__/compactor-tail-resolution.test.ts +107 -1
  20. package/src/__tests__/config-get-vision-flag.test.ts +136 -0
  21. package/src/__tests__/config-loader-backfill.test.ts +115 -18
  22. package/src/__tests__/config-watcher.test.ts +1 -1
  23. package/src/__tests__/context-token-estimator.test.ts +112 -57
  24. package/src/__tests__/conversation-abort-tool-results.test.ts +1 -1
  25. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +54 -3
  26. package/src/__tests__/conversation-agent-loop-overflow.test.ts +31 -6
  27. package/src/__tests__/conversation-agent-loop.test.ts +77 -3
  28. package/src/__tests__/conversation-app-control-lifecycle.test.ts +1 -1
  29. package/src/__tests__/conversation-clean-command.test.ts +137 -0
  30. package/src/__tests__/conversation-confirmation-signals.test.ts +1 -1
  31. package/src/__tests__/conversation-fork-crud.test.ts +161 -0
  32. package/src/__tests__/conversation-lifecycle.test.ts +1 -1
  33. package/src/__tests__/conversation-load-cleaned-at.test.ts +279 -0
  34. package/src/__tests__/conversation-load-history-repair.test.ts +1 -1
  35. package/src/__tests__/conversation-media-retry.test.ts +19 -8
  36. package/src/__tests__/conversation-pairing.test.ts +2 -2
  37. package/src/__tests__/conversation-process-callsite.test.ts +1 -1
  38. package/src/__tests__/conversation-provider-retry-repair.test.ts +1 -1
  39. package/src/__tests__/conversation-queue.test.ts +1 -1
  40. package/src/__tests__/conversation-runtime-assembly.test.ts +290 -85
  41. package/src/__tests__/conversation-seed-composer.test.ts +66 -4
  42. package/src/__tests__/conversation-slash-commands.test.ts +36 -8
  43. package/src/__tests__/conversation-slash-queue.test.ts +1 -1
  44. package/src/__tests__/conversation-slash-unknown.test.ts +1 -1
  45. package/src/__tests__/conversation-speed-override.test.ts +1 -1
  46. package/src/__tests__/conversation-surfaces-task-progress.test.ts +220 -0
  47. package/src/__tests__/conversation-workspace-cache-state.test.ts +1 -1
  48. package/src/__tests__/conversation-workspace-injection.test.ts +5 -1
  49. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +5 -1
  50. package/src/__tests__/credential-security-invariants.test.ts +6 -0
  51. package/src/__tests__/cu-unified-flow.test.ts +10 -1
  52. package/src/__tests__/date-context.test.ts +45 -0
  53. package/src/__tests__/dm-backfill.test.ts +64 -0
  54. package/src/__tests__/dm-persistence.test.ts +33 -0
  55. package/src/__tests__/document-find-replace.test.ts +501 -0
  56. package/src/__tests__/external-plugin-loader.test.ts +91 -19
  57. package/src/__tests__/first-greeting.test.ts +23 -2
  58. package/src/__tests__/guardian-action-no-hardcoded-copy.test.ts +0 -1
  59. package/src/__tests__/guardian-dispatch.test.ts +1 -0
  60. package/src/__tests__/headless-browser-navigate.test.ts +172 -0
  61. package/src/__tests__/heartbeat-service.test.ts +24 -164
  62. package/src/__tests__/helpers/channel-test-adapter.ts +0 -2
  63. package/src/__tests__/host-app-control-proxy.test.ts +241 -0
  64. package/src/__tests__/host-bash-proxy.test.ts +6 -0
  65. package/src/__tests__/host-browser-proxy.test.ts +10 -0
  66. package/src/__tests__/host-cu-proxy.test.ts +8 -1
  67. package/src/__tests__/host-file-proxy.test.ts +8 -1
  68. package/src/__tests__/host-proxy-preactivation.test.ts +200 -13
  69. package/src/__tests__/host-transfer-proxy.test.ts +8 -1
  70. package/src/__tests__/identity-routes.test.ts +57 -0
  71. package/src/__tests__/inbound-slack-persistence.test.ts +3 -0
  72. package/src/__tests__/injector-background-turn.test.ts +153 -0
  73. package/src/__tests__/injector-chain.test.ts +7 -0
  74. package/src/__tests__/injector-document-comments.test.ts +378 -0
  75. package/src/__tests__/injector-pkb-v2-silenced.test.ts +4 -25
  76. package/src/__tests__/lifecycle-memory-v2-seed.test.ts +9 -2
  77. package/src/__tests__/list-messages-attachments.test.ts +21 -17
  78. package/src/__tests__/list-messages-hidden-metadata.test.ts +217 -0
  79. package/src/__tests__/list-messages-page-latest.test.ts +130 -14
  80. package/src/__tests__/list-messages-tool-merge.test.ts +17 -16
  81. package/src/__tests__/llm-callsite-catalog.test.ts +25 -0
  82. package/src/__tests__/llm-catalog-parity.test.ts +3 -0
  83. package/src/__tests__/llm-context-normalization.test.ts +0 -2
  84. package/src/__tests__/llm-request-log-agent-loop-exit-reason.test.ts +116 -0
  85. package/src/__tests__/llm-request-log-error-payload.test.ts +138 -0
  86. package/src/__tests__/llm-request-log-source-clickhouse.test.ts +2 -0
  87. package/src/__tests__/llm-resolver.test.ts +340 -3
  88. package/src/__tests__/log-export-routes.test.ts +99 -2
  89. package/src/__tests__/managed-profile-guard.test.ts +10 -0
  90. package/src/__tests__/message-queue-steer.test.ts +114 -0
  91. package/src/__tests__/notification-decision-fallback.test.ts +0 -91
  92. package/src/__tests__/notification-decision-strategy.test.ts +14 -31
  93. package/src/__tests__/notification-deep-link.test.ts +15 -0
  94. package/src/__tests__/notification-guardian-path.test.ts +1 -2
  95. package/src/__tests__/notification-platform-adapter.test.ts +5 -4
  96. package/src/__tests__/notification-telegram-adapter.test.ts +1 -0
  97. package/src/__tests__/notification-vellum-adapter.test.ts +113 -0
  98. package/src/__tests__/openai-provider.test.ts +323 -3
  99. package/src/__tests__/openai-responses-cutover-guard.test.ts +3 -3
  100. package/src/__tests__/openai-responses-provider.test.ts +4 -4
  101. package/src/__tests__/openrouter-provider-only.test.ts +51 -3
  102. package/src/__tests__/openrouter-token-estimation.test.ts +34 -25
  103. package/src/__tests__/outbound-slack-persistence.test.ts +187 -20
  104. package/src/__tests__/pending-interactions-resolved-event.test.ts +190 -0
  105. package/src/__tests__/platform-proxy-context.test.ts +6 -1
  106. package/src/__tests__/platform.test.ts +0 -3
  107. package/src/__tests__/plugin-source-watcher.test.ts +302 -0
  108. package/src/__tests__/plugin-tool-contribution.test.ts +3 -3
  109. package/src/__tests__/plugin-types.test.ts +2 -2
  110. package/src/__tests__/process-message-background-slack.test.ts +1 -51
  111. package/src/__tests__/process-message-display-content.test.ts +21 -16
  112. package/src/__tests__/provider-catalog-visibility.test.ts +16 -0
  113. package/src/__tests__/provider-platform-proxy-integration.test.ts +27 -25
  114. package/src/__tests__/secret-routes-platform-proxy.test.ts +1 -1
  115. package/src/__tests__/server-history-render.test.ts +83 -4
  116. package/src/__tests__/steer-tool-repair.test.ts +249 -0
  117. package/src/__tests__/system-prompt.test.ts +57 -101
  118. package/src/__tests__/terminal-tools.test.ts +11 -1
  119. package/src/__tests__/thinking-block-replay.test.ts +113 -0
  120. package/src/__tests__/thread-backfill.test.ts +370 -22
  121. package/src/__tests__/tool-executor.test.ts +90 -1
  122. package/src/__tests__/tool-result-metadata-plumbing.test.ts +167 -0
  123. package/src/__tests__/twilio-routes.test.ts +1 -1
  124. package/src/__tests__/web-fetch.test.ts +2 -2
  125. package/src/__tests__/workspace-git-service.test.ts +88 -5
  126. package/src/__tests__/workspace-migration-087-memory-router-balanced-profile.test.ts +228 -0
  127. package/src/__tests__/workspace-migration-088-deprecate-background-conversation-override.test.ts +158 -0
  128. package/src/a2a/__tests__/agent-card.test.ts +98 -0
  129. package/src/a2a/__tests__/e2e-a2a-channel.test.ts +597 -0
  130. package/src/a2a/__tests__/protocol-helpers.test.ts +113 -0
  131. package/src/a2a/__tests__/task-store.test.ts +246 -0
  132. package/src/a2a/agent-card.ts +58 -0
  133. package/src/a2a/feature-gate.ts +8 -0
  134. package/src/a2a/protocol-constants.ts +21 -0
  135. package/src/a2a/protocol-errors.ts +50 -0
  136. package/src/a2a/protocol-types.ts +162 -0
  137. package/src/a2a/task-store.ts +168 -0
  138. package/src/agent/attachments.ts +1 -0
  139. package/src/agent/loop.ts +208 -22
  140. package/src/background-wake/next-wake.test.ts +289 -0
  141. package/src/background-wake/next-wake.ts +172 -0
  142. package/src/browser/operations.ts +15 -0
  143. package/src/channels/config.ts +9 -0
  144. package/src/channels/types.ts +14 -0
  145. package/src/cli/commands/__tests__/conversations-slack.test.ts +572 -0
  146. package/src/cli/commands/__tests__/memory-v2.test.ts +9 -12
  147. package/src/cli/{__tests__ → commands/__tests__}/notifications.test.ts +201 -28
  148. package/src/cli/commands/__tests__/schedules.test.ts +469 -0
  149. package/src/cli/commands/conversations.ts +128 -1
  150. package/src/cli/commands/inference-providers.ts +147 -1
  151. package/src/cli/commands/memory-v2.ts +308 -0
  152. package/src/cli/commands/notifications.ts +89 -37
  153. package/src/cli/commands/plugins.ts +67 -0
  154. package/src/cli/commands/schedules.ts +297 -5
  155. package/src/cli/lib/__tests__/search-plugins.test.ts +261 -0
  156. package/src/cli/lib/install-from-github.ts +8 -9
  157. package/src/cli/lib/search-plugins.ts +163 -0
  158. package/src/cli/program.ts +14 -0
  159. package/src/cli/utils/conversation-id.ts +17 -5
  160. package/src/config/assistant-feature-flags.ts +24 -54
  161. package/src/config/bundled-skills/app-builder/SKILL.md +117 -1
  162. package/src/config/bundled-skills/document-editor/SKILL.md +115 -0
  163. package/src/config/bundled-skills/document-editor/TOOLS.json +240 -0
  164. package/src/config/bundled-skills/document-editor/tools/comment-list.ts +12 -0
  165. package/src/config/bundled-skills/document-editor/tools/comment-reply.ts +12 -0
  166. package/src/config/bundled-skills/document-editor/tools/comment-resolve.ts +12 -0
  167. package/src/config/bundled-skills/document-editor/tools/document-find.ts +12 -0
  168. package/src/config/bundled-skills/document-editor/tools/document-replace-text.ts +12 -0
  169. package/src/config/bundled-skills/media-processing/SKILL.md +8 -0
  170. package/src/config/bundled-skills/phone-calls/SKILL.md +1 -1
  171. package/src/config/bundled-skills/schedule/SKILL.md +8 -0
  172. package/src/config/bundled-tool-registry.ts +22 -12
  173. package/src/config/call-site-defaults.ts +124 -0
  174. package/src/config/feature-flag-registry.json +111 -23
  175. package/src/config/llm-resolver.ts +66 -1
  176. package/src/config/schema.ts +2 -0
  177. package/src/config/schemas/__tests__/memory-v2.test.ts +7 -3
  178. package/src/config/schemas/call-site-catalog.ts +21 -0
  179. package/src/config/schemas/channels.ts +9 -0
  180. package/src/config/schemas/conversations.ts +10 -0
  181. package/src/config/schemas/heartbeat.ts +14 -0
  182. package/src/config/schemas/llm.ts +4 -3
  183. package/src/config/schemas/memory-retrospective.ts +1 -1
  184. package/src/config/schemas/memory-v2.ts +51 -4
  185. package/src/config/schemas/memory.ts +3 -1
  186. package/src/config/seed-inference-profiles.ts +99 -29
  187. package/src/context/compactor.ts +80 -13
  188. package/src/context/token-estimator.ts +72 -31
  189. package/src/context/window-manager.ts +25 -0
  190. package/src/credential-health/credential-health-service.ts +34 -19
  191. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +3 -22
  192. package/src/daemon/__tests__/conversation-tool-setup.test.ts +66 -6
  193. package/src/daemon/__tests__/native-web-search-metadata.test.ts +357 -0
  194. package/src/daemon/__tests__/web-search-status-text.test.ts +287 -0
  195. package/src/daemon/conversation-agent-loop-handlers.ts +231 -23
  196. package/src/daemon/conversation-agent-loop.ts +252 -56
  197. package/src/daemon/conversation-lifecycle.ts +142 -116
  198. package/src/daemon/conversation-messaging.ts +3 -0
  199. package/src/daemon/conversation-process.ts +273 -0
  200. package/src/daemon/conversation-queue-manager.ts +14 -0
  201. package/src/daemon/conversation-runtime-assembly.ts +144 -75
  202. package/src/daemon/conversation-slash.ts +37 -5
  203. package/src/daemon/conversation-surfaces.ts +45 -2
  204. package/src/daemon/conversation-tool-setup.ts +7 -0
  205. package/src/daemon/conversation.ts +42 -12
  206. package/src/daemon/date-context.ts +40 -0
  207. package/src/daemon/first-greeting.ts +10 -0
  208. package/src/daemon/guardian-action-generators.ts +1 -125
  209. package/src/daemon/handlers/__tests__/config-a2a-accept.test.ts +498 -0
  210. package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +248 -0
  211. package/src/daemon/handlers/__tests__/config-a2a-invite.test.ts +154 -0
  212. package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +133 -0
  213. package/src/daemon/handlers/__tests__/config-a2a.test.ts +95 -0
  214. package/src/daemon/handlers/config-a2a.ts +449 -0
  215. package/src/daemon/handlers/config-model.test.ts +1 -0
  216. package/src/daemon/handlers/conversations.ts +80 -0
  217. package/src/daemon/handlers/shared.ts +92 -29
  218. package/src/daemon/host-app-control-proxy.ts +69 -18
  219. package/src/daemon/host-bash-proxy.ts +1 -1
  220. package/src/daemon/host-cu-proxy.ts +1 -1
  221. package/src/daemon/host-file-proxy.ts +1 -1
  222. package/src/daemon/host-proxy-preactivation.ts +85 -18
  223. package/src/daemon/host-transfer-proxy.ts +1 -1
  224. package/src/daemon/lifecycle.ts +67 -65
  225. package/src/daemon/memory-v2-startup.ts +49 -13
  226. package/src/daemon/message-protocol.ts +4 -0
  227. package/src/daemon/message-types/conversations.ts +8 -0
  228. package/src/daemon/message-types/document-comments.ts +50 -0
  229. package/src/daemon/message-types/messages.ts +68 -1
  230. package/src/daemon/message-types/notifications.ts +21 -0
  231. package/src/daemon/message-types/surfaces.ts +3 -1
  232. package/src/daemon/message-types/web-activity.ts +57 -0
  233. package/src/daemon/pkb-reminder-builder.test.ts +10 -53
  234. package/src/daemon/pkb-reminder-builder.ts +4 -19
  235. package/src/daemon/plugin-source-watcher.ts +135 -3
  236. package/src/daemon/process-message.ts +72 -12
  237. package/src/daemon/query-complexity-router.ts +75 -0
  238. package/src/daemon/skill-memory-refresh.ts +5 -1
  239. package/src/daemon/trust-context.ts +6 -0
  240. package/src/daemon/wake-target-adapter.ts +2 -0
  241. package/src/documents/document-comments-store.test.ts +338 -0
  242. package/src/documents/document-comments-store.ts +237 -0
  243. package/src/documents/document-store.ts +202 -0
  244. package/src/export/__tests__/transcript-formatter.test.ts +121 -0
  245. package/src/export/transcript-formatter.ts +54 -20
  246. package/src/heartbeat/__tests__/heartbeat-service.test.ts +44 -1
  247. package/src/heartbeat/heartbeat-service.ts +35 -191
  248. package/src/home/__tests__/feed-types.test.ts +40 -0
  249. package/src/home/__tests__/suggested-prompts.test.ts +33 -2
  250. package/src/home/feed-types.ts +20 -3
  251. package/src/home/home-content-refresh.ts +52 -0
  252. package/src/home/home-greeting-cache.ts +69 -0
  253. package/src/home/home-greeting.ts +94 -0
  254. package/src/home/suggested-prompts.ts +177 -9
  255. package/src/ipc/cli-client.ts +147 -45
  256. package/src/memory/__tests__/conversation-queries.test.ts +220 -0
  257. package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +135 -2
  258. package/src/memory/__tests__/memory-retrospective-enqueue.test.ts +2 -50
  259. package/src/memory/__tests__/memory-retrospective-job.test.ts +407 -10
  260. package/src/memory/conversation-crud.ts +133 -43
  261. package/src/memory/conversation-queries.ts +87 -1
  262. package/src/memory/conversation-title-service.ts +26 -4
  263. package/src/memory/db-init.ts +22 -0
  264. package/src/memory/delivery-crud.ts +41 -0
  265. package/src/memory/delivery-status.ts +141 -15
  266. package/src/memory/external-conversation-store.ts +32 -1
  267. package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +84 -3
  268. package/src/memory/graph/conversation-graph-memory.ts +18 -6
  269. package/src/memory/graph/tools.ts +6 -37
  270. package/src/memory/invite-store.ts +53 -0
  271. package/src/memory/jobs-worker.ts +21 -1
  272. package/src/memory/llm-request-log-source-clickhouse.ts +7 -2
  273. package/src/memory/llm-request-log-store.ts +92 -1
  274. package/src/memory/memory-retrospective-constants.ts +28 -0
  275. package/src/memory/memory-retrospective-enqueue.ts +4 -22
  276. package/src/memory/memory-retrospective-job.ts +438 -21
  277. package/src/memory/memory-retrospective-startup-cleanup.ts +3 -3
  278. package/src/memory/memory-v2-activation-log-store.ts +26 -8
  279. package/src/memory/migrations/100-core-tables.ts +1 -0
  280. package/src/memory/migrations/109-external-conversation-bindings.ts +1 -0
  281. package/src/memory/migrations/250-provider-connection-base-url-and-models.ts +28 -0
  282. package/src/memory/migrations/251-a2a-tasks.ts +49 -0
  283. package/src/memory/migrations/252-llm-request-log-agent-loop-exit-reason.ts +32 -0
  284. package/src/memory/migrations/253-conversation-last-notified-profile.ts +15 -0
  285. package/src/memory/migrations/253-document-comments.ts +47 -0
  286. package/src/memory/migrations/254-external-conversation-binding-chat-name.ts +43 -0
  287. package/src/memory/migrations/255-channel-inbound-delivery-attempts.ts +24 -0
  288. package/src/memory/migrations/256-memory-v2-injection-events.ts +113 -0
  289. package/src/memory/migrations/257-strip-base-url-non-openai-compatible.ts +22 -0
  290. package/src/memory/migrations/258-onboarding-events-prior-assistants.ts +13 -0
  291. package/src/memory/migrations/259-conversation-cleaned-at.ts +33 -0
  292. package/src/memory/migrations/index.ts +20 -0
  293. package/src/memory/migrations/registry.ts +33 -0
  294. package/src/memory/onboarding-events-store.ts +7 -0
  295. package/src/memory/schema/a2a.ts +15 -0
  296. package/src/memory/schema/calls.ts +1 -0
  297. package/src/memory/schema/conversations.ts +3 -0
  298. package/src/memory/schema/index.ts +1 -0
  299. package/src/memory/schema/inference.ts +2 -0
  300. package/src/memory/schema/infrastructure.ts +2 -0
  301. package/src/memory/v2/__tests__/activation-store.test.ts +25 -23
  302. package/src/memory/v2/__tests__/cli-command-store.test.ts +404 -0
  303. package/src/memory/v2/__tests__/frontmatter-sweep.test.ts +25 -4
  304. package/src/memory/v2/__tests__/injection-events.test.ts +318 -0
  305. package/src/memory/v2/__tests__/injection.test.ts +221 -17
  306. package/src/memory/v2/__tests__/page-index.test.ts +365 -1
  307. package/src/memory/v2/__tests__/router.test.ts +489 -1
  308. package/src/memory/v2/__tests__/static-context.test.ts +12 -1
  309. package/src/memory/v2/activation-store.ts +14 -16
  310. package/src/memory/v2/cli-command-content.ts +19 -0
  311. package/src/memory/v2/cli-command-store.ts +304 -0
  312. package/src/memory/v2/consolidation-job.ts +14 -0
  313. package/src/memory/v2/frontmatter-sweep.ts +7 -1
  314. package/src/memory/v2/injection-events.ts +101 -0
  315. package/src/memory/v2/injection.ts +69 -29
  316. package/src/memory/v2/page-index.ts +246 -19
  317. package/src/memory/v2/page-store.ts +18 -0
  318. package/src/memory/v2/router.ts +209 -55
  319. package/src/memory/v2/static-context.ts +4 -4
  320. package/src/memory/v2/types.ts +23 -0
  321. package/src/messaging/providers/a2a/__tests__/deliver.test.ts +274 -0
  322. package/src/messaging/providers/a2a/deliver.ts +156 -0
  323. package/src/messaging/providers/gmail/client.ts +9 -2
  324. package/src/messaging/providers/index.ts +18 -3
  325. package/src/messaging/providers/slack/__tests__/adapter-mention-rendering.test.ts +329 -3
  326. package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +34 -1
  327. package/src/messaging/providers/slack/adapter.ts +178 -25
  328. package/src/messaging/providers/slack/api.test.ts +54 -0
  329. package/src/messaging/providers/slack/api.ts +119 -3
  330. package/src/messaging/providers/slack/client.ts +12 -0
  331. package/src/messaging/providers/slack/deep-link.ts +20 -1
  332. package/src/messaging/providers/slack/message-metadata.test.ts +48 -0
  333. package/src/messaging/providers/slack/message-metadata.ts +156 -0
  334. package/src/messaging/providers/slack/render-transcript.test.ts +107 -75
  335. package/src/messaging/providers/slack/render-transcript.ts +176 -49
  336. package/src/messaging/providers/slack/send.test.ts +77 -0
  337. package/src/messaging/providers/slack/send.ts +8 -2
  338. package/src/messaging/providers/slack/types.ts +14 -0
  339. package/src/notifications/__tests__/broadcaster.test.ts +203 -0
  340. package/src/notifications/__tests__/decision-engine.test.ts +283 -0
  341. package/src/notifications/__tests__/deterministic-checks.test.ts +286 -0
  342. package/src/notifications/__tests__/emit-signal-home-feed.test.ts +5 -1
  343. package/src/notifications/__tests__/home-feed-side-effect.test.ts +521 -36
  344. package/src/notifications/adapters/macos.ts +12 -2
  345. package/src/notifications/broadcaster.ts +29 -4
  346. package/src/notifications/conversation-seed-composer.ts +14 -2
  347. package/src/notifications/copy-composer.ts +17 -64
  348. package/src/notifications/decision-engine.ts +111 -44
  349. package/src/notifications/deferred-emit.ts +135 -0
  350. package/src/notifications/deterministic-checks.ts +96 -0
  351. package/src/notifications/emit-signal.ts +10 -1
  352. package/src/notifications/home-feed-side-effect.ts +136 -27
  353. package/src/notifications/signal.ts +0 -4
  354. package/src/notifications/types.ts +8 -0
  355. package/src/oauth/connect-orchestrator.ts +3 -0
  356. package/src/oauth/credential-token-resolver.ts +2 -0
  357. package/src/oauth/manual-token-connection.ts +19 -0
  358. package/src/oauth/oauth-store.ts +12 -0
  359. package/src/oauth/platform-connection.test.ts +43 -3
  360. package/src/oauth/platform-connection.ts +13 -4
  361. package/src/oauth/seed-providers.ts +22 -0
  362. package/src/permissions/prompter.ts +5 -2
  363. package/src/permissions/secret-prompter.ts +4 -1
  364. package/src/plugins/defaults/injectors.ts +118 -26
  365. package/src/plugins/external-plugin-loader.ts +82 -10
  366. package/src/plugins/types.ts +16 -7
  367. package/src/prompts/__tests__/system-prompt.test.ts +44 -45
  368. package/src/prompts/__tests__/task-progress-hint-section.test.ts +4 -8
  369. package/src/prompts/normalize-onboarding.ts +40 -0
  370. package/src/prompts/sections.ts +32 -14
  371. package/src/prompts/system-prompt.ts +105 -76
  372. package/src/prompts/template-detection.ts +37 -0
  373. package/src/prompts/templates/BOOTSTRAP-CONTENT-AUTOMATION.md +141 -0
  374. package/src/prompts/templates/BOOTSTRAP.md +13 -5
  375. package/src/prompts/templates/VOICE.md +3 -0
  376. package/src/prompts/templates/system-sections.ts +51 -10
  377. package/src/providers/__tests__/inference.test.ts +2 -0
  378. package/src/providers/anthropic/client.ts +132 -5
  379. package/src/providers/call-site-routing.ts +24 -6
  380. package/src/providers/connection-resolution.ts +63 -13
  381. package/src/providers/fireworks/client.ts +20 -2
  382. package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +74 -0
  383. package/src/providers/inference/__tests__/base-url-route-validation.test.ts +342 -0
  384. package/src/providers/inference/__tests__/base-url-security.test.ts +189 -0
  385. package/src/providers/inference/__tests__/codex-token-refresh.test.ts +254 -0
  386. package/src/providers/inference/__tests__/connections-openai-compatible.test.ts +175 -0
  387. package/src/providers/inference/__tests__/connections-status-label.test.ts +15 -0
  388. package/src/providers/inference/adapter-factory.ts +24 -21
  389. package/src/providers/inference/auth.ts +15 -3
  390. package/src/providers/inference/backfill.ts +14 -1
  391. package/src/providers/inference/codex-token-refresh.ts +128 -0
  392. package/src/providers/inference/connections.ts +85 -5
  393. package/src/providers/inference/resolve-auth.ts +50 -5
  394. package/src/providers/model-catalog.ts +244 -242
  395. package/src/providers/model-intents.ts +3 -3
  396. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +235 -0
  397. package/src/providers/openai/chat-completions-provider.ts +215 -25
  398. package/src/providers/openai/responses-provider.ts +9 -3
  399. package/src/providers/openrouter/client.ts +46 -4
  400. package/src/providers/platform-proxy/constants.ts +3 -4
  401. package/src/providers/provider-catalog-visibility.ts +3 -1
  402. package/src/providers/provider-send-message.ts +27 -12
  403. package/src/providers/registry.ts +30 -1
  404. package/src/providers/types.ts +25 -0
  405. package/src/runtime/__tests__/agent-wake.test.ts +214 -0
  406. package/src/runtime/__tests__/background-job-runner.test.ts +128 -0
  407. package/src/runtime/agent-wake.ts +212 -57
  408. package/src/runtime/auth/route-policy.ts +20 -3
  409. package/src/runtime/background-job-runner.ts +26 -0
  410. package/src/runtime/channel-reply-delivery.ts +182 -47
  411. package/src/runtime/channel-retry-sweep.ts +141 -16
  412. package/src/runtime/http-server.ts +7 -16
  413. package/src/runtime/http-types.ts +7 -51
  414. package/src/runtime/pending-interactions.ts +51 -8
  415. package/src/runtime/routes/__tests__/consolidation-routes.test.ts +258 -0
  416. package/src/runtime/routes/__tests__/content-source-routes.test.ts +162 -0
  417. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +121 -5
  418. package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +275 -44
  419. package/src/runtime/routes/__tests__/llm-call-sites-routes.test.ts +12 -0
  420. package/src/runtime/routes/__tests__/memory-v2-routes.test.ts +14 -0
  421. package/src/runtime/routes/__tests__/memory-v2-simulate-route.test.ts +271 -0
  422. package/src/runtime/routes/__tests__/sanity-routes.test.ts +280 -0
  423. package/src/runtime/routes/__tests__/slack-channel-routes.test.ts +266 -0
  424. package/src/runtime/routes/approval-routes.ts +4 -1
  425. package/src/runtime/routes/channel-availability-routes.ts +5 -0
  426. package/src/runtime/routes/chatgpt-subscription-auth-routes.ts +246 -0
  427. package/src/runtime/routes/consolidation-routes.ts +100 -0
  428. package/src/runtime/routes/content-source-routes.ts +78 -0
  429. package/src/runtime/routes/conversation-cli-routes.ts +146 -1
  430. package/src/runtime/routes/conversation-query-routes.ts +130 -12
  431. package/src/runtime/routes/conversation-routes.ts +288 -76
  432. package/src/runtime/routes/document-comments-routes.ts +287 -0
  433. package/src/runtime/routes/documents-routes.ts +33 -0
  434. package/src/runtime/routes/home-feed-routes.ts +6 -3
  435. package/src/runtime/routes/host-app-control-routes.ts +1 -1
  436. package/src/runtime/routes/host-browser-routes.ts +8 -1
  437. package/src/runtime/routes/identity-routes.ts +21 -0
  438. package/src/runtime/routes/inbound-message-handler.ts +288 -58
  439. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +365 -6
  440. package/src/runtime/routes/inbound-stages/background-dispatch.ts +283 -82
  441. package/src/runtime/routes/index.ts +14 -4
  442. package/src/runtime/routes/inference-provider-connection-routes.ts +192 -3
  443. package/src/runtime/routes/integrations/a2a.ts +294 -0
  444. package/src/runtime/routes/llm-call-sites-routes.ts +11 -1
  445. package/src/runtime/routes/log-export-routes.ts +39 -0
  446. package/src/runtime/routes/memory-v2-routes.ts +217 -0
  447. package/src/runtime/routes/notification-routes.ts +19 -2
  448. package/src/runtime/routes/question-routes.ts +4 -1
  449. package/src/runtime/routes/sanity-routes.ts +159 -0
  450. package/src/runtime/routes/slack-channel-routes.ts +187 -0
  451. package/src/runtime/routes/subagents-routes.ts +41 -0
  452. package/src/runtime/services/conversation-serializer.ts +30 -4
  453. package/src/schedule/integration-status.ts +3 -1
  454. package/src/security/__tests__/oauth2-device-code.test.ts +479 -0
  455. package/src/security/oauth2-device-code.ts +307 -0
  456. package/src/security/oauth2.ts +26 -9
  457. package/src/security/secure-keys.ts +5 -0
  458. package/src/skills/catalog-install.ts +6 -2
  459. package/src/subagent/manager.ts +2 -0
  460. package/src/tools/browser/__tests__/pinned-tabs.test.ts +80 -0
  461. package/src/tools/browser/browser-execution.ts +93 -0
  462. package/src/tools/browser/cdp-client/__tests__/factory.test.ts +28 -0
  463. package/src/tools/browser/cdp-client/__tests__/types.test.ts +1 -0
  464. package/src/tools/browser/cdp-client/cdp-inspect-client.ts +10 -0
  465. package/src/tools/browser/cdp-client/extension-cdp-client.ts +15 -1
  466. package/src/tools/browser/cdp-client/factory.ts +87 -3
  467. package/src/tools/browser/cdp-client/local-cdp-client.ts +9 -0
  468. package/src/tools/browser/cdp-client/types.ts +36 -0
  469. package/src/tools/browser/pinned-tabs.ts +90 -0
  470. package/src/tools/document/document-comment-tool.test.ts +379 -0
  471. package/src/tools/document/document-comment-tool.ts +156 -0
  472. package/src/tools/document/document-tool.ts +128 -2
  473. package/src/tools/memory/register.ts +1 -9
  474. package/src/tools/network/__tests__/web-fetch-metadata.test.ts +229 -0
  475. package/src/tools/network/__tests__/web-search-metadata.test.ts +346 -0
  476. package/src/tools/network/domain-normalize.ts +17 -0
  477. package/src/tools/network/web-fetch.ts +213 -64
  478. package/src/tools/network/web-search.ts +191 -66
  479. package/src/tools/registry.ts +2 -2
  480. package/src/tools/terminal/safe-env.ts +3 -2
  481. package/src/tools/tool-approval-handler.ts +19 -12
  482. package/src/tools/types.ts +41 -2
  483. package/src/tools/ui-surface/definitions.ts +3 -1
  484. package/src/types/onboarding-context.ts +4 -0
  485. package/src/util/__tests__/favicon.test.ts +84 -0
  486. package/src/util/favicon.ts +40 -0
  487. package/src/util/platform.ts +0 -5
  488. package/src/workspace/git-service.ts +75 -4
  489. package/src/workspace/migrations/087-memory-router-balanced-profile.ts +91 -0
  490. package/src/workspace/migrations/088-deprecate-background-conversation-override.ts +103 -0
  491. package/src/workspace/migrations/registry.ts +4 -0
  492. package/src/__tests__/guardian-action-conversation-turn.test.ts +0 -441
  493. package/src/config/bundled-skills/document/SKILL.md +0 -54
  494. package/src/config/bundled-skills/document/TOOLS.json +0 -106
  495. package/src/daemon/seed-files.ts +0 -18
  496. package/src/memory/graph/__tests__/remember-description.test.ts +0 -55
  497. package/src/runtime/guardian-action-conversation-turn.ts +0 -99
  498. package/src/runtime/routes/interface-routes.ts +0 -43
  499. /package/src/config/bundled-skills/{document → document-editor}/tools/document-create.ts +0 -0
  500. /package/src/config/bundled-skills/{document → document-editor}/tools/document-delete.ts +0 -0
  501. /package/src/config/bundled-skills/{document → document-editor}/tools/document-list.ts +0 -0
  502. /package/src/config/bundled-skills/{document → document-editor}/tools/document-read.ts +0 -0
  503. /package/src/config/bundled-skills/{document → document-editor}/tools/document-update.ts +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vellumai/assistant",
3
- "version": "0.8.2",
3
+ "version": "0.8.4",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "exports": {
@@ -0,0 +1,271 @@
1
+ /**
2
+ * Tests for the `agent_loop_exit` instrumentation added in this PR.
3
+ *
4
+ * Coverage targets:
5
+ * 1. **One emit per run** — the idempotency guard fires once, even if the
6
+ * code path would otherwise reach two emit sites (the empty-response
7
+ * throw → catch-block fallback case).
8
+ * 2. **Reason matches break site** — for each reachable break site, the
9
+ * emitted reason is the one documented in `AgentLoopExitReason`.
10
+ * 3. **Always the last AgentEvent of terminal runs** — consumers can rely on
11
+ * positional ordering to find it when a run reaches a terminal state.
12
+ *
13
+ * Sites not exercised here (`empty_response_exhausted`, `aborted_via_error`)
14
+ * require deeper provider fakery and are best covered by integration tests
15
+ * once we wire up the empty-response pipeline mock.
16
+ */
17
+ import { describe, expect, test } from "bun:test";
18
+
19
+ import type {
20
+ AgentEvent,
21
+ CheckpointDecision,
22
+ CheckpointInfo,
23
+ } from "../agent/loop.js";
24
+ import { AgentLoop } from "../agent/loop.js";
25
+ import type {
26
+ Message,
27
+ Provider,
28
+ ProviderResponse,
29
+ SendMessageOptions,
30
+ ToolDefinition,
31
+ } from "../providers/types.js";
32
+
33
+ // ---------------------------------------------------------------------------
34
+ // Helpers (mirrored from agent-loop.test.ts so this file is self-contained)
35
+ // ---------------------------------------------------------------------------
36
+
37
+ function createMockProvider(responses: ProviderResponse[]): {
38
+ provider: Provider;
39
+ } {
40
+ let callIndex = 0;
41
+ const provider: Provider = {
42
+ name: "mock",
43
+ async sendMessage(
44
+ _messages: Message[],
45
+ _tools?: ToolDefinition[],
46
+ _systemPrompt?: string,
47
+ _options?: SendMessageOptions,
48
+ ): Promise<ProviderResponse> {
49
+ const response = responses[callIndex] ?? responses[responses.length - 1];
50
+ callIndex++;
51
+ return response;
52
+ },
53
+ };
54
+ return { provider };
55
+ }
56
+
57
+ function textResponse(text: string): ProviderResponse {
58
+ return {
59
+ content: [{ type: "text", text }],
60
+ model: "mock-model",
61
+ usage: { inputTokens: 10, outputTokens: 5 },
62
+ stopReason: "end_turn",
63
+ };
64
+ }
65
+
66
+ function toolUseResponse(
67
+ id: string,
68
+ name: string,
69
+ input: Record<string, unknown>,
70
+ ): ProviderResponse {
71
+ return {
72
+ content: [{ type: "tool_use", id, name, input }],
73
+ model: "mock-model",
74
+ usage: { inputTokens: 10, outputTokens: 5 },
75
+ stopReason: "tool_use",
76
+ };
77
+ }
78
+
79
+ const dummyTools: ToolDefinition[] = [
80
+ {
81
+ name: "read_file",
82
+ description: "Read a file",
83
+ input_schema: { type: "object", properties: { path: { type: "string" } } },
84
+ },
85
+ ];
86
+
87
+ const userMessage: Message = {
88
+ role: "user",
89
+ content: [{ type: "text", text: "Hello" }],
90
+ };
91
+
92
+ function lastExitEvent(
93
+ events: AgentEvent[],
94
+ ): Extract<AgentEvent, { type: "agent_loop_exit" }> | undefined {
95
+ return events.find(
96
+ (e): e is Extract<AgentEvent, { type: "agent_loop_exit" }> =>
97
+ e.type === "agent_loop_exit",
98
+ );
99
+ }
100
+
101
+ function countExitEvents(events: AgentEvent[]): number {
102
+ return events.filter((e) => e.type === "agent_loop_exit").length;
103
+ }
104
+
105
+ // ---------------------------------------------------------------------------
106
+ // Tests
107
+ // ---------------------------------------------------------------------------
108
+
109
+ describe("AgentLoop exit-reason instrumentation", () => {
110
+ test("emits exit event exactly once with 'no_tool_calls' on plain text response", async () => {
111
+ const { provider } = createMockProvider([textResponse("Hi there!")]);
112
+ const loop = new AgentLoop(provider, "system prompt");
113
+
114
+ const events: AgentEvent[] = [];
115
+ await loop.run([userMessage], (e) => {
116
+ events.push(e);
117
+ });
118
+
119
+ expect(countExitEvents(events)).toBe(1);
120
+ const exit = lastExitEvent(events);
121
+ expect(exit?.reason).toBe("no_tool_calls");
122
+ });
123
+
124
+ test("agent_loop_exit is the last event emitted", async () => {
125
+ const { provider } = createMockProvider([textResponse("Hi there!")]);
126
+ const loop = new AgentLoop(provider, "system prompt");
127
+
128
+ const events: AgentEvent[] = [];
129
+ await loop.run([userMessage], (e) => {
130
+ events.push(e);
131
+ });
132
+
133
+ expect(events.length).toBeGreaterThan(0);
134
+ expect(events[events.length - 1].type).toBe("agent_loop_exit");
135
+ });
136
+
137
+ test("emits 'aborted_pre_call' when signal is already aborted at run start", async () => {
138
+ const { provider } = createMockProvider([textResponse("never sent")]);
139
+ const loop = new AgentLoop(provider, "system prompt");
140
+
141
+ const controller = new AbortController();
142
+ controller.abort();
143
+
144
+ const events: AgentEvent[] = [];
145
+ await loop.run([userMessage], (e) => { events.push(e); }, controller.signal);
146
+
147
+ expect(countExitEvents(events)).toBe(1);
148
+ expect(lastExitEvent(events)?.reason).toBe("aborted_pre_call");
149
+ });
150
+
151
+ test("emits 'yield_to_user' when tool result requests yieldToUser", async () => {
152
+ const { provider } = createMockProvider([
153
+ toolUseResponse("t1", "read_file", { path: "/a.txt" }),
154
+ ]);
155
+ const toolExecutor = async () => ({
156
+ content: "ok",
157
+ isError: false,
158
+ yieldToUser: true,
159
+ });
160
+ const loop = new AgentLoop(
161
+ provider,
162
+ "system",
163
+ {},
164
+ dummyTools,
165
+ toolExecutor,
166
+ );
167
+
168
+ const events: AgentEvent[] = [];
169
+ await loop.run([userMessage], (e) => { events.push(e); });
170
+
171
+ expect(countExitEvents(events)).toBe(1);
172
+ expect(lastExitEvent(events)?.reason).toBe("yield_to_user");
173
+ });
174
+
175
+ test("does not emit agent_loop_exit when onCheckpoint yields control", async () => {
176
+ const { provider } = createMockProvider([
177
+ toolUseResponse("t1", "read_file", { path: "/a.txt" }),
178
+ textResponse("never reached"),
179
+ ]);
180
+ const toolExecutor = async () => ({ content: "ok", isError: false });
181
+ const loop = new AgentLoop(
182
+ provider,
183
+ "system",
184
+ {},
185
+ dummyTools,
186
+ toolExecutor,
187
+ );
188
+
189
+ const onCheckpoint = (_info: CheckpointInfo): CheckpointDecision =>
190
+ "yield";
191
+
192
+ const events: AgentEvent[] = [];
193
+ await loop.run(
194
+ [userMessage],
195
+ (e) => { events.push(e); },
196
+ undefined,
197
+ undefined,
198
+ onCheckpoint,
199
+ );
200
+
201
+ expect(countExitEvents(events)).toBe(0);
202
+ });
203
+
204
+ test("emits 'error' when provider throws an unhandled error", async () => {
205
+ const provider: Provider = {
206
+ name: "broken",
207
+ async sendMessage(): Promise<ProviderResponse> {
208
+ throw new Error("provider exploded");
209
+ },
210
+ };
211
+ const loop = new AgentLoop(provider, "system prompt");
212
+
213
+ const events: AgentEvent[] = [];
214
+ await loop.run([userMessage], (e) => { events.push(e); });
215
+
216
+ expect(countExitEvents(events)).toBe(1);
217
+ expect(lastExitEvent(events)?.reason).toBe("error");
218
+ });
219
+
220
+ test("does not double-emit when multiple exit conditions stack", async () => {
221
+ // Tool returns yieldToUser AND the controller is aborted post-response —
222
+ // the first reached condition wins, but the guard prevents a second
223
+ // emit even if subsequent code paths attempt one.
224
+ const { provider } = createMockProvider([
225
+ toolUseResponse("t1", "read_file", { path: "/a.txt" }),
226
+ ]);
227
+ const toolExecutor = async () => ({
228
+ content: "ok",
229
+ isError: false,
230
+ yieldToUser: true,
231
+ });
232
+ const loop = new AgentLoop(
233
+ provider,
234
+ "system",
235
+ {},
236
+ dummyTools,
237
+ toolExecutor,
238
+ );
239
+
240
+ const events: AgentEvent[] = [];
241
+ await loop.run([userMessage], (e) => { events.push(e); });
242
+
243
+ expect(countExitEvents(events)).toBe(1);
244
+ });
245
+
246
+ test("emits 'aborted_during_tools' when signal aborts after tool execution", async () => {
247
+ const controller = new AbortController();
248
+ const { provider } = createMockProvider([
249
+ toolUseResponse("t1", "read_file", { path: "/a.txt" }),
250
+ ]);
251
+ // Abort the signal inside the tool executor so by the time the loop
252
+ // re-checks signal.aborted post-tools the abort has landed.
253
+ const toolExecutor = async () => {
254
+ controller.abort();
255
+ return { content: "ok", isError: false };
256
+ };
257
+ const loop = new AgentLoop(
258
+ provider,
259
+ "system",
260
+ {},
261
+ dummyTools,
262
+ toolExecutor,
263
+ );
264
+
265
+ const events: AgentEvent[] = [];
266
+ await loop.run([userMessage], (e) => { events.push(e); }, controller.signal);
267
+
268
+ expect(countExitEvents(events)).toBe(1);
269
+ expect(lastExitEvent(events)?.reason).toBe("aborted_during_tools");
270
+ });
271
+ });
@@ -257,7 +257,7 @@ const anthropicStub = { name: "anthropic" };
257
257
  mock.module("../providers/registry.js", () => ({
258
258
  getProvider: () => anthropicStub,
259
259
  listProviders: () => ["anthropic"],
260
- initializeProviders: () => {},
260
+ initializeProviders: async () => {},
261
261
  resolveProviderFromConnection: async () => anthropicStub,
262
262
  }));
263
263
 
@@ -0,0 +1,195 @@
1
+ /**
2
+ * Integration tests for the agent loop's `provider_error` recording path.
3
+ *
4
+ * When the `llmCall` pipeline throws (provider rejected the request before
5
+ * returning a usable response), the loop must emit a `provider_error` event
6
+ * carrying the loop-level raw request and the thrown error so downstream
7
+ * consumers can persist an `llm_request_logs` row. Without this, rejected
8
+ * calls leave nothing in the LLM inspector — only a pino log line.
9
+ *
10
+ * Coverage:
11
+ * - Emits `provider_error` with `rawRequest`, `error`, and `actualProvider`
12
+ * when the provider throws a `ProviderError`.
13
+ * - `rawRequest` carries the message history, tools, and system prompt the
14
+ * loop attempted to send — so the row replays/debugs cleanly.
15
+ * - `actualProvider` echoes `ProviderError.provider` when available, falling
16
+ * back to `provider.name` for non-ProviderError throws.
17
+ * - The error is still re-thrown internally (the existing `error` event
18
+ * still fires after the new `provider_error` event), preserving the
19
+ * outer-catch behavior (abort/Sentry/break).
20
+ * - Skips emission on user-aborted runs — there is no provider rejection
21
+ * worth recording when the user cancelled.
22
+ */
23
+
24
+ import { describe, expect, test } from "bun:test";
25
+
26
+ import type { AgentEvent } from "../agent/loop.js";
27
+ import { AgentLoop } from "../agent/loop.js";
28
+ import type {
29
+ Message,
30
+ Provider,
31
+ ProviderResponse,
32
+ SendMessageOptions,
33
+ ToolDefinition,
34
+ } from "../providers/types.js";
35
+ import { ProviderError } from "../util/errors.js";
36
+
37
+ /**
38
+ * Build a provider that throws on every `sendMessage` call. Records what
39
+ * the loop attempted to send so the test can assert `rawRequest` carries
40
+ * the right payload.
41
+ */
42
+ function makeThrowingProvider(
43
+ name: string,
44
+ throwFn: () => Error,
45
+ ): {
46
+ provider: Provider;
47
+ calls: Array<{
48
+ messages: Message[];
49
+ tools?: ToolDefinition[];
50
+ systemPrompt?: string;
51
+ }>;
52
+ } {
53
+ const calls: Array<{
54
+ messages: Message[];
55
+ tools?: ToolDefinition[];
56
+ systemPrompt?: string;
57
+ }> = [];
58
+ const provider: Provider = {
59
+ name,
60
+ async sendMessage(
61
+ messages: Message[],
62
+ tools?: ToolDefinition[],
63
+ systemPrompt?: string,
64
+ _options?: SendMessageOptions,
65
+ ): Promise<ProviderResponse> {
66
+ calls.push({ messages: [...messages], tools, systemPrompt });
67
+ throw throwFn();
68
+ },
69
+ };
70
+ return { provider, calls };
71
+ }
72
+
73
+ describe("AgentLoop provider_error event emission", () => {
74
+ test("emits provider_error with loop-level rawRequest when provider throws ProviderError", async () => {
75
+ const thrown = new ProviderError(
76
+ "Anthropic API error (429): rate limited",
77
+ "anthropic",
78
+ 429,
79
+ { retryAfterMs: 1500 },
80
+ );
81
+ const { provider, calls } = makeThrowingProvider("anthropic", () => thrown);
82
+
83
+ const events: AgentEvent[] = [];
84
+ const loop = new AgentLoop(provider, "you are a helpful assistant");
85
+
86
+ await loop.run(
87
+ [{ role: "user", content: [{ type: "text", text: "hi" }] }],
88
+ (e) => {
89
+ events.push(e);
90
+ },
91
+ );
92
+
93
+ expect(calls).toHaveLength(1);
94
+
95
+ const providerErrorEvent = events.find((e) => e.type === "provider_error");
96
+ expect(providerErrorEvent).toBeDefined();
97
+ if (providerErrorEvent?.type !== "provider_error") {
98
+ throw new Error("type narrowing");
99
+ }
100
+ expect(providerErrorEvent.error).toBe(thrown);
101
+ expect(providerErrorEvent.actualProvider).toBe("anthropic");
102
+
103
+ // rawRequest should carry the loop-level abstract shape: messages,
104
+ // tools, systemPrompt, and the provider name we tried to dispatch
105
+ // through. The provider-specific shape (e.g. Gemini's `contents`) is
106
+ // never built because the provider threw before returning it.
107
+ const raw = providerErrorEvent.rawRequest as Record<string, unknown>;
108
+ expect(raw.provider).toBe("anthropic");
109
+ expect(raw.systemPrompt).toBe("you are a helpful assistant");
110
+ expect(Array.isArray(raw.messages)).toBe(true);
111
+ expect((raw.messages as Message[])[0].role).toBe("user");
112
+ });
113
+
114
+ test("error event still fires after provider_error (outer catch behavior unchanged)", async () => {
115
+ const thrown = new ProviderError(
116
+ "Gemini API error (500): internal",
117
+ "gemini",
118
+ 500,
119
+ );
120
+ const { provider } = makeThrowingProvider("gemini", () => thrown);
121
+
122
+ const events: AgentEvent[] = [];
123
+ const loop = new AgentLoop(provider, "system");
124
+
125
+ await loop.run(
126
+ [{ role: "user", content: [{ type: "text", text: "hi" }] }],
127
+ (e) => {
128
+ events.push(e);
129
+ },
130
+ );
131
+
132
+ const providerErrorIdx = events.findIndex(
133
+ (e) => e.type === "provider_error",
134
+ );
135
+ const errorIdx = events.findIndex((e) => e.type === "error");
136
+ expect(providerErrorIdx).toBeGreaterThanOrEqual(0);
137
+ expect(errorIdx).toBeGreaterThanOrEqual(0);
138
+ // Recording-first ordering is load-bearing: a consumer that sees the
139
+ // generic `error` event and shuts the stream down must have already
140
+ // received the `provider_error` row for the rejected call.
141
+ expect(providerErrorIdx).toBeLessThan(errorIdx);
142
+ });
143
+
144
+ test("falls back to provider.name when a non-ProviderError is thrown", async () => {
145
+ const thrown = new Error("unexpected SDK boom");
146
+ const { provider } = makeThrowingProvider("openai", () => thrown);
147
+
148
+ const events: AgentEvent[] = [];
149
+ const loop = new AgentLoop(provider, "system");
150
+
151
+ await loop.run(
152
+ [{ role: "user", content: [{ type: "text", text: "hi" }] }],
153
+ (e) => {
154
+ events.push(e);
155
+ },
156
+ );
157
+
158
+ const providerErrorEvent = events.find((e) => e.type === "provider_error");
159
+ expect(providerErrorEvent).toBeDefined();
160
+ if (providerErrorEvent?.type !== "provider_error") {
161
+ throw new Error("type narrowing");
162
+ }
163
+ // The thrown Error has no `.provider` field, so the event falls back to
164
+ // the dispatching provider's `name` — keeps the persisted log row's
165
+ // `provider` column populated even for surprise errors.
166
+ expect(providerErrorEvent.actualProvider).toBe("openai");
167
+ expect(providerErrorEvent.error).toBe(thrown);
168
+ });
169
+
170
+ test("does NOT emit provider_error on user-aborted runs", async () => {
171
+ const controller = new AbortController();
172
+ const thrown = new Error("aborted");
173
+ const { provider } = makeThrowingProvider("anthropic", () => {
174
+ // Pre-abort then throw so the loop's catch sees `signal.aborted === true`.
175
+ controller.abort();
176
+ return thrown;
177
+ });
178
+
179
+ const events: AgentEvent[] = [];
180
+ const loop = new AgentLoop(provider, "system");
181
+
182
+ await loop.run(
183
+ [{ role: "user", content: [{ type: "text", text: "hi" }] }],
184
+ (e) => {
185
+ events.push(e);
186
+ },
187
+ controller.signal,
188
+ );
189
+
190
+ const providerErrorEvent = events.find((e) => e.type === "provider_error");
191
+ // Cancellation should never produce a recording row — there's no
192
+ // provider rejection worth logging when the user pulled the plug.
193
+ expect(providerErrorEvent).toBeUndefined();
194
+ });
195
+ });
@@ -181,6 +181,88 @@ describe("AgentLoop", () => {
181
181
  ]);
182
182
  });
183
183
 
184
+ test("re-resolves override profile before each provider call", async () => {
185
+ const toolCallId = "tool-1";
186
+ const { provider, calls } = createMockProvider([
187
+ toolUseResponse(toolCallId, "read_file", { path: "/tmp/test.txt" }),
188
+ textResponse("File contents received."),
189
+ ]);
190
+
191
+ let overrideProfile: string | undefined;
192
+ const toolExecutor = async () => {
193
+ overrideProfile = "quality-optimized";
194
+ return { content: "ok", isError: false };
195
+ };
196
+ const loop = new AgentLoop(
197
+ provider,
198
+ "system",
199
+ {},
200
+ dummyTools,
201
+ toolExecutor,
202
+ );
203
+
204
+ await loop.run(
205
+ [userMessage],
206
+ collectEvents([]),
207
+ undefined,
208
+ "req-1",
209
+ undefined,
210
+ "mainAgent",
211
+ undefined,
212
+ undefined,
213
+ undefined,
214
+ () => overrideProfile,
215
+ );
216
+
217
+ expect(calls).toHaveLength(2);
218
+ expect(calls[0].options?.config?.overrideProfile).toBeUndefined();
219
+ expect(calls[1].options?.config?.overrideProfile).toBe("quality-optimized");
220
+ });
221
+
222
+ test("re-resolves max input tokens before truncating tool results", async () => {
223
+ const toolCallId = "tool-1";
224
+ const toolOutput = "x".repeat(2_500);
225
+ const { provider, calls } = createMockProvider([
226
+ toolUseResponse(toolCallId, "read_file", { path: "/tmp/test.txt" }),
227
+ textResponse("File contents received."),
228
+ ]);
229
+
230
+ let maxInputTokens = 1_000;
231
+ const toolExecutor = async () => {
232
+ maxInputTokens = 10_000;
233
+ return { content: toolOutput, isError: false };
234
+ };
235
+ const loop = new AgentLoop(
236
+ provider,
237
+ "system",
238
+ {},
239
+ dummyTools,
240
+ toolExecutor,
241
+ );
242
+
243
+ await loop.run(
244
+ [userMessage],
245
+ collectEvents([]),
246
+ undefined,
247
+ "req-1",
248
+ undefined,
249
+ "mainAgent",
250
+ undefined,
251
+ undefined,
252
+ 1_000,
253
+ undefined,
254
+ () => maxInputTokens,
255
+ );
256
+
257
+ const secondCallMessages = calls[1].messages;
258
+ const lastMsg = secondCallMessages[secondCallMessages.length - 1];
259
+ const toolResultBlock = lastMsg.content.find(
260
+ (b): b is Extract<ContentBlock, { type: "tool_result" }> =>
261
+ b.type === "tool_result",
262
+ );
263
+ expect(toolResultBlock?.content).toBe(toolOutput);
264
+ });
265
+
184
266
  // 3. Multi-turn tool loop
185
267
  test("supports multi-turn tool execution", async () => {
186
268
  const { provider, calls } = createMockProvider([
@@ -470,7 +552,6 @@ describe("AgentLoop", () => {
470
552
  ).toBe(false);
471
553
  });
472
554
 
473
-
474
555
  // 9. Tool executor error results are forwarded correctly
475
556
  test("forwards tool error results to provider", async () => {
476
557
  const { provider, calls } = createMockProvider([
@@ -1767,7 +1848,9 @@ describe("AgentLoop", () => {
1767
1848
  ]);
1768
1849
 
1769
1850
  // message_complete emitted for tool_use response + retry text response (not the empty one)
1770
- const messageCompletes = events.filter((e) => e.type === "message_complete");
1851
+ const messageCompletes = events.filter(
1852
+ (e) => e.type === "message_complete",
1853
+ );
1771
1854
  expect(messageCompletes).toHaveLength(2);
1772
1855
  });
1773
1856
 
@@ -1883,7 +1966,9 @@ describe("AgentLoop", () => {
1883
1966
  expect(calls).toHaveLength(3);
1884
1967
 
1885
1968
  // message_complete: tool_use response + final empty response (retry exhausted)
1886
- const messageCompletes = events.filter((e) => e.type === "message_complete");
1969
+ const messageCompletes = events.filter(
1970
+ (e) => e.type === "message_complete",
1971
+ );
1887
1972
  expect(messageCompletes).toHaveLength(2);
1888
1973
 
1889
1974
  // The last assistant message in history is the empty one