@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
@@ -176,6 +176,12 @@ let mockConversationRow: Record<string, unknown> = {
176
176
  title: null,
177
177
  };
178
178
  let mockMessageById: Record<string, unknown> | null = null;
179
+ const deleteMessageByIdMock = mock(() => ({
180
+ segmentIds: [],
181
+ deletedSummaryIds: [],
182
+ }));
183
+ const reserveMessageMock = mock(async () => ({ id: "msg-reserve" }));
184
+ const updateMessageContentMock = mock(() => {});
179
185
  mock.module("../memory/conversation-crud.js", () => ({
180
186
  setConversationOriginChannelIfUnset: () => {},
181
187
  updateConversationUsage: () => {},
@@ -189,7 +195,7 @@ mock.module("../memory/conversation-crud.js", () => ({
189
195
  }),
190
196
  getConversationOriginInterface: () => null,
191
197
  addMessage: () => ({ id: "mock-msg-id" }),
192
- deleteMessageById: () => {},
198
+ deleteMessageById: deleteMessageByIdMock,
193
199
  updateConversationContextWindow: () => {},
194
200
  updateConversationSlackContextWatermark:
195
201
  updateConversationSlackContextWatermarkMock,
@@ -197,6 +203,36 @@ mock.module("../memory/conversation-crud.js", () => ({
197
203
  getConversationOriginChannel: () => null,
198
204
  getMessageById: () => mockMessageById,
199
205
  getLastUserTimestampBefore: () => 0,
206
+ reserveMessage: reserveMessageMock,
207
+ updateMessageContent: updateMessageContentMock,
208
+ // The real schema is a Zod object; tests don't exercise validation,
209
+ // so a passthrough is sufficient — the production code at
210
+ // `handleMessageComplete` only branches on `success` and reads two
211
+ // fields off `data`. `safeParse` of an empty object satisfies the
212
+ // schema (every field is optional).
213
+ messageMetadataSchema: {
214
+ safeParse: (input: unknown) => ({ success: true, data: input ?? {} }),
215
+ },
216
+ }));
217
+
218
+ // The B3 indexing-restoration path imports `indexMessageNow` from
219
+ // `../memory/indexer.js` and `projectAssistantMessage` from
220
+ // `../memory/conversation-attention-store.js`; without these stubs the
221
+ // real modules would try to open a SQLite DB and read a real config.
222
+ const indexMessageNowMock = mock(async () => ({
223
+ indexedSegments: 0,
224
+ enqueuedJobs: 0,
225
+ }));
226
+ const projectAssistantMessageMock = mock(() => false);
227
+ const publishSyncInvalidationMock = mock(async () => {});
228
+ mock.module("../memory/indexer.js", () => ({
229
+ indexMessageNow: indexMessageNowMock,
230
+ }));
231
+ mock.module("../memory/conversation-attention-store.js", () => ({
232
+ projectAssistantMessage: projectAssistantMessageMock,
233
+ }));
234
+ mock.module("../runtime/sync/sync-publisher.js", () => ({
235
+ publishSyncInvalidation: publishSyncInvalidationMock,
200
236
  }));
201
237
 
202
238
  afterAll(() => {
@@ -692,6 +728,13 @@ beforeEach(() => {
692
728
  mockSlackChronologicalContext = null;
693
729
  loadSlackChronologicalContextMock.mockClear();
694
730
  getSlackCompactionWatermarkForPrefixMock.mockClear();
731
+ deleteMessageByIdMock.mockClear();
732
+ reserveMessageMock.mockClear();
733
+ updateMessageContentMock.mockClear();
734
+ indexMessageNowMock.mockClear();
735
+ projectAssistantMessageMock.mockClear();
736
+ publishSyncInvalidationMock.mockClear();
737
+ mockMessageById = null;
695
738
  // Orchestrator pipelines (overflowReduce, persistence, …) run through the
696
739
  // plugin registry; reset and re-register every default so the pipelines
697
740
  // dispatch to middleware backed by the mocked collaborators these tests
@@ -783,6 +826,9 @@ describe("session-agent-loop", () => {
783
826
  _requestId,
784
827
  onCheckpoint,
785
828
  ) => {
829
+ // Prime the assistant row anchor for LLM call 1 — production code
830
+ // emits this from `AgentLoop.run` just before `provider.sendMessage`.
831
+ await onEvent({ type: "llm_call_started" });
786
832
  await onEvent({
787
833
  type: "message_complete",
788
834
  message: {
@@ -808,6 +854,9 @@ describe("session-agent-loop", () => {
808
854
  hasToolUse: true,
809
855
  history: messages,
810
856
  });
857
+ // Prime the anchor again for LLM call 2 — multi-call agent turns
858
+ // reserve a fresh assistant row per LLM call.
859
+ await onEvent({ type: "llm_call_started" });
811
860
  await onEvent({
812
861
  type: "message_complete",
813
862
  message: {
@@ -1064,6 +1113,9 @@ describe("session-agent-loop", () => {
1064
1113
  const events: ServerMessage[] = [];
1065
1114
 
1066
1115
  const agentLoopRun: AgentLoopRun = async (messages, onEvent) => {
1116
+ // Prime the assistant row anchor — production code emits this from
1117
+ // `AgentLoop.run` just before `provider.sendMessage`.
1118
+ await onEvent({ type: "llm_call_started" });
1067
1119
  // Simulate tool_use + error during execution
1068
1120
  onEvent({
1069
1121
  type: "tool_use",
@@ -1113,6 +1165,9 @@ describe("session-agent-loop", () => {
1113
1165
  const events: ServerMessage[] = [];
1114
1166
 
1115
1167
  const agentLoopRun: AgentLoopRun = async (messages, onEvent) => {
1168
+ // Prime the assistant row anchor — production code emits this from
1169
+ // `AgentLoop.run` just before `provider.sendMessage`.
1170
+ await onEvent({ type: "llm_call_started" });
1116
1171
  onEvent({
1117
1172
  type: "message_complete",
1118
1173
  message: {
@@ -1173,6 +1228,9 @@ describe("session-agent-loop", () => {
1173
1228
  };
1174
1229
 
1175
1230
  const agentLoopRun: AgentLoopRun = async (messages, onEvent) => {
1231
+ // Prime the assistant row anchor — production code emits this from
1232
+ // `AgentLoop.run` just before `provider.sendMessage`.
1233
+ await onEvent({ type: "llm_call_started" });
1176
1234
  onEvent({
1177
1235
  type: "message_complete",
1178
1236
  message: {
@@ -1238,6 +1296,9 @@ describe("session-agent-loop", () => {
1238
1296
  };
1239
1297
 
1240
1298
  const agentLoopRun: AgentLoopRun = async (messages, onEvent) => {
1299
+ // Prime the assistant row anchor — production code emits this from
1300
+ // `AgentLoop.run` just before `provider.sendMessage`.
1301
+ await onEvent({ type: "llm_call_started" });
1241
1302
  onEvent({
1242
1303
  type: "message_complete",
1243
1304
  message: {
@@ -1320,6 +1381,9 @@ describe("session-agent-loop", () => {
1320
1381
  };
1321
1382
 
1322
1383
  const agentLoopRun: AgentLoopRun = async (messages, onEvent) => {
1384
+ // Prime the assistant row anchor — production code emits this from
1385
+ // `AgentLoop.run` just before `provider.sendMessage`.
1386
+ await onEvent({ type: "llm_call_started" });
1323
1387
  onEvent({
1324
1388
  type: "message_complete",
1325
1389
  message: {
@@ -1388,6 +1452,9 @@ describe("session-agent-loop", () => {
1388
1452
  }> = [];
1389
1453
 
1390
1454
  const agentLoopRun: AgentLoopRun = async (messages, onEvent) => {
1455
+ // Prime the assistant row anchor — production code emits this from
1456
+ // `AgentLoop.run` just before `provider.sendMessage`.
1457
+ await onEvent({ type: "llm_call_started" });
1391
1458
  onEvent({ type: "text_delta", text: "Hi." });
1392
1459
  onEvent({
1393
1460
  type: "message_complete",
@@ -1463,6 +1530,9 @@ describe("session-agent-loop", () => {
1463
1530
  }> = [];
1464
1531
 
1465
1532
  const agentLoopRun: AgentLoopRun = async (messages, onEvent) => {
1533
+ // Prime the assistant row anchor — production code emits this from
1534
+ // `AgentLoop.run` just before `provider.sendMessage`.
1535
+ await onEvent({ type: "llm_call_started" });
1466
1536
  // No text_delta — pure tool-call response
1467
1537
  onEvent({
1468
1538
  type: "message_complete",
@@ -1526,6 +1596,9 @@ describe("session-agent-loop", () => {
1526
1596
  const events: ServerMessage[] = [];
1527
1597
 
1528
1598
  const agentLoopRun: AgentLoopRun = async (messages, onEvent) => {
1599
+ // Prime the assistant row anchor — production code emits this from
1600
+ // `AgentLoop.run` just before `provider.sendMessage`.
1601
+ await onEvent({ type: "llm_call_started" });
1529
1602
  onEvent({
1530
1603
  type: "message_complete",
1531
1604
  message: {
@@ -1638,6 +1711,9 @@ describe("session-agent-loop", () => {
1638
1711
  });
1639
1712
 
1640
1713
  const agentLoopRun: AgentLoopRun = async (messages, onEvent) => {
1714
+ // Prime the assistant row anchor — production code emits this from
1715
+ // `AgentLoop.run` just before `provider.sendMessage`.
1716
+ await onEvent({ type: "llm_call_started" });
1641
1717
  onEvent({
1642
1718
  type: "message_complete",
1643
1719
  message: {
@@ -1728,6 +1804,11 @@ describe("session-agent-loop", () => {
1728
1804
  };
1729
1805
 
1730
1806
  const agentLoopRun: AgentLoopRun = async (messages, onEvent) => {
1807
+ // Prime the assistant row anchor — production code emits this from
1808
+ // `AgentLoop.run` just before `provider.sendMessage`. Retry branches
1809
+ // need this on every invocation: each agent-loop iteration reserves
1810
+ // its own row.
1811
+ await onEvent({ type: "llm_call_started" });
1731
1812
  callCount++;
1732
1813
  if (callCount === 1) {
1733
1814
  onEvent({
@@ -1855,6 +1936,11 @@ describe("session-agent-loop", () => {
1855
1936
  };
1856
1937
 
1857
1938
  const agentLoopRun: AgentLoopRun = async (messages, onEvent) => {
1939
+ // Prime the assistant row anchor — production code emits this from
1940
+ // `AgentLoop.run` just before `provider.sendMessage`. Retry branches
1941
+ // need this on every invocation: each agent-loop iteration reserves
1942
+ // its own row.
1943
+ await onEvent({ type: "llm_call_started" });
1858
1944
  callCount++;
1859
1945
  if (callCount === 1) {
1860
1946
  onEvent({
@@ -1940,6 +2026,11 @@ describe("session-agent-loop", () => {
1940
2026
  mockOverflowAction = "auto_compress_latest_turn";
1941
2027
 
1942
2028
  const agentLoopRun: AgentLoopRun = async (messages, onEvent) => {
2029
+ // Prime the assistant row anchor — production code emits this from
2030
+ // `AgentLoop.run` just before `provider.sendMessage`. Retry branches
2031
+ // need this on every invocation: each agent-loop iteration reserves
2032
+ // its own row.
2033
+ await onEvent({ type: "llm_call_started" });
1943
2034
  callCount++;
1944
2035
  if (callCount <= 2) {
1945
2036
  onEvent({
@@ -2237,6 +2328,9 @@ describe("session-agent-loop", () => {
2237
2328
 
2238
2329
  const agentLoopRun: AgentLoopRun = async (messages, onEvent) => {
2239
2330
  agentLoopCalls++;
2331
+ // Prime the assistant row anchor — production code emits this from
2332
+ // `AgentLoop.run` just before `provider.sendMessage`.
2333
+ await onEvent({ type: "llm_call_started" });
2240
2334
  onEvent({
2241
2335
  type: "message_complete",
2242
2336
  message: {
@@ -2285,6 +2379,11 @@ describe("session-agent-loop", () => {
2285
2379
  let callCount = 0;
2286
2380
 
2287
2381
  const agentLoopRun: AgentLoopRun = async (messages, onEvent) => {
2382
+ // Prime the assistant row anchor — production code emits this from
2383
+ // `AgentLoop.run` just before `provider.sendMessage`. Retry branches
2384
+ // need this on every invocation: each agent-loop iteration reserves
2385
+ // its own row.
2386
+ await onEvent({ type: "llm_call_started" });
2288
2387
  callCount++;
2289
2388
  if (callCount === 1) {
2290
2389
  onEvent({
@@ -2369,6 +2468,11 @@ describe("session-agent-loop", () => {
2369
2468
  _reqId,
2370
2469
  onCheckpoint,
2371
2470
  ) => {
2471
+ // Prime the assistant row anchor — production code emits this from
2472
+ // `AgentLoop.run` just before `provider.sendMessage`. Retry branches
2473
+ // need this on every invocation: each agent-loop iteration reserves
2474
+ // its own row.
2475
+ await onEvent({ type: "llm_call_started" });
2372
2476
  // Simulate tool use followed by checkpoint
2373
2477
  onEvent({ type: "tool_use", id: "tu-1", name: "file_read", input: {} });
2374
2478
  onEvent({
@@ -2442,6 +2546,11 @@ describe("session-agent-loop", () => {
2442
2546
  _reqId,
2443
2547
  onCheckpoint,
2444
2548
  ) => {
2549
+ // Prime the assistant row anchor — production code emits this from
2550
+ // `AgentLoop.run` just before `provider.sendMessage`. Retry branches
2551
+ // need this on every invocation: each agent-loop iteration reserves
2552
+ // its own row.
2553
+ await onEvent({ type: "llm_call_started" });
2445
2554
  onEvent({ type: "tool_use", id: "tu-1", name: "file_read", input: {} });
2446
2555
  onEvent({
2447
2556
  type: "tool_result",
@@ -2504,6 +2613,9 @@ describe("session-agent-loop", () => {
2504
2613
  const abortController = new AbortController();
2505
2614
 
2506
2615
  const agentLoopRun: AgentLoopRun = async (messages, onEvent) => {
2616
+ // Prime the assistant row anchor — production code emits this from
2617
+ // `AgentLoop.run` just before `provider.sendMessage`.
2618
+ await onEvent({ type: "llm_call_started" });
2507
2619
  onEvent({
2508
2620
  type: "message_complete",
2509
2621
  message: {
@@ -2564,6 +2676,9 @@ describe("session-agent-loop", () => {
2564
2676
  resolveAssistantAttachmentsMock.mockClear();
2565
2677
 
2566
2678
  const agentLoopRun: AgentLoopRun = async (messages, onEvent) => {
2679
+ // Prime the assistant row anchor — production code emits this from
2680
+ // `AgentLoop.run` just before `provider.sendMessage`.
2681
+ await onEvent({ type: "llm_call_started" });
2567
2682
  onEvent({
2568
2683
  type: "message_complete",
2569
2684
  message: {
@@ -2603,6 +2718,9 @@ describe("session-agent-loop", () => {
2603
2718
  test("increments turnCount after successful run", async () => {
2604
2719
  const ctx = makeCtx({
2605
2720
  agentLoopRun: async (messages, onEvent) => {
2721
+ // Prime the assistant row anchor — production code emits this from
2722
+ // `AgentLoop.run` just before `provider.sendMessage`.
2723
+ await onEvent({ type: "llm_call_started" });
2606
2724
  onEvent({
2607
2725
  type: "message_complete",
2608
2726
  message: {
@@ -2636,6 +2754,9 @@ describe("session-agent-loop", () => {
2636
2754
  test("clears processing state and abort controller", async () => {
2637
2755
  const ctx = makeCtx({
2638
2756
  agentLoopRun: async (messages, onEvent) => {
2757
+ // Prime the assistant row anchor — production code emits this from
2758
+ // `AgentLoop.run` just before `provider.sendMessage`.
2759
+ await onEvent({ type: "llm_call_started" });
2639
2760
  onEvent({
2640
2761
  type: "message_complete",
2641
2762
  message: {
@@ -2699,8 +2820,13 @@ describe("session-agent-loop", () => {
2699
2820
  const ctx = makeCtx({
2700
2821
  agentLoopRun: async (
2701
2822
  messages: Message[],
2702
- onEvent: (event: AgentEvent) => void,
2823
+ onEvent: (event: AgentEvent) => void | Promise<void>,
2703
2824
  ) => {
2825
+ // Prime the assistant row anchor — production code emits this from
2826
+ // `AgentLoop.run` just before `provider.sendMessage`. Must be
2827
+ // awaited so the assistant row is reserved before message_complete
2828
+ // tries to write into it.
2829
+ await onEvent({ type: "llm_call_started" });
2704
2830
  onEvent({
2705
2831
  type: "message_complete",
2706
2832
  message: {
@@ -3044,6 +3170,280 @@ describe("session-agent-loop", () => {
3044
3170
  });
3045
3171
  });
3046
3172
 
3173
+ describe("B3 pre-allocation: indexing + cleanup", () => {
3174
+ test("handleMessageComplete indexes and projects the finalized assistant row", async () => {
3175
+ // The pre-B3 path inserted assistant rows via `addMessage`, which ran
3176
+ // the memory indexer and the conversation-attention projector as
3177
+ // side-effects of the insert. B3 splits the write into
3178
+ // `reserveMessage` + `updateMessageContent`, both of which are CRUD-only,
3179
+ // so the indexing + projection calls had to be re-driven explicitly
3180
+ // after `updateContent` succeeds. Codex P1 caught a regression where
3181
+ // this path was missing entirely; this test pins it down.
3182
+ mockMessageById = {
3183
+ id: "msg-reserve",
3184
+ conversationId: "test-conv",
3185
+ createdAt: 1234567,
3186
+ role: "assistant",
3187
+ content: "[]",
3188
+ metadata: null,
3189
+ };
3190
+ // Force attention projection to report a state change so we also
3191
+ // observe the sync-invalidation publish path on the same turn.
3192
+ projectAssistantMessageMock.mockImplementationOnce(() => true);
3193
+
3194
+ const agentLoopRun: AgentLoopRun = async (messages, onEvent) => {
3195
+ await onEvent({ type: "llm_call_started" });
3196
+ // `message_complete` is awaited so `handleMessageComplete` (and its
3197
+ // async indexer + projector chain) completes before the next event
3198
+ // or before the loop returns. Without the await the projector's
3199
+ // synchronous call still races against the test's assertion phase
3200
+ // because the indexer's `await` yields microtasks.
3201
+ await onEvent({
3202
+ type: "message_complete",
3203
+ message: {
3204
+ role: "assistant",
3205
+ content: [{ type: "text", text: "indexed reply" }],
3206
+ },
3207
+ });
3208
+ onEvent({
3209
+ type: "usage",
3210
+ inputTokens: 10,
3211
+ outputTokens: 5,
3212
+ model: "test",
3213
+ providerDurationMs: 50,
3214
+ });
3215
+ return [
3216
+ ...messages,
3217
+ {
3218
+ role: "assistant" as const,
3219
+ content: [
3220
+ { type: "text", text: "indexed reply" },
3221
+ ] as ContentBlock[],
3222
+ },
3223
+ ];
3224
+ };
3225
+
3226
+ const ctx = makeCtx({ agentLoopRun });
3227
+ await runAgentLoopImpl(ctx, "hi", "msg-1", () => {});
3228
+
3229
+ // Indexer fired with the reserved row's id + the finalized content.
3230
+ expect(indexMessageNowMock).toHaveBeenCalledTimes(1);
3231
+ const indexCallArgs = indexMessageNowMock.mock.calls[0] as unknown as [
3232
+ {
3233
+ messageId: string;
3234
+ conversationId: string;
3235
+ role: string;
3236
+ content: string;
3237
+ createdAt: number;
3238
+ scopeId: string;
3239
+ },
3240
+ unknown,
3241
+ ];
3242
+ const indexCall = indexCallArgs[0];
3243
+ expect(indexCall).toMatchObject({
3244
+ messageId: "msg-reserve",
3245
+ conversationId: "test-conv",
3246
+ role: "assistant",
3247
+ createdAt: 1234567,
3248
+ scopeId: "default",
3249
+ });
3250
+ expect(indexCall.content).toContain("indexed reply");
3251
+
3252
+ // Attention projector fired with the same row coordinates.
3253
+ expect(projectAssistantMessageMock).toHaveBeenCalledTimes(1);
3254
+ const projectCall = projectAssistantMessageMock.mock
3255
+ .calls[0] as unknown as [
3256
+ { conversationId: string; messageId: string; messageAt: number },
3257
+ ];
3258
+ expect(projectCall[0]).toEqual({
3259
+ conversationId: "test-conv",
3260
+ messageId: "msg-reserve",
3261
+ messageAt: 1234567,
3262
+ });
3263
+
3264
+ // Projection reported a state change → sync invalidation fires with
3265
+ // the conversation `:metadata` tag. The mock also receives a
3266
+ // `:messages` invalidation from the orchestrator's
3267
+ // `publishLoopMessagesChanged` post-loop emit, so we filter by tag
3268
+ // rather than asserting a total call count.
3269
+ const metadataPublishes = (
3270
+ publishSyncInvalidationMock.mock.calls as unknown as Array<[string[]]>
3271
+ ).filter((args) => args[0]?.includes("conversation:test-conv:metadata"));
3272
+ expect(metadataPublishes).toHaveLength(1);
3273
+ });
3274
+
3275
+ test("handleMessageComplete skips sync invalidation when attention state unchanged", async () => {
3276
+ // Mirror of the previous test but with the default projector return
3277
+ // (`false`). The projection still runs every turn, but the sync
3278
+ // invalidation publish must be gated on attention-state movement to
3279
+ // avoid flooding clients with no-op metadata refreshes.
3280
+ mockMessageById = {
3281
+ id: "msg-reserve",
3282
+ conversationId: "test-conv",
3283
+ createdAt: 999,
3284
+ role: "assistant",
3285
+ content: "[]",
3286
+ metadata: null,
3287
+ };
3288
+
3289
+ const agentLoopRun: AgentLoopRun = async (messages, onEvent) => {
3290
+ await onEvent({ type: "llm_call_started" });
3291
+ // See sibling test — `message_complete` must be awaited so the
3292
+ // projector call lands before the assertion phase.
3293
+ await onEvent({
3294
+ type: "message_complete",
3295
+ message: {
3296
+ role: "assistant",
3297
+ content: [{ type: "text", text: "quiet" }],
3298
+ },
3299
+ });
3300
+ onEvent({
3301
+ type: "usage",
3302
+ inputTokens: 1,
3303
+ outputTokens: 1,
3304
+ model: "test",
3305
+ providerDurationMs: 1,
3306
+ });
3307
+ return [
3308
+ ...messages,
3309
+ {
3310
+ role: "assistant" as const,
3311
+ content: [{ type: "text", text: "quiet" }] as ContentBlock[],
3312
+ },
3313
+ ];
3314
+ };
3315
+
3316
+ const ctx = makeCtx({ agentLoopRun });
3317
+ await runAgentLoopImpl(ctx, "hi", "msg-1", () => {});
3318
+
3319
+ expect(projectAssistantMessageMock).toHaveBeenCalledTimes(1);
3320
+ // The mock will still receive a `:messages` invalidation from the
3321
+ // orchestrator's `publishLoopMessagesChanged` — filter to the
3322
+ // `:metadata` tag and assert it never landed.
3323
+ const metadataPublishes = (
3324
+ publishSyncInvalidationMock.mock.calls as unknown as Array<[string[]]>
3325
+ ).filter((args) => args[0]?.includes("conversation:test-conv:metadata"));
3326
+ expect(metadataPublishes).toHaveLength(0);
3327
+ });
3328
+
3329
+ test("handleLlmCallStarted deletes a stranded reservation before reserving a new row", async () => {
3330
+ // Simulates a retry path: the first LLM call reserves an assistant row
3331
+ // but exits without `message_complete` (e.g. context-overflow rescue,
3332
+ // ordering-error rescue, image-overflow rescue). The next
3333
+ // `llm_call_started` must delete the stranded row so the transcript
3334
+ // does not accumulate empty assistant bubbles.
3335
+ reserveMessageMock
3336
+ .mockImplementationOnce(async () => ({ id: "msg-strand-A" }))
3337
+ .mockImplementationOnce(async () => ({ id: "msg-strand-B" }));
3338
+ // Indexer/projector mocks default to no-op; no finalized row in this
3339
+ // test, so `mockMessageById` stays null.
3340
+
3341
+ const agentLoopRun: AgentLoopRun = async (messages, onEvent) => {
3342
+ // First LLM call: reserve msg-strand-A, never finalize.
3343
+ await onEvent({ type: "llm_call_started" });
3344
+ // Second LLM call: should delete msg-strand-A before reserving
3345
+ // msg-strand-B.
3346
+ await onEvent({ type: "llm_call_started" });
3347
+ // Finalize the second one so the loop has a valid assistant message
3348
+ // and exits cleanly.
3349
+ onEvent({
3350
+ type: "message_complete",
3351
+ message: {
3352
+ role: "assistant",
3353
+ content: [{ type: "text", text: "retry succeeded" }],
3354
+ },
3355
+ });
3356
+ onEvent({
3357
+ type: "usage",
3358
+ inputTokens: 5,
3359
+ outputTokens: 3,
3360
+ model: "test",
3361
+ providerDurationMs: 25,
3362
+ });
3363
+ return [
3364
+ ...messages,
3365
+ {
3366
+ role: "assistant" as const,
3367
+ content: [
3368
+ { type: "text", text: "retry succeeded" },
3369
+ ] as ContentBlock[],
3370
+ },
3371
+ ];
3372
+ };
3373
+
3374
+ const ctx = makeCtx({ agentLoopRun });
3375
+ await runAgentLoopImpl(ctx, "hi", "msg-1", () => {});
3376
+
3377
+ // Exactly one delete fires — for msg-strand-A, before the second
3378
+ // reserve. The second reservation is committed via `updateContent`
3379
+ // (not deleted), and after the run completes
3380
+ // `assistantRowAwaitingFinalization` is false, so no further delete
3381
+ // is attempted on shutdown.
3382
+ expect(deleteMessageByIdMock).toHaveBeenCalledTimes(1);
3383
+ const strandDeleteCall = deleteMessageByIdMock.mock
3384
+ .calls[0] as unknown as [string];
3385
+ expect(strandDeleteCall[0]).toBe("msg-strand-A");
3386
+ expect(reserveMessageMock).toHaveBeenCalledTimes(2);
3387
+ });
3388
+
3389
+ test("provider-error branch deletes the orphaned reservation and repoints lastAssistantMessageId", async () => {
3390
+ // Codex P2 regression: B3 reserves an empty assistant row at
3391
+ // `llm_call_started`. When the call exits via the provider-error
3392
+ // branch (no `message_complete`), the synthetic error message is
3393
+ // inserted separately. Without cleanup the transcript would carry
3394
+ // both the empty reserved row AND the error message, and
3395
+ // `syncLastAssistantMessageToDisk` (which reads
3396
+ // `state.lastAssistantMessageId`) would mis-target the deleted
3397
+ // reservation id.
3398
+ reserveMessageMock.mockImplementationOnce(async () => ({
3399
+ id: "msg-orphaned-reservation",
3400
+ }));
3401
+
3402
+ const agentLoopRun: AgentLoopRun = async (messages, onEvent) => {
3403
+ // Reserve the orphan.
3404
+ await onEvent({ type: "llm_call_started" });
3405
+ // Provider rejects — writes the llm_request_log row and arms
3406
+ // `state.providerErrorUserMessage` via `handleError`.
3407
+ onEvent({
3408
+ type: "provider_error",
3409
+ error: new Error("upstream 500"),
3410
+ rawRequest: { model: "gpt-4.1", messages: [] },
3411
+ actualProvider: "openai",
3412
+ });
3413
+ onEvent({
3414
+ type: "error",
3415
+ error: new Error("upstream 500"),
3416
+ });
3417
+ // No assistant message in the result — the synthetic-error branch
3418
+ // below the agent loop fires.
3419
+ return messages;
3420
+ };
3421
+
3422
+ const ctx = makeCtx({ agentLoopRun });
3423
+ await runAgentLoopImpl(ctx, "hi", "msg-1", () => {});
3424
+
3425
+ // The orphan was deleted exactly once, before the synthetic error
3426
+ // message landed.
3427
+ expect(deleteMessageByIdMock).toHaveBeenCalledTimes(1);
3428
+ const deleteCall = deleteMessageByIdMock.mock.calls[0] as unknown as [
3429
+ string,
3430
+ ];
3431
+ expect(deleteCall[0]).toBe("msg-orphaned-reservation");
3432
+
3433
+ // Post-loop `syncLastAssistantMessageToDisk` targets the synthetic
3434
+ // error row's id (`mock-msg-id` from the mocked `addMessage`), NOT
3435
+ // the deleted reservation id. This is the externally-observable
3436
+ // proof that `state.lastAssistantMessageId` was repointed.
3437
+ expect(syncMessageToDiskMock).toHaveBeenCalled();
3438
+ const syncCalls = syncMessageToDiskMock.mock.calls as unknown as Array<
3439
+ [string, string, number]
3440
+ >;
3441
+ const lastSync = syncCalls[syncCalls.length - 1];
3442
+ expect(lastSync?.[1]).toBe("mock-msg-id");
3443
+ expect(lastSync?.[1]).not.toBe("msg-orphaned-reservation");
3444
+ });
3445
+ });
3446
+
3047
3447
  describe("pkbSystemReminderBlock metadata persistence", () => {
3048
3448
  test("persists pkbSystemReminderBlock in full mode with PKB active", async () => {
3049
3449
  const reminder = "<system_reminder>\npkb content\n</system_reminder>";
@@ -3908,6 +4308,11 @@ describe("session-agent-loop", () => {
3908
4308
  let callCount = 0;
3909
4309
  const agentLoopRun: AgentLoopRun = async (messages, onEvent) => {
3910
4310
  callCount++;
4311
+ // Prime the assistant row anchor — production code emits this from
4312
+ // `AgentLoop.run` just before `provider.sendMessage`. Retry branches
4313
+ // need this on every invocation: each agent-loop iteration reserves
4314
+ // its own row.
4315
+ await onEvent({ type: "llm_call_started" });
3911
4316
  if (callCount === 1) {
3912
4317
  // Trigger convergence path: error + appended assistant message so
3913
4318
  // updatedHistory.length > preRunHistoryLength at the strip site.
@@ -3968,5 +4373,86 @@ describe("session-agent-loop", () => {
3968
4373
  );
3969
4374
  expect(stripCalls.length).toBeGreaterThanOrEqual(1);
3970
4375
  });
4376
+
4377
+ test("strip-site marker write is non-fatal when the helper throws", async () => {
4378
+ setConversationHistoryStrippedAtMock.mockImplementation(() => {
4379
+ throw new Error("db write failed");
4380
+ });
4381
+
4382
+ mockReducerStepFn = (msgs: Message[]) => ({
4383
+ messages: msgs,
4384
+ tier: "forced_compaction",
4385
+ state: {
4386
+ appliedTiers: ["forced_compaction"],
4387
+ injectionMode: "full",
4388
+ exhausted: false,
4389
+ },
4390
+ estimatedTokens: 5000,
4391
+ });
4392
+
4393
+ let callCount = 0;
4394
+ const agentLoopRun: AgentLoopRun = async (messages, onEvent) => {
4395
+ callCount++;
4396
+ // Prime the assistant row anchor — production code emits this from
4397
+ // `AgentLoop.run` just before `provider.sendMessage`. Retry branches
4398
+ // need this on every invocation: each agent-loop iteration reserves
4399
+ // its own row.
4400
+ await onEvent({ type: "llm_call_started" });
4401
+ if (callCount === 1) {
4402
+ onEvent({
4403
+ type: "error",
4404
+ error: new Error("context_length_exceeded"),
4405
+ });
4406
+ onEvent({
4407
+ type: "usage",
4408
+ inputTokens: 100,
4409
+ outputTokens: 0,
4410
+ model: "test-model",
4411
+ providerDurationMs: 50,
4412
+ });
4413
+ return [
4414
+ ...messages,
4415
+ {
4416
+ role: "assistant" as const,
4417
+ content: [{ type: "text", text: "partial" }] as ContentBlock[],
4418
+ },
4419
+ ];
4420
+ }
4421
+ onEvent({
4422
+ type: "message_complete",
4423
+ message: {
4424
+ role: "assistant",
4425
+ content: [{ type: "text", text: "recovered" }],
4426
+ },
4427
+ });
4428
+ onEvent({
4429
+ type: "usage",
4430
+ inputTokens: 50,
4431
+ outputTokens: 25,
4432
+ model: "test-model",
4433
+ providerDurationMs: 100,
4434
+ });
4435
+ return [
4436
+ ...messages,
4437
+ {
4438
+ role: "assistant" as const,
4439
+ content: [{ type: "text", text: "recovered" }] as ContentBlock[],
4440
+ },
4441
+ ];
4442
+ };
4443
+
4444
+ const ctx = makeCtx({
4445
+ agentLoopRun,
4446
+ contextWindowManager: {
4447
+ shouldCompact: () => ({ needed: false, estimatedTokens: 0 }),
4448
+ maybeCompact: async () => ({ compacted: false }),
4449
+ } as unknown as AgentLoopConversationContext["contextWindowManager"],
4450
+ });
4451
+
4452
+ // Must not throw — the strip-site marker write is wrapped in try/catch.
4453
+ await expect(
4454
+ runAgentLoopImpl(ctx, "hello", "msg-1", () => {}),
4455
+ ).resolves.toBeUndefined();
4456
+ });
3971
4457
  });
3972
4458
  });