@vellumai/assistant 0.8.4 → 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 (802) hide show
  1. package/AGENTS.md +33 -1
  2. package/ARCHITECTURE.md +3 -3
  3. package/bunfig.toml +6 -1
  4. package/docs/browser-use-architecture-phase2.md +1 -1
  5. package/docs/credential-execution-service.md +6 -6
  6. package/docs/plugins.md +4 -3
  7. package/knip.json +2 -1
  8. package/node_modules/@vellumai/skill-host-contracts/src/client.ts +12 -13
  9. package/node_modules/@vellumai/skill-host-contracts/src/skill-host.ts +4 -1
  10. package/node_modules/@vellumai/skill-host-contracts/src/tool-types.ts +16 -14
  11. package/openapi.yaml +2748 -216
  12. package/package.json +1 -1
  13. package/src/__tests__/actor-token-service.test.ts +3 -2
  14. package/src/__tests__/agent-loop-exit-reason.test.ts +102 -9
  15. package/src/__tests__/agent-loop-override-profile.test.ts +2 -1
  16. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +1 -0
  17. package/src/__tests__/agent-wake-override-profile.test.ts +1 -0
  18. package/src/__tests__/always-loaded-tools-guard.test.ts +2 -2
  19. package/src/__tests__/annotate-risk-options.test.ts +1 -0
  20. package/src/__tests__/anthropic-provider.test.ts +34 -37
  21. package/src/__tests__/approval-cascade.test.ts +1 -0
  22. package/src/__tests__/approval-routes-http.test.ts +9 -13
  23. package/src/__tests__/assert-not-live-db.ts +79 -0
  24. package/src/__tests__/assistant-event-hub-self-exclusion.test.ts +293 -0
  25. package/src/__tests__/assistant-feature-flags-integration.test.ts +12 -28
  26. package/src/__tests__/audit-log-rotation.test.ts +72 -18
  27. package/src/__tests__/auto-analysis-end-to-end.test.ts +6 -6
  28. package/src/__tests__/background-workers-disk-pressure.test.ts +8 -11
  29. package/src/__tests__/browser-skill-endstate.test.ts +3 -3
  30. package/src/__tests__/btw-routes.test.ts +5 -5
  31. package/src/__tests__/call-controller.test.ts +3 -3
  32. package/src/__tests__/cancel-resolves-conversation-key.test.ts +1 -1
  33. package/src/__tests__/channel-approval-routes.test.ts +3 -2
  34. package/src/__tests__/channel-guardian.test.ts +6 -5
  35. package/src/__tests__/channel-readiness-slack-remote.test.ts +175 -0
  36. package/src/__tests__/channel-reply-delivery.test.ts +35 -0
  37. package/src/__tests__/channel-retry-sweep.test.ts +320 -3
  38. package/src/__tests__/checker.test.ts +18 -27
  39. package/src/__tests__/compaction-events.test.ts +2 -0
  40. package/src/__tests__/compaction-trail-store.test.ts +264 -0
  41. package/src/__tests__/compactor-call-site-logging.test.ts +215 -0
  42. package/src/__tests__/compactor-preserved-tail-count.test.ts +1 -0
  43. package/src/__tests__/computer-use-skill-manifest-regression.test.ts +12 -16
  44. package/src/__tests__/computer-use-tools.test.ts +14 -18
  45. package/src/__tests__/config-loader-backfill.test.ts +13 -28
  46. package/src/__tests__/config-loader-corrupt.test.ts +5 -5
  47. package/src/__tests__/config-loader-platform-defaults.test.ts +93 -26
  48. package/src/__tests__/config-loader-quarantine-bulletin.test.ts +3 -3
  49. package/src/__tests__/config-managed-gemini-defaults.test.ts +3 -4
  50. package/src/__tests__/config-schema.test.ts +10 -10
  51. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +0 -1
  52. package/src/__tests__/connection-model-compat.test.ts +83 -0
  53. package/src/__tests__/contacts-tools.test.ts +3 -2
  54. package/src/__tests__/context-token-estimator.test.ts +22 -0
  55. package/src/__tests__/conversation-abort-tool-results.test.ts +5 -0
  56. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +2 -1
  57. package/src/__tests__/conversation-agent-loop-handlers-max-tokens.test.ts +55 -0
  58. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +2 -1
  59. package/src/__tests__/conversation-agent-loop-overflow.test.ts +231 -2
  60. package/src/__tests__/conversation-agent-loop.test.ts +581 -54
  61. package/src/__tests__/conversation-analysis-routes.test.ts +1 -0
  62. package/src/__tests__/conversation-app-control-instantiation.test.ts +31 -24
  63. package/src/__tests__/conversation-app-control-lifecycle.test.ts +1 -0
  64. package/src/__tests__/conversation-attention-store.test.ts +101 -0
  65. package/src/__tests__/conversation-attention-telegram.test.ts +3 -2
  66. package/src/__tests__/conversation-clear-safety.test.ts +25 -25
  67. package/src/__tests__/conversation-confirmation-signals.test.ts +1 -0
  68. package/src/__tests__/conversation-delete-schedule-cleanup.test.ts +1 -1
  69. package/src/__tests__/conversation-disk-view-integration.test.ts +2 -2
  70. package/src/__tests__/conversation-error.test.ts +61 -0
  71. package/src/__tests__/conversation-fork-crud.test.ts +239 -15
  72. package/src/__tests__/conversation-fork-route.test.ts +3 -2
  73. package/src/__tests__/conversation-history-web-search.test.ts +1 -0
  74. package/src/__tests__/conversation-inference-profile-list.test.ts +3 -2
  75. package/src/__tests__/conversation-inference-profile-route.test.ts +3 -2
  76. package/src/__tests__/conversation-lifecycle.test.ts +53 -11
  77. package/src/__tests__/conversation-list-source.test.ts +3 -2
  78. package/src/__tests__/conversation-load-history-repair.test.ts +2 -1
  79. package/src/__tests__/{conversation-load-cleaned-at.test.ts → conversation-load-history-stripped.test.ts} +14 -13
  80. package/src/__tests__/conversation-pairing.test.ts +53 -0
  81. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +26 -7
  82. package/src/__tests__/conversation-process-callsite.test.ts +1 -0
  83. package/src/__tests__/conversation-provider-retry-repair.test.ts +6 -0
  84. package/src/__tests__/conversation-queue.test.ts +333 -291
  85. package/src/__tests__/conversation-routes-disk-view.test.ts +112 -18
  86. package/src/__tests__/conversation-routes-guardian-reply.test.ts +33 -8
  87. package/src/__tests__/conversation-routes-slash-commands.test.ts +68 -2
  88. package/src/__tests__/conversation-runtime-assembly.test.ts +78 -0
  89. package/src/__tests__/conversation-skill-tools.test.ts +40 -147
  90. package/src/__tests__/conversation-slash-queue.test.ts +84 -32
  91. package/src/__tests__/conversation-slash-unknown.test.ts +5 -0
  92. package/src/__tests__/conversation-speed-override.test.ts +1 -0
  93. package/src/__tests__/conversation-store.test.ts +1 -1
  94. package/src/__tests__/conversation-surfaces-action-delivery.test.ts +46 -0
  95. package/src/__tests__/conversation-surfaces-data-persist.test.ts +1 -0
  96. package/src/__tests__/conversation-surfaces-standalone-payloads.test.ts +6 -3
  97. package/src/__tests__/conversation-surfaces-standalone.test.ts +6 -3
  98. package/src/__tests__/conversation-surfaces-state-update.test.ts +3 -3
  99. package/src/__tests__/conversation-surfaces-table-action.test.ts +7 -17
  100. package/src/__tests__/conversation-sync-tags.test.ts +218 -35
  101. package/src/__tests__/conversation-title-service.test.ts +1 -0
  102. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +30 -0
  103. package/src/__tests__/conversation-usage.test.ts +1 -0
  104. package/src/__tests__/conversation-workspace-cache-state.test.ts +2 -0
  105. package/src/__tests__/conversation-workspace-injection.test.ts +6 -1
  106. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +6 -1
  107. package/src/__tests__/credential-broker-browser-fill.test.ts +3 -3
  108. package/src/__tests__/credential-broker-server-use.test.ts +5 -5
  109. package/src/__tests__/credential-execution-client.test.ts +72 -1
  110. package/src/__tests__/credential-execution-feature-gates.test.ts +19 -19
  111. package/src/__tests__/credential-execution-tools.test.ts +6 -6
  112. package/src/__tests__/credential-health-service.test.ts +252 -3
  113. package/src/__tests__/credential-security-invariants.test.ts +6 -5
  114. package/src/__tests__/credential-vault-unit.test.ts +21 -21
  115. package/src/__tests__/credential-vault.test.ts +5 -5
  116. package/src/__tests__/cross-provider-web-search.test.ts +56 -2
  117. package/src/__tests__/db-connection-isolation.test.ts +7 -6
  118. package/src/__tests__/db-conversation-fork-lineage-migration.test.ts +8 -10
  119. package/src/__tests__/db-conversation-inference-profile-migration.test.ts +7 -10
  120. package/src/__tests__/db-llm-request-log-provider-migration.test.ts +9 -15
  121. package/src/__tests__/db-test-helpers.ts +58 -0
  122. package/src/__tests__/disk-pressure-guard.test.ts +58 -41
  123. package/src/__tests__/disk-pressure-lifecycle.test.ts +13 -10
  124. package/src/__tests__/disk-pressure-routes.test.ts +0 -33
  125. package/src/__tests__/disk-pressure-tools.test.ts +0 -4
  126. package/src/__tests__/dm-persistence.test.ts +26 -40
  127. package/src/__tests__/document-create-dedupe.test.ts +189 -0
  128. package/src/__tests__/document-find-replace.test.ts +3 -2
  129. package/src/__tests__/document-tool-security.test.ts +81 -2
  130. package/src/__tests__/dynamic-page-surface.test.ts +2 -2
  131. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +5 -4
  132. package/src/__tests__/email-html-renderer.test.ts +12 -0
  133. package/src/__tests__/encrypted-store-test-helpers.ts +56 -0
  134. package/src/__tests__/encrypted-store.test.ts +11 -9
  135. package/src/__tests__/feature-flag-test-helpers.ts +53 -0
  136. package/src/__tests__/filing-service.test.ts +1 -0
  137. package/src/__tests__/first-greeting.test.ts +62 -12
  138. package/src/__tests__/gateway-flag-listener.test.ts +236 -0
  139. package/src/__tests__/gemini-provider.test.ts +104 -0
  140. package/src/__tests__/guardian-action-sweep.test.ts +3 -2
  141. package/src/__tests__/guardian-dispatch.test.ts +0 -1
  142. package/src/__tests__/guardian-outbound-http.test.ts +10 -7
  143. package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +48 -3
  144. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +2 -1
  145. package/src/__tests__/heartbeat-disk-pressure.test.ts +5 -0
  146. package/src/__tests__/heartbeat-service.test.ts +5 -0
  147. package/src/__tests__/helpers/mock-logger.ts +26 -0
  148. package/src/__tests__/host-bash-routes.test.ts +1 -0
  149. package/src/__tests__/host-cu-routes-targeted.test.ts +1 -0
  150. package/src/__tests__/host-file-routes-targeted.test.ts +1 -0
  151. package/src/__tests__/host-shell-tool.test.ts +6 -5
  152. package/src/__tests__/host-transfer-routes-targeted.test.ts +1 -0
  153. package/src/__tests__/http-conversation-lineage.test.ts +3 -2
  154. package/src/__tests__/http-user-message-parity.test.ts +29 -7
  155. package/src/__tests__/identity-intro-cache.test.ts +133 -22
  156. package/src/__tests__/inbound-slack-persistence.test.ts +44 -72
  157. package/src/__tests__/inference-profile-reaper.test.ts +3 -2
  158. package/src/__tests__/inference-profile-session-ipc.test.ts +3 -2
  159. package/src/__tests__/init-feature-flag-overrides.test.ts +5 -6
  160. package/src/__tests__/injector-disk-pressure.test.ts +3 -17
  161. package/src/__tests__/inline-skill-load-permissions.test.ts +4 -4
  162. package/src/__tests__/list-messages-hidden-metadata.test.ts +80 -0
  163. package/src/__tests__/list-messages-tool-merge.test.ts +70 -11
  164. package/src/__tests__/llm-context-normalization.test.ts +42 -0
  165. package/src/__tests__/llm-request-log-call-site.test.ts +136 -0
  166. package/src/__tests__/llm-request-log-source-clickhouse.test.ts +26 -0
  167. package/src/__tests__/llm-resolver.test.ts +408 -9
  168. package/src/__tests__/llm-schema.test.ts +1 -1
  169. package/src/__tests__/llm-usage-store.test.ts +66 -0
  170. package/src/__tests__/logger.test.ts +89 -0
  171. package/src/__tests__/manual-token-reconciliation.test.ts +76 -1
  172. package/src/__tests__/mcp-abort-signal.test.ts +16 -2
  173. package/src/__tests__/mcp-client-auth.test.ts +14 -0
  174. package/src/__tests__/media-generate-image.test.ts +31 -0
  175. package/src/__tests__/memory-v2-static-injector.test.ts +7 -7
  176. package/src/__tests__/messaging-send-tool.test.ts +1 -0
  177. package/src/__tests__/migration-import-from-url.test.ts +3 -3
  178. package/src/__tests__/mock-gateway-ipc.ts +18 -2
  179. package/src/__tests__/model-intents.test.ts +4 -6
  180. package/src/__tests__/native-web-search.test.ts +30 -2
  181. package/src/__tests__/notification-deep-link.test.ts +62 -0
  182. package/src/__tests__/notification-guardian-path.test.ts +0 -1
  183. package/src/__tests__/oauth-commands-routes.test.ts +37 -0
  184. package/src/__tests__/oauth-provider-visibility.test.ts +8 -8
  185. package/src/__tests__/oauth-store.test.ts +3 -2
  186. package/src/__tests__/onboarding-template-contract.test.ts +4 -3
  187. package/src/__tests__/openai-provider.test.ts +54 -9
  188. package/src/__tests__/openai-responses-provider.test.ts +176 -14
  189. package/src/__tests__/openrouter-provider-only.test.ts +27 -5
  190. package/src/__tests__/outbound-slack-persistence.test.ts +46 -1
  191. package/src/__tests__/pending-interactions-resolved-event.test.ts +0 -1
  192. package/src/__tests__/persistence-pipeline.test.ts +139 -1
  193. package/src/__tests__/persistence-secret-redaction.test.ts +83 -12
  194. package/src/__tests__/platform-bash-auto-approve.test.ts +2 -2
  195. package/src/__tests__/platform.test.ts +2 -2
  196. package/src/__tests__/plugin-api-tool-definition.test.ts +92 -0
  197. package/src/__tests__/plugin-bootstrap.test.ts +11 -13
  198. package/src/__tests__/plugin-tool-contribution.test.ts +50 -40
  199. package/src/__tests__/plugin-types.test.ts +3 -2
  200. package/src/__tests__/prechat-onboarding-contract.test.ts +131 -98
  201. package/src/__tests__/pricing.test.ts +12 -0
  202. package/src/__tests__/process-message-background-slack.test.ts +21 -16
  203. package/src/__tests__/process-message-display-content.test.ts +19 -22
  204. package/src/__tests__/provider-catalog-visibility.test.ts +9 -9
  205. package/src/__tests__/provider-platform-proxy-integration.test.ts +216 -4
  206. package/src/__tests__/provider-registry-ollama.test.ts +45 -22
  207. package/src/__tests__/prune-jobs-changes-parser.test.ts +61 -0
  208. package/src/__tests__/recording-handler.test.ts +1 -0
  209. package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +1 -0
  210. package/src/__tests__/registry.test.ts +84 -84
  211. package/src/__tests__/relay-server.test.ts +10 -10
  212. package/src/__tests__/require-fresh-approval.test.ts +2 -2
  213. package/src/__tests__/runtime-attachment-metadata.test.ts +3 -2
  214. package/src/__tests__/runtime-events-sse-bilingual.test.ts +154 -0
  215. package/src/__tests__/schedule-store.test.ts +16 -1
  216. package/src/__tests__/scheduler-reuse-conversation.test.ts +48 -3
  217. package/src/__tests__/secret-ingress-http.test.ts +5 -1
  218. package/src/__tests__/secure-keys.test.ts +3 -3
  219. package/src/__tests__/send-endpoint-busy.test.ts +81 -42
  220. package/src/__tests__/server-history-render.test.ts +4 -1
  221. package/src/__tests__/shell-tool-proxy-mode.test.ts +1 -1
  222. package/src/__tests__/skill-feature-flags-integration.test.ts +8 -10
  223. package/src/__tests__/skill-feature-flags.test.ts +16 -18
  224. package/src/__tests__/skill-load-feature-flag.test.ts +5 -5
  225. package/src/__tests__/skill-projection-feature-flag.test.ts +48 -37
  226. package/src/__tests__/skill-projection.benchmark.test.ts +7 -13
  227. package/src/__tests__/skill-tool-factory.test.ts +97 -96
  228. package/src/__tests__/slack-channel-config.test.ts +3 -3
  229. package/src/__tests__/subagent-call-site-routing.test.ts +11 -3
  230. package/src/__tests__/subagent-disposal.test.ts +27 -8
  231. package/src/__tests__/subagent-fork-notifications.test.ts +24 -9
  232. package/src/__tests__/subagent-fork-spawn.test.ts +13 -4
  233. package/src/__tests__/subagent-manager-notify.test.ts +20 -8
  234. package/src/__tests__/subagent-notify-parent.test.ts +6 -5
  235. package/src/__tests__/subagent-spawn-tool-fork.test.ts +58 -0
  236. package/src/__tests__/subagent-tools.test.ts +2 -1
  237. package/src/__tests__/suggestion-routes.test.ts +2 -0
  238. package/src/__tests__/sync-message-contract.test.ts +59 -0
  239. package/src/__tests__/system-prompt.test.ts +183 -131
  240. package/src/__tests__/terminal-tools.test.ts +1 -1
  241. package/src/__tests__/test-preload-verifier.ts +68 -0
  242. package/src/__tests__/test-preload.ts +32 -39
  243. package/src/__tests__/tool-approval-handler.test.ts +1 -5
  244. package/src/__tests__/tool-execute-pipeline.test.ts +2 -2
  245. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +2 -5
  246. package/src/__tests__/tool-executor-lifecycle-events.test.ts +35 -12
  247. package/src/__tests__/tool-executor.test.ts +64 -72
  248. package/src/__tests__/tool-grant-request-escalation.test.ts +1 -6
  249. package/src/__tests__/tool-preview-lifecycle.test.ts +1 -0
  250. package/src/__tests__/tool-result-metadata-plumbing.test.ts +1 -0
  251. package/src/__tests__/trusted-contact-approval-notifier.test.ts +0 -1
  252. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +1 -6
  253. package/src/__tests__/trusted-contact-multichannel.test.ts +0 -1
  254. package/src/__tests__/twilio-routes.test.ts +3 -2
  255. package/src/__tests__/ui-file-upload-surface.test.ts +2 -2
  256. package/src/__tests__/usage-routes.test.ts +3 -0
  257. package/src/__tests__/validate-input.test.ts +381 -0
  258. package/src/__tests__/verification-control-plane-policy.test.ts +3 -2
  259. package/src/__tests__/voice-scoped-grant-consumer.test.ts +2 -1
  260. package/src/__tests__/voice-session-bridge.test.ts +37 -28
  261. package/src/__tests__/workspace-git-service.test.ts +6 -5
  262. package/src/__tests__/workspace-migration-089-move-memory-tree-out-of-v3.test.ts +86 -0
  263. package/src/__tests__/workspace-migration-090-memory-router-cost-optimized-profile.test.ts +326 -0
  264. package/src/__tests__/workspace-migration-091-retighten-migration-onboarding-thread.test.ts +166 -0
  265. package/src/acp/__tests__/prepare-agent-env.test.ts +146 -0
  266. package/src/acp/prepare-agent-env.ts +78 -0
  267. package/src/acp/session-manager.ts +6 -7
  268. package/src/agent/loop.ts +88 -0
  269. package/src/api/README.md +127 -0
  270. package/src/api/constants/call-sites.ts +27 -0
  271. package/src/api/events/assistant-outbound-attachment.ts +51 -0
  272. package/src/api/events/assistant-text-delta.ts +32 -0
  273. package/src/api/events/assistant-turn-start.ts +33 -0
  274. package/src/api/events/document-comment-created.ts +48 -0
  275. package/src/api/events/document-comment-deleted.ts +24 -0
  276. package/src/api/events/document-comment-reopened.ts +25 -0
  277. package/src/api/events/document-comment-resolved.ts +27 -0
  278. package/src/api/events/generation-cancelled.ts +24 -0
  279. package/src/api/events/generation-handoff.ts +41 -0
  280. package/src/api/events/message-complete.ts +42 -0
  281. package/src/api/events/open-url.ts +30 -0
  282. package/src/api/events/relationship-state-updated.ts +25 -0
  283. package/src/api/events/tool-use-start.ts +32 -0
  284. package/src/api/index.ts +129 -0
  285. package/src/api/package.json +10 -0
  286. package/src/api/responses/llm-context-response.ts +39 -0
  287. package/src/api/responses/llm-request-log-entry.ts +93 -0
  288. package/src/api/responses/memory-recall-log.ts +65 -0
  289. package/src/api/responses/memory-v2-activation-log.ts +78 -0
  290. package/src/background-wake/background-wake-routes.test.ts +868 -0
  291. package/src/background-wake/platform-client.test.ts +308 -0
  292. package/src/background-wake/platform-client.ts +167 -0
  293. package/src/background-wake/publisher.ts +91 -0
  294. package/src/background-wake/runtime-registry.ts +24 -0
  295. package/src/background-wake/wake-intent-hooks.test.ts +282 -0
  296. package/src/calls/guardian-dispatch.ts +1 -0
  297. package/src/calls/voice-session-bridge.ts +4 -4
  298. package/src/cli/commands/__tests__/browser.test.ts +23 -5
  299. package/src/cli/commands/__tests__/conversations-slack.test.ts +16 -0
  300. package/src/cli/commands/__tests__/domain-register.test.ts +110 -0
  301. package/src/cli/commands/__tests__/domain-status.test.ts +33 -33
  302. package/src/cli/commands/__tests__/inference-send.test.ts +108 -5
  303. package/src/cli/commands/__tests__/memory-v2-compare-render.test.ts +98 -0
  304. package/src/cli/commands/__tests__/memory-v2.test.ts +1 -0
  305. package/src/cli/commands/__tests__/memory-v3-render.test.ts +340 -0
  306. package/src/cli/commands/__tests__/notifications.test.ts +184 -40
  307. package/src/cli/commands/browser.ts +247 -0
  308. package/src/cli/commands/channels/__tests__/channels.test.ts +143 -0
  309. package/src/cli/commands/channels/index.ts +229 -0
  310. package/src/cli/commands/domain.ts +91 -41
  311. package/src/cli/commands/inference.ts +93 -40
  312. package/src/cli/commands/memory-v2-compare-render.ts +115 -0
  313. package/src/cli/commands/memory-v2.ts +176 -1
  314. package/src/cli/commands/memory-v3-render.ts +491 -0
  315. package/src/cli/commands/memory-v3.ts +567 -0
  316. package/src/cli/commands/notifications.ts +365 -55
  317. package/src/cli/lib/open-browser.ts +7 -2
  318. package/src/cli/program.ts +4 -0
  319. package/src/config/assistant-feature-flags.ts +39 -46
  320. package/src/config/bundled-skills/document-editor/SKILL.md +16 -3
  321. package/src/config/bundled-skills/document-editor/TOOLS.json +18 -0
  322. package/src/config/bundled-skills/document-editor/tools/document-open.ts +12 -0
  323. package/src/config/bundled-skills/image-studio/SKILL.md +4 -0
  324. package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +2 -2
  325. package/src/config/bundled-skills/media-processing/tools/ingest-media.ts +13 -8
  326. package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +10 -3
  327. package/src/config/bundled-skills/phone-calls/references/TRANSCRIPTS.md +16 -14
  328. package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +7 -2
  329. package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +7 -2
  330. package/src/config/bundled-skills/schedule/SKILL.md +1 -1
  331. package/src/config/bundled-skills/schedule/TOOLS.json +2 -2
  332. package/src/config/bundled-skills/settings/tools/open-system-settings.ts +1 -0
  333. package/src/config/bundled-tool-registry.ts +2 -0
  334. package/src/config/call-site-defaults.ts +8 -7
  335. package/src/config/feature-flag-cache.ts +86 -0
  336. package/src/config/feature-flag-registry.json +33 -17
  337. package/src/config/llm-context-resolution.ts +10 -1
  338. package/src/config/llm-resolver.ts +121 -15
  339. package/src/config/loader.ts +4 -5
  340. package/src/config/schemas/__tests__/memory-v2.test.ts +228 -1
  341. package/src/config/schemas/call-site-catalog.ts +21 -7
  342. package/src/config/schemas/heartbeat.ts +1 -1
  343. package/src/config/schemas/llm.ts +102 -2
  344. package/src/config/schemas/memory-v2.ts +272 -0
  345. package/src/config/schemas/memory.ts +2 -1
  346. package/src/config/schemas/services.ts +6 -2
  347. package/src/config/seed-inference-profiles.ts +36 -16
  348. package/src/context/compactor.ts +52 -0
  349. package/src/context/token-estimator.ts +10 -5
  350. package/src/conversations/__tests__/message-consolidation.test.ts +350 -0
  351. package/src/conversations/message-consolidation.ts +404 -0
  352. package/src/credential-execution/executable-discovery.ts +40 -0
  353. package/src/credential-execution/process-manager.ts +6 -2
  354. package/src/credential-health/credential-health-service.ts +125 -40
  355. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +3 -6
  356. package/src/daemon/__tests__/conversation-surfaces-launch.test.ts +13 -15
  357. package/src/daemon/__tests__/conversation-tool-setup-exclude.test.ts +2 -3
  358. package/src/daemon/__tests__/daemon-skill-host.test.ts +2 -0
  359. package/src/daemon/__tests__/meet-manifest-loader.test.ts +25 -12
  360. package/src/daemon/__tests__/native-web-search-metadata.test.ts +1 -0
  361. package/src/daemon/__tests__/switch-inference-profile-tool.test.ts +107 -0
  362. package/src/daemon/__tests__/web-search-status-text.test.ts +1 -0
  363. package/src/daemon/conversation-agent-loop-handlers.ts +390 -80
  364. package/src/daemon/conversation-agent-loop.ts +244 -90
  365. package/src/daemon/conversation-error.ts +64 -6
  366. package/src/daemon/conversation-lifecycle.ts +27 -22
  367. package/src/daemon/conversation-messaging.ts +84 -43
  368. package/src/daemon/conversation-process.ts +74 -37
  369. package/src/daemon/conversation-runtime-assembly.ts +38 -17
  370. package/src/daemon/conversation-skill-tools.ts +14 -30
  371. package/src/daemon/conversation-surfaces.ts +69 -34
  372. package/src/daemon/conversation-tool-setup.ts +77 -32
  373. package/src/daemon/conversation-usage.ts +2 -0
  374. package/src/daemon/conversation.ts +40 -75
  375. package/src/daemon/daemon-control.ts +1 -1
  376. package/src/daemon/daemon-skill-host.ts +9 -2
  377. package/src/daemon/disk-pressure-guard.ts +39 -29
  378. package/src/daemon/first-greeting.ts +31 -13
  379. package/src/daemon/handlers/config-model.test.ts +1 -0
  380. package/src/daemon/handlers/conversations.ts +11 -3
  381. package/src/daemon/handlers/shared.ts +6 -1
  382. package/src/daemon/host-browser-proxy.ts +5 -5
  383. package/src/daemon/host-cu-proxy.ts +4 -4
  384. package/src/daemon/host-file-proxy.ts +4 -4
  385. package/src/daemon/host-proxy-base.ts +4 -4
  386. package/src/daemon/host-transfer-proxy.ts +10 -10
  387. package/src/daemon/lifecycle.ts +29 -26
  388. package/src/daemon/mcp-reload-service.ts +1 -1
  389. package/src/daemon/meet-manifest-loader.ts +11 -24
  390. package/src/daemon/message-types/conversations.ts +22 -27
  391. package/src/daemon/message-types/document-comments.ts +8 -44
  392. package/src/daemon/message-types/home.ts +2 -14
  393. package/src/daemon/message-types/integrations.ts +2 -7
  394. package/src/daemon/message-types/messages.ts +25 -48
  395. package/src/daemon/message-types/subagents.ts +6 -0
  396. package/src/daemon/message-types/sync.ts +14 -0
  397. package/src/daemon/process-message.ts +9 -9
  398. package/src/daemon/providers-setup.ts +1 -1
  399. package/src/daemon/server.ts +16 -0
  400. package/src/daemon/shutdown-handlers.ts +24 -5
  401. package/src/daemon/switch-inference-profile-tool.ts +62 -0
  402. package/src/daemon/tool-setup-types.ts +7 -0
  403. package/src/daemon/wake-target-adapter.ts +10 -0
  404. package/src/documents/document-store.ts +38 -0
  405. package/src/export/__tests__/transcript-formatter.test.ts +1 -0
  406. package/src/heartbeat/__tests__/heartbeat-service.test.ts +30 -1
  407. package/src/heartbeat/heartbeat-service.ts +63 -0
  408. package/src/home/__tests__/feed-writer.test.ts +161 -0
  409. package/src/home/__tests__/post-connect-feed.test.ts +1 -0
  410. package/src/home/__tests__/suggested-prompts.test.ts +55 -59
  411. package/src/home/feed-writer.ts +146 -7
  412. package/src/home/home-greeting.ts +0 -9
  413. package/src/home/suggested-prompts.ts +27 -154
  414. package/src/ipc/__tests__/cli-ipc.test.ts +1 -0
  415. package/src/ipc/gateway-client.test.ts +4 -1
  416. package/src/ipc/gateway-flag-listener.ts +123 -0
  417. package/src/ipc/skill-routes/__tests__/memory.test.ts +1 -0
  418. package/src/ipc/skill-routes/__tests__/registries.test.ts +36 -7
  419. package/src/ipc/skill-routes/memory.ts +4 -3
  420. package/src/ipc/skill-routes/registries.ts +35 -40
  421. package/src/memory/__tests__/db-async-query.test.ts +165 -0
  422. package/src/memory/__tests__/db-maintenance.test.ts +115 -0
  423. package/src/memory/__tests__/jobs-store-enqueue-gate.test.ts +242 -0
  424. package/src/memory/__tests__/jobs-store-job-classes.test.ts +28 -1
  425. package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +26 -5
  426. package/src/memory/__tests__/memory-retrospective-enqueue.test.ts +1 -0
  427. package/src/memory/__tests__/memory-retrospective-job.test.ts +8 -0
  428. package/src/memory/__tests__/memory-retrospective-startup-cleanup.test.ts +1 -0
  429. package/src/memory/__tests__/memory-v2-activation-log-store.test.ts +31 -0
  430. package/src/memory/auto-analysis-enqueue.ts +5 -1
  431. package/src/memory/conversation-attention-store.ts +17 -3
  432. package/src/memory/conversation-crud.ts +423 -182
  433. package/src/memory/conversation-starters-cadence.ts +3 -1
  434. package/src/memory/conversation-title-service.ts +19 -3
  435. package/src/memory/db-async-query.ts +214 -0
  436. package/src/memory/db-connection.ts +29 -19
  437. package/src/memory/db-init.ts +14 -0
  438. package/src/memory/db-maintenance.ts +30 -21
  439. package/src/memory/db-singleton.ts +77 -0
  440. package/src/memory/delivery-channels.ts +82 -0
  441. package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +2 -4
  442. package/src/memory/graph/bootstrap.ts +8 -1
  443. package/src/memory/graph/capability-seed.ts +7 -3
  444. package/src/memory/graph/conversation-graph-memory.ts +100 -17
  445. package/src/memory/graph/extraction.ts +1 -5
  446. package/src/memory/graph/graph-search.ts +7 -1
  447. package/src/memory/graph/retriever.test.ts +3 -3
  448. package/src/memory/indexer.ts +28 -18
  449. package/src/memory/job-handlers/cleanup.ts +76 -18
  450. package/src/memory/job-handlers/conversation-starters.ts +1 -4
  451. package/src/memory/job-handlers/embedding.test.ts +3 -2
  452. package/src/memory/jobs/__tests__/embed-concept-page.test.ts +5 -2
  453. package/src/memory/jobs/embed-pkb-file.ts +6 -1
  454. package/src/memory/jobs-store.ts +14 -0
  455. package/src/memory/jobs-worker.ts +66 -22
  456. package/src/memory/llm-request-log-source-clickhouse.ts +122 -2
  457. package/src/memory/llm-request-log-source-local.ts +31 -0
  458. package/src/memory/llm-request-log-source.ts +40 -2
  459. package/src/memory/llm-request-log-store.ts +228 -1
  460. package/src/memory/llm-usage-store.ts +24 -0
  461. package/src/memory/memory-retrospective-enqueue.ts +8 -1
  462. package/src/memory/memory-retrospective-job.ts +5 -0
  463. package/src/memory/memory-v2-activation-log-store.ts +110 -7
  464. package/src/memory/migrations/260-rename-cleaned-at.ts +44 -0
  465. package/src/memory/migrations/261-llm-usage-add-raw-usage.ts +36 -0
  466. package/src/memory/migrations/262-memory-v3-coactivation.ts +57 -0
  467. package/src/memory/migrations/263-memory-v3-auto-edges.ts +50 -0
  468. package/src/memory/migrations/264-llm-request-log-call-site.ts +29 -0
  469. package/src/memory/migrations/265-drop-provider-connection-status.ts +26 -0
  470. package/src/memory/migrations/266-messages-client-message-id.ts +43 -0
  471. package/src/memory/migrations/index.ts +19 -0
  472. package/src/memory/migrations/registry.ts +33 -0
  473. package/src/memory/schema/conversations.ts +10 -2
  474. package/src/memory/schema/inference.ts +0 -1
  475. package/src/memory/schema/infrastructure.ts +21 -0
  476. package/src/memory/tool-usage-store.ts +36 -8
  477. package/src/memory/v2/__tests__/backfill-jobs.test.ts +5 -2
  478. package/src/memory/v2/__tests__/consolidation-job.test.ts +1 -0
  479. package/src/memory/v2/__tests__/harness-compare.test.ts +186 -0
  480. package/src/memory/v2/__tests__/harness-metrics.test.ts +83 -0
  481. package/src/memory/v2/__tests__/harness-oracle.test.ts +257 -0
  482. package/src/memory/v2/__tests__/harness-replay-input.test.ts +230 -0
  483. package/src/memory/v2/__tests__/harness-runner.test.ts +135 -0
  484. package/src/memory/v2/__tests__/injection.test.ts +127 -98
  485. package/src/memory/v2/__tests__/qdrant.test.ts +36 -0
  486. package/src/memory/v2/__tests__/router.test.ts +171 -3
  487. package/src/memory/v2/__tests__/sweep-job.test.ts +6 -3
  488. package/src/memory/v2/harness/compare.ts +57 -0
  489. package/src/memory/v2/harness/metrics.ts +128 -0
  490. package/src/memory/v2/harness/oracle.ts +145 -0
  491. package/src/memory/v2/harness/replay-input.ts +240 -0
  492. package/src/memory/v2/harness/retriever.ts +74 -0
  493. package/src/memory/v2/harness/router-retriever.ts +43 -0
  494. package/src/memory/v2/harness/runner.ts +112 -0
  495. package/src/memory/v2/harness/trace.ts +64 -0
  496. package/src/memory/v2/injection.ts +21 -15
  497. package/src/memory/v2/prompts/router.ts +26 -1
  498. package/src/memory/v2/qdrant.ts +14 -2
  499. package/src/memory/v2/router.ts +171 -18
  500. package/src/memory/v3/__tests__/coactivation-store.test.ts +422 -0
  501. package/src/memory/v3/__tests__/consolidation-job.test.ts +466 -0
  502. package/src/memory/v3/__tests__/coretrieval-seed.test.ts +270 -0
  503. package/src/memory/v3/__tests__/edge-learning-job.test.ts +324 -0
  504. package/src/memory/v3/__tests__/edges.test.ts +706 -0
  505. package/src/memory/v3/__tests__/filter.test.ts +560 -0
  506. package/src/memory/v3/__tests__/gate.test.ts +637 -0
  507. package/src/memory/v3/__tests__/index-composition.test.ts +291 -0
  508. package/src/memory/v3/__tests__/loop.test.ts +775 -0
  509. package/src/memory/v3/__tests__/retriever.test.ts +226 -0
  510. package/src/memory/v3/__tests__/scouts.test.ts +489 -0
  511. package/src/memory/v3/__tests__/shadow-diff.test.ts +225 -0
  512. package/src/memory/v3/__tests__/shadow-middleware.test.ts +398 -0
  513. package/src/memory/v3/__tests__/system-prompts.test.ts +154 -0
  514. package/src/memory/v3/__tests__/traversal.test.ts +508 -0
  515. package/src/memory/v3/__tests__/tree-index.test.ts +280 -0
  516. package/src/memory/v3/__tests__/tree-store.test.ts +529 -0
  517. package/src/memory/v3/__tests__/tree-walk.test.ts +784 -0
  518. package/src/memory/v3/__tests__/validate.test.ts +277 -0
  519. package/src/memory/v3/auto-edges.ts +223 -0
  520. package/src/memory/v3/coactivation-store.ts +124 -0
  521. package/src/memory/v3/consolidation-job.ts +323 -0
  522. package/src/memory/v3/coretrieval-seed.ts +240 -0
  523. package/src/memory/v3/edge-learning-job.ts +160 -0
  524. package/src/memory/v3/edges.ts +286 -0
  525. package/src/memory/v3/filter.ts +286 -0
  526. package/src/memory/v3/gate.ts +349 -0
  527. package/src/memory/v3/index-composition.ts +126 -0
  528. package/src/memory/v3/llm-capture.ts +46 -0
  529. package/src/memory/v3/loop.ts +430 -0
  530. package/src/memory/v3/maintenance.ts +144 -0
  531. package/src/memory/v3/prompt-context.ts +33 -0
  532. package/src/memory/v3/prompts/consolidation.ts +458 -0
  533. package/src/memory/v3/prompts/system-prompts.ts +196 -0
  534. package/src/memory/v3/retriever.ts +33 -0
  535. package/src/memory/v3/scouts.ts +431 -0
  536. package/src/memory/v3/shadow-diff.ts +287 -0
  537. package/src/memory/v3/shadow-middleware.ts +347 -0
  538. package/src/memory/v3/traversal.ts +211 -0
  539. package/src/memory/v3/tree-index.ts +237 -0
  540. package/src/memory/v3/tree-store.ts +394 -0
  541. package/src/memory/v3/tree-walk.ts +356 -0
  542. package/src/memory/v3/types.ts +65 -0
  543. package/src/memory/v3/validate.ts +323 -0
  544. package/src/notifications/__tests__/emit-signal-home-feed.test.ts +1 -0
  545. package/src/notifications/__tests__/home-feed-side-effect.test.ts +1 -0
  546. package/src/notifications/adapters/macos.ts +18 -1
  547. package/src/notifications/adapters/platform.ts +1 -1
  548. package/src/notifications/adapters/slack.ts +45 -11
  549. package/src/notifications/broadcaster.ts +114 -63
  550. package/src/notifications/conversation-pairing.ts +23 -3
  551. package/src/notifications/decision-engine.ts +1 -4
  552. package/src/notifications/decisions-store.ts +32 -1
  553. package/src/notifications/deliveries-store.ts +45 -0
  554. package/src/notifications/edit-notification.ts +201 -0
  555. package/src/notifications/emit-signal.ts +40 -50
  556. package/src/notifications/signal.ts +10 -0
  557. package/src/notifications/types.ts +37 -0
  558. package/src/oauth/byo-connection.test.ts +67 -3
  559. package/src/oauth/byo-connection.ts +32 -5
  560. package/src/oauth/connect-orchestrator.ts +9 -0
  561. package/src/oauth/connection-resolver.test.ts +76 -0
  562. package/src/oauth/connection-resolver.ts +49 -10
  563. package/src/oauth/manual-token-connection.ts +51 -3
  564. package/src/oauth/seed-providers.ts +3 -0
  565. package/src/permissions/approval-policy.test.ts +19 -5
  566. package/src/permissions/approval-policy.ts +14 -3
  567. package/src/permissions/checker.ts +21 -8
  568. package/src/permissions/prompter.ts +3 -3
  569. package/src/permissions/question-prompter.ts +5 -2
  570. package/src/permissions/secret-prompter.ts +2 -2
  571. package/src/platform/client.test.ts +24 -1
  572. package/src/platform/client.ts +8 -0
  573. package/src/platform/feature-gate.ts +15 -0
  574. package/src/plugin-api/index.ts +4 -0
  575. package/src/plugin-api/types.ts +7 -33
  576. package/src/plugins/defaults/index.ts +6 -0
  577. package/src/plugins/defaults/injectors.ts +20 -19
  578. package/src/plugins/defaults/persistence.ts +25 -6
  579. package/src/plugins/external-plugin-loader.ts +5 -68
  580. package/src/plugins/types.ts +68 -29
  581. package/src/proactive-artifact/aux-message-injector.ts +17 -4
  582. package/src/proactive-artifact/job.test.ts +1 -0
  583. package/src/prompts/__tests__/system-prompt.test.ts +4 -4
  584. package/src/prompts/__tests__/task-progress-hint-section.test.ts +3 -9
  585. package/src/prompts/persona-resolver.ts +36 -21
  586. package/src/prompts/sections.ts +39 -7
  587. package/src/prompts/system-prompt.ts +84 -221
  588. package/src/prompts/template-detection.ts +10 -4
  589. package/src/prompts/templates/BOOTSTRAP.md +9 -13
  590. package/src/prompts/templates/IDENTITY.md +0 -2
  591. package/src/prompts/templates/system-sections.ts +230 -8
  592. package/src/providers/__tests__/connection-model-compat.test.ts +233 -0
  593. package/src/providers/__tests__/registry-native-web-search.test.ts +122 -0
  594. package/src/providers/__tests__/retry-callsite.test.ts +85 -5
  595. package/src/providers/anthropic/client.ts +32 -66
  596. package/src/providers/call-site-routing.ts +42 -6
  597. package/src/providers/connection-model-compat.ts +61 -0
  598. package/src/providers/connection-resolution.ts +47 -14
  599. package/src/providers/fireworks/client.ts +1 -0
  600. package/src/providers/gemini/client.ts +70 -6
  601. package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +0 -2
  602. package/src/providers/inference/__tests__/base-url-security.test.ts +2 -3
  603. package/src/providers/inference/__tests__/{connections-status-label.test.ts → connections-label.test.ts} +12 -111
  604. package/src/providers/inference/adapter-factory.ts +3 -0
  605. package/src/providers/inference/auth.ts +0 -8
  606. package/src/providers/inference/connections.ts +3 -66
  607. package/src/providers/inference/resolve-auth.ts +2 -3
  608. package/src/providers/minimax/client.ts +106 -0
  609. package/src/providers/model-catalog.ts +78 -1
  610. package/src/providers/model-intents.ts +4 -4
  611. package/src/providers/openai/__tests__/api-error-detail.test.ts +120 -0
  612. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +157 -5
  613. package/src/providers/openai/chat-completions-provider.ts +116 -15
  614. package/src/providers/openai/codex-models.ts +20 -0
  615. package/src/providers/openai/responses-provider.ts +87 -30
  616. package/src/providers/openrouter/client.ts +13 -8
  617. package/src/providers/provider-send-message.ts +20 -5
  618. package/src/providers/registry.ts +48 -8
  619. package/src/providers/retry.ts +50 -7
  620. package/src/providers/search-provider-catalog.ts +17 -9
  621. package/src/providers/thinking-config.ts +26 -1
  622. package/src/providers/types.ts +9 -0
  623. package/src/providers/usage-tracking.ts +2 -0
  624. package/src/runtime/AGENTS.md +2 -2
  625. package/src/runtime/__tests__/agent-wake.test.ts +1 -0
  626. package/src/runtime/__tests__/background-job-runner.test.ts +1 -0
  627. package/src/runtime/access-request-helper.ts +1 -0
  628. package/src/runtime/agent-wake.ts +1 -0
  629. package/src/runtime/assistant-event-hub.ts +76 -6
  630. package/src/runtime/auth/route-policy.ts +46 -0
  631. package/src/runtime/btw-sidechain.ts +0 -6
  632. package/src/runtime/channel-readiness-service.ts +68 -0
  633. package/src/runtime/channel-reply-delivery.ts +23 -0
  634. package/src/runtime/channel-retry-sweep.ts +47 -14
  635. package/src/runtime/confirmation-request-guardian-bridge.ts +1 -1
  636. package/src/runtime/http-types.ts +0 -2
  637. package/src/runtime/migrations/vbundle-builder.ts +12 -4
  638. package/src/runtime/pending-interactions.ts +0 -1
  639. package/src/runtime/routes/__tests__/bookmark-routes.test.ts +1 -0
  640. package/src/runtime/routes/__tests__/conversation-compaction-routes.test.ts +406 -0
  641. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +204 -0
  642. package/src/runtime/routes/__tests__/heartbeat-routes.test.ts +1 -1
  643. package/src/runtime/routes/__tests__/home-feed-routes.test.ts +209 -1
  644. package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +13 -50
  645. package/src/runtime/routes/__tests__/memory-v2-simulate-route.test.ts +76 -9
  646. package/src/runtime/routes/__tests__/memory-v3-simulate-params.test.ts +35 -0
  647. package/src/runtime/routes/__tests__/plugins-routes.test.ts +512 -0
  648. package/src/runtime/routes/__tests__/slack-channel-routes.test.ts +3 -2
  649. package/src/runtime/routes/__tests__/surface-content-routes.test.ts +294 -0
  650. package/src/runtime/routes/__tests__/task-routes.test.ts +48 -3
  651. package/src/runtime/routes/acp-routes-list.test.ts +3 -0
  652. package/src/runtime/routes/acp-routes.test.ts +255 -6
  653. package/src/runtime/routes/acp-routes.ts +8 -1
  654. package/src/runtime/routes/app-management-routes.ts +111 -4
  655. package/src/runtime/routes/avatar-routes.ts +10 -10
  656. package/src/runtime/routes/background-wake-routes.ts +356 -0
  657. package/src/runtime/routes/browser-tabs-routes.ts +200 -0
  658. package/src/runtime/routes/btw-routes.ts +4 -10
  659. package/src/runtime/routes/conversation-analysis-routes.ts +6 -0
  660. package/src/runtime/routes/conversation-cli-routes.ts +1 -1
  661. package/src/runtime/routes/conversation-compaction-routes.ts +263 -0
  662. package/src/runtime/routes/conversation-list-routes.ts +159 -4
  663. package/src/runtime/routes/conversation-management-routes.ts +108 -26
  664. package/src/runtime/routes/conversation-query-routes.ts +200 -44
  665. package/src/runtime/routes/conversation-routes.ts +409 -521
  666. package/src/runtime/routes/conversation-starter-routes.ts +6 -3
  667. package/src/runtime/routes/conversations-import-routes.ts +19 -6
  668. package/src/runtime/routes/disk-pressure-routes.ts +1 -1
  669. package/src/runtime/routes/documents-routes.ts +10 -1
  670. package/src/runtime/routes/domain-routes.ts +60 -10
  671. package/src/runtime/routes/email-routes.ts +5 -2
  672. package/src/runtime/routes/events-routes.ts +54 -10
  673. package/src/runtime/routes/group-routes.ts +35 -8
  674. package/src/runtime/routes/home-feed-routes.ts +129 -0
  675. package/src/runtime/routes/host-browser-routes.ts +10 -2
  676. package/src/runtime/routes/host-cu-routes.ts +2 -2
  677. package/src/runtime/routes/identity-intro-cache.ts +61 -16
  678. package/src/runtime/routes/identity-routes.ts +30 -9
  679. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +96 -3
  680. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +530 -6
  681. package/src/runtime/routes/inbound-stages/background-dispatch.ts +57 -8
  682. package/src/runtime/routes/index.ts +10 -0
  683. package/src/runtime/routes/inference-profile-session-handler.ts +22 -12
  684. package/src/runtime/routes/inference-profile-session-routes.ts +7 -1
  685. package/src/runtime/routes/inference-provider-connection-routes.ts +5 -26
  686. package/src/runtime/routes/integrations/vercel.ts +15 -0
  687. package/src/runtime/routes/llm-call-sites-routes.ts +32 -5
  688. package/src/runtime/routes/llm-context-normalization.ts +7 -2
  689. package/src/runtime/routes/memory-item-routes.ts +8 -3
  690. package/src/runtime/routes/memory-v2-routes.ts +215 -5
  691. package/src/runtime/routes/memory-v3-routes.ts +474 -0
  692. package/src/runtime/routes/migration-routes.ts +32 -28
  693. package/src/runtime/routes/notification-routes.ts +63 -1
  694. package/src/runtime/routes/oauth-commands-routes.ts +6 -1
  695. package/src/runtime/routes/plugins-routes.ts +337 -0
  696. package/src/runtime/routes/rename-conversation-routes.ts +6 -2
  697. package/src/runtime/routes/secret-routes.ts +25 -5
  698. package/src/runtime/routes/settings-routes.ts +12 -11
  699. package/src/runtime/routes/slack-channel-routes.ts +5 -4
  700. package/src/runtime/routes/surface-action-routes.ts +1 -38
  701. package/src/runtime/routes/surface-content-routes.ts +12 -5
  702. package/src/runtime/routes/surface-conversation-resolver.ts +65 -0
  703. package/src/runtime/routes/wipe-conversation-routes.ts +3 -0
  704. package/src/runtime/routes/workspace-routes.ts +25 -10
  705. package/src/runtime/services/__tests__/analyze-conversation.test.ts +2 -0
  706. package/src/runtime/slack-dm-text-delivery.ts +177 -0
  707. package/src/runtime/sync/resource-sync-events.ts +106 -38
  708. package/src/runtime/sync/sync-publisher.test.ts +49 -0
  709. package/src/runtime/sync/sync-publisher.ts +2 -1
  710. package/src/runtime/tool-grant-request-helper.ts +1 -0
  711. package/src/runtime/verification-outbound-actions.ts +73 -1
  712. package/src/schedule/schedule-store.ts +8 -1
  713. package/src/schedule/scheduler.ts +111 -15
  714. package/src/security/__tests__/provider-key-env-fallback.test.ts +3 -3
  715. package/src/security/encrypted-store.ts +7 -16
  716. package/src/security/store-path-override.ts +61 -0
  717. package/src/signals/user-message.ts +5 -8
  718. package/src/skills/validate-input.ts +177 -0
  719. package/src/subagent/manager.ts +13 -13
  720. package/src/subagent/types.ts +6 -0
  721. package/src/tasks/tool-sanitizer.ts +2 -2
  722. package/src/telemetry/types.ts +12 -0
  723. package/src/telemetry/usage-telemetry-reporter.test.ts +48 -0
  724. package/src/telemetry/usage-telemetry-reporter.ts +1 -0
  725. package/src/tools/acp/spawn.test.ts +119 -0
  726. package/src/tools/acp/spawn.ts +15 -2
  727. package/src/tools/apps/definitions.ts +36 -28
  728. package/src/tools/ask-question/ask-question-tool.test.ts +3 -3
  729. package/src/tools/ask-question/ask-question-tool.ts +38 -45
  730. package/src/tools/browser/__tests__/browser-execution-acquire.test.ts +2 -8
  731. package/src/tools/browser/__tests__/pinned-tabs.test.ts +70 -0
  732. package/src/tools/browser/browser-execution.ts +16 -3
  733. package/src/tools/browser/cdp-client/__tests__/browser-tabs-factory.test.ts +402 -0
  734. package/src/tools/browser/cdp-client/__tests__/types.test.ts +3 -0
  735. package/src/tools/browser/cdp-client/cdp-inspect-client.ts +12 -0
  736. package/src/tools/browser/cdp-client/extension-cdp-client.ts +27 -1
  737. package/src/tools/browser/cdp-client/factory.ts +100 -17
  738. package/src/tools/browser/cdp-client/local-cdp-client.ts +12 -0
  739. package/src/tools/browser/cdp-client/types.ts +65 -0
  740. package/src/tools/browser/pinned-tabs.ts +96 -40
  741. package/src/tools/computer-use/definitions.ts +282 -336
  742. package/src/tools/credential-execution/make-authenticated-request.ts +3 -9
  743. package/src/tools/credential-execution/manage-secure-command-tool.ts +3 -9
  744. package/src/tools/credential-execution/run-authenticated-command.ts +3 -9
  745. package/src/tools/credentials/vault.ts +3 -9
  746. package/src/tools/document/document-tool.ts +189 -7
  747. package/src/tools/execution-target.ts +18 -23
  748. package/src/tools/executor.ts +24 -56
  749. package/src/tools/filesystem/edit.ts +3 -9
  750. package/src/tools/filesystem/list.ts +3 -9
  751. package/src/tools/filesystem/read.ts +3 -9
  752. package/src/tools/filesystem/write.ts +3 -9
  753. package/src/tools/host-filesystem/edit.test.ts +1 -0
  754. package/src/tools/host-filesystem/edit.ts +3 -9
  755. package/src/tools/host-filesystem/read.test.ts +1 -0
  756. package/src/tools/host-filesystem/read.ts +3 -9
  757. package/src/tools/host-filesystem/transfer.test.ts +31 -6
  758. package/src/tools/host-filesystem/transfer.ts +3 -9
  759. package/src/tools/host-filesystem/write.test.ts +1 -0
  760. package/src/tools/host-filesystem/write.ts +3 -9
  761. package/src/tools/host-terminal/host-shell.ts +3 -9
  762. package/src/tools/mcp/mcp-tool-factory.ts +1 -10
  763. package/src/tools/memory/register.test.ts +1 -1
  764. package/src/tools/memory/register.ts +4 -9
  765. package/src/tools/network/__tests__/managed-search-proxy.test.ts +282 -0
  766. package/src/tools/network/__tests__/web-search.test.ts +211 -3
  767. package/src/tools/network/managed-search-proxy.ts +183 -0
  768. package/src/tools/network/web-fetch.ts +3 -9
  769. package/src/tools/network/web-search.ts +224 -76
  770. package/src/tools/policy-context.ts +3 -1
  771. package/src/tools/registry.ts +150 -123
  772. package/src/tools/schedule/create.ts +1 -1
  773. package/src/tools/schema-transforms.ts +1 -1
  774. package/src/tools/skills/execute.ts +3 -9
  775. package/src/tools/skills/load.ts +3 -9
  776. package/src/tools/skills/skill-tool-factory.ts +18 -44
  777. package/src/tools/subagent/notify-parent.ts +3 -9
  778. package/src/tools/subagent/spawn.ts +3 -0
  779. package/src/tools/system/request-permission.ts +3 -9
  780. package/src/tools/terminal/shell.ts +3 -9
  781. package/src/tools/tool-approval-handler.ts +10 -4
  782. package/src/tools/tool-defaults.ts +94 -0
  783. package/src/tools/tool-name-aliases.ts +72 -14
  784. package/src/tools/types.ts +32 -101
  785. package/src/tools/ui-surface/definitions.ts +104 -108
  786. package/src/types/onboarding-context.ts +6 -0
  787. package/src/usage/attribution.ts +32 -1
  788. package/src/usage/pricing.ts +23 -0
  789. package/src/usage/types.ts +12 -0
  790. package/src/util/browser.ts +7 -2
  791. package/src/util/logger.ts +16 -7
  792. package/src/util/platform.ts +7 -2
  793. package/src/util/sqlite3-runtime.ts +65 -0
  794. package/src/workspace/migrations/086-revert-stale-gemini-mis-rewrites.ts +1 -0
  795. package/src/workspace/migrations/089-move-memory-tree-out-of-v3.ts +86 -0
  796. package/src/workspace/migrations/090-memory-router-cost-optimized-profile.ts +109 -0
  797. package/src/workspace/migrations/091-retighten-migration-onboarding-thread.ts +41 -0
  798. package/src/workspace/migrations/registry.ts +6 -0
  799. package/src/__tests__/compaction-strip-metadata-clear.test.ts +0 -206
  800. package/src/__tests__/message-complete-display-id.test.ts +0 -175
  801. package/src/daemon/query-complexity-router.ts +0 -75
  802. package/src/prompts/cache-boundary.ts +0 -8
@@ -159,8 +159,8 @@ mock.module("../daemon/disk-pressure-policy.js", () => ({
159
159
  const updateMessageMetadataMock = mock(
160
160
  (_id: string, _updates: Record<string, unknown>) => {},
161
161
  );
162
- const clearStrippedInjectionMetadataForConversationMock = mock(
163
- (_conversationId: string) => {},
162
+ const setConversationHistoryStrippedAtMock = mock(
163
+ (_conversationId: string, _historyStrippedAt: number | null) => {},
164
164
  );
165
165
  const updateConversationSlackContextWatermarkMock = mock(
166
166
  (_conversationId: string, _watermarkTs: string, _compactedAt?: number) => {},
@@ -176,12 +176,17 @@ 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: () => {},
182
188
  updateMessageMetadata: updateMessageMetadataMock,
183
- clearStrippedInjectionMetadataForConversation:
184
- clearStrippedInjectionMetadataForConversationMock,
189
+ setConversationHistoryStrippedAt: setConversationHistoryStrippedAtMock,
185
190
  getMessages: () => [],
186
191
  getConversation: () => mockConversationRow,
187
192
  provenanceFromTrustContext: () => ({
@@ -190,7 +195,7 @@ mock.module("../memory/conversation-crud.js", () => ({
190
195
  }),
191
196
  getConversationOriginInterface: () => null,
192
197
  addMessage: () => ({ id: "mock-msg-id" }),
193
- deleteMessageById: () => {},
198
+ deleteMessageById: deleteMessageByIdMock,
194
199
  updateConversationContextWindow: () => {},
195
200
  updateConversationSlackContextWatermark:
196
201
  updateConversationSlackContextWatermarkMock,
@@ -198,6 +203,36 @@ mock.module("../memory/conversation-crud.js", () => ({
198
203
  getConversationOriginChannel: () => null,
199
204
  getMessageById: () => mockMessageById,
200
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,
201
236
  }));
202
237
 
203
238
  afterAll(() => {
@@ -684,10 +719,8 @@ beforeEach(() => {
684
719
  mockHasProactiveArtifactCompleted = true;
685
720
  mockTryClaimProactiveArtifactTrigger = false;
686
721
  runProactiveArtifactJobMock.mockClear();
687
- clearStrippedInjectionMetadataForConversationMock.mockClear();
688
- clearStrippedInjectionMetadataForConversationMock.mockImplementation(
689
- () => {},
690
- );
722
+ setConversationHistoryStrippedAtMock.mockClear();
723
+ setConversationHistoryStrippedAtMock.mockImplementation(() => {});
691
724
  applyRuntimeInjectionsMock.mockClear();
692
725
  buildUnifiedTurnContextBlockMock.mockClear();
693
726
  resolveTurnTimezoneContextMock.mockClear();
@@ -695,6 +728,13 @@ beforeEach(() => {
695
728
  mockSlackChronologicalContext = null;
696
729
  loadSlackChronologicalContextMock.mockClear();
697
730
  getSlackCompactionWatermarkForPrefixMock.mockClear();
731
+ deleteMessageByIdMock.mockClear();
732
+ reserveMessageMock.mockClear();
733
+ updateMessageContentMock.mockClear();
734
+ indexMessageNowMock.mockClear();
735
+ projectAssistantMessageMock.mockClear();
736
+ publishSyncInvalidationMock.mockClear();
737
+ mockMessageById = null;
698
738
  // Orchestrator pipelines (overflowReduce, persistence, …) run through the
699
739
  // plugin registry; reset and re-register every default so the pipelines
700
740
  // dispatch to middleware backed by the mocked collaborators these tests
@@ -786,6 +826,9 @@ describe("session-agent-loop", () => {
786
826
  _requestId,
787
827
  onCheckpoint,
788
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" });
789
832
  await onEvent({
790
833
  type: "message_complete",
791
834
  message: {
@@ -811,6 +854,9 @@ describe("session-agent-loop", () => {
811
854
  hasToolUse: true,
812
855
  history: messages,
813
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" });
814
860
  await onEvent({
815
861
  type: "message_complete",
816
862
  message: {
@@ -1067,6 +1113,9 @@ describe("session-agent-loop", () => {
1067
1113
  const events: ServerMessage[] = [];
1068
1114
 
1069
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" });
1070
1119
  // Simulate tool_use + error during execution
1071
1120
  onEvent({
1072
1121
  type: "tool_use",
@@ -1116,6 +1165,9 @@ describe("session-agent-loop", () => {
1116
1165
  const events: ServerMessage[] = [];
1117
1166
 
1118
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" });
1119
1171
  onEvent({
1120
1172
  type: "message_complete",
1121
1173
  message: {
@@ -1176,6 +1228,9 @@ describe("session-agent-loop", () => {
1176
1228
  };
1177
1229
 
1178
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" });
1179
1234
  onEvent({
1180
1235
  type: "message_complete",
1181
1236
  message: {
@@ -1218,20 +1273,8 @@ describe("session-agent-loop", () => {
1218
1273
  await runAgentLoopImpl(ctx, "hello", "msg-1", (msg) => events.push(msg));
1219
1274
 
1220
1275
  expect(recordRequestLogMock).toHaveBeenCalledTimes(1);
1221
- const call = recordRequestLogMock.mock.calls[0] as unknown as [
1222
- string,
1223
- string,
1224
- string,
1225
- undefined,
1226
- string,
1227
- ];
1228
- expect(call).toEqual([
1229
- "test-conv",
1230
- JSON.stringify(rawRequest),
1231
- JSON.stringify(rawResponse),
1232
- undefined,
1233
- "fireworks",
1234
- ]);
1276
+ const call = recordRequestLogMock.mock.calls[0] as unknown as unknown[];
1277
+ expect(call[4]).toBe("fireworks");
1235
1278
  });
1236
1279
 
1237
1280
  test("record request log falls back to the runtime provider when no actual provider is supplied", async () => {
@@ -1253,6 +1296,9 @@ describe("session-agent-loop", () => {
1253
1296
  };
1254
1297
 
1255
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" });
1256
1302
  onEvent({
1257
1303
  type: "message_complete",
1258
1304
  message: {
@@ -1335,6 +1381,9 @@ describe("session-agent-loop", () => {
1335
1381
  };
1336
1382
 
1337
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" });
1338
1387
  onEvent({
1339
1388
  type: "message_complete",
1340
1389
  message: {
@@ -1377,20 +1426,9 @@ describe("session-agent-loop", () => {
1377
1426
  await runAgentLoopImpl(ctx, "hello", "msg-1", (msg) => events.push(msg));
1378
1427
 
1379
1428
  expect(recordRequestLogMock).toHaveBeenCalledTimes(1);
1380
- const call = recordRequestLogMock.mock.calls[0] as unknown as [
1381
- string,
1382
- string,
1383
- string,
1384
- undefined,
1385
- string,
1386
- ];
1387
- expect(call).toEqual([
1388
- "test-conv",
1389
- JSON.stringify(rawRequest),
1390
- JSON.stringify(rawResponse),
1391
- undefined,
1392
- "openai",
1393
- ]);
1429
+ const call = recordRequestLogMock.mock.calls[0] as unknown as unknown[];
1430
+ expect(call[1]).toBe(JSON.stringify(rawRequest));
1431
+ expect(call[2]).toBe(JSON.stringify(rawResponse));
1394
1432
  });
1395
1433
  });
1396
1434
 
@@ -1414,6 +1452,9 @@ describe("session-agent-loop", () => {
1414
1452
  }> = [];
1415
1453
 
1416
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" });
1417
1458
  onEvent({ type: "text_delta", text: "Hi." });
1418
1459
  onEvent({
1419
1460
  type: "message_complete",
@@ -1489,6 +1530,9 @@ describe("session-agent-loop", () => {
1489
1530
  }> = [];
1490
1531
 
1491
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" });
1492
1536
  // No text_delta — pure tool-call response
1493
1537
  onEvent({
1494
1538
  type: "message_complete",
@@ -1552,6 +1596,9 @@ describe("session-agent-loop", () => {
1552
1596
  const events: ServerMessage[] = [];
1553
1597
 
1554
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" });
1555
1602
  onEvent({
1556
1603
  type: "message_complete",
1557
1604
  message: {
@@ -1664,6 +1711,9 @@ describe("session-agent-loop", () => {
1664
1711
  });
1665
1712
 
1666
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" });
1667
1717
  onEvent({
1668
1718
  type: "message_complete",
1669
1719
  message: {
@@ -1754,6 +1804,11 @@ describe("session-agent-loop", () => {
1754
1804
  };
1755
1805
 
1756
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" });
1757
1812
  callCount++;
1758
1813
  if (callCount === 1) {
1759
1814
  onEvent({
@@ -1881,6 +1936,11 @@ describe("session-agent-loop", () => {
1881
1936
  };
1882
1937
 
1883
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" });
1884
1944
  callCount++;
1885
1945
  if (callCount === 1) {
1886
1946
  onEvent({
@@ -1966,6 +2026,11 @@ describe("session-agent-loop", () => {
1966
2026
  mockOverflowAction = "auto_compress_latest_turn";
1967
2027
 
1968
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" });
1969
2034
  callCount++;
1970
2035
  if (callCount <= 2) {
1971
2036
  onEvent({
@@ -2042,6 +2107,154 @@ describe("session-agent-loop", () => {
2042
2107
  expect(complete).toBeDefined();
2043
2108
  });
2044
2109
 
2110
+ test("emits budget_yield_unrecovered when auto_compress rerun yields at mid-loop budget", async () => {
2111
+ // Regression test for the silent-stall failure mode:
2112
+ // when every recovery layer has been applied (tier reducer
2113
+ // exhausted + auto_compress_latest_turn emergency compaction)
2114
+ // and the final `agentLoop.run` STILL yields at the mid-loop
2115
+ // budget checkpoint, the orchestrator used to fall through to
2116
+ // post-turn cleanup with NO `agent_loop_exit_reason` emitted —
2117
+ // the turn just stopped mid-action and `llm_request_logs` showed
2118
+ // a NULL exit reason on the final row. We now emit
2119
+ // `budget_yield_unrecovered` so the inspector and dashboards can
2120
+ // attribute the silent stall.
2121
+ const events: ServerMessage[] = [];
2122
+ let callCount = 0;
2123
+
2124
+ // Reducer exhausts all 4 tiers on first call so the convergence
2125
+ // loop runs exactly one iteration before falling through to
2126
+ // the auto_compress_latest_turn branch.
2127
+ mockReducerStepFn = (msgs: Message[]) => ({
2128
+ messages: msgs,
2129
+ tier: "injection_downgrade",
2130
+ state: {
2131
+ appliedTiers: [
2132
+ "forced_compaction",
2133
+ "tool_result_truncation",
2134
+ "media_stubbing",
2135
+ "injection_downgrade",
2136
+ ],
2137
+ injectionMode: "minimal",
2138
+ exhausted: true,
2139
+ },
2140
+ estimatedTokens: 120_000,
2141
+ });
2142
+
2143
+ mockOverflowAction = "auto_compress_latest_turn";
2144
+
2145
+ // Sits between the preflight budget (preflightBudget =
2146
+ // 100k * 0.95 = 95k — anything above triggers preflight reducer
2147
+ // *before* we get to the convergence/auto_compress path under
2148
+ // test) and the mid-loop threshold (preflightBudget * 0.85 =
2149
+ // ≈80.75k — anything above flips yieldedForBudget on a checkpoint
2150
+ // call). 90k satisfies both so the path reaches call 3.
2151
+ mockEstimateTokens = 90_000;
2152
+
2153
+ const agentLoopRun: AgentLoopRun = async (
2154
+ messages,
2155
+ onEvent,
2156
+ _signal,
2157
+ _reqId,
2158
+ onCheckpoint,
2159
+ ) => {
2160
+ callCount++;
2161
+ if (callCount <= 2) {
2162
+ // Calls 1 (initial) and 2 (convergence rerun): error so
2163
+ // `state.contextTooLargeDetected` stays true through
2164
+ // convergence exit and we enter the auto_compress branch.
2165
+ onEvent({
2166
+ type: "error",
2167
+ error: new Error("context_length_exceeded"),
2168
+ });
2169
+ onEvent({
2170
+ type: "usage",
2171
+ inputTokens: 100,
2172
+ outputTokens: 0,
2173
+ model: "test-model",
2174
+ providerDurationMs: 50,
2175
+ });
2176
+ return messages;
2177
+ }
2178
+ // Call 3: the auto_compress_latest_turn rerun. Invoke
2179
+ // onCheckpoint so the orchestrator's mid-loop budget check
2180
+ // flips `yieldedForBudget` to true, then return without
2181
+ // finishing — mirroring what AgentLoop.run does when its
2182
+ // checkpoint returns "yield".
2183
+ if (onCheckpoint) {
2184
+ await onCheckpoint({
2185
+ turnIndex: 0,
2186
+ toolCount: 1,
2187
+ hasToolUse: true,
2188
+ history: messages,
2189
+ });
2190
+ }
2191
+ return messages;
2192
+ };
2193
+
2194
+ const ctx = makeCtx({
2195
+ agentLoopRun,
2196
+ hasNoClient: true,
2197
+ contextWindowManager: {
2198
+ shouldCompact: () => ({ needed: false, estimatedTokens: 0 }),
2199
+ maybeCompact: async () => ({
2200
+ compacted: true,
2201
+ messages: [
2202
+ { role: "user", content: [{ type: "text", text: "Hello" }] },
2203
+ ] as Message[],
2204
+ compactedPersistedMessages: 3,
2205
+ summaryText: "Compressed summary",
2206
+ previousEstimatedInputTokens: 120000,
2207
+ estimatedInputTokens: 30000,
2208
+ maxInputTokens: 100000,
2209
+ thresholdTokens: 80000,
2210
+ compactedMessages: 5,
2211
+ summaryCalls: 1,
2212
+ summaryInputTokens: 300,
2213
+ summaryOutputTokens: 100,
2214
+ summaryModel: "mock-model",
2215
+ }),
2216
+ } as unknown as AgentLoopConversationContext["contextWindowManager"],
2217
+ });
2218
+
2219
+ await runAgentLoopImpl(ctx, "hello", "msg-1", (msg) => events.push(msg));
2220
+
2221
+ // Observability emit: exit reason was stamped onto the latest
2222
+ // llm_request_logs row.
2223
+ expect(setAgentLoopExitReasonOnLatestLogMock).toHaveBeenCalledWith(
2224
+ "test-conv",
2225
+ "budget_yield_unrecovered",
2226
+ );
2227
+
2228
+ // We did NOT also emit context_too_large — the auto_compress
2229
+ // branch resets `contextTooLargeDetected` before its rerun and
2230
+ // the rerun's yield-for-budget keeps it false, so the error
2231
+ // branch above stays skipped.
2232
+ expect(setAgentLoopExitReasonOnLatestLogMock).not.toHaveBeenCalledWith(
2233
+ "test-conv",
2234
+ "context_too_large",
2235
+ );
2236
+
2237
+ // User-facing emit: the classified BUDGET_YIELD_UNRECOVERED
2238
+ // error is sent to the client so the UI can render a notice
2239
+ // instead of leaving the turn looking like a silent ghost. The
2240
+ // assistant-side notice persistence is exercised in the overflow
2241
+ // suite (`conversation-agent-loop-overflow.test.ts`); this test
2242
+ // owns the observability + emit contract.
2243
+ const conversationError = events.find(
2244
+ (e) => e.type === "conversation_error",
2245
+ );
2246
+ expect(conversationError).toBeDefined();
2247
+ if (conversationError && "code" in conversationError) {
2248
+ expect(conversationError.code).toBe("BUDGET_YIELD_UNRECOVERED");
2249
+ expect(conversationError.retryable).toBe(true);
2250
+ expect(conversationError.errorCategory).toBe(
2251
+ "budget_yield_unrecovered",
2252
+ );
2253
+ } else {
2254
+ throw new Error("conversation_error missing `code` field");
2255
+ }
2256
+ });
2257
+
2045
2258
  test("recovery loop is bounded by maxAttempts", async () => {
2046
2259
  const events: ServerMessage[] = [];
2047
2260
  let reducerCalls = 0;
@@ -2115,6 +2328,9 @@ describe("session-agent-loop", () => {
2115
2328
 
2116
2329
  const agentLoopRun: AgentLoopRun = async (messages, onEvent) => {
2117
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" });
2118
2334
  onEvent({
2119
2335
  type: "message_complete",
2120
2336
  message: {
@@ -2163,6 +2379,11 @@ describe("session-agent-loop", () => {
2163
2379
  let callCount = 0;
2164
2380
 
2165
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" });
2166
2387
  callCount++;
2167
2388
  if (callCount === 1) {
2168
2389
  onEvent({
@@ -2247,6 +2468,11 @@ describe("session-agent-loop", () => {
2247
2468
  _reqId,
2248
2469
  onCheckpoint,
2249
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" });
2250
2476
  // Simulate tool use followed by checkpoint
2251
2477
  onEvent({ type: "tool_use", id: "tu-1", name: "file_read", input: {} });
2252
2478
  onEvent({
@@ -2320,6 +2546,11 @@ describe("session-agent-loop", () => {
2320
2546
  _reqId,
2321
2547
  onCheckpoint,
2322
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" });
2323
2554
  onEvent({ type: "tool_use", id: "tu-1", name: "file_read", input: {} });
2324
2555
  onEvent({
2325
2556
  type: "tool_result",
@@ -2382,6 +2613,9 @@ describe("session-agent-loop", () => {
2382
2613
  const abortController = new AbortController();
2383
2614
 
2384
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" });
2385
2619
  onEvent({
2386
2620
  type: "message_complete",
2387
2621
  message: {
@@ -2442,6 +2676,9 @@ describe("session-agent-loop", () => {
2442
2676
  resolveAssistantAttachmentsMock.mockClear();
2443
2677
 
2444
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" });
2445
2682
  onEvent({
2446
2683
  type: "message_complete",
2447
2684
  message: {
@@ -2481,6 +2718,9 @@ describe("session-agent-loop", () => {
2481
2718
  test("increments turnCount after successful run", async () => {
2482
2719
  const ctx = makeCtx({
2483
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" });
2484
2724
  onEvent({
2485
2725
  type: "message_complete",
2486
2726
  message: {
@@ -2514,6 +2754,9 @@ describe("session-agent-loop", () => {
2514
2754
  test("clears processing state and abort controller", async () => {
2515
2755
  const ctx = makeCtx({
2516
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" });
2517
2760
  onEvent({
2518
2761
  type: "message_complete",
2519
2762
  message: {
@@ -2577,8 +2820,13 @@ describe("session-agent-loop", () => {
2577
2820
  const ctx = makeCtx({
2578
2821
  agentLoopRun: async (
2579
2822
  messages: Message[],
2580
- onEvent: (event: AgentEvent) => void,
2823
+ onEvent: (event: AgentEvent) => void | Promise<void>,
2581
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" });
2582
2830
  onEvent({
2583
2831
  type: "message_complete",
2584
2832
  message: {
@@ -2922,6 +3170,280 @@ describe("session-agent-loop", () => {
2922
3170
  });
2923
3171
  });
2924
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
+
2925
3447
  describe("pkbSystemReminderBlock metadata persistence", () => {
2926
3448
  test("persists pkbSystemReminderBlock in full mode with PKB active", async () => {
2927
3449
  const reminder = "<system_reminder>\npkb content\n</system_reminder>";
@@ -3769,8 +4291,8 @@ describe("session-agent-loop", () => {
3769
4291
  });
3770
4292
  });
3771
4293
 
3772
- describe("compaction-strip metadata consistency", () => {
3773
- test("clears pkbSystemReminderBlock metadata when convergence strip runs", async () => {
4294
+ describe("compaction-strip marker persistence", () => {
4295
+ test("records historyStrippedAt when convergence strip runs", async () => {
3774
4296
  // Reducer: succeed on first call, returning reduced messages.
3775
4297
  mockReducerStepFn = (msgs: Message[]) => ({
3776
4298
  messages: msgs,
@@ -3786,6 +4308,11 @@ describe("session-agent-loop", () => {
3786
4308
  let callCount = 0;
3787
4309
  const agentLoopRun: AgentLoopRun = async (messages, onEvent) => {
3788
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" });
3789
4316
  if (callCount === 1) {
3790
4317
  // Trigger convergence path: error + appended assistant message so
3791
4318
  // updatedHistory.length > preRunHistoryLength at the strip site.
@@ -3841,21 +4368,16 @@ describe("session-agent-loop", () => {
3841
4368
 
3842
4369
  await runAgentLoopImpl(ctx, "hello", "msg-1", () => {});
3843
4370
 
3844
- // The bulk-clear helper must have been called with the conversation id
3845
- // at least once (one of the three strip sites fired).
3846
- const clearCalls =
3847
- clearStrippedInjectionMetadataForConversationMock.mock.calls.filter(
3848
- (call) => call[0] === "test-conv",
3849
- );
3850
- expect(clearCalls.length).toBeGreaterThanOrEqual(1);
4371
+ const stripCalls = setConversationHistoryStrippedAtMock.mock.calls.filter(
4372
+ (call) => call[0] === "test-conv",
4373
+ );
4374
+ expect(stripCalls.length).toBeGreaterThanOrEqual(1);
3851
4375
  });
3852
4376
 
3853
- test("strip-site clear is non-fatal when the helper throws", async () => {
3854
- clearStrippedInjectionMetadataForConversationMock.mockImplementation(
3855
- () => {
3856
- throw new Error("db write failed");
3857
- },
3858
- );
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
+ });
3859
4381
 
3860
4382
  mockReducerStepFn = (msgs: Message[]) => ({
3861
4383
  messages: msgs,
@@ -3871,6 +4393,11 @@ describe("session-agent-loop", () => {
3871
4393
  let callCount = 0;
3872
4394
  const agentLoopRun: AgentLoopRun = async (messages, onEvent) => {
3873
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" });
3874
4401
  if (callCount === 1) {
3875
4402
  onEvent({
3876
4403
  type: "error",
@@ -3922,7 +4449,7 @@ describe("session-agent-loop", () => {
3922
4449
  } as unknown as AgentLoopConversationContext["contextWindowManager"],
3923
4450
  });
3924
4451
 
3925
- // Must not throw — the strip-site clear is wrapped in try/catch.
4452
+ // Must not throw — the strip-site marker write is wrapped in try/catch.
3926
4453
  await expect(
3927
4454
  runAgentLoopImpl(ctx, "hello", "msg-1", () => {}),
3928
4455
  ).resolves.toBeUndefined();