@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
@@ -15,6 +15,7 @@ import { optimizeImageForTransport } from "../agent/image-optimize.js";
15
15
  import type {
16
16
  AgentEvent,
17
17
  AgentLoop,
18
+ AgentLoopExitReason,
18
19
  CheckpointDecision,
19
20
  CheckpointInfo,
20
21
  } from "../agent/loop.js";
@@ -25,11 +26,16 @@ import type {
25
26
  TurnChannelContext,
26
27
  TurnInterfaceContext,
27
28
  } from "../channels/types.js";
29
+ import { isAssistantFeatureFlagEnabled } from "../config/assistant-feature-flags.js";
28
30
  import {
29
31
  contextWindowConfigFromEffective,
32
+ type EffectiveContextWindow,
30
33
  resolveEffectiveContextWindow,
31
34
  } from "../config/llm-context-resolution.js";
32
- import { resolveCallSiteConfig } from "../config/llm-resolver.js";
35
+ import {
36
+ resolveCallSiteConfig,
37
+ resolveDefaultProfileKey,
38
+ } from "../config/llm-resolver.js";
33
39
  import { getConfig } from "../config/loader.js";
34
40
  import type { LLMCallSite } from "../config/schemas/llm.js";
35
41
  import type { ContextWindowConfig } from "../config/types.js";
@@ -62,6 +68,7 @@ import {
62
68
  getLastUserTimestampBefore,
63
69
  getMessageById,
64
70
  provenanceFromTrustContext,
71
+ setLastNotifiedInferenceProfile,
65
72
  updateConversationContextWindow,
66
73
  updateConversationSlackContextWatermark,
67
74
  } from "../memory/conversation-crud.js";
@@ -71,7 +78,9 @@ import {
71
78
  isReplaceableTitle,
72
79
  queueRegenerateConversationTitle,
73
80
  } from "../memory/conversation-title-service.js";
81
+ import { isBackgroundConversationType } from "../memory/conversation-types.js";
74
82
  import type { ConversationGraphMemory } from "../memory/graph/conversation-graph-memory.js";
83
+ import { backfillMessageIdOnLogs } from "../memory/llm-request-log-store.js";
75
84
  import { recordMemoryRecallLog } from "../memory/memory-recall-log-store.js";
76
85
  import { enqueueMemoryRetrospectiveOnCompaction } from "../memory/memory-retrospective-enqueue.js";
77
86
  import { PKB_WORKSPACE_SCOPE } from "../memory/pkb/types.js";
@@ -109,6 +118,7 @@ import type {
109
118
  MemoryResult,
110
119
  OverflowReduceArgs,
111
120
  OverflowReduceResult,
121
+ PersistAddResult,
112
122
  PersistArgs,
113
123
  PersistResult,
114
124
  TurnContext as PluginTurnContext,
@@ -212,6 +222,10 @@ import {
212
222
  SYNC_TAGS,
213
223
  } from "./message-types/sync.js";
214
224
  import { parseActualTokensFromError } from "./parse-actual-tokens-from-error.js";
225
+ import {
226
+ classifyQueryComplexity,
227
+ complexityTierToProfileKey,
228
+ } from "./query-complexity-router.js";
215
229
  import type { TraceEmitter } from "./trace-emitter.js";
216
230
  import type { TrustContext } from "./trust-context.js";
217
231
  import { stripHistoricalWebSearchResults } from "./web-search-history.js";
@@ -518,12 +532,11 @@ export interface AgentLoopConversationContext {
518
532
  /** Per-turn snapshot of channelCapabilities, frozen at message-processing start. */
519
533
  currentTurnChannelCapabilities?: ChannelCapabilities;
520
534
  /**
521
- * Per-turn snapshot of the resolved inference-profile override. Read by
535
+ * Current inference-profile override for this turn. Read by
522
536
  * `createToolExecutor` so `ToolContext.overrideProfile` carries the same
523
- * profile the agent loop is sending to the provider. Without this, a tool
524
- * that spawns nested subagents (e.g. `subagent_spawn`) cannot recover the
525
- * override from a row read because the in-flight subagent's own row never
526
- * had `inferenceProfile` set.
537
+ * profile the agent loop is sending to the provider. Refreshed between
538
+ * model calls so an explicitly confirmed profile session opened mid-turn
539
+ * is inherited by later tool executions and nested subagents.
527
540
  */
528
541
  currentTurnOverrideProfile?: string;
529
542
  commandIntent?: { type: string; payload?: string; languageCode?: string };
@@ -533,7 +546,6 @@ export interface AgentLoopConversationContext {
533
546
  assistantId?: string;
534
547
  voiceCallControlPrompt?: string;
535
548
  transportHints?: string[];
536
- slackRuntimeContextNotice?: string;
537
549
  clientTimezone?: string;
538
550
 
539
551
  readonly coreToolNames: Set<string>;
@@ -667,6 +679,11 @@ export async function runAgentLoopImpl(
667
679
  requestId: reqId,
668
680
  });
669
681
  let yieldedForHandoff = false;
682
+ let yieldedForBudget = false;
683
+ let pendingCheckpointYield: "budget" | "handoff" | null = null;
684
+ let emitTerminalExit:
685
+ | ((reason: AgentLoopExitReason) => Promise<void>)
686
+ | null = null;
670
687
 
671
688
  // Default user-initiated turns to the `mainAgent` call site. Other
672
689
  // invocation contexts (heartbeat, filing, analyze, etc.) pass their own
@@ -688,31 +705,134 @@ export async function runAgentLoopImpl(
688
705
  // spawned subagent's background conversation) wins over the row read
689
706
  // so the agent loop's own background-skip rule doesn't zero out an
690
707
  // explicitly inherited override.
691
- const turnOverrideProfile =
708
+ const userExplicitOverride =
692
709
  options?.overrideProfile ??
693
710
  getConversationOverrideProfileFromRow(turnStartConversation);
694
711
 
695
712
  const config = getConfig();
713
+
714
+ // Query complexity routing: when no explicit user override is set and the
715
+ // feature flag is enabled, classify the query and route to the appropriate
716
+ // profile for this turn. The override is ephemeral (not persisted).
717
+ let turnOverrideProfile = userExplicitOverride;
718
+ if (
719
+ !userExplicitOverride &&
720
+ turnCallSite === "mainAgent" &&
721
+ isAssistantFeatureFlagEnabled("query-complexity-routing", config)
722
+ ) {
723
+ const tier = await classifyQueryComplexity(content);
724
+ if (tier && tier !== "balanced") {
725
+ const routedProfile = complexityTierToProfileKey(tier);
726
+ if (config.llm.profiles?.[routedProfile]) {
727
+ turnOverrideProfile = routedProfile;
728
+ }
729
+ }
730
+ }
731
+
732
+ // Notify clients when the auto-router selected a non-default profile.
733
+ if (turnOverrideProfile && turnOverrideProfile !== userExplicitOverride) {
734
+ const profileEntry = config.llm.profiles?.[turnOverrideProfile];
735
+ const label = profileEntry?.label ?? turnOverrideProfile;
736
+ broadcastMessage({
737
+ type: "turn_profile_auto_routed",
738
+ conversationId: ctx.conversationId,
739
+ profile: turnOverrideProfile,
740
+ profileLabel: label,
741
+ });
742
+ }
743
+
744
+ // Only use the complexity-routed profile as a fallback — not the initial
745
+ // explicit override. If a mid-turn session expiry clears the conversation
746
+ // override, the old behavior (return undefined → revert to workspace
747
+ // defaults) must be preserved for non-routed turns.
748
+ const complexityRoutedProfile =
749
+ turnOverrideProfile !== userExplicitOverride
750
+ ? turnOverrideProfile
751
+ : undefined;
752
+ const readCurrentOverrideProfile = (): string | undefined =>
753
+ options?.overrideProfile ??
754
+ getConversationOverrideProfileFromRow(
755
+ getConversation(ctx.conversationId),
756
+ ) ??
757
+ complexityRoutedProfile;
758
+
696
759
  const effectiveContextWindow = resolveEffectiveContextWindow({
697
760
  llm: config.llm,
698
761
  callSite: turnCallSite,
699
762
  overrideProfile: turnOverrideProfile ?? undefined,
700
763
  });
701
- const turnContextWindowConfig = contextWindowConfigFromEffective(
764
+ let currentEffectiveContextWindow: EffectiveContextWindow =
765
+ effectiveContextWindow;
766
+ let currentContextWindowConfig = contextWindowConfigFromEffective(
702
767
  resolveCallSiteConfig(turnCallSite, config.llm, {
703
768
  overrideProfile: turnOverrideProfile ?? undefined,
704
769
  }).contextWindow,
705
- effectiveContextWindow,
770
+ currentEffectiveContextWindow,
706
771
  );
707
- (
772
+ const contextWindowManager =
708
773
  ctx.contextWindowManager as ContextWindowManager & {
709
774
  updateConfig?: (config: ContextWindowConfig) => void;
775
+ };
776
+ contextWindowManager.updateConfig?.(currentContextWindowConfig);
777
+
778
+ let appliedOverrideProfile = turnOverrideProfile;
779
+ const refreshCurrentProfileState = (): string | undefined => {
780
+ const currentOverrideProfile = readCurrentOverrideProfile();
781
+ if (currentOverrideProfile !== appliedOverrideProfile) {
782
+ currentEffectiveContextWindow = resolveEffectiveContextWindow({
783
+ llm: config.llm,
784
+ callSite: turnCallSite,
785
+ overrideProfile: currentOverrideProfile,
786
+ });
787
+ currentContextWindowConfig = contextWindowConfigFromEffective(
788
+ resolveCallSiteConfig(turnCallSite, config.llm, {
789
+ overrideProfile: currentOverrideProfile,
790
+ }).contextWindow,
791
+ currentEffectiveContextWindow,
792
+ );
793
+ contextWindowManager.updateConfig?.(currentContextWindowConfig);
794
+ appliedOverrideProfile = currentOverrideProfile;
795
+ rlog.info(
796
+ { overrideProfile: currentOverrideProfile ?? null },
797
+ "Turn inference profile changed mid-loop",
798
+ );
710
799
  }
711
- ).updateConfig?.(turnContextWindowConfig);
800
+ ctx.currentTurnOverrideProfile = currentOverrideProfile;
801
+ return currentOverrideProfile;
802
+ };
803
+ const resolveCurrentOverrideProfile = (): string | undefined =>
804
+ refreshCurrentProfileState();
805
+ const resolveCurrentMaxInputTokens = (): number => {
806
+ refreshCurrentProfileState();
807
+ return currentEffectiveContextWindow.maxInputTokens;
808
+ };
809
+ const resolveCurrentContextWindowConfig = (): ContextWindowConfig => {
810
+ refreshCurrentProfileState();
811
+ return currentContextWindowConfig;
812
+ };
813
+ const resolveCurrentContextBudget = (): {
814
+ overflowRecovery: EffectiveContextWindow["overflowRecovery"];
815
+ providerMaxTokens: number;
816
+ preflightBudget: number;
817
+ } => {
818
+ refreshCurrentProfileState();
819
+ const overflowRecovery = currentEffectiveContextWindow.overflowRecovery;
820
+ const providerMaxTokens = currentEffectiveContextWindow.maxInputTokens;
821
+ const baseSafetyMargin = overflowRecovery.safetyMarginRatio;
822
+ const messageCount = ctx.messages.length;
823
+ const safetyMargin =
824
+ messageCount > 50 ? Math.max(baseSafetyMargin, 0.15) : baseSafetyMargin;
825
+ return {
826
+ overflowRecovery,
827
+ providerMaxTokens,
828
+ preflightBudget: Math.floor(providerMaxTokens * (1 - safetyMargin)),
829
+ };
830
+ };
712
831
 
713
- // Snapshot for `createToolExecutor` to read into `ToolContext.overrideProfile`
714
- // see field doc on `AgentLoopConversationContext` for why the tool needs
715
- // it (nested subagent spawns can't recover the override from a row read).
832
+ // Initial value for `createToolExecutor` to read into
833
+ // `ToolContext.overrideProfile`. `resolveCurrentOverrideProfile` refreshes
834
+ // this between model calls so a confirmed profile session opened by a tool
835
+ // applies to later tool executions and nested subagents in the same turn.
716
836
  ctx.currentTurnOverrideProfile = turnOverrideProfile;
717
837
 
718
838
  // Capture the turn channel context *before* any awaits so a second
@@ -1026,6 +1146,7 @@ export async function runAgentLoopImpl(
1026
1146
  {
1027
1147
  message: result.messages[0]!,
1028
1148
  sourceChannelTs: null,
1149
+ tagLineProvenance: "none",
1029
1150
  },
1030
1151
  ...retainedRenderedMessages,
1031
1152
  ],
@@ -1085,7 +1206,7 @@ export async function runAgentLoopImpl(
1085
1206
  precomputedEstimate: compactCheck.estimatedTokens,
1086
1207
  conversationOriginChannel:
1087
1208
  getConversationOriginChannel(ctx.conversationId) ?? undefined,
1088
- overrideProfile: turnOverrideProfile ?? null,
1209
+ overrideProfile: resolveCurrentOverrideProfile() ?? null,
1089
1210
  };
1090
1211
  let compacted: Awaited<
1091
1212
  ReturnType<typeof ctx.contextWindowManager.maybeCompact>
@@ -1450,6 +1571,26 @@ export async function runAgentLoopImpl(
1450
1571
  }
1451
1572
  }
1452
1573
 
1574
+ // Resolve the effective profile key for this turn and detect changes.
1575
+ // Only inject model_profile into the turn context when the profile
1576
+ // changed since the last turn (or on the first turn of a conversation)
1577
+ // to avoid per-turn token cost.
1578
+ const effectiveProfileKey =
1579
+ turnOverrideProfile ??
1580
+ config.llm.activeProfile ??
1581
+ resolveDefaultProfileKey("mainAgent", config.llm);
1582
+ const lastNotified = turnStartConversation?.lastNotifiedInferenceProfile;
1583
+ let modelProfileStr: string | null = null;
1584
+ if (effectiveProfileKey != null && effectiveProfileKey !== lastNotified) {
1585
+ const profileEntry = config.llm.profiles?.[effectiveProfileKey];
1586
+ const resolved = resolveCallSiteConfig(turnCallSite, config.llm, {
1587
+ overrideProfile: turnOverrideProfile ?? undefined,
1588
+ });
1589
+ const label = profileEntry?.label ?? effectiveProfileKey;
1590
+ modelProfileStr = resolved.model ? `${label} (${resolved.model})` : label;
1591
+ setLastNotifiedInferenceProfile(ctx.conversationId, effectiveProfileKey);
1592
+ }
1593
+
1453
1594
  const baseTurnContext = {
1454
1595
  timestamp,
1455
1596
  interfaceName,
@@ -1458,6 +1599,7 @@ export async function runAgentLoopImpl(
1458
1599
  clientTimezone: timezoneContext.clientTimezone,
1459
1600
  detectedTimezone: timezoneContext.detectedTimezone,
1460
1601
  timeSinceLastMessage,
1602
+ modelProfile: modelProfileStr,
1461
1603
  };
1462
1604
  const unifiedTurnContextStr = buildUnifiedTurnContextBlock(
1463
1605
  isGuardian
@@ -1614,8 +1756,10 @@ export async function runAgentLoopImpl(
1614
1756
  nowScratchpad,
1615
1757
  voiceCallControlPrompt: ctx.voiceCallControlPrompt ?? null,
1616
1758
  transportHints: ctx.transportHints ?? null,
1617
- slackRuntimeContextNotice: ctx.slackRuntimeContextNotice ?? null,
1618
1759
  isNonInteractive: !isInteractiveResolved,
1760
+ isBackgroundConversation: isBackgroundConversationType(
1761
+ turnStartConversation?.conversationType,
1762
+ ),
1619
1763
  subagentStatusBlock,
1620
1764
  slackChronologicalMessages,
1621
1765
  slackActiveThreadFocusBlock,
@@ -1698,15 +1842,9 @@ export async function runAgentLoopImpl(
1698
1842
  // After runtime injections are applied, estimate the prompt token count
1699
1843
  // and proactively invoke the reducer if already above budget. This avoids
1700
1844
  // a wasted provider round-trip that would just fail with context_too_large.
1701
- const overflowRecovery = effectiveContextWindow.overflowRecovery;
1702
- const providerMaxTokens = effectiveContextWindow.maxInputTokens;
1703
- // Widen safety margin for large conversations where estimation error
1704
- // compounds across many messages with tool results.
1705
- const baseSafetyMargin = overflowRecovery.safetyMarginRatio;
1706
- const messageCount = ctx.messages.length;
1707
- const safetyMargin =
1708
- messageCount > 50 ? Math.max(baseSafetyMargin, 0.15) : baseSafetyMargin;
1709
- const preflightBudget = Math.floor(providerMaxTokens * (1 - safetyMargin));
1845
+ const initialContextBudget = resolveCurrentContextBudget();
1846
+ const overflowRecovery = initialContextBudget.overflowRecovery;
1847
+ const preflightBudget = initialContextBudget.preflightBudget;
1710
1848
  let reducerState: ReducerState | undefined;
1711
1849
 
1712
1850
  const toolTokenBudget = ctx.agentLoop.getToolTokenBudget(runMessages);
@@ -1783,10 +1921,10 @@ export async function runAgentLoopImpl(
1783
1921
  runMessages,
1784
1922
  systemPrompt: ctx.systemPrompt,
1785
1923
  providerName: estimationProviderName,
1786
- contextWindow: turnContextWindowConfig,
1924
+ contextWindow: resolveCurrentContextWindowConfig(),
1787
1925
  preflightBudget,
1788
1926
  toolTokenBudget,
1789
- maxAttempts: overflowRecovery.maxAttempts,
1927
+ maxAttempts: resolveCurrentContextBudget().overflowRecovery.maxAttempts,
1790
1928
  abortSignal: abortController.signal,
1791
1929
  compactFn: async (msgs, signal, opts) => {
1792
1930
  // Route the reducer's forced-compaction tier through the
@@ -1816,7 +1954,7 @@ export async function runAgentLoopImpl(
1816
1954
  signal,
1817
1955
  options: {
1818
1956
  ...(opts ?? {}),
1819
- overrideProfile: turnOverrideProfile ?? null,
1957
+ overrideProfile: resolveCurrentOverrideProfile() ?? null,
1820
1958
  },
1821
1959
  },
1822
1960
  buildPluginTurnContext(ctx, reqId),
@@ -2070,8 +2208,9 @@ export async function runAgentLoopImpl(
2070
2208
  };
2071
2209
  const eventHandler = (event: AgentEvent) =>
2072
2210
  dispatchAgentEvent(state, deps, event);
2073
-
2074
- let yieldedForBudget = false;
2211
+ emitTerminalExit = async (reason: AgentLoopExitReason): Promise<void> => {
2212
+ await eventHandler({ type: "agent_loop_exit", reason });
2213
+ };
2075
2214
 
2076
2215
  const onCheckpoint = async (
2077
2216
  checkpoint: CheckpointInfo,
@@ -2080,6 +2219,7 @@ export async function runAgentLoopImpl(
2080
2219
 
2081
2220
  if (ctx.canHandoffAtCheckpoint()) {
2082
2221
  yieldedForHandoff = true;
2222
+ pendingCheckpointYield = "handoff";
2083
2223
  return "yield";
2084
2224
  }
2085
2225
 
@@ -2087,7 +2227,8 @@ export async function runAgentLoopImpl(
2087
2227
  // yield if we're approaching the preflight budget. This lets the
2088
2228
  // conversation-agent-loop run compaction before the provider rejects.
2089
2229
  if (overflowRecovery.enabled) {
2090
- const midLoopThreshold = preflightBudget * 0.85;
2230
+ const midLoopThreshold =
2231
+ resolveCurrentContextBudget().preflightBudget * 0.85;
2091
2232
  const estimated = await runTokenEstimatePipeline(checkpoint.history);
2092
2233
  if (estimated > midLoopThreshold) {
2093
2234
  rlog.warn(
@@ -2095,6 +2236,7 @@ export async function runAgentLoopImpl(
2095
2236
  "Token estimate approaching budget — yielding for compaction",
2096
2237
  );
2097
2238
  yieldedForBudget = true;
2239
+ pendingCheckpointYield = "budget";
2098
2240
  return "yield";
2099
2241
  }
2100
2242
  }
@@ -2123,7 +2265,9 @@ export async function runAgentLoopImpl(
2123
2265
  turnCallSite,
2124
2266
  loopTurnCtx,
2125
2267
  turnOverrideProfile,
2126
- effectiveContextWindow.maxInputTokens,
2268
+ resolveCurrentMaxInputTokens(),
2269
+ resolveCurrentOverrideProfile,
2270
+ resolveCurrentMaxInputTokens,
2127
2271
  );
2128
2272
 
2129
2273
  rlog.info(
@@ -2131,6 +2275,11 @@ export async function runAgentLoopImpl(
2131
2275
  "Agent loop run completed",
2132
2276
  );
2133
2277
 
2278
+ if (yieldedForHandoff) {
2279
+ await emitTerminalExit?.("checkpoint_handoff");
2280
+ pendingCheckpointYield = null;
2281
+ }
2282
+
2134
2283
  // ── Proactive mid-loop compaction ───────────────────────────────
2135
2284
  // When the agent loop yielded because the token budget check in
2136
2285
  // onCheckpoint detected approaching limits, run compaction on the
@@ -2140,12 +2289,14 @@ export async function runAgentLoopImpl(
2140
2289
  let midLoopCompactAttempts = 0;
2141
2290
  while (
2142
2291
  yieldedForBudget &&
2143
- midLoopCompactAttempts < overflowRecovery.maxAttempts &&
2292
+ midLoopCompactAttempts <
2293
+ resolveCurrentContextBudget().overflowRecovery.maxAttempts &&
2144
2294
  !state.contextTooLargeDetected &&
2145
2295
  !abortController.signal.aborted
2146
2296
  ) {
2147
2297
  midLoopCompactAttempts++;
2148
2298
  yieldedForBudget = false;
2299
+ pendingCheckpointYield = null;
2149
2300
 
2150
2301
  rlog.info(
2151
2302
  { phase: "mid-loop-compact" },
@@ -2187,10 +2338,11 @@ export async function runAgentLoopImpl(
2187
2338
  options: {
2188
2339
  lastCompactedAt: ctx.contextCompactedAt ?? undefined,
2189
2340
  force: true,
2190
- targetInputTokensOverride: preflightBudget,
2341
+ targetInputTokensOverride:
2342
+ resolveCurrentContextBudget().preflightBudget,
2191
2343
  conversationOriginChannel:
2192
2344
  getConversationOriginChannel(ctx.conversationId) ?? undefined,
2193
- overrideProfile: turnOverrideProfile ?? null,
2345
+ overrideProfile: resolveCurrentOverrideProfile() ?? null,
2194
2346
  },
2195
2347
  },
2196
2348
  buildPluginTurnContext(ctx, reqId),
@@ -2276,7 +2428,9 @@ export async function runAgentLoopImpl(
2276
2428
  turnCallSite,
2277
2429
  loopTurnCtx,
2278
2430
  turnOverrideProfile,
2279
- effectiveContextWindow.maxInputTokens,
2431
+ resolveCurrentMaxInputTokens(),
2432
+ resolveCurrentOverrideProfile,
2433
+ resolveCurrentMaxInputTokens,
2280
2434
  );
2281
2435
  }
2282
2436
 
@@ -2290,7 +2444,8 @@ export async function runAgentLoopImpl(
2290
2444
  {
2291
2445
  phase: "mid-loop-compact",
2292
2446
  midLoopCompactAttempts,
2293
- maxAttempts: overflowRecovery.maxAttempts,
2447
+ maxAttempts:
2448
+ resolveCurrentContextBudget().overflowRecovery.maxAttempts,
2294
2449
  },
2295
2450
  "Mid-loop compaction exhausted all attempts — escalating to convergence loop",
2296
2451
  );
@@ -2333,7 +2488,9 @@ export async function runAgentLoopImpl(
2333
2488
  turnCallSite,
2334
2489
  loopTurnCtx,
2335
2490
  turnOverrideProfile,
2336
- effectiveContextWindow.maxInputTokens,
2491
+ resolveCurrentMaxInputTokens(),
2492
+ resolveCurrentOverrideProfile,
2493
+ resolveCurrentMaxInputTokens,
2337
2494
  );
2338
2495
 
2339
2496
  if (state.orderingErrorDetected) {
@@ -2402,7 +2559,9 @@ export async function runAgentLoopImpl(
2402
2559
  turnCallSite,
2403
2560
  loopTurnCtx,
2404
2561
  turnOverrideProfile,
2405
- effectiveContextWindow.maxInputTokens,
2562
+ resolveCurrentMaxInputTokens(),
2563
+ resolveCurrentOverrideProfile,
2564
+ resolveCurrentMaxInputTokens,
2406
2565
  );
2407
2566
  if (state.imageTooLargeDetected) {
2408
2567
  rlog.error(
@@ -2473,18 +2632,21 @@ export async function runAgentLoopImpl(
2473
2632
  toolTokenBudget,
2474
2633
  },
2475
2634
  );
2476
- let correctedTarget = preflightBudget;
2635
+ const convergenceBudget = resolveCurrentContextBudget();
2636
+ let correctedTarget = convergenceBudget.preflightBudget;
2477
2637
  if (actualTokens && estimatedTokensAtOverflow > 0) {
2478
2638
  const estimationErrorRatio = actualTokens / estimatedTokensAtOverflow;
2479
2639
  if (estimationErrorRatio > 1.0) {
2480
- correctedTarget = Math.floor(preflightBudget / estimationErrorRatio);
2640
+ correctedTarget = Math.floor(
2641
+ convergenceBudget.preflightBudget / estimationErrorRatio,
2642
+ );
2481
2643
  rlog.warn(
2482
2644
  {
2483
2645
  phase: "convergence",
2484
2646
  actualTokens,
2485
2647
  estimatedTokens: estimatedTokensAtOverflow,
2486
2648
  estimationErrorRatio: estimationErrorRatio.toFixed(2),
2487
- preflightBudget,
2649
+ preflightBudget: convergenceBudget.preflightBudget,
2488
2650
  correctedTarget,
2489
2651
  },
2490
2652
  "Adjusting compaction target based on observed estimation error",
@@ -2509,11 +2671,11 @@ export async function runAgentLoopImpl(
2509
2671
  systemPrompt: ctx.systemPrompt,
2510
2672
  tools: undefined,
2511
2673
  compaction: emergencyConfig,
2512
- maxInputTokens: effectiveContextWindow.maxInputTokens,
2674
+ maxInputTokens: resolveCurrentMaxInputTokens(),
2513
2675
  previousEstimatedInputTokens: estimatedTokensAtOverflow,
2514
2676
  force: true,
2515
2677
  signal: abortController.signal,
2516
- overrideProfile: turnOverrideProfile ?? null,
2678
+ overrideProfile: resolveCurrentOverrideProfile() ?? null,
2517
2679
  nonPersistedPrefixCount:
2518
2680
  ctx.contextWindowManager.nonPersistedPrefixCount,
2519
2681
  });
@@ -2551,7 +2713,7 @@ export async function runAgentLoopImpl(
2551
2713
  }
2552
2714
 
2553
2715
  let convergenceAttempts = 0;
2554
- const maxAttempts = overflowRecovery.maxAttempts;
2716
+ const maxAttempts = convergenceBudget.overflowRecovery.maxAttempts;
2555
2717
 
2556
2718
  while (
2557
2719
  state.contextTooLargeDetected &&
@@ -2580,7 +2742,7 @@ export async function runAgentLoopImpl(
2580
2742
  {
2581
2743
  providerName: estimationProviderName,
2582
2744
  systemPrompt: ctx.systemPrompt,
2583
- contextWindow: turnContextWindowConfig,
2745
+ contextWindow: resolveCurrentContextWindowConfig(),
2584
2746
  targetTokens: correctedTarget,
2585
2747
  toolTokenBudget,
2586
2748
  },
@@ -2588,7 +2750,7 @@ export async function runAgentLoopImpl(
2588
2750
  (msgs, signal, opts) =>
2589
2751
  ctx.contextWindowManager.maybeCompact(msgs, signal!, {
2590
2752
  ...(opts ?? {}),
2591
- overrideProfile: turnOverrideProfile ?? null,
2753
+ overrideProfile: resolveCurrentOverrideProfile() ?? null,
2592
2754
  }),
2593
2755
  abortController.signal,
2594
2756
  );
@@ -2664,7 +2826,9 @@ export async function runAgentLoopImpl(
2664
2826
  turnCallSite,
2665
2827
  loopTurnCtx,
2666
2828
  turnOverrideProfile,
2667
- effectiveContextWindow.maxInputTokens,
2829
+ resolveCurrentMaxInputTokens(),
2830
+ resolveCurrentOverrideProfile,
2831
+ resolveCurrentMaxInputTokens,
2668
2832
  );
2669
2833
 
2670
2834
  // If the rerun still yields at checkpoint, the turn is still
@@ -2742,7 +2906,7 @@ export async function runAgentLoopImpl(
2742
2906
  force: true,
2743
2907
  minKeepRecentUserTurns: 0,
2744
2908
  targetInputTokensOverride: correctedTarget,
2745
- overrideProfile: turnOverrideProfile ?? null,
2909
+ overrideProfile: resolveCurrentOverrideProfile() ?? null,
2746
2910
  },
2747
2911
  },
2748
2912
  buildPluginTurnContext(ctx, reqId),
@@ -2825,7 +2989,9 @@ export async function runAgentLoopImpl(
2825
2989
  turnCallSite,
2826
2990
  loopTurnCtx,
2827
2991
  turnOverrideProfile,
2828
- effectiveContextWindow.maxInputTokens,
2992
+ resolveCurrentMaxInputTokens(),
2993
+ resolveCurrentOverrideProfile,
2994
+ resolveCurrentMaxInputTokens,
2829
2995
  );
2830
2996
  }
2831
2997
  // action === "fail_gracefully" falls through to the final error below
@@ -2837,6 +3003,8 @@ export async function runAgentLoopImpl(
2837
3003
  new Error("context_length_exceeded"),
2838
3004
  { phase: "agent_loop" },
2839
3005
  );
3006
+ await emitTerminalExit?.("context_too_large");
3007
+ pendingCheckpointYield = null;
2840
3008
  onEvent(buildConversationErrorMessage(ctx.conversationId, classified));
2841
3009
  }
2842
3010
  }
@@ -2942,7 +3110,7 @@ export async function runAgentLoopImpl(
2942
3110
  const errorAssistantMessage = createAssistantMessage(
2943
3111
  state.providerErrorUserMessage,
2944
3112
  );
2945
- await runPipeline<PersistArgs, PersistResult>(
3113
+ const errorPersistResult = (await runPipeline<PersistArgs, PersistResult>(
2946
3114
  "persistence",
2947
3115
  getMiddlewaresFor("persistence"),
2948
3116
  defaultPersistenceTerminal,
@@ -2955,9 +3123,30 @@ export async function runAgentLoopImpl(
2955
3123
  },
2956
3124
  buildPluginTurnContext(ctx, reqId),
2957
3125
  DEFAULT_TIMEOUTS.persistence,
2958
- );
3126
+ )) as PersistAddResult;
2959
3127
  persistedErrorAssistantMessage = true;
2960
3128
  newMessages.push(errorAssistantMessage);
3129
+ // Pipe the just-assigned message id into any orphaned LLM request log
3130
+ // row(s) for this turn. The success path links rows via
3131
+ // `handleMessageComplete` -> `backfillMessageIdOnLogs`, but provider-
3132
+ // failure turns never fire `message_complete` (the synthetic assistant
3133
+ // message is persisted directly above), so without this call the rows
3134
+ // from `handleProviderError` stay with `message_id IS NULL` and a
3135
+ // later turn's backfill sweep would wrong-attach them to that turn's
3136
+ // assistant message. Scope is per-conversation, so concurrent runs on
3137
+ // other conversations cannot collide. Non-fatal — a DB hiccup must
3138
+ // not escalate a provider rejection into a turn-level throw.
3139
+ try {
3140
+ backfillMessageIdOnLogs(
3141
+ ctx.conversationId,
3142
+ errorPersistResult.message.id,
3143
+ );
3144
+ } catch (err) {
3145
+ rlog.warn(
3146
+ { err },
3147
+ "Failed to backfill message_id on provider-error LLM request logs (non-fatal)",
3148
+ );
3149
+ }
2961
3150
  // Do NOT send assistant_text_delta here — handleProviderError already
2962
3151
  // emitted a conversation_error event for this same error text, and the
2963
3152
  // client renders it as an InlineChatErrorAlert. Sending a text delta
@@ -3014,11 +3203,11 @@ export async function runAgentLoopImpl(
3014
3203
  state.exchangeLlmCallCount,
3015
3204
  {
3016
3205
  tokens: state.lastCallInputTokens,
3017
- maxTokens: effectiveContextWindow.maxInputTokens,
3206
+ maxTokens: resolveCurrentMaxInputTokens(),
3018
3207
  },
3019
3208
  {
3020
3209
  callSite: turnCallSite,
3021
- overrideProfile: turnOverrideProfile ?? null,
3210
+ overrideProfile: resolveCurrentOverrideProfile() ?? null,
3022
3211
  },
3023
3212
  );
3024
3213
 
@@ -3078,6 +3267,10 @@ export async function runAgentLoopImpl(
3078
3267
 
3079
3268
  // Re-check: the user may have cancelled during attachment resolution
3080
3269
  if (abortController.signal.aborted) {
3270
+ if (pendingCheckpointYield === "budget") {
3271
+ await emitTerminalExit?.("aborted_after_checkpoint");
3272
+ pendingCheckpointYield = null;
3273
+ }
3081
3274
  ctx.emitActivityState("idle", "generation_cancelled", "global", reqId);
3082
3275
  ctx.traceEmitter.emit(
3083
3276
  "generation_cancelled",
@@ -3219,6 +3412,10 @@ export async function runAgentLoopImpl(
3219
3412
  aborted: abortController.signal.aborted,
3220
3413
  };
3221
3414
  if (isUserCancellation(err, errorCtx)) {
3415
+ if (pendingCheckpointYield === "budget") {
3416
+ await emitTerminalExit?.("aborted_after_checkpoint");
3417
+ pendingCheckpointYield = null;
3418
+ }
3222
3419
  ctx.emitActivityState("idle", "generation_cancelled", "global", reqId);
3223
3420
  rlog.info("Generation cancelled by user");
3224
3421
  ctx.traceEmitter.emit(
@@ -3311,7 +3508,6 @@ export async function runAgentLoopImpl(
3311
3508
  ctx.diskPressureCleanupModeActive = false;
3312
3509
  ctx.preactivatedSkillIds = undefined;
3313
3510
  ctx.currentTurnOverrideProfile = undefined;
3314
- ctx.slackRuntimeContextNotice = undefined;
3315
3511
  // Channel command intents (e.g. Telegram /start) are single-turn metadata.
3316
3512
  // Clear at turn end so they never leak into subsequent unrelated messages.
3317
3513
  ctx.commandIntent = undefined;