@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
@@ -219,36 +219,7 @@ describe("token estimator", () => {
219
219
  expect(largeFileTokens).toBe(smallFileTokens);
220
220
  });
221
221
 
222
- // Non-Anthropic providers use base64 payload size for image estimation
223
- test("scales image token estimate with base64 payload size (non-Anthropic)", () => {
224
- const smallImageTokens = estimateContentBlockTokens(
225
- {
226
- type: "image",
227
- source: {
228
- type: "base64",
229
- media_type: "image/png",
230
- data: "a".repeat(64),
231
- },
232
- },
233
- { providerName: "openai" },
234
- );
235
- const largeImageTokens = estimateContentBlockTokens(
236
- {
237
- type: "image",
238
- source: {
239
- type: "base64",
240
- media_type: "image/png",
241
- data: "a".repeat(60_000),
242
- },
243
- },
244
- { providerName: "openai" },
245
- );
246
-
247
- expect(largeImageTokens).toBeGreaterThan(smallImageTokens);
248
- expect(largeImageTokens - smallImageTokens).toBeGreaterThan(1000);
249
- });
250
-
251
- test("estimates Anthropic image tokens from dimensions, not base64 size", () => {
222
+ test("estimates image tokens from dimensions, not base64 size", () => {
252
223
  // Build a minimal valid PNG header encoding 1920x1080 dimensions.
253
224
  // PNG header: 8-byte signature + 4-byte IHDR length + 4-byte "IHDR" + 4-byte width + 4-byte height = 24 bytes minimum
254
225
  const pngHeader = Buffer.alloc(24);
@@ -278,55 +249,139 @@ describe("token estimator", () => {
278
249
  const fullPayload = Buffer.concat([pngHeader, padding]);
279
250
  const base64Data = fullPayload.toString("base64");
280
251
 
281
- const anthropicTokens = estimateContentBlockTokens(
282
- {
283
- type: "image",
284
- source: { type: "base64", media_type: "image/png", data: base64Data },
285
- },
286
- { providerName: "anthropic" },
287
- );
288
-
289
252
  // 1920x1080 scaled to fit 1568px bounding box: dimScale = 1568/1920 = 0.8167
290
253
  // scaledWidth = round(1920 * 0.8167) = 1568, scaledHeight = round(1080 * 0.8167) = 882
291
254
  // pixels = 1568 * 882 = 1,382,976 > 1,200,000 → mpScale = sqrt(1200000/1382976) = 0.9315
292
255
  // scaledWidth = round(1568 * 0.9315) = 1461, scaledHeight = round(882 * 0.9315) = 822
293
256
  // tokens = ceil(1461 * 822 / 750) = ceil(1601.26) = ~1,602
294
- // With IMAGE_BLOCK_OVERHEAD_TOKENS and media_type overhead, still well under 5000
295
- expect(anthropicTokens).toBeLessThan(5_000);
296
-
297
- // Verify it's NOT using base64 size (which would be ~50,000+ tokens)
298
- const nonAnthropicTokens = estimateContentBlockTokens(
299
- {
300
- type: "image",
301
- source: { type: "base64", media_type: "image/png", data: base64Data },
302
- },
303
- { providerName: "openai" },
304
- );
305
- expect(nonAnthropicTokens).toBeGreaterThan(50_000);
257
+ // With IMAGE_BLOCK_OVERHEAD_TOKENS and media_type overhead, still well under 5000.
258
+ // Same result for every provider — dimension-based estimate is universal.
259
+ for (const providerName of [
260
+ "anthropic",
261
+ "openai",
262
+ "openrouter",
263
+ "gemini",
264
+ ]) {
265
+ const tokens = estimateContentBlockTokens(
266
+ {
267
+ type: "image",
268
+ source: { type: "base64", media_type: "image/png", data: base64Data },
269
+ },
270
+ { providerName },
271
+ );
272
+ expect(tokens).toBeLessThan(5_000);
273
+ }
306
274
  });
307
275
 
308
- test("falls back to max tokens when Anthropic image dimensions can't be parsed", () => {
276
+ test("falls back to max tokens when image dimensions can't be parsed", () => {
309
277
  // Corrupted base64 that won't parse as a valid image header
310
278
  const corruptedData = Buffer.from(
311
279
  "not-a-valid-image-header-at-all",
312
280
  ).toString("base64");
313
281
 
282
+ for (const providerName of ["anthropic", "openai", "openrouter"]) {
283
+ const tokens = estimateContentBlockTokens(
284
+ {
285
+ type: "image",
286
+ source: {
287
+ type: "base64",
288
+ media_type: "image/png",
289
+ data: corruptedData,
290
+ },
291
+ },
292
+ { providerName },
293
+ );
294
+
295
+ // Falls back to the per-image cap (1,600 tokens). Total = 16 (block
296
+ // overhead) + ceil(9/4) (media_type) + 1600 = 1619.
297
+ expect(tokens).toBeGreaterThanOrEqual(1_600);
298
+ expect(tokens).toBeLessThan(2_000);
299
+ }
300
+ });
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.
314
334
  const tokens = estimateContentBlockTokens(
315
335
  {
316
336
  type: "image",
317
337
  source: {
318
338
  type: "base64",
319
339
  media_type: "image/png",
320
- data: corruptedData,
340
+ data: makePngBase64(3000, 3000),
321
341
  },
322
342
  },
323
- { providerName: "anthropic" },
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" },
324
365
  );
366
+ expect(tokens).toBeGreaterThanOrEqual(4_128);
367
+ expect(tokens).toBeLessThan(4_200);
368
+ });
325
369
 
326
- // Should fall back to ANTHROPIC_IMAGE_MAX_TOKENS (1,600)
327
- // Total = 16 (block overhead) + ceil(9/4) (media_type) + 1600 = 1619
328
- expect(tokens).toBeGreaterThanOrEqual(1_600);
329
- expect(tokens).toBeLessThan(2_000);
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);
330
385
  });
331
386
 
332
387
  test("Anthropic image tokens are the same for same-dimension images regardless of payload size", () => {
@@ -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
  });
@@ -195,6 +195,9 @@ mock.module("../memory/conversation-crud.js", () => ({
195
195
  updateMessageContent: () => {},
196
196
  updateMessageMetadata: () => {},
197
197
  clearStrippedInjectionMetadataForConversation: () => {},
198
+ setLastNotifiedInferenceProfile: () => {},
199
+ getLastUserTimestampBefore: () => 0,
200
+ getConversationOverrideProfileFromRow: () => undefined,
198
201
  }));
199
202
 
200
203
  afterAll(() => {
@@ -301,6 +304,7 @@ mock.module("../daemon/history-repair.js", () => ({
301
304
  }));
302
305
 
303
306
  const recordUsageMock = mock((..._args: unknown[]) => {});
307
+ const setAgentLoopExitReasonOnLatestLogMock = mock(() => {});
304
308
  mock.module("../daemon/conversation-usage.js", () => ({
305
309
  recordUsage: recordUsageMock,
306
310
  }));
@@ -349,12 +353,23 @@ mock.module("../workspace/git-service.js", () => ({
349
353
  }));
350
354
 
351
355
  mock.module("../daemon/conversation-error.js", () => ({
352
- classifyConversationError: (_err: unknown, _ctx: unknown) => ({
353
- code: "CONVERSATION_PROCESSING_FAILED",
354
- userMessage: "Something went wrong processing your message.",
355
- retryable: false,
356
- errorCategory: "processing_failed",
357
- }),
356
+ classifyConversationError: (err: unknown, _ctx: unknown) => {
357
+ const message = err instanceof Error ? err.message : String(err);
358
+ if (/context.?length.?exceeded/i.test(message)) {
359
+ return {
360
+ code: "CONTEXT_TOO_LARGE",
361
+ userMessage: "Context too large.",
362
+ retryable: false,
363
+ errorCategory: "context_too_large",
364
+ };
365
+ }
366
+ return {
367
+ code: "CONVERSATION_PROCESSING_FAILED",
368
+ userMessage: "Something went wrong processing your message.",
369
+ retryable: false,
370
+ errorCategory: "processing_failed",
371
+ };
372
+ },
358
373
  isUserCancellation: (err: unknown, ctx: { aborted?: boolean }) => {
359
374
  if (!ctx.aborted) return false;
360
375
  if (err instanceof DOMException && err.name === "AbortError") return true;
@@ -394,6 +409,7 @@ mock.module("../agent/message-types.js", () => ({
394
409
  mock.module("../memory/llm-request-log-store.js", () => ({
395
410
  recordRequestLog: () => {},
396
411
  backfillMessageIdOnLogs: () => {},
412
+ setAgentLoopExitReasonOnLatestLog: setAgentLoopExitReasonOnLatestLogMock,
397
413
  }));
398
414
 
399
415
  mock.module("../memory/archive-store.js", () => ({
@@ -616,6 +632,7 @@ beforeEach(() => {
616
632
  mockOverflowAction = "fail_gracefully";
617
633
  mockApplyRuntimeInjections = (msgs) => msgs;
618
634
  recordUsageMock.mockClear();
635
+ setAgentLoopExitReasonOnLatestLogMock.mockClear();
619
636
  // Reset the plugin registry and re-register every default so the
620
637
  // orchestrator's pipelines (`overflowReduce`, `persistence`, …) dispatch to
621
638
  // the default middleware, which in turn hits the mocked collaborators
@@ -1907,6 +1924,10 @@ describe("session-agent-loop overflow recovery (JARVIS-110)", () => {
1907
1924
  // After exhausting mid-loop attempts, the convergence loop should
1908
1925
  // have been triggered (contextTooLargeDetected set to true)
1909
1926
  expect(convergenceReducerCalled).toBe(true);
1927
+ expect(setAgentLoopExitReasonOnLatestLogMock).toHaveBeenCalledWith(
1928
+ "test-conv",
1929
+ "context_too_large",
1930
+ );
1910
1931
  });
1911
1932
 
1912
1933
  // ── Test 9 ────────────────────────────────────────────────────────
@@ -2068,6 +2089,10 @@ describe("session-agent-loop overflow recovery (JARVIS-110)", () => {
2068
2089
 
2069
2090
  // Agent loop: 1 initial + 3 mid-loop re-entries + 2 convergence re-runs = 6 calls
2070
2091
  expect(agentLoopCallCount).toBe(6);
2092
+ expect(setAgentLoopExitReasonOnLatestLogMock).toHaveBeenCalledWith(
2093
+ "test-conv",
2094
+ "context_too_large",
2095
+ );
2071
2096
  });
2072
2097
 
2073
2098
  // ── Test 8 ────────────────────────────────────────────────────────
@@ -283,6 +283,7 @@ let mockSlackChronologicalContext: {
283
283
  renderedMessages: Array<{
284
284
  message: Message;
285
285
  sourceChannelTs: string | null;
286
+ tagLineProvenance: "none" | "slack-reaction" | "slack-timezone-message";
286
287
  }>;
287
288
  messages: Message[];
288
289
  compactableStartIndex: number;
@@ -386,6 +387,8 @@ mock.module("../daemon/history-repair.js", () => ({
386
387
 
387
388
  const recordUsageMock = mock(() => {});
388
389
  const recordRequestLogMock = mock(() => {});
390
+ const backfillMessageIdOnLogsMock = mock(() => {});
391
+ const setAgentLoopExitReasonOnLatestLogMock = mock(() => {});
389
392
  mock.module("../daemon/conversation-usage.js", () => ({
390
393
  recordUsage: recordUsageMock,
391
394
  }));
@@ -482,7 +485,8 @@ mock.module("../memory/archive-store.js", () => ({
482
485
 
483
486
  mock.module("../memory/llm-request-log-store.js", () => ({
484
487
  recordRequestLog: recordRequestLogMock,
485
- backfillMessageIdOnLogs: () => {},
488
+ backfillMessageIdOnLogs: backfillMessageIdOnLogsMock,
489
+ setAgentLoopExitReasonOnLatestLog: setAgentLoopExitReasonOnLatestLogMock,
486
490
  }));
487
491
 
488
492
  let mockHasProactiveArtifactCompleted = true;
@@ -658,6 +662,8 @@ beforeEach(() => {
658
662
  mockInjectionBlocks = {};
659
663
  recordUsageMock.mockClear();
660
664
  recordRequestLogMock.mockClear();
665
+ backfillMessageIdOnLogsMock.mockClear();
666
+ setAgentLoopExitReasonOnLatestLogMock.mockClear();
661
667
  syncMessageToDiskMock.mockClear();
662
668
  rebuildConversationDiskViewFromDbStateMock.mockClear();
663
669
  updateMessageMetadataMock.mockClear();
@@ -2298,6 +2304,10 @@ describe("session-agent-loop", () => {
2298
2304
 
2299
2305
  const handoff = events.find((e) => e.type === "generation_handoff");
2300
2306
  expect(handoff).toBeDefined();
2307
+ expect(setAgentLoopExitReasonOnLatestLogMock).toHaveBeenCalledWith(
2308
+ "test-conv",
2309
+ "checkpoint_handoff",
2310
+ );
2301
2311
  });
2302
2312
 
2303
2313
  test("continues when canHandoffAtCheckpoint returns false", async () => {
@@ -2357,6 +2367,10 @@ describe("session-agent-loop", () => {
2357
2367
 
2358
2368
  const handoff = events.find((e) => e.type === "generation_handoff");
2359
2369
  expect(handoff).toBeUndefined();
2370
+ expect(setAgentLoopExitReasonOnLatestLogMock).not.toHaveBeenCalledWith(
2371
+ "test-conv",
2372
+ "checkpoint_handoff",
2373
+ );
2360
2374
  const complete = events.find((e) => e.type === "message_complete");
2361
2375
  expect(complete).toBeDefined();
2362
2376
  });
@@ -2855,6 +2869,57 @@ describe("session-agent-loop", () => {
2855
2869
  );
2856
2870
  expect(conversationErrors.length).toBeGreaterThanOrEqual(1);
2857
2871
  });
2872
+
2873
+ test("pipes synthetic assistant message id into provider-error log rows via backfill", async () => {
2874
+ // Codex P1 regression test: the provider-failure turn must not leave
2875
+ // its `llm_request_logs` row orphaned. Without the backfill call in
2876
+ // the synthetic-message branch, a later turn's `handleMessageComplete`
2877
+ // sweep would wrong-attach this row to the wrong assistant message.
2878
+ const events: ServerMessage[] = [];
2879
+
2880
+ const agentLoopRun: AgentLoopRun = async (messages, onEvent) => {
2881
+ // 1) handleProviderError -> writes an `llm_request_logs` row with
2882
+ // messageId=null (the orphan we are trying to link).
2883
+ onEvent({
2884
+ type: "provider_error",
2885
+ error: new Error("upstream 500"),
2886
+ rawRequest: { model: "gpt-4.1", messages: [] },
2887
+ actualProvider: "openai",
2888
+ });
2889
+ // 2) handleError -> sets `state.providerErrorUserMessage`, which
2890
+ // activates the synthetic-message branch below the loop.
2891
+ onEvent({
2892
+ type: "error",
2893
+ error: new Error("upstream 500"),
2894
+ });
2895
+ // Provider returned no assistant content — same messages back.
2896
+ return messages;
2897
+ };
2898
+
2899
+ const ctx = makeCtx({ agentLoopRun });
2900
+ await runAgentLoopImpl(ctx, "hello", "msg-1", (msg) => events.push(msg));
2901
+
2902
+ // The orphan was written with messageId=undefined.
2903
+ expect(recordRequestLogMock).toHaveBeenCalledTimes(1);
2904
+ const recordCall = recordRequestLogMock.mock.calls[0] as unknown as [
2905
+ string,
2906
+ string,
2907
+ string,
2908
+ string | undefined,
2909
+ string | undefined,
2910
+ ];
2911
+ expect(recordCall[0]).toBe("test-conv");
2912
+ expect(recordCall[3]).toBeUndefined();
2913
+
2914
+ // The synthetic-message branch then piped the assigned message id
2915
+ // (from the mocked `addMessage` -> `{ id: "mock-msg-id" }`) into the
2916
+ // backfill primitive, scoped to this conversation.
2917
+ expect(backfillMessageIdOnLogsMock).toHaveBeenCalledTimes(1);
2918
+ const backfillCall = backfillMessageIdOnLogsMock.mock
2919
+ .calls[0] as unknown as [string, string];
2920
+ expect(backfillCall[0]).toBe("test-conv");
2921
+ expect(backfillCall[1]).toBe("mock-msg-id");
2922
+ });
2858
2923
  });
2859
2924
 
2860
2925
  describe("pkbSystemReminderBlock metadata persistence", () => {
@@ -2967,6 +3032,7 @@ describe("session-agent-loop", () => {
2967
3032
  "1700000020.000000",
2968
3033
  "1700000030.000000",
2969
3034
  ][index]!,
3035
+ tagLineProvenance: "none",
2970
3036
  })),
2971
3037
  compactableStartIndex: 0,
2972
3038
  };
@@ -3066,6 +3132,7 @@ describe("session-agent-loop", () => {
3066
3132
  "1700000020.000000",
3067
3133
  "1700000030.000000",
3068
3134
  ][index]!,
3135
+ tagLineProvenance: "none",
3069
3136
  })),
3070
3137
  compactableStartIndex: 0,
3071
3138
  };
@@ -3182,6 +3249,7 @@ describe("session-agent-loop", () => {
3182
3249
  "1700000030.000000",
3183
3250
  "1700000040.000000",
3184
3251
  ][index]!,
3252
+ tagLineProvenance: "none",
3185
3253
  })),
3186
3254
  compactableStartIndex: 0,
3187
3255
  };
@@ -3287,9 +3355,10 @@ describe("session-agent-loop", () => {
3287
3355
  {
3288
3356
  message: firstSummaryMessage,
3289
3357
  sourceChannelTs: null,
3358
+ tagLineProvenance: "none",
3290
3359
  },
3291
- mockSlackChronologicalContext.renderedMessages[2],
3292
- mockSlackChronologicalContext.renderedMessages[3],
3360
+ mockSlackChronologicalContext!.renderedMessages[2],
3361
+ mockSlackChronologicalContext!.renderedMessages[3],
3293
3362
  ],
3294
3363
  messages: firstCompactedMessages,
3295
3364
  compactableStartIndex: 1,
@@ -3328,6 +3397,7 @@ describe("session-agent-loop", () => {
3328
3397
  "1700000020.000000",
3329
3398
  "1700000030.000000",
3330
3399
  ][index]!,
3400
+ tagLineProvenance: "none",
3331
3401
  })),
3332
3402
  compactableStartIndex: 0,
3333
3403
  };
@@ -3485,6 +3555,7 @@ describe("session-agent-loop", () => {
3485
3555
  ],
3486
3556
  },
3487
3557
  sourceChannelTs: null,
3558
+ tagLineProvenance: "none",
3488
3559
  },
3489
3560
  {
3490
3561
  message: {
@@ -3492,6 +3563,7 @@ describe("session-agent-loop", () => {
3492
3563
  content: [{ type: "text", text: "after watermark reply" }],
3493
3564
  },
3494
3565
  sourceChannelTs: "1700000020.000000",
3566
+ tagLineProvenance: "none",
3495
3567
  },
3496
3568
  ],
3497
3569
  compactableStartIndex: 1,
@@ -3586,6 +3658,7 @@ describe("session-agent-loop", () => {
3586
3658
  ],
3587
3659
  },
3588
3660
  sourceChannelTs: null,
3661
+ tagLineProvenance: "none",
3589
3662
  },
3590
3663
  {
3591
3664
  message: {
@@ -3598,6 +3671,7 @@ describe("session-agent-loop", () => {
3598
3671
  ],
3599
3672
  },
3600
3673
  sourceChannelTs: "1700000121.000000",
3674
+ tagLineProvenance: "none",
3601
3675
  },
3602
3676
  ],
3603
3677
  compactableStartIndex: 1,
@@ -22,7 +22,7 @@ mock.module("../util/logger.js", () => ({
22
22
 
23
23
  mock.module("../providers/registry.js", () => ({
24
24
  getProvider: () => ({ name: "mock-provider" }),
25
- initializeProviders: () => {},
25
+ initializeProviders: async () => {},
26
26
  }));
27
27
 
28
28
  mock.module("../config/loader.js", () => ({