@vellumai/assistant 0.8.3 → 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 (342) hide show
  1. package/docker-entrypoint.sh +0 -1
  2. package/node_modules/@vellumai/gateway-client/src/types.ts +2 -0
  3. package/openapi.yaml +610 -16
  4. package/package.json +1 -1
  5. package/src/__tests__/agent-loop-exit-reason.test.ts +4 -5
  6. package/src/__tests__/agent-loop-override-profile.test.ts +1 -1
  7. package/src/__tests__/agent-loop.test.ts +88 -3
  8. package/src/__tests__/anthropic-provider.test.ts +272 -0
  9. package/src/__tests__/approval-cascade.test.ts +1 -1
  10. package/src/__tests__/background-workers-disk-pressure.test.ts +2 -1
  11. package/src/__tests__/channel-delivery-store.test.ts +193 -0
  12. package/src/__tests__/channel-reply-delivery.test.ts +284 -5
  13. package/src/__tests__/channel-retry-sweep.test.ts +274 -1
  14. package/src/__tests__/compaction-events.test.ts +1 -1
  15. package/src/__tests__/compactor-preserved-tail-count.test.ts +110 -0
  16. package/src/__tests__/config-watcher.test.ts +1 -1
  17. package/src/__tests__/context-token-estimator.test.ts +91 -1
  18. package/src/__tests__/conversation-abort-tool-results.test.ts +1 -1
  19. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +54 -3
  20. package/src/__tests__/conversation-agent-loop-overflow.test.ts +31 -6
  21. package/src/__tests__/conversation-agent-loop.test.ts +25 -7
  22. package/src/__tests__/conversation-app-control-lifecycle.test.ts +1 -1
  23. package/src/__tests__/conversation-clean-command.test.ts +137 -0
  24. package/src/__tests__/conversation-confirmation-signals.test.ts +1 -1
  25. package/src/__tests__/conversation-fork-crud.test.ts +161 -0
  26. package/src/__tests__/conversation-lifecycle.test.ts +1 -1
  27. package/src/__tests__/conversation-load-cleaned-at.test.ts +279 -0
  28. package/src/__tests__/conversation-load-history-repair.test.ts +1 -1
  29. package/src/__tests__/conversation-pairing.test.ts +2 -2
  30. package/src/__tests__/conversation-process-callsite.test.ts +1 -1
  31. package/src/__tests__/conversation-provider-retry-repair.test.ts +1 -1
  32. package/src/__tests__/conversation-queue.test.ts +1 -1
  33. package/src/__tests__/conversation-runtime-assembly.test.ts +264 -81
  34. package/src/__tests__/conversation-seed-composer.test.ts +66 -4
  35. package/src/__tests__/conversation-slash-commands.test.ts +36 -8
  36. package/src/__tests__/conversation-slash-queue.test.ts +1 -1
  37. package/src/__tests__/conversation-slash-unknown.test.ts +1 -1
  38. package/src/__tests__/conversation-speed-override.test.ts +1 -1
  39. package/src/__tests__/conversation-surfaces-task-progress.test.ts +220 -0
  40. package/src/__tests__/conversation-workspace-cache-state.test.ts +1 -1
  41. package/src/__tests__/conversation-workspace-injection.test.ts +5 -1
  42. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +5 -1
  43. package/src/__tests__/credential-security-invariants.test.ts +6 -0
  44. package/src/__tests__/cu-unified-flow.test.ts +10 -1
  45. package/src/__tests__/dm-backfill.test.ts +64 -0
  46. package/src/__tests__/dm-persistence.test.ts +33 -0
  47. package/src/__tests__/document-find-replace.test.ts +501 -0
  48. package/src/__tests__/first-greeting.test.ts +23 -2
  49. package/src/__tests__/headless-browser-navigate.test.ts +172 -0
  50. package/src/__tests__/host-bash-proxy.test.ts +6 -0
  51. package/src/__tests__/host-browser-proxy.test.ts +10 -0
  52. package/src/__tests__/host-cu-proxy.test.ts +8 -1
  53. package/src/__tests__/host-file-proxy.test.ts +8 -1
  54. package/src/__tests__/host-transfer-proxy.test.ts +8 -1
  55. package/src/__tests__/identity-routes.test.ts +57 -0
  56. package/src/__tests__/inbound-slack-persistence.test.ts +3 -0
  57. package/src/__tests__/injector-chain.test.ts +2 -0
  58. package/src/__tests__/injector-document-comments.test.ts +378 -0
  59. package/src/__tests__/injector-pkb-v2-silenced.test.ts +4 -25
  60. package/src/__tests__/list-messages-attachments.test.ts +21 -17
  61. package/src/__tests__/list-messages-hidden-metadata.test.ts +217 -0
  62. package/src/__tests__/list-messages-page-latest.test.ts +130 -14
  63. package/src/__tests__/list-messages-tool-merge.test.ts +17 -16
  64. package/src/__tests__/llm-context-normalization.test.ts +0 -2
  65. package/src/__tests__/llm-resolver.test.ts +85 -1
  66. package/src/__tests__/log-export-routes.test.ts +99 -2
  67. package/src/__tests__/message-queue-steer.test.ts +114 -0
  68. package/src/__tests__/openai-provider.test.ts +105 -0
  69. package/src/__tests__/openai-responses-provider.test.ts +4 -4
  70. package/src/__tests__/outbound-slack-persistence.test.ts +187 -20
  71. package/src/__tests__/pending-interactions-resolved-event.test.ts +190 -0
  72. package/src/__tests__/platform.test.ts +0 -3
  73. package/src/__tests__/plugin-source-watcher.test.ts +302 -0
  74. package/src/__tests__/process-message-background-slack.test.ts +1 -51
  75. package/src/__tests__/process-message-display-content.test.ts +21 -16
  76. package/src/__tests__/server-history-render.test.ts +83 -4
  77. package/src/__tests__/steer-tool-repair.test.ts +249 -0
  78. package/src/__tests__/system-prompt.test.ts +51 -28
  79. package/src/__tests__/terminal-tools.test.ts +11 -1
  80. package/src/__tests__/thinking-block-replay.test.ts +113 -0
  81. package/src/__tests__/thread-backfill.test.ts +370 -22
  82. package/src/__tests__/tool-executor.test.ts +90 -1
  83. package/src/__tests__/tool-result-metadata-plumbing.test.ts +167 -0
  84. package/src/__tests__/twilio-routes.test.ts +1 -1
  85. package/src/__tests__/web-fetch.test.ts +2 -2
  86. package/src/__tests__/workspace-git-service.test.ts +88 -5
  87. package/src/__tests__/workspace-migration-088-deprecate-background-conversation-override.test.ts +158 -0
  88. package/src/agent/attachments.ts +1 -0
  89. package/src/agent/loop.ts +57 -20
  90. package/src/background-wake/next-wake.test.ts +289 -0
  91. package/src/background-wake/next-wake.ts +172 -0
  92. package/src/browser/operations.ts +15 -0
  93. package/src/cli/commands/__tests__/conversations-slack.test.ts +572 -0
  94. package/src/cli/commands/__tests__/memory-v2.test.ts +9 -12
  95. package/src/cli/commands/conversations.ts +128 -1
  96. package/src/cli/commands/inference-providers.ts +147 -1
  97. package/src/cli/commands/memory-v2.ts +308 -0
  98. package/src/cli/commands/notifications.ts +24 -2
  99. package/src/cli/utils/conversation-id.ts +17 -5
  100. package/src/config/bundled-skills/app-builder/SKILL.md +2 -2
  101. package/src/config/bundled-skills/document-editor/SKILL.md +115 -0
  102. package/src/config/bundled-skills/document-editor/TOOLS.json +240 -0
  103. package/src/config/bundled-skills/document-editor/tools/comment-list.ts +12 -0
  104. package/src/config/bundled-skills/document-editor/tools/comment-reply.ts +12 -0
  105. package/src/config/bundled-skills/document-editor/tools/comment-resolve.ts +12 -0
  106. package/src/config/bundled-skills/document-editor/tools/document-find.ts +12 -0
  107. package/src/config/bundled-skills/document-editor/tools/document-replace-text.ts +12 -0
  108. package/src/config/bundled-skills/media-processing/SKILL.md +8 -0
  109. package/src/config/bundled-skills/schedule/SKILL.md +8 -0
  110. package/src/config/bundled-tool-registry.ts +22 -12
  111. package/src/config/call-site-defaults.ts +19 -0
  112. package/src/config/feature-flag-registry.json +99 -3
  113. package/src/config/llm-resolver.ts +16 -2
  114. package/src/config/schemas/__tests__/memory-v2.test.ts +4 -0
  115. package/src/config/schemas/call-site-catalog.ts +21 -0
  116. package/src/config/schemas/llm.ts +3 -0
  117. package/src/config/schemas/memory-v2.ts +48 -1
  118. package/src/context/compactor.ts +8 -1
  119. package/src/context/token-estimator.ts +47 -4
  120. package/src/context/window-manager.ts +25 -0
  121. package/src/credential-health/credential-health-service.ts +34 -19
  122. package/src/daemon/__tests__/conversation-tool-setup.test.ts +66 -6
  123. package/src/daemon/__tests__/native-web-search-metadata.test.ts +357 -0
  124. package/src/daemon/__tests__/web-search-status-text.test.ts +287 -0
  125. package/src/daemon/conversation-agent-loop-handlers.ts +153 -23
  126. package/src/daemon/conversation-agent-loop.ts +223 -54
  127. package/src/daemon/conversation-lifecycle.ts +142 -116
  128. package/src/daemon/conversation-messaging.ts +3 -0
  129. package/src/daemon/conversation-process.ts +273 -0
  130. package/src/daemon/conversation-queue-manager.ts +14 -0
  131. package/src/daemon/conversation-runtime-assembly.ts +135 -75
  132. package/src/daemon/conversation-slash.ts +37 -5
  133. package/src/daemon/conversation-surfaces.ts +45 -2
  134. package/src/daemon/conversation-tool-setup.ts +7 -0
  135. package/src/daemon/conversation.ts +42 -5
  136. package/src/daemon/first-greeting.ts +10 -0
  137. package/src/daemon/handlers/__tests__/config-a2a-accept.test.ts +498 -0
  138. package/src/daemon/handlers/config-a2a.ts +160 -0
  139. package/src/daemon/handlers/config-model.test.ts +1 -0
  140. package/src/daemon/handlers/conversations.ts +79 -0
  141. package/src/daemon/handlers/shared.ts +92 -29
  142. package/src/daemon/host-bash-proxy.ts +1 -1
  143. package/src/daemon/host-cu-proxy.ts +1 -1
  144. package/src/daemon/host-file-proxy.ts +1 -1
  145. package/src/daemon/host-transfer-proxy.ts +1 -1
  146. package/src/daemon/lifecycle.ts +18 -4
  147. package/src/daemon/message-protocol.ts +4 -0
  148. package/src/daemon/message-types/conversations.ts +8 -0
  149. package/src/daemon/message-types/document-comments.ts +50 -0
  150. package/src/daemon/message-types/messages.ts +68 -1
  151. package/src/daemon/message-types/surfaces.ts +3 -1
  152. package/src/daemon/message-types/web-activity.ts +57 -0
  153. package/src/daemon/plugin-source-watcher.ts +135 -3
  154. package/src/daemon/process-message.ts +69 -12
  155. package/src/daemon/query-complexity-router.ts +75 -0
  156. package/src/daemon/trust-context.ts +6 -0
  157. package/src/documents/document-comments-store.test.ts +338 -0
  158. package/src/documents/document-comments-store.ts +237 -0
  159. package/src/documents/document-store.ts +202 -0
  160. package/src/heartbeat/__tests__/heartbeat-service.test.ts +0 -1
  161. package/src/heartbeat/heartbeat-service.ts +1 -0
  162. package/src/home/__tests__/suggested-prompts.test.ts +33 -2
  163. package/src/home/feed-types.ts +6 -1
  164. package/src/home/home-content-refresh.ts +52 -0
  165. package/src/home/home-greeting-cache.ts +69 -0
  166. package/src/home/home-greeting.ts +94 -0
  167. package/src/home/suggested-prompts.ts +177 -9
  168. package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +135 -2
  169. package/src/memory/__tests__/memory-retrospective-job.test.ts +320 -6
  170. package/src/memory/conversation-crud.ts +133 -43
  171. package/src/memory/db-init.ts +16 -0
  172. package/src/memory/delivery-crud.ts +41 -0
  173. package/src/memory/delivery-status.ts +141 -15
  174. package/src/memory/external-conversation-store.ts +32 -1
  175. package/src/memory/jobs-worker.ts +21 -1
  176. package/src/memory/memory-retrospective-constants.ts +28 -0
  177. package/src/memory/memory-retrospective-enqueue.ts +3 -2
  178. package/src/memory/memory-retrospective-job.ts +408 -18
  179. package/src/memory/memory-retrospective-startup-cleanup.ts +3 -3
  180. package/src/memory/memory-v2-activation-log-store.ts +26 -8
  181. package/src/memory/migrations/100-core-tables.ts +1 -0
  182. package/src/memory/migrations/109-external-conversation-bindings.ts +1 -0
  183. package/src/memory/migrations/253-conversation-last-notified-profile.ts +15 -0
  184. package/src/memory/migrations/253-document-comments.ts +47 -0
  185. package/src/memory/migrations/254-external-conversation-binding-chat-name.ts +43 -0
  186. package/src/memory/migrations/255-channel-inbound-delivery-attempts.ts +24 -0
  187. package/src/memory/migrations/256-memory-v2-injection-events.ts +113 -0
  188. package/src/memory/migrations/257-strip-base-url-non-openai-compatible.ts +22 -0
  189. package/src/memory/migrations/258-onboarding-events-prior-assistants.ts +13 -0
  190. package/src/memory/migrations/259-conversation-cleaned-at.ts +33 -0
  191. package/src/memory/migrations/index.ts +17 -0
  192. package/src/memory/migrations/registry.ts +25 -0
  193. package/src/memory/onboarding-events-store.ts +7 -0
  194. package/src/memory/schema/calls.ts +1 -0
  195. package/src/memory/schema/conversations.ts +3 -0
  196. package/src/memory/schema/infrastructure.ts +1 -0
  197. package/src/memory/v2/__tests__/injection-events.test.ts +318 -0
  198. package/src/memory/v2/__tests__/injection.test.ts +31 -14
  199. package/src/memory/v2/__tests__/page-index.test.ts +365 -1
  200. package/src/memory/v2/__tests__/router.test.ts +489 -1
  201. package/src/memory/v2/consolidation-job.ts +14 -0
  202. package/src/memory/v2/injection-events.ts +101 -0
  203. package/src/memory/v2/injection.ts +21 -10
  204. package/src/memory/v2/page-index.ts +209 -7
  205. package/src/memory/v2/page-store.ts +18 -0
  206. package/src/memory/v2/router.ts +209 -55
  207. package/src/messaging/providers/index.ts +7 -1
  208. package/src/messaging/providers/slack/__tests__/adapter-mention-rendering.test.ts +329 -3
  209. package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +34 -1
  210. package/src/messaging/providers/slack/adapter.ts +178 -25
  211. package/src/messaging/providers/slack/api.test.ts +54 -0
  212. package/src/messaging/providers/slack/api.ts +119 -3
  213. package/src/messaging/providers/slack/client.ts +12 -0
  214. package/src/messaging/providers/slack/deep-link.ts +20 -1
  215. package/src/messaging/providers/slack/message-metadata.test.ts +48 -0
  216. package/src/messaging/providers/slack/message-metadata.ts +156 -0
  217. package/src/messaging/providers/slack/render-transcript.test.ts +107 -75
  218. package/src/messaging/providers/slack/render-transcript.ts +176 -49
  219. package/src/messaging/providers/slack/send.test.ts +77 -0
  220. package/src/messaging/providers/slack/send.ts +8 -2
  221. package/src/messaging/providers/slack/types.ts +14 -0
  222. package/src/notifications/__tests__/emit-signal-home-feed.test.ts +4 -1
  223. package/src/notifications/__tests__/home-feed-side-effect.test.ts +116 -54
  224. package/src/notifications/conversation-seed-composer.ts +14 -2
  225. package/src/notifications/deferred-emit.ts +135 -0
  226. package/src/notifications/emit-signal.ts +9 -1
  227. package/src/notifications/home-feed-side-effect.ts +60 -30
  228. package/src/oauth/connect-orchestrator.ts +3 -0
  229. package/src/oauth/credential-token-resolver.ts +2 -0
  230. package/src/oauth/manual-token-connection.ts +19 -0
  231. package/src/oauth/oauth-store.ts +12 -0
  232. package/src/oauth/seed-providers.ts +22 -0
  233. package/src/permissions/prompter.ts +5 -2
  234. package/src/permissions/secret-prompter.ts +4 -1
  235. package/src/plugins/defaults/injectors.ts +82 -9
  236. package/src/prompts/__tests__/system-prompt.test.ts +46 -2
  237. package/src/prompts/normalize-onboarding.ts +40 -0
  238. package/src/prompts/sections.ts +32 -14
  239. package/src/prompts/system-prompt.ts +105 -68
  240. package/src/prompts/template-detection.ts +37 -0
  241. package/src/prompts/templates/BOOTSTRAP-CONTENT-AUTOMATION.md +141 -0
  242. package/src/prompts/templates/BOOTSTRAP.md +8 -0
  243. package/src/prompts/templates/VOICE.md +3 -0
  244. package/src/prompts/templates/system-sections.ts +53 -3
  245. package/src/providers/anthropic/client.ts +132 -5
  246. package/src/providers/fireworks/client.ts +20 -2
  247. package/src/providers/inference/__tests__/base-url-route-validation.test.ts +342 -0
  248. package/src/providers/inference/__tests__/base-url-security.test.ts +189 -0
  249. package/src/providers/inference/__tests__/codex-token-refresh.test.ts +254 -0
  250. package/src/providers/inference/adapter-factory.ts +15 -1
  251. package/src/providers/inference/auth.ts +3 -3
  252. package/src/providers/inference/codex-token-refresh.ts +128 -0
  253. package/src/providers/inference/resolve-auth.ts +49 -6
  254. package/src/providers/model-catalog.ts +48 -1
  255. package/src/providers/openai/chat-completions-provider.ts +57 -20
  256. package/src/providers/openai/responses-provider.ts +9 -3
  257. package/src/providers/openrouter/client.ts +5 -1
  258. package/src/providers/types.ts +25 -0
  259. package/src/runtime/__tests__/agent-wake.test.ts +214 -0
  260. package/src/runtime/__tests__/background-job-runner.test.ts +128 -0
  261. package/src/runtime/agent-wake.ts +151 -56
  262. package/src/runtime/auth/route-policy.ts +7 -3
  263. package/src/runtime/background-job-runner.ts +26 -0
  264. package/src/runtime/channel-reply-delivery.ts +182 -47
  265. package/src/runtime/channel-retry-sweep.ts +141 -16
  266. package/src/runtime/http-types.ts +7 -4
  267. package/src/runtime/pending-interactions.ts +51 -8
  268. package/src/runtime/routes/__tests__/content-source-routes.test.ts +162 -0
  269. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +55 -1
  270. package/src/runtime/routes/__tests__/memory-v2-routes.test.ts +14 -0
  271. package/src/runtime/routes/__tests__/memory-v2-simulate-route.test.ts +271 -0
  272. package/src/runtime/routes/__tests__/sanity-routes.test.ts +280 -0
  273. package/src/runtime/routes/__tests__/slack-channel-routes.test.ts +266 -0
  274. package/src/runtime/routes/approval-routes.ts +4 -1
  275. package/src/runtime/routes/chatgpt-subscription-auth-routes.ts +246 -0
  276. package/src/runtime/routes/content-source-routes.ts +78 -0
  277. package/src/runtime/routes/conversation-cli-routes.ts +146 -1
  278. package/src/runtime/routes/conversation-query-routes.ts +60 -1
  279. package/src/runtime/routes/conversation-routes.ts +281 -76
  280. package/src/runtime/routes/document-comments-routes.ts +287 -0
  281. package/src/runtime/routes/documents-routes.ts +33 -0
  282. package/src/runtime/routes/home-feed-routes.ts +6 -3
  283. package/src/runtime/routes/host-app-control-routes.ts +1 -1
  284. package/src/runtime/routes/host-browser-routes.ts +8 -1
  285. package/src/runtime/routes/identity-routes.ts +21 -0
  286. package/src/runtime/routes/inbound-message-handler.ts +288 -58
  287. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +365 -6
  288. package/src/runtime/routes/inbound-stages/background-dispatch.ts +283 -82
  289. package/src/runtime/routes/index.ts +12 -4
  290. package/src/runtime/routes/inference-provider-connection-routes.ts +63 -7
  291. package/src/runtime/routes/integrations/a2a.ts +60 -1
  292. package/src/runtime/routes/log-export-routes.ts +39 -0
  293. package/src/runtime/routes/memory-v2-routes.ts +217 -0
  294. package/src/runtime/routes/notification-routes.ts +19 -2
  295. package/src/runtime/routes/question-routes.ts +4 -1
  296. package/src/runtime/routes/sanity-routes.ts +159 -0
  297. package/src/runtime/routes/slack-channel-routes.ts +187 -0
  298. package/src/runtime/services/conversation-serializer.ts +30 -4
  299. package/src/schedule/integration-status.ts +3 -1
  300. package/src/security/__tests__/oauth2-device-code.test.ts +479 -0
  301. package/src/security/oauth2-device-code.ts +307 -0
  302. package/src/security/oauth2.ts +26 -9
  303. package/src/security/secure-keys.ts +5 -0
  304. package/src/skills/catalog-install.ts +6 -2
  305. package/src/tools/browser/__tests__/pinned-tabs.test.ts +80 -0
  306. package/src/tools/browser/browser-execution.ts +93 -0
  307. package/src/tools/browser/cdp-client/__tests__/factory.test.ts +28 -0
  308. package/src/tools/browser/cdp-client/__tests__/types.test.ts +1 -0
  309. package/src/tools/browser/cdp-client/cdp-inspect-client.ts +10 -0
  310. package/src/tools/browser/cdp-client/extension-cdp-client.ts +15 -1
  311. package/src/tools/browser/cdp-client/factory.ts +87 -3
  312. package/src/tools/browser/cdp-client/local-cdp-client.ts +9 -0
  313. package/src/tools/browser/cdp-client/types.ts +36 -0
  314. package/src/tools/browser/pinned-tabs.ts +90 -0
  315. package/src/tools/document/document-comment-tool.test.ts +379 -0
  316. package/src/tools/document/document-comment-tool.ts +156 -0
  317. package/src/tools/document/document-tool.ts +128 -2
  318. package/src/tools/network/__tests__/web-fetch-metadata.test.ts +229 -0
  319. package/src/tools/network/__tests__/web-search-metadata.test.ts +346 -0
  320. package/src/tools/network/domain-normalize.ts +17 -0
  321. package/src/tools/network/web-fetch.ts +213 -64
  322. package/src/tools/network/web-search.ts +191 -66
  323. package/src/tools/terminal/safe-env.ts +3 -2
  324. package/src/tools/tool-approval-handler.ts +19 -12
  325. package/src/tools/types.ts +4 -0
  326. package/src/tools/ui-surface/definitions.ts +3 -1
  327. package/src/types/onboarding-context.ts +4 -0
  328. package/src/util/__tests__/favicon.test.ts +84 -0
  329. package/src/util/favicon.ts +40 -0
  330. package/src/util/platform.ts +0 -5
  331. package/src/workspace/git-service.ts +75 -4
  332. package/src/workspace/migrations/088-deprecate-background-conversation-override.ts +103 -0
  333. package/src/workspace/migrations/registry.ts +2 -0
  334. package/src/config/bundled-skills/document/SKILL.md +0 -54
  335. package/src/config/bundled-skills/document/TOOLS.json +0 -106
  336. package/src/daemon/seed-files.ts +0 -18
  337. package/src/runtime/routes/interface-routes.ts +0 -43
  338. /package/src/config/bundled-skills/{document → document-editor}/tools/document-create.ts +0 -0
  339. /package/src/config/bundled-skills/{document → document-editor}/tools/document-delete.ts +0 -0
  340. /package/src/config/bundled-skills/{document → document-editor}/tools/document-list.ts +0 -0
  341. /package/src/config/bundled-skills/{document → document-editor}/tools/document-read.ts +0 -0
  342. /package/src/config/bundled-skills/{document → document-editor}/tools/document-update.ts +0 -0
@@ -1,6 +1,6 @@
1
1
  import { beforeEach, describe, expect, mock, test } from "bun:test";
2
2
 
3
- import { eq } from "drizzle-orm";
3
+ import { asc, eq } from "drizzle-orm";
4
4
 
5
5
  mock.module("../util/logger.js", () => ({
6
6
  getLogger: () =>
@@ -9,6 +9,66 @@ mock.module("../util/logger.js", () => ({
9
9
  }),
10
10
  }));
11
11
 
12
+ const deliveryCalls: Array<{
13
+ conversationId: string;
14
+ externalChatId: string;
15
+ callbackUrl: string;
16
+ assistantId?: string;
17
+ messageId?: string;
18
+ startFromSegment?: number;
19
+ }> = [];
20
+ let deliverReplyViaCallbackImpl: (
21
+ ...args: unknown[]
22
+ ) => Promise<void> = async () => {};
23
+
24
+ mock.module("../runtime/channel-reply-delivery.js", () => ({
25
+ deliverReplyViaCallback: async (
26
+ conversationId: string,
27
+ externalChatId: string,
28
+ callbackUrl: string,
29
+ assistantId?: string,
30
+ options?: { messageId?: string; startFromSegment?: number },
31
+ ) => {
32
+ deliveryCalls.push({
33
+ conversationId,
34
+ externalChatId,
35
+ callbackUrl,
36
+ assistantId,
37
+ messageId: options?.messageId,
38
+ startFromSegment: options?.startFromSegment,
39
+ });
40
+ return deliverReplyViaCallbackImpl(
41
+ conversationId,
42
+ externalChatId,
43
+ callbackUrl,
44
+ assistantId,
45
+ options,
46
+ );
47
+ },
48
+ findAssistantReplyMessageIdForTurn: (
49
+ conversationId: string,
50
+ userMessageId: string,
51
+ ): string | undefined => {
52
+ const rows = getDb()
53
+ .select()
54
+ .from(messages)
55
+ .where(eq(messages.conversationId, conversationId))
56
+ .orderBy(asc(messages.createdAt))
57
+ .all();
58
+ const userIndex = rows.findIndex((row) => row.id === userMessageId);
59
+ if (userIndex === -1) return undefined;
60
+ let candidate: string | undefined;
61
+ for (let i = userIndex + 1; i < rows.length; i++) {
62
+ const row = rows[i];
63
+ if (row.role === "user") break;
64
+ if (row.role === "assistant") {
65
+ candidate = row.id;
66
+ }
67
+ }
68
+ return candidate;
69
+ },
70
+ }));
71
+
12
72
  import { getDb } from "../memory/db-connection.js";
13
73
  import { initializeDb } from "../memory/db-init.js";
14
74
  import * as deliveryCrud from "../memory/delivery-crud.js";
@@ -97,6 +157,8 @@ function seedFailedEventWithActorRoleOnly(
97
157
  describe("channel-retry-sweep", () => {
98
158
  beforeEach(() => {
99
159
  resetTables();
160
+ deliveryCalls.length = 0;
161
+ deliverReplyViaCallbackImpl = async () => {};
100
162
  });
101
163
 
102
164
  test("replays canonical payloads with trustClass correctly", async () => {
@@ -296,4 +358,215 @@ describe("channel-retry-sweep", () => {
296
358
  expect(capturedOptions?.trustContext?.sourceChannel).toBe("telegram");
297
359
  expect(capturedOptions?.isInteractive).toBe(false);
298
360
  });
361
+
362
+ test("delivery failure after successful replay does not requeue processing", async () => {
363
+ const inbound = deliveryCrud.recordInbound(
364
+ "telegram",
365
+ "chat-delivery-fails",
366
+ "msg-delivery-fails",
367
+ );
368
+ deliveryCrud.storePayload(inbound.eventId, {
369
+ content: "retry me",
370
+ sourceChannel: "telegram",
371
+ interface: "telegram",
372
+ externalChatId: "chat-delivery-fails",
373
+ replyCallbackUrl: "https://example.test/deliver/telegram",
374
+ trustCtx: {
375
+ trustClass: "unknown",
376
+ sourceChannel: "telegram",
377
+ requesterChatId: "chat-delivery-fails",
378
+ },
379
+ });
380
+
381
+ const db = getDb();
382
+ db.update(channelInboundEvents)
383
+ .set({
384
+ processingStatus: "failed",
385
+ processingAttempts: 1,
386
+ retryAfter: Date.now() - 1,
387
+ })
388
+ .where(eq(channelInboundEvents.id, inbound.eventId))
389
+ .run();
390
+ deliverReplyViaCallbackImpl = async () => {
391
+ throw new Error("fetch failed");
392
+ };
393
+
394
+ let processMessageCalls = 0;
395
+ await sweepFailedEvents(async (conversationId, _content, _ids, options) => {
396
+ processMessageCalls++;
397
+ options?.onEvent?.({
398
+ type: "message_complete",
399
+ conversationId,
400
+ messageId: "assistant-delivery-fails",
401
+ });
402
+ const messageId = "message-delivery-fails";
403
+ db.insert(messages)
404
+ .values({
405
+ id: messageId,
406
+ conversationId,
407
+ role: "user",
408
+ content: JSON.stringify([{ type: "text", text: "retry me" }]),
409
+ createdAt: Date.now(),
410
+ })
411
+ .run();
412
+ return { messageId };
413
+ });
414
+
415
+ const row = db
416
+ .select()
417
+ .from(channelInboundEvents)
418
+ .where(eq(channelInboundEvents.id, inbound.eventId))
419
+ .get();
420
+ expect(processMessageCalls).toBe(1);
421
+ expect(deliveryCalls).toHaveLength(1);
422
+ expect(row?.processingStatus).toBe("processed");
423
+ expect(row?.deliveryStatus).toBe("failed");
424
+ expect(row?.messageId).toBe("message-delivery-fails");
425
+ expect(
426
+ row?.rawPayload ? JSON.parse(row.rawPayload).replyMessageId : undefined,
427
+ ).toBe("assistant-delivery-fails");
428
+ });
429
+
430
+ test("delivery retry for processed events resumes delivery without processing", async () => {
431
+ const inbound = deliveryCrud.recordInbound(
432
+ "telegram",
433
+ "chat-delivery-only",
434
+ "msg-delivery-only",
435
+ );
436
+ deliveryCrud.storePayload(inbound.eventId, {
437
+ content: "already processed",
438
+ sourceChannel: "telegram",
439
+ interface: "telegram",
440
+ externalChatId: "chat-delivery-only",
441
+ replyCallbackUrl: "https://example.test/deliver/telegram",
442
+ assistantId: "assistant-1",
443
+ replyMessageId: "assistant-delivery-only",
444
+ });
445
+
446
+ const db = getDb();
447
+ db.update(channelInboundEvents)
448
+ .set({
449
+ processingStatus: "processed",
450
+ deliveryStatus: "failed",
451
+ processingAttempts: 1,
452
+ retryAfter: Date.now() - 1,
453
+ deliveredSegmentCount: 2,
454
+ })
455
+ .where(eq(channelInboundEvents.id, inbound.eventId))
456
+ .run();
457
+
458
+ let processMessageCalls = 0;
459
+ await sweepFailedEvents(async () => {
460
+ processMessageCalls++;
461
+ throw new Error("processMessage should not be called");
462
+ });
463
+
464
+ const row = db
465
+ .select()
466
+ .from(channelInboundEvents)
467
+ .where(eq(channelInboundEvents.id, inbound.eventId))
468
+ .get();
469
+ expect(processMessageCalls).toBe(0);
470
+ expect(deliveryCalls).toEqual([
471
+ {
472
+ conversationId: inbound.conversationId,
473
+ externalChatId: "chat-delivery-only",
474
+ callbackUrl: "https://example.test/deliver/telegram",
475
+ assistantId: "assistant-1",
476
+ messageId: "assistant-delivery-only",
477
+ startFromSegment: 2,
478
+ },
479
+ ]);
480
+ expect(row?.processingStatus).toBe("processed");
481
+ expect(row?.deliveryStatus).toBe("delivered");
482
+ expect(row?.retryAfter).toBeNull();
483
+ });
484
+
485
+ test("delivery retry resolves missing reply id from the linked user turn", async () => {
486
+ const inbound = deliveryCrud.recordInbound(
487
+ "telegram",
488
+ "chat-delivery-fallback",
489
+ "msg-delivery-fallback",
490
+ );
491
+ deliveryCrud.storePayload(inbound.eventId, {
492
+ content: "already processed",
493
+ sourceChannel: "telegram",
494
+ interface: "telegram",
495
+ externalChatId: "chat-delivery-fallback",
496
+ replyCallbackUrl: "https://example.test/deliver/telegram",
497
+ assistantId: "assistant-1",
498
+ });
499
+
500
+ const db = getDb();
501
+ db.insert(messages)
502
+ .values([
503
+ {
504
+ id: "user-delivery-fallback",
505
+ conversationId: inbound.conversationId,
506
+ role: "user",
507
+ content: JSON.stringify([
508
+ { type: "text", text: "already processed" },
509
+ ]),
510
+ createdAt: 1_000,
511
+ },
512
+ {
513
+ id: "assistant-delivery-fallback",
514
+ conversationId: inbound.conversationId,
515
+ role: "assistant",
516
+ content: JSON.stringify([{ type: "text", text: "reply" }]),
517
+ createdAt: 1_001,
518
+ },
519
+ {
520
+ id: "user-unrelated",
521
+ conversationId: inbound.conversationId,
522
+ role: "user",
523
+ content: JSON.stringify([{ type: "text", text: "newer turn" }]),
524
+ createdAt: 1_002,
525
+ },
526
+ {
527
+ id: "assistant-unrelated",
528
+ conversationId: inbound.conversationId,
529
+ role: "assistant",
530
+ content: JSON.stringify([{ type: "text", text: "newer reply" }]),
531
+ createdAt: 1_003,
532
+ },
533
+ ])
534
+ .run();
535
+ deliveryCrud.linkMessage(inbound.eventId, "user-delivery-fallback");
536
+ db.update(channelInboundEvents)
537
+ .set({
538
+ processingStatus: "processed",
539
+ deliveryStatus: "failed",
540
+ retryAfter: Date.now() - 1,
541
+ })
542
+ .where(eq(channelInboundEvents.id, inbound.eventId))
543
+ .run();
544
+
545
+ let processMessageCalls = 0;
546
+ await sweepFailedEvents(async () => {
547
+ processMessageCalls++;
548
+ throw new Error("processMessage should not be called");
549
+ });
550
+
551
+ const row = db
552
+ .select()
553
+ .from(channelInboundEvents)
554
+ .where(eq(channelInboundEvents.id, inbound.eventId))
555
+ .get();
556
+ expect(processMessageCalls).toBe(0);
557
+ expect(deliveryCalls).toEqual([
558
+ {
559
+ conversationId: inbound.conversationId,
560
+ externalChatId: "chat-delivery-fallback",
561
+ callbackUrl: "https://example.test/deliver/telegram",
562
+ assistantId: "assistant-1",
563
+ messageId: "assistant-delivery-fallback",
564
+ startFromSegment: 0,
565
+ },
566
+ ]);
567
+ expect(
568
+ row?.rawPayload ? JSON.parse(row.rawPayload).replyMessageId : undefined,
569
+ ).toBe("assistant-delivery-fallback");
570
+ expect(row?.deliveryStatus).toBe("delivered");
571
+ });
299
572
  });
@@ -52,7 +52,7 @@ mock.module("../memory/guardian-action-store.js", () => ({
52
52
 
53
53
  mock.module("../providers/registry.js", () => ({
54
54
  getProvider: () => ({ name: "mock-provider" }),
55
- initializeProviders: () => {},
55
+ initializeProviders: async () => {},
56
56
  }));
57
57
 
58
58
  mock.module("../config/loader.js", () => ({
@@ -0,0 +1,110 @@
1
+ import { describe, expect, mock, test } from "bun:test";
2
+
3
+ function makeLoggerStub(): Record<string, unknown> {
4
+ const stub: Record<string, unknown> = {};
5
+ for (const m of [
6
+ "info",
7
+ "warn",
8
+ "error",
9
+ "debug",
10
+ "trace",
11
+ "fatal",
12
+ "silent",
13
+ "child",
14
+ ]) {
15
+ stub[m] = m === "child" ? () => makeLoggerStub() : () => {};
16
+ }
17
+ return stub;
18
+ }
19
+
20
+ mock.module("../util/logger.js", () => ({
21
+ getLogger: () => makeLoggerStub(),
22
+ }));
23
+
24
+ mock.module("../memory/conversation-crud.js", () => ({
25
+ getMessages: () => [],
26
+ }));
27
+
28
+ mock.module("../memory/attachments-store.js", () => ({
29
+ getAttachmentMetadataForMessage: () => [],
30
+ getAttachmentContent: () => null,
31
+ }));
32
+
33
+ import { runAssistantDrivenCompaction } from "../context/compactor.js";
34
+ import type { Message, Provider } from "../providers/types.js";
35
+
36
+ const TAIL_TIMESTAMP =
37
+ "2026-05-21 (Thursday) 10:00:00 -05:00 (America/Chicago)";
38
+
39
+ const compactionResponse = `
40
+ <compaction_result>
41
+ <summary>
42
+ Earlier turns summarized here.
43
+ </summary>
44
+
45
+ <key_state>
46
+ - Nothing critical pending.
47
+ </key_state>
48
+
49
+ <tail_start timestamp="${TAIL_TIMESTAMP}" preview="tail anchor message" />
50
+ </compaction_result>
51
+ `;
52
+
53
+ function makeProvider(): Provider {
54
+ return {
55
+ name: "mock-provider",
56
+ sendMessage: async () => ({
57
+ content: [{ type: "text", text: compactionResponse }],
58
+ model: "mock-model",
59
+ usage: { inputTokens: 100, outputTokens: 50 },
60
+ stopReason: "end_turn",
61
+ }),
62
+ };
63
+ }
64
+
65
+ const userText = (text: string): Message => ({
66
+ role: "user",
67
+ content: [{ type: "text", text }],
68
+ });
69
+
70
+ const userTextWithTurnContext = (text: string, timestamp: string): Message => ({
71
+ role: "user",
72
+ content: [
73
+ {
74
+ type: "text",
75
+ text: `<turn_context>\ncurrent_time: ${timestamp}\n</turn_context>\n${text}`,
76
+ },
77
+ ],
78
+ });
79
+
80
+ const assistantText = (text: string): Message => ({
81
+ role: "assistant",
82
+ content: [{ type: "text", text }],
83
+ });
84
+
85
+ describe("runAssistantDrivenCompaction — preservedTailMessages count", () => {
86
+ test("reflects the pre-strip tail size so the reported cut point matches the user-visible split", async () => {
87
+ const messages: Message[] = [
88
+ userText("old user turn 1"),
89
+ assistantText("old assistant reply 1"),
90
+ userText("old user turn 2"),
91
+ userTextWithTurnContext("tail anchor message", TAIL_TIMESTAMP),
92
+ userText("<system_reminder>\nstale reminder\n</system_reminder>"),
93
+ userText("<knowledge_base>\nstale pkb\n</knowledge_base>"),
94
+ assistantText("most recent assistant reply"),
95
+ ];
96
+
97
+ const result = await runAssistantDrivenCompaction({
98
+ conversationId: "conv-test",
99
+ messages,
100
+ provider: makeProvider(),
101
+ systemPrompt: "system",
102
+ compaction: { enabled: true, autoThreshold: 0.7 },
103
+ maxInputTokens: 1000,
104
+ previousEstimatedInputTokens: 900,
105
+ });
106
+
107
+ expect(result.compacted).toBe(true);
108
+ expect(result.preservedTailMessages).toBe(4);
109
+ });
110
+ });
@@ -117,7 +117,7 @@ mock.module("../providers/registry.js", () => ({
117
117
  getProvider: () => undefined,
118
118
  listProviders: () => [],
119
119
  getProviderRoutingSource: () => undefined,
120
- initializeProviders: () => {},
120
+ initializeProviders: async () => {},
121
121
  // Required by `providers/inference/connections.ts` and
122
122
  // `providers/connection-resolution.ts`, both loaded transitively when
123
123
  // ConfigWatcher's deps resolve. Without these, the import chain throws
@@ -256,7 +256,12 @@ describe("token estimator", () => {
256
256
  // tokens = ceil(1461 * 822 / 750) = ceil(1601.26) = ~1,602
257
257
  // With IMAGE_BLOCK_OVERHEAD_TOKENS and media_type overhead, still well under 5000.
258
258
  // Same result for every provider — dimension-based estimate is universal.
259
- for (const providerName of ["anthropic", "openai", "openrouter"]) {
259
+ for (const providerName of [
260
+ "anthropic",
261
+ "openai",
262
+ "openrouter",
263
+ "gemini",
264
+ ]) {
260
265
  const tokens = estimateContentBlockTokens(
261
266
  {
262
267
  type: "image",
@@ -294,6 +299,91 @@ describe("token estimator", () => {
294
299
  }
295
300
  });
296
301
 
302
+ test("Gemini falls back to its max-tile budget for unparseable / HEIC images", () => {
303
+ // HEIC/HEIF coming from iOS attachments aren't parsed by
304
+ // parseImageDimensions, so the estimator sees null dims. The generic
305
+ // 1,600-token cap would under-count by ~2.5x for a typical iPhone photo
306
+ // that ends up at Gemini's 16-tile / 4,128-token ceiling. Use the
307
+ // Gemini-specific cap instead to avoid skipping compaction.
308
+ for (const mediaType of [
309
+ "image/heic",
310
+ "image/heif",
311
+ "image/png", // corrupted PNG also exercises the fallback
312
+ ]) {
313
+ const data = Buffer.from("not-a-valid-image-header-at-all").toString(
314
+ "base64",
315
+ );
316
+ const tokens = estimateContentBlockTokens(
317
+ {
318
+ type: "image",
319
+ source: { type: "base64", media_type: mediaType, data },
320
+ },
321
+ { providerName: "gemini" },
322
+ );
323
+ // 4128 (16 tiles * 258) + 16 (block overhead) + ceil(mediaType len / 4)
324
+ expect(tokens).toBeGreaterThanOrEqual(4_128);
325
+ expect(tokens).toBeLessThan(4_200);
326
+ }
327
+ });
328
+
329
+ test("Gemini image tokens scale with image area via 768x768 tiling", () => {
330
+ // Per Google's docs, Gemini tiles images larger than 384px into 768x768
331
+ // chunks at 258 tokens each, after resizing the longest side to ≤3072px.
332
+ // 3000x3000 (under the cap) → ceil(3000/768)^2 = 4*4 = 16 tiles → 4,128
333
+ // image tokens.
334
+ const tokens = estimateContentBlockTokens(
335
+ {
336
+ type: "image",
337
+ source: {
338
+ type: "base64",
339
+ media_type: "image/png",
340
+ data: makePngBase64(3000, 3000),
341
+ },
342
+ },
343
+ { providerName: "gemini" },
344
+ );
345
+ expect(tokens).toBeGreaterThanOrEqual(4_128);
346
+ expect(tokens).toBeLessThan(4_200);
347
+ });
348
+
349
+ test("Gemini clamps image dimensions to 3072px before tiling", () => {
350
+ // Google's docs state images are resized to a 3072px max side before
351
+ // tiling. Without the clamp, a 4000x4000 image would be counted as
352
+ // ceil(4000/768)^2 = 36 tiles (~9,288 tokens) instead of the actual
353
+ // ceil(3072/768)^2 = 16 tiles (~4,128 tokens), over-counting by ~2.25x
354
+ // and triggering spurious compaction.
355
+ const tokens = estimateContentBlockTokens(
356
+ {
357
+ type: "image",
358
+ source: {
359
+ type: "base64",
360
+ media_type: "image/png",
361
+ data: makePngBase64(4000, 4000),
362
+ },
363
+ },
364
+ { providerName: "gemini" },
365
+ );
366
+ expect(tokens).toBeGreaterThanOrEqual(4_128);
367
+ expect(tokens).toBeLessThan(4_200);
368
+ });
369
+
370
+ test("Gemini images ≤384px on both sides count as a single 258-token tile", () => {
371
+ const tokens = estimateContentBlockTokens(
372
+ {
373
+ type: "image",
374
+ source: {
375
+ type: "base64",
376
+ media_type: "image/png",
377
+ data: makePngBase64(200, 200),
378
+ },
379
+ },
380
+ { providerName: "gemini" },
381
+ );
382
+ // 258 (tile) + 16 (block overhead) + 3 (media type) = 277
383
+ expect(tokens).toBeGreaterThanOrEqual(258);
384
+ expect(tokens).toBeLessThan(300);
385
+ });
386
+
297
387
  test("Anthropic image tokens are the same for same-dimension images regardless of payload size", () => {
298
388
  // Build two PNG headers with the same dimensions (800x600) but different payload sizes
299
389
  function makePng(
@@ -19,7 +19,7 @@ mock.module("../memory/guardian-action-store.js", () => ({
19
19
 
20
20
  mock.module("../providers/registry.js", () => ({
21
21
  getProvider: () => ({ name: "mock-provider" }),
22
- initializeProviders: () => {},
22
+ initializeProviders: async () => {},
23
23
  }));
24
24
 
25
25
  mock.module("../config/loader.js", () => ({
@@ -78,7 +78,11 @@ mock.module("../config/loader.js", () => ({
78
78
  },
79
79
  },
80
80
  },
81
- profiles: {},
81
+ profiles: {
82
+ "quality-optimized": {
83
+ contextWindow: { maxInputTokens: 50000 },
84
+ },
85
+ },
82
86
  callSites: {},
83
87
  pricingOverrides: [],
84
88
  },
@@ -165,6 +169,7 @@ mock.module("../memory/conversation-crud.js", () => ({
165
169
  getConversationOriginChannel: () => null,
166
170
  getMessageById: () => null,
167
171
  getLastUserTimestampBefore: () => 0,
172
+ setLastNotifiedInferenceProfile: () => {},
168
173
  }));
169
174
 
170
175
  mock.module("../memory/conversation-disk-view.js", () => ({
@@ -350,12 +355,17 @@ import {
350
355
 
351
356
  // Captures every positional argument the loop passes to `agentLoop.run`.
352
357
  // The 8th positional argument is the per-turn `overrideProfile`, which is
353
- // what these tests assert on.
358
+ // what most tests assert on. The 10th and 11th positional arguments re-resolve
359
+ // that profile and its max-token budget between provider calls.
354
360
  interface CapturedAgentLoopRun {
355
361
  callSite: LLMCallSite | undefined;
356
362
  overrideProfile: string | undefined;
363
+ resolvedOverrideProfile: string | undefined;
364
+ resolvedEffectiveMaxInputTokens: number | undefined;
357
365
  }
358
366
 
367
+ let mutateBeforeResolveOverrideProfile: (() => void) | undefined;
368
+
359
369
  function makeCtx(
360
370
  captured: CapturedAgentLoopRun[],
361
371
  overrides?: Partial<AgentLoopConversationContext>,
@@ -371,8 +381,17 @@ function makeCtx(
371
381
  callSite?: LLMCallSite,
372
382
  _turnContext?: unknown,
373
383
  overrideProfile?: string,
384
+ _effectiveMaxInputTokens?: number,
385
+ resolveOverrideProfile?: () => string | undefined,
386
+ resolveEffectiveMaxInputTokens?: () => number | undefined,
374
387
  ): Promise<Message[]> => {
375
- captured.push({ callSite, overrideProfile });
388
+ mutateBeforeResolveOverrideProfile?.();
389
+ captured.push({
390
+ callSite,
391
+ overrideProfile,
392
+ resolvedOverrideProfile: resolveOverrideProfile?.(),
393
+ resolvedEffectiveMaxInputTokens: resolveEffectiveMaxInputTokens?.(),
394
+ });
376
395
  return [
377
396
  ...messages,
378
397
  {
@@ -511,6 +530,7 @@ beforeEach(() => {
511
530
  totalEstimatedCost: 0,
512
531
  title: null,
513
532
  };
533
+ mutateBeforeResolveOverrideProfile = undefined;
514
534
  resetPluginRegistryAndRegisterDefaults();
515
535
  });
516
536
 
@@ -631,4 +651,35 @@ describe("runAgentLoopImpl — per-conversation inferenceProfile", () => {
631
651
  expect(call.overrideProfile).toBe("fast");
632
652
  }
633
653
  });
654
+
655
+ test("re-resolves inferenceProfile when a tool changes it mid-turn", async () => {
656
+ mockConversationRow = {
657
+ id: "conv-1",
658
+ conversationType: "standard",
659
+ inferenceProfile: null,
660
+ contextSummary: null,
661
+ contextCompactedMessageCount: 0,
662
+ totalInputTokens: 0,
663
+ totalOutputTokens: 0,
664
+ totalEstimatedCost: 0,
665
+ title: null,
666
+ };
667
+ mutateBeforeResolveOverrideProfile = () => {
668
+ mockConversationRow = {
669
+ ...mockConversationRow!,
670
+ inferenceProfile: "quality-optimized",
671
+ };
672
+ };
673
+
674
+ const captured: CapturedAgentLoopRun[] = [];
675
+ const ctx = makeCtx(captured);
676
+
677
+ await runAgentLoopImpl(ctx, "hello", "msg-1", () => {});
678
+
679
+ expect(captured.length).toBeGreaterThan(0);
680
+ expect(captured[0].overrideProfile).toBeUndefined();
681
+ expect(captured[0].resolvedOverrideProfile).toBe("quality-optimized");
682
+ expect(captured[0].resolvedEffectiveMaxInputTokens).toBe(50000);
683
+ expect(ctx.currentTurnOverrideProfile).toBeUndefined();
684
+ });
634
685
  });