@vellumai/assistant 0.8.5 → 0.8.7

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 (1171) hide show
  1. package/AGENTS.md +33 -1
  2. package/ARCHITECTURE.md +1 -1
  3. package/Dockerfile +1 -0
  4. package/bun.lock +11 -2
  5. package/bunfig.toml +6 -1
  6. package/docker-entrypoint.sh +8 -6
  7. package/docs/credential-execution-service.md +6 -6
  8. package/docs/plugins.md +67 -31
  9. package/examples/plugins/echo/register.ts +4 -7
  10. package/knip.json +1 -0
  11. package/node_modules/@vellumai/environments/bun.lock +24 -0
  12. package/node_modules/@vellumai/environments/package.json +18 -0
  13. package/node_modules/@vellumai/environments/src/__tests__/package-boundary.test.ts +95 -0
  14. package/node_modules/@vellumai/environments/src/index.ts +11 -0
  15. package/node_modules/@vellumai/environments/src/seeds.ts +73 -0
  16. package/node_modules/@vellumai/environments/src/types.ts +70 -0
  17. package/node_modules/@vellumai/environments/tsconfig.json +20 -0
  18. package/node_modules/@vellumai/skill-host-contracts/src/assistant-event.ts +11 -0
  19. package/node_modules/@vellumai/skill-host-contracts/src/client.ts +15 -17
  20. package/node_modules/@vellumai/skill-host-contracts/src/skill-host.ts +10 -3
  21. package/node_modules/@vellumai/skill-host-contracts/src/tool-types.ts +16 -14
  22. package/openapi.yaml +5585 -469
  23. package/package.json +7 -3
  24. package/scripts/generate-openapi.ts +20 -13
  25. package/src/__tests__/actor-token-service.test.ts +3 -2
  26. package/src/__tests__/agent-loop-callsite-precedence.test.ts +42 -80
  27. package/src/__tests__/agent-loop-exit-reason.test.ts +336 -42
  28. package/src/__tests__/agent-loop-mutable-latest-user-message.test.ts +141 -0
  29. package/src/__tests__/agent-loop-override-profile.test.ts +21 -33
  30. package/src/__tests__/agent-loop-provider-error-recording.test.ts +6 -4
  31. package/src/__tests__/agent-loop-thinking.test.ts +17 -12
  32. package/src/__tests__/agent-loop.test.ts +207 -341
  33. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +5 -2
  34. package/src/__tests__/agent-wake-override-profile.test.ts +23 -40
  35. package/src/__tests__/always-loaded-tools-guard.test.ts +2 -2
  36. package/src/__tests__/annotate-risk-options.test.ts +1 -0
  37. package/src/__tests__/anthropic-provider.test.ts +201 -55
  38. package/src/__tests__/app-builder-skill-instructions.test.ts +22 -0
  39. package/src/__tests__/app-control-flow.test.ts +5 -0
  40. package/src/__tests__/approval-cascade.test.ts +5 -11
  41. package/src/__tests__/approval-routes-http.test.ts +13 -15
  42. package/src/__tests__/assert-not-live-db.ts +79 -0
  43. package/src/__tests__/assistant-event.test.ts +15 -0
  44. package/src/__tests__/assistant-feature-flags-integration.test.ts +11 -27
  45. package/src/__tests__/audit-log-rotation.test.ts +2 -2
  46. package/src/__tests__/auto-analysis-end-to-end.test.ts +6 -6
  47. package/src/__tests__/avatar-e2e.test.ts +7 -37
  48. package/src/__tests__/avatar-generator.test.ts +12 -42
  49. package/src/__tests__/avatar-identity-sync.test.ts +28 -3
  50. package/src/__tests__/background-shell-bash.test.ts +3 -7
  51. package/src/__tests__/background-workers-disk-pressure.test.ts +5 -8
  52. package/src/__tests__/browser-skill-endstate.test.ts +3 -3
  53. package/src/__tests__/btw-routes.test.ts +10 -14
  54. package/src/__tests__/call-controller.test.ts +3 -2
  55. package/src/__tests__/call-pointer-messages.test.ts +5 -3
  56. package/src/__tests__/call-site-routing-provider.test.ts +22 -40
  57. package/src/__tests__/catalog-files.test.ts +1 -0
  58. package/src/__tests__/channel-approval-routes.test.ts +51 -22
  59. package/src/__tests__/channel-approvals.test.ts +3 -1
  60. package/src/__tests__/channel-guardian.test.ts +3 -2
  61. package/src/__tests__/channel-invite-transport.test.ts +1 -5
  62. package/src/__tests__/channel-readiness-routes.test.ts +0 -4
  63. package/src/__tests__/channel-readiness-slack-remote.test.ts +170 -0
  64. package/src/__tests__/channel-reply-delivery.test.ts +35 -0
  65. package/src/__tests__/channel-retry-sweep.test.ts +388 -79
  66. package/src/__tests__/checker.test.ts +12 -12
  67. package/src/__tests__/circuit-breaker-pipeline.test.ts +3 -3
  68. package/src/__tests__/clawhub-files.test.ts +1 -0
  69. package/src/__tests__/compaction-events.test.ts +6 -17
  70. package/src/__tests__/compaction-pipeline.test.ts +1 -1
  71. package/src/__tests__/compaction-timeout-recovery.test.ts +37 -48
  72. package/src/__tests__/compaction-trail-store.test.ts +186 -0
  73. package/src/__tests__/compactor-call-site-logging.test.ts +1 -0
  74. package/src/__tests__/compactor-image-manifest-trust.test.ts +112 -0
  75. package/src/__tests__/compactor-preserved-tail-count.test.ts +1 -0
  76. package/src/__tests__/computer-use-skill-manifest-regression.test.ts +7 -5
  77. package/src/__tests__/computer-use-tools.test.ts +14 -16
  78. package/src/__tests__/config-loader-backfill.test.ts +13 -28
  79. package/src/__tests__/config-loader-corrupt.test.ts +5 -5
  80. package/src/__tests__/config-loader-platform-defaults.test.ts +93 -26
  81. package/src/__tests__/config-loader-quarantine-bulletin.test.ts +3 -3
  82. package/src/__tests__/config-managed-gemini-defaults.test.ts +3 -4
  83. package/src/__tests__/config-schema.test.ts +10 -10
  84. package/src/__tests__/config-watcher.test.ts +28 -0
  85. package/src/__tests__/connection-model-compat.test.ts +83 -0
  86. package/src/__tests__/contacts-tools.test.ts +3 -2
  87. package/src/__tests__/context-search-agent-runner.test.ts +6 -3
  88. package/src/__tests__/context-token-estimator.test.ts +56 -0
  89. package/src/__tests__/context-window-manager-compact-retry.test.ts +291 -0
  90. package/src/__tests__/conversation-abort-tool-results.test.ts +19 -7
  91. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +4 -2
  92. package/src/__tests__/conversation-agent-loop-handlers-max-tokens.test.ts +55 -0
  93. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +13 -27
  94. package/src/__tests__/conversation-agent-loop-overflow.test.ts +464 -90
  95. package/src/__tests__/conversation-agent-loop.test.ts +1069 -64
  96. package/src/__tests__/conversation-analysis-routes.test.ts +2 -3
  97. package/src/__tests__/conversation-app-control-instantiation.test.ts +29 -19
  98. package/src/__tests__/conversation-app-control-lifecycle.test.ts +2 -1
  99. package/src/__tests__/conversation-attention-store.test.ts +101 -0
  100. package/src/__tests__/conversation-attention-telegram.test.ts +3 -2
  101. package/src/__tests__/conversation-clear-safety.test.ts +20 -10
  102. package/src/__tests__/conversation-confirmation-signals.test.ts +16 -45
  103. package/src/__tests__/conversation-disk-view-integration.test.ts +2 -2
  104. package/src/__tests__/conversation-disk-view.test.ts +10 -17
  105. package/src/__tests__/conversation-error.test.ts +30 -0
  106. package/src/__tests__/conversation-fork-crud.test.ts +132 -157
  107. package/src/__tests__/conversation-fork-route.test.ts +19 -16
  108. package/src/__tests__/conversation-history-web-search.test.ts +1 -0
  109. package/src/__tests__/conversation-inference-profile-list.test.ts +3 -2
  110. package/src/__tests__/conversation-inference-profile-route.test.ts +3 -2
  111. package/src/__tests__/conversation-init.benchmark.test.ts +6 -6
  112. package/src/__tests__/conversation-lifecycle.test.ts +4 -2
  113. package/src/__tests__/conversation-list-source.test.ts +3 -2
  114. package/src/__tests__/conversation-load-history-repair.test.ts +5 -3
  115. package/src/__tests__/conversation-load-history-stripped.test.ts +2 -1
  116. package/src/__tests__/conversation-message-sync-tags.test.ts +3 -4
  117. package/src/__tests__/conversation-pairing.test.ts +87 -4
  118. package/src/__tests__/conversation-pre-run-repair.test.ts +1 -1
  119. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +30 -7
  120. package/src/__tests__/conversation-process-callsite.test.ts +28 -30
  121. package/src/__tests__/conversation-provider-retry-repair.test.ts +58 -44
  122. package/src/__tests__/conversation-queue.test.ts +603 -455
  123. package/src/__tests__/conversation-routes-disk-view.test.ts +6 -20
  124. package/src/__tests__/conversation-routes-guardian-reply.test.ts +35 -10
  125. package/src/__tests__/conversation-routes-slash-commands.test.ts +35 -4
  126. package/src/__tests__/conversation-runtime-assembly.test.ts +98 -22
  127. package/src/__tests__/conversation-runtime-workspace.test.ts +19 -1
  128. package/src/__tests__/conversation-skill-tools.test.ts +38 -142
  129. package/src/__tests__/conversation-slash-queue.test.ts +120 -62
  130. package/src/__tests__/conversation-slash-unknown.test.ts +18 -15
  131. package/src/__tests__/conversation-speed-override.test.ts +9 -22
  132. package/src/__tests__/conversation-stream-state.test.ts +484 -0
  133. package/src/__tests__/conversation-surfaces-action-delivery.test.ts +52 -15
  134. package/src/__tests__/conversation-surfaces-app-control.test.ts +32 -4
  135. package/src/__tests__/conversation-surfaces-data-persist.test.ts +1 -0
  136. package/src/__tests__/conversation-surfaces-standalone-payloads.test.ts +6 -3
  137. package/src/__tests__/conversation-surfaces-standalone.test.ts +6 -3
  138. package/src/__tests__/conversation-surfaces-state-update.test.ts +8 -5
  139. package/src/__tests__/conversation-surfaces-table-action.test.ts +13 -32
  140. package/src/__tests__/conversation-sync-tags.test.ts +128 -12
  141. package/src/__tests__/conversation-title-service.test.ts +1 -0
  142. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +53 -11
  143. package/src/__tests__/conversation-unread-route.test.ts +14 -2
  144. package/src/__tests__/conversation-usage.test.ts +1 -2
  145. package/src/__tests__/conversation-wipe.test.ts +1 -1
  146. package/src/__tests__/conversation-workspace-cache-state.test.ts +4 -1
  147. package/src/__tests__/conversation-workspace-injection.test.ts +53 -22
  148. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +32 -7
  149. package/src/__tests__/credential-broker-browser-fill.test.ts +3 -3
  150. package/src/__tests__/credential-broker-server-use.test.ts +5 -5
  151. package/src/__tests__/credential-execution-client.test.ts +72 -1
  152. package/src/__tests__/credential-execution-feature-gates.test.ts +10 -12
  153. package/src/__tests__/credential-execution-tools.test.ts +1 -2
  154. package/src/__tests__/credential-health-service.test.ts +252 -3
  155. package/src/__tests__/credential-security-invariants.test.ts +5 -6
  156. package/src/__tests__/credential-vault-unit.test.ts +19 -19
  157. package/src/__tests__/credential-vault.test.ts +5 -5
  158. package/src/__tests__/cross-provider-web-search.test.ts +61 -3
  159. package/src/__tests__/cu-unified-flow.test.ts +26 -1
  160. package/src/__tests__/db-connection-isolation.test.ts +7 -6
  161. package/src/__tests__/db-conversation-fork-lineage-migration.test.ts +8 -10
  162. package/src/__tests__/db-conversation-inference-profile-migration.test.ts +7 -10
  163. package/src/__tests__/db-llm-request-log-provider-migration.test.ts +9 -15
  164. package/src/__tests__/db-schedule-syntax-migration.test.ts +11 -0
  165. package/src/__tests__/db-test-helpers.ts +58 -0
  166. package/src/__tests__/disk-pressure-guard.test.ts +119 -36
  167. package/src/__tests__/disk-pressure-lifecycle.test.ts +13 -10
  168. package/src/__tests__/disk-pressure-routes.test.ts +9 -35
  169. package/src/__tests__/disk-pressure-tools.test.ts +0 -4
  170. package/src/__tests__/dm-persistence.test.ts +33 -42
  171. package/src/__tests__/document-create-dedupe.test.ts +189 -0
  172. package/src/__tests__/document-find-replace.test.ts +3 -2
  173. package/src/__tests__/document-tool-security.test.ts +81 -2
  174. package/src/__tests__/dynamic-page-surface.test.ts +68 -0
  175. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +5 -4
  176. package/src/__tests__/edit-propagation.test.ts +1 -2
  177. package/src/__tests__/empty-response-pipeline.test.ts +127 -5
  178. package/src/__tests__/encrypted-store-test-helpers.ts +56 -0
  179. package/src/__tests__/encrypted-store.test.ts +11 -9
  180. package/src/__tests__/feature-flag-test-helpers.ts +53 -0
  181. package/src/__tests__/filing-service.test.ts +3 -2
  182. package/src/__tests__/first-greeting.test.ts +103 -12
  183. package/src/__tests__/gateway-flag-listener.test.ts +0 -1
  184. package/src/__tests__/gemini-inline-media.test.ts +78 -0
  185. package/src/__tests__/gemini-provider.test.ts +375 -26
  186. package/src/__tests__/guardian-action-sweep.test.ts +3 -2
  187. package/src/__tests__/guardian-outbound-http.test.ts +3 -2
  188. package/src/__tests__/guardian-routing-state.test.ts +60 -71
  189. package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +48 -3
  190. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +10 -7
  191. package/src/__tests__/heartbeat-disk-pressure.test.ts +2 -0
  192. package/src/__tests__/heartbeat-service.test.ts +3 -1
  193. package/src/__tests__/helpers/mock-logger.ts +26 -0
  194. package/src/__tests__/history-repair-hook.test.ts +161 -0
  195. package/src/__tests__/history-repair-observability.test.ts +1 -1
  196. package/src/__tests__/history-repair.test.ts +2 -1
  197. package/src/__tests__/host-app-control-proxy.test.ts +2 -0
  198. package/src/__tests__/host-bash-routes.test.ts +1 -0
  199. package/src/__tests__/host-cu-proxy.test.ts +2 -0
  200. package/src/__tests__/host-cu-routes-targeted.test.ts +1 -0
  201. package/src/__tests__/host-file-edit-tool.test.ts +4 -2
  202. package/src/__tests__/host-file-proxy.test.ts +31 -0
  203. package/src/__tests__/host-file-read-tool.test.ts +4 -2
  204. package/src/__tests__/host-file-routes-targeted.test.ts +1 -0
  205. package/src/__tests__/host-file-write-tool.test.ts +9 -3
  206. package/src/__tests__/host-proxy-preactivation.test.ts +53 -14
  207. package/src/__tests__/host-shell-tool.test.ts +11 -5
  208. package/src/__tests__/host-transfer-routes-targeted.test.ts +1 -0
  209. package/src/__tests__/http-conversation-lineage.test.ts +3 -2
  210. package/src/__tests__/http-user-message-parity.test.ts +31 -9
  211. package/src/__tests__/identity-intro-cache.test.ts +154 -22
  212. package/src/__tests__/inbound-slack-persistence.test.ts +51 -74
  213. package/src/__tests__/inference-profile-reaper.test.ts +3 -2
  214. package/src/__tests__/inference-profile-session-ipc.test.ts +3 -2
  215. package/src/__tests__/injector-background-turn.test.ts +1 -1
  216. package/src/__tests__/injector-chain.test.ts +1 -1
  217. package/src/__tests__/injector-disk-pressure.test.ts +4 -18
  218. package/src/__tests__/injector-document-comments.test.ts +1 -1
  219. package/src/__tests__/injector-pkb-v2-silenced.test.ts +1 -1
  220. package/src/__tests__/injector-v3-suppression.test.ts +220 -0
  221. package/src/__tests__/inline-skill-load-permissions.test.ts +4 -4
  222. package/src/__tests__/list-messages-attachments.test.ts +7 -8
  223. package/src/__tests__/list-messages-hidden-metadata.test.ts +93 -11
  224. package/src/__tests__/list-messages-page-latest.test.ts +0 -1
  225. package/src/__tests__/list-messages-tool-merge.test.ts +36 -6
  226. package/src/__tests__/llm-call-pipeline.test.ts +21 -15
  227. package/src/__tests__/llm-context-normalization.test.ts +42 -0
  228. package/src/__tests__/llm-request-log-turn-query.test.ts +42 -86
  229. package/src/__tests__/llm-resolver.test.ts +346 -39
  230. package/src/__tests__/llm-schema.test.ts +1 -1
  231. package/src/__tests__/llm-usage-store.test.ts +45 -0
  232. package/src/__tests__/log-export-routes.test.ts +59 -0
  233. package/src/__tests__/managed-skill-lifecycle.test.ts +1 -8
  234. package/src/__tests__/manual-token-reconciliation.test.ts +76 -1
  235. package/src/__tests__/mcp-abort-signal.test.ts +14 -0
  236. package/src/__tests__/mcp-auth-routes.test.ts +15 -10
  237. package/src/__tests__/mcp-client-auth.test.ts +14 -0
  238. package/src/__tests__/mcp-health-check.test.ts +18 -13
  239. package/src/__tests__/memory-retrieval-pipeline.test.ts +1 -1
  240. package/src/__tests__/memory-v2-static-injector.test.ts +1 -1
  241. package/src/__tests__/messaging-send-tool.test.ts +9 -4
  242. package/src/__tests__/migration-export-http.test.ts +12 -12
  243. package/src/__tests__/migration-import-commit-http.test.ts +8 -8
  244. package/src/__tests__/migration-import-from-url.test.ts +3 -3
  245. package/src/__tests__/migration-import-preflight-http.test.ts +7 -7
  246. package/src/__tests__/migration-validate-http.test.ts +3 -3
  247. package/src/__tests__/mock-gateway-ipc.ts +18 -2
  248. package/src/__tests__/model-intents.test.ts +3 -3
  249. package/src/__tests__/native-web-search.test.ts +44 -22
  250. package/src/__tests__/notification-decision-identity.test.ts +9 -18
  251. package/src/__tests__/notification-decision-recipient-context.test.ts +3 -6
  252. package/src/__tests__/notification-deep-link.test.ts +62 -0
  253. package/src/__tests__/oauth-commands-routes.test.ts +38 -1
  254. package/src/__tests__/oauth-provider-visibility.test.ts +8 -8
  255. package/src/__tests__/oauth-store.test.ts +3 -2
  256. package/src/__tests__/onboarding-template-contract.test.ts +13 -2
  257. package/src/__tests__/openai-provider.test.ts +74 -79
  258. package/src/__tests__/openai-responses-provider.test.ts +90 -86
  259. package/src/__tests__/openrouter-provider-only.test.ts +27 -5
  260. package/src/__tests__/outbound-slack-persistence.test.ts +48 -2
  261. package/src/__tests__/overflow-reduce-pipeline.test.ts +2 -4
  262. package/src/__tests__/parallel-tool.benchmark.test.ts +24 -36
  263. package/src/__tests__/persistence-pipeline.test.ts +154 -27
  264. package/src/__tests__/persistence-secret-redaction.test.ts +85 -13
  265. package/src/__tests__/pipeline-runner.test.ts +2 -3
  266. package/src/__tests__/plugin-bootstrap.test.ts +60 -36
  267. package/src/__tests__/plugin-route-contribution.test.ts +6 -16
  268. package/src/__tests__/plugin-skill-contribution.test.ts +7 -17
  269. package/src/__tests__/plugin-tool-contribution.test.ts +51 -64
  270. package/src/__tests__/plugin-types.test.ts +7 -14
  271. package/src/__tests__/prechat-onboarding-contract.test.ts +23 -0
  272. package/src/__tests__/process-message-background-slack.test.ts +38 -32
  273. package/src/__tests__/process-message-display-content.test.ts +49 -64
  274. package/src/__tests__/provider-catalog-visibility.test.ts +9 -9
  275. package/src/__tests__/provider-commit-message-generator.test.ts +19 -14
  276. package/src/__tests__/provider-error-scenarios.test.ts +7 -6
  277. package/src/__tests__/provider-platform-proxy-integration.test.ts +215 -8
  278. package/src/__tests__/provider-registry-ollama.test.ts +45 -22
  279. package/src/__tests__/provider-send-message-override-profile.test.ts +9 -25
  280. package/src/__tests__/provider-streaming.benchmark.test.ts +12 -22
  281. package/src/__tests__/provider-usage-tracking.test.ts +0 -6
  282. package/src/__tests__/ratelimit.test.ts +9 -4
  283. package/src/__tests__/recording-handler.test.ts +1 -0
  284. package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +1 -0
  285. package/src/__tests__/registry.test.ts +82 -76
  286. package/src/__tests__/relay-server.test.ts +30 -23
  287. package/src/__tests__/retry-openrouter-only-normalization.test.ts +5 -8
  288. package/src/__tests__/retry-thinking-tool-choice.test.ts +10 -13
  289. package/src/__tests__/retry-verbosity-normalization.test.ts +5 -8
  290. package/src/__tests__/runtime-attachment-metadata.test.ts +3 -2
  291. package/src/__tests__/runtime-events-sse-reconnect.test.ts +353 -0
  292. package/src/__tests__/schedule-routes.test.ts +80 -10
  293. package/src/__tests__/schedule-store.test.ts +83 -1
  294. package/src/__tests__/schedule-tools.test.ts +125 -0
  295. package/src/__tests__/scheduler-reuse-conversation.test.ts +48 -3
  296. package/src/__tests__/secret-ingress-http.test.ts +7 -3
  297. package/src/__tests__/secret-prompt-log-hygiene.test.ts +11 -7
  298. package/src/__tests__/secret-prompter-channel-fallback.test.ts +11 -9
  299. package/src/__tests__/secret-response-routing.test.ts +13 -11
  300. package/src/__tests__/secure-keys.test.ts +3 -3
  301. package/src/__tests__/send-endpoint-busy.test.ts +83 -43
  302. package/src/__tests__/server-history-render.test.ts +4 -1
  303. package/src/__tests__/shell-observability.test.ts +249 -0
  304. package/src/__tests__/skill-feature-flags-integration.test.ts +19 -21
  305. package/src/__tests__/skill-feature-flags.test.ts +20 -22
  306. package/src/__tests__/skill-load-feature-flag.test.ts +15 -15
  307. package/src/__tests__/skill-projection-feature-flag.test.ts +44 -30
  308. package/src/__tests__/skill-projection.benchmark.test.ts +5 -7
  309. package/src/__tests__/skill-tool-factory.test.ts +96 -95
  310. package/src/__tests__/skills-files-catalog-fallback.test.ts +10 -0
  311. package/src/__tests__/skillssh-files.test.ts +1 -0
  312. package/src/__tests__/slack-channel-config.test.ts +3 -3
  313. package/src/__tests__/starter-task-flow.test.ts +6 -6
  314. package/src/__tests__/strip-memory-injections.test.ts +102 -14
  315. package/src/__tests__/subagent-call-site-routing.test.ts +13 -5
  316. package/src/__tests__/subagent-disposal.test.ts +27 -8
  317. package/src/__tests__/subagent-fork-notifications.test.ts +24 -9
  318. package/src/__tests__/subagent-fork-spawn.test.ts +13 -4
  319. package/src/__tests__/subagent-manager-notify.test.ts +20 -8
  320. package/src/__tests__/subagent-notify-parent.test.ts +5 -4
  321. package/src/__tests__/subagent-spawn-tool-fork.test.ts +58 -0
  322. package/src/__tests__/subagent-tools.test.ts +2 -1
  323. package/src/__tests__/suggestion-routes.test.ts +4 -3
  324. package/src/__tests__/sync-message-contract.test.ts +19 -16
  325. package/src/__tests__/system-prompt.test.ts +92 -0
  326. package/src/__tests__/terminal-tools.test.ts +3 -24
  327. package/src/__tests__/test-preload-verifier.ts +68 -0
  328. package/src/__tests__/test-preload.ts +32 -39
  329. package/src/__tests__/thread-backfill.test.ts +4 -9
  330. package/src/__tests__/title-generate-pipeline.test.ts +1 -1
  331. package/src/__tests__/token-estimate-pipeline.test.ts +2 -4
  332. package/src/__tests__/tool-error-pipeline.test.ts +2 -2
  333. package/src/__tests__/tool-execute-pipeline.test.ts +1 -1
  334. package/src/__tests__/tool-executor-lifecycle-events.test.ts +20 -7
  335. package/src/__tests__/tool-executor.test.ts +55 -10
  336. package/src/__tests__/tool-preview-lifecycle.test.ts +14 -11
  337. package/src/__tests__/tool-result-metadata-plumbing.test.ts +1 -0
  338. package/src/__tests__/tool-result-truncate-pipeline.test.ts +9 -12
  339. package/src/__tests__/tool-result-truncation.test.ts +3 -1
  340. package/src/__tests__/tools-audio-read.test.ts +113 -0
  341. package/src/__tests__/turn-boundary-resolution.test.ts +44 -84
  342. package/src/__tests__/turn-events-store.test.ts +11 -7
  343. package/src/__tests__/twilio-routes.test.ts +3 -2
  344. package/src/__tests__/validate-input.test.ts +381 -0
  345. package/src/__tests__/verification-control-plane-policy.test.ts +1 -0
  346. package/src/__tests__/voice-scoped-grant-consumer.test.ts +10 -7
  347. package/src/__tests__/voice-session-bridge.test.ts +50 -35
  348. package/src/__tests__/workspace-migration-090-memory-router-cost-optimized-profile.test.ts +326 -0
  349. package/src/__tests__/workspace-migration-091-retighten-migration-onboarding-thread.test.ts +166 -0
  350. package/src/acp/__tests__/prepare-agent-env.test.ts +143 -31
  351. package/src/acp/prepare-agent-env.ts +52 -11
  352. package/src/acp/session-manager.ts +5 -6
  353. package/src/agent/compaction-circuit.ts +140 -0
  354. package/src/agent/loop.ts +489 -85
  355. package/src/api/README.md +126 -2
  356. package/src/api/constants/call-sites.ts +27 -0
  357. package/src/api/constants/tool-execution.ts +21 -0
  358. package/src/api/events/assistant-activity-state.ts +75 -0
  359. package/src/api/events/assistant-outbound-attachment.ts +49 -0
  360. package/src/api/events/assistant-text-delta.ts +30 -0
  361. package/src/api/events/assistant-turn-start.ts +31 -0
  362. package/src/api/events/avatar-updated.ts +24 -0
  363. package/src/api/events/compaction-circuit-closed.ts +26 -0
  364. package/src/api/events/compaction-circuit-open.ts +28 -0
  365. package/src/api/events/confirmation-request.ts +114 -0
  366. package/src/api/events/contact-request.ts +33 -0
  367. package/src/api/events/conversation-error.ts +77 -0
  368. package/src/api/events/conversation-list-invalidated.ts +38 -0
  369. package/src/api/events/conversation-title-updated.ts +24 -0
  370. package/src/api/events/disk-pressure-status-changed.ts +61 -0
  371. package/src/api/events/document-comment-created.ts +44 -0
  372. package/src/api/events/document-comment-deleted.ts +22 -0
  373. package/src/api/events/document-comment-reopened.ts +23 -0
  374. package/src/api/events/document-comment-resolved.ts +25 -0
  375. package/src/api/events/document-editor-update.ts +27 -0
  376. package/src/api/events/error.ts +32 -0
  377. package/src/api/events/generation-cancelled.ts +22 -0
  378. package/src/api/events/generation-handoff.ts +39 -0
  379. package/src/api/events/home-feed-updated.ts +26 -0
  380. package/src/api/events/identity-changed.ts +32 -0
  381. package/src/api/events/interaction-resolved.ts +50 -0
  382. package/src/api/events/message-complete.ts +40 -0
  383. package/src/api/events/message-dequeued.ts +21 -0
  384. package/src/api/events/message-queued-deleted.ts +23 -0
  385. package/src/api/events/message-queued.ts +22 -0
  386. package/src/api/events/message-request-complete.ts +29 -0
  387. package/src/api/events/navigate-settings.ts +20 -0
  388. package/src/api/events/notification-intent.ts +33 -0
  389. package/src/api/events/open-url.ts +28 -0
  390. package/src/api/events/question-request.ts +67 -0
  391. package/src/{events → api/events}/relationship-state-updated.ts +6 -8
  392. package/src/api/events/secret-request.ts +42 -0
  393. package/src/api/events/subagent-event.ts +79 -0
  394. package/src/api/events/subagent-spawned.ts +40 -0
  395. package/src/api/events/subagent-status-changed.ts +65 -0
  396. package/src/api/events/sync-changed.ts +29 -0
  397. package/src/api/events/tool-result.ts +129 -0
  398. package/src/api/events/tool-use-start.ts +30 -0
  399. package/src/api/events/turn-profile-auto-routed.ts +28 -0
  400. package/src/api/events/ui-surface-complete.ts +30 -0
  401. package/src/api/events/ui-surface-dismiss.ts +22 -0
  402. package/src/api/events/ui-surface-show.ts +67 -0
  403. package/src/api/events/ui-surface-update.ts +26 -0
  404. package/src/api/events/usage-update.ts +34 -0
  405. package/src/api/events/user-message-echo.ts +35 -0
  406. package/src/api/index.ts +482 -3
  407. package/src/api/requests/dictation.ts +45 -0
  408. package/src/api/responses/disk-pressure-status.ts +26 -0
  409. package/src/api/responses/home.ts +217 -0
  410. package/src/api/responses/llm-context-response.ts +41 -0
  411. package/src/api/responses/llm-request-log-entry.ts +93 -0
  412. package/src/api/responses/memory-recall-log.ts +65 -0
  413. package/src/api/responses/memory-v2-activation-log.ts +78 -0
  414. package/src/api/responses/memory-v3-selection-log.ts +50 -0
  415. package/src/api/responses/subagent-detail.ts +48 -0
  416. package/src/approvals/guardian-decision-primitive.ts +7 -15
  417. package/src/approvals/guardian-request-resolvers.ts +6 -9
  418. package/src/avatar/__tests__/avatar-manifest.test.ts +236 -0
  419. package/src/avatar/__tests__/avatar-store.test.ts +193 -0
  420. package/src/avatar/avatar-manifest.ts +195 -0
  421. package/src/avatar/avatar-store.ts +113 -0
  422. package/src/avatar/traits-png-sync.ts +8 -2
  423. package/src/background-wake/background-wake-routes.test.ts +687 -52
  424. package/src/background-wake/next-wake.test.ts +31 -1
  425. package/src/background-wake/next-wake.ts +4 -1
  426. package/src/background-wake/platform-client.test.ts +308 -0
  427. package/src/background-wake/platform-client.ts +167 -0
  428. package/src/background-wake/publisher.ts +91 -0
  429. package/src/background-wake/runtime-registry.ts +2 -2
  430. package/src/background-wake/wake-intent-hooks.test.ts +282 -0
  431. package/src/calls/call-conversation-messages.ts +6 -4
  432. package/src/calls/guardian-action-sweep.ts +6 -4
  433. package/src/calls/guardian-dispatch.ts +1 -0
  434. package/src/calls/relay-server.ts +12 -8
  435. package/src/calls/voice-session-bridge.ts +17 -31
  436. package/src/cli/commands/__tests__/conversations-slack.test.ts +16 -0
  437. package/src/cli/commands/__tests__/memory-v3.test.ts +245 -0
  438. package/src/cli/commands/__tests__/notifications.test.ts +184 -40
  439. package/src/cli/commands/avatar.ts +17 -11
  440. package/src/cli/commands/channels/__tests__/channels.test.ts +143 -0
  441. package/src/cli/commands/channels/index.ts +229 -0
  442. package/src/cli/commands/conversations.ts +15 -1
  443. package/src/cli/commands/db/__tests__/repair.test.ts +540 -0
  444. package/src/cli/commands/db/__tests__/status.test.ts +253 -0
  445. package/src/cli/commands/db/format.ts +48 -0
  446. package/src/cli/commands/db/index.ts +29 -0
  447. package/src/cli/commands/db/repair-step-conversation-backfill.ts +345 -0
  448. package/src/cli/commands/db/repair-step-integrity.ts +146 -0
  449. package/src/cli/commands/db/repair-steps.ts +164 -0
  450. package/src/cli/commands/db/repair.ts +141 -0
  451. package/src/cli/commands/db/status.ts +366 -0
  452. package/src/cli/commands/memory-v3.ts +168 -203
  453. package/src/cli/commands/notifications.ts +365 -55
  454. package/src/cli/lib/cli-colors.ts +24 -6
  455. package/src/cli/lib/open-browser.ts +7 -2
  456. package/src/cli/program.ts +6 -5
  457. package/src/config/__tests__/feature-flag-registry-guard.test.ts +2 -2
  458. package/src/config/assistant-feature-flags.ts +25 -44
  459. package/src/config/bundled-skills/app-builder/SKILL.md +14 -3
  460. package/src/config/bundled-skills/document-editor/SKILL.md +5 -1
  461. package/src/config/bundled-skills/media-processing/services/reduce.ts +6 -9
  462. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +7 -2
  463. package/src/config/bundled-skills/schedule/SKILL.md +2 -2
  464. package/src/config/bundled-skills/schedule/TOOLS.json +10 -2
  465. package/src/config/bundled-skills/settings/tools/open-system-settings.ts +1 -0
  466. package/src/config/call-site-defaults.ts +3 -8
  467. package/src/config/feature-flag-cache.ts +86 -0
  468. package/src/config/feature-flag-registry.json +42 -26
  469. package/src/config/llm-context-resolution.ts +10 -1
  470. package/src/config/llm-resolver.ts +121 -15
  471. package/src/config/loader.ts +4 -5
  472. package/src/config/schemas/__tests__/memory-v2.test.ts +1 -211
  473. package/src/config/schemas/call-site-catalog.ts +8 -15
  474. package/src/config/schemas/heartbeat.ts +1 -1
  475. package/src/config/schemas/llm.ts +92 -4
  476. package/src/config/schemas/memory-lifecycle.ts +24 -0
  477. package/src/config/schemas/memory-v2.ts +0 -227
  478. package/src/config/schemas/memory-v3.ts +39 -0
  479. package/src/config/schemas/memory.ts +6 -1
  480. package/src/config/schemas/services.ts +6 -2
  481. package/src/config/schemas/timeouts.ts +3 -1
  482. package/src/config/seed-inference-profiles.ts +36 -16
  483. package/src/context/compactor.ts +54 -31
  484. package/src/context/token-estimator.ts +29 -5
  485. package/src/context/tool-result-truncation.ts +1 -43
  486. package/src/context/window-manager.ts +138 -20
  487. package/src/credential-execution/executable-discovery.ts +40 -0
  488. package/src/credential-execution/process-manager.ts +6 -2
  489. package/src/credential-health/credential-health-service.ts +125 -40
  490. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +3 -6
  491. package/src/daemon/__tests__/conversation-surfaces-launch.test.ts +15 -17
  492. package/src/daemon/__tests__/conversation-tool-setup-exclude.test.ts +1 -2
  493. package/src/daemon/__tests__/daemon-skill-host.test.ts +2 -0
  494. package/src/daemon/__tests__/meet-manifest-loader.test.ts +25 -12
  495. package/src/daemon/__tests__/native-web-search-metadata.test.ts +1 -0
  496. package/src/daemon/__tests__/switch-inference-profile-tool.test.ts +107 -0
  497. package/src/daemon/__tests__/web-search-status-text.test.ts +11 -6
  498. package/src/daemon/approval-generators.ts +4 -4
  499. package/src/daemon/config-watcher.ts +7 -1
  500. package/src/daemon/conversation-agent-loop-handlers.ts +613 -155
  501. package/src/daemon/conversation-agent-loop.ts +409 -605
  502. package/src/daemon/conversation-error.ts +40 -12
  503. package/src/daemon/conversation-history.ts +22 -6
  504. package/src/daemon/conversation-launch.ts +4 -8
  505. package/src/daemon/conversation-lifecycle.ts +10 -38
  506. package/src/daemon/conversation-messaging.ts +83 -44
  507. package/src/daemon/conversation-notifiers.ts +7 -5
  508. package/src/daemon/conversation-process.ts +174 -116
  509. package/src/daemon/conversation-runtime-assembly.ts +76 -30
  510. package/src/daemon/conversation-skill-tools.ts +14 -30
  511. package/src/daemon/conversation-store.ts +6 -5
  512. package/src/daemon/conversation-surfaces.ts +124 -103
  513. package/src/daemon/conversation-tool-setup.ts +36 -48
  514. package/src/daemon/conversation.ts +111 -166
  515. package/src/daemon/daemon-control.ts +1 -1
  516. package/src/daemon/daemon-skill-host.ts +7 -4
  517. package/src/daemon/disk-pressure-guard.ts +54 -50
  518. package/src/daemon/external-plugins-bootstrap.ts +46 -24
  519. package/src/daemon/first-greeting.ts +53 -13
  520. package/src/daemon/guardian-action-generators.ts +2 -2
  521. package/src/daemon/handlers/conversations.ts +6 -22
  522. package/src/daemon/handlers/shared.ts +10 -1
  523. package/src/daemon/handlers/skills.ts +15 -14
  524. package/src/daemon/host-app-control-proxy.ts +54 -1
  525. package/src/daemon/host-cu-proxy.ts +46 -22
  526. package/src/daemon/host-file-proxy.ts +25 -1
  527. package/src/daemon/host-proxy-preactivation.ts +25 -6
  528. package/src/daemon/lifecycle.ts +40 -67
  529. package/src/daemon/mcp-reload-service.ts +1 -1
  530. package/src/daemon/meet-manifest-loader.ts +10 -17
  531. package/src/daemon/message-protocol.ts +2 -3
  532. package/src/daemon/message-provenance.ts +49 -0
  533. package/src/daemon/message-types/contacts.ts +3 -20
  534. package/src/daemon/message-types/conversations.ts +25 -125
  535. package/src/daemon/message-types/document-comments.ts +8 -44
  536. package/src/daemon/message-types/documents.ts +3 -9
  537. package/src/daemon/message-types/home.ts +5 -18
  538. package/src/daemon/message-types/integrations.ts +4 -13
  539. package/src/daemon/message-types/messages.ts +47 -377
  540. package/src/daemon/message-types/notifications.ts +2 -32
  541. package/src/daemon/message-types/settings.ts +3 -8
  542. package/src/daemon/message-types/skills.ts +2 -0
  543. package/src/daemon/message-types/subagents.ts +6 -0
  544. package/src/daemon/message-types/surfaces.ts +2 -0
  545. package/src/daemon/message-types/sync.ts +12 -25
  546. package/src/daemon/message-types/workspace.ts +3 -11
  547. package/src/daemon/process-message.ts +58 -55
  548. package/src/daemon/providers-setup.ts +1 -1
  549. package/src/daemon/server.ts +28 -0
  550. package/src/daemon/switch-inference-profile-tool.ts +13 -3
  551. package/src/daemon/tool-setup-types.ts +0 -6
  552. package/src/daemon/tool-side-effects.ts +10 -7
  553. package/src/daemon/trust-context.ts +13 -0
  554. package/src/daemon/wake-target-adapter.ts +21 -1
  555. package/src/documents/document-store.ts +38 -0
  556. package/src/export/__tests__/transcript-formatter.test.ts +1 -0
  557. package/src/heartbeat/__tests__/heartbeat-service.test.ts +31 -0
  558. package/src/heartbeat/heartbeat-run-store.ts +31 -0
  559. package/src/heartbeat/heartbeat-service.ts +79 -0
  560. package/src/home/__tests__/feed-writer.test.ts +161 -0
  561. package/src/home/__tests__/post-connect-feed.test.ts +1 -0
  562. package/src/home/__tests__/suggested-prompts.test.ts +55 -59
  563. package/src/home/feature-gate.ts +22 -0
  564. package/src/home/feed-types.ts +36 -221
  565. package/src/home/feed-writer.ts +146 -7
  566. package/src/home/suggested-prompts.ts +27 -145
  567. package/src/ipc/__tests__/cli-ipc.test.ts +1 -0
  568. package/src/ipc/__tests__/email-ipc.test.ts +0 -9
  569. package/src/ipc/gateway-client.test.ts +4 -1
  570. package/src/ipc/routes/__tests__/route-adapter.test.ts +244 -0
  571. package/src/ipc/routes/route-adapter.ts +45 -6
  572. package/src/ipc/skill-routes/__tests__/memory.test.ts +19 -9
  573. package/src/ipc/skill-routes/__tests__/providers.test.ts +10 -10
  574. package/src/ipc/skill-routes/__tests__/registries.test.ts +59 -20
  575. package/src/ipc/skill-routes/memory.ts +27 -13
  576. package/src/ipc/skill-routes/providers.ts +5 -6
  577. package/src/ipc/skill-routes/registries.ts +39 -88
  578. package/src/live-voice/__tests__/live-voice-archive.test.ts +24 -11
  579. package/src/memory/__tests__/conversation-queries.test.ts +192 -8
  580. package/src/memory/__tests__/db-maintenance.test.ts +128 -0
  581. package/src/memory/__tests__/jobs-store-enqueue-gate.test.ts +1 -0
  582. package/src/memory/__tests__/jobs-store-job-classes.test.ts +5 -4
  583. package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +26 -5
  584. package/src/memory/__tests__/memory-retrospective-enqueue.test.ts +1 -0
  585. package/src/memory/__tests__/memory-retrospective-job.test.ts +11 -6
  586. package/src/memory/__tests__/memory-retrospective-startup-cleanup.test.ts +1 -0
  587. package/src/memory/__tests__/memory-v2-activation-log-store.test.ts +31 -0
  588. package/src/memory/__tests__/memory-v3-selections-migration.test.ts +103 -0
  589. package/src/memory/context-search/agent-runner.ts +2 -4
  590. package/src/memory/conversation-attention-store.ts +17 -3
  591. package/src/memory/conversation-crud.ts +386 -115
  592. package/src/memory/conversation-queries.ts +78 -22
  593. package/src/memory/db-connection.ts +29 -19
  594. package/src/memory/db-init.ts +12 -0
  595. package/src/memory/db-maintenance.ts +18 -2
  596. package/src/memory/db-singleton.ts +77 -0
  597. package/src/memory/delivery-channels.ts +82 -0
  598. package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +2 -4
  599. package/src/memory/graph/consolidation.ts +8 -11
  600. package/src/memory/graph/conversation-graph-memory.ts +41 -8
  601. package/src/memory/graph/extraction.ts +6 -9
  602. package/src/memory/graph/narrative.ts +2 -2
  603. package/src/memory/graph/pattern-scan.ts +2 -2
  604. package/src/memory/graph/retriever.test.ts +3 -3
  605. package/src/memory/graph/retriever.ts +20 -26
  606. package/src/memory/graph/tools.ts +4 -4
  607. package/src/memory/job-handlers/conversation-starters.ts +32 -32
  608. package/src/memory/job-handlers/embedding.test.ts +3 -2
  609. package/src/memory/job-handlers/summarization.ts +1 -2
  610. package/src/memory/jobs/__tests__/embed-concept-page.test.ts +5 -2
  611. package/src/memory/jobs-store.ts +3 -1
  612. package/src/memory/jobs-worker.ts +63 -40
  613. package/src/memory/llm-request-log-source-clickhouse.ts +55 -1
  614. package/src/memory/llm-request-log-source-local.ts +13 -0
  615. package/src/memory/llm-request-log-source.ts +21 -6
  616. package/src/memory/llm-request-log-store.ts +147 -3
  617. package/src/memory/llm-usage-store.ts +10 -0
  618. package/src/memory/memory-marker.ts +17 -0
  619. package/src/memory/memory-retrospective-job.ts +6 -2
  620. package/src/memory/memory-v2-activation-log-store.ts +13 -1
  621. package/src/memory/migrations/265-drop-provider-connection-status.ts +26 -0
  622. package/src/memory/migrations/266-messages-client-message-id.ts +43 -0
  623. package/src/memory/migrations/267-llm-usage-events-add-assistant-version.ts +46 -0
  624. package/src/memory/migrations/268-add-memory-v3-selections.ts +28 -0
  625. package/src/memory/migrations/269-schedule-script-timeout.ts +11 -0
  626. package/src/memory/migrations/270-messages-role-created-at-index.ts +18 -0
  627. package/src/memory/migrations/__tests__/267-llm-usage-events-add-assistant-version.test.ts +117 -0
  628. package/src/memory/migrations/index.ts +6 -0
  629. package/src/memory/schema/conversations.ts +9 -1
  630. package/src/memory/schema/inference.ts +0 -1
  631. package/src/memory/schema/infrastructure.ts +11 -0
  632. package/src/memory/v2/__tests__/backfill-jobs.test.ts +5 -2
  633. package/src/memory/v2/__tests__/consolidation-job.test.ts +124 -0
  634. package/src/memory/v2/__tests__/harness-metrics.test.ts +9 -0
  635. package/src/memory/v2/__tests__/harness-replay-input.test.ts +9 -4
  636. package/src/memory/v2/__tests__/harness-runner.test.ts +26 -0
  637. package/src/memory/v2/__tests__/migration.test.ts +11 -3
  638. package/src/memory/v2/__tests__/page-index.test.ts +37 -1
  639. package/src/memory/v2/__tests__/router.test.ts +14 -4
  640. package/src/memory/v2/__tests__/sweep-job.test.ts +9 -5
  641. package/src/memory/v2/backfill-jobs.ts +6 -0
  642. package/src/memory/v2/consolidation-job.ts +89 -9
  643. package/src/memory/v2/harness/metrics.ts +5 -1
  644. package/src/memory/v2/harness/replay-input.ts +19 -3
  645. package/src/memory/v2/harness/runner.ts +6 -0
  646. package/src/memory/v2/harness/trace.ts +6 -0
  647. package/src/memory/v2/migration.ts +5 -3
  648. package/src/memory/v2/page-index.ts +11 -0
  649. package/src/memory/v2/router.ts +8 -11
  650. package/src/memory/v2/sweep-job.ts +8 -11
  651. package/src/memory/v2/types.ts +1 -0
  652. package/src/memory/v3/__tests__/assign.test.ts +242 -0
  653. package/src/memory/v3/__tests__/capabilities.test.ts +118 -0
  654. package/src/memory/v3/__tests__/core.test.ts +39 -0
  655. package/src/memory/v3/__tests__/fixtures/eval-turns.json +36 -0
  656. package/src/memory/v3/__tests__/fixtures/live-turns.json +37 -0
  657. package/src/memory/v3/__tests__/health.test.ts +203 -0
  658. package/src/memory/v3/__tests__/live-integration.test.ts +330 -0
  659. package/src/memory/v3/__tests__/maintain-job.test.ts +288 -0
  660. package/src/memory/v3/__tests__/needle.test.ts +107 -0
  661. package/src/memory/v3/__tests__/orchestrate.test.ts +400 -0
  662. package/src/memory/v3/__tests__/reconcile.test.ts +274 -0
  663. package/src/memory/v3/__tests__/render-injection.test.ts +61 -0
  664. package/src/memory/v3/__tests__/router.test.ts +260 -0
  665. package/src/memory/v3/__tests__/selection-log-store.test.ts +179 -0
  666. package/src/memory/v3/__tests__/selector.test.ts +404 -0
  667. package/src/memory/v3/__tests__/shadow-plugin.test.ts +414 -0
  668. package/src/memory/v3/__tests__/snapshot.test.ts +168 -0
  669. package/src/memory/v3/__tests__/tree.test.ts +192 -0
  670. package/src/memory/v3/__tests__/types.test.ts +54 -0
  671. package/src/memory/v3/__tests__/working-set-eviction.test.ts +106 -0
  672. package/src/memory/v3/__tests__/working-set-skeleton.test.ts +44 -0
  673. package/src/memory/v3/assign.ts +268 -0
  674. package/src/memory/v3/capabilities.ts +124 -0
  675. package/src/memory/v3/core.ts +26 -0
  676. package/src/memory/v3/data/README.md +84 -0
  677. package/src/memory/v3/data/assignments.json +5 -0
  678. package/src/memory/v3/data/core.json +1 -0
  679. package/src/memory/v3/data/leaves/domain-a/topic-x.md +9 -0
  680. package/src/memory/v3/data/leaves/domain-a/topic-y.md +9 -0
  681. package/src/memory/v3/data/leaves/domain-b/topic-z.md +9 -0
  682. package/src/memory/v3/health.ts +0 -0
  683. package/src/memory/v3/maintain-job.ts +314 -0
  684. package/src/memory/v3/needle.ts +115 -0
  685. package/src/memory/v3/orchestrate.ts +114 -0
  686. package/src/memory/v3/page-content.ts +34 -0
  687. package/src/memory/v3/provider-blocks.ts +16 -0
  688. package/src/memory/v3/reconcile.ts +523 -0
  689. package/src/memory/v3/render-injection.ts +32 -0
  690. package/src/memory/v3/router.ts +184 -0
  691. package/src/memory/v3/selection-log-store.ts +84 -0
  692. package/src/memory/v3/selector.ts +211 -0
  693. package/src/memory/v3/shadow-plugin.ts +379 -0
  694. package/src/memory/v3/snapshot.ts +209 -0
  695. package/src/memory/v3/tree.ts +174 -0
  696. package/src/memory/v3/types.ts +46 -60
  697. package/src/memory/v3/working-set.ts +88 -0
  698. package/src/messaging/providers/slack/render-transcript.test.ts +1 -1
  699. package/src/messaging/providers/slack/render-transcript.ts +2 -2
  700. package/src/messaging/style-analyzer.ts +8 -11
  701. package/src/notifications/__tests__/emit-signal-home-feed.test.ts +1 -0
  702. package/src/notifications/__tests__/home-feed-side-effect.test.ts +1 -0
  703. package/src/notifications/adapters/slack.ts +45 -11
  704. package/src/notifications/broadcaster.ts +114 -63
  705. package/src/notifications/conversation-pairing.ts +30 -8
  706. package/src/notifications/decision-engine.ts +10 -13
  707. package/src/notifications/decisions-store.ts +32 -1
  708. package/src/notifications/deliveries-store.ts +45 -0
  709. package/src/notifications/edit-notification.ts +201 -0
  710. package/src/notifications/emit-signal.ts +11 -1
  711. package/src/notifications/preference-extractor.ts +11 -14
  712. package/src/notifications/signal.ts +10 -0
  713. package/src/notifications/types.ts +37 -0
  714. package/src/oauth/byo-connection.test.ts +67 -3
  715. package/src/oauth/byo-connection.ts +32 -5
  716. package/src/oauth/connect-orchestrator.ts +9 -0
  717. package/src/oauth/connection-resolver.test.ts +76 -0
  718. package/src/oauth/connection-resolver.ts +49 -10
  719. package/src/oauth/manual-token-connection.ts +51 -3
  720. package/src/oauth/seed-providers.ts +3 -0
  721. package/src/permissions/approval-policy.test.ts +19 -5
  722. package/src/permissions/approval-policy.ts +14 -3
  723. package/src/permissions/checker.ts +21 -8
  724. package/src/permissions/prompter.ts +42 -36
  725. package/src/permissions/question-prompter.test.ts +35 -26
  726. package/src/permissions/question-prompter.ts +6 -10
  727. package/src/platform/client.test.ts +24 -1
  728. package/src/platform/client.ts +8 -0
  729. package/src/platform/feature-gate.ts +15 -0
  730. package/src/plugin-api/index.ts +2 -0
  731. package/src/plugin-api/types.ts +25 -3
  732. package/src/plugins/defaults/circuit-breaker/middlewares/circuitBreaker.ts +93 -0
  733. package/src/plugins/defaults/circuit-breaker/package.json +15 -0
  734. package/src/plugins/defaults/circuit-breaker/register.ts +39 -0
  735. package/src/plugins/defaults/compaction/middlewares/compaction.ts +25 -0
  736. package/src/plugins/defaults/compaction/package.json +15 -0
  737. package/src/plugins/defaults/compaction/register.ts +35 -0
  738. package/src/plugins/defaults/compaction/terminal.ts +73 -0
  739. package/src/plugins/defaults/empty-response/middlewares/emptyResponse.ts +22 -0
  740. package/src/plugins/defaults/empty-response/package.json +15 -0
  741. package/src/plugins/defaults/empty-response/register.ts +28 -0
  742. package/src/plugins/defaults/empty-response/terminal.ts +106 -0
  743. package/src/plugins/defaults/history-repair/hooks/user-prompt-submit.ts +35 -0
  744. package/src/plugins/defaults/history-repair/package.json +15 -0
  745. package/src/plugins/defaults/history-repair/register.ts +24 -0
  746. package/src/{daemon/history-repair.ts → plugins/defaults/history-repair/terminal.ts} +48 -35
  747. package/src/plugins/defaults/index.ts +29 -40
  748. package/src/plugins/defaults/injectors/package.json +15 -0
  749. package/src/plugins/defaults/{injectors.ts → injectors/register.ts} +16 -46
  750. package/src/plugins/defaults/llm-call/middlewares/llmCall.ts +17 -0
  751. package/src/plugins/defaults/llm-call/package.json +15 -0
  752. package/src/plugins/defaults/{llm-call.ts → llm-call/register.ts} +6 -38
  753. package/src/plugins/defaults/memory-retrieval/middlewares/memoryRetrieval.ts +17 -0
  754. package/src/plugins/defaults/memory-retrieval/package.json +15 -0
  755. package/src/plugins/defaults/{memory-retrieval.ts → memory-retrieval/register.ts} +10 -48
  756. package/src/plugins/defaults/{overflow-reduce.ts → overflow-reduce/middlewares/overflowReduce.ts} +18 -77
  757. package/src/plugins/defaults/overflow-reduce/package.json +15 -0
  758. package/src/plugins/defaults/overflow-reduce/register.ts +42 -0
  759. package/src/plugins/defaults/persistence/middlewares/persistence.ts +19 -0
  760. package/src/plugins/defaults/persistence/package.json +15 -0
  761. package/src/plugins/defaults/persistence/register.ts +38 -0
  762. package/src/plugins/defaults/persistence/terminal.ts +83 -0
  763. package/src/plugins/defaults/title-generate/package.json +15 -0
  764. package/src/plugins/defaults/title-generate/register.ts +35 -0
  765. package/src/plugins/defaults/title-generate/terminal.ts +31 -0
  766. package/src/plugins/defaults/token-estimate/middlewares/tokenEstimate.ts +23 -0
  767. package/src/plugins/defaults/token-estimate/package.json +15 -0
  768. package/src/plugins/defaults/token-estimate/register.ts +34 -0
  769. package/src/plugins/defaults/token-estimate/terminal.ts +40 -0
  770. package/src/plugins/defaults/tool-error/middlewares/toolError.ts +21 -0
  771. package/src/plugins/defaults/tool-error/package.json +15 -0
  772. package/src/plugins/defaults/tool-error/register.ts +35 -0
  773. package/src/plugins/defaults/tool-error/terminal.ts +47 -0
  774. package/src/plugins/defaults/tool-execute/middlewares/toolExecute.ts +23 -0
  775. package/src/plugins/defaults/tool-execute/package.json +15 -0
  776. package/src/plugins/defaults/{tool-execute.ts → tool-execute/register.ts} +8 -46
  777. package/src/plugins/defaults/tool-result-truncate/middlewares/toolResultTruncate.ts +23 -0
  778. package/src/plugins/defaults/tool-result-truncate/package.json +15 -0
  779. package/src/plugins/defaults/tool-result-truncate/register.ts +35 -0
  780. package/src/plugins/defaults/tool-result-truncate/terminal.ts +113 -0
  781. package/src/plugins/defaults/tool-result-truncate/types.ts +22 -0
  782. package/src/plugins/external-plugin-loader.ts +2 -2
  783. package/src/plugins/pipeline.ts +0 -12
  784. package/src/plugins/types.ts +107 -102
  785. package/src/plugins/user-loader.ts +4 -3
  786. package/src/proactive-artifact/aux-message-injector.ts +0 -1
  787. package/src/proactive-artifact/job.test.ts +21 -8
  788. package/src/proactive-artifact/job.ts +3 -1
  789. package/src/prompts/__tests__/system-prompt.test.ts +4 -4
  790. package/src/prompts/sections.ts +20 -7
  791. package/src/prompts/system-prompt.ts +38 -40
  792. package/src/prompts/template-detection.ts +10 -4
  793. package/src/prompts/templates/BOOTSTRAP-CONTENT-AUTOMATION.md +2 -2
  794. package/src/prompts/templates/BOOTSTRAP.md +10 -10
  795. package/src/prompts/templates/IDENTITY.md +0 -2
  796. package/src/prompts/templates/system-sections.ts +6 -0
  797. package/src/providers/__tests__/connection-model-compat.test.ts +3 -4
  798. package/src/providers/__tests__/registry-native-web-search.test.ts +122 -0
  799. package/src/providers/__tests__/retry-callsite.test.ts +25 -25
  800. package/src/providers/__tests__/satellite-connection-routing.test.ts +7 -21
  801. package/src/providers/anthropic/client.ts +24 -5
  802. package/src/providers/call-site-routing.ts +34 -18
  803. package/src/providers/connection-model-compat.ts +23 -0
  804. package/src/providers/connection-resolution.ts +39 -20
  805. package/src/providers/fireworks/client.ts +1 -0
  806. package/src/providers/gemini/client.ts +176 -37
  807. package/src/providers/gemini/inline-media.ts +74 -0
  808. package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +0 -2
  809. package/src/providers/inference/__tests__/base-url-security.test.ts +2 -3
  810. package/src/providers/inference/__tests__/{connections-status-label.test.ts → connections-label.test.ts} +12 -111
  811. package/src/providers/inference/auth.ts +0 -8
  812. package/src/providers/inference/connections.ts +3 -66
  813. package/src/providers/inference/resolve-auth.ts +2 -3
  814. package/src/providers/model-catalog.ts +35 -1
  815. package/src/providers/model-intents.ts +3 -3
  816. package/src/providers/openai/__tests__/api-error-detail.test.ts +120 -0
  817. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +157 -7
  818. package/src/providers/openai/chat-completions-provider.ts +111 -16
  819. package/src/providers/openai/codex-models.ts +2 -0
  820. package/src/providers/openai/responses-provider.ts +54 -57
  821. package/src/providers/openrouter/client.ts +14 -14
  822. package/src/providers/provider-send-message.ts +23 -14
  823. package/src/providers/ratelimit.ts +1 -9
  824. package/src/providers/registry.ts +48 -8
  825. package/src/providers/retry.ts +16 -9
  826. package/src/providers/search-provider-catalog.ts +17 -9
  827. package/src/providers/types.ts +20 -2
  828. package/src/providers/usage-tracking.ts +1 -9
  829. package/src/runtime/__tests__/agent-wake.test.ts +132 -26
  830. package/src/runtime/__tests__/background-job-runner.test.ts +2 -3
  831. package/src/runtime/access-request-helper.ts +1 -0
  832. package/src/runtime/agent-wake.ts +93 -18
  833. package/src/runtime/assistant-event-hub.ts +2 -2
  834. package/src/runtime/auth/__tests__/guard-tests.test.ts +75 -109
  835. package/src/runtime/auth/__tests__/route-policy.test.ts +153 -170
  836. package/src/runtime/auth/route-policy.ts +42 -1069
  837. package/src/runtime/background-job-runner.ts +1 -4
  838. package/src/runtime/btw-sidechain.ts +3 -1
  839. package/src/runtime/channel-approvals.ts +3 -14
  840. package/src/runtime/channel-invite-transport.ts +5 -6
  841. package/src/runtime/channel-readiness-service.ts +70 -5
  842. package/src/runtime/channel-reply-delivery.ts +23 -0
  843. package/src/runtime/channel-retry-sweep.ts +59 -30
  844. package/src/runtime/confirmation-request-guardian-bridge.ts +1 -1
  845. package/src/runtime/conversation-stream-state.ts +294 -0
  846. package/src/runtime/http-router.ts +19 -22
  847. package/src/runtime/http-types.ts +12 -6
  848. package/src/runtime/invite-instruction-generator.ts +3 -3
  849. package/src/runtime/migrations/vbundle-builder.ts +3 -2
  850. package/src/runtime/pending-interactions.ts +2 -2
  851. package/src/runtime/routes/__tests__/avatar-state-routes.test.ts +565 -0
  852. package/src/runtime/routes/__tests__/bookmark-routes.test.ts +1 -0
  853. package/src/runtime/routes/__tests__/content-source-routes.test.ts +4 -4
  854. package/src/runtime/routes/__tests__/conversation-compaction-routes.test.ts +436 -0
  855. package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +237 -0
  856. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +98 -0
  857. package/src/runtime/routes/__tests__/heartbeat-routes.test.ts +1 -1
  858. package/src/runtime/routes/__tests__/home-feed-routes.test.ts +209 -1
  859. package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +26 -72
  860. package/src/runtime/routes/__tests__/memory-v2-simulate-route.test.ts +58 -5
  861. package/src/runtime/routes/__tests__/sanity-routes.test.ts +6 -6
  862. package/src/runtime/routes/__tests__/slack-channel-routes.test.ts +3 -2
  863. package/src/runtime/routes/__tests__/stt-routes.test.ts +3 -3
  864. package/src/runtime/routes/__tests__/suggest-trust-rule-routes.test.ts +5 -2
  865. package/src/runtime/routes/__tests__/surface-content-routes.test.ts +294 -0
  866. package/src/runtime/routes/__tests__/task-routes.test.ts +48 -3
  867. package/src/runtime/routes/__tests__/tts-routes.test.ts +3 -3
  868. package/src/runtime/routes/acp-routes-list.test.ts +3 -0
  869. package/src/runtime/routes/acp-routes.test.ts +97 -75
  870. package/src/runtime/routes/acp-routes.ts +29 -6
  871. package/src/runtime/routes/app-management-routes.ts +208 -28
  872. package/src/runtime/routes/app-routes.ts +25 -5
  873. package/src/runtime/routes/approval-routes.ts +16 -4
  874. package/src/runtime/routes/attachment-routes.ts +25 -1
  875. package/src/runtime/routes/audio-routes.ts +1 -0
  876. package/src/runtime/routes/audit-routes.ts +5 -0
  877. package/src/runtime/routes/auth-routes.ts +5 -0
  878. package/src/runtime/routes/avatar-routes.ts +238 -59
  879. package/src/runtime/routes/background-tool-routes.ts +9 -0
  880. package/src/runtime/routes/background-wake-routes.ts +201 -23
  881. package/src/runtime/routes/backup-routes.ts +45 -0
  882. package/src/runtime/routes/bookmark-routes.ts +13 -0
  883. package/src/runtime/routes/brain-graph-routes.ts +9 -0
  884. package/src/runtime/routes/browser-routes.ts +5 -0
  885. package/src/runtime/routes/browser-tabs-routes.ts +5 -0
  886. package/src/runtime/routes/btw-routes.ts +9 -5
  887. package/src/runtime/routes/cache-routes.ts +13 -0
  888. package/src/runtime/routes/call-routes.ts +21 -10
  889. package/src/runtime/routes/channel-availability-routes.ts +5 -1
  890. package/src/runtime/routes/channel-readiness-routes.ts +37 -4
  891. package/src/runtime/routes/channel-route-definitions.ts +21 -0
  892. package/src/runtime/routes/channel-verification-routes.ts +21 -0
  893. package/src/runtime/routes/chatgpt-subscription-auth-routes.ts +9 -2
  894. package/src/runtime/routes/client-routes.ts +9 -0
  895. package/src/runtime/routes/consolidation-routes.ts +13 -5
  896. package/src/runtime/routes/contact-prompt-routes.ts +9 -0
  897. package/src/runtime/routes/contact-routes.ts +90 -23
  898. package/src/runtime/routes/content-source-routes.ts +5 -1
  899. package/src/runtime/routes/conversation-analysis-routes.ts +11 -1
  900. package/src/runtime/routes/conversation-attention-routes.ts +5 -0
  901. package/src/runtime/routes/conversation-cli-routes.ts +54 -7
  902. package/src/runtime/routes/conversation-compaction-routes.ts +292 -0
  903. package/src/runtime/routes/conversation-list-routes.ts +225 -9
  904. package/src/runtime/routes/conversation-management-routes.ts +96 -28
  905. package/src/runtime/routes/conversation-query-routes.ts +148 -51
  906. package/src/runtime/routes/conversation-routes.ts +259 -158
  907. package/src/runtime/routes/conversation-starter-routes.ts +22 -13
  908. package/src/runtime/routes/conversations-import-routes.ts +25 -7
  909. package/src/runtime/routes/credential-prompt-routes.ts +5 -0
  910. package/src/runtime/routes/credential-routes.ts +25 -6
  911. package/src/runtime/routes/debug-bash-routes.ts +5 -0
  912. package/src/runtime/routes/debug-routes.ts +11 -2
  913. package/src/runtime/routes/defer-routes.ts +13 -0
  914. package/src/runtime/routes/diagnostics-routes.ts +37 -46
  915. package/src/runtime/routes/disk-pressure-routes.ts +17 -31
  916. package/src/runtime/routes/document-comments-routes.ts +46 -27
  917. package/src/runtime/routes/documents-routes.ts +31 -11
  918. package/src/runtime/routes/domain-routes.ts +61 -28
  919. package/src/runtime/routes/email-routes.ts +33 -0
  920. package/src/runtime/routes/events-routes.ts +114 -9
  921. package/src/runtime/routes/filing-routes.ts +9 -4
  922. package/src/runtime/routes/gateway-log-routes.ts +5 -0
  923. package/src/runtime/routes/global-search-routes.ts +53 -50
  924. package/src/runtime/routes/group-routes.ts +32 -5
  925. package/src/runtime/routes/guardian-action-routes.ts +9 -0
  926. package/src/runtime/routes/guardian-approval-interception.ts +0 -31
  927. package/src/runtime/routes/heartbeat-routes.ts +25 -9
  928. package/src/runtime/routes/home-feed-routes.ts +149 -16
  929. package/src/runtime/routes/home-state-routes.ts +8 -40
  930. package/src/runtime/routes/host-app-control-routes.ts +5 -0
  931. package/src/runtime/routes/host-bash-routes.ts +5 -0
  932. package/src/runtime/routes/host-browser-routes.ts +13 -0
  933. package/src/runtime/routes/host-cu-routes.ts +5 -0
  934. package/src/runtime/routes/host-file-routes.ts +26 -6
  935. package/src/runtime/routes/host-transfer-routes.ts +13 -2
  936. package/src/runtime/routes/http-adapter.ts +1 -2
  937. package/src/runtime/routes/identity-intro-cache.ts +72 -16
  938. package/src/runtime/routes/identity-routes.ts +42 -11
  939. package/src/runtime/routes/image-generation-routes.ts +5 -0
  940. package/src/runtime/routes/inbound-message-handler.ts +15 -11
  941. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +524 -12
  942. package/src/runtime/routes/inbound-stages/background-dispatch.ts +72 -27
  943. package/src/runtime/routes/index.ts +2 -0
  944. package/src/runtime/routes/inference-profile-session-routes.ts +13 -3
  945. package/src/runtime/routes/inference-provider-connection-routes.ts +26 -31
  946. package/src/runtime/routes/inference-send-routes.ts +11 -11
  947. package/src/runtime/routes/integrations/a2a.ts +30 -7
  948. package/src/runtime/routes/integrations/slack/channel.ts +19 -3
  949. package/src/runtime/routes/integrations/slack/share.ts +9 -2
  950. package/src/runtime/routes/integrations/telegram.ts +28 -9
  951. package/src/runtime/routes/integrations/twilio.ts +35 -7
  952. package/src/runtime/routes/integrations/vercel.ts +18 -3
  953. package/src/runtime/routes/internal-oauth-routes.ts +5 -0
  954. package/src/runtime/routes/internal-twilio-routes.ts +13 -0
  955. package/src/runtime/routes/llm-call-sites-routes.ts +39 -4
  956. package/src/runtime/routes/llm-context-normalization.ts +7 -2
  957. package/src/runtime/routes/log-export-routes.ts +28 -10
  958. package/src/runtime/routes/mcp-auth-routes.ts +25 -0
  959. package/src/runtime/routes/memory-item-routes.ts +21 -10
  960. package/src/runtime/routes/memory-v2-routes.ts +90 -36
  961. package/src/runtime/routes/memory-v3-routes.ts +283 -259
  962. package/src/runtime/routes/migration-rollback-routes.ts +5 -1
  963. package/src/runtime/routes/migration-routes.ts +49 -13
  964. package/src/runtime/routes/notification-routes.ts +80 -2
  965. package/src/runtime/routes/oauth-apps.ts +33 -11
  966. package/src/runtime/routes/oauth-commands-routes.ts +43 -15
  967. package/src/runtime/routes/oauth-connect-routes.ts +9 -0
  968. package/src/runtime/routes/oauth-lifecycle-routes.ts +5 -1
  969. package/src/runtime/routes/oauth-providers.ts +35 -10
  970. package/src/runtime/routes/platform-routes.ts +21 -0
  971. package/src/runtime/routes/playground/__tests__/force-compact.test.ts +3 -2
  972. package/src/runtime/routes/playground/__tests__/inject-failures.test.ts +37 -16
  973. package/src/runtime/routes/playground/__tests__/reset-circuit.test.ts +7 -3
  974. package/src/runtime/routes/playground/__tests__/state.test.ts +10 -3
  975. package/src/runtime/routes/playground/force-compact.ts +1 -1
  976. package/src/runtime/routes/playground/helpers.ts +0 -1
  977. package/src/runtime/routes/playground/inject-failures.ts +13 -8
  978. package/src/runtime/routes/playground/reset-circuit.ts +14 -9
  979. package/src/runtime/routes/playground/seed-conversation.ts +1 -1
  980. package/src/runtime/routes/playground/seeded-conversations.ts +3 -3
  981. package/src/runtime/routes/playground/state.ts +4 -3
  982. package/src/runtime/routes/plugins-routes.ts +22 -19
  983. package/src/runtime/routes/profiler-routes.ts +17 -4
  984. package/src/runtime/routes/ps-routes.ts +5 -0
  985. package/src/runtime/routes/publish-routes.ts +13 -3
  986. package/src/runtime/routes/question-routes.ts +5 -0
  987. package/src/runtime/routes/recording-routes.ts +25 -12
  988. package/src/runtime/routes/rename-conversation-routes.ts +5 -0
  989. package/src/runtime/routes/sanity-routes.ts +9 -2
  990. package/src/runtime/routes/schedule-routes.ts +137 -47
  991. package/src/runtime/routes/secret-routes.ts +17 -4
  992. package/src/runtime/routes/sequence-routes.ts +33 -0
  993. package/src/runtime/routes/settings-routes.ts +65 -19
  994. package/src/runtime/routes/skills-routes.ts +133 -69
  995. package/src/runtime/routes/slack-channel-routes.ts +5 -0
  996. package/src/runtime/routes/stt-routes.ts +13 -6
  997. package/src/runtime/routes/subagents-routes.ts +24 -18
  998. package/src/runtime/routes/suggest-trust-rule-routes.ts +7 -2
  999. package/src/runtime/routes/surface-action-routes.ts +10 -38
  1000. package/src/runtime/routes/surface-content-routes.ts +21 -6
  1001. package/src/runtime/routes/surface-conversation-resolver.ts +65 -0
  1002. package/src/runtime/routes/task-routes.ts +37 -0
  1003. package/src/runtime/routes/telemetry-routes.ts +9 -0
  1004. package/src/runtime/routes/trace-event-routes.ts +42 -1
  1005. package/src/runtime/routes/trust-rules-routes.ts +5 -0
  1006. package/src/runtime/routes/tts-routes.ts +13 -6
  1007. package/src/runtime/routes/types.ts +17 -8
  1008. package/src/runtime/routes/ui-request-routes.ts +5 -0
  1009. package/src/runtime/routes/upgrade-broadcast-routes.ts +5 -0
  1010. package/src/runtime/routes/usage-routes.ts +71 -3
  1011. package/src/runtime/routes/user-routes-cli.ts +9 -0
  1012. package/src/runtime/routes/user-routes.ts +5 -1
  1013. package/src/runtime/routes/wake-conversation-routes.ts +5 -0
  1014. package/src/runtime/routes/watcher-routes.ts +21 -0
  1015. package/src/runtime/routes/webhook-routes.ts +9 -0
  1016. package/src/runtime/routes/wipe-conversation-routes.ts +8 -0
  1017. package/src/runtime/routes/work-items-routes.ts +47 -19
  1018. package/src/runtime/routes/workspace-commit-routes.ts +5 -0
  1019. package/src/runtime/routes/workspace-routes.test.ts +42 -0
  1020. package/src/runtime/routes/workspace-routes.ts +120 -9
  1021. package/src/runtime/services/__tests__/analyze-conversation.test.ts +4 -4
  1022. package/src/runtime/services/analyze-conversation.ts +3 -6
  1023. package/src/runtime/services/conversation-serializer.ts +24 -2
  1024. package/src/runtime/slack-dm-text-delivery.ts +177 -0
  1025. package/src/runtime/sync/resource-sync-events.ts +17 -3
  1026. package/src/runtime/sync/sync-publisher.ts +2 -2
  1027. package/src/runtime/tool-grant-request-helper.ts +1 -0
  1028. package/src/schedule/run-script.ts +28 -3
  1029. package/src/schedule/schedule-store.ts +16 -1
  1030. package/src/schedule/scheduler.ts +114 -16
  1031. package/src/security/__tests__/provider-key-env-fallback.test.ts +3 -3
  1032. package/src/security/encrypted-store.ts +7 -16
  1033. package/src/security/store-path-override.ts +61 -0
  1034. package/src/signals/user-message.ts +10 -16
  1035. package/src/skills/catalog-files.ts +4 -1
  1036. package/src/skills/clawhub-files.ts +2 -0
  1037. package/src/skills/skillssh-files.ts +2 -0
  1038. package/src/skills/validate-input.ts +177 -0
  1039. package/src/subagent/manager.ts +16 -19
  1040. package/src/subagent/types.ts +6 -0
  1041. package/src/tasks/tool-sanitizer.ts +2 -2
  1042. package/src/telemetry/types.ts +26 -0
  1043. package/src/telemetry/usage-telemetry-reporter.test.ts +138 -1
  1044. package/src/telemetry/usage-telemetry-reporter.ts +31 -0
  1045. package/src/tools/acp/spawn.test.ts +88 -38
  1046. package/src/tools/apps/definitions.ts +42 -24
  1047. package/src/tools/ask-question/ask-question-tool.test.ts +120 -105
  1048. package/src/tools/ask-question/ask-question-tool.ts +85 -90
  1049. package/src/tools/browser/__tests__/browser-execution-acquire.test.ts +2 -8
  1050. package/src/tools/computer-use/definitions.ts +295 -289
  1051. package/src/tools/credential-execution/make-authenticated-request.ts +56 -51
  1052. package/src/tools/credential-execution/manage-secure-command-tool.ts +2 -2
  1053. package/src/tools/credential-execution/run-authenticated-command.ts +82 -77
  1054. package/src/tools/credentials/vault.ts +112 -111
  1055. package/src/tools/document/document-tool.ts +131 -8
  1056. package/src/tools/execution-target.ts +3 -6
  1057. package/src/tools/execution-timeout.ts +3 -4
  1058. package/src/tools/executor.ts +18 -55
  1059. package/src/tools/filesystem/edit.ts +45 -42
  1060. package/src/tools/filesystem/list.ts +33 -30
  1061. package/src/tools/filesystem/read.ts +54 -35
  1062. package/src/tools/filesystem/write.ts +34 -31
  1063. package/src/tools/host-filesystem/edit.test.ts +1 -0
  1064. package/src/tools/host-filesystem/edit.ts +44 -42
  1065. package/src/tools/host-filesystem/read.test.ts +1 -0
  1066. package/src/tools/host-filesystem/read.ts +49 -35
  1067. package/src/tools/host-filesystem/transfer.test.ts +31 -6
  1068. package/src/tools/host-filesystem/transfer.ts +121 -108
  1069. package/src/tools/host-filesystem/write.test.ts +1 -0
  1070. package/src/tools/host-filesystem/write.ts +33 -31
  1071. package/src/tools/host-terminal/host-shell.ts +50 -48
  1072. package/src/tools/mcp/mcp-tool-factory.ts +0 -2
  1073. package/src/tools/memory/register.ts +23 -24
  1074. package/src/tools/network/__tests__/managed-search-proxy.test.ts +282 -0
  1075. package/src/tools/network/__tests__/web-search.test.ts +211 -3
  1076. package/src/tools/network/managed-search-proxy.ts +183 -0
  1077. package/src/tools/network/web-fetch.ts +49 -46
  1078. package/src/tools/network/web-search.ts +215 -57
  1079. package/src/tools/policy-context.ts +3 -1
  1080. package/src/tools/registry.ts +184 -118
  1081. package/src/tools/schedule/create.ts +12 -1
  1082. package/src/tools/schedule/update.ts +16 -0
  1083. package/src/tools/shared/filesystem/audio-read.ts +122 -0
  1084. package/src/tools/shared/filesystem/image-read.ts +1 -1
  1085. package/src/tools/skills/execute.ts +34 -31
  1086. package/src/tools/skills/load.ts +29 -23
  1087. package/src/tools/skills/skill-tool-factory.ts +17 -36
  1088. package/src/tools/subagent/notify-parent.ts +35 -32
  1089. package/src/tools/subagent/spawn.ts +3 -0
  1090. package/src/tools/system/avatar-generator.ts +13 -22
  1091. package/src/tools/system/request-permission.ts +30 -27
  1092. package/src/tools/terminal/shell.ts +190 -61
  1093. package/src/tools/tool-approval-handler.ts +10 -4
  1094. package/src/tools/tool-defaults.ts +20 -9
  1095. package/src/tools/tool-manifest.ts +4 -4
  1096. package/src/tools/tool-name-aliases.ts +72 -14
  1097. package/src/tools/types.ts +86 -33
  1098. package/src/tools/ui-surface/definitions.ts +166 -94
  1099. package/src/types/onboarding-context.ts +6 -0
  1100. package/src/usage/attribution.ts +32 -1
  1101. package/src/usage/types.ts +10 -0
  1102. package/src/util/browser.ts +7 -2
  1103. package/src/util/errors.ts +2 -2
  1104. package/src/util/map-limit.ts +27 -0
  1105. package/src/util/platform.ts +15 -12
  1106. package/src/work-items/work-item-runner.ts +7 -2
  1107. package/src/workspace/migrations/028-recover-conversations-from-disk-view.ts +7 -20
  1108. package/src/workspace/migrations/090-memory-router-cost-optimized-profile.ts +109 -0
  1109. package/src/workspace/migrations/091-retighten-migration-onboarding-thread.ts +41 -0
  1110. package/src/workspace/migrations/092-backfill-v3-leaves.ts +169 -0
  1111. package/src/workspace/migrations/093-backfill-leaf-ids.ts +144 -0
  1112. package/src/workspace/migrations/094-seed-avatar-manifest.ts +155 -0
  1113. package/src/workspace/migrations/__tests__/094-seed-avatar-manifest.test.ts +136 -0
  1114. package/src/workspace/migrations/__tests__/backfill-leaf-ids.test.ts +175 -0
  1115. package/src/workspace/migrations/__tests__/backfill-v3-leaves.test.ts +124 -0
  1116. package/src/workspace/migrations/registry.ts +10 -0
  1117. package/src/workspace/provider-commit-message-generator.ts +15 -17
  1118. package/tsconfig.json +4 -1
  1119. package/src/__tests__/history-repair-pipeline.test.ts +0 -396
  1120. package/src/cli/commands/__tests__/memory-v3-render.test.ts +0 -340
  1121. package/src/cli/commands/memory-v3-render.ts +0 -344
  1122. package/src/daemon/message-types/disk-pressure.ts +0 -9
  1123. package/src/email/feature-gate.ts +0 -23
  1124. package/src/memory/v3/__tests__/coactivation-store.test.ts +0 -422
  1125. package/src/memory/v3/__tests__/consolidation-job.test.ts +0 -468
  1126. package/src/memory/v3/__tests__/edge-learning-job.test.ts +0 -324
  1127. package/src/memory/v3/__tests__/edges.test.ts +0 -563
  1128. package/src/memory/v3/__tests__/filter.test.ts +0 -512
  1129. package/src/memory/v3/__tests__/gate.test.ts +0 -574
  1130. package/src/memory/v3/__tests__/index-composition.test.ts +0 -233
  1131. package/src/memory/v3/__tests__/loop.test.ts +0 -530
  1132. package/src/memory/v3/__tests__/retriever.test.ts +0 -226
  1133. package/src/memory/v3/__tests__/scouts.test.ts +0 -440
  1134. package/src/memory/v3/__tests__/shadow-middleware.test.ts +0 -312
  1135. package/src/memory/v3/__tests__/system-prompts.test.ts +0 -154
  1136. package/src/memory/v3/__tests__/traversal.test.ts +0 -469
  1137. package/src/memory/v3/__tests__/tree-index.test.ts +0 -280
  1138. package/src/memory/v3/__tests__/tree-store.test.ts +0 -529
  1139. package/src/memory/v3/__tests__/tree-walk.test.ts +0 -707
  1140. package/src/memory/v3/__tests__/validate.test.ts +0 -245
  1141. package/src/memory/v3/auto-edges.ts +0 -223
  1142. package/src/memory/v3/coactivation-store.ts +0 -124
  1143. package/src/memory/v3/consolidation-job.ts +0 -323
  1144. package/src/memory/v3/edge-learning-job.ts +0 -160
  1145. package/src/memory/v3/edges.ts +0 -249
  1146. package/src/memory/v3/filter.ts +0 -281
  1147. package/src/memory/v3/gate.ts +0 -334
  1148. package/src/memory/v3/index-composition.ts +0 -113
  1149. package/src/memory/v3/llm-capture.ts +0 -46
  1150. package/src/memory/v3/loop.ts +0 -382
  1151. package/src/memory/v3/maintenance.ts +0 -144
  1152. package/src/memory/v3/prompt-context.ts +0 -33
  1153. package/src/memory/v3/prompts/consolidation.ts +0 -458
  1154. package/src/memory/v3/prompts/system-prompts.ts +0 -196
  1155. package/src/memory/v3/retriever.ts +0 -33
  1156. package/src/memory/v3/scouts.ts +0 -420
  1157. package/src/memory/v3/shadow-middleware.ts +0 -305
  1158. package/src/memory/v3/traversal.ts +0 -206
  1159. package/src/memory/v3/tree-index.ts +0 -237
  1160. package/src/memory/v3/tree-store.ts +0 -394
  1161. package/src/memory/v3/tree-walk.ts +0 -351
  1162. package/src/memory/v3/validate.ts +0 -300
  1163. package/src/plugins/defaults/circuit-breaker.ts +0 -141
  1164. package/src/plugins/defaults/compaction.ts +0 -141
  1165. package/src/plugins/defaults/empty-response.ts +0 -124
  1166. package/src/plugins/defaults/history-repair.ts +0 -83
  1167. package/src/plugins/defaults/persistence.ts +0 -127
  1168. package/src/plugins/defaults/title-generate.ts +0 -90
  1169. package/src/plugins/defaults/token-estimate.ts +0 -101
  1170. package/src/plugins/defaults/tool-error.ts +0 -119
  1171. package/src/plugins/defaults/tool-result-truncate.ts +0 -84
@@ -1,10 +1,13 @@
1
1
  import { rmSync, writeFileSync } from "node:fs";
2
2
  import { afterAll, beforeEach, describe, expect, mock, test } from "bun:test";
3
3
 
4
+ import { CompactionCircuit } from "../agent/compaction-circuit.js";
4
5
  import type {
5
6
  AgentEvent,
7
+ AgentLoopRunResult,
6
8
  CheckpointDecision,
7
9
  CheckpointInfo,
10
+ ExitReason,
8
11
  } from "../agent/loop.js";
9
12
  import type { ServerMessage } from "../daemon/message-protocol.js";
10
13
  import type { Message, ProviderResponse } from "../providers/types.js";
@@ -175,7 +178,7 @@ mock.module("../memory/conversation-crud.js", () => ({
175
178
  _convId: string,
176
179
  role: string,
177
180
  content: string,
178
- metadata?: Record<string, unknown>,
181
+ options?: { metadata?: Record<string, unknown> },
179
182
  ) => {
180
183
  // Simulate a persist failure for tests that need to exercise the
181
184
  // tail-persist-failed path in drainBatch. Triggered by matching any
@@ -186,13 +189,20 @@ mock.module("../memory/conversation-crud.js", () => ({
186
189
  }
187
190
  }
188
191
  const id = `msg-${Date.now()}-${capturedAddMessages.length}`;
189
- capturedAddMessages.push({ id, role, content, metadata });
192
+ capturedAddMessages.push({
193
+ id,
194
+ role,
195
+ content,
196
+ metadata: options?.metadata,
197
+ });
190
198
  return { id };
191
199
  },
192
200
  updateConversationUsage: () => {},
193
201
  updateConversationTitle: () => {},
194
202
  getMessageById: () => null,
195
203
  getLastUserTimestampBefore: () => 0,
204
+ reserveMessage: mock(async () => ({ id: "msg-reserve" })),
205
+ updateMessageContent: mock(() => {}),
196
206
  }));
197
207
 
198
208
  mock.module("../memory/conversation-queries.js", () => ({
@@ -315,16 +325,24 @@ interface PendingRun {
315
325
  resolve: (history: Message[]) => void;
316
326
  reject: (err: Error) => void;
317
327
  messages: Message[];
318
- onEvent: (event: AgentEvent) => void;
328
+ onEvent: (event: AgentEvent) => void | Promise<void>;
319
329
  onCheckpoint?: (
320
330
  checkpoint: CheckpointInfo,
321
331
  ) => CheckpointDecision | Promise<CheckpointDecision>;
332
+ /**
333
+ * Pause-reason recorded from the most recent `onCheckpoint` call, mirroring
334
+ * how the production loop carries it back via {@link AgentLoopRunResult}.
335
+ * `resolve(history)` packages this into the run result so the orchestrator
336
+ * derives its handoff bookkeeping the same way it does against the real loop.
337
+ */
338
+ exitReason: ExitReason | null;
322
339
  }
323
340
 
324
341
  let pendingRuns: PendingRun[] = [];
325
342
 
326
343
  mock.module("../agent/loop.js", () => ({
327
344
  AgentLoop: class {
345
+ compactionCircuit = new CompactionCircuit("test-conv");
328
346
  constructor() {}
329
347
  getToolTokenBudget() {
330
348
  return 0;
@@ -337,15 +355,33 @@ mock.module("../agent/loop.js", () => ({
337
355
  }
338
356
  async run(
339
357
  messages: Message[],
340
- onEvent: (event: AgentEvent) => void,
341
- _signal?: AbortSignal,
342
- _requestId?: string,
343
- onCheckpoint?: (
344
- checkpoint: CheckpointInfo,
345
- ) => CheckpointDecision | Promise<CheckpointDecision>,
346
- ): Promise<Message[]> {
347
- return new Promise<Message[]>((resolve, reject) => {
348
- pendingRuns.push({ resolve, reject, messages, onEvent, onCheckpoint });
358
+ onEvent: (event: AgentEvent) => void | Promise<void>,
359
+ options?: {
360
+ onCheckpoint?: (
361
+ checkpoint: CheckpointInfo,
362
+ ) => CheckpointDecision | Promise<CheckpointDecision>;
363
+ },
364
+ ): Promise<AgentLoopRunResult> {
365
+ return new Promise<AgentLoopRunResult>((resolveResult, reject) => {
366
+ const pending: PendingRun = {
367
+ resolve: (history: Message[]) =>
368
+ resolveResult({
369
+ history,
370
+ exitReason: pending.exitReason,
371
+ }),
372
+ reject,
373
+ messages,
374
+ onEvent,
375
+ exitReason: null,
376
+ onCheckpoint: options?.onCheckpoint
377
+ ? async (checkpoint) => {
378
+ const decision = await options.onCheckpoint!(checkpoint);
379
+ pending.exitReason = decision === "continue" ? null : decision;
380
+ return decision;
381
+ }
382
+ : undefined,
383
+ };
384
+ pendingRuns.push(pending);
349
385
  });
350
386
  }
351
387
  },
@@ -409,9 +445,9 @@ function makeConversation(
409
445
  "conv-1",
410
446
  provider,
411
447
  "system prompt",
412
- 4096,
413
448
  sendToClient ?? (() => {}),
414
449
  "/tmp",
450
+ { maxTokens: 4096 },
415
451
  );
416
452
  const conversationWithWorkspaceDeps =
417
453
  conversationObj as ConversationWithWorkspaceDeps;
@@ -471,7 +507,7 @@ async function waitForCondition(
471
507
  * that `runAgentLoop` expects (usage + message_complete) so the conversation
472
508
  * cleanly transitions out of its processing state.
473
509
  */
474
- function resolveRun(index: number) {
510
+ async function resolveRun(index: number) {
475
511
  const run = pendingRuns[index];
476
512
  if (!run) throw new Error(`No pending run at index ${index}`);
477
513
  // Emit the events runAgentLoop expects
@@ -479,14 +515,17 @@ function resolveRun(index: number) {
479
515
  role: "assistant",
480
516
  content: [{ type: "text", text: `reply-${index}` }],
481
517
  };
482
- run.onEvent({
518
+ // Prime the assistant row anchor — production code emits this from
519
+ // `AgentLoop.run` just before `provider.sendMessage`.
520
+ await run.onEvent({ type: "llm_call_started" });
521
+ await run.onEvent({
483
522
  type: "usage",
484
523
  inputTokens: 10,
485
524
  outputTokens: 5,
486
525
  model: "mock",
487
526
  providerDurationMs: 100,
488
527
  });
489
- run.onEvent({ type: "message_complete", message: assistantMsg });
528
+ await run.onEvent({ type: "message_complete", message: assistantMsg });
490
529
  // Return updated history with the assistant message appended
491
530
  run.resolve([...run.messages, assistantMsg]);
492
531
  }
@@ -519,12 +558,12 @@ describe("Conversation message queue", () => {
519
558
  const events2: ServerMessage[] = [];
520
559
 
521
560
  // Start first message — this will block on AgentLoop.run
522
- const p1 = conversation.processMessage(
523
- "msg-1",
524
- [],
525
- (e) => events1.push(e),
526
- "req-1",
527
- );
561
+ const p1 = conversation.processMessage({
562
+ content: "msg-1",
563
+ attachments: [],
564
+ onEvent: (e) => events1.push(e),
565
+ requestId: "req-1",
566
+ });
528
567
 
529
568
  // Wait for the first AgentLoop.run to be registered
530
569
  await waitForPendingRun(1);
@@ -533,18 +572,17 @@ describe("Conversation message queue", () => {
533
572
  expect(conversation.isProcessing()).toBe(true);
534
573
 
535
574
  // Enqueue second message — should NOT throw
536
- const result = conversation.enqueueMessage(
537
- "msg-2",
538
- [],
539
- (e) => events2.push(e),
540
- "req-2",
541
- );
575
+ const result = conversation.enqueueMessage({
576
+ content: "msg-2",
577
+ onEvent: (e) => events2.push(e),
578
+ requestId: "req-2",
579
+ });
542
580
  expect(result.queued).toBe(true);
543
581
  expect(result.requestId).toBe("req-2");
544
582
  expect(conversation.getQueueDepth()).toBe(1);
545
583
 
546
584
  // Complete the first message
547
- resolveRun(0);
585
+ await resolveRun(0);
548
586
  await p1;
549
587
 
550
588
  // After the first run resolves, the queue drains and triggers a second run.
@@ -557,7 +595,7 @@ describe("Conversation message queue", () => {
557
595
  expect(pendingRuns.length).toBe(2);
558
596
 
559
597
  // Complete the second run
560
- resolveRun(1);
598
+ await resolveRun(1);
561
599
  await new Promise((r) => setTimeout(r, 10));
562
600
  });
563
601
 
@@ -570,21 +608,29 @@ describe("Conversation message queue", () => {
570
608
  const events3: ServerMessage[] = [];
571
609
 
572
610
  // Start first message
573
- const p1 = conversation.processMessage(
574
- "msg-1",
575
- [],
576
- (e) => events1.push(e),
577
- "req-1",
578
- );
611
+ const p1 = conversation.processMessage({
612
+ content: "msg-1",
613
+ attachments: [],
614
+ onEvent: (e) => events1.push(e),
615
+ requestId: "req-1",
616
+ });
579
617
  await waitForPendingRun(1);
580
618
 
581
619
  // Enqueue two more sibling passthrough messages
582
- conversation.enqueueMessage("msg-2", [], (e) => events2.push(e), "req-2");
583
- conversation.enqueueMessage("msg-3", [], (e) => events3.push(e), "req-3");
620
+ conversation.enqueueMessage({
621
+ content: "msg-2",
622
+ onEvent: (e) => events2.push(e),
623
+ requestId: "req-2",
624
+ });
625
+ conversation.enqueueMessage({
626
+ content: "msg-3",
627
+ onEvent: (e) => events3.push(e),
628
+ requestId: "req-3",
629
+ });
584
630
  expect(conversation.getQueueDepth()).toBe(2);
585
631
 
586
632
  // Complete run 0 → drain pulls msg-2 and msg-3 into ONE batched run.
587
- resolveRun(0);
633
+ await resolveRun(0);
588
634
  await p1;
589
635
  await waitForPendingRun(2);
590
636
 
@@ -623,7 +669,7 @@ describe("Conversation message queue", () => {
623
669
  expect(combinedUserText).toContain("msg-3");
624
670
 
625
671
  // Resolve the batched run; message_complete must fan out to both clients.
626
- resolveRun(1);
672
+ await resolveRun(1);
627
673
  await new Promise((r) => setTimeout(r, 10));
628
674
 
629
675
  expect(events2.some((e) => e.type === "message_complete")).toBe(true);
@@ -637,20 +683,23 @@ describe("Conversation message queue", () => {
637
683
  const events2: ServerMessage[] = [];
638
684
 
639
685
  // Start first message
640
- const p1 = conversation.processMessage("msg-1", [], () => {}, "req-1");
686
+ const p1 = conversation.processMessage({
687
+ content: "msg-1",
688
+ attachments: [],
689
+ requestId: "req-1",
690
+ });
641
691
  await waitForPendingRun(1);
642
692
 
643
693
  // Enqueue second — simulating what handleUserMessage does
644
- const result = conversation.enqueueMessage(
645
- "msg-2",
646
- [],
647
- (e) => events2.push(e),
648
- "req-2",
649
- );
694
+ const result = conversation.enqueueMessage({
695
+ content: "msg-2",
696
+ onEvent: (e) => events2.push(e),
697
+ requestId: "req-2",
698
+ });
650
699
  expect(result.queued).toBe(true);
651
700
 
652
701
  // Complete first
653
- resolveRun(0);
702
+ await resolveRun(0);
654
703
  await p1;
655
704
  await waitForPendingRun(2);
656
705
 
@@ -664,7 +713,7 @@ describe("Conversation message queue", () => {
664
713
  });
665
714
 
666
715
  // Complete second run so the conversation finishes cleanly
667
- resolveRun(1);
716
+ await resolveRun(1);
668
717
  await new Promise((r) => setTimeout(r, 10));
669
718
  });
670
719
 
@@ -676,12 +725,24 @@ describe("Conversation message queue", () => {
676
725
  const events3: ServerMessage[] = [];
677
726
 
678
727
  // Start first message
679
- conversation.processMessage("msg-1", [], () => {}, "req-1");
728
+ conversation.processMessage({
729
+ content: "msg-1",
730
+ attachments: [],
731
+ requestId: "req-1",
732
+ });
680
733
  await waitForPendingRun(1);
681
734
 
682
735
  // Enqueue two more
683
- conversation.enqueueMessage("msg-2", [], (e) => events2.push(e), "req-2");
684
- conversation.enqueueMessage("msg-3", [], (e) => events3.push(e), "req-3");
736
+ conversation.enqueueMessage({
737
+ content: "msg-2",
738
+ onEvent: (e) => events2.push(e),
739
+ requestId: "req-2",
740
+ });
741
+ conversation.enqueueMessage({
742
+ content: "msg-3",
743
+ onEvent: (e) => events3.push(e),
744
+ requestId: "req-3",
745
+ });
685
746
  expect(conversation.getQueueDepth()).toBe(2);
686
747
 
687
748
  // Abort
@@ -727,12 +788,12 @@ describe("Conversation message queue", () => {
727
788
  const events: ServerMessage[] = [];
728
789
 
729
790
  // Start a message — blocks on AgentLoop.run
730
- const p1 = conversation.processMessage(
731
- "msg-1",
732
- [],
733
- (e) => events.push(e),
734
- "req-1",
735
- );
791
+ const p1 = conversation.processMessage({
792
+ content: "msg-1",
793
+ attachments: [],
794
+ onEvent: (e) => events.push(e),
795
+ requestId: "req-1",
796
+ });
736
797
  await waitForPendingRun(1);
737
798
 
738
799
  // Reject the AgentLoop.run() with a provider error to trigger the
@@ -754,23 +815,27 @@ describe("Conversation message queue", () => {
754
815
  await conversation.loadFromDb();
755
816
 
756
817
  // Start first message
757
- const p1 = conversation.processMessage("msg-1", [], () => {}, "req-1");
818
+ const p1 = conversation.processMessage({
819
+ content: "msg-1",
820
+ attachments: [],
821
+ requestId: "req-1",
822
+ });
758
823
  await waitForPendingRun(1);
759
824
 
760
825
  expect(conversation.getQueueDepth()).toBe(0);
761
826
 
762
- conversation.enqueueMessage("msg-2", [], () => {}, "req-2");
827
+ conversation.enqueueMessage({ content: "msg-2", requestId: "req-2" });
763
828
  expect(conversation.getQueueDepth()).toBe(1);
764
829
 
765
- conversation.enqueueMessage("msg-3", [], () => {}, "req-3");
830
+ conversation.enqueueMessage({ content: "msg-3", requestId: "req-3" });
766
831
  expect(conversation.getQueueDepth()).toBe(2);
767
832
 
768
- conversation.enqueueMessage("msg-4", [], () => {}, "req-4");
833
+ conversation.enqueueMessage({ content: "msg-4", requestId: "req-4" });
769
834
  expect(conversation.getQueueDepth()).toBe(3);
770
835
 
771
836
  // Complete first → drain pulls all three same-interface passthroughs
772
837
  // into a single batched run (depth → 0, runs → 2 total).
773
- resolveRun(0);
838
+ await resolveRun(0);
774
839
  await p1;
775
840
  await waitForPendingRun(2);
776
841
 
@@ -778,7 +843,7 @@ describe("Conversation message queue", () => {
778
843
  expect(pendingRuns.length).toBe(2);
779
844
 
780
845
  // Complete the batched run; conversation finishes cleanly.
781
- resolveRun(1);
846
+ await resolveRun(1);
782
847
  await new Promise((r) => setTimeout(r, 10));
783
848
  });
784
849
 
@@ -791,23 +856,31 @@ describe("Conversation message queue", () => {
791
856
  const events3: ServerMessage[] = [];
792
857
 
793
858
  // Start first message — blocks on AgentLoop.run
794
- const p1 = conversation.processMessage(
795
- "msg-1",
796
- [],
797
- (e) => events1.push(e),
798
- "req-1",
799
- );
859
+ const p1 = conversation.processMessage({
860
+ content: "msg-1",
861
+ attachments: [],
862
+ onEvent: (e) => events1.push(e),
863
+ requestId: "req-1",
864
+ });
800
865
  await waitForPendingRun(1);
801
866
 
802
867
  // Enqueue a message with empty content (will fail persistUserMessage)
803
- conversation.enqueueMessage("", [], (e) => events2.push(e), "req-2");
868
+ conversation.enqueueMessage({
869
+ content: "",
870
+ onEvent: (e) => events2.push(e),
871
+ requestId: "req-2",
872
+ });
804
873
  // Enqueue a valid message after the bad one
805
- conversation.enqueueMessage("msg-3", [], (e) => events3.push(e), "req-3");
874
+ conversation.enqueueMessage({
875
+ content: "msg-3",
876
+ onEvent: (e) => events3.push(e),
877
+ requestId: "req-3",
878
+ });
806
879
  expect(conversation.getQueueDepth()).toBe(2);
807
880
 
808
881
  // Complete first message — triggers drain. The empty message should fail
809
882
  // to persist, but the drain should continue to msg-3.
810
- resolveRun(0);
883
+ await resolveRun(0);
811
884
  await p1;
812
885
 
813
886
  // msg-3 should have been dequeued and started a new AgentLoop.run
@@ -824,7 +897,7 @@ describe("Conversation message queue", () => {
824
897
  expect(events3.some((e) => e.type === "message_dequeued")).toBe(true);
825
898
 
826
899
  // Complete the third message's run
827
- resolveRun(1);
900
+ await resolveRun(1);
828
901
  await new Promise((r) => setTimeout(r, 10));
829
902
 
830
903
  // msg-3 should have completed successfully
@@ -851,7 +924,11 @@ describe("Batched drain", () => {
851
924
  const events5: ServerMessage[] = [];
852
925
 
853
926
  // Start in-flight message (msg-1)
854
- const p1 = conversation.processMessage("msg-1", [], () => {}, "req-1");
927
+ const p1 = conversation.processMessage({
928
+ content: "msg-1",
929
+ attachments: [],
930
+ requestId: "req-1",
931
+ });
855
932
  await waitForPendingRun(1);
856
933
 
857
934
  // Enqueue 4 messages with interfaces [macos, macos, cli, macos].
@@ -860,46 +937,34 @@ describe("Batched drain", () => {
860
937
  userMessageInterface: iface,
861
938
  assistantMessageInterface: iface,
862
939
  });
863
- conversation.enqueueMessage(
864
- "msg-2",
865
- [],
866
- (e) => events2.push(e),
867
- "req-2",
868
- undefined,
869
- undefined,
870
- meta("macos"),
871
- );
872
- conversation.enqueueMessage(
873
- "msg-3",
874
- [],
875
- (e) => events3.push(e),
876
- "req-3",
877
- undefined,
878
- undefined,
879
- meta("macos"),
880
- );
881
- conversation.enqueueMessage(
882
- "msg-4",
883
- [],
884
- (e) => events4.push(e),
885
- "req-4",
886
- undefined,
887
- undefined,
888
- meta("cli"),
889
- );
890
- conversation.enqueueMessage(
891
- "msg-5",
892
- [],
893
- (e) => events5.push(e),
894
- "req-5",
895
- undefined,
896
- undefined,
897
- meta("macos"),
898
- );
940
+ conversation.enqueueMessage({
941
+ content: "msg-2",
942
+ onEvent: (e) => events2.push(e),
943
+ requestId: "req-2",
944
+ metadata: meta("macos"),
945
+ });
946
+ conversation.enqueueMessage({
947
+ content: "msg-3",
948
+ onEvent: (e) => events3.push(e),
949
+ requestId: "req-3",
950
+ metadata: meta("macos"),
951
+ });
952
+ conversation.enqueueMessage({
953
+ content: "msg-4",
954
+ onEvent: (e) => events4.push(e),
955
+ requestId: "req-4",
956
+ metadata: meta("cli"),
957
+ });
958
+ conversation.enqueueMessage({
959
+ content: "msg-5",
960
+ onEvent: (e) => events5.push(e),
961
+ requestId: "req-5",
962
+ metadata: meta("macos"),
963
+ });
899
964
  expect(conversation.getQueueDepth()).toBe(4);
900
965
 
901
966
  // Resolve msg-1 → batched run pulls macos msg-2 + msg-3.
902
- resolveRun(0);
967
+ await resolveRun(0);
903
968
  await p1;
904
969
  await waitForPendingRun(2);
905
970
 
@@ -927,7 +992,7 @@ describe("Batched drain", () => {
927
992
  );
928
993
 
929
994
  // Resolve the batched run → drain pulls the cli single-message run.
930
- resolveRun(1);
995
+ await resolveRun(1);
931
996
  await waitForPendingRun(3);
932
997
 
933
998
  // cli run contains msg-4 as a single-message run.
@@ -942,7 +1007,7 @@ describe("Batched drain", () => {
942
1007
  );
943
1008
 
944
1009
  // Resolve the cli run → drain pulls the final macos single-message run.
945
- resolveRun(2);
1010
+ await resolveRun(2);
946
1011
  await waitForPendingRun(4);
947
1012
  const finalHistory = pendingRuns[3].messages;
948
1013
  const finalUserText = finalHistory
@@ -957,7 +1022,7 @@ describe("Batched drain", () => {
957
1022
  // Four total runs: msg-1, batched [msg-2, msg-3], msg-4, msg-5.
958
1023
  expect(pendingRuns.length).toBe(4);
959
1024
 
960
- resolveRun(3);
1025
+ await resolveRun(3);
961
1026
  await new Promise((r) => setTimeout(r, 10));
962
1027
  });
963
1028
 
@@ -970,36 +1035,37 @@ describe("Batched drain", () => {
970
1035
  const eventsWorld: ServerMessage[] = [];
971
1036
 
972
1037
  // Start in-flight message
973
- const p1 = conversation.processMessage("msg-1", [], () => {}, "req-1");
1038
+ const p1 = conversation.processMessage({
1039
+ content: "msg-1",
1040
+ attachments: [],
1041
+ requestId: "req-1",
1042
+ });
974
1043
  await waitForPendingRun(1);
975
1044
 
976
1045
  // Enqueue ["hello", "/compact", "world"]. /compact resolves to a non-
977
1046
  // passthrough slash, so the batch builder stops at "hello" (length 1),
978
1047
  // then /compact takes the single-message /compact short-circuit path
979
1048
  // (no new runAgentLoop invocation), then "world" drains as its own run.
980
- conversation.enqueueMessage(
981
- "hello",
982
- [],
983
- (e) => eventsHello.push(e),
984
- "req-hello",
985
- );
986
- conversation.enqueueMessage(
987
- "/compact",
988
- [],
989
- (e) => eventsSlash.push(e),
990
- "req-slash",
991
- );
992
- conversation.enqueueMessage(
993
- "world",
994
- [],
995
- (e) => eventsWorld.push(e),
996
- "req-world",
997
- );
1049
+ conversation.enqueueMessage({
1050
+ content: "hello",
1051
+ onEvent: (e) => eventsHello.push(e),
1052
+ requestId: "req-hello",
1053
+ });
1054
+ conversation.enqueueMessage({
1055
+ content: "/compact",
1056
+ onEvent: (e) => eventsSlash.push(e),
1057
+ requestId: "req-slash",
1058
+ });
1059
+ conversation.enqueueMessage({
1060
+ content: "world",
1061
+ onEvent: (e) => eventsWorld.push(e),
1062
+ requestId: "req-world",
1063
+ });
998
1064
  expect(conversation.getQueueDepth()).toBe(3);
999
1065
 
1000
1066
  // Resolve msg-1 → drain pulls "hello" as its own run (batch stops at
1001
1067
  // /compact boundary).
1002
- resolveRun(0);
1068
+ await resolveRun(0);
1003
1069
  await p1;
1004
1070
  await waitForPendingRun(2);
1005
1071
 
@@ -1010,7 +1076,7 @@ describe("Batched drain", () => {
1010
1076
 
1011
1077
  // Resolve "hello" → drain pops /compact via the builder-rejected path,
1012
1078
  // runs its short-circuit (no new runAgentLoop), then drains "world".
1013
- resolveRun(1);
1079
+ await resolveRun(1);
1014
1080
  await waitForPendingRun(3);
1015
1081
 
1016
1082
  // /compact should have emitted its own message_complete via the short-
@@ -1019,7 +1085,7 @@ describe("Batched drain", () => {
1019
1085
  expect(eventsWorld.some((e) => e.type === "message_dequeued")).toBe(true);
1020
1086
  expect(pendingRuns.length).toBe(3);
1021
1087
 
1022
- resolveRun(2);
1088
+ await resolveRun(2);
1023
1089
  await new Promise((r) => setTimeout(r, 10));
1024
1090
  });
1025
1091
 
@@ -1042,7 +1108,11 @@ describe("Batched drain", () => {
1042
1108
  const eventsPlainB: ServerMessage[] = [];
1043
1109
 
1044
1110
  // Start in-flight message
1045
- const p1 = conversation.processMessage("msg-1", [], () => {}, "req-1");
1111
+ const p1 = conversation.processMessage({
1112
+ content: "msg-1",
1113
+ attachments: [],
1114
+ requestId: "req-1",
1115
+ });
1046
1116
  await waitForPendingRun(1);
1047
1117
 
1048
1118
  // Enqueue ["plain-a", "/status", "plain-b"]. /status resolves to a non-
@@ -1051,29 +1121,26 @@ describe("Batched drain", () => {
1051
1121
  // unknown-slash short-circuit path (no new runAgentLoop invocation — it
1052
1122
  // emits assistant_text_delta + message_complete inline), then "plain-b"
1053
1123
  // drains as its own run.
1054
- conversation.enqueueMessage(
1055
- "plain-a",
1056
- [],
1057
- (e) => eventsPlainA.push(e),
1058
- "req-plain-a",
1059
- );
1060
- conversation.enqueueMessage(
1061
- "/status",
1062
- [],
1063
- (e) => eventsSlash.push(e),
1064
- "req-slash",
1065
- );
1066
- conversation.enqueueMessage(
1067
- "plain-b",
1068
- [],
1069
- (e) => eventsPlainB.push(e),
1070
- "req-plain-b",
1071
- );
1124
+ conversation.enqueueMessage({
1125
+ content: "plain-a",
1126
+ onEvent: (e) => eventsPlainA.push(e),
1127
+ requestId: "req-plain-a",
1128
+ });
1129
+ conversation.enqueueMessage({
1130
+ content: "/status",
1131
+ onEvent: (e) => eventsSlash.push(e),
1132
+ requestId: "req-slash",
1133
+ });
1134
+ conversation.enqueueMessage({
1135
+ content: "plain-b",
1136
+ onEvent: (e) => eventsPlainB.push(e),
1137
+ requestId: "req-plain-b",
1138
+ });
1072
1139
  expect(conversation.getQueueDepth()).toBe(3);
1073
1140
 
1074
1141
  // Resolve msg-1 → drain pulls "plain-a" as its own run (batch stops at
1075
1142
  // the /status boundary).
1076
- resolveRun(0);
1143
+ await resolveRun(0);
1077
1144
  await p1;
1078
1145
  await waitForPendingRun(2);
1079
1146
 
@@ -1086,7 +1153,7 @@ describe("Batched drain", () => {
1086
1153
  // runs its unknown-slash short-circuit (no new runAgentLoop, emits
1087
1154
  // assistant_text_delta + message_complete inline), then drains "plain-b"
1088
1155
  // as its own run.
1089
- resolveRun(1);
1156
+ await resolveRun(1);
1090
1157
  await waitForPendingRun(3);
1091
1158
 
1092
1159
  // /status should have emitted its own assistant_text_delta + message_complete
@@ -1100,7 +1167,7 @@ describe("Batched drain", () => {
1100
1167
  // without a runAgentLoop invocation.
1101
1168
  expect(pendingRuns.length).toBe(3);
1102
1169
 
1103
- resolveRun(2);
1170
+ await resolveRun(2);
1104
1171
  await new Promise((r) => setTimeout(r, 10));
1105
1172
  });
1106
1173
 
@@ -1110,7 +1177,11 @@ describe("Batched drain", () => {
1110
1177
  await conversation.loadFromDb();
1111
1178
 
1112
1179
  // Start in-flight message
1113
- const p1 = conversation.processMessage("msg-1", [], () => {}, "req-1");
1180
+ const p1 = conversation.processMessage({
1181
+ content: "msg-1",
1182
+ attachments: [],
1183
+ requestId: "req-1",
1184
+ });
1114
1185
  await waitForPendingRun(1);
1115
1186
 
1116
1187
  // Two sibling messages, each with a distinct image attachment.
@@ -1132,11 +1203,19 @@ describe("Batched drain", () => {
1132
1203
  filePath: "/tmp/b.png",
1133
1204
  },
1134
1205
  ];
1135
- conversation.enqueueMessage("with-A", attachA, () => {}, "req-A");
1136
- conversation.enqueueMessage("with-B", attachB, () => {}, "req-B");
1206
+ conversation.enqueueMessage({
1207
+ content: "with-A",
1208
+ attachments: attachA,
1209
+ requestId: "req-A",
1210
+ });
1211
+ conversation.enqueueMessage({
1212
+ content: "with-B",
1213
+ attachments: attachB,
1214
+ requestId: "req-B",
1215
+ });
1137
1216
  expect(conversation.getQueueDepth()).toBe(2);
1138
1217
 
1139
- resolveRun(0);
1218
+ await resolveRun(0);
1140
1219
  await p1;
1141
1220
  await waitForPendingRun(2);
1142
1221
 
@@ -1170,7 +1249,7 @@ describe("Batched drain", () => {
1170
1249
  expect(allText).toContain("a.png");
1171
1250
  expect(allText).toContain("b.png");
1172
1251
 
1173
- resolveRun(1);
1252
+ await resolveRun(1);
1174
1253
  await new Promise((r) => setTimeout(r, 10));
1175
1254
  });
1176
1255
 
@@ -1185,34 +1264,33 @@ describe("Batched drain", () => {
1185
1264
  new MessageQueue(budget);
1186
1265
 
1187
1266
  // Start in-flight so subsequent enqueues are queued (not processed).
1188
- const p1 = conversation.processMessage("msg-1", [], () => {}, "req-1");
1267
+ const p1 = conversation.processMessage({
1268
+ content: "msg-1",
1269
+ attachments: [],
1270
+ requestId: "req-1",
1271
+ });
1189
1272
  await waitForPendingRun(1);
1190
1273
 
1191
1274
  // Fill to just-under budget: two ~500-char messages (1512+1512 = 3024 bytes).
1192
- const accepted1 = conversation.enqueueMessage(
1193
- "x".repeat(500),
1194
- [],
1195
- () => {},
1196
- "req-big-1",
1197
- );
1198
- const accepted2 = conversation.enqueueMessage(
1199
- "y".repeat(500),
1200
- [],
1201
- () => {},
1202
- "req-big-2",
1203
- );
1275
+ const accepted1 = conversation.enqueueMessage({
1276
+ content: "x".repeat(500),
1277
+ requestId: "req-big-1",
1278
+ });
1279
+ const accepted2 = conversation.enqueueMessage({
1280
+ content: "y".repeat(500),
1281
+ requestId: "req-big-2",
1282
+ });
1204
1283
  expect(accepted1.queued).toBe(true);
1205
1284
  expect(accepted2.queued).toBe(true);
1206
1285
  // A third would push the queue over budget → rejected. Capture its
1207
1286
  // onEvent callback so we can verify the queue_full error event reaches
1208
1287
  // the rejected caller (not just the synchronous return value).
1209
1288
  const rejectedEvents: ServerMessage[] = [];
1210
- const rejected = conversation.enqueueMessage(
1211
- "z".repeat(500),
1212
- [],
1213
- (e) => rejectedEvents.push(e),
1214
- "req-over",
1215
- );
1289
+ const rejected = conversation.enqueueMessage({
1290
+ content: "z".repeat(500),
1291
+ onEvent: (e) => rejectedEvents.push(e),
1292
+ requestId: "req-over",
1293
+ });
1216
1294
  expect(rejected.queued).toBe(false);
1217
1295
  expect(rejected.rejected).toBe(true);
1218
1296
  expect(conversation.getQueueDepth()).toBe(2);
@@ -1231,33 +1309,41 @@ describe("Batched drain", () => {
1231
1309
  }
1232
1310
 
1233
1311
  // Complete in-flight → drain pulls both queued passthroughs as ONE batched run.
1234
- resolveRun(0);
1312
+ await resolveRun(0);
1235
1313
  await p1;
1236
1314
  await waitForPendingRun(2);
1237
1315
  expect(conversation.getQueueDepth()).toBe(0);
1238
1316
 
1239
1317
  // Resolve the batched run.
1240
- resolveRun(1);
1318
+ await resolveRun(1);
1241
1319
  await new Promise((r) => setTimeout(r, 10));
1242
1320
 
1243
1321
  // After the full drain, the byte budget must be fully reclaimed — a fresh
1244
1322
  // round of enqueues up to the budget should succeed again. Spin up another
1245
1323
  // in-flight message to reach the queueing state.
1246
- const p2 = conversation.processMessage("msg-2", [], () => {}, "req-2");
1324
+ const p2 = conversation.processMessage({
1325
+ content: "msg-2",
1326
+ attachments: [],
1327
+ requestId: "req-2",
1328
+ });
1247
1329
  await waitForPendingRun(3);
1248
1330
  expect(
1249
- conversation.enqueueMessage("a".repeat(500), [], () => {}, "req-a")
1250
- .queued,
1331
+ conversation.enqueueMessage({
1332
+ content: "a".repeat(500),
1333
+ requestId: "req-a",
1334
+ }).queued,
1251
1335
  ).toBe(true);
1252
1336
  expect(
1253
- conversation.enqueueMessage("b".repeat(500), [], () => {}, "req-b")
1254
- .queued,
1337
+ conversation.enqueueMessage({
1338
+ content: "b".repeat(500),
1339
+ requestId: "req-b",
1340
+ }).queued,
1255
1341
  ).toBe(true);
1256
1342
 
1257
- resolveRun(2);
1343
+ await resolveRun(2);
1258
1344
  await p2;
1259
1345
  await waitForPendingRun(4);
1260
- resolveRun(3);
1346
+ await resolveRun(3);
1261
1347
  await new Promise((r) => setTimeout(r, 10));
1262
1348
  });
1263
1349
  });
@@ -1281,7 +1367,11 @@ describe("Batched drain correctness fixes", () => {
1281
1367
  const eventsRegular: ServerMessage[] = [];
1282
1368
 
1283
1369
  // Start in-flight message
1284
- const p1 = conversation.processMessage("msg-1", [], () => {}, "req-1");
1370
+ const p1 = conversation.processMessage({
1371
+ content: "msg-1",
1372
+ attachments: [],
1373
+ requestId: "req-1",
1374
+ });
1285
1375
  await waitForPendingRun(1);
1286
1376
 
1287
1377
  // Enqueue a surface-action message (activeSurfaceId set + tracked in
@@ -1289,25 +1379,23 @@ describe("Batched drain correctness fixes", () => {
1289
1379
  // same interface. The batch builder must reject the surface-action head
1290
1380
  // so each drains as its own run.
1291
1381
  conversation.surfaceActionRequestIds.add("req-surface");
1292
- conversation.enqueueMessage(
1293
- "surface action response",
1294
- [],
1295
- (e) => eventsSurface.push(e),
1296
- "req-surface",
1297
- "surface-1", // activeSurfaceId
1298
- );
1299
- conversation.enqueueMessage(
1300
- "regular follow-up",
1301
- [],
1302
- (e) => eventsRegular.push(e),
1303
- "req-regular",
1304
- );
1382
+ conversation.enqueueMessage({
1383
+ content: "surface action response",
1384
+ onEvent: (e) => eventsSurface.push(e),
1385
+ requestId: "req-surface",
1386
+ activeSurfaceId: "surface-1",
1387
+ });
1388
+ conversation.enqueueMessage({
1389
+ content: "regular follow-up",
1390
+ onEvent: (e) => eventsRegular.push(e),
1391
+ requestId: "req-regular",
1392
+ });
1305
1393
  expect(conversation.getQueueDepth()).toBe(2);
1306
1394
 
1307
1395
  // Complete run 0 → drain must NOT batch the surface-action with the
1308
1396
  // regular passthrough. Expect the surface-action to drain as a single
1309
1397
  // run first.
1310
- resolveRun(0);
1398
+ await resolveRun(0);
1311
1399
  await p1;
1312
1400
  await waitForPendingRun(2);
1313
1401
 
@@ -1322,7 +1410,7 @@ describe("Batched drain correctness fixes", () => {
1322
1410
 
1323
1411
  // Complete the surface-action run; drain pulls the regular passthrough
1324
1412
  // as its own separate run.
1325
- resolveRun(1);
1413
+ await resolveRun(1);
1326
1414
  await waitForPendingRun(3);
1327
1415
  expect(pendingRuns.length).toBe(3);
1328
1416
  expect(
@@ -1331,7 +1419,7 @@ describe("Batched drain correctness fixes", () => {
1331
1419
 
1332
1420
  // Total runs = 3: msg-1, surface-action, regular — NOT 2 (would mean
1333
1421
  // they were batched).
1334
- resolveRun(2);
1422
+ await resolveRun(2);
1335
1423
  await new Promise((r) => setTimeout(r, 10));
1336
1424
  });
1337
1425
 
@@ -1345,12 +1433,12 @@ describe("Batched drain correctness fixes", () => {
1345
1433
  const events4: ServerMessage[] = [];
1346
1434
 
1347
1435
  // Start in-flight message
1348
- const p1 = conversation.processMessage(
1349
- "msg-1",
1350
- [],
1351
- (e) => events1.push(e),
1352
- "req-1",
1353
- );
1436
+ const p1 = conversation.processMessage({
1437
+ content: "msg-1",
1438
+ attachments: [],
1439
+ onEvent: (e) => events1.push(e),
1440
+ requestId: "req-1",
1441
+ });
1354
1442
  await waitForPendingRun(1);
1355
1443
 
1356
1444
  // Enqueue three sibling passthroughs (msg-2 = head, msg-3 = mid,
@@ -1360,7 +1448,11 @@ describe("Batched drain correctness fixes", () => {
1360
1448
  // fresh one). Calling abort() now aborts that fresh controller, and
1361
1449
  // the drainBatch loop's abort check after msg-3's persist will break,
1362
1450
  // so msg-4 never persists.
1363
- conversation.enqueueMessage("msg-2", [], (e) => events2.push(e), "req-2");
1451
+ conversation.enqueueMessage({
1452
+ content: "msg-2",
1453
+ onEvent: (e) => events2.push(e),
1454
+ requestId: "req-2",
1455
+ });
1364
1456
 
1365
1457
  // Install a one-shot abort trigger on msg-3's dequeue event. We do
1366
1458
  // this before enqueueing so the wrapped callback is what drainBatch
@@ -1373,8 +1465,16 @@ describe("Batched drain correctness fixes", () => {
1373
1465
  conversation.abort();
1374
1466
  }
1375
1467
  };
1376
- conversation.enqueueMessage("msg-3", [], onMsg3Event, "req-3");
1377
- conversation.enqueueMessage("msg-4", [], (e) => events4.push(e), "req-4");
1468
+ conversation.enqueueMessage({
1469
+ content: "msg-3",
1470
+ onEvent: onMsg3Event,
1471
+ requestId: "req-3",
1472
+ });
1473
+ conversation.enqueueMessage({
1474
+ content: "msg-4",
1475
+ onEvent: (e) => events4.push(e),
1476
+ requestId: "req-4",
1477
+ });
1378
1478
  expect(conversation.getQueueDepth()).toBe(3);
1379
1479
 
1380
1480
  const persistedUserRowCountBefore = capturedAddMessages.filter(
@@ -1382,7 +1482,7 @@ describe("Batched drain correctness fixes", () => {
1382
1482
  ).length;
1383
1483
 
1384
1484
  // Complete run 0 → drain pulls the sibling batch.
1385
- resolveRun(0);
1485
+ await resolveRun(0);
1386
1486
  await p1;
1387
1487
 
1388
1488
  // Give the drain loop a chance to iterate. Abort happens on msg-3's
@@ -1412,12 +1512,12 @@ describe("Batched drain correctness fixes", () => {
1412
1512
  const events4: ServerMessage[] = [];
1413
1513
 
1414
1514
  // Start in-flight message
1415
- const p1 = conversation.processMessage(
1416
- "msg-1",
1417
- [],
1418
- (e) => events1.push(e),
1419
- "req-1",
1420
- );
1515
+ const p1 = conversation.processMessage({
1516
+ content: "msg-1",
1517
+ attachments: [],
1518
+ onEvent: (e) => events1.push(e),
1519
+ requestId: "req-1",
1520
+ });
1421
1521
  await waitForPendingRun(1);
1422
1522
 
1423
1523
  // Enqueue three siblings. Configure addMessage to throw for the second
@@ -1426,28 +1526,25 @@ describe("Batched drain correctness fixes", () => {
1426
1526
  // msg-tail's requestId (the LAST successful persist), not msg-mid's.
1427
1527
  addMessageShouldThrowForContent.add("msg-mid-unique-marker");
1428
1528
 
1429
- conversation.enqueueMessage(
1430
- "msg-head",
1431
- [],
1432
- (e) => events2.push(e),
1433
- "req-head",
1434
- );
1435
- conversation.enqueueMessage(
1436
- "msg-mid-unique-marker",
1437
- [],
1438
- (e) => events3.push(e),
1439
- "req-mid",
1440
- );
1441
- conversation.enqueueMessage(
1442
- "msg-tail",
1443
- [],
1444
- (e) => events4.push(e),
1445
- "req-tail",
1446
- );
1529
+ conversation.enqueueMessage({
1530
+ content: "msg-head",
1531
+ onEvent: (e) => events2.push(e),
1532
+ requestId: "req-head",
1533
+ });
1534
+ conversation.enqueueMessage({
1535
+ content: "msg-mid-unique-marker",
1536
+ onEvent: (e) => events3.push(e),
1537
+ requestId: "req-mid",
1538
+ });
1539
+ conversation.enqueueMessage({
1540
+ content: "msg-tail",
1541
+ onEvent: (e) => events4.push(e),
1542
+ requestId: "req-tail",
1543
+ });
1447
1544
  expect(conversation.getQueueDepth()).toBe(3);
1448
1545
 
1449
1546
  // Complete run 0 → batched drain.
1450
- resolveRun(0);
1547
+ await resolveRun(0);
1451
1548
  await p1;
1452
1549
  await waitForPendingRun(2);
1453
1550
 
@@ -1464,7 +1561,7 @@ describe("Batched drain correctness fixes", () => {
1464
1561
  ).toBe("req-tail");
1465
1562
 
1466
1563
  // Cleanup: resolve the batched run.
1467
- resolveRun(1);
1564
+ await resolveRun(1);
1468
1565
  await new Promise((r) => setTimeout(r, 20));
1469
1566
  });
1470
1567
 
@@ -1477,12 +1574,12 @@ describe("Batched drain correctness fixes", () => {
1477
1574
  const events3: ServerMessage[] = [];
1478
1575
  const events4: ServerMessage[] = [];
1479
1576
 
1480
- const p1 = conversation.processMessage(
1481
- "msg-1",
1482
- [],
1483
- (e) => events1.push(e),
1484
- "req-1",
1485
- );
1577
+ const p1 = conversation.processMessage({
1578
+ content: "msg-1",
1579
+ attachments: [],
1580
+ onEvent: (e) => events1.push(e),
1581
+ requestId: "req-1",
1582
+ });
1486
1583
  await waitForPendingRun(1);
1487
1584
 
1488
1585
  // Mid tail will fail to persist. After the batched run resolves,
@@ -1492,31 +1589,28 @@ describe("Batched drain correctness fixes", () => {
1492
1589
  // desync the client.
1493
1590
  addMessageShouldThrowForContent.add("fanout-mid-marker");
1494
1591
 
1495
- conversation.enqueueMessage(
1496
- "fanout-head",
1497
- [],
1498
- (e) => events2.push(e),
1499
- "req-fanout-head",
1500
- );
1501
- conversation.enqueueMessage(
1502
- "fanout-mid-marker",
1503
- [],
1504
- (e) => events3.push(e),
1505
- "req-fanout-mid",
1506
- );
1507
- conversation.enqueueMessage(
1508
- "fanout-tail",
1509
- [],
1510
- (e) => events4.push(e),
1511
- "req-fanout-tail",
1512
- );
1592
+ conversation.enqueueMessage({
1593
+ content: "fanout-head",
1594
+ onEvent: (e) => events2.push(e),
1595
+ requestId: "req-fanout-head",
1596
+ });
1597
+ conversation.enqueueMessage({
1598
+ content: "fanout-mid-marker",
1599
+ onEvent: (e) => events3.push(e),
1600
+ requestId: "req-fanout-mid",
1601
+ });
1602
+ conversation.enqueueMessage({
1603
+ content: "fanout-tail",
1604
+ onEvent: (e) => events4.push(e),
1605
+ requestId: "req-fanout-tail",
1606
+ });
1513
1607
 
1514
- resolveRun(0);
1608
+ await resolveRun(0);
1515
1609
  await p1;
1516
1610
  await waitForPendingRun(2);
1517
1611
 
1518
1612
  // Drive the batched run to emit message_complete via fanOutOnEvent.
1519
- resolveRun(1);
1613
+ await resolveRun(1);
1520
1614
  await new Promise((r) => setTimeout(r, 20));
1521
1615
 
1522
1616
  expect(events3.find((e) => e.type === "error")).toBeDefined();
@@ -1536,7 +1630,11 @@ describe("Batched drain correctness fixes", () => {
1536
1630
  await conversation.loadFromDb();
1537
1631
 
1538
1632
  // Start in-flight message
1539
- const p1 = conversation.processMessage("msg-1", [], () => {}, "req-1");
1633
+ const p1 = conversation.processMessage({
1634
+ content: "msg-1",
1635
+ attachments: [],
1636
+ requestId: "req-1",
1637
+ });
1540
1638
  await waitForPendingRun(1);
1541
1639
 
1542
1640
  // Snapshot the count before drain so we only compare batch-emitted
@@ -1544,12 +1642,12 @@ describe("Batched drain correctness fixes", () => {
1544
1642
  const baseline = activityStates.length;
1545
1643
 
1546
1644
  // Enqueue three sibling passthroughs.
1547
- conversation.enqueueMessage("msg-2", [], () => {}, "req-2");
1548
- conversation.enqueueMessage("msg-3", [], () => {}, "req-3");
1549
- conversation.enqueueMessage("msg-4", [], () => {}, "req-4");
1645
+ conversation.enqueueMessage({ content: "msg-2", requestId: "req-2" });
1646
+ conversation.enqueueMessage({ content: "msg-3", requestId: "req-3" });
1647
+ conversation.enqueueMessage({ content: "msg-4", requestId: "req-4" });
1550
1648
 
1551
1649
  // Complete run 0 → drain pulls the batched siblings as ONE run.
1552
- resolveRun(0);
1650
+ await resolveRun(0);
1553
1651
  await p1;
1554
1652
  await waitForPendingRun(2);
1555
1653
 
@@ -1569,7 +1667,7 @@ describe("Batched drain correctness fixes", () => {
1569
1667
  requestId: "req-2", // head's requestId, per the fix
1570
1668
  });
1571
1669
 
1572
- resolveRun(1);
1670
+ await resolveRun(1);
1573
1671
  await new Promise((r) => setTimeout(r, 10));
1574
1672
  });
1575
1673
 
@@ -1610,17 +1708,21 @@ describe("Conversation queue policy helpers", () => {
1610
1708
  await conversation.loadFromDb();
1611
1709
 
1612
1710
  // Start processing to make the session busy
1613
- conversation.processMessage("msg-1", [], () => {}, "req-1");
1711
+ conversation.processMessage({
1712
+ content: "msg-1",
1713
+ attachments: [],
1714
+ requestId: "req-1",
1715
+ });
1614
1716
  await waitForPendingRun(1);
1615
1717
 
1616
1718
  // Enqueue a message while processing
1617
- conversation.enqueueMessage("msg-2", [], () => {}, "req-2");
1719
+ conversation.enqueueMessage({ content: "msg-2", requestId: "req-2" });
1618
1720
  expect(conversation.hasQueuedMessages()).toBe(true);
1619
1721
 
1620
1722
  // Cleanup: resolve the pending run
1621
- resolveRun(0);
1723
+ await resolveRun(0);
1622
1724
  await waitForPendingRun(2);
1623
- resolveRun(1);
1725
+ await resolveRun(1);
1624
1726
  await new Promise((r) => setTimeout(r, 10));
1625
1727
  });
1626
1728
 
@@ -1637,7 +1739,11 @@ describe("Conversation queue policy helpers", () => {
1637
1739
  await conversation.loadFromDb();
1638
1740
 
1639
1741
  // Start processing — but don't enqueue anything
1640
- conversation.processMessage("msg-1", [], () => {}, "req-1");
1742
+ conversation.processMessage({
1743
+ content: "msg-1",
1744
+ attachments: [],
1745
+ requestId: "req-1",
1746
+ });
1641
1747
  await waitForPendingRun(1);
1642
1748
 
1643
1749
  expect(conversation.isProcessing()).toBe(true);
@@ -1645,7 +1751,7 @@ describe("Conversation queue policy helpers", () => {
1645
1751
  expect(conversation.canHandoffAtCheckpoint()).toBe(false);
1646
1752
 
1647
1753
  // Cleanup
1648
- resolveRun(0);
1754
+ await resolveRun(0);
1649
1755
  await new Promise((r) => setTimeout(r, 10));
1650
1756
  });
1651
1757
 
@@ -1654,20 +1760,24 @@ describe("Conversation queue policy helpers", () => {
1654
1760
  await conversation.loadFromDb();
1655
1761
 
1656
1762
  // Start processing
1657
- conversation.processMessage("msg-1", [], () => {}, "req-1");
1763
+ conversation.processMessage({
1764
+ content: "msg-1",
1765
+ attachments: [],
1766
+ requestId: "req-1",
1767
+ });
1658
1768
  await waitForPendingRun(1);
1659
1769
 
1660
1770
  // Enqueue a message
1661
- conversation.enqueueMessage("msg-2", [], () => {}, "req-2");
1771
+ conversation.enqueueMessage({ content: "msg-2", requestId: "req-2" });
1662
1772
 
1663
1773
  expect(conversation.isProcessing()).toBe(true);
1664
1774
  expect(conversation.hasQueuedMessages()).toBe(true);
1665
1775
  expect(conversation.canHandoffAtCheckpoint()).toBe(true);
1666
1776
 
1667
1777
  // Cleanup
1668
- resolveRun(0);
1778
+ await resolveRun(0);
1669
1779
  await waitForPendingRun(2);
1670
- resolveRun(1);
1780
+ await resolveRun(1);
1671
1781
  await new Promise((r) => setTimeout(r, 10));
1672
1782
  });
1673
1783
 
@@ -1705,16 +1815,16 @@ describe("Conversation checkpoint handoff", () => {
1705
1815
  const events1: ServerMessage[] = [];
1706
1816
 
1707
1817
  // Start processing first message
1708
- const p1 = conversation.processMessage(
1709
- "msg-1",
1710
- [],
1711
- (e) => events1.push(e),
1712
- "req-1",
1713
- );
1818
+ const p1 = conversation.processMessage({
1819
+ content: "msg-1",
1820
+ attachments: [],
1821
+ onEvent: (e) => events1.push(e),
1822
+ requestId: "req-1",
1823
+ });
1714
1824
  await waitForPendingRun(1);
1715
1825
 
1716
1826
  // Enqueue a second message while the first is processing
1717
- conversation.enqueueMessage("msg-2", [], () => {}, "req-2");
1827
+ conversation.enqueueMessage({ content: "msg-2", requestId: "req-2" });
1718
1828
  expect(conversation.hasQueuedMessages()).toBe(true);
1719
1829
 
1720
1830
  // The pending run should have received an onCheckpoint callback.
@@ -1728,11 +1838,11 @@ describe("Conversation checkpoint handoff", () => {
1728
1838
  history: [],
1729
1839
  });
1730
1840
 
1731
- // Because there is a queued message, the callback should return 'yield'
1732
- expect(decision).toBe("yield");
1841
+ // Because there is a queued message, the callback should yield for handoff
1842
+ expect(decision).toEqual("handoff");
1733
1843
 
1734
1844
  // Complete the run so the conversation finishes cleanly
1735
- resolveRun(0);
1845
+ await resolveRun(0);
1736
1846
  await p1;
1737
1847
 
1738
1848
  // After yield, the first message should emit generation_handoff
@@ -1747,7 +1857,7 @@ describe("Conversation checkpoint handoff", () => {
1747
1857
 
1748
1858
  // The queued message should now be draining (second run started)
1749
1859
  await waitForPendingRun(2);
1750
- resolveRun(1);
1860
+ await resolveRun(1);
1751
1861
  await new Promise((r) => setTimeout(r, 10));
1752
1862
  });
1753
1863
 
@@ -1756,7 +1866,11 @@ describe("Conversation checkpoint handoff", () => {
1756
1866
  await conversation.loadFromDb();
1757
1867
 
1758
1868
  // Start processing — no enqueued messages
1759
- const p1 = conversation.processMessage("msg-1", [], () => {}, "req-1");
1869
+ const p1 = conversation.processMessage({
1870
+ content: "msg-1",
1871
+ attachments: [],
1872
+ requestId: "req-1",
1873
+ });
1760
1874
  await waitForPendingRun(1);
1761
1875
 
1762
1876
  expect(conversation.hasQueuedMessages()).toBe(false);
@@ -1775,7 +1889,7 @@ describe("Conversation checkpoint handoff", () => {
1775
1889
  expect(decision).toBe("continue");
1776
1890
 
1777
1891
  // Cleanup
1778
- resolveRun(0);
1892
+ await resolveRun(0);
1779
1893
  await p1;
1780
1894
  });
1781
1895
 
@@ -1789,18 +1903,30 @@ describe("Conversation checkpoint handoff", () => {
1789
1903
  const events4: ServerMessage[] = [];
1790
1904
 
1791
1905
  // Start first message (mid-tool-use — will yield at the next checkpoint)
1792
- const p1 = conversation.processMessage(
1793
- "msg-1",
1794
- [],
1795
- (e) => events1.push(e),
1796
- "req-1",
1797
- );
1906
+ const p1 = conversation.processMessage({
1907
+ content: "msg-1",
1908
+ attachments: [],
1909
+ onEvent: (e) => events1.push(e),
1910
+ requestId: "req-1",
1911
+ });
1798
1912
  await waitForPendingRun(1);
1799
1913
 
1800
1914
  // Enqueue three sibling passthroughs while msg-1 is mid-turn
1801
- conversation.enqueueMessage("msg-2", [], (e) => events2.push(e), "req-2");
1802
- conversation.enqueueMessage("msg-3", [], (e) => events3.push(e), "req-3");
1803
- conversation.enqueueMessage("msg-4", [], (e) => events4.push(e), "req-4");
1915
+ conversation.enqueueMessage({
1916
+ content: "msg-2",
1917
+ onEvent: (e) => events2.push(e),
1918
+ requestId: "req-2",
1919
+ });
1920
+ conversation.enqueueMessage({
1921
+ content: "msg-3",
1922
+ onEvent: (e) => events3.push(e),
1923
+ requestId: "req-3",
1924
+ });
1925
+ conversation.enqueueMessage({
1926
+ content: "msg-4",
1927
+ onEvent: (e) => events4.push(e),
1928
+ requestId: "req-4",
1929
+ });
1804
1930
  expect(conversation.getQueueDepth()).toBe(3);
1805
1931
 
1806
1932
  // Simulate the agent loop yielding at the checkpoint (first run is mid-tool-use)
@@ -1812,10 +1938,10 @@ describe("Conversation checkpoint handoff", () => {
1812
1938
  hasToolUse: true,
1813
1939
  history: [],
1814
1940
  });
1815
- expect(decision).toBe("yield");
1941
+ expect(decision).toEqual("handoff");
1816
1942
 
1817
1943
  // Complete first run
1818
- resolveRun(0);
1944
+ await resolveRun(0);
1819
1945
  await p1;
1820
1946
 
1821
1947
  // The yielded drain pulls ALL THREE queued siblings as ONE batched run —
@@ -1829,7 +1955,7 @@ describe("Conversation checkpoint handoff", () => {
1829
1955
  expect(events4.some((e) => e.type === "message_dequeued")).toBe(true);
1830
1956
 
1831
1957
  // Resolve the batched run — message_complete fans out to all three clients.
1832
- resolveRun(1);
1958
+ await resolveRun(1);
1833
1959
  await new Promise((r) => setTimeout(r, 10));
1834
1960
 
1835
1961
  expect(events2.some((e) => e.type === "message_complete")).toBe(true);
@@ -1845,20 +1971,24 @@ describe("Conversation checkpoint handoff", () => {
1845
1971
  const events2: ServerMessage[] = [];
1846
1972
 
1847
1973
  // Start processing first message
1848
- const p1 = conversation.processMessage(
1849
- "msg-1",
1850
- [],
1851
- (e) => events1.push(e),
1852
- "req-1",
1853
- );
1974
+ const p1 = conversation.processMessage({
1975
+ content: "msg-1",
1976
+ attachments: [],
1977
+ onEvent: (e) => events1.push(e),
1978
+ requestId: "req-1",
1979
+ });
1854
1980
  await waitForPendingRun(1);
1855
1981
 
1856
1982
  // Enqueue a second message while the first is processing
1857
- conversation.enqueueMessage("msg-2", [], (e) => events2.push(e), "req-2");
1983
+ conversation.enqueueMessage({
1984
+ content: "msg-2",
1985
+ onEvent: (e) => events2.push(e),
1986
+ requestId: "req-2",
1987
+ });
1858
1988
  expect(conversation.hasQueuedMessages()).toBe(true);
1859
1989
 
1860
1990
  // Simulate tool-use turns: the agent loop calls onCheckpoint at each turn boundary.
1861
- // Because there is a queued message, the callback should return 'yield'.
1991
+ // Because there is a queued message, the callback should yield for handoff.
1862
1992
  const run = pendingRuns[0];
1863
1993
  expect(run.onCheckpoint).toBeDefined();
1864
1994
 
@@ -1870,10 +2000,10 @@ describe("Conversation checkpoint handoff", () => {
1870
2000
  hasToolUse: true,
1871
2001
  history: [],
1872
2002
  });
1873
- expect(decision).toBe("yield");
2003
+ expect(decision).toEqual("handoff");
1874
2004
 
1875
2005
  // Complete the run (AgentLoop resolves after yielding)
1876
- resolveRun(0);
2006
+ await resolveRun(0);
1877
2007
  await p1;
1878
2008
 
1879
2009
  // Verify generation_handoff was emitted (not plain message_complete)
@@ -1896,7 +2026,7 @@ describe("Conversation checkpoint handoff", () => {
1896
2026
  expect(events2.some((e) => e.type === "message_dequeued")).toBe(true);
1897
2027
 
1898
2028
  // Complete the second run
1899
- resolveRun(1);
2029
+ await resolveRun(1);
1900
2030
  await new Promise((r) => setTimeout(r, 10));
1901
2031
  });
1902
2032
 
@@ -1912,12 +2042,12 @@ describe("Conversation checkpoint handoff", () => {
1912
2042
  };
1913
2043
 
1914
2044
  // Start processing message A
1915
- const pA = conversation.processMessage(
1916
- "msg-A",
1917
- [],
1918
- (e) => eventsA.push(e),
1919
- "req-A",
1920
- );
2045
+ const pA = conversation.processMessage({
2046
+ content: "msg-A",
2047
+ attachments: [],
2048
+ onEvent: (e) => eventsA.push(e),
2049
+ requestId: "req-A",
2050
+ });
1921
2051
  await waitForPendingRun(1);
1922
2052
 
1923
2053
  // Enqueue messages B, C, D — each on a distinct userMessageInterface so the
@@ -1926,33 +2056,24 @@ describe("Conversation checkpoint handoff", () => {
1926
2056
  userMessageInterface: iface,
1927
2057
  assistantMessageInterface: iface,
1928
2058
  });
1929
- conversation.enqueueMessage(
1930
- "msg-B",
1931
- [],
1932
- makeHandler("B"),
1933
- "req-B",
1934
- undefined,
1935
- undefined,
1936
- meta("macos"),
1937
- );
1938
- conversation.enqueueMessage(
1939
- "msg-C",
1940
- [],
1941
- makeHandler("C"),
1942
- "req-C",
1943
- undefined,
1944
- undefined,
1945
- meta("cli"),
1946
- );
1947
- conversation.enqueueMessage(
1948
- "msg-D",
1949
- [],
1950
- makeHandler("D"),
1951
- "req-D",
1952
- undefined,
1953
- undefined,
1954
- meta("vellum"),
1955
- );
2059
+ conversation.enqueueMessage({
2060
+ content: "msg-B",
2061
+ onEvent: makeHandler("B"),
2062
+ requestId: "req-B",
2063
+ metadata: meta("macos"),
2064
+ });
2065
+ conversation.enqueueMessage({
2066
+ content: "msg-C",
2067
+ onEvent: makeHandler("C"),
2068
+ requestId: "req-C",
2069
+ metadata: meta("cli"),
2070
+ });
2071
+ conversation.enqueueMessage({
2072
+ content: "msg-D",
2073
+ onEvent: makeHandler("D"),
2074
+ requestId: "req-D",
2075
+ metadata: meta("vellum"),
2076
+ });
1956
2077
  expect(conversation.getQueueDepth()).toBe(3);
1957
2078
 
1958
2079
  // Handoff from A -> B
@@ -1965,8 +2086,8 @@ describe("Conversation checkpoint handoff", () => {
1965
2086
  hasToolUse: true,
1966
2087
  history: [],
1967
2088
  }),
1968
- ).toBe("yield");
1969
- resolveRun(0);
2089
+ ).toEqual("handoff");
2090
+ await resolveRun(0);
1970
2091
  await pA;
1971
2092
 
1972
2093
  // B should be draining
@@ -1982,8 +2103,8 @@ describe("Conversation checkpoint handoff", () => {
1982
2103
  hasToolUse: true,
1983
2104
  history: [],
1984
2105
  }),
1985
- ).toBe("yield");
1986
- resolveRun(1);
2106
+ ).toEqual("handoff");
2107
+ await resolveRun(1);
1987
2108
  await waitForPendingRun(3);
1988
2109
 
1989
2110
  // Handoff from C -> D
@@ -1997,8 +2118,8 @@ describe("Conversation checkpoint handoff", () => {
1997
2118
  hasToolUse: true,
1998
2119
  history: [],
1999
2120
  }),
2000
- ).toBe("yield");
2001
- resolveRun(2);
2121
+ ).toEqual("handoff");
2122
+ await resolveRun(2);
2002
2123
  await waitForPendingRun(4);
2003
2124
 
2004
2125
  // D has no more queued -> checkpoint should return 'continue'
@@ -2013,7 +2134,7 @@ describe("Conversation checkpoint handoff", () => {
2013
2134
  }),
2014
2135
  ).toBe("continue");
2015
2136
 
2016
- resolveRun(3);
2137
+ await resolveRun(3);
2017
2138
  await new Promise((r) => setTimeout(r, 10));
2018
2139
 
2019
2140
  // Verify FIFO dequeue order
@@ -2029,21 +2150,29 @@ describe("Conversation checkpoint handoff", () => {
2029
2150
  const eventsC: ServerMessage[] = [];
2030
2151
 
2031
2152
  // Start processing message A
2032
- const pA = conversation.processMessage(
2033
- "msg-A",
2034
- [],
2035
- (e) => eventsA.push(e),
2036
- "req-A",
2037
- );
2153
+ const pA = conversation.processMessage({
2154
+ content: "msg-A",
2155
+ attachments: [],
2156
+ onEvent: (e) => eventsA.push(e),
2157
+ requestId: "req-A",
2158
+ });
2038
2159
  await waitForPendingRun(1);
2039
2160
 
2040
2161
  // Enqueue B (empty content — will fail to persist) and C (valid)
2041
- conversation.enqueueMessage("", [], (e) => eventsB.push(e), "req-B");
2042
- conversation.enqueueMessage("msg-C", [], (e) => eventsC.push(e), "req-C");
2162
+ conversation.enqueueMessage({
2163
+ content: "",
2164
+ onEvent: (e) => eventsB.push(e),
2165
+ requestId: "req-B",
2166
+ });
2167
+ conversation.enqueueMessage({
2168
+ content: "msg-C",
2169
+ onEvent: (e) => eventsC.push(e),
2170
+ requestId: "req-C",
2171
+ });
2043
2172
  expect(conversation.getQueueDepth()).toBe(2);
2044
2173
 
2045
2174
  // Complete message A — triggers drain. B should fail, C should proceed.
2046
- resolveRun(0);
2175
+ await resolveRun(0);
2047
2176
  await pA;
2048
2177
 
2049
2178
  // C should have been dequeued and started a new AgentLoop.run
@@ -2060,7 +2189,7 @@ describe("Conversation checkpoint handoff", () => {
2060
2189
  expect(eventsC.some((e) => e.type === "message_dequeued")).toBe(true);
2061
2190
 
2062
2191
  // Complete C's run
2063
- resolveRun(1);
2192
+ await resolveRun(1);
2064
2193
  await new Promise((r) => setTimeout(r, 10));
2065
2194
 
2066
2195
  // C should have completed successfully
@@ -2072,7 +2201,11 @@ describe("Conversation checkpoint handoff", () => {
2072
2201
  await conversation.loadFromDb();
2073
2202
 
2074
2203
  // Start processing
2075
- const p1 = conversation.processMessage("msg-1", [], () => {}, "req-1");
2204
+ const p1 = conversation.processMessage({
2205
+ content: "msg-1",
2206
+ attachments: [],
2207
+ requestId: "req-1",
2208
+ });
2076
2209
  await waitForPendingRun(1);
2077
2210
 
2078
2211
  // The first run should have onCheckpoint
@@ -2097,7 +2230,7 @@ describe("Conversation checkpoint handoff", () => {
2097
2230
  expect(pendingRuns[1].onCheckpoint).toBeDefined();
2098
2231
 
2099
2232
  // Complete retry cleanly
2100
- resolveRun(1);
2233
+ await resolveRun(1);
2101
2234
  await p1;
2102
2235
  });
2103
2236
  });
@@ -2116,11 +2249,15 @@ describe("Conversation usage requestId correlation", () => {
2116
2249
  const conversation = makeConversation();
2117
2250
  await conversation.loadFromDb();
2118
2251
 
2119
- const p1 = conversation.processMessage("msg-1", [], () => {}, "req-42");
2252
+ const p1 = conversation.processMessage({
2253
+ content: "msg-1",
2254
+ attachments: [],
2255
+ requestId: "req-42",
2256
+ });
2120
2257
  await waitForPendingRun(1);
2121
2258
 
2122
2259
  // Complete the run — this triggers recordUsage with the request's ID
2123
- resolveRun(0);
2260
+ await resolveRun(0);
2124
2261
  await p1;
2125
2262
 
2126
2263
  // The usage event should carry the request ID, not null
@@ -2149,16 +2286,20 @@ describe("Terminal trace events on rejection/failure", () => {
2149
2286
  await conversation.loadFromDb();
2150
2287
 
2151
2288
  // Start first message
2152
- const p1 = conversation.processMessage("msg-1", [], () => {}, "req-1");
2289
+ const p1 = conversation.processMessage({
2290
+ content: "msg-1",
2291
+ attachments: [],
2292
+ requestId: "req-1",
2293
+ });
2153
2294
  await waitForPendingRun(1);
2154
2295
 
2155
2296
  // Enqueue empty content (will fail persistUserMessage)
2156
- conversation.enqueueMessage("", [], () => {}, "req-bad");
2297
+ conversation.enqueueMessage({ content: "", requestId: "req-bad" });
2157
2298
  // Enqueue valid message so drain continues
2158
- conversation.enqueueMessage("msg-3", [], () => {}, "req-3");
2299
+ conversation.enqueueMessage({ content: "msg-3", requestId: "req-3" });
2159
2300
 
2160
2301
  // Complete first — triggers drain, empty msg fails persist
2161
- resolveRun(0);
2302
+ await resolveRun(0);
2162
2303
  await p1;
2163
2304
  await waitForPendingRun(2);
2164
2305
 
@@ -2173,7 +2314,7 @@ describe("Terminal trace events on rejection/failure", () => {
2173
2314
  expect(errorTrace).toBeDefined();
2174
2315
 
2175
2316
  // Cleanup
2176
- resolveRun(1);
2317
+ await resolveRun(1);
2177
2318
  await new Promise((r) => setTimeout(r, 10));
2178
2319
  });
2179
2320
  });
@@ -2197,12 +2338,12 @@ describe("Conversation host attachment directives", () => {
2197
2338
  const conversation = makeConversation((msg) => clientEvents.push(msg));
2198
2339
  await conversation.loadFromDb();
2199
2340
 
2200
- const p1 = conversation.processMessage(
2201
- "msg-1",
2202
- [],
2203
- (e) => events.push(e),
2204
- "req-1",
2205
- );
2341
+ const p1 = conversation.processMessage({
2342
+ content: "msg-1",
2343
+ attachments: [],
2344
+ onEvent: (e) => events.push(e),
2345
+ requestId: "req-1",
2346
+ });
2206
2347
  await waitForPendingRun(1);
2207
2348
 
2208
2349
  const run = pendingRuns[0];
@@ -2215,6 +2356,7 @@ describe("Conversation host attachment directives", () => {
2215
2356
  },
2216
2357
  ],
2217
2358
  };
2359
+ await run.onEvent({ type: "llm_call_started" });
2218
2360
  run.onEvent({
2219
2361
  type: "usage",
2220
2362
  inputTokens: 10,
@@ -2266,12 +2408,12 @@ describe("Conversation host attachment directives", () => {
2266
2408
  const conversation = makeConversation((msg) => clientEvents.push(msg));
2267
2409
  await conversation.loadFromDb();
2268
2410
 
2269
- const p1 = conversation.processMessage(
2270
- "msg-1",
2271
- [],
2272
- (e) => events.push(e),
2273
- "req-1",
2274
- );
2411
+ const p1 = conversation.processMessage({
2412
+ content: "msg-1",
2413
+ attachments: [],
2414
+ onEvent: (e) => events.push(e),
2415
+ requestId: "req-1",
2416
+ });
2275
2417
  await waitForPendingRun(1);
2276
2418
 
2277
2419
  const run = pendingRuns[0];
@@ -2284,6 +2426,7 @@ describe("Conversation host attachment directives", () => {
2284
2426
  },
2285
2427
  ],
2286
2428
  };
2429
+ await run.onEvent({ type: "llm_call_started" });
2287
2430
  run.onEvent({
2288
2431
  type: "usage",
2289
2432
  inputTokens: 10,
@@ -2352,12 +2495,12 @@ describe("Conversation attachment event payloads", () => {
2352
2495
  const conversation = makeConversation();
2353
2496
  await conversation.loadFromDb();
2354
2497
 
2355
- const p1 = conversation.processMessage(
2356
- "msg-1",
2357
- [],
2358
- (e) => events.push(e),
2359
- "req-1",
2360
- );
2498
+ const p1 = conversation.processMessage({
2499
+ content: "msg-1",
2500
+ attachments: [],
2501
+ onEvent: (e) => events.push(e),
2502
+ requestId: "req-1",
2503
+ });
2361
2504
  await waitForPendingRun(1);
2362
2505
 
2363
2506
  const run = pendingRuns[0];
@@ -2377,6 +2520,7 @@ describe("Conversation attachment event payloads", () => {
2377
2520
  } as any,
2378
2521
  ],
2379
2522
  });
2523
+ await run.onEvent({ type: "llm_call_started" });
2380
2524
  run.onEvent({
2381
2525
  type: "usage",
2382
2526
  inputTokens: 10,
@@ -2414,16 +2558,16 @@ describe("Conversation attachment event payloads", () => {
2414
2558
  const conversation = makeConversation();
2415
2559
  await conversation.loadFromDb();
2416
2560
 
2417
- const p1 = conversation.processMessage(
2418
- "msg-1",
2419
- [],
2420
- (e) => events1.push(e),
2421
- "req-1",
2422
- );
2561
+ const p1 = conversation.processMessage({
2562
+ content: "msg-1",
2563
+ attachments: [],
2564
+ onEvent: (e) => events1.push(e),
2565
+ requestId: "req-1",
2566
+ });
2423
2567
  await waitForPendingRun(1);
2424
2568
 
2425
2569
  // Queue a second message so the first run yields via checkpoint handoff.
2426
- conversation.enqueueMessage("msg-2", [], () => {}, "req-2");
2570
+ conversation.enqueueMessage({ content: "msg-2", requestId: "req-2" });
2427
2571
 
2428
2572
  const run = pendingRuns[0];
2429
2573
  expect(run.onCheckpoint).toBeDefined();
@@ -2434,7 +2578,7 @@ describe("Conversation attachment event payloads", () => {
2434
2578
  hasToolUse: true,
2435
2579
  history: [],
2436
2580
  }),
2437
- ).toBe("yield");
2581
+ ).toEqual("handoff");
2438
2582
 
2439
2583
  const assistantMsg: Message = {
2440
2584
  role: "assistant",
@@ -2452,6 +2596,7 @@ describe("Conversation attachment event payloads", () => {
2452
2596
  } as any,
2453
2597
  ],
2454
2598
  });
2599
+ await run.onEvent({ type: "llm_call_started" });
2455
2600
  run.onEvent({
2456
2601
  type: "usage",
2457
2602
  inputTokens: 10,
@@ -2478,7 +2623,7 @@ describe("Conversation attachment event payloads", () => {
2478
2623
  expect(attachments[0].data).toBe("iVBORw0K");
2479
2624
 
2480
2625
  await waitForPendingRun(2);
2481
- resolveRun(1);
2626
+ await resolveRun(1);
2482
2627
  await new Promise((r) => setTimeout(r, 10));
2483
2628
  });
2484
2629
  });
@@ -2498,19 +2643,19 @@ describe("Regression: cancel semantics and error channel split", () => {
2498
2643
  await conversation.loadFromDb();
2499
2644
 
2500
2645
  // Start processing a message — collect events from the per-message callback
2501
- const p1 = conversation.processMessage(
2502
- "msg-1",
2503
- [],
2504
- (e) => msgEvents.push(e),
2505
- "req-1",
2506
- );
2646
+ const p1 = conversation.processMessage({
2647
+ content: "msg-1",
2648
+ attachments: [],
2649
+ onEvent: (e) => msgEvents.push(e),
2650
+ requestId: "req-1",
2651
+ });
2507
2652
  await waitForPendingRun(1);
2508
2653
 
2509
2654
  // User cancels — sets the abort signal
2510
2655
  conversation.abort();
2511
2656
 
2512
2657
  // Resolve the pending run so the abort-check path fires
2513
- resolveRun(0);
2658
+ await resolveRun(0);
2514
2659
  await p1;
2515
2660
 
2516
2661
  // generation_cancelled should be emitted via the per-message callback
@@ -2532,12 +2677,12 @@ describe("Regression: cancel semantics and error channel split", () => {
2532
2677
  await conversation.loadFromDb();
2533
2678
  linkAttachmentShouldThrow = true;
2534
2679
 
2535
- const p1 = conversation.processMessage(
2536
- "msg-1",
2537
- [],
2538
- (e) => events.push(e),
2539
- "req-1",
2540
- );
2680
+ const p1 = conversation.processMessage({
2681
+ content: "msg-1",
2682
+ attachments: [],
2683
+ onEvent: (e) => events.push(e),
2684
+ requestId: "req-1",
2685
+ });
2541
2686
  await waitForPendingRun(1);
2542
2687
  const run = pendingRuns[0];
2543
2688
  const assistantMsg: Message = {
@@ -2556,6 +2701,7 @@ describe("Regression: cancel semantics and error channel split", () => {
2556
2701
  } as any,
2557
2702
  ],
2558
2703
  });
2704
+ await run.onEvent({ type: "llm_call_started" });
2559
2705
  run.onEvent({
2560
2706
  type: "usage",
2561
2707
  inputTokens: 10,
@@ -2585,12 +2731,12 @@ describe("Regression: cancel semantics and error channel split", () => {
2585
2731
  const conversation = makeConversation();
2586
2732
  await conversation.loadFromDb();
2587
2733
 
2588
- const p1 = conversation.processMessage(
2589
- "msg-1",
2590
- [],
2591
- (e) => allEvents.push(e),
2592
- "req-1",
2593
- );
2734
+ const p1 = conversation.processMessage({
2735
+ content: "msg-1",
2736
+ attachments: [],
2737
+ onEvent: (e) => allEvents.push(e),
2738
+ requestId: "req-1",
2739
+ });
2594
2740
  await waitForPendingRun(1);
2595
2741
 
2596
2742
  // Simulate a provider failure
@@ -2614,26 +2760,24 @@ describe("Regression: cancel semantics and error channel split", () => {
2614
2760
 
2615
2761
  const eventsPerMsg: ServerMessage[][] = [[], [], []];
2616
2762
 
2617
- conversation.processMessage(
2618
- "msg-1",
2619
- [],
2620
- (e) => eventsPerMsg[0].push(e),
2621
- "req-1",
2622
- );
2763
+ conversation.processMessage({
2764
+ content: "msg-1",
2765
+ attachments: [],
2766
+ onEvent: (e) => eventsPerMsg[0].push(e),
2767
+ requestId: "req-1",
2768
+ });
2623
2769
  await waitForPendingRun(1);
2624
2770
 
2625
- conversation.enqueueMessage(
2626
- "msg-2",
2627
- [],
2628
- (e) => eventsPerMsg[1].push(e),
2629
- "req-2",
2630
- );
2631
- conversation.enqueueMessage(
2632
- "msg-3",
2633
- [],
2634
- (e) => eventsPerMsg[2].push(e),
2635
- "req-3",
2636
- );
2771
+ conversation.enqueueMessage({
2772
+ content: "msg-2",
2773
+ onEvent: (e) => eventsPerMsg[1].push(e),
2774
+ requestId: "req-2",
2775
+ });
2776
+ conversation.enqueueMessage({
2777
+ content: "msg-3",
2778
+ onEvent: (e) => eventsPerMsg[2].push(e),
2779
+ requestId: "req-3",
2780
+ });
2637
2781
 
2638
2782
  conversation.abort();
2639
2783
 
@@ -2668,19 +2812,23 @@ describe("Regression: cancel semantics and error channel split", () => {
2668
2812
  const events2: ServerMessage[] = [];
2669
2813
 
2670
2814
  // Start first message (promise intentionally not awaited — we test queue drain behavior)
2671
- const _p1 = conversation.processMessage(
2672
- "msg-1",
2673
- [],
2674
- (e) => events1.push(e),
2675
- "req-1",
2676
- );
2815
+ const _p1 = conversation.processMessage({
2816
+ content: "msg-1",
2817
+ attachments: [],
2818
+ onEvent: (e) => events1.push(e),
2819
+ requestId: "req-1",
2820
+ });
2677
2821
  await waitForPendingRun(1);
2678
2822
 
2679
2823
  // Enqueue a second message while the first is processing
2680
- conversation.enqueueMessage("msg-2", [], (e) => events2.push(e), "req-2");
2824
+ conversation.enqueueMessage({
2825
+ content: "msg-2",
2826
+ onEvent: (e) => events2.push(e),
2827
+ requestId: "req-2",
2828
+ });
2681
2829
 
2682
2830
  // Complete the first agent loop run
2683
- resolveRun(0);
2831
+ await resolveRun(0);
2684
2832
 
2685
2833
  // The turn should still complete (timeout fires) and drain the queue
2686
2834
  // even though commitTurnChanges never resolves.
@@ -2701,7 +2849,7 @@ describe("Regression: cancel semantics and error channel split", () => {
2701
2849
 
2702
2850
  // Complete the second run so the test can clean up
2703
2851
  turnCommitHangForever = false;
2704
- resolveRun(1);
2852
+ await resolveRun(1);
2705
2853
  await new Promise((r) => origSetTimeout(r, 10));
2706
2854
  } finally {
2707
2855
  turnCommitHangForever = false;