@vellumai/assistant 0.8.5 → 0.8.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (544) hide show
  1. package/AGENTS.md +33 -1
  2. package/ARCHITECTURE.md +1 -1
  3. package/bunfig.toml +6 -1
  4. package/docs/credential-execution-service.md +6 -6
  5. package/docs/plugins.md +4 -3
  6. package/node_modules/@vellumai/skill-host-contracts/src/client.ts +12 -13
  7. package/node_modules/@vellumai/skill-host-contracts/src/skill-host.ts +4 -1
  8. package/node_modules/@vellumai/skill-host-contracts/src/tool-types.ts +16 -14
  9. package/openapi.yaml +1900 -166
  10. package/package.json +1 -1
  11. package/src/__tests__/actor-token-service.test.ts +3 -2
  12. package/src/__tests__/agent-loop-exit-reason.test.ts +102 -9
  13. package/src/__tests__/agent-loop-override-profile.test.ts +2 -1
  14. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +1 -0
  15. package/src/__tests__/agent-wake-override-profile.test.ts +1 -0
  16. package/src/__tests__/always-loaded-tools-guard.test.ts +2 -2
  17. package/src/__tests__/annotate-risk-options.test.ts +1 -0
  18. package/src/__tests__/approval-cascade.test.ts +1 -0
  19. package/src/__tests__/approval-routes-http.test.ts +9 -13
  20. package/src/__tests__/assert-not-live-db.ts +79 -0
  21. package/src/__tests__/assistant-feature-flags-integration.test.ts +9 -25
  22. package/src/__tests__/audit-log-rotation.test.ts +2 -2
  23. package/src/__tests__/auto-analysis-end-to-end.test.ts +6 -6
  24. package/src/__tests__/background-workers-disk-pressure.test.ts +5 -8
  25. package/src/__tests__/browser-skill-endstate.test.ts +3 -3
  26. package/src/__tests__/btw-routes.test.ts +3 -2
  27. package/src/__tests__/call-controller.test.ts +3 -2
  28. package/src/__tests__/channel-approval-routes.test.ts +3 -2
  29. package/src/__tests__/channel-guardian.test.ts +3 -2
  30. package/src/__tests__/channel-readiness-slack-remote.test.ts +175 -0
  31. package/src/__tests__/channel-reply-delivery.test.ts +35 -0
  32. package/src/__tests__/channel-retry-sweep.test.ts +320 -3
  33. package/src/__tests__/checker.test.ts +12 -12
  34. package/src/__tests__/compaction-events.test.ts +1 -0
  35. package/src/__tests__/compaction-trail-store.test.ts +264 -0
  36. package/src/__tests__/compactor-call-site-logging.test.ts +1 -0
  37. package/src/__tests__/compactor-preserved-tail-count.test.ts +1 -0
  38. package/src/__tests__/computer-use-skill-manifest-regression.test.ts +7 -5
  39. package/src/__tests__/computer-use-tools.test.ts +12 -14
  40. package/src/__tests__/config-loader-backfill.test.ts +13 -28
  41. package/src/__tests__/config-loader-corrupt.test.ts +5 -5
  42. package/src/__tests__/config-loader-platform-defaults.test.ts +93 -26
  43. package/src/__tests__/config-loader-quarantine-bulletin.test.ts +3 -3
  44. package/src/__tests__/config-managed-gemini-defaults.test.ts +3 -4
  45. package/src/__tests__/config-schema.test.ts +10 -10
  46. package/src/__tests__/connection-model-compat.test.ts +83 -0
  47. package/src/__tests__/contacts-tools.test.ts +3 -2
  48. package/src/__tests__/context-token-estimator.test.ts +22 -0
  49. package/src/__tests__/conversation-abort-tool-results.test.ts +5 -0
  50. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +1 -0
  51. package/src/__tests__/conversation-agent-loop-handlers-max-tokens.test.ts +55 -0
  52. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +1 -0
  53. package/src/__tests__/conversation-agent-loop-overflow.test.ts +34 -0
  54. package/src/__tests__/conversation-agent-loop.test.ts +488 -2
  55. package/src/__tests__/conversation-analysis-routes.test.ts +1 -0
  56. package/src/__tests__/conversation-app-control-instantiation.test.ts +29 -19
  57. package/src/__tests__/conversation-app-control-lifecycle.test.ts +1 -0
  58. package/src/__tests__/conversation-attention-store.test.ts +101 -0
  59. package/src/__tests__/conversation-attention-telegram.test.ts +3 -2
  60. package/src/__tests__/conversation-confirmation-signals.test.ts +1 -0
  61. package/src/__tests__/conversation-error.test.ts +30 -0
  62. package/src/__tests__/conversation-fork-crud.test.ts +69 -8
  63. package/src/__tests__/conversation-fork-route.test.ts +3 -2
  64. package/src/__tests__/conversation-history-web-search.test.ts +1 -0
  65. package/src/__tests__/conversation-inference-profile-list.test.ts +3 -2
  66. package/src/__tests__/conversation-inference-profile-route.test.ts +3 -2
  67. package/src/__tests__/conversation-lifecycle.test.ts +1 -0
  68. package/src/__tests__/conversation-list-source.test.ts +3 -2
  69. package/src/__tests__/conversation-load-history-repair.test.ts +2 -1
  70. package/src/__tests__/conversation-load-history-stripped.test.ts +1 -0
  71. package/src/__tests__/conversation-pairing.test.ts +53 -0
  72. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +26 -7
  73. package/src/__tests__/conversation-process-callsite.test.ts +1 -0
  74. package/src/__tests__/conversation-provider-retry-repair.test.ts +5 -0
  75. package/src/__tests__/conversation-queue.test.ts +333 -291
  76. package/src/__tests__/conversation-routes-disk-view.test.ts +3 -18
  77. package/src/__tests__/conversation-routes-guardian-reply.test.ts +33 -8
  78. package/src/__tests__/conversation-routes-slash-commands.test.ts +33 -2
  79. package/src/__tests__/conversation-runtime-assembly.test.ts +78 -0
  80. package/src/__tests__/conversation-skill-tools.test.ts +38 -142
  81. package/src/__tests__/conversation-slash-queue.test.ts +84 -32
  82. package/src/__tests__/conversation-slash-unknown.test.ts +5 -0
  83. package/src/__tests__/conversation-speed-override.test.ts +1 -0
  84. package/src/__tests__/conversation-surfaces-action-delivery.test.ts +46 -0
  85. package/src/__tests__/conversation-surfaces-data-persist.test.ts +1 -0
  86. package/src/__tests__/conversation-surfaces-standalone-payloads.test.ts +6 -3
  87. package/src/__tests__/conversation-surfaces-standalone.test.ts +6 -3
  88. package/src/__tests__/conversation-surfaces-state-update.test.ts +3 -3
  89. package/src/__tests__/conversation-surfaces-table-action.test.ts +7 -17
  90. package/src/__tests__/conversation-sync-tags.test.ts +128 -12
  91. package/src/__tests__/conversation-title-service.test.ts +1 -0
  92. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +30 -0
  93. package/src/__tests__/conversation-usage.test.ts +1 -0
  94. package/src/__tests__/conversation-workspace-cache-state.test.ts +1 -0
  95. package/src/__tests__/conversation-workspace-injection.test.ts +5 -0
  96. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +5 -0
  97. package/src/__tests__/credential-broker-browser-fill.test.ts +3 -3
  98. package/src/__tests__/credential-broker-server-use.test.ts +5 -5
  99. package/src/__tests__/credential-execution-client.test.ts +72 -1
  100. package/src/__tests__/credential-execution-feature-gates.test.ts +10 -12
  101. package/src/__tests__/credential-health-service.test.ts +252 -3
  102. package/src/__tests__/credential-security-invariants.test.ts +5 -5
  103. package/src/__tests__/credential-vault-unit.test.ts +19 -19
  104. package/src/__tests__/credential-vault.test.ts +5 -5
  105. package/src/__tests__/cross-provider-web-search.test.ts +56 -2
  106. package/src/__tests__/db-connection-isolation.test.ts +7 -6
  107. package/src/__tests__/db-conversation-fork-lineage-migration.test.ts +8 -10
  108. package/src/__tests__/db-conversation-inference-profile-migration.test.ts +7 -10
  109. package/src/__tests__/db-llm-request-log-provider-migration.test.ts +9 -15
  110. package/src/__tests__/db-test-helpers.ts +58 -0
  111. package/src/__tests__/disk-pressure-guard.test.ts +58 -41
  112. package/src/__tests__/disk-pressure-lifecycle.test.ts +13 -10
  113. package/src/__tests__/disk-pressure-routes.test.ts +0 -33
  114. package/src/__tests__/disk-pressure-tools.test.ts +0 -4
  115. package/src/__tests__/dm-persistence.test.ts +26 -40
  116. package/src/__tests__/document-create-dedupe.test.ts +189 -0
  117. package/src/__tests__/document-find-replace.test.ts +3 -2
  118. package/src/__tests__/document-tool-security.test.ts +81 -2
  119. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +5 -4
  120. package/src/__tests__/encrypted-store-test-helpers.ts +56 -0
  121. package/src/__tests__/encrypted-store.test.ts +11 -9
  122. package/src/__tests__/feature-flag-test-helpers.ts +53 -0
  123. package/src/__tests__/filing-service.test.ts +1 -0
  124. package/src/__tests__/first-greeting.test.ts +62 -12
  125. package/src/__tests__/gateway-flag-listener.test.ts +0 -1
  126. package/src/__tests__/gemini-provider.test.ts +26 -0
  127. package/src/__tests__/guardian-action-sweep.test.ts +3 -2
  128. package/src/__tests__/guardian-outbound-http.test.ts +3 -2
  129. package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +48 -3
  130. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +1 -0
  131. package/src/__tests__/heartbeat-disk-pressure.test.ts +1 -0
  132. package/src/__tests__/heartbeat-service.test.ts +1 -0
  133. package/src/__tests__/helpers/mock-logger.ts +26 -0
  134. package/src/__tests__/host-bash-routes.test.ts +1 -0
  135. package/src/__tests__/host-cu-routes-targeted.test.ts +1 -0
  136. package/src/__tests__/host-file-routes-targeted.test.ts +1 -0
  137. package/src/__tests__/host-shell-tool.test.ts +5 -4
  138. package/src/__tests__/host-transfer-routes-targeted.test.ts +1 -0
  139. package/src/__tests__/http-conversation-lineage.test.ts +3 -2
  140. package/src/__tests__/http-user-message-parity.test.ts +29 -7
  141. package/src/__tests__/identity-intro-cache.test.ts +133 -22
  142. package/src/__tests__/inbound-slack-persistence.test.ts +44 -72
  143. package/src/__tests__/inference-profile-reaper.test.ts +3 -2
  144. package/src/__tests__/inference-profile-session-ipc.test.ts +3 -2
  145. package/src/__tests__/injector-disk-pressure.test.ts +3 -17
  146. package/src/__tests__/inline-skill-load-permissions.test.ts +4 -4
  147. package/src/__tests__/list-messages-hidden-metadata.test.ts +80 -0
  148. package/src/__tests__/llm-context-normalization.test.ts +42 -0
  149. package/src/__tests__/llm-resolver.test.ts +331 -0
  150. package/src/__tests__/llm-schema.test.ts +1 -1
  151. package/src/__tests__/manual-token-reconciliation.test.ts +76 -1
  152. package/src/__tests__/mcp-abort-signal.test.ts +14 -0
  153. package/src/__tests__/mcp-client-auth.test.ts +14 -0
  154. package/src/__tests__/messaging-send-tool.test.ts +1 -0
  155. package/src/__tests__/migration-import-from-url.test.ts +3 -3
  156. package/src/__tests__/mock-gateway-ipc.ts +18 -2
  157. package/src/__tests__/model-intents.test.ts +3 -3
  158. package/src/__tests__/native-web-search.test.ts +30 -2
  159. package/src/__tests__/notification-deep-link.test.ts +62 -0
  160. package/src/__tests__/oauth-commands-routes.test.ts +37 -0
  161. package/src/__tests__/oauth-provider-visibility.test.ts +8 -8
  162. package/src/__tests__/oauth-store.test.ts +3 -2
  163. package/src/__tests__/onboarding-template-contract.test.ts +3 -2
  164. package/src/__tests__/openai-provider.test.ts +8 -9
  165. package/src/__tests__/openai-responses-provider.test.ts +70 -10
  166. package/src/__tests__/openrouter-provider-only.test.ts +27 -5
  167. package/src/__tests__/outbound-slack-persistence.test.ts +46 -1
  168. package/src/__tests__/persistence-pipeline.test.ts +139 -1
  169. package/src/__tests__/persistence-secret-redaction.test.ts +83 -12
  170. package/src/__tests__/plugin-bootstrap.test.ts +9 -11
  171. package/src/__tests__/plugin-tool-contribution.test.ts +41 -38
  172. package/src/__tests__/process-message-background-slack.test.ts +21 -16
  173. package/src/__tests__/process-message-display-content.test.ts +19 -22
  174. package/src/__tests__/provider-catalog-visibility.test.ts +9 -9
  175. package/src/__tests__/provider-platform-proxy-integration.test.ts +216 -4
  176. package/src/__tests__/provider-registry-ollama.test.ts +45 -22
  177. package/src/__tests__/recording-handler.test.ts +1 -0
  178. package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +1 -0
  179. package/src/__tests__/registry.test.ts +82 -76
  180. package/src/__tests__/relay-server.test.ts +10 -10
  181. package/src/__tests__/runtime-attachment-metadata.test.ts +3 -2
  182. package/src/__tests__/schedule-store.test.ts +16 -1
  183. package/src/__tests__/scheduler-reuse-conversation.test.ts +48 -3
  184. package/src/__tests__/secret-ingress-http.test.ts +5 -1
  185. package/src/__tests__/secure-keys.test.ts +3 -3
  186. package/src/__tests__/send-endpoint-busy.test.ts +81 -42
  187. package/src/__tests__/server-history-render.test.ts +4 -1
  188. package/src/__tests__/skill-feature-flags-integration.test.ts +8 -10
  189. package/src/__tests__/skill-feature-flags.test.ts +14 -16
  190. package/src/__tests__/skill-load-feature-flag.test.ts +5 -5
  191. package/src/__tests__/skill-projection-feature-flag.test.ts +44 -30
  192. package/src/__tests__/skill-projection.benchmark.test.ts +5 -7
  193. package/src/__tests__/skill-tool-factory.test.ts +96 -95
  194. package/src/__tests__/slack-channel-config.test.ts +3 -3
  195. package/src/__tests__/subagent-call-site-routing.test.ts +11 -3
  196. package/src/__tests__/subagent-disposal.test.ts +27 -8
  197. package/src/__tests__/subagent-fork-notifications.test.ts +24 -9
  198. package/src/__tests__/subagent-fork-spawn.test.ts +13 -4
  199. package/src/__tests__/subagent-manager-notify.test.ts +20 -8
  200. package/src/__tests__/subagent-notify-parent.test.ts +5 -4
  201. package/src/__tests__/subagent-spawn-tool-fork.test.ts +58 -0
  202. package/src/__tests__/subagent-tools.test.ts +2 -1
  203. package/src/__tests__/suggestion-routes.test.ts +1 -0
  204. package/src/__tests__/system-prompt.test.ts +38 -0
  205. package/src/__tests__/test-preload-verifier.ts +68 -0
  206. package/src/__tests__/test-preload.ts +32 -39
  207. package/src/__tests__/tool-executor-lifecycle-events.test.ts +20 -7
  208. package/src/__tests__/tool-executor.test.ts +55 -10
  209. package/src/__tests__/tool-preview-lifecycle.test.ts +1 -0
  210. package/src/__tests__/tool-result-metadata-plumbing.test.ts +1 -0
  211. package/src/__tests__/twilio-routes.test.ts +3 -2
  212. package/src/__tests__/validate-input.test.ts +381 -0
  213. package/src/__tests__/verification-control-plane-policy.test.ts +1 -0
  214. package/src/__tests__/voice-scoped-grant-consumer.test.ts +2 -1
  215. package/src/__tests__/voice-session-bridge.test.ts +37 -28
  216. package/src/__tests__/workspace-migration-090-memory-router-cost-optimized-profile.test.ts +326 -0
  217. package/src/__tests__/workspace-migration-091-retighten-migration-onboarding-thread.test.ts +166 -0
  218. package/src/acp/session-manager.ts +5 -6
  219. package/src/agent/loop.ts +80 -0
  220. package/src/api/README.md +124 -2
  221. package/src/api/constants/call-sites.ts +27 -0
  222. package/src/api/events/assistant-outbound-attachment.ts +51 -0
  223. package/src/api/events/assistant-text-delta.ts +32 -0
  224. package/src/api/events/assistant-turn-start.ts +33 -0
  225. package/src/api/events/document-comment-created.ts +48 -0
  226. package/src/api/events/document-comment-deleted.ts +24 -0
  227. package/src/api/events/document-comment-reopened.ts +25 -0
  228. package/src/api/events/document-comment-resolved.ts +27 -0
  229. package/src/api/events/generation-cancelled.ts +24 -0
  230. package/src/api/events/generation-handoff.ts +41 -0
  231. package/src/api/events/message-complete.ts +42 -0
  232. package/src/api/events/open-url.ts +30 -0
  233. package/src/{events → api/events}/relationship-state-updated.ts +3 -3
  234. package/src/api/events/tool-use-start.ts +32 -0
  235. package/src/api/index.ts +128 -3
  236. package/src/api/responses/llm-context-response.ts +39 -0
  237. package/src/api/responses/llm-request-log-entry.ts +93 -0
  238. package/src/api/responses/memory-recall-log.ts +65 -0
  239. package/src/api/responses/memory-v2-activation-log.ts +78 -0
  240. package/src/background-wake/background-wake-routes.test.ts +687 -52
  241. package/src/background-wake/platform-client.test.ts +308 -0
  242. package/src/background-wake/platform-client.ts +167 -0
  243. package/src/background-wake/publisher.ts +91 -0
  244. package/src/background-wake/runtime-registry.ts +2 -2
  245. package/src/background-wake/wake-intent-hooks.test.ts +282 -0
  246. package/src/calls/guardian-dispatch.ts +1 -0
  247. package/src/calls/voice-session-bridge.ts +4 -4
  248. package/src/cli/commands/__tests__/conversations-slack.test.ts +16 -0
  249. package/src/cli/commands/__tests__/notifications.test.ts +184 -40
  250. package/src/cli/commands/channels/__tests__/channels.test.ts +143 -0
  251. package/src/cli/commands/channels/index.ts +229 -0
  252. package/src/cli/commands/memory-v3-render.ts +147 -0
  253. package/src/cli/commands/memory-v3.ts +255 -4
  254. package/src/cli/commands/notifications.ts +365 -55
  255. package/src/cli/lib/open-browser.ts +7 -2
  256. package/src/cli/program.ts +2 -0
  257. package/src/config/assistant-feature-flags.ts +23 -42
  258. package/src/config/bundled-skills/document-editor/SKILL.md +5 -1
  259. package/src/config/bundled-skills/schedule/SKILL.md +1 -1
  260. package/src/config/bundled-skills/schedule/TOOLS.json +2 -2
  261. package/src/config/bundled-skills/settings/tools/open-system-settings.ts +1 -0
  262. package/src/config/call-site-defaults.ts +1 -1
  263. package/src/config/feature-flag-cache.ts +86 -0
  264. package/src/config/feature-flag-registry.json +17 -17
  265. package/src/config/llm-context-resolution.ts +10 -1
  266. package/src/config/llm-resolver.ts +121 -15
  267. package/src/config/loader.ts +4 -5
  268. package/src/config/schemas/__tests__/memory-v2.test.ts +15 -0
  269. package/src/config/schemas/heartbeat.ts +1 -1
  270. package/src/config/schemas/llm.ts +90 -1
  271. package/src/config/schemas/memory-v2.ts +26 -0
  272. package/src/config/schemas/services.ts +6 -2
  273. package/src/config/seed-inference-profiles.ts +36 -16
  274. package/src/context/token-estimator.ts +10 -5
  275. package/src/credential-execution/executable-discovery.ts +40 -0
  276. package/src/credential-execution/process-manager.ts +6 -2
  277. package/src/credential-health/credential-health-service.ts +125 -40
  278. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +3 -6
  279. package/src/daemon/__tests__/conversation-surfaces-launch.test.ts +13 -15
  280. package/src/daemon/__tests__/conversation-tool-setup-exclude.test.ts +1 -2
  281. package/src/daemon/__tests__/daemon-skill-host.test.ts +2 -0
  282. package/src/daemon/__tests__/meet-manifest-loader.test.ts +25 -12
  283. package/src/daemon/__tests__/native-web-search-metadata.test.ts +1 -0
  284. package/src/daemon/__tests__/switch-inference-profile-tool.test.ts +107 -0
  285. package/src/daemon/__tests__/web-search-status-text.test.ts +1 -0
  286. package/src/daemon/conversation-agent-loop-handlers.ts +389 -68
  287. package/src/daemon/conversation-agent-loop.ts +132 -28
  288. package/src/daemon/conversation-error.ts +33 -5
  289. package/src/daemon/conversation-messaging.ts +84 -43
  290. package/src/daemon/conversation-process.ts +74 -37
  291. package/src/daemon/conversation-runtime-assembly.ts +29 -9
  292. package/src/daemon/conversation-skill-tools.ts +14 -30
  293. package/src/daemon/conversation-surfaces.ts +69 -34
  294. package/src/daemon/conversation-tool-setup.ts +33 -48
  295. package/src/daemon/conversation.ts +26 -46
  296. package/src/daemon/daemon-control.ts +1 -1
  297. package/src/daemon/daemon-skill-host.ts +9 -2
  298. package/src/daemon/disk-pressure-guard.ts +27 -29
  299. package/src/daemon/first-greeting.ts +31 -13
  300. package/src/daemon/handlers/shared.ts +6 -1
  301. package/src/daemon/lifecycle.ts +12 -12
  302. package/src/daemon/mcp-reload-service.ts +1 -1
  303. package/src/daemon/meet-manifest-loader.ts +10 -17
  304. package/src/daemon/message-types/conversations.ts +20 -22
  305. package/src/daemon/message-types/document-comments.ts +8 -44
  306. package/src/daemon/message-types/home.ts +2 -2
  307. package/src/daemon/message-types/integrations.ts +2 -7
  308. package/src/daemon/message-types/messages.ts +23 -38
  309. package/src/daemon/message-types/subagents.ts +6 -0
  310. package/src/daemon/process-message.ts +9 -9
  311. package/src/daemon/providers-setup.ts +1 -1
  312. package/src/daemon/server.ts +16 -0
  313. package/src/daemon/switch-inference-profile-tool.ts +13 -3
  314. package/src/daemon/tool-setup-types.ts +0 -6
  315. package/src/daemon/wake-target-adapter.ts +10 -0
  316. package/src/documents/document-store.ts +38 -0
  317. package/src/export/__tests__/transcript-formatter.test.ts +1 -0
  318. package/src/heartbeat/__tests__/heartbeat-service.test.ts +29 -0
  319. package/src/heartbeat/heartbeat-service.ts +63 -0
  320. package/src/home/__tests__/feed-writer.test.ts +161 -0
  321. package/src/home/__tests__/post-connect-feed.test.ts +1 -0
  322. package/src/home/__tests__/suggested-prompts.test.ts +55 -59
  323. package/src/home/feed-writer.ts +146 -7
  324. package/src/home/suggested-prompts.ts +27 -145
  325. package/src/ipc/__tests__/cli-ipc.test.ts +1 -0
  326. package/src/ipc/gateway-client.test.ts +4 -1
  327. package/src/ipc/skill-routes/__tests__/memory.test.ts +1 -0
  328. package/src/ipc/skill-routes/__tests__/registries.test.ts +36 -7
  329. package/src/ipc/skill-routes/memory.ts +4 -3
  330. package/src/ipc/skill-routes/registries.ts +28 -29
  331. package/src/memory/__tests__/jobs-store-enqueue-gate.test.ts +1 -0
  332. package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +26 -5
  333. package/src/memory/__tests__/memory-retrospective-enqueue.test.ts +1 -0
  334. package/src/memory/__tests__/memory-retrospective-job.test.ts +1 -0
  335. package/src/memory/__tests__/memory-retrospective-startup-cleanup.test.ts +1 -0
  336. package/src/memory/__tests__/memory-v2-activation-log-store.test.ts +31 -0
  337. package/src/memory/conversation-attention-store.ts +17 -3
  338. package/src/memory/conversation-crud.ts +352 -112
  339. package/src/memory/db-connection.ts +29 -19
  340. package/src/memory/db-init.ts +4 -0
  341. package/src/memory/db-singleton.ts +77 -0
  342. package/src/memory/delivery-channels.ts +82 -0
  343. package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +2 -4
  344. package/src/memory/graph/retriever.test.ts +3 -3
  345. package/src/memory/job-handlers/embedding.test.ts +3 -2
  346. package/src/memory/jobs/__tests__/embed-concept-page.test.ts +5 -2
  347. package/src/memory/jobs-worker.ts +12 -1
  348. package/src/memory/llm-request-log-source-clickhouse.ts +80 -0
  349. package/src/memory/llm-request-log-source-local.ts +24 -0
  350. package/src/memory/llm-request-log-source.ts +31 -0
  351. package/src/memory/llm-request-log-store.ts +188 -3
  352. package/src/memory/memory-v2-activation-log-store.ts +95 -1
  353. package/src/memory/migrations/265-drop-provider-connection-status.ts +26 -0
  354. package/src/memory/migrations/266-messages-client-message-id.ts +43 -0
  355. package/src/memory/migrations/index.ts +2 -0
  356. package/src/memory/schema/conversations.ts +9 -1
  357. package/src/memory/schema/inference.ts +0 -1
  358. package/src/memory/v2/__tests__/backfill-jobs.test.ts +5 -2
  359. package/src/memory/v2/__tests__/harness-metrics.test.ts +9 -0
  360. package/src/memory/v2/__tests__/harness-replay-input.test.ts +9 -4
  361. package/src/memory/v2/__tests__/harness-runner.test.ts +26 -0
  362. package/src/memory/v2/__tests__/sweep-job.test.ts +6 -3
  363. package/src/memory/v2/harness/metrics.ts +5 -1
  364. package/src/memory/v2/harness/replay-input.ts +19 -3
  365. package/src/memory/v2/harness/runner.ts +6 -0
  366. package/src/memory/v2/harness/trace.ts +6 -0
  367. package/src/memory/v3/__tests__/consolidation-job.test.ts +2 -4
  368. package/src/memory/v3/__tests__/coretrieval-seed.test.ts +270 -0
  369. package/src/memory/v3/__tests__/edges.test.ts +144 -1
  370. package/src/memory/v3/__tests__/filter.test.ts +48 -0
  371. package/src/memory/v3/__tests__/gate.test.ts +96 -33
  372. package/src/memory/v3/__tests__/index-composition.test.ts +58 -0
  373. package/src/memory/v3/__tests__/loop.test.ts +250 -5
  374. package/src/memory/v3/__tests__/scouts.test.ts +49 -0
  375. package/src/memory/v3/__tests__/shadow-diff.test.ts +225 -0
  376. package/src/memory/v3/__tests__/shadow-middleware.test.ts +88 -2
  377. package/src/memory/v3/__tests__/traversal.test.ts +39 -0
  378. package/src/memory/v3/__tests__/tree-walk.test.ts +77 -0
  379. package/src/memory/v3/__tests__/validate.test.ts +32 -0
  380. package/src/memory/v3/coretrieval-seed.ts +240 -0
  381. package/src/memory/v3/edges.ts +58 -21
  382. package/src/memory/v3/filter.ts +27 -22
  383. package/src/memory/v3/gate.ts +51 -36
  384. package/src/memory/v3/index-composition.ts +18 -5
  385. package/src/memory/v3/loop.ts +65 -17
  386. package/src/memory/v3/scouts.ts +15 -4
  387. package/src/memory/v3/shadow-diff.ts +287 -0
  388. package/src/memory/v3/shadow-middleware.ts +44 -2
  389. package/src/memory/v3/traversal.ts +6 -1
  390. package/src/memory/v3/tree-walk.ts +6 -1
  391. package/src/memory/v3/validate.ts +56 -33
  392. package/src/notifications/__tests__/emit-signal-home-feed.test.ts +1 -0
  393. package/src/notifications/__tests__/home-feed-side-effect.test.ts +1 -0
  394. package/src/notifications/adapters/slack.ts +45 -11
  395. package/src/notifications/broadcaster.ts +114 -63
  396. package/src/notifications/conversation-pairing.ts +23 -3
  397. package/src/notifications/decisions-store.ts +32 -1
  398. package/src/notifications/deliveries-store.ts +45 -0
  399. package/src/notifications/edit-notification.ts +201 -0
  400. package/src/notifications/emit-signal.ts +11 -1
  401. package/src/notifications/signal.ts +10 -0
  402. package/src/notifications/types.ts +37 -0
  403. package/src/oauth/byo-connection.test.ts +67 -3
  404. package/src/oauth/byo-connection.ts +32 -5
  405. package/src/oauth/connect-orchestrator.ts +9 -0
  406. package/src/oauth/connection-resolver.test.ts +76 -0
  407. package/src/oauth/connection-resolver.ts +49 -10
  408. package/src/oauth/manual-token-connection.ts +51 -3
  409. package/src/oauth/seed-providers.ts +3 -0
  410. package/src/permissions/approval-policy.test.ts +19 -5
  411. package/src/permissions/approval-policy.ts +14 -3
  412. package/src/permissions/checker.ts +21 -8
  413. package/src/platform/client.test.ts +24 -1
  414. package/src/platform/client.ts +8 -0
  415. package/src/platform/feature-gate.ts +15 -0
  416. package/src/plugins/defaults/injectors.ts +2 -8
  417. package/src/plugins/defaults/persistence.ts +25 -6
  418. package/src/plugins/types.ts +57 -13
  419. package/src/proactive-artifact/job.test.ts +1 -0
  420. package/src/prompts/__tests__/system-prompt.test.ts +4 -4
  421. package/src/prompts/system-prompt.ts +38 -40
  422. package/src/prompts/template-detection.ts +10 -4
  423. package/src/prompts/templates/BOOTSTRAP.md +7 -11
  424. package/src/prompts/templates/IDENTITY.md +0 -2
  425. package/src/providers/__tests__/connection-model-compat.test.ts +3 -4
  426. package/src/providers/__tests__/registry-native-web-search.test.ts +122 -0
  427. package/src/providers/call-site-routing.ts +33 -9
  428. package/src/providers/connection-model-compat.ts +23 -0
  429. package/src/providers/connection-resolution.ts +39 -20
  430. package/src/providers/fireworks/client.ts +1 -0
  431. package/src/providers/gemini/client.ts +24 -3
  432. package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +0 -2
  433. package/src/providers/inference/__tests__/base-url-security.test.ts +2 -3
  434. package/src/providers/inference/__tests__/{connections-status-label.test.ts → connections-label.test.ts} +12 -111
  435. package/src/providers/inference/auth.ts +0 -8
  436. package/src/providers/inference/connections.ts +3 -66
  437. package/src/providers/inference/resolve-auth.ts +2 -3
  438. package/src/providers/model-catalog.ts +35 -1
  439. package/src/providers/model-intents.ts +3 -3
  440. package/src/providers/openai/__tests__/api-error-detail.test.ts +120 -0
  441. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +157 -5
  442. package/src/providers/openai/chat-completions-provider.ts +110 -12
  443. package/src/providers/openai/codex-models.ts +2 -0
  444. package/src/providers/openai/responses-provider.ts +53 -53
  445. package/src/providers/openrouter/client.ts +13 -8
  446. package/src/providers/provider-send-message.ts +18 -9
  447. package/src/providers/registry.ts +48 -8
  448. package/src/providers/retry.ts +16 -4
  449. package/src/providers/search-provider-catalog.ts +17 -9
  450. package/src/providers/types.ts +9 -0
  451. package/src/runtime/__tests__/agent-wake.test.ts +1 -0
  452. package/src/runtime/__tests__/background-job-runner.test.ts +1 -0
  453. package/src/runtime/access-request-helper.ts +1 -0
  454. package/src/runtime/auth/route-policy.ts +10 -0
  455. package/src/runtime/channel-readiness-service.ts +68 -0
  456. package/src/runtime/channel-reply-delivery.ts +23 -0
  457. package/src/runtime/channel-retry-sweep.ts +47 -14
  458. package/src/runtime/confirmation-request-guardian-bridge.ts +1 -1
  459. package/src/runtime/migrations/vbundle-builder.ts +3 -2
  460. package/src/runtime/routes/__tests__/bookmark-routes.test.ts +1 -0
  461. package/src/runtime/routes/__tests__/conversation-compaction-routes.test.ts +406 -0
  462. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +98 -0
  463. package/src/runtime/routes/__tests__/heartbeat-routes.test.ts +1 -1
  464. package/src/runtime/routes/__tests__/home-feed-routes.test.ts +209 -1
  465. package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +13 -50
  466. package/src/runtime/routes/__tests__/memory-v2-simulate-route.test.ts +51 -3
  467. package/src/runtime/routes/__tests__/memory-v3-simulate-params.test.ts +35 -0
  468. package/src/runtime/routes/__tests__/slack-channel-routes.test.ts +3 -2
  469. package/src/runtime/routes/__tests__/surface-content-routes.test.ts +294 -0
  470. package/src/runtime/routes/__tests__/task-routes.test.ts +48 -3
  471. package/src/runtime/routes/acp-routes-list.test.ts +3 -0
  472. package/src/runtime/routes/app-management-routes.ts +111 -4
  473. package/src/runtime/routes/background-wake-routes.ts +188 -20
  474. package/src/runtime/routes/btw-routes.ts +4 -4
  475. package/src/runtime/routes/conversation-analysis-routes.ts +6 -0
  476. package/src/runtime/routes/conversation-compaction-routes.ts +263 -0
  477. package/src/runtime/routes/conversation-list-routes.ts +147 -0
  478. package/src/runtime/routes/conversation-management-routes.ts +39 -14
  479. package/src/runtime/routes/conversation-query-routes.ts +60 -10
  480. package/src/runtime/routes/conversation-routes.ts +186 -140
  481. package/src/runtime/routes/conversations-import-routes.ts +19 -6
  482. package/src/runtime/routes/documents-routes.ts +10 -1
  483. package/src/runtime/routes/group-routes.ts +11 -0
  484. package/src/runtime/routes/home-feed-routes.ts +129 -0
  485. package/src/runtime/routes/identity-intro-cache.ts +61 -16
  486. package/src/runtime/routes/identity-routes.ts +30 -9
  487. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +530 -6
  488. package/src/runtime/routes/inbound-stages/background-dispatch.ts +57 -8
  489. package/src/runtime/routes/index.ts +2 -0
  490. package/src/runtime/routes/inference-provider-connection-routes.ts +5 -26
  491. package/src/runtime/routes/integrations/vercel.ts +15 -0
  492. package/src/runtime/routes/llm-context-normalization.ts +7 -2
  493. package/src/runtime/routes/memory-v3-routes.ts +160 -2
  494. package/src/runtime/routes/migration-routes.ts +20 -13
  495. package/src/runtime/routes/notification-routes.ts +63 -1
  496. package/src/runtime/routes/oauth-commands-routes.ts +6 -1
  497. package/src/runtime/routes/surface-action-routes.ts +1 -38
  498. package/src/runtime/routes/surface-content-routes.ts +12 -5
  499. package/src/runtime/routes/surface-conversation-resolver.ts +65 -0
  500. package/src/runtime/routes/wipe-conversation-routes.ts +3 -0
  501. package/src/runtime/services/__tests__/analyze-conversation.test.ts +2 -0
  502. package/src/runtime/slack-dm-text-delivery.ts +177 -0
  503. package/src/runtime/sync/resource-sync-events.ts +1 -1
  504. package/src/runtime/tool-grant-request-helper.ts +1 -0
  505. package/src/schedule/schedule-store.ts +8 -1
  506. package/src/schedule/scheduler.ts +111 -15
  507. package/src/security/__tests__/provider-key-env-fallback.test.ts +3 -3
  508. package/src/security/encrypted-store.ts +7 -16
  509. package/src/security/store-path-override.ts +61 -0
  510. package/src/signals/user-message.ts +5 -8
  511. package/src/skills/validate-input.ts +177 -0
  512. package/src/subagent/manager.ts +13 -13
  513. package/src/subagent/types.ts +6 -0
  514. package/src/tasks/tool-sanitizer.ts +2 -2
  515. package/src/tools/apps/definitions.ts +35 -21
  516. package/src/tools/browser/__tests__/browser-execution-acquire.test.ts +2 -8
  517. package/src/tools/computer-use/definitions.ts +268 -266
  518. package/src/tools/document/document-tool.ts +131 -8
  519. package/src/tools/execution-target.ts +2 -5
  520. package/src/tools/executor.ts +18 -55
  521. package/src/tools/host-filesystem/edit.test.ts +1 -0
  522. package/src/tools/host-filesystem/read.test.ts +1 -0
  523. package/src/tools/host-filesystem/transfer.test.ts +31 -6
  524. package/src/tools/host-filesystem/write.test.ts +1 -0
  525. package/src/tools/mcp/mcp-tool-factory.ts +0 -2
  526. package/src/tools/network/__tests__/managed-search-proxy.test.ts +282 -0
  527. package/src/tools/network/__tests__/web-search.test.ts +211 -3
  528. package/src/tools/network/managed-search-proxy.ts +183 -0
  529. package/src/tools/network/web-search.ts +199 -44
  530. package/src/tools/policy-context.ts +3 -1
  531. package/src/tools/registry.ts +146 -103
  532. package/src/tools/schedule/create.ts +1 -1
  533. package/src/tools/skills/skill-tool-factory.ts +17 -36
  534. package/src/tools/subagent/spawn.ts +3 -0
  535. package/src/tools/tool-approval-handler.ts +10 -4
  536. package/src/tools/tool-name-aliases.ts +72 -14
  537. package/src/tools/types.ts +17 -15
  538. package/src/tools/ui-surface/definitions.ts +98 -86
  539. package/src/types/onboarding-context.ts +6 -0
  540. package/src/usage/attribution.ts +32 -1
  541. package/src/util/browser.ts +7 -2
  542. package/src/workspace/migrations/090-memory-router-cost-optimized-profile.ts +109 -0
  543. package/src/workspace/migrations/091-retighten-migration-onboarding-thread.ts +41 -0
  544. package/src/workspace/migrations/registry.ts +4 -0
@@ -59,6 +59,7 @@ import { commitAppTurnChanges } from "../memory/app-git-service.js";
59
59
  import { getApp, listAppFiles, resolveAppDir } from "../memory/app-store.js";
60
60
  import { enqueueAutoAnalysisOnCompaction } from "../memory/auto-analysis-enqueue.js";
61
61
  import {
62
+ deleteMessageById,
62
63
  getConversation,
63
64
  getConversationOriginChannel,
64
65
  getConversationOriginInterface,
@@ -79,7 +80,10 @@ import {
79
80
  } from "../memory/conversation-title-service.js";
80
81
  import { isBackgroundConversationType } from "../memory/conversation-types.js";
81
82
  import type { ConversationGraphMemory } from "../memory/graph/conversation-graph-memory.js";
82
- import { backfillMessageIdOnLogs } from "../memory/llm-request-log-store.js";
83
+ import {
84
+ backfillMessageIdOnLogs,
85
+ recordSyntheticAgentErrorMessageLog,
86
+ } from "../memory/llm-request-log-store.js";
83
87
  import { recordMemoryRecallLog } from "../memory/memory-recall-log-store.js";
84
88
  import { enqueueMemoryRetrospectiveOnCompaction } from "../memory/memory-retrospective-enqueue.js";
85
89
  import { PKB_WORKSPACE_SCOPE } from "../memory/pkb/types.js";
@@ -216,16 +220,37 @@ import type {
216
220
  } from "./message-protocol.js";
217
221
  import type { MemoryRecalled } from "./message-types/memory.js";
218
222
  import type { ConfirmationStateChanged } from "./message-types/messages.js";
219
- import {
220
- conversationMetadataSyncTag,
221
- SYNC_TAGS,
222
- } from "./message-types/sync.js";
223
+ import { conversationMetadataSyncTag } from "./message-types/sync.js";
223
224
  import { parseActualTokensFromError } from "./parse-actual-tokens-from-error.js";
224
225
  import type { TraceEmitter } from "./trace-emitter.js";
225
226
  import type { TrustContext } from "./trust-context.js";
226
227
  import { stripHistoricalWebSearchResults } from "./web-search-history.js";
227
228
 
228
229
  const log = getLogger("conversation-agent-loop");
230
+
231
+ /**
232
+ * Best-effort persistence of the history-stripped marker after an
233
+ * injection-strip event (compaction / overflow recovery). The marker is a
234
+ * durability hint, not turn-critical state — a transient SQLite write failure
235
+ * (SQLITE_BUSY, disk-full, read-only FS) must not abort the turn. Logs a
236
+ * warning and continues on failure, preserving the long-standing non-fatal
237
+ * contract for this metadata write.
238
+ */
239
+ function markHistoryStrippedBestEffort(
240
+ conversationId: string,
241
+ strippedAt: number,
242
+ logger: ReturnType<typeof getLogger>,
243
+ ): void {
244
+ try {
245
+ setConversationHistoryStrippedAt(conversationId, strippedAt);
246
+ } catch (err) {
247
+ logger.warn(
248
+ { err },
249
+ "Failed to persist history-stripped marker after compaction strip (non-fatal)",
250
+ );
251
+ }
252
+ }
253
+
229
254
  const DISK_PRESSURE_ERROR_CODE = "DISK_SPACE_CRITICAL" as const;
230
255
  const DISK_PRESSURE_ERROR_CATEGORY = "disk_pressure";
231
256
 
@@ -513,7 +538,12 @@ export interface AgentLoopConversationContext {
513
538
  surfaceType: SurfaceType;
514
539
  title?: string;
515
540
  data: SurfaceData;
516
- actions?: Array<{ id: string; label: string; style?: string }>;
541
+ actions?: Array<{
542
+ id: string;
543
+ label: string;
544
+ style?: string;
545
+ data?: Record<string, unknown>;
546
+ }>;
517
547
  display?: string;
518
548
  persistent?: boolean;
519
549
  }>;
@@ -541,12 +571,6 @@ export interface AgentLoopConversationContext {
541
571
  * turn start.
542
572
  */
543
573
  toolRoutedProfile?: string;
544
- /**
545
- * True when the user has explicitly selected an inference profile for this
546
- * conversation (via the composer profile picker). When set, tool-based
547
- * auto-routing is suppressed — the user's explicit choice takes precedence.
548
- */
549
- hasExplicitProfileOverride?: boolean;
550
574
  commandIntent?: { type: string; payload?: string; languageCode?: string };
551
575
  trustContext?: TrustContext;
552
576
  /** Task-run scope for the current turn. Cleared at turn end so queued/drained turns don't inherit it. */
@@ -724,8 +748,6 @@ export async function runAgentLoopImpl(
724
748
  options?.overrideProfile ??
725
749
  getConversationOverrideProfileFromRow(turnStartConversation);
726
750
 
727
- ctx.hasExplicitProfileOverride = !!userExplicitOverride;
728
-
729
751
  const config = getConfig();
730
752
 
731
753
  // Tool-based auto-routing: the switch_inference_profile tool lets the model
@@ -746,12 +768,14 @@ export async function runAgentLoopImpl(
746
768
  llm: config.llm,
747
769
  callSite: turnCallSite,
748
770
  overrideProfile: turnOverrideProfile ?? undefined,
771
+ selectionSeed: ctx.conversationId,
749
772
  });
750
773
  let currentEffectiveContextWindow: EffectiveContextWindow =
751
774
  effectiveContextWindow;
752
775
  let currentContextWindowConfig = contextWindowConfigFromEffective(
753
776
  resolveCallSiteConfig(turnCallSite, config.llm, {
754
777
  overrideProfile: turnOverrideProfile ?? undefined,
778
+ selectionSeed: ctx.conversationId,
755
779
  }).contextWindow,
756
780
  currentEffectiveContextWindow,
757
781
  );
@@ -770,10 +794,12 @@ export async function runAgentLoopImpl(
770
794
  llm: config.llm,
771
795
  callSite: turnCallSite,
772
796
  overrideProfile: currentOverrideProfile,
797
+ selectionSeed: ctx.conversationId,
773
798
  });
774
799
  currentContextWindowConfig = contextWindowConfigFromEffective(
775
800
  resolveCallSiteConfig(turnCallSite, config.llm, {
776
801
  overrideProfile: currentOverrideProfile,
802
+ selectionSeed: ctx.conversationId,
777
803
  }).contextWindow,
778
804
  currentEffectiveContextWindow,
779
805
  );
@@ -1028,10 +1054,7 @@ export async function runAgentLoopImpl(
1028
1054
  });
1029
1055
  onEvent({
1030
1056
  type: "sync_changed",
1031
- tags: [
1032
- SYNC_TAGS.conversationsList,
1033
- conversationMetadataSyncTag(ctx.conversationId),
1034
- ],
1057
+ tags: [conversationMetadataSyncTag(ctx.conversationId)],
1035
1058
  });
1036
1059
  },
1037
1060
  };
@@ -2312,7 +2335,7 @@ export async function runAgentLoopImpl(
2312
2335
  // so we compact the "raw" persistent messages.
2313
2336
  const rawHistory = stripInjectionsForCompaction(updatedHistory);
2314
2337
  ctx.messages = rawHistory;
2315
- setConversationHistoryStrippedAt(ctx.conversationId, Date.now());
2338
+ markHistoryStrippedBestEffort(ctx.conversationId, Date.now(), rlog);
2316
2339
 
2317
2340
  ctx.emitActivityState(
2318
2341
  "thinking",
@@ -2596,7 +2619,7 @@ export async function runAgentLoopImpl(
2596
2619
 
2597
2620
  if (updatedHistory.length > preRunHistoryLength) {
2598
2621
  ctx.messages = stripInjectionsForCompaction(updatedHistory);
2599
- setConversationHistoryStrippedAt(ctx.conversationId, Date.now());
2622
+ markHistoryStrippedBestEffort(ctx.conversationId, Date.now(), rlog);
2600
2623
  convergenceStripped = true;
2601
2624
  preRepairMessages = updatedHistory;
2602
2625
  preRunHistoryLength = updatedHistory.length;
@@ -2841,7 +2864,7 @@ export async function runAgentLoopImpl(
2841
2864
  // pre-rerun messages.
2842
2865
  if (updatedHistory.length > preRunHistoryLength) {
2843
2866
  ctx.messages = stripInjectionsForCompaction(updatedHistory);
2844
- setConversationHistoryStrippedAt(ctx.conversationId, Date.now());
2867
+ markHistoryStrippedBestEffort(ctx.conversationId, Date.now(), rlog);
2845
2868
  convergenceStripped = true;
2846
2869
  preRepairMessages = updatedHistory;
2847
2870
  preRunHistoryLength = updatedHistory.length;
@@ -3110,8 +3133,12 @@ export async function runAgentLoopImpl(
3110
3133
  assistantMessageInterface:
3111
3134
  capturedTurnInterfaceContext.assistantMessageInterface,
3112
3135
  };
3136
+ let yieldNoticePersistedId: string | null = null;
3113
3137
  try {
3114
- await runPipeline<PersistArgs, PersistResult>(
3138
+ const yieldPersistResult = (await runPipeline<
3139
+ PersistArgs,
3140
+ PersistResult
3141
+ >(
3115
3142
  "persistence",
3116
3143
  getMiddlewaresFor("persistence"),
3117
3144
  defaultPersistenceTerminal,
@@ -3124,7 +3151,8 @@ export async function runAgentLoopImpl(
3124
3151
  },
3125
3152
  buildPluginTurnContext(ctx, reqId),
3126
3153
  DEFAULT_TIMEOUTS.persistence,
3127
- );
3154
+ )) as PersistAddResult;
3155
+ yieldNoticePersistedId = yieldPersistResult.message.id;
3128
3156
  } catch (err) {
3129
3157
  // Non-fatal — a DB hiccup must not escalate a budget-yield exit into
3130
3158
  // a turn-level throw. The live SSE event was already emitted, so the
@@ -3134,6 +3162,48 @@ export async function runAgentLoopImpl(
3134
3162
  "Failed to persist budget_yield_unrecovered notice (non-fatal)",
3135
3163
  );
3136
3164
  }
3165
+ // Record a synthetic `llm_request_logs` row for the yield so the
3166
+ // inspector's call rail surfaces a clickable, distinctly-rendered
3167
+ // entry for the failure itself. Without this row, the loop yields
3168
+ // silently — the user sees the notice in chat but the inspector
3169
+ // call list ends at the last actual LLM call with no way to scope
3170
+ // the "what compactions led to this failure?" question to the
3171
+ // yield event.
3172
+ //
3173
+ // Recorded *before* emitTerminalExit so the synthetic row exists
3174
+ // by the time the dispatcher's post-loop hook runs. The row
3175
+ // already carries `agent_loop_exit_reason` at insert time, so
3176
+ // `setAgentLoopExitReasonOnLatestLog`'s IS NULL guard skips it
3177
+ // and stamps the prior real mainAgent call instead — preserving
3178
+ // the existing "latest LLM call carries the exit reason"
3179
+ // invariant other consumers depend on.
3180
+ //
3181
+ // `preparedRequest` snapshots the best-known LLM request state
3182
+ // at yield time — `updatedHistory` (the conversation state the
3183
+ // next call would have been built from) plus the input-token
3184
+ // budget that just failed. Mirrors the role of `request_payload`
3185
+ // on real LLM-call rows; the notice text lives on
3186
+ // `response_payload`.
3187
+ if (yieldNoticePersistedId !== null && budgetYieldClassification) {
3188
+ try {
3189
+ recordSyntheticAgentErrorMessageLog({
3190
+ conversationId: ctx.conversationId,
3191
+ messageId: yieldNoticePersistedId,
3192
+ exitReason: "budget_yield_unrecovered",
3193
+ noticeText: budgetYieldClassification.userMessage,
3194
+ preparedRequest: {
3195
+ messages: updatedHistory,
3196
+ maxInputTokensBudget: resolveCurrentMaxInputTokens() ?? null,
3197
+ },
3198
+ createdAt: Date.now(),
3199
+ });
3200
+ } catch (err) {
3201
+ rlog.warn(
3202
+ { err },
3203
+ "Failed to record budget_yield_unrecovered synthetic call log (non-fatal)",
3204
+ );
3205
+ }
3206
+ }
3137
3207
  await emitTerminalExit?.("budget_yield_unrecovered");
3138
3208
  }
3139
3209
 
@@ -3154,6 +3224,34 @@ export async function runAgentLoopImpl(
3154
3224
  !abortController.signal.aborted &&
3155
3225
  !yieldedForHandoff
3156
3226
  ) {
3227
+ // Drop any reservation stranded by the failed LLM call before
3228
+ // inserting the synthetic error message. The B3 pre-allocation
3229
+ // path reserves an empty assistant row at `llm_call_started`;
3230
+ // when the call exits through the provider-error branch (no
3231
+ // `message_complete`), `assistantRowAwaitingFinalization` stays
3232
+ // true. Without this delete the transcript would carry both the
3233
+ // empty reserved row AND the error message — and downstream sync
3234
+ // (`syncLastAssistantMessageToDisk`) would mis-target the empty
3235
+ // row. After delete we set `lastAssistantMessageId` to the new
3236
+ // error row's id so the post-loop emission paths still point at
3237
+ // a real message.
3238
+ if (
3239
+ state.assistantRowAwaitingFinalization &&
3240
+ state.lastAssistantMessageId
3241
+ ) {
3242
+ // Direct `deleteMessageById` (not via the `persistence` pipeline):
3243
+ // see the same rationale on the matching cleanup in
3244
+ // `handleLlmCallStarted` — an unfinalized reservation has no
3245
+ // observable history for plugins.
3246
+ try {
3247
+ deleteMessageById(state.lastAssistantMessageId);
3248
+ } catch (err) {
3249
+ rlog.warn(
3250
+ { err, messageId: state.lastAssistantMessageId },
3251
+ "Failed to clean up stranded reserved assistant row on provider-error path (non-fatal)",
3252
+ );
3253
+ }
3254
+ }
3157
3255
  const errChannelMeta = {
3158
3256
  ...provenanceFromTrustContext(ctx.trustContext),
3159
3257
  userMessageChannel: capturedTurnChannelContext.userMessageChannel,
@@ -3181,6 +3279,15 @@ export async function runAgentLoopImpl(
3181
3279
  DEFAULT_TIMEOUTS.persistence,
3182
3280
  )) as PersistAddResult;
3183
3281
  persistedErrorAssistantMessage = true;
3282
+ // Repoint `lastAssistantMessageId` at the synthetic error row so the
3283
+ // post-loop sync, attachment resolution, and `message_complete`/
3284
+ // `generation_handoff` emissions all reference a real, persisted
3285
+ // message id. The previous reservation (if any) was already deleted
3286
+ // above. Mark finalization complete so the next LLM call in this run
3287
+ // (or a downstream handler) doesn't try to clean up an id that
3288
+ // already corresponds to a finalized row.
3289
+ state.lastAssistantMessageId = errorPersistResult.message.id;
3290
+ state.assistantRowAwaitingFinalization = false;
3184
3291
  newMessages.push(errorAssistantMessage);
3185
3292
  // Pipe the just-assigned message id into any orphaned LLM request log
3186
3293
  // row(s) for this turn. The success path links rows via
@@ -3446,10 +3553,7 @@ export async function runAgentLoopImpl(
3446
3553
  });
3447
3554
  onEvent({
3448
3555
  type: "sync_changed",
3449
- tags: [
3450
- SYNC_TAGS.conversationsList,
3451
- conversationMetadataSyncTag(ctx.conversationId),
3452
- ],
3556
+ tags: [conversationMetadataSyncTag(ctx.conversationId)],
3453
3557
  });
3454
3558
  },
3455
3559
  signal: abortController.signal,
@@ -3694,7 +3798,7 @@ export async function applyCompactionResult(
3694
3798
  result.summaryText,
3695
3799
  ctx.contextCompactedMessageCount,
3696
3800
  );
3697
- setConversationHistoryStrippedAt(ctx.conversationId, compactedAt);
3801
+ markHistoryStrippedBestEffort(ctx.conversationId, compactedAt, log);
3698
3802
  if (options.slackContextCompactionWatermarkTs) {
3699
3803
  updateConversationSlackContextWatermark(
3700
3804
  ctx.conversationId,
@@ -1,3 +1,4 @@
1
+ import { ConnectionResolutionError } from "../providers/connection-resolution.js";
1
2
  import { getProviderRoutingSource } from "../providers/registry.js";
2
3
  import { isAbortReason } from "../util/abort-reasons.js";
3
4
  import { ProviderError, ProviderNotConfiguredError } from "../util/errors.js";
@@ -251,14 +252,28 @@ export function classifyConversationError(
251
252
  if (error instanceof ProviderNotConfiguredError) {
252
253
  return {
253
254
  ...providerNotConfiguredClassification({
254
- connectionName:
255
- error.connectionName ?? attribution.connectionName,
255
+ connectionName: error.connectionName ?? attribution.connectionName,
256
256
  profileName: error.profileName ?? attribution.profileName,
257
257
  }),
258
258
  debugDetails,
259
259
  };
260
260
  }
261
261
 
262
+ if (error instanceof ConnectionResolutionError) {
263
+ return {
264
+ code: "PROVIDER_NOT_CONFIGURED",
265
+ userMessage:
266
+ "No compatible provider connection found for this profile. Check your provider connections in Settings.",
267
+ retryable: true,
268
+ debugDetails,
269
+ errorCategory: "provider_not_configured",
270
+ ...(error.connectionName ? { connectionName: error.connectionName } : {}),
271
+ ...(attribution.profileName
272
+ ? { profileName: attribution.profileName }
273
+ : {}),
274
+ };
275
+ }
276
+
262
277
  // Phase-specific overrides
263
278
  if (ctx.phase === "regenerate") {
264
279
  const base = classifyCore(error, message, attribution);
@@ -790,6 +805,21 @@ export function budgetYieldUnrecoveredClassification(): ClassifiedConversationEr
790
805
  };
791
806
  }
792
807
 
808
+ /**
809
+ * Classify a model response that stopped because the output-token limit was
810
+ * reached. The turn may have produced useful partial text, so the recovery is
811
+ * a follow-up user turn rather than a retry of the same request.
812
+ */
813
+ export function maxTokensReachedClassification(): ClassifiedConversationError {
814
+ return {
815
+ code: "MAX_TOKENS_REACHED",
816
+ userMessage:
817
+ "I hit the response limit before I could finish. Continue and I'll pick up from where I stopped.",
818
+ retryable: true,
819
+ errorCategory: "max_tokens_reached",
820
+ };
821
+ }
822
+
793
823
  /**
794
824
  * Build a `conversation_error` server message from a classified error.
795
825
  */
@@ -811,8 +841,6 @@ export function buildConversationErrorMessage(
811
841
  ...(classified.connectionName
812
842
  ? { connectionName: classified.connectionName }
813
843
  : {}),
814
- ...(classified.profileName
815
- ? { profileName: classified.profileName }
816
- : {}),
844
+ ...(classified.profileName ? { profileName: classified.profileName } : {}),
817
845
  };
818
846
  }
@@ -26,6 +26,7 @@ import {
26
26
  } from "../memory/attachments-store.js";
27
27
  import {
28
28
  addMessage,
29
+ extractImageSourcePaths,
29
30
  getConversation,
30
31
  provenanceFromTrustContext,
31
32
  setConversationOriginChannelIfUnset,
@@ -284,22 +285,44 @@ export function buildSlackMetaForPersistence(params: {
284
285
  return writeSlackMetadata(slackMeta);
285
286
  }
286
287
 
288
+ // ── EnqueueMessageOptions ────────────────────────────────────────────
289
+
290
+ /** Options for `enqueueMessage`. Only `content` is required; everything
291
+ * else has a sensible default or is genuinely optional. */
292
+ export interface EnqueueMessageOptions {
293
+ content: string;
294
+ attachments?: UserMessageAttachment[];
295
+ onEvent?: (msg: ServerMessage) => void;
296
+ requestId?: string;
297
+ activeSurfaceId?: string;
298
+ currentPage?: string;
299
+ metadata?: Record<string, unknown>;
300
+ isInteractive?: boolean;
301
+ displayContent?: string;
302
+ transport?: ConversationTransportMetadata;
303
+ clientMessageId?: string;
304
+ }
305
+
287
306
  // ── enqueueMessage ───────────────────────────────────────────────────
288
307
 
289
308
  export function enqueueMessage(
290
309
  ctx: MessagingConversationContext,
291
- content: string,
292
- attachments: UserMessageAttachment[],
293
- onEvent: (msg: ServerMessage) => void,
294
- requestId: string,
295
- activeSurfaceId?: string,
296
- currentPage?: string,
297
- metadata?: Record<string, unknown>,
298
- options?: { isInteractive?: boolean },
299
- displayContent?: string,
300
- transport?: ConversationTransportMetadata,
301
- clientMessageId?: string,
310
+ options: EnqueueMessageOptions,
302
311
  ): { queued: boolean; requestId: string; rejected?: boolean } {
312
+ const {
313
+ content,
314
+ attachments = [],
315
+ onEvent,
316
+ requestId = crypto.randomUUID(),
317
+ activeSurfaceId,
318
+ currentPage,
319
+ metadata,
320
+ isInteractive,
321
+ displayContent,
322
+ transport,
323
+ clientMessageId,
324
+ } = options;
325
+
303
326
  if (!ctx.processing) {
304
327
  return { queued: false, requestId };
305
328
  }
@@ -316,20 +339,20 @@ export function enqueueMessage(
316
339
  content,
317
340
  attachments,
318
341
  requestId,
319
- onEvent,
342
+ onEvent: onEvent ?? (() => {}),
320
343
  activeSurfaceId,
321
344
  currentPage,
322
345
  metadata,
323
346
  turnChannelContext,
324
347
  turnInterfaceContext,
325
- isInteractive: options?.isInteractive,
348
+ isInteractive,
326
349
  transport,
327
350
  displayContent,
328
351
  sentAt: Date.now(),
329
352
  clientMessageId,
330
353
  });
331
354
  if (!accepted) {
332
- onEvent({
355
+ onEvent?.({
333
356
  type: "error",
334
357
  conversationId: ctx.conversationId,
335
358
  message:
@@ -341,16 +364,26 @@ export function enqueueMessage(
341
364
  return { queued: true, requestId };
342
365
  }
343
366
 
367
+ // ── PersistMessageOptions ────────────────────────────────────────────
368
+
369
+ /** Shared options for `persistUserMessage` and `persistQueuedMessageBody`. */
370
+ export interface PersistMessageOptions {
371
+ content: string;
372
+ attachments?: UserMessageAttachment[];
373
+ requestId?: string;
374
+ metadata?: Record<string, unknown>;
375
+ displayContent?: string;
376
+ clientMessageId?: string;
377
+ }
378
+
344
379
  // ── persistUserMessage ───────────────────────────────────────────────
345
380
 
346
381
  export async function persistUserMessage(
347
382
  ctx: MessagingConversationContext,
348
- content: string,
349
- attachments: UserMessageAttachment[],
350
- requestId?: string,
351
- metadata?: Record<string, unknown>,
352
- displayContent?: string,
353
- ): Promise<string> {
383
+ options: PersistMessageOptions,
384
+ ): Promise<{ id: string; deduplicated: boolean }> {
385
+ const { content, attachments = [] } = options;
386
+
354
387
  if (ctx.processing) {
355
388
  throw new Error("Conversation is already processing a message");
356
389
  }
@@ -359,20 +392,23 @@ export async function persistUserMessage(
359
392
  throw new Error("Message content or attachments are required");
360
393
  }
361
394
 
362
- const reqId = requestId ?? uuid();
395
+ const reqId = options.requestId ?? uuid();
363
396
  ctx.currentRequestId = reqId;
364
397
  ctx.processing = true;
365
398
  ctx.abortController = new AbortController();
366
399
 
367
400
  try {
368
- return await persistQueuedMessageBody(
369
- ctx,
370
- content,
401
+ const result = await persistQueuedMessageBody(ctx, {
402
+ ...options,
371
403
  attachments,
372
- reqId,
373
- metadata,
374
- displayContent,
375
- );
404
+ requestId: reqId,
405
+ });
406
+ if (result.deduplicated) {
407
+ ctx.processing = false;
408
+ ctx.abortController = null;
409
+ ctx.currentRequestId = undefined;
410
+ }
411
+ return result;
376
412
  } catch (err) {
377
413
  ctx.processing = false;
378
414
  ctx.abortController = null;
@@ -394,12 +430,16 @@ export async function persistUserMessage(
394
430
  */
395
431
  export async function persistQueuedMessageBody(
396
432
  ctx: MessagingConversationContext,
397
- content: string,
398
- attachments: UserMessageAttachment[],
399
- requestId: string,
400
- metadata: Record<string, unknown> | undefined,
401
- displayContent: string | undefined,
402
- ): Promise<string> {
433
+ options: PersistMessageOptions,
434
+ ): Promise<{ id: string; deduplicated: boolean }> {
435
+ const {
436
+ content,
437
+ attachments = [],
438
+ requestId = uuid(),
439
+ metadata,
440
+ displayContent,
441
+ clientMessageId,
442
+ } = options;
403
443
  const attachmentInputs = attachments.map((attachment) => ({
404
444
  id: attachment.id,
405
445
  filename: attachment.filename,
@@ -431,13 +471,7 @@ export async function persistQueuedMessageBody(
431
471
  const turnIfCtx =
432
472
  extractTurnInterfaceContext(metadata) ?? ctx.getTurnInterfaceContext();
433
473
  const provenance = provenanceFromTrustContext(ctx.trustContext);
434
- const imageSourcePaths: Record<string, string> = {};
435
- for (let i = 0; i < attachments.length; i++) {
436
- const a = attachments[i];
437
- if (a.filePath && a.mimeType.toLowerCase().startsWith("image/")) {
438
- imageSourcePaths[`${i}:${a.filename}`] = a.filePath;
439
- }
440
- }
474
+ const imageSourcePaths = extractImageSourcePaths(attachments);
441
475
 
442
476
  // Strip the transient `slackInbound` carrier key from the persisted
443
477
  // metadata — it's an in-memory plumbing field, not a stored column value.
@@ -468,7 +502,7 @@ export async function persistQueuedMessageBody(
468
502
  assistantMessageInterface: turnIfCtx.assistantMessageInterface,
469
503
  }
470
504
  : {}),
471
- ...(Object.keys(imageSourcePaths).length > 0 ? { imageSourcePaths } : {}),
505
+ ...(imageSourcePaths ? { imageSourcePaths } : {}),
472
506
  ...(slackMeta ? { slackMeta } : {}),
473
507
  };
474
508
 
@@ -486,8 +520,15 @@ export async function persistQueuedMessageBody(
486
520
  "user",
487
521
  contentToPersist,
488
522
  mergedMetadata,
523
+ undefined,
524
+ clientMessageId,
489
525
  );
490
526
 
527
+ if (persistedUserMessage.deduplicated) {
528
+ ctx.messages.pop();
529
+ return { id: persistedUserMessage.id, deduplicated: true };
530
+ }
531
+
491
532
  if (turnCtx) {
492
533
  setConversationOriginChannelIfUnset(
493
534
  ctx.conversationId,
@@ -569,7 +610,7 @@ export async function persistQueuedMessageBody(
569
610
  );
570
611
  }
571
612
 
572
- return persistedUserMessage.id;
613
+ return { id: persistedUserMessage.id, deduplicated: false };
573
614
  } catch (err) {
574
615
  ctx.messages.pop();
575
616
  throw err;